[
  {
    "path": ".cargo/config.toml",
    "content": "[build]\nrustflags = [\"-Z\", \"threads=8\"]\n\n[target.'cfg(target_arch = \"wasm32\")']\nrustflags = [\n  \"-Zthreads=8\",\n  \"-Zwasm-c-abi=spec\",\n  \"--cfg=web_sys_unstable_apis\",\n  \"--cfg=getrandom_backend=\\\"wasm_js\\\"\",\n]\n"
  },
  {
    "path": ".config/nextest.toml",
    "content": "[profile.ci]\nfail-fast = false\nslow-timeout = { period = \"30s\", terminate-after = 4 }\ntest-threads = 1\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": ""
  },
  {
    "path": ".gitattributes",
    "content": "*.rs linguist-detectable=true\n*.js linguist-detectable=false\n*.html linguist-detectable=false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/defect-report.md",
    "content": "---\nname: Defect Report\nabout: Report issues to improve TetaNES\ntitle: ''\nlabels: needs-triage bug\nassignees: lukexor\n\n---\n\n## Describe the issue\n\nA clear and concise description of what the defect is.\n\nBe sure to try the suggestions in the\n[Troubleshooting](https://github.com/lukexor/tetanes#troubleshooting) section of\nthe README first.\n\n## To Reproduce\n\nSteps to reproduce the behavior:\n\n1. Load '...'\n1. Press '....'\n1. See error\n\n## Expected behavior\n\nA clear and concise description of what you expected to happen.\n\n## Screenshots, Logs or Artifacts\n\nIf applicable, attach screenshots, logs, configuration files, or save states to\nhelp explain or reproduce the issue. See the\n[Directories](https://github.com/lukexor/tetanes#directories)\nsection for file locations.\n\n## Environment\n\n- ROM title\n  - Please don't attach any download links or ROMs due to copyright laws.\n- Operating System and Version\n  - e.g. Windows 7, macOS Mojave 10.14.6, Ubuntu 22.04\n- Browser Vendor and Version (if using TetaNES Web)\n  - e.g. Chrome 77.0.3865\n- TetaNES Version\n  - Can be found in the `About` menu, e.g. 0.12.1\n\n## Additional context\n\nAdd any other context about the issue here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature Request\nabout: Suggest an idea\ntitle: ''\nlabels: needs-triage enhancement\nassignees: lukexor\n\n---\n\n## Is your feature request related to a problem?\n\nA clear and concise description of what the problem is. e.g. I'm always\nfrustrated when [...].\n\n## Describe the solution you'd like\n\nA clear and concise description of what you want to happen.\n\n## Describe alternatives you've considered\n\nA clear and concise description of any alternative solutions or features you've\nconsidered.\n\n## Additional context\n\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "---\nversion: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    assignees:\n      - \"lukexor\"\n    open-pull-requests-limit: 1\n    groups:\n      ci-dependencies:\n        patterns:\n          - \"*\"\n"
  },
  {
    "path": ".github/workflows/cd.yml",
    "content": "---\nname: CD\n\n# yamllint disable-line rule:truthy\non:\n  release:\n    types: [published]\n  workflow_dispatch:\n    inputs:\n      tag:\n        description: \"Release tag\"\n        required: true\n        type: string\n      os:\n        description: \"Target platform\"\n        required: true\n        type: choice\n        options:\n          - all\n          - linux\n          - macos\n          - windows\n          - web\n\npermissions:\n  contents: write\n\nenv:\n  # Unnecessary for CI and just pollutes cache\n  CARGO_INCREMENTAL: 0\n  # Remove debug symbols, substantially reduces cache size\n  CARGO_PROFILE_DEV_DEBUG: 0\n  CARGO_PROFILE_TEST_DEBUG: 0\n  CARGO_TERM_COLOR: always\n  RUST_BACKTRACE: 1\n\njobs:\n  build-linux:\n    name: Build Linux Artifacts (${{ matrix.target }})\n    if: >\n      ((startsWith(github.event.release.name, 'tetanes')\n        && !startsWith(github.event.release.name, 'tetanes-core'))\n      || (inputs.tag && (inputs.os == 'all' || inputs.os == 'linux')))\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - target: x86_64-unknown-linux-gnu\n          # TODO: aarch64 linux having trouble with docker in CI\n          # - target: aarch64-unknown-linux-gnu\n    defaults:\n      run:\n        shell: bash\n    steps:\n      - uses: actions/checkout@v6\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: nightly\n      - uses: Swatinem/rust-cache@v2\n      - uses: baptiste0928/cargo-install@v3\n        with:\n          crate: cross\n          git: https://github.com/cross-rs/cross\n          commit: 19bc73f\n      - uses: taiki-e/install-action@v2\n        with:\n          tool: cargo-make,cargo-deb,cargo-pgo\n      - run: |\n          sudo apt update\n          sudo apt install -y libudev-dev libasound2-dev libssl-dev libfuse2\n      - if: startsWith(matrix.target, 'x86_64')\n        run: |\n          time cargo make build-artifacts -- --target ${{ matrix.target }}\n      # aarch64 requires cross building\n      - if: startsWith(matrix.target, 'aarch64')\n        run: |\n          export CROSS_CONTAINER_IN_CONTAINER=true\n          time cargo make build-artifacts -- --target ${{ matrix.target }} --cross\n      - if: success()\n        uses: actions/upload-artifact@v7\n        with:\n          name: ${{ matrix.target }}-artifacts\n          path: tetanes/dist/\n  build-macos:\n    name: Build macOS Artifacts (${{ matrix.target }})\n    if: >\n      ((startsWith(github.event.release.name, 'tetanes')\n        && !startsWith(github.event.release.name, 'tetanes-core'))\n      || (inputs.tag && (inputs.os == 'all' || inputs.os == 'macos')))\n    runs-on: macos-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - target: x86_64-apple-darwin\n          - target: aarch64-apple-darwin\n    defaults:\n      run:\n        shell: bash\n    steps:\n      - uses: actions/checkout@v6\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: nightly\n      - uses: Swatinem/rust-cache@v2\n      - uses: taiki-e/setup-cross-toolchain-action@v1\n        with:\n          target: ${{ matrix.target }}\n      - uses: taiki-e/install-action@v2\n        with:\n          tool: cargo-make,cargo-pgo\n      - run: |\n          time cargo make build-artifacts -- --target ${{ matrix.target }}\n      - if: success()\n        uses: actions/upload-artifact@v7\n        with:\n          name: ${{ matrix.target }}-artifacts\n          path: tetanes/dist/\n  build-windows:\n    name: Build Windows Artifacts (${{ matrix.target }})\n    if: >\n      ((startsWith(github.event.release.name, 'tetanes')\n        && !startsWith(github.event.release.name, 'tetanes-core'))\n      || (inputs.tag && (inputs.os == 'all' || inputs.os == 'windows')))\n    runs-on: windows-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - target: x86_64-pc-windows-msvc\n          # TODO: windows aarch64\n          # - target: aarch64-pc-windows-msvc\n    defaults:\n      run:\n        shell: bash\n    steps:\n      - uses: actions/checkout@v6\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: nightly\n      - uses: Swatinem/rust-cache@v2\n      - uses: taiki-e/setup-cross-toolchain-action@v1\n        with:\n          target: ${{ matrix.target }}\n      - uses: taiki-e/install-action@v2\n        with:\n          tool: cargo-make,cargo-wix,cargo-pgo\n      - if: startsWith(matrix.target, 'x86_64')\n        run: |\n          time cargo make build-artifacts -- --target ${{ matrix.target }}\n      - if: success()\n        uses: actions/upload-artifact@v7\n        with:\n          name: ${{ matrix.target }}-artifacts\n          path: tetanes/dist/\n  build-web:\n    name: Build Web Artifacts (wasm32-unknown-unknown)\n    if: >\n      ((startsWith(github.event.release.name, 'tetanes')\n        && !startsWith(github.event.release.name, 'tetanes-core'))\n      || (inputs.tag && (inputs.os == 'all' || inputs.os == 'web')))\n    runs-on: ubuntu-latest\n    defaults:\n      run:\n        shell: bash\n    steps:\n      - uses: actions/checkout@v6\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: nightly\n          targets: wasm32-unknown-unknown\n      - uses: Swatinem/rust-cache@v2\n      - uses: taiki-e/install-action@v2\n        with:\n          tool: cargo-make,trunk\n      - run: |\n          sudo apt update\n          sudo apt install -y libudev-dev libasound2-dev libssl-dev libfuse2\n      - run: |\n          time cargo make build-artifacts -- --target wasm32-unknown-unknown\n      - if: success()\n        uses: actions/upload-artifact@v7\n        with:\n          name: wasm32-unknown-unknown-artifacts\n          path: tetanes/dist/\n  upload-artifacts:\n    name: Attach Release Artifacts\n    runs-on: ubuntu-latest\n    needs: [build-linux, build-macos, build-windows, build-web]\n    if: |\n      always() && contains(needs.*.result, 'success')\n    steps:\n      - uses: actions/download-artifact@v8\n        with:\n          path: artifacts\n          merge-multiple: true\n      - env:\n          GH_TOKEN: ${{ github.token }}\n        run: |\n          gh release upload ${{ github.event.release.tag_name || inputs.tag }} \\\n            artifacts/* --clobber --repo \"${{ github.repository }}\"\n  update-tetanes-web:\n    name: Update TetaNES Web\n    needs: build-web\n    runs-on: ubuntu-latest\n    env:\n      RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          repository: \"lukexor/lukeworks\"\n          token: ${{ secrets.REPOS }}\n      - uses: actions/download-artifact@v8\n        with:\n          path: artifacts\n          pattern: \"wasm32-unknown-unknown-artifacts\"\n      - id: commit\n        run: |\n          rm -f web/public/tetanes-web/*\n          tar -xzf artifacts/*.tar.gz -C web/public/tetanes-web/\n          VERSION=${RELEASE_TAG#\"tetanes-v\"}\n          echo \"version=${VERSION}\" >> \"$GITHUB_OUTPUT\"\n      - uses: stefanzweifel/git-auto-commit-action@v7\n        with:\n          file_pattern: \"web/public/tetanes-web/*\"\n          commit_message: Updated TetaNES Web v${{ steps.commit.outputs.version }}\n  update-homebrew-formula:\n    name: Update Homebrew Formula\n    needs: build-macos\n    runs-on: ubuntu-latest\n    env:\n      RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          repository: \"lukexor/homebrew-formulae\"\n          token: ${{ secrets.REPOS }}\n      - uses: actions/download-artifact@v8\n        with:\n          path: artifacts\n          pattern: \"*-apple-darwin-artifacts\"\n          merge-multiple: true\n      - id: commit\n        run: |\n          x86_64_SHA=$(cat artifacts/*-x86_64-apple-darwin.tar.gz-sha256.txt | awk '{ print $1 }')\n          aarch64_SHA=$(cat artifacts/*-aarch64-apple-darwin.tar.gz-sha256.txt | awk '{ print $1 }')\n          VERSION=${RELEASE_TAG#\"tetanes-v\"}\n          cat tetanes.rb.tmpl | \\\n            sed \"s/%VERSION%/${VERSION}/g\" | \\\n            sed \"s/%x86_64_SHA%/${x86_64_SHA}/g\" | \\\n            sed \"s/%aarch64_SHA%/${aarch64_SHA}/g\" \\\n            > Casks/tetanes.rb\n          echo \"version=${VERSION}\" >> \"$GITHUB_OUTPUT\"\n      - uses: stefanzweifel/git-auto-commit-action@v7\n        with:\n          file_pattern: \"*.rb\"\n          commit_message: Version Bump v${{ steps.commit.outputs.version }}\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "---\nname: CI\n\n# yamllint disable-line rule:truthy\non:\n  push:\n    branches: [main]\n    paths-ignore:\n      - \"**.md\"\n  pull_request:\n    paths-ignore:\n      - \"**.md\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n\nenv:\n  # Unnecessary for CI and just pollutes cache\n  CARGO_INCREMENTAL: 0\n  # Remove debug symbols, substantially reduces cache size\n  CARGO_PROFILE_DEV_DEBUG: 0\n  CARGO_PROFILE_TEST_DEBUG: 0\n  CARGO_TERM_COLOR: always\n  RUST_LOG: debug\n  RUST_BACKTRACE: 1\n\njobs:\n  format:\n    name: Check format\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: nightly\n          targets: wasm32-unknown-unknown\n          components: clippy\n      - uses: Swatinem/rust-cache@v2\n      - run: |\n          time cargo fmt --all --check\n\n  lint-web:\n    name: Lint Web\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: nightly\n          targets: wasm32-unknown-unknown\n          components: clippy\n      - uses: Swatinem/rust-cache@v2\n      - run: |\n          time cargo clippy --locked --lib --bin tetanes --target wasm32-unknown-unknown --all-features --keep-going -- -D warnings\n\n  lint-tetanes:\n    name: Lint TetaNES (${{ matrix.os }})\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [macos-latest, ubuntu-latest, windows-latest]\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: nightly\n          components: clippy\n      - uses: Swatinem/rust-cache@v2\n      - if: startsWith(matrix.os, 'ubuntu')\n        run: |\n          sudo apt update\n          sudo apt install -y libudev-dev libasound2-dev\n      - run: |\n          cargo clippy --locked -p tetanes --all-features --keep-going -- -D warnings\n\n  lint-tetanes-core:\n    name: Lint TetaNES Core (${{ matrix.os }})\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [macos-latest, ubuntu-latest, windows-latest]\n        toolchain: [nightly, stable, 1.85]\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ matrix.toolchain }}\n          components: clippy\n      - uses: Swatinem/rust-cache@v2\n      - env:\n          # Unset nightly RUSTFLAGS so we can lint with non-nightly toolchains\n          CARGO_ENCODED_RUSTFLAGS: \"\"\n        run: |\n          cargo +${{ matrix.toolchain }} clippy --locked -p tetanes-core --all-features --keep-going -- -D warnings\n\n  # No tests currently\n  # test-tetanes:\n  #   name: Test TetaNES\n  #   runs-on: ubuntu-latest\n  #   steps:\n  #     - uses: actions/checkout@v6\n  #       with:\n  #         fetch-depth: 0\n  #     - uses: dtolnay/rust-toolchain@master\n  #       with:\n  #         toolchain: nightly\n  #     - uses: taiki-e/install-action@v2\n  #       with:\n  #         tool: cargo-nextest\n  #     - uses: Swatinem/rust-cache@v2\n  #     - run: |\n  #         sudo apt update\n  #         sudo apt install -y libudev-dev libasound2-dev\n  #     - run: |\n  #         cargo nextest run --locked -p tetanes --all-features --profile ci\n\n  test-tetanes-core:\n    name: Test TetaNES Core\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: nightly\n      - uses: taiki-e/install-action@v2\n        with:\n          tool: cargo-nextest\n      - uses: Swatinem/rust-cache@v2\n      - run: |\n          cargo nextest run --locked -p tetanes-core --all-features --profile ci\n\n  docs-web:\n    name: Docs Web\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: nightly\n          targets: wasm32-unknown-unknown\n      - uses: Swatinem/rust-cache@v2\n      - env:\n          RUSTDOCFLAGS: -D warnings\n        run: |\n          time cargo doc --locked --no-deps --document-private-items --lib --target wasm32-unknown-unknown --all-features --keep-going\n\n  docs-tetanes:\n    name: Docs TetaNES\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [macos-latest, ubuntu-latest, windows-latest]\n    steps:\n      - uses: actions/checkout@v6\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: nightly\n      - uses: Swatinem/rust-cache@v2\n      - if: startsWith(matrix.os, 'ubuntu')\n        run: |\n          sudo apt update\n          sudo apt install -y libudev-dev libasound2-dev\n      - env:\n          RUSTDOCFLAGS: -D warnings\n        run: |\n          cargo doc --locked --no-deps --document-private-items --all-features --workspace --keep-going\n"
  },
  {
    "path": ".github/workflows/outdated.yml",
    "content": "---\nname: Check Outdated\n\n# yamllint disable-line rule:truthy\non:\n  schedule:\n    # At 06:00 on day-of-month 2 and 16\n    - cron: \"0 6 2,16 * *\"\n\npermissions:\n  contents: read\n\nenv:\n  # Unnecessary for CI and just pollutes cache\n  CARGO_INCREMENTAL: 0\n  # Remove debug symbols, substantially reduces cache size\n  CARGO_PROFILE_DEV_DEBUG: 0\n  CARGO_PROFILE_TEST_DEBUG: 0\n  CARGO_TERM_COLOR: always\n  RUST_BACKTRACE: 1\n\njobs:\n  outdated:\n    name: Check Outdated\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - uses: dtolnay/install@cargo-outdated\n      - run: |\n          # gilrs and sysinfo currently conflict over objc2-core-foundation\n          # accesskit is blocked by egui upgrading it\n          # criterion is blocked by pprof upgrading it\n          cargo outdated -e -d 1 --exit-code 1 \\\n            --ignore gilrs \\\n            --ignore sysinfo \\\n            --ignore accesskit \\\n            --ignore accesskit_winit \\\n            --ignore criterion\n"
  },
  {
    "path": ".github/workflows/release-pr.yml",
    "content": "---\nname: Release PR\n\n# yamllint disable-line rule:truthy\non:\n  push:\n    branches: [main]\n\npermissions:\n  pull-requests: write\n  contents: write\n  id-token: write\n\nenv:\n  # Unnecessary for CI and just pollutes cache\n  CARGO_INCREMENTAL: 0\n  # Remove debug symbols, substantially reduces cache size\n  CARGO_PROFILE_DEV_DEBUG: 0\n  CARGO_PROFILE_TEST_DEBUG: 0\n  CARGO_TERM_COLOR: always\n  RUST_BACKTRACE: 1\n\njobs:\n  release-pr:\n    name: Release PR\n    runs-on: ubuntu-latest\n    if: ${{ github.repository_owner == 'lukexor' }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n          # Required to trigger post-release workflows\n          token: ${{ secrets.RELEASE_PLZ_TOKEN }}\n      - uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: nightly\n      - uses: Swatinem/rust-cache@v2\n      - run: |\n          sudo apt update\n          sudo apt install -y libudev-dev libasound2-dev\n      - uses: taiki-e/install-action@v2\n        with:\n          tool: cargo-nextest\n      - uses: Swatinem/rust-cache@v2\n      - run: |\n          cargo nextest run --locked -p tetanes-core --all-features --profile ci\n      - name: Run release-plz\n        uses: MarcoIeni/release-plz-action@v0.5\n        env:\n          # Required to trigger post-release workflows\n          GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/security.yml",
    "content": "---\nname: Security Audit\n\n# yamllint disable-line rule:truthy\non:\n  schedule:\n    # At 06:00 once a week on Sunday\n    - cron: \"0 6 * * 0\"\n  push:\n    branches: [main]\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  audit:\n    name: Security Audit\n    runs-on: ubuntu-22.04\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n      - uses: EmbarkStudios/cargo-deny-action@v2\n"
  },
  {
    "path": ".github/workflows/triage.yml",
    "content": "---\nname: Triage Issues\n\n# yamllint disable-line rule:truthy\non:\n  issues:\n    types: [opened, reopened]\n\npermissions:\n  issues: write\n\njobs:\n  triage:\n    name: Triage Issue\n    runs-on: ubuntu-latest\n    steps:\n      - name: add needs-triage label\n        uses: andymckay/labeler@master\n        with:\n          add-labels: \"needs-triage\"\n          ignore-if-labeled: true\n"
  },
  {
    "path": ".gitignore",
    "content": "tmp/\ntest_results*\ntarget/\n.DS_Store\n!assets/macos/.DS_Store\nlogs/\ndist/\n.direnv\nperf.*\n"
  },
  {
    "path": ".gitmodules",
    "content": ""
  },
  {
    "path": ".prettierignore",
    "content": "*.wxs\n"
  },
  {
    "path": ".rgignore",
    "content": "roms\ntest_roms\ndocs\n"
  },
  {
    "path": "Cargo.toml",
    "content": "# Disabled for now since it was ICEing\n# cargo-features = [\"codegen-backend\"]\n\n[workspace]\nresolver = \"2\"\nmembers = [\"tetanes\", \"tetanes-core\", \"tetanes-utils\"]\n\n[workspace.package]\nversion = \"0.14.1\"\nedition = \"2024\"\nlicense = \"MIT OR Apache-2.0\"\nauthors = [\"Luke Petherbridge <me@lukeworks.tech>\"]\nreadme = \"README.md\"\nrepository = \"https://github.com/lukexor/tetanes.git\"\nhomepage = \"https://lukeworks.tech/tetanes\"\n\n[workspace.lints.clippy]\nall = { level = \"warn\", priority = -1 }\nmissing_const_for_fn = \"warn\"\nprint_literal = \"warn\"\n\n[workspace.lints.rust]\nfuture_incompatible = \"warn\"\nnonstandard_style = \"warn\"\nrust_2018_compatibility = \"warn\"\nrust_2018_idioms = \"warn\"\nrust_2021_compatibility = \"warn\"\nunused = \"warn\"\n\n[workspace.dependencies]\nanyhow = \"1.0\"\nbincode = { version = \"2.0\", default-features = false, features = [\n  \"std\",\n  \"serde\",\n] }\ncfg-if = \"1.0\"\nclap = { version = \"4.5\", default-features = false, features = [\n  \"std\",\n  \"help\",\n  \"usage\",\n  \"suggestions\",\n  \"derive\",\n] }\ndirs = \"6.0\"\nimage = { version = \"0.25\", default-features = false, features = [\"png\"] }\nserde = { version = \"1.0\", features = [\"derive\"] }\ntetanes-core = { version = \"0.14\", path = \"tetanes-core\" }\nthiserror = \"2.0\"\ntracing = { version = \"0.1\", default-features = false, features = [\n  \"std\",\n  \"release_max_level_info\",\n] }\ntracing-subscriber = { version = \"0.3\", default-features = false, features = [\n  \"ansi\",\n  \"std\",\n  \"registry\",\n  \"parking_lot\",\n] }\nserde_json = \"1.0\"\nweb-time = \"1.0\"\nweb-sys = \"0.3.95\"\n\n[profile.dev]\n# `debug = false` Saves both compile time and WASM download times\ndebug = false\n# Playable framerates in development\nopt-level = 1\n# Higher opt-level for deps speeds up incremental compile times and can improve\n# runtime performance\n[profile.dev.package.\"*\"]\nopt-level = 3\n[profile.dev.build-override]\nopt-level = 3\n\n# TODO: Would be nice to move lto to `dist` but Trunk doesn't support profiles yet\n# See: https://github.com/trunk-rs/trunk/issues/605\n#      https://github.com/trunk-rs/trunk/issues/933\n[profile.release]\ncodegen-units = 1\nlto = true\n# See: https://smallcultfollowing.com/babysteps/blog/2024/05/02/unwind-considered-harmful/\npanic = \"abort\"\nstrip = true\n\n# Profile to run performance profiling with debug symbols\n[profile.perf]\ninherits = \"release\"\nlto = \"off\"\ndebug = true\nstrip = false\n\n[workspace.metadata.wix]\nupgrade-guid = \"DB76CEB0-15B8-4727-9C3E-55819AB5E7B9\"\npath-guid = \"5731AE63-80DE-4CD7-ADFA-9E79BEDCE08B\"\nlicense = false\neula = false\n"
  },
  {
    "path": "Cross.toml",
    "content": "[build]\npre-build = [\n  \"dpkg --add-architecture $CROSS_DEB_ARCH\",\n  \"\"\"apt update && apt install -y \\\n      libudev-dev:$CROSS_DEB_ARCH \\\n      libssl-dev:$CROSS_DEB_ARCH \\\n      libasound2-dev:$CROSS_DEB_ARCH\n  \"\"\",\n]\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "                              Apache License\n                        Version 2.0, January 2004\n                     https://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n   \"License\" shall mean the terms and conditions for use, reproduction,\n   and distribution as defined by Sections 1 through 9 of this document.\n\n   \"Licensor\" shall mean the copyright owner or entity authorized by\n   the copyright owner that is granting the License.\n\n   \"Legal Entity\" shall mean the union of the acting entity and all\n   other entities that control, are controlled by, or are under common\n   control with that entity. For the purposes of this definition,\n   \"control\" means (i) the power, direct or indirect, to cause the\n   direction or management of such entity, whether by contract or\n   otherwise, or (ii) ownership of fifty percent (50%) or more of the\n   outstanding shares, or (iii) beneficial ownership of such entity.\n\n   \"You\" (or \"Your\") shall mean an individual or Legal Entity\n   exercising permissions granted by this License.\n\n   \"Source\" form shall mean the preferred form for making modifications,\n   including but not limited to software source code, documentation\n   source, and configuration files.\n\n   \"Object\" form shall mean any form resulting from mechanical\n   transformation or translation of a Source form, including but\n   not limited to compiled object code, generated documentation,\n   and conversions to other media types.\n\n   \"Work\" shall mean the work of authorship, whether in Source or\n   Object form, made available under the License, as indicated by a\n   copyright notice that is included in or attached to the work\n   (an example is provided in the Appendix below).\n\n   \"Derivative Works\" shall mean any work, whether in Source or Object\n   form, that is based on (or derived from) the Work and for which the\n   editorial revisions, annotations, elaborations, or other modifications\n   represent, as a whole, an original work of authorship. For the purposes\n   of this License, Derivative Works shall not include works that remain\n   separable from, or merely link (or bind by name) to the interfaces of,\n   the Work and Derivative Works thereof.\n\n   \"Contribution\" shall mean any work of authorship, including\n   the original version of the Work and any modifications or additions\n   to that Work or Derivative Works thereof, that is intentionally\n   submitted to Licensor for inclusion in the Work by the copyright owner\n   or by an individual or Legal Entity authorized to submit on behalf of\n   the copyright owner. For the purposes of this definition, \"submitted\"\n   means any form of electronic, verbal, or written communication sent\n   to the Licensor or its representatives, including but not limited to\n   communication on electronic mailing lists, source code control systems,\n   and issue tracking systems that are managed by, or on behalf of, the\n   Licensor for the purpose of discussing and improving the Work, but\n   excluding communication that is conspicuously marked or otherwise\n   designated in writing by the copyright owner as \"Not a Contribution.\"\n\n   \"Contributor\" shall mean Licensor and any individual or Legal Entity\n   on behalf of whom a Contribution has been received by Licensor and\n   subsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   copyright license to reproduce, prepare Derivative Works of,\n   publicly display, publicly perform, sublicense, and distribute the\n   Work and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\n   this License, each Contributor hereby grants to You a perpetual,\n   worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n   (except as stated in this section) patent license to make, have made,\n   use, offer to sell, sell, import, and otherwise transfer the Work,\n   where such license applies only to those patent claims licensable\n   by such Contributor that are necessarily infringed by their\n   Contribution(s) alone or by combination of their Contribution(s)\n   with the Work to which such Contribution(s) was submitted. If You\n   institute patent litigation against any entity (including a\n   cross-claim or counterclaim in a lawsuit) alleging that the Work\n   or a Contribution incorporated within the Work constitutes direct\n   or contributory patent infringement, then any patent licenses\n   granted to You under this License for that Work shall terminate\n   as of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\n   Work or Derivative Works thereof in any medium, with or without\n   modifications, and in Source or Object form, provided that You\n   meet the following conditions:\n\n   (a) You must give any other recipients of the Work or\n       Derivative Works a copy of this License; and\n\n   (b) You must cause any modified files to carry prominent notices\n       stating that You changed the files; and\n\n   (c) You must retain, in the Source form of any Derivative Works\n       that You distribute, all copyright, patent, trademark, and\n       attribution notices from the Source form of the Work,\n       excluding those notices that do not pertain to any part of\n       the Derivative Works; and\n\n   (d) If the Work includes a \"NOTICE\" text file as part of its\n       distribution, then any Derivative Works that You distribute must\n       include a readable copy of the attribution notices contained\n       within such NOTICE file, excluding those notices that do not\n       pertain to any part of the Derivative Works, in at least one\n       of the following places: within a NOTICE text file distributed\n       as part of the Derivative Works; within the Source form or\n       documentation, if provided along with the Derivative Works; or,\n       within a display generated by the Derivative Works, if and\n       wherever such third-party notices normally appear. The contents\n       of the NOTICE file are for informational purposes only and\n       do not modify the License. You may add Your own attribution\n       notices within Derivative Works that You distribute, alongside\n       or as an addendum to the NOTICE text from the Work, provided\n       that such additional attribution notices cannot be construed\n       as modifying the License.\n\n   You may add Your own copyright statement to Your modifications and\n   may provide additional or different license terms and conditions\n   for use, reproduction, or distribution of Your modifications, or\n   for any such Derivative Works as a whole, provided Your use,\n   reproduction, and distribution of the Work otherwise complies with\n   the conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\n   any Contribution intentionally submitted for inclusion in the Work\n   by You to the Licensor shall be under the terms and conditions of\n   this License, without any additional terms or conditions.\n   Notwithstanding the above, nothing herein shall supersede or modify\n   the terms of any separate license agreement you may have executed\n   with Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\n   names, trademarks, service marks, or product names of the Licensor,\n   except as required for reasonable and customary use in describing the\n   origin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\n   agreed to in writing, Licensor provides the Work (and each\n   Contributor provides its Contributions) on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n   implied, including, without limitation, any warranties or conditions\n   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n   PARTICULAR PURPOSE. You are solely responsible for determining the\n   appropriateness of using or redistributing the Work and assume any\n   risks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\n   whether in tort (including negligence), contract, or otherwise,\n   unless required by applicable law (such as deliberate and grossly\n   negligent acts) or agreed to in writing, shall any Contributor be\n   liable to You for damages, including any direct, indirect, special,\n   incidental, or consequential damages of any character arising as a\n   result of this License or out of the use or inability to use the\n   Work (including but not limited to damages for loss of goodwill,\n   work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses), even if such Contributor\n   has been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\n   the Work or Derivative Works thereof, You may choose to offer,\n   and charge a fee for, acceptance of support, warranty, indemnity,\n   or other liability obligations and/or rights consistent with this\n   License. However, in accepting such obligations, You may act only\n   on Your own behalf and on Your sole responsibility, not on behalf\n   of any other Contributor, and only if You agree to indemnify,\n   defend, and hold each Contributor harmless for any liability\n   incurred by, or claims asserted against, such Contributor by reason\n   of your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work.\n\n   To apply the Apache License to your work, attach the following\n   boilerplate notice, with the fields enclosed by brackets \"[]\"\n   replaced with your own identifying information. (Don't include\n   the brackets!)  The text should be enclosed in the appropriate\n   comment syntax for the file format. We also recommend that a\n   file or class name and description of purpose be included on the\n   same \"printed page\" as the copyright notice for easier\n   identification within third-party archives.\n\nCopyright [yyyy] [name of copyright owner]\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n\thttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "MIT License\n\nCopyright (c) 2021 Luke Petherbridge <me@lukeworks.tech>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile.toml",
    "content": "[env]\nCARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true\n\n[config]\nreduce_output = false\nskip_core_tasks = true\ndefault_to_workspace = false\n\n[tasks.default]\nalias = \"run\"\n\n[tasks.version]\ndescription = \"Print the crate version\"\ncategory = \"Tools\"\nscript = [\"echo Version: ${CARGO_MAKE_PROJECT_VERSION}\"]\n\n[tasks.clean]\ndescription = \"Clean up build artifacts\"\ncategory = \"Development\"\ncommand = \"cargo\"\nargs = [\"clean\"]\n\n[tasks.check-fmt]\ndescription = \"Check format\"\ncategory = \"Development\"\ncommand = \"cargo\"\nargs = [\"fmt\", \"--all\", \"--check\"]\n\n[tasks.lint-web]\ndescription = \"Lint TetaNES Web\"\ncategory = \"Development\"\ncommand = \"cargo\"\nargs = [\"clippy\", \"--locked\", \"--lib\", \"--bin\", \"tetanes\", \"--target\", \"wasm32-unknown-unknown\", \"--all-features\", \"--keep-going\"]\ndependencies = [\"add-wasm-target\"]\n\n[tasks.lint]\ndescription = \"Lint TetaNES\"\ncategory = \"Development\"\ncommand = \"cargo\"\nargs = [\"clippy\", \"--locked\", \"--all-features\", \"--keep-going\"]\ndependencies = [\"lint-web\"]\n\n[tasks.pgo-profile]\ndescription = \"Run benchmark to generate PGO profile\"\ncategory = \"Build\"\ncommand = \"cargo\"\nargs = [\"pgo\", \"bench\", \"--\", \"--bench\", \"clock_frame\"]\n\n[tasks.pgo-build]\ndescription = \"Optimize TetaNES with PGO\"\ncategory = \"Build\"\ncommand = \"cargo\"\nargs = [\"pgo\", \"optimize\", \"build\", \"--\", \"-p\", \"tetanes\", \"${@}\"]\n\n[tasks.build]\ndescription = \"Build TetaNES with PGO\"\ncategory = \"Build\"\ndependencies = [\"pgo-profile\", \"pgo-build\"]\n\n[tasks.build-artifacts]\ndescription = \"Build TetaNES Artifacts for a given target_arch\"\ncategory = \"Build\"\ncommand = \"cargo\"\nargs = [\"run\", \"--bin\", \"build_artifacts\", \"${@}\"]\n\n[tasks.build-cross]\ndescription = \"Cross-Build TetaNES for a given target_arch\"\ncategory = \"Build\"\ncommand = \"cross\"\nargs = [\"build\", \"-p\", \"tetanes\", \"${@}\"]\n\n[tasks.bench]\ndescription = \"Benchmark TetaNES\"\ncategory = \"Development\"\ncommand = \"perf\"\nargs = [\n  \"stat\", \"-e\",\n  \"cycles,instructions,cache-misses,cache-references,branch-misses,branches,L1-dcache-load-misses,L1-dcache-loads\",\n  \"taskset\", \"-c\", \"0\", \"cargo\", \"bench\", \"--profile\", \"perf\", \"--bench\", \"clock_frame\", \"${@}\"\n]\n\n[tasks.test]\ndescription = \"Test TetaNES\"\ncategory = \"Development\"\ncommand = \"cargo\"\nargs = [\"nextest\", \"run\", \"--locked\", \"--all-features\", \"--no-fail-fast\", \"${@}\"]\n\n[tasks.run]\ndescription = \"Run TetaNES in release mode\"\ncategory = \"Development\"\ncommand = \"cargo\"\nargs = [\"run\", \"-p\", \"tetanes\", \"--release\", \"${@}\"]\n\n[tasks.profile]\ndescription = \"Run TetaNES in release mode w/profiling\"\ncategory = \"Development\"\ncommand = \"cargo\"\nargs = [\"run\", \"-p\", \"tetanes\", \"--release\", \"--features\", \"profiling\", \"${@}\"]\n\n[tasks.dev]\ndescription = \"Run TetaNES in development mode\"\ncategory = \"Development\"\ncommand = \"cargo\"\nargs = [\"run\", \"-p\", \"tetanes\", \"${@}\"]\n\n[tasks.add-wasm-target]\ndescription = \"Add wasm target\"\ncategory = \"Development\"\ncommand = \"rustup\"\nargs = [\"target\", \"add\", \"wasm32-unknown-unknown\"]\n\n[tasks.build-web]\ndescription = \"Build TetaNES Web\"\ncategory = \"Build\"\ncommand = \"trunk\"\nargs = [\"build\", \"--config\", \"tetanes/Cargo.toml\", \"--release\", \"--dist\", \"dist/web\", \"--public-url\", \"./\"]\ndependencies = [\"add-wasm-target\"]\n\n[tasks.docs-web]\ndescription = \"Document TetaNES Web\"\ncategory = \"Documentation\"\ncommand = \"cargo\"\nargs = [\"doc\", \"--locked\", \"--no-deps\", \"--document-private-items\", \"--lib\", \"--target\", \"wasm32-unknown-unknown\", \"--all-features\", \"--keep-going\"]\ndependencies = [\"add-wasm-target\"]\n\n[tasks.docs]\ndescription = \"Document TetaNES\"\ncategory = \"Documentation\"\ncommand = \"cargo\"\nargs = [\"doc\", \"--locked\", \"--no-deps\", \"--document-private-items\", \"--all-features\", \"--workspace\", \"--keep-going\"]\ndependencies = [\"docs-web\"]\n\n[tasks.run-web]\ndescription = \"Run TetaNES Web in release mode\"\ncategory = \"Development\"\ncommand = \"trunk\"\nargs = [\"serve\", \"--release\", \"--config\", \"tetanes/Cargo.toml\", \"--address\", \"0.0.0.0\"]\ndependencies = [\"add-wasm-target\"]\n\n[tasks.profile-web]\ndescription = \"Run TetaNES Web in release mode w/profiling\"\ncategory = \"Development\"\ncommand = \"trunk\"\nargs = [\"serve\", \"--release\", \"--features\", \"profiling\", \"--config\", \"tetanes/Cargo.toml\", \"--address\", \"0.0.0.0\"]\ndependencies = [\"add-wasm-target\"]\n\n[tasks.dev-web]\ndescription = \"Run TetaNES Web in development mode\"\ncategory = \"Development\"\ncommand = \"trunk\"\nargs = [\"serve\", \"--config\", \"tetanes/Cargo.toml\", \"--address\", \"0.0.0.0\"]\ndependencies = [\"add-wasm-target\"]\n"
  },
  {
    "path": "README.md",
    "content": "<!-- markdownlint-disable no-inline-html no-duplicate-heading -->\n\n# TetaNES\n\n[![Build Status]][build] [![Doc Status]][docs] [![Latest Version]][crates.io]\n[![Downloads]][crates.io] [![License]][gnu]\n\n[build status]: https://img.shields.io/github/actions/workflow/status/lukexor/tetanes/ci.yml?branch=main\n[build]: https://github.com/lukexor/tetanes/actions/workflows/ci.yml\n[doc status]: https://img.shields.io/docsrs/tetanes?style=plastic\n[docs]: https://docs.rs/tetanes/\n[latest version]: https://img.shields.io/crates/v/tetanes?style=plastic\n[crates.io]: https://crates.io/crates/tetanes\n[downloads]: https://img.shields.io/crates/d/tetanes?style=plastic\n[license]: https://img.shields.io/crates/l/tetanes?style=plastic\n[gnu]: https://github.com/lukexor/tetanes/blob/main/LICENSE-MIT\n\n<!-- markdownlint-disable line-length -->\n📖 [Summary](#summary) - ✨ [Features](#features) - 🌆 [Screenshots](#screenshots) - 🚀 [Getting\nStarted](#getting-started) - 🛠️ [Roadmap](#roadmap) - ⚠️ [Known\nIssues](#known-issues) - 💬 [Contact](#contact)\n<!-- markdownlint-enable line-length -->\n\n## Summary\n\n<img width=\"100%\" alt=\"TetaNES\"\n  src=\"https://raw.githubusercontent.com/lukexor/tetanes/main/static/tetanes.png\">\n\n> photo credit for background: [Zsolt Palatinus](https://unsplash.com/@sunitalap)\n> on [unsplash](https://unsplash.com/photos/pEK3AbP8wa4)\n\n`TetaNES` is a cross-platform emulator for the Nintendo Entertainment System\n(NES) released in Japan in 1983 and North America in 1986, written in\n[Rust][] using [wgpu][]. It runs on Linux, macOS, Windows, and in a web browser\nwith [Web Assembly][].\n\nIt started as a personal curiosity that turned into a passion project. It is\nstill being actively developed with new features and improvements constantly\nbeing added. It is a fairly accurate emulator that can play most NES titles.\n\n`TetaNES` is also meant to showcase using Rust's performance, memory safety, and\nfearless concurrency features in a large project. Features used in this project\ninclude complex enums, traits, generics, matching, iterators, channels, and\nthreads.\n\nTry it out in your [browser](https://lukeworks.tech/tetanes-web)!\n\n## Features\n\n- Runs on Linux, macOS, Windows, and Web.\n- Standalone emulation core in `tetanes-core`.\n- NTSC, PAL and Dendy emulation.\n- Headless Mode when using `tetanes-core`.\n- Pixellate and NTSC filters.\n- CRT shader for that retro feel.\n- Up to 4 players with gamepad support.\n- Zapper (Light Gun) support using the mouse.\n- iNES and NES 2.0 ROM header formats supported.\n- Over 30 supported mappers covering >90% of licensed games.\n- Game Genie Codes.\n- PPU Debugger\n- Runtime performance stats\n- Preference snd keybonding menus using [egui](https://egui.rs).\n  - Increase/Decrease speed & Fast Forward\n  - Visual & Instant Rewind\n  - Save & Load States\n  - Battery-backed RAM saves\n  - Screenshots\n  - Gameplay recording and playback\n  - Audio recording\n\n## Screenshots\n\n<img width=\"48%\" alt=\"Donkey Kong\"\n  src=\"https://raw.githubusercontent.com/lukexor/tetanes/main/static/donkey_kong.png\">&nbsp;&nbsp;<img\n  width=\"48%\" alt=\"Super Mario Bros.\"\n  src=\"https://raw.githubusercontent.com/lukexor/tetanes/main/static/super_mario_bros.png\">\n<img width=\"48%\" alt=\"The Legend of Zelda\"\n  src=\"https://raw.githubusercontent.com/lukexor/tetanes/main/static/legend_of_zelda.png\">&nbsp;&nbsp;<img\n  width=\"48%\" alt=\"Metroid\"\n  src=\"https://raw.githubusercontent.com/lukexor/tetanes/main/static/metroid.png\">\n\n## TetaNES Core\n\nTetaNES is split into two crates. This is the primary crate, which provides the\ncross-platform emulator UI\nbinary. [tetanes-core](https://crates.io/crates/tetanes_core) is the emulation\nlibrary that emulator developers can use to develop custom emulator applications\nwith. `tetanes-core` is aimed to have stronger stability guarantees, but it's\nstill not `1.0` yet and there are several large features on the roadmap that may\nresult in breaking changes.\n\n## Stability\n\nPreferences and save file formats are fairly stable at this point, but since\nTetaNES is not yet `1.0` and there are several large features on the roadmap\nthat may result in breaking changes which may result in being unable to restore\nyour preferences or save files.\n\nOnce some of these larger features are completed, and `1.0` is released, more\neffort will be dedicatged to versioning these files for backward compatibility\nin the event of future breaking changes.\n\n## Getting Started\n\n`TetaNES` runs on all major operating systems (Linux, macOS, Windows, and the\nweb).\n\n### Install\n\nThere are multiple options for installation, depending on your operating system,\npreference and existing tooling.\n\n#### Linux\n\n##### Ubuntu/Debian\n\nA `.deb` package is provided under `Assets` on the latest [Release][]. Once\ndownloaded, you can install it and the required dependencies. e.g.\n\n```sh\nsudo apt install ./tetanes-0.10.0-1-amd64.deb\n```\n\n##### Other Distros\n\nAn [AppImage](https://appimage.org/) is provided  under `Assets` on the latest\n[Release][]. Simply download it and put it wherever you want.\n\nA `.tar.gz` package is also provided under `Assets` on the latest\n[Release][]. You can place the `tetanes` binary anywhere in your `PATH`.\n\nThe following dependencies are required to be installed:\n\n- ALSA Shared Library\n- GTK3\n\ne.g.\n\n`apt install libasound2 libgtk-3-0`\n\n`dnf install alsa-lib gtk3`\n\n`pacman -Sy alsa-lib gtk3`\n\n#### MacOS\n\n##### App Bundle\n\nThe easiest is to download the correct app bundle for your processor. The `.dmg`\ndownloads can be found under the `Assets` section of the latest\n[Release][].\n\n##### Homebrew\n\n`TetaNES` can also be installed through [Homebrew](https://brew.sh/).\n\n```sh\nbrew install lukexor/formulae/tetanes\n```\n\n#### Windows\n\nA windows installer is provided under `Assets` on the latest [Release][]. Note:\nYou will need the latest [\"Microsoft Visual C++ 2015 - 2022\nRedistributable\"](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-microsoft-visual-c-redistributable-version)\ninstalled, otherwise you'll get an error about `vcruntime140.dll` not being found.\n\n#### Cargo Install\n\nYou can also build and install with `cargo` which comes with [rustup](https://www.rust-lang.org/tools/install).\n\n```sh\ncargo install tetanes\n```\n\nThis will install the latest released version of the `TetaNES` binary to your\n`cargo` bin directory located at either `$HOME/.cargo/bin/` on a Unix-like\nplatform or `%USERPROFILE%\\.cargo\\bin` on Windows.\n\nAlternatively, if you have [`cargo binstall`](https://crates.io/crates/cargo-binstall/)\ninstalled:\n\n```sh\ncargo binstall tetanes\n```\n\nThis will try to find the target binary for your platform from the latest\n[Release][] or install from source, similar to above.\n\n#### Web\n\nYou can also play directly in your web browser without installing by visiting\n<https://lukeworks.tech/tetanes-web>.\n\n### Usage\n\n```text\nUsage: tetanes [OPTIONS] [PATH]\n\nArguments:\n  [PATH]  The NES ROM to load or a directory containing `.nes` ROM files.\n  [default: current directory]\n\nOptions:\n      --rewind                     Enable rewinding\n  -s, --silent                     Silence audio\n  -f, --fullscreen                 Start fullscreen\n  -4, --four-player <FOUR_PLAYER>  Set four player adapter. [default: 'disabled']\n                                   [possible values: disabled, four-score, satellite]\n  -z, --zapper                     Enable zapper gun\n      --no-threaded                Disable multi-threaded\n  -m, --ram-state <RAM_STATE>      Choose power-up RAM state. [default: \"all-zeros\"]\n                                   [possible values: all-zeros, all-ones, random]\n  -w, --emulate-ppu-warmup         Whether to emulate PPU warmup where writes to\n                                   certain registers are ignored. Can result in\n                                   some games not working correctly\n  -r, --region <REGION>            Choose default NES region. [default: \"ntsc\"]\n                                   [possible values: ntsc, pal, dendy]\n  -i, --save-slot <SAVE_SLOT>      Save slot. [default: 1]\n      --no-load                    Don't load save state on start\n      --no-save                    Don't auto save state or save on exit\n  -x, --speed <SPEED>              Emulation speed. [default: 1.0]\n  -g, --genie-code <GENIE_CODE>    Add Game Genie Code(s). e.g. `AATOZE`\n                                   (Start Super Mario Bros. with 9 lives)\n      --config <CONFIG>            Custom Config path\n  -c, --clean                      \"Default Config\" (skip user config and previous\n                                   save states)\n  -d, --debug                      Start with debugger open\n  -h, --help                       Print help\n  -V, --version                    Print version\n```\n\n[iNES][] and [NES 2.0][] formatted ROMS are supported, though some advanced `NES\n2.0` features may not be implemented.\n\n[ines]: https://wiki.nesdev.org/w/index.php/INES\n[nes 2.0]: https://wiki.nesdev.org/w/index.php/NES_2.0\n\n### Supported Mappers\n\nSupport for the following mappers is currently implemented or in development:\n\n<!-- markdownlint-disable line-length -->\n\n| #   | Name                  | Example Games                              | # of Games<sup>1</sup> | % of Games<sup>1</sup> |\n| --- | --------------------- | ------------------------------------------ | ---------------------- | ---------------------- |\n| 000 | NROM                  | Bomberman, Donkey Kong, Super Mario Bros.  | ~247                   | ~10%                   |\n| 001 | SxROM/MMC1B/C         | Metroid, Legend of Zelda, Tetris           | ~680                   | ~28%                   |\n| 002 | UxROM                 | Castlevania, Contra, Mega Man              | ~270                   | ~11%                   |\n| 003 | CNROM                 | Arkanoid, Paperboy, Pipe Dream             | ~155                   | ~6%                    |\n| 004 | TxROM/MMC3/MMC6       | Kirby's Adventure, Super Mario Bros. 2/3   | ~599                   | ~24%                   |\n| 005 | ExROM/MMC5            | Castlevania 3, Laser Invasion              | ~24                    | &lt;0.01%              |\n| 007 | AxROM                 | Battletoads, Marble Madness                | ~75                    | ~3%                    |\n| 009 | PxROM/MMC2            | Punch Out!!                                | 1                      | &lt;0.01%              |\n| 010 | FxROM/MMC4            | Fire Emblem Gaiden                         | 3                      | &lt;0.01%              |\n| 011 | Color Dreams          | Crystal Mines, Metal Fighter               | 15                     | ~1%                    |\n| 016 | Bandai FCG            | Dragon Ball: Daimaou Fukkatsu              | 14                     | ~1%                    |\n| 018 | Jaleco SS 88006       | Magic John                                 | 15                     | ~1%                    |\n| 019 | Namco163              | Battle Fleet, Dragon Ninja                 | 20                     | ~1%                    |\n| 024 | VRC6a                 | Akumajou Densetsu                          | 1                      | &lt;0.01%              |\n| 026 | VRC6b                 | Madara, Esper Dream 2                      | 2                      | &lt;0.01%              |\n| 034 | BNROM/NINA-001        | Deadly Towers, Impossible Mission II       | 3                      | &lt;0.01%              |\n| 066 | GxROM/MxROM           | Super Mario Bros. + Duck Hunt              | ~17                    | &lt;0.01%              |\n| 069 | Sunsoft/FME-7         | Batman: Return of the Joker, Gimmick!      | ~15                    | &lt;0.01%              |\n| 071 | Camerica/Codemasters  | Firehawk, Bee 52, MiG 29 - Soviet Fighter  | ~15                    | &lt;0.01%              |\n| 076 | DxROM/Namco 108       | Megami Tensei: Digital Devil Story         | 1                      | &lt;0.01%              |\n| 079 | NINA-003/006          | Black Jack, Double Strike                  | 16                     | &lt;0.01%              |\n| 088 | DxROM/Namco 108       | Quinty, Dragon Spirit - Aratanaru Densetsu | 3                      | &lt;0.01%              |\n| 095 | DxROM/Namco 108       | Dragon Buster                              | 1                      | &lt;0.01%              |\n| 113 | NINA-003/006          | HES 6-in-1, Total Funpak                   | ~3                     | &lt;0.01%              |\n| 144 | Color Dreams          | Death Race                                 | 1                      | &lt;0.01%              |\n| 146 | NINA-003/006          | Galactic Crusader                          | 1                      | &lt;0.01%              |\n| 153 | Bandai FCG            | Famicom Jump II: Saikyou no 7-nin          | 1                      | &lt;0.01%              |\n| 154 | DxROM/Namco 108       | Devil Man                                  | 1                      | &lt;0.01%              |\n| 157 | Bandai FCG/Datach     | SD Gundam Wars                             | 7                      | &lt;0.01%              |\n| 155 | SxROM/MMC1A           | Tatakae!! Ramen Man: Sakuretsu Choujin     | 2                      | &lt;0.01%              |\n| 159 | Bandai FCG            | Dragon Ball Z: Kyoushuu! Saiya-jin         | 4                      | &lt;0.01%              |\n| 206 | DxROM/Namco 108       | Fantasy Zone, Gauntlet                     | 45                     | ~2%                    |\n| 210 | Namco175/340          | Dream Master, Family Circuit '91           | 4                      | &lt;0.01%              |\n|     |                       |                                            | ~2256 / 2447           | ~92.2%                 |\n\n<!-- markdownlint-enable line-length -->\n\n1. [Source](http://bootgod.dyndns.org:7777/stats.php?page=6) [Mirror](https://nescartdb.com/)\n\n### Controls\n\nKeybindings can be customized in the keybindings menu. Below are the defaults.\n\nNES joypad:\n\n| Button    | Keyboard (Player 1) | Controller       |\n| --------- | ------------------- | ---------------- |\n| A         | Z                   | East             |\n| B         | X                   | South            |\n| A (Turbo) | A                   | North            |\n| B (Turbo) | S                   | West             |\n| Select    | Q                   | Select           |\n| Start     | W                   | Start            |\n| D-Pad     | Arrow Keys          | D-Pad            |\n\nController Layout:\n\nSDL-compatible mappings are used:\n<https://github.com/mdqinc/SDL_GameControllerDB?tab=readme-ov-file> but can be\noverriden by setting `SDL_GAMECONTROLLERCONFIG`.\n\n```text\n           Left Triggers                        Right Triggers\n              _=====_                               _=====_\n             / _____ \\                             / _____ \\\n           +.-'_____'-.---------------------------.-'     '-.+\n          /   |     |  '.                       .'            \\\n         / ___| /|\\ |___ \\                     /      (N)      \\\n        / |      |      | ;    _         _    ;                 ;  Action Pad\n D-Pad  | | <---   ---> | |  <:_|       |_:>  |  (W)       (E)  |  (South, East,\n        | |___   |   ___| ;  Select    Start  ;                 ;   North, West)\n        |\\    | \\|/ |    /  _              _   \\      (S)      /|\n        | \\   |_____|  .','\" \"',        ,'\" \"', '.           .' |\n        |  '-.______.-' / Left  \\------/ Right \\  '-._____.-'   |\n        |              /\\ Stick /      \\ Stick /\\               |\n        |             /  '.___.'        '.___.'  \\              |\n        |            /                            \\             |\n         \\          /                              \\           /\n          \\________/                                \\_________/\n```\n\nEmulator shortcuts:\n\n| Action                        | Keyboard     | Controller     |\n| ----------------------------- | ------------ | -------------- |\n| Pause                         | Escape       | Guide Button   |\n| About TetaNES                 | F1           |                |\n| Preferences Menu              | Ctrl-P or F2 |                |\n| Load/Open ROM                 | Ctrl-O or F3 |                |\n| Quit                          | Ctrl-Q       |                |\n| Reset                         | Ctrl-R       |                |\n| Power Cycle                   | Ctrl-H       |                |\n| Increase Speed by 25%         | =            | Right Shoulder |\n| Decrease Speed by 25%         | -            | Left Shoulder  |\n| Increase Emulation Scale      | Shift-=      |                |\n| Decrease Emulation Scale      | Shift--      |                |\n| Increase UI Scale             | Ctrl-=       |                |\n| Decrease UI Scale             | Ctrl--       |                |\n| Fast-Forward 2x               | Space (Hold) |                |\n| Set Save State Slot (1-4)     | Ctrl-(1-4)   |                |\n| Save State                    | Ctrl-S       |                |\n| Load State                    | Ctrl-L       |                |\n| Instant Rewind                | R (Tap)      |                |\n| Visual Rewind                 | R (Hold)     |                |\n| Take Screenshot               | F10          |                |\n| Toggle Gameplay Recording     | Shift-V      |                |\n| Toggle Audio Recording        | Shift-R      |                |\n| Toggle Audio                  | Ctrl-M       |                |\n| Toggle Pulse Channel 1        | Shift-1      |                |\n| Toggle Pulse Channel 2        | Shift-2      |                |\n| Toggle Triangle Channel       | Shift-3      |                |\n| Toggle Noise Channel          | Shift-4      |                |\n| Toggle DMC Channel            | Shift-5      |                |\n| Toggle Mapper Channel         | Shift-6      |                |\n| Toggle Fullscreen             | Ctrl-Enter   |                |\n| Toggle NTSC Filter            | Ctrl-N       |                |\n| Toggle CRT Shader             | Ctrl-T       |                |\n| Toggle CPU Debugger           | Shift-D      |                |\n| Toggle PPU Debugger           | Shift-P      |                |\n| Toggle APU Debugger           | Shift-A      |                |\n\nWhile the CPU Debugger is open:\n\n| Action                        | Keyboard |\n| ----------------------------- | -------- |\n| Step a single CPU instruction | C        |\n| Step over a function          | O        |\n| Step out of a function        | Shift-O  |\n| Step a single scanline        | Shift-L  |\n| Step an entire frame          | Shift-F  |\n\nWhile the PPU Debugger is open:\n\n| Action                         | Keyboard        |\n| ------------------------------ | --------------- |\n| Move debug scanline up by 1    | Ctrl-Up         |\n| Move debug scanline up by 10   | Ctrl-Shift-Up   |\n| Move debug scanline down by 1  | Ctrl-Down       |\n| Move debug scanline down by 10 | Ctrl-Shift-Down |\n\nOther mappings can be found and modified in the `Config -> Keybinds` menu.\n\n### Directories\n\n`TetaNES` saves files to disk to support a number of features and, depending on the\nfile type, varies based on operating system.\n\n#### Preferences\n\n- Linux: `$HOME/.config`\n- macOS: `$HOME/Library/Application Support`\n- Windows: `%LOCALAPPDATA%\\tetanes`\n- Web: localStorage (e.g. `config/config.json`)\n\n\n#### Screenshots\n\n- Linux, macOS, & Windows: `$HOME/Pictures`\n- Web: Does not currently support saving screenshots.\n\n#### Replay Recordings\n\n- Linux, macOS, & Windows: `$HOME/Documents`\n- Web: Does not currently support saving recordings.\n\n#### Audio Recordings\n\n- Linux, macOS, & Windows: `$HOME/Music`\n- Web: Does not currently support saving recordings.\n\n#### Battery-backed RAM, save states, and logs\n\n- Linux: `$HOME/.local/share/tetanes`\n- macOS: `$HOME/Library/Application Support/tetanes`\n- Windows: `%LOCALAPPDATA%\\tetanes`\n- Web: localStorage (e.g. `data/save/AO Demo/slot-1.sav`)\n\n### Powerup State\n\nThe original NES hardware had semi-random contents located in RAM upon power-up\nand several games made use of this to seed their Random Number Generators\n(RNGs). By default, `TetaNES` honors the original hardware and emulates\nrandomized powerup RAM state. This shows up in several games such as `Final\nFantasy`, `River City Ransom`, and `Impossible Mission II`, amongst others. Not\nemulating this would make these games seem deterministic when they weren't\nintended to be.\n\nIf you would like `TetaNES` to provide fully deterministic emulated power-up\nstate, you'll need to change the `RAM State` setting in the configuration menu\nand trigger a power-cycle or use the `-m`/`--ram_state` flag from the command\nline.\n\n### Building/Running\n\nTo build/run `TetaNES`, you'll need a nightly version of the compiler and run\n`cargo build` or `cargo build --release` (if you want better framerates).\n\nTo run the web version, you'll also need the `wasm32-unknown-unknown` target and\n[trunk](https://trunkrs.dev/) installed:\n\n```sh\nrustup target add wasm32-unknown-unknown\ntrunk serve --release\n```\n\nUnit and integration tests can be run with `cargo test`. There are also several\ntest roms that can be run to test various capabilities of the emulator. They are\nall located in the `tetanes-core/tests_roms/` directory.\n\nRun them in a similar way you would run a game. e.g.\n\n```sh\ncargo run --release tetanes-core/test_roms/cpu/nestest.nes\n```\n\n#### Feature Flags\n\n- **webgpu** - Enables the\n  [`BrowserWebGpu`](https://docs.rs/wgpu/latest/wgpu/enum.Backend.html) backend\n  for TetaNES Web. The default is to use `WebGl2` until WebGPU is stable across\n  all platforms and browsers. Currently pending Firefox and Chrome on Linux\n  (See: <https://caniuse.com/webgpu>).\n\n### Troubleshooting\n\nIf you get an error running a ROM that's using the supported Mapper list above,\nit could be a corrupted or incompatible ROM format. If you're unsure which games\nuse which mappers, see <http://bootgod.dyndns.org:7777/>. Trying other\nversions of the same game from different sources sometimes resolves the issue.\n\nIf you get some other error when trying to start a game that previously\nworked, try removing any configurations or save states from the\n[Directories](#directories) listed above to ensure it's not an incompatible\nsavestate file causing the issue.\n\nIf you encounter any shortcuts not working, ensure your operating system does\nnot have a binding for it that is overriding it. macOS specifically has many\nthings bound to `Ctrl-*`.\n\nIf an an issue is not already created, please use the [github issue tracker][]\nto create it.\n\n## Roadmap\n\nSee [ROADMAP.md][].\n\n## Known Issues\n\nSee the [github issue tracker][].\n\n## Documentation\n\nIn addition to the wealth of information in the `docs/` directory, I also\nreferenced these websites extensively during development:\n\n- [NES Documentation (PDF)](https://nesdev.org/NESDoc.pdf)\n- [NES Dev Wiki](https://wiki.nesdev.org/w/index.php/Nesdev_Wiki)\n- [6502 Datasheet](https://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf)\n\n## License\n\n`TetaNES` is licensed under a MIT or Apache-2.0 license. See the `LICENSE-MIT`\nor `LICENSE-APACHE` file in the root for a copy.\n\n## Contribution\n\nWhile this is primarily a personal project, I welcome any contributions or\nsuggestions. Feel free to submit a pull request if you want to help out!\n\n### Contact\n\nFor issue reporting, please use the [github issue tracker][]. You can also\ncontact me directly at <https://lukeworks.tech/contact/>.\n\n## Credits\n\nImplementation was inspiried by several amazing NES projects, without which I\nwould not have been able to understand or digest all the information on the NES\nwiki.\n\n- [fogleman NES](https://github.com/fogleman/nes)\n- [sprocketnes](https://github.com/pcwalton/sprocketnes)\n- [nes-emulator](https://github.com/MichaelBurge/nes-emulator)\n- [LaiNES](https://github.com/AndreaOrru/LaiNES)\n- [ANESE](https://github.com/daniel5151/ANESE)\n- [FCEUX](https://fceux.com/web/home.html)\n\nI also couldn't have gotten this far without the amazing people over on the\n[NES Dev Forums](https://forums.nesdev.org/):\n\n- [blargg](https://forums.nesdev.org/memberlist.php?mode=viewprofile&u=17) for\n  all his amazing [test roms](https://wiki.nesdev.org/w/index.php/Emulator_tests)\n- [bisqwit](https://bisqwit.iki.fi/) for his test roms & integer NTSC video\n  implementation\n- [Disch](https://forums.nesdev.org/memberlist.php?mode=viewprofile&u=33)\n- [Quietust](https://forums.nesdev.org/memberlist.php?mode=viewprofile&u=7)\n- [rainwarrior](https://forums.nesdev.org/memberlist.php?mode=viewprofile&u=5165)\n- And many others who helped me understand the stickier bits of emulation\n\nAlso, a huge shout out to\n[OneLoneCoder](https://github.com/OneLoneCoder/) for his\n[NES](https://github.com/OneLoneCoder/olcNES) and\n[olcPixelGameEngine](https://github.com/OneLoneCoder/olcPixelGameEngine)\nseries as those helped a ton in some recent refactorings.\n\n[rust]: https://www.rust-lang.org/\n[wgpu]: https://wgpu.rs/\n[web assembly]: https://webassembly.org/\n[github issue tracker]: https://github.com/lukexor/tetanes/issues\n[ROADMAP.md]: ROADMAP.md\n[Release]: https://github.com/lukexor/tetanes/releases/latest\n"
  },
  {
    "path": "ROADMAP.md",
    "content": "# Roadmap\n\n- NES Formats & Run Modes\n  - [x] NTSC\n  - [x] PAL\n  - [x] Dendy\n  - [x] Headless mode\n- Central Processing Unit (CPU)\n  - [x] Official Instructions\n  - [x] Unofficial Instructions\n  - [x] Cycle Accurate\n- Picture Processing Unit (PPU)\n  - [x] Pixellate Filter\n  - [x] NTSC Filter\n  - [x] CRT Filter\n- Audio Processing Unit (APU)\n  - [x] Pulse Channels\n  - [x] Triangle Channel\n  - [x] Noise Channel\n  - [x] Delta Modulation Channel (DMC)\n- Player Input\n  - [x] 1-2 Player w/ Keyboard or Controllers\n  - [x] 3-4 Player Support w/ Controllers\n  - [x] Zapper (Light Gun)\n- Cartridge\n  - [x] iNES Format\n  - [x] NES 2.0 Format\n  - [ ] Complete NES 2.0 support\n  - Mappers\n    - [x] Mapper 000 - NROM\n    - [x] Mapper 001 - SxROM/MMC1B/C\n    - [x] Mapper 002 - UxROM\n    - [x] Mapper 003 - CNROM\n    - [x] Mapper 004 - TxROM/MMC3/MMC6\n    - [x] Mapper 005 - ExROM/MMC5\n    - [x] Mapper 007 - AxROM\n    - [x] Mapper 009 - PxROM/MMC2\n    - [x] Mapper 010 - FxROM/MMC4\n    - [x] Mapper 011 - Color Dreams\n    - [x] Mapper 019 - Namco 163\n    - [ ] Mapper 023 - VRC2b/VRC4e\n    - [ ] Mapper 025 - VRC4b/VRC4d\n    - [x] Mapper 024 - VRC6a\n    - [x] Mapper 026 - VRC6b\n    - [x] Mapper 034 - BNROM/NINA-001\n    - [ ] Mapper 064 - RAMBO-1\n    - [x] Mapper 066 - GxROM/MxROM\n    - [ ] Mapper 068 - After Burner\n    - [x] Mapper 069 - FME-7/Sunsoft 5B\n    - [x] Mapper 071 - Camerica/Codemasters/BF909x\n    - [x] Mapper 079 - NINA-03/NINA-06\n    - [x] Mapper 155 - SxROM/MMC1A\n    - [x] Mapper 206 - DxROM/Namco 118/MIMIC-1\n- Releases\n  - [x] macOS Binaries\n  - [x] Linux Binaries\n  - [x] Windows Binaries\n- [x] User Interface (UI)\n  - [x] WebAssembly (WASM) - Run TetaNES in the browser!\n  - [x] Configurable keybinds and default settings\n  - Menus\n    - [x] Configuration options\n    - [x] Customize Keybinds & Controllers\n    - [x] Load/Open ROM with file browser\n    - [x] Recent Game Selection\n    - [x] About Menu\n    - [ ] Config paths overrides\n  - [x] Increase/Decrease Speed\n  - [x] Fast-forward\n  - [x] Instant Rewind (2 seconds)\n  - [x] Visual Rewind (Holding R will time-travel backward)\n  - [x] Save/Load State\n  - [x] Auto-save\n  - [x] Take Screenshots\n  - [x] Gameplay Recording\n  - [x] Sound Recording (Save those memorable tunes!)\n  - [x] Toggle Fullscreen\n  - [x] Toggle Sound\n    - [x] Toggle individual sound channels\n  - [x] Toggle FPS\n  - [x] Toggle Messages\n  - [x] Change Video Filter\n  - Game Genie Support\n    - [x] Command-Line\n    - [ ] UI Menu\n  - [ ] [WideNES](https://prilik.com/ANESE/wideNES)\n  - [ ] Network Multi-player\n  - [ ] Self Updater\n  - [x] Drag and drop load ROMs\n- Testing/Debugging/Documentation\n  - [x] Debugger (Displays CPU/PPU status, registers, and disassembly)\n    - [x] Step Into/Out/Over\n    - [x] Step Scanline/Frame\n    - [ ] Breakpoints\n    - [ ] Modify state\n    - [ ] Labels\n  - [ ] Hex Memory Editor & Debugger\n  - PPU Viewer\n    - [ ] Scanline Hit Configuration (For debugging IRQ Nametable changes)\n    - [x] Nametable Viewer (background rendering)\n    - [x] CHR Viewer (sprite tiles)\n    - [x] OAM Viewer (on screen sprites)\n    - [x] Palette Viewer\n  - [ ] APU Viewer (Displays audio status and registers)\n  - [x] Automated ROM tests (including [nestest](https://www.qmtpro.com/~nes/misc/nestest.txt))\n  - [ ] Detailed Documentation\n  - Logging\n    - [x] Environment logging\n    - [x] File logging\n"
  },
  {
    "path": "assets/linux/tetanes.desktop",
    "content": "[Desktop Entry]\nName=TetaNES\nExec=tetanes\nIcon=icon\nType=Application\nCategories=Game;\n"
  },
  {
    "path": "assets/macos/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"https://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n  <key>CFBundleDevelopmentRegion</key>\n  <string>English</string>\n  <key>CFBundleIdentifier</key>\n  <string>tech.lukeworks.tetanes</string>\n  <key>CFBundleName</key>\n  <string>tetanes</string>\n  <key>CFBundleDisplayName</key>\n  <string>TetaNES</string>\n  <key>CFBundleExecutable</key>\n  <string>tetanes</string>\n  <key>CFBundleIconFile</key>\n  <string>Icon</string>\n  <key>CFBundleVersion</key>\n  <string>%VERSION%</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "cliff.toml",
    "content": "[changelog]\nheader = \"\"\"\n<!-- markdownlint-disable-file no-duplicate-heading -->\n\n# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\\n\n\"\"\"\nbody = \"\"\"\n{% if version %}\\\n    {% if previous.version %}\\\n        ## [{{ version | trim_start_matches(pat=\"v\") }}](<REPO>/compare/{{ previous.version }}..{{ version }}) - {{ timestamp | date(format=\"%Y-%m-%d\") }}\n    {% else %}\\\n        ## [{{ version | trim_start_matches(pat=\"v\") }}] - {{ timestamp | date(format=\"%Y-%m-%d\") }}\n    {% endif %}\\\n{% else %}\\\n    ## [unreleased]\n{% endif %}\\\n\n{% macro commit(commit) -%}\n    - {% if commit.scope %}*({{ commit.scope }})* {% endif %}{% if commit.breaking %}[**breaking**] {% endif %}\\\n        {{ commit.message | upper_first }} - ([{{ commit.id | truncate(length=7, end=\"\") }}](<REPO>/commit/{{ commit.id }}))\\\n{% endmacro -%}\n\n{% for group, commits in commits | group_by(attribute=\"group\") %}\n    ### {{ group | striptags | trim | upper_first }}\n    {% for commit in commits\n    | filter(attribute=\"scope\")\n    | sort(attribute=\"scope\") %}\n        {{ self::commit(commit=commit) }}\\\n    {% endfor %}\n    {% raw %}\\n{% endraw %}\\\n    {%- for commit in commits %}\n        {%- if not commit.scope -%}\n            {{ self::commit(commit=commit) }}\n        {% endif -%}\n    {% endfor -%}\n{% endfor %}\\n\n\"\"\"\ntrim = true\nfooter = \"\"\npostprocessors = [\n  { pattern = '<REPO>', replace = \"https://github.com/lukexor/tetanes\" },\n]\n\n[git]\nconventional_commits = true\nfilter_unconventional = true\nsplit_commits = false\ncommit_preprocessors = [\n  { pattern = '\\((\\w+\\s)?#([0-9]+)\\)', replace = \"([#${2}](<REPO>/issues/${2}))\" },\n]\ncommit_parsers = [\n  { message = \"^feat\", group = \"<!-- 0 -->⛰️  Features\" },\n  { message = \"^fix\", group = \"<!-- 1 -->🐛 Bug Fixes\" },\n  { message = \"^doc\", group = \"<!-- 3 -->📚 Documentation\" },\n  { message = \"^perf\", group = \"<!-- 4 -->⚡ Performance\" },\n  { message = \"^refactor\", group = \"<!-- 2 -->🚜 Refactor\" },\n  { message = \"^style\", group = \"<!-- 5 -->🎨 Styling\" },\n  { message = \"^test\", group = \"<!-- 6 -->🧪 Testing\" },\n  { message = \"^chore\\\\(release\\\\): prepare for\", skip = true },\n  { message = \"^chore\\\\(deps\\\\)\", skip = true },\n  { message = \"^build\\\\(deps\\\\)\", skip = true },\n  { message = \"^chore\\\\(pr\\\\)\", skip = true },\n  { message = \"^chore\\\\(pull\\\\)\", skip = true },\n  { message = \"^chore|ci\", group = \"<!-- 7 -->⚙️ Miscellaneous Tasks\" },\n  { body = \".*security\", group = \"<!-- 8 -->🛡️ Security\" },\n  { message = \"^revert\", group = \"<!-- 9 -->◀️ Revert\" },\n]\nprotect_breaking_commits = false\nfilter_commits = true\ntag_pattern = \"v[0-9].*\"\nskip_tags = \"beta|alpha\"\nignore_tags = \"rc\"\ntopo_order = false\nsort_commits = \"newest\"\n"
  },
  {
    "path": "deny.toml",
    "content": "[graph]\nall-features = true\nno-default-features = false\n\n[output]\nfeature-depth = 1\n\n[advisories]\nversion = 2\ndb-path = \"~/.cargo/advisory-db\"\ndb-urls = [\"https://github.com/rustsec/advisory-db\"]\nyanked = \"warn\"\nignore = [\n  { id = \"RUSTSEC-2024-0436\", reason = \"paste is a downstream dependency of many crates with no upgrade path\" },\n  { id = \"RUSTSEC-2025-0141\", reason = \"need to plan an upgrade path from bincode to rkyv\" },\n]\n\n[licenses]\nversion = 2\nallow = [\n  \"Apache-2.0\",                     # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)\n  \"Apache-2.0 WITH LLVM-exception\", # https://spdx.org/licenses/LLVM-exception.html\n  \"BSD-2-Clause\",                   # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)\n  \"BSD-3-Clause\",                   # https://tldrlegal.com/license/bsd-3-clause-license-(revised)\n  \"BSL-1.0\",                        # https://tldrlegal.com/license/boost-software-license-1.0-explained\n  \"CC0-1.0\",                        # https://creativecommons.org/publicdomain/zero/1.0/\n  \"CDLA-Permissive-2.0\",            # https://cdla.dev/permissive-2-0/. Used by webpki-roots on Linux.\n  \"ISC\",                            # https://www.tldrlegal.com/license/isc-license\n  \"MIT\",                            # https://tldrlegal.com/license/mit-license\n  \"MPL-2.0\",                        # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11.\n  \"OFL-1.1\",                        # https://spdx.org/licenses/OFL-1.1.html\n  \"OpenSSL\",                        # https://openssl-library.org/source/license/index.html\n  \"Ubuntu-font-1.0\",                # https://ubuntu.com/legal/font-licence\n  \"Unicode-3.0\",                    # https://spdx.org/licenses/Unicode-3.0.html\n  \"Zlib\",                           # https://tldrlegal.com/license/zlib-libpng-license-(zlib)\n]\nconfidence-threshold = 0.8\nexceptions = []\n\n[licenses.private]\nignore = false\nregistries = []\n\n[bans]\nmultiple-versions = \"allow\"\nwildcards = \"deny\"\nhighlight = \"all\"\nworkspace-default-features = \"allow\"\nexternal-default-features = \"allow\"\nallow = []\ndeny = []\nskip = []\nskip-tree = []\n\n[sources]\nunknown-registry = \"deny\"\nunknown-git = \"deny\"\nallow-registry = [\"https://github.com/rust-lang/crates.io-index\"]\nallow-git = []\n\n[sources.allow-org]\ngithub = []\ngitlab = []\nbitbucket = []\n"
  },
  {
    "path": "docs/apu/apu_ref.txt",
    "content": "NES APU Sound Hardware Reference\n--------------------------------\n\nThis reference covers Nintendo Entertainment System (NES) sound hardware in as\nmuch detail as I know. It is intended primarily to assist in the implementation\nof emulators and might also be useful as a programmer reference.\n\nTables, diagrams, and formulas are formatted for a mono-spaced font, like\nCourier. \n\nThe latest version is kept at http://www.slack.net/~ant/nes-emu/apu_ref.txt\n\n\n-----\nIntro\n-----\n\nThis reference is based on the results of tests I have run on a 1988-model US\nNTSC NES which contains the G revision of the 2A03 CPU/APU and the NES-CPU-07\nversion of the main board. PAL hardware will be covered once tests are\nperformed on it. Feel free to incorporate this information in references and\nother documentation.\n\nWhile implementing a NES sound emulator, even after reading the available\ndocumentation I still had many unanswered questions, so I made a simple\ndevelopment cartridge to test on a real NES. This was very successful and\nrevealed many new details. My notes consisted of differences from existing\ndocumentation, but this didn't seem to be a very reliable way to release my\nfindings, so I decided to write a concise reference.\n\nFor those familiar with NESSOUND.TXT and DMC.TXT, the following differences\nshould be specifically noted:\n\nCorrections:\n    - DMC table entry $D should be $2A0 instead of $2A8\n    - Frame sequencer\n    - Square's duty generator\n\nClarifications:\n    - DMC\n    - Triangle's linear counter\n    - Length Counter operation and status register behavior\n\nIt should go without saying that the model presented here probably doesn't\nmatch the actual logic gate arrangement in the NES. It makes no difference how\nthe hardware is implemented, as long as its behavior matches what is described\nhere\n\nCorrections, questions and additions are welcome. I keep up with the forum at\nhttp://nesdev.parodius.com/ and can be contacted via blargg at the mail.com\ndomain.\n\n\n-----\nTo Do\n-----\n\n- See if comprehensive emulator test ROM is even practical.\n\n- Probe complete power-up state and reset state.\n\n- Test PAL hardware to determine APU frame rate, DMC and noise period tables.\n\n- Check complete behavior of each unit of each channel to be sure common units\nbehave the same for all channels.\n\n- Double-check details on NES hardware again.\n\n- Determine post-DAC filtering done before output.\n\n\n--------\nOverview\n--------\n\nThe APU is composed of five channels: square 1, square 2, triangle, noise,\ndelta modulation channel (DMC). Each has a variable-rate timer clocking a\nwaveform generator, and various modulators driven by low-frequency clocks from\na frame sequencer. The DMC plays samples while the other channels play\nwaveforms. The waveform channels have duration control, some have a volume\nenvelope unit, and a couple have a frequency sweep unit.\n\n        Square 1/Square 2\n\n$4000/4 ddle nnnn   duty, loop env/disable length, env disable, vol/env\nperiod\n$4001/5 eppp nsss   enable sweep, period, negative, shift\n$4002/6 pppp pppp   period low\n$4003/7 llll lppp   length index, period high\n\n        Triangle\n\n$4008   clll llll   control, linear counter load\n$400A   pppp pppp   period low\n$400B   llll lppp   length index, period high\n\n        Noise\n\n$400C   --le nnnn   loop env/disable length, env disable, vol/env period\n$400E   s--- pppp   short mode, period index\n$400F   llll l---   length index\n\n        DMC\n\n$4010   il-- ffff   IRQ enable, loop, frequency index\n$4011   -ddd dddd   DAC\n$4012   aaaa aaaa   sample address\n$4013   llll llll   sample length\n\n        Common\n\n$4015   ---d nt21   length ctr enable: DMC, noise, triangle, pulse 2, 1\n$4017   fd-- ----   5-frame cycle, disable frame interrupt\n\n        Status (read)\n\n$4015   if-d nt21   DMC IRQ, frame IRQ, length counter statuses\n\n\n------\nBasics\n------\n\nHexadecimal values are prefixed by a $ except for some single-hex-digit\nsequences where it's clear that they are hex. Bits are numbered from 0 to 7,\ncorresponding with the least to most significant bits of a byte; bit n has a\nbinary weight of 2^n.\n\nA flag is a two-state variable that can be either set or clear. When\nimplemented in a bit, clear = 0 and set = 1.\n\nA divider outputs a clock every n input clocks, where n is the divider's\nperiod. It contains a counter which is decremented on the arrival of each\nclock. When it reaches 0, it is reloaded with the period and an output clock is\ngenerated. Resetting a divider reloads its counter without generating an output\nclock. Changing a divider's period doesn't affect its current count.\n\nA sequencer generates a series of values or events based on the repetition of a\nseries of steps, starting with the first. When clocked the next step of the\nsequence is generated.\n\nIn the block diagrams, the triangular symbol is a control gate; if control is\nnon-zero, the input is passed unchanged to the output, otherwise the output is\n0.\n\n        control\n           | \n           v \n          |\\\n    in -->| >-- out\n          |/\n\n\nExcept for the status register, all other registers are write-only. The \"value\nof the register\" refers to the last value written to the register.\n\nThe NTSC NES has a master clock based on a 21.47727 MHz crystal which is\ndivided by 12 to obtain a ~1.79 MHz clock source. Both clocks are used by the\nAPU.\n\nThe CPU's IRQ line is level-sensitive, so the APU's interrupt flags must be\ncleared once a CPU IRQ is acknowledged, otherwise the CPU will immediately be\ninterrupt again once its inhibit flag is cleared.\n\nIn general, the APU is a collection of many independent units which are always\nrunning in parallel. Modification of a channel's parameter usually affects only\none sub-unit and doesn't take effect until that unit's next internal cycle\nbegins.\n\nEach section begins with an overview and an optional block diagram, which\nprovide a framework for the information that follows. In order to reduce\nambiguity, there is very little re-statement of information.\n\n\n---------------\nFrame Sequencer\n---------------\n\nThe frame sequencer contains a divider and a sequencer which clocks various\nunits.\n\nThe divider generates an output clock rate of just under 240 Hz, and appears to\nbe derived by dividing the 21.47727 MHz system clock by 89490. The sequencer is\nclocked by the divider's output.\n\nOn a write to $4017, the divider and sequencer are reset, then the sequencer is\nconfigured. Two sequences are available, and frame IRQ generation can be\ndisabled.\n\n    mi-- ----       mode, IRQ disable\n\nIf the mode flag is clear, the 4-step sequence is selected, otherwise the\n5-step sequence is selected and the sequencer is immediately clocked once.\n\n    f = set interrupt flag\n    l = clock length counters and sweep units\n    e = clock envelopes and triangle's linear counter\n\nmode 0: 4-step  effective rate (approx)\n---------------------------------------\n    - - - f      60 Hz\n    - l - l     120 Hz\n    e e e e     240 Hz\n\nmode 1: 5-step  effective rate (approx)\n---------------------------------------\n    - - - - -   (interrupt flag never set)\n    l - l - -    96 Hz\n    e e e e -   192 Hz\n\nAt any time if the interrupt flag is set and the IRQ disable is clear, the\nCPU's IRQ line is asserted.\n\n\n--------------\nLength Counter\n--------------\n\nA length counter allows automatic duration control. Counting can be halted and\nthe counter can be disabled by clearing the appropriate bit in the status\nregister, which immediately sets the counter to 0 and keeps it there.\n\nThe halt flag is in the channel's first register. For the square and noise\nchannels, it is bit 5, and for the triangle, bit 7:\n\n    --h- ----       halt (noise and square channels)\n    h--- ----       halt (triangle channel)\n\nNote that the bit position for the halt flag is also mapped to another flag in\nthe Length Counter (noise and square) or Linear Counter (triangle).\n\nUnless disabled, a write the channel's fourth register immediately reloads the\ncounter with the value from a lookup table, based on the index formed by the\nupper 5 bits:\n\n    iiii i---       length index\n    \n    bits  bit 3\n    7-4   0   1\n        -------\n    0   $0A $FE\n    1   $14 $02\n    2   $28 $04\n    3   $50 $06\n    4   $A0 $08\n    5   $3C $0A\n    6   $0E $0C\n    7   $1A $0E\n    8   $0C $10\n    9   $18 $12\n    A   $30 $14\n    B   $60 $16\n    C   $C0 $18\n    D   $48 $1A\n    E   $10 $1C\n    F   $20 $1E\n\nSee the clarifications section for a possible explanation for the values left\ncolumn of the table.\n\nWhen clocked by the frame sequencer, if the halt flag is clear and the counter\nis non-zero, it is decremented.\n\n\n---------------\nStatus Register\n---------------\n\nThe status register at $4015 allows control and query of the channels' length\ncounters, and query of the DMC and frame interrupts. It is the only register\nwhich can also be read.\n\nWhen $4015 is read, the status of the channels' length counters and bytes\nremaining in the current DMC sample, and interrupt flags are returned.\nAfterwards the Frame Sequencer's frame interrupt flag is cleared.\n\n    if-d nt21\n    \n    IRQ from DMC\n    frame interrupt\n    DMC sample bytes remaining > 0\n    triangle length counter > 0\n    square 2 length counter > 0\n    square 1 length counter > 0\n\nWhen $4015 is written to, the channels' length counter enable flags are set, \nthe DMC is possibly started or stopped, and the DMC's IRQ occurred flag is\ncleared.\n\n    ---d nt21   DMC, noise, triangle, square 2, square 1\n\nIf d is set and the DMC's DMA reader has no more sample bytes to fetch, the DMC\nsample is restarted. If d is clear then the DMA reader's sample bytes remaining\nis set to 0.\n\n\n------------------\nEnvelope Generator\n------------------\n\nAn envelope generator can generate a constant volume or a saw envelope with\noptional looping. It contains a divider and a counter.\n\nA channel's first register controls the envelope:\n\n    --ld nnnn       loop, disable, n\n\nNote that the bit position for the loop flag is also mapped to a flag in the\nLength Counter.\n\nThe divider's period is set to n + 1.\n\nWhen clocked by the frame sequencer, one of two actions occurs: if there was a\nwrite to the fourth channel register since the last clock, the counter is set\nto 15 and the divider is reset, otherwise the divider is clocked.\n\nWhen the divider outputs a clock, one of two actions occurs: if loop is set and\ncounter is zero, it is set to 15, otherwise if counter is non-zero, it is\ndecremented.\n\nWhen disable is set, the channel's volume is n, otherwise it is the value in\nthe counter. Unless overridden by some other condition, the channel's DAC\nreceives the channel's volume value.\n\n\n-----\nTimer\n-----\n\nAll channels use a timer which is a divider driven by the ~1.79 MHz clock.\n\nThe noise channel and DMC use lookup tables to set the timer's period. For the\nsquare and triangle channels, the third and fourth registers form an 11-bit\nvalue and the divider's period is set to this value *plus one*.\n\n    llll llll       low 8 bits of period   (third register)\n    ---- -hhh       upper 3 bits of period (fourth register)\n\n\n----------\nSweep Unit\n----------\n\nThe sweep unit can adjust a square channel's period periodically. It contains a\ndivider and a shifter.\n\nA channel's second register configures the sweep unit:\n\n    eppp nsss       enable, period, negate, shift\n\nThe divider's period is set to p + 1.\n\nThe shifter continuously calculates a result based on the channel's period. The\nchannel's period (from the third and fourth registers) is first shifted right\nby s bits. If negate is set, the shifted value's bits are inverted, and on the\nsecond square channel, the inverted value is incremented by 1. The resulting\nvalue is added with the channel's current period, yielding the final result.\n\nWhen the sweep unit is clocked, the divider is *first* clocked and then if\nthere was a write to the sweep register since the last sweep clock, the divider\nis reset.\n\nWhen the channel's period is less than 8 or the result of the shifter is\ngreater than $7FF, the channel's DAC receives 0 and the sweep unit doesn't\nchange the channel's period. Otherwise, if the sweep unit is enabled and the\nshift count is greater than 0, when the divider outputs a clock, the channel's\nperiod in the third and fourth registers are updated with the result of the\nshifter.\n\n\n--------------\nSquare Channel\n--------------\n\n                   +---------+    +---------+\n                   |  Sweep  |--->|Timer / 2|\n                   +---------+    +---------+\n                        |              |\n                        |              v \n                        |         +---------+    +---------+\n                        |         |Sequencer|    | Length  |\n                        |         +---------+    +---------+\n                        |              |              |\n                        v              v              v\n    +---------+        |\\             |\\             |\\          +---------+\n    |Envelope |------->| >----------->| >----------->| >-------->|   DAC   |\n    +---------+        |/             |/             |/          +---------+\n\nThere are two square channels beginning at registers $4000 and $4004. Each\ncontains the following: Envelope Generator, Sweep Unit, Timer with\ndivide-by-two on the output, 8-step sequencer, Length Counter.\n\n$4000/$4004: duty, envelope\n$4001/$4005: sweep unit\n$4002/$4006: period low\n$4003/$4007: reload length counter, period high\n\nIn addition to the envelope, the first register controls the duty cycle of the\nsquare wave, without resetting the position of the sequencer:\n\n    dd-- ----       duty cycle select\n    \n    d   waveform sequence\n    ---------------------\n         _       1\n    0   - ------ 0 (12.5%)\n\n         __      1\n    1   -  ----- 0 (25%)\n\n         ____    1\n    2   -    --- 0 (50%)\n\n        _  _____ 1\n    3    --      0 (25% negated)\n\n\nWhen the fourth register is written to, the sequencer is restarted.\n\nThe sequencer is clocked by the divided timer output.\n\nWhen the sequencer output is low, the DAC receives 0.\n\n\n--------------\nLinear Counter\n--------------\n\nThe Linear Counter serves as a second more-accurate duration counter for the\ntriangle channel. It contains a counter and an internal halt flag.\n\nRegister $4008 contains a control flag and reload value:\n\n    crrr rrrr   control flag, reload value\n\nNote that the bit position for the control flag is also mapped to a flag in the\nLength Counter.\n\nWhen register $400B is written to, the halt flag is set.\n\nWhen clocked by the frame sequencer, the following actions occur in order:\n\n    1) If halt flag is set, set counter to reload value, otherwise if counter\nis non-zero, decrement it.\n\n    2) If control flag is clear, clear halt flag.\n\n\n----------------\nTriangle Channel\n----------------\n\n                   +---------+    +---------+\n                   |LinearCtr|    | Length  |\n                   +---------+    +---------+\n                        |              |\n                        v              v\n    +---------+        |\\             |\\         +---------+    +---------+ \n    |  Timer  |------->| >----------->| >------->|Sequencer|--->|   DAC   |\n    +---------+        |/             |/         +---------+    +---------+ \n\nThe triangle channel contains the following: Timer, 32-step sequencer, Length\nCounter, Linear Counter, 4-bit DAC.\n\n$4008: length counter disable, linear counter\n$400A: period low\n$400B: length counter reload, period high\n\nWhen the timer generates a clock and the Length Counter and Linear Counter both\nhave a non-zero count, the sequencer is clocked.\n\nThe sequencer feeds the following repeating 32-step sequence to the DAC:\n\n    F E D C B A 9 8 7 6 5 4 3 2 1 0 0 1 2 3 4 5 6 7 8 9 A B C D E F\n\nAt the lowest two periods ($400B = 0 and $400A = 0 or 1), the resulting\nfrequency is so high that the DAC effectively outputs a value half way between\n7 and 8.\n\n\n-------------   \nNoise Channel\n-------------\n\n    +---------+    +---------+    +---------+\n    |  Timer  |--->| Random  |    | Length  |\n    +---------+    +---------+    +---------+\n                        |              |\n                        v              v\n    +---------+        |\\             |\\         +---------+\n    |Envelope |------->| >----------->| >------->|   DAC   |\n    +---------+        |/             |/         +---------+\n\nThe noise channel starts at register $400C and contains the following: Length\nCounter, Envelope Generator, Timer, 15-bit right shift register with feedback,\n4-bit DAC.\n\n$400C: envelope\n$400E: mode, period\n$400F: reload length counter                   \n\nRegister $400E sets the random generator mode and timer period based on a 4-bit\nindex into a period table:\n\n    m--- iiii       mode, period index \n    \n    i   timer period\n    ----------------\n    0     $004\n    1     $008\n    2     $010\n    3     $020\n    4     $040\n    5     $060\n    6     $080\n    7     $0A0\n    8     $0CA\n    9     $0FE\n    A     $17C\n    B     $1FC\n    C     $2FA\n    D     $3F8\n    E     $7F2\n    F     $FE4\n\nThe shift register is clocked by the timer and the vacated bit 14 is filled\nwith the exclusive-OR of *pre-shifted* bits 0 and 1 (mode = 0) or bits 0 and 6\n(mode = 1), resulting in 32767-bit and 93-bit sequences, respectively.\n\nWhen bit 0 of the shift register is set, the DAC receives 0.\n\nOn power-up, the shift register is loaded with the value 1.\n\n\n------------------------------\nDelta Modulation Channel (DMC)\n------------------------------\n\n    +----------+    +---------+\n    |DMA Reader|    |  Timer  |\n    +----------+    +---------+\n         |               |\n         |               v\n    +----------+    +---------+     +---------+     +---------+ \n    |  Buffer  |----| Output  |---->| Counter |---->|   DAC   |\n    +----------+    +---------+     +---------+     +---------+ \n\nThe DMC can output samples composed of 1-bit deltas and its DAC can be directly\nchanged. It contains the following: DMA reader, interrupt flag, sample buffer,\nTimer, output unit, 7-bit counter tied to 7-bit DAC.\n\n$4010: mode, frequency\n$4011: DAC\n$4012: address\n$4013: length\n\nOn power-up, the DAC counter contains 0.\n\nRegister $4010 sets the interrupt enable, loop, and timer period. If the new\ninterrupt enabled status is clear, the interrupt flag is cleared.\n\n    il-- ffff       interrupt enabled, loop, frequency index\n    \n    f   period\n    ----------\n    0   $1AC\n    1   $17C\n    2   $154\n    3   $140\n    4   $11E\n    5   $0FE\n    6   $0E2\n    7   $0D6\n    8   $0BE\n    9   $0A0\n    A   $08E\n    B   $080\n    C   $06A\n    D   $054\n    E   $048\n    F   $036\n\nA write to $4011 sets the counter and DAC to a new value:\n\n    -ddd dddd       new DAC value\n\nSample Buffer\n-------------\nThe sample buffer either holds a single sample byte or is empty. It is filled\nby the DMA reader and can only be emptied by the output unit, so once loaded\nwith a sample it will be eventually output.\n\nDMA Reader\n----------\nThe DMA reader fills the sample buffer with successive bytes from the current\nsample, whenever it becomes empty. It has an address counter and a bytes remain\ncounter.\n\nWhen the DMC sample is restarted, the address counter is set to register $4012\n* $40 + $C000 and the bytes counter is set to register $4013 * $10 + 1.\n\nWhen the sample buffer is in an empty state and the bytes counter is non-zero,\nthe following occur: The sample buffer is filled with the next sample byte read\nfrom memory at the current address, subject to whatever mapping hardware is\npresent (the same as CPU memory accesses). The address is incremented; if it\nexceeds $FFFF, it is wrapped around to $8000. The bytes counter is decremented;\nif it becomes zero and the loop flag is set, the sample is restarted (see\nabove), otherwise if the bytes counter becomes zero and the interrupt enabled\nflag is set, the interrupt flag is set.\n\nWhen the DMA reader accesses a byte of memory, the CPU is suspended for 4 clock\ncycles.\n\nOutput Unit\n-----------\nThe output unit continually outputs complete sample bytes or silences of equal\nduration. It contains an 8-bit right shift register, a counter, and a silence\nflag.\n\nWhen an output cycle is started, the counter is loaded with 8 and if the sample\nbuffer is empty, the silence flag is set, otherwise the silence flag is cleared\nand the sample buffer is emptied into the shift register.\n\nOn the arrival of a clock from the timer, the following actions occur in order:\n\n    1. If the silence flag is clear, bit 0 of the shift register is applied to\nthe DAC counter: If bit 0 is clear and the counter is greater than 1, the\ncounter is decremented by 2, otherwise if bit 0 is set and the counter is less\nthan 126, the counter is incremented by 2.\n\n    1) The shift register is clocked.\n    \n    2) The counter is decremented. If it becomes zero, a new cycle is started.\n\n\n----------\nDAC Output\n----------\n\nThe DACs for each channel are implemented in a way that causes non-linearity\nand interaction between channels, so calculation of the resulting amplitude is\nsomewhat involved.\n\nThe normalized audio output level is the sum of two groups of channels:\n\n    output = square_out + tnd_out\n    \n    \n                          95.88\n    square_out = -----------------------\n                        8128\n                 ----------------- + 100\n                 square1 + square2\n\n\n                          159.79\n    tnd_out = ------------------------------\n                          1\n              ------------------------ + 100\n              triangle   noise    dmc\n              -------- + ----- + -----\n                8227     12241   22638\n\n\nwhere triangle, noise, dmc, square1 and square2 are the values fed to their\nDACs. The dmc ranges from 0 to 127 and the others range from 0 to 15. When the\nsub-denominator of a group is zero, its output is 0. The output ranges from 0.0\nto 1.0.\n\n\nImplementation Using Lookup Table \n---------------------------------\nThe formulas can be efficiently implemented using two lookup tables: a 31-entry\ntable for the two square channels and a 203-entry table for the remaining\nchannels (due to the approximation of tnd_out, the numerators are adjusted\nslightly to preserve the normalized output range).\n\n    square_table [n] = 95.52 / (8128.0 / n + 100)\n    \n    square_out = square_table [square1 + square2]\n    \nThe latter table is approximated (within 4%) by using a base unit close to the\nDMC's DAC.\n\n    tnd_table [n] = 163.67 / (24329.0 / n + 100)\n    \n    tnd_out = tnd_table [3 * triangle + 2 * noise + dmc]\n\n\nLinear Approximation\n--------------------\nA linear approximation can also be used, which results in slightly louder DMC\nsamples, but otherwise fairly accurate operation since the wave channels use a\nsmall portion of the transfer curve. The overall volume will be reduced due to\nthe headroom required by the DMC approximation.\n\n    square_out = 0.00752 * (square1 + square2)\n    \n    tnd_out = 0.00851 * triangle + 0.00494 * noise + 0.00335 * dmc\n\nThis linear approximation neglects the attenuating effect the DMC has when its\nDAC is in the upper level. This factor can be calculated using the main formula\nto form a ratio, and precalculated into a 128-entry lookup table.\n\n                     tnd_out(triangle=15,dmc=d) - tnd_out(triangle=0,dmc=d)\n    attenuation(d) = ------------------------------------------------------\n                                  tnd_out(triangle=15,dmc=0)\n\n-------------------\nUnreliable Behavior\n-------------------\n\n(The following behaviors probably don't need to be emulated due to their\nunreliability since stable code will avoid invoking it, and since their\nbehavior is somewhat difficult to precisely predict.)\n\nIf the frame IRQ is set just as register $4015 is being read, it seems to be\nignored (similar to polling $2002 for the vbl flag).\n\nThe DMC's DMA reader seems to check for an empty buffer every few CPU cycles,\nrather than every cycle or continuously.\n\nWriting to the DAC register ($4011) while a sample is playing sometimes has no\neffect, probably because the DMC's output unit is clocking the counter at the\nsame moment as the write.\n\n\n--------------\nClarifications\n--------------\n\n(The following are meant only as re-statements of the main content, rather than\nadditions of new content.)\n\nBecause the envelope loop and length counter disable flags are mapped to the\nsame bit, the length counter can't be used while the envelope is in loop mode.\nSimilar applies to the triangle channel, where the linear counter and length\ncounter are both controlled by the same bit in register $4008.\n\nUnlike the other waveform channels, the triangle channel is silenced by\nstopping its waveform at whatever phase it's at, rather than causing zero to be\nsent to its DAC.\n\nThe length counter table seems to be set up for standard note durations for 4/4\ntime at 160 bpm and 180 bpm. If bit 3 is 0, the following results (Dn is bit n\nof the fourth channel register):\n\n            180bpm  160bpm\n    D6-D4   D7=0    D7=1    note\n    -------------------------------\n    $00     10      12      16th\n    $01     20      24      8th\n    $02     40      48      4th (one beat)\n    $03     80      96      half\n    $04     160     192     whole\n    $05     60      72      4th dotted\n    $06     14      16      8th triplet (*3 = a 4th)\n    $07     26      32      4th triplet (*3 = a half)\n\n\n-------------\nCollaborators\n-------------\n\nBrad Taylor's NESSOUND.TXT and DMC.TXT as a starting point for testing.\nNTSC NES for testing on.\nNesdev forum for feedback.\nxodnizel for testing results, correction to DMC table, feedback.\nBloopaws/Draci for feedback, possible explanation of length counter table\nvalues.\n\n\n-------\nHistory\n-------\n\n2003.12.01\n    Made development cartridge and started testing on NES hardware.\n\n2003.12.14\n    Started project.\n\n2003.12.20\n    A few draft sections were posted to Nesdev or e-mailed privately.\n\n2004.01.02\n    Draft version posted to Nesdev. Corrected tnd_table formula.\n\n2004.01.02\n    Corrected incorrect \"correction\" to tnd_table formula. Double-checked them.\n\n2004.01.03\n    Corrected envelope flag name to \"disable\" (it was named \"enable\").\n    Added effective frequencies of frame sequencer outputs.\n    Added Overview, Unreliable Behavior, Clarifications, and Collaborators\nsections.\n\n2004.01.04\n    Adjusted linear approximation (difficult to find a compromise).\n    A few minor edits.\n\n2004.01.30\n    First release. Probably won't be doing much with it for a while.\n\n"
  },
  {
    "path": "docs/apu/audio_psuedo_code.txt",
    "content": "LENGTH_TABLE // used by $4003 WRITE Pulse1/2\n\nAPU\n    sequencer_mode              // $4017 WRITE D7, clock_frame_sequencer()\n    sequencer_phase             // $4017 WRITE 0, clock_frame_sequencer()\n    sequencer_counter           // $4017 WRITE clocks_to_next_phase(), clock_frame_sequencer()\n    irq_pending                 // $4017 WRITE if !irq_enabled false, clock_frame_sequencer()\n                                // $4015 READ irq_pending = false\n    irq_enabled                 // $4017 WRITE !D6, clock_frame_sequencer()\n\n    clock()                     // Clocked every CPU cycle\n    clock_frame_sequencer()\n    clocks_to_next_phase()      // used in $4017 WRITE\n    clock_quarter_frame()       // used in $4017 WRITE if sequencer_mode\n    clock_half_frame()          // used in $4017 WRITE if sequencer_mode\n    sample()                    //\n\nPulse1/2\n    DUTY_TABLE                  // used by duty_cycle/duty_counter\n\n    enabled                     // $4015 WRITE D0\n\n    duty_cycle                  // $4000 WRITE D7..D6, clock(), output()\n    duty_counter                // $4003 WRITE 0, output()\n    freq_timer                  // $4002 WRITE D7..D0, $4003 D2..D0 << 8, clock_half_frame()\n                                // sweep_forced_silent()\n    freq_counter                // $4003 WRITE freq_timer, clock()\n\n    length_enabled              // $4000 WRITE !D5, clock_half_frame()\n    length_counter              // $4003 WRITE if enabled LENGTH_TABLE[ D7..D3 ], output()\n                                // $4015 WRITE if !enabled 0, clock_half_frame()\n                                // $4015 READ\n\n    decay_enabled               // $4000 WRITE !D4, output()\n    decay_loop                  // $4000 WRITE D5, clock_quarter_frame()\n    decay_reset                 // $4003 WRITE true, clock_quarter_frame()\n    decay_volume                // $4000 WRITE D3..D0, output(), clock_quarter_frame()\n    decay_constant_volume       // output(), clock_quarter_frame()\n    decay_counter               // clock_quarter_frame()\n\n    sweep_enabled               // $4001 WRITE D7 && sweep_shift != 0, clock_half_frame()\n    sweep_reload                // $4001 WRITE true, clock_half_frame()\n    sweep_timer                 // $4001 WRITE D6..D4, clock_half_frame()\n    sweep_counter               // clock_half_frame()\n    sweep_negate                // $4001 WRITE D3, clock_half_frame(), sweep_forced_silent()\n    sweep_shift                 // $4001 WRITE D2..D0, clock_half_frame(), sweep_forced_silent()\n\n    clock()                     // Clocked every APU cycle (CPU Cycle % 2 == 0)\n    clock_quarter_frame()       //\n    clock_half_frame()          //\n    output()                    //\n    sweep_forced_silent()       // output(), clock_half_frame()\n\nTriangle\n    enabled                     //\n\n    ultrasonic                  //\n    step                        //\n    freq_timer                  //\n    freq_counter                //\n\n    length_enabled              //\n    length_counter              //\n\n    linear_control              //\n    linear_load                 //\n    linear_reload               //\n\n    clock()                     //\n    clock_quarter_frame()       //\n    clock_half_frame()          //\n    output()                    //\n\nNoise\n    FREQ_TABLE                  //\n\n    enabled                     //\n\n    freq_timer                  //\n    freq_counter                //\n\n    shift                       // u16: default to 1\n    shift_mode                  //\n\n    length_counter              //\n\n    decay_enabled               //\n    decay_reset                 //\n    decay_loop                  //\n    decay_volume                //\n    decay_constant_volume       //\n    decay_counter               //\n\n    clock()                     //\n    clock_quarter_frame()       //\n    clock_half_frame()          //\n    output()                    //\n\nDMC\n    addr                        //\n    addr_load                   //\n    length                      //\n    length_load                 //\n    irq_pending                 //\n    loops                       //\n\n    sample_buffer               //\n    output                      //\n    output_bits                 //\n    output_shift                //\n    output_silent               //\n\n    freq_timer                  //\n    freq_counter                //\n\n    clock()                     //\n    output()                    //\n\n========================================================\n\n$4000 write:\n    duty_table =        dutytables[ v.76 ] // duty_cycle\n    length_enabled =    !v.5 // length_counter.enabled\n    // envelope\n    decay_loop =        v.5 // looping\n    decay_enabled =     !v.4 // enabled\n    decay_V =           v.3210 // volume\n    \n========================================================\n    \n$4001 write:\n    sweep_timer =       v.654 // divider_period\n    sweep_negate =      v.3\n    sweep_shift =       v.210\n    sweep_reload =      true\n    sweep_enabled =     v.7  &&  sweep_shift != 0\n    \n========================================================\n    \n$4002 write:\n    freq_timer =        v           (low 8 bits) // timer_period\n    \n========================================================\n    \n$4003 write:\n    freq_timer =        v.210       (high 3 bits)\n    \n    if( channel_enabled )\n        length_counter =    lengthtable[ v.76543 ]\n        \n    ; phase is also reset here  (important for games like SMB)\n    freq_counter =      freq_timer // timer\n    duty_counter =      0 // sequencer_step\n    \n    ; decay is also flagged for reset here\n    decay_reset_flag =  true // envelope start\n    \n========================================================\n    \n$4015 write:\n    channel_enabled =   v.0\n    if( !channel_enabled )\n        length_counter = 0\n        \n    ; ... other channels and DMC here ...\n    \n========================================================\n    \n$4017 write:\n    sequencer_mode =    v.7     ; switch between 5-step (1) and 4-step (0) mode\n    irq_enabled =       !v.6\n    next_seq_phase =    0\n    sequencer_counter = ClocksToNextSequence()\n    ; see: http://wiki.nesdev.com/w/index.php/APU_Frame_Counter\n    ; for example, this will be 3728.5 APU cycles, or 7457 CPU cycles.\n    ; It might be easier to work in CPU cycles so you don't have to deal with\n    ;  half cycles.\n    \n    if(sequencer_mode)\n    {\n        Clock_QuarterFrame()                    ; see below\n        Clock_HalfFrame()\n    }\n    if(!irq_enabled)\n        irq_pending = false             ; acknowledge Frame IRQ\n        \n========================================================\n\n$4015 read:\n    output = 0\n    \n    if( length_counter != 0 )       output |= 0x01\n    ; ... other channels length counters here\n    \n    if( irq_pending )\n        output |= 0x40\n    \n    ; ... DMC IRQ state read back here\n    \n    irq_pending = false                 ; IRQ acknowledged on $4015 read\n    \n    return output\n    \n\n========================================================\n\nEvery APU Cycle:\n    \n    ;;;;;;;;;;;;;;;;;;;;;;;;;;;\n    ; clock pulse wave\n    \n    if( freq_counter > 0 ) // timer\n        --freq_counter\n    else\n    {\n        freq_counter = freq_timer\n        duty_counter = (duty_counter + 1) & 7\n    }\n    \n    ; ... clock other channels here\n    \n    ;;;;;;;;;;;;;;;;;;;;;;;;;;;\n    ; clock frame sequencer\n    if( sequencer_counter > 0 )\n        --sequencer_counter\n    else\n    {\n        ; see http://wiki.nesdev.com/w/index.php/APU_Frame_Counter for more details on here\n        ;  I'm just giving the basic idea here to conceptualize it\n        \n        if( next_seq_phase causes a Quarter Frame Clock )\n            Clock_QuarterFrame();\n        if( next_seq_phase causes a Half Frame Clock )\n            Clock_HalfFrame();\n        if( irq_enabled && next_seq_phase causes an IRQ )\n            irq_pending = true          ; raise IRQ\n            \n        ++next_seq_phase\n        if( next_seq_phase > max phases for this mode )\n            next_seq_phase = 0\n            \n        sequencer_counter = ClocksToNextSequence()\n    }\n    \n    \n    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n    ; determine audio output\n    if(    duty_table[ duty_counter ]   ; current duty phase is high\n        && length_counter != 0          ; length counter is nonzero (channel active)\n        && !IsSweepForcingSilence()     ; sweep unit is not forcing channel to be silent\n        )\n    {\n        ; output current volume\n        if(decay_enabled)       output = decay_hidden_vol\n        else                    output = decay_V\n    }\n    else            ; low duty, or channel is silent\n        output = 0\n        \n    ; ... mix other channels with output here\n    \n    \n========================================================\n\nClock_QuarterFrame:\n    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n    ; quarter frame clocks Decay\n    if( decay_reset_flag )\n    {\n        decay_reset_flag =  false\n        decay_hidden_vol =  0xF\n        decay_counter =     decay_V\n    }\n    else\n    {\n        if( decay_counter > 0 )\n            --decay_counter\n        else\n        {\n            decay_counter = decay_V\n            if( decay_hidden_vol > 0 )\n                --decay_hidden_vol\n            else if( decay_loop )\n                decay_hidden_vol = 0xF\n        }\n    }\n    \n    \n========================================================\n\nClock_HalfFrame:\n    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n    ; half frame clocks Sweep\n    if( sweep_reload )\n    {\n        sweep_counter = sweep_timer\n        ; note there's an edge case here -- see http://wiki.nesdev.com/w/index.php/APU_Sweep\n        ;   for details.  You can probably ignore it for now\n        \n        sweep_reload = false\n    }\n    else if( sweep_counter > 0 )\n        --sweep_counter\n    else\n    {\n        sweep_counter = sweep_timer\n        if( sweep_enabled && !IsSweepForcingSilence() )\n        {\n            if(sweep_negate)\n                freq_timer -= (freq_timer >> sweep_shift) + 1   ; note: +1 for Pulse1 only.  Pulse2\n                has no +1\n            else\n                freq_timer += (freq_timer >> sweep_shift)\n        }\n    }\n    \n    \n    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n    ; half frame also clocks length\n    if( length_enabled && length_counter > 0 )\n        --length_counter\n        \n      \n========================================================  \n        \nIsSweepForcingSilence:\n\n    if( freq_timer < 8 )\n        return true\n        \n    else if(    !sweep_negate\n                &&\n                freq_timer + (freq_timer >> sweep_shift) >= 0x800    )\n        return true\n        \n    else\n        return false\n\n\nPulse 2: Identical to Pulse 1 with the following changes:\n\n- use $4004-4007 instead of $4000-4003\n- $4015 reads/writes use bit 1 instead of bit 0\n- no '+ 1' when doing sweep negate\n\nTriangle:\nA few things to note about the triangle:\n\n- It's clocked at twice the rate of other channels (use CPU clock instead of APU clock)\n- To silence it, you stop clocking the tri-step unit, but do not change its output. This is in\n  contrast to other channels where you silence them by forcing output to zero.\n- There is no volume control, but Tri might appear quieter sometimes due to interference from the\n  DMC. See http://wiki.nesdev.com/w/index.php/APU_Mixer for details\n- When the freq timer is < 2, it goes \"ultrasonic\" and is effectively silenced by forcing output to\n  \"7.5\" (this causes a pop).\n\n$4015 read / write:  Same as Pulse1, only use bit 2 instead of bit 0\n        Note 4015 touches length counter only, it does not do anything with linear counter\n\n========================================================\n\n$4008 write:\n    linear_control = v.7\n    length_enabled = !v.7\n    linear_load = v.6543210\n\n========================================================\n\n$400A write:\n    freq_timer = v                  (low 8 bits)\n\n========================================================\n\n$400B write:\n    freq_timer = v.210              (high 3 bits)\n\n    if( channel_enabled )\n        length_counter = lengthtable[ v.76543 ]\n\n    linear_reload = true\n\n\n========================================================\n\nEvery **CPU** Cycle:\n    ; Note the Triangle is clocked at twice the rate of other channels!\n    ; It is clocked by CPU cycle and not by APU cycle!\n\n    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n    ;; clock tri wave\n\n    ultrasonic = false\n    if( freq_timer < 2 && freq_counter == 0 )\n        ultrasonic = true\n\n    clock_triunit = true\n    if( length_counter == 0 )       clock_triunit = false\n    if( linear_counter == 0 )       clock_triunit = false\n    if( ultrasonic )                clock_triunit = false\n\n    if( clock_triunit )\n    {\n        if( freq_counter > 0 )\n            --freq_counter\n        else\n        {\n            freq_counter = freq_timer\n            tri_step = (tri_step + 1) & 0x1F    ; tri-step bound to 00..1F range\n        }\n    }\n\n\n    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n    ;; determine audio output\n\n    ; the xor here creates the 'triangle' shape\n    if( ultrasonic )                output = 7.5\n    else if( tri_step & 0x10 )      output = tri_step ^ 0x1F\n    else                            output = tri_step\n\n\n========================================================\n\nClock_QuarterFrame:\n    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n    ; quarter frame clocks Linear\n\n    if( linear_reload )\n        linear_counter = linear_load\n    else if( linear_counter > 0 )\n        --linear_counter\n\n    if( !linear_control )\n        linear_reload = false\n\n\n\n========================================================\n\nClock_HalfFrame:\n\n    ; clock Length counter, same as Pulse\n\nNoise:\n\nNotes:\n- noise_shift must never be zero or the noise channel will never produce any output. Initialize it\n  with 1 at bootup / hard reset.\n- with below implementation, noise_shift must not be signed-16 bit (unsigned is OK, or something\n  larget than 16 bit is OK). If signed, the right-shift will feed in unwanted 1s.\n\n$4015 read / write:  Same as Pulse1, only use bit 3 instead of bit 0\n\n========================================================\n\n$400C write:\n    ; same as $4000, only ignore bits 6 and 7 because noise has no duty\n\n========================================================\n\n$400E write:\n    freq_timer = noise_freq_table[ v.3210 ]  ; see http://wiki.nesdev.com/w/index.php/APU_Noise for\n    freq table\n    shift_mode = v.7\n\n========================================================\n\n$400F write:\n    if( channel_enabled )\n        length_counter = lengthtable[ v.76543 ]\n\n    decay_reset_flag = true\n\n========================================================\n\nEvery APU Cycle:\n\n    ;;;;;;;;;;;;;;;;;;;;;;;;;;;\n    ; clock noise shift\n\n    if( freq_counter > 0 )\n        --freq_counter\n    else\n    {\n        freq_counter = freq_timer\n\n                            ; note, set bit fifteen here, not bits 1 and 5\n        if( shift_mode )    noise_shift.15 = noise_shift.6 ^ noise_shift.0\n        else                noise_shift.15 = noise_shift.1 ^ noise_shift.0\n\n        noise_shift >>= 1\n    }\n\n\n    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n    ; determine audio output\n    if(    noise_shift.0 == 0           ; current noise output is low (output vol when low --\n    opposite of pulse)\n        && length_counter != 0          ; length counter is nonzero (channel active)\n        )\n    {\n        ; output current volume\n        if(decay_enabled)       output = decay_hidden_vol\n        else                    output = decay_V\n    }\n    else            ; high shift output, or channel is silent\n        output = 0\n\n\n========================================================\n\nClock_QuarterFrame:\n    ; clock Decay, same as Pulse\n\n\n\n========================================================\n\nClock_HalfFrame:\n    ; clock Length counter, same as Pulse\n\nDMC\n\n$4010 write:\n    dmcirq_enabled =    v.7\n    dmc_loop =          v.6\n    freq_timer =        dmc_freq_table[ v.3210 ]   ; see http://wiki.nesdev.com/w/index.php/APU_DMC for freq table\n    \n    if( !dmcirq_enabled )\n        dmcirq_pending = false  ; acknowledge IRQ if disabled\n      \n    \n\n\n========================================================\n\n$4011 write:\n    output = v.6543210    ; note there is some edge case weirdness here, see wiki for details\n        \n========================================================\n    \n$4012 write:\n    addrload = $C000 | v<<6\n\n========================================================\n    \n$4013 write:\n    lengthload = (v<<4) + 1\n    \n    \n========================================================\n\n$4015 write:\n    if( v.4 )\n    {\n        if( length == 0 )\n        {\n            length = lengthload\n            addr = addrload\n        }\n    }\n    else\n        length = 0\n        \n    dmcirq_pending = false      ; acknowledge DMC IRQ on write\n        \n========================================================\n\n$4015 read:\n    v.4 = (length > 0)\n    v.7 = dmcirq_pending\n    \n    ; ... other channels and frame IRQ set other bits\n\n    \n========================================================\n\nEvery ?CPU? cycle????\n( not sure if DMC runs on APU cycles or CPU cycles.  It doesn't really matter because all the\nfrequencies are even.  The wiki lists freqs in CPU cycles, so.... *shrug* )\n\n\n\n    ;;;;;;;;;;;;;;;;;;;;;;;;\n    ;  Clock DMC unit\n\n    if( freq_counter > 0 )\n        --freq_counter\n    else\n    {\n        freq_counter = freq_timer\n        \n        if( !output_unit_silent )\n        {\n            if( (output_shift & 1) && output < $7E )    output += 2\n            if(!(output_shift & 1) && output > $01 )    output -= 2\n        }\n        --bits_in_output_unit\n        output_shift >>= 1\n            \n        if( bits_in_output_unit == 0 )\n        {\n            bits_in_output_unit = 8\n            output_shift = sample_buffer\n            output_unit_silent = is_sample_buffer_empty\n            is_sample_buffer_empty = true\n        }\n    }\n    \n    ;;;;;;;;;;;;;;;;;;;;;;;;;;\n    ;  Perform DMA if necessary\n    \n    if( length > 0 && is_sample_buffer_empty )\n    {\n        sample_buffer = DMAReadFromCPU( addr )\n        ; note:  this DMA halts the CPU for up to 4 cycles.\n        ;  See wiki for timing details.  Note that all commercial games will work\n        ;  fine if you ignore these stolen cycles, but some tech\n        ;  demos and test ROMs will glitch/fail.  So getting these stolen cycles\n        ;  correct is not super important unless you're putting a lot of emphasis\n        ;  on accuracy.\n        is_sample_buffer_empty = false\n        addr = (addr + 1) | $8000     ; <- wrap $FFFF to $8000\n        --length\n        \n        if(length == 0)\n        {\n            if( dmc_loop )\n            {\n                length = lengthload\n                addr = addrload\n            }\n            else if( dmcirq_enabled )\n                dmcirq_pending = true       ; raise IRQ\n        }\n    }\n    \n    \n    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n    ;; Determine channel output\n    \n    \n    ; output is always 'output' ... the 7 bit value written to $4011 and modified by the DMC unit\n\n\nSpecifically there are 3 filters:\n\n1 lowpass:\nout[i]=(in[i]-out[i-1])*0.815686\n\nand 2 highpass:\nout[i]=out[i-1]*0.996039+in[i]-in[i-1]\nout[i]=out[i-1]*0.999835+in[i]-in[i-1]\n\n// 'sample' is your output sample as generated by your APU\n// 'output' is what you will actually output\n//\n// initialize all intermediate vars to 0.0\n\n// low pass\nLP_In = sample\nLP_Out = (LP_In - LP_Out) * 0.815686\n\n\n// high pass A\nHPA_Out = HPA_Out*0.996039 + LP_Out - HPA_Prev\nHPA_Prev = LP_Out\n\n// high pass B\nHPB_Out = HPB_Out*0.999835 + HPA_Out - HPB_Prev\nHPB_Prev = HPA_Out\n\n\noutput = HPB_Out\n\n// scale output to be within min/max bounds\n\n\nSpit out a line of text at each of the following events:\n\n- 4008-400B writes\n- 4017 writes\n- linear clocks\n- Frame start\n\nOn each line, include the contents of the Linear Counter so you can see how it's being updated. If you can see where your counting is going wrong, you can see where the problem in your code is.\n\nWe wary of denormal numbers. Set very tiny floating point values to 0 to avoid performance issues.\n"
  },
  {
    "path": "docs/apu/blargg_tests_readme.txt",
    "content": "NES APU Frame Counter Update\n----------------------------\n\nI have run more tests on the NES APU and come up with new information\nabout the exact timing of the frame counter and length counter, and some\nsubtle behavior. The information here either extends or contradicts what\nis stated in the NES APU reference and on the nesdev wiki.\n\nNot documented here is a delay when changing modes by writing to $4017.\nThis is quite complex and I haven't fully worked out its exact\noperation. Once determined, documented, and tested, the information here\nshould still be valid. This delay when changing modes involves the\ncurrent mode running a few clocks before switching to the new mode, so\nit only affects the rare case where $4017 is written within a few clocks\nof a frame counter step. This delay does not cause the steps to occur\nany later than shown below; it only causes the first few clocks of the\nnew mode to be transparent, allowing the previous mode to \"show\nthrough\".\n\nAlso not documented is the exact operation of the envelope, sweep, and\ntriangle's linear counter when register writes occur close to clocking.\n\nRefer to tests.txt for a description of the test ROMs included.\n\nI have not yet fully updated my APU emulator and tested it with this\ninformation, so report any problems you have with implementation.\n\nShay <hotpop.com@blargg> (swap to e-mail)\n\n\nClock Jitter\n------------\nChanges to the mode by writing to $4017 only occur on *even* internal\nAPU clocks; if written on an odd clock, the first step of the mode is\ndelayed by one clock. At power-up and reset, the APU is randomly in an\nodd or even cycle with respect to the first clock of the first\ninstruction executed by the CPU.\n\n      ; assume even APU and CPU clocks occur together\n      lda   #$00\n      sta   $4017       ; mode begins in one clock\n      sta   <0          ; delay 3 clocks\n      sta   $4017       ; mode begins immediately\n\n\nMode 0 Timing\n-------------\n-5    lda   #$00\n-3    sta   $4017\n0     (write occurs here)\n1\n2\n3\n...\n      Step 1\n7459  Clock linear\n...\n      Step 2\n14915 Clock linear & length\n...\n      Step 3\n22373 Clock linear\n...\n      Step 4\n29830 Set frame irq\n29831 Clock linear & length and set frame irq\n29832 Set frame irq\n...\n      Step 1\n37289 Clock linear\n...\netc.\n\n\nMode 1 Timing\n-------------\n-5    lda   #$80\n-3    sta   $4017\n0     (write occurs here)\n      Step 0\n1     Clock linear & length\n2\n...\n      Step 1\n7459  Clock linear\n...\n      Step 2\n14915 Clock linear & length\n...\n      Step 3\n22373 Clock linear\n...\n      Step 4\n29829 (do nothing)\n...\n      Step 0\n37283 Clock linear & length\n...\netc.\n\n\nLength Halt\n-----------\nWrite to halt flag is delayed by one clock:\n\n      $10->$4000  clear halt flag\n0     $00->$4017  begin mode 0\n14914 $30->$4000  set halt flag\n14915 Length not clocked\n\n      $10->$4000  clear halt flag\n0     $00->$4017  begin mode 0\n14915 $30->$4000  set halt flag\n      Length clocked\n\n      $30->$4000  set halt flag\n0     $00->$4017  begin mode 0\n14914 $10->$4000  clear halt flag\n14915 Length clocked\n\n      $30->$4000  set halt flag\n0     $00->$4017  begin mode 0\n14915 $10->$4000  clear halt flag\n      Length not clocked\n      \n\n\nLength Reload\n-------------\nLength reload is completely ignored if written during length clocking\nand length counter is non-zero before clocking:\n\n      $38->$4003  make length non-zero\n0     $00->$4017\n14914 Write to $4003\n      Length reloaded\n14915 Length clocked\n\n      $38->$4003  make length non-zero\n0     $00->$4017\n14915 Write to $4003\n      Length not reloaded\n      Length clocked\n\n      $00->$4015  clear length counter\n      $01->$4015\n0     $00->$4017\n14915 Write to $4003\n      Length reloaded\n      Length not clocked\n\nMisc\n----\n- The frame IRQ flag is cleared only when $4015 is read or $4017 is\nwritten with bit 6 set ($40 or $c0).\n\n- The IRQ handler is invoked at minimum 29833 clocks after writing $00\nto $4017 (assuming the frame IRQ flag isn't already set, and nothing\nelse generates an IRQ during that time).\n\n- After reset or power-up, APU acts as if $4017 were written with $00\nfrom 9 to 12 clocks before first instruction begins. It is as if this\noccurs (this generates a 10 clock delay):\n\n      lda   #$00\n      sta   $4017       ; 1\n      lda   <0          ; 9 delay\n      nop\n      nop\n      nop\nreset:\n      ...\n\n- As shown, the frame irq flag is set three times in a row. Thus when\npolling it, always read $4015 an extra time after the flag is found to\nbe set, to be sure it's clear afterwards,\n\nwait: bit   $4015       ; V flag reflects frame IRQ flag\n      bvc   wait\n      bit   $4015       ; be sure irq flag is clear\n\nor better yet, clear it before polling it:\n\n      bit   $4015       ; clear flag first\nwait: bit   $4015       ; V flag reflects frame IRQ flag\n      bvc   wait\n\n"
  },
  {
    "path": "docs/apu/mixer_readme.txt",
    "content": "NES APU Mixer Tests\r\n-------------------\r\nThese tests verify proper operation of the NES APU's sound channel\r\nmixer, including relative volumes of channels and non-linear mixing.\r\nTests MUST be run from a freshly-powered NES, as this is the only way to\r\nensure that the triangle wave doesn't interfere.\r\n\r\nAll tests beep, play a test sound, then beep again. For all but the\r\nnoise test, there should be near silence between the beeps. For the\r\nnoise test, noise will fade in and out. There shouldn't be any\r\nnoticeable tone when heard through a speaker (through headphones, faint\r\ntones might be audible).\r\n\r\n\r\nInternal operation\r\n------------------\r\nThe tests have the channel under test generate a tone, then generate the\r\ninverse waveform using the DMC DAC, canceling to (near) silence if\r\neverything is correct.\r\n\r\nThe DMC test verifies that non-linearity of the DMC DAC. The noise and\r\ntriangle tests verify relative volume of the noise and triangle to the\r\nDMC, and that the DMC DAC affects attenuation of them properly. Finally,\r\nthe square test verifies relative volume of the squares to the DMC,\r\nnon-linearity of the square DACs, how one square affects the other\r\n(slightly), and that the square DAC non-linearity is separate from the\r\nDMC.\r\n\r\n\r\nFlashes, clicks, other glitches\r\n-------------------------------\r\nIf a test prints \"passed\", it passed, even if there were some flashes or\r\nodd sounds. Only a test which prints \"done\" at the end requires that you\r\nwatch/listen while it runs in order to determine whether it passed. Such\r\ntests involve things which the CPU cannot directly test.\r\n\r\n\r\nAlternate output\r\n----------------\r\nTests generally print information on screen, but also report the final\r\nresult audibly, and output text to memory, in case the PPU doesn't work\r\nor there isn't one, as in an NSF or a NES emulator early in development.\r\n\r\nAfter the tests are done, the final result is reported as a series of\r\nbeeps (see below). For NSF builds, any important diagnostic bytes are\r\nalso reported as beeps, before the final result.\r\n\r\n\r\nOutput at $6000\r\n---------------\r\nAll text output is written starting at $6004, with a zero-byte\r\nterminator at the end. As more text is written, the terminator is moved\r\nforward, so an emulator can print the current text at any time.\r\n\r\nThe test status is written to $6000. $80 means the test is running, $81\r\nmeans the test needs the reset button pressed, but delayed by at least\r\n100 msec from now. $00-$7F means the test has completed and given that\r\nresult code.\r\n\r\nTo allow an emulator to know when one of these tests is running and the\r\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\r\n$G1 is written to $6001-$6003.\r\n\r\n\r\nAudible output\r\n--------------\r\nA byte is reported as a series of tones. The code is in binary, with a\r\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\r\nThe first tone is always a zero. A final code of 0 means passed, 1 means\r\nfailure, and 2 or higher indicates a specific reason. See the source\r\ncode of the test for more information about the meaning of a test code.\r\nThey are found after the set_test macro. For example, the cause of test\r\ncode 3 would be found in a line containing set_test 3. Examples:\r\n\r\n\tTones         Binary  Decimal  Meaning\r\n\t- - - - - - - - - - - - - - - - - - - - \r\n\tlow              0      0      passed\r\n\tlow high        01      1      failed\r\n\tlow high low   010      2      error 2\r\n\r\n\r\nNSF versions\r\n------------\r\nMany NSF-based tests require that the NSF player either not interrupt\r\nthe init routine with the play routine, or if it does, not interrupt the\r\nplay routine again if it hasn't returned yet. This is because many tests\r\nneed to run for a while without returning.\r\n\r\nNSF versions also make periodic clicks to prevent the NSF player from\r\nthinking the track is silent and thus ending the track before it's done\r\ntesting.\r\n\r\n-- \r\nShay Green <gblargg@gmail.com>\r\n"
  },
  {
    "path": "docs/apu/test_readme.txt",
    "content": "NES APU Tests\n-------------\nThese ROMs test many aspects of the APU that are visible to the CPU.\nReally obsucre things are not tested here.\n\n\n1-len_ctr\n---------\nTests length counter operation for the four main channels\n\n2) Problem with length counter load or $4015\n3) Problem with length table, timing, or $4015\n4) Writing $80 to $4017 should clock length immediately\n5) Writing 0 to $4017 shouldn't clock length immediately\n6) Disabling via $4015 should clear length counter\n7) When disabled via $4015, length shouldn't allow reloading\n8) Halt bit should suspend length clocking\n\n\n2-len_table\n-----------\nVerifies all length table entries\n\n\n3-irq_flag\n----------\nVerifies basic operation of frame irq flag\n\n2) Flag shouldn't be set in $4017 mode $40\n3) Flag shouldn't be set in $4017 mode $80\n4) Flag should be set in $4017 mode $00\n5) Reading flag should clear it\n6) Writing $00 or $80 to $4017 shouldn't affect flag\n7) Writing $40 or $C0 to $4017 should clear flag\n\n\n4-jitter\n--------\nTests for APU clock jitter. Also tests basic timing of frame irq flag\nsince it's needed to determine jitter.\n\n3) Frame irq is set too late\n4) Even jitter not handled properly\n5) Odd jitter not handled properly\n\n\n5-len_timing\n------------\nVerifies timing of length counter clocks in both modes\n\n2) First length of mode 0 is too soon\n3) First length of mode 0 is too late\n4) Second length of mode 0 is too soon\n5) Second length of mode 0 is too late\n6) Third length of mode 0 is too soon\n7) Third length of mode 0 is too late\n8) First length of mode 1 is too soon\n9) First length of mode 1 is too late\n10) Second length of mode 1 is too soon\n11) Second length of mode 1 is too late\n12) Third length of mode 1 is too soon\n13) Third length of mode 1 is too late\n\n\n6-irq_flag_timing\n-----------------\nFrame interrupt flag is set three times in a row 29831 clocks after\nwriting $00 to $4017.\n\n3) Flag first set too late\n4) Flag last set too soon\n5) Flag last set too late \n\n\n7-dmc_basics\n------------\nVerifies basic DMC operation\n\n2) DMC isn't working well enough to test further\n3) Starting DMC should reload length from $4013\n4) Writing $10 to $4015 should restart DMC if previous sample finished\n5) Writing $10 to $4015 should not affect DMC if previous sample is\nstill playing\n6) Writing $00 to $4015 should stop current sample\n7) Changing $4013 shouldn't affect current sample length\n8) Shouldn't set DMC IRQ flag when flag is disabled\n9) Should set IRQ flag when enabled and sample ends\n10) Reading IRQ flag shouldn't clear it\n11) Writing to $4015 should clear IRQ flag\n12) Disabling IRQ flag should clear it\n13) Looped sample shouldn't end until $00 is written to $4015\n14) Looped sample shouldn't ever set IRQ flag\n15) Clearing loop flag and then setting again shouldn't stop loop\n16) Clearing loop flag should end sample once it reaches end\n17) Looped sample should reload length from $4013 each time it reaches\nend\n18) $4013=0 should give 1-byte sample\n19) There should be a one-byte buffer that's filled immediately if empty\n\n\n8-dmc_rates\n-----------\nVerifies the DMC's 16 rates\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "docs/apu/volume_readme.txt",
    "content": "Volume tests for NES\r\n\r\n_____________________________________________________________________\r\nBackground\r\n\r\nThe NES has four tone generator channels and one digital sample\r\nplayback channel:\r\n\r\n1. Rectangular pulse (\"square\") wave\r\n2. Another square wave\r\n3. 32-step triangle wave\r\n4. Binary noise generated by a linear feedback shift register\r\n5. Delta pulse code modulation; also allows writes of raw LPCM to\r\n   the counter for use as a generic 7-bit DAC\r\n\r\nUnlike systems such as the GBA, which digitally add all channels\r\nbefore passing them to a DAC, the NES has a separate DAC for each\r\nchannel and mixes them analog.  Nonlinearities in this mixing can\r\ncause one channel to affect the volume of another channel.\r\n\r\nThe NES uses an unsigned DAC, meaning that each channel can generate\r\nonly positive signal values.  Such a DAC generates a lot of DC, and\r\nthe NES has a high-pass filter on its audio output to block the DC.\r\nDifferent emulators use different time constants on their DC filters,\r\nwhich human listeners generally can't perceive.  So you can't just\r\nmeasure the maximum voltage; you have to measure the difference\r\nbetween the high and low values.\r\n\r\nDifferent emulators use different amounts of headroom in the 16-bit\r\nrange, depending on what Famicom expansion audio chips are present.\r\nSo you have to compare relative volumes, not absolute volumes.\r\n\r\n_____________________________________________________________________\r\nThe test pattern\r\n\r\nThis program demonstrates the channel balance among implementations\r\nof the NES architecture.  \r\n\r\nThe pattern consists of a set of 12 tones, as close to 1000 Hz as\r\nthe NES allows:\r\n1. Channel 1, 1/8 duty\r\n2. Channel 1, 1/4 duty\r\n3. Channel 1, 1/2 duty\r\n4. Channel 1, 3/4 duty\r\n5. Channels 1 and 2, 1/8 duty\r\n6. Channels 1 and 2, 1/4 duty\r\n7. Channels 1 and 2, 1/2 duty\r\n8. Channels 1 and 2, 3/4 duty\r\n9. Channel 3\r\n10. Channel 4, long LFSR period\r\n11. Channel 4, short LFSR period\r\n12. Channel 5, amplitude 30\r\n\r\nWhen the user presses A on controller 1, the pattern plays three\r\ntimes, with channel 5 held steady at 0, 48, and 96.  The high point\r\nof tone 12 each time is 30 units above the level for that time,\r\nthat is, 30, 78, and 126 respectively.\r\n\r\n_____________________________________________________________________\r\nRecordings\r\n\r\nThe files in the 'recordings' folder are recordings of volumes.nes\r\nrun in various environments.  They were recorded at 44100 Hz and then\r\nencoded using OggDropXPd 1.90 (libvorbis 1.2.0) at -q 7.00.\r\n\r\n* nes-001.ogg: Nintendo Entertainment System (NTSC U/C) with PowerPak\r\n* nestopia.ogg: Nestopia 1.40\r\n* nintendulator.ogg: Nintendulator snapshot 2009-02-28\r\n* fceux.ogg: FCEUX 2.0.4-interim 2008-11-24\r\n\r\n_____________________________________________________________________\r\nLegal\r\n\r\nCopyright (c) 2009 Damian Yerrick\r\n\r\nThe program and manual are under the following license:\r\n\r\n  This work is provided 'as-is', without any express or implied\r\n  warranty. In no event will the authors be held liable for any\r\n  damages arising from the use of this work.\r\n\r\n  Permission is granted to anyone to use this work for any\r\n  purpose, including commercial applications, and to alter it and\r\n  redistribute it freely, subject to the following restrictions:\r\n\r\n   1. The origin of this work must not be misrepresented; you\r\n      must not claim that you wrote the original work. If you use\r\n      this work in a product, an acknowledgment in the product\r\n      documentation would be appreciated but is not required.\r\n   2. Altered source versions must be plainly marked as such,\r\n      and must not be misrepresented as being the original work.\r\n   3. This notice may not be removed or altered from any\r\n      source distribution.\r\n\r\n  The term \"source\" refers to the preferred form of a work for making\r\n  changes to it. \r\n\r\n"
  },
  {
    "path": "docs/cartridge_board_list.txt",
    "content": "10 Yard Fight                        Nintendo                 E   NROM\n110 in 1 (Canada?)                   Supervision\n1942                                 Capcom                   C+  NROM\n1943                                 Capcom                   C+  UNROM\n6 in 1                               Caltron                  A\n720                                  Mindscape                C   SGROM\n8 Eyes                               Taxan                    C-  TLROM\nA Boy and His Blob                   Absolute                 C   SLROM\nAbadox                               Milton Bradley           C   SLROM\nAction 52                            Active Enterprises       A   023-N507\nAddams Family                        Ocean                    B   SLROM\nAdv of Bayou Billy                   Konami                   C-  SLROM\nAdv of Dino Riki                     Hudson                   C   CNROM\nAdv of Lolo                          HAL                      C+  SEROM\nAdv of Lolo 2                        HAL                      B-  TEROM\nAdv of Lolo 3                        HAL                      B+  SLROM\nAdv of Tom Saywer                    Seta                     B   SLROM\nAdventure Island                     Hudson                   D   CNROM\nAdventure Island 2                   Hudson                   B-  TLROM\nAdventure Island 3                   Hudson                   B   TLROM\nAfter Burner                         Tengen                   C+  800042-01\nAir Fortress                         HAL                      C   SJROM\nAirwolf                              Acclaim                  C   SHROM\nAl Unser Racing                      Data East                C   SKROM\nAladdin Deck Enhancer                Camerica                 A+\nAlfred Chicken                       Mindscape                B+  UNROM\nAlien 3                              LJN                      B+  55741\nAlien Syndrome                       Tengen                   C+\nAll Pro Basketball                   Vic Tokai                C   SLROM\nAlpha Mission                        SNK                      C   CNROM\nAmagon                               American Sammy           C   UNROM\nAmerican Gladiators                  Gametek                  B-  SLROM\nAnticipation                         Nintendo                 C   SEROM\nArch Rivals                          Acclaim                  C   AMROM\nArchon                               Activision               B   UNROM\nArkanoid                             Taito                    B+  CNROM\nArkanoid Controller                  Taito                    A\nArkistas Ring                        American Sammy           B   CNROM\nAstyanax                             Jaleco                   C   TLROM\nAthena                               SNK                      C   UNROM\nAthletic World                       Bandai                   B-  CNROM\nAttack of the Killer Tomatoes        T*HQ                     B-  SLROM\nBaby Boomer                          Color Dreams             B-\nBack to the Future                   LJN                      D   CNROM\nBack to the Future 2 & 3             LJN                      C   SLROM\nBad Dudes                            Data East                C   TLROM\nBad News Baseball                    Tecmo                    B   SLROM\nBad Street Brawler                   Mattel                   C   SGROM\nBalloon Fight                        Nintendo                 C   NROM\nBandit Kings/China                   Koei                     B-  ETROM\nBarbie                               Hi Tech                  B   SLROM\nBard's Tale                          FCI                      B-  SNROM\nBase Wars                            Ultra                    C   TKROM\nBaseball                             Nintendo                 E   NROM\nBaseball Simulator                   Culture Brain            C+  SKROM\nBaseball Stars                       SNK                      C+  SKROM\nBaseball Stars 2                     Romstar                  B+  TKROM\nBases Loaded                         Jaleco                   E   SFROM\nBases Loaded 2                       Jaleco                   D   SL3ROM\nBases Loaded 3                       Jaleco                   C   TLROM\nBases Loaded 4                       Jaleco                   C+  TLROM\nBatman                               Sunsoft                  D   NES_B4\nBatman Return/Joker                  Sunsoft                  B-  NES_BTR\nBatman Returns                       Konami                   B+  TLROM\nBattle Chess                         Data East                B+  SKROM\nBattle of Olympus                    Broderbund               C   SGROM\nBattle Tank                          Absolute                 B   CNROM\nBattle Toads                         Tradewest                D   AOROM\nBattle Toads & Double Dragon         Tradewest                B+  AOROM\nBattleship                           Mindscape                B   CNROM\nBee 52                               Camerica                 B+\nBeetlejuice                          LJN                      C+  AMROM\nBest of the Best                     Electro Brain            B+  UOROM\nBible Adventures                     Wisdom Tree              B+\nBible Adventures (blue label)        Wisdom Tree              B+\nBible Buffet                         Wisdom Tree              B+\nBig Bird Hide & Speak                Hi Tech                  B   SLROM\nBig Foot                             Acclaim                  B-  SLROM\nBig Nose Freaks Out                  Camerica                 A\nBig Nose Freaks Out (Aladdin Cart)   Camerica                 A+\nBig Nose the Caveman                 Camerica                 B\nBill & Ted's Excellent Adventure     LJN                      C+  SLROM\nBill Elliott's NASCAR Challenge      Konami                   C+  TSROM\nBionic Commando                      Capcom                   C   SGROM\nBlack Bass                           Hot B                    B   UNROM\nBlackjack                            American Video           A-\nBlades of Steel                      Konami                   D   UNROM\nBlaster Master                       Sunsoft                  D   SL2ROM\nBlue Marlin                          Hot B                    B+  TLROM\nBlues Brothers                       Titus                    B   UNROM\nBo Jackson Baseball                  Data East                C+  TSROM\nBomberman                            Hudson                   C   NROM\nBomberman 2                          Hudson                   B   SNROM\nBonks Adventure                      Hudson                   B+  TLROM\nBoulder Dash                         JVC                      B+  SEROM\nBreak Time                           FCI                      B+  SFROM\nBreakthru                            Data East                C   (SLROM)\nBubble Bath Babes                    Panesian                 A+\nBubble Bobble                        Taito                    D   SFROM\nBubble Bobble 2                      Taito                    B   TLROM\nBucky O'Hare                         Konami                   B   TLROM\nBugs Bunny Birthday Blowout          Kemco                    B   TSROM\nBugs Bunny Crazy Castle              Kemco                    B+  SBROM\nBump & Jump                          Vic Tokai                B-  CNROM\nBurai Fighter                        Taxan                    B-  TEROM\nBurger Time                          Data East                B-  NROM\nCabal                                Milton Bradley           C   AMROM\nCaesars Palace                       Virgin                   B   UNROM\nCalifornia Games                     Milton Bradley           C   UNROM\nCaptain America                      Data East                B   TLROM\nCaptain Comic                        Color Dreams             B\nCaptain Planet                       Mindscape                B   TLROM\nCaptain Skyhawk                      Milton Bradley           C   AMROM\nCasino Kid                           Sofel                    C+  UNROM\nCasino Kid 2                         Sofel                    B+  UNROM\nCastelian                            Triffix                  B   UNROM\nCastle of Deceit                     Bunch Games              B\nCastle of Dragon                     Seta                     B   UNROM\nCastlequest                          Nexoft                   B   CNROM\nCastlevania                          Konami                   E   UNROM\nCastlevania 2                        Konami                   D   SLROM\nCastlevania 3                        Konami                   C-  ELROM\nCaveman Games                        Data East                B-  SLROM\nChallenge of the Dragon              Color Dreams             B\nChampionship Bowling                 Romstar                  B   CNROM\nChampionship Pool                    Mindscape                B+  UNROM\nCheetahman II                        Active Enterprises       A\nChessmaster                          Hi Tech                  B   SJROM\nChiller                              American Game Carts Inc  B+\nChubby Cherub                        Bandai                   B   NROM\nCircus Caper                         Toho                     B   SLROM\nCity Connection                      Jaleco                   B   CNROM\nClash at Demonhead                   Vic Tokai                B-  SLROM\nClassic Concentration                Gametek                  B-  UNROM\nCliffhanger                          Imagesoft                B   TLROM\nClu Clu Land                         Nintendo                 B-  NES_RROM\nCobra Command                        Data East                C   SLROM\nCobra Triangle                       Nintendo                 B-  ANROM\nCodename Viper                       Capcom                   C   TLROM\nColor A Dinosaur                     Virgin                   A-  UNROM\nCommando                             Capcom                   C   UNROM\nConan                                Mindscape                B   UNROM\nConflict                             Vic Tokai                C+  SKROM\nConquest Crystal Palace              Asmik                    C+  TLROM\nContra                               Konami                   E   UNROM\nContra Force                         Konami                   B   TLROM\nCool World                           Ocean                    B   SLROM\nCowboy Kid                           Romstar                  A-  TLROM\nCrash & the Boys Street Challange    American Technos         B   TLROM\nCrash Dummies                        LJN                      B   55741\nCrystal Mines                        Color Dreams             B+\nCrystalis                            SNK                      C   TKROM\nCyberball                            Jaleco                   B   TLROM\nCybernoid                            Acclaim                  C   CNROM\nDance Aerobics                       Nintendo                 C+  SBROM\nDarkman                              Ocean                    B   SLROM\nDarkwing Duck                        Capcom                   B+  SLROM\nDash Galaxy                          Data East                C   CNROM\nDay Dreamin' Davey                   HAL                      B+  SLROM\nDays of Thunder                      Mindscape                C+  TLROM\nDeadly Towers                        Broderbund               C+  BNROM\nDeath Race                           American Game Carts Inc  B+\nDeathbots                            American Video           B+\nDefender 2                           HAL                      B   NROM\nDefender of the Crown                Ultra                    C+  SGROM\nDefenders of Dynacron City           JVC                      B   TLROM\nDeja Vu                              Kemco                    C+  TKROM\nDemon Sword                          Taito                    C+  SL1ROM\nDesert Commander                     Kemco                    C-  SKROM\nDestination Earthstar                Acclaim                  C   CNROM\nDestiny/Emporer                      Capcom                   B-  SNROM\nDick Tracy                           Bandai                   C+  UNROM\nDie Hard                             Activision               B+  SLROM\nDig Dug 2                            Bandai                   B   NROM\nDigger                               Milton Bradley           B+  AMROM\nDirty Harry                          Mindscape                C+  TLROM\nDisney Adventure                     Capcom                   B   SLROM\nDizzy the Adventurer (Aladdin Cart)  Camerica                 A+\nDonkey Kong                          Nintendo                 B-  NROM\nDonkey Kong 3                        Nintendo                 B-  NROM\nDonkey Kong Classics                 Nintendo                 B-  CNROM\nDonkey Kong Jr                       Nintendo                 B-  NROM\nDonkey Kong Jr Math                  Nintendo                 A-  NROM\nDouble Dare                          Gametek                  B   AOROM\nDouble Dragon                        Tradewest                E   SLROM\nDouble Dragon 2                      Acclaim                  D   TL1ROM\nDouble Dragon 3                      Acclaim                  C+  TLROM\nDouble Dribble                       Konami                   D   UNROM\nDouble Strike                        American Video           B+\nDr. Chaos                            FCI                      C   UNROM\nDr. Jeckyl/Mr. Hyde                  Bandai                   C+  SFROM (SFDOROM)\nDr. Mario                            Nintendo                 D   SEROM\nDracula                              Imagesoft                B   TSROM\nDragon Fighter                       Sofel                    B   SLROM\nDragon Power                         Bandai                   C   GNROM\nDragon Spirit                        Bandai                   B   TLROM\nDragon Strike                        FCI                      C   TLROM\nDragon Warrior                       Nintendo                 E   SAROM\nDragon Warrior 2                     Enix                     C+  SNROM\nDragon Warrior 3                     Enix                     B+  SUROM\nDragon Warrior 4                     Enix                     B   SUROM\nDragon's Lair                        Imagesoft                B   UNROM\nDuck Hunt                            Nintendo                 F   NROM\nDuck Tales                           Capcom                   C+  UNROM\nDuck Tales 2                         Capcom                   B+  UNROM\nDudes with Attitude                  American Video           B+\nDungeon Magic                        Taito                    C   SKROM\nDusty Diamond All Star Softball      Broderbund               B-  SLROM\nDyno Warz                            Bandai                   C+  SLROM\nElevator Action                      Taito                    B-  NROM\nEliminator Boat Duel                 Electro Brain            C+  SLROM\nEmpire Strikes Back                  JVC                      B   TLROM\nExcitebike                           Nintendo                 E   NROM\nExodus                               Wisdom Tree              B+\nF 117 Stealth                        Microprose               B+  TLROM\nF 15 City War                        American Video           B\nF 15 Strike Eagle                    Microprose               B+  TLROM\nF1 Built to Win                      Seta                     B+  SKROM\nFamily Feud                          Gametek                  B+  SHROM\nFantastic Adv Dizzy (Aladdin Cart)   Camerica                 A+\nFantastic Adventures of Dizzy        Camerica                 B-\nFantasy Zone                         Tengen                   C\nFaria                                Nexoft                   B+  SKROM\nFast Break                           Tradewest                C   SCROM\nFaxanadu                             Nintendo                 C-  SGROM\nFelix the Cat                        Hudson                   B+  TSROM\nFerrari Grand Prix                   Acclaim                  B   SLROM\nFester's Quest                       Sunsoft                  C   SLROM\nFighting Golf                        SNK                      C   SLROM\nFinal Fantasy                        Nintendo                 C+  SNROM\nFire & Ice                           Tecmo                    A-  TLROM\nFire Hawk                            Camerica                 B\nFirehouse Rescue                     Gametek                  B   CNROM\nFist of the North Star               Taxan                    C+  UNROM\nFlight of the Intruder               Mindscape                B   UNROM\nFlintstones                          Taito                    C+  TLROM\nFlintstones 2                        Taito                    A+\nFlying Dragon                        Culture Brain            C   UNROM\nFlying Warriors                      Culture Brain            C   SLROM\nFrankenstein                         Bandai                   B+  SLRROM\nFreedom Force                        Sunsoft                  D   SLROM\nFriday The 13th                      LJN                      C-  CNROM\nFun House                            Hi Tech                  B+  UNROM\nG I Joe                              Taxan                    B+  TLROM\nG I Joe Atlantis Factor              Capcom                   C+  TLROM\nGalactic Crusader                    Bunch Games              B\nGalaga                               Bandai                   B   NROM\nGalaxy 5000                          Activision               A-  TLROM\nGame Action Replay                   STD                      A-\nGame Genie                           Galoob                   D\nGargoyle's Quest 2                   Capcom                   B+  TLROM\nGauntlet (licensed)                  Tengen                   C   DRROM\nGauntlet (unlicensed)                Tengen                   C\nGauntlet 2                           Mindscape                C   TSROM\nGemfire                              Koei                     B-  EKROM\nGenghis Kahn                         Koei                     C+  SOROM\nGeorge Forman                        Acclaim                  B   55741\nGhost & Goblins                      Capcom                   C   UNROM\nGhost Lion                           Kemco                    B   SKROM\nGhostbusters                         Activision               B-  CNROM\nGhostbusters 2                       Activision               B   SLROM\nGhoul School                         Electro Brain            B-  SLROM\nGilligans Island                     Bandai                   B+  UNROM\nGoal                                 Jaleco                   D   SL3ROM\nGoal 2                               Jaleco                   B-  TLSROM\nGodzilla                             Toho                     C+  SLROM\nGodzilla 2                           Toho                     B   SLROM\nGold Metal Challenge                 Capcom                   B+  TKROM\nGolf                                 Nintendo                 E   NROM\nGolf Grand Slam                      Atlus                    B+  SLROM\nGolf Power                           Virgin                   B+  SNROM\nGolgo 13 Top Secret Episode          Vic Tokai                D   SLROM\nGoonies 2                            Konami                   C+  351258\nGotcha                               LJN                      C   CNROM\nGradius                              Konami                   C-  CNROM\nGreat Waldo Search                   T*HQ                     B-  SLROM\nGremlins 2                           Sunsoft                  B-  TLROM\nGuardian Legend                      Broderbund               C+  UNROM\nGuerrilla War                        SNK                      C   SLROM\nGum Shoe                             Nintendo                 C+  GNROM\nGun Nac                              Ascii                    B   TLROM\nGunsmoke                             Capcom                   B-  UNROM\nGyromite                             Nintendo                 E   NROM\nGyruss                               Ultra                    C+  CNROM\nHarlem Globetrotters                 Gametek                  B   SLROM\nHatris                               Bullet Proof             A   SNROM\nHeavy Barrel                         Data East                C   TLROM\nHeavy Shreddin'                      Parker Brothers          B-  SLROM\nHeroes of the Lance                  FCI                      C   SKROM\nHigh Speed                           Tradewest                B-  TQROM\nHillsfar                             FCI                      B   SNROM\nHogan's Alley                        Nintendo                 D   NROM\nHollywood Squares                    Gametek                  B+  UNROM\nHome Alone                           T*HQ                     B-  TSROM\nHome Alone 2                         T*HQ                     B-  TLROM\nHook                                 Imagesoft                B   SLROM\nHoops                                Jaleco                   D   SLROM\nHot Slots                            Panesian                 A+\nHudson Hawk                          Imagesoft                B   SLROM\nHunt for Red October                 Hi Tech                  C   TLROM\nHydlide                              FCI                      D   NROM\nI Can Remember                       Gametek                  B   CNROM\nIce Climber                          Nintendo                 C+  NROM\nIce Hockey                           Nintendo                 D   NROM\nIkari Warriors                       SNK                      C-  UNROM\nIkari Warriors 2                     SNK                      C-  SGROM\nIkari Warriors 3                     SNK                      B-  SLROM\nImage Fight                          Irem                     B   TSROM\nImmortal                             Electronic Arts          B   TLROM\nImpossible Mission 2                 American Video           A-\nImpossible Mission 2                 SEI                      B+\nIndiana Jones Last Crusade           UBI Soft                 B+  UNROM\nIndiana Jones Last Crusade           Taito                    B+  SGROM\nIndiana Jones Temple of Doom         Tengen                   B-\nIndiana Jones Temple of Doom         Mindscape                C   TFROM\nIndy Heat                            Tradewest                B   AMROM\nInfiltrator                          Mindscape                B   TLROM\nIron Tank                            SNK                      C   SLROM\nIsolated Warrior                     NTVIC                    B   TLROM\nJack Nicklaus Golf                   Konami                   B-  351258\nJackal                               Konami                   C   UNROM\nJackie Chan Kung Fu                  Hudson                   B   TLROM\nJames Bond Jr                        T*HQ                     B-  TLROM\nJaws                                 LJN                      D   CNROM\nJeopardy                             Gametek                  C   AOROM\nJeopardy 25th Anniversary            Gametek                  C+  ANROM\nJeopardy Jr                          Gametek                  C   ANROM\nJeopardy Super                       Gametek                  B   SLROM\nJetsons                              Taito                    B+  TLROM\nJimmy Connors Tennis                 UBI Soft                 B+  UNROM\nJoe & Mac                            Data East                B+  TLROM\nJordan vs. Bird                      Milton Bradley           C   UNROM\nJoshua                               Wisdom Tree              B+\nJourney to Silius                    Sunsoft                  C+  SLROM\nJoust                                HAL                      B   CNROM\nJungle Book                          Virgin                   A-  TLROM\nJurassic Park                        Ocean                    B   TSROM\nKarate Champ                         Data East                C   CNROM\nKarate Kid                           LJN                      C   CNROM\nKarnov                               Data East                C   DEIROM\nKick Master                          Taito                    C+  TLROM\nKickle Cubicle                       Irem                     B+  TLROM\nKid Icarus                           Nintendo                 C-  SNROM\nKid Klown                            Kemco                    B   TSROM\nKid Kool                             Vic Tokai                C+  UNROM\nKid Niki                             Data East                C   SGROM\nKing Neptune's Adventure             Color Dreams             B+\nKing of Kings                        Wisdom Tree              B+\nKing of Kings (cartoon mule)         Wisdom Tree              B+\nKing of the Ring                     Acclaim                  B+  55741\nKings Knight                         Square                   C+  CNROM\nKings of the Beach                   Ultra                    C   CNROM\nKings Quest 5                        Konami                   B+  TSROM\nKirbys Adventure                     Nintendo                 B-  TKROM\nKiwi Kraze                           Taito                    B+  TLROM\nKlash Ball                           Sofel                    B-  UNROM\nKlax                                 Tengen                   B+\nKnight Rider                         Acclaim                  B-  SCIROM\nKrazy Kreatures                      American Video           B+\nKrion Conquest                       Vic Tokai                B   TLROM\nKrusty's Fun House                   Acclaim                  C+  TLROM\nKung Fu                              Nintendo                 E   NROM\nKung Fu Heroes                       Culture Brain            C   CNROM\nL'Empereur                           Koei                     B   ETROM\nLaser Invasion                       Konami                   B-  ELROM\nLast Action Hero                     Imagesoft                B+  TLROM\nLast Ninja                           Jaleco                   B-  TLROM\nLast Starfighter                     Mindscape                C+  CNROM\nLegacy/Wizard                        Broderbund               C   TFROM\nLegend of Kage                       Taito                    C-  CNROM\nLegendary Wings                      Capcom                   B   UNROM\nLegends/Diamond                      Bandai                   B+  TLROM\nLemmings                             Sunsoft                  B-  SLROM\nLethal Weapon                        Ocean                    B   SLROM\nLife Force                           Konami                   C   351258\nLinus Spacehead                      Camerica                 B\nLinus Spacehead (Aladdin Cart)       Camerica                 A+\nLittle League Baseball               SNK                      B-  SLROM\nLittle Mermaid                       Capcom                   B   UNROM\nLittle Nemo                          Capcom                   B+  TLROM\nLittle Ninja Brothers                Culture Brain            C   TLROM\nLittle Sampson                       Taito                    B   TLROM\nLode Runner                          Broderbund               B   NROM\nLone Ranger                          Konami                   B-  TLROM\nLoopz                                Mindscape                B+  UNROM\nLow G Man                            Taxan                    C   TLROM\nLunar Pool                           FCI                      C+  NROM\nM C Kids                             Virgin                   C+  TSROM\nM.U.L.E.                             Mindscape                C+  SNROM\nMach Rider                           Nintendo                 C-  NROM\nMad Max                              Mindscape                C+  TLROM\nMafat Conspiracy                     Vic Tokai                C   TLROM\nMagic Darts                          Romstar                  B   SLRROM\nMagic of Scheherazade                Culture Brain            C-  SLROM\nMagician                             Taxan                    A-  TKROM\nMagmax                               FCI                      C-  NROM\nMajor League Baseball                LJN                      E   CNROM\nManiac Mansion                       Jaleco                   C+  SNROM\nMappyland                            Taxan                    C+  TFROM\nMarble Madness                       Milton Bradley           C+  ANROM\nMario Brothers                       Nintendo                 B-  NROM\nMario Is Missing                     Mindscape                B   TLROM\nMario Time Machine                   Mindscape                A-  TLROM\nMarvel's X-Men                       LJN                      B-  UNROM\nMaster Chu & the Drunkard Hu         Color Dreams             B\nMaxi 15                              American Video           A\nMechanized Attack                    SNK                      C   SCROM\nMega Man                             Capcom                   B-  UNROM\nMega Man 2                           Capcom                   C   SGROM\nMega Man 3                           Capcom                   C   TLROM\nMega Man 4                           Capcom                   B-  TGROM\nMega Man 5                           Capcom                   B   TLROM\nMega Man 6                           Nintendo                 B+  TGROM\nMenace Beach                         Color Dreams             A-\nMendel Palace                        Hudson                   B   TLROM\nMermaids of Atlantis                 American Video           B+\nMetal Fighter                        Color Dreams             B\nMetal Gear                           Ultra                    D   UNROM\nMetal Mech                           Jaleco                   B-  SLROM\nMetal Storm                          Irem                     B+  TLROM\nMetroid                              Nintendo                 D   SNROM\nMichael Andretti World GP            American Sammy           B-  TLROM\nMickey Mousecapade                   Capcom                   C+  CNROM\nMickey Numbers                       Hi Tech                  B+  TLROM\nMickey's Safari in Letterland        Hi Tech                  B+  55741\nMicro Machines                       Camerica                 A-\nMicro Machines (Aladdin Cart)        Camerica                 A+\nMig 29                               Camerica                 C+\nMight & Magic                        American Sammy           B+  TKROM\nMighty Bombjack                      Tecmo                    B   CNROM\nMighty Final Fight                   Capcom                   B+  TLROM\nMike Tyson's Punch Out               Nintendo                 D   PNROM\nMillipede                            HAL                      B   NROM\nMilon's Secret Castle                Hudson                   C   CNROM\nMiracle Piano                        Mindscape                A-  SJROM\nMission Cobra                        Bunch Games              A-\nMission Impossible                   Ultra                    C+  352026\nMonopoly                             Parker Brothers          B-  SLROM\nMonster in my Pocket                 Konami                   B+  TLROM\nMonster Party                        Bandai                   B   SLROM\nMonster Truck Rally                  INTV                     B   CNROM\nMoon Ranger                          Bunch Games              C\nMotor City Patrol                    Matchbox                 C+  SLROM\nMs. Pacman                           Namco                    A   NROM\nMs. Pacman                           Tengen                   B+\nMuppet Adventure                     Hi Tech                  B   SGROM\nMuscle                               Bandai                   B-  NROM\nMutant Virus                         American Software        B+  SLROM\nMystery Quest                        Taxan                    C+  CNROM\nNARC                                 Acclaim                  C   AMROM\nNES Open Golf                        Nintendo                 B-  SNROM\nNFL Football                         LJN                      C   UNROM\nNigel Mansell                        Gametek                  B+  SLROM\nNightmare on Elm Street              LJN                      B   AMROM\nNightshade                           Ultra                    C+  TLROM\nNinja Crusaders                      American Sammy           B   TGROM\nNinja Gaiden                         Tecmo                    E   SLROM\nNinja Gaiden 2                       Tecmo                    C-  TLROM\nNinja Gaiden 3                       Tecmo                    C+  TLROM\nNinja Kid                            Bandai                   C+  CNROM\nNombunagas Ambition                  Koei                     C   SOROM\nNombunagas Ambition 2                Koei                     B   ETROM\nNorth & South                        Kemco                    B+  TSROM\nOperation Secret Storm               Color Dreams             A\nOperation Wolf                       Taito                    D   SLROM\nORB-3D                               Hi Tech                  C   SCROM\nOthello                              Acclaim                  C   NROM\nOverlord                             Virgin                   B-  SN1\nP'radikus Conflict                   Color Dreams             B\nPac Man                              Namco                    A   56504\nPac Man (licensed)                   Tengen                   B-  NROM\nPac Man (unlicensed)                 Tengen                   B-\nPac Mania                            Tengen                   A\nPalamedes                            Hot B                    B+  SEROM\nPanic Resturant                      Taito                    B   TLROM\nPaperboy                             Mindscape                C   CNROM\nPaperboy 2                           Mindscape                B   UOROM\nParodius (England)                   Palcom\nPebble Beach Golf                    Bandai                   C+  CNROM\nPeek A Boo Poker                     Panesian                 A+\nPerfect Fit                          Gametek                  B   CNROM\nPesterminator                        Color Dreams             B\nPeter Pan & the Pirates              T*HQ                     B-  SFROM\nPhantom Fighter                      FCI                      C+  SGROM\nPictionary                           LJN                      B-  SLROM\nPinball                              Nintendo                 D   NROM\nPinball Quest                        Jaleco                   B   SLROM\nPinbot                               Nintendo                 B   TQROM\nPipe Dream                           Bullet Proof             B   CNROM\nPirates                              Ultra                    B   SKROM\nPlatoon                              Sunsoft                  D   SLROM\nPlay Action Football                 Nintendo                 D   TLSROM\nPool of Radiance                     FCI                      B+  TKROM\nPopeye                               Nintendo                 B-  NROM\nPOW                                  SNK                      C   SLROM\nPower Blade                          Taito                    B-  TLROM\nPower Blade 2                        Taito                    B-  TLROM\nPower Punch 2                        American Softworks       B   TLROM\nPredator                             Activision               C+  SLROM\nPrince of Persia                     Virgin                   C+  UNROM\nPrincess Tomato                      Hudson                   B+  SGROM\nPro Am                               Nintendo                 D   SEROM\nPro Am 2                             Tradewest                B   AOROM\nPro Sport Hockey                     Jaleco                   B+  TLSROM\nPro Wrestling                        Nintendo                 D   UNROM\nPuggsly's Scavenger Hunt             Ocean                    B   SLROM\nPunch Out                            Nintendo                 C+  PNROM\nPunisher                             LJN                      B   TLROM\nPuss N Boots                         Electro Brain            B-  UNROM\nPuzzle                               American Video           A-\nPuzznic                              Taito                    B+  CNROM\nPyramid                              American Video           A-\nQ*Bert                               Ultra                    B-  CNROM\nQix                                  Taito                    A   SNROM\nQuantum Fighter                      HAL                      B   TLROM\nQuarterback                          Tradewest                D   CNROM\nQuattro Adventure                    Camerica                 B+\nQuattro Adventure (Aladdin Cart)     Camerica                 A+\nQuattro Arcade                       Camerica                 A-\nQuattro Sports                       Camerica                 B\nQuattro Sports (Aladdin Cart)        Camerica                 A+\nRace America                         Absolute                 B+  SLROM\nRacket Attack                        Jaleco                   C-  SLROM\nRad Gravity                          Activision               B-  SLROM\nRad Racer                            Nintendo                 D   SGROM\nRad Racer 2                          Square                   B-  TVROM\nRad Racket                           American Video           A-\nRaid 2020                            Color Dreams             B\nRaid on Bungling Bay                 Broderbund               B   NROM\nRainbow Island                       Taito                    B+  UNROM\nRally Bike                           Romstar                  B   UNROM\nRambo                                Acclaim                  E   UNROM\nRampage                              Data East                C+  TFROM\nRampart                              Jaleco                   B   TLROM\nRBI Baseball (licensed)              Tengen                   C+  DEROM\nRBI Baseball (unlicensed)            Tengen                   C\nRBI Baseball 2                       Tengen                   C+\nRBI Baseball 3                       Tengen                   B-\nRemote Control                       Hi Tech                  C   SLROM\nRen + Stimpy Buckaroos               T*HQ                     B-  TLROM\nRenegade                             Taito                    C-  UNROM\nRescue                               Kemco                    C-  SLROM\nRescue Rangers                       Capcom                   C+  SLROM\nRescue Rangers 2                     Capcom                   B+  SLROM\nRing King                            Data East                C   DEROM\nRiver City Ransom                    American Technos         C   TLROM\nRoad Blasters                        Mindscape                C+  SLROM\nRoad Runner                          Tengen                   B+\nRobin Hood                           Virgin                   C+  SGROM\nRobo Cop                             Data East                C   TL1ROM\nRobo Cop 2                           Data East                B-  SLROM\nRobo Cop 3                           Ocean                    B   SLROM\nRobo Demons                          Color Dreams             B\nRobo Warrior                         Jaleco                   B   UNROM\nRock N Ball                          NTVIC                    B   TFROM\nRocket Ranger                        Kemco                    B-  SGROM\nRocketeer                            Bandai                   B-  SGROM\nRockin Kats                          Atlus                    B+  TLROM\nRocky & Bullwinkle                   T*HQ                     B-  TLROM\nRoger Clemens                        LJN                      C   53361\nRoller Games                         Ultra                    B-  TLROM\nRollerball                           HAL                      B   SFROM\nRollerblade Racer                    Hi Tech                  B   53361\nRolling Thunder                      Tengen                   C+\nRomance/3 Kingdoms                   Koei                     C   SOROM\nRomance/3 Kingdoms 2                 Koei                     B   EWROM\nRoundball                            Mindscape                B-  TSROM\nRush N Attack                        Konami                   D   UNROM\nRygar                                Tecmo                    C   UNROM\nSCAT                                 Natsume                  B   SLROM\nSecret Scout                         Color Dreams             A\nSection Z                            Capcom                   C   UNROM\nSeicross                             FCI                      D   NROM\nSesame Street 1-2-3                  Hi Tech                  C+  SEROM (SCROROM)\nSesame Street 123/ABC                Hi Tech                  B   SLROM\nSesame Street A-B-C                  Hi Tech                  C+  SEROM\nSesame Street Countdown              Hi Tech                  A-  SLROM\nShadow of the Ninja                  Natsume                  B   TLROM\nShadowgate                           Kemco                    C+  TKROM\nShatterhand                          Jaleco                   B-  TLROM\nShingen the Ruler                    Hot B                    C   SNROM\nShinobi                              Tengen                   C+\nShockwave                            American Game Carts Inc  B\nShooting Range                       Bandai                   B   CNROM\nShort Order/Eggsplode                Nintendo                 B+  SBROM\nSide Pocket                          Data East                B   UNROM\nSilent Assault                       Color Dreams             B\nSilent Service                       Ultra                    C-  351258\nSilk Worm                            American Sammy           C   SLROM\nSilver Surfer                        Arcadia                  B   TSROM\nSimpsons Bart Meets Radioactive Man  Acclaim                  B+  55741\nSimpsons Bart Vs Space Mutants       Acclaim                  D   SLROM\nSimpsons Bart Vs World               Acclaim                  B-  53361\nSkate or Die                         Ultra                    D   351258\nSkate or Die 2                       Electronic Arts          B   SLROM\nSki or Die                           Ultra                    B-  351908\nSkull & Crossbones                   Tengen                   B\nSky Kid                              Sunsoft                  B-  SCEOROM\nSky Shark                            Taito                    D   SL1ROM\nSlalom                               Nintendo                 B-  NROM\nSmash TV                             Acclaim                  C+  51555\nSnake Rattle & Roll                  Nintendo                 C+  SEROM\nSnakes Revenge                       Ultra                    C   SLROM\nSnoopy Silly Sports                  Kemco                    B+  SLROM\nSnow Brothers                        Capcom                   B   SLROM\nSoccer                               Nintendo                 D   NROM\nSolar Jetman                         Tradewest                C   AOROM\nSolitaire                            American Video           A-\nSoloman's Key                        Tecmo                    C+  CNROM\nSolstice                             Imagesoft                C   ANROM\nSpace Shuttle                        Absolute                 B+  SGROM\nSpelunker                            Broderbund               C+  NROM\nSpiderman                            LJN                      B-  53361\nSpiritual Warfare                    Wisdom Tree              B+\nSpot                                 Arcadia                  B-  SNROM\nSpy Hunter                           Sunsoft                  C   CNROM\nSpy vs. Spy                          Kemco                    B-  NROM\nSqoon                                Irem                     B+  NROM\nStack Up                             Nintendo                 A-  HVC\nStadium Events                       Bandai                   B\nStanley                              Electro Brain            B   TLROM\nStar Force                           Tecmo                    C+  CNROM\nStar Soldier                         Taxan                    C-  CNROM\nStar Trek 25th Anniversary           Ultra                    B   TLROM\nStar Trek: The Next Generation       Absolute                 A-  UNROM\nStar Tropics                         Nintendo                 C-  HKROM\nStar Voyager                         Acclaim                  C+  CNROM\nStar Wars                            JVC                      B   TSROM\nStarship Hector                      Hudson                   B-  UNROM\nStealth ATF                          Activision               C+  SLROM\nStinger                              Konami                   B-  UNROM\nStreet Cop                           Bandai                   A-  SLROM\nStreet Fighter 2010                  Capcom                   B   TLROM\nStrider                              Capcom                   C   SGROM\nStunt Kids                           Camerica                 A-\nSunday Funday                        Wisdom Tree              B+\nSuper C                              Konami                   C   352026\nSuper Cars                           Electro Brain            B+  UNROM\nSuper Dodge Ball                     Imagesoft                C   SLROM\nSuper Glove Ball                     Mattel                   C   UNROM\nSuper Mario Brothers                 Nintendo                 F   NROM\nSuper Mario Brothers 2               Nintendo                 D   TSROM\nSuper Mario Brothers 3               Nintendo                 D   TSROM\nSuper Mario/Duck Hunt                Nintendo                 F   MH\nSuper Mario/Duck Hunt/Track Meet     Nintendo                 C   COB\nSuper Off Road                       Tradewest                C-  AMROM\nSuper Pitfall                        Activision               C+  UNROM\nSuper Spike V'Ball                   Nintendo                 C-  TLROM\nSuper Spike/World Cup                Nintendo                 B-  COB\nSuper Sprint                         Tengen                   C   COB\nSuper Spy Hunter                     Sunsoft                  B+  TLROM\nSuper Team Games                     Nintendo                 C-  CNROM\nSuperman                             Kemco                    B+  SLROM\nSwamp Thing                          T*HQ                     C+  SLROM\nSword Master                         Activision               A-  TLROM\nSwords & Serpents                    Acclaim                  B+  UNROM\nT&C Surf Design                      LJN                      E   CNROM\nTaboo                                Tradewest                C+  SEROM\nTag Team Wrestling                   Data East                C   NROM\nTaggin Dragon                        Bunch Games              B\nTalespin                             Capcom                   B   SLROM\nTarget Renegade                      Taito                    C+  SLROM\nTecmo Baseball                       Tecmo                    C   SGROM\nTecmo Basketball                     Tecmo                    C+  TKROM\nTecmo Bowl                           Tecmo                    E   SLROM\nTecmo Cup Soccer                     Tecmo                    B+  SLROM\nTecmo Super Bowl                     Tecmo                    C-  TKROM\nTecmo Wrestling                      Tecmo                    C   SLROM\nTeenage Turtles                      Ultra                    E   351908\nTeenage Turtles 2                    Ultra                    D   TLROM\nTeenage Turtles 3                    Konami                   B-  TLROM\nTeenage Turtles Tournament Fighters  Konami                   B+  TLROM\nTennis                               Nintendo                 D   NROM\nTerminator                           Mindscape                B+  TLROM\nTerminator 2 Judgement Day           LJN                      B-  53361\nTerra Cresta                         Vic Tokai                B+  UNROM\nTetris                               Tengen                   A-  (CNROM compatible)\nTetris                               Nintendo                 D   SEROM\nTetris 2                             Nintendo                 B-  TSROM\nThree Stooges                        Activision               C+  SLROM\nThrilla Safari                       LJN                      B   53361\nThunder & Lightning                  Romstar                  B+  GNROM\nThunderbirds                         Activision               C+  SLROM\nThundercade                          American Sammy           C+  UNROM\nTiger Heli                           Acclaim                  C+  CNROM\nTiles of Fate                        American Video           B+\nTime Lord                            Milton Bradley           C   AMROM\nTimes of Lore                        Toho                     B-  UNROM\nTiny Toon                            Konami                   C+  TLROM\nTiny Toon Cartoon Workshop           Konami                   B+  TSROM\nTiny Toons 2                         Konami                   B+  TLROM\nTo The Earth                         Nintendo                 C+  TEROM\nToki                                 Taito                    B+  TLROM\nTom & Jerry                          Hi Tech                  B+  TLROM\nTombs & Treasures                    Infocom                  A-  SGROM\nToobin                               Tengen                   B\nTop Gun                              Konami                   D   351298\nTop Gun 2                            Konami                   D   352026\nTop Players Tennis                   Asmik                    C+  SLROM\nTotal Recall                         Acclaim                  D   UNROM\nTotally Rad                          Jaleco                   B-  TLROM\nTouchdown Fever                      SNK                      B+  SFROM\nToxic Crusader                       Bandai                   B   TLROM\nTrack & Field                        Konami                   D   CNROM\nTrack & Field 2                      Konami                   D   SLROM\nTreasure Master                      American Softworks       A-  SLROM\nTrick Shooting                       Nintendo                 C+  SCROM\nTrog                                 Acclaim                  B+  UNROM\nTrojan                               Capcom                   C   UNROM\nTrolls on Treasure Island            American Video           A-\nTwin Cobra                           American Sammy           C+  TLROM\nTwin Eagle                           Romstar                  C+  UNROM\nUltima/Exodus                        FCI                      C-  SNROM\nUltima/Quest Avatar                  FCI                      B   SNROM\nUltima/War Destiny                   FCI                      B+  SNROM\nUltimate Air Combat                  Activision               B+  TLROM\nUltimate Basketball                  American Sammy           C   TLROM\nUltimate League Soccer               American Video           A-\nUltimate Stuntman                    Camerica                 C+\nUncharted Waters                     Koei                     B-  ETROM\nUninvited                            Kemco                    B+  TKROM\nUntouchables                         Ocean                    B-  SLROM\nUrban Champion                       Nintendo                 D   NROM\nVegas Dream                          HAL                      B   SKROM\nVenice Beach Volleyball              American Video           A-\nVice Project Doom                    American Sammy           C+  TLROM\nVideomation                          T*HQ                     B+  CPROM\nVindicators                          Tengen                   C\nVolleyball                           Nintendo                 C   NROM\nWacky Races                          Atlus                    B+  TLROM\nWall Street Kid                      Sofel                    B   UNROM\nWally Bear and the No Gang           American Video           A-\nWario Woods                          Nintendo                 B+  TKROM\nWayne Gretzky                        T*HQ                     C+  UNROM\nWayne's World                        T*HQ                     B   TLROM\nWerewolf                             Data East                C   TLROM\nWheel/Fortune                        Gametek                  C   AOROM\nWheel/Fortune/Family                 Gametek                  C   ANROM\nWheel/Fortune/Junior                 Gametek                  C   ANROM\nWheel/Fortune/Vanna                  Gametek                  B   AOROM\nWhere in Time is Carmen              Konami                   B   TSROM\nWhere's Waldo                        T*HQ                     C+  TSROM\nWho Framed Roger Rabbit              LJN                      B-  ANROM\nWhomp Em                             Jaleco                   B   TLROM\nWidget                               Atlus                    B   TLROM\nWild Gunman                          Nintendo                 C   NROM\nWillow                               Capcom                   B-  SLROM\nWin Lose or Draw                     Hi Tech                  C   SGROM\nWinter Games                         Acclaim                  B-  UNROM\nWizardry                             Nexoft                   C+  SKROM\nWizardry 2 Knight of Diamonds        Ascii                    B+  TKROM\nWizards & Warriors                   Acclaim                  D   ANROM\nWizards & Warriors 2 - Ironsword     Acclaim                  D   AOROM\nWizards & Warriors 3                 Acclaim                  C-  54425\nWolverine                            LJN                      B-  TLROM\nWorld Champ                          Romstar                  B+  TLROM\nWorld Championship Wrestling         FCI                      D   TLROM\nWorld Class Track Meet               Nintendo                 D   CNROM\nWorld Cup Soccer                     Nintendo                 C-  TLROM\nWorld Games                          Milton Bradley           B-  ANROM\nWorld Runner 3D                      Acclaim                  C-  UNROM\nWrath of the Black Manta             Taito                    C+  SLROM\nWrecking Crew                        Nintendo                 C+  NROM\nWrestlemania                         Acclaim                  D   ANROM\nWrestlemania Challenge               LJN                      C-  UNROM\nWrestlemania Steel Cage              LJN                      C+  53361\nWurm                                 Asmik                    B-  TLROM\nXenophobe                            Sunsoft                  B-  SFROM\nXevious                              Bandai                   C+  NROM\nXexyz                                Hudson                   C+  SLROM\nYo Noid                              Capcom                   C+  SLROM\nYoshi                                Nintendo                 C-  SFROM\nYoshi's Cookie                       Nintendo                 B-  TFROM\nYoung Indy                           Jaleco                   B+  TLROM\nZanac                                FCI                      C+  UNROM\nZelda                                Nintendo                 E   SNROM\nZelda 2                              Nintendo                 D   SKROM\nZen                                  Konami                   B   TLROM\nZoda's Revenge, Startropics 2        Nintendo                 B-  HKROM\nZombie Nation                        Meldac                   B   TLROM\n"
  },
  {
    "path": "docs/cpu/branch_timing_readme.txt",
    "content": "NES 6502 Branch Timing Test ROMs\n--------------------------------\nThese ROMs test timing of the branch instruction, including edge cases\nwhich an emulator might get wrong. When run on a NES they all give a\npassing result. Each ROM runs several tests and reports the result on\nscreen and by beeping a number of times. See below for the meaning of\nfailure codes for each test. THE TESTS MUST BE RUN (*AND* *PASS*) IN\nORDER, because some earlier ROMs test things that later ones assume will\nwork properly.\n\nSource code for each test is included, and most tests are clearly\ndivided into sections. Support code is also included, but it runs on a\ncustom devcart and assembler so it will require some effort to assemble.\nContact me if you'd like assistance porting them to your setup.\n\n\nBranch Timing Summary\n---------------------\nAn untaken branch takes 2 clocks. A taken branch takes 3 clocks. A taken\nbranch that crosses a page takes 4 clocks. Page crossing occurs when the\nhigh byte of the branch target address is different than the high byte\nof address of the next instruction:\n\nbranch_target:\n\t...\n\tbne branch_target\nnext_instruction:\n\tnop\n\t...\nbranch_target:\n\n\n1.Branch_Basics\n---------------\nTests branch timing basics and PPU NMI timing, which is needed for the\ntests\n\n2) NMI period is too short\n3) NMI period is too long\n4) Branch not taken is too long\n5) Branch not taken is too short\n6) Branch taken is too long\n7) Branch taken is too short\n\n\n2.Backward_Branch\n-----------------\nTests backward (negative) branch timing.\n\n2) Branch from $E4FD to $E4FC is too long\n3) Branch from $E4FD to $E4FC is too short\n4) Branch from $E5FE to $E5FD is too long\n5) Branch from $E5FE to $E5FD is too short\n6) Branch from $E700 to $E6FF is too long\n7) Branch from $E700 to $E6FF is too short\n8) Branch from $E801 to $E800 is too long\n9) Branch from $E801 to $E800 is too short\n\n\n3.Forward_Branch\n----------------\nTests forward (positive) branch timing.\n\n2) Branch from $E5FC to $E5FF is too long\n3) Branch from $E5FC to $E5FF is too short\n4) Branch from $E6FD to $E700 is too long\n5) Branch from $E6FD to $E700 is too short\n6) Branch from $E7FE to $E801 is too long\n7) Branch from $E7FE to $E801 is too short\n8) Branch from $E8FF to $E902 is too long\n9) Branch from $E8FF to $E902 is too short\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "docs/cpu/dummy_writes_readme.txt",
    "content": "NES Double-Write Behavior Tests\n----------------------------------\nThese tests verify that the CPU is doing double-writes properly.\n\nDouble-write is a side effect of the NES CPU when it is executing\na read-modify-write instruction: It first reads the original value,\nthen writes back the same value, and then writes the modified value.\n\n\nFor example, the cycle by cycle listing of an absolute-addressing\ninstruction such as INC is as follows\n(from 65doc.txt by John West and Marko Mkel):\n\n     Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,\n                                     SLO, SRE, RLA, RRA, ISB, DCP)\n\n        #  address R/W description\n       --- ------- --- ------------------------------------------\n        1    PC     R  fetch opcode, increment PC\n        2    PC     R  fetch low byte of address, increment PC\n        3    PC     R  fetch high byte of address, increment PC\n        4  address  R  read from effective address\n        5  address  W  write the value back to effective address,\n                       and do the operation on it\n        6  address  W  write the new value to effective address\n\n\nTwo sets of tests are provided:\nOne that uses OAM data ($2004) for testing, and one that\nuses PPU memory ($2007).\n\nThe OAM data testing is only valid on emulators. The actual NES console\nfails the test, because the OAM read port is not reliable on the real\nconsole.\n\nThe PPUMEM test can be used on emulators and on the real NES.\n\nThe PPUMEM test requires that the emulator implements open bus behavior\nproperly. Without open bus behavior the testing will not work as expected.\nBecause of that, an extensive set of tests is first performed for\nthe open bus behavior.\n\nTests in the OAM version:\n\n\t #2: OAM reading is too unreliable.\n\t #3: Writes to OAM should automatically increment SPRADDR\n\t #4: Reads from OAM should not automatically increment SPRADDR\n\t #5: Some opcodes failed the test.\n\t #6: OAM reads are unreliable.\n\t #7: ROM should not be writable.\n\nFail codes #2 and #6 are basically the same thing, except #2 is\ngiven if #5 also fails. If #5 passes, but the OAM read test\nfailed, #6 is given instead.\n\nExpected output in the OAM version:\n\tTEST: cpu_dummy_writes_oam\n\tThis program verifies that the\n\tCPU does 2x writes properly.\n\tAny read-modify-write opcode\n\tshould first write the origi-\n\tnal value; then the calculated\n\tvalue exactly 1 cycle later.\n\n\tRequirement: OAM memory reads\n\tMUST be reliable. This is\n\toften the case on emulators,\n\tbut NOT on the real NES.\n\tNevertheless, this test can be\n\tused to see if the CPU in the\n\temulator is built properly.\n\n\tTesting OAM.  The screen will go blank for a moment now.\n\tOK; Verifying opcodes...\n\t0E2E4E6ECEEE 1E3E5E7EDEFE \n\t0F2F4F6FCFEF 1F3F5F7FDFFF \n\t03234363C3E3 13335373D3F3 \n\t1B3B5B7BDBFB              \n\n\tPassed\n\nTests in the PPUMEM version:\n\n\t#2: Non-palette PPU memory reads should have one-byte buffer\n\t#3: A single write to $2005 must not change the address used by $2007 when vblank is on.\n\t#4: Even two writes to $2005 must not change the address used by $2007 when vblank is on.\n\t#5: A single write to $2006 must not change the address used by $2007 when vblank is on.\n\t#6: A single write to $2005 must change the address toggle for both $2005 and $2006.\n\t#7: Sequential PPU memory read does not work\n\t#8: Sequential PPU memory write does not work\n\t#9: Some opcodes failed the test.\n\t#10: Open bus behavior is wrong.\n\t#11: ROM should not be writable.\n\nExpected output in the PPUMEM version:\n\n\tTEST: cpu_dummy_writes_ppumem\n\tThis program verifies that the\n\tCPU does 2x writes properly.\n\tAny read-modify-write opcode\n\tshould first write the origi-\n\tnal value; then the calculated\n\tvalue exactly 1 cycle later.\n\n\tVerifying open bus behavior.\n\t      W- W- WR W- W- W- W- WR\n\t2000+ 0  1  2  3  4  5  6  7 \n\t  R0: 0- 0- 00 0- 0- 0- 0- 00\n\t  R1: 0- 0- 00 0- 0- 0- 0- 00\n\t  R3: 0- 0- 00 0- 0- 0- 0- 00\n\t  R5: 0- 0- 00 0- 0- 0- 0- 00\n\t  R6: 0- 0- 00 0- 0- 0- 0- 00\n\tOK; Verifying opcodes...\n\t0E2E4E6ECEEE 1E3E5E7EDEFE \n\t0F2F4F6FCFEF 1F3F5F7FDFFF \n\t03234363C3E3 13335373D3F3 \n\t1B3B5B7BDBFB              \n\n\tPassed\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe text output may include ANSI color codes, which take the form of\nan esc character ($1B), an opening bracket ('['), and a sequence of\nnumbers and semicolon characters, terminated by a non-digit character ('m').\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\n-- \nShay Green <gblargg@gmail.com>\nJoel Yliluoma <bisqwit@iki.fi>\n"
  },
  {
    "path": "docs/cpu/exec_space_readme.txt",
    "content": "NES Memory Execution Tests\n----------------------------------\nThese tests verify that the CPU can execute code from any possible\nmemory location, even if that is mapped as I/O space.\n\nIn addition, two obscure side effects are tested:\n\n1. The PPU open bus. Any write to PPU will update the open bus.\n   Reading from 2002 updates the low 5 bits. Reading from 2007\n   updates 8 bits. The open bus is shown in any addresss/bit\n   that the PPU does not write to. Read from 2000, you get open bus.\n   Read from 2006, ditto. Read from 2002, you get that in high 3 bits.\n   Additionally, the open bus decays automatically to zero in about one\n   second if not refreshed.\n   This test requires that a value written to $2003 can be read back\n   from $2001 within a time window of one or two frames.\n\n2. One-byte opcodes must issue a dummy read to the byte immediately\n   following that opcode. The CPU always does a fetch of the second\n   byte, before it has even begun executing the opcode in the first\n   place.\n\nAdditionally, the following PPU features must be working properly:\n\n1. PPU memory writes and reads through $2006/$2007\n2. The address high/low toggle reset at $2002\n3. A single write through $2006 must not affect the address used by $2007\n4. NMI should fire sometimes to salvage a broken program, if the JSR/JMP\n   never reaches its intended destination. (Only required in the\n   test IF the CPU and/or open bus are not working properly.)\n\nThe test is done FIVE times: Once with JSR $2001, again with JMP $2001,\nand then with RTS (with target address of $2001), and then with a JMP\nthat expects to return with an RTI opcode. Finally, with a regular\nJSR, but the return from the code is done through a BRK instruction.\n\nTests and results:\n\n\t #2: PPU memory access through $2007 does not work properly. (Use other tests to determine the exact problem.)\n\t #3: PPU open bus implementation is missing or incomplete: A write to $2003, followed by a read from $2001 should return the same value as was written.\n\t #4: The RTS at $2001 was never executed. (If NMI has not been implemented in the emulator, the symptom of this failure is that the program crashes and does not output either \"Fail\" nor \"Passed\").\n\t #5: An RTS opcode should still do a dummy fetch of the next opcode.  (The same goes for all one-byte opcodes, really.)\n\t #6: I have no idea what happened, but the test did not work as supposed to. In any case, the problem is in the PPU.\n\t #7: A jump to $2001 should never execute code from $8001 / $9001 / $A001 / $B001 / $C001 / $D001 / $E001.\n\t #8: Okay, the test passed when JSR was used, but NOT when the opcode was JMP. I definitely did not think any emulator would trigger this result.\n\t #9: Your PPU is broken in mind-defyingly random ways.\n\t #10: RTS to $2001 never returned. This message never gets displayed.\n\t #11: The test passed when JSR was used, and when JMP was used, but NOT when RTS was used. Caught ya! Paranoia wins.\n\t #12: Your PPU gave up reason at the last moment.\n\t #13: JMP to $2001 never returned. Again, this message never gets displayed.\n\t #14: An RTI opcode should still do a dummy fetch of the next opcode.  (The same goes for all one-byte opcodes, really.)\n\t #15: An RTI opcode should not destroy the PPU. Somehow that still appears to be the case here.\n\t #16: IRQ occurred uncalled\n\t #17: JSR to $2001 never returned. (Never displayed)\n\t #18: The BRK instruction should issue an automatic fetch of the byte that follows right after the BRK. (The same goes for all one-byte opcodes, but with BRK it should be a bit more obvious than with others.)\n\t #19: A BRK opcode should not destroy the PPU. Somehow that still appears to be the case here.\n\t \n\nExpected output:\n\tTEST:test_cpu_exec_space_ppuio\n\tThis program verifies that the\n\tCPU can execute code from any\n\tpossible location that it can\n\taddress, including I/O space.\n\n\tIn addition, it will be tested\n\tthat an RTS instruction does a\n\tdummy read of the byte that\n\timmediately follows the\n\tinstructions.\n\n\tJSR+RTS TEST OK\n\tJMP+RTS TEST OK\n\tRTS+RTS TEST OK\n\tJMP+RTI TEST OK\n\tJMP+BRK TEST OK\n\n\tPassed\n\nExpected output in the other test:\n\n\tTEST: test_cpu_exec_space_apu\n\tThis program verifies that the\n\tCPU can execute code from any\n\tpossible location that it can\n\taddress, including I/O space.\n\t\n\tIn this test, it is also\n\tverified that not only all\n\twrite-only APU I/O ports\n\treturn the open bus, but\n\talso the unallocated I/O\n\tspace in $4018..$40FF.\n\t\n\t40FF 40\n\tPassed\n\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe text output may include ANSI color codes, which take the form of\nan esc character ($1B), an opening bracket ('['), and a sequence of\nnumbers and semicolon characters, terminated by a non-digit character ('m').\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\n-- \nShay Green <gblargg@gmail.com>\nJoel Yliluoma <bisqwit@iki.fi>\n"
  },
  {
    "path": "docs/cpu/instr_misc_readme.txt",
    "content": "NES CPU Instruction Behavior Misc Tests\r\n----------------------------------------\r\nThese tests verify miscellaneous instruction behavior.\r\n\r\n\r\n01-abs_x_wrap\r\n-------------\r\nVerifies that $FFFF wraps around to 0 for STA abs,X and LDA abs,X.\r\n\r\n\r\n02-branch_wrap\r\n--------------\r\nVerifies that branching past end or before beginning of RAM wraps\r\naround.\r\n\r\n\r\n03-dummy_reads\r\n--------------\r\nTests some instructions that do dummy reads before the real read/write.\r\nDoesn't test all instructions.\r\n\r\nTests LDA and STA with modes (ZP,X), (ZP),Y and ABS,X\r\nDummy reads for the following cases are tested:\r\n\r\nLDA ABS,X or (ZP),Y when carry is generated from low byte\r\nSTA ABS,X or (ZP),Y\r\nROL ABS,X always\r\n\r\n\r\n04-dummy_reads_apu\r\n------------------\r\nTests dummy reads for (hopefully) ALL instructions which do them,\r\nincluding unofficial ones. Prints opcode(s) of failed instructions.\r\nRequires that APU implement $4015 IRQ flag reading.\r\n\r\n\r\nMulti-tests\r\n-----------\r\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\r\nWhen run, they list the subtests as they are run. The final result code\r\nrefers to the first sub-test that failed. For more information about any\r\nfailed subtests, run them individually from rom_singles/ and\r\nnsf_singles/.\r\n\r\n\r\nFlashes, clicks, other glitches\r\n-------------------------------\r\nIf a test prints \"passed\", it passed, even if there were some flashes or\r\nodd sounds. Only a test which prints \"done\" at the end requires that you\r\nwatch/listen while it runs in order to determine whether it passed. Such\r\ntests involve things which the CPU cannot directly test.\r\n\r\n\r\nText output\r\n-----------\r\nTests generally print information on screen, but also output information\r\nin other ways, in case the PPU doesn't work or there isn't one, as in an\r\nNSF or a NES emulator early in development.\r\n\r\nWhen building as an NSF, the final result is reported as a series of\r\nbeeps (see below). Any important diagnostic bytes are also reported as\r\nbeeps, before the final result.\r\n\r\nAll text output is written starting at $6004, with a zero-byte\r\nterminator at the end. As more text is written, the terminator is moved\r\nforward, so an emulator can print the current text at any time.\r\n\r\nThe test status is written to $6000. $80 means the test is running, $81\r\nmeans the test needs the reset button pressed, but delayed by at least\r\n100 msec from now. $00-$7F means the test has completed and given that\r\nresult code.\r\n\r\nTo allow an emulator to know when one of these tests is running and the\r\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\r\n$G1 is written to $6001-$6003.\r\n\r\nSee the source code for more information about a particular test and why\r\nit might be failing. Each test has comments anout its operation.\r\n\r\n\r\nNSF versions\r\n------------\r\nMany NSF-based tests require that the NSF player either not interrupt\r\nthe init routine with the play routine, or if it does, not interrupt the\r\nplay routine again if it hasn't returned yet. This is because many tests\r\nneed to run for a while without returning.\r\n\r\nNSF versions also make periodic clicks to prevent the NSF player from\r\nthinking the track is silent and thus ending the track before it's done\r\ntesting.\r\n\r\nIn addition to the other text output methods described above, NSF builds\r\nreport essential information bytes audibly, including the final result.\r\nA byte is reported as a series of tones. The code is in binary, with a\r\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\r\nThe first tone is always a zero. A final code of 0 means passed, 1 means\r\nfailure, and 2 or higher indicates a specific reason as listed in the\r\nsource code by the corresponding set_code line. Examples:\r\n\r\n\tTones         Binary  Decimal  Meaning\r\n\t- - - - - - - - - - - - - - - - - - - - \r\n\tlow              0      0      passed\r\n\tlow high        01      1      failed\r\n\tlow high low   010      2      error 2\r\n\r\n-- \r\nShay Green <gblargg@gmail.com>\r\n"
  },
  {
    "path": "docs/cpu/instr_test_readme.txt",
    "content": "NES CPU Instruction Behavior Tests\r\n----------------------------------\r\nThese tests verify most instruction behavior fairly thoroughly,\r\nincluding unofficial instructions. Failing instructions are listed by\r\ntheir opcode and name. Serious errors in behavior of basic opcodes might\r\ncause many false errors. These tests will NOT help you figure out what\r\nis wrong with your implementation of the failed instructions, simply\r\nwhether you are failing any, and which those are.\r\n\r\nall_instrs.nes tests (almost) all instructions, including unofficial\r\nones, while official_only.nes tests only official (\"documented\")\r\ninstructions. The *_singles/ test all instructions, but test the\r\nofficial ones first, so you can tell whether you pass those even if your\r\nemulator hangs on the unofficial ones.\r\n\r\nThe nsf_singles builds audibly report the opcodes of any failed\r\ninstructions before the final result.\r\n\r\n\r\nInternal operation\r\n------------------\r\nInstructions are tested by setting many combinations of input values for\r\nregisters, flags, and memory, running the instruction under test, then\r\nupdating a running checksum with the resulting values. After trying all\r\ninteresting input combinations, the checksum is compared with the\r\ncorrect one to find whether the instruction passed.\r\n\r\nThis approach is used for all instructions, even those that shouldn't\r\ncare the value of any registers or modify them. This catches an emulator\r\nincorrectly looking at or modifying registers in those instructions.\r\n\r\nThis approach makes it very easy to write the tests, since the\r\ninstructions don't have to be each coded for separately; instead, only\r\nthe different addressing modes need separate tests.\r\n\r\ninstrs: what opcodes to test, along with their names.\r\n\r\ninstr_template: template for instructions. First byte is replaced with\r\nopcode. After executing instruction and anything after, it should jump\r\nto instr_done.\r\n\r\noperand: where to place byte operand for instruction. This value comes\r\nfrom the table of values to test, using an index separate from that used\r\nto set the other registers before executing the instruction.\r\n\r\nset_in: things to execute before the instruction. On entry, A is value\r\nput in operand, and Y is index used in table.\r\n\r\ncheck_out: things to execute after the instruction.\r\n\r\nvalues2: if defined, set of values to use for operand. Default uses same\r\nset as for other registers.\r\n\r\ntest_values: routine to actually run the tests. test_normal does what's\r\ndescribed above.\r\n\r\ncorrect_checksums: list of checksums for each instruction. Generated\r\nwhen CALIBRATE=1 is uncommented.\r\n\r\n\r\nInstructions\r\n------------\r\nU = Unofficial\r\nX = Freezes CPU, so not tested\r\n? = Inconsistent/unknown behavior, so not tested\r\n\r\n00   BRK #n\r\n01   ORA (z,X)\r\n02 X KIL\r\n03 U SLO (z,X)\r\n04 U DOP z\r\n05   ORA z\r\n06   ASL z\r\n07 U SLO z\r\n08   PHP\r\n09   ORA #n\r\n0A   ASL A\r\n0B U AAC #n\r\n0C U TOP abs\r\n0D   ORA a\r\n0E   ASL a\r\n0F U SLO abs\r\n10   BPL r\r\n11   ORA (z),Y\r\n12 X KIL\r\n13 U SLO (z),Y\r\n14 U DOP z,X\r\n15   ORA z,X\r\n16   ASL z,X\r\n17 U SLO z,X\r\n18   CLC\r\n19   ORA a,Y\r\n1A U NOP\r\n1B U SLO abs,Y\r\n1C U TOP abs,X\r\n1D   ORA a,X\r\n1E   ASL a,X\r\n1F U SLO abs,X\r\n20   JSR a\r\n21   AND (z,X)\r\n22 X KIL\r\n23 U RLA (z,X)\r\n24   BIT z\r\n25   AND z\r\n26   ROL z\r\n27 U RLA z\r\n28   PLP\r\n29   AND #n\r\n2A   ROL A\r\n2B U AAC #n\r\n2C   BIT a\r\n2D   AND a\r\n2E   ROL a\r\n2F U RLA abs\r\n30   BMI r\r\n31   AND (z),Y\r\n32 X KIL\r\n33 U RLA (z),Y\r\n34 U DOP z,X\r\n35   AND z,X\r\n36   ROL z,X\r\n37 U RLA z,X\r\n38   SEC\r\n39   AND a,Y\r\n3A U NOP\r\n3B U RLA abs,Y\r\n3C U TOP abs,X\r\n3D   AND a,X\r\n3E   ROL a,X\r\n3F U RLA abs,X\r\n40   RTI\r\n41   EOR (z,X)\r\n42 X KIL\r\n43 U SRE (z,X)\r\n44 U DOP z\r\n45   EOR z\r\n46   LSR z\r\n47 U SRE z\r\n48   PHA\r\n49   EOR #n\r\n4A   LSR A\r\n4B U ASR #n\r\n4C   JMP a\r\n4D   EOR a\r\n4E   LSR a\r\n4F U SRE abs\r\n50   BVC r\r\n51   EOR (z),Y\r\n52 X KIL\r\n53 U SRE (z),Y\r\n54 U DOP z,X\r\n55   EOR z,X\r\n56   LSR z,X\r\n57 U SRE z,X\r\n58   CLI\r\n59   EOR a,Y\r\n5A U NOP\r\n5B U SRE abs,Y\r\n5C U TOP abs,X\r\n5D   EOR a,X\r\n5E   LSR a,X\r\n5F U SRE abs,X\r\n60   RTS\r\n61   ADC (z,X)\r\n62 X KIL\r\n63 U RRA (z,X)\r\n64 U DOP z\r\n65   ADC z\r\n66   ROR z\r\n67 U RRA z\r\n68   PLA\r\n69   ADC #n\r\n6A   ROR A\r\n6B U ARR #n\r\n6C   JMP (a)\r\n6D   ADC a\r\n6E   ROR a\r\n6F U RRA abs\r\n70   BVS r\r\n71   ADC (z),Y\r\n72 X KIL\r\n73 U RRA (z),Y\r\n74 U DOP z,X\r\n75   ADC z,X\r\n76   ROR z,X\r\n77 U RRA z,X\r\n78   SEI\r\n79   ADC a,Y\r\n7A U NOP\r\n7B U RRA abs,Y\r\n7C U TOP abs,X\r\n7D   ADC a,X\r\n7E   ROR a,X\r\n7F U RRA abs,X\r\n80 U DOP #n\r\n81   STA (z,X)\r\n82 U DOP #n\r\n83 U AAX (z,X)\r\n84   STY z\r\n85   STA z\r\n86   STX z\r\n87 U AAX z\r\n88   DEY\r\n89 U DOP #n\r\n8A   TXA\r\n8B ? XAA #n\r\n8C   STY a\r\n8D   STA a\r\n8E   STX a\r\n8F U AAX abs\r\n90   BCC r\r\n91   STA (z),Y\r\n92 X KIL\r\n93 ? AXA (z),Y\r\n94   STY z,X\r\n95   STA z,X\r\n96   STX z,Y\r\n97 U AAX z,Y\r\n98   TYA\r\n99   STA a,Y\r\n9A   TXS\r\n9B ? XAS abs,Y\r\n9C U SYA abs,X\r\n9D   STA a,X\r\n9E U SXA abs,Y\r\n9F ? AXA abs,Y\r\nA0   LDY #n\r\nA1   LDA (z,X)\r\nA2   LDX #n\r\nA3 U LAX (z,X)\r\nA4   LDY z\r\nA5   LDA z\r\nA6   LDX z\r\nA7 U LAX z\r\nA8   TAY\r\nA9   LDA #n\r\nAA   TAX\r\nAB U ATX #n\r\nAC   LDY a\r\nAD   LDA a\r\nAE   LDX a\r\nAF U LAX abs\r\nB0   BCS r\r\nB1   LDA (z),Y\r\nB2 X KIL\r\nB3 U LAX (z),Y\r\nB4   LDY z,X\r\nB5   LDA z,X\r\nB6   LDX z,Y\r\nB7 U LAX z,Y\r\nB8   CLV\r\nB9   LDA a,Y\r\nBA   TSX\r\nBB ? LAR abs,Y\r\nBC   LDY a,X\r\nBD   LDA a,X\r\nBE   LDX a,Y\r\nBF U LAX abs,Y\r\nC0   CPY #n\r\nC1   CMP (z,X)\r\nC2 U DOP #n\r\nC3 U DCP (z,X)\r\nC4   CPY z\r\nC5   CMP z\r\nC6   DEC z\r\nC7 U DCP z\r\nC8   INY\r\nC9   CMP #n\r\nCA   DEX\r\nCB U AXS #n\r\nCC   CPY a\r\nCD   CMP a\r\nCE   DEC a\r\nCF U DCP abs\r\nD0   BNE r\r\nD1   CMP (z),Y\r\nD2 X KIL\r\nD3 U DCP (z),Y\r\nD4 U DOP z,X\r\nD5   CMP z,X\r\nD6   DEC z,X\r\nD7 U DCP z,X\r\nD8   CLD\r\nD9   CMP a,Y\r\nDA U NOP\r\nDB U DCP abs,Y\r\nDC U TOP abs,X\r\nDD   CMP a,X\r\nDE   DEC a,X\r\nDF U DCP abs,X\r\nE0   CPX #n\r\nE1   SBC (z,X)\r\nE2 U DOP #n\r\nE3 U ISC (z,X)\r\nE4   CPX z\r\nE5   SBC z\r\nE6   INC z\r\nE7 U ISC z\r\nE8   INX\r\nE9   SBC #n\r\nEA   NOP\r\nEB U SBC #n\r\nEC   CPX a\r\nED   SBC a\r\nEE   INC a\r\nEF U ISC abs\r\nF0   BEQ r\r\nF1   SBC (z),Y\r\nF2 X KIL\r\nF3 U ISC (z),Y\r\nF4 U DOP z,X\r\nF5   SBC z,X\r\nF6   INC z,X\r\nF7 U ISC z,X\r\nF8   SED\r\nF9   SBC a,Y\r\nFA U NOP\r\nFB U ISC abs,Y\r\nFC U TOP abs,X\r\nFD   SBC a,X\r\nFE   INC a,X\r\nFF U ISC abs,X\r\n\r\n\r\nMulti-tests\r\n-----------\r\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\r\nWhen run, they list the subtests as they are run. The final result code\r\nrefers to the first sub-test that failed. For more information about any\r\nfailed subtests, run them individually from rom_singles/ and\r\nnsf_singles/.\r\n\r\n\r\nFlashes, clicks, other glitches\r\n-------------------------------\r\nIf a test prints \"passed\", it passed, even if there were some flashes or\r\nodd sounds. Only a test which prints \"done\" at the end requires that you\r\nwatch/listen while it runs in order to determine whether it passed. Such\r\ntests involve things which the CPU cannot directly test.\r\n\r\n\r\nAlternate output\r\n----------------\r\nTests generally print information on screen, but also report the final\r\nresult audibly, and output text to memory, in case the PPU doesn't work\r\nor there isn't one, as in an NSF or a NES emulator early in development.\r\n\r\nAfter the tests are done, the final result is reported as a series of\r\nbeeps (see below). For NSF builds, any important diagnostic bytes are\r\nalso reported as beeps, before the final result.\r\n\r\n\r\nOutput at $6000\r\n---------------\r\nAll text output is written starting at $6004, with a zero-byte\r\nterminator at the end. As more text is written, the terminator is moved\r\nforward, so an emulator can print the current text at any time.\r\n\r\nThe test status is written to $6000. $80 means the test is running, $81\r\nmeans the test needs the reset button pressed, but delayed by at least\r\n100 msec from now. $00-$7F means the test has completed and given that\r\nresult code.\r\n\r\nTo allow an emulator to know when one of these tests is running and the\r\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\r\n$G1 is written to $6001-$6003.\r\n\r\n\r\nAudible output\r\n--------------\r\nA byte is reported as a series of tones. The code is in binary, with a\r\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\r\nThe first tone is always a zero. A final code of 0 means passed, 1 means\r\nfailure, and 2 or higher indicates a specific reason. See the source\r\ncode of the test for more information about the meaning of a test code.\r\nThey are found after the set_test macro. For example, the cause of test\r\ncode 3 would be found in a line containing set_test 3. Examples:\r\n\r\n\tTones         Binary  Decimal  Meaning\r\n\t- - - - - - - - - - - - - - - - - - - - \r\n\tlow              0      0      passed\r\n\tlow high        01      1      failed\r\n\tlow high low   010      2      error 2\r\n\r\n\r\nNSF versions\r\n------------\r\nMany NSF-based tests require that the NSF player either not interrupt\r\nthe init routine with the play routine, or if it does, not interrupt the\r\nplay routine again if it hasn't returned yet. This is because many tests\r\nneed to run for a while without returning.\r\n\r\nNSF versions also make periodic clicks to prevent the NSF player from\r\nthinking the track is silent and thus ending the track before it's done\r\ntesting.\r\n\r\n-- \r\nShay Green <gblargg@gmail.com>\r\n"
  },
  {
    "path": "docs/cpu/instr_timing_readme.txt",
    "content": "NES CPU Instruction Timing Test\r\n-------------------------------\r\nThese tests verify timing of all NES CPU instructions, except the 12\r\nthat freeze the CPU.\r\n\r\nThe individual tests report the opcode of any failed instructions.\r\ninstr_timing prints the measured and correct times. branch_timing runs\r\nthe branch instruction in 8 different situations: four not taken, and\r\nfour taken. For each of these four, the first two are for a\r\nnon-page-cross both negative and positive, and the second two cross a\r\npage. The correct times are 2 2 2 2 3 3 4 4.\r\n\r\n\r\nRequirements\r\n------------\r\n- Basic CPU instruction behavior\r\n- Basic APU length counter operation\r\n\r\n\r\nInternal operation\r\n------------------\r\nEach instruction is timed by setting up appropriate conditions,\r\nsynchronizing to the APU length counter and then loading it with 2,\r\nexecuting the instruction in a loop that stops once the length counter\r\nexpires. The number of loop iterations indicates how many clocks the\r\ninstruction took.\r\n\r\nMulti-tests\r\n-----------\r\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\r\nWhen run, they list the subtests as they are run. The final result code\r\nrefers to the first sub-test that failed. For more information about any\r\nfailed subtests, run them individually from rom_singles/ and\r\nnsf_singles/.\r\n\r\n\r\nFlashes, clicks, other glitches\r\n-------------------------------\r\nIf a test prints \"passed\", it passed, even if there were some flashes or\r\nodd sounds. Only a test which prints \"done\" at the end requires that you\r\nwatch/listen while it runs in order to determine whether it passed. Such\r\ntests involve things which the CPU cannot directly test.\r\n\r\n\r\nAlternate output\r\n----------------\r\nTests generally print information on screen, but also report the final\r\nresult audibly, and output text to memory, in case the PPU doesn't work\r\nor there isn't one, as in an NSF or a NES emulator early in development.\r\n\r\nAfter the tests are done, the final result is reported as a series of\r\nbeeps (see below). For NSF builds, any important diagnostic bytes are\r\nalso reported as beeps, before the final result.\r\n\r\n\r\nOutput at $6000\r\n---------------\r\nAll text output is written starting at $6004, with a zero-byte\r\nterminator at the end. As more text is written, the terminator is moved\r\nforward, so an emulator can print the current text at any time.\r\n\r\nThe test status is written to $6000. $80 means the test is running, $81\r\nmeans the test needs the reset button pressed, but delayed by at least\r\n100 msec from now. $00-$7F means the test has completed and given that\r\nresult code.\r\n\r\nTo allow an emulator to know when one of these tests is running and the\r\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\r\n$G1 is written to $6001-$6003.\r\n\r\n\r\nAudible output\r\n--------------\r\nA byte is reported as a series of tones. The code is in binary, with a\r\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\r\nThe first tone is always a zero. A final code of 0 means passed, 1 means\r\nfailure, and 2 or higher indicates a specific reason. See the source\r\ncode of the test for more information about the meaning of a test code.\r\nThey are found after the set_test macro. For example, the cause of test\r\ncode 3 would be found in a line containing set_test 3. Examples:\r\n\r\n\tTones         Binary  Decimal  Meaning\r\n\t- - - - - - - - - - - - - - - - - - - - \r\n\tlow              0      0      passed\r\n\tlow high        01      1      failed\r\n\tlow high low   010      2      error 2\r\n\r\n\r\nNSF versions\r\n------------\r\nMany NSF-based tests require that the NSF player either not interrupt\r\nthe init routine with the play routine, or if it does, not interrupt the\r\nplay routine again if it hasn't returned yet. This is because many tests\r\nneed to run for a while without returning.\r\n\r\nNSF versions also make periodic clicks to prevent the NSF player from\r\nthinking the track is silent and thus ending the track before it's done\r\ntesting.\r\n\r\n-- \r\nShay Green <gblargg@gmail.com>\r\n"
  },
  {
    "path": "docs/cpu/interrupts_readme.txt",
    "content": "NES CPU Interrupt Tests\n-----------------------\nTests behavior and timing of CPU in the presence of interrupts, both IRQ\nand NMI.\n\n\nCLI Latency Summary\n-------------------\nThe RTI instruction affects IRQ inhibition immediately. If an IRQ is\npending and an RTI is executed that clears the I flag, the CPU will\ninvoke the IRQ handler immediately after RTI finishes executing.\n\nThe CLI, SEI, and PLP instructions effectively delay changes to the I\nflag until after the next instruction. For example, if an interrupt is\npending and the I flag is currently set, executing CLI will execute the\nnext instruction before the CPU invokes the IRQ handler. This delay only\naffects inhibition, not the value of the I flag itself; CLI followed by\nPHP will leave the I flag cleared in the saved status byte on the stack\n(bit 2), as expected.\n\n\n1-cli_latency\n-------------\nTests the delay in CLI taking effect, and some basic aspects of IRQ\nhandling and the APU frame IRQ (needed by the tests). It uses the APU's\nframe IRQ and first verifies that it works well enough for the tests.\n\nThe later tests execute CLI followed by SEI and equivalent pairs of\ninstructions (CLI, PLP, where the PLP sets the I flag). These should\nonly allow at most one invocation of the IRQ handler, even if it doesn't\nacknowledge the source of the IRQ. RTI is also tested, which behaves\ndifferently. These tests also *don't* disable interrupts after the first\nIRQ, in order to test whether a pair of instructions allows only one\ninterrupt or causes continuous interrupts that block the main code from\ncontinuing.\n\n2) RTI should not adjust return address (as RTS does)\n3) APU should generate IRQ when $4017 = $00\n4) Exactly one instruction after CLI should execute before IRQ is taken\n5) CLI SEI should allow only one IRQ just after SEI\n6) In IRQ allowed by CLI SEI, I flag should be set in saved status flags\n7) CLI PLP should allow only one IRQ just after PLP\n8) PLP SEI should allow only one IRQ just after SEI\n9) PLP PLP should allow only one IRQ just after PLP\n10) CLI RTI should not allow any IRQs\n11) Unacknowledged IRQ shouldn't let any mainline code run\n12) RTI RTI shouldn't let any mainline code run\n\n\n2-nmi_and_brk\n-------------\nNMI behavior when it interrupts BRK. Occasionally fails on\nNES due to PPU-CPU synchronization.\n\nResult when run:\nNMI BRK --\n27  36  00 NMI before CLC\n26  36  00 NMI after CLC\n26  36  00 \n36  00  00 NMI interrupting BRK, with B bit set on stack\n36  00  00 \n36  00  00 \n36  00  00 \n36  00  00 \n27  36  00 NMI after SEC at beginning of IRQ handler\n27  36  00 \n\n\n3-nmi_and_irq\n-------------\nNMI behavior when it interrupts IRQ vectoring.\n\nResult when run:\nNMI IRQ\n23  00 NMI occurs before LDA #1\n21  00 NMI occurs after LDA #1 (Z flag clear)\n21  00\n20  00 NMI occurs after CLC, interrupting IRQ\n20  00\n20  00\n20  00\n20  00\n20  00\n20  00 Same result for 7 clocks before IRQ is vectored\n25  20 IRQ occurs, then NMI occurs after SEC in IRQ handler\n25  20\n\n\n4-irq_and_dma\n-------------\nHas IRQ occur at various times around sprite DMA.\nFirst column refers to what instruction IRQ occurred\nafter. Second column is time of IRQ, in CPU clocks relative\nto some arbitrary starting point.\n\n0 +0\n1 +1\n1 +2\n2 +3\n2 +4\n4 +5\n4 +6\n7 +7\n7 +8\n7 +9\n7 +10\n8 +11\n8 +12\n8 +13\n...\n8 +524\n8 +525\n8 +526\n9 +527\n\n\n5-branch_delays_irq\n-------------------\nA taken non-page-crossing branch ignores IRQ during\nits last clock, so that next instruction executes\nbefore the IRQ. Other instructions would execute the\nNMI before the next instruction.\n\nThe same occurs for NMI, though that's not tested here.\n\ntest_jmp\nT+ CK PC\n00 02 04 NOP\n01 01 04 \n02 03 07 JMP\n03 02 07 \n04 01 07 \n05 02 08 NOP\n06 01 08 \n07 03 08 JMP\n08 02 08 \n09 01 08 \n\ntest_branch_not_taken\nT+ CK PC\n00 02 04 CLC\n01 01 04 \n02 02 06 BCS\n03 01 06 \n04 02 07 NOP\n05 01 07 \n06 04 0A JMP\n07 03 0A \n08 02 0A \n09 01 0A JMP\n\ntest_branch_taken_pagecross\nT+ CK PC\n00 02 0D CLC\n01 01 0D \n02 04 00 BCC\n03 03 00 \n04 02 00 \n05 01 00 \n06 04 03 LDA $100\n07 03 03 \n08 02 03 \n09 01 03 \n\ntest_branch_taken\nT+ CK PC\n00 02 04 CLC\n01 01 04 \n02 03 07 BCC\n03 02 07 \n04 05 0A LDA $100 *** This is the special case\n05 04 0A \n06 03 0A \n07 02 0A \n08 01 0A \n09 03 0A JMP\n\nMulti-tests\n-----------\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\nWhen run, they list the subtests as they are run. The final result code\nrefers to the first sub-test that failed. For more information about any\nfailed subtests, run them individually from rom_singles/ and\nnsf_singles/.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "docs/cpu/opcode_list.txt",
    "content": "  0 #00 BRK : mode:  6 Implied          size: 2  cycles: 7  page_cycles: 0\n  1 #01 ORA : mode:  7 IndexedIndirect  size: 2  cycles: 6  page_cycles: 0\n  2 #02 KIL : mode:  6 Implied          size: 0  cycles: 2  page_cycles: 0\n  3 #03 SLO : mode:  7 IndexedIndirect  size: 0  cycles: 8  page_cycles: 0\n  4 #04 NOP : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n  5 #05 ORA : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n  6 #06 ASL : mode: 11 ZeroPaged        size: 2  cycles: 5  page_cycles: 0\n  7 #07 SLO : mode: 11 ZeroPaged        size: 0  cycles: 5  page_cycles: 0\n  8 #08 PHP : mode:  6 Implied          size: 1  cycles: 3  page_cycles: 0\n  9 #09 ORA : mode:  5 Immediate        size: 2  cycles: 2  page_cycles: 0\n 10 #0A ASL : mode:  4 Accumulator      size: 1  cycles: 2  page_cycles: 0\n 11 #0B ANC : mode:  5 Immediate        size: 0  cycles: 2  page_cycles: 0\n 12 #0C NOP : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n 13 #0D ORA : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n 14 #0E ASL : mode:  1 Absolute         size: 3  cycles: 6  page_cycles: 0\n 15 #0F SLO : mode:  1 Absolute         size: 0  cycles: 6  page_cycles: 0\n 16 #10 BPL : mode: 10 Relative         size: 2  cycles: 2  page_cycles: 1\n 17 #11 ORA : mode:  9 IndirectIndexed  size: 2  cycles: 5  page_cycles: 1\n 18 #12 KIL : mode:  6 Implied          size: 0  cycles: 2  page_cycles: 0\n 19 #13 SLO : mode:  9 IndirectIndexed  size: 0  cycles: 8  page_cycles: 0\n 20 #14 NOP : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n 21 #15 ORA : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n 22 #16 ASL : mode: 12 ZeroPagedX       size: 2  cycles: 6  page_cycles: 0\n 23 #17 SLO : mode: 12 ZeroPagedX       size: 0  cycles: 6  page_cycles: 0\n 24 #18 CLC : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n 25 #19 ORA : mode:  3 AbsoluteY        size: 3  cycles: 4  page_cycles: 1\n 26 #1A NOP : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n 27 #1B SLO : mode:  3 AbsoluteY        size: 0  cycles: 7  page_cycles: 0\n 28 #1C NOP : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n 29 #1D ORA : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n 30 #1E ASL : mode:  2 AbsoluteX        size: 3  cycles: 7  page_cycles: 0\n 31 #1F SLO : mode:  2 AbsoluteX        size: 0  cycles: 7  page_cycles: 0\n 32 #20 JSR : mode:  1 Absolute         size: 3  cycles: 6  page_cycles: 0\n 33 #21 AND : mode:  7 IndexedIndirect  size: 2  cycles: 6  page_cycles: 0\n 34 #22 KIL : mode:  6 Implied          size: 0  cycles: 2  page_cycles: 0\n 35 #23 RLA : mode:  7 IndexedIndirect  size: 0  cycles: 8  page_cycles: 0\n 36 #24 BIT : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n 37 #25 AND : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n 38 #26 ROL : mode: 11 ZeroPaged        size: 2  cycles: 5  page_cycles: 0\n 39 #27 RLA : mode: 11 ZeroPaged        size: 0  cycles: 5  page_cycles: 0\n 40 #28 PLP : mode:  6 Implied          size: 1  cycles: 4  page_cycles: 0\n 41 #29 AND : mode:  5 Immediate        size: 2  cycles: 2  page_cycles: 0\n 42 #2A ROL : mode:  4 Accumulator      size: 1  cycles: 2  page_cycles: 0\n 43 #2B ANC : mode:  5 Immediate        size: 0  cycles: 2  page_cycles: 0\n 44 #2C BIT : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n 45 #2D AND : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n 46 #2E ROL : mode:  1 Absolute         size: 3  cycles: 6  page_cycles: 0\n 47 #2F RLA : mode:  1 Absolute         size: 0  cycles: 6  page_cycles: 0\n 48 #30 BMI : mode: 10 Relative         size: 2  cycles: 2  page_cycles: 1\n 49 #31 AND : mode:  9 IndirectIndexed  size: 2  cycles: 5  page_cycles: 1\n 50 #32 KIL : mode:  6 Implied          size: 0  cycles: 2  page_cycles: 0\n 51 #33 RLA : mode:  9 IndirectIndexed  size: 0  cycles: 8  page_cycles: 0\n 52 #34 NOP : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n 53 #35 AND : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n 54 #36 ROL : mode: 12 ZeroPagedX       size: 2  cycles: 6  page_cycles: 0\n 55 #37 RLA : mode: 12 ZeroPagedX       size: 0  cycles: 6  page_cycles: 0\n 56 #38 SEC : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n 57 #39 AND : mode:  3 AbsoluteY        size: 3  cycles: 4  page_cycles: 1\n 58 #3A NOP : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n 59 #3B RLA : mode:  3 AbsoluteY        size: 0  cycles: 7  page_cycles: 0\n 60 #3C NOP : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n 61 #3D AND : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n 62 #3E ROL : mode:  2 AbsoluteX        size: 3  cycles: 7  page_cycles: 0\n 63 #3F RLA : mode:  2 AbsoluteX        size: 0  cycles: 7  page_cycles: 0\n 64 #40 RTI : mode:  6 Implied          size: 1  cycles: 6  page_cycles: 0\n 65 #41 EOR : mode:  7 IndexedIndirect  size: 2  cycles: 6  page_cycles: 0\n 66 #42 KIL : mode:  6 Implied          size: 0  cycles: 2  page_cycles: 0\n 67 #43 SRE : mode:  7 IndexedIndirect  size: 0  cycles: 8  page_cycles: 0\n 68 #44 NOP : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n 69 #45 EOR : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n 70 #46 LSR : mode: 11 ZeroPaged        size: 2  cycles: 5  page_cycles: 0\n 71 #47 SRE : mode: 11 ZeroPaged        size: 0  cycles: 5  page_cycles: 0\n 72 #48 PHA : mode:  6 Implied          size: 1  cycles: 3  page_cycles: 0\n 73 #49 EOR : mode:  5 Immediate        size: 2  cycles: 2  page_cycles: 0\n 74 #4A LSR : mode:  4 Accumulator      size: 1  cycles: 2  page_cycles: 0\n 75 #4B ALR : mode:  5 Immediate        size: 0  cycles: 2  page_cycles: 0\n 76 #4C JMP : mode:  1 Absolute         size: 3  cycles: 3  page_cycles: 0\n 77 #4D EOR : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n 78 #4E LSR : mode:  1 Absolute         size: 3  cycles: 6  page_cycles: 0\n 79 #4F SRE : mode:  1 Absolute         size: 0  cycles: 6  page_cycles: 0\n 80 #50 BVC : mode: 10 Relative         size: 2  cycles: 2  page_cycles: 1\n 81 #51 EOR : mode:  9 IndirectIndexed  size: 2  cycles: 5  page_cycles: 1\n 82 #52 KIL : mode:  6 Implied          size: 0  cycles: 2  page_cycles: 0\n 83 #53 SRE : mode:  9 IndirectIndexed  size: 0  cycles: 8  page_cycles: 0\n 84 #54 NOP : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n 85 #55 EOR : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n 86 #56 LSR : mode: 12 ZeroPagedX       size: 2  cycles: 6  page_cycles: 0\n 87 #57 SRE : mode: 12 ZeroPagedX       size: 0  cycles: 6  page_cycles: 0\n 88 #58 CLI : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n 89 #59 EOR : mode:  3 AbsoluteY        size: 3  cycles: 4  page_cycles: 1\n 90 #5A NOP : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n 91 #5B SRE : mode:  3 AbsoluteY        size: 0  cycles: 7  page_cycles: 0\n 92 #5C NOP : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n 93 #5D EOR : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n 94 #5E LSR : mode:  2 AbsoluteX        size: 3  cycles: 7  page_cycles: 0\n 95 #5F SRE : mode:  2 AbsoluteX        size: 0  cycles: 7  page_cycles: 0\n 96 #60 RTS : mode:  6 Implied          size: 1  cycles: 6  page_cycles: 0\n 97 #61 ADC : mode:  7 IndexedIndirect  size: 2  cycles: 6  page_cycles: 0\n 98 #62 KIL : mode:  6 Implied          size: 0  cycles: 2  page_cycles: 0\n 99 #63 RRA : mode:  7 IndexedIndirect  size: 0  cycles: 8  page_cycles: 0\n100 #64 NOP : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n101 #65 ADC : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n102 #66 ROR : mode: 11 ZeroPaged        size: 2  cycles: 5  page_cycles: 0\n103 #67 RRA : mode: 11 ZeroPaged        size: 0  cycles: 5  page_cycles: 0\n104 #68 PLA : mode:  6 Implied          size: 1  cycles: 4  page_cycles: 0\n105 #69 ADC : mode:  5 Immediate        size: 2  cycles: 2  page_cycles: 0\n106 #6A ROR : mode:  4 Accumulator      size: 1  cycles: 2  page_cycles: 0\n107 #6B ARR : mode:  5 Immediate        size: 0  cycles: 2  page_cycles: 0\n108 #6C JMP : mode:  8 Indirect         size: 3  cycles: 5  page_cycles: 0\n109 #6D ADC : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n110 #6E ROR : mode:  1 Absolute         size: 3  cycles: 6  page_cycles: 0\n111 #6F RRA : mode:  1 Absolute         size: 0  cycles: 6  page_cycles: 0\n112 #70 BVS : mode: 10 Relative         size: 2  cycles: 2  page_cycles: 1\n113 #71 ADC : mode:  9 IndirectIndexed  size: 2  cycles: 5  page_cycles: 1\n114 #72 KIL : mode:  6 Implied          size: 0  cycles: 2  page_cycles: 0\n115 #73 RRA : mode:  9 IndirectIndexed  size: 0  cycles: 8  page_cycles: 0\n116 #74 NOP : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n117 #75 ADC : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n118 #76 ROR : mode: 12 ZeroPagedX       size: 2  cycles: 6  page_cycles: 0\n119 #77 RRA : mode: 12 ZeroPagedX       size: 0  cycles: 6  page_cycles: 0\n120 #78 SEI : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n121 #79 ADC : mode:  3 AbsoluteY        size: 3  cycles: 4  page_cycles: 1\n122 #7A NOP : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n123 #7B RRA : mode:  3 AbsoluteY        size: 0  cycles: 7  page_cycles: 0\n124 #7C NOP : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n125 #7D ADC : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n126 #7E ROR : mode:  2 AbsoluteX        size: 3  cycles: 7  page_cycles: 0\n127 #7F RRA : mode:  2 AbsoluteX        size: 0  cycles: 7  page_cycles: 0\n128 #80 NOP : mode:  5 Immediate        size: 2  cycles: 2  page_cycles: 0\n129 #81 STA : mode:  7 IndexedIndirect  size: 2  cycles: 6  page_cycles: 0\n130 #82 NOP : mode:  5 Immediate        size: 0  cycles: 2  page_cycles: 0\n131 #83 SAX : mode:  7 IndexedIndirect  size: 0  cycles: 6  page_cycles: 0\n132 #84 STY : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n133 #85 STA : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n134 #86 STX : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n135 #87 SAX : mode: 11 ZeroPaged        size: 0  cycles: 3  page_cycles: 0\n136 #88 DEY : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n137 #89 NOP : mode:  5 Immediate        size: 0  cycles: 2  page_cycles: 0\n138 #8A TXA : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n139 #8B XAA : mode:  5 Immediate        size: 0  cycles: 2  page_cycles: 0\n140 #8C STY : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n141 #8D STA : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n142 #8E STX : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n143 #8F SAX : mode:  1 Absolute         size: 0  cycles: 4  page_cycles: 0\n144 #90 BCC : mode: 10 Relative         size: 2  cycles: 2  page_cycles: 1\n145 #91 STA : mode:  9 IndirectIndexed  size: 2  cycles: 6  page_cycles: 0\n146 #92 KIL : mode:  6 Implied          size: 0  cycles: 2  page_cycles: 0\n147 #93 AHX : mode:  9 IndirectIndexed  size: 0  cycles: 6  page_cycles: 0\n148 #94 STY : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n149 #95 STA : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n150 #96 STX : mode: 13 ZeroPagedY       size: 2  cycles: 4  page_cycles: 0\n151 #97 SAX : mode: 13 ZeroPagedY       size: 0  cycles: 4  page_cycles: 0\n152 #98 TYA : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n153 #99 STA : mode:  3 AbsoluteY        size: 3  cycles: 5  page_cycles: 0\n154 #9A TXS : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n155 #9B TAS : mode:  3 AbsoluteY        size: 0  cycles: 5  page_cycles: 0\n156 #9C SHY : mode:  2 AbsoluteX        size: 0  cycles: 5  page_cycles: 0\n157 #9D STA : mode:  2 AbsoluteX        size: 3  cycles: 5  page_cycles: 0\n158 #9E SHX : mode:  3 AbsoluteY        size: 0  cycles: 5  page_cycles: 0\n159 #9F AHX : mode:  3 AbsoluteY        size: 0  cycles: 5  page_cycles: 0\n160 #A0 LDY : mode:  5 Immediate        size: 2  cycles: 2  page_cycles: 0\n161 #A1 LDA : mode:  7 IndexedIndirect  size: 2  cycles: 6  page_cycles: 0\n162 #A2 LDX : mode:  5 Immediate        size: 2  cycles: 2  page_cycles: 0\n163 #A3 LAX : mode:  7 IndexedIndirect  size: 0  cycles: 6  page_cycles: 0\n164 #A4 LDY : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n165 #A5 LDA : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n166 #A6 LDX : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n167 #A7 LAX : mode: 11 ZeroPaged        size: 0  cycles: 3  page_cycles: 0\n168 #A8 TAY : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n169 #A9 LDA : mode:  5 Immediate        size: 2  cycles: 2  page_cycles: 0\n170 #AA TAX : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n171 #AB LAX : mode:  5 Immediate        size: 0  cycles: 2  page_cycles: 0\n172 #AC LDY : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n173 #AD LDA : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n174 #AE LDX : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n175 #AF LAX : mode:  1 Absolute         size: 0  cycles: 4  page_cycles: 0\n176 #B0 BCS : mode: 10 Relative         size: 2  cycles: 2  page_cycles: 1\n177 #B1 LDA : mode:  9 IndirectIndexed  size: 2  cycles: 5  page_cycles: 1\n178 #B2 KIL : mode:  6 Implied          size: 0  cycles: 2  page_cycles: 0\n179 #B3 LAX : mode:  9 IndirectIndexed  size: 0  cycles: 5  page_cycles: 1\n180 #B4 LDY : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n181 #B5 LDA : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n182 #B6 LDX : mode: 13 ZeroPagedY       size: 2  cycles: 4  page_cycles: 0\n183 #B7 LAX : mode: 13 ZeroPagedY       size: 0  cycles: 4  page_cycles: 0\n184 #B8 CLV : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n185 #B9 LDA : mode:  3 AbsoluteY        size: 3  cycles: 4  page_cycles: 1\n186 #BA TSX : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n187 #BB LAS : mode:  3 AbsoluteY        size: 0  cycles: 4  page_cycles: 1\n188 #BC LDY : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n189 #BD LDA : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n190 #BE LDX : mode:  3 AbsoluteY        size: 3  cycles: 4  page_cycles: 1\n191 #BF LAX : mode:  3 AbsoluteY        size: 0  cycles: 4  page_cycles: 1\n192 #C0 CPY : mode:  5 Immediate        size: 2  cycles: 2  page_cycles: 0\n193 #C1 CMP : mode:  7 IndexedIndirect  size: 2  cycles: 6  page_cycles: 0\n194 #C2 NOP : mode:  5 Immediate        size: 0  cycles: 2  page_cycles: 0\n195 #C3 DCP : mode:  7 IndexedIndirect  size: 0  cycles: 8  page_cycles: 0\n196 #C4 CPY : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n197 #C5 CMP : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n198 #C6 DEC : mode: 11 ZeroPaged        size: 2  cycles: 5  page_cycles: 0\n199 #C7 DCP : mode: 11 ZeroPaged        size: 0  cycles: 5  page_cycles: 0\n200 #C8 INY : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n201 #C9 CMP : mode:  5 Immediate        size: 2  cycles: 2  page_cycles: 0\n202 #CA DEX : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n203 #CB AXS : mode:  5 Immediate        size: 0  cycles: 2  page_cycles: 0\n204 #CC CPY : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n205 #CD CMP : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n206 #CE DEC : mode:  1 Absolute         size: 3  cycles: 6  page_cycles: 0\n207 #CF DCP : mode:  1 Absolute         size: 0  cycles: 6  page_cycles: 0\n208 #D0 BNE : mode: 10 Relative         size: 2  cycles: 2  page_cycles: 1\n209 #D1 CMP : mode:  9 IndirectIndexed  size: 2  cycles: 5  page_cycles: 1\n210 #D2 KIL : mode:  6 Implied          size: 0  cycles: 2  page_cycles: 0\n211 #D3 DCP : mode:  9 IndirectIndexed  size: 0  cycles: 8  page_cycles: 0\n212 #D4 NOP : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n213 #D5 CMP : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n214 #D6 DEC : mode: 12 ZeroPagedX       size: 2  cycles: 6  page_cycles: 0\n215 #D7 DCP : mode: 12 ZeroPagedX       size: 0  cycles: 6  page_cycles: 0\n216 #D8 CLD : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n217 #D9 CMP : mode:  3 AbsoluteY        size: 3  cycles: 4  page_cycles: 1\n218 #DA NOP : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n219 #DB DCP : mode:  3 AbsoluteY        size: 0  cycles: 7  page_cycles: 0\n220 #DC NOP : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n221 #DD CMP : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n222 #DE DEC : mode:  2 AbsoluteX        size: 3  cycles: 7  page_cycles: 0\n223 #DF DCP : mode:  2 AbsoluteX        size: 0  cycles: 7  page_cycles: 0\n224 #E0 CPX : mode:  5 Immediate        size: 2  cycles: 2  page_cycles: 0\n225 #E1 SBC : mode:  7 IndexedIndirect  size: 2  cycles: 6  page_cycles: 0\n226 #E2 NOP : mode:  5 Immediate        size: 0  cycles: 2  page_cycles: 0\n227 #E3 ISC : mode:  7 IndexedIndirect  size: 0  cycles: 8  page_cycles: 0\n228 #E4 CPX : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n229 #E5 SBC : mode: 11 ZeroPaged        size: 2  cycles: 3  page_cycles: 0\n230 #E6 INC : mode: 11 ZeroPaged        size: 2  cycles: 5  page_cycles: 0\n231 #E7 ISC : mode: 11 ZeroPaged        size: 0  cycles: 5  page_cycles: 0\n232 #E8 INX : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n233 #E9 SBC : mode:  5 Immediate        size: 2  cycles: 2  page_cycles: 0\n234 #EA NOP : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n235 #EB SBC : mode:  5 Immediate        size: 0  cycles: 2  page_cycles: 0\n236 #EC CPX : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n237 #ED SBC : mode:  1 Absolute         size: 3  cycles: 4  page_cycles: 0\n238 #EE INC : mode:  1 Absolute         size: 3  cycles: 6  page_cycles: 0\n239 #EF ISC : mode:  1 Absolute         size: 0  cycles: 6  page_cycles: 0\n240 #F0 BEQ : mode: 10 Relative         size: 2  cycles: 2  page_cycles: 1\n241 #F1 SBC : mode:  9 IndirectIndexed  size: 2  cycles: 5  page_cycles: 1\n242 #F2 KIL : mode:  6 Implied          size: 0  cycles: 2  page_cycles: 0\n243 #F3 ISC : mode:  9 IndirectIndexed  size: 0  cycles: 8  page_cycles: 0\n244 #F4 NOP : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n245 #F5 SBC : mode: 12 ZeroPagedX       size: 2  cycles: 4  page_cycles: 0\n246 #F6 INC : mode: 12 ZeroPagedX       size: 2  cycles: 6  page_cycles: 0\n247 #F7 ISC : mode: 12 ZeroPagedX       size: 0  cycles: 6  page_cycles: 0\n248 #F8 SED : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n249 #F9 SBC : mode:  3 AbsoluteY        size: 3  cycles: 4  page_cycles: 1\n250 #FA NOP : mode:  6 Implied          size: 1  cycles: 2  page_cycles: 0\n251 #FB ISC : mode:  3 AbsoluteY        size: 0  cycles: 7  page_cycles: 0\n252 #FC NOP : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n253 #FD SBC : mode:  2 AbsoluteX        size: 3  cycles: 4  page_cycles: 1\n254 #FE INC : mode:  2 AbsoluteX        size: 3  cycles: 7  page_cycles: 0\n255 #FF ISC : mode:  2 AbsoluteX        size: 0  cycles: 7  page_cycles: 0\n"
  },
  {
    "path": "docs/cpu/reset_readme.txt",
    "content": "CPU Power/Reset Tests\r\n---------------------\r\nVerifies CPU register values at power, and changes that occur during\r\nreset. Also verifies that RAM isn't modified during reset.\r\n\r\n\r\nExpected behavior\r\n-----------------\r\nAt power:\r\n\tA, X, Y = 0\r\n\tP = $34\r\n\tS = $FD\r\n\r\nAfter reset:\r\n\tA, X, Y unchanged\r\n\tI flag set (P ORed with $04)\r\n\tS decremented by 3, but nothing written to stack\r\n\r\n\r\nMulti-tests\r\n-----------\r\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\r\nWhen run, they list the subtests as they are run. The final result code\r\nrefers to the first sub-test that failed. For more information about any\r\nfailed subtests, run them individually from rom_singles/ and\r\nnsf_singles/.\r\n\r\n\r\nFlashes, clicks, other glitches\r\n-------------------------------\r\nIf a test prints \"passed\", it passed, even if there were some flashes or\r\nodd sounds. Only a test which prints \"done\" at the end requires that you\r\nwatch/listen while it runs in order to determine whether it passed. Such\r\ntests involve things which the CPU cannot directly test.\r\n\r\n\r\nAlternate output\r\n----------------\r\nTests generally print information on screen, but also report the final\r\nresult audibly, and output text to memory, in case the PPU doesn't work\r\nor there isn't one, as in an NSF or a NES emulator early in development.\r\n\r\nAfter the tests are done, the final result is reported as a series of\r\nbeeps (see below). For NSF builds, any important diagnostic bytes are\r\nalso reported as beeps, before the final result.\r\n\r\n\r\nOutput at $6000\r\n---------------\r\nAll text output is written starting at $6004, with a zero-byte\r\nterminator at the end. As more text is written, the terminator is moved\r\nforward, so an emulator can print the current text at any time.\r\n\r\nThe test status is written to $6000. $80 means the test is running, $81\r\nmeans the test needs the reset button pressed, but delayed by at least\r\n100 msec from now. $00-$7F means the test has completed and given that\r\nresult code.\r\n\r\nTo allow an emulator to know when one of these tests is running and the\r\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\r\n$G1 is written to $6001-$6003.\r\n\r\n\r\nAudible output\r\n--------------\r\nA byte is reported as a series of tones. The code is in binary, with a\r\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\r\nThe first tone is always a zero. A final code of 0 means passed, 1 means\r\nfailure, and 2 or higher indicates a specific reason. See the source\r\ncode of the test for more information about the meaning of a test code.\r\nThey are found after the set_test macro. For example, the cause of test\r\ncode 3 would be found in a line containing set_test 3. Examples:\r\n\r\n\tTones         Binary  Decimal  Meaning\r\n\t- - - - - - - - - - - - - - - - - - - - \r\n\tlow              0      0      passed\r\n\tlow high        01      1      failed\r\n\tlow high low   010      2      error 2\r\n\r\n\r\nNSF versions\r\n------------\r\nMany NSF-based tests require that the NSF player either not interrupt\r\nthe init routine with the play routine, or if it does, not interrupt the\r\nplay routine again if it hasn't returned yet. This is because many tests\r\nneed to run for a while without returning.\r\n\r\nNSF versions also make periodic clicks to prevent the NSF player from\r\nthinking the track is silent and thus ending the track before it's done\r\ntesting.\r\n\r\n-- \r\nShay Green <gblargg@gmail.com>\r\n"
  },
  {
    "path": "docs/genie_codes",
    "content": "Castlevania:\nSZSVLYSA : Infinite Health\nAVEEZZSA : Infinite Energy\nOXNGLZVK : Infinite Lives\nASOGOPIA : Start w/ 80 Hearts\nAANGSAGE AANKXPGE : Start w/ 8 Lives\nKXESUZKA KZSSEZKA : Weapons don't use Hearts\n\nCastlevania III:\nOXEEZZSE : Infinite Health\nOXOAUPSE : Infinite Lives\nOOKPPAIE : Start stage w/ 99 Hearts\n\nDonkey Kong:\nSXNGOZVG : Infinite Lives\n\nBack to the Future II/III:\nSXXELOVK : Infinite Lives\nGZEEPZST GZOEZZST : Infinite Fuel\n"
  },
  {
    "path": "docs/mapper/000.txt",
    "content": "\r\n========================\r\n=  Mapper 000          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nNROM\r\n\"no mapper\"\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nIce Climber\r\nExcitebike\r\nBalloon Fight\r\nSuper Mario Bros.\r\n\r\n\r\n\r\nNotes:\r\n--------------------------\r\nNo swapping of any kind.  All slots fixed, mirroring is hardwired, etc."
  },
  {
    "path": "docs/mapper/001.txt",
    "content": "\r\n========================\r\n=  Mapper 001          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nMMC1\r\nSxROM\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nFinal Fantasy\r\nMega Man 2\r\nBlaster Master\r\nMetroid\r\nKid Icarus\r\nZelda\r\nZelda 2\r\nCastlevania 2\r\n\r\n\r\nNotes:\r\n---------------------------\r\nMMC1 is unique in that not only must the registers be written to *one bit at a time*, but also you cannot\r\nwrite to the registers directly.\r\n\r\nInternal registers are 5 bits wide.  Meaning to complete a \"full\" write, games must write to a register 5\r\ntimes (low bit first).  This is usually accomplished with something like the following:\r\n\r\n   LDA value_to_write\r\n   STA $9FFF    ; 1st bit written\r\n   LSR A\r\n   STA $9FFF    ; 2nd bit written\r\n   LSR A\r\n   STA $9FFF    ; 3rd bit written\r\n   LSR A\r\n   STA $9FFF    ; 4th bit written\r\n   LSR A\r\n   STA $9FFF    ; final 5th bit written -- full write is complete\r\n\r\nWriting to anywhere in $8000-FFFF will do -- however the address you write to on the last of the 5 writes\r\nwill determine which internal register gets filled.  The address written to for the first 4 writes *does not\r\nmatter at all*... though games generally write to the same address anyway (like in the above example).\r\n\r\nTo illustrate this:\r\n\r\n   LDA #$00  ; we want to write 0 to a reg\r\n   STA $8000\r\n   STA $8000\r\n   STA $8000\r\n   STA $8000  ; first 4 writes go to $8000\r\n   STA $E000  ; 5th write goes to $E000\r\n\r\nThe above code will affects reg $E000 only!!!   Despite $8000 being written to several times, reg $8000\r\nremains totally unchanged!\r\n\r\nHow this works is that when the game writes to $8000-FFFF, it goes to a hidden temporary register.  That\r\nregister records the bits being written.  Only after all 5 bits are written does the final 5-bit value move\r\nto the desired *actual* register.\r\n\r\nOnly bits 7 and 0 are significant when writing to a register:\r\n\r\nTemporary reg port ($8000-FFFF):\r\n  [r... ...d]\r\n     r = reset flag\r\n     d = data bit\r\n\r\nWhen 'r' is set:\r\n  - 'd' is ignored\r\n  - hidden temporary reg is reset (so that the next write is the \"first\" write)\r\n  - bits 2,3 of reg $8000 are set (16k PRG mode, $8000 swappable)\r\n  - other bits of $8000 (and other regs) are unchanged\r\n\r\nWhen 'r' is clear:\r\n  - 'd' proceeds as the next bit written in the 5-bit sequence\r\n  - If this completes the 5-bit sequence:\r\n      - temporary reg is copied to actual internal reg (which reg depends on the last address written to)\r\n      - temporary reg is reset (so that next write is the \"first\" write)\r\n\r\n\r\nConfusing?  Yeah it looks confusing, but isn't really.  For an example:\r\n\r\n  LDA #$00\r\n  STA $8000 ; 1st write ('r' bit is clear)\r\n  STA $8000 ; 2nd write\r\n\r\n  LDA #$80\r\n  STA $8000 ; reset ('r' bit is set)\r\n\r\n  LDA #$00\r\n  STA $8000 ; 1st write (not 3rd!)\r\n\r\n\r\n\r\nVariants:\r\n--------------------------\r\nThere are also a slew of board variations which are assigned to mapper 001 as well.  See the sections at the\r\nbottom for details.  Determining which variant a game uses is difficult -- likely you'll need to fall back\r\nto a CRC or hash check.\r\n\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\nNote again, these registers are internal and are not accessed directly!  Read notes above.\r\n\r\n\r\n  $8000-9FFF:  [...C PSMM]\r\n    C = CHR Mode (0=8k mode, 1=4k mode)\r\n    P = PRG Size (0=32k mode, 1=16k mode)\r\n    S = Slot select:\r\n        0 = $C000 swappable, $8000 fixed to page $00 (mode A)\r\n        1 = $8000 swappable, $C000 fixed to page $0F (mode B)\r\n        This bit is ignored when 'P' is clear (32k mode)\r\n    M = Mirroring control:\r\n        %00 = 1ScA\r\n        %01 = 1ScB\r\n        %10 = Vert\r\n        %11 = Horz\r\n\r\n\r\n  $A000-BFFF:  [...C CCCC]\r\n    CHR Reg 0\r\n\r\n  $C000-DFFF:  [...C CCCC]\r\n    CHR Reg 1\r\n\r\n  $E000-FFFF:  [...W PPPP]\r\n    W = WRAM Disable (0=enabled, 1=disabled)\r\n    P = PRG Reg\r\n\r\n\r\nDisabled WRAM cannot be read or written.  Earlier MMC1 versions apparently do not have this bit implemented.\r\nLater ones do.\r\n\r\n\r\n\r\nCHR Setup:\r\n--------------------------\r\nThere are 2 CHR regs and 2 CHR modes.\r\n\r\n            $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n          +---------------------------------------------------------------+\r\nC=0:      |                            <$A000>                            |\r\n          +---------------------------------------------------------------+\r\nC=1:      |             $A000             |             $C000             |\r\n          +-------------------------------+-------------------------------+\r\n\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\nThere is 1 PRG reg and 3 PRG modes.\r\n\r\n               $8000   $A000   $C000   $E000\r\n             +-------------------------------+\r\nP=0:         |            <$E000>            |\r\n             +-------------------------------+\r\nP=1, S=0:    |     { 0 }     |     $E000     |\r\n             +---------------+---------------+\r\nP=1, S=1:    |     $E000     |     {$0F}     |\r\n             +---------------+---------------+\r\n\r\n\r\nOn Powerup:\r\n----------------------------\r\n\r\nThis varies from version to version.  Earlier MMC1 versions have no determined startup state.  Later ones do.\r\n\r\n - bits 2,3 of $8000 are set (16k PRG mode, $8000 swappable)\r\n\r\nWRAM Disable varies wildly from version to version.  Some versions don't have it at all, other versions have\r\nit cleared initially, others have it set initially, and others have it random.  To be \"safe\", when\r\nhomebrewing, assume it's disabled (and have your game explicitly enable it before accessing WRAM), and when\r\nemudeving, assume it's enabled at startup (or else some early MMC1 games will break in your emu).\r\n\r\n\r\n\r\n\r\nAdditional Notes:\r\n----------------------------\r\n\r\nConsecutive writes that are too close together are apparently ignored.  One game where this is significant\r\nis Bill & Ted's Excellent Video Game Adventure.  That game does the following HORRIBLY SLOPPY code to reset\r\nthe mapper:\r\n\r\n  INC $FFFF  (where $FFFF contains $FF when read)\r\n\r\nFor those of you who really know your 6502... you know that this will read $FFFF (getting $FF), write that\r\nvalue ($FF) back to $FFFF, increment it by one, then write the new value ($00) to $FFFF.  This results in\r\ntwo register writes:  $FF, then $00.\r\n\r\nNormally, such writes would reset the mapper, then write a single data bit.  However if your emu does it\r\nlike that, the game will crash, as the game expects the next write to be the 1st in a 5-bit sequence (and\r\nyour emu will treat it like the 2nd).\r\n\r\nHowever these writes are performed on consecutive CPU cycles -- which apparently are too close to each other.\r\nAs such, only the first write (of $FF) is acknowledged and performed, and the second write (of $00) is\r\nignored.  Emulating in this manner results in a fully functioning game.\r\n\r\nSo while it is unsure exactly how far apart the writes must be, you can assume that the distance between\r\nthem must be at least 2 CPU cycles.  Such that Read/Modify/Write instructions (like INC) will only\r\nacknowledge the first write, but two consecutive write instructions (like 2 side-by-side STA's) will work\r\nnormally.\r\n\r\n\r\n-----------------------------------------\r\n-----------------------------------------\r\n\r\n\r\nSpecial Variant -- SUROM:\r\n--------------------------\r\n\r\nExample Games:\r\n  Dragon Warrior 4\r\n  Dragon Quest 4\r\n\r\n\r\nThe MMC1 PRG reg is only 4 bits wide.  This means that normally, page $0F is the highest page number you can\r\naccess.  With 16k pages... this limits typical MMC1 to 256k PRG ($10 pages * $4000 per page).  SUROM\r\n\"hijacks\" one of the bits from the CHR registers and uses it as an additional PRG bit.  This allows for\r\naccess to $1F pages, allowing 512k PRG.\r\n\r\n  $A000-BFFF:  [...C CCCC]     CHR reg 0\r\n               [...P ....]     hijacked PRG bit\r\n\r\n  $C000-DFFF:  [...C CCCC]     CHR reg 1\r\n               [...P ....]     hijacked PRG bit\r\n\r\nWhen in 4k CHR mode, 'P' in both $A000 and $C000 *must* be set to the same value, or else pages will\r\nconstantly be swapped as graphics render!  In 8k CHR mode (which is what DQ4 uses), $C000 is irrelevant\r\nsince it is ignored, and $A000 is used exclusively.\r\n\r\nThe hijacked PRG bit selects which 256k block is used for *ALL* PRG... *including* fixed pages.  Meaning\r\nfixed page $0F @ $C000 can swap between page $0F and $1F.\r\n\r\n\r\n\r\nSpecial Variant -- SOROM:\r\n--------------------------\r\n\r\nExample Games:\r\n  Nobunaga's Ambition\r\n  Romance of the Three Kingdoms\r\n  Genghis Khan\r\n\r\n\r\nSOROM has 16k PRG-RAM (instead of the typical 8k), and hijacks unused bits from the CHR regs in order to\r\nselect which 8k PRG-RAM page is at $6000-7FFF.  The first 8k of PRG-RAM (page 0) is not battery backed --\r\nbut the second 8k is.\r\n\r\nWhen in 4k CHR Mode:\r\n\r\n  $A000-BFFF:  [.... R..C]\r\n    R = PRG-RAM page select\r\n    C = CHR reg 0\r\n\r\n  $C000-DFFF:  [.... R..C]\r\n    R = PRG-RAM page select\r\n    C = CHR reg 1\r\n\r\n  In 4k CHR mode, above 'R' bits MUST be set to the same value or else PRG-RAM will automatically swap as\r\nthe PPU fetches tiles to render!\r\n\r\n\r\nWhen in 8k mode:\r\n\r\n  $A000-BFFF:  [.... R...]   PRG-RAM page select\r\n  $C000-DFFF:  [.... ....]   Unused\r\n\r\n\r\n\r\nSpecial Variant -- SXROM:\r\n--------------------------\r\n\r\nExample Games:\r\n  Final Fantasy 1 & 2  (the combo cart, not the individual games)\r\n  Best Play Pro Yakyuu Special\r\n\r\n\r\nSXROM is sort of like a combination of SUROM and SOROM.  It uses bits from CHR regs to have an additional\r\nPRG bit, and also to have swappable PRG-RAM.  SXROM has a whopping 32k PRG-RAM (all of which can be battery\r\nbacked).\r\n\r\n\r\nWhen in 8k CHR mode:\r\n\r\n  $A000-BFFF:  [...P RR..]\r\n    P = PRG-ROM 256k block select (just like on SUROM)\r\n    R = PRG-RAM page select (selects 8k @ $6000-7FFF, just like SOROM)\r\n\r\n\r\nThe behaviour when in 4k CHR mode is similar to SUROM, in that the registers must\r\nbe identical or else undesired swapping will occur as the PPU renders."
  },
  {
    "path": "docs/mapper/002.txt",
    "content": "\r\n========================\r\n=  Mapper 002          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nUxROM (and compatible)\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nMega Man\r\nCastlevania\r\nContra\r\nDuck Tales\r\nMetal Gear\r\n\r\n\r\nNotes:\r\n---------------------------\r\nUxROM has bus conflicts, however mapper 002 is meant to be UxROM and compatible.  So some mappers which were\r\nsimilar in function, but did not have bus conflicts are included.\r\n\r\nAdditionally, UxROM does not have an 8 bit reg.  UNROM is capped at 128k PRG, and UOROM is capped at 256k.\r\n\r\nSo to be \"safe\":\r\n - for homebrewing:  assume bus conflicts, do not exceed 256k\r\n - for emudev:  assume no bus conflicts, use all 8 PRG reg bits\r\n\r\nThere is no CHR swapping.  Every mapper 002 game I've ever seen has CHR-RAM.\r\n\r\n\r\nRegisters (**BUS CONFLICTS** sometimes):\r\n--------------------------\r\n  $8000-FFFF:  [PPPP PPPP]\r\n    PRG Reg\r\n\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $8000     |     { -1}     |\r\n    +---------------+---------------+\r\n"
  },
  {
    "path": "docs/mapper/003.txt",
    "content": "\r\n========================\r\n=  Mapper 003          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nCNROM (and compatible)\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nSolomon's Key\r\nArkanoid\r\nArkista's Ring\r\nBump 'n' Jump\r\nCybernoid\r\n\r\n\r\nRegisters (**BUS CONFLICTS** sometimes):\r\n--------------------------\r\n  $8000-FFFF:  [CCCC CCCC]\r\n    CHR Reg (selects 8k @ $0000)\r\n\r\n\r\nNotes:\r\n---------------------------\r\nCNROM has bus conflicts, however mapper 003 is meant to be CNROM and compatible.  So some mappers which were\r\nsimilar in function, but did not have bus conflicts are included.\r\n\r\nAdditionally, CNROM's reg is only 2 bits wide... therefore it is capped at 32k CHR.\r\n\r\nSo to be \"safe\":\r\n - for homebrewing:  assume bus conflicts, do not exceed 32k CHR\r\n - for emudev:  assume no bus conflicts, use all 8 reg bits\r\n\r\nThere is no PRG swapping.\r\n\r\n\r\nThe game Cybernoid seems to behave very strangely.  It uses unprepped system RAM... and it is as if it\r\nactually relies on bus conflicts (AND written value with value read from address)!\r\n"
  },
  {
    "path": "docs/mapper/004.txt",
    "content": "\r\n========================\r\n=  Mapper 004          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nMMC3\r\nTxROM\r\n(MMC6)\r\n(HxROM)\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nMega Man 3, 4, 5, 6\r\nKirby's Adventure\r\nGauntlet\r\nRad Racer 2\r\nStartropics 1, 2 (MMC6)\r\nSuper Mario Bros. 2, 3\r\n... a zillion other games (most common mapper)\r\n\r\n\r\n4-Screen Notes:\r\n---------------------------\r\nTR1ROM and TVROM are two of the *very few* boards to use 4-screen mirroring.  The only mapper 004 games which\r\nuse 4-screen mirroring I know of are Rad Racer 2 and Gauntlet.  Several other games are incorrectly labelled\r\nas being 4-screen when they are in fact, not (ex: Gauntlet 2).\r\n\r\nTR1ROM and TVROM are both configured in a way which uses on-cart WRAM as VRAM for the nametables.  However\r\nthis means the WRAM is not tied to the CPU, therefore they do not have any SRAM/WRAM!\r\n\r\nSo to be \"safe\":\r\n- when homebrewing:  choose either 4-screen or WRAM.  You can't have both\r\n- when emudeving:  permanently disable WRAM when 4-screen.  This does not break any games.\r\n\r\n4-screen mirroring for these boards is hardwired!  When in 4-screen mode, your emu must ignore writes to the\r\nmirroring reg.\r\n\r\nAlso note that many Rad Racer 2 dumps are floating around which do not indicate it is 4-screen.  So if you\r\ntry that game in your emu and the graphics are screwed, that's the first thing to check.\r\n\r\n\r\nIRQ Notes:\r\n---------------------------\r\nIRQ Operation on this mapper is simple at first glance, however its precise operation gets very complex.\r\nThis mapper is infamously one of the hardest (if not the very hardest) mapper to emulate accurately -- a\r\ndouble-whammy since it's also hands down the most common mapper around.\r\n\r\nBe sure to read 'Basic IRQ operation' below, and I recommend you seriously consider skimming 'Detailed IRQ\r\noperation' as well -- especially if you're emudeving.\r\n\r\n\r\n\r\nOther notes:\r\n---------------------------\r\nLow G Man will actually confirm that WRAM disabling works properly, and will break if it isn't.  So if your\r\nemu ignores WRAM disabling and always has it enabled, Low G Man will break (specifically, during the\r\nlevel 1 boss fight)\r\n\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $E001\r\n\r\n\r\n  $8000:  [CP.. .AAA]\r\n    C = CHR mode select (see CHR setup)\r\n    P = PRG mode select (see PRG setup)\r\n    A = Address for use with $8001\r\n\r\n\r\n  $8001:  [DDDD DDDD]  --  data port\r\n      R:0 ->  CHR reg 0\r\n      R:1 ->  CHR reg 1\r\n      R:2 ->  CHR reg 2\r\n      R:3 ->  CHR reg 3\r\n      R:4 ->  CHR reg 4\r\n      R:5 ->  CHR reg 5\r\n      R:6 ->  PRG reg 0\r\n      R:7 ->  PRG reg 1\r\n\r\n\r\n  $A000:  [.... ...M]\r\n    Mirroring:  0=Vert\r\n                1=Horz\r\n\r\n    Ignore when 4-screen\r\n\r\n\r\n  $A001:  [EW.. ....]\r\n    E = Enable WRAM (0=disabled, 1=enabled)\r\n    W = WRAM write protect (0=writable, 1=not writable)\r\n\r\n\r\n  $C000:  [IIII IIII]    IRQ Reload value\r\n  $C001:  [.... ....]    IRQ Clear\r\n  $E000:  [.... ....]    IRQ Acknowledge / Disable\r\n  $E001:  [.... ....]    IRQ Enable\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n               $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n             +---------------+---------------+-------+-------+-------+-------+\r\nCHR Mode 0:  |     <R:0>     |     <R:1>     |  R:2  |  R:3  |  R:4  |  R:5  |\r\n             +---------------+---------------+---------------+---------------+\r\nCHR Mode 1:  |  R:2  |  R:3  |  R:4  |  R:5  |     <R:0>     |     <R:1>     |\r\n             +-------+-------+-------+-------+---------------+---------------+\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n               $8000   $A000   $C000   $E000  \r\n             +-------+-------+-------+-------+\r\nPRG Mode 0:  |  R:6  |  R:7  | { -2} | { -1} |\r\n             +-------+-------+-------+-------+\r\nPRG Mode 1:  | { -2} |  R:7  |  R:6  | { -1} |\r\n             +-------+-------+-------+-------+\r\n\r\n\r\nBasic IRQ Operation\r\n---------------------------\r\n\r\nMMC3 IRQs utilize a scanline counter.  The basic steps to making it work are as follows:\r\n\r\n1)  Set the desired number of scanlines you want to wait by writing N to $C000\r\n2)  Reset the internal IRQ counter by writing any value to $C001\r\n3)  Enable IRQs by writing any value to $E001\r\n\r\nAn IRQ will then fire after N+1 rendered scanlines, at which point, you would write any value to $E000 to\r\nacknowledge the IRQ, and disable further IRQs (and possibly repeat the above 3 steps if you want to fire\r\nanother IRQ this frame).\r\n\r\nThe MMC3 counts scanlines by watching the CHR accesses the NES makes.  Therefore in order for this IRQ\r\ncounter to work properly, the NES must be making the accesses that the MMC3 is expecting.  If you have\r\nabnormal settings, you will confuse the MMC3 and IRQs will not work properly.  Therefore, you must follow\r\nthese rules:\r\n\r\n1)  The IRQ counter will only count when the PPU is on (sprites and/or BG enabled).\r\n2)  Do not manipulate $2006 or $2007 while using IRQ counter\r\n3)  Do not set $C000 to $00\r\n4)  BG and Sprites must use opposing pattern tables for CHR.  EG:\r\n   a)  if 8x16 sprites, BG must use $0xxx, *ALL* sprites must use $1xxx\r\n   b)  if 8x8 sprites, if BG is using $0xxx, sprites must use $1xxx\r\n   c)  if 8x8 sprites, if BG is using $1xxx, sprites must use $0xxx   (slightly abnormal)\r\n\r\nWith settings 'a' and 'b', the IRQ will occur after dot 260.  With setting 'c', it will occur after dot 324\r\nof the *previous* scanline.\r\n\r\n\r\nThe IRQ Counter consists of several parts:\r\n1)  Actual 8-bit IRQ counter (not directly accessable)\r\n2)  8-bit latch, or reload value (reg $C000)\r\n3)  IRQ Enable flag\r\n4)  IRQ Pending flag\r\n\r\nIRQ Registers interact with the above parts as follows:\r\n  reg $C000 - sets IRQ Reload value\r\n  reg $C001 - sets the actual IRQ counter to 0 (regardless of what value is written)\r\n  reg $E000 - clears both IRQ Enable flag and IRQ Pending flag\r\n  reg $E001 - sets IRQ Enable flag\r\n\r\nEvery time the MMC3 detects a scanline, the following IRQ Counter logic is executed.  Note this occurs EVEN\r\nIF IRQs are disabled (the IRQ counter is always counting):\r\n\r\n- If IRQ Counter is 0...\r\n     a)  reload IRQ counter with IRQ Reload value\r\n\r\n- Otherwise...\r\n     a)  Decrement IRQ counter by 1\r\n     b)  If IRQ counter is now 0 and IRQs are enabled, trigger IRQ\r\n\r\nNote that 241 scanlines are counted per frame (the 240 rendered scanlines, and the \"prerender\" scanline).\r\n\r\n\r\n\r\n\r\n\r\nDetailed IRQ Operation\r\n---------------------------\r\n\r\nMMC3 detects scanlines by watching A12 ($1000) on the PPU bus.  Every time a rising edge occurs (transitions\r\nfrom 0->1), and it hasn't been too close to the previous rising edge, the IRQ counter gets clocked.\r\n\r\nUnder *normal* conditions (BG using $0xxx, sprites using $1xxx), A12 will rise exactly 8 times every scanline\r\n(once for each sprite CHR fetch).  However the 8 rises are so close together that only the first is 'seen'.\r\n\r\nDuring rendering and pre-render scanlines the PPU is fetching NT and CHR data from the cart through a series\r\nof reads.  Each read updates the PPU Address lines (including A12), and each read takes 2 PPU cycles (2\r\ndots).  There are 4 reads per tile, and 42 tiles per scanline:\r\n\r\n- 32 BG tiles\r\n- 8 Sprite tiles (for the next scanline)\r\n- 2 BG tiles (for the next scanline)\r\n\r\n\r\nEach tile requires 4 reads, each read is 2 dots:\r\ndot 0:  Name table fetch ($2xxx -- A12 is low)\r\ndot 2:  Attribute fetch  ($2xxx -- A12 is low)\r\ndot 4:  Low CHR fetch    ($0xxx or $1xxx -- A12 is low or high)\r\ndot 6:  High CHR fetch   ($0xxx or $1xxx -- A12 is low or high)\r\n\r\nIf the tile being fetched is using the right-hand pattern table ($1xxx), then A12 goes high on dot 4 of that\r\n8-dot sequence.  Otherwise, A12 stays low throughout.\r\n\r\nThis 8-dot sequence is repeated for each tile.. meaning there are 42 opportunities for A12 to rise.  These\r\nopportunities occur on the following dots:\r\n\r\n4, 12, 20, ..., 244, 252                 (32 BG tiles)\r\n260, 268, 276, 284, 292, 300, 308, 316   (8 Spr tiles)\r\n324, 332                                 (2 BG tiles)\r\n\r\n(You might be able to see now how I came up with those 260, 324 numbers I threw at you earlier)\r\n\r\nMMC3 seems to ignore rises that are too close together.  This is why the 8 sprite fetches will only clock\r\nthe counter once.  Exactly how far apart the rising edges have to be is unknown, but it is somewhere between\r\n14 and 16 dots.  So any two consecutive opportunities are too close together (including the most distant\r\n332->4), but any two non-consecutive opportunities will both be acknowledged.\r\n\r\nFiguring whether the tile is being fetched from $0xxx or $1xxx is usually easy.  BG and 8x8 sprites are\r\nalways fetched from an assigned pattern table (configurable by PPU reg $2000).  However, 8x16 sprites can\r\ncome from either pattern table.  So which tile is begin fetched depends on which sprite is being fetched....\r\nwhich depends on what scanline you're on, and what sprites are found to be in-range on that scanline.  For\r\nscanlines which contain less than 8 sprites, tile $FF is fetched as a dummy (in 8x16 sprites, this would be\r\nfrom the $1xxx pattern table).\r\n\r\nThis is why, when you have 8x16 sprites, ALL sprites must use the right-hand pattern table.  If you have\r\nsprites using the left and the right, you'll probably end up having some scanlines where the IRQ counter\r\ncounts the same scanline multiple times!  All depending on which sprites are in-range and when.  For example,\r\nif there are 4 sprites on the scanline using $0xxx, and 4 using $1xxx, the IRQ counter might count the\r\nscanline anywhere from 1 to 4 times!\r\n\r\n0,0,0,0,1,1,1,1   <---  all 4 rises consecutive, will only clock once\r\n0,1,0,1,0,1,0,1   <---- all 4 rises nonconsecutive, counter clocked each time!\r\n\r\nThis is also why the IRQ counter isn't clocked when both BG and sprites use the left pattern table (since\r\nthere is never any rising edge, the MMC3 never detects any scanlines).\r\n\r\n\r\n\r\n$2006 and $2007\r\n---------------------------\r\nA game can manually clock the IRQ counter (either on accident, or by design) by manipulating $2006 and $2007.\r\nA12 is updated (potentially triggering a rising edge) when the PPU address is updated by these registers.\r\nOn $2007 reads/writes, and on the second $2006 write.  This is why messing with $2006 and $2007 after you\r\nprep your IRQ stuff may screw up your IRQs unless you're careful.\r\n\r\n\r\n\r\n\r\nIRQ Counter priming\r\n---------------------------\r\nSome games seem to prime the IRQ counter by repeatedly writing $0000 and $1010 to $2006.  This toggles A12,\r\nclocking the IRQ counter.  It is unknown whether or not this is actually required.\r\n\r\n\r\n\r\nReload value of $00\r\n---------------------------\r\nDifferent MMC3 versions behave differently when you set $C000 to $00.  There are at least two (possibly more)\r\nbehaviors.  These behaviors are mentioned in the readme accompanied with blargg's MMC3 test ROMs, which, if\r\nyou're emudeving, I highly recommend you pick up.  Otherwise, the behavior of having a reload value of $00\r\nis unreliable and/or undesirable, and should be avoided at all costs when homebrewing/hacking.\r\n\r\n\r\n\r\n\r\nSpecial Variant -- MMC6:\r\n--------------------------\r\n\r\nStartropics 1 and 2 are both MMC6 games (not MMC3).  However, they are unfortunately assigned the same mapper\r\nnumber, despite being slightly incompatible.  There is no simple way to determine MMC3 from MMC6.  You'll\r\nprobably have to use a CRC or hash check or something.\r\n\r\nFor the most part they are the same -- but the big difference is the WRAM.  MMC6 has only 1k of WRAM,\r\nwhereas MMC3 games have 8k.  It is also mapped a bit differently, and is enabled/disabled differently from\r\nMMC3.\r\n\r\nMMC6 registers are as follows.  All other registers behave just as they do on MMC3:\r\n\r\n\r\n  $8000:  [CPW. .AAA]\r\n    C,P,A = Same as on MMC3\r\n    W = WRAM Enable (0=disabled, 1=enabled)\r\n\r\n  $A001:  [HhLl ....]\r\n    H,L = Enable WRAM block (0=disabled, 1=enabled)\r\n    h,l = WRAM block write protect (0=writes disabled, 1=writes enabled)\r\n\r\n\r\nThe 1k of WRAM is split into 2 512 byte blocks... one at $7000-71FF and another at $7200-73FF.  Each block\r\ncan be controlled independently through $A001.  H,h bits deal with the high block ($7200), and L,l bits deal\r\nwith the low block ($7000).\r\n\r\nIf only one block is enabled, the disabled block will read back as $00.  However if BOTH blocks are disabled,\r\nreading either will return open bus.\r\n\r\n$7000-73FF is mirrored throughout $7400-7FFF.  However, $6000-6FFF is always open bus (unmapped).\r\n\r\n$8000.5, when clear (to disable WRAM), simply sets $A001 to $00 and keeps it there.  Writing to $A001 when\r\n$8000.5 is clear will have no effect."
  },
  {
    "path": "docs/mapper/005.txt",
    "content": "\r\n========================\r\n=  Mapper 005          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nMMC5\r\nExROM\r\n\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nCastlevania 3\r\nJust Breed\r\nUncharted Waters\r\nRomance of the 3 Kingdoms 2\r\nLaser Invasion\r\nMetal Slader Glory\r\nUchuu Keibitai SDF\r\nShin 4 Nin Uchi Mahjong - Yakuman Tengoku\r\n\r\n\r\nTest ROM Notes:\r\n---------------------------\r\n- Uchuu Keibitai SDF is the only known game to use split screen mode (during the intro, where it\r\n  shows ship stats)\r\n- Shin 4 Nin Uchi Mahjong uses the extra PCM channel ($5011) as well as the other extra sound\r\n- Uncharted Waters does PRG-RAM swapping\r\n- Just Breed uses ExAttribute mode everywhere, as well as the extra sound.\r\n- Bandit Kings of Ancient China writes PRG-RAM through the $8000+ ROM area. Failure to emulate this\r\n  causes corruption when the background is restored on the world map.\r\n\r\n\r\nGeneral Notes:\r\n---------------------------\r\nMMC5 is the infamous juggernaut mapper.  It does a whole slew of neat tricks, making it far more\r\npowerful than any other mapper around.  Though despite its apparent complexity, it's surprisingly\r\nstraightforward to emulate (that doesn't mean it's easy, though).\r\n\r\nIt's a shame that the only real games to use this mapper were a ton of really, really terrible Koei\r\nstrategy games.  Such a waste.\r\n\r\n\r\nRAM Notes:\r\n----------------------------\r\nMMC5 can address up to 64k PRG-RAM!  This is significantly more than the usual 8k.  When emulating,\r\nit's easiest just to give MMC5 games a full 64k, since the header doesn't really provide a decent\r\nway to indicate how much PRG-RAM actually exists.\r\n\r\nIn addition to PRG-RAM, the MMC5 itself has a full 1k of 'ExRAM' which can be accessed by both the\r\nCPU and PPU.  This ExRAM can be used for many things... from plain vanilla WRAM, to an extra\r\nnametable, to a seperate split screen, to extending normal attribute tables.\r\n\r\n\r\nThis document's organization:\r\n---------------------------\r\nSince there are so many registers for this mapper, and it has so many features, registers will be\r\nlisted and outlined as the features are explained... and the overall registers section will be\r\nextremely brief -- serving primarily as a very quick reference or checklist.\r\n\r\nMisc Modes and Setup:\r\n---------------------------\r\n\r\n  $5102:  [.... ..AA]    PRG-RAM Protect A\r\n  $5103:  [.... ..BB]    PRG-RAM Protect B\r\n      To allow writing to PRG-RAM you must set these regs to the following values:\r\n         A=%10\r\n         B=%01\r\n      Any other values will prevent PRG-RAM writing.\r\n\r\n  $5104:  [.... ..XX]    ExRAM mode\r\n      %00 = Extra Nametable mode    (\"Ex0\")\r\n      %01 = Extended Attribute mode (\"Ex1\")\r\n      %10 = CPU access mode         (\"Ex2\")\r\n      %11 = CPU read-only mode      (\"Ex3\")\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\nThe MMC5 has two sets of CHR regs.  One set is used for sprites, the other is used for BG.  The MMC5\r\ncarefully watches what tiles are being fetched and when (or has some other way of syncing with the\r\nNES somehow), which allows it to tell when the NES is fetching BG tiles, and when it's fetching\r\nsprite tiles.  As such, it can use different regs accordingly, allowing games to basically have 12k\r\nof CHR \"active\" at once instead of the usual 8k!  This means you can have a full 512 tiles\r\nexclusively for sprites, and have an additional 256 tiles for the BG!\r\n\r\nCHR Mode Select Reg:\r\n $5101:  [.... ..CC]\r\n      %00 = 8k Mode\r\n      %01 = 4k Mode\r\n      %10 = 2k Mode\r\n      %11 = 1k Mode\r\n\r\n'High' CHR Reg:\r\n  $5130  [.... ..HH]  (see below)\r\n\r\n'A' Regs:\r\n  $5120 - $5127\r\n'B' Regs:\r\n  $5128 - $512B\r\n\r\nWhen in 8x16 sprite mode, both sets of registers are used.  The 'A' set is used for sprite tiles,\r\nand the 'B' set is used for BG.  This makes it so that sprites can have a full 8k of CHR available,\r\nwithout having to share any of the tiles with the BG (since the BG uses its own 4k of CHR,\r\ndesignated by the 'B' set).  It is unsure what you will get when reading CHR via $2007.\r\n\r\nWhen in 8x8 sprite mode, only one set is used for both BG and sprites.  Either 'A' or 'B', depending\r\non which set is written to last.  If 'B' is used, $1000-1FFF always mirrors $0000-0FFF (making the\r\n'B' set pretty worthless with 8x8 sprites)\r\n\r\n\r\n'A' Set (sprites):\r\n              $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n            +---------------------------------------------------------------+\r\n  C=%00:    |                             $5127                             |\r\n            +---------------------------------------------------------------+\r\n  C=%01:    |             $5123             |             $5127             |\r\n            +-------------------------------+-------------------------------+\r\n  C=%10:    |     $5121     |     $5123     |     $5125     |     $5127     |\r\n            +---------------+---------------+---------------+---------------+\r\n  C=%11:    | $5120 | $5121 | $5122 | $5123 | $5124 | $5125 | $5126 | $5127 |\r\n            +-------+-------+-------+-------+-------+-------+-------+-------+\r\n\r\n'B' Set (BG):\r\n              $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n             +-------------------------------+-------------------------------+\r\n   C=%00:    |                             $512B                             |\r\n             +-------------------------------+-------------------------------+\r\n   C=%01:    |             $512B             |             $512B             |\r\n             +-------------------------------+-------------------------------+\r\n   C=%10:    |     $5129     |     $512B     |     $5129     |     $512B     |\r\n             +---------------+---------------+---------------+---------------+\r\n   C=%11:    | $5128 | $5129 | $512A | $512B | $5128 | $5129 | $512A | $512B |\r\n             +-------+-------+-------+-------+-------+-------+-------+-------+\r\n\r\nNote that unlike most other mappers, these CHR pages are in *actual* sizes.  IE:  when in 4k mode,\r\nregisters contain 4k page numbers.  But when in 2k mode, register contain 2k page numbers.\r\n\r\nCHR Regs are actually 10 bits wide, not just 8.  When you write to the regs, the value written is\r\nthe low 8 bits, and the high 2 bits are copied from $5130.  Example:\r\n\r\n  LDA #$00\r\n  STA $5130  ; high bits = 0\r\n  LDA #$20\r\n  STA $5127  ; $5127 now = $020\r\n\r\n  LDA #$02\r\n  STA $5130\r\n  LDA #$41\r\n  STA $5123  ; $5123 now = $241\r\n             ; and $5127 still = $020 (not $220)\r\n\r\n$5130 has an additional role in ExAttribute mode.\r\n\r\n\r\n\r\nPRG/RAM Setup:\r\n---------------------------\r\n\r\n\r\n$5100:  [.... ..PP]    PRG Mode Select:\r\n    %00 = 32k\r\n    %01 = 16k\r\n    %10 = 16k+8k\r\n    %11 = 8k\r\n\r\n$5113:  [.... .PPP]        (simplified, but technically inaccurate -- see below)\r\n  8k PRG-RAM page @ $6000\r\n\r\n$5114-5117:  [RPPP PPPP]\r\n  R = ROM select (0=select RAM, 1=select ROM)  **unused in $5117**\r\n  P = PRG page\r\n\r\nThe high bit allows the game to select between ROM and RAM.  This allows the game to put PRG-RAM anywhere\r\nbetween $6000-DFFF (but no higher, since $5117 always selects ROM)\r\n\r\nOnly RAM can be swapped to $6000-7FFF.\r\n$5117 always selects ROM, never RAM (ROM always at $E000-FFFF).\r\n\r\n                $6000   $8000   $A000   $C000   $E000  \r\n              +-------+-------------------------------+\r\n   P=%00:     | $5113 |           <<$5117>>           |\r\n              +-------+-------------------------------+\r\n   P=%01:     | $5113 |    <$5115>    |    <$5117>    |\r\n              +-------+---------------+-------+-------+\r\n   P=%10:     | $5113 |    <$5115>    | $5116 | $5117 |\r\n              +-------+---------------+-------+-------+\r\n   P=%11:     | $5113 | $5114 | $5115 | $5116 | $5117 |\r\n              +-------+-------+-------+-------+-------+\r\n\r\n\r\nTechnically, $5113 should look something like:\r\n   [.... .CPP]\r\n     C = Chip select\r\n     P = 8k PRG-RAM page on selected chip\r\n\r\nMMC5 can address two seperate RAM chips, each up to 32k in size.\r\n\r\nThis detail can impact how RAM is mirrored across pages if the chip sizes are less than 32k.  For\r\nexample, Uncharted Waters has two 8k chips (only 16k total -- but on two seperate chips), so it uses\r\nselects pages $00 and $04, rather than $00 and $01 like you may expect.  This is because bit 2 is\r\nthe chip select, and the 8k on each chip is mirrored to every page on that chip... that is...\r\n$00-$03 would all select the first 8k.\r\n\r\nNote that no commercial games rely on this mirroring -- therefore you can take the easy way out and\r\nsimply give all MMC5 games 64k PRG-RAM.\r\n\r\n\r\nMirroring:\r\n---------------------------\r\n  $5105:  [DDCC BBAA]\r\n\r\n\r\nMMC5 allows each NT slot to be configured:\r\n  [   A   ][   B   ]\r\n  [   C   ][   D   ]\r\n\r\nValues can be the following:\r\n  %00 = NES internal NTA\r\n  %01 = NES internal NTB\r\n  %10 = use ExRAM as NT\r\n  %11 = Fill Mode\r\n\r\n\r\nFor example... some typical mirroring setups would be:\r\n              (  D  C  B  A)\r\n  Horz:  $50  (%01 01 00 00)\r\n  Vert:  $44  (%01 00 01 00)\r\n  1ScA:  $00  (%00 00 00 00)\r\n  1ScB:  $55  (%01 01 01 01)\r\n  Fill:  $ff  (%11 11 11 11)\r\n\r\n\r\nExRAM can act as a 3rd nametable here... but only in Ex0 or Ex1 (see $5104 above).  If in Ex2 or\r\nEx3, the PPU will get $00 when it attempts to read from the nametable.  Note that while ExRAM can be\r\nused as a nametable in Ex1, it's probably a bad idea, since ExRAM is also used for Extended\r\nattributes in that mode.  Therefore, when using ExRAM as a nametable, you should stick to Ex0.\r\n\r\nFill Mode is a virtual nametable.  It is not a full nametable, but rather, as the PPU attempts to\r\nread it, the MMC5 will feed it a specific tile -- thus appearing as though there's a full nametable\r\nfilled with a single tile.  The tile can be configured by the game with the following regs:\r\n\r\n  $5106:  [TTTT TTTT]     Fill Tile\r\n  $5107:  [.... ..AA]     Fill Attribute bits\r\n\r\n\r\n\r\nExtended Attribute Mode:\r\n---------------------------\r\nWhen in Ex1 mode (see $5104 above), ordinary attribute tables and BG CHR regs are ignored, and\r\ninstead, each byte in ExRAM coresponds with an onscreen tile, and assigns that tile a 4k CHR page\r\n(allowing you to choose from 16k tiles instead of 256) and its own attribute bits (allowing each 8x8\r\ntile to have its own palette, rather than having the normal 16x16 blocks).\r\n\r\nBytes in ExRAM:\r\n  [AACC CCCC]\r\n    A = Attribute bits\r\n    C = 4k CHR Page\r\n\r\nAdditionally... $5130 is used directly as the high 2 bits of CHR for every on-screen BG tile when in\r\nthis mode.  It effectively selects a 256k block for BG to use (in addition to its normal use with\r\nCHR swapping).  $5130's runtime value affects all BG tiles, therefore changing $5130 will\r\nimmediately swap all on-screen BG when in this mode.  Therefore, if/when you change $5130 to swap\r\nCHR for sprites, you must write to $5130 again with the desired value for the BG.\r\n\r\nSprites are unaffected by this mode and still use the normal CHR regs.\r\n\r\nWhich tile uses which byte in ExRAM depends on its position in the nametable.  Scrolling is\r\nirrelevent.  The tile at $2000 always uses the first byte in ExRAM, $2001 uses the second, etc.\r\n$2400, $2800, and $2C00 also use the first byte of ExRAM.\r\n\r\n\r\nCPU Accessing ExRAM:\r\n---------------------------\r\nExRAM can be accessed by the CPU via $5C00-$5FFF.  Whether or not you can read or write depends on the\r\ncurrent mode (see $5104):\r\n\r\n   Mode   Readable  Writable\r\n   -------------------------\r\n   Ex0       no        *\r\n   Ex1       no        *\r\n   Ex2      yes       yes\r\n   Ex3      yes        no\r\n\r\nIn Ex0 and Ex1, ExRAM can only be written DURING RENDERING (insane, I know).  If a game attempts to\r\nwrite outside of rendering, $00 is written instead of the desired value.  Writes have absolutely no\r\neffect in Ex3.\r\n\r\nAttempting to read when not readable will return open bus.\r\n\r\n\r\n\r\n\r\n8 * 8 -> 16 Multiplier:\r\n---------------------------\r\nMMC5 has a nifty multiplier, similar to the SNES's.\r\n\r\non write:\r\n  $5205:  multiplicand\r\n  $5206:  multiplier\r\n\r\non read:\r\n  $5205:  low 8 bits of product\r\n  $5206:  high 8 bits of product\r\n\r\nBasic functionality is, you write two values you want multiplied to $5205 and $5206, then read the\r\nproduct back.  Multiplication is unsigned.  There is no noticable delay -- that is, the product can\r\nbe read back right after writing.\r\n\r\n\r\n\r\n\r\nSplit Screen:\r\n---------------------------\r\nA unique feature to MMC5 is its ability to split the screen vertically down the middle.  However due\r\nto some limitations that couldn't be avoided, it ended up not being that useful of a feature.\r\n\r\nNote:  Split screen mode is only allowed in Ex0 or Ex1.  When in Ex2 and Ex3, it is always disabled.\r\nI do not know whether or not the split is affected by Extended Attributes when in Ex1.  Judging by\r\nthe $5202, I would assume it isn't, but that's a total guess.\r\n\r\n  $5200:  [ER.T TTTT]    Split control\r\n    E = Enable  (0=split mode disabled, 1=split mode enabled)\r\n    R = Right side  (0=split will be on left side, 1=split will be on right)\r\n    T = tile number to split at\r\n\r\n  $5201:  [YYYY YYYY]    Split Y scroll\r\n\r\n  $5202:  [CCCC CCCC]    4k CHR Page for split\r\n\r\n34 BG tiles are fetched per scanline.  MMC5 performs the split by watching which BG tile is being\r\nfetched, and if it is within the split region, replacing the normal NT data with the split screen\r\ndata according to the absolute screen position of the tile (i.e., ignoring the coarse horizontal and\r\nvertical scroll output as part of the VRAM address for the fetch).  Since it operates on a per-tile\r\nbasis... fine horizontal scrolling \"carries into\" the split region.  Setting the horizontal scroll\r\nto 1-7 will result in the split being moved to the left 1-7 pixels, however when you scroll to 8,\r\nthe split will \"snap\" back to its normal position.\r\n\r\n\r\nLeft Split:\r\n  Tiles 0 to T-1 are the split.\r\n  Tiles T and on are rendered normally.\r\n\r\nRight Split:\r\n  Tiles 0 to T-1 are rendered normally.\r\n  Tiles T and on are the split.\r\n\r\n\r\nThere is no coarse horizontal scrolling of any kind for the split.  Right-side splits will always\r\nshow the right-hand side of the nametable, and left-hand splits will always show the left-hand side\r\nof the nametable.  Coarse horizontal scrolling can still be used for the non-split region.\r\n\r\nExRAM is always used as the nametable in split screen mode.\r\n\r\nVertical scrolling for the split operates like normal vertical scrolling.  0-239 are valid scroll\r\nvalues, whereas 240-255 will display Attribute table data as NT data for the first few scanlines.\r\nThe split nametable will wrap so that the top of the nametable will appear below as you scroll (just\r\nas if vertical mirroring were employed).\r\n\r\n$5202 selects (yet another) CHR page to use for the BG.  This page is used for the split region only.\r\n\r\n\r\n\r\nIRQ Operation:\r\n---------------------------\r\nMMC5 has a scanline counter for IRQs, however it is significantly more sophisticated than MMC3's,\r\nand doesn't suffer from the same restrictions.  It is also a bit easier to use.\r\n\r\nWrite:\r\n  $5203:  [IIII IIII]    IRQ Target\r\n  $5204:  [E... ....]    IRQ Enable (0=disabled, 1=enabled)\r\n\r\nRead:\r\n  $5204:  [PI.. ....]\r\n    P = IRQ currently pending\r\n    I = \"In Frame\" signal\r\n\r\n  Reading $5204 will clear the pending flag (acknowledging the IRQ).\r\n\r\n\r\n\r\nBasic operation:\r\n1)  Write the desired scanline number to $5203\r\n2)  Enable IRQs by setting $5204.7\r\n\r\nIRQ will then trip on the given scanline number (provided PPU rendering is enabled).  The only\r\nthing to note here is that this behavior changes drastically if you turn the PPU off mid-frame...\r\nand that an IRQ will never occur when the target scanline number is 0 or greater than (?or equal\r\nto?) $F0.\r\n\r\nThe \"In Frame\" signal will read back as set when the PPU is rendering (during scanlines 0-239).\r\nThough its actual behavior and how it interacts with the IRQ counter is a bit more complex.\r\n\r\n\r\nDetailed Operation:\r\n\r\nThe IRQ counter is an up counter, rather than a down counter (like MMC3).  Every time the MMC5\r\ndetects a scanline, it does the following:\r\n\r\n- If In Frame Signal is clear...\r\n   a) Set In Frame signal\r\n   b) Reset IRQ counter to 0\r\n   c) Clear IRQ pending flag (automatically acknowledging IRQ)\r\n\r\n- otherwise...\r\n   a) Increment IRQ counter\r\n   b) If IRQ counter now equals the trigger value, raise IRQ pending flag\r\n\r\nNote that the IRQ pending flag is raised *regardless* of whether or not IRQs are enabled.  However,\r\nthis will only trigger an IRQ on the 6502 if both this flag *and* the IRQ enable flag is set.\r\nTherefore IRQs must still be enabled for this to have an effect, however the pending flag can still\r\nbe read back as set via $5204 even when IRQs are disabled.\r\n\r\nAlso note that the IRQ counter is compared after it is incremented.  This is why a trigger value of\r\n0 will never trigger an IRQ.\r\n\r\nAt any time when the MMC5 detects that the PPU is inactive, the In Frame signal is automatically\r\ncleared.  The MMC5 will detect this after rendering for the frame is complete, and as soon as the\r\nPPU is turned off via $2001.  This is why turning off the PPU mid-frame will disrupt IRQs -- since\r\nthe In Frame signal being cleared will reset the IRQ counter next scanline.\r\n\r\nHOW the MMC5 detects scanlines is still unknown.  One theory is that it looks for the two dummy\r\nnametable fetches at the end of the scanline.  Or perhaps it counts the number of fetches the PPU\r\nperforms.  Nobody knows for sure.\r\n\r\nThe IRQ will trip at the *start* of the desired scanline.  Or, more precisely, near the very end of\r\nthe previous scanline (closest I can figure is dot 336).  That is... if the trigger line is set to\r\n1, the IRQ will trip on dot 336 of scanline 0.\r\n\r\nI am unsure whether or not the last rendered scanline (239) is detected by the MMC5.  I would assume\r\nit is, which would mean a trigger value of $F0 would trip an IRQ at the end of rendering.  Trigger\r\nvalues above $F0 will never be reached, since rendering stops before then, and the in-frame signal\r\nwould automatically clear.\r\n\r\n\r\n\r\n\r\nSound:\r\n---------------------------\r\nThe MMC5 also has 3 additional sound channels!  (Will the list of features ever stop?!?!).\r\nUnfortunately, due to the NES being dumbed down, these can only be heard on a Famicom (or a modified\r\nNES).\r\n\r\nThere are 2 additional Pulse channels, and 1 additional PCM channel.\r\n\r\nRegisters for them are as follows:\r\n\r\nWrite:\r\n  $5000-5003:  Regs for Pulse 1\r\n  $5004-5007:  Regs for Pulse 2\r\n  $5010:       PCM read-only mode output (no games use this part of the PCM)\r\n  $5011:       PCM read/write mode output\r\n  $5015:  [.... ..BA]   Enable flags for Pulse 1 (A), 2 (B)  (0=disable, 1=enable)\r\n\r\nRead:\r\n  $5015   [.... ..BA]   Length status for Pulse 1 (A), 2 (B)\r\n\r\n\r\nPulse channels behave identically to the native NES pulse channels, only they lack a sweep unit.\r\nRather than going into details on their function, I recommend you pick up blargg's apu reference.\r\n\r\n$5000-5007 operate just as $4000-4007 do $5015 operates just as $4015 does (for reads and writes)\r\n\r\n\r\nNobody knows exactly how the PCM channel of the MMC5 works.  The patent documentation is unclear,\r\nand no games seem to use it apart from $5011.  $5010 likely does *something*... but nobody knows\r\nwhat.\r\n\r\n$5011 operates exactly like $4011, only it is 8 bits wide instead of 7.  Games *do* use this\r\nregister to output sound.\r\n\r\n\r\nPowerup:\r\n---------------------------\r\nGames seem to expect $5117 to be $FF on powerup (last PRG page swapped in).  Additionally, Romance\r\nof the 3 Kingdoms 2 seems to expect it to be in 8k PRG mode ($5100 = $03).\r\n\r\n\r\n\r\nRegister Overview:\r\n---------------------------\r\nDue to the massive number of registers on this mapper, this section will be brief.  Registers were\r\nall covered in detail in the sections above -- this is just to recap them all:\r\n\r\n\r\nWritable Regs:\r\n  $5000-5003:  Sound, Pulse 1\r\n  $5004-5007:  Sound, Pulse 2\r\n  $5010-5011:  Sound, PCM\r\n  $5015:       Sound, General\r\n  $5100:       PRG Mode Select\r\n  $5101:       CHR Mode Select\r\n  $5102-5103:  PRG-RAM Write protect\r\n  $5104:       ExRAM Mode\r\n  $5105:       Mirroring Mode\r\n  $5106:       Fill Tile\r\n  $5107:       Fill Attribute\r\n  $5113:       PRG-RAM reg\r\n  $5114-5117:  PRG regs\r\n  $5120-5127:  CHR regs 'A'\r\n  $5128-512B:  CHR regs 'B'\r\n  $5130:       CHR high bits\r\n  $5200:       Split Screen control\r\n  $5201:       Split Screen V Scroll\r\n  $5202:       Split Screen CHR Page\r\n  $5203:       IRQ Trigger\r\n  $5204:       IRQ Control\r\n  $5205-5206:  8*8->16 Multiplier\r\n  $5C00-5FFF:  ExRAM CPU Access\r\n\r\n\r\nReadable Regs:\r\n  $5015:       Sound Status\r\n  $5204:       IRQ Status\r\n  $5205-5206:  8*8->16 Multiplier Product\r\n  $5C00-5FFF:  ExRAM CPU Access\r\n"
  },
  {
    "path": "docs/mapper/007.txt",
    "content": "\r\n========================\r\n=  Mapper 007          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nAxROM\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nBattletoads\r\nTime Lord\r\nMarble Madness\r\n\r\n\r\nNotes:\r\n---------------------------\r\nAMROM and AOROM have bus conflicts, ANROM does not\r\nAMROM and ANROM are capped at 128k PRG\r\nAOROM is capped at 256k PRG\r\n\r\nThere is no CHR swapping.  Every mapper 007 game I've ever seen has CHR-RAM.\r\n\r\n\r\nRegisters (**BUS CONFLICTS** sometimes):\r\n--------------------------\r\n  $8000-FFFF:  [...M .PPP]\r\n    M = Mirroring:\r\n        0 = 1ScA\r\n        1 = 1ScB\r\n\r\n    P = PRG Reg  (only 2 bits wide on AMROM/ANROM)\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------------------------------+\r\n    |             $8000             |\r\n    +-------------------------------+\r\n"
  },
  {
    "path": "docs/mapper/009.txt",
    "content": "\r\n========================\r\n=  Mapper 009          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nMMC2\r\nPxROM\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nMike Tyson's Punch Out!!\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $A000-FFFF, $F000\r\n\r\n\r\n  $A000:      PRG Reg\r\n  $B000:      CHR Reg 0A\r\n  $C000:      CHR Reg 0B\r\n  $D000:      CHR Reg 1A\r\n  $E000:      CHR Reg 1B\r\n  $F000:  [.... ...M]   Mirroring:\r\n    0 = Vert\r\n    1 = Horz\r\n\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n       $8000   $A000   $C000   $E000  \r\n     +-------+-------+-------+-------+\r\n     | $A000 | { -3} | { -2} | { -1} |\r\n     +-------+-------+-------+-------+\r\n\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------------------------------+-------------------------------+\r\n    |        $B000 or $C000         |        $D000 or $E000         |\r\n    +-------------------------------+-------------------------------+\r\n\r\n\r\nWhich reg is used depends on the state of the respective latch.  See below.\r\n\r\n\r\nLatch:\r\n---------------------------\r\nThere are two latches on the MMC2.  One associated with the left pattern table ($0xxx) and another associated\r\nwith right ($1xxx).  Each latch operates independently.\r\n\r\nWhenever tile $FD is fetched, the appropriate latch is cleared, and whenever tile $FE is fetched, the\r\nappropriate latch is set.  This allows games to do mid-scanline swapping automatically by having $FD and $FE\r\nbe special marker tiles.\r\n\r\nWhen the $0xxx latch is clear, $B000 is used.  When set, $C000 is used.\r\nWhen the $1xxx latch is clear, $D000 is used.  When set, $E000 is used.\r\n\r\nThe swap occurs after the tile is fetched, not before.  So if the latch is clear, and tile $FE is loaded,\r\ntile $FE from the first reg will be drawn to the screen, but the next tile drawn will be from the second reg.\r\n\r\nLatches can be manipulated by hand by reading from the appropriate PPU address ($0FDx, $0FEx, $1FDx, $1FEx)\r\nvia $2007.\r\n\r\n\r\n"
  },
  {
    "path": "docs/mapper/010.txt",
    "content": "\r\n========================\r\n=  Mapper 010          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nMMC4\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nFire Emblem\r\n\r\n\r\nNotes:\r\n--------------------------\r\n  This mapper is identical to MMC2 (mapper 009) with the exception of the PRG setup.  See Mapper 009 for\r\ndetails.\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n       $8000   $A000   $C000   $E000  \r\n     +---------------+---------------+\r\n     |     $A000     |     { -1}     |\r\n     +---------------+---------------+\r\n"
  },
  {
    "path": "docs/mapper/011.txt",
    "content": "\r\n========================\r\n=  Mapper 011          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nCrystal Mines\r\nMetal Fighter\r\n\r\nNotes:\r\n--------------------------\r\nThis mapper suffers from bus conflicts!\r\n\r\n\r\nRegisters **BUS CONFLICTS**:\r\n--------------------------\r\n  $8000-FFFF:  [CCCC LLPP]\r\n    P = Select 32k PRG page @ $8000-FFFF\r\n    L = Lockout defeat usage\r\n    C = Select 8k CHR page @ $0000-1FFF\r\n\r\n\r\nLockout defeat:\r\n--------------------------\r\nI have no idea how this works.  Kevtris page makes mention of it.  From an emulation standpoint, it's not all\r\nthat important."
  },
  {
    "path": "docs/mapper/013.txt",
    "content": "\r\n========================\r\n=  Mapper 013          =\r\n========================\r\n\r\n\r\naka:\r\n--------------------------\r\nCPROM\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nVideomation\r\n\r\n\r\nNotes:\r\n--------------------------\r\nThis mapper uses 16k of CHR-RAM.  CHR-RAM is swappable.\r\nIt also has bus conflicts!\r\n\r\n\r\n\r\nRegisters (**BUS CONFLICTS**):\r\n--------------------------\r\n  $8000-FFFF:  [.... ..CC]\r\n    C = Select 4k CHR (RAM) page @ $1000-1FFF\r\n\r\n\r\n\r\nCHR Setup:\r\n--------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------------------------------+-------------------------------+\r\n    |             { 0 }             |             $8000             |\r\n    +-------------------------------+-------------------------------+\r\n"
  },
  {
    "path": "docs/mapper/015.txt",
    "content": "\r\n========================\r\n=  Mapper 015          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\n100-in-1 Contra Function 16\r\n\r\n\r\nNotes:\r\n---------------------------\r\n\r\nPossible bus conflicts???\r\n\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $8000-FFFF:  A~[.... .... .... ..OO]\r\n                 [pMPP PPPP]\r\n    O = Mode\r\n    p = Low bit of PRG page (not always used)\r\n    P = High bits of PRG page\r\n    M = Mirroring  (0=Vert, 1=Horz)\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\nDepending on the Mode used, the 'p' bit may not be used.  In the chart below, \"P\" will indicate that only the\r\n'P' bits are used to form a 6-bit page number... whereas \"Pp\" will indicate the full 7-bit page number.\r\n\r\n\r\n           $8000   $A000   $C000   $E000  \r\n         +---------------+---------------+\r\nMode 0:  |       P       |     P OR 1    |\r\n         +---------------+---------------+\r\nMode 1:  |       P       |     { -1}     |\r\n         +---------------+---------------+\r\nMode 2:  |  Pp   |  Pp   |  Pp   |  Pp   |\r\n         +---------------+---------------+\r\nMode 3:  |       P       |       P       |\r\n         +---------------+---------------+\r\n\r\n\r\nPowerup:\r\n---------------------------\r\nAll regs reset to 0 on powerup.\r\n"
  },
  {
    "path": "docs/mapper/016.txt",
    "content": "\r\n========================\r\n=  Mapper 016          =\r\n=       + 159          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nBandai (something or other)\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nDragon Ball - Dai Maou Jukkatsu      (016)\r\nDragon Ball Z Gaiden                 (016)\r\nDragon Ball Z 2                      (016)\r\nRokudenashi Blues                    (016)\r\nAkuma-kun - Makai no Wana            (016)\r\nDragon Ball Z - Kyoushuu! Saiya Jin  (159)\r\nSD Gundam Gaiden                     (159)\r\nMagical Taruruuto Kun 1, 2           (159)\r\n\r\n\r\nTwo Mappers:\r\n---------------------------\r\n016 and 159 are mapped the exact same way.  Registers are all the same and whatnot.  And in fact, for a\r\nwhile, both mappers were assigned the same mapper number (016).  Therefore, you may come across mapper 159\r\ngames that are still marked as mapper 016.\r\n\r\nThe difference between the two is in the EPROM.  These mappers don't have traditional SRAM (I couldn't tell\r\nyou why).  Instead, they have EPROM that has to be written to one bit at a time, with very strange register\r\nwrites.\r\n\r\nMapper 016 has 256 bytes of EPROM, and is accessed high bit first\r\nMapper 159 has 128 bytes of EPROM, and is accessed low bit first\r\n\r\nFor further details, see the section at the bottom.\r\n\r\nApart from EPROM, the mappers are 100% identical in function.\r\n\r\n\r\n\r\nNotes:\r\n---------------------------\r\nSince there's EPROM, there's no SRAM (EPROM is used to save games).\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $6000-FFFF, $000F\r\n\r\nNote:  below regs are listed as $800x, but note they also exist at $6000-7FFF\r\n\r\n\r\n  $8000-8007:   CHR Regs\r\n  $8008:        PRG Reg (16k @ $8000)\r\n\r\n  $8009:  [.... ..MM]   Mirroring:\r\n    %00 = Vert\r\n    %01 = Horz\r\n    %10 = 1ScA\r\n    %11 = 1ScB\r\n\r\n  $800A:  [.... ...E]   IRQ Enable (0=disabled)\r\n  $800B:                Low 8 bits of IRQ Counter\r\n  $800C:                High 8 bits of IRQ Counter\r\n\r\n  $800D:  EPROM I/O\r\n\r\nanother note:   since PRG is mapped to $8000-FFFF, EPROM I/O reg can only be read via $6xxx or $7xxx.  To my\r\nknowledge no other registers are readable.  It also appears that reading from *ANY* address in $6xxx-7xxx\r\nwill read the EPROM I/O reg.  Rokudenashi Blues will poll $7F00 and will wait for bit 4 to be 0 before\r\ncontinuing (so if you're giving open bus @ 7F00, the game will deadlock)\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n    | $8000 | $8001 | $8002 | $8003 | $8004 | $8005 | $8006 | $8007 | \r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $8008     |     { -1}     |\r\n    +---------------+---------------+\r\n\r\n\r\n\r\nIRQs:\r\n---------------------------\r\nIRQs are nice and simple.\r\n\r\nWhen enabled, the 16-bit IRQ counter counts down every CPU cycle, wrapping from $0000->FFFF.  When the\r\ncounter makes the transition from $0001->$0000, an IRQ is generated.\r\n\r\nWhen disabled, the IRQ counter does not count.\r\n\r\nAny write to $800A will acknowledge the IRQ\r\n\r\n$800B and $800C change the IRQ counter directly -- not a reload value.\r\n\r\n\r\nEPROM:\r\n---------------------------\r\nEPROM is a real nightmare.  Nobody knows for sure exactly how it works -- but by examining the game code,\r\npatterns surface.  Games do a series of extremely cryptic writes to $800D, and occasionally read a single\r\nbit from $800D.  By examining some logs I made of the games I've noticed a small bit of patterns which I\r\nlist below, along with my guess as to what the game is attempting to do by performing that pattern:\r\n\r\n\r\n  write $00\r\n  write $40\r\n  write $60     Start I/O\r\n  write $20\r\n  write $00\r\n\r\n  write $00\r\n  write $20     Output '0' bit\r\n  write $00\r\n\r\n  write $00\r\n  write $40\r\n  write $60     Output '1' bit\r\n  write $40\r\n  write $00\r\n\r\n  write $00\r\n  write $20\r\n  write $A0    I have absolutly no clue\r\n  Read\r\n  write $00\r\n\r\n  write $60\r\n  write $E0    Read a single bit\r\n  Read\r\n  write $40\r\n\r\n  write $00\r\n  write $20\r\n  write $60    Stop I/O\r\n  write $40\r\n  write $C0\r\n\r\n\r\nThese likely aren't the only patterns that games perform.  I recall seeing occasional writes of $80 and\r\nother stuff thrown in there in some games.  Also -- not all games follow this pattern, so looking for these\r\nspecific writes will not work for at least one other game.\r\n\r\nIt seems that only bits 5-7 of the written value are relevent (hereon, they will be referred to as D5 - D7).\r\nBit 4 ($10) is the only significant bit when read.  Other bits are most likely open bus.\r\n\r\n\r\nWhen writing bytes to EPROM, games will generally perform 8 \"output\" patterns (either output 0 or output 1,\r\ndepending on the bits it wants to write), followed by a 9th output pattern, which I would assume finalizes\r\nthe write and/or possibly moves the 8 bits from a latch to EPROM.\r\n\r\nWhen reading bytes, games will generally perform 8 \"read\" patterns, followed by a single output pattern\r\n(which I would assume finalizes the read).\r\n\r\nSometimes when the game is writing bits, it's writing data to be stored on EPROM, and other times it's\r\nsetting the desired EPROM address and/or read/write mode.  Knowing which it's doing involves keeping track\r\nof the state it's currently it and what it has done last, etc, etc.\r\n\r\nBut again -- nobody *really* knows how it works.  The method I've employed in my emu is outlined below -- and\r\nit appears to work for every game I've tried, but I *KNOW* it's not accurate.  But, short of some hardware\r\nguru acquiring a handful of these carts and doing a thorough RE job, that's about the best anyone can do.\r\n\r\n\r\n\u000b\r\nEmulating EPROM:\r\n-----------------------\r\n\r\nSUPER FAT IMPORTANT NOTE:  This is just the method of EPROM emulation I employ in my emu.\r\n\r\n     ***THIS IS NOT HOW THE ACTUAL HARDWARE WORKS***\r\n\r\nDo not use this as a final word or anything -- this is simply the product of lots of guesswork, speculation,\r\nand trial and error.\r\n\r\n\r\nD5 appears to be the \"trigger\" bit, and D6 appears to be the \"signal\" bit.  I have no clue what D7 does, and\r\nignoring it completely has worked for me (though I'm sure it does have some purpose).  \"Commands\" are sent\r\nby toggling D5 (0->1->0).  Two states of D6 are observed -- one when D5 rises (0->1), and one when it falls\r\n(1->0).  Using these two observed states, you get 4 possible commands.  The command is sent when D5 falls.\r\n\r\nExample:\r\n\r\n         byte  D6 D5 \r\n  write: $00    0 0\r\n  write: $40    1 0\r\n  write: $60    1 1  <--  D5 rise:    D6=1\r\n  write: $40    1 0  <--  D5 fall:    D6=1, command \"1,1\" sent here\r\n  write: $00    0 0\r\n\r\nThe above sequence would issue a \"1,1\" command.\r\n\r\nCommands:\r\n\r\n  Name     rise,fall       example write sequence\r\n  ------------------------------------------------\r\n  Write 0     0,0          $00, $20, $00\r\n  Write 1     1,1          $00, $40, $60, $40, $00\r\n  Open        1,0          $00, $40, $60, $20, $00\r\n  Close       0,1          $00, $20, $60, $40, $C0\r\n\r\n\r\nThe unit can be in one of several modes:\r\n\r\n  - Closed\r\n  - Select\r\n  - Address\r\n  - Write\r\n  - Read\r\n\r\nI also use an 8-bit temporary value, an 8-bit address (or 7-bit address, if 128 byte EPROM) and 9-step bit\r\ncounter.\r\n\r\nI would assume the unit is Closed on startup (and possibly reset).\r\n\r\n\r\nBasic Concept overview:\r\n\r\n\r\n  \"Write 0\" and \"Write 1\" commands advance the 9-step bit counter.  The first 8 writes fill the appropriate\r\nbit in the temporary value.  The 9th write will take the temp value and move it to either the address (if in\r\nAddress mode), or to the desired area in EPROM (if in Write mode), and the mode will update accordingly.\r\nBasically the first 8 writes fill the temp value and the 9th moves it to where it needs to go.\r\n\r\n  Reads operate similarly... but the temp buffer isn't affected by the writes, and the 9th step doesn't copy\r\nthe temp value anywhere.  Note however that games will perform a write between each bit read (presumably to\r\nadvance it to the next bit) -- so you should do nothing but return the appropriate bit when the game reads\r\nthe EPROM I/O Reg (do not advance it to the next bit on read).\r\n\r\n  \"Select\" mode exists on 256 byte EPROM only (mapper 016).  It is used to select between read/write mode.\r\nBit 0 of the 8-bit value written when in Select mode determines read/write mode.  On 128 byte EPROM (mapper\r\n159), the high bit of the address selects read/write mode.  In both cases, 1=read mode, 0=write mode.\r\n\r\n  Remember that on 128 byte, values are written low bit first... but on 256 byte, they're written high bit\r\nfirst.  Bits are read the same order they're written.\r\n\r\n  Doing anything but opening when the unit is closed has no effect.\r\n\r\n\r\nLogic Flow Details (256-byte ... mapper 016)\r\n--------------------------------------------\r\n\r\nOpening from Closed Mode:\r\n  a) Enter Select Mode\r\n\r\nOpening from non-Closed Mode:\r\n  a) if in Select Mode, increment address by 1\r\n  b) enter Select Mode.\r\n  c) Reset bit counter (next write is the first write in the 9-write sequence)\r\n\r\nWriting in Select Mode:\r\n  a) If low bit of written value = 1\r\n     -) Enter Read Mode\r\n  b) otherwise...\r\n     -) Enter Address Mode\r\n\r\nWriting in Address Mode:\r\n  a) written value becomes address\r\n  b) Enter Write mode\r\n\r\nWriting in Write Mode:\r\n  a) written value moves to current address of EPROM\r\n  b) mode is not changed\r\n\r\nWriting in Read Mode:\r\n  a) Enter Select Mode\r\n\r\n\r\n\r\nLogic Flow Details (128-byte ... mapper 159)\r\n--------------------------------------------\r\n\r\nOpening from Closed Mode:\r\n  a) Enter Address Mode\r\n\r\nOpening from non-Closed Mode:\r\n  a) increment address by 1 (wrap $7F->00)\r\n  b) do not change mode\r\n  c) Reset bit counter (next write is the first write in the 9-write sequence)\r\n\r\nWriting in Address Mode:\r\n  a) written value becomes address (low 7 bits only)\r\n  b) if high bit of written value is set...\r\n     -) Enter Read Mode\r\n  c) otherwise...\r\n     -) Enter Write Mode\r\n\r\nWriting in Write Mode:\r\n  a) written value moves to current address of EPROM\r\n  b) Enter Address mode\r\n\r\nWriting in Read Mode:\r\n  a) Enter Address Mode\r\n\r\n\u0000"
  },
  {
    "path": "docs/mapper/018.txt",
    "content": "\r\n========================\r\n=  Mapper 018          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nThe Lord of King\r\nMagic John\r\nPizza Pop\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $F003\r\n\r\n  $800x,$900x:  [.... PPPP]  PRG Regs\r\n  $A00x-$D00x:  [.... CCCC]  CHR Regs\r\n  $E00x:  [.... IIII]  IRQ Reload value\r\n  $F000:  [.... ....]  IRQ Reset\r\n  $F001:  [.... SSSE]  IRQ Control\r\n     S = Size of IRQ counter\r\n     E = Enable\r\n\r\n  $F002:  [.... ..MM]  Mirroring\r\n     %00 = Horz\r\n     %01 = Vert\r\n     %10 = 1ScA\r\n     %11 = 1ScB\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\nOnly low 4 bits of written value significant [.... CCCC]\r\n\r\n2 regs combined to get an 8-bit page number\r\n\r\n$x000 or $x002 are the low 4 bits\r\n$x001 or $x003 are the high 4 bits\r\n\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n    |$A000+1|$A002+3|$B000+1|$B002+3|$C000+1|$C002+3|$D000+1|$D002+3|\r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\nSame as CHR, $x000 low, $x001 high\r\n\r\n      $8000   $A000   $C000   $E000 \r\n    +-------+-------+-------+-------+\r\n    |$8000+1|$8002+3|$9000+1| { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\n\r\nIRQ:\r\n---------------------------\r\n\r\n16-bit IRQ Reload value is set via regs $E00x.  $E000 sets the low 4 bits, $E003 sets the high 4 bits.\r\n\r\nWhen enabled, the IRQ counter counts down every CPU cycle.  When it wraps, an IRQ is generated.\r\n\r\nThe 'S' bits in the control reg determine the size of the IRQ counter.  It can be 4, 8, 12, or 16 bits wide:\r\n\r\n  %000 = 16 bits wide\r\n  %001 = 12 bits wide\r\n  %01x = 8 bits wide\r\n  %1xx = 4 bits wide\r\n\r\nIf the counter is less than 16 bits, the high bits are not altered by IRQ counter clocking; they retain their\r\nvalue.\r\n\r\nExample:  if the IRQ counter contains $1232, and is in 4-bit mode, it counts like so:\r\n\r\n  $1232\r\n  $1231\r\n  $1230\r\n  $123F  <--- IRQ here\r\n  $123E\r\n   ...\r\n\r\n\r\nAny write to the reset reg ($F000) will copy the 16-bit reload value into the IRQ counter (full 16 bits are\r\ncopied, regardless of current 'S' value).\r\n\r\nAny write to $F000 or $F001 will acknowledge the IRQ.\r\n"
  },
  {
    "path": "docs/mapper/019.txt",
    "content": "\r\n========================\r\n=  Mapper 019          =\r\n=       + 210          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nNamcot 106\r\nN106\r\n\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nDigital Devil Story - Megami Tensei 2   (019)\r\nFinal Lap                      (019)\r\nRolling Thunder (J)            (019)\r\nSplatter House                 (019)\r\nMappy Kids                     (019)\r\nFamily Circuit '91             (210)\r\nWagyan Land 2,3                (210)\r\nDream Master                   (210)\r\n\r\n\r\n\r\nGeneral Notes:\r\n--------------------------\r\nFor a while, this mapper number was shared with 210.  Therefore, there are a lot of ROMs floating around that\r\nare labelled as mapper 019 that are really mapper 210.  \r\n\r\nSome games require CHR-RAM in addition to any CHR-ROM present.  I'm uncertain exactly how much, but giving\r\nthem 8k seems to work.\r\n\r\nMapper 019 also has an additional 128 bytes of Sound RAM, which is used for waveform tables and sound\r\nregisters.  Kaijuu Monogatari uses this as battery backed SRAM.\r\n\r\nThe rest of the doc applies to both mapper numbers.  Differences between the two (mirroring and sound) will\r\nbe noted where appropriate.\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\nRange,Mask:   $4800-FFFF, $F800\r\n\r\nWritable and Readable:\r\n  $4800:  [DDDD DDDD]    Sound Data port (see Sound section for details)\r\n                           (mapper 019 only)\r\n\r\n  $5000:  [IIII IIII]    Low 8 bits of IRQ counter\r\n  $5800:  [EIII IIII]\r\n      E = IRQ Enable (0=disabled, 1=enabled)\r\n      I = High 7 bits of IRQ counter\r\n\r\n\r\n\r\n  $6000-7FFF:   mapped to PRG-RAM, not registers\r\n\r\nWritable only:\r\n  $8000-B800:          CHR Regs\r\n  $C000-D800:          Mirroring Regs   (mapper 019 only)\r\n  $E000:  [..PP PPPP]  PRG Reg 0 (8k @ $8000)\r\n  $E800:  [HLPP PPPP]\r\n      H = High CHR RAM Disable  (see CHR setup for details)\r\n      L = Low CHR RAM Disable\r\n      P = PRG Reg 1 (8k @ A000)\r\n\r\n  $F000:  [..PP PPPP]  PRG Reg 2 (8k @ $C000)\r\n\r\n  $F800:  [IAAA AAAA]  Sound Address (with auto-increment enable bit)\r\n                       (See Sound section for details) (mapper 019 only)\r\n\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    | $E000 | $E800 | $F000 | { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\n\r\nCHR Setup:\r\n--------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n    | $8000 | $8800 | $9000 | $9800 | $A000 | $A800 | $B000 | $B800 |\r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n\r\nPage numbers lower than $E0 will select CHR-ROM.  Page numbers greater than or equal to $E0 will select\r\nCHR-RAM (RAM page N - $E0)  *unless* CHR-RAM for the region is disabled via the appropriate bit in $E800.\r\n\r\n$E800.6, when set, disables RAM selection for $0xxx ($8000-9800 will always select ROM)\r\n$E800.7, when set, disables RAM selection for $1xxx ($A000-B800 will always select ROM)\r\n\r\nCHR-RAM disable allows games to utilize all 256k of CHR-ROM.  When CHR-RAM is enabled, only 224k can be\r\naccessed.\r\n\r\n\r\nMirroring:\r\n--------------------------\r\n\r\nThis section applies to mapper 019 only.  210 has hardwired mirroring\r\n\r\n  [ $C000 ][ $C800 ]\r\n  [ $D000 ][ $D800 ]\r\n\r\nValues less than $E0 select a CHR-ROM page for a NT.  Values $E0 and up use NES's internal nametables\r\n(low bit selects which).\r\n\r\nTypical Examples:\r\n         $C000 $C800 $D000 $D800\r\n         -----------------------\r\nHorz:     $E0   $E0   $E1   $E1\r\nVert:     $E0   $E1   $E0   $E1\r\n\r\n\r\n\r\nIRQ Operation:\r\n--------------------------\r\n\r\nIRQs are driven by a 15-bit CPU cycle up-counter.  $5000 and $5800 are *direct* access to the IRQ counter\r\n(they are not a reload value).  Games can also read back the real-time state of the IRQ counter by reading\r\nthose regs.\r\n\r\nWhen IRQs are enabled, the following occurs every CPU cycle:\r\n\r\n- If IRQ Counter = $7FFF\r\n   a) Trip IRQ\r\n\r\n- otherwise...\r\n   a) Increment IRQ counter by 1\r\n\r\nReading/Writing $5000 or $5800 will acknowledge the pending IRQ.\r\n\r\n\r\nSources on the behavior of this IRQ counter vary.  Some say that the IRQ counter wraps from $7FFF to $0000,\r\nand trips an IRQ only when it wraps -- however Sangokushi 2 polls $5800, and emulating IRQs that way results\r\nin the game locking up shortly after it starts (once it sees that $5800 is not what it expects, it resets the\r\nIRQ counter and loops)\r\n\r\nEmulating the IRQ counter as above seems to work for every game out there -- although it probably isn't 100%\r\naccurate.\r\n\r\n\r\n\r\n\r\nSound:\r\n--------------------------\r\n\r\nSound applies to mapper 019 only.  Mapper 210 has no extra sound.\r\n\r\nN106 has some pretty sweet expansion sound.  And it's used in several games to boot!  (More than any other\r\nexpansion except for FDS)\r\n\r\nThe N106 has up to 8 additional sound channels, each which plays back a configurable waveform of variable\r\nlength, as well as having full volume control for each channel.\r\n\r\nThere are 128 bytes of Sound RAM inside the N106 which is used to hold the waveform data, as well as sound\r\nregisters.  This RAM is accessed by setting the desired address by writing to $F800, then writing the\r\ndesired data to $4800.  $4800 is also readable.\r\n\r\n  $F800:  [IAAA AAAA]\r\n     I = Auto-increment flag\r\n     A = Sound RAM Address\r\n\r\nIf the auto-increment flag is set, the Sound RAM address will increment (wrapping $7F->00) after every $4800\r\nread/write.\r\n\r\nSound Channel registers (inside Sound RAM):\r\n\r\nregs:  \"A\"   \"B\"   \"C\"   \"D\"   \"E\"\r\n      ------------------------------\r\nCh 0 - $40   $42   $44   $46   $47\r\nCh 1 - $48   $4A   $4C   $4E   $4F\r\nCh 2 - $50   $52   $54   $56   $57\r\nCh 3 - $58   $5A   $5C   $5E   $5F\r\nCh 4 - $60   $62   $64   $66   $67\r\nCh 5 - $68   $6A   $6C   $6E   $6F\r\nCh 6 - $70   $72   $74   $76   $77\r\nCh 7 - $78   $7A   $7C   $7E   $7F\r\n\r\n\r\n  \"A\":  [FFFF FFFF]     Low 8 freq bits\r\n  \"B\":  [FFFF FFFF]     Mid 8 freq bits\r\n  \"C\":  [...L LLFF]\r\n      F = High 2 freq bits\r\n      L = Instrument Length (4 * (8-L))\r\n\r\n  \"D\":  [AAAA AAAA]     Instrument address\r\n  \"E\":  [.... VVVV]     Volume\r\n\r\n\r\nSpecial Reg $7F:\r\n   [.EEE VVVV]\r\n      E = Number of Enabled channels (E+1)\r\n      V = Channel 7's volume control\r\n\r\n\r\n\r\n      Instruments:\r\n\r\nInstruments are in 4-bit samples.  Each byte in sound RAM represents two samples (low 4 bits being the first\r\nsample, high 4 bits being the second sample).  Each channel has an address which it uses to look for the\r\ninstrument ('A' bits in reg \"D\"), as well as a length indicating how many samples are in the instrument ('L'\r\nbits in reg \"C\").\r\n\r\nThe instrument address is in 4-bit samples.  IE:  When the instrument address is $20, the instrument starts\r\nat the low 4 bits of byte address $10.  A instrument address of $41 would be the high 4 bits of byte address\r\n$20.\r\n\r\nInstrument Length is 4 * (8-L)  4-bit samples.  Therefore if L=3, the instrument is 20 4-bit samples long.\r\n\r\nSamples are unsigned:  '0' is low, 'F' is high.\r\n\r\nFor an example waveform... given the following instrument:\r\n\r\n  $A8 DC EE FF FF EF DE AC 58 23 11 00 00 10 21 53  (length of 32 ... L=0)\r\n\r\nThe following waveform (a pseudo-sine wave) would be produced:\r\n\r\n\r\nF -       *****\r\nE -     **     **\r\nD -    *         *\r\nC -   *           *\r\nB -\r\nA -  *             *\r\n9 - \r\n8 - *               *\r\n7 - \r\n6 -\r\n5 -                  *             *\r\n4 -\r\n3 -                   *           *\r\n2 -                    *         *\r\n1 -                     **     **\r\n0 -                       *****\r\n  __________________________________\r\n\r\nThe waveform would continually loop this pattern\r\n\r\n\r\n\r\n      Channel Disabling:\r\n\r\nReg $7F controls the number of enabled channels.  As little as 1 or as many as all 8 channels can be enabled.\r\nWhen not all channels are enabled, the high channels are the ones being used.  That is, if only 3 channels\r\nare enabled, channels 5, 6, and 7 are the ones enabled, and the others are disabled.\r\n\r\nDisabling channels frees up more Sound RAM space for instruments (since the lower channels' registers are\r\nunused when disabled).  Also, since there are fewer channels to clock, the enabled channels are clocked more\r\nquickly, resulting in higher quality sound and potentially higher tones (see frequency calculation)\r\n\r\n\r\n       Frequency Calculation:\r\n\r\nThe generated tone of each channel can be calculated with the following formula:\r\n\r\n                F * CPU_CLOCK\r\n  Hz =   --------------------------\r\n          $F0000 * (E+1) * (8-L)*4\r\n\r\nwhere:\r\n            F = the 18-bit Freq value\r\n    CPU_CLOCK = CPU clock rate (1789772.727272 on NTSC)\r\n            E = Enabled Channels (bits as written to reg $7F)\r\n            L = Instrument Length (bits as written)\r\n\r\n\r\n  Or... you can figure it as the number of CPU cycles that have to pass before the channel takes the next\r\nstep through its instrument:\r\n\r\n            $F0000 * (E+1)\r\nCycs =    ------------------\r\n                  F\r\n\r\n\r\n  When F is 0, the channel is essentially \"frozen\" at it's current position and does not update (and thus,\r\nbecomes silent).\r\n"
  },
  {
    "path": "docs/mapper/021.txt",
    "content": "\r\n========================\r\n=  Mapper 021          =\r\n=       + 023          =\r\n=       + 025          =  \r\n========================\r\n\r\naka\r\n--------------------------\r\nVRC4\r\n\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nWai Wai World 2          (021)\r\nGanbare Goemon Gaiden 2  (021)\r\nBoku Dracula-kun         (023)\r\nTiny Toon Adventures (J) (023)\r\nGradius 2 (J)            (025)\r\nBio Miracle Bokutte Upa  (025)\r\n\r\n\r\n\r\nMultiple numbers, just one mapper:\r\n--------------------------\r\nThese three mapper numbers (021, 023, 025) collectively represent different wiring variations of the same mapper:\r\nVRC4.  Each variation operates exactly the same, only the registers used are different because they all use\r\ndifferent address lines.  Some lines are even reversed from the norm.\r\n\r\n   variant   lines     registers                       Mapper Number\r\n   =================================================================\r\n   VRC4a:    A1, A2    $x000, $x002, $x004, $x006      021\r\n   VRC4b:    A1, A0    $x000, $x002, $x001, $x003      025\r\n   VRC4c:    A6, A7    $x000, $x040, $x080, $x0C0      021\r\n   VRC4d:    A3, A2    $x000, $x008, $x004, $x00C      025\r\n   VRC4e:    A2, A3    $x000, $x004, $x008, $x00C      023\r\n   VRC4f:    A0, A1    $x000, $x001, $x002, $x003      023  * see below *\r\n\r\n\r\nThis doc will use the 'VRC4a' registers (0,2,4,6) in all following register descriptions.  For other\r\nvariants, use the above chart to convert.\r\n\r\nI'm unsure whether or not 'VRC4f' really exists.  It seems to use the same registers are mapper 023's VRC2\r\ncounterpart (see 022.txt) but also has IRQ functionality (appears to be used by Tiny Toon Adventures).  Could\r\nit be that 023 is really VRC4 and not a VRC4+VRC2 mix?\r\n\r\n\r\nRegisters:\r\n--------------------------\r\nSome registers are mirrored across several addresses.  For example, writing to $9004 has the same effect as\r\nwriting to $9006.\r\n\r\n$8000-$8006:  [...P PPPP]   PRG Reg 0\r\n$9000,$9002:  [.... ..MM]   Mirroring:\r\n   %00 = Vert\r\n   %01 = Horz\r\n   %10 = 1ScA\r\n   %11 = 1ScB\r\n\r\n$9004,$9006:  [.... ..M.]   PRG Swap Mode Select\r\n$A000-$A006:  [...P PPPP]   PRG Reg 1\r\n$B000-$E006:  [.... CCCC]   CHR Regs (see CHR Setup)\r\n\r\n$F000+$F002:  [.... IIII]   IRQ Reload Value (see IRQ section)\r\n$F004         [.... .MEA]   IRQ Control (see IRQ section)\r\n$F006         [.... ....]   IRQ Acknowledge (see IRQ section)\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\nThere are two PRG modes, which can be seleted via $9004.\r\n\r\n               $8000   $A000   $C000   $E000  \r\n             +-------+-------+-------+-------+\r\nPRG Mode 0:  | $8000 | $A000 | { -2} | { -1} |\r\n             +-------+-------+-------+-------+\r\nPRG Mode 1:  | { -2} | $A000 | $8000 | { -1} |\r\n             +-------+-------+-------+-------+\r\n\r\n\r\nCHR Setup:\r\n--------------------------\r\nThe VRC4 only has 5 data pins.  To compensate, two CHR regs are combined to form a single page number. \r\nOne reg contains the high 5 bits and the other reg contains the low 4 bits (allowing for 9-bit page numbers)\r\n\r\nExample:  $B000+$B002  select 1k CHR page @ $0000\r\n    if   $B000=$03\r\n    and  $B002=$01\r\n    then use page $13\r\n\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n    |$B000+2|$B004+6|$C000+2|$C004+6|$D000+2|$D004+6|$E000+2|$E004+6|\r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n\r\n\r\n--------------------------------------------------\r\n--------------------------------------------------\r\n\r\n\r\nVRC IRQs:\r\n--------------------------\r\nVRC IRQ logic is shared by VRC4, VRC6, and VRC7.  IRQs for all of those mappers operate exactly the same way.\r\nTherefore, this section applies to all of those mappers (other docs refer here).\r\n\r\nOne thing in paticular to note with VRC4 that is different from VRC6, VRC7 is that the reload register is\r\nsplit in two just as CHR regs are.  $F000 specifies the low 4 bits of the reload value, and $F002 specfies\r\nthe high 4 bits.  This only happens in VRC4.   VRC6 and VRC7 have a single 8-bit register to specify the\r\nreload value.  The rest of this doc will refer to this reload value as a single register.\r\n\r\n\r\nNotes:\r\n--------------------------\r\nVRC IRQs are unique in that they simulate a scanline counter, without actually counting scanlines.  The IRQ\r\ncounter is actually a CPU cycle counter, with a prescaler that divides clocks by ~113.666667 CPU cycles (one\r\nNTSC scanline).  This results in the IRQ counter being clocked once per scanline -- however unlike true\r\nscanline counters, it will be clocked even when the PPU is inactive, and even during VBlank!\r\n\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\nThere are 3 registers relevant to VRC IRQs.  See respective mapper doc for which register corresponds to\r\nwhich address:\r\n\r\n  IRQ Reload:       [IIII IIII]\r\n     This register specifies the counter reload value.  It does not affect the counter itself.\r\n\r\n  IRQ Control:      [.... .MEA]\r\n     M = IRQ Mode (0=scanline mode, 1=CPU cycle mode)\r\n     E = Enable (0=disabled, 1=enabled)\r\n     A = Enable-on-acknowledge (see below)\r\n\r\n     - If 'E' is written as set, the IRQ counter will be immediately reloaded with the \r\n       reload value, and the prescaler will be reset.  IRQs will also be enabled.\r\n     - If 'E' is written as clear, the IRQ counter and prescaler are not changed, and IRQs are disabled.\r\n     - Any write to this register will acknowledge the IRQ.\r\n\r\n\r\n  IRQ Acknowledge:  [.... ....]\r\n     Any write to this register will acknowledge the IRQ.  In addition, the 'A' control bit is copied to the\r\n'E' control bit (enabling or disabling IRQs).  No write to this register will change the state of the IRQ\r\ncounter or prescaler.\r\n\r\n\r\nOperation:\r\n--------------------------\r\n\r\nWhen in scanline mode ('M' control bit clear), a prescaler divides the passing CPU cycles by 114, 114, then\r\n113 (and then repeats that pattern).  This averages 113 + 2/3 CPU cycles (1 NTSC scanline).  When the\r\nprescaler is reset, the sequence is reset, and it will be 114 CPU cycles until the next IRQ counter clock.\r\n\r\nA simple way to emulate prescaler behavior is to have it reset to 341, and subtract 3 every CPU cycle.  When\r\nit drops to or below 0, increment it by 341 and clock the IRQ counter once.  This will produce the\r\n114,114,113 repeating pattern.\r\n\r\nWhen in cycle mode ('M' control bit set), the prescaler is effectively bypassed, and the IRQ counter gets\r\nclocked every CPU cycle.  In this mode, the prescaler remains unchanged by passing CPU cycles.\r\n\r\nIf IRQs are disabled, neither the prescaler nor IRQ counter get clocked.\r\n\r\n\r\nWhen the IRQ counter is clocked:\r\n - If IRQ counter = $FF...\r\n    a) reload IRQ counter with reload value\r\n    b) trip IRQ\r\n\r\n - otherwise...\r\n    a) increment IRQ counter by 1"
  },
  {
    "path": "docs/mapper/022.txt",
    "content": " ========================\r\n=  Mapper 022          =\r\n=       + 023          =\r\n=       + 025          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nVRC2\r\n\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nGanbare Pennant Race     (022)\r\nTwinBee 3                (022)\r\nWai Wai World            (023)\r\nGanbare Goemon Gaiden    (025)\r\n\r\n\r\n\r\n\r\nMultiple numbers, just one mapper:\r\n--------------------------\r\nThese mapper numbers (022, 023) represent 2 wiring variations of the same mapper:  VRC2.  Each variation\r\noperates the same, only the registers used are different because their lines are reversed from each other:\r\n\r\nvariant   lines     registers                       Mapper Number\r\n=================================================================\r\nVRC2a:    A1, A0    $x000, $x002, $x001, $x003      022   * divides CHR bank select by two\r\nVRC2b:    A0, A1    $x000, $x001, $x002, $x003      023\r\nVRC2c:    A1, A0    $x000, $x002, $x001, $x003      025   * does NOT divide CHR bank select by two\r\n\r\nThis doc will use the 'VRC2b' registers (0,1,2,3) in all following register descriptions.  For 'VRC2a',\r\nsimply reverse $x001 and $x002 registers.\r\n\r\n\r\nVRC2a CHR:\r\n---------------------------\r\nImportant note!  On VRC2a (mapper 022) only the high 7 bits of the CHR regs are used -- the low bit is\r\nignored.  Therefore, you effectively have to right-shift the CHR page by 1 to get the actual page number.\r\n\r\nFor example... both $06 and $07 would both indicate page $03\r\n\r\nThis applies to VRC2a only.  VRC2b (mapper 023) behaves normally.\r\n\r\n\r\n\r\nVRC2 vs. VRC4:\r\n--------------------------\r\nVRC2 is strikingly similar to VRC4 (see mapper 021).  The differences are:\r\n\r\n1)  VRC4 has IRQs, VRC2 does not\r\n2)  VRC4 has 5 bits for PRG regs, VRC2 only has 4 bits\r\n3)  VRC4 has 2 PRG modes, VRC2 does not.\r\n4)  VRC4 has 9 bit CHR banks, VRC2 only has 8\r\n5)  VRC4 internally supports external RAM, VRC2 does not\r\n\r\n\r\nThose differences aside -- they act exactly the same.\r\n\r\n\r\n\r\nRegisters:\r\n--------------------------\r\nSome registers are mirrored across several addresses.  For example, writing to $8003 has the same effect as\r\nwriting to $8000.\r\n\r\n\r\n$8000-$8003:  [.... PPPP]   PRG Reg 0 (select 8k @ $8000)\r\n$9000-$9003:  [.... ..MM]   Mirroring:\r\n%00 = Vert\r\n%01 = Horz\r\n%10 = 1ScA\r\n%11 = 1ScB\r\n\r\n$A000-$A003:  [.... PPPP]   PRG Reg 1 (select 8k @ $A000)\r\n$B000-$E003:  [.... CCCC]   CHR Regs (see CHR Setup)\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n$8000   $A000   $C000   $E000  \r\n+-------+-------+-------+-------+\r\n| $8000 | $A000 | { -2} | { -1} |\r\n+-------+-------+-------+-------+\r\n\r\n\r\n\r\nCHR Setup:\r\n--------------------------\r\nThe VRC2 only has 4 data pins for CHR Regs.  To compensate, two CHR regs are combined to form a single page\r\nnumber.  One reg contains the high 4 bits and the other reg contains the low 4 bits (allowing for 8-bit page\r\nnumbers)\r\n\r\nExample:  $B000+$B001  select 1k CHR page @ $0000\r\nif   $B000=$03\r\nand  $B001=$01\r\nthen use page $13 (VRC2b)\r\n   or page $09 (VRC2a -- see notes above)\r\n\r\n\r\n$0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n+-------+-------+-------+-------+-------+-------+-------+-------+\r\n|$B000+1|$B002+3|$C000+1|$C002+3|$D000+1|$D002+3|$E000+1|$E002+3|\r\n+-------+-------+-------+-------+-------+-------+-------+-------+"
  },
  {
    "path": "docs/mapper/023.txt",
    "content": "\r\n========================\r\n=  Mapper 023          =\r\n========================\r\n\r\n\r\nMapper 023 includes both a variant of VRC4, and a variant of VRC2.  Both of which are covered in other docs.\r\n\r\n\r\nFor details see the following:\r\n\r\nmapper 021 (VRC4)\r\nmapper 022 (VRC2)"
  },
  {
    "path": "docs/mapper/024.txt",
    "content": "\r\n========================\r\n=  Mapper 024          =\r\n=       + 026          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nVRC6\r\n\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nAkumajou Densetsu        (024)\r\nMadara                   (026)\r\nEsper Dream 2            (026)\r\n\r\n\r\n\r\nMultiple numbers, just one mapper:\r\n--------------------------\r\nAs is the VRC way...  VRC6 comes in two varieties.  Both variants operate exactly the same, only the reigster\r\naddresses are different:\r\n\r\n   variant   lines     registers                       Mapper Number\r\n   =================================================================\r\n   VRC6a:    A0, A1    $x000, $x001, $x002, $x003      024\r\n   VRC6b:    A1, A0    $x000, $x002, $x001, $x003      026\r\n\r\n\r\n\r\nThis doc will use the 'VRC6a' registers (0,1,2,3) in all following register descriptions.  For 'VRC6b',\r\nsimply reverse $x001 and $x002.\r\n\r\n\r\nRegisters:\r\n--------------------------\r\nSome registers are mirrored across several addresses.  For example, writing to $8003 has the same effect as\r\nwriting to $8000.\r\n\r\n$8000-$8003:  [PPPP PPPP]   PRG Reg 0  (Select 16k @ $8000)\r\n$9000-$9002:                Sound, Pulse 1  (see sound section)\r\n$A000-$A002:                Sound, Pulse 2\r\n$B000-$B002:                Sound, Sawtooth\r\n$B003:        [.... MM..]   Mirroring:\r\n   %00 = Vert\r\n   %01 = Horz\r\n   %10 = 1ScA\r\n   %11 = 1ScB\r\n\r\n$C000-$C003:  [PPPP PPPP]   PRG Reg 1  (Select 8k @ $C000)\r\n$D000-$E003:  [CCCC CCCC]   CHR regs (See CHR setup)\r\n$F000-$F002:                IRQ regs (See IRQ section)\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+-------+-------+\r\n    |     $8000     | $C000 | { -1} |\r\n    +---------------+-------+-------+\r\n\r\n\r\nCHR Setup:\r\n--------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n    | $D000 | $D001 | $D002 | $D003 | $E000 | $E001 | $E002 | $E003 |\r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n\r\n\r\nIRQs:\r\n--------------------------\r\n\r\nVRC6 use the \"VRC IRQ\" setup shared by several VRCs.  It uses the following registers:\r\n\r\n\r\n  $F000:   [IIII IIII]   IRQ Reload\r\n  $F001:   [.... .MEA]   IRQ Control\r\n  $F002:   [.... ....]   IRQ Acknowledge\r\n\r\nFor info on how these IRQs work, see the \"VRC IRQs\" section in mapper 021\r\n\r\n\r\n==========================\r\nSound:\r\n--------------------------\r\n\r\nVRC6 has two additional pulse channels, and one sawtooth channel.  Both operate very similarly to the NES's\r\nnative channels.\r\n\r\n\r\n\r\n  Pulse Channels:\r\n  ------------------------\r\n\r\n$9000, $A000:  [MDDD VVVV]\r\n      M = Mode (0=normal mode, 1=digitized mode)\r\n      D = Duty cycle (duty cycle is (D+1)/16)\r\n      V = Volume\r\n\r\n$9001, $A001:  [FFFF FFFF]\r\n      F = Low 8 bits of Frequency\r\n\r\n$9002, $A002:  [E... FFFF]\r\n      F = High 4 bits of Frequency\r\n      E = Channel Enable (0=disabled, 1=enabled)\r\n\r\n\r\nPulse 1 uses regs $900x\r\nPulse 2 uses regs $A00x\r\n\r\n\r\n  Just like the NES's own pulse channels, an internal counter is counted down each CPU cycle, and when it\r\nwraps, it's reloaded with the 'F' frequency value, and the duty cycle unit takes another step.  VRC6's pulses\r\ncan have a duty cycle anywhere between 1/16 and 8/16 depending on the given 'D' value.\r\n\r\n  Channel output is either 0 or 'V', depending on the current state of the duty cycle unit (or digitized\r\nmode).\r\n\r\n  When 'M' is set (digitized mode), the duty cycle is ignored, and 'V' is always output.  In this mode, the\r\nchannel essentially is no longer a Pulse wave, but rather $9000/$A000 acts like a 4-bit PCM streaming\r\nregister (similar to $4011).\r\n\r\n  When 'E' is clear (channel disabled), output of the channel is forced to '0' (silencing the channel).\r\n\r\nGenerated tone in Hz can be calculated by the following:\r\n\r\n       CPU_CLOCK\r\nHz = -------------\r\n      (F+1) * 16\r\n\r\n\r\n  Sawtooth Channel:\r\n  ------------------------\r\n\r\n$B000:  [..AA AAAA]\r\n       A = Accum Rate\r\n\r\n$B001:  [FFFF FFFF]\r\n       F = Low 8 bits of frequency\r\n\r\n$B002:  [E... FFFF]\r\n       F = High 4 bits of frequency\r\n       E = Channel Enable (0=disabled, 1=enabled)\r\n\r\n\r\n  The sawtooth uses an 8-bit accumulation register.  Every time it is clocked, 'A' is added until the 7th\r\nclock, at which point it is reset to 0.  The high 5 bits of this accumulation reg are then used as the\r\nchannel output.  Strangely, though, the accumulation register seems to only be clocked once for every *two*\r\ntimes the frequency divider expires.  This results in a tone that's an octave lower than you might expect.\r\n\r\n  It's difficult to put in words, so here's an example using $0B as a value for the accum rate ('A'):\r\n\r\nStep    Accum.   Channel output\r\n-------------------------------\r\n 0       $00       $00\r\n 1       $00       $00     odd steps do nothing\r\n 2       $0B       $01     even steps.. add value of 'A' to accum\r\n 3       $0B       $01\r\n 4       $16       $02\r\n 5       $16       $02\r\n 6       $21       $04\r\n 7       $21       $04\r\n 8       $2C       $05\r\n 9       $2C       $05\r\n10       $37       $06\r\n11       $37       $06\r\n12       $42       $08     6th and final time 'A' is added\r\n13       $42       $08\r\n 0       $00       $00     7th time, accum is reset instead\r\n                         ... and the process repeats\r\n\r\n  Channel output is the high 5 bits of the accumulation reg (right shift reg by 3).  If the accum rate is too\r\nhigh, the accum reg WILL wrap at 8 bits, causing ugly distortion.  The highest accum rate you can use without\r\nwrapping is $2A.\r\n\r\n  If 'E' is clear (channel disabled), channel output is forced to 0 (silencing the channel).\r\n\r\nGenerated tone in Hz can be calculated by the following:\r\n\r\n       CPU_CLOCK\r\nHz = -------------\r\n      (F+1) * 14"
  },
  {
    "path": "docs/mapper/025.txt",
    "content": "\r\n========================\r\n=  Mapper 025          =\r\n========================\r\n\r\n\r\nMapper 025 is a variant of VRC4, which is covered in another doc.\r\n\r\nFor info, see mapper 021.\r\n"
  },
  {
    "path": "docs/mapper/026.txt",
    "content": "\r\n========================\r\n=  Mapper 026          =\r\n========================\r\n\r\n\r\nMapper 026 is a variant of VRC6, which is covered in another doc.\r\n\r\nFor info, see mapper 024.\r\n"
  },
  {
    "path": "docs/mapper/032.txt",
    "content": "\r\n ========================\r\n =  Mapper 032          =\r\n ========================\r\n \r\n Example Games:\r\n --------------------------\r\n Image Fight\r\n Major League\r\n Kaiketsu Yanchamaru 2\r\n   \r\n Registers:\r\n --------------------------\r\n \r\n Range,Mask:   $8000-BFFF, $F007\r\n \r\n   $8000-$8007:  [...P PPPP]    PRG Reg 0\r\n   $9000-$9007:  [.... ..PM]  ** see footnote\r\n       P = PRG Mode\r\n       M = Mirroring (0=Vert, 1=Horz)\r\n   $A000-$A007:  [...P PPPP]    PRG Reg 1\r\n   $B000-$B007:  [CCCC CCCC]    CHR Regs\r\n \r\n \r\n PRG Setup:\r\n ---------------------------\r\n \r\n                $8000   $A000   $C000   $E000  \r\n              +-------+-------+-------+-------+\r\n PRG Mode 0:  | $8000 | $A000 | { -2} | { -1} |\r\n              +-------+-------+-------+-------+\r\n PRG Mode 1:  | { -2} | $A000 | $8000 | { -1} |\r\n              +-------+-------+-------+-------+\r\n \r\n \r\n CHR Setup:\r\n ---------------------------\r\n \r\n       $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n     +-------+-------+-------+-------+-------+-------+-------+-------+\r\n     | $B000 | $B001 | $B002 | $B003 | $B004 | $B005 | $B006 | $B007 |\r\n     +-------+-------+-------+-------+-------+-------+-------+-------+\r\n\r\n Footnote:\r\n --------------------------\r\n Major League wants hardwired 1-screen mirroring. (CIRAM A10 is tied to +5V\r\n on this game). Additionally, the register at $9000 is entirely disabled: \r\n the game can only request \"PRG mode 0\".\r\n\r\n A NES 2.0 submapper has been assigned for this difference.\r\n Otherwise you'll have to use a hash check."
  },
  {
    "path": "docs/mapper/033.txt",
    "content": "\r\n========================\r\n=  Mapper 033          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nAkira\r\nBakushou!! Jinsei Gekijou\r\nDon Doko Don\r\nInsector X\r\n\r\n\r\nNote:\r\n--------------------------\r\nMost dumps of mapper 048 games floating around are erroneously labelled as mapper 033.  Mapper 033 does not\r\nhave IRQs, mapper 048 does, and mirroring on each is handled a bit differently.  Apart from that, the two\r\nare very similar.\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\nRange,Mask:   $8000-BFFF, $A003\r\n\r\n  $8000 [.MPP PPPP]\r\n    M = Mirroring (0=Vert, 1=Horz)\r\n    P = PRG Reg 0 (8k @ $8000)\r\n\r\n  $8001 [..PP PPPP]   PRG Reg 1 (8k @ $A000)\r\n  $8002 [CCCC CCCC]   CHR Reg 0 (2k @ $0000)\r\n  $8003 [CCCC CCCC]   CHR Reg 1 (2k @ $0800)\r\n  $A000 [CCCC CCCC]   CHR Reg 2 (1k @ $1000)\r\n  $A001 [CCCC CCCC]   CHR Reg 3 (1k @ $1400)\r\n  $A002 [CCCC CCCC]   CHR Reg 4 (1k @ $1800)\r\n  $A003 [CCCC CCCC]   CHR Reg 5 (1k @ $1C00)\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    | $8000 | $8001 | { -2} | { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +---------------+---------------+-------+-------+-------+-------+\r\n    |     $8002     |     $8003     | $A000 | $A001 | $A002 | $A003 |\r\n    +---------------+---------------+-------+-------+-------+-------+"
  },
  {
    "path": "docs/mapper/034.txt",
    "content": "\r\n========================\r\n=  Mapper 034          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nBxROM\r\nNINA-001\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nDarkseed               (BxROM)\r\nMashou                 (BxROM)\r\nImpossible Mission 2   (NINA-001)\r\n\r\n\r\nNotes:\r\n--------------------------\r\nHow these two seperate and completely imcompatible mappers got assigned the same mapper number is a mystery.\r\nBxROM and NINA-001 are both assigned mapper 034, however they both work totally differently.  There is no\r\nreliable way to tell the difference between the two apart from a CRC or Hash check.\r\n\r\n\r\n=================================\r\nBxROM\r\n=================================\r\n\r\nBxROM has bus conflicts... however this mapper also covers some BxROM compatible boards that do\r\nnot suffer from bus conflicts.\r\n\r\n\r\nRegisters (**BUS CONFLICTS** sometimes):\r\n--------------------------\r\n  $8000-FFFF:  Select 32k PRG @ $8000\r\n\r\nNote on a real BxROM, only the low 2 bits are used (PRG capped at 128k).  But since this is BxROM and\r\ncompatible, emus should use all 8 bits\r\n\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n       $8000   $A000   $C000   $E000  \r\n     +-------------------------------+\r\n     |             $8000             |\r\n     +-------------------------------+\r\n\r\n\r\n\r\n=================================\r\nNINA-001\r\n=================================\r\n\r\nRegisters:\r\n--------------------------\r\n  $7FFD:   Select 32k PRG @ $8000\r\n  $7FFE:   Select 4k CHR @ $0000\r\n  $7FFF:   Select 4k CHR @ $1000\r\n\r\nI'm not sure whether or not WRAM can also exist at $6000-7FFF\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------------------------------+\r\n    |             $7FFD             |\r\n    +-------------------------------+\r\n\r\nCHR Setup:\r\n--------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------------------------------+-------------------------------+\r\n    |             $7FFE             |             $7FFF             |\r\n    +-------------------------------+-------------------------------+"
  },
  {
    "path": "docs/mapper/044.txt",
    "content": "\r\n========================\r\n=  Mapper 044          =\r\n========================\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nSuper Big 7-in-1\r\n\r\n\r\n\r\nNotes:\r\n---------------------------\r\n\r\nThis mapper is an MMC3 based multicart.  The multicart selects a block of PRG and CHR depending on the\r\nselected game, and the MMC3 regs act as they normally would within the given block.  For info on MMC3, see\r\nmapper 004.\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $E001\r\n\r\nAll registers behave exactly like a normal MMC3, except for:\r\n\r\n  $A001:  [EW.. .BBB]\r\n    E,W = Same as on typical MMC3\r\n    B = Block select\r\n\r\n\r\nBlocks:\r\n---------------------------\r\n\r\nSelecting block 7 is the same as selecting block 6.\r\n\r\nAll blocks have 128k PRG and CHR... except for block 6 which has 256k PRG and CHR.  All MMC3 selected pages\r\nare chosen from the given block (including fixed pages).  This can be accomplished by ANDing the MMC3 regs\r\nwith a given value, and ORing them with a value based on the current block:\r\n\r\n  Block   PRG-AND   PRG-OR    CHR-AND   CHR-OR\r\n  ---------------------------------------------\r\n    0       $0F      $00        $7F      $000\r\n    1       $0F      $10        $7F      $080\r\n    2       $0F      $20        $7F      $100\r\n    3       $0F      $30        $7F      $180\r\n    4       $0F      $40        $7F      $200\r\n    5       $0F      $50        $7F      $280\r\n   6,7      $1F      $60        $FF      $300\r\n\r\n\r\n\r\nPowerup:\r\n---------------------------\r\n\r\nBlock 0 must be selected at powerup (and possibly reset?)"
  },
  {
    "path": "docs/mapper/045.txt",
    "content": "\r\n========================\r\n=  Mapper 045          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nSuper 8-in-1\r\nSuper 4-in-1\r\nSuper 1000000-in-1\r\n\r\n\r\nNotes:\r\n---------------------------\r\n\r\nThis mapper is another MMC3 multicart, only it works a bit strangely.  The multicart selects PRG/CHR blocks\r\nindependently through 4 internal registers (accessed via $6000-7FFF).  MMC3 registers then operate normally\r\nwithin the current block.\r\n\r\nFor info on MMC3, see mapper 004.\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $6000-7FFF:  Multicart regs\r\n  $8000-FFFF:  Same as MMC3 for selected blocks\r\n\r\nWhen Multicart regs are locked, writes to $6000-7FFF proceed to PRG-RAM, as normal.\r\n\r\nWhere the game writes in the $6000-7FFF range doesn't matter.  An internal counter selects which reg gets\r\nwritten to.  ie:\r\n\r\n  LDA #$00\r\n  STA $6000 ; first write, goes to reg 0\r\n  STA $6000 ; second write, goes to reg 1\r\n  STA $6000 ; reg 2\r\n  STA $6000 ; reg 3\r\n  STA $6000 ; back to reg 0, etc\r\n\r\n\r\n  Reg 0:  [CCCC CCCC]   Low 8 bits of CHR-OR\r\n  Reg 1:  [PPPP PPPP]   PRG-OR\r\n  Reg 2:  [CCCC SSSS]\r\n          S = CHR-AND block size\r\n          C = High 4 bits of CHR-OR\r\n  Reg 3:  [.LAA AAAA]\r\n          L = Lock Multicart regs (1=locked)\r\n          A = Inverted PRG-AND\r\n\r\nOnce multicart regs are locked, the only way to unlock is to Reset the system.\r\n\r\n\r\nCHR Setup:\r\n--------------------------\r\n\r\n'S' bits are somewhat strange.  They seem to select the size of the CHR block to mask out:\r\n  'S'   Block size     CHR-AND\r\n  ----------------------------\r\n  $F       256k          $FF\r\n  $E       128k          $7F\r\n  $D        64k          $3F\r\n       ...\r\n  $8         2k          $01\r\n  7-0        1k          $00\r\n\r\n\r\nAn easy way to emulate this:\r\n\r\n   chr_and = 0xFF >> ~S_bits;\r\n\r\nCHR-OR is straightforward\r\n\r\n\r\n\r\nPRG Setup:\r\n-------------------------\r\n\r\nPRG-OR is straightforward.\r\n\r\nPRG-AND is inverted.  XOR written value with $3F for actual PRG-AND.\r\n\r\n\r\n\r\nOdd game behavior:\r\n-------------------------\r\n\r\nGames seem to set the multicart registers in a loop that runs 256 times.  Why it does this isn't known,\r\nneither is whether or not it is actually necessary.\r\n\r\n\r\n\r\nPowerup and reset:\r\n-------------------------\r\n\r\nBlock 0 must be selected on powerup and reset.  Regs must be unlocked, as well... and they must be reset so\r\nthat the next write will write to reg 0.\r\n"
  },
  {
    "path": "docs/mapper/046.txt",
    "content": "\r\n========================\r\n=  Mapper 046          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nRumblestation 15-in-1\r\n\r\n\r\nBus Conflicts?:\r\n---------------------------\r\nNo idea whether or not this mapper suffers from bus conflicts.  Use caution!\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRegs at $6000-7FFF means no PRG-RAM.\r\n\r\n\r\n  $6000-7FFF:  [CCCC PPPP]   High CHR, PRG bits\r\n  $8000-FFFF:  [.CCC ...P]   Low CHR, PRG bits\r\n\r\n'C' selects 8k CHR @ $0000\r\n'P' select 32k PRG @ $8000\r\n\r\n\r\nPowerup:\r\n---------------------------\r\n$6000 set to 0 on powerup.\r\n"
  },
  {
    "path": "docs/mapper/047.txt",
    "content": "\r\n========================\r\n=  Mapper 047          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nSuper Spike V'Ball + Nintendo World Cup\r\n\r\n\r\nNotes:\r\n---------------------------\r\n\r\nYet another MMC3 multicart.  See mapper 004 for info on MMC3.\r\n\r\nThere is no PRG-RAM.  The multicart reg lies at $6000-7FFF, but is only writable when MMC3 PRG-RAM is enabled\r\nand writable (see $A001)\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $6000-7FFF:  [.... ...B]  Block select\r\n  $8000-FFFF:  Same as MMC3 for selected block\r\n\r\n\r\nEach block has 128k PRG and 128k CHR."
  },
  {
    "path": "docs/mapper/048.txt",
    "content": "\r\n========================\r\n=  Mapper 048          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nBubble Bobble 2 (J)\r\nDon Doko Don 2\r\nCaptain Saver\r\nFlintstones, The (J)\r\n\r\n\r\nNotes:\r\n--------------------------\r\nMost dumps of mapper 048 games floating around are erroneously labelled as mapper 033.  Mapper 033 does not\r\nhave IRQs, mapper 048 does, and mirroring on each is handled a bit differently.  Apart from that, the two are\r\nvery similar.\r\n\r\nThis mapper is very similar to MMC3 in a lot of ways, including how the IRQ counter operates.\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $E003\r\n\r\n  $8000:    PRG Reg 0 (8k @ $8000)\r\n  $8001:    PRG Reg 1 (8k @ $A000)\r\n\r\n  $8002:    CHR Reg 0 (2k @ $0000)\r\n  $8003:    CHR Reg 1 (2k @ $0800)\r\n  $A000:    CHR Reg 2 (1k @ $1000)\r\n  $A001:    CHR Reg 3 (1k @ $1400)\r\n  $A002:    CHR Reg 4 (1k @ $1800)\r\n  $A003:    CHR Reg 5 (1k @ $1C00)\r\n\r\n  $C000:    IRQ Reload\r\n  $C001:    IRQ Clear\r\n  $C002:    IRQ Enable\r\n  $C003:    IRQ Acknowledge\r\n\r\n  $E000: [.M.. ....]   Mirroring\r\n    0 = Vert\r\n    1 = Horz\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    | $8000 | $8001 | { -2} | { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +---------------+---------------+-------+-------+-------+-------+\r\n    |     $8002     |     $8003     | $A000 | $A001 | $A002 | $A003 |\r\n    +---------------+---------------+-------+-------+-------+-------+\r\n\r\n\r\n\r\nIRQs:\r\n---------------------------\r\n\r\nIRQs on this mapper seem to behave exactly like MMC3's IRQs, with 2 exceptions:\r\n\r\n 1)  The written reload value is inverted.  EG:  Writing $06 to the reload reg on this mapper would be like\r\nwriting $F9 on MMC3.  You can simply XOR the writes with $FF and it will work just like MMC3.\r\n\r\n 2)  The IRQ seems to trip a little later than it does on MMC3.  It looks like about a 4 CPU cycle delay from\r\nthe normal MMC3 IRQ time.  Failure to put in this delay results in shaking and other graphical quirks in some\r\ngames.\r\n\r\n\r\nThe registers on this mapper corespond directly to regs on the MMC3:\r\n\r\n  048  -  MMC3\r\n---------------\r\n $C000   $C000   (XOR written value with $FF)\r\n $C001   $C001\r\n $C002   $E001\r\n $C003   $E000\r\n\r\n\r\nFor details on MMC3 IRQ operation, see mapper 004."
  },
  {
    "path": "docs/mapper/049.txt",
    "content": "\r\n========================\r\n=  Mapper 049          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nSuper HIK 4-in-1\r\n\r\n\r\nNotes:\r\n---------------------------\r\n\r\nYet another MMC3 multicart.  For info on MMC3, see mapper 004.\r\n\r\nThere is no PRG-RAM.  The multicart reg lies at $6000-7FFF, but is only writable when MMC3 PRG-RAM is enabled\r\nand writable (see $A001)\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $6000-7FFF:  [BBPP ...O]  Multicart reg\r\n       B = Block\r\n       P = 32k PRG Reg\r\n       O = PRG Mode (0=32k mode)\r\n\r\n  $8000-FFFF:  Same as MMC3 for selected block\r\n\r\n\r\nEach block is 128k PRG and 128k CHR\r\n\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n  When the 'O' mode bit is clear, ordinary MMC3 PRG regs are ignored, and instead, 32k PRG page 'P' is swapped\r\nin at $8000.  When 'O' is set, 'P' is ignored, and MMC3 PRG regs work normally for the current block.\r\n\r\n\r\nPowerup:\r\n---------------------------\r\n\r\n$6000 set to 0 on powerup."
  },
  {
    "path": "docs/mapper/050.txt",
    "content": "\r\n========================\r\n=  Mapper 050          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nSuper Mario Bros. (JU) (Alt Levels)   (SMB2j pirate cart)\r\n\r\n\r\nNotes:\r\n---------------------------\r\nNo PRG-RAM.  PRG setup is bizarre, as is the scrambled PRG reg.\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $4020-5FFF, $4120\r\n\r\n\r\n  $4020:  [.... HLLM]\r\n    L,M,H = Low, middle, and high bits of PRG Reg\r\n\r\n\r\n  $4120:  [.... ...E]  IRQ Enable (0=Disabled, 1=Enabled)\r\n\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $6000   $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+-------+\r\n    | {$0F} | { 8 } | { 9 } | $4020 | {$0B} |\r\n    +-------+-------+-------+-------+-------+\r\n\r\n\r\nIRQs:\r\n---------------------------\r\n\r\n  Writing to $4120 with E=0 will disable IRQs, acknowledge the pending IRQ, and reset the IRQ counter to 0.\r\nWriting with E=1 will just enable IRQs (but will not change anything else).\r\n\r\n  When enabled, The IRQ counter will count up every CPU cycle.  When it makes the transition from\r\n$0FFF->$1000, and IRQ is generated.  The counter appears to be a full 16-bits (so it will not wrap until\r\n$FFFF)\r\n\r\n"
  },
  {
    "path": "docs/mapper/052.txt",
    "content": "\r\n========================\r\n=  Mapper 052          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nMario 7-in-1\r\n\r\n\r\nNotes:\r\n---------------------------\r\n\r\nYet another MMC3 multicart.  For info on MMC3, see mapper 004.\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $6000-7FFF:  [.MHL SBPP]  Multicart reg\r\n       P = PRG Block (bits 0,1)\r\n       B = CHR+PRG Block Select bit (PRG bit 2, CHR bit 1)\r\n       S = PRG Block size (0=512k   1=256k)\r\n       L = CHR Block low bit (bit 0)\r\n       H = CHR Block high bit (bit 2)\r\n       M = CHR Block size (0=256k   1=128k)\r\n\r\n  $8000-FFFF:  Same as MMC3 for selected block\r\n\r\n\r\n$6000 can only be written to once ... and only if PRG-RAM is enabled and writable (see $A001).  Once $6000\r\nhas been written to, $6000-7FFF maps to PRG-RAM\r\n\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n   'S'    PRG-AND    PRG-OR\r\n   ------------------------\r\n    0       $1F    %BP0 0000\r\n    1       $0F    %BPP 0000\r\n\r\n 'B' and 'P' bits make a 3-bit value used as PRG-OR (left shift 4).  When 'S' is clear, the low bit of that\r\nvalue is forced to 0.\r\n\r\nPRG swapping behaves just like a normal MMC3 within this selected block\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n   'M'    CHR-AND    CHR-OR\r\n   ------------------------\r\n    0       $FF    %HB 0000 0000\r\n    1       $7F    %HB L000 0000\r\n\r\n 'H', 'B' and 'L' bits make a 3-bit value used as CHR-OR (left shift 7).  When 'M' is clear, the low bit of\r\nthat value is forced to 0.\r\n\r\nCHR swapping behaves just like a normal MMC3 within this selected block\r\n\r\n\r\nPowerup and Reset:\r\n---------------------------\r\n\r\n$6000 set to 0 on reset and powerup.\r\n"
  },
  {
    "path": "docs/mapper/057.txt",
    "content": "\r\n========================\r\n=  Mapper 057          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nGK 47-in-1\r\n6-in-1 (SuperGK)\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $8800\r\n\r\n  $8000:  [.H.. .AAA]\r\n     H = High bit of CHR reg (bit 4)\r\n     A = Low 3 bits of CHR Reg (OR with 'B' bits)\r\n\r\n  $8800:  [PPPO MBBB]\r\n     P = PRG Reg\r\n     O = PRG Mode\r\n     M = Mirroring (0=Vert, 1=Horz)\r\n     B = Low 3 bits of CHR Reg (OR with 'A' bits)\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n'A' and 'B' bits combine with an OR to get the low 3 bits of the desired page, and the 'H' bit is the high\r\nbit.  This 4-bit value selects an 8k page @ $0000\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n               $8000   $A000   $C000   $E000  \r\n             +---------------+---------------+\r\nPRG Mode 0:  |     $8800     |     $8800     |\r\n             +-------------------------------+\r\nPRG Mode 1:  |            <$8800>            |\r\n             +-------------------------------+\r\n"
  },
  {
    "path": "docs/mapper/058.txt",
    "content": "\r\n========================\r\n=  Mapper 058          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\n68-in-1 (Game Star)\r\nStudy and Game 32-in-1\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $8000-FFFF:  A~[.... .... MOCC CPPP]\r\n    P = PRG page select\r\n    C = CHR page select (8k @ $0000)\r\n    O = PRG Mode\r\n    M = Mirroring (0=Vert, 1=Horz)\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n               $8000   $A000   $C000   $E000  \r\n             +-------------------------------+\r\nPRG Mode 0:  |            <$8000>            |\r\n             +-------------------------------+\r\nPRG Mode 1:  |     $8000     |     $8000     |\r\n             +---------------+---------------+\r\n"
  },
  {
    "path": "docs/mapper/060.txt",
    "content": "\r\n========================\r\n=  Mapper 060          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nReset Based 4-in-1\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThis mapper is very, very unique.\r\n\r\nIt's a multicart that consists of four NROM games, each with 16k PRG (put at $8000 and $C000) and 8k CHR.\r\nThe current block that is selected is determined by an internal register that can only be incremented by a\r\nsoft reset!\r\n\r\nI would assume the register is 2 bits wide?  Don't know for sure.\r\n"
  },
  {
    "path": "docs/mapper/061.txt",
    "content": "\r\n========================\r\n=  Mapper 061          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\n20-in-1\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $8000-FFFF:  A~[.... .... M.LO HHHH]\r\n    H = High 4 bits of PRG Reg\r\n    L = Low bit of PRG Reg\r\n    O = PRG Mode\r\n    M = Mirroring (0=Vert, 1=Horz)\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\nPRG Reg is 5 bits -- combination of 'H' and 'L' bits.\r\n\r\n               $8000   $A000   $C000   $E000  \r\n             +-------------------------------+\r\nPRG Mode 0:  |            <$8000>            |\r\n             +-------------------------------+\r\nPRG Mode 1:  |     $8000     |     $8000     |\r\n             +---------------+---------------+\r\n"
  },
  {
    "path": "docs/mapper/062.txt",
    "content": "\r\n========================\r\n=  Mapper 062          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nSuper 700-in-1\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $8000-FFFF:  A~[..pp pppp MPOC CCCC]\r\n                 [.... ..cc]\r\n    p = Low bits of PRG Reg\r\n    P = High bit of PRG Reg\r\n    c = Low bits of CHR Reg\r\n    C = High bits of CHR Reg\r\n    O = PRG Mode\r\n    M = Mirroring (0=Vert, 1=Horz)\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n\r\n               $8000   $A000   $C000   $E000  \r\n             +-------------------------------+\r\nPRG Mode 0:  |            <$8000>            |\r\n             +-------------------------------+\r\nPRG Mode 1:  |     $8000     |     $8000     |\r\n             +---------------+---------------+\r\n\r\n\r\nCHR Setup:\r\n----------------------------\r\n  'C' and 'c' select an 8k page @ $0000"
  },
  {
    "path": "docs/mapper/064.txt",
    "content": "\r\n========================\r\n=  Mapper 064          =\r\n========================\r\n\r\n\r\naka\r\n--------------------------\r\nTengen RAMBO-1\r\n\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nKlax\r\nSkull and Crossbones\r\nShinobi\r\n\r\n\r\nNotes:\r\n--------------------------\r\nThis mapper is very similar to MMC3.  It uses a similar swapping system, but adds a little functionality.\r\n\r\nIRQs are set up similar as well... but have some major differences.\r\n\r\nThis is one of those mappers that is a big pain to impliment in an emu -- especially since so few games use\r\nit.  And the games that use it really blow hard.\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $E001\r\n\r\n  $8000:  [CPK. AAAA]\r\n    C = CHR mode select\r\n    P = PRG mode select\r\n    K = full 1k CHR mode select (see CHR setup)\r\n    A = Address for use with $8001\r\n\r\n  $8001:  [DDDD DDDD]   Data port\r\n    R:0 ->  CHR reg 0\r\n    R:1 ->  CHR reg 1\r\n    R:2 ->  CHR reg 2\r\n    R:3 ->  CHR reg 3\r\n    R:4 ->  CHR reg 4\r\n    R:5 ->  CHR reg 5\r\n    R:6 ->  PRG reg 0\r\n    R:7 ->  PRG reg 1\r\n    R:8 ->  CHR reg 6\r\n    R:9 ->  CHR reg 7\r\n       R:A - R:E not used\r\n    R:F ->  PRG reg 2\r\n\r\n  $A000:  [.... ...M]   Mirroring\r\n    0 = Vert\r\n    1 = Horz\r\n\r\n\r\n\r\n  $C000:  [IIII IIII]   IRQ Reload value\r\n  $C001:  [.... ...M]   IRQ Mode select and reset\r\n    0 = Scanline (A12) mode\r\n    1 = Cycle mode\r\n\r\n  $E000:  [.... ....]   IRQ Acknowledge/Disable\r\n  $E001:  [.... ....]   IRQ Enable\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\nPRG mode is selected via $8000.6\r\n\r\n               $8000   $A000   $C000   $E000  \r\n             +-------+-------+-------+-------+\r\nPRG Mode 0:  |  R:6  |  R:7  |  R:F  | { -1} |\r\n             +-------+-------+-------+-------+\r\nPRG Mode 1:  |  R:F  |  R:6  |  R:7  | { -1} |\r\n             +-------+-------+-------+-------+\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n$8000 has 2 bits to configure CHR modes.  Therefore there are effectively 4 CHR modes.\r\n\r\n  $8000:  [CPK. AAAA]   <---  C and K bits relevent to CHR\r\n\r\n\r\n              $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n            +---------------+---------------+-------+-------+-------+-------+\r\nC=0, K=0    |     <R:0>     |     <R:1>     |  R:2  |  R:3  |  R:4  |  R:5  |\r\n            +---------------+---------------+-------+-------+-------+-------+\r\nC=0, K=1    |  R:0  |  R:8  |  R:1  |  R:9  |  R:2  |  R:3  |  R:4  |  R:5  |\r\n            +-------+-------+-------+-------+---------------+---------------+\r\nC=1, K=0    |  R:2  |  R:3  |  R:4  |  R:5  |     <R:0>     |     <R:1>     |\r\n            +-------+-------+-------+-------+---------------+---------------+\r\nC=1, K=1    |  R:2  |  R:3  |  R:4  |  R:5  |  R:0  |  R:8  |  R:1  |  R:9  |\r\n            +-------+-------+-------+-------+-------+-------+-------+-------+\r\n\r\n\r\n\r\nIRQs:\r\n---------------------------\r\n\r\nThere are two seperate IRQ modes.  One uses A12 to count scanlines in a manner just like MMC3 does (see\r\nmapper 004 for details on how scanlines are counted and the restrictions involved).  The other mode uses CPU\r\ncycles with a 4-step prescaler (so the IRQ counter gets clocked every 4 CPU cycles).\r\n\r\nRegardless of the mode used to clock the counter... every time the counter is clocked, the following actions\r\noccur:\r\n\r\n- If Reset reg ($C001) was written to after previous clock...\r\n     a)  reload IRQ counter with IRQ Reload value **PLUS ONE**\r\n\r\n- Otherwise... If IRQ Counter is 0...\r\n     a)  reload IRQ counter with IRQ Reload value\r\n\r\n- Otherwise...\r\n     a)  Decrement IRQ counter by 1\r\n     b)  If IRQ counter is now 0 and IRQs are enabled, trigger IRQ\r\n\r\n\r\nJust like with MMC3, the counter is clocked and updated even when IRQs are disabled -- however IRQs will only\r\nbe triggered when enabled.\r\n\r\nNote about the plus one:  I'm not sure if 1 is really added or if there's simply an additional 1 clock delay\r\nbefore the IRQ counter is updated.  From a software standpoint, it doesn't really matter -- adding the\r\nadditional 1 works without any side-effects.\r\n\r\n\r\nRegisters involved with IRQs:\r\n---------------------------\r\n\r\n  $C000:   [IIII IIII] - IRQ Reload value\r\n\r\n  $C001:   [.... ...M] - IRQ Reset reg, mode select\r\n    0 = Scanline mode (A12)\r\n    1 = CPU Cycle mode (with prescaler)\r\n\r\n    Any write to this register will make it so that the IRQ counter will reload with the reload value +1 on\r\nits next clock.  Whether or not writing to this register clears the IRQ counter like it does with MMC3 isn't\r\nknown... and doesn't matter, since it's reloaded later anyway.\r\n\r\n    Also, any write to this register will reset the CPU cycle prescaler (so that it will be 4 CPU cycles\r\nuntil the next clock).\r\n\r\n  $E000:   [.... ....] - IRQ Acknowledge/Disable\r\n    Any write to this register will acknowledge the pending IRQ, and disable IRQs\r\n\r\n  $E001:   [.... ....] - IRQ Enable\r\n    Any write to this register will enable IRQs\r\n\r\n\r\n\r\nA note about IRQs:\r\n------------------\r\n\r\n  Scanline IRQs seem to trip a little later than they do on the MMC3.  It looks like about a 5 dot delay\r\nfrom the normal MMC3 IRQ time (265 instead of 260).  Failure to put in this delay results in shaking and\r\nother graphical quirks in some games... notably Klax.  This delay also seems to exist for CPU cycle driven\r\nIRQs (Skull & Crossbones will suffer without it).  Perhaps the RAMBO-1's IRQ generating hardware is a little\r\nslower than usual?\r\n\r\n  Apart from that timing difference, A12 clocks RAMBO-1's IRQ counter just exactly like it does MMC3, so all\r\nthe notes about A12, $2006/7, etc from the mapper 004 documenation apply to this mapper as well."
  },
  {
    "path": "docs/mapper/065.txt",
    "content": "\r\n========================\r\n=  Mapper 065          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nDaiku no Gen San 2\r\nKaiketsu Yanchamaru 3\r\nSpartan X 2\r\n\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\n  $8000:  PRG Reg 0 (8k @ $8000)\r\n  $A000:  PRG Reg 1 (8k @ $A000)\r\n  $C000:  PRG Reg 2 (8k @ $C000)\r\n\r\n  $B000-$B007:  CHR regs\r\n\r\n  $9001:  [M... ....]  Mirroring\r\n    0 = Vert\r\n    1 = Horz\r\n\r\n  $9003:  [E... ....]  IRQ Enable (0=disabled, 1=enabled)\r\n  $9004:  [.... ....]  Reload IRQ counter\r\n  $9005:  [IIII IIII]  High 8 bits of IRQ Reload value\r\n  $9006:  [IIII IIII]  Low 8 bits of IRQ Reload value\r\n\r\n\r\nOn Powerup:\r\n---------------------------\r\nOn powerup, it appears as though PRG regs are inited to specific values:\r\n\r\n  $8000 = $00\r\n  $A000 = $01\r\n  $C000 = $FE\r\n\r\nGames do rely on this and will crash otherwise.\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    | $8000 | $A000 | $C000 | { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n    | $B000 | $B001 | $B002 | $B003 | $B004 | $B005 | $B006 | $B007 |\r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n\r\n\r\n\r\nIRQs:\r\n---------------------------\r\n\r\nThis mapper's IRQ system is very simple.  There's a 16-bit internal down counter which (when enabled),\r\ndecrements by 1 every CPU cycle.  When the counter reaches 0, an IRQ is fired.  The counter stops at 0 -- it\r\ndoes not wrap and isn't automatically reloaded.\r\n\r\nAny write to $9003 or $9004 will acknowledge the pending IRQ.\r\n\r\nAny write to $9004 will copy the 16-bit reload value into the counter.\r\n\r\n$9006 and $9005 set the reload value, but do not have any effect on the actual counter.  Note that $9005 is\r\nthe HIGH bits, not the low bits."
  },
  {
    "path": "docs/mapper/066.txt",
    "content": "\r\n========================\r\n=  Mapper 066          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nGxROM and compatible\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nDoraemon\r\nDragon Power\r\nGumshoe\r\nThunder & Lightning\r\nSuper Mario Bros. + Duck Hunt\r\n\r\n\r\nNotes:\r\n---------------------------\r\nI do not know whether or not this mapper suffers from bus conflicts.  Use caution!\r\n\r\nThis mapper is INFAMOUS for having bad headers.  Probably 80% or more of these ROMs floating around out\r\nthere have the wrong mirroring mode set in the header.\r\n\r\nSome games are marked as mapper 066 that are really mapper 140.  See mapper 140 for info.\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n  $8000-FFFF:  [..PP ..CC]\r\n    P = Selects 32k PRG @ $8000\r\n    C = Selects 8k CHR @ $0000"
  },
  {
    "path": "docs/mapper/067.txt",
    "content": "\r\n========================\r\n=  Mapper 067          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nFantasy Zone 2 (J)\r\nMito Koumon - Sekai Manyuu Ki\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $F800\r\n\r\n  $8800:  CHR Reg 0  (2k @ $0000)\r\n  $9800:  CHR Reg 1  (2k @ $0800)\r\n  $A800:  CHR Reg 2  (2k @ $1000)\r\n  $B800:  CHR Reg 3  (2k @ $1800)\r\n\r\n  $C800:  IRQ Load (write twice)\r\n  $D800:  [...E ....]  IRQ Enable (0=disabled, 1=enabled)\r\n\r\n  $E800:  [.... ..MM]  Mirroring\r\n    %00 = Vert\r\n    %01 = Horz\r\n    %10 = 1ScA\r\n    %11 = 1ScB\r\n\r\n  $F800:  PRG Reg  (16k @ $8000)\r\n\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +---------------+---------------+---------------+---------------+\r\n    |     $8800     |     $9800     |     $A800     |     $B800     |\r\n    +---------------+---------------+---------------+---------------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $F800     |     { -1}     |\r\n    +---------------+---------------+\r\n\r\n\r\n\r\nIRQ Operation:\r\n---------------------------\r\n\r\n  $C800 is a write-twice register (similar to $2005 and $2006).  The first write sets the *high* 8 bits of the\r\nIRQ counter, and the second write sets the *low* 8 bits.  This directly changes the actual IRQ counter -- not\r\na reload value.\r\n\r\n  Any write to $D800 will acknowledge the IRQ, and will also reset the toggle so that the next write to\r\n$C800 will be the first write.  $D800, of course, also enables/disables IRQs (bit 4).\r\n\r\n  The IRQ counter, when enabled, counts down every CPU cycle.  When it wraps ($0000->FFFF), it disables\r\nitself and triggers an IRQ."
  },
  {
    "path": "docs/mapper/068.txt",
    "content": "\r\n========================\r\n=  Mapper 068          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nAfter Burner 2\r\nMaharaja\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $F000\r\n\r\n  $8000:  CHR Reg 0  (2k @ $0000)\r\n  $9000:  CHR Reg 1  (2k @ $0800)\r\n  $A000:  CHR Reg 2  (2k @ $1000)\r\n  $B000:  CHR Reg 3  (2k @ $1800)\r\n\r\n  $C000:  [.NNN NNNN]  NT-ROM Reg 0\r\n  $D000:  [.NNN NNNN]  NT-ROM Reg 1\r\n  $E000:  [...R ...M]  Mirroring (see section below)\r\n\r\n  $F000:  PRG Reg (16k @ $8000)\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +---------------+---------------+---------------+---------------+\r\n    |     $8000     |     $9000     |     $A000     |     $B000     |\r\n    +---------------+---------------+---------------+---------------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $F000     |     { -1}     |\r\n    +---------------+---------------+\r\n\r\n\r\n\r\nMirroring:\r\n---------------------------\r\n\r\nThe mirroring reg has two significant bits:\r\n\r\n  $E000:  [...R ...M]\r\n\r\n  'M' selects H/V:\r\n     0 = Vert\r\n     1 = Horz\r\n\r\n  'R' selects whether or not to use CHR-ROM as nametables.\r\n     0 = normal mirroring\r\n     1 = use CHR-ROM\r\n\r\nWhen 'R' is set, $C000 and $D000 are used to select 1k CHR-ROM pages to use as nametables.  They are arranged\r\nin either Horz or Vert mirroring fashion depending on the 'M' bit ($C000 would be used in place of NTA,\r\n$D000 in place of NTB).\r\n\r\n R=1, M=0:\r\n      [ $C000 ][ $D000 ]\r\n      [ $C000 ][ $D000 ]\r\n\r\n R=1, M=1:\r\n      [ $C000 ][ $C000 ]\r\n      [ $D000 ][ $D000 ]\r\n\r\nNote that CHR-ROM for nametables is taken from the last 128k of CHR.  This means you must effectively OR the\r\nvalue written to $C000/$D000 with $80."
  },
  {
    "path": "docs/mapper/069.txt",
    "content": "\r\n========================\r\n=  Mapper 069          =\r\n========================\r\n\r\n\r\naka\r\n--------------------------\r\nFME-7\r\nSunsoft 5B\r\n\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nGimmick!\r\nBatman:  Return of the Joker\r\nHebereke\r\nGremlins 2 (J)\r\n\r\n\r\nNotes:\r\n--------------------------\r\nThis mapper is FME-7 and compatible.  Sunsoft 5B operates the same as FME-7, only it has additional sound\r\nhardware.  For a long time, it was thought Gimmick! uses FME-7, so the expansion sound is labeled as FME-7\r\nin various places -- however -- technically FME-7 has no extra sound.\r\n\r\nGimmick! is the only known game to use the extra sound found on Sunsoft 5B\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $E000\r\n\r\n  $8000:  [.... AAAA]   Address for use with $A000\r\n\r\n  $A000:  [DDDD DDDD]   Data port\r\n    R:0-7 ->  CHR Regs\r\n    R:8-B ->  PRG Regs\r\n    R:C   ->  Mirroring\r\n    R=D-F ->  IRQ Control\r\n\r\n  $C000:  [.... AAAA]   Address for use with $E000 (sound)\r\n\r\n  $E000:  [DDDD DDDD]   Data port (sound -- see sound section)\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\nR:8 controls $6000-7FFF.  It can map in PRG-RAM, PRG-ROM, or leave it unmapped (open bus), depending on the\r\nmode it sets:\r\n\r\nR:8:  [ERPP PPPP]\r\n   E = Enable RAM (0=disabled, 1=enabled)\r\n   R = RAM/ROM select (0=ROM, 1=RAM)\r\n   P = PRG page\r\n\r\n if E=0 and R=1, RAM is selected, but it's disabled, resulting in open bus.  In case it's still unclear:\r\n\r\n R=0:       ROM @ $6000-7FFF\r\n R=1, E=0:  Open Bus @ $6000-7FFF\r\n R=1, E=1:  RAM @ $6000-7FFF\r\n\r\n\r\nR:9 - R:B appear to be a full 8 bits:  [PPPP PPPP], and select only ROM.\r\n\r\n      $6000   $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+-------+\r\n    |  R:8  |  R:9  |  R:A  |  R:B  | { -1} |\r\n    +-------+-------+-------+-------+-------+\r\n\r\n\r\nNo games seem to use more than 8k PRG-RAM, so I'm unsure whether or not it's swappable when selected.  I\r\ndon't see why it wouldn't be.\r\n\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n    |  R:0  |  R:1  |  R:2  |  R:3  |  R:4  |  R:5  |  R:6  |  R:7  |\r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n\r\n\r\nMirroring:\r\n---------------------------\r\n\r\nR:C:  [.... ..MM]\r\n  %00 = Vert\r\n  %01 = Horz\r\n  %10 = 1ScA\r\n  %11 = 1ScB\r\n\r\n\r\nIRQs:\r\n---------------------------\r\n\r\nThis mapper has a 16-bit IRQ counter which decrements every CPU cycle.  When it wraps from $0000->FFFF, an\r\nIRQ is tripped.\r\n\r\n reg R:E sets the low 8 bits of the counter\r\n reg R:F sets the high 8 bits\r\n\r\n  Note the regs change the actual counter -- not a reload value.\r\n\r\n reg R:D is the IRQ control:\r\n   [C... ...T]\r\n   C = Enable countdown (0=disabled, 1=enabled)\r\n   T = Enable IRQ triggering (0=disabled, 1=enabled)\r\n\r\nIn order for IRQs to work as expected, both bits must be set.  If either bit is cleared, an IRQ won't occur:\r\n\r\n C=0, T=1:  IRQs are enabled, but the counter will never decrement\r\n C=1, T=0:  Counter decrements, but IRQs are disabled\r\n\r\nAcknowledging IRQs can only be done by disabling them (T=0).\r\n\r\n\r\n\r\nSound:\r\n---------------------------\r\n\r\nSunsoft 5B appears to be identical to the AY 3-8910 (or a similar chip -- possibly a different AY 3-891x or a\r\nYM2149).  The only game to use the sound, Gimmick!, does not use the envelope or noise functionality that\r\nexists on the AY 3-891x, however, through testing it has been shown that such functionality does in fact\r\nexist.\r\n\r\nThe sound info below is a simplified version of the behavior.  Envelope and Noise are not covered (aside from\r\nthe noise shift formula), and registers relating to those areas are not mentioned.  However the information\r\nbelow is enough to satisfy Gimmick!  If you want further information and full register descriptions, consult\r\nan AY 3-8910 datasheet or doc.\r\n\r\nSunsoft 5B has 3 Square channels (no configurable duty cycle -- always play at 50% duty).  Each operate\r\nsimilarly to the native NES sound channels.  They output sound at 1 octave lower than what may be expected,\r\nthough (see below).\r\n\r\n\r\nSound Regs:\r\n---------------------------\r\n\r\n  $C000:  [.... AAAA]   Address for use with $E000\r\n\r\n  $E000:  [DDDD DDDD]   Data port:\r\n\r\n      R:0 ->   [FFFF FFFF]   Chan 0, Low 8 bits of Freq\r\n      R:1 ->   [.... FFFF]   Chan 0, High 4 bits of Freq\r\n      R:2 ->   [FFFF FFFF]   Chan 1, Low 8 bits of Freq\r\n      R:3 ->   [.... FFFF]   Chan 1, High 4 bits of Freq\r\n      R:4 ->   [FFFF FFFF]   Chan 2, Low 8 bits of Freq\r\n      R:5 ->   [.... FFFF]   Chan 2, High 4 bits of Freq\r\n\r\n      R:7 ->   [.... .CBA]   Channel disable flags (0=enabled, 1=disabled)\r\n           C = Disable Chan 2\r\n           B = Disable Chan 1\r\n           A = Disable Chan 0\r\n\r\n      R:8 ->   [.... VVVV]   Chan 0, Volume\r\n      R:9 ->   [.... VVVV]   Chan 1, Volume\r\n      R:A ->   [.... VVVV]   Chan 2, Volume\r\n\r\n\r\nOperation:\r\n---------------------------\r\n\r\nFor tone generation, a counter is counted up each CPU cycle.  When it reaches the given 'F' value, it resets\r\nto zero, and another step through the duty cycle is taken.  These squares' duty cycles are fixed at 50%\r\n(AY 3-8910 docs say 8/16, but see below).\r\n\r\nEmulating in this fashion, with a 16-step duty, these channels play 1 octave higher than they should!\r\nTherefore, either channels are only clocked every other CPU cycle... or (what I find to be easiest to\r\nemulate) the duty is actually 16/32 instead of 8/16, or something else is going on.  I do not know which is\r\nactually happening.\r\n\r\nThe generated tone in Hz can be calculated with the following:\r\n\r\n       CPU_CLOCK\r\nHz = -------------\r\n      (F+1) * 32\r\n\r\n\r\nWhen the duty cycle outputs high, 'V' is output, otherwise 0 is output.  When the channel is disabled (see\r\nR:7), 0 is forced as output for the channel.\r\n\r\n\r\nNon-linear volume:\r\n---------------------------\r\n\r\nOutput volume is non-linear... increasing in steps of 3 dB.\r\n\r\nOutput can be calculated with the following pseudo-code:\r\n\r\n  vol = 1.0;\r\n  for(i = 0; i < 0x10; ++i)\r\n  {\r\n    sunsoft_out[i] = vol * base;\r\n    vol *= step;\r\n  }\r\n\r\nWhere 'base' can be adjusted to match your native NES sound channel levels, and 'step' is \"10^(dB/20)\".\r\n\r\nFor 3 dB, 'step' would be ~1.4125\r\n\r\n\r\nNoise Formula:\r\n---------------------------\r\n\r\n      >>             >>\r\n+-->[nnnn nnnn nnnn nnnn]->output\r\n|                   |  |\r\n|                   | ++\r\n|                   | |\r\n|                   v v\r\n+-------------------XOR\r\n   \r\n\r\n- 16-bit right-shift reg\r\n- bits 0,3 (before shift) XOR to create new input bit\r\n- bit 0 is shifted to output\r\n- initial feed is 1"
  },
  {
    "path": "docs/mapper/070.txt",
    "content": "\r\n========================\r\n=  Mapper 070          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nFamily Trainer - Manhattan Police\r\nFamily Trainer - Meiro Daisakusen\r\nKamen Rider Club\r\nSpace Shadow\r\n\r\n\r\nNotes:\r\n---------------------------\r\nI do not know whether or not this mapper suffers from bus conflicts.  Use caution!\r\n\r\nMany of these games use the family trainer mat as an input device.\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n  $8000-FFFF:  [PPPP CCCC]\r\n    P = Selects 16k PRG @ $8000\r\n    C = Selects 8k CHR @ $0000\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $8000     |     { -1}     |\r\n    +---------------+---------------+"
  },
  {
    "path": "docs/mapper/071.txt",
    "content": "\r\n========================\r\n=  Mapper 071          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nMiG 29 - Soviet Fighter\r\nFire Hawk\r\nThe Fantastic Adventures of Dizzy\r\nBee 52\r\n\r\n\r\nNotes:\r\n--------------------------\r\nThis mapper covers several Camerica/Codemasters boards.  One in paticular that needs to be noted is the board\r\nused by Fire Hawk -- which has mapper controlled 1-screen mirroring.  On other boards, mirroring is\r\nhardwired!  This is yet another one of those terrific mapper number incompatibilities.\r\n\r\nSome of these games are EXTREMELY DIFFICULT to emulate.  Not because the mapper is complicated (it's actually\r\nvery simple), but because the games are picky about timing and use some seldom used aspects of the NES.\r\n\r\nIn paticular:\r\n- Bee 52 uses the sprite overflow flag ($2002.5)\r\n- MiG 29 uses DMC IRQs, and is VERY PICKY about their timing.  If your DMC IRQ timing isn't spot on (or at\r\nleast really freaking close), this game will glitch like hell.\r\n\r\n\r\nThis mapper also involves a custom lockout defeat circuit which is mostly unimportant for emulation purposes.\r\nDetails will not be mentioned here, but are outlined in Kevtris' Camerica Mappers documentation.\r\n\r\nFire Hawk does some strange timing code when changing the mirroring mode.  It is unknown whether or not any\r\nspecial timing is required.\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\n  $8000-9FFF:  [...M ....]  Mirroring (for Fire Hawk only!)\r\n     0 = 1ScA\r\n     1 = 1ScB\r\n\r\n  $C000-FFFF:  PRG Select (16k @ $8000)\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $C000     |     { -1}     |\r\n    +---------------+---------------+\r\n"
  },
  {
    "path": "docs/mapper/072.txt",
    "content": "========================\r\n=  Mapper 072          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nPinball Quest (J)\r\nMoero!! Pro Tennis\r\nMoero!! Juudou Warriors\r\n\r\n\r\nRegisters (**BUS CONFLICTS**):\r\n---------------------------\r\n\r\n$8000-FFFF:  [PCRS DDDD]\r\nP = When a 1 is written after a 0 was previously written,\r\n the bottom three bits of the data bus are copied to the PRG bank select\r\nC = When a 1 is written after a 0 was previously written,\r\n the bottom four bits of the data bus are copied to the CHR bank select\r\nR = For games that have add-on sound, while 0,\r\n the ADPCM playback IC is held in reset and unable to make sound\r\nS = For games that have add-on sound, when the value written here changes\r\n  (direction unknown because the datasheet contradicts itself), \r\n the sound specified by the bottom 5 bits of the address bus is played.\r\n Leaving the value at 0 will probably result in erratic audio playback.\r\nD = the three- or four- bit bank number to switch to, as appropriate.\r\n\r\n\r\nNotes:\r\n---------------------------\r\n\r\nCommands pass through a latch.  Rather than writing to the regs directly, you write the\r\ndesired page number and command to the latch, then send another command that readies it for the next time.\r\n\r\nCommands (PC bits together):\r\n%00 = Do nothing (prepare for next write)\r\n%01 = Set CHR Page\r\n%10 = Set PRG page\r\n%11 = Set both simultaneously\r\n\r\nExample:\r\nIf a game wanted to select CHR page 3, it would first write $43, then $03.  The $43 fills the latch with command \r\nbits $4, which instruct bank $3 to be used for CHR; then the write of $03 prepares for the next write by\r\nresetting the command bits to $0. The $03 should be able to be any value from $00 to $0F, because the command\r\nbits are what is crucial.\r\n\r\nNo current theory explains why games go to any effort to put the bank's nybble in the second byte, although perhaps\r\nit has to do with not disturbing the bank registers while the logic propagates.\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n$0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n+---------------------------------------------------------------+\r\n|                            CHR Reg                            |\r\n+---------------------------------------------------------------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n$8000   $A000   $C000   $E000  \r\n+---------------+---------------+\r\n|    PRG Reg    |     { -1}     |\r\n+---------------+---------------+"
  },
  {
    "path": "docs/mapper/073.txt",
    "content": "\r\n========================\r\n=  Mapper 073          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nVRC3\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nSalamander\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $F000\r\n\r\n  $8000:  [.... IIII]   Bits  0- 3 of IRQ reload value\r\n  $9000:  [.... IIII]   Bits  4- 7 of IRQ reload value\r\n  $A000:  [.... IIII]   Bits  8-11 of IRQ reload value\r\n  $B000:  [.... IIII]   Bits 12-15 of IRQ reload value\r\n\r\n  $C000:  [.... .MEA]   IRQ Control\r\n     M = IRQ Mode (0=16-bit mode, 1=8-bit mode)\r\n     E = IRQ Enable (0=disabled, 1=enabled)\r\n     A = Enable-on-Acknowledge (see IRQ section)\r\n\r\n  $D000:  [.... ....]   IRQ Acknowledge (see IRQ section)\r\n\r\n  $F000:  [.... PPPP]   PRG Select (16k @ $8000)\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $F000     |     { -1}     |\r\n    +---------------+---------------+\r\n\r\n\r\nIRQs:\r\n---------------------------\r\n\r\nVRC3 IRQs operate differently from other VRCs.  The counter is 16 bits instead of 8 bits, and there is no\r\nscanline mode -- only CPU cycle mode.  Other aspects, however, are very similar.\r\n\r\n$8000-B000 set the 16-bit reload value (not the actual IRQ counter).  When $C000 is written to with the 'E'\r\nbit set, the reload value is copied into the actual IRQ counter.\r\n\r\nWhen enabled, the IRQ counter will increment by 1 every CPU cycle until it wraps, at which point the IRQ\r\ncounter is reloaded with the reload value (relevent bits only!  see Modes below) and an IRQ is tripped.\r\n\r\nAny write to $C000 or $D000 will acknowledge the IRQ.\r\n\r\nAny write to $D000 will also copy the 'A' control bit to the 'E' control bit... enabling or disabling IRQs.\r\nThis does not change the contents of the IRQ counter.\r\n\r\n\r\nModes:\r\n---------------------------\r\nThere are 8-bit and 16-bit modes for the IRQ counter, as controlled by the 'M' bit in $C000.\r\n\r\n  In 16-bit mode (M=0):\r\n    - Counter is a full 16-bits.\r\n    - IRQ is triggered when IRQ counter is incremented from $FFFF\r\n\r\n\r\n  In 8-bit mode (M=1):\r\n    - Only the low 8-bit bits of counter are used\r\n    - IRQ is triggered when low 8 bits of IRQ counter are incremented from $FF\r\n    - Incrementing the low bits *never* alters the high bits of the counter\r\n    - When low 8 bits wrap, only the low 8 bits are copied from the reload value... high bits remain unchanged\r\n    - Reloading via $C000 write will still reload all 16 bits.\r\n"
  },
  {
    "path": "docs/mapper/074.txt",
    "content": "\r\n========================\r\n=  Mapper 074          =\r\n========================\r\n\r\n\r\naka:\r\n--------------------------\r\nPirate MMC3 variant\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nDi 4 Ci - Ji Qi Ren Dai Zhan\r\nJi Jia Zhan Shi\r\n\r\n\r\nNotes:\r\n--------------------------\r\nThis mapper is a modified MMC3 (or is based on MMC3?).\r\n\r\nIn addition to any CHR-ROM present, there is also an additional 2k of CHR-RAM which is selectable.  CHR pages\r\n$08 and $09 select CHR-RAM, other pages select CHR-ROM\r\n\r\nApart from that, this mapper behaves exactly like your typical MMC3.  See mapper 004 for details."
  },
  {
    "path": "docs/mapper/075.txt",
    "content": "\r\n========================\r\n=  Mapper 075          =\r\n========================\r\n\r\n\r\naka:\r\n--------------------------\r\nVRC1\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nTetsuwan Atom\r\nGanbare Goemon! - Karakuri Douchuu\r\n\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $F000\r\n\r\n  $8000:  [.... PPPP]   PRG Reg 0 (8k @ $8000)\r\n  $A000:  [.... PPPP]   PRG Reg 1 (8k @ $A000)\r\n  $C000:  [.... PPPP]   PRG Reg 2 (8k @ $C000)\r\n\r\n  $9000:  [.... .BAM]   Mirroring, CHR reg high bits\r\n     M = Mirroring (0=Vert, 1=Horz)\r\n     A = High bit of CHR Reg 0\r\n     B = High bit of CHR Reg 1\r\n\r\n  $E000:  [.... CCCC]   Low 4 bits of CHR Reg 0 (4k @ $0000)\r\n  $F000:  [.... CCCC]   Low 4 bits of CHR Reg 1 (4k @ $1000)\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    | $8000 | $A000 | $C000 | { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n  CHR regs are 5 bits wide.  The low 4 bits of each reg are set by $E000 and $F000, and the high bit is taken\r\nfrom the appropriate bits of $9000.\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------------------------------+-------------------------------+\r\n    |       $9000.1 + $E000         |       $9000.2 + $F000         |\r\n    +-------------------------------+-------------------------------+\r\n\r\n"
  },
  {
    "path": "docs/mapper/076.txt",
    "content": "\r\n ========================\r\n =  Mapper 076          =\r\n ========================\r\n \r\n \r\n Example Games:\r\n --------------------------\r\n Digital Devil Story - Megami Tensei\r\n \r\n \r\n Notes:\r\n ---------------------------\r\n This mapper is a rewire of the Namcot 108 mapper IC to increase CHR to 128k.\r\n The trade off is coarser CHR banking. \r\n \r\n Registers:\r\n ---------------------------\r\n \r\n Range,Mask:   $8000-FFFF, $8001\r\n \r\n \r\n   $8000:  [.... .AAA]\r\n     A = Address for use with $8001\r\n \r\n \r\n   $8001:  [..DD DDDD]    Data port:\r\n       R:2 ->  CHR reg 0  (2k @ $0000)\r\n       R:3 ->  CHR reg 1  (2k @ $0800)\r\n       R:4 ->  CHR reg 2  (2k @ $1000)\r\n       R:5 ->  CHR reg 3  (2k @ $1800)\r\n       R:6 ->  PRG reg 0  (8k @ $8000)\r\n       R:7 ->  PRG reg 1  (8k @ $a000)\r\n \r\n CHR Setup:\r\n ---------------------------\r\n \r\n       $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n     +---------------+---------------+---------------+---------------+\r\n     |      R:2      |      R:3      |      R:4      |      R:5      |\r\n     +---------------+---------------+---------------+---------------+\r\n \r\n \r\n PRG Setup:\r\n ---------------------------\r\n \r\n       $8000   $A000   $C000   $E000  \r\n     +-------+-------+-------+-------+\r\n     |  R:6  |  R:7  | { -2} | { -1} |\r\n     +-------+-------+-------+-------+"
  },
  {
    "path": "docs/mapper/077.txt",
    "content": "========================\r\n=  Mapper 077          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nNapoleon Senki\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThis mapper uses an 8 KiB SRAM to provide both 6 KiB of CHR-RAM and\r\nfour-screen mirroring.\r\n\r\nRegisters: (** BUS CONFLICTS **)\r\n---------------------------\r\n\r\n$8000-FFFF:  [CCCC PPPP]\r\nC = CHR Reg (2k @ $0000)\r\nP = PRG Reg (32k @ $8000)\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\nCHR-RAM is fixed at $0800-$1FFF.  CHR-ROM is swappable at $0000:\r\n\r\n$0000-$0400 $0800-$0C00 $1000-$1400 $1800-$1C00 $2000-$2400  $2800-$2C00\r\n+-----------+-----------+-----------+-----------+-----------+-------------+\r\n| $8000,ROM |  {1},RAM  |  {2},RAM  |  {3},RAM  |  {0},RAM  |Internal VRAM|\r\n+-----------+-----------+-----------+-----------+-----------+-------------+\r\n\r\nWhen making an emulator, you do not need to care about the specific order of\r\nthe CHR-RAM banks: just provide 10KiB from $0800-$2FFF."
  },
  {
    "path": "docs/mapper/078.txt",
    "content": "\r\n========================\r\n=  Mapper 078          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nHoly Diver\r\nUchuusen - Cosmo Carrier\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThis mapper number covers two seperate mappers which are *almost* identical... however the mirroring control\r\non each is different (making them incompatible).  You'll probably have to do a CRC or Hash check to figure\r\nout which mirroring setup to use.\r\n\r\nI think some emus might also look at the mirroring bit in the iNES header to determine which setup to use --\r\nhowever the ROMs I have do not seem to have the mirroring bit set differently, so I don't know how well that\r\nwould work (not to mention it's probably not a good idea anyway).\r\n\r\n\r\nRegisters: (** BUS CONFLICTS **)\r\n---------------------------\r\n\r\n  $8000-FFFF:  [CCCC MPPP]\r\n    C = CHR Reg (8k @ $0000)\r\n    P = PRG Reg (16k @ $8000)\r\n    M = Mirroring:\r\n\r\n       --For Uchuusen - Cosmo Carrier--\r\n        0 = 1ScA\r\n        1 = 1ScB\r\n\r\n       --For Holy Diver--\r\n        0 = Horz\r\n        1 = Vert\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $8000     |     { -1}     |\r\n    +---------------+---------------+\r\n\r\n"
  },
  {
    "path": "docs/mapper/079.txt",
    "content": "\r\n========================\r\n=  Mapper 079          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nBlackjack\r\nDudes with Attitude\r\nF-15 City War\r\nKrazy Kreatures\r\n\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $4100-5FFF, $4100\r\n\r\n   be sure to make note of the mask -- $4200 does not map to the register, but $4300 does.\r\n\r\n\r\n  $4100:  [.CPP PCCC]\r\n    C = CHR Reg (8k @ $0000)\r\n    P = PRG Reg (32k @ $8000)\r\n\r\nNote the high bit of the CHR Reg.\r\n"
  },
  {
    "path": "docs/mapper/080.txt",
    "content": "========================\r\n=  Mapper 080          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nKyonshiizu 2\r\nMinelvaton Saga\r\nTaito Grand Prix - Eikou heno License\r\n\r\n\r\nNotes:\r\n---------------------------\r\nRegs appear at $7EFx, I'm unsure whether or not PRG-RAM can exist at $6000-7EFF\r\n\r\nFudou Myouou Den is often marked to use this mapper -- however it uses mapper 207.\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n$7EF0-7EF5:  CHR Regs\r\n\r\n$7EF6,7EF7:  [.... ...M]  Mirroring\r\n0 = Horz\r\n1 = Vert\r\n\r\n$7EF8,7EF9:  Internal RAM permission ($A3 enables reads/writes; any other value disables)\r\n\r\n$7EFA,7EFB:  PRG Reg 0 (8k @ $8000)\r\n$7EFC,7EFD:  PRG Reg 1 (8k @ $A000)\r\n$7EFE,7EFF:  PRG Reg 2 (8k @ $C000)\r\n\r\n$7F00-7FFF:  128 Bytes of RAM, mirrored once.\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n$0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n+---------------+---------------+-------+-------+-------+-------+\r\n|    <$7EF0>    |    <$7EF1>    | $7EF2 | $7EF3 | $7EF4 | $7EF5 |\r\n+---------------+---------------+-------+-------+-------+-------+\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n$8000   $A000   $C000   $E000  \r\n+-------+-------+-------+-------+\r\n| $7EFA | $7EFC | $7EFE | { -1} |\r\n+-------+-------+-------+-------+"
  },
  {
    "path": "docs/mapper/082.txt",
    "content": "\r\n========================\r\n=  Mapper 082          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nSD Keiji - Blader\r\nKyuukyoku Harikiri Stadium\r\n\r\n\r\nNotes:\r\n---------------------------\r\nRegs appear at $7EFx, I'm unsure whether or not PRG-RAM can exist at $6000-7FFF\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $7EF0-7EF5:  CHR Regs\r\n\r\n  $7EF6:  [.... ..CM]  CHR Mode/Mirroring\r\n    C = CHR Mode select\r\n    M = Mirroring:\r\n       0 = Horz\r\n       1 = Vert\r\n\r\n  $7EFA:  [PPPP PP..]  PRG Reg 0 (8k @ $8000)\r\n  $7EFB:  [PPPP PP..]  PRG Reg 1 (8k @ $A000)\r\n  $7EFC:  [PPPP PP..]  PRG Reg 2 (8k @ $C000)\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n                $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n              +---------------+---------------+-------+-------+-------+-------+\r\nCHR Mode 0:   |    <$7EF0>    |    <$7EF1>    | $7EF2 | $7EF3 | $7EF4 | $7EF5 |\r\n              +---------------+---------------+---------------+---------------+\r\nCHR Mode 1:   | $7EF2 | $7EF3 | $7EF4 | $7EF5 |    <$7EF0>    |    <$7EF1>    |\r\n              +-------+-------+-------+-------+---------------+---------------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    | $7EFA | $7EFB | $7EFC | { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\nNote:  remember that the low 2 bits are not used (right-shift written values by 2)"
  },
  {
    "path": "docs/mapper/085.txt",
    "content": "\r\n========================\r\n=  Mapper 085          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nVRC7\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nLagrange Point\r\nTiny Toon Adventures 2  (J)\r\n\r\n\r\n\r\nVRC7a vs. VRC7b\r\n--------------------------\r\nLagrange Point ('VRC7a') and Tiny Toon Adventures 2 ('VRC7b') both operate exactly the same, but are wired a\r\nbit differently.  VRC7a uses $x010 for regs, and VRC7b uses $x008.  Registers below are listed as they exist\r\non VRC7a.  For VRC7b, make the appropriate adjustments\r\n\r\nAlso, only Lagrange Point seems to use the extra sound.  It's unknown whether or not the sound hardware\r\nexists on VRC7b, as Tiny Toon doesn't use it.\r\n\r\n\r\nCHR-RAM note:\r\n--------------------------\r\nLagrange Point, for some reason I still don't understand, swaps its 8k CHR-RAM around.  How this offers any\r\nfunctionality is beyond me, but the game does it, so your emu must support it.\r\n\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\n$8000:   PRG Reg 0  (8k @ $8000)\r\n$8010:   PRG Reg 1  (8k @ $A000)\r\n$9000:   PRG Reg 2  (8k @ $C000)\r\n\r\n$9010:   Sound Address Reg (see below)\r\n$9030:   Sound Data Port (see below)\r\n\r\n$A000-$D010:  CHR Regs\r\n\r\n$E000:   [.... ..MM]   Mirroring:\r\n       %00 = Vert\r\n       %01 = Horz\r\n       %10 = 1ScA\r\n       %11 = 1ScB\r\n\r\n$E010:   [IIII IIII]   IRQ Reload value\r\n$F000:   [.... .MEA]   IRQ Control\r\n$F010:   [.... ....]   IRQ Acknowledge\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    | $8000 | $8010 | $9000 | { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\n\r\nCHR Setup:\r\n--------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n    | $A000 | $A010 | $B000 | $B010 | $C000 | $C010 | $D000 | $D010 |\r\n    +-------+-------+-------+-------+-------+-------+-------+-------+\r\n\r\n\r\nIRQs:\r\n--------------------------\r\n\r\nVRC7 uses the \"VRC IRQ\" setup shared by several VRCs.  It uses the following registers:\r\n\r\n\r\n  $E010:   [IIII IIII]   IRQ Reload\r\n  $F000:   [.... .MEA]   IRQ Control\r\n  $F010:   [.... ....]   IRQ Acknowledge\r\n\r\nFor info on how these IRQs work, see the \"VRC IRQs\" section in mapper 021\r\n\r\n\r\n----------------------------------------------------------------------------\r\n----------------------------------------------------------------------------\r\n-----  VRC7 Sound          -------------------------------------------------\r\n----------------------------------------------------------------------------\r\n----------------------------------------------------------------------------\r\n\r\nVRC7 has additional sound channels!  It is a slightly dumbed down version of the YM2413 (aka OPLL).  There\r\nare only 6 harmony channels and no rhythmic channels.\r\n\r\nStrap yourself in.  FM-Synth is a beast.\r\n\r\n\r\n---------------------------------------------\r\nDisclaimers:\r\n---------------------------------------------\r\n\r\nInformation here is pieced together from the Yamaha YM2413 Application Manual (\"YM2413.pdf\"), and Mitsutaka\r\nOkazaki's \"emu2413.c\" emulator.  Anyone whose looked at those sources know they are not the easiest things\r\nto comprehend without prior experience with FM synth, so here I attempt to explain things in a more\r\ntraditional form.\r\n\r\nI don't really care about YM2413 (I hate FM synth... I find it extremely ugly), so I only cover items on the\r\nVRC7 here (ie: no rhythmic information).  If you want details about a full YM2413, you'll have to look elsewhere.\r\n\r\nI am NOT confident about this information being 100% accurate.  I made every effort to be as accurate as\r\npossible, and my implementation based on the below info sounds *very close* to recordings of the real thing,\r\nbut I do hear some subtle differences.  I graciously welcome any corrections anyone can offer.\r\n\r\nBitwidths of various counters are kind of an educated guess.  With the exception of the phase accumulator,\r\nwhich is the only counter whose size is hinted at in the documentation... so I'm fairly certain it is in\r\nfact 18 bits wide.\r\n\r\nI mention the use of various lookup tables.  I do not know if these lookup tables actually exist on the\r\nhardware, or if the values are calculated at runtime.  Likewise the actual size of these lookup tables is\r\nentirely unknown to me.  You can choose your own size in your implementation.\r\n\r\n\r\n---------------------------------------------\r\nFM-Synth basics & other fundamental concepts:\r\n---------------------------------------------\r\n\r\nThe basic idea of FM-Synth is you have 2 sine waves (aka, \"slots\"), a \"modulator\" and a \"carrier\".  The\r\noutput of the carrier is what you actually hear.  The output of the modulator alters the frequency of the\r\ncarrier, effectively acting like a supersonic vibrato.  This bends and twists the carrier's waveform into\r\na myriad of different shapes, producing all kinds of different sounds.\r\n\r\nEach of the 6 channels have 2 slots (a Carrier and a Modulator).  Each slot behaves independently and has\r\nits own settings and counters.  Note that I will refer to \"slots\" often in these docs.  Do not confuse\r\na slot for the whole channel.\r\n\r\n\"ADSR\" stands for Attack/Decay/Sustain/Release.  These represent 4 phases of amplitude (volume) changes in\r\nsynthesized audio.  This is a common technique in all synth audio (not just FM-Synth).\r\n    - Attack is when the tone begins, and you have a rapid increase in volume, increasing to *above* the\r\n        desired output level.\r\n    - Decay is when attack has reached its maximum, and the volume starts to decline to the desired\r\n       output level.\r\n   - Sustain is when the volume has reached the desired level.  It holds the volume at that level for as\r\n       long as the tone is to be played.  Although sometimes the volume might slowly drop.\r\n   - Release is when the tone is done, and volume gradually decreases until it's completely silent.\r\n\r\n\"Key on\" / \"Key off\" represents the entry and exit into ADSR.  You can think of it like a piano or a keyboard...\r\nwhen you \"key on\", you are pressing a key, and when you \"key off\" you are releasing a key.  Effectively,\r\nthis means that when you key on, you enter \"Attack\", and when you key off, you enter \"Release\".\r\n\r\n\r\n\r\n---------------------------------------------\r\nVolume and Attenuation:\r\n---------------------------------------------\r\n\r\nVRC7 doesn't really have a concept of an output volume.  Instead, it does everything with \"attenuation\",\r\nwhich is basically the opposite of volume.  Attenuation is like a forced reduction -- so high attenuation\r\nmeans low output.  Zero attenuation means the output is as high as possible.\r\n\r\nAll attenuation levels are expressed in decibels (dB), which is a logarithmic (non-linear) scale.  VRC7's\r\nthreshhold or maximum attenuation is 48 dB.  This means that at 48 dB, output is zero.\r\n\r\nNote that even though dB are non-linear, you can still work with them as if they were linear.  That is, \r\n10dB + 10dB is still 20dB.  The only thing is that when converted to linear units, 20dB is MUCH\r\nMUCH more than 2x 10dB.\r\n\r\nSince VRC7 handles all its output levels in terms of dB, this means you will only need to convert from\r\ndB to linear units in exactly one place:  when determining the linear output of the \"slot\".\r\n\r\nConverting dB <-> Linear can be accomplished with the below formulas:\r\n\r\n    dB     = -20 * log10( Linear ) * scale      (if Linear = 0, dB = +inf)\r\n    Linear = 10 ^ (dB / -20 / scale)\r\n\r\n'scale' is an optional factor you can use to scale up dB so that they're in an easier to use base.\r\n\r\nI recommend using (1<<23)/48 for a scale (this would mean that 1<<23 would represent 48 dB).  This will\r\nmake envelope calculations much easier (see Envelope Generation section for details).\r\n\r\nRemember the threshhold is 48 dB.  So if you have 48 dB or higher, Linear=0.\r\n\r\n\r\n---------------------------------------------\r\nClock rate:\r\n---------------------------------------------\r\n\r\nVRC7 has its own oscillator to drive the clock rate.  It's clocked at 3.6 MHz (exactly 2x the NTSC\r\nNES CPU clock rate), but those clocks are divided by 72, effectively making the rate at which each individual\r\nunit is clocked 49715.90909 Hz.\r\n\r\nI find it very likely that clocking each individual unit is done serially across the 72 cycles, but the\r\neffect that detail has on the generated audio is tiny to the point of being insignificant.\r\n\r\nTo think of this in terms of CPU cycles, you could say that all units are clocked once every 36 CPU\r\ncycles on NTSC.  However, this is techncially inaccurate, as the NES clock does not drive the VRC7.\r\nAnd on PAL systems, the clock rate doesn't sync up like that.\r\n\r\n---------------------------------------------\r\nRegisters:\r\n---------------------------------------------\r\n\r\nRegister descriptions to follow.  Details as to what each field actually does will not be covered here\r\nbut will be explained in future sections.\r\n\r\n\r\n  $9010:  [..AA AAAA]\r\n    A = Address for use with $9030\r\n   \r\n  $9030:  [DDDD DDDD]  --  data port\r\n      R:00-R:07  ->  Custom instrument settings (see below)\r\n     \r\n     R:1x:  [FFFF FFFF] (where x=0-5, selecting the channel)\r\n         F = low 8 bits of F-Num (frequency control)\r\n        \r\n     R:2x:  [..SK BBBF] (where x=0-5, selecting the channel)\r\n         F = high bit of F-Num\r\n        B = Block select (or octave)\r\n        K = Key on  (1=key on, 0=key off)\r\n        S = Sustain On (poorly named, has no impact on Sustain mode -- actually affects Release)\r\n        \r\n     R:3x:  [IIII VVVV]\r\n         I = Instrument select\r\n        V = 'Volume' (poorly named, it's more like \"Carrier Base Attenuation Level\")\r\n\r\n  (regs R:1x, 2x, and 3x apply to both Carrier and Modulator\r\n   regs R:0x apply differently to each                      )\r\n   \r\n\r\n  R:00: [AFPK MMMM]    (applies to Modulator)\r\n  R:01: [AFPK MMMM]    (applies to Carrier)\r\n      A = Enable Amplitude Modulation (AM)\r\n     F = Enable Frequency Modulation (FM)\r\n     P = Disable Percussive Mode  (0=percussive, 1=normal)\r\n     K = Key Scale Rate (KSR)\r\n     M = 'MULTI' Freqency multiplier\r\n     \r\n  R:02:  [KKLL LLLL]\r\n      K = Modulator Key Scale Level (KSL)\r\n     L = Modulator base attenuation level\r\n\r\n  R:03:  [KK.C MFFF]\r\n      K = Carrier Key Scale Level (KSL)\r\n     C = Carrier rectify sine wave  (0=full sine wave,  1=half sine wave)\r\n     M = Modulator rectify sine wave\r\n     F = Modulator Feedback level\r\n   \r\n  R:04:  [AAAA DDDD]   (Modulator)\r\n  R:05:  [AAAA DDDD]   (Carrier)\r\n      A = Attack Rate\r\n     D = Decay Rate\r\n     \r\n  R:06:  [SSSS RRRR]   (Modulator)\r\n  R:07:  [SSSS RRRR]   (Carrier)\r\n      S = Sustain Level\r\n     R = Release Rate\r\n   \r\nThere are 16 selectable instruments (selected via R:3x).  Instrument 0 is configurable via regs\r\nR:00 through R:07.  The other instruments are fixed at the below values:\r\n  \r\n    0x03,0x21,0x04,0x06,0x8D,0xF2,0x42,0x17  // instrument 1\r\n    0x13,0x41,0x05,0x0E,0x99,0x96,0x63,0x12  // instrument 2\r\n    0x31,0x11,0x10,0x0A,0xF0,0x9C,0x32,0x02  // instrument 3\r\n    0x21,0x61,0x1D,0x07,0x9F,0x64,0x20,0x27  // instrument 4\r\n    0x22,0x21,0x1E,0x06,0xF0,0x76,0x08,0x28  // instrument 5\r\n    0x02,0x01,0x06,0x00,0xF0,0xF2,0x03,0x95  // instrument 6\r\n    0x21,0x61,0x1C,0x07,0x82,0x81,0x16,0x07  // instrument 7\r\n    0x23,0x21,0x1A,0x17,0xEF,0x82,0x25,0x15  // instrument 8\r\n    0x25,0x11,0x1F,0x00,0x86,0x41,0x20,0x11  // instrument 9\r\n    0x85,0x01,0x1F,0x0F,0xE4,0xA2,0x11,0x12  // instrument A\r\n    0x07,0xC1,0x2B,0x45,0xB4,0xF1,0x24,0xF4  // instrument B\r\n    0x61,0x23,0x11,0x06,0x96,0x96,0x13,0x16  // instrument C\r\n    0x01,0x02,0xD3,0x05,0x82,0xA2,0x31,0x51  // instrument D\r\n    0x61,0x22,0x0D,0x02,0xC3,0x7F,0x24,0x05  // instrument E\r\n    0x21,0x62,0x0E,0x00,0xA1,0xA0,0x44,0x17  // instrument F\r\n   \r\n\r\n**** SIDE NOTE ****\r\nWriting to Regs R:00 through R:07 do NOT seem to have an immediate effect on channels using\r\ninstrument 0.  Lagrange Point (Track 2 of the NSF) will write to these regs while a channel\r\nusing instrument 0 is still keyed on and audible, resulting in an ugly and very noticable\r\n\"blurp\" noise at the end of a note.  This is not heard on the real hardware, so instrument\r\ndata must be cached somehow.  Perhaps it only takes effect when the channel is keyed on,\r\nor when R:3x is written to?  Don't know exactly.\r\n\r\n\r\n\r\n\r\n---------------------------------------------\r\nPhase / Frequency Calculation:\r\n---------------------------------------------\r\n\r\nEach slot has an 18-bit up counter which determines the current phase (position in the sine wave).\r\nEach clock, this counter is incremented:\r\n    phase += F * (1 << B) * M * V / 2\r\n\r\nwhere:\r\n    F = 9-bit F-num of the channel\r\n   B = 3-bit Block of the channel\r\n   M = see below\r\n   V = vibrato (FM) output\r\n\r\nR:00 or R:01 specify a 4 bit 'MULTI' value.  That MULTI value is run through the below LUT to get 'M':\r\n\r\n    MULTI:  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F   (hex)\r\n    M:      1   2   4   6   8  10  12  14  16  18  20  20  24  24  30  30   (dec)\r\n   \r\nIf FM is enabled for the slot (see R:00 or R:01 for the enable bit), 'V' is the output of the\r\nFM unit.  See AM/FM section for details.\r\n\r\nIf FM is disabled, 'V' = 1\r\n\r\n\r\nAnother 'phase_secondary' value is used to actually generate the phase:\r\n\r\n    phase_secondary = phase + adj\r\n   \r\nFor the Carrier:\r\n adj = the output of the Modulator.  Note that slot output is 20-bits wide, but the phase is only 18\r\n bits wide... this means that the high 2 bits of the modulator output are effectively dropped.\r\n\r\nFor the Modulator:\r\n R:03 has a 3-bit 'F' value specifying the feedback level.\r\n if F=0:     adj = 0\r\n otherwise:  adj = previous_output_of_modulator >> (8 - F)\r\n \r\n \r\n\r\nThe bits of the phase_secondary value are extracted and used to generate the sine wave:\r\n\r\n  phase_secondary:  [RI IIII III. .... ....]\r\n    R:  rectification bit\r\n   I:  index to half-sine lookup table\r\n      ** 'I' may be more or less bits depending on how big your half-sine lookup table is **\r\n\r\n'R' determines what to do with output after it's been converted to a linear level.  See next section\r\nfor details of this bit, and details of the half-sine table.\r\n\r\n\r\n---------------------------------------------\r\nAttenuation / Output calculation:\r\n---------------------------------------------\r\n\r\nThe attenuation level determines the slot output on each clock.  Attenuation level is determined\r\nas follows:\r\n\r\n  TOTAL = half_sine_table[I] + base + key_scale + envelope + AM\r\n\r\n  \r\nhalf_sine_table[I]:\r\n--------------\r\nThe half-sine table mentioned in the previous section does not actually hold the output of the sine\r\nfunction.  Rather, it holds the attenuation level of the sine function.  Example:\r\n\r\nsin(pi/2) = 1   ~~~>  I='0100 0000'  ~~~>  half_sine_table[ I ] = 0 dB\r\nsin(0) = 0      ~~~>  I='0000 0000'  ~~~>  half_sine_table[ I ] = +inf dB\r\n\r\nThis table is effectively:\r\n  half_sine_table[I] = Convert_Linear_To_dB(   sin( pi * I / (1 << bitwidth_of_I) )   )\r\n\r\n  \r\n  \r\nbase:\r\n--------------\r\nFor Modulator:  base = (0.75 * L), where L is the 6-bit base level (see register R:02)\r\nFor Carrier:    base = (3.00 * L), where L is the 4-bit 'volume' (see register R:3x)\r\n\r\n\r\n\r\nkey_scale:\r\n--------------\r\nKey Scale Level, 'K', is a 2-bit value (see regs R:02, R:03) that adds attenuation as the pitch\r\nof the tone increases (ie:  higher pitches = quieter).\r\n\r\nIf K=0:  key_scale=0\r\nOtherwise:\r\n  F = high 4 bits of the current F-Num\r\n  B = 3-bit Block (Octave)\r\n  A = table[ F ] - 6 * (7-B)\r\n\r\n  if A < 0:   key_scale = 0\r\n  otherwise:  key_scale = A >> (3-K)\r\n  \r\ntable:\r\n   F:     $0     $1     $2     $3     $4     $5     $6     $7     $8     $9     $A     $B     $C     $D     $E     $F\r\n   A:     0.00  18.00  24.00  27.75  30.00  32.25  33.75  35.25  36.00  37.50  38.25  39.00  39.75  40.50  41.25  42.00\r\n \r\n \r\nenvelope:\r\n--------------\r\nOutput of the envelope generator.  See Envelope Generation section for details.\r\n\r\n\r\nAM:\r\n--------------\r\nIf Amplitude modulation is enabled for the slot (see R:00, R:01), AM is the output of the amplitude\r\nmodulation unit.  Otherwise, AM=0.\r\n\r\nSee AM/FM section for details.\r\n\r\n\r\n\r\n\r\nFinally... after all that, we have our 'TOTAL'.  This is the total attenuation for the slot.\r\n\r\n1) This attenutation is then converted to linear units to get the preliminary output.  This is\r\nscaled up to a 20-bit value\r\n\r\n2) If the high bit ('R') of the 18-bit 'phase_secondary' value (see previous section) is set, this\r\nmeans we are in the negative portion of the sine wave, which means output needs to be negated.\r\nHowever, if we are rectifying to a half sine wave (see R:03), output is zero'd instead.\r\n\r\n3) Output is then run through a filter which averages this output with the previous clock's output\r\n\r\n4) The result is the FINAL, actual output.\r\n\r\nPseudo-code to clarify:\r\n\r\n   total = half_sine_table[I] + base + key_scale + envelope + AM\r\n   prevoutput = output\r\n   \r\n   // 1)\r\n   output = convert_dB_to_Linear( total ) * (1<<20)\r\n   \r\n   // 2)\r\n   if R:\r\n      if  halfsine:  output = 0\r\n     else:          output = -output\r\n     \r\n   // 3)\r\n      FINAL = (output + prevoutput) / 2\r\n     \r\n     \r\n'FINAL' is what the slot actually outputs.  This is a 20-bit value.  The modulator's output will\r\nbe sent to the carrier, and the carrier's output will be audible (though you will want to scale it\r\ndown... 20-bit audio is crazy loud when ouputting 16-bit samples).\r\n\r\n'FINAL' is also the value used when calculating the modulator's feedback (see prev section).\r\n\r\n\r\n\r\n---------------------------------------------\r\nEnvelope Generation:\r\n---------------------------------------------\r\n\r\nEach slot has a 23-bit up counter (hereon 'EGC') for envelope generation, very similar to the\r\n18-bit phase counter.  It determines the output of the envelope generator... which adds attenuation\r\nto the output (see previous section).\r\n\r\n\r\nThe envelope generator operates as an ADSR unit.  When the channel is keyed on, both the Carrier\r\nand the Modulator enter the Attack Phase.  When keyed off, they enter Release phase.\r\n\r\nWhen the ADSR unit completes a full ADSR cycle, it enters a 5th 'Idle' phase.\r\n\r\nEGC is incremented every clock.  The value by which it's incremented depends on which phase of ADSR\r\nwe're in.  Those rates are then adjusted by a 'Key Scale Rate' factor (see R:00, R:01).\r\n\r\nEGC also serves as the direct output of the envelope generator (except in the Attack phase).\r\nWhen EGC=0, output is 0 dB, and whdn EGC=(1<<23), output is 48 dB.  Because of this, I\r\nrecommend scaling all units in your emulator to work with dB in this (1<<23)/48 base.  Doing\r\nso results in minimal unit conversion.\r\n\r\nFormula for determining the rate to increase EGC:\r\n   BF   = (3-bit Channel Block << 1) + high bit of F-Num... forming a 4-bit value\r\n   K    = Key Scale Rate bit (see R:00, R:01)\r\n   if K:        KB = BF\r\n   otherwise:   KB = BF >> 2\r\n\r\n   R    = base rate (see subsections below)\r\n   RKS  = R*4 + KB\r\n   RH   = RKS >> 2   (if RH > 15, use RH=15)\r\n   RL   = RKS & 3\r\n   \r\n \r\nThe subsections below will provide a value for R, then will use RH and RL to determine\r\nthe rate by which EGC is incremented.\r\n\r\nNote that if R=0, then EGC is not incremented at all.\r\n\r\nAttack:\r\n-------\r\n   R = slot attack rate (4-bits as written to R:04, R:05)\r\n   EGC += (12 * (RL+4)) << RH\r\n   \r\n   Once EGC wraps, reset EGC to zero and enter Decay phase\r\n   \r\nDecay:\r\n-------\r\n   R = slot decay rate (4-bits as written to R:04, R:05)\r\n   EGC += (RL+4) << (RH-1)\r\n   \r\n   Once output level reaches the slot sustain level (see R:06, R:07), set EGC to the sustain\r\n   level (do not reset it to 0!), and enter Sustain phase.\r\n   \r\n   The sustain level is (3 dB * L), where L is the 4-bit value written to the register.\r\n   This means you enter Sustain when EGC >= (3 * L * (1<<23) / 48)\r\n   \r\nSustain:\r\n-------\r\n   If slot is percussive (see R:00, R:01):  R = slot RELEASE rate (R:06, R:07, low bits)\r\n   otherwise:                               R = 0\r\n   EGC += (RL+4) << (RH-1)\r\n   \r\n   When EGC reaches (1<<23), output is fixed at 48 dB and enter Idle phase\r\n   \r\nRelease:\r\n-------\r\n   If channel has \"Sustain On\" set (see R:2x),  R = 5\r\n   otherwise, if slot is percussive:            R = slot release rate (R:06, R:07)\r\n   otherwise:                                   R = 7\r\n   EGC += (RL+4) << (RH-1)\r\n   \r\n   When EGC reaches (1<<23), output is fixed at 48 dB and enter Idle phase\r\n   \r\nIdle:\r\n-------\r\n   R=0\r\n   EGC not incremented\r\n   Output fixed at 48 dB\r\n   \r\n   \r\nAs previously mentioned, the output of the envelope generator is EGC, except in Attack phase.\r\nIn Attack, the actual rate of attack is logarithmic (it also decreases attenuation, rather than\r\nincreasing it).\r\n\r\n   attack_output = 48 dB - (48 dB * ln(EGC) / ln(1<<23))\r\n   (ln = natural log)\r\n\r\n   \r\n---------------------------------------------\r\nKey On / Key Off:\r\n---------------------------------------------\r\n\r\nR:2x has the Key On bit for the channel.  This bit only has an impact when its state transitions.\r\nUpon transition, do the following for both Carrier and Modulator:\r\n\r\nWhen being set (0->1):   (key on)\r\n   - Reset EGC to zero\r\n   - Reset 18-bit phase counter to zero\r\n   - Enter Attack phase\r\n   \r\nWhen being clear (1->0):  (key off)\r\n   - If currently in attack, EGC must be set to the current output level\r\n   - Enter Release phase\r\n\r\n   \r\n---------------------------------------------\r\nAM/FM:\r\n---------------------------------------------\r\n\r\nThere is one AM unit and one FM unit.  The output of these units are shared across all slots.\r\n\r\nBoth units have a 20-bit counter that is increased by 'rate' every clock.\r\n\r\n  sinx = sin(2 * pi * counter / (1<<20))\r\n\r\nAM unit:\r\n  'rate' = 78\r\n  AM_output = (1.0 + sinx) * 0.6 dB   (emu2413 uses 1.2 dB instead of 0.6, but that sounds way too steep to me)\r\n  \r\n  See the \"Attenuation / Output calculation\" section for how this output is applied\r\n\r\n  \r\nFM unit:\r\n  'rate' = 105\r\n  FM_output = 2 ^ (13.75 / 1200 * sinx)\r\n    (note:  '^' is exponent, not xor)\r\n   \r\n  See the \"Phase / Frequency Calculation\" section for how this output is applied"
  },
  {
    "path": "docs/mapper/086.txt",
    "content": "\r\n========================\r\n=  Mapper 086          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nMoero!! Pro Yakyuu (Black)\r\nMoero!! Pro Yakyuu (Red)\r\n\r\n\r\nNotes:\r\n---------------------------\r\nRegs are at $6000-7FFF, so these games have no SRAM.\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\n  $6000-6FFF:  [.CPP ..CC]\r\n    P = Selects 32k PRG @ $8000\r\n    C = Selects 8k CHR @ $0000\r\n\r\n  $7000-7FFF:  [..SS IIII]  Sound control  **not sure about this**\r\n    S = Sound Start/Stop:\r\n                 %10 = start sound effect?\r\n       anything else = stop sound effect?\r\n\r\n    I = Sound effect ID\r\n\r\n\r\nSound:\r\n--------------------------\r\n\r\nThis mapper has some sort of sample playback mechanism.  Writing to $7xxx starts/stops the currently playing\r\nsample.  How this playback works (apart from the sketchy notes above) is a complete mystery to me.\r\n\r\nThe sound effects themselves are NOT PART of the .nes file.  Therefore the only real way to support them\r\ncurrently would be to load external .wav files or something and play them back when they're triggered."
  },
  {
    "path": "docs/mapper/087.txt",
    "content": "\r\n========================\r\n=  Mapper 087          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nArgus (J)\r\nCity Connection (J)\r\nNinja Jajamaru Kun\r\n\r\n\r\nNotes:\r\n---------------------------\r\nRegs are at $6000-7FFF, so these games have no SRAM.\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n\r\n  $6000-7FFF:  [.... ..AB]\r\n    B = High CHR Bit\r\n    A = Low CHR Bit\r\n\r\n  This reg selects 8k CHR @ $0000.  Note the reversed bit orders.  Most games using this mapper only have 16k\r\nCHR, so the 'B' bit is usually unused.\r\n"
  },
  {
    "path": "docs/mapper/088.txt",
    "content": "\r\n========================\r\n=  Mapper 088          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nQuinty (J)\r\nNamcot Mahjong 3\r\nDragon Spirit - Aratanaru Densetsu\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $8001\r\n\r\n\r\n  $8000:  [.... .AAA]  Address for use with $8001\r\n\r\n\r\n  $8001:  [DDDD DDDD]\r\n    Data port:\r\n      R:0 ->  CHR reg 0\r\n      R:1 ->  CHR reg 1\r\n      R:2 ->  CHR reg 2\r\n      R:3 ->  CHR reg 3\r\n      R:4 ->  CHR reg 4\r\n      R:5 ->  CHR reg 5\r\n      R:6 ->  PRG reg 0\r\n      R:7 ->  PRG reg 1\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\nCHR is split into two halves.  $0xxx can only have CHR from the first 64k, $1xxx can only have CHR from the\r\nsecond 64k.\r\n\r\n\r\n\r\n       $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n     +---------------+---------------+-------+-------+-------+-------+\r\n     |     <R:0>     |     <R:1>     |  R:2  |  R:3  |  R:4  |  R:5  |\r\n     +---------------+---------------+-------+-------+-------+-------+\r\n     |                               |                               |\r\n     |  AND written values with $3F  |  OR written values with $40   |\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    |  R:6  |  R:7  | { -2} | { -1} |\r\n    +-------+-------+-------+-------+\r\n"
  },
  {
    "path": "docs/mapper/089.txt",
    "content": "\r\n========================\r\n=  Mapper 089          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nMito Koumon\r\n\r\n\r\nRegisters: (**BUS CONFLICTS**)\r\n--------------------------\r\n  $8000-FFFF:  [CPPP MCCC]\r\n    C = Select 8k CHR @ $0000\r\n    P = Select 16k PRG @ $8000\r\n    M = Mirroring:\r\n      0 = 1ScA\r\n      1 = 1ScB\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $8000     |     { -1}     |\r\n    +---------------+---------------+\r\n"
  },
  {
    "path": "docs/mapper/090.txt",
    "content": "\r\n========================\r\n=  Mapper 090          =\r\n=       + 209          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nTekken 2 Pirate Cart\r\na big fat pile of ass\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nTekken 2                (090)\r\nMortal Kombat 2         (090)\r\nSuper Contra 3          (090)\r\nSuper Mario World       (090)\r\nShin Samurai Spirits 2  (209)\r\n\r\n\r\nRants:\r\n---------------------------\r\nThis mapper is such a big pain in the ass.  Not only is it overly complicated in every possible way, but\r\nevery single game that uses it SUCKS.  Plus the composers for these horrible pirate games must have been\r\ntone-deaf, because the music is always out of key.  I freaking hate this mapper with a passion (can you\r\ntell?).\r\n\r\n\r\n090 vs. 209\r\n---------------------------\r\n209 split from 090 somewhere along the line... but at some time, 090 was shared by both.  Therefore you may\r\ncome across ROMs mislabelled as 090 that are actually 209.\r\n\r\nThe two mappers are exactly the same.  The only difference is a jumper setting which controls the extended\r\nnametable control.  090 has extended NT control permanently disabled, 209 has it enabled.  Why this is in a\r\njumper setting I don't know... since the game can already freaking enable/disable the mode through software!\r\nRegardless... two mappers are needed because some games that don't use the NT control don't disable it\r\nthrough software (they rely on the jumper setting disabling it).\r\n\r\nThis doc, as a whole, applies to both 090 and 209 -- with the exception of the Mirroring section, which draws\r\nthe distinctions between the two.\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThis mapper has no PRG-RAM.  As suprising as that is.\r\n\r\nIn addition to the above mentioned jumper setting that controls mirroring, there are 2 other dipswitch\r\nsettings which can be read back by the game via reg $5000.  Changing the dipswitch can change the game being\r\nplayed in some ROMs (really, it's more or less the same game, just with slight differences).\r\n\r\n\r\nThis document's organization:\r\n---------------------------\r\nSince there are so many registers for this mapper, registers will be listed and outlined as the features are\r\nexplained... and the overall registers section will be extremely brief -- serving primarily as a very quick\r\nreference or checklist.\r\n\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n $8000-8003 are PRG regs.  $8004-8007 are mirrors of them.\r\n    $8000-$8003:  [.PPP PPPP]\r\n\r\n $D000 is the PRG mode select (among other things):\r\n    $D000:  [SRNC CPPP]\r\n      R,N = Relate to Mirroring (see mirroring section for details)\r\n      C = Relate to CHR Setup (see chr setup for details)\r\n      S = Put PRG @ $6000-7FFF\r\n      P = PRG Mode Select\r\n\r\nIf 'S' is clear, $6000-7FFF is always open bus.  It is only when 'S' is set, that $6000 reflects the page\r\nindicated in the setup chart below.\r\n\r\nNotice that page numbers are \"actual\" pages.\r\n\r\nSome modes are bit reversed (as marked below).  This means that the PRG registers are to be interpretted\r\nbackwards:\r\n\r\n  [.ABC DEFG]  normal order\r\n  [.GFE DCBA]  bit reversed order\r\n\r\n\r\n                      $6000        $8000   $A000   $C000   $E000  \r\n               +-----------------+-------------------------------+\r\nPRG Mode %000  |  ($8003 * 4)+3  |             { -1}             |\r\n               +-----------------+-------------------------------+\r\nPRG Mode %001  |  ($8003 * 2)+1  |     $8001     |     { -1}     |\r\n               +-----------------+---------------+---------------+\r\nPRG Mode %010  |      $8003      | $8000 | $8001 | $8002 | { -1} |\r\n               +-----------------+-------+-------+-------+-------+\r\nPRG Mode %011  |      $8003      | $8000 | $8001 | $8002 | { -1} |  *BIT REVERSE*\r\n               +-----------------+-------------------------------+\r\nPRG Mode %100  |  ($8003 * 4)+3  |             $8003             |\r\n               +-----------------+-------------------------------+\r\nPRG Mode %101  |  ($8003 * 2)+1  |     $8001     |     $8003     |\r\n               +-----------------+---------------+---------------+\r\nPRG Mode %110  |      $8003      | $8000 | $8001 | $8002 | $8003 |\r\n               +-----------------+-------+-------+-------+-------+\r\nPRG Mode %111  |      $8003      | $8000 | $8001 | $8002 | $8003 |  *BIT REVERSE*\r\n               +-----------------+-------+-------+-------+-------+\r\n\r\n\r\nIn case you don't see the patterns:\r\n - PRG modes %1xx are the same as %0xx, only $8003 is used for the last page instead of {-1}\r\n - $6000 is always swapped to the last 8k in the block specified by $8003.  In %1xx modes, this means $6000\r\nwill always mirror $E000.\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n   $9000-9007 are CHR regs -- each specifies the low 8 bits of the CHR page\r\n   $A000-A007 -- specifies the high 8 bits of the CHR page (work with above regs)\r\n\r\nThe rest of this section refers to above regs as $900x only -- but note that it all includes $900x and $A00x.\r\n\r\nCHR Mode is set by the following:\r\n\r\n   $D000:  [SRNC CPPP]\r\n     R,N = Relate to Mirroring (see mirroring section for details)\r\n     S,P = Relate to PRG (see prg setup for details)\r\n     C = CHR Mode\r\n\r\n   $D003:  [M.BH HHHH]\r\n     M = Mirror CHR (very strange, see below)\r\n     B = CHR Block mode (0=enabled, 1=disabled)\r\n     H = CHR Block (when in block mode)\r\n\r\n\r\nIn CHR Block mode ('B' clear), $A00x is ignored, and instead, the H bits selects a 256k block for all CHR.\r\n$9000-9007 select a page within that block.\r\n\r\nIn normal mode ('B' set), $9000-9007 select a page from the entire CHR.\r\n\r\nMirror CHR mode ('M' set), only takes effect when in 1k or 2k mode ('C' = %10 or %11).  In this mode,\r\n$0800-$0FFF always mirrors $0000-07FF.  ($1800 is unaffected, however).  This is relatively easily\r\nemulatable by using $9000+$9001 in place of $9002+$9003 in the chart below.\r\n\r\nNote that page numbers are in \"actual\" pages.\r\n\r\n                 $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n               +---------------------------------------------------------------+\r\nCHR Mode %00:  |                             $9000                             |\r\n               +---------------------------------------------------------------+\r\nCHR Mode %01:  |             $9000             |             $9004             |\r\n               +-------------------------------+-------------------------------+\r\nCHR Mode %10:  |     $9000     |     $9002     |     $9004     |     $9006     |\r\n               +---------------+---------------+---------------+---------------+\r\nCHR Mode %11:  | $9000 | $9001 | $9002 | $9003 | $9004 | $9005 | $9006 | $9007 |\r\n               +-------+-------+-------+-------+-------+-------+-------+-------+\r\n\r\n\r\n\r\nMirroring:\r\n---------------------------\r\n\r\nAt first glance... mirroring appears simple:\r\n\r\n  $D001:  [.... ..MM]\r\n    %00 = Vert\r\n    %01 = Horz\r\n    %10 = 1ScA\r\n    %11 = 1ScB\r\n\r\nHowever there is a special setting to complicate this, of course.\r\n\r\nNote!  For mapper 090, the above is it!  None of the below special mirroring stuff applies.  The below\r\nmirroring info applies *only* to mapper 209.\r\n\r\n\r\n   $D000:  [SRNC CPPP]\r\n     S,P = Relate to PRG (see prg setup for details)\r\n     C = Relates to CHR (see chr setup for details)\r\n     N = Enable advanced NT control (0=disabled, 1=enabled)\r\n     R = disable NT RAM (0=NT can be RAM or ROM, 1=NT ROM only)\r\n\r\nWhen 'N' is clear, $D001 controls mirroring, and all other mirroring regs are ignored (including 'R' bit of\r\n$D000).  When 'N' is set, $D001 is ignored, and the below regs control mirroring.\r\n\r\n\r\n   $D002:  [A... ....]   NT RAM select bit\r\n   $B000-B003   NT Regs (low 8 bits)\r\n   $B004-B007   NT Regs (high 8 bits)\r\n\r\nJust like with normal CHR Regs, NT CHR regs are 16-bits... $B000-B003 specify the low bits, and $B004-B007\r\nspecify the high bit.  They are arranged in the following:\r\n\r\n   [ $B000 ][ $B001 ]\r\n   [ $B002 ][ $B003 ]\r\n\r\nWhen 'R' is set, $D002 is ignored, and CHR-ROM is always used as NT (with page selected by appropriate reg).\r\nWhen 'R' is clear... CHR-ROM is only used if bit 7 of the NT Reg does not match the 'A' bit of $D002.  If the\r\nbits match, then NES internal NT RAM is used instead (either NTA or NTB, depending on bit 0 of the NT reg)\r\n\r\n\r\n\r\nIRQs\r\n---------------------------\r\n\r\nIRQs on this mapper are 100% completely insane.  They decided to do everything possible in order to make IRQs\r\nas obfuscated and ridiculous as possible.\r\n\r\nIRQs are triggered by any one of 4 sources:\r\n 1) CPU Cycles\r\n 2) A12 Rises\r\n 3) PPU Reads (wtf, I know, but it's true)\r\n 4) CPU Writes (wtf, I know, but it's true)\r\n\r\nI *think* the only method used by any games is the A12.  CPU Cycles may also be used... and I really doubt\r\nthe other two are used anywhere.\r\n\r\nA12 rises operate just like they do for MMC3 (mapper 004 -- see that doc for details).  One key difference:\r\nUnlike the MMC3, nearby rises are not ignored.  This means that under \"normal\" conditions, this IRQ counter\r\nis clocked 8 times per scanline (not just once).\r\n\r\nClocks are first run through a prescaler, which divides the clocks by either 256 or 8 (prescaling by 8 is\r\nuseful with A12 mode).\r\n\r\nAlso.. the counter can be configured to count up, or count down!  Among other oddities.\r\n\r\nRelated regs are as follows:\r\n\r\n  $C001:  [DU.. FPSS]\r\n    D = Count-down mode (0=disabled, 1=enabled)\r\n    U = Count-up mode (0=disabled, 1=enabled)\r\n    F = Funky mode (0=disabled, 1=enabled) -- see below\r\n    P = Prescaler size (0=256, 1=8)\r\n    S = IRQ source:\r\n      %00 = CPU Cycles\r\n      %01 = PPU A12 rising edges\r\n      %10 = PPU Reads\r\n      %11 = CPU Writes\r\n\r\n\r\n  $C002:  [.... ....]   Any write here will acknowledge and disable IRQs\r\n  $C003:  [.... ....]   Any write here will enable IRQs\r\n  $C000:  [.... ...E]   Alternate method:\r\n     writing to this reg with E=0:  same as writing to $C002\r\n     writing to this reg with E=1:  same as writing to $C003\r\n\r\n  $C004:  [PPPP PPPP]  Prescaler.  Any write here will set the prescaler to 'P' XOR $C006\r\n  $C005:  [IIII IIII]  IRQ Counter.  Any write here will set the IRQ counter to 'I' XOR $C006\r\n  $C006:  [XXXX XXXX]  This value is used as a XOR when writing to $C004/5\r\n\r\n  $C007:  Funky Mode Reg\r\n\r\n\r\n\r\n$C004 and $C005 directly change the IRQ counter/prescaler.  They do not change a reload value.\r\n\r\nWhen Count-up and count-down mode are both enabled, or both disabled, the IRQ counter will stand still.  Only\r\none can be enabled for IRQs to work.\r\n\r\nWhen the prescaler is in 3-bit mode (divide by 8), the high 5 bits of the prescaler remain unchanged when\r\nclocked and only the low 3 bits are used.  When the low 3 bits wrap, the IRQ counter is clocked.  8-bit mode\r\n(divide by 256) works as you'd expect.\r\n\r\nWhen the IRQ counter wraps (either $FF->00 or $00->FF, depending on whether it's incrementing or\r\ndecrementing), an IRQ is tripped (if enabled).\r\n\r\nDisabling IRQs does not stop the counter or prescaler from counting, it simply stops the IRQ from being\r\ngenerated.\r\n\r\n\r\n\r\nFunky Mode:\r\n\r\nWhen 'F' in $C001 is clear, $C007 is ignored.  When set, exact operation is unknown.  It appears to funkify\r\nthe prescaler.  $C007 containing any value other than $FF will result in the IRQ counter not being clocked at\r\nall... and $FF will result in the prescaler dividing by strange amounts (sometimes 8?  sometimes 12?\r\nsometimes 257?).  Details are unknown.  Fortunately, no games use this funky mode.\r\n\r\n\r\nOther crap:\r\n---------------------------\r\n\r\n  $5000:  [DD.. ....]   Dipswitch settings (readable only)\r\n    These bits can be read back as any value depending on dipswitch settings on the cart.  The high bit, in\r\npaticular, has an effect in some games.\r\n\r\n\r\n  $5800, $5801:   8*8->16 multiplication reg.  (read+write)\r\n    These are similar to MMC5's multiplication reg.  You write two values you want multiplied to $5801 and\r\n$5800, then the 16-bit product can be read back ($5800 has low 8 bits, $5801 has high 8 bits).\r\nMulitplication is unsigned.  Multiplication appears to need some processing time.  After writing values, wait\r\n8 CPU cycles before reading.\r\n\r\n  $5803:  a single byte of RAM (read+write)\r\n\r\n\r\n  $5804-$5807 may also be RAM -- it's unknown.\r\n\r\n\r\n\r\nRegisters:\r\n---------------------------\r\nRegisters were all covered in detail in previous sections.  This section is just an overall\r\nreference/checklist.\r\n\r\n\r\n\r\nRange, Mask:  $5000-FFFF, $F007\r\n\r\n\r\n  $5000:        Dipswitch          (read only)\r\n  $5800-5801:   8*8->16 multiplier (read+write)\r\n  $5803:        RAM                (read+write)\r\n  $5804-5807:   ???                (possibly RAM)\r\n\r\n  $8000-8003:   PRG Regs\r\n  $8004-8007:   Mirror of PRG Regs\r\n\r\n  $9000-9007:   CHR Regs (low bits)\r\n  $A000-A007:   CHR Regs (high bits)\r\n\r\n  $B000-B003:   NT Regs (low bits)\r\n  $B004-B007:   NT Regs (high bits)\r\n\r\n  $C000-C007:   IRQ Regs\r\n\r\n  $D000-D003:   Control/Mode Regs\r\n  $D004-D007:   mirror $D000-D003"
  },
  {
    "path": "docs/mapper/091.txt",
    "content": "\r\n========================\r\n=  Mapper 091          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nStreet Fighter III\r\nSuper Mario & Sonic 2\r\n\r\n\r\nNotes:\r\n---------------------------\r\nRegs exist at $6000-7FFF, so this mapper has no SRAM.\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $6000-7FFF, $7003\r\n\r\n  $6000-6003:  CHR Regs\r\n  $7000-7001:  [.... PPPP]  PRG Regs\r\n\r\n  $7002 [.... ....]  IRQ Stop\r\n  $7003 [.... ....]  IRQ Start\r\n\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +---------------+---------------+---------------+---------------+\r\n    |     $6000     |     $6001     |     $6002     |     $6003     |\r\n    +---------------+---------------+---------------+---------------+\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    | $7000 | $7001 | { -2} | { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\n\r\nIRQs:\r\n---------------------------\r\n\r\nIRQs on this mapper seem to behave exactly like MMC3 -- except it's fixed so that it will only fire after 8\r\nscanlines.  This is easily emulatable by using MMC3 logic.\r\n\r\nWrite to $7002/$7003 can translate directly to write(s) to the following MMC3 registers:\r\n\r\non $7002 write:\r\n   a) write to $E000\r\n\r\non $7003 write:\r\n   a) write $07 to $C000\r\n   b) write to $C001\r\n   c) write to $E001\r\n\r\n\r\nFor details on MMC3 IRQ operation, see mapper 004"
  },
  {
    "path": "docs/mapper/092.txt",
    "content": "\r\n========================\r\n=  Mapper 092          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nMoero!! Pro Soccer\r\nMoero!! Pro Yakyuu '88 - Ketteiban\r\n\r\n\r\nNotes:\r\n---------------------------\r\n\r\n   This mapper is identical to mapper 072 except for the different PRG Setup.  See mapper 072 for details.\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     { 0 }     |    PRG Reg    |\r\n    +---------------+---------------+\r\n\r\n"
  },
  {
    "path": "docs/mapper/093.txt",
    "content": "\r\n========================\r\n=  Mapper 093          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nFantasy Zone (J)\r\n\r\n\r\nRegisters: (**BUS CONFLICTS**)\r\n--------------------------\r\n  $8000-FFFF:  [PPPP ...M]\r\n    P = PRG Reg  (16k @ $8000)\r\n    M = Mirroring:\r\n      0 = Vert\r\n      1 = Horz\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $8000     |     { -1}     |\r\n    +---------------+---------------+\r\n\r\n"
  },
  {
    "path": "docs/mapper/094.txt",
    "content": "\r\n========================\r\n=  Mapper 094          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nSenjou no Ookami\r\n\r\n\r\nRegisters: (**BUS CONFLICTS**)\r\n--------------------------\r\n  $8000-FFFF:  [...P PP..]   PRG Reg  (16k @ $8000)\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $8000     |     { -1}     |\r\n    +---------------+---------------+\r\n\r\n"
  },
  {
    "path": "docs/mapper/095.txt",
    "content": "\r\n========================\r\n=  Mapper 095          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nMMC3  (modified)\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nDragon Buster (J)\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThis mapper is a modified MMC3.  It behaves exactly like your normal MMC3, only mirroring is handled\r\ndifferently.  For details on MMC3, refer to mapper 004.\r\n\r\n\r\nRegs:\r\n---------------------------\r\n\r\n$8000:  [CP.. .AAA]\r\n   C = CHR Mode\r\n   P = PRG Mode\r\n   A = Address for $8001\r\n\r\n\r\nThis register operates exactly like it does on your normal MMC3.  It is mentioned here because the 'C' bit\r\nhas another usage for mirroring.\r\n\r\n\r\n\r\nThe normal mirroring reg ($A000) is totally ignored, and the CHR regs select nametables:\r\n\r\nWhen 'C' is set:\r\n   [ R:2 ][ R:3 ]\r\n   [ R:4 ][ R:5 ]\r\n\r\nWhen 'C' is clear:\r\n   [ R:0 ][ R:0 ]\r\n   [ R:1 ][ R:1 ]\r\n\r\n\r\nFor mirroring, only bit 5 of the CHR regs is significant.  Bit 5 of the appropriate reg selects either NTA or\r\nNTB.\r\n"
  },
  {
    "path": "docs/mapper/096.txt",
    "content": "\r\n========================\r\n=  Mapper 096          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nOeka Kids - Anpanman no Hiragana Daisuki\r\nOeka Kids - Anpanman to Oekaki Shiyou!!\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThese games use the Oeka kids tablet -- so you'll need to add support for that if you really want to test\r\nthese.\r\n\r\nThese games use 32k of CHR-RAM, which is swappable in a very unique fashion.  Be sure to read the CHR Setup\r\nsection in detail.\r\n\r\n\r\nRegisters:\r\n---------------------------\r\nI'm unsure whether or not this mapper suffers from bus conflicts.  Use caution!\r\n\r\n\r\n  $8000-FFFF:  [.... .CPP]\r\n    C = CHR Block select (see CHR Setup)\r\n    P = PRG Page select (32k @ $8000)\r\n\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\nThis mapper is tricky!!!\r\n\r\nFirstly, this mapper divides the 32k CHR-RAM into two 16k blocks (above 'C' bit selects which block is used).\r\nThe selected pages (including the fixed page) are taken from only the currently selected 16k block.\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------------------------------+-------------------------------+\r\n    |         **See below**         |             { 3 }             |\r\n    +-------------------------------+-------------------------------+\r\n\r\n\r\nBut that's the easy part.  This mapper does a very, very cool trick which watches the PPU address lines to\r\neffectively \"split\" the nametable into 4 smaller sections -- thereby assigning a different CHR-RAM page to\r\neach section.  This allows **every single tile in the NT** to have a unique tile graphic!\r\n\r\n\r\nLong story short:\r\n\r\n  A nametable spans from $2000-$23BF   ($23C0-$23FF are the attribute table).\r\n  The mapper breaks the NT up like so:\r\n\r\n     $2000-20FF = use CHR page 0\r\n     $2100-21FF = use CHR page 1\r\n     $2200-22FF = use CHR page 2\r\n     $2300-23BF = use CHR page 3\r\n\r\n  the other nametables at $2400, $2800, $2C00 are broken up in the same fashion.\r\n\r\n\r\n\r\n\r\nLong story long:\r\n\r\n  PPU Address lines are modified as the PPU fetches tiles, and also when the game manually changes the PPU\r\naddress (via the second write to $2006 --- or by the increment after read/writing $2007).  The mapper\r\nmonitors every change to the PPU Address lines, and when it lies within a certain range, it swaps the\r\nappropriate CHR page in.\r\n\r\n  It will only swap CHR when the address falls between $2000-2FFF (or mirrored regions like $6000-6FFF,\r\n$A000-AFFF, $E000-EFFF).  $3xxx will not trigger a swap.\r\n\r\n  When in that range, it checks to make sure the address is not attribute tables ((Addr AND $03FF) < $03C0).\r\nNote I'm not 100% sure if the mapper really does this or not.  It's very possible that attribute fetches will\r\nalso swap CHR... this would not really disrupt anything other than making the game be more careful about its\r\nPPU writes.\r\n\r\n  When all that checks out, bits 8 and 9 (Addr AND $0300) select the 4k CHR page to swap in to $0000.\r\n\r\n\r\n  Note that the mapper does not distinguish between PPU driven line changes and game driven line changes.\r\nThis means that games can manually swap the CHR page by doing specific writes to $2006:\r\n\r\n\r\nLDA #$20\r\nSTA $2006\r\nSTA $2006   ; Addr set to $20xx -- CHR page 0 selected\r\n\r\nLDA #$21\r\nSTA $2006\r\nSTA $2006   ; Addr set to $21xx -- CHR page 1 selected\r\n\r\n  And in fact, games would HAVE to do that to select CHR, since that's the only way to fill CHR RAM with the\r\ndesired data.  So make sure your emu supports this."
  },
  {
    "path": "docs/mapper/097.txt",
    "content": "\r\n========================\r\n=  Mapper 097          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nKaiketsu Yanchamaru\r\n\r\n\r\nRegisters:\r\n--------------------------\r\nI'm not sure whether or not this mapper suffers from bus conflicts.  Use caution!\r\n\r\n  $8000-FFFF:  [MM.. PPPP]\r\n    P = PRG Reg  (16k @ $C000)\r\n    M = Mirroring:\r\n       %00 = 1ScA\r\n       %01 = Horz\r\n       %10 = Vert\r\n       %11 = 1ScB\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     { -1}     |     $8000     |\r\n    +---------------+---------------+\r\n\r\n"
  },
  {
    "path": "docs/mapper/105.txt",
    "content": "\r\n========================\r\n=  Mapper 105          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nNES-EVENT\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nNintendo World Championships 1990\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThis mapper is an MMC1 with crazy wiring and a huge 30-bit CPU cycle driven IRQ counter.  Registers are all\r\ninternal and not directly accessable -- and the latch must be written to 1 bit at a time -- just like on a\r\nnormal MMC1.  For details on how regs are written to, see mapper 001.\r\n\r\nThis mapper has 8k CHR-RAM, and it is not swappable.\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nNote that like a normal MMC1, registers are internal and not accessed directly.\r\n\r\n\r\n  $8000-9FFF:   [.... PSMM]  Same as MMC1 (but CHR mode bit isn't used)\r\n\r\n  $A000-BFFF:   [...I OAA.]\r\n       I = IRQ control / initialization toggle\r\n       O = PRG Mode/Chip select\r\n       A = PRG Reg 'A'\r\n\r\n  $C000-DFFF:   [.... ....]  Unused\r\n\r\n  $E000-FFFF:   [...W BBBB]\r\n       W = WRAM disable (same as MMC1)\r\n       B = PRG Reg 'B'\r\n\r\n\r\n\r\nPowerup / Reset / Initialization:\r\n---------------------------\r\n\r\n  On powerup and reset, the first 32k of PRG (from the first PRG chip) is selected at $8000 *no matter what*.\r\nPRG cannot be swapped until the mapper has been \"initialized\" by setting the 'I' bit to 0, then to '1'.  This\r\ntoggling will \"unlock\" PRG swapping on the mapper.\r\n\r\n  Note 'I' also controls the IRQ counter (see below)\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n  There are 2 PRG chips, each 128k.  The 'O' bit selects between the chips, and also determines which PRG Reg\r\nis used to select the page.\r\n\r\n  O=0:  Use first PRG chip (first 128k), use 'A' PRG Reg, 32k swap\r\n  O=1:  Use second PRG chip (second 128k), use 'B' PRG Reg, MMC1 style swap\r\n\r\n  In addition, if the mapper has not been \"unlocked\", the first 32k of the first chip is always selected\r\nregardless (as if $A000 contained $00).\r\n\r\n  Modes as listed below:\r\n\r\n                  $8000   $A000   $C000   $E000\r\n                +-------------------------------+\r\nUninitialized:  |             { 0 }             |  <-- use first 128k\r\n                +-------------------------------+\r\nO=0:            |             $A000             |  <-- use first 128k\r\n                +-------------------------------+\r\nO=1, P=0:       |            <$E000>            |  <-- use second 128k\r\n                +-------------------------------+\r\nO=1, P=1, S=0:  |     { 0 }     |     $E000     |  <-- use second 128k\r\n                +---------------+---------------+\r\nO=1, P=1, S=1:  |     $E000     |     {$07}     |  <-- use second 128k\r\n                +---------------+---------------+\r\n\r\n\r\n\r\n\r\nIRQ Counter:\r\n---------------------------\r\n\r\n  The 'I' bit in $A000 controls the IRQ counter.  When cleared, the IRQ counter counts up every cycle.  When\r\nset, the IRQ counter is reset to 0 and stays there (does not count), and the pending IRQ is acknowledged.\r\n\r\n  The cart has 4 dipswitches which control how high the counter must reach for an IRQ to be generated.\r\n\r\n  The IRQ counter is 30 bits wide.. when it reaches the following value, an IRQ is fired:\r\n\r\n  [1D CBAx xxxx xxxx xxxx xxxx xxxx xxxx]\r\n    ^ ^^^\r\n    | |||\r\n    either 0 or 1, depending on the corresponding dipswitch.\r\n\r\nSo if all dipswitches are open (use '0' above), the counter must reach $20000000.\r\nIf all dipswitches are closed (use '1' above), the counter must reach $3E000000.\r\netc\r\n\r\n  In the official tournament, 'C' was closed, and the others were open, so the counter had to reach $2800000."
  },
  {
    "path": "docs/mapper/107.txt",
    "content": "\r\n========================\r\n=  Mapper 107          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nMagic Dragon\r\n\r\n\r\nRegisters:\r\n---------------------------\r\nI do not know whether or not this mapper suffers from bus conflicts.  Use caution!\r\n\r\n  $8000-FFFF:  [PPPP PPP.]\r\n               [CCCC CCCC]\r\n    P = Selects 32k PRG @ $8000\r\n    C = Selects 8k CHR @ $0000\r\n\r\nThis is very strange.  Bits 1-7 seem to be used by both CHR and PRG."
  },
  {
    "path": "docs/mapper/112.txt",
    "content": "\r\n========================\r\n=  Mapper 112          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nHuang Di\r\nSan Guo Zhi - Qun Xiong Zheng Ba\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $E001\r\n\r\n\r\n  $8000:  [.... .AAA]\r\n    A = Address for use with $A000\r\n\r\n\r\n  $A000:  [DDDD DDDD]\r\n    Data port:\r\n      R:0 ->  PRG reg 0\r\n      R:1 ->  PRG reg 1\r\n      R:2 ->  CHR reg 0\r\n      R:3 ->  CHR reg 1\r\n      R:4 ->  CHR reg 2\r\n      R:5 ->  CHR reg 3\r\n      R:6 ->  CHR reg 4\r\n      R:7 ->  CHR reg 5\r\n\r\n\r\n  $E000:  [.... ...M]\r\n    Mirroring:  0=Vert\r\n                1=Horz\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +---------------+---------------+-------+-------+-------+-------+\r\n    |     <R:2>     |     <R:3>     |  R:4  |  R:5  |  R:6  |  R:7  |\r\n    +---------------+---------------+-------+-------+-------+-------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    |  R:0  |  R:1  | { -2} | { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\n"
  },
  {
    "path": "docs/mapper/113.txt",
    "content": "\r\n========================\r\n=  Mapper 113          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nHES 6-in-1\r\nMind Blower Pak\r\nTotal Funpak\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $4100-5FFF, $4100\r\n\r\n   be sure to make note of the mask -- $4200 does not map to the register, but $4300 does.\r\n\r\n\r\n  $4100:  [MCPP PCCC]\r\n    C = CHR Reg (8k @ $0000)\r\n    P = PRG Reg (32k @ $8000)\r\n    M = Mirroring:\r\n        0 = Horz\r\n        1 = Vert\r\n\r\nNote the high bit of the CHR Reg.\r\n"
  },
  {
    "path": "docs/mapper/115.txt",
    "content": "\r\n========================\r\n=  Mapper 115          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nYuu Yuu Hakusho Final - Makai Saikyou Retsuden\r\n\r\n\r\nNotes:\r\n---------------------------\r\nMMC3 variant.  For info on MMC3, see mapper 004.\r\n\r\nRegs at $6000-7FFF means no PRG-RAM\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $6000-7FFF, $6001\r\n\r\n  $6000:  [O... PPPP]\r\n    O = PRG Mode\r\n    P = 16k PRG Page\r\n\r\n  $6001:  [.... ...C]\r\n    C = CHR Block select\r\n\r\n  $8000-FFFF:  Same as MMC3\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n 'C' selects a 256k CHR block for all the CHR selected by the MMC3.  You can think of this as a CHR-OR of\r\n$000 or $100 depending on 'C'.\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n       $8000   $A000   $C000   $E000  \r\n      +-------------------------------+\r\nO=0:  |              MMC3             |\r\n      +-------------------------------+\r\nO=1:  |     $6000     |     MMC3      |\r\n      +---------------+---------------+\r\n\r\nNormal MMC3 PRG setup applies.  If the O mode bit is set, then $8000-BFFF no longer reflects the typical MMC3\r\nsetup, and instead has a 16k page selected by $6000.\r\n"
  },
  {
    "path": "docs/mapper/118.txt",
    "content": "\r\n========================\r\n=  Mapper 118          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nMMC3  (modified)\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nArmadillo\r\nPro Sport Hockey\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThis mapper is a modified MMC3.  It behaves exactly like your normal MMC3, only mirroring is handled\r\ndifferently.  For details on MMC3, refer to mapper 004.\r\n\r\n\r\n\r\nRegs:\r\n---------------------------\r\n\r\n$8000:  [CP.. .AAA]\r\n   C = CHR Mode\r\n   P = PRG Mode\r\n   A = Address for $8001\r\n\r\n\r\nThis register operates exactly like it does on your normal MMC3.  It is mentioned here because the 'C' bit\r\nhas another usage for mirroring.\r\n\r\n\r\n\r\nThe normal mirroring reg ($A000) is totally ignored, and the CHR regs select nametables:\r\n\r\nWhen 'C' is set:\r\n   [ R:2 ][ R:3 ]\r\n   [ R:4 ][ R:5 ]\r\n\r\nWhen 'C' is clear:\r\n   [ R:0 ][ R:0 ]\r\n   [ R:1 ][ R:1 ]\r\n\r\n\r\nFor mirroring, only bit 7 of the CHR regs is significant.  Bit 7 of the appropriate reg selects either NTA or\r\nNTB.\r\n"
  },
  {
    "path": "docs/mapper/119.txt",
    "content": "\r\n========================\r\n=  Mapper 119          =\r\n========================\r\n\r\naka\r\n--------------------------\r\nTQROM\r\nMMC3 (alternate)\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nHigh Speed\r\nPinbot\r\n\r\n\r\nNotes:\r\n---------------------------\r\nIn addition to any CHR-ROM present, this mapper has 8k CHR-RAM.  CHR-RAM is selectable just like ROM.  Bit 6\r\nof each CHR Reg (R:0 - R:5) indicates whether ROM or RAM is selected (1=use RAM).\r\n\r\nOther than that, this mapper is your plain, vanilla MMC3.  See mapper 004 for details.\r\n"
  },
  {
    "path": "docs/mapper/140.txt",
    "content": "\r\n========================\r\n=  Mapper 140          =\r\n========================\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nBio Senshi Dan - Increaser Tono Tatakai\r\n\r\n\r\nNotes:\r\n---------------------------\r\nRegs lie at $6000-7FFF, so there's no SRAM\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n  $6000-7FFF:  [..PP CCCC]\r\n    P = Selects 32k PRG @ $8000\r\n    C = Selects 8k CHR @ $0000"
  },
  {
    "path": "docs/mapper/152.txt",
    "content": "\r\n========================\r\n=  Mapper 152          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nArkanoid 2 (J)\r\nGegege no Kitarou 2\r\n\r\n\r\n\r\nRegisters: (**BUS CONFLICTS**)\r\n--------------------------\r\n  $8000-FFFF:  [MPPP CCCC]\r\n    M = Mirroring:\r\n        0 = 1ScA\r\n        1 = 1ScB\r\n\r\n    P = PRG Reg (16k @ $8000)\r\n    C = CHR Reg (8k @ $0000)\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $8000     |     { -1}     |\r\n    +---------------+---------------+\r\n\r\n"
  },
  {
    "path": "docs/mapper/154.txt",
    "content": "\r\n========================\r\n=  Mapper 154          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nDevil Man\r\n\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $8000-FFFF, $8001\r\n\r\n\r\n  $8000:  [.M.. .AAA]\r\n    M = Mirroring\r\n       0 = 1ScA\r\n       1 = 1ScB\r\n    A = Address for use with $8001\r\n\r\n\r\n  $8001:  [DDDD DDDD]\r\n    Data port:\r\n      R:0 ->  CHR reg 0\r\n      R:1 ->  CHR reg 1\r\n      R:2 ->  CHR reg 2\r\n      R:3 ->  CHR reg 3\r\n      R:4 ->  CHR reg 4\r\n      R:5 ->  CHR reg 5\r\n      R:6 ->  PRG reg 0  (8k @ $8000)\r\n      R:7 ->  PRG reg 1  (8k @ $A000)\r\n\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +---------------+---------------+-------+-------+-------+-------+\r\n    |     <R:0>     |     <R:1>     |  R:2  |  R:3  |  R:4  |  R:5  |\r\n    +---------------+---------------+-------+-------+-------+-------+\r\n\r\nR:0,R:1  select CHR from the first 64k block.  R:2-R:5 select CHR from the second 64k block.\r\n\r\nTherefore, you must effectively AND the written values to R:0,R:1 with $3F, and OR the written values to\r\nR:2-R:5 with $40.\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    |  R:6  |  R:7  | { -2} | { -1} |\r\n    +-------+-------+-------+-------+\r\n"
  },
  {
    "path": "docs/mapper/159.txt",
    "content": "\r\n========================\r\n=  Mapper 159          =\r\n========================\r\n\r\nThis mapper is covered in full in mapper 016's doc.  See that doc for details.\r\n"
  },
  {
    "path": "docs/mapper/164.txt",
    "content": "\r\n========================\r\n=  Mapper 164          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nFinal Fantasy V\r\n\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $5000-FFFF, $F300\r\n\r\n   $5000, $D000:  PRG reg (32k @ $8000)\r\n\r\n$6000-7FFF may have SRAM (not sure)\r\n\r\n\r\nOn Reset\r\n---------------------------\r\nReg seems to contain $FF on powerup/reset\r\n\r\n\r\nNotes:\r\n---------------------------\r\n\r\nSwapping is really simple -- the thing that is funky is the register range/mask.  $5000 and $D000 will access\r\nthe register, however $5100, $5200, etc will not.\r\n"
  },
  {
    "path": "docs/mapper/165.txt",
    "content": "\r\n========================\r\n=  Mapper 165          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nFire Emblem (Unl)  (some weird ?Chinese? pirate version)\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThis mapper is a strange MMC2+MMC3 hybrid.  Register style, PRG, mirroring, ?and even IRQs? of MMC3, with the\r\nCHR swapping and CHR latch functionality of MMC2.\r\n\r\nThere is 4k CHR-RAM in addition to any CHR-ROM present.\r\n\r\nFor details on MMC3, see mapper 004.  For details on MMC2, see mapper 009.  Both will be referenced heavily\r\nin this doc.\r\n\r\n\r\nOperation:\r\n---------------------------\r\nRegister layout, PRG Setup, SRAM enabling, Mirroring, all function as they do on your vanilla MMC3.\r\n\r\nThe CHR Regs (R:0 - R:5) are used in MMC2 style:\r\n\r\n\r\nCHR Setup:\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------------------------------+-------------------------------+\r\n    |      <<R:0>> or <<R:1>>       |      <<R:2>> or <<R:4>>       |\r\n    +-------------------------------+-------------------------------+\r\n\r\nThe same latches that exist on MMC2 exist on this mapper as well, and determine the appropriate reg.\r\n\r\nCHR page 0 is CHR-RAM, other pages are CHR-ROM.\r\n\r\n\r\nNotes:\r\n---------------------------\r\n\r\nThis game specifically will read ppu$xFD0 or ppu$xFE0 via $2007 to manually toggle the latch (specifically,\r\nto swap in the CHR-RAM page).  Failure to emulate this method of MMC2 latch toggling will result in garbled\r\ngraphics."
  },
  {
    "path": "docs/mapper/180.txt",
    "content": "\r\n========================\r\n=  Mapper 180          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nCrazy Climber (J)\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThis game uses a special input device (the crazy climber controller), so you'll need to emulate that in order\r\nto really test this mapper.\r\n\r\n\r\nRegisters: (*** BUS CONFLICTS ***)\r\n--------------------------\r\n\r\n$8000-FFFF:  [.... .PPP]\r\nPRG Reg (16k @ $C000)\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n$8000   $A000   $C000   $E000  \r\n+---------------+---------------+\r\n|     { 0 }     |     $8000     |\r\n+---------------+---------------+\r\n\r\n\r\nPowerup:\r\n--------------------------\r\nThe register will probably contain 0 on cold powerup, but this is not guaranteed. The contents will be unchanged on reboot."
  },
  {
    "path": "docs/mapper/182.txt",
    "content": "\r\n========================\r\n=  Mapper 182          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nPocahontas\r\nSuper Donkey Kong\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nThis mapper is an MMC3 with its registers all scrambled to hell.  Rather than a typical register outline,\r\nthis section will \"translate\" mapper 182 registers to their coresponding \"normal\" MMC3 counterpart.  For MMC3\r\ndetails, see mapper 004.\r\n\r\nRange, Mask:   $8000-FFFF, $E001\r\n\r\n\r\n   Mapper 182    MMC3\r\n  --------------------\r\n     $8000        -\r\n     $8001      $A000\r\n     $A000      $8000 (addresses further scrambled, see below)\r\n     $A001        -\r\n     $C000      $8001\r\n     $C001      $C000+$C001 *\r\n     $E000      $E000\r\n     $E001      $E001\r\n\r\n\r\nA write to $C001 would be like a write to both $C000 and $C001 on a normal MMC3 (sets reload value, and\r\nclears the IRQ counter).\r\n\r\nThe Address/Data port registers are further scrambled:\r\n\r\n\r\n   Mapper 182    MMC3\r\n  --------------------\r\n       R:0       R:0\r\n       R:1       R:3\r\n       R:2       R:1\r\n       R:3       R:5\r\n       R:4       R:6\r\n       R:5       R:7\r\n       R:6       R:2\r\n       R:7       R:4\r\n\r\nOther than this scrambling mess, the mapper operates exactly like a normal MMC3."
  },
  {
    "path": "docs/mapper/184.txt",
    "content": "\r\n========================\r\n=  Mapper 184          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nAtlantis no Nazo\r\nThe Wing of Madoola\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n  $6000-7FFF:  [.HHH .LLL]\r\n    H = Selects 4k CHR @ $1000\r\n    L = Selects 4k CHR @ $0000\r\n\r\nRegs at $6000-7FFF means no SRAM\r\nThe most significant bit of H is always set in hardware. (i.e. its range is 4 to 7)"
  },
  {
    "path": "docs/mapper/185.txt",
    "content": "========================\r\n=  Mapper 185          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nSpy Vs. Spy (J)\r\nMighty Bomb Jack (J)\r\n\r\nRegisters: (**BUS CONFLICTS**)\r\n---------------------------\r\n$8000-FFFF:  [..CC ..CC]\r\nCHR Reg\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThis mapper is retarded.  These games only have 8k of CHR, and they attempt to disable CHR by writing\r\na specific value to the CHR Reg, then VERIFY that garbage is read back, then they swap back to the\r\nactual CHR.  If they don't get the expected garbage, they lock up.\r\n\r\nPerhaps this was some sort of copy protection?\r\n\r\nEach game has their own value that enables/disables CHR.  Rather than failing to attempt to list all the\r\nexact values used here (I don't know what all of them are), I can provide some logic:\r\n\r\nif C AND $0F is nonzero, and if C does not equal $13:  CHR is enabled\r\notherwise CHR is disabled\r\n\r\nWhen CHR is disabled, the pattern tables are open bus. Theoretically, this should\r\nreturn the LSB of the address read, but real-world behavior may vary."
  },
  {
    "path": "docs/mapper/189.txt",
    "content": "\r\n========================\r\n=  Mapper 189          =\r\n========================\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nThunder Warrior\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThis mapper is a modified MMC3.  Everything operates just as it does on the MMC3, only the normal PRG regs\r\n(R:6,R:7) are ignored, and a new PRG Reg is used instead.\r\n\r\nFor details on MMC3, see mapper 004\r\n\r\n\r\nRegisters:\r\n---------------------------\r\nRegs at $6000-7FFF means no SRAM\r\n\r\n\r\n  $4120-7FFF:  [AAAA BBBB]\r\n     A,B:  PRG Reg\r\n\r\n\r\n  $8000-FFFF:  Same as on MMC3\r\n\r\n\r\nPRG Setup:\r\n--------------------------\r\n\r\n'A' and 'B' bits of the $4120 reg seem to be effectively OR'd.\r\nThat is... $30, $03, and $21  will all select page 3\r\n\r\n     $8000   $A000   $C000   $E000  \r\n    +-------------------------------+\r\n    |             $4120             |\r\n    +-------------------------------+\r\n"
  },
  {
    "path": "docs/mapper/191.txt",
    "content": "\r\n========================\r\n=  Mapper 191          =\r\n========================\r\n\r\n\r\naka:\r\n--------------------------\r\nPirate MMC3 variant\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nSugoro Quest - Dice no Senshitachi (As)\r\n\r\n\r\nNotes:\r\n--------------------------\r\nThis mapper is a modified MMC3 (or is based on MMC3?).\r\n\r\nIn addition to any CHR-ROM present, there is also an additional 2k of CHR-RAM which is selectable.  Bit 7 of\r\neach CHR reg selects RAM or ROM (1=RAM, 0=ROM)\r\n\r\nApart from that, this mapper behaves exactly like your typical MMC3.  See mapper 004 for details."
  },
  {
    "path": "docs/mapper/192.txt",
    "content": "\r\n========================\r\n=  Mapper 192          =\r\n========================\r\n\r\n\r\naka:\r\n--------------------------\r\nPirate MMC3 variant\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nYing Lie Qun Xia Zhuan\r\n\r\n\r\nNotes:\r\n--------------------------\r\nThis mapper is a modified MMC3 (or is based on MMC3?).\r\n\r\nIn addition to any CHR-ROM present, there is also an additional 4k of CHR-RAM which is selectable.\r\n\r\nCHR Pages $08-$0B are CHR-RAM, other pages are CHR-ROM.\r\n\r\nApart from that, this mapper behaves exactly like your typical MMC3.  See mapper 004 for details."
  },
  {
    "path": "docs/mapper/193.txt",
    "content": "\r\n========================\r\n=  Mapper 193          =\r\n========================\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nFighting Hero (Unl)\r\n\r\n\r\n\r\nRegisters:\r\n---------------------------\r\nRegs at $6000-7FFF = no SRAM\r\n\r\nRange,Mask:   $6000-7FFF, $6003\r\n\r\n\r\n  $6000:  CHR Reg 0\r\n  $6001:  CHR Reg 1\r\n  $6002:  CHR Reg 2\r\n  $6003:  PRG Reg\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +-------------------------------+---------------+---------------+\r\n    |           <<$6000>>           |    <$6001>    |    <$6002>    |\r\n    +-------------------------------+---------------+---------------+\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    | $6003 | { -3} | { -2} | { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\n\r\n"
  },
  {
    "path": "docs/mapper/194.txt",
    "content": "\r\n========================\r\n=  Mapper 194          =\r\n========================\r\n\r\n\r\naka:\r\n--------------------------\r\nPirate MMC3 variant\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nDai-2-Ji - Super Robot Taisen (As)\r\n\r\n\r\nNotes:\r\n--------------------------\r\nThis mapper is a modified MMC3 (or is based on MMC3?).\r\n\r\nIn addition to any CHR-ROM present, there is also an additional 2k of CHR-RAM which is selectable.\r\n\r\nCHR Pages $00-$01 are CHR-RAM, other pages are CHR-ROM.\r\n\r\nApart from that, this mapper behaves exactly like your typical MMC3.  See mapper 004 for details."
  },
  {
    "path": "docs/mapper/200.txt",
    "content": "\r\n========================\r\n=  Mapper 200          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\n1200-in-1\r\n36-in-1\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n\r\n  $8000-FFFF:  A~[.... ....  .... MRRR]\r\n    M = Mirroring (0=Vert, 1=Horz)\r\n    R = PRG/CHR Reg\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +---------------------------------------------------------------+\r\n    |                             $8000                             |\r\n    +---------------------------------------------------------------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n     $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $8000     |     $8000     |\r\n    +---------------+---------------+\r\n\r\n"
  },
  {
    "path": "docs/mapper/201.txt",
    "content": "\r\n========================\r\n=  Mapper 201          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\n8-in-1\r\n21-in-1 (2006-CA) (Unl)\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n\r\n  $8000-FFFF:  A~[.... ....  RRRR RRRR]\r\n    R = PRG/CHR Reg\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +---------------------------------------------------------------+\r\n    |                             $8000                             |\r\n    +---------------------------------------------------------------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n     $8000   $A000   $C000   $E000  \r\n    +-------------------------------+\r\n    |             $8000             |\r\n    +-------------------------------+\r\n\r\n"
  },
  {
    "path": "docs/mapper/203.txt",
    "content": "\r\n========================\r\n=  Mapper 203          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\n35-in-1\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n\r\n  $8000-FFFF:  [PPPP PPCC]\r\n    P = PRG Reg\r\n    C = CHR Reg\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +---------------------------------------------------------------+\r\n    |                             $8000                             |\r\n    +---------------------------------------------------------------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n     $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $8000     |     $8000     |\r\n    +---------------+---------------+\r\n\r\n"
  },
  {
    "path": "docs/mapper/205.txt",
    "content": "\r\n========================\r\n=  Mapper 205          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\n15-in-1\r\n3-in-1\r\n\r\n\r\nRegisters:\r\n---------------------------\r\nRegs at $6000-7FFF means no SRAM\r\n\r\n  $6000-7FFF:   [.... ..MM]  Game Mode / Block\r\n\r\n  $8000-FFFF:   MMC3\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThese multicarts select the game mode by writing to $6000-7FFF, the individual games then use traditional\r\nMMC3 style regs at $8000-FFFF.  The MMC3 regs only swap to pages *within* the block specifed by the game\r\nmode.  This can be easily emulated by ANDing the page numbers written to MMC3 with certain values, and then\r\nORing them with other values, based on the selected block.\r\n\r\nChart below to illustrate:\r\n\r\n  Block    PRG-AND     PRG-OR    CHR-AND    CHR-OR\r\n  -------------------------------------------------\r\n   0         $1F        $00        $FF       $000\r\n   1         $1F        $10        $FF       $080\r\n   2         $0F        $20        $7F       $100\r\n   3         $0F        $30        $7F       $180\r\n\r\n\r\nFor details on MMC3, see mapper 004\r\n"
  },
  {
    "path": "docs/mapper/207.txt",
    "content": "\r\n========================\r\n=  Mapper 207          =\r\n========================\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nFudou Myouou Den\r\n\r\n\r\nNotes:\r\n---------------------------\r\nRegs appear at $7EFx, I'm unsure whether or not PRG-RAM can exist at $6000-7FFF\r\n\r\nThis mapper is just like mapper 080, with mirroring handled differently. For details,\r\nrefer to mapper 080.\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $7EF0:  [MCCC CCCC]\r\n     M = Mirroring 0\r\n     C = CHR Reg 0\r\n\r\n  $7EF1:  [MCCC CCCC]\r\n     M = Mirroring 1\r\n     C = CHR Reg 1\r\n\r\n  $7EF2-7EF5:  CHR Regs 2-5\r\n\r\n\r\n  $7EFA,7EFB:  PRG Reg 0 (8k @ $8000)\r\n  $7EFC,7EFD:  PRG Reg 1 (8k @ $A000)\r\n  $7EFE,7EFF:  PRG Reg 2 (8k @ $C000)\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n       $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n     +---------------+---------------+-------+-------+-------+-------+\r\n     |    <$7EF0>    |    <$7EF1>    | $7EF2 | $7EF3 | $7EF4 | $7EF5 |\r\n     +---------------+---------------+-------+-------+-------+-------+\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    | $7EFA | $7EFC | $7EFE | { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\nMirroring:\r\n---------------------------\r\n\r\n  [ $7EF0 ][ $7EF0 ]\r\n  [ $7EF1 ][ $7EF1 ]\r\n\r\nMirroring bit of appropriate reg selects NTA or NTB\r\n"
  },
  {
    "path": "docs/mapper/209.txt",
    "content": "\r\n========================\r\n=  Mapper 209          =\r\n========================\r\n\r\nThis mapper is covered in full in mapper 090's doc.  See that doc for details.\r\n"
  },
  {
    "path": "docs/mapper/210.txt",
    "content": "\r\n========================\r\n=  Mapper 210          =\r\n========================\r\n\r\nThis mapper is covered in full in mapper 019's doc.  See that doc for details.\r\n"
  },
  {
    "path": "docs/mapper/225.txt",
    "content": "\r\n========================\r\n=  Mapper 225          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\n52 Games\r\n58-in-1\r\n64-in-1\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $5800-5803:  [.... RRRR]  RAM  (readable/writable)\r\n                (16 bits of RAM -- 4 bits in each of the 4 regs)\r\n  $5804-5FFF:    mirrors $5800-5803\r\n\r\n  $8000-FFFF:  A~[.HMO PPPP PPCC CCCC]\r\n    H = High bit (acts as bit 7 for PRG and CHR regs)\r\n    M = Mirroring (0=Vert, 1=Horz)\r\n    O = PRG Mode\r\n    P = PRG Reg\r\n    C = CHR Reg\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n               $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n             +---------------------------------------------------------------+\r\nCHR Mode 0:  |                             $8000                             |\r\n             +---------------------------------------------------------------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n               $8000   $A000   $C000   $E000  \r\n             +-------------------------------+\r\nPRG Mode 0:  |            <$8000>            |\r\n             +-------------------------------+\r\nPRG Mode 1:  |     $8000     |     $8000     |\r\n             +---------------+---------------+\r\n"
  },
  {
    "path": "docs/mapper/226.txt",
    "content": "\r\n========================\r\n=  Mapper 226          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\n76-in-1\r\nSuper 42-in-1\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange, Mask:  $8000-FFFF, $8001\r\n\r\n  $8000:  [PMOP PPPP]\r\n     P = Low 6 bits of PRG Reg\r\n     M = Mirroring (0=Horz, 1=Vert)\r\n     O = PRG Mode\r\n\r\n  $8001:  [.... ...H]\r\n     H = high bit of PRG\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\nLow 6 bits of the PRG Reg come from $8000, high bit comes from $8001\r\n\r\n\r\n               $8000   $A000   $C000   $E000  \r\n             +-------------------------------+\r\nPRG Mode 0:  |             <Reg>             |\r\n             +-------------------------------+\r\nPRG Mode 1:  |      Reg      |      Reg      |\r\n             +---------------+---------------+\r\n"
  },
  {
    "path": "docs/mapper/227.txt",
    "content": "\r\n========================\r\n=  Mapper 227          =\r\n========================\r\n\r\n\r\nExample Game:\r\n--------------------------\r\n1200-in-1\r\n\r\n\r\nNotes:\r\n---------------------------\r\nThis mapper has 8k CHR-RAM, and also has the ability to write protect it's CHR-RAM!\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $8000-FFFF:  A~[.... ..LP  OPPP PPMS]\r\n    L = Last PRG Page Mode\r\n    P = PRG Reg\r\n    O = Mode\r\n    M = Mirroring (0=Vert, 1=Horz)\r\n    S = PRG Size\r\n\r\n\r\nSetup:\r\n---------------------------\r\n\r\nWhen 'O' is set, CHR-RAM is write protected (writes have no effect).  'O' also changes the PRG mode.\r\n\r\nNote there is funky ANDs and ORs going on below depending on the modes:\r\n\r\n\r\n                  $8000   $A000   $C000   $E000  \r\n                +---------------+---------------+\r\nO=1, S=0:       |       P       |       P       |\r\n                +-------------------------------+\r\nO=1, S=1:       |             < P >             |\r\n                +-------------------------------+\r\nO=0, S=0, L=0:  |       P       |   P AND $38   |\r\n                +---------------+---------------+\r\nO=0, S=1, L=0:  |   P AND $3E   |   P AND $38   |\r\n                +---------------+---------------+\r\nO=0, S=0, L=1:  |       P       |   P  OR $07   |\r\n                +---------------+---------------+\r\nO=0, S=1, L=1:  |   P AND $3E   |   P  OR $07   |\r\n                +---------------+---------------+\r\n\r\n\r\n"
  },
  {
    "path": "docs/mapper/228.txt",
    "content": "\r\n========================\r\n=  Mapper 228          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nAction 52\r\nCheetah Men II\r\n\r\n\r\nNotes:\r\n---------------------------\r\nCheetah Men II is infamous for how freaking terrible it is.  Action 52 is none better.  These games are SO\r\nbad, it's hilarious.\r\n\r\nAction 52's PRG size is weird (not a power of 2 value).  This is because there are 3 seperate 512k PRG chips.\r\nPRG Setup section will cover details.\r\n\r\n\r\nPowerup and Reset:\r\n---------------------------\r\nApparently the games expect $00 to be written to $8000 on powerup/reset.\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $4020-4023:  [.... RRRR]  RAM  (readable/writable)\r\n                (16 bits of RAM -- 4 bits in each of the 4 regs)\r\n  $4024-5FFF:    mirrors $4020-4023\r\n\r\n  $8000-FFFF:    [.... ..CC]   Low 2 bits of CHR\r\n               A~[..MH HPPP PPO. CCCC]\r\n\r\n    M = Mirroring (0=Vert, 1=Horz)\r\n    H = PRG Chip Select\r\n    P = PRG Page Select\r\n    O = PRG Mode\r\n    C = High 4 bits of CHR\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +---------------------------------------------------------------+\r\n    |                             $8000                             |\r\n    +---------------------------------------------------------------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n'H' bits select the PRG chip.  Each chip is 512k in size.  Chip 2 does not exist, and when selected, will\r\nresult in open bus.  The Action 52 .nes ROM file contains chips 0, 1, and 3:\r\n\r\nchip 0:  offset 0x000010\r\nchip 1:  offset 0x080010\r\nchip 2:  -- non existant --\r\nchip 3:  offset 0x100010\r\n\r\n'P' selects the PRG page on the currently selected chip.\r\n\r\n               $8000   $A000   $C000   $E000  \r\n             +-------------------------------+\r\nPRG Mode 0:  |            <$8000>            |\r\n             +-------------------------------+\r\nPRG Mode 1:  |     $8000     |     $8000     |\r\n             +---------------+---------------+\r\n\r\n"
  },
  {
    "path": "docs/mapper/230.txt",
    "content": "\r\n========================\r\n=  Mapper 230          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\n22-in-1\r\n\r\n\r\nReset Driven:\r\n---------------------------\r\nThe mapper has 2 main modes:  Contra mode, and multicart mode.  Performing a Soft Reset switches between\r\nthem.\r\n\r\n\r\nNotes:\r\n---------------------------\r\n\r\nThis multicart has an odd PRG size (not power of 2).  This is because there are 2 PRG chips.\r\nThe first is 128k and contains Contra, the other is 512k and contains the multicart.\r\n\r\nA soft reset changes which chip is used as well as other stuff relating to the mode\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n\r\n Contra Mode $8000-FFFF:     [.... .PPP]\r\n\r\n Multicart Mode $8000-FFFF:  [.MOP PPPP]\r\n\r\n    M = Mirroring (0=Horz, 1=Vert)\r\n    O = PRG Mode\r\n    P = PRG Page\r\n\r\nNote:\r\n  Mirroring is always Vert in Contra Mode.\r\n\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n                $8000   $A000   $C000   $E000  \r\n              +---------------+---------------+\r\nContra Mode:  |     $8000     |     { 7 }     |  <---  use chip 0\r\n              +-------------------------------+\r\nMulti Mode 0: |             <Reg>             |  <---  use chip 1\r\n              +-------------------------------+\r\nMulti Mode 1: |      Reg      |      Reg      |  <---  use chip 1\r\n              +---------------+---------------+\r\n\r\n\r\nchip 0 = 128k PRG  (offset 0x00010-0x2000F)\r\nchip 1 = 512k PRG  (offset 0x20010-0xA000F)"
  },
  {
    "path": "docs/mapper/231.txt",
    "content": "\r\n========================\r\n=  Mapper 231          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\n20-in-1\r\n\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n $8000-FFFF:     A~[.... .... M.LP PPP.]\r\n    M = Mirroring (0=Vert, 1=Horz)\r\n    L = Low bit of PRG\r\n    P = High bits of PRG\r\n\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\nNote that 'L' and 'P' bits make up the PRG reg, and the 'L' is the low bit.\r\n\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    | $8000 AND $1E |     $8000     |\r\n    +---------------+---------------+\r\n\r\n"
  },
  {
    "path": "docs/mapper/232.txt",
    "content": "\r\n========================\r\n=  Mapper 232          =\r\n========================\r\n\r\nExample Games:\r\n--------------------------\r\nQuattro Adventure\r\nQuattro Sports\r\nQuattro Arcade\r\n\r\n\r\nNotes:\r\n--------------------------\r\nThis is another Camerica/Codemasters mapper like 071.  Like 071, this mapper also involves a custom lockout\r\ndefeat circuit which is mostly unimportant for emulation purposes.  Details will not be mentioned here, but\r\nare outlined in Kevtris' Camerica Mappers documentation.\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n $8000-BFFF:   [...B B...]   PRG Block Select\r\n\r\n $C000-FFFF:   [.... ..PP]   PRG Page Select\r\n    \r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\nPages are taken from the 64k block currently selected by $8000.\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +---------------+---------------+\r\n    |     $C000     |     { 3 }     |\r\n    +---------------+---------------+\r\n"
  },
  {
    "path": "docs/mapper/233.txt",
    "content": "\r\n========================\r\n=  Mapper 233          =\r\n========================\r\n\r\n\r\nExample Game:\r\n--------------------------\r\n???? \"42-in-1\"  ????\r\n\r\n\r\nNotes:\r\n---------------------------\r\nSources report this mapper as \"42-in-1\" with description layed out below.  I did not test this,\r\nsince I could not find a copy of the ROM in question.  The only ROM I have that's marked as\r\n233 is \"Unknown Multicart 1\", and it does *not* follow the description in this doc at all.\r\n\r\nThere is a \"Super 42-in-1\"... but that is mapper 226.  226, by the way, is strikingly similar\r\nto the below description.  I wonder if below description really applies to 233?\r\n\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n $8000-FFFF:  [MMOP PPPP]\r\n    M = Mirroring\r\n    O = PRG Mode\r\n    P = PRG Page\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n              $8000   $A000   $C000   $E000  \r\n            +-------------------------------+\r\nPRG Mode 0: |            <$8000>            |\r\n            +-------------------------------+\r\nPRG Mode 1: |     $8000     |     $8000     |\r\n            +---------------+---------------+\r\n\r\n\r\nMirroring:\r\n---------------------------\r\n\r\n  'M' mirroring bits:\r\n     %00 = See below\r\n     %01 = Vert\r\n     %10 = Horz\r\n     %11 = 1ScB\r\n\r\n\r\n  Mode %00 (almost, but not quite 1ScA):\r\n    [  NTA  ][  NTA  ]\r\n    [  NTA  ][  NTB  ]\r\n\r\n\r\n\r\n-----------------------------------------------\r\n--------------------------\r\n\"Unknown Multi Cart 1\"\r\n--------------------------\r\n-----------------------------------------------\r\n\r\n\r\n\r\nNotes:\r\n--------------------------\r\n\r\nThis ROM is a mystery.\r\n\r\nIt consists of 32 individual mapper 0 games, each 16k PRG + 8k CHR.\r\n\r\nGames do not use compatible mirroring modes -- the ROM includes Ice Climber (which uses H mirroring) and\r\nExcitebike (V mirroring).  \r\n\r\nNo games seem to be modified to make any kind of writes to $8000-FFFF, so I didn't find any clue as to what\r\nmapper registers are being written to (if any).\r\n\r\nThere does not appear to be *ANY* game selection screen for the multicart.  I have paged through every 16k\r\nof PRG and they all run the individual games straight up.  So unless the mapper aligns to 8k (or smaller) pages\r\non startup and there's some crap hidden in there, I don't see how these games are held together.\r\n\r\nCould it be that this ROM dump is bad or incomplete?  The *only* other thing I can think of is that this cart might\r\nbe reset based (a la mapper 060) -- but that is complicated by the fact that games use conflicting mirroring modes.\r\nSo perhaps the mapper sets the mirroring mode based on what game is selected?\r\n\r\n\r\nMore weirdness:  games seem to have mismatching PRG/CHR pages.  Ice Climber's PRG is on page $1F, but its CHR is\r\non page $17.  It seems that you need to subtract 8 from the PRG page to get the matching CHR page (and wrap where\r\nappropriate)\r\n\r\nEven more weirdness:  despite each game only having 16k PRG, some of the games in the package normally have 32k!\r\nI don't know if the games were just hacked and had a bunch of stuff removed for what.\r\n\r\nIt's possible this ROM dump is bogus and/or bad.  It might even be assigned the wrong mapper number.\r\n\r\n\r\n\r\nAt any rate... here are the games.\r\n\r\n\r\n\r\nGame name          PRG page  CHR Page  Mirroring\r\n----------------------------------------------------------\r\nGalaxian              $00      $18        Horz\r\n10 Yard Fight         $01      $19        Horz\r\nDKJ Math              $02      $1A        Vert\r\nAntarctic Adventure   $03      $1B        Horz\r\nBalloon Fight         $04      $1C        Horz\r\nBaseball              $05      $1D        Horz\r\nBattle City           $06      $1E        Horz\r\nBinary Land           $07      $1F        Horz\r\nBurger Time           $08      $00        Horz\r\nChack 'n Pop          $09      $01        Horz\r\nClu Clu Land          $0A      $02        Horz\r\nLode Runner           $0B      $03        Vert\r\n?? Go / Othello ??    $0C      $04        ????\r\nField Combat          $0D      $05        Horz   ** really 32k PRG? **\r\nDevil World           $0E      $06        Horz\r\nDig Dug               $0F      $07        Horz\r\nDonkey Kong           $10      $08        Horz\r\nDonkey Kong Jr.       $11      $09        Horz\r\nDonkey Kong 3         $12      $0A        Vert\r\nDoor Door             $13      $0B        Vert\r\nDuck Hunt             $14      $0C        Vert\r\nExcitebike            $15      $0D        Vert\r\nExerion               $16      $0E        Horz\r\nF1-Race               $17      $0F        Vert\r\nFormation Z           $18      $10        Horz\r\nFront Line            $19      $11        Horz\r\nGalaga                $1A      $12        Horz   ** really 32k PRG? **\r\nGolf                  $1B      $13        Vert\r\nHogan's Alley         $1C      $14        Vert\r\nHyper Olympic         $1D      $15        Vert\r\n?? some gun game ??   $1E      $16        ????\r\nIce Climber           $1F      $17        Horz\r\n\r\n"
  },
  {
    "path": "docs/mapper/234.txt",
    "content": "========================\r\n=  Mapper 234          =\r\n========================\r\n\r\nExample Game:\r\n--------------------------\r\nMaxi 15\r\n\r\n\r\nNotes:\r\n--------------------------\r\nTypical for the Atari 2600, but strange for the NES: Registers lie at\r\n$FF80-$FFFF but bankswitching happens on reads, as well as writes. Bus\r\nconflicts are thus avoided by storing the library of desired bankswitch\r\nvalues in ROM.\r\n\r\nExample:\r\n\r\nLDA $FF80  ; where $FF80 contains $62\r\n\r\nwould (ignoring bus conflicts) have the same effect on the mapper as:\r\n\r\nLDA #$62\r\nSTA $FF80\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $FF80-FFFF, $FFF8\r\n\r\n$FF80, $FF88, $FF90, $FF98:  [MOQq BBBb]  Reg 0\r\nM = Mirroring (0=Vert, 1=Horz)\r\nO = Mode (0=CNROM, 1=NINA-03)\r\nB,b = Block selection\r\nq = ROMs 3+4 /Enable (0=normal, 1=disable ROM further from cartridge edge)\r\n\r\n Since the cartridge was distributed with only ROMs 1+2\r\n  populated, this is irrelevant.\r\n\r\n This bit seems to have been intended to have been an extra\r\n  address line for ROMs 3+4, enabling a total of 1.5M/1.5M in the\r\n  cartridge, but a mistake prevents it from working.\r\n\r\nQ = ROM switch (0=enable ROMs 1+2, 1=enable ROMs 3+4)\r\n i.e. 0 for normal operation\r\n\r\n$FFC0, $FFC8, $FFD0, $FFD8:  [.... ..LL]  Reg 1\r\nL = Lockout defeat (charge pump drive)\r\n\r\n$FFE8, $FFF0:   [.cCC ...P]               Reg 2\r\nC,c = CHR page\r\nP = PRG page\r\n\r\n\r\nOnce the bottom 6 bits of Reg 0 contain a non-zero value, Reg 0 and Reg 1\r\nare locked and cannot be changed until the system is reset.\r\n\r\nReg 2 is never locked.\r\n\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n8k page @ $0000 selected by the following:\r\n\r\n'O'     CHR page\r\n---------------------\r\n0      %BB BbCC\r\n1      %BB BcCC\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n32k page @ $8000 selected by the following:\r\n\r\n'O'     PRG page\r\n---------------------\r\n0      %BBBb\r\n1      %BBBP\r\n\r\n\r\n\r\nOn Powerup/Reset:\r\n---------------------------\r\n\r\nRegs all filled with 0 and unlocked."
  },
  {
    "path": "docs/mapper/240.txt",
    "content": "\r\n========================\r\n=  Mapper 240          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nJing Ke Xin Zhuan\r\nSheng Huo Lie Zhuan\r\n\r\n\r\nRegisters:\r\n--------------------------\r\n  $4020-5FFF:  [PPPP CCCC]\r\n    P = Selects 32k PRG @ $8000\r\n    C = Selects 8k CHR @ $0000"
  },
  {
    "path": "docs/mapper/242.txt",
    "content": "\r\n========================\r\n=  Mapper 242          =\r\n========================\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nWai Xing Zhan Shi\r\n\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\n  $8000-FFFF:  A~[.... .... .PPP P.M.]\r\n    P = PRG Reg (32k @ $8000)\r\n    M = Mirroring (0=Vert, 1=Horz)\r\n\r\n\r\nPowerup/Reset:\r\n---------------------------\r\n\r\nRegister set to 0 on powerup/reset."
  },
  {
    "path": "docs/mapper/243.txt",
    "content": "\r\n========================\r\n=  Mapper 243          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nHoney\r\nPoker III 5-in-1\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $4020-4FFF, $4101\r\n\r\n  $4100:  [.... .AAA]   Address for use with $4101\r\n\r\n  $4101:   Data port\r\n     R:2 -> [.... ...H]  High bit of CHR reg\r\n     R:4 -> [.... ...L]  Low bit of CHR reg\r\n     R:5 -> [.... .PPP]  PRG reg  (32k @ $8000)\r\n     R:6 -> [.... ..DD]  Middle bits of CHR reg\r\n     R:7 -> [.... .MM.]  Mirroring\r\n         %00 = Horz\r\n         %01 = Vert\r\n         %10 = See below\r\n         %11 = 1ScB\r\n\r\n\r\nMirroring:\r\n---------------------------\r\n\r\nMirroing mode %10 is not quite 1ScB:\r\n\r\n  [  NTA  ][  NTB  ]\r\n  [  NTB  ][  NTB  ]\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n8k CHR page @ $0000 is selected by the given 4 bit CHR page number ('HDDL')\r\n\r\n"
  },
  {
    "path": "docs/mapper/245.txt",
    "content": "\r\n========================\r\n=  Mapper 245          =\r\n========================\r\n\r\n\r\nExample Games:\r\n--------------------------\r\nChu Han Zheng Ba - The War Between Chu & Han\r\nXing Ji Wu Shi - Super Fighter\r\nYin He Shi Dai\r\nYong Zhe Dou e Long - Dragon Quest VII (As)\r\nDong Fang de Chuan Shuo - The Hyrule Fantasy\r\n\r\n\r\nNotes:\r\n---------------------------\r\nAnother ?Chinese? MMC3 clone.  Very similar to your typical MMC3.  For MMC3 info, see mapper 004.\r\n\r\nRegister layout is identical to a typical MMC3.\r\n\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\nCHR-RAM is not swappable.  When there is no CHR-ROM present, 8k CHR-RAM is fixed.  However the CHR Mode bit\r\n($8000.7) can still \"flip\" the left/right pattern tables.\r\n\r\nExample:\r\n\r\n                    $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n                  +-------------------------------+-------------------------------+\r\nCHR-RAM, Mode 0:  |             { 0 }             |             { 1 }             |\r\n                  +-------------------------------+-------------------------------+\r\nCHR-RAM, Mode 1:  |             { 1 }             |             { 0 }             |\r\n                  +---------------------------------------------------------------+\r\nCHR-ROM:          |                          Typical MMC3                         |\r\n                  +---------------------------------------------------------------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\nPRG Setup is the same as a normal MMC3, although there's a PRG-AND of $3F, and games select a 512k Block with\r\nbit 1 of R:0.  Pretty simple really:\r\n\r\n   R:0:  [.... ..P.]\r\n\r\n  'P'    PRG-AND    PRG-OR\r\n --------------------------\r\n   0       $3F        $00\r\n   1       $3F        $40\r\n\r\n\r\nR:0 remains the normal MMC3 CHR reg, as well.  Although the game that uses it as a PRG block selector (\"DQ7\")\r\nuses CHR-RAM, so it is normally ignored.\r\n\r\n"
  },
  {
    "path": "docs/mapper/246.txt",
    "content": "\r\n========================\r\n=  Mapper 246          =\r\n========================\r\n\r\n\r\nExample Game:\r\n--------------------------\r\nFong Shen Bang - Zhu Lu Zhi Zhan\r\n\r\n\r\nNotes:\r\n--------------------------\r\n\r\nRegs lie at $6000-67FF, but SRAM exists at $6800-7FFF.\r\n\r\nDon't know if there's only 6k of SRAM, or if there's 8k, but the first 2k is inaccessable.  I find the latter\r\nmore likely.\r\n\r\n\r\nRegisters:\r\n---------------------------\r\n\r\nRange,Mask:   $6000-67FF, $6007\r\n\r\n\r\n  $6000-6003:  PRG Regs\r\n  $6004-6007:  CHR Regs\r\n\r\n\r\nCHR Setup:\r\n---------------------------\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +---------------+---------------+---------------+---------------+\r\n    |     $6004     |     $6005     |     $6006     |     $6007     |\r\n    +---------------+---------------+---------------+---------------+\r\n\r\n\r\nPRG Setup:\r\n---------------------------\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    | $6000 | $6001 | $6002 | $6003 |\r\n    +-------+-------+-------+-------+\r\n\r\n\r\nPowerup/Reset:\r\n---------------------------\r\n$6003 set to $FF on powerup (and probably reset, but not sure).\r\n"
  },
  {
    "path": "docs/mapper/__ READ THIS FIRST __.txt",
    "content": "\r\n     ******************************************\r\n     *     iNES Mappers by Mapper Number      *\r\n     *               v0.6.2                   *\r\n     *              by Disch                  *\r\n     *                                        *\r\n     *    modifications made by NESDEV wiki   *\r\n     *      members, commited by Bregalad     *\r\n     ******************************************\r\n\r\n\r\nRead this doc\r\n--------------------------\r\n\r\n  The mapper pages use charts and symbols and abbreviations and stuff which aren't clarified in each\r\nindividual doc, but are covered here.  You could probably get away with just skimming this doc and/or only\r\ncoming back to it if something in a specific mapper doc seems unclear.\r\n\r\n\r\n\r\nRAM Names\r\n--------------------------\r\n\r\n  \"WRAM\", \"SRAM\", and \"PRG-RAM\" are used synonymously and inconsistently in these docs.  Kind of sloppy of\r\nme, I know.  All three terms refer to on-cartridge RAM.\r\n\r\n\r\n\r\nMirroring\r\n--------------------------\r\n\r\n  The NES only has two physical nametables.  These nametables are referred to as \"NTA\" and \"NTB\".  There are\r\n4 \"slots\" for nametables to be accessed:  $2000 (upper-left), $2400 (upper-right), $2800 (lower-left), and\r\n$2C00 (lower-right)\r\n\r\n  Mappers which can customize the nametable layout may have a chart like the below to illustrate which\r\nnametable goes to which slot:\r\n\r\n   [  $2000  ][  $2400  ]\r\n   [  $2800  ][  $2C00  ]\r\n\r\n\r\n  Most mappers which control mirroring usually pick from 2 to 4 standard mirroring configurations:\r\nHorizontal (\"Horz\"), Vertical (\"Vert\"), 1-Screen A (\"1ScA\"), and 1-Screen B (\"1ScB\").  These arrange the\r\nnametables like so:\r\n\r\n       Vert              Horz\r\n  --------------    --------------\r\n  [ NTA ][ NTB ]    [ NTA ][ NTA ]\r\n  [ NTA ][ NTB ]    [ NTB ][ NTB ]\r\n\r\n       1ScA              1ScB\r\n  --------------    --------------\r\n  [ NTA ][ NTA ]    [ NTB ][ NTB ]\r\n  [ NTA ][ NTA ]    [ NTB ][ NTB ]\r\n\r\n\r\n  A few mappers also support 4-screen mirroring, which uses 4 full nametables so that each slot has its own\r\nunique nametable.  Since the NES only has 2k for nametables, for a game to have 4-screen mirroring,\r\nadditional VRAM must be present on the cartridge.  I only know of a grand total of three games which use\r\n4-screen mirroring, and they will be mentioned in their respective docs.\r\n\r\n\r\n\r\nSwap Charts\r\n----------------------------\r\n\r\n  PRG/CHR swapping schemes are generally outlined in a chart.  A PRG chart might look like so:\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    | $7EFA | $7EFC | $7EFE | { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\n  This indicates which register is used to select a PRG page for which region.  In this example, the register\r\nat $7EFA selects an 8k page for $8000-9FFF.\r\n\r\n  Numbers surrounded by {curly braces} mean the page is fixed.  Here, $E000-FFFF is fixed to page -1.\r\nNegative pages indicate the last pages are used.  IE:  \"-1\" means to use the last page of PRG, \"-2\" would be\r\nthe second last, etc.\r\n\r\n  CHR charts work similarly:\r\n\r\n      $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n    +---------------+---------------+-------+-------+-------+-------+\r\n    |    <$7EF0>    |    <$7EF1>    | $7EF2 | $7EF3 | $7EF4 | $7EF5 |\r\n    +---------------+---------------+-------+-------+-------+-------+\r\n\r\n  \r\n  Here, the register at $7EF3 selects a 1k CHR page for $1400-17FF, while $7EF0 selects a 2k CHR page for\r\n$0000-07FF.\r\n\r\n  Numbers surrounded by <> symbols indicate the low bits of the given page number are ignored.  This is\r\ntypical where a mapper deals with several different page sizes.  For example, $7EF0 selects a 2k page, but\r\nits low bit is ignored (effectively, you must right-shift its value by 1 for the actual page number).\r\n\r\n  Example:  if $7EF0=$05, 2k page $02 would be selected ($05 right shift 1 = $02)\r\n\r\n  Double <>'s (example:  \"<<$7EF0>>\") would mean the low 2 bits are ignored (right shift the value by 2).\r\n\r\n  Numbers without <> symbols are referred to as \"actual\" page numbers.\r\n\r\n  Charts may have multiple rows if there are multiple swapping modes.\r\n\r\n\r\n\r\nErroneous noob swapping\r\n----------------------------\r\n  Some newbies tend to make an understandable, but incorrect assumption about how swapping works.  Given the\r\nfollowing CHR chart:\r\n\r\n               $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00 \r\n             +---------------+---------------+-------+-------+-------+-------+\r\nCHR Mode 0:  |     <R:0>     |     <R:1>     |  R:2  |  R:3  |  R:4  |  R:5  |\r\n             +---------------+---------------+---------------+---------------+\r\nCHR Mode 1:  |  R:2  |  R:3  |  R:4  |  R:5  |     <R:0>     |     <R:1>     |\r\n             +-------+-------+-------+-------+---------------+---------------+\r\n\r\n  A newbie might think that they can cleverly manipulate modes to select 1k pages across the board, rather\r\nthan having those two 2k chunks in there.  IE:  They think that they can set R:2-R:5 in mode 0, then switch\r\nto mode 1, set R:2-R:5 again... and that would select each 1k page individually.\r\n\r\n  This, of course, is not now it works.  \"Swapping\" isn't actually swapping.  What's actually happening is\r\nwhen the NES reads from a certain address the high bits of the address are being replaced by the contents\r\nof a mapper register.  Because of this, mapper registers (and swapping modes) are accessed constantly at\r\nruntime... not just when the value is written.\r\n\r\n  For example... setting R:2 in mode 0, then switching to mode 1 will have the exact same effect as switching\r\nto mode 1 first, then setting R:2.  Both methods end up in mode 1, and both set only R:2... meaning the end\r\nresult is selecting a 1k page at $0000.\r\n\r\n  You might say a second \"swap\" occurs when the mode is changed.  That is... if a game were to change modes,\r\nthey would see the pattern tables \"flip\", even though they didn't swap anything.\r\n\r\n\r\n\r\nRegister bit layouts\r\n----------------------------\r\n\r\n  Registers often have different bits of the written value do different things.  Or sometimes only some bits\r\nare significant and others are ignored.  In these situations, bitfields are indicated by a pair of brackets.\r\nExample:\r\n\r\n  $8000:  [CP.. .AAA]\r\n\r\n  The above shows 3 seperate things ('A', 'P', and 'C') that the register controls, and which bits are\r\nassigned to those things.  Bits marked as '.' are irrelevent and unused.  These bits are listed high bit\r\nfirst (here, 'C' would be bit 7)\r\n\r\n  Some mappers (usually multicarts) also take bits from the address written to -- not just the value\r\nwritten.  These instances will be marked with brackets with \"A~\" before them.  A good example of this:\r\n\r\n  $8000-FFFF:    [.... ..CC]\r\n               A~[..MH HPPP PPO. CCCC]\r\n\r\n  The first bracket represents the value written, and the second bracket (with the A~) represents the address\r\nwritten to.\r\n\r\n\r\nAddress/Data ports\r\n----------------------------\r\n\r\n  Many mappers have several registers which are accessed by writing an address to one area, then writing the\r\ndata you want to write to the reg to another area.  The most common example of this is MMC3 (mapper 004).\r\n$8000 is the address port, and $8001 is the data port.\r\n\r\n  Since $8001 actually accesses 8 different registers, $8001 can't appear in charts and descriptions and\r\nstuff.  So for address/data ports like this, the accessed registers are referred to as \"R:#\"  (where # is the\r\nhex address by which they're accessed).  For example MMC3's 8 regs would be \"R:0\" through \"R:7\".\r\n\r\n  For example, if a game wanted to change R:4, it would do the following:\r\n\r\nLDA #$04\r\nSTA $8000  ; set address to $04\r\nLDA whatever\r\nSTA $8001  ; since address is $04, this sets R:4\r\n\r\n\r\n\r\nTiming / Dots\r\n----------------------------\r\n\r\n  When discussing the timing of PPU triggered IRQs, I refer to 'dots'.  IE:  \"The IRQ will fire on dot 260 of\r\nthe scanline\".  'Dots' are otherwise known as PPU Cycles.  Each scanline consists of 341 dots -- and on NTSC,\r\nthere are 3 dots to every 1 CPU cycle.\r\n\r\n\r\n\r\nBus Conflicts\r\n-----------------------------\r\n\r\n  Some simple mappers suffer from bus conflicts.  This means that when registers share CPU space with PRG,\r\nthe value you write to the address must match what is read from that address or bad things will happen!\r\n\r\n\r\nMany games do this by having a LUT of common values somewhere and indexing it:\r\n\r\n     Swap_LUT:\r\n     .db $00, $01, $02, $03, $04, $05, $06, $07\r\n\r\n     PRG_Swap:\r\n         ;  assume A is the desired page to swap to (00-07)\r\n       TAX\r\n       STA Swap_LUT,X\r\n       RTS\r\n\r\nThis ABSOLUTELY NEEDS TO BE DONE for these mappers!  Do not try to shortcut this!  You will break your ROM!\r\n\r\nI'm sure I missed some mappers that have bus conflicts -- but I tried to mark all the ones I know do, and\r\nsuspect might.\r\n\r\nWhen a bus conflicting write occurs, the result is usually an AND of the two potential values -- but such\r\nbehavior should not be relied on.\r\n\r\n\r\n\r\n\r\nRegister Masking / Ranges\r\n------------------------------\r\n\r\n  Many times, a single register can be accessed by several addresses.  For example when you see something\r\nlike:\r\n\r\n  $8000-FFFF:   PRG Reg\r\n\r\n  That means a write to anywhere between $8000-FFFF will access the PRG Reg.\r\n\r\n  In that same vein, sometimes not all address lines are used when decoding which register is to be accessed.\r\nThat is, some bits of the address don't matter.  This creates a masking effect where registers are mirrored\r\nin a semi-weird fashion accross an address range.  This would be marked in docs with something like:\r\n\r\n\r\nRange,Mask:   $8000-FFFF, $E001\r\n\r\n\r\nThis would mean that within the range $8000-FFFF, you'd use $E001 as a mask for determining which register to\r\nuse.  IE:  $D3F7 would mirror $C001, because $D3F7 AND $E001 = $C001.\r\n\r\n\r\n\r\nPRG/CHR Masking\r\n------------------------------\r\n\r\n  When a game selects a page higher than there is ROM for, the page number would be masked to select an\r\nappropriate page.  For example... if a game only has $08 pages of CHR, and it selects page $0A, then it would\r\nactually select page $02 (because $0A AND ($08-1) = $02).\r\n\r\n  In that same vein... fixed \"last pages\" {-1}, {-2}, etc are really pages $FFFF, $FFFE, etc -- and the mask\r\nhappens to make that select the last or second last page.\r\n\r\n  This is why PRG/CHR sizes must always be a power of 2, except in extremely rare cases where there's an odd\r\nnumber of chips (and those cases are handled specially by the mapper).\r\n\r\n\r\n\r\nPowerup/Reset\r\n-----------------------------\r\n\r\n  Do not assume the state of anything at startup.  Mapper registers, like RAM, contain pseudo-random garbage\r\non system powerup, except in special cases, which will be noted in the appropriate docs.\r\n\r\n  If no such note is made, you cannot assume anything.\r\n\r\n\r\n\r\nPRG-AND, PRG-OR, Blocks, etc\r\n-----------------------------\r\n\r\n  Multicarts (and even some single game carts) employ a type of block system which lets the game choose a\r\nblock, and then will only swap to pages within that block.  In these docs I often illustrate this with PRG-OR\r\nand PRG-AND values.\r\n\r\n  For an example, let's say you have a game with the following PRG pages selected:\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    |  $02  |  $16  | { -2} | { -1} |\r\n    +-------+-------+-------+-------+\r\n\r\n  And let's say the PRG-AND is $0F, and the PRG-OR is $20.  This would result in the following pages being\r\nselected:\r\n\r\n      $8000   $A000   $C000   $E000  \r\n    +-------+-------+-------+-------+\r\n    |  $22  |  $26  |  $2E  |  $2F  |\r\n    +-------+-------+-------+-------+\r\n\r\n  simply, it's  \"(desiredpage AND PRGAND) OR PRGOR\".  Note that even the fixed pages are affected by this.\r\n\r\n  CHR-AND and CHR-OR operate the same way, but with CHR pages.\r\n\r\n  Always apply these values *before* any downshifting caused by <> symbols.\r\n\r\nBregalad's notice\r\n-----------------\r\n\r\nI imported changes that were made from Disch's original documents and compiled them into a new version.\r\nDisch himself clarified that changes to his own work were welcome in a NESDEV forums post : http://forums.nesdev.com/viewtopic.php?f=3&t=10275\r\nHe also said he keeps his distance to emulation scene and is probably not going to ever update this himself, so that is the reason for me to update the doccuments instead.\r\nI did not do any of these changes, rather, they were made by NESDEV wiki members such as lidnariq, Rainwarrior, and tepples (among possibly others).\r\nThe details are in the changelog. I just reviewed them and integrated them here in order to keep the documents up to date.\r\n\r\nFinally I'd want to say a big thank to disch and congratulations for doing such a huge work of documenting so many mappers at the same time,\r\nwhile keeping it coherent and very comprehensive.\r\n"
  },
  {
    "path": "docs/mapper/changes.txt",
    "content": "v0.7 (Bregalad)\r\n================================\r\n- Imported changes from NESDEV wiki:\r\n     - mapper 005 : - Correct info for mode B CHR switching in 8k mode\r\n                    - Add info about Bandits king of Ancient China's emulation\r\n                    - Add info for sound\r\n                    - Fixes misspelling\r\n     - mapper 021 : A fixes about IRQ operation\r\n     - mapper 022 : Combined with mapper 025\r\n     - mapper 032 : A game requires an NES2.0 sumbapper, not all registers uses all bits\r\n     - mapper 072 : Major changes in explanation how the command/data works\r\n     - mapper 076 : Major changes in PRG banking explanation\r\n     - mapper 077 : Major changes in how CHR-RAM and NT-RAM works\r\n     - mapper 080 : Added info about 128 bytes internal RAM\r\n     - mapper 085 : Added extended information about sound (also by Disch).\r\n     - mapper 091 : One more example game\r\n     - mapper 113 : Changed example games\r\n     - mapper 184 : High bit of 4 is always set in hardware\r\n     - mapper 185 : Add note about open bus pattern table\r\n     - mapper 207 : Is mapper 080 with mirroring handled differently\r\n     - mapper 234 : Better wording of explanations of the strange way the regs are accessed\r\n- Mappers 072, 077, 078, 089, 093, 094, 152, 180 and 185 are confirmed to have bus conflicts\r\n- Did some spelling and corrects two errors in mapper 001\r\n\r\nv0.6.1\r\n==================================\r\n- Fixed minor goof in 189\r\n- made 4x4 bit RAM more clear in 225,228\r\n- fixed various spelling goofs\r\n- added info about vertical scrolling in 005's split screen mode\r\n- added some info about a common noob error in the main readme\r\n- added technical correction in 005 about PRG-RAM chip select\r\n- fixed some mapper 034 stuff (BxROM and compatible)\r\n- fixed mapper 067 IRQ info.  Had first/second writes backwards.\r\n- fixed mapper 073 IRQ info.  Had modes backwards.\r\n- fixed 049 -- had PRG modes mixed up\r\n- fixed error in 090 -- had CHR block modes backwards\r\n- fixed 203\r\n- additional notes and crap for 233\r\n- alternative freq formula for N106 audio was wrong.  Fixed it.\r\n- clarified 230\r\n- changed D:# naming convention to R:# to avoid confusion\r\n     (d is often used for bit positions)\r\n\r\nStill 111 mapper numbers  (more coming, I swear!)\r\n\r\n\r\nv0.6\r\n==================================\r\nFirst real release (111 mapper numbers)"
  },
  {
    "path": "docs/mapper/mmc3_irq_tests_readme.txt",
    "content": "NTSC NES MMC3 IRQ Counter Test ROMs\n-----------------------------------\nThese ROMs test much of MMC3 IRQ counter behavior on an NTSC NES PPU.\nThey have been tested on an actual NES with on several MMC3 cartridges\nand all give a passing result. Many tests are written specifically to\ncatch likely errors in an emulator.\n\nEach ROM runs several tests and reports the result on screen and by\nbeeping a number of times. Failure codes for each ROM are listed below.\nIt's best to run the tests in order, because some earlier ROMs test\nthings that later ones assume will work properly.\n\nThe ROMs mainly test behavior by manually clocking the MMC3's IRQ\ncounter by writing to $2006 to change the current VRAM address. The last\ntwo ROMs test different revisions of the MMC3, so at most only one will\npass on a particular emulator.\n\nAll the asm source is included, and most tests are clearly divided into\nsections. The code runs on a custom devcart and assembler so it will\nrequire some effort to assemble. Contact me if you'd like assistance\nporting them to your setup.\n\n\nMMC3 Operation\n--------------\nI have fairly thoroughly tested MMC3 IRQ counter operation and found the\nfollowing behaviors that differ as described in kevtris's (draft?) MMC3\ndocumentation:\n\n- The counter can be clocked manually via bit 12 of the VRAM address\neven when $2000 = $00 (bg and sprites both use tiles from $0xxx).\n\n- The IRQ flag is not set when the counter is cleared by writing to\n$C001.\n\n- I uncovered some pathological behavior that isn't covered by the test\nROMs. If $C001 is written, the counter clocked, then $C001 written\nagain, on the next counter clock the counter will be ORed with $80\n(revision B)/frozen (revision A) and neither decremented nor reloaded.\nIf $C001 is written again at this point, on the next counter clock it\nwill be reloaded normally. I put a check in my emulator and none of the\nseveral games I tested ever caused this situation to occur, so it's\nprobably not a good idea to implement this.\n\nThe MMC3 in Crystalis (referred to here as revision A) worked as\ndescribed in kevtris's document, with the above changes. The MMC3 in\nSuper Mario Bros. 3 and Mega Man 3 (I think revision B, but I don't have\nthe special screw driver) further differed when $C000 was written with\n0:\n\n- Writing 0 to $C000 works no differently than any other value written;\nit will cause the counter to be reloaded every time it is clocked (once\nit reaches zero).\n\n- When the counter is clocked, if it's not zero, it is decremented,\notherwise it is reloaded with the last value written to $C000. *After*\ndecrementing/reloading, if the counter is zero and IRQ is enabled via\n$E001, the IRQ flag is set.\n\n\n1.Clocking\n----------\nTests counter operation. Requires support for clocking via manual\ntoggling of VRAM address.\n\n2) Counter/IRQ/A12 clocking isn't working at all\n3) Should decrement when A12 is toggled via $2006\n4) Writing to $C000 shouldn't cause reload\n5) Writing to $C001 shouldn't cause immediate reload\n6) Should reload (no decrement) on first clock after clear\n7) IRQ should be set when counter is decremented to 0\n8) IRQ should never be set when disabled\n9) Should reload when clocked when counter is 0\n\n\n2.Details\n---------\nTests counter details.\n\n2) Counter isn't working when reloaded with 255\n3) Counter should run even when IRQ is disabled\n4) Counter should run even after IRQ flag has been set\n5) IRQ should not be set when counter reloads with non-zero\n6) IRQ should not be set when counter is cleared via $C001\n7) Counter should be clocked 241 times in PPU frame\n\n\n3.A12 Clocking\n--------------\nTests clocking via bit 12 of VRAM address.\n\n2) Shouldn't be clocked when A12 doesn't change\n3) Shouldn't be clocked when A12 changes to 0\n4) Should be clocked when A12 changes to 1 via $2006 write\n5) Should be clocked when A12 changes to 1 via $2007 read\n6) Should be clocked when A12 changes to 1 via $2007 write\n\n\n4.Scanline Timing\n-----------------\nTests basic timing for scanlines 0, 1, and 240.\n\n2) Scanline 0 time is too soon\n3) Scanline 0 time is too late\n4) Scanline 1 time is too soon\n5) Scanline 1 time is too late\n6) Scanline 239 time is too soon\n7) Scanline 239 time is too late\n\n\n5.MMC3 Rev A\n------------\nTests MMC3 revision A differences (tested with Crystalis board).\n\n2) IRQ should be set when reloading to 0 after clear\n3) IRQ shouldn't occur when reloading after counter normally reaches 0\n\n\n6.MMC3 Rev B\n------------\nTests MMC3 revision B differences (tested with Super Mario Bros. 3 and\nMega Man 3 boards).\n\n2) Should reload and set IRQ every clock when reload is 0\n3) IRQ should be set when counter is 0 after reloading\n\n-- \nShay Green <hotpop.com@blargg> (swap to e-mail)\n"
  },
  {
    "path": "docs/mapper/mmc3_test_readme.txt",
    "content": "NES MMC3 Tests\r\n--------------\r\nThese tests verify a small part of MMC3 (and some MMC6) behavior, mostly\r\nrelated to the scanline counter and IRQ. They should be run in order.\r\n\r\nThe ROMs mainly test behavior by manually clocking the MMC3's IRQ\r\ncounter by writing to $2006 to change the current VRAM address. The last\r\ntwo ROMs test behavior that differs among MMC3 chips.\r\n\r\n\r\nMMC3 Operation\r\n--------------\r\nI have fairly thoroughly tested MMC3 IRQ counter operation and found the\r\nfollowing behaviors that differ as described in kevtris's (draft?) MMC3\r\ndocumentation:\r\n\r\n- The counter can be clocked manually via bit 12 of the VRAM address\r\neven when $2000 = $00 (bg and sprites both use tiles from $0xxx).\r\n\r\n- The IRQ flag is not set when the counter is cleared by writing to\r\n$C001.\r\n\r\n- I uncovered some pathological behavior that isn't covered by the test\r\nROMs. If $C001 is written, the counter clocked, then $C001 written\r\nagain, on the next counter clock the counter will be ORed with $80\r\n(revision B)/frozen (revision A) and neither decremented nor reloaded.\r\nIf $C001 is written again at this point, on the next counter clock it\r\nwill be reloaded normally. I put a check in my emulator and none of the\r\nseveral games I tested ever caused this situation to occur, so it's\r\nprobably not a good idea to implement this.\r\n\r\nThe MMC3 in Crystalis (referred to here as revision A) worked as\r\ndescribed in kevtris's document, with the above changes. The MMC3 in\r\nSuper Mario Bros. 3 and Mega Man 3 (I think revision B, but I don't have\r\nthe special screw driver) further differed when $C000 was written with\r\n0:\r\n\r\n- Writing 0 to $C000 works no differently than any other value written;\r\nit will cause the counter to be reloaded every time it is clocked (once\r\nit reaches zero).\r\n\r\n- When the counter is clocked, if it's not zero, it is decremented,\r\notherwise it is reloaded with the last value written to $C000. *After*\r\ndecrementing/reloading, if the counter is zero and IRQ is enabled via\r\n$E001, the IRQ flag is set.\r\n\r\n\r\nMulti-tests\r\n-----------\r\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\r\nWhen run, they list the subtests as they are run. The final result code\r\nrefers to the first sub-test that failed. For more information about any\r\nfailed subtests, run them individually from rom_singles/ and\r\nnsf_singles/.\r\n\r\n\r\nFlashes, clicks, other glitches\r\n-------------------------------\r\nIf a test prints \"passed\", it passed, even if there were some flashes or\r\nodd sounds. Only a test which prints \"done\" at the end requires that you\r\nwatch/listen while it runs in order to determine whether it passed. Such\r\ntests involve things which the CPU cannot directly test.\r\n\r\n\r\nAlternate output\r\n----------------\r\nTests generally print information on screen, but also report the final\r\nresult audibly, and output text to memory, in case the PPU doesn't work\r\nor there isn't one, as in an NSF or a NES emulator early in development.\r\n\r\nAfter the tests are done, the final result is reported as a series of\r\nbeeps (see below). For NSF builds, any important diagnostic bytes are\r\nalso reported as beeps, before the final result.\r\n\r\n\r\nOutput at $6000\r\n---------------\r\nAll text output is written starting at $6004, with a zero-byte\r\nterminator at the end. As more text is written, the terminator is moved\r\nforward, so an emulator can print the current text at any time.\r\n\r\nThe test status is written to $6000. $80 means the test is running, $81\r\nmeans the test needs the reset button pressed, but delayed by at least\r\n100 msec from now. $00-$7F means the test has completed and given that\r\nresult code.\r\n\r\nTo allow an emulator to know when one of these tests is running and the\r\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\r\n$G1 is written to $6001-$6003.\r\n\r\n\r\nAudible output\r\n--------------\r\nA byte is reported as a series of tones. The code is in binary, with a\r\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\r\nThe first tone is always a zero. A final code of 0 means passed, 1 means\r\nfailure, and 2 or higher indicates a specific reason. See the source\r\ncode of the test for more information about the meaning of a test code.\r\nThey are found after the set_test macro. For example, the cause of test\r\ncode 3 would be found in a line containing set_test 3. Examples:\r\n\r\n\tTones         Binary  Decimal  Meaning\r\n\t- - - - - - - - - - - - - - - - - - - - \r\n\tlow              0      0      passed\r\n\tlow high        01      1      failed\r\n\tlow high low   010      2      error 2\r\n\r\n\r\nNSF versions\r\n------------\r\nMany NSF-based tests require that the NSF player either not interrupt\r\nthe init routine with the play routine, or if it does, not interrupt the\r\nplay routine again if it hasn't returned yet. This is because many tests\r\nneed to run for a while without returning.\r\n\r\nNSF versions also make periodic clicks to prevent the NSF player from\r\nthinking the track is silent and thus ending the track before it's done\r\ntesting.\r\n\r\n-- \r\nShay Green <gblargg@gmail.com>\r\n"
  },
  {
    "path": "docs/memory_mapping.txt",
    "content": "                     **************************************\n                     **                                  **\n                     **        NES Memory Mapping        **\n                     **           version 1.0            **\n                     **                                  **\n                     **             by Disch!            **\n                     **************************************\n\n===================================\nIntroduction\n===================================\n\n    There are a bunch of docs around which cover 6502 and asm hacking in NES\ngames.  They do a good job of showing how you can modify existing game code to\nmake it do something new.  Some even cover how to insert your own code and jump\nto it, allowing you to add new stuff to your hack.  But one related area which\nis crucial to really understanding how to do all this is either only briefly\ntouched on with little explanation, or is not touched on at all.  Leaving the\nreader with many unanswered questions.\n\n    I'm talking, of course, about mappers.  People hear terms like \"mapper\",\nand \"PRG swapping\" thrown around...  but not everyone understands what they do\nor how they work.  If you're one of these people, hopefully this doc will shed\nsome light on the subject.\n\n    This doc primarily focuses on the PRG/CHR swapping aspect of mappers.\nOther aspects of mappers (such as IRQ generation, mirroring control, sound,\netc) are not covered in this document.\n\n\n===================================\nWhat should you know?\n===================================\n\n    This doc does not cover 6502.  It doesn't assume you know any 6502, however\nI doubt much of the information here will be very useful without it.\n\n    This doc will assume you know nothing about NES architecture, and will\ncover some basic ares that relate to NES<->Mapper interaction.\n\n    However... despite it not relying on you having much previous experience,\nthe information gained will probably be useless unless you've been around the\nblock a few times.\n\n\n===================================\nROM offsets vs. CPU Addresses\n===================================\n\n    ROM offsets and CPU Addresses are two distinctly different things.  ROM\noffsets indicate a position the .nes file -- and CPU addresses indicate an\narea in the NES CPU memory map.  Mappers are the key to translating between\nthe two.\n\n    In this doc... ROM offsets will always be prefixed with '0x', and CPU\naddresses will always be prefixed with '$'.\n\n    Tangent note:  some docs and tutorials erroneously call CPU addresses \"RAM\noffsets\" (and in fact, it says this in FCEUXD as well).  I want to punch these\nauthors, as that term is false and misleading (you're not always dealing with\nRAM, and it doesn't really offset anything).  I urge you to refrain from using\nthese terms, and to just say \"address\" for CPU addresses and \"offset\" for ROM\noffsets.  It may not seem like a big deal, but it may ultimately lead to\nconfusion when you really do need to differentiate between what's RAM and what\nisn't (and when you actually do have to deal with real RAM offsets).\n\n===================================\nA look at the NES Memory Map\n===================================\n\n    The first step to understanding the mapper's primary function (memory\nmapping) is to examine the basic NES memory Map.  You may have seen similar\ncharts in other NES docs, but they usually clump the cartridge and NES\ntogether:\n\n     NES CPU Memory Map:\n     +-------------+----------------------------+\n     | Address     |  Mapped to                 |\n     +-------------+----------------------------+\n     |$0000 - $1FFF|  System RAM                |\n     |$2000 - $3FFF|  PPU Registers             |\n     |$4000 - $401F|  CPU/pAPU/Joypad Registers |\n     |$4020 - $FFFF|  Cartridge                 |\n     +-------------+----------------------------+\n        The NES cannot address anything higher than $FFFF\n\n     Typical Cartridge memory map:\n     +-------------+----------------------------------+\n     | Address     |  Mapped to                       |\n     +-------------+----------------------------------+\n     |$4020 - $5FFF|  Unused                          |\n     |$6000 - $7FFF|  PRG-RAM (aka SRAM)              |\n     |$8000 - $FFFF|  PRG-ROM (ie:  the actual game)  |\n     +-------------+----------------------------------+\n\n    This is how the vast majority of NES games look.  The NES CPU Memory Map\nas listed above is *always* the same.  However, the cartridge memory map can\nvary wildly according to the cartridge layout and the mapper.\n\n    What the hell does this mean?  Well this means that any time the game reads\nor writes to an address below $4020, it's doing something local to the NES.\nHowever when it reads/writes to an address above $4020, it's accessing\nsomething on the cartridge and is subject to mapper intervention:\n\nLDA $0300   ;  reading from NES system RAM\nLDA $8123   ;  reading from cartridge (typically PRG-ROM)\n\n\n===================================\nA look at .nes ROM files\n===================================\n\n    The next step to understanding how mappers map cartridge information is\nunderstand what we'll be mapping.  This is actually pretty straightforward...\nthe .nes file contains 3 main parts:\n\n\n1)  iNES Header\n2)  PRG-ROM\n3)  CHR-ROM  (assuming the game has any)\n\n    PRG and CHR ROM sizes are indicated by the header.\n\n    The iNES header is $10 bytes in size, which means that PRG-ROM starts at\noffset 0x00010 in the .nes file.\n\n\n\n===================================\nWhy a mapper is needed\n===================================\n\n    For our first example... let's take a look at a small, simple game like\nSuper Mario Bros.  This ROM has 32K PRG.  You can see this in FCEUXD by loading\nthe ROM, then going to Help | Message Log (it will say \"2 x 16KiB\")\n\n    32K is $8000 bytes.  This fits PERFECTLY into the typical cartridge memory\nmap.  For this paticular situation... a mapper isn't needed, because all of\nthe PRG can fit into the allotted space at address range $8000-$FFFF.  This\nmeans that when the game is reading from address $8000, it's reading offset\n0x00010 (the first byte of PRG-ROM).\n\n    Note, however... that this is ALL of the space in the CPU memory map that\nis designated for PRG-ROM.  So how on Earth are games able to fit in more PRG?\nThis is accomplished by a technique performed by mappers called PRG swapping.\n\n    But before we get into PRG swapping... let's start with something a little\neasier to visualize....\n\n\n===================================\nCHR Swapping\n===================================\n\n    CHR Swapping is the same concept as PRG swapping, only it deals with CHR\ninstead of PRG.  This makes it a bit easier to visualize.\n\n    A good game to use as an example is Super Mario Bros. 2.  Load up this ROM\nin FCEUXD, start the first level, and open up the PPU Viewer (Tools | PPU\nViewer).  At the same tile, load up the ROM in your favorite tile editor and\njump to offset 0x26010\n\n    Notice how in FCEUXD, the tiles on the bottom of the right-hand pattern\ntable are animating.  By looking at how the tiles are layed out in your tile\neditor, you should be able to see that the game is just cycling through a list\nof tiles.\n\n    This actually is a great demonstration of what swapping is and how it\nworks.  Instead of looking at the pattern tables and seeing a bunch of\ntiles... try to look at it and see \"slots\":\n\n   +----------------------------++----------------------------+\n   |           Slot 0           ||           Slot 4           |\n   |                            ||                            |\n   +----------------------------++----------------------------+\n   |           Slot 1           ||           Slot 5           |\n   |                            ||                            |\n   +----------------------------++----------------------------+\n   |           Slot 2           ||           Slot 6           |\n   |                            ||                            |\n   +----------------------------++----------------------------+\n   |           Slot 3           ||           Slot 7           |\n   |                            ||                            |\n   +----------------------------++----------------------------+\n\n    Each slot holds 4 rows of tiles (64 tiles)... this represents 1k ($400\nbytes) of CHR.  Now the NES cannot \"see\" more than 8k at a time (the above 8\nslots).  However... games can have more graphics by putting different CHR\n\"pages\" in those slots.  When it wants to change which page is in the slot, it\n\"swaps in\" a new page.  The page that was previously visible is removed, and\nthe new page becomes visible.\n\n    SMB2 is constantly switching which CHR page is put in slots 6 and 7.  By\nconstantly swapping in new pages, it appears as though the tiles are animating.\nThis is a common animation technique used by NES games.\n\n    The pages themselves can be seen in a tile editor.  SMB2's CHR-ROM starts\nat offset 0x20010.  Which means:\n\npage $00 = 0x20010 - 0x2040F   (the first 64 tiles)\npage $01 = 0x20410 - 0x2080F   (the next 64 tiles)\npage $02 = 0x20810 - 0x20C0F\n...\npage $7F = 0x3FC10 - 0x4000F   (the last 64 tiles)\n  (the above assumes 1k pages)\n\n\n\nNote about CHR-RAM:\n   Some games use CHR-RAM instead of CHR-ROM (these games don't have any\nCHR-ROM, and all their graphics data is intermingled in the PRG.  Castlevania,\nFinal Fantasy are examples of these types of games).  For these games, the\nabove doesn't apply.  CHR-RAM is handled differently from CHR-ROM and is beyond\nthe scope of this document (it's more related to PPU registers than mapper\nstuff)\n\n===================================\nPRG Swapping\n===================================\n\n    Some people can pick up the concept of CHR swapping pretty quick, but then\nstruggle with PRG swapping.  Which is a little strange, because it's the EXACT\nsame concept.  Rather than looking at the $8000-$FFFF area in CPU addressing\nspace as a big chunk of PRG... look at it as a series of slots.  For another\nvisual of a typical NES CPU memory map:\n\n\n    $0000  +-----------------+      $8000  +-----------------+\n           |                 |             |                 |\n           |  System RAM     |             |  PRG Slot 0     |\n           |                 |             |                 |\n    $2000  +-----------------+      $A000  +-----------------+\n           |                 |             |                 |\n           |  PPU Registers  |             |  PRG Slot 1     |\n           |                 |             |                 |\n    $4000  +-----------------+      $C000  +-----------------+\n           |  CPU/etc Regs   |             |                 |\n    $5000  +-----------------+             |  PRG Slot 2     |\n           |  Unused         |             |                 |\n    $6000  +-----------------+      $E000  +-----------------+\n           |                 |             |                 |\n           |  SRAM           |             |  PRG Slot 3     |\n           |                 |             |                 |\n           +-----------------+             +-----------------+\n\n\n    Just like how in CHR swapping, slots are filled with pages of tiles... with\nPRG swapping, slots are filled by pages of PRG.\n\n    So while a game can only \"see\" 32k of PRG at any one time... it can still\naccess much more than 32k by swapping out different pages.\n\n    Just as CHR pages are stored in order in the .nes file, so are PRG pages:\n\npage $00 = 0x00010 - 0x0200F\npage $01 = 0x02010 - 0x0400F\npage $02 = 0x04010 - 0x0600F\n...\netc\n   (the above assumes 8k pages)\n\n\n\n\n===================================\nConverting ROM Offsets and CPU Addresses\n===================================\n\n    6502 code and NES games all deal with CPU addresses.  None of it deals with\nROM offsets.  This is why NES pointers appear so funky to newcomers.  People\nsometimes come across a pointer such as \"63 91\", and get confused when they\nfind that it actually points to offset 0x15173.  But when you consider\neverything mentioned above.. it really does make perfect sense:\n\n\n- \"63 91\" = $9163 (CPU address)\n- $9163 is in \"Slot 0\" ($8000-9FFF)\n- $9163 is $1163 bytes into the slot (slot starts at $8000)\n- for this to point to 0x15173... page $0A (assuming 8k pages) must be swapped\n     in slot 0:\n\n$0A * $2000 = 0x14000      (page# * page_size = start_of_page)\n0x14000 + $1163 = 0x15163  (start_of_page + bytes_into_slot = offset)\n0x15163 + 0x10 =  0x15173  (adjust for iNES header)\n\n    That same pointer, however, could point to 0x0D173 or 0x05173 or 0x11173.\nIt all depends on which page is swapped in.\n\n    So how do you find out which page is swapped in?  Well it's quite easy in\nFCEUXD.  Simply open up the hex editor (Tools | Hex Editor), go to View | NES\nMemory, go to the desired CPU address, right click on it, and select \"Go Here\nIn ROM File\".  The hex editor will flip over to ROM offset mode and will give\nyou the offset to the desired PRG.\n\n    Note, however, that games are CONSTANTLY swapping PRG pages.  It's not\nuncommon for games to swap pages multiple times every frame.  Therefore it's\nrecommended you snap the debugger at the appropriate time before finding which\npages are swapped in.  And remember that just because a page is swapped in now,\ndoesn't mean it will be swapped in later.\n\n\n===================================\nVariable slot sizes, swappable slots, and hardwired slots\n===================================\n\n    Up until now, I've been using 8k pages as examples.  This is because the\nmost common mapper (MMC3, mapper 4) uses 8k PRG pages.  However, other mappers\nuse different sizes.  Some mappers even let the game PICK the size!\n\n    Different size pages operate just like you may expect.  16k pages ($4000\nbytes) would be stored in the ROM as:\n\npage $00 = 0x00010 - 0x0400F\npage $01 = 0x04010 - 0x0800F\n... etc\n\n    And 32k pages ($8000 bytes -- yes, some mappers use pages this big) would\nbe stored as:\n\npage $00 = 0x00010 - 0x0800F\npage $01 = 0x08010 - 0x1000F\n... etc\n\n\n   Generally slots are arranged like so:\n\n            8k         16k         32k\n$8000   +--------+  +--------+  +--------+\n        |        |  |        |  |        |\n$A000   +--------+  |        |  |        |\n        |        |  |        |  |        |\n$C000   +--------+  +--------+  |        |\n        |        |  |        |  |        |\n$E000   +--------+  |        |  |        |\n        |        |  |        |  |        |\n        +--------+  +--------+  +--------+\n\n\n    Mappers don't always let the game select a page for every slot.  Some slots\nare fixed so that they always contain a specific page.  These slots are often\ncalled \"hardwired\" (even though that term is technically inaccurate... \"fixed\"\nis more appropriate).  Usually, the last slot is fixed to the last page of PRG,\nand cannot be swapped out.  This all depends on the mapper being used and\npossibly the settings of that mapper.\n\n\n\n===================================\nNitty Gritty specific mapper details\n===================================\n\n    Everything mentioned so far as been intentionally generic.  The concepts\nare sound and apply to every mapper out there.  However each individual mapper\ndoes vary slightly in slot arrangement, PRG/CHR swapping ability,\nswappable/fixed slots, and even additional features not mentioned in this doc.\nAnd while the info mentioned above is all sound... you're eventually going to\nneed to know the specifics.\n\n    So here is a short list of some common mappers and their arrangement.  This\nis by no means a complete list.  I have no intention of making a complete list.\nThere are sources for mapper details elsewhere -- if the mapper your game uses\nis not listed here, you'll have to find a different resource.\n\n    This list also does not go into details of mapper registers.  If you want\nto know how to manually swap out pages or do other things with the mapper,\nyou'd be best suited to find a technical doc on said mapper.\n\n\n    Finding out which mapper your game uses is pretty easy.  Many emulators\nwill tell you this information after the ROM is loaded.  In FCEUXD, once you\nload the ROM, go to Help | Message Log, and it will tell you the mapper number.\n\n\n************************\n*  Mapper 0  --  NROM  *\n************************\n\n    Mapper 0 is no mapper.  There is no PRG/CHR swapping ability.\n\n\n************************\n*  Mapper 1  --  MMC1  *\n************************\n\n    Mapper 1 lets the game choose between 3 different PRG swapping schemes.\n99% of games using this mapper use the first scheme listed:\n\nPRG:\n16k Slot @ $8000 = Swappable\n16k Slot @ $C000 = Fixed to last PRG page\n---------OR--------\n16k Slot @ $8000 = Fixed to first PRG page (page 0)\n16k Slot @ $C000 = Swappable\n---------OR--------\n32k Slot @ $8000 = Swappable\n\n    Mapper 1 also let's game switch between 2 different CHR swapping schemes.\nHowever, many games using this mapper use CHR-RAM.  For those that don't:\n\nCHR:\n4k Slot @ ppu$0000 = Swappable\n4k Slot @ ppu$1000 = Swappable\n---------OR--------\n8k Slot @ ppu$0000 = Swappable\n\n\n    There are a handful of rare exceptions which rewire MMC1 in funky ways.\nDragon Warrior 4, for instance, in a way breaks the above PRG swapping rules.\nFor these games, refer to an MMC1 technical doc.\n\n\n*************************\n*  Mapper 2  --  UxROM  *\n*************************\n\n    Mapper 2 is very simple.  Only 1 swapping scheme.  All games using this\nmapper have CHR-RAM, and thus do not swap CHR.\n\nPRG:\n16k Slot @ $8000 = Swappable\n16k Slot @ $C000 = Fixed to last PRG page\n\n\n*************************\n*  Mapper 4  --  MMC3   *\n*************************\n\n    MMC3 is hands down the most common mapper around.  Odds are whatever game\nyou're hacking (if it's a US release) is MMC3.  Most games using this mapper\nuse CHR-ROM and swap as indicated below:\n\nPRG:\n8k Slot @ $8000 = Swappable\n8k Slot @ $A000 = Swappable\n8k Slot @ $C000 = Fixed to 2nd last PRG page\n8k Slot @ $E000 = Fixed to last PRG page\n----------OR----------\n8k Slot @ $8000 = Fixed to 2nd last PRG page\n8k Slot @ $A000 = Swappable\n8k Slot @ $C000 = Swappable\n8k Slot @ $E000 = Fixed to last PRG page\n\n\nCHR:\n2k Slot @ ppu$0000 = Swappable\n2k Slot @ ppu$0800 = Swappable\n1k Slot @ ppu$1000 = Swappable\n1k Slot @ ppu$1400 = Swappable\n1k Slot @ ppu$1800 = Swappable\n1k Slot @ ppu$1C00 = Swappable\n----------OR----------\n1k Slot @ ppu$0000 = Swappable\n1k Slot @ ppu$0400 = Swappable\n1k Slot @ ppu$0800 = Swappable\n1k Slot @ ppu$0C00 = Swappable\n2k Slot @ ppu$1000 = Swappable\n2k Slot @ ppu$1800 = Swappable\n\n\n===================================\nOne final note\n===================================\n\n    When it comes to mappers... anything and everything goes.  A \"mapper\"\nis really a combination of cartridge layout, wiring, MMC, and anything\nadditional on the cart.  Theoretically, it is even possible for an\nadditional processor to be on the cartridge!\n\n    So while there are general traits that are shared by many mappers...\nthere are no \"rules\".  The charts and explanations should be absorbed\nwith the understanding that they are not carved in stone... and that there\nare mappers out there which deviate from the normal patterns.\n\n\n===================================\nThat's it!\n===================================\n\n    That's about everything I can think of.  Hopefully this will clarify some\nof the grey areas left by other docs.\n\n\n\n===================================\nVersion History\n===================================\n\nJune 25, 2007    -   version 1.0  -  Initial release"
  },
  {
    "path": "docs/nes_arch.txt",
    "content": "\n               Nintendo Entertainment System Architecture\n                        version 1.4 [09/09/1996]\n\n                 by Marat Fayzullin [fms@freeflight.com]\n                   WWW: http://www.freeflight.com/fms/\n                               IRC: RST38h\n\n\n  The following document describes the workings of Nintendo Entertainment\nSystem videogame console, also known as Famicom in the East (Korea, Japan),\nand Dandy in Europe (Russia, etc.). Note that this document is in no way\nbased on any official Nintendo information and may be incomplete and\nincorrect in many places. \"Nintendo Entertainment System\" and \"Famicom\" are\nregistered trademarks of Nintendo.\n\n  I would like to thank following people for their help in obtaining this\ninformation and writing a NES emulator, as well as the moral support from\nsome of them:\n\n  (sorted alphabetically)\n  Pascal Felber     Pan of Anthrox       John Stiles\n  Kawasedo          Patrick Lesaard      Tink\n  Marcel de Kogel   Paul Robson          Bas Vijfwinkel\n  Alex Krasivsky    Serge Skorobogatov\n\n  The current version of this file is missing some information, such as\nsound hardware. I will add these parts in later releases. If you have any\ninformation on NES, which is not in this manual, feel free to write to\nfms@freeflight.com. Your help will be appreciated. \n\n\n******************************* Contents *******************************\n\n  1. General Architecture\n  2. Interrupts\n  3. I/O Ports\n  4. PPU Memory\n  5. Hit/VBlank Bits\n  6. Joysticks\n  7. Sprites\n  8. Memory Mappers\n     a) Sequential\n     b) Konami\n     c) VROM Switch\n     d) 5202 Chip\n     e) Others\n  9. Sound (to be written)\n\n\n************************* General Architecture *************************\n\n  NES is based on the 6502 CPU, and a custom video controller known as PPU\n(Picture Processing Unit). The PPU's video memory is separated from the\nmain CPU memory and can be read/written via special ports. Cartridges may\ncontain both ROM appearing in the main CPU address space at $8000-$FFFF,\nand VROM or VRAM appearing in the PPU address space at $0000-$1FFF and\ncontaining the Pattern Tables (aka Tile Tables). In smaller cartridges,\nwhich only have 16kB ROM, it takes place at $C000-$FFFF leaving $8000-$BFFF\narea unused. Internal NES VRAM is located at addresses $2000-$3FFF in the \nPPU memory. Some cartridges also have RAM at $6000-$7FFF, which may or may\nnot be battery-backed. \n\nCPU Memory Map\n--------------------------------------- $10000\n Upper Bank of Cartridge ROM\n--------------------------------------- $C000\n Lower Bank of Cartridge ROM\n--------------------------------------- $8000\n Cartridge RAM (may be battery-backed)\n--------------------------------------- $6000\n Expansion Modules\n--------------------------------------- $5000\n Input/Output\n--------------------------------------- $2000\n 2kB Internal RAM, mirrored 4 times\n--------------------------------------- $0000\n\n\n****************************** Interrupts ******************************\n\n  NES uses non-maskable interrupts (NMIs) generated by PPU in the end of\neach frame (so-called VBlank interrupts). Maskable interrupts, or IRQs,\ncan also be generated by circuitry in a cart, but most carts do not\ngenerate them. The VBlank interrupts can be enabled/disabled by writing\n1/0 into 7th bit of $2000. When a VBlank interrupts occur, CPU pushes\nreturn address and the status register on stack, and jumps to the address\nstored at location $FFFA (ROM in NES). The interrupt handler is supposed\nto finish its execution with RTI command which returns CPU to the main\nprogram execution. More information on the interrupt handling can be found\nin a decent book on 6502 CPU. \n\n\n****************************** I/O ports *******************************\n\n   NES internal I/O ports are mapped into the areas of $2000-$2007 and \n$4000-$4017. Some ports' usage is unknown or unclear, and any information\nis appreciated.\n\nI/O Ports Map\n------+-----+---------------------------------------------------------------\n$2000 | RW  | PPU Control Register 1\n      | 0-1 | Name Table to show:\n      |     |\n      |     |           +-----------+-----------+\n      |     |           | 2 ($2800) | 3 ($2C00) |\n      |     |           +-----------+-----------+\n      |     |           | 0 ($2000) | 1 ($2400) |\n      |     |           +-----------+-----------+\n      |     |\n      |     | Remember, though, that because of the mirroring, there are  \n      |     | only 2 real Name Tables, not 4.\n      |   2 | Vertical Write, 1 = PPU memory address increments by 32:\n      |     |\n      |     |    Name Table, VW=0          Name Table, VW=1\n      |     |   +----------------+        +----------------+\n      |     |   |----> write     |        | | write        |\n      |     |   |                |        | V              |\n      |     |\n      |   3 | Sprite Pattern Table address, 1 = $1000, 0 = $0000\n      |   4 | Screen Pattern Table address, 1 = $1000, 0 = $0000\n      |   5 | Sprite Size, 1 = 8x16, 0 = 8x8\n      |   6 | Hit Switch, 1 = generate interrupts on Hit (incorrect ???)\n      |   7 | VBlank Switch, 1 = generate interrupts on VBlank\n------+-----+---------------------------------------------------------------\n$2001 | RW  | PPU Control Register 2\n      |   0 | Unknown (???)\n      |   1 | Image Mask, 0 = don't show left 8 columns of the screen\n      |   2 | Sprite Mask, 0 = don't show sprites in left 8 columns \n      |   3 | Screen Switch, 1 = show picture, 0 = blank screen\n      |   4 | Sprites Switch, 1 = show sprites, 0 = hide sprites\n      | 5-7 | Unknown (???)\n------+-----+---------------------------------------------------------------\n$2002 | R   | PPU Status Register\n      | 0-5 | Unknown (???)\n      |   6 | Hit Flag, 1 = PPU refresh has hit sprite #0\n      |     | This flag resets to 0 when VBlank starts, or CPU reads $2002\n      |     | (see \"Hit/VBlank Bits\").\n      |   7 | VBlank Flag, 1 = PPU is generating a Vertical Blanking Impulse\n      |     | This flag resets to 0 when VBlank ends, or CPU reads $2002\n      |     | (see \"Hit/VBlank Bits\").\n------+-----+---------------------------------------------------------------\n$2003 | W   | Sprite Memory Address\n      |     | Used to set the address in the 256-byte Sprite Memory to be \n      |     | accessed via $2004. This address will increment by 1 after\n      |     | each access to $2004. The Sprite Memory contains coordinates,\n      |     | colors, and other attributes of the sprites (see \"Sprites\").\n------+-----+---------------------------------------------------------------\n$2004 | RW  | Sprite Memory Data\n      |     | Used to read/write the Sprite Memory. The address is set via\n      |     | $2003 and increments after each access. The Sprite Memory \n      |     | contains coordinates, colors, and other attributes of the \n      |     | sprites (see \"Sprites\").\n------+-----+---------------------------------------------------------------\n$2005 | W   | Background Scroll\n      |     | There are two scroll registers, vertical and horizontal, \n      |     | which are both written via this port. The first value written\n      |     | will go into the Vertical Scroll Register (unless it is >239,\n      |     | then it will be ignored). The second value will appear in the\n      |     | Horizontal Scroll Register. The Name Tables are assumed to be\n      |     | arranged in the following way:\n      |     |\n      |     |           +-----------+-----------+\n      |     |           | 2 ($2800) | 3 ($2C00) |\n      |     |           +-----------+-----------+\n      |     |           | 0 ($2000) | 1 ($2400) |\n      |     |           +-----------+-----------+\n      |     |\n      |     | When scrolled, the picture may span over several Name Tables.\n      |     | Remember, though, that because of the mirroring, there are\n      |     | only 2 real Name Tables, not 4.\n------+-----+---------------------------------------------------------------\n$2006 |     | PPU Memory Address\n      |     | See \"PPU Memory\".\n------+-----+---------------------------------------------------------------\n$2007 |     | PPU Memory Data\n      |     | See \"PPU Memory\".\n------+-----+---------------------------------------------------------------\n$4000-$4013 | Sound Registers\n            | See \"Sound\".\n------+-----+---------------------------------------------------------------\n$4014 | W   | DMA Access to the Sprite Memory\n      |     | Writing a value N into this port, causes an area of CPU memory\n      |     | at address $100*N to be transferred into the Sprite Memory.\n------+-----+---------------------------------------------------------------\n$4015 | W   | Sound Switch\n      |   0 | Channel 1, 1 = enable sound\n      |   1 | Channel 2, 1 = enable sound\n      |   2 | Channel 3, 1 = enable sound\n      |   3 | Channel 4, 1 = enable sound\n      |   4 | Channel 5, 1 = enable sound\n      | 5-7 | Unused (???)\n------+-----+---------------------------------------------------------------\n$4016 | RW  | Joystick 1 + Strobe\n      |   0 | Joystick 1 data\n      |   1 | Joystick 1 presence, 0 = connected\n      | 2-5 | Unused, set to 0 (???)\n      | 6-7 | Unknown, set to 10 (???)\n      |     | See \"Joysticks\".\n------+-----+---------------------------------------------------------------\n$4017 | R   | Joystick 2\n      |   0 | Joystick 2 data\n      |   1 | Joystick 2 presence, 0 = connected\n      | 2-5 | Unused, set to 0 (???)\n      | 6-7 | Unknown, set to 10 (???)\n      |     | See \"Joysticks\".\n------+-----+---------------------------------------------------------------\n\n\n****************************** PPU Memory ******************************\n\n  In a real NES, reading/writing PPU memory should only be attempted\nduring VBlank period. Many smaller ROMs have read-only memory (VROM) for\nthe Pattern Tables. In this case, you won't be able to write into this\nmemory. The $3F00 and $3F10 locations in VRAM mirror each other (i.e. it\nis the same memory cell) and define the background color of the picture. \n\n   Writing to PPU memory:\n   a) Write upper address byte into $2006\n   b) Write lower address byte into $2006\n   c) Write data into $2007. After each write, the address will\n      increment either by 1 (bit 2 of $2000 is 0) or by 32 (bit 2 of\n      $2000 is 1).\n\n   Reading from PPU memory:\n   a) Write upper address byte into $2006\n   b) Write lower address byte into $2006\n   c) Read data from $2007. The first byte read from $2007 will be\n      invalid. Then, the address will increment by 1 after each\n      read.\n\n  Name Table contains tile numbers organized into 32 rows of 32 bytes\neach. Tiles are 8x8 pixels each. Therefore, the whole Name Table is 32x32\ntiles or 256x256 pixels. In the NTSC version of NES, upper and lower 16\npixels are not shown, thus, the screen becomes 256x224 pixels. In the PAL\nversion of NES, upper and lower 8 pixels are not show, thus, the screen\nbecomes 256x240 pixels.\n\n  Pattern Table contains tile images in the following format:\n\n  Character   Colors      Contents of Pattern Table\n  ...o....    00010000    00010000 $10  +-> 00000000 $00\n  ..O.O...    00202000    00000000 $00  |   00101000 $28\n  .0...0..    03000300    01000100 $44  |   01000100 $44\n  O.....O.    20000020    00000000 $00  |   10000010 $82\n  ooooooo. -> 11111110    11111110 $FE  |   00000000 $00\n  O.....O.    20000020    00000000 $00  |   10000010 $82\n  0.....0.    30000030    10000010 $82  |   10000010 $82\n  ........    00000000    00000000 $00  |   00000000 $00\n                              +---------+\n\n  Note that only two bits for each pixel of a character are stored in the\nPattern Table. Other two are taken from the Attribute Table. Thus, the total\nnumber of simultaneous colors on the NES screen is 16.\n\n  Each byte in the Attribute Table represents a 4x4 group of tiles on the \nscreen, which makes an 8x8 attribute table. Each 4x4 tile group is\nsubdivided into four 2x2 squares as follows:\n\n   (0,0)  (1,0) 0|  (2,0)  (3,0) 1\n   (0,1)  (1,1)  |  (2,1)  (3,1)\n   --------------+----------------\n   (0,2)  (1,2) 2|  (2,2)  (3,2) 3\n   (0,3)  (1,3)  |  (2,3)  (3,3)\n\n  The attribute byte contains upper two bits of the color number for each\n2x2 square (the lower two bits are stored in the Pattern Table): \n\n  Bits   Function                        Tiles\n  --------------------------------------------------------------\n  7,6    Upper color bits for square 3   (2,2),(3,2),(2,3),(3,3)    \n  5,4    Upper color bits for square 2   (0,2),(1,2),(0,3),(1,3)\n  3,2    Upper color bits for square 1   (2,0),(3,0),(2,1),(3,1)\n  1,0    Upper color bits for square 0   (0,0),(1,0),(0,1),(1,1)\n   \n  There are two 16-byte Palette Tables: the one at $3F00, used for the\npicture, and another one at $3F10, containing the sprite palette. The\n$3F00 and $3F10 locations in VRAM mirror each other (i.e. it is the same\nmemory cell) and define the background color of the picture. \n\n  There is only enough VRAM for 2 Name Tables and Attribute Tables. Two\nothers are going to be mirrors of the first two, i.e. exact copies of them.\nWhich pages are mirrored depends on the cartridge circuitry. With vertical\nmirroring, tables 2 and 3 are the mirrors of pages 0 and 1 appropriately.\nWith horizontal mirroring, pages 1 and 3 are the mirrors of pages 0 and 2\nappropriately.\n\nPPU Memory Map\n--------------------------------------- $4000\n Empty\n--------------------------------------- $3F20\n Sprite Palette\n--------------------------------------- $3F10\n Image Palette\n--------------------------------------- $3F00\n Empty\n--------------------------------------- $3000\n Attribute Table 3\n--------------------------------------- $2FC0\n Name Table 3 (32x25 tiles)\n--------------------------------------- $2C00\n Attribute Table 2\n--------------------------------------- $2BC0\n Name Table 2 (32x25 tiles)\n--------------------------------------- $2800\n Attribute Table 1\n--------------------------------------- $27C0\n Name Table 1 (32x25 tiles)\n--------------------------------------- $2400\n Attribute Table 0\n--------------------------------------- $23C0\n Name Table 0 (32x25 tiles)\n--------------------------------------- $2000\n Pattern Table 1 (256x2x8, may be VROM)\n--------------------------------------- $1000\n Pattern Table 0 (256x2x8, may be VROM)\n--------------------------------------- $0000\n\n\n*************************** Hit/VBlank Bits ****************************\n\n   The VBlank flag is contained in the 7th bit of read-only location $2002. \nIt indicates whether PPU is scanning the screen, or generating a vertical\nblanking impulse. It is set in the end of each frame (scanline 232), and\nstays on until the next screen refresh starts from the scanline 8. The\nprogram can reset this bit prematurely by reading from $2002.\n\n   The Hit flag is contained in the 6th bit of read-only location $2002. \nIt goes to 1 when PPU starts refreshing the first scanline where sprite#0\nis located. For example, if sprite#0's Y coordinate is 34, the Hit flag\nwill be set in scanline 34. The Hit flag is reset when vertical blanking\nimpulse starts. The program can reset this bit prematurely by reading from\n$2002. \n\n\n******************************* Joysticks ******************************\n\n  There are two joysticks which are accessed via locations $4016 and\n$4017. To reset joysticks, write first 1, then 0 into $4016. This way, you\nwill generate a strobe in the joysticks' circuitry. Then, read either from\n$4016 (for joystick 0) or from $4017 (for joystick 1).  Each read will\ngive you the status of a single button in the 0th bit (1 if pressed, 0\notherwise): \n\nRead # |    1      2      3      4      5      6      7      8\n-------+---------------------------------------------------------\nButton |    A      B   SELECT   START   UP    DOWN   LEFT  RIGHT\n\n  Bit 1 indicates whether joystick is connected to the port or not. It is\nset to 0 if the joystick is connected, 1 otherwise. Bits 6 and 7 of\n$4016/$4017 also seem to have some significance, which is not clear yet.\nThe rest of bits is set to zeroes. Some games expect to get *exactly* $41\nfrom $4016/$4017, if a button is pressed, which has to be taken into\naccount. \n\n\n******************************* Sprites ********************************\n\n  There are 64 sprites, which can be either 8x8 or 8x16 pixels. Sprites\npatterns are stored in on of the Pattern Tables in the PPU Memory. Sprite\nattributes are stored in the Sprite Memory of 256 bytes, which is not a\npart of neither CPU nor PPU address space. The entire contents of Sprite\nMemory can be written via DMA transfer using location $4014 (see above).\nSprite Memory can also be accessed byte-by-byte by putting the starting\naddress into $2003 and then writing/reading $2004 (the address will be\nincremented after each access). The format of sprite attributes is as\nfollows: \n\nSprite Attribute RAM:\n| Sprite#0 | Sprite#1 | ... | Sprite#62 | Sprite#63 |\n     |          |\n     +---- 4 bytes: 0: Y position of the left-top corner - 1\n                    1: Sprite pattern number\n                    2: Color and attributes:\n                       bits 1,0: two upper bits of color\n                       bits 2,3,4: Unknown (???)\n                       bit 5: if 1, display sprite behind background\n                       bit 6: if 1, flip sprite horizontally\n                       bit 7: if 1, flip sprite vertically\n                    3: X position of the left-top corner\n\nSprite patterns are fetched in the exactly same way as the tile patterns\nfor the background picture. The only difference occurs in the 16x8\nsprites: the top half of the sprite is taken from the Sprite Pattern Table\nset in the $2000 port, while the bottom part is taken from the same\nlocation of the alternative Pattern Table. Therefore, if PPU is displaying\na 16x8 sprite, and the Sprite Pattern Table is set to $1000, the bottom\nhalf of this sprite will be taken out of the $0000 Pattern Table, and vice\nversa. \n\n\n**************************** Memory Mappers ****************************\n\n  There are many diffirent memory mappers (aka MMCs) used in the NES \ncartridges. They are used to switch ROM and VROM pages, and do some other \ntasks. I will only describe the MMCs I'm familiar with. Any new \ninformation on these and other MMCs is highly appreciated. The MMC \nnumbers are given in terms of the .NES file field \"Mapper Type\".\n\n\n1.  Mapper #1, Sequential\n\n  This is a sequential mapper used in many 256kB cartridges, such as\nBomberman 2, Destiny Of The Emperor, Megaman 2, Airwolf, Operation Wolf,\nCastlevania 2, Silk Worm, Yoshi, Break Thru. It may be used to switch ROM\nand VROM. If there is no VROM, 8kB of VRAM is present at $0000. In some\ncases (mostly RPG games) such cartridges also contain battery-backed RAM\nat $6000-$7FFF. The mapper has four 5bit registers, which are accessed via\nfollowing addresses: \n\nRegister  Address Range  Function\n---------------------------------------------------------------------------\n0         $8000-$9FFF    Mirroring and VROM Page Size select\n          The 0th bit of this register selects the mirroring type (1 for\n          horizontal, 0 for vertical). The 4th bit selects the size of\n          VROM pages. When it is 1, two 4kB VROM pages can be switched\n          independently at $0000 and $1000. Otherwise, there is a single\n          8kB VROM page at $0000.\n\n1         $A000-$BFFF    VROM page select\n          This register sets either 8kB or 4kB VROM page at $0000,\n          depending on the page size selected via register 0.\n\n2         $C000-$DFFF    Second VROM page select for 4kB pages\n          If 4kB VROM pages selected via register 0, this register sets\n          the VROM page at $1000. Otherwise, its value is ignored.\n\n3         $E000-$FFFF    ROM page select\n          This register sets 16kB ROM page at $8000. The page at $C000 is \n          always hardwired to the last ROM page in the cartridge. The \n          cartridge starts with page 0 at $8000.\n---------------------------------------------------------------------------\n\n  In order to write to a mapper register, write $80 into any of the\nlocations first. This will reset the mapper. Then write the value bit by\nbit into an appropriate address range. For example, the following assembly\ncode will write $0C into register 3: \n\n     lda  #$80     ; Resetting mapper\n     sta  $8000    ;\n     lda  #$0C     ; This is our value\n     sta  $EFD9    ; Writing bit 0\n     lsr  a        ; Shifting\n     sta  $EFD9    ; Writing bit 1\n     lsr  a        ; Shifting\n     sta  $EFD9    ; Writing bit 2\n     lsr  a        ; Shifting\n     sta  $EFD9    ; Writing bit 3\n     lsr  a        ; Shifting\n     sta  $EFD9    ; Writing bit 4\n\n\n2. Mapper #2, Konami\n\n  This is a quite simple mapper used in most Konami (Life Force,\nCastlevania, Metal Gear) and some other cartridges. It only switches the\nROM. All cartridges with this mapper have 8kB VRAM at $0000 (i.e. no\nVROM). The mapper has a single 8bit register which can be written via\nlocations $8000-$FFFF. It contains a number of 16kB ROM page at $8000. \nThe page at $C000 is always hardwired to the last ROM page in the\ncartridge. The cartridge starts with page 0 at $8000.\n\n  There is one more thing to note about this mapper: although any address\nin the $8000-$FFFF range can be used to access the mapper, most games\nprefer to use the address with the last digit equal to the value they\nwrite out. Thus, $07 can be written to $9FF7, $05 to $9FF5, and so forth.\nThe reason for this is unknown. \n\n\n3. Mapper #3, VROM Switch\n\n  Mapper #3, also known as a VROM switch, is used in the Goonies series\nand many Japanese-only games. It only allows you to switch 8kB pages of\nVROM. The ROM is either 16kB or 32kB and is not paged. The mapper has a\nsingle 8bit register which can be written via locations $8000-$FFFF. It\ncontains a number of 8kB VROM page at $0000. \n\n  As with mapper #2, many games use locations with the last digit equal to\nthe value being written. I do not know why. \n\n\n4. Mapper #4, 5202 Chip (???)\n\n  This mapper (or should I say 'an expansion chip'?) is used in many\nrecent cartridges, such as Batman Returns, Super Contra, Vindicators,\nSilver Surfer, etc. It is an extremely complicated device, which is able\nto generate its own interrupts via IRQ line, and has a set of commands to\nswitch ROM and VROM. VROM pages are 1kB, ROM pages appear to be 8kB. I do\nnot completely understand how this mapper works, so any information is\nappreciated. \n\n  The chip is controlled via following locations:\n\nAddress  Function\n---------------------------------------------------------------------------\n$8000    A command number (0-7) is written here. Also, write to this \n         register appears to reset the change made by a write into $E000.\n$8001    An value for command is written here.\n$A000    The 0th bit controls mirroring (1 = horizontal mirroring).\n$A001    Same as $8001 (???)\n$C000    Unknown\n$C001    Unknown\n$E000    The 5th bit appears to swap memory at $8000-$8FFF and \n         $A000-$AFFF, when set to 1.\n$E001    Unknown\n---------------------------------------------------------------------------\n\n  In order to use the mapper, you should first write a command number \ninto $8000, and then a value (page number) into $8001. Following commands \nexist:\n\nCmd  Function\n---------------------------------------------------------------------------\n 0   Select 2 consequent 1kB VROM pages at $0000. The 0th bit of a value\n     written into $8001 does not matter, i.e. 5 will always select pages\n     4 and 5.\n 1   Select 2 consequent 1kB VROM pages at $0800. The 0th bit of a value\n     written into $8001 does not matter, i.e. 5 will always select pages\n     4 and 5.\n 2   Select a 1kB VROM page at $1000.\n 3   Select a 1kB VROM page at $1400.\n 4   Select a 1kB VROM page at $1800.\n 5   Select a 1kB VROM page at $1C00.\n 6   Select a 8kB ROM page at $8000. The initial value seems to be 0.\n 7   Select a 8kB ROM page at $A000. The initial value seems to be 1.\n---------------------------------------------------------------------------\n\n  Note that the ROM pages at $C000 and $E000 are hardwired to the last \npages of the ROM, and can not be switched (they can be swapped via \n$E000 though).\n\n\n5. Other mappers\n\n  There are several other mappers, some of them very sophisticated. iNES\npartially supports them, but as this support either doesn't work\ncorrectly, or the mappers are uncommon (such as 100-in-1 cartridge mapper,\nI don't cover them here. \n\n\n******************************** Sound *********************************\n\n  To be written.\n\n\n\n---------------------\nMarat Fayzullin\n"
  },
  {
    "path": "docs/nes_graphics.txt",
    "content": "How NES Graphics work\nThe Basics\n---------------------\n\n        All the graphical information is stored within the 16kb memory of\nthe Picture Processing Unit (PPU).\n\n        The first area of PPU memory is known as the \"Pattern Tables.\"  The\npattern tables are 8kb in size, which is usually split in half, one part\nfor the background, the other for sprites.  The usage of the pattern tables\nis determined by the PPU control registers.  All graphics are stored in 8x8\npixel \"tiles\" within the pattern tables which are arranged to form\nbackgrounds and sprites.  The pattern tables (also known as VROM) contains\nhalf the graphics information for these tiles.  The NES is capable of\ndisplaying 16 colors at once, since each pixel is 4 bits that are a lookup of\nthe palette.  The pattern table contains the low two bits, and the upper two\neither come from the attribute table (for the background) or from sprite\nram (for sprites).\n\n        Some roms contain what is known as CHR-RAM.  CHR-RAM stands for\nCHaRacter RAM and contains 8kb banks of pattern tables which can be swapped\nin and out of the PPU via an MMC.  This saves the trouble of copying it\nfrom within the program code to the PPU like games without CHR-RAM do.\n\n        The second area of PPU memory is known as \"Name Tables.\"  There are\nusually two, although there are addresses for 4.  The other two are mirrors\nof the actual tables, which is determined by the mirroring bit of the .NES\nheader.  Each name table is 960 bytes, which corresponds with 960 8x8 tiles\nthat make up the background.  The background is 32x30 tiles, or 256x240 pixels.\nIn a NTSC NES, however, the top and bottom 8 lines are not displayed, making\nthe actual resolution 256x224.  Note that some games use only one name table,\nand others can use 4.  These settings are dependant on the memory mapper used.\n\n        Paired with each name table is an attribute table.  These tables\ncontain the upper two bits of each pixel's color that are matched with the\nlower two bits from the pattern table.  However, the attribute table is only\n64 bytes in size, meaning that each byte contains the upper two bits for a\ngroup of 4x4 tiles (or 32x32 pixels).  This puts a limitation on your choice\nof colors in the background.  However, you can get around the attribute table\nlimitation by using MMC5, which allows you to use 4 name tables and the\nupper two bits for each individual tile.  For more information, read\nY0SHi's very informative NESTECH.DOC\n\n        Also stored in PPU memory are two 16 color palettes.  One is used for\nthe background, the other for sprites.  These are not actual rgb palettes, but\nlookup tables of a 256 color palette that the PPU translates into tv signals.\nYou can modify the values stored in the palette, allowing you to create many\neffects such as fades, flashing, or transparency.\n\n        There is an independent area of memory known as sprite ram, which is\n256 bytes in size.  This memory stores 4 bytes of information for 64 sprites.\nThese 4 bytes contain the x and y location of the sprite on the screen, the\nupper two color bits, the tile index number (pattern table tile of the sprite),\nand information about flipping (horizontal and vertical), and priority\n(behind/on top of background).  Sprite ram can be accessed byte-by-byte through\nthe NES registers, or also can be loaded via DMA transfer through another\nregister.\n\n        There are various other aspects of the PPU that can be controlled via\nseveral nes registers.  Here's a list of registers and what they do:\n\nPPU Control register 1 (PPUCTRL0):\n* Selects the Name table to display\n* Sets the ppu address increment (for reading/writing)\n* Sets the address within the pattern table for sprite tiles\n* Sets the address within the pattern table for background tiles\n* Selects the sprite size (8x8 or 8x16)\n* Sets whether to execute an interupt when drawing sprite 0\n* Sets whether to execute an interupt during the Vblank period\n\nPPU Control register 2 (PPUCTRL1):\n* Sets the disply to color or mono-tone\n* Sets whether to clip the left 8 pixels of the background\n* Sets whether to clip sprites within the left 8 pixels of the background\n* Sets whether to display the screen or not\n* Selects the screen background color (black, red, blue, green)\n\nPPU Status register (PPUSTAT):\n* Returns whether PPU is in a Vblank period\n* Returns whether there are more than 8 sprites on the current scanline\n* Returns whether sprite 0 has been drawn on the current scanline\n\nBackground Scroll (BGSCROLL):\n* Sets the horizontal and vertical scroll (written to twice)\n"
  },
  {
    "path": "docs/nes_tech.txt",
    "content": "+---------------------------------------------+\n| Nintendo Entertainment System Documentation |\n|                                             |\n| Version: 2.00                               |\n+---------------------------------------------+\n\n+-------------------+\n| Table of Contents |\n+-------------------+\n\n        1. Introduction\n          A. Disclaimer\n          B. Why?\n          C. Mission\n          D. Dedications\n          E. \"Thank You\"s\n        2. Acronymns\n          A. Internals\n          B. Hardware\n        3. CPU\n          A. General Information\n          B. Memory Map\n          C. Interrupts\n          D. NES-specific Customizations\n          E. Notes\n        4. PPU\n          A. General Information\n          B. PPU Map\n          C. Name Tables\n          D. Pattern Tables\n          E. Attribute Tables\n          F. Palettes\n          G. Name Table Mirroring\n          H. Palette Mirroring\n          I. Background Scrolling\n          J. Screen and Sprite Layering\n          K. Sprites and SPR-RAM\n          L. Sprite #0 Hit Flag\n          M. Horizontal and Vertical Blanking\n          N. $2005/2006 Magic\n          O. VRAM Read/Writes\n          P. Notes\n        5. pAPU\n        6. Joypads, paddles, expansion ports\n          A. General Information\n          B. The Zapper\n          C. Four-player devices\n          D. Paddles\n          E. Power Pad\n          F. R.O.B. (Robot Operated Buddy)\n          G. Signatures\n          H. Expansion ports\n          I. Notes\n        7. Memory Mapping Hardware\n        8. Registers\n        9. File Formats\n          A. iNES Format (.NES)\n       10. Programming the NES\n          A. General Information\n          B. CPU Notes\n          C. PPU Notes\n       11. Emulation\n          A. General Information\n          B. CPU Notes\n          C. PPU Notes\n          D. APU Notes\n       12. Reference Material\n          A. CPU Information\n          B. PPU Information\n          C. APU Information\n          D. Memory Mapper Information\n          E. Mailing Lists\n          F. WWW Sites\n          G. Hardware Information\n\n\n\n+-----------------+\n| 1. Introduction |\n+-----------------+\n  \n  A. Disclaimer\n  -------------\n    I am in no way held responsible for the results of this information.\n    This is public-domain information, and should not be used for commercial\n    purposes.  If you wish to use this document for commercial purposes,\n    please contact me prior to development, so that I may discuss the outline\n    of your project with you. I am not trying to hinder anyone financially:\n    if you wish to do real Nintendo Entertainment System development, con-\n    tacting either Nintendo of America or Nintendo Company, Ltd. would be\n    wise. Their addresses are listed here:\n\n      Nintendo of America               Nintendo Company, Ltd.\n      P.O. Box 957                      60 Fukuine\n      Redmond, WA 98073                 Kamitakamatsu-cho,\n      USA                               Higashiyama-ku,\n                                        Koyoto 602, Japan\n\n    All titles of cartridges and console systems are registered trademarks\n    of their respective owners. (I just don't deem it necessary to list\n    every single one by hand).\n\n\n  B. Why?\n  -------\n    At the time this document was created, there was only one piece of lit-\n    erature covering the internals to the NES: Marat Fayzullin's documen-\n    tation, otherwise known as \"NES.DOC\".\n\n    While Fayzullin's documentation was lacking in many areas, it provided a\n    strong base for the basics, and in itself truly stated how complex the\n    little grey box was.\n\n    I took the opportunity to expand on \"NES.DOC,\" basing other people's\n    findings, as well as my own, on experience; experience which has helped\n    make this document what it has become today. The beginning stages of this\n    document looked almost like a replica of Fayzullin's documentation, with\n    both minor and severe changes. Marat Fayzullin himself later picked up a\n    copy of my documentation, and later began referring people to it.\n\n    Keep in mind, without Marat's \"NES.DOC\" document, I would have never had\n    any incentive to write this one.\n\n\n  C. Mission\n  ----------\n    The goal of this document is simplistic: to provide accurate and up-to-\n    date information regarding the Nintendo Entertainment System, and it's\n    Famicom counterpart.\n\n\n  D. Dedications\n  --------------\n    I'd like to dedicate this document to Alex Krasivsky. Alex has been a\n    great friend, and in my eyes, truly started the ball of emulation\n    rolling. During the good times, and the bad times, Alex was there.\n    Spasibo, Alex; umnyj Russki...\n\n\n  E. \"Thank You\"s\n  ---------------\n    I'd like to take the time and thank all the people who helped make this\n    document what it is today. I couldn't have done it without all of you.\n\n      Alex Krasivsky            - bcat@lapkin.rosprint.ru\n      Andrew Davie\n      Avatar Z                  - swahlen@nfinity.com\n      Barubary                  - barubary@mailexcite.com\n      Bluefoot                  - danmcc@injersey.com\n      CiXeL\n      Chi-Wen Yang              - yangfanw@ms4.hinet.net\n      Chris Hickman             - typhoonz@parodius.com\n      D                         - slf05@cc.usu.edu\n      Dan Boris                 - dan.boris@coat.com\n      David de Regt             - akilla@earthlink.net\n      Donald Moore              - moore@futureone.com\n      Fredrik Olsson            - flubba@hem2.passagen.se\n      Icer Addis                - bldlust@maelstrom.net\n      Jon Merkel                - jpm5974@omega.uta.edu\n      Kevin Horton              - khorton@iquest.net\n      Loopy                     - zxcvzxcv@netzero.net\n      Marat Fayzullin           - fms@cs.umd.edu\n      Mark Knibbs               - mark_k@iname.com\n      Martin Nielsen            - mnielsen@get2net.dk\n      Matt Conte                - itsbroke@classicgaming.com\n      Matthew Richey            - mr6v@andrew.cmu.edu\n      Memblers                  - 5010.0951@tcon.net\n      MiKael Iushin             - acc@tulatelecom.ru\n      Mike Perry                - mj-perry@uiuc.edu\n      Morgan Johansson          - morgan.johansson@mbox301.swipnet.se\n      Neill Corlett             - corlett@elwha.nrrc.ncsu.edu\n      Pat Mccomack              - splat@primenet.com\n      Patrik Alexandersson      - patrikus@hem2.passagen.se\n      Paul Robson               - AutismUK@aol.com\n      Ryan Auge                 - rauge@hay.net\n      Stumble                   - stumble@alpha.pulsar.net\n      Tennessee Carmel-Veilleux - veilleux@ameth.org\n      Thomas Steen              - Thomas.Steen@no.jotankers.com\n      Tony Young                - KBAAA@aol.com\n      Vince Indriolo            - indriolo@nm.picker.com\n      \\FireBug\\                 - lavos999@aol.com\n\n    Special thanks goes out to Stumble, for providing a myriad of infor-\n    mation over IRC, while avoiding sleep to do so.\n\n\n+--------------+\n| 2. Acronymns |\n+--------------+\n\n  A. Internals\n  ------------\n    CPU     - Central Processing Unit: Self-explanitory. The NES uses a\n              standard 6502 (NMOS).\n    PPU     - Picture Processing Unit: Used to control graphics, sprites,\n              and other video-oriented features.\n    pAPU    - pseuedo-Audio Processing Unit: Native to the CPU; generates\n              waveforms via (5) audio channels:: four (4) analogue, and\n              one (1) digital. There is no physical IC for audio process-\n              ing nor generation inside the NES.\n    MMC     - Multi-Memory Controller: Micro-controllers used in NES games\n              to access memory beyond the 6502 64Kbyte boundary. They can\n              also be used to access extra CHR-ROM, and may be used for\n              \"special effects\" such as forcing and IRQ, and other things.\n    VRAM    - Video RAM: The RAM which is internal to the PPU. There is\n              16kbits of VRAM installed in the NES.\n    SPR-RAM - Sprite RAM: 256 bytes of RAM which is used for sprites. It is\n              not part of VRAM nor ROM, though it's local to the PPU.\n    PRG-ROM - Program ROM: The actual program-code area of memory. Also can\n              be used to describe areas of code which are external to the\n              actual code area and are swapped in via an MMC.\n    PRG-RAM - Program RAM: Synonymous with PRG-ROM, except that it's RAM.\n    CHR-ROM - Character ROM: The VRAM data which is kept external to the PPU,\n              swapped in and out via an MMC, or \"loaded\" into VRAM during the\n              power-on sequence.\n    VROM    - Synonymous with CHR-ROM.\n    SRAM    - Save RAM: RAM which is commonly used in RPGs such as \"Cry-\n              stalis,\" the Final Fantasy series, and \"The Legend of Zelda.\"\n    WRAM    - Synonymous with SRAM.\n    DMC     - Delta Modulation Channel: The channel of the APU which handles\n              digital data. Commonly referred to as the PCM (Pulse Code\n              Modulation) channel.\n    EX-RAM  - Expansion RAM: This is the memory used within Nintendo's MMC5,\n              allowing games to extend the capabilities of VRAM.\n\n\n  B. Hardware\n  -----------\n    NES     - Nintendo Entertainment System: Self-explanitory.\n    Dandy   - Synonymous (hardware-wise) with the Famicom.\n    Famicom - Synonymous with the NES, except for not supporting the RAW\n              method of DMC digital audio playback.\n    FDS     - Famicom Disk System: Unit which sits atop the Famicom, support-\n              ing the use of 3\" double-sided floppy disks for games.\n\n\n\n+--------+\n| 3. CPU |\n+--------+\n\n  A. General Information\n  ----------------------\n    The NES uses a customized NMOS 6502 CPU, engineered and produced by\n    Ricoh. It's primary customization adds audio.\n\n    The NTSC NES runs at 1.7897725MHz, and 1.773447MHz for PAL.\n\n\n  B. Memory Map\n  -------------\n    +---------+-------+-------+-----------------------+\n    | Address | Size  | Flags | Description           |\n    +---------+-------+-------+-----------------------+\n    | $0000   | $800  |       | RAM                   |\n    | $0800   | $800  | M     | RAM                   |\n    | $1000   | $800  | M     | RAM                   |\n    | $1800   | $800  | M     | RAM                   |\n    | $2000   | 8     |       | Registers             |\n    | $2008   | $1FF8 |  R    | Registers             |\n    | $4000   | $20   |       | Registers             |\n    | $4020   | $1FDF |       | Expansion ROM         |\n    | $6000   | $2000 |       | SRAM                  |\n    | $8000   | $4000 |       | PRG-ROM               |\n    | $C000   | $4000 |       | PRG-ROM               |\n    +---------+-------+-------+-----------------------+\n           Flag Legend: M = Mirror of $0000\n                        R = Mirror of $2000-2008 every 8 bytes\n                            (e.g. $2008=$2000, $2018=$2000, etc.)\n\n\n  C. Interrupts\n  -------------\n    The 6502 has three (3) interrupts: IRQ/BRK, NMI, and RESET.\n\n    Each interrupt has a vector. A vector is a 16-bit address which spec-\n    ifies a location to \"jump to\" when the interrupt is triggered.\n\n    IRQ/BRK is triggered under two circumstances, hence it's split name:\n    when a software IRQ is executed (the BRK instruction), or when a\n    hardware IRQ is executed (via the IRQ line).\n\n    RESET is triggered on power-up. The ROM is loaded into memory, and\n    the 6502 jumps to the address specified in the RESET vector. No registers\n    are modified, and no memory is cleared; these only occur during power-up.\n\n    NMI stands for Non-Maskable Interrupt, and is generated by each refresh\n    (VBlank), which occurs at different intervals depending upon the system\n    used (PAL/NTSC).\n\n    NMI is updated 60 times/sec. on NTSC consoles, and 50 times/sec on PAL.\n    Interrupt latency on the 6502 is seven (7) cycles; this means it takes\n    seven (7) cycles to move in and out of an interrupt.\n\n    Most interrupts should return using the RTI instruction. Some NES carts\n    do not use this method, such as SquareSoft's \"Final Fantasy 1\" title.\n    These carts return from interrupts in a very odd fashion: by manipul-\n    ating the stack by hand, and then doing an RTS. This is technically\n    valid, but morally shunned.\n\n    The aforementioned nterrupts have the following vector addresses,\n    mapped to areas of ROM:\n\n      $FFFA = NMI\n      $FFFC = RESET\n      $FFFE = IRQ/BRK\n\n    Interrupt priorities are as follows:\n\n      Highest = RESET\n                NMI\n       Lowest = IRQ/BRK\n\n\n  D. NES-specific Customizations\n  ------------------------------\n    The NES's 6502 does not contain support for decimal mode. Both the\n    CLD and SED opcodes function normally, but the 'd' bit of P is unused\n    in both ADC and SBC. It is common practice for games to CLD prior to\n    code execution, as the status of 'd' is unknown on power-on and on\n    reset.\n\n    Audio registers are mapped internal to the CPU; all waveform gener-\n    ation is done internal to the CPU as well.\n\n\n  E. Notes\n  --------\n    Please note the two separate 16K PRG-ROM segments; they may be linear,\n    but they play separate roles depending upon the size of the cartridge.\n    Some games only hold one (1) 16K bank of PRG-ROM, which should be\n    loaded into both $C000 and $8000.\n\n    Most games load themselves into $8000, using 32K of PRG-ROM space. The\n    first game to use this method is Super Mario Brothers. However, all\n    games wit more than one (1) 16K bank of PRG-ROM load themselves into\n    $8000 as well. These games use Memory Mappers to swap in and out PRG-ROM\n    data, as well as CHR-ROM.\n\n    When a BRK is encountered, the NES's 6502 pushes the CPU status flag\n    onto the stack with the 'b' CPU bit set. On an IRQ or NMI, the CPU\n    pushes the status flag onto the stack with the 'b' bit clear. This is\n    done because of the fact that a hardware IRQ (IRQ) and a software\n    IRQ (BRK) both share the same vector. For example, one could use the\n    following code to distinguish the difference between the two:\n\n      C134: PLA                 ; Copy CPU status flag\n      C135: PHA                 ; Return status flag to stack\n      C136: AND #$10            ; Check D4 ('b' CPU bit)\n      C138: BNE is_BRK_opcode   ; If set then it is a software IRQ (BRK)\n\n    Executing BRK inside of NMI will result in the pushed 'b' bit being\n    set.\n\n    The 6502 has a bug in opcode $6C (jump absolute indirect). The CPU does\n    not correctly calculate the effective address if the low-byte is $FF.\n    Example:\n\n      C100: 4F\n      C1FF: 00\n      C200: 23\n        ..\n      D000: 6C FF C1 - JMP ($C1FF)\n\n    Logically, this will jump to address $2300. However, due to the fact\n    that the high-byte of the calculate address is *NOT* increased on a\n    page-wrap, this will actually jump to $4F00.\n\n    It should be noted that page wrapping does *NOT* occur in indexed-\n    indirect addressing modes. Due to limitations of zero-page, all\n    indexed-indirect read/writes should apply a logical AND #$FF to\n    the effective address after calculation. Example:\n    \n      C000: LDX #3        ; Reads indirect address from $0002+$0003,\n      C002: LDA ($FF,X)   ;   not $0102+$0103.\n\n\n\n+--------+\n| 4. PPU |\n+--------+\n\n  A. General Information\n  ----------------------\n    Mirroring (also referred to as \"shadowing\") is the process of mapping\n    particular addresses or address ranges to other addresses/ranges via\n    hardware.\n\n\n  B. Memory Map\n  -------------\n    Included here are two (2) memory maps. The first is a \"RAM Memory\n    Map,\" which despite being less verbose describes the actual regions\n    which point to physical RAM in the NES itself. The second is a\n    \"Programmer Memory Map\" which is quite verbose and describes the\n    entire memory region of the NES and how it's used/manipulated.\n\n        RAM Memory Map\n      +---------+-------+--------------------+\n      | Address | Size  | Description        |\n      +---------+-------+--------------------+\n      | $0000   | $1000 | Pattern Table #0   |\n      | $1000   | $1000 | Pattern Table #1   |\n      | $2000   | $800  | Name Tables        |\n      | $3F00   | $20   | Palettes           |\n      +---------+-------+--------------------+\n\n\n        Programmer Memory Map\n      +---------+-------+-------+--------------------+\n      | Address | Size  | Flags | Description        |\n      +---------+-------+-------+--------------------+\n      | $0000   | $1000 | C     | Pattern Table #0   |\n      | $1000   | $1000 | C     | Pattern Table #1   |\n      | $2000   | $3C0  |       | Name Table #0      |\n      | $23C0   | $40   |  N    | Attribute Table #0 |\n      | $2400   | $3C0  |  N    | Name Table #1      |\n      | $27C0   | $40   |  N    | Attribute Table #1 |\n      | $2800   | $3C0  |  N    | Name Table #2      |\n      | $2BC0   | $40   |  N    | Attribute Table #2 |\n      | $2C00   | $3C0  |  N    | Name Table #3      |\n      | $2FC0   | $40   |  N    | Attribute Table #3 |\n      | $3000   | $F00  |   R   |                    |\n      | $3F00   | $10   |       | Image Palette #1   |\n      | $3F10   | $10   |       | Sprite Palette #1  |\n      | $3F20   | $E0   |    P  |                    |\n      | $4000   | $C000 |     F |                    |\n      +---------+-------+-------+--------------------+\n                          C = Possibly CHR-ROM\n                          N = Mirrored (see Subsection G)\n                          P = Mirrored (see Subsection H)\n                          R = Mirror of $2000-2EFF (VRAM)\n                          F = Mirror of $0000-3FFF (VRAM)\n\n\n  C. Name Tables\n  --------------\n    The NES displays graphics using a matrix of \"tiles\"; this grid is called\n    a Name Table. Tiles themselves are 8x8 pixels. The entire Name Table\n    itself is 32x30 tiles (256x240 pixels). Keep in mind that the displayed\n    resolution differs between NTSC and PAL units.\n\n    The Name Tables holds the tile number of the data kept in the Pattern\n    Table (continue on).\n\n\n  D. Pattern Tables\n  -----------------\n    The Pattern Table contains the actual 8x8 tiles which the Name Table\n    refers to. It also holds the lower two (2) bits of the 4-bit colour\n    matrix needed to access all 16 colours of the NES palette. Example:\n\n       VRAM    Contents of                     Colour \n       Addr   Pattern Table                    Result\n      ------ ---------------                  --------\n      $0000: %00010000 = $10 --+              ...1.... Periods are used to\n        ..   %00000000 = $00   |              ..2.2... represent colour 0.\n        ..   %01000100 = $44   |              .3...3.. Numbers represent\n        ..   %00000000 = $00   +-- Bit 0      2.....2. the actual palette\n        ..   %11111110 = $FE   |              1111111. colour #.\n        ..   %00000000 = $00   |              2.....2.\n        ..   %10000010 = $82   |              3.....3.\n      $0007: %00000000 = $00 --+              ........\n\n      $0008: %00000000 = $00 --+\n        ..   %00101000 = $28   |\n        ..   %01000100 = $44   |\n        ..   %10000010 = $82   +-- Bit 1\n        ..   %00000000 = $00   |\n        ..   %10000010 = $82   |\n        ..   %10000010 = $82   |\n      $000F: %00000000 = $00 --+\n\n    The result of the above Pattern Table is the character 'A', as shown\n    in the \"Colour Result\" section in the upper right.\n\n\n  E. Attribute Tables\n  -------------------\n    Each byte in an Attribute Table represents a 4x4 group of tiles on the\n    screen. There's multiple ways to describe what the function of one (1)\n    byte in the Attribute Table is:\n\n      * Holds the upper two (2) bits of a 32x32 pixel grid, per 16x16 pixels.\n      * Holds the upper two (2) bits of sixteen (16) 8x8 tiles.\n      * Holds the upper two (2) bits of four (4) 4x4 tile grids.\n\n    It's quite confusing; two graphical diagrams may help:\n\n      +------------+------------+\n      |  Square 0  |  Square 1  |  #0-F represents an 8x8 tile\n      |   #0  #1   |   #4  #5   |\n      |   #2  #3   |   #6  #7   |  Square [x] represents four (4) 8x8 tiles\n      +------------+------------+   (i.e. a 16x16 pixel grid)\n      |  Square 2  |  Square 3  |\n      |   #8  #9   |   #C  #D   |\n      |   #A  #B   |   #E  #F   |\n      +------------+------------+\n\n    The actual format of the attribute byte is the following (and corris-\n    ponds to the above example):\n\n       Attribute Byte\n         (Square #)\n      ----------------\n          33221100\n          ||||||+--- Upper two (2) colour bits for Square 0 (Tiles #0,1,2,3)\n          ||||+----- Upper two (2) colour bits for Square 1 (Tiles #4,5,6,7)\n          ||+------- Upper two (2) colour bits for Square 2 (Tiles #8,9,A,B)\n          +--------- Upper two (2) colour bits for Square 3 (Tiles #C,D,E,F)\n\n\n  F. Palettes\n  -----------\n    The NES has two 16-colour \"palettes\": the Image Palette and the Sprite\n    Palette. These palettes are more of a \"lookup table\" than an actual\n    palette, since they do not hold physical RGB values.\n\n    D7-D6 of bytes written to $3F00-3FFF are ignored.\n\n\n  G. Name Table Mirroring\n  -----------------------\n    One should keep in mind that there are many forms of mirroring when\n    understanding the NES. Some methods even use CHR-ROM-mapped Name\n    Tables (mapper-specific).\n\n    The NES itself only contains 2048 ($800) bytes of RAM used for Name\n    Tables. However, as shown in Subsection B, the NES has the capability\n    of addressing up to four (4) Name Tables.\n\n    By default, many carts come with \"horizontal\" and \"vertical\" mirroring,\n    allowing you to change where the Name Tables point into the NES's PPU\n    RAM. This form of mirroring affects two (2) Name Tables simultaneously;\n    you cannot switch Name Tables independently.\n\n    The following chart should assist in understanding all the types of\n    mirroring encountered on the NES. Please note that the addresses\n    shown (12-bit in size) refer to the Name Table portion of the NES's\n    PPU RAM; one may consider these synonymous with \"$2xxx\" in the VRAM\n    region:\n\n        Name                       NT#0   NT#1   NT#2   NT#3   Flags\n      +--------------------------+------+------+------+------+-------+\n      | Horizontal               | $000 | $000 | $400 | $400 |       | \n      | Vertical                 | $000 | $400 | $000 | $400 |       | \n      | Four-screen              | $000 | $400 | $800 | $C00 | F     | \n      | Single-screen            |      |      |      |      |  S    | \n      | CHR-ROM mirroring        |      |      |      |      |   C   |\n      +--------------------------+------+------+------+------+-------+\n        F = Four-screen mirroring relies on an extra 2048 ($800) of RAM\n            (kept on the cart), resulting in four (4) physical independent\n            Name Tables.\n        S = Single-screen games have mappers which allow you to select\n            which PPU RAM area you want to use ($000, $400, $800, or\n            $C00); all the NTs point to the same PPU RAM address.\n        C = Mapper #68 (Afterburner 2) allows you to map CHR-ROM to the\n            Name Table region of the NES's PPU RAM area. Naturally this\n            makes the Name Table ROM-based, and one cannot write to it.\n            However, this feature can be controlled via the mapper itself,\n            allowing you to enable or disable this feature.\n\n\n  H. Palette Mirroring\n  --------------------\n    Mirroring occurs between the Image Palette and the Sprite Palette.\n    Any data which is written to $3F00 is mirrored to $3F10. Any data\n    written to $3F04 is mirrored to $3F14, etc. etc...\n\n    Colour #0 in the upper three (3) palettes of both the Image and Sprite\n    palette defines transparency (the actual colour stored there is not\n    drawn on-screen).\n\n    The PPU uses the value in $3F00 to define background colour.\n\n    For a more verbose explanation, assume the following:\n\n      * $0D has been written to $3F00 (mirrored to $3F10)\n      * $03 has been written to $3F08 (mirrored to $3F18)\n      * $1A has been written to $3F18\n      * $3F08 is read into the accumulator\n\n    The PPU will use $0D as the background colour, despite $3F08 holding\n    a value of $03 (since colour #0 in all the palette entries defines\n    transparency, it is not drawn). Finally, the accumulator will hold\n    a value of $1A, which is mirrored from $3F18. Again, the value of $1A\n    is not drawn, since colour #0 defines transparency.\n\n    The entire Image and Sprite Palettes are both mirrored to other areas\n    of VRAM as well; $3F20-3FFF are mirrors of both palettes, respectively.\n\n    D7-D6 of bytes written to $3F00-3FFF are ignored.\n\n\n  I. Background Scrolling\n  -----------------------\n    The NES can scroll the background (pre-rendered Name Table + Pattern\n    Table + Attribute Table) independently of the sprites which are over-\n    layed on top of it. The background can be scrolled horizontally and\n    vertically.\n\n    Scrolling works as follows:\n\n      Horizontal Scrolling             Vertical Scrolling\n        0          512\n        +-----+-----+                      +-----+ 0\n        |     |     |                      |     |\n        |  A  |  B  |                      |  A  |\n        |     |     |                      |     |\n        +-----+-----+                      +-----+\n                                           |     |\n                                           |  B  |\n                                           |     |\n                                           +-----+ 480\n\n    Name Table \"A\" is specified via Bits D1-D0 in register $2000, and \"B\" is\n    the Name Table after (due to mirroring, this is dynamic). This doesn't\n    work for game which use Horizontal & Vertical scrolling simultaneously.\n\n    The background will span across multiple Name Tables, as shown here:\n\n      +---------------+---------------+\n      | Name Table #2 | Name Table #3 |\n      |    ($2800)    |    ($2C00)    |\n      +---------------+---------------+\n      | Name Table #0 | Name Table #1 |\n      |    ($2000)    |    ($2400)    |\n      +---------------+---------------+\n\n    Writes to the Horizontal Scroll value in $2005 range from 0 to 256.\n    Writes to the Vertical Scroll value range from 0-239; values above 239\n    are considered negative (e.g. a write of 248 is really -8).\n\n\n  J. Screen and Sprite Layering\n  -----------------------------\n    There is a particular order in which the NES draws it's contents:\n\n      FRONT                                BACK\n      +----+-----------+----+-----------+-----+\n      | CI | OBJs 0-63 | BG | OBJs 0-63 | EXT |\n      +----+-----------+----+-----------+-----+\n           | SPR-RAM   |    | SPR-RAM   |\n           | BGPRI==0  |    | BGPRI==1  |\n           +-----------+    +-----------+\n\n    CI stands for 'Colour Intensity', which is synonmous with D7-D5 of\n    $2001. BG is the BackGround, and EXT is for the EXTension port video\n    signal.\n\n    'BGPRI' represents the 'Background Priority' bit in SPR-RAM, on a\n    per-sprite basis (D5, Byte 2).\n\n    OBJ numbers represent actual Sprite numbers, not Tile Index values.\n\n    FRONT is considered what is seen atop all other layers (drawn last),\n    and BACK is deemed what is below most other layers (drawn first).\n\n\n  K. Sprites and SPR-RAM\n  ----------------------\n    The NES supports 64 sprites, which can be either 8x8 or 8x16 pixels in\n    size. The sprite data is kept within the Pattern Table region of VRAM.\n\n    Sprite attributes such as flipping and priority, are stored in SPR-RAM,\n    which is a separate 256 byte area of memory, independent of ROM and\n    VRAM. The format of SPR-RAM is as follows:\n\n      +-----------+-----------+-----+------------+\n      | Sprite #0 | Sprite #1 | ... | Sprite #63 |\n      +-+------+--+-----------+-----+------------+\n        |      |   \n        +------+----------+--------------------------------------+\n        + Byte | Bits     | Description                          |\n        +------+----------+--------------------------------------+\n        |  0   | YYYYYYYY | Y Coordinate - 1. Consider the coor- |\n        |      |          | dinate the upper-left corner of the  |\n        |      |          | sprite itself.                       |\n        |  1   | IIIIIIII | Tile Index #                         |\n        |  2   | vhp000cc | Attributes                           |\n        |      |          |   v = Vertical Flip   (1=Flip)       |\n        |      |          |   h = Horizontal Flip (1=Flip)       |\n        |      |          |   p = Background Priority            |\n        |      |          |         0 = In front                 |\n        |      |          |         1 = Behind                   |\n        |      |          |   c = Upper two (2) bits of colour   |\n        |  3   | XXXXXXXX | X Coordinate (upper-left corner)     |\n        +------+----------+--------------------------------------+\n\n    The Tile Index # is obtained the same way as Name Table data.\n\n    Sprites which are 8x16 in size function a little bit differently. A\n    8x16 sprite which has an even-numbered Tile Index # use the Pattern\n    Table at $0000 in VRAM; odd-numbered Tile Index #s use $1000.\n    *NOTE*: Register $2000 has no effect on 8x16 sprites.\n\n    All 64 sprites contain an internal priority; sprite #0 is of a higher\n    priority than sprites #63 (sprite #0 should be drawn last, etc.).\n\n    Only eight (8) sprites can be displayed per scan-line. Each entry in\n    SPR-RAM is checked to see if it's in a horizontal range with the other\n    sprites. Remember, this is done on a per scan-line basis, not on a per\n    sprite basis (e.g. done 256 times, not 256/8 or 256/16 times).\n\n    (NOTE: On a real NES unit, if sprites are disabled (D4 of $2001 is 0)\n    for a long period of time, SPR-RAM will gradually degrade. A proposed\n    concept is that SPR-RAM is actually DRAM, and D4 controls the DRAM\n    refresh cycle).\n\n\n  L. Sprite #0 Hit Flag\n  ---------------------\n    The PPU is capable of figuring out where Sprite #0 is, and stores\n    it's findings in D6 of $2002. The way this works is as follows:\n\n    The PPU scans for the first actual non-transparent \"sprite pixel\" and\n    the first non-transparent \"background pixel.\" A \"background pixel\" is\n    a tile which is in use by the Name Table. Remember that colour #0\n    defines transparency.\n\n    The pixel which causes D6 to be set *IS* drawn.\n    \n    The following example should help. The following are two tiles.\n    Transparent colours (colour #0) are defined via the underscore ('_')\n    character. An asterisk ('*') represents when D6 will be set.\n\n       Sprite         BG         Result\n       ------         --         ------\n      __1111__     ________     __1111__\n      _111111_     _______2     _1111112    \n      11222211     ______21     11222211\n      112__211  +  _____211  =  112__*11  '*' will be drawn as colour #2\n      112__211     ____2111     112_2211\n      11222211     ___21111     11222211\n      _111111_     __211111     _1111111\n      __1111__     _2111111     _2111111\n\n    This also applies to sprites that are underneathe the BG (via the\n    'Background Priority' SPR-RAM bit), though the above example would\n    be 'BG+Sprite'.\n\n    Also, D6 is cleared (set to 0) after each VBlank.\n\n\n  M. Horizontal and Vertical Blanking\n  -----------------------------------\n    The NES, like every console, has a refresh: where the display device\n    relocates the electron gun to display visible data. The most common\n    display device is a television set. The refresh occurs 60 times a\n    second on an NTSC device, and 50 on a PAL device.\n\n    The gun itself draws pixels left to right: this process results in\n    one (1) horizontal scanline being drawn. After the gun is done drawing\n    the entire scanline, the gun must return to the left side of the\n    display device, becoming ready to draw the next scanline. The process\n    of the gun returning to the left side of the display is the Horizontal\n    Blank period (HBlank).\n\n    When the gun has completed drawing all of the scanlines, it must return\n    to the top of the display device; the time it takes for the gun to\n    re-position itself atop the device is called the Vertical Blank period\n    (VBlank).\n\n    As you can see from the below diagram, the gun more or less works in\n    a zig-zag pattern until VBlank is reached, then the process repeats:\n\n           +-----------+\n      +--->|***********| <-- Scanline 0\n      |    | ___---~~~ | <-- HBlank\n      V    |***********| <-- Scanline 1\n      B    | ___---~~~ | <-- HBlank\n      l    |    ...    |      ...\n      a    |    ...    |      ...\n      n    |***********| <-- Scanline 239\n      k    +-----+-----+\n      |          |\n      +--VBlank--+\n\n    An NTSC NES has the following refresh and screen layout:\n\n      +--------+ 0 ----+\n      |        |       |\n      |        |       |\n      | Screen |       +-- (0-239) 256x240 on-screen results\n      |        |       |\n      |        |       |\n      +--------+ 240 --+\n      |   ??   |       +-- (240-242) Unknown\n      +--------+ 243 --+\n      |        |       |\n      | VBlank |       +-- (243-262) VBlank\n      |        |       |\n      +--------+ 262 --+\n\n    The Vertical Blank (VBlank) flag is contained in D7 of $2002. It\n    indicates whether PPU is in VBlank or not. A program can reset D7 by\n    reading $2002.\n\n\n  N. $2005/2006 Magic\n  -------------------\n     For detailed information pertaining to the $2005 and $2006 registers,\n     refer to Loopy's $2005/2006 document. His document provides entirely\n     accurate information regarding how these registers work. Contact\n     Loopy for more information.\n\n\n  O. PPU Quirks\n  -------------\n    The first read from VRAM is invalid. Due to this aspect, the NES will\n    returned pseudo-buffered values from VRAM rather than linear as expec-\n    ted. See the below example:\n\n    VRAM $2000 contains $AA $BB $CC $DD.\n    VRAM incrementation value is 1.\n    The result of execution is printed in the comment field. \n\n      LDA #$20\n      STA $2006\n      LDA #$00\n      STA $2006        ; VRAM address now set at $2000\n      LDA $2007        ; A=??     VRAM Buffer=$AA\n      LDA $2007        ; A=$AA    VRAM Buffer=$BB\n      LDA $2007        ; A=$BB    VRAM Buffer=$CC\n      LDA #$20\n      STA $2006\n      LDA #$00\n      STA $2006        ; VRAM address now set at $2000\n      LDA $2007        ; A=$CC    VRAM Buffer=$AA\n      LDA $2007        ; A=$AA    VRAM Buffer=$BB\n\n    As shown, the PPU will post-increment it's internal address data after\n    the first read is performed. This *ONLY APPLIES* to VRAM $0000-3EFF\n    (e.g. Palette data and their respective mirrors do not suffer from\n    this phenomenon).\n\n\n  P. Notes\n  --------\n    The PPU will auto-increment the VRAM address by 1 or 32 (based on D2\n    of $2000) after accessing $2007.\n\n\n\n+---------+\n| 5. pAPU |\n+---------+\n   To be written. Prior information was inaccurate or incorrect. No one\n   has 100% accurate sound information at this time. This section will\n   be completed when someone decides to reverse engineer the pAPU section\n   of the NES, and provide me with information (or a reference to infor-\n   mation).\n\n\n\n+--------------------------------------+\n| 6. Joypads, paddles, expansion ports |\n+--------------------------------------+\n\n  A. General Information\n  ----------------------\n    The NES supports a myriad of input devices, including joypads, Zappers\n    (light guns), and four-player devices.\n\n    Joypad #1 and #2 are accessed via $4016 and $4017, respectively.\n\n    The joypads are reset via a strobing-method: writing 1, then 0, to\n    $4016. See Subsection H for information regarding \"half-strobing.\"\n\n    On a full strobe, the joypad's button status will be returned in a\n    single-bit stream (D0). Multiple reads need to be made to read all the\n    information about the controller.\n\n      1 = A          9 = Ignored   17 = +--+\n      2 = B         10 = Ignored   18 =    +-- Signature\n      3 = SELECT    11 = Ignored   19 =    |\n      4 = START     12 = Ignored   20 = +--+\n      5 = UP        13 = Ignored   21 = 0\n      6 = DOWN      14 = Ignored   22 = 0\n      7 = LEFT      15 = Ignored   23 = 0\n      8 = RIGHT     16 = Ignored   24 = 0\n\n    See Subsection G for information about Signatures.\n\n\n  B. The Zapper\n  -------------\n    The Zapper (otherwise known as the \"Light Gun\") simply uses bits\n    within $4016 and $4017, described in Section 8. See bits D4, D3, and\n    D0.\n\n    It is possible to have two Zapper units connected to both joypad\n    ports simultaneously.\n\n\n  C. Four-player devices\n  ----------------------\n    Some NES games allow the use of a four-player adapter, extending the\n    number of usable joypads from two (2) to four (4). Carts which use\n    the quad-player device are Tengen's \"Gauntlet II,\" and Nintendo's\n    \"RC Pro Am 2.\"\n\n    All four (4) controllers read their status-bits from D0 of $4016 or\n    $4017, as Subsection A states.\n\n    For register $4016, reads #1-8 control joypad #1, and reads #9-16\n    control joypad #3. For $4017, it is respective for joypad #2 and #4.\n\n    The following is a list of read #s and their results.\n\n      1 = A          9 = A         17 = +--+\n      2 = B         10 = B         18 =    +-- Signature\n      3 = SELECT    11 = SELECT    19 =    |\n      4 = START     12 = START     20 = +--+\n      5 = UP        13 = UP        21 = 0\n      6 = DOWN      14 = DOWN      22 = 0\n      7 = LEFT      15 = LEFT      23 = 0\n      8 = RIGHT     16 = RIGHT     24 = 0\n\n    See Subsection G for information about Signatures.\n\n\n  D. Paddles\n  ----------\n    Taito's \"Arkanoid\"  uses a paddle as it's primary controller.\n\n    The paddle position is read via D1 of $4017; the read data is inverted\n    (0=1, 1=0). The first value read is the MSB, and the 8th value read is\n    (obviously) the LSB. Valid value ranges are 98 to 242, where 98 rep-\n    resents the paddle being turned completely counter-clockwise.\n\n    For example, if %01101011 is read, the value would be NOT'd, making\n    %10010100 which is 146.\n\n    The paddle also contains one button, which is read via D1 of $4016. A\n    value of 1 specifies that the button is being pressed.\n\n\n  E. Power Pad\n  ------------\n    No information is currently available.\n\n\n  F. R.O.B. (Robot Operated Buddy)\n  --------------------------------\n    No information is currently available.\n\n\n  G. Signatures\n  -------------\n    A signature allows the programmer to detect if a device is connected\n    to one of the four (4) ports or not, and if so, what type of device it\n    is. Valid/known signatures are:\n\n      %0000 = Disconnected\n      %0001 = Joypad ($4016 only)\n      %0010 = Joypad ($4017 only)\n\n\n  H. Expansion ports\n  ------------------\n    The joypad strobing process requires dual writes: 1, then 0. If the\n    strobing process is not completed, or occurs in a non-standard order,\n    the joypads are no longer the item of communication: the expansion\n    port is.\n\n    For NES users, the expansion port is located on the bottom of the unit,\n    covered by a small grey piece of plastic. Famicom users have a limited\n    expansion port on the front of their unit, which was commonly used for\n    joypads or turbo-joypads.\n\n    Such an example of communicating with the expansion port would be the\n    following code:\n\n      LDA #%00000001\n      STA $4016\n      STA $4017           ; Begin read mode of expansion port\n      LDA #%00000011      ; Write %110 to the expansion port\n      STA $4016\n\n    I have yet to encounter a cart which actually uses this method of\n    communication.\n\n\n  I. Notes\n  --------\n    None.\n\n\n\n+----------------------------+\n| 7. Memory Mapping Hardware |\n+----------------------------+\n\n  Due to the large number of mappers used (over 64), the \"MMC\" section\n  which was once fluid in v0.53 of this document, has now been removed.\n\n  All is not lost, as another document by \\FireBug\\ of Vertigo 2099 con-\n  tains accurate information about nearly every mapper in existence. You\n  can retrieve a copy via one of the following URLs:\n\n    http://free.prohosting.com/~nintendo/mappers.nfo\n\n  Please note I take no responsibility for the information contained in the\n  aforementioned document. Contact lavos999@aol.com for more information.\n\n\n\n+--------------+\n| 8. Registers |\n+--------------+\n  Programmers communicate with the PPU and pAPU via registers, which are\n  nothing more than pre-set memory locations which allow the coder to make\n  changes to the NES. Without registers, programs wouldn't work: period.\n\n  Each register is a 16-bit address. Each register has a statistics field\n  in parentheses located immediately after its description. The legend:\n\n    R = Readable                    W = Writable\n    2 = Double-write register      16 = 16-bit register\n\n  NOTE: 16-bit registers actually consist of two linear 8-bit registers,\n        which can (and will be) *INDEPENDANTLY* assigned. The reason for\n        specifying them as 16-bit is for ease of documentation. For\n        instance, \"$4002+$4003\" would mean that D15-D8 would be in $4003,\n        and D7-D0 would be in $4002.\n  NOTE: Bits not listed are to be considered unused.\n\n    +---------+----------------------------------------------------------+\n    | Address | Description                                              |\n    +---------+----------------------------------------------------------+\n    |  $2000  | PPU Control Register #1 (W)                              |\n    |         |                                                          |\n    |         |    D7: Execute NMI on VBlank                             |\n    |         |           0 = Disabled                                   |\n    |         |           1 = Enabled                                    |\n    |         |    D6: PPU Master/Slave Selection --+                    |\n    |         |           0 = Master                +-- UNUSED           |\n    |         |           1 = Slave               --+                    |\n    |         |    D5: Sprite Size                                       |\n    |         |           0 = 8x8                                        |\n    |         |           1 = 8x16                                       |\n    |         |    D4: Background Pattern Table Address                  |\n    |         |           0 = $0000 (VRAM)                               |\n    |         |           1 = $1000 (VRAM)                               |\n    |         |    D3: Sprite Pattern Table Address                      |\n    |         |           0 = $0000 (VRAM)                               |\n    |         |           1 = $1000 (VRAM)                               |\n    |         |    D2: PPU Address Increment                             |\n    |         |           0 = Increment by 1                             |\n    |         |           1 = Increment by 32                            |\n    |         | D1-D0: Name Table Address                                |\n    |         |         00 = $2000 (VRAM)                                |\n    |         |         01 = $2400 (VRAM)                                |\n    |         |         10 = $2800 (VRAM)                                |\n    |         |         11 = $2C00 (VRAM)                                |\n    +---------+----------------------------------------------------------+\n    |  $2001  | PPU Control Register #2 (W)                              |\n    |         |                                                          |\n    |         | D7-D5: Full Background Colour (when D0 == 1)             |\n    |         |         000 = None  +------------+                       |\n    |         |         001 = Green              | NOTE: Do not use more |\n    |         |         010 = Blue               |       than one type   |\n    |         |         100 = Red   +------------+                       |\n    |         | D7-D5: Colour Intensity (when D0 == 0)                   |\n    |         |         000 = None            +--+                       |\n    |         |         001 = Intensify green    | NOTE: Do not use more |\n    |         |         010 = Intensify blue     |       than one type   |\n    |         |         100 = Intensify red   +--+                       |\n    |         |    D4: Sprite Visibility                                 |\n    |         |           0 = Sprites not displayed                      |\n    |         |           1 = Sprites visible                            |\n    |         |    D3: Background Visibility                             |\n    |         |           0 = Background not displayed                   |\n    |         |           1 = Background visible                         |\n    |         |    D2: Sprite Clipping                                   |\n    |         |           0 = Sprites invisible in left 8-pixel column   |\n    |         |           1 = No clipping                                |\n    |         |    D1: Background Clipping                               |\n    |         |           0 = BG invisible in left 8-pixel column        |\n    |         |           1 = No clipping                                |\n    |         |    D0: Display Type                                      |\n    |         |           0 = Colour display                             |\n    |         |           1 = Monochrome display                         |\n    +---------+----------------------------------------------------------+\n    |  $2002  | PPU Status Register (R)                                  |\n    |         |                                                          |\n    |         |    D7: VBlank Occurance                                  |\n    |         |          0 = Not occuring                                |\n    |         |          1 = In VBlank                                   |\n    |         |    D6: Sprite #0 Occurance                               |\n    |         |          0 = Sprite #0 not found                         |\n    |         |          1 = PPU has hit Sprite #0                       |\n    |         |    D5: Scanline Sprite Count                             |\n    |         |          0 = Eight (8) sprites or less on current scan-  |\n    |         |              line                                        |\n    |         |          1 = More than 8 sprites on current scanline     |\n    |         |    D4: VRAM Write Flag                                   |\n    |         |          0 = Writes to VRAM are respected                |\n    |         |          1 = Writes to VRAM are ignored                  |\n    |         |                                                          |\n    |         | NOTE: D7 is set to 0 after read occurs.                  |\n    |         | NOTE: After a read occurs, $2005 is reset, hence the     |\n    |         |       next write to $2005 will be Horizontal.            |\n    |         | NOTE: After a read occurs, $2006 is reset, hence the     |\n    |         |       next write to $2006 will be the high byte portion. |\n    |         |                                                          |\n    |         | For detailed information regarding D6, see Section 4,    |\n    |         | Subsection L.                                            |\n    +---------+----------------------------------------------------------+\n    |  $2003  | SPR-RAM Address Register (W)                             |\n    |         |                                                          |\n    |         | D7-D0: 8-bit address in SPR-RAM to access via $2004.     |\n    +---------+----------------------------------------------------------+\n    |  $2004  | SPR-RAM I/O Register (W)                                 |\n    |         |                                                          |\n    |         | D7-D0: 8-bit data written to SPR-RAM.                    |\n    +---------+----------------------------------------------------------+\n    |  $2005  | VRAM Address Register #1 (W2)                            |\n    |         |                                                          |\n    |         |  Commonly used used to \"pan/scroll\" the screen (sprites  |\n    |         |  excluded) horizontally and vertically. However, there   |\n    |         |  is no actual panning hardware inside the NES. This      |\n    |         |  register controls VRAM addressing lines.                |\n    |         |                                                          |\n    |         | Refer to Section 4, Subsection N, for more information.  |\n    +---------+----------------------------------------------------------+\n    |  $2006  | VRAM Address Register #2 (W2)                            |\n    |         |                                                          |\n    |         |  Commonly used to specify the 16-bit address in VRAM to  |\n    |         |  access via $2007. However, this register controls VRAM  |\n    |         |  addressing bits, and therefore should be used with      |\n    |         |  knowledge of how it works, and when it works.           |\n    |         |                                                          |\n    |         | Refer to Section 4, Subsection N, for more information.  |\n    +---------+----------------------------------------------------------+\n    |  $2007  | VRAM I/O Register (RW)                                   |\n    |         |                                                          |\n    |         | D7-D0: 8-bit data read/written from/to VRAM.             |\n    +---------+----------------------------------------------------------+\n    |  $4000  | pAPU Pulse #1 Control Register (W)                       |\n    |  $4001  | pAPU Pulse #1 Ramp Control Register (W)                  |\n    |  $4002  | pAPU Pulse #1 Fine Tune (FT) Register (W)                |\n    |  $4003  | pAPU Pulse #1 Coarse Tune (CT) Register (W)              |\n    |  $4004  | pAPU Pulse #2 Control Register (W)                       |\n    |  $4005  | pAPU Pulse #2 Ramp Control Register (W)                  |\n    |  $4006  | pAPU Pulse #2 Fine Tune Register (W)                     |\n    |  $4007  | pAPU Pulse #2 Coarse Tune Register (W)                   |\n    |  $4008  | pAPU Triangle Control Register #1 (W)                    |\n    |  $4009  | pAPU Triangle Control Register #2 (?)                    |\n    |  $400A  | pAPU Triangle Frequency Register #1 (W)                  |\n    |  $400B  | pAPU Triangle Frequency Register #2 (W)                  |\n    |  $400C  | pAPU Noise Control Register #1 (W)                       |\n    |  $400D  | Unused (???)                                             |\n    |  $400E  | pAPU Noise Frequency Register #1 (W)                     |\n    |  $400F  | pAPU Noise Frequency Register #2 (W)                     |\n    |  $4010  | pAPU Delta Modulation Control Register (W)               |\n    |  $4011  | pAPU Delta Modulation D/A Register (W)                   |\n    |  $4012  | pAPU Delta Modulation Address Register (W)               |\n    |  $4013  | pAPU Delta Modulation Data Length Register (W)           |\n    +---------+----------------------------------------------------------+\n    |  $4014  | Sprite DMA Register (W)                                  |\n    |         |                                                          |\n    |         |  Transfers 256 bytes of memory into SPR-RAM. The address |\n    |         |  read from is $100*N, where N is the value written.      |\n    +---------+----------------------------------------------------------+\n    |  $4015  | pAPU Sound/Vertical Clock Signal Register (R)            |\n    |         |                                                          |\n    |         |    D6: Vertical Clock Signal IRQ Availability            |\n    |         |           0 = One (1) frame occuring, hence IRQ cannot   |\n    |         |               occur                                      |\n    |         |           1 = One (1) frame is being interrupted via IRQ |\n    |         |    D4: Delta Modulation                                  |\n    |         |    D3: Noise                                             |\n    |         |    D2: Triangle                                          |\n    |         |    D1: Pulse #2                                          |\n    |         |    D0: Pulse #1                                          |\n    |         |           0 = Not in use                                 |\n    |         |           1 = In use                                     |\n    |         +----------------------------------------------------------+\n    |         | pAPU Channel Control (W)                                 |\n    |         |                                                          |\n    |         |    D4: Delta Modulation                                  |\n    |         |    D3: Noise                                             |\n    |         |    D2: Triangle                                          |\n    |         |    D1: Pulse #2                                          |\n    |         |    D0: Pulse #1                                          |\n    |         |           0 = Channel disabled                           |\n    |         |           1 = Channel enabled                            |\n    +---------+----------------------------------------------------------+\n    |  $4016  | Joypad #1 (RW)                                           |\n    |         |                                                          |\n    |         | READING:                                                 |\n    |         |    D4: Zapper Trigger                                    |\n    |         |           0 = Pulled                                     |\n    |         |           1 = Released (not held)                        |\n    |         |    D3: Zapper Sprite Detection                           |\n    |         |           0 = Sprite not in position                     |\n    |         |           1 = Sprite in front of cross-hair              |\n    |         |    D0: Joypad Data                                       |\n    |         +----------------------------------------------------------+\n    |         | WRITING:                                                 |\n    |         | Joypad Strobe (W)                                        |\n    |         |                                                          |\n    |         |    D0: Joypad Strobe                                     |\n    |         |           0 = Clear joypad strobe                        |\n    |         |           1 = Reset joypad strobe                        |\n    |         +----------------------------------------------------------+\n    |         | WRITING:                                                 |\n    |         | Expansion Port Latch (W)                                 |\n    |         |                                                          |\n    |         |    D0: Expansion Port Method                             |\n    |         |           0 = Write                                      |\n    |         |           1 = Read                                       |\n    +---------+----------------------------------------------------------+\n    |  $4017  | Joypad #2/SOFTCLK (RW)                                   |\n    |         |                                                          |\n    |         | READING:                                                 |\n    |         |    D7: Vertical Clock Signal (External)                  |\n    |         |           0 = Not occuring                               |\n    |         |           1 = Occuring                                   |\n    |         |    D6: Vertical Clock Signal (Internal)                  |\n    |         |           0 = Occuring     (D6 of $4016 affected)        |\n    |         |           1 = Not occuring (D6 of $4016 untouchable)     |\n    |         |    D4: Zapper Trigger                                    |\n    |         |           0 = Pulled                                     |\n    |         |           1 = Released (not held)                        |\n    |         |    D3: Zapper Sprite Detection                           |\n    |         |           0 = Sprite not in position                     |\n    |         |           1 = Sprite in front of cross-hair              |\n    |         |    D0: Joypad Data                                       |\n    |         +----------------------------------------------------------+\n    |         | WRITING:                                                 |\n    |         | Expansion Port Latch (W)                                 |\n    |         |                                                          |\n    |         |    D0: Expansion Port Method                             |\n    |         |           0 = ???                                        |\n    |         |           1 = Read                                       |\n    +---------+----------------------------------------------------------+\n\n\n\n+-----------------+\n| 9. File Formats |\n+-----------------+\n\n  A. iNES Format (.NES)\n  ---------------------\n    +--------+------+------------------------------------------+\n    | Offset | Size | Content(s)                               |\n    +--------+------+------------------------------------------+\n    |   0    |  3   | 'NES'                                    |\n    |   3    |  1   | $1A                                      |\n    |   4    |  1   | 16K PRG-ROM page count                   |\n    |   5    |  1   | 8K CHR-ROM page count                    |\n    |   6    |  1   | ROM Control Byte #1                      |\n    |        |      |   %####vTsM                              |\n    |        |      |    |  ||||+- 0=Horizontal mirroring      |\n    |        |      |    |  ||||   1=Vertical mirroring        |\n    |        |      |    |  |||+-- 1=SRAM enabled              |\n    |        |      |    |  ||+--- 1=512-byte trainer present  |\n    |        |      |    |  |+---- 1=Four-screen mirroring     |\n    |        |      |    |  |                                  |\n    |        |      |    +--+----- Mapper # (lower 4-bits)     |\n    |   7    |  1   | ROM Control Byte #2                      |\n    |        |      |   %####0000                              |\n    |        |      |    |  |                                  |\n    |        |      |    +--+----- Mapper # (upper 4-bits)     |\n    |  8-15  |  8   | $00                                      |\n    | 16-..  |      | Actual 16K PRG-ROM pages (in linear      |\n    |  ...   |      | order). If a trainer exists, it precedes |\n    |  ...   |      | the first PRG-ROM page.                  |\n    | ..-EOF |      | CHR-ROM pages (in ascending order).      |\n    +--------+------+------------------------------------------+\n\n\n\n+-------------------------+\n| 10. Programming the NES |\n+-------------------------+\n\n  A. General Information\n  ----------------------\n    None.\n\n\n  B. CPU Notes\n  ------------\n    None. See Section 11, Subsection B for more possible information.\n\n\n  C. PPU Notes\n  ------------\n    Reading and writing to VRAM consists of a multi-step process:\n\n      Writing to VRAM                    Reading from VRAM\n      ---------------                    -----------------\n      1) Wait for VBlank                 1) Wait for VBlank\n      2) Write upper VRAM address        2) Write upper VRAM address\n         byte into $2006                    byte into $2006\n      3) Write lower VRAM address        3) Write lower VRAM address\n         byte into $2006                    byte into $2006\n      4) Write data to $2007             4) Read $2007 (invalid data once)\n                                         5) Read data from $2007\n\n    NOTE: Step #4 when reading VRAM is only necessary when reading\n          VRAM data not in the $3F00-3FFF range.\n\n    NOTE: Accessing VRAM should only be performed during VBlank. Attempts\n          to access VRAM outside of VBlank will usually result in garbage\n          showing up on the screen. See Section 4, Subsection N for more\n          information regarding why this occurs.\n\n    Waiting for VBlank is quite simple:\n\n      8000: LDA $2002\n            BPL $8000\n\n    Reading $2002 will result in all bits being returned; however, D7\n    will be reset to 0 after the read is performed.\n\n    The actual on-screen palette used by the NES, as stated prior, is not\n    RGB. However, a near-exact replica can be found in common NES emulators\n    today. Contact the appropriate authors of these emulators to obtain\n    a valid RGB palette.\n\n    Be sure to clear the internal VRAM address via $2006 semi-often. You\n    will often encounter a situation where a palette fade or a VRAM update\n    will cause the screen \"to be trashed\" (squares on the screen, or what\n    seem to be graphical \"glitches\"). The reason for this is that your code\n    took longer than a VBlank. When the VBlank goes to refresh the screen\n    with the data in the PPU, it takes whatever value is in the internal\n    VRAM address and uses that as the starting base for Name Table #0.\n    The solution is to fix your code by re-assigning the VRAM address to\n    $0000 (or $2000), so that the refresh may occur successfully. Such\n    code would be:\n\n      LDA #$00\n      STA $2006\n      STA $2006\n\n    You will find code like this in commercial games quite often.\n\n\n\n+---------------+\n| 11. Emulation |\n+---------------+\n\n  A. General Information\n  ----------------------\n    If you're going to be programming an emulator in C or C++, please be\n    familiar with pointers. Being familiar with pointers will help you\n    out severely when it comes to handling mirroring and VRAM addressing.\n    For you assembly buffs out there, obviously pointers are nothing more\n    than indirect addressing -- it's easier to change a 32-bit value than\n    to swap in and out an entire 64K of data.\n\n    When SRAM ($6000-7FFF) is disabled, writes to the memory area should\n    be ignored. Reads will possibly return data previously left on the\n    bus, and therefore when emulated should return 0 (or should be trap-\n    ped).\n\n    RAM-based memory areas ($0000-07FF) should *NOT* be zeroed on RESET;\n    they should be zeroed on power on/off. (Technically, the RAM is not\n    zeroed on power on/off either: the RAM will slowly dissapate over\n    time when the unit it off. However, for emulation purposes, please\n    make sure that a cold boot and a warm boot do different things).\n\n    See Section 12, Subsection E for Mailing List information.\n\n\n  B. CPU Notes\n  ------------\n    The NES does not use a 65c02 (CMOS) CPU as rumored.\n\n    Ignore opcodes which are bad (or support the option of trapping them).\n    Some ROM images out there, such as \"Adventures of Lolo\" contain bad\n    opcodes, due to dirty connectors on the cartridge during the extract-\n    ion process (or other reasons).\n\n    There are 154 valid opcodes (out of 256 total) on the NES.\n\n\n  C. PPU Notes\n  ------------\n    The formulae to calculate the base address of a Name Table tile number\n    is:\n\n      (TILENUM * 16) + PATTERNTABLE\n\n    Where TILENUM is the tile number in the Name Table, and PATTERNTABLE\n    is the Pattern Table Address defined via register $2000.\n\n    It's recommended that DOS programmers use what is known as \"MODE-Q,\"\n    a 256x256x256 \"tweaked\" video mode, for writing their emulator. Try to\n    avoid Mode-X modes, as they are non-chained, and result in painfully\n    slow graphics. Chained modes (like MODE-13h) are linear, and work\n    best for speedy graphics. Since the NES's resolution is 256x240, the\n    aforementioned \"MODE-Q\" should meet all necessary requirements.\n\n    Most emulators do not limit the number of sprites which can be displayed\n    per scanline, while the actual NES will show flicker as a result of more\n    than eight (8). {Put some more garbage here; it's early...}\n\n    Emulators should _NOT_ mask out unused bits within registers; doing so\n    may result in a cart not working.\n\n\n  D. APU Notes\n  ------------\n    To be written.\n\n\n\n+------------------------+\n| 12. Reference Material |\n+------------------------+\n\n  A. CPU Information\n  ------------------\n    None.\n\n\n  B. PPU Information\n  ------------------\n    None.\n\n\n  C. APU Information\n  ------------------\n    None.\n\n\n  D. MMC Information\n  ------------------\n    None.\n\n\n  E. Mailing Lists\n  ----------------\n    There is a NES Development Mailing List in existence. Contact Mark Knibbs\n    for more information. This list is for anyone who wishes to discuss tech-\n    nical issues about the NES; it is not a list for picking up the latest\n    and greatest information about NES emulators or what not.\n\n\n  F. WWW Sites\n  ------------\n    The following are a list of WWW sites which contain NES-oriented\n    material. If you encounter errors, bad links, or other anomolies\n    while visiting these sites, contact the site authors/owners, NOT\n    me. Thanks.\n\n    http://nesdev.parodius.com/\n\n      Contains a verbose amount of documentation regarding anything\n      NES-oriented, including hard-to-find mapper documentation. Seems\n      to be a decent NES information depository.\n\n    http://www.ameth.org/~veilleux/NES_info.html\n\n      Currently only contains hardware-oriented material, such as\n      overviews of cart and unit ASICs, mappers, and MMCs. Many\n      pinout diagrams for mappers and NES units are available here.\n      Also provides documentation on NES repair, modifying your NES\n      to give stereo output, applying stereo mixing to your NES, and\n      much much more.\n\n\n  G. Hardware Information\n  -----------------------\n    The following security bits may be purchased from MCM Electronics\n    (http://www.mcmelectronics.com/):\n\n      For NES carts: 22-1145 (3.8mm security bit)\n      For NES units: 22-1150 (4.5mm security bit)\n\n    The 4.5mm security screw is also used for the Super Nintendo Enter-\n    tainment System (SNES), and Nintendo 64.\n\n\n"
  },
  {
    "path": "docs/ppu/blargg_tests_readme.txt",
    "content": "NTSC NES PPU Tests\n------------------\nThese ROMs test a few aspects of the NTSC NES PPU operation. They have been\ntested on an actual NES and all give a passing result. I wrote them to verify\nthat my NES emulator's PPU was working properly.\n\nEach ROM runs several tests and reports a result code on screen and by beeping\na number of times. A result code of 1 always indicates that all tests were\npassed; see below for the meaning of other codes for each test.\n\nThe main source code for each test is included, and most tests are clearly\ndivided into sections. Some of the common support code is included, but not\nall, since it runs on a custom setup. Contact me if you want to assemble the\ntests yourself.\n\nShay Green <hotpop.com@blargg> (swap to e-mail)\n\n\npalette_ram\n-----------\nPPU palette RAM read/write and mirroring test\n\n1) Tests passed\n2) Palette read shouldn't be buffered like other VRAM\n3) Palette write/read doesn't work\n4) Palette should be mirrored within $3f00-$3fff\n5) Write to $10 should be mirrored at $00\n6) Write to $00 should be mirrored at $10\n\n\npower_up_palette\n----------------\nReports whether initial values in palette at power-up match those\nthat my NES has. These values are probably unique to my NES.\n\n1) Palette matches\n2) Palette differs from table\n\n\nsprite_ram\n----------\nTests sprite RAM access via $2003, $2004, and $4014\n\n1) Tests passed\n2) Basic read/write doesn't work\n3) Address should increment on $2004 write\n4) Address should not increment on $2004 read\n5) Third sprite bytes should be masked with $e3 on read \n6) $4014 DMA copy doesn't work at all\n7) $4014 DMA copy should start at value in $2003 and wrap\n8) $4014 DMA copy should leave value in $2003 intact\n\n\nvbl_clear_time\n--------------\nThe VBL flag ($2002.7) is cleared by the PPU around 2270 CPU clocks\nafter NMI occurs.\n\n1) Tests passed\n2) VBL flag cleared too soon\n3) VBL flag cleared too late\n\n\nvram_access\n-----------\nTests PPU VRAM read/write and internal read buffer operation\n\n1) Tests passed\n2) VRAM reads should be delayed in a buffer\n3) Basic Write/read doesn't work\n4) Read buffer shouldn't be affected by VRAM write\n5) Read buffer shouldn't be affected by palette write\n6) Palette read should also read VRAM into read buffer\n7) \"Shadow\" VRAM read unaffected by palette transparent color mirroring\n"
  },
  {
    "path": "docs/ppu/nmi_sync_ntsc_readme.txt",
    "content": "NES Precise NMI Synchronization\r\n-------------------------------\r\nThis library allows synchronizing exactly to the PPU from within a\r\nnormal NMI handler. It allows PPU writes from within an NMI handler of\r\nthe same precision that is otherwise only possible using completely\r\ncycle-timed code. It supports NTSC and PAL.\r\n\r\nThe code is written for the ca65 assembler. Other assemblers will\r\nrequire minor changes.\r\n\r\nFor more about how the technique works, see\r\nhttp://wiki.nesdev.com/w/index.php/Consistent_frame_synchronization .\r\n\r\n\r\nDemos\r\n-----\r\nNTSC and PAL demos are included. These show minimal use of this library\r\nto manually draw a line using timed writes. They manually draw a line by\r\nsetting bit 0 of $2001 to enable monochrome mode. The time of the write\r\ndetermines the position on screen, so any synchronization problems will\r\ncause the line's left side to move. Reference lines are shown above and\r\nbelow the manually-drawn one, showing the correct left edge position.\r\n\r\nOn the NTSC version, the left pixel of the middle line will be darker,\r\nsince it's flashing:\r\n\t\r\n\t********************\r\n\t                 ***\r\n\t-*******************\r\n\t                 ***\r\n\t********************\r\n\r\nOn the PAL version, the left pixel or two will be darker, since it's\r\nflashing. The left edge's general position will change randomly each\r\ntime you press reset. The upper line shows the farthest left it can ever\r\nbe after reset, and the lower line shows the farthest right it can be.\r\nIt may appear as one of the following:\r\n\r\n\t********************\r\n\t                 ***\r\n\t-*******************\r\n\t                 ***\r\n\t  ******************\r\n\r\n\r\n\t********************\r\n\t                 ***\r\n\t -******************\r\n\t                 ***\r\n\t  ******************\r\n\r\n\r\n\t********************\r\n\t                 ***\r\n\t  -*****************\r\n\t                 ***\r\n\t  ******************\r\n\r\n\r\nUsage\r\n-----\r\nTo use this library:\r\n\r\n* Include \"nmi_sync.s\".\r\n\r\n* Call init_nmi_sync[_pal] before synchronization is needed, then wait\r\nin a loop that calls wait_nmi, does anything that is necessary between\r\nNMIs, then loops back.\r\n\r\n* Inside NMI, call begin_nmi_sync, do sprite DMA, delay appropriately,\r\nthen call end_nmi_sync. If running on NTSC NES, sprite and/or background\r\nrendering MUST be enabled before calling end_nmi_sync. It can be\r\ndisabled again after it returns.\r\n\r\n* On frames where synchronization isn't needed, but will be needed a few\r\nframes later, call track_nmi_sync. If it won't be needed for a long\r\ntime, nothing needs to be done, and init_nmi_sync can be called again\r\nlater to re-synchronize and start over.\r\n\r\nAfter end_nmi_sync returns, the next instruction will be synchronized to\r\n2286 (NTSC)/7471 (PAL) cycles after the frame began.\r\n\r\nIf the NMI handler's timing is off by even one cycle, synchronization\r\nwill fail sometimes. To verify timing, write an odd value to $2001 after\r\nsynchronization. The point where monochrome mode begins on the scanline\r\nshould be very stable. If it ever jiggles, then something is wrong in\r\nyour code.\r\n\r\nThe following demonstrates:\r\n\r\n\t.include \"nmi_sync.s\"\r\n\t\r\n\treset:\r\n\t\t...\r\n\t\tjsr init_nmi_sync/init_nmi_sync_pal\r\n\t\t\r\n\tloop:\r\n\t\tjsr wait_nmi\r\n\t\t...anything done outside of NMI...\r\n\t\tjmp loop\r\n\t\r\n\tnmi:\r\n\t\t...save registers...\r\n\t\tjsr begin_nmi_sync  ; count as 6 cycles\r\n\t\t...\r\n\t\t\r\n\t\t; Instructions between nmi: and STA $4014 must take an even\r\n\t\t; number of cycles. STA $4014 must be done as a part of\r\n\t\t; synchronization.\r\n\t\tsta $4014           ; count as 4 cycles\r\n\t\t...\r\n\t\t\r\n\t\t; Instructions between nmi: and here must take\r\n\t\t; 1715 (NTSC)/6900 (PAL) cycles.\r\n\t\t\r\n\t\tdelay 1715 - ...    ; NTSC\r\n\t\tdelay 6900 - ...    ; PAL\r\n\t\t\r\n\t\t; On NTSC, sprite and/or background rendering MUST be\r\n\t\t; enabled at this point, or else synchronization will\r\n\t\t; be lost.\r\n\t\t\r\n\t\tjsr end_nmi_sync\r\n\t\t\r\n\t\t; Sprite and background rendering can be disabled again\r\n\t\t; at this point, if it's not needed.\r\n\t\t\r\n\t\t; Next instruction is now synchronized to exactly\r\n\t\t; 2286 (NTSC)/7471 (PAL) cycles after cycle that\r\n\t\t; frame began in.\r\n\t\t...\r\n\t\t\r\n\t\t...restore registers...\r\n\t\trti\r\n\r\n\r\nNTSC Timing\r\n-----------\r\nGiven the following NMI handler\r\n\r\n\tnmi:\r\n\t\t...\r\n\t\tjsr end_nmi_sync\r\n\t\tdelay N cycles\r\n\t\tlda #$01\r\n\t\tsta $2001   ; writes 2286+N+5 cycles into frame\r\n\r\nThe $2001 write will be 2286+N+5 cycles after frame began. To have the\r\n$2001 write at a particular pixel, calculate N with\r\n\r\n\tpixel = y * 341 + x\r\n\tN = (pixel + 290) / 3\r\n\r\nFor example, to write at y=121 x=80, N should be 13877.\r\n\r\nThe pixel position can be calculated from N with\r\n\r\n\tpixel = N * 3 - 290\r\n\ty = pixel / 341\r\n\tx = pixel - (y * 341)\r\n\r\nwhere y=0 x=0 is the top-left pixel. For example, if the delay is 13877,\r\nthen the $2001 write will occur at y=121 x=80.\r\n\r\nAfter init_nmi_sync is called, the first, third, fifth, etc. frames have\r\nthe above timing. On the second, fourth, sixth, etc. frames, the write\r\nis one pixel LATER (x=81 in the example). This one-pixel jitter is an\r\nunavoidable hardware limitation.\r\n\r\nThe above applies to enabling monochrome mode by setting bit 0 of $2001;\r\nother registers take effect at slightly different times. For some\r\nregisters, the pixel written can vary slightly after pressing reset.\r\nIt's best to use the above as a guide, reduce delay until glitches occur\r\ndue to it occurring too early, increase delay until glitches occur as\r\nwell, then choose a delay in the middle of those two extremes.\r\n \r\n\r\nPAL Timing\r\n----------\r\nGiven the following NMI handler\r\n\r\n\tnmi:\r\n\t\t...\r\n\t\tjsr end_nmi_sync\r\n\t\tdelay N cycles\r\n\t\tlda #$01\r\n\t\tsta $2001   ; writes 7471+N+5 cycles into frame\r\n\r\nThe $2001 write will be 7471+N+5 cycles after frame began. To have the\r\n$2001 write at a particular pixel, calculate N with\r\n\r\n\tpixel = y * 341 + x\r\n\tN = (pixel * 5 + 1444) / 16\r\n\r\nFor example, to write at y=121 x=82, N should be 13009.\r\n\r\nThe pixel position can be calculated from N with\r\n\r\n\tpixel = (N * 16 - 1444 + extra) / 5\r\n\ty = pixel / 341\r\n\tx = pixel - (y * 341)\r\n\r\nwhere y=0 x=0 is the top-left pixel, and extra is an additional delay\r\nthat depends on whether it's an even or odd frame, and also a random\r\noffset selected when reset is pressed. For example, if the delay is\r\n13009, then the $2001 write will occur no earlier than y=121 x=81.\r\n\r\nAfter init_nmi_sync is called, the first, third, fifth, etc. frames have\r\nthe above timing. On the second, fourth, sixth, etc. frames, extra is 8\r\ngreater, causing the write to be one or two pixels LATER (x=83 or 84 in\r\nthe example). This jitter is an unavoidable hardware limitation.\r\n\r\nAfter pressing reset, extra is set to a random value from 0 to 7,\r\ncausing writes to be one or two pixels later. This doesn't change until\r\nreset is pressed. This is also unavoidable.\r\n\r\nThe above applies to enabling monochrome mode by setting bit 0 of $2001;\r\nother registers take effect at slightly different times. For some\r\nregisters, the pixel written can vary slightly after pressing reset.\r\nIt's best to use the above as a guide, reduce delay until glitches occur\r\ndue to it occurring too early, increase delay until glitches occur as\r\nwell, then choose a delay in the middle of those two extremes.\r\n\r\n\r\nLimitations\r\n-----------\r\n* DMC samples can't be played, since they introduce too much timing\r\nvariation. A normal NMI performs just as well/poorly in this case.\r\n\r\n* If NMI occurs while executing an instruction that takes more than\r\nthree cycles, synchronization will be upset for that frame. Note that a\r\ntaken branch counts as more than three cycles, due to an obscure detail.\r\nTo avoid this, call wait_nmi each frame, or sit in a loop of\r\ninstructions two/three-cycle instructions. I have found some workarounds\r\nthat allow NMI to occur during almost any instruction, but they require\r\nsome extra helper sprites and use of the sprite overflow flag; contact\r\nme for details.\r\n\r\n* Every frame from that point, NMI must either call begin/end_nmi_sync,\r\nor track_nmi_sync, or else synchronization will be lost and\r\ninit_nmi_sync will need to be called again.\r\n\r\n* The NMI handler must not read $2002 until after end_nmi_sync has been\r\ncalled.\r\n\r\n* After synchronizing, NMI and rendering must be enabled by the next\r\nframe, and left enabled (rendering can be disabled on PAL since it\r\ndoesn't affect PPU timing). If rendering isn't desired, it can be\r\nenabled just before calling end_nmi_sync, then disabled afterwards.\r\n\r\n* Sprite DMA must be done on frames needing synchronization, even if no\r\nsprites are being used.\r\n\r\n* Even when perfectly synchronized, frames don't always begin exactly on\r\na cycle. On NTSC, a given cycle will toggle between two adjacent pixels.\r\nOn PAL, it will toggle between the calculated pixel and one or two\r\npixels after. These effects are hardware limitations; this library\r\nsynchronizes as precisely as is possible in software.\r\n\r\n\r\nThanks\r\n------\r\n* Bregalad for his initial questions that inspired the idea, and for\r\ntrying an early version.\r\n\r\n\r\n-- \r\nShay Green <gblargg@gmail.com>\r\n"
  },
  {
    "path": "docs/ppu/oam_read_readme.txt",
    "content": "NES OAM Read Test\r\n-----------------\r\nTests OAM reading ($2004), being sure it reads the byte from OAM at the\r\ncurrent address in $2003. It scans OAM from 0 to $FF, testing each byte\r\nin sequence. It prints a '-' where it reads back from the current\r\naddress, and '*' where it doesn't. Each row represents 16 bytes of OAM,\r\n16 rows total.\r\n\r\n\r\nResults\r\n-------\r\nOn my NTSC front-loader NES, I get the following four general patterns\r\nat random after power/reset:\r\n\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n\r\noam_read\r\n\r\nPassed\r\n\r\n\r\n----------------\r\n----------------\r\n--------*------*\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n\r\n694ADBE0\r\noam_read\r\n\r\nFailed\r\n\r\n\r\n----------------\r\n----------------\r\n********--------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n----------------\r\n\r\nE9E8E60F\r\noam_read\r\n\r\nFailed\r\n\r\n\r\n****************\r\n*********-------\r\n--------*-*-*-*-\r\n*-*-*-*-*-*-*-*-\r\n*-*-*-*-*-*-*-*-\r\n*-*-*-*-*-*-*-*-\r\n***-*-*-*-*-*-*-\r\n*-*-*-*-*-*-*-*-\r\n*-*-*-*-*-*-*-*-\r\n*-*-*-*-*-*-*-*-\r\n*-*-*-*-*-*-*-*-\r\n*-*-*-*-*-*-*-*-\r\n*-*-*-*-*-*-*-*-\r\n*-*-*-*-*-*-*-*-\r\n***-*-*-*-*-*-*-\r\n*-*-*-*-*-*-*-*-\r\n\r\n44551956\r\noam_read\r\n\r\nFailed\r\n\r\n\r\nFlashes, clicks, other glitches\r\n-------------------------------\r\nSome tests might need to turn the screen off and on, or cause slight\r\naudio clicks. This does not indicate failure, and should be ignored.\r\nOnly the test result reported at the end is important, unless stated\r\notherwise.\r\n\r\n\r\nText output\r\n-----------\r\nTests generally print information on screen. They also output the same\r\ntext as a zero-terminted string beginning at $6004, allowing examination\r\nof output in an NSF player, or a NES emulator without a working PPU. The\r\ntests also work properly if the PPU doesn't set the VBL flag properly or\r\ndoesn't implement it at all.\r\n\r\nThe final result is displayed and also written to $6000. Before the test\r\nstarts, $80 is written there so you can tell when it's done. If a test\r\nneeds the NES to be reset, it writes $81 there (emulator should wait a\r\ncouple of frames after seeing $81). In addition, $DE $B0 $G1 is written\r\nto $6001-$6003 to allow an emulator to detect when a test is being run,\r\nas opposed to some other NES program. In NSF builds, the final result is\r\nalso reported via a series of beeps (see below).\r\n\r\nSee the source code for more information about a particular test and why\r\nit might be failing. Each test has comments and correct output at the\r\ntop.\r\n\r\n\r\nNSF versions\r\n------------\r\nMany NSF-based tests require that the NSF player either not interrupt\r\nthe init routine with the play routine, or if it does, not interrupt the\r\nplay routine again if it hasn't returned yet. This is because many tests\r\nneed to run for a while without returning.\r\n\r\nNSF versions also make periodic clicks to avoid the NSF player from\r\nthinking the track is silent and thus ending the track before it's done\r\ntesting.\r\n\r\nIn addition to the other text output methods described above, NSF builds\r\nreport essential information bytes audibly, including the final result.\r\nA byte is reported as a series of tones. The code is in binary, with a\r\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\r\nThe first tone is always a zero. A final code of 0 means passed, 1 means\r\nfailure, and 2 or higher indicates a specific reason as listed in the\r\nsource code by the corresponding set_code line. Examples:\r\n\r\nTones         Binary  Decimal  Meaning\r\n- - - - - - - - - - - - - - - - - - - - \r\nlow              0      0      passed\r\nlow high        01      1      failed\r\nlow high low   010      2      error 2\r\n\r\n-- \r\nShay Green <gblargg@gmail.com>\r\n"
  },
  {
    "path": "docs/ppu/oam_stress_readme.txt",
    "content": "NES OAM Stress Test\r\n-------------------\r\nThoroughly tests OAM address ($2003) and read/write ($2004). On an NTSC\r\nNES, this passes only for one of the four random PPU-CPU\r\nsynchronizations at power/reset. Test takes about 30 seconds, unless it\r\nfails.\r\n\r\nThis test randomly sets the address, then randomly either writes a\r\nrandom number of random bytes, or reads from the current address a\r\nrandom number of times and verifies that it matches what's expected. It\r\ndoes this for tens of seconds (refreshing OAM periodically so it doesn't\r\nfade). Once done, it verifies that all bytes in OAM match what's\r\nexpected.\r\n\r\nExpected behavior:\r\n\r\n$2003 write sets OAM address.\r\n\r\n$2004 write sets byte at current OAM address to byte written, then\r\nincrements OAM address.\r\n\r\n$2004 read gives byte at current OAM address, without modifying OAM\r\naddress.\r\n\r\n\r\nFlashes, clicks, other glitches\r\n-------------------------------\r\nSome tests might need to turn the screen off and on, or cause slight\r\naudio clicks. This does not indicate failure, and should be ignored.\r\nOnly the test result reported at the end is important, unless stated\r\notherwise.\r\n\r\n\r\nText output\r\n-----------\r\nTests generally print information on screen. They also output the same\r\ntext as a zero-terminted string beginning at $6004, allowing examination\r\nof output in an NSF player, or a NES emulator without a working PPU. The\r\ntests also work properly if the PPU doesn't set the VBL flag properly or\r\ndoesn't implement it at all.\r\n\r\nThe final result is displayed and also written to $6000. Before the test\r\nstarts, $80 is written there so you can tell when it's done. If a test\r\nneeds the NES to be reset, it writes $81 there (emulator should wait a\r\ncouple of frames after seeing $81). In addition, $DE $B0 $G1 is written\r\nto $6001-$6003 to allow an emulator to detect when a test is being run,\r\nas opposed to some other NES program. In NSF builds, the final result is\r\nalso reported via a series of beeps (see below).\r\n\r\nSee the source code for more information about a particular test and why\r\nit might be failing. Each test has comments and correct output at the\r\ntop.\r\n\r\n\r\nNSF versions\r\n------------\r\nMany NSF-based tests require that the NSF player either not interrupt\r\nthe init routine with the play routine, or if it does, not interrupt the\r\nplay routine again if it hasn't returned yet. This is because many tests\r\nneed to run for a while without returning.\r\n\r\nNSF versions also make periodic clicks to avoid the NSF player from\r\nthinking the track is silent and thus ending the track before it's done\r\ntesting.\r\n\r\nIn addition to the other text output methods described above, NSF builds\r\nreport essential information bytes audibly, including the final result.\r\nA byte is reported as a series of tones. The code is in binary, with a\r\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\r\nThe first tone is always a zero. A final code of 0 means passed, 1 means\r\nfailure, and 2 or higher indicates a specific reason as listed in the\r\nsource code by the corresponding set_code line. Examples:\r\n\r\nTones         Binary  Decimal  Meaning\r\n- - - - - - - - - - - - - - - - - - - - \r\nlow              0      0      passed\r\nlow high        01      1      failed\r\nlow high low   010      2      error 2\r\n\r\n-- \r\nShay Green <gblargg@gmail.com>\r\n"
  },
  {
    "path": "docs/ppu/open_bus_readme.txt",
    "content": "NES PPU Open-Bus Test\r\n---------------------\r\nTests behavior when reading from open-bus PPU bits/registers, those bits\r\nthat aren't otherwise defined. Unlike other open-bus addresses, the PPU\r\nones are separate. Takes about 5 seconds to run.\r\n\r\nThe PPU effectively has a \"decay register\", an 8-bit register. Each bit\r\ncan be refreshed with a 0 or 1. If a bit isn't refreshed with a 1 for\r\nabout 600 milliseconds, it will decay to 0 (some decay sooner, depending\r\non the NES and temperature).\r\n\r\nWriting to any PPU register sets the decay register to the value\r\nwritten. Reading from a PPU register is more complex. The following\r\nshows the effect of a read from each register:\r\n\r\n\tAddr    Open-bus bits\r\n\t\t\t7654 3210\r\n\t- - - - - - - - - - - - - - - -\r\n\t$2000   DDDD DDDD\r\n\t$2001   DDDD DDDD\r\n\t$2002   ---D DDDD\r\n\t$2003   DDDD DDDD\r\n\t$2004   ---- ----\r\n\t$2005   DDDD DDDD\r\n\t$2006   DDDD DDDD\r\n\t$2007   ---- ----   non-palette\r\n\t\t\tDD-- ----   palette\r\n\r\nA D means that this bit reads back as whatever is in the decay register\r\nat that bit, and doesn't refresh the decay register at that bit. A -\r\nmeans that this bit reads back as defined by the PPU, and refreshes the\r\ndecay register at the corresponding bit.\r\n\r\n\r\nFlashes, clicks, other glitches\r\n-------------------------------\r\nSome tests might need to turn the screen off and on, or cause slight\r\naudio clicks. This does not indicate failure, and should be ignored.\r\nOnly the test result reported at the end is important, unless stated\r\notherwise.\r\n\r\n\r\nText output\r\n-----------\r\nTests generally print information on screen. They also output the same\r\ntext as a zero-terminted string beginning at $6004, allowing examination\r\nof output in an NSF player, or a NES emulator without a working PPU. The\r\ntests also work properly if the PPU doesn't set the VBL flag properly or\r\ndoesn't implement it at all.\r\n\r\nThe final result is displayed and also written to $6000. Before the test\r\nstarts, $80 is written there so you can tell when it's done. If a test\r\nneeds the NES to be reset, it writes $81 there (emulator should wait a\r\ncouple of frames after seeing $81). In addition, $DE $B0 $G1 is written\r\nto $6001-$6003 to allow an emulator to detect when a test is being run,\r\nas opposed to some other NES program. In NSF builds, the final result is\r\nalso reported via a series of beeps (see below).\r\n\r\nSee the source code for more information about a particular test and why\r\nit might be failing. Each test has comments and correct output at the\r\ntop.\r\n\r\n\r\nNSF versions\r\n------------\r\nMany NSF-based tests require that the NSF player either not interrupt\r\nthe init routine with the play routine, or if it does, not interrupt the\r\nplay routine again if it hasn't returned yet. This is because many tests\r\nneed to run for a while without returning.\r\n\r\nNSF versions also make periodic clicks to avoid the NSF player from\r\nthinking the track is silent and thus ending the track before it's done\r\ntesting.\r\n\r\nIn addition to the other text output methods described above, NSF builds\r\nreport essential information bytes audibly, including the final result.\r\nA byte is reported as a series of tones. The code is in binary, with a\r\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\r\nThe first tone is always a zero. A final code of 0 means passed, 1 means\r\nfailure, and 2 or higher indicates a specific reason as listed in the\r\nsource code by the corresponding set_code line. Examples:\r\n\r\nTones         Binary  Decimal  Meaning\r\n- - - - - - - - - - - - - - - - - - - - \r\nlow              0      0      passed\r\nlow high        01      1      failed\r\nlow high low   010      2      error 2\r\n\r\n-- \r\nShay Green <gblargg@gmail.com>\r\n"
  },
  {
    "path": "docs/ppu/ppu_2c02_ref.txt",
    "content": "*******************************\n*NTSC 2C02 technical reference*\n*******************************\nBrad Taylor (BTTDgroup@hotmail.com)\n5th release: April 23rd, 2004\nThanks to the NES community. http://nesdev.parodius.com.\nSpecial thanks to Neal Tew for scrolling information.\nRecommended literature: Nintendo's PPU patent document (U.S.#4,824,106).\n\nNote: to display this document properly, your text viewer needs two things: \n1. support for the classic VGA-based text mode 256 character set with \nline-drawing characters. 2. word-wrap. windows notepad can easially do both \nif you change the font over to terminal style.\n\n\nTopics discussed\n----------------\n2C02 integrated components list\n2C02 pin nomenclature & signal descriptions\n2C02 programming\nVideo signal generation\nPPU base timing\nMiscellanious PPU info\nPPU memory access cycles\nFrame rendering details\nScanline rendering details\nIn-range object evaluation\nDetails of playfield render pipeline\nDetails of object pattern fetch & render\nExtra cycle frames\nThe MMC3's scanline counter\nPPU pixel priority quirk\nPPU scrolling & addressing in a nutshell\n\n\n+-------------------------------+\n|2C02 integrated components list|\n+-------------------------------+\n- control registers and misc. flags\n- pixel & scanline counters\n- colorburst phase generator\n- VRAM address latches & counters\n- picture address buffer (tile index byte)\n- VRAM read buffer\n- object attribute memory (OAM)\n- OAM element pointer register/counter\n- OAM temporary memory & scanline comparator\n- vertical & horizontal inverter\n- OAM pixel buffers\n- playfield pixel buffer\n- multiplexer\n- palette memory\n- level decoder/phase selector/DAC\n- byte pointer flip-flop\n\n\n+-------------------------------------------+\n|2C02 pin nomenclature & signal descriptions|\n+-------------------------------------------+\n          ___  ___\n         |*  \\/   |\nR/W  >01]        [40<  VCC\n  D0  [02]        [39>  ALE\n  D1  [03]        [38]  AD0\n  D2  [04]        [37]  AD1\n  D3  [05]        [36]  AD2\n  D4  [06]        [35]  AD3\n  D5  [07]        [34]  AD4\n  D6  [08]        [33]  AD5\n  D7  [09]        [32]  AD6\n  A2  >10]  2C02  [31]  AD7\n  A1  >11]        [30>  A8\n  A0  >12]        [29>  A9\n/CS  >13]        [28>  A10\nEXT0  [14]        [27>  A11\nEXT1  [15]        [26>  A12\nEXT2  [16]        [25>  A13\nEXT3  [17]        [24>  /R\nCLK  >18]        [23>  /W\n/VBL  <19]        [22<  /SYNC\nVEE  >20]        [21>  VOUT\n         |________|\n\n\nR/W, D0-D7, A2-A0, /CS: these are the PPU's control bus signals responsible \nfor programming the 2C02's internal registers. R/W controls data direction \n(write data into PPU reg on zero), A0-A2 selects the internal PPU register \nto read/write, and while /CS is set to zero, D0-D7 is used to transfer the \ndata bits to/from the selected register (if /CS=1, D0-D7 float). The next \nsection documents the operation of the registers.\n\nEXT0-EXT3: this bus can either be used as a pixel input (for overlapping \nexternally generated graphics with the 2C02's), or output (for driving \nanother graphics processor), depending on how the 2C02 is programmed. \nNormally this bus is programmed to be an input, since NES/FC mainboards \nalways ground these four pins.\n\nCLK: this is the 2C02's 21.48 MHz clock input line.\n\n/VBL: this signal issues a zero logic level when the PPU has entered it's \nVBLANK time, and can stay zero for as long as 20 scanlines. This signal is \nusually tied to the 2A03's /NMI line, in order to generate the non-maskable \ninterrupt on a per-frame basis. Software acknowledging the /VBL-based \ninterrupt usually quickly clear & set again a /VBL gate bit via a register, \nso that the time /VBL is active is usually less than a scanline. This output \nis also open-collector.\n\nVEE, VCC: ground, and +5VDC power signals, respectfully.\n\nVOUT: the 2C02's unbuffered composite video output. This signal usually \ntravels to a two-stage common collector transistor amplifier, in order to \nboost the video drive to support 75 ohm loads at 1 volt peak-to-peak.\n\n/SYNC: this signal when zero, will force the status of colorburst control, \nscanline and pixel counters/flip-flops used inside the PPU to definite \nstates. Generally, this is the means of which two 2C02s connected together \nin a master-slave config (via the EXT bus) can syncronize together; the \nmaster PPU's /VBL line feeds the vblank information to the slave's /SYNC \ninput. On Famicom consoles, this pin is always tied to logical one. On the \nNES however, this pin is tied in with the 2A03's reset input, and as a \nresult, the picture is always disabled while the reset switch is held in on \nan NES.\n\n/R, /W, ALE, AD0-AD7, A8-A13: these signals control the PPU-related data \nbus. ALE is activated (logical one) when the PPU puts address bits 0 thru 7 \non the AD bus (typically a 74LS373 is used to store the low address bus \ncontents). An active /R or /W signal (when logical zero) indicates that a \nmemory device connected to the PPU's AD bus may decode the 14-bit address \n(formed between the external A0-A7 latch, and the A8-A13 lines), and drive \ndata in the direction indicated (/R means data is being sent into the 2C02, \nopposite for /W). Never more than one /R, /W, or ALE signal is activated \nsimultaniously.\n\n\n+----------------+\n|2C02 programming|\n+----------------+\nThis section lays out how 2C02 ports & programmable internal memory \nstructures are organized. Names for these ports throughout the document will \nsimply consist of adding $200 to the end of the number (i.e., $2002). \nAnything not explained here will be later on.\n\n\nWritable 2C02 registers\n-----------------------\nreg\tbit\tdesc\n---\t---\t----\n0\t0\tX scroll name table selection.\n\t1\tY scroll name table selection.\n\t2\tincrement PPU address by 1/32 (0/1) on access to port 7\n\t3\tobject pattern table selection (if bit 5 = 0)\n\t4\tplayfield pattern table selection\n\t5\t8/16 scanline objects (0/1)\n\t6\tEXT bus direction (0:input; 1:output)\n\t7\t/VBL disable (when 0)\n\n1\t0\tdisable composite colorburst (when 1). Effectively causes gfx to go \nblack & white.\n\t1\tleft side screen column (8 pixels wide) playfield clipping (when 0).\n\t2\tleft side screen column (8 pixels wide) object clipping (when 0).\n\t3\tenable playfield display (on 1).\n\t4\tenable objects display (on 1).\n\t5\tR (to be documented)\n\t6\tG (to be documented)\n\t7\tB (to be documented)\n\n3\t-\tinternal object attribute memory index pointer (64 attributes, 32 bits \neach, byte granular access). stored value post-increments on access to port \n4.\n\n4\t-\treturns object attribute memory location indexed by port 3, then \nincrements port 3.\n\n5\t-\tscroll offset port.\n\n6\t-\tPPU address port to access with port 7.\n\n7\t-\tPPU memory write port.\n\n\n\nReadable 2C02 registers\n-----------------------\nreg\tbit\tdesc\n---\t---\t----\n2\t5\tmore than 8 objects on a single scanline have been detected in the last \nframe\n\t6\ta primary object pixel has collided with a playfield pixel in the last \nframe\n\t7\tvblank flag\n\n4\t-\tobject attribute memory write port (incrementing port 3 thenafter)\n\n7\t-\tPPU memory read port.\n\n\nObject attribute structure (4*8 bits)\n-------------------------------------\nofs\tbit\tdesc\n---\t---\t----\n0\t-\tscanline coordinate minus one of object's top pixel row.\n\n1\t-\ttile index number. Bit 0 here controls pattern table selection when reg \n0.5 = 1.\n\n2\t0\tpalette select low bit\n\t1\tpalette select high bit\n\t5\tobject priority (> playfield's if 0; < playfield's if 1)\n\t6\tapply bit reversal to fetched object pattern table data\n\t7\tinvert the 3/4-bit (8/16 scanlines/object mode) scanline address used to \naccess an object tile\n\n3\t-\tscanline pixel coordite of most left-hand side of object.\n\n\n+-----------------------+\n|Video signal generation|\n+-----------------------+\nA 21.48 MHz clock signal is fed into the 2C02. This is the NES's main clock \nline, which is shared by the 2A03.\n\nInside the PPU, the 21.48 MHz signal is used to clock a three-stage Johnson \ncounter. The complimentery outputs of both master and slave portions of each \nstage are used to form 12 mutually exclusive output phases- all 3.58 MHz \neach (the NTSC colorburst). These 12 different phases form the basis of all \ncolor generation for the PPU's composite video output.\n\nNaturally, when the user programs the lower 4-bits of a palette register, \nthey are essentially selecting any 1 of 12 phases to be routed to the PPU's \nvideo out pin (this corresponds to chrominance (tint/hue) video information) \nwhen the appropriate pixel indexes it. Other chrominance combinations (0 & \n13) are simply hardwired to a 1 or 0 to generate grayscale pixels.\n\nBits 4 & 5 of a palette entry selects 1 of 4 linear DC voltage offsets to \napply to the selected chrominance signal (this corresponds to luminance \n(brightness) video information) for a pixel.\n\nChrominance values 14 & 15 yield a black pixel color, regardless of any \nluminance value setting.\n\nLuminance value 0, mixed with chrominance value 13 yield a \"blacker than \nblack\" pixel color. This super black pixel has an output voltage level close \nto the vertical/horizontal syncronization pulses. Because of this, some \nvideo monitors will display warped/distorted screens for games which use \nthis color for black (Game Genie is the best example of this). Essentially \nwhat is happening is the video monitor's horizontal timing is compromised by \nwhat it thinks are extra syncronization pulses in the scanline. This is not \ndamaging to the monitors which are effected by it, but use of the super \nblack color should be avoided, due to the graphical distortion it causes.\n\nThe amplitude of the selected chrominance signal (via the 4 lower bits of a \npalette register) remain constant regardless of bits 4 or 5. Thus it is not \npossible to adjust the saturation level of a particular color.\n\n\n+---------------+\n|PPU base timing|\n+---------------+\nOther than the 3-stage Johnson counter, the 21.48 MHz signal is not used \ndirectly by any other PPU hardware. Instead, the signal is divided by 4 to \nget 5.37 MHz, and is used as the smallest unit of timing in the PPU. All \nfollowing references to PPU clock cycle (abbr. \"cc\") timing in this document \nwill be in respect to this timing base, unless otherwise indicated.\n\n- Pixels are rendered at the same rate as the base PPU clock. In other \nwords, 1 clock cycle= 1 pixel.\n\n- 341 PPU cc's make up the time of a typical scanline (or 341/3 CPU cc's).\n\n- One frame consists of 262 scanlines. This equals 341*262 PPU cc's per \nframe (divide by 3 for # of CPU cc's).\n\n\n+------------------------+\n|PPU memory access cycles|\n+------------------------+\nAll PPU memory access cycles are 2 clocks long, and can be made back-to-back \n(typically done during rendering). Here's how the access breaks down:\n\nAt the beginning of the access cycle, PPU address lines 8..13 are updated \nwith the target address. This data remains here until the next time an \naccess cycle occurs.\n\nThe lower 8-bits of the PPU address lines are multiplexed with the data bus, \nto reduce the PPU's pin count. On the first clock cycle of the access, \nA0..A7 are put on the PPU's data bus, and the ALE (address latch enable) \nline is activated for the first half of the cycle. This loads the lower \n8-bit address into an external 8-bit transparent latch strobed by ALE \n(74LS373 is used).\n\nOn the second clock cycle, the /RD (or /WR) line is activated, and stays \nactive for the entire cycle. Appropriate data is driven onto the bus during \nthis time.\n\n\n+----------------------+\n|Miscellanious PPU info|\n+----------------------+\n- The internal 25-element palette RAM can be accessed by programming the PPU \naddress port with a range in $3Fxx. Address bit [4] indicates whether the \nplayfield (0) or object (1) palettes should be selected. Address bits [3..2] \nindicates the palette index (0..3), and bits [1..0] specify the palette \nelement index (1..3). The transparency color palette element can be accessed \nwhen address bits 3..0 are all zero.\n\n- Reading from $2002 clears the vblank flag (bit 7), and resets the internal \n$2005/6 flip-flop. Writes here have no effect.\n\n- The output of pin /VBL on the 2C02 is the logical NAND between 2002.7 and \n2000.7.\n\n- $2002.5 and $2002.6 after being set, stay that way for the first 20 \nscanlines of the new frame, relative to the VINT.\n\n- palette RAM is accessed internally during playfield rendering (i.e., the \npalette address/data is never put on the PPU bus during this time). \nAdditionally, when the programmer accesses palette RAM via $2006/7, the \npalette address accessed actually does show up on the PPU address bus, but \nthe PPU's /RD & /WR flags are not activated. This is required; to prevent \nwriting over name table data falling under the approprite mirrored area \n(since the name table RAM's address decoder simply consists of an inverter \nconnected to the A13 line- effectively decoding all addresses in \n$2000-$3FFF).\n\n- Because the PPU cannot make a read from PPU memory immediately upon \nrequest (via $2007), there is an internal buffer, which acts as a 1-stage \ndata pipeline. As a read is requested, the contents of the read buffer are \nreturned to the NES's CPU. After this, at the PPU's earliest convience \n(according to PPU read cycle timings), the PPU will fetch the requested data \nfrom the PPU memory, and throw it in the read buffer. Writes to PPU mem via \n$2007 are pipelined as well, but I currently haven unknown to me if the PPU \nuses this same buffer (this could be easily tested by writing somthing to \n$2007, and seeing if the same value is returned immediately after reading).\n\n\n+-----------------------+\n|Frame rendering details|\n+-----------------------+\n  The following describes the PPU's status during all 262 scanlines of a \nframe. Any scanlines where work is done (like image rendering), consists of \nthe steps which will be described in the next section.\n\n0..19:\tStarting at the instant the VINT flag is pulled down (when a NMI is \ngenerated), 20 scanlines make up the period of time on the PPU which I like \nto call the VINT period. During this time, the PPU makes no access to it's \nexternal memory (i.e. name / pattern tables, etc.).\n\n20:\tAfter 20 scanlines worth of time go by (since the VINT flag was set), \nthe PPU starts to render scanlines. This first scanline is a dummy one; \nalthough it will access it's external memory in the same sequence it would \nfor drawing a valid scanline, no on-screen pixels are rendered during this \ntime, making the fetched background data immaterial. Both horizontal *and* \nvertical scroll counters are updated (presumably) at cc offset 256 in this \nscanline. Other than that, the operation of this scanline is identical to \nany other. The primary reason this scanline exists is to start the object \nrender pipeline, since it takes 256 cc's worth of time to determine which \nobjects are in range or not for any particular scanline.\n\n21..260: after rendering 1 dummy scanline, the PPU starts to render the \nactual data to be displayed on the screen. This is done for 240 scanlines, \nof course.\n\n261:\tafter the very last rendered scanline finishes, the PPU does nothing \nfor 1 scanline (i.e. the programmer gets screwed out of perfectly good VINT \ntime). When this scanline finishes, the VINT flag is set, and the process of \ndrawing lines starts all over again.\n\n\n+--------------------------+\n|Scanline rendering details|\n+--------------------------+\nNaturally, the PPU will fetch data from name, attribute, and pattern tables \nduring a scanline to produce an image on the screen. This section details \nthe PPU's doings during this time.\n\nAs explained before, external PPU memory can be accessed every 2 cc's. With \n341 cc's per scanline, this gives the PPU enough time to make 170 memory \naccesses per scanline (and it uses all of them!). After the 170th fetch, the \nPPU does nothing for 1 clock cycle. Remember that a single pixel is rendered \nevery clock cycle.\n\n\nMemory fetch phase 1 thru 128\n-----------------------------\n1. Name table byte\n2. Attribute table byte\n3. Pattern table bitmap #0\n4. Pattern table bitmap #1\n\nThis process is repeated 32 times (32 tiles in a scanline).\n\n\nThis is when the PPU retrieves the appropriate data from PPU memory for \nrendering the playfield. The first playfield tile fetched here is actually \nthe 3rd to be drawn on the screen (the playfield data for the first 2 tiles \nto be rendered on this scanline are fetched at the end of the scanline prior \nto this one).\n\nAll valid on-screen pixel data arrives at the PPU's video out pin during \nthis time (256 clocks). For determining the precise delay between when a \ntile's bitmap fetch phase starts (the whole 4 memory fetches), and when the \nfirst pixel of that tile's bitmap data hits the video out pin, the formula \nis (16-n) clock cycles, where n is the fine horizontal scroll offset (0..7 \npixels). This information is relivant for understanding the exact timing \noperation of the \"object 0 collision\" flag.\n\nNote that the PPU fetches an attribute table byte for every 8 sequential \nhorizontal pixels it draws. This essentially limits the PPU's color area \n(the area of pixels which are forced to use the same 3-color palette) to \nonly 8 horizontally sequential pixels.\n\nIt is also during this time that the PPU evaluates the \"Y coordinate\" \nentries of all 64 objects in object attribute RAM (OAM), to see if the \nobjects are within range (to be drawn on the screen) for the *next* scanline \n(this is why Y-coordinate entries in the OAM must be programmed to a value 1 \nless than the scanline the object is to appear on). Each evaluation \n(presumably) takes 4 clock cycles, for a total of 256 (which is why it's \ndone during on-screen pixel rendering).\n\n\nIn-range object evaluation\n--------------------------\nAn 8-bit comparator is used to calculate the 9-bit difference between the \ncurrent scanline (minus 21), and each Y-coordinate (plus 1) of every object \nentry in the OAM. Objects are considered in range if the comparator produces \na difference in the range of 0..7 (if $2000.5 currently = 0), or 0..15 (if \n$2000.5 currently = 1).\n\n(Note that a 9-bit comparison result is generated. This means that setting \nobject scanline coordinates for ranges -1..-15 are actually interpreted as \nranges 241..255. For this reason, objects with these ranges will never be \nconsidered to be part of any on-screen scanline range, and will not allow \nsmooth object scrolling off the top of the screen.)\n\nTile index (8 bits), X-coordinate (8 bits), & attribute information (4 bits; \nvertical inversion is excluded) from the in-range OAM element, plus the \nassociated 4-bit result of the range comparison accumulate in a part of the \nPPU called the \"sprite temporary memory\". Logical inversion is applied to \nthe loaded 4-bit range comparison result, if the object's vertical inversion \nattribute bit is set.\n\nSince object range evaluations occur sequentially through the OAM (starting \nfrom entry 0 to 63), the sprite temporary memory always fills in order from \nthe highest priority in-range object, to lower ones. A 4-bit \"in-range\" \ncounter is used to determine the number of found objects on the scanline \n(from 0 up to 8), and serves as an index pointer for placement of found \nobject data into the 8-element sprite temporary memory. The counter is reset \nat the beginning of the object evaluation phase, and is post-incremented \neverytime an object is found in-range. This occurs until the counter equals \n8, when found object data after this is discarded, and a flag (bit 5 of \n$2002) is raised, indicating that it is going to be dropping objects for the \nnext scanline.\n\nAn additional memory bit associated with the sprite temporary memory is used \nto indicate that the primary object (#0) was found to be in range. This will \nbe used later on to detect primary object-to-playfield pixel collisions.\n\n\nPlayfield render pipeline details\n---------------------------------\nAs pattern table & palette select data is fetched, it is loaded into \ninternal latches (the palette select data is selected from the fetched byte \nvia a 2-bit 1-of-4 selector).\n\nAt the start of a new tile fetch phase (every 8 cc's), both latched pattern \ntable bitmaps are loaded into the upper 8-bits of 2- 16-bit shift registers \n(which both shift right every clock cycle). The palette select data is also \ntransfered into another latch during this time (which feeds the serial \ninputs of 2 8-bit right shift registers shifted every clock). The pixel data \nis fed into these extra shift registers in order to implement fine \nhorizontal scrolling, since the periods when the PPU fetch tile data is \nfixed.\n\nA single bit from each shift register is selected, to form the valid 4-bit \nplayfield pixel for the current clock cycle. The bit selection offset is \nbased on the fine horizontal scroll value (this selects bit positions 0..7 \nfor all 4 shift registers). The selected 4-bit pixel data will then be fed \ninto the multiplexer (described later) to be mixed with object data.\n\n\nMemory fetch phase 129 thru 160\n-------------------------------\n1. Garbage name table byte\n2. Garbage name table byte\n3. Pattern table bitmap #0 for applicable object (for next scanline)\n4. Pattern table bitmap #1 for applicable object (for next scanline)\n\nThis process is repeated 8 times.\n\n\nThis is the period of time when the PPU retrieves the appropriate pattern \ntable data for the objects to be drawn on the *next* scanline. When less \nthan 8 objects exist on the next scanline (as the in-range object evaluation \ncounter indicates), dummy pattern table fetches take place for the remaining \nfetches. Internally, the fetched dummy-data is discarded, and replaced with \ncompletely transparent bitmap patterns).\n\nAlthough the fetched name table data is thrown away, and the name table \naddress is somewhat unpredictable, the address does seem to relate to the \nfirst name table tile to be fetched for the next scanline. This would seem \nto imply that PPU cc #256 is when the PPU's scroll/address counters have \ntheir horizontal scroll values automatically updated.\n\nIt should also be noted that because this fetch is required for objects on \nthe next scanline, it is neccessary for a garbage scanline to exist prior to \nthe very first scanline to be actually rendered, so that object attribute \nRAM entries can be evaluated, and the appropriate bitmap data retrieved.\n\nAs far as the wasted fetch phases here, this is because Nintendo wanted to \nreuse the playfield pattern table fetch hardware.\n\n\nDetails of object pattern fetch & render\n----------------------------------------\nWhere the PPU fetches pattern table data for an individual object is \nconditioned on the contents of the sprite temporary memory element, and \n$2000.5. If $2000.5 = 0, the tile index data is used as usual, and $2000.3 \nselects the pattern table to use. If $2000.5 = 1, the MSB of the range \nresult value become the LSB of the indexed tile, and the LSB of the tile \nindex value determines pattern table selection. The lower 3 bits of the \nrange result value are always used as the fine vertical offset into the \nselected pattern.\n\nHorizontal inversion (bit order reversing) is applied to fetched bitmaps, if \nindicated in the sprite temporary memory element.\n\nThe fetched pattern table data (which is 2 bytes), plus the associated 3 \nattribute bits (palette select & priority), and the x coordinate byte in \nsprite temporary memory are then loaded into a part of the PPU called the \n\"sprite buffer memory\" (the primary object present bit is also copied). This \nmemory area again, is large enough to hold the contents for 8 sprites.\n\nThe composition of one sprite buffer element here is: 2 8-bit shift \nregisters (the fetched pattern table data is loaded in here, where it will \nbe serialized at the appropriate time), a 3-bit latch (which holds the color \n& priority data for an object), and an 8-bit down counter (this is where the \nx coordinate is loaded).\n\nThe counter is decremented every time the PPU renders a pixel (the first 256 \ncc's of a scanline; see \"Memory fetch phase 1 thru 128\" above). When the \ncounter equals 0, the pattern table data in the shift registers will start \nto serialize (1 shift per clock). Before this time, or 8 clocks after, \nconsider the outputs of the serializers for each stage to be 0 \n(transparency).\n\nThe streams of all 8 object serializers are prioritized, and ultimately only \none stream (with palette select & priority information) is selected for \noutput to the multiplexer (where object & playfield pixels are prioritized).\n\nThe data for the first sprite buffer entry (including the primary object \npresent flag) has the first chance to enter the multiplexer, if it's output \npixel is non-transparent (non-zero). Otherwise, priority is passed to the \nnext serializer in the sprite buffer memory, and the test for \nnon-transparency is made again (the primary object present status will \nalways be passed to the multiplexer as false in this case). This is done \nuntil the last (8th) stage is reached, when the object data is passed \nthrough unconditionally. Keep in mind that this whole process occurs every \nclock cycle (hardware is used to determine priority instantly).\n\n\nMultiplexer operation\n---------------------\nThe multiplexer does 2 things: determines primary object collisions, and \ndecides which pixel data to pass through to index the palette RAM- either \nthe playfield's or the object's.\n\nPrimary object collisions occur when a non-transparent playfield pixel \ncoincides with a non-transparent object pixel, while the primary object \npresent status entering the multiplexer for the current clock cycle is true. \nThis causes a flip-flop ($2002.6) to be set, and remains set until the next \nframe starts to be rendered again.\n\nThe decision for selecting the data to pass through to the palette index is \nmade rather easilly. The condition to use object (opposed to playfield) data \nis:\n\n(OBJpri=foreground OR PFpixel=xparent) AND OBJpixel<>xparent\n\nSince the PPU has 2 palettes; one for objects, and one for playfield, the \nappropriate palette will be selected depending on which pixel data is passed \nthrough.\n\nAfter the palette look-up, the operation of events follows the \naforementioned steps in the \"video signal generation\" section.\n\n\nMemory fetch phase 161 thru 168\n-------------------------------\n1. Name table byte\n2. Attribute table byte\n3. Pattern table bitmap #0 (for next scanline)\n4. Pattern table bitmap #1 (for next scanline)\n\nThis process is repeated 2 times.\n\n\nIt is during this time that the PPU fetches the appliciable playfield data \nfor the first and second tiles to be rendered on the screen for the *next* \nscanline. These fetches initialize the internal playfield pixel pipelines \n(2- 16-bit shift registers) with valid bitmap data. The rest of tiles \n(3..32) are fetched at the beginning of the following scanline.\n\n\nMemory fetch phase 169 thru 170\n-------------------------------\n1. Name table byte\n2. Name table byte\n\n\nI'm unclear of the reason why this particular access to memory is made. The \nname table address that is accessed 2 times in a row here, is also the same \nnametable address that points to the 3rd tile to be rendered on the screen \n(or basically, the first name table address that will be accessed when the \nPPU is fetching playfield data on the next scanline).\n\n\nAfter memory access 170\n-----------------------\nThe PPU simply rests for 1 cycle here (or the equivelant of half a memory \naccess cycle) before repeating the whole pixel/scanline rendering process.\n\n\n+------------------+\n|Extra cycle frames|\n+------------------+\nScanline 20 is the only scanline that has variable length. On every odd \nframe, this scanline is only 340 cycles (the dead cycle at the end is \nremoved). This is done to cause a shift in the NTSC colorburst phase.\n\nYou see, a 3.58 MHz signal, the NTSC colorburst, is required to be modulated \ninto a luminance carrying signal in order for color to be generated on an \nNTSC monitor. Since the PPU's video out consists of basically square waves \n(as opposed to sine waves, which would be preferred), it takes an entire \ncolorburst cycle (1/3.58 MHz) for an NTSC monitor to identify the color of a \nPPU pixel accurately.\n\nBut now you remember that the PPU renders pixels at 5.37 MHz- 1.5x the rate \nof the colorburst. This means that if a single pixel resides on a scanline \nwith a color different to those surrounding it, the pixel will probably be \nmisrepresented on the screen, sometimes appearing faintly.\n\nWell, to somewhat fix this problem, they added this extra pixel into every \nodd frame (shifting the colorburst phase over a bit), and changing the way \nthe monitor interprets isolated colored pixels each frame. This is why when \nyou play games with detailed background graphics, the background seems to \nflicker a bit. Once you start scrolling the screen however, it seems as if \nsome pixels become invisible; this is how stationary PPU images would look \nwithout this cycle removed from odd frames.\n\nCertain scroll rates expose this NTSC PPU color caveat regardless of the \ntoggling phase shift. Some of Zelda 2's dungeon backgrounds are a good place \nto see this effect.\n\n\n+---------------------------+\n|The MMC3's scanline counter|\n+---------------------------+\nAs most people know, the MMC3 bases it's scanline counter on PPU address \nline A13 (which is why IRQ's can be fired off manually by toggling A13 a \nbunch of times via $2006). What's not common knowledge is the number of \ntimes A13 is expected to toggle in a scanline (although if you've been \npaying close attention to the doc here, you should already know ;)\n\nA13 was probably used for the IRQ counter (as opposed to using the PPU's \n/READ line) because this address line already needed to be connected to the \nMMC for bankswitching purposes (so in other words, to reduce the MMC3's pin \ncount by 1). They also probably used this method of counting (as opposed to \na CPU cycle counter) since A13 cycles (0 -> 1) exactly 42 times per \nscanline, whereas the CPU count of cycles per scanline is not an exact \ninteger (113.67). Having said that, I guess Nintendo wanted to provide an \n\"easy-to-use\" method of generating special image effects, without making \nprogrammers have to figure out how many clock cycles to program an IRQ \ncounter with (a pretty lame excuse for not providing an IRQ counter with CPU \nclock cycle precision (which would have been more useful and versatile)).\n\nRegardless of any values PPU registers are programmed with, A13 will operate \nin a predictable fashion during image rendering (and if you understand how \nPPU addressing works, you should understand that A13 is the *only* address \nline with fixed behaviour during image rendering).\n\n\n+------------------------+\n|PPU pixel priority quirk|\n+------------------------+\nObject data is prioritized between itself, then prioritized between the \nplayfield. There are some odd side effects to this scheme of rendering, \nhowever. For instance, imagine a low priority object pixel with foreground \npriority, a high priority object pixel with background priority, and a \nplayfield pixel all coinciding (all non-transparent).\n\nIdeally, the playfield is considered to be the middle layer between \nbackground and foreground priority objects. This means that the playfield \npixel should hide the background priority object pixel (regardless of object \npriority), and the foreground priority object should appear atop the PF \npixel.\n\nHowever, because of the way the PPU renders (as just described), OBJ \npriority is evaluated first, and therefore the background object pixel wins, \nwhich means that you'll only be seeing the PF pixel after this mess.\n\nA good game to demonstrate this behaviour is Megaman 2. Go into airman's \nstage. First, jump into the energy bar, just to confirm that megaman's \nsprite is of a higher priority than the energy bar's. Now, get to the second \nhalf of the stage, where the clouds cover the energy bar. The energy bar \nwill be ontop of the clouds, but megaman will be behind them. Now, look what \nhappens when you jump into the energy bar here... you see the clouds where \nmegaman underlaps the energy bar.\n\n\n+----------------------------------------+\n|PPU scrolling & addressing in a nutshell|\n+----------------------------------------+\nThe upcoming chart is a 2-dimensional matrix representing how address/data \nentering/leaving the PPU relates to it's internal counters and registers.\n\nThe top row of the diagram reprents data entering the PPU, and how internal \nPPU registers are directly effected by it. The left column here describes \nthe means of how the data enters the PPU (either by programming PPU \nregisters, or when the PPU fetches data off the VRAM data bus), and the \nright column shows how the bits of the written data is mapped to the \ninternal PPU registers (the bits of these registers are then reprogrammed \nwith value of the specified data bit). Numbers 0..7 are used here to \nrepresent the data bits written (numbers bits not displayed here means that \nthis data is unused), and \"-\" is used to indicate that a binary value of 0 \nis to be written.\n\nThe middle row and right column of the diagram represents a model of the \nPPU's internal counters and latches directly related to \nscrolling/addressing. The top row of the blocks represent the \nlatches/registers. If a bottom row to the blocks exist, these are counters \nthat when loaded, load with the value of the latches directly atop them. The \noperation of the counters, and when they are loaded, will be described \nlater.\n\nThe bottom row of the diagram represents how the status of internal PPU \nregisters/counters effect PPU address lines. The description of the columns \nhere are similar to the first row's. However, the digits appearing in the \nright column now represent the PPU's physical address lines 0..13 \n(hexidecimal digits are used in the diagram). The absence of address line \n#'s not appearing here are explained by the notes written next to the access \ntype description in the left column. Finally, address bits that map to PPU \nregisters which have counters below them get their signal only from the \ncounter part of the device, never the latch (top) part.\n\n\n\nregister/counter nomenclature\n-----------------------------\nNT:\tname table\nAT:\tattribute/color table\nPT:\tpattern table\nFV:\tfine vertical scroll latch/counter\nFH:\tfine horizontal scroll latch\nVT:\tvertical tile index latch/counter\nHT:\thorizontal tile index latch/counter\nV:\tvertical name table selection latch/counter\nH:\thorizontal name table selection latch/counter\nS:\tplayfield pattern table selection latch\nPAR:\tpicture address register (as named in patent document)\nAR:\ttile attribute (palette select) value latch\n/1:\tfirst  write to 2005 or 2006 since reading 2002\n/2:\tsecond write to 2005 or 2006 since reading 2002\n\n\nͻ\n2000                 1  0                     4               \n2005/1                            76543  210                  \n2005/2          210        76543                              \n2006/1          -54  3  2  10                                 \n2006/2                       765  43210                       \nNT read                                          76543210     \nAT read (4)                                                10 \nĶ\n               ͻͻͻͻͻͻͻͻͻ\nPPU registers   FVVH   VT   HT FHS     PARAR\nPPU counters   ĶĶĶĶĶͼͼͼͼ\n               ͼͼͼͼͼ                      \nĶ\n2007 access      DC  B  A  98765  43210                       \nNT read (1)          B  A  98765  43210                       \nAT read (1,2,4)      B  A  543c   210b                        \nPT read (3)     210                           C  BA987654     \nͼ\nnotes:\n\n1: address lines DC = 10.\n\n2: address lines 9876 = 1111.\n\n3: address line D = 0. address line 3 relates to the pattern table fetch\noccuring (the PPU always makes them in pairs).\n\n4: The PPU has an internal 4-position, 2-bit shifter, which it uses for \nobtaining the 2-bit palette select data during an attribute table byte \nfetch. To represent how this data is shifted in the diagram, letters c..a \nare used in the diagram to represent the 3-bit right-shift position amount \nto apply to the data read from the attribute data (a is always 0). This is \nwhy you only see bits 0 and 1 used off the read attribute data in the \ndiagram.\n\n\nCounter operation\n-----------------\nDuring picture rendering, or VRAM access via 2007, the scroll counters (FV, \nV, H, VT & HT) increment. The fashion in which they increment is determined \nby the type of VRAM access the PPU is doing.\n\n\nVRAM access via 2007\n--------------------\nIf the VRAM address increment bit (2000.2) is clear (inc. amt. = 1), all the \nscroll counters are daisy-chained (in the order of HT, VT, H, V, FV) so that \nthe carry out of each counter controls the next counter's clock rate. The \nresult is that all 5 counters function as a single 15-bit one. Any access to \n2007 clocks the HT counter here.\n\nIf the VRAM address increment bit is set (inc. amt. = 32), the only \ndifference is that the HT counter is no longer being clocked, and the VT \ncounter is now being clocked by access to 2007.\n\n\nVRAM access during rendering\n----------------------------\nBecause of how name table data is organized, the counters cannot operate in \nthe same fashion as they do during 2007 access. During the time screen data \nis to be rendered (when 2001.3 or 2001.4 is 1, and scanline range (relative \nto VINT) is 20..260), 2 counters are established in the PPU (to fetch name, \nattribute, and pattern table data), and are clocked as will be described.\n\nThe first one, the horizontal scroll counter, consists of 6 bits, and is \nmade up by daisy-chaining the HT counter to the H counter. The HT counter is \nthen clocked every 8 pixel dot clocks (or every 8/3 CPU clock cycles).\n\nThe second counter, the vertical scroll, is 9 bits, and is made up by \ndaisy-chaining FV to VT, and VT to V. FV is clocked by the PPU's horizontal \nblanking impulse, and therefore will increment every scanline. VT operates \nhere as a divide-by-30 counter, and will only generate a carry condition \nwhen the count increments from 29 to 30 (the counter will also reset). \nDividing by 30 is neccessary to prevent attribute data in the name tables \nfrom being used as tile index data.\n\n\ncounter loading/updating\n------------------------\nThere are 2 conditions that update all 5 PPU scroll counters with the \ncontents of the latches adjacent to them. The first is after a write to \n2006/2. The second, is at the beginning of scanline 20, when the PPU starts \nrendering data for the first time in a frame (this update won't happen if \nall rendering is disabled via 2001.3 and 2001.4).\n\nThere is one condition that updates the H & HT counters, and that is at the \nend of the horizontal blanking period of a scanline. Again, image rendering \nmust be occuring for this update to be effective.\n\n\nestablishing full split screen scrolls mid-frame\n------------------------------------------------\nalthough it is not possible to update FV to any desired value mid-screen \nexclusively via 2006 (since the MSB is zero'd out from the write), it is \npossible to mix writes to 2005 & 2006 together, so that it is possible. By \nresetting the 2005/2006 pointer flip-flop (by reading $2002), writing bytes \nto the below registers in this sequence, will allow all scroll counters to \nbe updated with ANY desired value, including FV. Note that only relivant \nupdates are mentioned, since data in the scroll latches is overwritten many \ntimes in the example below.\n\nreg\tupdate\n---\t------\n2006:\tnametable toggle bits (V, H).\n2005:\tFV & bits 3,4 of VT.\n2005:\tFH. This is effective immediately.\n2006:\tHT & bits 0,1,2 of VT.\n\nIt is on the last write to 2006 that all values previously written will be \nloaded into the scroll counters.\n\n\nEOF\n\n"
  },
  {
    "path": "docs/ppu/ppu_scrolling.txt",
    "content": "Subject: [nesdev] the skinny on nes scrolling\r\nDate: Tue, 13 Apr 1999 16:42:00 -0600\r\nFrom: loopy <zxcvzxcv@netzero.net>\r\nReply-To: nesdev@onelist.com\r\nTo: nesdev@onelist.com\r\n\r\nFrom: loopy <zxcvzxcv@netzero.net>\r\n\r\n---------\r\nthe current information on background scrolling is sufficient for most games;\r\nhowever, there are a few that require a more complete understanding.\r\n\r\nhere are the related registers:\r\n        (v) vram address, a.k.a. 2006 which we all know and love.  (16 bits)\r\n        (t) another temp vram address (16 bits)\r\n           (you can really call them 15 bits, the last isn't used)\r\n       (x) tile X offset (3 bits)\r\n\r\nthe ppu uses the vram address for both reading/writing to vram thru 2007,\r\nand for fetching nametable data to draw the background.  as it's drawing the\r\nbackground, it updates the address to point to the nametable data currently\r\nbeing drawn.  bits 0-11 hold the nametable address (-$2000).  bits 12-14 are\r\nthe tile Y offset.\r\n\r\n---------\r\nstuff that affects register contents:\r\n(sorry for the shorthand logic but i think it's easier to see this way)\r\n\r\n2000 write:\r\n        t:0000110000000000=d:00000011\r\n2005 first write:\r\n        t:0000000000011111=d:11111000\r\n        x=d:00000111\r\n2005 second write:\r\n        t:0000001111100000=d:11111000\r\n        t:0111000000000000=d:00000111\r\n2006 first write:\r\n        t:0011111100000000=d:00111111\r\n        t:1100000000000000=0\r\n2006 second write:\r\n        t:0000000011111111=d:11111111\r\n        v=t\r\nscanline start (if background and sprites are enabled):\r\n        v:0000010000011111=t:0000010000011111\r\nframe start (line 0) (if background and sprites are enabled):\r\n        v=t\r\n\r\nnote!  2005 and 2006 share the toggle that selects between first/second\r\nwrites.  reading 2002 will clear it.\r\n\r\nnote!  all of this info agrees with the tests i've run on a real nes.  BUT\r\nif there's something you don't agree with, please let me know so i can verify\r\nit.\r\n\r\n________________________________________________________\r\nNetZero - We believe in a FREE Internet.  Shouldn't you?\r\nGet your FREE Internet Access and Email at\r\nhttp://www.netzero.net/download.html\r\n\r\n------------------------------------------------------------------------\r\nNew hobbies? New curiosities? New enthusiasms?\r\nhttp://www.ONElist.com\r\nSign up for a new e-mail list today!\r\n\r\n\r\nSubject: [nesdev] Re: the skinny on nes scrolling\r\nDate: Tue, 13 Apr 1999 17:48:54 -0600\r\nFrom: loopy <zxcvzxcv@netzero.net>\r\nReply-To: nesdev@onelist.com\r\nTo: nesdev@onelist.com\r\n\r\nFrom: loopy <zxcvzxcv@netzero.net>\r\n\r\n(more notes on ppu logic)\r\n\r\nyou can think of bits 0,1,2,3,4 of the vram address as the \"x scroll\"(*8)\r\nthat the ppu increments as it draws.  as it wraps from 31 to 0, bit 10 is\r\nswitched.  you should see how this causes horizontal wrapping between name\r\ntables (0,1) and (2,3).\r\n\r\nyou can think of bits 5,6,7,8,9 as the \"y scroll\"(*8).  this functions\r\nslightly different from the X.  it wraps to 0 and bit 11 is switched when\r\nit's incremented from _29_ instead of 31.  there are some odd side effects\r\nfrom this.. if you manually set the value above 29 (from either 2005 or\r\n2006), the wrapping from 29 obviously won't happen, and attrib data will be\r\nused as name table data.  the \"y scroll\" still wraps to 0 from 31, but\r\nwithout switching bit 11.  this explains why writing 240+ to 'Y' in 2005\r\nappeared as a negative scroll value.\r\n\r\n________________________________________________________\r\nNetZero - We believe in a FREE Internet.  Shouldn't you?\r\nGet your FREE Internet Access and Email at\r\nhttp://www.netzero.net/download.html\r\n\r\n------------------------------------------------------------------------\r\nLooking for a new hobby?  Want to make a new friend?\r\nhttp://www.ONElist.com\r\nCome join one of the 115,000 e-mail communities at ONElist!\r\n"
  },
  {
    "path": "docs/ppu/read_buffer_test_readme.txt",
    "content": "NES PPU Read Buffer Tests\n----------------------------------\nThis mammoth test pack tests many aspects of the NES system,\nmostly centering around the PPU $2007 read buffer.\n\nThe test will take about 20 seconds.\n\nThe program attempts to do as many tests as possible before\nreporting the result. When the screen is blanked for a long\ntime, audio is used to report progress. A low-pitched fat\ntone indicates failure; bright beeps indicate progress.\n\nIf a sub-test fails, at a certain point the list of all\nfailed tests is provided in a numeric form, and a textual\nexplanation of the first failed test is shown.\n\n\nFull list of tests performed is below.\nNote that the tests are not performed in a numerical order.\nFor example, test #47 (does palette reading work at all) is\nperformed before test #7 (does sequential palette reading work).\n\n\tTest  2 (TEST_PPUMEMORYIO):\n\n\tPPU memory I/O does not work.\n\tPossible areas of problem:\n\t- PPU not implemented\n\t- PPU memory writing ($2007)\n\t- PPU memory reading ($2007)\n\t- PPU memory area $2C00-$2FFF\n\n\tTest  3 (TEST_ONEBYTEBUFFER):\n\n\tNon-palette PPU memory reads\n\tshould have one-byte buffer.\n\n\tTest  4 (TEST_CIRAM_READ):\n\n\tCIRAM reading\n\tdoes not work.\n\n\tTest  5 (TEST_CIRAM_SEQ_READ_1):\n\n\tSequential CIRAM reading\n\twith  1-byte increment\n\tdoes not work.\n\n\tTest  6 (TEST_CIRAM_SEQ_READ_32):\n\n\tSequential CIRAM reading\n\twith 32-byte increment\n\tdoes not work.\n\n\tTest  7 (TEST_PALETTE_RAM_SEQ_READ_1):\n\n\tSequential PALETTE reading\n\twith  1-byte increment\n\tdoes not work.\n\n\tTest  8 (TEST_PALETTE_RAM_SEQ_READ_32):\n\n\tSequential PALETTE reading\n\twith 32-byte increment\n\tdoes not work.\n\n\tTest  9 (TEST_CHRROM_READ):\n\n\tCHR-ROM reading\n\tdoes not work.\n\n\tTest 10 (TEST_CHRROM_SEQ_READ_1):\n\n\tSequential CHR-ROM reading\n\twith  1-byte increment\n\tdoes not work.\n\n\tTest 11 (TEST_CHRROM_SEQ_READ_32):\n\n\tSequential CHR-ROM reading\n\twith 32-byte increment\n\tdoes not work.\n\n\tTest 12 (TEST_CIRAM_SEQ_WRITE_1):\n\n\tSequential CIRAM writes\n\twith  1-byte increment\n\tdoes not work.\n\n\tTest 13 (TEST_CIRAM_SEQ_WRITE_32):\n\n\tSequential CIRAM writes\n\twith 32-byte increment\n\tdoes not work.\n\n\tTest 14 (TEST_NTA_MIRRORING_FAIL_1NTA):\n\n\t1-nametable setup seems to\n\tbe active, even though this\n\tROM is explicitly configured\n\tfor horizontal mirroring.\n\n\tTest 15 (TEST_NTA_MIRRORING_FAIL_4NTA):\n\n\tFour-screen setup seems to\n\tbe active, even though this\n\tROM is explicitly configured\n\tfor horizontal mirroring.\n\n\tTest 16 (TEST_NTA_MIRRORING_FAIL_VERT):\n\n\tVertical mirroring seems to\n\tbe active, even though this\n\tROM is explicitly configured\n\tfor horizontal mirroring.\n\n\tTest 17 (TEST_PPU_OPEN_BUS):\n\n\tAny data that is transferred\n\tthrough PPU I/O should linger\n\tand be readable for a while\n\tin any PPU register that does\n\tnot have a read function.\n\tThis is called \"open bus\".\n\tTo minimally pass this test,\n\tyou need to at least provide\n\ta bridge between $2003(W) and\n\t$2000(R).\n\n\tTest 18 (TEST_PPU_OPEN_BUS_SHORTCUT):\n\n\tReading a write-only PPU\n\tregister should not just give\n\tthe current value of SPRADDR.\n\tThat would be a too lazy\n\tworkaround for a failed test!\n\n\tTest 19 (TEST_PPU_OPENBUS_MUST_NOT_COPY_READBUFFER):\n\n\tPPU memory read buffer is not\n\tthe open bus. Reading the bus\n\tshould repeat the last value\n\tthat was transferred, not\n\tdisclose the buffered byte.\n\n\tTest 20 (TEST_PPU_OPENBUS_FROM_WRITE2000_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2000 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 21 (TEST_PPU_OPENBUS_FROM_WRITE2001_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2001 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 22 (TEST_PPU_OPENBUS_FROM_WRITE2002_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2002 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 23 (TEST_PPU_OPENBUS_FROM_WRITE2003_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2003 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 24 (TEST_PPU_OPENBUS_FROM_WRITE2004_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2004 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 25 (TEST_PPU_OPENBUS_FROM_WRITE2005_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2005 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 26 (TEST_PPU_OPENBUS_FROM_WRITE2006_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2006 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 27 (TEST_PPU_OPENBUS_FROM_WRITE2007_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2007 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 28 (TEST_PPU_OPENBUS_FROM_READ2000_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2000 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 29 (TEST_PPU_OPENBUS_FROM_READ2001_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2001 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 30 (TEST_PPU_OPENBUS_FROM_READ2002_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2002 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 31 (TEST_PPU_OPENBUS_FROM_READ2003_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2003 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 32 (TEST_PPU_OPENBUS_FROM_READ2004_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2004 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 33 (TEST_PPU_OPENBUS_FROM_READ2005_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2005 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 34 (TEST_PPU_OPENBUS_FROM_READ2006_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2006 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 35 (TEST_PPU_OPENBUS_INDEXED):\n\n\tSTA $2000,Y with Y=7 must\n\tissue a dummy read to $2007.\n\n\tTest 36 (TEST_PPU_OPENBUS_INDEXED2):\n\n\tSTA $1FF0,Y with Y=$17 mustn't\n\tissue a dummy read to $2007.\n\n\tTest 37 (TEST_PPU_OPENBUS_FROM_READ_MIRROR_MUST_WRITETO_READBUFFER):\n\n\tA read from a mirrored copy\n\tof $2007 must act as if\n\t$2007 was read, and update\n\tthe same read buffer.\n\n\tTest 38 (TEST_PPU_READ_WITH_AND):\n\n\tThe AND instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 39 (TEST_PPU_READ_WITH_ORA):\n\n\tThe ORA instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 40 (TEST_PPU_READ_WITH_EOR):\n\n\tThe EOR instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 41 (TEST_PPU_READ_WITH_CMP):\n\n\tThe CMP instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 42 (TEST_PPU_READ_WITH_CPX):\n\n\tThe CPX instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 43 (TEST_PPU_READ_WITH_CPY):\n\n\tThe CPY instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 44 (TEST_PPU_READ_WITH_ADC):\n\n\tThe ADC instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 45 (TEST_PPU_READ_WITH_SBC):\n\n\tThe SBC instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 46 (TEST_ONEBYTEBUFFER_PALETTE):\n\n\tPalette reads from PPU should\n\tnot have one-byte buffer.\n\n\tTest 47 (TEST_PALETTE_READS):\n\n\tPalette reads from PPU do not\n\tseem to be working at all.\n\n\tTest 48 (TEST_PALETTE_READS_UNRELIABLE):\n\n\tPalette reads  from PPU seem\n\tto work randomly.\n\n\tTest 49 (TEST_PALETTE_MIRRORS):\n\n\tPalette indexes $3F1x should\n\tbe mirrors of $3F0x when\n\tx is 0, 4, 8, or C.\n\n\tTest 50 (TEST_PALETTE_UNIQUE):\n\n\tIt must be possible to store\n\tunique data in each of $3F00,\n\t$3F04, $3F08 and $3F0C.\n\n\tTest 51 (TEST_PPU_PALETTE_WRAP):\n\n\tPPU addresses 3F00-3F1F\n\tshould be mirrored within\n\tthe whole 3F00-3FFF region,\n\tfor a total of 8 times.\n\n\tTest 52 (TEST_PPU_MEMORY_14BIT_A):\n\n\tFailed sub-test 1 of:\n\tThe two MSB within the PPU\n\tmemory address should be\n\tcompletely ignored in all\n\tcircumstances, effectively\n\tmirroring the 0000-3FFF\n\taddress range within the\n\twhole 0000-FFFF region,\n\tfor a total of 4 times.\n\n\tTest 53 (TEST_PPU_MEMORY_14BIT_B):\n\n\tFailed sub-test 2 of:\n\tThe two MSB within the PPU\n\tmemory address should be\n\tcompletely ignored in all\n\tcircumstances, effectively\n\tmirroring the 0000-3FFF\n\taddress range within the\n\twhole 0000-FFFF region,\n\tfor a total of 4 times.\n\n\tTest 54 (TEST_PPU_MIRROR_3000):\n\n\tPPU memory range 3000-3EFE\n\tshould be a mirror of the\n\tPPU memory range 2000-2EFE.\n\n\tTest 55 (TEST_PPU_READ_3EFF):\n\n\tSetting PPU address to 3EFF\n\tand reading $2007 twice\n\tshould give the data at\n\t$3F00, not the data at $2EFF.\n\n\tTest 56 (TEST_PPU_MIRROR_2F):\n\n\tReading PPU memory range 3Fxx\n\tshould put contents of 2Fxx\n\tinto the read buffer.\n\n\tTest 57 (TEST_PPU_SEQ_READ_WRAP):\n\n\tSetting PPU address to 3FFF\n\t& reading $2007 thrice should\n\tgive the contents of $0000.\n\n\tTest 58 (SEQ_READ_INTERNAL):\n\n\tUnexpected: VROM contents at\n\t$0000 and $1FFF read the same.\n\tThis should never happen in\n\tthis test ROM.\n\n\tTest 59 (TEST_VADDR):\n\n\tRelationship between $2005\n\tand $2006 is not implemented\n\tproperly. Here is a guide.\n\tIt explains which registers\n\tuse which parts of the address.\n\tNote that only the second\n\twrite to $2006 updates the\n\taddress really used by $2007.\n\tFEDCBA9876543210ZYX: bit pos.\n\t  ^^^^^^^^^^^^^^------ =$2007\n\tzz543210-------------- $2006#1\n\t        76543210------ $2006#2\n\t           76543210--- $2005#1\n\t 210--76543----------- $2005#2\n\t    10---------------- $2000\n\n\tTest 60 (TEST_RAM_MIRRORING):\n\n\tCPU RAM at 0000-07FF should\n\tbe mirrored 4 times, in the\n\tfollowing address ranges:\n\t- 0000-07FF\n\t- 0800-0FFF\n\t- 1000-17FF\n\t- 1800-1FFF\n\n\tTest 61 (TEST_PPUIO_MIRRORING):\n\n\tPPU I/O memory at 2000-2007\n\tshould be mirrored within the\n\twhole 2000-3FFF region, for\n\ta total of 1024 times.\n\n\tTest 62 (TEST_SPHIT_AND_VBLANK):\n\n\tSprite 0 hit flag should not\n\tread as set during vblank.\n\n\tTest 63 (TEST_SPHIT_DIRECT):\n\n\tSprite 0 hit test by poking\n\tdata directly into $2003-4\n\t^ Possible causes for failure:\n\t- $2003/$2004 not implemented\n\t- No sprite 0 hit tests\n\t- Way too long vblank period\n\n\tTest 64 (TEST_SPHIT_DIRECT_READBUFFER):\n\n\tSending 5 bytes of data into\n\t$2003 and $2004 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 65 (TEST_SPHIT_DMA_ROM):\n\n\tSprite 0 hit test using DMA\n\t($4014) using ROM as source\n\t^ Possible causes for failure:\n\t- $4014 DMA cannot read from\n\t  anything other than RAM\n\n\tTest 66 (TEST_SPHIT_DMA_READBUFFER):\n\n\tInvoking a $4014 DMA with a\n\tnon-$20 value  must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 67 (TEST_SPHIT_DMA_PPU_BUS):\n\n\tSprite 0 hit test using DMA\n\t($4014) using PPU I/O bus\n\tas source\n\t^ In this test, $4014 <- #$20.\n\t  Possible causes for failure:\n\t- DMA does not do proper reads\n\t- PPU bus does not preserve\n\t  last transferred values\n\t- $2002 read returned a value\n\t  that differs from expected\n\t- $2004 read modifies the OAM\n\n\tTest 68 (TEST_DMA_PPU_SIDEEFFECT):\n\n\tWriting $20 into $4014 should\n\tgenerate 32 reads into $2007\n\tas a side-effect, each time\n\tincrementing the PPU read\n\taddress.\n\n\tTest 69 (TEST_SPHIT_DMA_RAM):\n\n\tSprite 0 hit test using DMA.\n\tAll internal RAM pages are\n\ttested, including mirrored\n\taddresses. Failing the test\n\tmay imply faulty mirroring.\n\n\tTest 70 (TEST_CHRROM_READ_BANKED):\n\n\tCHR ROM read through $2007\n\tdoes not honor mapper 3\n\t(CNROM) bank switching\n\n\tTest 71 (TEST_CHRROM_READ_BANKED_BUFFER):\n\n\tThe $2007 read buffer should\n\tnot retroactively react to\n\tchanges in VROM mapping.\n\tWhen you read $2007, the\n\tdata is stored in a buffer\n\t(\"latch\"), and the previous\n\tcontent of the buffer is\n\treturned. It is not a delayed\n\tread request.\n\n\tTest 72 (TEST_CHRROM_WRITE):\n\n\tCHR ROM on mapper 3 (CNROM boards)\n\tmust not be writable.\n\n\tTest 73 (TEST_BUFFER_DELAY_BLANK_1FRAME):\n\n\tThe PPU read buffer should\n\tsurvive 1 frame of idle\n\twith rendering disabled.\n\n\tTest 74 (TEST_BUFFER_DELAY_BLANK_2SECONDS):\n\n\tThe PPU read buffer should\n\tsurvive 2 seconds of idle\n\twith rendering disabled.\n\n\tTest 75 (TEST_BUFFER_DELAY_INTERNAL):\n\n\tUnexpected: VROM contents at\n\t$1Bxx did not match what was\n\thardcoded into the program.\n\n\tTest 76 (TEST_BUFFER_DELAY_VISIBLE_1FRAME):\n\n\tThe PPU read buffer should\n\tsurvive 1 frame of idle\n\twith rendering enabled.\n\n\tTest 77 (TEST_BUFFER_DELAY_VISIBLE_1SECOND):\n\n\tThe PPU read buffer should\n\tsurvive 1 second of idle\n\twith rendering enabled.\n\n\tTest 78 (TEST_BUFFER_DELAY_VISIBLE_3SECONDS):\n\n\tThe PPU read buffer should\n\tsurvive 3 seconds of idle\n\twith rendering enabled.\n\n\tTest 79 (TEST_BUFFER_DELAY_VISIBLE_7SECONDS):\n\n\tThe PPU read buffer should\n\tsurvive 7 seconds of idle\n\twith rendering enabled.\n\n\n\nExpected output:\n\n  TEST:test_ppu_read_buffer      :)\n  -------------------------------\n  Testing basic PPU memory I/O.\n  Performing tests that combine\n  sprite 0 hit flag, $4014 DMA\n  and the RAM mirroring...\n  Graphical artifacts during\n  this test are OK and expected.\n  \n                Hit  No-Hit\n  Direct poke   OK   OK\n  DMA with ROM  OK   OK\n  DMA + PPU bus OK   OK\n  DMA with RAM  OK   OK\n  -------------------------------\n   This next test will take a while.\n   In order to distract you with\n   entertainment, art is provided.\n   Contemplate on the art while\n   the test is in progress.\n\n   \n  Passed\n  \nThe \":)\" should be blue/purple; the \"OK\" should be brownish\norange, and the \"Graphical artifacts\" paragraph should\nalso be brownish orange. Everything else should be white.\n\nIn the painting by Thomas Kinkade that is shown before\nthe \"Passed\" text appears, the ground should be pleasantly\ngreen.\n\n\nThe text outputted to the $6000 console is\nslightly different than the reference shown above,\nbecause parts of the text above are placed on the\nscreen directly, and for other reasons.\n\n\nBecause this ROM contains a large amount of text and\nsome graphics data, portions of the ROM had to be\ncompressed to avoid increasing the ROM size too much.\nShould one want to rebuild the ROM, a particular set\nof tools will be needed; including nasm, gcc, and php.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe text output may include ANSI color codes, which take the form of\nan esc character ($1B), an opening bracket ('['), and a sequence of\nnumbers and semicolon characters, terminated by a non-digit character ('m').\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\n-- \nJoel Yliluoma <bisqwit@iki.fi>\nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "docs/ppu/sprite_hit_readme.txt",
    "content": "\n\n01-basics\n---------\nTests basic sprite 0 hit behavior (nothing timing related).\n\n2) Flag isn't working at all\n3) Should hit even when completely behind background\n4) Should miss when background rendering is off\n5) Should miss when sprite rendering is off\n6) Should miss when all rendering is off\n7) All-transparent sprite should miss\n8) Only low two palette index bits are relevant\n9) Any non-zero palette index should hit with any other\n10) Should miss when background is all transparent\n11) Should always miss other sprites\n\n\n02-alignment\n------------\nTests alignment of sprite hit with background.\nPlaces a solid background tile in the middle of the screen and\nplaces the sprite on all four edges both overlapping and\nnon-overlapping.\n\n2) Basic sprite-background alignment is way off\n3) Sprite should miss left side of bg tile\n4) Sprite should hit left side of bg tile\n5) Sprite should miss right side of bg tile\n6) Sprite should hit right side of bg tile\n7) Sprite should miss top of bg tile\n8) Sprite should hit top of bg tile\n9) Sprite should miss bottom of bg tile\n10) Sprite should hit bottom of bg tile\n\n\n03-corners\n----------\nTests sprite 0 hit using a sprite with a single pixel set,\nfor each of the four corners.\n\n2) Lower-right pixel should hit\n3) Lower-left pixel should hit\n4) Upper-right pixel should hit\n5) Upper-left pixel should hit\n\n\n04-flip\n-------\nTests sprite 0 hit for single pixel sprite and background.\n\n2) Horizontal flipping doesn't work\n3) Vertical flipping doesn't work\n4) Horizontal + Vertical flipping doesn't work\n\n\n05-left_clip\n------------\nTests sprite 0 hit with regard to clipping of left 8 pixels of screen.\n\n2) Should miss when entirely in left-edge clipping\n3) Left-edge clipping occurs when $2001 is not $1E\n4) Left-edge clipping is off when $2001 = $1E\n5) Left-edge clipping should block hits only when X = 0\n6) Should miss; sprite pixel covered by left-edge clip\n7) Should hit; sprite pixel outside left-edge clip\n8) Should hit; sprite pixel outside left-edge clip\n\n\n06-right_edge\n-------------\nTests sprite 0 hit with regard to column 255 (ignored) and off\nright edge of screen.\n\n2) Should always miss when X = 255\n3) Should hit; sprite has pixels < 255\n4) Should miss; sprite pixel is at 255\n5) Should hit; sprite pixel is at 254\n6) Should also hit; sprite pixel is at 254\n\n\n07-screen_bottom\n----------------\nTests sprite 0 hit with regard to bottom of screen.\n\n2) Should always miss when Y >= 239\n3) Can hit when Y < 239\n4) Should always miss when Y = 255\n5) Should hit; sprite pixel is at 238\n6) Should miss; sprite pixel is at 239\n7) Should hit; sprite pixel is at 238\n\n\n08-double_height\n----------------\nTests basic sprite 0 hit double-height operation.\n\n2) Lower sprite tile should miss bottom of bg tile\n3) Lower sprite tile should hit bottom of bg tile\n3) Lower sprite tile should miss top of bg tile\n4) Lower sprite tile should hit top of bg tile\n\n\n09-timing\n---------\nTests sprite 0 hit timing to PPU clock accuracy.\n\n2) PPU VBL timing is wrong\n3) Flag set too soon for upper-left corner\n4) Flag set too late for upper-left corner\n5) Flag set too soon for upper-right corner\n6) Flag set too late for upper-right corner\n7) Flag set too soon for lower-left corner\n8) Flag set too late for lower-left corner\n9) Flag cleared too soon at end of VBL\n10) Flag cleared too late at end of VBL\n\n\n10-timing_order\n---------------\nEnsures that hit time is based on position on screen, and unaffected\nby which pixel of sprite is being hit (upper-left, lower-right, etc.)\n\n2) Upper-left corner\n3) Upper-right corner\n4) Lower-left corner\n5) Lower-right corner\n6) Hit time shouldn't be based on pixels under left clip\n7) Hit time shouldn't be based on pixels at X=255\n8) Hit time shouldn't be based on pixels off right edge\n\nMulti-tests\n-----------\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\nWhen run, they list the subtests as they are run. The final result code\nrefers to the first sub-test that failed. For more information about any\nfailed subtests, run them individually from rom_singles/ and\nnsf_singles/.\n\n\nMulti-tests\n-----------\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\nWhen run, they list the subtests as they are run. The final result code\nrefers to the first sub-test that failed. For more information about any\nfailed subtests, run them individually from rom_singles/ and\nnsf_singles/.\n\n\nMulti-tests\n-----------\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\nWhen run, they list the subtests as they are run. The final result code\nrefers to the first sub-test that failed. For more information about any\nfailed subtests, run them individually from rom_singles/ and\nnsf_singles/.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "docs/ppu/sprite_overflow_readme.txt",
    "content": "\n\n01-basics\n---------\nTests basic operation of sprite overflow flag (bit 5 of $2002).\n\n2) Should set flag when 9 sprites are on scanline\n3) Reading $2002 shouldn't clear flag\n4) Shouldn't clear flag at beginning of VBL\n5) Should clear flag at end of VBL\n6) Shouldn't set flag when $2001=$00\n7) Should set normally when $2001=$08\n8) Should set normally when $2001=$10\n\n\n02-details\n----------\nTests details of sprite overflow flag\n\n2) Should set flag even when sprites are under left clip\n3) Disabling rendering shouldn't clear flag\n4) Should clear flag at the end of VBL even when $2001=0\n5) Should set flag even when sprite Y coordinates are 239\n6) Shouldn't set flag when sprite Y coordinates are 240 (off screen)\n7) Shouldn't set flag when sprite Y coordinates are 255 (off screen)\n8) Should set flag regardless of which sprites are involved\n9) Shouldn't set flag when all scanlines have 7 or fewer sprites\n10) Double-height sprites aren't handled properly\n\n\n03-timing\n---------\nTests sprite overflow flag timing to PPU clock accuracy\n\n2) PPU VBL timing is wrong\n3) PPU VBL timing is wrong\n3) Flag cleared too early at end of VBL\n4) Flag cleared too late at end of VBL\n5) Flag set too early for first scanline\n6) Flag set too late for first scanline\n7) Horizontal positions shouldn't affect timing\n8) Set too early for last sprites on first scanline\n9) Set too late for last sprites on first scanline\n10) Set too early for last scanline\n11) Set too late for last scanline\n12) Set too early when 9th sprite # is way after 8th\n13) Set too late when 9th sprite # is way after 8th\n14) Overflow on second scanline occurs too early\n15) Overflow on second scanline occurs too late\n\n\n04-obscure\n----------\nTests the pathological behavior when 8 sprites are on a scanline\nand the one just after the 8th is not on the scanline. After that,\nthe PPU interprets different bytes of each following sprite as\nits Y coordinate. 1 2 3 4 5 6 7 8 9 10 11 12 13 14: If 1-8 are\non the same scanline, 9 isn't, then the second byte of 10, the\nthird byte of 11, fourth byte of 12, first byte of 13, second byte\nof 14, etc. are treated as those sprites' Y coordinates for the\npurpose of setting the overflow flag. This search continues until\nall sprites have been scanned or one of the (erroneously interpreted)\nY coordinates places the sprite within the scanline.\n\n2) Checks that second byte of sprite #10 is treated as its Y \n3) Checks that third byte of sprite #11 is treated as its Y \n4) Checks that fourth byte of sprite #12 is treated as its Y \n5) Checks that first byte of sprite #13 is treated as its Y \n6) Checks that second byte of sprite #14 is treated as its Y \n7) Checks that search stops at the last (64th) sprite\n8) Same as test #2 but using a different range of sprites\n\n\n05-emulator\n-----------\nTests things that an optimized emulator is likely get wrong\n\n2) Didn't set flag when $2002 wasn't read during frame\n3) Disabling rendering didn't recalculate flag time\n4) Changing sprite RAM didn't recalculate flag time\n5) Changing sprite height didn't recalculate time\n\nMulti-tests\n-----------\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\nWhen run, they list the subtests as they are run. The final result code\nrefers to the first sub-test that failed. For more information about any\nfailed subtests, run them individually from rom_singles/ and\nnsf_singles/.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "docs/ppu/tv_readme.txt",
    "content": "TV pass or fail?\r\n\r\nThis program is designed for NES and tests various aspects of the\r\ndisplay it is connected to.  Press the A Button to switch screens.\r\n\r\n_____________________________________________________________________\r\nNTSC chroma/luma crosstalk\r\n\r\nThe PPU in the PlayChoice arcade system generates RGB video, with\r\nred, green, and blue color information on separate cables.\r\n\r\nThe PPU in the NES generates composite video, with chroma (color)\r\nand luma (brightness) information carried on one cable at different\r\nfrequency bands.  To keep the circuit cheap, it does not perform\r\nproper filtering to keep the chroma from bleeding into the luma.\r\nThis especially has an effect on 45 degree diagonal lines.  But an\r\naccurate emulator must preserve the same artifacts, as games such as\r\nBlaster Master rely on them to create the richest color palette.\r\n\r\nThis screen displays something noticeably different on an NTSC NES\r\nPPU vs. the RGB PPU that most PC based NES emulators emulate.\r\n\r\nDisplay on RGB system:            Display on NTSC system:\r\n,---------.                       ,---------.\r\n|  =====  |                       |  =====  |\r\n|%%%%%%%%%|                       | PASS!   |\r\n|         |                       |         |\r\n| PRESS A |                       | PRESS A |\r\n`---------'                       `---------'\r\n\r\n_____________________________________________________________________\r\nPixel aspect ratio\r\n\r\nPC displays most commonly generate square pixels.  A square pixel\r\non an NTSC display is 7/12 of a chroma cycle wide, but the NES PPU\r\ndid not generate square pixels.  Instead, it generated pixels 8/12\r\nof a chroma cycle wide, which are somewhat wider than they are tall.\r\nThis made games' graphics appear stretched.  If they are displayed\r\nwith square pixels on a PC based emulator, graphics will not appear\r\nwith the intended proportions.\r\n\r\nThis screen shows three rectangles.  One is a square on NTSC NES\r\nand PlayChoice, one is a square on PAL NES, and one is a square\r\nwith square pixels.\r\n\r\n_____________________________________________________________________\r\nLegal\r\n\r\nCopyright 2007 Damian Yerrick\r\nDo not distribute this quick and dirty preview version to the public\r\nuntil it has been tested on an NES.\r\n"
  },
  {
    "path": "docs/ppu/vbl_nmi_readme.txt",
    "content": "NES PPU Tests\n-------------\nThese tests verify the behavior and timing of the NTSC PPU's VBL flag,\nNMI enable, and NMI interrupt. Timing is tested to an accuracy of one\nPPU clock. Note that often the NES starts up with a different value in\nthe clock divider, causing PPU timing to be slightly different and fail\nsome of the tests. These test the timings that have been most fully\ndocumented and emulated.\n\n\n01-vbl_basics\n-------------\nTests basic VBL operation and VBL period.\n\n2) VBL period is way off\n3) Reading VBL flag should clear it\n4) Writing $2002 shouldn't affect VBL flag\n5) $2002 should be mirrored at $200A\n6) $2002 should be mirrored every 8 bytes up to $2FFA\n7) VBL period is too short with BG off\n8) VBL period is too long with BG off\n\n\n02-vbl_set_time\n---------------\nVerifies time VBL flag is set.\n\nReads $2002 twice and prints VBL flags from\nthem. Test is run one PPU clock later each time,\naround the time the flag is set.\n\n00 - V\n01 - V\n02 - V\n03 - V   ; after some resets this is - -\n04 - -   ; flag setting is suppressed\n05 V -\n06 V -\n07 V -\n08 V -\n\n\n03-vbl_clear_time\n-----------------\nTests time VBL flag is cleared.\n\nReads $2002 and prints VBL flag.\nTest is run one PPU clock later each line,\naround the time the flag is cleared.\n\n00 V\n01 V\n02 V\n03 V\n04 V\n05 V\n06 -\n07 -\n08 -\n\n\n04-nmi_control\n--------------\nTests immediate NMI behavior when enabling while VBL flag is already set\n\n2) Shouldn't occur when disabled\n3) Should occur when enabled and VBL begins\n4) $2000 should be mirrored every 8 bytes\n5) Should occur immediately if enabled while VBL flag is set\n6) Shouldn't occur if enabled while VBL flag is clear\n7) Shouldn't occur again if writing $80 when already enabled\n8) Shouldn't occur again if writing $80 when already enabled 2\n9) Should occur again if enabling after disabled\n10) Should occur again if enabling after disabled 2\n11) Immediate occurence should be after NEXT instruction\n\n\n05-nmi_timing\n-------------\nTests NMI timing.\n\nPrints which instruction NMI occurred\nafter. Test is run one PPU clock later\neach line.\n\n00 4\n01 4\n02 4\n03 3\n04 3\n05 3\n06 3\n07 3\n08 3\n09 2\n\n\n06-suppression\n--------------\nTests behavior when $2002 is read near time\nVBL flag is set.\n\nReads $2002 one PPU clock later each time.\nPrints whether VBL flag read back as set, and\nwhether NMI occurred.\n\n00 - N\n01 - N\n02 - N\n03 - N  ; normal behavior\n04 - -  ; flag never set, no NMI\n05 V -  ; flag read back as set, but no NMI\n06 V -\n07 V N  ; normal behavior\n08 V N\n09 V N\n\n\n07-nmi_on_timing\n----------------\nTests NMI occurrence when enabled near time\nVBL flag is cleared.\n\nEnables NMI one PPU clock later on each line.\nPrints whether NMI occurred.\n\n00 N\n01 N\n02 N\n03 N\n04 N\n05 -\n06 -\n07 -\n08 -\n\n\n08-nmi_off_timing\n-----------------\nTests NMI occurrence when disabled near time\nVBL flag is set.\n\nDisables NMI one PPU clock later on each line.\nPrints whether NMI occurred.\n\n03 -\n04 -\n05 -\n06 -\n07 N\n08 N\n09 N\n0A N\n0B N\n0C N\n\n\n09-even_odd_frames\n------------------\nTests clock skipped on every other PPU frame when BG rendering\nis enabled.\n\nTries pattern of BG enabled/disabled during a sequence of\n5 frames, then finds how many clocks were skipped. Prints\nnumber skipped clocks to help find problems.\n\nCorrect output: 00 01 01 02\n\n\n10-even_odd_timing\n------------------\nTests timing of skipped clock every other frame\nwhen BG is enabled.\n\nOutput: 08 08 09 07 \n\n2) Clock is skipped too soon, relative to enabling BG\n3) Clock is skipped too late, relative to enabling BG\n4) Clock is skipped too soon, relative to disabling BG\n5) Clock is skipped too late, relative to disabling BG\n\nMulti-tests\n-----------\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\nWhen run, they list the subtests as they are run. The final result code\nrefers to the first sub-test that failed. For more information about any\nfailed subtests, run them individually from rom_singles/ and\nnsf_singles/.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "docs/ppu/vbl_nmi_timing_readme.txt",
    "content": "NTSC NES PPU VBL/NMI Timing Tests\n---------------------------------\nThese ROMs test the timing of the VBL flag and NMI to an accuracy of a\nsingle PPU clock, and also check special cases. They have been tested on\nan actual NES and all give a passing result. Sometimes the NES starts up\nwith a different PPU timing that causes some of the tests to fail; these\ntests don't check that timing arrangement.\n\nEach ROM runs several tests and reports the result on screen and by\nbeeping a number of times. See below for the meaning of failure codes\nfor each test. It's best to run the tests in order, because later ROMs\ndepend on things tested by earlier ROMs and will give erroneous results\nif any earlier ones failed.\n\nSource code for each test is included, and most tests are clearly\ndivided into sections. Support code is also included, but it runs on a\ncustom devcart and assembler so it will require some effort to assemble.\nContact me if you'd like assistance porting them to your setup.\n\n\n1.frame_basics\n--------------\nTests basic VBL flag operation and general timing of PPU frames.\n\n2) VBL flag isn't being set\n3) VBL flag should be cleared after being read\n4) PPU frame with BG enabled is too short\n5) PPU frame with BG enabled is too long\n6) PPU frame with BG disabled is too short\n7) PPU frame with BG disabled is too long\n\n\n2.vbl_timing\n------------\nTests timing of VBL being set, and special case where reading VBL flag\nas it would be set causes it to not be set for that frame.\n\n2) Flag should read as clear 3 PPU clocks before VBL\n3) Flag should read as set 0 PPU clocks after VBL\n4) Flag should read as clear 2 PPU clocks before VBL\n5) Flag should read as set 1 PPU clock after VBL\n6) Flag should read as clear 1 PPU clock before VBL\n7) Flag should read as set 2 PPU clocks after VBL\n8) Reading 1 PPU clock before VBL should suppress setting\n\n\n3.even_odd_frames\n-----------------\nTest clock skipped when BG is enabled on odd PPU frames. Tests\nenable/disable BG during 5 consecutive frames, then see how many clocks\nwere skipped. Patterns are shown as XXXXX, where each X can either be B\n(BG enabled) or - (BG disabled).\n\n2) Pattern ----- should not skip any clocks\n3) Pattern BB--- should skip 1 clock\n4) Pattern B--B- (one even, one odd) should skip 1 clock\n5) Pattern -B--B (one odd, one even) should skip 1 clock\n6) Pattern BB-BB (two pairs) should skip 2 clocks\n\n\n4.vbl_clear_timing\n------------------\nTests timing of VBL flag clearing.\n\n2) Cleared 3 or more PPU clocks too early\n3) Cleared 2 PPU clocks too early\n4) Cleared 1 PPU clock too early \n5) Cleared 3 or more PPU clocks too late\n6) Cleared 2 PPU clocks too late\n7) Cleared 1 PPU clock too late\n\n\n5.nmi_suppression\n-----------------\nTests timing of NMI suppression when reading VBL flag just as it's set,\nand that this doesn't occur when reading one clock before or after.\n\n2) Reading flag 3 PPU clocks before set shouldn't suppress NMI\n3) Reading flag when it's set should suppress NMI\n4) Reading flag 3 PPU clocks after set shouldn't suppress NMI\n5) Reading flag 2 PPU clocks before set shouldn't suppress NMI\n6) Reading flag 1 PPU clock after set should suppress NMI\n7) Reading flag 4 PPU clocks after set shouldn't suppress NMI\n8) Reading flag 4 PPU clocks before set shouldn't suppress NMI\n9) Reading flag 1 PPU clock before set should suppress NMI\n10)Reading flag 2 PPU clocks after set shouldn't suppress NMI\n\n\n6.nmi_disable\n-------------\nTests NMI occurrence when disabling NMI just as VBL flag is set, and\njust after.\n\n2) NMI shouldn't occur when disabled 0 PPU clocks after VBL\n3) NMI should occur when disabled 3 PPU clocks after VBL\n4) NMI shouldn't occur when disabled 1 PPU clock after VBL\n5) NMI should occur when disabled 4 PPU clocks after VBL\n6) NMI shouldn't occur when disabled 1 PPU clock before VBL\n7) NMI should occur when disabled 2 PPU clocks after VBL\n\n\n7.nmi_timing\n------------\nTests timing of NMI and immediate occurrence when enabled with VBL flag\nalready set.\n\n2) NMI occurred 3 or more PPU clocks too early\n3) NMI occurred 2 PPU clocks too early\n4) NMI occurred 1 PPU clock too early\n5) NMI occurred 3 or more PPU clocks too late\n6) NMI occurred 2 PPU clocks too late\n7) NMI occurred 1 PPU clock too late\n8) NMI should occur if enabled when VBL already set\n9) NMI enabled when VBL already set should delay 1 instruction\n10)NMI should be possible multiple times in VBL\n\n-- \nShay Green <hotpop.com@blargg> (swap to e-mail)\n"
  },
  {
    "path": "release-plz.toml",
    "content": "[workspace]\nallow_dirty = false\nchangelog_config = \"cliff.toml\"\nchangelog_update = true\ndependencies_update = true\ngit_release_enable = true\ngit_tag_enable = true\npr_labels = [\"release\"]\npublish_allow_dirty = false\nsemver_check = true\n\n[[package]]\nname = \"tetanes\"\ngit_release_name = \"TetaNES v{{ version }}\"\n\n[[package]]\nname = \"tetanes-core\"\ngit_release_name = \"TetaNES Core v{{ version }}\"\n"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\nchannel = \"nightly\"\ncomponents = [\"rustfmt\", \"clippy\", \"llvm-tools-preview\"]\ntargets = [\"wasm32-unknown-unknown\"]\nprofile = \"default\"\n"
  },
  {
    "path": "tetanes/CHANGELOG.md",
    "content": "<!-- markdownlint-disable-file no-duplicate-heading -->\n\n# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [0.14.1](https://github.com/lukexor/tetanes/compare/0.13.0..0.14.1) - 2026-04-20\n\n### 🐛 Bug Fixes\n\n\n- Fixed missing debug cfg guards - ([2b64734](https://github.com/lukexor/tetanes/commit/2b647342356cd6ae48b38e28427af47ef62e2186))\n\n### 🎨 Styling\n\n\n- Fixed formatting - ([d7e37de](https://github.com/lukexor/tetanes/commit/d7e37de56a47b475a01b259b9988e1574e75b33e))\n- Fixed nightly lints - ([801bb3c](https://github.com/lukexor/tetanes/commit/801bb3c95a95a7f98efb5a993f2efe7061daf00b))\n\n### ⚙️ Miscellaneous Tasks\n\n\n- Update packages - ([439af37](https://github.com/lukexor/tetanes/commit/439af377f93c0fb8af1353d1802da6ab5b8c407d))\n\n\n## [0.13.0](https://github.com/lukexor/tetanes/compare/0.12.2..0.13.0) - 2026-02-14\n\n### 🐛 Bug Fixes\n\n\n- Change cycle to u32 to improve 32-bit platforms like wasm - ([86f597c](https://github.com/lukexor/tetanes/commit/86f597c3c1422040e8f98ac4dfb29f3a2ffdc81f))\n- Add hours and minutes to run time - ([4b5f6e6](https://github.com/lukexor/tetanes/commit/4b5f6e6fcec20e053e2ca736e1e4eec09d35803f))\n- Fixed closing secondary windows - ([81b0e81](https://github.com/lukexor/tetanes/commit/81b0e81bae07d6dd6b459fbbb79550aa45c1e7b1))\n- Fixed linux link spelling and small html loader issue - ([637385d](https://github.com/lukexor/tetanes/commit/637385d7ccbb157204bd3d90a311abc8d438a60e))\n\n### ⚡ Performance\n\n\n- Cpu opcode refactor - ([8916097](https://github.com/lukexor/tetanes/commit/8916097c39ee18828d9fd7bf70185a19eaf8e098))\n\n### 🎨 Styling\n\n\n- Fix nightly lints - ([40cd6e4](https://github.com/lukexor/tetanes/commit/40cd6e4520a1915f4b18537e14b160d11c4eda36))\n\n### ⚙️ Miscellaneous Tasks\n\n\n- Updated deps - ([ccb7463](https://github.com/lukexor/tetanes/commit/ccb7463c98be1e35fe7e6a47c2df9d76796ee349))\n- Update deps - ([782dc21](https://github.com/lukexor/tetanes/commit/782dc213f25f34cc47af0c2f71cdf9bfd44ae28b))\n- Try to shore up CD builds/uploads - ([96d3193](https://github.com/lukexor/tetanes/commit/96d3193aebefded818cff7bf6bd00bade1341a7f))\n\n\n## [0.12.2](https://github.com/lukexor/tetanes/compare/0.12.1..0.12.2) - 2025-04-05\n\n### ⛰️  Features\n\n\n- Recent rom enhancements. closes #391 - ([d70ca9b](https://github.com/lukexor/tetanes/commit/d70ca9bee97444d72af2442ceaf750b6cc03fb4e))\n- Add don't show again checkbox to update window. closes #390 - ([5cb1186](https://github.com/lukexor/tetanes/commit/5cb1186a0cd1d0016197f21bf89ad7d44339a94e))\n\n### 🐛 Bug Fixes\n\n\n- Fixed ctrlc handling - ([131aa18](https://github.com/lukexor/tetanes/commit/131aa1879dd9d5594a66a4f2ce054bfa7674969d))\n- Revert input serialization change, as it broke run-ahead - ([6489c57](https://github.com/lukexor/tetanes/commit/6489c579c738f88068423affb3833edd9105e523))\n- Fix controls in index - ([567137b](https://github.com/lukexor/tetanes/commit/567137b5418525ec51dd63a50b23230b93a9f952))\n- Removed extra touch scaling - ([44f41db](https://github.com/lukexor/tetanes/commit/44f41db0e83fcca49cd472115b5def9417aa6fc4))\n- Removed download button for Mobile - ([035362a](https://github.com/lukexor/tetanes/commit/035362a21571782865c3fc4c7609d9f027a52a05))\n- Fix touch events - ([1a8d3da](https://github.com/lukexor/tetanes/commit/1a8d3dad5243bb31858575e99e6961c33a2521d4))\n- Fixed some configuration/repaint issues - ([8eb07da](https://github.com/lukexor/tetanes/commit/8eb07da1dbc53c7c2cebfe7b206dbd17ee98b1f5))\n- Basic cpu error recovery options - ([c60d8d1](https://github.com/lukexor/tetanes/commit/c60d8d1d02dde9e3383849e1af43111f26db29c7))\n- Remove color emojis from window titles. closes #410 - ([b149648](https://github.com/lukexor/tetanes/commit/b149648649c875df3303f6208e7de31995e2143b))\n- Fix ctrl-c handling while file dialog is open - ([cb006f2](https://github.com/lukexor/tetanes/commit/cb006f2846e302e7498749fc8f079c7bce7b5739))\n- Show warning if no audio device on startup. closes #388 - ([b0625e5](https://github.com/lukexor/tetanes/commit/b0625e5ccac22c0114bf19981549c9da1b5f2cab))\n- Handle ctrl-c. closes #386 - ([817ee58](https://github.com/lukexor/tetanes/commit/817ee5843a1b8da8676555a52ef2a9a8489477ec))\n\n### 🚜 Refactor\n\n\n- Changed input serializing - ([ad37b47](https://github.com/lukexor/tetanes/commit/ad37b47ab07a54597eeb91b95fb524a0ea6b4e43))\n- Cleaned up Memory struct - ([7cb31fb](https://github.com/lukexor/tetanes/commit/7cb31fb3878ee4e7e9b4ca9eabe123d570cc3176))\n\n### 📚 Documentation\n\n\n- Fix urls - ([8136c42](https://github.com/lukexor/tetanes/commit/8136c42bdab474e17ceda78819225349a3f1c520))\n- Ensure features display in docs.rs - ([f58b31c](https://github.com/lukexor/tetanes/commit/f58b31cc9d27f0187d759796c64e34ccea3f784a))\n- Remove custom docs.rs metadata - ([201b3bc](https://github.com/lukexor/tetanes/commit/201b3bc4f2ccddbb7b737fced5fa8ed67fac5d25))\n\n### ⚡ Performance\n\n\n- Re-enable puffin_egui with patched versions - ([24ee0cc](https://github.com/lukexor/tetanes/commit/24ee0cc6717442fdebead0f075994ab35cb382da))\n\n### 🎨 Styling\n\n\n- Updated loading for tetanes-web - ([02b7a24](https://github.com/lukexor/tetanes/commit/02b7a2439afb379a577408f586a313fac11a4c36))\n- Some small cleanup - ([4757228](https://github.com/lukexor/tetanes/commit/4757228455fc1e9093ce1a6436fe48e8156df918))\n- Warn if surface format isn't supported, add test gamma png - ([9a03731](https://github.com/lukexor/tetanes/commit/9a0373154d8527c5051e59e9748d2d6249b91288))\n\n### ⚙️ Miscellaneous Tasks\n\n\n- Updated dependencies - ([6683613](https://github.com/lukexor/tetanes/commit/66836130cbd3c2409ed25b2af7292ddec8efa032))\n- Reduce dependencies - ([b16fcd3](https://github.com/lukexor/tetanes/commit/b16fcd353f7bc3233030c49243ddf1f65c80778e))\n- Zip windows installer - ([ccfb7f4](https://github.com/lukexor/tetanes/commit/ccfb7f42184aabda574c783e46b82e9cfb4dd6e6))\n- Updated deps - ([828b770](https://github.com/lukexor/tetanes/commit/828b770071cd9a5e54967573abef9d31888017bc))\n- Add versions to artifacts - ([a29241f](https://github.com/lukexor/tetanes/commit/a29241f2508972bc998519ec0bdf11041e5c22ba))\n\n\n## [0.12.1](https://github.com/lukexor/tetanes/compare/0.12.0..0.12.1) - 2025-03-13\n\n### ⛰️  Features\n\n\n- Added shortcuts for shaders and ppu warmup flag - ([408b122](https://github.com/lukexor/tetanes/commit/408b122ed98f7edb7a26085fb921aa006bde7091))\n\n### 📚 Documentation\n\n\n- Fixed cargo doc url - ([782f7c5](https://github.com/lukexor/tetanes/commit/782f7c51b68c5fb52b483a3f151bd3db227286a9))\n- Updated changelog and readmes - ([a4a3e8c](https://github.com/lukexor/tetanes/commit/a4a3e8c0775a7261b91f4238756ac5a20d2c4b48))\n\n### ⚙️ Miscellaneous Tasks\n\n\n- Fix/update ci, docs, and fixed nightly issue with tetanes-core - ([a6150ba](https://github.com/lukexor/tetanes/commit/a6150bad6703bbc661d7d5c8b63f5a6d47991868))\n\n\n<!-- markdownlint-disable-file no-duplicate-heading no-multiple-blanks line-length -->\n\n# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [0.12.0](https://github.com/lukexor/tetanes/compare/tetanes-v0.11.0..tetanes-v0.12.0) - 2025-03-12\n\n### ⛰️  Features\n\n\n- Jalecoss88006 - ([406777a](https://github.com/lukexor/tetanes/commit/406777abad8d61490aae2a33e2e71fc617db3f55))\n- Namco163 - ([89d7fb4](https://github.com/lukexor/tetanes/commit/89d7fb4617bf844ad2090cd92f0e92cda9cc91fc))\n- Added sunsoft/fme-7 - ([303dad8](https://github.com/lukexor/tetanes/commit/303dad85d0a6586a88b7067f2070c5ac9e4da6e4))\n- Added nina003/nina006 - ([29503c3](https://github.com/lukexor/tetanes/commit/29503c3efc81fb3110eef63e9666de1e0c912015))\n- Added dxrom ([#340](https://github.com/lukexor/tetanes/issues/340)) - ([906af59](https://github.com/lukexor/tetanes/commit/906af59038e95874dda254e02998030c913d8c61))\n- Ppu-viewer ([#339](https://github.com/lukexor/tetanes/issues/339)) - ([fce7d89](https://github.com/lukexor/tetanes/commit/fce7d89f78148e9a367d47122eef7e6e8fe45b34))\n- Bandai mappers 016, 153, 157, 159 ([#335](https://github.com/lukexor/tetanes/issues/335)) - ([f555ea4](https://github.com/lukexor/tetanes/commit/f555ea48d0273bc9d41b998926d451398acbb73c))\n- Allow exporting save states in web ([#311](https://github.com/lukexor/tetanes/issues/311)) - ([627bbec](https://github.com/lukexor/tetanes/commit/627bbece49739ff479e69ba9e83df828c4d4a633))\n- Add a debug build label - ([46b3d94](https://github.com/lukexor/tetanes/commit/46b3d94e5fd24900a95554a295257f0891ac1c53))\n- Add test panic debug button - ([3866efa](https://github.com/lukexor/tetanes/commit/3866efab39ced8f3431ee27d24962a55397a9f07))\n- Added screen reader/accesskit support - ([5fd1a73](https://github.com/lukexor/tetanes/commit/5fd1a73f112f74a6c0a81e722485842dd37e0a38))\n- Added ui setting/debug windows - ([db8b122](https://github.com/lukexor/tetanes/commit/db8b122af6c5a52ad23ed89ffd6f2feb35515603))\n- Enable webgpu for browsers that support it. closes #297 ([#298](https://github.com/lukexor/tetanes/issues/298)) - ([a6bde61](https://github.com/lukexor/tetanes/commit/a6bde619454bf8d77f98d462d89eccea4b0e42fc))\n\n### 🐛 Bug Fixes\n\n\n- Fixed several issues - ([60fcd90](https://github.com/lukexor/tetanes/commit/60fcd90e740833e94deb98896a17a51fcda38998))\n- Fix cycle overflow - ([a4e1f05](https://github.com/lukexor/tetanes/commit/a4e1f058c6e899e9fd11578bfb40a36d6ea1980e))\n- Add temporary webgpu flag - ([179e868](https://github.com/lukexor/tetanes/commit/179e868c9e1cee92df1d0568b60403c2df7579cb))\n- Temporary wasm fix for check-cfg - ([30c6a61](https://github.com/lukexor/tetanes/commit/30c6a61c0d562f875a0d979ad655e199d7c7019a))\n- Fix tetanes-core compiling on stable. closes #360 - ([adc5673](https://github.com/lukexor/tetanes/commit/adc5673a3ed5d80aff339c3ab6d95013fcb2d715))\n- Fixed deny.toml - ([2c1f186](https://github.com/lukexor/tetanes/commit/2c1f18603f043c2dcb17db8fa8958ea1cbfd88d4))\n- Fixed bank size check - ([c84c012](https://github.com/lukexor/tetanes/commit/c84c012c310ad466c5167b94f0228f5a482dec43))\n- Fixed wasm - ([bd27814](https://github.com/lukexor/tetanes/commit/bd278140bcc7e7d433917f14e99038f2e6453027))\n- Fixed video frame size - ([153094d](https://github.com/lukexor/tetanes/commit/153094d81d444376b112224409544375588c4f97))\n- Fix scroll issues - ([218d786](https://github.com/lukexor/tetanes/commit/218d7860421eb4cfc4d7b833132f4c476935777a))\n- Fixed increasing scale on web - ([8c4265e](https://github.com/lukexor/tetanes/commit/8c4265e10fc8b62cd7dcaa8a828fed1a07100a9f))\n- Fixed shortcut text - ([cb73c21](https://github.com/lukexor/tetanes/commit/cb73c216936ad49dca4e2595485df4ccea957eaa))\n- Fixed joypad keybinds and some UI styling - ([bc2f093](https://github.com/lukexor/tetanes/commit/bc2f093b4d02c54744f791f336a102424a7e5af1))\n- Enable puffin on wasm - ([0b6f794](https://github.com/lukexor/tetanes/commit/0b6f79429c5d2a642c0ef6301bbcc9818973a234))\n- Fix window theme - ([e3c42c7](https://github.com/lukexor/tetanes/commit/e3c42c7720f558c7348e2b82b3573d4748158850))\n- Fixed window aspect ratio - ([17db5c8](https://github.com/lukexor/tetanes/commit/17db5c8a037ab3aefab560bca67545964069658f))\n- Don't log/error when sending frames while paused - ([50825f8](https://github.com/lukexor/tetanes/commit/50825f82e9f04418fdefd56707ef2ec50cddd5ed))\n- Fixed pause state when loading replay - ([d743b31](https://github.com/lukexor/tetanes/commit/d743b31c190cd93e42e3ab78b497e59bcc4ade88))\n- Fixed roms path to default to current directory, if valid, and canonicalize - ([e00273f](https://github.com/lukexor/tetanes/commit/e00273f740f7fc095bc02c7ce6d0ba132a14c9bc))\n- Ensure pixel brightness is using the same palette - ([ad2f873](https://github.com/lukexor/tetanes/commit/ad2f873f5652016b96317c000b4abbe0e35de421))\n- Move some calculations to vertex shader that don't depend on v_uv - ([a6f262d](https://github.com/lukexor/tetanes/commit/a6f262db5d83950e86e0ec78bb74fc63e5c2bf85))\n- Fixed logging location - ([ff36033](https://github.com/lukexor/tetanes/commit/ff36033d7bbbf64924d97d6e9a88dcf4db7dc60c))\n- Fixed issue with lower end platforms not supporting larger texture dimensions - ([ef214db](https://github.com/lukexor/tetanes/commit/ef214dbc2f2eee016b7abdb0c2b0ee1858381ee4))\n- Fix window resizing while handling zoom changes - ([6b3f690](https://github.com/lukexor/tetanes/commit/6b3f690b8ec21b907d353a7cad8561217e8d9dcf))\n\n### 🚜 Refactor\n\n\n- [**breaking**] Split mapper traits - ([3e4a372](https://github.com/lukexor/tetanes/commit/3e4a372dfdc4295851c93cca96044f84645ae14e))\n- Removed egui-wgpu and egui-winit dependencies. ([#315](https://github.com/lukexor/tetanes/issues/315)) - ([b3d4e2c](https://github.com/lukexor/tetanes/commit/b3d4e2c70c6ee4cfa9aaf53a11c1ae802610ff99))\n- Platform/ui cleanup - ([39f66e6](https://github.com/lukexor/tetanes/commit/39f66e6e912f9c95cf9c458cd072e5e041af09e3))\n- Moved around platform code to condense it - ([0f18928](https://github.com/lukexor/tetanes/commit/0f18928b8f8ed031cac7a170557c0296916c99bc))\n- Prefer deferred viewports ([#306](https://github.com/lukexor/tetanes/issues/306)) - ([e1e60d1](https://github.com/lukexor/tetanes/commit/e1e60d19599ab883cbb034047519e6eb831d6c6c))\n\n### 📚 Documentation\n\n\n- Extra cpu comments - ([80f3366](https://github.com/lukexor/tetanes/commit/80f3366e3fab1257201ab0d9af673c4318edabef))\n\n### ⚡ Performance\n\n\n- Restore sprite presence check, ~2% gain - ([c6d353a](https://github.com/lukexor/tetanes/commit/c6d353a8fc12b506656a8cd70561ef1830ba9284))\n- More perf and added flamegraph - ([31edf0c](https://github.com/lukexor/tetanes/commit/31edf0c63bcc30867f0049a231e7d366db4bde8d))\n- Performance tweaks - ([d9a3019](https://github.com/lukexor/tetanes/commit/d9a3019ec0c0014d8850158d38c27289dc885020))\n\n### 🎨 Styling\n\n\n- Fix lints - ([bc9f6bc](https://github.com/lukexor/tetanes/commit/bc9f6bc293d413cf780a2aa0253ad7d64951d193))\n- Slight cleanup - ([63e31a9](https://github.com/lukexor/tetanes/commit/63e31a9755266bec88d5c79e064506999f03aea2))\n- Fixed format - ([d62ea28](https://github.com/lukexor/tetanes/commit/d62ea285cb5fe73ac41e7364f0ca3f32281a0e88))\n\n### ⚙️ Miscellaneous Tasks\n\n\n- Update deps - ([5b077c0](https://github.com/lukexor/tetanes/commit/5b077c01b1e68a60d3e295fe108732a3b8abbbd6))\n- Bumped version - ([28fa93f](https://github.com/lukexor/tetanes/commit/28fa93f226447fd409b5d3846cd0f7e14a793f83))\n- Update deps - ([509dbd4](https://github.com/lukexor/tetanes/commit/509dbd48a34cd6a360da0fba3786ed73445381fc))\n- Fix ci - ([da64229](https://github.com/lukexor/tetanes/commit/da64229966295d85b0f62b0e3827d76767116602))\n- Fix deny.toml - ([64a2401](https://github.com/lukexor/tetanes/commit/64a24010c72926c555ae74ffb4f1acb2c0aefffb))\n- Updated deps - ([906c877](https://github.com/lukexor/tetanes/commit/906c877700d551fd74e0545e03f544ea2255823f))\n- Updated deps - ([825719e](https://github.com/lukexor/tetanes/commit/825719e7f56ef6263f22a6da82d31f02d05af570))\n- Updated deps - ([4712d6d](https://github.com/lukexor/tetanes/commit/4712d6d6de3ce7eccec8f1971fcb0f2411f91e3d))\n- Restore nightly ci - ([eb2a2c5](https://github.com/lukexor/tetanes/commit/eb2a2c58ecd802810709f5e367253857d51a47d0))\n- Update dependencies - ([4947a8c](https://github.com/lukexor/tetanes/commit/4947a8cf6883eda0b0c55fcd7bcf98cf8fd7dee9))\n- Remove puffin_egui reference in wasm - ([16845f3](https://github.com/lukexor/tetanes/commit/16845f39e28c816c847a9d403dbedde38c815c1d))\n- More dependency cleanup - ([1971e4f](https://github.com/lukexor/tetanes/commit/1971e4f2c5aaf6f8a2d6ce2a03c978362d44afe1))\n- Clean up dependencies - ([254fe54](https://github.com/lukexor/tetanes/commit/254fe543293b0c96c78ce25bdaeef2f250a9fb14))\n- Remove auto-assign from triage - ([9a2804b](https://github.com/lukexor/tetanes/commit/9a2804b94b1a412214159495d2e6410a63555572))\n- Restrict homebrew cd to .rb files - ([3c1e390](https://github.com/lukexor/tetanes/commit/3c1e3907d7477dbe9f6953d9b8b9b0aeb1ef5966))\n- Fix update homebrew formula runs-on - ([9e66a07](https://github.com/lukexor/tetanes/commit/9e66a073fa1ef9e276a2ca85ccc4e4281b50e7bc))\n- Fix cd upload - ([892d184](https://github.com/lukexor/tetanes/commit/892d184cc25ca7903cb4a5f7372f47e722866125))\n- Restore RELEASE_PLZ_TOKEN - ([18de294](https://github.com/lukexor/tetanes/commit/18de2946b82a44efdba96e5918eba381ad3a1a75))\n- Remove need for RELEASE_PLZ_TOKEN - ([b6c8478](https://github.com/lukexor/tetanes/commit/b6c84780123ca5d9dfc841e2a3e6266b7d3cc4b9))\n- Try to fix release cd - ([c7d5f51](https://github.com/lukexor/tetanes/commit/c7d5f514a84bd3b728686893e3211b63ec21a9c9))\n\n## [0.11.0](https://github.com/lukexor/tetanes/compare/tetanes-v0.10.0..tetanes-v0.11.0) - 2024-06-12\n\n### ⛰️  Features\n\n\n- Shader support with crt-easymode ([#285](https://github.com/lukexor/tetanes/pull/285)) - ([e5042ef](https://github.com/lukexor/tetanes/commit/e5042efd45642ac2a13d7ac695bba1cce77c69c9))\n- Auto-save cfg at a set interval ([#279](https://github.com/lukexor/tetanes/pull/279)) - ([e6941d8](https://github.com/lukexor/tetanes/commit/e6941d8e47c73910cf99c5ae9d52e9d5f4b7bade))\n- Add UI persistence. closes [#257](https://github.com/lukexor/tetanes/pull/257) ([#277](https://github.com/lukexor/tetanes/pull/277)) - ([4c861f7](https://github.com/lukexor/tetanes/commit/4c861f7f59d99ee536e135a238ae3b621178bd08))\n- Added config and save/sram state persistence to web ([#274](https://github.com/lukexor/tetanes/pull/274)) - ([8c7f6df](https://github.com/lukexor/tetanes/commit/8c7f6df4a8894b544da1c6480659ee26ea28f342))\n- Added always on top option. enabled shortcut for embed viewports - ([489f61e](https://github.com/lukexor/tetanes/commit/489f61ef668094fa8e685a592cad0ec8b46d1c38))\n- Added data man demo and changed name of nebs n' debs to demo - ([d7d2bae](https://github.com/lukexor/tetanes/commit/d7d2bae10da45239cf3e8142a00e893dd95fdc23))\n- Added mapper 11 - ([03d2074](https://github.com/lukexor/tetanes/commit/03d2074d3d58fcf652fecb9d77f4e96e8c007aae))\n\n### 🐛 Bug Fixes\n\n\n- Fixed a number of issues caused by the crt-shader PR - ([8c31927](https://github.com/lukexor/tetanes/commit/8c31927ee332d0593402b7c6b632dbcafe4fa964))\n- Ntsc tweaks - ([3042fa7](https://github.com/lukexor/tetanes/commit/3042fa7b928faf69e10040b4eb981a4c4f8f3ce3))\n- Fixed some frame clocking issues - ([80ef7b5](https://github.com/lukexor/tetanes/commit/80ef7b50df3ea00500df70f86904d0a3cfcfdb53))\n- Fixed blocking checking for updates on start - ([f48c634](https://github.com/lukexor/tetanes/commit/f48c63445bf2f2be224c7632781101c9a4075dbe))\n- Revert rfd features back - ([30cec26](https://github.com/lukexor/tetanes/commit/30cec26fd44d284e124e33422d2cbcccd5d0814a))\n- Fixed Data Man url - ([882004a](https://github.com/lukexor/tetanes/commit/882004ac9f44aa0c51ca88bf99552f9187fad8e9))\n- Cleaned up pausing, parking, and control flow. Closes [#251](https://github.com/lukexor/tetanes/pull/251) - ([72cf88a](https://github.com/lukexor/tetanes/commit/72cf88ac6991953222bd3dd1d395f7f9035c98ef))\n- Remove unfocused/occluded pausing for now until a less error-prone cross-platform solution can be designed - ([a5549e6](https://github.com/lukexor/tetanes/commit/a5549e6f026d201e9e6c7f0acfc56ee734c85a95))\n- Remove bold from controls - ([0cfa0e9](https://github.com/lukexor/tetanes/commit/0cfa0e9edaad86b7566763ef8444649bef462af1))\n- Fix excess redraw requests - ([caf88c0](https://github.com/lukexor/tetanes/commit/caf88c002fb8f18072e5fad95c967dd79f09afff))\n- Fixed wasm resizing to be restricted by browser viewport ([#243](https://github.com/lukexor/tetanes/pull/243)) - ([b59d4c9](https://github.com/lukexor/tetanes/commit/b59d4c906fbd41d95e21e58bffc28074028947c4))\n- Disable rewind when low on memory. clear rewind memory when disabled - ([4d5e1c4](https://github.com/lukexor/tetanes/commit/4d5e1c4dbe43cceb9ab8d4c33ca832830b2d31d8))\n- Remove redrawing every clock - ([8cea6c1](https://github.com/lukexor/tetanes/commit/8cea6c14718dfd564b1f8ec9e60fc57b2d0602d0))\n- Fixed web build relative urls - ([1423bdb](https://github.com/lukexor/tetanes/commit/1423bdb75766da2d656d24a6075ee38d36002ec9))\n- Fixed a number of issues with loading roms and unintentionally blocking wasm - ([e257575](https://github.com/lukexor/tetanes/commit/e257575c24cde809d156ae6451575ec7cfd70aad))\n- Fix clock timing on web. closes [#234](https://github.com/lukexor/tetanes/pull/234) - ([57d323d](https://github.com/lukexor/tetanes/commit/57d323d44408269b1c72932baa4b3b534e69f70d))\n- Fix frame stats when toggled via menu. closes [#233](https://github.com/lukexor/tetanes/pull/233) - ([347066b](https://github.com/lukexor/tetanes/commit/347066b8f4000dcd88e3a76e8b948b55198ecceb))\n- Add scrolling to lists - ([62ff074](https://github.com/lukexor/tetanes/commit/62ff0745a846cc8aed910244448becd98f155abf))\n- Fix changing slider/drag values - ([8580135](https://github.com/lukexor/tetanes/commit/8580135f6e1e14a84214ca24efd57dbaf7595997))\n\n### 🚜 Refactor\n\n\n- Removed a number of panic cases and cleaned up platform checks - ([bdb71a9](https://github.com/lukexor/tetanes/commit/bdb71a96792778cb0ad6bedf44e0ef5cbfa703e4))\n- Frame timing cleanup - ([1e920fd](https://github.com/lukexor/tetanes/commit/1e920fdd4ca56010ebe380152c817025a1b4c127))\n- Some initialization error handling cleanup - ([507d9a0](https://github.com/lukexor/tetanes/commit/507d9a04c439081afc585761311082b0069c7111))\n- Small gui cleanup - ([880e9ee](https://github.com/lukexor/tetanes/commit/880e9ee4b33d8b12924598e688f01b5e79289590))\n\n### 📚 Documentation\n\n\n- Fixed docs and changelog - ([4c7a694](https://github.com/lukexor/tetanes/commit/4c7a6949e52b6734fd6a78f6d9567c70e12b3ae4))\n\n### ⚙️ Miscellaneous Tasks\n\n\n- Split out web build so it can run on any platform - ([dcaec14](https://github.com/lukexor/tetanes/commit/dcaec14828288d758c713861a7aef6c03e4da47b))\n- Upgrade ringbuf - ([5d7abe2](https://github.com/lukexor/tetanes/commit/5d7abe291493f43b14a0239157dfed930db17d6c))\n\n\n## [0.10.0](https://github.com/lukexor/tetanes/compare/tetanes-v0.9.0..tetanes-v0.10.0) - 2024-05-16\n\n### ⛰️  Features\n\n- *(mapper)* Added Vrc6 mapper - ([fd2075d](https://github.com/lukexor/tetanes/commit/fd2075d98c7b4ef8643e5ea433c936201e414a04))\n\n- Added controller support - ([7550bce](https://github.com/lukexor/tetanes/commit/7550bce09738cbfc5360c08e8323e70e68d0eb54))\n- Initial re-structure of painter and viewports - ([5feabbe](https://github.com/lukexor/tetanes/commit/5feabbe197e59a9999960b40c2f069f7527eb421))\n- Perf stats and ui cleanup - ([8d7d0d4](https://github.com/lukexor/tetanes/commit/8d7d0d482958d63d3b2c853dc01bf81f7bf54b84))\n- Switched to lazy APU catch-up - ([4a95de3](https://github.com/lukexor/tetanes/commit/4a95de3e1eb364ac13e189b321d4cbe480e67da3))\n- Add headless options, run_ahead methods, audio fixes, and performance improvements - ([a1a1b9b](https://github.com/lukexor/tetanes/commit/a1a1b9bae19530dfc4592a33cfb318f8d5e0df75))\n- Added run-ahead feature - ([3349045](https://github.com/lukexor/tetanes/commit/3349045adce0ecdf9c38f0ccd41ddcbd72c9f7a8))\n- Add cycle-accurate feature - ([6d0db9f](https://github.com/lukexor/tetanes/commit/6d0db9f1c0a5b1b7b83ab3c29b055b2addbad40f))\n- Added rewind - ([4cc7b65](https://github.com/lukexor/tetanes/commit/4cc7b657b085666b5500c0f0c6f2eabcb7cdabd2))\n\n### 🐛 Bug Fixes\n\n- *(ppu)* Fixed oam read stomping on sprite0's y-byte - ([dc51191](https://github.com/lukexor/tetanes/commit/dc511914a14d5ccf2071022981a3126b9ff47b40))\n- *(wasm)* Overhauled wasm build - ([2892587](https://github.com/lukexor/tetanes/commit/28925874661988423acab72be73b6ffb405924ee))\n\n- Revert frame buffer back to u16 to fix emphasis - ([bc7f5fa](https://github.com/lukexor/tetanes/commit/bc7f5fa741a93877087e3b6f71c7ecfdacc06637))\n- Made ppu warm optional, default to false - ([1693681](https://github.com/lukexor/tetanes/commit/16936817a5bd1278c835eedf6fa63d410f047c2a))\n- Revert 240pee rename - ([f82f763](https://github.com/lukexor/tetanes/commit/f82f76397ea7e00afe171235e776207ed8b10c2b))\n- Fixed 240pee test rom path - ([77f9702](https://github.com/lukexor/tetanes/commit/77f9702f0c2ff6caa073cdbbcf4816614daf8675))\n- Fixed saving config - ([361447f](https://github.com/lukexor/tetanes/commit/361447fc1bc178498a23c2464c3f0fe353e5c165))\n- Fixed setting APU sample_rate - ([ed52eb7](https://github.com/lukexor/tetanes/commit/ed52eb78d3b2cfb921622c19760d87fe72b1189e))\n- Fixed selecting audio sample rate - ([24d6bfc](https://github.com/lukexor/tetanes/commit/24d6bfc78d5ce2021fcea7edbf53a9f2af3f74e9))\n- Disabled webgpu since it panics on a double borrow currently - ([c93e7ad](https://github.com/lukexor/tetanes/commit/c93e7adbd2dd5ad3ba8c7fef5f60f874ccf839da))\n- Remove toggling vsync and fixed wasm frame rate - ([f937396](https://github.com/lukexor/tetanes/commit/f9373964e5f91c5dc75b7bbd93c456f7253eae8d))\n- Some timing and ui fixes - ([fe9d123](https://github.com/lukexor/tetanes/commit/fe9d1232df8294445d60db6ccc448f71df489d44))\n- Fixed exrom irq - ([7cc2540](https://github.com/lukexor/tetanes/commit/7cc254034f40c76af6a5c444895afc34e1509e46))\n- Removed unused revision - ([4376c9a](https://github.com/lukexor/tetanes/commit/4376c9a1396186dbfcd6a70f8c5d77940eb3f0b8))\n- Cleaned up mappers - ([22c97e2](https://github.com/lukexor/tetanes/commit/22c97e228c8afb521be25c7a45eab5bb8b8e83cd))\n- Documentation and ui updates - ([38fc88f](https://github.com/lukexor/tetanes/commit/38fc88f873bf2b5b27f0d82bbff82895747581ae))\n- Fixed apu tests - ([6ff1362](https://github.com/lukexor/tetanes/commit/6ff1362a080143632a8384207c82adf9f9d244dc))\n- More audio fixes - ([7e0f2f5](https://github.com/lukexor/tetanes/commit/7e0f2f5de552ef0b99a07f08e5bd47deced44df4))\n- Fixed mmc5 pulse channels - ([0588103](https://github.com/lukexor/tetanes/commit/05881036c965e8ffb9231f0fd0a1738298e9b869))\n- Some fixes for audio channels sounding off - ([46a773d](https://github.com/lukexor/tetanes/commit/46a773d81d86a60c6dded568bb1525fcd868468d))\n- Fixed apu region sample_period - ([2a440ca](https://github.com/lukexor/tetanes/commit/2a440caa9d1cb43f9097b0a5c5e60a9ab746fa81))\n- Fixed some frame rate performance issues - ([ca304b0](https://github.com/lukexor/tetanes/commit/ca304b05dc762478be54a2d365249bb4e05a8b68))\n- Fixed replay and rewind - ([55dc8d7](https://github.com/lukexor/tetanes/commit/55dc8d7e06c3ec9366936d02599739119a8cc6f1))\n- Fixed some path and config issues - ([ef60f1b](https://github.com/lukexor/tetanes/commit/ef60f1b9bd3f996e8bcb7435ed9da21f827cd327))\n- Fixed some region, configs, and features - ([e5d4f4a](https://github.com/lukexor/tetanes/commit/e5d4f4a21cb96bf0051b8827150f76f1b3a579bc))\n- Fixed chr_ram test - ([d24009b](https://github.com/lukexor/tetanes/commit/d24009bab6575a1c74cbd138745f57ad7a2c5cd7))\n- Fixed apu linear counter loading - ([76ae795](https://github.com/lukexor/tetanes/commit/76ae7958df948e174cc3d9c1fe1f8ce5d3d7279a))\n- Read nes2.0 region header - ([0c70e87](https://github.com/lukexor/tetanes/commit/0c70e87a4edc002e018d01598584c6bab1d0a2ae))\n- Fixed chr-rom writing - ([50724a6](https://github.com/lukexor/tetanes/commit/50724a6a485cac0c4d87d941931ef2129cab6d4b))\n- Improved PAL support - ([acb4db8](https://github.com/lukexor/tetanes/commit/acb4db8cc79af64c594a0990fd5d80043b2bb5cd))\n\n### 🚜 Refactor\n\n- *(cpu)* Moved DMA values inside CPU - ([7257d18](https://github.com/lukexor/tetanes/commit/7257d18ed2154787e8ad25fa1ea309d5442098c4))\n\n- Various event/UI cleanup - ([bd1f984](https://github.com/lukexor/tetanes/commit/bd1f984ffbc41ca0ff73a80a000670c25d0f3361))\n- Config overhaul and keybind menus - ([1fbb4ba](https://github.com/lukexor/tetanes/commit/1fbb4bafc66190b7d406434e12cf0666c3453bac))\n- Thread local irq - ([cc9cbea](https://github.com/lukexor/tetanes/commit/cc9cbea644acf745ed5d99e2f80a92271c8d8d63))\n- Various cleanup - ([889e41f](https://github.com/lukexor/tetanes/commit/889e41fb91f4db51f2269be7ce51d290fba48cca))\n- Some audio cleanup - ([6eeff9e](https://github.com/lukexor/tetanes/commit/6eeff9e33a606869f46b29212dd06da18be26ab7))\n- Major config overhaul - ([34076be](https://github.com/lukexor/tetanes/commit/34076be0266d3c3838630b57aa705371e9460c2d))\n- Major platform and error handling overhaul - ([eb6e546](https://github.com/lukexor/tetanes/commit/eb6e5468ccb9a1d711658d55e6b3f0f84c154ddb))\n- Audio mixer overhaul and .raw recording - ([44cc47c](https://github.com/lukexor/tetanes/commit/44cc47c932afb0ba136458fb962cf6ac39e865ac))\n- Fixed audio - ([fe26c1b](https://github.com/lukexor/tetanes/commit/fe26c1bbb240ba447cd9487b5f0fb9adead6c2be))\n- Clean up some wasm code - ([dd489d3](https://github.com/lukexor/tetanes/commit/dd489d38eca98e180534fe03b4920cc3b5fa4fa3))\n- Inlined puffin for now - ([945ff0e](https://github.com/lukexor/tetanes/commit/945ff0e750727717dffab39528e321bf6dc8bc2e))\n- Moved audio filtering/decimation to apu - ([4a38d23](https://github.com/lukexor/tetanes/commit/4a38d23df45594def155d1e11ed8869230195580))\n- Major module overhaul - ([ca92f51](https://github.com/lukexor/tetanes/commit/ca92f5176855a635cb1244b912030d9c9859f7cd))\n- Various updates - ([da213ae](https://github.com/lukexor/tetanes/commit/da213ae36aa0b4763c643d089e390d184d69dc19))\n- Cleaned up menus - ([dd9e726](https://github.com/lukexor/tetanes/commit/dd9e726e8e3c9616aa09ff2cebf726462fe96f87))\n- Made ram_state consistent - ([81d3bc9](https://github.com/lukexor/tetanes/commit/81d3bc9849c2dd30bca22933c5478b7c787259c7))\n\n### 📚 Documentation\n\n\n- Updated docs - ([806c078](https://github.com/lukexor/tetanes/commit/806c0789b65644d5a4e7d0365026fcf011d8770b))\n- Updated readme - ([16db37d](https://github.com/lukexor/tetanes/commit/16db37d3fc8e1a5507e613e498bf239098e691e4))\n- Fixed docs - ([3de4078](https://github.com/lukexor/tetanes/commit/3de40789ba4df94711d5dbf9361857479147e73b))\n- Added temporary readme - ([bf0d5db](https://github.com/lukexor/tetanes/commit/bf0d5db99ec0baff09c0e4bdc2c2500d1025080f))\n- Fixed README - ([6d48eec](https://github.com/lukexor/tetanes/commit/6d48eec5bb8fb4bfadea5f836776db1f64bbd04a))\n- Fix README for real - ([a5fd672](https://github.com/lukexor/tetanes/commit/a5fd672c176c7c6ce52ae75cce656c18c62a221a))\n- Fix README - ([05abf3c](https://github.com/lukexor/tetanes/commit/05abf3c89c7003559885c3b40b9d36dd591a9b2b))\n- Updated README - ([ffb7b21](https://github.com/lukexor/tetanes/commit/ffb7b2129642d872b968b8017fdd7149e7e71d1d))\n- Updated README roadmap - ([0941d86](https://github.com/lukexor/tetanes/commit/0941d863d6bb1a4d5b2347132127c4b350dcebbb))\n\n### ⚡ Performance\n\n\n- Improved cpu usage - ([e17a901](https://github.com/lukexor/tetanes/commit/e17a901c1a3bc0ce37ff416b19a2ec484234b87f))\n\n### 🧪 Testing\n\n\n- Fixed test - ([4beb787](https://github.com/lukexor/tetanes/commit/4beb78713c5a71eb3fb31fd293abee535b702184))\n- Fixed tests - ([531683d](https://github.com/lukexor/tetanes/commit/531683d24e32ec5ebc17ba54420204c4411b531c))\n- Moved tests to tetanes-core - ([3d105ff](https://github.com/lukexor/tetanes/commit/3d105ff5fcc28503844ed6187cf9560361a3f90f))\n\n### ⚙️ Miscellaneous Tasks\n\n\n- Added linux builds - ([3f9c244](https://github.com/lukexor/tetanes/commit/3f9c244c3b87817d679a91ad9e596bcad79ad498))\n- Updated ci for release - ([da465d7](https://github.com/lukexor/tetanes/commit/da465d7f535e02b8526026ca4e443e2a23d58da4))\n- Moved lints and added const fns - ([cfe5678](https://github.com/lukexor/tetanes/commit/cfe5678f541a125a742a01fdc157333fb60b3e7e))\n- Updated deps and msrv - ([f66b75e](https://github.com/lukexor/tetanes/commit/f66b75ea2be1846c5aa70a4a94616134ebcaabe2))\n- Updated deps and msrv - ([01d5f68](https://github.com/lukexor/tetanes/commit/01d5f6877b561c1bc3d5ea1e3b8fed294ee71439))\n- Updated tetanes-web dependencies - ([3fe0dba](https://github.com/lukexor/tetanes/commit/3fe0dbaf59d508cddffe1b197e811d3089d22170))\n- Updated ci - ([f34a125](https://github.com/lukexor/tetanes/commit/f34a125b98c164566905d9962368fff5bfb346a8))\n- Increase MSRV - ([684e771](https://github.com/lukexor/tetanes/commit/684e771a488f1cb88541b62265556b0fc79664a8))\n- Refactor workflows - ([41f4bc5](https://github.com/lukexor/tetanes/commit/41f4bc51bdc163704c3b481f72413c77d51a4a59))\n- Update Cargo.lock - ([98168f8](https://github.com/lukexor/tetanes/commit/98168f8ef734a7fc5a5ad3592fb76fe07b4d9a62))\n- Update license - ([7e84f7f](https://github.com/lukexor/tetanes/commit/7e84f7f38e54d216f5a97fac9af4d9690ed5ea40))\n- Add readme badges - ([60c185c](https://github.com/lukexor/tetanes/commit/60c185c61dec39407eee8af5c069ffc456daec52))\n\n## [0.9.0](https://github.com/lukexor/tetanes/compare/v0.8.0..tetanes-v0.9.0) - 2023-10-31\n\n### ⛰️ Features\n\n- Added famicom 4-player support (fixes #78 - ([141e4ed](https://github.com/lukexor/tetanes/commit/141e4ed7b33e93d1cf183be327070d6532a16324))\n- Added clock_inspect to Cpu - ([34944e6](https://github.com/lukexor/tetanes/commit/34944e63a4b0c72626c3313d2b849f0fa64a1c62))\n- Added `Mapper::empty()` - ([30678c1](https://github.com/lukexor/tetanes/commit/30678c127231614316b3df97d7c95501ae77287c))\n\n### 🐛 Bug Fixes\n\n- *(events)* Fixed toggling menus - ([f30ade8](https://github.com/lukexor/tetanes/commit/f30ade860c3dd5ff995cadd4e409159d7fce9d91))\n\n- Fixed wasm - ([7abd62a](https://github.com/lukexor/tetanes/commit/7abd62ad5a3178b5f77ae6c5c026d336d3237908))\n- Fixed warnings - ([f88d760](https://github.com/lukexor/tetanes/commit/f88d760fa4d8c6fd9f532e70ba76f95b54273e5c))\n- Fixed a number of bugs - ([5fd85af](https://github.com/lukexor/tetanes/commit/5fd85afd53efb8321f767b98372218eefc6e06a5))\n- Fixed default tile attr - ([9002fa8](https://github.com/lukexor/tetanes/commit/9002fa87806fcd56009369bbcd2644400ae7c5a1))\n- Fixed exram mode - ([8507984](https://github.com/lukexor/tetanes/commit/85079842d9ffe89b3e8ae61143642e09b3c471e6))\n- Fix crosshair changes - ([10d843e](https://github.com/lukexor/tetanes/commit/10d843e78cb04a69ad8d40e334a21268f294861b))\n- Fix audio on loading another rom - ([d7cc16c](https://github.com/lukexor/tetanes/commit/d7cc16cf7475f2b8bd632c2fc74a7b3c09447127))\n- Improved wasm render performance - ([561be90](https://github.com/lukexor/tetanes/commit/561be907f652a7f4879e943b44274547c0e43172))\n- Web audio tweaks - ([5184e11](https://github.com/lukexor/tetanes/commit/5184e11ba6fd104b59668e57744fb03b993483b5))\n- Fixed game genie codes - ([0206d6f](https://github.com/lukexor/tetanes/commit/0206d6fd20e5aa66da716eb07c6e077bfe4c5eed))\n- Fixed update rate clamping - ([2133b84](https://github.com/lukexor/tetanes/commit/2133b84ba865a0e715fa98129d9d6997540bd3e5))\n- Fix resetting sprite presence - ([d219ce0](https://github.com/lukexor/tetanes/commit/d219ce02320573498c84651af1585df08ed0c44e))\n- Fixed missing Reset changes - ([808fcac](https://github.com/lukexor/tetanes/commit/808fcac032d3b10731e330ecdcb9c468117a5425))\n- Fixed missed clock changes - ([1b5313b](https://github.com/lukexor/tetanes/commit/1b5313bf61115457beb50d919bb1129e54adc7cc))\n- Fixed toggling debugger - ([e7bcfc1](https://github.com/lukexor/tetanes/commit/e7bcfc1238fd21f957eed582b92a35e236e9884c))\n- Fixed resetting output_buffer - ([0802b2b](https://github.com/lukexor/tetanes/commit/0802b2b35c14f34fc8d3e73f3bd1ce940b4a8f48))\n- Fixed confirm quit - ([48d6538](https://github.com/lukexor/tetanes/commit/48d6538d25833a0817b0a27344a58a2a4918ab68))\n\n### 🚜 Refactor\n\n- Various updates - ([da213ae](https://github.com/lukexor/tetanes/commit/da213ae36aa0b4763c643d089e390d184d69dc19))\n- Small renames - ([0dea0b6](https://github.com/lukexor/tetanes/commit/0dea0b6d15204f1fdfbe91ef8f9365993ffafaf2))\n- Various cleanup - ([8d25103](https://github.com/lukexor/tetanes/commit/8d251030a9782bfb9f18fb29ce67b3853b8cf9bd))\n- Cleaned up some interfaces - ([da3ba1b](https://github.com/lukexor/tetanes/commit/da3ba1b1b93f7ecacec3a93746026c0da174cbd4))\n- Genie code cleanup - ([e483eb5](https://github.com/lukexor/tetanes/commit/e483eb5e9a793b8883710399a7bb39c3cc6ed3ee))\n- Added region getters - ([74d4a76](https://github.com/lukexor/tetanes/commit/74d4a769fd089e3ff18295d88c5eaa60dcc208be))\n- Cleaned up setting region - ([45dc2a4](https://github.com/lukexor/tetanes/commit/45dc2a42928a7d9c507351965969b7976ca7b25c))\n- Flatten NTSC palette - ([792d7db](https://github.com/lukexor/tetanes/commit/792d7dbc45ec230df4de63b552d24bb4bbabc5c6))\n- Converted system palette to array of tuples - ([284f54b](https://github.com/lukexor/tetanes/commit/284f54b877ccbf6103920cba483ee7d0175f4c5d))\n- Condensed MapRead and MapWrite to MemMap trait - ([bce1c77](https://github.com/lukexor/tetanes/commit/bce1c7794ab0dc6ab493618f697fcc088864afb0))\n- Made control methods consistent - ([f93040d](https://github.com/lukexor/tetanes/commit/f93040d25128f50c20226ba3d52d638dbdd85ac3))\n- Switch u16 addresses to use from_le_bytes - ([d8936af](https://github.com/lukexor/tetanes/commit/d8936afaf8e3da54616d430cf3488f64a1aae5ef))\n- Moved genie to it's own module - ([77b571f](https://github.com/lukexor/tetanes/commit/77b571f990c4f86d30e160382ff773486a8a54a9))\n- Cleaned up Power and Clock traits - ([533c0c3](https://github.com/lukexor/tetanes/commit/533c0c3485cc73f880c4d43b2f937c0e606d0360))\n- Cleaned up bg tile fetching - ([0710f16](https://github.com/lukexor/tetanes/commit/0710f162928964209898ef7fdf6aacd3a3e4a1a0))\n- Move NTSC palette declaration - ([9edffd1](https://github.com/lukexor/tetanes/commit/9edffd1be33b3f79e1fb1a187bc89d3aede58804))\n- Cleaned up memory traits - ([c98f7ff](https://github.com/lukexor/tetanes/commit/c98f7fffc59f3ac399864c7ff130cbaad99762f6))\n- Swapped lazy_static for once_cell - ([cc9e67f](https://github.com/lukexor/tetanes/commit/cc9e67f643cf60ad982c88979b92f0ca843d505a))\n\n### ⚡ Performance\n\n- Cleaned up inlines - ([b791cc3](https://github.com/lukexor/tetanes/commit/b791cc3ef7ece4fe0b627ff7332020453aa086ce))\n- Added inline to cart clock - ([eb9a0e0](https://github.com/lukexor/tetanes/commit/eb9a0e0d04fa6ec2d7f36935841dbda36a365bf8))\n- Changed decoding loops - ([a181ed4](https://github.com/lukexor/tetanes/commit/a181ed46e23534294463856ca3fec3c55cf2938f))\n- Performance tweaks - ([0c06758](https://github.com/lukexor/tetanes/commit/0c0675811709a2b590c273cd9010766e055b61ad))\n\n### 🎨 Styling\n\n- Fixed nightly lints - ([3bab00d](https://github.com/lukexor/tetanes/commit/3bab00dd1478be71f11baaed97c3aa8f3cc6d241))\n\n### 🧪 Testing\n\n- Disable broken test for now - ([93857cb](https://github.com/lukexor/tetanes/commit/93857cbd81d5b3f82ed157fdb87c0fa22aff1bc7))\n- Remove vimspector for global config - ([56edc15](https://github.com/lukexor/tetanes/commit/56edc15af9285d27d2e180ae585ecf91cc6f1e2a))\n\n### ⚙️ Miscellaneous Tasks\n\n- Increase MSRV - ([684e771](https://github.com/lukexor/tetanes/commit/684e771a488f1cb88541b62265556b0fc79664a8))\n\n## [0.8.0](https://github.com/lukexor/tetanes/compare/v0.7.0...v0.8.0) - 2022-06-20\n\n### Added\n\n- Added `Mapper 024` and `Mapper 026` support.\n- Added `Mapper 071` support.\n- Added `Mapper 066` support.\n- Added `Mapper 155` support. [#36](https://github.com/lukexor/tetanes/pull/36)\n- Added configurable keybindings via `config.json`.\n- Added `Config` menu.\n- Added `Keybind` menu (still a WIP).\n- Added `Load ROM` menu.\n- Added `About` menu.\n- Added `Zapper` light gun support with a mouse.\n- Added lots of automated test roms.\n- Added `4-Player` support. [#32](https://github.com/lukexor/tetanes/issues/32)\n- Added audio `Dynamic Rate Control` feature.\n- Added `Cycle Accurate` feature.\n\n### Changed\n\n- Various `README` improvements.\n- Default `VSync` to `true`.\n- Default `MMC1` to PRG RAM enable.\n- Changed audio filtering and playback.\n- Redesigned `TetaNES Web` UI and improved performance.\n\n### Fixed\n\n- Fixed Power Cycle/Reset affecting `ppuaddr`.\n- Fixed reset causing\n  segfault. [#50](https://github.com/lukexor/tetanes/issues/50)\n- Fixed reset and load updating the correct ROM\n  banks. [#51](https://github.com/lukexor/tetanes/issues/51)\n- Fixed `OAM` emulation. [#31](https://github.com/lukexor/tetanes/issues/31)\n- Fixed `DMA` emulation. [#30](https://github.com/lukexor/tetanes/issues/30)\n- Fixed 512k `SxROM` games.\n- Fixed `IRQ` and `NMI` emulation.\n\n### Removed\n\n- Removed `vcpkg` feature support due to flaky failures.\n\n### Breaking\n\n- Major refactor of all features, affecting save and replay files.\n- Removed several command-line flags in favor of `config.json` and `Config`\n  menu.\n"
  },
  {
    "path": "tetanes/Cargo.toml",
    "content": "[package]\nname = \"tetanes\"\nversion.workspace = true\nedition.workspace = true\nlicense.workspace = true\ndescription = \"A cross-platform NES Emulator written in Rust using wgpu\"\nauthors.workspace = true\nreadme = \"../README.md\"\nrepository.workspace = true\nhomepage.workspace = true\ncategories = [\"emulators\", \"wasm\"]\nkeywords = [\"nes\", \"emulator\", \"wasm\"]\nexclude = [\"/bin\"]\ndefault-run = \"tetanes\"\n\n[[bin]]\nname = \"tetanes\"\ntest = false\nbench = false\n\n[[bin]]\nname = \"build_artifacts\"\ntest = false\nbench = false\n\n[lints]\nworkspace = true\n\n[features]\ndefault = []\nprofiling = [\"tetanes-core/profiling\"]\n# Until webgpu is stable on all platforms\nwebgpu = []\n\n[dependencies]\nanyhow.workspace = true\nbincode.workspace = true\nbytemuck = \"1.15\"\ncfg-if.workspace = true\nchrono = { version = \"0.4\", default-features = false, features = [\n  \"std\",\n  \"clock\",\n] }\ncrossbeam = \"0.8\"\ndirs.workspace = true\negui = { version = \"0.34\", default-features = false, features = [\n  \"bytemuck\",\n  \"color-hex\",\n  \"default_fonts\",\n  \"persistence\",\n  \"serde\",\n] }\negui_extras = { version = \"0.34\", default-features = false, features = [\n  \"image\",\n  \"serde\",\n] }\ngilrs = { version = \"0.11\", features = [\"serde-serialize\"] }\nhound = \"3.5\"\nimage.workspace = true\nparking_lot = \"0.12\"\nprofiling = { version = \"1.0\", default-features = false }\nringbuf = \"0.4\"\nserde.workspace = true\nserde_json.workspace = true\nsysinfo = \"0.38\"\ntetanes-core.workspace = true\nthingbuf = \"0.1\"\nthiserror.workspace = true\ntracing.workspace = true\ntracing-subscriber.workspace = true\nuuid = { version = \"1.16\", features = [\"v4\", \"serde\"] }\nwebbrowser = { version = \"1.0\", features = [\"hardened\", \"disable-wsl\"] }\nwgpu = \"29.0\"\nwinit = { version = \"0.30\", features = [\"serde\"] }\n\n[target.'cfg(not(target_arch = \"wasm32\"))'.dependencies]\n# Blocked upgrading until egui upgrades accesskit\naccesskit = \"0.24\"\n# Blocked upgrading until accesskit can be upgraded\naccesskit_winit = \"0.32\"\narboard = { version = \"3.4\", default-features = false, features = [\n  \"wayland-data-control\",\n  \"windows-sys\",\n] }\nclap.workspace = true\ncpal = \"0.17\"\nctrlc = { version = \"3.5\", features = [\"termination\"] }\negui = { version = \"0.34\", default-features = false }\npollster = \"0.4\"\nreqwest = { version = \"0.13\", default-features = false, features = [\n  \"rustls\",\n  \"blocking\",\n] }\nrfd = \"0.17\"\nsemver = \"1\"\nsysinfo = { version = \"0.38\", default-features = false, features = [\n  \"system\",\n  \"disk\",\n  \"network\",\n] }\ntracing-appender = \"0.2\"\n\n[target.'cfg(target_arch = \"wasm32\")'.dependencies]\nbase64 = \"0.22\"\nchrono = { version = \"0.4\", default-features = false, features = [\n  \"std\",\n  \"clock\",\n  \"wasmbind\",\n] }\nconsole_error_panic_hook = \"0.1\"\ncpal = { version = \"0.17\", features = [\"wasm-bindgen\"] }\n# Required because of downstream dependencies: https://docs.rs/getrandom/latest/getrandom/#webassembly-support\ngetrandom = { version = \"0.4\", features = [\"wasm_js\"] }\ntracing-web = \"0.1\"\nuuid = { version = \"1.10\", features = [\"v4\", \"serde\", \"rng-getrandom\"] }\nwgpu = { version = \"29.0\", features = [\n  \"webgl\",\n  \"fragile-send-sync-non-atomic-wasm\", # Safe because we're not enabling atomics\n] }\nweb-sys = { workspace = true, features = [\n  \"Clipboard\",\n  \"ClipboardEvent\",\n  \"DataTransfer\",\n  \"Document\",\n  \"DomTokenList\",\n  \"Element\",\n  \"File\",\n  \"FileList\",\n  \"FileReader\",\n  \"HtmlAnchorElement\",\n  \"HtmlCanvasElement\",\n  \"HtmlElement\",\n  \"HtmlInputElement\",\n  \"HtmlDivElement\",\n  \"Navigator\",\n  \"SpeechSynthesis\",\n  \"SpeechSynthesisUtterance\",\n  \"Window\",\n] }\nwasm-bindgen = \"0.2.118\"\nwasm-bindgen-futures = \"0.4\"\nzip = { version = \"8.0\", default-features = false, features = [\"deflate\"] }\n\n[package.metadata.deb]\nextended-description = \"\"\"\n`TetaNES` is a cross-platform emulator for the Nintendo Entertainment System\n(NES) released in Japan in 1983 and North America in 1986, written in\nRust using wgpu. It runs on Linux, macOS, Windows, and in a web browser\nwith Web Assembly.\n\nIt started as a personal curiosity that turned into a passion project. It is\nstill being actively developed with new features and improvements constantly\nbeing added. It is a fairly accurate emulator that can play most NES titles.\n\n`TetaNES` is also meant to showcase using Rust's performance, memory safety, and\nfearless concurrency features in a large project. Features used in this project\ninclude complex enums, traits, generics, matching, iterators, channels, and\nthreads.\n\nTry it out in your browser (https://lukeworks.tech/tetanes-web)!\n\"\"\"\nsection = \"game\"\nassets = [\n  [\n    'target/release/tetanes',\n    '/usr/bin/',\n    '755',\n  ],\n  [\n    \"README.md\",\n    \"usr/share/doc/tetanes/README\",\n    \"644\",\n  ],\n]\n"
  },
  {
    "path": "tetanes/assets/main.css",
    "content": "@font-face {\n  font-family: \"Pixeloid Sans\";\n  src:\n    local(\"Pixeloid Sans\"),\n    url(\"./pixeloid-sans.ttf\") format(\"truetype\");\n}\n@font-face {\n  font-family: \"Pixeloid Sans Bold\";\n  src:\n    local(\"Pixeloid Sans Bold\"),\n    url(\"./pixeloid-sans-bold.ttf\") format(\"truetype\");\n}\n\nbody {\n  --color: #e6b673;\n  --heading: #a9491f;\n  --background: #0f1419;\n  background-color: var(--background);\n  max-width: 80%;\n  margin: auto;\n  margin-bottom: 100px;\n  color: var(--color);\n  font-family: \"Pixeloid Sans\", \"Courier New\", Courier, monospace;\n}\n\nh1 {\n  color: var(--heading);\n  font-family: \"Pixeloid Sans Bold\", \"Courier New\", Courier, monospace;\n  margin-top: 80px;\n  margin-bottom: 40px;\n  line-height: 0.5;\n  text-align: center;\n}\n\nh1 span {\n  color: var(--color);\n  font-family: \"Pixeloid Sans\", \"Courier New\", Courier, monospace;\n  font-size: 0.8rem;\n}\n\nh2 {\n  color: var(--heading);\n  font-family: \"Pixeloid Sans Bold\", \"Courier New\", Courier, monospace;\n  text-align: center;\n  margin-top: 40px;\n}\n\np {\n  font-size: 0.9rem;\n  max-width: 70ch;\n  margin: 15px 0;\n}\n\ntable {\n  --color: #333;\n  border-collapse: separate;\n  border-color: var(--color);\n  border-spacing: 0;\n  border: 0.5px solid var(--color);\n  text-align: left;\n  width: 100%;\n}\n\nth {\n  color: var(--heading);\n}\n\nth,\ntd {\n  padding: 5px;\n  border: 0.5px solid var(--color);\n}\n\na {\n  color: #36a3d9;\n  text-decoration: none;\n}\n\na:hover {\n  text-decoration: underline;\n}\n\ncanvas {\n  width: fit-content;\n  height: fit-content;\n  outline: none;\n}\n\n#wrapper {\n  display: flex;\n  justify-content: center;\n  flex-wrap: wrap;\n  margin-bottom: 40px;\n}\n\n#content {\n  max-width: 70ch;\n  margin: auto;\n}\n\n.hidden {\n  display: none !important;\n}\n.absolute {\n  position: absolute;\n}\n\n#loading-status {\n  position: absolute;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  width: 880px;\n  height: 696px;\n  border: 2px dotted #e6b673;\n  margin: 0;\n}\n\n.loader {\n  border: 4px solid #e6b673;\n  border-top: 4px solid #a9491f;\n  border-radius: 50%;\n  width: 16px;\n  height: 16px;\n  margin: 8px;\n  animation: spin 2s linear infinite;\n}\n\n#error {\n  color: #ff3333;\n  text-align: center;\n  margin: auto;\n}\n\n.version-download {\n  position: relative;\n  width: max-content;\n  margin: auto;\n}\n\n.version-download div {\n  width: 100%;\n}\n\n.version-download a {\n  display: block;\n  padding: 0.8rem;\n  background: #14191f;\n  color: #a9491f;\n  font-family: \"Pixeloid Sans Bold\", \"Courier New\", Courier, monospace;\n}\n\n.version-download a:hover {\n  background: #212733;\n  text-decoration: none;\n  cursor: pointer;\n}\n\n@keyframes spin {\n  0% {\n    transform: rotate(0deg);\n  }\n  100% {\n    transform: rotate(360deg);\n  }\n}\n"
  },
  {
    "path": "tetanes/assets/pixeloid-license.txt",
    "content": "﻿Copyright © 2020-2021 GGBot (https://ggbot.net/fonts/), with Reserved Font Name \"Pixeloid\".\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at:\nhttps://openfontlicense.org/\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe goals of the Open Font License (OFL) are to stimulate worldwide\ndevelopment of collaborative font projects, to support the font creation\nefforts of academic and linguistic communities, and to provide a free and\nopen framework in which fonts may be shared and improved in partnership\nwith others.\n\nThe OFL allows the licensed fonts to be used, studied, modified and\nredistributed freely as long as they are not sold by themselves. The\nfonts, including any derivative works, can be bundled, embedded,\nredistributed and/or sold with any software provided that any reserved\nnames are not used by derivative works. The fonts and derivatives,\nhowever, cannot be released under any other type of license. The\nrequirement for fonts to remain under this license does not apply\nto any document created using the fonts or their derivatives.\n\nDEFINITIONS\n\"Font Software\" refers to the set of files released by the Copyright\nHolder(s) under this license and clearly marked as such. This may\ninclude source files, build scripts and documentation.\n\n\"Reserved Font Name\" refers to any names specified as such after the\ncopyright statement(s).\n\n\"Original Version\" refers to the collection of Font Software components as\ndistributed by the Copyright Holder(s).\n\n\"Modified Version\" refers to any derivative made by adding to, deleting,\nor substituting -- in part or in whole -- any of the components of the\nOriginal Version, by changing formats or by porting the Font Software to a\nnew environment.\n\n\"Author\" refers to any designer, engineer, programmer, technical\nwriter or other person who contributed to the Font Software.\n\nPERMISSION & CONDITIONS\nPermission is hereby granted, free of charge, to any person obtaining\na copy of the Font Software, to use, study, copy, merge, embed, modify,\nredistribute, and sell modified and unmodified copies of the Font\nSoftware, subject to the following conditions:\n\n1) Neither the Font Software nor any of its individual components,\nin Original or Modified Versions, may be sold by itself.\n\n2) Original or Modified Versions of the Font Software may be bundled,\nredistributed and/or sold with any software, provided that each copy\ncontains the above copyright notice and this license. These can be\nincluded either as stand-alone text files, human-readable headers or\nin the appropriate machine-readable metadata fields within text or\nbinary files as long as those fields can be easily viewed by the user.\n\n3) No Modified Version of the Font Software may use the Reserved Font\nName(s) unless explicit written permission is granted by the corresponding\nCopyright Holder. This restriction only applies to the primary font name as\npresented to the users.\n\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\nSoftware shall not be used to promote, endorse or advertise any\nModified Version, except to acknowledge the contribution(s) of the\nCopyright Holder(s) and the Author(s) or with their explicit written\npermission.\n\n5) The Font Software, modified or unmodified, in part or in whole,\nmust be distributed entirely under this license, and must not be\ndistributed under any other license. The requirement for fonts to\nremain under this license does not apply to any document created\nusing the Font Software.\n\nTERMINATION\nThis license becomes null and void if any of the above conditions are\nnot met.\n\nDISCLAIMER\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\nOTHER DEALINGS IN THE FONT SOFTWARE.\n"
  },
  {
    "path": "tetanes/assets/roms/alter_ego.txt",
    "content": "Development notes\n\nAfter completing Lawn Master, I had plan to try use C compiler to make a simple\nNES game. From my previous experience with programming in C for micros (Genesis\nand ZX Spectrum), and from things that thefox did, I knew it could be very\nworthy in terms of development speed. The plan was to check it, and, in case of\nsuccess, prove that C is an actual option to develop NES games, not just a\ntheoretical possibility.\n\nI wanted to make a project very fast, so I decided to not do an original game\nthis time, because design takes most of the time, and just make a port. I've\nseen two new ZX Spectrum games by Denis Grachev, Join and Alter ego, at WoS when\nthey were released, and liked the combination of simplicity, playability, and\nsort of retro appeal in them. I made a low-level library in 6502 assembly to use\nin the project first. When main features of the library were implemented, I have\nsent a mail to Denis, asking if he would allow to port Alter Ego. It took some\ntime, from June 8 to 17, to get the answer. Denis gave his permission, but I\nalready was busy with other project, and only has been able to start on the port\nJune 25.\n\nDevelopment process took about 10 days, the game was fully completed, but\nwithout music, at July 5. This includes finishing the library, writing all the\ngame code from scratch, reverse-engineering levels format, and beating up both\nthe original game and port few times to test everything. The most difficult part\nthat was not expected by me initially was redesign of all the levels from\nscratch. Initially I thought I can just convert them, edit a bit, and draw new\ngraphics, but in order to be able to use more colors and make better graphics I\nhad to completely redo all the levels to the NES attribute grid, only keeping\noverall design of the original levels. In other words, none of the original data\nget into the port, and there were some changes to make it more playable as well,\nso it is actually more like a remake than a port. Levels and graphics redesign\ntook most of the time. I also made 5 graphics sets instead of 3 sets from the\noriginal.\n\nCode part was relatively easy both in assembly (low-level libary) and C (game),\nexcept for few WTF bugs that took some time to figure out. There are about 1000\nlines of assembly code for library, 1000 lines of FamiTone code (has been\nadapted easiliy), and ~1500 lines of C code. Even total number of lines, 3500,\nis significally less than amount of assembly code in my previous NES games that\nwere written in assembly, had ~5000 lines each (including FamiTone too), and\nwere much simpler gameplay and game logic wise.\n\nAs the game was a bit short on RAM, I've put FamiTone vars along with palette\nbuffer into the stack page. Despite being written in C, the game uses ~20 bytes\nof the stack at most.\n\nOther part of speeding up the development process was 'outsourcing' of the\nmusic. I knew it is a risky decision, because any other person involved into a\nproject actually increases overall time, not decreases it, but I just tired from\nmaking everything by myself all the time. It did increased time very\nconsiderably - although I've negotiated about the music with kulor even before\nstarting any actual work on the game, by different reasons including personal\nbusyness and some misunderstanding, he only started 15th, ten days after the\ngame development itself was completed. This amount of music revealed a lot of\nbugs and problems in FamiTone, not all of which were fixed, and data of one of\ntracks was fixed by hand due to lack of time. Music was finished 22th, just in\ntime for DiHalt demoparty. Initially I planned to just release game, but because\nthe party date was now close, and there was a multiplatform game compo, I\ndecided to release the game there to get more publicity.\n\nMy conclucion regarding C usage on NES is that it is worthy indeed. It speeds up\nand simplifies development process a lot because it greatly reduces amount of\ncode to be written and debugged, and the code is much more readable. However, to\nuse C you just have to know the system and 6502 very well, because debugging is\nmuch more difficult - in case of the problems when C code does not work as\nexpected, you need to figure out what to do by examining of the generated\nassembly code. So it is not easy way to program for NES, it actually requires\nmore knowledge than programming in assembly. Execution speed is, of course,\nlower, but this wasn't an issue for this project, the size of the generated code\nwas more important actually - it is much larger than it could be if programmed\nin assembly by hand.\n\nPlease note that the game is released as freeware, not Public Domain. There are\nthree authors involved. I personally grant you rights to do whatever you want\nwith things I created (code, sound effects, graphics), but rights to other\ncomponents (game concept, characters, title, music) are reserved to authors of\nthese components. I.e., if you want to port it somewhere else, you need to ask\nDenis Grachev (and Kulor, if you need music) for permission.\n\n\nSoftware used\n\nCC65 - C compiler and assembler Notepad++ - for all the code and text works\nFamiTracker - to make all the music and sound effects UnrealSpeccy - playing and\nreversing the original version Borland Turbo Explorer - to make a level editor,\nbut it was only used to view levels FCEUX, VirtuaNES (profiler mod) - to test\neverything, some others for compatibility tests NES Screen Tool - to design all\nthe graphics, screens, and levels Inkscape, Blender, GIMP, CutePDF Writer - to\nmake manual and label\n\nhttps://shiru.untergrund.net/software.shtml\n"
  },
  {
    "path": "tetanes/assets/roms/ao_demo.txt",
    "content": "AO\nby Second Dimension\n\nAO is a homebrew puzzle game. Each of the 33 levels consists of a fixed, single\nscreen environment where a brick needs to be guided to a goal pit (exit point)\nin a maze-like environment shown through a top-down view. The brick is\ncontrolled directly and the view is in 2D, but the player needs to think in 3D\nto solve the puzzle. The brick takes up two spaces when it is laying on its side\nand one space when it is standing up. To get around in the maze the brick often\nneeds to be brought into a specific position so it can move without falling off\nthe edge. For instance: some passages only have room for a single space and then\nthe brick cannot pass unless it is in a good position. When falling off the\nledge the level restarts right away and there are unlimited lives. Gradually\nadditional elements are introduced such as switches, teleportation blocks and\nweak floors. The game contains a competitive two-player mode where two players\nmove through the maze simultaneously with the goal to reach the exit first. The\ngame provides 1,000 seconds to complete the game, but it is possible to continue\nplaying even when time has run out.\n\nhttps://www.second-dimension.com/catalog/ao\n"
  },
  {
    "path": "tetanes/assets/roms/assimilate.txt",
    "content": "Assimilate\nby Nessylum Games\n\nEver wanted to anal probe someone? Well the wait is over!\n\nAssimilate is the game you and your dopey little friends have been waiting\nfor. Join the super-ship Ossan for twenty plus levels of zombifying,\nbrainwashing, human-dominating excitement. Will you succeed in conquering the\nentire human race? Or will you cry yourself to sleep after watching Ossan\nexplode in a fiery ball of humiliating destruction? What happens is up to you.\n\nBegin assimilation.\n\nPretty awesome action game, also available on actual cartridge.\n\nhttps://www.youtube.com/watch?v=cXCGeqvQ7Y4\nhttps://forums.nesdev.org/viewtopic.php?t=7087&hilit=assimilate\n"
  },
  {
    "path": "tetanes/assets/roms/blade_buster.txt",
    "content": "Blade Buster\nby High Level Challenge\n\nIt is produced within the constraints of the NES in fact, it runs on emulator\nand the operation of the actual machine has also been confirmed.\n\nYou have 2 minutes in a caravan format to compete the score at 5 minutes. It has\nbecome a game with a sense of exhilaration that has good old shoot before being\nshot playing style.\n\nhttp://hlc6502.web.fc2.com/Bbuster.htm\n"
  },
  {
    "path": "tetanes/assets/roms/cheril_the_goddess.txt",
    "content": "Cheril the Goddess\nBy The Mojon Twins\n\nYou control Cheril. There’s plenty of things to do, and you’ll need special\nobjects. Cheril can only carry ONE object at a time, so if you try to get a new\none while you are carring an object, you will drop the one you carry in the\nplace of the one you take. To get/interchange objects press DOWN. Besides,\nyou’ll have to give those objects a use. To use an object, just walk to the\nplace you want to use it and press DOWN. For Cheril to fly, she has to\n«push». You can make her «push» by pressing UP. But beware: «pushing» for too\nlong drains Cheril’s vitality, so you have to do this carefully. Timing short\n«pushes» can make her gain momentum. Take your time to master the technique, it\nwon’t take long. Cheril can also fire power balls. You can fire pressing\nFIRE. Power balls also drain Cheril, but think that enemies do quite a lot of\nharm. Use this feature wisely, and just when needed! Cheril can also regain her\nvitality by means of a special action we won’t reveal ‘cause it’s easy enough to\nfind out! You can choose the difficulty level, but the easiest level won’t show\nthe real ending.\n\nhttps://forums.nesdev.org/viewtopic.php?t=15367\n"
  },
  {
    "path": "tetanes/assets/roms/dushlan.txt",
    "content": "////////////////////////////////////////////////////////////////////////////\n//                                Dúshlán                                 //\n//                    Copyright (c)2016 Peter McQuillan                   //\n//                          All Rights Reserved.                          //\n//      Distributed under the BSD Software License (see license.txt)      //\n////////////////////////////////////////////////////////////////////////////\n\nThis package contains the source code for the game 'Dúshlán'.\nDúshlán is the Irish for 'Challenge'.\nThis game is written in 6502 assembly for the Nintendo Entertainment System (NES).\n\nThe code was developed using the ASM6 assembler.\n\nThere is a NES file included in the package that can be used on an emulator,\nor an a real NES using a Flash cart like Powerpak or Everdrive.\n\nThe game itself is based on the classic Tetris, but with a few twists on the\ngame and some extra features that are not commonly available like ghost (where\nyou can see where your piece would go if you dropped it) and save (where you\ncan swap a piece in play for later usage).\n\nThere are two possible button controls for the game, the normal controls are\n\nLEFT     -  Move left\nRIGHT    -  Move right\nDOWN     -  Drop piece\nUP       -  Swap/Save a piece for later usage\nA        -  Rotate piece clockwise\nB        -  Rotate piece anticlockwise\nSTART    -  Pause/Resume game\nSELECT   -  Enable/Disable ghost mode\n\nThere is also an alternate control system available on the main menu, these\nare the button definitions for it:\n\nLEFT     -  move left\nRIGHT    -  move right\nDOWN     -  Rotate piece clockwise\nUP       -  Rotate piece anticlockwise\nA        -  Drop piece\nB        -  Swap/Save a piece for later usage\nSTART    -  Pause/Resume game\nSELECT   -  Enable/Disable ghost mode\n\nYou are able to modify the behaviour of the 'Drop piece' key in the settings\non the main menu. The three possible options are\n\nFull - Pressing the drop key will cause the piece to fall down the screen as far as\npossible\nWhile Held - This will cause the piece to drop as long as the drop key is pressed\n(effectively works like a down key)\nMixture - If you quickly press/tap the drop key, the piece will fall down the screen\nas far as possible, however holding the drop key will drop the piece as long as the\ndrop key is pressed - therefore a mixture of 'Full' and 'While Held'\n\nThanks to Teuthida for the music and sound effects.\nThanks to Derek Andrews for the ggsound sound engine\nThanks also to Shiru, Drag, Damian Yerrick and Joe Granato for some code snippets used.\n\nPlease direct any questions or comments to beatofthedrum@gmail.com\n\nhttps://github.com/soiaf/Dushlan\n"
  },
  {
    "path": "tetanes/assets/roms/from_below.txt",
    "content": "From Below\nby Matt Hughson\n\nFROM BELOW is a falling block puzzle game featuring:\n\nSoft Drops Hard Drops Wall Kicks T-Spins Lock Delay 3 modes of play: Kraken\nBattle Mode\n\nThe signature mode of FROM BELOW. Battle the Kraken by clear lines across the\nonslaught of attacking Kraken Tentacles. The Tentacles push more blocks onto the\nscreen every few seconds, forcing to act quickly, and strategize on an every\nchanging board.\n\nClassic Mode\n\nThe classic block falling mechanics you know and love without any new\ngimicks. Modernize for 2020, with Hard Drops, Lock Delay, and more, making this\n(hopefully) the best feeling puzzle game on the NES!\n\nTurn Based Kraken Battle Mode\n\nSimilar to “Kraken Battle Mode”, but instead of the Kraken attacking every few\nseconds, it advances its tentacle every time you drop a piece. Make every move\ncount, as this move favors slow, deliberate play!\n\nhttps://mhughson.itch.io/from-below/devlog/212679/vs-system-beta-0100\n"
  },
  {
    "path": "tetanes/assets/roms/lan_master.txt",
    "content": "Lan Master\nby Shiru\n\nDevelopment notes\n\nThe project was started circa November 2010. I wanted to do a simple game that\nwon't take too long, and expected it to be finished in 2010. It took a half of a\nyear in the end, with some breaks and some side projects, including NES Screen\nTool and FamiTone.\n\nAt the very beginning I decided it going to be a simple NetWalk-like game (the\nNetWalk by Gamos, 1996), but without a server and with few additional\nelements. I had much of previous experience with games of this kind, so gameplay\npart was planned from the very beginning. The first thing that was done is\ngraphics mock up made with Graphics Gale, title screen and gameplay screen. With\nminor changes it became actual graphics.\n\nThe most difficult part of gameplay code, tracing, was done in C first, within\nthe level editor. Then it was rewritten in 6502 assembly. Adding the sound was a\ndifficult part as well, because most of the code game was done before I made\nFamiTone, and it wasn't designed properly. So sound update calls are really\nmessy in the code.\n\nThe game was developed without using real hardware at all, and only was tested\non the hardware at pre-release state. There are about 5100 lines of manually\nwritten code, and also data generated by tools.\n\nOne feature that I wanted to make was different palettes for levels. It was also\nsuggested by 0xabad1dea after testing on the hardware. I actually implemented\nthis feature, including color emphasis bits, but it just didn't look right\n(colors were too dark or too bright, and very different between the emulators),\nso it was removed.\n\nAfter the release, a problem was reported by mbrenaman - the ending screen was\nnot working properly on the real hardware, and also part of the cursor was\nmissing if the game is continued using a passcode. Series of fixes was done in\norder to fix the problem.\n\n\nSoftware used:\n\nGraphics Gale - both title screen and in-game graphics were made in it,\nconverted and edited with NES Screen Tool afterwards Notepad++ - for all the\ncode and text works NESASM v3.1 - to compile the code, no bugs everyone talking\nabout were surfaced in this project FamiTracker - to make all the music and\nsound effects FCEUX, Nintendulator - to test everything Borland Turbo Explorer -\nto make level editor Inkscape, Blender, CutePDF Writer - to make manual and\nlabel\n\nhttps://shiru.untergrund.net/software.shtml\n"
  },
  {
    "path": "tetanes/assets/roms/lawn_mower.txt",
    "content": "Lawn Mower\nby Shiru\n\nThe goal of this game is to mow all the grass before you run out of gas. Collect\ngas cans to keep yourself from running on empty.\n\nhttps://shiru.untergrund.net/software.shtml\n"
  },
  {
    "path": "tetanes/assets/roms/mad_wizard.txt",
    "content": "Mad Wizard\nBy Sly Dog Studios\n\nTake an adventure in the world of Candelabra!\n\nThe evil summoner Amondus from The Order of the Talon has taken over Prim,\nHekl's once happy homeland. And nothing drives a wizard more crazy than having\ntheir territory trampled on!\n\nCan you help Hekl defeat the enemies that Amondus has populated throughout the\nlandscape? To do so, you will need to master the art of levitation, find magic\nspells that will assist you in reaching new areas, and upgrade your weapons. All\nof these will be necessary in order to give Hekl the power he needs to restore\npeace to Prim. Do you have what it takes? If you dare, venture into this, the\nfirst installment of the Candelabra series!\n\nhttps://www.retrousb.com/product_info.php?cPath=31&products_id=137\n"
  },
  {
    "path": "tetanes/assets/roms/micro_knight.txt",
    "content": "Micro Knight\nBy SDM\n\nhttps://forums.nesdev.org/viewtopic.php?t=13450\n"
  },
  {
    "path": "tetanes/assets/roms/nebs_n_debs.txt",
    "content": "Nebs 'n Debs\nBy Dullahan Software\n\nNebs 'n Debs is a new game for the original Nintendo Entertainment System. Run,\njump, and dash your way through 12 levels as you search for the missing parts of\nDebs's ship to escape the hostile alien planet Vespasian 7MV! Nebs 'n Debs runs\non the same type of game cartridge as the original Super Mario Bros.\n\nhttps://dullahan-software.itch.io/nebs-n-debs\n"
  },
  {
    "path": "tetanes/assets/roms/owlia.txt",
    "content": "The Legends of Owlia\nBy Gradual Games\n\nThe Legends of Owlia is Gradual Games' second release for the NES. It is an\naction-adventure game inspired by StarTropics, Crystalis, and the Legend of\nZelda.\n\nOnce upon a time. On a world far beyond imagining. Six great owls brought forth\na land called Owlia. Together, they reigned in peace and wisdom for eighty\nthousand years. However, their pride in the beautiful land and sky of Owlia led\nthem to forget the vast vast seas... Mermon, King of the Mermen, oft rose to the\nsurface to view the Land of Owlia. His desire for sunlight, sky, and green\nforests grew until he decided the seas were not enough for him. He endeavored to\nsummon the six great owls one by one. He began sapping their powers of flight,\nempowering his minions to float towards the Land of Owlia to claim it for his\nown. However, one great owl eluded him.. Silmaran, the White King. Soaring high\nabove the Land of Owlia, as Mermon's forces grew in power, he searched for one\nwho might heed his call to rescue the great owls and restore the Land of Owlia.\n\nGuide heroine Adlanniel and her owl friend Tyto to free the great owls and\ndefeat Mermon king of the Mermen!!\n\n    12 enemy types and 5 fierce bosses!\n    7 full scrolling overworld maps!\n    5 screen by screen dungeons filled with puzzles!\n    8 cool techniques for your Owl Tyto to perform!\n    Slash enemies with your trusty sword!\n    Password system to save your progress!\n\nhttps://www.infiniteneslives.com/owlia.php\n"
  },
  {
    "path": "tetanes/assets/roms/streemerz.txt",
    "content": "\n\n\n          __| __|  __| _ \\  _ \\ __ `__ \\   _ \\  __|_  /\n        \\__ \\ |   |    __/  __/ |   |   |  __/ |     /\n        ____/\\__|_|  \\___|\\___|_|  _|  _|\\___|_|   ___|\n        SUPER  STRENGTH  EMERGENCY  SQUAD  -  ZETA  ...\n\n\n        NES port              (C) 2012 Faux Game Company\n                                       www.fauxgame.com\n\n        Original version      (C) 2010 Mr. Podunkian\n                                       www.superfundungeonrun.com\n\n\nInfo\n----\n\n\"Try climbing to the top of this one by throwing streamers and climbing\n them. On your way up you better watch out for the various pie throwing\n clowns, burning candles and bouncing balls, because if they get you,\n you'll die a little each time.\"\n\nThese were the orders given to you, Operative JOE when you were ordered\nto infiltrate the evil MASTER Y's floating fortress to destroy the TIGER\nARMY's top secret weapon.\n\n\nControls\n--------\n\nLeft/Right    Move\n\nA             Shoot the streamer (in normal game modes)\n              Flip the gravity (in Streeeeeemerz mode)\n\nStart         Pause the game\n\n\nTranslations\n------------\n\nFrench translation of the game is now available courtesy of Feel My Geek\nand Lestat.\n\nDisclaimer: The translation was provided by a 3rd party, and I have\nno way of verifying its accuracy in comparison to the English version.\n\n\nLicense\n-------\n\nDon't be a dick.\n\n\nVersion History\n---------------\n\n- v02 (2013-02-01)\n  Fixed infinite bouncing bug in Streeeeeemerz mode.\n  Fine-tuned controller handling during screen transitions.\n  Added French translation.\n\n- v01 (2012-10-10)\n  Initial release.\n"
  },
  {
    "path": "tetanes/assets/roms/super_painter.txt",
    "content": "Super Painter\nBy RetroSouls & Kulor\n\nTrapped in a colorless world, armed with a paintbrush - there’s only one thing\nto do! As Super Painter, you’ll have to fill in all the missing color from the\nwalls and ledges of 25 charming stages. Watch out for enemies and pits to the\nbottom, and don’t box yourself in - when you’re done painting, you’ll have to\nrace to the magic door to the next level. It’s platform puzzling at its\nfinest!\n\nhttps://www.retrosouls.net/?page_id=901\n"
  },
  {
    "path": "tetanes/assets/roms/tiger_jenny.txt",
    "content": "Tiger Jenny\nBy Ludosity\n\nTiger Jenny by Ludosity is a NES game set in the same universe as “Ittle Dew” it\ntakes place a thousand years before the events of that game. Battle your way\nthrough the forests to seek vengeance on the Turnip Witch who dwells in her\ncastle.\n\nhttps://pdroms.de/files/nintendo-nintendoentertainmentsystem-nes-famicom-fc/tiger-jenny\n"
  },
  {
    "path": "tetanes/assets/roms/yun.txt",
    "content": "Yun\nBy The Mojon Twins\n\nThe main goal is helping yun capturing every single being to fill the pantry of\nher restaurant. The Big Marsh near Lake Potoña (province of Badajoz), formed by\nthree areas (the marsh wood, the marsh abandoned factory and the mash desert) is\nfull of walking flesh Yun must capture.\n\nTo capture her enemies, Yun must stun them by means of hitting them with a\nbubble. Once they are stunned, they can be captured just touching them.\n\nYun’s bubbles are quite resistant. You can hump on them and let them carry you\nupwards, which is sometimes the only way to progress in the level.\n\nBesides, there’s some points where Yun might need a key to keep going.\n\nhttps://www.mojontwins.com/juegos_mojonos/yun/\n"
  },
  {
    "path": "tetanes/build.rs",
    "content": "fn main() {\n    if let Ok(target) = std::env::var(\"TARGET\") {\n        println!(\"cargo:rustc-env=DEFAULT_TARGET={target}\");\n    }\n}\n"
  },
  {
    "path": "tetanes/index.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <base data-trunk-public-url />\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <title>TetaNES Web</title>\n    <link\n      rel=\"preload\"\n      href=\"./pixeloid-sans.ttf\"\n      crossorigin=\"anonymous\"\n      as=\"font\"\n      type=\"font/ttf\"\n    />\n    <link\n      rel=\"preload\"\n      href=\"./pixeloid-sans-bold.ttf\"\n      crossorigin=\"anonymous\"\n      as=\"font\"\n      type=\"font/ttf\"\n    />\n\n    <link data-trunk rel=\"inline\" href=\"assets/main.css\" />\n    <link data-trunk rel=\"icon\" href=\"assets/tetanes_icon.png\" />\n    <link data-trunk rel=\"copy-file\" href=\"assets/pixeloid-sans.ttf\" />\n    <link data-trunk rel=\"copy-file\" href=\"assets/pixeloid-sans-bold.ttf\" />\n    <link\n      data-trunk\n      rel=\"rust\"\n      data-bin=\"tetanes\"\n      data-initializer=\"initializer.js\"\n      data-wasm-opt=\"4\"\n      data-wasm-opt-params=\"--enable-bulk-memory --enable-nontrapping-float-to-int\"\n      data-no-import\n    />\n  </head>\n  <body>\n    <noscript>\n      This page contains WebAssembly and Javascript content, please enable\n      Javascript in your browser.\n    </noscript>\n\n    <h1>TetaNES<br /><span id=\"version\"></span></h1>\n\n    <p id=\"error\" class=\"hidden\">\n      An internal error occurred. Try refreshing the page or filing a\n      <a href=\"https://github.com/lukexor/tetanes/issues/new\">bug report</a>.\n    </p>\n\n    <div id=\"wrapper\">\n      <h2 id=\"loading-status\">\n        <span class=\"loader\"></span>\n        Loading...\n      </h2>\n      <canvas id=\"frame\" width=\"880\" height=\"696\"></canvas>\n      <input type=\"file\" id=\"load-rom\" accept=\".nes\" class=\"hidden\" />\n      <input type=\"file\" id=\"load-replay\" accept=\".replay\" class=\"hidden\" />\n    </div>\n\n    <div id=\"content\">\n      <p>\n        <em>TetaNES</em> is a cross-platform emulator for the Nintendo\n        Entertainment System (NES) released in Japan in 1983 and North America\n        in 1986, written using\n        <a href=\"https://www.rust-lang.org/\" title=\"Rust\">Rust</a> and\n        <a href=\"https://wgpu.rs/\" title=\"wgpu\">wgpu</a>. It runs on Linux,\n        macOS, Windows, and in a web browser with\n        <a href=\"https://webassembly.org/\">WebAssembly</a>. While the web\n        version is playable, the desktop version is much more performant and\n        fully featured.\n      </p>\n\n      <p>\n        Load any NES ROM which uses the\n        <a href=\"https://www.nesdev.org/wiki/INES\">iNES</a> or\n        <a href=\"https://www.nesdev.org/wiki/NES_2.0\">NES 2.0</a> header format.\n      </p>\n\n      <p>\n        You can check out the code on\n        <a href=\"https://github.com/lukexor/tetanes\">github</a> or download the\n        desktop version:\n      </p>\n\n      <div id=\"version-download\" class=\"hidden version-download\">\n        <a id=\"selected-version\" rel=\"nofollow noopener\" target=\"_blank\">\n          Download for Windows\n        </a>\n        <div id=\"version-options\" class=\"hidden absolute\">\n          <a\n            id=\"x86_64-pc-windows-msvc\"\n            rel=\"nofollow noopener\"\n            target=\"_blank\"\n          >\n            Download for Windows\n          </a>\n          <a id=\"aarch64-apple-darwin\" rel=\"nofollow noopener\" target=\"_blank\">\n            Download for Mac - Apple Chip\n          </a>\n          <a id=\"x86_64-apple-darwin\" rel=\"nofollow noopener\" target=\"_blank\">\n            Download for Mac - Intel Chip\n          </a>\n          <a\n            id=\"x86_64-unknown-linux-gnu\"\n            rel=\"nofollow noopener\"\n            target=\"_blank\"\n          >\n            Download for Linux\n          </a>\n        </div>\n      </div>\n\n      <h2>Controls</h2>\n      <table>\n        <tr>\n          <th>Action</th>\n          <th>Key</th>\n        </tr>\n        <tr>\n          <td>A Button</td>\n          <td>Z</td>\n        </tr>\n        <tr>\n          <td>B Button</td>\n          <td>X</td>\n        </tr>\n        <tr>\n          <td>A Button (Turbo)</td>\n          <td>A</td>\n        </tr>\n        <tr>\n          <td>B Button (Turbo)</td>\n          <td>S</td>\n        </tr>\n        <tr>\n          <td>Select Button</td>\n          <td>Q</td>\n        </tr>\n        <tr>\n          <td>Start Button</td>\n          <td>W</td>\n        </tr>\n        <tr>\n          <td>D-Pad</td>\n          <td>Arrow Keys</td>\n        </tr>\n      </table>\n      <p>\n        Other mappings can be found and modified in the `Config -> Keybinds`\n        menu.\n      </p>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "tetanes/initializer.js",
    "content": "export default function () {\n  const loading = document.getElementById(\"loading-status\");\n  const error = document.getElementById(\"error\");\n  return {\n    onStart: () => {\n      console.log(\"Loading...\");\n      console.time(\"initializer\");\n      loading.classList.remove(\"hidden\");\n      error.classList.add(\"hidden\");\n    },\n    onProgress: ({ current, total }) => {\n      if (!total) {\n        console.log(`Loading... ${current} bytes`);\n      } else {\n        console.log(`Loading... ${Math.round(current / total) * 100}%`);\n      }\n    },\n    onComplete: () => {\n      console.log(\"Loading... done!\");\n      console.timeEnd(\"initializer\");\n      loading.classList.add(\"hidden\");\n    },\n    onSuccess: () => {\n      console.log(\"Loading... successful!\");\n      error.classList.add(\"hidden\");\n    },\n    onFailure: (error) => {\n      console.error(`Loading... failed! ${error}`);\n      loading.classList.add(\"hidden\");\n      error.classList.remove(\"hidden\");\n      error.innerText = `Loading... failed! ${error}`;\n    },\n  };\n}\n"
  },
  {
    "path": "tetanes/shaders/crt-easymode.wgsl",
    "content": "//  CRT Shader by EasyMode\n//  License: GPL\n//\n//  A flat CRT shader ideally for 1080p or higher displays.\n//\n//  Recommended Settings:\n//\n//  Video\n//  - Aspect Ratio:  4:3\n//  - Integer Scale: Off\n//\n//  Shader\n//  - Filter: Nearest\n//  - Scale:  Don't Care\n//\n//  Example RGB Mask Parameter Settings:\n//\n//  Aperture Grille (Default)\n//  - Dot Width:  1\n//  - Dot Height: 1\n//  - Stagger:    0\n//\n//  Lottes' Shadow Mask\n//  - Dot Width:  2\n//  - Dot Height: 1\n//  - Stagger:    3\n//\n//  Adapted from https://github.com/libretro/glsl-shaders/blob/master/crt/shaders/crt-easymode.glsl\n\nvar<private> vertices: array<vec2<f32>, 3> = array<vec2<f32>, 3>(\n    vec2<f32>(-1.0, -1.0),\n    vec2<f32>(3.0, -1.0),\n    vec2<f32>(-1.0, 3.0),\n);\n\n// Vertex shader\n\nstruct VertexOutput {\n    @builtin(position) position: vec4<f32>,\n    @location(0) tex_dims: vec2<f32>,\n    @location(1) inv_tex_dims: vec2<f32>,\n    @location(2) v_uv: vec2<f32>,\n};\n\n@vertex\nfn vs_main(\n    @builtin(vertex_index) v_idx: u32\n) -> VertexOutput {\n    var out: VertexOutput;\n    let vert = vertices[v_idx];\n\n    // Convert x from -1.0..1.0 to 0.0..1.0 and y from -1.0..1.0 to 1.0..0.0\n    out.position = vec4(vert, 0.0, 1.0);\n    out.tex_dims = vec2<f32>(textureDimensions(tex));\n    out.inv_tex_dims = 1.0 / out.tex_dims;\n    out.v_uv = fma(vert, vec2(0.5, -0.5), vec2(0.5, 0.5));\n    return out;\n}\n\n// Fragment shader\n\nstruct Output {\n    screen_size: vec2<f32>,\n    // Uniform buffers need to be at least 16 bytes in WebGL.\n    // See https://github.com/gfx-rs/wgpu/issues/2072\n    _padding: vec2<f32>,\n}\n@group(0) @binding(0) var<uniform> out: Output;\n\n@group(1) @binding(0) var tex: texture_2d<f32>;\n@group(1) @binding(1) var tex_sampler: sampler;\n\nconst PI = 3.141592653589;\n\nconst SHARPNESS_H = 0.5;\nconst SHARPNESS_V = 1.0;\nconst MASK_STRENGTH = 0.3;\nconst MASK_DOT_WIDTH = 1.0;\nconst MASK_DOT_HEIGHT = 1.0;\nconst MASK_STAGGER = 0.0;\nconst MASK_SIZE = 1.0;\nconst SCANLINE_STRENGTH = 1.0;\nconst SCANLINE_BEAM_WIDTH_MIN = 1.5;\nconst SCANLINE_BEAM_WIDTH_MAX = 1.5;\nconst SCANLINE_BRIGHT_MIN = 0.35;\nconst SCANLINE_BRIGHT_MAX = 0.65;\nconst SCANLINE_CUTOFF = 2000.0;\nconst GAMMA_INPUT = 2.4;\nconst GAMMA_OUTPUT = 2.2;\nconst BRIGHT_BOOST = 1.3;\nconst DILATION = 1.0;\n\n// apply half-circle s-curve to distance for sharper (more pixelated) interpolation\nfn curve_distance(x: f32, sharp: f32) -> f32 {\n    let x_step = step(0.5, x);\n    let curve = 0.5 - sqrt(0.25 - (x - x_step) * (x - x_step)) * sign(0.5 - x);\n\n    return mix(x, curve, sharp);\n}\n\nfn filter_lanczos(coeffs: vec4<f32>, color_matrix: mat4x4<f32>) -> vec3<f32> {\n    var col = color_matrix * coeffs;\n    let sample_min = min(color_matrix[1], color_matrix[2]);\n    let sample_max = max(color_matrix[1], color_matrix[2]);\n\n    col = clamp(col, sample_min, sample_max);\n\n    return col.rgb;\n}\n\nfn dilate(col: vec4<f32>) -> vec4<f32> {\n    let x = mix(vec4<f32>(1.0), col, DILATION);\n\n    return col * x;\n}\n\nfn tex2d(c: vec2<f32>) -> vec4<f32> {\n    return dilate(textureSample(tex, tex_sampler, c));\n}\n\nfn get_color_matrix(co: vec2<f32>, dx: vec2<f32>) -> mat4x4<f32> {\n    return mat4x4<f32>(tex2d(co - dx), tex2d(co), tex2d(co + dx), tex2d(co + 2.0 * dx));\n}\n\nconst NES_HEIGHT = 240.0;\n\n@fragment\nfn fs_main(\n    @location(0) tex_dims: vec2<f32>,\n    @location(1) inv_tex_dims: vec2<f32>,\n    @location(2) v_uv: vec2<f32>\n) -> @location(0) vec4<f32> {\n    let pix_co = v_uv * tex_dims - vec2<f32>(0.5, 0.5);\n    let tex_co = (floor(pix_co) + vec2<f32>(0.5, 0.5)) * inv_tex_dims;\n    let dist = fract(pix_co);\n\n    var curve_x = curve_distance(dist.x, SHARPNESS_H * SHARPNESS_H);\n    var coeffs = PI * vec4<f32>(1.0 + curve_x, curve_x, 1.0 - curve_x, 2.0 - curve_x);\n\n    coeffs = max(abs(coeffs), vec4(1e-5));\n    coeffs = 2.0 * sin(coeffs) * sin(coeffs * 0.5) / (coeffs * coeffs);\n    coeffs /= dot(coeffs, vec4<f32>(1.0));\n\n    let dx = vec2<f32>(inv_tex_dims.x, 0.0);\n    let dy = vec2<f32>(0.0, inv_tex_dims.y);\n    var col = filter_lanczos(coeffs, get_color_matrix(tex_co, dx));\n    var col2 = filter_lanczos(coeffs, get_color_matrix(tex_co + dy, dx));\n\n    col = mix(col, col2, curve_distance(dist.y, SHARPNESS_V));\n    col = pow(col, vec3<f32>(GAMMA_INPUT / (DILATION + 1.0)));\n\n    let luma = dot(vec3<f32>(0.2126, 0.7152, 0.0722), col);\n    let bright = (max(col.r, max(col.g, col.b)) + luma) * 0.5;\n    let scan_bright = clamp(bright, SCANLINE_BRIGHT_MIN, SCANLINE_BRIGHT_MAX);\n    let scan_beam = clamp(bright * SCANLINE_BEAM_WIDTH_MAX, SCANLINE_BEAM_WIDTH_MIN, SCANLINE_BEAM_WIDTH_MAX);\n    var scan_weight = 1.0 - pow(cos(v_uv.y * 2.0 * PI * NES_HEIGHT) * 0.5 + 0.5, scan_beam) * SCANLINE_STRENGTH;\n\n    let mask = 1.0 - MASK_STRENGTH;\n    let mod_fac = floor(v_uv * out.screen_size * tex_dims / (tex_dims * vec2<f32>(MASK_SIZE, MASK_DOT_HEIGHT * MASK_SIZE)));\n    let dot_no = i32(((mod_fac.x + (mod_fac.y % 2.0) * MASK_STAGGER) / MASK_DOT_WIDTH % 3.0));\n\n    var mask_weight: vec3<f32>;\n    if dot_no == 0 {\n        mask_weight = vec3<f32>(1.0, mask, mask);\n    } else if dot_no == 1 {\n        mask_weight = vec3<f32>(mask, 1.0, mask);\n    } else {\n        mask_weight = vec3<f32>(mask, mask, 1.0);\n    }\n\n    if tex_dims.y >= SCANLINE_CUTOFF {\n        scan_weight = 1.0;\n    }\n\n    col2 = col.rgb;\n    col *= vec3<f32>(scan_weight);\n    col = mix(col, col2, scan_bright);\n    col *= mask_weight;\n    col = pow(col, vec3<f32>(1.0 / GAMMA_OUTPUT));\n\n    return vec4<f32>(col * BRIGHT_BOOST, 1.0);\n}\n"
  },
  {
    "path": "tetanes/shaders/gui.wgsl",
    "content": "// Vertex shader\n\nstruct VertexOutput {\n    @builtin(position) position: vec4<f32>,\n    @location(0) v_uv: vec2<f32>,\n    @location(1) v_color: vec4<f32>, // gamma 0-1\n};\n\nstruct Output {\n    screen_size: vec2<f32>,\n    // Uniform buffers need to be at least 16 bytes in WebGL.\n    // See https://github.com/gfx-rs/wgpu/issues/2072\n    _padding: vec2<u32>,\n};\n@group(0) @binding(0) var<uniform> out: Output;\n\n// 0-1 linear  from  0-1 sRGB gamma\nfn linear_from_gamma_rgb(srgb: vec3<f32>) -> vec3<f32> {\n    let cutoff = srgb < vec3<f32>(0.04045);\n    let lower = srgb / vec3<f32>(12.92);\n    let higher = pow((srgb + vec3<f32>(0.055)) / vec3<f32>(1.055), vec3<f32>(2.4));\n    return select(higher, lower, cutoff);\n}\n\n// 0-1 sRGB gamma  from  0-1 linear\nfn gamma_from_linear_rgb(rgb: vec3<f32>) -> vec3<f32> {\n    let cutoff = rgb < vec3<f32>(0.0031308);\n    let lower = rgb * vec3<f32>(12.92);\n    let higher = vec3<f32>(1.055) * pow(rgb, vec3<f32>(1.0 / 2.4)) - vec3<f32>(0.055);\n    return select(higher, lower, cutoff);\n}\n\n// 0-1 sRGBA gamma  from  0-1 linear\nfn gamma_from_linear_rgba(linear_rgba: vec4<f32>) -> vec4<f32> {\n    return vec4<f32>(gamma_from_linear_rgb(linear_rgba.rgb), linear_rgba.a);\n}\n\n// [u8; 4] SRGB as u32 -> [r, g, b, a] in 0.-1\nfn unpack_color(color: u32) -> vec4<f32> {\n    return vec4<f32>(\n        f32(color & 255u),\n        f32((color >> 8u) & 255u),\n        f32((color >> 16u) & 255u),\n        f32((color >> 24u) & 255u),\n    ) / 255.0;\n}\n\nfn position_from_screen(screen_pos: vec2<f32>) -> vec4<f32> {\n    return vec4<f32>(\n        2.0 * screen_pos.x / out.screen_size.x - 1.0,\n        1.0 - 2.0 * screen_pos.y / out.screen_size.y,\n        0.0,\n        1.0,\n    );\n}\n\n@vertex\nfn vs_main(\n    @location(0) v_pos: vec2<f32>,\n    @location(1) v_uv: vec2<f32>,\n    @location(2) v_color: u32,\n) -> VertexOutput {\n    var out: VertexOutput;\n    out.v_uv = v_uv;\n    out.v_color = unpack_color(v_color);\n    out.position = position_from_screen(v_pos);\n    return out;\n}\n\n// Fragment shader\n\n@group(1) @binding(0) var tex: texture_2d<f32>;\n@group(1) @binding(1) var tex_sampler: sampler;\n\n@fragment\nfn fs_main(\n    @location(0) v_uv: vec2<f32>,\n    @location(1) v_color: vec4<f32>\n) -> @location(0) vec4<f32> {\n    let tex = textureSample(tex, tex_sampler, v_uv);\n    let tex_gamma = gamma_from_linear_rgba(tex);\n    return v_color * tex_gamma;\n}\n"
  },
  {
    "path": "tetanes/src/bin/build_artifacts.rs",
    "content": "#![allow(unused)]\n\nuse anyhow::Context;\nuse cfg_if::cfg_if;\nuse clap::Parser;\nuse std::{\n    env,\n    ffi::OsStr,\n    fs,\n    path::{Path, PathBuf},\n    process::{Command, ExitStatus, Output},\n};\n\n/// CLI options\n#[derive(Parser, Debug)]\n#[must_use]\npub struct Args {\n    /// Target platform to build for. e.g. `x86_64-unknown-linux-gnu`.\n    #[clap(long, default_value = env!(\"DEFAULT_TARGET\"))]\n    target: String,\n    /// Build for a target platform different from the host using\n    /// `cross`. e.g. `aarch64-unknown-linux-gnu`.\n    #[clap(long)]\n    cross: bool,\n    /// Clean `dist` directory before building.\n    #[clap(long)]\n    clean: bool,\n}\n\n/// Build context with required variables and platform targets.\n#[derive(Debug)]\n#[must_use]\nstruct Build {\n    version: &'static str,\n    bin_name: &'static str,\n    bin_path: PathBuf,\n    app_name: &'static str,\n    target_arch: String,\n    #[cfg(target_os = \"linux\")]\n    cross: bool,\n    cargo_target_dir: PathBuf,\n    dist_dir: PathBuf,\n}\n\nfn main() -> anyhow::Result<()> {\n    let args = Args::parse();\n    let build = Build::new(args)?;\n\n    println!(\"building artifacts: {build:?}...\");\n\n    if build.target_arch == \"wasm32-unknown-unknown\" {\n        build.make([\"build-web\"])?;\n        build.compress_web_artifacts()?;\n    } else {\n        let build_args = [\"build\", \"--target\", &build.target_arch];\n        cfg_if! {\n            if #[cfg(target_os = \"linux\")] {\n                let build_args = if build.cross {\n                    vec![\"build-cross\"]\n                } else {\n                    build_args.to_vec()\n                };\n                build.make(build_args)?;\n                build.create_linux_artifacts()?;\n            } else if #[cfg(target_os = \"macos\")] {\n                build.make(build_args)?;\n                build.create_macos_app()?;\n            } else if #[cfg(target_os = \"windows\")] {\n                build.create_windows_installer()?;\n            }\n        }\n    }\n\n    Ok(())\n}\n\nimpl Build {\n    /// Create a new build context by cleaning up any previous artifacts and ensuring the\n    /// dist directory is created.\n    fn new(args: Args) -> anyhow::Result<Self> {\n        let bin_name = env!(\"CARGO_PKG_NAME\");\n        let dist_dir = PathBuf::from(bin_name).join(\"dist\");\n\n        if args.clean {\n            let _ = remove_dir_all(&dist_dir); // ignore if not found\n        }\n        create_dir_all(&dist_dir)?;\n\n        let cargo_target_dir =\n            PathBuf::from(env::var(\"CARGO_TARGET_DIR\").unwrap_or_else(|_| \"target\".to_string()));\n        let target_arch = args.target;\n\n        Ok(Build {\n            version: env!(\"CARGO_PKG_VERSION\"),\n            bin_name,\n            bin_path: cargo_target_dir\n                .join(&target_arch)\n                .join(\"release\")\n                .join(bin_name),\n            app_name: \"TetaNES\",\n            target_arch,\n            #[cfg(target_os = \"linux\")]\n            cross: args.cross,\n            cargo_target_dir,\n            dist_dir,\n        })\n    }\n\n    /// Run `cargo make` to build binary.\n    ///\n    /// Note: Wix on Windows bakes in the build step\n    fn make(\n        &self,\n        args: impl IntoIterator<Item = impl AsRef<OsStr>>,\n    ) -> anyhow::Result<ExitStatus> {\n        let mut cmd = Command::new(\"cargo\");\n        cmd.arg(\"make\");\n        for arg in args {\n            cmd.arg(arg);\n        }\n        // TODO: disable lto and make pgo build\n        cmd_spawn_wait(&mut cmd)\n    }\n\n    /// Create a dist directory for artifacts.\n    fn create_build_dir(&self, dir: impl AsRef<Path>) -> anyhow::Result<PathBuf> {\n        let build_dir = self.cargo_target_dir.join(dir);\n\n        println!(\"creating build directory: {build_dir:?}\");\n\n        let _ = remove_dir_all(&build_dir); // ignore if not found\n        create_dir_all(&build_dir)?;\n\n        Ok(build_dir)\n    }\n\n    /// Write out a SHA256 checksum for a file.\n    fn write_sha256(&self, file: impl AsRef<Path>, output: impl AsRef<Path>) -> anyhow::Result<()> {\n        let file = file.as_ref();\n        let output = output.as_ref();\n\n        let shasum = {\n            cfg_if! {\n                if #[cfg(target_os = \"windows\")] {\n                    cmd_output(Command::new(\"powershell\")\n                        .args([\"-Command\", \"$ErrorActionPreference = 'Stop';\"])\n                        .arg(format!(\"Get-FileHash -Algorithm SHA256 {} | select-object -ExpandProperty Hash\", file.display())))?\n                } else {\n                    cmd_output(Command::new(\"shasum\")\n                        .current_dir(file.parent().with_context(|| format!(\"no parent directory for {file:?}\"))?)\n                        .args([\"-a\", \"256\"])\n                        .arg(file.file_name().with_context(|| format!(\"no file_name for {file:?}\"))?))?\n                }\n            }\n        };\n        let sha256 = std::str::from_utf8(&shasum.stdout)\n            .with_context(|| format!(\"invalid sha output for {file:?}\"))?\n            .trim()\n            .to_owned();\n\n        println!(\"sha256: {sha256}\");\n\n        write(output, shasum.stdout)\n    }\n\n    /// Create a Gzipped tarball.\n    fn tar_gz(\n        &self,\n        tgz_name: impl AsRef<str>,\n        directory: impl AsRef<Path>,\n        files: impl IntoIterator<Item = impl AsRef<Path>>,\n    ) -> anyhow::Result<()> {\n        let directory = directory.as_ref();\n        let tgz_name = tgz_name.as_ref();\n        let tgz_path = self.dist_dir.join(tgz_name);\n\n        let mut cmd = Command::new(\"tar\");\n        cmd.arg(\"-czvf\")\n            .arg(&tgz_path)\n            .arg(format!(\"--directory={}\", directory.display()));\n        for file in files {\n            cmd.arg(file.as_ref());\n        }\n\n        cmd_spawn_wait(&mut cmd)?;\n        self.write_sha256(\n            tgz_path,\n            self.dist_dir.join(format!(\"{tgz_name}-sha256.txt\")),\n        )\n    }\n\n    /// Create linux artifacts (.tar.gz, .deb and .AppImage).\n    #[cfg(target_os = \"linux\")]\n    fn create_linux_artifacts(&self) -> anyhow::Result<()> {\n        println!(\"creating linux artifacts...\");\n\n        let build_dir = self.create_build_dir(\"linux\")?;\n\n        // Binary .tar.gz\n        copy(\"README.md\", build_dir.join(\"README.md\"))?;\n        copy(\"LICENSE-MIT\", build_dir.join(\"LICENSE-MIT\"))?;\n        copy(\"LICENSE-APACHE\", build_dir.join(\"LICENSE-APACHE\"))?;\n\n        let bin_path_build = build_dir.join(self.bin_name);\n        copy(&self.bin_path, &bin_path_build)?;\n\n        self.tar_gz(\n            format!(\n                \"{}-{}-{}.tar.gz\",\n                self.bin_name, self.version, self.target_arch\n            ),\n            &build_dir,\n            [\".\"],\n        )?;\n\n        // TODO: Fix deb/AppImage for cross builds\n        if !self.cross {\n            // Debian .deb\n            // NOTE: 1- is the deb revision number\n            let deb_name = format!(\"{}-{}-1-amd64.deb\", self.bin_name, self.version);\n            let deb_path_dist = self.dist_dir.join(&deb_name);\n            cmd_spawn_wait(\n                Command::new(\"cargo\")\n                    .args([\n                        \"deb\",\n                        \"-v\",\n                        \"-p\",\n                        \"tetanes\",\n                        \"--target\",\n                        &self.target_arch,\n                        \"--no-build\", // already built\n                        \"--no-strip\", // already stripped\n                        \"-o\",\n                    ])\n                    .arg(&deb_path_dist),\n            )?;\n\n            self.write_sha256(\n                &deb_path_dist,\n                self.dist_dir.join(format!(\"{deb_name}-sha256.txt\")),\n            )?;\n\n            // AppImage\n            let arch = if self.target_arch.starts_with(\"x86_64\") {\n                \"x86_64\"\n            } else if self.target_arch.starts_with(\"aarch64\") {\n                \"aarch64\"\n            } else {\n                anyhow::bail!(\"invalid linux target_arch: {}\", self.target_arch);\n            };\n            let linuxdeploy_cmd = format!(\"vendored/linuxdeploy-{arch}.AppImage\");\n            let app_dir = build_dir.join(\"AppDir\");\n            let desktop_name = format!(\"assets/linux/{}.desktop\", self.bin_name);\n            cmd_spawn_wait(\n                Command::new(&linuxdeploy_cmd)\n                    .arg(\"-e\")\n                    .arg(&self.bin_path)\n                    .args([\n                        \"-i\",\n                        \"assets/linux/icon.png\",\n                        \"-d\",\n                        &desktop_name,\n                        \"--appdir\",\n                    ])\n                    .arg(&app_dir)\n                    .args([\"--output\", \"appimage\"]),\n            )?;\n\n            // NOTE: AppImage name is derived from tetanes.desktop\n            // Rename to lowercase\n            let app_image_name = format!(\n                \"{}-{}-{}.AppImage\",\n                self.bin_name, self.version, self.target_arch\n            );\n            let app_image_path =\n                PathBuf::from(format!(\"{}-{}.AppImage\", self.app_name, self.target_arch));\n            let app_image_path_dist = self.dist_dir.join(&app_image_name);\n            rename(&app_image_path, &app_image_path_dist)?;\n            self.write_sha256(\n                &app_image_path_dist,\n                self.dist_dir.join(format!(\"{app_image_name}-sha256.txt\")),\n            )?;\n        }\n\n        Ok(())\n    }\n\n    /// Create macOS artifacts (.app in a .tar.gz and separate .dmg).\n    #[cfg(target_os = \"macos\")]\n    fn create_macos_app(&self) -> anyhow::Result<()> {\n        println!(\"creating macos app...\");\n\n        let build_dir = self.create_build_dir(\"macos\")?;\n\n        let artifact_name = format!(\"{}-{}-{}\", self.bin_name, self.version, self.target_arch);\n        let volume = PathBuf::from(\"/Volumes\").join(&artifact_name);\n        let app_name = format!(\"{}.app\", self.app_name);\n        let dmg_name = format!(\"{artifact_name}-uncompressed.dmg\");\n        let dmg_path = build_dir.join(dmg_name);\n        let dmg_name_compressed = format!(\"{artifact_name}.dmg\");\n        let dmg_path_compressed = build_dir.join(&dmg_name_compressed);\n        let dmg_path_dist = self.dist_dir.join(&dmg_name_compressed);\n\n        if let Err(err) = cmd_status(Command::new(\"hdiutil\").arg(\"detach\").arg(&volume)) {\n            eprintln!(\"failed to detach volume: {err:?}\");\n        }\n        cmd_spawn_wait(\n            Command::new(\"hdiutil\")\n                .args([\"create\", \"-size\", \"50m\", \"-volname\", &artifact_name])\n                .arg(&dmg_path),\n        )?;\n        cmd_spawn_wait(Command::new(\"hdiutil\").arg(\"attach\").arg(&dmg_path))?;\n\n        let _ = cmd_status(Command::new(\"mdutil\").args([\"-i\", \"off\"]).arg(&volume));\n\n        let app_dir = volume.join(&app_name);\n        create_dir_all(app_dir.join(\"Contents/MacOS\"))?;\n        create_dir_all(app_dir.join(\"Contents/Resources\"))?;\n        create_dir_all(volume.join(\".Picture\"))?;\n\n        println!(\"updating Info.plist version: {}\", self.version);\n\n        let mut info_plist = read_to_string(\"assets/macos/Info.plist\")?;\n        info_plist = info_plist.replace(\"%VERSION%\", self.version);\n        write(app_dir.join(\"Contents/Info.plist\"), info_plist)?;\n\n        // TODO: maybe include readme/license?\n        copy(\n            \"assets/macos/Icon.icns\",\n            app_dir.join(\"Contents/Resources/Icon.icns\"),\n        )?;\n        copy(\n            \"assets/macos/background.png\",\n            volume.join(\".Picture/background.png\"),\n        )?;\n        copy(\"assets/macos/.DS_Store\", volume.join(\".DS_Store\"))?;\n        copy(\n            &self.bin_path,\n            app_dir.join(\"Contents/MacOS\").join(self.bin_name),\n        )?;\n\n        symlink(\"/Applications\", volume.join(\"Applications\"))?;\n\n        println!(\"configuring app bundle window...\");\n\n        let app_bin_path = app_dir.join(\"Contents/MacOS\").join(self.bin_name);\n        cmd_spawn_wait(\n            Command::new(\"codesign\")\n                .args([\"--force\", \"--sign\", \"-\"])\n                .arg(&app_bin_path),\n        )?;\n        // TODO: fix\n        // ensure spctl --assess --type execute \"${VOLUME}/${APP_NAME}.app\"\n        cmd_spawn_wait(\n            Command::new(\"codesign\")\n                .args([\"--verify\", \"--strict\", \"--verbose=2\"])\n                .arg(&app_bin_path),\n        )?;\n\n        self.tar_gz(\n            format!(\n                \"{}-{}-{}.tar.gz\",\n                self.bin_name, self.version, self.target_arch\n            ),\n            &volume,\n            [&app_name],\n        )?;\n\n        std::thread::sleep(std::time::Duration::from_secs(2));\n\n        if let Err(err) = cmd_spawn_wait(\n            Command::new(\"hdiutil\")\n                .args([\"detach\", \"-force\"])\n                .arg(&volume),\n        ) {\n            eprintln!(\"first detach failed, retrying: {err:?}\");\n            std::thread::sleep(std::time::Duration::from_secs(3));\n            cmd_spawn_wait(\n                Command::new(\"hdiutil\")\n                    .args([\"detach\", \"-force\"])\n                    .arg(&volume),\n            )?;\n        }\n\n        cmd_spawn_wait(\n            Command::new(\"hdiutil\")\n                .args([\"convert\", \"-format\", \"UDBZ\", \"-o\"])\n                .arg(&dmg_path_compressed)\n                .arg(&dmg_path),\n        )?;\n\n        rename(&dmg_path_compressed, &dmg_path_dist)?;\n        self.write_sha256(\n            &dmg_path_dist,\n            self.dist_dir\n                .join(format!(\"{dmg_name_compressed}-sha256.txt\")),\n        )\n    }\n\n    /// Create Windows artifacts (.msi).\n    #[cfg(target_os = \"windows\")]\n    fn create_windows_installer(&self) -> anyhow::Result<()> {\n        println!(\"creating windows installer...\");\n\n        let build_dir = self.create_build_dir(\"windows\")?;\n\n        let artifact_name = format!(\"{}-{}-{}\", self.bin_name, self.version, self.target_arch);\n        let installer_name = format!(\"{artifact_name}.msi\");\n        let installer_path_build = build_dir.join(&installer_name);\n        let zip_name = format!(\"{artifact_name}.zip\");\n        let zip_path_dist = self.dist_dir.join(&zip_name);\n\n        cmd_spawn_wait(\n            Command::new(\"cargo\")\n                .args([\n                    \"wix\",\n                    \"-v\",\n                    \"-p\",\n                    \"tetanes\",\n                    \"--target\",\n                    &self.target_arch,\n                    \"--nocapture\",\n                    \"-o\",\n                ])\n                .arg(&installer_path_build),\n        )?;\n\n        cmd_spawn_wait(Command::new(\"powershell\").args([\n            \"-Command\",\n            \"$ErrorActionPreference = 'Stop';\",\n            \"Compress-Archive\",\n            \"-Force\",\n            \"-Path\",\n            &installer_path_build.to_string_lossy(),\n            \"-DestinationPath\",\n            &zip_path_dist.to_string_lossy(),\n        ]))?;\n\n        self.write_sha256(\n            &zip_path_dist,\n            self.dist_dir.join(format!(\"{zip_name}-sha256.txt\")),\n        )\n    }\n\n    /// Compress web artifacts (.tar.gz).\n    fn compress_web_artifacts(&self) -> anyhow::Result<()> {\n        println!(\"compressing web artifacts...\");\n\n        let build_dir = self.dist_dir.join(\"web\");\n        self.tar_gz(\n            format!(\n                \"{}-{}-{}.tar.gz\",\n                self.bin_name, self.version, self.target_arch\n            ),\n            &build_dir,\n            [\".\"],\n        )?;\n\n        remove_dir_all(&build_dir)\n    }\n}\n\n/// Helper function to `copy` a file and report contextual errors.\nfn copy(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> anyhow::Result<u64> {\n    let src = src.as_ref();\n    let dst = dst.as_ref();\n\n    println!(\"copying: {src:?} to {dst:?}\");\n\n    fs::copy(src, dst).with_context(|| format!(\"failed to copy {src:?} to {dst:?}\"))\n}\n\n/// Helper function to `rename` a file and report contextual errors.\nfn rename(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> anyhow::Result<()> {\n    let src = src.as_ref();\n    let dst = dst.as_ref();\n\n    println!(\"renaming: {src:?} to {dst:?}\");\n\n    fs::rename(src, dst).with_context(|| format!(\"failed to rename {src:?} to {dst:?}\"))\n}\n\n/// Helper function to `create_dir_all` a directory and report contextual errors.\nfn create_dir_all(dir: impl AsRef<Path>) -> anyhow::Result<()> {\n    let dir = dir.as_ref();\n\n    println!(\"creating dir: {dir:?}\");\n\n    fs::create_dir_all(dir).with_context(|| format!(\"failed to create {dir:?}\"))\n}\n\n/// Helper function to `remove_dir_all` a directory and report contextual errors.\nfn remove_dir_all(dir: impl AsRef<Path>) -> anyhow::Result<()> {\n    let dir = dir.as_ref();\n\n    println!(\"removing dir: {dir:?}\");\n\n    fs::remove_dir_all(dir).with_context(|| format!(\"failed to remove {dir:?}\"))\n}\n\n/// Helper function to `write` to a file and report contextual errors.\nfn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> anyhow::Result<()> {\n    let path = path.as_ref();\n\n    println!(\"writing to path: {path:?}\");\n\n    let contents = contents.as_ref();\n    fs::write(path, contents).with_context(|| format!(\"failed to write to {path:?}\"))\n}\n\n/// Helper function to `read_to_string` and report contextual errors.\n#[cfg(target_os = \"macos\")]\nfn read_to_string(path: impl AsRef<Path>) -> anyhow::Result<String> {\n    let path = path.as_ref();\n\n    println!(\"reading to string: {path:?}\");\n\n    fs::read_to_string(path).with_context(|| format!(\"failed to read {path:?}\"))\n}\n\n/// Helper function to `symlink` and report contextual errors.\n#[cfg(unix)]\nfn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> anyhow::Result<()> {\n    use std::os::unix::fs::symlink;\n\n    let src = src.as_ref();\n    let dst = dst.as_ref();\n\n    println!(\"symlinking: {src:?} to {dst:?}\");\n\n    symlink(src, dst).with_context(|| format!(\"failed to symlink {src:?} to {dst:?}\"))\n}\n\n/// Helper function to `spawn` [`Command`] and `wait` while reporting contextual errors.\nfn cmd_spawn_wait(cmd: &mut Command) -> anyhow::Result<ExitStatus> {\n    println!(\"running: {cmd:?}\");\n\n    cmd.spawn()\n        .with_context(|| format!(\"failed to spawn {cmd:?}\"))?\n        .wait()\n        .with_context(|| format!(\"failed to run {cmd:?}\"))\n}\n\n/// Helper function to run [`Command`] with `output` while reporting contextual errors.\nfn cmd_output(cmd: &mut Command) -> anyhow::Result<Output> {\n    println!(\"running: {cmd:?}\");\n\n    cmd.output()\n        .with_context(|| format!(\"failed to run {cmd:?}\"))\n}\n\n/// Helper function to run [`Command`] with `status` while reporting contextual errors.\nfn cmd_status(cmd: &mut Command) -> anyhow::Result<ExitStatus> {\n    println!(\"running: {cmd:?}\");\n\n    cmd.status()\n        .with_context(|| format!(\"failed to run {cmd:?}\"))\n}\n"
  },
  {
    "path": "tetanes/src/error.rs",
    "content": "pub type Error = anyhow::Error;\npub type Result<T, E = Error> = anyhow::Result<T, E>;\n\npub use Error as NesError;\npub use Result as NesResult;\n"
  },
  {
    "path": "tetanes/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(\n    html_favicon_url = \"https://github.com/lukexor/tetanes/blob/main/assets/linux/icon.png?raw=true\",\n    html_logo_url = \"https://github.com/lukexor/tetanes/blob/main/assets/linux/icon.png?raw=true\"\n)]\n#![cfg_attr(docsrs, feature(doc_auto_cfg))]\n\npub mod error;\npub mod logging;\npub mod nes;\npub mod platform;\npub mod sys;\npub mod thread;\n"
  },
  {
    "path": "tetanes/src/logging.rs",
    "content": "use crate::sys::logging;\nuse std::env;\nuse tracing_subscriber::{\n    Registry,\n    filter::Targets,\n    layer::{Layered, SubscriberExt},\n    util::SubscriberInitExt,\n};\n\nfn create_registry() -> Layered<Targets, Registry> {\n    let default_log = if cfg!(debug_assertions) {\n        \"warn,tetanes=debug,tetanes-core=debug\"\n    } else {\n        \"warn,tetanes=info,tetanes-core=info\"\n    };\n    let default_filter = default_log.parse::<Targets>().unwrap_or_default();\n\n    tracing_subscriber::registry().with(\n        env::var(\"RUST_LOG\")\n            .ok()\n            .and_then(|filter| filter.parse::<Targets>().ok())\n            .unwrap_or(default_filter),\n    )\n}\n\n/// Initialize logging.\npub fn init() -> anyhow::Result<logging::Log> {\n    let (registry, log) = logging::init_impl(create_registry())?;\n    if let Err(err) = registry.try_init() {\n        anyhow::bail!(\"setting tracing default failed: {err:?}\");\n    }\n\n    Ok(log)\n}\n"
  },
  {
    "path": "tetanes/src/main.rs",
    "content": "//! A NES Emulator written in Rust with `WebAssembly` support\n//!\n//! USAGE:\n//!     tetanes [FLAGS] [OPTIONS] [path]\n//!\n//! FLAGS:\n//!     -f, --fullscreen    Start fullscreen.\n//!     -h, --help          Prints help information\n//!     -V, --version       Prints version information\n//!\n//! OPTIONS:\n//!     -s, --scale <scale>    Window scale [default: 3.0]\n//!\n//! ARGS:\n//!     <path>    The NES ROM to load, a directory containing `.nes` ROM files, or a recording\n//!               playback `.playback` file. [default: current directory]\n\n#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n\nuse cfg_if::cfg_if;\nuse tetanes::{\n    logging,\n    nes::{Nes, config::Config},\n};\n\n#[cfg(not(target_arch = \"wasm32\"))]\nmod opts;\n\ncfg_if! {\n    if #[cfg(target_arch = \"wasm32\")] {\n        fn load_config() -> anyhow::Result<Config> {\n            Ok(Config::load(None))\n        }\n    } else {\n        fn load_config() -> anyhow::Result<Config> {\n            use clap::Parser;\n\n            let opts = opts::Opts::parse();\n            tracing::debug!(\"CLI Options: {opts:?}\");\n\n            opts.load()\n        }\n    }\n}\n\nfn main() -> anyhow::Result<()> {\n    let log = logging::init();\n    if let Err(err) = log {\n        eprintln!(\"failed to initialize logging: {err:?}\");\n    }\n\n    Nes::run(load_config()?)\n}\n"
  },
  {
    "path": "tetanes/src/nes/action.rs",
    "content": "//! An [`Action`] is an enumerated list of possible state changes to `TetaNES`.\n//!\n//! It allows for event handling and test abstractions such as being able to map a custom keybind\n//! to a given state change.\n\nuse crate::nes::renderer::{gui::Menu, shader::Shader};\nuse serde::{Deserialize, Serialize};\nuse tetanes_core::{\n    action::Action as DeckAction,\n    apu::Channel,\n    common::{NesRegion, ResetKind},\n    input::{FourPlayer, JoypadBtn, Player},\n    mapper::{Bf909Revision, MapperRevision, Mmc3Revision},\n    video::VideoFilter,\n};\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum Action {\n    Ui(Ui),\n    Menu(Menu),\n    Feature(Feature),\n    Setting(Setting),\n    Deck(DeckAction),\n    Debug(Debug),\n}\n\nimpl PartialOrd for Action {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for Action {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        self.as_ref().cmp(other.as_ref())\n    }\n}\n\nimpl Action {\n    pub const BINDABLE: [Self; 112] = [\n        Self::Ui(Ui::Quit),\n        Self::Ui(Ui::TogglePause),\n        Self::Ui(Ui::LoadRom),\n        Self::Ui(Ui::UnloadRom),\n        Self::Ui(Ui::LoadReplay),\n        Self::Menu(Menu::About),\n        Self::Menu(Menu::Keybinds),\n        Self::Menu(Menu::PerfStats),\n        Self::Menu(Menu::Preferences),\n        Self::Feature(Feature::ToggleReplayRecording),\n        Self::Feature(Feature::ToggleAudioRecording),\n        Self::Feature(Feature::VisualRewind),\n        Self::Feature(Feature::InstantRewind),\n        Self::Feature(Feature::TakeScreenshot),\n        Self::Setting(Setting::ToggleFullscreen),\n        Self::Setting(Setting::ToggleEmbedViewports),\n        Self::Setting(Setting::ToggleAlwaysOnTop),\n        Self::Setting(Setting::ToggleAudio),\n        Self::Setting(Setting::ToggleRewinding),\n        Self::Setting(Setting::ToggleOverscan),\n        Self::Setting(Setting::ToggleMenubar),\n        Self::Setting(Setting::ToggleMessages),\n        Self::Setting(Setting::ToggleFps),\n        Self::Setting(Setting::FastForward),\n        Self::Setting(Setting::IncrementScale),\n        Self::Setting(Setting::DecrementScale),\n        Self::Setting(Setting::IncrementSpeed),\n        Self::Setting(Setting::DecrementSpeed),\n        Self::Setting(Setting::SetShader(Shader::Default)),\n        Self::Setting(Setting::SetShader(Shader::CrtEasymode)),\n        Self::Deck(DeckAction::Reset(ResetKind::Soft)),\n        Self::Deck(DeckAction::Reset(ResetKind::Hard)),\n        Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::Left))),\n        Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::Right))),\n        Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::Up))),\n        Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::Down))),\n        Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::A))),\n        Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::B))),\n        Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::TurboA))),\n        Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::TurboB))),\n        Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::Select))),\n        Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::Start))),\n        Self::Deck(DeckAction::Joypad((Player::Two, JoypadBtn::Left))),\n        Self::Deck(DeckAction::Joypad((Player::Two, JoypadBtn::Right))),\n        Self::Deck(DeckAction::Joypad((Player::Two, JoypadBtn::Up))),\n        Self::Deck(DeckAction::Joypad((Player::Two, JoypadBtn::Down))),\n        Self::Deck(DeckAction::Joypad((Player::Two, JoypadBtn::A))),\n        Self::Deck(DeckAction::Joypad((Player::Two, JoypadBtn::B))),\n        Self::Deck(DeckAction::Joypad((Player::Two, JoypadBtn::TurboA))),\n        Self::Deck(DeckAction::Joypad((Player::Two, JoypadBtn::TurboB))),\n        Self::Deck(DeckAction::Joypad((Player::Two, JoypadBtn::Select))),\n        Self::Deck(DeckAction::Joypad((Player::Two, JoypadBtn::Start))),\n        Self::Deck(DeckAction::Joypad((Player::Three, JoypadBtn::Left))),\n        Self::Deck(DeckAction::Joypad((Player::Three, JoypadBtn::Right))),\n        Self::Deck(DeckAction::Joypad((Player::Three, JoypadBtn::Up))),\n        Self::Deck(DeckAction::Joypad((Player::Three, JoypadBtn::Down))),\n        Self::Deck(DeckAction::Joypad((Player::Three, JoypadBtn::A))),\n        Self::Deck(DeckAction::Joypad((Player::Three, JoypadBtn::B))),\n        Self::Deck(DeckAction::Joypad((Player::Three, JoypadBtn::TurboA))),\n        Self::Deck(DeckAction::Joypad((Player::Three, JoypadBtn::TurboB))),\n        Self::Deck(DeckAction::Joypad((Player::Three, JoypadBtn::Select))),\n        Self::Deck(DeckAction::Joypad((Player::Three, JoypadBtn::Start))),\n        Self::Deck(DeckAction::Joypad((Player::Four, JoypadBtn::Left))),\n        Self::Deck(DeckAction::Joypad((Player::Four, JoypadBtn::Right))),\n        Self::Deck(DeckAction::Joypad((Player::Four, JoypadBtn::Up))),\n        Self::Deck(DeckAction::Joypad((Player::Four, JoypadBtn::Down))),\n        Self::Deck(DeckAction::Joypad((Player::Four, JoypadBtn::A))),\n        Self::Deck(DeckAction::Joypad((Player::Four, JoypadBtn::B))),\n        Self::Deck(DeckAction::Joypad((Player::Four, JoypadBtn::TurboA))),\n        Self::Deck(DeckAction::Joypad((Player::Four, JoypadBtn::TurboB))),\n        Self::Deck(DeckAction::Joypad((Player::Four, JoypadBtn::Select))),\n        Self::Deck(DeckAction::Joypad((Player::Four, JoypadBtn::Start))),\n        Self::Deck(DeckAction::ToggleZapperConnected),\n        // Self::Deck(DeckAction::ZapperAim), // Binding doesn't make sense\n        Self::Deck(DeckAction::ZapperTrigger),\n        Self::Deck(DeckAction::FourPlayer(FourPlayer::Disabled)),\n        Self::Deck(DeckAction::FourPlayer(FourPlayer::FourScore)),\n        Self::Deck(DeckAction::FourPlayer(FourPlayer::Satellite)),\n        // Only allow bindings up to 8 slots\n        Self::Deck(DeckAction::SetSaveSlot(1)),\n        Self::Deck(DeckAction::SetSaveSlot(2)),\n        Self::Deck(DeckAction::SetSaveSlot(3)),\n        Self::Deck(DeckAction::SetSaveSlot(4)),\n        Self::Deck(DeckAction::SetSaveSlot(5)),\n        Self::Deck(DeckAction::SetSaveSlot(6)),\n        Self::Deck(DeckAction::SetSaveSlot(7)),\n        Self::Deck(DeckAction::SetSaveSlot(8)),\n        Self::Deck(DeckAction::SaveState),\n        Self::Deck(DeckAction::LoadState),\n        Self::Deck(DeckAction::ToggleApuChannel(Channel::Pulse1)),\n        Self::Deck(DeckAction::ToggleApuChannel(Channel::Pulse2)),\n        Self::Deck(DeckAction::ToggleApuChannel(Channel::Triangle)),\n        Self::Deck(DeckAction::ToggleApuChannel(Channel::Noise)),\n        Self::Deck(DeckAction::ToggleApuChannel(Channel::Dmc)),\n        Self::Deck(DeckAction::ToggleApuChannel(Channel::Mapper)),\n        Self::Deck(DeckAction::MapperRevision(MapperRevision::Mmc3(\n            Mmc3Revision::A,\n        ))),\n        Self::Deck(DeckAction::MapperRevision(MapperRevision::Mmc3(\n            Mmc3Revision::BC,\n        ))),\n        Self::Deck(DeckAction::MapperRevision(MapperRevision::Mmc3(\n            Mmc3Revision::Acc,\n        ))),\n        Self::Deck(DeckAction::MapperRevision(MapperRevision::Bf909(\n            Bf909Revision::Bf909x,\n        ))),\n        Self::Deck(DeckAction::MapperRevision(MapperRevision::Bf909(\n            Bf909Revision::Bf9097,\n        ))),\n        Self::Deck(DeckAction::SetNesRegion(NesRegion::Auto)),\n        Self::Deck(DeckAction::SetNesRegion(NesRegion::Ntsc)),\n        Self::Deck(DeckAction::SetNesRegion(NesRegion::Pal)),\n        Self::Deck(DeckAction::SetNesRegion(NesRegion::Dendy)),\n        Self::Deck(DeckAction::SetVideoFilter(VideoFilter::Pixellate)),\n        Self::Deck(DeckAction::SetVideoFilter(VideoFilter::Ntsc)),\n        Self::Debug(Debug::Toggle(DebugKind::Cpu)),\n        Self::Debug(Debug::Toggle(DebugKind::Ppu)),\n        Self::Debug(Debug::Toggle(DebugKind::Apu)),\n        Self::Debug(Debug::Step(DebugStep::Into)),\n        Self::Debug(Debug::Step(DebugStep::Out)),\n        Self::Debug(Debug::Step(DebugStep::Over)),\n        Self::Debug(Debug::Step(DebugStep::Scanline)),\n        Self::Debug(Debug::Step(DebugStep::Frame)),\n    ];\n\n    pub const fn is_joypad(&self) -> bool {\n        matches!(self, Action::Deck(DeckAction::Joypad(_)))\n    }\n\n    pub fn joypad_player(&self, player: Player) -> bool {\n        matches!(self, Action::Deck(DeckAction::Joypad((p, _))) if p == &player)\n    }\n}\n\nimpl std::fmt::Display for Action {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.as_ref())\n    }\n}\n\nimpl AsRef<str> for Action {\n    fn as_ref(&self) -> &str {\n        match self {\n            Action::Ui(ui) => match ui {\n                Ui::Quit => \"Quit\",\n                Ui::TogglePause => \"Toggle Pause\",\n                Ui::LoadRom => \"Load ROM\",\n                Ui::UnloadRom => \"Unload ROM\",\n                Ui::LoadReplay => \"Load Replay\",\n            },\n            Action::Menu(menu) => match menu {\n                Menu::About => \"Toggle About\",\n                Menu::Keybinds => \"Toggle Keybinds\",\n                Menu::PerfStats => \"Toggle Performance Stats\",\n                Menu::PpuViewer => \"Toggle PPU Viewer\",\n                Menu::Preferences => \"Toggle Preferences\",\n            },\n            Action::Feature(feature) => match feature {\n                Feature::ToggleReplayRecording => \"Toggle Replay Recording\",\n                Feature::ToggleAudioRecording => \"Toggle Audio Recording\",\n                Feature::VisualRewind => \"Visual Rewind\",\n                Feature::InstantRewind => \"Instant Rewind\",\n                Feature::TakeScreenshot => \"Take Screenshot\",\n            },\n            Action::Setting(setting) => match setting {\n                Setting::ToggleFullscreen => \"Toggle Fullscreen\",\n                Setting::ToggleEmbedViewports => \"Toggle Embed Viewports\",\n                Setting::ToggleAlwaysOnTop => \"Toggle Always On Top\",\n                Setting::ToggleAudio => \"Toggle Audio\",\n                Setting::ToggleRewinding => \"Toggle Rewinding\",\n                Setting::ToggleOverscan => \"Toggle Overscan\",\n                Setting::ToggleMenubar => \"Toggle Menubar\",\n                Setting::ToggleMessages => \"Toggle Messages\",\n                Setting::ToggleScreenReader => \"Toggle Screen Reader\",\n                Setting::ToggleFps => \"Toggle FPS\",\n                Setting::FastForward => \"Fast Forward\",\n                Setting::IncrementScale => \"Increment Scale\",\n                Setting::DecrementScale => \"Decrement Scale\",\n                Setting::IncrementSpeed => \"Increment Speed\",\n                Setting::DecrementSpeed => \"Decrement Speed\",\n                Setting::SetShader(shader) => match shader {\n                    Shader::Default => \"Set Default Shader\",\n                    Shader::CrtEasymode => \"Set Shader to CRT Easymode\",\n                },\n            },\n            Action::Deck(deck) => match deck {\n                DeckAction::Reset(kind) => match kind {\n                    ResetKind::Soft => \"Reset\",\n                    ResetKind::Hard => \"Power Cycle\",\n                },\n                DeckAction::Joypad((_, joypad)) => match joypad {\n                    JoypadBtn::Left => \"Joypad Left\",\n                    JoypadBtn::Right => \"Joypad Right\",\n                    JoypadBtn::Up => \"Joypad Up\",\n                    JoypadBtn::Down => \"Joypad Down\",\n                    JoypadBtn::A => \"Joypad A\",\n                    JoypadBtn::B => \"Joypad B\",\n                    JoypadBtn::TurboA => \"Joypad Turbo A\",\n                    JoypadBtn::TurboB => \"Joypad Turbo B\",\n                    JoypadBtn::Select => \"Joypad Select\",\n                    JoypadBtn::Start => \"Joypad Start\",\n                },\n                DeckAction::ToggleZapperConnected => \"Zapper Gun Toggle\",\n                DeckAction::ZapperAim(_) => \"Zapper Aim\",\n                DeckAction::ZapperAimOffscreen => \"Zapper Aim Offscreen (Hold)\",\n                DeckAction::ZapperTrigger => \"Zapper Trigger\",\n                DeckAction::FourPlayer(FourPlayer::Disabled) => \"4-Player Disable\",\n                DeckAction::FourPlayer(FourPlayer::FourScore) => \"4-Player Enable (FourScore)\",\n                DeckAction::FourPlayer(FourPlayer::Satellite) => \"4-Player Enable (Satellite)\",\n                DeckAction::SetSaveSlot(1) => \"Set Save Slot 1\",\n                DeckAction::SetSaveSlot(2) => \"Set Save Slot 2\",\n                DeckAction::SetSaveSlot(3) => \"Set Save Slot 3\",\n                DeckAction::SetSaveSlot(4) => \"Set Save Slot 4\",\n                DeckAction::SetSaveSlot(5) => \"Set Save Slot 5\",\n                DeckAction::SetSaveSlot(6) => \"Set Save Slot 6\",\n                DeckAction::SetSaveSlot(7) => \"Set Save Slot 7\",\n                DeckAction::SetSaveSlot(8) => \"Set Save Slot 8\",\n                DeckAction::SetSaveSlot(_) => \"Set Save Slot N\",\n                DeckAction::SaveState => \"Save State\",\n                DeckAction::LoadState => \"Load State\",\n                DeckAction::ToggleApuChannel(channel) => match channel {\n                    Channel::Pulse1 => \"Toggle Pulse1 Channel\",\n                    Channel::Pulse2 => \"Toggle Pulse2 Channel\",\n                    Channel::Triangle => \"Toggle Triangle Channel\",\n                    Channel::Noise => \"Toggle Noise Channel\",\n                    Channel::Dmc => \"Toggle DMC Channel\",\n                    Channel::Mapper => \"Toggle Mapper Channel\",\n                },\n                DeckAction::MapperRevision(rev) => match rev {\n                    MapperRevision::Mmc3(mmc3) => match mmc3 {\n                        Mmc3Revision::A => \"Set Mapper to MMC3A\",\n                        Mmc3Revision::BC => \"Set Mapper to MMC3B/C\",\n                        Mmc3Revision::Acc => \"Set Mapper to MC-ACC\",\n                    },\n                    MapperRevision::Bf909(bf909) => match bf909 {\n                        Bf909Revision::Bf909x => \"Set Mapper to BF909x\",\n                        Bf909Revision::Bf9097 => \"Set Mapper to BF9097\",\n                    },\n                },\n                DeckAction::SetNesRegion(region) => match region {\n                    NesRegion::Auto => \"Set Region to Auto\",\n                    NesRegion::Ntsc => \"Set Region to NTSC\",\n                    NesRegion::Pal => \"Set Region to PAL\",\n                    NesRegion::Dendy => \"Set Region to Dendy\",\n                },\n                DeckAction::SetVideoFilter(filter) => match filter {\n                    VideoFilter::Pixellate => \"Set Filter to Pixellate\",\n                    VideoFilter::Ntsc => \"Set Filter to NTSC\",\n                },\n            },\n            Action::Debug(debug) => match debug {\n                Debug::Toggle(debugger) => match debugger {\n                    DebugKind::Cpu => \"Toggle Debugger\",\n                    DebugKind::Ppu => \"Toggle PPU Viewer\",\n                    DebugKind::Apu => \"Toggle APU Mixer\",\n                },\n                Debug::Step(step) => match step {\n                    DebugStep::Into => \"Debug Step\",\n                    DebugStep::Out => \"Debug Step Out\",\n                    DebugStep::Over => \"Debug Step Over\",\n                    DebugStep::Scanline => \"Debug Step Scanline\",\n                    DebugStep::Frame => \"Debug Step Frame\",\n                },\n            },\n        }\n    }\n}\n\nimpl TryFrom<&str> for Action {\n    type Error = anyhow::Error;\n\n    fn try_from(s: &str) -> Result<Self, Self::Error> {\n        Ok(match s {\n            \"Quit\" => Self::Ui(Ui::Quit),\n            \"Toggle Pause\" => Self::Ui(Ui::TogglePause),\n            \"Load ROM\" => Self::Ui(Ui::LoadRom),\n            \"Unload ROM\" => Self::Ui(Ui::UnloadRom),\n            \"Load Replay\" => Self::Ui(Ui::LoadReplay),\n            \"Toggle About Window\" => Self::Menu(Menu::About),\n            \"Toggle Keybinds Menu\" => Self::Menu(Menu::Keybinds),\n            \"Toggle Performance Stats Window\" => Self::Menu(Menu::PerfStats),\n            \"Toggle PPU Viewer\" => Self::Menu(Menu::PpuViewer),\n            \"Toggle Preferences Menu\" => Self::Menu(Menu::Preferences),\n            \"Toggle Replay Recording\" => Self::Feature(Feature::ToggleReplayRecording),\n            \"Toggle Audio Recording\" => Self::Feature(Feature::ToggleAudioRecording),\n            \"Visual Rewind\" => Self::Feature(Feature::VisualRewind),\n            \"Instant Rewind\" => Self::Feature(Feature::InstantRewind),\n            \"Take Screenshot\" => Self::Feature(Feature::TakeScreenshot),\n            \"Toggle Fullscreen\" => Self::Setting(Setting::ToggleFullscreen),\n            \"Toggle Embed Viewports\" => Self::Setting(Setting::ToggleEmbedViewports),\n            \"Toggle Always On Top\" => Self::Setting(Setting::ToggleAlwaysOnTop),\n            \"Toggle Audio\" => Self::Setting(Setting::ToggleAudio),\n            \"Toggle Rewinding\" => Self::Setting(Setting::ToggleRewinding),\n            \"Toggle Overscan\" => Self::Setting(Setting::ToggleOverscan),\n            \"Toggle Menubar\" => Self::Setting(Setting::ToggleMenubar),\n            \"Toggle Messages\" => Self::Setting(Setting::ToggleMessages),\n            \"Toggle FPS\" => Self::Setting(Setting::ToggleFps),\n            \"Fast Forward\" => Self::Setting(Setting::FastForward),\n            \"Increment Scale\" => Self::Setting(Setting::IncrementScale),\n            \"Decrement Scale\" => Self::Setting(Setting::DecrementScale),\n            \"Increment Speed\" => Self::Setting(Setting::IncrementSpeed),\n            \"Decrement Speed\" => Self::Setting(Setting::DecrementSpeed),\n            \"Set Default Shader\" => Self::Setting(Setting::SetShader(Shader::Default)),\n            \"Set Shader to CRT Easymode\" => Self::Setting(Setting::SetShader(Shader::CrtEasymode)),\n            \"Reset\" => Self::Deck(DeckAction::Reset(ResetKind::Soft)),\n            \"Power Cycle\" => Self::Deck(DeckAction::Reset(ResetKind::Hard)),\n            \"Joypad Left (P1)\" => Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::Left))),\n            \"Joypad Right (P1)\" => Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::Right))),\n            \"Joypad Up (P1)\" => Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::Up))),\n            \"Joypad Down (P1)\" => Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::Down))),\n            \"Joypad A (P1)\" => Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::A))),\n            \"Joypad B (P1)\" => Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::B))),\n            \"Joypad Turbo A (P1)\" => {\n                Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::TurboA)))\n            }\n            \"Joypad Turbo B (P1)\" => {\n                Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::TurboB)))\n            }\n            \"Joypad Select (P1)\" => {\n                Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::Select)))\n            }\n            \"Joypad Start (P1)\" => Self::Deck(DeckAction::Joypad((Player::One, JoypadBtn::Start))),\n            \"Toggle Zapper Connected\" => Self::Deck(DeckAction::ToggleZapperConnected),\n            \"Zapper Aim\" => Self::Deck(DeckAction::ZapperAim((0, 0))),\n            \"Zapper Aim Offscreen (Hold)\" => Self::Deck(DeckAction::ZapperAimOffscreen),\n            \"Zapper Trigger\" => Self::Deck(DeckAction::ZapperTrigger),\n            \"Disable Four Player Mode\" => Self::Deck(DeckAction::FourPlayer(FourPlayer::Disabled)),\n            \"Enable Four Player (FourScore)\" => {\n                Self::Deck(DeckAction::FourPlayer(FourPlayer::FourScore))\n            }\n            \"Enable Four Player (Satellite)\" => {\n                Self::Deck(DeckAction::FourPlayer(FourPlayer::Satellite))\n            }\n            \"Set Save Slot 1\" => Self::Deck(DeckAction::SetSaveSlot(1)),\n            \"Set Save Slot 2\" => Self::Deck(DeckAction::SetSaveSlot(2)),\n            \"Set Save Slot 3\" => Self::Deck(DeckAction::SetSaveSlot(3)),\n            \"Set Save Slot 4\" => Self::Deck(DeckAction::SetSaveSlot(4)),\n            \"Set Save Slot 5\" => Self::Deck(DeckAction::SetSaveSlot(5)),\n            \"Set Save Slot 6\" => Self::Deck(DeckAction::SetSaveSlot(6)),\n            \"Set Save Slot 7\" => Self::Deck(DeckAction::SetSaveSlot(7)),\n            \"Set Save Slot 8\" => Self::Deck(DeckAction::SetSaveSlot(8)),\n            \"Save State\" => Self::Deck(DeckAction::SaveState),\n            \"Load State\" => Self::Deck(DeckAction::LoadState),\n            \"Toggle Pulse1 Channel\" => Self::Deck(DeckAction::ToggleApuChannel(Channel::Pulse1)),\n            \"Toggle Pulse2 Channel\" => Self::Deck(DeckAction::ToggleApuChannel(Channel::Pulse2)),\n            \"Toggle Triangle Channel\" => {\n                Self::Deck(DeckAction::ToggleApuChannel(Channel::Triangle))\n            }\n            \"Toggle Noise Channel\" => Self::Deck(DeckAction::ToggleApuChannel(Channel::Noise)),\n            \"Toggle DMC Channel\" => Self::Deck(DeckAction::ToggleApuChannel(Channel::Dmc)),\n            \"Toggle Mapper Channel\" => Self::Deck(DeckAction::ToggleApuChannel(Channel::Mapper)),\n            \"Set Mapper Rev. to MMC3A\" => Self::Deck(DeckAction::MapperRevision(\n                MapperRevision::Mmc3(Mmc3Revision::A),\n            )),\n            \"Set Mapper Rev. to MMC3B/C\" => Self::Deck(DeckAction::MapperRevision(\n                MapperRevision::Mmc3(Mmc3Revision::BC),\n            )),\n            \"Set Mapper Rev. to MC-ACC\" => Self::Deck(DeckAction::MapperRevision(\n                MapperRevision::Mmc3(Mmc3Revision::Acc),\n            )),\n            \"Set Mapper Rev. to BF909x\" => Self::Deck(DeckAction::MapperRevision(\n                MapperRevision::Bf909(Bf909Revision::Bf909x),\n            )),\n            \"Set Mapper Rev. to BF9097\" => Self::Deck(DeckAction::MapperRevision(\n                MapperRevision::Bf909(Bf909Revision::Bf9097),\n            )),\n            \"Set Region to Auto-Detect\" => Self::Deck(DeckAction::SetNesRegion(NesRegion::Auto)),\n            \"Set Region to NTSC\" => Self::Deck(DeckAction::SetNesRegion(NesRegion::Ntsc)),\n            \"Set Region to PAL\" => Self::Deck(DeckAction::SetNesRegion(NesRegion::Pal)),\n            \"Set Region to Dendy\" => Self::Deck(DeckAction::SetNesRegion(NesRegion::Dendy)),\n            \"Set Filter to Pixellate\" => {\n                Self::Deck(DeckAction::SetVideoFilter(VideoFilter::Pixellate))\n            }\n            \"Set Filter to NTSC\" => Self::Deck(DeckAction::SetVideoFilter(VideoFilter::Ntsc)),\n            \"Toggle CPU Debugger\" => Self::Debug(Debug::Toggle(DebugKind::Cpu)),\n            \"Toggle PPU Debugger\" => Self::Debug(Debug::Toggle(DebugKind::Ppu)),\n            \"Toggle APU Debugger\" => Self::Debug(Debug::Toggle(DebugKind::Apu)),\n            \"Step Into (CPU Debugger)\" => Self::Debug(Debug::Step(DebugStep::Into)),\n            \"Step Out (CPU Debugger)\" => Self::Debug(Debug::Step(DebugStep::Out)),\n            \"Step Over (CPU Debugger)\" => Self::Debug(Debug::Step(DebugStep::Over)),\n            \"Step Scanline (CPU Debugger)\" => Self::Debug(Debug::Step(DebugStep::Scanline)),\n            \"Step Frame (CPU Debugger)\" => Self::Debug(Debug::Step(DebugStep::Frame)),\n            _ => return Err(anyhow::anyhow!(\"Invalid action string\")),\n        })\n    }\n}\n\nimpl From<Ui> for Action {\n    fn from(state: Ui) -> Self {\n        Self::Ui(state)\n    }\n}\n\nimpl From<Menu> for Action {\n    fn from(menu: Menu) -> Self {\n        Self::Menu(menu)\n    }\n}\n\nimpl From<Feature> for Action {\n    fn from(feature: Feature) -> Self {\n        Self::Feature(feature)\n    }\n}\n\nimpl From<Setting> for Action {\n    fn from(setting: Setting) -> Self {\n        Self::Setting(setting)\n    }\n}\n\nimpl From<(Player, JoypadBtn)> for Action {\n    fn from((player, btn): (Player, JoypadBtn)) -> Self {\n        Self::Deck(DeckAction::Joypad((player, btn)))\n    }\n}\n\nimpl From<DeckAction> for Action {\n    fn from(deck: DeckAction) -> Self {\n        Self::Deck(deck)\n    }\n}\n\nimpl From<Debug> for Action {\n    fn from(action: Debug) -> Self {\n        Self::Debug(action)\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum Ui {\n    Quit,\n    TogglePause,\n    LoadRom,\n    LoadReplay,\n    UnloadRom,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum Feature {\n    ToggleReplayRecording,\n    ToggleAudioRecording,\n    VisualRewind,\n    InstantRewind,\n    TakeScreenshot,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum Setting {\n    ToggleFullscreen,\n    ToggleEmbedViewports,\n    ToggleAlwaysOnTop,\n    ToggleAudio,\n    ToggleRewinding,\n    ToggleOverscan,\n    ToggleMenubar,\n    ToggleMessages,\n    ToggleScreenReader,\n    ToggleFps,\n    FastForward,\n    IncrementScale,\n    DecrementScale,\n    IncrementSpeed,\n    DecrementSpeed,\n    SetShader(Shader),\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum DebugKind {\n    Cpu,\n    Ppu,\n    Apu,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum DebugStep {\n    Into,\n    Out,\n    Over,\n    Scanline,\n    Frame,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum Debug {\n    Toggle(DebugKind),\n    Step(DebugStep),\n}\n"
  },
  {
    "path": "tetanes/src/nes/audio.rs",
    "content": "use crate::nes::config::Config;\nuse anyhow::{Context, anyhow};\nuse cpal::traits::{DeviceTrait, HostTrait, StreamTrait};\nuse ringbuf::{\n    CachingCons, CachingProd, HeapRb,\n    producer::Producer,\n    traits::{Consumer, Observer, Split},\n};\nuse std::{fs::File, io::BufWriter, iter, path::PathBuf, sync::Arc};\nuse tetanes_core::time::Duration;\nuse tracing::{debug, error, info, trace, warn};\n\ntype SampleRb = Arc<HeapRb<f32>>;\ntype SampleProducer = CachingProd<SampleRb>;\ntype SampleConsumer = CachingCons<SampleRb>;\n\n/// Represents the state of the audio stream.\n#[derive(Debug)]\n#[must_use]\npub enum State {\n    /// Audio is disabled.\n    Disabled,\n    /// No audio output device was found or no devices found to support desired configuration.\n    NoOutputDevice,\n    /// Audio output stream has been started.\n    Started,\n    /// Audio output stream has been stopped.\n    Stopped,\n}\n\n#[derive(Debug)]\n#[must_use]\npub enum CallbackMsg {\n    NewSamples,\n    UpdateResampleRatio(f32),\n    Enable(bool),\n    Record(bool),\n}\n\n#[must_use]\npub struct Audio {\n    pub enabled: bool,\n    pub sample_rate: f32,\n    pub latency: Duration,\n    pub buffer_size: usize,\n    pub host: cpal::Host,\n    output: Option<Output>,\n}\n\nimpl std::fmt::Debug for Audio {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Audio\")\n            .field(\"enabled\", &self.enabled)\n            .field(\"sample_rate\", &self.sample_rate)\n            .field(\"latency\", &self.latency)\n            .field(\"buffer_size\", &self.buffer_size)\n            .field(\"output\", &self.output)\n            .finish_non_exhaustive()\n    }\n}\n\nimpl Audio {\n    /// Creates a new audio mixer.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if the audio device fails to be opened.\n    pub fn new(enabled: bool, mut sample_rate: f32, latency: Duration, buffer_size: usize) -> Self {\n        let host = cpal::default_host();\n        let output = Output::create(&host, sample_rate, latency, buffer_size);\n        if let Some(output) = &output {\n            let desired_sample_rate = sample_rate as u32;\n            if output.config.sample_rate != desired_sample_rate {\n                sample_rate = output.config.sample_rate as f32;\n                debug!(\n                    \"Unable to match desired sample_rate: {desired_sample_rate}. Using {sample_rate} instead\",\n                );\n            }\n        }\n        Self {\n            enabled,\n            sample_rate,\n            latency,\n            buffer_size,\n            host,\n            output,\n        }\n    }\n\n    /// Whether the audio mixer is currently enabled.\n    pub fn enabled(&self) -> bool {\n        self.enabled\n            && self\n                .output\n                .as_ref()\n                .and_then(|output| output.mixer.as_ref())\n                .is_some_and(|mixer| !mixer.paused)\n    }\n\n    /// Returns the current audio device, if any.\n    pub fn device(&self) -> Option<&cpal::Device> {\n        self.output.as_ref().map(|output| &output.device)\n    }\n\n    /// Set whether the audio mixer is enabled. Returns [`State`] representing the state of\n    /// the audio stream as a result of being enabled/disabled.\n    pub fn set_enabled(&mut self, enabled: bool) -> anyhow::Result<State> {\n        self.enabled = enabled;\n        if self.enabled {\n            self.start()\n        } else {\n            Ok(self.stop())\n        }\n    }\n\n    /// Processes generated audio samples.\n    pub fn process(&mut self, samples: &[f32]) {\n        if let Some(mixer) = &mut self\n            .output\n            .as_mut()\n            .and_then(|output| output.mixer.as_mut())\n        {\n            mixer.process(samples);\n        }\n    }\n\n    /// Returns the number of audio channels.\n    #[must_use]\n    pub fn channels(&self) -> u16 {\n        self.output\n            .as_ref()\n            .map_or(0, |output| output.config.channels)\n    }\n\n    /// Returns the `Duration` of audio queued for playback.\n    #[must_use]\n    pub fn queued_time(&self) -> Duration {\n        self.output\n            .as_ref()\n            .and_then(|output| output.mixer.as_ref())\n            .map_or(Duration::default(), |mixer| {\n                let queued_seconds =\n                    mixer.producer.occupied_len() as f32 / self.sample_rate / mixer.channels as f32;\n                Duration::from_secs_f32(queued_seconds)\n            })\n    }\n\n    /// Pause or resume the audio output stream. If `paused` is false and the stream is not started\n    /// yet, it will be started.\n    pub fn pause(&mut self, paused: bool) {\n        if let Some(mixer) = &mut self\n            .output\n            .as_mut()\n            .and_then(|output| output.mixer.as_mut())\n        {\n            mixer.pause(paused);\n        }\n    }\n\n    /// Recreate audio output device.\n    fn recreate_output(&mut self) -> anyhow::Result<State> {\n        let _ = self.stop();\n        self.output = Output::create(&self.host, self.sample_rate, self.latency, self.buffer_size);\n        self.start()\n    }\n\n    /// Set the output sample rate that the audio device uses. Requires restarting the audio stream\n    /// and so may fail.\n    pub fn set_sample_rate(&mut self, sample_rate: f32) -> anyhow::Result<State> {\n        self.sample_rate = sample_rate;\n        self.recreate_output()\n    }\n\n    /// Set the buffer size used by the audio device for playback. Requires restarting the audio\n    /// stream and so may fail.\n    pub fn set_buffer_size(&mut self, buffer_size: usize) -> anyhow::Result<State> {\n        self.buffer_size = buffer_size;\n        self.recreate_output()\n    }\n\n    /// Set the latency used by the audio device for playback. Requires restarting the audio\n    /// stream and so may fail.\n    pub fn set_latency(&mut self, latency: Duration) -> anyhow::Result<State> {\n        self.latency = latency;\n        self.recreate_output()\n    }\n\n    /// Whether the mixer is currently recording samples to a file.\n    pub fn is_recording(&self) -> bool {\n        self.output\n            .as_ref()\n            .and_then(|output| output.mixer.as_ref())\n            .is_some_and(|mixer| mixer.recording.is_some())\n    }\n\n    /// Start recording audio to a file.\n    pub fn start_recording(&mut self) -> anyhow::Result<()> {\n        if let Some(mixer) = &mut self\n            .output\n            .as_mut()\n            .and_then(|output| output.mixer.as_mut())\n        {\n            mixer.start_recording()\n        } else {\n            Ok(())\n        }\n    }\n\n    /// Stop recording audio to a file.\n    pub fn stop_recording(&mut self) -> anyhow::Result<Option<PathBuf>> {\n        self.output\n            .as_mut()\n            .and_then(|output| output.mixer.as_mut())\n            .map_or(Ok(None), |mixer| mixer.stop_recording())\n    }\n\n    /// Start the audio output stream. Returns [`State`] representing the state of the audio stream.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if the audio stream could not be started.\n    pub fn start(&mut self) -> anyhow::Result<State> {\n        if self.enabled {\n            if let Some(output) = &mut self.output {\n                output.start()?;\n                Ok(State::Started)\n            } else {\n                Ok(State::NoOutputDevice)\n            }\n        } else {\n            Ok(State::Disabled)\n        }\n    }\n\n    /// Stop the audio output stream.\n    pub fn stop(&mut self) -> State {\n        if let Some(output) = &mut self.output {\n            output.stop();\n            State::Stopped\n        } else {\n            State::NoOutputDevice\n        }\n    }\n\n    /// Returns a list of available hosts for the current platform.\n    pub fn available_hosts(&self) -> Vec<cpal::HostId> {\n        cpal::available_hosts()\n    }\n\n    /// Returns an iterator over the audio devices available to the host on the system. If no\n    /// devices are available, `None` is returned.\n    ///\n    /// # Errors\n    ///\n    /// If the device is no longer valid (i.e. has been disconnected), an error is returned.\n    pub fn available_devices(&self) -> anyhow::Result<cpal::Devices> {\n        Ok(self.host.devices()?)\n    }\n\n    /// Return an iterator over supported device configurations. If no devices are available, `None` is\n    /// returned.\n    ///\n    /// # Errors\n    ///\n    /// If the device is no longer valid (i.e. has been disconnected), an error is returned.\n    pub fn supported_configs(&self) -> Option<anyhow::Result<cpal::SupportedOutputConfigs>> {\n        self.output.as_ref().map(|output| {\n            output\n                .device\n                .supported_output_configs()\n                .context(\"failed to get supported configurations\")\n        })\n    }\n}\n\n#[must_use]\nstruct Output {\n    device: cpal::Device,\n    config: cpal::StreamConfig,\n    sample_format: cpal::SampleFormat,\n    latency: Duration,\n    mixer: Option<Mixer>,\n}\n\nimpl std::fmt::Debug for Output {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Audio\")\n            .field(\"config\", &self.config)\n            .field(\"sample_format\", &self.sample_format)\n            .field(\"mixer\", &self.mixer)\n            .finish_non_exhaustive()\n    }\n}\n\nimpl Output {\n    fn create(\n        host: &cpal::Host,\n        sample_rate: f32,\n        latency: Duration,\n        buffer_size: usize,\n    ) -> Option<Self> {\n        let Some(device) = host.default_output_device() else {\n            warn!(\"no available audio devices found\");\n            return None;\n        };\n        debug!(\n            \"device name: {}\",\n            device\n                .description()\n                .as_ref()\n                .map(|desc| desc.name())\n                .unwrap_or(\"unknown\")\n        );\n        let (config, sample_format) = match Self::choose_config(&device, sample_rate, buffer_size) {\n            Ok(config) => config,\n            Err(err) => {\n                warn!(\"failed to find a matching device configuration: {err:?}\");\n                return None;\n            }\n        };\n        Some(Self {\n            device,\n            config,\n            sample_format,\n            latency,\n            mixer: None,\n        })\n    }\n\n    /// Choose the best audio configuration for the given device and sample_rate.\n    fn choose_config(\n        device: &cpal::Device,\n        sample_rate: f32,\n        buffer_size: usize,\n    ) -> anyhow::Result<(cpal::StreamConfig, cpal::SampleFormat)> {\n        let mut supported_configs = device.supported_output_configs()?;\n        let desired_sample_rate = sample_rate as u32;\n        let desired_buffer_size = buffer_size as u32;\n        debug!(\"desired: sample rate: {desired_sample_rate}, buffer_size: {buffer_size}\");\n\n        let chosen_config = supported_configs\n            .find(|config| {\n                let supports_sample_rate = config.max_sample_rate() >= desired_sample_rate\n                    && config.min_sample_rate() <= desired_sample_rate;\n                let supports_sample_format = config.sample_format() == cpal::SampleFormat::F32;\n                let supports_buffer_size = match config.buffer_size() {\n                    cpal::SupportedBufferSize::Range { min, max } => {\n                        (*min..=*max).contains(&desired_buffer_size)\n                    }\n                    cpal::SupportedBufferSize::Unknown => false,\n                };\n                let supported =\n                    supports_sample_rate && supports_sample_format && supports_buffer_size;\n                if supported {\n                    debug!(\"supported config: {config:?}\",);\n                } else {\n                    trace!(\"unsupported config: {config:?}\",);\n                }\n                supported\n            })\n            .or_else(|| {\n                let config = device\n                    .supported_output_configs()\n                    .ok()\n                    .and_then(|mut c| c.next());\n                debug!(\"falling back to first supported config: {config:?}\");\n                config\n            })\n            .map(|config| {\n                debug!(\"chosen config: {config:?}\");\n                let min_sample_rate = config.min_sample_rate();\n                let max_sample_rate = config.max_sample_rate();\n                config.with_sample_rate(desired_sample_rate.clamp(min_sample_rate, max_sample_rate))\n            })\n            .ok_or_else(|| anyhow!(\"no supported audio configurations found\"))?;\n        let sample_format = chosen_config.sample_format();\n        let buffer_size = match chosen_config.buffer_size() {\n            cpal::SupportedBufferSize::Range { min, max } => {\n                desired_buffer_size.min(*max).max(*min)\n            }\n            cpal::SupportedBufferSize::Unknown => desired_buffer_size,\n        };\n        let mut config = cpal::StreamConfig::from(chosen_config);\n        config.buffer_size = cpal::BufferSize::Fixed(buffer_size);\n        Ok((config, sample_format))\n    }\n\n    fn start(&mut self) -> anyhow::Result<()> {\n        if let Some(ref mixer) = self.mixer {\n            mixer.stream.play()?;\n            return Ok(());\n        }\n\n        info!(\"starting audio stream with config: {:?}\", self.config);\n        self.mixer = Some(Mixer::start(\n            &self.device,\n            &self.config,\n            self.latency,\n            self.sample_format,\n        )?);\n        Ok(())\n    }\n\n    fn stop(&mut self) {\n        if let Some(mut mixer) = self.mixer.take() {\n            mixer.pause(true);\n        }\n    }\n}\n\n#[must_use]\npub(crate) struct Mixer {\n    stream: cpal::Stream,\n    paused: bool,\n    channels: u16,\n    sample_rate: u32,\n    sample_latency: usize,\n    producer: SampleProducer,\n    processed_samples: Vec<f32>,\n    recording: Option<(PathBuf, hound::WavWriter<BufWriter<File>>)>,\n}\n\nimpl std::fmt::Debug for Mixer {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Audio\")\n            .field(\"paused\", &self.paused)\n            .field(\"channels\", &self.channels)\n            .field(\"sample_rate\", &self.sample_rate)\n            .field(\"sample_latency\", &self.sample_latency)\n            .field(\"queued_len\", &self.producer.occupied_len())\n            .field(\"processed_len\", &self.processed_samples.len())\n            .field(\"recording\", &self.recording.is_some())\n            .finish_non_exhaustive()\n    }\n}\n\nimpl Mixer {\n    fn start(\n        device: &cpal::Device,\n        config: &cpal::StreamConfig,\n        latency: Duration,\n        sample_format: cpal::SampleFormat,\n    ) -> anyhow::Result<Self> {\n        use cpal::SampleFormat;\n\n        let channels = config.channels;\n        let sample_rate = config.sample_rate;\n        let sample_latency =\n            (latency.as_secs_f32() * sample_rate as f32 * channels as f32).ceil() as usize;\n        let processed_samples = Vec::with_capacity(2 * sample_latency);\n        let buffer = HeapRb::<f32>::new(2 * sample_latency);\n        let (producer, consumer) = buffer.split();\n\n        let stream = match sample_format {\n            SampleFormat::I8 => Self::make_stream::<i8>(device, config, consumer),\n            SampleFormat::I16 => Self::make_stream::<i16>(device, config, consumer),\n            SampleFormat::I32 => Self::make_stream::<i32>(device, config, consumer),\n            SampleFormat::I64 => Self::make_stream::<i64>(device, config, consumer),\n            SampleFormat::U8 => Self::make_stream::<u8>(device, config, consumer),\n            SampleFormat::U16 => Self::make_stream::<u16>(device, config, consumer),\n            SampleFormat::U32 => Self::make_stream::<u32>(device, config, consumer),\n            SampleFormat::U64 => Self::make_stream::<u64>(device, config, consumer),\n            SampleFormat::F32 => Self::make_stream::<f32>(device, config, consumer),\n            SampleFormat::F64 => Self::make_stream::<f64>(device, config, consumer),\n            sample_format => Err(anyhow!(\"Unsupported sample format {sample_format}\")),\n        }?;\n        stream.play()?;\n\n        Ok(Self {\n            stream,\n            paused: false,\n            channels,\n            sample_rate,\n            sample_latency,\n            producer,\n            processed_samples,\n            recording: None,\n        })\n    }\n\n    /// Pause or resume the audio output stream. If `paused` is false and the stream is not started\n    /// yet, it will be started.\n    fn pause(&mut self, paused: bool) {\n        if paused && !self.paused {\n            let _ = self.stop_recording();\n            self.processed_samples.clear();\n            // FIXME: Currently cpal doesn't let the underyling audio device empty samples before\n            // pausing which leads to the remaining audio playing again upon resume. The only work\n            // around is to leave the stream playing\n            // if let Err(err) = self.stream.pause() {\n            //     error!(\"failed to pause audio stream: {err:?}\");\n            // }\n        } else if !paused && self.paused {\n            // if let Err(err) = self.stream.play() {\n            //     error!(\"failed to resume audio stream: {err:?}\");\n            // }\n        }\n        self.paused = paused;\n    }\n\n    fn start_recording(&mut self) -> anyhow::Result<()> {\n        let _ = self.stop_recording();\n        let path = Config::default_audio_dir()\n            .join(\n                chrono::Local::now()\n                    .format(\"recording_%Y-%m-%d_at_%H_%M_%S\")\n                    .to_string(),\n            )\n            .with_extension(\"wav\");\n        if let Some(parent) = path.parent()\n            && !parent.exists()\n        {\n            std::fs::create_dir_all(parent).with_context(|| {\n                format!(\n                    \"failed to create audio recording directory: {}\",\n                    parent.display()\n                )\n            })?;\n        }\n        let spec = hound::WavSpec {\n            channels: self.channels,\n            sample_rate: self.sample_rate,\n            bits_per_sample: 32,\n            sample_format: hound::SampleFormat::Float,\n        };\n        let writer =\n            hound::WavWriter::create(&path, spec).context(\"failed to create audio recording\")?;\n        self.recording = Some((path, writer));\n        Ok(())\n    }\n\n    fn stop_recording(&mut self) -> anyhow::Result<Option<PathBuf>> {\n        if let Some((path, mut recording)) = self.recording.take() {\n            match recording.flush() {\n                Ok(_) => Ok(Some(path)),\n                Err(err) => Err(anyhow!(\"failed to flush audio recording: {err:?}\")),\n            }\n        } else {\n            Ok(None)\n        }\n    }\n\n    fn make_stream<T>(\n        device: &cpal::Device,\n        config: &cpal::StreamConfig,\n        mut consumer: SampleConsumer,\n    ) -> anyhow::Result<cpal::Stream>\n    where\n        T: cpal::SizedSample + cpal::FromSample<f32>,\n    {\n        Ok(device.build_output_stream(\n            config,\n            move |out: &mut [T], _info| {\n                for (sample, value) in out\n                    .iter_mut()\n                    .zip(consumer.pop_iter().chain(iter::repeat(0.0)))\n                {\n                    *sample = T::from_sample(value);\n                }\n            },\n            |err| error!(\"an error occurred on stream: {err}\"),\n            None,\n        )?)\n    }\n\n    fn process(&mut self, samples: &[f32]) {\n        if self.paused {\n            return;\n        }\n        for sample in samples {\n            for _ in 0..self.channels {\n                self.processed_samples.push(*sample);\n            }\n            if let Some((_, recording)) = &mut self.recording {\n                // TODO: push slice to recording thread\n                if let Err(err) = recording.write_sample(*sample) {\n                    error!(\"failed to write audio sample: {err:?}\");\n                    let _ = self.stop_recording();\n                }\n            }\n        }\n        let processed_len = self.processed_samples.len();\n        let len = self.producer.vacant_len().min(processed_len);\n        let queued_len = self\n            .producer\n            .push_iter(&mut self.processed_samples.drain(..len));\n        trace!(\n            \"processed: {processed_len}, queued: {queued_len}, buffer len: {}\",\n            self.producer.occupied_len()\n        );\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/config.rs",
    "content": "use crate::nes::{\n    action::Action,\n    input::{ActionBindings, Gamepads, Input},\n    renderer::shader::Shader,\n    rom::HOMEBREW_ROMS,\n};\nuse anyhow::Context;\nuse egui::ahash::HashSet;\nuse serde::{Deserialize, Serialize};\nuse std::{\n    collections::{BTreeMap, VecDeque},\n    path::PathBuf,\n};\nuse tetanes_core::{\n    action::Action as DeckAction, common::NesRegion, control_deck::Config as DeckConfig, fs,\n    input::Player, ppu, time::Duration,\n};\nuse tracing::{error, info};\nuse uuid::Uuid;\n\n/// The maximum number of recent ROM entries to keep.\nconst MAX_RECENT_ROMS: usize = 10;\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\n#[must_use]\n#[serde(default)] // Ensures new fields don't break existing configurations\npub struct AudioConfig {\n    pub enabled: bool,\n    pub buffer_size: usize,\n    pub latency: Duration,\n}\n\nimpl Default for AudioConfig {\n    fn default() -> Self {\n        Self {\n            enabled: true,\n            buffer_size: if cfg!(target_arch = \"wasm32\") {\n                // Too low a value for wasm causes audio underruns in Chrome\n                2048\n            } else {\n                512\n            },\n            latency: if cfg!(target_arch = \"wasm32\") {\n                Duration::from_millis(80)\n            } else {\n                Duration::from_millis(50)\n            },\n        }\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]\n#[must_use]\n#[serde(default)] // Ensures new fields don't break existing configurations\npub struct EmulationConfig {\n    pub auto_load: bool,\n    pub auto_save: bool,\n    pub auto_save_interval: Duration,\n    pub rewind: bool,\n    pub rewind_seconds: u32,\n    pub rewind_interval: u32,\n    pub run_ahead: usize,\n    pub save_slot: u8,\n    pub speed: f32,\n    pub threaded: bool,\n}\n\nimpl Default for EmulationConfig {\n    fn default() -> Self {\n        Self {\n            auto_load: true,\n            auto_save: true,\n            auto_save_interval: Duration::from_secs(5),\n            rewind: true,\n            rewind_seconds: 30,\n            rewind_interval: 2,\n            // WASM struggles to run fast enough with run-ahead and low latency is not needed in\n            // debug builds.\n            run_ahead: if cfg!(any(debug_assertions, target_arch = \"wasm32\")) {\n                0\n            } else {\n                1\n            },\n            save_slot: 1,\n            speed: 1.0,\n            threaded: true,\n        }\n    }\n}\n\n/// Recently loaded ROM.\n#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum RecentRom {\n    /// Path to a local file.\n    Path(PathBuf),\n    /// Included Homebrew title.\n    Homebrew { name: String },\n}\n\nimpl RecentRom {\n    /// Return the name or title of this ROM.\n    pub fn name(&self) -> &str {\n        match self {\n            RecentRom::Path(path) => fs::filename(path).split('.').next().unwrap_or(\"??\"),\n            RecentRom::Homebrew { name } => name,\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\n#[must_use]\n#[serde(default)] // Ensures new fields don't break existing configurations\npub struct RendererConfig {\n    pub fullscreen: bool,\n    pub always_on_top: bool,\n    pub hide_overscan: bool,\n    pub scale: f32,\n    pub zoom: f32,\n    pub recent_roms: VecDeque<RecentRom>,\n    pub roms_path: Option<PathBuf>,\n    pub show_perf_stats: bool,\n    pub show_messages: bool,\n    pub show_menubar: bool,\n    pub embed_viewports: bool,\n    pub dark_theme: bool,\n    pub shader: Shader,\n    #[serde(default)]\n    pub show_updates: bool,\n}\n\nimpl Default for RendererConfig {\n    fn default() -> Self {\n        Self {\n            fullscreen: false,\n            always_on_top: false,\n            hide_overscan: true,\n            scale: 3.0,\n            zoom: 1.0,\n            recent_roms: VecDeque::default(),\n            roms_path: std::env::current_dir().ok(),\n            show_perf_stats: false,\n            show_messages: true,\n            show_menubar: true,\n            embed_viewports: false,\n            dark_theme: true,\n            shader: Shader::default(),\n            show_updates: true,\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\n#[must_use]\n#[serde(default)] // Ensures new fields don't break existing configurations\npub struct InputConfig {\n    pub action_bindings: Vec<ActionBindings>,\n    pub gamepad_assignments: [(Player, Option<Uuid>); 4],\n    #[serde(skip)]\n    pub shortcuts: BTreeMap<Action, ActionBindings>,\n    #[serde(skip)]\n    pub joypads: [BTreeMap<Action, ActionBindings>; 4],\n}\n\nimpl Default for InputConfig {\n    fn default() -> Self {\n        let shortcuts = ActionBindings::default_shortcuts();\n        let joypads = [Player::One, Player::Two, Player::Three, Player::Four]\n            .map(ActionBindings::default_player_bindings);\n        let action_bindings = shortcuts\n            .iter()\n            .chain(joypads.iter().flatten())\n            .map(|(_, bindings)| *bindings)\n            .collect();\n\n        Self {\n            action_bindings,\n            shortcuts,\n            joypads,\n            gamepad_assignments: std::array::from_fn(|i| {\n                (Player::try_from(i).expect(\"valid player assignment\"), None)\n            }),\n        }\n    }\n}\n\nimpl InputConfig {\n    pub fn set_binding(&mut self, action: Action, input: Input, binding: usize) {\n        // Clear existing binding, if any\n        self.clear_binding(input);\n\n        match self\n            .action_bindings\n            .iter_mut()\n            .find(|bind| bind.action == action)\n        {\n            Some(bind) => bind.bindings[binding] = Some(input),\n            None => {\n                let mut bindings = [None; 3];\n                bindings[binding] = Some(input);\n                self.action_bindings\n                    .push(ActionBindings { action, bindings });\n            }\n        }\n        let keybinds = if let Action::Deck(DeckAction::Joypad((player, _))) = action {\n            &mut self.joypads[player as usize]\n        } else {\n            &mut self.shortcuts\n        };\n        keybinds\n            .entry(action)\n            .and_modify(|bind| bind.bindings[binding] = Some(input))\n            .or_insert_with(|| {\n                let mut bindings = [None; 3];\n                bindings[binding] = Some(input);\n                ActionBindings { action, bindings }\n            });\n    }\n\n    pub fn clear_binding(&mut self, input: Input) {\n        for bind in &mut self.action_bindings {\n            if let Some((binding, existing_input)) = bind\n                .bindings\n                .iter_mut()\n                .enumerate()\n                .find(|(_, i)| **i == Some(input))\n            {\n                let keybinds = if let Action::Deck(DeckAction::Joypad((player, _))) = bind.action {\n                    &mut self.joypads[player as usize]\n                } else {\n                    &mut self.shortcuts\n                };\n                keybinds\n                    .entry(bind.action)\n                    .and_modify(|bind| bind.bindings[binding] = None);\n                *existing_input = None;\n            }\n        }\n    }\n\n    pub fn update_gamepad_assignments(&mut self, gamepads: &Gamepads) {\n        let assigned = self\n            .gamepad_assignments\n            .iter()\n            .filter_map(|(_, uuid)| *uuid)\n            .collect::<HashSet<_>>();\n        let mut available = gamepads.connected_uuids();\n        for (_, assigned_uuid) in &mut self.gamepad_assignments {\n            match assigned_uuid {\n                Some(uuid) => {\n                    if !gamepads.is_connected(uuid) {\n                        *assigned_uuid = None;\n                    }\n                }\n                None => {\n                    if let Some(uuid) = available.next()\n                        && !assigned.contains(uuid)\n                    {\n                        *assigned_uuid = Some(*uuid);\n                    }\n                }\n            }\n        }\n    }\n\n    pub fn next_gamepad_unassigned(&mut self) -> Option<Player> {\n        self.gamepad_assignments\n            .iter()\n            .find(|(_, u)| u.is_none())\n            .map(|(player, _)| *player)\n    }\n\n    pub const fn gamepad_assigned_to(&self, player: Player) -> Option<Uuid> {\n        self.gamepad_assignments[player as usize].1\n    }\n\n    pub fn gamepad_assignment(&self, uuid: &Uuid) -> Option<Player> {\n        self.gamepad_assignments\n            .iter()\n            .find(|(_, u)| u.as_ref().is_some_and(|u| u == uuid))\n            .map(|(player, _)| *player)\n    }\n\n    pub const fn assign_gamepad(&mut self, player: Player, uuid: Uuid) {\n        self.gamepad_assignments[player as usize].1 = Some(uuid);\n    }\n\n    pub fn unassign_gamepad(&mut self, player: Player) -> Option<Uuid> {\n        std::mem::take(&mut self.gamepad_assignments[player as usize].1)\n    }\n\n    pub fn unassign_gamepad_name(&mut self, uuid: &Uuid) -> Option<Player> {\n        if let Some((player, uuid)) = self\n            .gamepad_assignments\n            .iter_mut()\n            .find(|(_, u)| u.as_ref() == Some(uuid))\n        {\n            *uuid = None;\n            Some(*player)\n        } else {\n            None\n        }\n    }\n}\n\n/// NES emulation configuration settings.\n///\n/// # Config JSON\n///\n/// Configuration for `TetaNES` is stored (by default) in `~/.config/tetanes/config.json`\n/// with defaults that can be customized in the `TetaNES` config menu.\n#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]\n#[must_use]\n#[serde(default)] // Ensures new fields don't break existing configurations\npub struct Config {\n    pub deck: DeckConfig,\n    pub emulation: EmulationConfig,\n    pub audio: AudioConfig,\n    pub renderer: RendererConfig,\n    pub input: InputConfig,\n}\n\nimpl Config {\n    pub const SAVE_DIR: &'static str = \"save\";\n    pub const SAVE_EXTENSION: &'static str = \"sav\";\n    pub const WINDOW_TITLE: &'static str = \"TetaNES\";\n    pub const FILENAME: &'static str = \"config.json\";\n\n    #[must_use]\n    pub fn default_config_dir() -> PathBuf {\n        dirs::config_local_dir().map_or_else(\n            || PathBuf::from(\"config\"),\n            |dir| dir.join(DeckConfig::BASE_DIR),\n        )\n    }\n\n    #[must_use]\n    pub fn default_data_dir() -> PathBuf {\n        dirs::data_local_dir().map_or_else(\n            || PathBuf::from(\"data\"),\n            |dir| dir.join(DeckConfig::BASE_DIR),\n        )\n    }\n\n    #[must_use]\n    pub fn default_picture_dir() -> PathBuf {\n        dirs::picture_dir().map_or_else(\n            || PathBuf::from(\"pictures\"),\n            |dir| dir.join(DeckConfig::BASE_DIR),\n        )\n    }\n\n    #[must_use]\n    pub fn default_audio_dir() -> PathBuf {\n        dirs::audio_dir().map_or_else(\n            || PathBuf::from(\"music\"),\n            |dir| dir.join(DeckConfig::BASE_DIR),\n        )\n    }\n\n    #[must_use]\n    pub fn config_path() -> PathBuf {\n        Self::default_config_dir().join(Self::FILENAME)\n    }\n\n    #[must_use]\n    pub fn save_path(name: &str, slot: u8) -> PathBuf {\n        Self::default_data_dir()\n            .join(Self::SAVE_DIR)\n            .join(name)\n            .join(format!(\"slot-{slot}\"))\n            .with_extension(Self::SAVE_EXTENSION)\n    }\n\n    pub fn reset(&mut self) {\n        *self = Self::default();\n    }\n\n    pub fn save(&self) -> anyhow::Result<()> {\n        let path = Config::config_path();\n        let data = serde_json::to_vec_pretty(&self).context(\"failed to serialize config\")?;\n\n        fs::save_raw(path, &data).context(\"failed to save config\")?;\n\n        Ok(())\n    }\n\n    pub fn load(path: Option<PathBuf>) -> Self {\n        let path = path.unwrap_or_else(Config::config_path);\n\n        let mut config = if fs::exists(&path) {\n            info!(\"Loading saved configuration\");\n            fs::load_raw(&path)\n                .context(\"failed to load config\")\n                .and_then(|data| Ok(serde_json::from_slice::<Self>(&data)?))\n                .with_context(|| format!(\"failed to parse {path:?}\"))\n                .unwrap_or_else(|err| {\n                    error!(\"Invalid config: {path:?}, reverting to defaults. Error: {err:?}\",);\n                    Self::default()\n                })\n        } else {\n            info!(\"Loading default configuration\");\n            Self::default()\n        };\n\n        for binding in &config.input.action_bindings {\n            if let Action::Deck(DeckAction::Joypad((player, _))) = binding.action {\n                config.input.joypads[player as usize].insert(binding.action, *binding);\n            } else {\n                config.input.shortcuts.insert(binding.action, *binding);\n            }\n        }\n\n        // Only keep recent Homebrew ROMs that are still available.\n        let homebrew_roms = HOMEBREW_ROMS\n            .iter()\n            .map(|rom| rom.name)\n            .collect::<HashSet<_>>();\n        config.renderer.recent_roms.retain(|rom| match rom {\n            RecentRom::Path(_) => true,\n            RecentRom::Homebrew { name } => homebrew_roms.contains(name.as_str()),\n        });\n\n        config\n    }\n\n    pub fn increment_speed(&mut self) -> f32 {\n        self.emulation.speed = self.next_increment_speed();\n        self.emulation.speed\n    }\n\n    pub fn next_increment_speed(&self) -> f32 {\n        if self.emulation.speed <= 1.75 {\n            self.emulation.speed + 0.25\n        } else {\n            self.emulation.speed\n        }\n    }\n\n    pub fn decrement_speed(&mut self) -> f32 {\n        self.emulation.speed = self.next_decrement_speed();\n        self.emulation.speed\n    }\n\n    pub fn next_decrement_speed(&self) -> f32 {\n        if self.emulation.speed >= 0.50 {\n            self.emulation.speed - 0.25\n        } else {\n            self.emulation.speed\n        }\n    }\n\n    pub fn increment_scale(&mut self) -> f32 {\n        self.renderer.scale = self.next_increment_scale();\n        self.renderer.scale\n    }\n\n    pub fn next_increment_scale(&self) -> f32 {\n        if self.renderer.scale <= 4.0 {\n            self.renderer.scale + 1.0\n        } else {\n            self.renderer.scale\n        }\n    }\n\n    pub fn decrement_scale(&mut self) -> f32 {\n        self.renderer.scale = self.next_decrement_scale();\n        self.renderer.scale\n    }\n\n    pub fn next_decrement_scale(&self) -> f32 {\n        if self.renderer.scale >= 2.0 {\n            self.renderer.scale - 1.0\n        } else {\n            self.renderer.scale\n        }\n    }\n\n    #[must_use]\n    pub fn window_size(&self, aspect_ratio: f32) -> egui::Vec2 {\n        self.window_size_for_scale(aspect_ratio, self.renderer.scale)\n    }\n\n    #[must_use]\n    pub fn window_size_for_scale(&self, aspect_ratio: f32, scale: f32) -> egui::Vec2 {\n        let texture_size = self.texture_size();\n        egui::Vec2::new(\n            (scale * aspect_ratio * texture_size.x).ceil(),\n            (scale * texture_size.y).ceil(),\n        )\n    }\n\n    #[must_use]\n    pub const fn texture_size(&self) -> egui::Vec2 {\n        let width = ppu::size::WIDTH;\n        let height = if self.renderer.hide_overscan {\n            ppu::size::HEIGHT - 16\n        } else {\n            ppu::size::HEIGHT\n        };\n        egui::Vec2::new(width as f32, height as f32)\n    }\n\n    pub fn shortcut(&self, action: impl Into<Action>) -> String {\n        let action = action.into();\n        self.input\n            .shortcuts\n            .get(&action)\n            .or_else(|| self.input.joypads[0].get(&action))\n            .and_then(|bind| bind.bindings[0])\n            .map(Input::fmt)\n            .unwrap_or_default()\n    }\n\n    pub fn action_input(&self, action: impl Into<Action>) -> Option<Input> {\n        let action = action.into();\n        self.input\n            .shortcuts\n            .get(&action)\n            .or_else(|| {\n                self.input\n                    .joypads\n                    .iter()\n                    .map(|bind| bind.get(&action))\n                    .next()\n                    .flatten()\n            })\n            .and_then(|bind| bind.bindings[0])\n    }\n\n    // Add a recently loaded ROM.\n    pub fn add_recent_rom(&mut self, rom: RecentRom) {\n        self.renderer.recent_roms.retain(|r| r != &rom);\n        self.renderer.recent_roms.push_front(rom);\n        if self.renderer.recent_roms.len() > MAX_RECENT_ROMS {\n            self.renderer.recent_roms.pop_back();\n        }\n    }\n}\n\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum FrameRate {\n    X50,\n    X59,\n    #[default]\n    X60,\n}\n\nimpl FrameRate {\n    pub const MIN: Self = Self::X50;\n    pub const MAX: Self = Self::X60;\n\n    pub fn duration(&self) -> Duration {\n        Duration::from_secs_f32(f32::from(self).recip())\n    }\n}\n\nimpl From<FrameRate> for u32 {\n    fn from(frame_rate: FrameRate) -> Self {\n        match frame_rate {\n            FrameRate::X50 => 50,\n            FrameRate::X59 => 59,\n            FrameRate::X60 => 60,\n        }\n    }\n}\n\nimpl From<&FrameRate> for u32 {\n    fn from(frame_rate: &FrameRate) -> Self {\n        Self::from(*frame_rate)\n    }\n}\n\nimpl From<FrameRate> for f32 {\n    fn from(frame_rate: FrameRate) -> Self {\n        u32::from(frame_rate) as f32\n    }\n}\n\nimpl From<&FrameRate> for f32 {\n    fn from(frame_rate: &FrameRate) -> Self {\n        Self::from(*frame_rate)\n    }\n}\n\nimpl From<NesRegion> for FrameRate {\n    fn from(region: NesRegion) -> Self {\n        match region {\n            NesRegion::Auto | NesRegion::Ntsc => Self::X60,\n            NesRegion::Pal => Self::X50,\n            NesRegion::Dendy => Self::X59,\n        }\n    }\n}\n\nimpl From<&NesRegion> for FrameRate {\n    fn from(region: &NesRegion) -> Self {\n        Self::from(*region)\n    }\n}\n\nimpl AsRef<str> for FrameRate {\n    fn as_ref(&self) -> &str {\n        match self {\n            Self::X50 => \"50 Hz\",\n            Self::X59 => \"59 Hz\",\n            Self::X60 => \"60 Hz\",\n        }\n    }\n}\n\nimpl std::fmt::Display for FrameRate {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.as_ref())\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/emulation/replay.rs",
    "content": "use crate::nes::{config::Config, event::EmulationEvent};\nuse chrono::Local;\nuse serde::{Deserialize, Serialize};\nuse std::{\n    cmp::Ordering,\n    io::Read,\n    path::{Path, PathBuf},\n};\nuse tetanes_core::{\n    cpu::Cpu,\n    fs,\n    input::{JoypadBtn, Player},\n};\nuse tracing::warn;\nuse winit::event::ElementState;\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct State((Cpu, Vec<ReplayFrame>));\n\n#[derive(Debug, Serialize, Deserialize)]\npub enum ReplayEvent {\n    Joypad((Player, JoypadBtn, ElementState)),\n    ZapperAim((u16, u16)),\n    ZapperTrigger,\n}\n\nimpl From<ReplayEvent> for EmulationEvent {\n    fn from(event: ReplayEvent) -> Self {\n        match event {\n            ReplayEvent::Joypad(state) => Self::Joypad(state),\n            ReplayEvent::ZapperAim(pos) => Self::ZapperAim(pos),\n            ReplayEvent::ZapperTrigger => Self::ZapperTrigger,\n        }\n    }\n}\n\nimpl TryFrom<EmulationEvent> for ReplayEvent {\n    type Error = anyhow::Error;\n\n    fn try_from(event: EmulationEvent) -> Result<Self, Self::Error> {\n        Ok(match event {\n            EmulationEvent::Joypad(state) => Self::Joypad(state),\n            EmulationEvent::ZapperAim(pos) => Self::ZapperAim(pos),\n            EmulationEvent::ZapperTrigger => Self::ZapperTrigger,\n            _ => return Err(anyhow::anyhow!(\"invalid replay event: {event:?}\")),\n        })\n    }\n}\n\n#[derive(Debug, Serialize, Deserialize)]\n#[must_use]\npub struct ReplayFrame {\n    pub frame: u32,\n    pub event: ReplayEvent,\n}\n\n#[derive(Default, Debug)]\n#[must_use]\npub struct Record {\n    pub start: Option<Cpu>,\n    pub events: Vec<ReplayFrame>,\n}\n\nimpl Record {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn start(&mut self, cpu: Cpu) {\n        self.start = Some(cpu);\n        self.events.clear();\n    }\n\n    pub fn stop(&mut self, name: &str) -> anyhow::Result<Option<PathBuf>> {\n        self.save(name)\n    }\n\n    pub fn push(&mut self, frame: u32, event: EmulationEvent) {\n        if self.start.is_some()\n            && let Ok(event) = ReplayEvent::try_from(event)\n        {\n            self.events.push(ReplayFrame { frame, event });\n        }\n    }\n\n    /// Saves the replay recording out to a file.\n    pub fn save(&mut self, name: &str) -> anyhow::Result<Option<PathBuf>> {\n        let Some(start) = self.start.take() else {\n            return Ok(None);\n        };\n\n        if self.events.is_empty() {\n            tracing::debug!(\"not saving - no replay events\");\n            return Ok(None);\n        }\n\n        let replay_path = Config::default_data_dir()\n            .join(\n                Local::now()\n                    .format(&format!(\"tetanes_replay_{name}_%Y-%m-%d_%H.%M.%S\"))\n                    .to_string(),\n            )\n            .with_extension(\"replay\");\n        let events = std::mem::take(&mut self.events);\n\n        fs::save(&replay_path, &State((start, events)))?;\n\n        Ok(Some(replay_path))\n    }\n}\n\n#[derive(Default, Debug)]\n#[must_use]\npub struct Replay {\n    pub events: Vec<ReplayFrame>,\n}\n\nimpl Replay {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Loads a replay recording file.\n    pub fn load_path(&mut self, path: impl AsRef<Path>) -> anyhow::Result<Cpu> {\n        let path = path.as_ref();\n        let State((cpu, mut events)) = fs::load(path)?;\n        events.reverse(); // So we can pop off the end\n        self.events = events;\n        Ok(cpu)\n    }\n\n    /// Loads a replay from a reader.\n    pub fn load(&mut self, mut replay: impl Read) -> anyhow::Result<Cpu> {\n        let mut events = Vec::new();\n        replay.read_to_end(&mut events)?;\n        let State((cpu, mut events)) = fs::load_bytes(&events)?;\n        events.reverse(); // So we can pop off the end\n        self.events = events;\n        Ok(cpu)\n    }\n\n    pub fn next(&mut self, frame: u32) -> Option<EmulationEvent> {\n        if let Some(event) = self.events.last() {\n            match event.frame.cmp(&frame) {\n                Ordering::Less | Ordering::Equal => {\n                    if event.frame < frame {\n                        warn!(\"out of order replay event: {} < {frame}\", event.frame);\n                    }\n                    return self.events.pop().map(|event| event.event).map(Into::into);\n                }\n                Ordering::Greater => (),\n            }\n        }\n        None\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/emulation/rewind.rs",
    "content": "use crate::nes::{emulation::State, renderer::gui::MessageType};\nuse tetanes_core::{\n    cpu::Cpu,\n    fs::{Error, Result},\n    ppu::frame::Buffer,\n};\nuse tracing::error;\n\n#[derive(Default, Debug, Clone)]\n#[must_use]\npub struct Frame {\n    pub buffer: Buffer,\n    pub state: Vec<u8>,\n}\n\n#[derive(Default, Debug)]\n#[must_use]\npub struct Rewind {\n    pub enabled: bool,\n    pub interval_counter: usize,\n    pub index: usize,\n    pub count: usize,\n    pub interval: usize,\n    pub seconds: usize,\n    pub frames: Vec<Option<Frame>>,\n}\n\nimpl Rewind {\n    const TARGET_FPS: usize = 60;\n\n    pub fn new(enabled: bool, seconds: u32, interval: u32) -> Self {\n        let interval = interval as usize;\n        let seconds = seconds as usize;\n        Self {\n            enabled,\n            interval_counter: 0,\n            index: 0,\n            count: 0,\n            interval,\n            seconds,\n            frames: vec![None; Self::frame_size(seconds, interval)],\n        }\n    }\n\n    const fn frame_size(seconds: usize, interval: usize) -> usize {\n        Self::TARGET_FPS * seconds / interval\n    }\n\n    pub fn set_enabled(&mut self, enabled: bool) {\n        self.enabled = enabled;\n        if !enabled {\n            self.clear();\n        }\n    }\n\n    pub fn set_seconds(&mut self, seconds: u32) {\n        self.seconds = seconds as usize;\n        self.frames\n            .resize(Self::frame_size(self.seconds, self.interval), None);\n    }\n\n    pub fn set_interval(&mut self, interval: u32) {\n        self.interval = interval as usize;\n        self.frames\n            .resize(Self::frame_size(self.seconds, self.interval), None);\n    }\n\n    pub fn push(&mut self, cpu: &Cpu) -> Result<()> {\n        if !self.enabled {\n            return Ok(());\n        }\n        self.interval_counter += 1;\n        if self.interval_counter >= self.interval {\n            self.interval_counter = 0;\n\n            let config = bincode::config::legacy();\n            let state = bincode::serde::encode_to_vec(cpu, config)\n                .map_err(|err| Error::SerializationFailed(err.to_string()))?;\n            self.frames[self.index] = Some(Frame {\n                buffer: cpu.bus.ppu.frame.buffer.clone(),\n                state,\n            });\n\n            self.count += 1;\n            self.index += 1;\n            if self.index >= self.frames.len() {\n                self.index = 0;\n            }\n        }\n        Ok(())\n    }\n\n    pub fn pop(&mut self) -> Option<Cpu> {\n        if !self.enabled {\n            return None;\n        }\n        if self.count > 0 {\n            self.count -= 1;\n            self.index -= 1;\n            if self.index == 0 {\n                self.index = self.frames.len() - 1;\n            }\n\n            let frame = self.frames[self.index].take()?;\n            let config = bincode::config::legacy();\n            bincode::serde::decode_from_slice::<Cpu, _>(&frame.state, config)\n                .map(|(mut cpu, _)| {\n                    cpu.bus.input.clear(); // Discard inputs while rewinding\n                    cpu.bus.ppu.frame.buffer = frame.buffer;\n                    cpu\n                })\n                .map_err(|err| error!(\"Failed to deserialize CPU state: {err:?}\"))\n                .ok()\n        } else {\n            None\n        }\n    }\n\n    pub fn clear(&mut self) {\n        self.interval_counter = 0;\n        self.index = 0;\n        self.count = 0;\n        self.frames.fill(None);\n    }\n}\n\nimpl State {\n    pub fn rewind_disabled(&mut self) {\n        self.add_message(\n            MessageType::Warn,\n            \"Rewind disabled. You can enable it in the Preferences menu.\",\n        );\n    }\n\n    pub fn instant_rewind(&mut self) {\n        if !self.rewind.enabled {\n            return self.rewind_disabled();\n        }\n        // ~2 seconds worth of frames @ 60 FPS\n        let mut rewind_frames = 120 / self.rewind.interval;\n        while let Some(mut cpu) = self.rewind.pop() {\n            cpu.bus.input.clear(); // Discard inputs while rewinding\n            self.control_deck.load_cpu(cpu);\n            rewind_frames -= 1;\n            if rewind_frames == 0 {\n                break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/emulation.rs",
    "content": "use crate::{\n    nes::{\n        RunState,\n        action::DebugStep,\n        audio::{Audio, State as AudioState},\n        config::{Config, FrameRate},\n        emulation::{replay::Record, rewind::Rewind},\n        event::{ConfigEvent, EmulationEvent, NesEvent, NesEventProxy, RendererEvent, UiEvent},\n        renderer::{FrameRecycle, gui::MessageType},\n    },\n    thread,\n};\nuse anyhow::{Context, anyhow};\nuse chrono::Local;\nuse crossbeam::channel;\nuse egui::ViewportId;\nuse replay::Replay;\nuse std::{\n    collections::VecDeque,\n    io::{self, Read},\n    path::{Path, PathBuf},\n    thread::JoinHandle,\n};\nuse tetanes_core::{\n    apu::Apu,\n    common::{NesRegion, Regional, Reset, ResetKind},\n    control_deck::{self, ControlDeck, LoadedRom},\n    cpu::Cpu,\n    ppu,\n    time::{Duration, Instant},\n    video::Frame,\n};\nuse thingbuf::mpsc::{blocking::Sender as BufSender, errors::TrySendError};\nuse tracing::{debug, error, trace};\nuse winit::event::ElementState;\n\npub mod replay;\npub mod rewind;\n\n#[derive(Debug, Copy, Clone, PartialEq)]\n#[must_use]\npub struct FrameStats {\n    pub timestamp: Instant,\n    pub fps: f32,\n    pub fps_min: f32,\n    pub frame_time: f32,\n    pub frame_time_max: f32,\n    pub frame_count: usize,\n}\n\nimpl Default for FrameStats {\n    fn default() -> Self {\n        Self {\n            timestamp: Instant::now(),\n            fps: 0.0,\n            fps_min: 0.0,\n            frame_time: 0.0,\n            frame_time_max: 0.0,\n            frame_count: 0,\n        }\n    }\n}\n\nimpl FrameStats {\n    pub fn new() -> Self {\n        Self::default()\n    }\n}\n\n#[derive(Debug)]\n#[must_use]\npub struct FrameTimeDiag {\n    frame_count: usize,\n    history: VecDeque<f32>,\n    sum: f32,\n    avg: f32,\n    last_update: Instant,\n}\n\nimpl FrameTimeDiag {\n    const MAX_HISTORY: usize = 120;\n    const UPDATE_INTERVAL: Duration = Duration::from_millis(300);\n\n    fn new() -> Self {\n        Self {\n            frame_count: 0,\n            history: VecDeque::with_capacity(Self::MAX_HISTORY),\n            sum: 0.0,\n            avg: 1.0 / 60.0,\n            last_update: Instant::now(),\n        }\n    }\n\n    fn push(&mut self, frame_time: f32) {\n        self.frame_count += 1;\n\n        // Ignore the first few frames to allow the average to stabilize\n        if frame_time.is_finite() && self.frame_count >= 10 {\n            if self.history.len() >= Self::MAX_HISTORY\n                && let Some(oldest) = self.history.pop_front()\n            {\n                self.sum -= oldest;\n            }\n            self.sum += frame_time;\n            self.history.push_back(frame_time);\n        }\n    }\n\n    fn avg(&mut self) -> f32 {\n        if !self.history.is_empty() {\n            let now = Instant::now();\n            if now > self.last_update + Self::UPDATE_INTERVAL {\n                self.last_update = now;\n                self.avg = self.sum / self.history.len() as f32;\n            }\n        }\n        self.avg\n    }\n\n    fn history(&self) -> impl Iterator<Item = &f32> {\n        self.history.iter()\n    }\n\n    fn reset(&mut self) {\n        self.frame_count = 0;\n        self.history.clear();\n        self.sum = 0.0;\n        self.avg = 1.0 / 60.0;\n        self.last_update = Instant::now();\n    }\n}\n\nfn shutdown(tx: &NesEventProxy, err: impl std::fmt::Display) {\n    error!(\"{err}\");\n    tx.event(UiEvent::Terminate);\n}\n\n#[derive(Debug)]\n#[must_use]\nenum Threads {\n    Single(Box<Single>),\n    Multi(Multi),\n}\n\n#[derive(Debug)]\n#[must_use]\nstruct Single {\n    state: State,\n}\n\n#[derive(Debug)]\n#[must_use]\nstruct Multi {\n    tx: channel::Sender<NesEvent>,\n    handle: JoinHandle<()>,\n}\n\nimpl Multi {\n    fn spawn(\n        proxy_tx: NesEventProxy,\n        frame_tx: BufSender<Frame, FrameRecycle>,\n        cfg: &Config,\n    ) -> anyhow::Result<Self> {\n        let (tx, rx) = channel::bounded(128);\n        Ok(Self {\n            tx,\n            handle: std::thread::Builder::new()\n                .name(\"emulation\".into())\n                .spawn({\n                    let cfg = cfg.clone();\n                    move || Self::main(proxy_tx, rx, frame_tx, &cfg)\n                })?,\n        })\n    }\n\n    fn main(\n        tx: NesEventProxy,\n        rx: channel::Receiver<NesEvent>,\n        frame_tx: BufSender<Frame, FrameRecycle>,\n        cfg: &Config,\n    ) {\n        debug!(\"emulation thread started\");\n        let mut state = State::new(tx, frame_tx, cfg); // Has to be created on the thread, since\n        loop {\n            while let Ok(event) = rx.try_recv() {\n                state.on_event(&event);\n            }\n\n            state.try_clock_frame();\n        }\n    }\n}\n\n#[derive(Debug)]\n#[must_use]\npub struct Emulation {\n    threads: Threads,\n}\n\nimpl Emulation {\n    /// Initializes the renderer in a platform-agnostic way.\n    pub fn new(\n        tx: NesEventProxy,\n        frame_tx: BufSender<Frame, FrameRecycle>,\n        cfg: &Config,\n    ) -> anyhow::Result<Self> {\n        let threaded = cfg.emulation.threaded\n            && std::thread::available_parallelism().is_ok_and(|count| count.get() > 1);\n        let backend = if threaded {\n            Threads::Multi(Multi::spawn(tx, frame_tx, cfg)?)\n        } else {\n            Threads::Single(Box::new(Single {\n                state: State::new(tx, frame_tx, cfg),\n            }))\n        };\n\n        Ok(Self { threads: backend })\n    }\n\n    /// Handle event.\n    pub fn on_event(&mut self, event: &NesEvent) {\n        match &mut self.threads {\n            Threads::Single(single) => single.state.on_event(event),\n            Threads::Multi(Multi { tx, handle }) => {\n                handle.thread().unpark();\n                if let Err(err) = tx.try_send(event.clone()) {\n                    error!(\"failed to send emulation event: {event:?}. {err:?}\");\n                }\n            }\n        }\n    }\n\n    pub fn try_clock_frame(&mut self) {\n        match &mut self.threads {\n            Threads::Single(single) => single.state.try_clock_frame(),\n            // Multi-threaded emulation handles it's own clock timing and redraw requests\n            Threads::Multi(Multi { handle, .. }) => handle.thread().unpark(),\n        }\n    }\n\n    pub fn terminate(&mut self) {\n        match &mut self.threads {\n            Threads::Single(_) => (),\n            Threads::Multi(Multi { tx, handle }) => {\n                handle.thread().unpark();\n                if let Err(err) = tx.try_send(NesEvent::Ui(UiEvent::Terminate)) {\n                    error!(\"failed to send termination event. {err:?}\");\n                }\n            }\n        }\n    }\n}\n\n#[derive(Debug)]\n#[must_use]\npub struct State {\n    tx: NesEventProxy,\n    control_deck: ControlDeck,\n    audio: Audio,\n    frame_tx: BufSender<Frame, FrameRecycle>,\n    frame_latency: usize,\n    target_frame_duration: Duration,\n    last_clock_time: Instant,\n    clock_time_accumulator: f32,\n    last_frame_time: Instant,\n    frame_time_diag: FrameTimeDiag,\n    run_state: RunState,\n    threaded: bool,\n    rewinding: bool,\n    rewind: Rewind,\n    record: Record,\n    replay: Replay,\n    save_slot: u8,\n    auto_save: bool,\n    auto_save_interval: Duration,\n    last_auto_save: Instant,\n    auto_load: bool,\n    speed: f32,\n    run_ahead: usize,\n    show_frame_stats: bool,\n}\n\nimpl Drop for State {\n    fn drop(&mut self) {\n        self.unload_rom();\n    }\n}\n\nimpl State {\n    fn new(tx: NesEventProxy, frame_tx: BufSender<Frame, FrameRecycle>, cfg: &Config) -> Self {\n        let mut control_deck = ControlDeck::with_config(cfg.deck.clone());\n        let audio = Audio::new(\n            cfg.audio.enabled,\n            Apu::DEFAULT_SAMPLE_RATE,\n            cfg.audio.latency,\n            cfg.audio.buffer_size,\n        );\n        if cfg.audio.enabled && audio.device().is_none() {\n            tx.event(ConfigEvent::AudioEnabled(false));\n            tx.event(UiEvent::Message((\n                MessageType::Warn,\n                \"No audio device found.\".into(),\n            )));\n        }\n        if Apu::DEFAULT_SAMPLE_RATE != audio.sample_rate {\n            control_deck.set_sample_rate(audio.sample_rate);\n        }\n        let rewind = Rewind::new(\n            cfg.emulation.rewind,\n            cfg.emulation.rewind_seconds,\n            cfg.emulation.rewind_interval,\n        );\n        let target_frame_duration = FrameRate::from(cfg.deck.region).duration();\n        let mut state = Self {\n            tx,\n            control_deck,\n            audio,\n            frame_tx,\n            frame_latency: 1,\n            target_frame_duration,\n            last_clock_time: Instant::now(),\n            clock_time_accumulator: 0.0,\n            last_frame_time: Instant::now(),\n            frame_time_diag: FrameTimeDiag::new(),\n            run_state: RunState::AutoPaused,\n            threaded: cfg.emulation.threaded\n                && std::thread::available_parallelism().is_ok_and(|count| count.get() > 1),\n            rewinding: false,\n            rewind,\n            record: Record::new(),\n            replay: Replay::new(),\n            save_slot: cfg.emulation.save_slot,\n            auto_save: cfg.emulation.auto_save,\n            auto_save_interval: cfg.emulation.auto_save_interval,\n            last_auto_save: Instant::now(),\n            auto_load: cfg.emulation.auto_load,\n            speed: cfg.emulation.speed,\n            run_ahead: cfg.emulation.run_ahead,\n            show_frame_stats: false,\n        };\n        state.update_region(cfg.deck.region);\n        state\n    }\n\n    pub(crate) fn add_message<S: ToString>(&mut self, ty: MessageType, msg: S) {\n        self.tx.event(UiEvent::Message((ty, msg.to_string())));\n    }\n\n    fn write_deck<T>(\n        &mut self,\n        writer: impl FnOnce(&mut ControlDeck) -> control_deck::Result<T>,\n    ) -> Option<T> {\n        writer(&mut self.control_deck)\n            .map_err(|err| self.on_error(err))\n            .ok()\n    }\n\n    fn on_error(&mut self, err: impl Into<anyhow::Error>) {\n        use tetanes_core::mem::Read;\n\n        let err = err.into();\n        error!(\"Emulation error: {err:?}\");\n        if self.control_deck.cpu_corrupted() {\n            let cpu = self.control_deck.cpu();\n            let opcode = cpu.peek(cpu.pc.wrapping_sub(1));\n            self.tx.event(EmulationEvent::CpuCorrupted {\n                instr: Cpu::INSTR_REF[usize::from(opcode)],\n            });\n        } else {\n            self.add_message(MessageType::Error, err);\n        }\n    }\n\n    /// Handle event.\n    fn on_event(&mut self, event: &NesEvent) {\n        match event {\n            NesEvent::Ui(UiEvent::Terminate) => {\n                self.unload_rom();\n                debug!(\"emulation stopped\");\n            }\n            NesEvent::Emulation(event) => self.on_emulation_event(event),\n            NesEvent::Config(event) => self.on_config_event(event),\n            _ => (),\n        }\n    }\n\n    /// Handle emulation event.\n    fn on_emulation_event(&mut self, event: &EmulationEvent) {\n        match event {\n            EmulationEvent::AddDebugger(debugger) => {\n                self.control_deck.add_debugger(debugger.clone());\n            }\n            EmulationEvent::RemoveDebugger(debugger) => {\n                self.control_deck.remove_debugger(debugger.clone());\n            }\n            EmulationEvent::AudioRecord(recording) => {\n                if self.control_deck.is_running() {\n                    self.audio_record(*recording);\n                }\n            }\n            EmulationEvent::CpuCorrupted { .. } => (), // Ignore, as only this module emits this\n            // event\n            EmulationEvent::DebugStep(step) => {\n                if self.control_deck.is_running() {\n                    match step {\n                        DebugStep::Into => {\n                            self.write_deck(|deck| deck.clock_instr());\n                            self.send_frame();\n                        }\n                        DebugStep::Out => {\n                            // TODO: track stack frames list on jsr, irq, brk\n                            // while stack frame == previous stack frame, clock_instr, send_frame\n                            self.send_frame();\n                        }\n                        DebugStep::Over => {\n                            // TODO: track stack frames list on jsr, irq, brk\n                            // while stack frame != previous stack frame, clock_instr, send_frame\n                            self.send_frame();\n                        }\n                        DebugStep::Scanline => {\n                            if self.write_deck(|deck| deck.clock_scanline()).is_some() {\n                                self.send_frame();\n                            }\n                        }\n                        DebugStep::Frame => {\n                            if self.write_deck(|deck| deck.clock_frame()).is_some() {\n                                self.send_frame();\n                            }\n                        }\n                    }\n                }\n            }\n            EmulationEvent::InstantRewind => {\n                if self.control_deck.is_running() {\n                    self.instant_rewind();\n                }\n            }\n            EmulationEvent::Joypad((player, button, state)) => {\n                if self.control_deck.is_running() {\n                    let pressed = *state == ElementState::Pressed;\n                    let joypad = self.control_deck.joypad_mut(*player);\n                    joypad.set_button(*button, pressed);\n                    self.record\n                        .push(self.control_deck.frame_number(), event.clone());\n                }\n            }\n            EmulationEvent::LoadReplay((name, replay)) => {\n                if self.control_deck.is_running() {\n                    self.load_replay(name, &mut io::Cursor::new(replay));\n                }\n            }\n            EmulationEvent::LoadReplayPath(path) => {\n                if self.control_deck.is_running() {\n                    self.load_replay_path(path);\n                }\n            }\n            EmulationEvent::LoadRom((name, rom)) => {\n                self.load_rom(name, &mut io::Cursor::new(rom));\n            }\n            EmulationEvent::LoadRomPath(path) => self.load_rom_path(path),\n            EmulationEvent::LoadState(slot) => self.load_state(*slot),\n            EmulationEvent::RunState(mode) => self.set_run_state(*mode),\n            EmulationEvent::ReplayRecord(recording) => {\n                if self.control_deck.is_running() {\n                    self.replay_record(*recording);\n                }\n            }\n            EmulationEvent::Reset(kind) => {\n                self.frame_time_diag.reset();\n                if self.control_deck.is_running() || self.control_deck.cpu_corrupted() {\n                    self.control_deck.reset(*kind);\n                    match kind {\n                        ResetKind::Soft => self.add_message(MessageType::Info, \"Reset\"),\n                        ResetKind::Hard => self.add_message(MessageType::Info, \"Power Cycled\"),\n                    }\n                }\n            }\n            EmulationEvent::RequestFrame => self.send_frame(),\n            EmulationEvent::Rewinding(rewind) => {\n                if self.control_deck.is_running() {\n                    if self.rewind.enabled {\n                        self.rewinding = *rewind;\n                        if self.rewinding {\n                            self.add_message(MessageType::Info, \"Rewinding...\");\n                        }\n                    } else {\n                        self.rewind_disabled();\n                    }\n                }\n            }\n            EmulationEvent::SaveState(slot) => self.save_state(*slot, false),\n            EmulationEvent::ShowFrameStats(show) => {\n                self.frame_time_diag.reset();\n                self.show_frame_stats = *show;\n            }\n            EmulationEvent::Screenshot => {\n                if self.control_deck.is_running() {\n                    match self.save_screenshot() {\n                        Ok(filename) => {\n                            self.add_message(\n                                MessageType::Info,\n                                format!(\"Screenshot Saved: {}\", filename.display()),\n                            );\n                        }\n                        Err(err) => self.on_error(err),\n                    }\n                }\n            }\n            EmulationEvent::UnloadRom => self.unload_rom(),\n            EmulationEvent::ZapperAim((x, y)) => {\n                self.control_deck.aim_zapper(*x, *y);\n                self.record\n                    .push(self.control_deck.frame_number(), event.clone());\n            }\n            EmulationEvent::ZapperTrigger => {\n                self.control_deck.trigger_zapper();\n                self.record\n                    .push(self.control_deck.frame_number(), event.clone());\n            }\n        }\n    }\n\n    /// Handle config event.\n    fn on_config_event(&mut self, event: &ConfigEvent) {\n        match event {\n            ConfigEvent::ApuChannelEnabled((channel, enabled)) => {\n                let prev_enabled = self.control_deck.channel_enabled(*channel);\n                self.control_deck\n                    .set_apu_channel_enabled(*channel, *enabled);\n                if prev_enabled != *enabled {\n                    let enabled_text = if *enabled { \"Enabled\" } else { \"Disabled\" };\n                    self.add_message(\n                        MessageType::Info,\n                        format!(\"{enabled_text} APU Channel {channel:?}\"),\n                    );\n                }\n            }\n            ConfigEvent::AudioBuffer(buffer_size) => {\n                if let Err(err) = self.audio.set_buffer_size(*buffer_size) {\n                    self.on_error(err);\n                }\n            }\n            ConfigEvent::AudioEnabled(enabled) => match self.audio.set_enabled(*enabled) {\n                Ok(state) => match state {\n                    AudioState::Started => self.add_message(MessageType::Info, \"Audio Enabled\"),\n                    AudioState::Disabled | AudioState::Stopped => {\n                        self.add_message(MessageType::Info, \"Audio Disabled\")\n                    }\n                    AudioState::NoOutputDevice => (),\n                },\n                Err(err) => self.on_error(err),\n            },\n            ConfigEvent::AudioLatency(latency) => {\n                if let Err(err) = self.audio.set_latency(*latency) {\n                    self.on_error(err);\n                }\n            }\n            ConfigEvent::AutoLoad(enabled) => self.auto_load = *enabled,\n            ConfigEvent::AutoSave(enabled) => self.auto_save = *enabled,\n            ConfigEvent::AutoSaveInterval(interval) => self.auto_save_interval = *interval,\n            ConfigEvent::ConcurrentDpad(enabled) => {\n                self.control_deck.set_concurrent_dpad(*enabled);\n            }\n            ConfigEvent::EmulatePpuWarmup(enabled) => {\n                self.control_deck.set_emulate_ppu_warmup(*enabled);\n            }\n            ConfigEvent::FourPlayer(four_player) => {\n                self.control_deck.set_four_player(*four_player);\n            }\n            ConfigEvent::GenieCodeAdded(genie_code) => {\n                self.control_deck\n                    .cpu_mut()\n                    .bus\n                    .add_genie_code(genie_code.clone());\n            }\n            ConfigEvent::GenieCodeRemoved(code) => {\n                self.control_deck.remove_genie_code(code);\n            }\n            ConfigEvent::RamState(ram_state) => {\n                self.control_deck.set_ram_state(*ram_state);\n            }\n            ConfigEvent::Region(region) => {\n                self.control_deck.set_region(*region);\n                self.update_region(*region);\n            }\n            ConfigEvent::RewindEnabled(enabled) => self.rewind.set_enabled(*enabled),\n            ConfigEvent::RewindInterval(interval) => self.rewind.set_interval(*interval),\n            ConfigEvent::RewindSeconds(seconds) => self.rewind.set_seconds(*seconds),\n            ConfigEvent::RunAhead(run_ahead) => self.run_ahead = *run_ahead,\n            ConfigEvent::MapperRevisions(revs) => {\n                self.control_deck.set_mapper_revisions(*revs);\n            }\n            ConfigEvent::SaveSlot(slot) => self.save_slot = *slot,\n            ConfigEvent::Speed(speed) => {\n                self.speed = *speed;\n                self.control_deck.set_frame_speed(*speed);\n            }\n            ConfigEvent::VideoFilter(filter) => self.control_deck.set_filter(*filter),\n            ConfigEvent::ZapperConnected(connected) => {\n                self.control_deck.connect_zapper(*connected);\n            }\n            _ => (),\n        }\n    }\n\n    fn update_frame_stats(&mut self) {\n        if !self.show_frame_stats {\n            return;\n        }\n\n        self.frame_time_diag\n            .push(self.last_frame_time.elapsed().as_secs_f32());\n        self.last_frame_time = Instant::now();\n        let frame_time = self.frame_time_diag.avg();\n        let frame_time_max = self\n            .frame_time_diag\n            .history()\n            .fold(-f32::INFINITY, |a, b| a.max(*b));\n        let mut fps = 1.0 / frame_time;\n        let mut fps_min = 1.0 / frame_time_max;\n        if !fps.is_finite() {\n            fps = 0.0;\n        }\n        if !fps_min.is_finite() {\n            fps_min = 0.0;\n        }\n        self.tx.event(RendererEvent::FrameStats(FrameStats {\n            timestamp: Instant::now(),\n            fps,\n            fps_min,\n            frame_time: frame_time * 1000.0,\n            frame_time_max: frame_time_max * 1000.0,\n            frame_count: self.frame_time_diag.frame_count,\n        }));\n    }\n\n    fn send_frame(&mut self) {\n        match self.frame_tx.try_send_ref() {\n            Ok(mut frame) => self.control_deck.frame_buffer_into(&mut frame),\n            Err(TrySendError::Full(_)) => trace!(\"dropped frame\"),\n            Err(_) => shutdown(&self.tx, \"failed to get frame\"),\n        }\n    }\n\n    fn set_run_state(&mut self, mode: RunState) {\n        if !self.control_deck.cpu_corrupted() {\n            self.run_state = mode;\n            if self.run_state.paused() {\n                if let Some(rom) = self.control_deck.loaded_rom()\n                    && let Err(err) = self.record.stop(&rom.name)\n                {\n                    self.on_error(err);\n                }\n            } else {\n                self.last_auto_save = Instant::now();\n                // To avoid having a large dip in frame stats when unpausing\n                self.last_frame_time = Instant::now();\n            }\n            self.audio.pause(self.run_state.paused());\n        }\n    }\n\n    fn save_state(&mut self, slot: u8, auto: bool) {\n        if let Some(rom) = self.control_deck.loaded_rom() {\n            let data_dir = Config::save_path(&rom.name, slot);\n            match self.control_deck.save_state(data_dir) {\n                Ok(_) => {\n                    if !auto {\n                        self.add_message(MessageType::Info, format!(\"State {slot} Saved\"));\n                    }\n                }\n                Err(err) => self.on_error(err),\n            }\n        }\n    }\n\n    fn load_state(&mut self, slot: u8) {\n        if let Some(rom) = self.control_deck.loaded_rom() {\n            let save_path = Config::save_path(&rom.name, slot);\n            match self.control_deck.load_state(save_path) {\n                Ok(_) => self.add_message(MessageType::Info, format!(\"State {slot} Loaded\")),\n                Err(control_deck::Error::NoSaveStateFound) => {\n                    self.add_message(MessageType::Warn, format!(\"State {slot} Not Found\"));\n                }\n                Err(err) => {\n                    self.on_error(err);\n                }\n            }\n        }\n    }\n\n    fn unload_rom(&mut self) {\n        if let Some(rom) = self.control_deck.loaded_rom() {\n            if self.auto_save {\n                let save_path = Config::save_path(&rom.name, self.save_slot);\n                if let Err(err) = self.control_deck.save_state(save_path) {\n                    self.on_error(err);\n                }\n            }\n            self.replay_record(false);\n            self.rewind.clear();\n            let _ = self.audio.stop();\n            if let Err(err) = self.control_deck.unload_rom() {\n                self.on_error(err);\n            }\n            self.tx.event(RendererEvent::RomUnloaded);\n            self.tx.event(RendererEvent::RequestRedraw {\n                viewport_id: ViewportId::ROOT,\n                when: Instant::now(),\n            });\n            self.frame_time_diag.reset();\n        }\n    }\n\n    fn on_load_rom(&mut self, rom: LoadedRom) {\n        if self.auto_load {\n            let save_path = Config::save_path(&rom.name, self.save_slot);\n            if let Err(err) = self.control_deck.load_state(save_path)\n                && !matches!(err, control_deck::Error::NoSaveStateFound)\n            {\n                error!(\"failed to load state: {err:?}\");\n            }\n        }\n        if let Err(err) = self.audio.start() {\n            self.tx.event(ConfigEvent::AudioEnabled(false));\n            self.on_error(err);\n        }\n        self.tx.event(RendererEvent::RomLoaded(rom));\n        self.tx.event(RendererEvent::RequestRedraw {\n            viewport_id: ViewportId::ROOT,\n            when: Instant::now(),\n        });\n        self.frame_time_diag.reset();\n        self.last_auto_save = Instant::now();\n        // To avoid having a large dip in frame stats after loading\n        self.last_frame_time = Instant::now();\n    }\n\n    fn load_rom_path(&mut self, path: impl AsRef<std::path::Path>) {\n        let path = path.as_ref();\n        self.unload_rom();\n        match self.control_deck.load_rom_path(path) {\n            Ok(rom) => self.on_load_rom(rom),\n            Err(err) => self.on_error(err),\n        }\n    }\n\n    fn load_rom(&mut self, name: &str, rom: &mut impl Read) {\n        self.unload_rom();\n        match self.control_deck.load_rom(name, rom) {\n            Ok(rom) => self.on_load_rom(rom),\n            Err(err) => self.on_error(err),\n        }\n    }\n\n    fn on_load_replay(&mut self, start: Cpu, name: impl AsRef<str>) {\n        self.add_message(\n            MessageType::Info,\n            format!(\"Loaded Replay Recording {:?}\", name.as_ref()),\n        );\n        self.control_deck.load_cpu(start);\n        self.tx.event(RendererEvent::ReplayLoaded);\n        self.tx.event(RendererEvent::RequestRedraw {\n            viewport_id: ViewportId::ROOT,\n            when: Instant::now(),\n        });\n    }\n\n    fn load_replay_path(&mut self, path: impl AsRef<Path>) {\n        let path = path.as_ref();\n        match self.replay.load_path(path) {\n            Ok(start) => self.on_load_replay(start, path.to_string_lossy()),\n            Err(err) => self.on_error(err),\n        }\n    }\n\n    fn load_replay(&mut self, name: &str, replay: &mut impl Read) {\n        match self.replay.load(replay) {\n            Ok(start) => self.on_load_replay(start, name),\n            Err(err) => self.on_error(err),\n        }\n    }\n\n    fn update_region(&mut self, region: NesRegion) {\n        self.target_frame_duration = FrameRate::from(region).duration();\n        self.frame_latency = (self.audio.latency.as_secs_f32()\n            / self.target_frame_duration.as_secs_f32())\n        .ceil() as usize;\n    }\n\n    fn audio_record(&mut self, recording: bool) {\n        if self.control_deck.is_running() {\n            if !recording && self.audio.is_recording() {\n                match self.audio.stop_recording() {\n                    Ok(Some(filename)) => {\n                        self.add_message(\n                            MessageType::Info,\n                            format!(\"Saved Replay Recording {filename:?}\"),\n                        );\n                    }\n                    Err(err) => self.on_error(err),\n                    _ => (),\n                }\n            } else if recording && let Err(err) = self.audio.start_recording() {\n                self.on_error(err);\n            }\n        }\n    }\n\n    fn replay_record(&mut self, recording: bool) {\n        if self.control_deck.is_running() {\n            if recording {\n                self.record.start(self.control_deck.cpu().clone());\n            } else if let Some(rom) = self.control_deck.loaded_rom() {\n                match self.record.stop(&rom.name) {\n                    Ok(Some(filename)) => {\n                        self.add_message(\n                            MessageType::Info,\n                            format!(\"Saved Replay Recording {filename:?}\"),\n                        );\n                    }\n                    Err(err) => self.on_error(err),\n                    _ => (),\n                }\n            }\n        }\n    }\n\n    fn save_screenshot(&mut self) -> anyhow::Result<PathBuf> {\n        let picture_dir = Config::default_picture_dir();\n        let filename = picture_dir\n            .join(\n                Local::now()\n                    .format(\"screenshot_%Y-%m-%d_at_%H_%M_%S\")\n                    .to_string(),\n            )\n            .with_extension(\"png\");\n        let image = image::ImageBuffer::<image::Rgba<u8>, &[u8]>::from_raw(\n            u32::from(ppu::size::WIDTH),\n            u32::from(ppu::size::HEIGHT),\n            self.control_deck.frame_buffer(),\n        )\n        .ok_or_else(|| anyhow!(\"failed to create image buffer\"))?;\n\n        if !picture_dir.exists() {\n            std::fs::create_dir_all(&picture_dir)\n                .with_context(|| format!(\"failed to create screenshot dir: {picture_dir:?}\"))?;\n        }\n\n        // TODO: provide wasm download\n        image\n            .save(&filename)\n            .map(|_| filename.clone())\n            .with_context(|| format!(\"failed to save screenshot: {filename:?}\"))\n    }\n\n    fn park_duration(&self) -> Option<Duration> {\n        let park_epsilon = Duration::from_millis(1);\n        // Park if we're paused, occluded, or not running\n        let duration = if self.run_state.paused() || !self.control_deck.is_running() {\n            Some(self.target_frame_duration - park_epsilon)\n        } else if self.rewinding || !self.audio.enabled() {\n            (self.clock_time_accumulator < self.target_frame_duration.as_secs_f32()).then(|| {\n                Duration::from_secs_f32(\n                    self.target_frame_duration.as_secs_f32() - self.clock_time_accumulator,\n                )\n                .saturating_sub(park_epsilon)\n            })\n        } else {\n            (self.audio.queued_time() > self.audio.latency)\n                // Even though we just did a comparison, audio is still being consumed so this\n                // could underflow\n                .then(|| self.audio.queued_time().saturating_sub(self.audio.latency))\n        };\n        duration.map(|duration| {\n            // Parking thread is only required for Multi-threaded emulation to save CPU cycles.\n            if self.threaded {\n                duration\n            } else {\n                Duration::ZERO\n            }\n        })\n    }\n\n    fn try_clock_frame(&mut self) {\n        let last_clock_duration = self.last_clock_time.elapsed();\n        self.last_clock_time = Instant::now();\n        self.clock_time_accumulator += last_clock_duration.as_secs_f32();\n        if self.clock_time_accumulator > 0.02 {\n            self.clock_time_accumulator = 0.02;\n        }\n\n        // If any frames are still pending, request a redraw\n        if !self.frame_tx.is_empty() {\n            self.tx.event(RendererEvent::RequestRedraw {\n                viewport_id: ViewportId::ROOT,\n                when: Instant::now(),\n            });\n        }\n\n        if let Some(park_timeout) = self.park_duration() {\n            thread::park_timeout(park_timeout);\n            return;\n        }\n\n        if self.rewinding {\n            match self.rewind.pop() {\n                Some(cpu) => {\n                    self.control_deck.load_cpu(cpu);\n                    self.send_frame();\n                    self.update_frame_stats();\n                }\n                None => self.rewinding = false,\n            }\n        } else {\n            if let Some(event) = self.replay.next(self.control_deck.frame_number()) {\n                self.on_emulation_event(&event);\n            }\n\n            let run_ahead = if self.speed > 1.0 { 0 } else { self.run_ahead };\n            let res =\n                self.control_deck\n                    .clock_frame_ahead(run_ahead, |frame_buffer, audio_samples| {\n                        self.audio.process(audio_samples);\n                        match self.frame_tx.try_send_ref() {\n                            Ok(mut frame) => {\n                                frame.clear();\n                                frame.extend_from_slice(frame_buffer);\n                            }\n                            Err(TrySendError::Full(_)) => debug!(\"dropped frame\"),\n                            Err(_) => shutdown(&self.tx, \"failed to get frame\"),\n                        }\n                    });\n            match res {\n                Ok(()) => {\n                    self.update_frame_stats();\n                    if let Err(err) = self.rewind.push(self.control_deck.cpu()) {\n                        self.rewind.set_enabled(false);\n                        self.on_error(err);\n                    }\n                    if self.auto_save && self.last_auto_save.elapsed() > self.auto_save_interval {\n                        self.last_auto_save = Instant::now();\n                        self.save_state(self.save_slot, true);\n                    }\n                }\n                Err(err) => self.on_error(err),\n            }\n        }\n\n        self.clock_time_accumulator -= self.target_frame_duration.as_secs_f32();\n        // Request to draw this frame\n        self.tx.event(RendererEvent::RequestRedraw {\n            viewport_id: ViewportId::ROOT,\n            when: Instant::now(),\n        });\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/event.rs",
    "content": "use crate::{\n    feature,\n    nes::{\n        Nes, RunState, Running, State,\n        action::{Action, Debug, DebugKind, DebugStep, Feature, Setting, Ui},\n        config::{Config, RecentRom},\n        emulation::FrameStats,\n        input::{ActionBindings, AxisDirection, Gamepads, Input, InputBindings},\n        renderer::{\n            gui::{Menu, MessageType},\n            shader::Shader,\n        },\n        rom::RomData,\n    },\n    platform::open_file_dialog,\n};\nuse anyhow::anyhow;\nuse egui::ViewportId;\nuse std::{path::PathBuf, sync::Arc};\nuse tetanes_core::{\n    action::Action as DeckAction,\n    apu::{Apu, Channel},\n    common::{NesRegion, ResetKind},\n    control_deck::{LoadedRom, MapperRevisionsConfig},\n    cpu::instr::InstrRef,\n    debug::Debugger,\n    genie::GenieCode,\n    input::{FourPlayer, JoypadBtn, Player},\n    mem::RamState,\n    ppu::Ppu,\n    time::{Duration, Instant},\n    video::VideoFilter,\n};\nuse tracing::{debug, error, trace, warn};\nuse uuid::Uuid;\nuse winit::{\n    application::ApplicationHandler,\n    event::{DeviceEvent, DeviceId, ElementState, WindowEvent},\n    event_loop::{ActiveEventLoop, ControlFlow, DeviceEvents, EventLoop, EventLoopProxy},\n    keyboard::PhysicalKey,\n    window::WindowId,\n};\n\n#[derive(Default, Debug, Copy, Clone)]\n#[must_use]\npub struct Response {\n    pub consumed: bool,\n    pub repaint: bool,\n}\n\n#[derive(Debug, Clone)]\npub struct NesEventProxy(EventLoopProxy<NesEvent>);\n\nimpl NesEventProxy {\n    pub fn new(event_loop: &EventLoop<NesEvent>) -> Self {\n        Self(event_loop.create_proxy())\n    }\n\n    pub fn event(&self, event: impl Into<NesEvent>) {\n        let event = event.into();\n        trace!(\"sending event: {event:?}\");\n        if self.0.send_event(event).is_err() {\n            warn!(\"event loop closed\");\n        }\n    }\n\n    pub const fn inner(&self) -> &EventLoopProxy<NesEvent> {\n        &self.0\n    }\n}\n\n#[derive(Debug, Clone)]\n#[must_use]\npub enum NesEvent {\n    // For some reason accesskit_winit::Event isn't Clone\n    #[cfg(not(target_arch = \"wasm32\"))]\n    AccessKit {\n        window_id: WindowId,\n        event: AccessKitWindowEvent,\n    },\n    Config(ConfigEvent),\n    Debug(DebugEvent),\n    Emulation(EmulationEvent),\n    Renderer(RendererEvent),\n    Ui(UiEvent),\n}\n\n#[cfg(not(target_arch = \"wasm32\"))]\n#[derive(Debug, Clone)]\npub enum AccessKitWindowEvent {\n    InitialTreeRequested,\n    ActionRequested(accesskit::ActionRequest),\n    AccessibilityDeactivated,\n}\n\n#[cfg(not(target_arch = \"wasm32\"))]\nimpl From<accesskit_winit::Event> for NesEvent {\n    fn from(event: accesskit_winit::Event) -> Self {\n        use accesskit_winit::WindowEvent;\n        Self::AccessKit {\n            window_id: event.window_id,\n            event: match event.window_event {\n                WindowEvent::InitialTreeRequested => AccessKitWindowEvent::InitialTreeRequested,\n                WindowEvent::ActionRequested(request) => {\n                    AccessKitWindowEvent::ActionRequested(request)\n                }\n                WindowEvent::AccessibilityDeactivated => {\n                    AccessKitWindowEvent::AccessibilityDeactivated\n                }\n            },\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq)]\n#[must_use]\npub enum ConfigEvent {\n    ActionBindings(Vec<ActionBindings>),\n    ActionBindingSet((Action, Input, usize)),\n    ActionBindingClear(Input),\n    AlwaysOnTop(bool),\n    ApuChannelEnabled((Channel, bool)),\n    ApuChannelsEnabled([bool; Apu::MAX_CHANNEL_COUNT]),\n    AudioBuffer(usize),\n    AudioEnabled(bool),\n    AudioLatency(Duration),\n    AutoLoad(bool),\n    AutoSave(bool),\n    AutoSaveInterval(Duration),\n    ConcurrentDpad(bool),\n    DarkTheme(bool),\n    EmbedViewports(bool),\n    EmulatePpuWarmup(bool),\n    FourPlayer(FourPlayer),\n    Fullscreen(bool),\n    GamepadAssign((Player, Uuid)),\n    GamepadAssignments([(Player, Option<Uuid>); 4]),\n    GamepadUnassign(Player),\n    GenieCodeAdded(GenieCode),\n    GenieCodeClear,\n    GenieCodeRemoved(String),\n    HideOverscan(bool),\n    MapperRevisions(MapperRevisionsConfig),\n    RamState(RamState),\n    RecentRomsClear,\n    Region(NesRegion),\n    RewindEnabled(bool),\n    RewindInterval(u32),\n    RewindSeconds(u32),\n    RunAhead(usize),\n    SaveSlot(u8),\n    Scale(f32),\n    Shader(Shader),\n    ShowMenubar(bool),\n    ShowMessages(bool),\n    ShowUpdates(bool),\n    Speed(f32),\n    VideoFilter(VideoFilter),\n    ZapperConnected(bool),\n}\n\nimpl From<ConfigEvent> for NesEvent {\n    fn from(event: ConfigEvent) -> Self {\n        Self::Config(event)\n    }\n}\n\n#[derive(Debug, Clone)]\n#[must_use]\npub enum DebugEvent {\n    Ppu(Box<Ppu>),\n}\n\nimpl From<DebugEvent> for NesEvent {\n    fn from(event: DebugEvent) -> Self {\n        Self::Debug(event)\n    }\n}\n\n#[derive(Debug, Clone, PartialEq)]\n#[must_use]\npub enum EmulationEvent {\n    AddDebugger(Debugger),\n    RemoveDebugger(Debugger),\n    AudioRecord(bool),\n    CpuCorrupted { instr: InstrRef },\n    DebugStep(DebugStep),\n    InstantRewind,\n    Joypad((Player, JoypadBtn, ElementState)),\n    LoadReplay((String, ReplayData)),\n    LoadReplayPath(PathBuf),\n    LoadRom((String, RomData)),\n    LoadRomPath(PathBuf),\n    LoadState(u8),\n    RunState(RunState),\n    ReplayRecord(bool),\n    Reset(ResetKind),\n    RequestFrame,\n    Rewinding(bool),\n    SaveState(u8),\n    ShowFrameStats(bool),\n    Screenshot,\n    UnloadRom,\n    ZapperAim((u16, u16)),\n    ZapperTrigger,\n}\n\nimpl From<EmulationEvent> for NesEvent {\n    fn from(event: EmulationEvent) -> Self {\n        Self::Emulation(event)\n    }\n}\n\n#[derive(Debug, Clone)]\n#[must_use]\npub enum RendererEvent {\n    ViewportResized((f32, f32)),\n    FrameStats(FrameStats),\n    ShowMenubar(bool),\n    ToggleFullscreen,\n    ReplayLoaded,\n    ResizeTexture,\n    ResizeWindow,\n    ResourcesReady,\n    RequestRedraw {\n        viewport_id: ViewportId,\n        when: Instant,\n    },\n    RomLoaded(LoadedRom),\n    RomUnloaded,\n    Menu(Menu),\n}\n\nimpl From<RendererEvent> for NesEvent {\n    fn from(event: RendererEvent) -> Self {\n        Self::Renderer(event)\n    }\n}\n\n#[derive(Debug, Clone, PartialEq)]\n#[must_use]\npub enum UiEvent {\n    Error(String),\n    Message((MessageType, String)),\n    UpdateAvailable(String),\n    LoadRomDialog,\n    LoadReplayDialog,\n    FileDialogCancelled,\n    Terminate,\n}\n\nimpl From<UiEvent> for NesEvent {\n    fn from(event: UiEvent) -> Self {\n        Self::Ui(event)\n    }\n}\n\n#[derive(Clone, PartialEq)]\npub struct ReplayData(pub Vec<u8>);\n\nimpl std::fmt::Debug for ReplayData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"ReplayData({} bytes)\", self.0.len())\n    }\n}\n\nimpl AsRef<[u8]> for ReplayData {\n    fn as_ref(&self) -> &[u8] {\n        &self.0\n    }\n}\n\nimpl ApplicationHandler<NesEvent> for Nes {\n    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: NesEvent) {\n        if self.state.is_exiting() {\n            return;\n        }\n\n        trace!(\"user event: {event:?}\");\n\n        match event {\n            NesEvent::Renderer(RendererEvent::ResourcesReady) => {\n                if let Err(err) = self.init_running(event_loop) {\n                    error!(\"failed to create window: {err:?}\");\n                    event_loop.exit();\n                    return;\n                }\n\n                // Disable device events to save some cpu as they're mostly duplicated in\n                // WindowEvents\n                event_loop.listen_device_events(DeviceEvents::Never);\n\n                if let State::Running(state) = &mut self.state\n                    && let Some(window) = state.renderer.root_window()\n                {\n                    if window.is_visible().unwrap_or(true) {\n                        state.repaint_times.insert(window.id(), Instant::now());\n                    } else {\n                        // Immediately redraw the root window on start if not\n                        // visible. Fixes a bug where `window.request_redraw()` events\n                        // may not be sent if the window isn't visible, which is the\n                        // case until the first frame is drawn.\n                        if let Err(err) = state.renderer.redraw(\n                            window.id(),\n                            event_loop,\n                            &mut state.gamepads,\n                            &mut state.cfg,\n                        ) {\n                            state.renderer.on_error(err);\n                        }\n                    }\n                }\n            }\n            NesEvent::Ui(UiEvent::Terminate) => event_loop.exit(),\n            _ => (),\n        }\n\n        if let State::Running(state) = &mut self.state {\n            state.user_event(event_loop, event);\n        }\n    }\n\n    fn resumed(&mut self, event_loop: &ActiveEventLoop) {\n        if self.state.is_exiting() {\n            return;\n        }\n\n        debug!(\"resumed event\");\n\n        if let State::Running(state) = &mut self.state {\n            if feature!(Suspend) {\n                state.renderer.recreate_window(event_loop);\n            }\n            if let Some(window_id) = state.renderer.root_window_id() {\n                state.repaint_times.insert(window_id, Instant::now());\n            }\n        } else if let State::Suspended { should_terminate } = &self.state\n            && let Err(err) =\n                self.request_renderer_resources(event_loop, Arc::clone(should_terminate))\n        {\n            error!(\"failed to request renderer resources: {err:?}\");\n            event_loop.exit();\n        }\n    }\n\n    fn window_event(\n        &mut self,\n        event_loop: &ActiveEventLoop,\n        window_id: WindowId,\n        event: WindowEvent,\n    ) {\n        if self.state.is_exiting() {\n            return;\n        }\n\n        trace!(\"window event: {window_id:?} {event:?}\");\n\n        if let State::Running(state) = &mut self.state {\n            state.window_event(event_loop, window_id, event);\n        }\n    }\n\n    fn device_event(\n        &mut self,\n        event_loop: &ActiveEventLoop,\n        device_id: DeviceId,\n        event: DeviceEvent,\n    ) {\n        if self.state.is_exiting() {\n            return;\n        }\n\n        trace!(\"device event: {device_id:?} {event:?}\");\n\n        if let State::Running(state) = &mut self.state {\n            state.device_event(event_loop, device_id, event);\n        }\n    }\n\n    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {\n        if self.state.is_exiting() {\n            return;\n        } else if self.should_terminate() {\n            event_loop.exit();\n        }\n\n        if let State::Running(state) = &mut self.state {\n            state.about_to_wait(event_loop);\n        }\n    }\n\n    fn suspended(&mut self, event_loop: &ActiveEventLoop) {\n        if self.state.is_exiting() {\n            return;\n        }\n\n        debug!(\"suspended event\");\n\n        if let State::Running(state) = &mut self.state {\n            state.suspended(event_loop);\n        }\n    }\n\n    fn exiting(&mut self, event_loop: &ActiveEventLoop) {\n        if self.state.is_exiting() {\n            return;\n        }\n\n        debug!(\"exiting\");\n\n        if let State::Running(state) = &mut self.state {\n            state.exiting(event_loop);\n        } else if feature!(AbortOnExit) {\n            panic!(\"exited unexpectedly\");\n        }\n        self.state = State::Exiting;\n    }\n}\n\nimpl ApplicationHandler<NesEvent> for Running {\n    fn user_event(&mut self, _event_loop: &ActiveEventLoop, mut event: NesEvent) {\n        match event {\n            NesEvent::Config(ref event) => {\n                let Config {\n                    deck,\n                    emulation,\n                    audio,\n                    renderer,\n                    input,\n                } = &mut self.cfg;\n                match event {\n                    ConfigEvent::ActionBindings(bindings) => {\n                        input.action_bindings.clone_from(bindings);\n                        self.input_bindings = InputBindings::from_input_config(input);\n                    }\n                    ConfigEvent::ActionBindingSet((action, set_input, binding)) => {\n                        input.set_binding(*action, *set_input, *binding);\n                        self.input_bindings.insert(*set_input, *action);\n                    }\n                    ConfigEvent::ActionBindingClear(clear_input) => {\n                        input.clear_binding(*clear_input);\n                        self.input_bindings.remove(clear_input);\n                    }\n                    ConfigEvent::AlwaysOnTop(always_on_top) => {\n                        renderer.always_on_top = *always_on_top;\n                        self.renderer\n                            .set_always_on_top(self.cfg.renderer.always_on_top);\n                    }\n                    ConfigEvent::ApuChannelEnabled((channel, enabled)) => {\n                        deck.channels_enabled[*channel as usize] = *enabled;\n                    }\n                    ConfigEvent::ApuChannelsEnabled(enabled) => {\n                        deck.channels_enabled = *enabled;\n                    }\n                    ConfigEvent::AudioBuffer(buffer_size) => {\n                        audio.buffer_size = *buffer_size;\n                    }\n                    ConfigEvent::AudioEnabled(enabled) => audio.enabled = *enabled,\n                    ConfigEvent::AudioLatency(latency) => audio.latency = *latency,\n                    ConfigEvent::AutoLoad(enabled) => emulation.auto_load = *enabled,\n                    ConfigEvent::AutoSave(enabled) => emulation.auto_save = *enabled,\n                    ConfigEvent::AutoSaveInterval(interval) => {\n                        emulation.auto_save_interval = *interval;\n                    }\n                    ConfigEvent::ConcurrentDpad(enabled) => deck.concurrent_dpad = *enabled,\n                    ConfigEvent::DarkTheme(enabled) => renderer.dark_theme = *enabled,\n                    ConfigEvent::EmbedViewports(embed) => renderer.embed_viewports = *embed,\n                    ConfigEvent::EmulatePpuWarmup(enabled) => deck.emulate_ppu_warmup = *enabled,\n                    ConfigEvent::FourPlayer(four_player) => deck.four_player = *four_player,\n                    ConfigEvent::Fullscreen(fullscreen) => renderer.fullscreen = *fullscreen,\n                    ConfigEvent::GamepadAssign((player, uuid)) => {\n                        input.assign_gamepad(*player, *uuid);\n                        if let Some(name) = self.gamepads.gamepad_name_by_uuid(uuid) {\n                            self.tx.event(UiEvent::Message((\n                                MessageType::Info,\n                                format!(\"Assigned gamepad `{name}` to player {player:?}.\",),\n                            )));\n                        }\n                    }\n                    ConfigEvent::GamepadUnassign(player) => {\n                        if let Some(uuid) = input.unassign_gamepad(*player)\n                            && let Some(name) = self.gamepads.gamepad_name_by_uuid(&uuid)\n                        {\n                            self.tx.event(UiEvent::Message((\n                                MessageType::Info,\n                                format!(\"Unassigned gamepad `{name}` from player {player:?}.\"),\n                            )));\n                        }\n                    }\n                    ConfigEvent::GamepadAssignments(assignments) => {\n                        input.gamepad_assignments = *assignments;\n                    }\n                    ConfigEvent::GenieCodeAdded(genie_code) => {\n                        deck.genie_codes.push(genie_code.clone());\n                    }\n                    ConfigEvent::GenieCodeClear => deck.genie_codes.clear(),\n                    ConfigEvent::GenieCodeRemoved(code) => {\n                        deck.genie_codes.retain(|genie| genie.code() != code);\n                    }\n                    ConfigEvent::HideOverscan(hide) => renderer.hide_overscan = *hide,\n                    ConfigEvent::MapperRevisions(revs) => deck.mapper_revisions = *revs,\n                    ConfigEvent::RamState(ram_state) => deck.ram_state = *ram_state,\n                    ConfigEvent::RecentRomsClear => renderer.recent_roms.clear(),\n                    ConfigEvent::Region(region) => deck.region = *region,\n                    ConfigEvent::RewindEnabled(enabled) => emulation.rewind = *enabled,\n                    ConfigEvent::RewindInterval(interval) => {\n                        emulation.rewind_interval = *interval;\n                    }\n                    ConfigEvent::RewindSeconds(seconds) => {\n                        emulation.rewind_seconds = *seconds;\n                    }\n                    ConfigEvent::RunAhead(run_ahead) => emulation.run_ahead = *run_ahead,\n                    ConfigEvent::SaveSlot(slot) => emulation.save_slot = *slot,\n                    ConfigEvent::Scale(scale) => renderer.scale = *scale,\n                    ConfigEvent::Shader(shader) => renderer.shader = *shader,\n                    ConfigEvent::ShowMenubar(show) => renderer.show_menubar = *show,\n                    ConfigEvent::ShowMessages(show) => renderer.show_messages = *show,\n                    ConfigEvent::ShowUpdates(show) => renderer.show_updates = *show,\n                    ConfigEvent::Speed(speed) => emulation.speed = *speed,\n                    ConfigEvent::VideoFilter(filter) => deck.filter = *filter,\n                    ConfigEvent::ZapperConnected(connected) => deck.zapper = *connected,\n                }\n                // Root viewport needs repainting when Config changes to update deferred viewports\n                if let Some(window_id) = self.renderer.root_window_id() {\n                    self.repaint_times.insert(window_id, Instant::now());\n                }\n            }\n            NesEvent::Renderer(RendererEvent::RequestRedraw { viewport_id, when }) => {\n                if let Some(window_id) = self.renderer.window_id_for_viewport(viewport_id) {\n                    self.repaint_times.insert(\n                        window_id,\n                        self.repaint_times\n                            .get(&window_id)\n                            .map_or(when, |last| (*last).min(when)),\n                    );\n                }\n            }\n            NesEvent::Emulation(EmulationEvent::LoadRom((ref name, _))) => {\n                self.cfg\n                    .add_recent_rom(RecentRom::Homebrew { name: name.clone() });\n            }\n            NesEvent::Ui(ref event) => self.on_ui_event(event),\n            _ => (),\n        }\n\n        // Only wake emulation of relevant events\n        if matches!(event, NesEvent::Emulation(_) | NesEvent::Config(_)) {\n            self.emulation.on_event(&event);\n        }\n        self.renderer.on_event(&mut event, &self.cfg);\n    }\n\n    fn resumed(&mut self, _event_loop: &ActiveEventLoop) {}\n\n    fn window_event(\n        &mut self,\n        event_loop: &ActiveEventLoop,\n        window_id: WindowId,\n        event: WindowEvent,\n    ) {\n        let res = self.renderer.on_window_event(window_id, &event);\n        if res.repaint && event != WindowEvent::RedrawRequested {\n            self.repaint_times.insert(window_id, Instant::now());\n        }\n\n        if !res.consumed {\n            match event {\n                WindowEvent::RedrawRequested => {\n                    self.emulation.try_clock_frame();\n\n                    if let Err(err) = self.renderer.redraw(\n                        window_id,\n                        event_loop,\n                        &mut self.gamepads,\n                        &mut self.cfg,\n                    ) {\n                        self.renderer.on_error(err);\n                    }\n                    self.repaint_times.remove(&window_id);\n                }\n                WindowEvent::Resized(_) if Some(window_id) == self.renderer.root_window_id() => {\n                    self.cfg.renderer.fullscreen = self.renderer.fullscreen();\n                }\n                WindowEvent::Focused(focused) => {\n                    if focused && !self.occluded {\n                        self.repaint_times.insert(window_id, Instant::now());\n                        if self.renderer.rom_loaded() && self.run_state().auto_paused() {\n                            self.set_run_state(RunState::Running);\n                        }\n                    } else {\n                        let time_since_last_save = Instant::now() - self.renderer.last_save_time;\n                        if time_since_last_save > Duration::from_secs(30)\n                            && let Err(err) = self.renderer.save(&self.cfg)\n                        {\n                            error!(\"failed to save rendererer state: {err:?}\");\n                        }\n                        if self\n                            .renderer\n                            .window(window_id)\n                            .and_then(|win| win.is_minimized())\n                            .unwrap_or(false)\n                        {\n                            self.repaint_times.remove(&window_id);\n                            if self.renderer.rom_loaded() && !self.run_state().paused() {\n                                self.set_run_state(RunState::AutoPaused);\n                            }\n                        }\n                    }\n                }\n                WindowEvent::Occluded(occluded) => {\n                    self.occluded = occluded;\n                    // Note: Does not trigger on all platforms (e.g. linux)\n                    if occluded {\n                        self.repaint_times.remove(&window_id);\n                        if self.renderer.rom_loaded() && !self.run_state().paused() {\n                            self.set_run_state(RunState::AutoPaused);\n                        }\n                    } else {\n                        self.repaint_times.insert(window_id, Instant::now());\n                        if self.renderer.rom_loaded() && self.run_state().auto_paused() {\n                            self.set_run_state(RunState::Running);\n                        }\n                    }\n                }\n                WindowEvent::KeyboardInput {\n                    event,\n                    is_synthetic,\n                    ..\n                } => {\n                    // Winit generates fake \"synthetic\" KeyboardInput events when the focus\n                    // is changed to the window, or away from it. Synthetic key presses\n                    // represent no real key presses and should be ignored.\n                    // See https://github.com/rust-windowing/winit/issues/3543\n                    if (!is_synthetic || event.state != ElementState::Pressed)\n                        && let PhysicalKey::Code(key) = event.physical_key\n                    {\n                        self.on_input(\n                            window_id,\n                            Input::Key(key, self.modifiers.state()),\n                            event.state,\n                            event.repeat,\n                        );\n                    }\n                }\n                WindowEvent::ModifiersChanged(modifiers) => {\n                    self.modifiers = modifiers;\n                }\n                WindowEvent::MouseInput { button, state, .. } => {\n                    self.on_input(window_id, Input::Mouse(button), state, false);\n                }\n                WindowEvent::DroppedFile(path)\n                    if Some(window_id) == self.renderer.root_window_id() =>\n                {\n                    self.event(EmulationEvent::LoadRomPath(path));\n                }\n                _ => (),\n            }\n        }\n    }\n\n    fn device_event(\n        &mut self,\n        _event_loop: &ActiveEventLoop,\n        _device_id: DeviceId,\n        event: DeviceEvent,\n    ) {\n        if let DeviceEvent::MouseMotion { delta } = event {\n            self.renderer.on_mouse_motion(delta);\n        }\n    }\n\n    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {\n        self.gamepads.update_events();\n        if let Some(window_id) = self.renderer.root_window_id() {\n            let res = self.renderer.on_gamepad_update(&self.gamepads);\n            self.gamepads.set_ui_consumes(res.consumed);\n            if res.repaint {\n                self.repaint_times.insert(window_id, Instant::now());\n            }\n            if res.consumed {\n                let now = Instant::now();\n                for wid in self.renderer.all_window_ids() {\n                    self.repaint_times.insert(wid, now);\n                }\n            } else {\n                while let Some(event) = self.gamepads.next_event() {\n                    self.on_gamepad_event(window_id, event);\n                    self.repaint_times.insert(window_id, Instant::now());\n                }\n            }\n            // Always repaint when single threaded\n            if !self.cfg.emulation.threaded {\n                self.repaint_times.insert(window_id, Instant::now());\n            }\n        }\n\n        self.update_repaint_times(event_loop);\n    }\n\n    fn suspended(&mut self, event_loop: &ActiveEventLoop) {\n        if feature!(Suspend)\n            && let Err(err) = self.renderer.drop_window()\n        {\n            error!(\"failed to suspend window: {err:?}\");\n            event_loop.exit();\n        }\n    }\n\n    fn exiting(&mut self, _event_loop: &ActiveEventLoop) {\n        if let Err(err) = self.renderer.save(&self.cfg) {\n            error!(\"failed to save rendererer state: {err:?}\");\n        }\n        self.emulation.terminate();\n        self.renderer.destroy();\n\n        if feature!(AbortOnExit) {\n            panic!(\"exited unexpectedly\");\n        }\n    }\n\n    fn memory_warning(&mut self, _event_loop: &ActiveEventLoop) {\n        self.renderer\n            .add_message(MessageType::Warn, \"Your system memory is running low...\");\n        if self.cfg.emulation.rewind {\n            self.cfg.emulation.rewind = false;\n            self.event(ConfigEvent::RewindEnabled(false));\n        }\n    }\n}\n\nimpl Running {\n    fn run_state(&self) -> RunState {\n        self.renderer.gui.borrow().run_state\n    }\n\n    fn set_run_state(&mut self, state: RunState) {\n        self.renderer.gui.borrow_mut().run_state = state;\n        self.event(EmulationEvent::RunState(state));\n    }\n\n    pub fn update_repaint_times(&mut self, event_loop: &ActiveEventLoop) {\n        let mut next_repaint_time = self.repaint_times.values().min().copied();\n        self.repaint_times.retain(|window_id, when| {\n            if *when > Instant::now() {\n                return true;\n            }\n            next_repaint_time = None;\n\n            if let Some(window) = self.renderer.window(*window_id) {\n                if !window.is_minimized().unwrap_or(false) {\n                    window.request_redraw();\n                }\n                // Repaint time will get removed as soon as we receive the RequestRedraw event\n                true\n            } else {\n                false\n            }\n        });\n\n        event_loop.set_control_flow(ControlFlow::WaitUntil(match next_repaint_time {\n            Some(next_repaint_time) => next_repaint_time,\n            None => Instant::now() + Duration::from_millis(16),\n        }));\n    }\n\n    pub fn on_ui_event(&mut self, event: &UiEvent) {\n        match event {\n            UiEvent::Message((ty, msg)) => self.renderer.add_message(*ty, msg),\n            UiEvent::Error(err) => self.renderer.on_error(anyhow!(err.clone())),\n            UiEvent::LoadRomDialog => {\n                match open_file_dialog(\n                    \"Load ROM\",\n                    \"NES ROMs\",\n                    &[\"nes\"],\n                    self.cfg.renderer.roms_path.as_ref(),\n                ) {\n                    Ok(maybe_path) => {\n                        if let Some(path) = maybe_path {\n                            self.event(EmulationEvent::LoadRomPath(path));\n                        }\n                    }\n                    Err(err) => {\n                        error!(\"failed to open rom dialog: {err:?}\");\n                        self.event(UiEvent::Error(\"failed to open rom dialog\".to_string()));\n                    }\n                }\n            }\n            UiEvent::LoadReplayDialog => {\n                match open_file_dialog(\n                    \"Load Replay\",\n                    \"Replay Recording\",\n                    &[\"replay\"],\n                    Some(Config::default_data_dir()),\n                ) {\n                    Ok(maybe_path) => {\n                        if let Some(path) = maybe_path {\n                            self.event(EmulationEvent::LoadReplayPath(path));\n                        }\n                    }\n                    Err(err) => {\n                        error!(\"failed to open replay dialog: {err:?}\");\n                        self.event(UiEvent::Error(\"failed to open replay dialog\".to_string()));\n                    }\n                }\n            }\n            UiEvent::FileDialogCancelled => {\n                if self.renderer.rom_loaded() && self.run_state().auto_paused() {\n                    self.set_run_state(RunState::Running);\n                }\n            }\n            UiEvent::UpdateAvailable(_) | UiEvent::Terminate => (),\n        }\n    }\n\n    /// Trigger a custom event.\n    pub fn event(&mut self, event: impl Into<NesEvent>) {\n        let mut event = event.into();\n        trace!(\"Nes event: {event:?}\");\n\n        self.emulation.on_event(&event);\n        self.renderer.on_event(&mut event, &self.cfg);\n        match event {\n            NesEvent::Ui(event) => self.on_ui_event(&event),\n            NesEvent::Emulation(event) => match event {\n                EmulationEvent::LoadRomPath(path) => {\n                    if let Ok(path) = path.canonicalize() {\n                        self.cfg.add_recent_rom(RecentRom::Path(path));\n                    }\n                }\n                EmulationEvent::Reset(_) => self.set_run_state(RunState::Running),\n                _ => (),\n            },\n            _ => (),\n        }\n    }\n\n    /// Handle gamepad event.\n    pub fn on_gamepad_event(&mut self, window_id: WindowId, event: gilrs::Event) {\n        use gilrs::EventType;\n\n        // Connect first because we may not have a name set yet\n        if event.event == EventType::Connected {\n            self.gamepads.connect(event.id);\n        }\n\n        if let Some(uuid) = self.gamepads.gamepad_uuid(event.id) {\n            match event.event {\n                EventType::ButtonPressed(button, _) => {\n                    if let Some(player) = self.cfg.input.gamepad_assignment(&uuid) {\n                        self.on_input(\n                            window_id,\n                            Input::Button(player, button),\n                            ElementState::Pressed,\n                            false,\n                        );\n                    }\n                }\n                EventType::ButtonRepeated(button, _) => {\n                    if let Some(player) = self.cfg.input.gamepad_assignment(&uuid) {\n                        self.on_input(\n                            window_id,\n                            Input::Button(player, button),\n                            ElementState::Pressed,\n                            true,\n                        );\n                    }\n                }\n                EventType::ButtonReleased(button, _) => {\n                    if let Some(player) = self.cfg.input.gamepad_assignment(&uuid) {\n                        self.on_input(\n                            window_id,\n                            Input::Button(player, button),\n                            ElementState::Released,\n                            false,\n                        );\n                    }\n                }\n                EventType::AxisChanged(axis, value, _) => {\n                    if let Some(player) = self.cfg.input.gamepad_assignment(&uuid) {\n                        if let (Some(direction), state) = Gamepads::axis_state(value) {\n                            self.on_input(\n                                window_id,\n                                Input::Axis(player, axis, direction),\n                                state,\n                                false,\n                            );\n                        } else {\n                            for direction in [AxisDirection::Positive, AxisDirection::Negative] {\n                                self.on_input(\n                                    window_id,\n                                    Input::Axis(player, axis, direction),\n                                    ElementState::Released,\n                                    false,\n                                );\n                            }\n                        }\n                    }\n                }\n                EventType::Connected => {\n                    let saved_assignment = self.cfg.input.gamepad_assignment(&uuid);\n                    if let Some(player) =\n                        saved_assignment.or_else(|| self.cfg.input.next_gamepad_unassigned())\n                        && let Some(name) = self.gamepads.gamepad_name_by_uuid(&uuid)\n                    {\n                        self.renderer.add_message(\n                            MessageType::Info,\n                            format!(\"Assigned gamepad `{name}` to player {player:?}.\"),\n                        );\n                        self.cfg.input.assign_gamepad(player, uuid);\n                    }\n                }\n                EventType::Disconnected => {\n                    self.gamepads.disconnect(event.id);\n                    if let Some(player) = self.cfg.input.unassign_gamepad_name(&uuid)\n                        && let Some(name) = self.gamepads.gamepad_name_by_uuid(&uuid)\n                    {\n                        self.renderer.add_message(\n                            MessageType::Info,\n                            format!(\"Unassigned gamepad `{name}` from player {player:?}.\"),\n                        );\n                    }\n                }\n                _ => (),\n            }\n        }\n    }\n\n    /// Handle user input mapped to key bindings.\n    pub fn on_input(\n        &mut self,\n        window_id: WindowId,\n        input: Input,\n        state: ElementState,\n        repeat: bool,\n    ) {\n        if let Some(action) = self.input_bindings.get(&input).copied() {\n            trace!(\"action: {action:?}, state: {state:?}, repeat: {repeat:?}\");\n            let released = state == ElementState::Released;\n            let is_root_window = Some(window_id) == self.renderer.root_window_id();\n            match action {\n                Action::Ui(ui_state) if released => match ui_state {\n                    Ui::Quit => self.tx.event(UiEvent::Terminate),\n                    Ui::TogglePause => {\n                        if is_root_window && self.renderer.rom_loaded() {\n                            self.set_run_state(match self.run_state() {\n                                RunState::Running => RunState::ManuallyPaused,\n                                RunState::ManuallyPaused | RunState::AutoPaused => {\n                                    RunState::Running\n                                }\n                            });\n                        }\n                    }\n                    Ui::LoadRom => {\n                        if self.renderer.rom_loaded() && !self.run_state().paused() {\n                            self.set_run_state(RunState::AutoPaused);\n                        }\n                        // NOTE: Due to some platforms file dialogs blocking the event loop,\n                        // loading requires a round-trip in order for the above pause to\n                        // get processed.\n                        self.tx.event(UiEvent::LoadRomDialog);\n                    }\n                    Ui::UnloadRom => {\n                        if self.renderer.rom_loaded() {\n                            self.event(EmulationEvent::UnloadRom);\n                        }\n                    }\n                    Ui::LoadReplay => {\n                        if self.renderer.rom_loaded() {\n                            if !self.run_state().paused() {\n                                self.set_run_state(RunState::AutoPaused);\n                            }\n                            // NOTE: Due to some platforms file dialogs blocking the event loop,\n                            // loading requires a round-trip in order for the above pause to\n                            // get processed.\n                            self.tx.event(UiEvent::LoadReplayDialog);\n                        }\n                    }\n                },\n                Action::Menu(menu) if released => self.event(RendererEvent::Menu(menu)),\n                Action::Feature(feature) if is_root_window => match feature {\n                    Feature::ToggleReplayRecording if released => {\n                        if feature!(Filesystem) {\n                            if self.renderer.rom_loaded() {\n                                self.replay_recording = !self.replay_recording;\n                                self.event(EmulationEvent::ReplayRecord(self.replay_recording));\n                            }\n                        } else {\n                            self.renderer.add_message(\n                                MessageType::Warn,\n                                \"Replay recordings are not supported yet on this platform.\",\n                            );\n                        }\n                    }\n                    Feature::ToggleAudioRecording if released => {\n                        if feature!(Filesystem) {\n                            if self.renderer.rom_loaded() {\n                                self.audio_recording = !self.audio_recording;\n                                self.event(EmulationEvent::AudioRecord(self.audio_recording));\n                            }\n                        } else {\n                            self.renderer.add_message(\n                                MessageType::Warn,\n                                \"Audio recordings are not supported yet on this platform.\",\n                            );\n                        }\n                    }\n                    Feature::TakeScreenshot if released => {\n                        if feature!(Filesystem) {\n                            if self.renderer.rom_loaded() {\n                                self.event(EmulationEvent::Screenshot);\n                            }\n                        } else {\n                            self.renderer.add_message(\n                                MessageType::Warn,\n                                \"Screenshots are not supported yet on this platform.\",\n                            );\n                        }\n                    }\n                    Feature::VisualRewind => {\n                        if !self.rewinding {\n                            if repeat {\n                                self.rewinding = true;\n                                self.event(EmulationEvent::Rewinding(self.rewinding));\n                            } else if released {\n                                self.event(EmulationEvent::InstantRewind);\n                            }\n                        } else if released {\n                            self.rewinding = false;\n                            self.event(EmulationEvent::Rewinding(self.rewinding));\n                        }\n                    }\n                    _ => (),\n                },\n                Action::Setting(setting) => match setting {\n                    Setting::ToggleFullscreen if released => {\n                        self.cfg.renderer.fullscreen = !self.cfg.renderer.fullscreen;\n                        self.renderer.set_fullscreen(\n                            self.cfg.renderer.fullscreen,\n                            self.cfg.renderer.embed_viewports,\n                        );\n                    }\n                    Setting::ToggleEmbedViewports if released => {\n                        self.cfg.renderer.embed_viewports = !self.cfg.renderer.embed_viewports;\n                        self.renderer\n                            .set_embed_viewports(self.cfg.renderer.embed_viewports);\n                    }\n                    Setting::ToggleAlwaysOnTop if released => {\n                        self.cfg.renderer.always_on_top = !self.cfg.renderer.always_on_top;\n                        self.renderer\n                            .set_always_on_top(self.cfg.renderer.always_on_top);\n                    }\n                    Setting::ToggleAudio if released => {\n                        self.cfg.audio.enabled = !self.cfg.audio.enabled;\n                        self.event(ConfigEvent::AudioEnabled(self.cfg.audio.enabled));\n                    }\n                    Setting::ToggleMenubar if released => {\n                        self.cfg.renderer.show_menubar = !self.cfg.renderer.show_menubar;\n                        self.event(RendererEvent::ShowMenubar(self.cfg.renderer.show_menubar));\n                    }\n                    Setting::IncrementScale if released => {\n                        let scale = self.cfg.renderer.scale;\n                        let new_scale = self.cfg.increment_scale();\n                        if scale != new_scale {\n                            self.event(ConfigEvent::Scale(new_scale));\n                        }\n                    }\n                    Setting::DecrementScale if released => {\n                        let scale = self.cfg.renderer.scale;\n                        let new_scale = self.cfg.decrement_scale();\n                        if scale != new_scale {\n                            self.event(ConfigEvent::Scale(new_scale));\n                        }\n                    }\n                    Setting::IncrementSpeed if released => {\n                        let speed = self.cfg.emulation.speed;\n                        let new_speed = self.cfg.increment_speed();\n                        if speed != new_speed {\n                            self.event(ConfigEvent::Speed(self.cfg.emulation.speed));\n                            self.renderer.add_message(\n                                MessageType::Info,\n                                format!(\"Increased Emulation Speed to {new_speed}\"),\n                            );\n                        }\n                    }\n                    Setting::DecrementSpeed if released => {\n                        let speed = self.cfg.emulation.speed;\n                        let new_speed = self.cfg.decrement_speed();\n                        if speed != new_speed {\n                            self.event(ConfigEvent::Speed(self.cfg.emulation.speed));\n                            self.renderer.add_message(\n                                MessageType::Info,\n                                format!(\"Decreased Emulation Speed to {new_speed}\"),\n                            );\n                        }\n                    }\n                    Setting::FastForward\n                        if !repeat && is_root_window && self.renderer.rom_loaded() =>\n                    {\n                        let new_speed = if released { 1.0 } else { 2.0 };\n                        let speed = self.cfg.emulation.speed;\n                        if speed != new_speed {\n                            self.cfg.emulation.speed = new_speed;\n                            self.event(ConfigEvent::Speed(self.cfg.emulation.speed));\n                            if new_speed == 2.0 {\n                                self.renderer\n                                    .add_message(MessageType::Info, \"Fast forwarding\");\n                            }\n                        }\n                    }\n                    Setting::SetShader(shader) if released => {\n                        let shader = if self.cfg.renderer.shader == shader {\n                            Shader::Default\n                        } else {\n                            shader\n                        };\n                        self.cfg.renderer.shader = shader;\n                        self.event(ConfigEvent::Shader(shader));\n                    }\n                    _ => (),\n                },\n                Action::Deck(action) => match action {\n                    DeckAction::Reset(kind) if released => {\n                        self.event(EmulationEvent::Reset(kind));\n                    }\n                    DeckAction::Joypad((player, button)) if !repeat && is_root_window => {\n                        self.event(EmulationEvent::Joypad((player, button, state)));\n                    }\n                    // Handled by `gui` module\n                    DeckAction::ZapperAim(_)\n                    | DeckAction::ZapperAimOffscreen\n                    | DeckAction::ZapperTrigger => (),\n                    DeckAction::SetSaveSlot(slot) if released => {\n                        if feature!(Storage) {\n                            if self.cfg.emulation.save_slot != slot {\n                                self.cfg.emulation.save_slot = slot;\n                                self.renderer.add_message(\n                                    MessageType::Info,\n                                    format!(\"Changed Save Slot to {slot}\"),\n                                );\n                            }\n                        } else {\n                            self.renderer.add_message(\n                                MessageType::Warn,\n                                \"Save states are not supported yet on this platform.\",\n                            );\n                        }\n                    }\n                    DeckAction::SaveState if released && is_root_window => {\n                        if feature!(Storage) {\n                            self.event(EmulationEvent::SaveState(self.cfg.emulation.save_slot));\n                        } else {\n                            self.renderer.add_message(\n                                MessageType::Warn,\n                                \"Save states are not supported yet on this platform.\",\n                            );\n                        }\n                    }\n                    DeckAction::LoadState if released && is_root_window => {\n                        if feature!(Storage) {\n                            self.event(EmulationEvent::LoadState(self.cfg.emulation.save_slot));\n                        } else {\n                            self.renderer.add_message(\n                                MessageType::Warn,\n                                \"Save states are not supported yet on this platform.\",\n                            );\n                        }\n                    }\n                    DeckAction::ToggleApuChannel(channel) if released => {\n                        self.cfg.deck.channels_enabled[channel as usize] =\n                            !self.cfg.deck.channels_enabled[channel as usize];\n                        self.event(ConfigEvent::ApuChannelEnabled((\n                            channel,\n                            self.cfg.deck.channels_enabled[channel as usize],\n                        )));\n                    }\n                    DeckAction::MapperRevision(rev) if released => {\n                        self.cfg.deck.mapper_revisions.set(rev);\n                        self.event(ConfigEvent::MapperRevisions(self.cfg.deck.mapper_revisions));\n                        self.renderer.add_message(\n                            MessageType::Info,\n                            format!(\"Changed Mapper Revision to {rev}\"),\n                        );\n                    }\n                    DeckAction::SetNesRegion(region) if released => {\n                        self.cfg.deck.region = region;\n                        self.event(ConfigEvent::Region(self.cfg.deck.region));\n                        self.renderer.add_message(\n                            MessageType::Info,\n                            format!(\"Changed NES Region to {region:?}\"),\n                        );\n                    }\n                    DeckAction::SetVideoFilter(filter) if released => {\n                        let filter = if self.cfg.deck.filter == filter {\n                            VideoFilter::Pixellate\n                        } else {\n                            filter\n                        };\n                        self.cfg.deck.filter = filter;\n                        self.event(ConfigEvent::VideoFilter(filter));\n                    }\n                    _ => (),\n                },\n                Action::Debug(action) => match action {\n                    Debug::Toggle(kind) if released => {\n                        if matches!(kind, DebugKind::Ppu) {\n                            self.event(RendererEvent::Menu(Menu::PpuViewer));\n                        } else {\n                            self.renderer.add_message(\n                                MessageType::Warn,\n                                format!(\"{kind:?} is not implemented yet\"),\n                            );\n                        }\n                    }\n                    Debug::Step(step) if (released | repeat) && is_root_window => {\n                        self.event(EmulationEvent::DebugStep(step));\n                    }\n                    _ => (),\n                },\n                _ => (),\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/input.rs",
    "content": "use crate::nes::{\n    action::{Action, Debug, DebugKind, DebugStep, Feature, Setting, Ui},\n    config::{Config, InputConfig},\n    renderer::{gui::Menu, shader::Shader},\n};\nuse egui::ahash::HashMap;\nuse serde::{Deserialize, Serialize};\nuse std::{\n    collections::{BTreeMap, VecDeque},\n    iter::Peekable,\n    ops::{Deref, DerefMut},\n};\nuse tetanes_core::{\n    action::Action as DeckAction,\n    apu::Channel,\n    common::ResetKind,\n    input::{JoypadBtn, Player},\n    video::VideoFilter,\n};\nuse tracing::warn;\nuse uuid::Uuid;\nuse winit::{\n    event::{ElementState, MouseButton},\n    keyboard::{KeyCode, ModifiersState},\n};\n\nmacro_rules! action_binding {\n    ($action:expr => $bindings:expr) => {{\n        let action = $action.into();\n        (action, ActionBindings::new(action, $bindings))\n    }};\n    ($action:expr => $modifiers:expr, $key:expr) => {\n        action_binding!($action => [Some(Input::Key($key, $modifiers)), None, None])\n    };\n    ($action:expr => $modifiers1:expr, $key1:expr; $modifiers2:expr, $key2:expr) => {\n        action_binding!(\n            $action => [Some(Input::Key($key1, $modifiers1)), Some(Input::Key($key2, $modifiers2)), None]\n        )\n    };\n}\n\n#[allow(unused_macro_rules)]\nmacro_rules! shortcut_map {\n    (@ $action:expr => $key:expr) => {\n        action_binding!($action => ModifiersState::empty(), $key)\n    };\n    (@ $action:expr => $key1:expr; $key2:expr) => {\n        action_binding!($action => ModifiersState::empty(), $key1; ModifiersState::empty(), $key2)\n    };\n    (@ $action:expr => :$modifiers:expr, $key:expr) => {\n        action_binding!($action => $modifiers, $key)\n    };\n    (@ $action:expr => :$modifiers1:expr, $key1:expr; $key2:expr) => {\n        action_binding!($action => $modifiers1, $key1; ModifiersState::empty(), $key2)\n    };\n    (@ $action:expr => :$modifiers1:expr, $key1:expr; :$modifiers2:expr, $key2:expr) => {\n        action_binding!($action => $modifiers1, $key1; $modifiers2, $key2)\n    };\n    ($({ $action:expr => $(:$modifiers1:expr,) ?$key1:expr$(; $(:$modifiers2:expr,)? $key2:expr)? }),+$(,)?) => {\n        vec![$(shortcut_map!(@ $action => $(:$modifiers1,)? $key1$(; $(:$modifiers2,)? $key2)?),)+]\n    };\n}\n\nmacro_rules! gamepad_map {\n    (@ $action:expr => $player:expr; $button:expr) => {\n        action_binding!($action => [Some(Input::Button($player, $button)), None, None])\n    };\n    (@ $action:expr => $player:expr; $button1:expr; ($button2:expr, $state:expr)) => {\n        action_binding!($action => [Some(Input::Button($player, $button1)), Some(Input::Axis($player, $button2, $state)), None])\n    };\n    ($({ $action:expr => $player:expr; $button1:expr$(; ($button2:expr, $state:expr))? }),+$(,)?) => {\n        vec![$(gamepad_map!(@ $action => $player; $button1$(; ($button2, $state))?),)+]\n    };\n}\n\nmacro_rules! mouse_map {\n    (@ $action:expr => $button:expr) => {\n        action_binding!($action => [Some(Input::Mouse($button)), None, None])\n    };\n    ($({ $action:expr => $button:expr }),+$(,)?) => {\n        vec![$(mouse_map!(@ $action => $button),)+]\n    };\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum Input {\n    Key(KeyCode, ModifiersState),\n    Mouse(MouseButton),\n    Button(Player, gilrs::Button),\n    Axis(Player, gilrs::Axis, AxisDirection),\n}\n\nimpl Input {\n    pub fn fmt(input: Input) -> String {\n        use winit::{\n            event::MouseButton,\n            keyboard::{KeyCode, ModifiersState},\n        };\n\n        match input {\n            Input::Key(keycode, modifiers) => {\n                let mut s = String::with_capacity(32);\n                if modifiers.contains(ModifiersState::CONTROL) {\n                    s += \"Ctrl\";\n                }\n                if modifiers.contains(ModifiersState::SHIFT) {\n                    if !s.is_empty() {\n                        s += \"+\";\n                    }\n                    s += \"Shift\";\n                }\n                if modifiers.contains(ModifiersState::ALT) {\n                    if !s.is_empty() {\n                        s += \"+\";\n                    }\n                    s += \"Alt\";\n                }\n                if modifiers.contains(ModifiersState::SUPER) {\n                    if !s.is_empty() {\n                        s += \"+\";\n                    }\n                    s += \"Super\";\n                }\n                let ch = match keycode {\n                    KeyCode::Backquote => \"`\",\n                    KeyCode::Backslash | KeyCode::IntlBackslash => \"\\\\\",\n                    KeyCode::BracketLeft => \"[\",\n                    KeyCode::BracketRight => \"]\",\n                    KeyCode::Comma | KeyCode::NumpadComma => \",\",\n                    KeyCode::Digit0 => \"0\",\n                    KeyCode::Digit1 => \"1\",\n                    KeyCode::Digit2 => \"2\",\n                    KeyCode::Digit3 => \"3\",\n                    KeyCode::Digit4 => \"4\",\n                    KeyCode::Digit5 => \"5\",\n                    KeyCode::Digit6 => \"6\",\n                    KeyCode::Digit7 => \"7\",\n                    KeyCode::Digit8 => \"8\",\n                    KeyCode::Digit9 => \"9\",\n                    KeyCode::Equal => \"=\",\n                    KeyCode::KeyA => \"A\",\n                    KeyCode::KeyB => \"B\",\n                    KeyCode::KeyC => \"C\",\n                    KeyCode::KeyD => \"D\",\n                    KeyCode::KeyE => \"E\",\n                    KeyCode::KeyF => \"F\",\n                    KeyCode::KeyG => \"G\",\n                    KeyCode::KeyH => \"H\",\n                    KeyCode::KeyI => \"I\",\n                    KeyCode::KeyJ => \"J\",\n                    KeyCode::KeyK => \"K\",\n                    KeyCode::KeyL => \"L\",\n                    KeyCode::KeyM => \"M\",\n                    KeyCode::KeyN => \"N\",\n                    KeyCode::KeyO => \"O\",\n                    KeyCode::KeyP => \"P\",\n                    KeyCode::KeyQ => \"Q\",\n                    KeyCode::KeyR => \"R\",\n                    KeyCode::KeyS => \"S\",\n                    KeyCode::KeyT => \"T\",\n                    KeyCode::KeyU => \"U\",\n                    KeyCode::KeyV => \"V\",\n                    KeyCode::KeyW => \"W\",\n                    KeyCode::KeyX => \"X\",\n                    KeyCode::KeyY => \"Y\",\n                    KeyCode::KeyZ => \"Z\",\n                    KeyCode::Minus | KeyCode::NumpadSubtract => \"-\",\n                    KeyCode::Period | KeyCode::NumpadDecimal => \".\",\n                    KeyCode::Quote => \"'\",\n                    KeyCode::Semicolon => \";\",\n                    KeyCode::Slash | KeyCode::NumpadDivide => \"/\",\n                    KeyCode::Backspace | KeyCode::NumpadBackspace => \"Backspace\",\n                    KeyCode::Enter | KeyCode::NumpadEnter => \"Enter\",\n                    KeyCode::Space => \"Space\",\n                    KeyCode::Tab => \"Tab\",\n                    KeyCode::Delete => \"Delete\",\n                    KeyCode::End => \"End\",\n                    KeyCode::Help => \"Help\",\n                    KeyCode::Home => \"Home\",\n                    KeyCode::Insert => \"Ins\",\n                    KeyCode::PageDown => \"PageDown\",\n                    KeyCode::PageUp => \"PageUp\",\n                    KeyCode::ArrowDown => \"Down\",\n                    KeyCode::ArrowLeft => \"Left\",\n                    KeyCode::ArrowRight => \"Right\",\n                    KeyCode::ArrowUp => \"Up\",\n                    KeyCode::Numpad0 => \"Num0\",\n                    KeyCode::Numpad1 => \"Num1\",\n                    KeyCode::Numpad2 => \"Num2\",\n                    KeyCode::Numpad3 => \"Num3\",\n                    KeyCode::Numpad4 => \"Num4\",\n                    KeyCode::Numpad5 => \"Num5\",\n                    KeyCode::Numpad6 => \"Num6\",\n                    KeyCode::Numpad7 => \"Num7\",\n                    KeyCode::Numpad8 => \"Num8\",\n                    KeyCode::Numpad9 => \"Num9\",\n                    KeyCode::NumpadAdd => \"+\",\n                    KeyCode::NumpadEqual => \"=\",\n                    KeyCode::NumpadHash => \"#\",\n                    KeyCode::NumpadMultiply => \"*\",\n                    KeyCode::NumpadParenLeft => \"(\",\n                    KeyCode::NumpadParenRight => \")\",\n                    KeyCode::NumpadStar => \"*\",\n                    KeyCode::Escape => \"Escape\",\n                    KeyCode::Fn => \"Fn\",\n                    KeyCode::F1 => \"F1\",\n                    KeyCode::F2 => \"F2\",\n                    KeyCode::F3 => \"F3\",\n                    KeyCode::F4 => \"F4\",\n                    KeyCode::F5 => \"F5\",\n                    KeyCode::F6 => \"F6\",\n                    KeyCode::F7 => \"F7\",\n                    KeyCode::F8 => \"F8\",\n                    KeyCode::F9 => \"F9\",\n                    KeyCode::F10 => \"F10\",\n                    KeyCode::F11 => \"F11\",\n                    KeyCode::F12 => \"F12\",\n                    KeyCode::F13 => \"F13\",\n                    KeyCode::F14 => \"F14\",\n                    KeyCode::F15 => \"F15\",\n                    KeyCode::F16 => \"F16\",\n                    KeyCode::F17 => \"F17\",\n                    KeyCode::F18 => \"F18\",\n                    KeyCode::F19 => \"F19\",\n                    KeyCode::F20 => \"F20\",\n                    KeyCode::F21 => \"F21\",\n                    KeyCode::F22 => \"F22\",\n                    KeyCode::F23 => \"F23\",\n                    KeyCode::F24 => \"F24\",\n                    KeyCode::F25 => \"F25\",\n                    KeyCode::F26 => \"F26\",\n                    KeyCode::F27 => \"F27\",\n                    KeyCode::F28 => \"F28\",\n                    KeyCode::F29 => \"F29\",\n                    KeyCode::F30 => \"F30\",\n                    KeyCode::F31 => \"F31\",\n                    KeyCode::F32 => \"F32\",\n                    KeyCode::F33 => \"F33\",\n                    KeyCode::F34 => \"F34\",\n                    KeyCode::F35 => \"F35\",\n                    _ => \"\",\n                };\n                if !ch.is_empty() {\n                    if !s.is_empty() {\n                        s += \"+\";\n                    }\n                    s += ch;\n                }\n                s.shrink_to_fit();\n                s\n            }\n            Input::Button(_, button) => format!(\"{button:#?}\"),\n            Input::Axis(_, axis, direction) => format!(\"{axis:#?} {direction:#?}\"),\n            Input::Mouse(button) => match button {\n                MouseButton::Left => String::from(\"Left Click\"),\n                MouseButton::Right => String::from(\"Right Click\"),\n                MouseButton::Middle => String::from(\"Middle Click\"),\n                MouseButton::Back => String::from(\"Back Click\"),\n                MouseButton::Forward => String::from(\"Forward Click\"),\n                MouseButton::Other(id) => format!(\"Button {id} Click\"),\n            },\n        }\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum AxisDirection {\n    Negative, // Left or Up\n    Positive, // Right or Down\n}\n\npub type Bindings = [Option<Input>; 3];\n\n#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]\n#[must_use]\npub struct ActionBindings {\n    pub action: Action,\n    pub bindings: Bindings,\n}\n\nimpl ActionBindings {\n    pub const fn new(action: Action, bindings: Bindings) -> Self {\n        Self { action, bindings }\n    }\n\n    pub fn empty(action: Action) -> Self {\n        Self {\n            action,\n            bindings: Default::default(),\n        }\n    }\n}\n\nimpl ActionBindings {\n    pub fn default_shortcuts() -> BTreeMap<Action, ActionBindings> {\n        use KeyCode::*;\n        const SHIFT: ModifiersState = ModifiersState::SHIFT;\n        const CONTROL: ModifiersState = ModifiersState::CONTROL;\n\n        let mut bindings = Action::BINDABLE\n            .into_iter()\n            .filter(|action| !action.is_joypad())\n            .map(|action| (action, ActionBindings::empty(action)))\n            .collect::<BTreeMap<_, _>>();\n\n        bindings.extend(shortcut_map!(\n            { Debug::Step(DebugStep::Frame) => :SHIFT, KeyF },\n            { Debug::Step(DebugStep::Into) => KeyC },\n            { Debug::Step(DebugStep::Out) => :SHIFT, KeyO },\n            { Debug::Step(DebugStep::Over) => KeyO },\n            { Debug::Step(DebugStep::Scanline) => :SHIFT, KeyL },\n            { Debug::Toggle(DebugKind::Apu) => :SHIFT, KeyA },\n            { Debug::Toggle(DebugKind::Cpu) => :SHIFT, KeyD },\n            { Debug::Toggle(DebugKind::Ppu) => :SHIFT, KeyP },\n            { DeckAction::LoadState => :CONTROL, KeyL },\n            { DeckAction::Reset(ResetKind::Hard) => :CONTROL, KeyH },\n            { DeckAction::Reset(ResetKind::Soft) => :CONTROL, KeyR },\n            { DeckAction::SaveState => :CONTROL, KeyS },\n            { DeckAction::SetSaveSlot(1) => :CONTROL, Digit1 },\n            { DeckAction::SetSaveSlot(2) => :CONTROL, Digit2 },\n            { DeckAction::SetSaveSlot(3) => :CONTROL, Digit3 },\n            { DeckAction::SetSaveSlot(4) => :CONTROL, Digit4 },\n            { DeckAction::SetSaveSlot(5) => :CONTROL, Digit5 },\n            { DeckAction::SetSaveSlot(6) => :CONTROL, Digit6 },\n            { DeckAction::SetSaveSlot(7) => :CONTROL, Digit7 },\n            { DeckAction::SetSaveSlot(8) => :CONTROL, Digit8 },\n            { DeckAction::SetVideoFilter(VideoFilter::Ntsc) => :CONTROL, KeyN },\n            { DeckAction::ToggleApuChannel(Channel::Dmc) => :SHIFT, Digit5 },\n            { DeckAction::ToggleApuChannel(Channel::Mapper) => :SHIFT, Digit6 },\n            { DeckAction::ToggleApuChannel(Channel::Noise) => :SHIFT, Digit4 },\n            { DeckAction::ToggleApuChannel(Channel::Pulse1) => :SHIFT, Digit1 },\n            { DeckAction::ToggleApuChannel(Channel::Pulse2) => :SHIFT, Digit2 },\n            { DeckAction::ToggleApuChannel(Channel::Triangle) => :SHIFT, Digit3 },\n            { Feature::InstantRewind => KeyR },\n            { Feature::TakeScreenshot => F10 },\n            { Feature::ToggleAudioRecording => :SHIFT, KeyR },\n            { Feature::ToggleReplayRecording => :SHIFT, KeyV },\n            { Feature::VisualRewind => KeyR },\n            { Menu::About => F1 },\n            { Menu::Keybinds => :CONTROL, KeyK; F3 },\n            { Menu::Preferences => :CONTROL, KeyP; F2 },\n            { Menu::PerfStats => :CONTROL, KeyF },\n            { Setting::DecrementScale => :SHIFT, Minus },\n            { Setting::DecrementSpeed => Minus },\n            { Setting::FastForward => Space },\n            { Setting::IncrementScale => :SHIFT, Equal },\n            { Setting::IncrementSpeed => Equal },\n            { Setting::ToggleAudio => :CONTROL, KeyM },\n            { Setting::ToggleFullscreen => :CONTROL, Enter },\n            { Setting::ToggleMenubar => :CONTROL, KeyE },\n            { Setting::SetShader(Shader::CrtEasymode) => :CONTROL, KeyT },\n            { Ui::LoadRom => :CONTROL, KeyO; F3 },\n            { Ui::Quit => :CONTROL, KeyQ },\n            { Ui::TogglePause => Escape },\n        ));\n        bindings.extend(mouse_map!(\n            { DeckAction::ZapperTrigger => MouseButton::Left },\n            { DeckAction::ZapperAimOffscreen => MouseButton::Right }\n        ));\n\n        bindings\n    }\n\n    pub fn default_player_bindings(player: Player) -> BTreeMap<Action, ActionBindings> {\n        use KeyCode::*;\n        use gilrs::{Axis, Button};\n\n        let mut bindings = Action::BINDABLE\n            .into_iter()\n            .filter(|action| action.joypad_player(player))\n            .map(|action| (action, ActionBindings::empty(action)))\n            .collect::<BTreeMap<_, _>>();\n\n        bindings.extend(gamepad_map!(\n            { (player, JoypadBtn::A) => player; Button::East },\n            { (player, JoypadBtn::TurboA) => player; Button::North },\n            { (player, JoypadBtn::B) => player; Button::South },\n            { (player, JoypadBtn::TurboB) => player; Button::West },\n            { (player, JoypadBtn::Up) => player; Button::DPadUp; (Axis::LeftStickY, AxisDirection::Negative) },\n            { (player, JoypadBtn::Down) => player; Button::DPadDown; (Axis::LeftStickY, AxisDirection::Positive) },\n            { (player, JoypadBtn::Left) => player; Button::DPadLeft; (Axis::LeftStickX, AxisDirection::Negative) },\n            { (player, JoypadBtn::Right) => player; Button::DPadRight; (Axis::LeftStickX, AxisDirection::Positive) },\n            { (player, JoypadBtn::Select) => player; Button::Select },\n            { (player, JoypadBtn::Start) => player; Button::Start },\n        ));\n\n        let additional_bindings = match player {\n            Player::One => shortcut_map!(\n                { (Player::One, JoypadBtn::A) => KeyZ },\n                { (Player::One, JoypadBtn::TurboA) => KeyA },\n                { (Player::One, JoypadBtn::B) => KeyX },\n                { (Player::One, JoypadBtn::TurboB) => KeyS },\n                // FIXME: These overwrite Axis bindings above because there are only two binding\n                // slots available at present\n                { (Player::One, JoypadBtn::Up) => ArrowUp },\n                { (Player::One, JoypadBtn::Down) => ArrowDown },\n                { (Player::One, JoypadBtn::Left) => ArrowLeft },\n                { (Player::One, JoypadBtn::Right) => ArrowRight },\n                { (Player::One, JoypadBtn::Select) => KeyQ },\n                { (Player::One, JoypadBtn::Start) => KeyW },\n            ),\n            _ => Vec::new(),\n        };\n\n        for (action, addtl_binding) in additional_bindings {\n            if let Some((_, existing_bindings)) = bindings\n                .iter_mut()\n                .find(|(existing_action, _)| **existing_action == action)\n            {\n                for binding in &mut existing_bindings.bindings {\n                    if binding.is_none() {\n                        *binding = addtl_binding.bindings[0];\n                    }\n                }\n            } else {\n                bindings.insert(action, addtl_binding);\n            }\n        }\n\n        bindings\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\npub struct InputBindings(HashMap<Input, Action>);\n\nimpl InputBindings {\n    pub fn from_input_config(cfg: &InputConfig) -> Self {\n        Self(\n            cfg.action_bindings\n                .iter()\n                .flat_map(|bind| {\n                    bind.bindings\n                        .iter()\n                        .flatten()\n                        .map(|input| (*input, bind.action))\n                })\n                .collect(),\n        )\n    }\n}\n\nimpl Deref for InputBindings {\n    type Target = HashMap<Input, Action>;\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl DerefMut for InputBindings {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.0\n    }\n}\n\n/// Represents gamepad input state.\n#[derive(Default, Debug)]\npub struct Gamepads {\n    connected: HashMap<gilrs::GamepadId, Uuid>,\n    inner: Option<gilrs::Gilrs>,\n    events: VecDeque<gilrs::Event>,\n    ui_consumes: bool,\n}\n\nimpl Gamepads {\n    pub fn new() -> Self {\n        let mut connected = HashMap::default();\n        let mut gilrs = gilrs::Gilrs::new();\n        let mut events = VecDeque::new();\n        match &mut gilrs {\n            Ok(inputs) => {\n                for (id, gamepad) in inputs.gamepads() {\n                    let uuid = Self::create_uuid(&gamepad);\n                    tracing::debug!(\"gamepad connected: {} ({uuid})\", gamepad.name());\n                    connected.insert(id, uuid);\n                }\n                events.reserve(256);\n            }\n            Err(err) => {\n                warn!(\"failed to initialize inputs: {err:?}\");\n            }\n        }\n\n        Self {\n            connected,\n            inner: gilrs.ok(),\n            events,\n            ui_consumes: false,\n        }\n    }\n\n    pub fn update_events(&mut self) {\n        if let Some(inner) = self.inner.as_mut() {\n            while let Some(event) = inner.next_event() {\n                self.events.push_back(event);\n            }\n        }\n    }\n\n    pub fn axis_state(value: f32) -> (Option<AxisDirection>, ElementState) {\n        let direction = if value >= 0.6 {\n            Some(AxisDirection::Positive)\n        } else if value <= -0.6 {\n            Some(AxisDirection::Negative)\n        } else {\n            None\n        };\n        let state = if direction.is_some() {\n            ElementState::Pressed\n        } else {\n            ElementState::Released\n        };\n        (direction, state)\n    }\n\n    pub fn has_events(&self) -> bool {\n        !self.events.is_empty()\n    }\n\n    pub fn input_from_event(\n        &self,\n        event: &gilrs::Event,\n        cfg: &Config,\n    ) -> Option<(Input, ElementState)> {\n        use gilrs::EventType;\n        if let Some(player) = self\n            .connected\n            .get(&event.id)\n            .and_then(|uuid| cfg.input.gamepad_assignment(uuid))\n        {\n            match event.event {\n                EventType::ButtonPressed(button, _) => {\n                    Some((Input::Button(player, button), ElementState::Pressed))\n                }\n                EventType::ButtonRepeated(button, _) => {\n                    Some((Input::Button(player, button), ElementState::Pressed))\n                }\n                EventType::ButtonReleased(button, _) => {\n                    Some((Input::Button(player, button), ElementState::Released))\n                }\n                EventType::AxisChanged(axis, value, _) => {\n                    if let (Some(direction), state) = Gamepads::axis_state(value) {\n                        Some((Input::Axis(player, axis, direction), state))\n                    } else {\n                        None\n                    }\n                }\n                _ => None,\n            }\n        } else {\n            None\n        }\n    }\n\n    pub fn connected_gamepad(&self, id: gilrs::GamepadId) -> Option<gilrs::Gamepad<'_>> {\n        self.inner\n            .as_ref()\n            .and_then(|inner| inner.connected_gamepad(id))\n    }\n\n    pub fn gamepad(&self, id: gilrs::GamepadId) -> Option<gilrs::Gamepad<'_>> {\n        self.inner.as_ref().map(|inner| inner.gamepad(id))\n    }\n\n    pub fn gamepad_by_uuid(&self, uuid: &Uuid) -> Option<gilrs::Gamepad<'_>> {\n        self.inner.as_ref().and_then(|inner| {\n            self.connected\n                .iter()\n                .find(|(_, u)| *u == uuid)\n                .and_then(|(id, _)| inner.connected_gamepad(*id))\n        })\n    }\n\n    pub fn gamepad_name_by_uuid(&self, uuid: &Uuid) -> Option<String> {\n        self.gamepad_by_uuid(uuid).map(|g| g.name().to_string())\n    }\n\n    pub fn gamepad_uuid(&self, id: gilrs::GamepadId) -> Option<Uuid> {\n        self.connected_gamepad(id).map(|g| Self::create_uuid(&g))\n    }\n\n    pub fn is_connected(&self, uuid: &Uuid) -> bool {\n        self.gamepad_by_uuid(uuid).is_some()\n    }\n\n    pub fn list(&self) -> Option<Peekable<gilrs::ConnectedGamepadsIterator<'_>>> {\n        self.inner.as_ref().map(|inner| inner.gamepads().peekable())\n    }\n\n    pub fn connected_uuids(&self) -> impl Iterator<Item = &Uuid> {\n        self.connected.values()\n    }\n\n    pub fn events(&self) -> impl Iterator<Item = &gilrs::Event> {\n        self.events.iter()\n    }\n\n    pub fn next_event(&mut self) -> Option<gilrs::Event> {\n        self.events.pop_back()\n    }\n\n    pub fn clear_events(&mut self) {\n        self.events.clear();\n    }\n\n    pub fn set_ui_consumes(&mut self, consumes: bool) {\n        if self.ui_consumes && !consumes {\n            self.events.clear();\n        }\n        self.ui_consumes = consumes;\n    }\n\n    pub fn connect(&mut self, gamepad_id: gilrs::GamepadId) {\n        if let Some(gamepad) = self.connected_gamepad(gamepad_id) {\n            let uuid = Self::create_uuid(&gamepad);\n            tracing::debug!(\"gamepad connected: {} ({uuid})\", gamepad.name());\n            self.connected.insert(gamepad.id(), uuid);\n        }\n    }\n\n    pub fn disconnect(&mut self, gamepad_id: gilrs::GamepadId) {\n        if let Some(gamepad) = self.gamepad(gamepad_id) {\n            let uuid = Self::create_uuid(&gamepad);\n            tracing::debug!(\"gamepad disconnected: {} ({uuid})\", gamepad.name());\n        }\n        self.connected.remove(&gamepad_id);\n    }\n\n    pub fn create_uuid(gamepad: &gilrs::Gamepad<'_>) -> Uuid {\n        let uuid = Uuid::from_bytes(gamepad.uuid());\n        if uuid != Uuid::nil() {\n            return uuid;\n        }\n\n        // See: https://gitlab.com/gilrs-project/gilrs/-/issues/107\n\n        // SDL always uses USB bus for UUID\n        let bustype = u32::to_be(0x03);\n\n        // Version is not available.\n        let version = 0;\n        let vendor_id = gamepad.vendor_id().unwrap_or(0);\n        let product_id = gamepad.product_id().unwrap_or(0);\n\n        if vendor_id == 0 && product_id == 0 {\n            Uuid::new_v4()\n        } else {\n            Uuid::from_fields(\n                bustype,\n                vendor_id,\n                0,\n                &[\n                    (product_id >> 8) as u8,\n                    product_id as u8,\n                    0,\n                    0,\n                    (version >> 8) as u8,\n                    version as u8,\n                    0,\n                    0,\n                ],\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/renderer/clipboard.rs",
    "content": "#[must_use]\npub struct Clipboard {\n    #[cfg(not(target_arch = \"wasm32\"))]\n    inner: Option<arboard::Clipboard>,\n    /// Fallback.\n    text: String,\n}\n\nimpl Default for Clipboard {\n    #[allow(clippy::derivable_impls)]\n    fn default() -> Self {\n        Self {\n            #[cfg(not(target_arch = \"wasm32\"))]\n            inner: arboard::Clipboard::new()\n                .map_err(|err| tracing::warn!(\"failed to initialize clipboard: {err:?}\"))\n                .ok(),\n            text: String::new(),\n        }\n    }\n}\n\nimpl std::fmt::Debug for Clipboard {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let mut res = f.debug_struct(\"Clipboard\");\n        #[cfg(not(target_arch = \"wasm32\"))]\n        res.field(\"inner\", &self.inner.as_ref().map(|_| \"arboard\"));\n        res.field(\"text\", &self.text).finish_non_exhaustive()\n    }\n}\n\nimpl Clipboard {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn get(&mut self) -> Option<String> {\n        #[cfg(not(target_arch = \"wasm32\"))]\n        if let Some(inner) = self.inner.as_mut() {\n            return inner\n                .get_text()\n                .map_err(|err| tracing::warn!(\"clipboard paste error: {err:?}\"))\n                .ok();\n        }\n\n        Some(self.text.clone())\n    }\n\n    pub fn set(&mut self, text: impl Into<String>) {\n        let text = text.into();\n        #[cfg(not(target_arch = \"wasm32\"))]\n        if let Some(inner) = self.inner.as_mut() {\n            if let Err(err) = inner.set_text(text) {\n                tracing::warn!(\"clipboard paste error: {err:?}\");\n            }\n            return;\n        }\n\n        self.text = text\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/renderer/event.rs",
    "content": "use crate::{\n    feature,\n    nes::{\n        config::Config,\n        event::{ConfigEvent, NesEvent, RendererEvent, Response, UiEvent},\n        input::{Gamepads, Input},\n        renderer::{\n            Renderer, State, Viewport,\n            gui::{Gui, lib::pixels_per_point},\n        },\n    },\n};\nuse egui::{PointerButton, SystemTheme, ViewportCommand, ViewportId};\nuse winit::{\n    dpi::PhysicalPosition,\n    event::{\n        ElementState, Force, KeyEvent, MouseButton, MouseScrollDelta, Touch, TouchPhase,\n        WindowEvent,\n    },\n    keyboard::{Key, KeyCode, ModifiersState, NamedKey, PhysicalKey},\n    window::{Theme, WindowId},\n};\n\nimpl Renderer {\n    /// Handle event.\n    pub fn on_event(&mut self, event: &mut NesEvent, cfg: &Config) {\n        {\n            let painter = self.painter.borrow();\n            if let Some(render_state) = painter.render_state() {\n                self.gui.borrow_mut().on_event(&render_state.queue, event);\n            }\n        }\n\n        match event {\n            NesEvent::Renderer(event) => match event {\n                RendererEvent::ViewportResized(_) => self.resize_window(cfg),\n                RendererEvent::ResizeTexture => self.resize_texture = true,\n                RendererEvent::RomLoaded(_) => {\n                    let state = self.state.borrow();\n                    if state.focused != Some(ViewportId::ROOT) {\n                        self.ctx\n                            .send_viewport_cmd_to(ViewportId::ROOT, ViewportCommand::Focus);\n                    }\n                    // Loading ROM may need to resize to different region\n                    self.resize_texture = true;\n                }\n                _ => (),\n            },\n            NesEvent::Config(event) => match event {\n                ConfigEvent::DarkTheme(enabled) => {\n                    self.ctx.set_visuals(if *enabled {\n                        Gui::dark_theme()\n                    } else {\n                        Gui::light_theme()\n                    });\n                }\n                ConfigEvent::EmbedViewports(embed) => {\n                    if feature!(OsViewports) {\n                        self.ctx.set_embed_viewports(*embed);\n                    }\n                }\n                ConfigEvent::Fullscreen(fullscreen) => {\n                    if feature!(OsViewports) {\n                        self.ctx\n                            .set_embed_viewports(*fullscreen || cfg.renderer.embed_viewports);\n                    }\n                    if self.fullscreen() != *fullscreen {\n                        self.ctx\n                            .send_viewport_cmd_to(ViewportId::ROOT, ViewportCommand::Focus);\n                        self.ctx.send_viewport_cmd_to(\n                            ViewportId::ROOT,\n                            ViewportCommand::Fullscreen(*fullscreen),\n                        );\n                    }\n                }\n                ConfigEvent::Region(_) | ConfigEvent::HideOverscan(_) | ConfigEvent::Scale(_) => {\n                    self.resize_texture = true;\n                }\n                ConfigEvent::Shader(shader) => {\n                    self.painter.borrow_mut().set_shader(*shader);\n                }\n                _ => (),\n            },\n            #[cfg(not(target_arch = \"wasm32\"))]\n            NesEvent::AccessKit { window_id, event } => {\n                use crate::nes::event::AccessKitWindowEvent;\n                if let Some(viewport_id) = self.viewport_id_for_window(*window_id) {\n                    let mut state = self.state.borrow_mut();\n                    if let Some(viewport) = state.viewports.get_mut(&viewport_id) {\n                        match event {\n                            AccessKitWindowEvent::InitialTreeRequested => {\n                                self.ctx.enable_accesskit();\n                                self.ctx.request_repaint_of(viewport_id);\n                            }\n                            AccessKitWindowEvent::ActionRequested(request) => {\n                                viewport\n                                    .raw_input\n                                    .events\n                                    .push(egui::Event::AccessKitActionRequest(request.clone()));\n                                self.ctx.request_repaint_of(viewport_id);\n                            }\n                            AccessKitWindowEvent::AccessibilityDeactivated => {\n                                self.ctx.disable_accesskit();\n                            }\n                        }\n                    };\n                }\n            }\n            _ => (),\n        }\n    }\n\n    /// Handle window event.\n    pub fn on_window_event(&mut self, window_id: WindowId, event: &WindowEvent) -> Response {\n        let Some(viewport_id) = self.viewport_id_for_window(window_id) else {\n            return Response::default();\n        };\n\n        let State {\n            viewports,\n            focused,\n            pointer_touch_id,\n            ..\n        } = &mut *self.state.borrow_mut();\n        let Some(viewport) = viewports.get_mut(&viewport_id) else {\n            return Response::default();\n        };\n\n        #[cfg(not(target_arch = \"wasm32\"))]\n        if let Some(window) = &viewport.window {\n            tracing::trace!(\"process accesskit event: {event:?}\");\n            self.accesskit.process_event(window, event);\n        }\n\n        let pixels_per_point = viewport\n            .window\n            .as_ref()\n            .map_or(1.0, |window| pixels_per_point(&self.ctx, window));\n\n        match event {\n            WindowEvent::Focused(new_focused) => {\n                *focused = if *new_focused {\n                    Some(viewport_id)\n                } else {\n                    None\n                };\n            }\n            // Note: Does not trigger on all platforms\n            WindowEvent::Occluded(occluded) => viewport.occluded = *occluded,\n            WindowEvent::CloseRequested | WindowEvent::Destroyed => {\n                if viewport_id == ViewportId::ROOT {\n                    self.tx.event(UiEvent::Terminate);\n                } else {\n                    viewport.info.events.push(egui::ViewportEvent::Close);\n                    self.gui.borrow_mut().close_viewport(viewport_id);\n\n                    // We may need to repaint both us and our parent to close the window,\n                    // and perhaps twice (once to notice the close-event, once again to enforce it).\n                    // `request_repaint_of` does a double-repaint though:\n                    self.ctx.request_repaint_of(viewport_id);\n                    self.ctx.request_repaint_of(viewport.ids.parent);\n                }\n            }\n            // To support clipboard in wasm, we need to intercept the Paste event so that\n            // we don't try to use the clipboard fallback logic for paste. Associated\n            // behavior in the wasm platform layer handles setting the clipboard text.\n            WindowEvent::KeyboardInput {\n                event:\n                    KeyEvent {\n                        physical_key: PhysicalKey::Code(key),\n                        ..\n                    },\n                ..\n            } => {\n                if let Some(key) = key_from_keycode(*key) {\n                    use egui::Key;\n\n                    let modifiers = self.ctx.input(|i| i.modifiers);\n\n                    if feature!(ConsumePaste) && is_paste_command(modifiers, key) {\n                        return Response {\n                            consumed: true,\n                            repaint: true,\n                        };\n                    }\n\n                    if matches!(key, Key::Plus | Key::Equals | Key::Minus | Key::Num0)\n                        && (modifiers.ctrl || modifiers.command)\n                    {\n                        self.zoom_changed = true;\n                    }\n                }\n            }\n            WindowEvent::Resized(size) => {\n                self.painter\n                    .borrow_mut()\n                    .on_window_resized(viewport_id, size.width, size.height);\n            }\n            WindowEvent::ThemeChanged(theme) => {\n                self.ctx\n                    .send_viewport_cmd(ViewportCommand::SetTheme(if *theme == Theme::Light {\n                        SystemTheme::Light\n                    } else {\n                        SystemTheme::Dark\n                    }));\n            }\n            _ => (),\n        };\n\n        let res = match event {\n            WindowEvent::ScaleFactorChanged { scale_factor, .. } => {\n                let native_pixels_per_point = *scale_factor as f32;\n                viewport.info.native_pixels_per_point = Some(native_pixels_per_point);\n                Response {\n                    repaint: true,\n                    consumed: false,\n                }\n            }\n            WindowEvent::MouseInput { state, button, .. } => {\n                Self::on_mouse_button_input(viewport.cursor_pos, viewport, *state, *button);\n                Response {\n                    repaint: true,\n                    consumed: self.ctx.egui_wants_pointer_input(),\n                }\n            }\n            WindowEvent::MouseWheel { delta, .. } => {\n                Self::on_mouse_wheel(viewport, pixels_per_point, *delta);\n                Response {\n                    repaint: true,\n                    consumed: self.ctx.egui_wants_pointer_input(),\n                }\n            }\n            WindowEvent::CursorMoved { position, .. } => {\n                Self::on_cursor_moved(viewport, pixels_per_point, *position);\n                Response {\n                    repaint: true,\n                    consumed: self.ctx.egui_is_using_pointer(),\n                }\n            }\n            WindowEvent::CursorLeft { .. } => {\n                viewport.cursor_pos = None;\n                viewport.raw_input.events.push(egui::Event::PointerGone);\n                Response {\n                    repaint: true,\n                    consumed: false,\n                }\n            }\n            // WindowEvent::TouchpadPressure {device_id, pressure, stage, ..  } => {} // TODO\n            WindowEvent::Touch(touch) => {\n                Self::on_touch(viewport, pointer_touch_id, pixels_per_point, touch);\n                let consumed = match touch.phase {\n                    TouchPhase::Started | TouchPhase::Ended | TouchPhase::Cancelled => {\n                        self.ctx.egui_wants_pointer_input()\n                    }\n                    TouchPhase::Moved => self.ctx.egui_is_using_pointer(),\n                };\n                Response {\n                    repaint: true,\n                    consumed,\n                }\n            }\n            WindowEvent::KeyboardInput {\n                event,\n                is_synthetic,\n                ..\n            } => {\n                // Winit generates fake \"synthetic\" KeyboardInput events when the focus\n                // is changed to the window, or away from it. Synthetic key presses\n                // represent no real key presses and should be ignored.\n                // See https://github.com/rust-windowing/winit/issues/3543\n                if *is_synthetic && event.state == ElementState::Pressed {\n                    Response {\n                        repaint: true,\n                        consumed: false,\n                    }\n                } else {\n                    Self::on_keyboard_input(viewport, event);\n\n                    // When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes.\n                    let consumed = self.ctx.egui_wants_keyboard_input()\n                        || event.logical_key == Key::Named(NamedKey::Tab);\n                    Response {\n                        repaint: true,\n                        consumed,\n                    }\n                }\n            }\n            WindowEvent::Focused(focused) => {\n                viewport\n                    .raw_input\n                    .events\n                    .push(egui::Event::WindowFocused(*focused));\n                Response {\n                    repaint: true,\n                    consumed: false,\n                }\n            }\n            WindowEvent::HoveredFile(path) => {\n                if let Some(viewport) = viewports.get_mut(&viewport_id) {\n                    viewport.raw_input.hovered_files.push(egui::HoveredFile {\n                        path: Some(path.clone()),\n                        ..Default::default()\n                    });\n                }\n                Response {\n                    repaint: true,\n                    consumed: false,\n                }\n            }\n            WindowEvent::HoveredFileCancelled => {\n                if let Some(viewport) = viewports.get_mut(&viewport_id) {\n                    viewport.raw_input.hovered_files.clear();\n                }\n                Response {\n                    repaint: true,\n                    consumed: false,\n                }\n            }\n            WindowEvent::DroppedFile(path) => {\n                if let Some(viewport) = viewports.get_mut(&viewport_id) {\n                    viewport.raw_input.hovered_files.clear();\n                    viewport.raw_input.dropped_files.push(egui::DroppedFile {\n                        path: Some(path.clone()),\n                        ..Default::default()\n                    });\n                }\n                Response {\n                    repaint: true,\n                    consumed: false,\n                }\n            }\n            WindowEvent::ModifiersChanged(state) => {\n                let state = state.state();\n\n                let alt = state.alt_key();\n                let ctrl = state.control_key();\n                let shift = state.shift_key();\n                let super_ = state.super_key();\n\n                if let Some(viewport) = viewports.get_mut(&viewport_id) {\n                    viewport.raw_input.modifiers.alt = alt;\n                    viewport.raw_input.modifiers.ctrl = ctrl;\n                    viewport.raw_input.modifiers.shift = shift;\n                    viewport.raw_input.modifiers.mac_cmd = cfg!(target_os = \"macos\") && super_;\n                    viewport.raw_input.modifiers.command = if cfg!(target_os = \"macos\") {\n                        super_\n                    } else {\n                        ctrl\n                    };\n                }\n\n                Response {\n                    repaint: true,\n                    consumed: false,\n                }\n            }\n\n            // Things that may require repaint:\n            WindowEvent::RedrawRequested\n            | WindowEvent::CursorEntered { .. }\n            | WindowEvent::Destroyed\n            | WindowEvent::Occluded(_)\n            | WindowEvent::Resized(_)\n            | WindowEvent::Moved(_)\n            | WindowEvent::ThemeChanged(_)\n            | WindowEvent::TouchpadPressure { .. }\n            | WindowEvent::CloseRequested => Response {\n                repaint: true,\n                consumed: false,\n            },\n\n            // Things we completely ignore:\n            WindowEvent::ActivationTokenDone { .. }\n            | WindowEvent::AxisMotion { .. }\n            | WindowEvent::DoubleTapGesture { .. }\n            | WindowEvent::RotationGesture { .. }\n            | WindowEvent::PanGesture { .. } => Response {\n                repaint: false,\n                consumed: false,\n            },\n\n            WindowEvent::PinchGesture { delta, .. } => {\n                // Positive delta values indicate magnification (zooming in).\n                // Negative delta values indicate shrinking (zooming out).\n                let zoom_factor = (*delta as f32).exp();\n                viewport\n                    .raw_input\n                    .events\n                    .push(egui::Event::Zoom(zoom_factor));\n                Response {\n                    repaint: true,\n                    consumed: self.ctx.egui_wants_pointer_input(),\n                }\n            }\n            WindowEvent::Ime(_) => Response::default(),\n        };\n\n        let gui_res = self.gui.borrow_mut().on_window_event(event);\n\n        Response {\n            repaint: res.repaint || gui_res.repaint,\n            consumed: res.consumed || gui_res.consumed,\n        }\n    }\n\n    pub fn on_mouse_motion(&mut self, delta: (f64, f64)) {\n        let State {\n            viewports, focused, ..\n        } = &mut *self.state.borrow_mut();\n        if let Some(id) = *focused\n            && let Some(viewport) = viewports.get_mut(&id)\n        {\n            viewport\n                .raw_input\n                .events\n                .push(egui::Event::MouseMoved(egui::Vec2 {\n                    x: delta.0 as f32,\n                    y: delta.1 as f32,\n                }));\n        }\n    }\n\n    fn on_mouse_button_input(\n        pointer_pos: Option<egui::Pos2>,\n        viewport: &mut Viewport,\n        state: ElementState,\n        button: MouseButton,\n    ) {\n        if let Some(pos) = pointer_pos\n            && let Some(button) = pointer_button_from_mouse(button)\n        {\n            let pressed = state == ElementState::Pressed;\n\n            viewport.raw_input.events.push(egui::Event::PointerButton {\n                pos,\n                button,\n                pressed,\n                modifiers: viewport.raw_input.modifiers,\n            });\n        }\n    }\n\n    fn on_cursor_moved(\n        viewport: &mut Viewport,\n        pixels_per_point: f32,\n        pos_in_pixels: PhysicalPosition<f64>,\n    ) {\n        let pos_in_points = egui::pos2(\n            pos_in_pixels.x as f32 / pixels_per_point,\n            pos_in_pixels.y as f32 / pixels_per_point,\n        );\n        viewport.cursor_pos = Some(pos_in_points);\n\n        viewport\n            .raw_input\n            .events\n            .push(egui::Event::PointerMoved(pos_in_points));\n    }\n\n    fn on_touch(\n        viewport: &mut Viewport,\n        pointer_touch_id: &mut Option<u64>,\n        pixels_per_point: f32,\n        touch: &Touch,\n    ) {\n        // Emit touch event\n        viewport.raw_input.events.push(egui::Event::Touch {\n            device_id: egui::TouchDeviceId(egui::epaint::util::hash(touch.device_id)),\n            id: egui::TouchId::from(touch.id),\n            phase: match touch.phase {\n                TouchPhase::Started => egui::TouchPhase::Start,\n                TouchPhase::Moved => egui::TouchPhase::Move,\n                TouchPhase::Ended => egui::TouchPhase::End,\n                TouchPhase::Cancelled => egui::TouchPhase::Cancel,\n            },\n            pos: egui::pos2(\n                touch.location.x as f32 / pixels_per_point,\n                touch.location.y as f32 / pixels_per_point,\n            ),\n            force: match touch.force {\n                Some(Force::Normalized(force)) => Some(force as f32),\n                Some(Force::Calibrated {\n                    force,\n                    max_possible_force,\n                    ..\n                }) => Some((force / max_possible_force) as f32),\n                None => None,\n            },\n        });\n        // If we're not yet translating a touch or we're translating this very\n        // touch …\n        if pointer_touch_id.is_none()\n            || pointer_touch_id.is_some_and(|touch_id| touch_id == touch.id)\n        {\n            // … emit PointerButton resp. PointerMoved events to emulate mouse\n            match touch.phase {\n                TouchPhase::Started => {\n                    *pointer_touch_id = Some(touch.id);\n                    // First move the pointer to the right location\n                    Self::on_cursor_moved(viewport, pixels_per_point, touch.location);\n                    Self::on_mouse_button_input(\n                        viewport.cursor_pos,\n                        viewport,\n                        ElementState::Pressed,\n                        MouseButton::Left,\n                    );\n                }\n                TouchPhase::Moved => {\n                    Self::on_cursor_moved(viewport, pixels_per_point, touch.location);\n                }\n                TouchPhase::Ended => {\n                    *pointer_touch_id = None;\n                    Self::on_mouse_button_input(\n                        viewport.cursor_pos,\n                        viewport,\n                        ElementState::Released,\n                        MouseButton::Left,\n                    );\n                    // The pointer should vanish completely to not get any\n                    // hover effects\n                    viewport.cursor_pos = None;\n                    viewport.raw_input.events.push(egui::Event::PointerGone);\n                }\n                TouchPhase::Cancelled => {\n                    *pointer_touch_id = None;\n                    viewport.cursor_pos = None;\n                    viewport.raw_input.events.push(egui::Event::PointerGone);\n                }\n            }\n        }\n    }\n\n    fn on_mouse_wheel(viewport: &mut Viewport, pixels_per_point: f32, delta: MouseScrollDelta) {\n        let modifiers = viewport.raw_input.modifiers;\n        let (unit, delta) = match delta {\n            MouseScrollDelta::LineDelta(x, y) => (egui::MouseWheelUnit::Line, egui::vec2(x, y)),\n            MouseScrollDelta::PixelDelta(PhysicalPosition { x, y }) => (\n                egui::MouseWheelUnit::Point,\n                egui::vec2(x as f32, y as f32) / pixels_per_point,\n            ),\n        };\n        viewport.raw_input.events.push(egui::Event::MouseWheel {\n            unit,\n            delta,\n            phase: egui::TouchPhase::Move,\n            modifiers,\n        });\n    }\n\n    fn on_keyboard_input(viewport: &mut Viewport, event: &KeyEvent) {\n        let KeyEvent {\n            // Represents the position of a key independent of the currently active layout.\n            //\n            // It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode).\n            // The most prevalent use case for this is games. For example the default keys for the player\n            // to move around might be the W, A, S, and D keys on a US layout. The position of these keys\n            // is more important than their label, so they should map to Z, Q, S, and D on an \"AZERTY\"\n            // layout. (This value is `KeyCode::KeyW` for the Z key on an AZERTY layout.)\n            physical_key,\n            // Represents the results of a keymap, i.e. what character a certain key press represents.\n            // When telling users \"Press Ctrl-F to find\", this is where we should\n            // look for the \"F\" key, because they may have a dvorak layout on\n            // a qwerty keyboard, and so the logical \"F\" character may not be located on the physical `KeyCode::KeyF` position.\n            logical_key,\n            text,\n            state,\n            ..\n        } = event;\n\n        let pressed = *state == ElementState::Pressed;\n\n        let physical_key = if let PhysicalKey::Code(keycode) = *physical_key {\n            key_from_keycode(keycode)\n        } else {\n            None\n        };\n\n        let logical_key = key_from_winit_key(logical_key);\n\n        // Helpful logging to enable when adding new key support\n        tracing::trace!(\n            \"logical {:?} -> {:?},  physical {:?} -> {:?}\",\n            event.logical_key,\n            logical_key,\n            event.physical_key,\n            physical_key\n        );\n\n        let modifiers = viewport.raw_input.modifiers;\n        if let Some(logical_key) = logical_key {\n            if pressed {\n                if is_cut_command(modifiers, logical_key) {\n                    viewport.raw_input.events.push(egui::Event::Cut);\n                    return;\n                } else if is_copy_command(modifiers, logical_key) {\n                    viewport.raw_input.events.push(egui::Event::Copy);\n                    return;\n                } else if is_paste_command(modifiers, logical_key) {\n                    if let Some(contents) = viewport.clipboard.get() {\n                        let contents = contents.replace(\"\\r\\n\", \"\\n\");\n                        if !contents.is_empty() {\n                            viewport.raw_input.events.push(egui::Event::Paste(contents));\n                        }\n                    }\n                    return;\n                }\n            }\n\n            viewport.raw_input.events.push(egui::Event::Key {\n                key: logical_key,\n                physical_key,\n                pressed,\n                repeat: false, // egui will fill this in for us!\n                modifiers,\n            });\n        }\n\n        if let Some(text) = &text {\n            // Make sure there is text, and that it is not control characters\n            // (e.g. delete is sent as \"\\u{f728}\" on macOS).\n            if !text.is_empty() && text.chars().all(is_printable_char) {\n                // On some platforms we get here when the user presses Cmd-C (copy), ctrl-W, etc.\n                // We need to ignore these characters that are side-effects of commands.\n                // Also make sure the key is pressed (not released). On Linux, text might\n                // contain some data even when the key is released.\n                let is_cmd = modifiers.ctrl || modifiers.command || modifiers.mac_cmd;\n                if pressed && !is_cmd {\n                    viewport\n                        .raw_input\n                        .events\n                        .push(egui::Event::Text(text.to_string()));\n                }\n            }\n        }\n    }\n\n    /// Handle gamepad event updates.\n    pub fn on_gamepad_update(&self, gamepads: &Gamepads) -> Response {\n        if self.gui.borrow().keybinds.wants_input() && gamepads.has_events() {\n            Response {\n                consumed: true,\n                repaint: true,\n            }\n        } else {\n            Response::default()\n        }\n    }\n}\n\nimpl TryFrom<(egui::Key, egui::Modifiers)> for Input {\n    type Error = ();\n\n    fn try_from((key, modifiers): (egui::Key, egui::Modifiers)) -> Result<Self, Self::Error> {\n        let keycode = keycode_from_key(key).ok_or(())?;\n        let modifiers = modifiers_state_from_modifiers(modifiers);\n        Ok(Input::Key(keycode, modifiers))\n    }\n}\n\nimpl From<PointerButton> for Input {\n    fn from(button: PointerButton) -> Self {\n        Input::Mouse(mouse_button_from_pointer(button))\n    }\n}\n\npub fn is_cut_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {\n    keycode == egui::Key::Cut\n        || (modifiers.command && keycode == egui::Key::X)\n        || (cfg!(target_os = \"windows\") && modifiers.shift && keycode == egui::Key::Delete)\n}\n\npub fn is_copy_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {\n    keycode == egui::Key::Copy\n        || (modifiers.command && keycode == egui::Key::C)\n        || (cfg!(target_os = \"windows\") && modifiers.ctrl && keycode == egui::Key::Insert)\n}\n\npub fn is_paste_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {\n    keycode == egui::Key::Paste\n        || (modifiers.command && keycode == egui::Key::V)\n        || (cfg!(target_os = \"windows\") && modifiers.shift && keycode == egui::Key::Insert)\n}\n\n/// Winit sends special keys (backspace, delete, F1, …) as characters.\n/// Ignore those.\n/// We also ignore '\\r', '\\n', '\\t'.\n/// Newlines are handled by the `Key::Enter` event.\npub const fn is_printable_char(chr: char) -> bool {\n    let is_in_private_use_area = '\\u{e000}' <= chr && chr <= '\\u{f8ff}'\n        || '\\u{f0000}' <= chr && chr <= '\\u{ffffd}'\n        || '\\u{100000}' <= chr && chr <= '\\u{10fffd}';\n\n    !is_in_private_use_area && !chr.is_ascii_control()\n}\n\npub fn key_from_winit_key(key: &winit::keyboard::Key) -> Option<egui::Key> {\n    match key {\n        winit::keyboard::Key::Named(named_key) => key_from_named_key(*named_key),\n        winit::keyboard::Key::Character(str) => egui::Key::from_name(str.as_str()),\n        winit::keyboard::Key::Unidentified(_) | winit::keyboard::Key::Dead(_) => None,\n    }\n}\n\npub fn key_from_named_key(named_key: winit::keyboard::NamedKey) -> Option<egui::Key> {\n    use egui::Key;\n    use winit::keyboard::NamedKey;\n\n    Some(match named_key {\n        NamedKey::Enter => Key::Enter,\n        NamedKey::Tab => Key::Tab,\n        NamedKey::ArrowDown => Key::ArrowDown,\n        NamedKey::ArrowLeft => Key::ArrowLeft,\n        NamedKey::ArrowRight => Key::ArrowRight,\n        NamedKey::ArrowUp => Key::ArrowUp,\n        NamedKey::End => Key::End,\n        NamedKey::Home => Key::Home,\n        NamedKey::PageDown => Key::PageDown,\n        NamedKey::PageUp => Key::PageUp,\n        NamedKey::Backspace => Key::Backspace,\n        NamedKey::Delete => Key::Delete,\n        NamedKey::Insert => Key::Insert,\n        NamedKey::Escape => Key::Escape,\n        NamedKey::Cut => Key::Cut,\n        NamedKey::Copy => Key::Copy,\n        NamedKey::Paste => Key::Paste,\n\n        NamedKey::Space => Key::Space,\n\n        NamedKey::F1 => Key::F1,\n        NamedKey::F2 => Key::F2,\n        NamedKey::F3 => Key::F3,\n        NamedKey::F4 => Key::F4,\n        NamedKey::F5 => Key::F5,\n        NamedKey::F6 => Key::F6,\n        NamedKey::F7 => Key::F7,\n        NamedKey::F8 => Key::F8,\n        NamedKey::F9 => Key::F9,\n        NamedKey::F10 => Key::F10,\n        NamedKey::F11 => Key::F11,\n        NamedKey::F12 => Key::F12,\n        NamedKey::F13 => Key::F13,\n        NamedKey::F14 => Key::F14,\n        NamedKey::F15 => Key::F15,\n        NamedKey::F16 => Key::F16,\n        NamedKey::F17 => Key::F17,\n        NamedKey::F18 => Key::F18,\n        NamedKey::F19 => Key::F19,\n        NamedKey::F20 => Key::F20,\n        NamedKey::F21 => Key::F21,\n        NamedKey::F22 => Key::F22,\n        NamedKey::F23 => Key::F23,\n        NamedKey::F24 => Key::F24,\n        NamedKey::F25 => Key::F25,\n        NamedKey::F26 => Key::F26,\n        NamedKey::F27 => Key::F27,\n        NamedKey::F28 => Key::F28,\n        NamedKey::F29 => Key::F29,\n        NamedKey::F30 => Key::F30,\n        NamedKey::F31 => Key::F31,\n        NamedKey::F32 => Key::F32,\n        NamedKey::F33 => Key::F33,\n        NamedKey::F34 => Key::F34,\n        NamedKey::F35 => Key::F35,\n        _ => {\n            tracing::trace!(\"Unknown key: {named_key:?}\");\n            return None;\n        }\n    })\n}\n\npub const fn key_from_keycode(keycode: KeyCode) -> Option<egui::Key> {\n    Some(match keycode {\n        KeyCode::ArrowDown => egui::Key::ArrowDown,\n        KeyCode::ArrowLeft => egui::Key::ArrowLeft,\n        KeyCode::ArrowRight => egui::Key::ArrowRight,\n        KeyCode::ArrowUp => egui::Key::ArrowUp,\n\n        KeyCode::Escape => egui::Key::Escape,\n        KeyCode::Tab => egui::Key::Tab,\n        KeyCode::Backspace => egui::Key::Backspace,\n        KeyCode::Enter | KeyCode::NumpadEnter => egui::Key::Enter,\n\n        KeyCode::Insert => egui::Key::Insert,\n        KeyCode::Delete => egui::Key::Delete,\n        KeyCode::Home => egui::Key::Home,\n        KeyCode::End => egui::Key::End,\n        KeyCode::PageUp => egui::Key::PageUp,\n        KeyCode::PageDown => egui::Key::PageDown,\n\n        // Punctuation\n        KeyCode::Space => egui::Key::Space,\n        KeyCode::Comma => egui::Key::Comma,\n        KeyCode::Period => egui::Key::Period,\n        KeyCode::Semicolon => egui::Key::Semicolon,\n        KeyCode::Backslash => egui::Key::Backslash,\n        KeyCode::Slash | KeyCode::NumpadDivide => egui::Key::Slash,\n        KeyCode::BracketLeft => egui::Key::OpenBracket,\n        KeyCode::BracketRight => egui::Key::CloseBracket,\n        KeyCode::Backquote => egui::Key::Backtick,\n\n        KeyCode::Cut => egui::Key::Cut,\n        KeyCode::Copy => egui::Key::Copy,\n        KeyCode::Paste => egui::Key::Paste,\n        KeyCode::Minus | KeyCode::NumpadSubtract => egui::Key::Minus,\n        KeyCode::NumpadAdd => egui::Key::Plus,\n        KeyCode::Equal => egui::Key::Equals,\n\n        KeyCode::Digit0 | KeyCode::Numpad0 => egui::Key::Num0,\n        KeyCode::Digit1 | KeyCode::Numpad1 => egui::Key::Num1,\n        KeyCode::Digit2 | KeyCode::Numpad2 => egui::Key::Num2,\n        KeyCode::Digit3 | KeyCode::Numpad3 => egui::Key::Num3,\n        KeyCode::Digit4 | KeyCode::Numpad4 => egui::Key::Num4,\n        KeyCode::Digit5 | KeyCode::Numpad5 => egui::Key::Num5,\n        KeyCode::Digit6 | KeyCode::Numpad6 => egui::Key::Num6,\n        KeyCode::Digit7 | KeyCode::Numpad7 => egui::Key::Num7,\n        KeyCode::Digit8 | KeyCode::Numpad8 => egui::Key::Num8,\n        KeyCode::Digit9 | KeyCode::Numpad9 => egui::Key::Num9,\n\n        KeyCode::KeyA => egui::Key::A,\n        KeyCode::KeyB => egui::Key::B,\n        KeyCode::KeyC => egui::Key::C,\n        KeyCode::KeyD => egui::Key::D,\n        KeyCode::KeyE => egui::Key::E,\n        KeyCode::KeyF => egui::Key::F,\n        KeyCode::KeyG => egui::Key::G,\n        KeyCode::KeyH => egui::Key::H,\n        KeyCode::KeyI => egui::Key::I,\n        KeyCode::KeyJ => egui::Key::J,\n        KeyCode::KeyK => egui::Key::K,\n        KeyCode::KeyL => egui::Key::L,\n        KeyCode::KeyM => egui::Key::M,\n        KeyCode::KeyN => egui::Key::N,\n        KeyCode::KeyO => egui::Key::O,\n        KeyCode::KeyP => egui::Key::P,\n        KeyCode::KeyQ => egui::Key::Q,\n        KeyCode::KeyR => egui::Key::R,\n        KeyCode::KeyS => egui::Key::S,\n        KeyCode::KeyT => egui::Key::T,\n        KeyCode::KeyU => egui::Key::U,\n        KeyCode::KeyV => egui::Key::V,\n        KeyCode::KeyW => egui::Key::W,\n        KeyCode::KeyX => egui::Key::X,\n        KeyCode::KeyY => egui::Key::Y,\n        KeyCode::KeyZ => egui::Key::Z,\n\n        KeyCode::F1 => egui::Key::F1,\n        KeyCode::F2 => egui::Key::F2,\n        KeyCode::F3 => egui::Key::F3,\n        KeyCode::F4 => egui::Key::F4,\n        KeyCode::F5 => egui::Key::F5,\n        KeyCode::F6 => egui::Key::F6,\n        KeyCode::F7 => egui::Key::F7,\n        KeyCode::F8 => egui::Key::F8,\n        KeyCode::F9 => egui::Key::F9,\n        KeyCode::F10 => egui::Key::F10,\n        KeyCode::F11 => egui::Key::F11,\n        KeyCode::F12 => egui::Key::F12,\n        KeyCode::F13 => egui::Key::F13,\n        KeyCode::F14 => egui::Key::F14,\n        KeyCode::F15 => egui::Key::F15,\n        KeyCode::F16 => egui::Key::F16,\n        KeyCode::F17 => egui::Key::F17,\n        KeyCode::F18 => egui::Key::F18,\n        KeyCode::F19 => egui::Key::F19,\n        KeyCode::F20 => egui::Key::F20,\n        KeyCode::F21 => egui::Key::F21,\n        KeyCode::F22 => egui::Key::F22,\n        KeyCode::F23 => egui::Key::F23,\n        KeyCode::F24 => egui::Key::F24,\n        KeyCode::F25 => egui::Key::F25,\n        KeyCode::F26 => egui::Key::F26,\n        KeyCode::F27 => egui::Key::F27,\n        KeyCode::F28 => egui::Key::F28,\n        KeyCode::F29 => egui::Key::F29,\n        KeyCode::F30 => egui::Key::F30,\n        KeyCode::F31 => egui::Key::F31,\n        KeyCode::F32 => egui::Key::F32,\n        KeyCode::F33 => egui::Key::F33,\n        KeyCode::F34 => egui::Key::F34,\n        KeyCode::F35 => egui::Key::F35,\n        _ => {\n            return None;\n        }\n    })\n}\n\npub const fn keycode_from_key(key: egui::Key) -> Option<KeyCode> {\n    Some(match key {\n        egui::Key::ArrowDown => KeyCode::ArrowDown,\n        egui::Key::ArrowLeft => KeyCode::ArrowLeft,\n        egui::Key::ArrowRight => KeyCode::ArrowRight,\n        egui::Key::ArrowUp => KeyCode::ArrowUp,\n\n        egui::Key::Escape => KeyCode::Escape,\n        egui::Key::Tab => KeyCode::Tab,\n        egui::Key::Backspace => KeyCode::Backspace,\n        egui::Key::Enter => KeyCode::Enter,\n\n        egui::Key::Insert => KeyCode::Insert,\n        egui::Key::Delete => KeyCode::Delete,\n        egui::Key::Home => KeyCode::Home,\n        egui::Key::End => KeyCode::End,\n        egui::Key::PageUp => KeyCode::PageUp,\n        egui::Key::PageDown => KeyCode::PageDown,\n\n        // Punctuation\n        egui::Key::Space => KeyCode::Space,\n        egui::Key::Comma => KeyCode::Comma,\n        egui::Key::Period => KeyCode::Period,\n        egui::Key::Semicolon => KeyCode::Semicolon,\n        egui::Key::Backslash => KeyCode::Backslash,\n        egui::Key::Slash => KeyCode::Slash,\n        egui::Key::OpenBracket => KeyCode::BracketLeft,\n        egui::Key::CloseBracket => KeyCode::BracketRight,\n\n        egui::Key::Cut => KeyCode::Cut,\n        egui::Key::Copy => KeyCode::Copy,\n        egui::Key::Paste => KeyCode::Paste,\n        egui::Key::Minus => KeyCode::Minus,\n        egui::Key::Plus => KeyCode::NumpadAdd,\n        egui::Key::Equals => KeyCode::Equal,\n\n        egui::Key::Num0 => KeyCode::Digit0,\n        egui::Key::Num1 => KeyCode::Digit1,\n        egui::Key::Num2 => KeyCode::Digit2,\n        egui::Key::Num3 => KeyCode::Digit3,\n        egui::Key::Num4 => KeyCode::Digit4,\n        egui::Key::Num5 => KeyCode::Digit5,\n        egui::Key::Num6 => KeyCode::Digit6,\n        egui::Key::Num7 => KeyCode::Digit7,\n        egui::Key::Num8 => KeyCode::Digit8,\n        egui::Key::Num9 => KeyCode::Digit9,\n\n        egui::Key::A => KeyCode::KeyA,\n        egui::Key::B => KeyCode::KeyB,\n        egui::Key::C => KeyCode::KeyC,\n        egui::Key::D => KeyCode::KeyD,\n        egui::Key::E => KeyCode::KeyE,\n        egui::Key::F => KeyCode::KeyF,\n        egui::Key::G => KeyCode::KeyG,\n        egui::Key::H => KeyCode::KeyH,\n        egui::Key::I => KeyCode::KeyI,\n        egui::Key::J => KeyCode::KeyJ,\n        egui::Key::K => KeyCode::KeyK,\n        egui::Key::L => KeyCode::KeyL,\n        egui::Key::M => KeyCode::KeyM,\n        egui::Key::N => KeyCode::KeyN,\n        egui::Key::O => KeyCode::KeyO,\n        egui::Key::P => KeyCode::KeyP,\n        egui::Key::Q => KeyCode::KeyQ,\n        egui::Key::R => KeyCode::KeyR,\n        egui::Key::S => KeyCode::KeyS,\n        egui::Key::T => KeyCode::KeyT,\n        egui::Key::U => KeyCode::KeyU,\n        egui::Key::V => KeyCode::KeyV,\n        egui::Key::W => KeyCode::KeyW,\n        egui::Key::X => KeyCode::KeyX,\n        egui::Key::Y => KeyCode::KeyY,\n        egui::Key::Z => KeyCode::KeyZ,\n\n        egui::Key::F1 => KeyCode::F1,\n        egui::Key::F2 => KeyCode::F2,\n        egui::Key::F3 => KeyCode::F3,\n        egui::Key::F4 => KeyCode::F4,\n        egui::Key::F5 => KeyCode::F5,\n        egui::Key::F6 => KeyCode::F6,\n        egui::Key::F7 => KeyCode::F7,\n        egui::Key::F8 => KeyCode::F8,\n        egui::Key::F9 => KeyCode::F9,\n        egui::Key::F10 => KeyCode::F10,\n        egui::Key::F11 => KeyCode::F11,\n        egui::Key::F12 => KeyCode::F12,\n        egui::Key::F13 => KeyCode::F13,\n        egui::Key::F14 => KeyCode::F14,\n        egui::Key::F15 => KeyCode::F15,\n        egui::Key::F16 => KeyCode::F16,\n        egui::Key::F17 => KeyCode::F17,\n        egui::Key::F18 => KeyCode::F18,\n        egui::Key::F19 => KeyCode::F19,\n        egui::Key::F20 => KeyCode::F20,\n        egui::Key::F21 => KeyCode::F21,\n        egui::Key::F22 => KeyCode::F22,\n        egui::Key::F23 => KeyCode::F23,\n        egui::Key::F24 => KeyCode::F24,\n        egui::Key::F25 => KeyCode::F25,\n        egui::Key::F26 => KeyCode::F26,\n        egui::Key::F27 => KeyCode::F27,\n        egui::Key::F28 => KeyCode::F28,\n        egui::Key::F29 => KeyCode::F29,\n        egui::Key::F30 => KeyCode::F30,\n        egui::Key::F31 => KeyCode::F31,\n        egui::Key::F32 => KeyCode::F32,\n        egui::Key::F33 => KeyCode::F33,\n        egui::Key::F34 => KeyCode::F34,\n        egui::Key::F35 => KeyCode::F35,\n\n        _ => return None,\n    })\n}\n\npub fn modifiers_from_modifiers_state(modifier_state: ModifiersState) -> egui::Modifiers {\n    egui::Modifiers {\n        alt: modifier_state.alt_key(),\n        ctrl: modifier_state.control_key(),\n        shift: modifier_state.shift_key(),\n        #[cfg(target_os = \"macos\")]\n        mac_cmd: modifier_state.super_key(),\n        #[cfg(not(target_os = \"macos\"))]\n        mac_cmd: false,\n        #[cfg(target_os = \"macos\")]\n        command: modifier_state.super_key(),\n        #[cfg(not(target_os = \"macos\"))]\n        command: modifier_state.control_key(),\n    }\n}\n\npub fn modifiers_state_from_modifiers(modifiers: egui::Modifiers) -> ModifiersState {\n    let mut modifiers_state = ModifiersState::empty();\n    if modifiers.shift {\n        modifiers_state |= ModifiersState::SHIFT;\n    }\n    if modifiers.ctrl {\n        modifiers_state |= ModifiersState::CONTROL;\n    }\n    if modifiers.alt {\n        modifiers_state |= ModifiersState::ALT;\n    }\n    #[cfg(target_os = \"macos\")]\n    if modifiers.mac_cmd {\n        modifiers_state |= ModifiersState::SUPER;\n    }\n    // TODO: egui doesn't seem to support SUPER on Windows/Linux\n    modifiers_state\n}\n\npub const fn pointer_button_from_mouse(button: MouseButton) -> Option<PointerButton> {\n    Some(match button {\n        MouseButton::Left => PointerButton::Primary,\n        MouseButton::Right => PointerButton::Secondary,\n        MouseButton::Middle => PointerButton::Middle,\n        MouseButton::Back => PointerButton::Extra1,\n        MouseButton::Forward => PointerButton::Extra2,\n        MouseButton::Other(_) => return None,\n    })\n}\n\npub const fn mouse_button_from_pointer(button: PointerButton) -> MouseButton {\n    match button {\n        PointerButton::Primary => MouseButton::Left,\n        PointerButton::Secondary => MouseButton::Right,\n        PointerButton::Middle => MouseButton::Middle,\n        PointerButton::Extra1 => MouseButton::Back,\n        PointerButton::Extra2 => MouseButton::Forward,\n    }\n}\n\npub const fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::CursorIcon> {\n    use egui::CursorIcon;\n\n    match cursor_icon {\n        CursorIcon::None => None,\n\n        CursorIcon::Alias => Some(winit::window::CursorIcon::Alias),\n        CursorIcon::AllScroll => Some(winit::window::CursorIcon::AllScroll),\n        CursorIcon::Cell => Some(winit::window::CursorIcon::Cell),\n        CursorIcon::ContextMenu => Some(winit::window::CursorIcon::ContextMenu),\n        CursorIcon::Copy => Some(winit::window::CursorIcon::Copy),\n        CursorIcon::Crosshair => Some(winit::window::CursorIcon::Crosshair),\n        CursorIcon::Default => Some(winit::window::CursorIcon::Default),\n        CursorIcon::Grab => Some(winit::window::CursorIcon::Grab),\n        CursorIcon::Grabbing => Some(winit::window::CursorIcon::Grabbing),\n        CursorIcon::Help => Some(winit::window::CursorIcon::Help),\n        CursorIcon::Move => Some(winit::window::CursorIcon::Move),\n        CursorIcon::NoDrop => Some(winit::window::CursorIcon::NoDrop),\n        CursorIcon::NotAllowed => Some(winit::window::CursorIcon::NotAllowed),\n        CursorIcon::PointingHand => Some(winit::window::CursorIcon::Pointer),\n        CursorIcon::Progress => Some(winit::window::CursorIcon::Progress),\n\n        CursorIcon::ResizeHorizontal => Some(winit::window::CursorIcon::EwResize),\n        CursorIcon::ResizeNeSw => Some(winit::window::CursorIcon::NeswResize),\n        CursorIcon::ResizeNwSe => Some(winit::window::CursorIcon::NwseResize),\n        CursorIcon::ResizeVertical => Some(winit::window::CursorIcon::NsResize),\n\n        CursorIcon::ResizeEast => Some(winit::window::CursorIcon::EResize),\n        CursorIcon::ResizeSouthEast => Some(winit::window::CursorIcon::SeResize),\n        CursorIcon::ResizeSouth => Some(winit::window::CursorIcon::SResize),\n        CursorIcon::ResizeSouthWest => Some(winit::window::CursorIcon::SwResize),\n        CursorIcon::ResizeWest => Some(winit::window::CursorIcon::WResize),\n        CursorIcon::ResizeNorthWest => Some(winit::window::CursorIcon::NwResize),\n        CursorIcon::ResizeNorth => Some(winit::window::CursorIcon::NResize),\n        CursorIcon::ResizeNorthEast => Some(winit::window::CursorIcon::NeResize),\n        CursorIcon::ResizeColumn => Some(winit::window::CursorIcon::ColResize),\n        CursorIcon::ResizeRow => Some(winit::window::CursorIcon::RowResize),\n\n        CursorIcon::Text => Some(winit::window::CursorIcon::Text),\n        CursorIcon::VerticalText => Some(winit::window::CursorIcon::VerticalText),\n        CursorIcon::Wait => Some(winit::window::CursorIcon::Wait),\n        CursorIcon::ZoomIn => Some(winit::window::CursorIcon::ZoomIn),\n        CursorIcon::ZoomOut => Some(winit::window::CursorIcon::ZoomOut),\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/renderer/gui/keybinds.rs",
    "content": "use crate::nes::{\n    action::Action,\n    config::Config,\n    event::{ConfigEvent, NesEventProxy, RendererEvent},\n    input::{Gamepads, Input},\n    renderer::gui::lib::ViewportOptions,\n};\nuse egui::{\n    Align2, Button, CentralPanel, Context, Grid, ScrollArea, Ui, Vec2, ViewportClass, ViewportId,\n};\nuse parking_lot::Mutex;\nuse std::sync::{\n    Arc,\n    atomic::{AtomicBool, Ordering},\n};\nuse tetanes_core::{input::Player, time::Instant};\nuse uuid::Uuid;\nuse winit::event::ElementState;\n\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]\npub enum Tab {\n    #[default]\n    Shortcuts,\n    Joypad(Player),\n}\n\n#[derive(Debug)]\n#[must_use]\npub struct State {\n    tx: NesEventProxy,\n    tab: Tab,\n    pending_input: Option<PendingInput>,\n    gamepad_unassign_confirm: Option<(Player, Player, Uuid)>,\n}\n\n#[derive(Debug)]\n#[must_use]\npub struct Keybinds {\n    pub id: ViewportId,\n    open: Arc<AtomicBool>,\n    state: Arc<Mutex<State>>,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct PendingInput {\n    action: Action,\n    input: Option<Input>,\n    binding: usize,\n    conflict: Option<Action>,\n}\n\n#[derive(Debug)]\n#[must_use]\npub struct GamepadState {\n    input_events: Vec<(Input, ElementState)>,\n    connected: Option<Vec<ConnectedGamepad>>,\n}\n\n#[derive(Debug, PartialEq, Eq)]\n#[must_use]\npub struct ConnectedGamepad {\n    uuid: Uuid,\n    name: String,\n    assignment: Option<Player>,\n}\n\nimpl Keybinds {\n    const TITLE: &'static str = \"TetaNES - Keybinds\";\n\n    pub fn new(tx: NesEventProxy) -> Self {\n        Self {\n            id: ViewportId::from_hash_of(Self::TITLE),\n            open: Arc::new(AtomicBool::new(false)),\n            state: Arc::new(Mutex::new(State {\n                tx,\n                tab: Tab::default(),\n                pending_input: None,\n                gamepad_unassign_confirm: None,\n            })),\n        }\n    }\n\n    pub fn wants_input(&self) -> bool {\n        self.state.try_lock().is_some_and(|state| {\n            state.pending_input.is_some() || state.gamepad_unassign_confirm.is_some()\n        })\n    }\n\n    pub fn open(&self) -> bool {\n        self.open.load(Ordering::Acquire)\n    }\n\n    pub fn set_open(&self, open: bool, ctx: &Context) {\n        self.open.store(open, Ordering::Release);\n        if open {\n            ctx.send_viewport_cmd_to(self.id, egui::ViewportCommand::Close);\n        }\n    }\n\n    pub fn toggle_open(&self, ctx: &Context) {\n        let Ok(open) = self\n            .open\n            .fetch_update(Ordering::Release, Ordering::Acquire, |open| Some(!open))\n        else {\n            return;\n        };\n        if open {\n            ctx.send_viewport_cmd_to(self.id, egui::ViewportCommand::Close);\n        }\n    }\n\n    pub fn show(&mut self, ui: &mut Ui, opts: ViewportOptions, cfg: Config, gamepads: &Gamepads) {\n        if !self.open() {\n            return;\n        }\n\n        let open = Arc::clone(&self.open);\n        let state = Arc::clone(&self.state);\n\n        let mut viewport_builder = egui::ViewportBuilder::default().with_title(Self::TITLE);\n        if opts.always_on_top {\n            viewport_builder = viewport_builder.with_always_on_top();\n        }\n        let gamepad_state = GamepadState {\n            input_events: gamepads\n                .events()\n                .filter_map(|event| gamepads.input_from_event(event, &cfg))\n                .collect::<Vec<_>>(),\n            connected: gamepads.list().map(|gamepad_list| {\n                gamepad_list\n                    .map(|(_, gamepad)| {\n                        let uuid = Gamepads::create_uuid(&gamepad);\n                        ConnectedGamepad {\n                            uuid,\n                            name: gamepad.name().to_string(),\n                            assignment: cfg.input.gamepad_assignment(&uuid),\n                        }\n                    })\n                    .collect::<Vec<_>>()\n            }),\n        };\n\n        ui.show_viewport_deferred(self.id, viewport_builder, move |ui, class| {\n            if class == ViewportClass::EmbeddedWindow {\n                let mut window_open = open.load(Ordering::Acquire);\n                egui::Window::new(Keybinds::TITLE)\n                    .open(&mut window_open)\n                    .default_rect(ui.content_rect().shrink(16.0))\n                    .show(ui, |ui| {\n                        state.lock().ui(ui, opts.enabled, &cfg, &gamepad_state);\n                    });\n                open.store(window_open, Ordering::Release);\n            } else {\n                CentralPanel::default().show_inside(ui, |ui| {\n                    state.lock().ui(ui, opts.enabled, &cfg, &gamepad_state);\n                });\n                if ui.input(|i| i.viewport().close_requested()) {\n                    open.store(false, Ordering::Release);\n                }\n            }\n            if !open.load(Ordering::Acquire) {\n                let mut state = state.lock();\n                state.pending_input = None;\n                state.gamepad_unassign_confirm = None;\n            }\n        });\n    }\n}\n\nimpl State {\n    fn ui(&mut self, ui: &mut Ui, enabled: bool, cfg: &Config, gamepad_state: &GamepadState) {\n        self.show_set_keybind_window(ui.ctx(), cfg, &gamepad_state.input_events);\n        self.show_gamepad_unassign_window(ui.ctx());\n\n        ui.add_enabled_ui(enabled, |ui| {\n            ui.horizontal(|ui| {\n                ui.selectable_value(&mut self.tab, Tab::Shortcuts, \"Shortcuts\");\n                ui.selectable_value(&mut self.tab, Tab::Joypad(Player::One), \"Player1\");\n                ui.selectable_value(&mut self.tab, Tab::Joypad(Player::Two), \"Player2\");\n                ui.selectable_value(&mut self.tab, Tab::Joypad(Player::Three), \"Player3\");\n                ui.selectable_value(&mut self.tab, Tab::Joypad(Player::Four), \"Player4\");\n            });\n\n            ui.separator();\n\n            match self.tab {\n                Tab::Shortcuts => self.list(ui, None, cfg, gamepad_state.connected.as_deref()),\n                Tab::Joypad(player) => {\n                    self.list(ui, Some(player), cfg, gamepad_state.connected.as_deref())\n                }\n            }\n        });\n    }\n\n    fn list(\n        &mut self,\n        ui: &mut Ui,\n        player: Option<Player>,\n        cfg: &Config,\n        connected_gamepads: Option<&[ConnectedGamepad]>,\n    ) {\n        ui.set_min_height(ui.available_height());\n\n        if let Some(player) = player {\n            self.player_gamepad_combo(ui, player, connected_gamepads);\n\n            ui.separator();\n        }\n\n        ScrollArea::both().auto_shrink(false).show(ui, |ui| {\n            let grid = Grid::new(\"keybind_list\")\n                .num_columns(4)\n                .spacing([10.0, 6.0]);\n            grid.show(ui, |ui| {\n                ui.heading(\"Action\");\n                ui.heading(\"Binding #1\");\n                ui.heading(\"Binding #2\");\n                ui.heading(\"Binding #3\");\n                ui.end_row();\n\n                let keybinds = match player {\n                    None => &cfg.input.shortcuts,\n                    Some(player) => &cfg.input.joypads[player as usize],\n                };\n\n                let mut clear_bind = None;\n                for (action, bind) in keybinds {\n                    ui.strong(action.to_string());\n                    for (slot, input) in bind.bindings.iter().enumerate() {\n                        let button = Button::new(input.map(Input::fmt).unwrap_or_default())\n                            // Make enough room for larger inputs like controller joysticks\n                            .min_size(Vec2::new(135.0, 0.0));\n                        let res = ui\n                            .add(button)\n                            .on_hover_text(\"Click to set. Right-click to unset.\");\n                        if res.clicked() {\n                            self.pending_input = Some(PendingInput {\n                                action: *action,\n                                input: None,\n                                binding: slot,\n                                conflict: None,\n                            });\n                        } else if res.secondary_clicked()\n                            && let Some(input) = input\n                        {\n                            clear_bind = Some(input)\n                        }\n                    }\n                    ui.end_row();\n                }\n                if let Some(input) = clear_bind.take() {\n                    self.tx.event(ConfigEvent::ActionBindingClear(*input));\n                }\n            });\n        });\n    }\n\n    fn player_gamepad_combo(\n        &mut self,\n        ui: &mut Ui,\n        player: Player,\n        connected_gamepads: Option<&[ConnectedGamepad]>,\n    ) {\n        ui.horizontal(|ui| {\n            let gamepad_label = \"🎮 Assigned Gamepad:\";\n\n            let unassigned = \"Unassigned\".to_string();\n            match connected_gamepads {\n                Some(gamepads) => {\n                    if gamepads.is_empty() {\n                        ui.add_enabled_ui(false, |ui| {\n                            let combo = egui::ComboBox::from_label(gamepad_label)\n                                .selected_text(\"No Gamepads Connected\");\n                            combo.show_ui(ui, |_| {});\n                        });\n                    } else {\n                        let mut assigned = gamepads\n                            .iter()\n                            .find(|gamepad| gamepad.assignment == Some(player));\n                        let previous_assigned = assigned;\n                        let combo = egui::ComboBox::from_label(gamepad_label).selected_text(\n                            assigned\n                                .as_ref()\n                                .map_or(&unassigned, |assignment| &assignment.name),\n                        );\n                        combo.show_ui(ui, |ui| {\n                            ui.selectable_value(&mut assigned, None, unassigned);\n                            for assignment in gamepads {\n                                ui.selectable_value(\n                                    &mut assigned,\n                                    Some(assignment),\n                                    &assignment.name,\n                                );\n                            }\n                        });\n                        if previous_assigned != assigned {\n                            match &assigned {\n                                Some(gamepad) => {\n                                    match assigned.as_ref().and_then(|gamepad| gamepad.assignment) {\n                                        Some(player) => {\n                                            self.gamepad_unassign_confirm =\n                                                Some((player, player, gamepad.uuid));\n                                        }\n                                        None => {\n                                            self.tx.event(ConfigEvent::GamepadAssign((\n                                                player,\n                                                gamepad.uuid,\n                                            )));\n                                        }\n                                    }\n                                }\n                                None => self.tx.event(ConfigEvent::GamepadUnassign(player)),\n                            }\n                        }\n                    }\n                }\n                None => {\n                    ui.add_enabled_ui(false, |ui| {\n                        let combo = egui::ComboBox::from_label(gamepad_label)\n                            .selected_text(\"Gamepads not supported\");\n                        combo.show_ui(ui, |_| {});\n                    });\n                }\n            }\n        });\n    }\n\n    pub fn show_set_keybind_window(\n        &mut self,\n        ctx: &Context,\n        cfg: &Config,\n        gamepad_events: &[(Input, ElementState)],\n    ) {\n        let mut set_keybind_open = self.pending_input.is_some();\n        let res = egui::Window::new(\"🖮 Set Keybind\")\n            .anchor(Align2::CENTER_CENTER, Vec2::ZERO)\n            .collapsible(false)\n            .resizable(false)\n            .open(&mut set_keybind_open)\n            .show(ctx, |ui| self.set_keybind(ui, cfg, gamepad_events));\n        if let Some(ref res) = res {\n            // Force on-top focus when embedded\n            if set_keybind_open {\n                ctx.move_to_top(res.response.layer_id);\n                res.response.request_focus();\n            } else {\n                ctx.memory_mut(|m| m.surrender_focus(res.response.id));\n            }\n        }\n        if !set_keybind_open {\n            self.pending_input = None;\n        }\n    }\n\n    pub fn set_keybind(\n        &mut self,\n        ui: &mut Ui,\n        cfg: &Config,\n        gamepad_events: &[(Input, ElementState)],\n    ) {\n        let Some(PendingInput {\n            action,\n            binding,\n            mut input,\n            mut conflict,\n            ..\n        }) = self.pending_input\n        else {\n            return;\n        };\n\n        if let Some(action) = conflict {\n            ui.label(format!(\"Conflict with {action}.\"));\n            ui.horizontal(|ui| {\n                if ui.button(\"Overwrite\").clicked() {\n                    conflict = None;\n                }\n                if ui.button(\"Cancel\").clicked() {\n                    self.pending_input = None;\n                    input = None;\n                }\n            });\n        } else {\n            ui.label(format!(\n                \"Press any key on your keyboard or controller to set a new binding for {action}.\",\n            ));\n        }\n\n        match input {\n            Some(input) => {\n                if conflict.is_none() {\n                    self.pending_input = None;\n                    self.tx\n                        .event(ConfigEvent::ActionBindingSet((action, input, binding)));\n                }\n            }\n            None => {\n                let captured = if let Some(keybind) = &mut self.pending_input {\n                    let input = ui.input(|i| {\n                        use egui::Event;\n\n                        // Find first released key/button event\n                        for event in &i.events {\n                            match *event {\n                                Event::Key {\n                                    physical_key: Some(key),\n                                    pressed: false,\n                                    modifiers,\n                                    ..\n                                } => {\n                                    // TODO: Ignore unsupported key mappings for now as egui supports less\n                                    // overall than winit\n                                    return Input::try_from((key, modifiers)).ok();\n                                }\n                                Event::PointerButton {\n                                    button,\n                                    pressed: false,\n                                    ..\n                                } => {\n                                    return Some(Input::from(button));\n                                }\n                                _ => (),\n                            }\n                        }\n                        for (input, state) in gamepad_events {\n                            if *state == ElementState::Released {\n                                return Some(*input);\n                            }\n                        }\n                        None\n                    });\n\n                    if let Some(input) = input {\n                        keybind.input = Some(input);\n                        let binds = cfg\n                            .input\n                            .shortcuts\n                            .iter()\n                            .chain(cfg.input.joypads.iter().flatten());\n                        for (action, bind) in binds {\n                            if bind\n                                .bindings\n                                .iter()\n                                .any(|b| b == &Some(input) && *action != keybind.action)\n                            {\n                                keybind.conflict = Some(*action);\n                            }\n                        }\n                        Some(input)\n                    } else {\n                        None\n                    }\n                } else {\n                    None\n                };\n\n                if let Some(input) = captured {\n                    if let Some(pending) = self.pending_input.take_if(|p| p.conflict.is_none()) {\n                        self.tx.event(ConfigEvent::ActionBindingSet((\n                            pending.action,\n                            input,\n                            pending.binding,\n                        )));\n                    }\n                    let dialog_viewport = ui.ctx().viewport_id();\n                    let when = Instant::now();\n                    self.tx.event(RendererEvent::RequestRedraw {\n                        viewport_id: dialog_viewport,\n                        when,\n                    });\n                    if dialog_viewport != ViewportId::ROOT {\n                        self.tx.event(RendererEvent::RequestRedraw {\n                            viewport_id: ViewportId::ROOT,\n                            when,\n                        });\n                    }\n                }\n            }\n        }\n    }\n\n    fn show_gamepad_unassign_window(&mut self, ctx: &Context) {\n        if self.gamepad_unassign_confirm.is_none() {\n            return;\n        }\n\n        let mut gamepad_unassign_open = self.gamepad_unassign_confirm.is_some();\n        let res = egui::Window::new(\"🎮 Unassign Gamepad\")\n            .anchor(Align2::CENTER_CENTER, Vec2::ZERO)\n            .collapsible(false)\n            .resizable(false)\n            .open(&mut gamepad_unassign_open)\n            .show(ctx, |ui| self.gamepad_unassign_confirm(ui));\n        if let Some(ref res) = res {\n            // Force on-top focus when embedded\n            if gamepad_unassign_open {\n                ctx.move_to_top(res.response.layer_id);\n                res.response.request_focus();\n            } else {\n                ctx.memory_mut(|m| m.surrender_focus(res.response.id));\n            }\n        }\n        if !gamepad_unassign_open {\n            self.gamepad_unassign_confirm = None;\n        }\n    }\n\n    fn gamepad_unassign_confirm(&mut self, ui: &mut Ui) {\n        if let Some((existing_player, new_player, uuid)) = self.gamepad_unassign_confirm {\n            ui.label(format!(\"Unassign gamepad from Player {existing_player}?\"));\n            ui.horizontal(|ui| {\n                if ui.button(\"Yes\").clicked() {\n                    self.tx.event(ConfigEvent::GamepadUnassign(existing_player));\n                    self.tx\n                        .event(ConfigEvent::GamepadAssign((new_player, uuid)));\n                    self.gamepad_unassign_confirm = None;\n                }\n                if ui.button(\"Cancel\").clicked() {\n                    self.gamepad_unassign_confirm = None;\n                }\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/renderer/gui/lib.rs",
    "content": "use crate::nes::{\n    config::Config,\n    input::{Gamepads, Input},\n    renderer::event::{\n        key_from_keycode, modifiers_from_modifiers_state, pointer_button_from_mouse,\n    },\n};\nuse egui::{\n    Checkbox, Context, KeyboardShortcut, Pos2, Rect, Response, Sense, TextStyle, TextWrapMode, Ui,\n    Widget, WidgetText,\n};\nuse std::ops::{Deref, DerefMut};\nuse tetanes_core::ppu;\nuse winit::{event::ElementState, window::Window};\n\n#[derive(Debug, Copy, Clone)]\n#[must_use]\npub struct ViewportOptions {\n    pub enabled: bool,\n    pub always_on_top: bool,\n}\n\n#[derive(Debug, Copy, Clone)]\npub enum ShowShortcut {\n    Yes,\n    No,\n}\n\nimpl ShowShortcut {\n    pub fn then<T>(&self, f: impl FnOnce() -> T) -> Option<T> {\n        match self {\n            Self::Yes => Some(f()),\n            Self::No => None,\n        }\n    }\n}\n\npub trait ShortcutText<'a>\nwhere\n    Self: Sized + 'a,\n{\n    fn shortcut_text(self, shortcut_text: impl Into<WidgetText>) -> ShortcutWidget<'a, Self> {\n        ShortcutWidget {\n            inner: self,\n            shortcut_text: shortcut_text.into(),\n            phantom: std::marker::PhantomData,\n        }\n    }\n}\n\npub fn cursor_to_zapper(x: f32, y: f32, rect: Rect) -> Option<Pos2> {\n    let width = ppu::size::WIDTH as f32;\n    let height = ppu::size::HEIGHT as f32;\n    // Normalize x/y to 0..=1 and scale to PPU dimensions\n    let x = ((x - rect.min.x) / rect.width()) * width;\n    let y = ((y - rect.min.y) / rect.height()) * height;\n    ((0.0..width).contains(&x) && (0.0..height).contains(&y)).then_some(Pos2::new(x, y))\n}\n\npub fn input_down(ui: &mut Ui, gamepads: &Gamepads, cfg: &Config, input: Input) -> bool {\n    ui.input_mut(|i| match input {\n        Input::Key(keycode, modifier_state) => key_from_keycode(keycode).is_some_and(|key| {\n            let modifiers = modifiers_from_modifiers_state(modifier_state);\n            i.key_down(key) && i.modifiers == modifiers\n        }),\n        Input::Button(player, button) => cfg\n            .input\n            .gamepad_assigned_to(player)\n            .and_then(|uuid| gamepads.gamepad_by_uuid(&uuid))\n            .is_some_and(|g| g.is_pressed(button)),\n        Input::Mouse(mouse_button) => pointer_button_from_mouse(mouse_button)\n            .is_some_and(|pointer| i.pointer.button_down(pointer)),\n        Input::Axis(player, axis, direction) => cfg\n            .input\n            .gamepad_assigned_to(player)\n            .and_then(|uuid| gamepads.gamepad_by_uuid(&uuid))\n            .and_then(|g| g.axis_data(axis).map(|data| data.value()))\n            .is_some_and(|value| {\n                let (dir, state) = Gamepads::axis_state(value);\n                dir == Some(direction) && state == ElementState::Pressed\n            }),\n    })\n}\n\n#[must_use]\npub struct ShortcutWidget<'a, T> {\n    inner: T,\n    shortcut_text: WidgetText,\n    phantom: std::marker::PhantomData<&'a ()>,\n}\n\nimpl<T> Deref for ShortcutWidget<'_, T> {\n    type Target = T;\n    fn deref(&self) -> &Self::Target {\n        &self.inner\n    }\n}\n\nimpl<T> DerefMut for ShortcutWidget<'_, T> {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.inner\n    }\n}\n\nimpl<T> Widget for ShortcutWidget<'_, T>\nwhere\n    T: Widget,\n{\n    fn ui(self, ui: &mut Ui) -> Response {\n        ui.horizontal(|ui| {\n            let res = self.inner.ui(ui);\n\n            if !self.shortcut_text.is_empty() {\n                let shortcut_galley = self.shortcut_text.into_galley(\n                    ui,\n                    Some(TextWrapMode::Extend),\n                    f32::INFINITY,\n                    TextStyle::Button,\n                );\n\n                let available_rect = ui.available_rect_before_wrap();\n\n                let gap_before_shortcut_text = ui.spacing().item_spacing.x;\n                let mut desired_size = shortcut_galley.size();\n                desired_size.x += gap_before_shortcut_text;\n                // Ensure sense is set to hover so that screen readers don't try to read it,\n                // consistent with `shortcut_text` on `Button`\n                let (rect, _) = ui.allocate_at_least(desired_size, Sense::hover());\n\n                if ui.is_rect_visible(rect) {\n                    let text_pos = Pos2::new(\n                        available_rect.max.x - shortcut_galley.size().x,\n                        rect.center().y - 0.5 * shortcut_galley.size().y,\n                    );\n                    ui.painter()\n                        .galley(text_pos, shortcut_galley, ui.visuals().weak_text_color());\n                }\n            }\n            res\n        })\n        .inner\n    }\n}\n\n#[must_use]\npub struct ToggleValue<'a> {\n    selected: &'a mut bool,\n    text: WidgetText,\n}\n\nimpl<'a> ToggleValue<'a> {\n    pub fn new(selected: &'a mut bool, text: impl Into<WidgetText>) -> Self {\n        Self {\n            selected,\n            text: text.into(),\n        }\n    }\n}\n\nimpl Widget for ToggleValue<'_> {\n    fn ui(self, ui: &mut Ui) -> Response {\n        let mut res = ui.selectable_label(*self.selected, self.text);\n        if res.clicked() {\n            *self.selected = !*self.selected;\n            res.mark_changed();\n        }\n        res\n    }\n}\n\n#[must_use]\npub struct RadioValue<'a, T> {\n    current_value: &'a mut T,\n    alternative: T,\n    text: WidgetText,\n}\n\nimpl<'a, T: PartialEq> RadioValue<'a, T> {\n    pub fn new(current_value: &'a mut T, alternative: T, text: impl Into<WidgetText>) -> Self {\n        Self {\n            current_value,\n            alternative,\n            text: text.into(),\n        }\n    }\n}\n\nimpl<T: PartialEq> Widget for RadioValue<'_, T> {\n    fn ui(self, ui: &mut Ui) -> Response {\n        let mut res = ui.radio(*self.current_value == self.alternative, self.text);\n        if res.clicked() && *self.current_value != self.alternative {\n            *self.current_value = self.alternative;\n            res.mark_changed();\n        }\n        res\n    }\n}\n\nimpl<'a> ShortcutText<'a> for Checkbox<'a> {}\nimpl<'a> ShortcutText<'a> for ToggleValue<'a> {}\nimpl<'a, T> ShortcutText<'a> for RadioValue<'a, T> {}\n\nimpl TryFrom<Input> for KeyboardShortcut {\n    type Error = ();\n\n    fn try_from(val: Input) -> Result<Self, Self::Error> {\n        if let Input::Key(keycode, modifier_state) = val {\n            Ok(KeyboardShortcut {\n                logical_key: key_from_keycode(keycode).ok_or(())?,\n                modifiers: modifiers_from_modifiers_state(modifier_state),\n            })\n        } else {\n            Err(())\n        }\n    }\n}\n\npub fn screen_center(ctx: &Context) -> Option<Pos2> {\n    ctx.input(|i| {\n        let outer_rect = i.viewport().outer_rect?;\n        let size = outer_rect.size();\n        let monitor_size = i.viewport().monitor_size?;\n        if 1.0 < monitor_size.x && 1.0 < monitor_size.y {\n            let x = (monitor_size.x - size.x) / 2.0;\n            let y = (monitor_size.y - size.y) / 2.0;\n            Some(Pos2::new(x, y))\n        } else {\n            None\n        }\n    })\n}\n\npub fn screen_size_in_pixels(window: &Window) -> egui::Vec2 {\n    let size = window.inner_size();\n    egui::vec2(size.width as f32, size.height as f32)\n}\n\npub fn pixels_per_point(egui_ctx: &egui::Context, window: &Window) -> f32 {\n    let native_pixels_per_point = window.scale_factor() as f32;\n    let egui_zoom_factor = egui_ctx.zoom_factor();\n    egui_zoom_factor * native_pixels_per_point\n}\n\npub fn inner_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<egui::Rect> {\n    let inner_pos_px = window.inner_position().ok()?;\n    let inner_pos_px = egui::pos2(inner_pos_px.x as f32, inner_pos_px.y as f32);\n\n    let inner_size_px = window.inner_size();\n    let inner_size_px = egui::vec2(inner_size_px.width as f32, inner_size_px.height as f32);\n\n    let inner_rect_px = egui::Rect::from_min_size(inner_pos_px, inner_size_px);\n\n    Some(inner_rect_px / pixels_per_point)\n}\n\npub fn outer_rect_in_points(window: &Window, pixels_per_point: f32) -> Option<egui::Rect> {\n    let outer_pos_px = window.outer_position().ok()?;\n    let outer_pos_px = egui::pos2(outer_pos_px.x as f32, outer_pos_px.y as f32);\n\n    let outer_size_px = window.outer_size();\n    let outer_size_px = egui::vec2(outer_size_px.width as f32, outer_size_px.height as f32);\n\n    let outer_rect_px = egui::Rect::from_min_size(outer_pos_px, outer_size_px);\n\n    Some(outer_rect_px / pixels_per_point)\n}\n\npub fn to_winit_icon(icon: &egui::IconData) -> Option<winit::window::Icon> {\n    if icon.is_empty() {\n        None\n    } else {\n        match winit::window::Icon::from_rgba(icon.rgba.clone(), icon.width, icon.height) {\n            Ok(winit_icon) => Some(winit_icon),\n            Err(err) => {\n                tracing::warn!(\"Invalid IconData: {err}\");\n                None\n            }\n        }\n    }\n}\n\n/// An animated dashed rectangle.\npub fn animated_dashed_rect(\n    ui: &mut Ui,\n    rect: Rect,\n    stroke: impl Into<egui::Stroke>,\n    dash_length: f32,\n    gap_length: f32,\n) {\n    if ui.is_rect_visible(rect) {\n        ui.ctx().request_repaint(); // because it is animated\n\n        let rect = [\n            rect.left_top(),\n            rect.right_top(),\n            rect.right_bottom(),\n            rect.left_bottom(),\n            rect.left_top(),\n        ];\n        let time = ui.input(|i| i.time as f32);\n        let total_length = dash_length + gap_length;\n        let dash_offset = (time * 10.0) % total_length;\n\n        ui.painter().add(egui::Shape::dashed_line_with_offset(\n            &rect,\n            stroke,\n            &[dash_length],\n            &[gap_length],\n            dash_offset,\n        ));\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/renderer/gui/ppu_viewer.rs",
    "content": "use crate::nes::{\n    event::{DebugEvent, EmulationEvent, NesEventProxy},\n    renderer::{\n        gui::lib::{ViewportOptions, animated_dashed_rect},\n        painter::RenderState,\n        texture::Texture,\n    },\n};\nuse egui::{\n    CentralPanel, Color32, Context, CursorIcon, DragValue, Grid, Image, Label, Panel, Pos2, Rect,\n    ScrollArea, Sense, Slider, StrokeKind, Ui, Vec2, ViewportClass, ViewportId,\n};\nuse parking_lot::Mutex;\nuse std::sync::{\n    Arc,\n    atomic::{AtomicBool, Ordering},\n};\nuse tetanes_core::{\n    debug::PpuDebugger,\n    mapper::Map,\n    ppu::{self, Ppu, addr, cycle, scanline, scroll::Scroll, sprite::Sprite},\n};\n\n#[derive(Debug)]\n#[must_use]\nstruct State {\n    tx: NesEventProxy,\n    tab: Tab,\n    // TODO: persist in config\n    refresh_cycle: u16,\n    refresh_scanline: u16,\n    show_refresh_lines: bool,\n    show_dividers: bool,\n    show_tile_grid: bool,\n    show_scroll_overlay: bool,\n    show_attr_grid_16x: bool,\n    show_attr_grid_32x: bool,\n    nametables: NametablesState,\n    pattern_tables: PatternTablesState,\n    oam: OamState,\n    palette: PalettesState,\n    ppu: Ppu,\n}\n\n#[derive(Debug)]\n#[must_use]\nstruct NametablesState {\n    pixels: Vec<u8>,\n    texture: Texture,\n    zoom: f32,\n    selected: Option<Vec2>,\n}\n\n#[derive(Debug)]\n#[must_use]\nstruct PatternTablesState {\n    pixels: Vec<u8>,\n    texture: Texture,\n    zoom: f32,\n    selected: Option<Vec2>,\n}\n\n#[derive(Debug)]\n#[must_use]\nstruct OamState {\n    oam_pixels: Vec<u8>,\n    sprite_pixels: Vec<u8>,\n    sprites: Vec<Sprite>,\n    oam_texture: Texture,\n    sprites_texture: Texture,\n    zoom: f32,\n    oam_selected: Option<Vec2>,\n}\n\n#[derive(Debug)]\n#[must_use]\nstruct PalettesState {\n    size: Vec2,\n    pixels: Vec<u8>,\n    colors: Vec<u8>,\n    zoom: f32,\n    selected: Option<Vec2>,\n}\n\n#[derive(Debug, Copy, Clone)]\n#[must_use]\nstruct NametableTile {\n    index: u16,\n    uv: Rect,\n    col: u16,\n    row: u16,\n    x: u16, // 0..=248\n    y: u16, // 0..=232\n    nametable_addr: u16,\n    tile_addr: u16,\n    palette_index: u8,\n    palette_addr: u16,\n    attr_addr: u16,\n    attr_val: u8,\n}\n\nimpl Default for NametableTile {\n    fn default() -> Self {\n        Self {\n            index: 0,\n            uv: Rect::NOTHING,\n            col: 0,\n            row: 0,\n            x: 0,\n            y: 0,\n            nametable_addr: 0,\n            tile_addr: 0,\n            palette_index: 0,\n            palette_addr: 0,\n            attr_addr: 0,\n            attr_val: 0,\n        }\n    }\n}\n\n#[derive(Debug, Copy, Clone)]\n#[must_use]\nstruct ChrTile {\n    index: u16,\n    uv: Rect,\n    tile_addr: u16,\n}\n\nimpl Default for ChrTile {\n    fn default() -> Self {\n        Self {\n            index: 0,\n            uv: Rect::NOTHING,\n            tile_addr: 0,\n        }\n    }\n}\n\n#[derive(Debug, Copy, Clone)]\n#[must_use]\nstruct PaletteColor {\n    index: u8,\n    value: u8,\n    addr: u16,\n    color: Color32,\n}\n\nimpl Default for PaletteColor {\n    fn default() -> Self {\n        Self {\n            index: 0,\n            value: 0,\n            addr: 0,\n            color: Color32::BLACK,\n        }\n    }\n}\n\n#[derive(Debug)]\n#[must_use]\npub struct PpuViewer {\n    pub id: ViewportId,\n    open: Arc<AtomicBool>,\n    state: Arc<Mutex<State>>,\n}\n\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]\npub enum Tab {\n    #[default]\n    Nametables,\n    PatternTables,\n    Oam,\n    Palette,\n}\n\nimpl PpuViewer {\n    const TITLE: &'static str = \"TetaNES - PPU Viewer\";\n\n    pub fn new(tx: NesEventProxy, render_state: &mut RenderState) -> Self {\n        Self {\n            id: ViewportId::from_hash_of(Self::TITLE),\n            open: Arc::new(AtomicBool::new(false)),\n            state: Arc::new(Mutex::new(State {\n                tx,\n                tab: Tab::default(),\n                refresh_cycle: 0,\n                refresh_scanline: scanline::VBLANK_NTSC,\n                show_refresh_lines: false,\n                show_dividers: true,\n                show_tile_grid: false,\n                show_scroll_overlay: false,\n                show_attr_grid_16x: false,\n                show_attr_grid_32x: false,\n                nametables: NametablesState {\n                    // 4 nametables with 4 color channels (RGBA)\n                    pixels: vec![0x00; 4 * 4 * ppu::size::FRAME],\n                    texture: Texture::new(\n                        render_state,\n                        2.0 * Vec2::new(ppu::size::WIDTH as f32, ppu::size::HEIGHT as f32),\n                        1.0,\n                        Some(\"nes nametables\"),\n                    ),\n                    zoom: 1.5,\n                    selected: None,\n                },\n                pattern_tables: PatternTablesState {\n                    // 2 pattern tables with 4 color channels (RGBA)\n                    pixels: vec![0x00; 2 * 4 * ppu::size::FRAME],\n                    texture: Texture::new(\n                        render_state,\n                        Vec2::new(ppu::size::WIDTH as f32, ppu::size::WIDTH as f32 / 2.0),\n                        1.0,\n                        Some(\"nes pattern tables\"),\n                    ),\n                    zoom: 3.0,\n                    selected: None,\n                },\n                oam: OamState {\n                    // 64 8x8 sprites with 4 color channels (RGBA)\n                    oam_pixels: vec![0x00; 64 * 8 * 8 * 4],\n                    // 1 nametable with 4 color channels (RGBA)\n                    sprite_pixels: vec![0x00; 4 * ppu::size::FRAME],\n                    // 64 sprites\n                    sprites: vec![Sprite::new(); 64],\n                    oam_texture: Texture::new(\n                        render_state,\n                        Vec2::splat(64.0),\n                        1.0,\n                        Some(\"nes oam\"),\n                    ),\n                    sprites_texture: Texture::new(\n                        render_state,\n                        Vec2::new(ppu::size::WIDTH as f32, ppu::size::HEIGHT as f32),\n                        1.0,\n                        Some(\"nes sprites\"),\n                    ),\n                    zoom: 3.0,\n                    oam_selected: None,\n                },\n                palette: PalettesState {\n                    // 2 palette tables\n                    size: Vec2::new(64.0, 32.0),\n                    // 32 palette colors with 4 color channels (RGBA)\n                    pixels: vec![0x00; 4 * 32],\n                    // 32 colors\n                    colors: vec![0x00; 32],\n                    zoom: 3.0,\n                    selected: None,\n                },\n                ppu: Ppu::default(),\n            })),\n        }\n    }\n\n    pub const fn id(&self) -> ViewportId {\n        self.id\n    }\n\n    pub fn open(&self) -> bool {\n        self.open.load(Ordering::Acquire)\n    }\n\n    pub fn set_open(&self, open: bool, ctx: &Context) {\n        self.open.store(open, Ordering::Release);\n        self.state.lock().update_debugger(open);\n        if open {\n            ctx.send_viewport_cmd_to(self.id, egui::ViewportCommand::Close);\n        }\n    }\n\n    pub fn toggle_open(&self, ctx: &Context) {\n        let Ok(open) = self\n            .open\n            .fetch_update(Ordering::Release, Ordering::Acquire, |open| Some(!open))\n        else {\n            return;\n        };\n        self.state.lock().update_debugger(!open);\n        if open {\n            ctx.send_viewport_cmd_to(self.id, egui::ViewportCommand::Close);\n        }\n    }\n\n    pub fn update_ppu(&mut self, queue: &wgpu::Queue, ppu: Ppu) {\n        let mut state = self.state.lock();\n        match state.tab {\n            Tab::Nametables => {\n                ppu.load_nametables(&mut state.nametables.pixels);\n                let mut pixels = std::mem::take(&mut state.palette.pixels);\n                let mut colors = std::mem::take(&mut state.palette.colors);\n                ppu.load_palettes(&mut pixels, &mut colors);\n                state.palette.pixels = pixels;\n                state.palette.colors = colors;\n                state\n                    .nametables\n                    .texture\n                    .update(queue, &state.nametables.pixels);\n            }\n            Tab::PatternTables => {\n                ppu.load_pattern_tables(&mut state.pattern_tables.pixels);\n                state\n                    .pattern_tables\n                    .texture\n                    .update(queue, &state.pattern_tables.pixels);\n            }\n            Tab::Oam => {\n                let mut oam_pixels = std::mem::take(&mut state.oam.oam_pixels);\n                let mut sprite_pixels = std::mem::take(&mut state.oam.sprite_pixels);\n                let mut sprites = std::mem::take(&mut state.oam.sprites);\n\n                // Clear to black each frame\n                sprite_pixels.chunks_mut(4).for_each(|chunk| {\n                    chunk[0] = 0;\n                    chunk[1] = 0;\n                    chunk[2] = 0;\n                    chunk[3] = 255;\n                });\n                ppu.load_oam(&mut oam_pixels, &mut sprite_pixels, &mut sprites);\n\n                state.oam.oam_pixels = oam_pixels;\n                state.oam.sprite_pixels = sprite_pixels;\n                state.oam.sprites = sprites;\n\n                state.oam.oam_texture.update(queue, &state.oam.oam_pixels);\n                state\n                    .oam\n                    .sprites_texture\n                    .update(queue, &state.oam.sprite_pixels);\n            }\n            Tab::Palette => {\n                let mut pixels = std::mem::take(&mut state.palette.pixels);\n                let mut colors = std::mem::take(&mut state.palette.colors);\n                ppu.load_palettes(&mut pixels, &mut colors);\n                state.palette.pixels = pixels;\n                state.palette.colors = colors;\n            }\n        }\n        state.ppu = ppu;\n    }\n\n    pub fn show(&mut self, ui: &mut Ui, opts: ViewportOptions) {\n        if !self.open.load(Ordering::Relaxed) {\n            return;\n        }\n\n        let open = Arc::clone(&self.open);\n        let state = Arc::clone(&self.state);\n\n        let mut viewport_builder = egui::ViewportBuilder::default()\n            .with_title(Self::TITLE)\n            .with_inner_size(Vec2::new(1024.0, 768.0));\n        if opts.always_on_top {\n            viewport_builder = viewport_builder.with_always_on_top();\n        }\n\n        ui.show_viewport_deferred(self.id, viewport_builder, move |ui, class| {\n            if class == ViewportClass::EmbeddedWindow {\n                let mut window_open = open.load(Ordering::Acquire);\n                egui::Window::new(PpuViewer::TITLE)\n                    .open(&mut window_open)\n                    .show(ui, |ui| state.lock().ui(ui, opts.enabled));\n                open.store(window_open, Ordering::Release);\n            } else {\n                CentralPanel::default().show_inside(ui, |ui| state.lock().ui(ui, opts.enabled));\n                if ui.input(|i| i.viewport().close_requested()) {\n                    open.store(false, Ordering::Release);\n                }\n            }\n        });\n    }\n}\n\nimpl State {\n    fn update_debugger(&self, open: bool) {\n        let tx = self.tx.clone();\n        let debugger = PpuDebugger {\n            cycle: self.refresh_cycle,\n            scanline: self.refresh_scanline,\n            callback: Arc::new(move |ppu| tx.event(DebugEvent::Ppu(Box::new(ppu)))),\n        };\n        self.tx.event(if open {\n            EmulationEvent::AddDebugger(debugger.into())\n        } else {\n            EmulationEvent::RemoveDebugger(debugger.into())\n        });\n    }\n\n    fn ui(&mut self, ui: &mut Ui, enabled: bool) {\n        ui.add_enabled_ui(enabled, |ui| {\n            Panel::top(\"ppu_viewer_menubar\").show_inside(ui, |ui| {\n                ui.horizontal(|ui| {\n                    ui.selectable_value(&mut self.tab, Tab::Nametables, \"Nametables\");\n                    ui.selectable_value(&mut self.tab, Tab::PatternTables, \"Pattern Tables\");\n                    ui.selectable_value(&mut self.tab, Tab::Oam, \"OAM\");\n                    ui.selectable_value(&mut self.tab, Tab::Palette, \"Palette\");\n                });\n            });\n\n            match self.tab {\n                Tab::Nametables => self.nametables_tab(ui),\n                Tab::PatternTables => self.pattern_tables_tab(ui),\n                Tab::Oam => self.oam_tab(ui),\n                Tab::Palette => self.palette_tab(ui),\n            }\n        });\n    }\n\n    fn grid_settings(&mut self, ui: &mut Ui) {\n        let res = ui\n            .checkbox(&mut self.show_dividers, \"Table Dividers\")\n            .on_hover_text(\"Show divider lines between tables.\");\n        if res.changed() {\n            // TODO: update config\n        }\n\n        let res = ui\n            .checkbox(&mut self.show_tile_grid, \"Tile Grid\")\n            .on_hover_text(\"Show grid lines between tiles.\");\n        if res.changed() {\n            // TODO: update config\n        }\n    }\n\n    fn general_settings(&mut self, ui: &mut Ui) {\n        ui.strong(\"Refresh on:\")\n            .on_hover_cursor(CursorIcon::Help)\n            .on_hover_text(\"Change which PPU cycle/scanline viewer state refreshes on.\");\n\n        ui.indent(\"refresh_settings\", |ui| {\n            ui.horizontal(|ui| {\n                let drag = DragValue::new(&mut self.refresh_cycle)\n                    .range(0..=cycle::END)\n                    .suffix(\" cycle\");\n                let res = ui.add(drag);\n                if res.changed() {\n                    self.update_debugger(true);\n                }\n            });\n\n            ui.horizontal(|ui| {\n                let drag = DragValue::new(&mut self.refresh_scanline)\n                    .range(0..=self.ppu.prerender_scanline)\n                    .suffix(\" scanline\");\n                let res = ui.add(drag);\n                if res.changed() {\n                    self.update_debugger(true);\n                }\n            });\n        });\n    }\n\n    fn nametables_tab(&mut self, ui: &mut Ui) {\n        Panel::right(\"nametable_panel\").show_inside(ui, |ui| {\n            ScrollArea::vertical().show(ui, |ui| {\n                ui.add_space(12.0);\n                ui.heading(\"Nametable Info\");\n                ui.separator();\n\n                let grid = Grid::new(\"nametables_info\")\n                    .num_columns(2)\n                    .spacing([40.0, 6.0]);\n                grid.show(ui, |ui| {\n                    ui.strong(\"Mirroring:\");\n                    ui.label(format!(\"{:?}\", self.ppu.mirroring()));\n                    ui.end_row();\n                });\n\n                ui.add_space(16.0);\n                ui.heading(\"Selected Tile\");\n                ui.separator();\n                self.nametable_tile(ui, \"nametable_tile_selected\", self.nametables.selected);\n\n                ui.add_space(16.0);\n                ui.separator();\n\n                ui.collapsing(\"Settings\", |ui| {\n                    self.general_settings(ui);\n\n                    let res = ui\n                        .checkbox(&mut self.show_refresh_lines, \"Refresh Markers\")\n                        .on_hover_text(\n                            \"Show lines indicating the current refresh cycle and scanline.\",\n                        );\n                    if res.changed() {\n                        // TODO: update config\n                    }\n\n                    self.grid_settings(ui);\n\n                    let res = ui\n                        .checkbox(&mut self.show_scroll_overlay, \"Scroll Overlay\")\n                        .on_hover_text(\"Show scroll position overlay.\");\n                    if res.changed() {\n                        // TODO: update config\n                    }\n\n                    let res = ui\n                        .checkbox(&mut self.show_attr_grid_16x, \"Attribute Grid (16x16)\")\n                        .on_hover_text(\"Show grid lines within each attribute block.\");\n                    if res.changed() {\n                        // TODO: update config\n                    }\n\n                    let res = ui\n                        .checkbox(&mut self.show_attr_grid_32x, \"Attribute Grid (32x32)\")\n                        .on_hover_text(\"Show grid lines between attribute blocks.\");\n                    if res.changed() {\n                        // TODO: update config\n                    }\n\n                    zoom_slider(ui, &mut self.nametables.zoom);\n                });\n            });\n        });\n\n        let texture_size = self.nametables.texture.size;\n        CentralPanel::default().show_inside(ui, |ui| {\n            let scroll = ScrollArea::both()\n                .min_scrolled_width(texture_size.x)\n                .min_scrolled_height(texture_size.y);\n            scroll.show(ui, |ui| {\n                let image = Image::from_texture(self.nametables.texture.sized())\n                    .fit_to_exact_size(self.nametables.zoom * texture_size)\n                    .sense(Sense::click());\n\n                let res = ui.add(image).on_hover_cursor(CursorIcon::Cell);\n                let image_rect = res.rect;\n\n                if let Some(pos) = res.hover_pos()\n                    && image_rect.contains(pos)\n                {\n                    self.nametable_hover(ui, &res, pos);\n                }\n\n                if self.show_dividers {\n                    // Split the 4x4 nametables in half vertically and horizontally\n                    ui.painter().vline(\n                        image_rect.center().x,\n                        image_rect.y_range(),\n                        (1.0, Color32::WHITE),\n                    );\n                    ui.painter().hline(\n                        image_rect.x_range(),\n                        image_rect.center().y,\n                        (1.0, Color32::WHITE),\n                    );\n                }\n\n                if self.show_refresh_lines {\n                    let cycle_offset =\n                        self.refresh_cycle as f32 * image_rect.size().x / 2.0 / cycle::END as f32;\n                    let scanline_offset = self.refresh_scanline as f32 * image_rect.size().y\n                        / 2.0\n                        / self.ppu.prerender_scanline as f32;\n                    ui.painter().vline(\n                        image_rect.left() + cycle_offset,\n                        image_rect.y_range(),\n                        (1.0, Color32::RED),\n                    );\n                    ui.painter().vline(\n                        image_rect.center().x + cycle_offset,\n                        image_rect.y_range(),\n                        (1.0, Color32::RED),\n                    );\n                    ui.painter().hline(\n                        image_rect.x_range(),\n                        image_rect.top() + scanline_offset,\n                        (1.0, Color32::GREEN),\n                    );\n                    ui.painter().hline(\n                        image_rect.x_range(),\n                        image_rect.center().y + scanline_offset,\n                        (1.0, Color32::GREEN),\n                    );\n                }\n\n                if self.show_tile_grid {\n                    paint_grid(ui, image_rect, 60.0, 64.0, Color32::LIGHT_BLUE);\n                }\n\n                if self.show_attr_grid_16x {\n                    paint_grid(ui, image_rect, 30.0, 32.0, Color32::LIGHT_RED);\n                }\n\n                if self.show_attr_grid_32x {\n                    // Because 32x doesn't divide evenly into 240, split this up into two passes with a\n                    // dividing line, forcing the leftover attribute space to be at the bottom. Also\n                    // halve the number of rows\n                    let top_rect = Rect::from_min_max(image_rect.min, image_rect.right_center());\n                    let bot_rect =\n                        Rect::from_min_max(image_rect.left_center(), image_rect.right_bottom());\n\n                    paint_grid(ui, top_rect, 7.5, 16.0, Color32::LIGHT_GREEN);\n                    ui.painter().hline(\n                        top_rect.x_range(),\n                        top_rect.bottom(),\n                        (1.0, Color32::LIGHT_GREEN),\n                    );\n                    paint_grid(ui, bot_rect, 7.5, 16.0, Color32::LIGHT_GREEN);\n                }\n\n                if self.show_scroll_overlay {\n                    self.nametable_scroll_overlay(ui, image_rect);\n                }\n\n                if let Some(offset) = self.nametables.selected {\n                    let selection =\n                        tile_selection(image_rect, self.nametables.texture.size, offset);\n                    animated_dashed_rect(ui, selection, (1.0, Color32::WHITE), 3.0, 3.0);\n                }\n            });\n        });\n    }\n\n    fn nametable_hover(&mut self, ui: &mut Ui, res: &egui::Response, pos: Pos2) {\n        let image_rect = res.rect;\n        let texture_size = self.nametables.texture.size;\n\n        let offset = translate_screen_pos_to_tile(pos, image_rect, texture_size);\n        let selection = tile_selection(image_rect, texture_size, offset);\n\n        animated_dashed_rect(\n            ui,\n            selection,\n            (1.0, Color32::from_white_alpha(220)),\n            3.0,\n            3.0,\n        );\n\n        res.clone().on_hover_ui_at_pointer(|ui| {\n            self.nametable_tile(ui, \"nametable_tile_hover\", Some(offset));\n        });\n        if res.clicked() {\n            self.nametables.selected = Some(offset);\n        }\n    }\n\n    fn nametable_tile_from_offset(&self, offset: Vec2, texture_size: Vec2) -> NametableTile {\n        let Vec2 { x, y } = offset;\n\n        // Get row/column 8x8 tile and the nametable it's in\n        let mut col = x as u16 / 8;\n        let mut row = y as u16 / 8;\n        let nametable = if col >= 32 { 1 } else { 0 } | if row >= 30 { 2 } else { 0 };\n\n        // Wrap row/column to a single nametable\n        col &= 31;\n        if row >= 30 {\n            // Not a power of two, so can't bitwise &\n            row -= 30;\n        }\n\n        let nametable_index = (row << 5) + col;\n        let base_nametable_addr = addr::NAMETABLE_START | (nametable * ppu::size::NAMETABLE);\n        let base_attr_addr = base_nametable_addr + addr::ATTR_OFFSET;\n\n        let nametable_addr = base_nametable_addr + nametable_index;\n        let tile_index = u16::from(self.ppu.mapper.chr_peek(nametable_addr, &self.ppu.ciram));\n        let tile_addr = self.ppu.ctrl.bg_select + (tile_index << 4);\n\n        let supertile = ((row & 0xFC) << 1) + (col >> 2);\n        let attr_addr = base_attr_addr + supertile;\n        let attr_val = self.ppu.mapper.chr_peek(attr_addr, &self.ppu.ciram);\n\n        let attr_shift = (col & 0x02) | ((row & 0x02) << 1);\n        // TODO: handle mmc5 extended attributes\n        let palette_addr = ((attr_val >> attr_shift) & 0x03) << 2;\n        let palette_index = palette_addr >> 2;\n        let palette_addr = addr::PALETTE_START + u16::from(palette_addr);\n\n        let tile_uv = Rect::from_min_size(\n            (Vec2::new(x, y) / texture_size).to_pos2(),\n            Vec2::splat(8.0) / texture_size,\n        );\n\n        let x = (x as u16) % ppu::size::WIDTH;\n        let y = (y as u16) % ppu::size::HEIGHT;\n\n        NametableTile {\n            index: tile_index,\n            uv: tile_uv,\n            col,\n            row,\n            x,\n            y,\n            nametable_addr,\n            tile_addr,\n            palette_index,\n            palette_addr,\n            attr_addr,\n            attr_val,\n        }\n    }\n\n    fn nametable_tile(&mut self, ui: &mut Ui, label: &str, offset: Option<Vec2>) {\n        let tile = offset\n            .map(|offset| self.nametable_tile_from_offset(offset, self.nametables.texture.size));\n        let NametableTile {\n            uv,\n            index,\n            col,\n            row,\n            x,\n            y,\n            nametable_addr,\n            tile_addr,\n            palette_index,\n            palette_addr,\n            attr_addr,\n            attr_val,\n            ..\n        } = tile.unwrap_or_default();\n\n        let grid = Grid::new(label).num_columns(2).spacing([40.0, 6.0]);\n        grid.show(ui, |ui| {\n            ui.strong(\"Tile:\");\n            let tile_image = Image::from_texture(self.nametables.texture.sized())\n                .uv(uv)\n                .maintain_aspect_ratio(false) // Ignore original aspect ratio\n                .fit_to_exact_size(Vec2::splat(64.0))\n                .sense(Sense::click());\n            ui.add(tile_image);\n            ui.end_row();\n\n            ui.strong(\"Palette:\");\n            if tile.is_some() {\n                self.palette_row(\n                    ui,\n                    palette_index.into(),\n                    ui.cursor().min,\n                    Vec2::splat(16.0),\n                    true,\n                );\n            }\n            ui.end_row();\n\n            ui.strong(\"Column, Row:\");\n            if tile.is_some() {\n                ui.label(format!(\"{col}, {row}\"));\n            }\n            ui.end_row();\n\n            ui.strong(\"X, Y:\");\n            if tile.is_some() {\n                ui.label(format!(\"{x}, {y}\"));\n            }\n            ui.end_row();\n\n            ui.strong(\"Nametable Address:\");\n            if tile.is_some() {\n                ui.label(format!(\"${nametable_addr:04X}\"));\n            }\n            ui.end_row();\n\n            ui.strong(\"Tile Index:\");\n            if tile.is_some() {\n                ui.label(format!(\"${index:02X}\"));\n            }\n            ui.end_row();\n\n            ui.strong(\"Tile Address:\");\n            if tile.is_some() {\n                ui.label(format!(\"${tile_addr:04X}\"));\n            }\n            ui.end_row();\n\n            ui.strong(\"Palette Index:\");\n            if tile.is_some() {\n                ui.label(format!(\"{palette_index}\"));\n            }\n            ui.end_row();\n\n            ui.strong(\"Palette Address:\");\n            if tile.is_some() {\n                ui.label(format!(\"${palette_addr:04X}\"));\n            }\n            ui.end_row();\n\n            ui.strong(\"Attribute Address:\");\n            if tile.is_some() {\n                ui.label(format!(\"${attr_addr:04X}\"));\n            }\n            ui.end_row();\n\n            ui.strong(\"Attribute Value:\");\n            if tile.is_some() {\n                ui.label(format!(\"${attr_val:02X}\"));\n            }\n            ui.end_row();\n        });\n    }\n\n    fn nametable_scroll_overlay(&self, ui: &mut Ui, image_rect: Rect) {\n        let Ppu {\n            cycle,\n            scanline,\n            vblank_scanline,\n            prerender_scanline,\n            scroll,\n            ..\n        } = self.ppu;\n        let use_scroll_t = scanline >= vblank_scanline\n            || (scanline == scanline::VISIBLE_END && cycle >= cycle::SPR_EVAL_END)\n            || (scanline == prerender_scanline && cycle < cycle::BG_PREFETCH_START + 7);\n        let scroll_v = if use_scroll_t { scroll.t } else { scroll.v };\n\n        let mut scroll_x = ((scroll_v & Scroll::COARSE_X_MASK) << 3)\n            | (((scroll_v & Scroll::NT_X_MASK) >> 10) * ppu::size::WIDTH);\n        let scroll_y = ((scroll_v & Scroll::COARSE_Y_MASK) >> 2)\n            | (((scroll_v & Scroll::NT_Y_MASK) >> 11) * ppu::size::HEIGHT)\n            | ((scroll_v & Scroll::FINE_Y_MASK) >> 12);\n\n        if use_scroll_t {\n            scroll_x |= scroll.fine_x;\n        } else {\n            // During rendering, subtract according to current cycle/scanline\n            if cycle <= scanline::VISIBLE_END {\n                if cycle >= 8 {\n                    scroll_x = scroll_x.saturating_sub(cycle & !0x07);\n                }\n                // Adjust for 2x increments at end of last scanline\n                scroll_x = scroll_x.saturating_sub(16);\n            } else if cycle >= cycle::BG_PREFETCH_START + 7 {\n                scroll_x = scroll_x.saturating_sub(8);\n                if cycle >= cycle::BG_PREFETCH_END {\n                    scroll_x = scroll_x.saturating_sub(8);\n                }\n            }\n            scroll_x += scroll.fine_x;\n        }\n\n        // Scroll overlay\n        let nametable_size = image_rect.size() / 2.0;\n        // Translate scroll_x/scroll_y to image space\n        let scroll = Vec2::new(scroll_x as f32, scroll_y as f32) * image_rect.size()\n            / self.nametables.texture.size;\n        let scroll_min = image_rect.min + scroll;\n        let scroll_max = scroll_min + nametable_size;\n        let overlay = Rect::from_min_max(scroll_min, scroll_max.min(image_rect.max));\n        ui.painter().rect(\n            overlay,\n            0.0,\n            Color32::from_black_alpha(75),\n            (1.0, Color32::WHITE),\n            egui::StrokeKind::Inside,\n        );\n\n        // Wrap overlay around the right/bottom edge\n        let Vec2 { x, y } = scroll_max - image_rect.max;\n        let wrapped_size = Vec2::new(\n            if x > 0.0 { x } else { nametable_size.x },\n            if y > 0.0 { y } else { nametable_size.y },\n        );\n        if wrapped_size.max_elem() > 0.0 {\n            ui.painter().rect(\n                Rect::from_min_size(image_rect.min, wrapped_size),\n                0.0,\n                Color32::from_black_alpha(75),\n                (1.0, Color32::WHITE),\n                egui::StrokeKind::Inside,\n            );\n        }\n    }\n\n    fn pattern_tables_tab(&mut self, ui: &mut Ui) {\n        Panel::right(\"pattern_tables_panel\").show_inside(ui, |ui| {\n            ScrollArea::vertical().show(ui, |ui| {\n                ui.add_space(12.0);\n                ui.heading(\"Selected Tile\");\n                ui.separator();\n                self.pattern_tables_tile(\n                    ui,\n                    \"pattern_tables_tile_selected\",\n                    self.pattern_tables.selected,\n                );\n\n                ui.add_space(16.0);\n                ui.separator();\n\n                ui.collapsing(\"Settings\", |ui| {\n                    self.general_settings(ui);\n                    self.grid_settings(ui);\n                    // TODO: Selectable palette/last known palette\n                    zoom_slider(ui, &mut self.pattern_tables.zoom);\n                });\n            });\n        });\n\n        let texture_size = self.pattern_tables.texture.size;\n        CentralPanel::default().show_inside(ui, |ui| {\n            let scroll = ScrollArea::both()\n                .min_scrolled_width(texture_size.x)\n                .min_scrolled_height(texture_size.y);\n            scroll.show(ui, |ui| {\n                let image = Image::from_texture(self.pattern_tables.texture.sized())\n                    .fit_to_exact_size(self.pattern_tables.zoom * texture_size)\n                    .sense(Sense::click());\n\n                let res = ui.add(image).on_hover_cursor(CursorIcon::Cell);\n                let image_rect = res.rect;\n\n                if let Some(pos) = res.hover_pos()\n                    && image_rect.contains(pos)\n                {\n                    self.pattern_tables_hover(ui, &res, pos);\n                }\n\n                if self.show_dividers {\n                    ui.painter().vline(\n                        image_rect.center().x,\n                        image_rect.y_range(),\n                        (1.0, Color32::WHITE),\n                    );\n                }\n\n                if self.show_tile_grid {\n                    paint_grid(ui, image_rect, 16.0, 32.0, Color32::LIGHT_BLUE);\n                }\n\n                if let Some(offset) = self.pattern_tables.selected {\n                    let selection =\n                        tile_selection(image_rect, self.pattern_tables.texture.size, offset);\n                    animated_dashed_rect(ui, selection, (1.0, Color32::WHITE), 3.0, 3.0);\n                }\n            });\n        });\n    }\n\n    fn pattern_tables_hover(&mut self, ui: &mut Ui, res: &egui::Response, pos: Pos2) {\n        let image_rect = res.rect;\n        let texture_size = self.pattern_tables.texture.size;\n\n        let offset = translate_screen_pos_to_tile(pos, image_rect, texture_size);\n        let selection = tile_selection(image_rect, texture_size, offset);\n\n        animated_dashed_rect(\n            ui,\n            selection,\n            (1.0, Color32::from_white_alpha(220)),\n            3.0,\n            3.0,\n        );\n\n        res.clone().on_hover_ui_at_pointer(|ui| {\n            self.pattern_tables_tile(ui, \"pattern_tables_tile_hover\", Some(offset));\n        });\n        if res.clicked() {\n            self.pattern_tables.selected = Some(offset);\n        }\n    }\n\n    fn pattern_chr_tile_from_offset(&self, offset: Vec2, texture_size: Vec2) -> ChrTile {\n        let Vec2 { x, y } = offset;\n\n        // Get row/column 8x8 tile and the pattern table it's in\n        let mut col = x as u16 / 8;\n        let row = y as u16 / 8;\n        let pattern_table = if col >= 16 { 1 } else { 0 };\n\n        // Wrap column to a single pattern table\n        col &= 15;\n\n        let tile_uv = Rect::from_min_size(\n            (Vec2::new(x, y) / texture_size).to_pos2(),\n            Vec2::splat(8.0) / texture_size,\n        );\n        let tile_addr = (pattern_table << 12) | ((col + (row << 4)) << 4);\n\n        ChrTile {\n            index: (tile_addr >> 4) & 0xFF,\n            uv: tile_uv,\n            tile_addr,\n        }\n    }\n\n    fn pattern_tables_tile(&mut self, ui: &mut Ui, label: &str, offset: Option<Vec2>) {\n        let tile = offset.map(|offset| {\n            self.pattern_chr_tile_from_offset(offset, self.pattern_tables.texture.size)\n        });\n        let ChrTile {\n            uv,\n            index,\n            tile_addr,\n            ..\n        } = tile.unwrap_or_default();\n\n        let grid = Grid::new(label).num_columns(2).spacing([40.0, 6.0]);\n        grid.show(ui, |ui| {\n            ui.strong(\"Tile:\");\n            let tile_image = Image::from_texture(self.pattern_tables.texture.sized())\n                .uv(uv)\n                .maintain_aspect_ratio(false) // Ignore original aspect ratio\n                .fit_to_exact_size(Vec2::splat(64.0))\n                .sense(Sense::click());\n            ui.add(tile_image);\n            ui.end_row();\n\n            ui.strong(\"Tile Index:\");\n            if tile.is_some() {\n                ui.label(format!(\"${index:02X}\"));\n            }\n            ui.end_row();\n\n            ui.strong(\"Tile Address:\");\n            if tile.is_some() {\n                ui.label(format!(\"${tile_addr:04X}\"));\n            }\n            ui.end_row();\n        });\n    }\n\n    fn oam_tab(&mut self, ui: &mut Ui) {\n        Panel::right(\"oam_panel\").show_inside(ui, |ui| {\n            ScrollArea::vertical().show(ui, |ui| {\n                ui.add_space(12.0);\n                ui.heading(\"Selected Tile\");\n                ui.separator();\n                self.oam_tile(ui, \"oam_selected\", self.oam.oam_selected);\n\n                ui.add_space(16.0);\n                ui.separator();\n\n                ui.collapsing(\"Settings\", |ui| {\n                    self.general_settings(ui);\n\n                    let res = ui\n                        .checkbox(&mut self.show_tile_grid, \"Tile Grid\")\n                        .on_hover_text(\"Show grid lines between tiles.\");\n                    if res.changed() {\n                        // TODO: update config\n                    }\n\n                    zoom_slider(ui, &mut self.oam.zoom);\n                });\n            });\n        });\n\n        CentralPanel::default().show_inside(ui, |ui| {\n            let scroll = ScrollArea::both()\n                .min_scrolled_width(self.oam.oam_texture.size.x)\n                .min_scrolled_height(self.oam.oam_texture.size.y);\n            scroll.show(ui, |ui| {\n                ui.horizontal(|ui| {\n                    // Draw OAM tiles\n                    let image = Image::from_texture(self.oam.oam_texture.sized())\n                        .fit_to_exact_size(2.0 * self.oam.zoom * self.oam.oam_texture.size)\n                        .sense(Sense::click());\n\n                    let res = ui.add(image).on_hover_cursor(CursorIcon::Cell);\n                    let oam_image_rect = res.rect;\n\n                    if let Some(pos) = res.hover_pos()\n                        && oam_image_rect.contains(pos)\n                    {\n                        self.oam_hover(ui, &res, pos);\n                    }\n\n                    if self.show_tile_grid {\n                        paint_grid(ui, oam_image_rect, 8.0, 8.0, Color32::LIGHT_BLUE);\n                    }\n\n                    let image = Image::from_texture(self.oam.sprites_texture.sized())\n                        // match OAM size\n                        .shrink_to_fit()\n                        .sense(Sense::click());\n\n                    let res = ui.add(image).on_hover_cursor(CursorIcon::Cell);\n                    let spr_image_rect = res.rect;\n\n                    if let Some(pos) = res.hover_pos()\n                        && spr_image_rect.contains(pos)\n                    {\n                        self.sprites_hover(ui, &res, pos);\n                    }\n\n                    if self.show_tile_grid {\n                        paint_grid(ui, spr_image_rect, 30.0, 32.0, Color32::LIGHT_BLUE);\n                    }\n\n                    if let Some(offset) = self.oam.oam_selected {\n                        let selection =\n                            tile_selection(oam_image_rect, self.oam.oam_texture.size, offset);\n                        animated_dashed_rect(ui, selection, (1.0, Color32::WHITE), 3.0, 3.0);\n\n                        let sprite_index =\n                            (offset.x / 8.0) as usize + (offset.y / 8.0) as usize * 8;\n                        let sprite = self.oam.sprites.get(sprite_index);\n                        if let Some(sprite) = sprite {\n                            let offset = Vec2::new(\n                                ((sprite.x as f32) / 8.0).floor() * 8.0,\n                                ((sprite.y as f32) / 8.0).floor() * 8.0,\n                            );\n                            if offset.x < ppu::size::WIDTH as f32\n                                && offset.y < ppu::size::HEIGHT as f32\n                            {\n                                let selection = tile_selection(\n                                    spr_image_rect,\n                                    self.oam.sprites_texture.size,\n                                    offset,\n                                );\n                                animated_dashed_rect(\n                                    ui,\n                                    selection,\n                                    (1.0, Color32::WHITE),\n                                    3.0,\n                                    3.0,\n                                );\n                            }\n                        }\n                    }\n                });\n            });\n        });\n    }\n\n    fn oam_hover(&mut self, ui: &mut Ui, res: &egui::Response, pos: Pos2) {\n        let image_rect = res.rect;\n        let texture_size = self.oam.oam_texture.size;\n\n        let offset = translate_screen_pos_to_tile(pos, image_rect, texture_size);\n        let selection = tile_selection(image_rect, texture_size, offset);\n\n        animated_dashed_rect(\n            ui,\n            selection,\n            (1.0, Color32::from_white_alpha(220)),\n            3.0,\n            3.0,\n        );\n\n        let sprite_index = (offset.x / 8.0) as usize + (offset.y / 8.0) as usize * 8;\n        let sprite = self.oam.sprites.get(sprite_index);\n        if sprite.is_some() {\n            res.clone().on_hover_ui_at_pointer(|ui| {\n                self.oam_tile(ui, \"oam_hover\", Some(offset));\n            });\n            if res.clicked() {\n                self.oam.oam_selected = Some(offset);\n            }\n        }\n    }\n\n    fn sprites_hover(&mut self, ui: &mut Ui, res: &egui::Response, pos: Pos2) {\n        let image_rect = res.rect;\n        let texture_size = self.oam.sprites_texture.size;\n\n        let offset = translate_screen_pos_to_tile(pos, image_rect, texture_size);\n        let selection = tile_selection(image_rect, texture_size, offset);\n\n        animated_dashed_rect(\n            ui,\n            selection,\n            (1.0, Color32::from_white_alpha(220)),\n            3.0,\n            3.0,\n        );\n\n        let sprite_index = self.oam.sprites.iter().position(|sprite| {\n            let grid_x = sprite.x as f32 / 8.0;\n            let grid_y = sprite.y as f32 / 8.0;\n            let x_min = grid_x.floor() * 8.0;\n            let x_max = grid_x.ceil() * 8.0;\n            let y_min = grid_y.floor() * 8.0;\n            let y_max = grid_y.ceil() * 8.0;\n            (x_min..=x_max).contains(&offset.x) && (y_min..=y_max).contains(&offset.y)\n        });\n        if let Some(index) = sprite_index {\n            let offset = Vec2::new((index % 8) as f32, (index / 8) as f32) * 8.0;\n\n            res.clone().on_hover_ui_at_pointer(|ui| {\n                self.oam_tile(ui, \"oam_hover\", Some(offset));\n            });\n            if res.clicked() {\n                self.oam.oam_selected = Some(offset);\n            }\n        }\n    }\n\n    fn oam_tile(&mut self, ui: &mut Ui, label: &str, offsets: Option<Vec2>) {\n        let tile =\n            offsets.map(|offset| self.oam_tile_from_offset(offset, self.oam.oam_texture.size));\n        let ChrTile {\n            uv,\n            index,\n            tile_addr,\n            ..\n        } = tile.unwrap_or_default();\n\n        let grid = Grid::new(label).num_columns(2).spacing([40.0, 6.0]);\n        grid.show(ui, |ui| {\n            ui.strong(\"Tile:\");\n            let tile_image = Image::from_texture(self.oam.oam_texture.sized())\n                .uv(uv)\n                .maintain_aspect_ratio(false) // Ignore original aspect ratio\n                .fit_to_exact_size(Vec2::splat(64.0))\n                .sense(Sense::click());\n            ui.add(tile_image);\n            ui.end_row();\n\n            ui.strong(\"Tile Index:\");\n            if tile.is_some() {\n                ui.label(format!(\"${index:02X}\"));\n            }\n            ui.end_row();\n\n            ui.strong(\"Tile Address:\");\n            if tile.is_some() {\n                ui.label(format!(\"${tile_addr:04X}\"));\n            }\n            ui.end_row();\n\n            // TODO: sprite index, palete address, position, horizontal/vertical flip/backgroud\n            // priority, palette row\n        });\n    }\n\n    fn oam_tile_from_offset(&self, offset: Vec2, texture_size: Vec2) -> ChrTile {\n        let Vec2 { x, y } = offset;\n\n        // Get row/column 8x8 tile\n        let col = x as u16 / 8;\n        let row = y as u16 / 8;\n\n        let tile_uv = Rect::from_min_size(\n            (Vec2::new(x, y) / texture_size).to_pos2(),\n            Vec2::splat(8.0) / texture_size,\n        );\n        let index = col + (row * 8);\n        ChrTile {\n            index,\n            uv: tile_uv,\n            tile_addr: self.oam.sprites[index as usize].tile_addr,\n        }\n    }\n\n    fn palette_tab(&mut self, ui: &mut Ui) {\n        Panel::right(\"palette_panel\").show_inside(ui, |ui| {\n            ScrollArea::vertical().show(ui, |ui| {\n                ui.add_space(12.0);\n                ui.heading(\"Selected Color\");\n                ui.separator();\n                self.palette(ui, \"palette_info_selected\", self.palette.selected);\n            });\n        });\n\n        CentralPanel::default().show_inside(ui, |ui| {\n            ScrollArea::both().show(ui, |ui| {\n                ui.horizontal(|ui| {\n                    let res = self\n                        .palette_grid(ui, 4.0 * self.palette.zoom * self.palette.size)\n                        .on_hover_cursor(CursorIcon::Cell);\n                    let palette_rect = res.rect;\n\n                    if let Some(pos) = res.hover_pos()\n                        && palette_rect.contains(pos)\n                    {\n                        self.palette_hover(ui, &res, pos);\n                    }\n\n                    if let Some(offset) = self.palette.selected {\n                        let selection = tile_selection(palette_rect, self.palette.size, offset);\n                        animated_dashed_rect(ui, selection, (1.0, Color32::WHITE), 3.0, 3.0);\n                    }\n                });\n            });\n        });\n    }\n\n    fn palette_hover(&mut self, ui: &mut Ui, res: &egui::Response, pos: Pos2) {\n        let image_rect = res.rect;\n\n        let offset = translate_screen_pos_to_tile(pos, image_rect, self.palette.size);\n        let selection = tile_selection(image_rect, self.palette.size, offset);\n\n        animated_dashed_rect(\n            ui,\n            selection,\n            (1.0, Color32::from_white_alpha(220)),\n            3.0,\n            3.0,\n        );\n\n        res.clone().on_hover_ui_at_pointer(|ui| {\n            self.palette(ui, \"palette_hover\", Some(offset));\n        });\n        if res.clicked() {\n            self.palette.selected = Some(offset);\n        }\n    }\n\n    fn palette_color_from_offset(&self, offset: Vec2) -> PaletteColor {\n        let Vec2 { x, y } = offset;\n\n        // Get row/column 32x32 palette and the palette table it's in\n        let mut col = x as u16 / 8;\n        let row = y as u16 / 8;\n        let palette = if col >= 4 { 1 } else { 0 };\n\n        // Wrap column to a single palette table\n        col &= 3;\n\n        let index = col + row * 4;\n        let color_index = palette * 0x10 + index;\n        let pixel_idx = color_index as usize * 4;\n        PaletteColor {\n            index: index as u8,\n            addr: addr::PALETTE_START + color_index,\n            value: self.palette.colors[color_index as usize],\n            color: if let [red, green, blue] = self.palette.pixels[pixel_idx..pixel_idx + 3] {\n                Color32::from_rgb(red, green, blue)\n            } else {\n                Color32::default()\n            },\n        }\n    }\n\n    fn palette(&mut self, ui: &mut Ui, label: &str, offset: Option<Vec2>) {\n        let palette = offset.map(|offset| self.palette_color_from_offset(offset));\n        let PaletteColor {\n            index,\n            value,\n            color,\n            addr,\n            ..\n        } = palette.unwrap_or_default();\n\n        let grid = Grid::new(label).num_columns(2).spacing([40.0, 6.0]);\n        grid.show(ui, |ui| {\n            ui.strong(\"Color:\");\n            let (rect, _) = ui.allocate_exact_size(Vec2::splat(32.0), Sense::hover());\n            ui.painter().rect_filled(rect, 1.0, color);\n            ui.end_row();\n\n            ui.strong(\"Index:\");\n            if palette.is_some() {\n                ui.label(format!(\"${index:02X}\"));\n            }\n            ui.end_row();\n\n            ui.strong(\"Value:\");\n            if palette.is_some() {\n                ui.label(format!(\"${value:02X}\"));\n            }\n            ui.end_row();\n\n            ui.strong(\"Palette Address:\");\n            if palette.is_some() {\n                ui.label(format!(\"${addr:02X}\"));\n            }\n            ui.end_row();\n\n            ui.strong(\"Hex:\");\n            if palette.is_some() {\n                ui.label(&color.to_hex()[0..7]); // Truncate the alpha channel\n            }\n            ui.end_row();\n\n            ui.strong(\"RGB:\");\n            if palette.is_some() {\n                let (r, g, b, _) = &color.to_tuple();\n                ui.label(format!(\"({r:03}, {g:03}, {b:03})\"));\n            }\n            ui.end_row();\n        });\n    }\n\n    fn palette_row(&self, ui: &mut Ui, index: usize, pos: Pos2, size: Vec2, show_backdrop: bool) {\n        for x in 0..4 {\n            let mut idx = (index * 4 + x) * 4;\n            if show_backdrop && x == 0 {\n                idx = 0;\n            }\n            if let [red, green, blue] = self.palette.pixels[idx..idx + 3] {\n                let pos = pos + Vec2::new(x as f32 * size.x, 0.0);\n                let rect = Rect::from_min_max(pos, pos + size);\n                ui.painter()\n                    .rect_filled(rect, 0.0, Color32::from_rgb(red, green, blue));\n            }\n        }\n    }\n\n    fn palette_grid(&self, ui: &mut Ui, size: Vec2) -> egui::Response {\n        ui.vertical(|ui| {\n            ui.horizontal(|ui| {\n                let res = ui.add(Label::new(\"Background\"));\n                ui.add_space(size.x / 2.0 - res.rect.width());\n                ui.add(Label::new(\"Sprites\"));\n            });\n\n            let (rect, res) = ui.allocate_exact_size(size, Sense::click());\n            ui.painter()\n                .rect_stroke(rect, 0.0, (1.0, Color32::BLACK), StrokeKind::Inside);\n\n            let size = Vec2::new(size.x / 8.0, size.y / 4.0);\n            for offset in [0, 4] {\n                for (y, index) in (offset..offset + 4).enumerate() {\n                    let pos =\n                        rect.min + Vec2::new(offset as f32 * size.x, y as f32 * size.y).floor();\n                    self.palette_row(ui, index, pos, size, false);\n                }\n            }\n\n            res\n        })\n        .inner\n    }\n}\n\n/// A Zoom slider\nfn zoom_slider(ui: &mut Ui, zoom: &mut f32) {\n    ui.horizontal(|ui| {\n        let drag = Slider::new(zoom, 0.1..=5.0).step_by(0.05).suffix(\"x\");\n        let res = ui.add(drag);\n        if res.changed() {\n            // TODO: update config\n        }\n        ui.label(\"Zoom\")\n            .on_hover_cursor(CursorIcon::Help)\n            .on_hover_text(\"Zoom preview in or out.\");\n    });\n}\n\n/// A grid overlay.\nfn paint_grid(ui: &mut Ui, rect: Rect, y_spacing: f32, x_spacing: f32, color: Color32) {\n    let min = rect.min;\n    let max = rect.max;\n    let size = rect.size();\n    let x_increment = size.x / x_spacing;\n    let mut x = min.x + x_increment;\n    while x < max.x {\n        ui.painter().vline(x, rect.y_range(), (1.0, color));\n        x += x_increment;\n    }\n\n    let y_increment = size.y / y_spacing;\n    let mut y = min.y + y_increment;\n    while y < max.y {\n        ui.painter().hline(rect.x_range(), y, (1.0, color));\n        y += y_increment;\n    }\n}\n\n/// Translate position in screen space to texture space and find containing 8x8 tile offset\nfn translate_screen_pos_to_tile(pos: Pos2, image_rect: Rect, texture_size: Vec2) -> Vec2 {\n    let normalized_pos = (pos - image_rect.min) / image_rect.size();\n    let texture_pos = normalized_pos * texture_size;\n    (texture_pos / 8.0).floor() * 8.0\n}\n\n/// Return tile selection rectangle given an offset.\nfn tile_selection(image_rect: Rect, texture_size: Vec2, tile_offset: Vec2) -> Rect {\n    let scale = image_rect.size() / texture_size;\n    Rect::from_min_size(\n        image_rect.min + scale * tile_offset,\n        scale * Vec2::splat(8.0),\n    )\n}\n"
  },
  {
    "path": "tetanes/src/nes/renderer/gui/preferences.rs",
    "content": "use crate::{\n    feature,\n    nes::{\n        action::Setting,\n        config::{AudioConfig, Config, EmulationConfig, RendererConfig},\n        event::{ConfigEvent, NesEventProxy, UiEvent},\n        renderer::{\n            gui::{\n                MessageType,\n                lib::{RadioValue, ShortcutText, ShowShortcut, ViewportOptions},\n            },\n            shader::Shader,\n        },\n    },\n};\nuse egui::{\n    Align, CentralPanel, Checkbox, Context, CursorIcon, DragValue, Grid, Key, Layout, ScrollArea,\n    Slider, TextEdit, Ui, Vec2, ViewportClass, ViewportId,\n};\nuse parking_lot::Mutex;\nuse std::sync::{\n    Arc,\n    atomic::{AtomicBool, Ordering},\n};\nuse tetanes_core::{\n    action::Action as DeckAction, apu::Channel, common::NesRegion,\n    control_deck::Config as DeckConfig, fs, genie::GenieCode, input::FourPlayer, mem::RamState,\n    time::Duration, video::VideoFilter,\n};\n\n#[derive(Debug)]\n#[must_use]\npub struct State {\n    tx: NesEventProxy,\n    tab: Tab,\n    genie_entry: GenieEntry,\n}\n\n#[derive(Debug)]\n#[must_use]\npub struct Preferences {\n    pub id: ViewportId,\n    open: Arc<AtomicBool>,\n    state: Arc<Mutex<State>>,\n}\n\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]\npub enum Tab {\n    #[default]\n    Emulation,\n    Audio,\n    Video,\n    Input,\n}\n\n#[derive(Default, Debug, Clone, PartialEq, Eq)]\npub struct GenieEntry {\n    code: String,\n    error: Option<String>,\n}\n\nimpl Preferences {\n    const TITLE: &'static str = \"TetaNES - Preferences\";\n\n    pub fn new(tx: NesEventProxy) -> Self {\n        Self {\n            id: egui::ViewportId::from_hash_of(Self::TITLE),\n            open: Arc::new(AtomicBool::new(false)),\n            state: Arc::new(Mutex::new(State {\n                tx,\n                tab: Tab::default(),\n                genie_entry: GenieEntry::default(),\n            })),\n        }\n    }\n\n    pub fn open(&self) -> bool {\n        self.open.load(Ordering::Acquire)\n    }\n\n    pub fn set_open(&self, open: bool, ctx: &Context) {\n        self.open.store(open, Ordering::Release);\n        if open {\n            ctx.send_viewport_cmd_to(self.id, egui::ViewportCommand::Close);\n        }\n    }\n\n    pub fn toggle_open(&self, ctx: &Context) {\n        let Ok(open) = self\n            .open\n            .fetch_update(Ordering::Release, Ordering::Acquire, |open| Some(!open))\n        else {\n            return;\n        };\n        if open {\n            ctx.send_viewport_cmd_to(self.id, egui::ViewportCommand::Close);\n        }\n    }\n\n    pub fn show(&mut self, ui: &mut Ui, opts: ViewportOptions, cfg: Config) {\n        if !self.open() {\n            return;\n        }\n\n        let open = Arc::clone(&self.open);\n        let state = Arc::clone(&self.state);\n\n        let mut viewport_builder = egui::ViewportBuilder::default().with_title(Self::TITLE);\n        if opts.always_on_top {\n            viewport_builder = viewport_builder.with_always_on_top();\n        }\n\n        ui.show_viewport_deferred(self.id, viewport_builder, move |ui, class| {\n            if class == ViewportClass::EmbeddedWindow {\n                let mut window_open = open.load(Ordering::Acquire);\n                egui::Window::new(Preferences::TITLE)\n                    .open(&mut window_open)\n                    .default_rect(ui.content_rect().shrink(16.0))\n                    .show(ui, |ui| state.lock().ui(ui, opts.enabled, &cfg));\n                open.store(window_open, Ordering::Release);\n            } else {\n                CentralPanel::default()\n                    .show_inside(ui, |ui| state.lock().ui(ui, opts.enabled, &cfg));\n                if ui.input(|i| i.viewport().close_requested()) {\n                    open.store(false, Ordering::Release);\n                }\n            }\n        });\n    }\n\n    pub fn show_genie_codes_entry(&mut self, ui: &mut Ui, cfg: &Config) {\n        self.state.lock().genie_codes_entry(ui, cfg);\n    }\n\n    pub fn genie_codes_list(tx: &NesEventProxy, ui: &mut Ui, cfg: &Config, scroll: bool) {\n        if !cfg.deck.genie_codes.is_empty() {\n            ui.vertical(|ui| {\n                ui.horizontal(|ui| {\n                    ui.strong(\"Current Genie Codes:\");\n                    if ui.button(\"Clear All\").clicked() {\n                        tx.event(ConfigEvent::GenieCodeClear);\n                    }\n                });\n\n                let render_codes = |ui: &mut Ui, cfg: &Config| {\n                    ui.indent(\"current_genie_codes\", |ui| {\n                        let grid = Grid::new(\"genie_codes\").num_columns(2).spacing([40.0, 6.0]);\n                        grid.show(ui, |ui| {\n                            for genie in &cfg.deck.genie_codes {\n                                ui.label(genie.code());\n                                // icon: waste basket\n                                if ui.button(\"🗑\").clicked() {\n                                    tx.event(ConfigEvent::GenieCodeRemoved(\n                                        genie.code().to_string(),\n                                    ));\n                                }\n                                ui.end_row();\n                            }\n                        });\n                    })\n                };\n                if scroll {\n                    ScrollArea::vertical().show(ui, |ui| {\n                        render_codes(ui, cfg);\n                    });\n                } else {\n                    render_codes(ui, cfg);\n                }\n            });\n        }\n    }\n\n    pub fn save_slot_radio(\n        tx: &NesEventProxy,\n        ui: &mut Ui,\n        mut save_slot: u8,\n        cfg: &Config,\n        show_shortcut: ShowShortcut,\n    ) {\n        ui.vertical(|ui| {\n            for slot in 1..=4 {\n                let radio = RadioValue::new(&mut save_slot, slot, slot.to_string()).shortcut_text(\n                    show_shortcut\n                        .then(|| cfg.shortcut(DeckAction::SetSaveSlot(slot)))\n                        .unwrap_or_default(),\n                );\n                if ui.add(radio).changed() {\n                    tx.event(ConfigEvent::SaveSlot(save_slot));\n                }\n            }\n        });\n        ui.vertical(|ui| {\n            for slot in 5..=8 {\n                let radio = RadioValue::new(&mut save_slot, slot, slot.to_string()).shortcut_text(\n                    show_shortcut\n                        .then(|| cfg.shortcut(DeckAction::SetSaveSlot(slot)))\n                        .unwrap_or_default(),\n                );\n                if ui.add(radio).changed() {\n                    tx.event(ConfigEvent::SaveSlot(save_slot));\n                }\n            }\n        });\n    }\n\n    pub fn speed_slider(tx: &NesEventProxy, ui: &mut Ui, mut speed: f32) {\n        let slider = Slider::new(&mut speed, 0.25..=2.0)\n            .step_by(0.25)\n            .suffix(\"x\");\n        let res = ui\n            .add(slider)\n            .on_hover_text(\"Adjust the speed of the NES emulation.\");\n        if res.changed() {\n            tx.event(ConfigEvent::Speed(speed));\n        }\n    }\n\n    pub fn run_ahead_slider(tx: &NesEventProxy, ui: &mut Ui, mut run_ahead: usize) {\n        let slider = Slider::new(&mut run_ahead, 0..=4);\n        let res = ui\n            .add(slider)\n            .on_hover_text(\"Simulate a number of frames in the future to reduce input lag.\");\n        if res.changed() {\n            tx.event(ConfigEvent::RunAhead(run_ahead));\n        }\n    }\n\n    pub fn rewind_checkbox(\n        tx: &NesEventProxy,\n        ui: &mut Ui,\n        mut rewind: bool,\n        shortcut: impl Into<Option<String>>,\n    ) {\n        let shortcut = shortcut.into();\n        let icon = shortcut.as_ref().map(|_| \"🔄 \").unwrap_or_default();\n        let checkbox = Checkbox::new(&mut rewind, format!(\"{icon}Enable Rewinding\"))\n            .shortcut_text(shortcut.unwrap_or_default());\n        let res = ui\n            .add(checkbox)\n            .on_hover_text(\"Enable instant and visual rewinding. Increases memory usage.\");\n        if res.clicked() {\n            tx.event(ConfigEvent::RewindEnabled(rewind));\n        }\n    }\n\n    pub fn zapper_checkbox(\n        tx: &NesEventProxy,\n        ui: &mut Ui,\n        mut zapper: bool,\n        shortcut: impl Into<Option<String>>,\n    ) {\n        let shortcut = shortcut.into();\n        let icon = shortcut.as_ref().map(|_| \"🔫 \").unwrap_or_default();\n        let checkbox = Checkbox::new(&mut zapper, format!(\"{icon}Enable Zapper Gun\"))\n            .shortcut_text(shortcut.unwrap_or_default());\n        let res = ui\n            .add(checkbox)\n            .on_hover_text(\"Enable the Zapper Light Gun for games that support it.\");\n        if res.clicked() {\n            tx.event(ConfigEvent::ZapperConnected(zapper));\n        }\n    }\n\n    pub fn overscan_checkbox(\n        tx: &NesEventProxy,\n        ui: &mut Ui,\n        mut hide_overscan: bool,\n        shortcut: impl Into<Option<String>>,\n    ) {\n        let shortcut = shortcut.into();\n        let icon = shortcut.as_ref().map(|_| \"📺 \").unwrap_or_default();\n        let checkbox = Checkbox::new(&mut hide_overscan, format!(\"{icon}Hide Overscan\"))\n            .shortcut_text(shortcut.unwrap_or_default());\n        let res = ui.add(checkbox)\n            .on_hover_text(\"Traditional CRT displays would crop the top and bottom edges of the image. Disable this to show the overscan.\");\n        if res.clicked() {\n            tx.event(ConfigEvent::HideOverscan(hide_overscan));\n        }\n    }\n\n    pub fn video_filter_radio(\n        tx: &NesEventProxy,\n        ui: &mut Ui,\n        mut filter: VideoFilter,\n        cfg: &Config,\n        show_shortcut: ShowShortcut,\n    ) {\n        let previous_filter = filter;\n\n        let shortcut =\n            show_shortcut.then(|| cfg.shortcut(DeckAction::SetVideoFilter(VideoFilter::Pixellate)));\n        let icon = shortcut.as_ref().map(|_| \"🌁 \").unwrap_or_default();\n        let radio = RadioValue::new(\n            &mut filter,\n            VideoFilter::Pixellate,\n            format!(\"{icon}Pixellate\"),\n        )\n        .shortcut_text(shortcut.unwrap_or_default());\n        ui.add(radio).on_hover_text(\"Basic pixel-perfect rendering\");\n\n        let shortcut =\n            show_shortcut.then(|| cfg.shortcut(DeckAction::SetVideoFilter(VideoFilter::Ntsc)));\n        let icon = shortcut.as_ref().map(|_| \"📼 \").unwrap_or_default();\n        let radio = RadioValue::new(&mut filter, VideoFilter::Ntsc, format!(\"{icon}Ntsc\"))\n            .shortcut_text(shortcut.unwrap_or_default());\n        ui.add(radio).on_hover_text(\n            \"Emulate traditional NTSC rendering where chroma spills over into luma.\",\n        );\n\n        if filter != previous_filter {\n            tx.event(ConfigEvent::VideoFilter(filter));\n        }\n    }\n\n    pub fn shader_radio(\n        tx: &NesEventProxy,\n        ui: &mut Ui,\n        mut shader: Shader,\n        cfg: &Config,\n        show_shortcut: ShowShortcut,\n    ) {\n        let previous_shader = shader;\n\n        let shortcut = show_shortcut.then(|| cfg.shortcut(Setting::SetShader(Shader::Default)));\n        let icon = shortcut.as_ref().map(|_| \"🗋 \").unwrap_or_default();\n        let radio = RadioValue::new(&mut shader, Shader::Default, format!(\"{icon}Default\"))\n            .shortcut_text(shortcut.unwrap_or_default());\n        ui.add(radio).on_hover_text(\"Default shader.\");\n\n        let shortcut = show_shortcut.then(|| cfg.shortcut(Setting::SetShader(Shader::CrtEasymode)));\n        let icon = shortcut.as_ref().map(|_| \"📺 \").unwrap_or_default();\n        let radio = RadioValue::new(\n            &mut shader,\n            Shader::CrtEasymode,\n            format!(\"{icon}CRT Easymode\"),\n        )\n        .shortcut_text(shortcut.unwrap_or_default());\n        ui.add(radio)\n            .on_hover_text(\"Emulate traditional CRT aperture grill masking.\");\n\n        if shader != previous_shader {\n            tx.event(ConfigEvent::Shader(shader));\n        }\n    }\n\n    pub fn four_player_radio(tx: &NesEventProxy, ui: &mut Ui, mut four_player: FourPlayer) {\n        let previous_four_player = four_player;\n        ui.radio_value(&mut four_player, FourPlayer::Disabled, \"Disabled\");\n        ui.radio_value(&mut four_player, FourPlayer::FourScore, \"Four Score\")\n            .on_hover_text(\"Enable NES Four Score for games that support 4 players.\");\n        ui.radio_value(&mut four_player, FourPlayer::Satellite, \"Satellite\")\n            .on_hover_text(\"Enable NES Satellite for games that support 4 players.\");\n        if four_player != previous_four_player {\n            tx.event(ConfigEvent::FourPlayer(four_player));\n        }\n    }\n\n    pub fn nes_region_radio(tx: &NesEventProxy, ui: &mut Ui, mut region: NesRegion) {\n        let previous_region = region;\n        ui.radio_value(&mut region, NesRegion::Auto, \"Auto\")\n            .on_hover_text(\"Auto-detect region based on loaded ROM.\");\n        ui.radio_value(&mut region, NesRegion::Ntsc, \"NTSC\")\n            .on_hover_text(\"Emulate NTSC timing and aspect-ratio.\");\n        ui.radio_value(&mut region, NesRegion::Pal, \"PAL\")\n            .on_hover_text(\"Emulate PAL timing and aspect-ratio.\");\n        ui.radio_value(&mut region, NesRegion::Dendy, \"Dendy\")\n            .on_hover_text(\"Emulate Dendy timing and aspect-ratio.\");\n        if region != previous_region {\n            tx.event(ConfigEvent::Region(region));\n        }\n    }\n\n    pub fn ram_state_radio(tx: &NesEventProxy, ui: &mut Ui, mut ram_state: RamState) {\n        let previous_ram_state = ram_state;\n        ui.radio_value(&mut ram_state, RamState::AllZeros, \"All 0x00\")\n            .on_hover_text(\"Clear startup RAM to all zeroes for predictable emulation.\");\n        ui.radio_value(&mut ram_state, RamState::AllOnes, \"All 0xFF\")\n            .on_hover_text(\"Clear startup RAM to all ones for predictable emulation.\");\n        ui.radio_value(&mut ram_state, RamState::Random, \"Random\")\n            .on_hover_text(\"Randomize startup RAM, which some games use as a basic RNG seed.\");\n        if ram_state != previous_ram_state {\n            tx.event(ConfigEvent::RamState(ram_state));\n        }\n    }\n\n    pub fn menubar_checkbox(\n        tx: &NesEventProxy,\n        ui: &mut Ui,\n        mut show_menubar: bool,\n        shortcut: impl Into<Option<String>>,\n    ) {\n        let shortcut = shortcut.into();\n        let icon = shortcut.as_ref().map(|_| \"☰ \").unwrap_or_default();\n        let checkbox = Checkbox::new(&mut show_menubar, format!(\"{icon}Show Menu Bar\"))\n            .shortcut_text(shortcut.unwrap_or_default());\n        let res = ui.add(checkbox).on_hover_text(\"Show the menu bar.\");\n        if res.clicked() {\n            tx.event(ConfigEvent::ShowMenubar(show_menubar));\n        }\n    }\n\n    pub fn messages_checkbox(\n        tx: &NesEventProxy,\n        ui: &mut Ui,\n        mut show_messages: bool,\n        shortcut: impl Into<Option<String>>,\n    ) {\n        let shortcut = shortcut.into();\n        // icon: document with text\n        let icon = shortcut.as_ref().map(|_| \"🖹 \").unwrap_or_default();\n        let checkbox = Checkbox::new(&mut show_messages, format!(\"{icon}Show Messages\"))\n            .shortcut_text(shortcut.unwrap_or_default());\n        let res = ui\n            .add(checkbox)\n            .on_hover_text(\"Show shortcut and emulator messages.\");\n        if res.clicked() {\n            tx.event(ConfigEvent::ShowMessages(show_messages));\n        }\n    }\n\n    pub fn screen_reader_checkbox(ui: &mut Ui, shortcut: impl Into<Option<String>>) {\n        let shortcut = shortcut.into();\n        // icon: document with text\n        let icon = shortcut.as_ref().map(|_| \"🔈 \").unwrap_or_default();\n        let mut screen_reader = ui.ctx().options(|o| o.screen_reader);\n        let checkbox = Checkbox::new(&mut screen_reader, format!(\"{icon}Enable Screen Reader\"))\n            .shortcut_text(shortcut.unwrap_or_default());\n        let res = ui\n            .add(checkbox)\n            .on_hover_text(\"Enable screen reader to read buttons and labels out loud.\");\n        if res.clicked() {\n            ui.ctx().options_mut(|o| o.screen_reader = screen_reader);\n        }\n    }\n\n    pub fn window_scale_radio(tx: &NesEventProxy, ui: &mut Ui, mut scale: f32) {\n        let previous_scale = scale;\n        ui.vertical(|ui| {\n            ui.radio_value(&mut scale, 1.0, \"1x\");\n            ui.radio_value(&mut scale, 2.0, \"2x\");\n            ui.radio_value(&mut scale, 3.0, \"3x\");\n        });\n        ui.vertical(|ui| {\n            ui.radio_value(&mut scale, 4.0, \"4x\");\n            ui.radio_value(&mut scale, 5.0, \"5x\");\n        });\n        if scale != previous_scale {\n            tx.event(ConfigEvent::Scale(scale));\n        }\n    }\n\n    pub fn fullscreen_checkbox(\n        tx: &NesEventProxy,\n        ui: &mut Ui,\n        mut fullscreen: bool,\n        shortcut: impl Into<Option<String>>,\n    ) {\n        let shortcut = shortcut.into();\n        // icon: screen\n        let icon = shortcut.as_ref().map(|_| \"🖵 \").unwrap_or_default();\n        let checkbox = Checkbox::new(&mut fullscreen, format!(\"{icon}Fullscreen\"))\n            .shortcut_text(shortcut.unwrap_or_default());\n        if ui.add(checkbox).clicked() {\n            tx.event(ConfigEvent::Fullscreen(fullscreen));\n        }\n    }\n\n    pub fn embed_viewports_checkbox(\n        tx: &NesEventProxy,\n        ui: &mut Ui,\n        cfg: &Config,\n        shortcut: impl Into<Option<String>>,\n    ) {\n        if feature!(OsViewports) {\n            ui.add_enabled_ui(!cfg.renderer.fullscreen, |ui| {\n                let shortcut = shortcut.into();\n                // icon: maximize\n                let icon = shortcut.as_ref().map(|_| \"🗖 \").unwrap_or_default();\n                let mut embed_viewports = ui.ctx().embed_viewports();\n                let checkbox =\n                    Checkbox::new(&mut embed_viewports, format!(\"{icon}Embed Viewports\"))\n                        .shortcut_text(shortcut.unwrap_or_default());\n                let res = ui.add(checkbox).on_disabled_hover_text(\n                    \"Non-embedded viewports are not supported while in fullscreen.\",\n                );\n                if res.clicked() {\n                    ui.ctx().set_embed_viewports(embed_viewports);\n                    tx.event(ConfigEvent::EmbedViewports(embed_viewports));\n                }\n            });\n        }\n    }\n\n    pub fn always_on_top_checkbox(\n        tx: &NesEventProxy,\n        ui: &mut Ui,\n        mut always_on_top: bool,\n        shortcut: impl Into<Option<String>>,\n    ) {\n        if feature!(OsViewports) {\n            let shortcut = shortcut.into();\n            let icon = shortcut.as_ref().map(|_| \"🔝 \").unwrap_or_default();\n            let checkbox = Checkbox::new(&mut always_on_top, format!(\"{icon}Always on Top\"))\n                .shortcut_text(shortcut.unwrap_or_default());\n            // FIXME: Currently when not using embeded viewports, toggling always on top from\n            // the preferences window will focus the primary window, potentially obscuring the\n            // preferences window\n            if ui.add(checkbox).clicked() {\n                tx.event(ConfigEvent::AlwaysOnTop(always_on_top));\n            }\n        }\n    }\n}\n\nimpl State {\n    fn ui(&mut self, ui: &mut Ui, enabled: bool, cfg: &Config) {\n        ui.add_enabled_ui(enabled, |ui| {\n            ui.set_min_height(ui.available_height());\n\n            ui.horizontal(|ui| {\n                ui.selectable_value(&mut self.tab, Tab::Emulation, \"Emulation\");\n                ui.selectable_value(&mut self.tab, Tab::Audio, \"Audio\");\n                ui.selectable_value(&mut self.tab, Tab::Video, \"Video\");\n                ui.selectable_value(&mut self.tab, Tab::Input, \"Input\");\n            });\n\n            ui.separator();\n\n            ScrollArea::both().show(ui, |ui| {\n                match self.tab {\n                    Tab::Emulation => self.emulation_tab(ui, cfg),\n                    Tab::Audio => Self::audio_tab(&self.tx, ui, cfg),\n                    Tab::Video => Self::video_tab(&self.tx, ui, cfg),\n                    Tab::Input => Self::input_tab(&self.tx, ui, cfg),\n                }\n\n                ui.separator();\n\n                ui.horizontal(|ui| {\n                    if ui.button(\"Restore Defaults\").clicked() {\n                        Self::restore_defaults(&self.tx, ui.ctx());\n                    }\n\n                    if feature!(Storage) && ui.button(\"Clear Save States\").clicked() {\n                        Self::clear_save_states(&self.tx);\n                    }\n\n                    if feature!(Filesystem) && ui.button(\"Clear Recent ROMs\").clicked() {\n                        self.tx.event(ConfigEvent::RecentRomsClear);\n                    }\n\n                    #[cfg(target_arch = \"wasm32\")]\n                    if ui.button(\"Download Save States\").clicked()\n                        && let Err(err) = crate::platform::download_save_states()\n                    {\n                        self.tx\n                            .event(UiEvent::Message((MessageType::Error, err.to_string())));\n                    }\n                });\n            });\n        });\n    }\n\n    fn emulation_tab(&mut self, ui: &mut Ui, cfg: &Config) {\n        let EmulationConfig {\n            mut auto_save,\n            auto_save_interval,\n            mut auto_load,\n            rewind,\n            mut rewind_interval,\n            mut rewind_seconds,\n            run_ahead,\n            save_slot,\n            speed,\n            ..\n        } = cfg.emulation;\n        let DeckConfig {\n            mut emulate_ppu_warmup,\n            four_player,\n            ram_state,\n            region,\n            ..\n        } = cfg.deck;\n\n        let grid = Grid::new(\"emulation_checkboxes\")\n            .num_columns(2)\n            .spacing([80.0, 6.0]);\n        grid.show(ui, |ui| {\n            let tx = &self.tx;\n\n            let res = ui.checkbox(&mut auto_load, \"Auto-Load\")\n                .on_hover_text(\"Automatically load game state from the current save slot on load.\");\n            if res.changed() {\n                tx.event(ConfigEvent::AutoLoad(\n                    auto_load,\n                ));\n            }\n            ui.end_row();\n\n            ui.vertical(|ui| {\n                Preferences::rewind_checkbox(tx, ui, rewind, None);\n\n                ui.add_enabled_ui(rewind, |ui| {\n                    ui.indent(\"rewind_settings\", |ui| {\n                        ui.horizontal(|ui| {\n                            let suffix = if rewind_seconds == 1 { \" second\" } else { \" seconds\" };\n                            let drag = DragValue::new(&mut rewind_seconds)\n                                .range(1..=360)\n                                .suffix(suffix);\n                            let res = ui.add(drag)\n                                .on_hover_text(\"The maximum number of seconds to rewind.\");\n                            if res.changed() {\n                                tx.event(ConfigEvent::RewindSeconds(rewind_seconds));\n                            }\n                        });\n\n                        ui.horizontal(|ui| {\n                    let suffix = if rewind_interval == 1 { \" frame\" } else { \" frames\" };\n                            let drag = DragValue::new(&mut rewind_interval)\n                                .range(1..=60)\n                                .prefix(\"every \")\n                                .suffix(suffix);\n                            let res = ui.add(drag)\n                                .on_hover_text(\"The frame interval to save rewind states.\");\n                            if res.changed() {\n                                tx.event(ConfigEvent::RewindInterval(rewind_interval));\n                            }\n                        });\n                    });\n                });\n            });\n\n            ui.vertical(|ui| {\n                let res = ui.checkbox(&mut auto_save, \"Auto-Save\")\n                    .on_hover_text(concat!(\n                        \"Automatically save game state to the current save slot \",\n                        \"on exit or unloading and an optional interval. \",\n                        \"Setting to 0 will disable saving on an interval.\",\n                    ));\n                if res.changed() {\n                    tx.event(ConfigEvent::AutoSave(\n                        auto_save,\n                    ));\n                }\n\n                ui.add_enabled_ui(auto_save, |ui| {\n                    ui.indent(\"auto_save_settings\", |ui| {\n                        ui.horizontal(|ui| {\n                            let mut auto_save_interval = auto_save_interval.as_secs();\n                            let suffix = if auto_save_interval == 1 { \" second\" } else { \" seconds\" };\n                            let drag = DragValue::new(&mut auto_save_interval)\n                                .range(0..=60)\n                                .prefix(\"every \")\n                                .suffix(suffix);\n                            let res = ui.add(drag)\n                                .on_hover_text(concat!(\n                                    \"Set the interval to auto-save game state. \",\n                                    \"A value of `0` will still save on exit or unload while Auto-Save is enabled.\"\n                                ));\n                            if res.changed() {\n                                tx.event(ConfigEvent::AutoSaveInterval(Duration::from_secs(auto_save_interval)));\n                            }\n                        });\n                    });\n                });\n            });\n            ui.end_row();\n\n            let res = ui.checkbox(&mut emulate_ppu_warmup, \"Emulate PPU Warmup\")\n                .on_hover_text(concat!(\n                    \"Set whether to emulate PPU warmup where writes to certain registers are ignored. \",\n                    \"Can result in some games not working correctly\"\n                ));\n            if res.clicked() {\n                tx.event(ConfigEvent::EmulatePpuWarmup(emulate_ppu_warmup));\n            }\n            ui.end_row();\n        });\n\n        ui.separator();\n\n        let grid = Grid::new(\"emulation_sliders\")\n            .num_columns(2)\n            .spacing([40.0, 6.0]);\n        grid.show(ui, |ui| {\n            let tx = &self.tx;\n\n            ui.horizontal(|ui| {\n                Preferences::speed_slider(tx, ui, speed);\n                ui.label(\"Emulation Speed\")\n                    .on_hover_cursor(CursorIcon::Help)\n                    .on_hover_text(\"Change the speed of the emulation.\");\n            });\n            ui.end_row();\n\n            ui.horizontal(|ui| {\n                Preferences::run_ahead_slider(tx, ui, run_ahead);\n                ui.label(\"Run Ahead\")\n                    .on_hover_cursor(CursorIcon::Help)\n                    .on_hover_text(\n                        \"Simulate a number of frames in the future to reduce input lag.\",\n                    );\n            });\n            ui.end_row();\n        });\n\n        ui.separator();\n\n        let grid = Grid::new(\"emulation_radios\")\n            .num_columns(4)\n            .spacing([20.0, 6.0]);\n        grid.show(ui, |ui| {\n            let tx = &self.tx;\n\n            ui.with_layout(Layout::left_to_right(Align::Min), |ui| {\n                ui.strong(\"Save Slot:\")\n                    .on_hover_cursor(CursorIcon::Help)\n                    .on_hover_text(\"Select which slot to use when saving or loading game state.\");\n            });\n            Grid::new(\"save_slots\")\n                .num_columns(2)\n                .spacing([20.0, 6.0])\n                .show(ui, |ui| {\n                    Preferences::save_slot_radio(tx, ui, save_slot, cfg, ShowShortcut::No)\n                });\n\n            ui.with_layout(Layout::left_to_right(Align::Min), |ui| {\n                ui.strong(\"Four Player:\")\n                    .on_hover_cursor(CursorIcon::Help)\n                    .on_hover_text(\n                    \"Some game titles support up to 4 players (requires connected controllers).\",\n                );\n            });\n            ui.vertical(|ui| Preferences::four_player_radio(tx, ui, four_player));\n            ui.end_row();\n\n            ui.with_layout(Layout::left_to_right(Align::Min), |ui| {\n                ui.strong(\"NES Region:\")\n                    .on_hover_cursor(CursorIcon::Help)\n                    .on_hover_text(\"Which regional NES hardware to emulate.\");\n            });\n            ui.vertical(|ui| Preferences::nes_region_radio(tx, ui, region));\n\n            ui.with_layout(Layout::left_to_right(Align::Min), |ui| {\n                ui.strong(\"RAM State:\")\n                    .on_hover_cursor(CursorIcon::Help)\n                    .on_hover_text(\"What values are read from NES RAM on load.\");\n            });\n            ui.vertical(|ui| Preferences::ram_state_radio(tx, ui, ram_state));\n            ui.end_row();\n        });\n\n        let grid = Grid::new(\"genie_codes\").num_columns(2).spacing([40.0, 6.0]);\n        grid.show(ui, |ui| {\n            self.genie_codes_entry(ui, cfg);\n            Preferences::genie_codes_list(&self.tx, ui, cfg, false);\n        });\n    }\n\n    fn audio_tab(tx: &NesEventProxy, ui: &mut Ui, cfg: &Config) {\n        let AudioConfig {\n            latency,\n            mut buffer_size,\n            mut enabled,\n        } = cfg.audio;\n        let DeckConfig {\n            channels_enabled, ..\n        } = cfg.deck;\n\n        let res = ui.checkbox(&mut enabled, \"Enable Audio\");\n        if res.clicked() {\n            tx.event(ConfigEvent::AudioEnabled(enabled));\n        }\n\n        ui.add_enabled_ui(cfg.audio.enabled, |ui| {\n            ui.indent(\"apu_channels\", |ui| {\n                Grid::new(\"apu_channels\")\n                    .spacing([60.0, 6.0])\n                    .num_columns(2)\n                    .show(ui, |ui| {\n\n                        let mut pulse1_enabled = channels_enabled[0];\n                        if ui.checkbox(&mut pulse1_enabled, \"Enable Pulse1\").clicked() {\n                            tx.event(ConfigEvent::ApuChannelEnabled((Channel::Pulse1, pulse1_enabled)));\n                        }\n                        let mut noise_enabled = channels_enabled[3];\n                        if ui.checkbox(&mut noise_enabled, \"Enable Noise\").clicked() {\n                            tx.event(ConfigEvent::ApuChannelEnabled((Channel::Noise, noise_enabled)));\n                        }\n                        ui.end_row();\n\n                        let mut pulse1_enabled = channels_enabled[1];\n                        if ui.checkbox(&mut pulse1_enabled, \"Enable Pulse2\").clicked() {\n                            tx.event(ConfigEvent::ApuChannelEnabled((Channel::Pulse2, pulse1_enabled)));\n                        }\n                        let mut dmc_enabled = channels_enabled[4];\n                        if ui.checkbox(&mut dmc_enabled, \"Enable DMC\").clicked() {\n                            tx.event(ConfigEvent::ApuChannelEnabled((Channel::Dmc, dmc_enabled)));\n                        }\n                        ui.end_row();\n\n                        let mut triangle_enabled = channels_enabled[2];\n                        if ui.checkbox(&mut triangle_enabled, \"Enable Triangle\").clicked() {\n                            tx.event(ConfigEvent::ApuChannelEnabled((Channel::Triangle, triangle_enabled)));\n                        }\n                        let mut mapper_enabled = channels_enabled[5];\n                        if ui.checkbox(&mut mapper_enabled, \"Enable Mapper\").clicked() {\n                            tx.event(ConfigEvent::ApuChannelEnabled((Channel::Mapper, mapper_enabled)));\n                        }\n                        ui.end_row();\n                    });\n\n                ui.separator();\n\n                Grid::new(\"audio_settings\")\n                    .spacing([40.0, 6.0])\n                    .num_columns(2)\n                    .show(ui, |ui| {\n                        ui.horizontal(|ui| {\n                            let drag = DragValue::new(&mut buffer_size)\n                                .speed(10)\n                                .range(128..=8192)\n                                .prefix(\"buffer \")\n                                .suffix(\" samples\");\n                            let res = ui.add(drag)\n                                .on_hover_text(\n                                    \"The audio sample buffer size allocated to the sound driver. Increased audio buffer size can help reduce audio underruns.\",\n                                );\n                            if res.changed() {\n                                tx.event(ConfigEvent::AudioBuffer(buffer_size));\n                            }\n                        });\n                        ui.end_row();\n\n                        ui.horizontal(|ui| {\n                            let mut latency = latency.as_millis() as u64;\n                            let drag = DragValue::new(&mut latency)\n                                .range(1..=1000)\n                                .suffix(\" ms latency\");\n                            let res = ui.add(drag)\n                                .on_hover_text(\n                                    \"The amount of queued audio before sending to the sound driver. Increased audio latency can help reduce audio underruns.\",\n                                );\n                            if res.changed() {\n                                tx.event(ConfigEvent::AudioLatency(Duration::from_millis(latency)));\n                            }\n                    });\n                        ui.end_row();\n                    });\n            });\n        });\n    }\n\n    fn video_tab(tx: &NesEventProxy, ui: &mut Ui, cfg: &Config) {\n        let RendererConfig {\n            always_on_top,\n            fullscreen,\n            hide_overscan,\n            scale,\n            shader,\n            show_menubar,\n            show_messages,\n            ..\n        } = cfg.renderer;\n        let DeckConfig { filter, .. } = cfg.deck;\n\n        Grid::new(\"video_checkboxes\")\n            .spacing([80.0, 6.0])\n            .num_columns(2)\n            .show(ui, |ui| {\n                Preferences::menubar_checkbox(tx, ui, show_menubar, None);\n                Preferences::fullscreen_checkbox(tx, ui, fullscreen, None);\n                ui.end_row();\n\n                Preferences::messages_checkbox(tx, ui, show_messages, None);\n                Preferences::embed_viewports_checkbox(tx, ui, cfg, None);\n                ui.end_row();\n\n                Preferences::overscan_checkbox(tx, ui, hide_overscan, None);\n                Preferences::always_on_top_checkbox(tx, ui, always_on_top, None);\n                ui.end_row();\n            });\n\n        ui.separator();\n\n        Grid::new(\"video_preferences\")\n            .num_columns(2)\n            .spacing([40.0, 6.0])\n            .show(ui, |ui| {\n                ui.with_layout(Layout::left_to_right(Align::Min), |ui| {\n                    ui.strong(\"Window Scale:\");\n                });\n                Grid::new(\"save_slots\")\n                    .num_columns(2)\n                    .spacing([20.0, 6.0])\n                    .show(ui, |ui| {\n                        Preferences::window_scale_radio(tx, ui, scale);\n                    });\n                ui.end_row();\n\n                ui.with_layout(Layout::left_to_right(Align::Min), |ui| {\n                    ui.strong(\"Video Filter:\");\n                });\n                ui.vertical(|ui| {\n                    Preferences::video_filter_radio(tx, ui, filter, cfg, ShowShortcut::No);\n                });\n                ui.end_row();\n\n                ui.with_layout(Layout::left_to_right(Align::Min), |ui| {\n                    ui.strong(\"Shader:\");\n                });\n                ui.vertical(|ui| Preferences::shader_radio(tx, ui, shader, cfg, ShowShortcut::No));\n            });\n    }\n\n    fn input_tab(tx: &NesEventProxy, ui: &mut Ui, cfg: &Config) {\n        let DeckConfig {\n            mut concurrent_dpad,\n            zapper,\n            ..\n        } = cfg.deck;\n\n        Grid::new(\"input_checkboxes\")\n            .num_columns(2)\n            .spacing([80.0, 6.0])\n            .show(ui, |ui| {\n                Preferences::zapper_checkbox(tx, ui, zapper, None);\n                ui.end_row();\n\n                let res = ui.checkbox(&mut concurrent_dpad, \"Enable Concurrent D-Pad\");\n                if res.clicked() {\n                    tx.event(ConfigEvent::ConcurrentDpad(concurrent_dpad));\n                }\n            });\n    }\n\n    pub fn genie_codes_entry(&mut self, ui: &mut Ui, cfg: &Config) {\n        let tx = &self.tx;\n        ui.vertical(|ui| {\n            // desired_width below doesn't have the desired effect\n            ui.allocate_space(Vec2::new(200.0, 0.0));\n\n            let genie_label = ui.strong(\"Add Genie Code(s):\")\n                .on_hover_cursor(CursorIcon::Help)\n                .on_hover_text(\n                    \"A Game Genie Code is a 6 or 8 letter string that temporarily modifies game memory during operation. e.g. `AATOZE` will start Super Mario Bros. with 9 lives.\\n\\nYou can enter one code per line.\"\n                );\n\n            let text_edit = TextEdit::multiline(&mut self.genie_entry.code)\n                .hint_text(\"e.g. AATOZE\")\n                .desired_width(200.0);\n            let entry_res = ui.add(text_edit)\n                .labelled_by(genie_label.id);\n            if entry_res.changed() {\n                self.genie_entry.error = None;\n            }\n\n            let has_entry = !self.genie_entry.code.is_empty();\n            let add_clicked = ui.horizontal(|ui| {\n                ui.add_enabled_ui(has_entry, |ui| {\n                    let add_clicked = ui.button(\"Add\").clicked();\n                    if ui.button(\"Clear\").clicked() {\n                        self.genie_entry.code.clear();\n                        self.genie_entry.error = None;\n                    }\n                    add_clicked\n                }).inner\n            }).inner;\n\n            if (has_entry && entry_res.lost_focus() && ui.input(|i| i.key_pressed(Key::Enter)))\n                || add_clicked\n            {\n                for code in self.genie_entry.code.lines() {\n                    let code = code.trim();\n                    if code.is_empty() {\n                        continue;\n                    }\n                    match GenieCode::parse(code) {\n                        Ok(hex) => {\n                            let code = GenieCode::from_raw(code.to_string(), &hex);\n                            if !cfg.deck.genie_codes.contains(&code) {\n                                tx.event(ConfigEvent::GenieCodeAdded(code));\n                            }\n                        }\n                        Err(err) => self.genie_entry.error = Some(err.to_string()),\n                    }\n                }\n                if self.genie_entry.error.is_none() {\n                    self.genie_entry.code.clear();\n                }\n            }\n\n            if let Some(error) = &self.genie_entry.error {\n                ui.colored_label(ui.visuals().error_fg_color, error);\n            }\n        });\n    }\n\n    fn restore_defaults(tx: &NesEventProxy, ctx: &Context) {\n        ctx.memory_mut(|mem| *mem = Default::default());\n\n        // Inform all cfg updates\n        let Config {\n            deck,\n            emulation,\n            audio,\n            renderer,\n            input,\n        } = Config::default();\n\n        let events = [\n            ConfigEvent::ActionBindings(input.action_bindings),\n            ConfigEvent::AlwaysOnTop(renderer.always_on_top),\n            ConfigEvent::ApuChannelsEnabled(deck.channels_enabled),\n            ConfigEvent::AudioBuffer(audio.buffer_size),\n            ConfigEvent::AudioEnabled(audio.enabled),\n            ConfigEvent::AudioLatency(audio.latency),\n            ConfigEvent::AutoLoad(emulation.auto_load),\n            ConfigEvent::AutoSave(emulation.auto_save),\n            ConfigEvent::AutoSaveInterval(emulation.auto_save_interval),\n            ConfigEvent::ConcurrentDpad(deck.concurrent_dpad),\n            ConfigEvent::DarkTheme(renderer.dark_theme),\n            ConfigEvent::EmbedViewports(renderer.embed_viewports),\n            ConfigEvent::FourPlayer(deck.four_player),\n            ConfigEvent::Fullscreen(renderer.fullscreen),\n            ConfigEvent::GamepadAssignments(input.gamepad_assignments),\n            ConfigEvent::GenieCodeClear,\n            ConfigEvent::HideOverscan(renderer.hide_overscan),\n            ConfigEvent::MapperRevisions(deck.mapper_revisions),\n            ConfigEvent::RamState(deck.ram_state),\n            // Clearing recent roms is handled in a separate button\n            ConfigEvent::Region(deck.region),\n            ConfigEvent::RewindEnabled(emulation.rewind),\n            ConfigEvent::RewindInterval(emulation.rewind_interval),\n            ConfigEvent::RewindSeconds(emulation.rewind_seconds),\n            ConfigEvent::RunAhead(emulation.run_ahead),\n            ConfigEvent::SaveSlot(emulation.save_slot),\n            ConfigEvent::Shader(renderer.shader),\n            ConfigEvent::ShowMenubar(renderer.show_menubar),\n            ConfigEvent::ShowMessages(renderer.show_messages),\n            ConfigEvent::Speed(emulation.speed),\n            ConfigEvent::VideoFilter(deck.filter),\n            ConfigEvent::ZapperConnected(deck.zapper),\n        ];\n\n        for event in events {\n            tx.event(event);\n        }\n    }\n\n    pub(crate) fn clear_save_states(tx: &NesEventProxy) {\n        let data_dir = Config::default_data_dir();\n        match fs::clear_dir(data_dir) {\n            Ok(_) => tx.event(UiEvent::Message((\n                MessageType::Info,\n                \"Save States cleared.\".to_string(),\n            ))),\n            Err(_) => tx.event(UiEvent::Message((\n                MessageType::Error,\n                \"Failed to clear Save States.\".to_string(),\n            ))),\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/renderer/gui.rs",
    "content": "use crate::{\n    feature,\n    nes::{\n        RunState,\n        action::{Debug, DebugKind, DebugStep, Feature, Setting, Ui as UiAction},\n        config::{Config, RecentRom, RendererConfig},\n        emulation::FrameStats,\n        event::{\n            ConfigEvent, DebugEvent, EmulationEvent, NesEvent, NesEventProxy, RendererEvent,\n            Response, UiEvent,\n        },\n        input::Gamepads,\n        renderer::{\n            gui::{\n                keybinds::Keybinds,\n                lib::{\n                    ShortcutText, ShowShortcut, ToggleValue, ViewportOptions, cursor_to_zapper,\n                    input_down,\n                },\n                ppu_viewer::PpuViewer,\n                preferences::Preferences,\n            },\n            painter::RenderState,\n            texture::Texture,\n        },\n        rom::{HOMEBREW_ROMS, RomAsset},\n        version::Version,\n    },\n    sys::{SystemInfo, info::System},\n};\nuse egui::{\n    Align, Button, CentralPanel, Color32, Context, CornerRadius, CursorIcon, Direction, FontData,\n    FontDefinitions, FontFamily, Frame, Grid, Image, Layout, Panel, Pos2, Rect, RichText,\n    ScrollArea, Sense, Stroke, Ui, UiBuilder, ViewportClass, ViewportId, Visuals, hex_color,\n    include_image,\n    style::{HandleShape, Selection, TextCursorStyle, WidgetVisuals},\n};\nuse serde::{Deserialize, Serialize};\nuse std::sync::{\n    Arc,\n    atomic::{AtomicBool, Ordering},\n};\nuse tetanes_core::{\n    action::Action as DeckAction,\n    common::{NesRegion, ResetKind},\n    control_deck::LoadedRom,\n    cpu::instr::InstrRef,\n    ppu,\n    time::{Duration, Instant},\n};\nuse tracing::{error, info, warn};\nuse winit::event::WindowEvent;\n\nmod keybinds;\npub mod lib;\nmod ppu_viewer;\nmod preferences;\n\nconst UI_SETTINGS_TITLE: &str = \"🔧 UI Settings\";\n#[cfg(debug_assertions)]\nconst UI_INSPECTION_TITLE: &str = \"🔍 UI Inspection\";\n#[cfg(debug_assertions)]\nconst UI_MEMORY_TITLE: &str = \"📝 UI Memory\";\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]\npub enum Menu {\n    About,\n    Keybinds,\n    PerfStats,\n    PpuViewer,\n    Preferences,\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\npub enum MessageType {\n    Info,\n    Warn,\n    Error,\n}\n\n#[derive(Debug)]\n#[must_use]\npub struct Gui {\n    ctx: Context,\n    initialized: bool,\n    title: String,\n    tx: NesEventProxy,\n    pub nes_texture: Texture,\n    corrupted_cpu_instr: Option<InstrRef>,\n    pub run_state: RunState,\n    pub menu_height: f32,\n    nes_frame: Rect,\n    about_open: bool,\n    ui_settings_id: ViewportId,\n    ui_settings_open: Arc<AtomicBool>,\n    #[cfg(debug_assertions)]\n    ui_inspection_id: ViewportId,\n    #[cfg(debug_assertions)]\n    ui_inspection_open: Arc<AtomicBool>,\n    #[cfg(debug_assertions)]\n    ui_memory_id: ViewportId,\n    #[cfg(debug_assertions)]\n    ui_memory_open: Arc<AtomicBool>,\n    perf_stats_open: bool,\n    update_window_open: bool,\n    version: Version,\n    pub keybinds: Keybinds,\n    preferences: Preferences,\n    debugger_open: bool,\n    ppu_viewer: PpuViewer,\n    apu_mixer_open: bool,\n    viewport_info_open: bool,\n    replay_recording: bool,\n    audio_recording: bool,\n    frame_stats: FrameStats,\n    messages: Vec<(MessageType, String, Instant)>,\n    pub loaded_rom: Option<LoadedRom>,\n    about_homebrew_rom_open: Option<RomAsset>,\n    start: Instant,\n    sys: System,\n    pub error: Option<String>,\n    enable_auto_update: bool,\n    dont_show_updates: bool,\n}\n\nimpl Gui {\n    const MSG_TIMEOUT: Duration = Duration::from_secs(3);\n    const MAX_MESSAGES: usize = 5;\n    const NO_ROM_LOADED: &'static str = \"No ROM is loaded.\";\n\n    /// Create a `Gui` instance.\n    pub fn new(\n        ctx: Context,\n        tx: NesEventProxy,\n        render_state: &mut RenderState,\n        cfg: &Config,\n    ) -> Self {\n        let nes_texture = Texture::new(\n            render_state,\n            cfg.texture_size(),\n            cfg.deck.region.aspect_ratio(),\n            Some(\"nes frame\"),\n        );\n\n        Self {\n            ctx,\n            initialized: false,\n            title: Config::WINDOW_TITLE.to_string(),\n            tx: tx.clone(),\n            nes_texture,\n            corrupted_cpu_instr: None,\n            run_state: RunState::Running,\n            menu_height: 0.0,\n            nes_frame: Rect::ZERO,\n            about_open: false,\n            ui_settings_id: egui::ViewportId::from_hash_of(UI_SETTINGS_TITLE),\n            ui_settings_open: Arc::new(AtomicBool::new(false)),\n            #[cfg(debug_assertions)]\n            ui_inspection_id: egui::ViewportId::from_hash_of(UI_INSPECTION_TITLE),\n            #[cfg(debug_assertions)]\n            ui_inspection_open: Arc::new(AtomicBool::new(false)),\n            #[cfg(debug_assertions)]\n            ui_memory_id: egui::ViewportId::from_hash_of(UI_MEMORY_TITLE),\n            #[cfg(debug_assertions)]\n            ui_memory_open: Arc::new(AtomicBool::new(false)),\n            perf_stats_open: false,\n            update_window_open: false,\n            version: Version::new(),\n            keybinds: Keybinds::new(tx.clone()),\n            preferences: Preferences::new(tx.clone()),\n            debugger_open: false,\n            ppu_viewer: PpuViewer::new(tx, render_state),\n            apu_mixer_open: false,\n            viewport_info_open: false,\n            replay_recording: false,\n            audio_recording: false,\n            frame_stats: FrameStats::new(),\n            messages: Vec::new(),\n            loaded_rom: None,\n            about_homebrew_rom_open: None,\n            start: Instant::now(),\n            sys: System::default(),\n            error: None,\n            enable_auto_update: false,\n            dont_show_updates: false,\n        }\n    }\n\n    pub fn on_window_event(&mut self, event: &WindowEvent) -> Response {\n        match event {\n            WindowEvent::KeyboardInput { .. } | WindowEvent::MouseInput { .. }\n                if self.keybinds.wants_input() =>\n            {\n                Response {\n                    consumed: true,\n                    ..Default::default()\n                }\n            }\n            _ => Response::default(),\n        }\n    }\n\n    pub fn on_event(&mut self, queue: &wgpu::Queue, event: &mut NesEvent) {\n        match event {\n            NesEvent::Ui(UiEvent::UpdateAvailable(version)) => {\n                self.version.set_latest(version.clone());\n                self.update_window_open = true;\n                self.ctx.request_repaint();\n            }\n            NesEvent::Emulation(event) => match event {\n                EmulationEvent::ReplayRecord(recording) => {\n                    self.replay_recording = *recording;\n                }\n                EmulationEvent::AudioRecord(recording) => {\n                    self.audio_recording = *recording;\n                }\n                EmulationEvent::CpuCorrupted { instr } => {\n                    self.corrupted_cpu_instr = Some(*instr);\n                    self.ctx.request_repaint();\n                }\n                EmulationEvent::RunState(mode) => {\n                    self.run_state = *mode;\n                }\n                _ => (),\n            },\n            NesEvent::Renderer(event) => match event {\n                RendererEvent::FrameStats(stats) => {\n                    self.frame_stats = *stats;\n                }\n                // Toggling true is handled in the menu widget\n                RendererEvent::ShowMenubar(show) if !*show => {\n                    self.menu_height = 0.0;\n                }\n                RendererEvent::ReplayLoaded => {\n                    self.run_state = RunState::Running;\n                    self.tx.event(EmulationEvent::RunState(self.run_state));\n                }\n                RendererEvent::RomUnloaded => {\n                    self.run_state = RunState::Running;\n                    self.tx.event(EmulationEvent::RunState(self.run_state));\n                    self.loaded_rom = None;\n                    self.title = Config::WINDOW_TITLE.to_string();\n                }\n                RendererEvent::RomLoaded(rom) => {\n                    self.run_state = RunState::Running;\n                    self.tx.event(EmulationEvent::RunState(self.run_state));\n                    self.title = format!(\"{} :: {}\", Config::WINDOW_TITLE, rom.name);\n                    self.loaded_rom = Some(rom.clone());\n                }\n                RendererEvent::Menu(menu) => match menu {\n                    Menu::About => self.about_open = !self.about_open,\n                    Menu::Keybinds => self.keybinds.toggle_open(&self.ctx),\n                    Menu::PerfStats => {\n                        self.perf_stats_open = !self.perf_stats_open;\n                        self.tx\n                            .event(EmulationEvent::ShowFrameStats(self.perf_stats_open));\n                    }\n                    Menu::PpuViewer => self.ppu_viewer.toggle_open(&self.ctx),\n                    Menu::Preferences => self.preferences.toggle_open(&self.ctx),\n                },\n                _ => (),\n            },\n            NesEvent::Debug(DebugEvent::Ppu(ppu)) => {\n                self.ppu_viewer.update_ppu(queue, std::mem::take(ppu));\n                self.ctx.request_repaint_of(self.ppu_viewer.id());\n            }\n            _ => (),\n        }\n    }\n\n    pub fn add_message<S>(&mut self, ty: MessageType, text: S)\n    where\n        S: Into<String>,\n    {\n        let text = text.into();\n        match ty {\n            MessageType::Info => info!(\"{text}\"),\n            MessageType::Warn => warn!(\"{text}\"),\n            MessageType::Error => error!(\"{text}\"),\n        }\n        self.messages\n            .push((ty, text, Instant::now() + Self::MSG_TIMEOUT));\n    }\n\n    pub fn loaded_region(&self) -> Option<NesRegion> {\n        self.loaded_rom.as_ref().map(|rom| rom.region)\n    }\n\n    pub fn aspect_ratio(&self, cfg: &Config) -> f32 {\n        let region = cfg\n            .deck\n            .region\n            .is_auto()\n            .then(|| self.loaded_region())\n            .flatten()\n            .unwrap_or(cfg.deck.region);\n        region.aspect_ratio()\n    }\n\n    /// Create the UI.\n    pub fn ui(&mut self, ui: &mut Ui, cfg: &Config, gamepads: &Gamepads) {\n        if !self.initialized {\n            self.initialize(ui, cfg);\n        }\n\n        if cfg.renderer.show_menubar {\n            Panel::top(\"menubar\").show_inside(ui, |ui| self.menubar(ui, cfg));\n        }\n\n        let viewport_opts = ViewportOptions {\n            enabled: !self.keybinds.wants_input(),\n            always_on_top: cfg.renderer.always_on_top,\n        };\n\n        CentralPanel::default()\n            .frame(Frame::canvas(ui.style()))\n            .show_inside(ui, |ui| {\n                self.nes_frame(ui, viewport_opts.enabled, cfg, gamepads);\n            });\n\n        self.preferences.show(ui, viewport_opts, cfg.clone());\n        self.keybinds.show(ui, viewport_opts, cfg.clone(), gamepads);\n        self.ppu_viewer.show(ui, viewport_opts);\n\n        self.show_about_window(ui, viewport_opts.enabled);\n        self.show_about_homebrew_window(ui, viewport_opts.enabled);\n\n        self.show_performance_window(ui, viewport_opts.enabled, cfg);\n        self.show_update_window(ui, viewport_opts.enabled, cfg);\n\n        Self::show_viewport(\n            UI_SETTINGS_TITLE,\n            ui,\n            viewport_opts,\n            &self.ui_settings_open,\n            |ctx, ui| {\n                ScrollArea::both().show(ui, |ui| ctx.settings_ui(ui));\n            },\n        );\n\n        #[cfg(debug_assertions)]\n        {\n            Self::show_viewport(\n                UI_INSPECTION_TITLE,\n                ui,\n                viewport_opts,\n                &self.ui_inspection_open,\n                |ctx, ui| {\n                    ScrollArea::both().show(ui, |ui| ctx.inspection_ui(ui));\n                },\n            );\n            Self::show_viewport(\n                UI_MEMORY_TITLE,\n                ui,\n                viewport_opts,\n                &self.ui_memory_open,\n                |ctx, ui| {\n                    ScrollArea::both().show(ui, |ui| ctx.memory_ui(ui));\n                },\n            );\n        }\n    }\n\n    fn initialize(&mut self, ui: &mut Ui, cfg: &Config) {\n        let theme = if cfg.renderer.dark_theme {\n            Self::dark_theme()\n        } else {\n            Self::light_theme()\n        };\n        ui.set_visuals(theme);\n        {\n            let style = ui.style_mut();\n            style.spacing.scroll.floating = false;\n            style.spacing.scroll.foreground_color = false;\n            style.spacing.scroll.bar_width = 8.0;\n        }\n\n        const FONT: (&str, &[u8]) = (\n            \"pixeloid-sans\",\n            include_bytes!(\"../../../assets/pixeloid-sans.ttf\"),\n        );\n        const BOLD_FONT: (&str, &[u8]) = (\n            \"pixeloid-sans-bold\",\n            include_bytes!(\"../../../assets/pixeloid-sans-bold.ttf\"),\n        );\n        const MONO_FONT: (&str, &[u8]) = (\n            \"pixeloid-mono\",\n            include_bytes!(\"../../../assets/pixeloid-mono.ttf\"),\n        );\n\n        egui_extras::install_image_loaders(ui);\n\n        let mut fonts = FontDefinitions::default();\n        for (name, data) in [FONT, BOLD_FONT, MONO_FONT] {\n            let font_data = FontData::from_static(data);\n            fonts.font_data.insert(name.to_string(), font_data.into());\n        }\n\n        match fonts.families.get_mut(&FontFamily::Proportional) {\n            Some(font) => font.insert(0, FONT.0.to_string()),\n            None => tracing::warn!(\"failed to set proportional font\"),\n        }\n        match fonts.families.get_mut(&FontFamily::Monospace) {\n            Some(font) => font.insert(0, MONO_FONT.0.to_string()),\n            None => tracing::warn!(\"failed to set monospace font\"),\n        }\n        ui.set_fonts(fonts);\n\n        // Check for update on start\n        if self.version.requires_updates() {\n            let notify_latest = false;\n            self.version.check_for_updates(&self.tx, notify_latest);\n        }\n\n        self.initialized = true;\n    }\n\n    fn show_about_window(&mut self, ctx: &Context, enabled: bool) {\n        let mut about_open = self.about_open;\n        egui::Window::new(\"ℹ About TetaNES\")\n            .open(&mut about_open)\n            .show(ctx, |ui| self.about(ui, enabled));\n        self.about_open = about_open;\n    }\n\n    fn show_about_homebrew_window(&mut self, ctx: &Context, enabled: bool) {\n        let Some(rom) = self.about_homebrew_rom_open else {\n            return;\n        };\n\n        let mut about_homebrew_open = true;\n        egui::Window::new(format!(\"ℹ About {}\", rom.name))\n            .open(&mut about_homebrew_open)\n            .show(ctx, |ui| {\n                ui.add_enabled_ui(enabled, |ui| {\n                    ScrollArea::vertical().show(ui, |ui| {\n                        ui.strong(\"Author(s):\");\n                        ui.label(rom.authors);\n                        ui.add_space(12.0);\n\n                        ui.strong(\"Description:\");\n                        ui.label(rom.description);\n                        ui.add_space(12.0);\n\n                        ui.strong(\"Source:\");\n                        ui.hyperlink(rom.source);\n                    });\n                });\n            });\n        if !about_homebrew_open {\n            self.about_homebrew_rom_open = None;\n        }\n    }\n\n    pub(super) fn show_viewport_info_window(\n        &mut self,\n        ctx: &Context,\n        id: egui::ViewportId,\n        info: &egui::ViewportInfo,\n    ) {\n        egui::Window::new(format!(\"ℹ Viewport Info ({id:?})\"))\n            .open(&mut self.viewport_info_open)\n            .show(ctx, |ui| info.ui(ui));\n    }\n\n    fn show_performance_window(&mut self, ctx: &Context, enabled: bool, cfg: &Config) {\n        let mut perf_stats_open = self.perf_stats_open;\n        egui::Window::new(\"🛠 Performance Stats\")\n            .open(&mut perf_stats_open)\n            .show(ctx, |ui| {\n                ui.add_enabled_ui(enabled, |ui| self.performance_stats(ui, cfg));\n            });\n        self.perf_stats_open = perf_stats_open;\n    }\n\n    pub(super) fn close_viewport(&self, viewport_id: ViewportId) {\n        match viewport_id {\n            id if id == self.keybinds.id => self.keybinds.set_open(false, &self.ctx),\n            id if id == self.ppu_viewer.id => self.ppu_viewer.set_open(false, &self.ctx),\n            id if id == self.preferences.id => self.preferences.set_open(false, &self.ctx),\n            id if id == self.ui_settings_id => {\n                self.ui_settings_open.store(false, Ordering::Release);\n                self.ctx\n                    .send_viewport_cmd_to(self.ui_settings_id, egui::ViewportCommand::Close);\n            }\n            #[cfg(debug_assertions)]\n            id if id == self.ui_inspection_id => {\n                self.ui_inspection_open.store(false, Ordering::Release);\n                self.ctx\n                    .send_viewport_cmd_to(self.ui_inspection_id, egui::ViewportCommand::Close);\n            }\n            #[cfg(debug_assertions)]\n            id if id == self.ui_memory_id => {\n                self.ui_memory_open.store(false, Ordering::Release);\n                self.ctx\n                    .send_viewport_cmd_to(self.ui_memory_id, egui::ViewportCommand::Close);\n            }\n            _ => (),\n        }\n    }\n\n    fn show_viewport(\n        title: impl Into<String>,\n        ui: &mut Ui,\n        opts: ViewportOptions,\n        open: &Arc<AtomicBool>,\n        add_contents: impl Fn(&Context, &mut Ui) + Send + Sync + 'static,\n    ) {\n        if !open.load(Ordering::Acquire) {\n            return;\n        }\n\n        let title = title.into();\n        let viewport_id = egui::ViewportId::from_hash_of(&title);\n        let mut viewport_builder = egui::ViewportBuilder::default().with_title(&title);\n        if opts.always_on_top {\n            viewport_builder = viewport_builder.with_always_on_top();\n        }\n\n        let open = Arc::clone(open);\n        let ctx = ui.ctx().clone();\n        ui.show_viewport_deferred(viewport_id, viewport_builder, move |ui, class| {\n            if class == ViewportClass::EmbeddedWindow {\n                let mut window_open = open.load(Ordering::Acquire);\n                egui::Window::new(&title)\n                    .open(&mut window_open)\n                    .vscroll(true)\n                    .show(ui, |ui| {\n                        ui.add_enabled_ui(opts.enabled, |ui| add_contents(&ctx, ui));\n                    });\n                open.store(window_open, Ordering::Release);\n            } else {\n                CentralPanel::default().show_inside(ui, |ui| {\n                    ui.add_enabled_ui(opts.enabled, |ui| add_contents(&ctx, ui));\n                });\n                if ui.input(|i| i.viewport().close_requested()) {\n                    open.store(false, Ordering::Release);\n                }\n            }\n        });\n    }\n\n    fn show_update_window(&mut self, ctx: &Context, enabled: bool, cfg: &Config) {\n        let mut update_window_open = self.update_window_open && cfg.renderer.show_updates;\n        let mut close_window = false;\n        egui::Window::new(\"🌐 Update Available\")\n            .open(&mut update_window_open)\n            .resizable(false)\n            .show(ctx, |ui| {\n                ui.add_enabled_ui(enabled, |ui| {\n                    ui.label(format!(\n                        \"An update is available for TetaNES! (v{})\",\n                        self.version.latest(),\n                    ));\n                    ui.hyperlink(\"https://github.com/lukexor/tetanes/releases\");\n\n                    ui.add_space(15.0);\n\n                    // TODO: Add auto-update for each platform\n                    if self.enable_auto_update {\n                        ui.label(\"Would you like to install it and restart?\");\n                        ui.add_space(15.0);\n\n                        ui.checkbox(&mut self.dont_show_updates, \"Don't show this again\");\n                        ui.add_space(15.0);\n\n                        ui.with_layout(Layout::right_to_left(Align::Min), |ui| {\n                            let res = ui.button(\"Skip\").on_hover_text(format!(\n                                \"Keep the current version of TetaNES (v{}).\",\n                                self.version.current()\n                            ));\n                            if res.clicked() {\n                                close_window = true;\n                            }\n\n                            let res = ui.button(\"Continue\").on_hover_text(format!(\n                                \"Install the latest version (v{}) restart TetaNES.\",\n                                self.version.current()\n                            ));\n                            if res.clicked()\n                                && let Err(err) = self.version.install_update_and_restart()\n                            {\n                                self.add_message(\n                                    MessageType::Error,\n                                    format!(\"Failed to install update: {err}\"),\n                                );\n                                close_window = true;\n                            }\n                        });\n                    } else {\n                        ui.label(\"Click the above link to download the update for your system.\");\n                        ui.add_space(15.0);\n\n                        ui.checkbox(&mut self.dont_show_updates, \"Don't show this again\");\n                        ui.add_space(15.0);\n\n                        ui.with_layout(Layout::right_to_left(Align::Min), |ui| {\n                            if ui.button(\"  OK  \").clicked() {\n                                close_window = true;\n                            }\n                        });\n                    }\n                });\n            });\n        if close_window\n            || update_window_open != self.update_window_open && cfg.renderer.show_updates\n        {\n            self.update_window_open = false;\n            if self.dont_show_updates == cfg.renderer.show_updates {\n                self.tx\n                    .event(ConfigEvent::ShowUpdates(!self.dont_show_updates));\n                self.dont_show_updates = false;\n            }\n        }\n    }\n\n    fn menubar(&mut self, ui: &mut Ui, cfg: &Config) {\n        ui.add_enabled_ui(!self.keybinds.wants_input(), |ui| {\n            let inner_res = egui::MenuBar::new().ui(ui, |ui| {\n                ui.horizontal_wrapped(|ui| {\n                    Self::toggle_dark_mode_button(&self.tx, ui);\n                    ui.separator();\n\n                    ui.menu_button(\"📁 File\", |ui| self.file_menu(ui, cfg));\n                    ui.menu_button(\"🔨 Controls\", |ui| self.controls_menu(ui, cfg));\n                    ui.menu_button(\"🔧 Config\", |ui| self.config_menu(ui, cfg));\n                    // icon: screen\n                    ui.menu_button(\"🖵 Window\", |ui| self.window_menu(ui, cfg));\n                    ui.menu_button(\"🕷 Debug\", |ui| self.debug_menu(ui, cfg));\n                    ui.menu_button(\"❓ Help\", |ui| self.help_menu(ui));\n\n                    if cfg!(debug_assertions) {\n                        ui.separator();\n                        ui.label(\n                            RichText::new(\"⚠ Debug build ⚠\")\n                                .small()\n                                .color(ui.visuals().warn_fg_color),\n                        )\n                        .on_hover_text(\"TetaNES was compiled with debug assertions enabled.\");\n                    }\n                });\n            });\n            let spacing = ui.style().spacing.item_spacing;\n            let border = 1.0;\n            let height = inner_res.response.rect.height() + spacing.y + border;\n            if height != self.menu_height {\n                self.menu_height = height;\n                self.tx.event(RendererEvent::ResizeTexture);\n            }\n        });\n    }\n\n    pub fn toggle_dark_mode_button(tx: &NesEventProxy, ui: &mut Ui) {\n        if ui.style().visuals.dark_mode {\n            let button = Button::new(\"☀\").frame(false);\n            let res = ui.add(button).on_hover_text(\"Switch to light mode\");\n            if res.clicked() {\n                ui.ctx().set_visuals(Self::light_theme());\n                tx.event(ConfigEvent::DarkTheme(false));\n            }\n        } else {\n            let button = Button::new(\"🌙\").frame(false);\n            let res = ui.add(button).on_hover_text(\"Switch to dark mode\");\n            if res.clicked() {\n                ui.ctx().set_visuals(Self::dark_theme());\n                tx.event(ConfigEvent::DarkTheme(true));\n            }\n        }\n    }\n\n    fn file_menu(&mut self, ui: &mut Ui, cfg: &Config) {\n        let button = Button::new(\"📂 Load ROM...\").shortcut_text(cfg.shortcut(UiAction::LoadRom));\n        if ui.add(button).clicked() {\n            if self.loaded_rom.is_some() {\n                self.run_state = RunState::AutoPaused;\n                self.tx.event(EmulationEvent::RunState(self.run_state));\n            }\n            // NOTE: Due to some platforms file dialogs blocking the event loop,\n            // loading requires a round-trip in order for the above pause to\n            // get processed.\n            self.tx.event(UiEvent::LoadRomDialog);\n        }\n\n        ui.menu_button(\"🍺 Homebrew ROM...\", |ui| self.homebrew_rom_menu(ui));\n\n        let tx = &self.tx;\n\n        ui.add_enabled_ui(self.loaded_rom.is_some(), |ui| {\n            let button =\n                Button::new(\"⏹ Unload ROM...\").shortcut_text(cfg.shortcut(UiAction::UnloadRom));\n            let res = ui.add(button).on_disabled_hover_text(Self::NO_ROM_LOADED);\n            if res.clicked() {\n                tx.event(EmulationEvent::UnloadRom);\n            }\n\n            let button =\n                Button::new(\"🎞 Load Replay\").shortcut_text(cfg.shortcut(UiAction::LoadReplay));\n            let res = ui\n                .add(button)\n                .on_hover_text(\"Load a replay file for the currently loaded ROM.\")\n                .on_disabled_hover_text(Self::NO_ROM_LOADED);\n            if res.clicked() {\n                self.run_state = RunState::AutoPaused;\n                tx.event(EmulationEvent::RunState(self.run_state));\n                // NOTE: Due to some platforms file dialogs blocking the event loop,\n                // loading requires a round-trip in order for the above pause to\n                // get processed.\n                tx.event(UiEvent::LoadReplayDialog);\n            }\n        });\n\n        if feature!(Filesystem) {\n            ui.menu_button(\"🗄 Recently Played...\", |ui| {\n                // Sizing pass here since the width of the submenu can change as recent ROMS are\n                // added or cleared.\n                ui.scope_builder(UiBuilder::new().sizing_pass(), |ui| {\n                    if cfg.renderer.recent_roms.is_empty() {\n                        ui.label(\"No recent ROMs\");\n                    } else {\n                        for rom in &cfg.renderer.recent_roms {\n                            ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);\n                            if ui.button(rom.name()).clicked() {\n                                match rom {\n                                    RecentRom::Homebrew { name } => {\n                                        match HOMEBREW_ROMS.iter().find(|rom| rom.name == name) {\n                                            Some(rom) => {\n                                                tx.event(EmulationEvent::LoadRom((\n                                                    rom.name.to_string(),\n                                                    rom.data(),\n                                                )));\n                                            }\n                                            None => {\n                                                tx.event(UiEvent::Message((\n                                                    MessageType::Error,\n                                                    \"Failed to load rom\".into(),\n                                                )));\n                                            }\n                                        }\n                                    }\n                                    RecentRom::Path(path) => {\n                                        tx.event(EmulationEvent::LoadRomPath(path.to_path_buf()))\n                                    }\n                                }\n                            }\n                        }\n                    }\n                });\n            });\n\n            ui.separator();\n        }\n\n        if feature!(Storage) {\n            ui.add_enabled_ui(self.loaded_rom.is_some(), |ui| {\n                let button =\n                    Button::new(\"💾 Save State\").shortcut_text(cfg.shortcut(DeckAction::SaveState));\n                let res = ui\n                    .add(button)\n                    .on_hover_text(\"Save the current state to the selected save slot.\")\n                    .on_disabled_hover_text(Self::NO_ROM_LOADED);\n                if res.clicked() {\n                    tx.event(EmulationEvent::SaveState(cfg.emulation.save_slot));\n                };\n\n                let button =\n                    Button::new(\"⎗ Load State\").shortcut_text(cfg.shortcut(DeckAction::LoadState));\n                let res = ui\n                    .add(button)\n                    .on_hover_text(\"Load a previous state from the selected save slot.\")\n                    .on_disabled_hover_text(Self::NO_ROM_LOADED);\n                if res.clicked() {\n                    tx.event(EmulationEvent::LoadState(cfg.emulation.save_slot));\n                }\n            });\n\n            // icon: # in a square\n            ui.menu_button(\"󾠬 Save Slot...\", |ui| {\n                Preferences::save_slot_radio(\n                    tx,\n                    ui,\n                    cfg.emulation.save_slot,\n                    cfg,\n                    ShowShortcut::Yes,\n                );\n            });\n        }\n\n        if feature!(OsViewports) {\n            ui.separator();\n\n            let button = Button::new(\"⎆ Quit\").shortcut_text(cfg.shortcut(UiAction::Quit));\n            if ui.add(button).clicked() {\n                tx.event(UiEvent::Terminate);\n            };\n        }\n    }\n\n    fn homebrew_rom_menu(&mut self, ui: &mut Ui) {\n        ScrollArea::vertical().show(ui, |ui| {\n            for rom in HOMEBREW_ROMS {\n                ui.horizontal(|ui| {\n                    if ui.button(rom.name).clicked() {\n                        self.tx\n                            .event(EmulationEvent::LoadRom((rom.name.to_string(), rom.data())));\n                    }\n                    let res = ui.button(\"ℹ\").on_hover_ui(|ui| {\n                        ui.set_max_width(400.0);\n                        Self::about_homebrew(ui, rom);\n                    });\n                    if res.clicked() {\n                        self.about_homebrew_rom_open = Some(rom);\n                    }\n                });\n            }\n        });\n    }\n\n    fn controls_menu(&mut self, ui: &mut Ui, cfg: &Config) {\n        let tx = &self.tx;\n\n        ui.add_enabled_ui(self.loaded_rom.is_some(), |ui| {\n            let button = Button::new(if self.run_state.paused() {\n                \"▶ Resume\"\n            } else {\n                \"⏸ Pause\"\n            })\n            .shortcut_text(cfg.shortcut(UiAction::TogglePause));\n            let res = ui.add(button).on_disabled_hover_text(Self::NO_ROM_LOADED);\n            if res.clicked() {\n                self.run_state = match self.run_state {\n                    RunState::Running => RunState::ManuallyPaused,\n                    RunState::ManuallyPaused | RunState::AutoPaused => RunState::Running,\n                };\n                tx.event(EmulationEvent::RunState(self.run_state));\n            };\n        });\n\n        let button = Button::new(if cfg.audio.enabled {\n            \"🔇 Mute\"\n        } else {\n            \"🔊 Unmute\"\n        })\n        .shortcut_text(cfg.shortcut(Setting::ToggleAudio));\n        if ui.add(button).clicked() {\n            tx.event(ConfigEvent::AudioEnabled(!cfg.audio.enabled));\n        };\n\n        ui.separator();\n\n        ui.add_enabled_ui(self.loaded_rom.is_some(), |ui| {\n            ui.add_enabled_ui(cfg.emulation.rewind, |ui| {\n                let button = Button::new(\"⟲ Instant Rewind\")\n                    .shortcut_text(cfg.shortcut(Feature::InstantRewind));\n                let disabled_hover_text = if self.loaded_rom.is_none() {\n                    Self::NO_ROM_LOADED\n                } else {\n                    \"Rewind can be enabled under the `Config` menu.\"\n                };\n                let res = ui\n                    .add(button)\n                    .on_hover_text(\"Instantly rewind state to a previous point.\")\n                    .on_disabled_hover_text(disabled_hover_text);\n                if res.clicked() {\n                    tx.event(EmulationEvent::InstantRewind);\n                };\n            });\n\n            let button = Button::new(\"🔃 Reset\")\n                .shortcut_text(cfg.shortcut(DeckAction::Reset(ResetKind::Soft)));\n            let res = ui\n                .add(button)\n                .on_hover_text(\"Emulate a soft reset of the NES.\")\n                .on_disabled_hover_text(Self::NO_ROM_LOADED);\n            if res.clicked() {\n                tx.event(EmulationEvent::Reset(ResetKind::Soft));\n            };\n\n            let button = Button::new(\"🔌 Power Cycle\")\n                .shortcut_text(cfg.shortcut(DeckAction::Reset(ResetKind::Hard)));\n            let res = ui\n                .add(button)\n                .on_hover_text(\"Emulate a power cycle of the NES.\")\n                .on_disabled_hover_text(Self::NO_ROM_LOADED);\n            if res.clicked() {\n                tx.event(EmulationEvent::Reset(ResetKind::Hard));\n            };\n        });\n\n        if feature!(Filesystem) {\n            ui.separator();\n\n            ui.add_enabled_ui(self.loaded_rom.is_some(), |ui| {\n                let button = Button::new(\"🖼 Screenshot\")\n                    .shortcut_text(cfg.shortcut(Feature::TakeScreenshot));\n                let res = ui.add(button).on_disabled_hover_text(Self::NO_ROM_LOADED);\n                if res.clicked() {\n                    tx.event(EmulationEvent::Screenshot);\n                };\n\n                let button_txt = if self.replay_recording {\n                    \"⏹ Stop Replay Recording\"\n                } else {\n                    \"🎞 Record Replay\"\n                };\n                let button = Button::new(button_txt)\n                    .shortcut_text(cfg.shortcut(Feature::ToggleReplayRecording));\n                let res = ui\n                    .add(button)\n                    .on_hover_text(\"Record or stop recording a game replay file.\")\n                    .on_disabled_hover_text(Self::NO_ROM_LOADED);\n                if res.clicked() {\n                    tx.event(EmulationEvent::ReplayRecord(!self.replay_recording));\n                };\n\n                let button_txt = if self.audio_recording {\n                    \"⏹ Stop Audio Recording\"\n                } else {\n                    \"🎤 Record Audio\"\n                };\n                let button = Button::new(button_txt)\n                    .shortcut_text(cfg.shortcut(Feature::ToggleAudioRecording));\n                let res = ui\n                    .add(button)\n                    .on_hover_text(\"Record or stop recording a audio file.\")\n                    .on_disabled_hover_text(Self::NO_ROM_LOADED);\n                if res.clicked() {\n                    tx.event(EmulationEvent::AudioRecord(!self.audio_recording));\n                };\n            });\n        }\n    }\n\n    fn config_menu(&mut self, ui: &mut Ui, cfg: &Config) {\n        let tx = &self.tx;\n\n        Preferences::zapper_checkbox(\n            tx,\n            ui,\n            cfg.deck.zapper,\n            cfg.shortcut(DeckAction::ToggleZapperConnected),\n        );\n        Preferences::rewind_checkbox(\n            tx,\n            ui,\n            cfg.emulation.rewind,\n            cfg.shortcut(Setting::ToggleRewinding),\n        );\n        Preferences::overscan_checkbox(\n            tx,\n            ui,\n            cfg.renderer.hide_overscan,\n            cfg.shortcut(Setting::ToggleOverscan),\n        );\n\n        ui.separator();\n\n        ui.menu_button(\"🕒 Emulation Speed...\", |ui| {\n            let speed = cfg.emulation.speed;\n            let button =\n                Button::new(\"Increment\").shortcut_text(cfg.shortcut(Setting::IncrementSpeed));\n            if ui.add(button).clicked() {\n                let new_speed = cfg.next_increment_speed();\n                if speed != new_speed {\n                    tx.event(ConfigEvent::Speed(new_speed));\n                }\n            }\n\n            let button =\n                Button::new(\"Decrement\").shortcut_text(cfg.shortcut(Setting::DecrementSpeed));\n            if ui.add(button).clicked() {\n                let new_speed = cfg.next_decrement_speed();\n                if speed != new_speed {\n                    tx.event(ConfigEvent::Speed(new_speed));\n                }\n            }\n            Preferences::speed_slider(tx, ui, cfg.emulation.speed);\n        });\n        ui.menu_button(\"🏃 Run Ahead...\", |ui| {\n            Preferences::run_ahead_slider(tx, ui, cfg.emulation.run_ahead);\n        });\n\n        ui.separator();\n\n        ui.menu_button(\"🌉 Video Filter...\", |ui| {\n            Preferences::video_filter_radio(tx, ui, cfg.deck.filter, cfg, ShowShortcut::Yes);\n        });\n        ui.menu_button(\"🕶 Shader...\", |ui| {\n            Preferences::shader_radio(tx, ui, cfg.renderer.shader, cfg, ShowShortcut::Yes);\n        });\n        ui.menu_button(\"🌎 Nes Region...\", |ui| {\n            Preferences::nes_region_radio(tx, ui, cfg.deck.region);\n        });\n        ui.menu_button(\"🎮 Four Player...\", |ui| {\n            Preferences::four_player_radio(tx, ui, cfg.deck.four_player);\n        });\n        ui.menu_button(\"📓 Game Genie Codes...\", |ui| {\n            self.preferences.show_genie_codes_entry(ui, cfg);\n\n            ui.separator();\n\n            Preferences::genie_codes_list(tx, ui, cfg, true);\n        });\n\n        ui.separator();\n\n        let mut preferences_open = self.preferences.open();\n        // icon: gear\n        let toggle = ToggleValue::new(&mut preferences_open, \"🔧 Preferences\")\n            .shortcut_text(cfg.shortcut(Menu::Preferences));\n        if ui.add(toggle).clicked() {\n            self.preferences.set_open(preferences_open, &self.ctx);\n        }\n\n        let mut keybinds_open = self.keybinds.open();\n        // icon: keyboard\n        let toggle = ToggleValue::new(&mut keybinds_open, \"🖮 Keybinds\")\n            .shortcut_text(cfg.shortcut(Menu::Keybinds));\n        if ui.add(toggle).clicked() {\n            self.keybinds.set_open(keybinds_open, &self.ctx);\n        };\n    }\n\n    fn window_menu(&mut self, ui: &mut Ui, cfg: &Config) {\n        use Setting::*;\n\n        let tx = &self.tx;\n        let RendererConfig {\n            scale,\n            fullscreen,\n            always_on_top,\n            show_menubar,\n            show_messages,\n            ..\n        } = cfg.renderer;\n\n        ui.menu_button(\"📏 Window Scale...\", |ui| {\n            let button = Button::new(\"Increment\").shortcut_text(cfg.shortcut(IncrementScale));\n            if ui.add(button).clicked() {\n                let new_scale = cfg.next_increment_scale();\n                if scale != new_scale {\n                    tx.event(ConfigEvent::Scale(scale));\n                }\n            }\n\n            let button = Button::new(\"Decrement\").shortcut_text(cfg.shortcut(DecrementScale));\n            if ui.add(button).clicked() {\n                let new_scale = cfg.next_decrement_scale();\n                if scale != new_scale {\n                    tx.event(ConfigEvent::Scale(scale));\n                }\n            }\n\n            Preferences::window_scale_radio(tx, ui, cfg.renderer.scale);\n        });\n        egui::gui_zoom::zoom_menu_buttons(ui);\n\n        ui.separator();\n\n        Preferences::fullscreen_checkbox(tx, ui, fullscreen, cfg.shortcut(ToggleFullscreen));\n        Preferences::embed_viewports_checkbox(tx, ui, cfg, cfg.shortcut(ToggleEmbedViewports));\n        Preferences::always_on_top_checkbox(tx, ui, always_on_top, cfg.shortcut(ToggleAlwaysOnTop));\n\n        ui.separator();\n\n        Preferences::menubar_checkbox(tx, ui, show_menubar, cfg.shortcut(ToggleMenubar));\n        Preferences::messages_checkbox(tx, ui, show_messages, cfg.shortcut(ToggleMessages));\n        if feature!(ScreenReader) {\n            Preferences::screen_reader_checkbox(ui, cfg.shortcut(ToggleScreenReader));\n        }\n    }\n\n    fn debug_menu(&mut self, ui: &mut Ui, cfg: &Config) {\n        let tx = &self.tx;\n\n        let mut perf_stats_open = self.perf_stats_open;\n        let toggle = ToggleValue::new(&mut perf_stats_open, \"🛠 Performance Stats\")\n            .shortcut_text(cfg.shortcut(Menu::PerfStats));\n        let res = ui\n            .add(toggle)\n            .on_hover_text(\"Enable a performance statistics overlay\");\n        if res.clicked() {\n            self.perf_stats_open = perf_stats_open;\n            tx.event(EmulationEvent::ShowFrameStats(self.perf_stats_open));\n        }\n\n        let mut gui_settings_open = self.ui_settings_open.load(Ordering::Acquire);\n        let toggle = ToggleValue::new(&mut gui_settings_open, UI_SETTINGS_TITLE);\n        let res = ui.add(toggle).on_hover_text(\"Toggle the UI style window\");\n        if res.clicked() {\n            self.ui_settings_open\n                .store(gui_settings_open, Ordering::Release);\n        }\n\n        #[cfg(debug_assertions)]\n        {\n            let mut gui_inspection_open = self.ui_inspection_open.load(Ordering::Acquire);\n            let toggle = ToggleValue::new(&mut gui_inspection_open, \"🔍 UI Inspection\");\n            let res = ui\n                .add(toggle)\n                .on_hover_text(\"Toggle the UI inspection window\");\n            if res.clicked() {\n                self.ui_inspection_open\n                    .store(gui_inspection_open, Ordering::Release);\n            }\n\n            let mut gui_memory_open = self.ui_memory_open.load(Ordering::Acquire);\n            let toggle = ToggleValue::new(&mut gui_memory_open, \"📝 UI Memory\");\n            let res = ui.add(toggle).on_hover_text(\"Toggle the UI memory window\");\n            if res.clicked() {\n                self.ui_memory_open\n                    .store(gui_memory_open, Ordering::Release);\n            }\n\n            ui.toggle_value(&mut self.viewport_info_open, \"ℹ Viewport Info\");\n\n            #[cfg(target_arch = \"wasm32\")]\n            if ui.button(\"❗Test panic!\").clicked() {\n                panic!(\"panic test\");\n            }\n        }\n\n        ui.separator();\n\n        ui.add_enabled_ui(false, |ui| {\n            let debugger_shortcut = cfg.shortcut(Debug::Toggle(DebugKind::Cpu));\n            let toggle = ToggleValue::new(&mut self.debugger_open, \"🚧 Debugger\")\n                .shortcut_text(debugger_shortcut);\n            ui.add(toggle)\n                .on_hover_text(\"Toggle the Debugger.\")\n                .on_disabled_hover_text(\"Not yet implemented.\");\n        });\n\n        let ppu_viewer_shortcut = cfg.shortcut(Debug::Toggle(DebugKind::Ppu));\n        let mut open = self.ppu_viewer.open();\n        let toggle =\n            ToggleValue::new(&mut open, \"🌇 PPU Viewer\").shortcut_text(ppu_viewer_shortcut);\n        let res = ui.add(toggle).on_hover_text(\"Toggle the PPU Viewer.\");\n        if res.clicked() {\n            self.ppu_viewer.set_open(open, &self.ctx);\n        }\n\n        ui.add_enabled_ui(false, |ui| {\n            let apu_mixer_shortcut = cfg.shortcut(Debug::Toggle(DebugKind::Apu));\n            let toggle = ToggleValue::new(&mut self.apu_mixer_open, \"🎼 APU Mixer\")\n                .shortcut_text(apu_mixer_shortcut);\n            ui.add(toggle)\n                .on_hover_text(\"Toggle the APU Mixer.\")\n                .on_disabled_hover_text(\"Not yet implemented.\");\n        });\n\n        ui.separator();\n\n        ui.add_enabled_ui(self.loaded_rom.is_some(), |ui| {\n            let button =\n                Button::new(\"➡ Step\").shortcut_text(cfg.shortcut(Debug::Step(DebugStep::Into)));\n            let res = ui\n                .add(button)\n                .on_hover_text(\"Step a single CPU instruction.\")\n                .on_disabled_hover_text(Self::NO_ROM_LOADED);\n            if res.clicked() {\n                tx.event(EmulationEvent::DebugStep(DebugStep::Into));\n            }\n\n            let button =\n                Button::new(\"⬆ Step Out\").shortcut_text(cfg.shortcut(Debug::Step(DebugStep::Out)));\n            let res = ui\n                .add(button)\n                .on_hover_text(\"Step out of the current CPU function.\")\n                .on_disabled_hover_text(Self::NO_ROM_LOADED);\n            if res.clicked() {\n                tx.event(EmulationEvent::DebugStep(DebugStep::Out));\n            }\n\n            let button = Button::new(\"⮫ Step Over\")\n                .shortcut_text(cfg.shortcut(Debug::Step(DebugStep::Over)));\n            let res = ui\n                .add(button)\n                .on_hover_text(\"Step over the next CPU instruction.\")\n                .on_disabled_hover_text(Self::NO_ROM_LOADED);\n            if res.clicked() {\n                tx.event(EmulationEvent::DebugStep(DebugStep::Over));\n            }\n\n            let button = Button::new(\"➖ Step Scanline\")\n                .shortcut_text(cfg.shortcut(Debug::Step(DebugStep::Scanline)));\n            let res = ui\n                .add(button)\n                .on_hover_text(\"Step an entire PPU scanline.\")\n                .on_disabled_hover_text(Self::NO_ROM_LOADED);\n            if res.clicked() {\n                tx.event(EmulationEvent::DebugStep(DebugStep::Scanline));\n            }\n\n            let button = Button::new(\"🖼 Step Frame\")\n                .shortcut_text(cfg.shortcut(Debug::Step(DebugStep::Frame)));\n            let res = ui\n                .add(button)\n                .on_hover_text(\"Step an entire PPU Frame.\")\n                .on_disabled_hover_text(Self::NO_ROM_LOADED);\n            if res.clicked() {\n                tx.event(EmulationEvent::DebugStep(DebugStep::Frame));\n            }\n        });\n    }\n\n    fn nes_frame(&mut self, ui: &mut Ui, enabled: bool, cfg: &Config, gamepads: &Gamepads) {\n        ui.add_enabled_ui(enabled, |ui| {\n            let tx = &self.tx;\n\n            CentralPanel::default().show_inside(ui, |ui| {\n                if self.loaded_rom.is_some() {\n                    let layout = Layout {\n                        main_dir: Direction::TopDown,\n                        main_align: Align::Center,\n                        cross_align: Align::Center,\n                        ..Default::default()\n                    };\n                    ui.with_layout(layout, |ui| {\n                        let image = Image::from_texture(self.nes_texture.sized())\n                            .shrink_to_fit()\n                            .sense(Sense::click());\n\n                        let hover_cursor = if cfg.deck.zapper {\n                            CursorIcon::Crosshair\n                        } else {\n                            CursorIcon::Default\n                        };\n\n                        let res = ui.add(image).on_hover_cursor(hover_cursor);\n                        self.nes_frame = res.rect;\n\n                        if cfg.deck.zapper {\n                            if res.clicked() {\n                                tx.event(EmulationEvent::ZapperTrigger);\n                            }\n                            if\n                                cfg\n                                .action_input(DeckAction::ZapperAimOffscreen)\n                                .is_some_and(|input| input_down(ui, gamepads, cfg, input))\n                            {\n                                let pos = (ppu::size::WIDTH + 10, ppu::size::HEIGHT + 10);\n                                tx.event(EmulationEvent::ZapperAim(pos));\n                            } else if let Some(Pos2 { x, y }) = res\n                                .hover_pos()\n                                .and_then(|Pos2 { x, y }| cursor_to_zapper(x, y, res.rect))\n                            {\n                                let pos = (x.round() as u16, y.round() as u16);\n                                tx.event(EmulationEvent::ZapperAim(pos));\n                            }\n                        }\n                    });\n                } else {\n                    ui.vertical_centered(|ui| {\n                        ui.horizontal_centered(|ui| {\n                            let image = Image::new(include_image!(\"../../../assets/tetanes.png\"))\n                                .shrink_to_fit()\n                                .tint(Color32::GRAY);\n                            ui.add(image);\n                        });\n                    });\n                }\n            });\n\n            let mut recording_labels = Vec::new();\n            if self.replay_recording {\n                recording_labels.push(\"Replay\");\n            }\n            if self.audio_recording {\n                recording_labels.push(\"Audio\");\n            }\n            if !recording_labels.is_empty() {\n                Frame::side_top_panel(ui.style()).show(ui, |ui| {\n                    ui.with_layout(\n                        Layout::top_down_justified(Align::LEFT).with_main_wrap(true),\n                        |ui| {\n                            ui.label(\n                                RichText::new(format!(\n                                    \"Recording {}...\",\n                                    recording_labels.join(\" & \")\n                                ))\n                                .italics(),\n                            )\n                        },\n                    );\n                });\n            }\n\n            if cfg.renderer.show_messages {\n                if let Some(instr) = self.corrupted_cpu_instr {\n                    Frame::popup(ui.style()).show(ui, |ui| {\n                        ui.with_layout(\n                            Layout::top_down_justified(Align::LEFT).with_main_wrap(true),\n                            |ui| {\n                                ui.colored_label(\n                                    Color32::RED,\n                                    format!(\n                                        \"Invalid CPU opcode: ${:02X} {:?} #{:?} encountered. Title: {}\",\n                                        instr.opcode,\n                                        instr.instr,\n                                        instr.addr_mode,\n                                        self.loaded_rom.as_ref().map(|rom| rom.name.as_str()).unwrap_or_default()\n                                    ),\n                                );\n\n                                ui.vertical(|ui| {\n                                    ui.label(\"Recovery options:\");\n                                    ui.horizontal(|ui| {\n                                        if ui.button(\"Reset\").clicked() {\n                                            self.tx.event(EmulationEvent::Reset(ResetKind::Soft));\n                                            self.corrupted_cpu_instr = None;\n                                        }\n                                        if ui.button(\"Power Cycle\").clicked() {\n                                            self.tx.event(EmulationEvent::Reset(ResetKind::Hard));\n                                            self.corrupted_cpu_instr = None;\n                                        }\n                                    });\n                                    ui.horizontal(|ui| {\n                                        if ui.button(\"Clear Save States\").clicked() {\n                                            preferences::State::clear_save_states(&self.tx);\n                                        }\n                                        if ui.button(\"Load ROM\").clicked() {\n                                            self.tx.event(UiEvent::LoadRomDialog);\n                                        }\n                                    });\n                                });\n                            },\n                        );\n                    });\n                }\n\n                if self.error.is_some() {\n                    Frame::popup(ui.style()).show(ui, |ui| {\n                        ui.with_layout(\n                            Layout::top_down_justified(Align::LEFT).with_main_wrap(true),\n                            |ui| {\n                                self.error_bar(ui);\n                            },\n                        );\n                    });\n                }\n\n                if !self.messages.is_empty() {\n                    Frame::popup(ui.style()).show(ui, |ui| {\n                        ui.with_layout(\n                            Layout::top_down_justified(Align::LEFT).with_main_wrap(true),\n                            |ui| {\n                                self.message_bar(ui);\n                            },\n                        );\n                    });\n                }\n\n                if self.run_state.paused() {\n                    Frame::new().inner_margin(5.0).show(ui, |ui| {\n                        ui.heading(RichText::new(\"⏸\").color(Color32::LIGHT_GRAY).size(40.0));\n                    });\n                }\n            }\n        });\n    }\n\n    fn performance_stats(&mut self, ui: &mut Ui, cfg: &Config) {\n        let grid = Grid::new(\"perf_stats\").num_columns(2).spacing([40.0, 6.0]);\n        grid.show(ui, |ui| {\n            ui.ctx().request_repaint_after(Duration::from_secs(1));\n\n            self.sys.update();\n\n            let good_color = if ui.style().visuals.dark_mode {\n                hex_color!(\"#b8cc52\")\n            } else {\n                hex_color!(\"#86b300\")\n            };\n            let warn_color = ui.style().visuals.warn_fg_color;\n            let bad_color = ui.style().visuals.error_fg_color;\n            let fps_color = |fps| match fps {\n                fps if fps < 30.0 => bad_color,\n                fps if fps < 60.0 => warn_color,\n                _ => good_color,\n            };\n            let frame_time_color = |time| match time {\n                time if time <= 1000.0 * 1.0 / 60.0 => good_color,\n                time if time <= 1000.0 * 1.0 / 30.0 => warn_color,\n                _ => bad_color,\n            };\n\n            let fps = self.frame_stats.fps;\n            ui.strong(\"FPS:\");\n            if fps.is_finite() {\n                ui.colored_label(fps_color(fps), format!(\"{fps:.2}\"));\n            } else {\n                ui.label(\"N/A\");\n            }\n            ui.end_row();\n\n            let fps_min = self.frame_stats.fps_min;\n            ui.strong(\"FPS (min):\");\n            if fps_min.is_finite() {\n                ui.colored_label(fps_color(fps_min), format!(\"{fps_min:.2}\"));\n            } else {\n                ui.label(\"N/A\");\n            }\n            ui.end_row();\n\n            let frame_time = self.frame_stats.frame_time;\n            ui.strong(\"Frame Time:\");\n            if frame_time.is_finite() {\n                ui.colored_label(frame_time_color(frame_time), format!(\"{frame_time:.2} ms\"));\n            } else {\n                ui.label(\"N/A\");\n            }\n            ui.end_row();\n\n            let frame_time_max = self.frame_stats.frame_time_max;\n            ui.strong(\"Frame Time (max):\");\n            if frame_time_max.is_finite() {\n                ui.colored_label(\n                    frame_time_color(frame_time_max),\n                    format!(\"{frame_time_max:.2} ms\"),\n                );\n            } else {\n                ui.label(\"N/A\");\n            }\n            ui.end_row();\n\n            ui.strong(\"Frame Count:\");\n            ui.label(format!(\"{}\", self.frame_stats.frame_count));\n            ui.end_row();\n\n            if let Some(stats) = self.sys.stats() {\n                let cpu_color = |cpu| match cpu {\n                    cpu if cpu <= 25.0 => good_color,\n                    cpu if cpu <= 50.0 => warn_color,\n                    _ => bad_color,\n                };\n                const fn bytes_to_mb(bytes: u64) -> u64 {\n                    bytes / 0x100000\n                }\n\n                ui.label(\"\");\n                ui.end_row();\n\n                ui.strong(\"CPU:\");\n                ui.colored_label(\n                    cpu_color(stats.cpu_usage),\n                    format!(\"{:.2}%\", stats.cpu_usage),\n                );\n                ui.end_row();\n\n                ui.strong(\"Memory:\");\n                ui.label(format!(\"{} MB\", bytes_to_mb(stats.memory)));\n                ui.end_row();\n\n                let du = stats.disk_usage;\n                ui.strong(\"Disk read new/total:\");\n                ui.label(format!(\n                    \"{:.2}/{:.2} MB\",\n                    bytes_to_mb(du.read_bytes),\n                    bytes_to_mb(du.total_read_bytes)\n                ));\n                ui.end_row();\n\n                ui.strong(\"Disk written new/total:\");\n                ui.label(format!(\n                    \"{:.2}/{:.2} MB\",\n                    bytes_to_mb(du.written_bytes),\n                    bytes_to_mb(du.total_written_bytes),\n                ));\n                ui.end_row();\n            }\n\n            ui.label(\"\");\n            ui.end_row();\n\n            ui.strong(\"Run Time:\");\n            let run_time = {\n                let secs = self.start.elapsed().as_secs();\n                let days = secs / 86_400;\n                let hours = (secs % 86_400) / 3_600;\n                let mins = (secs % 3_600) / 60;\n                let secs = secs % 60;\n                if days > 0 {\n                    format!(\"{days}d {hours}h {mins}m {secs}s\")\n                } else if hours > 0 {\n                    format!(\"{hours}h {mins}m {secs}s\")\n                } else if mins > 0 {\n                    format!(\"{mins}m {secs}s\")\n                } else {\n                    format!(\"{secs}s\")\n                }\n            };\n            ui.label(run_time);\n            ui.end_row();\n\n            let (cursor_pos, zapper_pos) = match ui.input(|i| i.pointer.latest_pos()) {\n                Some(Pos2 { x, y }) => {\n                    let zapper_pos = match cursor_to_zapper(x, y, self.nes_frame) {\n                        Some(Pos2 { x, y }) => format!(\"({x:.0}, {y:.0})\"),\n                        None => \"(-, -)\".to_string(),\n                    };\n                    (format!(\"({x:.0}, {y:.0})\"), zapper_pos)\n                }\n                None => (\"(-, -)\".to_string(), \"(-, -)\".to_string()),\n            };\n\n            ui.strong(\"Cursor Pos:\");\n            ui.label(cursor_pos);\n            ui.end_row();\n\n            if cfg.deck.zapper {\n                ui.strong(\"Zapper Pos:\");\n                ui.label(zapper_pos);\n                ui.end_row();\n            }\n        });\n    }\n\n    fn help_menu(&mut self, ui: &mut Ui) {\n        if self.version.requires_updates() && ui.button(\"🌐 Check for Updates...\").clicked() {\n            let notify_latest = true;\n            self.version.check_for_updates(&self.tx, notify_latest);\n        }\n        ui.toggle_value(&mut self.about_open, \"ℹ About\");\n    }\n\n    fn about(&mut self, ui: &mut Ui, enabled: bool) {\n        ui.add_enabled_ui(enabled, |ui| {\n            ui.with_layout(Layout::left_to_right(Align::Min), |ui| {\n                let image = Image::new(include_image!(\"../../../assets/tetanes_icon.png\"))\n                    .max_height(50.0)\n                    .shrink_to_fit();\n                ui.add(image);\n\n                ui.vertical(|ui| {\n                    let grid = Grid::new(\"version\").num_columns(2).spacing([40.0, 6.0]);\n                    grid.show(ui, |ui| {\n                        ui.strong(\"Version:\");\n                        ui.label(self.version.current());\n                        ui.end_row();\n\n                        ui.strong(\"GitHub:\");\n                        ui.hyperlink(\"https://github.com/lukexor/tetanes\");\n                        ui.end_row();\n                    });\n\n                    if feature!(Filesystem) {\n                        ui.separator();\n                        ui.horizontal_wrapped(|ui| {\n                            let grid = Grid::new(\"directories\").num_columns(2).spacing([40.0, 6.0]);\n                            grid.show(ui, |ui| {\n                                let config_dir = Config::default_config_dir();\n                                ui.strong(\"Preferences:\");\n                                ui.label(format!(\"{}\", config_dir.display()));\n                                ui.end_row();\n\n                                let data_dir = Config::default_data_dir();\n                                ui.strong(\"Save States/RAM, Replays: \");\n                                ui.label(format!(\"{}\", data_dir.display()));\n                                ui.end_row();\n\n                                let picture_dir = Config::default_picture_dir();\n                                ui.strong(\"Screenshots: \");\n                                ui.label(format!(\"{}\", picture_dir.display()));\n                                ui.end_row();\n\n                                let audio_dir = Config::default_audio_dir();\n                                ui.strong(\"Audio Recordings: \");\n                                ui.label(format!(\"{}\", audio_dir.display()));\n                                ui.end_row();\n                            });\n                        });\n                    }\n                });\n            });\n        });\n    }\n\n    fn about_homebrew(ui: &mut Ui, rom: RomAsset) {\n        ScrollArea::vertical().show(ui, |ui| {\n            ui.strong(\"Author(s):\");\n            ui.label(rom.authors);\n            ui.add_space(12.0);\n\n            ui.strong(\"Description:\");\n            ui.label(rom.description);\n            ui.add_space(12.0);\n\n            ui.strong(\"Source:\");\n            ui.hyperlink(rom.source);\n        });\n    }\n\n    fn message_bar(&mut self, ui: &mut Ui) {\n        let now = Instant::now();\n        self.messages.retain(|(_, _, expires)| now < *expires);\n        self.messages.dedup_by(|a, b| a.1.eq(&b.1));\n        for (ty, message, _) in self.messages.iter().take(Self::MAX_MESSAGES) {\n            let visuals = &ui.style().visuals;\n            let (icon, color) = match ty {\n                MessageType::Info => (\"ℹ\", visuals.widgets.noninteractive.fg_stroke.color),\n                MessageType::Warn => (\"⚠\", visuals.warn_fg_color),\n                MessageType::Error => (\"❗\", visuals.error_fg_color),\n            };\n            ui.colored_label(color, format!(\"{icon} {message}\"));\n        }\n    }\n\n    fn error_bar(&mut self, ui: &mut Ui) {\n        if let Some(error) = self.error.clone() {\n            let available_width = ui.available_width();\n            ui.set_min_width(available_width);\n            ui.horizontal(|ui| {\n                let res = ui.colored_label(Color32::RED, error);\n                ui.add_space(available_width - res.rect.width() - 30.0);\n                if ui.button(\"❌\").clicked() {\n                    self.error = None;\n                }\n            });\n        }\n    }\n\n    pub fn dark_theme() -> egui::Visuals {\n        Visuals {\n            dark_mode: true,\n            widgets: egui::style::Widgets {\n                noninteractive: WidgetVisuals {\n                    weak_bg_fill: hex_color!(\"#14191f\"),\n                    bg_fill: hex_color!(\"#14191f\"),\n                    bg_stroke: Stroke::new(1f32, hex_color!(\"#253340\")), // separators, indentation lines\n                    fg_stroke: Stroke::new(1f32, hex_color!(\"#e6b673\")), // normal text color\n                    corner_radius: CornerRadius::ZERO,\n                    expansion: 0.0,\n                },\n                inactive: WidgetVisuals {\n                    weak_bg_fill: hex_color!(\"#253340\"), // button background\n                    bg_fill: hex_color!(\"#253340\"),      // checkbox background\n                    bg_stroke: Stroke::default(),\n                    fg_stroke: Stroke::new(1f32, hex_color!(\"#a9491f\")), // button text\n                    corner_radius: CornerRadius::ZERO,\n                    expansion: 0.0,\n                },\n                hovered: WidgetVisuals {\n                    weak_bg_fill: hex_color!(\"#212733\"),\n                    bg_fill: hex_color!(\"#212733\"),\n                    bg_stroke: Stroke::new(1f32, hex_color!(\"#f29718\")), // e.g. hover over window edge or button\n                    fg_stroke: Stroke::new(1.5f32, hex_color!(\"#ffb454\")),\n                    corner_radius: CornerRadius::ZERO,\n                    expansion: 1.0,\n                },\n                active: WidgetVisuals {\n                    weak_bg_fill: hex_color!(\"#253340\"),\n                    bg_fill: hex_color!(\"#253340\"),\n                    bg_stroke: Stroke::new(1f32, hex_color!(\"#fed7aa\")),\n                    fg_stroke: Stroke::new(1f32, hex_color!(\"#fed7aa\")),\n                    corner_radius: CornerRadius::ZERO,\n                    expansion: 1.0,\n                },\n                open: WidgetVisuals {\n                    weak_bg_fill: hex_color!(\"#151a1e\"),\n                    bg_fill: hex_color!(\"#14191f\"),\n                    bg_stroke: Stroke::new(1f32, hex_color!(\"#253340\")),\n                    fg_stroke: Stroke::new(1f32, hex_color!(\"#ffb454\")),\n                    corner_radius: CornerRadius::ZERO,\n                    expansion: 0.0,\n                },\n            },\n            selection: Selection {\n                bg_fill: hex_color!(\"#253340\"),\n                stroke: Stroke::new(1f32, hex_color!(\"#ffb454\")),\n            },\n            hyperlink_color: hex_color!(\"#36a3d9\"),\n            faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so\n            extreme_bg_color: hex_color!(\"#091015\"),             // e.g. TextEdit background\n            code_bg_color: hex_color!(\"#253340\"),\n            warn_fg_color: hex_color!(\"#e7c547\"),\n            error_fg_color: hex_color!(\"#ff3333\"),\n            window_corner_radius: CornerRadius::ZERO,\n            window_fill: hex_color!(\"#14191f\"),\n            window_stroke: Stroke::new(1f32, hex_color!(\"#253340\")),\n            window_highlight_topmost: true,\n            menu_corner_radius: CornerRadius::ZERO,\n            panel_fill: hex_color!(\"#14191f\"),\n            text_cursor: TextCursorStyle {\n                stroke: Stroke::new(1f32, hex_color!(\"#95e6cb\")),\n                ..Default::default()\n            },\n            striped: true,\n            handle_shape: HandleShape::Rect { aspect_ratio: 1.25 },\n            ..Default::default()\n        }\n    }\n\n    pub fn light_theme() -> egui::Visuals {\n        egui::Visuals {\n            dark_mode: false,\n            widgets: egui::style::Widgets {\n                noninteractive: WidgetVisuals {\n                    weak_bg_fill: hex_color!(\"#ffffff\"),\n                    bg_fill: hex_color!(\"#ffffff\"),\n                    bg_stroke: Stroke::new(1f32, hex_color!(\"#d9d7ce\")), // separators, indentation lines\n                    fg_stroke: Stroke::new(1f32, hex_color!(\"#253340\")), // normal text color\n                    corner_radius: CornerRadius::ZERO,\n                    expansion: 0.0,\n                },\n                inactive: WidgetVisuals {\n                    weak_bg_fill: hex_color!(\"#d9d8d7\"), // button background\n                    bg_fill: hex_color!(\"#d9d8d7\"),      // checkbox background\n                    bg_stroke: Stroke::default(),\n                    fg_stroke: Stroke::new(1f32, hex_color!(\"#a2441b\")), // button text\n                    corner_radius: CornerRadius::ZERO,\n                    expansion: 0.0,\n                },\n                hovered: WidgetVisuals {\n                    weak_bg_fill: hex_color!(\"#ffd9b3\"),\n                    bg_fill: hex_color!(\"#ffd9b3\"),\n                    bg_stroke: Stroke::new(1f32, hex_color!(\"#ff6a00\")), // e.g. hover over window edge or button\n                    fg_stroke: Stroke::new(1.5f32, hex_color!(\"#ff6a00\")),\n                    corner_radius: CornerRadius::ZERO,\n                    expansion: 1.0,\n                },\n                active: WidgetVisuals {\n                    weak_bg_fill: hex_color!(\"#d9d7ce\"),\n                    bg_fill: hex_color!(\"#d9d7ce\"),\n                    bg_stroke: Stroke::new(1f32, hex_color!(\"#3e4b59\")),\n                    fg_stroke: Stroke::new(1f32, hex_color!(\"#3e4b59\")),\n                    corner_radius: CornerRadius::ZERO,\n                    expansion: 1.0,\n                },\n                open: WidgetVisuals {\n                    weak_bg_fill: hex_color!(\"#f3f3f3\"),\n                    bg_fill: hex_color!(\"#ffffff\"),\n                    bg_stroke: Stroke::new(1f32, hex_color!(\"#d9d7ce\")),\n                    fg_stroke: Stroke::new(1f32, hex_color!(\"#ff6a00\")),\n                    corner_radius: CornerRadius::ZERO,\n                    expansion: 0.0,\n                },\n            },\n            selection: Selection {\n                bg_fill: hex_color!(\"#efc9a3\"),\n                stroke: Stroke::new(1f32, hex_color!(\"#b2340b\")),\n            },\n            hyperlink_color: hex_color!(\"#36a3d9\"),\n            faint_bg_color: Color32::from_additive_luminance(5), // visible, but barely so\n            extreme_bg_color: hex_color!(\"#e6e1cf\"),             // e.g. TextEdit background\n            code_bg_color: hex_color!(\"#fafafa\"),\n            warn_fg_color: hex_color!(\"#e7c547\"),\n            error_fg_color: hex_color!(\"#ff3333\"),\n            window_fill: hex_color!(\"#f0eee4\"),\n            window_stroke: Stroke::new(1f32, hex_color!(\"#d9d8d7\")),\n            panel_fill: hex_color!(\"#f0eee4\"),\n            text_cursor: TextCursorStyle {\n                stroke: Stroke::new(1f32, hex_color!(\"#4cbf99\")),\n                ..Default::default()\n            },\n            ..Self::dark_theme()\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/renderer/painter.rs",
    "content": "use crate::nes::renderer::shader::{self, Shader};\nuse anyhow::{Context, anyhow};\nuse egui::{\n    NumExt, ViewportId, ViewportIdMap, ViewportIdSet,\n    ahash::HashMap,\n    epaint::{self, Primitive, Vertex},\n};\nuse std::{\n    borrow::Cow,\n    collections::hash_map::Entry,\n    iter,\n    num::{NonZeroU32, NonZeroU64},\n    ops::{Deref, Range},\n    sync::Arc,\n};\nuse wgpu::util::DeviceExt;\nuse winit::{dpi::PhysicalSize, window::Window};\n\n#[derive(Debug)]\n#[must_use]\npub struct Surface {\n    inner: wgpu::Surface<'static>,\n    shader_resources: Option<shader::Resources>,\n    width: u32,\n    height: u32,\n}\n\nimpl Surface {\n    pub fn new(\n        instance: &wgpu::Instance,\n        window: Arc<Window>,\n        size: PhysicalSize<u32>,\n    ) -> anyhow::Result<Self> {\n        Ok(Self {\n            inner: instance.create_surface(window)?,\n            shader_resources: None,\n            width: size.width,\n            height: size.height,\n        })\n    }\n\n    fn create_texture_view(\n        &self,\n        device: &wgpu::Device,\n        format: wgpu::TextureFormat,\n    ) -> wgpu::TextureView {\n        device\n            .create_texture(&wgpu::TextureDescriptor {\n                label: Some(\"surface_texture\"),\n                size: wgpu::Extent3d {\n                    width: self.width,\n                    height: self.height,\n                    depth_or_array_layers: 1,\n                },\n                mip_level_count: 1,\n                sample_count: 1,\n                dimension: wgpu::TextureDimension::D2,\n                format,\n                usage: wgpu::TextureUsages::RENDER_ATTACHMENT\n                    | wgpu::TextureUsages::TEXTURE_BINDING,\n                view_formats: &[],\n            })\n            .create_view(&wgpu::TextureViewDescriptor::default())\n    }\n\n    fn set_shader(\n        &mut self,\n        device: &wgpu::Device,\n        format: wgpu::TextureFormat,\n        uniform_bind_group_layout: &wgpu::BindGroupLayout,\n        shader: Shader,\n    ) {\n        self.shader_resources = shader::Resources::new(\n            device,\n            format,\n            self.create_texture_view(device, format),\n            uniform_bind_group_layout,\n            shader,\n        );\n    }\n}\n\nimpl Deref for Surface {\n    type Target = wgpu::Surface<'static>;\n    fn deref(&self) -> &Self::Target {\n        &self.inner\n    }\n}\n\n#[derive(Debug)]\n#[must_use]\npub struct Painter {\n    instance: wgpu::Instance,\n    render_state: Option<RenderState>,\n    surfaces: ViewportIdMap<Surface>,\n}\n\nimpl Default for Painter {\n    fn default() -> Self {\n        let descriptor = if cfg!(all(target_arch = \"wasm32\", not(feature = \"webgpu\"))) {\n            // TODO: WebGPU is still unsafe/experimental on Linux in Chrome and still nightly on\n            // Firefox\n            wgpu::InstanceDescriptor {\n                backends: wgpu::Backends::all().difference(wgpu::Backends::BROWSER_WEBGPU),\n                ..wgpu::InstanceDescriptor::new_without_display_handle()\n            }\n        } else {\n            wgpu::InstanceDescriptor::new_without_display_handle()\n        };\n        Self {\n            instance: wgpu::Instance::new(descriptor),\n            render_state: None,\n            surfaces: Default::default(),\n        }\n    }\n}\n\nimpl Painter {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn set_shader(&mut self, shader: Shader) {\n        if let Some(render_state) = &mut self.render_state {\n            render_state.shader = shader;\n            for surface in self.surfaces.values_mut() {\n                surface.set_shader(\n                    &render_state.device,\n                    render_state.format,\n                    &render_state.uniform_bind_group_layout,\n                    shader,\n                );\n            }\n        }\n    }\n\n    pub async fn set_window(\n        &mut self,\n        viewport_id: ViewportId,\n        window: Option<Arc<Window>>,\n    ) -> anyhow::Result<()> {\n        if let Some(window) = window {\n            if let Entry::Vacant(entry) = self.surfaces.entry(viewport_id) {\n                let size = window.inner_size();\n                let mut surface = Surface::new(&self.instance, window, size)?;\n\n                let render_state = match &mut self.render_state {\n                    Some(render_state) => render_state,\n                    None => {\n                        let render_state = RenderState::create(&self.instance, &surface).await?;\n                        self.render_state.get_or_insert(render_state)\n                    }\n                };\n\n                if let (Some(width), Some(height)) =\n                    (NonZeroU32::new(size.width), NonZeroU32::new(size.height))\n                {\n                    render_state.resize_surface(&mut surface, width, height);\n                }\n\n                entry.insert(surface);\n            }\n        } else {\n            self.surfaces.clear();\n        }\n\n        Ok(())\n    }\n\n    pub fn paint(\n        &mut self,\n        viewport_id: ViewportId,\n        pixels_per_point: f32,\n        clipped_primitives: &[epaint::ClippedPrimitive],\n        textures_delta: &epaint::textures::TexturesDelta,\n    ) {\n        let Some(render_state) = &mut self.render_state else {\n            return;\n        };\n        let Some(surface) = self.surfaces.get_mut(&viewport_id) else {\n            return;\n        };\n\n        let mut encoder =\n            render_state\n                .device\n                .create_command_encoder(&wgpu::CommandEncoderDescriptor {\n                    label: Some(\"encoder\"),\n                });\n\n        // Upload all resources for the GPU.\n\n        let size_in_pixels = [surface.width, surface.height];\n        let screen_descriptor = ScreenDescriptor {\n            size_in_pixels,\n            pixels_per_point,\n        };\n\n        for (id, image_delta) in &textures_delta.set {\n            render_state.update_texture(*id, image_delta);\n        }\n        render_state.update_buffers(clipped_primitives, &screen_descriptor);\n\n        let output_frame = match surface.get_current_texture() {\n            wgpu::CurrentSurfaceTexture::Success(frame) => frame,\n            wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame,\n            wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => return,\n            wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {\n                if let (Some(width), Some(height)) = (\n                    NonZeroU32::new(surface.width),\n                    NonZeroU32::new(surface.height),\n                ) {\n                    render_state.resize_surface(surface, width, height);\n                }\n                return;\n            }\n            wgpu::CurrentSurfaceTexture::Validation => {\n                tracing::error!(\"failed to acquire next frame\");\n                return;\n            }\n        };\n\n        {\n            let view = match &surface.shader_resources {\n                Some(shader) => &shader.view,\n                None => &output_frame\n                    .texture\n                    .create_view(&wgpu::TextureViewDescriptor::default()),\n            };\n            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {\n                label: Some(\"main_render_pass\"),\n                color_attachments: &[Some(wgpu::RenderPassColorAttachment {\n                    view,\n                    resolve_target: None,\n                    depth_slice: None,\n                    ops: wgpu::Operations {\n                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),\n                        store: wgpu::StoreOp::Store,\n                    },\n                })],\n                depth_stencil_attachment: None,\n                timestamp_writes: None,\n                occlusion_query_set: None,\n                multiview_mask: None,\n            });\n\n            render_state.render(&mut render_pass, clipped_primitives, &screen_descriptor);\n        }\n\n        if let Some(shader) = &surface.shader_resources {\n            let view = &output_frame\n                .texture\n                .create_view(&wgpu::TextureViewDescriptor::default());\n\n            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {\n                label: Some(\"main_render_pass\"),\n                color_attachments: &[Some(wgpu::RenderPassColorAttachment {\n                    view,\n                    resolve_target: None,\n                    depth_slice: None,\n                    ops: wgpu::Operations {\n                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),\n                        store: wgpu::StoreOp::Store,\n                    },\n                })],\n                depth_stencil_attachment: None,\n                timestamp_writes: None,\n                occlusion_query_set: None,\n                multiview_mask: None,\n            });\n\n            render_pass.set_scissor_rect(0, 0, size_in_pixels[0], size_in_pixels[1]);\n            render_pass.set_viewport(\n                0.0,\n                0.0,\n                size_in_pixels[0] as f32,\n                size_in_pixels[1] as f32,\n                0.0,\n                1.0,\n            );\n            render_pass.set_pipeline(&shader.render_pipeline);\n            render_pass.set_bind_group(0, &render_state.uniform_bind_group, &[]);\n            render_pass.set_bind_group(1, &shader.texture_bind_group, &[]);\n            render_pass.draw(0..3, 0..1);\n        }\n\n        for id in &textures_delta.free {\n            render_state.textures.remove(id);\n        }\n\n        render_state.queue.submit(iter::once(encoder.finish()));\n\n        output_frame.present();\n    }\n\n    pub const fn render_state(&self) -> Option<&RenderState> {\n        self.render_state.as_ref()\n    }\n\n    pub const fn render_state_mut(&mut self) -> Option<&mut RenderState> {\n        self.render_state.as_mut()\n    }\n\n    pub fn on_window_resized(&mut self, viewport_id: ViewportId, width: u32, height: u32) {\n        if let (Some(width), Some(height)) = (NonZeroU32::new(width), NonZeroU32::new(height))\n            && let Some(surface) = self.surfaces.get_mut(&viewport_id)\n            && let Some(render_state) = &mut self.render_state\n        {\n            render_state.resize_surface(surface, width, height);\n        }\n    }\n\n    pub fn retain_surfaces(&mut self, viewport_ids: &ViewportIdSet) {\n        self.surfaces.retain(|id, _| viewport_ids.contains(id));\n    }\n\n    pub fn destroy(&mut self) {\n        self.surfaces.clear();\n        let _ = self.render_state.take();\n    }\n}\n\n#[derive(Debug)]\n#[must_use]\nstruct SlicedBuffer {\n    buffer: wgpu::Buffer,\n    slices: Vec<Range<usize>>,\n    capacity: wgpu::BufferAddress,\n}\n\n#[derive(Debug)]\n#[must_use]\npub struct RenderState {\n    pub device: wgpu::Device,\n    pub queue: wgpu::Queue,\n    pub format: wgpu::TextureFormat,\n\n    pipeline: wgpu::RenderPipeline,\n\n    index_buffer: SlicedBuffer,\n    vertex_buffer: SlicedBuffer,\n\n    uniform_buffer: wgpu::Buffer,\n    previous_uniform_buffer_content: UniformBuffer,\n    uniform_bind_group: wgpu::BindGroup,\n    uniform_bind_group_layout: wgpu::BindGroupLayout,\n    texture_bind_group_layout: wgpu::BindGroupLayout,\n\n    shader: Shader,\n    /// Map of egui texture IDs to textures and their associated bindgroups (texture view +\n    /// sampler). The texture may be None if the `TextureId` is just a handle to a user-provided\n    /// sampler.\n    textures: HashMap<epaint::TextureId, (Option<wgpu::Texture>, wgpu::BindGroup)>,\n    next_texture_id: u64,\n    samplers: HashMap<epaint::textures::TextureOptions, wgpu::Sampler>,\n}\n\nimpl RenderState {\n    async fn create(\n        instance: &wgpu::Instance,\n        surface: &wgpu::Surface<'_>,\n    ) -> anyhow::Result<Self> {\n        let adapter = instance\n            .request_adapter(&wgpu::RequestAdapterOptions {\n                power_preference: wgpu::PowerPreference::HighPerformance,\n                compatible_surface: Some(surface),\n                force_fallback_adapter: false,\n            })\n            .await\n            .context(\"failed to find suitable wgpu adapter\")?;\n\n        tracing::debug!(\"requested wgpu adapter: {:?}\", adapter.get_info());\n\n        let base_limits = if adapter.get_info().backend == wgpu::Backend::Gl {\n            wgpu::Limits::downlevel_webgl2_defaults()\n        } else {\n            wgpu::Limits::default()\n        };\n        let device_descriptor = wgpu::DeviceDescriptor {\n            label: Some(\"wgpu device\"),\n            // TODO: maybe CLEAR_TEXTURE?\n            required_limits: wgpu::Limits {\n                max_texture_dimension_2d: 8192,\n                ..base_limits\n            },\n            ..Default::default()\n        };\n        let mut connection = adapter.request_device(&device_descriptor).await;\n        // Creating device may fail if adapter doesn't support the default cfg, so try to\n        // recover with lower limits. Specifically max_texture_dimension_2d has a downlevel default\n        // of 2048. egui_wgpu wants 8192 for 4k displays, but not all platforms support that yet.\n        if let Err(err) = connection {\n            tracing::error!(\"failed to create wgpu device: {err:?}, retrying with lower limits\");\n            connection = adapter\n                .request_device(&wgpu::DeviceDescriptor {\n                    required_limits: wgpu::Limits {\n                        max_texture_dimension_2d: 4096,\n                        // Default Edge installed on Windows 10 is limited to 6 attachments,\n                        // and we never need more than 1.\n                        max_color_attachments: 6,\n                        ..base_limits\n                    },\n                    ..device_descriptor\n                })\n                .await\n        }\n\n        let capabilities = surface.get_capabilities(&adapter);\n        let format = capabilities\n            .formats\n            .iter()\n            .copied()\n            .find(|format| {\n                // egui prefers these formats\n                matches!(\n                    format,\n                    wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm\n                )\n            })\n            .unwrap_or_else(|| {\n                tracing::warn!(format = ?capabilities.formats[0], \"failling back to first available format\");\n                capabilities.formats[0]\n            });\n\n        let (device, queue) =\n            connection.map_err(|err| anyhow!(\"failed to create wgpu device: {err:?}\"))?;\n\n        let shader_module_desc =\n            wgpu::include_wgsl!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/shaders/gui.wgsl\"));\n        let shader_module = device.create_shader_module(shader_module_desc);\n\n        let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {\n            label: Some(\"gui uniform buffer\"),\n            contents: bytemuck::cast_slice(&[UniformBuffer::default()]),\n            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,\n        });\n\n        let uniform_bind_group_layout =\n            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {\n                label: Some(\"gui uniform bind group layout\"),\n                entries: &[wgpu::BindGroupLayoutEntry {\n                    binding: 0,\n                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,\n                    ty: wgpu::BindingType::Buffer {\n                        ty: wgpu::BufferBindingType::Uniform,\n                        has_dynamic_offset: false,\n                        min_binding_size: NonZeroU64::new(\n                            std::mem::size_of::<UniformBuffer>() as _,\n                        ),\n                    },\n                    count: None,\n                }],\n            });\n\n        let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {\n            label: Some(\"gui uniform bind group\"),\n            layout: &uniform_bind_group_layout,\n            entries: &[wgpu::BindGroupEntry {\n                binding: 0,\n                resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {\n                    buffer: &uniform_buffer,\n                    offset: 0,\n                    size: None,\n                }),\n            }],\n        });\n\n        let texture_bind_group_layout =\n            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {\n                label: Some(\"gui texture bind group layout\"),\n                entries: &[\n                    wgpu::BindGroupLayoutEntry {\n                        binding: 0,\n                        visibility: wgpu::ShaderStages::FRAGMENT,\n                        ty: wgpu::BindingType::Texture {\n                            sample_type: wgpu::TextureSampleType::Float { filterable: true },\n                            multisampled: false,\n                            view_dimension: wgpu::TextureViewDimension::D2,\n                        },\n                        count: None,\n                    },\n                    wgpu::BindGroupLayoutEntry {\n                        binding: 1,\n                        visibility: wgpu::ShaderStages::FRAGMENT,\n                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),\n                        count: None,\n                    },\n                ],\n            });\n\n        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {\n            label: Some(\"gui pipeline layout\"),\n            bind_group_layouts: &[\n                Some(&uniform_bind_group_layout),\n                Some(&texture_bind_group_layout),\n            ],\n            immediate_size: 0,\n        });\n\n        let pipeline =\n            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {\n                label: Some(\"gui pipeline\"),\n                layout: Some(&pipeline_layout),\n                vertex: wgpu::VertexState {\n                    entry_point: Some(\"vs_main\"),\n                    module: &shader_module,\n                    buffers: &[wgpu::VertexBufferLayout {\n                        array_stride: 5 * 4,\n                        step_mode: wgpu::VertexStepMode::Vertex,\n                        // 0: vec2 position\n                        // 1: vec2 uv coordinates\n                        // 2: uint color\n                        attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32],\n                    }],\n                    compilation_options: wgpu::PipelineCompilationOptions::default()\n                },\n                fragment: Some(wgpu::FragmentState {\n                    module: &shader_module,\n                    entry_point: Some(\"fs_main\"),\n                    targets: &[Some(wgpu::ColorTargetState {\n                        format,\n                        blend: Some(wgpu::BlendState {\n                            color: wgpu::BlendComponent {\n                                src_factor: wgpu::BlendFactor::One,\n                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,\n                                operation: wgpu::BlendOperation::Add,\n                            },\n                            alpha: wgpu::BlendComponent {\n                                src_factor: wgpu::BlendFactor::OneMinusDstAlpha,\n                                dst_factor: wgpu::BlendFactor::One,\n                                operation: wgpu::BlendOperation::Add,\n                            },\n                        }),\n                        write_mask: wgpu::ColorWrites::ALL,\n                    })],\n                    compilation_options: wgpu::PipelineCompilationOptions::default()\n                }),\n                primitive: wgpu::PrimitiveState::default(),\n                depth_stencil: None,\n                multisample: wgpu::MultisampleState::default(),\n                multiview_mask: None,\n                cache: None,\n            }\n        );\n\n        const INDEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =\n            (std::mem::size_of::<u32>() * 1024 * 3) as _;\n        const VERTEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =\n            (std::mem::size_of::<Vertex>() * 1024) as _;\n\n        let index_buffer = SlicedBuffer {\n            buffer: Self::create_index_buffer(&device, INDEX_BUFFER_START_CAPACITY),\n            slices: Vec::with_capacity(64),\n            capacity: INDEX_BUFFER_START_CAPACITY,\n        };\n        let vertex_buffer = SlicedBuffer {\n            buffer: Self::create_vertex_buffer(&device, VERTEX_BUFFER_START_CAPACITY),\n            slices: Vec::with_capacity(64),\n            capacity: VERTEX_BUFFER_START_CAPACITY,\n        };\n\n        Ok(Self {\n            device,\n            queue,\n            format,\n\n            pipeline,\n\n            index_buffer,\n            vertex_buffer,\n\n            uniform_buffer,\n            previous_uniform_buffer_content: Default::default(),\n            uniform_bind_group,\n            uniform_bind_group_layout,\n            texture_bind_group_layout,\n\n            shader: Shader::default(),\n            textures: Default::default(),\n            next_texture_id: 0,\n            samplers: Default::default(),\n        })\n    }\n\n    pub fn max_texture_side(&self) -> u32 {\n        self.device.limits().max_texture_dimension_2d\n    }\n\n    pub fn register_texture(\n        &mut self,\n        label: Option<&str>,\n        view: &wgpu::TextureView,\n        sampler_descriptor: wgpu::SamplerDescriptor<'_>,\n    ) -> epaint::TextureId {\n        let sampler = self.device.create_sampler(&sampler_descriptor);\n        let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {\n            label,\n            layout: &self.texture_bind_group_layout,\n            entries: &[\n                wgpu::BindGroupEntry {\n                    binding: 0,\n                    resource: wgpu::BindingResource::TextureView(view),\n                },\n                wgpu::BindGroupEntry {\n                    binding: 1,\n                    resource: wgpu::BindingResource::Sampler(&sampler),\n                },\n            ],\n        });\n\n        let id = epaint::TextureId::User(self.next_texture_id);\n        self.textures.insert(id, (None, bind_group));\n        self.next_texture_id += 1;\n\n        id\n    }\n\n    fn create_vertex_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {\n        device.create_buffer(&wgpu::BufferDescriptor {\n            label: Some(\"gui vertex buffer\"),\n            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,\n            size,\n            mapped_at_creation: false,\n        })\n    }\n\n    fn create_index_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {\n        device.create_buffer(&wgpu::BufferDescriptor {\n            label: Some(\"gui index buffer\"),\n            usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,\n            size,\n            mapped_at_creation: false,\n        })\n    }\n\n    fn resize_surface(&self, surface: &mut Surface, width: NonZeroU32, height: NonZeroU32) {\n        surface.width = width.get();\n        surface.height = height.get();\n        surface.configure(\n            &self.device,\n            &wgpu::SurfaceConfiguration {\n                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,\n                format: self.format,\n                width: width.get(),\n                height: height.get(),\n                // TODO: Support disabling vsync\n                present_mode: wgpu::PresentMode::AutoVsync,\n                desired_maximum_frame_latency: 2,\n                alpha_mode: wgpu::CompositeAlphaMode::Auto,\n                view_formats: vec![self.format],\n            },\n        );\n        surface.set_shader(\n            &self.device,\n            self.format,\n            &self.uniform_bind_group_layout,\n            self.shader,\n        );\n    }\n\n    pub fn update_texture(&mut self, id: epaint::TextureId, image_delta: &epaint::ImageDelta) {\n        let width = image_delta.image.width() as u32;\n        let height = image_delta.image.height() as u32;\n\n        let size = wgpu::Extent3d {\n            width,\n            height,\n            depth_or_array_layers: 1,\n        };\n\n        let data_color32 = match &image_delta.image {\n            epaint::ImageData::Color(image) => {\n                assert_eq!(\n                    width as usize * height as usize,\n                    image.pixels.len(),\n                    \"Mismatch between texture size and texel count\"\n                );\n                Cow::Borrowed(&image.pixels)\n            }\n        };\n        let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());\n\n        let queue_write_data_to_texture = |texture, origin| {\n            self.queue.write_texture(\n                wgpu::TexelCopyTextureInfo {\n                    texture,\n                    mip_level: 0,\n                    origin,\n                    aspect: wgpu::TextureAspect::All,\n                },\n                data_bytes,\n                wgpu::TexelCopyBufferLayout {\n                    offset: 0,\n                    bytes_per_row: Some(4 * width),\n                    rows_per_image: Some(height),\n                },\n                size,\n            );\n        };\n\n        if let Some(pos) = image_delta.pos {\n            // update the existing texture\n            let (texture, _bind_group) = self\n                .textures\n                .get(&id)\n                .expect(\"Tried to update a texture that has not been allocated yet.\");\n            let origin = wgpu::Origin3d {\n                x: pos[0] as u32,\n                y: pos[1] as u32,\n                z: 0,\n            };\n            queue_write_data_to_texture(\n                texture.as_ref().expect(\"Tried to update user texture.\"),\n                origin,\n            );\n        } else {\n            // allocate a new texture\n            // Use same label for all resources associated with this texture id (no point in retyping the type)\n            let label_str = format!(\"texture_{id:?}\");\n            let label = Some(label_str.as_str());\n            let texture = {\n                self.device.create_texture(&wgpu::TextureDescriptor {\n                    label,\n                    size,\n                    mip_level_count: 1,\n                    sample_count: 1,\n                    dimension: wgpu::TextureDimension::D2,\n                    format: wgpu::TextureFormat::Rgba8UnormSrgb, // Minspec for wgpu WebGL emulation is WebGL2, so this should always be supported.\n                    usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,\n                    view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb],\n                })\n            };\n            let sampler = self\n                .samplers\n                .entry(image_delta.options)\n                .or_insert_with(|| Self::create_sampler(image_delta.options, &self.device));\n\n            let view = texture.create_view(&wgpu::TextureViewDescriptor::default());\n            let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {\n                label,\n                layout: &self.texture_bind_group_layout,\n                entries: &[\n                    wgpu::BindGroupEntry {\n                        binding: 0,\n                        resource: wgpu::BindingResource::TextureView(&view),\n                    },\n                    wgpu::BindGroupEntry {\n                        binding: 1,\n                        resource: wgpu::BindingResource::Sampler(sampler),\n                    },\n                ],\n            });\n            let origin = wgpu::Origin3d::ZERO;\n            queue_write_data_to_texture(&texture, origin);\n            self.textures.insert(id, (Some(texture), bind_group));\n        };\n    }\n\n    pub fn update_buffers(\n        &mut self,\n        paint_jobs: &[epaint::ClippedPrimitive],\n        screen_descriptor: &ScreenDescriptor,\n    ) {\n        let screen_size_in_points = screen_descriptor.screen_size_in_points();\n\n        let uniform_buffer_content = UniformBuffer {\n            screen_size_in_points,\n            _padding: Default::default(),\n        };\n        if uniform_buffer_content != self.previous_uniform_buffer_content {\n            self.queue.write_buffer(\n                &self.uniform_buffer,\n                0,\n                bytemuck::cast_slice(&[uniform_buffer_content]),\n            );\n            self.previous_uniform_buffer_content = uniform_buffer_content;\n        }\n\n        // Determine how many vertices & indices need to be rendered, and gather prepare callbacks\n        // let mut callbacks = Vec::new();\n        let (vertex_count, index_count) =\n            paint_jobs.iter().fold((0, 0), |acc, clipped_primitive| {\n                if let Primitive::Mesh(mesh) = &clipped_primitive.primitive {\n                    (acc.0 + mesh.vertices.len(), acc.1 + mesh.indices.len())\n                } else {\n                    acc\n                }\n            });\n\n        if index_count > 0 {\n            self.index_buffer.slices.clear();\n\n            let required_index_buffer_size = (std::mem::size_of::<u32>() * index_count) as u64;\n            if self.index_buffer.capacity < required_index_buffer_size {\n                // Resize index buffer if needed.\n                self.index_buffer.capacity =\n                    (self.index_buffer.capacity * 2).at_least(required_index_buffer_size);\n                self.index_buffer.buffer =\n                    Self::create_index_buffer(&self.device, self.index_buffer.capacity);\n            }\n\n            let index_buffer_staging = self.queue.write_buffer_with(\n                &self.index_buffer.buffer,\n                0,\n                NonZeroU64::new(required_index_buffer_size).expect(\"valid index buffer size\"),\n            );\n\n            let Some(mut index_buffer_staging) = index_buffer_staging else {\n                panic!(\n                    \"Failed to create staging buffer for index data. Index count: {index_count}. Required index buffer size: {required_index_buffer_size}. Actual size {} and capacity: {} (bytes)\",\n                    self.index_buffer.buffer.size(),\n                    self.index_buffer.capacity\n                );\n            };\n\n            let mut index_offset = 0;\n            for epaint::ClippedPrimitive { primitive, .. } in paint_jobs {\n                if let Primitive::Mesh(mesh) = primitive {\n                    let size = mesh.indices.len() * std::mem::size_of::<u32>();\n                    index_buffer_staging\n                        .slice(index_offset..(size + index_offset))\n                        .copy_from_slice(bytemuck::cast_slice(&mesh.indices));\n                    self.index_buffer\n                        .slices\n                        .push(index_offset..(size + index_offset));\n                    index_offset += size;\n                }\n            }\n        }\n\n        if vertex_count > 0 {\n            self.vertex_buffer.slices.clear();\n\n            let required_vertex_buffer_size = (std::mem::size_of::<Vertex>() * vertex_count) as u64;\n            if self.vertex_buffer.capacity < required_vertex_buffer_size {\n                // Resize vertex buffer if needed.\n                self.vertex_buffer.capacity =\n                    (self.vertex_buffer.capacity * 2).at_least(required_vertex_buffer_size);\n                self.vertex_buffer.buffer =\n                    Self::create_vertex_buffer(&self.device, self.vertex_buffer.capacity);\n            }\n\n            let vertex_buffer_staging = self.queue.write_buffer_with(\n                &self.vertex_buffer.buffer,\n                0,\n                NonZeroU64::new(required_vertex_buffer_size).expect(\"valid vertex buffer size\"),\n            );\n\n            let Some(mut vertex_buffer_staging) = vertex_buffer_staging else {\n                panic!(\n                    \"Failed to create staging buffer for vertex data. Vertex count: {vertex_count}. Required vertex buffer size: {required_vertex_buffer_size}. Actual size {} and capacity: {} (bytes)\",\n                    self.vertex_buffer.buffer.size(),\n                    self.vertex_buffer.capacity\n                );\n            };\n\n            let mut vertex_offset = 0;\n            for epaint::ClippedPrimitive { primitive, .. } in paint_jobs {\n                if let Primitive::Mesh(mesh) = primitive {\n                    let size = mesh.vertices.len() * std::mem::size_of::<Vertex>();\n                    vertex_buffer_staging\n                        .slice(vertex_offset..(size + vertex_offset))\n                        .copy_from_slice(bytemuck::cast_slice(&mesh.vertices));\n                    self.vertex_buffer\n                        .slices\n                        .push(vertex_offset..(size + vertex_offset));\n                    vertex_offset += size;\n                }\n            }\n        }\n    }\n\n    pub fn render<'rp>(\n        &'rp self,\n        render_pass: &mut wgpu::RenderPass<'rp>,\n        paint_jobs: &'rp [epaint::ClippedPrimitive],\n        screen_descriptor: &ScreenDescriptor,\n    ) {\n        let pixels_per_point = screen_descriptor.pixels_per_point;\n        let size_in_pixels = screen_descriptor.size_in_pixels;\n\n        render_pass.set_scissor_rect(0, 0, size_in_pixels[0], size_in_pixels[1]);\n        render_pass.set_viewport(\n            0.0,\n            0.0,\n            size_in_pixels[0] as f32,\n            size_in_pixels[1] as f32,\n            0.0,\n            1.0,\n        );\n        render_pass.set_pipeline(&self.pipeline);\n        render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);\n\n        let mut index_buffer_slices = self.index_buffer.slices.iter();\n        let mut vertex_buffer_slices = self.vertex_buffer.slices.iter();\n\n        for epaint::ClippedPrimitive {\n            clip_rect,\n            primitive,\n        } in paint_jobs\n        {\n            let rect = ScissorRect::new(clip_rect, pixels_per_point, size_in_pixels);\n\n            if rect.width == 0 || rect.height == 0 {\n                // Skip rendering zero-sized clip areas.\n                if let Primitive::Mesh(_) = primitive {\n                    // If this is a mesh, we need to advance the index and vertex buffer iterators:\n                    index_buffer_slices.next();\n                    vertex_buffer_slices.next();\n                }\n                continue;\n            }\n\n            render_pass.set_scissor_rect(rect.x, rect.y, rect.width, rect.height);\n\n            if let Primitive::Mesh(mesh) = primitive {\n                // These expects should be valid because update_buffers inserts a slice for every\n                // primitive\n                let index_buffer_slice = index_buffer_slices\n                    .next()\n                    .expect(\"valid index buffer slice\");\n                let vertex_buffer_slice = vertex_buffer_slices\n                    .next()\n                    .expect(\"valid vertex buffer slice\");\n\n                if let Some((_texture, bind_group)) = self.textures.get(&mesh.texture_id) {\n                    render_pass.set_bind_group(1, bind_group, &[]);\n                    render_pass.set_index_buffer(\n                        self.index_buffer\n                            .buffer\n                            .slice(index_buffer_slice.start as u64..index_buffer_slice.end as u64),\n                        wgpu::IndexFormat::Uint32,\n                    );\n                    render_pass.set_vertex_buffer(\n                        0,\n                        self.vertex_buffer.buffer.slice(\n                            vertex_buffer_slice.start as u64..vertex_buffer_slice.end as u64,\n                        ),\n                    );\n                    render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);\n                } else {\n                    tracing::warn!(\"Missing texture: {:?}\", mesh.texture_id);\n                }\n            }\n        }\n\n        render_pass.set_scissor_rect(0, 0, size_in_pixels[0], size_in_pixels[1]);\n    }\n\n    fn create_sampler(\n        options: epaint::textures::TextureOptions,\n        device: &wgpu::Device,\n    ) -> wgpu::Sampler {\n        let mag_filter = match options.magnification {\n            epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest,\n            epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear,\n        };\n        let min_filter = match options.minification {\n            epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest,\n            epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear,\n        };\n        let address_mode = match options.wrap_mode {\n            epaint::textures::TextureWrapMode::ClampToEdge => wgpu::AddressMode::ClampToEdge,\n            epaint::textures::TextureWrapMode::Repeat => wgpu::AddressMode::Repeat,\n            epaint::textures::TextureWrapMode::MirroredRepeat => wgpu::AddressMode::MirrorRepeat,\n        };\n        device.create_sampler(&wgpu::SamplerDescriptor {\n            label: Some(&format!(\n                \"gui sampler (mag: {mag_filter:?}, min {min_filter:?})\"\n            )),\n            mag_filter,\n            min_filter,\n            address_mode_u: address_mode,\n            address_mode_v: address_mode,\n            ..Default::default()\n        })\n    }\n}\n\n/// Uniform buffer used when rendering.\n#[derive(Default, Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]\n#[repr(C)]\nstruct UniformBuffer {\n    screen_size_in_points: [f32; 2],\n    // Uniform buffers need to be at least 16 bytes in WebGL.\n    // See https://github.com/gfx-rs/wgpu/issues/2072\n    _padding: [u32; 2],\n}\n\nimpl PartialEq for UniformBuffer {\n    fn eq(&self, other: &Self) -> bool {\n        self.screen_size_in_points == other.screen_size_in_points\n    }\n}\n\n/// Information about the screen used for rendering.\npub struct ScreenDescriptor {\n    /// Size of the window in physical pixels.\n    pub size_in_pixels: [u32; 2],\n\n    /// HiDPI scale factor (pixels per point).\n    pub pixels_per_point: f32,\n}\n\nimpl ScreenDescriptor {\n    /// size in \"logical\" points\n    fn screen_size_in_points(&self) -> [f32; 2] {\n        [\n            self.size_in_pixels[0] as f32 / self.pixels_per_point,\n            self.size_in_pixels[1] as f32 / self.pixels_per_point,\n        ]\n    }\n}\n\n/// A Rect in physical pixel space, used for setting clipping rectangles.\nstruct ScissorRect {\n    x: u32,\n    y: u32,\n    width: u32,\n    height: u32,\n}\n\nimpl ScissorRect {\n    fn new(clip_rect: &epaint::Rect, pixels_per_point: f32, target_size: [u32; 2]) -> Self {\n        // Transform clip rect to physical pixels:\n        let clip_min_x = pixels_per_point * clip_rect.min.x;\n        let clip_min_y = pixels_per_point * clip_rect.min.y;\n        let clip_max_x = pixels_per_point * clip_rect.max.x;\n        let clip_max_y = pixels_per_point * clip_rect.max.y;\n\n        // Round to integer:\n        let clip_min_x = clip_min_x.round() as u32;\n        let clip_min_y = clip_min_y.round() as u32;\n        let clip_max_x = clip_max_x.round() as u32;\n        let clip_max_y = clip_max_y.round() as u32;\n\n        // Clamp:\n        let clip_min_x = clip_min_x.clamp(0, target_size[0]);\n        let clip_min_y = clip_min_y.clamp(0, target_size[1]);\n        let clip_max_x = clip_max_x.clamp(clip_min_x, target_size[0]);\n        let clip_max_y = clip_max_y.clamp(clip_min_y, target_size[1]);\n\n        Self {\n            x: clip_min_x,\n            y: clip_min_y,\n            width: clip_max_x - clip_min_x,\n            height: clip_max_y - clip_min_y,\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/renderer/shader.rs",
    "content": "use serde::{Deserialize, Serialize};\nuse thiserror::Error;\n\n#[derive(Error, Debug)]\n#[must_use]\n#[error(\"failed to parse `VideoFilter`\")]\npub struct ParseShaderError;\n\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum Shader {\n    Default,\n    #[default]\n    CrtEasymode,\n}\n\nimpl Shader {\n    pub const fn as_slice() -> &'static [Self] {\n        &[Self::Default, Self::CrtEasymode]\n    }\n}\n\nimpl AsRef<str> for Shader {\n    fn as_ref(&self) -> &str {\n        match self {\n            Self::Default => \"Default\",\n            Self::CrtEasymode => \"CRT Easymode\",\n        }\n    }\n}\n\nimpl TryFrom<usize> for Shader {\n    type Error = ParseShaderError;\n\n    fn try_from(value: usize) -> Result<Self, Self::Error> {\n        Ok(match value {\n            0 => Self::Default,\n            1 => Self::CrtEasymode,\n            _ => return Err(ParseShaderError),\n        })\n    }\n}\n\n#[derive(Debug)]\n#[must_use]\npub struct Resources {\n    pub view: wgpu::TextureView,\n    pub texture_bind_group: wgpu::BindGroup,\n    pub render_pipeline: wgpu::RenderPipeline,\n}\n\nimpl Resources {\n    pub fn new(\n        device: &wgpu::Device,\n        format: wgpu::TextureFormat,\n        view: wgpu::TextureView,\n        uniform_bind_group_layout: &wgpu::BindGroupLayout,\n        shader: Shader,\n    ) -> Option<Self> {\n        let shader_module_desc = match shader {\n            Shader::Default => return None,\n            Shader::CrtEasymode => wgpu::include_wgsl!(concat!(\n                env!(\"CARGO_MANIFEST_DIR\"),\n                \"/shaders/crt-easymode.wgsl\"\n            )),\n        };\n        let shader_module = device.create_shader_module(shader_module_desc);\n\n        let texture_bind_group_layout =\n            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {\n                label: Some(\"bind group layout\"),\n                entries: &[\n                    wgpu::BindGroupLayoutEntry {\n                        binding: 0,\n                        visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,\n                        ty: wgpu::BindingType::Texture {\n                            sample_type: wgpu::TextureSampleType::Float { filterable: true },\n                            multisampled: false,\n                            view_dimension: wgpu::TextureViewDimension::D2,\n                        },\n                        count: None,\n                    },\n                    wgpu::BindGroupLayoutEntry {\n                        binding: 1,\n                        visibility: wgpu::ShaderStages::FRAGMENT,\n                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),\n                        count: None,\n                    },\n                ],\n            });\n        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {\n            label: Some(\"sampler\"),\n            address_mode_u: wgpu::AddressMode::ClampToEdge,\n            address_mode_v: wgpu::AddressMode::ClampToEdge,\n            address_mode_w: wgpu::AddressMode::ClampToEdge,\n            mag_filter: wgpu::FilterMode::Nearest,\n            min_filter: wgpu::FilterMode::Nearest,\n            mipmap_filter: wgpu::MipmapFilterMode::Nearest,\n            ..Default::default()\n        });\n        let texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {\n            label: Some(\"nes frame bind group\"),\n            layout: &texture_bind_group_layout,\n            entries: &[\n                wgpu::BindGroupEntry {\n                    binding: 0,\n                    resource: wgpu::BindingResource::TextureView(&view),\n                },\n                wgpu::BindGroupEntry {\n                    binding: 1,\n                    resource: wgpu::BindingResource::Sampler(&sampler),\n                },\n            ],\n        });\n        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {\n            label: Some(\"shader pipeline layout\"),\n            bind_group_layouts: &[\n                Some(uniform_bind_group_layout),\n                Some(&texture_bind_group_layout),\n            ],\n            immediate_size: 0,\n        });\n\n        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {\n            label: Some(\"render pipeline\"),\n            layout: Some(&pipeline_layout),\n            vertex: wgpu::VertexState {\n                module: &shader_module,\n                entry_point: Some(\"vs_main\"),\n                buffers: &[],\n                compilation_options: wgpu::PipelineCompilationOptions::default(),\n            },\n            fragment: Some(wgpu::FragmentState {\n                module: &shader_module,\n                entry_point: Some(\"fs_main\"),\n                targets: &[Some(wgpu::ColorTargetState {\n                    format,\n                    blend: None,\n                    write_mask: wgpu::ColorWrites::ALL,\n                })],\n                compilation_options: wgpu::PipelineCompilationOptions::default(),\n            }),\n            primitive: wgpu::PrimitiveState::default(),\n            depth_stencil: None,\n            multisample: wgpu::MultisampleState::default(),\n            multiview_mask: None,\n            cache: None,\n        });\n\n        Some(Self {\n            view,\n            texture_bind_group,\n            render_pipeline,\n        })\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/renderer/texture.rs",
    "content": "use crate::nes::renderer::painter::RenderState;\nuse egui::{TextureId, Vec2, load::SizedTexture};\n\n#[derive(Debug)]\n#[must_use]\npub struct Texture {\n    pub label: Option<&'static str>,\n    pub id: TextureId,\n    pub texture: wgpu::Texture,\n    pub size: Vec2,\n    pub output_size: Vec2,\n    pub view: wgpu::TextureView,\n    pub aspect_ratio: f32,\n}\n\nimpl Texture {\n    pub fn new(\n        render_state: &mut RenderState,\n        size: Vec2,\n        aspect_ratio: f32,\n        label: Option<&'static str>,\n    ) -> Self {\n        let max_texture_side = render_state.max_texture_side() as f32;\n        let texture = render_state\n            .device\n            .create_texture(&wgpu::TextureDescriptor {\n                label,\n                size: wgpu::Extent3d {\n                    width: size.x.min(max_texture_side) as u32,\n                    height: size.y.min(max_texture_side) as u32,\n                    depth_or_array_layers: 1,\n                },\n                mip_level_count: 1,\n                sample_count: 1,\n                dimension: wgpu::TextureDimension::D2,\n                format: wgpu::TextureFormat::Rgba8UnormSrgb,\n                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,\n                view_formats: &[],\n            });\n\n        let view = texture.create_view(&wgpu::TextureViewDescriptor {\n            label,\n            dimension: Some(wgpu::TextureViewDimension::D2),\n            ..Default::default()\n        });\n        let sampler_descriptor = wgpu::SamplerDescriptor {\n            label: Some(\"sampler\"),\n            address_mode_u: wgpu::AddressMode::ClampToEdge,\n            address_mode_v: wgpu::AddressMode::ClampToEdge,\n            address_mode_w: wgpu::AddressMode::ClampToEdge,\n            mag_filter: wgpu::FilterMode::Nearest,\n            min_filter: wgpu::FilterMode::Nearest,\n            mipmap_filter: wgpu::MipmapFilterMode::Nearest,\n            ..Default::default()\n        };\n        let id = render_state.register_texture(label, &view, sampler_descriptor);\n\n        Self {\n            label,\n            texture,\n            size,\n            output_size: Vec2 {\n                x: size.x * aspect_ratio,\n                y: size.y,\n            },\n            view,\n            aspect_ratio,\n            id,\n        }\n    }\n\n    pub fn resize(&mut self, render_state: &mut RenderState, size: Vec2, aspect_ratio: f32) {\n        *self = Self::new(render_state, size, aspect_ratio, self.label);\n    }\n\n    pub fn sized(&self) -> SizedTexture {\n        SizedTexture::new(self.id, self.output_size)\n    }\n\n    pub fn update(&self, queue: &wgpu::Queue, bytes: &[u8]) {\n        self.update_partial(queue, bytes, Vec2::ZERO, self.size);\n    }\n\n    pub fn update_partial(&self, queue: &wgpu::Queue, bytes: &[u8], origin: Vec2, size: Vec2) {\n        let size = wgpu::Extent3d {\n            width: size.x as u32,\n            height: size.y as u32,\n            depth_or_array_layers: 1,\n        };\n        queue.write_texture(\n            wgpu::TexelCopyTextureInfo {\n                aspect: wgpu::TextureAspect::All,\n                texture: &self.texture,\n                mip_level: 0,\n                origin: wgpu::Origin3d {\n                    x: origin.x as u32,\n                    y: origin.y as u32,\n                    z: 0,\n                },\n            },\n            bytes,\n            wgpu::TexelCopyBufferLayout {\n                offset: 0,\n                bytes_per_row: Some(4 * size.width),\n                rows_per_image: Some(size.height),\n            },\n            size,\n        );\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/renderer.rs",
    "content": "use crate::{\n    feature,\n    nes::{\n        RunState,\n        config::Config,\n        event::{EmulationEvent, NesEvent, NesEventProxy, RendererEvent, UiEvent},\n        input::Gamepads,\n        renderer::{\n            clipboard::Clipboard,\n            event::translate_cursor,\n            gui::{Gui, MessageType},\n            painter::Painter,\n        },\n    },\n    platform::{self, BuilderExt, Initialize},\n    thread,\n};\nuse anyhow::Context;\nuse crossbeam::channel::{self, Receiver};\nuse egui::{\n    DeferredViewportUiCallback, OutputCommand, Vec2, ViewportBuilder, ViewportClass,\n    ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo,\n    ViewportOutput, WindowLevel, ahash::HashMap,\n};\nuse parking_lot::Mutex;\nuse std::{\n    cell::RefCell,\n    collections::{BTreeMap, hash_map::Entry},\n    rc::Rc,\n    sync::Arc,\n};\nuse tetanes_core::{\n    fs, ppu,\n    time::{Duration, Instant},\n    video::Frame,\n};\nuse thingbuf::{\n    Recycle,\n    mpsc::{blocking::Receiver as BufReceiver, errors::TryRecvError},\n};\nuse tracing::{debug, error, info, trace};\nuse winit::{\n    dpi::{LogicalSize, PhysicalPosition, PhysicalSize},\n    event_loop::ActiveEventLoop,\n    window::{CursorGrabMode, Theme, Window, WindowButtons, WindowId},\n};\n\npub mod clipboard;\npub mod event;\npub mod gui;\npub mod painter;\npub mod shader;\npub mod texture;\n\npub const OVERSCAN_TRIM: usize = (4 * ppu::size::WIDTH * 8) as usize;\n\n#[derive(Debug)]\n#[must_use]\npub struct FrameRecycle;\n\nimpl Recycle<Frame> for FrameRecycle {\n    fn new_element(&self) -> Frame {\n        Frame::new()\n    }\n\n    fn recycle(&self, _frame: &mut Frame) {}\n}\n\n#[must_use]\npub struct State {\n    pub(crate) viewports: ViewportIdMap<Viewport>,\n    viewport_from_window: HashMap<WindowId, ViewportId>,\n    pub(crate) focused: Option<ViewportId>,\n    pointer_touch_id: Option<u64>,\n    pub(crate) start_time: Instant,\n}\n\nimpl std::fmt::Debug for State {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"State\")\n            .field(\"viewports\", &self.viewports)\n            .field(\"viewport_from_window\", &self.viewport_from_window)\n            .field(\"focused\", &self.focused)\n            .field(\"start_time\", &self.focused)\n            .finish()\n    }\n}\n\n#[derive(Default)]\n#[must_use]\npub struct Viewport {\n    pub(crate) ids: ViewportIdPair,\n    class: ViewportClass,\n    builder: ViewportBuilder,\n    pub(crate) info: ViewportInfo,\n    pub(crate) raw_input: egui::RawInput,\n    pub(crate) viewport_ui_cb: Option<Arc<DeferredViewportUiCallback>>,\n    pub(crate) window: Option<Arc<Window>>,\n    pub(crate) occluded: bool,\n    cursor_icon: Option<egui::CursorIcon>,\n    cursor_pos: Option<egui::Pos2>,\n    pub(crate) clipboard: Clipboard,\n}\n\nimpl std::fmt::Debug for Viewport {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Viewport\")\n            .field(\"ids\", &self.ids)\n            // .field(\"class\", &self.class) // why not?!\n            .field(\"builder\", &self.builder)\n            .field(\"info\", &self.info)\n            .field(\"raw_input\", &self.raw_input)\n            .field(\n                \"viewport_ui_cb\",\n                &self.viewport_ui_cb.as_ref().map(|_| \"fn\"),\n            )\n            .field(\"window\", &self.window)\n            .field(\"occluded\", &self.occluded)\n            .field(\"cursor_icon\", &self.cursor_icon)\n            .field(\"clipboard\", &self.clipboard)\n            .finish_non_exhaustive()\n    }\n}\n\n#[must_use]\npub struct Renderer {\n    pub(crate) state: Rc<RefCell<State>>,\n    painter: Rc<RefCell<Painter>>,\n    frame_rx: BufReceiver<Frame, FrameRecycle>,\n    tx: NesEventProxy,\n    redraw_tx: Arc<Mutex<NesEventProxy>>,\n    pub(crate) gui: Rc<RefCell<Gui>>,\n    pub(crate) ctx: egui::Context,\n    #[cfg(not(target_arch = \"wasm32\"))]\n    accesskit: accesskit_winit::Adapter,\n    first_frame: bool,\n    pub(crate) last_save_time: Instant,\n    zoom_changed: bool,\n    resize_texture: bool,\n}\n\nimpl std::fmt::Debug for Renderer {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Renderer\")\n            .field(\"state\", &self.state)\n            .field(\"painter\", &self.painter)\n            .field(\"frame_rx\", &self.frame_rx)\n            .field(\"tx\", &self.tx)\n            .field(\"redraw_tx\", &self.redraw_tx)\n            .field(\"gui\", &self.gui)\n            .field(\"ctx\", &self.ctx)\n            .field(\"first_frame\", &self.first_frame)\n            .field(\"last_save_time\", &self.last_save_time)\n            .field(\"zoom_changed\", &self.zoom_changed)\n            .field(\"resize_texture\", &self.resize_texture)\n            .finish_non_exhaustive()\n    }\n}\n\n#[must_use]\npub struct Resources {\n    pub(crate) ctx: egui::Context,\n    pub(crate) window: Arc<Window>,\n    pub(crate) painter: Painter,\n}\n\nimpl std::fmt::Debug for Resources {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Resources\")\n            .field(\"window\", &self.window)\n            .finish_non_exhaustive()\n    }\n}\n\nimpl Renderer {\n    /// Initializes the renderer in a platform-agnostic way.\n    pub fn new(\n        _event_loop: &ActiveEventLoop,\n        tx: NesEventProxy,\n        resources: Resources,\n        frame_rx: BufReceiver<Frame, FrameRecycle>,\n        cfg: &Config,\n    ) -> anyhow::Result<Self> {\n        let Resources {\n            ctx,\n            window,\n            mut painter,\n        } = resources;\n\n        let redraw_tx = Arc::new(Mutex::new(tx.clone()));\n        ctx.set_request_repaint_callback({\n            let redraw_tx = redraw_tx.clone();\n            move |info| {\n                // IMPORTANT: Wasm can't block\n                if let Some(tx) = redraw_tx.try_lock() {\n                    tx.event(RendererEvent::RequestRedraw {\n                        viewport_id: info.viewport_id,\n                        when: Instant::now() + info.delay,\n                    });\n                } else {\n                    tracing::warn!(\"failed to lock redraw_tx\");\n                }\n            }\n        });\n\n        // Platforms like wasm don't easily support multiple viewports, and even if it could spawn\n        // multiple canvases for each viewport, the async requirements of wgpu would make it\n        // impossible to render until wasm-bindgen gets proper non-blocking async/await support.\n        if feature!(OsViewports) {\n            ctx.set_embed_viewports(cfg.renderer.embed_viewports);\n        }\n\n        let mut viewport_from_window = HashMap::default();\n        viewport_from_window.insert(window.id(), ViewportId::ROOT);\n\n        let mut viewports = ViewportIdMap::default();\n        let mut viewport = Viewport {\n            ids: ViewportIdPair::ROOT,\n            class: ViewportClass::Root,\n            info: ViewportInfo {\n                title: Some(Config::WINDOW_TITLE.to_string()),\n                ..Default::default()\n            },\n            window: Some(Arc::clone(&window)),\n            ..Default::default()\n        };\n        Viewport::update_info(&mut viewport.info, &ctx, &window);\n        viewports.insert(viewport.ids.this, viewport);\n\n        painter.set_shader(cfg.renderer.shader);\n        let render_state = painter.render_state_mut();\n        let Some(render_state) = render_state else {\n            anyhow::bail!(\"painter state is not initialized yet\");\n        };\n\n        let gui = Rc::new(RefCell::new(Gui::new(\n            ctx.clone(),\n            tx.clone(),\n            render_state,\n            cfg,\n        )));\n\n        if let Err(err) = Self::load(&ctx, cfg) {\n            tracing::error!(\"{err:?}\");\n        }\n\n        // Must be done before the window is shown for the first time, which is true here, because\n        // first_frame is set to true below\n        #[cfg(not(target_arch = \"wasm32\"))]\n        let accesskit = {\n            accesskit_winit::Adapter::with_event_loop_proxy(\n                _event_loop,\n                &window,\n                tx.inner().clone(),\n            )\n        };\n\n        let state = State {\n            viewports,\n            viewport_from_window,\n            focused: None,\n            pointer_touch_id: None,\n            start_time: Instant::now(),\n        };\n\n        Ok(Self {\n            state: Rc::new(RefCell::new(state)),\n            painter: Rc::new(RefCell::new(painter)),\n            frame_rx,\n            tx,\n            redraw_tx,\n            ctx,\n            #[cfg(not(target_arch = \"wasm32\"))]\n            accesskit,\n            gui,\n            first_frame: true,\n            last_save_time: Instant::now(),\n            zoom_changed: false,\n            resize_texture: false,\n        })\n    }\n\n    pub fn destroy(&mut self) {\n        let State {\n            viewports,\n            viewport_from_window,\n            focused,\n            ..\n        } = &mut *self.state.borrow_mut();\n        viewports.clear();\n        viewport_from_window.clear();\n        *focused = None;\n        self.painter.borrow_mut().destroy();\n    }\n\n    pub fn root_window_id(&self) -> Option<WindowId> {\n        self.window_id_for_viewport(ViewportId::ROOT)\n    }\n\n    pub fn window_id_for_viewport(&self, viewport_id: ViewportId) -> Option<WindowId> {\n        let state = self.state.borrow();\n        state\n            .viewports\n            .get(&viewport_id)\n            .and_then(|viewport| viewport.window.as_ref())\n            .map(|window| window.id())\n    }\n\n    pub fn viewport_id_for_window(&self, window_id: WindowId) -> Option<ViewportId> {\n        let state = self.state.borrow();\n        state\n            .viewport_from_window\n            .get(&window_id)\n            .and_then(|id| state.viewports.get(id).map(|viewport| viewport.ids.this))\n    }\n\n    pub fn root_viewport<R>(&self, reader: impl FnOnce(&Viewport) -> R) -> Option<R> {\n        let state = self.state.borrow();\n        state.viewports.get(&ViewportId::ROOT).map(reader)\n    }\n\n    pub fn root_window(&self) -> Option<Arc<Window>> {\n        self.root_viewport(|viewport| viewport.window.clone())\n            .flatten()\n    }\n\n    pub fn all_window_ids(&self) -> Vec<WindowId> {\n        let state = self.state.borrow();\n        state\n            .viewports\n            .values()\n            .filter_map(|viewport| viewport.window.as_ref().map(|w| w.id()))\n            .collect()\n    }\n\n    pub fn window(&self, window_id: WindowId) -> Option<Arc<Window>> {\n        let state = self.state.borrow();\n        state.viewport_from_window.get(&window_id).and_then(|id| {\n            state\n                .viewports\n                .get(id)\n                .and_then(|viewport| viewport.window.clone())\n        })\n    }\n\n    pub fn window_size(&self, cfg: &Config) -> Vec2 {\n        self.window_size_for_scale(cfg, cfg.renderer.scale)\n    }\n\n    pub fn window_size_for_scale(&self, cfg: &Config, scale: f32) -> Vec2 {\n        let gui = self.gui.borrow();\n        let aspect_ratio = gui.aspect_ratio(cfg);\n        let mut window_size = cfg.window_size_for_scale(aspect_ratio, scale);\n        window_size.y += gui.menu_height;\n        window_size\n    }\n\n    pub fn find_max_scale_for_width(&self, width: f32, cfg: &Config) -> f32 {\n        let mut scale = cfg.renderer.scale;\n        let mut size = self.window_size_for_scale(cfg, scale);\n        while scale > 1.0 && size.x > width {\n            scale -= 1.0;\n            size = self.window_size_for_scale(cfg, scale);\n        }\n        scale\n    }\n\n    pub fn all_viewports_occluded(&self) -> bool {\n        let state = self.state.borrow();\n        state.viewports.values().all(|viewport| viewport.occluded)\n    }\n\n    pub fn inner_size(&self) -> Option<PhysicalSize<u32>> {\n        self.root_window().map(|win| win.inner_size())\n    }\n\n    pub fn fullscreen(&self) -> bool {\n        self.root_window()\n            .map(|win| win.fullscreen().is_some())\n            .unwrap_or(false)\n    }\n\n    pub fn set_fullscreen(&mut self, fullscreen: bool, embed_viewports: bool) {\n        if feature!(OsViewports) {\n            self.ctx.set_embed_viewports(fullscreen || embed_viewports);\n        }\n        self.ctx\n            .send_viewport_cmd_to(ViewportId::ROOT, ViewportCommand::Focus);\n        self.ctx\n            .send_viewport_cmd_to(ViewportId::ROOT, ViewportCommand::Fullscreen(fullscreen));\n    }\n\n    pub fn set_embed_viewports(&mut self, embed: bool) {\n        self.ctx.set_embed_viewports(embed);\n    }\n\n    pub fn set_always_on_top(&mut self, always_on_top: bool) {\n        let state = self.state.borrow();\n        for viewport_id in state.viewports.keys() {\n            self.ctx.send_viewport_cmd_to(\n                *viewport_id,\n                ViewportCommand::WindowLevel(if always_on_top {\n                    WindowLevel::AlwaysOnTop\n                } else {\n                    WindowLevel::Normal\n                }),\n            );\n        }\n    }\n\n    fn initialize_all_windows(&mut self, event_loop: &ActiveEventLoop) {\n        if self.ctx.embed_viewports() {\n            return;\n        }\n\n        let State {\n            viewports,\n            viewport_from_window,\n            ..\n        } = &mut *self.state.borrow_mut();\n        for viewport in viewports.values_mut() {\n            viewport.initialize_window(\n                self.tx.clone(),\n                event_loop,\n                &self.ctx,\n                viewport_from_window,\n                &self.painter,\n            );\n        }\n    }\n\n    pub fn rom_loaded(&self) -> bool {\n        self.gui.borrow().loaded_rom.is_some()\n    }\n\n    pub fn add_message<S>(&mut self, ty: MessageType, text: S)\n    where\n        S: Into<String>,\n    {\n        self.gui.borrow_mut().add_message(ty, text);\n        self.ctx.request_repaint();\n    }\n\n    pub fn on_error(&mut self, err: anyhow::Error) {\n        error!(\"error: {err:?}\");\n        self.tx\n            .event(EmulationEvent::RunState(RunState::AutoPaused));\n        self.gui.borrow_mut().error = Some(err.to_string());\n    }\n\n    pub fn load(ctx: &egui::Context, cfg: &Config) -> anyhow::Result<()> {\n        let path = Config::default_config_dir().join(\"gui.dat\");\n        if fs::exists(&path) {\n            let data = fs::load_raw(path).context(\"failed to load gui memory\")?;\n            let config = bincode::config::legacy();\n            let (memory, _) = bincode::serde::decode_from_slice(&data, config)\n                .context(\"failed to deserialize gui memory\")?;\n            ctx.memory_mut(|mem| {\n                *mem = memory;\n            });\n            info!(\"Loaded UI state\");\n        }\n        ctx.memory_mut(|mem| {\n            mem.options.zoom_factor = cfg.renderer.zoom;\n        });\n        Ok(())\n    }\n\n    pub fn auto_save(&mut self, cfg: &Config) -> anyhow::Result<()> {\n        let time_since_last_save = Instant::now() - self.last_save_time;\n        if time_since_last_save > Duration::from_secs(10) {\n            self.save(cfg)?;\n        }\n        Ok(())\n    }\n\n    pub fn save(&mut self, cfg: &Config) -> anyhow::Result<()> {\n        cfg.save()?;\n\n        let path = Config::default_config_dir().join(\"gui.dat\");\n        self.ctx.memory(|mem| {\n            let config = bincode::config::legacy();\n            let data = bincode::serde::encode_to_vec(mem, config)\n                .context(\"failed to serialize gui memory\")?;\n            fs::save_raw(path, &data).context(\"failed to save gui memory\")\n        })?;\n        self.last_save_time = Instant::now();\n\n        Ok(())\n    }\n\n    /// Request renderer resources (creating gui context, window, painter, etc).\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if any resources can't be created correctly or `init_running` has already\n    /// been called.\n    pub fn request_resources(\n        event_loop: &ActiveEventLoop,\n        tx: &NesEventProxy,\n        cfg: &Config,\n    ) -> anyhow::Result<(egui::Context, Arc<Window>, Receiver<Painter>)> {\n        let ctx = egui::Context::default();\n\n        let window_size = cfg.window_size(cfg.deck.region.aspect_ratio());\n        let mut builder = egui::ViewportBuilder::default()\n            .with_title(Config::WINDOW_TITLE)\n            .with_visible(false) // hide until first frame is rendered. required by AccessKit\n            .with_fullscreen(cfg.renderer.fullscreen)\n            .with_active(true)\n            .with_resizable(true)\n            .with_inner_size(window_size)\n            .with_min_inner_size(Vec2::new(ppu::size::WIDTH as f32, ppu::size::HEIGHT as f32));\n        if cfg.renderer.always_on_top {\n            builder = builder.with_always_on_top();\n        }\n        let window = Arc::new(Self::create_window(&ctx, event_loop, builder)?);\n        window.set_theme(Some(if cfg.renderer.dark_theme {\n            Theme::Dark\n        } else {\n            Theme::Light\n        }));\n\n        let (painter_tx, painter_rx) = channel::bounded(1);\n        thread::spawn({\n            let window = Arc::clone(&window);\n            let event_tx = tx.clone();\n            async move {\n                debug!(\"creating painter...\");\n                match Self::create_painter(window).await {\n                    Ok(painter) => {\n                        painter_tx.send(painter).expect(\"failed to send painter\");\n                        event_tx.event(RendererEvent::ResourcesReady);\n                    }\n                    Err(err) => {\n                        error!(\"failed to create painter: {err:?}\");\n                        event_tx.event(UiEvent::Terminate);\n                    }\n                }\n            }\n        });\n\n        Ok((ctx, window, painter_rx))\n    }\n\n    pub fn create_window(\n        ctx: &egui::Context,\n        event_loop: &ActiveEventLoop,\n        builder: ViewportBuilder,\n    ) -> anyhow::Result<Window> {\n        let native_pixels_per_point = event_loop\n            .primary_monitor()\n            .or_else(|| event_loop.available_monitors().next())\n            .map_or_else(\n                || {\n                    tracing::debug!(\n                        \"Failed to find a monitor - assuming native_pixels_per_point of 1.0\"\n                    );\n                    1.0\n                },\n                |m| m.scale_factor() as f32,\n            );\n        let zoom_factor = ctx.zoom_factor();\n        let pixels_per_point = zoom_factor * native_pixels_per_point;\n\n        let ViewportBuilder {\n            title,\n            position,\n            inner_size,\n            min_inner_size,\n            max_inner_size,\n            fullscreen,\n            maximized,\n            resizable,\n            icon,\n            active,\n            visible,\n            window_level,\n            ..\n        } = builder;\n\n        let title = title.unwrap_or_else(|| Config::WINDOW_TITLE.to_owned());\n        let mut window_attrs = Window::default_attributes()\n            .with_title(title.clone())\n            .with_resizable(resizable.unwrap_or(true))\n            .with_visible(visible.unwrap_or(true))\n            .with_maximized(maximized.unwrap_or(false))\n            .with_window_level(match window_level.unwrap_or_default() {\n                WindowLevel::AlwaysOnBottom => winit::window::WindowLevel::AlwaysOnBottom,\n                WindowLevel::AlwaysOnTop => winit::window::WindowLevel::AlwaysOnTop,\n                WindowLevel::Normal => winit::window::WindowLevel::Normal,\n            })\n            .with_fullscreen(\n                fullscreen.and_then(|e| e.then_some(winit::window::Fullscreen::Borderless(None))),\n            )\n            .with_active(active.unwrap_or(true))\n            .with_platform(&title);\n\n        if let Some(size) = inner_size {\n            window_attrs = window_attrs.with_inner_size(PhysicalSize::new(\n                pixels_per_point * size.x,\n                pixels_per_point * size.y,\n            ));\n        }\n\n        if let Some(size) = min_inner_size {\n            window_attrs = window_attrs.with_min_inner_size(PhysicalSize::new(\n                pixels_per_point * size.x,\n                pixels_per_point * size.y,\n            ));\n        }\n\n        if let Some(size) = max_inner_size {\n            window_attrs = window_attrs.with_max_inner_size(PhysicalSize::new(\n                pixels_per_point * size.x,\n                pixels_per_point * size.y,\n            ));\n        }\n\n        if let Some(pos) = position {\n            window_attrs = window_attrs.with_position(PhysicalPosition::new(\n                pixels_per_point * pos.x,\n                pixels_per_point * pos.y,\n            ));\n        }\n\n        if let Some(icon) = icon {\n            let winit_icon = gui::lib::to_winit_icon(&icon);\n            window_attrs = window_attrs.with_window_icon(winit_icon);\n        }\n\n        let window = event_loop.create_window(window_attrs)?;\n\n        if let Some(size) = inner_size\n            && window\n                .request_inner_size(PhysicalSize::new(\n                    pixels_per_point * size.x,\n                    pixels_per_point * size.y,\n                ))\n                .is_some()\n        {\n            debug!(\"Failed to set window size\");\n        }\n        if let Some(size) = min_inner_size {\n            window.set_min_inner_size(Some(PhysicalSize::new(\n                pixels_per_point * size.x,\n                pixels_per_point * size.y,\n            )));\n        }\n\n        debug!(\"created new window: {:?}\", window.id());\n\n        Ok(window)\n    }\n\n    pub async fn create_painter(window: Arc<Window>) -> anyhow::Result<Painter> {\n        // The window must be ready with a non-zero size before `Painter::set_window` is called,\n        // otherwise the wgpu surface won't be configured correctly.\n        let start = Instant::now();\n        loop {\n            let size = window.inner_size();\n            if size.width > 0 && size.height > 0 {\n                break;\n            }\n            thread::sleep(Duration::from_millis(10)).await;\n        }\n        debug!(\n            \"waited {:.02}s for window creation\",\n            start.elapsed().as_secs_f32()\n        );\n\n        let mut painter = Painter::new();\n        painter\n            .set_window(ViewportId::ROOT, Some(Arc::clone(&window)))\n            .await?;\n\n        Ok(painter)\n    }\n\n    pub fn recreate_window(&mut self, event_loop: &ActiveEventLoop) {\n        if self.ctx.embed_viewports() {\n            return;\n        }\n\n        let State {\n            viewports,\n            viewport_from_window,\n            ..\n        } = &mut *self.state.borrow_mut();\n        let builder = viewports\n            .get(&ViewportId::ROOT)\n            .map(|viewport| viewport.builder.clone())\n            .unwrap_or_default();\n        let viewport = Self::create_or_update_viewport(\n            &self.ctx,\n            viewports,\n            ViewportIdPair::ROOT,\n            ViewportClass::Root,\n            builder,\n            None,\n        );\n\n        viewport.initialize_window(\n            self.tx.clone(),\n            event_loop,\n            &self.ctx,\n            viewport_from_window,\n            &self.painter,\n        );\n    }\n\n    pub fn drop_window(&mut self) -> anyhow::Result<()> {\n        if self.ctx.embed_viewports() {\n            return Ok(());\n        }\n        let mut state = self.state.borrow_mut();\n        state.viewports.remove(&ViewportId::ROOT);\n        Renderer::set_painter_window(\n            self.tx.clone(),\n            Rc::clone(&self.painter),\n            ViewportId::ROOT,\n            None,\n        );\n        Ok(())\n    }\n\n    fn set_painter_window(\n        tx: NesEventProxy,\n        painter: Rc<RefCell<Painter>>,\n        viewport_id: ViewportId,\n        window: Option<Arc<Window>>,\n    ) {\n        // This is fine because we won't be yielding. Native platforms call `block_on` and\n        // wasm is single-threaded with `spawn_local` and runs on the next microtick.\n        #[allow(clippy::await_holding_refcell_ref)]\n        thread::spawn(async move {\n            if let Err(err) = painter.borrow_mut().set_window(viewport_id, window).await {\n                error!(\"failed to set painter window on viewport id {viewport_id:?}: {err:?}\");\n                tx.event(NesEvent::Ui(UiEvent::Terminate));\n            }\n        });\n    }\n\n    fn create_or_update_viewport<'a>(\n        ctx: &egui::Context,\n        viewports: &'a mut ViewportIdMap<Viewport>,\n        ids: ViewportIdPair,\n        class: ViewportClass,\n        mut builder: ViewportBuilder,\n        viewport_ui_cb: Option<Arc<DeferredViewportUiCallback>>,\n    ) -> &'a mut Viewport {\n        if builder.icon.is_none() {\n            builder.icon = viewports\n                .get_mut(&ids.parent)\n                .and_then(|viewport| viewport.builder.icon.clone());\n        }\n\n        match viewports.entry(ids.this) {\n            Entry::Vacant(entry) => entry.insert(Viewport {\n                ids,\n                class,\n                builder,\n                viewport_ui_cb,\n                ..Default::default()\n            }),\n            Entry::Occupied(mut entry) => {\n                let viewport = entry.get_mut();\n                viewport.class = class;\n                viewport.ids.parent = ids.parent;\n                viewport.info.parent = Some(ids.parent);\n                viewport.viewport_ui_cb = viewport_ui_cb;\n\n                let (delta_commands, recreate) = viewport.builder.patch(builder);\n                if recreate {\n                    viewport.window = None;\n                    viewport.raw_input = Default::default();\n                    viewport.cursor_icon = None;\n                } else if let Some(window) = &viewport.window {\n                    Self::process_viewport_commands(\n                        ctx,\n                        &mut viewport.info,\n                        delta_commands,\n                        window,\n                    );\n                }\n\n                entry.into_mut()\n            }\n        }\n    }\n\n    pub fn handle_platform_output(viewport: &mut Viewport, platform_output: egui::PlatformOutput) {\n        let egui::PlatformOutput {\n            cursor_icon,\n            commands,\n            ..\n        } = platform_output;\n\n        viewport.set_cursor(cursor_icon);\n\n        for command in commands {\n            match command {\n                OutputCommand::OpenUrl(open_url) => Self::open_url_in_browser(&open_url.url),\n                OutputCommand::CopyText(copied_text) => {\n                    if !copied_text.is_empty() {\n                        viewport.clipboard.set(copied_text);\n                    }\n                }\n                OutputCommand::CopyImage(_) => (),\n            }\n        }\n    }\n\n    fn open_url_in_browser(url: &str) {\n        if let Err(err) = webbrowser::open(url) {\n            tracing::warn!(\"failed to open url: {err:?}\");\n        }\n    }\n\n    fn handle_viewport_output(\n        ctx: &egui::Context,\n        viewports: &mut ViewportIdMap<Viewport>,\n        outputs: BTreeMap<ViewportId, ViewportOutput>,\n    ) {\n        for (id, output) in outputs {\n            let ids = ViewportIdPair::from_self_and_parent(id, output.parent);\n            let viewport = Self::create_or_update_viewport(\n                ctx,\n                viewports,\n                ids,\n                output.class,\n                output.builder,\n                output.viewport_ui_cb,\n            );\n            if let Some(window) = viewport.window.as_ref() {\n                Self::process_viewport_commands(ctx, &mut viewport.info, output.commands, window);\n            }\n        }\n    }\n\n    fn process_viewport_commands(\n        ctx: &egui::Context,\n        info: &mut ViewportInfo,\n        commands: impl IntoIterator<Item = ViewportCommand>,\n        window: &Window,\n    ) {\n        let pixels_per_point = gui::lib::pixels_per_point(ctx, window);\n        for command in commands {\n            match command {\n                ViewportCommand::Close => {\n                    info.events.push(egui::ViewportEvent::Close);\n                }\n                ViewportCommand::StartDrag => {\n                    // If `.has_focus()` is not checked on x11 the input will be permanently taken until the app is killed!\n                    if window.has_focus()\n                        && let Err(err) = window.drag_window()\n                    {\n                        tracing::warn!(\"{command:?}: {err}\");\n                    }\n                }\n                ViewportCommand::InnerSize(size) => {\n                    let width_px = pixels_per_point * size.x.max(1.0);\n                    let height_px = pixels_per_point * size.y.max(1.0);\n                    let requested_size = PhysicalSize::new(width_px, height_px);\n                    if let Some(_returned_inner_size) = window.request_inner_size(requested_size) {\n                        // On platforms where the size is entirely controlled by the user the\n                        // applied size will be returned immediately, resize event in such case\n                        // may not be generated.\n                        // e.g. Linux\n\n                        // On platforms where resizing is disallowed by the windowing system, the current\n                        // inner size is returned immediately, and the user one is ignored.\n                        // e.g. Android, iOS, …\n\n                        // However, comparing the results is prone to numerical errors\n                        // because the linux backend converts physical to logical and back again.\n                        // So let's just assume it worked:\n\n                        info.inner_rect = gui::lib::inner_rect_in_points(window, pixels_per_point);\n                        info.outer_rect = gui::lib::outer_rect_in_points(window, pixels_per_point);\n                    } else {\n                        // e.g. macOS, Windows\n                        // The request went to the display system,\n                        // and the actual size will be delivered later with the [`WindowEvent::Resized`].\n                    }\n                }\n                ViewportCommand::BeginResize(direction) => {\n                    use egui::viewport::ResizeDirection as EguiResizeDirection;\n                    use winit::window::ResizeDirection;\n\n                    if let Err(err) = window.drag_resize_window(match direction {\n                        EguiResizeDirection::North => ResizeDirection::North,\n                        EguiResizeDirection::South => ResizeDirection::South,\n                        EguiResizeDirection::East => ResizeDirection::East,\n                        EguiResizeDirection::West => ResizeDirection::West,\n                        EguiResizeDirection::NorthEast => ResizeDirection::NorthEast,\n                        EguiResizeDirection::SouthEast => ResizeDirection::SouthEast,\n                        EguiResizeDirection::NorthWest => ResizeDirection::NorthWest,\n                        EguiResizeDirection::SouthWest => ResizeDirection::SouthWest,\n                    }) {\n                        tracing::warn!(\"{command:?}: {err}\");\n                    }\n                }\n                ViewportCommand::Title(title) => {\n                    window.set_title(&title);\n                }\n                ViewportCommand::Transparent(v) => window.set_transparent(v),\n                ViewportCommand::Visible(v) => window.set_visible(v),\n                ViewportCommand::OuterPosition(pos) => {\n                    window.set_outer_position(PhysicalPosition::new(\n                        pixels_per_point * pos.x,\n                        pixels_per_point * pos.y,\n                    ));\n                }\n                ViewportCommand::MinInnerSize(s) => {\n                    window.set_min_inner_size((s.is_finite() && s != Vec2::ZERO).then_some(\n                        PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),\n                    ));\n                }\n                ViewportCommand::MaxInnerSize(s) => {\n                    window.set_max_inner_size((s.is_finite() && s != Vec2::INFINITY).then_some(\n                        PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),\n                    ));\n                }\n                ViewportCommand::ResizeIncrements(s) => {\n                    window.set_resize_increments(s.map(|s| {\n                        PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y)\n                    }));\n                }\n                ViewportCommand::Resizable(v) => window.set_resizable(v),\n                ViewportCommand::EnableButtons {\n                    close,\n                    minimized,\n                    maximize,\n                } => window.set_enabled_buttons(\n                    if close {\n                        WindowButtons::CLOSE\n                    } else {\n                        WindowButtons::empty()\n                    } | if minimized {\n                        WindowButtons::MINIMIZE\n                    } else {\n                        WindowButtons::empty()\n                    } | if maximize {\n                        WindowButtons::MAXIMIZE\n                    } else {\n                        WindowButtons::empty()\n                    },\n                ),\n                ViewportCommand::Minimized(v) => {\n                    window.set_minimized(v);\n                    info.minimized = Some(v);\n                }\n                ViewportCommand::Maximized(v) => {\n                    window.set_maximized(v);\n                    info.maximized = Some(v);\n                }\n                ViewportCommand::Fullscreen(v) => {\n                    window.set_fullscreen(v.then_some(winit::window::Fullscreen::Borderless(None)));\n                    info.fullscreen = Some(v);\n                }\n                ViewportCommand::Decorations(v) => window.set_decorations(v),\n                ViewportCommand::WindowLevel(l) => {\n                    use egui::viewport::WindowLevel as EguiWindowLevel;\n                    use winit::window::WindowLevel;\n                    window.set_window_level(match l {\n                        EguiWindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom,\n                        EguiWindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop,\n                        EguiWindowLevel::Normal => WindowLevel::Normal,\n                    });\n                }\n                ViewportCommand::Icon(icon) => {\n                    let winit_icon = icon.and_then(|icon| gui::lib::to_winit_icon(&icon));\n                    window.set_window_icon(winit_icon);\n                }\n                ViewportCommand::IMERect(rect) => {\n                    window.set_ime_cursor_area(\n                        PhysicalPosition::new(\n                            pixels_per_point * rect.min.x,\n                            pixels_per_point * rect.min.y,\n                        ),\n                        PhysicalSize::new(\n                            pixels_per_point * rect.size().x,\n                            pixels_per_point * rect.size().y,\n                        ),\n                    );\n                }\n                ViewportCommand::IMEAllowed(v) => window.set_ime_allowed(v),\n                ViewportCommand::IMEPurpose(p) => window.set_ime_purpose(match p {\n                    egui::viewport::IMEPurpose::Password => winit::window::ImePurpose::Password,\n                    egui::viewport::IMEPurpose::Terminal => winit::window::ImePurpose::Terminal,\n                    egui::viewport::IMEPurpose::Normal => winit::window::ImePurpose::Normal,\n                }),\n                ViewportCommand::Focus if !window.has_focus() => {\n                    window.focus_window();\n                }\n                ViewportCommand::RequestUserAttention(a) => {\n                    window.request_user_attention(match a {\n                        egui::UserAttentionType::Reset => None,\n                        egui::UserAttentionType::Critical => {\n                            Some(winit::window::UserAttentionType::Critical)\n                        }\n                        egui::UserAttentionType::Informational => {\n                            Some(winit::window::UserAttentionType::Informational)\n                        }\n                    });\n                }\n                ViewportCommand::SetTheme(t) => window.set_theme(match t {\n                    egui::SystemTheme::Light => Some(winit::window::Theme::Light),\n                    egui::SystemTheme::Dark => Some(winit::window::Theme::Dark),\n                    egui::SystemTheme::SystemDefault => None,\n                }),\n                ViewportCommand::ContentProtected(v) => window.set_content_protected(v),\n                ViewportCommand::CursorPosition(pos) => {\n                    if let Err(err) = window.set_cursor_position(PhysicalPosition::new(\n                        pixels_per_point * pos.x,\n                        pixels_per_point * pos.y,\n                    )) {\n                        tracing::warn!(\"{command:?}: {err}\");\n                    }\n                }\n                ViewportCommand::CursorGrab(o) => {\n                    if let Err(err) = window.set_cursor_grab(match o {\n                        egui::viewport::CursorGrab::None => CursorGrabMode::None,\n                        egui::viewport::CursorGrab::Confined => CursorGrabMode::Confined,\n                        egui::viewport::CursorGrab::Locked => CursorGrabMode::Locked,\n                    }) {\n                        tracing::warn!(\"{command:?}: {err}\");\n                    }\n                }\n                ViewportCommand::CursorVisible(v) => window.set_cursor_visible(v),\n                ViewportCommand::MousePassthrough(passthrough) => {\n                    if let Err(err) = window.set_cursor_hittest(!passthrough) {\n                        tracing::warn!(\"{command:?}: {err}\");\n                    }\n                }\n                _ => (),\n            }\n        }\n    }\n\n    /// Request redraw.\n    pub fn redraw(\n        &mut self,\n        window_id: WindowId,\n        event_loop: &ActiveEventLoop,\n        gamepads: &mut Gamepads,\n        cfg: &mut Config,\n    ) -> anyhow::Result<()> {\n        if self.first_frame {\n            self.initialize()?;\n            self.resize_window(cfg);\n        }\n        self.initialize_all_windows(event_loop);\n\n        if self.all_viewports_occluded() {\n            return Ok(());\n        }\n\n        let Some(viewport_id) = self.viewport_id_for_window(window_id) else {\n            return Ok(());\n        };\n\n        self.handle_resize(viewport_id, cfg);\n\n        let (viewport_ui_cb, viewport_info, raw_input) = {\n            let State {\n                viewports,\n                start_time,\n                ..\n            } = &mut *self.state.borrow_mut();\n\n            let Some(viewport) = viewports.get_mut(&viewport_id) else {\n                return Ok(());\n            };\n            let Some(window) = &viewport.window else {\n                return Ok(());\n            };\n            // Always render the root viewport unless all viewports are occluded to ensure deferred\n            // viewports correctly get Config and Gamepads updates.\n            if viewport.occluded && viewport_id != ViewportId::ROOT {\n                return Ok(());\n            }\n\n            Viewport::update_info(&mut viewport.info, &self.ctx, window);\n\n            let viewport_ui_cb = viewport.viewport_ui_cb.clone();\n\n            // On Windows, a minimized window will have 0 width and height.\n            // See: https://github.com/rust-windowing/winit/issues/208\n            // This solves an issue where egui window positions would be changed when minimizing on Windows.\n            let screen_size_in_pixels = gui::lib::screen_size_in_pixels(window);\n            let screen_size_in_points =\n                screen_size_in_pixels / gui::lib::pixels_per_point(&self.ctx, window);\n\n            let viewport_info = viewport.info.clone();\n            let mut raw_input = viewport.raw_input.take();\n            raw_input.time = Some(start_time.elapsed().as_secs_f64());\n            raw_input.screen_rect = (screen_size_in_points.x > 0.0\n                && screen_size_in_points.y > 0.0)\n                .then(|| egui::Rect::from_min_size(egui::Pos2::ZERO, screen_size_in_points));\n            raw_input.viewport_id = viewport_id;\n            raw_input\n                .viewports\n                .entry(viewport_id)\n                .or_default()\n                .native_pixels_per_point = Some(window.scale_factor() as f32);\n\n            (viewport_ui_cb, viewport_info, raw_input)\n        };\n\n        // Copy NES frame buffer before drawing UI because a UI interaction might cause a texture\n        // resize tied to a configuration change.\n        if viewport_id == ViewportId::ROOT\n            && let Some(render_state) = &self.painter.borrow().render_state()\n        {\n            let mut frame_buffer = self.frame_rx.try_recv_ref();\n            while self.frame_rx.remaining() < 2 {\n                trace!(\"skipping frame\");\n                frame_buffer = self.frame_rx.try_recv_ref();\n            }\n            match frame_buffer {\n                Ok(frame_buffer) => {\n                    let gui = self.gui.borrow_mut();\n                    let is_ntsc = gui.loaded_region().unwrap_or(cfg.deck.region).is_ntsc();\n                    gui.nes_texture.update(\n                        &render_state.queue,\n                        if cfg.renderer.hide_overscan && is_ntsc {\n                            &frame_buffer[OVERSCAN_TRIM..frame_buffer.len() - OVERSCAN_TRIM]\n                        } else {\n                            &frame_buffer\n                        },\n                    );\n                }\n                Err(TryRecvError::Closed) => {\n                    error!(\"frame channel closed unexpectedly, exiting\");\n                    event_loop.exit();\n                    return Ok(());\n                }\n                // Empty frames are fine as we may repaint more often than 60fps due to\n                // UI interactions with keyboard/mouse\n                _ => (),\n            }\n        }\n\n        // Mutated by accesskit below on platforms that support it\n        #[allow(unused_mut)]\n        let mut output = self.ctx.run_ui(raw_input, |ui| {\n            match &viewport_ui_cb {\n                Some(viewport_ui_cb) => viewport_ui_cb(ui),\n                None => self.gui.borrow_mut().ui(ui, cfg, gamepads),\n            }\n            self.gui\n                .borrow_mut()\n                .show_viewport_info_window(&self.ctx, viewport_id, &viewport_info);\n        });\n\n        {\n            let State {\n                viewports,\n                viewport_from_window,\n                ..\n            } = &mut *self.state.borrow_mut();\n\n            let Some(viewport) = viewports.get_mut(&viewport_id) else {\n                return Ok(());\n            };\n\n            viewport.info.events.clear(); // they should have been processed\n\n            let Viewport {\n                window: Some(window),\n                ..\n            } = viewport\n            else {\n                return Ok(());\n            };\n\n            let clipped_primitives = self.ctx.tessellate(output.shapes, output.pixels_per_point);\n\n            window.pre_present_notify();\n            self.painter.borrow_mut().paint(\n                viewport_id,\n                output.pixels_per_point,\n                &clipped_primitives,\n                &output.textures_delta,\n            );\n\n            if std::mem::take(&mut self.first_frame) {\n                window.set_visible(true);\n            }\n\n            let active_viewports_ids = output\n                .viewport_output\n                .keys()\n                .copied()\n                .collect::<ViewportIdSet>();\n\n            if feature!(ScreenReader) && self.ctx.options(|o| o.screen_reader) {\n                platform::speak_text(&output.platform_output.events_description());\n            }\n            #[cfg(not(target_arch = \"wasm32\"))]\n            if let Some(update) = output.platform_output.accesskit_update.take() {\n                tracing::trace!(\"update accesskit: {update:?}\");\n                self.accesskit.update_if_active(|| update);\n            }\n\n            Self::handle_platform_output(viewport, output.platform_output);\n            Self::handle_viewport_output(&self.ctx, viewports, output.viewport_output);\n            if std::mem::take(&mut self.zoom_changed) {\n                cfg.renderer.zoom = self.ctx.zoom_factor();\n            }\n\n            // Prune dead viewports\n            viewports.retain(|id, _| active_viewports_ids.contains(id));\n            viewport_from_window.retain(|_, id| active_viewports_ids.contains(id));\n            self.painter\n                .borrow_mut()\n                .retain_surfaces(&active_viewports_ids);\n        }\n\n        if let Err(err) = self.auto_save(cfg) {\n            error!(\"failed to auto save UI state: {err:?}\");\n        }\n\n        Ok(())\n    }\n\n    fn handle_resize(&mut self, viewport_id: ViewportId, cfg: &Config) {\n        if viewport_id == ViewportId::ROOT && self.resize_texture {\n            tracing::debug!(\"resizing window and texture\");\n\n            self.tx.event(EmulationEvent::RequestFrame);\n            self.resize_window(cfg);\n\n            if let Some(render_state) = self.painter.borrow_mut().render_state_mut() {\n                let texture_size = cfg.texture_size();\n                let mut gui = self.gui.borrow_mut();\n                let aspect_ratio = gui.aspect_ratio(cfg);\n                gui.nes_texture\n                    .resize(render_state, texture_size, aspect_ratio);\n            }\n            self.resize_texture = false;\n        }\n    }\n\n    fn resize_window(&self, cfg: &Config) {\n        if !self.fullscreen() {\n            let desired_window_size = self.window_size(cfg);\n\n            // On some platforms, e.g. wasm, window width is constrained by the\n            // viewport width, so try to find the max scale that will fit\n            if feature!(ConstrainedViewport) {\n                let res = platform::renderer::constrain_window_to_viewport(\n                    self,\n                    desired_window_size.x,\n                    cfg,\n                );\n                if res.consumed {\n                    return;\n                }\n            }\n\n            if let Some(window) = self.root_window() {\n                tracing::debug!(\"resizing window: {desired_window_size:?}\");\n\n                let _ = window.request_inner_size(LogicalSize::new(\n                    desired_window_size.x,\n                    desired_window_size.y,\n                ));\n            }\n        }\n    }\n}\n\nimpl Viewport {\n    pub fn initialize_window(\n        &mut self,\n        tx: NesEventProxy,\n        event_loop: &ActiveEventLoop,\n        ctx: &egui::Context,\n        viewport_from_window: &mut HashMap<WindowId, ViewportId>,\n        painter: &Rc<RefCell<Painter>>,\n    ) {\n        if self.window.is_some() {\n            return;\n        }\n\n        let viewport_id = self.ids.this;\n\n        match Renderer::create_window(ctx, event_loop, self.builder.clone()) {\n            Ok(window) => {\n                viewport_from_window.insert(window.id(), viewport_id);\n                let window = Arc::new(window);\n\n                Renderer::set_painter_window(\n                    tx,\n                    Rc::clone(painter),\n                    viewport_id,\n                    Some(Arc::clone(&window)),\n                );\n\n                debug!(\n                    \"created new viewport window: {:?} ({:?})\",\n                    self.builder.title,\n                    window.id()\n                );\n\n                self.info.title = self.builder.title.clone();\n                self.info.minimized = window.is_minimized();\n                self.info.maximized = Some(window.is_maximized());\n                self.window = Some(window);\n            }\n            Err(err) => error!(\"Failed to create window: {err}\"),\n        }\n    }\n\n    pub fn update_info(info: &mut ViewportInfo, ctx: &egui::Context, window: &Window) {\n        let pixels_per_point = gui::lib::pixels_per_point(ctx, window);\n        let has_position = window.is_minimized().is_none_or(|minimized| !minimized);\n\n        let inner_rect = has_position\n            .then(|| gui::lib::inner_rect_in_points(window, pixels_per_point))\n            .flatten();\n        let outer_rect = has_position\n            .then(|| gui::lib::outer_rect_in_points(window, pixels_per_point))\n            .flatten();\n\n        let monitor_size = window.current_monitor().map(|monitor| {\n            let size = monitor.size().to_logical::<f32>(pixels_per_point.into());\n            egui::vec2(size.width, size.height)\n        });\n\n        let title = window.title();\n        if !title.is_empty() {\n            info.title = Some(title);\n        }\n        info.native_pixels_per_point = Some(window.scale_factor() as f32);\n\n        info.monitor_size = monitor_size;\n        info.inner_rect = inner_rect;\n        info.outer_rect = outer_rect;\n\n        if !cfg!(target_os = \"macos\") {\n            // Asking for minimized/maximized state at runtime can lead to a deadlock on macOS\n            info.maximized = Some(window.is_maximized());\n            info.minimized = Some(window.is_minimized().unwrap_or(false));\n        }\n\n        info.fullscreen = Some(window.fullscreen().is_some());\n        info.focused = Some(window.has_focus());\n    }\n\n    fn set_cursor(&mut self, cursor_icon: egui::CursorIcon) {\n        if self.cursor_icon == Some(cursor_icon) {\n            // Prevent flickering near frame boundary when Windows OS tries to control cursor icon for window resizing.\n            // On other platforms: just early-out to save CPU.\n            return;\n        }\n        let Some(window) = &self.window else {\n            return;\n        };\n\n        let is_pointer_in_window = self.cursor_pos.is_some();\n        if is_pointer_in_window {\n            self.cursor_icon = Some(cursor_icon);\n\n            if let Some(cursor) = translate_cursor(cursor_icon) {\n                window.set_cursor_visible(true);\n                window.set_cursor(cursor);\n            } else {\n                window.set_cursor_visible(false);\n            }\n        } else {\n            self.cursor_icon = None;\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes/rom.rs",
    "content": "#[derive(Clone, PartialEq)]\npub struct RomData(pub Vec<u8>);\n\nimpl std::fmt::Debug for RomData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"RomData({} bytes)\", self.0.len())\n    }\n}\n\nimpl AsRef<[u8]> for RomData {\n    fn as_ref(&self) -> &[u8] {\n        &self.0\n    }\n}\n\n#[derive(Copy, Clone)]\n#[must_use]\npub struct RomAsset {\n    pub name: &'static str,\n    pub authors: &'static str,\n    pub description: &'static str,\n    pub source: &'static str,\n    pub data_fn: &'static dyn Fn() -> Vec<u8>,\n}\n\nimpl std::fmt::Debug for RomAsset {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"RomAsset\")\n            .field(\"name\", &self.name)\n            .field(\"authors\", &self.authors)\n            .field(\"description\", &self.description)\n            .field(\"source\", &self.source)\n            .finish_non_exhaustive()\n    }\n}\n\nimpl RomAsset {\n    pub const fn new(\n        name: &'static str,\n        authors: &'static str,\n        description: &'static str,\n        source: &'static str,\n        data_fn: &'static dyn Fn() -> Vec<u8>,\n    ) -> Self {\n        Self {\n            name,\n            authors,\n            description,\n            source,\n            data_fn,\n        }\n    }\n\n    pub fn data(&self) -> RomData {\n        RomData((self.data_fn)())\n    }\n}\n\nmacro_rules! rom_assets {\n    ($(($name:expr, $filename:expr, $authors:expr, $description:expr, $source:expr$(,)?)),*$(,)?) => {[$(\n        {\n            fn data_fn() -> Vec<u8> {\n                include_bytes!(concat!(\n                    env!(\"CARGO_MANIFEST_DIR\"),\n                    \"/assets/roms/\",\n                    $filename\n                )).to_vec()\n            }\n            RomAsset::new(\n                $name,\n                $authors,\n                $description,\n                $source,\n                &data_fn\n            )\n        },\n    )*]};\n}\n\npub const HOMEBREW_ROMS: [RomAsset; 18] = rom_assets!(\n    (\n        \"Alter Ego\",\n        \"alter_ego.nes\",\n        \"Denis Grachev, Shiru, and Kulor\",\n        \"The game is a logic platformer. You control a hero and his alter ego. You have to switch between them to clear a level. It is a bit similar to Binary Land.\",\n        \"https://shiru.untergrund.net/software.shtml\",\n    ),\n    (\n        \"AO Demo\",\n        \"ao_demo.nes\",\n        \"Second Dimension\",\n        \"If you like puzzle games and you're up for a challenge, AO is the game for you. The objective is to roll the brick around the game board and drop it through the goal pit.\n\nIt sounds easy, right? You'll have to avoid falling off of the edge while maneuvering around tight areas. Do you think you can make it through all the levels before your score reaches zero? If you run into a wall and get stuck, you won't lose when the timer runs out, so you can still finish all of the levels.\n\nAO features 30 challenging puzzles for 1 or 2 players.\",\n        \"https://www.second-dimension.com/catalog/ao\",\n    ),\n    (\n        \"Assimilate\",\n        \"assimilate.nes\",\n        \"Nessylum Games\",\n        \"Ever wanted to anal probe someone? Well the wait is over!\n\nAssimilate is the game you and your dopey little friends have been waiting for. Join the super-ship Ossan for twenty plus levels of zombifying, brainwashing, human-dominating excitement. Will you succeed in conquering the entire human race? Or will you cry yourself to sleep after watching Ossan explode in a fiery ball of humiliating destruction?\nWhat happens is up to you.\n\nBegin assimilation.\",\n        \"https://forums.nesdev.org/viewtopic.php?t=7087&hilit=assimilate\"\n    ),\n    (\n        \"Blade Buster\",\n        \"blade_buster.nes\",\n        \"High Level Challenge\",\n        \"You have 2 minutes in a caravan format to compete the score at 5 minutes. It has become a game with a sense of exhilaration that has good old shoot before being shot playing style.\",\n        \"http://hlc6502.web.fc2.com/Bbuster.htm\",\n    ),\n    (\n        \"Cheril the Goddess\",\n        \"cheril_the_goddess.nes\",\n        \"The Mojon Twins\",\n        \"You control Cheril. There’s plenty of things to do, and you’ll need special objects. Cheril can only carry ONE object at a time, so if you try to get a new one while you are carring an object, you will drop the one you carry in the place of the one you take.\n\nTo get/interchange objects press DOWN. Besides, you’ll have to give those objects a use. To use an object, just walk to the place you want to use it and press DOWN. For Cheril to fly, she has to «push». You can make her «push» by pressing UP.\n\nBut beware: «pushing» for too long drains Cheril’s vitality, so you have to do this carefully. Timing short «pushes» can make her gain momentum. Take your time to master the technique, it won’t take long.\n\nCheril can also fire power balls. You can fire pressing FIRE. Power balls also drain Cheril, but think that enemies do quite a lot of harm. Use this feature wisely, and just when needed!\n\nCheril can also regain her vitality by means of a special action we won’t reveal ‘cause it’s easy enough to find out!\n\nYou can choose the difficulty level, but the easiest level won’t show the real ending.\",\n        \"https://forums.nesdev.org/viewtopic.php?t=15367\",\n    ),\n    (\n        \"Data Man Demo\",\n        \"data_man_demo.nes\",\n        \"Darkbits (Olof Naessen, Per Larsson, Ted Steen)\",\n        \"Do you have what it takes to save the System?\n\nThe Master and his evil minions have invaded the system and will not stop until every piece of data is corrupted. It’s up to you to save it before time runs out and the system crashes!\n\nIt won’t be easy. You’ll have to protect the Central Processing Unit from hordes of attacking minions and ultimately from the Master himself. Face it alone, or with a friend!\",\n        \"https://datamangame.com/\",\n    ),\n    (\n        \"Dushlan\",\n        \"dushlan.nes\",\n        \"Peter McQuillan\",\n        \"The game itself is based on the classic Tetris, but with a few twists on the\ngame and some extra features that are not commonly available like ghost (where\nyou can see where your piece would go if you dropped it) and save (where you\ncan swap a piece in play for later usage).\",\n        \"https://github.com/soiaf/Dushlan\",\n    ),\n    (\n        \"From Below\",\n        \"from_below.nes\",\n        \"Matt Hughson\",\n        \"FROM BELOW is a falling block puzzle game featuring:\n\nSoft Drops Hard Drops Wall Kicks T-Spins Lock Delay 3 modes of play: Kraken\nBattle Mode\n\nThe signature mode of FROM BELOW. Battle the Kraken by clear lines across the\nonslaught of attacking Kraken Tentacles. The Tentacles push more blocks onto the\nscreen every few seconds, forcing to act quickly, and strategize on an every\nchanging board.\",\n        \"https://mhughson.itch.io/from-below/devlog/212679/vs-system-beta-0100\",\n    ),\n    (\n        \"Lan Master\",\n        \"lan_master.nes\",\n        \"Shiru\",\n        \"Lan Master is a puzzle game for NES, inspired by the game NetWalk. The goal is to connect all of the computers on each level. Rotate the pieces and connect the wires before the timer runs out!\n\nThere are fifty levels in all, with increasing difficulty. A password system is included so whether you’re playing in an emulator or on a console, you can come back later and pick up where you left off.\",\n        \"https://shiru.untergrund.net/software.shtml\",\n    ),\n    (\n        \"Lawn Mower\",\n        \"lawn_mower.nes\",\n        \"Shiru\",\n        \"The goal of this game is to mow all the grass before you run out of gas. Collect gas cans to keep yourself from running on empty.\",\n        \"https://shiru.untergrund.net/software.shtml\",\n    ),\n    (\n        \"Mad Wizard\",\n        \"mad_wizard.nes\",\n        \"Sly Dog Studios\",\n        \"Take an adventure in the world of Candelabra!\n\nThe evil summoner Amondus from The Order of the Talon has taken over Prim, Hekl's once happy homeland. And nothing drives a wizard more crazy than having their territory trampled on!\n\nCan you help Hekl defeat the enemies that Amondus has populated throughout the landscape? To do so, you will need to master the art of levitation, find magic spells that will assist you in reaching new areas, and upgrade your weapons. All of these will be necessary in order to give Hekl the power he needs to restore peace to Prim. Do you have what it takes? If you dare, venture into this, the first installment of the Candelabra series!\",\n        \"The goal of this game is to mow all the grass before you run out of gas. Collect gas cans to keep yourself from running on empty.\",\n    ),\n    (\n        \"Micro Knight\",\n        \"micro_knight.nes\",\n        \"SDM\",\n        \"\",\n        \"https://forums.nesdev.org/viewtopic.php?t=13450\",\n    ),\n    (\n        \"Nebs 'n Debs Demo\",\n        \"nebs_n_debs_demo.nes\",\n        \"Dullahan Software\",\n        \"Run, jump, and dash your way through 12 levels as you search for the missing parts of Debs's ship to escape the hostile alien planet Vespasian 7MV! Nebs 'n Debs runs on the same type of game cartridge as the original Super Mario Bros.\",\n        \"https://dullahan-software.itch.io/nebs-n-debs\",\n    ),\n    (\n        \"Owlia\",\n        \"owlia.nes\",\n        \"Gradual Games\",\n        \"The Legends of Owlia is Gradual Games' second release for the NES. It is an action-adventure game inspired by StarTropics, Crystalis, and the Legend of Zelda. \",\n        \"https://www.infiniteneslives.com/owlia.php\",\n    ),\n    (\n        \"Streemerz\",\n        \"streemerz.nes\",\n        \"Mr. Podunkian & Faux Game Co.\",\n        r#\"\"Try climbing to the top of this one by throwing streamers and climbing them. On your way up you better watch out for the various pie throwing clowns, burning candles and bouncing balls, because if they get you, you'll die a little each time.\"\n\nThese were the orders given to you, Operative JOE when you were ordered to infiltrate the evil MASTER Y's floating fortress to destroy the TIGER ARMY's top secret weapon.\"#,\n        \"https://www.fauxgame.com/\",\n    ),\n    (\n        \"Super Painter\",\n        \"super_painter.nes\",\n        \"RetroSouls & Kulor\",\n        \"Trapped in a colorless world, armed with a paintbrush - there’s only one thing to do! As Super Painter, you’ll have to fill in all the missing color from the walls and ledges of 25 charming stages. Watch out for enemies and pits to the bottom, and don’t box yourself in - when you’re done painting, you’ll have to race to the magic door to the next level. It’s platform puzzling at its finest!\",\n        \"https://www.retrosouls.net/?page_id=901\",\n    ),\n    (\n        \"Tiger Jenny\",\n        \"tiger_jenny.nes\",\n        \"Ludosity\",\n        \"Tiger Jenny by Ludosity is a NES game set in the same universe as “Ittle Dew” it takes place a thousand years before the events of that game. Battle your way through the forests to seek vengeance on the Turnip Witch who dwells in her castle.\",\n        \"https://pdroms.de/files/nintendo-nintendoentertainmentsystem-nes-famicom-fc/tiger-jenny\",\n    ),\n    (\n        \"Yun\",\n        \"yun.nes\",\n        \"The Mojon Twins\",\n        \"The main goal is helping yun capturing every single being to fill the pantry of her restaurant. The Big Marsh near Lake Potoña (province of Badajoz), formed by three areas (the marsh wood, the marsh abandoned factory and the mash desert) is full of walking flesh Yun must capture.\n\nTo capture her enemies, Yun must stun them by means of hitting them with a bubble. Once they are stunned, they can be captured just touching them.\n\nYun’s bubbles are quite resistant. You can hump on them and let them carry you upwards, which is sometimes the only way to progress in the level.\n\nBesides, there’s some points where Yun might need a key to keep going.\",\n        \"https://www.mojontwins.com/juegos_mojonos/yun/\",\n    ),\n);\n"
  },
  {
    "path": "tetanes/src/nes/version.rs",
    "content": "use std::cell::RefCell;\n\n#[cfg(not(target_arch = \"wasm32\"))]\nmod fetcher {\n    use reqwest::blocking::Client;\n    use std::cell::Cell;\n    use std::time::{Duration, Instant};\n\n    #[derive(Debug, Clone)]\n    #[must_use]\n    pub struct Fetcher {\n        client: Option<Client>,\n        rate_limit: Duration,\n        last_request_time: Cell<Instant>,\n    }\n\n    impl Default for Fetcher {\n        fn default() -> Self {\n            Self {\n                client: Self::create_client(),\n                rate_limit: Duration::from_secs(1),\n                last_request_time: Cell::new(Instant::now()),\n            }\n        }\n    }\n\n    impl Fetcher {\n        fn create_client() -> Option<Client> {\n            use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};\n\n            let mut headers = HeaderMap::new();\n            headers.insert(\n                USER_AGENT,\n                HeaderValue::from_str(\"tetanes (me@lukeworks.tech)\").ok()?,\n            );\n            reqwest::blocking::Client::builder()\n                .default_headers(headers)\n                .build()\n                .ok()\n        }\n\n        pub fn update_available(&self, version: &'static str) -> anyhow::Result<Option<String>> {\n            #[derive(Debug, serde::Deserialize)]\n            #[must_use]\n            struct ApiError {\n                detail: Option<String>,\n            }\n\n            #[derive(Debug, serde::Deserialize)]\n            #[must_use]\n            struct ApiErrors {\n                errors: Vec<ApiError>,\n            }\n\n            // Partial deserialization of the full response\n            #[derive(Debug, serde::Deserialize)]\n            #[must_use]\n            struct Crate {\n                newest_version: String,\n            }\n\n            // Partial deserialization of the full response\n            #[derive(Debug, serde::Deserialize)]\n            #[must_use]\n            struct CrateResponse {\n                #[serde(rename = \"crate\")]\n                cr: Crate,\n            }\n\n            if self.last_request_time.get().elapsed() < self.rate_limit {\n                std::thread::sleep(\n                    (self.last_request_time.get() + self.rate_limit) - Instant::now(),\n                );\n            }\n\n            self.last_request_time.set(Instant::now());\n            let Some(client) = &self.client else {\n                anyhow::bail!(\"failed to create http client\");\n            };\n            let content = client\n                .get(\"https://crates.io/api/v1/crates/tetanes\")\n                .send()\n                .and_then(|res| res.text())?;\n            if let Ok(res) = serde_json::from_str::<ApiErrors>(&content) {\n                anyhow::bail!(\n                    \"encountered crates.io API errors: {}\",\n                    res.errors\n                        .into_iter()\n                        .filter_map(|error| error.detail)\n                        .collect::<Vec<_>>()\n                        .join(\",\")\n                );\n            }\n\n            match serde_json::from_str::<CrateResponse>(&content) {\n                Ok(CrateResponse {\n                    cr: Crate { newest_version, .. },\n                }) => {\n                    if Self::is_newer(&newest_version, version) {\n                        Ok(Some(newest_version))\n                    } else {\n                        Ok(None)\n                    }\n                }\n                Err(err) => anyhow::bail!(\"failed to deserialize crates.io response: {err:?}\"),\n            }\n        }\n\n        fn is_newer(new: &str, old: &str) -> bool {\n            match (semver::Version::parse(old), semver::Version::parse(new)) {\n                (Ok(old), Ok(new)) => new > old,\n                _ => false,\n            }\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\n#[must_use]\npub struct Version {\n    current: &'static str,\n    latest: RefCell<String>,\n}\n\nimpl Default for Version {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Version {\n    pub fn new() -> Self {\n        Self {\n            current: env!(\"CARGO_PKG_VERSION\"),\n            latest: RefCell::new(env!(\"CARGO_PKG_VERSION\").to_string()),\n        }\n    }\n\n    pub const fn current(&self) -> &str {\n        self.current\n    }\n\n    pub fn latest(&self) -> String {\n        self.latest.borrow().clone()\n    }\n\n    pub fn set_latest(&mut self, version: String) {\n        self.latest.replace(version);\n    }\n\n    pub const fn requires_updates(&self) -> bool {\n        cfg!(not(target_arch = \"wasm32\"))\n    }\n\n    #[cfg(target_arch = \"wasm32\")]\n    pub const fn check_for_updates(\n        &mut self,\n        _tx: &crate::nes::event::NesEventProxy,\n        _notify_latest: bool,\n    ) {\n    }\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    pub fn check_for_updates(\n        &mut self,\n        tx: &crate::nes::event::NesEventProxy,\n        notify_latest: bool,\n    ) {\n        use crate::nes::{\n            event::{ConfigEvent, UiEvent},\n            renderer::gui::MessageType,\n        };\n\n        let spawn_update = std::thread::Builder::new()\n            .name(\"check_updates\".into())\n            .spawn({\n                let current_version = self.current;\n                let fetcher = fetcher::Fetcher::default();\n                let tx = tx.clone();\n                move || {\n                    let newest_version = fetcher.update_available(current_version);\n                    match newest_version {\n                        Ok(Some(version)) => tx.event(UiEvent::UpdateAvailable(version)),\n                        Ok(None) => {\n                            if notify_latest {\n                                tx.event(UiEvent::Message((\n                                    MessageType::Info,\n                                    format!(\"TetaNES v{current_version} is up to date!\"),\n                                )));\n                            }\n                        }\n                        Err(err) => {\n                            tx.event(UiEvent::Message((MessageType::Error, err.to_string())));\n                        }\n                    }\n                    if notify_latest {\n                        tx.event(ConfigEvent::ShowUpdates(true));\n                    }\n                }\n            });\n        if let Err(err) = spawn_update {\n            tx.event(UiEvent::Message((\n                MessageType::Error,\n                format!(\"Failed to check for updates: {err}\"),\n            )));\n        }\n    }\n\n    pub fn install_update_and_restart(&mut self) -> anyhow::Result<()> {\n        // TODO: Implement install/restart for each platform\n        anyhow::bail!(\"not yet implemented\");\n    }\n}\n"
  },
  {
    "path": "tetanes/src/nes.rs",
    "content": "//! User Interface representing the the NES Control Deck\n\nuse crate::{\n    nes::{\n        emulation::Emulation,\n        event::{NesEvent, NesEventProxy},\n        input::{Gamepads, InputBindings},\n        renderer::{FrameRecycle, Renderer, Resources, painter::Painter},\n    },\n    platform::Initialize,\n};\nuse anyhow::Context;\nuse cfg_if::cfg_if;\nuse config::Config;\nuse crossbeam::channel::Receiver;\nuse egui::ahash::HashMap;\nuse std::sync::{\n    Arc,\n    atomic::{AtomicBool, Ordering},\n};\nuse tetanes_core::{time::Instant, video::Frame};\nuse thingbuf::mpsc::blocking;\nuse winit::{\n    event::Modifiers,\n    event_loop::{ActiveEventLoop, EventLoop},\n    window::{Window, WindowId},\n};\n\npub mod action;\npub mod audio;\npub mod config;\npub mod emulation;\npub mod event;\npub mod input;\npub mod renderer;\npub mod rom;\npub mod version;\n\n/// Represents all the NES Emulation state.\n#[derive(Debug)]\n#[must_use]\npub struct Nes {\n    /// Set during initialization, then taken and set to `None` when running because\n    /// `EventLoopProxy` can only be created on the initial `EventLoop` and not on\n    /// `&EventLoopWindowTarget`.\n    pub(crate) init_state: Option<(Config, NesEventProxy)>,\n    /// Initially `Suspended`. `Pending` after `Resume` event received and spanwed. `Running` after\n    /// resources future completes.\n    pub(crate) state: State,\n}\n\n#[derive(Debug)]\n#[must_use]\npub(crate) enum State {\n    Suspended {\n        should_terminate: Arc<AtomicBool>,\n    },\n    Pending {\n        ctx: egui::Context,\n        window: Arc<Window>,\n        painter_rx: Receiver<Painter>,\n        should_terminate: Arc<AtomicBool>,\n    },\n    Running(Box<Running>),\n    Exiting,\n}\n\nimpl Default for State {\n    fn default() -> Self {\n        Self::Suspended {\n            should_terminate: Default::default(),\n        }\n    }\n}\n\nimpl State {\n    pub const fn is_exiting(&self) -> bool {\n        matches!(self, Self::Exiting)\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq)]\n#[must_use]\npub enum RunState {\n    Running,\n    ManuallyPaused,\n    AutoPaused,\n}\n\nimpl RunState {\n    pub const fn paused(&self) -> bool {\n        matches!(self, Self::ManuallyPaused | Self::AutoPaused)\n    }\n\n    pub const fn auto_paused(&self) -> bool {\n        matches!(self, Self::AutoPaused)\n    }\n\n    pub const fn manually_paused(&self) -> bool {\n        matches!(self, Self::ManuallyPaused)\n    }\n}\n\n/// Represents the NES running state.\n#[derive(Debug)]\npub(crate) struct Running {\n    pub(crate) cfg: Config,\n    // Only used by wasm currently\n    #[cfg_attr(target_arch = \"wasm32\", allow(unused))]\n    pub(crate) tx: NesEventProxy,\n    pub(crate) should_terminate: Arc<AtomicBool>,\n    pub(crate) emulation: Emulation,\n    pub(crate) renderer: Renderer,\n    pub(crate) input_bindings: InputBindings,\n    pub(crate) gamepads: Gamepads,\n    pub(crate) modifiers: Modifiers,\n    pub(crate) replay_recording: bool,\n    pub(crate) audio_recording: bool,\n    pub(crate) rewinding: bool,\n    pub(crate) occluded: bool,\n    pub(crate) repaint_times: HashMap<WindowId, Instant>,\n}\n\nimpl Nes {\n    /// Runs the NES application by starting the event loop.\n    ///\n    /// # Errors\n    ///\n    /// If event loop fails to build or run, then an error is returned.\n    pub fn run(cfg: Config) -> anyhow::Result<()> {\n        // Set up window, events and NES state\n        let event_loop = EventLoop::<NesEvent>::with_user_event().build()?;\n        let nes = Nes::new(cfg, &event_loop);\n        cfg_if! {\n            if #[cfg(target_arch = \"wasm32\")] {\n                use winit::platform::web::EventLoopExtWebSys;\n                event_loop.spawn_app(nes);\n            } else {\n                let mut nes = nes;\n                event_loop.run_app(&mut nes)?;\n            }\n        }\n        Ok(())\n    }\n\n    /// Return whether the application should terminate.\n    pub fn should_terminate(&self) -> bool {\n        match &self.state {\n            State::Suspended { should_terminate }\n            | State::Pending {\n                should_terminate, ..\n            } => should_terminate.load(Ordering::Relaxed),\n            State::Running(running) => running.should_terminate.load(Ordering::Relaxed),\n            State::Exiting => true,\n        }\n    }\n\n    /// Create the NES instance.\n    pub fn new(cfg: Config, event_loop: &EventLoop<NesEvent>) -> Self {\n        let should_terminate = Arc::new(AtomicBool::new(false));\n        #[cfg(not(target_arch = \"wasm32\"))]\n        // Minor issue if this fails, but not enough to terminate the program\n        let _ = ctrlc::set_handler({\n            let should_terminate = Arc::clone(&should_terminate);\n            move || {\n                should_terminate.store(true, Ordering::Relaxed);\n            }\n        });\n\n        Self {\n            init_state: Some((cfg, NesEventProxy::new(event_loop))),\n            state: State::Suspended { should_terminate },\n        }\n    }\n\n    /// Request renderer resources (creating gui context, window, painter, etc).\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if any resources can't be created correctly or `init_running` has already\n    /// been called.\n    pub(crate) fn request_renderer_resources(\n        &mut self,\n        event_loop: &ActiveEventLoop,\n        should_terminate: Arc<AtomicBool>,\n    ) -> anyhow::Result<()> {\n        let (cfg, tx) = self\n            .init_state\n            .as_ref()\n            .context(\"config unexpectedly already taken\")?;\n\n        let (ctx, window, painter_rx) = Renderer::request_resources(event_loop, tx, cfg)?;\n\n        self.state = State::Pending {\n            ctx,\n            window,\n            painter_rx,\n            should_terminate,\n        };\n\n        Ok(())\n    }\n\n    /// Initialize the running state after a window and GPU resources are created. Transitions\n    /// `state` from `Some(PendingGpuResources { .. })` to `Some(Running { .. })`.\n    ///\n    /// # Errors\n    ///\n    /// If GPU resources failed to be requested, the emulation or renderer fails to build, then an\n    /// error is returned.\n    pub(crate) fn init_running(&mut self, event_loop: &ActiveEventLoop) -> anyhow::Result<()> {\n        match std::mem::take(&mut self.state) {\n            State::Pending {\n                ctx,\n                window,\n                painter_rx,\n                should_terminate,\n            } => {\n                let resources = Resources {\n                    ctx,\n                    window,\n                    painter: painter_rx.recv()?,\n                };\n                let (frame_tx, frame_rx) = blocking::with_recycle::<Frame, _>(10, FrameRecycle);\n                let (mut cfg, tx) = self\n                    .init_state\n                    .take()\n                    .context(\"config unexpectedly already taken\")?;\n\n                let input_bindings = InputBindings::from_input_config(&cfg.input);\n                let gamepads = Gamepads::new();\n                cfg.input.update_gamepad_assignments(&gamepads);\n\n                let emulation = Emulation::new(tx.clone(), frame_tx.clone(), &cfg)?;\n                let renderer = Renderer::new(event_loop, tx.clone(), resources, frame_rx, &cfg)?;\n\n                let mut running = Running {\n                    cfg,\n                    tx,\n                    should_terminate,\n                    emulation,\n                    renderer,\n                    input_bindings,\n                    gamepads,\n                    modifiers: Modifiers::default(),\n                    replay_recording: false,\n                    audio_recording: false,\n                    rewinding: false,\n                    occluded: false,\n                    repaint_times: HashMap::default(),\n                };\n                running.initialize()?;\n                self.state = State::Running(Box::new(running));\n                Ok(())\n            }\n            State::Running(running) => {\n                self.state = State::Running(running);\n                Ok(())\n            }\n            State::Suspended { .. } | State::Exiting => anyhow::bail!(\"not in pending state\"),\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes/src/opts.rs",
    "content": "use clap::{Parser, ValueEnum};\nuse std::path::PathBuf;\nuse tetanes::nes::config::Config;\nuse tetanes_core::genie::GenieCode;\n\n#[derive(Debug, Clone)]\npub(crate) struct FourPlayer(tetanes_core::input::FourPlayer);\n\nimpl ValueEnum for FourPlayer {\n    fn value_variants<'a>() -> &'a [Self] {\n        use tetanes_core::input::FourPlayer::*;\n        &[Self(Disabled), Self(FourScore), Self(Satellite)]\n    }\n\n    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {\n        Some(clap::builder::PossibleValue::new(self.0.as_str()))\n    }\n}\n\n#[derive(Debug, Clone)]\npub(crate) struct RamState(tetanes_core::mem::RamState);\n\nimpl ValueEnum for RamState {\n    fn value_variants<'a>() -> &'a [Self] {\n        use tetanes_core::mem::RamState::*;\n        &[Self(AllZeros), Self(AllOnes), Self(Random)]\n    }\n\n    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {\n        Some(clap::builder::PossibleValue::new(self.0.as_str()))\n    }\n}\n\n#[derive(Debug, Clone)]\npub(crate) struct NesRegion(tetanes_core::common::NesRegion);\n\nimpl ValueEnum for NesRegion {\n    fn value_variants<'a>() -> &'a [Self] {\n        use tetanes_core::common::NesRegion::*;\n        &[Self(Ntsc), Self(Pal), Self(Dendy)]\n    }\n\n    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {\n        Some(clap::builder::PossibleValue::new(self.0.as_str()))\n    }\n}\n\n/// `TetaNES` CLI Config Options\n#[derive(Parser, Debug)]\n#[command(version, author, about, long_about = None)]\n#[must_use]\npub struct Opts {\n    /// The NES ROM to load or a directory containing `.nes` ROM files. [default: current directory]\n    pub(crate) path: Option<PathBuf>,\n    /// Enable rewinding.\n    #[arg(long)]\n    pub(crate) rewind: bool,\n    /// Silence audio.\n    #[arg(short, long)]\n    pub(crate) silent: bool,\n    /// Start fullscreen.\n    #[arg(short, long)]\n    pub(crate) fullscreen: bool,\n    /// Set four player adapter. [default: 'disabled']\n    #[arg(short = '4', long, value_enum)]\n    pub(crate) four_player: Option<FourPlayer>,\n    /// Enable zapper gun.\n    #[arg(short, long)]\n    pub(crate) zapper: bool,\n    /// Disable multi-threaded.\n    #[arg(long)]\n    pub(crate) no_threaded: bool,\n    /// Choose power-up RAM state. [default: \"all-zeros\"]\n    #[arg(short = 'm', long, value_enum)]\n    pub(crate) ram_state: Option<RamState>,\n    /// Whether to emulate PPU warmup where writes to certain registers are ignored. Can result in\n    /// some games not working correctly.\n    #[arg(short = 'w', long)]\n    pub(crate) emulate_ppu_warmup: bool,\n    /// Choose default NES region. [default: \"ntsc\"]\n    #[arg(short = 'r', long, value_enum)]\n    pub(crate) region: Option<NesRegion>,\n    /// Save slot. [default: 1]\n    #[arg(short = 'i', long)]\n    pub(crate) save_slot: Option<u8>,\n    /// Don't load save state on start.\n    #[arg(long)]\n    pub(crate) no_load: bool,\n    /// Don't auto save state or save on exit.\n    #[arg(long)]\n    pub(crate) no_save: bool,\n    #[arg(short = 'x', long)]\n    /// Emulation speed. [default: 1.0]\n    pub(crate) speed: Option<f32>,\n    /// Add Game Genie Code(s). e.g. `AATOZE` (Start Super Mario Bros. with 9 lives).\n    #[arg(short, long)]\n    pub(crate) genie_code: Vec<String>,\n    /// Custom Config path.\n    #[arg(long)]\n    pub(crate) config: Option<PathBuf>,\n    /// \"Default Config\" (skip user config and previous save states)\n    #[arg(short, long)]\n    pub(crate) clean: bool,\n    /// Start with debugger open.\n    #[arg(short, long)]\n    pub(crate) debug: bool,\n}\n\nimpl Opts {\n    /// Loads a base `Config`, merging with CLI options\n    pub fn load(self) -> anyhow::Result<Config> {\n        let mut cfg = if self.clean {\n            Config::default()\n        } else {\n            Config::load(self.config.clone())\n        };\n\n        if let Some(FourPlayer(four_player)) = self.four_player {\n            cfg.deck.four_player = four_player;\n        }\n        cfg.deck.zapper = self.zapper || cfg.deck.zapper;\n        if let Some(RamState(ram_state)) = self.ram_state {\n            cfg.deck.ram_state = ram_state;\n        }\n        cfg.deck.emulate_ppu_warmup = self.emulate_ppu_warmup || cfg.deck.emulate_ppu_warmup;\n        if let Some(NesRegion(region)) = self.region {\n            cfg.deck.region = region;\n        }\n        cfg.deck.genie_codes.reserve(self.genie_code.len());\n        for genie_code in self.genie_code.into_iter() {\n            cfg.deck.genie_codes.push(GenieCode::new(genie_code)?);\n        }\n\n        cfg.emulation.auto_load = if self.clean {\n            false\n        } else {\n            !self.no_load && cfg.emulation.auto_load\n        };\n        cfg.emulation.rewind = self.rewind || cfg.emulation.rewind;\n        cfg.emulation.auto_save = if self.clean {\n            false\n        } else {\n            !self.no_save && cfg.emulation.auto_save\n        };\n        if let Some(save_slot) = self.save_slot {\n            cfg.emulation.save_slot = save_slot\n        }\n        if let Some(speed) = self.speed {\n            cfg.emulation.speed = speed\n        }\n        cfg.emulation.threaded = !self.no_threaded && cfg.emulation.threaded;\n\n        cfg.audio.enabled = !self.silent && cfg.audio.enabled;\n\n        cfg.renderer.roms_path = self\n            .path\n            .or(cfg.renderer.roms_path)\n            .and_then(|path| path.canonicalize().ok());\n        cfg.renderer.fullscreen = self.fullscreen || cfg.renderer.fullscreen;\n\n        Ok(cfg)\n    }\n}\n"
  },
  {
    "path": "tetanes/src/platform.rs",
    "content": "use crate::sys::platform;\nuse std::path::{Path, PathBuf};\n\npub use platform::*;\n\n/// Trait for any type requiring platform-specific initialization.\npub trait Initialize {\n    /// Initialize type.\n    fn initialize(&mut self) -> anyhow::Result<()>;\n}\n\n/// Extension trait for any builder that provides platform-specific behavior.\npub trait BuilderExt {\n    /// Sets platform-specific options.\n    fn with_platform(self, title: &str) -> Self;\n}\n\n/// Method for platforms supporting opening a file dialog.\npub fn open_file_dialog(\n    title: impl Into<String>,\n    name: impl Into<String>,\n    extensions: &[impl ToString],\n    dir: Option<impl AsRef<Path>>,\n) -> anyhow::Result<Option<PathBuf>> {\n    platform::open_file_dialog_impl(title, name, extensions, dir)\n}\n\n/// Speak the given text out loud for platforms that support it.\n#[allow(clippy::missing_const_for_fn)]\npub fn speak_text(text: &str) {\n    platform::speak_text_impl(text);\n}\n\npub mod renderer {\n    use super::*;\n    use crate::nes::{config::Config, event::Response, renderer::Renderer};\n\n    pub fn constrain_window_to_viewport(\n        renderer: &Renderer,\n        desired_window_width: f32,\n        cfg: &Config,\n    ) -> Response {\n        platform::renderer::constrain_window_to_viewport_impl(renderer, desired_window_width, cfg)\n    }\n}\n\n/// Platform-specific feature capabilities.\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\n#[must_use]\npub enum Feature {\n    AbortOnExit,\n    Blocking,\n    ConstrainedViewport,\n    ConsumePaste,\n    Filesystem,\n    ScreenReader,\n    Storage,\n    Suspend,\n    OsViewports,\n}\n\n/// Checks if the current platform supports a given feature.\n#[macro_export]\nmacro_rules! feature {\n    ($feature: tt) => {{\n        use $crate::platform::Feature::*;\n        match $feature {\n            // Wasm should never be able to exit\n            AbortOnExit => cfg!(target_arch = \"wasm32\"),\n            Blocking | Filesystem | OsViewports => {\n                cfg!(not(target_arch = \"wasm32\"))\n            }\n            ConstrainedViewport | ConsumePaste | ScreenReader => {\n                cfg!(target_arch = \"wasm32\")\n            }\n            Storage => true,\n            Suspend => cfg!(target_os = \"android\"),\n        }\n    }};\n}\n"
  },
  {
    "path": "tetanes/src/sys/info/os.rs",
    "content": "use crate::sys::{DiskUsage, SystemInfo, SystemStats};\nuse std::time::{Duration, Instant};\nuse sysinfo::{ProcessRefreshKind, RefreshKind};\n\n#[derive(Debug)]\npub struct System {\n    sys: Option<sysinfo::System>,\n    updated: Instant,\n}\n\nimpl Default for System {\n    fn default() -> Self {\n        let sys = if sysinfo::IS_SUPPORTED_SYSTEM {\n            let mut sys = sysinfo::System::new_with_specifics(\n                RefreshKind::nothing().with_processes(\n                    ProcessRefreshKind::nothing()\n                        .with_cpu()\n                        .with_memory()\n                        .with_disk_usage(),\n                ),\n            );\n            sys.refresh_specifics(\n                RefreshKind::nothing().with_processes(\n                    ProcessRefreshKind::nothing()\n                        .with_cpu()\n                        .with_memory()\n                        .with_disk_usage(),\n                ),\n            );\n            Some(sys)\n        } else {\n            None\n        };\n\n        Self {\n            sys,\n            updated: Instant::now(),\n        }\n    }\n}\n\nimpl SystemInfo for System {\n    fn update(&mut self) {\n        if let Some(sys) = &mut self.sys {\n            // NOTE: refreshing sysinfo is cpu-intensive if done too frequently and skews the\n            // results\n            let update_interval = Duration::from_secs(1);\n            assert!(update_interval > sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);\n            if self.updated.elapsed() >= update_interval {\n                sys.refresh_specifics(\n                    sysinfo::RefreshKind::nothing().with_processes(\n                        sysinfo::ProcessRefreshKind::nothing()\n                            .with_cpu()\n                            .with_memory()\n                            .with_disk_usage(),\n                    ),\n                );\n                self.updated = Instant::now();\n            }\n        }\n    }\n\n    fn stats(&self) -> Option<SystemStats> {\n        self.sys\n            .as_ref()\n            .and_then(|sys| sys.process(sysinfo::Pid::from_u32(std::process::id())))\n            .map(|proc| {\n                let du = proc.disk_usage();\n                SystemStats {\n                    cpu_usage: proc.cpu_usage(),\n                    memory: proc.memory(),\n                    disk_usage: DiskUsage {\n                        read_bytes: du.read_bytes,\n                        total_read_bytes: du.total_read_bytes,\n                        written_bytes: du.written_bytes,\n                        total_written_bytes: du.total_written_bytes,\n                    },\n                }\n            })\n    }\n}\n"
  },
  {
    "path": "tetanes/src/sys/info/wasm.rs",
    "content": "use crate::sys::{SystemInfo, SystemStats};\n\n#[derive(Default, Debug)]\npub struct System {}\n\nimpl SystemInfo for System {\n    fn update(&mut self) {}\n\n    fn stats(&self) -> Option<SystemStats> {\n        None\n    }\n}\n"
  },
  {
    "path": "tetanes/src/sys/info.rs",
    "content": "use cfg_if::cfg_if;\n\ncfg_if! {\n    if #[cfg(target_arch = \"wasm32\")] {\n        mod wasm;\n        pub use wasm::*;\n    } else {\n        mod os;\n        pub use os::*;\n    }\n}\n"
  },
  {
    "path": "tetanes/src/sys/logging/os.rs",
    "content": "use anyhow::Context;\nuse std::path::PathBuf;\nuse tracing_appender::{\n    non_blocking::{NonBlockingBuilder, WorkerGuard},\n    rolling::{RollingFileAppender, Rotation},\n};\nuse tracing_subscriber::{\n    fmt, layer::SubscriberExt, registry::LookupSpan, util::SubscriberInitExt,\n};\n\n#[must_use]\npub struct Log {\n    _guard: WorkerGuard,\n}\n\npub fn init_impl<S>(registry: S) -> anyhow::Result<(impl SubscriberInitExt, Log)>\nwhere\n    S: SubscriberExt + for<'a> LookupSpan<'a> + Sync + Send,\n{\n    let file_appender = RollingFileAppender::builder()\n        .rotation(Rotation::DAILY)\n        .max_log_files(3)\n        .filename_prefix(\"tetanes\")\n        .filename_suffix(\"log\")\n        .build(\n            dirs::data_local_dir()\n                .map(|dir| dir.join(\"tetanes/logs\"))\n                .unwrap_or_else(|| PathBuf::from(\"logs\")),\n        )\n        .context(\"failed to create log file\")?;\n    let (file_writer, guard) = NonBlockingBuilder::default()\n        .buffered_lines_limit(4096)\n        .thread_name(\"tetanes-logger\")\n        .finish(file_appender);\n\n    let registry = registry\n        .with(\n            fmt::layer()\n                .compact()\n                .with_line_number(true)\n                .with_thread_ids(true)\n                .with_thread_names(true)\n                .with_writer(file_writer),\n        )\n        .with(\n            fmt::layer()\n                .compact()\n                .with_line_number(true)\n                .with_thread_ids(true)\n                .with_thread_names(true)\n                .with_writer(std::io::stderr),\n        );\n\n    Ok((registry, Log { _guard: guard }))\n}\n"
  },
  {
    "path": "tetanes/src/sys/logging/wasm.rs",
    "content": "use std::panic;\nuse tracing_subscriber::{\n    fmt::{self, format::Pretty},\n    layer::SubscriberExt,\n    registry::LookupSpan,\n    util::SubscriberInitExt,\n};\nuse tracing_web::{MakeWebConsoleWriter, performance_layer};\n\npub struct Log;\n\npub fn init_impl<S>(registry: S) -> anyhow::Result<(impl SubscriberInitExt, Log)>\nwhere\n    S: SubscriberExt + for<'a> LookupSpan<'a> + Sync + Send,\n{\n    panic::set_hook(Box::new(|info: &panic::PanicHookInfo<'_>| {\n        let error_div = web_sys::window()\n            .and_then(|window| window.document())\n            .and_then(|document| document.get_element_by_id(\"error\"));\n        if let Some(error_div) = error_div\n            && let Err(err) = error_div.class_list().remove_1(\"hidden\")\n        {\n            tracing::error!(\"{err:?}\")\n        }\n\n        console_error_panic_hook::hook(info);\n    }));\n\n    let console_layer = fmt::layer()\n        .compact()\n        .with_line_number(true)\n        .with_ansi(false)\n        .without_time() // Not available in wasm\n        .with_writer(MakeWebConsoleWriter::new());\n    let perf_layer = performance_layer().with_details_from_fields(Pretty::default());\n    let registry = registry.with(console_layer).with(perf_layer);\n\n    Ok((registry, Log))\n}\n"
  },
  {
    "path": "tetanes/src/sys/logging.rs",
    "content": "use cfg_if::cfg_if;\n\ncfg_if! {\n    if #[cfg(target_arch = \"wasm32\")] {\n        mod wasm;\n        pub use wasm::*;\n    } else {\n        mod os;\n        pub use os::*;\n    }\n}\n"
  },
  {
    "path": "tetanes/src/sys/platform/os.rs",
    "content": "use crate::{\n    nes::{Running, event::EmulationEvent, renderer::Renderer},\n    platform::{BuilderExt, Initialize},\n};\nuse std::path::{Path, PathBuf};\nuse tracing::error;\nuse winit::window::WindowAttributes;\n\n/// Method for platforms supporting opening a file dialog.\npub fn open_file_dialog_impl(\n    title: impl Into<String>,\n    name: impl Into<String>,\n    extensions: &[impl ToString],\n    dir: Option<impl AsRef<Path>>,\n) -> anyhow::Result<Option<PathBuf>> {\n    let mut dialog = rfd::FileDialog::new()\n        .set_title(title)\n        .add_filter(name, extensions);\n    if let Some(dir) = dir {\n        dialog = dialog.set_directory(dir.as_ref());\n    }\n    Ok(dialog.pick_file())\n}\n\n/// Speak the given text out loud.\npub const fn speak_text_impl(_text: &str) {}\n\nimpl Initialize for Running {\n    /// Initialize by loading a ROM from the command line, if provided.\n    fn initialize(&mut self) -> anyhow::Result<()> {\n        if let Some(path) = self.cfg.renderer.roms_path.take() {\n            if path.is_file() {\n                if let Some(parent) = path.parent() {\n                    self.cfg.renderer.roms_path = Some(parent.to_path_buf());\n                }\n                self.event(EmulationEvent::LoadRomPath(path));\n            } else if path.exists() {\n                self.cfg.renderer.roms_path = Some(path);\n            }\n        }\n\n        Ok(())\n    }\n}\n\nimpl Initialize for Renderer {\n    fn initialize(&mut self) -> anyhow::Result<()> {\n        Ok(())\n    }\n}\n\nimpl BuilderExt for WindowAttributes {\n    /// Sets platform-specific window options.\n    fn with_platform(self, _title: &str) -> Self {\n        use anyhow::Context;\n        use image::{ImageFormat, ImageReader};\n        use std::io::Cursor;\n\n        static WINDOW_ICON: &[u8] = include_bytes!(\"../../../assets/tetanes_icon.png\");\n\n        let icon = ImageReader::with_format(Cursor::new(WINDOW_ICON), ImageFormat::Png)\n            .decode()\n            .context(\"failed to decode window icon\");\n\n        let window_attrs = self.with_window_icon(\n            icon.and_then(|png| {\n                let width = png.width();\n                let height = png.height();\n                winit::window::Icon::from_rgba(png.into_rgba8().into_vec(), width, height)\n                    .with_context(|| \"failed to create window icon\")\n            })\n            .map_err(|err| error!(\"{err:?}\"))\n            .ok(),\n        );\n\n        #[cfg(target_os = \"linux\")]\n        let window_attrs = {\n            use winit::platform::wayland::WindowAttributesExtWayland as _;\n\n            window_attrs.with_name(_title, \"\")\n        };\n\n        // Ensures that viewport windows open in a separate window instead of a tab, which has\n        // issues with certain preference toggles like fullscreen that effect the root viewport.\n        #[cfg(target_os = \"macos\")]\n        let window_attrs = {\n            use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS};\n\n            window_attrs\n                .with_tabbing_identifier(_title)\n                .with_option_as_alt(OptionAsAlt::Both)\n        };\n\n        window_attrs\n    }\n}\n\npub mod renderer {\n    use super::*;\n    use crate::nes::{config::Config, event::Response};\n\n    pub fn constrain_window_to_viewport_impl(\n        _renderer: &Renderer,\n        _desired_window_width: f32,\n        _cfg: &Config,\n    ) -> Response {\n        Response::default()\n    }\n}\n"
  },
  {
    "path": "tetanes/src/sys/platform/wasm.rs",
    "content": "// TODO: Remove. See: https://github.com/rustwasm/wasm-bindgen/issues/4283\n#![allow(unexpected_cfgs)]\n\nuse crate::{\n    nes::{\n        Running,\n        event::{EmulationEvent, NesEventProxy, RendererEvent, ReplayData, UiEvent},\n        renderer::{Renderer, State, gui},\n        rom::RomData,\n    },\n    platform::{BuilderExt, Initialize},\n    thread,\n};\nuse anyhow::{Context, bail};\nuse std::{\n    path::{Path, PathBuf},\n    rc::Rc,\n};\nuse wasm_bindgen::prelude::*;\nuse web_sys::{\n    FileReader, HtmlAnchorElement, HtmlCanvasElement, HtmlInputElement, js_sys::Uint8Array,\n};\nuse winit::{platform::web::WindowAttributesExtWebSys, window::WindowAttributes};\n\nconst BIN_NAME: &str = env!(\"CARGO_PKG_NAME\");\nconst VERSION: &str = env!(\"CARGO_PKG_VERSION\");\nconst OS_OPTIONS: [(Os, Arch, &str); 5] = [\n    (Os::Unknown, Arch::X86_64, html_ids::SELECTED_VERSION),\n    (Os::Windows, Arch::X86_64, html_ids::WINDOWS_X86_LINK),\n    (Os::MacOs, Arch::Aarch64, html_ids::MACOS_AARCH64_LINK),\n    (Os::MacOs, Arch::X86_64, html_ids::MACOS_X86_LINK),\n    (Os::Linux, Arch::X86_64, html_ids::LINUX_X86_LINK),\n];\n\n#[derive(Debug)]\npub struct System;\n\n/// Method for platforms supporting opening a file dialog.\npub fn open_file_dialog_impl(\n    _title: impl Into<String>,\n    _name: impl Into<String>,\n    extensions: &[impl ToString],\n    _dir: Option<impl AsRef<Path>>,\n) -> anyhow::Result<Option<PathBuf>> {\n    let input_id = match extensions[0].to_string().as_str() {\n        \"nes\" => html_ids::ROM_INPUT,\n        \"replay\" => html_ids::REPLAY_INPUT,\n        _ => bail!(\"unsupported file extension\"),\n    };\n\n    let input = web_sys::window()\n        .and_then(|window| window.document())\n        .and_then(|document| document.get_element_by_id(input_id))\n        .and_then(|input| input.dyn_into::<HtmlInputElement>().ok());\n    match input {\n        Some(input) => {\n            // To prevent event loop receiving events while dialog is open\n            if let Some(canvas) = get_canvas() {\n                let _ = canvas.blur();\n            }\n            input.click();\n        }\n        None => bail!(\"failed to find file input element\"),\n    }\n\n    Ok(None)\n}\n\n/// Speak the given text out loud.\npub fn speak_text_impl(text: &str) {\n    if text.is_empty() {\n        return;\n    }\n\n    if let Some(window) = web_sys::window() {\n        tracing::debug!(\"Speaking {text:?}\");\n\n        if let Ok(speech_synthesis) = window.speech_synthesis() {\n            speech_synthesis.cancel(); // interrupt previous speech, if any\n\n            if let Ok(utterance) = web_sys::SpeechSynthesisUtterance::new_with_text(text) {\n                utterance.set_rate(1.0);\n                utterance.set_pitch(1.0);\n                utterance.set_volume(1.0);\n                speech_synthesis.speak(&utterance);\n            }\n        }\n    }\n}\n\n/// Helper method to log and send errors to the UI thread from javascript.\nfn on_error(tx: &NesEventProxy, err: JsValue) {\n    tracing::error!(\"{err:?}\");\n    tx.event(UiEvent::Error(\n        err.as_string()\n            .unwrap_or_else(|| \"failed to load rom\".to_string()),\n    ));\n}\n\n/// Sets up the window resize handler for responding to changes in the viewport size.\nfn set_resize_handler(window: &web_sys::Window, tx: &NesEventProxy) {\n    let on_resize = Closure::<dyn FnMut(_)>::new({\n        let tx = tx.clone();\n        move |_: web_sys::Event| {\n            if let Some(window) = web_sys::window() {\n                let width = window\n                    .inner_width()\n                    .ok()\n                    .and_then(|w| w.as_f64())\n                    .map_or(0.0, |w| w as f32);\n                let height = window\n                    .inner_height()\n                    .ok()\n                    .and_then(|h| h.as_f64())\n                    .map_or(0.0, |h| h as f32);\n                tx.event(RendererEvent::ViewportResized((width, height)));\n            }\n        }\n    });\n\n    let on_resize_cb = on_resize.as_ref().unchecked_ref();\n    if let Err(err) = window.add_event_listener_with_callback(\"resize\", on_resize_cb) {\n        on_error(tx, err);\n    }\n\n    on_resize.forget();\n}\n\n/// Sets up the onload handler for reading loaded files.\nfn set_file_onload_handler(\n    tx: NesEventProxy,\n    input_id: &'static str,\n    reader: web_sys::FileReader,\n    file_name: String,\n) -> anyhow::Result<()> {\n    let on_load = Closure::<dyn FnMut()>::new({\n        let reader = reader.clone();\n        move || match reader.result() {\n            Ok(result) => {\n                let data = Uint8Array::new(&result).to_vec();\n                let event = match input_id {\n                    html_ids::ROM_INPUT => {\n                        EmulationEvent::LoadRom((file_name.clone(), RomData(data)))\n                    }\n                    html_ids::REPLAY_INPUT => {\n                        EmulationEvent::LoadReplay((file_name.clone(), ReplayData(data)))\n                    }\n                    _ => unreachable!(\"unsupported input id\"),\n                };\n                tx.event(event);\n                focus_canvas();\n            }\n            Err(err) => on_error(&tx, err),\n        }\n    });\n\n    reader.set_onload(Some(on_load.as_ref().unchecked_ref()));\n\n    on_load.forget();\n\n    Ok(())\n}\n\n/// Sets up the onchange and oncancel handlers for file input elements.\nfn set_file_onchange_handlers(\n    document: &web_sys::Document,\n    tx: &NesEventProxy,\n    input_id: &'static str,\n) -> anyhow::Result<()> {\n    let on_change = Closure::<dyn FnMut(_)>::new({\n        let tx = tx.clone();\n        move |evt: web_sys::Event| match FileReader::new() {\n            Ok(reader) => {\n                let Some(file) = evt\n                    .current_target()\n                    .and_then(|target| target.dyn_into::<HtmlInputElement>().ok())\n                    .and_then(|input| input.files())\n                    .and_then(|files| files.item(0))\n                else {\n                    tx.event(UiEvent::FileDialogCancelled);\n                    return;\n                };\n                if let Err(err) = reader\n                    .read_as_array_buffer(&file)\n                    .map(|_| set_file_onload_handler(tx.clone(), input_id, reader, file.name()))\n                {\n                    on_error(&tx, err);\n                }\n            }\n            Err(err) => on_error(&tx, err),\n        }\n    });\n\n    let on_cancel = Closure::<dyn FnMut(_)>::new({\n        let tx = tx.clone();\n        move |_: web_sys::Event| {\n            focus_canvas();\n            tx.event(UiEvent::FileDialogCancelled);\n        }\n    });\n\n    let input = document\n        .get_element_by_id(input_id)\n        .with_context(|| format!(\"valid {input_id} button\"))?;\n    let on_change_cb = on_change.as_ref().unchecked_ref();\n    let on_cancel_cb = on_cancel.as_ref().unchecked_ref();\n    if let Err(err) = input\n        .add_event_listener_with_callback(\"change\", on_change_cb)\n        .and_then(|_| input.add_event_listener_with_callback(\"cancel\", on_cancel_cb))\n    {\n        on_error(tx, err)\n    }\n\n    on_change.forget();\n    on_cancel.forget();\n\n    Ok(())\n}\n\npub mod renderer {\n    use super::*;\n    use crate::nes::{\n        config::Config,\n        event::Response,\n        input::Gamepads,\n        renderer::{Viewport, gui::Gui},\n    };\n    use std::cell::RefCell;\n    use wasm_bindgen_futures::JsFuture;\n    use winit::dpi::LogicalSize;\n\n    pub fn constrain_window_to_viewport_impl(\n        renderer: &Renderer,\n        desired_window_width: f32,\n        cfg: &Config,\n    ) -> Response {\n        if let Some(window) = renderer.root_window()\n            && let Some(canvas) = crate::platform::get_canvas()\n        {\n            // Can't use `Window::inner_size` here because it's reported incorrectly so\n            // use `get_client_bounding_rect` instead.\n            let window_width = canvas.get_bounding_client_rect().width() as f32;\n\n            if window_width < desired_window_width {\n                tracing::debug!(\n                    \"window width ({window_width}) is less than desired ({desired_window_width})\"\n                );\n\n                let scale = if let Some(viewport_width) = web_sys::window()\n                    .and_then(|win| win.inner_width().ok())\n                    .and_then(|width| width.as_f64())\n                    .map(|width| width as f32)\n                {\n                    renderer.find_max_scale_for_width(0.8 * viewport_width, cfg)\n                } else {\n                    1.0\n                };\n\n                tracing::debug!(\"max scale for viewport: {scale}\");\n                let new_window_size = renderer.window_size_for_scale(cfg, scale);\n                if (window_width - new_window_size.x).abs() > 1.0 {\n                    tracing::debug!(\"constraining window to viewport: {new_window_size:?}\");\n\n                    let _ = window\n                        .request_inner_size(LogicalSize::new(new_window_size.x, new_window_size.y));\n                }\n                return Response {\n                    consumed: true,\n                    repaint: true,\n                };\n            }\n        }\n\n        Response::default()\n    }\n\n    pub fn set_clipboard_text(state: &Rc<RefCell<State>>, text: String) -> Response {\n        let State {\n            viewports, focused, ..\n        } = &mut *state.borrow_mut();\n\n        let Some(viewport) = focused.and_then(|id| viewports.get_mut(&id)) else {\n            return Response::default();\n        };\n\n        // Requires creating an event and setting the clipboard\n        // here because internally we try to manage a\n        // fallback clipboard for platforms not supported by the current\n        // clipboard backends.\n        //\n        // This has associated behavior in the renderer to prevent\n        // sending 'paste events' (ctrl/cmd+V) to bypass its internal\n        // clipboard handling.\n        viewport\n            .raw_input\n            .events\n            .push(egui::Event::Paste(text.clone()));\n        viewport.clipboard.set(text);\n\n        Response {\n            consumed: true,\n            repaint: true,\n        }\n    }\n\n    pub fn process_input(\n        ctx: &egui::Context,\n        state: &Rc<RefCell<State>>,\n        gui: &Rc<RefCell<Gui>>,\n    ) -> Response {\n        let (viewport_ui_cb, raw_input) = {\n            let State {\n                viewports,\n                start_time,\n                focused,\n                ..\n            } = &mut *state.borrow_mut();\n\n            let Some(viewport) = focused.and_then(|id| viewports.get_mut(&id)) else {\n                return Response::default();\n            };\n            let Some(window) = &viewport.window else {\n                return Response::default();\n            };\n            if viewport.occluded {\n                return Response::default();\n            }\n\n            Viewport::update_info(&mut viewport.info, ctx, window);\n\n            let viewport_ui_cb = viewport.viewport_ui_cb.clone();\n\n            // On Windows, a minimized window will have 0 width and height.\n            // See: https://github.com/rust-windowing/winit/issues/208\n            // This solves an issue where egui window positions would be changed when minimizing on Windows.\n            let screen_size_in_pixels = gui::lib::screen_size_in_pixels(window);\n            let screen_size_in_points =\n                screen_size_in_pixels / gui::lib::pixels_per_point(ctx, window);\n\n            let mut raw_input = viewport.raw_input.take();\n            raw_input.time = Some(start_time.elapsed().as_secs_f64());\n            raw_input.screen_rect = (screen_size_in_points.x > 0.0\n                && screen_size_in_points.y > 0.0)\n                .then(|| egui::Rect::from_min_size(egui::Pos2::ZERO, screen_size_in_points));\n            raw_input.viewport_id = viewport.ids.this;\n            raw_input.viewports = viewports\n                .iter()\n                .map(|(id, viewport)| (*id, viewport.info.clone()))\n                .collect();\n\n            (viewport_ui_cb, raw_input.take())\n        };\n\n        // For the purposes of processing inputs, we don't need or care about gamepad or cfg state\n        let config = Config::default();\n        let gamepads = Gamepads::default();\n        let mut output = ctx.run_ui(raw_input, |ui| match &viewport_ui_cb {\n            Some(viewport_ui_cb) => viewport_ui_cb(ui),\n            None => gui.borrow_mut().ui(ui, &config, &gamepads),\n        });\n\n        let State {\n            viewports, focused, ..\n        } = &mut *state.borrow_mut();\n\n        let Some(viewport) = focused.and_then(|id| viewports.get_mut(&id)) else {\n            return Response::default();\n        };\n\n        viewport.info.events.clear();\n\n        let commands = std::mem::take(&mut output.platform_output.commands);\n        for command in commands {\n            use egui::OutputCommand;\n            if let OutputCommand::CopyText(copied_text) = command {\n                tracing::warn!(\"Copied text: {copied_text}\");\n                if !copied_text.is_empty()\n                    && let Some(clipboard) =\n                        web_sys::window().map(|window| window.navigator().clipboard())\n                {\n                    let promise = clipboard.write_text(&copied_text);\n                    let future = JsFuture::from(promise);\n                    let future = async move {\n                        if let Err(err) = future.await {\n                            tracing::error!(\n                                \"Cut/Copy failed: {}\",\n                                err.as_string().unwrap_or_else(|| format!(\"{err:#?}\"))\n                            );\n                        }\n                    };\n                    thread::spawn(future);\n                }\n            }\n        }\n\n        Response {\n            consumed: true,\n            repaint: true,\n        }\n    }\n}\n\n/// Enumeration of supported operating systems.\n#[derive(Debug, Copy, Clone)]\n#[must_use]\nenum Os {\n    Unknown,\n    Windows,\n    #[allow(clippy::enum_variant_names)]\n    MacOs,\n    Linux,\n    Mobile,\n}\n\nimpl std::fmt::Display for Os {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let os = match self {\n            Os::Windows => \"Windows\",\n            Os::MacOs => \"macOS\",\n            Os::Linux => \"Linux\",\n            _ => \"Desktop\",\n        };\n        write!(f, \"{os}\")\n    }\n}\n\n/// Enumeration of supported CPU architectures.\n#[derive(Debug, Copy, Clone)]\n#[must_use]\nenum Arch {\n    X86_64,\n    Aarch64,\n}\n\nimpl std::fmt::Display for Arch {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let arch = match self {\n            Arch::X86_64 => \"x86_64\",\n            Arch::Aarch64 => \"aarch64\",\n        };\n        write!(f, \"{arch}\")\n    }\n}\n\n/// Converts the operating system and architecture to a human-readable string.\nconst fn platform_to_string(os: Os, arch: Arch) -> &'static str {\n    match (os, arch) {\n        (Os::Windows, Arch::X86_64) => \"Windows\",\n        (Os::MacOs, Arch::X86_64) => \"Mac - Intel Chip\",\n        (Os::MacOs, Arch::Aarch64) => \"Mac - Apple Chip\",\n        (Os::Linux, Arch::X86_64) => \"Linux\",\n        (Os::Mobile, _) => \"Mobile\",\n        _ => \"Desktop\",\n    }\n}\n\n#[wasm_bindgen]\nextern \"C\" {\n    /// Extends the `Navigator` object to support the `userAgentData` method.\n    #[wasm_bindgen(extends = web_sys::Navigator)]\n    type NavigatorExt;\n\n    /// The `NavigatorUAData` is what's returned from `navigator.userAgentData` on browsers that\n    /// support it.\n    type NavigatorUAData;\n\n    /// The `HighEntropyValues` object is returned from `navigator.userAgentData.getHighEntropyValues`.\n    #[derive(Debug)]\n    #[wasm_bindgen(js_name = Object)]\n    type HighEntropyValues;\n\n    /// `navigator.userAgentData` for browsers that support it.\n    #[wasm_bindgen(method, getter, js_name = userAgentData)]\n    fn user_agent_data(this: &NavigatorExt) -> Option<NavigatorUAData>;\n\n    /// `navigator.userAgentData.getHighEntropyValues()` for browsers that support it.\n    #[wasm_bindgen(method, js_name = getHighEntropyValues)]\n    async fn get_high_entropy_values(this: &NavigatorUAData, hints: Vec<String>) -> JsValue;\n\n    /// `HighEntropyValues.mobile` indicates whether the detected platform is a mobile device.\n    #[wasm_bindgen(method, getter, js_class = \"HighEntropyValues\")]\n    fn mobile(this: &HighEntropyValues) -> bool;\n\n    /// `HighEntropyValues.platform` indicates the detected OS platform (e.g. `Windows`).\n    #[wasm_bindgen(method, getter, js_class = \"HighEntropyValues\")]\n    fn platform(this: &HighEntropyValues) -> String;\n\n    /// `HighEntropyValues.platform` indicates the detected CPU architecture. (e.g. `x86`).\n    #[wasm_bindgen(method, getter, js_class = \"HighEntropyValues\")]\n    fn architecture(this: &HighEntropyValues) -> String;\n}\n\n/// Detects the user's platform and architecture.\nasync fn detect_user_platform() -> anyhow::Result<(Os, Arch)> {\n    let navigator = web_sys::window()\n        .map(|win| win.navigator())\n        .context(\"failed to get navigator\")?;\n\n    let user_agent = navigator.user_agent().unwrap_or_default();\n    let mut os = if user_agent.contains(\"Mobile\") {\n        anyhow::bail!(\"mobile download is unsupported\");\n    } else if user_agent.contains(\"Windows\") {\n        Os::Windows\n    } else if user_agent.contains(\"Mac\") {\n        Os::MacOs\n    } else if user_agent.contains(\"Linux\") {\n        Os::Linux\n    } else {\n        Os::Unknown\n    };\n    let mut arch = Arch::X86_64;\n\n    // FIXME: Currently unsupported on Firefox/Safari but it's the only way to derive\n    // macOS aarch64\n    let navigator_ext = NavigatorExt { obj: navigator };\n    let Some(ua_data) = navigator_ext.user_agent_data() else {\n        return Ok((os, arch));\n    };\n    let Ok(ua_values) = ua_data\n        .get_high_entropy_values(vec![\n            \"architecture\".into(),\n            \"platform\".into(),\n            \"bitness\".into(),\n        ])\n        .await\n        .dyn_into::<HighEntropyValues>()\n    else {\n        return Ok((os, arch));\n    };\n    if ua_values.mobile() {\n        os = Os::Mobile;\n    } else {\n        match ua_values.platform().as_str() {\n            \"Windows\" => os = Os::Windows,\n            \"macOS\" => {\n                os = Os::MacOs;\n                arch = if ua_values.architecture().starts_with(\"x86\") {\n                    Arch::X86_64\n                } else {\n                    Arch::Aarch64\n                };\n            }\n            \"Linux\" => os = Os::Linux,\n            _ => (),\n        }\n    };\n\n    Ok((os, arch))\n}\n\n/// Constructs the download URL for the given operating system and architecture.\nfn download_url_by_os(os: Os, arch: Arch) -> String {\n    let base_url =\n        format!(\"https://github.com/lukexor/tetanes/releases/download/tetanes-v{VERSION}\");\n    match os {\n        Os::MacOs => format!(\"{base_url}/{BIN_NAME}-{arch}.dmg\"),\n        Os::Windows => format!(\"{base_url}/{BIN_NAME}-{arch}.msi\"),\n        Os::Linux => format!(\"{base_url}/{BIN_NAME}-{arch}-unknown-linux-gnu.tar.gz\"),\n        _ => format!(\"https://github.com/lukexor/tetanes/releases/tag/tetanes-v{VERSION}\"),\n    }\n}\n\n/// Sets the download links to the correct release artifacts.\nfn set_download_versions(document: &web_sys::Document) {\n    if let Some(version) = document.get_element_by_id(html_ids::VERSION) {\n        version.set_inner_html(concat!(\"v\", env!(\"CARGO_PKG_VERSION\")));\n    }\n\n    let document = document.clone();\n    thread::spawn(async move {\n        // Update download links to the correct release artifacts\n        for (os, arch, id) in OS_OPTIONS {\n            if let Some(download_link) = document\n                .get_element_by_id(id)\n                .and_then(|el| el.dyn_into::<HtmlAnchorElement>().ok())\n            {\n                download_link.set_href(&download_url_by_os(os, arch));\n                let platform = platform_to_string(os, arch);\n                download_link.set_inner_text(&format!(\"Download for {platform}\"));\n            }\n        }\n\n        // Set selected version to detected platform\n        if let Some(selected_version) = document\n            .get_element_by_id(html_ids::SELECTED_VERSION)\n            .and_then(|el| el.dyn_into::<HtmlAnchorElement>().ok())\n            && let Ok((os, arch)) = detect_user_platform().await\n        {\n            selected_version.set_href(&download_url_by_os(os, arch));\n            let platform = platform_to_string(os, arch);\n            selected_version.set_inner_text(&format!(\"Download for {platform}\"));\n\n            // Add mouseover/mouseout event listeners to version download links and make them visible\n            if let (Some(version_download), Some(version_options)) = (\n                document.get_element_by_id(html_ids::VERSION_DOWNLOAD),\n                document.get_element_by_id(html_ids::VERSION_OPTIONS),\n            ) {\n                let on_mouseover = Closure::<dyn FnMut(_)>::new({\n                    let version_options = version_options.clone();\n                    move |_: web_sys::MouseEvent| {\n                        if let Err(err) = version_options.class_list().remove_1(\"hidden\") {\n                            tracing::error!(\"{err:?}\");\n                        }\n                    }\n                });\n                let on_mouseout = Closure::<dyn FnMut(_)>::new(move |_: web_sys::MouseEvent| {\n                    if let Err(err) = version_options.class_list().add_1(\"hidden\") {\n                        tracing::error!(\"{err:?}\");\n                    }\n                });\n                let on_mouseover_cb = on_mouseover.as_ref().unchecked_ref();\n                let on_mouseout_cb = on_mouseout.as_ref().unchecked_ref();\n                if let Err(err) = version_download\n                    .add_event_listener_with_callback(\"mouseover\", on_mouseover_cb)\n                    .and_then(|_| {\n                        version_download\n                            .add_event_listener_with_callback(\"mouseout\", on_mouseout_cb)\n                    })\n                    .and_then(|_| version_download.class_list().remove_1(\"hidden\"))\n                {\n                    tracing::error!(\"{err:?}\");\n                }\n                on_mouseover.forget();\n                on_mouseout.forget();\n                if let Err(err) = version_download.class_list().remove_1(\"hidden\") {\n                    tracing::error!(\"{err:?}\");\n                }\n            }\n        }\n    });\n}\n\n/// Hides the loading status when the WASM module has finished loading.\nfn finish_loading(document: &web_sys::Document, tx: &NesEventProxy) -> anyhow::Result<()> {\n    if let Some(status) = document.get_element_by_id(html_ids::LOADING_STATUS)\n        && let Err(err) = status.class_list().add_1(\"hidden\")\n    {\n        on_error(tx, err);\n    }\n\n    Ok(())\n}\n\nimpl Initialize for Running {\n    /// Initialize JS event handlers and DOM elements.\n    fn initialize(&mut self) -> anyhow::Result<()> {\n        let window = web_sys::window().context(\"valid window\")?;\n        let document = window.document().context(\"valid html document\")?;\n\n        set_download_versions(&document);\n        set_resize_handler(&window, &self.tx);\n        for input_id in [html_ids::ROM_INPUT, html_ids::REPLAY_INPUT] {\n            set_file_onchange_handlers(&document, &self.tx, input_id)?;\n        }\n\n        finish_loading(&document, &self.tx)?;\n\n        Ok(())\n    }\n}\n\nimpl Initialize for Renderer {\n    /// Initialize JS event handlers and DOM elements.\n    fn initialize(&mut self) -> anyhow::Result<()> {\n        let document = web_sys::window()\n            .and_then(|window| window.document())\n            .context(\"failed to get html document\")?;\n\n        let on_paste = Closure::<dyn FnMut(_)>::new({\n            let ctx = self.ctx.clone();\n            let state = Rc::clone(&self.state);\n            move |evt: web_sys::ClipboardEvent| {\n                if let Some(data) = evt.clipboard_data()\n                    && let Ok(text) = data.get_data(\"text\")\n                {\n                    let text = text.replace(\"\\r\\n\", \"\\n\");\n                    if !text.is_empty() {\n                        let res = renderer::set_clipboard_text(&state, text);\n                        if res.repaint {\n                            ctx.request_repaint();\n                        }\n                        if res.consumed {\n                            evt.stop_propagation();\n                            evt.prevent_default();\n                        }\n                    }\n                }\n            }\n        });\n        if let Err(err) =\n            document.add_event_listener_with_callback(\"paste\", on_paste.as_ref().unchecked_ref())\n        {\n            tracing::error!(\"failed to set paste handler: {err:?}\");\n        }\n        on_paste.forget();\n\n        let on_cut = Closure::<dyn FnMut(_)>::new({\n            let ctx = self.ctx.clone();\n            let state = Rc::clone(&self.state);\n            let gui = Rc::clone(&self.gui);\n            move |evt: web_sys::ClipboardEvent| {\n                // Some browsers require transient activation, so we have to write to the clipboard\n                // now\n                let res = renderer::process_input(&ctx, &state, &gui);\n                if res.repaint {\n                    ctx.request_repaint();\n                }\n                if res.consumed {\n                    evt.stop_propagation();\n                    evt.prevent_default();\n                }\n            }\n        });\n        if let Err(err) =\n            document.add_event_listener_with_callback(\"cut\", on_cut.as_ref().unchecked_ref())\n        {\n            tracing::error!(\"failed to set cut handler: {err:?}\");\n        }\n        on_cut.forget();\n\n        let on_copy = Closure::<dyn FnMut(_)>::new({\n            let ctx = self.ctx.clone();\n            let state = Rc::clone(&self.state);\n            let gui = Rc::clone(&self.gui);\n            move |evt: web_sys::ClipboardEvent| {\n                // Some browsers require transient activation, so we have to write to the clipboard\n                // now\n                let res = renderer::process_input(&ctx, &state, &gui);\n                if res.repaint {\n                    ctx.request_repaint();\n                }\n                if res.consumed {\n                    evt.stop_propagation();\n                    evt.prevent_default();\n                }\n            }\n        });\n        if let Err(err) =\n            document.add_event_listener_with_callback(\"copy\", on_copy.as_ref().unchecked_ref())\n        {\n            tracing::error!(\"failed to set copy handler: {err:?}\");\n        }\n        on_copy.forget();\n\n        if let Some(canvas) = get_canvas() {\n            let on_keydown = Closure::<dyn FnMut(_)>::new(move |evt: web_sys::KeyboardEvent| {\n                use egui::Key;\n\n                let prevent_default = Key::from_name(&evt.key()).is_none_or(|key| {\n                    // Allow ctrl/meta + X, C, V through\n                    !matches!(key, Key::X | Key::C | Key::V) || !(evt.ctrl_key() || evt.meta_key())\n                });\n\n                if prevent_default {\n                    evt.prevent_default();\n                }\n            });\n            if let Err(err) = canvas\n                .add_event_listener_with_callback(\"keydown\", on_keydown.as_ref().unchecked_ref())\n            {\n                tracing::error!(\"failed to set keydown handler: {err:?}\");\n            }\n            on_keydown.forget();\n\n            // Because we want to capture cut/copy/paste, `prevent_default` is disabled on winit,\n            // so restore default behavior on other winit events\n            for event in [\n                \"touchstart\",\n                \"keyup\",\n                \"wheel\",\n                \"contextmenu\",\n                \"pointerdown\",\n                \"pointermove\",\n            ] {\n                let on_event = Closure::<dyn FnMut(_)>::new({\n                    let canvas = canvas.clone();\n                    move |evt: web_sys::Event| {\n                        evt.prevent_default();\n                        if event == \"pointerdown\" {\n                            let _ = canvas.focus();\n                        }\n                    }\n                });\n                if let Err(err) = canvas\n                    .add_event_listener_with_callback(event, on_event.as_ref().unchecked_ref())\n                {\n                    tracing::error!(\"failed to set {event} handler: {err:?}\");\n                }\n                on_event.forget();\n            }\n        }\n\n        Ok(())\n    }\n}\n\npub fn download_save_states() -> anyhow::Result<()> {\n    use crate::nes::config::Config;\n    use anyhow::{Context, anyhow};\n    use base64::Engine;\n    use std::io::{Cursor, Write};\n    use tetanes_core::{control_deck::Config as DeckConfig, sys::fs::local_storage};\n    use wasm_bindgen::JsCast;\n    use web_sys::{self, js_sys};\n    use zip::write::{SimpleFileOptions, ZipWriter};\n\n    let local_storage = local_storage()?;\n    let mut zip = ZipWriter::new(Cursor::new(Vec::with_capacity(30 * 1024)));\n\n    for key in js_sys::Object::keys(&local_storage)\n        .iter()\n        .filter_map(|key| key.as_string())\n        .filter(|key| {\n            key.ends_with(Config::SAVE_EXTENSION) || key.ends_with(DeckConfig::SRAM_EXTENSION)\n        })\n    {\n        zip.start_file(&*key, SimpleFileOptions::default())?;\n        let Some(data) = local_storage\n            .get_item(&key)\n            .map_err(|_| anyhow!(\"failed to find data for {key}\"))?\n            .and_then(|value| serde_json::from_str::<Vec<u8>>(&value).ok())\n        else {\n            continue;\n        };\n        zip.write_all(&data)?;\n    }\n\n    let res = zip.finish()?;\n\n    let document = web_sys::window()\n        .and_then(|window| window.document())\n        .context(\"failed to get document\")?;\n\n    let link = document\n        .create_element(\"a\")\n        .map_err(|err| anyhow!(\"failed to create link element: {err:?}\"))?;\n    link.set_attribute(\n        \"href\",\n        &format!(\n            \"data:text/plain;base64,{}\",\n            base64::prelude::BASE64_STANDARD.encode(res.into_inner())\n        ),\n    )\n    .map_err(|err| anyhow!(\"failed to set href attribute: {err:?}\"))?;\n    link.set_attribute(\"download\", \"tetanes-save-states.zip\")\n        .map_err(|err| anyhow!(\"failed to set download attribute: {err:?}\"))?;\n\n    let link: web_sys::HtmlAnchorElement =\n        web_sys::HtmlAnchorElement::unchecked_from_js(link.into());\n    link.click();\n\n    Ok(())\n}\n\nimpl BuilderExt for WindowAttributes {\n    /// Sets platform-specific window options.\n    fn with_platform(self, _title: &str) -> Self {\n        // Prevent default false allows cut/copy/paste\n        self.with_canvas(get_canvas()).with_prevent_default(false)\n    }\n}\n\nmod html_ids {\n    //! HTML element IDs used to interact with the DOM.\n\n    pub(super) const CANVAS: &str = \"frame\";\n    pub(super) const LOADING_STATUS: &str = \"loading-status\";\n    pub(super) const ROM_INPUT: &str = \"load-rom\";\n    pub(super) const REPLAY_INPUT: &str = \"load-replay\";\n    pub(super) const VERSION: &str = \"version\";\n    pub(super) const VERSION_DOWNLOAD: &str = \"version-download\";\n    pub(super) const VERSION_OPTIONS: &str = \"version-options\";\n    pub(super) const SELECTED_VERSION: &str = \"selected-version\";\n    pub(super) const WINDOWS_X86_LINK: &str = \"x86_64-pc-windows-msvc\";\n    pub(super) const MACOS_X86_LINK: &str = \"x86_64-apple-darwin\";\n    pub(super) const MACOS_AARCH64_LINK: &str = \"aarch64-apple-darwin\";\n    pub(super) const LINUX_X86_LINK: &str = \"x86_64-unknown-linux-gnu\";\n}\n\n/// Gets the primary canvas element.\npub fn get_canvas() -> Option<web_sys::HtmlCanvasElement> {\n    web_sys::window()\n        .and_then(|win| win.document())\n        .and_then(|doc| doc.get_element_by_id(html_ids::CANVAS))\n        .and_then(|canvas| canvas.dyn_into::<HtmlCanvasElement>().ok())\n}\n\n/// Focuses the canvas element.\npub fn focus_canvas() {\n    if let Some(canvas) = get_canvas() {\n        let _ = canvas.focus();\n    }\n}\n"
  },
  {
    "path": "tetanes/src/sys/platform.rs",
    "content": "use cfg_if::cfg_if;\n\ncfg_if! {\n    if #[cfg(target_arch = \"wasm32\")] {\n        mod wasm;\n        pub use wasm::*;\n    } else {\n        mod os;\n        pub use os::*;\n    }\n}\n"
  },
  {
    "path": "tetanes/src/sys/thread/os.rs",
    "content": "use std::{future::Future, thread};\nuse tetanes_core::time::{Duration, Instant};\n\n/// Spawn a future to be run until completion.\npub fn spawn_impl<F>(future: F)\nwhere\n    F: Future<Output = ()> + 'static,\n{\n    pollster::block_on(future)\n}\n\n/// Blocks unless or until the current thread's token is made available or\n/// the specified duration has been reached (may wake spuriously).\npub fn park_timeout_impl(dur: Duration) {\n    let beginning_park = Instant::now();\n    let mut timeout_remaining = dur;\n    loop {\n        thread::park_timeout(timeout_remaining);\n        let elapsed = beginning_park.elapsed();\n        if elapsed >= dur {\n            break;\n        }\n        timeout_remaining = dur - elapsed;\n    }\n}\n\n/// Sleeps the current thread for the specified duration.\npub async fn sleep_impl(dur: Duration) {\n    // TODO: Async is a lie and is only required to allow the web impl to be non-blocking\n    thread::sleep(dur);\n}\n"
  },
  {
    "path": "tetanes/src/sys/thread/wasm.rs",
    "content": "use std::future::Future;\nuse tetanes_core::time::Duration;\nuse wasm_bindgen_futures::JsFuture;\nuse web_sys::js_sys::{Function, Promise};\n\n/// Spawn a future to be run until completion.\npub fn spawn_impl<F>(future: F)\nwhere\n    F: Future<Output = ()> + 'static,\n{\n    wasm_bindgen_futures::spawn_local(future);\n}\n\n/// Blocking, and thus parking is not allowed in wasm.\n#[allow(clippy::missing_const_for_fn)]\npub fn park_timeout_impl(_dur: Duration) {}\n\n/// Sleeps the current thread for the specified duration.\npub async fn sleep_impl(dur: Duration) {\n    let mut cb = |resolve: Function, _reject: Function| {\n        if let Some(window) = web_sys::window()\n            && let Err(err) = window.set_timeout_with_callback_and_timeout_and_arguments_0(\n                &resolve,\n                dur.as_secs() as i32,\n            )\n        {\n            tracing::error!(\"failed to call window.set_timeout: {err:?}\");\n        }\n    };\n    if let Err(err) = JsFuture::from(Promise::new(&mut cb)).await {\n        tracing::error!(\"failed to create sleep future: {err:?}\");\n    }\n}\n"
  },
  {
    "path": "tetanes/src/sys/thread.rs",
    "content": "use cfg_if::cfg_if;\n\ncfg_if! {\n    if #[cfg(target_arch = \"wasm32\")] {\n        mod wasm;\n        pub use wasm::*;\n    } else {\n        mod os;\n        pub use os::*;\n    }\n}\n"
  },
  {
    "path": "tetanes/src/sys.rs",
    "content": "pub mod info;\npub mod logging;\npub mod platform;\npub mod thread;\n\n#[derive(Debug)]\npub struct DiskUsage {\n    pub read_bytes: u64,\n    pub total_read_bytes: u64,\n    pub written_bytes: u64,\n    pub total_written_bytes: u64,\n}\n\n#[derive(Debug)]\npub struct SystemStats {\n    pub cpu_usage: f32,\n    pub memory: u64,\n    pub disk_usage: DiskUsage,\n}\n\npub trait SystemInfo {\n    fn update(&mut self);\n    fn stats(&self) -> Option<SystemStats>;\n}\n"
  },
  {
    "path": "tetanes/src/thread.rs",
    "content": "use crate::sys::thread;\nuse std::future::Future;\nuse tetanes_core::time::Duration;\n\n/// Spawn a future to be run until completion.\npub fn spawn<F>(future: F)\nwhere\n    F: Future<Output = ()> + 'static,\n{\n    thread::spawn_impl(future);\n}\n\n/// Blocks unless or until the current thread's token is made available or\n/// the specified duration has been reached (may wake spuriously).\npub fn park_timeout(dur: Duration) {\n    thread::park_timeout_impl(dur);\n}\n\n/// Sleeps the current thread for the specified duration.\npub async fn sleep(dur: Duration) {\n    thread::sleep_impl(dur).await\n}\n"
  },
  {
    "path": "tetanes/wix/main.wxs",
    "content": "<?xml version='1.0' encoding='windows-1252'?>\n<!--\n  Copyright (C) 2017 Christopher R. Field.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n  https://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n-->\n\n<!--\n  The \"cargo wix\" subcommand provides a variety of predefined variables available\n  for customization of this template. The values for each variable are set at\n  installer creation time. The following variables are available:\n\n  TargetTriple      = The rustc target triple name.\n  TargetEnv         = The rustc target environment. This is typically either\n                      \"msvc\" or \"gnu\" depending on the toolchain downloaded and\n                      installed.\n  TargetVendor      = The rustc target vendor. This is typically \"pc\", but Rust\n                      does support other vendors, like \"uwp\".\n  CargoTargetBinDir = The complete path to the directory containing the\n                      binaries (exes) to include. The default would be\n                      \"target\\release\\\". If an explicit rustc target triple is\n                      used, i.e. cross-compiling, then the default path would\n                      be \"target\\<CARGO_TARGET>\\<CARGO_PROFILE>\",\n                      where \"<CARGO_TARGET>\" is replaced with the \"CargoTarget\"\n                      variable value and \"<CARGO_PROFILE>\" is replaced with the\n                      value from the \"CargoProfile\" variable. This can also\n                      be overriden manually with tne \"target-bin-dir\" flag.\n  CargoTargetDir    = The path to the directory for the build artifacts, i.e.\n                      \"target\".\n  CargoProfile      = The cargo profile used to build the binaries\n                      (usually \"debug\" or \"release\").\n  Version           = The version for the installer. The default is the\n                      \"Major.Minor.Fix\" semantic versioning number of the Rust\n                      package.\n-->\n\n<!--\n  Please do not remove these pre-processor If-Else blocks. These are used with\n  the `cargo wix` subcommand to automatically determine the installation\n  destination for 32-bit versus 64-bit installers. Removal of these lines will\n  cause installation errors.\n-->\n<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?>\n    <?define PlatformProgramFilesFolder = \"ProgramFiles64Folder\" ?>\n<?else ?>\n    <?define PlatformProgramFilesFolder = \"ProgramFilesFolder\" ?>\n<?endif ?>\n\n<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>\n\n    <Product\n        Id='*'\n        Name='TetaNES'\n        UpgradeCode='DB76CEB0-15B8-4727-9C3E-55819AB5E7B9'\n        Manufacturer='Luke Petherbridge'\n        Language='1033'\n        Codepage='1252'\n        Version='$(var.Version)'>\n\n        <Package Id='*'\n            Keywords='Installer'\n            Description='A cross-platform NES Emulator written in Rust and wgpu.'\n            Manufacturer='Luke Petherbridge'\n            InstallerVersion='450'\n            Languages='1033'\n            Compressed='yes'\n            InstallScope='perMachine'\n            SummaryCodepage='1252'\n            />\n\n        <MajorUpgrade\n            Schedule='afterInstallInitialize'\n            DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.'/>\n\n        <Media Id='1' Cabinet='tetanes.cab' EmbedCab='yes'/>\n        <Property Id='DiskPrompt' Value='TetaNES Installation'/>\n\n        <Directory Id='TARGETDIR' Name='SourceDir'>\n            <Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>\n                <Directory Id='APPLICATIONFOLDER' Name='TetaNES'>\n                    <Component Id='Path' Guid='5731AE63-80DE-4CD7-ADFA-9E79BEDCE08B' KeyPath='yes'>\n                        <Environment\n                            Id='PATH'\n                            Name='PATH'\n                            Value='[Bin]'\n                            Permanent='no'\n                            Part='last'\n                            Action='set'\n                            System='yes'/>\n                    </Component>\n                    <Component Id='TetaNES.exe' Guid='70d7d0a6-3208-430d-896c-a9116cfcbeb9'>\n                        <File\n                            Id='TetaNES.exe'\n                            Name='TetaNES.exe'\n                            DiskId='1'\n                            Source='$(var.CargoTargetBinDir)\\tetanes.exe'\n                            KeyPath='yes'\n                            Checksum='yes'/>\n                    </Component>\n                </Directory>\n            </Directory>\n        </Directory>\n\n        <Feature\n            Id='Application'\n            Title='Application'\n            Description='Installs the [ProductName] executable and dependencies.'\n            Level='1'\n            ConfigurableDirectory='APPLICATIONFOLDER'\n            AllowAdvertise='no'\n            Display='expand'\n            Absent='disallow'>\n\n            <ComponentRef Id='TetaNES.exe'/>\n\n            <Feature\n                Id='Environment'\n                Title='PATH Environment Variable'\n                Description='Add the install location of [ProductName] to the PATH system environment variable. This allows [ProductName] to be called from any location.'\n                Level='1'\n                Absent='allow'>\n                <ComponentRef Id='Path'/>\n            </Feature>\n        </Feature>\n\n        <SetProperty Id='ARPINSTALLLOCATION' Value='[APPLICATIONFOLDER]' After='CostFinalize'/>\n        <Property Id='WIXUI_INSTALLDIR' Value='APPLICATIONFOLDER' />\n\n        <Icon Id='ProductICO' SourceFile='.\\assets\\windows\\tetanes_icon.ico'/>\n        <Property Id='ARPPRODUCTICON' Value='ProductICO' />\n        <Property Id='ARPHELPLINK' Value='https://docs.rs/tetanes'/>\n        <!-- The banner BMP dimensions are 493 x 58 pixels. -->\n        <WixVariable Id='WixUIBannerBmp' Value='.\\assets\\windows\\tetanes_banner.bmp'/>\n        <!-- The dialog BMP dimensions are 493 x 312 pixels. -->\n        <WixVariable Id='WixUIDialogBmp' Value='.\\assets\\windows\\tetanes_dialog.bmp'/>\n\n        <UI>\n            <UIRef Id='WixUI_InstallDir'/>\n            <Publish Dialog='WelcomeDlg' Control='Next' Event='NewDialog' Value='InstallDirDlg' Order='99'>1</Publish>\n            <Publish Dialog='ExitDialog' Control='Finish' Event='DoAction' Value='LaunchApplication' Order='99'>WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>\n        </UI>\n        <Property Id='WIXUI_EXITDIALOGOPTIONALTEXT' Value='Have fun gaming!' />\n        <Property Id='WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT' Value='Launch TetaNES' />\n        <Property Id='WixShellExecTarget' Value='[#TetaNES.exe]' />\n        <CustomAction Id='LaunchApplication' BinaryKey='WixCA' DllEntry='WixShellExec' Impersonate='yes' />\n    </Product>\n\n</Wix>\n"
  },
  {
    "path": "tetanes-core/CHANGELOG.md",
    "content": "<!-- markdownlint-disable-file no-duplicate-heading -->\n\n# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [0.14.1](https://github.com/lukexor/tetanes/compare/0.13.0..0.14.1) - 2026-04-20\n\n### 🐛 Bug Fixes\n\n\n- Fixed palette x offset - ([350a892](https://github.com/lukexor/tetanes/commit/350a8925dffcd49e737884971b90f11d2a737996))\n\n### 🎨 Styling\n\n\n- Fixed 1.85 lints - ([ddfb3eb](https://github.com/lukexor/tetanes/commit/ddfb3eb318397984df76fbd3cb36c8a383ab0368))\n- Fixed nightly lints - ([801bb3c](https://github.com/lukexor/tetanes/commit/801bb3c95a95a7f98efb5a993f2efe7061daf00b))\n\n\n## [0.13.0](https://github.com/lukexor/tetanes/compare/0.12.2..0.13.0) - 2026-02-14\n\n### 🐛 Bug Fixes\n\n\n- Change cycle to u32 to improve 32-bit platforms like wasm - ([86f597c](https://github.com/lukexor/tetanes/commit/86f597c3c1422040e8f98ac4dfb29f3a2ffdc81f))\n- Fixed turbo - ([5f149c3](https://github.com/lukexor/tetanes/commit/5f149c3be9eaa74bd5583dd41daf4cbae2cbf0a9))\n- Ignore header bytes 14/15. closed #430 - ([8ff2bd9](https://github.com/lukexor/tetanes/commit/8ff2bd96d055c5a2551e44deb69c8a5e1c5b191e))\n- Fixed memory methods to allow working with &[u8] - ([6c2cf4a](https://github.com/lukexor/tetanes/commit/6c2cf4aa33e5fa7ba3c903c775b6ecfcf47f059d))\n\n### ⚡ Performance\n\n\n- Cpu opcode refactor - ([8916097](https://github.com/lukexor/tetanes/commit/8916097c39ee18828d9fd7bf70185a19eaf8e098))\n- Convert Vec<u8> to Box<[u8]> for ~2.5% gain - ([6f34112](https://github.com/lukexor/tetanes/commit/6f34112aa193499ed69115fe8f70774bbe6e4a74))\n\n### ⚙️ Miscellaneous Tasks\n\n\n- Updated deps - ([ccb7463](https://github.com/lukexor/tetanes/commit/ccb7463c98be1e35fe7e6a47c2df9d76796ee349))\n- Update deps - ([782dc21](https://github.com/lukexor/tetanes/commit/782dc213f25f34cc47af0c2f71cdf9bfd44ae28b))\n\n\n## [0.12.2](https://github.com/lukexor/tetanes/compare/0.12.1..0.12.2) - 2025-04-05\n\n### 🐛 Bug Fixes\n\n\n- Revert input serialization change, as it broke run-ahead - ([6489c57](https://github.com/lukexor/tetanes/commit/6489c579c738f88068423affb3833edd9105e523))\n- Fix touch events - ([1a8d3da](https://github.com/lukexor/tetanes/commit/1a8d3dad5243bb31858575e99e6961c33a2521d4))\n- Basic cpu error recovery options - ([c60d8d1](https://github.com/lukexor/tetanes/commit/c60d8d1d02dde9e3383849e1af43111f26db29c7))\n\n### 🚜 Refactor\n\n\n- Changed input serializing - ([ad37b47](https://github.com/lukexor/tetanes/commit/ad37b47ab07a54597eeb91b95fb524a0ea6b4e43))\n- Cleaned up Memory struct - ([7cb31fb](https://github.com/lukexor/tetanes/commit/7cb31fb3878ee4e7e9b4ca9eabe123d570cc3176))\n\n### 📚 Documentation\n\n\n- Fix urls - ([8136c42](https://github.com/lukexor/tetanes/commit/8136c42bdab474e17ceda78819225349a3f1c520))\n- Add notes about stability - ([af0ce20](https://github.com/lukexor/tetanes/commit/af0ce20410f4088453788a264f8102823c7cf7da))\n- Ensure features display in docs.rs - ([f58b31c](https://github.com/lukexor/tetanes/commit/f58b31cc9d27f0187d759796c64e34ccea3f784a))\n\n### 🧪 Testing\n\n\n- Fix tracing init in tests - ([e368ad5](https://github.com/lukexor/tetanes/commit/e368ad5ab41d3c484dcaf17a7a8ff8abae48aef9))\n\n\n## [0.12.1](https://github.com/lukexor/tetanes/compare/0.12.0..0.12.1) - 2025-03-13\n\n### ⛰️  Features\n\n\n- Added shortcuts for shaders and ppu warmup flag - ([408b122](https://github.com/lukexor/tetanes/commit/408b122ed98f7edb7a26085fb921aa006bde7091))\n\n### 🐛 Bug Fixes\n\n\n- Fixed issues with some mmc1 games - ([496cf41](https://github.com/lukexor/tetanes/commit/496cf41ced63949fd6d8be5402989e927baf92b8))\n\n### 📚 Documentation\n\n\n- Fixed cargo doc url - ([782f7c5](https://github.com/lukexor/tetanes/commit/782f7c51b68c5fb52b483a3f151bd3db227286a9))\n- Updated changelog and readmes - ([a4a3e8c](https://github.com/lukexor/tetanes/commit/a4a3e8c0775a7261b91f4238756ac5a20d2c4b48))\n\n### ⚙️ Miscellaneous Tasks\n\n\n- Fix/update ci, docs, and fixed nightly issue with tetanes-core - ([a6150ba](https://github.com/lukexor/tetanes/commit/a6150bad6703bbc661d7d5c8b63f5a6d47991868))\n\n\n<!-- markdownlint-disable-file no-duplicate-heading no-multiple-blanks line-length -->\n\n# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [0.12.0](https://github.com/lukexor/tetanes/compare/tetanes-v0.11.0..tetanes-v0.12.0) - 2025-03-12\n\n### ⛰️  Features\n\n\n- Jalecoss88006 - ([406777a](https://github.com/lukexor/tetanes/commit/406777abad8d61490aae2a33e2e71fc617db3f55))\n- Namco163 - ([89d7fb4](https://github.com/lukexor/tetanes/commit/89d7fb4617bf844ad2090cd92f0e92cda9cc91fc))\n- Added sunsoft/fme-7 - ([303dad8](https://github.com/lukexor/tetanes/commit/303dad85d0a6586a88b7067f2070c5ac9e4da6e4))\n- Added nina003/nina006 - ([29503c3](https://github.com/lukexor/tetanes/commit/29503c3efc81fb3110eef63e9666de1e0c912015))\n- Added dxrom ([#340](https://github.com/lukexor/tetanes/issues/340)) - ([906af59](https://github.com/lukexor/tetanes/commit/906af59038e95874dda254e02998030c913d8c61))\n- Ppu-viewer ([#339](https://github.com/lukexor/tetanes/issues/339)) - ([fce7d89](https://github.com/lukexor/tetanes/commit/fce7d89f78148e9a367d47122eef7e6e8fe45b34))\n- Bandai mappers 016, 153, 157, 159 ([#335](https://github.com/lukexor/tetanes/issues/335)) - ([f555ea4](https://github.com/lukexor/tetanes/commit/f555ea48d0273bc9d41b998926d451398acbb73c))\n- Allow exporting save states in web ([#311](https://github.com/lukexor/tetanes/issues/311)) - ([627bbec](https://github.com/lukexor/tetanes/commit/627bbece49739ff479e69ba9e83df828c4d4a633))\n- Add a debug build label - ([46b3d94](https://github.com/lukexor/tetanes/commit/46b3d94e5fd24900a95554a295257f0891ac1c53))\n- Add test panic debug button - ([3866efa](https://github.com/lukexor/tetanes/commit/3866efab39ced8f3431ee27d24962a55397a9f07))\n- Added screen reader/accesskit support - ([5fd1a73](https://github.com/lukexor/tetanes/commit/5fd1a73f112f74a6c0a81e722485842dd37e0a38))\n- Added ui setting/debug windows - ([db8b122](https://github.com/lukexor/tetanes/commit/db8b122af6c5a52ad23ed89ffd6f2feb35515603))\n- Enable webgpu for browsers that support it. closes #297 ([#298](https://github.com/lukexor/tetanes/issues/298)) - ([a6bde61](https://github.com/lukexor/tetanes/commit/a6bde619454bf8d77f98d462d89eccea4b0e42fc))\n\n### 🐛 Bug Fixes\n\n\n- Fixed several issues - ([60fcd90](https://github.com/lukexor/tetanes/commit/60fcd90e740833e94deb98896a17a51fcda38998))\n- Fix cycle overflow - ([a4e1f05](https://github.com/lukexor/tetanes/commit/a4e1f058c6e899e9fd11578bfb40a36d6ea1980e))\n- Add temporary webgpu flag - ([179e868](https://github.com/lukexor/tetanes/commit/179e868c9e1cee92df1d0568b60403c2df7579cb))\n- Temporary wasm fix for check-cfg - ([30c6a61](https://github.com/lukexor/tetanes/commit/30c6a61c0d562f875a0d979ad655e199d7c7019a))\n- Fix tetanes-core compiling on stable. closes #360 - ([adc5673](https://github.com/lukexor/tetanes/commit/adc5673a3ed5d80aff339c3ab6d95013fcb2d715))\n- Fixed deny.toml - ([2c1f186](https://github.com/lukexor/tetanes/commit/2c1f18603f043c2dcb17db8fa8958ea1cbfd88d4))\n- Fixed bank size check - ([c84c012](https://github.com/lukexor/tetanes/commit/c84c012c310ad466c5167b94f0228f5a482dec43))\n- Fixed wasm - ([bd27814](https://github.com/lukexor/tetanes/commit/bd278140bcc7e7d433917f14e99038f2e6453027))\n- Fixed video frame size - ([153094d](https://github.com/lukexor/tetanes/commit/153094d81d444376b112224409544375588c4f97))\n- Fix scroll issues - ([218d786](https://github.com/lukexor/tetanes/commit/218d7860421eb4cfc4d7b833132f4c476935777a))\n- Fixed increasing scale on web - ([8c4265e](https://github.com/lukexor/tetanes/commit/8c4265e10fc8b62cd7dcaa8a828fed1a07100a9f))\n- Fixed shortcut text - ([cb73c21](https://github.com/lukexor/tetanes/commit/cb73c216936ad49dca4e2595485df4ccea957eaa))\n- Fixed joypad keybinds and some UI styling - ([bc2f093](https://github.com/lukexor/tetanes/commit/bc2f093b4d02c54744f791f336a102424a7e5af1))\n- Enable puffin on wasm - ([0b6f794](https://github.com/lukexor/tetanes/commit/0b6f79429c5d2a642c0ef6301bbcc9818973a234))\n- Fix window theme - ([e3c42c7](https://github.com/lukexor/tetanes/commit/e3c42c7720f558c7348e2b82b3573d4748158850))\n- Fixed window aspect ratio - ([17db5c8](https://github.com/lukexor/tetanes/commit/17db5c8a037ab3aefab560bca67545964069658f))\n- Don't log/error when sending frames while paused - ([50825f8](https://github.com/lukexor/tetanes/commit/50825f82e9f04418fdefd56707ef2ec50cddd5ed))\n- Fixed pause state when loading replay - ([d743b31](https://github.com/lukexor/tetanes/commit/d743b31c190cd93e42e3ab78b497e59bcc4ade88))\n- Fixed roms path to default to current directory, if valid, and canonicalize - ([e00273f](https://github.com/lukexor/tetanes/commit/e00273f740f7fc095bc02c7ce6d0ba132a14c9bc))\n- Ensure pixel brightness is using the same palette - ([ad2f873](https://github.com/lukexor/tetanes/commit/ad2f873f5652016b96317c000b4abbe0e35de421))\n- Move some calculations to vertex shader that don't depend on v_uv - ([a6f262d](https://github.com/lukexor/tetanes/commit/a6f262db5d83950e86e0ec78bb74fc63e5c2bf85))\n- Fixed logging location - ([ff36033](https://github.com/lukexor/tetanes/commit/ff36033d7bbbf64924d97d6e9a88dcf4db7dc60c))\n- Fixed issue with lower end platforms not supporting larger texture dimensions - ([ef214db](https://github.com/lukexor/tetanes/commit/ef214dbc2f2eee016b7abdb0c2b0ee1858381ee4))\n- Fix window resizing while handling zoom changes - ([6b3f690](https://github.com/lukexor/tetanes/commit/6b3f690b8ec21b907d353a7cad8561217e8d9dcf))\n\n### 🚜 Refactor\n\n\n- [**breaking**] Split mapper traits - ([3e4a372](https://github.com/lukexor/tetanes/commit/3e4a372dfdc4295851c93cca96044f84645ae14e))\n- Removed egui-wgpu and egui-winit dependencies. ([#315](https://github.com/lukexor/tetanes/issues/315)) - ([b3d4e2c](https://github.com/lukexor/tetanes/commit/b3d4e2c70c6ee4cfa9aaf53a11c1ae802610ff99))\n- Platform/ui cleanup - ([39f66e6](https://github.com/lukexor/tetanes/commit/39f66e6e912f9c95cf9c458cd072e5e041af09e3))\n- Moved around platform code to condense it - ([0f18928](https://github.com/lukexor/tetanes/commit/0f18928b8f8ed031cac7a170557c0296916c99bc))\n- Prefer deferred viewports ([#306](https://github.com/lukexor/tetanes/issues/306)) - ([e1e60d1](https://github.com/lukexor/tetanes/commit/e1e60d19599ab883cbb034047519e6eb831d6c6c))\n\n### 📚 Documentation\n\n\n- Extra cpu comments - ([80f3366](https://github.com/lukexor/tetanes/commit/80f3366e3fab1257201ab0d9af673c4318edabef))\n\n### ⚡ Performance\n\n\n- Restore sprite presence check, ~2% gain - ([c6d353a](https://github.com/lukexor/tetanes/commit/c6d353a8fc12b506656a8cd70561ef1830ba9284))\n- More perf and added flamegraph - ([31edf0c](https://github.com/lukexor/tetanes/commit/31edf0c63bcc30867f0049a231e7d366db4bde8d))\n- Performance tweaks - ([d9a3019](https://github.com/lukexor/tetanes/commit/d9a3019ec0c0014d8850158d38c27289dc885020))\n\n### 🎨 Styling\n\n\n- Fix lints - ([bc9f6bc](https://github.com/lukexor/tetanes/commit/bc9f6bc293d413cf780a2aa0253ad7d64951d193))\n- Slight cleanup - ([63e31a9](https://github.com/lukexor/tetanes/commit/63e31a9755266bec88d5c79e064506999f03aea2))\n- Fixed format - ([d62ea28](https://github.com/lukexor/tetanes/commit/d62ea285cb5fe73ac41e7364f0ca3f32281a0e88))\n\n### ⚙️ Miscellaneous Tasks\n\n\n- Update deps - ([5b077c0](https://github.com/lukexor/tetanes/commit/5b077c01b1e68a60d3e295fe108732a3b8abbbd6))\n- Bumped version - ([28fa93f](https://github.com/lukexor/tetanes/commit/28fa93f226447fd409b5d3846cd0f7e14a793f83))\n- Update deps - ([509dbd4](https://github.com/lukexor/tetanes/commit/509dbd48a34cd6a360da0fba3786ed73445381fc))\n- Fix ci - ([da64229](https://github.com/lukexor/tetanes/commit/da64229966295d85b0f62b0e3827d76767116602))\n- Fix deny.toml - ([64a2401](https://github.com/lukexor/tetanes/commit/64a24010c72926c555ae74ffb4f1acb2c0aefffb))\n- Updated deps - ([906c877](https://github.com/lukexor/tetanes/commit/906c877700d551fd74e0545e03f544ea2255823f))\n- Updated deps - ([825719e](https://github.com/lukexor/tetanes/commit/825719e7f56ef6263f22a6da82d31f02d05af570))\n- Updated deps - ([4712d6d](https://github.com/lukexor/tetanes/commit/4712d6d6de3ce7eccec8f1971fcb0f2411f91e3d))\n- Restore nightly ci - ([eb2a2c5](https://github.com/lukexor/tetanes/commit/eb2a2c58ecd802810709f5e367253857d51a47d0))\n- Update dependencies - ([4947a8c](https://github.com/lukexor/tetanes/commit/4947a8cf6883eda0b0c55fcd7bcf98cf8fd7dee9))\n- Remove puffin_egui reference in wasm - ([16845f3](https://github.com/lukexor/tetanes/commit/16845f39e28c816c847a9d403dbedde38c815c1d))\n- More dependency cleanup - ([1971e4f](https://github.com/lukexor/tetanes/commit/1971e4f2c5aaf6f8a2d6ce2a03c978362d44afe1))\n- Clean up dependencies - ([254fe54](https://github.com/lukexor/tetanes/commit/254fe543293b0c96c78ce25bdaeef2f250a9fb14))\n- Remove auto-assign from triage - ([9a2804b](https://github.com/lukexor/tetanes/commit/9a2804b94b1a412214159495d2e6410a63555572))\n- Restrict homebrew cd to .rb files - ([3c1e390](https://github.com/lukexor/tetanes/commit/3c1e3907d7477dbe9f6953d9b8b9b0aeb1ef5966))\n- Fix update homebrew formula runs-on - ([9e66a07](https://github.com/lukexor/tetanes/commit/9e66a073fa1ef9e276a2ca85ccc4e4281b50e7bc))\n- Fix cd upload - ([892d184](https://github.com/lukexor/tetanes/commit/892d184cc25ca7903cb4a5f7372f47e722866125))\n- Restore RELEASE_PLZ_TOKEN - ([18de294](https://github.com/lukexor/tetanes/commit/18de2946b82a44efdba96e5918eba381ad3a1a75))\n- Remove need for RELEASE_PLZ_TOKEN - ([b6c8478](https://github.com/lukexor/tetanes/commit/b6c84780123ca5d9dfc841e2a3e6266b7d3cc4b9))\n- Try to fix release cd - ([c7d5f51](https://github.com/lukexor/tetanes/commit/c7d5f514a84bd3b728686893e3211b63ec21a9c9))\n\n\n## [0.11.0](https://github.com/lukexor/tetanes/compare/tetanes-core-v0.10.0..tetanes-core-v0.11.0) - 2024-06-12\n\n### ⛰️  Features\n\n\n- Added config and save/sram state persistence to web ([#274](https://github.com/lukexor/tetanes/pull/274)) - ([8c7f6df](https://github.com/lukexor/tetanes/commit/8c7f6df4a8894b544da1c6480659ee26ea28f342))\n- Added mapper 11 - ([03d2074](https://github.com/lukexor/tetanes/commit/03d2074d3d58fcf652fecb9d77f4e96e8c007aae))\n- Updated game database mapper names - ([86d246b](https://github.com/lukexor/tetanes/commit/86d246be9a52b64ed4191c970c6a727a31c21cb5))\n\n### 🐛 Bug Fixes\n\n\n- Ntsc tweaks - ([3042fa7](https://github.com/lukexor/tetanes/commit/3042fa7b928faf69e10040b4eb981a4c4f8f3ce3))\n- Fixed fast forwarding - ([a6f87bb](https://github.com/lukexor/tetanes/commit/a6f87bb58ac3728471f673ade821e18579686b1a))\n- Cleaned up pausing, parking, and control flow. Closes [#251](https://github.com/lukexor/tetanes/pull/251) - ([72cf88a](https://github.com/lukexor/tetanes/commit/72cf88ac6991953222bd3dd1d395f7f9035c98ef))\n- Disable rewind when low on memory. clear rewind memory when disabled - ([4d5e1c4](https://github.com/lukexor/tetanes/commit/4d5e1c4dbe43cceb9ab8d4c33ca832830b2d31d8))\n\n### 🚜 Refactor\n\n\n- Removed a number of panic cases and cleaned up platform checks - ([bdb71a9](https://github.com/lukexor/tetanes/commit/bdb71a96792778cb0ad6bedf44e0ef5cbfa703e4))\n- Add Sram trait and some mapper cleanup - ([ad03755](https://github.com/lukexor/tetanes/commit/ad0375506644f990e726c536f29bdf62d34d9e84))\n\n### 📚 Documentation\n\n\n- Fixed docs and changelog - ([4c7a694](https://github.com/lukexor/tetanes/commit/4c7a6949e52b6734fd6a78f6d9567c70e12b3ae4))\n- Fixed docs - ([7a491c1](https://github.com/lukexor/tetanes/commit/7a491c14a2cb93db489c8bcb05d65f63bd1ed9d7))\n\n### 🧪 Testing\n\n\n- Update tests after ntsc change - ([f47f6c0](https://github.com/lukexor/tetanes/commit/f47f6c08ec2678c90e66b58d1297d20a6a72090b))\n- Avoid serde_json::from_reader in tests as it's faster to just … ([#244](https://github.com/lukexor/tetanes/pull/244)) - ([3ca03ac](https://github.com/lukexor/tetanes/commit/3ca03ac68fab4d809dee39466fd661f887d2575d))\n\n\n## [0.10.0](https://github.com/lukexor/tetanes/compare/tetanes-v0.9.0..tetanes-core-v0.10.0) - 2024-05-16\n\nInitial release.\n"
  },
  {
    "path": "tetanes-core/Cargo.toml",
    "content": "[package]\nname = \"tetanes-core\"\nversion.workspace = true\nrust-version = \"1.85.0\"\nedition.workspace = true\nlicense.workspace = true\ndescription = \"A NES Emulator written in Rust\"\nauthors.workspace = true\nreadme = \"README.md\"\nrepository.workspace = true\nhomepage.workspace = true\ncategories = [\"emulators\"]\nkeywords = [\"nes\", \"emulator\"]\n\n[lib]\ncrate-type = [\"cdylib\", \"rlib\"]\n\n[[bench]]\nname = \"clock_frame\"\nharness = false\n\n[lints]\nworkspace = true\n\n[features]\ndefault = []\nprofiling = []\ntrace = []\n\n[dependencies]\nbincode.workspace = true\nbitflags = { version = \"2.10\", features = [\"serde\"] }\ncfg-if.workspace = true\ndirs.workspace = true\nflate2 = \"1.0\"\nrand = \"0.10\"\nserde.workspace = true\nthiserror.workspace = true\ntracing.workspace = true\n\n[target.'cfg(target_arch = \"wasm32\")'.dependencies]\nserde_json.workspace = true\nweb-time.workspace = true\nweb-sys = { workspace = true, features = [\"Storage\", \"Window\"] }\n\n[dev-dependencies]\nanyhow.workspace = true\nimage.workspace = true\nserde_json.workspace = true\ntracing-subscriber.workspace = true\n"
  },
  {
    "path": "tetanes-core/README.md",
    "content": "<!-- markdownlint-disable no-inline-html no-duplicate-heading -->\n\n# TetaNES Core\n\n[![Build Status]][build] [![Doc Status]][docs] [![Latest Version]][crates.io]\n[![Downloads]][crates.io] [![License]][gnu]\n\n[build status]: https://img.shields.io/github/actions/workflow/status/lukexor/tetanes/ci.yml?branch=main\n[build]: https://github.com/lukexor/tetanes/actions/workflows/ci.yml\n[doc status]: https://img.shields.io/docsrs/tetanes-core?style=plastic\n[docs]: https://docs.rs/tetanes-core/\n[latest version]: https://img.shields.io/crates/v/tetanes-core?style=plastic\n[crates.io]: https://crates.io/crates/tetanes-core\n[downloads]: https://img.shields.io/crates/d/tetanes-core?style=plastic\n[license]: https://img.shields.io/crates/l/tetanes-core?style=plastic\n[gnu]: https://github.com/lukexor/tetanes/blob/main/LICENSE-MIT\n\n<!-- markdownlint-disable line-length -->\n📖 [Summary](#summary) - ✨ [Features](#features) - 🚧 [Building](#building) - 🚀 [Getting\nStarted](#getting-started) - ⚠️ [Known Issues](#known-issues) - 💬 [Contact](#contact)\n<!-- markdownlint-enable line-length -->\n\n## Summary\n\n<img width=\"100%\" alt=\"TetaNES\"\n  src=\"https://raw.githubusercontent.com/lukexor/tetanes/main/static/tetanes.png\">\n\n> photo credit for background: [Zsolt Palatinus](https://unsplash.com/@sunitalap)\n> on [unsplash](https://unsplash.com/photos/pEK3AbP8wa4)\n\nThis is the core emulation library for `TetaNES`. Savvy developers can build their\nown custom emulation libraries or applications in Rust on top of `tetanes-core`.\n\nSome community examples:\n\n- [NES Bundler](https://github.com/tedsteen/nes-bundler) - Transform your\n  NES-game into a single executable targeting your favourite OS!\n- [Dappicom](https://github.com/tonk-gg/dappicom) - Dappicom is a provable\n  Nintendo Entertainment System emulator written in Noir and Rust.\n- [NESBox](https://github.com/mantou132/nesbox/) - NESBox's vision is to become\n  the preferred platform for people playing online multiplayer games, providing\n  an excellent user experience for all its users.\n\n## Minimum Supported Rust Version (MSRV)\n\nThe current minimum Rust version is `1.85.0`.\n\n## Features\n\n- NTSC, PAL and Dendy emulation.\n- Headless Mode.\n- Pixellate and NTSC filters.\n- Zapper (Light Gun) support.\n- iNES and NES 2.0 ROM header formats supported.\n- Over 30 supported mappers covering >90% of licensed games.\n- Game Genie Codes.\n- Preference snd keybonding menus using [egui](https://egui.rs).\n  - Increase/Decrease speed & Fast Forward\n  - Save & Load States\n  - Battery-backed RAM saves\n\n### Building\n\nTo build the project, you'll need a nightly version of the compiler and run\n`cargo build` or `cargo build --release` (if you want better framerates).\n\n### Getting Started\n\nBelow is a basic example of setting up `tetanes_core` with a ROM and running the\nemulation. For a more in-depth example see the `tetanes::nes::emulation` module.\n\n```rust no_run\nuse tetanes_core::prelude::*;\n\nfn main() -> anyhow::Result<()> {\n    let mut control_deck = ControlDeck::new();\n\n    // Load a ROM from the filesystem.\n    // See also: `ControlDeck::load_rom` for loading anything that implements `Read`.\n    control_deck.load_rom_path(\"some_awesome_game.nes\")?;\n\n    while control_deck.is_running() {\n      // See also: `ControlDeck::clock_frame_output` and `ControlDeck::clock_frame_into`\n      control_deck.clock_frame()?;\n\n      let audio_samples = control_deck.audio_samples();\n      // Process audio samples (e.g. by sending it to an audio device)\n      control_deck.clear_audio_samples();\n\n      let frame_buffer = control_deck.frame_buffer();\n      // Process frame buffer (e.g. by rendering it to the screen)\n\n      // If not relying on vsync, sleep or otherwise wait the remainder of the\n      // 16ms frame time to clock again\n    }\n\n    Ok(())\n}\n```\n\n## Stability\n\nThe aim is for general stability, but the version isn't `1.0` yet and there are\nseveral large features on the roadmap that may result in breaking changes. This\napplies to both APIs and save file formats.\n\nOnce some of these larger features are completed, and `1.0` is released, more\neffort will be dedicatged to versioning these files for backward compatibility\nin the event of future breaking changes.\n\n## Known Issues\n\nSee the [github issue tracker][].\n\n### Contact\n\nFor issue reporting, please use the [github issue tracker][]. You can also\ncontact me directly at <https://lukeworks.tech/contact/>.\n\n[github issue tracker]: https://github.com/lukexor/tetanes/issues\n"
  },
  {
    "path": "tetanes-core/benches/clock_frame.rs",
    "content": "#![allow(clippy::expect_used, reason = \"fine in a benchmark\")]\n\nuse std::{\n    fs::File,\n    hint::black_box,\n    path::{Path, PathBuf},\n    time::Instant,\n};\nuse tetanes_core::prelude::*;\n\nfn main() {\n    const FRAMES_TO_RUN: u32 = 400;\n    const ITERATIONS: u32 = 30;\n\n    let rom_path = std::env::args()\n        .find(|arg| arg.ends_with(\".nes\"))\n        .map(PathBuf::from)\n        .map(|path| {\n            if path.exists() {\n                path\n            } else {\n                // The working directory of every benchmark is set to the root directory of\n                // the package the benchmark belongs to.\n                //\n                // So if path is relative, it might be relative to the workspace not the package\n                // the benchmark is in\n                std::env::current_dir()\n                    .expect(\"valid cwd\")\n                    .join(\"..\")\n                    .join(path)\n            }\n        })\n        .unwrap_or_else(|| {\n            let base_path = Path::new(env!(\"CARGO_MANIFEST_DIR\"));\n            base_path.join(\"test_roms/spritecans.nes\")\n        });\n    let rom_path = rom_path.canonicalize().expect(\"valid rom path\");\n    let mut rom = File::open(&rom_path).expect(\"failed to open path\");\n\n    let mut deck = ControlDeck::with_config(Config {\n        ram_state: RamState::AllZeros,\n        ..Default::default()\n    });\n    deck.load_rom(rom_path.to_string_lossy(), &mut rom)\n        .expect(\"failed to load rom\");\n\n    // Warmup\n    for _ in 0..3 {\n        deck.reset(ResetKind::Hard);\n        while deck.frame_number() < FRAMES_TO_RUN {\n            black_box(deck.clock_frame()).expect(\"valid frame clock\");\n            deck.clear_audio_samples();\n        }\n    }\n\n    let start = Instant::now();\n    for _ in 0..ITERATIONS {\n        deck.reset(ResetKind::Hard);\n        while deck.frame_number() < FRAMES_TO_RUN {\n            black_box(deck.clock_frame()).expect(\"valid frame clock\");\n            deck.clear_audio_samples();\n        }\n    }\n    let elapsed = start.elapsed().as_secs_f64();\n\n    let ms_per_frame = (elapsed / f64::from(FRAMES_TO_RUN * ITERATIONS)) * 1000.0;\n    println!(\"=== RESULTS ===\");\n    println!(\"{elapsed:.2} s total\");\n    println!(\"{ms_per_frame:.3} ms/frame\");\n}\n"
  },
  {
    "path": "tetanes-core/game_database.txt",
    "content": "# CRC, Region, Mapper, Sub-Mapper, ChrBanks, PrgRomBanks, PrgRamBanks, Battery, Mirroring, SubMapper, Title\n    1388B3, PAL, 4, 0, 16, 16, 0, false, Vertical, \"Mega Man 3 (Europe) (Rev A).nes\"\n    21ED29, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Reigen Doushi (Japan).nes\"\n    837960, NTSC, 4, 0, 32, 16, 0, true, Horizontal, \"King's Quest V (USA).nes\"\n    8E2D30, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Blodia Land - Puzzle Quest (Japan).nes\"\n    9AF6BE, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Wheel of Fortune - Family Edition (USA).nes\"\n    A53242, NTSC, 113, 0, 16, 8, 0, false, Horizontal, \"Fun Blaster Pak (Australia) (Unl).nes\"\n    AD1189, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Teenage Mutant Hero Turtles (Europe).nes\"\n    E95D86, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Jimmy Connors Tennis (USA).nes\"\n   123BFFE, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Color a Dinosaur (USA).nes\"\n   143EEB4, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Airball (Unknown) (Proto 1).nes\"\n   15D4555, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Sunman (Europe) (Proto).nes\"\n   16C93D8, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Salad no Kuni no Tomato Hime (Japan).nes\"\n   18A8699, NTSC, 4, 0, 32, 8, 0, false, Vertical, \"Roger Clemens' MVP Baseball (USA).nes\"\n   1934171, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Terminator 2 - Judgment Day (USA) (Beta).nes\"\n   1B4CA89, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"King's Knight (USA).nes\"\n   22589B9, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Tecmo Super Bowl (Japan).nes\"\n   23A5A32, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Gyromite (World).nes\"\n   2589598, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Highway Star (Japan).nes\"\n   26C1E1A, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Cosmos Cop (Asia) (Mega Soft) (Unl).nes\"\n   26C5FCA, NTSC, 70, 0, 0, 8, 0, false, Horizontal, \"Saint Seiya - Ougon Densetsu (Japan).nes\"\n   26E41C5, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Mad Max (USA).nes\"\n   28374F2, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Yamamura Misa Suspense - Kyouto Zaiteku Satsujin Jiken (Japan).nes\"\n   2863604, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Sukeban Deka III (Japan).nes\"\n   2B9E7C2, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Monster Party (USA).nes\"\n   2C41438, NTSC, 177, 0, 0, 32, 0, true, Horizontal, \"Xing He Zhan Shi (China) (Unl).nes\"\n   2CC3973, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Ninja Kid (USA).nes\"\n   2D7976B, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Penguin-kun Wars (Japan).nes\"\n   2E0ADA4, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Totally Rad (Europe).nes\"\n   2EE3706, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Strider (USA).nes\"\n   3272E9B, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Krion Conquest, The (USA).nes\"\n   354868A, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Family Trainer 1 - Athletic World (Japan).nes\"\n   35DC2E9, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Pinball (World).nes\"\n   37006F7, NTSC, 116, 0, 0, 16, 0, false, FourScreen, \"Chuugoku Taitei (Asia) (Unl).nes\"\n   39B4A9C, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Chou-Wakusei Senki - MetaFight (Japan).nes\"\n   3B8DEFA, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Monopoly (France).nes\"\n   3D56CF7, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Olympus no Tatakai (Japan).nes\"\n   3E2898F, NTSC, 4, 0, 32, 16, 0, true, Horizontal, \"Wario no Mori (Japan).nes\"\n   3EC46AF, NTSC, 69, 0, 32, 8, 0, false, Horizontal, \"Batman - Return of the Joker (USA).nes\"\n   3F899CD, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Hunt for Red October, The (USA).nes\"\n   3FB57B6, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Zombie Nation (USA).nes\"\n   4109355, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Gekitotsu Yonku Battle (Japan).nes\"\n   4142764, PAL, 4, 0, 16, 8, 0, false, Vertical, \"Joe & Mac - Caveman Ninja (Europe).nes\"\n   41553C3, NTSC, 168, 0, 0, 4, 0, true, Horizontal, \"Racermate Challenge II (USA) (v3.12.027) (Unl).nes\"\n   430DB08, PAL, 4, 0, 16, 8, 0, false, Vertical, \"Zen - Intergalactic Ninja (Europe).nes\"\n   45E8CD8, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Jigoku Gokuraku Maru (Japan).nes\"\n   4766130, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Legend of the Ghost Lion (USA).nes\"\n   49325D9, NTSC, 112, 0, 0, 8, 0, false, Vertical, \"Huang Di (Asia) (Unl).nes\"\n   504B007, PAL, 0, 0, 1, 1, 0, false, Vertical, \"Donkey Kong Jr. Math (USA, Europe).nes\"\n   5104517, PAL, 7, 0, 1, 8, 0, false, Horizontal, \"Ivan 'Ironman' Stewart's Super Off Road (Europe).nes\"\n   51CD5F2, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Pachi-Slot Adventure 3 - Bitaoshii 7 Kenzan! (Japan).nes\"\n   537322A, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"NARC (USA).nes\"\n   5378607, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Metal Mech - Man & Machine (USA).nes\"\n   546BD12, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Fushigi no Umi no Nadia (Japan).nes\"\n   54CB4EB, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Astyanax (USA).nes\"\n   554394F, NTSC, 113, 0, 4, 2, 0, false, Vertical, \"Metal Fighter (Asia) (Hacker) (Unl).nes\"\n   58F23A2, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Image Fight (USA).nes\"\n   59E0CDF, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Eliminator Boat Duel (USA).nes\"\n   5A688C8, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Casino Kid (USA).nes\"\n   5C4AF33, NTSC, 93, 0, 0, 8, 0, false, Vertical, \"Shanghai (Japan) (Sample).nes\"\n   5CE560C, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Legends of the Diamond - The Baseball Championship Game (USA).nes\"\n   5F04EAC, NTSC, 82, 0, 0, 8, 0, true, Horizontal, \"SD Keiji - Blader (Japan).nes\"\n   6144B4A, NTSC, 77, 0, 0, 8, 0, false, FourScreen, \"Napoleon Senki (Japan).nes\"\n   63E5653, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Addams Family, The - Pugsley's Scavenger Hunt (USA).nes\"\n   6406EB9, NTSC, 113, 0, 8, 8, 0, false, Horizontal, \"Total Funpak (Australia) (Unl).nes\"\n   6689AA4, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Wrath of the Black Manta (Europe).nes\"\n   6961BE4, NTSC, 1, 0, 16, 16, 0, false, Horizontal, \"Skate or Die 2 - The Search for Double Trouble (USA).nes\"\n   6D72C83, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Fist of the North Star (USA).nes\"\n   6F15215, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Thexder (Japan).nes\"\n   6F9C714, NTSC, 18, 0, 16, 8, 0, false, Horizontal, \"Ninja Jajamaru - Ginga Daisakusen (Japan).nes\"\n   719260C, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Cadillac (Japan).nes\"\n   71D4C2D, PAL, 4, 0, 32, 8, 0, false, Vertical, \"WWF King of the Ring (Europe).nes\"\n   73A0EBE, PAL, 1, 0, 16, 8, 0, false, Vertical, \"P.O.W. - Prisoners of War (Europe).nes\"\n   74EC424, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Arabian Dream Scheherazade (Japan).nes\"\n   775DC68, NTSC, 150, 0, 0, 2, 0, false, Horizontal, \"Taiwan Mahjong 2 (Asia) (Unl).nes\"\n   78CED30, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Choujin - Ultra Baseball (Japan).nes\"\n   7910BF9, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Family Quiz - 4-nin wa Rival (Japan).nes\"\n   794F2A5, NTSC, 1, 0, 1, 32, 0, true, Horizontal, \"Dragon Quest IV - Michibikareshi Monotachi (Japan) (Rev A).nes\"\n   7977186, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Hiryuu no Ken Special - Fighting Wars (Japan).nes\"\n   7D92C31, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"RPG Jinsei Game (Japan).nes\"\n   83E4FC1, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Parodius (Europe).nes\"\n   8439D55, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Special Tag Team Pro Wrestling (Japan).nes\"\n   85DE7C9, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Die Hard (USA).nes\"\n   897021B, NTSC, 69, 0, 32, 8, 0, false, Horizontal, \"Gremlin 2 - Shinshu Tanjou (Japan).nes\"\n   8E11357, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Super Dyna'mix Badminton (Japan).nes\"\n   902C8F0, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Sanada Juu Yuushi (Japan).nes\"\n   91ED5A9, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Kyuukyoku Tiger (Japan).nes\"\n   92EC15C, NTSC, 3, 0, 2, 2, 0, false, Vertical, \"Ikinari Musician (Japan).nes\"\n   939852F, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"M.U.L.E. (USA).nes\"\n   93E845F, NTSC, 1, 0, 4, 8, 0, true, Horizontal, \"Artelius (Japan).nes\"\n   955B54C, NTSC, 79, 0, 4, 2, 0, false, Horizontal, \"Dudes with Attitude (USA) (Rev 1) (Unl).nes\"\n   96D8364, PAL, 2, 0, 1, 8, 0, false, Vertical, \"DuckTales 2 (Europe).nes\"\n   973F714, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Jangou (Japan).nes\"\n   9874777, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Marble Madness (USA).nes\"\n   98C672A, NTSC, 19, 0, 32, 16, 0, true, Horizontal, \"Sangokushi II - Haou no Tairiku (Japan).nes\"\n   99B8CAA, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Mahjong RPG Dora Dora Dora (Japan).nes\"\n   9C083B7, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Track & Field II (USA).nes\"\n   9C1FC7D, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Mizushima Shinji no Daikoushien (Japan).nes\"\n   9C31CD4, NTSC, 11, 0, 4, 2, 0, false, Horizontal, \"Galactic Crusader (USA) (Unl).nes\"\n   9FFDF45, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Super Momotarou Dentetsu (Japan).nes\"\n   A0926BD, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"MotorCity Patrol (USA).nes\"\n   A3FC393, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Lode Runner (Japan).nes\"\n   A42D84F, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Takeda Shingen (Japan).nes\"\n   A73A792, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Meitantei Holmes - M kara no Chousenjou (Japan).nes\"\n   A7E62D4, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"F-117A - Stealth Fighter (USA).nes\"\n   AB26DB6, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"Exodus - Journey to the Promised Land (USA) (v4.0) (Unl).nes\"\n   ABDD5CA, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Spot - The Video Game (Japan).nes\"\n   AC1AA8F, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Castlevania (USA).nes\"\n   ACFC3CD, NTSC, 173, 0, 0, 2, 0, false, Horizontal, \"Mahjong Block (Unknown) (Unl).nes\"\n   AE3CC5E, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Pennant League, The - Home Run Nighter '90 (Japan).nes\"\n   AE6C9E2, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Castelian (USA).nes\"\n   AEA38F7, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Escape from Atlantis, The (USA) (Proto 2) (Unl).nes\"\n   AFB395E, NTSC, 5, 0, 16, 8, 0, false, Horizontal, \"Gun Sight (Japan).nes\"\n   B0E128F, NTSC, 105, 0, 0, 16, 0, true, Horizontal, \"Nintendo World Championships 1990 (USA).nes\"\n   B13658B, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Hyokkori Hyoutan-jima - Nazo no Kaizokusen (Japan).nes\"\n   B3513A0, NTSC, 3, 0, 1, 2, 0, false, Vertical, \"Monstruo de los Globos, El (Spain) (Rev 1) (Gluk Video) (Unl).nes\"\n   B404915, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Captain Planet and the Planeteers (USA).nes\"\n   B58880C, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Ice Climber (Japan).nes\"\n   B6443D4, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Star Wars - The Empire Strikes Back (Japan).nes\"\n   B8E8649, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Hiryuu no Ken - Ougi no Sho (Japan).nes\"\n   B8F8128, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Smash T.V. (Europe).nes\"\n   BB5B3A0, NTSC, 66, 0, 4, 8, 0, false, Vertical, \"Family Block (Japan).nes\"\n   BBF80CB, NTSC, 82, 0, 0, 8, 0, false, Vertical, \"Kyuukyoku Harikiri Stadium - Heisei Gannen Ban (Japan).nes\"\n   BCAA4D7, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"F-15 Strike Eagle (USA).nes\"\n   BDD8DD9, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Jeopardy! 25th Anniversary Edition (USA).nes\"\n   BE0A328, NTSC, 16, 0, 1, 16, 0, false, Horizontal, \"Datach - SD Gundam - Gundam Wars (Japan).nes\"\n   C1792DA, NTSC, 19, 0, 16, 8, 0, false, Vertical, \"Famista '90 (Japan).nes\"\n   C1FE23D, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Little Red Hood - Xiao Hong Mao (Asia) (Unl).nes\"\n   C2E7863, NTSC, 4, 0, 16, 16, 0, false, Vertical, \"Dirty Harry (USA).nes\"\n   C401790, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Bomber Man II (Japan).nes\"\n   C462638, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Dengeki - Big Bang! (Japan) (Beta).nes\"\n   C47946D, NTSC, 210, 1, 16, 8, 0, false, Vertical, \"Chibi Maruko-chan - Uki Uki Shopping (Japan).nes\"\n   C5A6297, NTSC, 67, 0, 0, 8, 0, false, Vertical, \"Fantasy Zone II - Opa-Opa no Namida (Japan).nes\"\n   C5F3973, PAL, 0, 0, 1, 2, 0, false, Horizontal, \"Pro Action Replay (Europe) (v1.2) (Cart Present) (Unl).nes\"\n   C783F0C, PAL, 0, 0, 1, 1, 0, false, Horizontal, \"Devil World (Europe).nes\"\n   C918A65, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Kekkyoku Nankyoku Daibouken (Japan) (Rev 1).nes\"\n   CC9FFEC, NTSC, 23, 0, 0, 8, 0, false, Horizontal, \"Ganbare Goemon 2 (Japan).nes\"\n   CD79B71, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Cosmo Genesis (Japan).nes\"\n   CF42E69, NTSC, 159, 0, 16, 8, 0, false, Horizontal, \"Magical Taruruuto-kun - Fantastic World!! (Japan).nes\"\n   D14285A, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"DuckTales 2 (Germany).nes\"\n   D15687D, NTSC, 0, 0, 32, 16, 0, false, Horizontal, \"Super Cartridge Ver 7 - 4 in 1 (Asia) (Unl).nes\"\n   D203DE5, NTSC, 0, 0, 32, 16, 0, false, Horizontal, \"Super Cartridge Ver 9 - 3 in 1 (Asia) (Unl).nes\"\n   D3482D7, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Seirei Densetsu Lickle (Japan).nes\"\n   D473EE6, NTSC, 186, 0, 0, 16, 0, false, Horizontal, \"Study Box (Japan).nes\"\n   D65E7C7, NTSC, 69, 0, 16, 16, 0, false, Vertical, \"Gimmick! (Japan).nes\"\n   D9F5BD1, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Maniac Mansion (USA).nes\"\n   DA00298, NTSC, 1, 0, 16, 16, 0, false, Horizontal, \"Gozonji - Yaji Kita Chin Douchuu (Japan).nes\"\n   DA0E723, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Ski or Die (Europe).nes\"\n   DA28A50, PAL, 3, 0, 4, 2, 0, false, Horizontal, \"Stadium Events (Europe).nes\"\n   DA5E32E, NTSC, 87, 0, 0, 2, 0, false, Vertical, \"Urusei Yatsura - Lum no Wedding Bell (Japan).nes\"\n   DB4B382, NTSC, 18, 0, 16, 8, 0, false, Horizontal, \"Plasma Ball (Japan).nes\"\n   DC53188, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Jesus - Kyoufu no Bio Monster (Japan).nes\"\n   E0060C8, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Happily Ever After (USA) (Proto).nes\"\n   E1683C5, NTSC, 80, 0, 0, 8, 0, false, Vertical, \"Mirai Shinwa Jarvas (Japan).nes\"\n   E997CF6, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Takeda Shingen 2 (Japan).nes\"\n   EAA7515, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Yousei Monogatari - Rod Land (Japan).nes\"\n   EC6C023, NTSC, 5, 0, 32, 16, 0, true, Horizontal, \"Gemfire (USA).nes\"\n   ED96F42, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Gremlins 2 - The New Batch (USA).nes\"\n   EF730E7, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Twin Cobra (USA).nes\"\n   F05FF0A, NTSC, 185, 0, 0, 2, 0, false, Vertical, \"Seicross (Japan).nes\"\n   F1BABE7, NTSC, 18, 0, 16, 8, 0, true, Horizontal, \"Jajamaru Gekimaden - Maboroshi no Kinmajou (Japan).nes\"\n   F1CC048, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Double Dragon (USA).nes\"\n   F20210D, NTSC, 241, 0, 0, 8, 0, false, Vertical, \"Journey to the West (Asia) (Unl).nes\"\n   F3B89E3, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"F-1 Race (Japan) (Beta).nes\"\n   F5F1F86, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Zenbei Pro Basket (Japan).nes\"\n   F86FEB4, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Times of Lore (USA).nes\"\n   FC8E9B7, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"North & South - Wakuwaku Nanboku Sensou (Japan).nes\"\n   FCFC04D, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Mega Man 2 (USA).nes\"\n   FD6BFC8, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Western Kids (Japan).nes\"\n   FEC90D2, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Section-Z (USA).nes\"\n   FF6A3B5, NTSC, 69, 0, 32, 8, 0, false, Horizontal, \"Dynamite Batman (Japan).nes\"\n   FFDE258, NTSC, 4, 0, 8, 4, 0, false, Horizontal, \"Fantasy Zone (USA) (Unl).nes\"\n  1011394F, NTSC, 112, 0, 0, 8, 0, false, Horizontal, \"Zhen Ben Xi You Ji (Asia) (Unl).nes\"\n  10119E6B, NTSC, 93, 0, 0, 8, 0, false, Horizontal, \"Fantasy Zone (Japan).nes\"\n  10124E09, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Robodemons (USA) (Unl).nes\"\n  10180072, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Flying Warriors (USA).nes\"\n  1027C432, NTSC, 2, 0, 1, 16, 0, false, Vertical, \"Momotarou Dentetsu (Japan).nes\"\n  103E7E7F, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Willow (USA).nes\"\n  1066B66D, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"SD Gundam - Gachapon Senshi 3 - Eiyuu Senki (Japan).nes\"\n  10B0F8B0, NTSC, 80, 0, 0, 8, 0, false, Vertical, \"Taito Grand Prix - Eikou e no License (Japan).nes\"\n  10BAEEF3, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Son Son (Japan).nes\"\n  10C06E27, NTSC, 1, 0, 1, 16, 0, false, Vertical, \"Chuck Yeager's Fighter Combat (USA) (Proto).nes\"\n  10C8F2FA, NTSC, 19, 0, 16, 8, 0, true, Horizontal, \"Dokuganryuu Masamune (Japan).nes\"\n  10C9A789, NTSC, 19, 0, 32, 16, 0, true, Horizontal, \"Digital Devil Story - Megami Tensei II (Japan) (Rev A).nes\"\n  10D62149, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Nangoku Shirei!! - Spy vs Spy (Japan).nes\"\n  11C9AC37, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Princess Tomato in Salad Kingdom (USA) (Beta).nes\"\n  11D08CC6, NTSC, 11, 0, 4, 2, 0, false, Vertical, \"Metal Fighter (USA) (Unl).nes\"\n  12078AFD, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Mendel Palace (USA).nes\"\n  1208E754, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Matsumoto Tooru no Kabushiki Hisshou Gaku - Part II (Japan).nes\"\n  12481CC0, NTSC, 4, 0, 16, 16, 0, false, Vertical, \"Mega Man 3 (USA) (Beta).nes\"\n  1248326D, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Akuma no Shoutaijou (Japan).nes\"\n  126EBF66, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Bugs Bunny Birthday Blowout, The (USA).nes\"\n  12748678, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Days of Thunder (USA).nes\"\n  127D76F4, NTSC, 4, 0, 32, 32, 0, true, Horizontal, \"Kirby's Adventure (Germany).nes\"\n  12906664, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Castlequest (USA).nes\"\n  12B2C361, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Who Framed Roger Rabbit (USA).nes\"\n  12C6D5C7, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"1943 - The Battle of Midway (USA).nes\"\n  12E6CB79, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Door Door (Japan).nes\"\n  12F048DF, NTSC, 75, 0, 0, 8, 0, false, Vertical, \"Jajamaru Ninpou Chou (Japan).nes\"\n  1300A8B7, NTSC, 4, 0, 4, 4, 0, false, Horizontal, \"Pro Yakyuu - Family Stadium '87 (Japan).nes\"\n  1335CB05, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Crystalis (USA).nes\"\n  1352F1B9, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Greg Norman's Golf Power (USA).nes\"\n  1353A134, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Mission Impossible (Europe).nes\"\n  136CA449, NTSC, 16, 0, 32, 16, 0, false, Horizontal, \"Dragon Ball Z Gaiden - Saiya Jin Zetsumetsu Keikaku (Japan).nes\"\n  138862C5, PAL, 2, 0, 1, 8, 0, false, Vertical, \"WWF Wrestlemania Challenge (Europe).nes\"\n  1394F57E, NTSC, 1, 0, 2, 2, 0, false, Horizontal, \"Tetris (USA).nes\"\n  139B15BA, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Golf '92, The (Japan).nes\"\n  139EB5B5, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Indiana Jones and the Temple of Doom (USA) (Unl).nes\"\n  13C6617E, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Batman - The Video Game (USA).nes\"\n  13C774DD, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Double Dragon II - The Revenge (USA).nes\"\n  13D5B1A4, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Time Lord (USA).nes\"\n  13DA2122, NTSC, 88, 0, 16, 8, 0, false, Vertical, \"Quinty (Japan).nes\"\n  13E01649, PAL, 4, 0, 16, 8, 0, true, Horizontal, \"Shadowgate (Europe).nes\"\n  13E09D7A, NTSC, 4, 0, 16, 16, 0, true, FourScreen, \"Dragon Wars (USA) (Proto).nes\"\n  14105C13, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"Spiritual Warfare (USA) (v6.1) (Unl).nes\"\n  1411005B, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Sugoro Quest - Dice no Senshitachi (Japan).nes\"\n  14255C57, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Corvette ZR-1 Challenge (Europe).nes\"\n  1425D7F4, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Arkista's Ring (USA).nes\"\n  14374128, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Hiryuu no Ken Special - Fighting Wars (Japan) (Beta).nes\"\n  145A9A6C, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Devil World (Japan) (Rev A).nes\"\n  1488E95F, PAL, 0, 0, 8, 4, 0, false, Vertical, \"Silent Assault (Asia) (PAL) (Unl).nes\"\n  149C0EC3, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Famicom Shougi - Ryuuousen (Japan).nes\"\n  14A81635, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Moon Ranger (USA) (Unl).nes\"\n  14CD576E, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Abadox (Japan).nes\"\n  14F477C3, NTSC, 66, 0, 4, 4, 0, false, Horizontal, \"AV Mahjong Club (Asia) (Unl).nes\"\n  1500E835, NTSC, 33, 0, 0, 8, 0, false, Horizontal, \"Jetsons, The - Cogswell's Caper (Japan).nes\"\n  15141401, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Asmik-kun Land (Japan).nes\"\n  15454CB2, NTSC, 143, 0, 0, 2, 0, false, Vertical, \"Magical Mathematics (Asia) (NTSC) (Unl).nes\"\n  1545BD13, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Gimmi a Break - Shijou Saikyou no Quiz Ou Ketteisen 2 (Japan).nes\"\n  1570A0C8, NTSC, 189, 0, 0, 8, 0, false, Horizontal, \"Gluk the Thunder Warrior (Spain) (Gluk Video) (Unl).nes\"\n  1590CF62, PAL, 4, 0, 16, 8, 0, true, Horizontal, \"Capcom's Gold Medal Challenge '92 (Europe).nes\"\n  15A1CBB0, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Shatterhand (USA) (Beta).nes\"\n  15F0D3F1, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Wayne Gretzky Hockey (USA).nes\"\n  15FE6D0F, NTSC, 5, 0, 16, 16, 0, true, Horizontal, \"Bandit Kings of Ancient China (USA).nes\"\n  161D717B, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Bad Dudes (USA).nes\"\n  162CCBD0, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Toukyou Pachi-Slot Adventure (Japan) (Rev A).nes\"\n  162F328E, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Sansuu 4 Nen - Keisan Game (Japan) (Beta).nes\"\n  163ECCAE, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Kekkyoku Nankyoku Daibouken (Japan).nes\"\n  1675A6C1, NTSC, 4, 0, 32, 8, 0, false, Vertical, \"War on Wheels (USA) (Proto).nes\"\n  1677D21D, PAL, 1, 0, 16, 8, 0, false, Vertical, \"Nigel Mansell's World Championship Racing (Europe) (En,Fr,De,Es,It).nes\"\n  16A0A3A3, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Hitler no Fukkatsu - Top Secret (Japan).nes\"\n  16E93F39, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Tashiro Masashi no Princess ga Ippai (Japan).nes\"\n  16EBA50A, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Star Trek - 25th Anniversary (USA).nes\"\n  171251E3, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"1942 (Japan, USA).nes\"\n  17389E3D, PAL, 1, 0, 16, 8, 0, false, Vertical, \"RoadBlasters (Europe).nes\"\n  174F860A, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Indora no Hikari (Japan).nes\"\n  175C4A3C, NTSC, 75, 0, 0, 8, 0, false, Horizontal, \"Moero!! Junior Basket - Two on Two (Japan).nes\"\n  175EDA0B, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Great Boxing - Rush Up (Japan).nes\"\n  1771EA8F, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Athletic World (USA).nes\"\n  1773F76D, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Flipull - An Exciting Cube Game (Japan) (En).nes\"\n  178DBA78, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Pro Action Replay (Europe) (v1.0) (Unl).nes\"\n  179A0D57, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Tecmo Super Bowl (USA).nes\"\n  18027A1F, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Phantom Air Mission (Europe).nes\"\n  1829616A, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Gorby no Pipeline Daisakusen (Japan).nes\"\n  183859D2, NTSC, 16, 0, 32, 16, 0, false, Horizontal, \"Dragon Ball Z - Kyoushuu! Saiya Jin (Japan).nes\"\n  184C2124, NTSC, 5, 0, 32, 16, 0, true, Horizontal, \"Sangokushi II (Japan).nes\"\n  18A04825, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Terminator 2 - Judgment Day (Europe).nes\"\n  18A2E74F, NTSC, 4, 0, 1, 32, 0, false, Horizontal, \"Mega Man 4 (USA) (Rev A).nes\"\n  18A885B0, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"2010 - Street Fighter (Japan).nes\"\n  18A9F0D9, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Baseball Stars II (USA).nes\"\n  18B249E5, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Barbie (USA) (Rev A).nes\"\n  18D44BBA, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Isaki Shuugorou no Keiba Hisshou Gaku (Japan).nes\"\n  190E52FF, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Bio Force Ape (Japan) (En) (Proto).nes\"\n  192D546F, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"RoboCop (USA).nes\"\n  1948810E, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"Spiritual Warfare (USA) (v5.1) (Unl).nes\"\n  1973AEA8, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"American Gladiators (USA).nes\"\n  198C2F41, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Dr. Mario (Japan, USA).nes\"\n  198F1C37, NTSC, 0, 0, 4, 4, 0, false, Horizontal, \"Super Pang II (Asia) (Unl).nes\"\n  1992D163, NTSC, 4, 0, 4, 2, 0, false, Horizontal, \"Adventures of Lolo 2 (USA).nes\"\n  19CE7F12, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Magical Mathematics (Asia) (PAL) (Unl).nes\"\n  19E81461, NTSC, 16, 0, 1, 16, 0, false, Horizontal, \"Datach - Dragon Ball Z - Gekitou Tenkaichi Budoukai (Japan).nes\"\n  1A018A26, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Huge Insect (Asia) (Unl).nes\"\n  1A2A7EF7, NTSC, 178, 0, 0, 32, 0, true, Vertical, \"San Guo Zhong Lie Zhuan (China) (Unl).nes\"\n  1A2EA6B9, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Super Pinball (Japan).nes\"\n  1A7E97ED, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Meitantei Holmes - Kiri no London Satsujin Jiken (Japan).nes\"\n  1AC701B5, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Tenchi o Kurau (Japan).nes\"\n  1AE7B933, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Bad Street Brawler (USA).nes\"\n  1B421E9C, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Star Soldier (Japan).nes\"\n  1B71CCDB, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Gauntlet II (USA).nes\"\n  1B7BD879, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Fushigi na Blobby - Blobania no Kiki (Japan).nes\"\n  1B932BEA, PAL, 4, 0, 1, 32, 0, false, Horizontal, \"Mega Man 4 (Europe).nes\"\n  1BC686A8, NTSC, 71, 0, 1, 8, 0, false, Horizontal, \"Fire Hawk (USA) (Unl).nes\"\n  1C212E9D, PAL, 119, 0, 0, 8, 0, false, Horizontal, \"High Speed (Europe).nes\"\n  1C2A58FF, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Nakajima Satoru Kanshuu - F-1 Hero 2 (Japan).nes\"\n  1C31DD60, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Exploding Fist (USA) (Proto 2).nes\"\n  1C66BAF6, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Super Pitfall (Japan).nes\"\n  1C9EA55C, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Three Stooges, The (USA) (Beta).nes\"\n  1CB9A019, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Hit the Ice - VHL - The Video Hockey League (USA) (Proto).nes\"\n  1CED086F, NTSC, 5, 0, 16, 16, 0, true, Horizontal, \"Ishin no Arashi (Japan).nes\"\n  1CEE0C21, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Digger - The Legend of the Lost City (USA).nes\"\n  1CF48EF1, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Shuang Xiang Pao (Asia) (Unl).nes\"\n  1D0F4D6B, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Black Bass, The (USA).nes\"\n  1D20A5C6, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Galaxy 5000 (USA).nes\"\n  1D2D93FF, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"G.I. Joe - A Real American Hero (USA).nes\"\n  1D41CC8C, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Gyruss (USA).nes\"\n  1D5B03A5, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Jackal (USA).nes\"\n  1D6DECCC, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Rocketeer, The (USA).nes\"\n  1D89610E, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Great Battle Cyber (Japan).nes\"\n  1D8BF724, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Venus Senki - Back the City (Japan).nes\"\n  1DAC6208, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Snow Brothers (USA).nes\"\n  1DB07C0D, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Galaga - Demons of Death (USA).nes\"\n  1DBD1D2B, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Geimos (Japan).nes\"\n  1DC0F740, NTSC, 210, 2, 16, 16, 0, false, Vertical, \"Wagyan Land 2 (Japan).nes\"\n  1E407387, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Baltron (Japan) (Beta).nes\"\n  1E472E7A, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Advanced Dungeons & Dragons - Heroes of the Lance (Japan).nes\"\n  1E4D3831, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Yoshi no Tamago (Japan).nes\"\n  1EB4A920, NTSC, 79, 0, 4, 2, 0, false, Vertical, \"Double Strike - Aerial Attack Force (USA) (v1.1) (Unl).nes\"\n  1EBB5B42, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Bomberman II (USA).nes\"\n  1ED48C5C, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Dash Yarou (Japan).nes\"\n  1ED5C801, PAL, 4, 0, 16, 16, 0, false, Horizontal, \"Super Mario Bros. 3 (Europe).nes\"\n  1ED7D6BE, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Yamamura Misa Suspense - Kyouto Hana no Misshitsu Satsujin Jiken (Japan).nes\"\n  1EFE38EB, PAL, 7, 0, 1, 8, 0, false, Horizontal, \"Captain Skyhawk (Europe).nes\"\n  1F2D9DB7, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Baltron (Japan).nes\"\n  1F6660E6, PAL, 1, 0, 16, 4, 0, false, Horizontal, \"Barker Bill's Trick Shooting (Europe).nes\"\n  1F6EA423, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Baseball Simulator 1.000 (USA).nes\"\n  1F74EA6C, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Arctic (Japan).nes\"\n  1FA8C4A4, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Superman (USA).nes\"\n  1FF251AE, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Teenage Mutant Ninja Turtles (USA) (Beta).nes\"\n  20353E63, NTSC, 1, 0, 8, 8, 0, false, Horizontal, \"Fox's Peter Pan & the Pirates - The Revenge of Captain Hook (USA).nes\"\n  203583D5, NTSC, 23, 0, 0, 8, 0, false, Vertical, \"TwinBee 3 - Poko Poko Daimaou (Japan) (Beta).nes\"\n  2055971A, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Mario Is Missing! (USA).nes\"\n  2061772A, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Tantei Jinguuji Saburou - Toki no Sugiyuku Mama ni... (Japan).nes\"\n  209B4BED, NTSC, 26, 0, 16, 16, 0, true, Horizontal, \"Esper Dream 2 - Aratanaru Tatakai (Japan).nes\"\n  209F3587, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Untouchables, The (USA).nes\"\n  20A5219B, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Conquest of the Crystal Palace (USA).nes\"\n  20AF7E1A, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Chiki Chiki Machine Mou Race (Japan).nes\"\n  20C5D187, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Utsurun Desu (Japan).nes\"\n  20C795EB, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Untouchables, The (USA) (Rev B).nes\"\n  20CC079D, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Mother (Japan).nes\"\n  20F98977, NTSC, 87, 0, 0, 1, 0, false, Vertical, \"City Connection (Japan).nes\"\n  213CB3FB, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"U.S. Championship V'Ball (Japan).nes\"\n  219DFABF, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Ninja-kun - Ashura no Shou (Japan).nes\"\n  21DD2174, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Haja no Fuuin (Japan).nes\"\n  21E28F50, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Parody World - Monster Party (Japan) (Proto).nes\"\n  21F2A1A6, PAL, 4, 0, 32, 8, 0, false, Vertical, \"WWF Wrestlemania Steel Cage Challenge (Europe).nes\"\n  21F85681, NTSC, 87, 0, 0, 2, 0, false, Vertical, \"Hyper Olympic (Japan) (Genteiban!).nes\"\n  21F8C4AB, NTSC, 89, 0, 0, 8, 0, false, Vertical, \"Tenka no Goikenban - Mito Koumon (Japan).nes\"\n  2220E14A, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Space Shuttle Project (USA).nes\"\n  2225C20F, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Genghis Khan (USA).nes\"\n  22276213, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Menace Beach (USA) (Unl).nes\"\n  227CF577, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Adventures of Rocky and Bullwinkle and Friends, The (USA).nes\"\n  22AB9694, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Rod Land (Europe).nes\"\n  22D6D5BD, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Jikuu Yuuden - Debias (Japan).nes\"\n  231BC76E, NTSC, 11, 0, 4, 2, 0, false, Horizontal, \"Chiller (Australia) (Unl).nes\"\n  2328046E, NTSC, 7, 0, 1, 16, 0, false, Horizontal, \"IronSword - Wizards & Warriors II (USA).nes\"\n  2337F45E, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Hokuto no Ken (Japan) (Beta).nes\"\n  2370C0A9, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Rollerblade Racer (USA).nes\"\n  2394AE1C, PAL, 243, 0, 0, 2, 0, false, Horizontal, \"Happy Pairs (Asia) (PAL) (Unl).nes\"\n  239971D1, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Zhuang Qiu Chuan Shuo Hua Zhuang II - Ball Story (China) (Unl).nes\"\n  23BEFF5E, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Parasol Stars - The Story of Bubble Bobble III (Europe) (Beta).nes\"\n  23BF0507, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Dengeki - Big Bang! (Japan).nes\"\n  23C3FB2D, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Dungeon Magic - Sword of the Elements (USA).nes\"\n  23D17F5E, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Lone Ranger, The (USA).nes\"\n  23D7D48F, PAL, 7, 0, 1, 16, 0, false, Horizontal, \"Battletoads-Double Dragon (Europe).nes\"\n  23D91BC6, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Mahjong (Japan) (Rev B).nes\"\n  23E03DC1, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Dong Dong Nao 1 (Asia) (Unl).nes\"\n  23E9C736, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Shin Satomi Hakken-Den - Hikari to Yami no Tatakai (Japan).nes\"\n  23F38647, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Zunou Senkan Galg (Japan) (En) (Beta).nes\"\n  23F4B48F, NTSC, 4, 0, 1, 16, 0, false, Horizontal, \"Wily & Right no Rockboard - That's Paradise (Japan).nes\"\n  240863B9, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Airball (Unknown) (Proto 2).nes\"\n  240C6DE8, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Elysion (Japan).nes\"\n  240DE736, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Star Wars - The Empire Strikes Back (USA).nes\"\n  243A8735, NTSC, 32, 0, 0, 8, 0, false, Horizontal, \"Major League (Japan).nes\"\n  2447E03B, NTSC, 19, 0, 16, 8, 0, false, Vertical, \"Top Striker (Japan).nes\"\n  24598791, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Duck Hunt (World).nes\"\n  2470402B, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Famicom Igo Nyuumon (Japan).nes\"\n  2472C3EB, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Pyramid (USA) (Rev 1) (Unl).nes\"\n  247CC73D, NTSC, 243, 0, 0, 2, 0, false, Horizontal, \"Poker II (Asia) (Unl).nes\"\n  248566A7, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Total Recall (USA).nes\"\n  24BA12DD, NTSC, 71, 0, 1, 16, 0, false, Vertical, \"Micro Machines (USA) (Aladdin Compact Cartridge) (Unl).nes\"\n  24BA90CA, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Cobra Command (Japan).nes\"\n  24EECC15, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Sesame Street ABC & 123 (USA).nes\"\n  250F7913, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Ultima - Exodus (Japan).nes\"\n  2526C943, NTSC, 66, 0, 4, 8, 0, false, Vertical, \"Takahashi Meijin no Bugutte Honey (Japan).nes\"\n  252FFD12, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Sweet Home (Japan).nes\"\n  2538D860, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Pinball Quest (USA).nes\"\n  2545214C, NTSC, 1, 0, 2, 4, 0, true, Horizontal, \"Dragon Warrior (USA) (Rev A).nes\"\n  25468546, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Gegege no Kitarou - Youkai Daimakyou (Japan).nes\"\n  25519E6E, NTSC, 3, 0, 8, 2, 0, false, Vertical, \"Pyramid (Japan) (Hacker inc.) (Unl).nes\"\n  25551F3F, PAL, 9, 0, 16, 8, 0, false, Horizontal, \"Mike Tyson's Punch-Out!! (Europe) (Rev A).nes\"\n  256392F1, PAL, 4, 0, 16, 8, 0, true, Horizontal, \"Formula 1 Sensation (Europe).nes\"\n  25952141, NTSC, 4, 0, 16, 32, 0, true, Horizontal, \"Advanced Dungeons & Dragons - Pool of Radiance (USA).nes\"\n  25EDAF5C, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Okkotoshi Puzzle - Tonjan! (Japan).nes\"\n  26049798, NTSC, 4, 0, 32, 16, 0, true, Horizontal, \"My Life My Love - Boku no Yume - Watashi no Negai (Japan).nes\"\n  262B5A1D, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Star Soldier (USA).nes\"\n  262F31AC, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Zelda II - The Adventure of Link (USA) (GameCube Edition).nes\"\n  263AC8A0, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Rampage (USA).nes\"\n  264F26B1, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Oishinbo - Kyuukyoku no Menu Sanbon Shoubu (Japan).nes\"\n  2651F227, NTSC, 4, 0, 32, 8, 0, true, Horizontal, \"Tecmo NBA Basketball (USA).nes\"\n  26535EF5, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Wizards & Warriors (USA) (Rev A).nes\"\n  26796758, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Rampart (USA).nes\"\n  267DE4CC, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Fisher-Price - I Can Remember (USA).nes\"\n  267E592F, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Karnov (Japan).nes\"\n  268E39D0, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Tantei Jinguuji Saburou - Yokohamakou Renzoku Satsujin Jiken (Japan).nes\"\n  26BB1C8C, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Hello Kitty no Ohanabatake (Japan).nes\"\n  26BD6EC6, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Star Luster (Japan).nes\"\n  26BFED27, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Super Chinese 2 - Dragon Kid (Japan).nes\"\n  26CEC726, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Kaguya Hime Densetsu (Japan).nes\"\n  26D3082C, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Joe & Mac (USA).nes\"\n  26E39935, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Moon Crystal (Japan).nes\"\n  26E82008, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Bucky O'Hare (Japan).nes\"\n  2705EAEB, NTSC, 234, 0, 0, 32, 0, false, Horizontal, \"Maxi 15 (USA) (Unl).nes\"\n  270EAED5, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Onyanko Town (Japan).nes\"\n  2746B39E, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Tetris Flash (Japan).nes\"\n  276237B3, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Karnov (Japan) (Rev 1).nes\"\n  276AC722, NTSC, 159, 0, 16, 16, 0, false, Horizontal, \"SD Gundam Gaiden - Knight Gundam Monogatari (Japan) (Rev 1).nes\"\n  27738241, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Popeye no Eigo Asobi (Japan).nes\"\n  27777635, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Volleyball (USA, Europe).nes\"\n  279710DC, NTSC, 7, 0, 1, 16, 0, false, Horizontal, \"Battletoads (USA).nes\"\n  27AA3933, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Seicross (USA).nes\"\n  27C16011, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Baken Hisshou Gaku - Gate In (Japan).nes\"\n  27CA0679, PAL, 7, 0, 1, 8, 0, false, Vertical, \"Danny Sullivan's Indy Heat (Europe).nes\"\n  27D14A54, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Jaws (USA).nes\"\n  27D34A57, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Golgo 13 - Dainishou - Icarus no Nazo (Japan).nes\"\n  27DDF227, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Athena (USA).nes\"\n  27F8D0D2, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Punisher, The (USA).nes\"\n  280AD3C5, PAL, 1, 0, 16, 4, 0, false, Vertical, \"Knight Rider (Europe).nes\"\n  282745C5, NTSC, 191, 0, 0, 16, 0, false, Horizontal, \"Q Boy (Asia) (Unl).nes\"\n  283AD224, NTSC, 32, 0, 0, 16, 0, false, Horizontal, \"Ai Sensei no Oshiete - Watashi no Hoshi (Japan).nes\"\n  28492586, PAL, 4, 0, 4, 2, 0, false, Horizontal, \"Burai Fighter (Europe).nes\"\n  2856111F, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Overlord (USA).nes\"\n  2858933B, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Satsui no Kaisou - Soft House Renzoku Satsujin Jiken (Japan).nes\"\n  286FCD20, NTSC, 21, 0, 0, 16, 0, true, Horizontal, \"Ganbare Goemon Gaiden 2 - Tenka no Zaihou (Japan).nes\"\n  28C1D3D5, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Kamen no Ninja - Akakage (Japan).nes\"\n  28C2DFCE, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Die Hard (Japan).nes\"\n  28F9B41F, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Bases Loaded 4 (USA).nes\"\n  28FB71AE, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Defender of the Crown (USA).nes\"\n  2915FAF0, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Incantation (Asia) (Unl).nes\"\n  291BCD7D, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Pachio-kun 2 (Japan).nes\"\n  29582CA1, NTSC, 243, 0, 0, 2, 0, false, Horizontal, \"Honey Peach - Mei Nv Quan (Asia) (Unl).nes\"\n  2969A5C1, NTSC, 79, 0, 1, 1, 0, false, Horizontal, \"Pyramid (USA) (Unl).nes\"\n  297198B9, NTSC, 4, 0, 4, 2, 0, false, Horizontal, \"Adventures of Lolo (Japan).nes\"\n  29DD37F4, NTSC, 69, 0, 32, 8, 0, false, Vertical, \"Batman - Return of the Joker (USA) (Beta).nes\"\n  29DE87AF, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Family Trainer 3 - Aerobics Studio (Japan).nes\"\n  29E173FF, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Gyrodine (Japan).nes\"\n  29EC0FD1, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"1999 - Hore, Mitakotoka! Seikimatsu (Japan).nes\"\n  2A01F9D1, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Wagyan Land (Japan).nes\"\n  2A1919FE, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Kamen Rider SD - Granshocker no Yabou (Japan).nes\"\n  2A3CA509, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Downtown - Nekketsu Monogatari (Japan).nes\"\n  2A46B57F, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Terra Cresta (USA).nes\"\n  2A5F4C5A, NTSC, 132, 0, 0, 2, 0, false, Vertical, \"Jin Gwok Sei Chuen Saang (Asia) (Unl).nes\"\n  2A6559A1, NTSC, 33, 0, 0, 8, 0, false, Horizontal, \"Operation Wolf (Japan).nes\"\n  2A662AC7, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Jeopardy! (USA) (Rev A).nes\"\n  2A7D3ADF, NTSC, 19, 0, 16, 8, 0, false, Vertical, \"Dragon Ninja (Japan).nes\"\n  2AAF0804, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"Spiritual Warfare (USA) (Beta) (Unl).nes\"\n  2AC5233C, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Hinotori - Houou Hen - Gaou no Bouken (Japan).nes\"\n  2AC87283, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Elevator Action (USA).nes\"\n  2AE535CA, NTSC, 19, 0, 16, 8, 0, false, Vertical, \"Dragon Ninja (Japan) (Rev A).nes\"\n  2AE97660, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Ghostbusters II (USA).nes\"\n  2B11E0B0, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Nobunaga no Yabou - Zenkoku Ban (Japan) (Rev A).nes\"\n  2B1497DC, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Shadowgate (Sweden).nes\"\n  2B160BF0, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Mighty Final Fight (Japan).nes\"\n  2B20B022, PAL, 4, 0, 32, 8, 0, false, Horizontal, \"Gremlins 2 - The New Batch (Europe).nes\"\n  2B20ED9B, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Shui Guo Li (Asia) (Unl).nes\"\n  2B378D11, NTSC, 7, 0, 1, 16, 0, false, Horizontal, \"Double Dare (USA).nes\"\n  2B462010, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Balloon Fight (Japan).nes\"\n  2B4D80AE, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Battle Formula (Japan).nes\"\n  2B750BF9, NTSC, 87, 0, 0, 2, 0, false, Vertical, \"Urusei Yatsura - Lum no Wedding Bell (Japan) (Beta).nes\"\n  2BA86F76, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Gran Aventura Submarina, La (Spain) (Gluk Video) (Unl).nes\"\n  2BB3DABE, NTSC, 82, 0, 0, 8, 0, true, Horizontal, \"Kyuukyoku Harikiri Stadium III (Japan).nes\"\n  2BB6A0F8, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Sherlock Holmes - Hakushaku Reijou Yuukai Jiken (Japan).nes\"\n  2BC25D5A, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Ghoul School (USA).nes\"\n  2BC67AA8, NTSC, 4, 0, 1, 32, 0, false, Horizontal, \"Mega Man 4 (USA).nes\"\n  2BCF2132, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Heavy Barrel (Japan).nes\"\n  2BE254E9, NTSC, 64, 0, 0, 2, 0, false, Vertical, \"Dig Dug II (Japan).nes\"\n  2BF0F9C5, PAL, 0, 0, 1, 1, 0, false, Horizontal, \"Mario Bros. (Europe) (Rev A).nes\"\n  2BF61C53, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Jetsons, The - Cogswell's Caper (USA).nes\"\n  2BFB1186, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Be-Bop-Highschool - Koukousei Gokuraku Densetsu (Japan).nes\"\n  2C043781, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Paperboy (Japan).nes\"\n  2C088DC5, PAL, 4, 0, 32, 32, 0, true, Horizontal, \"Kirby's Adventure (Europe).nes\"\n  2C2DDFB4, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Battle Chess (USA).nes\"\n  2C33161D, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Advanced Dungeons & Dragons - Hillsfar (Japan).nes\"\n  2C4421B2, NTSC, 16, 0, 16, 8, 0, false, Horizontal, \"Akuma-kun - Makai no Wana (Japan).nes\"\n  2C5908A7, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Advanced Dungeons & Dragons - DragonStrike (USA).nes\"\n  2C5FAC1C, NTSC, 1, 0, 16, 8, 0, true, Vertical, \"Famicom Shougi - Ryuuousen (Japan) (Beta).nes\"\n  2C624B5F, NTSC, 64, 0, 0, 8, 0, false, Vertical, \"Xybots (USA) (Proto) (Unl).nes\"\n  2C7D68F3, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Pac-Man (Japan) (En) (Rev B).nes\"\n  2C818014, NTSC, 9, 0, 16, 8, 0, false, Horizontal, \"Mike Tyson's Punch-Out!! (Japan, USA) (Rev A).nes\"\n  2CAAE01C, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Felix the Cat (USA).nes\"\n  2CECD630, NTSC, 36, 0, 0, 4, 0, false, Vertical, \"Policeman (Spain) (Gluk Video) (Unl).nes\"\n  2CF5DB05, NTSC, 176, 0, 0, 8, 0, false, Horizontal, \"Zhi Li Xiao Zhuang Yuan (China) (Unl).nes\"\n  2D020965, PAL, 1, 0, 1, 16, 0, true, Horizontal, \"NES Open Tournament Golf (Europe).nes\"\n  2D1FEE70, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Magician (USA) (Beta 2).nes\"\n  2D273AA4, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Ikari Warriors (USA) (Rev A).nes\"\n  2D2F91B8, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Mahou no Princess Minky Momo - Remember Dream (Japan).nes\"\n  2D41EF92, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Uncanny X-Men, The (USA).nes\"\n  2D664D99, NTSC, 4, 0, 1, 32, 0, false, Horizontal, \"Rockman 6 - Shijou Saidai no Tatakai!! (Japan).nes\"\n  2D75C7A9, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Cobra Command (USA).nes\"\n  2D8730E2, NTSC, 0, 0, 4, 2, 0, false, Horizontal, \"Poker Mahjong - Pu Ke Mao Que (Asia) (Unl).nes\"\n  2DB7C31E, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Hook (Japan).nes\"\n  2DBB054D, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"HVC Kensa Cassette Controller Test (Japan).nes\"\n  2DC05A6F, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Astro Robo Sasa (Japan).nes\"\n  2DC331A2, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"New York Nyankies (Japan).nes\"\n  2DD71ACB, NTSC, 1, 0, 1, 32, 0, true, Horizontal, \"Dragon Quest IV - Michibikareshi Monotachi (Japan).nes\"\n  2DDC2DC3, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Thunderbirds (USA).nes\"\n  2DEB12B8, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Venice Beach Volleyball (Asia) (Unl).nes\"\n  2DFF7FDC, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Great Waldo Search, The (USA).nes\"\n  2E0741B6, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Home Alone 2 - Lost in New York (USA).nes\"\n  2E0F51AF, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Nintendo - NTF2 Test Cartridge (NES Test) (USA) (Rev 1).nes\"\n  2E1E7FD8, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Dead Fox (Japan).nes\"\n  2E2ACAE9, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Gambler Jiko Chuushinha - Mahjong Game (Japan).nes\"\n  2E326A1D, NTSC, 4, 0, 4, 4, 0, false, Vertical, \"R.B.I. Baseball (USA) (Unl).nes\"\n  2E4CCF46, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Game Genie (USA) (Unl).nes\"\n  2E563C66, NTSC, 4, 0, 4, 8, 0, false, Horizontal, \"Mappy-Land (Japan).nes\"\n  2E6301ED, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Super Mario Bros. 3 (USA) (Rev A).nes\"\n  2E68ACFC, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Tiger-Heli (Japan).nes\"\n  2E6EE98D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Harlem Globetrotters (USA).nes\"\n  2EA8CC16, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Indiana Jones and the Last Crusade (USA) (UBI Soft).nes\"\n  2F128512, NTSC, 3, 0, 16, 2, 0, false, Vertical, \"Family Trainer 4 - Jogging Race (Japan).nes\"\n  2F1686E5, NTSC, 0, 0, 8, 8, 0, false, Horizontal, \"Super Cartridge Ver 2 - 10 in 1 (Asia) (Unl).nes\"\n  2F2D1FA9, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Castle of Dragon (USA).nes\"\n  2F2E30F7, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Ninja Ryuuken Den III - Yomi no Hakobune (Japan).nes\"\n  2F52BBE0, NTSC, 148, 0, 0, 2, 0, false, Horizontal, \"Mahjong Trap - Si Cuan Ma Que - Zhi Fu Pian (Asia) (Unl).nes\"\n  2F55BE88, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Lunar Ball (Japan).nes\"\n  2F66E302, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"California Games (USA).nes\"\n  2F698C4D, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Monster Truck Rally (USA).nes\"\n  2FA8CBB4, NTSC, 79, 0, 4, 2, 0, false, Horizontal, \"Puzzle (USA) (Beta) (Unl).nes\"\n  2FBEA66D, NTSC, 79, 0, 4, 2, 0, false, Horizontal, \"F15 City War (Spain) (Gluk Video) (Unl).nes\"\n  2FC1ABAE, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Hudson Hawk (Japan).nes\"\n  2FD2E632, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Defender of the Crown (France).nes\"\n  2FE20D79, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Flintstones, The - The Rescue of Dino & Hoppy (USA).nes\"\n  2FFDE228, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Times of Lore (Japan).nes\"\n  303D4371, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Jordan vs Bird - One On One (USA).nes\"\n  304FA926, PAL, 0, 0, 1, 1, 0, false, Horizontal, \"Tennis (Europe).nes\"\n  3057B904, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Pennant League!! - Home Run Nighter (Japan).nes\"\n  305B4E62, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Super C (USA).nes\"\n  308DA987, PAL, 7, 0, 1, 16, 0, false, Horizontal, \"R.C. Pro-Am II (Europe).nes\"\n  30A225A8, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Columbus - Ougon no Yoake (Japan).nes\"\n  30BF2DBA, NTSC, 86, 0, 0, 8, 0, false, Vertical, \"Moero!! Pro Yakyuu (Japan).nes\"\n  30C5E6CF, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"DuckTales 2 (France).nes\"\n  31957AE4, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Palamedes II - Star Twinkle, Hoshi no Mabataki (Japan).nes\"\n  31B44C65, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Captain Tsubasa Vol. II - Super Striker (Japan).nes\"\n  31C7AD13, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Shounen Ashibe - Nepal Daibouken no Maki (Japan).nes\"\n  32086826, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Paperboy (USA).nes\"\n  322C9F6A, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Destructor, El (Spain) (Gluk Video) (Unl).nes\"\n  3256114C, NTSC, 4, 0, 1, 32, 0, false, Horizontal, \"America Oudan Ultra Quiz - Shijou Saidai no Tatakai (Japan).nes\"\n  326AB3B6, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"8 Eyes (USA).nes\"\n  3275FD7E, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Hottarman no Chitei Tanken (Japan).nes\"\n  3293AFEA, NTSC, 66, 0, 4, 8, 0, false, Vertical, \"Mississippi Satsujin Jiken (Japan) (Rev A).nes\"\n  329C0349, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Nintendo - NTF2 Test Cartridge (USA).nes\"\n  32CF4307, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Conflict (USA).nes\"\n  32E02CB8, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Nintendo World Class Service - Port Test Cartridge (USA).nes\"\n  32FA246F, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Tag Team Pro-Wrestling (Japan).nes\"\n  32FB0583, NTSC, 3, 0, 2, 2, 0, false, Horizontal, \"Arkanoid (USA).nes\"\n  33007B67, NTSC, 177, 0, 0, 64, 0, true, Vertical, \"Mei Guo Fu Hao - American Man (China) (Unl).nes\"\n  330DE468, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Obake no Q Tarou - Wanwan Panic (Japan).nes\"\n  3322105A, NTSC, 1, 0, 16, 2, 0, false, Horizontal, \"Sky Kid (USA).nes\"\n  332C47E0, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Radia Senki - Reimei Hen (Japan).nes\"\n  333C48A0, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Werewolf - The Last Warrior (USA).nes\"\n  336093EF, NTSC, 66, 0, 4, 8, 0, false, Vertical, \"Doraemon (Japan) (Rev A).nes\"\n  3368F7FB, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Wheel of Fortune (USA) (Rev A).nes\"\n  339437F6, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Sesame Street 123 (USA).nes\"\n  33B899C9, NTSC, 16, 0, 16, 8, 0, false, Horizontal, \"Dragon Ball - Daimaou Fukkatsu (Japan).nes\"\n  340713DD, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Crystalis (USA) (Beta).nes\"\n  3417EC46, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Swords and Serpents (USA).nes\"\n  342727B1, NTSC, 80, 0, 0, 8, 0, false, Horizontal, \"Yamamura Misa Suspense - Kyouto Ryuu no Tera Satsujin Jiken (Japan).nes\"\n  343C7BB0, NTSC, 3, 0, 2, 2, 0, false, Vertical, \"Tetris (USA) (Unl).nes\"\n  34540318, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Legend of Zelda, The (USA) (Rev B) (GameCube Edition).nes\"\n  345D3A1A, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Castle of Deceit (USA) (Unl).nes\"\n  34629104, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Teenage Mutant Hero Turtles - Tournament Fighters (Europe).nes\"\n  348D3FF1, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Shatterhand (Europe).nes\"\n  34BB757B, PAL, 1, 0, 1, 8, 0, false, Horizontal, \"Dynablaster (Europe).nes\"\n  34C1E893, PAL, 1, 0, 16, 8, 0, false, Vertical, \"Bigfoot (Europe).nes\"\n  34DDF806, NTSC, 243, 0, 0, 2, 0, false, Horizontal, \"Strategist (Asia) (NTSC) (Unl).nes\"\n  34DEBDFD, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Tamura Koushou Mahjong Seminar (Japan).nes\"\n  34EAB034, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Heavy Barrel (USA).nes\"\n  350D835E, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Gradius (USA).nes\"\n  35476E87, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Wolverine (USA).nes\"\n  358E29DD, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Chevaliers du Zodiaque, Les - La Legende d'Or (France).nes\"\n  35B6FEBF, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"NFL (USA).nes\"\n  35C41CD4, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Air Fortress (USA).nes\"\n  35C6F574, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Young Indiana Jones Chronicles, The (USA).nes\"\n  35D8C961, NTSC, 19, 0, 16, 8, 0, false, Horizontal, \"Mappy Kids (Japan).nes\"\n  35EFFD0E, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Rambo (Japan).nes\"\n  360AA8B4, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Datsugoku (Japan).nes\"\n  36584C96, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Robocco Wars (Japan).nes\"\n  367566CE, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Strider Hiryu (Japan) (Proto).nes\"\n  3691C120, NTSC, 18, 0, 16, 16, 0, false, Horizontal, \"Mezase Top Pro - Green ni Kakeru Yume (Japan).nes\"\n  369DA42D, NTSC, 19, 0, 16, 8, 0, true, Vertical, \"King of Kings (Japan).nes\"\n  36B35988, NTSC, 79, 0, 4, 2, 0, false, Vertical, \"Double Strike - Aerial Attack Force (USA) (v1.0) (Unl).nes\"\n  36C3B13A, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Rod Land featuring Rit and Tam (Europe) (Beta).nes\"\n  36CA3102, NTSC, 4, 0, 32, 16, 0, false, FourScreen, \"Rocman X (Asia) (Unl).nes\"\n  37088EFF, NTSC, 4, 0, 32, 32, 0, true, Horizontal, \"Kirby's Adventure (Canada).nes\"\n  370CEB65, NTSC, 70, 0, 0, 8, 0, false, Horizontal, \"Family Trainer 5 - Meiro Daisakusen (Japan).nes\"\n  37138039, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"WWF Wrestlemania (USA).nes\"\n  3719A26D, NTSC, 4, 0, 4, 2, 0, false, Horizontal, \"Family Jockey (Japan).nes\"\n  37397194, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Adventures of Lolo 3 (Europe).nes\"\n  3747CD0B, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Nintendo World Class Service - Control Deck Test Cartridge (USA).nes\"\n  37A5EB52, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Probotector II - Return of the Evil Forces (Europe).nes\"\n  37B62D04, NTSC, 118, 0, 0, 16, 0, false, Vertical, \"Ys III - Wanderers from Ys (Japan).nes\"\n  37BA3261, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Back to the Future Part II & III (USA).nes\"\n  37C474D5, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Rygar (USA) (Rev A).nes\"\n  37CB1801, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Top Gun (Japan).nes\"\n  37E24797, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Nekketsu Kakutou Densetsu (Japan).nes\"\n  37F59450, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Aigina no Yogen - Balubalouk no Densetsu Yori (Japan).nes\"\n  3824F7A5, PAL, 1, 0, 4, 2, 0, false, Horizontal, \"Snake Rattle n Roll (Europe).nes\"\n  3836EEAC, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Tanigawa Kouji no Shougi Shinan II (Japan).nes\"\n  383CABBF, NTSC, 119, 0, 0, 8, 0, false, Horizontal, \"High Speed (USA).nes\"\n  3869E598, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Hollywood Squares (USA).nes\"\n  38810A91, NTSC, 0, 0, 1, 2, 0, false, FourScreen, \"Mach Rider (Japan, USA) (Rev A).nes\"\n  38946C43, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Fisher-Price - Firehouse Rescue (USA).nes\"\n  38B590E4, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Dusty Diamond's All-Star Softball (USA).nes\"\n  38BFC03C, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Shanghai II (Japan).nes\"\n  38DE7053, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Pro Wrestling (Europe).nes\"\n  38EF66B5, NTSC, 87, 0, 0, 2, 0, false, Vertical, \"Choplifter (Japan) (En) (Rev 1).nes\"\n  38FBCC85, NTSC, 71, 0, 1, 16, 0, false, Vertical, \"Fantastic Adventures of Dizzy, The (USA) (Unl).nes\"\n  391AA1B8, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Bloody Warriors - Shan-Go no Gyakushuu (Japan).nes\"\n  396F0D59, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Sekiryuuou (Japan).nes\"\n  398B8182, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Darkman (USA).nes\"\n  39B68AA3, NTSC, 23, 0, 0, 8, 0, false, Horizontal, \"Jarinko Chie - Bakudan Musume no Shiawase Sagashi (Japan).nes\"\n  39BB6616, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Monopoly (Germany).nes\"\n  39D43261, NTSC, 4, 0, 32, 8, 0, true, Horizontal, \"Deja Vu (Sweden).nes\"\n  39F2CE4B, NTSC, 5, 0, 16, 16, 0, true, Horizontal, \"Suikoden - Tenmei no Chikai (Japan).nes\"\n  3A0965B1, NTSC, 2, 0, 1, 16, 0, false, Horizontal, \"Paperboy 2 (USA).nes\"\n  3A4D4D10, PAL, 9, 0, 16, 8, 0, false, Horizontal, \"Mike Tyson's Punch-Out!! (Europe).nes\"\n  3A8723B9, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Wrath of the Black Manta (USA) (Rev A).nes\"\n  3A8F81B0, NTSC, 184, 0, 0, 2, 0, false, Vertical, \"Madoola no Tsubasa (Japan).nes\"\n  3A990EE0, NTSC, 71, 0, 1, 8, 0, false, Vertical, \"Stunt Kids (USA) (Unl).nes\"\n  3AC0830A, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Action in New York (Europe).nes\"\n  3B0F4DB2, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Adventures of Dr. Franken, The (USA) (Proto).nes\"\n  3B1A7EEF, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Murder Club - Honkaku Mystery Adventure (Japan).nes\"\n  3B3F88F0, NTSC, 1, 0, 2, 4, 0, true, Horizontal, \"Dragon Warrior (USA).nes\"\n  3B7F5B3B, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Jurassic Park (USA).nes\"\n  3B90D11E, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Universe Soldiers, The (Unknown) (Unl).nes\"\n  3BB31E38, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Little Ninja Brothers (Europe).nes\"\n  3BBFF3A6, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Best Play Pro Yakyuu (Japan).nes\"\n  3BE244EF, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Little Mermaid, The (USA).nes\"\n  3BE91A23, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Pajama Hero - Nemo (Japan).nes\"\n  3BF55966, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Ganso Saiyuuki - Super Monkey Daibouken (Japan).nes\"\n  3C5C81D4, NTSC, 4, 0, 4, 4, 0, false, Vertical, \"R.B.I. Baseball (USA).nes\"\n  3C7E38F5, NTSC, 11, 0, 4, 2, 0, false, Vertical, \"Master Chu and the Drunkard Hu (USA) (Unl).nes\"\n  3CCB5D57, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Klax (Japan).nes\"\n  3CD4B420, NTSC, 33, 0, 0, 8, 0, false, Vertical, \"Takeshi no Sengoku Fuuunji (Japan) (Beta).nes\"\n  3CD6BB0E, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Lost Word of Jenny - Ushinawareta Message (Japan).nes\"\n  3CF67AEC, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Twin Eagle (Japan).nes\"\n  3CF749DE, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Adventures in the Magic Kingdom (USA) (Beta 1).nes\"\n  3D0996B2, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Pirates! (USA).nes\"\n  3D1C3137, NTSC, 78, 0, 0, 8, 0, false, FourScreen, \"Uchuusen Cosmo Carrier (Japan).nes\"\n  3D1C4894, NTSC, 4, 0, 1, 8, 0, false, Horizontal, \"Ninja Crusaders (USA).nes\"\n  3D3FF543, NTSC, 79, 0, 8, 4, 0, false, Horizontal, \"AV Dragon Mahjang (Japan) (Unl).nes\"\n  3D4B64F1, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"J.League Winning Goal (Japan).nes\"\n  3D564757, PAL, 0, 0, 1, 2, 0, false, Horizontal, \"10-Yard Fight (USA, Europe).nes\"\n  3D95D866, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Sanrio Carnival 2 (Japan).nes\"\n  3DA2085E, NTSC, 2, 0, 1, 16, 0, false, Vertical, \"Maniac Mansion (Japan).nes\"\n  3DBD6DAF, PAL, 1, 0, 16, 8, 0, false, Vertical, \"Hoops (Europe).nes\"\n  3DCADA42, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Hoshi o Miru Hito (Japan).nes\"\n  3E00A373, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Meikyuu Kumikyoku - Milon no Daibouken (Japan).nes\"\n  3E1271D5, NTSC, 79, 0, 4, 2, 0, false, Vertical, \"Tiles of Fate (USA) (Unl).nes\"\n  3E170708, NTSC, 0, 0, 2, 4, 0, false, Horizontal, \"Final Combat (Asia) (NTSC) (Unl).nes\"\n  3E470FE0, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Downtown - Nekketsu Koushinkyoku - Soreyuke Daiundoukai (Japan).nes\"\n  3E58A87E, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Freedom Force (USA).nes\"\n  3E59E951, NTSC, 168, 0, 0, 4, 0, true, Horizontal, \"Racermate Challenge II (USA) (v6.02.002) (Unl).nes\"\n  3E785DC3, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Air Fortress (Japan).nes\"\n  3E95BA25, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Pac-Man (Japan).nes\"\n  3ECA3DDA, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Bases Loaded 3 (USA).nes\"\n  3ECDB1F7, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Rampart (Japan).nes\"\n  3EDCF7E8, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Mega Man 5 (USA).nes\"\n  3EEA372E, NTSC, 189, 0, 0, 8, 0, false, Vertical, \"Thunder Warrior (Asia) (Unl).nes\"\n  3EFF62E4, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Dark Lord (Japan).nes\"\n  3F0C8136, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Golf Grand Slam (Japan).nes\"\n  3F0FD764, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Blaster Master (USA).nes\"\n  3F15D20D, NTSC, 16, 0, 1, 32, 0, true, Horizontal, \"Famicom Jump II - Saikyou no 7 Nin (Japan).nes\"\n  3F2450EA, NTSC, 0, 0, 4, 2, 0, false, Horizontal, \"Galactic Crusader (Asia) (Unl).nes\"\n  3F2BDA65, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Maniac Mansion (Sweden).nes\"\n  3F56A392, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Captain ED (Japan).nes\"\n  3F57E040, NTSC, 1, 0, 1, 8, 0, false, Vertical, \"Square Deal (Japan) (Beta).nes\"\n  3F78037C, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Mighty Final Fight (USA).nes\"\n  3F7AD415, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Nobunaga no Yabou - Zenkoku Ban (Japan).nes\"\n  3F8D6889, NTSC, 18, 0, 16, 16, 0, false, Horizontal, \"Moe Pro! - Saikyou Hen (Japan).nes\"\n  3FA96277, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Super Star Force (Japan).nes\"\n  3FC1DC19, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Deblock (Japan).nes\"\n  3FE272FB, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Legend of Zelda, The (USA).nes\"\n  3FEA656A, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Quarter Back Scramble (Japan).nes\"\n  3FF10E3D, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"1943 - The Battle of Midway (Japan) (Beta).nes\"\n  3FF44F87, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Tetris 2 + Bombliss (Japan).nes\"\n  3FFA5762, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"American Dream (Japan).nes\"\n  401349A8, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Balloon Fight (USA).nes\"\n  401521F7, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Wacky Races (USA).nes\"\n  4022C94E, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Smurfs, The (Europe) (En,Fr,De,Es).nes\"\n  404B2E8B, NTSC, 4, 0, 8, 4, 0, false, FourScreen, \"Rad Racer II (USA).nes\"\n  4057C51B, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Ghostbusters (Japan).nes\"\n  40684E95, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Solomon's Key (USA).nes\"\n  407D6FFD, NTSC, 47, 0, 0, 16, 0, false, Vertical, \"Super Spike V'Ball + Nintendo World Cup (USA).nes\"\n  40A5E676, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Bible Adventures (USA) (Unl).nes\"\n  40BFA660, PAL, 3, 0, 4, 2, 0, false, Horizontal, \"Tiger-Heli (Europe).nes\"\n  40C0AD47, NTSC, 33, 0, 0, 8, 0, false, Horizontal, \"Flintstones, The - The Rescue of Dino & Hoppy (Japan).nes\"\n  40D159B6, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Baseball Stars (USA).nes\"\n  40DAFCBA, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Bad News Baseball (USA).nes\"\n  40DBF7A2, PAL, 243, 0, 0, 4, 0, false, Horizontal, \"Olympic I.Q. (Asia) (PAL) (Unl).nes\"\n  40ED2A9D, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Taboo - The Sixth Sense (USA).nes\"\n  41462D21, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Sou Setsu Ryuu (Japan) (Beta).nes\"\n  4156A3CD, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Keroppi to Keroriinu no Splash Bomb! (Japan).nes\"\n  415E5109, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Meikyuu no Tatsujin - Daimeiro (Japan).nes\"\n  41632CB6, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Zombie Hunter (Japan).nes\"\n  4178497A, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Booby Kids (Japan).nes\"\n  4185ADA1, PAL, 0, 0, 4, 2, 0, false, Horizontal, \"Super Pang (Asia) (PAL) (Unl).nes\"\n  419461D0, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Super Cars (USA).nes\"\n  41CC30A7, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"World Super Tennis (Japan).nes\"\n  41CF5B6A, NTSC, 246, 0, 0, 32, 0, true, Horizontal, \"Feng Shen Bang (Asia) (Unl).nes\"\n  41D32FD7, PAL, 7, 0, 1, 16, 0, false, Horizontal, \"Aladdin (Europe).nes\"\n  41F5D38D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Kero Kero Keroppi no Daibouken 2 - Donuts Ike wa Oosawagi! (Japan).nes\"\n  41F9E0AA, NTSC, 118, 0, 0, 8, 0, false, Horizontal, \"Pro Sport Hockey (USA).nes\"\n  4220C170, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Wheel of Fortune Starring Vanna White (USA).nes\"\n  4232C609, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Drac's Night Out (USA) (Proto).nes\"\n  423ADA8E, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Skate or Die (USA).nes\"\n  42749A95, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"P'Radikus Conflict (USA) (Unl).nes\"\n  429103C9, NTSC, 210, 2, 16, 8, 0, false, Vertical, \"Famista '94 (Japan).nes\"\n  4318A2F8, NTSC, 1, 0, 16, 4, 0, false, Horizontal, \"Barker Bill's Trick Shooting (USA).nes\"\n  4339865C, NTSC, 69, 0, 16, 8, 0, false, Horizontal, \"Pyokotan no Daimeiro (Japan).nes\"\n  43539A3C, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Space Harrier (Japan).nes\"\n  435AEEC6, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Panic Restaurant (USA).nes\"\n  437E7B69, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Double Dribble (USA).nes\"\n  43B0944B, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Shin Jinrui - The New Type (Japan).nes\"\n  43D01C10, NTSC, 4, 0, 32, 8, 0, true, Horizontal, \"Deja Vu (USA).nes\"\n  43D30C2F, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Ms. Pac-Man (USA) (Unl).nes\"\n  441AEAE6, NTSC, 94, 0, 0, 8, 0, false, Horizontal, \"Senjou no Ookami (Japan).nes\"\n  441DE6D8, PAL, 1, 0, 16, 8, 0, true, Horizontal, \"Pirates! (Europe).nes\"\n  443FC6CD, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Darkwing Duck (Germany).nes\"\n  44B060DA, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Shogun (Japan).nes\"\n  44D21F83, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"10-Yard Fight (Japan) (Rev 1).nes\"\n  44F34172, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Sqoon (USA).nes\"\n  44F92026, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Pachinko Daisakusen 2 (Japan).nes\"\n  4536FE1C, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Majou Densetsu II - Daimashikyou Galious (Japan).nes\"\n  455CA7DE, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Igo Meikan (Japan).nes\"\n  4582F22E, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Otaku no Seiza - An Adventure in the Otaku Galaxy (Japan).nes\"\n  45878D7F, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Cat Ninden Teyandee (Japan).nes\"\n  459D0C2A, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Dungeon & Magic - Swords of Element (Japan).nes\"\n  45A41784, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Jackie Chan's Action Kung Fu (USA).nes\"\n  45A9DB6F, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Section-Z (Europe).nes\"\n  45F03D2E, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Faria - A World of Mystery & Danger! (USA).nes\"\n  46135141, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Swords and Serpents (France).nes\"\n  4640EBE0, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"President no Sentaku (Japan).nes\"\n  4642DDA6, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Nobunaga's Ambition (USA).nes\"\n  46480432, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Godzilla - Monster of Monsters! (Europe).nes\"\n  464A67AB, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Kung Fu (Europe).nes\"\n  465E5483, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Sword Master (USA).nes\"\n  466EFDC2, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Final Fantasy (Japan) (Rev 0A).nes\"\n  4681691A, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Demon Sword (USA).nes\"\n  4686C5DD, NTSC, 41, 0, 0, 16, 0, false, Horizontal, \"Caltron - 6 in 1 (USA) (Unl).nes\"\n  46931EA0, PAL, 1, 0, 4, 2, 0, false, Horizontal, \"R.C. Pro-Am (Europe) (Rev A).nes\"\n  46B5751B, NTSC, 244, 0, 0, 8, 0, false, Vertical, \"Decathlon (Asia) (Unl).nes\"\n  46E0D37D, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Legend of Zelda, The (USA) (Rev A) (GameCube Edition).nes\"\n  46F30F2D, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Maison Ikkoku (Japan).nes\"\n  46FD7843, NTSC, 210, 2, 16, 8, 0, false, Vertical, \"Splatter House - Wanpaku Graffiti (Japan).nes\"\n  471173E7, PAL, 243, 0, 0, 2, 0, false, Horizontal, \"Chinese Checkers (Asia) (PAL) (Unl).nes\"\n  47232739, NTSC, 210, 2, 4, 8, 0, false, Horizontal, \"Top Rider (Japan).nes\"\n  4751A751, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Nigel Mansell's World Championship Challenge (USA).nes\"\n  475CDBFE, NTSC, 72, 0, 0, 8, 0, false, Horizontal, \"Pinball Quest (Japan).nes\"\n  476E022B, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Rock 'n' Ball (USA).nes\"\n  477A478D, NTSC, 3, 0, 8, 2, 0, false, Horizontal, \"AV Poker (Japan) (Unl).nes\"\n  47918D84, PAL, 243, 0, 0, 2, 0, false, Horizontal, \"Auto-Upturn (Asia) (PAL) (Unl).nes\"\n  47B6A39F, PAL, 1, 0, 16, 8, 0, true, Horizontal, \"Zelda II - The Adventure of Link (Europe) (Rev B).nes\"\n  47C2020B, NTSC, 19, 0, 16, 16, 0, false, Horizontal, \"Hydlide 3 - Yami kara no Houmonsha (Japan).nes\"\n  47EA8047, PAL, 0, 0, 8, 16, 0, false, Horizontal, \"Hell Fighter (Asia) (PAL) (Unl).nes\"\n  47F7F860, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Superman (Sunsoft) (USA) (Proto).nes\"\n  47FD88CF, PAL, 1, 0, 16, 8, 0, true, Horizontal, \"Zelda II - The Adventure of Link (Europe).nes\"\n  481519B1, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Gekitou Stadium!! (Japan).nes\"\n  48239B42, NTSC, 113, 0, 8, 4, 0, false, Horizontal, \"Mahjang Companion (Asia) (Hacker) (Unl).nes\"\n  4823EEFE, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Ultima - Warriors of Destiny (USA).nes\"\n  482C79AF, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Taboo - The Sixth Sense (USA) (Rev A).nes\"\n  48349B0B, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Dragon Quest II - Akuryou no Kamigami (Japan).nes\"\n  484A60DB, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Ballblazer (Japan).nes\"\n  485AC098, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Sanrio Carnival (Japan).nes\"\n  4864C304, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Donkey Kong Jr. (World) (Rev A).nes\"\n  489D19AB, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Kame no Ongaeshi - Urashima Densetsu (Japan).nes\"\n  489EF6A2, NTSC, 1, 0, 16, 2, 0, false, Horizontal, \"Airwolf (USA).nes\"\n  48B8EE58, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Four Players' Tennis (Europe).nes\"\n  48CA0EE1, NTSC, 69, 0, 32, 8, 0, false, Horizontal, \"Barcode World (Japan).nes\"\n  48E904D0, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Snake's Revenge (USA).nes\"\n  48F68D40, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Clu Clu Land (World).nes\"\n  490E8A4C, NTSC, 25, 0, 0, 16, 0, false, Horizontal, \"Teenage Mutant Ninja Turtles II - The Manhattan Project (Japan).nes\"\n  49123146, NTSC, 23, 0, 0, 8, 0, false, Horizontal, \"Getsu Fuuma Den (Japan).nes\"\n  491CD95E, NTSC, 0, 0, 16, 16, 0, false, Horizontal, \"Jurassic Boy (Asia) (Unl).nes\"\n  491D8CDB, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Family Pinball (Japan).nes\"\n  493BD2FF, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Star Gate (Japan).nes\"\n  4942BDA8, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Magic Candle, The (Japan).nes\"\n  498187B6, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Wizardry - Proving Grounds of the Mad Overlord (Japan).nes\"\n  49AEB3A6, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Excitebike (Japan, USA).nes\"\n  49DA2F76, NTSC, 18, 0, 16, 8, 0, false, Horizontal, \"Pizza Pop! (Japan).nes\"\n  49F745E0, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"720 Degrees (USA).nes\"\n  4A601A2C, NTSC, 25, 0, 0, 16, 0, false, Horizontal, \"Teenage Mutant Ninja Turtles (Japan).nes\"\n  4A99B47E, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Gomoku Narabe (Japan).nes\"\n  4AE58F5D, NTSC, 18, 0, 16, 16, 0, false, Horizontal, \"Shin Moero!! Pro Yakyuu (Japan).nes\"\n  4AEA40F7, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Tom & Jerry (Japan).nes\"\n  4B041B6B, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Ivan 'Ironman' Stewart's Super Off Road (USA).nes\"\n  4B0DACCE, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Dragon Fighter (Japan).nes\"\n  4B40CBD9, NTSC, 232, 0, 0, 16, 0, false, Vertical, \"Pegasus 4 in 1 (Unknown) (Unl).nes\"\n  4B5177E9, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Kunio-kun no Nekketsu Soccer League (Japan).nes\"\n  4B6EF399, NTSC, 188, 0, 0, 16, 0, false, Horizontal, \"Karaoke Studio Senyou Cassette Vol. 1 (Japan).nes\"\n  4B750880, PAL, 4, 0, 32, 8, 0, false, Vertical, \"Flintstones, The - The Surprise at Dinosaur Peak (Europe).nes\"\n  4BB6B430, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Tetsudou Ou - Famicom Boardgame (Japan).nes\"\n  4BB9B840, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Isolated Warrior (Europe).nes\"\n  4BC75F16, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"King Neptune's Adventure (USA) (Beta) (Unl).nes\"\n  4BF80AF8, NTSC, 0, 0, 32, 16, 0, false, Horizontal, \"Super Cartridge Ver 3 - 8 in 1 (Asia) (Unl).nes\"\n  4C049CFE, NTSC, 69, 0, 32, 8, 0, false, Horizontal, \"Honoo no Toukyuuji - Dodge Danpei (Japan).nes\"\n  4C0E8BBB, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Satomi Hakkenden (Japan).nes\"\n  4C5836BD, NTSC, 19, 0, 32, 16, 0, false, Horizontal, \"Namco Classic (Japan).nes\"\n  4D1AC58C, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"David Crane's A Boy and His Blob - Trouble on Blobolonia (USA).nes\"\n  4D1DF589, PAL, 1, 0, 16, 8, 0, true, Horizontal, \"Turbo Racing (Europe).nes\"\n  4D345422, PAL, 1, 0, 16, 2, 0, false, Horizontal, \"Airwolf (Europe).nes\"\n  4D3FBA78, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Dr. Jekyll and Mr. Hyde (USA).nes\"\n  4D527D4A, NTSC, 11, 0, 2, 2, 0, false, Vertical, \"Tagin' Dragon (USA) (Unl).nes\"\n  4D68CFB1, NTSC, 38, 0, 0, 8, 0, false, Vertical, \"Crime Busters (Unknown) (Unl).nes\"\n  4D7859A9, NTSC, 69, 0, 16, 8, 0, false, Horizontal, \"Batman (Japan).nes\"\n  4D7D896C, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Pro Action Replay (Europe) (v1.2) (No Cart Present) (Unl).nes\"\n  4DCD15EE, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"World Boxing (Japan).nes\"\n  4DFD949E, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Sqoon (Japan).nes\"\n  4E22368D, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Top Gun (USA).nes\"\n  4E42F13A, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"1943 - The Battle of Valhalla (Japan).nes\"\n  4E44FF44, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Bonk's Adventure (USA).nes\"\n  4E5257D7, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Osomatsu-kun (Japan).nes\"\n  4E77733A, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Hunt for Red October, The (USA) (Rev A).nes\"\n  4E959173, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Gotcha! - The Sport! (USA).nes\"\n  4E99CEA4, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Bikkuri Nekketsu Shin Kiroku! - Harukanaru Kin Medal (Japan).nes\"\n  4EC0FECC, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Ufouria - The Saga (Europe).nes\"\n  4ECD4624, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Mission Impossible (France).nes\"\n  4ED3C6F1, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Predator (Japan).nes\"\n  4ED5AA56, NTSC, 1, 0, 1, 8, 0, true, Vertical, \"Bard's Tale, The - Tales of the Unknown (USA) (Beta 1).nes\"\n  4F032933, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Ike Ike! Nekketsu Hockey-bu - Subette Koronde Dairantou (Japan).nes\"\n  4F089E8A, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Galaxy 5000 (Europe).nes\"\n  4F16C504, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Taito Basketball (Japan).nes\"\n  4F2F1846, NTSC, 206, 0, 8, 8, 0, false, Vertical, \"Famista '89 - Kaimaku Ban!! (Japan).nes\"\n  4F3B2E57, NTSC, 66, 0, 4, 8, 0, false, Vertical, \"Dragon Ball - Le Secret du Dragon (France).nes\"\n  4F467410, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Ikari Warriors II - Victory Road (USA).nes\"\n  4F48B240, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Trojan (Europe).nes\"\n  4F74E236, NTSC, 2, 0, 1, 16, 0, false, Vertical, \"Wonderland Dizzy (Unknown) (Proto) (1993-09-24) (Unl).nes\"\n  4F9DBBE5, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Rambo (USA) (Rev A).nes\"\n  4FBBE319, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Adventures in the Magic Kingdom (USA) (Beta 2).nes\"\n  4FC2F673, NTSC, 75, 0, 0, 8, 0, false, Vertical, \"Ganbare Goemon! - Karakuri Douchuu (Japan).nes\"\n  505F9715, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Wizards & Warriors (USA).nes\"\n  5062A34B, NTSC, 184, 0, 0, 2, 0, false, Vertical, \"Atlantis no Nazo (Japan) (Sample).nes\"\n  506E259D, NTSC, 1, 0, 1, 32, 0, true, Horizontal, \"Dragon Warrior IV (USA).nes\"\n  50893B58, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Gekitou Pro Wrestling!! - Toukon Densetsu (Japan).nes\"\n  509E6032, PAL, 2, 0, 1, 16, 0, false, Horizontal, \"Paperboy 2 (Europe).nes\"\n  50A1B3FE, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Donkey Kong Jr. + Jr. Lesson (Japan).nes\"\n  50CCC8ED, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Battleship (USA).nes\"\n  50CCDA33, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Taiyou no Shinden (Japan).nes\"\n  50D141FC, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Yo! Noid (USA).nes\"\n  50D296B3, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Tombs & Treasure (USA).nes\"\n  50DA4867, NTSC, 4, 0, 32, 8, 0, true, Horizontal, \"Shadow Brain (Japan).nes\"\n  50F3E338, NTSC, 188, 0, 0, 16, 0, false, Horizontal, \"Karaoke Studio Senyou Cassette Vol. 2 (Japan).nes\"\n  50FD0CC6, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Double Dragon III - The Sacred Stones (USA).nes\"\n  5104833E, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Kick Master (USA).nes\"\n  5112DC21, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Wild Gunman (World) (Rev A).nes\"\n  516B2412, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Kyouryuu Sentai Zyuranger (Japan).nes\"\n  517611FE, NTSC, 0, 0, 16, 16, 0, false, Horizontal, \"Super Cartridge Ver 8 - 4 in 1 (Asia) (Unl).nes\"\n  51BD8336, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Foton - The Ultimate Game on Planet Earth (Japan).nes\"\n  51BEE3EA, NTSC, 1, 0, 16, 2, 0, false, Horizontal, \"Family Feud (USA).nes\"\n  51BF28AF, PAL, 7, 0, 1, 8, 0, false, Horizontal, \"Marble Madness (Europe).nes\"\n  51C51C35, PAL, 3, 0, 4, 2, 0, false, Vertical, \"Gradius (Europe).nes\"\n  51C70247, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"Joshua & the Battle of Jericho (USA) (v5.0) (Unl).nes\"\n  5229FCDD, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Kyoro-chan Land (Japan).nes\"\n  52387646, NTSC, 1, 0, 1, 8, 0, true, Vertical, \"Super Mario Bros. 2 (USA) (Beta).nes\"\n  5248CAF3, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Simpsons, The - Bart vs. the Space Mutants (USA) (Rev A).nes\"\n  524A5A32, PAL, 7, 0, 1, 16, 0, false, Horizontal, \"Battletoads (Europe).nes\"\n  52880295, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Winter Games (USA).nes\"\n  529B621F, NTSC, 1, 0, 8, 8, 0, false, Horizontal, \"Super Mario Bros. + Duck Hunt + World Class Track Meet (USA).nes\"\n  52B58732, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Yoshi's Cookie (USA).nes\"\n  52E2B5E0, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Super Mario Bros. 3 (Japan) (Rev A).nes\"\n  530BCCB4, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Red Arremer II (Japan).nes\"\n  5318CDB9, NTSC, 4, 0, 32, 8, 0, true, Horizontal, \"Kouryuu Densetsu Villgust Gaiden (Japan).nes\"\n  532A27E6, NTSC, 4, 0, 32, 16, 0, true, Horizontal, \"Might & Magic - Secret of the Inner Sanctum (USA).nes\"\n  53328FC4, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Chester Field - Ankoku Shin e no Chousen (Japan) (Beta).nes\"\n  5337F73C, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Niji no Silk Road (Japan).nes\"\n  535C5446, NTSC, 3, 0, 8, 2, 0, false, Vertical, \"Idol Shisen Mahjong (Japan) (Unl).nes\"\n  536E5200, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Kitty's Catch (USA) (Proto) (Unl).nes\"\n  538218B2, PAL, 2, 0, 1, 8, 0, false, Horizontal, \"Ikari Warriors (Europe).nes\"\n  538CD2EA, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Three Stooges, The (USA).nes\"\n  5393D949, NTSC, 76, 0, 16, 8, 0, false, Vertical, \"Digital Devil Story - Megami Tensei (Japan).nes\"\n  5397E80B, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Tenkaichi Bushi - Keru Naguuru (Japan).nes\"\n  53A9B53A, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Ferrari Grand Prix Challenge (Europe).nes\"\n  53A9E2BA, NTSC, 4, 0, 32, 16, 0, true, Horizontal, \"Earth Bound (USA) (Proto).nes\"\n  5440811C, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Stick Hunter - Exciting Ice Hockey (Japan).nes\"\n  548A2C3C, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Karnov (USA).nes\"\n  54E43C57, PAL, 4, 0, 32, 16, 0, false, Horizontal, \"Star Wars - The Empire Strikes Back (Europe).nes\"\n  5529431F, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Operation Wolf (Europe).nes\"\n  55397DB3, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Wizardry II - Llylgamyn no Isan (Japan).nes\"\n  555042B3, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Minna no Taabou no Nakayoshi Daisakusen (Japan).nes\"\n  55761931, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Ninja Hattori-kun - Ninja wa Syugyou de Gozaru (Japan).nes\"\n  55773880, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Adventures of Gilligan's Island, The (USA).nes\"\n  5581E835, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Popeye (Japan).nes\"\n  55B4052B, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Makai Island (USA) (Proto).nes\"\n  55DB7E2A, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Mario's Time Machine (USA).nes\"\n  560BF5A6, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"King of Kings, The (USA) (v1.2) (Unl).nes\"\n  563C2CC0, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Kiwi Kraze - A Bird-Brained Adventure! (USA).nes\"\n  563E394A, NTSC, 150, 0, 0, 4, 0, false, Horizontal, \"Mahjong Academy (Asia) (Unl).nes\"\n  565A4681, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Hissatsu Doujou Yaburi (Japan).nes\"\n  565B1BDB, PAL, 0, 0, 1, 1, 0, false, Horizontal, \"Golf (Europe).nes\"\n  56756615, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Princess Tomato in the Salad Kingdom (USA).nes\"\n  567E1620, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Ikari III - The Rescue (USA).nes\"\n  56B9F640, NTSC, 216, 0, 0, 4, 0, false, Vertical, \"Magic Jewelry 2 (Asia) (Unl).nes\"\n  56F05853, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Ufouria - The Saga (Europe) (Beta).nes\"\n  5734EB9E, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"World Class Track Meet (USA).nes\"\n  5746A461, NTSC, 19, 0, 16, 8, 0, false, Vertical, \"Final Lap (Japan).nes\"\n  574E5F8B, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Pirates! (Germany).nes\"\n  576A0DE8, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Fun House (USA).nes\"\n  57AC67AF, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Super Mario Bros. 2 (USA).nes\"\n  57C2AE4E, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Cliffhanger (USA).nes\"\n  57D162F1, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Mickey Mouse III - Yume Fuusen (Japan).nes\"\n  57DD23D1, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Faxanadu (USA).nes\"\n  57E220D0, NTSC, 4, 0, 1, 32, 0, true, Horizontal, \"Final Fantasy III (Japan).nes\"\n  57E9B21C, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Vegas Connection - Casino kara Ai o Komete (Japan).nes\"\n  5800BE2D, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Toobin' (USA) (Unl).nes\"\n  58152B42, NTSC, 142, 0, 0, 4, 0, false, Vertical, \"Pipe 5 (Asia) (Unl).nes\"\n  58507BC9, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Famicom Top Management (Japan).nes\"\n  585BA83D, PAL, 4, 0, 16, 16, 0, false, Horizontal, \"Krusty's Fun House (Europe).nes\"\n  586A3277, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Milon's Secret Castle (USA).nes\"\n  588A31FE, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Untouchables, The (USA) (Rev A).nes\"\n  588E7492, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Tecmo Bowl (Japan).nes\"\n  58C7DDAF, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Captain America and the Avengers (USA).nes\"\n  58D1F46A, PAL, 0, 0, 2, 4, 0, false, Horizontal, \"Final Combat (Asia) (PAL) (Unl).nes\"\n  58E63E82, NTSC, 112, 0, 0, 8, 0, false, Horizontal, \"Chik Bik Ji Jin - Saam Gwok Ji (Asia) (Unl).nes\"\n  59280BEC, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Jackie Chan (Japan).nes\"\n  5931BE01, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"New Ghostbusters II (Japan).nes\"\n  59449E3B, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Mahjong Taisen (Japan).nes\"\n  5991B9D0, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Simpsons, The - Bartman Meets Radioactive Man (USA).nes\"\n  59977A46, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Mach Rider (Japan, USA).nes\"\n  5A0454F3, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Ys II - Ancient Ys Vanished - The Final Chapter (Japan).nes\"\n  5A18F611, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Tsuri Kichi Sanpei - Blue Marlin Hen (Japan).nes\"\n  5A4F156D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Hudson Hawk (USA).nes\"\n  5A5A0CD9, NTSC, 1, 0, 4, 8, 0, true, Horizontal, \"Daisenryaku (Japan).nes\"\n  5A62F17F, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Captain America and the Avengers (Australia).nes\"\n  5A6860F1, NTSC, 4, 0, 1, 16, 0, false, Horizontal, \"Shougi Meikan '92 (Japan).nes\"\n  5A8B4DA8, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Formula One - Built to Win (USA).nes\"\n  5AB54795, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Chuugoku Janshi Story - Tonfuu (Japan).nes\"\n  5ABBF861, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"New Ghostbusters II (USA) (Proto).nes\"\n  5ADBF660, NTSC, 25, 0, 0, 8, 0, false, Horizontal, \"Gradius II (Japan).nes\"\n  5AEFBC94, PAL, 150, 0, 0, 4, 0, false, Horizontal, \"Jovial Race (Asia) (PAL) (Unl).nes\"\n  5B16A3C8, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"Sunday Funday - The Ride (USA) (Unl).nes\"\n  5B457641, NTSC, 16, 0, 1, 16, 0, false, Horizontal, \"Datach - Ultraman Club - Supokon Fight! (Japan).nes\"\n  5B4B6056, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Little Nemo - The Dream Master (USA).nes\"\n  5B4C6146, NTSC, 4, 0, 8, 4, 0, false, Horizontal, \"Family Boxing (Japan).nes\"\n  5B5AB1F8, PAL, 4, 0, 16, 16, 0, false, Horizontal, \"Little Samson (Europe).nes\"\n  5B6CA654, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Barbie (USA).nes\"\n  5B7AC91F, NTSC, 18, 0, 16, 8, 0, false, Horizontal, \"Goal!! (Japan).nes\"\n  5B837E8D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Alien Syndrome (Japan).nes\"\n  5BB62688, NTSC, 4, 0, 8, 4, 0, false, Vertical, \"Ring King (USA).nes\"\n  5BC9D7A1, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Al Unser Jr. Turbo Racing (USA).nes\"\n  5C123EF7, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Wizardry III - Diamond no Kishi (Japan).nes\"\n  5C5A1AB8, NTSC, 3, 0, 2, 2, 0, false, Horizontal, \"Tetris (Bulletproof) (Japan) (Rev B).nes\"\n  5C9063E0, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Golf (Japan).nes\"\n  5CAA3E61, NTSC, 144, 0, 8, 4, 0, false, Vertical, \"Death Race (USA) (Unl).nes\"\n  5CD5FDA4, NTSC, 66, 0, 4, 8, 0, false, Vertical, \"Family Trainer 9 - Fuuun Takeshi-jou 2 (Japan).nes\"\n  5CDB2823, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Pac-Man (Japan) (Rev A).nes\"\n  5CE55F5B, PAL, 3, 0, 4, 2, 0, false, Vertical, \"Star Force (Europe).nes\"\n  5CF536F4, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Power Blade (USA).nes\"\n  5CF6A82E, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Sesame Street Countdown (USA).nes\"\n  5D0D3047, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Werewolf - The Last Warrior (Europe).nes\"\n  5D105C10, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Hissatsu Shigoto Nin (Japan).nes\"\n  5D1301C5, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Mappy (Japan).nes\"\n  5D2444D7, NTSC, 86, 0, 0, 8, 0, false, Vertical, \"Moero!! Pro Yakyuu (Japan) (Rev 2).nes\"\n  5D2B1962, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Tetris 2 (Europe).nes\"\n  5D40C08A, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Pescatore (Japan) (Proto).nes\"\n  5D99053D, PAL, 3, 0, 4, 2, 0, false, Vertical, \"Track & Field in Barcelona (Europe).nes\"\n  5DA9CEC8, NTSC, 11, 0, 2, 2, 0, false, Horizontal, \"Mission Cobra (USA) (Unl).nes\"\n  5DBD6099, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Adventures in the Magic Kingdom (USA).nes\"\n  5DC9BC41, NTSC, 7, 0, 1, 8, 0, false, Vertical, \"Solstice - The Quest for the Staff of Demnos (USA) (Beta).nes\"\n  5DCE2EEA, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Darkwing Duck (USA).nes\"\n  5DE61639, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Advanced Dungeons & Dragons - Hillsfar (USA).nes\"\n  5DEC84F8, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Chiisana Obake - Acchi Socchi Kocchi (Japan).nes\"\n  5E24EEDA, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Fuzzical Fighter (Japan).nes\"\n  5E345B6D, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Magmax (Japan).nes\"\n  5E36D3BE, NTSC, 113, 0, 16, 16, 0, false, Vertical, \"Real Player's Pak (Australia) (Unl).nes\"\n  5E66EAEA, NTSC, 13, 0, 0, 2, 0, false, Vertical, \"Videomation (USA).nes\"\n  5E6D9975, PAL, 7, 0, 1, 8, 0, false, Horizontal, \"Wizards & Warriors (Europe).nes\"\n  5E767671, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Silent Service (USA).nes\"\n  5E900522, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Bubble Bobble (USA).nes\"\n  5EA7D410, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"WCW World Championship Wrestling (USA).nes\"\n  5EB8E707, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Magic Darts (USA).nes\"\n  5ED6F221, NTSC, 4, 0, 32, 32, 0, true, Horizontal, \"Kirby's Adventure (USA) (Rev A).nes\"\n  5EDEC8CD, NTSC, 1, 0, 2, 2, 0, false, Vertical, \"Virus (USA) (Beta) (1990-02-02).nes\"\n  5EE6008E, NTSC, 1, 0, 16, 4, 0, false, Horizontal, \"Mechanized Attack (USA).nes\"\n  5F0BCE2A, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Break Time - The National Pool Tour (USA).nes\"\n  5F14DC48, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"FC Genjin - Freakthoropus Computerus (Japan).nes\"\n  5F2C3195, NTSC, 4, 0, 8, 4, 0, false, Vertical, \"Super Sprint (USA) (Unl).nes\"\n  5F5BFA54, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"Exodus - Journey to the Promised Land (USA) (v5.0) (Unl).nes\"\n  5F6E8A07, NTSC, 66, 0, 4, 8, 0, false, Vertical, \"Paris-Dakar Rally Special (Japan).nes\"\n  5FAB6BCE, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Devil World (Japan).nes\"\n  5FD2AAB1, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Bo Jackson Baseball (USA).nes\"\n  6025C660, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Wally Bear and the No! Gang (USA) (Beta) (Unl).nes\"\n  603AAA57, NTSC, 4, 0, 16, 16, 0, false, Vertical, \"Mega Man 3 (USA).nes\"\n  6058C65D, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Spartan X (Japan).nes\"\n  607BD020, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Die Hard (Europe).nes\"\n  60925D08, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Tecmo Cup - Football Game (Europe).nes\"\n  60A3B803, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Hokuto no Ken (Japan).nes\"\n  60A59624, NTSC, 113, 0, 16, 8, 0, false, Horizontal, \"Mind Blower Pak (Australia) (Unl).nes\"\n  60AA9AE0, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Prince of Persia (Germany).nes\"\n  60AD090A, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Family Trainer 1 - Athletic World (Japan) (Rev 1).nes\"\n  60E563F1, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Tatakai no Banka (Japan) (Rev A).nes\"\n  60E63537, NTSC, 1, 0, 8, 8, 0, false, Horizontal, \"Super Mario Bros. + Duck Hunt + World Class Track Meet (USA) (Rev A).nes\"\n  60EA98A0, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Maniac Mansion (Germany).nes\"\n  61061352, NTSC, 112, 0, 0, 2, 0, false, Horizontal, \"Master Shooter (Asia) (Unl).nes\"\n  61179BFA, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Jungle Book, The (USA).nes\"\n  61253D1C, NTSC, 11, 0, 4, 4, 0, false, Vertical, \"Raid 2020 (USA) (Unl).nes\"\n  6150517C, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Rockman 2 - Dr. Wily no Nazo (Japan).nes\"\n  619BEA12, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Gun-Dec (Japan).nes\"\n  61A852EA, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Battle Stadium - Senbatsu Pro Yakyuu (Japan).nes\"\n  61B4295A, NTSC, 87, 0, 0, 2, 0, false, Vertical, \"Jajamaru no Daibouken (Japan).nes\"\n  61D86167, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Street Cop (USA).nes\"\n  62217BA7, NTSC, 184, 0, 0, 2, 0, false, Vertical, \"Wing of Madoola, The (Japan) (Sample).nes\"\n  622E054A, PAL, 1, 0, 16, 8, 0, false, Vertical, \"Guerrilla War (Europe).nes\"\n  622F059D, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Chack'n Pop (Japan).nes\"\n  623020FB, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Kiddy Sun in Fantasia (Taiwan).nes\"\n  626ABD49, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Takahashi Meijin no Bouken-jima III (Japan).nes\"\n  6272C549, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Terminator, The (USA, Europe).nes\"\n  62C67984, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Nekketsu Koukou Dodgeball-bu (Japan).nes\"\n  62E2E7FC, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Stanley - The Search for Dr. Livingston (USA).nes\"\n  6328B44D, PAL, 4, 0, 16, 8, 0, false, Vertical, \"Parodius (Europe) (Beta).nes\"\n  63338C3C, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Formation Z (Japan).nes\"\n  63469396, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Hokuto no Ken 4 - Shichisei Haken Den - Hokuto Shinken no Kanata e (Japan).nes\"\n  636923BB, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Spelunker (Japan).nes\"\n  637134E8, NTSC, 193, 0, 0, 8, 0, false, Horizontal, \"Fighting Hero (Asia) (Unl).nes\"\n  6377CB75, NTSC, 1, 0, 2, 8, 0, true, Horizontal, \"A Ressha de Ikou (Japan).nes\"\n  637A7ACB, NTSC, 1, 0, 1, 16, 0, true, Vertical, \"Tenchi o Kurau (Japan) (Rev A).nes\"\n  637FE65C, NTSC, 79, 0, 4, 2, 0, false, Vertical, \"Krazy Kreatures (USA) (Unl).nes\"\n  638DBC52, NTSC, 193, 0, 0, 8, 0, false, Vertical, \"War in the Gulf (Brazil) (CCE, Gluk Video) (Unl).nes\"\n  6396B988, NTSC, 5, 0, 16, 16, 0, true, Horizontal, \"Empereur, L' (Japan).nes\"\n  63AEA200, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Donkey Kong Jr. no Sansuu Asobi (Japan).nes\"\n  63C4E122, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Guevara (Japan).nes\"\n  63D38B86, NTSC, 71, 0, 1, 8, 0, false, Vertical, \"Dreamworld Pogie (Unknown) (Proto).nes\"\n  63D3AFF4, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Rockin' Kats (USA) (Beta).nes\"\n  63E992AC, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Alfred Chicken (USA).nes\"\n  63FCC0DD, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Rescue - The Embassy Mission (USA).nes\"\n  6435C095, NTSC, 1, 0, 4, 4, 0, false, Horizontal, \"Short Order + Egg-Splode! (USA).nes\"\n  6439F53A, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Mini Putt (Japan).nes\"\n  644E312B, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Ankoku Shinwa - Yamato Takeru Densetsu (Japan).nes\"\n  6479E76A, NTSC, 18, 0, 16, 8, 0, false, Horizontal, \"Terao no Dosukoi Oozumou (Japan).nes\"\n  64A02715, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Gekikame Ninja Den (Japan).nes\"\n  64B710D2, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Pro Wrestling (USA) (Rev A).nes\"\n  64B8CDE2, NTSC, 64, 0, 0, 4, 0, false, Vertical, \"Klax (USA) (Beta) (Unl).nes\"\n  64BBCB77, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Warpman (Japan).nes\"\n  64BD6CDB, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Hirake! Ponkikki (Japan).nes\"\n  64C0FA3B, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Little Mermaid - Ningyo Hime (Japan).nes\"\n  64C96F53, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Raid on Bungeling Bay (Japan).nes\"\n  64FD3BA6, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Nekketsu Koukou Dodgeball-bu - Soccer Hen (Japan).nes\"\n  652F3324, NTSC, 18, 0, 16, 8, 0, false, Horizontal, \"Tsuru Pika Hagemaru - Mezase! Tsuru Seko no Akashi (Japan).nes\"\n  654F4E90, PAL, 1, 0, 1, 8, 0, false, Horizontal, \"Rad Racer (Europe).nes\"\n  65518EAE, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Addams Family, The (USA).nes\"\n  655EFEED, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Ikari Warriors (USA).nes\"\n  656D4265, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Urban Champion (World).nes\"\n  656FA3B5, NTSC, 87, 0, 0, 2, 0, false, Vertical, \"Argus (Japan).nes\"\n  657F7875, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Soccer (World).nes\"\n  65B6AF68, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"World of Card Games, The (Asia) (Unl).nes\"\n  65D1AB64, PAL, 4, 0, 32, 8, 0, false, Horizontal, \"Jetsons, The - Cogswell's Caper (Europe).nes\"\n  66066326, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Sword Master (Europe).nes\"\n  662B8C9C, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Excitebike (USA) (GameCube Edition).nes\"\n  666BE5EC, PAL, 4, 0, 16, 8, 0, false, Vertical, \"New Zealand Story, The (Europe).nes\"\n  668D1715, PAL, 4, 0, 32, 16, 0, true, Horizontal, \"Wario's Woods (Europe).nes\"\n  66DD04E1, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Meiji Ishin (Japan).nes\"\n  66EBDB64, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Skate or Die (Europe).nes\"\n  66ED9C00, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Bananan Ouji no Daibouken (Japan).nes\"\n  66F4D9F5, NTSC, 1, 0, 16, 4, 0, false, Horizontal, \"Knight Rider (Japan).nes\"\n  66F6A39E, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Darkwing Duck (Europe).nes\"\n  671F23A8, PAL, 5, 0, 16, 16, 0, false, Horizontal, \"Castlevania III - Dracula's Curse (Europe).nes\"\n  6720ABAC, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Bucky O'Hare (Europe).nes\"\n  67555417, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"SD Gundam - Gachapon Senshi 4 - NewType Story (Japan).nes\"\n  6772CA86, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Juuouki (Japan).nes\"\n  67751094, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Adventures of Bayou Billy, The (USA).nes\"\n  6776A977, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Tokoro-san no Mamoru mo Semeru mo (Japan).nes\"\n  67811DA6, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Dash Galaxy in the Alien Asylum (USA).nes\"\n  67861A27, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Baseball (USA) (GameCube Edition).nes\"\n  67A3C362, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Touch Down Fever (Japan).nes\"\n  67CBC0A0, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Hammerin' Harry (Europe).nes\"\n  67D5C3F9, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Hello Kitty World (Japan).nes\"\n  67F77118, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Rocket Ranger (USA).nes\"\n  67FC2E40, NTSC, 69, 0, 16, 16, 0, false, Vertical, \"Mr. Gimmick (USA) (Proto).nes\"\n  6800C5B3, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Tom Sawyer no Bouken (Japan).nes\"\n  680D2EDA, NTSC, 150, 0, 0, 2, 0, false, Horizontal, \"Chess Academy (Asia) (NTSC) (Unl).nes\"\n  680DA78D, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Bible Adventures (USA) (v1.4) (Unl).nes\"\n  681798A8, PAL, 3, 0, 4, 2, 0, false, Vertical, \"City Connection (Europe).nes\"\n  68379FDB, NTSC, 79, 0, 2, 2, 0, true, Vertical, \"Pipemania (Australia) (HES) (Unl).nes\"\n  68383607, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Wheel of Fortune - Junior Edition (USA).nes\"\n  684AFCCD, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Space Hunter (Japan).nes\"\n  684B292F, NTSC, 19, 0, 32, 16, 0, false, Horizontal, \"Namco Classic II (Japan).nes\"\n  6866A989, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Scarabeus (USA) (Sample).nes\"\n  689971F9, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Super Dodge Ball (USA).nes\"\n  68AFEF5F, NTSC, 3, 0, 8, 2, 0, false, Vertical, \"Bubble Bath Babes (USA) (Unl).nes\"\n  68C62E50, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Home Alone 2 - Lost in New York (Europe).nes\"\n  68CF9B78, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Fester's Quest (Europe).nes\"\n  68EC97CB, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Desert Commander (USA).nes\"\n  68F9B5F5, PAL, 1, 0, 1, 16, 0, false, Horizontal, \"Defender of the Crown (Europe).nes\"\n  690AFE9F, PAL, 4, 0, 32, 16, 0, false, Vertical, \"Ultimate Air Combat (Europe) (En,Fr,De) (Beta).nes\"\n  692F2096, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Secret Scout in the Temple of Demise (USA) (Beta) (Unl).nes\"\n  6944A01A, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Isolated Warrior (USA).nes\"\n  694C801F, PAL, 7, 0, 1, 16, 0, false, Horizontal, \"IronSword - Wizards & Warriors II (Europe).nes\"\n  695515A2, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Platoon (USA) (Rev A).nes\"\n  69565F13, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"TM Network - Live in Power Bowl (Japan).nes\"\n  69635A6E, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Rollerball (USA).nes\"\n  696D7839, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Tecmo Cup - Soccer Game (USA).nes\"\n  6997F5E1, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Last Starfighter, The (USA).nes\"\n  699FA085, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Othello (USA).nes\"\n  69BCDB8B, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Navy Blue (Japan).nes\"\n  69D07DDB, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Saiyuuki World (Japan).nes\"\n  69FEECB2, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Flipull - An Exciting Cube Game (Japan) (En) (Rev 1).nes\"\n  6A154B68, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Ghostbusters (USA).nes\"\n  6A1F628A, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Shadowgate (USA).nes\"\n  6A457A43, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Takahashi Meijin no Bouken-jima (Japan).nes\"\n  6A483073, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Pesterminator (USA) (Unl).nes\"\n  6A6B7239, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Hana no Star Kaidou (Japan).nes\"\n  6A88579F, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Jeopardy! Junior Edition (USA).nes\"\n  6ABAD366, NTSC, 1, 0, 8, 16, 0, false, Horizontal, \"Bases Loaded (USA) (Rev B).nes\"\n  6AE69227, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Sou Setsu Ryuu III - The Rosetta Stone (Japan).nes\"\n  6AE762AE, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Hyper Sports (Japan) (Rev 1).nes\"\n  6B53006A, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Battle of Olympus, The (USA).nes\"\n  6B761858, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Adventures in the Magic Kingdom (Europe).nes\"\n  6BB6A0CE, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Palamedes (USA).nes\"\n  6BC33D2F, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Wizardry - Knight of Diamonds - The Second Scenario (USA).nes\"\n  6BC65D7E, NTSC, 66, 0, 4, 8, 0, false, Vertical, \"Youkai Club (Japan).nes\"\n  6BD7047A, NTSC, 79, 0, 4, 4, 0, false, Vertical, \"Robert Byrne's Pool Challenge (USA) (Proto) (Unl).nes\"\n  6C1AB645, PAL, 4, 0, 16, 8, 0, true, Vertical, \"Jurassic Park (Europe).nes\"\n  6C4A9735, PAL, 1, 0, 1, 8, 0, true, Horizontal, \"WWF Wrestlemania (Europe).nes\"\n  6C61B622, NTSC, 72, 0, 0, 8, 0, false, Vertical, \"Moero!! Pro Tennis (Japan).nes\"\n  6C70A17B, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Chester Field - Ankoku Shin e no Chousen (Japan).nes\"\n  6C93377C, NTSC, 71, 0, 1, 4, 0, false, Vertical, \"Bee 52 (USA) (Unl).nes\"\n  6C940A59, NTSC, 4, 0, 16, 8, 0, false, FourScreen, \"SD Gundam - Gachapon Senshi 5 - Battle of Universal Century (Japan).nes\"\n  6CCA1C1F, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Family Trainer 7 - Daiundoukai (Japan).nes\"\n  6CD46979, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Bandai Golf - Challenge Pebble Beach (USA).nes\"\n  6CD9CC23, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Bakushou! Star Monomane Shitennou (Japan).nes\"\n  6CDC0CD9, NTSC, 33, 0, 0, 8, 0, false, Horizontal, \"Bubble Bobble 2 (Japan).nes\"\n  6D65CAC6, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Terra Cresta (Japan).nes\"\n  6DC28B5A, NTSC, 25, 0, 0, 8, 0, false, Horizontal, \"Bio Miracle Bokutte Upa (Japan).nes\"\n  6DCBAAFD, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"RoboCop (Europe).nes\"\n  6DCE4B23, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Double Dragon - Sou Setsu Ryuu (Japan).nes\"\n  6DECD886, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Guerrilla War (USA).nes\"\n  6E0EB43E, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Puss n Boots - Pero's Great Adventure (USA).nes\"\n  6E4DCFD2, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Roundball - 2-on-2 Challenge (USA).nes\"\n  6E68E31A, NTSC, 16, 0, 32, 8, 0, false, Horizontal, \"Dragon Ball 3 - Gokuu Den (Japan).nes\"\n  6E85D8DD, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Adventures of Tom Sawyer (USA).nes\"\n  6EC51DE5, NTSC, 210, 2, 16, 8, 0, false, Vertical, \"Famista '92 (Japan).nes\"\n  6ED31CCD, NTSC, 1, 0, 1, 8, 0, false, Vertical, \"Chip's Challenge (USA) (v0.924B) (Proto).nes\"\n  6EE4BB0A, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Mega Man (USA).nes\"\n  6EE94D32, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Smash T.V. (USA).nes\"\n  6EEA1B10, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Ninja Gaiden (USA) (Beta).nes\"\n  6F10097D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Simpsons, The - Bart vs. the Space Mutants (USA).nes\"\n  6F27300B, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Teenage Mutant Ninja Turtles (Italy).nes\"\n  6F4E4312, NTSC, 5, 0, 32, 32, 0, true, Horizontal, \"Aoki Ookami to Shiroki Mejika - Genchou Hishi (Japan).nes\"\n  6F5D9B2A, NTSC, 32, 0, 0, 8, 0, false, Horizontal, \"Meikyuu-jima (Japan).nes\"\n  6F6686B0, NTSC, 65, 0, 0, 8, 0, false, Horizontal, \"Spartan X 2 (Japan).nes\"\n  6F790F9B, NTSC, 2, 0, 1, 8, 0, true, Horizontal, \"Rainbow Islands - The Story of Bubble Bobble 2 (Japan) (Sample).nes\"\n  6F8AF3E8, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Top Gun - The Second Mission (USA).nes\"\n  6F97C721, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Donkey Kong (World) (Rev A).nes\"\n  6FB349E2, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Mickey's Adventure in Numberland (USA).nes\"\n  6FD5A271, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Whomp 'Em (USA).nes\"\n  6FD69F34, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Dr. Mario (USA) (Beta) (1990-04-27).nes\"\n  7002FE8D, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Life Force - Salamander (Europe).nes\"\n  70080810, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Metroid (USA).nes\"\n  701B1ADF, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Takahashi Meijin no Bouken-jima II (Japan).nes\"\n  705BD7C3, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Superstar Pro Wrestling (Japan).nes\"\n  7077B075, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Lethal Weapon (USA).nes\"\n  7080D1F8, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Power Soccer (Japan).nes\"\n  70860FCA, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Popeye (World) (Rev A).nes\"\n  708EA2BE, NTSC, 4, 0, 32, 16, 0, true, Horizontal, \"Joy Mech Fight (Japan).nes\"\n  709C9399, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Shadow Warriors (Europe).nes\"\n  70CE3771, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Prince of Persia (USA).nes\"\n  70F31D2C, PAL, 71, 0, 1, 16, 0, false, Vertical, \"Cosmic Spacehead (Europe) (En,Fr,De,Es) (Unl).nes\"\n  70F67AB7, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Musashi no Bouken (Japan).nes\"\n  711896B8, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Xenophobe (USA).nes\"\n  711C2B0E, NTSC, 4, 0, 2, 2, 0, false, Horizontal, \"Super Chinese (Japan).nes\"\n  7156CB4D, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Muppet Adventure - Chaos at the Carnival (USA).nes\"\n  716DAEA5, NTSC, 19, 0, 32, 16, 0, true, Horizontal, \"Juvei Quest (Japan) (Rev 1).nes\"\n  7172F3D4, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Kabushiki Doujou (Japan).nes\"\n  719571B3, PAL, 0, 0, 1, 1, 0, false, Horizontal, \"Road Fighter (Europe).nes\"\n  71BF075F, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Adventures of Lolo (USA).nes\"\n  71C01B19, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Little Red Hood (Australia) (Unl).nes\"\n  71C9ED1E, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Kujaku Ou (Japan).nes\"\n  71CAF097, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"3-D Block (Asia) (RCM Group) (Unl).nes\"\n  71D8C6E9, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Ultima - Seija e no Michi (Japan).nes\"\n  721B5217, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Daiva - Imperial of Nirsartia (Japan).nes\"\n  728BFA8D, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Route-16 Turbo (Japan).nes\"\n  72928698, NTSC, 69, 0, 16, 8, 0, false, Horizontal, \"Hebereke (Japan).nes\"\n  72E66392, NTSC, 11, 0, 2, 4, 0, false, Vertical, \"Crystal Mines (USA) (Unl).nes\"\n  72FA78C3, NTSC, 139, 0, 0, 4, 0, false, Horizontal, \"Mahjang Companion (Asia) (Sachen) (Unl).nes\"\n  73140EEF, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Bible Adventures (USA) (v1.3) (Unl).nes\"\n  7329118D, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Casino Kid II (USA).nes\"\n  73298C87, PAL, 4, 0, 32, 16, 0, false, Horizontal, \"Super Mario Bros. + Tetris + Nintendo World Cup (Europe).nes\"\n  73418721, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Challenger (Japan).nes\"\n  73620901, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Dr. Chaos (USA).nes\"\n  736FEBC4, NTSC, 4, 0, 32, 16, 0, true, Vertical, \"Meng Huan - Xiang Shuai Chuan Qi Zhi Xue Hai Piao Ling (China) (Unl).nes\"\n  739A1027, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Nuts & Milk (Japan).nes\"\n  73C09BCB, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Hanafuda Yuukyou Den - Nagarebana Oryuu (Japan) (Unl) [b].nes\"\n  73C246D4, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Bible Adventures (USA) (v1.1) (Unl).nes\"\n  73C7FCF4, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"DuckTales 2 (USA).nes\"\n  73D5F7D3, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Sqoon (Japan) (Rev A).nes\"\n  73E41AC7, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"100 Man Dollar Kid - Maboroshi no Teiou Hen (Japan).nes\"\n  73F7E5D8, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Deep Dungeon III - Yuushi e no Tabi (Japan).nes\"\n  73FB55AC, NTSC, 243, 0, 0, 4, 0, false, Vertical, \"Lightgun Game 2 in 1 - Cosmocop + Cyber Monster (Asia) (Unl).nes\"\n  7416903F, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Simpsons, The - Bart vs. the World (USA).nes\"\n  74189E12, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"S.C.A.T. - Special Cybernetic Attack Team (USA).nes\"\n  743387FF, NTSC, 85, 0, 0, 32, 0, true, Horizontal, \"Lagrange Point (Japan).nes\"\n  74386F15, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Godzilla - Monster of Monsters! (USA).nes\"\n  74663267, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Hiryuu no Ken II - Dragon no Tsubasa (Japan).nes\"\n  7474AC92, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Kabuki - Quantum Fighter (USA).nes\"\n  74920C13, NTSC, 168, 0, 0, 4, 0, true, Horizontal, \"Racermate Challenge II (USA) (v3.11.088) (Unl).nes\"\n  74BEA652, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"3 in 1 Supergun (Asia) (Unl).nes\"\n  74EE0FFC, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Radac Tailor-Made (Japan).nes\"\n  74F0A89F, NTSC, 185, 0, 0, 2, 0, false, Horizontal, \"B-Wings (Japan).nes\"\n  75255F88, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"P.O.W. - Prisoners of War (USA).nes\"\n  752743EC, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Willow (Japan).nes\"\n  753768A6, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Vice - Project Doom (USA).nes\"\n  757EFB63, NTSC, 0, 0, 1, 2, 0, true, Horizontal, \"Skate Boy (Spain) (Gluk Video) (Unl).nes\"\n  75901B18, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Titan (Japan).nes\"\n  759418D2, PAL, 2, 0, 1, 8, 0, false, Horizontal, \"Alfred Chicken (Europe).nes\"\n  75A7E399, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Lot Lot (Japan).nes\"\n  75B3EB37, NTSC, 18, 0, 16, 8, 0, false, Horizontal, \"Saiyuuki World 2 - Tenjoukai no Majin (Japan).nes\"\n  75C3E7D4, PAL, 3, 0, 4, 2, 0, false, Horizontal, \"Solomon's Key (Europe).nes\"\n  7653103A, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Mighty Final Fight (Europe).nes\"\n  7678F1D5, NTSC, 80, 0, 0, 16, 0, false, Horizontal, \"Fudou Myouou Den (Japan).nes\"\n  768A1B6A, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Nakajima Satoru - F-1 Hero (Japan).nes\"\n  76C161E3, PAL, 1, 0, 1, 16, 0, false, Horizontal, \"Faxanadu (Europe).nes\"\n  771C8855, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Adventure Island II (USA).nes\"\n  771CE357, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Hokuto no Ken 3 - Shin Seiki Souzou Seiken Restuden (Japan).nes\"\n  7739672E, NTSC, 0, 0, 4, 2, 0, false, Vertical, \"Metal Fighter (Asia) (Sachen) (Unl).nes\"\n  77512388, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Millipede - Kyodai Konchuu no Gyakushuu (Japan).nes\"\n  7751588D, PAL, 1, 0, 1, 8, 0, false, Horizontal, \"Metroid (Europe).nes\"\n  77833016, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Chubby Cherub (USA).nes\"\n  77BF8B23, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Hydlide (USA).nes\"\n  77D59400, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Nintendo World Class Service - Power Pad Test Cartridge (USA).nes\"\n  77DA06CF, NTSC, 115, 0, 0, 8, 0, false, Horizontal, \"AV Kyuukyoku Mahjong 2 (Asia) (Unl).nes\"\n  77DCBBA3, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Eggerland - Meikyuu no Fukkatsu (Japan).nes\"\n  77F0F71D, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Simpsons, The - Bartman Meets Radioactive Man (USA) (Beta).nes\"\n  78211EBF, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Daiku no Gen-san (Japan).nes\"\n  7840B18D, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Chitei Senkuu Vazolder (Japan).nes\"\n  784272F2, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Hector '87 (Japan).nes\"\n  78A48B23, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Baseball (Japan).nes\"\n  78B657AC, NTSC, 118, 0, 0, 16, 0, false, Vertical, \"Armadillo (Japan).nes\"\n  78C4460D, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Cocoron (Japan).nes\"\n  78C72C75, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"S.C.A.T. - Special Cybernetic Attack Team (USA) (Beta).nes\"\n  78CC796B, NTSC, 4, 0, 32, 8, 0, false, Vertical, \"Batman Returns (Unknown) (Beta).nes\"\n  790B295B, PAL, 4, 0, 4, 2, 0, false, Horizontal, \"To the Earth (Europe).nes\"\n  792070A9, NTSC, 232, 0, 0, 16, 0, false, Vertical, \"Quattro Arcade (USA) (Unl).nes\"\n  794CAAB6, NTSC, 75, 0, 0, 8, 0, false, Vertical, \"Tetsuwan Atom (Japan).nes\"\n  795BC424, PAL, 137, 0, 0, 2, 0, false, FourScreen, \"Great Wall, The (Asia) (PAL) (Unl).nes\"\n  79698B98, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"God Slayer - Haruka Tenkuu no Sonata (Japan).nes\"\n  7980C4F7, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Ultraman Club 2 - Kaettekita Ultraman Club (Japan).nes\"\n  7984AE6D, NTSC, 79, 0, 4, 2, 0, false, Horizontal, \"Puzzle (Spain) (Gluk Video) (Unl).nes\"\n  798EEB98, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"TaleSpin (USA).nes\"\n  79D48F34, PAL, 4, 0, 32, 8, 0, false, Horizontal, \"Batman Returns (Europe).nes\"\n  79F688BC, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Gauntlet II (Europe).nes\"\n  7A11D2C9, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Igo Shinan '92 (Japan).nes\"\n  7A3A49ED, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Elevator Action (Japan) (Rev A).nes\"\n  7A497AE3, NTSC, 33, 0, 0, 8, 0, false, Horizontal, \"Don Doko Don (Japan).nes\"\n  7AA02377, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Outlanders (Japan).nes\"\n  7AC3E8A1, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"RoboCop (USA) (Beta).nes\"\n  7AE0BF3C, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Zelda no Densetsu 1 - The Hyrule Fantasy (Japan).nes\"\n  7AE5C002, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Jackie Chan's Action Kung Fu (Europe).nes\"\n  7B0A41B9, NTSC, 2, 0, 1, 16, 0, false, Vertical, \"Esper Bouken Tai (Japan).nes\"\n  7B44FB2A, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Ide Yousuke Meijin no Jissen Mahjong II (Japan).nes\"\n  7B4ED0BB, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"WWF King of the Ring (USA).nes\"\n  7B5206AF, NTSC, 16, 0, 16, 8, 0, false, Horizontal, \"Meimon! Daisan Yakyuubu (Japan).nes\"\n  7B55D481, PAL, 1, 0, 16, 8, 0, false, Vertical, \"Ghostbusters II (Europe).nes\"\n  7B6DC772, NTSC, 95, 0, 4, 8, 0, false, Vertical, \"Dragon Buster (Japan).nes\"\n  7B72FBA4, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Thunderbirds (Japan).nes\"\n  7BA3F8AE, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"North & South (Europe).nes\"\n  7BB5664F, NTSC, 4, 0, 4, 8, 0, false, Horizontal, \"Super Xevious - Gump no Nazo (Japan).nes\"\n  7BCCAFBB, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Teenage Mutant Ninja Turtles II - The Arcade Game (Australia).nes\"\n  7BD8F902, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Takahashi Meijin no Bouken-jima IV (Japan).nes\"\n  7BF8A890, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Ninja Ryuuken Den II - Ankoku no Jashin Ken (Japan).nes\"\n  7C108923, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Saint Seiya - Ougon Densetsu Kanketsu Hen (Japan) (Beta).nes\"\n  7C16F819, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Nintendo World Cup (Europe) (Rev A).nes\"\n  7C27AB86, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Aces - Iron Eagle 3 (Japan).nes\"\n  7C3D2EA3, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"SWAT - Special Weapons and Tactics (Japan).nes\"\n  7C42CB7B, NTSC, 3, 0, 2, 2, 0, false, Vertical, \"Banana (Japan) (Beta) (1986-06-30).nes\"\n  7C4A72D8, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Ninja Gaiden (USA).nes\"\n  7C596E45, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Portopia Renzoku Satsujin Jiken (Japan).nes\"\n  7C6A3D51, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Mickey Mousecapade (USA).nes\"\n  7C6F615F, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Attack of the Killer Tomatoes (USA).nes\"\n  7C7A0A73, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Bashi Bazook - Morphoid Masher (USA) (Proto).nes\"\n  7CB0D70D, PAL, 7, 0, 1, 8, 0, false, Horizontal, \"Solstice - The Quest for the Staff of Demnos (Europe).nes\"\n  7CF6B30A, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Locksmith (Asia) (PAL) (Unl).nes\"\n  7D55CF29, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Attack Animal Gakuen (Japan).nes\"\n  7D5F149B, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Ninja Gaiden - Episode II - The Dark Sword of Chaos (USA) (Beta) (1990-01-18).nes\"\n  7D6C2065, NTSC, 1, 0, 1, 16, 0, true, Vertical, \"Legend of Robin Hood, The (USA) (Proto).nes\"\n  7DA77F11, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Jungle Book, The (Europe).nes\"\n  7DCB4C18, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Mutant Virus, The - Crisis in a Computer World (USA).nes\"\n  7DD0AFC8, PAL, 0, 0, 16, 4, 0, false, Vertical, \"Challenge of the Dragon (Asia) (PAL) (Unl).nes\"\n  7E053E64, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"BattleCity (Japan).nes\"\n  7E4BA78F, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Adventure Island Part II, The (Europe).nes\"\n  7E57FBEC, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Town & Country Surf Designs - Thrilla's Surfari (USA).nes\"\n  7E9BCA05, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Crackout (USA) (Proto).nes\"\n  7EABDA5C, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"King of Kings, The (USA) (v5.0) (Unl).nes\"\n  7EAE9A13, NTSC, 234, 0, 0, 32, 0, false, Horizontal, \"Maxi 15 (USA) (Rev 1) (Unl).nes\"\n  7EC6F75B, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Magical Kid's Doropie (Japan).nes\"\n  7EE02CA2, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Bard's Tale, The (Japan).nes\"\n  7EE625EB, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Road Fighter (Japan).nes\"\n  7F08D0D9, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Nintendo World Cup (Europe) (Rev B).nes\"\n  7F24EFC0, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Bible Adventures (USA) (v1.2) (Unl).nes\"\n  7F45CFF5, NTSC, 92, 0, 0, 16, 0, false, Vertical, \"Moero!! Pro Soccer (Japan).nes\"\n  7F495283, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Pooyan (Japan).nes\"\n  7F4CB1B4, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Double Dribble (Europe).nes\"\n  7F801368, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Tecmo Cup - Football Game (Spain).nes\"\n  7F9C1DEC, NTSC, 7, 0, 1, 2, 0, false, Vertical, \"BB Car (Unknown) (Unl).nes\"\n  7FA191E7, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Track & Field II (USA) (Rev A).nes\"\n  7FA2CC55, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Castle Excellent (Japan).nes\"\n  7FB74A43, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Toki (USA).nes\"\n  7FF76219, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Tecmo World Wrestling (USA).nes\"\n  80250D64, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Monster in My Pocket (Europe).nes\"\n  803B9979, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"J.League Fighting Soccer - The King of Ace Strikers (Japan).nes\"\n  804F898A, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Dragon Unit (Japan).nes\"\n  805F81BC, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Metal Gear (Japan).nes\"\n  806DE21E, PAL, 7, 0, 1, 16, 0, false, Horizontal, \"Wizards & Warriors III - Kuros...Visions of Power (Europe).nes\"\n  808606F0, NTSC, 210, 1, 16, 8, 0, false, Vertical, \"Famista '91 (Japan).nes\"\n  80D63472, PAL, 0, 0, 2, 1, 0, false, Horizontal, \"Sidewinder (Asia) (PAL) (Unl).nes\"\n  80F39D59, NTSC, 79, 0, 4, 2, 0, false, Horizontal, \"Poke Block (Asia) (Unl).nes\"\n  80FB7E6B, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Super Mario USA (Japan).nes\"\n  8106E694, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Blaster Master (Europe).nes\"\n  810B7AB9, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Robo Warrior (USA).nes\"\n  8111BA08, NTSC, 7, 0, 1, 16, 0, false, Horizontal, \"Solar Jetman - Hunt for the Golden Warpship (USA).nes\"\n  811F06D9, NTSC, 66, 0, 4, 8, 0, false, Vertical, \"Dragon Power (USA).nes\"\n  81210F63, PAL, 0, 0, 1, 1, 0, false, Horizontal, \"Pac-Man (Europe).nes\"\n  81241DEC, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"F-1 Sensation (Japan).nes\"\n  81389607, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Magmax (USA).nes\"\n  816AD178, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Batman - The Video Game (USA) (Beta 1).nes\"\n  817431EC, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Metal Gear (USA).nes\"\n  8192D2E7, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Kid Niki - Radical Ninja (USA).nes\"\n  81A5EB65, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Tiny Toon Adventures 2 - Trouble in Wackyland (USA).nes\"\n  81AF4AF9, PAL, 2, 0, 1, 8, 0, false, Horizontal, \"Crackout (Europe).nes\"\n  81B2A3CD, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Noah's Ark (Europe).nes\"\n  81B7F1A8, NTSC, 210, 1, 16, 8, 0, false, Vertical, \"Heisei Tensai Bakabon (Japan).nes\"\n  821F2F9F, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Famicom Meijin Sen (Japan) (Rev A).nes\"\n  821FEB7A, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Ikki (Japan).nes\"\n  822F17EB, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Softball Tengoku (Japan).nes\"\n  828F8F1F, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Sensha Senryaku - Sabaku no Kitsune (Japan).nes\"\n  8293803A, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Prince of Persia (France).nes\"\n  82AFA828, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Clash at Demonhead (USA).nes\"\n  82BE4724, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Commando (USA).nes\"\n  83000991, PAL, 2, 0, 1, 8, 0, false, Horizontal, \"Side Pocket (Europe).nes\"\n  8308FED7, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Fighting Golf (Japan).nes\"\n  831F9C1A, NTSC, 79, 0, 8, 2, 0, false, Horizontal, \"Ultimate League Soccer (USA) (Unl).nes\"\n  8324A464, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Kakefu-kun no Jump Tengoku - Speed Jigoku (Japan).nes\"\n  836685C4, PAL, 1, 0, 4, 8, 0, false, Horizontal, \"Mario & Yoshi (Europe).nes\"\n  8366CF72, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Titan Warriors (USA) (Proto).nes\"\n  836C4FA7, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"10-Yard Fight (Japan).nes\"\n  836FE2C2, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Jack Nicklaus' Greatest 18 Holes of Major Championship Golf (Europe).nes\"\n  837A3D8A, PAL, 4, 0, 16, 16, 0, false, Vertical, \"Mega Man 3 (Europe).nes\"\n  83CB743F, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Koushien (Japan).nes\"\n  83EA7B04, NTSC, 4, 0, 1, 32, 0, false, Horizontal, \"Battle Baseball (Japan).nes\"\n  83EAF3B1, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Best Keiba - Derby Stallion (Japan) (Rev A).nes\"\n  83FC38F8, NTSC, 4, 0, 4, 8, 0, false, Horizontal, \"Mappy-Land (USA).nes\"\n  84148F73, NTSC, 1, 0, 16, 16, 0, false, Horizontal, \"Goal! (USA).nes\"\n  841B69B6, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Hatris (USA).nes\"\n  84382231, NTSC, 9, 0, 16, 8, 0, false, Horizontal, \"Punch-Out!! (Japan) (Gold Edition).nes\"\n  846C9304, NTSC, 4, 0, 32, 16, 0, true, Vertical, \"Lin Ze Xu Jin Yan (China) (Unl).nes\"\n  847D672D, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Bill Elliott's NASCAR Challenge (USA).nes\"\n  84BE00E9, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"4 Nin Uchi Mahjong (Japan) (Rev A).nes\"\n  84C4A12E, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Metal Gear (Europe).nes\"\n  84F7FC31, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Chip 'n Dale - Rescue Rangers (Europe).nes\"\n  850090BC, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"White Lion Densetsu (Japan).nes\"\n  851EB9BE, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Shooting Range (USA).nes\"\n  8531C166, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Majaventure - Mahjong Senki (Japan).nes\"\n  85323FD6, NTSC, 79, 0, 4, 2, 0, false, Vertical, \"Krazy Kreatures (USA) (Beta) (Unl).nes\"\n  853FEEA4, PAL, 4, 0, 4, 2, 0, false, Horizontal, \"Adventures of Lolo 2 (Europe).nes\"\n  856E7600, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Superman (Japan).nes\"\n  8575A0CB, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Hydlide Special (Japan).nes\"\n  8593E5AD, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"World Champ - Super Boxing Great Fight (USA).nes\"\n  859C65E1, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Little League Baseball - Championship Series (USA).nes\"\n  85A6C0D5, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Tecmo Bowl (USA) (Rev A).nes\"\n  85BC0777, NTSC, 4, 0, 1, 32, 0, false, Horizontal, \"Mahjong Club Nagatachou - Sousaisen (Japan).nes\"\n  85BFFFEF, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Goonies II, The - Fratelli Saigo no Chousen (Japan) (Beta).nes\"\n  85C5953F, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Hostages - The Embassy Mission (Japan).nes\"\n  85D02CD4, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Bugs Bunny Birthday Bash (USA) (Beta) [b].nes\"\n  85E0090B, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Choujinrou Senki Warwolf (Japan).nes\"\n  85F12D37, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Gambler Jiko Chuushinha 2 (Japan).nes\"\n  86083FBC, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Corre Benny (Spain) (Gluk Video) (Unl).nes\"\n  8650BE49, NTSC, 4, 0, 16, 8, 0, true, Vertical, \"McDonaldland (France).nes\"\n  86670C93, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Slalom (USA).nes\"\n  86759C0F, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Monopoly (Japan).nes\"\n  86867830, PAL, 3, 0, 4, 2, 0, false, Vertical, \"Adventure Island Classic (Europe).nes\"\n  869501CA, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Dragon Quest III - Soshite Densetsu e... (Japan) (Rev B).nes\"\n  86964EDD, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Teenage Mutant Ninja Turtles - Tournament Fighters (USA).nes\"\n  86974CCC, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"King of Kings, The (USA) (v1.3) (Unl).nes\"\n  86ACB36B, NTSC, 3, 0, 2, 2, 0, false, Horizontal, \"Banana (Japan).nes\"\n  86B0D1CF, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Robin Hood - Prince of Thieves (USA).nes\"\n  86C495C6, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Gargoyle's Quest II (Europe).nes\"\n  86DBA660, NTSC, 219, 0, 0, 2, 0, false, Vertical, \"3-D Block (Asia) (Hwang Shinwei) (Unl).nes\"\n  86E02D65, PAL, 4, 0, 8, 4, 0, false, Vertical, \"Tecmo World Cup Soccer (Europe).nes\"\n  872DE7A2, NTSC, 4, 0, 32, 16, 0, false, Vertical, \"F-15 Strike Eagle (Sweden) (Sv,Da,Fi).nes\"\n  8752DCCB, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Puzznic (Japan).nes\"\n  87CE3F34, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Dragon Wars (Japan).nes\"\n  87D7CAF0, NTSC, 3, 0, 1, 2, 0, false, Vertical, \"Othello (Japan).nes\"\n  87DA4BD0, NTSC, 185, 0, 0, 2, 0, false, Vertical, \"Sansuu 3 Nen - Keisan Game (Japan).nes\"\n  88053D25, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Dragon Quest (Japan).nes\"\n  88062D9A, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Nekketsu! Street Basket - Ganbare Dunk Heroes (Japan).nes\"\n  882E1901, NTSC, 79, 0, 4, 2, 0, false, Vertical, \"Venice Beach Volleyball (USA) (Unl).nes\"\n  88338ED5, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Cyberball (USA).nes\"\n  883454EA, NTSC, 87, 0, 0, 2, 0, false, Vertical, \"Choplifter (Japan).nes\"\n  8889C564, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Immortal, The (USA).nes\"\n  889129CB, NTSC, 4, 0, 32, 16, 0, true, Horizontal, \"StarTropics (USA).nes\"\n  8897A8F1, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Goonies II, The (Europe).nes\"\n  88A6B192, NTSC, 79, 0, 4, 2, 0, false, Horizontal, \"F15 City War (USA) (v1.0) (Unl).nes\"\n  88C30FDA, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Super Turrican (Europe).nes\"\n  88E1A5F4, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Lee Trevino's Fighting Golf (USA).nes\"\n  8904149E, PAL, 7, 0, 1, 16, 0, false, Horizontal, \"Solar Jetman - Hunt for the Golden Warpship (Europe).nes\"\n  892434DD, NTSC, 71, 0, 1, 16, 0, false, Vertical, \"Ultimate Stuntman, The (USA) (Unl).nes\"\n  8927FD4C, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Rockin' Kats (USA).nes\"\n  892CBBC2, NTSC, 185, 0, 0, 2, 0, false, Vertical, \"Sansuu 2 Nen - Keisan Game (Japan).nes\"\n  894EFDBC, NTSC, 16, 0, 1, 16, 0, false, Horizontal, \"Datach - Crayon Shin-chan - Ora to Poi Poi (Japan).nes\"\n  89550500, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Igo Shinan (Japan).nes\"\n  89567668, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Boulder Dash (Japan).nes\"\n  89821E2B, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"RoboCop 2 (Europe).nes\"\n  898E4232, PAL, 7, 0, 1, 8, 0, false, Horizontal, \"Cobra Triangle (Europe).nes\"\n  899213DC, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Mickey Mouse - Dream Balloon (USA) (Beta).nes\"\n  89984244, PAL, 7, 0, 1, 16, 0, false, Horizontal, \"Lion King, The (Europe).nes\"\n  89A45446, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Bram Stoker's Dracula (Europe).nes\"\n  89D42098, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Pac-Land (Japan).nes\"\n  89E085FE, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Zoids 2 - Zenebas no Gyakushuu (Japan).nes\"\n  89EC53C8, PAL, 2, 0, 1, 8, 0, false, Vertical, \"DuckTales 2 (Europe) (Beta).nes\"\n  8A043CD6, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Mafat Conspiracy, The (USA).nes\"\n  8A0C7337, PAL, 0, 0, 1, 1, 0, false, Vertical, \"Excitebike (Europe).nes\"\n  8A12A7D9, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Family Trainer 8 - Totsugeki! Fuuun Takeshi-jou (Japan).nes\"\n  8A368744, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Ginga no Sannin (Japan).nes\"\n  8A36D2B7, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Gojira (Japan).nes\"\n  8A5BC0D3, NTSC, 4, 0, 8, 4, 0, false, Horizontal, \"Tecmo World Cup Soccer (Japan).nes\"\n  8A640AEF, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Double Dragon II - The Revenge (USA) (Rev A).nes\"\n  8A65E57C, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Banana Prince (Germany).nes\"\n  8A7D0ABE, NTSC, 33, 0, 0, 8, 0, false, Horizontal, \"Akira (Japan).nes\"\n  8A96E00D, NTSC, 23, 0, 0, 8, 0, false, Horizontal, \"Wai Wai World (Japan).nes\"\n  8AB52A24, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Blades of Steel (USA).nes\"\n  8ADA3497, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"RoadBlasters (USA).nes\"\n  8AF25130, NTSC, 4, 0, 4, 2, 0, false, Horizontal, \"Babel no Tou (Japan).nes\"\n  8B03F74D, NTSC, 21, 0, 0, 16, 0, false, Horizontal, \"Wai Wai World 2 - SOS!! Paseri Jou (Japan).nes\"\n  8B4A2866, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Sansuu 4 Nen - Keisan Game (Japan).nes\"\n  8B4D2443, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Rescue - The Embassy Mission (Europe).nes\"\n  8B7D3C75, PAL, 1, 0, 4, 2, 0, false, Vertical, \"Anticipation (Europe).nes\"\n  8B9CB8F2, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Dong Dong Nao II - Guo Zhong Ying Wen (Yi) (Asia) (Unl).nes\"\n  8B9D3E9C, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Rad Racer (USA).nes\"\n  8BA75848, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Karateka (Japan).nes\"\n  8BCA5146, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Indiana Jones and the Last Crusade (USA) (Taito).nes\"\n  8BCB0993, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Pachio-kun 3 (Japan).nes\"\n  8BCDE59A, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Athena (Japan).nes\"\n  8BDD3D93, PAL, 4, 0, 32, 8, 0, false, Vertical, \"Gremlins 2 - The New Batch (Europe) (Beta).nes\"\n  8BF29CB6, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Chip 'n Dale - Rescue Rangers (USA).nes\"\n  8C252AC4, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Bad Dudes vs. Dragon Ninja (Europe).nes\"\n  8C4D59D6, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Derby Stallion - Zenkoku Ban (Japan).nes\"\n  8C5A784E, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Dragon Warrior II (USA).nes\"\n  8C71F706, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"King Neptune's Adventure (USA) (Unl).nes\"\n  8C88536F, PAL, 4, 0, 32, 8, 0, false, Vertical, \"George Foreman's KO Boxing (Europe).nes\"\n  8C8DEDB6, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"G.I. Joe - The Atlantis Factor (USA).nes\"\n  8C95A69F, NTSC, 4, 0, 1, 32, 0, true, Vertical, \"Shen Tan Ke Nan (China) (Unl).nes\"\n  8CA72D80, NTSC, 82, 0, 0, 8, 0, true, Horizontal, \"Kyuukyoku Harikiri Koushien (Japan).nes\"\n  8CACCA85, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Hudson's Adventure Island II (USA) (Beta).nes\"\n  8CE478DB, NTSC, 5, 0, 16, 16, 0, true, Horizontal, \"Nobunaga's Ambition II (USA).nes\"\n  8D26FDEA, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"SD Sengoku Bushou Retsuden (Japan).nes\"\n  8D3C33B3, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Soccer League - Winner's Cup (Japan).nes\"\n  8D5B77C0, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Ginga Eiyuu Densetsu (Japan).nes\"\n  8D77E5E6, NTSC, 4, 0, 1, 16, 0, true, Horizontal, \"Business Wars (Japan).nes\"\n  8D901FAD, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Captain Planet and the Planeteers (Europe).nes\"\n  8D97155C, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"California Raisins - The Grape Escape (USA) (Proto 2).nes\"\n  8D9AD3BF, PAL, 2, 0, 1, 8, 0, false, Horizontal, \"Indiana Jones and the Last Crusade (Europe).nes\"\n  8DA4E539, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Idol Hakkenden (Japan).nes\"\n  8DA651D4, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Street Fighter 2010 - The Final Fight (USA).nes\"\n  8DA6667D, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Nintendo World Cup (Europe).nes\"\n  8DB31730, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Mario Bros. (USA) (GameCube Edition).nes\"\n  8DB43824, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Darkwing Duck (USA) (Beta).nes\"\n  8DCD9486, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Jumbo Ozaki no Hole in One Professional (Japan).nes\"\n  8DD92725, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Adventures of Lolo 3 (USA).nes\"\n  8E0D9179, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Chuuka Taisen (Japan).nes\"\n  8E373118, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Battle Storm (Japan).nes\"\n  8E62D229, NTSC, 185, 0, 0, 2, 0, false, Horizontal, \"Sansuu 1 Nen - Keisan Game (Japan).nes\"\n  8E7ABDFC, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Magnum Kikiippatsu - Empire City - 1931 (Japan).nes\"\n  8EAB381C, NTSC, 79, 0, 8, 4, 0, false, Vertical, \"Deathbots (USA) (Rev 1) (Unl).nes\"\n  8EE25F78, NTSC, 80, 0, 0, 8, 0, true, Vertical, \"Minelvaton Saga - Ragon no Fukkatsu (Japan).nes\"\n  8EE7C43E, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Kid Klown in Night Mayor World (USA).nes\"\n  8EEF8B76, NTSC, 4, 0, 1, 32, 0, true, Horizontal, \"Last Armageddon (Japan).nes\"\n  8F011713, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Kurogane Hiroshi no Yosou Daisuki! - Kachiuma Densetsu (Japan).nes\"\n  8F154A0D, NTSC, 3, 0, 4, 1, 0, false, Horizontal, \"Pu Ke Jing Ling (China) (Unl).nes\"\n  8F197B0A, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Rygar (Europe).nes\"\n  8F4497EE, NTSC, 3, 0, 2, 2, 0, false, Horizontal, \"Peepar Time (Japan).nes\"\n  8F628D51, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Hiryuu no Ken III - 5 Nin no Dragon (Japan).nes\"\n  8FA6E92C, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Rackets & Rivals (Europe).nes\"\n  8FF31896, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"M.U.S.C.L.E. - Tag Team Match (USA).nes\"\n  900E3A23, NTSC, 4, 0, 32, 16, 0, true, Horizontal, \"Silva Saga (Japan).nes\"\n  90150FAC, NTSC, 177, 0, 0, 64, 0, true, Horizontal, \"Wang Zi Fu Chou Ji (China) (Unl).nes\"\n  90226E40, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Power Punch II (USA).nes\"\n  902E3168, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Ninja Gaiden III - The Ancient Ship of Doom (USA).nes\"\n  9044550E, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Family Trainer 10 - Rairai Kyonsees (Japan).nes\"\n  905B93F6, NTSC, 3, 0, 1, 2, 0, false, Horizontal, \"Monstruo de los Globos, El (Spain) (Gluk Video) (Unl).nes\"\n  90600B85, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Seicross (Japan) (Rev 1).nes\"\n  908505EE, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Super Arabian (Japan).nes\"\n  90C773C1, NTSC, 118, 0, 0, 8, 0, false, Vertical, \"Goal! Two (USA).nes\"\n  90D68A43, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Garry Kitchen's BattleTank (USA).nes\"\n  90ECDADE, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Tsuppari Oozumou (Japan).nes\"\n  90F3F161, NTSC, 86, 0, 0, 8, 0, false, Vertical, \"Moero!! Pro Yakyuu (Japan) (Rev 1).nes\"\n  90F6FA33, NTSC, 22, 0, 0, 8, 0, false, Horizontal, \"Ganbare Pennant Race! (Japan).nes\"\n  91328C1D, NTSC, 23, 0, 0, 8, 0, false, Horizontal, \"Tiny Toon Adventures (Japan).nes\"\n  91440AAB, NTSC, 147, 0, 0, 8, 0, false, Vertical, \"Chinese KungFu (Asia) (Unl).nes\"\n  915A53A7, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Hyper Sports (Japan).nes\"\n  917770D8, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Where in Time Is Carmen Sandiego (USA).nes\"\n  917D9262, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Shufflepuck Cafe (Japan).nes\"\n  9198279E, PAL, 7, 0, 1, 8, 0, false, Horizontal, \"Time Lord (Europe).nes\"\n  91AC514E, NTSC, 88, 0, 16, 8, 0, false, Horizontal, \"Namcot Mahjong III - Mahjong Tengoku (Japan).nes\"\n  91B4B1D7, PAL, 66, 0, 2, 4, 0, false, Vertical, \"Super Mario Bros. + Duck Hunt (Europe).nes\"\n  91D52E9A, NTSC, 18, 0, 16, 8, 0, false, Horizontal, \"USA Ice Hockey in FC (Japan).nes\"\n  91E2E863, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Magician (USA).nes\"\n  92197173, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Magic of Scheherazade, The (USA).nes\"\n  9235B57B, NTSC, 71, 0, 1, 16, 0, false, Vertical, \"Micro Machines (USA) (Unl).nes\"\n  9237B447, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Over Horizon (Europe).nes\"\n  923F915B, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Ultraman Club 3 - Matamata Shutsugeki!! Ultra Kyoudai (Japan).nes\"\n  9247C38D, PAL, 119, 0, 0, 8, 0, false, Horizontal, \"Pin Bot (Europe).nes\"\n  924CDE0B, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Eliminator Boat Duel (Europe).nes\"\n  92547F1C, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Ys (Japan).nes\"\n  9273F18E, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"America Daitouryou Senkyo (Japan).nes\"\n  927C7A3A, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Joust (Japan).nes\"\n  92924548, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Ice Hockey (Europe).nes\"\n  92A2185C, NTSC, 9, 0, 16, 8, 0, false, Horizontal, \"Mike Tyson's Punch-Out!! (Japan, USA).nes\"\n  92A3D007, NTSC, 34, 0, 8, 4, 0, false, Vertical, \"Impossible Mission II (USA) (Unl).nes\"\n  92C138E4, NTSC, 1, 0, 8, 16, 0, false, Horizontal, \"Miracle Piano Teaching System, The (USA).nes\"\n  92DD67EA, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Flying Warriors (USA) (Beta).nes\"\n  92F04530, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Goonies 2 - Fratelli Saigo no Chousen (Japan).nes\"\n  93216279, NTSC, 46, 0, 0, 64, 0, false, Vertical, \"Rumble Station - 15 in 1 (USA) (Unl).nes\"\n  93484CC9, NTSC, 234, 0, 0, 32, 0, false, Horizontal, \"Maxi-15 Pack (Australia) (Unl).nes\"\n  934DB14A, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"All-Pro Basketball (USA).nes\"\n  9369A2F8, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Ghost'n Goblins (Europe).nes\"\n  93991433, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Low G Man - The Low Gravity Man (USA).nes\"\n  93A2EEFB, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Final Fantasy II (USA) (Proto).nes\"\n  93A7D26C, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Keiba Simulation - Honmei (Japan).nes\"\n  93B2CEC4, NTSC, 65, 0, 0, 16, 0, false, Horizontal, \"Daiku no Gen-san 2 - Akage no Dan no Gyakushuu (Japan).nes\"\n  93B49582, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Target Renegade (USA).nes\"\n  93F3A490, NTSC, 64, 0, 0, 4, 0, false, Horizontal, \"Klax (USA) (Unl).nes\"\n  942B1210, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Michael Andretti's World GP (USA).nes\"\n  94476A70, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Mega Man (Europe).nes\"\n  948E0BD6, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Taiyou no Yuusha - Fighbird (Japan).nes\"\n  9509F703, NTSC, 4, 0, 32, 16, 0, true, Horizontal, \"Metal Max (Japan).nes\"\n  952A9E77, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Choujin Sentai Jetman (Japan).nes\"\n  9552E8DF, NTSC, 66, 0, 4, 8, 0, false, Vertical, \"Dragon Ball - Shen Long no Nazo (Japan).nes\"\n  9561798D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Saint Seiya - Ougon Densetsu Kanketsu Hen (Japan).nes\"\n  9568EB74, NTSC, 1, 0, 1, 8, 0, false, Vertical, \"Mezase Pachi Pro - Pachio-kun (Japan) (Beta).nes\"\n  956E3D90, NTSC, 4, 0, 32, 8, 0, true, Horizontal, \"Daikaijuu Deburas (Japan).nes\"\n  958E4BAE, NTSC, 1, 0, 16, 4, 0, false, Horizontal, \"Orb 3D (USA).nes\"\n  95CE3B58, PAL, 4, 0, 16, 16, 0, false, Vertical, \"Simpsons, The - Bartman Meets Radioactive Man (Europe).nes\"\n  95D3BFFF, PAL, 3, 0, 4, 2, 0, true, Horizontal, \"Tiger-Heli (Europe) (Rev A).nes\"\n  95E4E594, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"QIX (USA).nes\"\n  96087988, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"RoboCop 3 (USA).nes\"\n  9630A7E5, PAL, 1, 0, 16, 8, 0, false, Vertical, \"Lee Trevino's Fighting Golf (Europe).nes\"\n  9632C470, PAL, 1, 0, 16, 8, 0, false, Vertical, \"Addams Family, The - Pugsley's Scavenger Hunt (Europe).nes\"\n  965834BD, NTSC, 18, 0, 16, 8, 0, false, Horizontal, \"Ninja Jajamaru - Ginga Daisakusen (Japan) (Beta).nes\"\n  967011AD, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Mario Is Missing! (Europe).nes\"\n  96773F32, NTSC, 19, 0, 32, 16, 0, true, Vertical, \"Digital Devil Story - Megami Tensei II (Japan).nes\"\n  969EF9E4, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Winter Games (USA) (Rev A).nes\"\n  96BA90B0, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Houma ga Toki (Japan).nes\"\n  96CFB4D8, PAL, 7, 0, 1, 8, 0, false, Horizontal, \"Digger T. Rock - The Legend of the Lost City (Europe).nes\"\n  96DFC776, NTSC, 4, 0, 8, 8, 0, false, Vertical, \"R.B.I. Baseball 2 (USA) (Unl).nes\"\n  96E6C1CE, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Major League Baseball (USA) (Rev A).nes\"\n  972D08C5, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Tsuppari Wars (Japan).nes\"\n  972D2784, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Soccer (Europe) (Rev A).nes\"\n  9735D267, PAL, 1, 0, 4, 2, 0, false, Horizontal, \"Dr. Mario (Europe).nes\"\n  973BBF75, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Secret Scout in the Temple of Demise (USA) (Unl).nes\"\n  9747AC09, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Monopoly (USA).nes\"\n  974D0745, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Fighting Road (Japan).nes\"\n  974E8840, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Best Play Pro Yakyuu '90 (Japan).nes\"\n  976893D2, PAL, 3, 0, 4, 2, 0, false, Horizontal, \"Alpha Mission (Europe).nes\"\n  978E19FC, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Predator (Australia).nes\"\n  979C5314, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Super Pitfall (USA).nes\"\n  97BC4585, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Touhou Kenbun Roku (Japan).nes\"\n  97CAD370, NTSC, 10, 0, 16, 16, 0, true, Horizontal, \"Fire Emblem - Ankoku Ryuu to Hikari no Tsurugi (Japan).nes\"\n  97D52C06, PAL, 1, 0, 16, 8, 0, true, Horizontal, \"Zelda II - The Adventure of Link (Europe) (Rev A).nes\"\n  9806CB84, NTSC, 7, 0, 1, 16, 0, false, Horizontal, \"Battletoads (Japan).nes\"\n  980BE936, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Hyper Olympic (Japan).nes\"\n  982DFB38, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Mickey's Safari in Letterland (USA).nes\"\n  983948A5, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Karate Kid, The (USA).nes\"\n  983D8175, NTSC, 16, 0, 1, 16, 0, false, Horizontal, \"Datach - Battle Rush - Build Up Robot Tournament (Japan).nes\"\n  985B1D05, NTSC, 3, 0, 2, 2, 0, false, Horizontal, \"TwinBee (Japan).nes\"\n  988798A8, NTSC, 4, 0, 1, 32, 0, false, Horizontal, \"Mega Man 6 (USA).nes\"\n  988B446D, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Lunar Pool (USA).nes\"\n  988C290E, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Bard's Tale, The (Japan) (Sample).nes\"\n  98977591, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Kage (Japan).nes\"\n  989C1019, PAL, 4, 0, 32, 16, 0, false, Horizontal, \"F-15 Strike Eagle (Europe).nes\"\n  98A97A59, NTSC, 4, 0, 1, 32, 0, false, Horizontal, \"Masuzoe Youichi - Asa Made Famicom (Japan).nes\"\n  98C7B4DA, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Wanpaku Duck Yume Bouken (Japan) (Beta).nes\"\n  98CCC9AB, NTSC, 68, 0, 0, 8, 0, false, Horizontal, \"Maharaja (Japan).nes\"\n  99083B3A, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"Joshua & the Battle of Jericho (USA) (v6.0) (Unl).nes\"\n  990985C0, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"RoboCop 2 (USA) (Rev A).nes\"\n  99240573, NTSC, 16, 0, 32, 16, 0, false, Horizontal, \"Dragon Ball Z II - Gekishin Freeza!! (Japan).nes\"\n  992AF039, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Hokkaidou Rensa Satsujin - Okhotsk ni Kiyu (Japan).nes\"\n  99686DAD, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Chip to Dale no Daisakusen 2 (Japan).nes\"\n  998422FC, PAL, 4, 0, 32, 16, 0, true, Horizontal, \"StarTropics (Europe).nes\"\n  9992F445, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Championship Bowling (Japan).nes\"\n  999577B6, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Goonies II, The (USA).nes\"\n  999584A8, PAL, 0, 0, 1, 2, 0, false, Horizontal, \"Galaga (Europe).nes\"\n  99A28276, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Robo Warrior (Europe).nes\"\n  99A62E47, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Black Bass II, The (Japan).nes\"\n  99A9F57E, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Anticipation (USA).nes\"\n  99C395F9, NTSC, 33, 0, 0, 8, 0, false, Horizontal, \"Captain Saver (Japan).nes\"\n  99D15A91, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Spelunker (USA).nes\"\n  99D38676, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Taito Chase H.Q. (Japan).nes\"\n  99DDDB04, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Tiny Toon Adventures (USA).nes\"\n  9A23A458, NTSC, 3, 0, 8, 2, 0, false, Horizontal, \"AV Pachi-Slot (Japan) (Unl).nes\"\n  9A2DB086, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Super Mario Bros. (Europe) (Rev A).nes\"\n  9A808C3B, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Quarth (Japan).nes\"\n  9AACD75D, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Sansuu 5 & 6 Nen - Keisan Game (Japan).nes\"\n  9AB274AE, NTSC, 228, 0, 0, 16, 0, false, Vertical, \"Cheetahmen II (USA) (Unl).nes\"\n  9ACE456E, PAL, 0, 0, 16, 8, 0, false, Horizontal, \"Silver Eagle (Asia) (PAL) (Unl).nes\"\n  9ADFC8F0, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Ultraman Club - Kaijuu Daikessen!! (Japan).nes\"\n  9B05B278, PAL, 4, 0, 16, 8, 0, false, Vertical, \"World Champ - Super Boxing Great Fight (Europe).nes\"\n  9B3C5124, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Pachio-kun 5 (Japan).nes\"\n  9B506A48, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Wrecking Crew (World).nes\"\n  9B53F848, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Shikinjou (Japan).nes\"\n  9B568CC4, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Lemmings (Europe).nes\"\n  9B821A83, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Bard's Tale, The (USA).nes\"\n  9BAC73EF, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Uninvited (USA).nes\"\n  9BD3F3C2, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Bugs Bunny Blowout, The (Europe).nes\"\n  9BDCD892, NTSC, 4, 0, 32, 16, 0, true, Horizontal, \"Might and Magic - Book One - Secret of the Inner Sanctum (Japan).nes\"\n  9BDE3267, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Adventures of Dino Riki (USA).nes\"\n  9C04C8D5, NTSC, 16, 0, 16, 8, 0, false, Horizontal, \"Sakigake!! Otoko Juku - Shippuu Ichi Gou Sei (Japan).nes\"\n  9C053F24, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Igo Shinan '93 (Japan).nes\"\n  9C18762B, NTSC, 5, 0, 16, 16, 0, true, Horizontal, \"Empereur, L' (USA).nes\"\n  9C304DEC, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Hunt for Red October, The (Europe).nes\"\n  9C3E8FC0, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Mezase Pachi Pro - Pachio-kun (Japan).nes\"\n  9C521240, NTSC, 185, 0, 0, 2, 0, false, Horizontal, \"Mighty Bomb Jack (Japan).nes\"\n  9C537919, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Tetris 2 (USA).nes\"\n  9C58F4A6, NTSC, 4, 0, 1, 32, 0, true, Horizontal, \"Momotarou Densetsu Gaiden (Japan).nes\"\n  9C924719, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Rockin' Kats (Europe).nes\"\n  9C9F3571, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Track & Field (USA).nes\"\n  9CBADC25, NTSC, 5, 0, 32, 32, 0, true, Horizontal, \"Just Breed (Japan).nes\"\n  9CBB0291, NTSC, 4, 0, 8, 4, 0, false, Vertical, \"Super Sprint (Japan).nes\"\n  9CBC8253, NTSC, 4, 0, 4, 8, 0, false, Horizontal, \"Family Circuit (Japan).nes\"\n  9CFA55E7, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Famicom Meijin Sen (Japan).nes\"\n  9D048EA4, NTSC, 96, 0, 0, 8, 0, false, Vertical, \"Oeka Kids - Anpanman to Oekaki Shiyou!! (Japan).nes\"\n  9D21FE96, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Lupin Sansei - Pandora no Isan (Japan).nes\"\n  9D34EDC5, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Musashi no Ken - Tadaima Shugyou Chuu (Japan).nes\"\n  9D38F8F9, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Pac-Man (USA) (Tengen) (Unl).nes\"\n  9D45D8EC, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Emoyan no 10 Bai Pro Yakyuu (Japan).nes\"\n  9D779B08, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Donkey Kong (USA) (GameCube Edition).nes\"\n  9D976153, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Akagawa Jirou no Yuurei Ressha (Japan).nes\"\n  9D9A4A26, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Karakuri Kengou Den Musashi Lord - Karakuribito Hashiru (Japan).nes\"\n  9DC96EC7, NTSC, 18, 0, 16, 16, 0, false, Horizontal, \"Moe Pro! '90 - Kandou Hen (Japan).nes\"\n  9DDF9017, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"G.I. Joe - A Real American Hero - The Atlantis Factor (USA) (Beta).nes\"\n  9DF58E80, NTSC, 1, 0, 8, 16, 0, false, Horizontal, \"Miracle Piano Teaching System, The (France).nes\"\n  9E356267, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Morita Shougi (Japan).nes\"\n  9E36080E, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Meimon! Takonishi Ouendan - Kouha 6 Nin Shuu (Japan).nes\"\n  9E379698, NTSC, 71, 0, 1, 16, 0, false, Vertical, \"Linus Spacehead's Cosmic Crusade (USA) (En,Fr,De,Es) (Unl).nes\"\n  9E382EBF, NTSC, 1, 0, 4, 4, 0, false, Horizontal, \"Dance Aerobics (USA).nes\"\n  9E4701CB, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Mickey Mouse - Fushigi no Kuni no Daibouken (Japan).nes\"\n  9E4E9CC2, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Pac-Man (USA) (Namco).nes\"\n  9E6092A4, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Tiny Toon Adventures Cartoon Workshop (USA).nes\"\n  9E66A66B, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Dynamite Bowl (Japan) (Rev A).nes\"\n  9E777EA5, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Ninja Cop Saizou (Japan).nes\"\n  9E898385, NTSC, 65, 0, 0, 8, 0, false, Horizontal, \"Kaiketsu Yancha Maru 3 - Taiketsu! Zouringen (Japan).nes\"\n  9EA1DC76, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Rainbow Islands (USA).nes\"\n  9EBDC94E, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Hototogisu (Japan).nes\"\n  9ECB9DCD, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Perfect Bowling (Japan).nes\"\n  9EDBE2E2, NTSC, 19, 0, 16, 8, 0, false, Horizontal, \"Rolling Thunder (Japan).nes\"\n  9EDD2159, NTSC, 7, 0, 1, 16, 0, false, Horizontal, \"R.C. Pro-Am II (USA).nes\"\n  9EE83916, NTSC, 72, 0, 0, 8, 0, false, Vertical, \"Moero!! Juudou Warriors (Japan).nes\"\n  9EEFB4B4, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Pachi-Slot Adventure 2 - Sorotta-kun no Pachi-Slot Tanteidan (Japan).nes\"\n  9EF351DC, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Sansuu 5 & 6 Nen - Keisan Game (Japan) (Beta).nes\"\n  9EFF96D2, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Raf World (Japan).nes\"\n  9F01687D, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Shadowgate (France).nes\"\n  9F03B11F, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Moero TwinBee - Cinnamon Hakase o Sukue! (Japan).nes\"\n  9F2712DF, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Cosmic Wars (Japan).nes\"\n  9F2EEF20, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Blues Brothers, The (USA).nes\"\n  9F432594, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Starship Hector (USA).nes\"\n  9F5138CB, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Super Rugby (Japan).nes\"\n  9F6C119C, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Remote Control (USA).nes\"\n  9F6CE171, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Ultimate Basketball (USA).nes\"\n  9F8336DB, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Miracle Ropit's - 2100 Nen no Daibouken (Japan).nes\"\n  9FAE4D46, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Ide Yousuke Meijin no Jissen Mahjong (Japan) (Rev A).nes\"\n  9FB32923, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Godzilla 2 - War of the Monsters (USA).nes\"\n  9FD35802, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Buggy Popper (Japan).nes\"\n  9FD718FD, NTSC, 4, 0, 1, 32, 0, false, Horizontal, \"Gorilla Man, The (Japan).nes\"\n  9FFE2F55, NTSC, 1, 0, 16, 4, 0, false, Horizontal, \"Sky Shark (USA) (Rev 0A).nes\"\n  A0006B26, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Golgo 13 - Daiisshou - Kamigami no Tasogare (Japan).nes\"\n  A0230D75, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"WWF Wrestlemania Challenge (USA).nes\"\n  A038AFF2, PAL, 4, 0, 16, 8, 0, false, Vertical, \"Tiny Toon Adventures (Europe).nes\"\n  A03A422B, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Major League Baseball (USA).nes\"\n  A045FE1D, PAL, 232, 0, 0, 16, 0, false, Vertical, \"Super Sports Challenge (Europe) (Unl).nes\"\n  A0568E1D, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Renegade (USA).nes\"\n  A058219D, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Taro's Quest (USA) (Proto).nes\"\n  A07C1F81, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Kinnikuman - Muscle Tag Match (Japan) (Rev 1).nes\"\n  A08B4701, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Kinnikuman - Muscle Tag Match (Japan).nes\"\n  A0A095C4, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Bump'n'Jump (USA).nes\"\n  A0A5A0B9, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"James Bond Jr (Europe).nes\"\n  A0B0B742, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Super Mario Bros. 3 (USA).nes\"\n  A0C31A57, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Indiana Jones and the Temple of Doom (USA).nes\"\n  A0DF4B8F, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Krusty's Fun House (USA).nes\"\n  A0F99BB8, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Akumajou Dracula (Japan).nes\"\n  A166548F, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Dragon Fighter (USA).nes\"\n  A189843D, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Magic Jewelry (Asia) (Unl).nes\"\n  A1A0C13F, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Double Dragon (Europe).nes\"\n  A1C0DA00, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Mario Bros. Classic (Europe).nes\"\n  A1DC16C0, NTSC, 0, 0, 64, 32, 0, false, FourScreen, \"Street Heroes (Asia) (Unl).nes\"\n  A1F90826, PAL, 1, 0, 4, 8, 0, false, Vertical, \"Air Fortress (Europe).nes\"\n  A1FF4E1D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Swamp Thing (USA).nes\"\n  A20B4983, NTSC, 0, 0, 8, 8, 0, false, Horizontal, \"Popo Team (Asia) (Unl).nes\"\n  A2194CAD, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Legendary Wings (USA).nes\"\n  A21E675C, NTSC, 34, 0, 1, 8, 0, false, Horizontal, \"Mashou (Japan).nes\"\n  A222F5A0, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Ninja-kun - Majou no Bouken (Japan).nes\"\n  A22657FA, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Nintendo World Cup (USA).nes\"\n  A23CB659, NTSC, 79, 0, 4, 2, 0, false, Vertical, \"Volley Ball (Spain) (Gluk Video) (Unl).nes\"\n  A23F0A27, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Exerion (Japan) (En) (Proto) [b].nes\"\n  A2469526, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Xevious (Japan) (En) (Rev 1).nes\"\n  A25A750F, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Ultima - Quest of the Avatar (USA).nes\"\n  A2623BC1, NTSC, 68, 0, 0, 8, 0, true, Horizontal, \"Nantettatte!! Baseball (Japan).nes\"\n  A262A81F, NTSC, 16, 0, 32, 16, 0, false, Horizontal, \"Rokudenashi Blues (Japan).nes\"\n  A2AF25D0, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Defender II (USA).nes\"\n  A2D074F5, PAL, 0, 0, 2, 1, 0, false, Horizontal, \"Lucky Bingo 777 (Asia) (PAL) (Unl).nes\"\n  A2F713C0, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"SD Gundam - Gachapon Senshi 2 - Capsule Senki (Japan).nes\"\n  A31142FF, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Adventures of Rad Gravity, The (Europe).nes\"\n  A342A5FD, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Rambo (USA).nes\"\n  A38857EB, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Lipple Island (Japan).nes\"\n  A3BF2ADA, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Hero Quest (USA) (Proto).nes\"\n  A3C0D49F, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Classic Concentration (USA).nes\"\n  A4062017, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Ultima - Exodus (USA).nes\"\n  A46D7F02, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Astyanax (USA) (Beta).nes\"\n  A485ABED, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Ferrari (Japan).nes\"\n  A48D26C1, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Caesars Palace (USA) (Beta).nes\"\n  A49253C6, NTSC, 4, 0, 8, 4, 0, false, Horizontal, \"Family Tennis (Japan).nes\"\n  A49B48B8, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Dragon Quest III - Soshite Densetsu e... (Japan) (Rev 0A).nes\"\n  A4BDCC1D, PAL, 1, 0, 1, 8, 0, true, Horizontal, \"Elite (Europe) (En,Fr,De).nes\"\n  A4DCDF28, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Kujaku Ou II (Japan).nes\"\n  A4DCF72E, PAL, 4, 0, 32, 16, 0, false, Horizontal, \"Mega Man 5 (Europe).nes\"\n  A4E935DF, NTSC, 69, 0, 32, 16, 0, false, Horizontal, \"Honoo no Toukyuuji - Dodge Danpei 2 (Japan).nes\"\n  A547A6EC, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Raid on Bungeling Bay (Japan) (En) (Rev A).nes\"\n  A55701DD, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"King of Kings, The (USA) (v1.1) (Unl).nes\"\n  A558FB52, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Destiny of an Emperor (USA).nes\"\n  A55FA397, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Back to the Future (USA).nes\"\n  A56208A0, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Jongbou (Japan).nes\"\n  A58A8DA1, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Ushio to Tora - Shinen no Taiyou (Japan).nes\"\n  A5E6BAF9, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Dragon Slayer 4 - Drasle Family (Japan).nes\"\n  A5E89675, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Captain Comic - The Adventure (USA) (Unl).nes\"\n  A5E8D2CD, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"BreakThru (USA).nes\"\n  A60CA3D6, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Nightshade (USA).nes\"\n  A60FBA51, NTSC, 4, 0, 1, 32, 0, true, Horizontal, \"Double Moon Densetsu (Japan).nes\"\n  A6153536, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Attack of the Killer Tomatoes (Europe).nes\"\n  A6638CBA, PAL, 1, 0, 1, 16, 0, false, Horizontal, \"Mega Man 2 (Europe).nes\"\n  A6648353, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Conflict (Japan).nes\"\n  A66596D9, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Sou Setsu Ryuu II - The Revenge (Japan).nes\"\n  A69A1F2A, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Donkey Kong Jr. (USA) (GameCube Edition).nes\"\n  A69F29FA, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Lemmings (USA).nes\"\n  A6A725B8, NTSC, 2, 0, 1, 16, 0, false, Vertical, \"Best of the Best - Championship Karate (USA).nes\"\n  A713DD30, PAL, 69, 0, 16, 16, 0, false, Vertical, \"Mr. Gimmick (Europe).nes\"\n  A725B2D3, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Defenders of Dynatron City (USA).nes\"\n  A72FDE03, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Densetsu no Kishi - Elrond (Japan).nes\"\n  A781FFAA, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Free Fall (USA) (Proto).nes\"\n  A7B0536C, NTSC, 33, 0, 0, 8, 0, false, Horizontal, \"Don Doko Don 2 (Japan).nes\"\n  A7D3635E, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Nekketsu Kouha Kunio-kun (Japan).nes\"\n  A7DE65E4, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Predator (USA).nes\"\n  A7E784ED, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Igo Shinan '94 (Japan).nes\"\n  A7EF8F80, NTSC, 0, 0, 32, 8, 0, false, Horizontal, \"Gaiapolis (Asia) (Unl).nes\"\n  A80A0F01, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Incredible Crash Dummies, The (USA).nes\"\n  A80FA181, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Faxanadu (Japan).nes\"\n  A851CAE9, NTSC, 16, 0, 32, 8, 0, false, Horizontal, \"Nishimura Kyoutarou Mystery - Blue Train Satsujin Jiken (Japan).nes\"\n  A86A5318, NTSC, 1, 0, 1, 32, 0, true, Horizontal, \"Dragon Warrior III (USA).nes\"\n  A8784932, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Gun.Smoke (USA).nes\"\n  A8923256, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Snoopy's Silly Sports Spectacular! (USA).nes\"\n  A8A9B982, NTSC, 185, 0, 0, 1, 0, false, Vertical, \"Bird Week (Japan).nes\"\n  A8B0DA56, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Race America (USA).nes\"\n  A8D93537, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"RoboCop 3 (Europe).nes\"\n  A8E6A7C2, PAL, 0, 0, 8, 4, 0, false, Horizontal, \"Mahjong World, The - Ma Que Shi Jie (Asia) (PAL) (Unl).nes\"\n  A8F4D99E, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Boulder Dash (USA).nes\"\n  A8F5C2AB, NTSC, 4, 0, 4, 4, 0, false, Vertical, \"Vindicators (USA) (Unl).nes\"\n  A9068D17, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Virus (USA) (Beta) (1989).nes\"\n  A91460B8, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Solstice (Japan).nes\"\n  A9217EA2, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Teenage Mutant Ninja Turtles II - The Arcade Game (USA).nes\"\n  A923E441, NTSC, 133, 0, 0, 4, 0, false, Horizontal, \"Jovial Race (Unknown) (Unl).nes\"\n  A93527E2, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Castlevania (Europe).nes\"\n  A9415562, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Kid Niki - Radical Ninja (USA) (Rev A).nes\"\n  A94591B0, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Contra Force (USA).nes\"\n  A9541452, NTSC, 16, 0, 32, 16, 0, false, Horizontal, \"Dragon Ball Z II - Gekishin Freeza!! (Japan) (Rev 1).nes\"\n  A95A915A, NTSC, 243, 0, 0, 2, 0, false, Horizontal, \"Tasac (Asia) (Unl).nes\"\n  A9660690, PAL, 1, 0, 16, 8, 0, false, Vertical, \"Snow Brothers (Europe).nes\"\n  A97567A4, PAL, 1, 0, 1, 8, 0, false, Horizontal, \"Battle of Olympus, The (Europe).nes\"\n  A98046B8, NTSC, 10, 0, 16, 16, 0, true, Horizontal, \"Fire Emblem Gaiden (Japan).nes\"\n  A9842027, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Mahjong (Japan) (Rev A).nes\"\n  A99016C6, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Rollerball (Australia).nes\"\n  A9BBF44F, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Pac-Man (USA) (Tengen).nes\"\n  AA20F73D, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Shatterhand (USA).nes\"\n  AA4318AE, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"I Love Softball (Japan).nes\"\n  AA4997C1, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Rollergames (USA).nes\"\n  AA6BB985, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Kid Kool and the Quest for the Seven Wonder Herbs (USA).nes\"\n  AA74A4D8, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Kung-Fu Heroes (USA).nes\"\n  AA9F9765, NTSC, 23, 0, 0, 8, 0, false, Vertical, \"Mad City (Japan) (Beta).nes\"\n  AAA985D7, PAL, 1, 0, 16, 8, 0, false, Vertical, \"Swamp Thing (Europe).nes\"\n  AAC2E75E, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Mighty Bomb Jack (USA).nes\"\n  AAED295C, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"R.C. Pro-Am (USA) (Rev A).nes\"\n  AAEF2264, NTSC, 168, 0, 0, 4, 0, true, Horizontal, \"Racermate Challenge II (USA) (v5.01.033) (Unl).nes\"\n  AAF49344, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Snow Bros. (Japan).nes\"\n  AB2006B4, PAL, 3, 0, 2, 2, 0, false, Vertical, \"Donkey Kong Classics (USA, Europe).nes\"\n  AB2AC325, PAL, 1, 0, 16, 8, 0, false, Vertical, \"David Crane's A Boy and His Blob - Trouble on Blobolonia (Europe).nes\"\n  AB41445E, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Super Spy Hunter (USA).nes\"\n  AB47A50E, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Ikari (Japan).nes\"\n  AB671224, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Journey to Silius (Europe).nes\"\n  ABAA6F78, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Matsumoto Tooru no Kabushiki Hisshou Gaku - Vol. 1 (Japan).nes\"\n  ABBF7217, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Sangokushi (Japan).nes\"\n  AC136F2D, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"King's Knight (Japan).nes\"\n  AC3E5677, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Hudson Hawk (Europe).nes\"\n  AC4BF9DC, NTSC, 75, 0, 0, 8, 0, false, Vertical, \"Exciting Boxing (Japan).nes\"\n  AC609320, PAL, 4, 0, 32, 8, 0, false, Horizontal, \"Flintstones, The - The Rescue of Dino & Hoppy (Europe).nes\"\n  AC652B47, NTSC, 73, 0, 0, 8, 0, false, Vertical, \"Salamander (Japan).nes\"\n  AC8DCDEA, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Cybernoid - The Fighting Machine (USA).nes\"\n  AC92E9E0, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Brush Roller (Asia) (Unl).nes\"\n  AC97AA09, NTSC, 172, 0, 0, 2, 0, false, Vertical, \"1991 Du Ma Racing (Asia) (Unl).nes\"\n  AC9895CC, NTSC, 23, 0, 0, 8, 0, false, Horizontal, \"Dragon Scroll - Yomigaerishi Maryuu (Japan).nes\"\n  ACA145D8, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Karate Champ (USA).nes\"\n  ACA15643, NTSC, 5, 0, 16, 32, 0, true, Horizontal, \"Uncharted Waters (USA).nes\"\n  ACE56F39, NTSC, 19, 0, 16, 8, 0, false, Horizontal, \"Mindseeker (Japan).nes\"\n  AD0394F0, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Roundball - 2-on-2 Challenge (Europe).nes\"\n  AD12A34F, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Tecmo Baseball (USA).nes\"\n  AD9C63E2, NTSC, 70, 0, 0, 4, 0, false, Horizontal, \"Space Shadow (Japan).nes\"\n  ADA1B12F, NTSC, 3, 0, 8, 2, 0, false, Horizontal, \"Hot Slots (Asia) (Unl).nes\"\n  ADA40FB2, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Arcadia VI (USA) (Proto).nes\"\n  ADB5D0B3, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Championship Lode Runner (Japan).nes\"\n  ADB810F8, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Woody Poko (Japan).nes\"\n  ADF606F6, NTSC, 33, 0, 0, 16, 0, false, Horizontal, \"Bakushou!! Jinsei Gekijou (Japan).nes\"\n  ADFAD6B6, NTSC, 188, 0, 0, 8, 0, false, Horizontal, \"Karaoke Studio (Japan).nes\"\n  ADFFD64F, NTSC, 210, 2, 16, 8, 0, false, Vertical, \"Famista '93 (Japan).nes\"\n  AE128FAC, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Law of the West (Japan).nes\"\n  AE280E20, NTSC, 4, 0, 1, 16, 0, false, Horizontal, \"Shougi Meikan '93 (Japan).nes\"\n  AE321339, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Pro Yakyuu - Family Stadium '88 (Japan).nes\"\n  AE52DECE, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Millipede (USA).nes\"\n  AE5C3D94, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Baseball Star - Mezase Sankanou!! (Japan).nes\"\n  AE64CA77, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Ice Hockey (USA).nes\"\n  AE7DF77F, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"M.C. Kids (USA) (Beta).nes\"\n  AE8666B4, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"City Connection (USA).nes\"\n  AE97627C, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Bugs Bunny Fun House (USA) (Beta).nes\"\n  AE9F33D0, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"North and South (USA).nes\"\n  AEB2D754, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Double Dragon II - The Revenge (Europe).nes\"\n  AEB7FCE9, NTSC, 33, 0, 0, 8, 0, false, Horizontal, \"Power Blazer (Japan).nes\"\n  AEBD6549, NTSC, 33, 0, 0, 16, 0, false, Horizontal, \"Bakushou!! Jinsei Gekijou 3 (Japan).nes\"\n  AF05F37E, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"George Foreman's KO Boxing (USA).nes\"\n  AF4010EA, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"World Class Track Meet (USA) (Rev A).nes\"\n  AF5676DE, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Lode Runner (USA).nes\"\n  AF65AA84, PAL, 4, 0, 16, 8, 0, false, Vertical, \"Low G Man - The Low Gravity Man (Europe).nes\"\n  AF6B5B85, NTSC, 1, 0, 1, 16, 0, true, Vertical, \"Sweet Home (Japan) (Beta).nes\"\n  AFB46DD6, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Thundercade (USA).nes\"\n  AFC32114, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Safety Rally (Japan).nes\"\n  AFDCBD24, PAL, 0, 0, 1, 1, 0, false, Horizontal, \"Baseball (USA, Europe).nes\"\n  B0480AE9, NTSC, 5, 0, 16, 8, 0, false, Horizontal, \"Laser Invasion (USA).nes\"\n  B049A8C4, NTSC, 16, 0, 32, 16, 0, true, Horizontal, \"SD Gundam Gaiden - Knight Gundam Monogatari 2 - Hikari no Knight (Japan).nes\"\n  B04BA659, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Black Bass, The (Japan).nes\"\n  B06C0674, NTSC, 75, 0, 0, 8, 0, false, Vertical, \"King Kong 2 - Ikari no Megaton Punch (Japan).nes\"\n  B0874760, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Bowling (Unknown) (Proto).nes\"\n  B0BC46D1, NTSC, 66, 0, 4, 8, 0, false, Vertical, \"Dragon Ball - Le Secret du Dragon (France) (Rev A).nes\"\n  B0CD000F, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Wayne's World (USA).nes\"\n  B0EBF3DB, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"M.C. Kids (USA).nes\"\n  B1250D0C, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Super Contra (Japan).nes\"\n  B134D713, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Abadox - The Deadly Inner War (USA).nes\"\n  B13F00D4, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Probotector (Europe).nes\"\n  B14EA4D2, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Iron Tank - The Invasion of Normandy (USA).nes\"\n  B15653BD, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Heracles no Eikou - Toujin Makyou Den (Japan).nes\"\n  B1612FE6, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Xexyz (USA).nes\"\n  B1723338, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Star Voyager (USA).nes\"\n  B174B680, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Dig Dug (Japan).nes\"\n  B17574F3, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Advanced Dungeons & Dragons - Heroes of the Lance (USA).nes\"\n  B19A55DD, NTSC, 64, 0, 0, 4, 0, false, Horizontal, \"Road Runner (USA) (Unl).nes\"\n  B1A94B82, NTSC, 70, 0, 0, 8, 0, false, Horizontal, \"Pocket Zaurus - Juu Ouken no Nazo (Japan).nes\"\n  B1B16B8A, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Wit's (Japan).nes\"\n  B1C937C8, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Parasol Stars - Rainbow Islands II (Europe).nes\"\n  B20C1030, NTSC, 93, 0, 0, 8, 0, false, Vertical, \"Shanghai (Japan).nes\"\n  B2530AFC, NTSC, 0, 0, 1, 2, 0, true, Vertical, \"Family BASIC (Japan) (v3.0).nes\"\n  B2781C19, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Tom & Jerry - The Ultimate Game of Cat and Mouse! (Europe).nes\"\n  B27B8CF4, NTSC, 23, 0, 0, 8, 0, false, Horizontal, \"Contra (Japan).nes\"\n  B297B5E7, NTSC, 92, 0, 0, 16, 0, false, Vertical, \"Moero!! Pro Yakyuu '88 - Ketteiban (Japan).nes\"\n  B2EF7F4B, NTSC, 4, 0, 32, 32, 0, true, Vertical, \"Kirby's Adventure (France).nes\"\n  B30599A1, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Star Wars (USA) (Beta).nes\"\n  B3769A51, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Platoon (USA).nes\"\n  B3783F2A, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Rygar (USA).nes\"\n  B3974D6C, NTSC, 1, 0, 8, 16, 0, false, Horizontal, \"Miracle Piano Teaching System, The (Germany).nes\"\n  B39A3F5B, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"JJ (Japan).nes\"\n  B3C30BEA, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Xevious (Japan).nes\"\n  B3D74C0D, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Donkey Kong 3 (World).nes\"\n  B400172A, PAL, 2, 0, 1, 8, 0, false, Vertical, \"California Games (Europe).nes\"\n  B4113F3C, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Hatris (Japan).nes\"\n  B422A67A, NTSC, 64, 0, 0, 8, 0, false, Horizontal, \"Skull & Crossbones (USA) (Unl).nes\"\n  B4241FCC, NTSC, 1, 0, 8, 16, 0, false, Horizontal, \"Bases Loaded (USA).nes\"\n  B459EDC4, NTSC, 66, 0, 4, 2, 0, false, Vertical, \"Uforce Power Games (USA) (Proto 1).nes\"\n  B462718E, PAL, 232, 0, 0, 16, 0, false, Vertical, \"Super Sports Challenge (Europe) (Plug-Thru Cart) (Unl).nes\"\n  B462BF6F, PAL, 3, 0, 2, 2, 0, false, Horizontal, \"Mighty Bomb Jack (Europe).nes\"\n  B4735FAC, NTSC, 5, 0, 64, 32, 0, true, Horizontal, \"Metal Slader Glory (Japan).nes\"\n  B47569E2, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Happy Birthday Bugs (Japan).nes\"\n  B4801882, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Secret Ties (USA) (Proto).nes\"\n  B4BADF56, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Tanque (Spain) (Gluk Video) (Unl).nes\"\n  B4C81ADB, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Indiana Jones and the Temple of Doom (USA) (Rev A).nes\"\n  B4CDF95F, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"RoboCop 2 (USA).nes\"\n  B4E4879E, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Ghosts'n Goblins (USA).nes\"\n  B4FF91E7, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Garfield - A Week of Garfield (Japan).nes\"\n  B5576820, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Momotarou Densetsu (Japan).nes\"\n  B5D10D5C, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Jeopardy! (USA).nes\"\n  B5D28EA2, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Mystery Quest (USA).nes\"\n  B5E24324, NTSC, 4, 0, 1, 8, 0, false, Horizontal, \"Ninja Crusaders - Ryuuga (Japan).nes\"\n  B5E392E2, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Little Samson (USA).nes\"\n  B5E83C9A, NTSC, 178, 0, 0, 32, 0, true, Horizontal, \"Xing Ji Zheng Ba (China) (Unl).nes\"\n  B5F7E661, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"World Grand-Prix - Pole to Finish (Japan).nes\"\n  B5FF71AB, NTSC, 19, 0, 16, 8, 0, false, Horizontal, \"Battle Fleet (Japan).nes\"\n  B629D555, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Totally Rad (USA).nes\"\n  B64078F3, NTSC, 4, 0, 16, 8, 0, true, Vertical, \"Shadowgate (Germany).nes\"\n  B6661BDA, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Wall Street Kid (USA).nes\"\n  B668C7FC, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Castlevania (USA) (Rev A).nes\"\n  B67D16F6, PAL, 1, 0, 1, 16, 0, false, Vertical, \"Robin Hood - Prince of Thieves (Europe).nes\"\n  B683A856, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Blaster Master (USA) (Beta).nes\"\n  B68F9814, PAL, 4, 0, 16, 8, 0, false, Vertical, \"Astyanax (Europe).nes\"\n  B69F7C0F, NTSC, 3, 0, 1, 2, 0, false, Horizontal, \"Fire Dragon (Asia) (Unl).nes\"\n  B6A2B981, NTSC, 79, 0, 4, 2, 0, false, Vertical, \"Solitaire (USA) (Unl).nes\"\n  B6A727FA, NTSC, 113, 0, 8, 2, 0, false, Horizontal, \"Papillon (Asia) (Unl).nes\"\n  B6B5C372, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Trog! (Europe).nes\"\n  B6BF5137, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Fester's Quest (USA).nes\"\n  B6D2D300, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Ms. Pac-Man (USA).nes\"\n  B70129F4, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Tokkyuu Shirei Solbrain (Japan).nes\"\n  B7773A07, NTSC, 4, 0, 1, 32, 0, false, Horizontal, \"Aa Yakyuu Jinsei Icchokusen (Japan).nes\"\n  B780521C, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Ninja Gaiden II - The Dark Sword of Chaos (USA).nes\"\n  B786AB95, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Dynamite Bowl (Japan).nes\"\n  B786C2AC, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"Spiritual Warfare (USA) (v6.0) (Unl).nes\"\n  B79C320D, PAL, 2, 0, 1, 8, 0, false, Horizontal, \"Gun.Smoke (Europe).nes\"\n  B79F2651, NTSC, 0, 0, 4, 2, 0, false, Vertical, \"Chiller (USA) (Unl).nes\"\n  B7D69A6D, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Magician (USA) (Beta 1).nes\"\n  B7F28915, NTSC, 16, 0, 16, 8, 0, true, Horizontal, \"Magical Taruruuto-kun 2 - Mahou Daibouken (Japan).nes\"\n  B7F39933, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Prince of Persia (Europe).nes\"\n  B80192B7, PAL, 2, 0, 1, 8, 0, false, Horizontal, \"Jimmy Connors Tennis (Europe).nes\"\n  B811C054, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Fleet Commander (Japan).nes\"\n  B834EB30, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"City Adventure Touch - Mystery of Triangle (Japan).nes\"\n  B843EB84, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Silent Service (USA) (Rev A).nes\"\n  B84A73CC, NTSC, 4, 0, 1, 32, 0, true, Horizontal, \"Tenchi o Kurau II - Shokatsu Koumei Den (Japan) (Rev A).nes\"\n  B8747ABF, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Best Play Pro Yakyuu Special (Japan).nes\"\n  B87AB35A, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Circus Charlie (Japan).nes\"\n  B89888C9, NTSC, 232, 0, 0, 16, 0, false, Vertical, \"Quattro Adventure (USA) (Unl).nes\"\n  B8B9ACA3, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Wild Gunman (Japan, USA).nes\"\n  B8DAD5D2, NTSC, 0, 0, 8, 4, 0, false, Horizontal, \"Mahjan Samit Kabukicho Hen (Asia) (Unl).nes\"\n  B918580C, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Treasure Master (USA).nes\"\n  B9582F60, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Kidou Senshi Z Gundam - Hot Scramble (Japan) (Final Version).nes\"\n  B95E9E7F, NTSC, 9, 0, 16, 8, 0, false, Horizontal, \"Punch-Out!! (USA).nes\"\n  B96F8321, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Hottarman no Chitei Tanken (Japan) (Beta).nes\"\n  B976219A, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Jumpin' Kid - Jack to Mame no Ki Monogatari (Japan).nes\"\n  B9762DA8, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Fisher-Price - Perfect Fit (USA).nes\"\n  B979CAD5, NTSC, 32, 0, 0, 8, 0, false, Horizontal, \"Kaiketsu Yancha Maru 2 - Karakuri Land (Japan).nes\"\n  B97BFDD7, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Exploding Fist (USA) (Proto 1).nes\"\n  B9AB06AA, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Money Game II, The - Kabutochou no Kiseki (Japan).nes\"\n  B9B4D9E0, NTSC, 118, 0, 0, 8, 0, false, Horizontal, \"NES Play Action Football (USA).nes\"\n  B9CF171F, NTSC, 1, 0, 16, 16, 0, false, Horizontal, \"Bases Loaded II - Second Season (USA).nes\"\n  B9DC755E, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Ikinari Musician (Japan) (Beta).nes\"\n  BA322865, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Zelda II - The Adventure of Link (USA).nes\"\n  BA327FD9, PAL, 69, 0, 32, 8, 0, false, Horizontal, \"Batman - Return of the Joker (Europe).nes\"\n  BA43568A, NTSC, 80, 0, 0, 8, 0, false, Horizontal, \"Kyonshiizu 2 (Japan) (Sample).nes\"\n  BA51AC6F, NTSC, 78, 0, 0, 8, 0, false, FourScreen, \"Holy Diver (Japan).nes\"\n  BA58ED29, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"'89 Dennou Kyuusei Uranai (Japan).nes\"\n  BA766EC6, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Heracles no Eikou II - Titan no Metsubou (Japan).nes\"\n  BAACF521, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Bloque Magico, El (Spain) (Gluk Video) (Unl).nes\"\n  BACA10A9, NTSC, 33, 0, 0, 8, 0, false, Horizontal, \"Golfkko Open (Japan).nes\"\n  BAD36C17, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Star Wars (Japan) (Victor).nes\"\n  BAEBA201, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Mario Open Golf (Japan).nes\"\n  BB0F2D56, NTSC, 132, 0, 0, 4, 0, false, Vertical, \"Creatom (Spain) (Gluk Video) (Unl).nes\"\n  BB435255, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Shinsenden (Japan).nes\"\n  BB6D7949, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Teenage Mutant Ninja Turtles III - The Manhattan Project (USA).nes\"\n  BB7F829A, NTSC, 5, 0, 16, 8, 0, false, Horizontal, \"Uchuu Keibitai SDF (Japan).nes\"\n  BBA58BE5, NTSC, 70, 0, 0, 8, 0, false, Horizontal, \"Family Trainer 6 - Manhattan Police (Japan).nes\"\n  BBB710D9, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"F-15 Strike Eagle (France) (En,Fr,Nl).nes\"\n  BBE40DC4, NTSC, 11, 0, 2, 4, 0, false, Vertical, \"Baby Boomer (USA) (Unl).nes\"\n  BBED6E6E, NTSC, 3, 0, 2, 2, 0, false, Horizontal, \"Legend of Kage, The (USA).nes\"\n  BBF464EB, PAL, 0, 0, 2, 4, 0, false, Vertical, \"Pyramid II (Asia) (PAL) (Unl).nes\"\n  BBFE23F4, PAL, 4, 0, 16, 8, 0, false, Vertical, \"Panic Restaurant (Europe).nes\"\n  BC06543C, NTSC, 0, 0, 1, 1, 0, true, Vertical, \"Booky Man (Spain) (Gluk Video) (Unl).nes\"\n  BC11E61A, NTSC, 19, 0, 16, 8, 0, false, Horizontal, \"Kaijuu Monogatari (Japan).nes\"\n  BC25A18B, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Shadow Warriors II - Ninja Gaiden II (Europe).nes\"\n  BC7364BB, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Wait and See (Russia) (Unl).nes\"\n  BC7485B5, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Elite (Unknown) (NTSC Demo).nes\"\n  BC7B1D0F, NTSC, 33, 0, 0, 8, 0, false, Horizontal, \"Bakushou!! Jinsei Gekijou 2 (Japan).nes\"\n  BC7FEDB9, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Little Ninja Brothers (USA).nes\"\n  BC80FB52, NTSC, 5, 0, 32, 16, 0, true, Horizontal, \"Royal Blood (Japan).nes\"\n  BC9BFFCB, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Adventures of Bayou Billy, The (Europe).nes\"\n  BCACBBF4, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Metal Storm (USA).nes\"\n  BCCFEF1C, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Kabuki - Quantum Fighter (Europe).nes\"\n  BCE77871, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Pipe Dream (USA).nes\"\n  BCF68611, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Great Deal (Japan).nes\"\n  BD018F0F, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Dancing Blocks (Asia) (PAL) (Unl).nes\"\n  BD139BE7, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Escape from Atlantis, The (USA) (Proto 1) (Unl).nes\"\n  BD154C3E, NTSC, 71, 0, 1, 16, 0, false, Vertical, \"Big Nose the Caveman (USA) (Unl).nes\"\n  BD29178A, NTSC, 79, 0, 4, 2, 0, false, Horizontal, \"Dudes with Attitude (USA) (Unl).nes\"\n  BD339E75, PAL, 1, 0, 16, 8, 0, false, Vertical, \"Best of the Best - Championship Karate (Europe).nes\"\n  BD50F230, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Doki!Doki! Yuuenchi - Crazy Land Daisakusen (Japan).nes\"\n  BD523011, NTSC, 19, 0, 32, 16, 0, false, Horizontal, \"Namco Prism Zone - Dream Master (Japan).nes\"\n  BD9D0E85, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Bomber King (Japan).nes\"\n  BDA183BB, NTSC, 4, 0, 32, 16, 0, false, Vertical, \"Teenage Mutant Ninja Turtles II - The Arcade Game (Unknown) (Beta).nes\"\n  BDA7925E, NTSC, 87, 0, 0, 2, 0, false, Horizontal, \"Kage no Densetsu (Japan).nes\"\n  BDA8F8E4, NTSC, 70, 0, 0, 8, 0, false, Horizontal, \"Gegege no Kitarou 2 - Youkai Gundan no Chousen (Japan).nes\"\n  BDC124E5, NTSC, 4, 0, 1, 16, 0, false, Horizontal, \"Shaffle Fight (Japan).nes\"\n  BDE3AE9B, NTSC, 66, 0, 4, 8, 0, false, Vertical, \"Doraemon (Japan).nes\"\n  BDE7A7B5, NTSC, 113, 0, 4, 2, 0, false, Horizontal, \"Rad Racket - Deluxe Tennis II (USA) (Unl).nes\"\n  BDE93999, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Dynowarz - Destruction of Spondylus (USA).nes\"\n  BDF046EF, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Cabal (USA).nes\"\n  BE06853F, NTSC, 16, 0, 1, 16, 0, false, Horizontal, \"Datach - J League Super Top Players (Japan).nes\"\n  BE0E93C3, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Rainbow Islands - Bubble Bobble 2 (Europe).nes\"\n  BE17E27B, NTSC, 243, 0, 0, 4, 0, false, Horizontal, \"Poke III (Asia) (Unl).nes\"\n  BE250388, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Capcom's Gold Medal Challenge '92 (USA).nes\"\n  BE387AF0, NTSC, 3, 0, 2, 1, 0, false, Horizontal, \"Joust (USA).nes\"\n  BE3BF3B3, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Shingen the Ruler (USA).nes\"\n  BE95B219, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Tatakai no Banka (Japan).nes\"\n  BEB15855, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Friday the 13th (USA).nes\"\n  BEB30478, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Exerion (Japan) (En).nes\"\n  BEB8AB01, PAL, 66, 0, 4, 8, 0, false, Vertical, \"Gumshoe (USA, Europe).nes\"\n  BED47813, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Rollergames (Europe).nes\"\n  BEE1C0D9, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Silver Surfer (USA).nes\"\n  BEE30C5F, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Cross Fire (USA) (Proto).nes\"\n  BEE54426, NTSC, 79, 0, 8, 4, 0, false, Vertical, \"Deathbots (USA) (Unl).nes\"\n  BEFE5480, NTSC, 177, 0, 0, 64, 0, true, Horizontal, \"Xing Zhan Qing Yuan (China) (Unl).nes\"\n  BF0C485D, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Jackpot (Australia) (Unl).nes\"\n  BF250AF2, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Tag Team Wrestling (USA).nes\"\n  BF3635CF, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Makai Mura (Japan).nes\"\n  BF4F4BA6, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Castle Quest (Japan).nes\"\n  BF700470, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Addams Family, The (Europe) (En,Fr,De).nes\"\n  BF7F54B4, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Baseball Fighter (Japan).nes\"\n  BF888B75, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Hook (Europe).nes\"\n  BF93112A, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Metal Flame Psybuster (Japan).nes\"\n  BFBFD25D, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Adventure Island 3 (USA).nes\"\n  C0103592, PAL, 1, 0, 16, 16, 0, false, Horizontal, \"Goal! (Europe).nes\"\n  C05A365B, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Chou Fuyuu Yousai Exed Exes (Japan).nes\"\n  C05A63B2, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Super Spike V'Ball (Europe).nes\"\n  C060ED0A, NTSC, 1, 0, 16, 16, 0, false, Horizontal, \"Grand Master (Japan).nes\"\n  C06FACFC, PAL, 243, 0, 0, 2, 0, false, Horizontal, \"Strategist (Asia) (PAL) (Unl).nes\"\n  C09227A0, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"RoboCop (Japan).nes\"\n  C0B23520, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Q-bert (USA).nes\"\n  C0EDEDD0, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Blades of Steel (Europe).nes\"\n  C0F251EA, PAL, 4, 0, 32, 16, 0, false, Horizontal, \"Ultimate Air Combat (Europe) (En,Fr,De).nes\"\n  C115A022, PAL, 4, 0, 16, 8, 0, false, Vertical, \"Rampart (Europe).nes\"\n  C1719664, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Tatakae!! Rahmen Man - Sakuretsu Choujin 102 Gei (Japan).nes\"\n  C1B43207, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Danny Sullivan's Indy Heat (USA).nes\"\n  C1B79B14, NTSC, 146, 0, 4, 2, 0, false, Vertical, \"Shuang Ying (Asia) (Unl).nes\"\n  C1BA8BB9, NTSC, 4, 0, 1, 16, 0, true, Horizontal, \"Project Q (Japan).nes\"\n  C1C3636B, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Star Wars (USA).nes\"\n  C1D7AB1D, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Choujikuu Yousai - Macross (Japan).nes\"\n  C1E91D3F, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Spy vs Spy (Europe).nes\"\n  C1FBF659, NTSC, 23, 0, 0, 8, 0, false, Horizontal, \"Akumajou Special - Boku Dracula-kun (Japan).nes\"\n  C226157D, NTSC, 79, 0, 4, 2, 0, false, Vertical, \"Venice Beach Volleyball (USA) (Beta) (Unl).nes\"\n  C22BC87B, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Seiryaku Simulation - Inbou no Wakusei - Shancara (Japan).nes\"\n  C22C23AB, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Pachinko Daisakusen (Japan).nes\"\n  C22F3E9F, NTSC, 1, 0, 16, 8, 0, true, Vertical, \"Advanced Dungeons & Dragons - Heroes of the Lance (USA) (Beta).nes\"\n  C22FF1D8, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"RoboCop 2 (Japan).nes\"\n  C247A23D, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Batman Returns (USA).nes\"\n  C247CC80, NTSC, 210, 1, 16, 32, 0, true, Vertical, \"Family Circuit '91 (Japan).nes\"\n  C2730C30, NTSC, 34, 0, 1, 8, 0, false, Horizontal, \"Deadly Towers (USA).nes\"\n  C2840372, NTSC, 16, 0, 32, 16, 0, true, Vertical, \"SD Gundam Gaiden - Knight Gundam Monogatari 3 - Densetsu no Kishi Dan (Japan).nes\"\n  C2A4612E, NTSC, 79, 0, 4, 2, 0, false, Vertical, \"Blackjack (USA) (Unl).nes\"\n  C2EF3422, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Best Play Pro Yakyuu II (Japan).nes\"\n  C30848D3, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Slalom (Europe).nes\"\n  C30C9EC9, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Shoukoushi Ceddie (Japan).nes\"\n  C313EF54, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Jack Nicklaus' Greatest 18 Holes of Major Championship Golf (USA).nes\"\n  C32E9672, PAL, 4, 0, 16, 8, 0, false, Vertical, \"Tiny Toon Adventures 2 - Trouble in Wackyland (Europe).nes\"\n  C3463A3D, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Where's Waldo (USA).nes\"\n  C372399B, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Genpei Touma Den - Computer Boardgame (Japan).nes\"\n  C37F225C, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Spelunker II - Yuusha e no Chousen (Japan).nes\"\n  C3A0A3E0, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Lunar Pool (Europe).nes\"\n  C3C0811D, NTSC, 96, 0, 0, 8, 0, false, Vertical, \"Oeka Kids - Anpanman no Hiragana Daisuki (Japan).nes\"\n  C3C7A568, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Tiger-Heli (USA).nes\"\n  C3CCC493, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Robin Hood - Prince of Thieves (USA) (Rev A).nes\"\n  C3DE7C69, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Best Play Pro Yakyuu Special (Japan) (Rev A).nes\"\n  C42E648A, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Bigfoot (USA).nes\"\n  C46969DF, PAL, 4, 0, 16, 16, 0, false, Horizontal, \"Super Mario Bros. 3 (Europe) (Wii VC).nes\"\n  C471E42D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Castlevania II - Simon's Quest (USA).nes\"\n  C47EFC0E, NTSC, 79, 0, 4, 2, 0, false, Vertical, \"Trolls on Treasure Island (USA) (Unl).nes\"\n  C48363B4, NTSC, 4, 0, 32, 16, 0, true, Horizontal, \"Dai-2-ji Super Robot Taisen (Japan).nes\"\n  C48DDB52, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Dragon's Lair (Japan).nes\"\n  C49FCAB4, NTSC, 1, 0, 4, 2, 0, false, Vertical, \"Dr. Mario (USA) (Beta).nes\"\n  C4A02712, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Spy vs Spy (USA).nes\"\n  C4B6ED3C, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Bill & Ted's Excellent Video Game Adventure (USA).nes\"\n  C4BC85A2, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Life Force (USA).nes\"\n  C4C3949A, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Mario Bros. (World).nes\"\n  C4E1886F, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Bard's Tale II, The - The Destiny Knight (Japan).nes\"\n  C4E81924, PAL, 4, 0, 32, 8, 0, false, Horizontal, \"Beauty and the Beast (Europe).nes\"\n  C527C297, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Alien 3 (USA).nes\"\n  C528ED56, PAL, 4, 0, 16, 8, 0, false, Vertical, \"Super Spy Hunter (Europe).nes\"\n  C53CF1D0, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Track & Field II (Europe).nes\"\n  C5657C12, PAL, 4, 0, 32, 16, 0, false, Horizontal, \"Teenage Mutant Hero Turtles II - The Arcade Game (Europe).nes\"\n  C58EEA57, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Mahjong Trap - Si Cuan Ma Que (Asia) (Unl).nes\"\n  C5B0B1AB, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Stinger (USA).nes\"\n  C5CFE54E, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Toki no Tabibito (Japan).nes\"\n  C6000085, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Conan (USA).nes\"\n  C6182024, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Romance of the Three Kingdoms (USA).nes\"\n  C6224026, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Bikkuriman World - Gekitou Sei Senshi (Japan).nes\"\n  C6557E02, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Family Mahjong (Japan).nes\"\n  C67865A2, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Star Force (Japan).nes\"\n  C68363F6, NTSC, 180, 0, 0, 8, 0, false, Vertical, \"Crazy Climber (Japan).nes\"\n  C6ADD8C5, NTSC, 4, 0, 4, 2, 0, false, Horizontal, \"Valkyrie no Bouken - Toki no Kagi Densetsu (Japan).nes\"\n  C6B5D7E0, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Palamedes (Japan).nes\"\n  C6C2EDB5, NTSC, 1, 0, 16, 4, 0, false, Horizontal, \"Magic Johnson's Fast Break (USA).nes\"\n  C6DD7E69, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Stealth ATF (USA).nes\"\n  C7197FB1, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Spy Hunter (USA).nes\"\n  C739E88E, NTSC, 86, 0, 0, 8, 0, false, Vertical, \"Moero!! Pro Yakyuu (Japan) (Beta).nes\"\n  C73B82FC, NTSC, 11, 0, 4, 2, 0, false, Vertical, \"Shockwave (USA) (Unl).nes\"\n  C740EB46, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Arch Rivals - A Basketbrawl! (USA).nes\"\n  C7642467, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Konamic Sports in Seoul (Japan).nes\"\n  C769BB34, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Parasol Henbee (Japan).nes\"\n  C76AADF4, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Mugen Senshi Valis (Japan).nes\"\n  C7BCC981, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Super Mogura Tataki!! - Pokkun Mogurar (Japan).nes\"\n  C7F0C457, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Crash 'n' the Boys - Street Challenge (USA).nes\"\n  C811DC7A, NTSC, 19, 0, 16, 8, 0, false, Vertical, \"Youkai Douchuuki (Japan).nes\"\n  C8228B54, PAL, 4, 0, 32, 8, 0, false, Horizontal, \"Incredible Crash Dummies, The (Europe).nes\"\n  C829007E, NTSC, 66, 0, 8, 4, 0, false, Horizontal, \"AV Mahjong Club (Japan) (Unl).nes\"\n  C8AD4F32, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Cobra Triangle (USA).nes\"\n  C8BD1908, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Ghostbusters (Japan) (Beta).nes\"\n  C8EBD977, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Tiny Toon Adventures Cartoon Workshop (Europe).nes\"\n  C8EDC97E, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Mitsume ga Tooru (Japan).nes\"\n  C8F203F9, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Addams Family, The - Pugsley's Scavenger Hunt (Europe) (Beta).nes\"\n  C9187B43, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Kagerou Densetsu (Japan).nes\"\n  C92B814B, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Blue Shadow (Europe).nes\"\n  C9484BB3, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Pro Yakyuu Satsujin Jiken! (Japan).nes\"\n  C9556B36, NTSC, 1, 0, 1, 32, 0, true, Horizontal, \"Final Fantasy I, II (Japan).nes\"\n  C973699D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Vegas Dream (USA).nes\"\n  C99B690A, PAL, 1, 0, 4, 8, 0, false, Horizontal, \"Bubble Bobble (Europe).nes\"\n  C9EDF585, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Little Magic (Japan).nes\"\n  CA033B3A, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Dragon's Lair (USA).nes\"\n  CA094848, NTSC, 32, 0, 0, 16, 0, false, Horizontal, \"Perman Part 2 - Himitsu Kessha Madoodan o Taose! (Japan).nes\"\n  CA0A869E, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Silent Assault (USA) (Unl).nes\"\n  CA24A1A2, NTSC, 67, 0, 0, 8, 0, false, Horizontal, \"Mito Koumon - Sekai Manyuu Ki (Japan).nes\"\n  CA503F32, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Itadaki Street - Watashi no Omise ni Yottette (Japan).nes\"\n  CA594ACE, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Super Mario Bros. 2 (USA) (Rev A).nes\"\n  CA5EDBFC, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Home Alone (USA).nes\"\n  CA69751B, NTSC, 19, 0, 16, 8, 0, false, Horizontal, \"Star Wars (Japan) (Namco).nes\"\n  CA6A7BF1, NTSC, 4, 0, 4, 2, 0, false, Horizontal, \"Sky Kid (Japan).nes\"\n  CA730971, NTSC, 4, 0, 16, 32, 0, true, Horizontal, \"Advanced Dungeons & Dragons - Pool of Radiance (Japan).nes\"\n  CA96AD0E, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Top Gun - Dual Fighters (Japan).nes\"\n  CAB40A6C, PAL, 243, 0, 0, 2, 0, false, Horizontal, \"Magic Cube (Asia) (PAL) (Unl).nes\"\n  CB04726D, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Alfombra Magica, La (Spain) (Gluk Video) (Unl).nes\"\n  CB0A3AF4, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Captain Silver (Japan).nes\"\n  CB0A76B1, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Square no Tom Sawyer (Japan).nes\"\n  CB17D41E, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Color a Dinosaur (USA) (Beta).nes\"\n  CB32E243, NTSC, 4, 0, 1, 32, 0, true, Vertical, \"Tenchi o Kurau II - Shokatsu Koumei Den (Japan).nes\"\n  CB35FA90, NTSC, 23, 0, 0, 8, 0, false, Horizontal, \"Contra (Japan) (Sample).nes\"\n  CB5ACB49, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Capcom Barcelona '92 (Japan).nes\"\n  CB8F9AB7, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Batman - The Video Game (USA) (Beta 2).nes\"\n  CBF4366F, NTSC, 118, 0, 0, 8, 0, false, Vertical, \"Alien Syndrome (USA) (Unl).nes\"\n  CBFB6DE5, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Yume Penguin Monogatari (Japan).nes\"\n  CC3544B0, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Triathron, The (Japan).nes\"\n  CC37094C, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Phantom Fighter (USA).nes\"\n  CC553FC4, NTSC, 4, 0, 32, 16, 0, false, Vertical, \"Star Trek - 25th Anniversary (Germany).nes\"\n  CC6CA4DC, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Chip 'n Dale - Rescue Rangers 2 (USA) (Beta).nes\"\n  CC7A4DCA, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Abarenbou Tengu (Japan).nes\"\n  CCAF543A, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"JuJu Densetsu (Japan).nes\"\n  CCC03440, NTSC, 156, 0, 0, 8, 0, false, Horizontal, \"Buzz & Waldog (USA) (Proto) (Unl).nes\"\n  CCCAF368, NTSC, 232, 0, 0, 16, 0, false, Vertical, \"Quattro Sports (USA) (Unl).nes\"\n  CCDCBFC6, NTSC, 71, 0, 1, 16, 0, false, Vertical, \"Big Nose Freaks Out (USA) (Unl).nes\"\n  CCF35C02, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Sangokushi (Japan) (Rev A).nes\"\n  CD10DCE2, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Kickle Cubicle (USA).nes\"\n  CD50A092, NTSC, 4, 0, 8, 8, 0, false, FourScreen, \"Gauntlet (USA) (Unl).nes\"\n  CD7A2FD7, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Hanjuku Hero (Japan).nes\"\n  CD883CDC, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Nintendo World Class Service - Joystick Test Cartridge (USA).nes\"\n  CDC641FC, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Championship Pool (USA).nes\"\n  CE00022D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Tecmo Bowl (USA).nes\"\n  CE06F2D4, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Over Horizon (Japan).nes\"\n  CE07194F, NTSC, 66, 0, 4, 8, 0, false, Horizontal, \"Kidou Senshi Z Gundam - Hot Scramble (Japan).nes\"\n  CE228874, NTSC, 4, 0, 4, 2, 0, false, Horizontal, \"Burai Fighter (USA).nes\"\n  CE2450C0, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Gegege no Kitarou - Youkai Daimakyou (Japan) (Beta).nes\"\n  CE67507A, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"BurgerTime (Japan).nes\"\n  CE77B4BE, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Ferrari Grand Prix Challenge (USA).nes\"\n  CEA35D5A, NTSC, 80, 0, 0, 8, 0, false, Vertical, \"Kyuukyoku Harikiri Stadium (Japan).nes\"\n  CEB65B06, NTSC, 7, 0, 1, 16, 0, false, Horizontal, \"Battletoads-Double Dragon (USA).nes\"\n  CEBD2A31, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Final Fantasy (USA).nes\"\n  CEC28502, NTSC, 136, 0, 0, 2, 0, false, Vertical, \"Wei Lai Xiao Zi (Asia) (Unl).nes\"\n  CEE5857B, NTSC, 1, 0, 1, 32, 0, false, Horizontal, \"Ninjara Hoi! (Japan).nes\"\n  CF0C9D97, NTSC, 185, 0, 0, 2, 0, false, Vertical, \"Spy vs Spy (Japan).nes\"\n  CF23290F, NTSC, 19, 0, 32, 16, 0, true, Horizontal, \"Juvei Quest (Japan).nes\"\n  CF26A149, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Twin Eagle (USA).nes\"\n  CF322BB3, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"John Elway's Quarterback (USA).nes\"\n  CF40B1C5, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Shadowgate (Japan).nes\"\n  CF4483AF, NTSC, 3, 0, 2, 2, 0, false, Horizontal, \"Banana (Japan) (Beta) (Earlier).nes\"\n  CF4487A2, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Super Jeopardy! (USA).nes\"\n  CF4DBDBE, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Front Line (Japan).nes\"\n  CF5F8AF0, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Golf Grand Slam (USA).nes\"\n  CF6D0D7A, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Top Gun (USA) (Rev A).nes\"\n  CF701DA4, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Flappy (Japan).nes\"\n  CF7CA9BD, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Castelian (Europe).nes\"\n  CF849F72, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Tecmo World Wrestling (Europe).nes\"\n  CF9CF7A2, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Romancia (Japan).nes\"\n  CFAE9DFA, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Spot - The Video Game (USA).nes\"\n  CFD29C93, NTSC, 4, 0, 32, 16, 0, false, Vertical, \"Star Wars - The Empire Strikes Back (USA) (Beta).nes\"\n  CFD4A281, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Money Game, The (Japan).nes\"\n  CFD5AC62, NTSC, 11, 0, 16, 8, 0, false, Vertical, \"Bible Buffet (USA) (v6.0) (Unl).nes\"\n  CFE02ADA, PAL, 0, 0, 16, 8, 0, false, Horizontal, \"Darkman (Europe).nes\"\n  D029F841, PAL, 2, 0, 1, 8, 0, false, Vertical, \"DuckTales (Europe).nes\"\n  D04A40E6, NTSC, 0, 0, 4, 2, 0, false, Horizontal, \"Bingo 75 (Asia) (Unl).nes\"\n  D054FFB0, NTSC, 4, 0, 32, 16, 0, true, Vertical, \"Zoda's Revenge - StarTropics II (USA).nes\"\n  D074653D, NTSC, 3, 0, 2, 2, 0, false, Vertical, \"Tetris (Bulletproof) (Japan) (Rev A).nes\"\n  D09B74DC, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Great Tank (Japan).nes\"\n  D0A9F4E1, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Famicom Yakyuu Ban (Japan).nes\"\n  D0CC5EC8, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Mashin Eiyuu Den Wataru Gaiden (Japan).nes\"\n  D0DF525E, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Power Blade (Europe).nes\"\n  D0DF726E, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Zoids - Chuuou Tairiku no Tatakai (Japan).nes\"\n  D0E53454, NTSC, 80, 0, 0, 8, 0, false, Vertical, \"Kyuukyoku Harikiri Stadium - '88 Senshu Shin Data Version (Japan).nes\"\n  D0E96F6B, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Double Dribble (USA) (Rev A).nes\"\n  D0EB749F, NTSC, 18, 0, 16, 8, 0, false, Horizontal, \"Lord of King, The (Japan).nes\"\n  D0F70E36, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Batman - The Video Game (Europe).nes\"\n  D1397940, NTSC, 97, 0, 0, 16, 0, false, Horizontal, \"Kaiketsu Yancha Maru (Japan).nes\"\n  D152FB02, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Circus Caper (USA).nes\"\n  D153CAF6, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Swords and Serpents (Europe).nes\"\n  D161888B, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Kick Off (Europe).nes\"\n  D1691028, NTSC, 154, 0, 16, 8, 0, false, Horizontal, \"Devil Man (Japan).nes\"\n  D175B0CB, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Super Real Baseball '88 (Japan).nes\"\n  D17B76DA, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Donald Land (Japan).nes\"\n  D188963D, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Challenge of the Dragon (USA) (Unl).nes\"\n  D18E6BE3, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Cowboy Kid (USA).nes\"\n  D19ADDEB, NTSC, 119, 0, 0, 8, 0, false, Horizontal, \"Pin Bot (USA).nes\"\n  D19DCB2B, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Gun Nac (USA).nes\"\n  D1E50064, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Game Designer Yousei Soft - Dezaemon (Japan).nes\"\n  D1EA84C3, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Caveman Games (USA).nes\"\n  D1F7DF3A, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Hokuto no Ken 2 (Japan).nes\"\n  D201EDCE, NTSC, 113, 0, 8, 2, 0, false, Horizontal, \"AV Hanafuda Club (Japan) (Unl).nes\"\n  D2038FC5, NTSC, 32, 0, 0, 8, 0, false, Horizontal, \"Image Fight (Japan).nes\"\n  D20BB617, PAL, 118, 0, 0, 8, 0, false, Vertical, \"Eric Cantona Football Challenge - Goal! 2 (Europe).nes\"\n  D229FD5C, PAL, 9, 0, 16, 8, 0, false, Horizontal, \"Punch-Out!! (Europe).nes\"\n  D2562072, NTSC, 7, 0, 1, 16, 0, false, Horizontal, \"Wizards & Warriors III - Kuros...Visions of Power (USA).nes\"\n  D2574720, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Bionic Commando (USA).nes\"\n  D2674B0A, NTSC, 0, 0, 4, 4, 0, false, Horizontal, \"Qi Wang - Chinese Chess (Asia) (Unl).nes\"\n  D2699893, NTSC, 88, 0, 16, 8, 0, false, Horizontal, \"Dragon Spirit - Aratanaru Densetsu (Japan).nes\"\n  D26EFD78, NTSC, 66, 0, 2, 4, 0, false, Vertical, \"Super Mario Bros. + Duck Hunt (USA).nes\"\n  D273B409, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Power Blade 2 (USA).nes\"\n  D27B9D50, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Terminator 2 (Japan).nes\"\n  D29DB3C7, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Final Fantasy II (Japan).nes\"\n  D2BC86F3, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Ikari II - Dogosoken (Japan).nes\"\n  D308D52C, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Raid on Bungeling Bay (USA).nes\"\n  D31DC910, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Rockman (Japan) (En).nes\"\n  D31EB7BB, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Star Trek V - The Final Frontier (Unknown) (Proto).nes\"\n  D323B806, NTSC, 210, 2, 32, 16, 0, false, Vertical, \"Wagyan Land 3 (Japan).nes\"\n  D343C66A, NTSC, 16, 0, 16, 16, 0, false, Horizontal, \"Famicom Jump - Eiyuu Retsuden (Japan).nes\"\n  D353D351, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"F-15 Strike Eagle (Germany).nes\"\n  D364F816, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Barbie (Europe).nes\"\n  D3BFF72E, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Town & Country Surf Designs - Wood & Water Rage (USA).nes\"\n  D3EC98AA, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Columbus - Ougon no Yoake (Japan) (Sample).nes\"\n  D40FA953, NTSC, 185, 0, 0, 2, 0, false, Horizontal, \"Mighty Bomb Jack (Japan) (Rev A).nes\"\n  D445F698, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Super Mario Bros. (World).nes\"\n  D44B412E, PAL, 1, 0, 1, 8, 0, true, Horizontal, \"Legend of Zelda, The (Europe) (Rev A).nes\"\n  D4611B79, NTSC, 4, 0, 32, 8, 0, false, Vertical, \"WWF Wrestlemania Steel Cage Challenge (USA).nes\"\n  D467C0CC, NTSC, 23, 0, 0, 8, 0, false, Horizontal, \"Parodius Da! (Japan).nes\"\n  D4924CBA, NTSC, 0, 0, 2, 2, 0, false, Horizontal, \"Taiwan Mahjong - Tai Wan Ma Que 16 (Asia) (Unl).nes\"\n  D49DCA84, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Robin Hood - Prince of Thieves (Germany).nes\"\n  D4D9E21A, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Tennis (Japan, USA).nes\"\n  D532E98F, NTSC, 5, 0, 16, 16, 0, false, Horizontal, \"Shin 4 Nin Uchi Mahjong - Yakuman Tengoku (Japan).nes\"\n  D534C98E, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Fire 'n Ice (USA).nes\"\n  D568563F, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Wanpaku Kokkun no Gourmet World (Japan).nes\"\n  D5941AA9, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Final Mission (Japan).nes\"\n  D5C588DF, PAL, 1, 0, 16, 8, 0, false, Vertical, \"Snowboard Challenge (Europe).nes\"\n  D5C64257, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Kung Fu (Japan, USA).nes\"\n  D6190C63, NTSC, 4, 0, 16, 16, 0, true, Vertical, \"Tower of Radia (USA) (Proto).nes\"\n  D630EE8F, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Aussie Rules Footy (Australia).nes\"\n  D63B30F5, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Tom & Jerry - The Ultimate Game of Cat and Mouse! (USA).nes\"\n  D679627A, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Spider-Man - Return of the Sinister Six (USA).nes\"\n  D67FD6A6, PAL, 1, 0, 1, 8, 0, false, Horizontal, \"Kid Icarus (Europe) (Rev A).nes\"\n  D68A6F33, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Dungeon Kid (Japan).nes\"\n  D6AD4E9D, PAL, 0, 0, 1, 1, 0, false, Horizontal, \"Pinball (Europe) (Rev A).nes\"\n  D6BBD8BA, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Tennis (USA) (GameCube Edition).nes\"\n  D6EFAB8D, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Solomon's Key 2 (USA) (Beta).nes\"\n  D6F7383E, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Castlevania II - Simon's Quest (Europe).nes\"\n  D6FE9826, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Viva! Las Vegas (Japan).nes\"\n  D7077D96, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"U.S. Championship V'Ball (Japan) (Beta).nes\"\n  D7215873, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Doraemon - Giga Zombie no Gyakushuu (Japan).nes\"\n  D72560E1, PAL, 1, 0, 16, 16, 0, false, Horizontal, \"Racket Attack (Europe).nes\"\n  D738C059, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Dick Tracy (USA).nes\"\n  D73AA04C, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Cool World (USA).nes\"\n  D745D7CB, PAL, 0, 0, 1, 2, 0, false, Horizontal, \"Xevious (Europe).nes\"\n  D74B2719, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Super Team Games (USA).nes\"\n  D754F500, NTSC, 68, 0, 0, 8, 0, true, Vertical, \"Nantettatte!! Baseball (Japan) (Sample).nes\"\n  D7794AFC, NTSC, 4, 0, 32, 32, 0, true, Horizontal, \"Kirby's Adventure (USA).nes\"\n  D78BFB28, PAL, 2, 0, 1, 8, 0, false, Horizontal, \"Top Gun (Europe).nes\"\n  D7AA0B6D, NTSC, 88, 0, 8, 8, 0, false, Vertical, \"Dragon Buster II - Yami no Fuuin (Japan).nes\"\n  D7B35F7D, PAL, 2, 0, 1, 8, 0, false, Horizontal, \"Konami Hyper Soccer (Europe).nes\"\n  D7CB398F, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Ninja Ryuuken Den (Japan).nes\"\n  D7E29C03, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Dragon Spirit - The New Legend (USA).nes\"\n  D7F6320C, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Chessmaster, The (USA).nes\"\n  D7FABAC1, NTSC, 22, 0, 0, 8, 0, false, Horizontal, \"TwinBee 3 - Poko Poko Daimaou (Japan).nes\"\n  D80B44BC, NTSC, 66, 0, 4, 8, 0, false, Horizontal, \"Thunder & Lightning (USA).nes\"\n  D821A1C6, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Solomon no Kagi (Japan).nes\"\n  D8230D0E, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Hook (USA).nes\"\n  D8578BFD, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Zen - Intergalactic Ninja (USA).nes\"\n  D8748E0A, NTSC, 18, 0, 16, 8, 0, false, Horizontal, \"Magic John (Japan).nes\"\n  D898A900, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Dou Zhi Pin Pan - Wisdom Boy (China) (Unl).nes\"\n  D89E5A67, NTSC, 3, 0, 2, 2, 0, false, Horizontal, \"Arkanoid (Japan).nes\"\n  D8D42F2F, NTSC, 0, 0, 2, 2, 0, false, Horizontal, \"Chinese Checkers (Asia) (NTSC) (Unl).nes\"\n  D8EE7669, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Adventures of Rad Gravity, The (USA).nes\"\n  D8EFF0DF, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Gradius (Japan).nes\"\n  D8F651E2, NTSC, 2, 0, 1, 16, 0, false, Vertical, \"Seirei Gari (Japan).nes\"\n  D9084936, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Gun Nac (Japan).nes\"\n  D91104F1, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"4 Nin Uchi Mahjong (Japan).nes\"\n  D920F9DF, NTSC, 33, 0, 0, 8, 0, false, Horizontal, \"Takeshi no Sengoku Fuuunji (Japan).nes\"\n  D923EB5B, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Chip to Dale no Daisakusen (Japan).nes\"\n  D9323EE6, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Willow (Europe).nes\"\n  D97595A3, NTSC, 87, 0, 0, 1, 0, false, Vertical, \"Ninja Jajamaru-kun (Japan).nes\"\n  D97C31B0, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Lasalle Ishii no Child's Quest (Japan).nes\"\n  D996AB4E, NTSC, 66, 0, 4, 2, 0, false, Vertical, \"Uforce Power Games (USA) (Proto 2) [b].nes\"\n  D99A8804, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Super Pitfall II (USA) (Proto).nes\"\n  D9BB572C, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Wizardry - Proving Grounds of the Mad Overlord (USA).nes\"\n  D9C093B1, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Space Invaders (Japan).nes\"\n  D9F0749F, PAL, 1, 0, 1, 8, 0, false, Horizontal, \"Kid Icarus (USA, Europe).nes\"\n  D9F1E47C, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Rockman 3 - Dr. Wily no Saigo! (Japan).nes\"\n  D9F45BE9, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Gimmi a Break - Shijou Saikyou no Quiz Ou Ketteisen (Japan).nes\"\n  DA2CB59A, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Nightmare on Elm Street, A (USA).nes\"\n  DA430FB3, NTSC, 113, 0, 8, 2, 0, false, Horizontal, \"AV Soccer (Japan) (Unl).nes\"\n  DA690D17, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Nihonichi no Mei Kantoku (Japan).nes\"\n  DA8E4AF4, NTSC, 4, 0, 32, 8, 0, true, Horizontal, \"Tecmo NBA Basketball (USA) (Rev A).nes\"\n  DA8F65AE, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Untouchables, The (Japan).nes\"\n  DAB84A9C, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Flintstones, The - The Surprise at Dinosaur Peak! (USA).nes\"\n  DAD34EE6, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Tao (Japan).nes\"\n  DAD88CC5, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Guardic Gaiden (Japan).nes\"\n  DAEE19F2, NTSC, 1, 0, 8, 16, 0, false, Horizontal, \"Bases Loaded (USA) (Rev A).nes\"\n  DAF9D7E3, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"BurgerTime (USA).nes\"\n  DB05106E, NTSC, 16, 0, 16, 8, 0, false, Horizontal, \"Crayon Shin-chan - Ora to Poi Poi (Japan).nes\"\n  DB196068, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Takeshi no Chousenjou (Japan).nes\"\n  DB1D03E5, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Caesars Palace (USA).nes\"\n  DB2D4F9D, NTSC, 4, 0, 16, 16, 0, true, Vertical, \"Tecmo Super Bowl (USA) (Beta).nes\"\n  DB479677, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Kiteretsu Daihyakka (Japan).nes\"\n  DB564628, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Mario Open Golf (Japan) (Rev A).nes\"\n  DB99D0CB, NTSC, 71, 0, 1, 8, 0, false, Vertical, \"Dizzy the Adventurer (USA) (Aladdin Compact Cartridge) (Unl).nes\"\n  DB9C072D, PAL, 7, 0, 1, 8, 0, false, Vertical, \"Arch Rivals - A Basketbrawl! (Europe).nes\"\n  DB9DCF89, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Bomberman (USA).nes\"\n  DBB06A25, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Dig Dug II - Trouble in Paradise (USA).nes\"\n  DBC5ECD9, NTSC, 0, 0, 32, 16, 0, false, Horizontal, \"Super Cartridge Ver 4 - 6 in 1 (Asia) (Unl).nes\"\n  DBECE74F, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"SD Hero Soukessen - Taose! Aku no Gundan (Japan).nes\"\n  DBF90772, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Alpha Mission (USA).nes\"\n  DC02F095, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Pictionary - The Game of Video Quick Draw (USA).nes\"\n  DC1E07D2, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Hyakkiyagyou (Japan).nes\"\n  DC320617, NTSC, 0, 0, 16, 16, 0, false, Horizontal, \"Super Cartridge Ver 5 - 7 in 1 (Asia) (Unl).nes\"\n  DC45A886, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Downtown Special - Kunio-kun no Jidaigeki Da yo Zenin Shuugou! (Japan).nes\"\n  DC4DA5D4, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Side Pocket (USA).nes\"\n  DC529482, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Maniac Mansion (Italy).nes\"\n  DC52BF0C, NTSC, 16, 0, 32, 16, 0, false, Horizontal, \"Dragon Ball Z III - Ressen Jinzou Ningen (Japan).nes\"\n  DC75732F, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Cosmic Epsilon (Japan).nes\"\n  DCB7C0A1, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Wanpaku Duck Yume Bouken (Japan).nes\"\n  DCB972CE, NTSC, 16, 0, 16, 8, 0, true, Horizontal, \"Magical Taruruuto-kun - Fantastic World!! (Japan) (Rev 1).nes\"\n  DCD8D6F4, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Bakushou!! Ai no Gekijou (Japan).nes\"\n  DCDF06DE, NTSC, 4, 0, 4, 4, 0, false, Horizontal, \"Pro Yakyuu - Family Stadium (Japan).nes\"\n  DD062F9C, NTSC, 7, 0, 1, 4, 0, false, Horizontal, \"R.C. Pro-Am (USA).nes\"\n  DD29FD59, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Family Mahjong II - Shanghai e no Michi (Japan).nes\"\n  DD8ED0F7, NTSC, 70, 0, 0, 8, 0, false, Horizontal, \"Kamen Rider Club (Japan).nes\"\n  DDA190F9, PAL, 146, 0, 4, 2, 0, false, Vertical, \"Twin Eagle (Asia) (PAL) (Unl).nes\"\n  DDC6D9C9, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Alien 3 (Europe).nes\"\n  DDCBDA16, NTSC, 243, 0, 0, 4, 0, false, Vertical, \"Lightgun Game 2 in 1 - Tough Cop + Super Tough Cop (Asia) (Unl).nes\"\n  DDD90C39, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Shadow of the Ninja (USA).nes\"\n  DDDC56B8, NTSC, 240, 0, 0, 8, 0, true, Horizontal, \"Sheng Huo Lie Zhuan (Asia) (Unl).nes\"\n  DE0C29A9, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Maniac Mansion (USA) (Beta).nes\"\n  DE25B90F, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Rush'n Attack (USA).nes\"\n  DE395EFD, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Gradius (Japan) (ArchiMENdes Hen).nes\"\n  DE581355, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Dr. Mario (Japan, USA) (Rev A).nes\"\n  DE7E4629, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Simpsons, The - Bart vs. the Space Mutants (Europe).nes\"\n  DE84354A, NTSC, 1, 0, 16, 8, 0, false, Vertical, \"Superman (Japan) (Beta).nes\"\n  DE8FD935, NTSC, 4, 0, 4, 2, 0, false, Horizontal, \"To the Earth (USA).nes\"\n  DE9C9C64, NTSC, 80, 0, 0, 8, 0, false, Vertical, \"Kyonshiizu 2 (Japan).nes\"\n  DF31B364, NTSC, 112, 0, 0, 4, 0, false, Horizontal, \"Cobra Mission (Asia) (Unl).nes\"\n  DF3776C6, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Sword Master (Japan).nes\"\n  DF3E45D2, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Mad City (Japan).nes\"\n  DF43E073, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"RoboCop versus The Terminator (USA) (Proto).nes\"\n  DF4EDC13, PAL, 1, 0, 4, 2, 0, false, Horizontal, \"Adventures of Lolo (Europe).nes\"\n  DF64963B, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Infiltrator (USA).nes\"\n  DF67DAA1, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Stack-Up (World).nes\"\n  DF6D0CE8, PAL, 1, 0, 16, 8, 0, false, Vertical, \"Iron Tank - The Invasion of Normandy (Europe).nes\"\n  DFA111F1, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Bram Stoker's Dracula (USA).nes\"\n  DFAD3F66, NTSC, 245, 0, 0, 16, 0, true, Vertical, \"Ying Xiong Yuan Yi Jing Chuan Qi (China) (Unl).nes\"\n  DFC0CE21, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Super Black Onyx (Japan).nes\"\n  DFD70E27, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Xevious - The Avenger (USA).nes\"\n  DFEFE8CD, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Clu Clu Land (USA) (GameCube Edition).nes\"\n  E02133AC, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Magic Darts (Japan).nes\"\n  E043C6A5, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Lethal Weapon (Europe).nes\"\n  E0604F76, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"F-1 Race (Japan).nes\"\n  E08C8A60, NTSC, 4, 0, 1, 32, 0, true, Horizontal, \"Pachio-kun 4 (Japan).nes\"\n  E095C3F2, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Bubble Bobble Part 2 (USA).nes\"\n  E0AC6242, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Rush'n Attack (Europe).nes\"\n  E0B6B7BB, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Zoids - Chuuou Tairiku no Tatakai (Japan) (Rev A).nes\"\n  E0CBC2BA, NTSC, 1, 0, 16, 16, 0, true, Horizontal, \"Chaos World (Japan).nes\"\n  E0FFFBD2, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Little Nemo - The Dream Master (Europe).nes\"\n  E116447F, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"BinGuo 75 (Asia) (Unl).nes\"\n  E1383DEB, NTSC, 26, 0, 32, 16, 0, false, Horizontal, \"Mouryou Senki Madara (Japan).nes\"\n  E145B441, NTSC, 1, 0, 16, 16, 0, false, Horizontal, \"Day Dreamin' Davey (USA).nes\"\n  E149E0B2, NTSC, 1, 0, 8, 2, 0, true, Horizontal, \"Nintendo - NTF2 System Cartridge (USA).nes\"\n  E14F0A3F, NTSC, 1, 0, 1, 8, 0, false, Vertical, \"Super Pinball (Japan) (Beta).nes\"\n  E1526228, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Ki no Bouken - The Quest of Ki (Japan).nes\"\n  E15C973D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Exciting Rally - World Rally Championship (Japan).nes\"\n  E170404C, NTSC, 16, 0, 16, 16, 0, true, Horizontal, \"SD Gundam Gaiden - Knight Gundam Monogatari (Japan).nes\"\n  E19293A2, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Best Play Pro Yakyuu - Shin Data (Japan).nes\"\n  E19EE99C, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Bucky O'Hare (USA).nes\"\n  E1B260DA, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Argos no Senshi (Japan).nes\"\n  E1C03EB6, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Egypt (Japan).nes\"\n  E1C41D7C, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Rally Bike (USA).nes\"\n  E1C59D94, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Top Gun - The Second Mission (Europe).nes\"\n  E211B93A, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Donkey Kong Jr. (Japan).nes\"\n  E2265BF4, NTSC, 0, 0, 8, 4, 0, false, Horizontal, \"Rockball (Asia) (Unl).nes\"\n  E2281986, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Marusa no Onna (Japan).nes\"\n  E2313813, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Code Name - Viper (USA).nes\"\n  E24483B1, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Obocchama-kun (Japan).nes\"\n  E24DF353, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Totsuzen! Macchoman (Japan).nes\"\n  E292AA10, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Zanac (USA).nes\"\n  E2A79A57, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Rollerball (Japan).nes\"\n  E2B43A68, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Amagon (USA).nes\"\n  E2C4EDCE, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Journey to Silius (USA).nes\"\n  E3027EBE, PAL, 1, 0, 4, 8, 0, false, Horizontal, \"Chessmaster, The (Europe).nes\"\n  E305202E, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Time Zone (Japan).nes\"\n  E30B2BCF, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Pachicom (Japan).nes\"\n  E326E0F5, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"ASO - Armored Scrum Object (Japan) (En) (Beta).nes\"\n  E333FFA1, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Igo Shinan '91 (Japan).nes\"\n  E349AF38, NTSC, 24, 0, 16, 16, 0, false, Horizontal, \"Akumajou Densetsu (Japan).nes\"\n  E353969F, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Last Ninja, The (USA).nes\"\n  E37A39AB, PAL, 4, 0, 8, 8, 0, false, Vertical, \"Yoshi's Cookie (Europe).nes\"\n  E387C77F, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Ultimate Air Combat (USA).nes\"\n  E3C5BB3D, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Mission Impossible (USA).nes\"\n  E3E2C3BF, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"ASO - Armored Scrum Object (Japan).nes\"\n  E402B134, PAL, 3, 0, 4, 2, 0, false, Vertical, \"Dropzone (Europe).nes\"\n  E40B4973, NTSC, 4, 0, 4, 2, 0, false, Horizontal, \"Metro-Cross (Japan).nes\"\n  E429F0D3, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Cycle Race - Road Man (Japan).nes\"\n  E4362167, NTSC, 85, 0, 0, 8, 0, false, Horizontal, \"Tiny Toon Adventures 2 - Montana Land e Youkoso (Japan).nes\"\n  E44001D8, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Casino Derby (Japan).nes\"\n  E46AEE21, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Thomas the Tank Engine and Friends (USA) (Proto).nes\"\n  E46B1C5D, NTSC, 140, 0, 0, 8, 0, false, Vertical, \"Mississippi Satsujin Jiken (Japan).nes\"\n  E4776A2B, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Blues Brothers, The (Europe).nes\"\n  E47E9FA7, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Sky Destroyer (Japan).nes\"\n  E492D45A, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Zippy Race (Japan).nes\"\n  E4A6E151, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Family Trainer 10 - Rairai Kyonsees (Japan) (Beta).nes\"\n  E4A7D436, NTSC, 4, 0, 32, 32, 0, true, Horizontal, \"Hoshi no Kirby - Yume no Izumi no Monogatari (Japan).nes\"\n  E4E7C62D, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Time Diver Eon Man (USA) (Proto).nes\"\n  E50A9130, NTSC, 1, 0, 4, 4, 0, false, Horizontal, \"Bugs Bunny Crazy Castle, The (USA).nes\"\n  E53F7A55, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Cosmo Police Galivan (Japan).nes\"\n  E54138A9, PAL, 0, 0, 1, 1, 0, false, Horizontal, \"Balloon Fight (Europe).nes\"\n  E542E3CF, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Monster in My Pocket (USA).nes\"\n  E56AA5E8, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Sou Setsu Ryuu II - The Revenge (Japan) (Beta).nes\"\n  E575687C, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Star Trek - The Next Generation (USA).nes\"\n  E57E5384, PAL, 0, 0, 1, 2, 0, false, Vertical, \"Mach Rider (Europe).nes\"\n  E5901A99, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Kickle Cubicle (Europe).nes\"\n  E592F53A, PAL, 3, 0, 4, 2, 0, false, Vertical, \"Athletic World (Europe).nes\"\n  E5A8401B, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Stealth ATF (Europe).nes\"\n  E5A972BE, PAL, 7, 0, 1, 4, 0, false, Horizontal, \"R.C. Pro-Am (Europe).nes\"\n  E5EA0EBE, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Tecmo Bowl (USA) (Beta).nes\"\n  E5FCC4C1, PAL, 1, 0, 4, 2, 0, false, Horizontal, \"Boulder Dash (Europe).nes\"\n  E616FF0A, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Advanced Dungeons & Dragons - Dragons of Flame (Japan).nes\"\n  E62E3382, NTSC, 71, 0, 1, 8, 0, false, Vertical, \"MiG 29 - Soviet Fighter (USA) (Unl).nes\"\n  E63D9193, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Tanigawa Kouji no Shougi Shinan III (Japan).nes\"\n  E64B8975, NTSC, 19, 0, 16, 8, 0, true, Vertical, \"Sangokushi - Chuugen no Hasha (Japan).nes\"\n  E661918C, NTSC, 69, 0, 16, 16, 0, false, Vertical, \"Gimmick! (Japan) (Beta).nes\"\n  E66AD6B8, PAL, 0, 0, 1, 2, 0, false, Vertical, \"25th Anniversary Super Mario Bros. (Europe) (Promo, Virtual Console).nes\"\n  E66BDDCF, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Donald Duck (Japan).nes\"\n  E681B300, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Trolls in Crazyland, The (Europe).nes\"\n  E6857563, NTSC, 4, 0, 32, 8, 0, false, Vertical, \"Mike Tyson's Intergalactic Power Punch (USA) (Beta) [b].nes\"\n  E6A477B2, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"3-D WorldRunner (USA).nes\"\n  E6B30BB3, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Kamen no Ninja - Hanamaru (Japan).nes\"\n  E6C9029E, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Motocross Champion (Japan).nes\"\n  E6DF6616, NTSC, 87, 0, 0, 2, 0, false, Vertical, \"Goonies (Japan).nes\"\n  E6F08E93, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Pro Wrestling (USA).nes\"\n  E71D034E, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Snake's Revenge (Europe).nes\"\n  E71DB268, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Faxanadu (USA) (Rev A).nes\"\n  E73E7260, NTSC, 4, 0, 4, 4, 0, false, Vertical, \"Pac-Mania (USA) (Unl).nes\"\n  E74A91BB, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Silk Worm (USA).nes\"\n  E74AA15A, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Kaettekita! Gunjin Shougi - Nanya Sore! (Japan).nes\"\n  E78A394C, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"SD Battle Oozumou - Heisei Hero Basho (Japan).nes\"\n  E7C981A2, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Widget (USA).nes\"\n  E7D2C49D, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Golf (USA).nes\"\n  E7DA8A04, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Last Action Hero (USA).nes\"\n  E7DDFEE3, NTSC, 4, 0, 16, 16, 0, false, Horizontal, \"Super Mario Bros. 3 (Japan).nes\"\n  E8000BF7, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Pinball (USA) (GameCube Edition).nes\"\n  E840FD21, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Super Spike V'Ball (USA).nes\"\n  E85B4D3D, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Hit Marmot (Asia) (Unl).nes\"\n  E8A11BD7, NTSC, 3, 0, 1, 2, 0, false, Horizontal, \"Porter (Asia) (Unl).nes\"\n  E8AF6FF5, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Nagagutsu o Haita Neko - Sekai Isshuu 80 Nichi Daibouken (Japan).nes\"\n  E8BAA782, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Gunhed - Aratanaru Tatakai (Japan).nes\"\n  E9023072, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Formation Z (Japan) (Rev A).nes\"\n  E911BCC4, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Galaga (Japan).nes\"\n  E9176129, NTSC, 4, 0, 4, 2, 0, false, Horizontal, \"Burai Fighter (Japan).nes\"\n  E943EC4D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Frankenstein - The Monster Returns (USA).nes\"\n  E949EF8A, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Astro Fang - Super Machine (Japan).nes\"\n  E94D5181, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Future Wars - Mirai Senshi Lios (Japan).nes\"\n  E94E883D, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Super Mario Bros. 2 (Europe).nes\"\n  E95454FC, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Cross Fire (Japan).nes\"\n  E95E51E0, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Hyaku no Sekai no Monogatari - The Tales on a Watery Wilderness (Japan).nes\"\n  E98AB943, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Ren & Stimpy Show, The - Buckeroo$! (USA).nes\"\n  E9A6C211, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Ski or Die (USA).nes\"\n  E9AD2163, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Cosmos Cop (Spain) (Gluk Video) (Unl).nes\"\n  E9C387EC, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"River City Ransom (USA).nes\"\n  E9D352EB, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Double Dragon III - The Sacred Stones (Europe).nes\"\n  E9EDBA24, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Karateka (Japan) (Beta).nes\"\n  E9F16673, NTSC, 3, 0, 8, 2, 0, false, Horizontal, \"Peek-A-Boo Poker (Asia) (Unl).nes\"\n  E9F8EF15, PAL, 4, 0, 16, 16, 0, false, Horizontal, \"Simpsons, The - Bart vs. the World (Europe).nes\"\n  EA113128, NTSC, 11, 0, 8, 4, 0, false, Vertical, \"Operation Secret Storm (USA) (Unl).nes\"\n  EA19080A, NTSC, 79, 0, 4, 2, 0, false, Horizontal, \"Puzzle (USA) (Unl).nes\"\n  EA27B477, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Terminator 2 - Judgment Day (USA).nes\"\n  EA31CCD3, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Layla (Japan).nes\"\n  EA3E78DD, NTSC, 18, 0, 32, 8, 0, false, Horizontal, \"Toukon Club (Japan).nes\"\n  EA4EB69E, NTSC, 1, 0, 4, 8, 0, false, Horizontal, \"Touch Down Fever (USA).nes\"\n  EA89963F, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Tatakae! Chou Robot Seimeitai Transformers - Convoy no Nazo (Japan).nes\"\n  EA90F3E2, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Family Trainer 2 - Running Stadium (Japan).nes\"\n  EAB002AE, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Dino-Hockey (USA) (Proto).nes\"\n  EAB93CFB, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Adventures of Lolo 2 (Japan).nes\"\n  EAC38105, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Championship Bowling (USA).nes\"\n  EAF7ED72, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Legend of Zelda, The (USA) (Rev A).nes\"\n  EB0BDA7E, NTSC, 64, 0, 0, 8, 0, false, Horizontal, \"Shinobi (USA) (Unl).nes\"\n  EB15169E, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Heavy Shreddin' (USA).nes\"\n  EB465156, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Kero Kero Keroppi no Daibouken (Japan).nes\"\n  EB4CCA31, PAL, 0, 0, 4, 2, 0, false, Vertical, \"Master Chu and the Drunkard Hu (Asia) (PAL) (Unl).nes\"\n  EB61133B, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Puzznic (USA).nes\"\n  EB764567, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Druaga no Tou (Japan).nes\"\n  EB803610, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Wurm - Journey to the Center of the Earth! (USA).nes\"\n  EB84C54C, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Beetlejuice (USA).nes\"\n  EB92B32A, NTSC, 25, 0, 0, 16, 0, true, Horizontal, \"Ganbare Goemon Gaiden - Kieta Ougon Kiseru (Japan).nes\"\n  EB9960EE, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Destination Earthstar (USA).nes\"\n  EBB5E666, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Little Mermaid - Ningyo Hime (Japan) (Beta).nes\"\n  EBCF8419, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"Samsara Naga (Japan).nes\"\n  EBCFE7C5, NTSC, 1, 0, 16, 4, 0, false, Horizontal, \"Knight Rider (USA).nes\"\n  EBD0644D, NTSC, 0, 0, 4, 2, 0, false, Vertical, \"Dao Shuai (Asia) (Unl).nes\"\n  EC0517C4, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Zunou Senkan Galg (Japan).nes\"\n  EC0FC2DE, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Sesame Street ABC (USA).nes\"\n  EC40E71B, NTSC, 140, 0, 0, 8, 0, false, Vertical, \"Bio Senshi Dan - Increaser Tono Tatakai (Japan).nes\"\n  EC8A884F, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Moai-kun (Japan).nes\"\n  EC968C51, NTSC, 4, 0, 8, 8, 0, false, FourScreen, \"Gauntlet (USA).nes\"\n  ECBF33CE, NTSC, 4, 0, 16, 16, 0, true, Horizontal, \"F1 Circus (Japan).nes\"\n  ECCD4089, NTSC, 1, 0, 16, 16, 0, false, Horizontal, \"Racket Attack (USA).nes\"\n  ED2465BE, NTSC, 5, 0, 16, 16, 0, false, Horizontal, \"Castlevania III - Dracula's Curse (USA).nes\"\n  ED3FA60E, PAL, 4, 0, 32, 8, 0, false, Horizontal, \"Spider-Man - Return of the Sinister Six (Europe).nes\"\n  ED4D696F, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Magic Block (Asia) (Mega Soft) (Unl).nes\"\n  ED77B453, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Asterix (Europe) (En,Fr,De,Es,It).nes\"\n  ED7F5555, PAL, 1, 0, 1, 8, 0, true, Horizontal, \"Legend of Zelda, The (Europe).nes\"\n  EDC3662B, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Operation Wolf (USA) (Rev 0A).nes\"\n  EDCF1B71, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Solstice - The Quest for the Staff of Demnos (USA).nes\"\n  EDDCC468, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"DuckTales 2 (Japan).nes\"\n  EE219A49, PAL, 3, 0, 4, 2, 0, false, Horizontal, \"Paperboy (Europe).nes\"\n  EE6892EB, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Trog! (USA).nes\"\n  EE7E61DE, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"U-Force Test (USA).nes\"\n  EE810D55, NTSC, 74, 0, 0, 32, 0, true, Vertical, \"You Ling Xing Dong (China) (Unl).nes\"\n  EE8E6553, NTSC, 5, 0, 32, 16, 0, true, Horizontal, \"Sangokushi II (Japan) (Rev AB).nes\"\n  EE921D8E, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Teenage Mutant Ninja Turtles (USA).nes\"\n  EEE111C2, NTSC, 3, 0, 8, 2, 0, false, Vertical, \"Soap Panic (Japan) (Unl).nes\"\n  EEE6314E, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Solomon no Kagi 2 - Coolmintou Kyuushutsu Sakusen (Japan).nes\"\n  EEE9A682, NTSC, 5, 0, 16, 16, 0, true, Horizontal, \"Nobunaga no Yabou - Sengoku Gunyuu Den (Japan).nes\"\n  EF7996BF, NTSC, 19, 0, 16, 8, 0, false, Vertical, \"Erika to Satoru no Yume Bouken (Japan).nes\"\n  EFB09075, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"DuckTales (USA).nes\"\n  EFB2B7E8, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Penguin & Seal, The (Asia, Australia) (Unl).nes\"\n  EFCF375D, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Super Glove Ball (USA).nes\"\n  EFD26E37, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Captain Skyhawk (USA) (Rev A).nes\"\n  F00584B6, NTSC, 4, 0, 16, 8, 0, true, Horizontal, \"Cyber Stadium Series - Base Wars (USA).nes\"\n  F009DDD2, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Toxic Crusaders (USA).nes\"\n  F011E490, NTSC, 5, 0, 32, 16, 0, true, Horizontal, \"Romance of the Three Kingdoms II (USA).nes\"\n  F03E6D72, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Squashed (USA) (Proto).nes\"\n  F053AC5F, NTSC, 4, 0, 32, 8, 0, true, Horizontal, \"Deja Vu (Japan).nes\"\n  F05870D5, NTSC, 79, 0, 8, 4, 0, false, Vertical, \"Mermaids of Atlantis - The Riddle of the Magic Bubble (USA) (Unl).nes\"\n  F08E8EF0, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Parallel World (Japan).nes\"\n  F0C198FF, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"New Ghostbusters II (Europe).nes\"\n  F0E9971B, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Gargoyle's Quest II (USA).nes\"\n  F161A5D8, NTSC, 4, 0, 1, 32, 0, false, Horizontal, \"Rockman 4 - Aratanaru Yabou!! (Japan).nes\"\n  F17486DF, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Super Chinese 3 (Japan).nes\"\n  F181C021, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Legacy of the Wizard (USA).nes\"\n  F184EB2D, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Solomon's Key 2 (Europe).nes\"\n  F19A11AF, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Sted - Iseki Wakusei no Yabou (Japan).nes\"\n  F1C76AED, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Game Party (Japan).nes\"\n  F1E6B576, NTSC, 86, 0, 0, 8, 0, false, Vertical, \"Moero!! Pro Yakyuu (Japan) (Rev 3).nes\"\n  F1FED9B8, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Flying Dragon - The Secret Scroll (USA).nes\"\n  F2096D9C, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"California Raisins - The Grape Escape (USA) (Proto 1).nes\"\n  F24D4F03, NTSC, 177, 0, 0, 64, 0, true, Horizontal, \"Shang Gu Shen Jian (China) (Unl).nes\"\n  F2594374, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Matendouji (Japan).nes\"\n  F283CF58, PAL, 3, 0, 4, 2, 0, false, Horizontal, \"Colorful Dragon (Asia) (PAL) (Unl).nes\"\n  F2CE3641, NTSC, 68, 0, 0, 8, 0, false, Horizontal, \"After Burner (Japan).nes\"\n  F2FC8212, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Bomber Man (Japan).nes\"\n  F304F1B9, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Archon (USA).nes\"\n  F31D36A3, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Home Alone (USA) (Rev A).nes\"\n  F31DCC15, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Summer Carnival '92 - Recca (Japan).nes\"\n  F32748A1, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Chiyonofuji no Ooichou (Japan).nes\"\n  F3623561, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Zoids Mokushiroku (Japan).nes\"\n  F37BEFD5, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Blue Marlin, The (USA).nes\"\n  F3808245, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"WWF Wrestlemania Challenge (Japan).nes\"\n  F3841DCD, NTSC, 79, 0, 4, 2, 0, false, Horizontal, \"F15 City War (USA) (v1.1) (Unl).nes\"\n  F3F1269D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Captain Tsubasa (Japan).nes\"\n  F41ADD60, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Sanma no Mei Tantei (Japan).nes\"\n  F42B0DBD, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Karate Champ (USA) (Rev A).nes\"\n  F450DB3A, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Elnark no Zaihou (Japan).nes\"\n  F4615036, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Hoops (USA).nes\"\n  F46EF39A, PAL, 37, 0, 0, 16, 0, false, Horizontal, \"Super Mario Bros. + Tetris + Nintendo World Cup (Europe) (Rev A).nes\"\n  F471827D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Addams Family, The - Uncle Fester's Quest (USA) (Beta).nes\"\n  F4B70BFE, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Maniac Mansion (France).nes\"\n  F4DD5BA5, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Puzslot (Japan).nes\"\n  F4DFDB14, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"KlashBall (USA).nes\"\n  F4E5DF0E, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Kawa no Nushi Tsuri (Japan).nes\"\n  F518DD58, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Captain Skyhawk (USA).nes\"\n  F51A7F46, NTSC, 16, 0, 1, 16, 0, false, Horizontal, \"Datach - Yu Yu Hakusho - Bakutou Ankoku Bujutsukai (Japan).nes\"\n  F532F09A, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Golgo 13 - Top Secret Episode (USA).nes\"\n  F540677B, NTSC, 5, 0, 32, 32, 0, true, Horizontal, \"Nobunaga no Yabou - Bushou Fuuun Roku (Japan).nes\"\n  F54B34BD, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Kings of the Beach - Professional Beach Volleyball (USA).nes\"\n  F56135C0, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Nishimura Kyoutarou Mystery - Super Express Satsujin Jiken (Japan).nes\"\n  F568A7A4, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Family School (Japan).nes\"\n  F59CFC3D, PAL, 1, 0, 1, 16, 0, true, Horizontal, \"Maniac Mansion (Europe).nes\"\n  F5A1B8FB, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Street Gangs (Europe).nes\"\n  F5B2AFCA, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Maniac Mansion (Spain).nes\"\n  F5CEEF8F, NTSC, 206, 0, 8, 8, 0, false, Horizontal, \"Family Mahjong (Japan) (Rev A).nes\"\n  F5F435B1, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Binary Land (Japan).nes\"\n  F6035030, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Contra (USA).nes\"\n  F6139EE9, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Labyrinth (Japan).nes\"\n  F613A8F9, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"World Games (USA).nes\"\n  F6271A51, NTSC, 25, 0, 0, 8, 0, false, Horizontal, \"Racer Mini Yonku - Japan Cup (Japan).nes\"\n  F62B0327, NTSC, 2, 0, 1, 16, 0, false, Vertical, \"Big Nose and the Witchdoctor (USA) (Beta) (Unl).nes\"\n  F635C594, NTSC, 184, 0, 0, 2, 0, false, Vertical, \"Kanshakudama Nage Kantarou no Toukaidou Gojuusan Tsugi (Japan).nes\"\n  F64CB545, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Chuugoku Senseijutsu (Japan).nes\"\n  F651398D, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Wrath of the Black Manta (USA).nes\"\n  F66EC512, NTSC, 4, 0, 8, 8, 0, false, Horizontal, \"Yoshi no Cookie (Japan).nes\"\n  F6751D3D, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Flying Hero (Japan).nes\"\n  F6898A59, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"James Bond Jr (USA).nes\"\n  F699EE7E, NTSC, 68, 0, 0, 8, 0, false, Horizontal, \"After Burner (USA) (Unl).nes\"\n  F6A9CB75, NTSC, 168, 0, 0, 4, 0, true, Horizontal, \"Racermate Challenge II (USA) (v9.03.128) (Unl).nes\"\n  F6AB12A2, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Rescue - The Embassy Mission (USA) (Beta).nes\"\n  F6B9799C, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"NES Open Tournament Golf (USA).nes\"\n  F714FAE3, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Mahjong Taikai (Japan).nes\"\n  F71E7EDD, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Final Fantasy (Japan) (Rev B).nes\"\n  F732C8FD, NTSC, 71, 0, 1, 16, 0, false, Vertical, \"Fantastic Adventures of Dizzy, The (USA) (Aladdin Compact Cartridge) (Unl).nes\"\n  F74DFC91, NTSC, 1, 0, 1, 8, 0, false, Horizontal, \"Win, Lose or Draw (USA).nes\"\n  F7606810, NTSC, 0, 0, 1, 2, 0, true, Vertical, \"Family BASIC (Japan) (v2.0a).nes\"\n  F760F1CB, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Dough Boy (Japan).nes\"\n  F7762A20, NTSC, 4, 0, 4, 8, 0, false, Horizontal, \"Side Pocket (Japan).nes\"\n  F7893859, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Higemaru - Makai-jima - Nanatsu no Shima Daibouken (Japan).nes\"\n  F79A75D7, NTSC, 4, 0, 32, 16, 0, true, Horizontal, \"Wario's Woods (USA).nes\"\n  F7A9822E, NTSC, 0, 0, 16, 16, 0, false, Horizontal, \"Super Cartridge Ver 6 - 6 in 1 (Asia) (Unl).nes\"\n  F7B852E4, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Sanrio Cup - Pon Pon Volley (Japan).nes\"\n  F7D20181, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Ikari III (Japan).nes\"\n  F7E07B83, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Moulin Rouge Senki - Melville no Honoo (Japan).nes\"\n  F808AF60, NTSC, 184, 0, 0, 2, 0, false, Vertical, \"Atlantis no Nazo (Japan).nes\"\n  F80BDC50, NTSC, 33, 0, 0, 8, 0, false, Horizontal, \"Insector X (Japan).nes\"\n  F83E0D2D, PAL, 1, 0, 16, 8, 0, false, Vertical, \"Chip 'n Dale - Rescue Rangers 2 (Europe).nes\"\n  F85E264D, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Tetrastar - The Fighter (Japan).nes\"\n  F863D5BB, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Donkey Kong (Japan).nes\"\n  F885D931, NTSC, 1, 0, 16, 8, 0, true, Horizontal, \"Faria - Fuuin no Tsurugi (Japan).nes\"\n  F89300FB, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Mottomo Abunai Deka (Japan).nes\"\n  F8A713BE, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Hudson's Adventure Island (USA).nes\"\n  F8C1A690, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Famicom Doubutsu Seitai Zukan! - Katte ni Shirokuma - Mori o Sukue no Maki! (Japan).nes\"\n  F8C358D7, PAL, 0, 0, 2, 2, 0, false, Vertical, \"Millionaire (Asia) (PAL) (Unl).nes\"\n  F8D53171, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"8 Eyes (Japan).nes\"\n  F919795D, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Dragon's Lair (Europe).nes\"\n  F927FA43, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Blue Marlin, The (Japan).nes\"\n  F92BE3EC, NTSC, 64, 0, 0, 8, 0, false, Horizontal, \"Rolling Thunder (USA) (Unl).nes\"\n  F92BE7F2, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Flight of the Intruder (USA).nes\"\n  F96D07C8, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Moeru! Oniisan (Japan).nes\"\n  F989296C, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Batsu & Terry - Makyou no Tetsujin Race (Japan).nes\"\n  F99E37EB, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Chris Evert & Ivan Lendl in Top Players' Tennis (USA).nes\"\n  F9B4240F, NTSC, 5, 0, 16, 16, 0, true, Horizontal, \"Nobunaga no Yabou - Sengoku Gunyuu Den (Japan) (Rev A).nes\"\n  F9FC0700, PAL, 2, 0, 1, 8, 0, false, Horizontal, \"Hero Quest (Europe) (Proto).nes\"\n  FA014BA1, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Silent Service (Europe).nes\"\n  FA2A8A8B, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Nichibutsu Mahjong III - Mahjong G Men (Japan).nes\"\n  FA43146B, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Guardian Legend, The (USA).nes\"\n  FA434E09, NTSC, 1, 0, 1, 8, 0, true, Vertical, \"Bard's Tale, The - Tales of the Unknown (USA) (Beta 2).nes\"\n  FA6D4281, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Seikima II - Akuma no Gyakushuu! (Japan).nes\"\n  FA704C86, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Rainbow Islands - The Story of Bubble Bobble 2 (Japan).nes\"\n  FA73D3A2, PAL, 4, 0, 32, 8, 0, false, Horizontal, \"Days of Thunder (Europe).nes\"\n  FA74F656, NTSC, 4, 0, 32, 16, 0, false, Vertical, \"F-15 Strike Eagle (Italy).nes\"\n  FA7E02FA, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Hayauchi Super Igo (Japan).nes\"\n  FA7EE642, PAL, 1, 0, 1, 16, 0, false, Horizontal, \"Bionic Commando (Europe).nes\"\n  FB1C0551, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Monster Maker - 7 Tsu no Hihou (Japan).nes\"\n  FB2B6B10, NTSC, 178, 0, 0, 64, 0, true, Horizontal, \"Fan Kong Jing Ying (China) (Unl).nes\"\n  FB2F949F, NTSC, 112, 0, 0, 8, 0, false, Vertical, \"San Guo Zhi - Qun Xiong Zheng Ba (Asia) (Unl).nes\"\n  FB3439FC, NTSC, 0, 0, 32, 8, 0, false, Horizontal, \"Super Cartridge Ver 1 - 4 in 1 (Asia) (Unl).nes\"\n  FB69743A, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Aoki Ookami to Shiroki Mejika - Genghis Khan (Japan).nes\"\n  FB77099E, NTSC, 1, 0, 4, 8, 0, false, Vertical, \"Garfield - A Week of Garfield (Japan) (Sample).nes\"\n  FB8A9B80, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Airwolf (Japan).nes\"\n  FB98D46E, PAL, 0, 0, 1, 1, 0, false, Horizontal, \"Ice Climber (USA, Europe).nes\"\n  FBD48274, PAL, 4, 0, 16, 8, 0, false, Vertical, \"Felix the Cat (Europe).nes\"\n  FBDD0F1B, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Totsuzen! Macchoman (Japan) (Beta).nes\"\n  FBF8A785, NTSC, 7, 0, 1, 8, 0, false, Horizontal, \"Wheel of Fortune (USA).nes\"\n  FC00A282, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Ninja-kun - Majou no Bouken (Japan) (Rev 1).nes\"\n  FC2DA286, PAL, 3, 0, 4, 2, 0, false, Horizontal, \"Puzznic (Europe).nes\"\n  FC2F9B2D, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"McDonaldland (Europe).nes\"\n  FC3236D1, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Total Recall (Europe).nes\"\n  FC3E5C86, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Trojan (USA).nes\"\n  FC5026EE, PAL, 3, 0, 4, 2, 0, false, Horizontal, \"Battleship (Europe) (En,Fr,De,Es).nes\"\n  FC5783A7, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Chip 'n Dale - Rescue Rangers 2 (USA).nes\"\n  FC778215, NTSC, 0, 0, 8, 4, 0, false, Horizontal, \"Mahjong World, The - Ma Que Shi Jie (Asia) (NTSC) (Unl).nes\"\n  FC8DEBEF, NTSC, 152, 0, 0, 4, 0, false, Vertical, \"Arkanoid II (Japan) (Beta).nes\"\n  FCB13110, NTSC, 1, 0, 16, 8, 0, false, Horizontal, \"Golf Club - Birdy Rush (Japan).nes\"\n  FCB5CB1E, NTSC, 2, 0, 1, 8, 0, false, Horizontal, \"Puyo Puyo (Japan).nes\"\n  FCBF28B1, NTSC, 23, 0, 0, 8, 0, false, Horizontal, \"Crisis Force (Japan).nes\"\n  FCD772EB, PAL, 4, 0, 16, 8, 0, false, Horizontal, \"Star Wars (Europe).nes\"\n  FCDACA80, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Elevator Action (Japan).nes\"\n  FCE408A4, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Star Force (USA).nes\"\n  FCE71311, NTSC, 3, 0, 4, 2, 0, false, Horizontal, \"Stadium Events (USA).nes\"\n  FCEBCC5F, PAL, 0, 0, 1, 1, 0, false, Vertical, \"M82 Game Selectable Working Product Display (Europe).nes\"\n  FD21F54D, NTSC, 1, 0, 1, 16, 0, false, Horizontal, \"Robin Hood - Prince of Thieves (Spain).nes\"\n  FD45E9C1, NTSC, 1, 0, 1, 8, 0, true, Horizontal, \"Tetris 2 + Bombliss (Japan) (Rev A).nes\"\n  FD55DD33, NTSC, 112, 0, 0, 16, 0, false, Horizontal, \"Fighting Hero III (Asia) (Unl).nes\"\n  FD63E7AC, NTSC, 4, 0, 8, 8, 0, false, Vertical, \"R.B.I. Baseball 3 (USA) (Unl).nes\"\n  FD7E9A7E, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"Legend of Prince Valiant, The (Europe).nes\"\n  FD8D6C75, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"Loopz (USA).nes\"\n  FDB8AA9A, NTSC, 4, 0, 32, 8, 0, false, Horizontal, \"Juuryoku Soukou Metal Storm (Japan).nes\"\n  FDDF2135, NTSC, 4, 0, 32, 16, 0, false, Horizontal, \"Rockman 5 - Blues no Wana! (Japan).nes\"\n  FDE14CCE, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Volguard II (Japan).nes\"\n  FDE1C7ED, NTSC, 1, 0, 16, 16, 0, false, Horizontal, \"Sesame Street - Big Bird's Hide & Speak (USA).nes\"\n  FDF4569B, NTSC, 1, 0, 4, 2, 0, false, Horizontal, \"Snake Rattle n Roll (USA).nes\"\n  FDFF80D5, PAL, 1, 0, 2, 2, 0, false, Horizontal, \"Tetris (Europe).nes\"\n  FE08D602, PAL, 1, 0, 16, 8, 0, false, Horizontal, \"TaleSpin (Europe).nes\"\n  FE18E6B6, NTSC, 0, 0, 1, 2, 0, false, Vertical, \"Bokosuka Wars (Japan).nes\"\n  FE3488D1, NTSC, 5, 0, 16, 32, 0, true, Horizontal, \"Daikoukai Jidai (Japan).nes\"\n  FE364BE5, NTSC, 1, 0, 1, 16, 0, true, Horizontal, \"Deep Dungeon IV - Kuro no Youjutsushi (Japan).nes\"\n  FE387FE5, NTSC, 32, 0, 0, 8, 0, false, Horizontal, \"Perman (Japan).nes\"\n  FE4ED42B, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Max Warrior - Wakusei Kaigenrei (Japan).nes\"\n  FE84FCAC, NTSC, 4, 0, 16, 8, 0, false, Vertical, \"Monster in My Pocket (USA) (Beta).nes\"\n  FE907015, PAL, 2, 0, 1, 8, 0, false, Vertical, \"Guardian Legend, The (Europe).nes\"\n  FE99BBED, NTSC, 4, 0, 16, 8, 0, false, Horizontal, \"Soreike! Anpanman - Minna de Hiking Game! (Japan).nes\"\n  FE9FE4DA, NTSC, 3, 0, 4, 2, 0, false, Vertical, \"Nagagutsu o Haita Neko - Sekai Isshuu 80 Nichi Daibouken (Japan) (Beta).nes\"\n  FF1CEFAA, NTSC, 0, 0, 1, 2, 0, false, Horizontal, \"Duck Maze (Australia) (Unl).nes\"\n  FF24D794, NTSC, 0, 0, 1, 1, 0, false, Vertical, \"Hogan's Alley (World).nes\"\n  FF53D73E, NTSC, 2, 0, 1, 8, 0, false, Vertical, \"DuckTales (USA) (Beta).nes\"\n  FFD9DB04, NTSC, 0, 0, 1, 1, 0, false, Horizontal, \"Honshougi - Naitou 9 Dan Shougi Hiden (Japan).nes\"\n  FFE8507E, NTSC, 4, 0, 32, 8, 0, true, Horizontal, \"Nakayoshi to Issho (Japan).nes\"\n  FFFDC310, NTSC, 79, 0, 8, 2, 0, false, Horizontal, \"Ultimate League Soccer (Italy) (Unl).nes\"\n"
  },
  {
    "path": "tetanes-core/src/action.rs",
    "content": "//! An [`Action`] is an enumerated list of possible state changes to [`ControlDeck`].\n//!\n//! It allows for event handling and test abstractions such as being able to map a custom keybind\n//! to a given state change.\n//!\n//! [`ControlDeck`]: crate::control_deck::ControlDeck\n\nuse crate::{\n    apu::Channel,\n    common::{NesRegion, ResetKind},\n    input::{FourPlayer, JoypadBtn, Player},\n    mapper::MapperRevision,\n    video::VideoFilter,\n};\nuse serde::{Deserialize, Serialize};\n\n/// A user action that maps to a possible state change on [`ControlDeck`]. Used for event\n/// handling and test abstractions.\n///\n/// [`ControlDeck`]: crate::control_deck::ControlDeck\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum Action {\n    /// Reset the [`ControlDeck`](crate::control_deck::ControlDeck).\n    Reset(ResetKind),\n    /// Update the [`Joypad`](crate::input::Joypad) button state.\n    Joypad((Player, JoypadBtn)),\n    /// Toggle the [`Zapper`](crate::input::Zapper) connected state.\n    ToggleZapperConnected,\n    /// Update the [`Zapper`](crate::input::Zapper) aim position.\n    ZapperAim((u16, u16)),\n    /// Update the [`Zapper`](crate::input::Zapper) aim position to offscreen.\n    ZapperAimOffscreen,\n    /// Trigger the [`Zapper`](crate::input::Zapper) trigger.\n    ZapperTrigger,\n    /// Set [`FourPlayer`] mode.\n    FourPlayer(FourPlayer),\n    /// Set the slot to use for save states.\n    SetSaveSlot(u8),\n    /// Save the current state to the currently set save slot.\n    SaveState,\n    /// Load the current state from the currently set save slot.\n    LoadState,\n    /// Toggle the [`Apu`](crate::apu::Apu) [`Channel`].\n    ToggleApuChannel(Channel),\n    /// Set the [`MapperRevision`].\n    MapperRevision(MapperRevision),\n    /// Set the [`NesRegion`].\n    SetNesRegion(NesRegion),\n    /// Set the [`VideoFilter`].\n    SetVideoFilter(VideoFilter),\n}\n"
  },
  {
    "path": "tetanes-core/src/apu/dmc.rs",
    "content": "//! APU DMC (Delta Modulation Channel) implementation.\n//!\n//! See: <https://www.nesdev.org/wiki/APU_DMC>\n\nuse crate::{\n    apu::timer::{Timer, TimerCycle},\n    common::{Clock, NesRegion, Regional, Reset, ResetKind, Sample},\n};\nuse serde::{Deserialize, Serialize};\nuse tracing::trace;\n\n/// APU DMC (Delta Modulation Channel) provides sample playback.\n///\n/// See: <https://www.nesdev.org/wiki/APU_DMC>\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Dmc {\n    pub region: NesRegion,\n    pub timer: Timer,\n    pub force_silent: bool,\n    pub irq_enabled: bool,\n    pub irq_pending: bool,\n    pub dma_pending: bool,\n    pub loops: bool,\n    pub addr: u16,\n    pub sample_addr: u16,\n    pub bytes_remaining: u16,\n    pub sample_length: u16,\n    pub sample_buffer: u8,\n    pub buffer_empty: bool,\n    pub init: u8,\n    pub output_level: u8,\n    pub bits_remaining: u8,\n    pub shift: u8,\n    pub silence: bool,\n    pub should_clock: bool,\n}\n\nimpl Default for Dmc {\n    fn default() -> Self {\n        Self::new(NesRegion::default())\n    }\n}\n\nimpl Dmc {\n    const PERIOD_TABLE_NTSC: [u16; 16] = [\n        428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,\n    ];\n    const PERIOD_TABLE_PAL: [u16; 16] = [\n        398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,\n    ];\n\n    pub const fn new(region: NesRegion) -> Self {\n        Self {\n            region,\n            timer: Timer::preload(Self::period(region, 0)),\n            force_silent: false,\n            irq_enabled: false,\n            irq_pending: false,\n            dma_pending: false,\n            loops: false,\n            addr: 0xC000,\n            sample_addr: 0x0000,\n            bytes_remaining: 0x0000,\n            sample_length: 0x0001,\n            sample_buffer: 0x00,\n            buffer_empty: true,\n            init: 0,\n            output_level: 0x00,\n            bits_remaining: 0x08,\n            shift: 0x00,\n            silence: true,\n            should_clock: false,\n        }\n    }\n\n    #[must_use]\n    pub const fn silent(&self) -> bool {\n        self.force_silent\n    }\n\n    pub const fn set_silent(&mut self, silent: bool) {\n        self.force_silent = silent;\n    }\n\n    #[cold]\n    #[must_use]\n    pub fn irq_pending_in(&self, cycles_to_run: u32) -> bool {\n        if self.irq_enabled && self.bytes_remaining > 0 {\n            let cycles_to_empty = (u16::from(self.bits_remaining) + (self.bytes_remaining - 1) * 8)\n                * self.timer.period;\n            cycles_to_run >= u32::from(cycles_to_empty)\n        } else {\n            false\n        }\n    }\n\n    #[must_use]\n    pub const fn dma_addr(&self) -> u16 {\n        self.addr\n    }\n\n    fn init_sample(&mut self) {\n        self.addr = self.sample_addr;\n        self.bytes_remaining = self.sample_length;\n        trace!(\n            \"APU DMC sample started. bytes remaining: {}\",\n            self.bytes_remaining\n        );\n        self.should_clock = self.bytes_remaining > 0;\n    }\n\n    /// Load a sample into the DMC buffer - returns `true` if an IRQ is triggered.\n    pub fn load_buffer(&mut self, val: u8) {\n        if self.bytes_remaining > 0 {\n            self.sample_buffer = val;\n            self.buffer_empty = false;\n            if self.addr == 0xFFFF {\n                self.addr = 0x8000;\n            } else {\n                self.addr += 1;\n            }\n            self.bytes_remaining -= 1;\n            trace!(\"APU DMC bytes remaining: {}\", self.bytes_remaining);\n            if self.bytes_remaining == 0 {\n                self.should_clock = false;\n                if self.loops {\n                    self.init_sample();\n                } else if self.irq_enabled {\n                    self.irq_pending = true;\n                }\n            }\n        }\n    }\n\n    const fn period(region: NesRegion, val: u8) -> u16 {\n        let index = (val & 0x0F) as usize;\n        match region {\n            NesRegion::Auto | NesRegion::Ntsc | NesRegion::Dendy => {\n                Self::PERIOD_TABLE_NTSC[index] - 1\n            }\n            NesRegion::Pal => Self::PERIOD_TABLE_PAL[index] - 1,\n        }\n    }\n\n    /// $4010 DMC timer\n    pub const fn write_timer(&mut self, val: u8) {\n        self.irq_enabled = val & 0x80 == 0x80;\n        self.loops = val & 0x40 == 0x40;\n        self.timer.period = Self::period(self.region, val);\n        if !self.irq_enabled {\n            self.irq_pending = false;\n        }\n    }\n\n    /// $4011 DMC output\n    pub const fn write_output(&mut self, val: u8) {\n        self.output_level = val & 0x7F;\n    }\n\n    /// $4012 DMC addr load\n    pub fn write_addr(&mut self, val: u8) {\n        self.sample_addr = 0xC000 | (u16::from(val) << 6);\n    }\n\n    /// $4013 DMC length\n    pub fn write_length(&mut self, val: u8) {\n        self.sample_length = (u16::from(val) << 4) | 1;\n    }\n\n    /// $4015 WRITE\n    pub fn set_enabled(&mut self, enabled: bool, cycle: u32) {\n        if !enabled {\n            self.bytes_remaining = 0;\n            self.should_clock = false;\n        } else if self.bytes_remaining == 0 {\n            self.init_sample();\n            // Delay a number of cycles based on even/odd cycle\n            self.init = if cycle & 0x01 == 0x00 { 2 } else { 3 };\n        }\n    }\n\n    #[inline(always)]\n    pub fn should_clock(&mut self) -> bool {\n        if self.init > 0 {\n            self.init -= 1;\n            if self.init == 0 && self.buffer_empty && self.bytes_remaining > 0 {\n                trace!(\"APU DMC DMA pending\");\n                self.dma_pending = true;\n            }\n        }\n        self.should_clock\n    }\n}\n\nimpl Sample for Dmc {\n    fn output(&self) -> f32 {\n        if self.silent() {\n            0.0\n        } else {\n            f32::from(self.output_level)\n        }\n    }\n}\n\nimpl TimerCycle for Dmc {\n    fn cycle(&self) -> u32 {\n        self.timer.cycle\n    }\n}\n\nimpl Clock for Dmc {\n    //                          Timer\n    //                            |\n    //                            v\n    // Reader ---> Buffer ---> Shifter ---> Output level ---> (to the mixer)\n    fn clock(&mut self) {\n        if self.timer.tick() {\n            if !self.silence {\n                // Update output level but clamp to 0..=127 range\n                if self.shift & 0x01 == 0x01 {\n                    if self.output_level <= 125 {\n                        self.output_level += 2;\n                    }\n                } else if self.output_level >= 2 {\n                    self.output_level -= 2;\n                }\n                self.shift >>= 1;\n            }\n\n            if self.bits_remaining > 0 {\n                self.bits_remaining -= 1;\n            }\n            trace!(\"APU DMC bits remaining: {}\", self.bits_remaining);\n\n            if self.bits_remaining == 0 {\n                self.bits_remaining = 8;\n                self.silence = self.buffer_empty;\n                if !self.buffer_empty {\n                    self.shift = self.sample_buffer;\n                    self.buffer_empty = true;\n                    if self.bytes_remaining > 0 {\n                        trace!(\"APU DMC DMA pending\");\n                        self.dma_pending = true;\n                    }\n                }\n            }\n        }\n    }\n}\n\nimpl Regional for Dmc {\n    fn region(&self) -> NesRegion {\n        self.region\n    }\n\n    fn set_region(&mut self, region: NesRegion) {\n        self.region = region;\n        self.timer.period = Self::period(region, 0);\n    }\n}\n\nimpl Reset for Dmc {\n    fn reset(&mut self, kind: ResetKind) {\n        self.timer.reset(kind);\n        self.timer.period = Self::period(self.region, 0);\n        self.timer.reload();\n        self.timer.cycle += 1; // FIXME: Startup timing is slightly wrong, DMA tests fail with the\n        // default\n        if let ResetKind::Hard = kind {\n            self.sample_addr = 0xC000;\n            self.sample_length = 1;\n        }\n        self.irq_enabled = false;\n        self.irq_pending = false;\n        self.dma_pending = false;\n        self.loops = false;\n        self.addr = 0x0000;\n        self.bytes_remaining = 0;\n        self.sample_buffer = 0x00;\n        self.buffer_empty = true;\n        self.output_level = 0x00;\n        self.bits_remaining = 0x08;\n        self.shift = 0x00;\n        self.silence = true;\n        self.should_clock = false;\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/apu/envelope.rs",
    "content": "//! APU Envelope implementation.\n//!\n//! See: <https://www.nesdev.org/wiki/APU_Envelope>\n\nuse crate::common::{Clock, Reset, ResetKind};\nuse serde::{Deserialize, Serialize};\n\n/// APU Envelope provides volume control for APU waveform channels.\n///\n/// See: <https://www.nesdev.org/wiki/APU_Envelope>\n#[derive(Default, Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Envelope {\n    pub start: bool,\n    pub constant_volume: bool,\n    pub volume: u8,\n    pub divider: u8,\n    pub counter: u8,\n    pub loops: bool,\n}\n\nimpl Envelope {\n    pub const fn new() -> Self {\n        Self {\n            start: false,\n            constant_volume: false,\n            volume: 0,\n            divider: 0,\n            counter: 0,\n            loops: false,\n        }\n    }\n\n    #[inline]\n    #[must_use]\n    pub const fn volume(&self) -> u8 {\n        if self.constant_volume {\n            self.volume\n        } else {\n            self.counter\n        }\n    }\n\n    #[inline]\n    pub const fn restart(&mut self) {\n        self.start = true;\n    }\n\n    /// $4000/$4004/$400C Envelope control\n    #[inline]\n    pub const fn write_ctrl(&mut self, val: u8) {\n        self.loops = (val & 0x20) == 0x20; // D5\n        self.constant_volume = (val & 0x10) == 0x10; // D4\n        self.volume = val & 0x0F; // D3..D0\n    }\n}\n\nimpl Clock for Envelope {\n    fn clock(&mut self) {\n        if self.start {\n            self.start = false;\n            self.counter = 15;\n            self.divider = self.volume;\n        } else if self.divider > 0 {\n            self.divider -= 1;\n        } else {\n            self.divider = self.volume;\n            if self.counter > 0 {\n                self.counter -= 1;\n            } else if self.loops {\n                self.counter = 15;\n            }\n        }\n    }\n}\n\nimpl Reset for Envelope {\n    fn reset(&mut self, _kind: ResetKind) {\n        self.start = false;\n        self.constant_volume = false;\n        self.volume = 0;\n        self.divider = 0;\n        self.counter = 0;\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/apu/filter.rs",
    "content": "//! Digital filters for the [`Apu`](crate::apu::Apu).\n//!\n//! See <https://www.nesdev.org/wiki/APU_Mixer>\n\nuse crate::{\n    common::{NesRegion, Sample},\n    cpu::Cpu,\n};\nuse serde::{Deserialize, Serialize};\nuse std::f32::consts::{PI, TAU};\n\n/// A trait for audio processing that consumes samples.\npub trait Consume {\n    fn consume(&mut self, sample: f32);\n}\n\n/// Represents a digital filter with certain characteristics.\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub enum FilterKind {\n    Identity,\n    HighPass,\n    LowPass,\n}\n\n/// An infinite impulse response (IIR) filter.\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Iir {\n    pub alpha: f32,\n    pub prev_output: f32,\n    pub prev_input: f32,\n    pub delta: f32,\n    pub kind: FilterKind,\n}\n\nimpl Iir {\n    pub const fn identity() -> Self {\n        Self {\n            alpha: 0.0,\n            prev_output: 0.0,\n            prev_input: 0.0,\n            delta: 0.0,\n            kind: FilterKind::Identity,\n        }\n    }\n\n    pub fn high_pass(sample_rate: f32, cutoff: f32) -> Self {\n        let period = 1.0 / sample_rate;\n        let cutoff_period = 1.0 / cutoff;\n        let alpha = cutoff_period / (cutoff_period + period);\n        Self {\n            alpha,\n            prev_output: 0.0,\n            prev_input: 0.0,\n            delta: 0.0,\n            kind: FilterKind::HighPass,\n        }\n    }\n\n    pub fn low_pass(sample_rate: f32, cutoff: f32) -> Self {\n        let period = 1.0 / sample_rate;\n        let cutoff_period = 1.0 / (TAU * cutoff);\n        let alpha = cutoff_period / (cutoff_period + period);\n        Self {\n            alpha,\n            prev_output: 0.0,\n            prev_input: 0.0,\n            delta: 0.0,\n            kind: FilterKind::LowPass,\n        }\n    }\n}\n\nimpl Consume for Iir {\n    fn consume(&mut self, sample: f32) {\n        self.prev_output = self.output();\n        self.delta = sample - self.prev_input;\n        self.prev_input = sample;\n    }\n}\n\nimpl Sample for Iir {\n    fn output(&self) -> f32 {\n        match self.kind {\n            FilterKind::Identity => self.prev_input,\n            FilterKind::HighPass => self.alpha * self.prev_output + self.alpha * self.delta,\n            FilterKind::LowPass => self.prev_output + self.alpha * self.delta,\n        }\n    }\n}\n\n/// A finite impulse response (FIR) filter.\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Fir {\n    pub kernel: Box<[f32]>,\n    pub inputs: Box<[f32]>,\n    pub input_index: usize,\n    pub kind: FilterKind,\n}\n\nimpl Fir {\n    pub fn low_pass(sample_rate: f32, cutoff: f32, window_size: usize) -> Self {\n        Self {\n            kernel: windowed_sinc_kernel(sample_rate, cutoff, window_size),\n            inputs: vec![0.0; window_size + 1].into(),\n            input_index: 0,\n            kind: FilterKind::LowPass,\n        }\n    }\n}\n\nimpl Consume for Fir {\n    fn consume(&mut self, sample: f32) {\n        self.inputs[self.input_index] = sample;\n        self.input_index += 1;\n        if self.input_index >= self.inputs.len() {\n            self.input_index = 0;\n        }\n    }\n}\n\nimpl Sample for Fir {\n    fn output(&self) -> f32 {\n        let kernel = &self.kernel[..];\n        let inputs = &self.inputs[..];\n        let idx = self.input_index;\n\n        let mut sum = 0f32;\n\n        // input_index..inputs.len()\n        let end = (inputs.len() - idx).min(kernel.len());\n        for i in 0..end {\n            sum = kernel[i].mul_add(inputs[i + idx], sum);\n        }\n\n        // 0..input_index\n        for i in 0..idx {\n            sum = kernel[end + i].mul_add(inputs[i], sum);\n        }\n\n        sum\n    }\n}\n\n/// Generate a windowed sinc kernel.\npub fn windowed_sinc_kernel(sample_rate: f32, cutoff: f32, window_size: usize) -> Box<[f32]> {\n    fn blackman_window(index: usize, window_size: usize) -> f32 {\n        let i = index as f32;\n        let m = window_size as f32;\n        0.42 - 0.5 * ((TAU * i) / m).cos() + 0.08 * ((2.0 * TAU * i) / m).cos()\n    }\n\n    fn sinc(index: usize, fc: f32, window_size: usize) -> f32 {\n        let i = index as f32;\n        let m = window_size as f32;\n        let shifted_index = i - (m / 2.0);\n        if index == (window_size / 2) {\n            TAU * fc\n        } else {\n            (TAU * fc * shifted_index).sin() / shifted_index\n        }\n    }\n\n    fn normalize(input: Box<[f32]>) -> Box<[f32]> {\n        let sum: f32 = input.iter().sum();\n        input.into_iter().map(|x| x / sum).collect()\n    }\n\n    let fc = cutoff / sample_rate;\n    let mut kernel = Vec::with_capacity(window_size);\n    for i in 0..=window_size {\n        kernel.push(sinc(i, fc, window_size) * blackman_window(i, window_size));\n    }\n    normalize(kernel.into())\n}\n\n/// Represents a digital audio filter.\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub enum Filter {\n    Iir(Iir),\n    Fir(Fir),\n}\n\nimpl Consume for Filter {\n    fn consume(&mut self, sample: f32) {\n        match self {\n            Filter::Iir(iir) => iir.consume(sample),\n            Filter::Fir(fir) => fir.consume(sample),\n        }\n    }\n}\n\nimpl Sample for Filter {\n    fn output(&self) -> f32 {\n        match self {\n            Filter::Iir(iir) => iir.output(),\n            Filter::Fir(fir) => fir.output(),\n        }\n    }\n}\n\nimpl From<Iir> for Filter {\n    fn from(filter: Iir) -> Self {\n        Self::Iir(filter)\n    }\n}\n\nimpl From<Fir> for Filter {\n    fn from(filter: Fir) -> Self {\n        Self::Fir(filter)\n    }\n}\n\n/// Represents a filter with a given sampling period.\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct SampledFilter {\n    pub filter: Filter,\n    pub sample_period: f32,\n    pub period_counter: f32,\n}\n\nimpl SampledFilter {\n    pub fn new(filter: impl Into<Filter>, sample_rate: f32) -> Self {\n        Self {\n            filter: filter.into(),\n            sample_period: 1.0 / sample_rate,\n            period_counter: 0.0,\n        }\n    }\n}\n\n/// Represents a chain of filters for a given [`NesRegion`].\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct FilterChain {\n    pub region: NesRegion,\n    pub dt: f32,\n    pub filters: [SampledFilter; 6],\n}\n\nimpl FilterChain {\n    pub fn new(region: NesRegion, output_rate: f32) -> Self {\n        let clock_rate = Cpu::region_clock_rate(region);\n        let intermediate_sample_rate = output_rate * 2.0 + (PI / 32.0);\n        let intermediate_cutoff = output_rate * 0.4;\n\n        let filters = [\n            SampledFilter::new(Iir::identity(), 1.0),\n            SampledFilter::new(Iir::low_pass(clock_rate, intermediate_cutoff), clock_rate),\n            // first-order high-pass filter at 90 Hz\n            SampledFilter::new(\n                Iir::high_pass(intermediate_sample_rate, 90.0),\n                intermediate_sample_rate,\n            ),\n            // first-order high-pass filter at 440 Hz\n            SampledFilter::new(\n                Iir::high_pass(intermediate_sample_rate, 440.0),\n                intermediate_sample_rate,\n            ),\n            // first-order low-pass filter at 14 kHz\n            SampledFilter::new(\n                Iir::low_pass(intermediate_sample_rate, 14000.0),\n                intermediate_sample_rate,\n            ),\n            // TODO: Support famicom filter selection\n            // // first-order high-pass filter at 37 Hz\n            // filters.push(SampledFilter::new(\n            //     Iir::high_pass(intermediate_sample_rate, 37.0),\n            //     intermediate_sample_rate,\n            // ));\n            // high-quality low-pass filter\n            {\n                let window_size = 160;\n                let intermediate_cutoff = output_rate * 0.45;\n                SampledFilter::new(\n                    Fir::low_pass(intermediate_sample_rate, intermediate_cutoff, window_size),\n                    intermediate_sample_rate,\n                )\n            },\n        ];\n\n        Self {\n            region,\n            dt: 1.0 / clock_rate,\n            filters,\n        }\n    }\n}\n\nimpl Consume for FilterChain {\n    fn consume(&mut self, sample: f32) {\n        // Add sample to identity filter\n        self.filters[0].filter.consume(sample);\n        for i in 1..self.filters.len() {\n            let prev = i - 1;\n            let current = i;\n            while self.filters[current].period_counter >= self.filters[current].sample_period {\n                self.filters[current].period_counter -= self.filters[current].sample_period;\n                let prev_output = self.filters[prev].filter.output();\n                self.filters[current].filter.consume(prev_output);\n            }\n            self.filters[current].period_counter += self.dt;\n        }\n    }\n}\n\nimpl Sample for FilterChain {\n    fn output(&self) -> f32 {\n        self.filters.last().map_or(0.0, |f| f.filter.output())\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/apu/frame_counter.rs",
    "content": "//! The APU Frame Counter implementation.\n//!\n//! See: <https://www.nesdev.org/wiki/APU_Frame_Counter>\n\nuse crate::common::{NesRegion, Reset, ResetKind};\nuse serde::{Deserialize, Serialize};\nuse tracing::trace;\n\n/// The APU Frame Counter generates a low-frequency clock for each APU channel.\n///\n/// See: <https://www.nesdev.org/wiki/APU_Frame_Counter>\n#[derive(Default, Debug, Clone, Serialize, Deserialize)]\npub struct FrameCounter {\n    pub region: NesRegion,\n    pub step_cycles: [u32; 6],\n    pub step: usize,\n    pub mode: u8,\n    pub write_buffer: Option<u8>,\n    pub write_delay: u8,\n    pub block_counter: u8,\n    pub cycle: u32,\n    pub inhibit_irq: bool, // Set by $4017 D6\n    pub irq_pending: bool,\n}\n\n/// The Frame Counter clock type.\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum FrameType {\n    #[default]\n    None,\n    Quarter,\n    Half,\n}\n\nimpl FrameCounter {\n    const STEP4_CYCLES_NTSC: [u32; 6] = [7457, 14913, 22371, 29828, 29829, 29830];\n    const STEP5_CYCLES_NTSC: [u32; 6] = [7457, 14913, 22371, 29829, 37281, 37282];\n    const STEP4_CYCLES_PAL: [u32; 6] = [8313, 16627, 24939, 33252, 33253, 33254];\n    const STEP5_CYCLES_PAL: [u32; 6] = [8313, 16627, 24939, 33253, 41565, 41566];\n\n    const FRAME_TYPE: [FrameType; 6] = [\n        FrameType::Quarter,\n        FrameType::Half,\n        FrameType::Quarter,\n        FrameType::None,\n        FrameType::Half,\n        FrameType::None,\n    ];\n\n    pub const fn new(region: NesRegion) -> Self {\n        let mode = 0;\n        let step_cycles = Self::step_cycles(mode, region);\n        Self {\n            region,\n            step_cycles,\n            step: 0,\n            mode,\n            write_buffer: None,\n            write_delay: 0,\n            block_counter: 0,\n            cycle: 0,\n            inhibit_irq: false,\n            irq_pending: false,\n        }\n    }\n\n    pub const fn set_region(&mut self, region: NesRegion) {\n        self.region = region;\n        self.step_cycles = Self::step_cycles(self.mode, region);\n    }\n\n    const fn step_cycles(mode: u8, region: NesRegion) -> [u32; 6] {\n        match (mode, region) {\n            (0, NesRegion::Auto | NesRegion::Ntsc | NesRegion::Dendy) => Self::STEP4_CYCLES_NTSC,\n            (0, NesRegion::Pal) => Self::STEP4_CYCLES_PAL,\n            (_, NesRegion::Auto | NesRegion::Ntsc | NesRegion::Dendy) => Self::STEP5_CYCLES_NTSC,\n            (_, NesRegion::Pal) => Self::STEP5_CYCLES_PAL,\n        }\n    }\n\n    /// On write to $4017\n    pub fn write(&mut self, val: u8, cycle: u32) {\n        self.write_buffer = Some(val);\n        // Writes occurring on odd clocks are delayed\n        self.write_delay = if cycle & 0x01 == 0x01 { 4 } else { 3 };\n        trace!(\"APU $4017 write delay cycles: {}\", self.write_delay);\n        self.inhibit_irq = val & 0x40 == 0x40; // D6\n        if self.inhibit_irq {\n            trace!(\"APU Frame Counter IRQ inhibit\");\n            self.irq_pending = false;\n        }\n    }\n\n    #[inline(always)]\n    pub const fn should_clock(&mut self, cycles: u32) -> bool {\n        self.block_counter > 0\n            || self.write_buffer.is_some()\n            || (self.cycle + cycles) >= (self.step_cycles[self.step] - 1)\n    }\n\n    // mode 0: 4-step  effective rate (approx)\n    // ---------------------------------------\n    // - - - f f f      60 Hz\n    // - l - - l -     120 Hz\n    // e e e - e -     240 Hz\n    //\n    // mode 1: 5-step  effective rate (approx)\n    // ---------------------------------------\n    // - - - - - -     (interrupt flag never set)\n    // - l - - l -     96 Hz\n    // e e e - e -     192 Hz\n    pub fn clock_with(&mut self, cycles: u32, mut on_clock: impl FnMut(FrameType)) -> u32 {\n        let mut cycles_ran = 0;\n        let step_cycles = self.step_cycles[self.step];\n        if self.cycle + cycles >= step_cycles {\n            if !self.inhibit_irq && self.mode == 0 && self.step >= 3 {\n                trace!(\n                    \"APU Frame Counter IRQ pending - cycles: {} >= {step_cycles}\",\n                    self.cycle + cycles\n                );\n                self.irq_pending = true;\n            }\n\n            let ty = Self::FRAME_TYPE[self.step];\n            if ty != FrameType::None && self.block_counter == 0 {\n                on_clock(ty);\n                // Do not allow writes to $4017 to clock for the next cycle (odd + following even\n                // cycle)\n                self.block_counter = 2;\n            }\n\n            if step_cycles >= self.cycle {\n                cycles_ran = step_cycles - self.cycle;\n            }\n\n            self.step += 1;\n            if self.step == 6 {\n                trace!(\n                    \"APU Frame Counter total cycles: {}\",\n                    self.cycle + cycles_ran\n                );\n                self.step = 0;\n                self.cycle = 0;\n            } else {\n                self.cycle += cycles_ran;\n            }\n        } else {\n            cycles_ran = cycles;\n            self.cycle += cycles_ran;\n        }\n\n        if let Some(val) = self.write_buffer {\n            self.write_delay -= 1;\n            if self.write_delay == 0 {\n                self.mode = if val & 0x80 == 0x80 { 1 } else { 0 };\n                self.step_cycles = Self::step_cycles(self.mode, self.region);\n                self.step = 0;\n                self.cycle = 0;\n                self.write_buffer = None;\n                if self.mode == 1 && self.block_counter == 0 {\n                    // Writing to $4017 with bit 7 set will immediately generate a quarter/half frame\n                    on_clock(FrameType::Half);\n                    self.block_counter = 2;\n                }\n            }\n        }\n\n        if self.block_counter > 0 {\n            self.block_counter -= 1;\n        }\n\n        cycles_ran\n    }\n}\n\nimpl Reset for FrameCounter {\n    fn reset(&mut self, kind: ResetKind) {\n        self.cycle = 0;\n        if kind == ResetKind::Hard {\n            self.mode = 0;\n            self.step_cycles = Self::step_cycles(self.mode, self.region);\n            // After reset, APU acts as if $4017 was written 9-12 clocks before first instruction,\n            // Reset acts as if $00 was written to $4017\n            self.write(0x00, 0);\n            self.write_delay -= 1; // FIXME: Startup timing is slightly wrong, reset_timing fails\n            // with the default\n        }\n        self.step = 0;\n        self.block_counter = 0;\n        self.irq_pending = false;\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/apu/length_counter.rs",
    "content": "//! APU Length Counter implementation.\n//!\n//! See: <https://www.nesdev.org/wiki/APU_Length_Counter>\n\nuse crate::{\n    apu::Channel,\n    common::{Clock, Reset, ResetKind},\n};\nuse serde::{Deserialize, Serialize};\n\n/// APU Length Counter provides duration control for APU waveform channels.\n///\n/// See: <https://www.nesdev.org/wiki/APU_Length_Counter>\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct LengthCounter {\n    pub enabled: bool,\n    pub channel: Channel,\n    pub halt: bool,\n    pub new_halt: bool,\n    pub counter: u8, // Entry into LENGTH_TABLE\n    pub previous_counter: u8,\n    pub reload: u8,\n}\n\nimpl LengthCounter {\n    const LENGTH_TABLE: [u8; 32] = [\n        10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96,\n        22, 192, 24, 72, 26, 16, 28, 32, 30,\n    ];\n\n    pub const fn new(channel: Channel) -> Self {\n        Self {\n            enabled: false,\n            channel,\n            halt: false,\n            new_halt: false,\n            counter: 0,\n            previous_counter: 0,\n            reload: 0,\n        }\n    }\n\n    #[inline]\n    pub const fn write(&mut self, val: u8) {\n        if self.enabled {\n            self.reload = Self::LENGTH_TABLE[val as usize]; // D7..D3\n            self.previous_counter = self.counter;\n        }\n    }\n\n    #[inline]\n    pub const fn set_enabled(&mut self, enabled: bool) {\n        if !enabled {\n            self.counter = 0;\n        }\n        self.enabled = enabled;\n    }\n\n    #[inline]\n    pub const fn reload(&mut self) {\n        if self.reload > 0 {\n            if self.counter == self.previous_counter {\n                self.counter = self.reload;\n            }\n            self.reload = 0;\n        }\n        self.halt = self.new_halt;\n    }\n\n    #[inline]\n    pub const fn write_ctrl(&mut self, halt: bool) {\n        self.new_halt = halt;\n    }\n}\n\nimpl Clock for LengthCounter {\n    fn clock(&mut self) {\n        if self.counter > 0 && !self.halt {\n            self.counter -= 1;\n        }\n    }\n}\n\nimpl Reset for LengthCounter {\n    fn reset(&mut self, kind: ResetKind) {\n        self.enabled = false;\n        match kind {\n            ResetKind::Soft => {\n                if self.channel != Channel::Triangle {\n                    self.halt = false;\n                    self.new_halt = false;\n                    self.counter = 0;\n                    self.reload = 0;\n                    self.previous_counter = 0;\n                }\n            }\n            ResetKind::Hard => {\n                self.halt = false;\n                self.new_halt = false;\n                self.counter = 0;\n                self.reload = 0;\n                self.previous_counter = 0;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/apu/noise.rs",
    "content": "//! APU Noise Channel implementation.\n//!\n//! See: <https://www.nesdev.org/wiki/APU_Noise>\n\nuse crate::{\n    apu::{\n        Channel,\n        envelope::Envelope,\n        length_counter::LengthCounter,\n        timer::{Timer, TimerCycle},\n    },\n    common::{Clock, NesRegion, Regional, Reset, ResetKind, Sample},\n};\nuse serde::{Deserialize, Serialize};\n\n/// Noise shift mode.\n#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]\npub enum ShiftMode {\n    /// Zero (XOR bits 0 and 1)\n    Zero,\n    /// One (XOR bits 0 and 6)\n    One,\n}\n\n/// APU Noise Channel provides pseudo-random noise generation.\n///\n/// See: <https://www.nesdev.org/wiki/APU_Noise>\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Noise {\n    pub region: NesRegion,\n    pub timer: Timer,\n    pub shift: u16,\n    pub shift_mode: ShiftMode,\n    pub length: LengthCounter,\n    pub envelope: Envelope,\n    pub force_silent: bool,\n}\n\nimpl Default for Noise {\n    fn default() -> Self {\n        Self::new(NesRegion::default())\n    }\n}\n\nimpl Noise {\n    const PERIOD_TABLE_NTSC: [u16; 16] = [\n        4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,\n    ];\n    const PERIOD_TABLE_PAL: [u16; 16] = [\n        4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778,\n    ];\n\n    pub const fn new(region: NesRegion) -> Self {\n        Self {\n            region,\n            timer: Timer::new(Self::period(region, 0)),\n            shift: 1, // defaults to 1 on power up\n            shift_mode: ShiftMode::Zero,\n            length: LengthCounter::new(Channel::Noise),\n            envelope: Envelope::new(),\n            force_silent: false,\n        }\n    }\n\n    #[must_use]\n    pub const fn is_muted(&self) -> bool {\n        (self.shift & 0x01) == 0x01 || self.silent()\n    }\n\n    #[must_use]\n    pub const fn silent(&self) -> bool {\n        self.force_silent\n    }\n\n    pub const fn set_silent(&mut self, silent: bool) {\n        self.force_silent = silent;\n    }\n\n    const fn period(region: NesRegion, val: u8) -> u16 {\n        let index = (val & 0x0F) as usize;\n        match region {\n            NesRegion::Auto | NesRegion::Ntsc | NesRegion::Dendy => {\n                Self::PERIOD_TABLE_NTSC[index] - 1\n            }\n            NesRegion::Pal => Self::PERIOD_TABLE_PAL[index] - 1,\n        }\n    }\n\n    pub fn clock_quarter_frame(&mut self) {\n        self.envelope.clock();\n    }\n\n    pub fn clock_half_frame(&mut self) {\n        self.clock_quarter_frame();\n        self.length.clock();\n    }\n\n    /// $400C Noise control\n    pub const fn write_ctrl(&mut self, val: u8) {\n        self.length.write_ctrl((val & 0x20) == 0x20); // !D5\n        self.envelope.write_ctrl(val);\n    }\n\n    /// $400E Noise timer\n    pub const fn write_timer(&mut self, val: u8) {\n        self.timer.period = Self::period(self.region, val);\n        self.shift_mode = if (val & 0x80) == 0x80 {\n            ShiftMode::One\n        } else {\n            ShiftMode::Zero\n        };\n    }\n\n    /// $400F Length counter\n    pub const fn write_length(&mut self, val: u8) {\n        self.length.write(val >> 3);\n        self.envelope.restart();\n    }\n\n    pub const fn set_enabled(&mut self, enabled: bool) {\n        self.length.set_enabled(enabled);\n    }\n\n    pub const fn volume(&self) -> u8 {\n        if self.length.counter > 0 {\n            self.envelope.volume()\n        } else {\n            0\n        }\n    }\n}\n\nimpl Sample for Noise {\n    fn output(&self) -> f32 {\n        if self.is_muted() {\n            0f32\n        } else {\n            f32::from(self.volume())\n        }\n    }\n}\n\nimpl TimerCycle for Noise {\n    fn cycle(&self) -> u32 {\n        self.timer.cycle\n    }\n}\n\nimpl Clock for Noise {\n    //    Timer --> Shift Register   Length Counter\n    //                    |                |\n    //                    v                v\n    // Envelope -------> Gate ----------> Gate --> (to mixer)\n    fn clock(&mut self) {\n        if self.timer.tick() {\n            let shift_by = if self.shift_mode == ShiftMode::One {\n                6\n            } else {\n                1\n            };\n            let feedback = (self.shift & 0x01) ^ ((self.shift >> shift_by) & 0x01);\n            self.shift >>= 1;\n            self.shift |= feedback << 14;\n        }\n    }\n}\n\nimpl Regional for Noise {\n    fn region(&self) -> NesRegion {\n        self.region\n    }\n\n    fn set_region(&mut self, region: NesRegion) {\n        self.region = region;\n    }\n}\n\nimpl Reset for Noise {\n    fn reset(&mut self, kind: ResetKind) {\n        self.timer.reset(kind);\n        self.timer.period = Self::period(self.region, 0);\n        self.length.reset(kind);\n        self.envelope.reset(kind);\n        self.shift = 1;\n        self.shift_mode = ShiftMode::Zero;\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/apu/pulse.rs",
    "content": "//! APU Pulse Channel implementation.\n//!\n//! See: <https://www.nesdev.org/wiki/APU_Pulse>\n\nuse crate::{\n    apu::{\n        Channel,\n        envelope::Envelope,\n        length_counter::LengthCounter,\n        timer::{Timer, TimerCycle},\n    },\n    common::{Clock, Reset, ResetKind, Sample},\n};\nuse serde::{Deserialize, Serialize};\n\n/// Pulse Channel output frequency. Supports MMC5 being able to pulse at ultrasonic frequencies.\n#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]\npub enum OutputFreq {\n    Default,\n    Ultrasonic,\n}\n\n/// Pulse Channel selection.\n#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]\npub enum PulseChannel {\n    One,\n    Two,\n}\n\n/// APU Pulse Channel provides square wave generation.\n///\n/// See: <https://www.nesdev.org/wiki/APU_Pulse>\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Pulse {\n    pub channel: PulseChannel,\n    pub real_period: u16,\n    pub timer: Timer,\n    pub duty: u8,       // Select row in DUTY_TABLE\n    pub duty_cycle: u8, // Select column in DUTY_TABLE\n    pub length: LengthCounter,\n    pub envelope: Envelope,\n    pub sweep: Sweep,\n    pub force_silent: bool,\n    pub output_freq: OutputFreq,\n}\n\nimpl Default for Pulse {\n    fn default() -> Self {\n        Self::new(PulseChannel::One, OutputFreq::Default)\n    }\n}\n\nimpl Pulse {\n    const DUTY_TABLE: [[u8; 8]; 4] = [\n        [0, 0, 0, 0, 0, 0, 0, 1],\n        [0, 0, 0, 0, 0, 0, 1, 1],\n        [0, 0, 0, 0, 1, 1, 1, 1],\n        [1, 1, 1, 1, 1, 1, 0, 0],\n    ];\n\n    pub const fn new(channel: PulseChannel, output_freq: OutputFreq) -> Self {\n        Self {\n            channel,\n            real_period: 0,\n            timer: Timer::new(0),\n            duty: 0u8,\n            duty_cycle: 0,\n            length: LengthCounter::new(match channel {\n                PulseChannel::One => Channel::Pulse1,\n                PulseChannel::Two => Channel::Pulse2,\n            }),\n            envelope: Envelope::new(),\n            sweep: Sweep::new(channel),\n            force_silent: false,\n            output_freq,\n        }\n    }\n\n    #[inline]\n    pub fn is_muted(&self) -> bool {\n        // MMC5 doesn't mute at ultasonic frequencies\n        self.output_freq == OutputFreq::Default\n            && (self.real_period < 8 || (!self.sweep.negate && self.sweep.target_period > 0x7FF))\n            || self.silent()\n    }\n\n    #[must_use]\n    pub const fn silent(&self) -> bool {\n        self.force_silent\n    }\n\n    pub const fn set_silent(&mut self, silent: bool) {\n        self.force_silent = silent;\n    }\n\n    const fn update_target_period(&mut self) {\n        let delta = self.real_period >> self.sweep.shift;\n        if self.sweep.negate {\n            self.sweep.target_period = self.real_period - delta;\n            if let PulseChannel::One = self.channel {\n                self.sweep.target_period = self.sweep.target_period.wrapping_sub(1);\n            }\n        } else {\n            self.sweep.target_period = self.real_period + delta;\n        }\n    }\n\n    const fn set_period(&mut self, period: u16) {\n        self.real_period = period;\n        self.timer.period = (period * 2) + 1;\n        self.update_target_period();\n    }\n\n    const fn clock_sweep(&mut self) {\n        self.sweep.divider = self.sweep.divider.wrapping_sub(1);\n        if self.sweep.divider == 0 {\n            if self.sweep.shift > 0\n                && self.sweep.enabled\n                && self.real_period >= 8\n                && self.sweep.target_period <= 0x7FF\n            {\n                self.set_period(self.sweep.target_period);\n            }\n            self.sweep.divider = self.sweep.period;\n        }\n\n        if self.sweep.reload {\n            self.sweep.divider = self.sweep.period;\n            self.sweep.reload = false;\n        }\n    }\n\n    pub fn clock_quarter_frame(&mut self) {\n        self.envelope.clock();\n    }\n\n    pub fn clock_half_frame(&mut self) {\n        self.clock_quarter_frame();\n        self.length.clock();\n        self.clock_sweep();\n    }\n\n    /// $4000/$4004 Pulse control\n    pub const fn write_ctrl(&mut self, val: u8) {\n        self.length.write_ctrl((val & 0x20) == 0x20); // !D5\n        self.envelope.write_ctrl(val);\n        self.duty = (val & 0xC0) >> 6;\n    }\n\n    /// $4001/$4005 Pulse sweep\n    pub const fn write_sweep(&mut self, val: u8) {\n        self.sweep.enabled = (val & 0x80) == 0x80;\n        self.sweep.negate = (val & 0x08) == 0x08;\n        self.sweep.period = ((val & 0x70) >> 4) + 1;\n        self.sweep.shift = val & 0x07;\n        self.update_target_period();\n        self.sweep.reload = true;\n    }\n\n    /// $4002/$4006 Pulse timer lo\n    pub fn write_timer_lo(&mut self, val: u8) {\n        self.set_period(self.real_period & 0x0700 | u16::from(val));\n    }\n\n    /// $4003/$4007 Pulse timer hi\n    pub fn write_timer_hi(&mut self, val: u8) {\n        self.length.write(val >> 3);\n        self.set_period(self.real_period & 0xFF | (u16::from(val & 0x07) << 8));\n        self.duty_cycle = 0;\n        self.envelope.restart();\n    }\n\n    pub const fn set_enabled(&mut self, enabled: bool) {\n        self.length.set_enabled(enabled);\n    }\n\n    pub const fn volume(&self) -> u8 {\n        if self.length.counter > 0 {\n            self.envelope.volume()\n        } else {\n            0\n        }\n    }\n}\n\nimpl Sample for Pulse {\n    fn output(&self) -> f32 {\n        if self.is_muted() {\n            0.0\n        } else {\n            f32::from(\n                Self::DUTY_TABLE[self.duty as usize][self.duty_cycle as usize] * self.volume(),\n            )\n        }\n    }\n}\n\nimpl TimerCycle for Pulse {\n    fn cycle(&self) -> u32 {\n        self.timer.cycle\n    }\n}\n\nimpl Clock for Pulse {\n    //                  Sweep -----> Timer\n    //                    |            |\n    //                    |            |\n    //                    |            v\n    //                    |        Sequencer   Length Counter\n    //                    |            |             |\n    //                    |            |             |\n    //                    v            v             v\n    // Envelope -------> Gate -----> Gate -------> Gate --->(to mixer)\n    fn clock(&mut self) {\n        if self.timer.tick() {\n            self.duty_cycle = self.duty_cycle.wrapping_sub(1) & 0x07;\n        }\n    }\n}\n\nimpl Reset for Pulse {\n    fn reset(&mut self, kind: ResetKind) {\n        self.timer.reset(kind);\n        self.length.reset(kind);\n        self.envelope.reset(kind);\n        self.sweep.reset(kind);\n        self.update_target_period();\n        self.duty = 0;\n        self.duty_cycle = 0;\n    }\n}\n\n/// APU Sweep provides frequency sweeping for the APU pulse channels.\n///\n/// See: <https://www.nesdev.org/wiki/APU_Sweep>\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct Sweep {\n    pub enabled: bool,\n    pub channel: PulseChannel,\n    pub negate: bool, // Treats PulseChannel 1 differently than PulseChannel 2\n    pub reload: bool,\n    pub shift: u8,\n    pub timer: u16,\n    pub divider: u8,\n    pub period: u8,\n    pub target_period: u16,\n}\n\nimpl Sweep {\n    pub const fn new(channel: PulseChannel) -> Self {\n        Self {\n            enabled: false,\n            channel,\n            negate: false,\n            reload: false,\n            shift: 0,\n            timer: 0,\n            divider: 0,\n            period: 0,\n            target_period: 0,\n        }\n    }\n}\n\nimpl Reset for Sweep {\n    fn reset(&mut self, _kind: ResetKind) {\n        self.enabled = false;\n        self.period = 0;\n        self.negate = false;\n        self.reload = false;\n        self.shift = 0;\n        self.divider = 0;\n        self.target_period = 0;\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/apu/timer.rs",
    "content": "//! Timer abstraction for the [`Apu`](crate::apu::Apu).\n\nuse crate::common::{Reset, ResetKind};\nuse serde::{Deserialize, Serialize};\n\n/// Trait for types that have timers.\npub trait TimerCycle {\n    fn cycle(&self) -> u32;\n}\n\n/// A timer that generates a clock signal based on a divider and a period. The timer is clocked\n/// every (period + 1) * divider cycles.\n#[derive(Default, Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Timer {\n    pub cycle: u32,\n    pub counter: u16,\n    pub period: u16,\n}\n\nimpl Timer {\n    pub const fn new(period: u16) -> Self {\n        Self {\n            cycle: 0,\n            counter: 0,\n            period,\n        }\n    }\n\n    pub const fn preload(period: u16) -> Self {\n        let mut timer = Self::new(period);\n        timer.counter = timer.period;\n        timer\n    }\n\n    pub const fn reload(&mut self) {\n        self.counter = self.period;\n    }\n\n    pub const fn tick(&mut self) -> bool {\n        self.cycle += 1;\n        if self.counter == 0 {\n            self.counter = self.period;\n            return true;\n        }\n        self.counter -= 1;\n        false\n    }\n}\n\nimpl Reset for Timer {\n    fn reset(&mut self, _kind: ResetKind) {\n        self.counter = 0;\n        self.period = 0;\n        self.cycle = 0;\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn timer() {\n        // Period (10 + 1) == 11 + initial clock\n        let mut timer = Timer::new(10);\n        let mut expected = [false; 23];\n        expected[0] = true;\n        expected[11] = true;\n        expected[22] = true;\n        assert_eq!(expected, [(); 23].map(|_| timer.tick()));\n        assert_eq!(23, timer.cycle);\n\n        // Period (10 + 1) == 11\n        let mut timer = Timer::preload(10);\n        let mut expected = [false; 22];\n        expected[10] = true;\n        expected[21] = true;\n        assert_eq!(expected, [(); 22].map(|_| timer.tick()));\n        assert_eq!(22, timer.cycle);\n\n        // Period (10 * 2) + 1 == 22 + initial clock\n        let mut timer = Timer::new((10 * 2) + 1);\n        let mut expected = [false; 45];\n        expected[0] = true;\n        expected[22] = true;\n        expected[44] = true;\n        assert_eq!(expected, [(); 45].map(|_| timer.tick()));\n        assert_eq!(45, timer.cycle);\n\n        // Period (10 * 2) + 1 == 22\n        let mut timer = Timer::preload((10 * 2) + 1);\n        let mut expected = [false; 44];\n        expected[21] = true;\n        expected[43] = true;\n        assert_eq!(expected, [(); 44].map(|_| timer.tick()));\n        assert_eq!(44, timer.cycle);\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/apu/triangle.rs",
    "content": "//! APU Triangle Channel implementation.\n//!\n//! See: <https://www.nesdev.org/wiki/APU_Triangle>\n\nuse crate::{\n    apu::{\n        Channel,\n        length_counter::LengthCounter,\n        timer::{Timer, TimerCycle},\n    },\n    common::{Clock, Reset, ResetKind, Sample},\n};\nuse serde::{Deserialize, Serialize};\n\n/// APU Triangle Channel provides triangle wave generation.\n///\n/// See: <https://www.nesdev.org/wiki/APU_Triangle>\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Triangle {\n    pub timer: Timer,\n    pub sequence: u8,\n    pub length: LengthCounter,\n    pub linear: LinearCounter,\n    pub force_silent: bool,\n}\n\nimpl Default for Triangle {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Triangle {\n    const SEQUENCE: [u8; 32] = [\n        15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n        12, 13, 14, 15,\n    ];\n\n    pub const fn new() -> Self {\n        Self {\n            timer: Timer::new(0),\n            sequence: 0,\n            length: LengthCounter::new(Channel::Triangle),\n            linear: LinearCounter::new(),\n            force_silent: false,\n        }\n    }\n\n    #[must_use]\n    pub const fn silent(&self) -> bool {\n        self.force_silent\n    }\n\n    pub const fn set_silent(&mut self, silent: bool) {\n        self.force_silent = silent;\n    }\n\n    pub fn clock_quarter_frame(&mut self) {\n        self.linear.clock();\n    }\n\n    pub fn clock_half_frame(&mut self) {\n        self.clock_quarter_frame();\n        self.length.clock();\n    }\n\n    /// $4008 Linear counter control\n    pub const fn write_linear_counter(&mut self, val: u8) {\n        self.linear.control = (val & 0x80) == 0x80; // D7\n        self.linear.write(val & 0x7F); // D6..D0;\n        self.length.write_ctrl(self.linear.control); // !D7\n    }\n\n    /// $400A Triangle timer lo\n    pub fn write_timer_lo(&mut self, val: u8) {\n        self.timer.period = (self.timer.period & 0xFF00) | u16::from(val); // D7..D0\n    }\n\n    /// $400B Triangle timer high\n    pub fn write_timer_hi(&mut self, val: u8) {\n        self.length.write(val >> 3);\n        self.timer.period = (self.timer.period & 0x00FF) | (u16::from(val & 0x07) << 8); // D2..D0\n        self.linear.reload = true;\n    }\n\n    pub const fn set_enabled(&mut self, enabled: bool) {\n        self.length.set_enabled(enabled);\n    }\n}\n\nimpl Sample for Triangle {\n    fn output(&self) -> f32 {\n        if self.silent() {\n            0.0\n        } else if self.timer.period < 2 {\n            // This is normally silenced by a lowpass filter on real hardware\n            // See: https://forums.nesdev.org/viewtopic.php?t=10658\n            7.5\n        } else {\n            f32::from(Self::SEQUENCE[self.sequence as usize])\n        }\n    }\n}\n\nimpl TimerCycle for Triangle {\n    fn cycle(&self) -> u32 {\n        self.timer.cycle\n    }\n}\n\nimpl Clock for Triangle {\n    //       Linear Counter   Length Counter\n    //             |                |\n    //             v                v\n    // Timer ---> Gate ----------> Gate ---> Sequencer ---> (to mixer)\n    fn clock(&mut self) {\n        if self.timer.tick() && self.length.counter > 0 && self.linear.counter > 0 {\n            self.sequence = (self.sequence + 1) & 0x1F;\n        }\n    }\n}\n\nimpl Reset for Triangle {\n    fn reset(&mut self, kind: ResetKind) {\n        self.length.reset(kind);\n        self.linear.reset(kind);\n        self.sequence = 0;\n    }\n}\n\n/// APU Linear Counter provides duration control for the APU triangle channel.\n///\n/// See: <https://www.nesdev.org/wiki/APU_Triangle>\n#[derive(Default, Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct LinearCounter {\n    pub reload: bool,\n    pub control: bool,\n    pub counter_reload: u8,\n    pub counter: u8,\n}\n\nimpl LinearCounter {\n    pub const fn new() -> Self {\n        Self {\n            reload: false,\n            control: false,\n            counter_reload: 0u8,\n            counter: 0u8,\n        }\n    }\n\n    pub const fn write(&mut self, val: u8) {\n        self.counter_reload = val;\n    }\n}\n\nimpl Clock for LinearCounter {\n    fn clock(&mut self) {\n        if self.reload {\n            self.counter = self.counter_reload;\n        } else if self.counter > 0 {\n            self.counter -= 1;\n        }\n        if !self.control {\n            self.reload = false;\n        }\n    }\n}\n\nimpl Reset for LinearCounter {\n    fn reset(&mut self, _kind: ResetKind) {\n        self.counter = 0;\n        self.counter_reload = 0;\n        self.reload = false;\n        self.control = false;\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/apu.rs",
    "content": "//! NES APU (Audio Processing Unit) implementation.\n//!\n//! See: <https://www.nesdev.org/wiki/APU>\n\nuse crate::{\n    apu::{\n        dmc::Dmc,\n        filter::{Consume, FilterChain},\n        frame_counter::{FrameCounter, FrameType},\n        noise::Noise,\n        pulse::{OutputFreq, Pulse, PulseChannel},\n        timer::TimerCycle,\n        triangle::Triangle,\n    },\n    common::{Clock, NesRegion, Regional, Reset, ResetKind, Sample},\n    cpu::Cpu,\n};\nuse serde::{Deserialize, Serialize};\nuse thiserror::Error;\nuse tracing::{trace, warn};\n\npub mod dmc;\npub mod noise;\npub mod pulse;\npub mod triangle;\n\npub mod envelope;\npub mod filter;\npub mod frame_counter;\npub mod length_counter;\npub mod timer;\n\n/// Error when parsing `Channel` from a `usize`.\n#[derive(Error, Debug)]\n#[must_use]\n#[error(\"failed to parse `Channel`\")]\npub struct ParseChannelError;\n\n/// [`Apu`] Channel.\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum Channel {\n    Pulse1,\n    Pulse2,\n    Triangle,\n    Noise,\n    Dmc,\n    Mapper,\n}\n\nimpl TryFrom<usize> for Channel {\n    type Error = ParseChannelError;\n\n    fn try_from(value: usize) -> Result<Self, Self::Error> {\n        match value {\n            0 => Ok(Self::Pulse1),\n            1 => Ok(Self::Pulse2),\n            2 => Ok(Self::Triangle),\n            3 => Ok(Self::Noise),\n            4 => Ok(Self::Dmc),\n            5 => Ok(Self::Mapper),\n            _ => Err(ParseChannelError),\n        }\n    }\n}\n\n/// NES APU (Audio Processing Unit).\n///\n/// See: <https://wiki.nesdev.org/w/index.php/APU>\n#[derive(Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Apu {\n    pub frame_counter: FrameCounter,\n    pub master_clock: u32,\n    pub cpu_cycle: u32,\n    pub clock: u32,\n    pub clock_rate: f32,\n    pub region: NesRegion,\n    pub pulse1: Pulse,\n    pub pulse2: Pulse,\n    pub triangle: Triangle,\n    pub noise: Noise,\n    pub dmc: Dmc,\n    pub filter_chain: FilterChain,\n    #[serde(skip, default = \"Apu::default_channel_outputs\")]\n    pub channel_outputs: Box<[f32]>,\n    #[serde(skip)]\n    pub audio_samples: Vec<f32>,\n    pub sample_rate: f32,\n    pub sample_period: f32,\n    pub sample_counter: f32,\n    pub speed: f32,\n    pub mapper_enabled: bool,\n    pub skip_mixing: bool,\n    pub should_clock: bool,\n}\n\nimpl Default for Apu {\n    fn default() -> Self {\n        Self::new(NesRegion::default())\n    }\n}\n\nimpl Apu {\n    pub const DEFAULT_SAMPLE_RATE: f32 = 44_100.0;\n    // 5 APU channels + 1 Mapper channel\n    pub const MAX_CHANNEL_COUNT: usize = 6;\n    pub const CYCLE_SIZE: u32 = 10_000;\n\n    /// Create a new APU instance.\n    pub fn new(region: NesRegion) -> Self {\n        let clock_rate = Cpu::region_clock_rate(region);\n        let sample_rate = Self::DEFAULT_SAMPLE_RATE;\n        let sample_period = clock_rate / sample_rate;\n        Self {\n            frame_counter: FrameCounter::new(region),\n            master_clock: 0,\n            cpu_cycle: 0,\n            clock: 0,\n            clock_rate,\n            region,\n            pulse1: Pulse::new(PulseChannel::One, OutputFreq::Default),\n            pulse2: Pulse::new(PulseChannel::Two, OutputFreq::Default),\n            triangle: Triangle::new(),\n            noise: Noise::new(region),\n            dmc: Dmc::new(region),\n            filter_chain: FilterChain::new(region, sample_rate),\n            channel_outputs: Self::default_channel_outputs(),\n            audio_samples: Vec::with_capacity((sample_rate / 60.0) as usize),\n            sample_rate,\n            sample_period,\n            sample_counter: sample_period,\n            speed: 1.0,\n            mapper_enabled: true,\n            skip_mixing: false,\n            should_clock: false,\n        }\n    }\n\n    pub fn default_channel_outputs() -> Box<[f32]> {\n        vec![0.0; Self::MAX_CHANNEL_COUNT * Self::CYCLE_SIZE as usize].into()\n    }\n\n    #[inline(always)]\n    pub fn add_mapper_output(&mut self, output: f32) {\n        self.channel_outputs\n            [(self.master_clock as usize * Self::MAX_CHANNEL_COUNT) + Channel::Mapper as usize] =\n            output;\n    }\n\n    /// Filter and mix audio sample based on region sampling rate.\n    #[inline]\n    pub fn process_outputs(&mut self) {\n        if self.skip_mixing {\n            return;\n        }\n\n        for outputs in self\n            .channel_outputs\n            .chunks_exact(Self::MAX_CHANNEL_COUNT)\n            .take(self.master_clock as usize)\n        {\n            let [pulse1, pulse2, triangle, noise, dmc, mapper] = outputs else {\n                warn!(\"invalid channel outputs\");\n                return;\n            };\n            let pulse_idx = (pulse1 + pulse2) as usize;\n            let tnd_idx = (3.0f32.mul_add(*triangle, 2.0 * noise) + dmc) as usize;\n            let apu_output = PULSE_TABLE[pulse_idx] + TND_TABLE[tnd_idx];\n            let mapper_output = self.mapper_enabled as u8 as f32 * *mapper;\n\n            self.filter_chain.consume(apu_output + mapper_output);\n            self.sample_counter -= 1.0;\n            if self.sample_counter <= 1.0 {\n                self.audio_samples.push(self.filter_chain.output());\n                self.sample_counter += self.sample_period;\n            }\n        }\n    }\n\n    /// Set the audio sample rate.\n    #[inline]\n    pub fn set_sample_rate(&mut self, sample_rate: f32) {\n        self.sample_rate = sample_rate;\n        let sample_rate = self.sample_rate / self.speed;\n        self.filter_chain = FilterChain::new(self.region, sample_rate);\n        let clock_rate = Cpu::region_clock_rate(self.region);\n        self.sample_period = clock_rate / sample_rate;\n    }\n\n    /// Set the frame speed of the APU, which affects the sampling rate.\n    pub fn set_frame_speed(&mut self, speed: f32) {\n        self.speed = speed;\n        let sample_rate = self.sample_rate / self.speed;\n        self.filter_chain = FilterChain::new(self.region, sample_rate);\n        let clock_rate = Cpu::region_clock_rate(self.region);\n        self.sample_period = clock_rate / sample_rate;\n    }\n\n    /// Whether a given channel is enabled.\n    #[must_use]\n    pub const fn channel_enabled(&self, channel: Channel) -> bool {\n        match channel {\n            Channel::Pulse1 => !self.pulse1.silent(),\n            Channel::Pulse2 => !self.pulse2.silent(),\n            Channel::Triangle => !self.triangle.silent(),\n            Channel::Noise => !self.noise.silent(),\n            Channel::Dmc => !self.dmc.silent(),\n            Channel::Mapper => self.mapper_enabled,\n        }\n    }\n\n    /// Enable or disable a given channel.\n    pub const fn set_channel_enabled(&mut self, channel: Channel, enabled: bool) {\n        match channel {\n            Channel::Pulse1 => self.pulse1.set_silent(!enabled),\n            Channel::Pulse2 => self.pulse2.set_silent(!enabled),\n            Channel::Triangle => self.triangle.set_silent(!enabled),\n            Channel::Noise => self.noise.set_silent(!enabled),\n            Channel::Dmc => self.dmc.set_silent(!enabled),\n            Channel::Mapper => self.mapper_enabled = enabled,\n        }\n    }\n\n    /// Toggle a given channel.\n    pub const fn toggle_channel(&mut self, channel: Channel) {\n        match channel {\n            Channel::Pulse1 => self.pulse1.set_silent(!self.pulse1.silent()),\n            Channel::Pulse2 => self.pulse2.set_silent(!self.pulse2.silent()),\n            Channel::Triangle => self.triangle.set_silent(!self.triangle.silent()),\n            Channel::Noise => self.noise.set_silent(!self.noise.silent()),\n            Channel::Dmc => self.dmc.set_silent(!self.dmc.silent()),\n            Channel::Mapper => self.mapper_enabled = !self.mapper_enabled,\n        }\n    }\n\n    pub fn clock_lazy(&mut self) {\n        self.cpu_cycle = self.cpu_cycle.wrapping_add(1);\n        self.master_clock += 1;\n        if self.master_clock == Self::CYCLE_SIZE - 1 {\n            self.clock_sync();\n        } else if self.should_clock() {\n            self.clock_to(self.master_clock);\n        }\n    }\n\n    /// Runs all componnets up to master clock, synchronizing them.\n    #[cold]\n    #[inline(never)]\n    pub fn clock_sync(&mut self) {\n        self.clock_to(self.master_clock);\n\n        self.process_outputs();\n\n        debug_assert_eq!(self.master_clock, self.clock);\n        self.master_clock = 0;\n        self.clock = 0;\n        self.pulse1.timer.cycle = 0;\n        self.pulse2.timer.cycle = 0;\n        self.triangle.timer.cycle = 0;\n        self.noise.timer.cycle = 0;\n        self.dmc.timer.cycle = 0;\n    }\n\n    #[inline(always)]\n    fn should_clock(&mut self) -> bool {\n        // Clock every cycle while DMC is running to get accurate CPU stalling, sprite DMA\n        // emulation, etc\n        if self.dmc.should_clock() || self.should_clock {\n            self.should_clock = false;\n            return true;\n        }\n        let cycles = self.master_clock - self.clock;\n        self.frame_counter.should_clock(cycles) || self.dmc.irq_pending_in(cycles)\n    }\n\n    fn channel_clock_to(&mut self, channel: Channel, cycle: u32) {\n        fn clock_to<T>(instance: &mut T, cycle: u32, offset: usize, outputs: &mut [f32])\n        where\n            T: Clock + TimerCycle + Sample,\n        {\n            while instance.cycle() < cycle {\n                instance.clock();\n                outputs[((instance.cycle() - 1) as usize * Apu::MAX_CHANNEL_COUNT) + offset] =\n                    instance.output();\n            }\n        }\n\n        let offset = channel as usize;\n        let outputs = &mut self.channel_outputs;\n        match channel {\n            Channel::Pulse1 => clock_to(&mut self.pulse1, cycle, offset, outputs),\n            Channel::Pulse2 => clock_to(&mut self.pulse2, cycle, offset, outputs),\n            Channel::Triangle => clock_to(&mut self.triangle, cycle, offset, outputs),\n            Channel::Noise => clock_to(&mut self.noise, cycle, offset, outputs),\n            Channel::Dmc => clock_to(&mut self.dmc, cycle, offset, outputs),\n            _ => (),\n        }\n    }\n\n    fn clock_to(&mut self, cycle: u32) {\n        self.master_clock = cycle;\n\n        let cycles = self.master_clock - self.clock;\n        trace!(\n            \"APU cycles to run: {} ({} - {}) - CYC:{}\",\n            cycles, self.master_clock, self.clock, self.cpu_cycle,\n        );\n        while self.master_clock - self.clock > 0 {\n            self.clock += self\n                .frame_counter\n                .clock_with(self.master_clock - self.clock, |ty| match ty {\n                    FrameType::Quarter => {\n                        trace!(\"APU Quarter Frame clock - CYC:{}\", self.cpu_cycle);\n                        self.pulse1.clock_quarter_frame();\n                        self.pulse2.clock_quarter_frame();\n                        self.triangle.clock_quarter_frame();\n                        self.noise.clock_quarter_frame();\n                    }\n                    FrameType::Half => {\n                        trace!(\"APU Half Frame clock - CYC:{}\", self.cpu_cycle);\n                        self.pulse1.clock_half_frame();\n                        self.pulse2.clock_half_frame();\n                        self.triangle.clock_half_frame();\n                        self.noise.clock_half_frame();\n                    }\n                    _ => (),\n                });\n\n            self.pulse1.length.reload();\n            self.pulse2.length.reload();\n            self.triangle.length.reload();\n            self.noise.length.reload();\n\n            self.channel_clock_to(Channel::Pulse1, self.clock);\n            self.channel_clock_to(Channel::Pulse2, self.clock);\n            self.channel_clock_to(Channel::Triangle, self.clock);\n            self.channel_clock_to(Channel::Noise, self.clock);\n            self.channel_clock_to(Channel::Dmc, self.clock);\n        }\n    }\n\n    /// $4000 Pulse1, $4004 Pulse2, and $400C Noise Control.\n    pub fn write_ctrl(&mut self, channel: Channel, val: u8) {\n        self.clock_to(self.master_clock);\n        match channel {\n            Channel::Pulse1 => {\n                trace!(\"APU $4000 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n                self.pulse1.write_ctrl(val);\n            }\n            Channel::Pulse2 => {\n                trace!(\"APU $4004 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n                self.pulse2.write_ctrl(val);\n            }\n            Channel::Noise => {\n                trace!(\"APU $400C write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n                self.noise.write_ctrl(val);\n            }\n            _ => panic!(\"{channel:?} does not have a control register\"),\n        }\n        self.should_clock = true;\n    }\n\n    /// $4001 Pulse1 and $4005 Pulse2 Sweep.\n    pub fn write_sweep(&mut self, channel: Channel, val: u8) {\n        self.clock_to(self.master_clock);\n        match channel {\n            Channel::Pulse1 => {\n                trace!(\"APU $4001 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n                self.pulse1.write_sweep(val);\n            }\n            Channel::Pulse2 => {\n                trace!(\"APU $4005 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n                self.pulse2.write_sweep(val);\n            }\n            _ => panic!(\"{channel:?} does not have a sweep register\"),\n        }\n    }\n\n    /// $4002 Pulse1, $4006 Pulse2, $400A Triangle, $400E Noise, and $4010 DMC Timer Low Byte.\n    pub fn write_timer_lo(&mut self, channel: Channel, val: u8) {\n        self.clock_to(self.master_clock);\n        match channel {\n            Channel::Pulse1 => {\n                trace!(\"APU $4002 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n                self.pulse1.write_timer_lo(val);\n            }\n            Channel::Pulse2 => {\n                trace!(\"APU $4006 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n                self.pulse2.write_timer_lo(val);\n            }\n            Channel::Triangle => {\n                trace!(\"APU $400A write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n                self.triangle.write_timer_lo(val);\n            }\n            Channel::Noise => {\n                trace!(\"APU $400E write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n                self.noise.write_timer(val);\n            }\n            Channel::Dmc => {\n                trace!(\"APU $4010 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n                self.dmc.write_timer(val);\n            }\n            _ => panic!(\"{channel:?} does not have a timer_lo register\"),\n        }\n    }\n\n    /// $4003 Pulse1, $4007 Pulse2, and $400B Triangle Timer High Byte.\n    pub fn write_timer_hi(&mut self, channel: Channel, val: u8) {\n        self.clock_to(self.master_clock);\n        match channel {\n            Channel::Pulse1 => {\n                trace!(\"APU $4003 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n                self.pulse1.write_timer_hi(val);\n                self.should_clock = self.pulse1.length.enabled;\n            }\n            Channel::Pulse2 => {\n                trace!(\"APU $4007 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n                self.pulse2.write_timer_hi(val);\n                self.should_clock = self.pulse2.length.enabled;\n            }\n            Channel::Triangle => {\n                trace!(\"APU $400B write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n                self.triangle.write_timer_hi(val);\n                self.should_clock = self.triangle.length.enabled;\n            }\n            _ => panic!(\"{channel:?} does not have a timer_hi register\"),\n        }\n    }\n\n    /// $4008 Triangle Linear Counter.\n    pub fn write_linear_counter(&mut self, val: u8) {\n        self.clock_to(self.master_clock);\n        trace!(\"APU $4008 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n        self.triangle.write_linear_counter(val);\n        self.should_clock = true;\n    }\n\n    /// $400F Noise and $4013 DMC Length.\n    pub fn write_length(&mut self, channel: Channel, val: u8) {\n        self.clock_to(self.master_clock);\n        trace!(\"APU $400F write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n        match channel {\n            Channel::Noise => {\n                self.noise.write_length(val);\n                self.should_clock = self.noise.length.enabled;\n            }\n            Channel::Dmc => self.dmc.write_length(val),\n            _ => panic!(\"{channel:?} does not have a length register\"),\n        }\n    }\n\n    /// $4011 DMC Output Level.\n    pub fn write_dmc_output(&mut self, val: u8) {\n        self.clock_to(self.master_clock);\n        trace!(\"APU $4011 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n        // Only 7-bits are used\n        self.dmc.write_output(val & 0x7F);\n        // $4011 applies new output right away, not on timer reload.\n        let offset = Channel::Dmc as usize;\n        self.channel_outputs[(self.dmc.timer.cycle as usize * Apu::MAX_CHANNEL_COUNT) + offset] =\n            self.dmc.output();\n    }\n\n    /// $4012 DMC Sample Addr.\n    pub fn write_dmc_addr(&mut self, val: u8) {\n        self.clock_to(self.master_clock);\n        trace!(\"APU $4012 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n        self.dmc.write_addr(val);\n    }\n\n    /// Read APU Status.\n    ///\n    /// $4015   if-d nt21   DMC IRQ, frame IRQ, length counter statuses\n    pub fn read_status(&mut self) -> u8 {\n        self.clock_to(self.master_clock);\n        let val = self.peek_status();\n        trace!(\"APU $4015 read: ${val:02X} - CYC:{}\", self.cpu_cycle);\n        if self.frame_counter.irq_pending {\n            trace!(\"APU Frame Counter IRQ - CYC:{}\", self.cpu_cycle);\n        }\n        self.frame_counter.irq_pending = false;\n        val\n    }\n\n    /// Read APU Status without side-effects.\n    ///\n    /// Non-mutating version of `read_status`.\n    pub fn peek_status(&self) -> u8 {\n        let mut status = 0x00;\n        if self.pulse1.length.counter > 0 {\n            status |= 0x01;\n        }\n        if self.pulse2.length.counter > 0 {\n            status |= 0x02;\n        }\n        if self.triangle.length.counter > 0 {\n            status |= 0x04;\n        }\n        if self.noise.length.counter > 0 {\n            status |= 0x08;\n        }\n        if self.dmc.bytes_remaining > 0 {\n            trace!(\"dmc bytes remaining: {}\", self.dmc.bytes_remaining);\n            status |= 0x10;\n        }\n        if self.frame_counter.irq_pending {\n            status |= 0x40;\n        }\n        if self.dmc.irq_pending {\n            status |= 0x80;\n        }\n        status\n    }\n\n    /// Write APU Status.\n    ///\n    /// $4015   ---d nt21   length ctr enable: DMC, noise, triangle, pulse 2, 1\n    pub fn write_status(&mut self, val: u8) {\n        self.clock_to(self.master_clock);\n        trace!(\"APU $4015 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n        self.pulse1.set_enabled(val & 0x01 == 0x01);\n        self.pulse2.set_enabled(val & 0x02 == 0x02);\n        self.triangle.set_enabled(val & 0x04 == 0x04);\n        self.noise.set_enabled(val & 0x08 == 0x08);\n        self.dmc.set_enabled(val & 0x10 == 0x10, self.cpu_cycle);\n        self.dmc.irq_pending = false;\n    }\n\n    /// $4017 APU Frame Counter.\n    pub fn write_frame_counter(&mut self, val: u8) {\n        self.clock_to(self.master_clock);\n        trace!(\"APU $4017 write: ${val:02X} - CYC:{}\", self.cpu_cycle);\n        self.frame_counter.write(val, self.cpu_cycle);\n    }\n\n    // Return pending IRQ.\n    #[inline(always)]\n    pub const fn irq_pending(&self) -> bool {\n        self.frame_counter.irq_pending | self.dmc.irq_pending\n    }\n\n    // Return pending DMA.\n    #[inline(always)]\n    pub const fn dma_pending(&self) -> bool {\n        self.dmc.dma_pending\n    }\n\n    // Clear pending DMA.\n    #[inline(always)]\n    pub const fn clear_dma_pending(&mut self) {\n        self.dmc.dma_pending = false;\n    }\n}\n\nimpl Regional for Apu {\n    fn region(&self) -> NesRegion {\n        self.region\n    }\n\n    fn set_region(&mut self, region: NesRegion) {\n        if self.region != region {\n            self.region = region;\n            self.clock_rate = Cpu::region_clock_rate(region);\n            self.filter_chain = FilterChain::new(region, self.sample_rate);\n            self.sample_period = self.clock_rate / self.sample_rate;\n            self.frame_counter.set_region(region);\n            self.noise.set_region(region);\n            self.dmc.set_region(region);\n        }\n    }\n}\n\nimpl Reset for Apu {\n    fn reset(&mut self, kind: ResetKind) {\n        self.cpu_cycle = 0;\n        self.master_clock = 0;\n        self.clock = 0;\n        self.should_clock = false;\n        self.frame_counter.reset(kind);\n        self.pulse1.reset(kind);\n        self.pulse2.reset(kind);\n        self.triangle.reset(kind);\n        self.noise.reset(kind);\n        self.dmc.reset(kind);\n    }\n}\n\nimpl std::fmt::Debug for Apu {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {\n        f.debug_struct(\"Apu\")\n            .field(\"cpu_cycle\", &self.cpu_cycle)\n            .field(\"master_clock\", &self.master_clock)\n            .field(\"cycle\", &self.clock)\n            .field(\"frame_counter\", &self.frame_counter)\n            .field(\"pulse1\", &self.pulse1)\n            .field(\"pulse2\", &self.pulse2)\n            .field(\"triangle\", &self.triangle)\n            .field(\"noise\", &self.noise)\n            .field(\"dmc\", &self.dmc)\n            .field(\"filter_chain\", &self.filter_chain)\n            .field(\"audio_samples_len\", &self.audio_samples.len())\n            .finish()\n    }\n}\n\n/// [`Pulse`] channel lookup table.\n///\n/// See: <https://www.nesdev.org/wiki/APU_Mixer>\n///\n/// Original calculation:\n///\n/// ```rust\n/// let mut pulse_table = [0.0; 31];\n/// for (i, val) in pulse_table.iter_mut().enumerate().skip(1) {\n///     *val = 95.52 / (8_128.0 / (i as f32) + 100.0);\n/// }\n/// ```\n#[rustfmt::skip]\npub static PULSE_TABLE: [f32; 31] = [\n    0.0,          0.011_609_139, 0.022_939_48, 0.034_000_948, 0.044_803,    0.055_354_66,\n    0.065_664_53, 0.075_740_82,  0.085_591_4,  0.095_223_75,  0.104_645_04, 0.113_862_15,\n    0.122_881_64, 0.131_709_8,   0.140_352_64, 0.148_815_96,  0.157_105_25, 0.165_225_88,\n    0.173_182_92, 0.180_981_26,  0.188_625_59, 0.196_120_46,  0.203_470_17, 0.210_678_94,\n    0.217_750_76, 0.224_689_5,   0.231_498_87, 0.238_182_47,  0.244_743_78, 0.251_186_07,\n    0.257_512_57,\n];\n\n/// [`Triangle`]/[`Noise`]/[`Dmc`] channels lookup table.\n///\n/// See: <https://www.nesdev.org/wiki/APU_Mixer>\n///\n/// Original calculation:\n///\n/// ```rust\n/// let mut tnd_table = [0.0; 203];\n/// for (i, val) in tnd_table.iter_mut().enumerate().skip(1) {\n///     *val = 163.67 / (24_329.0 / (i as f32) + 100.0);\n/// }\n/// ```\n#[rustfmt::skip]\npub static TND_TABLE: [f32; 203] = [\n    0.0,           0.006_699_824, 0.013_345_02,  0.019_936_256, 0.026_474_18,  0.032_959_443,\n    0.039_392_676, 0.045_774_5,   0.052_105_535, 0.058_386_38,  0.064_617_634, 0.070_799_87,\n    0.076_933_69,  0.083_019_62,  0.089_058_26,  0.095_050_134, 0.100_995_794, 0.106_895_77,\n    0.112_750_58,  0.118_560_754, 0.124_326_79,  0.130_049_18,  0.135_728_45,  0.141_365_05,\n    0.146_959_5,   0.152_512_22,  0.158_023_7,   0.163_494_4,   0.168_924_76,  0.174_315_24,\n    0.179_666_28,  0.184_978_3,   0.190_251_74,  0.195_486_98,  0.200_684_47,  0.205_844_63,\n    0.210_967_81,  0.216_054_44,  0.221_104_92,  0.226_119_6,   0.231_098_88,  0.236_043_11,\n    0.240_952_72,  0.245_828_,    0.250_669_36,  0.255_477_1,   0.260_251_64,  0.264_993_28,\n    0.269_702_37,  0.274_379_22,  0.279_024_18,  0.283_637_58,  0.288_219_72,  0.292_770_95,\n    0.297_291_52,  0.301_781_8,   0.306_242_1,   0.310_672_67,  0.315_073_85,  0.319_445_88,\n    0.323_789_12,  0.328_103_78,  0.332_390_2,   0.336_648_6,   0.340_879_3,   0.345_082_55,\n    0.349_258_63,  0.353_407_77,  0.357_530_27,  0.361_626_36,  0.365_696_34,  0.369_740_37,\n    0.373_758_76,  0.377_751_74,  0.381_719_56,  0.385_662_44,  0.389_580_64,  0.393_474_37,\n    0.397_343_84,  0.401_189_3,   0.405_011_,    0.408_809_07,  0.412_583_83,  0.416_335_46,\n    0.420_064_15,  0.423_770_13,  0.427_453_6,   0.431_114_76,  0.434_753_84,  0.438_370_97,\n    0.441_966_44,  0.445_540_4,   0.449_093_,    0.452_624_53,  0.456_135_06,  0.459_624_9,\n    0.463_094_12,  0.466_542_93,  0.469_971_57,  0.473_380_15,  0.476_768_94,  0.480_137_94,\n    0.483_487_52,  0.486_817_7,   0.490_128_73,  0.493_420_7,   0.496_693_88,  0.499_948_32,\n    0.503_184_26,  0.506_401_84,  0.509_601_2,   0.512_782_45,  0.515_945_85,  0.519_091_4,\n    0.522_219_5,   0.525_330_07,  0.528_423_25,  0.531_499_3,   0.534_558_36,  0.537_600_5,\n    0.540_625_93,  0.543_634_8,   0.546_627_04,  0.549_603_04,  0.552_562_83,  0.555_506_47,\n    0.558_434_3,   0.561_346_23,  0.564_242_5,   0.567_123_23,  0.569_988_5,   0.572_838_4,\n    0.575_673_2,   0.578_492_94,  0.581_297_7,   0.584_087_6,   0.586_862_8,   0.589_623_45,\n    0.592_369_56,  0.595_101_36,  0.597_818_9,   0.600_522_3,   0.603_211_6,   0.605_887_,\n    0.608_548_64,  0.611_196_6,   0.613_830_8,   0.616_451_56,  0.619_059_,    0.621_653_14,\n    0.624_234_,    0.626_801_85,  0.629_356_7,   0.631_898_64,  0.634_427_7,   0.636_944_2,\n    0.639_448_05,  0.641_939_34,  0.644_418_24,  0.646_884_86,  0.649_339_2,   0.651_781_4,\n    0.654_211_5,   0.656_629_74,  0.659_036_04,  0.661_430_6,   0.663_813_4,   0.666_184_66,\n    0.668_544_35,  0.670_892_6,   0.673_229_46,  0.675_555_05,  0.677_869_44,  0.680_172_74,\n    0.682_464_96,  0.684_746_2,   0.687_016_6,   0.689_276_2,   0.691_525_04,  0.693_763_3,\n    0.695_990_9,   0.698_208_03,  0.700_414_8,   0.702_611_1,   0.704_797_2,   0.706_973_1,\n    0.709_138_8,   0.711_294_5,   0.713_440_1,   0.715_575_9,   0.717_701_8,   0.719_817_9,\n    0.721_924_25,  0.724_020_96,  0.726_108_,    0.728_185_65,  0.730_253_8,   0.732_312_56,\n    0.734_361_95,  0.736_402_1,   0.738_433_1,   0.740_454_9,   0.742_467_6,\n];\n"
  },
  {
    "path": "tetanes-core/src/bus.rs",
    "content": "//! NES Memory/Data Bus implementation.\n//!\n//! <https://wiki.nesdev.org/w/index.php/CPU_memory_map>\n\nuse crate::{\n    apu::{Apu, Channel},\n    cart::Cart,\n    common::{Clock, NesRegion, Regional, Reset, ResetKind, Sample, Sram},\n    fs,\n    genie::GenieCode,\n    input::{Input, InputRegisters, Player},\n    mapper::{Map, Mapper},\n    mem::{ConstArray, RamState, Read, Write},\n    ppu::Ppu,\n};\nuse serde::{Deserialize, Serialize};\nuse std::{collections::HashMap, path::Path};\n\n/// NES Bus\n///\n/// <https://wiki.nesdev.org/w/index.php/CPU_memory_map>\n///\n/// |-----------------| $FFFF |-----------------|\n/// | PRG-ROM         |       |                 |\n/// |-----------------| $8000 |-----------------|\n/// | PRG-RAM or SRAM |       | PRG-RAM or SRAM |\n/// |-----------------| $6000 |-----------------|\n/// | Expansion       |       | Expansion       |\n/// | Modules         |       | Modules         |\n/// |-----------------| $4020 |-----------------|\n/// | APU/Input       |       |                 |\n/// | Registers       |       |                 |\n/// |- - - - - - - - -| $4000 |                 |\n/// | PPU Mirrors     |       | I/O Registers   |\n/// | $2000-$2007     |       |                 |\n/// |- - - - - - - - -| $2008 |                 |\n/// | PPU Registers   |       |                 |\n/// |-----------------| $2000 |-----------------|\n/// | WRAM Mirrors    |       |                 |\n/// | $0000-$07FF     |       |                 |\n/// |- - - - - - - - -| $0800 |                 |\n/// | WRAM            |       | 2K Internal     |\n/// |- - - - - - - - -| $0200 | Work RAM        |\n/// | Stack           |       |                 |\n/// |- - - - - - - - -| $0100 |                 |\n/// | Zero Page       |       |                 |\n/// |-----------------| $0000 |-----------------|\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\n#[repr(C)]\npub struct Bus {\n    /// Picture Processing Unit.\n    pub ppu: Ppu,\n    /// Audio Processing Unit.\n    pub apu: Apu,\n    /// Joypad and Zapper inputs.\n    pub input: Input,\n    // 2K NES Work Ram available to the CPU.\n    pub wram: Box<ConstArray<u8, { size::WRAM }>>,\n    /// Game GENIE codes.\n    pub genie_codes: HashMap<u16, GenieCode>,\n    /// Whatever was last read or written to to the Bus.\n    pub open_bus: u8,\n    /// RAM initialization state.\n    #[serde(skip)]\n    pub ram_state: RamState,\n    /// NES Region.\n    pub region: NesRegion,\n}\n\nimpl Default for Bus {\n    fn default() -> Self {\n        Self::new(NesRegion::default(), RamState::default())\n    }\n}\n\npub mod size {\n    // 2K NES Work Ram available to the CPU.\n    pub const WRAM: usize = 0x800;\n}\n\nimpl Bus {\n    pub fn new(region: NesRegion, ram_state: RamState) -> Self {\n        Self {\n            wram: Box::new(ConstArray::new()),\n            ppu: Ppu::new(region),\n            apu: Apu::new(region),\n            input: Input::new(region),\n            genie_codes: HashMap::new(),\n            open_bus: 0x00,\n            ram_state,\n            region,\n        }\n    }\n\n    pub fn load_cart(&mut self, cart: Cart) {\n        self.ppu.load_mapper(cart.mapper);\n    }\n\n    pub fn unload_cart(&mut self) {\n        self.ppu.load_mapper(Mapper::default());\n    }\n\n    #[must_use]\n    #[inline]\n    #[allow(clippy::missing_const_for_fn)] // false positive on non-const deref coercion\n    pub fn wram(&self) -> &[u8; size::WRAM] {\n        &self.wram\n    }\n\n    /// Add a Game Genie code to override memory reads/writes.\n    ///\n    /// # Errors\n    ///\n    /// Errors if genie code is invalid.\n    pub fn add_genie_code(&mut self, genie_code: GenieCode) {\n        let addr = genie_code.addr();\n        self.genie_codes.insert(addr, genie_code);\n    }\n\n    /// Remove a Game Genie code.\n    pub fn remove_genie_code(&mut self, code: &str) {\n        self.genie_codes.retain(|_, gc| gc.code() != code);\n    }\n\n    /// Remove all Game Genie codes.\n    pub fn clear_genie_codes(&mut self) {\n        self.genie_codes.clear();\n    }\n\n    fn genie_read(&self, addr: u16, val: u8) -> u8 {\n        self.genie_codes\n            .get(&addr)\n            .map_or(val, |genie_code| genie_code.read(val))\n    }\n\n    #[inline]\n    #[must_use]\n    pub fn audio_samples(&self) -> &[f32] {\n        &self.apu.audio_samples\n    }\n\n    #[inline]\n    pub fn clear_audio_samples(&mut self) {\n        self.apu.audio_samples.clear();\n    }\n\n    #[inline]\n    pub fn cpu_clock(&mut self) {\n        self.ppu.mapper.clock();\n        let output = self.ppu.mapper.output();\n        self.input.clock();\n        self.apu.add_mapper_output(output);\n        self.apu.clock_lazy();\n    }\n}\n\nimpl Read for Bus {\n    fn read(&mut self, addr: u16) -> u8 {\n        let addr = match addr {\n            0x0800..=0x1FFF => addr & 0x07FF,\n            0x2008..=0x3FFF => addr & 0x2007,\n            _ => addr,\n        };\n        self.open_bus = match addr {\n            0x0000..=0x07FF => self.wram[usize::from(addr)],\n            0x4100..=0xFFFF => {\n                let val = self.ppu.mapper.prg_read(addr);\n                self.genie_read(addr, val)\n            }\n            0x2002 => self.ppu.read_status(),\n            0x2004 => self.ppu.read_oamdata(),\n            0x2007 => self.ppu.read_data(),\n            0x4015 => self.apu.read_status(),\n            0x4016 => self.input.read(Player::One, &self.ppu),\n            0x4017 => self.input.read(Player::Two, &self.ppu),\n            0x2000 | 0x2001 | 0x2003 | 0x2005 | 0x2006 => self.ppu.open_bus,\n            _ => self.open_bus,\n        };\n        self.open_bus\n    }\n\n    fn peek(&self, addr: u16) -> u8 {\n        let addr = match addr {\n            0x0800..=0x1FFF => addr & 0x07FF,\n            0x2008..=0x3FFF => addr & 0x2007,\n            _ => addr,\n        };\n        match addr {\n            0x0000..=0x07FF => self.wram[usize::from(addr)],\n            0x4100..=0xFFFF => {\n                let val = self.ppu.mapper.prg_peek(addr);\n                self.genie_read(addr, val)\n            }\n            0x2002 => self.ppu.peek_status(),\n            0x2004 => self.ppu.peek_oamdata(),\n            0x2007 => self.ppu.peek_data(),\n            0x4015 => self.apu.peek_status(),\n            0x4016 => self.input.peek(Player::One, &self.ppu),\n            0x4017 => self.input.peek(Player::Two, &self.ppu),\n            0x2000 | 0x2001 | 0x2003 | 0x2005 | 0x2006 => self.ppu.open_bus,\n            _ => self.open_bus,\n        }\n    }\n}\n\nimpl Write for Bus {\n    fn write(&mut self, addr: u16, val: u8) {\n        self.open_bus = val;\n        let addr = match addr {\n            0x0800..=0x1FFF => addr & 0x07FF,\n            0x2008..=0x3FFF => addr & 0x2007,\n            _ => addr,\n        };\n        match addr {\n            0x0000..=0x07FF => self.wram[usize::from(addr)] = val,\n            0x4100..=0xFFFF => self.ppu.mapper.prg_write(addr, val),\n            0x2000 => self.ppu.write_ctrl(val),\n            0x2001 => self.ppu.write_mask(val),\n            0x2002 => self.ppu.open_bus = val,\n            0x2003 => self.ppu.write_oamaddr(val),\n            0x2004 => self.ppu.write_oamdata(val),\n            0x2005 => self.ppu.write_scroll(val),\n            0x2006 => self.ppu.write_addr(val),\n            0x2007 => self.ppu.write_data(val),\n            0x4000 => self.apu.write_ctrl(Channel::Pulse1, val),\n            0x4001 => self.apu.write_sweep(Channel::Pulse1, val),\n            0x4002 => self.apu.write_timer_lo(Channel::Pulse1, val),\n            0x4003 => self.apu.write_timer_hi(Channel::Pulse1, val),\n            0x4004 => self.apu.write_ctrl(Channel::Pulse2, val),\n            0x4005 => self.apu.write_sweep(Channel::Pulse2, val),\n            0x4006 => self.apu.write_timer_lo(Channel::Pulse2, val),\n            0x4007 => self.apu.write_timer_hi(Channel::Pulse2, val),\n            0x4008 => self.apu.write_linear_counter(val),\n            0x400A => self.apu.write_timer_lo(Channel::Triangle, val),\n            0x400B => self.apu.write_timer_hi(Channel::Triangle, val),\n            0x400C => self.apu.write_ctrl(Channel::Noise, val),\n            0x400E => self.apu.write_timer_lo(Channel::Noise, val),\n            0x400F => self.apu.write_length(Channel::Noise, val),\n            0x4010 => self.apu.write_timer_lo(Channel::Dmc, val),\n            0x4011 => self.apu.write_dmc_output(val),\n            0x4012 => self.apu.write_dmc_addr(val),\n            0x4013 => self.apu.write_length(Channel::Dmc, val),\n            0x4015 => self.apu.write_status(val),\n            0x4016 => self.input.write(val),\n            0x4017 => self.apu.write_frame_counter(val),\n            0x4014 => (), // DMA handled by CPU\n            _ => (),\n        }\n    }\n}\n\nimpl Regional for Bus {\n    fn region(&self) -> NesRegion {\n        self.region\n    }\n\n    fn set_region(&mut self, region: NesRegion) {\n        self.region = region;\n        self.ppu.set_region(region);\n        self.apu.set_region(region);\n        self.input.set_region(region);\n    }\n}\n\nimpl Reset for Bus {\n    fn reset(&mut self, kind: ResetKind) {\n        if kind == ResetKind::Hard {\n            self.ram_state.fill(&mut **self.wram);\n        }\n        self.ppu.reset(kind);\n        self.apu.reset(kind);\n    }\n}\n\nimpl Sram for Bus {\n    fn save(&self, path: impl AsRef<Path>) -> fs::Result<()> {\n        self.ppu.mapper.save(path)\n    }\n\n    fn load(&mut self, path: impl AsRef<Path>) -> fs::Result<()> {\n        self.ppu.mapper.load(path)\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use crate::{\n        mapper::{Cnrom, Nrom},\n        mem::Memory,\n    };\n\n    #[test]\n    fn load_cart_values() {\n        let mut bus = Bus::default();\n        #[rustfmt::skip]\n        let rom: [u8; 16] = [\n            0x4E, 0x45, 0x53, 0x1A,\n            0x00, 0x00, 0x02, 0x00,\n            0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00,\n        ];\n        let cart = Cart::from_rom(\"load_cart_test\", &mut rom.as_slice(), RamState::default())\n            .expect(\"valid cart\");\n\n        let expected_mirroring = cart.mirroring();\n        let expected_region = cart.region();\n        bus.load_cart(cart);\n\n        assert_eq!(bus.ppu.region(), expected_region, \"ppu region\");\n        assert_eq!(bus.apu.region(), expected_region, \"apu region\");\n        assert!(\n            matches!(bus.ppu.mapper, Mapper::Nrom(_)),\n            \"mapper is Nrom: {:?}\",\n            bus.ppu.mapper\n        );\n        assert_eq!(bus.ppu.mirroring(), expected_mirroring, \"mirroring\");\n    }\n\n    #[test]\n    fn load_cart_chr_rom() {\n        let mut bus = Bus::default();\n        let mut cart = Cart::empty();\n        let mut chr_rom = Memory::new(0x2000);\n        chr_rom.fill(0x66);\n        // Cnrom doesn't provide CHR-RAM\n        cart.mapper = Cnrom::load(&cart, chr_rom, Memory::new(0x4000)).unwrap();\n        bus.load_cart(cart);\n\n        bus.write(0x2006, 0x00);\n        bus.write(0x2006, 0x00);\n        bus.read(0x2007);\n        assert_eq!(bus.read(0x2007), 0x66, \"chr_rom start\");\n        bus.write(0x2006, 0x1F);\n        bus.write(0x2006, 0xFF);\n        bus.read(0x2007);\n        assert_eq!(bus.read(0x2007), 0x66, \"chr_rom end\");\n\n        // Writes disallowed\n        bus.write(0x2006, 0x00);\n        bus.write(0x2006, 0x10);\n        bus.write(0x2007, 0x77);\n\n        bus.write(0x2006, 0x00);\n        bus.write(0x2006, 0x10);\n        bus.read(0x2007);\n        assert_eq!(bus.read(0x2007), 0x66, \"chr_rom read-only\");\n    }\n\n    #[test]\n    fn load_cart_chr_ram() {\n        let mut bus = Bus::default();\n        let mut cart = Cart::empty();\n        cart.mapper = Nrom::load(&cart, Memory::empty(), Memory::new(cart.prg_rom_size)).unwrap();\n        if let Mapper::Nrom(nrom) = &mut cart.mapper {\n            nrom.chr.fill(0x66);\n        }\n        bus.load_cart(cart);\n\n        bus.write(0x2006, 0x00);\n        bus.write(0x2006, 0x00);\n        bus.read(0x2007);\n        assert_eq!(bus.read(0x2007), 0x66, \"chr_ram start\");\n        bus.write(0x2006, 0x1F);\n        bus.write(0x2006, 0xFF);\n        bus.read(0x2007);\n        assert_eq!(bus.read(0x2007), 0x66, \"chr_ram end\");\n\n        // Writes allowed\n        bus.write(0x2006, 0x10);\n        bus.write(0x2006, 0x00);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        bus.ppu.clock();\n        bus.ppu.clock();\n        bus.write(0x2007, 0x77);\n\n        bus.write(0x2006, 0x10);\n        bus.write(0x2006, 0x00);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        bus.ppu.clock();\n        bus.ppu.clock();\n        bus.read(0x2007);\n        assert_eq!(bus.read(0x2007), 0x77, \"chr_ram write\");\n    }\n\n    #[test]\n    fn genie_codes() {\n        let mut bus = Bus::default();\n        let mut cart = Cart::empty();\n        let mut prg_rom = Memory::new(0x8000);\n\n        let code = \"YYKPOYZZ\"; // The Legend of Zelda: New character with 8 Hearts\n        let addr = 0x9F41;\n        let orig_value = 0x22; // 3 Hearts\n        let new_value = 0x77; // 8 Hearts\n\n        prg_rom[(addr & 0x7FFF) as usize] = orig_value;\n        cart.mapper = Nrom::load(&cart, Memory::new(cart.chr_rom_size), prg_rom).unwrap();\n\n        bus.load_cart(cart);\n        bus.add_genie_code(GenieCode::new(code.to_string()).expect(\"valid genie code\"));\n\n        assert_eq!(bus.peek(addr), new_value, \"peek code value\");\n        assert_eq!(bus.read(addr), new_value, \"read code value\");\n        bus.remove_genie_code(code);\n        assert_eq!(bus.peek(addr), orig_value, \"peek orig value\");\n        assert_eq!(bus.read(addr), orig_value, \"read orig value\");\n    }\n\n    #[test]\n    fn clock() {\n        let mut bus = Bus::default();\n\n        bus.ppu.clock_to(12);\n        assert_eq!(bus.ppu.master_clock, 12, \"ppu clock\");\n        bus.cpu_clock();\n        assert_eq!(bus.apu.master_clock, 1, \"apu clock\");\n    }\n\n    #[test]\n    fn read_write_ram() {\n        let mut bus = Bus::default();\n\n        bus.write(0x0001, 0x66);\n        assert_eq!(bus.peek(0x0001), 0x66, \"peek ram\");\n        assert_eq!(bus.read(0x0001), 0x66, \"read ram\");\n        assert_eq!(bus.read(0x0801), 0x66, \"peek mirror 1\");\n        assert_eq!(bus.read(0x0801), 0x66, \"read mirror 1\");\n        assert_eq!(bus.read(0x1001), 0x66, \"peek mirror 2\");\n        assert_eq!(bus.read(0x1001), 0x66, \"read mirror 2\");\n        assert_eq!(bus.read(0x1801), 0x66, \"peek mirror 3\");\n        assert_eq!(bus.read(0x1801), 0x66, \"read mirror 3\");\n\n        bus.write(0x0802, 0x77);\n        assert_eq!(bus.read(0x0002), 0x77, \"write mirror 1\");\n        bus.write(0x1002, 0x88);\n        assert_eq!(bus.read(0x0002), 0x88, \"write mirror 2\");\n        bus.write(0x1802, 0x99);\n        assert_eq!(bus.read(0x0002), 0x99, \"write mirror 3\");\n    }\n\n    #[test]\n    #[ignore = \"todo\"]\n    fn read_write_ppu() {\n        // read: PPUSTATUS, OAMDATA, PPUDATA + Mirrors\n        // peek: PPUSTATUS, OAMDATA, PPUDATA + Mirrors\n        // write: PPUCTRL, PPUMASK, OAMADDR, OAMDATA, PPUSCROLL, PPUADDR, PPUDATA + Mirrors\n        todo!()\n    }\n\n    #[test]\n    #[ignore = \"todo\"]\n    fn read_write_apu() {\n        // read: APU_STATUS\n        // write: APU_STATUS, APU_FRAME_COUNTER\n        todo!()\n    }\n\n    #[test]\n    #[ignore = \"todo\"]\n    fn write_apu_pulse() {\n        // write: APU_CTRL_PULSE1, APU_SWEEP_PULSE1, APU_TIMER_LO_PULSE1, APU_TIMER_HI_PULSE1\n        // write: APU_CTRL_PULSE2, APU_SWEEP_PULSE2, APU_TIMER_LO_PULSE2, APU_TIMER_HI_PULSE2\n        todo!();\n    }\n\n    #[test]\n    #[ignore = \"todo\"]\n    fn write_apu_triangle() {\n        // write: APU_LIN_CTR_TRIANGLE, APU_TIMER_LO_TRIANGLE, APU_TIMER_HI_TRIANGLE\n        todo!();\n    }\n\n    #[test]\n    #[ignore = \"todo\"]\n    fn write_apu_noise() {\n        // write: APU_CTRL_NOISE, APU_TIMER_NOISE, APU_LENGTH_NOISE\n        todo!()\n    }\n\n    #[test]\n    #[ignore = \"todo\"]\n    fn write_dmc() {\n        // write: APU_TIMER_DMC, APU_OUTPUT_DMC, APU_ADDR_LOAD_DMC, APU_LENGTH_DMC\n        todo!()\n    }\n\n    #[test]\n    #[ignore = \"todo\"]\n    fn read_write_input() {\n        todo!()\n    }\n\n    #[test]\n    #[ignore = \"todo\"]\n    fn read_write_mapper() {\n        todo!()\n    }\n\n    #[test]\n    #[ignore = \"todo\"]\n    fn reset() {\n        todo!()\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/cart.rs",
    "content": "//! NES cartridge implementation.\n\nuse crate::{\n    common::{NesRegion, Regional},\n    fs,\n    mapper::{\n        self, Axrom, BandaiFCG, Bf909x, Bnrom, Cnrom, ColorDreams, Exrom, Fxrom, Gxrom,\n        JalecoSs88006, Mapper, Mmc1Revision, Namco163, Nina003006, Nrom, Pxrom, SunsoftFme7, Sxrom,\n        Txrom, Uxrom, Vrc6, m024_m026_vrc6::Revision as Vrc6Revision, m034_nina001::Nina001,\n    },\n    mem::{Memory, RamState},\n    ppu::Mirroring,\n};\nuse serde::{Deserialize, Serialize};\nuse std::{\n    fs::File,\n    io::{BufReader, Read},\n    path::Path,\n};\nuse thiserror::Error;\nuse tracing::{debug, error, info};\n\nconst PRG_ROM_BANK_SIZE: usize = 0x4000;\nconst CHR_ROM_BANK_SIZE: usize = 0x2000;\n\npub type Result<T> = std::result::Result<T, Error>;\n\n#[derive(Error, Debug)]\n#[must_use]\npub enum Error {\n    #[error(\"invalid nes header (found: ${value:04X} at byte: {byte}). {message}\")]\n    InvalidHeader {\n        byte: u8,\n        value: u8,\n        message: String,\n    },\n    #[error(\"mapper: {0}\")]\n    InvalidMapper(#[from] mapper::Error),\n    #[error(\"{context}: {source:?}\")]\n    Io {\n        context: String,\n        source: std::io::Error,\n    },\n}\n\nimpl Error {\n    pub fn io(source: std::io::Error, context: impl Into<String>) -> Self {\n        Self::Io {\n            context: context.into(),\n            source,\n        }\n    }\n}\n\n/// An NES cartridge.\n#[derive(Debug)]\n#[must_use]\npub struct Cart {\n    pub name: String,\n    pub header: NesHeader,\n    pub region: NesRegion,\n    pub ram_state: RamState,\n    pub mapper: Mapper,\n    pub chr_rom: Memory<Box<[u8]>>, // Character ROM\n    pub prg_rom: Memory<Box<[u8]>>, // Program ROM\n    pub chr_rom_size: usize,\n    pub chr_ram_size: usize,\n    pub prg_rom_size: usize,\n    pub prg_ram_size: usize,\n    pub game_info: Option<GameInfo>,\n}\n\nimpl Default for Cart {\n    fn default() -> Self {\n        Self::empty()\n    }\n}\n\nimpl Cart {\n    pub fn empty() -> Self {\n        Self {\n            name: \"Empty Cart\".to_string(),\n            header: NesHeader::default(),\n            region: NesRegion::default(),\n            ram_state: RamState::default(),\n            mapper: Mapper::none(),\n            chr_rom: Memory::new(CHR_ROM_BANK_SIZE),\n            prg_rom: Memory::new(PRG_ROM_BANK_SIZE),\n            chr_rom_size: CHR_ROM_BANK_SIZE,\n            chr_ram_size: 0,\n            prg_rom_size: PRG_ROM_BANK_SIZE,\n            prg_ram_size: 0,\n            game_info: None,\n        }\n    }\n\n    /// Load `Cart` from a ROM path.\n    ///\n    /// # Errors\n    ///\n    /// If the NES header is corrupted, the ROM file cannot be read, or the data does not match\n    /// the header, then an error is returned.\n    pub fn from_path<P: AsRef<Path>>(path: P, ram_state: RamState) -> Result<Self> {\n        let path = path.as_ref();\n        let mut rom = BufReader::new(\n            File::open(path)\n                .map_err(|err| Error::io(err, format!(\"failed to open rom {path:?}\")))?,\n        );\n        Self::from_rom(path.to_string_lossy(), &mut rom, ram_state)\n    }\n\n    /// Load `Cart` from ROM data.\n    ///\n    /// # Errors\n    ///\n    /// If the NES header is invalid, or the ROM data does not match the header, then an error is\n    /// returned.\n    pub fn from_rom<S, F>(name: S, mut rom_data: &mut F, ram_state: RamState) -> Result<Self>\n    where\n        S: ToString,\n        F: Read,\n    {\n        let name = name.to_string();\n        let mut header = NesHeader::load(&mut rom_data)?;\n        debug!(\"{header:?}\");\n\n        let prg_rom_size = (header.prg_rom_banks as usize) * PRG_ROM_BANK_SIZE;\n        let mut prg_rom = Memory::new(prg_rom_size);\n        rom_data.read_exact(&mut prg_rom).map_err(|err| {\n            if let std::io::ErrorKind::UnexpectedEof = err.kind() {\n                Error::InvalidHeader {\n                    byte: 4,\n                    value: header.prg_rom_banks as u8,\n                    message: format!(\n                        \"expected `{}` prg-rom banks ({prg_rom_size} total bytes)\",\n                        header.prg_rom_banks\n                    ),\n                }\n            } else {\n                Error::io(err, \"failed to read prg-rom\")\n            }\n        })?;\n\n        let prg_ram_size = Self::calculate_ram_size(header.prg_ram_shift)?;\n\n        let chr_rom_size = (header.chr_rom_banks as usize) * CHR_ROM_BANK_SIZE;\n        let mut chr_rom = Memory::new(chr_rom_size);\n        if chr_rom_size > 0 {\n            rom_data.read_exact(&mut chr_rom).map_err(|err| {\n                if let std::io::ErrorKind::UnexpectedEof = err.kind() {\n                    Error::InvalidHeader {\n                        byte: 5,\n                        value: header.chr_rom_banks as u8,\n                        message: format!(\n                            \"expected `{}` chr-rom banks ({prg_rom_size} total bytes)\",\n                            header.chr_rom_banks\n                        ),\n                    }\n                } else {\n                    Error::io(err, \"failed to read chr-rom\")\n                }\n            })?;\n        }\n\n        let chr_ram_size = if chr_rom_size > 0 {\n            0\n        } else {\n            Self::calculate_ram_size(header.chr_ram_shift)?\n        };\n\n        let game_info = Self::lookup_info(&prg_rom, &chr_rom);\n        if let Some(game_info) = &game_info {\n            header.mapper_num = game_info.mapper_num;\n        }\n        let region = if matches!(header.variant, NesVariant::INes | NesVariant::Nes2) {\n            match header.tv_mode {\n                1 => NesRegion::Pal,\n                3 => NesRegion::Dendy,\n                _ => game_info\n                    .as_ref()\n                    .map(|info| info.region)\n                    .unwrap_or_default(),\n            }\n        } else {\n            game_info\n                .as_ref()\n                .map(|info| info.region)\n                .unwrap_or_default()\n        };\n\n        let mut cart = Self {\n            name,\n            header,\n            region,\n            ram_state,\n            mapper: Mapper::none(),\n            chr_rom: chr_rom.clone(),\n            prg_rom: prg_rom.clone(),\n            chr_rom_size,\n            chr_ram_size,\n            prg_rom_size,\n            prg_ram_size,\n            game_info,\n        };\n        cart.mapper = match cart.header.mapper_num {\n            0 => Nrom::load(&cart, chr_rom, prg_rom)?,\n            1 => Sxrom::load(&cart, chr_rom, prg_rom, Mmc1Revision::BC)?,\n            2 => Uxrom::load(&cart, chr_rom, prg_rom)?,\n            3 => Cnrom::load(&cart, chr_rom, prg_rom)?,\n            4 | 76 | 88 | 95 | 154 | 206 => Txrom::load(&cart, chr_rom, prg_rom)?,\n            5 => Exrom::load(&cart, chr_rom, prg_rom)?,\n            7 => Axrom::load(&cart, chr_rom, prg_rom)?,\n            9 => Pxrom::load(&cart, chr_rom, prg_rom)?,\n            10 => Fxrom::load(&cart, chr_rom, prg_rom)?,\n            11 | 144 => ColorDreams::load(&cart, chr_rom, prg_rom)?,\n            16 | 153 | 157 | 159 => BandaiFCG::load(&cart, chr_rom, prg_rom)?,\n            18 => JalecoSs88006::load(&cart, chr_rom, prg_rom)?,\n            19 | 210 => Namco163::load(&cart, chr_rom, prg_rom)?,\n            24 => Vrc6::load(&cart, chr_rom, prg_rom, Vrc6Revision::A)?,\n            26 => Vrc6::load(&cart, chr_rom, prg_rom, Vrc6Revision::B)?,\n            34 => {\n                // ≥ 16K implies NINA-001; ≤ 8K implies BNROM\n                if chr_rom_size >= 0x4000 {\n                    Nina001::load(&cart, chr_rom, prg_rom)?\n                } else {\n                    Bnrom::load(&cart, chr_rom, prg_rom)?\n                }\n            }\n            66 => Gxrom::load(&cart, chr_rom, prg_rom)?,\n            69 => SunsoftFme7::load(&cart, chr_rom, prg_rom)?,\n            71 => Bf909x::load(&cart, chr_rom, prg_rom)?,\n            79 | 113 | 146 => Nina003006::load(&cart, chr_rom, prg_rom)?,\n            155 => Sxrom::load(&cart, chr_rom, prg_rom, Mmc1Revision::A)?,\n            _ => Mapper::none(),\n        };\n\n        info!(\"loaded ROM `{cart}`\");\n        debug!(\"{cart:?}\");\n\n        Ok(cart)\n    }\n\n    #[must_use]\n    #[allow(clippy::missing_const_for_fn)] // false positive on non-const deref coercion\n    pub fn name(&self) -> &str {\n        &self.name\n    }\n\n    #[must_use]\n    pub const fn is_ines(&self) -> bool {\n        matches!(\n            self.header.variant,\n            NesVariant::ArchaicINes | NesVariant::INes07 | NesVariant::INes\n        )\n    }\n\n    #[must_use]\n    pub const fn is_nes2(&self) -> bool {\n        matches!(self.header.variant, NesVariant::Nes2)\n    }\n\n    /// Returns whether this cartridge has battery-backed Save RAM.\n    #[must_use]\n    pub const fn battery_backed(&self) -> bool {\n        self.header.flags & 0x02 == 0x02\n    }\n\n    /// Returns `RamState`.\n    pub const fn ram_state(&self) -> RamState {\n        self.ram_state\n    }\n\n    /// Returns hardware configured `Mirroring`.\n    pub fn mirroring(&self) -> Mirroring {\n        if self.header.flags & 0x08 == 0x08 {\n            Mirroring::FourScreen\n        } else {\n            match self.header.flags & 0x01 {\n                0 => Mirroring::Horizontal,\n                1 => Mirroring::Vertical,\n                _ => unreachable!(\"impossible mirroring\"),\n            }\n        }\n    }\n\n    /// Returns the Mapper number for this Cart.\n    #[must_use]\n    pub fn mapper_num(&self) -> u16 {\n        self.game_info\n            .as_ref()\n            .map(|info| info.mapper_num)\n            .unwrap_or(self.header.mapper_num)\n    }\n\n    /// Returns the Sub-Mapper number for this Cart.\n    #[must_use]\n    pub fn submapper_num(&self) -> u8 {\n        self.game_info\n            .as_ref()\n            .map(|info| info.submapper_num)\n            .unwrap_or(self.header.submapper_num)\n    }\n\n    /// Returns the Mapper and Board name for this Cart.\n    #[must_use]\n    pub fn mapper_board(&self) -> &'static str {\n        NesHeader::mapper_board(self.mapper_num())\n    }\n\n    pub fn chr_size(&self) -> usize {\n        match &self.mapper {\n            Mapper::None(_) => 0,\n            Mapper::Nrom(nrom) => nrom.chr.len(),\n            Mapper::Sxrom(sxrom) => sxrom.chr.len(),\n            Mapper::Uxrom(uxrom) => uxrom.chr.len(),\n            Mapper::Cnrom(cnrom) => cnrom.chr_rom.len(),\n            Mapper::Txrom(txrom) => txrom.chr.len(),\n            Mapper::Exrom(exrom) => exrom.chr_rom.len(),\n            Mapper::Axrom(axrom) => axrom.chr.len(),\n            Mapper::Pxrom(pxrom) => pxrom.chr_rom.len(),\n            Mapper::Fxrom(fxrom) => fxrom.chr_rom.len(),\n            Mapper::ColorDreams(color_dreams) => color_dreams.chr_rom.len(),\n            Mapper::BandaiFCG(bandai_fcg) => bandai_fcg.chr.len(),\n            Mapper::JalecoSs88006(jaleco_ss88006) => jaleco_ss88006.chr_rom.len(),\n            Mapper::Namco163(namco163) => namco163.chr_rom.len(),\n            Mapper::Vrc6(vrc6) => vrc6.chr_rom.len(),\n            Mapper::Bnrom(bnrom) => bnrom.chr.len(),\n            Mapper::Gxrom(gxrom) => gxrom.chr_rom.len(),\n            Mapper::Nina001(nina001) => nina001.chr_rom.len(),\n            Mapper::SunsoftFme7(sunsoft_fme7) => sunsoft_fme7.chr_rom.len(),\n            Mapper::Bf909x(bf909x) => bf909x.chr.len(),\n            Mapper::Nina003006(nina003006) => nina003006.chr_rom.len(),\n        }\n    }\n\n    /// Returns CHR-RAM sized based on the Cart header, or defaults to given size.\n    pub(crate) fn chr_rom_or_ram(\n        &self,\n        chr_rom: Memory<Box<[u8]>>,\n        size: usize,\n    ) -> (Memory<Box<[u8]>>, bool) {\n        if chr_rom.is_empty() {\n            (\n                Memory::with_ram_state(\n                    if self.chr_ram_size > 0 {\n                        self.chr_ram_size\n                    } else {\n                        size\n                    },\n                    self.ram_state,\n                ),\n                true,\n            )\n        } else {\n            (chr_rom, false)\n        }\n    }\n\n    /// Returns PRG-RAM sized based on the Cart header, or defaults to given size.\n    pub(crate) fn prg_ram_or_default(&self, size: usize) -> Memory<Box<[u8]>> {\n        Memory::with_ram_state(\n            if self.prg_ram_size > 0 {\n                self.prg_ram_size\n            } else {\n                size\n            },\n            self.ram_state,\n        )\n    }\n\n    fn calculate_ram_size(value: u8) -> Result<usize> {\n        if value > 0 {\n            64usize\n                .checked_shl(value.into())\n                .ok_or_else(|| Error::InvalidHeader {\n                    byte: 11,\n                    value,\n                    message: \"header ram size larger than 64\".to_string(),\n                })\n        } else {\n            Ok(0)\n        }\n    }\n\n    fn lookup_info(prg_rom: &[u8], chr: &[u8]) -> Option<GameInfo> {\n        const GAME_DB: &[u8] = include_bytes!(\"../game_db.dat\");\n\n        let Ok(games) = fs::load_bytes::<Vec<GameInfo>>(GAME_DB) else {\n            error!(\"failed to load `game_regions.dat`\");\n            return None;\n        };\n\n        let mut crc32 = fs::compute_crc32(prg_rom);\n        if !chr.is_empty() {\n            crc32 = fs::compute_combine_crc32(crc32, chr);\n        }\n\n        match games.binary_search_by(|game| game.crc32.cmp(&crc32)) {\n            Ok(index) => {\n                info!(\n                    \"found game matching crc: {crc32:#010X}. info: {:?}\",\n                    games[index]\n                );\n                Some(games[index].clone())\n            }\n            Err(_) => {\n                info!(\"no game found matching crc: {crc32:#010X}\");\n                None\n            }\n        }\n    }\n}\n\nimpl Regional for Cart {\n    fn region(&self) -> NesRegion {\n        self.region\n    }\n\n    fn set_region(&mut self, region: NesRegion) {\n        self.region = region;\n    }\n}\n\nimpl std::fmt::Display for Cart {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {\n        write!(\n            f,\n            \"{} - {}, CHR-ROM: {}K, CHR-RAM: {}K, PRG-ROM: {}K, PRG-RAM: {}K, Mirroring: {:?}, Battery: {}\",\n            self.name,\n            self.mapper_board(),\n            self.chr_rom_size / 0x0400,\n            self.chr_ram_size / 0x0400,\n            self.prg_rom_size / 0x0400,\n            self.prg_ram_size / 0x0400,\n            self.mirroring(),\n            self.battery_backed(),\n        )\n    }\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct GameInfo {\n    pub crc32: u32,\n    pub region: NesRegion,\n    pub mapper_num: u16,\n    pub submapper_num: u8,\n}\n\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]\n#[must_use]\npub enum NesVariant {\n    #[default]\n    ArchaicINes,\n    INes07,\n    INes,\n    Nes2,\n}\n\n/// An `iNES` or `NES 2.0` formatted header representing hardware specs of a given NES cartridge.\n///\n/// <https://wiki.nesdev.org/w/index.php/INES>\n/// <https://wiki.nesdev.org/w/index.php/NES_2.0>\n/// <https://nesdev.org/NESDoc.pdf> (page 28)\n#[derive(Default, Copy, Clone, PartialEq, Eq)]\n#[must_use]\npub struct NesHeader {\n    pub variant: NesVariant,\n    pub mapper_num: u16,    // The primary mapper number\n    pub submapper_num: u8,  // NES 2.0 https://wiki.nesdev.org/w/index.php/NES_2.0_submappers\n    pub flags: u8,          // Mirroring, Battery, Trainer, VS Unisystem, Playchoice-10, NES 2.0\n    pub prg_rom_banks: u16, // Number of 16KB PRG-ROM banks (Program ROM)\n    pub chr_rom_banks: u16, // Number of 8KB CHR-ROM banks (Character ROM)\n    pub prg_ram_shift: u8,  // NES 2.0 PRG-RAM\n    pub chr_ram_shift: u8,  // NES 2.0 CHR-RAM\n    pub tv_mode: u8,        // NES 2.0 NTSC/PAL indicator\n    pub vs_data: u8,        // NES 2.0 VS System data\n}\n\nimpl NesHeader {\n    /// Load `NesHeader` from a ROM path.\n    ///\n    /// # Errors\n    ///\n    /// If the NES header is corrupted, the ROM file cannot be read, or the data does not match\n    /// the header, then an error is returned.\n    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {\n        let path = path.as_ref();\n        let mut rom = BufReader::new(\n            File::open(path)\n                .map_err(|err| Error::io(err, format!(\"failed to open rom {path:?}\")))?,\n        );\n        Self::load(&mut rom)\n    }\n\n    /// Load `NesHeader` from ROM data.\n    ///\n    /// # Errors\n    ///\n    /// If the NES header is invalid, then an error is returned.\n    pub fn load<F: Read>(rom_data: &mut F) -> Result<Self> {\n        let mut header = [0u8; 16];\n        rom_data.read_exact(&mut header).map_err(|err| {\n            if let std::io::ErrorKind::UnexpectedEof = err.kind() {\n                Error::InvalidHeader {\n                    byte: 0,\n                    value: 0,\n                    message: \"expected 16-byte header\".to_string(),\n                }\n            } else {\n                Error::io(err, \"failed to read nes header\")\n            }\n        })?;\n\n        // Header checks\n        if header[0..4] != *b\"NES\\x1a\" {\n            return Err(Error::InvalidHeader {\n                byte: 0,\n                value: header[0],\n                message: \"nes header signature not found\".to_string(),\n            });\n        }\n        if (header[7] & 0x0C) == 0x04 {\n            return Err(Error::InvalidHeader {\n                byte: 7,\n                value: header[7],\n                message: \"header is corrupted by `DiskDude!`. repair and try again\".to_string(),\n            });\n        }\n        if (header[7] & 0x0C) == 0x0C {\n            return Err(Error::InvalidHeader {\n                byte: 7,\n                value: header[7],\n                message: \"unrecognized header format. repair and try again\".to_string(),\n            });\n        }\n\n        let mut prg_rom_banks = u16::from(header[4]);\n        let mut chr_rom_banks = u16::from(header[5]);\n        // Upper 4 bits of flags 6 = D0..D3 and 7 = D4..D7\n        let mut mapper_num = u16::from(((header[6] & 0xF0) >> 4) | (header[7] & 0xF0));\n        // Lower 4 bits of flag 6 = D0..D3, upper 4 bits of flag 7 = D4..D7\n        let flags = (header[6] & 0x0F) | ((header[7] & 0x0F) << 4);\n\n        // NES 2.0 Format\n        let mut submapper_num = 0;\n        let mut prg_ram_shift = 0;\n        let mut chr_ram_shift = 0;\n        let mut tv_mode = 0;\n        let mut vs_data = 0;\n        // If D2..D3 of flag 7 == 2, then NES 2.0 (supports bytes 0-15)\n        let variant = if header[7] & 0x0C == 0x08 {\n            // lower 4 bits of flag 8 = D8..D11 of mapper num\n            mapper_num |= u16::from(header[8] & 0x0F) << 8;\n            // upper 4 bits of flag 8 = D0..D3 of submapper\n            submapper_num = (header[8] & 0xF0) >> 4;\n            // lower 4 bits of flag 9 = D8..D11 of prg_rom_size\n            prg_rom_banks |= u16::from(header[9] & 0x0F) << 8;\n            // upper 4 bits of flag 9 = D8..D11 of chr_rom_size\n            chr_rom_banks |= u16::from(header[9] & 0xF0) << 4;\n            prg_ram_shift = header[10];\n            chr_ram_shift = header[11];\n            tv_mode = header[12];\n            vs_data = header[13];\n\n            if prg_ram_shift & 0x0F == 0x0F || prg_ram_shift & 0xF0 == 0xF0 {\n                return Err(Error::InvalidHeader {\n                    byte: 10,\n                    value: prg_ram_shift,\n                    message: \"invalid prg-ram size in header\".to_string(),\n                });\n            }\n            if chr_ram_shift & 0x0F == 0x0F || chr_ram_shift & 0xF0 == 0xF0 {\n                return Err(Error::InvalidHeader {\n                    byte: 11,\n                    value: chr_ram_shift,\n                    message: \"invalid chr-ram size in header\".to_string(),\n                });\n            }\n            if chr_ram_shift & 0xF0 == 0xF0 {\n                return Err(Error::InvalidHeader {\n                    byte: 11,\n                    value: chr_ram_shift,\n                    message: \"battery-backed chr-ram is currently not supported\".to_string(),\n                });\n            }\n            NesVariant::Nes2\n        } else if header[7] & 0x0C == 0x04 {\n            // If D2..D3 of flag 7 == 1, then archaic iNES (supports bytes 0-7)\n            for (i, value) in header.iter().enumerate().take(16).skip(8) {\n                if *value > 0 {\n                    return Err(Error::InvalidHeader {\n                        byte: i as u8,\n                        value: *value,\n                        message: format!(\n                            \"unrecogonized data found at header byte {i}. repair and try again\"\n                        ),\n                    });\n                }\n            }\n            NesVariant::ArchaicINes\n        } else if header[7] & 0x0C == 00 && header[12..=15].iter().all(|v| *v == 0) {\n            // If D2..D3 of flag 7 == 0 and bytes 12-15 are all 0, then iNES (supports bytes 0-9)\n            NesVariant::INes\n        } else {\n            // Else iNES 0.7 or archaic iNES (supports mapper high nibble)\n            NesVariant::INes07\n        };\n\n        // Trainer\n        if flags & 0x04 == 0x04 {\n            return Err(Error::InvalidHeader {\n                byte: 6,\n                value: header[6],\n                message: \"trained roms are currently not supported.\".to_string(),\n            });\n        }\n\n        Ok(Self {\n            variant,\n            mapper_num,\n            submapper_num,\n            flags,\n            prg_rom_banks,\n            chr_rom_banks,\n            prg_ram_shift,\n            chr_ram_shift,\n            tv_mode,\n            vs_data,\n        })\n    }\n\n    #[must_use]\n    pub const fn mapper_board(mapper_num: u16) -> &'static str {\n        match mapper_num {\n            0 => \"Mapper 000 - NROM\",\n            1 => \"Mapper 001 - SxROM/MMC1B/C\",\n            2 => \"Mapper 002 - UxROM\",\n            3 => \"Mapper 003 - CNROM\",\n            4 => \"Mapper 004 - TxROM/MMC3/MMC6\",\n            5 => \"Mapper 005 - ExROM/MMC5\",\n            6 => \"Mapper 006 - FFE 1M/2M\",\n            7 => \"Mapper 007 - AxROM\",\n            8 => \"Mapper 008 - FFE 1M/2M\", // Also Mapper 006 Submapper 4\n            9 => \"Mapper 009 - PxROM/MMC2\",\n            10 => \"Mapper 010 - FxROM/MMC4\",\n            11 => \"Mapper 011 - Color Dreams\",\n            12 => \"Mapper 012 - Gouder/FFE 4M/MMC3\",\n            13 => \"Mapper 013 - CPROM\",\n            14 => \"Mapper 014 - UNL SL1632\",\n            15 => \"Mapper 015 - K1029/30\",\n            16 => \"Mapper 016 - Bandai FCG\",\n            17 => \"Mapper 017 - FFE\",\n            18 => \"Mapper 018 - Jaleco SS 88006\",\n            19 => \"Mapper 019 - Namco 129/163\",\n            20 => \"Mapper 020 - FDS\",\n            21 => \"Mapper 021 - Vrc4a/Vrc4c\",\n            22 => \"Mapper 022 - Vrc2a\",\n            23 => \"Mapper 023 - Vrc4e\",\n            24 => \"Mapper 024 - Vrc6a\",\n            25 => \"Mapper 025 - Vrc4b\",\n            26 => \"Mapper 026 - Vrc6b\",\n            27 => \"Mapper 027 - Vrc4x\",\n            28 => \"Mapper 028 - Action 53\",\n            29 => \"Mapper 029 - Sealie Computing\",\n            30 => \"Mapper 030 - UNROM 512\",\n            31 => \"Mapper 031 - NSF\",\n            32 => \"Mapper 032 - Irem G101\",\n            33 => \"Mapper 033 - Taito TC0190\",\n            34 => \"Mapper 034 - BNROM/NINA-001\",\n            35 => \"Mapper 035 - JY Company\",\n            36 => \"Mapper 036 - TXC 22000\",\n            37 => \"Mapper 037 - MMC3 Multicart\",\n            38 => \"Mapper 038 - UNL PCI556\",\n            39 => \"Mapper 039 - Subor\",\n            40 => \"Mapper 040 - NTDEC 2722\",\n            41 => \"Mapper 041 - Caltron 6-in-1\",\n            42 => \"Mapper 042\",\n            43 => \"Mapper 043 - TONY-I/YS-612\",\n            44 => \"Mapper 044 - MMC3 Multicart\",\n            45 => \"Mapper 045 - MMC3 Multicart\",\n            46 => \"Mapper 046 - Color Dreams\",\n            47 => \"Mapper 047 - MMC3 Multicart\",\n            48 => \"Mapper 048 - Taito TC0690\",\n            49 => \"Mapper 049 - MMC Multicart\",\n            50 => \"Mapper 050\",\n            51 => \"Mapper 051\",\n            52 => \"Mapper 052 - Realtec 8213/MMC Multicaart\",\n            53 => \"Mapper 053 - Supervision\",\n            54 => \"Mapper 054 - Novel Diamond\",\n            55 => \"Mapper 055 - UNIF BTL-MARIO1-MALEE2\",\n            56 => \"Mapper 056\",\n            57 => \"Mapper 057\",\n            58 => \"Mapper 058\",\n            59 => \"Mapper 059 - BMC T3H53/D1038\",\n            60 => \"Mapper 060\",\n            61 => \"Mapper 061\",\n            62 => \"Mapper 062\",\n            63 => \"Mapper 063\",\n            64 => \"Mapper 064 - RAMBO-1\",\n            65 => \"Mapper 065 - Irem H3001\",\n            66 => \"Mapper 066 - GxROM/MxROM\",\n            67 => \"Mapper 067 - Sunsoft-3\",\n            68 => \"Mapper 068 - Sunsoft-4\",\n            69 => \"Mapper 069 - Sunsoft FME-7\",\n            70 => \"Mapper 070 - Bandai\",\n            71 => \"Mapper 071 - BF909x\",\n            72 => \"Mapper 072 - Jaleco JF-17\",\n            73 => \"Mapper 073 - Vrc3\",\n            74 => \"Mapper 074\",\n            75 => \"Mapper 075 - Vrc1\",\n            76 => \"Mapper 076 - NAMCOT-108\",\n            77 => \"Mapper 077\",\n            78 => \"Mapper 078\",\n            79 => \"Mapper 079 - NINA-03/06\",\n            80 => \"Mapper 080 - Taito X1005\",\n            81 => \"Mapper 081 - NTDEC 715021\",\n            82 => \"Mapper 082 - Taito X1017\",\n            83 => \"Mapper 083\",\n            84 => \"Mapper 084\",\n            85 => \"Mapper 085 - Vrc7\",\n            86 => \"Mapper 086 - Jaleco JF-13\",\n            87 => \"Mapper 087 - Jaleco JF-xx\",\n            88 => \"Mapper 088\",\n            89 => \"Mapper 089 - Sunsoft\",\n            90 => \"Mapper 090 - JY Company\",\n            91 => \"Mapper 091\",\n            92 => \"Mapper 092\",\n            93 => \"Mapper 093 - Sunsoft\",\n            94 => \"Mapper 094 - UxROM\",\n            95 => \"Mapper 095 - NAMCOT-3425\",\n            96 => \"Mapper 096 - Oeka Kids\",\n            97 => \"Mapper 097 - Irem TAM-S1\",\n            98 => \"Mapper 098\",\n            99 => \"Mapper 099 - Vs. System\",\n            100 => \"Mapper 100\",\n            101 => \"Mapper 101 - Jaleco JF-10\",\n            102 => \"Mapper 102\",\n            103 => \"Mapper 103\",\n            104 => \"Mapper 104 - Golden Five\",\n            105 => \"Mapper 105 - MMC1\",\n            106 => \"Mapper 106\",\n            107 => \"Mapper 107\",\n            108 => \"Mapper 108\",\n            109 => \"Mapper 109\",\n            110 => \"Mapper 110\",\n            111 => \"Mapper 111 - GTROM\",\n            112 => \"Mapper 112\",\n            113 => \"Mapper 113 - NINA-03/06\",\n            114 => \"Mapper 114 - MMC3\",\n            115 => \"Mapper 115 - MMC3\",\n            116 => \"Mapper 116 - SOMARI-P\",\n            117 => \"Mapper 117\",\n            118 => \"Mapper 118 - TxSROM\",\n            119 => \"Mapper 119 - TQROM\",\n            120 => \"Mapper 120\",\n            121 => \"Mapper 121 - MMC3\",\n            122 => \"Mapper 122\",\n            123 => \"Mapper 123 - MMC3\",\n            124 => \"Mapper 124\",\n            125 => \"Mapper 125 - UNL-LH32\",\n            126 => \"Mapper 126 - MMC36\",\n            127 => \"Mapper 127\",\n            128 => \"Mapper 128\",\n            129 => \"Mapper 129\",\n            130 => \"Mapper 130\",\n            131 => \"Mapper 131\",\n            132 => \"Mapper 132 - TXC\",\n            133 => \"Mapper 133 - Sachen 3009\",\n            134 => \"Mapper 134 - MMC3\",\n            135 => \"Mapper 135 - Sachen 8259A\",\n            136 => \"Mapper 136 - Sachen 3011\",\n            137 => \"Mapper 137 - Sachen 8259D\",\n            138 => \"Mapper 138 - Sachen 8259B\",\n            139 => \"Mapper 139 - Sachen 8259C\",\n            140 => \"Mapper 140 - Jaleco JF-11/14\",\n            141 => \"Mapper 141 - Sachen 8259A\",\n            142 => \"Mapper 142 - Kaiser KS-7032\",\n            143 => \"Mapper 143 - NROM\",\n            144 => \"Mapper 144 - Color Dreams\",\n            145 => \"Mapper 145 - Sachen SA-72007\",\n            146 => \"Mapper 146 - NINA-03/06\",\n            147 => \"Mapper 147 - Sachen 3018\",\n            148 => \"Mapper 148 - Sachen SA-008-A/Tengen 800008\",\n            149 => \"Mapper 149 - Sachen SA-0036\",\n            150 => \"Mapper 150 - Sach SA-015/630\",\n            151 => \"Mapper 151 - Vrc1\",\n            152 => \"Mapper 152\",\n            153 => \"Mapper 153 - Bandai FCG\",\n            154 => \"Mapper 154 - NAMCOT-3453\",\n            155 => \"Mapper 155 - SxROM/MMC1A\",\n            156 => \"Mapper 156 - Daou\",\n            157 => \"Mapper 157 - Bandai FCG\",\n            158 => \"Mapper 158 - Tengen 800037\",\n            159 => \"Mapper 159 - Bandai FCG\",\n            160 => \"Mapper 160\",\n            161 => \"Mapper 161\",\n            162 => \"Mapper 162 - Wàixīng\",\n            163 => \"Mapper 163 - Nánjīng\",\n            164 => \"Mapper 164 - Dōngdá/Yànchéng\",\n            165 => \"Mapper 165 - MMC3\",\n            166 => \"Mapper 166 - Subor\",\n            167 => \"Mapper 167 - Subor\",\n            168 => \"Mapper 168 - Racermate\",\n            169 => \"Mapper 169 - Yuxing\",\n            170 => \"Mapper 170\",\n            171 => \"Mapper 171 - Kaiser KS-7058\",\n            172 => \"Mapper 172\",\n            173 => \"Mapper 173\",\n            174 => \"Mapper 174\",\n            175 => \"Mapper 175 - Kaiser KS-7022\",\n            176 => \"Mapper 176 - MMC3\",\n            177 => \"Mapper 177 - Hénggé Diànzǐ\",\n            178 => \"Mapper 178\",\n            179 => \"Mapper 179\",\n            180 => \"Mapper 180 - UNROM\",\n            181 => \"Mapper 181\",\n            182 => \"Mapper 182 - MMC3\",\n            183 => \"Mapper 183\",\n            184 => \"Mapper 184 - Sunsoft\",\n            185 => \"Mapper 185 - CNROM\",\n            186 => \"Mapper 186\",\n            187 => \"Mapper 187 - Kǎshèng/MMC3\",\n            188 => \"Mapper 188 - Bandai Karaoke\",\n            189 => \"Mapper 189 - MMC3\",\n            190 => \"Mapper 190 -\",\n            191 => \"Mapper 191 - MMC3\",\n            192 => \"Mapper 192 - Wàixīng\",\n            193 => \"Mapper 193 - NTDEC TC-112\",\n            194 => \"Mapper 194 - MMC3\",\n            195 => \"Mapper 195 - Wàixīng/MMC3\",\n            196 => \"Mapper 196 - MMC3\",\n            197 => \"Mapper 197 - MMC3\",\n            198 => \"Mapper 198 - MMC3\",\n            199 => \"Mapper 199 - Wàixīng/MMC3\",\n            200 => \"Mapper 200\",\n            201 => \"Mapper 201 - NROM\",\n            202 => \"Mapper 202\",\n            203 => \"Mapper 203\",\n            204 => \"Mapper 204\",\n            205 => \"Mapper 205 - MMC3\",\n            206 => \"Mapper 206 - DxROM\",\n            207 => \"Mapper 207 - Taito X1-005\",\n            208 => \"Mapper 208 - MMC3\",\n            209 => \"Mapper 209 - JY Company\",\n            210 => \"Mapper 210 - Namco\",\n            211 => \"Mapper 211 - JyCompany\",\n            212 => \"Mapper 212\",\n            213 => \"Mapper 213\",\n            214 => \"Mapper 214\",\n            215 => \"Mapper 215 - MMC3\",\n            216 => \"Mapper 216\",\n            217 => \"Mapper 217 - MMC3\",\n            218 => \"Mapper 218\",\n            219 => \"Mapper 219 - Kǎshèng/MMC3\",\n            220 => \"Mapper 220\",\n            221 => \"Mapper 221 - NTDEC N625092\",\n            222 => \"Mapper 222\",\n            223 => \"Mapper 223\",\n            224 => \"Mapper 224 - Jncota/MMC3\",\n            225 => \"Mapper 225\",\n            226 => \"Mapper 226\",\n            227 => \"Mapper 227\",\n            228 => \"Mapper 228- Active Enterprises\",\n            229 => \"Mapper 229\",\n            230 => \"Mapper 230\",\n            231 => \"Mapper 231\",\n            232 => \"Mapper 232 - BF909x\",\n            233 => \"Mapper 233\",\n            234 => \"Mapper 234 - Maxi 15 Multicart\",\n            235 => \"Mapper 235\",\n            236 => \"Mapper 236 - Realtec\",\n            237 => \"Mapper 237\",\n            238 => \"Mapper 238 - MMC3\",\n            239 => \"Mapper 239\",\n            240 => \"Mapper 240\",\n            241 => \"Mapper 241 - BxROM\",\n            242 => \"Mapper 242\",\n            243 => \"Mapper 243 - Sachen SA-020A\",\n            244 => \"Mapper 244\",\n            245 => \"Mapper 245 - Wàixīng/MMC3\",\n            246 => \"Mapper 246\",\n            247 => \"Mapper 247\",\n            248 => \"Mapper 248\",\n            249 => \"Mapper 249 - MMC3\",\n            250 => \"Mapper 250 - Nitra/MMC3\",\n            251 => \"Mapper 251\",\n            252 => \"Mapper 252 - Wàixīng\",\n            253 => \"Mapper 253 - Wàixīng\",\n            254 => \"Mapper 254 - MMC3\",\n            255 => \"Mapper 255\",\n            _ => \"Invalid Mapper\",\n        }\n    }\n}\n\nimpl std::fmt::Debug for NesHeader {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {\n        f.debug_struct(\"NesHeader\")\n            .field(\"version\", &self.variant)\n            .field(\"mapper_num\", &format_args!(\"{:03}\", &self.mapper_num))\n            .field(\"submapper_num\", &self.submapper_num)\n            .field(\"flags\", &format_args!(\"0b{:08b}\", &self.flags))\n            .field(\"prg_rom_banks\", &self.prg_rom_banks)\n            .field(\"chr_rom_banks\", &self.chr_rom_banks)\n            .field(\"prg_ram_shift\", &self.prg_ram_shift)\n            .field(\"chr_ram_shift\", &self.chr_ram_shift)\n            .field(\"tv_mode\", &self.tv_mode)\n            .field(\"vs_data\", &self.vs_data)\n            .finish()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    macro_rules! test_headers {\n        ($(($test:ident, $data:expr, $header:expr$(,)?)),*$(,)?) => {$(\n            #[test]\n            fn $test() {\n                let header = NesHeader::load(&mut $data.as_slice()).expect(\"valid header\");\n                assert_eq!(header, $header);\n            }\n        )*};\n    }\n\n    #[rustfmt::skip]\n    test_headers!(\n        (\n            mapper000_horizontal,\n            [0x4E, 0x45, 0x53, 0x1A,\n             0x02, 0x01, 0x01, 0x00,\n             0x00, 0x00, 0x00, 0x00,\n             0x00, 0x00, 0x00, 0x00],\n            NesHeader {\n                variant: NesVariant::INes,\n                mapper_num: 0,\n                flags: 0b0000_0001,\n                prg_rom_banks: 2,\n                chr_rom_banks: 1,\n                ..NesHeader::default()\n            },\n        ),\n        (\n            mapper001_vertical,\n            [0x4E, 0x45, 0x53, 0x1A,\n             0x08, 0x00, 0x10, 0x00,\n             0x00, 0x00, 0x00, 0x00,\n             0x00, 0x00, 0x00, 0x00],\n            NesHeader {\n                variant: NesVariant::INes,\n                mapper_num: 1,\n                flags: 0b0000_0000,\n                prg_rom_banks: 8,\n                chr_rom_banks: 0,\n                ..NesHeader::default()\n            },\n        ),\n    );\n}\n"
  },
  {
    "path": "tetanes-core/src/common.rs",
    "content": "//! Common traits and constants.\n\nuse serde::{Deserialize, Serialize};\nuse std::{fmt::Write, path::Path};\nuse thiserror::Error;\n\n/// Default directory for save states.\npub const SAVE_DIR: &str = \"save\";\n/// Default directory for save RAM.\npub const SRAM_DIR: &str = \"sram\";\n\n#[derive(Error, Debug)]\n#[must_use]\n#[error(\"failed to parse `NesRegion`\")]\npub struct ParseNesRegionError;\n\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum NesRegion {\n    /// Auto-detect region based on ROM headers and a pre-built game database.\n    Auto,\n    /// NTSC, primarily North America.\n    #[default]\n    Ntsc,\n    /// PAL, primarily Japan and Europe.\n    Pal,\n    /// Dendy, primarily Russia.\n    Dendy,\n}\n\nimpl NesRegion {\n    pub const fn as_slice() -> &'static [Self] {\n        &[\n            NesRegion::Auto,\n            NesRegion::Ntsc,\n            NesRegion::Pal,\n            NesRegion::Dendy,\n        ]\n    }\n\n    #[must_use]\n    pub const fn is_auto(&self) -> bool {\n        matches!(self, Self::Auto)\n    }\n\n    #[must_use]\n    pub const fn is_ntsc(&self) -> bool {\n        matches!(self, Self::Auto | Self::Ntsc)\n    }\n\n    #[must_use]\n    pub const fn is_pal(&self) -> bool {\n        matches!(self, Self::Pal)\n    }\n\n    #[must_use]\n    pub const fn is_dendy(&self) -> bool {\n        matches!(self, Self::Dendy)\n    }\n\n    #[must_use]\n    pub fn aspect_ratio(&self) -> f32 {\n        // https://www.nesdev.org/wiki/Overscan\n        match self {\n            Self::Auto | Self::Ntsc => 8.0 / 7.0,\n            Self::Pal | Self::Dendy => 18.0 / 13.0,\n        }\n    }\n\n    #[must_use]\n    pub const fn as_str(&self) -> &'static str {\n        match self {\n            Self::Auto => \"auto\",\n            Self::Ntsc => \"ntsc\",\n            Self::Pal => \"pal\",\n            Self::Dendy => \"dendy\",\n        }\n    }\n}\n\nimpl std::fmt::Display for NesRegion {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let s = match self {\n            Self::Auto => \"Auto\",\n            Self::Ntsc => \"NTSC\",\n            Self::Pal => \"PAL\",\n            Self::Dendy => \"Dendy\",\n        };\n        write!(f, \"{s}\")\n    }\n}\n\nimpl AsRef<str> for NesRegion {\n    fn as_ref(&self) -> &str {\n        self.as_str()\n    }\n}\n\nimpl TryFrom<&str> for NesRegion {\n    type Error = ParseNesRegionError;\n\n    fn try_from(value: &str) -> Result<Self, Self::Error> {\n        match value {\n            \"auto\" => Ok(Self::Auto),\n            \"ntsc\" => Ok(Self::Ntsc),\n            \"pal\" => Ok(Self::Pal),\n            \"dendy\" => Ok(Self::Dendy),\n            _ => Err(ParseNesRegionError),\n        }\n    }\n}\n\nimpl TryFrom<usize> for NesRegion {\n    type Error = ParseNesRegionError;\n\n    fn try_from(value: usize) -> Result<Self, Self::Error> {\n        match value {\n            0 => Ok(Self::Auto),\n            1 => Ok(Self::Ntsc),\n            2 => Ok(Self::Pal),\n            3 => Ok(Self::Dendy),\n            _ => Err(ParseNesRegionError),\n        }\n    }\n}\n\n/// Trait for types that have different behavior depending on NES region.\n// NOTE: enum_dispatch requires absolute paths to types\npub trait Regional {\n    /// Return the current region.\n    fn region(&self) -> NesRegion {\n        NesRegion::default()\n    }\n\n    /// Set the region.\n    fn set_region(&mut self, _region: NesRegion) {}\n}\n\n/// Type of reset for types that have different behavior for reset vs power cycling.\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum ResetKind {\n    /// Soft reset generally doesn't zero-out most registers or RAM.\n    Soft,\n    /// Hard reset generally zeros-out most registers and RAM.\n    Hard,\n}\n\n/// Trait for types that can can be reset.\npub trait Reset {\n    /// Reset the component given the [`ResetKind`].\n    fn reset(&mut self, _kind: ResetKind) {}\n}\n\n/// Trait for types that can be clocked.\npub trait Clock {\n    /// Clock component once.\n    fn clock(&mut self) {}\n}\n\n/// Trait for types that can output `f32` audio samples.\npub trait Sample {\n    /// Output a single audio sample.\n    fn output(&self) -> f32 {\n        0.0\n    }\n}\n\n/// Trait for types that can save RAM to disk.\npub trait Sram {\n    /// Save RAM to a given path.\n    fn save(&self, _path: impl AsRef<Path>) -> crate::fs::Result<()> {\n        Ok(())\n    }\n\n    /// Load save RAM from a given path.\n    fn load(&mut self, _path: impl AsRef<Path>) -> crate::fs::Result<()> {\n        Ok(())\n    }\n}\n\n/// Prints a hex dump of a given byte array starting at `addr_offset`.\n#[must_use]\npub fn hexdump(data: &[u8], addr_offset: usize) -> Vec<String> {\n    use std::cmp;\n\n    let mut addr = 0;\n    let len = data.len();\n    let mut last_line_same = false;\n    let mut output = Vec::new();\n\n    let mut last_line = String::with_capacity(80);\n    while addr <= len {\n        let end = cmp::min(addr + 16, len);\n        let line_data = &data[addr..end];\n        let line_len = line_data.len();\n\n        let mut line = String::with_capacity(80);\n        for byte in line_data.iter() {\n            let _ = write!(line, \" {byte:02X}\");\n        }\n\n        if line_len % 16 > 0 {\n            let words_left = (16 - line_len) / 2;\n            for _ in 0..3 * words_left {\n                line.push(' ');\n            }\n        }\n\n        if line_len > 0 {\n            line.push_str(\"  |\");\n            for c in line_data {\n                if (*c as char).is_ascii() && !(*c as char).is_control() {\n                    let _ = write!(line, \"{}\", (*c as char));\n                } else {\n                    line.push('.');\n                }\n            }\n            line.push('|');\n        }\n        if last_line == line {\n            if !last_line_same {\n                last_line_same = true;\n                output.push(\"*\".to_string());\n            }\n        } else {\n            last_line_same = false;\n            output.push(format!(\"{:08x} {}\", addr + addr_offset, line));\n        }\n        last_line = line;\n\n        addr += 16;\n    }\n    output\n}\n\n#[cfg(test)]\npub(crate) mod tests {\n    use crate::{\n        action::Action,\n        common::{Regional, Reset, ResetKind},\n        control_deck::{Config, ControlDeck},\n        input::Player,\n        mem::RamState,\n        ppu::size,\n        video::VideoFilter,\n    };\n    use anyhow::Context;\n    use image::{ImageBuffer, Rgba};\n    use serde::{Deserialize, Serialize};\n    use std::{\n        collections::hash_map::DefaultHasher,\n        env,\n        fmt::Write,\n        fs::{self, File},\n        hash::{Hash, Hasher},\n        io::{BufReader, Read},\n        path::{Path, PathBuf},\n        sync::OnceLock,\n    };\n    use tracing::debug;\n\n    pub(crate) const RESULT_DIR: &str = \"test_results\";\n\n    static PASS_DIR: OnceLock<PathBuf> = OnceLock::new();\n    static FAIL_DIR: OnceLock<PathBuf> = OnceLock::new();\n\n    #[macro_export]\n    macro_rules! test_roms {\n        ($mod:ident, $directory:expr, $( $(#[ignore = $reason:expr])? $test:ident ),* $(,)?) => {\n            mod $mod {$(\n                $(#[ignore = $reason])?\n                #[test]\n                fn $test() -> anyhow::Result<()> {\n                    $crate::common::tests::test_rom($directory, stringify!($test))\n                }\n            )*}\n        };\n    }\n\n    // TODO: Instead of a bunch of optional fields, it should be an enum:\n    // enum FrameAction {\n    //   DeckAction(DeckAction),\n    //   FrameHash(u64),\n    //   AudioHash(u64),\n    // }\n    #[derive(Default, Debug, Clone, Serialize, Deserialize)]\n    #[serde(default)]\n    #[must_use]\n    struct TestFrame {\n        number: u32,\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        name: Option<String>,\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        hash: Option<u64>,\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        action: Option<Action>,\n        #[serde(skip_serializing)]\n        audio: bool,\n    }\n\n    #[derive(Debug, Clone, Serialize, Deserialize)]\n    #[must_use]\n    struct RomTest {\n        name: String,\n        #[serde(skip_serializing, default)]\n        audio: bool,\n        frames: Vec<TestFrame>,\n    }\n\n    fn get_rom_tests(directory: &str) -> anyhow::Result<(PathBuf, Vec<RomTest>)> {\n        let file = PathBuf::from(directory)\n            .join(\"tests\")\n            .with_extension(\"json\");\n        let mut content = String::with_capacity(1024);\n        File::open(&file)\n            .and_then(|mut file| file.read_to_string(&mut content))\n            .with_context(|| format!(\"failed to read rom test data: {file:?}\"))?;\n        let tests = serde_json::from_str(&content)\n            .with_context(|| format!(\"valid rom test data: {file:?}\"))?;\n        Ok((file, tests))\n    }\n\n    fn load_control_deck<P: AsRef<Path>>(path: P) -> ControlDeck {\n        let path = path.as_ref();\n        let mut rom = BufReader::new(File::open(path).expect(\"failed to open path\"));\n        let mut deck = ControlDeck::with_config(Config {\n            ram_state: RamState::AllZeros,\n            filter: VideoFilter::Pixellate,\n            ..Default::default()\n        });\n        deck.load_rom(path.to_string_lossy(), &mut rom)\n            .expect(\"failed to load rom\");\n        deck\n    }\n\n    fn on_frame_action(test_frame: &TestFrame, deck: &mut ControlDeck) {\n        if let Some(action) = test_frame.action {\n            debug!(\"{:?}\", action);\n            match action {\n                Action::Reset(kind) => deck.reset(kind),\n                Action::MapperRevision(rev) => deck.set_mapper_revision(rev),\n                Action::SetVideoFilter(filter) => deck.set_filter(filter),\n                Action::SetNesRegion(format) => deck.set_region(format),\n                Action::Joypad((player, button)) => {\n                    let joypad = deck.joypad_mut(player);\n                    joypad.set_button(button, true);\n                }\n                Action::ToggleZapperConnected => deck.connect_zapper(!deck.zapper_connected()),\n                Action::ZapperAim((x, y)) => deck.aim_zapper(x, y),\n                Action::ZapperTrigger => deck.trigger_zapper(),\n                Action::LoadState\n                | Action::SaveState\n                | Action::SetSaveSlot(_)\n                | Action::ToggleApuChannel(_)\n                | Action::ZapperAimOffscreen\n                | Action::FourPlayer(_) => (),\n            }\n        }\n    }\n\n    fn on_snapshot(\n        test: &str,\n        test_frame: &TestFrame,\n        deck: &mut ControlDeck,\n        count: usize,\n    ) -> anyhow::Result<Option<(u64, u64, u32, PathBuf)>> {\n        match test_frame.hash {\n            Some(expected) => {\n                let mut hasher = DefaultHasher::new();\n                if test_frame.audio {\n                    deck.audio_samples()\n                        .iter()\n                        .for_each(|s| s.to_le_bytes().hash(&mut hasher));\n                } else {\n                    deck.frame_buffer().hash(&mut hasher);\n                }\n                let actual = hasher.finish();\n                debug!(\n                    \"frame: {}, matched: {}\",\n                    test_frame.number,\n                    expected == actual\n                );\n\n                let base_dir = Path::new(env!(\"CARGO_MANIFEST_DIR\"));\n                let result_dir = if env::var(\"UPDATE_SNAPSHOT\").is_ok() || expected == actual {\n                    PASS_DIR.get_or_init(|| {\n                        let directory = base_dir.join(PathBuf::from(RESULT_DIR)).join(\"pass\");\n                        if let Err(err) = fs::create_dir_all(&directory) {\n                            panic!(\"created pass test results dir: {directory:?}. {err}\",);\n                        }\n                        directory\n                    })\n                } else {\n                    FAIL_DIR.get_or_init(|| {\n                        let directory = base_dir.join(PathBuf::from(RESULT_DIR)).join(\"fail\");\n                        if let Err(err) = fs::create_dir_all(&directory) {\n                            panic!(\"created fail test results dir: {directory:?}. {err}\",);\n                        }\n                        directory\n                    })\n                };\n                let mut filename = test.to_owned();\n                if let Some(ref name) = test_frame.name {\n                    let _ = write!(filename, \"_{name}\");\n                } else if count > 0 {\n                    let _ = write!(filename, \"_{}\", count + 1);\n                }\n                let screenshot = result_dir\n                    .join(PathBuf::from(filename))\n                    .with_extension(\"png\");\n\n                ImageBuffer::<Rgba<u8>, &[u8]>::from_raw(\n                    u32::from(size::WIDTH),\n                    u32::from(size::HEIGHT),\n                    deck.frame_buffer(),\n                )\n                .expect(\"valid frame\")\n                .save(&screenshot)\n                .with_context(|| format!(\"failed to save screenshot: {screenshot:?}\"))?;\n\n                Ok(Some((expected, actual, test_frame.number, screenshot)))\n            }\n            None => Ok(None),\n        }\n    }\n\n    pub(crate) fn test_rom(directory: &str, test_name: &str) -> anyhow::Result<()> {\n        thread_local! {\n            static INIT_TESTS: OnceLock<bool> = const { OnceLock::new() };\n        }\n\n        let base_dir = Path::new(env!(\"CARGO_MANIFEST_DIR\"));\n        let initialized = INIT_TESTS.with(|init| {\n            *init.get_or_init(|| {\n                use tracing_subscriber::{\n                    filter::Targets, fmt, layer::SubscriberExt, registry, util::SubscriberInitExt,\n                };\n                let _ = registry()\n                    .with(\n                        env::var(\"RUST_LOG\")\n                            .ok()\n                            .and_then(|filter| filter.parse::<Targets>().ok())\n                            .unwrap_or_default(),\n                    )\n                    .with(\n                        fmt::layer()\n                            .compact()\n                            .with_ansi(false)\n                            .without_time()\n                            .with_line_number(true)\n                            .with_thread_ids(true)\n                            .with_thread_names(true)\n                            .with_writer(std::io::stderr),\n                    )\n                    .try_init();\n                true\n            })\n        });\n        if initialized {\n            debug!(\"Initialized tests\");\n        }\n\n        let (test_file, mut tests) = get_rom_tests(directory)?;\n        let mut test = tests.iter_mut().find(|test| test.name.eq(test_name));\n        assert!(test.is_some(), \"No test found matching {test_name:?}\");\n        let test = test.as_mut().expect(\"definitely has a test\");\n\n        let rom = base_dir\n            .join(directory)\n            .join(PathBuf::from(&test.name))\n            .with_extension(\"nes\");\n        assert!(rom.exists(), \"No test rom found for {rom:?}\");\n\n        let mut deck = load_control_deck(&rom);\n        deck.cpu_mut().bus.apu.skip_mixing = !test.audio;\n\n        let mut results = Vec::new();\n        assert!(!test.frames.is_empty(), \"No test frames found for {rom:?}\");\n        for test_frame in test.frames.iter() {\n            debug!(\"{} - {:?}\", test_frame.number, deck.joypad_mut(Player::One));\n\n            while deck.frame_number() < test_frame.number {\n                deck.clock_frame().expect(\"valid frame clock\");\n                if deck.frame_number() != test_frame.number && !test_frame.audio {\n                    deck.clear_audio_samples();\n                }\n                deck.joypad_mut(Player::One).reset(ResetKind::Soft);\n                deck.joypad_mut(Player::Two).reset(ResetKind::Soft);\n            }\n\n            on_frame_action(test_frame, &mut deck);\n            if let Ok(Some(result)) = on_snapshot(&test.name, test_frame, &mut deck, results.len())\n            {\n                results.push(result);\n            }\n        }\n        let mut update_required = false;\n        for (mut expected, actual, frame_number, screenshot) in results {\n            if env::var(\"UPDATE_SNAPSHOT\").is_ok() && expected != actual {\n                expected = actual;\n                update_required = true;\n                if let Some(frame) = &mut test\n                    .frames\n                    .iter_mut()\n                    .find(|frame| frame.number == frame_number)\n                {\n                    frame.hash = Some(actual);\n                }\n            }\n            assert!(\n                expected == actual,\n                \"mismatched snapshot for {rom:?} -> {screenshot:?} (expected: {expected}, actual: {actual})\",\n            );\n        }\n        if update_required {\n            File::create(&test_file)\n                .context(\"failed to open rom test file\")\n                .and_then(|file| {\n                    serde_json::to_writer_pretty(file, &tests)\n                        .context(\"failed to serialize rom data\")\n                })\n                .with_context(|| format!(\"failed to update snapshot: {test_file:?}\"))?\n        }\n\n        Ok(())\n    }\n\n    test_roms!(\n        cpu,\n        \"test_roms/cpu\",\n        branch_backward, // Tests branches jumping backward\n        branch_basics,   // Tests branch instructions, including edge cases\n        branch_forward,  // Tests branches jumping forward\n        nestest,         // Tests all CPU instructions, including illegal opcodes\n        // Verifies ram and registers are set/cleared correctly after reset\n        ram_after_reset,\n        regs_after_reset,\n        // Tests CPU dummy reads\n        dummy_reads,\n        dummy_writes_oam,\n        dummy_writes_ppumem,\n        // Verifies cpu can execute code from any memory location, incl. I/O\n        exec_space_apu,\n        exec_space_ppuio,\n        flag_concurrency,\n        // Tests CPU several instruction combinations\n        instr_abs,\n        instr_abs_xy,\n        instr_basics,\n        instr_branches,\n        instr_brk,\n        instr_imm,\n        instr_imp,\n        instr_ind_x,\n        instr_ind_y,\n        instr_jmp_jsr,\n        instr_misc,\n        instr_rti,\n        instr_rts,\n        instr_special,\n        instr_stack,\n        instr_timing,\n        instr_zp,\n        instr_zp_xy,\n        // Tests IRQ/NMI timings\n        int_branch_delays_irq,\n        int_cli_latency,\n        int_irq_and_dma,\n        int_nmi_and_brk,\n        int_nmi_and_irq,\n        overclock,\n        // Tests cycle stealing behavior of DMC DMA while running sprite DMAs\n        sprdma_and_dmc_dma,\n        sprdma_and_dmc_dma_512,\n        timing_test, // Tests CPU timing\n    );\n    test_roms!(\n        ppu,\n        \"test_roms/ppu\",\n        _240pee,               // TODO: Run each test\n        color,                 // TODO: Test all color combinations\n        ntsc_torture,          // Tests PPU NTSC signal artifacts\n        oam_read,              // Tests OAM reading ($2004)\n        oam_stress,            // Stresses OAM ($2003) reads and writes ($2004)\n        open_bus,              // Tests PPU open bus behavior\n        palette,               // Tests simple scanline palette changes\n        palette_ram,           // Tests palette RAM access\n        read_buffer,           // Thoroughly tests PPU read buffer ($2007)\n        scanline,              // Tests scanline rendering\n        spr_hit_alignment,     // Tests sprite hit alignment\n        spr_hit_basics,        // Tests sprite hit basics\n        spr_hit_corners,       // Tests sprite hit corners\n        spr_hit_double_height, // Tests sprite hit in x16 height mode\n        spr_hit_edge_timing,   // Tests sprite hit edge timing\n        spr_hit_flip,          // Tests sprite hit with sprite flip\n        spr_hit_left_clip,     // Tests sprite hit with left edge clipped\n        spr_hit_right_edge,    // Tests sprite hit right edge\n        spr_hit_screen_bottom, // Tests sprite hit bottom\n        spr_hit_timing_basics, // Tests sprite hit timing\n        spr_hit_timing_order,  // Tests sprite hit order\n        spr_overflow_basics,   // Tests sprite overflow basics\n        spr_overflow_details,  // Tests more thorough sprite overflow\n        spr_overflow_emulator,\n        spr_overflow_obscure,    // Tests obscure sprite overflow cases\n        spr_overflow_timing,     // Tests sprite overflow timing\n        sprite_ram,              // Tests sprite ram\n        tv,                      // Tests NTSC color and NTSC/PAL aspect ratio\n        vbl_nmi_basics,          // Tests vblank NMI basics\n        vbl_nmi_clear_timing,    // Tests vblank NMI clear timing\n        vbl_nmi_control,         // Tests vblank NMI control\n        vbl_nmi_disable,         // Tests vblank NMI disable\n        vbl_nmi_even_odd_frames, // Tests vblank NMI on even/odd frames\n        #[ignore = \"clock is skipped too late relative to enabling BG Failed #3\"]\n        vbl_nmi_even_odd_timing, // Tests vblank NMI even/odd frame timing\n        vbl_nmi_frame_basics,    // Tests vblank NMI frame basics\n        vbl_nmi_off_timing,      // Tests vblank NMI off timing\n        vbl_nmi_on_timing,       // Tests vblank NMI on timing\n        vbl_nmi_set_time,        // Tests vblank NMI set timing\n        vbl_nmi_suppression,     // Tests vblank NMI supression\n        vbl_nmi_timing,          // Tests vblank NMI timing\n        vbl_timing,              // Tests vblank timing\n        vram_access,             // Tests video RAM access\n    );\n    test_roms!(\n        apu,\n        \"test_roms/apu\",\n        // DMC DMA during $2007 read causes 2-3 extra $2007\n        // reads before real read.\n        //\n        // Number of extra reads depends in CPU-PPU\n        // synchronization at reset.\n        dmc_dma_2007_read,\n        // DMC DMA during $2007 write has no effect.\n        // Output:\n        // 22 11 22 AA 44 55 66 77\n        // 22 11 22 AA 44 55 66 77\n        // 22 11 22 AA 44 55 66 77\n        // 22 11 22 AA 44 55 66 77\n        // 22 11 22 AA 44 55 66 77\n        dmc_dma_2007_write,\n        //  DMC DMA during $4016 read causes extra $4016\n        // read.\n        // Output:\n        // 08 08 07 08 08\n        dmc_dma_4016_read,\n        // Double read of $2007 sometimes ignores extra\n        //  read, and puts odd things into buffer.\n        //\n        // Output (depends on CPU-PPU synchronization):\n        // 22 33 44 55 66\n        // 22 44 55 66 77 or\n        // 22 33 44 55 66 or\n        // 02 44 55 66 77 or\n        // 32 44 55 66 77 or\n        // 85CFD627 or F018C287 or 440EF923 or E52F41A5\n        dmc_dma_double_2007_read,\n        // Read of $2007 just before write behaves normally.\n        //\n        // Output:\n        // 33 11 22 33 09 55 66 77\n        // 33 11 22 33 09 55 66 77\n        dmc_dma_read_write_2007,\n        // This NES program demonstrates abusing the NTSC NES's sampled sound\n        // playback hardware as a scanline timer to split the screen twice\n        // without needing to use a mapper-generated IRQ.\n        dpcmletterbox,\n        // Blargg's APU tests\n        //\n        // Misc\n        // ----\n        // - The frame IRQ flag is cleared only when $4015 is read or $4017 is\n        // written with bit 6 set ($40 or $c0).\n\n        // - The IRQ handler is invoked at minimum 29833 clocks after writing $00\n        // to $4017 (assuming the frame IRQ flag isn't already set, and nothing\n        // else generates an IRQ during that time).\n\n        // - After reset or power-up, APU acts as if $4017 were written with $00\n        // from 9 to 12 clocks before first instruction begins. It is as if this\n        // occurs (this generates a 10 clock delay):\n\n        //       lda   #$00\n        //       sta   $4017       ; 1\n        //       lda   <0          ; 9 delay\n        //       nop\n        //       nop\n        //       nop\n        // reset:\n        //       ...\n\n        // - As shown, the frame irq flag is set three times in a row. Thus when\n        // polling it, always read $4015 an extra time after the flag is found to\n        // be set, to be sure it's clear afterwards,\n\n        // wait: bit   $4015       ; V flag reflects frame IRQ flag\n        //       bvc   wait\n        //       bit   $4015       ; be sure irq flag is clear\n\n        // or better yet, clear it before polling it:\n\n        //       bit   $4015       ; clear flag first\n        // wait: bit   $4015       ; V flag reflects frame IRQ flag\n        //       bvc   wait\n        //\n        // See:\n        // <https://github.com/christopherpow/nes-test-roms/tree/master/blargg_apu_2005.07.30>\n        //\n        // Tests basic length counter operation\n        // 1) Passed tests\n        // 2) Problem with length counter load or $4015\n        // 3) Problem with length table, timing, or $4015\n        // 4) Writing $80 to $4017 should clock length immediately\n        // 5) Writing $00 to $4017 shouldn't clock length immediately\n        // 6) Clearing enable bit in $4015 should clear length counter\n        // 7) When disabled via $4015, length shouldn't allow reloading\n        // 8) Halt bit should suspend length clocking\n        len_ctr,\n        // Tests all length table entries.\n        // 1) Passed\n        // 2) Failed. Prints four bytes $II $ee $cc $02 that indicate the length\n        // load value written (ll), the value that the emulator uses ($ee), and the\n        // correct value ($cc).\n        len_table,\n        // Tests basic operation of frame irq flag.\n        // 1) Tests passed\n        // 2) Flag shouldn't be set in $4017 mode $40\n        // 3) Flag shouldn't be set in $4017 mode $80\n        // 4) Flag should be set in $4017 mode $00\n        // 5) Reading flag clears it\n        // 6) Writing $00 or $80 to $4017 doesn't affect flag\n        // 7) Writing $40 or $c0 to $4017 clears flag\n        irq_flag,\n        // Clock Jitter\n        // ------------\n        // Changes to the mode by writing to $4017 only occur on *even* internal\n        // APU clocks; if written on an odd clock, the first step of the mode is\n        // delayed by one clock. At power-up and reset, the APU is randomly in an\n        // odd or even cycle with respect to the first clock of the first\n        // instruction executed by the CPU.\n\n        // ; assume even APU and CPU clocks occur together\n        // lda   #$00\n        // sta   $4017       ; mode begins in one clock\n        // sta   <0          ; delay 3 clocks\n        // sta   $4017       ; mode begins immediately\n        //\n        // Tests for APU clock jitter. Also tests basic timing of frame irq flag\n        // since it's needed to determine jitter.\n        // 1) Passed tests\n        // 2) Frame irq is set too soon\n        // 3) Frame irq is set too late\n        // 4) Even jitter not handled properly\n        // 5) Odd jitter not handled properly\n        clock_jitter,\n        // Mode 0 Timing\n        // -------------\n        // -5    lda   #$00\n        // -3    sta   $4017\n        // 0     (write occurs here)\n        // 1\n        // 2\n        // 3\n        // ...\n        //       Step 1\n        // 7459  Clock linear\n        // ...\n        //       Step 2\n        // 14915 Clock linear & length\n        // ...\n        //       Step 3\n        // 22373 Clock linear\n        // ...\n        //       Step 4\n        // 29830 Set frame irq\n        // 29831 Clock linear & length and set frame irq\n        // 29832 Set frame irq\n        // ...\n        //       Step 1\n        // 37289 Clock linear\n        // ...\n        // etc.\n        //\n        // Return current jitter in A. Takes an even number of clocks. Tests length\n        // counter timing in mode 0.\n        // 1) Passed tests\n        // 2) First length is clocked too soon\n        // 3) First length is clocked too late\n        // 4) Second length is clocked too soon\n        // 5) Second length is clocked too late\n        // 6) Third length is clocked too soon\n        // 7) Third length is clocked too late\n        len_timing_mode0,\n        // Mode 1 Timing\n        // -------------\n        // -5    lda   #$80\n        // -3    sta   $4017\n        // 0     (write occurs here)\n        //       Step 0\n        // 1     Clock linear & length\n        // 2\n        // ...\n        //       Step 1\n        // 7459  Clock linear\n        // ...\n        //       Step 2\n        // 14915 Clock linear & length\n        // ...\n        //       Step 3\n        // 22373 Clock linear\n        // ...\n        //       Step 4\n        // 29829 (do nothing)\n        // ...\n        //       Step 0\n        // 37283 Clock linear & length\n        // ...\n        // etc.\n        //\n        // Tests length counter timing in mode 1.\n        // 1) Passed tests\n        // 2) First length is clocked too soon\n        // 3) First length is clocked too late\n        // 4) Second length is clocked too soon\n        // 5) Second length is clocked too late\n        // 6) Third length is clocked too soon\n        // 7) Third length is clocked too late\n        len_timing_mode1,\n        // Frame interrupt flag is set three times in a row 29831 clocks after\n        // writing $4017 with $00.\n        // 1) Success\n        // 2) Flag first set too soon\n        // 3) Flag first set too late\n        // 4) Flag last set too soon\n        // 5) Flag last set too late\n        irq_flag_timing,\n        // IRQ handler is invoked at minimum 29833 clocks after writing $00 to\n        // $4017.\n        // 1) Passed tests\n        // 2) Too soon\n        // 3) Too late\n        // 4) Never occurred\n        irq_timing,\n        // After reset or power-up, APU acts as if $4017 were written with $00 from\n        // 9 to 12 clocks before first instruction begins.\n        // 1) Success\n        // 2) $4015 didn't read back as $00 at power-up\n        // 3) Fourth step occurs too soon\n        // 4) Fourth step occurs too late\n        reset_timing,\n        // Changes to length counter halt occur after clocking length, not before.\n        // 1) Passed tests\n        // 2) Length shouldn't be clocked when halted at 14914\n        // 3) Length should be clocked when halted at 14915\n        // 4) Length should be clocked when unhalted at 14914\n        // 5) Length shouldn't be clocked when unhalted at 14915\n        len_halt_timing,\n        // Write to length counter reload should be ignored when made during length\n        // counter clocking and the length counter is not zero.\n        // 1) Passed tests\n        // 2) Reload just before length clock should work normally\n        // 3) Reload just after length clock should work normally\n        // 4) Reload during length clock when ctr = 0 should work normally\n        // 5) Reload during length clock when ctr > 0 should be ignored\n        len_reload_timing,\n        // Verifies timing of length counter clocks in both modes\n        // 2) First length of mode 0 is too soon\n        // 3) First length of mode 0 is too late\n        // 4) Second length of mode 0 is too soon\n        // 5) Second length of mode 0 is too late\n        // 6) Third length of mode 0 is too soon\n        // 7) Third length of mode 0 is too late\n        // 8) First length of mode 1 is too soon\n        // 9) First length of mode 1 is too late\n        // 10) Second length of mode 1 is too soon\n        // 11) Second length of mode 1 is too late\n        // 12) Third length of mode 1 is too soon\n        // 13) Third length of mode 1 is too late\n        len_timing,\n        // Verifies basic DMC operation\n        // 2) DMC isn't working well enough to test further\n        // 3) Starting DMC should reload length from $4013\n        // 4) Writing $10 to $4015 should restart DMC if previous sample finished\n        // 5) Writing $10 to $4015 should not affect DMC if previous sample is\n        // still playing\n        // 6) Writing $00 to $4015 should stop current sample\n        // 7) Changing $4013 shouldn't affect current sample length\n        // 8) Shouldn't set DMC IRQ flag when flag is disabled\n        // 9) Should set IRQ flag when enabled and sample ends\n        // 10) Reading IRQ flag shouldn't clear it\n        // 11) Writing to $4015 should clear IRQ flag\n        // 12) Disabling IRQ flag should clear it\n        // 13) Looped sample shouldn't end until $00 is written to $4015\n        // 14) Looped sample shouldn't ever set IRQ flag\n        // 15) Clearing loop flag and then setting again shouldn't stop loop\n        // 16) Clearing loop flag should end sample once it reaches end\n        // 17) Looped sample should reload length from $4013 each time it reaches\n        // end\n        // 18) $4013=0 should give 1-byte sample\n        // 19) There should be a one-byte buffer that's filled immediately if empty\n        dmc_basics,\n        // Verifies the DMC's 16 rates\n        dmc_rates,\n        // Reset\n        // See: <https://github.com/christopherpow/nes-test-roms/tree/master/apu_reset>\n        //\n        // At power and reset, $4015 is cleared.\n        // 2) At power, $4015 should be cleared\n        // 3) At reset, $4015 should be cleared\n        reset_4015_cleared,\n        // At power, it is as if $00 were written to $4017,\n        // then a 9-12 clock delay, then execution from address\n        // in reset vector.\n\n        // At reset, same as above, except last value written\n        // to $4017 is written again, rather than $00.\n\n        // The delay from when $00 was written to $4017 is\n        // printed. Delay after NES being powered off for a\n        // minute is usually 9.\n\n        // 2) Frame IRQ flag should be set later after power/reset\n        // 3) Frame IRQ flag should be set sooner after power/reset\n        reset_4017_timing,\n        // At power, $4017 = $00.\n        // At reset, $4017 mode is unchanged, but IRQ inhibit\n        // flag is sometimes cleared.\n\n        // 2) At power, $4017 should be written with $00\n        // 3) At reset, $4017 should should be rewritten with last value written\n        reset_4017_written,\n        // At power and reset, IRQ flag is clear.\n\n        // 2) At power, flag should be clear\n        // 3) At reset, flag should be clear\n        reset_irq_flag_cleared,\n        // At power and reset, length counters are enabled.\n\n        // 2) At power, length counters should be enabled\n        // 3) At reset, length counters should be enabled, triangle unaffected\n        reset_len_ctrs_enabled,\n        // At power and reset, $4017, $4015, and length counters work\n        // immediately.\n\n        // 2) At power, writes should work immediately\n        // 3) At reset, writes should work immediately\n        reset_works_immediately,\n        // 11 tests that verify a number of behaviors with the APU (including the frame counter)\n        //\n        // See: <https://forums.nesdev.org/viewtopic.php?f=3&t=11174>\n        test_1,\n        test_2,\n        test_3,\n        test_4,\n        test_5,\n        test_6,\n        test_7,\n        test_8,\n        test_9,\n        test_10,\n        // PAL APU tests\n        //\n        // See: <https://github.com/christopherpow/nes-test-roms/tree/master/pal_apu_tests>\n        //\n        // Tests basic length counter operation\n        // 1) Passed tests\n        // 2) Problem with length counter load or $4015\n        // 3) Problem with length table, timing, or $4015\n        // 4) Writing $80 to $4017 should clock length immediately\n        // 5) Writing $00 to $4017 shouldn't clock length immediately\n        // 6) Clearing enable bit in $4015 should clear length counter\n        // 7) When disabled via $4015, length shouldn't allow reloading\n        // 8) Halt bit should suspend length clocking\n        pal_len_ctr,\n        // Tests all length table entries.\n        // 1) Passed\n        // 2) Failed. Prints four bytes $II $ee $cc $02 that indicate the length load\n        // value written (ll), the value that the emulator uses ($ee), and the correct\n        // value ($cc).\n        pal_len_table,\n        // Tests basic operation of frame irq flag.\n        // 1) Tests passed\n        // 2) Flag shouldn't be set in $4017 mode $40\n        // 3) Flag shouldn't be set in $4017 mode $80\n        // 4) Flag should be set in $4017 mode $00\n        // 5) Reading flag clears it\n        // 6) Writing $00 or $80 to $4017 doesn't affect flag\n        // 7) Writing $40 or $c0 to $4017 clears flag\n        pal_irq_flag,\n        // Tests for APU clock jitter. Also tests basic timing of frame irq flag since\n        // it's needed to determine jitter. It's OK if you don't implement jitter, in\n        // which case you'll get error #5, but you can still run later tests without\n        // problem.\n        // 1) Passed tests\n        // 2) Frame irq is set too soon\n        // 3) Frame irq is set too late\n        // 4) Even jitter not handled properly\n        // 5) Odd jitter not handled properly\n        pal_clock_jitter,\n        // Tests length counter timing in mode 0.\n        // 1) Passed tests\n        // 2) First length is clocked too soon\n        // 3) First length is clocked too late\n        // 4) Second length is clocked too soon\n        // 5) Second length is clocked too late\n        // 6) Third length is clocked too soon\n        // 7) Third length is clocked too late\n        pal_len_timing_mode0,\n        // Tests length counter timing in mode 1.\n        // 1) Passed tests\n        // 2) First length is clocked too soon\n        // 3) First length is clocked too late\n        // 4) Second length is clocked too soon\n        // 5) Second length is clocked too late\n        // 6) Third length is clocked too soon\n        // 7) Third length is clocked too late\n        pal_len_timing_mode1,\n        // Frame interrupt flag is set three times in a row 33255 clocks after writing\n        // $4017 with $00.\n        // 1) Success\n        // 2) Flag first set too soon\n        // 3) Flag first set too late\n        // 4) Flag last set too soon\n        // 5) Flag last set too late\n        pal_irq_flag_timing,\n        // IRQ handler is invoked at minimum 33257 clocks after writing $00 to $4017.\n        // 1) Passed tests\n        // 2) Too soon\n        // 3) Too late\n        // 4) Never occurred\n        pal_irq_timing,\n        // Changes to length counter halt occur after clocking length, not before.\n        // 1) Passed tests\n        // 2) Length shouldn't be clocked when halted at 16628\n        // 3) Length should be clocked when halted at 16629\n        // 4) Length should be clocked when unhalted at 16628\n        // 5) Length shouldn't be clocked when unhalted at 16629\n        pal_len_halt_timing,\n        // Write to length counter reload should be ignored when made during length\n        // counter clocking and the length counter is not zero.\n        // 1) Passed tests\n        // 2) Reload just before length clock should work normally\n        // 3) Reload just after length clock should work normally\n        // 4) Reload during length clock when ctr = 0 should work normally\n        // 5) Reload during length clock when ctr > 0 should be ignored\n        pal_len_reload_timing,\n        #[ignore = \"todo: passes, compare output\"]\n        apu_env,\n        #[ignore = \"todo: passes, check status\"]\n        dmc_buffer_retained,\n        #[ignore = \"todo: passes, compare output\"]\n        dmc_latency,\n        #[ignore = \"todo: passes, compare output\"]\n        dmc_pitch,\n        #[ignore = \"todo: passes, check status\"]\n        dmc_status,\n        #[ignore = \"todo: passes, check status\"]\n        dmc_status_irq,\n        #[ignore = \"todo: passes, compare output\"]\n        lin_ctr,\n        #[ignore = \"todo: passes, compare output\"]\n        noise_pitch,\n        // Tests pulse behavior when writing to $4003/$4007 (reset duty but not dividers)\n        #[ignore = \"todo: unknown, compare output\"]\n        phase_reset,\n        #[ignore = \"todo: passes, compare output\"]\n        square_pitch,\n        #[ignore = \"todo: passes, compare output\"]\n        sweep_cutoff,\n        #[ignore = \"todo: passes, compare output\"]\n        sweep_sub,\n        #[ignore = \"todo: passes, compare output\"]\n        triangle_pitch,\n        // This program demonstrates the channel balance among implementations\n        // of the NES architecture.\n\n        // The pattern consists of a set of 12 tones, as close to 1000 Hz as\n        // the NES allows:\n        // 1. Channel 1, 1/8 duty\n        // 2. Channel 1, 1/4 duty\n        // 3. Channel 1, 1/2 duty\n        // 4. Channel 1, 3/4 duty\n        // 5. Channels 1 and 2, 1/8 duty\n        // 6. Channels 1 and 2, 1/4 duty\n        // 7. Channels 1 and 2, 1/2 duty\n        // 8. Channels 1 and 2, 3/4 duty\n        // 9. Channel 3\n        // 10. Channel 4, long LFSR period\n        // 11. Channel 4, short LFSR period\n        // 12. Channel 5, amplitude 30\n\n        // When the user presses A on controller 1, the pattern plays three\n        // times, with channel 5 held steady at 0, 48, and 96.  The high point\n        // of tone 12 each time is 30 units above the level for that time,\n        // that is, 30, 78, and 126 respectively.\n        //\n        // See: <https://github.com/christopherpow/nes-test-roms/tree/master/volume_tests>\n        #[ignore = \"todo: unknown, compare output\"]\n        volumes,\n        // Mixer\n        // The test status is written to $6000. $80 means the test is running, $81\n        // means the test needs the reset button pressed, but delayed by at least\n        // 100 msec from now. $00-$7F means the test has completed and given that\n        // result code.\n\n        // To allow an emulator to know when one of these tests is running and the\n        // data at $6000+ is valid, as opposed to some other NES program, $DE $B0\n        // $G1 is written to $6001-$6003.\n        //\n        // A byte is reported as a series of tones. The code is in binary, with a\n        // low tone for 0 and a high tone for 1, and with leading zeroes skipped.\n        // The first tone is always a zero. A final code of 0 means passed, 1 means\n        // failure, and 2 or higher indicates a specific reason. See the source\n        // code of the test for more information about the meaning of a test code.\n        // They are found after the set_test macro. For example, the cause of test\n        // code 3 would be found in a line containing set_test 3. Examples:\n\n        //  Tones         Binary  Decimal  Meaning\n        //  - - - - - - - - - - - - - - - - - - - -\n        //  low              0      0      passed\n        //  low high        01      1      failed\n        //  low high low   010      2      error 2\n        //\n        // See <https://github.com/christopherpow/nes-test-roms/tree/master/apu_mixer>\n        #[ignore = \"todo: passes, compare $6000 output\"]\n        dmc,\n        #[ignore = \"todo: passes, compare $6000 output\"]\n        noise,\n        #[ignore = \"todo: passes, compare $6000 output\"]\n        square,\n        #[ignore = \"todo: passes, compare $6000 output\"]\n        triangle,\n    );\n    test_roms!(\n        input,\n        \"test_roms/input\",\n        zapper_flip,\n        zapper_light,\n        #[ignore = \"todo\"]\n        zapper_stream,\n        #[ignore = \"todo\"]\n        zapper_trigger,\n    );\n    test_roms!(\n        m004_txrom,\n        \"test_roms/mapper/m004_txrom\",\n        a12_clocking,\n        clocking,\n        details,\n        rev_b,\n        scanline_timing,\n        big_chr_ram,\n        rev_a,\n    );\n    test_roms!(m005_exram, \"test_roms/mapper/m005_exrom\", exram, basics);\n}\n"
  },
  {
    "path": "tetanes-core/src/control_deck.rs",
    "content": "//! Control Deck implementation. The primary entry-point for emulating the NES.\n\nuse crate::{\n    apu::{self, Apu, Channel},\n    bus::Bus,\n    cart::{self, Cart},\n    common::{Clock, NesRegion, Regional, Reset, ResetKind, Sram},\n    cpu::Cpu,\n    debug::Debugger,\n    fs,\n    genie::{self, GenieCode},\n    input::{FourPlayer, Joypad, Player},\n    mapper::{Bf909Revision, Mapper, MapperRevision, Mmc3Revision},\n    mem::RamState,\n    ppu::Ppu,\n    video::{Video, VideoFilter},\n};\nuse bitflags::bitflags;\nuse serde::{Deserialize, Serialize};\nuse std::{\n    io::Read,\n    path::{Path, PathBuf},\n};\nuse thiserror::Error;\nuse tracing::{error, info};\n\n/// Result returned from [`ControlDeck`] methods.\npub type Result<T> = std::result::Result<T, Error>;\n\n/// Errors that [`ControlDeck`] can return.\n#[derive(Error, Debug)]\n#[must_use]\npub enum Error {\n    /// [`Cart`] error when loading a ROM.\n    #[error(transparent)]\n    Cart(#[from] cart::Error),\n    /// Battery-backed RAM error.\n    #[error(\"sram error: {0:?}\")]\n    Sram(fs::Error),\n    /// Save state error.\n    #[error(\"save state error: {0:?}\")]\n    SaveState(fs::Error),\n    /// When trying to load a save state that doesn't exist.\n    #[error(\"no save state found\")]\n    NoSaveStateFound,\n    /// Operational error indicating a ROM must be loaded first.\n    #[error(\"no rom is loaded\")]\n    RomNotLoaded,\n    /// CPU state is corrupted and emulation can't continue. Could be due to a bad ROM image or a\n    /// corrupt save state.\n    #[error(\"cpu state is corrupted\")]\n    CpuCorrupted,\n    /// Invalid Game Genie code error.\n    #[error(transparent)]\n    InvalidGenieCode(#[from] genie::Error),\n    /// Invalid file path.\n    #[error(\"invalid file path {0:?}\")]\n    InvalidFilePath(PathBuf),\n    #[error(\"unimplemented mapper `{0}`\")]\n    UnimplementedMapper(u16),\n    /// Filesystem error.\n    #[error(transparent)]\n    Fs(#[from] fs::Error),\n    /// IO error.\n    #[error(\"{context}: {source:?}\")]\n    Io {\n        context: String,\n        source: std::io::Error,\n    },\n}\n\nimpl Error {\n    pub fn io(source: std::io::Error, context: impl Into<String>) -> Self {\n        Self::Io {\n            context: context.into(),\n            source,\n        }\n    }\n}\n\nbitflags! {\n    /// Headless mode flags to disable audio and video processing, reducing CPU usage.\n    #[derive(Default, Debug, Copy, Clone, PartialEq, Serialize, Deserialize, )]\n    #[must_use]\n    pub struct HeadlessMode: u8 {\n        /// Disable audio mixing.\n        const NO_AUDIO = 0x01;\n        /// Disable pixel rendering.\n        const NO_VIDEO = 0x02;\n    }\n}\n\n/// Set of desired mapper revisions to use when loading a ROM matching the available mapper types.\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub struct MapperRevisionsConfig {\n    /// MMC3 mapper revision.\n    pub mmc3: Mmc3Revision,\n    /// BF909 mapper revision.\n    pub bf909: Bf909Revision,\n}\n\nimpl MapperRevisionsConfig {\n    /// Set the desired mapper revision to use when loading a ROM matching the available mapper types.\n    pub const fn set(&mut self, rev: MapperRevision) {\n        match rev {\n            MapperRevision::Mmc3(rev) => self.mmc3 = rev,\n            MapperRevision::Bf909(rev) => self.bf909 = rev,\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]\n#[serde(default)]\n#[must_use]\n/// Control deck configuration settings.\npub struct Config {\n    /// Video filter.\n    pub filter: VideoFilter,\n    /// NES region.\n    pub region: NesRegion,\n    /// RAM initialization state.\n    pub ram_state: RamState,\n    /// Four player adapter.\n    pub four_player: FourPlayer,\n    /// Enable zapper gun.\n    pub zapper: bool,\n    /// Game Genie codes.\n    pub genie_codes: Vec<GenieCode>,\n    /// Whether to support concurrent D-Pad input which wasn't possible on the original NES.\n    pub concurrent_dpad: bool,\n    /// Apu channels enabled.\n    pub channels_enabled: [bool; Apu::MAX_CHANNEL_COUNT],\n    /// Headless mode.\n    pub headless_mode: HeadlessMode,\n    /// Data directory for storing battery-backed RAM.\n    pub data_dir: PathBuf,\n    /// Which mapper revisions to emulate for any ROM loaded that uses this mapper.\n    pub mapper_revisions: MapperRevisionsConfig,\n    /// Whether to emulate PPU warmup where writes to certain registers are ignored. Can result in\n    /// some games not working correctly.\n    ///\n    /// See: <https://www.nesdev.org/wiki/PPU_power_up_state>\n    pub emulate_ppu_warmup: bool,\n}\n\nimpl Config {\n    /// Base directory for storing TetaNES data.\n    pub const BASE_DIR: &'static str = \"tetanes\";\n    /// Directory for storing battery-backed Cart RAM.\n    pub const SRAM_DIR: &'static str = \"sram\";\n    /// File extension for battery-backed Cart RAM.\n    pub const SRAM_EXTENSION: &'static str = \"sram\";\n\n    /// Returns the default directory where TetaNES data is stored.\n    #[inline]\n    #[must_use]\n    pub fn default_data_dir() -> PathBuf {\n        dirs::data_local_dir().map_or_else(|| PathBuf::from(\"data\"), |dir| dir.join(Self::BASE_DIR))\n    }\n\n    /// Returns the directory used to store battery-backed Cart RAM.\n    #[inline]\n    #[must_use]\n    pub fn sram_dir(&self) -> PathBuf {\n        self.data_dir.join(Self::SRAM_DIR)\n    }\n}\n\nimpl Default for Config {\n    fn default() -> Self {\n        Self {\n            filter: VideoFilter::default(),\n            region: NesRegion::Auto,\n            ram_state: RamState::Random,\n            four_player: FourPlayer::default(),\n            zapper: false,\n            genie_codes: Vec::new(),\n            concurrent_dpad: false,\n            channels_enabled: [true; Apu::MAX_CHANNEL_COUNT],\n            headless_mode: HeadlessMode::empty(),\n            data_dir: Self::default_data_dir(),\n            mapper_revisions: MapperRevisionsConfig::default(),\n            emulate_ppu_warmup: false,\n        }\n    }\n}\n\n/// Represents a loaded ROM [`Cart`].\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct LoadedRom {\n    /// Name of ROM.\n    pub name: String,\n    /// Whether the loaded Cart is battery-backed.\n    pub battery_backed: bool,\n    /// Auto-detected of the loaded Cart.\n    pub region: NesRegion,\n}\n\n/// Represents an NES Control Deck. Encapsulates the entire emulation state.\n#[derive(Debug, Clone)]\n#[must_use]\npub struct ControlDeck {\n    /// Whether a ROM is loaded and the emulation is currently running or not.\n    running: bool,\n    /// Video output and filtering.\n    video: Video,\n    /// Last frame number rendered, allowing `frame_buffer` to be cached if called multiple times.\n    last_frame_number: u32,\n    /// The currently loaded ROM [`Cart`], if any.\n    loaded_rom: Option<LoadedRom>,\n    /// Directory for storing battery-backed Cart RAM if a ROM is loaded.\n    sram_dir: PathBuf,\n    /// Mapper revisions to emulate for any ROM loaded that matches the given mappers.\n    mapper_revisions: MapperRevisionsConfig,\n    /// Whether to auto-detect the region based on the loaded Cart.\n    auto_detect_region: bool,\n    /// Remaining CPU cycles to execute used to clock a given number of seconds.\n    cycles_remaining: f32,\n    /// Emulated frame speed step ranging from 1 (0.25 speed) to 8 (2.0).\n    frame_speed_step: u16,\n    /// Accumulated frame speed to account for slower 1x speeds.\n    frame_accumulator: u16,\n    /// NES CPU.\n    cpu: Cpu,\n}\n\nimpl Default for ControlDeck {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl ControlDeck {\n    /// Create a NES `ControlDeck` with the default configuration.\n    pub fn new() -> Self {\n        Self::with_config(Config::default())\n    }\n\n    /// Create a NES `ControlDeck` with a configuration.\n    pub fn with_config(cfg: Config) -> Self {\n        let mut cpu = Cpu::new(Bus::new(cfg.region, cfg.ram_state));\n        cpu.bus.ppu.skip_rendering = cfg.headless_mode.contains(HeadlessMode::NO_VIDEO);\n        cpu.bus.ppu.emulate_warmup = cfg.emulate_ppu_warmup;\n        cpu.bus.apu.skip_mixing = cfg.headless_mode.contains(HeadlessMode::NO_AUDIO);\n        if cfg.region.is_auto() {\n            cpu.set_region(NesRegion::default());\n        } else {\n            cpu.set_region(cfg.region);\n        }\n        cpu.bus.input.set_concurrent_dpad(cfg.concurrent_dpad);\n        cpu.bus.input.set_four_player(cfg.four_player);\n        cpu.bus.input.connect_zapper(cfg.zapper);\n        for (i, enabled) in cfg.channels_enabled.iter().enumerate() {\n            match Channel::try_from(i) {\n                Ok(channel) => cpu.bus.apu.set_channel_enabled(channel, *enabled),\n                Err(apu::ParseChannelError) => tracing::error!(\"invalid APU channel: {i}\"),\n            }\n        }\n        for genie_code in cfg.genie_codes.iter().cloned() {\n            cpu.bus.add_genie_code(genie_code);\n        }\n        let video = Video::with_filter(cfg.filter);\n        Self {\n            running: false,\n            video,\n            last_frame_number: 0,\n            loaded_rom: None,\n            sram_dir: cfg.sram_dir(),\n            mapper_revisions: cfg.mapper_revisions,\n            auto_detect_region: cfg.region.is_auto(),\n            cycles_remaining: 0.0,\n            frame_speed_step: 4,\n            frame_accumulator: 0,\n            cpu,\n        }\n    }\n\n    /// Returns the path to the SRAM save file for a given ROM name which is used to store\n    /// battery-backed Cart RAM. Returns `None` when the current platform doesn't have a\n    /// `data` directory and no custom `data_dir` was configured.\n    pub fn sram_path(&self, name: &str) -> PathBuf {\n        self.sram_dir\n            .join(name)\n            .with_extension(Config::SRAM_EXTENSION)\n    }\n\n    /// Loads a ROM cartridge into memory\n    ///\n    /// # Errors\n    ///\n    /// If there is any issue loading the ROM, then an error is returned.\n    pub fn load_rom<S: ToString, F: Read>(&mut self, name: S, rom: &mut F) -> Result<LoadedRom> {\n        let name = name.to_string();\n        self.unload_rom()?;\n        let cart = Cart::from_rom(&name, rom, self.cpu.bus.ram_state)?;\n        if cart.mapper.is_none() {\n            return Err(Error::UnimplementedMapper(cart.mapper_num()));\n        }\n        let loaded_rom = LoadedRom {\n            name: name.clone(),\n            battery_backed: cart.battery_backed(),\n            region: cart.region(),\n        };\n        if self.auto_detect_region {\n            self.cpu.set_region(loaded_rom.region);\n        }\n        self.cpu.bus.load_cart(cart);\n        self.loaded_rom = Some(loaded_rom.clone());\n        self.update_mapper_revisions();\n        self.reset(ResetKind::Hard);\n        let sram_dir = self.sram_path(&name);\n        if let Err(err) = self.load_sram(sram_dir) {\n            error!(\"failed to load SRAM: {err:?}\");\n        }\n        Ok(loaded_rom)\n    }\n\n    /// Loads a ROM cartridge into memory from a path.\n    ///\n    /// # Errors\n    ///\n    /// If there is any issue loading the ROM, then an error is returned.\n    pub fn load_rom_path(&mut self, path: impl AsRef<std::path::Path>) -> Result<LoadedRom> {\n        use std::{fs::File, io::BufReader};\n\n        let path = path.as_ref();\n        let filename = fs::filename(path);\n        info!(\"loading ROM: {filename}\");\n        File::open(path)\n            .map_err(|err| Error::io(err, format!(\"failed to open rom {path:?}\")))\n            .and_then(|rom| self.load_rom(filename, &mut BufReader::new(rom)))\n    }\n\n    /// Unloads the currently loaded ROM and saves SRAM to disk if the Cart is battery-backed.\n    ///\n    /// # Errors\n    ///\n    /// If the loaded [`Cart`] is battery-backed and saving fails, then an error is returned.\n    pub fn unload_rom(&mut self) -> Result<()> {\n        if let Some(rom) = &self.loaded_rom {\n            let sram_dir = self.sram_path(&rom.name);\n            if let Err(err) = self.save_sram(sram_dir) {\n                error!(\"failed to save SRAM: {err:?}\");\n            }\n        }\n        self.loaded_rom = None;\n        self.cpu.bus.unload_cart();\n        self.running = false;\n        Ok(())\n    }\n\n    /// Load a previously saved CPU state.\n    #[inline]\n    pub fn load_cpu(&mut self, cpu: Cpu) {\n        self.cpu.load(cpu);\n    }\n\n    /// Set the [`MapperRevision`] to emulate for the any ROM loaded that uses this mapper.\n    #[inline]\n    pub const fn set_mapper_revision(&mut self, rev: MapperRevision) {\n        self.mapper_revisions.set(rev);\n        self.update_mapper_revisions();\n    }\n\n    /// Set the set of [`MapperRevisionsConfig`] to emulate for the any ROM loaded that uses this\n    /// mapper.\n    #[inline]\n    pub const fn set_mapper_revisions(&mut self, revs: MapperRevisionsConfig) {\n        self.mapper_revisions = revs;\n        self.update_mapper_revisions();\n    }\n\n    /// Internal method to update the loaded ROM mapper revision when `mapper_revisions` is\n    /// updated.\n    const fn update_mapper_revisions(&mut self) {\n        match &mut self.cpu.bus.ppu.mapper {\n            Mapper::Txrom(mapper) => {\n                mapper.set_revision(self.mapper_revisions.mmc3);\n            }\n            Mapper::Bf909x(mapper) => {\n                mapper.set_revision(self.mapper_revisions.bf909);\n            }\n            // Remaining mappers all have more concrete detection via ROM headers\n            Mapper::None(_)\n            | Mapper::Nrom(_)\n            | Mapper::Sxrom(_)\n            | Mapper::Uxrom(_)\n            | Mapper::Cnrom(_)\n            | Mapper::Exrom(_)\n            | Mapper::Axrom(_)\n            | Mapper::Pxrom(_)\n            | Mapper::Fxrom(_)\n            | Mapper::ColorDreams(_)\n            | Mapper::BandaiFCG(_)\n            | Mapper::JalecoSs88006(_)\n            | Mapper::Namco163(_)\n            | Mapper::Vrc6(_)\n            | Mapper::Bnrom(_)\n            | Mapper::Nina001(_)\n            | Mapper::Gxrom(_)\n            | Mapper::SunsoftFme7(_)\n            | Mapper::Nina003006(_) => (),\n        }\n    }\n\n    /// Set whether concurrent D-Pad input is enabled which wasn't possible on the original NES.\n    #[inline]\n    pub fn set_concurrent_dpad(&mut self, enabled: bool) {\n        self.cpu.bus.input.set_concurrent_dpad(enabled);\n    }\n\n    /// Set emulation RAM initialization state.\n    #[inline]\n    pub const fn set_ram_state(&mut self, ram_state: RamState) {\n        self.cpu.bus.ram_state = ram_state;\n    }\n\n    /// Set the headless mode which can increase performance when the frame and audio outputs are\n    /// not needed.\n    #[inline]\n    pub const fn set_headless_mode(&mut self, mode: HeadlessMode) {\n        self.cpu.bus.ppu.skip_rendering = mode.contains(HeadlessMode::NO_VIDEO);\n        self.cpu.bus.apu.skip_mixing = mode.contains(HeadlessMode::NO_AUDIO);\n    }\n\n    /// Set whether to emulate PPU warmup where writes to certain registers are ignored. Can result\n    /// in some games not working correctly.\n    ///\n    /// See: <https://www.nesdev.org/wiki/PPU_power_up_state>\n    #[inline]\n    pub const fn set_emulate_ppu_warmup(&mut self, enabled: bool) {\n        self.cpu.bus.ppu.emulate_warmup = enabled;\n    }\n\n    /// Adds a debugger callback to be executed any time the debugger conditions\n    /// match.\n    pub fn add_debugger(&mut self, debugger: Debugger) {\n        match debugger {\n            Debugger::Ppu(debugger) => self.cpu.bus.ppu.debugger = debugger,\n        }\n    }\n\n    /// Removes a debugger callback.\n    pub fn remove_debugger(&mut self, debugger: Debugger) {\n        match debugger {\n            Debugger::Ppu(_) => self.cpu.bus.ppu.debugger = Default::default(),\n        }\n    }\n\n    /// Returns the name of the currently loaded ROM [`Cart`]. Returns `None` if no ROM is loaded.\n    #[inline]\n    #[must_use]\n    pub const fn loaded_rom(&self) -> Option<&LoadedRom> {\n        self.loaded_rom.as_ref()\n    }\n\n    /// Returns the auto-detected [`NesRegion`] for the loaded ROM. Returns `None` if no ROM is\n    /// loaded.\n    #[inline]\n    #[must_use]\n    pub fn cart_region(&self) -> Option<NesRegion> {\n        self.loaded_rom.as_ref().map(|rom| rom.region)\n    }\n\n    /// Returns whether the loaded ROM is battery-backed. Returns `None` if no ROM is loaded.\n    #[inline]\n    #[must_use]\n    pub fn cart_battery_backed(&self) -> Option<bool> {\n        self.loaded_rom.as_ref().map(|rom| rom.battery_backed)\n    }\n\n    /// Returns the NES Work RAM.\n    #[inline]\n    #[must_use]\n    pub fn wram(&self) -> &[u8] {\n        self.cpu.bus.wram()\n    }\n\n    /// Save battery-backed Save RAM to a file (if cartridge supports it)\n    ///\n    /// # Errors\n    ///\n    /// If the file path is invalid or fails to save, then an error is returned.\n    pub fn save_sram(&self, path: impl AsRef<Path>) -> Result<()> {\n        if let Some(true) = self.cart_battery_backed() {\n            let path = path.as_ref();\n            if path.is_dir() {\n                return Err(Error::InvalidFilePath(path.to_path_buf()));\n            }\n\n            info!(\"saving SRAM...\");\n            self.cpu\n                .bus\n                .save(path.with_extension(Config::SRAM_EXTENSION))\n                .map_err(Error::Sram)?;\n        }\n        Ok(())\n    }\n\n    /// Load battery-backed Save RAM from a file (if cartridge supports it)\n    ///\n    /// # Errors\n    ///\n    /// If the file path is invalid or fails to load, then an error is returned.\n    pub fn load_sram(&mut self, path: impl AsRef<Path>) -> Result<()> {\n        if let Some(true) = self.cart_battery_backed() {\n            let path = path.as_ref();\n            if path.is_dir() {\n                return Err(Error::InvalidFilePath(path.to_path_buf()));\n            }\n            if path.is_file() {\n                info!(\"loading SRAM...\");\n                self.cpu\n                    .bus\n                    .load(path.with_extension(Config::SRAM_EXTENSION))\n                    .map_err(Error::Sram)?;\n            }\n        }\n        Ok(())\n    }\n\n    /// Save the current state of the console into a save file.\n    ///\n    /// # Errors\n    ///\n    /// If there is an issue saving the state, then an error is returned.\n    pub fn save_state(&mut self, path: impl AsRef<Path>) -> Result<()> {\n        if self.loaded_rom().is_none() {\n            return Err(Error::RomNotLoaded);\n        };\n        let path = path.as_ref();\n        fs::save(path, &self.cpu).map_err(Error::SaveState)\n    }\n\n    /// Load the console with data saved from a save state, if it exists.\n    ///\n    /// # Errors\n    ///\n    /// If there is an issue loading the save state, then an error is returned.\n    pub fn load_state(&mut self, path: impl AsRef<Path>) -> Result<()> {\n        if self.loaded_rom().is_none() {\n            return Err(Error::RomNotLoaded);\n        };\n        let path = path.as_ref();\n        if fs::exists(path) {\n            fs::load::<Cpu>(path)\n                .map_err(Error::SaveState)\n                .map(|mut cpu| {\n                    cpu.bus.input.clear(); // Discard inputs from save states\n                    self.load_cpu(cpu)\n                })\n        } else {\n            Err(Error::NoSaveStateFound)\n        }\n    }\n\n    /// Load the raw underlying frame buffer from the PPU for further processing.\n    #[inline]\n    pub fn frame_buffer_raw(&mut self) -> &[u16] {\n        self.cpu.bus.ppu.frame_buffer()\n    }\n\n    /// Load a frame worth of pixels.\n    #[inline]\n    pub fn frame_buffer(&mut self) -> &[u8] {\n        // Avoid applying filter if the frame number hasn't changed\n        let frame_number = self.cpu.bus.ppu.frame_number();\n        if self.last_frame_number == frame_number {\n            return &self.video.frame;\n        }\n\n        self.last_frame_number = frame_number;\n        self.video\n            .apply_filter(self.cpu.bus.ppu.frame_buffer(), frame_number)\n    }\n\n    /// Load a frame worth of pixels into the given buffer.\n    #[inline]\n    pub fn frame_buffer_into(&self, buffer: &mut [u8]) {\n        self.video.apply_filter_into(\n            self.cpu.bus.ppu.frame_buffer(),\n            self.cpu.bus.ppu.frame_number(),\n            buffer,\n        );\n    }\n\n    /// Get the current frame number.\n    #[inline(always)]\n    #[must_use]\n    pub const fn frame_number(&self) -> u32 {\n        self.cpu.bus.ppu.frame_number()\n    }\n\n    /// Get audio samples.\n    #[inline(always)]\n    #[must_use]\n    pub fn audio_samples(&self) -> &[f32] {\n        self.cpu.bus.audio_samples()\n    }\n\n    /// Clear audio samples.\n    #[inline]\n    pub fn clear_audio_samples(&mut self) {\n        self.cpu.bus.clear_audio_samples();\n    }\n\n    /// CPU clock rate based on currently configured NES region.\n    #[inline]\n    #[must_use]\n    pub const fn clock_rate(&self) -> f32 {\n        self.cpu.clock_rate()\n    }\n\n    /// Steps the control deck one CPU clock.\n    ///\n    /// # Errors\n    ///\n    /// If CPU encounters an invalid opcode, then an error is returned.\n    pub fn clock_instr(&mut self) -> Result<()> {\n        self.clock();\n        if self.cpu_corrupted() {\n            self.running = false;\n            return Err(Error::CpuCorrupted);\n        }\n        Ok(())\n    }\n\n    /// Steps the control deck the given number of seconds.\n    ///\n    /// # Errors\n    ///\n    /// If CPU encounters an invalid opcode, then an error is returned.\n    pub fn clock_seconds(&mut self, seconds: f32) -> Result<u32> {\n        self.cycles_remaining += self.clock_rate() * seconds;\n        let mut total_cycles = 0;\n        while self.cycles_remaining > 0.0 {\n            let start_cycles = self.cpu.cycle;\n            self.clock_instr()?;\n            let cycles = self.cpu.cycle - start_cycles;\n            total_cycles += cycles;\n            self.cycles_remaining -= cycles as f32;\n        }\n        Ok(total_cycles)\n    }\n\n    /// Steps the control deck the given  number of seconds, calling `handle_audito` with audio\n    /// samples and `handle_frame` with the `frame_buffer` if a frame is completed.\n    ///\n    /// # Errors\n    ///\n    /// If CPU encounters an invalid opcode, then an error is returned.\n    pub fn clock_seconds_output(\n        &mut self,\n        seconds: f32,\n        handle_audio: impl FnOnce(&[f32]),\n        handle_frame: impl FnOnce(&[u8]),\n    ) -> Result<()> {\n        let frame = self.frame_number();\n        self.clock_seconds(seconds)?;\n        let audio = self.cpu.bus.audio_samples();\n        handle_audio(audio);\n        self.cpu.bus.clear_audio_samples();\n        if frame != self.frame_number() {\n            let frame = self.video.apply_filter(\n                self.cpu.bus.ppu.frame_buffer(),\n                self.cpu.bus.ppu.frame_number(),\n            );\n            handle_frame(frame);\n        }\n        Ok(())\n    }\n\n    /// Steps the control deck an entire frame.\n    ///\n    /// # Errors\n    ///\n    /// If CPU encounters an invalid opcode, then an error is returned.\n    pub fn clock_frame(&mut self) -> Result<()> {\n        if !self.running {\n            return Err(Error::RomNotLoaded);\n        }\n\n        // Frames that aren't multiples of the default render 1 more/less frames\n        // every other frame\n        // e.g. a speed of 1.5 will clock # of frames: 1, 2, 1, 2, 1, 2, 1, 2, ...\n        // A speed of 0.5 will clock 0, 1, 0, 1, 0, 1, 0, 1, 0, ...\n        self.frame_accumulator += self.frame_speed_step;\n        let mut frames_to_clock = 0;\n        while self.frame_accumulator >= 4 {\n            self.frame_accumulator -= 4;\n            frames_to_clock += 1;\n        }\n\n        for _ in 0..frames_to_clock {\n            let frame = self.frame_number();\n            while frame == self.frame_number() {\n                self.clock_instr()?;\n            }\n            self.cpu.clock_sync();\n        }\n\n        Ok(())\n    }\n\n    /// Steps the control deck an entire frame, calling `handle_output` with the `frame_buffer` and\n    /// `audio_samples` for that frame.\n    ///\n    /// # Errors\n    ///\n    /// If CPU encounters an invalid opcode, then an error is returned.\n    pub fn clock_frame_output<T>(\n        &mut self,\n        handle_output: impl FnOnce(&[u8], &[f32]) -> T,\n    ) -> Result<T> {\n        self.clock_frame()?;\n        let frame = self.video.apply_filter(\n            self.cpu.bus.ppu.frame_buffer(),\n            self.cpu.bus.ppu.frame_number(),\n        );\n        let audio = self.cpu.bus.audio_samples();\n        let res = handle_output(frame, audio);\n        self.cpu.bus.clear_audio_samples();\n        Ok(res)\n    }\n\n    /// Steps the control deck an entire frame, copying the `frame_buffer` and\n    /// `audio_samples` for that frame into the provided buffers.\n    ///\n    /// # Errors\n    ///\n    /// If CPU encounteres an invalid opcode, an error is returned.\n    pub fn clock_frame_into(\n        &mut self,\n        frame_buffer: &mut [u8],\n        audio_samples: &mut [f32],\n    ) -> Result<()> {\n        self.clock_frame()?;\n        let frame = self.video.apply_filter(\n            self.cpu.bus.ppu.frame_buffer(),\n            self.cpu.bus.ppu.frame_number(),\n        );\n        frame_buffer.copy_from_slice(&frame[..frame_buffer.len()]);\n        let audio = self.cpu.bus.audio_samples();\n        audio_samples.copy_from_slice(&audio[..audio_samples.len()]);\n        self.clear_audio_samples();\n        Ok(())\n    }\n\n    /// Steps the control deck an entire frame with run-ahead frames to reduce input lag.\n    ///\n    /// # Errors\n    ///\n    /// If CPU encounters an invalid opcode, then an error is returned.\n    pub fn clock_frame_ahead<T>(\n        &mut self,\n        run_ahead: usize,\n        handle_output: impl FnOnce(&[u8], &[f32]) -> T,\n    ) -> Result<T> {\n        if run_ahead == 0 {\n            return self.clock_frame_output(handle_output);\n        }\n\n        // Clock current frame and save state so we can rewind\n        self.clock_frame()?;\n        let frame = std::mem::take(&mut self.cpu.bus.ppu.frame.buffer);\n        // Save state so we can rewind\n        let config = bincode::config::legacy();\n        let state = bincode::serde::encode_to_vec(&self.cpu, config)\n            .map_err(|err| fs::Error::SerializationFailed(err.to_string()))?;\n\n        // Clock additional frames and discard video/audio\n        self.cpu.bus.ppu.skip_rendering = true;\n        for _ in 1..run_ahead {\n            self.clock_frame()?;\n        }\n        self.cpu.bus.ppu.skip_rendering = false;\n\n        // Output the future frame video/audio\n        self.clear_audio_samples();\n        let result = self.clock_frame_output(handle_output)?;\n\n        // Restore back to current frame\n        let (mut state, _) = bincode::serde::decode_from_slice::<Cpu, _>(&state, config)\n            .map_err(|err| fs::Error::DeserializationFailed(err.to_string()))?;\n        state.bus.ppu.frame.buffer = frame;\n        self.load_cpu(state);\n\n        Ok(result)\n    }\n\n    /// Steps the control deck an entire frame with run-ahead frames to reduce input lag.\n    ///\n    /// # Errors\n    ///\n    /// If CPU encounters an invalid opcode, then an error is returned.\n    pub fn clock_frame_ahead_into(\n        &mut self,\n        run_ahead: usize,\n        frame_buffer: &mut [u8],\n        audio_samples: &mut [f32],\n    ) -> Result<()> {\n        if run_ahead == 0 {\n            return self.clock_frame_into(frame_buffer, audio_samples);\n        }\n\n        // Clock current frame and save state so we can rewind\n        self.clock_frame()?;\n        let frame = std::mem::take(&mut self.cpu.bus.ppu.frame.buffer);\n        // Save state so we can rewind\n        let config = bincode::config::legacy();\n        let state = bincode::serde::encode_to_vec(&self.cpu, config)\n            .map_err(|err| fs::Error::SerializationFailed(err.to_string()))?;\n\n        // Clock additional frames and discard video/audio\n        for _ in 1..run_ahead {\n            self.clock_frame()?;\n        }\n\n        // Output the future frame/audio\n        self.clear_audio_samples();\n        self.clock_frame_into(frame_buffer, audio_samples)?;\n\n        // Restore back to current frame\n        let (mut state, _) = bincode::serde::decode_from_slice::<Cpu, _>(&state, config)\n            .map_err(|err| fs::Error::DeserializationFailed(err.to_string()))?;\n        state.bus.ppu.frame.buffer = frame;\n        self.load_cpu(state);\n\n        Ok(())\n    }\n\n    /// Steps the control deck a single scanline.\n    ///\n    /// # Errors\n    ///\n    /// If CPU encounters an invalid opcode, then an error is returned.\n    pub fn clock_scanline(&mut self) -> Result<()> {\n        if !self.running {\n            return Err(Error::RomNotLoaded);\n        }\n\n        let current_scanline = self.cpu.bus.ppu.scanline;\n        while current_scanline == self.cpu.bus.ppu.scanline {\n            self.clock_instr()?;\n        }\n        Ok(())\n    }\n\n    /// Returns whether the CPU is corrupted or not which means it encounted an invalid/unhandled\n    /// opcode and can't proceed executing the current ROM.\n    #[cold]\n    #[inline(always)]\n    #[must_use]\n    pub const fn cpu_corrupted(&self) -> bool {\n        self.cpu.corrupted\n    }\n\n    /// Returns the current [`Cpu`] state.\n    #[inline]\n    pub const fn cpu(&self) -> &Cpu {\n        &self.cpu\n    }\n\n    /// Returns a mutable reference to the current [`Cpu`] state.\n    #[inline]\n    pub const fn cpu_mut(&mut self) -> &mut Cpu {\n        &mut self.cpu\n    }\n\n    /// Returns the current [`Ppu`] state.\n    #[inline]\n    pub const fn ppu(&self) -> &Ppu {\n        &self.cpu.bus.ppu\n    }\n\n    /// Returns a mutable reference to the current [`Ppu`] state.\n    #[inline]\n    pub const fn ppu_mut(&mut self) -> &mut Ppu {\n        &mut self.cpu.bus.ppu\n    }\n\n    /// Retu[ns the current [`Bus`] state.\n    #[inline]\n    pub const fn bus(&self) -> &Bus {\n        &self.cpu.bus\n    }\n\n    /// Returns a mutable reference to the current [`Bus`] state.\n    #[inline]\n    pub const fn bus_mut(&mut self) -> &mut Bus {\n        &mut self.cpu.bus\n    }\n\n    /// Returns the current [`Apu`] state.\n    #[inline]\n    pub const fn apu(&self) -> &Apu {\n        &self.cpu.bus.apu\n    }\n\n    /// Returns a mutable reference to the current [`Apu`] state.\n    #[inline]\n    pub const fn apu_mut(&mut self) -> &Apu {\n        &mut self.cpu.bus.apu\n    }\n\n    /// Returns the current [`Mapper`] state.\n    #[inline]\n    pub const fn mapper(&self) -> &Mapper {\n        &self.cpu.bus.ppu.mapper\n    }\n\n    /// Returns a mutable reference to the current [`Mapper`] state.\n    #[inline]\n    pub const fn mapper_mut(&mut self) -> &mut Mapper {\n        &mut self.cpu.bus.ppu.mapper\n    }\n\n    /// Returns the current four player mode.\n    #[inline]\n    pub const fn four_player(&self) -> FourPlayer {\n        self.cpu.bus.input.four_player\n    }\n\n    /// Enable/Disable Four Score for 4-player controllers.\n    #[inline]\n    pub fn set_four_player(&mut self, four_player: FourPlayer) {\n        self.cpu.bus.input.set_four_player(four_player);\n    }\n\n    /// Returns the current [`Joypad`] state for a given controller slot.\n    #[inline]\n    pub const fn joypad(&mut self, slot: Player) -> &Joypad {\n        self.cpu.bus.input.joypad(slot)\n    }\n\n    /// Returns a mutable reference to the current [`Joypad`] state for a given controller slot.\n    #[inline]\n    pub const fn joypad_mut(&mut self, slot: Player) -> &mut Joypad {\n        self.cpu.bus.input.joypad_mut(slot)\n    }\n\n    /// Returns whether the [`Zapper`](crate::input::Zapper) gun is connected.\n    #[inline]\n    pub const fn zapper_connected(&self) -> bool {\n        self.cpu.bus.input.zapper.connected\n    }\n\n    /// Enable [`Zapper`](crate::input::Zapper) gun.\n    #[inline]\n    pub const fn connect_zapper(&mut self, enabled: bool) {\n        self.cpu.bus.input.connect_zapper(enabled);\n    }\n\n    /// Returns the current [`Zapper`](crate::input::Zapper) aim position.\n    #[inline]\n    #[must_use]\n    pub const fn zapper_pos(&self) -> (u16, u16) {\n        let zapper = self.cpu.bus.input.zapper;\n        (zapper.x(), zapper.y())\n    }\n\n    /// Trigger [`Zapper`](crate::input::Zapper) gun.\n    #[inline]\n    pub fn trigger_zapper(&mut self) {\n        self.cpu.bus.input.zapper.trigger();\n    }\n\n    /// Aim [`Zapper`](crate::input::Zapper) gun.\n    #[inline]\n    pub const fn aim_zapper(&mut self, x: u16, y: u16) {\n        self.cpu.bus.input.zapper.aim(x, y);\n    }\n\n    /// Set the video filter for frame buffer output when calling [`ControlDeck::frame_buffer`].\n    #[inline]\n    pub const fn set_filter(&mut self, filter: VideoFilter) {\n        self.video.filter = filter;\n    }\n\n    /// Set the [`Apu`] sample rate.\n    #[inline]\n    pub fn set_sample_rate(&mut self, sample_rate: f32) {\n        self.cpu.bus.apu.set_sample_rate(sample_rate);\n    }\n\n    /// Set the emulation speed.\n    #[inline]\n    pub fn set_frame_speed(&mut self, speed: f32) {\n        self.frame_speed_step = (speed * 4.0) as u16;\n        self.cpu.bus.apu.set_frame_speed(speed);\n    }\n\n    /// Add a NES Game Genie code.\n    ///\n    /// # Errors\n    ///\n    /// If the genie code is invalid, an error is returned.\n    #[inline]\n    pub fn add_genie_code(&mut self, genie_code: String) -> Result<()> {\n        self.cpu.bus.add_genie_code(GenieCode::new(genie_code)?);\n        Ok(())\n    }\n\n    /// Remove a NES Game Genie code.\n    #[inline]\n    pub fn remove_genie_code(&mut self, genie_code: &str) {\n        self.cpu.bus.remove_genie_code(genie_code);\n    }\n\n    /// Remove all NES Game Genie codes.\n    #[inline]\n    pub fn clear_genie_codes(&mut self) {\n        self.cpu.bus.clear_genie_codes();\n    }\n\n    /// Returns whether a given [`Apu`] [`Channel`] is enabled.\n    #[inline]\n    #[must_use]\n    pub const fn channel_enabled(&self, channel: Channel) -> bool {\n        self.cpu.bus.apu.channel_enabled(channel)\n    }\n\n    /// Enable or disable a given [`Apu`] [`Channel`].\n    #[inline]\n    pub const fn set_apu_channel_enabled(&mut self, channel: Channel, enabled: bool) {\n        self.cpu.bus.apu.set_channel_enabled(channel, enabled);\n    }\n\n    /// Toggle a given [`Apu`] [`Channel`].\n    #[inline]\n    pub const fn toggle_apu_channel(&mut self, channel: Channel) {\n        self.cpu.bus.apu.toggle_channel(channel);\n    }\n\n    /// Returns whether the control deck is currently running.\n    #[inline]\n    #[must_use]\n    pub const fn is_running(&self) -> bool {\n        self.running\n    }\n}\n\nimpl Clock for ControlDeck {\n    /// Steps the control deck a single clock cycle.\n    #[inline(always)]\n    fn clock(&mut self) {\n        self.cpu.clock()\n    }\n}\n\nimpl Regional for ControlDeck {\n    /// Get the NES format for the emulation.\n    fn region(&self) -> NesRegion {\n        self.cpu.region()\n    }\n\n    /// Set the NES format for the emulation.\n    fn set_region(&mut self, region: NesRegion) {\n        self.auto_detect_region = region.is_auto();\n        if self.auto_detect_region {\n            self.cpu.set_region(self.cart_region().unwrap_or_default());\n        } else {\n            self.cpu.set_region(region);\n        }\n    }\n}\n\nimpl Reset for ControlDeck {\n    /// Resets the console.\n    fn reset(&mut self, kind: ResetKind) {\n        self.cpu.reset(kind);\n        if self.loaded_rom.is_some() {\n            self.running = true;\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/cpu/instr.rs",
    "content": "//! CPU Asddressing cmps and Operations\n\nuse crate::{\n    cpu::{Cpu, IrqFlags, Status},\n    mem::{Read, Write},\n};\nuse serde::{Deserialize, Serialize};\n\n/// List of all CPU official and unofficial operations.\n///\n/// # References\n///\n/// - <https://wiki.nesdev.org/w/index.php/6502_instructions>\n/// - <http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf>\n#[rustfmt::skip]\n#[allow(clippy::upper_case_acronyms)]\n#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]\n#[must_use]\npub enum Instr {\n    ADC, AND, ASL, BCC, BCS, BEQ, BIT, BMI, BNE, BPL, BRK, BVC, BVS, CLC, CLD, CLI, CLV, CMP, CPX,\n    CPY, DEC, DEX, DEY, EOR, INC, INX, INY, JMP, JSR, LDA, LDX, LDY, LSR, NOP, ORA, PHA, PHP, PLA,\n    PLP, ROL, ROR, RTI, RTS, SBC, SEC, SED, SEI, STA, STX, STY, TAX, TAY, TSX, TXA, TXS, TYA,\n    // \"Unofficial\" opcodes\n    ISB, DCP, AXS, LAS, LAX, AHX, SAX, XAA, SXA, RRA, TAS, SYA, ARR, SRE, ALR, RLA, ANC, SHAZ, ATX,\n    SHAA, SLO, #[default] HLT\n}\n\n/// CPU Addressing mode.\n#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]\n#[allow(clippy::upper_case_acronyms)]\n#[rustfmt::skip]\n#[must_use]\npub enum AddrMode {\n    // Accumulator and Implied\n    ACC, IMP,\n    // Immediate and relative\n    #[default] IMM, REL,\n    // Zero Page\n    ZP0, ABS, ZPX, ZPY,\n    // Indirect, with read/write variants\n    IND, IDX, IDY, IDYW,\n    // Absolute, with read/write variants\n    ABX, ABXW, ABY, ABYW,\n    // Special address mode, handled separately\n    OTH\n}\n\n/// CPU Opcode.\n#[derive(Debug, Copy, Clone)]\n#[must_use]\npub struct Op {\n    f: fn(&mut Cpu),\n    addr_mode: AddrMode,\n}\n\nimpl Op {\n    #[inline(always)]\n    pub fn run(&self, cpu: &mut Cpu) {\n        (self.f)(cpu)\n    }\n\n    #[inline(always)]\n    pub const fn addr_mode(&self) -> AddrMode {\n        self.addr_mode\n    }\n}\n\nmacro_rules! op {\n    ($f:ident, $addr_mode:ident) => {\n        Op {\n            f: Cpu::$f,\n            addr_mode: AddrMode::$addr_mode,\n        }\n    };\n}\n\n/// CPU Instruction Reference.\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[must_use]\npub struct InstrRef {\n    pub opcode: u8,\n    pub instr: Instr,\n    pub addr_mode: AddrMode,\n    pub cycles: u8,\n}\n\nimpl std::fmt::Display for InstrRef {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {\n        let instr = self.instr;\n        let unofficial = match instr {\n            Instr::HLT\n            | Instr::ISB\n            | Instr::DCP\n            | Instr::AXS\n            | Instr::LAS\n            | Instr::LAX\n            | Instr::AHX\n            | Instr::SAX\n            | Instr::XAA\n            | Instr::SXA\n            | Instr::RRA\n            | Instr::TAS\n            | Instr::SYA\n            | Instr::ARR\n            | Instr::SRE\n            | Instr::ALR\n            | Instr::RLA\n            | Instr::ANC\n            | Instr::SLO => \"*\",\n            Instr::NOP if self.opcode != 0xEA => \"*\", // 0xEA is the only official NOP\n            Instr::SBC if self.opcode == 0xEB => \"*\",\n            _ => \"\",\n        };\n        write!(f, \"{unofficial:1}{instr:?}\")\n    }\n}\n\nmacro_rules! instr {\n    ($opcode:expr, $instr:ident, $addr_mode:ident, $cycles:expr) => {\n        InstrRef {\n            opcode: $opcode,\n            instr: Instr::$instr,\n            addr_mode: AddrMode::$addr_mode,\n            cycles: $cycles,\n        }\n    };\n}\n\n/// CPU Addressing Modes\n///\n/// The 6502 can address 64KB from 0x0000 - 0xFFFF. The high byte is usually the page and the\n/// low byte the offset into the page. There are 256 total pages of 256 bytes.\nimpl Cpu {\n    /// 16x16 grid of 6502 opcode operations. Matches datasheet matrix for easy lookup\n    #[rustfmt::skip]\n    pub const OPS: [Op; 256] = [\n        //      0              1               2              3                4              5              6               7              8              9               A               B               C               D               E                F\n        /* 0 */ op!(brk, IMM), op!(ora, IDX),  op!(hlt, IMP), op!(slo,  IDX),  op!(nop, ZP0), op!(ora, ZP0), op!(aslm, ZP0), op!(slo, ZP0), op!(php, IMP), op!(ora, IMM),  op!(asla, ACC), op!(anc, IMM),  op!(nop,  ABS), op!(ora, ABS),  op!(aslm, ABS),  op!(slo,  ABS),\n        /* 1 */ op!(bpl, REL), op!(ora, IDY),  op!(hlt, IMP), op!(slo,  IDYW), op!(nop, ZPX), op!(ora, ZPX), op!(aslm, ZPX), op!(slo, ZPX), op!(clc, IMP), op!(ora, ABY),  op!(nop,  IMP), op!(slo, ABYW), op!(nop,  ABX), op!(ora, ABX),  op!(aslm, ABXW), op!(slo,  ABXW),\n        /* 2 */ op!(jsr, OTH), op!(and, IDX),  op!(hlt, IMP), op!(rla,  IDX),  op!(bit, ZP0), op!(and, ZP0), op!(rolm, ZP0), op!(rla, ZP0), op!(plp, IMP), op!(and, IMM),  op!(rola, ACC), op!(anc, IMM),  op!(bit,  ABS), op!(and, ABS),  op!(rolm, ABS),  op!(rla,  ABS),\n        /* 3 */ op!(bmi, REL), op!(and, IDY),  op!(hlt, IMP), op!(rla,  IDYW), op!(nop, ZPX), op!(and, ZPX), op!(rolm, ZPX), op!(rla, ZPX), op!(sec, IMP), op!(and, ABY),  op!(nop,  IMP), op!(rla, ABYW), op!(nop,  ABX), op!(and, ABX),  op!(rolm, ABXW), op!(rla,  ABXW),\n        /* 4 */ op!(rti, IMP), op!(eor, IDX),  op!(hlt, IMP), op!(sre,  IDX),  op!(nop, ZP0), op!(eor, ZP0), op!(lsrm, ZP0), op!(sre, ZP0), op!(pha, IMP), op!(eor, IMM),  op!(lsra, ACC), op!(alr, IMM),  op!(jmpa, ABS), op!(eor, ABS),  op!(lsrm, ABS),  op!(sre,  ABS),\n        /* 5 */ op!(bvc, REL), op!(eor, IDY),  op!(hlt, IMP), op!(sre,  IDYW), op!(nop, ZPX), op!(eor, ZPX), op!(lsrm, ZPX), op!(sre, ZPX), op!(cli, IMP), op!(eor, ABY),  op!(nop,  IMP), op!(sre, ABYW), op!(nop,  ABX), op!(eor, ABX),  op!(lsrm, ABXW), op!(sre,  ABXW),\n        /* 6 */ op!(rts, IMP), op!(adc, IDX),  op!(hlt, IMP), op!(rra,  IDX),  op!(nop, ZP0), op!(adc, ZP0), op!(rorm, ZP0), op!(rra, ZP0), op!(pla, IMP), op!(adc, IMM),  op!(rora, ACC), op!(arr, IMM),  op!(jmpi, IND), op!(adc, ABS),  op!(rorm, ABS),  op!(rra,  ABS),\n        /* 7 */ op!(bvs, REL), op!(adc, IDY),  op!(hlt, IMP), op!(rra,  IDYW), op!(nop, ZPX), op!(adc, ZPX), op!(rorm, ZPX), op!(rra, ZPX), op!(sei, IMP), op!(adc, ABY),  op!(nop,  IMP), op!(rra, ABYW), op!(nop,  ABX), op!(adc, ABX),  op!(rorm, ABXW), op!(rra,  ABXW),\n        /* 8 */ op!(nop, IMM), op!(sta, IDX),  op!(nop, IMM), op!(sax,  IDX),  op!(sty, ZP0), op!(sta, ZP0), op!(stx,  ZP0), op!(sax, ZP0), op!(dey, IMP), op!(nop, IMM),  op!(txa,  IMP), op!(xaa, IMM),  op!(sty,  ABS), op!(sta, ABS),  op!(stx,  ABS),  op!(sax,  ABS),\n        /* 9 */ op!(bcc, REL), op!(sta, IDYW), op!(hlt, IMP), op!(shaz, OTH),  op!(sty, ZPX), op!(sta, ZPX), op!(stx,  ZPY), op!(sax, ZPY), op!(tya, IMP), op!(sta, ABYW), op!(txs,  IMP), op!(tas, OTH),  op!(sya,  OTH), op!(sta, ABXW), op!(sxa,  OTH),  op!(shaa, OTH),\n        /* A */ op!(ldy, IMM), op!(lda, IDX),  op!(ldx, IMM), op!(lax,  IDX),  op!(ldy, ZP0), op!(lda, ZP0), op!(ldx,  ZP0), op!(lax, ZP0), op!(tay, IMP), op!(lda, IMM),  op!(tax,  IMP), op!(atx, IMM),  op!(ldy,  ABS), op!(lda, ABS),  op!(ldx,  ABS),  op!(lax,  ABS),\n        /* B */ op!(bcs, REL), op!(lda, IDY),  op!(hlt, IMP), op!(lax,  IDY),  op!(ldy, ZPX), op!(lda, ZPX), op!(ldx,  ZPY), op!(lax, ZPY), op!(clv, IMP), op!(lda, ABY),  op!(tsx,  IMP), op!(las, ABY),  op!(ldy,  ABX), op!(lda, ABX),  op!(ldx,  ABY),  op!(lax,  ABY),\n        /* C */ op!(cpy, IMM), op!(cpa, IDX),  op!(nop, IMM), op!(dcp,  IDX),  op!(cpy, ZP0), op!(cpa, ZP0), op!(dec,  ZP0), op!(dcp, ZP0), op!(iny, IMP), op!(cpa, IMM),  op!(dex,  IMP), op!(axs, IMM),  op!(cpy,  ABS), op!(cpa, ABS),  op!(dec,  ABS),  op!(dcp,  ABS),\n        /* D */ op!(bne, REL), op!(cpa, IDY),  op!(hlt, IMP), op!(dcp,  IDYW), op!(nop, ZPX), op!(cpa, ZPX), op!(dec,  ZPX), op!(dcp, ZPX), op!(cld, IMP), op!(cpa, ABY),  op!(nop,  IMP), op!(dcp, ABYW), op!(nop,  ABX), op!(cpa, ABX),  op!(dec,  ABXW), op!(dcp,  ABXW),\n        /* E */ op!(cpx, IMM), op!(sbc, IDX),  op!(nop, IMM), op!(isb,  IDX),  op!(cpx, ZP0), op!(sbc, ZP0), op!(inc,  ZP0), op!(isb, ZP0), op!(inx, IMP), op!(sbc, IMM),  op!(nop,  IMP), op!(sbc, IMM),  op!(cpx,  ABS), op!(sbc, ABS),  op!(inc,  ABS),  op!(isb,  ABS),\n        /* F */ op!(beq, REL), op!(sbc, IDY),  op!(hlt, IMP), op!(isb,  IDYW), op!(nop, ZPX), op!(sbc, ZPX), op!(inc,  ZPX), op!(isb, ZPX), op!(sed, IMP), op!(sbc, ABY),  op!(nop,  IMP), op!(isb, ABYW), op!(nop,  ABX), op!(sbc, ABX),  op!(inc,  ABXW), op!(isb,  ABXW),\n    ];\n\n    /// 16x16 grid of 6502 opcode instructions. Matches datasheet matrix for easy lookup\n    #[rustfmt::skip]\n    pub const INSTR_REF: [InstrRef; 256] = [\n        instr!(0x00, BRK, IMM, 7), instr!(0x01, ORA, IDX,  6), instr!(0x02, HLT, IMP, 2), instr!(0x03, SLO, IDX,  8), instr!(0x04, NOP, ZP0, 3), instr!(0x05, ORA, ZP0, 3), instr!(0x06, ASL, ZP0, 5), instr!(0x07, SLO, ZP0, 5), instr!(0x08, PHP, IMP, 3), instr!(0x09, ORA, IMM,  2), instr!(0x0A, ASL, ACC, 2), instr!(0x0B, ANC, IMM,  2), instr!(0x0C, NOP, ABS, 4), instr!(0x0D, ORA, ABS,  4), instr!(0x0E, ASL, ABS,  6), instr!(0x0F, SLO,  ABS,  6),\n        instr!(0x10, BPL, REL, 2), instr!(0x11, ORA, IDY,  5), instr!(0x12, HLT, IMP, 2), instr!(0x13, SLO, IDYW, 8), instr!(0x14, NOP, ZPX, 4), instr!(0x15, ORA, ZPX, 4), instr!(0x16, ASL, ZPX, 6), instr!(0x17, SLO, ZPX, 6), instr!(0x18, CLC, IMP, 2), instr!(0x19, ORA, ABY,  4), instr!(0x1A, NOP, IMP, 2), instr!(0x1B, SLO, ABYW, 7), instr!(0x1C, NOP, ABX, 4), instr!(0x1D, ORA, ABX,  4), instr!(0x1E, ASL, ABXW, 7), instr!(0x1F, SLO,  ABXW, 7),\n        instr!(0x20, JSR, OTH, 6), instr!(0x21, AND, IDX,  6), instr!(0x22, HLT, IMP, 2), instr!(0x23, RLA, IDX,  8), instr!(0x24, BIT, ZP0, 3), instr!(0x25, AND, ZP0, 3), instr!(0x26, ROL, ZP0, 5), instr!(0x27, RLA, ZP0, 5), instr!(0x28, PLP, IMP, 4), instr!(0x29, AND, IMM,  2), instr!(0x2A, ROL, ACC, 2), instr!(0x2B, ANC, IMM,  2), instr!(0x2C, BIT, ABS, 4), instr!(0x2D, AND, ABS,  4), instr!(0x2E, ROL, ABS,  6), instr!(0x2F, RLA,  ABS,  6),\n        instr!(0x30, BMI, REL, 2), instr!(0x31, AND, IDY,  5), instr!(0x32, HLT, IMP, 2), instr!(0x33, RLA, IDYW, 8), instr!(0x34, NOP, ZPX, 4), instr!(0x35, AND, ZPX, 4), instr!(0x36, ROL, ZPX, 6), instr!(0x37, RLA, ZPX, 6), instr!(0x38, SEC, IMP, 2), instr!(0x39, AND, ABY,  4), instr!(0x3A, NOP, IMP, 2), instr!(0x3B, RLA, ABYW, 7), instr!(0x3C, NOP, ABX, 4), instr!(0x3D, AND, ABX,  4), instr!(0x3E, ROL, ABXW, 7), instr!(0x3F, RLA,  ABXW, 7),\n        instr!(0x40, RTI, IMP, 6), instr!(0x41, EOR, IDX,  6), instr!(0x42, HLT, IMP, 2), instr!(0x43, SRE, IDX,  8), instr!(0x44, NOP, ZP0, 3), instr!(0x45, EOR, ZP0, 3), instr!(0x46, LSR, ZP0, 5), instr!(0x47, SRE, ZP0, 5), instr!(0x48, PHA, IMP, 3), instr!(0x49, EOR, IMM,  2), instr!(0x4A, LSR, ACC, 2), instr!(0x4B, ALR, IMM,  2), instr!(0x4C, JMP, ABS, 3), instr!(0x4D, EOR, ABS,  4), instr!(0x4E, LSR, ABS,  6), instr!(0x4F, SRE,  ABS,  6),\n        instr!(0x50, BVC, REL, 2), instr!(0x51, EOR, IDY,  5), instr!(0x52, HLT, IMP, 2), instr!(0x53, SRE, IDYW, 8), instr!(0x54, NOP, ZPX, 4), instr!(0x55, EOR, ZPX, 4), instr!(0x56, LSR, ZPX, 6), instr!(0x57, SRE, ZPX, 6), instr!(0x58, CLI, IMP, 2), instr!(0x59, EOR, ABY,  4), instr!(0x5A, NOP, IMP, 2), instr!(0x5B, SRE, ABYW, 7), instr!(0x5C, NOP, ABX, 4), instr!(0x5D, EOR, ABX,  4), instr!(0x5E, LSR, ABXW, 7), instr!(0x5F, SRE,  ABXW, 7),\n        instr!(0x60, RTS, IMP, 6), instr!(0x61, ADC, IDX,  6), instr!(0x62, HLT, IMP, 2), instr!(0x63, RRA, IDX,  8), instr!(0x64, NOP, ZP0, 3), instr!(0x65, ADC, ZP0, 3), instr!(0x66, ROR, ZP0, 5), instr!(0x67, RRA, ZP0, 5), instr!(0x68, PLA, IMP, 4), instr!(0x69, ADC, IMM,  2), instr!(0x6A, ROR, ACC, 2), instr!(0x6B, ARR, IMM,  2), instr!(0x6C, JMP, IND, 5), instr!(0x6D, ADC, ABS,  4), instr!(0x6E, ROR, ABS,  6), instr!(0x6F, RRA,  ABS,  6),\n        instr!(0x70, BVS, REL, 2), instr!(0x71, ADC, IDY,  5), instr!(0x72, HLT, IMP, 2), instr!(0x73, RRA, IDYW, 8), instr!(0x74, NOP, ZPX, 4), instr!(0x75, ADC, ZPX, 4), instr!(0x76, ROR, ZPX, 6), instr!(0x77, RRA, ZPX, 6), instr!(0x78, SEI, IMP, 2), instr!(0x79, ADC, ABY,  4), instr!(0x7A, NOP, IMP, 2), instr!(0x7B, RRA, ABYW, 7), instr!(0x7C, NOP, ABX, 4), instr!(0x7D, ADC, ABX,  4), instr!(0x7E, ROR, ABXW, 7), instr!(0x7F, RRA,  ABXW, 7),\n        instr!(0x80, NOP, IMM, 2), instr!(0x81, STA, IDX,  6), instr!(0x82, NOP, IMM, 2), instr!(0x83, SAX, IDX,  6), instr!(0x84, STY, ZP0, 3), instr!(0x85, STA, ZP0, 3), instr!(0x86, STX, ZP0, 3), instr!(0x87, SAX, ZP0, 3), instr!(0x88, DEY, IMP, 2), instr!(0x89, NOP, IMM,  2), instr!(0x8A, TXA, IMP, 2), instr!(0x8B, XAA, IMM,  2), instr!(0x8C, STY, ABS, 4), instr!(0x8D, STA, ABS,  4), instr!(0x8E, STX, ABS,  4), instr!(0x8F, SAX,  ABS , 4),\n        instr!(0x90, BCC, REL, 2), instr!(0x91, STA, IDYW, 6), instr!(0x92, HLT, IMP, 2), instr!(0x93, AHX, OTH,  6), instr!(0x94, STY, ZPX, 4), instr!(0x95, STA, ZPX, 4), instr!(0x96, STX, ZPY, 4), instr!(0x97, SAX, ZPY, 4), instr!(0x98, TYA, IMP, 2), instr!(0x99, STA, ABYW, 5), instr!(0x9A, TXS, IMP, 2), instr!(0x9B, TAS, OTH,  5), instr!(0x9C, SYA, OTH, 5), instr!(0x9D, STA, ABXW, 5), instr!(0x9E, SXA, OTH,  5), instr!(0x9F, SHAA, OTH,  5),\n        instr!(0xA0, LDY, IMM, 2), instr!(0xA1, LDA, IDX,  6), instr!(0xA2, LDX, IMM, 2), instr!(0xA3, LAX, IDX,  6), instr!(0xA4, LDY, ZP0, 3), instr!(0xA5, LDA, ZP0, 3), instr!(0xA6, LDX, ZP0, 3), instr!(0xA7, LAX, ZP0, 3), instr!(0xA8, TAY, IMP, 2), instr!(0xA9, LDA, IMM,  2), instr!(0xAA, TAX, IMP, 2), instr!(0xAB, ATX, IMM,  2), instr!(0xAC, LDY, ABS, 4), instr!(0xAD, LDA, ABS,  4), instr!(0xAE, LDX, ABS,  4), instr!(0xAF, LAX,  ABS,  4),\n        instr!(0xB0, BCS, REL, 2), instr!(0xB1, LDA, IDY,  5), instr!(0xB2, HLT, IMP, 2), instr!(0xB3, LAX, IDY,  5), instr!(0xB4, LDY, ZPX, 4), instr!(0xB5, LDA, ZPX, 4), instr!(0xB6, LDX, ZPY, 4), instr!(0xB7, LAX, ZPY, 4), instr!(0xB8, CLV, IMP, 2), instr!(0xB9, LDA, ABY,  4), instr!(0xBA, TSX, IMP, 2), instr!(0xBB, LAS, ABY,  4), instr!(0xBC, LDY, ABX, 4), instr!(0xBD, LDA, ABX,  4), instr!(0xBE, LDX, ABY,  4), instr!(0xBF, LAX,  ABY,  4),\n        instr!(0xC0, CPY, IMM, 2), instr!(0xC1, CMP, IDX,  6), instr!(0xC2, NOP, IMM, 2), instr!(0xC3, DCP, IDX,  8), instr!(0xC4, CPY, ZP0, 3), instr!(0xC5, CMP, ZP0, 3), instr!(0xC6, DEC, ZP0, 5), instr!(0xC7, DCP, ZP0, 5), instr!(0xC8, INY, IMP, 2), instr!(0xC9, CMP, IMM,  2), instr!(0xCA, DEX, IMP, 2), instr!(0xCB, AXS, IMM,  2), instr!(0xCC, CPY, ABS, 4), instr!(0xCD, CMP, ABS,  4), instr!(0xCE, DEC, ABS,  6), instr!(0xCF, DCP,  ABS,  6),\n        instr!(0xD0, BNE, REL, 2), instr!(0xD1, CMP, IDY,  5), instr!(0xD2, HLT, IMP, 2), instr!(0xD3, DCP, IDYW, 8), instr!(0xD4, NOP, ZPX, 4), instr!(0xD5, CMP, ZPX, 4), instr!(0xD6, DEC, ZPX, 6), instr!(0xD7, DCP, ZPX, 6), instr!(0xD8, CLD, IMP, 2), instr!(0xD9, CMP, ABY,  4), instr!(0xDA, NOP, IMP, 2), instr!(0xDB, DCP, ABYW, 7), instr!(0xDC, NOP, ABX, 4), instr!(0xDD, CMP, ABX,  4), instr!(0xDE, DEC, ABXW, 7), instr!(0xDF, DCP,  ABXW, 7),\n        instr!(0xE0, CPX, IMM, 2), instr!(0xE1, SBC, IDX,  6), instr!(0xE2, NOP, IMM, 2), instr!(0xE3, ISB, IDX,  8), instr!(0xE4, CPX, ZP0, 3), instr!(0xE5, SBC, ZP0, 3), instr!(0xE6, INC, ZP0, 5), instr!(0xE7, ISB, ZP0, 5), instr!(0xE8, INX, IMP, 2), instr!(0xE9, SBC, IMM,  2), instr!(0xEA, NOP, IMP, 2), instr!(0xEB, SBC, IMM,  2), instr!(0xEC, CPX, ABS, 4), instr!(0xED, SBC, ABS,  4), instr!(0xEE, INC, ABS,  6), instr!(0xEF, ISB,  ABS,  6),\n        instr!(0xF0, BEQ, REL, 2), instr!(0xF1, SBC, IDY,  5), instr!(0xF2, HLT, IMP, 2), instr!(0xF3, ISB, IDYW, 8), instr!(0xF4, NOP, ZPX, 4), instr!(0xF5, SBC, ZPX, 4), instr!(0xF6, INC, ZPX, 6), instr!(0xF7, ISB, ZPX, 6), instr!(0xF8, SED, IMP, 2), instr!(0xF9, SBC, ABY,  4), instr!(0xFA, NOP, IMP, 2), instr!(0xFB, ISB, ABYW, 7), instr!(0xFC, NOP, ABX, 4), instr!(0xFD, SBC, ABX,  4), instr!(0xFE, INC, ABXW, 7), instr!(0xFF, ISB,  ABXW, 7),\n    ];\n\n    /// Accumulator Addressing.\n    ///\n    /// No additional data is required, but the default target will be the accumulator.\n    ///\n    /// # Instructions\n    ///\n    /// ASL, ROL, LSR, ROR\n    ///\n    /// ```text\n    ///    #  address R/W description\n    ///   --- ------- --- -----------------------------------------------\n    ///    1    PC     R  fetch opcode, increment PC\n    ///    2    PC     R  read next instruction byte (and throw it away)\n    /// ```\n    ///\n    /// Implied Addressing.\n    ///\n    /// No additional data is required, but the default target will be the accumulator.\n    ///\n    /// ```text\n    ///    #  address R/W description\n    ///   --- ------- --- -----------------------------------------------\n    ///    1    PC     R  fetch opcode, increment PC\n    ///    2    PC     R  read next instruction byte (and throw it away)\n    /// ```\n    #[inline(always)]\n    pub fn acc_imp(&mut self) -> u16 {\n        self.read(self.pc); // Cycle 2, dummy read\n        0\n    }\n\n    /// Immediate Addressing.\n    ///\n    /// Uses the next byte as the value.\n    ///\n    /// ```text\n    ///    #  address R/W description\n    ///   --- ------- --- ------------------------------------------\n    ///    1    PC     R  fetch opcode, increment PC\n    ///    2    PC     R  fetch value, increment PC\n    /// ```\n    ///\n    /// Relative Addressing.\n    ///\n    /// This mode is only used by branching instructions. The address must be between -128 and +127,\n    /// allowing the branching instruction to move backward or forward relative to the current\n    /// program counter.\n    ///\n    /// # Notes\n    ///\n    /// The opcode fetch of the next instruction is included to this diagram for illustration\n    /// purposes. When determining real execution times, remember to subtract the last cycle.\n    ///\n    /// ```text\n    ///  #   address  R/W description\n    /// --- --------- --- ---------------------------------------------\n    ///  1     PC      R  fetch opcode, increment PC\n    ///  2     PC      R  fetch fetched_data, increment PC\n    ///  3     PC      R  Fetch opcode of next instruction,\n    ///                   If branch is taken, add fetched_data to PCL.\n    ///                   Otherwise increment PC.\n    ///  4+    PC*     R  Fetch opcode of next instruction.\n    ///                   Fix PCH. If it did not change, increment PC.\n    ///  5!    PC      R  Fetch opcode of next instruction,\n    ///                   increment PC.\n    ///\n    ///     * The high byte of Program Counter (PCH) may be invalid\n    ///       at this time, i.e. it may be smaller or bigger by $100.\n    ///     + If branch is taken, this cycle will be executed.\n    ///     ! If branch occurs to different page, this cycle will be\n    ///       executed.\n    /// ```\n    ///\n    /// Zero Page Addressing.\n    ///\n    /// Accesses the first 0xFF bytes of the address range, so this only requires one extra byte\n    /// instead of the usual two.\n    ///\n    /// # Read instructions\n    ///\n    /// LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT, LAX, NOP\n    ///\n    /// ```text\n    ///    #  address R/W description\n    ///   --- ------- --- ------------------------------------------\n    ///    1    PC     R  fetch opcode, increment PC\n    ///    2    PC     R  fetch address, increment PC\n    ///    3  address  R  read from effective address\n    /// ```\n    ///\n    /// # Read-Modify-Write instructions\n    ///\n    /// ASL, LSR, ROL, ROR, INC, DEC, SLO, SRE, RLA, RRA, ISB, DCP\n    ///\n    /// ```text\n    ///    #  address R/W description\n    ///   --- ------- --- ------------------------------------------\n    ///    1    PC     R  fetch opcode, increment PC\n    ///    2    PC     R  fetch address, increment PC\n    ///    3  address  R  read from effective address\n    ///    4  address  W  write the value back to effective address,\n    ///                   and do the operation on it\n    ///    5  address  W  write the new value to effective address\n    /// ```\n    ///\n    /// # Write instructions\n    ///\n    /// STA, STX, STY, SAX\n    ///\n    /// ```text\n    ///    #  address R/W description\n    ///   --- ------- --- ------------------------------------------\n    ///    1    PC     R  fetch opcode, increment PC\n    ///    2    PC     R  fetch address, increment PC\n    ///    3  address  W  write register to effective address\n    /// ```\n    #[inline(always)]\n    pub fn imm_rel_zp(&mut self) -> u16 {\n        u16::from(self.fetch_byte()) // Cycle 2\n    }\n\n    /// Zero Page Addressing w/ X offset.\n    ///\n    /// Same as Zero Page, but is offset by adding the x register.\n    ///\n    /// # Read instructions\n    ///\n    /// LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT, LAX, NOP\n    ///\n    /// ```text\n    ///  #   address  R/W description\n    /// --- --------- --- ------------------------------------------\n    ///  1     PC      R  fetch opcode, increment PC\n    ///  2     PC      R  fetch address, increment PC\n    ///  3   address   R  read from address, add index register to it\n    ///  4  address+X* R  read from effective address\n    ///\n    ///     * The high byte of the effective address is always zero,\n    ///       i.e. page boundary crossings are not handled.\n    /// ```\n    ///\n    /// # Read-Modify-Write instructions\n    ///\n    /// ASL, LSR, ROL, ROR, INC, DEC, SLO, SRE, RLA, RRA, ISB, DCP\n    ///\n    /// ```text\n    ///  #   address  R/W description\n    /// --- --------- --- ---------------------------------------------\n    ///  1     PC      R  fetch opcode, increment PC\n    ///  2     PC      R  fetch address, increment PC\n    ///  3   address   R  read from address, add index register X to it\n    ///  4  address+X* R  read from effective address\n    ///  5  address+X* W  write the value back to effective address,\n    ///                   and do the operation on it\n    ///  6  address+X* W  write the new value to effective address\n    ///\n    ///     * The high byte of the effective address is always zero,\n    ///       i.e. page boundary crossings are not handled.\n    /// ```\n    ///\n    /// # Write instructions\n    ///\n    /// STA, STX, STY, SAX\n    ///\n    /// ```text\n    ///  #   address  R/W description\n    /// --- --------- --- -------------------------------------------\n    ///  1     PC      R  fetch opcode, increment PC\n    ///  2     PC      R  fetch address, increment PC\n    ///  3   address   R  read from address, add index register to it\n    ///  4  address+X* W  write to effective address\n    ///\n    ///     * The high byte of the effective address is always zero,\n    ///       i.e. page boundary crossings are not handled.\n    /// ```\n    #[inline(always)]\n    pub fn zpx(&mut self) -> u16 {\n        let addr = u16::from(self.fetch_byte()); // Cycle 2\n        self.read(addr); // Cycle 3, dummy read\n        // High byte is always zero\n        addr.wrapping_add(u16::from(self.x)) & 0x00FF\n    }\n\n    /// Zero Page Addressing w/ Y offset.\n    ///\n    /// Same as Zero Page, but is offset by adding the y register.\n    ///\n    /// # Read instructions\n    ///\n    /// LDX, LAX\n    ///\n    /// ```text\n    ///  #   address  R/W description\n    /// --- --------- --- ------------------------------------------\n    ///  1     PC      R  fetch opcode, increment PC\n    ///  2     PC      R  fetch address, increment PC\n    ///  3   address   R  read from address, add index register to it\n    ///  4  address+Y* R  read from effective address\n    ///\n    ///     * The high byte of the effective address is always zero,\n    ///       i.e. page boundary crossings are not handled.\n    /// ```\n    ///\n    /// # Write instructions\n    ///\n    /// STX, SAX\n    ///\n    /// ```text\n    ///  #   address  R/W description\n    /// --- --------- --- -------------------------------------------\n    ///  1     PC      R  fetch opcode, increment PC\n    ///  2     PC      R  fetch address, increment PC\n    ///  3   address   R  read from address, add index register to it\n    ///  4  address+Y* W  write to effective address\n    ///\n    ///     * The high byte of the effective address is always zero,\n    ///       i.e. page boundary crossings are not handled.\n    /// ```\n    #[inline(always)]\n    pub fn zpy(&mut self) -> u16 {\n        let addr = u16::from(self.fetch_byte()); // Cycle 2\n        self.read(addr); // Cycle 3, dummy read\n        // High byte is always zero\n        addr.wrapping_add(u16::from(self.y)) & 0x00FF\n    }\n\n    /// Absolute Addressing.\n    ///\n    /// Uses a full 16-bit address as the next value.\n    ///\n    /// # Read instructions\n    ///\n    /// LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT, LAX, NOP\n    ///\n    /// ```text\n    ///  #  address R/W description\n    /// --- ------- --- ------------------------------------------\n    ///  1    PC     R  fetch opcode, increment PC\n    ///  2    PC     R  fetch low byte of address, increment PC\n    ///  3    PC     R  fetch high byte of address, increment PC\n    ///  4  address  R  read from effective address\n    /// ```\n    ///\n    /// # Read-Modify-Write instructions\n    ///\n    /// ASL, LSR, ROL, ROR, INC, DEC, SLO, SRE, RLA, RRA, ISB, DCP\n    ///\n    /// ```text\n    ///  #  address R/W description\n    /// --- ------- --- ------------------------------------------\n    ///  1    PC     R  fetch opcode, increment PC\n    ///  2    PC     R  fetch low byte of address, increment PC\n    ///  3    PC     R  fetch high byte of address, increment PC\n    ///  4  address  R  read from effective address\n    ///  5  address  W  write the value back to effective address,\n    ///                 and do the operation on it\n    ///  6  address  W  write the new value to effective address\n    /// ```\n    ///\n    /// # Write instructions\n    ///\n    /// STA, STX, STY, SAX\n    ///\n    /// ```text\n    ///  #  address R/W description\n    /// --- ------- --- ------------------------------------------\n    ///  1    PC     R  fetch opcode, increment PC\n    ///  2    PC     R  fetch low byte of address, increment PC\n    ///  3    PC     R  fetch high byte of address, increment PC\n    ///  4  address  W  write register to effective address\n    /// ```\n    #[inline(always)]\n    pub fn abs(&mut self) -> u16 {\n        self.fetch_word() // Cycles 2-3\n    }\n\n    /// Absolute Address w/ X offset.\n    ///\n    /// Same as Absolute, but is offset by adding the x register. If a page boundary is crossed, an\n    /// additional clock is required.\n    ///\n    /// # Read instructions\n    ///\n    /// LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT, LAX, LAE, SHS, NOP\n    ///\n    /// ```text\n    ///  #   address  R/W description\n    /// --- --------- --- ------------------------------------------\n    ///  1     PC      R  fetch opcode, increment PC\n    ///  2     PC      R  fetch low byte of address, increment PC\n    ///  3     PC      R  fetch high byte of address,\n    ///                   add index register to low address byte,\n    ///                   increment PC\n    ///  4  address+X* R  read from effective address,\n    ///                   fix the high byte of effective address\n    ///  5+ address+X  R  re-read from effective address\n    ///\n    ///     * The high byte of the effective address may be invalid\n    ///       at this time, i.e. it may be smaller by $100.\n    ///     + This cycle will be executed only if the effective address\n    ///       was invalid during cycle #4, i.e. page boundary was crossed.\n    /// ```\n    ///\n    /// # Read-Modify-Write instructions\n    ///\n    /// ASL, LSR, ROL, ROR, INC, DEC, SLO, SRE, RLA, RRA, ISB, DCP\n    ///\n    /// ```text\n    /// #   address  R/W description\n    /// -- --------- --- ------------------------------------------\n    /// 1    PC       R  fetch opcode, increment PC\n    /// 2    PC       R  fetch low byte of address, increment PC\n    /// 3    PC       R  fetch high byte of address,\n    ///                  add index register X to low address byte,\n    ///                  increment PC\n    /// 4  address+X* R  read from effective address,\n    ///                  fix the high byte of effective address\n    /// 5  address+X  R  re-read from effective address\n    /// 6  address+X  W  write the value back to effective address,\n    ///                  and do the operation on it\n    /// 7  address+X  W  write the new value to effective address\n    ///\n    ///     * The high byte of the effective address may be invalid\n    ///       at this time, i.e. it may be smaller by $100.\n    /// ```\n    ///\n    /// # Write instructions\n    ///\n    /// STA, STX, STY, SHA, SHX, SHY\n    ///\n    /// ```text\n    /// #   address  R/W description\n    /// -- --------- --- ------------------------------------------\n    /// 1     PC      R  fetch opcode, increment PC\n    /// 2     PC      R  fetch low byte of address, increment PC\n    /// 3     PC      R  fetch high byte of address,\n    ///                  add index register to low address byte,\n    ///                  increment PC\n    /// 4  address+X* R  read from effective address,\n    ///                  fix the high byte of effective address\n    /// 5  address+X  W  write to effective address\n    ///\n    ///     * The high byte of the effective address may be invalid\n    ///       at this time, i.e. it may be smaller by $100. Because\n    ///       the processor cannot undo a write to an invalid\n    ///       address, it always reads from the address first.\n    /// ```\n    #[inline(always)]\n    pub fn abx(&mut self, dummy_read: bool) -> u16 {\n        let base_addr = self.fetch_word(); // Cycles 2-3\n        let addr = base_addr.wrapping_add(u16::from(self.x));\n        if Cpu::pages_differ(base_addr, addr) || dummy_read {\n            // Cycle 4 dummy read with fixed high byte\n            self.read((base_addr & 0xFF00) | (addr & 0x00FF));\n        }\n        addr\n    }\n\n    /// Absolute Address w/ Y offset.\n    ///\n    /// Same as Absolute, but is offset by adding the y register. If a page boundary is crossed, an\n    /// additional clock is required.\n    ///\n    /// # Read instructions\n    ///\n    /// LDA, LDX, LDY, EOR, AND, ORA, ADC, SBC, CMP, BIT, LAX, LAE, SHS, NOP\n    ///\n    /// ```text\n    ///  #   address  R/W description\n    /// --- --------- --- ------------------------------------------\n    ///  1     PC      R  fetch opcode, increment PC\n    ///  2     PC      R  fetch low byte of address, increment PC\n    ///  3     PC      R  fetch high byte of address,\n    ///                   add index register to low address byte,\n    ///                   increment PC\n    ///  4  address+Y* R  read from effective address,\n    ///                   fix the high byte of effective address\n    ///  5+ address+Y  R  re-read from effective address\n    ///\n    ///     * The high byte of the effective address may be invalid\n    ///       at this time, i.e. it may be smaller by $100.\n    ///     + This cycle will be executed only if the effective address\n    ///       was invalid during cycle #4, i.e. page boundary was crossed.\n    /// ```\n    ///\n    /// # Read-Modify-Write instructions\n    ///\n    /// ASL, LSR, ROL, ROR, INC, DEC, SLO, SRE, RLA, RRA, ISB, DCP\n    ///\n    /// ```text\n    ///  #   address  R/W description\n    /// --- --------- --- ------------------------------------------\n    ///  1    PC       R  fetch opcode, increment PC\n    ///  2    PC       R  fetch low byte of address, increment PC\n    ///  3    PC       R  fetch high byte of address,\n    ///                   add index register Y to low address byte,\n    ///                   increment PC\n    ///  4  address+Y* R  read from effective address,\n    ///                   fix the high byte of effective address\n    ///  5  address+Y  R  re-read from effective address\n    ///  6  address+Y  W  write the value back to effective address,\n    ///                   and do the operation on it\n    ///  7  address+Y  W  write the new value to effective address\n    ///\n    ///     * The high byte of the effective address may be invalid\n    ///       at this time, i.e. it may be smaller by $100.\n    /// ```\n    ///\n    /// # Write instructions\n    ///\n    /// STA, STX, STY, SHA, SHX, SHY\n    ///\n    /// ```text\n    ///  #   address  R/W description\n    /// --- --------- --- ------------------------------------------\n    ///  1     PC      R  fetch opcode, increment PC\n    ///  2     PC      R  fetch low byte of address, increment PC\n    ///  3     PC      R  fetch high byte of address,\n    ///                   add index register to low address byte,\n    ///                   increment PC\n    ///  4  address+Y* R  read from effective address,\n    ///                   fix the high byte of effective address\n    ///  5  address+Y  W  write to effective address\n    ///\n    ///     * The high byte of the effective address may be invalid\n    ///       at this time, i.e. it may be smaller by $100. Because\n    ///       the processor cannot undo a write to an invalid\n    ///       address, it always reads from the address first.\n    /// ```\n    #[inline(always)]\n    pub fn aby(&mut self, dummy_read: bool) -> u16 {\n        let base_addr = self.fetch_word(); // Cycles 2 & 3\n        let addr = base_addr.wrapping_add(u16::from(self.y));\n        if Cpu::pages_differ(base_addr, addr) || dummy_read {\n            // Cycle 4 dummy read with fixed high byte\n            self.read((base_addr & 0xFF00) | (addr & 0x00FF));\n        }\n        addr\n    }\n\n    /// Indirect Addressing.\n    ///\n    /// The next 16-bit address is used to get the actual 16-bit address. This instruction has\n    /// a bug in the original hardware. If the lo byte is 0xFF, the hi byte would cross a page\n    /// boundary. However, this doesn't work correctly on the original hardware and instead\n    /// wraps back around to 0.\n    ///\n    /// # Instructions\n    ///\n    /// JMP\n    ///\n    /// ```text\n    ///  #   address  R/W description\n    /// --- --------- --- ------------------------------------------\n    ///  1     PC      R  fetch opcode, increment PC\n    ///  2     PC      R  fetch pointer address low, increment PC\n    ///  3     PC      R  fetch pointer address high, increment PC\n    ///  4   pointer   R  fetch low address to latch\n    ///  5  pointer+1* R  fetch PCH, copy latch to PCL\n    ///\n    ///     * The PCH will always be fetched from the same page\n    ///       than PCL, i.e. page boundary crossing is not handled.\n    /// ```\n    #[inline(always)]\n    pub fn ind(&mut self) -> u16 {\n        self.fetch_word()\n    }\n\n    /// Indirect X Addressing.\n    ///\n    /// The next 8-bit address is offset by the X register to get the actual 16-bit address from\n    /// page 0x00.\n    ///\n    /// # Read instructions\n    ///\n    /// LDA, ORA, EOR, AND, ADC, CMP, SBC, LAX\n    ///\n    /// ```text\n    ///  #    address   R/W description\n    /// --- ----------- --- ------------------------------------------\n    ///  1      PC       R  fetch opcode, increment PC\n    ///  2      PC       R  fetch pointer address, increment PC\n    ///  3    pointer    R  read from the address, add X to it\n    ///  4   pointer+X*  R  fetch effective address low\n    ///  5  pointer+X+1* R  fetch effective address high\n    ///  6    address    R  read from effective address\n    ///\n    ///     * The effective address is always fetched from zero page,\n    ///       i.e. the zero page boundary crossing is not handled.\n    /// ```\n    ///\n    /// # Read-Modify-Write instructions\n    ///\n    /// SLO, SRE, RLA, RRA, ISB, DCP\n    ///\n    /// ```text\n    ///  #    address   R/W description\n    /// --- ----------- --- ------------------------------------------\n    ///  1      PC       R  fetch opcode, increment PC\n    ///  2      PC       R  fetch pointer address, increment PC\n    ///  3    pointer    R  read from the address, add X to it\n    ///  4   pointer+X*  R  fetch effective address low\n    ///  5  pointer+X+1* R  fetch effective address high\n    ///  6    address    R  read from effective address\n    ///  7    address    W  write the value back to effective address,\n    ///                     and do the operation on it\n    ///  8    address    W  write the new value to effective address\n    ///\n    ///     * The effective address is always fetched from zero page,\n    ///       i.e. the zero page boundary crossing is not handled.\n    /// ```\n    ///\n    /// # Write instructions\n    ///\n    /// STA, SAX\n    ///\n    /// ```text\n    ///  #    address   R/W description\n    /// --- ----------- --- ------------------------------------------\n    ///  1      PC       R  fetch opcode, increment PC\n    ///  2      PC       R  fetch pointer address, increment PC\n    ///  3    pointer    R  read from the address, add X to it\n    ///  4   pointer+X*  R  fetch effective address low\n    ///  5  pointer+X+1* R  fetch effective address high\n    ///  6    address    W  write to effective address\n    ///\n    ///     * The effective address is always fetched from zero page,\n    ///       i.e. the zero page boundary crossing is not handled.\n    /// ```\n    #[inline(always)]\n    pub fn idx(&mut self) -> u16 {\n        let mut zero_addr = self.fetch_byte(); // Cycle 2\n        self.read(u16::from(zero_addr)); // Cycle 3 dummy read\n        zero_addr = zero_addr.wrapping_add(self.x);\n        let lo = self.read(u16::from(zero_addr)); // Cycle 4\n        let hi = self.read(u16::from(zero_addr.wrapping_add(1))); // Cycle 5\n        u16::from_le_bytes([lo, hi])\n    }\n\n    /// Indirect Y Addressing.\n    ///\n    /// The next 8-bit address is read to get a 16-bit address from page 0x00, which is then offset\n    /// by the Y register. If a page boundary is crossed, add a clock cycle.\n    ///\n    /// # Read instructions\n    ///\n    /// LDA, EOR, AND, ORA, ADC, SBC, CMP\n    ///\n    /// ```text\n    ///  #    address   R/W description\n    /// --- ----------- --- ------------------------------------------\n    ///  1      PC       R  fetch opcode, increment PC\n    ///  2      PC       R  fetch pointer address, increment PC\n    ///  3    pointer    R  fetch effective address low\n    ///  4   pointer+1*  R  fetch effective address high,\n    ///                     add Y to low byte of effective address\n    ///  5   address+Y+  R  read from effective address,\n    ///                     fix high byte of effective address\n    ///  6!  address+Y   R  read from effective address\n    ///\n    ///     * The effective address is always fetched from zero page,\n    ///       i.e. the zero page boundary crossing is not handled.\n    ///     + The high byte of the effective address may be invalid\n    ///       at this time, i.e. it may be smaller by $100.\n    ///     ! This cycle will be executed only if the effective address\n    ///       was invalid during cycle #5, i.e. page boundary was crossed.\n    /// ```\n    ///\n    /// # Read-Modify-Write instructions\n    ///\n    /// SLO, SRE, RLA, RRA, ISB, DCP\n    ///\n    /// ```text\n    ///  #    address   R/W description\n    /// --- ----------- --- ------------------------------------------\n    ///  1      PC       R  fetch opcode, increment PC\n    ///  2      PC       R  fetch pointer address, increment PC\n    ///  3    pointer    R  fetch effective address low\n    ///  4   pointer+1*  R  fetch effective address high,\n    ///                     add Y to low byte of effective address\n    ///  5   address+Y+  R  read from effective address,\n    ///                     fix high byte of effective address\n    ///  6   address+Y   R  re-read from effective address\n    ///  7   address+Y   W  write the value back to effective address,\n    ///                     and do the operation on it\n    ///  8   address+Y   W  write the new value to effective address\n    ///\n    ///     * The effective address is always fetched from zero page,\n    ///       i.e. the zero page boundary crossing is not handled.\n    ///     + The high byte of the effective address may be invalid\n    ///       at this time, i.e. it may be smaller by $100.\n    /// ```\n    ///\n    /// # Write instructions\n    ///\n    /// STA, SHA\n    ///\n    /// ```text\n    ///  #    address   R/W description\n    /// --- ----------- --- ------------------------------------------\n    ///  1      PC       R  fetch opcode, increment PC\n    ///  2      PC       R  fetch pointer address, increment PC\n    ///  3    pointer    R  fetch effective address low\n    ///  4   pointer+1*  R  fetch effective address high,\n    ///                     add Y to low byte of effective address\n    ///  5   address+Y+  R  read from effective address,\n    ///                     fix high byte of effective address\n    ///  6   address+Y   W  write to effective address\n    ///\n    ///     * The effective address is always fetched from zero page,\n    ///       i.e. the zero page boundary crossing is not handled.\n    ///     + The high byte of the effective address may be invalid\n    ///       at this time, i.e. it may be smaller by $100.\n    /// ```\n    #[inline(always)]\n    pub fn idy(&mut self, dummy_read: bool) -> u16 {\n        let zero_addr = self.fetch_byte(); // Cycle 2\n        let base_addr = {\n            let lo = self.read(u16::from(zero_addr)); // Cycle 4\n            let hi = self.read(u16::from(zero_addr.wrapping_add(1))); // Cycle 5\n            u16::from_le_bytes([lo, hi])\n        };\n\n        let addr = base_addr.wrapping_add(u16::from(self.y));\n        if Cpu::pages_differ(base_addr, addr) || dummy_read {\n            // Cycle 5 dummy read with fixed high byte\n            self.read((base_addr & 0xFF00) | (addr & 0x00FF));\n        }\n        addr\n    }\n}\n\n/// CPU instructions\nimpl Cpu {\n    // Storage opcodes\n\n    /// LDA: Load A with M\n    #[inline(always)]\n    pub fn lda(&mut self) {\n        let val = self.read_operand();\n        self.set_acc(val);\n    }\n    /// LDX: Load X with M\n    #[inline(always)]\n    pub fn ldx(&mut self) {\n        let val = self.read_operand();\n        self.set_x(val);\n    }\n    /// LDY: Load Y with M\n    #[inline(always)]\n    pub fn ldy(&mut self) {\n        let val = self.read_operand();\n        self.set_y(val);\n    }\n\n    /// STA: Store A into M\n    #[inline(always)]\n    pub fn sta(&mut self) {\n        self.write(self.operand, self.acc);\n    }\n    /// STX: Store X into M\n    #[inline(always)]\n    pub fn stx(&mut self) {\n        self.write(self.operand, self.x);\n    }\n    /// STY: Store Y into M\n    #[inline(always)]\n    pub fn sty(&mut self) {\n        self.write(self.operand, self.y);\n    }\n\n    /// TAX: Transfer A to X\n    #[inline(always)]\n    pub fn tax(&mut self) {\n        self.set_x(self.acc);\n    }\n    /// TAY: Transfer A to Y\n    #[inline(always)]\n    pub fn tay(&mut self) {\n        self.set_y(self.acc);\n    }\n    /// TSX: Transfer Stack Pointer to X\n    #[inline(always)]\n    pub fn tsx(&mut self) {\n        self.set_x(self.sp);\n    }\n    /// TXA: Transfer X to A\n    #[inline(always)]\n    pub fn txa(&mut self) {\n        self.set_acc(self.x);\n    }\n    /// TXS: Transfer X to Stack Pointer\n    #[inline(always)]\n    pub const fn txs(&mut self) {\n        self.set_sp(self.x);\n    }\n    /// TYA: Transfer Y to A\n    #[inline(always)]\n    pub fn tya(&mut self) {\n        self.set_acc(self.y);\n    }\n\n    // Arithmetic opcodes\n\n    /// ADC: Add M to A with Carry\n    #[inline(always)]\n    pub fn adc(&mut self) {\n        let val = self.read_operand();\n        self.add(val);\n    }\n    /// SBC: Subtract M from A with Carry\n    #[inline(always)]\n    pub fn sbc(&mut self) {\n        let val = self.read_operand();\n        self.add(val ^ 0xFF);\n    }\n    /// Utility function used by all add instructions\n    #[inline(always)]\n    fn add(&mut self, val: u8) {\n        let a = u16::from(self.acc);\n        let val = u16::from(val);\n        let carry = u16::from(self.status_bit(Status::C));\n        let res = a + val + carry;\n        self.set_zn_status(res as u8);\n        self.status\n            .set(Status::V, (a ^ val) & 0x80 == 0 && (a ^ res) & 0x80 != 0);\n        self.status.set(Status::C, res > 0xFF);\n        self.set_acc(res as u8);\n    }\n\n    /// INC: Increment M by One\n    #[inline(always)]\n    pub fn inc(&mut self) {\n        let addr = self.operand;\n        let val = self.read(addr);\n        self.write(addr, val); // Dummy write\n        let res = val.wrapping_add(1);\n        self.write(addr, res);\n        self.set_zn_status(res);\n    }\n    /// DEC: Decrement M by One\n    #[inline(always)]\n    pub fn dec(&mut self) {\n        let addr = self.operand;\n        let val = self.read(addr);\n        self.write(addr, val); // Dummy write\n        let res = val.wrapping_sub(1);\n        self.write(addr, res);\n        self.set_zn_status(res);\n    }\n\n    /// INX: Increment X by One\n    #[inline(always)]\n    pub fn inx(&mut self) {\n        self.set_x(self.x.wrapping_add(1));\n    }\n    /// INY: Increment Y by One\n    #[inline(always)]\n    pub fn iny(&mut self) {\n        self.set_y(self.y.wrapping_add(1));\n    }\n\n    /// DEX: Decrement X by One\n    #[inline(always)]\n    pub fn dex(&mut self) {\n        self.set_x(self.x.wrapping_sub(1));\n    }\n\n    /// DEY: Decrement Y by One\n    #[inline(always)]\n    pub fn dey(&mut self) {\n        self.set_y(self.y.wrapping_sub(1));\n    }\n\n    // Bitwise opcodes\n\n    /// AND: \"And\" M with A\n    #[inline(always)]\n    pub fn and(&mut self) {\n        let val = self.read_operand();\n        self.set_acc(self.acc & val);\n    }\n    /// EOR: \"Exclusive-Or\" M with A\n    #[inline(always)]\n    pub fn eor(&mut self) {\n        let val = self.read_operand();\n        self.set_acc(self.acc ^ val);\n    }\n    /// ORA: \"OR\" M with A\n    #[inline(always)]\n    pub fn ora(&mut self) {\n        let val = self.read_operand();\n        self.set_acc(self.acc | val);\n    }\n\n    /// ASL: Shift Left One Bit (A)\n    #[inline(always)]\n    fn asla(&mut self) {\n        let val = self.asl(self.acc);\n        self.set_acc(val);\n    }\n    /// ASL: Shift Left One Bit (M)\n    #[inline(always)]\n    fn aslm(&mut self) {\n        let addr = self.operand;\n        let val = self.read(addr);\n        self.write(addr, val); // Dummy write\n        let res = self.asl(val);\n        self.write(addr, res);\n    }\n    /// Utility function used by all ASL instructions\n    #[inline(always)]\n    fn asl(&mut self, val: u8) -> u8 {\n        self.status.set(Status::C, (val & 0x80) > 0);\n        let res = val.wrapping_shl(1);\n        self.set_zn_status(res);\n        res\n    }\n\n    /// LSR: Shift Right One Bit (A)\n    #[inline(always)]\n    pub fn lsra(&mut self) {\n        let res = self.lsr(self.acc);\n        self.set_acc(res);\n    }\n    /// LSR: Shift Right One Bit (M)\n    #[inline(always)]\n    pub fn lsrm(&mut self) {\n        let addr = self.operand;\n        let val = self.read(addr);\n        self.write(addr, val); // Dummy write\n        let res = self.lsr(val);\n        self.write(addr, res);\n    }\n    /// Utility function used by all LSR instructions\n    #[inline(always)]\n    fn lsr(&mut self, val: u8) -> u8 {\n        self.status.set(Status::C, (val & 1) > 0);\n        let res = val.wrapping_shr(1);\n        self.set_zn_status(res);\n        res\n    }\n\n    /// ROL: Rotate One Bit Left (A)\n    #[inline(always)]\n    pub fn rola(&mut self) {\n        let val = self.rol(self.acc);\n        self.set_acc(val);\n    }\n    /// ROL: Rotate One Bit Left (M)\n    #[inline(always)]\n    pub fn rolm(&mut self) {\n        let addr = self.operand;\n        let val = self.read(addr);\n        self.write(addr, val); // Dummy write\n        let val = self.rol(val);\n        self.write(addr, val);\n    }\n    /// Utility function used by all ROL instructions\n    #[inline(always)]\n    pub fn rol(&mut self, val: u8) -> u8 {\n        let carry = self.status_bit(Status::C);\n        self.status.set(Status::C, (val & 0x80) > 0);\n        let res = (val << 1) | carry;\n        self.set_zn_status(res);\n        res\n    }\n\n    /// ROR: Rotate One Bit Right (A)\n    #[inline(always)]\n    pub fn rora(&mut self) {\n        let val = self.ror(self.acc);\n        self.set_acc(val);\n    }\n    /// ROR: Rotate One Bit Right (M)\n    #[inline(always)]\n    pub fn rorm(&mut self) {\n        let addr = self.operand;\n        let val = self.read(addr);\n        self.write(addr, val); // Dummy write\n        let val = self.ror(val);\n        self.write(addr, val);\n    }\n    /// Utility function used by all ROR instructions\n    #[inline(always)]\n    fn ror(&mut self, val: u8) -> u8 {\n        let carry = self.status_bit(Status::C);\n        self.status.set(Status::C, (val & 1) > 0);\n        let res = (val >> 1) | (carry << 7);\n        self.set_zn_status(res);\n        res\n    }\n\n    /// BIT: Test Bits in M with A\n    #[inline(always)]\n    pub fn bit(&mut self) {\n        let val = self.read_operand();\n        self.status.set(Status::Z, (self.acc & val) == 0);\n        self.status.set(Status::N, (val & 0x80) > 0);\n        self.status.set(Status::V, (val & 0x40) > 0);\n    }\n\n    // Branch opcodes\n\n    /// BCC: Branch on Carry Clear\n    #[inline(always)]\n    pub fn bcc(&mut self) {\n        self.branch(!self.status.contains(Status::C));\n    }\n    /// BCS: Branch on Carry Set\n    #[inline(always)]\n    pub fn bcs(&mut self) {\n        self.branch(self.status.contains(Status::C));\n    }\n    /// BEQ: Branch on Result Zero\n    #[inline(always)]\n    pub fn beq(&mut self) {\n        self.branch(self.status.contains(Status::Z));\n    }\n    /// BMI: Branch on Result Negative\n    #[inline(always)]\n    pub fn bmi(&mut self) {\n        self.branch(self.status.contains(Status::N));\n    }\n    /// BNE: Branch on Result Not Zero\n    #[inline(always)]\n    pub fn bne(&mut self) {\n        self.branch(!self.status.contains(Status::Z));\n    }\n    /// BPL: Branch on Result Positive\n    #[inline(always)]\n    pub fn bpl(&mut self) {\n        self.branch(!self.status.contains(Status::N));\n    }\n    /// BVC: Branch on Overflow Clear\n    #[inline(always)]\n    pub fn bvc(&mut self) {\n        self.branch(!self.status.contains(Status::V));\n    }\n    /// BVS: Branch on Overflow Set\n    #[inline(always)]\n    pub fn bvs(&mut self) {\n        self.branch(self.status.contains(Status::V));\n    }\n    /// Utility function used by all branch instructions.\n    #[inline(always)]\n    fn branch(&mut self, branch: bool) {\n        if !branch {\n            return;\n        }\n        // If an interrupt occurs during the final cycle of a non-pagecrossing branch\n        // then it will be ignored until the next instruction completes\n        let run_irq = self.irq_flags.contains(IrqFlags::RUN_IRQ);\n        let prev_run_irq = self.irq_flags.contains(IrqFlags::PREV_RUN_IRQ);\n        if run_irq && !prev_run_irq {\n            self.irq_flags.remove(IrqFlags::RUN_IRQ);\n        }\n        self.read(self.pc); // Dummy read\n\n        let offset = i16::from(self.operand as i8);\n        if Self::page_crossed(self.pc, offset) {\n            self.read(self.pc); // Dummy read\n        }\n        self.pc = (self.pc as i16).wrapping_add(offset) as u16;\n    }\n\n    // Jump opcodes\n\n    /// JMP: Jump to Location (absolute)\n    ///\n    /// ```text\n    ///  #  address R/W description\n    /// --- ------- --- -------------------------------------------------\n    ///  1    PC     R  fetch opcode, increment PC\n    ///  2    PC     R  fetch low address byte, increment PC\n    ///  3    PC     R  copy low address byte to PCL, copy high address\n    ///                   byte to PCH\n    /// ```\n    #[inline(always)]\n    pub const fn jmpa(&mut self) {\n        self.pc = self.operand;\n    }\n    /// JMP: Jump to Location (indirect)\n    /// ```text\n    ///  #   address  R/W description\n    /// --- --------- --- ------------------------------------------\n    ///  1     PC      R  fetch opcode, increment PC\n    ///  2     PC      R  fetch pointer address low, increment PC\n    ///  3     PC      R  fetch pointer address high, increment PC\n    ///  4   pointer   R  fetch low address to latch\n    ///  5  pointer+1* R  fetch PCH, copy latch to PCL\n    ///\n    ///     * The PCH will always be fetched from the same page\n    ///       than PCL, i.e. page boundary crossing is not handled.\n    /// ```\n    #[inline(always)]\n    pub fn jmpi(&mut self) {\n        let addr = self.operand;\n        self.pc = if (addr & 0xFF) == 0xFF {\n            let lo = self.read(addr);\n            let hi = self.read(addr - 0xFF);\n            u16::from_le_bytes([lo, hi])\n        } else {\n            self.read_word(addr)\n        };\n    }\n    /// JSR: Jump to Location Save Return addr\n    ///\n    /// ```text\n    ///  #  address R/W description\n    /// --- ------- --- -------------------------------------------------\n    ///  1    PC     R  fetch opcode, increment PC\n    ///  2    PC     R  fetch low address byte, increment PC\n    ///  3  $0100,S  R  internal operation (predecrement S?)\n    ///  4  $0100,S  W  push PCH on stack, decrement S\n    ///  5  $0100,S  W  push PCL on stack, decrement S\n    ///  6    PC     R  copy low address byte to PCL, copy high address\n    ///                 byte to PCH\n    /// ```\n    #[inline(always)]\n    pub fn jsr(&mut self) {\n        let lo = self.fetch_byte();\n        self.read(self.pc); // Dummy read\n        self.push_word(self.pc);\n        let hi = self.fetch_byte();\n        let addr = u16::from_le_bytes([lo, hi]);\n        self.pc = addr;\n    }\n\n    /// RTI: Return from Interrupt\n    ///\n    /// ```text\n    ///  #  address R/W description\n    /// --- ------- --- -----------------------------------------------\n    ///  1    PC     R  fetch opcode, increment PC\n    ///  2    PC     R  read next instruction byte (and throw it away)\n    ///  3  $0100,S  R  increment S\n    ///  4  $0100,S  R  pull P from stack, increment S\n    ///  5  $0100,S  R  pull PCL from stack, increment S\n    ///  6  $0100,S  R  pull PCH from stack\n    /// ```\n    #[inline(always)]\n    pub fn rti(&mut self) {\n        self.read(self.pc); // Dummy read\n        let status = Status::from_bits_truncate(self.pop_byte());\n        self.set_status(status);\n        self.pc = self.pop_word();\n    }\n\n    /// RTS: Return from Subroutine\n    ///\n    /// ```text\n    ///  #  address R/W description\n    /// --- ------- --- -----------------------------------------------\n    ///  1    PC     R  fetch opcode, increment PC\n    ///  2    PC     R  read next instruction byte (and throw it away)\n    ///  3  $0100,S  R  increment S\n    ///  4  $0100,S  R  pull PCL from stack, increment S\n    ///  5  $0100,S  R  pull PCH from stack\n    ///  6    PC     R  increment PC\n    /// ```\n    #[inline(always)]\n    pub fn rts(&mut self) {\n        self.read(self.pc); // Dummy read\n        let addr = self.pop_word();\n        self.read(self.pc); // Dummy read\n        self.pc = addr.wrapping_add(1);\n    }\n\n    //  Register opcodes\n\n    /// CLC: Clear Carry Flag\n    #[inline(always)]\n    pub fn clc(&mut self) {\n        self.status.set(Status::C, false);\n    }\n    /// SEC: Set Carry Flag\n    #[inline(always)]\n    pub fn sec(&mut self) {\n        self.status.set(Status::C, true);\n    }\n    /// CLD: Clear Decimal Mode\n    #[inline(always)]\n    pub fn cld(&mut self) {\n        self.status.set(Status::D, false);\n    }\n    /// SED: Set Decimal Mode\n    #[inline(always)]\n    pub fn sed(&mut self) {\n        self.status.set(Status::D, true);\n    }\n    /// CLI: Clear Interrupt Disable Bit\n    #[inline(always)]\n    pub fn cli(&mut self) {\n        self.status.set(Status::I, false);\n    }\n    /// SEI: Set Interrupt Disable Status\n    #[inline(always)]\n    pub fn sei(&mut self) {\n        self.status.set(Status::I, true);\n    }\n    /// CLV: Clear Overflow Flag\n    #[inline(always)]\n    pub fn clv(&mut self) {\n        self.status.set(Status::V, false);\n    }\n\n    // Compare opcodes\n\n    /// CMP: Compare M and A\n    #[inline(always)]\n    pub fn cpa(&mut self) {\n        let val = self.read_operand();\n        self.cmp(self.acc, val);\n    }\n    /// CPX: Compare M and X\n    #[inline(always)]\n    pub fn cpx(&mut self) {\n        let val = self.read_operand();\n        self.cmp(self.x, val);\n    }\n    /// CPY: Compare M and Y\n    #[inline(always)]\n    pub fn cpy(&mut self) {\n        let val = self.read_operand();\n        self.cmp(self.y, val);\n    }\n    /// Utility function used by all compare instructions\n    #[inline(always)]\n    fn cmp(&mut self, reg: u8, val: u8) {\n        let result = reg.wrapping_sub(val);\n        self.status.set(Status::C, reg >= val);\n        self.set_zn_status(result);\n    }\n\n    // Stack opcodes\n\n    /// PHP: Push Processor Status on Stack\n    ///\n    /// ```text\n    ///  #  address R/W description\n    /// --- ------- --- -----------------------------------------------\n    ///  1    PC     R  fetch opcode, increment PC\n    ///  2    PC     R  read next instruction byte (and throw it away)\n    ///  3  $0100,S  W  push register on stack, decrement S\n    /// ```\n    #[inline(always)]\n    pub fn php(&mut self) {\n        // Set U and B when pushing during PHP and BRK\n        self.push_byte((self.status | Status::U | Status::B).bits());\n    }\n\n    /// PLP: Pull Processor Status from Stack\n    ///\n    /// ```text\n    ///  #  address R/W description\n    /// --- ------- --- -----------------------------------------------\n    ///  1    PC     R  fetch opcode, increment PC\n    ///  2    PC     R  read next instruction byte (and throw it away)\n    ///  3  $0100,S  R  increment S\n    ///  4  $0100,S  R  pull register from stack\n    ///  ```\n    #[inline(always)]\n    pub fn plp(&mut self) {\n        self.read(self.pc); // Dummy read\n        let status = Status::from_bits_truncate(self.pop_byte());\n        self.set_status(status);\n    }\n\n    /// PHA: Push A on Stack\n    ///\n    /// ```text\n    ///  #  address R/W description\n    /// --- ------- --- -----------------------------------------------\n    ///  1    PC     R  fetch opcode, increment PC\n    ///  2    PC     R  read next instruction byte (and throw it away)\n    ///  3  $0100,S  W  push register on stack, decrement S\n    /// ```\n    #[inline(always)]\n    pub fn pha(&mut self) {\n        self.push_byte(self.acc); // Cycle 3\n    }\n\n    /// PLA: Pull A from Stack\n    ///\n    /// ```text\n    ///  #  address R/W description\n    /// --- ------- --- -----------------------------------------------\n    ///  1    PC     R  fetch opcode, increment PC\n    ///  2    PC     R  read next instruction byte (and throw it away)\n    ///  3  $0100,S  R  increment S\n    ///  4  $0100,S  R  pull register from stack\n    /// ```\n    #[inline(always)]\n    pub fn pla(&mut self) {\n        self.read(Self::SP_BASE | u16::from(self.sp)); // Dummy read\n        self.acc = self.pop_byte(); // Cycle 4\n        self.set_zn_status(self.acc);\n    }\n\n    // System opcodes\n\n    /// BRK: Force Break Interrupt\n    ///\n    /// ```text\n    ///  #  address R/W description\n    /// --- ------- --- -----------------------------------------------\n    ///  1    PC     R  fetch opcode, increment PC\n    ///  2    PC     R  read next instruction byte (and throw it away),\n    ///                 increment PC\n    ///  3  $0100,S  W  push PCH on stack (with B flag set), decrement S\n    ///  4  $0100,S  W  push PCL on stack, decrement S\n    ///  5  $0100,S  W  push P on stack, decrement S\n    ///  6   $FFFE   R  fetch PCL\n    ///  7   $FFFF   R  fetch PCH\n    /// ```\n    #[inline(always)]\n    pub fn brk(&mut self) {\n        self.push_word(self.pc);\n\n        // Pushing status to the stack has to happen after checking NMI since it can hijack the BRK\n        // IRQ when it occurs between cycles 4 and 5.\n        // https://www.nesdev.org/wiki/CPU_interrupts#Interrupt_hijacking\n        //\n        // Set U and B when pushing during PHP and BRK\n        let status = (self.status | Status::U | Status::B).bits();\n        let nmi = self.irq_flags.contains(IrqFlags::NMI);\n        self.push_byte(status); // Cycle 5\n        self.status.set(Status::I, true);\n\n        if nmi {\n            self.irq_flags.remove(IrqFlags::NMI);\n            self.pc = self.read_word(Self::NMI_VECTOR); // Cycles 6-7\n            tracing::trace!(\n                \"NMI - PPU:{:3},{:3} CYC:{}\",\n                self.bus.ppu.cycle,\n                self.bus.ppu.scanline,\n                self.cycle\n            );\n        } else {\n            self.pc = self.read_word(Self::IRQ_VECTOR); // Cycles 6-7\n            tracing::trace!(\n                \"IRQ - PPU:{:3},{:3} CYC:{}\",\n                self.bus.ppu.cycle,\n                self.bus.ppu.scanline,\n                self.cycle\n            );\n        }\n\n        // Prevent NMI from triggering immediately after BRK\n        tracing::trace!(\n            \"Suppress NMI after BRK - PPU:{:3},{:3} CYC:{}, prev_nmi:{}\",\n            self.bus.ppu.cycle,\n            self.bus.ppu.scanline,\n            self.cycle,\n            self.irq_flags.contains(IrqFlags::PREV_NMI)\n        );\n        self.irq_flags.remove(IrqFlags::PREV_NMI);\n    }\n\n    /// NOP: No Operation\n    #[inline(always)]\n    pub fn nop(&mut self) {\n        let _ = self.read_operand();\n    }\n\n    // Unofficial opcodes\n\n    /// HLT: Captures all unimplemented opcodes and halts CPU\n    #[inline(always)]\n    pub fn hlt(&mut self) {\n        // Freezes CPU by rewiding and re-executing the bad opcode.\n        self.pc = self.pc.wrapping_sub(1);\n        // Prevent IRQ/NMI\n        self.clear_irq_flags(IrqFlags::PREV_RUN_IRQ | IrqFlags::PREV_NMI);\n\n        self.corrupted = true;\n        let opcode = usize::from(self.peek(self.pc.wrapping_sub(1)));\n        let instr = Cpu::INSTR_REF[opcode];\n        tracing::error!(\n            \"Invalid opcode ${opcode:02X} {:?} #{:?} encountered!\",\n            instr.instr,\n            instr.addr_mode,\n        );\n    }\n\n    /// ISC/ISB: Shortcut for INC then SBC\n    #[inline(always)]\n    pub fn isb(&mut self) {\n        let val = self.read_operand();\n        let addr = self.operand;\n        // INC\n        self.write(addr, val); // Dummy write\n        let val = val.wrapping_add(1);\n        // SBC\n        self.add(val ^ 0xFF);\n        self.write(addr, val);\n    }\n\n    /// DCP: Shortcut for DEC then CMP\n    #[inline(always)]\n    pub fn dcp(&mut self) {\n        let val = self.read_operand();\n        let addr = self.operand;\n        // DEC\n        self.write(addr, val); // Dummy write\n        let val = val.wrapping_sub(1);\n        // CMP\n        self.cmp(self.acc, val);\n        self.write(addr, val);\n    }\n\n    /// ATX: Shortcut for LDA & TAX\n    #[inline(always)]\n    pub fn atx(&mut self) {\n        let val = self.read_operand();\n        self.set_acc(val); // LDA\n        self.set_x(self.acc); // TAX\n    }\n\n    /// AXS: A & X into X\n    #[inline(always)]\n    pub fn axs(&mut self) {\n        let val = self.read_operand();\n        // CMP & DEX\n        let res = (self.acc & self.x).wrapping_sub(val);\n        self.status.set(Status::C, (self.acc & self.x) >= val);\n        self.set_x(res);\n    }\n\n    /// LAS: Shortcut for LDA then TSX, but ANDs memory stack pointer\n    #[inline(always)]\n    pub fn las(&mut self) {\n        let val = self.read_operand();\n        self.set_acc(val & self.sp);\n        self.set_x(self.acc);\n        self.set_sp(self.acc);\n    }\n\n    /// LAX: Shortcut for LDA then TAX\n    #[inline(always)]\n    pub fn lax(&mut self) {\n        let val = self.read_operand();\n        self.set_x(val);\n        self.set_acc(val);\n    }\n\n    /// SYA/A11/SHY/SAY/TEY: Combinations of STA/STX/STY\n    /// AND Y register with the high byte of the target address of the argument + 1. Store the\n    /// result in memory.\n    #[inline(always)]\n    pub fn sya(&mut self) {\n        let base_addr = self.fetch_word();\n        self.sya_sxa_axa(base_addr, self.x, self.y);\n    }\n\n    /// SXA/SHX/XAS: AND X with the high byte of the target address + 1\n    #[inline(always)]\n    pub fn sxa(&mut self) {\n        let base_addr = self.fetch_word();\n        self.sya_sxa_axa(base_addr, self.y, self.x);\n    }\n\n    /// SHA/AXA: AND X with A then AND with 7, then store in memory\n    #[inline(always)]\n    pub fn shaa(&mut self) {\n        let base_addr = self.fetch_word();\n        self.sya_sxa_axa(base_addr, self.y, self.x & self.acc);\n    }\n\n    /// AHX: And X with A stores A&X&H into {adr}\n    #[inline(always)]\n    pub fn shaz(&mut self) {\n        let zero_addr = self.fetch_byte();\n        let base_addr = {\n            let lo = self.read(u16::from(zero_addr));\n            let hi = self.read(u16::from(zero_addr.wrapping_add(1)));\n            u16::from_le_bytes([lo, hi])\n        };\n        self.sya_sxa_axa(base_addr, self.y, self.x & self.acc);\n    }\n\n    fn sya_sxa_axa(&mut self, base_addr: u16, index_reg: u8, val_reg: u8) {\n        let addr = base_addr.wrapping_add(u16::from(index_reg));\n        let page_crossed = Cpu::pages_differ(base_addr, addr);\n\n        let start_cycles = self.cycle;\n        // Dummy read with fixed high byte\n        self.read((base_addr & 0xFF00) | (addr & 0x00FF));\n\n        // Dummy read took more than 1 cycle, so it was interrupted by a DMA\n        let had_dma = (self.cycle - start_cycles) > 1;\n\n        let mut hi = (addr >> 8) as u8;\n        let lo = (addr & 0xFF) as u8;\n        if page_crossed {\n            hi &= val_reg;\n        }\n\n        let val = if had_dma {\n            val_reg\n        } else {\n            val_reg & ((base_addr >> 8) + 1) as u8\n        };\n        self.write(u16::from_le_bytes([lo, hi]), val);\n    }\n\n    /// SAX: AND A with X\n    #[inline(always)]\n    pub fn sax(&mut self) {\n        self.write(self.operand, self.acc & self.x);\n    }\n\n    /// XXA: Shortcutr for TXA with AND\n    #[inline(always)]\n    pub fn xaa(&mut self) {\n        let val = self.read_operand();\n        self.set_acc((self.acc | 0xEE) & self.x & val);\n    }\n\n    /// RRA: Shortcut for ROR then ADC\n    #[inline(always)]\n    pub fn rra(&mut self) {\n        let val = self.read_operand();\n        let addr = self.operand;\n        // ROR\n        self.write(addr, val); // Dummy write\n        let shifted_val = self.ror(val);\n        // ADC\n        self.add(shifted_val);\n        self.write(addr, shifted_val);\n    }\n\n    /// TAS: Shortcut for STA then TXS, Same as SHA but sets SP = A & X\n    #[inline(always)]\n    pub fn tas(&mut self) {\n        self.shaa();\n        // TXS\n        self.set_sp(self.x & self.acc);\n    }\n\n    /// ARR: Shortcut for AND #imm then ROR, but sets flags differently\n    /// C is bit 6 and V is bit 6 xor bit 5\n    #[inline(always)]\n    pub fn arr(&mut self) {\n        let val = self.read_operand();\n        let carry = self.status_bit(Status::C);\n        self.set_acc(((self.acc & val) >> 1) | (carry << 7));\n        self.status.set(Status::C, (self.acc & 0x40) > 0);\n        self.status.set(\n            Status::V,\n            (self.status_bit(Status::C) ^ (self.acc >> 5) & 0x01) > 0,\n        );\n    }\n\n    /// SRA: Shortcut for LSR then EOR\n    #[inline(always)]\n    pub fn sre(&mut self) {\n        let val = self.read_operand();\n        let addr = self.operand;\n        // LSR\n        self.write(addr, val); // Dummy write\n        let shifted_val = self.lsr(val);\n        // EOR\n        self.set_acc(self.acc ^ shifted_val);\n        self.write(addr, shifted_val);\n    }\n\n    /// ALR/ASR: Shortcut for AND #imm then LSR\n    #[inline(always)]\n    pub fn alr(&mut self) {\n        let val = self.read_operand();\n        self.set_acc(self.acc & val);\n        self.status.set(Status::C, (self.acc & 0x01) > 0);\n        self.set_acc(self.acc >> 1);\n    }\n\n    /// RLA: Shortcut for ROL then AND\n    #[inline(always)]\n    pub fn rla(&mut self) {\n        let val = self.read_operand();\n        let addr = self.operand;\n        // ROL\n        self.write(addr, val); // Dummy write\n        let shifted_val = self.rol(val);\n        // AND\n        self.set_acc(self.acc & shifted_val);\n        self.write(addr, shifted_val);\n    }\n\n    /// ANC/AAC: AND #imm but puts bit 7 into carry as if ASL was executed\n    #[inline(always)]\n    pub fn anc(&mut self) {\n        let val = self.read_operand();\n        self.set_acc(self.acc & val);\n        self.status.set(Status::C, self.status.contains(Status::N));\n    }\n\n    /// SLO: Shortcut for ASL then ORA\n    #[inline(always)]\n    pub fn slo(&mut self) {\n        let val = self.read_operand();\n        let addr = self.operand;\n        // ASL\n        self.write(addr, val); // Dummy write\n        let shifted_val = self.asl(val);\n        // ORA\n        self.set_acc(self.acc | shifted_val);\n        self.write(addr, shifted_val);\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/cpu.rs",
    "content": "//! 6502 Central Processing Unit (CPU) implementation.\n//!\n//! <https://wiki.nesdev.org/w/index.php/CPU>\n\nuse crate::{\n    bus::Bus,\n    common::{Clock, NesRegion, Regional, Reset, ResetKind},\n    mem::{Read, Write},\n};\nuse crate::{\n    cpu::instr::{\n        AddrMode,\n        Instr::{JMP, JSR},\n        InstrRef,\n    },\n    mapper::Map,\n};\nuse bitflags::bitflags;\nuse serde::{Deserialize, Serialize};\nuse std::fmt::{self};\nuse tracing::trace;\n\npub mod instr;\n\nbitflags! {\n    #[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]\n    #[must_use]\n    pub struct IrqFlags: u8 {\n        const NMI = 1 << 0;\n        const PREV_NMI = 1 << 1;\n        const PREV_NMI_PENDING = 1 << 2;\n        const RUN_IRQ = 1 << 3;\n        const PREV_RUN_IRQ = 1 << 4;\n        const DMA_DMC = 1 << 5;\n        const DMA_HALT = 1 << 6;\n        const DMA_DUMMY_READ = 1 << 7;\n    }\n}\n\n// Status Registers\n// https://wiki.nesdev.org/w/index.php/Status_flags\n// 7654 3210\n// NVUB DIZC\n// |||| ||||\n// |||| |||+- Carry\n// |||| ||+-- Zero\n// |||| |+--- Interrupt Disable\n// |||| +---- Decimal Mode - Not used in the NES but still has to function\n// |||+------ Break - 1 when pushed to stack from PHP/BRK, 0 from IRQ/NMI\n// ||+------- Unused - always set to 1 when pushed to stack\n// |+-------- Overflow\n// +--------- Negative\nbitflags! {\n    /// CPU Status Registers.\n    #[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]\n    #[must_use]\n    pub struct Status: u8 {\n        const C = 1;      // Carry\n        const Z = 1 << 1; // Zero\n        const I = 1 << 2; // Disable Interrupt\n        const D = 1 << 3; // Decimal Mode\n        const B = 1 << 4; // Break\n        const U = 1 << 5; // Unused\n        const V = 1 << 6; // Overflow\n        const N = 1 << 7; // Negative\n    }\n}\n\n/// The Central Processing Unit status and registers\n#[derive(Default, Clone, Serialize, Deserialize)]\n#[must_use]\n#[repr(C)]\npub struct Cpu {\n    pub cycle: u32, // total number of cycles ran\n    pub master_clock: u32,\n    // start/end cycle counts for reads/writes\n    pub start_cycles: u8,\n    pub end_cycles: u8,\n    pub pc: u16,             // program counter\n    pub operand: u16,        // opcode operand\n    pub addr_mode: AddrMode, // Addressing mode\n    pub sp: u8,              // stack pointer - stack is at $0100-$01FF\n    pub acc: u8,             // accumulator\n    pub x: u8,               // x register\n    pub y: u8,               // y register\n    pub status: Status,      // Status Registers\n    pub irq_flags: IrqFlags,\n    pub dma_oam_addr: Option<u16>,\n    pub bus: Bus,\n    #[serde(skip)]\n    pub corrupted: bool, // Encountering an invalid opcode corrupts CPU processing\n    #[serde(skip)]\n    pub disasm: String,\n}\n\nimpl Cpu {\n    const NTSC_MASTER_CLOCK_RATE: f32 = 21_477_272.0;\n    const NTSC_CPU_CLOCK_RATE: f32 = Self::NTSC_MASTER_CLOCK_RATE / 12.0;\n    const PAL_MASTER_CLOCK_RATE: f32 = 26_601_712.0;\n    const PAL_CPU_CLOCK_RATE: f32 = Self::PAL_MASTER_CLOCK_RATE / 16.0;\n    const DENDY_CPU_CLOCK_RATE: f32 = Self::PAL_MASTER_CLOCK_RATE / 15.0;\n\n    // Represents CPU/PPU alignment and would range from 1..=ppu.clock_divider-1\n    // if random PPU alignment was emulated\n    // See: https://www.nesdev.org/wiki/PPU_frame_timing#CPU-PPU_Clock_Alignment\n    const PPU_OFFSET: u32 = 1;\n\n    const NMI_VECTOR: u16 = 0xFFFA; // NMI Vector address\n    const IRQ_VECTOR: u16 = 0xFFFE; // IRQ Vector address\n    const RESET_VECTOR: u16 = 0xFFFC; // Vector address at reset\n    const POWER_ON_STATUS: Status = Status::U.union(Status::I);\n    const POWER_ON_SP: u8 = 0xFD;\n    const SP_BASE: u16 = 0x0100; // Stack-pointer starting address\n\n    /// Create a new CPU with the given bus.\n    pub fn new(bus: Bus) -> Self {\n        let mut cpu = Self {\n            cycle: 0,\n            master_clock: 0,\n            start_cycles: 6,\n            end_cycles: 6,\n            pc: 0x0000,\n            operand: 0,\n            addr_mode: AddrMode::default(),\n            sp: 0x00,\n            acc: 0x00,\n            x: 0x00,\n            y: 0x00,\n            status: Self::POWER_ON_STATUS,\n            irq_flags: IrqFlags::default(),\n            dma_oam_addr: None,\n            bus,\n            corrupted: false,\n            disasm: String::new(),\n        };\n        cpu.set_region(cpu.bus.region);\n        cpu\n    }\n\n    /// Load a CPU state.\n    pub fn load(&mut self, mut cpu: Self) {\n        // Doesn't make sense to load a debugger from a previous state\n        cpu.bus.ppu.debugger = std::mem::take(&mut self.bus.ppu.debugger);\n        *self = cpu;\n    }\n\n    /// Returns the CPU clock rate based on [`NesRegion`].\n    #[inline]\n    #[must_use]\n    pub const fn region_clock_rate(region: NesRegion) -> f32 {\n        match region {\n            NesRegion::Auto | NesRegion::Ntsc => Self::NTSC_CPU_CLOCK_RATE,\n            NesRegion::Pal => Self::PAL_CPU_CLOCK_RATE,\n            NesRegion::Dendy => Self::DENDY_CPU_CLOCK_RATE,\n        }\n    }\n\n    /// Clock rate based on currently configured NES region.\n    #[inline]\n    #[must_use]\n    pub const fn clock_rate(&self) -> f32 {\n        Self::region_clock_rate(self.bus.region)\n    }\n\n    /// Peek at the next instruction.\n    #[inline]\n    pub fn next_instr(&self) -> InstrRef {\n        let opcode = self.peek(self.pc);\n        Cpu::INSTR_REF[usize::from(opcode)]\n    }\n\n    /// Start OAM DMA.\n    #[inline]\n    pub fn start_oam_dma(&mut self, addr: u16) {\n        self.irq_flags.insert(IrqFlags::DMA_HALT);\n        self.dma_oam_addr = Some(addr);\n    }\n\n    /// Process an interrupted request.\n    ///\n    /// <https://wiki.nesdev.org/w/index.php/IRQ>\n    ///  #  address R/W description\n    /// --- ------- --- -----------------------------------------------\n    ///  1    PC     R  fetch PCH\n    ///  2    PC     R  fetch PCL\n    ///  3  $0100,S  W  push PCH to stack, decrement S\n    ///  4  $0100,S  W  push PCL to stack, decrement S\n    ///  5  $0100,S  W  push P to stack, decrement S\n    ///  6    PC     R  fetch low byte of interrupt vector\n    ///  7    PC     R  fetch high byte of interrupt vector\n    #[cold]\n    #[inline(never)]\n    pub fn irq(&mut self) {\n        if self.irq_flags(IrqFlags::DMA_HALT) && self.region() == NesRegion::Pal {\n            // Check for DMA on PAL\n            self.handle_dma(self.pc);\n        }\n\n        self.read(self.pc); // Dummy read\n        self.read(self.pc); // Dummy read\n        self.push_word(self.pc);\n\n        // Pushing status to the stack has to happen after checking NMI since it can hijack the BRK\n        // IRQ when it occurs between cycles 4 and 5.\n        // https://www.nesdev.org/wiki/CPU_interrupts#Interrupt_hijacking\n        //\n        // Set U and !B during push\n        let status = ((self.status | Status::U) & !Status::B).bits();\n        let nmi = self.irq_flags(IrqFlags::NMI);\n        self.push_byte(status);\n        self.status.set(Status::I, true);\n\n        if nmi {\n            self.clear_irq_flags(IrqFlags::NMI);\n            self.pc = self.read_word(Self::NMI_VECTOR);\n            self.clock_sync();\n            trace!(\n                \"NMI - PPU:{:3},{:3} CYC:{}\",\n                self.bus.ppu.cycle, self.bus.ppu.scanline, self.cycle\n            );\n        } else {\n            self.pc = self.read_word(Self::IRQ_VECTOR);\n            trace!(\n                \"IRQ - PPU:{:3},{:3} CYC:{}\",\n                self.bus.ppu.cycle, self.bus.ppu.scanline, self.cycle\n            );\n        }\n    }\n\n    /// Handle CPU interrupt requests, if any are pending.\n    #[inline(always)]\n    fn handle_interrupts(&mut self) {\n        let irq_pending_mapper = self.bus.ppu.mapper.irq_pending();\n        let dma_pending_mapper = self.bus.ppu.mapper.dma_pending();\n        let nmi_pending = self.bus.ppu.nmi_pending;\n        let irq_pending_apu = self.bus.apu.irq_pending();\n        let dma_pending_apu = self.bus.apu.dma_pending();\n\n        if dma_pending_apu {\n            self.bus.apu.clear_dma_pending();\n            self.irq_flags\n                .insert(IrqFlags::DMA_DMC | IrqFlags::DMA_HALT | IrqFlags::DMA_DUMMY_READ);\n        } else if dma_pending_mapper {\n            self.bus.ppu.mapper.clear_dma_pending();\n            self.irq_flags\n                .insert(IrqFlags::DMA_DMC | IrqFlags::DMA_HALT | IrqFlags::DMA_DUMMY_READ);\n        }\n\n        let flags = &mut self.irq_flags;\n\n        // https://www.nesdev.org/wiki/CPU_interrupts\n        //\n        // The internal signal goes high during φ1 of the cycle that follows the one where\n        // the edge is detected, and stays high until the NMI has been handled. NMI is handled only\n        // when `prev_nmi` is true.\n        flags.set(IrqFlags::PREV_NMI, flags.contains(IrqFlags::NMI));\n\n        // This edge detector polls the status of the NMI line during φ2 of each CPU cycle (i.e.,\n        // during the second half of each cycle, hence here in `end_cycle`) and raises an internal\n        // signal if the input goes from being high during one cycle to being low during the\n        // next.\n        let prev_nmi_pending = flags.contains(IrqFlags::PREV_NMI_PENDING);\n        if !prev_nmi_pending & nmi_pending {\n            flags.insert(IrqFlags::NMI);\n        }\n        flags.set(IrqFlags::PREV_NMI_PENDING, nmi_pending);\n\n        // The IRQ status at the end of the second-to-last cycle is what matters,\n        // so keep the second-to-last status.\n        flags.set(IrqFlags::PREV_RUN_IRQ, flags.contains(IrqFlags::RUN_IRQ));\n        let run_irq = (irq_pending_mapper | irq_pending_apu) & !self.status.intersects(Status::I);\n        flags.set(IrqFlags::RUN_IRQ, run_irq);\n\n        #[cfg(feature = \"trace\")]\n        if !flags.contains(IrqFlags::PREV_NMI_PENDING) && flags.contains(IrqFlags::RUN_IRQ) {\n            trace!(\n                \"IRQ: {} - CYC:{}\",\n                irq_pending_mapper | irq_pending_apu,\n                self.cycle\n            );\n        }\n    }\n\n    /// Start a CPU cycle.\n    #[inline(always)]\n    fn start_cycle(&mut self, increment: u8) {\n        self.master_clock = self.master_clock.wrapping_add(u32::from(increment));\n        self.cycle = self.cycle.wrapping_add(1);\n        self.bus.ppu.clock_to(self.master_clock - Self::PPU_OFFSET);\n        self.bus.cpu_clock();\n    }\n\n    /// End a CPU cycle.\n    #[inline(always)]\n    fn end_cycle(&mut self, increment: u8) {\n        self.master_clock = self.master_clock.wrapping_add(u32::from(increment));\n        self.bus.ppu.clock_to(self.master_clock - Self::PPU_OFFSET);\n\n        self.handle_interrupts();\n    }\n\n    /// Start a direct-memory access (DMA) cycle.\n    #[inline(always)]\n    fn start_dma_cycle(&mut self) {\n        // OAM DMA cycles count as halt/dummy reads for DMC DMA when both run at the same time\n        if self.irq_flags(IrqFlags::DMA_HALT) {\n            self.clear_irq_flags(IrqFlags::DMA_HALT);\n        } else {\n            self.clear_irq_flags(IrqFlags::DMA_DUMMY_READ);\n        }\n        self.start_cycle(self.start_cycles - 1);\n    }\n\n    /// Handle a direct-memory access (DMA) request.\n    #[cold]\n    #[inline(never)]\n    fn handle_dma(&mut self, addr: u16) {\n        trace!(\"Starting DMA - CYC:{}\", self.cycle);\n\n        self.start_cycle(self.start_cycles - 1);\n        self.bus.read(addr);\n        self.end_cycle(self.start_cycles + 1);\n        self.clear_irq_flags(IrqFlags::DMA_HALT);\n\n        let skip_dummy_reads = addr == 0x4016 || addr == 0x4017;\n\n        let mut oam_offset = 0;\n        let mut oam_dma_count = 0;\n        let mut read_val = 0;\n\n        loop {\n            let dma_dmc = self.irq_flags(IrqFlags::DMA_DMC);\n            let dma_oam_addr = self.dma_oam_addr;\n            if !dma_dmc & dma_oam_addr.is_none() {\n                break;\n            }\n\n            if self.cycle & 0x01 == 0x00 {\n                if dma_dmc\n                    & !self.irq_flags(IrqFlags::DMA_HALT)\n                    & !self.irq_flags(IrqFlags::DMA_DUMMY_READ)\n                {\n                    // DMC DMA ready to read a byte (halt and dummy read done before)\n                    self.start_dma_cycle();\n                    let dma_addr = self.bus.apu.dmc.dma_addr();\n                    read_val = self.bus.read(dma_addr);\n                    trace!(\n                        \"Loaded DMC DMA byte. ${dma_addr:04X}: {read_val} - CYC:{}\",\n                        self.cycle\n                    );\n                    self.end_cycle(self.start_cycles + 1);\n                    self.bus.apu.dmc.load_buffer(read_val);\n                    self.clear_irq_flags(IrqFlags::DMA_DMC);\n                } else if let Some(oam_addr) = dma_oam_addr {\n                    // DMC DMA not running or ready, run OAM DMA\n                    self.start_dma_cycle();\n                    read_val = self.bus.read(oam_addr + oam_offset);\n                    self.end_cycle(self.start_cycles + 1);\n                    oam_offset += 1;\n                    oam_dma_count += 1;\n                } else {\n                    // DMC DMA running, but not ready yet (needs to halt, or dummy read) and OAM\n                    // DMA isn't running\n                    debug_assert!(\n                        self.irq_flags(IrqFlags::DMA_HALT)\n                            | self.irq_flags(IrqFlags::DMA_DUMMY_READ)\n                    );\n                    self.start_dma_cycle();\n                    if !skip_dummy_reads {\n                        self.bus.read(addr); // throw away\n                    }\n                    self.end_cycle(self.start_cycles + 1);\n                }\n            } else if dma_oam_addr.is_some() & (oam_dma_count & 0x01 == 0x01) {\n                // OAM DMA write cycle, done on odd cycles after a read on even cycles\n                self.start_dma_cycle();\n                self.bus.write(0x2004, read_val);\n                self.end_cycle(self.start_cycles + 1);\n                oam_dma_count += 1;\n                if oam_dma_count == 0x200 {\n                    self.dma_oam_addr.take();\n                }\n            } else {\n                // Align to read cycle before starting OAM DMA (or align to perform DMC read)\n                self.start_dma_cycle();\n                if !skip_dummy_reads {\n                    self.bus.read(addr); // throw away\n                }\n                self.end_cycle(self.start_cycles + 1);\n            }\n        }\n    }\n\n    // Interrupt flag functions\n\n    /// Clear [`IrqFlags`] flags for the given bits.\n    #[inline(always)]\n    fn clear_irq_flags(&mut self, flags: IrqFlags) {\n        self.irq_flags &= !flags;\n    }\n\n    /// Returns `true` if the [`IrqFlags`] register is set.\n    #[inline(always)]\n    fn irq_flags(&self, flags: IrqFlags) -> bool {\n        (self.irq_flags & flags).bits() == flags.bits()\n    }\n\n    // Status Register functions\n\n    /// Set [`Status`] flags for the given bits.\n    #[inline(always)]\n    fn set_status(&mut self, status: Status) {\n        self.status = status & !Status::U & !Status::B;\n    }\n\n    /// Returns the [`Status`] register as a byte.\n    #[inline(always)]\n    const fn status_bit(&self, reg: Status) -> u8 {\n        self.status.intersection(reg).bits()\n    }\n\n    /// Set accumulator and update [`Status`] flags based on value.\n    #[inline(always)]\n    fn set_acc(&mut self, val: u8) {\n        self.set_zn_status(val);\n        self.acc = val;\n    }\n\n    /// Set x and update [`Status`] flags based on value.\n    #[inline(always)]\n    fn set_x(&mut self, val: u8) {\n        self.set_zn_status(val);\n        self.x = val;\n    }\n\n    /// Set y and update [`Status`] flags based on value.\n    #[inline(always)]\n    fn set_y(&mut self, val: u8) {\n        self.set_zn_status(val);\n        self.y = val;\n    }\n\n    /// Set stack pointer.\n    #[inline(always)]\n    const fn set_sp(&mut self, val: u8) {\n        self.sp = val;\n    }\n\n    /// Set both [`Status::Z`] and [`Status::N`] flags based on value.\n    #[inline(always)]\n    fn set_zn_status(&mut self, val: u8) {\n        self.status.set(Status::Z, val == 0x00);\n        self.status.set(Status::N, val & 0x80 > 0);\n    }\n\n    // Stack Functions\n\n    /// Push a byte to the stack.\n    #[inline(always)]\n    fn push_byte(&mut self, val: u8) {\n        self.write(Self::SP_BASE | u16::from(self.sp), val);\n        self.sp = self.sp.wrapping_sub(1);\n    }\n\n    /// Pull a byte from the stack.\n    #[inline(always)]\n    #[must_use]\n    fn pop_byte(&mut self) -> u8 {\n        self.sp = self.sp.wrapping_add(1);\n        self.read(Self::SP_BASE | u16::from(self.sp))\n    }\n\n    /// Peek byte at the top of the stack.\n    #[inline]\n    #[must_use]\n    pub fn peek_stack(&self) -> u8 {\n        self.peek(Self::SP_BASE | u16::from(self.sp.wrapping_add(1)))\n    }\n\n    /// Peek at the top of the stack.\n    #[inline]\n    #[must_use]\n    pub fn peek_stack_u16(&self) -> u16 {\n        let lo = self.peek(Self::SP_BASE | u16::from(self.sp));\n        let hi = self.peek(Self::SP_BASE | u16::from(self.sp.wrapping_add(1)));\n        u16::from_le_bytes([lo, hi])\n    }\n\n    /// Push a word (two bytes) to the stack\n    #[inline(always)]\n    fn push_word(&mut self, val: u16) {\n        let [lo, hi] = val.to_le_bytes();\n        self.push_byte(hi);\n        self.push_byte(lo);\n    }\n\n    /// Pull a word (two bytes) from the stack\n    #[inline(always)]\n    fn pop_word(&mut self) -> u16 {\n        let lo = self.pop_byte();\n        let hi = self.pop_byte();\n        u16::from_le_bytes([lo, hi])\n    }\n\n    // Memory accesses\n\n    /// Fetch a byte and increments PC by 1.\n    #[inline(always)]\n    #[must_use]\n    fn fetch_byte(&mut self) -> u8 {\n        let val = self.read(self.pc);\n        self.pc = self.pc.wrapping_add(1);\n        val\n    }\n\n    /// Fetch opcode operand based on addressing mode.\n    #[inline(always)]\n    #[must_use]\n    fn fetch_operand(&mut self) -> u16 {\n        match self.addr_mode {\n            AddrMode::ACC | AddrMode::IMP => self.acc_imp(),\n            AddrMode::IMM | AddrMode::REL | AddrMode::ZP0 => self.imm_rel_zp(),\n            AddrMode::ZPX => self.zpx(),\n            AddrMode::ZPY => self.zpy(),\n            AddrMode::IND => self.ind(),\n            AddrMode::IDX => self.idx(),\n            AddrMode::IDY => self.idy(false),\n            AddrMode::IDYW => self.idy(true),\n            AddrMode::ABS => self.abs(),\n            AddrMode::ABX => self.abx(false),\n            AddrMode::ABXW => self.abx(true),\n            AddrMode::ABY => self.aby(false),\n            AddrMode::ABYW => self.aby(true),\n            AddrMode::OTH => 0,\n        }\n    }\n\n    /// Fetch a 16-bit word and increments PC by 2.\n    #[inline(always)]\n    #[must_use]\n    fn fetch_word(&mut self) -> u16 {\n        let lo = self.fetch_byte();\n        let hi = self.fetch_byte();\n        u16::from_le_bytes([lo, hi])\n    }\n\n    /// Read operand value.\n    #[inline(always)]\n    #[must_use]\n    fn read_operand(&mut self) -> u8 {\n        if matches!(\n            self.addr_mode,\n            AddrMode::ACC | AddrMode::IMP | AddrMode::IMM | AddrMode::REL\n        ) {\n            self.operand as u8\n        } else {\n            self.read(self.operand)\n        }\n    }\n\n    /// Read a 16-bit word.\n    #[inline(always)]\n    #[must_use]\n    pub fn read_word(&mut self, addr: u16) -> u16 {\n        let lo = self.read(addr);\n        let hi = self.read(addr.wrapping_add(1));\n        u16::from_le_bytes([lo, hi])\n    }\n\n    /// Peek a 16-bit word without side effects.\n    #[inline]\n    #[must_use]\n    pub fn peek_word(&self, addr: u16) -> u16 {\n        let lo = self.peek(addr);\n        let hi = self.peek(addr.wrapping_add(1));\n        u16::from_le_bytes([lo, hi])\n    }\n\n    /// Disassemble the instruction at the given program counter.\n    pub fn disassemble(&mut self, pc: &mut u16) -> &str {\n        use fmt::Write;\n\n        self.disasm.clear();\n\n        let addr = { *pc };\n        let opcode = {\n            let byte = self.peek(*pc);\n            *pc = pc.wrapping_add(1);\n            byte\n        };\n        let _ = write!(self.disasm, \"${addr:04X} ${opcode:02X} \");\n\n        let mut peek_byte = || {\n            let byte = self.peek(*pc);\n            *pc = pc.wrapping_add(1);\n            byte\n        };\n        let mut peek_word = || {\n            let lo = peek_byte();\n            let hi = peek_byte();\n            (lo, hi, u16::from_le_bytes([lo, hi]))\n        };\n\n        let instr_ref = Cpu::INSTR_REF[usize::from(opcode)];\n        match instr_ref.addr_mode {\n            AddrMode::ACC | AddrMode::IMP => {\n                let _ = write!(self.disasm, \"        {instr_ref}\");\n            }\n            AddrMode::IMM => {\n                let byte = peek_byte();\n                let _ = write!(self.disasm, \"${byte:02X}     {instr_ref} #${byte:02X}\");\n            }\n            AddrMode::REL => {\n                let byte = peek_byte();\n                let addr = (*pc as i16).wrapping_add(i16::from(byte as i8)) as u16;\n                let _ = write!(self.disasm, \"${byte:02X}     {instr_ref} ${addr:04X}\");\n            }\n            AddrMode::ZP0 => {\n                let byte = peek_byte();\n                let val = self.peek(byte.into());\n                let _ = write!(\n                    self.disasm,\n                    \"${byte:02X}     {instr_ref} ${byte:02X} = #${val:02X}\"\n                );\n            }\n            AddrMode::ZPX => {\n                let byte = peek_byte();\n                let addr = byte.wrapping_add(self.x);\n                let val = self.peek(addr.into());\n                let _ = write!(\n                    self.disasm,\n                    \"${byte:02X}     {instr_ref} ${byte:02X},X @ ${addr:02X} = #${val:02X}\"\n                );\n            }\n            AddrMode::ZPY => {\n                let byte = peek_byte();\n                let addr = byte.wrapping_add(self.y);\n                let val = self.peek(addr.into());\n                let _ = write!(\n                    self.disasm,\n                    \"${byte:02X}     {instr_ref} ${byte:02X},Y @ ${addr:02X} = #${val:02X}\"\n                );\n            }\n            AddrMode::IND => {\n                let (byte1, byte2, base_addr) = peek_word();\n                let val = if (base_addr & 0xFF) == 0xFF {\n                    let lo = self.peek(base_addr);\n                    let hi = self.peek(base_addr - 0xFF);\n                    u16::from_le_bytes([lo, hi])\n                } else {\n                    self.peek_word(base_addr)\n                };\n                let _ = write!(\n                    self.disasm,\n                    \"${byte1:02X} ${byte2:02X} {instr_ref} (${base_addr:04X}) = ${val:04X}\"\n                );\n            }\n            AddrMode::IDX => {\n                let byte = peek_byte();\n                let zero_addr = byte.wrapping_add(self.x);\n                let lo = self.peek(u16::from(zero_addr));\n                let hi = self.peek(u16::from(zero_addr.wrapping_add(1)));\n                let addr = u16::from_le_bytes([lo, hi]);\n                let val = self.peek(addr);\n                let _ = write!(\n                    self.disasm,\n                    \"${byte:02X}     {instr_ref} (${byte:02X},X) @ ${addr:04X} = #${val:02X}\"\n                );\n            }\n            AddrMode::IDY | AddrMode::IDYW => {\n                let byte = peek_byte();\n                let base_addr = {\n                    let lo = self.peek(u16::from(byte));\n                    let hi = self.peek(u16::from(byte.wrapping_add(1)));\n                    u16::from_le_bytes([lo, hi])\n                };\n                let addr = base_addr.wrapping_add(u16::from(self.y));\n                let val = self.peek(addr);\n                let _ = write!(\n                    self.disasm,\n                    \"${byte:02X}     {instr_ref} (${byte:02X}),Y @ ${addr:04X} = #${val:02X}\"\n                );\n            }\n            AddrMode::ABS => {\n                let (byte1, byte2, addr) = peek_word();\n                if instr_ref.instr == JMP {\n                    let _ = write!(\n                        self.disasm,\n                        \"${byte1:02X} ${byte2:02X} {instr_ref} ${addr:04X}\"\n                    );\n                } else {\n                    let val = self.peek(addr);\n                    let _ = write!(\n                        self.disasm,\n                        \"${byte1:02X} ${byte2:02X} {instr_ref} ${addr:04X} = #${val:02X}\"\n                    );\n                }\n            }\n            AddrMode::ABX | AddrMode::ABXW => {\n                let (byte1, byte2, base_addr) = peek_word();\n                let addr = base_addr.wrapping_add(self.x.into());\n                let val = self.peek(addr);\n                let _ = write!(\n                    self.disasm,\n                    \"${byte1:02X} ${byte2:02X} {instr_ref} ${base_addr:04X},X @ ${addr:04X} = #${val:02X}\"\n                );\n            }\n            AddrMode::ABY | AddrMode::ABYW => {\n                let (byte1, byte2, base_addr) = peek_word();\n                let addr = base_addr.wrapping_add(self.y.into());\n                let val = self.peek(addr);\n                let _ = write!(\n                    self.disasm,\n                    \"${byte1:02X} ${byte2:02X} {instr_ref} ${base_addr:04X},Y @ ${addr:04X} = #${val:02X}\"\n                );\n            }\n            AddrMode::OTH => {\n                let (byte1, byte2, addr) = peek_word();\n                if instr_ref.instr == JSR {\n                    let _ = write!(\n                        self.disasm,\n                        \"${byte1:02X} ${byte2:02X} {instr_ref} ${addr:04X}\"\n                    );\n                } else {\n                    let val = self.peek(addr);\n                    let _ = write!(\n                        self.disasm,\n                        \"${byte1:02X} ${byte2:02X} {instr_ref} ${addr:04X} = #${val:02X}\"\n                    );\n                }\n            }\n        };\n        &self.disasm\n    }\n\n    /// Logs the disassembled instruction being executed.\n    #[cold]\n    #[inline(never)]\n    pub fn trace_instr(&mut self) {\n        if !tracing::enabled!(tracing::Level::TRACE) {\n            return;\n        }\n        let mut pc = self.pc;\n        let status = self.status;\n        let acc = self.acc;\n        let x = self.x;\n        let y = self.y;\n        let sp = self.sp;\n        let ppu_cycle = self.bus.ppu.cycle;\n        let ppu_scanline = self.bus.ppu.scanline;\n        let cycle = self.cycle;\n        let n = if status.contains(Status::N) { 'N' } else { 'n' };\n        let v = if status.contains(Status::V) { 'V' } else { 'v' };\n        let i = if status.contains(Status::I) { 'I' } else { 'i' };\n        let z = if status.contains(Status::Z) { 'Z' } else { 'z' };\n        let c = if status.contains(Status::C) { 'C' } else { 'c' };\n        println!(\n            \"{:<50} A:{acc:02X} X:{x:02X} Y:{y:02X} P:{n}{v}--d{i}{z}{c} SP:{sp:02X} PPU:{ppu_cycle:3},{ppu_scanline:3} CYC:{cycle}\",\n            self.disassemble(&mut pc),\n        );\n    }\n\n    // Utilities\n\n    /// Returns whether two addresses are on different memory pages.\n    #[inline(always)]\n    #[must_use]\n    const fn pages_differ(addr1: u16, addr2: u16) -> bool {\n        (addr1 & 0xFF00) != (addr2 & 0xFF00)\n    }\n\n    /// Returns whether a memory page is crossed using relative address.\n    #[inline(always)]\n    #[must_use]\n    const fn page_crossed(addr: u16, offset: i16) -> bool {\n        ((addr as i16 + offset) as u16 & 0xFF00) != (addr & 0xFF00)\n    }\n\n    /// Runs all componnets up to master clock, synchronizing them.\n    #[inline(always)]\n    pub fn clock_sync(&mut self) {\n        self.bus.ppu.clock_to(self.master_clock);\n        self.master_clock = self.master_clock.saturating_sub(self.bus.ppu.master_clock);\n        self.bus.ppu.master_clock = 0;\n        self.bus.apu.clock_sync();\n    }\n}\n\nimpl Clock for Cpu {\n    /// Runs the CPU one instruction.\n    #[inline(always)]\n    fn clock(&mut self) {\n        #[cfg(feature = \"trace\")]\n        self.trace_instr();\n\n        let opcode = self.fetch_byte(); // Cycle 1\n        let op = Cpu::OPS[usize::from(opcode)];\n        self.addr_mode = op.addr_mode();\n        self.operand = self.fetch_operand();\n        op.run(self);\n\n        if self\n            .irq_flags\n            .intersects(IrqFlags::PREV_RUN_IRQ | IrqFlags::PREV_NMI)\n        {\n            self.irq();\n        }\n    }\n}\n\nimpl Read for Cpu {\n    #[inline(always)]\n    fn read(&mut self, addr: u16) -> u8 {\n        if self.irq_flags(IrqFlags::DMA_HALT) {\n            self.handle_dma(addr);\n        }\n\n        self.start_cycle(self.start_cycles - 1);\n        let val = self.bus.read(addr);\n        self.end_cycle(self.end_cycles + 1);\n        val\n    }\n\n    fn peek(&self, addr: u16) -> u8 {\n        self.bus.peek(addr)\n    }\n}\n\nimpl Write for Cpu {\n    #[inline(always)]\n    fn write(&mut self, addr: u16, val: u8) {\n        self.start_cycle(self.start_cycles + 1);\n        if addr == 0x4014 {\n            self.start_oam_dma(u16::from(val) << 8);\n        } else {\n            self.bus.write(addr, val);\n        }\n        self.end_cycle(self.end_cycles - 1);\n    }\n}\n\nimpl Regional for Cpu {\n    #[inline(always)]\n    fn region(&self) -> NesRegion {\n        self.bus.region\n    }\n\n    fn set_region(&mut self, region: NesRegion) {\n        let (start_cycles, end_cycles) = match region {\n            NesRegion::Auto | NesRegion::Ntsc => (6, 6), // NTSC_MASTER_CLOCK_DIVIDER / 2\n            NesRegion::Pal => (8, 8),                    // PAL_MASTER_CLOCK_DIVIDER / 2\n            NesRegion::Dendy => (7, 8),                  // DENDY_MASTER_CLOCK_DIVIDER / 2\n        };\n        self.start_cycles = start_cycles;\n        self.end_cycles = end_cycles;\n        self.bus.set_region(region);\n        self.clock_sync();\n    }\n}\n\nimpl Reset for Cpu {\n    /// Resets the CPU\n    ///\n    /// Updates the PC, SP, and Status values to defined constants.\n    ///\n    /// These operations take the CPU 7 cycles.\n    fn reset(&mut self, kind: ResetKind) {\n        trace!(\"{:?} RESET\", kind);\n\n        match kind {\n            ResetKind::Soft => {\n                self.status.set(Status::I, true);\n                // Reset pushes to the stack similar to IRQ, but since the read bit is set, nothing is\n                // written except the SP being decremented\n                self.sp = self.sp.wrapping_sub(0x03);\n            }\n            ResetKind::Hard => {\n                self.acc = 0x00;\n                self.x = 0x00;\n                self.y = 0x00;\n                self.status = Self::POWER_ON_STATUS;\n                self.sp = Self::POWER_ON_SP;\n            }\n        }\n\n        self.bus.reset(kind);\n        self.cycle = 0;\n        self.master_clock = 0;\n        self.irq_flags = IrqFlags::default();\n        self.corrupted = false;\n\n        // Read directly from bus so as to not clock other components during reset\n        let lo = self.bus.read(Self::RESET_VECTOR);\n        let hi = self.bus.read(Self::RESET_VECTOR + 1);\n        self.pc = u16::from_le_bytes([lo, hi]);\n\n        // The CPU takes 7 cycles to reset/power on\n        // See:\n        // * <https://www.nesdev.org/wiki/CPU_interrupts>\n        // * <http://archive.6502.org/datasheets/synertek_programming_manual.pdf>\n        for _ in 0..7 {\n            self.start_cycle(self.start_cycles - 1);\n            self.end_cycle(self.start_cycles + 1);\n        }\n    }\n}\n\nimpl fmt::Debug for Cpu {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {\n        f.debug_struct(\"Cpu\")\n            .field(\"cycle\", &self.cycle)\n            .field(\"pc\", &format_args!(\"${:04X}\", self.pc))\n            .field(\"sp\", &format_args!(\"${:02X}\", self.sp))\n            .field(\"acc\", &format_args!(\"${:02X}\", self.acc))\n            .field(\"x\", &format_args!(\"${:02X}\", self.x))\n            .field(\"y\", &format_args!(\"${:02X}\", self.y))\n            .field(\"status\", &self.status)\n            .field(\"bus\", &self.bus)\n            .field(\"interrupt_flags\", &self.irq_flags)\n            .finish()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::{cart::Cart, cpu::instr::Instr::*, mapper::Nrom, mem::Memory};\n\n    #[test]\n    fn cycle_timing() {\n        use super::*;\n        let mut cpu = Cpu::new(Bus::default());\n        let mut cart = Cart::empty();\n        cart.mapper = Nrom::load(\n            &cart,\n            Memory::new(cart.chr_rom_size),\n            Memory::new(cart.prg_rom_size),\n        )\n        .unwrap();\n        cpu.bus.load_cart(cart);\n        cpu.reset(ResetKind::Hard);\n        cpu.clock();\n\n        assert_eq!(cpu.cycle, 14, \"cpu after power + one clock\");\n\n        for instr_ref in Cpu::INSTR_REF.iter() {\n            let extra_cycle = match instr_ref.instr {\n                BCC | BNE | BPL | BVC => 1,\n                _ => 0,\n            };\n            // Ignore invalid opcodes\n            if instr_ref.instr == HLT {\n                continue;\n            }\n            cpu.reset(ResetKind::Hard);\n            cpu.bus.write(0x0000, instr_ref.opcode);\n            cpu.clock();\n            let cpu_cyc = u32::from(7 + instr_ref.cycles + extra_cycle);\n            assert_eq!(\n                cpu.cycle, cpu_cyc,\n                \"cpu ${:02X} {:?} #{:?}\",\n                instr_ref.opcode, instr_ref.instr, instr_ref.addr_mode\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/debug.rs",
    "content": "use crate::ppu::Ppu;\nuse std::sync::Arc;\n\n#[derive(Debug, Clone, PartialEq)]\n#[must_use]\npub enum Debugger {\n    Ppu(PpuDebugger),\n}\n\nimpl From<PpuDebugger> for Debugger {\n    fn from(debugger: PpuDebugger) -> Self {\n        Self::Ppu(debugger)\n    }\n}\n\n#[derive(Clone)]\n#[must_use]\npub struct PpuDebugger {\n    pub cycle: u16,\n    pub scanline: u16,\n    pub callback: Arc<dyn Fn(Ppu) + Send + Sync + 'static>,\n}\n\nimpl Default for PpuDebugger {\n    fn default() -> Self {\n        Self {\n            cycle: u16::MAX,\n            scanline: u16::MAX,\n            callback: Arc::new(|_| {}),\n        }\n    }\n}\n\nimpl PartialEq for PpuDebugger {\n    fn eq(&self, other: &Self) -> bool {\n        self.cycle == other.cycle && self.scanline == other.scanline\n    }\n}\n\nimpl std::fmt::Debug for PpuDebugger {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"PpuDebugger\")\n            .field(\"cycle\", &self.cycle)\n            .field(\"scanline\", &self.scanline)\n            .finish_non_exhaustive()\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/error.rs",
    "content": "//! Error handling.\n\nuse std::path::PathBuf;\nuse thiserror::Error;\n\npub type Result<T> = std::result::Result<T, Error>;\n\n#[derive(Error, Debug)]\n#[must_use]\npub enum Error {\n    #[error(\"invalid save version (expected {expected:?}, found: {found:?})\")]\n    InvalidSaveVersion {\n        expected: &'static str,\n        found: String,\n    },\n    #[error(\"invalid tetanes header (path: {path:?}. {error}\")]\n    InvalidSaveHeader { path: PathBuf, error: String },\n    #[error(\"invalid configuration {value:?} for {field:?}\")]\n    InvalidConfig { field: &'static str, value: String },\n    #[error(\"{context}: {source:?}\")]\n    Io {\n        context: String,\n        source: std::io::Error,\n    },\n    #[error(\"{0}\")]\n    Unknown(String),\n}\n\nimpl Error {\n    pub fn io(source: std::io::Error, context: impl Into<String>) -> Self {\n        Self::Io {\n            context: context.into(),\n            source,\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/fs.rs",
    "content": "//! Filesystem utilities for save state and compression.\n\nuse crate::sys::fs;\nuse flate2::{Compression, read::DeflateDecoder, write::DeflateEncoder};\nuse serde::{Serialize, de::DeserializeOwned};\nuse std::{\n    io::{Cursor, Read, Write},\n    path::{Path, PathBuf},\n};\nuse thiserror::Error;\nuse tracing::warn;\n\nconst SAVE_FILE_MAGIC_LEN: usize = 8;\nconst SAVE_FILE_MAGIC: [u8; SAVE_FILE_MAGIC_LEN] = *b\"TETANES\\x1a\";\n// Keep this separate from Semver because breaking API changes may not invalidate the save format.\nconst SAVE_VERSION: &str = \"1\";\n\npub type Result<T> = std::result::Result<T, Error>;\n\n#[derive(Error, Debug)]\n#[must_use]\npub enum Error {\n    #[error(\"invalid tetanes header: {0}\")]\n    InvalidHeader(String),\n    #[error(\"failed to write tetanes header: {0:?}\")]\n    WriteHeaderFailed(std::io::Error),\n    #[error(\"failed to encode data: {0:?}\")]\n    EncodingFailed(std::io::Error),\n    #[error(\"failed to decode data: {0:?}\")]\n    DecodingFailed(std::io::Error),\n    #[error(\"failed to serialize data: {0:?}\")]\n    SerializationFailed(String),\n    #[error(\"failed to deserialize data: {0:?}\")]\n    DeserializationFailed(String),\n    #[error(\"invalid path: {0:?}\")]\n    InvalidPath(PathBuf),\n    #[error(\"{context}: {source:?}\")]\n    Io {\n        source: std::io::Error,\n        context: String,\n    },\n    #[error(\"{0}\")]\n    Custom(String),\n}\n\nimpl Error {\n    pub fn io(source: std::io::Error, context: impl Into<String>) -> Self {\n        Self::Io {\n            source,\n            context: context.into(),\n        }\n    }\n\n    pub fn custom(error: impl Into<String>) -> Self {\n        Self::Custom(error.into())\n    }\n}\n\n/// Writes a header including a magic string and a version\n///\n/// # Errors\n///\n/// If the header fails to write to disk, then an error is returned.\npub(crate) fn write_header(f: &mut impl Write) -> std::io::Result<()> {\n    f.write_all(&SAVE_FILE_MAGIC)?;\n    f.write_all(SAVE_VERSION.as_bytes())\n}\n\n/// Verifies a `TetaNES` saved state header.\n///\n/// # Errors\n///\n/// If the header fails to validate, then an error is returned.\npub(crate) fn validate_header(f: &mut impl Read) -> Result<()> {\n    let mut magic = [0u8; SAVE_FILE_MAGIC_LEN];\n    f.read_exact(&mut magic)\n        .map_err(|s| Error::InvalidHeader(s.to_string()))?;\n    if magic != SAVE_FILE_MAGIC {\n        return Err(Error::InvalidHeader(format!(\n            \"invalid magic (expected {SAVE_FILE_MAGIC:?}, found: {magic:?}\",\n        )));\n    }\n\n    let mut version = [0u8];\n    f.read_exact(&mut version)\n        .map_err(|s| Error::InvalidHeader(s.to_string()))?;\n    if version == SAVE_VERSION.as_bytes() {\n        Ok(())\n    } else {\n        Err(Error::InvalidHeader(format!(\n            \"invalid version (expected {SAVE_VERSION:?}, found: {version:?}\",\n        )))\n    }\n}\n\npub fn encode(mut writer: &mut impl Write, data: &[u8]) -> std::io::Result<()> {\n    let mut encoder = DeflateEncoder::new(&mut writer, Compression::default());\n    encoder.write_all(data)?;\n    encoder.finish()?;\n    Ok(())\n}\n\npub fn decode(data: impl Read) -> std::io::Result<Vec<u8>> {\n    let mut decoded = vec![];\n    let mut decoder = DeflateDecoder::new(data);\n    decoder.read_to_end(&mut decoded)?;\n    Ok(decoded)\n}\n\npub fn save<T>(path: impl AsRef<Path>, value: &T) -> Result<()>\nwhere\n    T: ?Sized + Serialize,\n{\n    let config = bincode::config::legacy();\n    let data = bincode::serde::encode_to_vec(value, config)\n        .map_err(|err| Error::SerializationFailed(err.to_string()))?;\n    let mut writer = fs::writer_impl(path)?;\n    write_header(&mut writer).map_err(Error::WriteHeaderFailed)?;\n    encode(&mut writer, &data).map_err(Error::EncodingFailed)?;\n    writer\n        .flush()\n        .map_err(|err| Error::io(err, \"failed to save data\"))?;\n    Ok(())\n}\n\npub fn save_raw(path: impl AsRef<Path>, value: &[u8]) -> Result<()> {\n    let mut writer = fs::writer_impl(path)?;\n    writer\n        .write_all(value)\n        .map_err(|err| Error::io(err, \"failed to save data\"))?;\n    writer\n        .flush()\n        .map_err(|err| Error::io(err, \"failed to save data\"))?;\n    Ok(())\n}\n\npub fn load<T>(path: impl AsRef<Path>) -> Result<T>\nwhere\n    T: DeserializeOwned,\n{\n    let mut reader = fs::reader_impl(path)?;\n    validate_header(&mut reader)?;\n    let data = decode(&mut reader).map_err(Error::DecodingFailed)?;\n    let config = bincode::config::legacy();\n    let (res, _) = bincode::serde::decode_from_slice(&data, config)\n        .map_err(|err| Error::DeserializationFailed(err.to_string()))?;\n    Ok(res)\n}\n\npub fn load_bytes<T>(bytes: &[u8]) -> Result<T>\nwhere\n    T: DeserializeOwned,\n{\n    let mut reader = Cursor::new(bytes);\n    validate_header(&mut reader)?;\n    let data = decode(&mut reader).map_err(Error::DecodingFailed)?;\n    let config = bincode::config::legacy();\n    let (res, _) = bincode::serde::decode_from_slice(&data, config)\n        .map_err(|err| Error::SerializationFailed(err.to_string()))?;\n    Ok(res)\n}\n\npub fn load_raw(path: impl AsRef<Path>) -> Result<Vec<u8>> {\n    let mut reader = fs::reader_impl(path)?;\n    let mut data = vec![];\n    reader\n        .read_to_end(&mut data)\n        .map_err(|err| Error::io(err, \"failed to load data\"))?;\n    Ok(data)\n}\n\npub fn clear_dir(path: impl AsRef<Path>) -> Result<()> {\n    fs::clear_dir_impl(path)\n}\n\npub fn exists(path: &Path) -> bool {\n    fs::exists_impl(path)\n}\n\npub fn filename(path: &Path) -> &str {\n    path.file_name()\n        .and_then(std::ffi::OsStr::to_str)\n        .unwrap_or_else(|| {\n            warn!(\"invalid path without file_name: {path:?}\");\n            \"??\"\n        })\n}\n\npub fn compute_crc32(data: &[u8]) -> u32 {\n    compute_combine_crc32(0, data)\n}\n\npub fn compute_combine_crc32(crc32: u32, data: &[u8]) -> u32 {\n    const BUFFER_SIZE: usize = 0x2000;\n    data.chunks(BUFFER_SIZE).fold(crc32, compute_crc32_buffer)\n}\n\nfn compute_crc32_buffer(crc32: u32, buffer: &[u8]) -> u32 {\n    buffer.iter().fold(crc32 ^ 0xFFFFFFFF, |crc32, byte| {\n        (crc32 >> 8) ^ CRC_TABLE[((crc32 ^ *byte as u32) & 0xFF) as usize]\n    }) ^ 0xFFFFFFFF\n}\n\nconst CRC_TABLE: [u32; 256] = [\n    0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,\n    0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,\n    0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,\n    0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,\n    0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,\n    0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,\n    0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,\n    0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,\n    0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,\n    0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,\n    0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,\n    0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,\n    0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,\n    0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,\n    0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,\n    0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,\n    0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,\n    0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,\n    0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,\n    0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,\n    0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,\n    0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,\n    0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,\n    0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,\n    0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,\n    0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,\n    0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,\n    0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,\n    0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,\n    0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,\n    0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,\n    0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,\n];\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn save_header() {\n        let mut file = Vec::new();\n        assert!(write_header(&mut file).is_ok(), \"write header\");\n        assert!(\n            validate_header(&mut file.as_slice()).is_ok(),\n            \"validate header\"\n        );\n    }\n\n    #[test]\n    fn crc32() {\n        let s = \"Lorem ipsum dolor sit amet, consectetur adipisicing elit\";\n        assert_eq!(compute_crc32(s.as_bytes()), 0xb9b4cbd5);\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/genie.rs",
    "content": "//! Game Genie code parsing.\n\nuse serde::{Deserialize, Serialize};\nuse std::{collections::HashMap, sync::OnceLock};\nuse thiserror::Error;\n\nstatic GENIE_MAP: OnceLock<HashMap<char, u8>> = OnceLock::new();\n\npub type Result<T> = std::result::Result<T, Error>;\n\n#[derive(Error, Debug)]\n#[error(\"invalid genie code {code:?}. {kind}\")]\npub struct Error {\n    code: String,\n    kind: ErrorKind,\n}\n\nimpl Error {\n    fn new(code: impl Into<String>, kind: ErrorKind) -> Self {\n        Self {\n            code: code.into(),\n            kind,\n        }\n    }\n\n    pub const fn kind(&self) -> ErrorKind {\n        self.kind\n    }\n}\n\n#[derive(Error, Debug, Copy, Clone)]\n#[must_use]\npub enum ErrorKind {\n    #[error(\"length must be 6 or 8 characters. found `{0}`\")]\n    InvalidLength(usize),\n    #[error(\"invalid character: `{0}`\")]\n    InvalidCharacter(char),\n}\n\n/// Game Genie Code\n#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub struct GenieCode {\n    code: String,\n    addr: u16,\n    data: u8,\n    compare: Option<u8>,\n}\n\nimpl GenieCode {\n    /// Creates a new `GenieCode` instance.\n    ///\n    /// # Errors\n    ///\n    /// This function will return an error if the given code is not the correct format.\n    pub fn new(code: String) -> Result<Self> {\n        let hex = Self::parse(&code)?;\n        Ok(Self::from_raw(code, &hex))\n    }\n\n    /// Creates a new `GenieCode` instance from raw hex values. `GenieCode` may not be valid if\n    /// `hex` is not the correct length. Use `GenieCode::parse` to validate the code.\n    pub fn from_raw(code: String, hex: &[u8]) -> Self {\n        let addr = 0x8000\n            + (((u16::from(hex[3]) & 7) << 12)\n                | ((u16::from(hex[5]) & 7) << 8)\n                | ((u16::from(hex[4]) & 8) << 8)\n                | ((u16::from(hex[2]) & 7) << 4)\n                | ((u16::from(hex[1]) & 8) << 4)\n                | (u16::from(hex[4]) & 7)\n                | (u16::from(hex[3]) & 8));\n        let data = if hex.len() == 6 {\n            ((hex[1] & 7) << 4) | ((hex[0] & 8) << 4) | (hex[0] & 7) | (hex[5] & 8)\n        } else {\n            ((hex[1] & 7) << 4) | ((hex[0] & 8) << 4) | (hex[0] & 7) | (hex[7] & 8)\n        };\n        let compare = if hex.len() == 8 {\n            Some(((hex[7] & 7) << 4) | ((hex[6] & 8) << 4) | (hex[6] & 7) | (hex[5] & 8))\n        } else {\n            None\n        };\n        Self {\n            code: code.to_ascii_uppercase(),\n            addr,\n            data,\n            compare,\n        }\n    }\n\n    fn generate_genie_map() -> HashMap<char, u8> {\n        // Game genie maps these letters to binary representations as a form of code obfuscation\n        HashMap::from([\n            ('A', 0x0),\n            ('P', 0x1),\n            ('Z', 0x2),\n            ('L', 0x3),\n            ('G', 0x4),\n            ('I', 0x5),\n            ('T', 0x6),\n            ('Y', 0x7),\n            ('E', 0x8),\n            ('O', 0x9),\n            ('X', 0xA),\n            ('U', 0xB),\n            ('K', 0xC),\n            ('S', 0xD),\n            ('V', 0xE),\n            ('N', 0xF),\n        ])\n    }\n\n    pub fn parse(code: &str) -> Result<Box<[u8]>> {\n        if code.len() != 6 && code.len() != 8 {\n            return Err(Error::new(code, ErrorKind::InvalidLength(code.len())));\n        }\n        let mut hex = Vec::with_capacity(code.len());\n        for s in code.chars() {\n            if let Some(h) = GENIE_MAP\n                .get_or_init(Self::generate_genie_map)\n                .get(&s.to_ascii_uppercase())\n            {\n                hex.push(*h);\n            } else {\n                return Err(Error::new(code, ErrorKind::InvalidCharacter(s)));\n            }\n        }\n        Ok(hex.into())\n    }\n\n    #[must_use]\n    #[allow(clippy::missing_const_for_fn)] // false positive on non-const deref coercion\n    pub fn code(&self) -> &str {\n        &self.code\n    }\n\n    #[must_use]\n    pub const fn addr(&self) -> u16 {\n        self.addr\n    }\n\n    #[must_use]\n    pub const fn read(&self, val: u8) -> u8 {\n        if let Some(compare) = self.compare {\n            if val == compare { self.data } else { val }\n        } else {\n            self.data\n        }\n    }\n}\n\nimpl std::fmt::Display for GenieCode {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", &self.code)\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/input.rs",
    "content": "//! [`Joypad`] and [`Zapper`] implementation.\n\nuse crate::{\n    common::{Clock, NesRegion, Reset, ResetKind},\n    cpu::Cpu,\n    ppu::{Ppu, size},\n};\nuse bitflags::bitflags;\nuse serde::{Deserialize, Serialize};\nuse std::str::FromStr;\nuse thiserror::Error;\nuse tracing::trace;\n\n#[derive(Error, Debug)]\n#[must_use]\n#[error(\"failed to parse `Player`\")]\npub struct ParsePlayerError;\n\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum Player {\n    #[default]\n    One,\n    Two,\n    Three,\n    Four,\n}\n\nimpl std::fmt::Display for Player {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let s = match self {\n            Self::One => \"One\",\n            Self::Two => \"Two\",\n            Self::Three => \"Three\",\n            Self::Four => \"Four\",\n        };\n        write!(f, \"{s}\")\n    }\n}\n\nimpl AsRef<str> for Player {\n    fn as_ref(&self) -> &str {\n        match self {\n            Self::One => \"one\",\n            Self::Two => \"two\",\n            Self::Three => \"three\",\n            Self::Four => \"four\",\n        }\n    }\n}\n\nimpl TryFrom<usize> for Player {\n    type Error = ParsePlayerError;\n\n    fn try_from(value: usize) -> Result<Self, Self::Error> {\n        match value {\n            0 => Ok(Self::One),\n            1 => Ok(Self::Two),\n            2 => Ok(Self::Three),\n            3 => Ok(Self::Four),\n            _ => Err(ParsePlayerError),\n        }\n    }\n}\n\npub trait InputRegisters {\n    fn read(&mut self, player: Player, ppu: &Ppu) -> u8;\n    fn peek(&self, player: Player, ppu: &Ppu) -> u8;\n    fn write(&mut self, val: u8);\n}\n\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum FourPlayer {\n    #[default]\n    Disabled,\n    FourScore,\n    Satellite,\n}\n\nimpl FourPlayer {\n    pub const fn as_slice() -> &'static [Self] {\n        &[Self::Disabled, Self::FourScore, Self::Satellite]\n    }\n\n    pub const fn as_str(&self) -> &'static str {\n        match self {\n            Self::Disabled => \"disabled\",\n            Self::FourScore => \"four-score\",\n            Self::Satellite => \"satellite\",\n        }\n    }\n}\n\nimpl AsRef<str> for FourPlayer {\n    fn as_ref(&self) -> &str {\n        self.as_str()\n    }\n}\n\nimpl std::fmt::Display for FourPlayer {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let s = match self {\n            Self::Disabled => \"Disabled\",\n            Self::FourScore => \"FourScore\",\n            Self::Satellite => \"Satellite\",\n        };\n        write!(f, \"{s}\")\n    }\n}\n\nimpl FromStr for FourPlayer {\n    type Err = &'static str;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"disabled\" => Ok(Self::Disabled),\n            \"four-score\" => Ok(Self::FourScore),\n            \"satellite\" => Ok(Self::Satellite),\n            _ => Err(\n                \"invalid FourPlayer value. valid options: `disabled`, `four-score`, or `satellite`\",\n            ),\n        }\n    }\n}\n\n#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Input {\n    pub joypads: [Joypad; 4],\n    pub signatures: [Joypad; 2],\n    pub zapper: Zapper,\n    pub turbo_timer: u32,\n    pub four_player: FourPlayer,\n}\n\nimpl Input {\n    pub fn new(region: NesRegion) -> Self {\n        Self {\n            joypads: [Joypad::new(); 4],\n            // Signature bits are reversed so they can shift right\n            signatures: [\n                Joypad::from_bytes(0b0000_1000),\n                Joypad::from_bytes(0b0000_0100),\n            ],\n            zapper: Zapper::new(region),\n            turbo_timer: 30,\n            four_player: FourPlayer::default(),\n        }\n    }\n\n    pub const fn joypad(&self, player: Player) -> &Joypad {\n        &self.joypads[player as usize]\n    }\n\n    pub const fn joypad_mut(&mut self, player: Player) -> &mut Joypad {\n        &mut self.joypads[player as usize]\n    }\n\n    pub fn set_region(&mut self, region: NesRegion) {\n        self.zapper.trigger_release_delay = Cpu::region_clock_rate(region) / 10.0;\n    }\n\n    pub fn set_concurrent_dpad(&mut self, enabled: bool) {\n        self.joypads\n            .iter_mut()\n            .for_each(|pad| pad.concurrent_dpad = enabled);\n    }\n\n    pub const fn connect_zapper(&mut self, connected: bool) {\n        self.zapper.connected = connected;\n    }\n\n    pub fn set_four_player(&mut self, four_player: FourPlayer) {\n        self.four_player = four_player;\n        self.reset(ResetKind::Hard);\n    }\n\n    pub fn clear(&mut self) {\n        for pad in &mut self.joypads {\n            pad.clear();\n        }\n        self.zapper.clear();\n    }\n}\n\nimpl InputRegisters for Input {\n    fn read(&mut self, player: Player, ppu: &Ppu) -> u8 {\n        // Read $4016/$4017 D0 8x for controller #1/#2.\n        // Read $4016/$4017 D0 8x for controller #3/#4.\n        // Read $4016/$4017 D0 8x for signature: 0b00010000/0b00100000\n        let zapper = if player == Player::Two {\n            self.zapper.read(ppu)\n        } else {\n            0x00\n        };\n\n        let player = player as usize;\n        assert!(player < 4);\n        let val = match self.four_player {\n            FourPlayer::Disabled => self.joypads[player].read(),\n            FourPlayer::FourScore => {\n                if self.joypads[player].index() < 8 {\n                    self.joypads[player].read()\n                } else if self.joypads[player + 2].index() < 8 {\n                    self.joypads[player + 2].read()\n                } else if self.signatures[player].index() < 8 {\n                    self.signatures[player].read()\n                } else {\n                    0x01\n                }\n            }\n            FourPlayer::Satellite => {\n                self.joypads[player].read() | (self.joypads[player + 2].read() << 1)\n            }\n        };\n\n        zapper | val | 0x40\n    }\n\n    fn peek(&self, player: Player, ppu: &Ppu) -> u8 {\n        // Read $4016/$4017 D0 8x for controller #1/#2.\n        // Read $4016/$4017 D0 8x for controller #3/#4.\n        // Read $4016/$4017 D0 8x for signature: 0b00010000/0b00100000\n        let zapper = if player == Player::Two {\n            self.zapper.read(ppu)\n        } else {\n            0x00\n        };\n\n        let player = player as usize;\n        assert!(player < 4);\n        let val = match self.four_player {\n            FourPlayer::Disabled => self.joypads[player].peek(),\n            FourPlayer::FourScore => {\n                if self.joypads[player].index() < 8 {\n                    self.joypads[player].peek()\n                } else if self.joypads[player + 2].index() < 8 {\n                    self.joypads[player + 2].peek()\n                } else if self.signatures[player].index() < 8 {\n                    self.signatures[player].peek()\n                } else {\n                    0x01\n                }\n            }\n            FourPlayer::Satellite => {\n                self.joypads[player].peek() | (self.joypads[player + 2].peek() << 1)\n            }\n        };\n\n        zapper | val | 0x40\n    }\n\n    fn write(&mut self, val: u8) {\n        for pad in &mut self.joypads {\n            pad.write(val);\n        }\n        for sig in &mut self.signatures {\n            sig.write(val);\n        }\n    }\n}\n\nimpl Clock for Input {\n    fn clock(&mut self) {\n        self.zapper.clock();\n        if self.turbo_timer > 0 {\n            self.turbo_timer -= 1;\n        }\n        if self.turbo_timer == 0 {\n            // Roughly 20Hz\n            self.turbo_timer += 89500;\n            for pad in &mut self.joypads {\n                if pad.button(JoypadBtnState::TURBO_A) {\n                    let pressed = pad.button(JoypadBtnState::A);\n                    pad.set_button(JoypadBtnState::A, !pressed);\n                }\n                if pad.button(JoypadBtnState::TURBO_B) {\n                    let pressed = pad.button(JoypadBtnState::B);\n                    pad.set_button(JoypadBtnState::B, !pressed);\n                }\n            }\n        }\n    }\n}\n\nimpl Reset for Input {\n    fn reset(&mut self, kind: ResetKind) {\n        for pad in &mut self.joypads {\n            pad.reset(kind);\n        }\n        self.signatures[0] = Joypad::from_bytes(0b0000_1000);\n        self.signatures[1] = Joypad::from_bytes(0b0000_0100);\n        self.zapper.reset(kind);\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum JoypadBtn {\n    /// Left D-Pad.\n    Left,\n    /// Right D-Pad.\n    Right,\n    /// Up D-Pad.\n    Up,\n    /// Down D-Pad.\n    Down,\n    /// A Button.\n    A,\n    /// B Button.\n    B,\n    /// A Button (Turbo).\n    TurboA,\n    /// B Button (Turbo).\n    TurboB,\n    /// Select Button.\n    Select,\n    /// Start Button.\n    Start,\n}\n\nimpl AsRef<str> for JoypadBtn {\n    fn as_ref(&self) -> &str {\n        match *self {\n            JoypadBtn::A => \"A\",\n            JoypadBtn::B => \"B\",\n            JoypadBtn::Select => \"Select\",\n            JoypadBtn::Start => \"Start\",\n            JoypadBtn::Up => \"Up\",\n            JoypadBtn::Down => \"Down\",\n            JoypadBtn::Left => \"Left\",\n            JoypadBtn::Right => \"Right\",\n            JoypadBtn::TurboA => \"A (Turbo)\",\n            JoypadBtn::TurboB => \"B (Turbo)\",\n        }\n    }\n}\n\nbitflags! {\n    #[derive(Default, Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)]\n    #[must_use]\n    pub struct JoypadBtnState: u16 {\n        const A = 0x01;\n        const B = 0x02;\n        const SELECT = 0x04;\n        const START = 0x08;\n        const UP = 0x10;\n        const DOWN = 0x20;\n        const LEFT = 0x40;\n        const RIGHT = 0x80;\n        const TURBO_A = 0x100;\n        const TURBO_B = 0x200;\n    }\n}\n\nimpl From<JoypadBtn> for JoypadBtnState {\n    fn from(button: JoypadBtn) -> Self {\n        match button {\n            JoypadBtn::A => Self::A,\n            JoypadBtn::B => Self::B,\n            JoypadBtn::Select => Self::SELECT,\n            JoypadBtn::Start => Self::START,\n            JoypadBtn::Up => Self::UP,\n            JoypadBtn::Down => Self::DOWN,\n            JoypadBtn::Left => Self::LEFT,\n            JoypadBtn::Right => Self::RIGHT,\n            JoypadBtn::TurboA => Self::TURBO_A,\n            JoypadBtn::TurboB => Self::TURBO_B,\n        }\n    }\n}\n\n#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Joypad {\n    pub buttons: JoypadBtnState,\n    pub concurrent_dpad: bool,\n    pub index: u8,\n    pub strobe: bool,\n}\n\nimpl Joypad {\n    pub const fn new() -> Self {\n        Self {\n            buttons: JoypadBtnState::empty(),\n            concurrent_dpad: false,\n            index: 0,\n            strobe: false,\n        }\n    }\n\n    #[must_use]\n    pub const fn button(&self, button: JoypadBtnState) -> bool {\n        self.buttons.contains(button)\n    }\n\n    pub fn set_button(&mut self, button: impl Into<JoypadBtnState>, pressed: bool) {\n        let button = button.into();\n        let prevent_concurrent_dpad = pressed && !self.concurrent_dpad;\n        if let Some(button) = match button {\n            JoypadBtnState::LEFT if prevent_concurrent_dpad => Some(JoypadBtnState::RIGHT),\n            JoypadBtnState::RIGHT if prevent_concurrent_dpad => Some(JoypadBtnState::LEFT),\n            JoypadBtnState::UP if prevent_concurrent_dpad => Some(JoypadBtnState::DOWN),\n            JoypadBtnState::DOWN if prevent_concurrent_dpad => Some(JoypadBtnState::UP),\n            JoypadBtnState::TURBO_A if !pressed => Some(JoypadBtnState::A),\n            JoypadBtnState::TURBO_B if !pressed => Some(JoypadBtnState::B),\n            _ => None,\n        } {\n            self.buttons.set(button, false);\n        }\n        self.buttons.set(button, pressed);\n    }\n\n    pub const fn from_bytes(val: u16) -> Self {\n        Self {\n            buttons: JoypadBtnState::from_bits_truncate(val),\n            concurrent_dpad: false,\n            index: 0,\n            strobe: false,\n        }\n    }\n\n    #[must_use]\n    pub const fn read(&mut self) -> u8 {\n        let val = self.peek();\n        if !self.strobe && self.index < 8 {\n            self.index += 1;\n        }\n        val\n    }\n\n    #[must_use]\n    pub const fn peek(&self) -> u8 {\n        if self.index < 8 {\n            ((self.buttons.bits() as u8) & (1 << self.index)) >> self.index\n        } else {\n            0x01\n        }\n    }\n\n    pub const fn write(&mut self, val: u8) {\n        let prev_strobe = self.strobe;\n        self.strobe = val & 0x01 == 0x01;\n        if prev_strobe && !self.strobe {\n            self.index = 0;\n        }\n    }\n\n    #[must_use]\n    pub const fn index(&self) -> u8 {\n        self.index\n    }\n\n    pub const fn clear(&mut self) {\n        self.buttons = JoypadBtnState::empty();\n    }\n}\n\nimpl Reset for Joypad {\n    fn reset(&mut self, _kind: ResetKind) {\n        self.buttons = JoypadBtnState::empty();\n        self.index = 0;\n        self.strobe = false;\n    }\n}\n\n#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Zapper {\n    #[serde(skip)] // Don't save triggered state\n    pub triggered: f32,\n    pub trigger_release_delay: f32,\n    #[serde(skip)] // Don't save zapper position\n    pub x: u16,\n    #[serde(skip)] // Don't save zapper position\n    pub y: u16,\n    pub radius: u16,\n    pub connected: bool,\n}\n\nimpl Zapper {\n    #[inline(always)]\n    #[must_use]\n    pub const fn x(&self) -> u16 {\n        self.x\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub const fn y(&self) -> u16 {\n        self.y\n    }\n\n    #[inline(always)]\n    pub fn trigger(&mut self) {\n        if self.triggered <= 0.0 {\n            self.triggered = self.trigger_release_delay;\n        }\n    }\n\n    #[inline(always)]\n    pub const fn aim(&mut self, x: u16, y: u16) {\n        self.x = x;\n        self.y = y;\n    }\n\n    pub const fn clear(&mut self) {\n        self.triggered = 0.0;\n    }\n}\n\nimpl Zapper {\n    fn new(region: NesRegion) -> Self {\n        Self {\n            triggered: 0.0,\n            // Zapper takes ~100ms to change to \"released\" after trigger is pulled\n            trigger_release_delay: Cpu::region_clock_rate(region) / 10.0,\n            x: 0,\n            y: 0,\n            radius: 3,\n            connected: false,\n        }\n    }\n\n    #[must_use]\n    fn read(&self, ppu: &Ppu) -> u8 {\n        if self.connected {\n            self.triggered() | self.light_sense(ppu)\n        } else {\n            0x00\n        }\n    }\n\n    fn triggered(&self) -> u8 {\n        if self.triggered > 0.0 { 0x10 } else { 0x00 }\n    }\n\n    fn light_sense(&self, ppu: &Ppu) -> u8 {\n        let width = size::WIDTH;\n        let height = size::HEIGHT;\n        let scanline = ppu.scanline;\n        let cycle = ppu.cycle;\n        let min_y = self.y.saturating_sub(self.radius);\n        let max_y = (self.y + self.radius).min(height - 1);\n        let min_x = self.x.saturating_sub(self.radius);\n        let max_x = (self.x + self.radius).min(width - 1);\n        for y in min_y..=max_y {\n            for x in min_x..=max_x {\n                let behind_ppu =\n                    scanline >= y && (scanline - y) <= 20 && (scanline != y || cycle > x);\n                let brightness = ppu.pixel_brightness(x, y);\n                if behind_ppu && brightness >= 85 {\n                    trace!(\"zapper light: {brightness}\");\n                    return 0x00;\n                }\n            }\n        }\n        0x08\n    }\n}\n\nimpl Clock for Zapper {\n    fn clock(&mut self) {\n        if self.triggered > 0.0 {\n            self.triggered -= 1.0;\n        }\n    }\n}\n\nimpl Reset for Zapper {\n    fn reset(&mut self, _kind: ResetKind) {\n        self.triggered = 0.0;\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/lib.rs",
    "content": "#![doc = include_str!(\"../README.md\")]\n#![doc(\n    html_favicon_url = \"https://github.com/lukexor/tetanes/blob/main/assets/linux/icon.png?raw=true\",\n    html_logo_url = \"https://github.com/lukexor/tetanes/blob/main/assets/linux/icon.png?raw=true\"\n)]\n#![cfg_attr(docsrs, feature(doc_auto_cfg))]\n\npub mod action;\npub mod apu;\npub mod bus;\npub mod cart;\npub mod debug;\npub mod fs;\npub mod time;\n#[macro_use]\npub mod common;\npub mod control_deck;\npub mod cpu;\npub mod error;\npub mod genie;\npub mod input;\npub mod mapper;\npub mod mem;\npub mod ppu;\npub mod sys;\npub mod video;\n\npub mod prelude {\n    //! The prelude re-exports all the common structs/enums used for basic NES emulation.\n\n    pub use crate::{\n        action::Action,\n        apu::{Apu, Channel},\n        cart::Cart,\n        common::{Clock, NesRegion, Regional, Reset, ResetKind, Sample},\n        control_deck::{Config, ControlDeck, HeadlessMode},\n        cpu::Cpu,\n        genie::GenieCode,\n        input::{FourPlayer, Input, Player},\n        mapper::{Map, Mapper, MapperRevision},\n        mem::RamState,\n        ppu::{Mirroring, Ppu},\n        video::Frame,\n    };\n}\n\n#[cfg(test)]\nmod tests {\n    use super::prelude::*;\n    use crate::{\n        apu::{\n            dmc::Dmc, filter::FilterChain, frame_counter::FrameCounter, noise::Noise, pulse::Pulse,\n            triangle::Triangle,\n        },\n        bus::{self, Bus},\n        cpu::{IrqFlags, Status, instr::AddrMode},\n        debug::PpuDebugger,\n        mapper::{\n            Axrom, BandaiFCG, Bf909x, Bnrom, Cnrom, ColorDreams, Exrom, Fxrom, Gxrom,\n            JalecoSs88006, Namco163, Nina001, Nina003006, Nrom, Pxrom, SunsoftFme7, Sxrom, Txrom,\n            Uxrom, Vrc6,\n        },\n        mem::{ConstArray, Memory},\n        ppu::{\n            CIRam, PaletteRam, ctrl::Ctrl, mask::Mask, scroll::Scroll, sprite::Sprite,\n            status::Status as PpuStatus,\n        },\n    };\n    use std::collections::HashMap;\n\n    /// Utility to aid in struct field layout size and alignment.\n    macro_rules! print_struct_layout {\n        ($ty:ty, $($field:ident: $field_ty:ty),+$(,)?) => {{\n            use ::std::mem::{offset_of, size_of};\n            let mut field_rows = vec![\n                $(\n                    (\n                        stringify!($field),\n                        offset_of!($ty, $field),\n                        size_of::<$field_ty>()\n                    ),\n                )+\n            ];\n            field_rows.sort_by_key(|&(_, offset, _)| offset);\n\n            println!(\"{} total size: {} bytes\", stringify!($ty), size_of::<$ty>());\n            for (field, offset, size) in field_rows {\n                println!(\"  {field:<25}: offset {offset:4}, size {size:4}\");\n            }\n        }};\n    }\n\n    /// Utility to aid in enum size and alignment.\n    macro_rules! print_enum_layout {\n        ($ty:ty, $($variant:ident($variant_ty:ty)),+$(,)?) => {{\n            println!(\"{} enum: {} bytes\", stringify!($ty), size_of::<$ty>());\n                $(\n                    println!(\"  {:<15}: size {:4}\", stringify!($variant), size_of::<$variant_ty>());\n                )+\n        }}\n    }\n\n    // Utility to help print alignment and size of struct field for cache-optimization.\n    #[test]\n    fn print_layouts() {\n        print_struct_layout!(\n            Cpu,\n            cycle: u32,\n            master_clock: u32,\n            start_cycles: u8,\n            end_cycles: u8,\n            pc: u16,\n            operand: u16,\n            addr_mode: AddrMode,\n            sp: u8,\n            acc: u8,\n            x: u8,\n            y: u8,\n            status: Status,\n            irq_flags: IrqFlags,\n            bus: Bus,\n            corrupted: bool,\n            disasm: String,\n        );\n\n        print_struct_layout!(\n            Bus,\n            wram: Memory<ConstArray<u8, { bus::size::WRAM }>>,\n            open_bus: u8,\n            ram_state: RamState,\n            region: NesRegion,\n            ppu: Ppu,\n            apu: Apu,\n            input: Input,\n            genie_codes: HashMap<u16, GenieCode>,\n        );\n\n        print_struct_layout!(\n            Ppu,\n            master_clock: u32,\n            cycle: u16,\n            scanline: u16,\n            mask: Mask,\n            ctrl: Ctrl,\n            scroll: Scroll,\n            tile_shift_lo: u16,\n            tile_shift_hi: u16,\n            tile_addr: u16,\n            tile_lo: u8,\n            tile_hi: u8,\n            clock_divider: u8,\n            open_bus: u8,\n            reset_signal: bool,\n\n            curr_palette: u8,\n            prev_palette: u8,\n            next_palette: u8,\n            skip_rendering: bool,\n\n            spr_count: u8,\n            spr_in_range: bool,\n            spr_zero_in_range: bool,\n            spr_zero_visible: bool,\n            oam_eval_done: bool,\n            oamaddr: u8,\n            oamaddr_lo: u8,\n            oamaddr_hi: u8,\n            secondary_oamaddr: u8,\n            overflow_count: u8,\n            oam_fetch: u8,\n\n            vblank_scanline: u16,\n            prerender_scanline: u16,\n            is_visible_scanline: bool,\n            is_prerender_scanline: bool,\n            is_render_scanline: bool,\n            is_pal_spr_eval_scanline: bool,\n\n            status: PpuStatus,\n\n            frame: Frame,\n            ciram: CIRam,\n\n            secondary_oamdata: ConstArray<u8, 32>,\n            sprites: Box<[Sprite]>,\n            spr_present: ConstArray<bool, 256>,\n            oamdata: ConstArray<u8, 256>,\n\n            palette: PaletteRam,\n            mapper: Mapper,\n\n            vram_buffer: u8,\n            prevent_vbl: bool,\n            region: NesRegion,\n            emulate_warmup: bool,\n\n            debugger: PpuDebugger,\n\n        );\n\n        print_struct_layout!(\n            Apu,\n            master_clock: u32,\n            clock: u32,\n            cpu_cycle: u32,\n            should_clock: bool,\n            sample_counter: f32,\n            sample_period: f32,\n            frame_counter: FrameCounter,\n            pulse1: Pulse,\n            pulse2: Pulse,\n            triangle: Triangle,\n            noise: Noise,\n            dmc: Dmc,\n            filter_chain: FilterChain,\n            audio_samples: Vec<f32>,\n            channel_outputs: Box<[f32]>,\n            clock_rate: f32,\n            sample_rate: f32,\n            speed: f32,\n            mapper_enabled: bool,\n            region: NesRegion,\n            skip_mixing: bool,\n        );\n\n        print_enum_layout!(\n            Mapper,\n            Nrom(Nrom),\n            Sxrom(Sxrom),\n            Uxrom(Uxrom),\n            Cnrom(Cnrom),\n            Txrom(Txrom),\n            Exrom(Exrom),\n            Axrom(Axrom),\n            Pxrom(Pxrom),\n            Fxrom(Fxrom),\n            ColorDreams(ColorDreams),\n            BandaiFCG(BandaiFCG),\n            JalecoSs88006(JalecoSs88006),\n            Namco163(Namco163),\n            Vrc6(Vrc6),\n            Bnrom(Bnrom),\n            Nina001(Nina001),\n            Gxrom(Gxrom),\n            SunsoftFme7(SunsoftFme7),\n            Bf909x(Bf909x),\n            Nina003006(Nina003006),\n        );\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/mapper/bandai_fcg.rs",
    "content": "//! `Bandai FCG` (Mappers 016, 153, 157, and 159).\n//!\n//! <https://www.nesdev.org/wiki/INES_Mapper_016>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, Sram},\n    fs,\n    mapper::{self, Map, Mapper, Mirroring},\n    mem::{Banks, Memory},\n    ppu::CIRam,\n};\nuse serde::{Deserialize, Serialize};\nuse std::{cmp::Ordering, path::Path};\n\n/// `Bandai FCG` registers.\n#[derive(Default, Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Regs {\n    pub prg_page: u8,\n    pub prg_bank_select: u8,\n    pub prg_ram_enabled: bool,\n    pub chr_regs: [u8; 8],\n    pub irq_latch: u8,\n    pub irq_counter: u16,\n    pub irq_enabled: bool,\n    pub irq_pending: bool,\n    pub irq_reload: u16,\n}\n\n/// Memory operation.\n#[derive(Default, Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub enum MemoryOp {\n    None,\n    Read,\n    Write,\n    #[default]\n    ReadWrite,\n}\n\n/// `Bandai FCG` (Mappers 016, 153, 157, and 159).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct BandaiFCG {\n    pub chr: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub prg_ram: Memory<Box<[u8]>>,\n    pub regs: Regs,\n    pub has_chr_ram: bool,\n    pub mirroring: Mirroring,\n    pub mapper_num: u16,\n    pub submapper_num: u8,\n    pub barcode_reader: Option<BarcodeReader>,\n    pub standard_eeprom: Option<Eeprom>,\n    pub extra_eeprom: Option<Eeprom>,\n    pub sram_access: MemoryOp,\n    pub reg_access: MemoryOp,\n    pub chr_banks: Banks,\n    pub prg_rom_banks: Banks,\n}\n\nimpl BandaiFCG {\n    const PRG_WINDOW: usize = 16 * 1024;\n    const PRG_RAM_SIZE: usize = 8 * 1024; // Mapper 153\n    const CHR_ROM_WINDOW: usize = 1024;\n    const CHR_RAM_SIZE: usize = 8 * 1024;\n\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let (chr, has_chr_ram) = cart.chr_rom_or_ram(chr_rom, Self::CHR_RAM_SIZE);\n        let chr_window = if has_chr_ram {\n            Self::CHR_RAM_SIZE\n        } else {\n            Self::CHR_ROM_WINDOW\n        };\n        let chr_banks = Banks::new(0x0000, 0x1FFF, chr.len(), chr_window)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_WINDOW)?;\n        let prg_ram = cart.prg_ram_or_default(Self::PRG_RAM_SIZE);\n        let mut bandai_fcg = Self {\n            chr,\n            prg_rom,\n            prg_ram,\n            regs: Regs::default(),\n            has_chr_ram,\n            mirroring: cart.mirroring(),\n            mapper_num: cart.mapper_num(),\n            submapper_num: cart.submapper_num(),\n            barcode_reader: None,\n            standard_eeprom: None,\n            extra_eeprom: None,\n            sram_access: MemoryOp::default(),\n            reg_access: MemoryOp::Write,\n            chr_banks,\n            prg_rom_banks,\n        };\n\n        // Mapper 157 is used for Datach Joint ROM System boards\n        if bandai_fcg.mapper_num == 16 {\n            // INES Mapper 016 submapper 4: FCG-1/2 ASIC, no serial EEPROM, banked CHR-ROM\n            // INES Mapper 016 submapper 5: LZ93D50 ASIC and no or 256-byte serial EEPROM, banked\n            // CHR-ROM\n\n            // Add a 256 byte serial EEPROM (24C02)\n            if matches!(bandai_fcg.submapper_num, 0 | 5) && bandai_fcg.prg_ram.len() == 256 {\n                // Connect a 256-byte EEPROM for iNES roms, and when submapper 5 + 256 bytes of\n                // save ram in header\n                bandai_fcg.standard_eeprom = Some(Eeprom::new(EepromModel::X24C02));\n            }\n        } else if bandai_fcg.mapper_num == 157 {\n            bandai_fcg.barcode_reader = Some(BarcodeReader::new());\n            // Datach Joint ROM System\n            //\n            // It contains an internal 256-byte serial EEPROM (24C02) that is shared among all\n            // Datach games.\n            //\n            // One game, Battle Rush: Build up Robot Tournament, has an additional external\n            // 128-byte serial EEPROM (24C01) on the game cartridge.\n            //\n            // The NES 2.0 header's PRG-NVRAM field will only denote whether the game cartridge has\n            // an additional 128-byte serial EEPROM\n            if !cart.is_nes2() || bandai_fcg.prg_ram.len() == 128 {\n                bandai_fcg.extra_eeprom = Some(Eeprom::new(EepromModel::X24C01));\n            }\n\n            // All mapper 157 games have an internal 256-byte EEPROM\n            bandai_fcg.standard_eeprom = Some(Eeprom::new(EepromModel::X24C02));\n        } else if bandai_fcg.mapper_num == 159 {\n            // LZ93D50 with 128 byte serial EEPROM (24C01)\n            bandai_fcg.standard_eeprom = Some(Eeprom::new(EepromModel::X24C01));\n        }\n\n        if bandai_fcg.mapper_num == 16 {\n            if matches!(bandai_fcg.submapper_num, 0 | 4) {\n                bandai_fcg.reg_access = MemoryOp::Read;\n            }\n            if matches!(bandai_fcg.submapper_num, 0 | 5) {\n                bandai_fcg.sram_access = MemoryOp::Read;\n            }\n        } else {\n            // For iNES Mapper 153 (with SRAM), the writeable ports must only be mirrored across\n            // $8000-$FFFF. Mappers 157 and 159 do not need to support the FCG-1 and -2 and so\n            // should only mirror the ports across $8000-$FFFF.\n            if bandai_fcg.mapper_num == 153 {\n                // Mapper 153 has regular save ram from $6000-$7FFF, need to remove the register for both read & writes\n                bandai_fcg.sram_access = MemoryOp::None;\n            } else {\n                bandai_fcg.sram_access = MemoryOp::Read;\n            }\n        }\n\n        let last_bank = bandai_fcg.prg_rom_banks.last();\n        bandai_fcg.prg_rom_banks.set(1, last_bank);\n\n        Ok(bandai_fcg.into())\n    }\n\n    fn write_chr_bank(&mut self, addr: u16, val: u8) {\n        let bank = usize::from(addr & 0x07);\n        self.regs.chr_regs[bank] = val;\n        if self.mapper_num == 153 || self.prg_rom_banks.page_count() >= 0x20 {\n            self.regs.prg_bank_select = 0;\n            for reg in self.regs.chr_regs {\n                self.regs.prg_bank_select |= (reg & 0x01) << 4;\n            }\n            self.prg_rom_banks\n                .set(0, (self.regs.prg_page | self.regs.prg_bank_select).into());\n            self.prg_rom_banks\n                .set(1, 0x0F | usize::from(self.regs.prg_bank_select));\n        } else if !self.has_chr_ram && self.mapper_num != 157 {\n            self.chr_banks.set(bank, val.into());\n        }\n\n        if let Some(eeprom) = &mut self.extra_eeprom {\n            if self.mapper_num == 157 && (addr & 0x0F) <= 3 {\n                eeprom.write_scl((val >> 3) & 0x01)\n            }\n        }\n    }\n\n    fn write_prg_bank(&mut self, val: u8) {\n        self.regs.prg_page = val & 0x0F;\n        self.prg_rom_banks\n            .set(0, (self.regs.prg_page | self.regs.prg_bank_select).into());\n    }\n\n    const fn write_mirroring(&mut self, val: u8) {\n        self.mirroring = match val & 0b11 {\n            0b00 => Mirroring::Vertical,\n            0b01 => Mirroring::Horizontal,\n            0b10 => Mirroring::SingleScreenA,\n            _ => Mirroring::SingleScreenB,\n        };\n    }\n\n    const fn write_irq_ctrl(&mut self, val: u8) {\n        self.regs.irq_enabled = val & 0x01 == 0x01;\n\n        // Wiki claims there is no reload value, however this seems to be the only way to make\n        // Famicom Jump II - Saikyou no 7 Nin work properly\n        if self.mapper_num != 16 || !matches!(self.submapper_num, 0 | 4) {\n            // On the LZ93D50 (Submapper 5), writing to this register also copies the latch to the\n            // actual counter.\n            self.regs.irq_counter = self.regs.irq_reload;\n        }\n\n        self.regs.irq_pending = false;\n    }\n\n    fn write_irq_latch(&mut self, addr: u16, val: u8) {\n        let (mask, val) = if addr & 0x0C == 0x0C {\n            (0x00FF, u16::from(val) << 8)\n        } else {\n            (0xFF00, u16::from(val))\n        };\n        if self.mapper_num != 16 || !matches!(self.submapper_num, 0 | 4) {\n            // On the LZ93D50 (Submapper 5), these registers instead modify a latch that will only\n            // be copied to the actual counter when register $800A is written to.\n            self.regs.irq_reload = (self.regs.irq_reload & mask) | val;\n        } else {\n            // On the FCG-1/2 (Submapper 4), writing to these two registers directly\n            // modifies the counter itself; all such games therefore disable counting before\n            // changing the counter value.\n            self.regs.irq_counter = (self.regs.irq_counter & mask) | val;\n        }\n    }\n\n    fn write_eeprom_ctrl(&mut self, val: u8) {\n        let sda = (val & 0x40) >> 6;\n        if let Some(eeprom) = &mut self.standard_eeprom {\n            let scl = (val & 0x20) >> 5;\n            eeprom.write(scl, sda);\n        }\n        if let Some(eeprom) = &mut self.extra_eeprom {\n            eeprom.write_sda(sda);\n        }\n    }\n\n    #[inline(always)]\n    pub const fn prg_ram_enabled(&self) -> bool {\n        self.mapper_num == 153 && self.regs.prg_ram_enabled\n    }\n}\n\nimpl Map for BandaiFCG {\n    // Mapper 016\n    //\n    // PPU $0000..=$03FF 1K switchable CHR-ROM bank\n    // PPU $0400..=$07FF 1K switchable CHR-ROM bank\n    // PPU $0800..=$0BFF 1K switchable CHR-ROM bank\n    // PPU $0c00..=$0FFF 1K switchable CHR-ROM bank\n    // PPU $1000..=$13FF 1K switchable CHR-ROM bank\n    // PPU $1400..=$17FF 1K switchable CHR-ROM bank\n    // PPU $1800..=$1BFF 1K switchable CHR-ROM bank\n    // PPU $1c00..=$1FFF 1K switchable CHR-ROM bank\n    // CPU $8000..=$BFFF 16K switchable PRG-ROM bank\n    // CPU $C000..=$FFFF 16K PRG-ROM bank, fixed to the last bank\n    //\n    // Mapper 153\n    //\n    // CPU $6000..=$7FFF 8K battery-backed WRAM\n    // CPU $8000..=$BFFF 16K switchable PRG-ROM bank\n    // CPU $C000..=$FFFF 16K PRG-ROM bank, fixed to the last bank\n    // PPU $0000..=$1FFF 8K fixed CHR-ROM bank\n    //\n    // Mapper 157\n    //\n    // CPU $8000..=$BFFF 16K switchable PRG-ROM bank\n    // CPU $C000..=$FFFF 16K PRG-ROM bank, fixed to the last bank\n    // PPU $0000..=$1FFF 8K fixed CHR-ROM bank\n    //\n    // Mapper 159\n    //\n    // PPU $0000..=$03FF 1K switchable CHR-ROM bank\n    // PPU $0400..=$07FF 1K switchable CHR-ROM bank\n    // PPU $0800..=$0BFF 1K switchable CHR-ROM bank\n    // PPU $0c00..=$0FFF 1K switchable CHR-ROM bank\n    // PPU $1000..=$13FF 1K switchable CHR-ROM bank\n    // PPU $1400..=$17FF 1K switchable CHR-ROM bank\n    // PPU $1800..=$1BFF 1K switchable CHR-ROM bank\n    // PPU $1c00..=$1FFF 1K switchable CHR-ROM bank\n    // CPU $8000..=$BFFF 16K switchable PRG-ROM bank\n    // CPU $C000..=$FFFF 16K PRG-ROM bank, fixed to the last bank\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr[self.chr_banks.translate(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Read a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_read(&mut self, addr: u16) -> u8 {\n        if matches!(addr, 0x6000..=0x7FFF) {\n            if !matches!(self.sram_access, MemoryOp::Read | MemoryOp::ReadWrite) {\n                return 0;\n            }\n\n            let mut val = 0x00;\n            if let Some(barcode_reader) = &mut self.barcode_reader {\n                val |= barcode_reader.read();\n            }\n            if let (Some(eeprom1), Some(eeprom2)) =\n                (&mut self.standard_eeprom, &mut self.extra_eeprom)\n            {\n                val |= (eeprom1.read() & eeprom2.read()) << 4;\n            } else if let Some(eeprom) = &mut self.standard_eeprom {\n                val |= eeprom.read() << 4;\n            }\n\n            val\n        } else {\n            self.prg_peek(addr)\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x6000..=0x7FFF if self.prg_ram_enabled() => self.prg_ram[usize::from(addr - 0x6000)],\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8, ciram: &mut CIRam) {\n        match addr {\n            0x0000..=0x1FFF => self.chr[self.chr_banks.translate(addr)] = val,\n            0x2000..=0x3EFF => ciram.write(addr, val, self.mirroring),\n            _ => (),\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        match addr {\n            0x6000..=0x7FFF if self.prg_ram_enabled() => {\n                self.prg_ram[usize::from(addr - 0x6000)] = val;\n            }\n            0x6000..=0xFFFF => match addr & 0x0F {\n                0x00..=0x07 => self.write_chr_bank(addr, val),\n                0x08 => self.write_prg_bank(val),\n                0x09 => self.write_mirroring(val),\n                0x0A => self.write_irq_ctrl(val),\n                0x0B..=0x0C => self.write_irq_latch(addr, val),\n                0x0D => {\n                    if self.mapper_num == 153 {\n                        self.regs.prg_ram_enabled = (val & 0x20) == 0x20;\n                    } else if matches!(self.sram_access, MemoryOp::Write | MemoryOp::ReadWrite) {\n                        self.write_eeprom_ctrl(val);\n                    }\n                }\n                _ => (),\n            },\n            _ => (),\n        }\n    }\n\n    /// Whether an IRQ is pending acknowledgement.\n    fn irq_pending(&self) -> bool {\n        self.regs.irq_pending\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Clock for BandaiFCG {\n    fn clock(&mut self) {\n        if let Some(barcode_reader) = &mut self.barcode_reader {\n            barcode_reader.clock();\n        }\n        // Checking counter before decrementing seems to be the only way to get both Famicom Jump\n        // II - Saikyou no 7 Nin (J) and Magical Taruruuto-kun 2 - Mahou Daibouken (J) to work\n        // without glitches with the same code.\n        if self.regs.irq_enabled {\n            if self.regs.irq_counter == 0 {\n                self.regs.irq_pending = true;\n            }\n            self.regs.irq_counter = self.regs.irq_counter.wrapping_sub(1);\n        }\n    }\n}\n\nimpl Sram for BandaiFCG {\n    fn save(&self, path: impl AsRef<Path>) -> fs::Result<()> {\n        if let Some(eeprom) = &self.standard_eeprom {\n            eeprom.save(&path)?;\n        }\n        if let Some(eeprom) = &self.extra_eeprom {\n            eeprom.save(&path)?;\n        }\n        Ok(())\n    }\n\n    fn load(&mut self, path: impl AsRef<Path>) -> fs::Result<()> {\n        if let Some(eeprom) = &mut self.standard_eeprom {\n            eeprom.load(&path)?;\n        }\n        if let Some(eeprom) = &mut self.extra_eeprom {\n            eeprom.load(&path)?;\n        }\n        Ok(())\n    }\n}\n\nimpl Regional for BandaiFCG {}\nimpl Reset for BandaiFCG {}\n\n#[derive(Default, Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct BarcodeReader {\n    pub data: Box<[u8]>,\n    pub master_clock: usize,\n    pub insert_cycle: usize,\n    pub new_barcode: u64,\n    pub new_barcode_digit_count: u32,\n}\n\nimpl BarcodeReader {\n    pub fn new() -> Self {\n        Self {\n            data: Default::default(),\n            master_clock: 0,\n            insert_cycle: 0,\n            new_barcode: 0,\n            new_barcode_digit_count: 0,\n        }\n    }\n\n    pub const fn read(&self) -> u8 {\n        let elapsed_cycles = self.master_clock - self.insert_cycle;\n        let bit_number = elapsed_cycles / 1000;\n        if bit_number < self.data.len() {\n            self.data[bit_number]\n        } else {\n            0x00\n        }\n    }\n\n    pub const fn input(&mut self, barcode: u64, digit_count: u32) {\n        self.new_barcode = barcode;\n        self.new_barcode_digit_count = digit_count;\n    }\n\n    pub fn barcode(&self) -> String {\n        format!(\n            \"{:0>width$}\",\n            self.new_barcode,\n            width = self.new_barcode_digit_count as usize\n        )\n    }\n\n    pub fn init(&mut self) {\n        self.insert_cycle = self.master_clock;\n\n        static PREFIX_PARITY_TYPE: [[u8; 6]; 10] = [\n            [8, 8, 8, 8, 8, 8],\n            [8, 8, 0, 8, 0, 0],\n            [8, 8, 0, 0, 8, 0],\n            [8, 8, 0, 0, 0, 8],\n            [8, 0, 8, 8, 0, 0],\n            [8, 0, 0, 8, 8, 0],\n            [8, 0, 0, 0, 8, 8],\n            [8, 0, 8, 0, 8, 0],\n            [8, 0, 8, 0, 0, 8],\n            [8, 0, 0, 8, 0, 8],\n        ];\n\n        static DATA_LEFT_ODD: [[u8; 7]; 10] = [\n            [8, 8, 8, 0, 0, 8, 0],\n            [8, 8, 0, 0, 8, 8, 0],\n            [8, 8, 0, 8, 8, 0, 0],\n            [8, 0, 0, 0, 0, 8, 0],\n            [8, 0, 8, 8, 8, 0, 0],\n            [8, 0, 0, 8, 8, 8, 0],\n            [8, 0, 8, 0, 0, 0, 0],\n            [8, 0, 0, 0, 8, 0, 0],\n            [8, 0, 0, 8, 0, 0, 0],\n            [8, 8, 8, 0, 8, 0, 0],\n        ];\n\n        static DATA_LEFT_EVEN: [[u8; 7]; 10] = [\n            [8, 0, 8, 8, 0, 0, 0],\n            [8, 0, 0, 8, 8, 0, 0],\n            [8, 8, 0, 0, 8, 0, 0],\n            [8, 0, 8, 8, 8, 8, 0],\n            [8, 8, 0, 0, 0, 8, 0],\n            [8, 0, 0, 0, 8, 8, 0],\n            [8, 8, 8, 8, 0, 8, 0],\n            [8, 8, 0, 8, 8, 8, 0],\n            [8, 8, 8, 0, 8, 8, 0],\n            [8, 8, 0, 8, 0, 0, 0],\n        ];\n\n        static DATA_RIGHT: [[u8; 7]; 10] = [\n            [0, 0, 0, 8, 8, 0, 8],\n            [0, 0, 8, 8, 0, 0, 8],\n            [0, 0, 8, 0, 0, 8, 8],\n            [0, 8, 8, 8, 8, 0, 8],\n            [0, 8, 0, 0, 0, 8, 8],\n            [0, 8, 8, 0, 0, 0, 8],\n            [0, 8, 0, 8, 8, 8, 8],\n            [0, 8, 8, 8, 0, 8, 8],\n            [0, 8, 8, 0, 8, 8, 8],\n            [0, 0, 0, 8, 0, 8, 8],\n        ];\n\n        let barcode = self.barcode();\n        let mut codes = Vec::new();\n        for ch in barcode.chars() {\n            codes.push(ch.to_digit(10).expect(\"valid barcode character\") as usize);\n        }\n\n        let mut data = Vec::<u8>::with_capacity(256);\n\n        data.extend([8; 33]);\n        data.extend([0, 8, 0]);\n\n        let mut sum = 0;\n        if barcode.len() == 13 {\n            for i in 0..6 {\n                let odd = PREFIX_PARITY_TYPE[codes[0]][i] != 0;\n                for j in 0..7 {\n                    data.push(if odd {\n                        DATA_LEFT_ODD[codes[i + 1]][j]\n                    } else {\n                        DATA_LEFT_EVEN[codes[i + 1]][j]\n                    });\n                }\n            }\n\n            data.extend([8, 0, 8, 0, 8]);\n\n            for code in codes.iter().skip(7).take(5) {\n                for code_data in DATA_RIGHT[*code].iter().take(7) {\n                    data.push(*code_data);\n                }\n            }\n\n            for (i, code) in codes.iter().enumerate().take(12) {\n                sum += if (i & 1) == 1 { *code * 3 } else { *code };\n            }\n        } else {\n            for code in codes.iter().take(4) {\n                for code_data in DATA_LEFT_ODD[*code].iter().take(7) {\n                    data.push(*code_data);\n                }\n            }\n\n            data.extend([8, 0, 8, 0, 8]);\n\n            for code in codes.iter().skip(4).take(3) {\n                for code_data in DATA_RIGHT[*code].iter().take(7) {\n                    data.push(*code_data);\n                }\n            }\n\n            for (i, code) in codes.iter().enumerate().take(7) {\n                sum += if (i & 1) == 1 { *code } else { *code * 3 };\n            }\n        }\n\n        sum = (10 - (sum % 10)) % 10;\n\n        for sum_data in DATA_RIGHT[sum].iter().take(7) {\n            data.push(*sum_data);\n        }\n\n        data.extend([0, 8, 0]);\n        data.extend([8; 32]);\n\n        self.data = data.into();\n    }\n}\n\nimpl Clock for BarcodeReader {\n    fn clock(&mut self) {\n        self.master_clock += 1;\n    }\n}\n\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub enum EepromModel {\n    X24C01,\n    X24C02,\n}\n\n#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub enum EepromMode {\n    #[default]\n    Idle,\n    Addr,\n    Read,\n    Write,\n    SendAck,\n    WaitAck,\n    ChipAddr,\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Eeprom {\n    pub model: EepromModel,\n    pub mode: EepromMode,\n    pub next_mode: EepromMode,\n    pub chip_addr: u8,\n    pub addr: u8,\n    pub data: u8,\n    pub counter: u8,\n    pub output: u8,\n    pub prev_scl: u8,\n    pub prev_sda: u8,\n    pub rom_data: Memory<Box<[u8]>>,\n}\n\nimpl Eeprom {\n    pub fn new(model: EepromModel) -> Self {\n        let rom_size = match model {\n            EepromModel::X24C01 => 128,\n            EepromModel::X24C02 => 256,\n        };\n        Self {\n            model,\n            mode: EepromMode::default(),\n            next_mode: EepromMode::default(),\n            chip_addr: 0,\n            addr: 0,\n            data: 0,\n            counter: 0,\n            output: 0,\n            prev_scl: 0,\n            prev_sda: 0,\n            rom_data: Memory::new(rom_size),\n        }\n    }\n\n    pub const fn read(&self) -> u8 {\n        self.output\n    }\n\n    pub fn write(&mut self, scl: u8, sda: u8) {\n        match self.model {\n            EepromModel::X24C01 => {\n                if self.prev_scl > 0 && scl > 0 && sda < self.prev_sda {\n                    // START is identified by a high to low transition of the SDA line while the\n                    // clock SCL is *stable* in the high state\n                    self.mode = EepromMode::Addr;\n                    self.addr = 0;\n                    self.counter = 0;\n                    self.output = 1;\n                } else if self.prev_scl > 0 && scl > 0 && sda > self.prev_sda {\n                    // STOP is identified by a low to high transition of the SDA line while the\n                    // clock SCL is *stable* in the high state\n                    self.mode = EepromMode::Idle;\n                    self.output = 1;\n                } else if scl > self.prev_scl {\n                    // Clock rise\n                    match self.mode {\n                        EepromMode::Addr => {\n                            // To initiate a write operation, the master sends a start condition\n                            // followed by a seven bit word address and a write bit.\n                            match self.counter.cmp(&7) {\n                                Ordering::Less => {\n                                    if let Some(addr) = self.write_bit(self.addr, sda) {\n                                        self.addr = addr;\n                                    }\n                                }\n                                Ordering::Equal => {\n                                    // 8th bit to determine if we're in read or write mode\n                                    self.counter = 8;\n                                    if sda > 0 {\n                                        self.next_mode = EepromMode::Read;\n                                        self.data = self.rom_data[usize::from(self.addr & 0x7F)];\n                                    } else {\n                                        self.next_mode = EepromMode::Write;\n                                    }\n                                }\n                                _ => (),\n                            }\n                        }\n                        EepromMode::Read => self.read_bit(),\n                        EepromMode::Write => {\n                            if let Some(data) = self.write_bit(self.data, sda) {\n                                self.data = data;\n                            }\n                        }\n                        EepromMode::SendAck => self.output = 0,\n                        EepromMode::WaitAck if sda == 0 => {\n                            // We expected an ack, but received something else, return to idle\n                            // mode\n                            self.next_mode = EepromMode::Idle;\n                        }\n                        _ => (),\n                    }\n                } else if scl < self.prev_scl {\n                    // Clock fall\n                    match self.mode {\n                        EepromMode::Addr if self.counter == 8 => {\n                            // After receiving the address, the X24C01 responds with an\n                            // acknowledge, then waits for eight bits of data\n                            self.mode = EepromMode::SendAck;\n                            self.output = 1;\n                        }\n                        EepromMode::SendAck => {\n                            // After sending an ack, move to the next mode of operation\n                            self.mode = self.next_mode;\n                            self.counter = 0;\n                            self.output = 1;\n                        }\n                        EepromMode::Read if self.counter == 8 => {\n                            // After sending all 8 bits, wait for an ack\n                            self.mode = EepromMode::WaitAck;\n                            self.addr = (self.addr + 1) & 0x7F;\n                        }\n                        EepromMode::Write if self.counter == 8 => {\n                            // After receiving all 8 bits, send an ack and then wait\n                            self.mode = EepromMode::SendAck;\n                            self.next_mode = EepromMode::Idle;\n                            self.rom_data[usize::from(self.addr & 0x7F)] = self.data;\n                            self.addr = (self.addr + 1) & 0x7F;\n                        }\n                        _ => (),\n                    }\n                }\n\n                self.prev_scl = scl;\n                self.prev_sda = sda;\n            }\n            EepromModel::X24C02 => {\n                if self.prev_scl > 0 && scl > 0 && sda < self.prev_sda {\n                    // START is identified by a high to low transition of the SDA line while the\n                    // clock SCL is *stable* in the high state\n                    self.mode = EepromMode::ChipAddr;\n                    self.counter = 0;\n                    self.output = 1;\n                } else if self.prev_scl > 0 && scl > 0 && sda > self.prev_sda {\n                    // STOP is identified by a low to high transition of the SDA line while the\n                    // clock SCL is *stable* in the high state\n                    self.mode = EepromMode::Idle;\n                    self.output = 1;\n                } else if scl > self.prev_scl {\n                    // Clock rise\n                    match self.mode {\n                        EepromMode::ChipAddr => {\n                            if let Some(chip_addr) = self.write_bit(self.chip_addr, sda) {\n                                self.chip_addr = chip_addr;\n                            }\n                        }\n                        EepromMode::Addr => {\n                            if let Some(addr) = self.write_bit(self.addr, sda) {\n                                self.addr = addr;\n                            }\n                        }\n                        EepromMode::Read => self.read_bit(),\n                        EepromMode::Write => {\n                            if let Some(data) = self.write_bit(self.data, sda) {\n                                self.data = data;\n                            }\n                        }\n                        EepromMode::SendAck => self.output = 0,\n                        EepromMode::WaitAck if sda == 0 => {\n                            self.next_mode = EepromMode::Read;\n                            self.data = self.rom_data[usize::from(self.addr)];\n                        }\n                        _ => (),\n                    }\n                } else if scl < self.prev_scl {\n                    // Clock fall\n                    match self.mode {\n                        // Upon a correct compare the X24C02 outputs an acknowledge on the SDA line\n                        EepromMode::ChipAddr if self.counter == 8 => {\n                            if (self.chip_addr & 0xA0) == 0xA0 {\n                                self.mode = EepromMode::SendAck;\n                                self.counter = 0;\n                                self.output = 1;\n\n                                // The last bit of the slave address defines the operation to\n                                // be performed. When set to one a read operation is selected,\n                                // when set to zero a write operations is selected\n                                if (self.chip_addr & 0x01) == 0x01 {\n                                    // Current Address Read\n                                    // Upon receipt of the slave address with the R/W bit set\n                                    // to one, the X24C02 issues an acknowledge and transmits\n                                    // the eight bit word during the next eight clock cycles\n                                    self.next_mode = EepromMode::Read;\n                                    self.data = self.rom_data[usize::from(self.addr)];\n                                } else {\n                                    self.mode = EepromMode::Addr;\n                                }\n                            } else {\n                                // This chip wasn't selected, go back to idle mode\n                                self.mode = EepromMode::Idle;\n                                self.counter = 0;\n                                self.output = 1;\n                            }\n                        }\n                        EepromMode::Addr if self.counter == 8 => {\n                            // Finished receiving all 8 bits of the address, send an ack and then starting writing the value\n                            self.mode = EepromMode::SendAck;\n                            self.next_mode = EepromMode::Write;\n                            self.counter = 0;\n                            self.output = 1;\n                        }\n                        EepromMode::Read if self.counter == 8 => {\n                            // After sending all 8 bits, wait for an ack\n                            self.mode = EepromMode::WaitAck;\n                            self.addr = self.addr.wrapping_add(1);\n                        }\n                        EepromMode::Write if self.counter == 8 => {\n                            // After receiving all 8 bits, send an ack and then wait\n                            self.mode = EepromMode::SendAck;\n                            self.next_mode = EepromMode::Write;\n                            self.counter = 0;\n                            self.rom_data[usize::from(self.addr)] = self.data;\n                            self.addr = self.addr.wrapping_add(1);\n                        }\n                        EepromMode::SendAck | EepromMode::WaitAck => {\n                            self.mode = self.next_mode;\n                            self.counter = 0;\n                            self.output = 1;\n                        }\n                        _ => (),\n                    }\n                }\n\n                self.prev_scl = scl;\n                self.prev_sda = sda;\n            }\n        }\n    }\n\n    pub fn write_scl(&mut self, scl: u8) {\n        self.write(scl, self.prev_sda);\n    }\n\n    pub fn write_sda(&mut self, sda: u8) {\n        self.write(self.prev_scl, sda);\n    }\n\n    pub const fn write_bit(&mut self, dest: u8, val: u8) -> Option<u8> {\n        if self.counter < 8 {\n            let mask = !(1 << self.counter);\n            let dest = (dest & mask) | (val << self.counter);\n            self.counter += 1;\n            Some(dest)\n        } else {\n            None\n        }\n    }\n\n    pub const fn read_bit(&mut self) {\n        if self.counter < 8 {\n            self.output = if self.data & (1 << self.counter) > 0 {\n                1\n            } else {\n                0\n            };\n            self.counter += 1;\n        }\n    }\n\n    pub const fn sram_extension(&self) -> &str {\n        match self.model {\n            EepromModel::X24C01 => \"eeprom128\",\n            EepromModel::X24C02 => \"eeprom256\",\n        }\n    }\n}\n\nimpl Sram for Eeprom {\n    fn save(&self, path: impl AsRef<Path>) -> fs::Result<()> {\n        let extension = self.sram_extension();\n        fs::save(path.as_ref().with_extension(extension), &self.rom_data)\n    }\n\n    fn load(&mut self, path: impl AsRef<Path>) -> fs::Result<()> {\n        let extension = self.sram_extension();\n        fs::load(path.as_ref().with_extension(extension)).map(|data| self.rom_data = data)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn bandai_fcg_barcode_formatting() {\n        let mut reader = BarcodeReader::new();\n        reader.input(4902425679235, 13);\n        assert_eq!(reader.barcode(), \"4902425679235\");\n\n        // Test zero-padding for EAN-8\n        reader.input(1234567, 8);\n        assert_eq!(reader.barcode(), \"01234567\");\n    }\n\n    #[test]\n    fn bandai_fcg_ean13_checksum() {\n        // EAN-13: first 12 digits -> checksum is 13th\n        // 490242567923 -> check digit 5\n        let mut reader = BarcodeReader::new();\n        reader.input(4902425679235, 13);\n        reader.init();\n\n        // The checksum is encoded in the last 7 data bits before the end guard\n        // End guard is [0,8,0] + 32x8, so check digit encoding ends at len-35\n        let check_digit_pattern = &reader.data[reader.data.len() - 35 - 7..reader.data.len() - 35];\n\n        // DATA_RIGHT[5] = [0, 8, 8, 0, 0, 0, 8]\n        assert_eq!(check_digit_pattern, &[0, 8, 8, 0, 0, 0, 8]);\n    }\n\n    #[test]\n    fn bandai_fcg_ean13_structure() {\n        let mut reader = BarcodeReader::new();\n        reader.input(4902425679235, 13);\n        reader.init();\n\n        // EAN-13 total: 33 (quiet) + 3 (start) + 42 (left) + 5 (center) + 42 (right) + 3 (end) + 32 (quiet)\n        // = 33 + 3 + 42 + 5 + 42 + 3 + 32 = 160\n        assert_eq!(reader.data.len(), 160);\n\n        // Start guard at index 33\n        assert_eq!(&reader.data[33..36], &[0, 8, 0]);\n\n        // Center guard at index 33 + 3 + 42 = 78\n        assert_eq!(&reader.data[78..83], &[8, 0, 8, 0, 8]);\n\n        // End guard at index 83 + 35 = 125 (after 5 digits * 7 bits)\n        assert_eq!(&reader.data[125..128], &[0, 8, 0]);\n    }\n\n    #[test]\n    fn bandai_fcg_ean8_structure() {\n        let mut reader = BarcodeReader::new();\n        // Valid EAN-8: 12345670 (checksum 0)\n        reader.input(12345670, 8);\n        reader.init();\n\n        // EAN-8: 33 + 3 + 28 + 5 + 28 + 3 + 32 = 132\n        assert_eq!(reader.data.len(), 132);\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m000_nrom.rs",
    "content": "//! `NROM` (Mapper 000).\n//!\n//! <https://wiki.nesdev.org/w/index.php/NROM>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, ResetKind, Sram},\n    mapper::{self, Map, Mapper},\n    mem::{Memory, RamState},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\n\n/// `NROM` (Mapper 000).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Nrom {\n    pub chr: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub prg_ram: Memory<Box<[u8]>>,\n    pub mirroring: Mirroring,\n    pub has_chr_ram: bool,\n    pub mirror_prg_rom: bool,\n    pub ram_state: RamState,\n}\n\nimpl Nrom {\n    const PRG_RAM_SIZE: usize = 8 * 1024;\n    const CHR_RAM_SIZE: usize = 8 * 1024;\n\n    /// Load `Nrom` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        // NROM doesn't have CHR-RAM - but a lot of homebrew games use Mapper 000 with CHR-RAM, so\n        // we'll provide some if no CHR-ROM is available.\n        let (chr, has_chr_ram) = cart.chr_rom_or_ram(chr_rom, Self::CHR_RAM_SIZE);\n        let nrom = Self {\n            chr,\n            prg_rom,\n            // Family Basic only supported 2-4K of PRG-RAM, but we'll provide 8K by default.\n            prg_ram: Memory::with_ram_state(Self::PRG_RAM_SIZE, cart.ram_state),\n            mirroring: cart.mirroring(),\n            has_chr_ram,\n            mirror_prg_rom: cart.prg_rom_size <= 0x4000,\n            ram_state: cart.ram_state,\n        };\n        Ok(nrom.into())\n    }\n}\n\nimpl Map for Nrom {\n    // PPU $0000..=$1FFF 8K Fixed CHR-ROM Bank\n    // CPU $6000..=$7FFF 2K or 4K PRG-RAM Family Basic only. 8K is provided by default.\n    // CPU $8000..=$BFFF 16K PRG-ROM Bank 1 for NROM128 or NROM256\n    // CPU $C000..=$FFFF 16K PRG-ROM Bank 2 for NROM256 or Bank 1 Mirror for NROM128\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            0x0000..=0x1FFF => self.chr[usize::from(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x8000..=0xBFFF => self.prg_rom[usize::from(addr & 0x3FFF)],\n            0xC000..=0xFFFF => {\n                let mirror = if self.mirror_prg_rom { 0x3FFF } else { 0x7FFF };\n                self.prg_rom[usize::from(addr & mirror)]\n            }\n            0x6000..=0x7FFF => self.prg_ram[usize::from(addr & 0x1FFF)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8, ciram: &mut CIRam) {\n        match addr {\n            0x2000..=0x3EFF => ciram.write(addr, val, self.mirroring),\n            0x0000..=0x1FFF if self.has_chr_ram => self.chr[usize::from(addr)] = val,\n            _ => (),\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        if let 0x6000..=0x7FFF = addr {\n            self.prg_ram[usize::from(addr & 0x1FFF)] = val;\n        }\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Nrom {\n    fn reset(&mut self, kind: ResetKind) {\n        if kind == ResetKind::Hard {\n            self.ram_state.fill(&mut self.prg_ram);\n        }\n    }\n}\n\nimpl Clock for Nrom {}\nimpl Regional for Nrom {}\nimpl Sram for Nrom {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m001_sxrom.rs",
    "content": "//! `SxROM`/`MMC1` (Mapper 001).\n//!\n//! <https://wiki.nesdev.org/w/index.php/SxROM>\n//! <https://wiki.nesdev.org/w/index.php/MMC1>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, ResetKind, Sram},\n    fs,\n    mapper::{self, Map, Mapper},\n    mem::{Banks, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\nuse std::path::Path;\n\n/// MMC1 Revision.\n#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum Revision {\n    /// MMC1 Revision A\n    A,\n    /// MMC1 Revisions B & C\n    #[default]\n    BC,\n}\n\n/// `SxROM` registers.\n#[derive(Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Regs {\n    write_just_occurred: u8,\n    write_buffer: u8,       // $8000-$FFFF - 5 bit shift register\n    shift_count: u8,        // How many times write_buffer has shifted\n    prg_ram_disabled: bool, // $E000-$FFFF bit 4\n    chr_mode: bool,         // $8000-$9FFF bit 4\n    prg_mode: bool,         // $8000-$9FFF bits 3\n    prg_bank_select: bool,  // $8000-$9FFF bit 2\n    last_chr_reg: u16,      // Last chr register written to\n    chr0: u8,               // $A000-$BFFF\n    chr1: u8,               // $C000-$DFFF\n    prg: u8,                // $E000-$FFFF bits 0-3\n}\n\n/// `SxROM`/`MMC1` (Mapper 001).\n#[derive(Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Sxrom {\n    pub chr: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub prg_ram: Memory<Box<[u8]>>,\n    pub chr_banks: Banks,\n    pub prg_ram_banks: Banks,\n    pub prg_rom_banks: Banks,\n    pub regs: Regs,\n    pub has_chr_ram: bool,\n    pub submapper_num: u8,\n    pub mirroring: Mirroring,\n    pub revision: Revision,\n    pub prg_select: bool,\n}\n\nimpl Sxrom {\n    const PRG_RAM_WINDOW: usize = 8 * 1024;\n    const PRG_ROM_WINDOW: usize = 16 * 1024;\n    const CHR_WINDOW: usize = 4 * 1024;\n    const PRG_RAM_SIZE: usize = 32 * 1024; // 32K is safely compatible sans NES 2.0 header\n    const CHR_RAM_SIZE: usize = 8 * 1024;\n\n    const SHIFT_REG_RESET: u8 = 0x80; // Reset shift register when bit 7 is set\n    const MIRRORING_MASK: u8 = 0x03; // 0b00011\n    const SLOT_SELECT_MASK: u8 = 0x04; // 0b00100\n    const PRG_MODE_MASK: u8 = 0x08; // 0b01000\n    const CHR_MODE_MASK: u8 = 0x10; // 0b10000\n\n    const DEFAULT_PRG_MODE: u8 = 0x0C; // Mode 3, 16k Fixed Last\n    const CHR_BANK_MASK: u8 = 0x1F;\n    const PRG_BANK_MASK: u8 = 0x0F;\n    const PRG_RAM_DISABLED: u8 = 0x10; // 0b10000\n\n    /// Load `Sxrom` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n        revision: Revision,\n    ) -> Result<Mapper, mapper::Error> {\n        let (chr, has_chr_ram) = cart.chr_rom_or_ram(chr_rom, Self::CHR_RAM_SIZE);\n        let prg_ram = cart.prg_ram_or_default(Self::PRG_RAM_SIZE);\n        let chr_banks = Banks::new(0x0000, 0x1FFF, chr.len(), Self::CHR_WINDOW)?;\n        let prg_ram_banks = Banks::new(0x6000, 0x7FFF, prg_ram.len(), Self::PRG_RAM_WINDOW)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_ROM_WINDOW)?;\n        let mut sxrom = Self {\n            prg_rom,\n            chr,\n            prg_ram,\n            chr_banks,\n            prg_ram_banks,\n            prg_rom_banks,\n            regs: Regs {\n                write_just_occurred: 0x00,\n                write_buffer: 0x00,\n                shift_count: 0,\n                prg_ram_disabled: false,\n                chr_mode: false,\n                prg_mode: false,\n                prg_bank_select: false,\n                last_chr_reg: 0xA000,\n                chr0: 0x00,\n                chr1: 0x00,\n                prg: 0x00,\n            },\n            has_chr_ram,\n            submapper_num: cart.submapper_num(),\n            mirroring: Mirroring::SingleScreenA,\n            revision,\n            prg_select: cart.prg_rom_size == 0x80000,\n        };\n        sxrom.process_register_write(0x8000, Self::DEFAULT_PRG_MODE);\n        sxrom.process_register_write(0xA000, 0x00);\n        sxrom.process_register_write(0xC000, 0x00);\n        sxrom.process_register_write(\n            0xE000,\n            if revision == Revision::BC {\n                0x00\n            } else {\n                Self::PRG_RAM_DISABLED\n            },\n        );\n        sxrom.regs.last_chr_reg = 0xA000;\n        sxrom.update_state();\n        Ok(sxrom.into())\n    }\n\n    /// Reset the shift register write buffer.\n    const fn reset_buffer(&mut self) {\n        self.regs.shift_count = 0;\n        self.regs.write_buffer = 0;\n    }\n\n    /// Process register write, extracting registers into flags.\n    const fn process_register_write(&mut self, addr: u16, val: u8) {\n        match addr & 0xE000 {\n            0x8000 => {\n                self.mirroring = match val & Self::MIRRORING_MASK {\n                    0b00 => Mirroring::SingleScreenA,\n                    0b01 => Mirroring::SingleScreenB,\n                    0b10 => Mirroring::Vertical,\n                    _ => Mirroring::Horizontal,\n                };\n                self.regs.prg_bank_select = (val & Self::SLOT_SELECT_MASK) != 0;\n                self.regs.prg_mode = (val & Self::PRG_MODE_MASK) != 0;\n                self.regs.chr_mode = (val & Self::CHR_MODE_MASK) != 0;\n            }\n            0xA000 => {\n                self.regs.last_chr_reg = addr;\n                self.regs.chr0 = val & Self::CHR_BANK_MASK;\n            }\n            0xC000 => {\n                self.regs.last_chr_reg = addr;\n                self.regs.chr1 = val & Self::CHR_BANK_MASK;\n            }\n            0xE000 => {\n                self.regs.prg = val & Self::PRG_BANK_MASK;\n                self.regs.prg_ram_disabled = (val & Self::PRG_RAM_DISABLED) != 0;\n            }\n            _ => (),\n        }\n    }\n\n    /// Update internal state based on register flags.\n    pub fn update_state(&mut self) {\n        let extra_reg = if self.regs.last_chr_reg == 0xC000 && self.regs.chr_mode {\n            self.regs.chr1\n        } else {\n            self.regs.chr0\n        };\n        let prg_bank_select = if self.prg_select {\n            extra_reg & Self::CHR_MODE_MASK\n        } else {\n            0x00\n        };\n\n        if self.submapper_num == 5 {\n            // Fixed PRG SEROM, SHROM, SH1ROM use a fixed 32k PRG-ROM with no banking support.\n            self.prg_rom_banks.set_range(0, 1, 0);\n        } else if self.regs.prg_mode {\n            if self.regs.prg_bank_select {\n                self.prg_rom_banks\n                    .set(0, (self.regs.prg | prg_bank_select).into());\n                self.prg_rom_banks\n                    .set(1, (Self::PRG_BANK_MASK | prg_bank_select).into());\n            } else {\n                self.prg_rom_banks.set(1, prg_bank_select.into());\n                self.prg_rom_banks\n                    .set(1, (self.regs.prg | prg_bank_select).into());\n            }\n        } else {\n            self.prg_rom_banks\n                .set_range(0, 1, ((self.regs.prg & 0xFE) | prg_bank_select).into()); // ignore low bit\n        }\n\n        if self.regs.chr_mode {\n            self.chr_banks.set(0, self.regs.chr0.into());\n            self.chr_banks.set(1, self.regs.chr1.into());\n        } else {\n            self.chr_banks.set(0, (self.regs.chr0 & 0x1E).into()); // ignore low bit\n            self.chr_banks.set(1, ((self.regs.chr0 & 0x1E) + 1).into()); // ignore low bit\n        }\n    }\n\n    #[inline(always)]\n    pub fn prg_ram_enabled(&self) -> bool {\n        self.revision == Revision::A || !self.regs.prg_ram_disabled\n    }\n\n    pub const fn set_revision(&mut self, revision: Revision) {\n        self.revision = revision;\n    }\n}\n\nimpl Map for Sxrom {\n    // PPU $0000..=$1FFF 4K CHR-ROM/RAM Bank Switchable\n    // CPU $6000..=$7FFF 8K PRG-RAM Bank (optional)\n    // CPU $8000..=$BFFF 16K PRG-ROM Bank Switchable or Fixed to First Bank\n    // CPU $C000..=$FFFF 16K PRG-ROM Bank Fixed to Last Bank or Switchable\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr[self.chr_banks.translate(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x6000..=0x7FFF if self.prg_ram_enabled() => {\n                self.prg_ram[self.prg_ram_banks.translate(addr)]\n            }\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8, ciram: &mut CIRam) {\n        match addr {\n            0x0000..=0x1FFF if self.has_chr_ram => self.chr[self.chr_banks.translate(addr)] = val,\n            0x2000..=0x3EFF => ciram.write(addr, val, self.mirroring),\n            _ => (),\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        match addr {\n            0x6000..=0x7FFF if self.prg_ram_enabled() => {\n                self.prg_ram[self.prg_ram_banks.translate(addr)] = val;\n            }\n            0x8000..=0xFFFF => {\n                // Writes data into a shift register. At every 5th\n                // write, the data is written out to the `SxROM` registers\n                // and the shift register is cleared\n                //\n                // Load Register $8000-$FFFF\n                // 7654 3210\n                // Rxxx xxxD\n                // |       +- Data bit to be shifted into shift register, LSB first\n                // +--------- 1: Reset shift register and write control with (Control OR $0C),\n                //               locking PRG-ROM at $C000-$FFFF to the last bank.\n                //\n                // Control $8000-$9FFF\n                // 43210\n                // CPPMM\n                // |||++- Mirroring (0: one-screen, lower bank; 1: one-screen, upper bank;\n                // |||               2: vertical; 3: horizontal)\n                // |++--- PRG-ROM bank mode (0, 1: switch 32K at $8000, ignoring low bit of bank number;\n                // |                         2: fix first bank at $8000 and switch 16K bank at $C000;\n                // |                         3: fix last bank at $C000 and switch 16K bank at $8000)\n                // +----- CHR-ROM bank mode (0: switch 8K at a time; 1: switch two separate 4K banks)\n                //\n                // CHR bank 0 $A000-$BFFF\n                // 42310\n                // CCCCC\n                // +++++- Select 4K or 8K CHR bank at PPU $0000 (low bit ignored in 8K mode)\n                //\n                // CHR bank 1 $C000-$DFFF\n                // 43210\n                // CCCCC\n                // +++++- Select 4K CHR bank at PPU $1000 (ignored in 8K mode)\n                //\n                // For Mapper001\n                // $A000 and $C000:\n                // 43210\n                // EDCBA\n                // |||||\n                // ||||+- CHR A12\n                // |||+-- CHR A13, if extant (CHR >= 16k)\n                // ||+--- CHR A14, if extant; and PRG-RAM A14, if extant (PRG-RAM = 32k)\n                // |+---- CHR A15, if extant; and PRG-RAM A13, if extant (PRG-RAM >= 16k)\n                // +----- CHR A16, if extant; and PRG-ROM A18, if extant (PRG-ROM = 512k)\n                //\n                // PRG bank $E000-$FFFF\n                // 43210\n                // RPPPP\n                // |++++- Select 16K PRG-ROM bank (low bit ignored in 32K mode)\n                // +----- PRG-RAM chip enable (0: enabled; 1: disabled; ignored on MMC1A)\n\n                if self.regs.write_just_occurred > 0 {\n                    return;\n                }\n                self.regs.write_just_occurred = 2;\n\n                if val & Self::SHIFT_REG_RESET == Self::SHIFT_REG_RESET {\n                    self.reset_buffer();\n                    self.regs.prg_mode = true;\n                    self.regs.prg_bank_select = true;\n                    self.update_state();\n                } else {\n                    // Move shift register and write lowest bit of val\n                    self.regs.write_buffer >>= 1;\n                    self.regs.write_buffer |= (val << 4) & 0x10;\n\n                    self.regs.shift_count += 1;\n                    // Check if its time to write\n                    if self.regs.shift_count == 5 {\n                        self.process_register_write(addr, self.regs.write_buffer);\n                        self.update_state();\n                        self.reset_buffer();\n                    }\n                }\n            }\n            _ => (),\n        }\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Sxrom {\n    fn reset(&mut self, kind: ResetKind) {\n        self.reset_buffer();\n        self.regs.prg_mode = true;\n        self.regs.prg_bank_select = true;\n        self.update_state();\n        if kind == ResetKind::Hard {\n            self.regs.write_just_occurred = 0;\n            self.regs.prg_ram_disabled = false;\n        }\n    }\n}\n\nimpl Clock for Sxrom {\n    fn clock(&mut self) {\n        if self.regs.write_just_occurred > 0 {\n            self.regs.write_just_occurred -= 1;\n        }\n    }\n}\n\nimpl Sram for Sxrom {\n    /// Save RAM to a given path.\n    fn save(&self, path: impl AsRef<Path>) -> fs::Result<()> {\n        fs::save(path.as_ref(), &self.prg_ram)\n    }\n\n    /// Load save RAM from a given path.\n    fn load(&mut self, path: impl AsRef<Path>) -> fs::Result<()> {\n        fs::load(path.as_ref()).map(|data: Memory<Box<[u8]>>| self.prg_ram = data)\n    }\n}\n\nimpl Regional for Sxrom {}\n\nimpl std::fmt::Debug for Sxrom {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"SxRom\")\n            .field(\"regs\", &self.regs)\n            .field(\"submapper_num\", &self.submapper_num)\n            .field(\"mirroring\", &self.mirroring)\n            .field(\"revision\", &self.revision)\n            .field(\"prg_select\", &self.prg_select)\n            .field(\"chr_banks\", &self.chr_banks)\n            .field(\"prg_ram_banks\", &self.prg_ram_banks)\n            .field(\"prg_ram_enabled\", &self.prg_ram_enabled())\n            .field(\"prg_rom_banks\", &self.prg_rom_banks)\n            .finish()\n    }\n}\n\nimpl std::fmt::Debug for Regs {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"SxRegs\")\n            .field(\"write_just_occurred\", &self.write_just_occurred)\n            .field(\"write_buffer\", &format_args!(\"0b{:08b}\", self.write_buffer))\n            .field(\"shift_count\", &self.shift_count)\n            .field(\"prg_ram_disabled\", &self.prg_ram_disabled)\n            .field(\"chr_mode\", &self.chr_mode)\n            .field(\"prg_mode\", &self.prg_mode)\n            .field(\"prg_bank_select\", &self.prg_bank_select)\n            .field(\"last_chr_reg\", &self.last_chr_reg)\n            .field(\"chr0\", &format_args!(\"${:02X}\", self.chr0))\n            .field(\"chr1\", &format_args!(\"${:02X}\", self.chr1))\n            .field(\"prg\", &format_args!(\"${:02X}\", self.prg))\n            .finish()\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m002_uxrom.rs",
    "content": "//! `UxROM` (Mapper 002).\n//!\n//! <https://wiki.nesdev.org/w/index.php/UxROM>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, Sram},\n    mapper::{self, Map, Mapper},\n    mem::{Banks, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\n\n/// `UxROM` (Mapper 002).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Uxrom {\n    pub chr: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub has_chr_ram: bool,\n    pub mirroring: Mirroring,\n    pub prg_rom_banks: Banks,\n}\n\nimpl Uxrom {\n    const PRG_ROM_WINDOW: usize = 16 * 1024;\n    const CHR_RAM_SIZE: usize = 8 * 1024;\n\n    /// Load `Uxrom` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let (chr, has_chr_ram) = cart.chr_rom_or_ram(chr_rom, Self::CHR_RAM_SIZE);\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_ROM_WINDOW)?;\n        let mut uxrom = Self {\n            chr,\n            prg_rom,\n            has_chr_ram,\n            mirroring: cart.mirroring(),\n            prg_rom_banks,\n        };\n        uxrom.prg_rom_banks.set(1, uxrom.prg_rom_banks.last());\n        Ok(uxrom.into())\n    }\n}\n\nimpl Map for Uxrom {\n    // PPU $0000..=$1FFF 8K Fixed CHR-ROM/CHR-RAM Bank\n    // CPU $8000..=$BFFF 16K PRG-ROM Bank Switchable\n    // CPU $C000..=$FFFF 16K PRG-ROM Fixed to Last Bank\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr[usize::from(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8, ciram: &mut CIRam) {\n        match addr {\n            0x0000..=0x1FFF => self.chr[usize::from(addr)] = val,\n            0x2000..=0x3EFF => ciram.write(addr, val, self.mirroring),\n            _ => (),\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        if let 0x8000..=0xFFFF = addr {\n            self.prg_rom_banks.set(0, val.into())\n        }\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Uxrom {}\nimpl Clock for Uxrom {}\nimpl Regional for Uxrom {}\nimpl Sram for Uxrom {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m003_cnrom.rs",
    "content": "//! `CNROM` (Mapper 003).\n//!\n//! <https://wiki.nesdev.org/w/index.php/CNROM>\n//! <https://wiki.nesdev.org/w/index.php/INES_Mapper_003>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, Sram},\n    mapper::{self, Map, Mapper},\n    mem::{Banks, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\n\n/// `CNROM` (Mapper 003).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Cnrom {\n    pub chr_rom: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub mirroring: Mirroring,\n    pub chr_banks: Banks,\n    pub mirror_prg_rom: bool,\n}\n\nimpl Cnrom {\n    const CHR_ROM_WINDOW: usize = 8 * 1024;\n\n    /// Load `Cnrom` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let chr_banks = Banks::new(0x0000, 0x1FFFF, chr_rom.len(), Self::CHR_ROM_WINDOW)?;\n        let mirror_prg_rom = prg_rom.len() <= 0x4000;\n        let cnrom = Self {\n            chr_rom,\n            prg_rom,\n            mirroring: cart.mirroring(),\n            chr_banks,\n            mirror_prg_rom,\n        };\n        Ok(cnrom.into())\n    }\n}\n\nimpl Map for Cnrom {\n    // PPU $0000..=$1FFF 8K CHR-ROM Banks Switchable\n    // CPU $8000..=$BFFF 16K PRG-ROM Bank Fixed\n    // CPU $C000..=$FFFF 16K PRG-ROM Bank Fixed or Bank 1 Mirror if only 16 KB PRG-ROM\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x8000..=0xBFFF => self.prg_rom[usize::from(addr & 0x3FFF)],\n            0xC000..=0xFFFF => {\n                let mirror = if self.mirror_prg_rom { 0x3FFF } else { 0x7FFF };\n                self.prg_rom[usize::from(addr & mirror)]\n            }\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        if let 0x8000..=0xFFFF = addr {\n            self.chr_banks.set(0, val.into())\n        }\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Cnrom {}\nimpl Clock for Cnrom {}\nimpl Regional for Cnrom {}\nimpl Sram for Cnrom {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m004_txrom.rs",
    "content": "//! `TxROM`/`MMC3` (Mapper 004).\n//!\n//! <https://wiki.nesdev.org/w/index.php/TxROM>\n//! <https://wiki.nesdev.org/w/index.php/MMC3>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, ResetKind, Sram},\n    fs,\n    mapper::{self, Map, Mapper},\n    mem::{Banks, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\nuse std::path::Path;\n\n/// MMC3 Revision.\n///\n/// See: <https://forums.nesdev.org/viewtopic.php?p=62546#p62546>\n///\n/// Known Revisions:\n///\n/// Conquest of the Crystal Palace (MMC3B S 9039 1 DB)\n/// Kickle Cubicle (MMC3B S 9031 3 DA)\n/// M.C. Kids (MMC3B S 9152 3 AB)\n/// Mega Man 3 (MMC3B S 9046 1 DB)\n/// Super Mario Bros. 3 (MMC3B S 9027 5 A)\n/// Startropics (MMC6B P 03'5)\n/// Batman (MMC3B 9006KP006)\n/// Golgo 13: The Mafat Conspiracy (MMC3B 9016KP051)\n/// Crystalis (MMC3B 9024KPO53)\n/// Legacy of the Wizard (MMC3A 8940EP)\n///\n/// Only major difference is the IRQ counter\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum Revision {\n    /// MMC3 Revision A\n    A,\n    /// MMC3 Revisions B & C\n    #[default]\n    BC,\n    /// Acclaims MMC3 clone - clocks on falling edge\n    Acc,\n}\n\n/// `TxROM` Registers.\n#[derive(Default, Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Regs {\n    pub bank_select: u8,\n    pub bank_values: [u8; 8],\n    pub irq_latch: u8,\n    pub irq_counter: u8,\n    pub irq_enabled: bool,\n    pub irq_pending: bool,\n    pub irq_reload: bool,\n    pub master_clock: u32,\n    pub a12_low_clock: u32,\n}\n\n/// `TxROM`/`MMC3` (Mapper 004).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Txrom {\n    pub chr: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub prg_ram: Memory<Box<[u8]>>,\n    pub ex_ram: Memory<Box<[u8]>>,\n    pub regs: Regs,\n    pub has_chr_ram: bool,\n    pub mirroring: Mirroring,\n    pub mapper_num: u16,\n    pub submapper_num: u8,\n    pub revision: Revision,\n    pub chr_banks: Banks,\n    pub prg_ram_banks: Banks,\n    pub prg_rom_banks: Banks,\n}\n\nimpl Txrom {\n    const PRG_WINDOW: usize = 8 * 1024;\n    const CHR_WINDOW: usize = 1024;\n    const CHR_WINDOW_76: usize = 2048;\n\n    const FOUR_SCREEN_RAM_SIZE: usize = 4 * 1024;\n    const PRG_RAM_SIZE: usize = 8 * 1024;\n    const CHR_RAM_SIZE: usize = 8 * 1024;\n\n    const PRG_MODE_MASK: u8 = 0x40; // Bit 6 of bank select\n    const CHR_INVERSION_MASK: u8 = 0x80; // Bit 7 of bank select\n\n    /// Create `Txrom` from `Cart`.\n    pub fn new(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n        chr_window: usize,\n    ) -> Result<Self, mapper::Error> {\n        let (chr, has_chr_ram) = cart.chr_rom_or_ram(chr_rom, Self::CHR_RAM_SIZE);\n        let prg_ram = Memory::with_ram_state(Self::PRG_RAM_SIZE, cart.ram_state);\n        let chr_banks = Banks::new(0x0000, 0x1FFF, chr.len(), chr_window)?;\n        let prg_ram_banks = Banks::new(0x6000, 0x7FFF, prg_ram.len(), Self::PRG_WINDOW)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_WINDOW)?;\n        let mut txrom = Self {\n            chr,\n            prg_rom,\n            prg_ram,\n            ex_ram: if cart.mirroring() == Mirroring::FourScreen {\n                Memory::new(Self::FOUR_SCREEN_RAM_SIZE)\n            } else {\n                Memory::empty()\n            },\n            regs: Regs::default(),\n            has_chr_ram,\n            mirroring: cart.mirroring(),\n            mapper_num: cart.mapper_num(),\n            submapper_num: cart.submapper_num(),\n            revision: Revision::BC, // TODO compare to known games\n            chr_banks,\n            prg_ram_banks,\n            prg_rom_banks,\n        };\n        let last_bank = txrom.prg_rom_banks.last();\n        txrom.prg_rom_banks.set(2, last_bank - 1);\n        txrom.prg_rom_banks.set(3, last_bank);\n        Ok(txrom)\n    }\n\n    /// Load `Txrom` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        Ok(Self::new(\n            cart,\n            chr_rom,\n            prg_rom,\n            if cart.mapper_num() == 76 {\n                Self::CHR_WINDOW_76\n            } else {\n                Self::CHR_WINDOW\n            },\n        )?\n        .into())\n    }\n\n    pub const fn bank_register(&self, index: usize) -> u8 {\n        self.regs.bank_values[index]\n    }\n\n    pub const fn set_revision(&mut self, rev: Revision) {\n        self.revision = rev;\n    }\n\n    #[inline]\n    const fn apply_prg_write_masks(&self, addr: &mut u16, val: &mut u8) {\n        // Redirects all 0x8000..=0xFFFF writes to 0x8000..=0x8001 as all other features do not\n        // exist for the corresponding mappers that call this\n        *addr &= 0x8001;\n        if *addr == 0x8000 {\n            // Disable CHR mode 1 and Prg mode 1\n            // PRG has the last two 8K banks fixed to the end.\n            // CHR assigns the left pattern table ($0000-$0FFF) two 2K banks, and the right pattern\n            // table ($1000-$1FFF) four 1K banks.\n            *val &= 0x3F;\n        }\n    }\n\n    pub fn update_prg_banks(&mut self) {\n        let prg_last = self.prg_rom_banks.last();\n        let prg_lo = self.regs.bank_values[6] as usize;\n        let prg_hi = self.regs.bank_values[7] as usize;\n        if self.regs.bank_select & Self::PRG_MODE_MASK == Self::PRG_MODE_MASK {\n            self.prg_rom_banks.set(0, prg_last - 1);\n            self.prg_rom_banks.set(1, prg_hi);\n            self.prg_rom_banks.set(2, prg_lo);\n        } else {\n            self.prg_rom_banks.set(0, prg_lo);\n            self.prg_rom_banks.set(1, prg_hi);\n            self.prg_rom_banks.set(2, prg_last - 1);\n        }\n        self.prg_rom_banks.set(3, prg_last);\n    }\n\n    pub fn set_chr_banks(&mut self, f: impl Fn(&mut Banks, &mut [u8])) {\n        f(&mut self.chr_banks, &mut self.regs.bank_values)\n    }\n\n    pub fn update_chr_banks(&mut self) {\n        match self.mapper_num {\n            76 => {\n                self.set_chr_banks(|banks, regs| {\n                    banks.set(0, regs[2] as usize);\n                    banks.set(1, regs[3] as usize);\n                    banks.set(2, regs[4] as usize);\n                    banks.set(3, regs[5] as usize);\n                });\n                return;\n            }\n            88 | 154 => {\n                self.set_chr_banks(|_, regs| {\n                    regs[0] &= 0x3F;\n                    regs[1] &= 0x3F;\n                    regs[2] |= 0x40;\n                    regs[3] |= 0x40;\n                    regs[4] |= 0x40;\n                    regs[5] |= 0x40;\n                });\n            }\n            _ => (),\n        }\n\n        // 1: two 2K banks at $1000-$1FFF, four 1 KB banks at $0000-$0FFF\n        // 0: two 2K banks at $0000-$0FFF, four 1 KB banks at $1000-$1FFF\n        let chr = self.regs.bank_values;\n        if self.regs.bank_select & Self::CHR_INVERSION_MASK == Self::CHR_INVERSION_MASK {\n            self.chr_banks.set(0, chr[2] as usize);\n            self.chr_banks.set(1, chr[3] as usize);\n            self.chr_banks.set(2, chr[4] as usize);\n            self.chr_banks.set(3, chr[5] as usize);\n            self.chr_banks.set_range(4, 5, (chr[0] & 0xFE) as usize);\n            self.chr_banks.set_range(6, 7, (chr[1] & 0xFE) as usize);\n        } else {\n            self.chr_banks.set_range(0, 1, (chr[0] & 0xFE) as usize);\n            self.chr_banks.set_range(2, 3, (chr[1] & 0xFE) as usize);\n            self.chr_banks.set(4, chr[2] as usize);\n            self.chr_banks.set(5, chr[3] as usize);\n            self.chr_banks.set(6, chr[4] as usize);\n            self.chr_banks.set(7, chr[5] as usize);\n        }\n    }\n\n    pub fn update_banks(&mut self) {\n        self.update_prg_banks();\n        self.update_chr_banks();\n    }\n\n    const fn is_a12_rising_edge(&mut self, addr: u16) -> bool {\n        if addr & 0x1000 > 0 {\n            // NOTE: This is technical 3 falling edges of M2 - but because the mapper doesn't have\n            // direct access to the CPUs clock, and is clocked after the PPU runs and calls this\n            // method, we're off by 1\n            let is_rising_edge = self.regs.a12_low_clock > 0\n                && self.regs.master_clock.wrapping_sub(self.regs.a12_low_clock) >= 4;\n            self.regs.a12_low_clock = 0;\n            return is_rising_edge;\n        } else if self.regs.a12_low_clock == 0 {\n            self.regs.a12_low_clock = self.regs.master_clock;\n        }\n        false\n    }\n}\n\nimpl Map for Txrom {\n    // PPU $0000..=$07FF (or $1000..=$17FF) 2K CHR-ROM/RAM Bank 1 Switchable --+\n    // PPU $0800..=$0FFF (or $1800..=$1FFF) 2K CHR-ROM/RAM Bank 2 Switchable --|-+\n    // PPU $1000..=$13FF (or $0000..=$03FF) 1K CHR-ROM/RAM Bank 3 Switchable --+ |\n    // PPU $1400..=$17FF (or $0400..=$07FF) 1K CHR-ROM/RAM Bank 4 Switchable --+ |\n    // PPU $1800..=$1BFF (or $0800..=$0BFF) 1K CHR-ROM/RAM Bank 5 Switchable ----+\n    // PPU $1C00..=$1FFF (or $0C00..=$0FFF) 1K CHR-ROM/RAM Bank 6 Switchable ----+\n    // PPU $2000..=$3EFF FourScreen Mirroring (optional)\n\n    // CPU $6000..=$7FFF 8K PRG-RAM Bank (optional)\n    // CPU $8000..=$9FFF (or $C000..=$DFFF) 8K PRG-ROM Bank 1 Switchable\n    // CPU $A000..=$BFFF 8K PRG-ROM Bank 2 Switchable\n    // CPU $C000..=$DFFF (or $8000..=$9FFF) 8K PRG-ROM Bank 3 Fixed to second-to-last Bank\n    // CPU $E000..=$FFFF 8K PRG-ROM Bank 4 Fixed to Last\n\n    /// Read a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_read(&mut self, addr: u16, ciram: &CIRam) -> u8 {\n        self.ppu_read(addr);\n        self.chr_peek(addr, ciram)\n    }\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr[self.chr_banks.translate(addr)],\n            0x2000..=0x3EFF => {\n                if self.mirroring == Mirroring::FourScreen {\n                    self.ex_ram[usize::from(addr & 0x1FFF)]\n                } else {\n                    ciram.peek(addr, self.mirroring)\n                }\n            }\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x6000..=0x7FFF => self.prg_ram[self.prg_ram_banks.translate(addr)],\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8, ciram: &mut CIRam) {\n        match addr {\n            0x0000..=0x1FFF => self.chr[self.chr_banks.translate(addr)] = val,\n            0x2000..=0x3EFF => {\n                if self.mirroring == Mirroring::FourScreen {\n                    self.ex_ram[usize::from(addr & 0x1FFF)] = val;\n                } else {\n                    ciram.write(addr, val, self.mirroring);\n                }\n            }\n            _ => (),\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    fn prg_write(&mut self, mut addr: u16, mut val: u8) {\n        match self.mapper_num {\n            76 | 88 | 95 | 206 => self.apply_prg_write_masks(&mut addr, &mut val),\n            154 => {\n                self.mirroring = if val & 0x40 == 0x40 {\n                    Mirroring::SingleScreenB\n                } else {\n                    Mirroring::SingleScreenA\n                };\n                self.apply_prg_write_masks(&mut addr, &mut val);\n            }\n            _ => (),\n        }\n\n        match addr {\n            0x6000..=0x7FFF => self.prg_ram[self.prg_ram_banks.translate(addr)] = val,\n            0x8000..=0xFFFF => {\n                //  7654 3210\n                // `CPMx xRRR`\n                //  |||   +++- Specify which bank register to update on next write to Bank Data register\n                //  |||        0: Select 2K CHR bank at PPU $0000-$07FF (or $1000-$17FF);\n                //  |||        1: Select 2K CHR bank at PPU $0800-$0FFF (or $1800-$1FFF);\n                //  |||        2: Select 1K CHR bank at PPU $1000-$13FF (or $0000-$03FF);\n                //  |||        3: Select 1K CHR bank at PPU $1400-$17FF (or $0400-$07FF);\n                //  |||        4: Select 1K CHR bank at PPU $1800-$1BFF (or $0800-$0BFF);\n                //  |||        5: Select 1K CHR bank at PPU $1C00-$1FFF (or $0C00-$0FFF);\n                //  |||        6: Select 8K PRG-ROM bank at $8000-$9FFF (or $C000-$DFFF);\n                //  |||        7: Select 8K PRG-ROM bank at $A000-$BFFF\n                //  ||+------- Nothing on the MMC3, see MMC6\n                //  |+-------- PRG-ROM bank mode (0: $8000-$9FFF swappable,\n                //  |                                $C000-$DFFF fixed to second-last bank;\n                //  |                             1: $C000-$DFFF swappable,\n                //  |                                $8000-$9FFF fixed to second-last bank)\n                //  +--------- CHR A12 inversion (0: two 2K banks at $0000-$0FFF,\n                //                                   four 1K banks at $1000-$1FFF;\n                //                                1: two 2K banks at $1000-$1FFF,\n                //                                   four 1K banks at $0000-$0FFF)\n                //\n\n                // Match only $8000/1, $A000/1, $C000/1, and $E000/1\n                match addr & 0xE001 {\n                    0x8000 => {\n                        self.regs.bank_select = val;\n                        self.update_banks();\n                    }\n                    0x8001 => {\n                        let bank = self.regs.bank_select & 0x07;\n                        self.regs.bank_values[bank as usize] = val;\n                        self.update_banks();\n                    }\n                    0xA000 => {\n                        if self.mirroring != Mirroring::FourScreen {\n                            self.mirroring = if val & 0x01 == 0x01 {\n                                Mirroring::Horizontal\n                            } else {\n                                Mirroring::Vertical\n                            };\n                            self.update_banks();\n                        }\n                    }\n                    0xA001 => {\n                        // TODO RAM protect? Might conflict with MMC6\n                    }\n                    // IRQ\n                    0xC000 => self.regs.irq_latch = val,\n                    0xC001 => self.regs.irq_reload = true,\n                    0xE000 => {\n                        self.regs.irq_enabled = false;\n                        self.regs.irq_pending = false;\n                    }\n                    0xE001 => self.regs.irq_enabled = true,\n                    _ => unreachable!(\"impossible address\"),\n                }\n            }\n            _ => (),\n        }\n\n        if self.mapper_num == 95 && addr & 0x01 == 0x01 {\n            let nametable1 = (self.bank_register(0) >> 5) & 0x01;\n            let nametable2 = (self.bank_register(1) >> 5) & 0x01;\n            self.mirroring = match (nametable1, nametable2) {\n                (0, 0) => Mirroring::SingleScreenA,\n                (1, 1) => Mirroring::SingleScreenB,\n                _ => Mirroring::Horizontal,\n            };\n        }\n    }\n\n    /// Synchronize a read from a PPU address.\n    fn ppu_read(&mut self, addr: u16) {\n        // Clock on PPU A12 rising edge\n        if self.is_a12_rising_edge(addr) {\n            let counter = self.regs.irq_counter;\n            if self.regs.irq_counter == 0 || self.regs.irq_reload {\n                self.regs.irq_counter = self.regs.irq_latch;\n            } else {\n                self.regs.irq_counter -= 1;\n            }\n            if self.revision == Revision::A {\n                if (counter > 0 || self.regs.irq_reload)\n                    && self.regs.irq_counter == 0\n                    && self.regs.irq_enabled\n                {\n                    self.regs.irq_pending = true;\n                }\n            } else if self.regs.irq_counter == 0 && self.regs.irq_enabled {\n                self.regs.irq_pending = true;\n            }\n            self.regs.irq_reload = false;\n        }\n    }\n\n    /// Whether an IRQ is pending acknowledgement.\n    fn irq_pending(&self) -> bool {\n        self.regs.irq_pending\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Txrom {\n    fn reset(&mut self, _kind: ResetKind) {\n        self.regs = Regs::default();\n        self.update_banks();\n        self.update_chr_banks();\n    }\n}\n\nimpl Clock for Txrom {\n    fn clock(&mut self) {\n        self.regs.master_clock = self.regs.master_clock.wrapping_add(1);\n    }\n}\n\nimpl Sram for Txrom {\n    /// Save RAM to a given path.\n    fn save(&self, path: impl AsRef<Path>) -> fs::Result<()> {\n        fs::save(path.as_ref(), &self.prg_ram)\n    }\n\n    /// Load save RAM from a given path.\n    fn load(&mut self, path: impl AsRef<Path>) -> fs::Result<()> {\n        fs::load(path.as_ref()).map(|data: Memory<Box<[u8]>>| self.prg_ram = data)\n    }\n}\n\nimpl Regional for Txrom {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m005_exrom.rs",
    "content": "//! `ExROM`/`MMC5` (Mapper 5).\n//!\n//! <https://wiki.nesdev.org/w/index.php/ExROM>\n//! <https://wiki.nesdev.org/w/index.php/MMC5>\n\nuse crate::{\n    apu::{\n        PULSE_TABLE, TND_TABLE,\n        dmc::Dmc,\n        pulse::{OutputFreq, Pulse, PulseChannel},\n    },\n    cart::Cart,\n    common::{Clock, NesRegion, Regional, Reset, ResetKind, Sample, Sram},\n    cpu::Cpu,\n    fs,\n    mapper::{self, Map, Mapper},\n    mem::{Banks, Memory},\n    ppu::{self, CIRam, Mirroring, PpuAddr},\n};\nuse bitflags::bitflags;\nuse serde::{Deserialize, Serialize};\nuse std::path::Path;\nuse tracing::warn;\n\n/// PRG banking mode.\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[must_use]\npub enum PrgMode {\n    Bank32k,\n    Bank16k,\n    Bank16_8k,\n    Bank8k,\n}\n\n/// CHR banking mode.\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[must_use]\npub enum ChrMode {\n    Bank8k,\n    Bank4k,\n    Bank2k,\n    Bank1k,\n}\n\n/// CHR bank registers.\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[must_use]\npub enum ChrBank {\n    Spr,\n    Bg,\n}\n\nbitflags! {\n    #[derive(Default, Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)]\n    #[must_use]\n    pub struct ExRamRW: u8 {\n        const W = 0x01;\n        const R = 0x02;\n        const RW = Self::R.bits() | Self::W.bits();\n    }\n}\n\n/// Exram mode registers.\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct ExRamMode {\n    pub bits: u8,\n    pub nametable: bool,\n    pub attr: bool,\n    pub rw: ExRamRW,\n}\n\nimpl Default for ExRamMode {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl ExRamMode {\n    pub const fn new() -> Self {\n        Self {\n            bits: 0x00,\n            nametable: false,\n            attr: false,\n            rw: ExRamRW::W,\n        }\n    }\n\n    pub const fn set(&mut self, val: u8) {\n        let val = val & 0b11;\n        self.bits = val;\n        self.nametable = val <= 0b01;\n        self.attr = val == 0b01;\n        self.rw = match val {\n            0b00 | 0b01 => ExRamRW::W,\n            0b10 => ExRamRW::RW,\n            _ => ExRamRW::R,\n        };\n    }\n}\n\n/// Exram nametable select.\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[must_use]\npub enum Nametable {\n    ScreenA,\n    ScreenB,\n    ExRam,\n    Fill,\n}\n\n/// Exram nametable mapping registers.\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct NametableMapping {\n    pub mode: u8,\n    pub select: [Nametable; 4],\n}\n\nimpl Default for NametableMapping {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl NametableMapping {\n    pub const fn new() -> Self {\n        Self {\n            mode: 0x00,\n            select: [Nametable::ScreenA; 4],\n        }\n    }\n\n    pub fn set(&mut self, val: u8) {\n        let nametable = |val: u8| match val & 0b11 {\n            0b00 => Nametable::ScreenA,\n            0b01 => Nametable::ScreenB,\n            0b10 => Nametable::ExRam,\n            _ => Nametable::Fill,\n        };\n        self.mode = val;\n        self.select = [\n            nametable(val),\n            nametable(val >> 2),\n            nametable(val >> 4),\n            nametable(val >> 6),\n        ];\n    }\n}\n\n/// Exram fill registers.\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Fill {\n    pub tile: u8,    // $5106\n    pub attr: usize, // $5107\n}\n\nimpl Default for Fill {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Fill {\n    pub const fn new() -> Self {\n        Self {\n            attr: 0x03,\n            tile: 0xFF,\n        }\n    }\n}\n\n/// Vertical split side.\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[must_use]\npub enum Side {\n    Left,\n    Right,\n}\n\n/// Vertical split mode.\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct VSplit {\n    pub mode: u8,      // $5200 [ES.T TTTT]\n    pub enabled: bool, // $5200 [E... ....]\n    pub side: Side,    // $5200 [.S.. ....]\n    pub tile: u8,      // $5200 [...T TTTT]\n    pub scroll: u8,    // $5201\n    pub bank: u8,      // $5202\n    pub in_region: bool,\n}\n\nimpl Default for VSplit {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl VSplit {\n    pub const fn new() -> Self {\n        Self {\n            mode: 0x00,\n            enabled: false,\n            side: Side::Left,\n            tile: 0x00,\n            scroll: 0x00,\n            bank: 0x00,\n            in_region: false,\n        }\n    }\n}\n\n/// `ExROM` registers.\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Regs {\n    pub prg_mode: PrgMode,                   // $5100\n    pub chr_mode: ChrMode,                   // $5101\n    pub prg_ram_protect: [u8; 2],            // $5102 - $5103\n    pub exram_mode: ExRamMode,               // $5104\n    pub nametable_mapping: NametableMapping, // $5105\n    pub fill: Fill,                          // $5106 - $5107\n    pub prg_banks: [usize; 5],               // $5113 - $5117\n    pub chr_banks: [usize; 16],              // $5120 - $512B\n    pub chr_hi: usize,                       // $5130\n    pub vsplit: VSplit,                      // $5200 - $5202\n    pub irq_scanline: u16,                   // $5203: Write $00 to disable IRQs\n    pub irq_enabled: bool,                   // $5204\n    pub irq_pending: bool,\n    pub multiplicand: u8, // $5205: write\n    pub multiplier: u8,   // $5206: write\n    pub mult_result: u16, // $5205: read lo, $5206: read hi\n}\n\nimpl Default for Regs {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Regs {\n    pub const fn new() -> Self {\n        Self {\n            prg_mode: PrgMode::Bank8k,\n            chr_mode: ChrMode::Bank1k,\n            prg_ram_protect: [0x00; 2],\n            exram_mode: ExRamMode::new(),\n            nametable_mapping: NametableMapping::new(),\n            fill: Fill::new(),\n            prg_banks: [0x00; 5],\n            chr_banks: [0x00; 16],\n            chr_hi: 0x00,\n            vsplit: VSplit::new(),\n            irq_scanline: 0x00,\n            irq_enabled: false,\n            irq_pending: false,\n            multiplicand: 0xFF,\n            multiplier: 0xFF,\n            mult_result: 0xFE01, // e.g. 0xFF * 0xFF\n        }\n    }\n}\n\n/// `ExROM` IRQ state.\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct IrqState {\n    pub in_frame: bool,\n    pub prev_addr: Option<u16>,\n    pub match_count: u8,\n    pub pending: bool,\n}\n\n/// Internally tracked PPU status.\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct PpuStatus {\n    pub fetch_count: u32,\n    pub reading: bool,\n    pub idle_count: u8,\n    pub sprite8x16: bool, // $2000 PPUCTRL: false = 8x8, true = 8x16\n    pub rendering: bool,\n    pub scanline: u16,\n}\n\n/// `ExROM`/`MMC5` (Mapper 5).\n#[derive(Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Exrom {\n    pub chr_rom: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub prg_ram: Memory<Box<[u8]>>,\n    pub ex_ram: Memory<Box<[u8]>>,\n    pub regs: Regs,\n    pub mirroring: Mirroring,\n    pub ppu_status: PpuStatus,\n    pub irq_state: IrqState,\n    pub chr_banks: Banks,\n    pub prg_ram_banks: Banks,\n    pub prg_rom_banks: Banks,\n    pub tile_cache: u16,\n    pub last_chr_write: ChrBank,\n    pub region: NesRegion,\n    pub pulse1: Pulse,\n    pub pulse2: Pulse,\n    pub dmc: Dmc,\n    pub dmc_mode: u8,\n    pub cpu_cycle: usize,\n    pub pulse_timer: f32,\n}\n\nimpl Exrom {\n    const PRG_WINDOW: usize = 0x2000;\n    const PRG_RAM_SIZE: usize = 0x10000; // Provide 64K since mappers don't always specify\n    const EXRAM_SIZE: usize = 0x0400;\n    const CHR_WINDOW: usize = 0x0400;\n\n    const ROM_SELECT_MASK: usize = 0x80; // High bit targets ROM bank switching\n    const BANK_MASK: usize = 0x7F; // Ignore high bit for ROM select\n\n    const SPR_FETCH_START: u32 = 64;\n    const SPR_FETCH_END: u32 = 81;\n\n    // This conveniently mirrors a 2-bit palette attribute to all four indexes\n    // https://www.nesdev.org/wiki/MMC5#Fill-mode_color_($5107)\n    const ATTR_MIRROR: [u8; 4] = [0x00, 0x55, 0xAA, 0xFF];\n\n    // // TODO: See about generating these using oncecell\n    // const ATTR_LOC: [u8; 256] = [\n    //     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,\n    //     0x07, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,\n    //     0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x08, 0x09, 0x0A, 0x0B, 0x0C,\n    //     0x0D, 0x0E, 0x0F, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x08, 0x09, 0x0A, 0x0B,\n    //     0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12,\n    //     0x13, 0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x10, 0x11,\n    //     0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x18,\n    //     0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,\n    //     0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,\n    //     0x27, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,\n    //     0x26, 0x27, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C,\n    //     0x2D, 0x2E, 0x2F, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x28, 0x29, 0x2A, 0x2B,\n    //     0x2C, 0x2D, 0x2E, 0x2F, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,\n    //     0x33, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31,\n    //     0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,\n    //     0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,\n    //     0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E,\n    //     0x3F,\n    // ];\n    // const ATTR_SHIFT: [u8; 128] = [\n    //     0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0,\n    //     2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2,\n    //     0, 0, 2, 2, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4,\n    //     6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6, 4, 4, 6, 6,\n    //     4, 4, 6, 6, 4, 4, 6, 6,\n    // ];\n\n    /// Load `Exrom` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let prg_ram = Memory::with_ram_state(Self::PRG_RAM_SIZE, cart.ram_state);\n        let chr_banks = Banks::new(0x0000, 0x1FFF, chr_rom.len(), Self::CHR_WINDOW)?;\n        let prg_ram_banks = Banks::new(0x6000, 0xFFFF, prg_ram.len(), Self::PRG_WINDOW)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_WINDOW)?;\n        let mut exrom = Self {\n            chr_rom,\n            prg_rom,\n            prg_ram,\n            ex_ram: Memory::new(Self::EXRAM_SIZE),\n            regs: Regs::new(),\n            mirroring: cart.mirroring(),\n            irq_state: IrqState {\n                in_frame: false,\n                prev_addr: None,\n                match_count: 0,\n                pending: false,\n            },\n            ppu_status: PpuStatus {\n                fetch_count: 0x00,\n                reading: false,\n                idle_count: 0x00,\n                sprite8x16: false,\n                rendering: false,\n                scanline: 0x0000,\n            },\n            chr_banks,\n            prg_ram_banks,\n            prg_rom_banks,\n            tile_cache: 0,\n            last_chr_write: ChrBank::Spr,\n            region: cart.region(),\n            pulse1: Pulse::new(PulseChannel::One, OutputFreq::Ultrasonic),\n            pulse2: Pulse::new(PulseChannel::Two, OutputFreq::Ultrasonic),\n            dmc: Dmc::new(cart.region()),\n            dmc_mode: 0x01, // Default to read mode\n            cpu_cycle: 0,\n            pulse_timer: 0.0,\n        };\n        exrom.regs.prg_banks[4] = exrom.prg_rom_banks.last() | Self::ROM_SELECT_MASK;\n        exrom.update_prg_banks();\n        Ok(exrom.into())\n    }\n\n    //              $6000   $8000   $A000   $C000   $E000\n    //            +-------+-------------------------------+\n    // P=%00:     | $5113 |           <<$5117>>           |\n    //            +-------+-------------------------------+\n    // P=%01:     | $5113 |    <$5115>    |    <$5117>    |\n    //            +-------+---------------+-------+-------+\n    // P=%10:     | $5113 |    <$5115>    | $5116 | $5117 |\n    //            +-------+---------------+-------+-------+\n    // P=%11:     | $5113 | $5114 | $5115 | $5116 | $5117 |\n    //            +-------+-------+-------+-------+-------+\n    pub fn update_prg_banks(&mut self) {\n        let mode = self.regs.prg_mode;\n        let banks = self.regs.prg_banks;\n\n        self.prg_ram_banks.set(0, banks[0]); // $5113 always selects RAM\n        match mode {\n            // $5117 always selects ROM\n            PrgMode::Bank32k => self.prg_rom_banks.set_range(0, 3, banks[4]),\n            PrgMode::Bank16k => {\n                self.set_prg_bank_range(0, 1, banks[2]);\n                self.prg_rom_banks\n                    .set_range(2, 3, banks[4] & Self::BANK_MASK);\n            }\n            PrgMode::Bank16_8k => {\n                self.set_prg_bank_range(0, 1, banks[2]);\n                self.set_prg_bank_range(2, 2, banks[3]);\n                self.prg_rom_banks.set(3, banks[4] & Self::BANK_MASK);\n            }\n            PrgMode::Bank8k => {\n                self.set_prg_bank_range(0, 0, banks[1]);\n                self.set_prg_bank_range(1, 1, banks[2]);\n                self.set_prg_bank_range(2, 2, banks[3]);\n                self.prg_rom_banks.set(3, banks[4] & Self::BANK_MASK);\n            }\n        };\n    }\n\n    pub fn set_prg_bank_range(&mut self, start: usize, end: usize, bank: usize) {\n        let rom = bank & Self::ROM_SELECT_MASK == Self::ROM_SELECT_MASK;\n        let bank = bank & Self::BANK_MASK;\n        if rom {\n            self.prg_rom_banks.set_range(start, end, bank);\n        } else {\n            self.prg_ram_banks.set_range(start + 1, end + 1, bank);\n        }\n    }\n\n    pub fn rom_select(&self, addr: u16) -> bool {\n        let mode = self.regs.prg_mode;\n        match addr {\n            0x6000..=0x7FFF => false,\n            0xE000..=0xFFFF => true,\n            _ => {\n                if mode == PrgMode::Bank32k {\n                    true\n                } else {\n                    use PrgMode::{Bank8k, Bank16_8k, Bank16k};\n                    let banks = self.regs.prg_banks;\n                    let bank = match (addr, mode) {\n                        (0x8000..=0x9FFF, Bank8k) => banks[1],\n                        (0x8000..=0xBFFF, Bank16k | Bank16_8k) | (0xA000..=0xBFFF, Bank8k) => {\n                            banks[2]\n                        }\n                        (0xC000..=0xDFFF, Bank8k | Bank16_8k) => banks[3],\n                        (0xC000..=0xDFFF, Bank16k) => banks[4],\n                        _ => 0x00,\n                    };\n                    bank & Self::ROM_SELECT_MASK == Self::ROM_SELECT_MASK\n                }\n            }\n        }\n    }\n\n    // 'A' Set (Sprites):\n    //               $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00\n    //             +---------------------------------------------------------------+\n    //   C=%00:    |                             $5127                             |\n    //             +---------------------------------------------------------------+\n    //   C=%01:    |             $5123             |             $5127             |\n    //             +-------------------------------+-------------------------------+\n    //   C=%10:    |     $5121     |     $5123     |     $5125     |     $5127     |\n    //             +---------------+---------------+---------------+---------------+\n    //   C=%11:    | $5120 | $5121 | $5122 | $5123 | $5124 | $5125 | $5126 | $5127 |\n    //             +-------+-------+-------+-------+-------+-------+-------+-------+\n    //\n    // 'B' Set (BG):\n    //               $0000   $0400   $0800   $0C00   $1000   $1400   $1800   $1C00\n    //             +-------------------------------+-------------------------------+\n    //   C=%00:    |                             $512B                             |\n    //             +-------------------------------+-------------------------------+\n    //   C=%01:    |             $512B             |             $512B             |\n    //             +-------------------------------+-------------------------------+\n    //   C=%10:    |     $5129     |     $512B     |     $5129     |     $512B     |\n    //             +---------------+---------------+---------------+---------------+\n    //   C=%11:    | $5128 | $5129 | $512A | $512B | $5128 | $5129 | $512A | $512B |\n    //             +-------+-------+-------+-------+-------+-------+-------+-------+\n    pub fn update_chr_banks(&mut self, chr_bank: ChrBank) {\n        let hi = self.regs.chr_hi;\n        let banks = match chr_bank {\n            ChrBank::Spr => &self.regs.chr_banks[0..8],\n            ChrBank::Bg => &self.regs.chr_banks[8..16],\n        };\n        // CHR banks are in actual page sizes which means they need to be shifted appropriately\n        match self.regs.chr_mode {\n            ChrMode::Bank8k => self.chr_banks.set_range(0, 7, hi | (banks[7] << 3)),\n            ChrMode::Bank4k => {\n                self.chr_banks.set_range(0, 3, hi | (banks[3] << 2));\n                self.chr_banks.set_range(4, 7, hi | (banks[7] << 2));\n            }\n            ChrMode::Bank2k => {\n                self.chr_banks.set_range(0, 1, hi | (banks[1] << 1));\n                self.chr_banks.set_range(2, 3, hi | (banks[3] << 1));\n                self.chr_banks.set_range(4, 5, hi | (banks[5] << 1));\n                self.chr_banks.set_range(6, 7, hi | (banks[7] << 1));\n            }\n            ChrMode::Bank1k => {\n                self.chr_banks.set(0, hi | banks[0]);\n                self.chr_banks.set(1, hi | banks[1]);\n                self.chr_banks.set(2, hi | banks[2]);\n                self.chr_banks.set(3, hi | banks[3]);\n                self.chr_banks.set(4, hi | banks[4]);\n                self.chr_banks.set(5, hi | banks[5]);\n                self.chr_banks.set(6, hi | banks[6]);\n                self.chr_banks.set(7, hi | banks[7]);\n            }\n        };\n    }\n\n    pub fn read_ex_ram(&self, addr: u16) -> u8 {\n        self.ex_ram[(addr & 0x03FF) as usize]\n    }\n\n    pub fn write_ex_ram(&mut self, addr: u16, val: u8) {\n        self.ex_ram[(addr & 0x03FF) as usize] = val;\n    }\n\n    pub const fn inc_fetch_count(&mut self) {\n        self.ppu_status.fetch_count += 1;\n    }\n\n    pub const fn fetch_count(&self) -> u32 {\n        self.ppu_status.fetch_count\n    }\n\n    pub const fn sprite8x16(&self) -> bool {\n        self.ppu_status.sprite8x16\n    }\n\n    pub fn spr_fetch(&self) -> bool {\n        (Self::SPR_FETCH_START..Self::SPR_FETCH_END).contains(&self.fetch_count())\n    }\n\n    pub const fn nametable_select(&self, addr: u16) -> Nametable {\n        self.regs.nametable_mapping.select[((addr >> 10) & 0x03) as usize]\n    }\n}\n\nimpl Map for Exrom {\n    // CHR mode 0\n    // PPU $0000..=$1FFF 8K switchable CHR bank\n    //\n    // CHR mode 1\n    // PPU $0000..=$0FFF 4K switchable CHR bank\n    // PPU $1000..=$1FFF 4K switchable CHR bank\n    //\n    // CHR mode 2\n    // PPU $0000..=$07FF 2K switchable CHR bank\n    // PPU $0800..=$0FFF 2K switchable CHR bank\n    // PPU $1000..=$17FF 2K switchable CHR bank\n    // PPU $1800..=$1FFF 2K switchable CHR bank\n    //\n    // CHR mode 3\n    // PPU $0000..=$03FF 1K switchable CHR bank\n    // PPU $0400..=$07FF 1K switchable CHR bank\n    // PPU $0800..=$0BFF 1K switchable CHR bank\n    // PPU $0C00..=$0FFF 1K switchable CHR bank\n    // PPU $1000..=$13FF 1K switchable CHR bank\n    // PPU $1400..=$17FF 1K switchable CHR bank\n    // PPU $1800..=$1BFF 1K switchable CHR bank\n    // PPU $1C00..=$1FFF 1K switchable CHR bank\n    //\n    // PPU $2000..=$3EFF Up to 3 Nametables + Fill mode\n    //\n    // PRG mode 0\n    // CPU $6000..=$7FFF 8K switchable PRG RAM bank\n    // CPU $8000..=$FFFF 32K switchable PRG ROM bank\n    //\n    // PRG mode 1\n    // CPU $6000..=$7FFF 8K switchable PRG RAM bank\n    // CPU $8000..=$BFFF 16K switchable PRG ROM/RAM bank\n    // CPU $C000..=$FFFF 16K switchable PRG ROM bank\n    //\n    // PRG mode 2\n    // CPU $6000..=$7FFF 8K switchable PRG RAM bank\n    // CPU $8000..=$BFFF 16K switchable PRG ROM/RAM bank\n    // CPU $C000..=$DFFF 8K switchable PRG ROM/RAM bank\n    // CPU $E000..=$FFFF 8K switchable PRG ROM bank\n    //\n    // PRG mode 3\n    // CPU $6000..=$7FFF 8K switchable PRG RAM bank\n    // CPU $8000..=$9FFF 8K switchable PRG ROM/RAM bank\n    // CPU $A000..=$BFFF 8K switchable PRG ROM/RAM bank\n    // CPU $C000..=$DFFF 8K switchable PRG ROM/RAM bank\n    // CPU $E000..=$FFFF 8K switchable PRG ROM bank\n\n    /// Read a byte from CHR-ROM/RAM at a given address.\n    fn chr_read(&mut self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => {\n                self.inc_fetch_count();\n                if self.sprite8x16() {\n                    match self.fetch_count() {\n                        Self::SPR_FETCH_START => self.update_chr_banks(ChrBank::Spr),\n                        Self::SPR_FETCH_END => self.update_chr_banks(ChrBank::Bg),\n                        _ => (),\n                    }\n                }\n            }\n            0x2000..=0x3EFF => {\n                let is_attr = addr.is_attr();\n                // Cache BG tile fetch for later attribute byte fetch\n                if self.regs.exram_mode.attr && !is_attr && !self.spr_fetch() {\n                    self.tile_cache = addr & 0x03FF;\n                }\n\n                // TODO: Detect split\n                // if self.regs.vsplit.in_region && !is_attr {\n                //     self.regs.vsplit.tile = ((self.regs.vsplit.scroll & 0xF8) << 2)\n                //         | ((self.fetch_count() / 4) & 0x1F) as u8;\n                // }\n\n                // Monitor tile fetches to trigger IRQs\n                // https://wiki.nesdev.org/w/index.php?title=MMC5#Scanline_Detection_and_Scanline_IRQ\n                let status = &mut self.ppu_status;\n                let irq_state = &mut self.irq_state;\n                // Wait for three consecutive fetches to match the same address, which means we're\n                // at the end of the render scanlines fetching dummy NT bytes\n                if addr <= 0x2FFF && Some(addr) == irq_state.prev_addr {\n                    irq_state.match_count += 1;\n                    status.fetch_count = 0;\n                    if irq_state.match_count == 2 {\n                        if irq_state.in_frame {\n                            // Scanline IRQ detected\n                            status.scanline += 1;\n                            if status.scanline == self.regs.irq_scanline {\n                                irq_state.pending = true;\n                                if self.regs.irq_enabled {\n                                    self.regs.irq_pending = true;\n                                }\n                            }\n                        } else {\n                            irq_state.in_frame = true;\n                            status.scanline = 0;\n                        }\n                    }\n                } else {\n                    irq_state.match_count = 0;\n                }\n                irq_state.prev_addr = Some(addr);\n                status.reading = true;\n            }\n            _ => (),\n        }\n        self.chr_peek(addr, ciram)\n    }\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => {\n                if self.regs.exram_mode.attr && !self.spr_fetch() {\n                    // Bits 6-7 of 4K CHR bank. Already shifted left by 8\n                    let bank_hi = self.regs.chr_hi << 10;\n                    // Bits 0-5 of 4k CHR bank\n                    let bank_lo = ((self.read_ex_ram(self.tile_cache) & 0x3F) as usize) << 12;\n                    let addr = bank_hi | bank_lo | (addr as usize) & 0x0FFF;\n                    self.chr_rom[addr]\n                } else {\n                    self.chr_rom[self.chr_banks.translate(addr)]\n                }\n            }\n            0x2000..=0x3EFF => {\n                let is_attr = addr.is_attr();\n                // TODO: vsplit\n                // if self.regs.vsplit.in_region {\n                //     if is_attr {\n                //         // let addr =\n                //         //     Self::ATTR_OFFSET | u16::from(ATTR_LOC[(self.regs.vsplit.tile as usize) >> 2]);\n                //         // let attr = self.read_exram(addr - 0x2000) as usize;\n                //         // let shift = ATTR_SHIFT[(self.regs.vsplit.tile as usize) & 0x7F] as usize;\n                //         // MappedRead::Data(ATTR_BITS[(attr >> shift) & 0x03])\n                //     } else {\n                //         MappedRead::Data(self.read_exram(self.regs.vsplit.tile.into()))\n                //     }\n                // }\n                if self.regs.exram_mode.attr && is_attr && !self.spr_fetch() {\n                    // ExAttr mode returns attr bits for all nametables, regardless of mapping\n                    let attr = (self.read_ex_ram(self.tile_cache) >> 6) & 0x03;\n                    Self::ATTR_MIRROR[attr as usize]\n                } else {\n                    let nametable_mode = self.regs.exram_mode.nametable;\n                    match self.nametable_select(addr) {\n                        Nametable::ScreenA => ciram[(addr & 0x03FF).into()],\n                        Nametable::ScreenB => {\n                            ciram[(ppu::size::NAMETABLE | (addr & 0x03FF)).into()]\n                        }\n                        Nametable::ExRam if nametable_mode => self.read_ex_ram(addr),\n                        Nametable::Fill if nametable_mode => {\n                            if is_attr {\n                                Self::ATTR_MIRROR[self.regs.fill.attr & 0x03]\n                            } else {\n                                self.regs.fill.tile\n                            }\n                        }\n                        // If nametable mode is not set, zero is read back\n                        _ => 0,\n                    }\n                }\n            }\n            _ => 0,\n        }\n    }\n\n    /// Read a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_read(&mut self, addr: u16) -> u8 {\n        match addr {\n            0xFFFA | 0xFFFB => {\n                self.irq_state.in_frame = false; // NMI clears in_frame\n                self.irq_state.prev_addr = None;\n                self.irq_state.pending = false;\n                self.regs.irq_pending = false;\n            }\n            _ => (),\n        }\n        let val = self.prg_peek(addr);\n        match addr {\n            0x5204 => {\n                self.irq_state.pending = false;\n                self.regs.irq_pending = false;\n            }\n            0x5010 => self.dmc.irq_pending = false,\n            _ => (),\n        }\n        val\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x5010 => {\n                // [I... ...M] DMC\n                // I = IRQ (0 = No IRQ triggered. 1 = IRQ was triggered.) Reading $5010 acknowledges the IRQ and clears this flag.\n                // M = Mode select (0 = write mode. 1 = read mode.)\n                (u8::from(self.dmc.irq_pending) << 7) | self.dmc_mode\n            }\n            0x5100 => self.regs.prg_mode as u8,\n            0x5101 => self.regs.chr_mode as u8,\n            0x5104 => self.regs.exram_mode.bits,\n            0x5105 => self.regs.nametable_mapping.mode,\n            0x5106 => self.regs.fill.tile,\n            0x5107 => self.regs.fill.attr as u8,\n            0x5015 => {\n                // [.... ..BA]   Length status for Pulse 1 (A), 2 (B)\n                let mut status = 0x00;\n                if self.pulse1.length.counter > 0 {\n                    status |= 0x01;\n                }\n                if self.pulse2.length.counter > 0 {\n                    status |= 0x02;\n                }\n                status\n            }\n            0x5113..=0x5117 => self.regs.prg_banks[(addr - 0x5113) as usize] as u8,\n            0x5120..=0x512B => self.regs.chr_banks[(addr - 0x5120) as usize] as u8,\n            0x5130 => self.regs.chr_hi as u8,\n            0x5200 => self.regs.vsplit.mode,\n            0x5201 => self.regs.vsplit.scroll,\n            0x5202 => self.regs.vsplit.bank,\n            0x5203 => self.regs.irq_scanline as u8,\n            0x5204 => {\n                // $5204:  [PI.. ....]\n                //   P = IRQ currently pending\n                //   I = \"In Frame\" signal\n\n                // Reading $5204 will clear the pending flag (acknowledging the IRQ).\n                // Clearing is done in the read() function\n                (u8::from(self.regs.irq_pending) << 7) | (u8::from(self.irq_state.in_frame) << 6)\n            }\n            0x5205 => (self.regs.mult_result & 0xFF) as u8,\n            0x5206 => ((self.regs.mult_result >> 8) & 0xFF) as u8,\n            0x5C00..=0x5FFF if self.regs.exram_mode.rw != ExRamRW::W => {\n                // Nametable/Attr modes are not used for RAM, thus are not readable\n                self.read_ex_ram(addr)\n            }\n            0x6000..=0xDFFF => {\n                if self.rom_select(addr) {\n                    self.prg_rom[self.prg_rom_banks.translate(addr)]\n                } else {\n                    self.prg_ram[self.prg_ram_banks.translate(addr)]\n                }\n            }\n            0xE000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8, ciram: &mut CIRam) {\n        match addr {\n            0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)] = val,\n            0x2000..=0x3EFF => match self.nametable_select(addr) {\n                Nametable::ScreenA => ciram[(addr & 0x03FF).into()] = val,\n                Nametable::ScreenB => ciram[(ppu::size::NAMETABLE | (addr & 0x03FF)).into()] = val,\n                Nametable::ExRam if self.regs.exram_mode.nametable => {\n                    self.write_ex_ram(addr, val);\n                }\n                _ => (),\n            },\n            _ => (),\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        match addr {\n            0x5000 => self.pulse1.write_ctrl(val),\n            // 0x5001 Has no effect since there is no Sweep unit\n            0x5002 => self.pulse1.write_timer_lo(val),\n            0x5003 => self.pulse1.write_timer_hi(val),\n            0x5004 => self.pulse2.write_ctrl(val),\n            // 0x5005 Has no effect since there is no Sweep unit\n            0x5006 => self.pulse2.write_timer_lo(val),\n            0x5007 => self.pulse2.write_timer_hi(val),\n            0x5010 => {\n                // [I... ...M] DMC\n                //   I = PCM IRQ enable (1 = enabled.)\n                //   M = Mode select (0 = write mode. 1 = read mode.)\n                self.dmc_mode = val & 0x01;\n                self.dmc.irq_enabled = val & 0x80 == 0x80;\n            }\n            0x5011 if self.dmc_mode == 0 && val != 0x00 => {\n                // [DDDD DDDD] PCM Data\n                // Write mode - writing $00 has no effect\n                self.dmc.write_output(val);\n            }\n            0x5015 => {\n                //  [.... ..BA]   Enable flags for Pulse 1 (A), 2 (B)  (0=disable, 1=enable)\n                self.pulse1.set_enabled(val & 0x01 == 0x01);\n                self.pulse2.set_enabled(val & 0x02 == 0x02);\n            }\n            0x5100 => {\n                // [.... ..PP] PRG Mode\n                self.regs.prg_mode = match val & 0x03 {\n                    0 => PrgMode::Bank32k,\n                    1 => PrgMode::Bank16k,\n                    2 => PrgMode::Bank16_8k,\n                    3 => PrgMode::Bank8k,\n                    _ => {\n                        warn!(\"invalid PrgMode value: ${:02X}\", val);\n                        self.regs.prg_mode\n                    }\n                };\n                self.update_prg_banks();\n            }\n            0x5101 => {\n                // [.... ..CC] CHR Mode\n                if self.regs.exram_mode.attr {\n                    // Bank switching is ignored in extended attribute mode, banks are always 4K\n                    self.regs.chr_mode = ChrMode::Bank4k;\n                } else {\n                    self.regs.chr_mode = match val & 0x03 {\n                        0 => ChrMode::Bank8k,\n                        1 => ChrMode::Bank4k,\n                        2 => ChrMode::Bank2k,\n                        3 => ChrMode::Bank1k,\n                        _ => {\n                            warn!(\"invalid ChrMode value: ${:02X}\", val);\n                            self.regs.chr_mode\n                        }\n                    };\n                }\n                self.update_chr_banks(self.last_chr_write);\n            }\n            0x5102 | 0x5103 => {\n                // To allow writing to PRG-RAM you must set:\n                //    A=%10\n                //    B=%01\n                // Any other value will prevent PRG-RAM writing.\n                // [.... ..AA]    PRG-RAM Protect A\n                // [.... ..BB]    PRG-RAM Protect B\n                self.regs.prg_ram_protect[(addr - 0x5102) as usize] = val & 0x03;\n            }\n            0x5104 => {\n                // [.... ..XX] ExRam mode\n                //   Value  RAM $5C00-$5FFF  RAM Nametable  Extended Attr\n                //   %00    Write Only       Yes            No\n                //   %01    Write Only       Yes            Yes\n                //   %10    Read/Write       No             No\n                //   %11    Read Only        No             No\n                self.regs.exram_mode.set(val);\n            }\n            0x5105 => {\n                // [.... ..HH]\n                // [DDCC BBAA]\n                //\n                // Allows each Nametable slot to be configured:\n                //   [   A   ][   B   ]\n                //   [   C   ][   D   ]\n                //\n                // Values can be the following:\n                //   %00 = NES internal NTA\n                //   %01 = NES internal NTB\n                //   %10 = use ExRAM as NT\n                //   %11 = Fill Mode\n                self.regs.nametable_mapping.set(val);\n\n                // Typical mirroring setups would be:\n                //                          D  C  B  A\n                //   Horizontal:     $50    01 01 00 00\n                //   Vertical:       $44    01 00 01 00\n                //   SingleScreenA:  $00    00 00 00 00\n                //   SingleScreenB:  $55    01 01 01 01\n                //   SingleScreen ExRAM:   $AA    10 10 10 10\n                //   SingleScreen Fill:    $FF    11 11 11 11\n                self.mirroring = match val {\n                    0x50 => Mirroring::Horizontal,\n                    0x44 => Mirroring::Vertical,\n                    0x00 => Mirroring::SingleScreenA,\n                    0x55 => Mirroring::SingleScreenB,\n                    // Any other combination means Mapper provides nametables\n                    _ => Mirroring::FourScreen,\n                };\n            }\n            0x5106 => self.regs.fill.tile = val, // [TTTT TTTT] Fill Tile\n            0x5107 => self.regs.fill.attr = (val & 0x03).into(), // [.... ..AA] Fill Attribute bits\n            0x5113..=0x5117 => {\n                // PRG Bank Switching\n                // $5113: [.... .PPP]\n                //      8k PRG-RAM @ $6000\n                // $5114-5117: [RPPP PPPP]\n                //      R = ROM select (0=select RAM, 1=select ROM)  **unused in $5117**\n                //      P = PRG page\n                let bank = (addr - 0x5113) as usize;\n                self.regs.prg_banks[bank] = val as usize;\n                self.update_prg_banks();\n            }\n            0x5120..=0x512B => {\n                let bank = (addr - 0x5120) as usize;\n                self.regs.chr_banks[bank] = val as usize;\n                if addr < 0x5128 {\n                    self.update_chr_banks(ChrBank::Spr);\n                } else {\n                    // Mirroring BG\n                    self.regs.chr_banks[bank + 4] = self.regs.chr_banks[bank];\n                    self.update_chr_banks(ChrBank::Bg);\n                }\n            }\n            0x5130 => self.regs.chr_hi = (val as usize & 0x03) << 8, // [.... ..HH]  CHR Bank Hi bits\n            0x5200 => {\n                // [ES.T TTTT]    Split control\n                //   E = Enable  (0=split mode disabled, 1=split mode enabled)\n                //   S = Vsplit side  (0=split will be on left side, 1=split will be on right)\n                //   T = tile number to split at\n                self.regs.vsplit.enabled = val & 0x80 == 0x80;\n                self.regs.vsplit.side = if val & 0x40 == 0x40 {\n                    Side::Right\n                } else {\n                    Side::Left\n                };\n                self.regs.vsplit.tile = val & 0x1F;\n            }\n            0x5201 => self.regs.vsplit.scroll = val, // [YYYY YYYY]  Split Y scroll\n            0x5202 => self.regs.vsplit.bank = val,   // [CCCC CCCC]  4k CHR Page for split\n            0x5203 => self.regs.irq_scanline = u16::from(val), // [IIII IIII]  IRQ Target\n            0x5204 => {\n                self.regs.irq_enabled = val & 0x80 > 0; // [E... ....] IRQ Enable (0=disabled, 1=enabled)\n                if !self.regs.irq_enabled {\n                    self.regs.irq_pending = false;\n                } else if self.irq_state.pending {\n                    self.regs.irq_pending = true;\n                }\n            }\n            0x5205 => {\n                self.regs.multiplicand = val;\n                self.regs.mult_result =\n                    u16::from(self.regs.multiplicand) * u16::from(self.regs.multiplier);\n            }\n            0x5206 => {\n                self.regs.multiplier = val;\n                self.regs.mult_result =\n                    u16::from(self.regs.multiplicand) * u16::from(self.regs.multiplier);\n            }\n            0x5207..=0x5209 => {}\n            0x5C00..=0x5FFF => match self.regs.exram_mode.rw {\n                ExRamRW::W => {\n                    let val = if self.ppu_status.rendering { val } else { 0x00 };\n                    self.write_ex_ram(addr, val);\n                }\n                ExRamRW::RW => self.write_ex_ram(addr, val),\n                _ => (),\n            },\n            0x6000..=0xDFFF if !self.rom_select(addr) => {\n                self.prg_ram[self.prg_ram_banks.translate(addr)] = val;\n            }\n            _ => (),\n        }\n    }\n\n    /// Synchronize a write to a PPU register at a given address.\n    fn ppu_write(&mut self, addr: u16, val: u8) {\n        match addr {\n            0x2000 => self.ppu_status.sprite8x16 = val & 0x20 > 0,\n            0x2001 => {\n                self.ppu_status.rendering = val & 0x18 > 0; // BG or Spr rendering enabled\n                if !self.ppu_status.rendering {\n                    self.irq_state.in_frame = false;\n                    self.irq_state.prev_addr = None;\n                }\n            }\n            _ => (),\n        }\n    }\n\n    /// Whether an IRQ is pending acknowledgement.\n    fn irq_pending(&self) -> bool {\n        self.regs.irq_pending || self.dmc.irq_pending\n    }\n\n    /// Whether an DMA is pending acknowledgement.\n    fn dma_pending(&self) -> bool {\n        self.dmc.dma_pending\n    }\n\n    /// Clear pending DMA.\n    fn clear_dma_pending(&mut self) {\n        self.dmc.dma_pending = false;\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Exrom {\n    fn reset(&mut self, _kind: ResetKind) {\n        self.regs.prg_mode = PrgMode::Bank8k;\n        self.regs.chr_mode = ChrMode::Bank1k;\n    }\n}\n\nimpl Clock for Exrom {\n    fn clock(&mut self) {\n        if self.ppu_status.reading {\n            self.ppu_status.idle_count = 0;\n        } else {\n            self.ppu_status.idle_count += 1;\n            // 3 CPU clocks == 1 ppu clock\n            if self.ppu_status.idle_count == 3 {\n                self.ppu_status.idle_count = 0;\n                self.irq_state.in_frame = false;\n                self.irq_state.prev_addr = None;\n            }\n        }\n        self.ppu_status.reading = false;\n\n        self.pulse1.clock();\n        self.pulse2.clock();\n        self.dmc.clock();\n        self.pulse_timer -= 1.0;\n        if self.pulse_timer <= 0.0 {\n            self.pulse1.clock_half_frame();\n            self.pulse2.clock_half_frame();\n            self.pulse_timer = Cpu::region_clock_rate(self.region) / 240.0;\n        }\n\n        self.pulse1.length.reload();\n        self.pulse2.length.reload();\n\n        self.cpu_cycle = self.cpu_cycle.wrapping_add(1);\n    }\n}\n\nimpl Regional for Exrom {\n    fn region(&self) -> NesRegion {\n        self.dmc.region()\n    }\n\n    fn set_region(&mut self, region: NesRegion) {\n        self.dmc.set_region(region);\n    }\n}\n\nimpl Sram for Exrom {\n    /// Save RAM to a given path.\n    fn save(&self, path: impl AsRef<Path>) -> fs::Result<()> {\n        fs::save(path.as_ref(), &self.prg_ram)\n    }\n\n    /// Load save RAM from a given path.\n    fn load(&mut self, path: impl AsRef<Path>) -> fs::Result<()> {\n        fs::load(path.as_ref()).map(|data: Memory<Box<[u8]>>| self.prg_ram = data)\n    }\n}\n\nimpl Sample for Exrom {\n    fn output(&self) -> f32 {\n        let pulse1 = self.pulse1.output();\n        let pulse2 = self.pulse2.output();\n        let pulse = PULSE_TABLE[(pulse1 + pulse2) as usize];\n        let dmc = TND_TABLE[self.dmc.output() as usize];\n        -(pulse + dmc)\n    }\n}\n\nimpl std::fmt::Debug for Exrom {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Exrom\")\n            .field(\"regs\", &self.regs)\n            .field(\"mirroring\", &self.mirroring)\n            .field(\"ppu_status\", &self.ppu_status)\n            .field(\"irq_state\", &self.irq_state)\n            .field(\"exram_len\", &self.ex_ram.len())\n            .field(\"prg_ram_banks\", &self.prg_ram_banks)\n            .field(\"prg_rom_banks\", &self.prg_rom_banks)\n            .field(\"chr_banks\", &self.chr_banks)\n            .field(\"tile_cache\", &self.tile_cache)\n            .field(\"last_chr_write\", &self.last_chr_write)\n            .field(\"region\", &self.region)\n            .field(\"pulse1\", &self.pulse1)\n            .field(\"pulse2\", &self.pulse2)\n            .field(\"dmc\", &self.dmc)\n            .field(\"dmc_mode\", &self.dmc_mode)\n            .field(\"cpu_cycle\", &self.cpu_cycle)\n            .field(\"pulse_timer\", &self.pulse_timer)\n            .finish()\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m007_axrom.rs",
    "content": "//! `AxROM` (Mapper 007).\n//!\n//! <https://wiki.nesdev.org/w/index.php/AxROM>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, Sram},\n    mapper::{self, Map, Mapper},\n    mem::{Banks, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\n\n/// `AxROM` (Mapper 007).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Axrom {\n    pub chr: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub has_chr_ram: bool,\n    pub mirroring: Mirroring,\n    pub prg_rom_banks: Banks,\n}\n\nimpl Axrom {\n    const PRG_ROM_WINDOW: usize = 32 * 1024;\n    const CHR_RAM_SIZE: usize = 8 * 1024;\n    const SINGLE_SCREEN_B: u8 = 0b10000;\n\n    /// Load `Axrom` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let (chr, has_chr_ram) = cart.chr_rom_or_ram(chr_rom, Self::CHR_RAM_SIZE);\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_ROM_WINDOW)?;\n        let axrom = Self {\n            chr,\n            prg_rom,\n            has_chr_ram,\n            mirroring: cart.mirroring(),\n            prg_rom_banks,\n        };\n        Ok(axrom.into())\n    }\n}\n\nimpl Map for Axrom {\n    // PPU $0000..=$1FFF 8K CHR-RAM Bank Fixed\n    // CPU $8000..=$FFFF 32K switchable PRG-ROM bank\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr[usize::from(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8, ciram: &mut CIRam) {\n        match addr {\n            0x0000..=0x1FFF => self.chr[usize::from(addr)] = val,\n            0x2000..=0x3EFF => ciram.write(addr, val, self.mirroring),\n            _ => (),\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        if let 0x8000..=0xFFFF = addr {\n            self.prg_rom_banks.set(0, (val & 0x0F).into());\n            self.mirroring = if val & Self::SINGLE_SCREEN_B == Self::SINGLE_SCREEN_B {\n                Mirroring::SingleScreenB\n            } else {\n                Mirroring::SingleScreenA\n            };\n        }\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Axrom {}\nimpl Clock for Axrom {}\nimpl Regional for Axrom {}\nimpl Sram for Axrom {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m009_pxrom.rs",
    "content": "//! `PxROM`/`MMC2` (Mapper 009).\n//!\n//! <https://wiki.nesdev.org/w/index.php/MMC2>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, ResetKind, Sram},\n    mapper::{self, Map, Mapper, Mirroring},\n    mem::{Banks, Memory},\n    ppu::CIRam,\n};\nuse serde::{Deserialize, Serialize};\n\n/// `PxROM`/`MMC2` (Mapper 009).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Pxrom {\n    pub chr_rom: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub prg_ram: Memory<Box<[u8]>>,\n    pub mirroring: Mirroring,\n    pub chr_banks: Banks,\n    pub prg_rom_banks: Banks,\n    // CHR-ROM $FD/0000 bank select ($B000-$BFFF)\n    // CHR-ROM $FE/0000 bank select ($C000-$CFFF)\n    // CHR-ROM $FD/1000 bank select ($D000-$DFFF)\n    // CHR-ROM $FE/1000 bank select ($E000-$EFFF)\n    // 7  bit  0\n    // ---- ----\n    // xxxC CCCC\n    //    | ||||\n    //    +-++++- Select 4K CHR-ROM bank for PPU $0000/$1000-$0FFF/$1FFF\n    //            used when latch 0/1 = $FD/$FE\n    pub latch: [usize; 2],\n    pub latch_banks: [u8; 4],\n}\n\nimpl Pxrom {\n    const PRG_WINDOW: usize = 8 * 1024;\n    const CHR_ROM_WINDOW: usize = 4 * 1024;\n    const PRG_RAM_SIZE: usize = 8 * 1024;\n\n    const MIRRORING_MASK: u8 = 0x01;\n\n    /// Load `Pxrom` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let prg_ram = Memory::with_ram_state(Self::PRG_RAM_SIZE, cart.ram_state);\n        let chr_banks = Banks::new(0x0000, 0x1FFF, chr_rom.len(), Self::CHR_ROM_WINDOW)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_WINDOW)?;\n        let mut pxrom = Self {\n            chr_rom,\n            prg_rom,\n            prg_ram,\n            mirroring: cart.mirroring(),\n            chr_banks,\n            prg_rom_banks,\n            latch: [0x00; 2],\n            latch_banks: [0x00; 4],\n        };\n        let last_bank = pxrom.prg_rom_banks.last();\n        pxrom.prg_rom_banks.set(1, last_bank - 2);\n        pxrom.prg_rom_banks.set(2, last_bank - 1);\n        pxrom.prg_rom_banks.set(3, last_bank);\n        Ok(pxrom.into())\n    }\n\n    pub fn update_banks(&mut self) {\n        let bank0 = self.latch_banks[self.latch[0]] as usize;\n        let bank1 = self.latch_banks[self.latch[1] + 2] as usize;\n        self.chr_banks.set(0, bank0);\n        self.chr_banks.set(1, bank1);\n    }\n}\n\nimpl Map for Pxrom {\n    // PPU $0000..=$0FFF Two 4K switchable CHR-ROM banks\n    // PPU $1000..=$1FFF Two 4K switchable CHR-ROM banks\n    // CPU $6000..=$7FFF 8K PRG-RAM bank (PlayChoice version only)\n    // CPU $8000..=$9FFF 8K switchable PRG-ROM bank\n    // CPU $A000..=$FFFF Three 8K PRG-ROM banks, fixed to the last three banks\n\n    /// Read a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_read(&mut self, addr: u16, ciram: &CIRam) -> u8 {\n        let val = self.chr_peek(addr, ciram);\n        // Update latch after read\n        match addr {\n            0x0FD8 | 0x0FE8 | 0x1FD8..=0x1FDF | 0x1FE8..=0x1FEF => {\n                let addr = addr as usize;\n                self.latch[addr >> 12] = ((addr >> 4) & 0xFF) - 0xFD;\n                self.update_banks();\n            }\n            _ => (),\n        }\n        val\n    }\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x6000..=0x7FFF => self.prg_ram[usize::from(addr & 0x1FFF)],\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        match addr {\n            0x6000..=0x7FFF => self.prg_ram[usize::from(addr & 0x1FFF)] = val,\n            0xA000..=0xAFFF => self.prg_rom_banks.set(0, (val & 0x0F).into()),\n            0xB000..=0xEFFF => {\n                self.latch_banks[((addr - 0xB000) >> 12) as usize] = val & 0x1F;\n                self.update_banks();\n            }\n            0xF000..=0xFFFF => {\n                self.mirroring = match val & Self::MIRRORING_MASK {\n                    0b00 => Mirroring::Vertical,\n                    _ => Mirroring::Horizontal,\n                };\n            }\n            _ => (),\n        }\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Pxrom {\n    fn reset(&mut self, _kind: ResetKind) {\n        self.latch = [0x00; 2];\n        self.latch_banks = [0x00; 4];\n        self.update_banks();\n    }\n}\n\nimpl Clock for Pxrom {}\nimpl Regional for Pxrom {}\nimpl Sram for Pxrom {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m010_fxrom.rs",
    "content": "//! `FxROM`/`MMC4` (Mapper 010).\n//!\n//! <https://wiki.nesdev.org/w/index.php/MMC4>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, ResetKind, Sram},\n    fs,\n    mapper::{self, Map, Mapper, Mirroring},\n    mem::{Banks, Memory},\n    ppu::CIRam,\n};\nuse serde::{Deserialize, Serialize};\nuse std::path::Path;\n\n/// `FxROM`/`MMC4` (Mapper 010).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Fxrom {\n    pub chr_rom: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub prg_ram: Memory<Box<[u8]>>,\n    pub mirroring: Mirroring,\n    pub chr_banks: Banks,\n    pub prg_rom_banks: Banks,\n    // CHR-ROM $FD/0000 bank select ($B000-$BFFF)\n    // CHR-ROM $FE/0000 bank select ($C000-$CFFF)\n    // CHR-ROM $FD/1000 bank select ($D000-$DFFF)\n    // CHR-ROM $FE/1000 bank select ($E000-$EFFF)\n    // 7  bit  0\n    // ---- ----\n    // xxxC CCCC\n    //    | ||||\n    //    +-++++- Select 4K CHR-ROM bank for PPU $0000/$1000-$0FFF/$1FFF\n    //            used when latch 0/1 = $FD/$FE\n    pub latch: [usize; 2],\n    pub latch_banks: [u8; 4],\n}\n\nimpl Fxrom {\n    const PRG_WINDOW: usize = 16 * 1024;\n    const CHR_ROM_WINDOW: usize = 4 * 1024;\n    const PRG_RAM_SIZE: usize = 8 * 1024;\n\n    const MIRRORING_MASK: u8 = 0x01;\n\n    /// Load `Fxrom` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let prg_ram = Memory::with_ram_state(Self::PRG_RAM_SIZE, cart.ram_state);\n        let chr_banks = Banks::new(0x0000, 0x1FFF, chr_rom.len(), Self::CHR_ROM_WINDOW)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_WINDOW)?;\n        let mut fxrom = Self {\n            chr_rom,\n            prg_rom,\n            prg_ram,\n            mirroring: cart.mirroring(),\n            chr_banks,\n            prg_rom_banks,\n            latch: [0x00; 2],\n            latch_banks: [0x00; 4],\n        };\n        fxrom.prg_rom_banks.set(1, fxrom.prg_rom_banks.last());\n        Ok(fxrom.into())\n    }\n\n    pub fn update_banks(&mut self) {\n        let bank0 = self.latch_banks[self.latch[0]] as usize;\n        let bank1 = self.latch_banks[self.latch[1] + 2] as usize;\n        self.chr_banks.set(0, bank0);\n        self.chr_banks.set(1, bank1);\n    }\n}\n\nimpl Map for Fxrom {\n    // PPU $0000..=$0FFF Two 4K switchable CHR-ROM banks\n    // PPU $1000..=$1FFF Two 4K switchable CHR-ROM banks\n    // CPU $6000..=$7FFF 8K PRG-RAM bank\n    // CPU $8000..=$BFFF 16K switchable PRG-ROM bank\n    // CPU $C000..=$FFFF 16K PRG-ROM bank, fixed to the last bank\n\n    /// Read a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_read(&mut self, addr: u16, ciram: &CIRam) -> u8 {\n        let val = self.chr_peek(addr, ciram);\n        // Update latch after read\n        match addr {\n            0x0FD8..=0x0FDF | 0x0FE8..=0xFEF | 0x1FD8..=0x1FDF | 0x1FE8..=0x1FEF => {\n                let addr = addr as usize;\n                self.latch[addr >> 12] = ((addr >> 4) & 0xFF) - 0xFD;\n                self.update_banks();\n            }\n            _ => (),\n        }\n        val\n    }\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x6000..=0x7FFF => self.prg_ram[usize::from(addr & 0x1FFF)],\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        match addr {\n            0x6000..=0x7FFF => self.prg_ram[usize::from(addr & 0x1FFF)] = val,\n            0xA000..=0xAFFF => {\n                self.prg_rom_banks.set(0, (val & 0x0F).into());\n            }\n            0xB000..=0xEFFF => {\n                self.latch_banks[((addr - 0xB000) >> 12) as usize] = val & 0x1F;\n                self.update_banks();\n            }\n            0xF000..=0xFFFF => {\n                self.mirroring = match val & Self::MIRRORING_MASK {\n                    0b00 => Mirroring::Vertical,\n                    _ => Mirroring::Horizontal,\n                };\n            }\n            _ => (),\n        }\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Fxrom {\n    fn reset(&mut self, _kind: ResetKind) {\n        self.latch = [0x00; 2];\n        self.latch_banks = [0x00; 4];\n        self.update_banks();\n    }\n}\n\nimpl Sram for Fxrom {\n    /// Save RAM to a given path.\n    fn save(&self, path: impl AsRef<Path>) -> fs::Result<()> {\n        fs::save(path.as_ref(), &self.prg_ram)\n    }\n\n    /// Load save RAM from a given path.\n    fn load(&mut self, path: impl AsRef<Path>) -> fs::Result<()> {\n        fs::load(path.as_ref()).map(|data: Memory<Box<[u8]>>| self.prg_ram = data)\n    }\n}\n\nimpl Clock for Fxrom {}\nimpl Regional for Fxrom {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m011_color_dreams.rs",
    "content": "//! `Color Dreams` (Mapper 011).\n//!\n//! <https://wiki.nesdev.org/w/index.php/Color_Dreams>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, Sram},\n    mapper::{self, Map, Mapper, Mirroring},\n    mem::{Banks, Memory},\n    ppu::CIRam,\n};\nuse serde::{Deserialize, Serialize};\n\n/// `Color Dreams` (Mapper 011).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct ColorDreams {\n    pub chr_rom: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub mapper_num: u16,\n    pub mirroring: Mirroring,\n    pub chr_banks: Banks,\n    pub prg_rom_banks: Banks,\n}\n\nimpl ColorDreams {\n    const PRG_WINDOW: usize = 32 * 1024;\n    const CHR_ROM_WINDOW: usize = 8 * 1024;\n\n    const CHR_BANK_MASK: u8 = 0b1111_0000;\n    const PRG_BANK_MASK: u8 = 0b0000_0011;\n\n    /// Load `ColorDreams` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let chr_banks = Banks::new(0x0000, 0x1FFF, chr_rom.len(), Self::CHR_ROM_WINDOW)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_WINDOW)?;\n        let color_dreams = Self {\n            chr_rom,\n            prg_rom,\n            mapper_num: cart.mapper_num(),\n            mirroring: cart.mirroring(),\n            chr_banks,\n            prg_rom_banks,\n        };\n        Ok(color_dreams.into())\n    }\n}\n\nimpl Map for ColorDreams {\n    // PPU $0000..=$1FFF 8K switchable CHR-ROM bank\n    // CPU $8000..=$FFFF 32K switchable PRG-ROM bank\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, mut val: u8) {\n        if let 0x8000..=0xFFFF = addr {\n            if self.mapper_num == 144 {\n                // Intentionally defective variant where only the least significant bit alwys wins\n                // bus conflict\n                // See: <https://www.nesdev.org/wiki/INES_Mapper_144>\n                val |= self.prg_read(addr) & 0x01;\n            }\n            self.chr_banks\n                .set(0, ((val & Self::CHR_BANK_MASK) >> 4).into());\n            self.prg_rom_banks\n                .set(0, (val & Self::PRG_BANK_MASK).into());\n        }\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for ColorDreams {}\nimpl Clock for ColorDreams {}\nimpl Regional for ColorDreams {}\nimpl Sram for ColorDreams {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m018_jalecoss88006.rs",
    "content": "//! `Jaleco SS88006` (Mapper 018).\n//!\n//! <https://www.nesdev.org/wiki/INES_Mapper_018>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, ResetKind, Sram},\n    mapper::{self, Map, Mapper},\n    mem::{BankAccess, Banks, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\n\n/// `Jaleco SS88006` page bit.\n#[derive(Debug)]\n#[must_use]\nenum PageBit {\n    Low,\n    High,\n}\n\nimpl PageBit {\n    const fn page(&self, page: usize, val: u8) -> usize {\n        let val = (val as usize) & 0x0F;\n        match self {\n            PageBit::Low => (page & 0xF0) | val,\n            PageBit::High => (val << 4) | (page & 0x0F),\n        }\n    }\n}\n\nimpl From<u16> for PageBit {\n    fn from(addr: u16) -> Self {\n        if addr & 0x01 == 0x01 {\n            Self::High\n        } else {\n            Self::Low\n        }\n    }\n}\n\n/// `Jaleco SS88006` registers.\n#[derive(Default, Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Regs {\n    pub irq_enabled: bool,\n    pub irq_pending: bool,\n    pub irq_reload: [u8; 4],\n    pub irq_counter_size: u8,\n}\n\n/// `Jaleco SS88006` (Mapper 018).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct JalecoSs88006 {\n    pub chr_rom: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub prg_ram: Memory<Box<[u8]>>,\n    pub regs: Regs,\n    pub irq_counter: u16,\n    pub mirroring: Mirroring,\n    pub chr_banks: Banks,\n    pub prg_ram_banks: Banks,\n    pub prg_rom_banks: Banks,\n}\n\nimpl JalecoSs88006 {\n    const PRG_WINDOW: usize = 8 * 1024;\n    const PRG_RAM_SIZE: usize = 8 * 1024;\n    const CHR_WINDOW: usize = 1024;\n\n    const IRQ_MASKS: [u16; 4] = [0xFFFF, 0x0FFF, 0x00FF, 0x000F];\n\n    /// Load `JalecoSs88006` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let prg_ram = cart.prg_ram_or_default(Self::PRG_RAM_SIZE);\n        let chr_banks = Banks::new(0x0000, 0x1FFF, chr_rom.len(), Self::CHR_WINDOW)?;\n        let prg_ram_banks = Banks::new(0x6000, 0x7FFF, prg_ram.len(), Self::PRG_WINDOW)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_WINDOW)?;\n        let mut jalecoss88006 = Self {\n            chr_rom,\n            prg_rom,\n            prg_ram,\n            regs: Regs::default(),\n            irq_counter: 0,\n            mirroring: cart.mirroring(),\n            chr_banks,\n            prg_ram_banks,\n            prg_rom_banks,\n        };\n        jalecoss88006\n            .prg_rom_banks\n            .set(3, jalecoss88006.prg_rom_banks.last());\n        Ok(jalecoss88006.into())\n    }\n\n    fn update_prg_bank(&mut self, bank: usize, val: u8, bits: PageBit) {\n        self.prg_rom_banks\n            .set(bank, bits.page(self.prg_rom_banks.page(bank), val));\n    }\n\n    fn update_chr_bank(&mut self, bank: usize, val: u8, bits: PageBit) {\n        self.chr_banks\n            .set(bank, bits.page(self.chr_banks.page(bank), val));\n    }\n}\n\nimpl Map for JalecoSs88006 {\n    // PPU $0000..=$03FF: 1K CHR Bank 1 Switchable\n    // PPU $0400..=$07FF: 1K CHR Bank 2 Switchable\n    // PPU $0800..=$0BFF: 1K CHR Bank 3 Switchable\n    // PPU $0C00..=$0FFF: 1K CHR Bank 4 Switchable\n    // PPU $1000..=$13FF: 1K CHR Bank 5 Switchable\n    // PPU $1400..=$17FF: 1K CHR Bank 6 Switchable\n    // PPU $1800..=$1BFF: 1K CHR Bank 7 Switchable\n    // PPU $1C00..=$1FFF: 1K CHR Bank 8 Switchable\n    //\n    // CPU $6000..=$7FFF: 8K PRG-RAM Bank, if WRAM is present\n    // CPU $8000..=$9FFF: 8K PRG-ROM Bank 1 Switchable\n    // CPU $A000..=$BFFF: 8K PRG-ROM Bank 2 Switchable\n    // CPU $C000..=$DFFF: 8K PRG-ROM Bank 3 Switchable\n    // CPU $E000..=$FFFF: 8K PRG-ROM Bank 4 Fixed to last\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x6000..=0x7FFF if self.prg_ram_banks.readable(addr) => {\n                self.prg_ram[self.prg_ram_banks.translate(addr)]\n            }\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        match addr {\n            0x6000..=0x7FFF => {\n                if self.prg_ram_banks.writable(addr) {\n                    self.prg_ram[self.prg_ram_banks.translate(addr)] = val;\n                }\n            }\n            _ => match addr & 0xF003 {\n                0x8000 | 0x8001 => self.update_prg_bank(0, val, PageBit::from(addr)),\n                0x8002 | 0x8003 => self.update_prg_bank(1, val, PageBit::from(addr)),\n                0x9000 | 0x9001 => self.update_prg_bank(2, val, PageBit::from(addr)),\n                0x9002 => {\n                    let prg_ram_access = if val & 0x01 == 0x01 {\n                        if val & 0x02 == 0x02 {\n                            BankAccess::ReadWrite\n                        } else {\n                            BankAccess::Read\n                        }\n                    } else {\n                        BankAccess::None\n                    };\n                    self.prg_ram_banks.set_access(0, prg_ram_access);\n                }\n                0xA000 | 0xA001 => self.update_chr_bank(0, val, PageBit::from(addr)),\n                0xA002 | 0xA003 => self.update_chr_bank(1, val, PageBit::from(addr)),\n                0xB000 | 0xB001 => self.update_chr_bank(2, val, PageBit::from(addr)),\n                0xB002 | 0xB003 => self.update_chr_bank(3, val, PageBit::from(addr)),\n                0xC000 | 0xC001 => self.update_chr_bank(4, val, PageBit::from(addr)),\n                0xC002 | 0xC003 => self.update_chr_bank(5, val, PageBit::from(addr)),\n                0xD000 | 0xD001 => self.update_chr_bank(6, val, PageBit::from(addr)),\n                0xD002 | 0xD003 => self.update_chr_bank(7, val, PageBit::from(addr)),\n                0xE000..=0xE003 => self.regs.irq_reload[(addr & 0x03) as usize] = val,\n                0xF000 => {\n                    self.regs.irq_pending = false;\n                    self.irq_counter = u16::from(self.regs.irq_reload[0])\n                        | (u16::from(self.regs.irq_reload[1]) << 4)\n                        | (u16::from(self.regs.irq_reload[2]) << 8)\n                        | (u16::from(self.regs.irq_reload[3]) << 12);\n                }\n                0xF001 => {\n                    self.regs.irq_enabled = val & 0x01 == 0x01;\n                    self.regs.irq_pending = false;\n                    if val & 0x08 == 0x08 {\n                        self.regs.irq_counter_size = 3;\n                    } else if val & 0x04 == 0x04 {\n                        self.regs.irq_counter_size = 2;\n                    } else if val & 0x02 == 0x02 {\n                        self.regs.irq_counter_size = 1;\n                    } else {\n                        self.regs.irq_counter_size = 0;\n                    }\n                }\n                0xF002 => {\n                    self.mirroring = match val & 0x03 {\n                        0b00 => Mirroring::Horizontal,\n                        0b01 => Mirroring::Vertical,\n                        0b10 => Mirroring::SingleScreenA,\n                        _ => Mirroring::SingleScreenB,\n                    };\n                }\n                0xF003 => {\n                    // TODO: Expansion audio\n                }\n                _ => (),\n            },\n        }\n    }\n\n    /// Whether an IRQ is pending acknowledgement.\n    fn irq_pending(&self) -> bool {\n        self.regs.irq_pending\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for JalecoSs88006 {\n    fn reset(&mut self, kind: ResetKind) {\n        self.regs = Regs::default();\n        if kind == ResetKind::Hard {\n            self.prg_rom_banks.set(3, self.prg_rom_banks.last());\n        }\n    }\n}\n\nimpl Clock for JalecoSs88006 {\n    fn clock(&mut self) {\n        if self.regs.irq_enabled {\n            let irq_mask = Self::IRQ_MASKS[self.regs.irq_counter_size as usize];\n            let counter = self.irq_counter & irq_mask;\n            if counter == 0 {\n                self.regs.irq_pending = true;\n            }\n            self.irq_counter =\n                (self.irq_counter & !irq_mask) | (counter.wrapping_sub(1) & irq_mask);\n        }\n    }\n}\n\nimpl Regional for JalecoSs88006 {}\nimpl Sram for JalecoSs88006 {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m019_namco163.rs",
    "content": "//! `Namco163` (Mapper 019).\n//!\n//! <https://www.nesdev.org/wiki/INES_Mapper_019>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, ResetKind, Sample, Sram},\n    fs,\n    mapper::{self, Map, Mapper},\n    mem::{BankAccess, Banks, ConstArray, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\n\n/// `Namco163` board.\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum Board {\n    #[default]\n    Unknown,\n    Namco163,\n    Namco175,\n    Namco340,\n}\n\n/// `Namco163` registers.\n#[derive(Default, Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Regs {\n    pub irq_counter: u16,\n    pub irq_pending: bool,\n    pub nt_select_lo: bool,\n    pub nt_select_hi: bool,\n    pub prg_ram_protect: u8,\n}\n\n/// `Namco163` (Mapper 019).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Namco163 {\n    pub chr_rom: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub prg_ram: Memory<Box<[u8]>>,\n    pub regs: Regs,\n    pub board: Board,\n    pub mapper_num: u16,\n    pub submapper_num: u8,\n    pub audio: Audio,\n    pub auto_detect_board: bool,\n    pub mirroring: Mirroring,\n    pub prg_ram_written_to: bool,\n    pub nt_bank_enable: [bool; 12],\n    pub chr_banks: Banks,\n    pub prg_ram_banks: Banks,\n    pub prg_rom_banks: Banks,\n}\n\nimpl Namco163 {\n    const PRG_WINDOW: usize = 8 * 1024;\n    const PRG_RAM_SIZE: usize = 8 * 1024;\n    const CHR_WINDOW: usize = 1024;\n\n    /// Load `Namco163` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let mut auto_detect_board = false;\n        let prg_ram = cart.prg_ram_or_default(Self::PRG_RAM_SIZE);\n        let chr_banks = Banks::new(0x0000, 0x3FFF, chr_rom.len(), Self::CHR_WINDOW)?;\n        let prg_ram_banks = Banks::new(0x6000, 0x7FFF, prg_ram.len(), Self::PRG_WINDOW)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_WINDOW)?;\n        let mut namco163 = Self {\n            chr_rom,\n            prg_rom,\n            prg_ram,\n            regs: Regs::default(),\n            board: match cart.mapper_num() {\n                19 => {\n                    auto_detect_board = cart.game_info.is_none();\n                    Board::Namco163\n                }\n                210 => match cart.submapper_num() {\n                    1 => Board::Namco175,\n                    2 => Board::Namco340,\n                    _ => {\n                        auto_detect_board = true;\n                        Board::Unknown\n                    }\n                },\n                _ => Board::Unknown,\n            },\n            mapper_num: cart.mapper_num(),\n            submapper_num: cart.submapper_num(),\n            audio: Audio::new(),\n            auto_detect_board,\n            mirroring: cart.mirroring(),\n            prg_ram_written_to: false,\n            nt_bank_enable: [false; 12],\n            chr_banks,\n            prg_ram_banks,\n            prg_rom_banks,\n        };\n        // Default 0x2000.=0x2FFF to NTRAM\n        for bank in 8..12 {\n            namco163.nt_bank_enable[bank] = true;\n            namco163.chr_banks.set(bank, ((bank - 8) * 0x0400) & 0x03FF);\n        }\n        namco163.prg_rom_banks.set(3, namco163.prg_rom_banks.last());\n        namco163.update_prg_ram_access();\n        Ok(namco163.into())\n    }\n\n    fn update_prg_ram_access(&mut self) {\n        if self.prg_ram_banks.banks_len() == 0 {\n            return;\n        }\n        let access = |read_write| {\n            if read_write {\n                BankAccess::ReadWrite\n            } else {\n                BankAccess::Read\n            }\n        };\n        let write_protect = self.regs.prg_ram_protect;\n        match self.board {\n            Board::Namco163 => {\n                self.prg_ram_banks.set_access_range(0, 3, access(true));\n            }\n            Board::Namco175 => {\n                self.prg_ram_banks\n                    .set_access_range(0, 3, access(write_protect & 0x01 == 0x01));\n            }\n            _ => {\n                self.prg_ram_banks.set_access_range(0, 3, BankAccess::None);\n            }\n        }\n    }\n\n    #[inline]\n    fn maybe_set_board(&mut self, board: Board) {\n        if self.auto_detect_board\n            && (!self.prg_ram_written_to || self.board != Board::Namco340)\n            && self.board != board\n        {\n            tracing::debug!(\"auto detecting board: {board:?}\");\n            self.board = board;\n        }\n    }\n}\n\nimpl Map for Namco163 {\n    // PPU $0000..=$03FF 1K CHR Bank 1 Switchable\n    // PPU $0400..=$07FF 1K CHR Bank 2 Switchable\n    // PPU $0800..=$0BFF 1K CHR Bank 3 Switchable\n    // PPU $0C00..=$0FFF 1K CHR Bank 4 Switchable\n    // PPU $1000..=$13FF 1K CHR Bank 5 Switchable\n    // PPU $1400..=$17FF 1K CHR Bank 6 Switchable\n    // PPU $1800..=$1BFF 1K CHR Bank 7 Switchable\n    // PPU $1C00..=$1FFF 1K CHR Bank 8 Switchable\n    // PPU $2000..=$23FF 1K CHR Bank 9 Switchable\n    // PPU $2400..=$27FF 1K CHR Bank 10 Switchable\n    // PPU $2800..=$2BFF 1K CHR Bank 11 Switchable\n    // PPU $2C00..=$2FFF 1K CHR Bank 12 Switchable\n    //\n    // CPU $6000..=$7FFF 8K PRG-RAM Bank, if WRAM is present\n    // CPU $8000..=$9FFF 8K PRG-ROM Bank 1 Switchable\n    // CPU $A000..=$BFFF 8K PRG-ROM Bank 2 Switchable\n    // CPU $C000..=$DFFF 8K PRG-ROM Bank 3 Switchable\n    // CPU $E000..=$FFFF 8K PRG-ROM Bank 4, fixed to last\n\n    // $0400..=$07FF bank 1 > page N -> addr + page * $0400\n    // $0800..=$0BFF bank 2 -> page N -> addr + page * $0400\n    // $0C00..=$0FFF bank 3 -> page N -> addr + page * $0400\n    // $1000..=$13FF bank 4 -> page N -> addr + page * $0400\n    // $1400..=$17FF bank 5 -> page N -> addr + page * $0400\n    // $1800..=$1BFF bank 6 -> page N -> addr + page * $0400\n    // $1C00..=$1FFF bank 7 -> page N -> addr + page * $0400\n    // $2000..=$23FF bank 8 -> page N -> addr + page * $0400\n    // $2400..=$27FF bank 9 -> page N -> addr + page * $0400\n    // $2800..=$2BFF bank 10 -> page N -> addr + page * $0400\n    // $2C00..=$2FFF bank 11 -> page N -> addr + page * $0400\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x3EFF => {\n                let bank = addr >> 10;\n                let addr = self.chr_banks.translate(addr);\n                if self.nt_bank_enable[bank as usize] {\n                    ciram[addr]\n                } else {\n                    self.chr_rom[addr]\n                }\n            }\n            _ => 0,\n        }\n    }\n\n    /// Read a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_read(&mut self, addr: u16) -> u8 {\n        match addr {\n            0x4800..=0x4FFF => self.audio.read_register(addr),\n            _ => self.prg_peek(addr),\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x6000..=0x7FFF => {\n                if self.prg_ram_banks.readable(addr) {\n                    self.prg_ram[self.prg_ram_banks.translate(addr)]\n                } else {\n                    0\n                }\n            }\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => match addr & 0xF800 {\n                0x4800 => self.audio.peek_register(addr),\n                0x5000 => (self.regs.irq_counter & 0xFF) as u8,\n                0x5800 => (self.regs.irq_counter >> 8) as u8,\n                _ => 0,\n            },\n        }\n    }\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8, ciram: &mut CIRam) {\n        if let 0x0000..=0x3EFF = addr {\n            let bank = addr >> 10;\n            let addr = self.chr_banks.translate(addr);\n            if self.nt_bank_enable[bank as usize] {\n                ciram[addr] = val;\n            }\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        match addr {\n            0x4800..=0x4FFF => {\n                self.maybe_set_board(Board::Namco163);\n                self.audio.write_register(addr, val)\n            }\n            0x5000..=0x57FF => {\n                self.maybe_set_board(Board::Namco163);\n                self.regs.irq_counter = (self.regs.irq_counter & 0xFF00) | u16::from(val);\n                self.regs.irq_pending = false;\n            }\n            0x5800..=0x5FFF => {\n                self.maybe_set_board(Board::Namco163);\n                self.regs.irq_counter = (self.regs.irq_counter & 0xFF) | (u16::from(val) << 8);\n                self.regs.irq_pending = false;\n            }\n            0x6000..=0x7FFF => {\n                self.prg_ram_written_to = true;\n                if self.board == Board::Namco340 {\n                    self.maybe_set_board(Board::Unknown);\n                }\n                if self.prg_ram_banks.writable(addr) {\n                    self.prg_ram[self.prg_ram_banks.translate(addr)] = val;\n                }\n            }\n            0x8000..=0xDFFF => {\n                if addr >= 0xC800 {\n                    self.maybe_set_board(Board::Namco163);\n                } else if addr >= 0xC000 && self.board != Board::Namco163 {\n                    self.maybe_set_board(Board::Namco175);\n                }\n\n                if addr >= 0xC000 && self.board == Board::Namco175 {\n                    self.regs.prg_ram_protect = val;\n                    self.update_prg_ram_access();\n                } else {\n                    let bank = ((addr - 0x8000) >> 11) as usize;\n                    let nt_select = match addr {\n                        0x8000..=0x9FFF => !self.regs.nt_select_lo,\n                        0xA000..=0xBFFF => !self.regs.nt_select_hi,\n                        _ => true,\n                    };\n                    let nt_bank_enable = nt_select && val >= 0xE0 && self.board == Board::Namco163;\n                    self.nt_bank_enable[bank] = nt_bank_enable;\n                    if nt_bank_enable {\n                        self.chr_banks.set(bank, (val & 0x01).into());\n                    } else {\n                        self.chr_banks.set(bank, val.into());\n                    }\n                }\n            }\n            0xE000..=0xE7FF => {\n                if val & 0x80 == 0x80 || (val & 0x40 == 0x40 && self.board != Board::Namco163) {\n                    self.maybe_set_board(Board::Namco340);\n                }\n\n                self.prg_rom_banks.set(0, (val & 0x3F).into());\n\n                match self.board {\n                    Board::Namco340 => {\n                        self.mirroring = match (val & 0xC0) >> 6 {\n                            0b00 => Mirroring::SingleScreenA,\n                            0b01 => Mirroring::Vertical,\n                            0b10 => Mirroring::Horizontal,\n                            _ => Mirroring::SingleScreenB,\n                        };\n                    }\n                    Board::Namco163 => self.audio.write_register(addr, val),\n                    _ => (),\n                }\n            }\n            0xE800..=0xEFFF => {\n                self.prg_rom_banks.set(1, (val & 0x3F).into());\n\n                if self.board == Board::Namco163 {\n                    self.regs.nt_select_lo = (val & 0x40) == 0x40;\n                    self.regs.nt_select_hi = (val & 0x80) == 0x80;\n                }\n            }\n            0xF000..=0xF7FF => self.prg_rom_banks.set(2, (val & 0x3F).into()),\n            0xF800..=0xFFFF => {\n                self.maybe_set_board(Board::Namco163);\n                if self.board == Board::Namco163 {\n                    self.regs.prg_ram_protect = val;\n                    self.update_prg_ram_access();\n                    self.audio.write_register(addr, val);\n                }\n            }\n            _ => (),\n        }\n    }\n\n    /// Whether an IRQ is pending acknowledgement.\n    fn irq_pending(&self) -> bool {\n        self.regs.irq_pending\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Namco163 {\n    fn reset(&mut self, kind: ResetKind) {\n        if kind == ResetKind::Hard {\n            self.regs = Regs::default();\n        }\n        for bank in 8..12 {\n            self.nt_bank_enable[bank] = true;\n            self.chr_banks.set(bank, ((bank - 8) * 0x0400) & 0x03FF);\n        }\n        self.prg_ram_written_to = false;\n        self.prg_rom_banks.set(3, self.prg_rom_banks.last());\n        self.update_prg_ram_access();\n        self.audio = Audio::new();\n    }\n}\n\nimpl Clock for Namco163 {\n    fn clock(&mut self) {\n        if self.regs.irq_counter & 0x8000 > 0 && self.regs.irq_counter & 0x7FFF != 0x7FFF {\n            self.regs.irq_counter = self.regs.irq_counter.wrapping_add(1);\n            if self.regs.irq_counter & 0x7FFF == 0x7FFF {\n                self.regs.irq_pending = true;\n            }\n        }\n        if self.board == Board::Namco163 {\n            self.audio.clock();\n        }\n    }\n}\n\nimpl Regional for Namco163 {}\n\nimpl Sram for Namco163 {\n    fn save(&self, path: impl AsRef<std::path::Path>) -> fs::Result<()> {\n        fs::save(path.as_ref(), &(&self.prg_ram, &self.audio.ram))\n    }\n\n    fn load(&mut self, path: impl AsRef<std::path::Path>) -> fs::Result<()> {\n        fs::load::<(Memory<Box<[u8]>>, ConstArray<u8, 0x80>)>(path.as_ref()).map(\n            |(prg_ram, audio_ram)| {\n                self.prg_ram = prg_ram;\n                self.audio.ram = audio_ram;\n            },\n        )\n    }\n}\n\nimpl Sample for Namco163 {\n    fn output(&self) -> f32 {\n        self.audio.output()\n    }\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Audio {\n    pub ram: ConstArray<u8, 0x80>,\n    pub addr: usize,\n    pub auto_increment: bool,\n    pub disabled: bool,\n    pub update_counter: u8,\n    pub current_channel: i8,\n    pub channel_out: [f32; Self::CHANNEL_COUNT],\n    pub out: f32,\n    #[serde(skip, default)]\n    phase_ext: [u32; Self::CHANNEL_COUNT],\n}\n\nimpl Default for Audio {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Audio {\n    const CHANNEL_COUNT: usize = 8;\n\n    const REG_FREQ_LOW: usize = 0x00;\n    const REG_FREQ_MID: usize = 0x02;\n    const REG_FREQ_HIGH: usize = 0x04;\n    const REG_WAVE_LEN: usize = 0x04;\n    const REG_WAVE_ADDR: usize = 0x06;\n    const REG_VOLUME: usize = 0x07;\n\n    pub fn new() -> Self {\n        Self {\n            ram: ConstArray::new(),\n            addr: 0,\n            auto_increment: false,\n            disabled: false,\n            update_counter: 0,\n            current_channel: 7,\n            channel_out: [0.0; Self::CHANNEL_COUNT],\n            out: 0.0,\n            phase_ext: [0; Self::CHANNEL_COUNT],\n        }\n    }\n\n    #[must_use]\n    pub fn read_register(&mut self, addr: u16) -> u8 {\n        let val = self.peek_register(addr);\n        if self.auto_increment {\n            self.addr = (self.addr + 1) & 0x7F;\n        }\n        val\n    }\n\n    #[must_use]\n    #[allow(clippy::missing_const_for_fn)] // false positive on non-const deref coercion\n    pub fn peek_register(&self, addr: u16) -> u8 {\n        if matches!(addr, 0x4800..=0x4FFF) {\n            self.ram[self.addr]\n        } else {\n            0\n        }\n    }\n\n    pub fn write_register(&mut self, addr: u16, val: u8) {\n        match addr {\n            0x4800..=0x4FFF => {\n                self.ram[self.addr] = val;\n                if self.auto_increment {\n                    self.addr = (self.addr + 1) & 0x7F;\n                }\n            }\n            0xE000..=0xE7FF => self.disabled = val & 0x40 == 0x40,\n            0xF800..=0xFFFF => {\n                self.addr = (val & 0x7F).into();\n                self.auto_increment = val & 0x80 == 0x80;\n            }\n            _ => (),\n        }\n    }\n\n    #[must_use]\n    #[inline]\n    pub const fn output(&self) -> f32 {\n        // TODO: -40db - it's not accurate according to https://www.nesdev.org/wiki/Namco_163_audio#Mixing\n        // but it's way too loud otherwise. Should fix root cause and update to use NES 2.0\n        // submapper_num, if set\n        0.0001 * self.out\n    }\n\n    #[inline]\n    fn update_output(&mut self) {\n        // \"Because the high frequency generated by the channel cycling can be unpleasant, and\n        // emulation of high frequency audio can be difficult, it is often preferred to simply sum\n        // the channel outputs, and divide the output volume by the number of active channels.\"\n        // See: https://www.nesdev.org/wiki/Namco_163_audio#Mixing\n        let channel_count = usize::from(self.channel_count());\n        self.out = self.channel_out.iter().skip(7 - channel_count).sum::<f32>()\n            / (channel_count + 1) as f32;\n    }\n\n    #[must_use]\n    #[inline]\n    const fn base_addr(&self) -> usize {\n        (0x40 + self.current_channel * 0x08) as usize\n    }\n\n    #[must_use]\n    #[inline]\n    const fn phase(&self) -> u32 {\n        self.phase_ext[self.current_channel as usize]\n    }\n\n    #[must_use]\n    #[inline]\n    fn wave_length(&self) -> u32 {\n        let base_addr = self.base_addr();\n        256 - u32::from(self.ram[base_addr + Self::REG_WAVE_LEN] & 0xFC)\n    }\n\n    #[must_use]\n    #[inline]\n    fn wave_address(&self) -> u32 {\n        let base_addr = self.base_addr();\n        u32::from(self.ram[base_addr + Self::REG_WAVE_ADDR])\n    }\n\n    #[must_use]\n    #[inline]\n    #[allow(clippy::missing_const_for_fn)] // false positive on non-const deref coercion\n    fn volume(&self) -> u8 {\n        let base_addr = self.base_addr();\n        self.ram[base_addr + Self::REG_VOLUME] & 0x0F\n    }\n\n    #[inline]\n    const fn set_phase(&mut self, phase: u32) {\n        self.phase_ext[self.current_channel as usize] = phase;\n    }\n\n    #[must_use]\n    #[inline]\n    fn frequency(&self) -> u32 {\n        let base_addr = self.base_addr();\n        let freq_high = u32::from(self.ram[base_addr + Self::REG_FREQ_HIGH] & 0x03) << 16;\n        let freq_mid = u32::from(self.ram[base_addr + Self::REG_FREQ_MID]) << 8;\n        let freq_low = u32::from(self.ram[base_addr + Self::REG_FREQ_LOW]);\n        freq_high | freq_mid | freq_low\n    }\n\n    #[inline]\n    fn update_channel(&mut self) {\n        let mut phase = self.phase();\n        let frequency = self.frequency();\n        let wave_length = self.wave_length();\n        let wave_addr = self.wave_address();\n        let volume = self.volume();\n\n        phase = (phase + frequency) % (wave_length << 16);\n        let sample_addr = (((phase >> 16) + wave_addr) & 0xFF) as usize;\n        let sample = if sample_addr & 0x01 == 0x01 {\n            self.ram[sample_addr / 2] >> 4\n        } else {\n            self.ram[sample_addr / 2] & 0x0F\n        };\n        self.channel_out[self.current_channel as usize] =\n            sample.wrapping_sub(8) as f32 * volume as f32;\n        self.update_output();\n        self.set_phase(phase);\n    }\n\n    #[must_use]\n    #[inline]\n    #[allow(clippy::missing_const_for_fn)] // false positive on non-const deref coercion\n    fn channel_count(&self) -> u8 {\n        (self.ram[0x7F] >> 4) & 0x07\n    }\n}\n\nimpl Clock for Audio {\n    fn clock(&mut self) {\n        if !self.disabled {\n            self.update_counter += 1;\n            if self.update_counter == 15 {\n                self.update_counter = 0;\n                self.update_channel();\n\n                self.current_channel -= 1;\n                if self.current_channel < 7 - self.channel_count() as i8 {\n                    self.current_channel = 7;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m024_m026_vrc6.rs",
    "content": "//! `VRC6` (Mapper 024).\n//!\n//! <https://www.nesdev.org/wiki/VRC6>\n\nuse crate::{\n    apu::PULSE_TABLE,\n    cart::Cart,\n    common::{Clock, Regional, Reset, ResetKind, Sample, Sram},\n    mapper::{self, Map, Mapper, vrc_irq::VrcIrq},\n    mem::{Banks, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\n\n/// `VRC6` revision.\n#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum Revision {\n    /// VRC6a\n    #[default]\n    A,\n    /// VRC6b\n    B,\n}\n\n/// `VRC6` registers.\n#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Regs {\n    pub banking_mode: u8,\n    pub prg: [usize; 4],\n    pub chr: [usize; 8],\n}\n\n/// `VRC6` (Mapper 024).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Vrc6 {\n    pub chr_rom: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub prg_ram: Memory<Box<[u8]>>,\n    pub regs: Regs,\n    pub revision: Revision,\n    pub mirroring: Mirroring,\n    pub irq: VrcIrq,\n    pub audio: Audio,\n    pub nt_banks: [usize; 4],\n    pub chr_banks: Banks,\n    pub prg_ram_banks: Banks,\n    pub prg_rom_banks: Banks,\n}\n\nimpl Vrc6 {\n    const PRG_RAM_SIZE: usize = 8 * 1024;\n    const PRG_WINDOW: usize = 8 * 1024;\n    const CHR_WINDOW: usize = 1024;\n\n    /// Load `Vrc6` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n        revision: Revision,\n    ) -> Result<Mapper, mapper::Error> {\n        let prg_ram = cart.prg_ram_or_default(Self::PRG_RAM_SIZE);\n        let chr_banks = Banks::new(0x0000, 0x1FFF, chr_rom.len(), Self::CHR_WINDOW)?;\n        let prg_ram_banks = Banks::new(0x6000, 0x7FFF, prg_ram.len(), Self::PRG_RAM_SIZE)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_WINDOW)?;\n        let mut vrc6 = Self {\n            chr_rom,\n            prg_rom,\n            prg_ram,\n            regs: Regs::default(),\n            revision,\n            mirroring: cart.mirroring(),\n            irq: VrcIrq::default(),\n            audio: Audio::new(),\n            nt_banks: [0; 4],\n            chr_banks,\n            prg_ram_banks,\n            prg_rom_banks,\n        };\n        vrc6.prg_rom_banks.set(3, vrc6.prg_rom_banks.last());\n        Ok(vrc6.into())\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub const fn prg_ram_enabled(&self) -> bool {\n        self.regs.banking_mode & 0x80 == 0x80\n    }\n\n    pub fn set_nametables(&mut self, nametables: &[usize]) {\n        for (bank, page) in nametables.iter().enumerate() {\n            self.set_nametable_page(bank, *page);\n        }\n    }\n\n    pub fn set_mirroring(&mut self, mirroring: Mirroring) {\n        self.mirroring = mirroring;\n        match self.mirroring {\n            Mirroring::Vertical => self.set_nametables(&[0, 1, 0, 1]),\n            Mirroring::Horizontal => self.set_nametables(&[0, 0, 1, 1]),\n            Mirroring::SingleScreenA => self.set_nametables(&[0, 0, 0, 0]),\n            Mirroring::SingleScreenB => self.set_nametables(&[1, 1, 1, 1]),\n            Mirroring::FourScreen => self.set_nametables(&[0, 1, 2, 3]),\n        }\n    }\n\n    pub const fn set_nametable_page(&mut self, bank: usize, page: usize) {\n        self.nt_banks[bank] = page;\n    }\n\n    pub fn update_chr_banks(&mut self) {\n        let (mask, or_mask) = if self.regs.banking_mode & 0x20 == 0x20 {\n            (0xFE, 1)\n        } else {\n            (0xFF, 0)\n        };\n\n        match self.regs.banking_mode & 0x03 {\n            0 => {\n                self.chr_banks.set(0, self.regs.chr[0]);\n                self.chr_banks.set(1, self.regs.chr[1]);\n                self.chr_banks.set(2, self.regs.chr[2]);\n                self.chr_banks.set(3, self.regs.chr[3]);\n                self.chr_banks.set(4, self.regs.chr[4]);\n                self.chr_banks.set(5, self.regs.chr[5]);\n                self.chr_banks.set(6, self.regs.chr[6]);\n                self.chr_banks.set(7, self.regs.chr[7]);\n            }\n            1 => {\n                self.chr_banks.set(0, self.regs.chr[0] & mask);\n                self.chr_banks.set(1, (self.regs.chr[0] & mask) | or_mask);\n                self.chr_banks.set(2, self.regs.chr[1] & mask);\n                self.chr_banks.set(3, (self.regs.chr[1] & mask) | or_mask);\n                self.chr_banks.set(4, self.regs.chr[2] & mask);\n                self.chr_banks.set(5, (self.regs.chr[2] & mask) | or_mask);\n                self.chr_banks.set(6, self.regs.chr[3] & mask);\n                self.chr_banks.set(7, (self.regs.chr[3] & mask) | or_mask);\n            }\n            _ => {\n                self.chr_banks.set(0, self.regs.chr[0]);\n                self.chr_banks.set(1, self.regs.chr[1]);\n                self.chr_banks.set(2, self.regs.chr[2]);\n                self.chr_banks.set(3, self.regs.chr[3]);\n                self.chr_banks.set(4, self.regs.chr[4] & mask);\n                self.chr_banks.set(5, (self.regs.chr[4] & mask) | or_mask);\n                self.chr_banks.set(6, self.regs.chr[5] & mask);\n                self.chr_banks.set(7, (self.regs.chr[5] & mask) | or_mask);\n            }\n        }\n\n        if self.regs.banking_mode & 0x10 == 0x10 {\n            // CHR-ROM\n            self.set_mirroring(Mirroring::FourScreen);\n            match self.regs.banking_mode & 0x2F {\n                0x20 | 0x27 => {\n                    self.set_nametable_page(0, self.regs.chr[6] & 0xFE);\n                    self.set_nametable_page(1, (self.regs.chr[6] & 0xFE) | 1);\n                    self.set_nametable_page(2, self.regs.chr[7] & 0xFE);\n                    self.set_nametable_page(3, (self.regs.chr[7] & 0xFE) | 1);\n                }\n                0x23 | 0x24 => {\n                    self.set_nametable_page(0, self.regs.chr[6] & 0xFE);\n                    self.set_nametable_page(1, self.regs.chr[7] & 0xFE);\n                    self.set_nametable_page(2, (self.regs.chr[6] & 0xFE) | 1);\n                    self.set_nametable_page(3, (self.regs.chr[7] & 0xFE) | 1);\n                }\n                0x28 | 0x2F => {\n                    self.set_nametable_page(0, self.regs.chr[6] & 0xFE);\n                    self.set_nametable_page(1, self.regs.chr[6] & 0xFE);\n                    self.set_nametable_page(2, self.regs.chr[7] & 0xFE);\n                    self.set_nametable_page(3, self.regs.chr[7] & 0xFE);\n                }\n                0x2B | 0x2C => {\n                    self.set_nametable_page(0, (self.regs.chr[6] & 0xFE) | 1);\n                    self.set_nametable_page(1, (self.regs.chr[7] & 0xFE) | 1);\n                    self.set_nametable_page(2, (self.regs.chr[6] & 0xFE) | 1);\n                    self.set_nametable_page(3, (self.regs.chr[7] & 0xFE) | 1);\n                }\n                _ => match self.regs.banking_mode & 0x07 {\n                    0 | 6 | 7 => {\n                        self.set_nametable_page(0, self.regs.chr[6]);\n                        self.set_nametable_page(1, self.regs.chr[6]);\n                        self.set_nametable_page(2, self.regs.chr[7]);\n                        self.set_nametable_page(3, self.regs.chr[7]);\n                    }\n                    1 | 5 => {\n                        self.set_nametable_page(0, self.regs.chr[4]);\n                        self.set_nametable_page(1, self.regs.chr[5]);\n                        self.set_nametable_page(2, self.regs.chr[6]);\n                        self.set_nametable_page(3, self.regs.chr[7]);\n                    }\n                    _ => {\n                        self.set_nametable_page(0, self.regs.chr[6]);\n                        self.set_nametable_page(1, self.regs.chr[7]);\n                        self.set_nametable_page(2, self.regs.chr[6]);\n                        self.set_nametable_page(3, self.regs.chr[7]);\n                    }\n                },\n            }\n        } else {\n            // CIRAM\n            match self.regs.banking_mode & 0x2F {\n                0x20 | 0x27 => self.set_mirroring(Mirroring::Vertical),\n                0x23 | 0x24 => self.set_mirroring(Mirroring::Horizontal),\n                0x28 | 0x2F => self.set_mirroring(Mirroring::SingleScreenA),\n                0x2B | 0x2C => self.set_mirroring(Mirroring::SingleScreenB),\n                _ => {\n                    self.set_mirroring(Mirroring::FourScreen);\n                    match self.regs.banking_mode & 0x07 {\n                        0 | 6 | 7 => {\n                            self.set_nametable_page(0, self.regs.chr[6] & 0x01);\n                            self.set_nametable_page(1, self.regs.chr[6] & 0x01);\n                            self.set_nametable_page(2, self.regs.chr[7] & 0x01);\n                            self.set_nametable_page(3, self.regs.chr[7] & 0x01);\n                        }\n                        1 | 5 => {\n                            self.set_nametable_page(0, self.regs.chr[4] & 0x01);\n                            self.set_nametable_page(1, self.regs.chr[5] & 0x01);\n                            self.set_nametable_page(2, self.regs.chr[6] & 0x01);\n                            self.set_nametable_page(3, self.regs.chr[7] & 0x01);\n                        }\n                        _ => {\n                            self.set_nametable_page(0, self.regs.chr[6] & 0x01);\n                            self.set_nametable_page(1, self.regs.chr[7] & 0x01);\n                            self.set_nametable_page(2, self.regs.chr[6] & 0x01);\n                            self.set_nametable_page(3, self.regs.chr[7] & 0x01);\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\nimpl Map for Vrc6 {\n    // PPU $0000..=$03FF 1K switchable CHR-ROM bank\n    // PPU $0400..=$07FF 1K switchable CHR-ROM bank\n    // PPU $0800..=$0BFF 1K switchable CHR-ROM bank\n    // PPU $0C00..=$0FFF 1K switchable CHR-ROM bank\n    // PPU $1000..=$13FF 1K switchable CHR-ROM bank\n    // PPU $1400..=$17FF 1K switchable CHR-ROM bank\n    // PPU $1800..=$1BFF 1K switchable CHR-ROM bank\n    // PPU $1C00..=$1FFF 1K switchable CHR-ROM bank\n    // PPU $2000..=$3EFF Switchable Nametables\n    //\n    // CPU $6000..=$7FFF 8K PRG-RAM bank, fixed\n    // CPU $8000..=$BFFF 16K switchable PRG-ROM bank\n    // CPU $C000..=$DFFF 8K switchable PRG-ROM bank\n    // CPU $E000..=$FFFF 8K PRG-ROM bank, fixed to the last bank\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)],\n            0x2000..=0x3EFF => {\n                let addr = addr - 0x2000;\n                let a10 = (self.nt_banks[((addr >> 10) & 0x03) as usize] << 10) as u16;\n                let addr = a10 | (!a10 & addr);\n                if self.regs.banking_mode & 0x10 == 0x00 {\n                    ciram[addr.into()]\n                } else {\n                    self.chr_rom[self.chr_banks.translate(addr)]\n                }\n            }\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x6000..=0x7FFF if self.prg_ram_enabled() => {\n                self.prg_ram[self.prg_ram_banks.translate(addr)]\n            }\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    fn prg_write(&mut self, mut addr: u16, val: u8) {\n        match addr {\n            0x6000..=0x7FFF => {\n                if self.prg_ram_enabled() {\n                    self.prg_ram[self.prg_ram_banks.translate(addr)] = val;\n                }\n            }\n            _ => {\n                if self.revision == Revision::B {\n                    // Revision B swaps A0 and A1 lines\n                    addr = (addr & 0xFFFC) | ((addr & 0x01) << 1) | ((addr & 0x02) >> 1);\n                }\n\n                // Only A0, A1 and A12-15 are used for registers, remaining addresses are mirrored.\n                match addr & 0xF003 {\n                    0x8000..=0x8003 => {\n                        // [.... PPPP]\n                        //       ||||\n                        //       ++++- Select 16 KB PRG-ROM bank at $8000-$BFFF\n                        self.prg_rom_banks\n                            .set_range(0, 1, ((val & 0x0F) << 1).into());\n                    }\n                    0x9000..=0x9003 | 0xA000..=0xA002 | 0xB000..=0xB002 => {\n                        self.audio.write_register(addr, val);\n                    }\n                    0xB003 => {\n                        // [W.PN MMDD]\n                        //  | || ||||\n                        //  | || ||++- PPU banking mode; see below\n                        //  | || ++--- Mirroring varies by banking mode, see below\n                        //  | |+------ 1: Nametables come from CHRROM, 0: Nametables come from CIRAM\n                        //  | +------- CHR A10 is 1: subject to further rules 0: according to the latched value\n                        //  +--------- PRG RAM enable\n                        self.regs.banking_mode = val;\n                        self.update_chr_banks();\n                    }\n                    0xC000..=0xC003 => {\n                        // [...P PPPP]\n                        //     | ||||\n                        //     +-++++- Select 8 KB PRG-ROM bank at $C000-$DFFF\n                        self.prg_rom_banks.set(2, (val & 0x1F).into());\n                    }\n                    0xD000..=0xD003 => {\n                        self.regs.chr[(addr & 0x03) as usize] = val.into();\n                        self.update_chr_banks();\n                    }\n                    0xE000..=0xE003 => {\n                        self.regs.chr[(4 + (addr & 0x03)) as usize] = val.into();\n                        self.update_chr_banks();\n                    }\n                    0xF000 => self.irq.write_reload(val),\n                    0xF001 => self.irq.write_control(val),\n                    0xF002 => self.irq.acknowledge(),\n                    _ => (),\n                }\n            }\n        }\n    }\n\n    /// Whether an IRQ is pending acknowledgement.\n    fn irq_pending(&self) -> bool {\n        self.irq.irq_pending\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Vrc6 {\n    fn reset(&mut self, kind: ResetKind) {\n        self.irq.reset(kind);\n        self.audio.reset(kind);\n    }\n}\n\nimpl Clock for Vrc6 {\n    fn clock(&mut self) {\n        self.irq.clock();\n        self.audio.clock();\n    }\n}\n\nimpl Regional for Vrc6 {}\nimpl Sram for Vrc6 {}\n\nimpl Sample for Vrc6 {\n    fn output(&self) -> f32 {\n        self.audio.output()\n    }\n}\n\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Audio {\n    pub pulse1: Pulse,\n    pub pulse2: Pulse,\n    pub saw: Saw,\n    pub halt: bool,\n    pub out: f32,\n}\n\nimpl Default for Audio {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Audio {\n    const fn new() -> Self {\n        Self {\n            pulse1: Pulse::new(),\n            pulse2: Pulse::new(),\n            saw: Saw::new(),\n            halt: false,\n            out: 0.0,\n        }\n    }\n\n    #[must_use]\n    fn output(&self) -> f32 {\n        let pulse_scale = PULSE_TABLE[PULSE_TABLE.len() - 1] / 15.0;\n        pulse_scale * self.out\n    }\n\n    fn write_register(&mut self, addr: u16, val: u8) {\n        // Only A0, A1 and A12-15 are used for registers, remaining addresses are mirrored.\n        match addr & 0xF003 {\n            0x9000..=0x9002 => self.pulse1.write_register(addr, val),\n            0x9003 => {\n                self.halt = val & 0x01 == 0x01;\n                let freq_shift = if val & 0x04 == 0x04 {\n                    8\n                } else if val & 0x02 == 0x02 {\n                    4\n                } else {\n                    0\n                };\n                self.pulse1.set_freq_shift(freq_shift);\n                self.pulse2.set_freq_shift(freq_shift);\n                self.saw.set_freq_shift(freq_shift);\n            }\n            0xA000..=0xA002 => self.pulse2.write_register(addr, val),\n            0xB000..=0xB002 => self.saw.write_register(addr, val),\n            _ => unreachable!(\"impossible Vrc6Audio register: {}\", addr),\n        }\n    }\n}\n\nimpl Clock for Audio {\n    fn clock(&mut self) {\n        if !self.halt {\n            self.pulse1.clock();\n            self.pulse2.clock();\n            self.saw.clock();\n\n            self.out = self.pulse1.volume() + self.pulse2.volume() + self.saw.volume();\n        }\n    }\n}\n\nimpl Reset for Audio {\n    fn reset(&mut self, _kind: ResetKind) {\n        self.halt = false;\n    }\n}\n\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Pulse {\n    pub enabled: bool,\n    pub volume: u8,\n    pub duty_cycle: u8,\n    pub ignore_duty: bool,\n    pub frequency: u16,\n    pub timer: u16,\n    pub step: u8,\n    pub freq_shift: u8,\n}\n\nimpl Default for Pulse {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Pulse {\n    const fn new() -> Self {\n        Self {\n            enabled: false,\n            volume: 0,\n            duty_cycle: 0,\n            ignore_duty: false,\n            frequency: 1,\n            timer: 1,\n            step: 0,\n            freq_shift: 0,\n        }\n    }\n\n    fn write_register(&mut self, addr: u16, val: u8) {\n        match addr & 0x03 {\n            0 => {\n                self.volume = val & 0x0F;\n                self.duty_cycle = (val & 0x70) >> 4;\n                self.ignore_duty = val & 0x80 == 0x80;\n            }\n            1 => self.frequency = (self.frequency & 0x0F00) | u16::from(val),\n            2 => {\n                self.frequency = ((u16::from(val) & 0x0F) << 8) | (self.frequency & 0xFF);\n                self.enabled = val & 0x80 == 0x80;\n                if !self.enabled {\n                    self.step = 0;\n                }\n            }\n            _ => unreachable!(\"impossible Vrc6Pulse register: {}\", addr),\n        }\n    }\n\n    const fn set_freq_shift(&mut self, val: u8) {\n        self.freq_shift = val;\n    }\n\n    fn volume(&self) -> f32 {\n        if self.enabled && (self.ignore_duty || self.step <= self.duty_cycle) {\n            f32::from(self.volume)\n        } else {\n            0.0\n        }\n    }\n}\n\nimpl Clock for Pulse {\n    fn clock(&mut self) {\n        if self.enabled {\n            self.timer -= 1;\n            if self.timer == 0 {\n                self.step = (self.step + 1) & 0x0F;\n                self.timer = (self.frequency >> self.freq_shift) + 1;\n            }\n        }\n    }\n}\n\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Saw {\n    pub enabled: bool,\n    pub accum: u8,\n    pub accum_rate: u8,\n    pub frequency: u16,\n    pub timer: u16,\n    pub step: u8,\n    pub freq_shift: u8,\n}\n\nimpl Default for Saw {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Saw {\n    const fn new() -> Self {\n        Self {\n            enabled: false,\n            accum: 0,\n            accum_rate: 0,\n            frequency: 1,\n            timer: 1,\n            step: 0,\n            freq_shift: 0,\n        }\n    }\n\n    fn write_register(&mut self, addr: u16, val: u8) {\n        match addr & 0x03 {\n            0 => {\n                self.accum_rate = val & 0x3F;\n            }\n            1 => self.frequency = (self.frequency & 0x0F00) | u16::from(val),\n            2 => {\n                self.frequency = ((u16::from(val) & 0x0F) << 8) | (self.frequency & 0xFF);\n                self.enabled = val & 0x80 == 0x80;\n                if !self.enabled {\n                    self.accum = 0;\n                    self.step = 0;\n                }\n            }\n            _ => unreachable!(\"impossible Vrc6Saw register: {}\", addr),\n        }\n    }\n\n    const fn set_freq_shift(&mut self, val: u8) {\n        self.freq_shift = val;\n    }\n\n    fn volume(&self) -> f32 {\n        if self.enabled {\n            f32::from(self.accum >> 3)\n        } else {\n            0.0\n        }\n    }\n}\n\nimpl Clock for Saw {\n    fn clock(&mut self) {\n        if self.enabled {\n            self.timer -= 1;\n            if self.timer == 0 {\n                self.step = (self.step + 1) % 14;\n                self.timer = (self.frequency >> self.freq_shift) + 1;\n\n                if self.step == 0 {\n                    self.accum = 0;\n                } else if self.step & 0x01 == 0x00 {\n                    self.accum += self.accum_rate;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m034_bnrom.rs",
    "content": "//! `BNROM` (Mapper 034).\n//!\n//! <https://www.nesdev.org/wiki/BNROM>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, Sram},\n    mapper::{self, Map, Mapper},\n    mem::{Banks, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\n\n/// `BNROM` (Mapper 034).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Bnrom {\n    pub chr: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub has_chr_ram: bool,\n    pub mirroring: Mirroring,\n    pub prg_rom_banks: Banks,\n}\n\nimpl Bnrom {\n    const PRG_ROM_WINDOW: usize = 32 * 1024;\n    const CHR_RAM_SIZE: usize = 8 * 1024;\n\n    /// Load `Bnrom` from `Cart`.\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let (chr, has_chr_ram) = cart.chr_rom_or_ram(chr_rom, Self::CHR_RAM_SIZE);\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_ROM_WINDOW)?;\n        let bnrom = Self {\n            chr,\n            prg_rom,\n            has_chr_ram,\n            mirroring: cart.mirroring(),\n            prg_rom_banks,\n        };\n        Ok(bnrom.into())\n    }\n}\n\nimpl Map for Bnrom {\n    // PPU $0000..=$1FFF 8K CHR-RAM Bank Fixed\n    // CPU $8000..=$FFFF 32K switchable PRG-ROM bank\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr[usize::from(addr) & (Self::CHR_RAM_SIZE - 1)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8, ciram: &mut CIRam) {\n        match addr {\n            0x0000..=0x1FFF if self.has_chr_ram => self.chr[usize::from(addr)] = val,\n            0x2000..=0x3EFF => ciram.write(addr, val, self.mirroring),\n            _ => (),\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        if let 0x8000..=0xFFFF = addr {\n            self.prg_rom_banks.set(0, val.into())\n        }\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Bnrom {}\nimpl Clock for Bnrom {}\nimpl Regional for Bnrom {}\nimpl Sram for Bnrom {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m034_nina001.rs",
    "content": "//! `NINA-001` (Mapper 034).\n//!\n//! <https://www.nesdev.org/wiki/NINA-001>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, Sram},\n    mapper::{self, Map, Mapper},\n    mem::{Banks, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\n\n/// `NINA-001` (Mapper 034).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Nina001 {\n    pub chr_rom: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub prg_ram: Memory<Box<[u8]>>,\n    pub mirroring: Mirroring,\n    pub chr_banks: Banks,\n    pub prg_rom_banks: Banks,\n}\n\nimpl Nina001 {\n    const PRG_ROM_WINDOW: usize = 32 * 1024;\n    const PRG_RAM_SIZE: usize = 8 * 1024;\n    const CHR_ROM_WINDOW: usize = 4 * 1024;\n\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let prg_ram = Memory::with_ram_state(Self::PRG_RAM_SIZE, cart.ram_state);\n        let chr_banks = Banks::new(0x0000, 0x1FFF, chr_rom.len(), Self::CHR_ROM_WINDOW)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_ROM_WINDOW)?;\n        let nina001 = Self {\n            chr_rom,\n            prg_rom,\n            prg_ram,\n            // hardwired to horizontal\n            mirroring: Mirroring::Horizontal,\n            chr_banks,\n            prg_rom_banks,\n        };\n        Ok(nina001.into())\n    }\n}\n\nimpl Map for Nina001 {\n    // PPU $0000..=$0FFF 4K switchable CHR ROM bank\n    // PPU $1000..=$1FFF 4K switchable CHR ROM bank\n    // CPU $8000..=$FFFF 32K switchable PRG ROM bank\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x6000..=0x7FFF => self.prg_ram[usize::from(addr) & (Self::PRG_RAM_SIZE - 1)],\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8, ciram: &mut CIRam) {\n        match addr {\n            0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)] = val,\n            0x2000..=0x3EFF => ciram.write(addr, val, self.mirroring),\n            _ => (),\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        if let 0x6000..=0x7FFF = addr {\n            match addr {\n                0x7FFD => self.prg_rom_banks.set(0, (val & 0x01).into()),\n                0x7FFE => self.chr_banks.set(0, (val & 0x0F).into()),\n                0x7FFF => self.chr_banks.set(1, (val & 0x0F).into()),\n                _ => (),\n            }\n            self.prg_ram[usize::from(addr) & (Self::PRG_RAM_SIZE - 1)] = val;\n        }\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Nina001 {}\nimpl Clock for Nina001 {}\nimpl Regional for Nina001 {}\nimpl Sram for Nina001 {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m066_gxrom.rs",
    "content": "//! `GxROM` (Mapper 066).\n//!\n//! <https://wiki.nesdev.org/w/index.php?title=GxROM>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, Sram},\n    mapper::{self, Map, Mapper},\n    mem::{Banks, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\n\n/// `GxROM` (Mapper 066).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Gxrom {\n    pub chr_rom: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub mirroring: Mirroring,\n    pub chr_banks: Banks,\n    pub prg_rom_banks: Banks,\n}\n\nimpl Gxrom {\n    const PRG_ROM_WINDOW: usize = 32 * 1024;\n    const CHR_WINDOW: usize = 8 * 1024;\n\n    const CHR_BANK_MASK: u8 = 0x0F; // 0b1111\n    const PRG_BANK_MASK: u8 = 0x30; // 0b110000\n\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let chr_banks = Banks::new(0x0000, 0x1FFF, chr_rom.len(), Self::CHR_WINDOW)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_ROM_WINDOW)?;\n        let gxrom = Self {\n            chr_rom,\n            prg_rom,\n            mirroring: cart.mirroring(),\n            chr_banks,\n            prg_rom_banks,\n        };\n        Ok(gxrom.into())\n    }\n}\n\nimpl Map for Gxrom {\n    // PPU $0000..=$1FFF 8K CHR-ROM Bank Switchable\n    // CPU $8000..=$FFFF 32K PRG-ROM Bank Switchable\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        if let 0x8000..=0xFFFF = addr {\n            self.chr_banks.set(0, (val & Self::CHR_BANK_MASK).into());\n            self.prg_rom_banks\n                .set(0, ((val & Self::PRG_BANK_MASK) >> 4).into());\n        }\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Gxrom {}\nimpl Clock for Gxrom {}\nimpl Regional for Gxrom {}\nimpl Sram for Gxrom {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m069_sunsoft_fme7.rs",
    "content": "//! `Sunsoft FME7` (Mapper 069).\n//!\n//! <https://www.nesdev.org/wiki/Sunsoft_FME-7>\n\nuse crate::{\n    apu::PULSE_TABLE,\n    cart::Cart,\n    common::{Clock, Regional, Reset, Sample, Sram},\n    mapper::{self, Map, Mapper},\n    mem::{Banks, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\n\n/// `Sunsoft FME7` registers.\n#[derive(Default, Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Regs {\n    pub command: u8,\n    pub parameter: u8,\n    pub prg_ram_enabled: bool,\n    pub irq_enabled: bool,\n    pub irq_pending: bool,\n    pub irq_counter_enabled: bool,\n    pub irq_counter: u16,\n}\n\n/// `Sunsoft FME7` (Mapper 069).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct SunsoftFme7 {\n    pub chr_rom: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub prg_ram: Memory<Box<[u8]>>,\n    pub regs: Regs,\n    pub mirroring: Mirroring,\n    pub audio: Audio,\n    pub chr_banks: Banks,\n    pub prg_banks: Banks,\n    pub prg_rom_banks: Banks,\n}\n\nimpl SunsoftFme7 {\n    const PRG_WINDOW: usize = 8 * 1024;\n    const PRG_RAM_SIZE: usize = 32 * 1024;\n    const CHR_WINDOW: usize = 1024;\n\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let prg_ram = Memory::with_ram_state(Self::PRG_RAM_SIZE, cart.ram_state);\n        let chr_banks = Banks::new(0x0000, 0x1FFF, chr_rom.len(), Self::CHR_WINDOW)?;\n        let prg_ram_banks = Banks::new(0x6000, 0x7FFF, prg_ram.len(), Self::PRG_WINDOW)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_WINDOW)?;\n        let mut sunsoft_fme7 = Self {\n            chr_rom,\n            prg_rom,\n            prg_ram,\n            regs: Regs::default(),\n            mirroring: cart.mirroring(),\n            audio: Audio::new(),\n            chr_banks,\n            prg_banks: prg_ram_banks,\n            prg_rom_banks,\n        };\n        sunsoft_fme7\n            .prg_rom_banks\n            .set(3, sunsoft_fme7.prg_rom_banks.last());\n        Ok(sunsoft_fme7.into())\n    }\n}\n\nimpl Map for SunsoftFme7 {\n    // PPU $0000..=$03FF 1K CHR-ROM Bank 1 Switchable\n    // PPU $0400..=$07FF 1K CHR-ROM Bank 2 Switchable\n    // PPU $0800..=$0BFF 1K CHR-ROM Bank 3 Switchable\n    // PPU $0C00..=$0FFF 1K CHR-ROM Bank 4 Switchable\n    // PPU $1000..=$13FF 1K CHR-ROM Bank 5 Switchable\n    // PPU $1400..=$17FF 1K CHR-ROM Bank 6 Switchable\n    // PPU $1800..=$1BFF 1K CHR-ROM Bank 7 Switchable\n    // PPU $1C00..=$1FFF 1K CHR-ROM Bank 8 Switchable\n\n    // CPU $6000..=$7FFF 8K PRG-ROM or PRG-RAM Bank 1 Switchable\n    // CPU $8000..=$9FFF 8K PRG-ROM Bank 1 Switchable\n    // CPU $A000..=$BFFF 8K PRG-ROM Bank 2 Switchable\n    // CPU $C000..=$DFFF 8K PRG-ROM Bank 3 Switchable\n    // CPU $E000..=$FFFF 8K PRG-ROM Bank 4 fixed to last\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x6000..=0x7FFF => {\n                if self.regs.prg_ram_enabled {\n                    self.prg_ram[self.prg_banks.translate(addr)]\n                } else {\n                    self.prg_rom[self.prg_banks.translate(addr)]\n                }\n            }\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        match addr {\n            0x6000..=0x7FFF if self.regs.prg_ram_enabled => {\n                self.prg_ram[self.prg_banks.translate(addr)] = val;\n            }\n            0x8000..=0x9FFF => self.regs.command = val & 0x0F,\n            0xA000..=0xBFFF => match self.regs.command {\n                0..=7 => self.chr_banks.set(self.regs.command.into(), val.into()),\n                8 => {\n                    self.regs.parameter = val;\n                    self.regs.prg_ram_enabled = val & 0x80 == 0x80;\n                    self.prg_banks.set(0, (val & 0x3F).into());\n                }\n                9..=0xB => {\n                    let bank = self.regs.command - 9;\n                    self.prg_rom_banks.set(bank.into(), (val & 0x3F).into());\n                }\n                0xC => {\n                    self.mirroring = match val & 0x03 {\n                        0b00 => Mirroring::Vertical,\n                        0b01 => Mirroring::Horizontal,\n                        0b10 => Mirroring::SingleScreenA,\n                        _ => Mirroring::SingleScreenB,\n                    }\n                }\n                0xD => {\n                    self.regs.irq_enabled = (val & 0x01) == 0x01;\n                    self.regs.irq_counter_enabled = (val & 0x80) == 0x80;\n                    self.regs.irq_pending = false;\n                }\n                0xE => self.regs.irq_counter = (self.regs.irq_counter & 0xFF00) | u16::from(val),\n                0xF => {\n                    self.regs.irq_counter = (self.regs.irq_counter & 0xFF) | (u16::from(val) << 8);\n                }\n                _ => (),\n            },\n            0xC000..=0xFFFF => self.audio.write_register(addr, val),\n            _ => (),\n        }\n    }\n\n    /// Whether an IRQ is pending acknowledgement.\n    fn irq_pending(&self) -> bool {\n        self.regs.irq_pending\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for SunsoftFme7 {}\n\nimpl Clock for SunsoftFme7 {\n    fn clock(&mut self) {\n        if self.regs.irq_counter_enabled {\n            self.regs.irq_counter = self.regs.irq_counter.wrapping_sub(1);\n            if self.regs.irq_counter == 0xFFFF && self.regs.irq_enabled {\n                self.regs.irq_pending = true;\n            }\n        }\n        self.audio.clock();\n    }\n}\n\nimpl Regional for SunsoftFme7 {}\nimpl Sram for SunsoftFme7 {}\n\nimpl Sample for SunsoftFme7 {\n    fn output(&self) -> f32 {\n        self.audio.output()\n    }\n}\n\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Audio {\n    clock_timer: u8,\n    register: u8,\n    registers: [u8; 16],\n    volumes: [u8; 16],\n    timers: [i16; 3],\n    steps: [u8; 3],\n    out: f32,\n}\n\nimpl Default for Audio {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Audio {\n    pub fn new() -> Self {\n        let mut audio = Self {\n            clock_timer: 1,\n            register: 0,\n            registers: [0; 16],\n            volumes: [0; 16],\n            timers: [0; 3],\n            steps: [0; 3],\n            out: 0.0,\n        };\n        let mut output = 1.0;\n        for volume in audio.volumes.iter_mut().skip(1) {\n            // +1.5dB 2x for every 1 step in volume\n            output *= 1.188_502_227_437_018_5;\n            output *= 1.188_502_227_437_018_5;\n            *volume = output as u8;\n        }\n        audio\n    }\n\n    #[must_use]\n    #[inline]\n    pub fn output(&self) -> f32 {\n        let pulse_scale = PULSE_TABLE[PULSE_TABLE.len() - 1] / 15.0;\n        pulse_scale * self.out\n    }\n\n    #[must_use]\n    #[inline]\n    pub fn period(&self, channel: usize) -> u16 {\n        let register = channel * 2;\n        u16::from(self.registers[register]) | (u16::from(self.registers[register + 1]) << 8)\n    }\n\n    #[must_use]\n    #[inline]\n    pub fn envelope_period(&self) -> u16 {\n        u16::from(self.registers[0x0B]) | (u16::from(self.registers[0x0C]) << 8)\n    }\n\n    #[must_use]\n    #[inline]\n    pub const fn noise_period(&self) -> u8 {\n        self.registers[0x06]\n    }\n\n    #[must_use]\n    #[inline]\n    pub const fn volume(&self, channel: usize) -> u8 {\n        self.volumes[(self.registers[channel + 8] & 0x0F) as usize]\n    }\n\n    #[must_use]\n    #[inline]\n    pub const fn envelope_enabled(&self, channel: usize) -> bool {\n        self.registers[channel + 8] & 0x10 == 0x10\n    }\n\n    #[must_use]\n    #[inline]\n    pub const fn square_enabled(&self, channel: usize) -> bool {\n        (self.registers[0x07] >> channel) & 0x01 == 0x00\n    }\n\n    #[must_use]\n    #[inline]\n    pub const fn noise_enabled(&self, channel: usize) -> bool {\n        (self.registers[0x07] >> (channel + 3)) & 0x01 == 0x00\n    }\n\n    const fn write_register(&mut self, addr: u16, val: u8) {\n        match addr {\n            0xC000..=0xDFFF => self.register = val,\n            0xE000..=0xFFFF if self.register <= 0x0F => {\n                self.registers[self.register as usize] = val;\n            }\n            _ => (),\n        }\n    }\n}\n\nimpl Clock for Audio {\n    fn clock(&mut self) {\n        if self.clock_timer == 0 {\n            self.clock_timer = 1;\n            for channel in 0..3 {\n                self.timers[channel] -= 1;\n                if self.timers[channel] <= 0 {\n                    self.timers[channel] = self.period(channel) as i16;\n                    self.steps[channel] = (self.steps[channel] + 1) & 0x0F;\n                }\n            }\n            self.out = [0, 1, 2]\n                .into_iter()\n                .filter(|&channel| self.square_enabled(channel) && self.steps[channel] < 0x08)\n                .map(|channel| self.volume(channel) as f32)\n                .sum();\n        }\n        self.clock_timer -= 1;\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m071_bf909x.rs",
    "content": "//! `Bf909x` (Mapper 071).\n//!\n//! <https://wiki.nesdev.org/w/index.php?title=INES_Mapper_071>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, Sram},\n    mapper::{self, Map, Mapper},\n    mem::{Banks, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\n\n/// `Bf909x` revision.\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum Revision {\n    #[default]\n    Bf909x,\n    Bf9097,\n}\n\n/// `Bf909x` (Mapper 071).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Bf909x {\n    pub chr: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub has_chr_ram: bool,\n    pub revision: Revision,\n    pub mirroring: Mirroring,\n    pub prg_rom_banks: Banks,\n}\n\nimpl Bf909x {\n    const PRG_ROM_WINDOW: usize = 16 * 1024;\n    const CHR_RAM_SIZE: usize = 8 * 1024;\n\n    const SINGLE_SCREEN_A: u8 = 0x10; // 0b10000\n\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let (chr, has_chr_ram) = cart.chr_rom_or_ram(chr_rom, Self::CHR_RAM_SIZE);\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_ROM_WINDOW)?;\n        let mut bf909x = Self {\n            chr,\n            prg_rom,\n            has_chr_ram,\n            revision: if cart.submapper_num() == 1 {\n                Revision::Bf9097\n            } else {\n                Revision::Bf909x\n            },\n            mirroring: cart.mirroring(),\n            prg_rom_banks,\n        };\n        bf909x.prg_rom_banks.set(1, bf909x.prg_rom_banks.last());\n        Ok(bf909x.into())\n    }\n\n    pub const fn set_revision(&mut self, rev: Revision) {\n        self.revision = rev;\n    }\n}\n\nimpl Map for Bf909x {\n    // PPU $0000..=$1FFF 8K Fixed CHR-ROM Banks\n    // CPU $8000..=$BFFF 16K PRG-ROM Bank Switchable\n    // CPU $C000..=$FFFF 16K PRG-ROM Fixed to Last Bank\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr[usize::from(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8, ciram: &mut CIRam) {\n        match addr {\n            0x0000..=0x1FFF if self.has_chr_ram => self.chr[usize::from(addr)] = val,\n            0x2000..=0x3EFF => ciram.write(addr, val, self.mirroring),\n            _ => (),\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        if let 0x8000..=0xFFFF = addr {\n            // Firehawk uses $9000 to change mirroring\n            if addr == 0x9000 {\n                self.revision = Revision::Bf9097;\n            }\n            if addr >= 0xC000 || self.revision != Revision::Bf9097 {\n                self.prg_rom_banks.set(0, val.into());\n            } else {\n                self.mirroring = if val & Self::SINGLE_SCREEN_A == Self::SINGLE_SCREEN_A {\n                    Mirroring::SingleScreenA\n                } else {\n                    Mirroring::SingleScreenB\n                };\n            }\n        }\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Bf909x {}\nimpl Clock for Bf909x {}\nimpl Regional for Bf909x {}\nimpl Sram for Bf909x {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/m079_nina003_006.rs",
    "content": "//! `NINA-003`/`NINA-006` (Mapper 079).\n//!\n//! <https://www.nesdev.org/wiki/NINA-001>\n\nuse crate::{\n    cart::Cart,\n    common::{Clock, Regional, Reset, Sram},\n    mapper::{self, Map, Mapper},\n    mem::{Banks, Memory},\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\n\n/// `NINA-003`/`NINA-006` (Mapper 079).\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Nina003006 {\n    pub chr_rom: Memory<Box<[u8]>>,\n    pub prg_rom: Memory<Box<[u8]>>,\n    pub mirroring: Mirroring,\n    pub mapper_num: u16,\n    pub chr_banks: Banks,\n    pub prg_rom_banks: Banks,\n}\n\nimpl Nina003006 {\n    const PRG_ROM_WINDOW: usize = 32 * 1024;\n    const CHR_ROM_WINDOW: usize = 8 * 1024;\n\n    pub fn load(\n        cart: &Cart,\n        chr_rom: Memory<Box<[u8]>>,\n        prg_rom: Memory<Box<[u8]>>,\n    ) -> Result<Mapper, mapper::Error> {\n        let chr_banks = Banks::new(0x0000, 0x1FFF, chr_rom.len(), Self::CHR_ROM_WINDOW)?;\n        let prg_rom_banks = Banks::new(0x8000, 0xFFFF, prg_rom.len(), Self::PRG_ROM_WINDOW)?;\n        let nina003006 = Self {\n            chr_rom,\n            prg_rom,\n            mirroring: cart.mirroring(),\n            mapper_num: cart.mapper_num(),\n            chr_banks,\n            prg_rom_banks,\n        };\n        Ok(nina003006.into())\n    }\n}\n\nimpl Map for Nina003006 {\n    // PPU $0000..=$1FFF 8K switchable CHR ROM bank\n    // CPU $8000..=$FFFF 32K switchable PRG ROM bank\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x0000..=0x1FFF => self.chr_rom[self.chr_banks.translate(addr)],\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring),\n            _ => 0,\n        }\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x8000..=0xFFFF => self.prg_rom[self.prg_rom_banks.translate(addr)],\n            _ => 0,\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        if (addr & 0xE100) == 0x4100 {\n            if self.mapper_num == 113 {\n                self.prg_rom_banks.set(0, ((val >> 3) & 0x07).into());\n                self.chr_banks\n                    .set(0, ((val & 0x07) | ((val >> 3) & 0x08)).into());\n                self.mirroring = if val & 0x80 == 0x80 {\n                    Mirroring::Vertical\n                } else {\n                    Mirroring::Horizontal\n                };\n            } else {\n                self.prg_rom_banks.set(0, ((val >> 3) & 0x01).into());\n                self.chr_banks.set(0, (val & 0x07).into());\n            }\n        }\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        self.mirroring\n    }\n}\n\nimpl Reset for Nina003006 {}\nimpl Clock for Nina003006 {}\nimpl Regional for Nina003006 {}\nimpl Sram for Nina003006 {}\n"
  },
  {
    "path": "tetanes-core/src/mapper/vrc_irq.rs",
    "content": "//! `VrcIrq`\n//!\n//! <https://www.nesdev.org/wiki/VRC_IRQ>\n\nuse crate::common::{Clock, Reset, ResetKind};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct VrcIrq {\n    pub reload: u8,\n    pub counter: u8,\n    pub prescalar_counter: i16,\n    pub irq_pending: bool,\n    pub enabled: bool,\n    pub enabled_after_ack: bool,\n    pub cycle_mode: bool,\n}\n\nimpl VrcIrq {\n    pub const fn write_reload(&mut self, val: u8) {\n        self.reload = val;\n    }\n\n    pub const fn write_control(&mut self, val: u8) {\n        self.enabled_after_ack = val & 0x01 == 0x01;\n        self.enabled = val & 0x02 == 0x02;\n        self.cycle_mode = val & 0x04 == 0x04;\n\n        if self.enabled {\n            self.counter = self.reload;\n            self.prescalar_counter = 341;\n        }\n\n        self.irq_pending = false;\n    }\n\n    pub const fn acknowledge(&mut self) {\n        self.enabled = self.enabled_after_ack;\n        self.irq_pending = false;\n    }\n}\n\nimpl Clock for VrcIrq {\n    #[inline]\n    fn clock(&mut self) {\n        if self.enabled {\n            self.prescalar_counter -= 3;\n            if self.cycle_mode || self.prescalar_counter <= 0 {\n                if self.counter == 0xFF {\n                    self.counter = self.reload;\n                    self.irq_pending = true;\n                } else {\n                    self.counter += 1;\n                }\n                self.prescalar_counter += 341;\n            }\n        }\n    }\n}\n\nimpl Reset for VrcIrq {\n    fn reset(&mut self, _kind: ResetKind) {\n        self.reload = 0;\n        self.counter = 0;\n        self.prescalar_counter = 0;\n        self.enabled = false;\n        self.enabled_after_ack = false;\n        self.cycle_mode = false;\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/mapper.rs",
    "content": "//! Memory Mappers for cartridges.\n//!\n//! <https://wiki.nesdev.org/w/index.php/Mapper>\n\nuse crate::{\n    common::{Clock, NesRegion, Regional, Reset, ResetKind, Sample, Sram},\n    fs, mem,\n    ppu::{CIRam, Mirroring},\n};\nuse serde::{Deserialize, Serialize};\nuse std::path::Path;\n\npub use bandai_fcg::BandaiFCG; // m016, m153, m157, m159\npub use m000_nrom::Nrom;\npub use m001_sxrom::{Revision as Mmc1Revision, Sxrom};\npub use m002_uxrom::Uxrom;\npub use m003_cnrom::Cnrom;\npub use m004_txrom::{Revision as Mmc3Revision, Txrom};\npub use m005_exrom::Exrom;\npub use m007_axrom::Axrom;\npub use m009_pxrom::Pxrom;\npub use m010_fxrom::Fxrom;\npub use m011_color_dreams::ColorDreams;\npub use m018_jalecoss88006::JalecoSs88006;\npub use m019_namco163::Namco163;\npub use m024_m026_vrc6::Vrc6;\npub use m034_bnrom::Bnrom;\npub use m034_nina001::Nina001;\npub use m066_gxrom::Gxrom;\npub use m069_sunsoft_fme7::SunsoftFme7;\npub use m071_bf909x::{Bf909x, Revision as Bf909Revision};\npub use m079_nina003_006::Nina003006;\n\npub mod bandai_fcg;\npub mod m000_nrom;\npub mod m001_sxrom;\npub mod m002_uxrom;\npub mod m003_cnrom;\npub mod m004_txrom;\npub mod m005_exrom;\npub mod m007_axrom;\npub mod m009_pxrom;\npub mod m010_fxrom;\npub mod m011_color_dreams;\npub mod m018_jalecoss88006;\npub mod m019_namco163;\npub mod m024_m026_vrc6;\npub mod m034_bnrom;\npub mod m034_nina001;\npub mod m066_gxrom;\npub mod m069_sunsoft_fme7;\npub mod m071_bf909x;\npub mod m079_nina003_006;\npub mod vrc_irq;\n\n/// Errors that mappers can return.\n#[derive(thiserror::Error, Debug)]\n#[must_use]\npub enum Error {\n    /// A mapper banking error.\n    #[error(transparent)]\n    Bank(#[from] mem::Error),\n}\n\n/// Allow user-controlled mapper revision for mappers that are difficult to auto-detect correctly.\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum MapperRevision {\n    // Mmc1 and Vrc6 should be properly detected by the mapper number\n    /// No known detection except DB lookup\n    Mmc3(Mmc3Revision),\n    /// Can compare to submapper 1, if header is correct\n    Bf909(Bf909Revision),\n}\n\nimpl std::fmt::Display for MapperRevision {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let s = match self {\n            Self::Mmc3(rev) => match rev {\n                Mmc3Revision::A => \"MMC3A\",\n                Mmc3Revision::BC => \"MMC3B/C\",\n                Mmc3Revision::Acc => \"MMC3Acc\",\n            },\n            Self::Bf909(rev) => match rev {\n                Bf909Revision::Bf909x => \"BF909x\",\n                Bf909Revision::Bf9097 => \"BF9097\",\n            },\n        };\n        write!(f, \"{s}\")\n    }\n}\n\n/// A `Mapper` is a specific cart variant with dedicated memory mapping logic for memory addressing and\n/// bank switching.\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\npub enum Mapper {\n    None(()),\n    /// `NROM` (Mapper 000)\n    Nrom(Nrom),\n    /// `SxROM`/`MMC1` (Mapper 001)\n    Sxrom(Sxrom),\n    /// `UxROM` (Mapper 002)\n    Uxrom(Uxrom),\n    /// `CNROM` (Mapper 003)\n    Cnrom(Cnrom),\n    /// `TxROM`/`MMC3` (Mappers 004, 088, 095, 206)\n    Txrom(Txrom),\n    /// `ExROM`/`MMC5` (Mapper 5)\n    Exrom(Box<Exrom>),\n    /// `AxROM` (Mapper 007)\n    Axrom(Axrom),\n    /// `PxROM`/`MMC2` (Mapper 009)\n    Pxrom(Pxrom),\n    /// `FxROM`/`MMC4` (Mapper 010)\n    Fxrom(Fxrom),\n    /// `Color Dreams` (Mapper 011)\n    ColorDreams(ColorDreams),\n    /// `Bandai FCG` (Mappers 016, 153, 157, and 159)\n    BandaiFCG(Box<BandaiFCG>),\n    /// `Jaleco SS88006` (Mapper 018)\n    JalecoSs88006(JalecoSs88006),\n    /// `Namco163` (Mapper 019)\n    Namco163(Box<Namco163>),\n    /// `VRC6` (Mapper 024).\n    Vrc6(Box<Vrc6>),\n    /// `BNROM` (Mapper 034).\n    Bnrom(Bnrom),\n    /// `NINA-001` (Mapper 034).\n    Nina001(Nina001),\n    /// `GxROM` (Mapper 066).\n    Gxrom(Gxrom),\n    /// `Sunsoft FME7` (Mapper 069).\n    SunsoftFme7(SunsoftFme7),\n    /// `Bf909x` (Mapper 071).\n    Bf909x(Bf909x),\n    /// `NINA-003`/`NINA-006` (Mapper 079).\n    Nina003006(Nina003006),\n}\n\n/// Implement `From<T>` for `Mapper`.\nmacro_rules! impl_from_board {\n    (@impl $variant:ident, $board:ident) => {\n        impl From<$board> for Mapper {\n            fn from(board: $board) -> Self {\n                Self::$variant(board)\n            }\n        }\n    };\n    (@impl $variant:ident, Box<$board:ident>) => {\n        impl From<$board> for Mapper {\n            fn from(board: $board) -> Self {\n                Self::$variant(Box::new(board))\n            }\n        }\n        impl From<Box<$board>> for Mapper {\n            fn from(board: Box<$board>) -> Self {\n                Self::$variant(board)\n            }\n        }\n    };\n    ($($variant:ident($($tt:tt)+)),+ $(,)?) => {\n        $(impl_from_board!(@impl $variant, $($tt)+);)+\n    };\n}\n\nimpl_from_board!(\n    Nrom(Nrom),\n    Sxrom(Sxrom),\n    Uxrom(Uxrom),\n    Cnrom(Cnrom),\n    Txrom(Txrom),\n    Exrom(Box<Exrom>),\n    Axrom(Axrom),\n    Pxrom(Pxrom),\n    Fxrom(Fxrom),\n    ColorDreams(ColorDreams),\n    BandaiFCG(Box<BandaiFCG>),\n    JalecoSs88006(JalecoSs88006),\n    Namco163(Box<Namco163>),\n    Vrc6(Box<Vrc6>),\n    Bnrom(Bnrom),\n    Nina001(Nina001),\n    Gxrom(Gxrom),\n    SunsoftFme7(SunsoftFme7),\n    Bf909x(Bf909x),\n    Nina003006(Nina003006),\n);\n\n/// Implement `Map` function for all `Mapper` variants.\nmacro_rules! impl_map {\n    ($self:expr, $fn:ident$(,)? $($args:expr),*$(,)?) => {\n        match $self {\n            Mapper::None(m) => m.$fn($($args),*),\n            Mapper::Nrom(m) => m.$fn($($args),*),\n            Mapper::Sxrom(m) => m.$fn($($args),*),\n            Mapper::Uxrom(m) => m.$fn($($args),*),\n            Mapper::Cnrom(m) => m.$fn($($args),*),\n            Mapper::Txrom(m) => m.$fn($($args),*),\n            Mapper::Exrom(m) => m.$fn($($args),*),\n            Mapper::Axrom(m) => m.$fn($($args),*),\n            Mapper::Pxrom(m) => m.$fn($($args),*),\n            Mapper::Fxrom(m) => m.$fn($($args),*),\n            Mapper::ColorDreams(m) => m.$fn($($args),*),\n            Mapper::BandaiFCG(m) => m.$fn($($args),*),\n            Mapper::JalecoSs88006(m) => m.$fn($($args),*),\n            Mapper::Namco163(m) => m.$fn($($args),*),\n            Mapper::Vrc6(m) => m.$fn($($args),*),\n            Mapper::Bnrom(m) => m.$fn($($args),*),\n            Mapper::Nina001(m) => m.$fn($($args),*),\n            Mapper::Gxrom(m) => m.$fn($($args),*),\n            Mapper::SunsoftFme7(m) => m.$fn($($args),*),\n            Mapper::Bf909x(m) => m.$fn($($args),*),\n            Mapper::Nina003006(m) => m.$fn($($args),*),\n        }\n    };\n}\n\nimpl Map for Mapper {\n    /// Read a byte from CHR-ROM/RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_read(&mut self, addr: u16, ciram: &CIRam) -> u8 {\n        impl_map!(self, chr_read, addr, ciram)\n    }\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        impl_map!(self, chr_peek, addr, ciram)\n    }\n\n    /// Read a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_read(&mut self, addr: u16) -> u8 {\n        impl_map!(self, prg_read, addr)\n    }\n\n    /// Read a byte from PRG-ROM/RAM at a given address.\n    #[inline(always)]\n    fn prg_peek(&self, addr: u16) -> u8 {\n        impl_map!(self, prg_peek, addr)\n    }\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8, ciram: &mut CIRam) {\n        impl_map!(self, chr_write, addr, val, ciram)\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    #[inline(always)]\n    fn prg_write(&mut self, addr: u16, val: u8) {\n        impl_map!(self, prg_write, addr, val)\n    }\n\n    /// Synchronize a read from a PPU address.\n    fn ppu_read(&mut self, addr: u16) {\n        impl_map!(self, ppu_read, addr)\n    }\n\n    /// Synchronize a write to a PPU address.\n    fn ppu_write(&mut self, addr: u16, val: u8) {\n        impl_map!(self, ppu_write, addr, val)\n    }\n\n    /// Whether an IRQ is pending acknowledgement.\n    fn irq_pending(&self) -> bool {\n        impl_map!(self, irq_pending)\n    }\n\n    /// Whether an DMA is pending acknowledgement.\n    fn dma_pending(&self) -> bool {\n        impl_map!(self, dma_pending)\n    }\n\n    /// Clear pending DMA.\n    fn clear_dma_pending(&mut self) {\n        impl_map!(self, clear_dma_pending)\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    #[inline(always)]\n    fn mirroring(&self) -> Mirroring {\n        impl_map!(self, mirroring)\n    }\n}\n\nimpl Sample for Mapper {\n    /// Output a single audio sample.\n    #[inline]\n    fn output(&self) -> f32 {\n        match self {\n            Self::Exrom(exrom) => exrom.output(),\n            Self::Namco163(namco163) => namco163.output(),\n            Self::Vrc6(vrc6) => vrc6.output(),\n            Self::SunsoftFme7(sunsoft_fme7) => sunsoft_fme7.output(),\n            _ => 0.0,\n        }\n    }\n}\n\nimpl Reset for Mapper {\n    /// Reset the component given the [`ResetKind`].\n    fn reset(&mut self, kind: ResetKind) {\n        impl_map!(self, reset, kind)\n    }\n}\n\nimpl Clock for Mapper {\n    /// Clock component once.\n    #[inline]\n    fn clock(&mut self) {\n        impl_map!(self, clock)\n    }\n}\n\nimpl Regional for Mapper {\n    /// Return the current region.\n    fn region(&self) -> NesRegion {\n        impl_map!(self, region)\n    }\n\n    /// Set the region.\n    fn set_region(&mut self, region: NesRegion) {\n        impl_map!(self, set_region, region)\n    }\n}\n\nimpl Sram for Mapper {\n    /// Save RAM to a given path.\n    fn save(&self, path: impl AsRef<Path>) -> fs::Result<()> {\n        impl_map!(self, save, path)\n    }\n\n    /// Load save RAM from a given path.\n    fn load(&mut self, path: impl AsRef<Path>) -> fs::Result<()> {\n        impl_map!(self, load, path)\n    }\n}\n\nimpl Mapper {\n    /// An empty Mapper.\n    pub const fn none() -> Self {\n        Self::None(())\n    }\n\n    /// Whether mapper is `None`.\n    pub const fn is_none(&self) -> bool {\n        matches!(self, Self::None(_))\n    }\n}\n\nimpl Default for Mapper {\n    fn default() -> Self {\n        Self::none()\n    }\n}\n\n/// Trait implemented for all [`Mapper`]s.\npub trait Map: Clock + Regional + Reset + Sram {\n    /// Read a byte from CHR-ROM/RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_read(&mut self, addr: u16, ciram: &CIRam) -> u8 {\n        self.chr_peek(addr, ciram)\n    }\n\n    /// Peek a byte from CHR-ROM/RAM at a given address.\n    // `chr_peek` has to be implemented at read from CHR and CIRam.\n    fn chr_peek(&self, _addr: u16, _ciram: &CIRam) -> u8;\n\n    /// Read a byte from PRG-ROM/RAM at a given address.\n    ///\n    /// Defaults to `prg_peek`.\n    #[inline(always)]\n    fn prg_read(&mut self, addr: u16) -> u8 {\n        self.prg_peek(addr)\n    }\n\n    /// Peek a byte from PRG-ROM/RAM at a given address.\n    // `prg_peek` has to be implemented to read PRG-ROM.\n    fn prg_peek(&self, _addr: u16) -> u8;\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    // `chr_write` has to be implemented at least to write to CIRam.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8, ciram: &mut CIRam) {\n        if let 0x2000..=0x3EFF = addr {\n            ciram.write(addr, val, self.mirroring());\n        }\n    }\n\n    /// Write a byte to PRG-RAM at a given address.\n    fn prg_write(&mut self, _addr: u16, _val: u8) {}\n\n    /// Synchronize a read from a PPU address.\n    fn ppu_read(&mut self, _addr: u16) {}\n\n    /// Synchronize a write to a PPU address.\n    fn ppu_write(&mut self, _addr: u16, _val: u8) {}\n\n    /// Whether an IRQ is pending acknowledgement.\n    fn irq_pending(&self) -> bool {\n        false\n    }\n\n    /// Clear pending DMA.\n    fn clear_dma_pending(&mut self) {}\n\n    /// Whether an DMA is pending acknowledgement.\n    fn dma_pending(&self) -> bool {\n        false\n    }\n\n    /// Returns the current [`Mirroring`] mode.\n    // All mappers have mirroring, even if it's hard-wired.\n    fn mirroring(&self) -> Mirroring;\n}\n\nimpl Map for () {\n    fn chr_peek(&self, addr: u16, ciram: &CIRam) -> u8 {\n        match addr {\n            0x2000..=0x3EFF => ciram.peek(addr, self.mirroring()),\n            _ => 0,\n        }\n    }\n\n    fn prg_peek(&self, _addr: u16) -> u8 {\n        0\n    }\n\n    fn mirroring(&self) -> Mirroring {\n        Mirroring::default()\n    }\n}\n\nimpl Sample for () {}\nimpl Reset for () {}\nimpl Clock for () {}\nimpl Regional for () {}\nimpl Sram for () {}\n"
  },
  {
    "path": "tetanes-core/src/mem.rs",
    "content": "//! Memory and Bankswitching implementations.\n\nuse rand::Rng;\nuse serde::{\n    Deserialize, Deserializer, Serialize, Serializer,\n    de::{SeqAccess, Visitor},\n    ser::SerializeTuple,\n};\nuse std::{\n    fmt,\n    marker::PhantomData,\n    num::NonZeroUsize,\n    ops::{Deref, DerefMut, Index, IndexMut, Range, RangeInclusive},\n    str::FromStr,\n};\nuse tracing::warn;\n\n/// Represents ROM or RAM memory in bytes, with a custom Debug implementation that avoids\n/// printing the entire contents.\n#[derive(Default, Copy, Clone, Serialize, Deserialize)]\npub struct Memory<D> {\n    data: D,\n}\n\nimpl Memory<Box<[u8]>> {\n    /// Create an empty `Memory` instance.\n    pub fn empty() -> Self {\n        Self {\n            data: Vec::new().into_boxed_slice(),\n        }\n    }\n\n    /// Create a default `Memory` instance.\n    pub fn new(mut size: usize) -> Self {\n        if size > 0 && !size.is_power_of_two() {\n            warn!(\"memory size {size} must be a power of two\");\n            size = size.next_power_of_two();\n        }\n        Self {\n            data: vec![0; size].into_boxed_slice(),\n        }\n    }\n\n    pub fn with_ram_state(size: usize, state: RamState) -> Self {\n        let mut mem = Self::new(size);\n        state.fill(&mut mem.data);\n        mem\n    }\n\n    /// Shortens `Memory` by keeping the first `size` bytes and dropping the rest.\n    pub fn truncate(&mut self, size: usize) {\n        let mut data = std::mem::take(&mut self.data).to_vec();\n        data.truncate(size);\n        self.data = data.into_boxed_slice();\n    }\n}\n\nimpl<T, const N: usize> Memory<ConstArray<T, N>> {\n    /// Create a default ROM `Memory` instance.\n    pub fn new_const() -> Self\n    where\n        T: Default + Copy,\n    {\n        Self::default()\n    }\n}\n\nimpl<const N: usize> Memory<ConstArray<u8, N>> {\n    /// Fill memory based on [`RamState`].\n    pub fn with_ram_state_const(state: RamState) -> Self {\n        let mut mem = Self::default();\n        state.fill(&mut *mem.data);\n        mem\n    }\n}\n\nimpl fmt::Debug for Memory<Box<[u8]>> {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"Memory\")\n            .field(\"len\", &self.data.len())\n            .finish()\n    }\n}\n\nimpl<T, const N: usize> fmt::Debug for Memory<ConstArray<T, N>> {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"Memory\")\n            .field(\"len\", &self.data.len())\n            .finish()\n    }\n}\n\nimpl<D> Deref for Memory<D> {\n    type Target = D;\n    fn deref(&self) -> &Self::Target {\n        &self.data\n    }\n}\n\nimpl<D: DerefMut> DerefMut for Memory<D> {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.data\n    }\n}\n\nimpl<T, D: AsRef<[T]>> AsRef<[T]> for Memory<D> {\n    fn as_ref(&self) -> &[T] {\n        self.data.as_ref()\n    }\n}\n\nimpl<T, D: AsMut<[T]>> AsMut<[T]> for Memory<D> {\n    fn as_mut(&mut self) -> &mut [T] {\n        self.data.as_mut()\n    }\n}\n\nimpl<T> Index<usize> for Memory<Box<[T]>> {\n    type Output = T;\n\n    #[inline(always)]\n    fn index(&self, index: usize) -> &Self::Output {\n        self.data.index(index & (self.data.len() - 1))\n    }\n}\n\nimpl<T> IndexMut<usize> for Memory<Box<[T]>> {\n    #[inline(always)]\n    fn index_mut(&mut self, index: usize) -> &mut Self::Output {\n        self.data.index_mut(index & (self.data.len() - 1))\n    }\n}\n\nimpl<T> Index<Range<usize>> for Memory<Box<[T]>> {\n    type Output = [T];\n\n    #[inline]\n    fn index(&self, range: Range<usize>) -> &Self::Output {\n        self.data\n            .index((range.start & (self.data.len() - 1))..range.end.min(self.len()))\n    }\n}\n\nimpl<T> IndexMut<Range<usize>> for Memory<Box<[T]>> {\n    #[inline]\n    fn index_mut(&mut self, range: Range<usize>) -> &mut Self::Output {\n        self.data\n            .index_mut((range.start & (self.data.len() - 1))..range.end.min(self.len()))\n    }\n}\n\nimpl<T> Index<RangeInclusive<usize>> for Memory<Box<[T]>> {\n    type Output = [T];\n\n    #[inline]\n    fn index(&self, range: RangeInclusive<usize>) -> &Self::Output {\n        self.data.index(\n            (range.start() & (self.data.len() - 1))..=*range.end().min(&(self.data.len() - 1)),\n        )\n    }\n}\n\nimpl<T> IndexMut<RangeInclusive<usize>> for Memory<Box<[T]>> {\n    #[inline]\n    fn index_mut(&mut self, range: RangeInclusive<usize>) -> &mut Self::Output {\n        self.data.index_mut(\n            (range.start() & (self.data.len() - 1))..=*range.end().min(&(self.data.len() - 1)),\n        )\n    }\n}\n\n#[repr(transparent)]\n#[derive(Copy, Clone)]\npub struct ConstArray<T, const N: usize> {\n    data: [T; N],\n}\n\nimpl<T, const N: usize> ConstArray<T, N> {\n    /// Create a new `ConstSlice` instance.\n    pub fn new() -> Self\n    where\n        T: Default + Copy,\n    {\n        Self::default()\n    }\n\n    /// Create a new `ConstSlice` instance filled with `val`.\n    pub const fn filled(val: T) -> Self\n    where\n        T: Copy,\n    {\n        Self { data: [val; N] }\n    }\n}\n\nimpl<T, const N: usize> fmt::Debug for ConstArray<T, N> {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"ConstArray\")\n            .field(\"len\", &self.data.len())\n            .finish()\n    }\n}\n\nimpl<T: Default + Copy, const N: usize> Default for ConstArray<T, N> {\n    fn default() -> Self {\n        Self {\n            data: [T::default(); N],\n        }\n    }\n}\n\nimpl<T, const N: usize> From<[T; N]> for ConstArray<T, N> {\n    fn from(data: [T; N]) -> Self {\n        Self { data }\n    }\n}\n\nimpl<T, const N: usize> Deref for ConstArray<T, N> {\n    type Target = [T; N];\n\n    #[inline]\n    fn deref(&self) -> &Self::Target {\n        &self.data\n    }\n}\n\nimpl<T, const N: usize> DerefMut for ConstArray<T, N> {\n    #[inline]\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.data\n    }\n}\n\nimpl<T, const N: usize> AsRef<[T]> for ConstArray<T, N> {\n    #[inline]\n    fn as_ref(&self) -> &[T] {\n        self.data.as_ref()\n    }\n}\n\nimpl<T, const N: usize> AsMut<[T]> for ConstArray<T, N> {\n    #[inline]\n    fn as_mut(&mut self) -> &mut [T] {\n        self.data.as_mut()\n    }\n}\n\nimpl<T, const N: usize> Index<usize> for ConstArray<T, N> {\n    type Output = T;\n\n    #[inline]\n    fn index(&self, index: usize) -> &Self::Output {\n        self.data.index(index & (N - 1))\n    }\n}\n\nimpl<T, const N: usize> IndexMut<usize> for ConstArray<T, N> {\n    #[inline]\n    fn index_mut(&mut self, index: usize) -> &mut Self::Output {\n        self.data.index_mut(index & (N - 1))\n    }\n}\n\nimpl<T, const N: usize> Index<Range<usize>> for ConstArray<T, N> {\n    type Output = [T];\n\n    #[inline]\n    fn index(&self, range: Range<usize>) -> &Self::Output {\n        self.data.index(range.start & (N - 1)..range.end.min(N))\n    }\n}\n\nimpl<T, const N: usize> IndexMut<Range<usize>> for ConstArray<T, N> {\n    #[inline]\n    fn index_mut(&mut self, range: Range<usize>) -> &mut Self::Output {\n        self.data.index_mut(range.start & (N - 1)..range.end.min(N))\n    }\n}\n\nimpl<T, const N: usize> Index<RangeInclusive<usize>> for ConstArray<T, N> {\n    type Output = [T];\n\n    #[inline]\n    fn index(&self, range: RangeInclusive<usize>) -> &Self::Output {\n        self.data\n            .index(range.start() & (N - 1)..=*range.end().min(&(N - 1)))\n    }\n}\n\nimpl<T, const N: usize> IndexMut<RangeInclusive<usize>> for ConstArray<T, N> {\n    #[inline]\n    fn index_mut(&mut self, range: RangeInclusive<usize>) -> &mut Self::Output {\n        self.data\n            .index_mut(range.start() & (N - 1)..=*range.end().min(&(N - 1)))\n    }\n}\n\nimpl<T: Serialize, const N: usize> Serialize for ConstArray<T, N> {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let mut s = serializer.serialize_tuple(N)?;\n        for item in &self.data {\n            s.serialize_element(item)?;\n        }\n        s.end()\n    }\n}\n\nimpl<'de, T, const N: usize> Deserialize<'de> for ConstArray<T, N>\nwhere\n    T: Deserialize<'de> + Default + Copy,\n{\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        struct ArrayVisitor<T, const N: usize>(PhantomData<T>);\n\n        impl<'de, T, const N: usize> Visitor<'de> for ArrayVisitor<T, N>\n        where\n            T: Deserialize<'de> + Default + Copy,\n        {\n            type Value = [T; N];\n\n            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n                formatter.write_str(&format!(\"an array of length {N}\"))\n            }\n\n            #[inline]\n            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>\n            where\n                A: SeqAccess<'de>,\n            {\n                let mut data = [T::default(); N];\n                for data in &mut data {\n                    match (seq.next_element())? {\n                        Some(val) => *data = val,\n                        None => return Err(serde::de::Error::invalid_length(N, &self)),\n                    }\n                }\n                Ok(data)\n            }\n        }\n\n        deserializer\n            .deserialize_tuple(N, ArrayVisitor(PhantomData))\n            .map(|data| Self { data })\n    }\n}\n\n/// A trait that represents memory read operations. Reads typically have side-effects.\npub trait Read {\n    /// Read from the given address.\n    #[inline(always)]\n    fn read(&mut self, addr: u16) -> u8 {\n        self.peek(addr)\n    }\n\n    /// Peek from the given address.\n    fn peek(&self, addr: u16) -> u8;\n}\n\n/// A trait that represents memory write operations.\npub trait Write {\n    /// Write value to the given address.\n    fn write(&mut self, addr: u16, val: u8);\n}\n\n/// RAM in a given state on startup.\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[must_use]\npub enum RamState {\n    #[default]\n    AllZeros,\n    AllOnes,\n    Random,\n}\n\nimpl RamState {\n    /// Return `RamState` options as a slice.\n    pub const fn as_slice() -> &'static [Self] {\n        &[Self::AllZeros, Self::AllOnes, Self::Random]\n    }\n\n    /// Return `RamState` as a `str`.\n    #[must_use]\n    pub const fn as_str(&self) -> &'static str {\n        match self {\n            Self::AllZeros => \"all-zeros\",\n            Self::AllOnes => \"all-ones\",\n            Self::Random => \"random\",\n        }\n    }\n\n    /// Fills data slice based on `RamState`.\n    pub fn fill(&self, data: &mut [u8]) {\n        match self {\n            RamState::AllZeros => data.fill(0x00),\n            RamState::AllOnes => data.fill(0xFF),\n            RamState::Random => {\n                rand::rng().fill_bytes(data);\n            }\n        }\n    }\n}\n\nimpl From<usize> for RamState {\n    fn from(value: usize) -> Self {\n        match value {\n            0 => Self::AllZeros,\n            1 => Self::AllOnes,\n            _ => Self::Random,\n        }\n    }\n}\n\nimpl AsRef<str> for RamState {\n    fn as_ref(&self) -> &str {\n        self.as_str()\n    }\n}\n\nimpl std::fmt::Display for RamState {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let s = match self {\n            Self::AllZeros => \"All $00\",\n            Self::AllOnes => \"All $FF\",\n            Self::Random => \"Random\",\n        };\n        write!(f, \"{s}\")\n    }\n}\n\nimpl FromStr for RamState {\n    type Err = &'static str;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"all-zeros\" => Ok(Self::AllZeros),\n            \"all-ones\" => Ok(Self::AllOnes),\n            \"random\" => Ok(Self::Random),\n            _ => Err(\"invalid RamState value. valid options: `all-zeros`, `all-ones`, or `random`\"),\n        }\n    }\n}\n\n/// Represents allowed memory bank access.\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]\n#[must_use]\npub enum BankAccess {\n    None,\n    Read,\n    ReadWrite,\n}\n\n/// Represents a set of memory banks.\n#[derive(Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Banks {\n    size: NonZeroUsize,\n    window: NonZeroUsize,\n    shift: usize,\n    page_mask: usize,\n    bank_mask: usize,\n    banks: Box<[usize]>,\n    access: Box<[BankAccess]>,\n    page_count: usize,\n}\n\n#[derive(thiserror::Error, Debug)]\n#[must_use]\npub enum Error {\n    #[error(\"Memory Bank `window` must a non-zero power of two\")]\n    InvalidWindow,\n    #[error(\"Memory Bank `size` must be non-zero\")]\n    InvalidSize,\n    #[error(\"Memory Bank `capacity` must be non-zero\")]\n    InvalidCapacity,\n}\n\nimpl Banks {\n    pub fn new(\n        start: usize,\n        end: usize,\n        capacity: usize,\n        window: impl TryInto<NonZeroUsize>,\n    ) -> Result<Self, Error> {\n        let window = window.try_into().map_err(|_| Error::InvalidWindow)?;\n        if !window.is_power_of_two() {\n            return Err(Error::InvalidWindow);\n        }\n\n        let size = NonZeroUsize::try_from(end - start).map_err(|_| Error::InvalidSize)?;\n        if capacity == 0 {\n            return Err(Error::InvalidCapacity);\n        }\n\n        let bank_count = (size.get() + 1) / window;\n        let page_count = capacity / window;\n\n        let mut banks = vec![0; bank_count].into_boxed_slice();\n        let access = vec![BankAccess::ReadWrite; bank_count].into_boxed_slice();\n        for (i, bank) in banks.iter_mut().enumerate() {\n            *bank = (i * window.get()) % capacity;\n        }\n\n        Ok(Self {\n            size,\n            window,\n            shift: window.trailing_zeros() as usize,\n            page_mask: page_count.saturating_sub(1),\n            bank_mask: bank_count.saturating_sub(1),\n            banks,\n            access,\n            page_count,\n        })\n    }\n\n    #[inline(always)]\n    pub fn set(&mut self, bank: usize, page: usize) {\n        let bank = bank & self.bank_mask;\n        self.banks[bank] = (page & self.page_mask) << self.shift;\n        debug_assert!(\n            self.banks[bank] <= self.page_count * self.window.get(),\n            \"memory page {} is out of bounds (max: {})\",\n            self.banks[bank],\n            self.page_count * self.window.get()\n        );\n    }\n\n    #[inline(always)]\n    pub fn set_range(&mut self, start: usize, end: usize, page: usize) {\n        let mut new_addr = (page & self.page_mask) << self.shift;\n        debug_assert!(\n            end < self.banks.len(),\n            \"end memory bank {end} is out of bounds (max {})\",\n            self.banks.len()\n        );\n        for bank in start..=end {\n            self.banks[bank] = new_addr;\n            debug_assert!(\n                self.banks[bank] <= self.page_count * self.window.get(),\n                \"memory page {} is out of bounds (max: {})\",\n                self.banks[bank],\n                self.page_count * self.window.get()\n            );\n            new_addr += self.window.get();\n        }\n    }\n\n    #[inline(always)]\n    pub fn set_access(&mut self, bank: usize, access: BankAccess) {\n        self.access[bank & self.bank_mask] = access;\n    }\n\n    #[inline(always)]\n    pub fn set_access_range(&mut self, start: usize, end: usize, access: BankAccess) {\n        for slot in start..=end {\n            self.set_access(slot, access);\n        }\n    }\n\n    #[inline(always)]\n    pub const fn readable(&self, addr: u16) -> bool {\n        matches!(\n            self.access[self.get(addr) & self.bank_mask],\n            BankAccess::Read | BankAccess::ReadWrite\n        )\n    }\n\n    #[inline(always)]\n    pub const fn writable(&self, addr: u16) -> bool {\n        matches!(\n            self.access[self.get(addr) & self.bank_mask],\n            BankAccess::ReadWrite\n        )\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub const fn last(&self) -> usize {\n        self.page_count.saturating_sub(1)\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub const fn banks_len(&self) -> usize {\n        self.banks.len()\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub const fn get(&self, addr: u16) -> usize {\n        (addr as usize & self.size.get()) >> self.shift\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub const fn translate(&self, addr: u16) -> usize {\n        (self.banks[self.get(addr) & self.bank_mask]) | (addr as usize) & (self.window.get() - 1)\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub const fn page(&self, bank: usize) -> usize {\n        self.banks[bank] >> self.shift\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub const fn page_offset(&self, bank: usize) -> usize {\n        self.banks[bank]\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub const fn page_count(&self) -> usize {\n        self.page_count\n    }\n}\n\nimpl std::fmt::Debug for Banks {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {\n        f.debug_struct(\"Bank\")\n            .field(\"size\", &format_args!(\"${:04X}\", self.size))\n            .field(\"window\", &format_args!(\"${:04X}\", self.window))\n            .field(\"shift\", &self.shift)\n            .field(\"mask\", &self.shift)\n            .field(\"banks\", &self.banks)\n            .field(\"page_count\", &self.page_count)\n            .finish()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn get_bank() {\n        let banks = Banks::new(\n            0x8000,\n            0xFFFF,\n            128 * 1024,\n            NonZeroUsize::new(0x4000).unwrap(),\n        )\n        .unwrap();\n        assert_eq!(banks.get(0x8000), 0);\n        assert_eq!(banks.get(0x9FFF), 0);\n        assert_eq!(banks.get(0xA000), 0);\n        assert_eq!(banks.get(0xBFFF), 0);\n        assert_eq!(banks.get(0xC000), 1);\n        assert_eq!(banks.get(0xDFFF), 1);\n        assert_eq!(banks.get(0xE000), 1);\n        assert_eq!(banks.get(0xFFFF), 1);\n    }\n\n    #[test]\n    fn bank_translate() {\n        let mut banks = Banks::new(\n            0x8000,\n            0xFFFF,\n            128 * 1024,\n            NonZeroUsize::new(0x2000).unwrap(),\n        )\n        .unwrap();\n\n        let last_bank = banks.last();\n        assert_eq!(last_bank, 15, \"bank count\");\n\n        assert_eq!(banks.translate(0x8000), 0x0000);\n        banks.set(0, 1);\n        assert_eq!(banks.translate(0x8000), 0x2000);\n        banks.set(0, 2);\n        assert_eq!(banks.translate(0x8000), 0x4000);\n        banks.set(0, 0);\n        assert_eq!(banks.translate(0x8000), 0x0000);\n        banks.set(0, banks.last());\n        assert_eq!(banks.translate(0x8000), 0x1E000);\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/ppu/ctrl.rs",
    "content": "//! PPUCTRL register implementation.\n//!\n//! See: <https://wiki.nesdev.org/w/index.php/PPU_registers#PPUCTRL>\n\nuse crate::common::{Reset, ResetKind};\nuse bitflags::bitflags;\nuse serde::{Deserialize, Serialize};\n\n/// PPUCTRL register.\n///\n/// See: <https://wiki.nesdev.org/w/index.php/PPU_registers#PPUCTRL>\n#[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]\n#[must_use]\npub struct Ctrl {\n    pub bg_select: u16,\n    pub spr_select: u16,\n    pub spr_height: u16,\n    pub vram_increment: bool,\n    pub master_slave: u8,\n    pub nmi_enabled: bool,\n    pub bits: Bits,\n}\n\nbitflags! {\n    // $2000 PPUCTRL\n    //\n    // https://wiki.nesdev.org/w/index.php/PPU_registers#PPUCTRL\n    // VPHB SINN\n    // |||| ||++- Nametable Select: 0b00 = $2000 (upper-left); 0b01 = $2400 (upper-right);\n    // |||| ||                      0b10 = $2800 (lower-left); 0b11 = $2C00 (lower-right)\n    // |||| |||+-   Also For PPUSCROLL: 1 = Add 256 to X scroll\n    // |||| ||+--   Also For PPUSCROLL: 1 = Add 240 to Y scroll\n    // |||| |+--- VRAM Increment Mode: 0 = add 1, going across; 1 = add 32, going down\n    // |||| +---- Sprite Pattern Select for 8x8: 0 = $0000, 1 = $1000, ignored in 8x16 mode\n    // |||+------ Background Pattern Select: 0 = $0000, 1 = $1000\n    // ||+------- Sprite Height: 0 = 8x8, 1 = 8x16\n    // |+-------- PPU Master/Slave: 0 = read from EXT, 1 = write to EXT\n    // +--------- NMI Enable: NMI at next vblank: 0 = off, 1: on\n    #[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]\n    #[must_use]\n    pub struct Bits: u8 {\n        const NAMETABLE1 = 0x01;\n        const NAMETABLE2 = 0x02;\n        const VRAM_INCREMENT = 0x04;\n        const SPR_SELECT = 0x08;\n        const BG_SELECT = 0x10;\n        const SPR_HEIGHT = 0x20;\n        const MASTER_SLAVE = 0x40;\n        const NMI_ENABLE = 0x80;\n    }\n}\n\nimpl Ctrl {\n    pub fn new() -> Self {\n        let mut ctrl = Self::default();\n        ctrl.write(0);\n        ctrl\n    }\n\n    pub const fn write(&mut self, val: u8) {\n        self.bits = Bits::from_bits_truncate(val);\n        // 0x1000 or 0x0000\n        self.spr_select = self.bits.contains(Bits::SPR_SELECT) as u16 * 0x1000;\n        // 0x1000 or 0x0000\n        self.bg_select = self.bits.contains(Bits::BG_SELECT) as u16 * 0x1000;\n        // 16 or 8\n        self.spr_height = self.bits.contains(Bits::SPR_HEIGHT) as u16 * 8 + 8;\n        // 1 or 0\n        self.master_slave = self.bits.contains(Bits::MASTER_SLAVE) as u8;\n        self.nmi_enabled = self.bits.contains(Bits::NMI_ENABLE);\n        // 32 or 1\n        self.vram_increment = self.bits.contains(Bits::VRAM_INCREMENT);\n    }\n}\n\nimpl Reset for Ctrl {\n    // https://www.nesdev.org/wiki/PPU_power_up_state\n    fn reset(&mut self, _kind: ResetKind) {\n        self.write(0);\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/ppu/frame.rs",
    "content": "//! PPU frame implementation.\n\nuse crate::{\n    common::{Reset, ResetKind},\n    mem::ConstArray,\n    ppu::{self, Ppu},\n};\nuse serde::{Deserialize, Serialize};\nuse std::ops::{Deref, DerefMut};\n\n/// PPU frame.\n#[derive(Clone, Serialize, Deserialize)]\n#[serde(transparent)]\n#[repr(transparent)]\n#[must_use]\npub struct Buffer(Box<ConstArray<u16, { ppu::size::FRAME }>>);\n\nimpl std::fmt::Debug for Buffer {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Buffer({} elements)\", self.0.len())\n    }\n}\n\nimpl Default for Buffer {\n    fn default() -> Self {\n        Self(Box::new(ConstArray::new()))\n    }\n}\n\nimpl Deref for Buffer {\n    type Target = [u16; ppu::size::FRAME];\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl DerefMut for Buffer {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.0\n    }\n}\n\n/// PPU frame.\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\n#[repr(C)]\npub struct Frame {\n    #[serde(skip)]\n    pub buffer: Buffer,\n    pub count: u32,\n}\n\nimpl Default for Frame {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Frame {\n    pub fn new() -> Self {\n        Self {\n            count: 0,\n            buffer: Buffer::default(),\n        }\n    }\n\n    #[inline(always)]\n    pub const fn increment(&mut self) {\n        self.count = self.count.wrapping_add(1);\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub fn pixel(&self, x: u16, y: u16) -> u16 {\n        self.buffer[usize::from(x) + (usize::from(y) << 8)]\n    }\n\n    #[inline(always)]\n    pub fn set_pixel(&mut self, x: u16, y: u16, color: u16) {\n        self.buffer[usize::from(x) + (usize::from(y) << 8)] = color;\n    }\n\n    #[must_use]\n    pub fn pixel_brightness(&self, x: u16, y: u16) -> u32 {\n        let pixel = self.pixel(x, y);\n        let index = (pixel as usize) * 3;\n        let red = Ppu::NTSC_PALETTE[index];\n        let green = Ppu::NTSC_PALETTE[index + 1];\n        let blue = Ppu::NTSC_PALETTE[index + 2];\n        u32::from(red) + u32::from(green) + u32::from(blue)\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub const fn number(&self) -> u32 {\n        self.count\n    }\n\n    #[inline(always)]\n    pub const fn is_odd(&self) -> bool {\n        self.count & 0x01 == 0x01\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub fn buffer(&self) -> &[u16; ppu::size::FRAME] {\n        &self.buffer\n    }\n}\n\nimpl Reset for Frame {\n    fn reset(&mut self, _kind: ResetKind) {\n        self.count = 0;\n        self.buffer = Buffer::default();\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/ppu/mask.rs",
    "content": "//! PPUMASK register implementation.\n//!\n//! See: <https://wiki.nesdev.org/w/index.php/PPU_registers#PPUMASK>\n\nuse crate::common::{Clock, NesRegion, Reset, ResetKind};\nuse bitflags::bitflags;\nuse serde::{Deserialize, Serialize};\n\n/// PPUMASK register.\n///\n/// See: <https://wiki.nesdev.org/w/index.php/PPU_registers#PPUMASK>\n#[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]\n#[must_use]\npub struct Mask {\n    pub emphasis: u16,\n    pub grayscale: u8,\n    pub rendering_enabled: bool,\n    pub prev_rendering_enabled: bool,\n    pub pending_rendering_update: bool,\n    pub show_left_bg: bool,\n    pub show_left_spr: bool,\n    pub show_bg: bool,\n    pub show_spr: bool,\n    pub bits: Bits,\n    pub region: NesRegion,\n}\n\nbitflags! {\n    // $2001 PPUMASK\n    //\n    // https://wiki.nesdev.org/w/index.php/PPU_registers#PPUMASK\n    // BGRs bMmG\n    // |||| |||+- Grayscale (0: normal color, 1: produce a grayscale display)\n    // |||| ||+-- 1: Show background in leftmost 8 pixels of screen, 0: Hide\n    // |||| |+--- 1: Show sprites in leftmost 8 pixels of screen, 0: Hide\n    // |||| +---- 1: Show background\n    // |||+------ 1: Show sprites\n    // ||+------- Emphasize red\n    // |+-------- Emphasize green\n    // +--------- Emphasize blue\n    #[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]\n    #[must_use]\n    pub struct Bits: u8 {\n        const GRAYSCALE = 0x01;\n        const SHOW_LEFT_BG = 0x02;\n        const SHOW_LEFT_SPR = 0x04;\n        const SHOW_BG = 0x08;\n        const SHOW_SPR = 0x10;\n        const EMPHASIZE_RED = 0x20;\n        const EMPHASIZE_GREEN = 0x40;\n        const EMPHASIZE_BLUE = 0x80;\n    }\n}\n\nimpl Mask {\n    pub fn new(region: NesRegion) -> Self {\n        let mut mask = Self {\n            region,\n            ..Default::default()\n        };\n        mask.write(0);\n        mask\n    }\n\n    #[inline]\n    pub fn write(&mut self, val: u8) {\n        self.bits = Bits::from_bits_truncate(val);\n        self.grayscale = if self.bits.contains(Bits::GRAYSCALE) {\n            0x30\n        } else {\n            0x3F\n        };\n        self.show_left_bg = self.bits.contains(Bits::SHOW_LEFT_BG);\n        self.show_left_spr = self.bits.contains(Bits::SHOW_LEFT_SPR);\n        self.show_bg = self.bits.contains(Bits::SHOW_BG);\n        self.show_spr = self.bits.contains(Bits::SHOW_SPR);\n        self.pending_rendering_update = self.rendering_enabled != (self.show_bg || self.show_spr);\n        self.update_emphasis();\n    }\n\n    pub fn update_emphasis(&mut self) {\n        self.emphasis = u16::from(\n            match self.region {\n                NesRegion::Auto | NesRegion::Ntsc => self.bits.intersection(\n                    Bits::EMPHASIZE_RED | Bits::EMPHASIZE_GREEN | Bits::EMPHASIZE_BLUE,\n                ),\n                NesRegion::Pal | NesRegion::Dendy => {\n                    // Red/Green are swapped for PAL/Dendy\n                    let mut emphasis = self.bits.intersection(Bits::EMPHASIZE_BLUE);\n                    emphasis.set(\n                        Bits::EMPHASIZE_GREEN,\n                        self.bits.contains(Bits::EMPHASIZE_RED),\n                    );\n                    emphasis.set(\n                        Bits::EMPHASIZE_RED,\n                        self.bits.contains(Bits::EMPHASIZE_GREEN),\n                    );\n                    emphasis\n                }\n            }\n            .bits(),\n        ) << 1;\n    }\n\n    #[inline]\n    pub fn set_region(&mut self, region: NesRegion) {\n        self.region = region;\n        self.update_emphasis();\n    }\n}\n\nimpl Reset for Mask {\n    // https://www.nesdev.org/wiki/PPU_power_up_state\n    fn reset(&mut self, _kind: ResetKind) {\n        self.write(0);\n    }\n}\n\nimpl Clock for Mask {\n    fn clock(&mut self) {\n        // Rendering enabled flag is set with a 1 cycle delay (setting it at cycle N won't take\n        // effect until cycle N+2)\n        if self.pending_rendering_update {\n            self.pending_rendering_update = false;\n\n            self.prev_rendering_enabled = self.rendering_enabled;\n            self.rendering_enabled = self.show_bg || self.show_spr;\n            self.pending_rendering_update = self.prev_rendering_enabled != self.rendering_enabled;\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/ppu/scroll.rs",
    "content": "//! PPUSCROLL register implementation.\n//!\n//! See: <https://wiki.nesdev.org/w/index.php/PPU_registers#PPUSCROLL>\n\nuse crate::common::{Reset, ResetKind};\nuse serde::{Deserialize, Serialize};\n\n/// PPUSCROLL register.\n///\n/// See: <https://wiki.nesdev.org/w/index.php/PPU_registers#PPUSCROLL>\n#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Scroll {\n    pub v: u16, // Subject to ADDR_MIRROR\n    pub delay_v: u16,\n    pub t: u16, // Temporary v - Also the addr of top-left onscreen tile\n    pub fine_x: u16,\n    pub fine_y: u16,\n    pub write_latch: bool, // 1st or 2nd write toggle\n    pub delay_v_cycles: u8,\n}\n\nimpl Scroll {\n    // PPUSCROLL masks\n    //   1 00 00000 00000\n    // yyy NN YYYYY XXXXX\n    // ||| || ||||| +++++- 5 bit coarse X\n    // ||| || +++++------- 5 bit coarse Y\n    // ||| |+------------- Nametable X offset\n    // ||| +-------------- Nametable Y offset\n    // +++---------------- 3 bit fine Y\n    pub const COARSE_X_MASK: u16 = 0x001F;\n    pub const COARSE_Y_MASK: u16 = 0x03E0;\n    pub const NT_X_MASK: u16 = 0x0400;\n    pub const NT_Y_MASK: u16 = 0x0800;\n    pub const FINE_Y_MASK: u16 = 0x7000;\n    const X_MAX_COL: u16 = 31; // last column of tiles - 255 pixel width / 8 pixel wide tiles\n    const Y_MAX_COL: u16 = 29; // last row of tiles - (240 pixel height / 8 pixel tall tiles) - 1\n    const Y_OVER_COL: u16 = 31; // overscan row\n    const Y_INCREMENT: u16 = 0x1000; // Increment y in bit 12\n\n    const ATTR_START: u16 = 0x23C0;\n    const ADDR_MIRROR: u16 = 0x3FFF; // 15 bits: yyy NN YYYYY XXXXX\n\n    pub const fn new() -> Self {\n        Self {\n            v: 0x0000,\n            t: 0x0000,\n            fine_x: 0x00,\n            fine_y: 0x00,\n            write_latch: false,\n            delay_v_cycles: 0,\n            delay_v: 0x0000,\n        }\n    }\n\n    // https://wiki.nesdev.org/w/index.php/PPU_scrolling#Tile_and_attribute_fetching\n    // NN 1111 YYY XXXXX\n    // || |||| ||| +++-- high 3 bits of coarse X (x/4)\n    // || |||| +++------ high 3 bits of coarse Y (y/4)\n    // || ++++---------- attribute offset (960 bytes)\n    // ++--------------- nametable select\n    #[inline(always)]\n    #[must_use]\n    pub const fn attr_addr(&self) -> u16 {\n        let nametable_select = self.v & (Self::NT_X_MASK | Self::NT_Y_MASK);\n        let y_bits = (self.v >> 4) & 0x38;\n        let x_bits = (self.v >> 2) & 0x07;\n        Self::ATTR_START | nametable_select | y_bits | x_bits\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub const fn attr_shift(&self) -> u16 {\n        (self.v & 0x02) | ((self.v >> 4) & 0x04)\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub const fn addr(&self) -> u16 {\n        self.v & Self::ADDR_MIRROR // Only the lower 14 bits are valid\n    }\n\n    // Writes to PPUSCROLL affect v and t\n    // 1st write writes X\n    // 2nd write writes Y\n    #[inline]\n    pub fn write(&mut self, val: u8) {\n        let val = u16::from(val);\n        let lo_5_bit_mask: u16 = 0x1F;\n        let fine_mask: u16 = 0x07;\n        let fine_rshift = 3;\n        if self.write_latch {\n            // Write Y on second write\n            // lo 3 bits goes into fine y, remaining 5 bits go into t for coarse y\n            // val: HGFEDCBA\n            // t:   .CBA..HG FED.....\n            let coarse_y_lshift = 5;\n            let fine_y_lshift = 12;\n            self.t = self.t & !(Self::FINE_Y_MASK | Self::COARSE_Y_MASK) // Empty Y\n                | (((val >> fine_rshift) & lo_5_bit_mask) << coarse_y_lshift) // Set coarse Y\n                | ((val & fine_mask) << fine_y_lshift); // Set fine Y\n        } else {\n            // Write X on first write\n            // lo 3 bits goes into fine x, remaining 5 bits go into t for coarse x\n            // val: HGFEDCBA\n            // t:   ........ ...HGFED\n            // x:                 CBA\n            self.t = self.t & !Self::COARSE_X_MASK // Empty coarse X\n                | ((val >> fine_rshift) & lo_5_bit_mask); // Set coarse X\n            self.fine_x = val & fine_mask; // Set fine X\n        }\n        self.write_latch = !self.write_latch;\n    }\n\n    // Write to PPUADDR affect v and t\n    // 1st write writes hi 6 bits\n    // 2nd write writes lo 8 bits\n    // Total size is a 14 bit addr\n    #[inline]\n    pub fn write_addr(&mut self, val: u8) {\n        if self.write_latch {\n            // Write lo address on second write\n            let lo_bits_mask = 0x7F00;\n            // val: HGFEDCBA\n            // t:   ........ HGFEDCBA\n            // v:   t\n            self.t = (self.t & lo_bits_mask) | u16::from(val);\n            // PPUADDR update is apparently delayed by 2-3 PPU cycles (based on Visual NES findings)\n            // A 3-cycle delay causes issues with the scanline test.\n            self.delay_v_cycles = 2;\n            self.delay_v = self.t;\n        } else {\n            // Write hi address on first write\n            let hi_bits_mask = 0x00FF;\n            let six_bits_mask = 0x003F;\n            // val: ..FEDCBA\n            //      FEDCBA98 76543210\n            // t:   00FEDCBA ........\n            self.t = (self.t & hi_bits_mask) | ((u16::from(val) & six_bits_mask) << 8);\n        }\n        self.write_latch = !self.write_latch;\n    }\n\n    #[inline(always)]\n    pub const fn set_v(&mut self, val: u16) {\n        self.v = val;\n        self.fine_y = self.v >> 12;\n    }\n\n    // Delayed update for PPUADDR after 2 PPU cycles (based on Visual NES findings)\n    // Returns true when it was updated so the PPU can inform mappers monitoring $2006 reads and\n    // writes. e.g. MMC3 clocks using A12\n    #[inline(always)]\n    pub const fn delayed_update(&mut self) -> bool {\n        if self.delay_v_cycles > 0 {\n            self.delay_v_cycles -= 1;\n            if self.delay_v_cycles == 0 {\n                self.set_v(self.delay_v);\n                return true;\n            }\n        }\n        false\n    }\n\n    // Increment PPUADDR v by either 1 (going across) or 32 (going down)\n    // Address wraps around\n    #[inline(always)]\n    pub const fn increment(&mut self, val: u16) {\n        self.set_v(self.v.wrapping_add(val));\n    }\n\n    // Copy Coarse X from register t and add it to PPUADDR v\n    #[inline(always)]\n    pub const fn copy_x(&mut self) {\n        //    .....N.. ...XXXXX\n        // t: .....F.. ...EDCBA\n        // v: .....F.. ...EDCBA\n        let x_mask = Self::NT_X_MASK | Self::COARSE_X_MASK;\n        self.set_v((self.v & !x_mask) | (self.t & x_mask));\n    }\n\n    // Copy Fine y and Coarse Y from register t and add it to PPUADDR v\n    #[inline(always)]\n    pub const fn copy_y(&mut self) {\n        //    .yyyN.YY YYY.....\n        // t: .IHGF.ED CBA.....\n        // v: .IHGF.ED CBA.....\n        let y_mask = Self::FINE_Y_MASK | Self::NT_Y_MASK | Self::COARSE_Y_MASK;\n        self.set_v((self.v & !y_mask) | (self.t & y_mask));\n    }\n\n    // Increment Coarse X\n    // 0-4 bits are incremented, with overflow toggling bit 10 which switches the horizontal\n    // nametable\n    // https://wiki.nesdev.org/w/index.php/PPU_scrolling#Wrapping_around\n    #[inline]\n    pub const fn increment_x(&mut self) {\n        // let v = self.v;\n        // If we've reached the last column, toggle horizontal nametable\n        if (self.v & Self::COARSE_X_MASK) == Self::X_MAX_COL {\n            self.set_v((self.v & !Self::COARSE_X_MASK) ^ Self::NT_X_MASK); // toggles X nametable\n        } else {\n            self.set_v(self.v + 1);\n        }\n    }\n\n    // Increment Fine Y\n    // Bits 12-14 are incremented for Fine Y, with overflow incrementing coarse Y in bits 5-9 with\n    // overflow toggling bit 11 which switches the vertical nametable\n    // https://wiki.nesdev.org/w/index.php/PPU_scrolling#Wrapping_around\n    #[inline]\n    pub const fn increment_y(&mut self) {\n        if (self.v & Self::FINE_Y_MASK) == Self::FINE_Y_MASK {\n            self.set_v(self.v & !Self::FINE_Y_MASK); // set fine y = 0 and overflow into coarse y\n            let mut y = (self.v & Self::COARSE_Y_MASK) >> 5; // Get 5 bits of coarse y\n            if y == Self::Y_MAX_COL {\n                y = 0;\n                // switches vertical nametable\n                self.set_v(self.v ^ Self::NT_Y_MASK);\n            } else if y == Self::Y_OVER_COL {\n                // Out of bounds. Does not switch nametable\n                // Some games use this\n                y = 0;\n            } else {\n                y += 1; // increment coarse y\n            }\n            self.set_v((self.v & !Self::COARSE_Y_MASK) | (y << 5)); // put coarse y back into v\n        } else {\n            // If fine y < 7 (0b111), increment\n            self.set_v(self.v + Self::Y_INCREMENT);\n        }\n    }\n\n    #[inline(always)]\n    pub const fn reset_latch(&mut self) {\n        self.write_latch = false;\n    }\n\n    #[inline(always)]\n    pub fn write_nametable_select(&mut self, val: u8) {\n        let nt_mask = Self::NT_Y_MASK | Self::NT_X_MASK;\n        // val: ......BA\n        // t: ....BA.. ........\n        self.t = (self.t & !nt_mask) | ((u16::from(val) & 0x03) << 10); // take lo 2 bits and set NN\n    }\n}\n\nimpl Reset for Scroll {\n    // https://www.nesdev.org/wiki/PPU_power_up_state\n    fn reset(&mut self, kind: ResetKind) {\n        if kind == ResetKind::Hard {\n            // v is not cleared on a a soft reset\n            self.v = 0x0000;\n        }\n        self.fine_x = 0x00;\n        self.write_latch = false;\n        self.delay_v_cycles = 0;\n        self.delay_v = 0x0000;\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/ppu/sprite.rs",
    "content": "//! PPU OAM Sprite implementation.\n//!\n//! See: <https://www.nesdev.org/wiki/PPU_OAM>\n\nuse serde::{Deserialize, Serialize};\nuse std::fmt;\n\n/// PPU OAM Sprite entry.\n///\n/// See: <https://www.nesdev.org/wiki/PPU_OAM>\n#[derive(Copy, Clone, Serialize, Deserialize)]\n#[must_use]\npub struct Sprite {\n    pub x: u16,\n    pub y: u16,\n    pub tile_addr: u16,\n    pub tile_lo: u8,\n    pub tile_hi: u8,\n    pub palette: u8,\n    pub bg_priority: bool,\n    pub flip_horizontal: bool,\n}\n\nimpl Sprite {\n    pub const fn new() -> Self {\n        Self {\n            x: 0xFF,\n            y: 0xFF,\n            tile_addr: 0x0000,\n            tile_lo: 0x00,\n            tile_hi: 0x00,\n            palette: 0x07,\n            bg_priority: true,\n            flip_horizontal: true,\n        }\n    }\n}\n\nimpl Default for Sprite {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl fmt::Debug for Sprite {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"Sprite\")\n            .field(\"x\", &self.x)\n            .field(\"y\", &self.y)\n            .field(\"tile_addr\", &format_args!(\"${:04X}\", &self.tile_addr))\n            .field(\"tile_lo\", &format_args!(\"${:02X}\", &self.tile_lo))\n            .field(\"tile_hi\", &format_args!(\"${:02X}\", &self.tile_hi))\n            .field(\"palette\", &format_args!(\"${:02X}\", &self.palette))\n            .field(\"bg_priority\", &self.bg_priority)\n            .field(\"flip_horizontal\", &self.flip_horizontal)\n            .finish()\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/ppu/status.rs",
    "content": "//! PPUSTATUS register implementation.\n//!\n//! See: <https://wiki.nesdev.org/w/index.php/PPU_registers#PPUSTATUS>\n\nuse crate::common::{Reset, ResetKind};\nuse bitflags::bitflags;\nuse serde::{Deserialize, Serialize};\n\n/// PPUSTATUS register.\n///\n/// See: <https://wiki.nesdev.org/w/index.php/PPU_registers#PPUSTATUS>\n#[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]\n#[must_use]\npub struct Status {\n    pub in_vblank: bool,\n    pub spr_zero_hit: bool,\n    pub spr_overflow: bool,\n    pub bits: Bits,\n}\n\nbitflags! {\n    // $2002 PPUSTATUS\n    //\n    // https://wiki.nesdev.org/w/index.php/PPU_registers#PPUSTATUS\n    // VSO. ....\n    // |||+-++++- PPU open bus. Returns stale PPU bus contents.\n    // ||+------- Sprite overflow. The intent was for this flag to be set\n    // ||         whenever more than eight sprites appear on a scanline, but a\n    // ||         hardware bug causes the actual behavior to be more complicated\n    // ||         and generate false positives as well as false negatives; see\n    // ||         PPU sprite evaluation. This flag is set during sprite\n    // ||         evaluation and cleared at dot 1 (the second dot) of the\n    // ||         pre-render line.\n    // |+-------- Sprite 0 Hit.  Set when a nonzero pixel of sprite 0 overlaps\n    // |          a nonzero background pixel; cleared at dot 1 of the pre-render\n    // |          line.  Used for raster timing.\n    // +--------- Vertical blank has started (0: not in vblank; 1: in vblank)\n    //            Set at dot 1 of line 241 (the line *after* the post-render\n    //            line); cleared after reading $2002 and at dot 1 of the\n    //            pre-render line.\n    #[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]\n    #[must_use]\n    pub struct Bits: u8 {\n        const UNUSED1 = 0x01;\n        const UNUSED2 = 0x02;\n        const UNUSED3 = 0x04;\n        const UNUSED4 = 0x08;\n        const UNUSED5 = 0x10;\n        const SPR_OVERFLOW = 0x20;\n        const SPR_ZERO_HIT = 0x40;\n        const VBLANK_STARTED = 0x80;\n    }\n}\n\nimpl Status {\n    pub fn new() -> Self {\n        let mut status = Self::default();\n        status.write(0);\n        status\n    }\n\n    #[inline]\n    pub const fn write(&mut self, val: u8) {\n        self.bits = Bits::from_bits_truncate(val);\n        self.spr_overflow = self.bits.contains(Bits::SPR_ZERO_HIT);\n        self.spr_zero_hit = self.bits.contains(Bits::SPR_ZERO_HIT);\n        self.in_vblank = self.bits.contains(Bits::VBLANK_STARTED);\n    }\n\n    #[inline(always)]\n    #[must_use]\n    pub const fn read(&self) -> u8 {\n        self.bits.bits()\n    }\n\n    #[inline(always)]\n    pub fn set_spr_overflow(&mut self, val: bool) {\n        self.bits.set(Bits::SPR_OVERFLOW, val);\n        self.spr_overflow = val;\n    }\n\n    #[inline(always)]\n    pub fn set_spr_zero_hit(&mut self, val: bool) {\n        self.bits.set(Bits::SPR_ZERO_HIT, val);\n        self.spr_zero_hit = val;\n    }\n\n    #[inline(always)]\n    pub fn set_in_vblank(&mut self, val: bool) {\n        self.bits.set(Bits::VBLANK_STARTED, val);\n        self.in_vblank = val;\n    }\n\n    #[inline(always)]\n    pub fn reset_in_vblank(&mut self) {\n        self.bits.remove(Bits::VBLANK_STARTED);\n        self.in_vblank = false;\n    }\n}\n\nimpl Reset for Status {\n    // https://www.nesdev.org/wiki/PPU_power_up_state\n    fn reset(&mut self, kind: ResetKind) {\n        if kind == ResetKind::Hard {\n            self.set_in_vblank(false); // Technically random\n            self.set_spr_zero_hit(false);\n            self.set_spr_overflow(false); // Technically random\n        }\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/ppu.rs",
    "content": "//! NES PPU (Picture Processing Unit) implementation.\n\nuse crate::{\n    common::{Clock, NesRegion, Regional, Reset, ResetKind},\n    debug::PpuDebugger,\n    mapper::{Map, Mapper},\n    mem::{ConstArray, Read, Write},\n    ppu::frame::Frame,\n};\nuse ctrl::Ctrl;\nuse mask::Mask;\nuse scroll::Scroll;\nuse serde::{Deserialize, Serialize};\nuse sprite::Sprite;\nuse status::Status;\nuse std::{\n    cmp::Ordering,\n    ops::{Index, IndexMut},\n};\nuse tracing::{error, trace};\n\npub mod ctrl;\npub mod frame;\npub mod mask;\npub mod scroll;\npub mod sprite;\npub mod status;\n\n/// Nametable Mirroring Mode\n///\n/// <https://wiki.nesdev.org/w/index.php/Mirroring#Nametable_Mirroring>\n#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]\n#[must_use]\npub enum Mirroring {\n    Vertical = 0,\n    #[default]\n    Horizontal = 1,\n    SingleScreenA = 2,\n    SingleScreenB = 3,\n    FourScreen = 4,\n}\n\n/// Palette RAM which enforces mirroring.\n#[derive(Debug, Copy, Clone, Serialize, Deserialize)]\n#[must_use]\n#[repr(transparent)]\npub struct PaletteRam(ConstArray<u8, 32>);\n\nimpl PaletteRam {\n    /// Return palette address, mirrored.\n    #[inline(always)]\n    const fn mirror(addr: u16) -> usize {\n        const PALETTE_MIRROR: [u8; 32] = [\n            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 17, 18, 19, 4, 21, 22, 23, 8,\n            25, 26, 27, 12, 29, 30, 31,\n        ];\n        PALETTE_MIRROR[(addr & 0x1F) as usize] as usize\n    }\n}\n\nimpl Read for PaletteRam {\n    #[inline(always)]\n    fn peek(&self, addr: u16) -> u8 {\n        self.0[Self::mirror(addr)]\n    }\n}\n\nimpl Write for PaletteRam {\n    #[inline(always)]\n    fn write(&mut self, addr: u16, val: u8) {\n        self.0[Self::mirror(addr)] = val;\n    }\n}\n\n/// Console-Internal RAM (VRAM) which enforces mirroring.\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\n#[repr(transparent)]\npub struct CIRam(Box<ConstArray<u8, { size::VRAM }>>);\n\nimpl CIRam {\n    // Maps addresses to nametable pages based on mirroring mode\n    //\n    // Vram:            [ A ] [ B ]\n    //\n    // Horizontal:      [ A ] [ a ]\n    //                  [ B ] [ b ]\n    //\n    // Vertical:        [ A ] [ B ]\n    //                  [ a ] [ b ]\n    //\n    // Single Screen A: [ A ] [ a ]\n    //                  [ a ] [ a ]\n    //\n    // Single Screen B: [ b ] [ B ]\n    //                  [ b ] [ b ]\n    //\n    // Fourscreen should not use this method and instead should rely on mapper translation.\n    #[inline(always)]\n    pub const fn mirror(addr: u16, mirroring: Mirroring) -> usize {\n        let nametable = (addr >> mirroring as u16) & size::NAMETABLE;\n        (nametable | (!nametable & addr & 0x03FF)) as usize\n    }\n\n    #[inline(always)]\n    pub fn read(&mut self, addr: u16, mirroring: Mirroring) -> u8 {\n        self.0[Self::mirror(addr, mirroring)]\n    }\n\n    #[inline(always)]\n    pub fn peek(&self, addr: u16, mirroring: Mirroring) -> u8 {\n        self.0[Self::mirror(addr, mirroring)]\n    }\n\n    #[inline(always)]\n    pub fn write(&mut self, addr: u16, val: u8, mirroring: Mirroring) {\n        self.0[Self::mirror(addr, mirroring)] = val\n    }\n}\n\nimpl Index<usize> for CIRam {\n    type Output = u8;\n\n    #[inline]\n    fn index(&self, index: usize) -> &Self::Output {\n        self.0.index(index)\n    }\n}\n\nimpl IndexMut<usize> for CIRam {\n    #[inline]\n    fn index_mut(&mut self, index: usize) -> &mut Self::Output {\n        self.0.index_mut(index)\n    }\n}\n\npub trait PpuAddr {\n    /// Returns whether this value can be used to fetch a nametable attribute byte.\n    fn is_attr(&self) -> bool;\n    /// Returns whether this value is a palette address.\n    fn is_palette(&self) -> bool;\n}\n\nimpl PpuAddr for u16 {\n    #[inline(always)]\n    fn is_attr(&self) -> bool {\n        (*self & (size::NAMETABLE - 1)) >= addr::ATTR_OFFSET\n    }\n\n    #[inline(always)]\n    fn is_palette(&self) -> bool {\n        *self >= addr::PALETTE_START\n    }\n}\n\n/// NES PPU.\n///\n/// See: <https://wiki.nesdev.org/w/index.php/PPU>\n#[derive(Debug, Clone, Serialize, Deserialize)]\n#[must_use]\n#[repr(C)]\npub struct Ppu {\n    /// Master clock synced to Cpu master clock.\n    pub master_clock: u32,\n    /// (0, 340) cycles per scanline.\n    pub cycle: u16,\n    /// (0, 261) NTSC or (0, 311) PAL/Dendy scanlines per frame.\n    pub scanline: u16,\n    /// $2001 PPUMASK (write-only).\n    pub mask: Mask,\n    // === 20 ===\n    /// $2000 PPUCTRL (write-only).\n    pub ctrl: Ctrl,\n    // === 30 ===\n    /// $2005 PPUSCROLL and $2006 PPUADDR (write-only).\n    pub scroll: Scroll,\n    /// Scanline that Vertical Blank (VBlank) starts on.\n    pub vblank_scanline: u16,\n    /// Scanline that Prerender starts on.\n    pub prerender_scanline: u16,\n    /// Tile shift low byte.\n    pub tile_shift_lo: u16,\n    /// Tile shift high byte.\n    pub tile_shift_hi: u16,\n    /// Tile address.\n    pub tile_addr: u16,\n    /// Tile fetch buffer low byte.\n    pub tile_lo: u8,\n    /// Tile fetch buffer high byte.\n    pub tile_hi: u8,\n    /// Master clock divider.\n    pub clock_divider: u8,\n    /// Whatever was last read or written to to the Ppu.\n    pub open_bus: u8,\n    /// Internal signal that clears status registers and prevents writes and cleared at the end of\n    /// VBlank.\n    /// See: <https://www.nesdev.org/wiki/PPU_power_up_state>\n    pub reset_signal: bool,\n\n    /// Current tile palette.\n    pub curr_palette: u8,\n    /// Previous tile palette.\n    pub prev_palette: u8,\n    /// Next tile palette.\n    pub next_palette: u8,\n    /// Whether PPU is skipping rendering (used for\n    /// [`HeadlessMode`](crate::control_deck::HeadlessMode)).\n    pub skip_rendering: bool,\n\n    /// Scanline is visible.\n    pub is_visible_scanline: bool,\n    /// Scanline is a pre-render scanline.\n    pub is_prerender_scanline: bool,\n    /// Scanline is a render scanline.\n    pub is_render_scanline: bool,\n\n    // === 64 : end of cache line ===\n    /// $2002 PPUSTATUS (read-only).\n    pub status: Status,\n    /// Scanline is a PAL sprite evaluation scanline.\n    pub is_pal_spr_eval_scanline: bool,\n\n    // Sprite/OAM evaluation.\n    /// Sprite is in scanline range.\n    pub spr_in_range: bool,\n    /// Sprite 0 is in scanline range.\n    pub spr_zero_in_range: bool,\n    /// Secondary OAM address.\n    pub secondary_oamaddr: u8,\n    /// OAM evaluation is complete for scanline.\n    pub oam_eval_done: bool,\n    /// OAM address low byte.\n    pub oamaddr_lo: u8,\n    /// OAM address high byte.\n    pub oamaddr_hi: u8,\n    /// OAM data fetch buffer.\n    pub oam_fetch: u8,\n    /// $2003 OAM addr (write-only).\n    pub oamaddr: u8,\n    /// Sprite 0 is visible.\n    pub spr_zero_visible: bool,\n    /// Number of sprites on the current scanline.\n    pub spr_count: u8,\n    /// Sprite overflow count (> 8 on a scanline).\n    pub overflow_count: u8,\n\n    /// Current PPU frame buffer.\n    pub frame: Frame,\n    /// Console-Internal RAM (CIRAM).\n    pub ciram: CIRam,\n\n    // === 128 : end of cache line ===\n    // Palette RAM\n    pub palette: PaletteRam,\n    /// Secondary OAM data on a given scanline.\n    pub secondary_oamdata: ConstArray<u8, 32>,\n\n    // === 192 : end of cache line ===\n    /// Each scanline can hold 8 sprites at a time before the `spr_overflow` flag is set.\n    pub sprites: Box<[Sprite; 8]>,\n    /// Whether a sprite is present at the given x-coordinate. Used for `spr_zero_hit` detection.\n    // This is a per-frame optimization, shouldn't need to be saved\n    #[serde(skip)]\n    pub spr_present: ConstArray<bool, 256>,\n    // === 384 : end of cache line\n    /// $2004 Object Attribute Memory (OAM) data (read/write).\n    pub oamdata: ConstArray<u8, 256>,\n\n    // === 640 : end of cache line\n    /// Mapper.\n    pub mapper: Mapper,\n    /// NMI pending.\n    pub nmi_pending: bool,\n\n    /// $2007 PPUDATA buffer.\n    pub vram_buffer: u8,\n    /// Prevents VBL from being triggered this frame.\n    pub prevent_vbl: bool,\n    /// Current NesRegion.\n    pub region: NesRegion,\n    /// Whether to emulate PPU warmup on power up.\n    pub emulate_warmup: bool,\n\n    /// Attached Ppu Debugger.\n    // Don't save debug state\n    #[serde(skip)]\n    pub debugger: PpuDebugger,\n}\n\nimpl Default for Ppu {\n    fn default() -> Self {\n        Self::new(NesRegion::default())\n    }\n}\n\npub mod addr {\n    //! Address constants.\n\n    pub const NAMETABLE_START: u16 = 0x2000;\n    pub const ATTR_OFFSET: u16 = 0x03C0;\n\n    pub const PALETTE_START: u16 = 0x3F00;\n    pub const PALETTE_END: u16 = 0x3F20;\n}\n\npub mod size {\n    //! Memory size constants.\n\n    pub const WIDTH: u16 = 256;\n    pub const HEIGHT: u16 = 240;\n    pub const FRAME: usize = (WIDTH * HEIGHT) as usize;\n\n    pub const NAMETABLE: u16 = 0x0400;\n    pub const OAM: usize = 256; // 64 4-byte sprites per frame\n    pub const SECONDARY_OAM: usize = 32; // 8 4-byte sprites per scanline\n\n    pub const VRAM: usize = 0x0800; // Two 1k Nametables\n    pub const PALETTE: usize = 32; // 32 possible colors at a time\n}\n\npub mod cycle {\n    //! Cycle constants.\n    //! <https://www.nesdev.org/wiki/PPU_rendering>\n\n    use std::ops::RangeInclusive;\n\n    pub const START: u16 = 0;\n    pub const ODD_SKIP: u16 = 339; // Odd frames skip the last cycle\n    pub const END: u16 = 340;\n\n    pub const VISIBLE_START: u16 = 1; // Tile data fetching starts\n    pub const VISIBLE_END: u16 = 256; // 2 cycles each for 4 fetches = 32 tiles\n\n    pub const VBLANK: u16 = VISIBLE_START; // When VBlank flag gets set/cleared\n\n    pub const OAM_CLEAR_START: u16 = 1;\n    pub const OAM_CLEAR_END: u16 = 64;\n\n    pub const SPR_EVAL_START: u16 = 65;\n    pub const SPR_EVAL_START1: u16 = 66; // Used to split up match arms\n    pub const SPR_EVAL_END0: u16 = 255; // Used to split up match arms\n    pub const SPR_EVAL_END: u16 = 256;\n    pub const SPR_FETCH_START: u16 = 257; // Sprites for next scanline fetch starts\n    pub const SPR_FETCH_END: u16 = 320; // 2 cycles each for 4 fetches = 8 sprites\n    pub const SPR_FETCH_RANGE: RangeInclusive<u16> = SPR_FETCH_START..=SPR_FETCH_END;\n\n    pub const BG_PREFETCH_START: u16 = 321; // Tile data for next scanline fetched\n    pub const BG_PREFETCH_END: u16 = 336; // 2 cycles each for 4 fetches = 2 tiles\n    pub const BG_PREFETCH_RANGE: RangeInclusive<u16> = BG_PREFETCH_START..=BG_PREFETCH_END;\n\n    pub const BG_DUMMY_START: u16 = 337; // Dummy fetches - use is unknown\n    pub const BG_DUMMY_END: u16 = END;\n\n    pub const INC_Y: u16 = 256; // Increase Y scroll when it reaches end of the screen\n    pub const COPY_Y_START: u16 = 280; // Copy Y scroll start\n    pub const COPY_Y_END: u16 = 304; // Copy Y scroll stop\n    pub const COPY_Y_RANGE: RangeInclusive<u16> = COPY_Y_START..=COPY_Y_END;\n\n    // Clock dividers\n    pub const DIVIDER_NTSC: u8 = 4;\n    pub const DIVIDER_PAL: u8 = 5;\n    pub const DIVIDER_DENDY: u8 = DIVIDER_PAL;\n}\n\npub mod scanline {\n    //! Scanline constants.\n    //! <https://www.nesdev.org/wiki/PPU_rendering>\n\n    pub const START: u16 = 0;\n\n    pub const VISIBLE_START: u16 = START;\n    pub const VISIBLE_END: u16 = 239;\n\n    pub const POSTRENDER: u16 = 240;\n    pub const PRERENDER_NTSC: u16 = 261;\n    pub const PRERENDER_PAL: u16 = 311;\n    pub const PRERENDER_DENDY: u16 = PRERENDER_PAL;\n\n    pub const VBLANK_NTSC: u16 = 241;\n    pub const VBLANK_PAL: u16 = VBLANK_NTSC;\n    pub const VBLANK_DENDY: u16 = 291;\n}\n\nimpl Ppu {\n    pub const NTSC_PALETTE: &'static [u8] = include_bytes!(\"../ntscpalette.pal\");\n\n    /// NES PPU System Palette\n    /// 64 total possible colors, though only 32 can be loaded at a time\n    #[rustfmt::skip]\n    pub const SYSTEM_PALETTE: [(u8,u8,u8); 64] = [\n        // 0x00\n        (0x54, 0x54, 0x54), (0x00, 0x1E, 0x74), (0x08, 0x10, 0x90), (0x30, 0x00, 0x88), // $00-$03\n        (0x44, 0x00, 0x64), (0x5C, 0x00, 0x30), (0x54, 0x04, 0x00), (0x3C, 0x18, 0x00), // $04-$07\n        (0x20, 0x2A, 0x00), (0x08, 0x3A, 0x00), (0x00, 0x40, 0x00), (0x00, 0x3C, 0x00), // $08-$0B\n        (0x00, 0x32, 0x3C), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00), // $0C-$0F\n        // 0x10\n        (0x98, 0x96, 0x98), (0x08, 0x4C, 0xC4), (0x30, 0x32, 0xEC), (0x5C, 0x1E, 0xE4), // $10-$13\n        (0x88, 0x14, 0xB0), (0xA0, 0x14, 0x64), (0x98, 0x22, 0x20), (0x78, 0x3C, 0x00), // $14-$17\n        (0x54, 0x5A, 0x00), (0x28, 0x72, 0x00), (0x08, 0x7C, 0x00), (0x00, 0x76, 0x28), // $18-$1B\n        (0x00, 0x66, 0x78), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00), // $1C-$1F\n        // 0x20\n        (0xEC, 0xEE, 0xEC), (0x4C, 0x9A, 0xEC), (0x78, 0x7C, 0xEC), (0xB0, 0x62, 0xEC), // $20-$23\n        (0xE4, 0x54, 0xEC), (0xEC, 0x58, 0xB4), (0xEC, 0x6A, 0x64), (0xD4, 0x88, 0x20), // $24-$27\n        (0xA0, 0xAA, 0x00), (0x74, 0xC4, 0x00), (0x4C, 0xD0, 0x20), (0x38, 0xCC, 0x6C), // $28-$2B\n        (0x38, 0xB4, 0xCC), (0x3C, 0x3C, 0x3C), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00), // $2C-$2F\n        // 0x30\n        (0xEC, 0xEE, 0xEC), (0xA8, 0xCC, 0xEC), (0xBC, 0xBC, 0xEC), (0xD4, 0xB2, 0xEC), // $30-$33\n        (0xEC, 0xAE, 0xEC), (0xEC, 0xAE, 0xD4), (0xEC, 0xB4, 0xB0), (0xE4, 0xC4, 0x90), // $34-$37\n        (0xCC, 0xD2, 0x78), (0xB4, 0xDE, 0x78), (0xA8, 0xE2, 0x90), (0x98, 0xE2, 0xB4), // $38-$3B\n        (0xA0, 0xD6, 0xE4), (0xA0, 0xA2, 0xA0), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00), // $3C-$3F\n    ];\n\n    /// Create a new PPU instance.\n    pub fn new(region: NesRegion) -> Self {\n        let mut ppu = Self {\n            master_clock: 0,\n            clock_divider: 0,\n            cycle: 0,\n            scanline: 0,\n            vblank_scanline: 0,\n            prerender_scanline: 0,\n            is_visible_scanline: true,\n            is_prerender_scanline: false,\n            is_render_scanline: true,\n            is_pal_spr_eval_scanline: false,\n            open_bus: 0x00,\n\n            mask: Mask::new(region),\n            scroll: Scroll::new(),\n            ctrl: Ctrl::new(),\n\n            // NOTE: PPU RAM is a bit more predictable at power on - games like Huge Insect don't\n            // properly initialize both nametables, which can result in garbage sprites when\n            // randomizing CIRAM.\n            palette: PaletteRam(ConstArray::new()),\n            mapper: Mapper::none(),\n            ciram: CIRam(Box::new(ConstArray::new())),\n\n            prev_palette: 0x00,\n            curr_palette: 0x00,\n            next_palette: 0x00,\n            tile_shift_lo: 0x0000,\n            tile_shift_hi: 0x0000,\n            tile_lo: 0x00,\n            tile_hi: 0x00,\n            tile_addr: 0x0000,\n\n            status: Status::new(),\n            nmi_pending: false,\n\n            oam_fetch: 0x00,\n            oamaddr: 0x0000,\n            oamaddr_lo: 0x00,\n            oamaddr_hi: 0x00,\n            oam_eval_done: false,\n            secondary_oamaddr: 0x0000,\n            overflow_count: 0,\n            spr_in_range: false,\n            spr_zero_in_range: false,\n            spr_zero_visible: false,\n            spr_count: 0,\n            vram_buffer: 0x00,\n\n            oamdata: ConstArray::new(),\n            secondary_oamdata: ConstArray::new(),\n            sprites: [Sprite::new(); 8].into(),\n            spr_present: ConstArray::new(),\n\n            prevent_vbl: false,\n            frame: Frame::new(),\n\n            region,\n            skip_rendering: false,\n            reset_signal: false,\n            emulate_warmup: false,\n\n            debugger: Default::default(),\n        };\n\n        ppu.set_region(ppu.region);\n\n        ppu\n    }\n\n    /// Read a byte from CHR-ROM/RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_read(&mut self, addr: u16) -> u8 {\n        self.mapper.chr_read(addr, &self.ciram)\n    }\n\n    /// Peek a byte from CHR-ROM/RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_peek(&self, addr: u16) -> u8 {\n        self.mapper.chr_peek(addr, &self.ciram)\n    }\n\n    /// Write a byte to CHR-RAM/CIRAM at a given address.\n    #[inline(always)]\n    fn chr_write(&mut self, addr: u16, val: u8) {\n        self.mapper.chr_write(addr, val, &mut self.ciram)\n    }\n\n    /// Read from `addr` on Ppu bus.\n    #[inline]\n    fn bus_read(&mut self, addr: u16) -> u8 {\n        self.open_bus = match addr {\n            0x0000..=0x3EFF => self.chr_read(addr),\n            0x3F00..=0x3FFF => self.palette.read(addr),\n            _ => {\n                error!(\"unexpected PPU memory access at ${:04X}\", addr);\n                0x00\n            }\n        };\n        self.open_bus\n    }\n\n    #[inline]\n    fn bus_peek(&self, addr: u16) -> u8 {\n        match addr {\n            0x0000..=0x3EFF => self.chr_peek(addr),\n            0x3F00..=0x3FFF => self.palette.peek(addr),\n            _ => {\n                error!(\"unexpected PPU memory access at ${:04X}\", addr);\n                0x00\n            }\n        }\n    }\n\n    /// Write `val` to `addr` on Ppu bus.\n    #[inline]\n    fn bus_write(&mut self, addr: u16, val: u8) {\n        self.open_bus = val;\n        match addr {\n            0x0000..=0x3EFF => self.chr_write(addr, val),\n            0x3F00..=0x3FFF => self.palette.write(addr, val),\n            _ => error!(\"unexpected PPU memory access at ${:04X}\", addr),\n        }\n    }\n\n    /// Return the current frame buffer.\n    #[inline]\n    #[must_use]\n    pub fn frame_buffer(&self) -> &[u16] {\n        self.frame.buffer()\n    }\n\n    /// Return the current frame number.\n    #[inline(always)]\n    #[must_use]\n    pub const fn frame_number(&self) -> u32 {\n        self.frame.number()\n    }\n\n    /// Get the pixel pixel brightness at the given coordinates.\n    #[inline]\n    #[must_use]\n    pub fn pixel_brightness(&self, x: u16, y: u16) -> u32 {\n        self.frame.pixel_brightness(x, y)\n    }\n\n    /// Load a Mapper into the PPU.\n    #[inline]\n    pub fn load_mapper(&mut self, mapper: Mapper) {\n        self.mapper = mapper;\n    }\n\n    /// Return the current Nametable mirroring mode.\n    #[inline]\n    pub fn mirroring(&self) -> Mirroring {\n        self.mapper.mirroring()\n    }\n\n    /// Snapshot the PPU state, excluding internal transient state, the current frame buffer.\n    pub fn snapshot(&self) -> Self {\n        Self {\n            master_clock: self.master_clock,\n            clock_divider: self.clock_divider,\n            cycle: self.cycle,\n            scanline: self.scanline,\n            vblank_scanline: self.vblank_scanline,\n            prerender_scanline: self.prerender_scanline,\n            is_visible_scanline: self.is_visible_scanline,\n            is_prerender_scanline: self.is_prerender_scanline,\n            is_render_scanline: self.is_render_scanline,\n            is_pal_spr_eval_scanline: self.is_pal_spr_eval_scanline,\n            open_bus: self.open_bus,\n\n            mask: self.mask,\n            scroll: self.scroll,\n            ctrl: self.ctrl,\n\n            palette: self.palette,\n            ciram: self.ciram.clone(),\n            mapper: self.mapper.clone(),\n\n            curr_palette: self.curr_palette,\n\n            status: self.status,\n\n            secondary_oamaddr: self.secondary_oamaddr,\n\n            oamdata: self.oamdata,\n            secondary_oamdata: self.secondary_oamdata,\n\n            sprites: self.sprites.clone(),\n\n            ..Default::default()\n        }\n    }\n\n    /// Load the passed given buffer with RGBA pixels from the current nametables.\n    pub fn load_nametables(&self, nametables: &mut [u8]) {\n        for i in 0..4 {\n            let base_addr = addr::NAMETABLE_START + i * size::NAMETABLE;\n            let x_offset = (i % 2) * size::WIDTH;\n            let y_offset = (i / 2) * size::HEIGHT;\n\n            for addr in base_addr..(base_addr + size::NAMETABLE - 64) {\n                let x_scroll = addr & Scroll::COARSE_X_MASK;\n                let y_scroll = (addr & Scroll::COARSE_Y_MASK) >> 5;\n\n                let base_nametable_addr =\n                    addr::NAMETABLE_START | (addr & (Scroll::NT_X_MASK | Scroll::NT_Y_MASK));\n                let base_attr_addr = base_nametable_addr + addr::ATTR_OFFSET;\n\n                let tile_index = u16::from(self.chr_peek(addr));\n                let tile_addr = self.ctrl.bg_select | (tile_index << 4);\n\n                let supertile = ((y_scroll & 0xFC) << 1) + (x_scroll >> 2);\n                let attr = u16::from(self.chr_peek(base_attr_addr + supertile));\n                let attr_shift = (x_scroll & 0x02) | ((y_scroll & 0x02) << 1);\n                let palette_addr = ((attr >> attr_shift) & 0x03) << 2;\n\n                let tile_num = x_scroll + (y_scroll << 5);\n                let tile_x = (tile_num % 32) << 3;\n                let tile_y = (tile_num / 32) << 3;\n\n                for y in 0..8 {\n                    let tile_addr = tile_addr + y;\n                    let tile_lo = self.chr_peek(tile_addr);\n                    let tile_hi = self.chr_peek(tile_addr + 8);\n                    for x in 0..8 {\n                        let tile_palette = (((tile_hi >> x) & 1) << 1) | (tile_lo >> x) & 1;\n                        let palette = palette_addr | u16::from(tile_palette);\n                        let color = self\n                            .palette\n                            .peek(addr::PALETTE_START | ((palette & 0x03 > 0) as u16 * palette));\n                        let x = tile_x + (7 - x);\n                        let y = tile_y + y;\n                        Self::set_pixel(\n                            u16::from(color & self.mask.grayscale) | self.mask.emphasis,\n                            x + x_offset,\n                            y + y_offset,\n                            2 * size::WIDTH,\n                            nametables,\n                        );\n                    }\n                }\n            }\n        }\n    }\n\n    /// Load the given buffer with RGBA pixels from the current pattern tables.\n    pub fn load_pattern_tables(&self, pattern_tables: &mut [u8]) {\n        for i in 0..2 {\n            let start = i * 0x1000;\n            let end = start + 0x1000;\n            let x_offset = (i % 2) * size::WIDTH / 2;\n            for tile_addr in (start..end).step_by(16) {\n                let tile_x = ((tile_addr % 0x1000) % 256) / 2;\n                let tile_y = ((tile_addr % 0x1000) / 256) * 8;\n                for y in 0..8 {\n                    let tile_lo = u16::from(self.chr_peek(tile_addr + y));\n                    let tile_hi = u16::from(self.chr_peek(tile_addr + y + 8));\n                    for x in 0..8 {\n                        let palette = (((tile_hi >> x) & 0x01) << 1) | ((tile_lo >> x) & 0x01);\n                        let color = u16::from(self.palette.peek(addr::PALETTE_START | palette));\n                        let x = tile_x + (7 - x);\n                        let y = tile_y + y;\n                        Self::set_pixel(color, x + x_offset, y, size::WIDTH, pattern_tables);\n                    }\n                }\n            }\n        }\n    }\n\n    /// Load the given buffer with RGBA pixels from the current pattern tables.\n    pub fn load_oam(\n        &self,\n        oam_table: &mut [u8],\n        sprite_nametable: &mut [u8],\n        sprites: &mut [Sprite],\n    ) {\n        // TODO: de-duplicate this with load_sprites\n        for (i, oamdata) in self.oamdata.chunks(4).enumerate() {\n            if let [y, tile_index, attr, x] = oamdata {\n                let sprite_x = u16::from(*x);\n                let sprite_y = u16::from(*y);\n                let tile_index = u16::from(*tile_index);\n                let palette = ((attr & 0x03) << 2) | 0x10;\n                let bg_priority = (attr & 0x20) == 0x20;\n                let flip_horizontal = (attr & 0x40) == 0x40;\n                let flip_vertical = (attr & 0x80) == 0x80;\n\n                let height = self.ctrl.spr_height;\n                let tile_addr = if height == 16 {\n                    // Use bit 0 of tile index to determine pattern table\n                    ((tile_index & 0x01) * 0x1000) | ((tile_index & 0xFE) << 4)\n                } else {\n                    self.ctrl.spr_select | (tile_index << 4)\n                };\n\n                sprites[i] = Sprite {\n                    x: sprite_x,\n                    y: sprite_y,\n                    tile_addr,\n                    palette,\n                    bg_priority,\n                    flip_horizontal,\n                    ..Sprite::default()\n                };\n\n                let tile_x = (i % 8) as u16 * 8;\n                let tile_y = (i / 8) as u16 * 8;\n                for y in 0..8 {\n                    let mut line_offset = if flip_vertical { (height) - 1 - y } else { y };\n                    if height == 16 && line_offset >= 8 {\n                        line_offset += 8;\n                    }\n                    let tile_lo = self.chr_peek(tile_addr + line_offset);\n                    let tile_hi = self.chr_peek(tile_addr + line_offset + 8);\n                    for x in 0..8 {\n                        let spr_color = if flip_horizontal {\n                            (((tile_hi >> x) & 0x01) << 1) | ((tile_lo >> x) & 0x01)\n                        } else {\n                            (((tile_hi << x) & 0x80) >> 6) | (((tile_lo << x) & 0x80) >> 7)\n                        };\n                        let palette = palette + spr_color;\n                        let color = self.palette.peek(\n                            addr::PALETTE_START\n                                | ((palette & 0x03 > 0) as u16 * u16::from(palette)),\n                        );\n\n                        Self::set_pixel(u16::from(color), tile_x + x, tile_y + y, 64, oam_table);\n\n                        let x = sprite_x + x;\n                        let y = sprite_y + y;\n                        let show_left_bg = self.mask.show_left_bg;\n                        let show_left_spr = self.mask.show_left_spr;\n                        let show_bg = self.mask.show_bg;\n                        let show_spr = self.mask.show_spr;\n                        let fine_x = self.scroll.fine_x;\n\n                        let left_clip_bg = x < 8 && !show_left_bg;\n                        let bg_color = if show_bg && !left_clip_bg {\n                            ((((self.tile_shift_hi << fine_x) & 0x8000) >> 14)\n                                | (((self.tile_shift_lo << fine_x) & 0x8000) >> 15))\n                                as u8\n                        } else {\n                            0\n                        };\n\n                        let left_clip_spr = x < 8 && !show_left_spr;\n                        if show_spr && !left_clip_spr && x < size::WIDTH && y < size::HEIGHT {\n                            let color = if bg_color == 0 || !bg_priority {\n                                color\n                            } else if (fine_x + (x & 0x07)) < 8 {\n                                self.prev_palette + bg_color\n                            } else {\n                                self.curr_palette + bg_color\n                            };\n                            Self::set_pixel(u16::from(color), x, y, size::WIDTH, sprite_nametable);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    /// Load the given buffer with RGBA pixels from the current palettes.\n    pub fn load_palettes(&self, palettes: &mut [u8], colors: &mut [u8]) {\n        for addr in addr::PALETTE_START..addr::PALETTE_END {\n            let offset = addr - addr::PALETTE_START;\n            let x = offset % 16;\n            let y = offset / 16;\n            let color = self.palette.peek(addr);\n            colors[usize::from(offset)] = color;\n            Self::set_pixel(u16::from(color), x, y, 16, palettes);\n        }\n    }\n\n    fn set_pixel(color: u16, x: u16, y: u16, width: u16, pixels: &mut [u8]) {\n        let index = (color as usize) * 3;\n        let idx = 4 * (usize::from(x) + usize::from(y) * usize::from(width));\n        assert!(Ppu::NTSC_PALETTE.len() > index + 2);\n        assert!(pixels.len() > 2);\n        assert!(idx + 2 < pixels.len());\n        pixels[idx] = Ppu::NTSC_PALETTE[index];\n        pixels[idx + 1] = Ppu::NTSC_PALETTE[index + 1];\n        pixels[idx + 2] = Ppu::NTSC_PALETTE[index + 2];\n        pixels[idx + 3] = 0xFF;\n    }\n\n    #[inline(always)]\n    const fn increment_vram_addr(&mut self) {\n        // During rendering, v increments coarse X and coarse Y simultaneously\n        if self.scanline > scanline::VISIBLE_END || !self.mask.rendering_enabled {\n            self.scroll\n                .increment(self.ctrl.vram_increment as u16 * 31 + 1);\n        } else {\n            self.scroll.increment_x();\n            self.scroll.increment_y();\n        }\n    }\n\n    fn start_vblank(&mut self) {\n        trace!(\"Start VBL - PPU:{:3},{:3}\", self.cycle, self.scanline);\n        if !self.prevent_vbl {\n            self.status.set_in_vblank(true);\n            if self.ctrl.nmi_enabled {\n                self.nmi_pending = true;\n                trace!(\"VBL NMI - PPU:{:3},{:3}\", self.cycle, self.scanline,);\n            }\n        }\n        self.prevent_vbl = false;\n    }\n\n    fn stop_vblank(&mut self) {\n        trace!(\n            \"Stop VBL, Sprite0 Hit, Overflow - PPU:{:3},{:3}\",\n            self.cycle, self.scanline\n        );\n        self.status.set_spr_zero_hit(false);\n        self.status.set_spr_overflow(false);\n        self.status.reset_in_vblank();\n        self.nmi_pending = false;\n        self.reset_signal = false;\n        self.open_bus = 0; // Clear open bus every frame\n    }\n\n    /// Fetch BG nametable byte.\n    ///\n    /// See: <https://wiki.nesdev.org/w/index.php/PPU_scrolling#Tile_and_attribute_fetching>\n    #[inline]\n    fn fetch_bg_nt_byte(&mut self) {\n        self.prev_palette = self.curr_palette;\n        self.curr_palette = self.next_palette;\n\n        self.tile_shift_lo |= u16::from(self.tile_lo);\n        self.tile_shift_hi |= u16::from(self.tile_hi);\n\n        let nametable_addr_mask = 0x0FFF; // Only need lower 12 bits\n        let addr = addr::NAMETABLE_START | (self.scroll.addr() & nametable_addr_mask);\n        let tile_index = u16::from(self.chr_read(addr));\n        self.tile_addr = self.ctrl.bg_select | (tile_index << 4) | self.scroll.fine_y;\n    }\n\n    /// Fetch BG attribute byte.\n    ///\n    /// See: <https://wiki.nesdev.org/w/index.php/PPU_scrolling#Tile_and_attribute_fetching>\n    #[inline(always)]\n    fn fetch_bg_attr_byte(&mut self) {\n        let addr = self.scroll.attr_addr();\n        let shift = self.scroll.attr_shift();\n        self.next_palette = ((self.chr_read(addr) >> shift) & 0x03) << 2;\n    }\n\n    /// Fetch 4 tiles and write out shift registers every 8th cycle.\n    /// Each tile fetch takes 2 cycles.\n    ///\n    /// See: <https://wiki.nesdev.org/w/index.php/PPU_scrolling#Tile_and_attribute_fetching>\n    #[inline]\n    fn bg_fetch_cycle(&mut self) {\n        let phase = self.cycle & 0x07;\n        if self.mask.prev_rendering_enabled && phase == 0 {\n            // Increment Coarse X every 8 cycles (e.g. 8 pixels) since sprites are 8x wide\n            self.scroll.increment_x();\n            // 256, Increment Fine Y when we reach the end of the screen\n            if self.cycle == cycle::INC_Y {\n                self.scroll.increment_y();\n            }\n            return;\n        }\n\n        match phase {\n            1 => self.fetch_bg_nt_byte(),\n            3 => self.fetch_bg_attr_byte(),\n            5 => self.tile_lo = self.chr_read(self.tile_addr),\n            7 => self.tile_hi = self.chr_read(self.tile_addr + 8),\n            _ => (),\n        }\n    }\n\n    fn oam_eval_cycle(&mut self) {\n        if self.cycle & 0x01 == 0x01 {\n            // Odd cycles are reads from OAM\n            self.oam_fetch = self.oamdata[self.oamaddr as usize];\n        } else {\n            // Local variables improve cache locality\n            let scanline = self.scanline;\n            let mut oam_eval_done = self.oam_eval_done;\n            let mut secondary_oamaddr = self.secondary_oamaddr;\n            let mut oam_fetch = self.oam_fetch;\n            let mut spr_in_range = self.spr_in_range;\n            let mut spr_zero_in_range = self.spr_zero_in_range;\n\n            let mut oamaddr_hi = self.oamaddr_hi;\n            let mut oamaddr_lo = self.oamaddr_lo;\n            let secondary_oamindex = secondary_oamaddr as usize & 0x1F;\n            debug_assert!(secondary_oamindex < self.secondary_oamdata.len());\n\n            // oamaddr rolled over, so we're done reading\n            if oam_eval_done {\n                oamaddr_hi = (oamaddr_hi + 1) & 0x3F;\n                if secondary_oamaddr >= 0x20 {\n                    oam_fetch = self.secondary_oamdata[secondary_oamindex];\n                }\n            } else {\n                // If previously not in range, interpret this byte as y\n                let y = u16::from(oam_fetch);\n                let height = self.ctrl.spr_height;\n                spr_in_range |= !spr_in_range && (y..y + height).contains(&scanline);\n\n                // Even cycles are writes to Secondary OAM\n                if secondary_oamaddr < 0x20 {\n                    self.secondary_oamdata[secondary_oamindex] = oam_fetch;\n\n                    if spr_in_range {\n                        oamaddr_lo += 1;\n                        secondary_oamaddr += 1;\n\n                        spr_zero_in_range |= oamaddr_hi == 0x00;\n                        if oamaddr_lo == 0x04 {\n                            spr_in_range = false;\n                            oamaddr_lo = 0x00;\n                            oamaddr_hi = (oamaddr_hi + 1) & 0x3F;\n                            oam_eval_done |= oamaddr_hi == 0x00;\n                        }\n                    } else {\n                        oamaddr_hi = (oamaddr_hi + 1) & 0x3F;\n                        oam_eval_done |= oamaddr_hi == 0x00;\n                    }\n                } else {\n                    oam_fetch = self.secondary_oamdata[secondary_oamindex];\n                    if spr_in_range {\n                        self.status.set_spr_overflow(true);\n                        oamaddr_lo += 1;\n                        if oamaddr_lo == 0x04 {\n                            oamaddr_lo = 0x00;\n                            oamaddr_hi = (oamaddr_hi + 1) & 0x3F;\n                        }\n\n                        match self.overflow_count.cmp(&0) {\n                            Ordering::Equal => self.overflow_count = 3,\n                            Ordering::Greater => {\n                                self.overflow_count -= 1;\n                                let no_overflow = self.overflow_count == 0;\n                                oam_eval_done |= no_overflow;\n                                if no_overflow {\n                                    oamaddr_lo = 0;\n                                }\n                            }\n                            Ordering::Less => (),\n                        }\n                    } else {\n                        oamaddr_hi = (oamaddr_hi + 1) & 0x3F;\n                        oamaddr_lo = (oamaddr_lo + 1) & 0x03;\n                        oam_eval_done |= oamaddr_hi == 0x00;\n                    }\n                }\n            }\n\n            self.oamaddr = (oamaddr_hi << 2) | (oamaddr_lo & 0x03);\n            self.oamaddr_hi = oamaddr_hi;\n            self.oamaddr_lo = oamaddr_lo;\n\n            self.oam_eval_done = oam_eval_done;\n            self.secondary_oamaddr = secondary_oamaddr;\n            self.oam_fetch = oam_fetch;\n            self.spr_in_range = spr_in_range;\n            self.spr_zero_in_range = spr_zero_in_range;\n        }\n    }\n\n    fn spr_eval_cycle(&mut self) {\n        // Local variables improve cache locality\n        match self.cycle {\n            // 1. Clear Secondary OAM\n            // 1..=64\n            cycle::OAM_CLEAR_START..=cycle::OAM_CLEAR_END => {\n                self.oam_fetch = 0xFF;\n                self.secondary_oamdata = ConstArray::filled(0xFF);\n            }\n            // 2. Read OAM to find first eight sprites on this scanline\n            // 3. With > 8 sprites, check (wrongly) for more sprites to set overflow flag\n            // 64..=256\n            cycle::SPR_EVAL_START => {\n                self.spr_in_range = false;\n                self.spr_zero_in_range = false;\n                self.secondary_oamaddr = 0x00;\n                self.oam_eval_done = false;\n                self.oamaddr_hi = (self.oamaddr >> 2) & 0x3F;\n                self.oamaddr_lo = self.oamaddr & 0x03;\n                self.oam_eval_cycle();\n            }\n            cycle::SPR_EVAL_END => {\n                self.spr_zero_visible = self.spr_zero_in_range;\n                self.spr_count = self.secondary_oamaddr >> 2;\n                self.oam_eval_cycle();\n            }\n            cycle::SPR_EVAL_START1..=cycle::SPR_EVAL_END0 => self.oam_eval_cycle(),\n            _ => (),\n        }\n    }\n\n    fn load_sprites(&mut self) {\n        // Local variables improve cache locality\n        let cycle = self.cycle;\n        let scanline = self.scanline;\n        let spr_count = usize::from(self.spr_count);\n\n        let idx = (cycle - cycle::SPR_FETCH_START) as usize / 8;\n        let oam_idx = idx << 2;\n\n        if let [y, tile_index, attr, x] = self.secondary_oamdata[oam_idx..=oam_idx + 3] {\n            let x = u16::from(x);\n            let y = u16::from(y);\n            let mut tile_index = u16::from(tile_index);\n            let flip_vertical = (attr & 0x80) == 0x80;\n\n            let height = self.ctrl.spr_height;\n            // Should be in the range 0..=7 or 0..=15 depending on sprite height\n            let mut line_offset = if (y..y + height).contains(&scanline) {\n                scanline - y\n            } else {\n                0\n            };\n            if flip_vertical {\n                line_offset = height - 1 - line_offset;\n            }\n\n            if idx >= spr_count {\n                line_offset = 0;\n                tile_index = 0xFF;\n            }\n\n            let tile_addr = if height == 16 {\n                // Use bit 0 of tile index to determine pattern table\n                let sprite_select = (tile_index & 0x01) * 0x1000;\n                if line_offset >= 8 {\n                    line_offset += 8;\n                }\n                sprite_select | ((tile_index & 0xFE) << 4) | line_offset\n            } else {\n                self.ctrl.spr_select | (tile_index << 4) | line_offset\n            };\n\n            if idx < spr_count {\n                self.sprites[idx] = Sprite {\n                    x,\n                    y,\n                    tile_addr,\n                    tile_lo: self.chr_read(tile_addr),\n                    tile_hi: self.chr_read(tile_addr + 8),\n                    palette: ((attr & 0x03) << 2) | 0x10,\n                    bg_priority: (attr & 0x20) == 0x20,\n                    flip_horizontal: (attr & 0x40) == 0x40,\n                };\n                let cycle = usize::from(x + 1);\n                self.spr_present[cycle..(cycle + 8).min(256)].fill(true);\n            } else {\n                // Fetches for remaining sprites/hidden fetch tile $FF\n                // Required for accurate MMC3 IRQ\n                let _ = self.chr_read(tile_addr);\n                let _ = self.chr_read(tile_addr + 8);\n            }\n        }\n    }\n\n    // https://wiki.nesdev.org/w/index.php/PPU_OAM\n    #[inline]\n    fn spr_fetch_cycle(&mut self) {\n        // OAMADDR set to $00 on prerender and visible scanlines\n        self.write_oamaddr(0x00);\n\n        match self.cycle & 0x07 {\n            // Garbage NT sprite fetch (257, 265, 273, etc.)\n            // Required for proper MC-ACC IRQs (MMC3 clone)\n            1 => self.fetch_bg_nt_byte(),   // Garbage NT fetch\n            3 => self.fetch_bg_attr_byte(), // Garbage attr fetch\n            // Cycle 260, 268, etc. This is an approximation (each tile is actually loaded in 8\n            // steps (e.g from 257 to 264))\n            4 => self.load_sprites(),\n            _ => (),\n        }\n    }\n\n    #[inline]\n    fn pixel_palette(&mut self) -> u8 {\n        let cycle = self.cycle;\n        let x = cycle - 1;\n        let show_left_bg = self.mask.show_left_bg;\n        let show_left_spr = self.mask.show_left_spr;\n        let show_bg = self.mask.show_bg;\n        let show_spr = self.mask.show_spr;\n        let fine_x = self.scroll.fine_x;\n        let bg_shift = 15 - fine_x;\n\n        let min_render_x = x >= 8;\n        let bg_mask = u8::from(show_bg & (show_left_bg | min_render_x));\n        let bg_color = bg_mask\n            * ((((self.tile_shift_hi >> bg_shift) & 0x01) << 1)\n                | ((self.tile_shift_lo >> bg_shift) & 0x01)) as u8;\n\n        let count = usize::from(self.spr_count);\n        if (count > 0)\n            & (show_spr & (show_left_spr | min_render_x))\n            & self.spr_present[usize::from(cycle)]\n        {\n            for (i, sprite) in self.sprites.iter().take(count).enumerate() {\n                let spr_shift = x.wrapping_sub(sprite.x);\n                if spr_shift <= 7 {\n                    let spr_shift = if sprite.flip_horizontal {\n                        spr_shift\n                    } else {\n                        7 - spr_shift\n                    };\n                    let spr_color = (((sprite.tile_hi >> spr_shift) & 0x01) << 1)\n                        | ((sprite.tile_lo >> spr_shift) & 0x01);\n\n                    if spr_color != 0 {\n                        if self.mask.rendering_enabled\n                            & !self.status.spr_zero_hit\n                            & self.spr_zero_visible\n                            & (cycle != 256)\n                            & (i == 0)\n                            & (bg_color != 0)\n                        {\n                            self.status.set_spr_zero_hit(true);\n                        }\n\n                        if !sprite.bg_priority | (bg_color == 0) {\n                            return sprite.palette + spr_color;\n                        }\n                        break;\n                    }\n                }\n            }\n        }\n\n        let palette_mask = u8::from((fine_x + (x & 0x07)) < 8);\n        let palette = palette_mask * self.prev_palette + (1 - palette_mask) * self.curr_palette;\n        palette + bg_color\n    }\n\n    #[inline]\n    fn headless_sprite_zero_hit(&mut self) {\n        if !self.spr_zero_visible || self.status.spr_zero_hit {\n            return;\n        }\n\n        let cycle = self.cycle;\n        let show_left_bg = self.mask.show_left_bg;\n        let show_left_spr = self.mask.show_left_spr;\n        let show_bg = self.mask.show_bg;\n        let show_spr = self.mask.show_spr;\n        let min_render_x = cycle >= 9;\n\n        let bg_mask = u8::from(show_bg & (show_left_bg | min_render_x));\n        if (bg_mask == 0)\n            | !(show_spr & (show_left_spr | min_render_x))\n            | (cycle == 256)\n            | !self.spr_present[usize::from(cycle)]\n        {\n            return;\n        }\n\n        let bg_shift = 15 - self.scroll.fine_x;\n        let bg_color = bg_mask\n            * ((((self.tile_shift_hi >> bg_shift) & 0x01) << 1)\n                | ((self.tile_shift_lo >> bg_shift) & 0x01)) as u8;\n        if bg_color == 0 {\n            return;\n        }\n\n        let sprite = &self.sprites[0];\n        let spr_shift = cycle.wrapping_sub(sprite.x).wrapping_sub(1);\n        if spr_shift <= 7 {\n            let spr_shift = if sprite.flip_horizontal {\n                spr_shift\n            } else {\n                7 - spr_shift\n            };\n            let spr_color = (((sprite.tile_hi >> spr_shift) & 0x01) << 1)\n                | ((sprite.tile_lo >> spr_shift) & 0x01);\n            if spr_color != 0 {\n                self.status.set_spr_zero_hit(true);\n            }\n        }\n    }\n\n    #[inline(always)]\n    fn render_pixel(&mut self) {\n        let addr = self.scroll.addr();\n        let color = if self.mask.rendering_enabled || !addr.is_palette() {\n            let palette = u16::from(self.pixel_palette());\n            self.palette\n                .read(addr::PALETTE_START | ((palette & 0x03 > 0) as u16 * palette))\n        } else {\n            self.palette.read(addr)\n        };\n\n        self.frame.set_pixel(\n            self.cycle - 1,\n            self.scanline,\n            u16::from(color & self.mask.grayscale) | self.mask.emphasis,\n        );\n    }\n\n    #[inline(always)]\n    pub fn clock_to(&mut self, clock: u32) {\n        let divider = u32::from(self.clock_divider);\n        while self.master_clock + divider <= clock {\n            self.clock();\n            self.master_clock += divider;\n        }\n    }\n\n    // $2000 | RW  | PPUCTRL\n    //       | 0-1 | Name Table to show:\n    //       |     |\n    //       |     |           +-----------+-----------+\n    //       |     |           | 2 ($2800) | 3 ($2C00) |\n    //       |     |           +-----------+-----------+\n    //       |     |           | 0 ($2000) | 1 ($2400) |\n    //       |     |           +-----------+-----------+\n    //       |     |\n    //       |     | Remember, though, that because of the mirroring, there are\n    //       |     | only 2 real Name Tables, not 4.\n    //       |   2 | Vertical Write, 1 = PPU memory address increments by 32:\n    //       |     |\n    //       |     |    Name Table, VW=0          Name Table, VW=1\n    //       |     |   +----------------+        +----------------+\n    //       |     |   |----> write     |        | | write        |\n    //       |     |   |                |        | V              |\n    //       |     |\n    //       |   3 | Sprite Pattern Table address, 1 = $1000, 0 = $0000\n    //       |   4 | Screen Pattern Table address, 1 = $1000, 0 = $0000\n    //       |   5 | Sprite Size, 1 = 8x16, 0 = 8x8\n    //       |   6 | Hit Switch, 1 = generate interrupts on Hit (incorrect ???)\n    //       |   7 | VBlank Switch, 1 = generate interrupts on VBlank\n    pub fn write_ctrl(&mut self, val: u8) {\n        self.open_bus = val;\n        if self.reset_signal {\n            return;\n        }\n        self.ctrl.write(val);\n        self.scroll.write_nametable_select(val);\n        // MMC5 tracks changes to PPUCTRL\n        self.mapper.ppu_write(0x2000, val);\n\n        trace!(\n            \"$2000 NMI Enabled: {} - PPU:{:3},{:3}\",\n            self.ctrl.nmi_enabled, self.cycle, self.scanline,\n        );\n\n        // By toggling NMI (bit 7) during VBlank without reading $2002, /NMI can be pulled low\n        // multiple times, causing multiple NMIs to be generated.\n        if !self.ctrl.nmi_enabled {\n            self.nmi_pending = false;\n        } else if self.status.in_vblank {\n            trace!(\n                \"$2000 NMI During VBL - PPU:{:3},{:3}\",\n                self.cycle, self.scanline\n            );\n            self.nmi_pending = true;\n        }\n    }\n\n    // $2001 | RW  | PPUMASK\n    //       |   0 | Unknown (???)\n    //       |   1 | BG Mask, 0 = don't show background in left 8 columns\n    //       |   2 | Sprite Mask, 0 = don't show sprites in left 8 columns\n    //       |   3 | BG Switch, 1 = show background, 0 = hide background\n    //       |   4 | Sprites Switch, 1 = show sprites, 0 = hide sprites\n    //       | 5-7 | Unknown (???)\n    #[inline(always)]\n    pub fn write_mask(&mut self, val: u8) {\n        self.open_bus = val;\n        if self.reset_signal {\n            return;\n        }\n        self.mask.write(val);\n        // MMC5 tracks changes to PPUMASK\n        self.mapper.ppu_write(0x2001, val);\n    }\n\n    // $2002 | R   | PPUSTATUS\n    //       | 0-5 | Unknown (???)\n    //       |   6 | Sprite0 Hit Flag, 1 = PPU rendering has hit sprite #0\n    //       |     | This flag resets to 0 when VBlank starts, or CPU reads $2002\n    //       |   7 | VBlank Flag, 1 = PPU is generating a Vertical Blanking Impulse\n    //       |     | This flag resets to 0 when VBlank ends, or CPU reads $2002\n    pub fn read_status(&mut self) -> u8 {\n        let status = self.peek_status();\n        // Top three bits ignored for open bus\n        self.open_bus |= status & 0xE0;\n\n        if self.nmi_pending {\n            trace!(\"$2002 NMI Ack - PPU:{:3},{:3}\", self.cycle, self.scanline);\n        }\n        self.nmi_pending = false;\n        self.status.reset_in_vblank();\n        self.scroll.reset_latch();\n\n        if self.scanline == self.vblank_scanline && self.cycle == cycle::START {\n            // Reading PPUSTATUS one clock before the start of vertical blank will read as clear\n            // and never set the flag or generate an NMI for that frame\n            trace!(\n                \"$2002 Prevent VBL - PPU:{:3},{:3}\",\n                self.cycle, self.scanline\n            );\n            self.prevent_vbl = true;\n        }\n\n        status\n    }\n\n    // $2002 | R   | PPUSTATUS\n    //       | 0-5 | Unknown (???)\n    //       |   6 | Sprite0 Hit Flag, 1 = PPU rendering has hit sprite #0\n    //       |     | This flag resets to 0 when VBlank starts, or CPU reads $2002\n    //       |   7 | VBlank Flag, 1 = PPU is generating a Vertical Blanking Impulse\n    //       |     | This flag resets to 0 when VBlank ends, or CPU reads $2002\n    //\n    // Non-mutating version of `read_status`.\n    #[inline(always)]\n    pub const fn peek_status(&self) -> u8 {\n        // Only upper 3 bits are connected for this register\n        (self.status.read() & 0xE0) | (self.open_bus & 0x1F)\n    }\n\n    // $2003 | W   | OAMADDR\n    //       |     | Used to set the address in the 256-byte Sprite Memory to be\n    //       |     | accessed via $2004. This address will increment by 1 after\n    //       |     | each access to $2004. The Sprite Memory contains coordinates,\n    //       |     | colors, and other attributes of the sprites.\n    #[inline(always)]\n    pub const fn write_oamaddr(&mut self, val: u8) {\n        self.open_bus = val;\n        self.oamaddr = val;\n    }\n\n    // $2004 | RW  | OAMDATA\n    //       |     | Used to read the Sprite Memory. The address is set via\n    //       |     | $2003 and increments after each access. The Sprite Memory\n    //       |     | contains coordinates, colors, and other attributes of the\n    //       |     | sprites.\n    #[inline(always)]\n    pub fn read_oamdata(&mut self) -> u8 {\n        self.open_bus = self.peek_oamdata();\n        self.open_bus\n    }\n\n    // $2004 | RW  | OAMDATA\n    //       |     | Used to read the Sprite Memory. The address is set via\n    //       |     | $2003 and increments after each access. The Sprite Memory\n    //       |     | contains coordinates, colors, and other attributes of the\n    //       |     | sprites.\n    // Non-mutating version of `read_oamdata`.\n    #[inline(always)]\n    pub fn peek_oamdata(&self) -> u8 {\n        // Reading OAMDATA during rendering will expose OAM accesses during sprite evaluation and loading\n        if self.scanline <= scanline::VISIBLE_END\n            && self.mask.rendering_enabled\n            && cycle::SPR_FETCH_RANGE.contains(&self.cycle)\n        {\n            self.secondary_oamdata[self.secondary_oamaddr as usize]\n        } else {\n            self.oamdata[self.oamaddr as usize]\n        }\n    }\n\n    // $2004 | RW  | OAMDATA\n    //       |     | Used to write the Sprite Memory. The address is set via\n    //       |     | $2003 and increments after each access. The Sprite Memory\n    //       |     | contains coordinates, colors, and other attributes of the\n    //       |     | sprites.\n    pub fn write_oamdata(&mut self, mut val: u8) {\n        self.open_bus = val;\n\n        if self.mask.rendering_enabled\n            && (self.is_visible_scanline\n                || self.is_prerender_scanline\n                || self.is_pal_spr_eval_scanline)\n        {\n            // https://www.nesdev.org/wiki/PPU_registers#OAMDATA\n            // Writes to OAMDATA during rendering do not modify values, but do perform a glitch\n            // increment of OAMADDR, bumping only the high 6 bits\n            self.oamaddr = self.oamaddr.wrapping_add(4);\n        } else {\n            if self.oamaddr & 0x03 == 0x02 {\n                // Bits 2-4 of sprite attr (byte 2) are unimplemented and always read back as 0\n                val &= 0xE3;\n            }\n            self.oamdata[self.oamaddr as usize] = val;\n            self.oamaddr = self.oamaddr.wrapping_add(1);\n        }\n    }\n\n    // $2005 | W   | PPUSCROLL\n    //       |     | There are two scroll registers, vertical and horizontal,\n    //       |     | which are both written via this port. The first value written\n    //       |     | will go into the Vertical Scroll Register (unless it is >239,\n    //       |     | then it will be ignored). The second value will appear in the\n    //       |     | Horizontal Scroll Register. The Name Tables are assumed to be\n    //       |     | arranged in the following way:\n    //       |     |\n    //       |     |           +-----------+-----------+\n    //       |     |           | 2 ($2800) | 3 ($2C00) |\n    //       |     |           +-----------+-----------+\n    //       |     |           | 0 ($2000) | 1 ($2400) |\n    //       |     |           +-----------+-----------+\n    //       |     |\n    //       |     | When scrolled, the picture may span over several Name Tables.\n    //       |     | Remember, though, that because of the mirroring, there are\n    //       |     | only 2 real Name Tables, not 4.\n    #[inline(always)]\n    pub fn write_scroll(&mut self, val: u8) {\n        self.open_bus = val;\n\n        if self.reset_signal {\n            return;\n        }\n        self.scroll.write(val);\n    }\n\n    // $2006 | W   | PPUADDR\n    #[inline(always)]\n    pub fn write_addr(&mut self, val: u8) {\n        self.open_bus = val;\n\n        if self.reset_signal {\n            return;\n        }\n        self.scroll.write_addr(val);\n    }\n\n    // $2007 | RW  | PPUDATA\n    pub fn read_data(&mut self) -> u8 {\n        let addr = self.scroll.addr();\n        self.increment_vram_addr();\n\n        // Buffering quirk resulting in a dummy read for the CPU\n        // for reading pre-palette data in $0000 - $3EFF\n        let prev_open_bus = self.open_bus;\n        let val = self.bus_read(addr);\n        // MMC3 clocks using A12\n        self.mapper.ppu_read(self.scroll.addr());\n        self.open_bus = if addr < addr::PALETTE_START {\n            let buffer = self.vram_buffer;\n            self.vram_buffer = val;\n            buffer\n        } else {\n            // Set internal buffer with mirrors of nametable when reading palettes\n            // Since we're reading from > $3EFF subtract $1000 to fill\n            // buffer with nametable mirror data\n            self.vram_buffer = self.bus_read(addr - 0x1000);\n            // Hi 2 bits of palette should be open bus\n            val | (prev_open_bus & 0xC0)\n        };\n\n        trace!(\n            \"PPU $2007 read: {:02X} - PPU:{:3},{:3}\",\n            self.open_bus, self.cycle, self.scanline\n        );\n\n        self.open_bus\n    }\n\n    // $2007 | RW  | PPUDATA\n    //\n    // Non-mutating version of `read_data`.\n    pub fn peek_data(&self) -> u8 {\n        let addr = self.scroll.addr();\n        if addr < addr::PALETTE_START {\n            self.vram_buffer\n        } else {\n            // Since we're reading from > $3EFF subtract $1000\n            // Hi 2 bits of palette should be open bus\n            self.bus_peek(addr - 0x1000) | (self.open_bus & 0xC0)\n        }\n    }\n\n    // $2007 | RW  | PPUDATA\n    pub fn write_data(&mut self, val: u8) {\n        let addr = self.scroll.addr();\n        trace!(\n            \"PPU $2007 write: ${addr:04X} -> {val:02X} - PPU:{:3},{:3}\",\n            self.cycle, self.scanline\n        );\n        self.increment_vram_addr();\n        self.bus_write(addr, val);\n        // MMC3 clocks using A12\n        self.mapper.ppu_read(self.scroll.addr());\n    }\n}\n\nimpl Clock for Ppu {\n    fn clock(&mut self) {\n        // === SCANLINE TRANSITION (cycle 340) ===\n        if self.cycle >= cycle::END {\n            self.cycle = 0;\n            self.scanline += 1;\n            // === POST-RENDER (240/261) ===\n            match self.scanline {\n                s if s == self.vblank_scanline - 1 => {\n                    self.frame.increment();\n                }\n                s if s > self.prerender_scanline => {\n                    // Wrap scanline back to 0\n                    self.scanline = 0;\n                    // Force prerender scanline sprite fetches to load the dummy $FF tiles (fixes\n                    // shaking in Ninja Gaiden 3 stage 1 after beating boss)\n                    self.spr_count = 0;\n                }\n                _ => (),\n            }\n\n            self.is_visible_scanline = self.scanline <= scanline::VISIBLE_END;\n            self.is_prerender_scanline = self.scanline == self.prerender_scanline;\n            self.is_render_scanline = self.is_visible_scanline | self.is_prerender_scanline;\n            // PAL refreshes OAM later due to extended vblank to avoid OAM decay\n            self.is_pal_spr_eval_scanline =\n                self.region.is_pal() && self.scanline >= self.vblank_scanline + 24;\n\n            if self.scanline == self.debugger.scanline && self.cycle == self.debugger.cycle {\n                (*self.debugger.callback)(self.snapshot());\n            }\n\n            return;\n        }\n\n        self.cycle += 1;\n\n        // === RENDER LINE (scanlins 0-239, 261) ===\n        if self.mask.rendering_enabled {\n            if self.is_render_scanline {\n                if self.cycle <= cycle::VISIBLE_END {\n                    if self.is_visible_scanline {\n                        self.spr_eval_cycle();\n                    }\n\n                    self.bg_fetch_cycle();\n\n                    if self.is_prerender_scanline && self.cycle <= 8 && self.oamaddr >= 0x08 {\n                        // If OAMADDR is not less than eight when rendering starts, the eight bytes\n                        // starting at OAMADDR & 0xF8 are copied to the first eight bytes of OAM\n                        let addr = (self.cycle as usize) - 1;\n                        let oamindex = (self.oamaddr as usize & 0xF8) + addr;\n                        self.oamdata[addr] = self.oamdata[oamindex];\n                    }\n                } else if self.cycle <= cycle::SPR_FETCH_END {\n                    if self.mask.prev_rendering_enabled && self.cycle == cycle::SPR_FETCH_START {\n                        // Copy X bits at the start of a new line since we're going to start writing\n                        // new x values to t\n                        self.scroll.copy_x();\n                        self.spr_present = ConstArray::new();\n                    }\n                    // 280..=304\n                    if self.is_prerender_scanline && cycle::COPY_Y_RANGE.contains(&self.cycle) {\n                        // Y scroll bits are supposed to be reloaded during this pixel range of PRERENDER\n                        // if rendering is enabled\n                        // https://wiki.nesdev.org/w/index.php/PPU_rendering#Pre-render_scanline_.28-1.2C_261.29\n                        self.scroll.copy_y();\n                    }\n                    self.spr_fetch_cycle();\n                } else {\n                    // 336\n                    if self.cycle <= cycle::BG_PREFETCH_END {\n                        self.bg_fetch_cycle();\n                    } else {\n                        // 337..=340\n                        self.fetch_bg_nt_byte();\n                    }\n\n                    self.oam_fetch = self.secondary_oamdata[0];\n\n                    if self.region.is_ntsc()\n                        && self.is_prerender_scanline\n                        && self.cycle == cycle::ODD_SKIP\n                        && self.frame.is_odd()\n                    {\n                        // NTSC behavior while rendering - each odd PPU frame is one clock shorter\n                        // (skipping from 339 over 340 to 0)\n                        trace!(\n                            \"Skipped odd frame cycle: {} - PPU:{:3},{:3}\",\n                            self.frame_number(),\n                            self.cycle,\n                            self.scanline\n                        );\n                        self.cycle = cycle::END;\n                    }\n                }\n            } else if self.is_pal_spr_eval_scanline {\n                self.spr_eval_cycle();\n                // 257..=320\n                if cycle::SPR_FETCH_RANGE.contains(&self.cycle) {\n                    self.write_oamaddr(0x00);\n                }\n            }\n        }\n\n        self.mask.clock();\n        if self.scroll.delayed_update()\n            && (!self.mask.rendering_enabled || self.scanline > scanline::VISIBLE_END)\n        {\n            // MMC3 clocks using A12\n            self.mapper.ppu_read(self.scroll.addr());\n        }\n\n        // Pixels should be put even if rendering is disabled, as this is what blanks out the\n        // screen. Rendering disabled just means we don't evaluate/read bg/sprite info\n        if self.is_visible_scanline && self.cycle <= cycle::VISIBLE_END {\n            if self.skip_rendering {\n                self.headless_sprite_zero_hit();\n            } else {\n                self.render_pixel();\n            }\n        }\n\n        if self.cycle <= cycle::VISIBLE_END || cycle::BG_PREFETCH_RANGE.contains(&self.cycle) {\n            self.tile_shift_lo <<= 1;\n            self.tile_shift_hi <<= 1;\n        }\n\n        // === VBLANK / IDLE ===\n        if self.scanline == self.vblank_scanline && self.cycle == cycle::VBLANK {\n            self.start_vblank();\n        } else if self.is_prerender_scanline && self.cycle == cycle::VBLANK {\n            self.stop_vblank();\n        }\n\n        if self.scanline == self.debugger.scanline && self.cycle == self.debugger.cycle {\n            (*self.debugger.callback)(self.snapshot());\n        }\n    }\n}\n\nimpl Regional for Ppu {\n    fn region(&self) -> NesRegion {\n        self.region\n    }\n\n    fn set_region(&mut self, region: NesRegion) {\n        // https://www.nesdev.org/wiki/Cycle_reference_chart\n        let (clock_divider, vblank_scanline, prerender_scanline) = match region {\n            NesRegion::Auto | NesRegion::Ntsc => (\n                cycle::DIVIDER_NTSC,\n                scanline::VBLANK_NTSC,\n                scanline::PRERENDER_NTSC,\n            ),\n            NesRegion::Pal => (\n                cycle::DIVIDER_PAL,\n                scanline::VBLANK_PAL,\n                scanline::PRERENDER_PAL,\n            ),\n            NesRegion::Dendy => (\n                cycle::DIVIDER_DENDY,\n                scanline::VBLANK_DENDY,\n                scanline::PRERENDER_DENDY,\n            ),\n        };\n        self.region = region;\n        self.clock_divider = clock_divider;\n        self.vblank_scanline = vblank_scanline;\n        self.prerender_scanline = prerender_scanline;\n        self.mask.set_region(region);\n    }\n}\n\nimpl Reset for Ppu {\n    fn reset(&mut self, kind: ResetKind) {\n        self.master_clock = 0;\n        self.cycle = 0;\n        self.scanline = 0;\n        self.is_visible_scanline = true;\n        self.is_prerender_scanline = false;\n        self.is_render_scanline = true;\n        self.is_pal_spr_eval_scanline = false;\n        self.open_bus = 0x00;\n\n        self.mask.reset(kind);\n        self.scroll.reset(kind);\n        self.ctrl.reset(kind);\n\n        self.mapper.reset(kind);\n\n        self.status.reset(kind);\n        self.nmi_pending = false;\n\n        self.oam_fetch = 0x00;\n        self.oam_eval_done = false;\n        self.secondary_oamaddr = 0x0000;\n        self.overflow_count = 0;\n        self.spr_in_range = false;\n        self.spr_zero_in_range = false;\n        self.spr_zero_visible = false;\n        self.spr_count = 0;\n        self.vram_buffer = 0x00;\n\n        if kind == ResetKind::Hard {\n            self.oamaddr = 0x0000;\n            self.oamdata = ConstArray::new();\n        } else {\n            self.reset_signal = self.emulate_warmup;\n        }\n        *self.sprites = [Sprite::new(); 8];\n        self.spr_present = ConstArray::new();\n        self.prevent_vbl = false;\n        self.frame.reset(kind);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::{\n        cart::Cart,\n        mapper::{Mmc1Revision, Sxrom},\n        mem::Memory,\n    };\n\n    #[test]\n    fn ciram_mirror_horizontal() {\n        assert_eq!(CIRam::mirror(0x2000, Mirroring::Horizontal), 0x0000);\n        assert_eq!(CIRam::mirror(0x2005, Mirroring::Horizontal), 0x0005);\n        assert_eq!(CIRam::mirror(0x23FF, Mirroring::Horizontal), 0x03FF);\n        assert_eq!(CIRam::mirror(0x2400, Mirroring::Horizontal), 0x0000);\n        assert_eq!(CIRam::mirror(0x2405, Mirroring::Horizontal), 0x0005);\n        assert_eq!(CIRam::mirror(0x27FF, Mirroring::Horizontal), 0x03FF);\n        assert_eq!(CIRam::mirror(0x2800, Mirroring::Horizontal), 0x0400);\n        assert_eq!(CIRam::mirror(0x2805, Mirroring::Horizontal), 0x0405);\n        assert_eq!(CIRam::mirror(0x2BFF, Mirroring::Horizontal), 0x07FF);\n        assert_eq!(CIRam::mirror(0x2C00, Mirroring::Horizontal), 0x0400);\n        assert_eq!(CIRam::mirror(0x2C05, Mirroring::Horizontal), 0x0405);\n        assert_eq!(CIRam::mirror(0x2FFF, Mirroring::Horizontal), 0x07FF);\n    }\n\n    #[test]\n    fn ciram_mirror_vertical() {\n        assert_eq!(CIRam::mirror(0x2000, Mirroring::Vertical), 0x0000);\n        assert_eq!(CIRam::mirror(0x2005, Mirroring::Vertical), 0x0005);\n        assert_eq!(CIRam::mirror(0x23FF, Mirroring::Vertical), 0x03FF);\n        assert_eq!(CIRam::mirror(0x2800, Mirroring::Vertical), 0x0000);\n        assert_eq!(CIRam::mirror(0x2805, Mirroring::Vertical), 0x0005);\n        assert_eq!(CIRam::mirror(0x2BFF, Mirroring::Vertical), 0x03FF);\n        assert_eq!(CIRam::mirror(0x2400, Mirroring::Vertical), 0x0400);\n        assert_eq!(CIRam::mirror(0x2405, Mirroring::Vertical), 0x0405);\n        assert_eq!(CIRam::mirror(0x27FF, Mirroring::Vertical), 0x07FF);\n        assert_eq!(CIRam::mirror(0x2C00, Mirroring::Vertical), 0x0400);\n        assert_eq!(CIRam::mirror(0x2C05, Mirroring::Vertical), 0x0405);\n        assert_eq!(CIRam::mirror(0x2FFF, Mirroring::Vertical), 0x07FF);\n    }\n\n    #[test]\n    fn ciram_mirror_single_screen_a() {\n        assert_eq!(CIRam::mirror(0x2000, Mirroring::SingleScreenA), 0x0000);\n        assert_eq!(CIRam::mirror(0x2005, Mirroring::SingleScreenA), 0x0005);\n        assert_eq!(CIRam::mirror(0x23FF, Mirroring::SingleScreenA), 0x03FF);\n        assert_eq!(CIRam::mirror(0x2800, Mirroring::SingleScreenA), 0x0000);\n        assert_eq!(CIRam::mirror(0x2805, Mirroring::SingleScreenA), 0x0005);\n        assert_eq!(CIRam::mirror(0x2BFF, Mirroring::SingleScreenA), 0x03FF);\n        assert_eq!(CIRam::mirror(0x2400, Mirroring::SingleScreenA), 0x0000);\n        assert_eq!(CIRam::mirror(0x2405, Mirroring::SingleScreenA), 0x0005);\n        assert_eq!(CIRam::mirror(0x27FF, Mirroring::SingleScreenA), 0x03FF);\n        assert_eq!(CIRam::mirror(0x2C00, Mirroring::SingleScreenA), 0x0000);\n        assert_eq!(CIRam::mirror(0x2C05, Mirroring::SingleScreenA), 0x0005);\n        assert_eq!(CIRam::mirror(0x2FFF, Mirroring::SingleScreenA), 0x03FF);\n    }\n\n    #[test]\n    fn ciram_mirror_single_screen_b() {\n        assert_eq!(CIRam::mirror(0x2000, Mirroring::SingleScreenB), 0x0400);\n        assert_eq!(CIRam::mirror(0x2005, Mirroring::SingleScreenB), 0x0405);\n        assert_eq!(CIRam::mirror(0x23FF, Mirroring::SingleScreenB), 0x07FF);\n        assert_eq!(CIRam::mirror(0x2800, Mirroring::SingleScreenB), 0x0400);\n        assert_eq!(CIRam::mirror(0x2805, Mirroring::SingleScreenB), 0x0405);\n        assert_eq!(CIRam::mirror(0x2BFF, Mirroring::SingleScreenB), 0x07FF);\n        assert_eq!(CIRam::mirror(0x2400, Mirroring::SingleScreenB), 0x0400);\n        assert_eq!(CIRam::mirror(0x2405, Mirroring::SingleScreenB), 0x0405);\n        assert_eq!(CIRam::mirror(0x27FF, Mirroring::SingleScreenB), 0x07FF);\n        assert_eq!(CIRam::mirror(0x2C00, Mirroring::SingleScreenB), 0x0400);\n        assert_eq!(CIRam::mirror(0x2C05, Mirroring::SingleScreenB), 0x0405);\n        assert_eq!(CIRam::mirror(0x2FFF, Mirroring::SingleScreenB), 0x07FF);\n    }\n\n    #[test]\n    fn vram_writes() {\n        let mut ppu = Ppu::default();\n        ppu.write_addr(0x23);\n        ppu.write_addr(0x05);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.write_data(0x66); // write to $2305\n\n        assert_eq!(ppu.chr_read(0x2305), 0x66);\n    }\n\n    #[test]\n    fn vram_reads() {\n        let mut ppu = Ppu::default();\n        ppu.write_ctrl(0x00);\n        ppu.bus_write(0x2305, 0x66);\n\n        ppu.write_addr(0x23);\n        ppu.write_addr(0x05);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.read_data(); // buffer read\n        assert_eq!(ppu.scroll.addr(), 0x2306);\n        assert_eq!(ppu.read_data(), 0x66);\n        assert_eq!(ppu.scroll.addr(), 0x2307);\n    }\n\n    #[test]\n    fn vram_read_pagecross() {\n        let mut ppu = Ppu::default();\n        ppu.write_ctrl(0x00);\n        ppu.bus_write(0x21FF, 0x66);\n        ppu.bus_write(0x2200, 0x77);\n\n        ppu.write_addr(0x21);\n        ppu.write_addr(0xFF);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.read_data(); // buffer read\n        assert_eq!(ppu.read_data(), 0x66);\n        assert_eq!(ppu.read_data(), 0x77);\n    }\n\n    #[test]\n    fn vram_read_vertical_increment() {\n        let mut ppu = Ppu::default();\n        ppu.write_ctrl(0b100);\n        ppu.bus_write(0x21FF, 0x66);\n        ppu.bus_write(0x21FF + 32, 0x77);\n        ppu.bus_write(0x21FF + 64, 0x88);\n\n        ppu.write_addr(0x21);\n        ppu.write_addr(0xFF);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.read_data(); // buffer read\n        assert_eq!(ppu.read_data(), 0x66);\n        assert_eq!(ppu.read_data(), 0x77);\n        assert_eq!(ppu.read_data(), 0x88);\n    }\n\n    // Horizontal: https://wiki.nesdev.org/w/index.php/Mirroring\n    //   [0x2000 A ] [0x2400 a ]\n    //   [0x2800 B ] [0x2C00 b ]\n    #[test]\n    fn vram_horizontal_mirror() {\n        let mut ppu = Ppu::default();\n        ppu.write_addr(0x24);\n        ppu.write_addr(0x05);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.write_data(0x66); // write to a at $2405\n\n        ppu.write_addr(0x28);\n        ppu.write_addr(0x05);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.write_data(0x77); // write to B at $2805\n\n        ppu.write_addr(0x20);\n        ppu.write_addr(0x05);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.read_data(); // buffer read\n        assert_eq!(ppu.read_data(), 0x66); // read A from $2005\n\n        ppu.write_addr(0x2C);\n        ppu.write_addr(0x05);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.read_data(); // buffer read\n        assert_eq!(ppu.read_data(), 0x77); // read b from $2C05\n    }\n\n    // Vertical: https://wiki.nesdev.org/w/index.php/Mirroring\n    //   [0x2000 A ] [0x2400 B ]\n    //   [0x2800 a ] [0x2C00 b ]\n    #[test]\n    fn vram_vertical_mirror() {\n        let mut ppu = Ppu::default();\n        let mut cart = Cart::default();\n        cart.mapper = Sxrom::load(\n            &cart,\n            Memory::new(0x2000),\n            Memory::new(0x4000),\n            Mmc1Revision::BC,\n        )\n        .unwrap();\n        // Set vertical mirroring mode via 5 writes\n        let mut val = 0b00_00_00_01_00;\n        for _ in 0..5 {\n            cart.mapper.prg_write(0x8000, val & 0b11);\n            cart.mapper.clock();\n            cart.mapper.clock();\n            val >>= 2;\n        }\n        ppu.load_mapper(cart.mapper);\n\n        ppu.write_addr(0x20);\n        ppu.write_addr(0x05);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.write_data(0x66); // write to A at $2005\n\n        ppu.write_addr(0x2C);\n        ppu.write_addr(0x05);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.write_data(0x77); // write to b at $2C05\n\n        ppu.write_addr(0x28);\n        ppu.write_addr(0x05);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.read_data(); // buffer read\n        assert_eq!(ppu.read_data(), 0x66); // read a from $2805\n\n        ppu.write_addr(0x24);\n        ppu.write_addr(0x05);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.read_data(); // buffer read\n        assert_eq!(ppu.read_data(), 0x77); // read B from $2405\n    }\n\n    #[test]\n    fn read_status_resets_latch() {\n        let mut ppu = Ppu::default();\n        ppu.bus_write(0x2305, 0x66);\n\n        ppu.write_addr(0x21);\n        ppu.write_addr(0x23);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.write_addr(0x05);\n        ppu.read_data(); // buffer read\n        assert_ne!(ppu.read_data(), 0x66);\n\n        ppu.read_status();\n\n        ppu.write_addr(0x23);\n        ppu.write_addr(0x05);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.read_data(); // buffer read\n        assert_eq!(ppu.read_data(), 0x66);\n    }\n\n    #[test]\n    fn vram_mirroring() {\n        let mut ppu = Ppu::default();\n        ppu.write_ctrl(0);\n        ppu.bus_write(0x2305, 0x66);\n\n        ppu.write_addr(0x63); // 0x6305 mirrors to 0x2305\n        ppu.write_addr(0x05);\n        // PPU writes to $2006 are delayed by 2 PPU clocks\n        ppu.clock();\n        ppu.clock();\n        ppu.read_data(); // buffer read\n        assert_eq!(ppu.scroll.addr(), 0x2306);\n        assert_eq!(ppu.read_data(), 0x66);\n        assert_eq!(ppu.scroll.addr(), 0x2307);\n    }\n\n    #[test]\n    fn read_status_resets_vblank() {\n        let mut ppu = Ppu::default();\n        ppu.status.set_in_vblank(true);\n\n        let status = ppu.read_status();\n        assert_eq!(status >> 7, 1);\n        assert_eq!(ppu.status.read() >> 7, 0);\n    }\n\n    #[test]\n    fn sprite_zero_hit_headless_visible_cycle() {\n        let mut ppu = Ppu::default();\n        ppu.write_mask(0x18);\n        ppu.skip_rendering = true;\n        ppu.scanline = 0;\n        ppu.cycle = 10;\n        ppu.scroll.fine_x = 0;\n\n        ppu.tile_shift_lo = 0x8000;\n        ppu.tile_shift_hi = 0x0000;\n\n        ppu.spr_zero_visible = true;\n        ppu.spr_present[9..17].fill(true);\n\n        ppu.sprites[0].x = 8;\n        ppu.sprites[0].tile_lo = 0b0100;\n        ppu.sprites[0].tile_hi = 0b0000;\n        ppu.sprites[0].flip_horizontal = true;\n        ppu.sprites[0].bg_priority = false;\n\n        ppu.clock();\n\n        assert!(ppu.status.spr_zero_hit);\n    }\n\n    #[test]\n    fn oam_read_write() {\n        let mut ppu = Ppu::default();\n        ppu.write_oamaddr(0x10);\n        ppu.write_oamdata(0x66);\n        ppu.write_oamdata(0x77);\n\n        ppu.write_oamaddr(0x10);\n        assert_eq!(ppu.read_oamdata(), 0x66);\n\n        ppu.write_oamaddr(0x11);\n        assert_eq!(ppu.read_oamdata(), 0x77);\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/sys/fs/os.rs",
    "content": "//! OS-specific filesystem operations.\n\nuse crate::fs::{Error, Result};\nuse std::{\n    fs::{File, create_dir_all, remove_dir_all},\n    io::{Read, Write},\n    path::Path,\n};\n\npub fn writer_impl(path: impl AsRef<Path>) -> Result<impl Write> {\n    let path = path.as_ref();\n    let Some(directory) = path.parent() else {\n        return Err(Error::InvalidPath(path.to_path_buf()));\n    };\n    if !directory.exists() {\n        create_dir_all(directory)\n            .map_err(|err| Error::io(err, format!(\"failed to create directory {directory:?}\")))?;\n    }\n    File::create(path)\n        .map_err(|source| Error::io(source, format!(\"failed to create file {path:?}\")))\n}\n\npub fn reader_impl(path: impl AsRef<Path>) -> Result<impl Read> {\n    let path = path.as_ref();\n    File::open(path).map_err(|source| Error::io(source, format!(\"failed to open file {path:?}\")))\n}\n\npub fn clear_dir_impl(path: impl AsRef<Path>) -> Result<()> {\n    let path = path.as_ref();\n    if !path.exists() {\n        return Ok(());\n    }\n    remove_dir_all(path)\n        .map_err(|source| Error::io(source, format!(\"failed to remove directory {path:?}\")))\n}\n\npub fn exists_impl(path: impl AsRef<Path>) -> bool {\n    let path = path.as_ref();\n    path.exists()\n}\n"
  },
  {
    "path": "tetanes-core/src/sys/fs/wasm.rs",
    "content": "//! Web-specific filesystem operations.\n\nuse crate::fs::{Error, Result};\nuse std::{\n    io::{self, Read, Write},\n    mem,\n    path::{Path, PathBuf},\n};\nuse web_sys::js_sys;\n\n#[derive(Debug)]\n#[must_use]\npub struct StoreWriter {\n    path: PathBuf,\n    data: Vec<u8>,\n}\n\npub struct StoreReader {\n    cursor: io::Cursor<Vec<u8>>,\n}\n\npub fn local_storage() -> Result<web_sys::Storage> {\n    let window = web_sys::window().ok_or_else(|| Error::custom(\"failed to get js window\"))?;\n    window\n        .local_storage()\n        .map_err(|err| {\n            tracing::error!(\"failed to get local storage: {err:?}\");\n            Error::custom(\"failed to get storage\")\n        })?\n        .ok_or_else(|| Error::custom(\"no storage available\"))\n}\n\nimpl Write for StoreWriter {\n    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {\n        self.data.extend_from_slice(buf);\n        Ok(buf.len())\n    }\n\n    fn flush(&mut self) -> io::Result<()> {\n        let local_storage = local_storage().map_err(io::Error::other)?;\n\n        let key = self.path.to_string_lossy();\n        let data = mem::take(&mut self.data);\n        let value = match serde_json::to_string(&data) {\n            Ok(value) => value,\n            Err(err) => {\n                self.data = data;\n                tracing::error!(\"failed to serialize data: {err:?}\");\n                return Err(io::Error::other(\"failed to serialize data\"));\n            }\n        };\n\n        if let Err(err) = local_storage.set_item(&key, &value) {\n            self.data = data;\n            tracing::error!(\"failed to store data in local storage: {err:?}\");\n            return Err(io::Error::other(\"failed to write data\"));\n        }\n\n        Ok(())\n    }\n}\n\nimpl Read for StoreReader {\n    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {\n        self.cursor.read(buf)\n    }\n}\n\npub fn writer_impl(path: impl AsRef<Path>) -> Result<impl Write> {\n    let path = path.as_ref();\n    Ok(StoreWriter {\n        path: path.to_path_buf(),\n        data: Vec::new(),\n    })\n}\n\npub fn reader_impl(path: impl AsRef<Path>) -> Result<impl Read> {\n    let path = path.as_ref();\n    let local_storage = local_storage()?;\n\n    let key = path.to_string_lossy().into_owned();\n    let data = local_storage\n        .get_item(&key)\n        .map_err(|_| Error::custom(\"failed to find data for {key}\"))?\n        .map(|value| {\n            serde_json::from_str(&value).map_err(|err| {\n                tracing::error!(\"failed to deserialize data: {err:?}\");\n                Error::custom(\"failed to deserialize data\")\n            })\n        })\n        .unwrap_or_else(|| Ok(Vec::new()))?;\n\n    Ok(StoreReader {\n        cursor: io::Cursor::new(data),\n    })\n}\n\npub fn clear_dir_impl(path: impl AsRef<Path>) -> Result<()> {\n    let path = path.as_ref().to_string_lossy();\n    let local_storage = local_storage()?;\n\n    for key in js_sys::Object::keys(&local_storage)\n        .iter()\n        .filter_map(|key| key.as_string())\n        .filter(|key| key.starts_with(&*path))\n    {\n        let _ = local_storage.remove_item(&key);\n    }\n\n    Ok(())\n}\n\npub fn exists_impl(path: impl AsRef<Path>) -> bool {\n    let path = path.as_ref();\n    let Ok(local_storage) = local_storage() else {\n        return false;\n    };\n\n    let key = path.to_string_lossy();\n    matches!(local_storage.get_item(&key), Ok(Some(_)))\n}\n"
  },
  {
    "path": "tetanes-core/src/sys/fs.rs",
    "content": "//! Platform-specific filesystem methods.\n\nuse cfg_if::cfg_if;\n\ncfg_if! {\n    if #[cfg(target_arch = \"wasm32\")] {\n        mod wasm;\n        pub use wasm::*;\n    } else {\n        mod os;\n        pub use os::*;\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/sys/time.rs",
    "content": "//! Platform-specific time and date methods.\n\nuse cfg_if::cfg_if;\n\ncfg_if! {\n    if #[cfg(target_arch = \"wasm32\")] {\n        pub use web_time::{Duration, Instant};\n    } else {\n        pub use std::time::{Duration, Instant};\n    }\n}\n"
  },
  {
    "path": "tetanes-core/src/sys.rs",
    "content": "//! System-specific modules.\n\npub mod fs;\npub mod time;\n"
  },
  {
    "path": "tetanes-core/src/time.rs",
    "content": "//! Time and Date methods.\n\npub use crate::sys::time::*;\n"
  },
  {
    "path": "tetanes-core/src/video.rs",
    "content": "//! Video output and filtering.\n\nuse crate::ppu::{self, Ppu};\nuse serde::{Deserialize, Serialize};\nuse std::{\n    f64::consts::PI,\n    ops::{Deref, DerefMut},\n    sync::OnceLock,\n};\nuse thiserror::Error;\n\n#[derive(Error, Debug)]\n#[must_use]\n#[error(\"failed to parse `VideoFilter`\")]\npub struct ParseVideoFilterError;\n\n#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[must_use]\npub enum VideoFilter {\n    Pixellate,\n    #[default]\n    Ntsc,\n}\n\nimpl VideoFilter {\n    pub const fn as_slice() -> &'static [Self] {\n        &[Self::Pixellate, Self::Ntsc]\n    }\n}\n\nimpl AsRef<str> for VideoFilter {\n    fn as_ref(&self) -> &str {\n        match self {\n            Self::Pixellate => \"Pixellate\",\n            Self::Ntsc => \"NTSC\",\n        }\n    }\n}\n\nimpl TryFrom<usize> for VideoFilter {\n    type Error = ParseVideoFilterError;\n\n    fn try_from(value: usize) -> Result<Self, Self::Error> {\n        Ok(match value {\n            0 => Self::Pixellate,\n            1 => Self::Ntsc,\n            _ => return Err(ParseVideoFilterError),\n        })\n    }\n}\n\n#[derive(Debug, Clone)]\n#[must_use]\npub struct Frame(Vec<u8>);\n\nimpl Frame {\n    pub const SIZE: usize = ppu::size::FRAME * 4;\n\n    /// Allocate a new frame for video output.\n    pub fn new() -> Self {\n        Self(\n            [(); Self::SIZE / 4]\n                .into_iter()\n                .flat_map(|_| [0, 0, 0, 255])\n                .collect(),\n        )\n    }\n}\n\nimpl Default for Frame {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Deref for Frame {\n    type Target = Vec<u8>;\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl DerefMut for Frame {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.0\n    }\n}\n\n#[derive(Clone)]\n#[must_use]\npub struct Video {\n    pub filter: VideoFilter,\n    pub frame: Frame,\n}\n\nimpl Default for Video {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Video {\n    /// Create a new Video decoder with the default filter.\n    pub fn new() -> Self {\n        Self::with_filter(VideoFilter::default())\n    }\n\n    /// Create a new Video encoder with a filter.\n    pub fn with_filter(filter: VideoFilter) -> Self {\n        Self {\n            filter,\n            frame: Frame::new(),\n        }\n    }\n\n    /// Applies the given filter to the given video buffer and returns the result.\n    pub fn apply_filter(&mut self, buffer: &[u16], frame_number: u32) -> &[u8] {\n        match self.filter {\n            VideoFilter::Pixellate => Self::decode_buffer(buffer, &mut self.frame),\n            VideoFilter::Ntsc => Self::apply_ntsc_filter(buffer, frame_number, &mut self.frame),\n        }\n\n        &self.frame\n    }\n\n    /// Applies the given filter to the given video buffer by coping into the provided buffer.\n    pub fn apply_filter_into(&self, buffer: &[u16], frame_number: u32, output: &mut [u8]) {\n        match self.filter {\n            VideoFilter::Pixellate => Self::decode_buffer(buffer, output),\n            VideoFilter::Ntsc => Self::apply_ntsc_filter(buffer, frame_number, output),\n        }\n    }\n\n    /// Fills a fully rendered frame with RGB colors.\n    pub fn decode_buffer(buffer: &[u16], output: &mut [u8]) {\n        for (color, pixels) in buffer.iter().zip(output.chunks_exact_mut(4)) {\n            let index = (*color as usize) * 3;\n            assert!(Ppu::NTSC_PALETTE.len() > index + 2);\n            assert!(pixels.len() > 2);\n            pixels[0] = Ppu::NTSC_PALETTE[index];\n            pixels[1] = Ppu::NTSC_PALETTE[index + 1];\n            pixels[2] = Ppu::NTSC_PALETTE[index + 2];\n        }\n    }\n\n    /// Applies the NTSC filter to the given video buffer.\n    ///\n    /// Amazing implementation Bisqwit! Much faster than my original, but boy what a pain\n    /// to translate it to Rust\n    /// Source: <https://bisqwit.iki.fi/jutut/kuvat/programming_examples/nesemu1/nesemu1.cc>\n    /// See also: <https://wiki.nesdev.org/w/index.php/NTSC_video>\n    pub fn apply_ntsc_filter(buffer: &[u16], frame_number: u32, output: &mut [u8]) {\n        let mut prev_color = 0;\n        for (idx, (color, pixels)) in buffer.iter().zip(output.chunks_exact_mut(4)).enumerate() {\n            let x = idx % 256;\n            let rgba = if x == 0 {\n                // Remove pixel 0 artifact from not having a valid previous pixel\n                0\n            } else {\n                let y = idx / 256;\n                let even_phase = if frame_number & 0x01 == 0x01 { 0 } else { 1 };\n                let phase = (2 + y * 341 + x + even_phase) % 3;\n                NTSC_PALETTE.get_or_init(generate_ntsc_palette)\n                    [phase + ((prev_color & 0x3F) as usize) * 3 + (*color as usize) * 3 * 64]\n            };\n            prev_color = u32::from(*color);\n            assert!(pixels.len() > 2);\n            pixels[0] = ((rgba >> 16) & 0xFF) as u8;\n            pixels[1] = ((rgba >> 8) & 0xFF) as u8;\n            pixels[2] = (rgba & 0xFF) as u8;\n            // Alpha should always be 255\n        }\n    }\n}\n\nimpl std::fmt::Debug for Video {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Video\")\n            .field(\"filter\", &self.filter)\n            .finish()\n    }\n}\n\npub static NTSC_PALETTE: OnceLock<Vec<u32>> = OnceLock::new();\nfn generate_ntsc_palette() -> Vec<u32> {\n    // NOTE: There's lot's to clean up here -- too many magic numbers and duplication but\n    // I'm afraid to touch it now that it works\n    // Source: https://bisqwit.iki.fi/jutut/kuvat/programming_examples/nesemu1/nesemu1.cc\n    // https://wiki.nesdev.org/w/index.php/NTSC_video\n\n    // Calculate the luma and chroma by emulating the relevant circuits:\n    const VOLTAGES: [i32; 16] = [\n        -6, -69, 26, -59, 29, -55, 73, -40, 68, -17, 125, 11, 68, 33, 125, 78,\n    ];\n\n    let mut ntsc_palette = vec![0; 512 * 64 * 3];\n\n    // Helper functions for converting YIQ to RGB\n    let gamma = 1.8; // Assumed display gamma\n    let gammafix = |color: f64| {\n        if color <= 0.0 {\n            0.0\n        } else {\n            color.powf(2.2 / gamma)\n        }\n    };\n    let yiq_divider = f64::from(9 * 10u32.pow(6));\n    for palette_offset in 0..3 {\n        for channel in 0..3 {\n            for color0_offset in 0..512 {\n                let emphasis = color0_offset / 64;\n\n                for color1_offset in 0..64 {\n                    let mut y = 0;\n                    let mut i = 0;\n                    let mut q = 0;\n                    // 12 samples of NTSC signal constitute a color.\n                    for sample in 0..12 {\n                        let noise = (sample + palette_offset * 4) % 12;\n                        // Sample either the previous or the current pixel.\n                        // Use pixel=color0_offset to disable artifacts.\n                        let pixel = if noise < 5 - channel * 2 {\n                            color0_offset\n                        } else {\n                            color1_offset\n                        };\n\n                        // Decode the color index.\n                        let chroma = pixel & 0x0F;\n                        // Forces luma to 0, 4, 8, or 12 for easy lookup\n                        let luma = if chroma < 0x0E { (pixel / 4) & 12 } else { 4 };\n                        // NES NTSC modulator (square wave between up to four voltage levels):\n                        let limit = if (chroma + 8 + sample) % 12 < 6 {\n                            12\n                        } else {\n                            0\n                        };\n                        let high = if chroma > limit { 1 } else { 0 };\n                        let emp_effect = if (152_278 >> (sample / 2 * 3)) & emphasis > 0 {\n                            0\n                        } else {\n                            2\n                        };\n                        let level = 40 + VOLTAGES[high + emp_effect + luma];\n                        // Ideal TV NTSC demodulator:\n                        let (sin, cos) = (PI * sample as f64 / 6.0).sin_cos();\n                        y += level;\n                        i += level * (cos * 5909.0) as i32;\n                        q += level * (sin * 5909.0) as i32;\n                    }\n                    // Store color at subpixel precision\n                    let y = f64::from(y) / 1980.0;\n                    let i = f64::from(i) / yiq_divider;\n                    let q = f64::from(q) / yiq_divider;\n                    let idx = palette_offset + color0_offset * 3 * 64 + color1_offset * 3;\n                    match channel {\n                        2 => {\n                            let rgb =\n                                255.0 * gammafix(q.mul_add(0.623_557, i.mul_add(0.946_882, y)));\n                            ntsc_palette[idx] += 0x10000 * rgb.clamp(0.0, 255.0) as u32;\n                        }\n                        1 => {\n                            let rgb =\n                                255.0 * gammafix(q.mul_add(-0.635_691, i.mul_add(-0.274_788, y)));\n                            ntsc_palette[idx] += 0x00100 * rgb.clamp(0.0, 255.0) as u32;\n                        }\n                        0 => {\n                            let rgb =\n                                255.0 * gammafix(q.mul_add(1.709_007, i.mul_add(-1.108_545, y)));\n                            ntsc_palette[idx] += rgb.clamp(0.0, 255.0) as u32;\n                        }\n                        _ => (), // invalid channel\n                    }\n                }\n            }\n        }\n    }\n\n    ntsc_palette\n}\n"
  },
  {
    "path": "tetanes-core/test_roms/apu/blargg_readme.txt",
    "content": "NES APU Frame Counter Update\n----------------------------\n\nI have run more tests on the NES APU and come up with new information\nabout the exact timing of the frame counter and length counter, and some\nsubtle behavior. The information here either extends or contradicts what\nis stated in the NES APU reference and on the nesdev wiki.\n\nNot documented here is a delay when changing modes by writing to $4017.\nThis is quite complex and I haven't fully worked out its exact\noperation. Once determined, documented, and tested, the information here\nshould still be valid. This delay when changing modes involves the\ncurrent mode running a few clocks before switching to the new mode, so\nit only affects the rare case where $4017 is written within a few clocks\nof a frame counter step. This delay does not cause the steps to occur\nany later than shown below; it only causes the first few clocks of the\nnew mode to be transparent, allowing the previous mode to \"show\nthrough\".\n\nAlso not documented is the exact operation of the envelope, sweep, and\ntriangle's linear counter when register writes occur close to clocking.\n\nRefer to tests.txt for a description of the test ROMs included.\n\nI have not yet fully updated my APU emulator and tested it with this\ninformation, so report any problems you have with implementation.\n\nShay <hotpop.com@blargg> (swap to e-mail)\n\n\nClock Jitter\n------------\nChanges to the mode by writing to $4017 only occur on *even* internal\nAPU clocks; if written on an odd clock, the first step of the mode is\ndelayed by one clock. At power-up and reset, the APU is randomly in an\nodd or even cycle with respect to the first clock of the first\ninstruction executed by the CPU.\n\n      ; assume even APU and CPU clocks occur together\n      lda   #$00\n      sta   $4017       ; mode begins in one clock\n      sta   <0          ; delay 3 clocks\n      sta   $4017       ; mode begins immediately\n\n\nMode 0 Timing\n-------------\n-5    lda   #$00\n-3    sta   $4017\n0     (write occurs here)\n1\n2\n3\n...\n      Step 1\n7459  Clock linear\n...\n      Step 2\n14915 Clock linear & length\n...\n      Step 3\n22373 Clock linear\n...\n      Step 4\n29830 Set frame irq\n29831 Clock linear & length and set frame irq\n29832 Set frame irq\n...\n      Step 1\n37289 Clock linear\n...\netc.\n\n\nMode 1 Timing\n-------------\n-5    lda   #$80\n-3    sta   $4017\n0     (write occurs here)\n      Step 0\n1     Clock linear & length\n2\n...\n      Step 1\n7459  Clock linear\n...\n      Step 2\n14915 Clock linear & length\n...\n      Step 3\n22373 Clock linear\n...\n      Step 4\n29829 (do nothing)\n...\n      Step 0\n37283 Clock linear & length\n...\netc.\n\n\nLength Halt\n-----------\nWrite to halt flag is delayed by one clock:\n\n      $10->$4000  clear halt flag\n0     $00->$4017  begin mode 0\n14914 $30->$4000  set halt flag\n14915 Length not clocked\n\n      $10->$4000  clear halt flag\n0     $00->$4017  begin mode 0\n14915 $30->$4000  set halt flag\n      Length clocked\n\n      $30->$4000  set halt flag\n0     $00->$4017  begin mode 0\n14914 $10->$4000  clear halt flag\n14915 Length clocked\n\n      $30->$4000  set halt flag\n0     $00->$4017  begin mode 0\n14915 $10->$4000  clear halt flag\n      Length not clocked\n      \n\n\nLength Reload\n-------------\nLength reload is completely ignored if written during length clocking\nand length counter is non-zero before clocking:\n\n      $38->$4003  make length non-zero\n0     $00->$4017\n14914 Write to $4003\n      Length reloaded\n14915 Length clocked\n\n      $38->$4003  make length non-zero\n0     $00->$4017\n14915 Write to $4003\n      Length not reloaded\n      Length clocked\n\n      $00->$4015  clear length counter\n      $01->$4015\n0     $00->$4017\n14915 Write to $4003\n      Length reloaded\n      Length not clocked\n\nMisc\n----\n- The frame IRQ flag is cleared only when $4015 is read or $4017 is\nwritten with bit 6 set ($40 or $c0).\n\n- The IRQ handler is invoked at minimum 29833 clocks after writing $00\nto $4017 (assuming the frame IRQ flag isn't already set, and nothing\nelse generates an IRQ during that time).\n\n- After reset or power-up, APU acts as if $4017 were written with $00\nfrom 9 to 12 clocks before first instruction begins. It is as if this\noccurs (this generates a 10 clock delay):\n\n      lda   #$00\n      sta   $4017       ; 1\n      lda   <0          ; 9 delay\n      nop\n      nop\n      nop\nreset:\n      ...\n\n- As shown, the frame irq flag is set three times in a row. Thus when\npolling it, always read $4015 an extra time after the flag is found to\nbe set, to be sure it's clear afterwards,\n\nwait: bit   $4015       ; V flag reflects frame IRQ flag\n      bvc   wait\n      bit   $4015       ; be sure irq flag is clear\n\nor better yet, clear it before polling it:\n\n      bit   $4015       ; clear flag first\nwait: bit   $4015       ; V flag reflects frame IRQ flag\n      bvc   wait\n\n"
  },
  {
    "path": "tetanes-core/test_roms/apu/dpcmletterbox.txt",
    "content": "DPCM Letterbox\n\nThis NES program demonstrates abusing the NTSC NES's sampled sound\nplayback hardware as a scanline timer to split the screen twice\nwithout needing to use a mapper-generated IRQ.\n\n== How it works ==\n\nThe NES has sample playback hardware that can trigger when it\nfinishes playing a differential pulse code modulated (DPCM) waveform.\nThere are eight samples to a byte, and a waveform is 16n + 1 bytes\nlong.  There are sixteen valid rates for sample playback, numbered 0\nto 15, and DPCM rate 15 has 54 CPU cycles per sample, or 54*3*8=1296\nPPU dots per byte, or 3.8 scanlines per byte.  But the time between\nNMI and the first sample data fetch drifts from frame to frame.\nSo at the start of the frame, the program has to measure exactly\nhow far apart the CPU and PPU are, and then the IRQ handler has to\nwaste a corresponding amount of time before doing raster effects.\n\nThis version has a slight visual artifact in the top overscan region\nbecause it uses sprite 0 as a timing reference so that it can be\nused even with an NMI handler whose execution time in CPU cycles\nvaries.  But a game with a cycle-timed NMI handler would not need\nsprite 0; it could use the end of the NMI handler as a reference.\n\nReset handler:\nSet up screen data\nEnable IRQ\nWait forever\n\nNMI handler:\nSet up sprite 0 hit at top of screen\nDisable sample playback\nTurn on background rendering at a fixed scroll position\nWait for Sprite 0 off and on\nTurn off background rendering\nClear number of elapsed IRQs\nEnable playback and IRQ\nMeasure time until IRQ in 8-cycle units\nConvert this to an amount of time to waste\nRead the controllers\nCompute next scroll value\nReturn\n\nIRQ handler:\nAdd 1 to elapsed IRQs\nRestart sample playback\nIf elapsed IRQs is at first threshold:\n  Waste time in 8-cycle units\n  Turn on background rendering\nIf elapsed IRQs is at second threshold:\n  Switch next sample playback to 17-byte mode\nIf elapsed IRQs is at third threshold:\n  Waste time in 8-cycle units\n  Turn off background rendering\n\n== Legal ==\n\nThe following license applies to the source code, binary code, and\nthis manual:\n\nCopyright 2010 Damian Yerrick\nCopying and distribution of this file, with or without modification,\nare permitted in any medium without royalty provided the copyright\nnotice and this notice are preserved.  This file is offered as-is,\nwithout any warranty.\n"
  },
  {
    "path": "tetanes-core/test_roms/apu/mixer.txt",
    "content": "NES APU Mixer Tests\n-------------------\nThese tests verify proper operation of the NES APU's sound channel\nmixer, including relative volumes of channels and non-linear mixing.\nTests MUST be run from a freshly-powered NES, as this is the only way to\nensure that the triangle wave doesn't interfere.\n\nAll tests beep, play a test sound, then beep again. For all but the\nnoise test, there should be near silence between the beeps. For the\nnoise test, noise will fade in and out. There shouldn't be any\nnoticeable tone when heard through a speaker (through headphones, faint\ntones might be audible).\n\n\nInternal operation\n------------------\nThe tests have the channel under test generate a tone, then generate the\ninverse waveform using the DMC DAC, canceling to (near) silence if\neverything is correct.\n\nThe DMC test verifies that non-linearity of the DMC DAC. The noise and\ntriangle tests verify relative volume of the noise and triangle to the\nDMC, and that the DMC DAC affects attenuation of them properly. Finally,\nthe square test verifies relative volume of the squares to the DMC,\nnon-linearity of the square DACs, how one square affects the other\n(slightly), and that the square DAC non-linearity is separate from the\nDMC.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/apu/pal_readme.txt",
    "content": "PAL NES APU Tests\n-----------------\nThese tests verify the PAL APU's frame sequencer timing. They have been tested\non a PAL NES and all give a passing result.\n\nEach .nes file runs several tests and reports the result on screen and by\nbeeping a number of times. See below for the meaning of failure codes for each\ntest. It's best to run the tests in order, because later tests depend on things\ntested by earlier tests and will give erroneous results if any earlier ones\nfailed.\n\nSource code for each test is included, and most tests are clearly divided into\nsections. Support code is also included, but it runs on a custom devcart and\nassembler so it will require some effort to assemble. Contact me if you'd like\nassistance porting them to your setup.\n\n\nFrame sequencer timing\n----------------------\nSee blargg_apu_2005.07.30 for more information about frame sequencer timing\nsubtleties. This only lists timing differences.\n\nMode 0: 4-step sequence\n\nAction      Envelopes &     Length Counter& Interrupt   Delay to next\n\t\t\tLinear Counter  Sweep Units     Flag        NTSC     PAL\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n$4017=$00   -               -               -           7459    8315\nStep 1      Clock           -               -           7456    8314\nStep 2      Clock           Clock           -           7458    8312\nStep 3      Clock           -               -           7458    8314\nStep 4      Clock           Clock       Set if enabled  7458    8314\n\n\nMode 1: 5-step sequence\n\nAction      Envelopes &     Length Counter& Interrupt   Delay to next\n\t\t\tLinear Counter  Sweep Units     Flag        NTSC     PAL\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n$4017=$80   -               -               -              1       1\nStep 1      Clock           Clock           -           7458    8314\nStep 2      Clock           -               -           7456    8314\nStep 3      Clock           Clock           -           7458    8312\nStep 4      Clock           -               -           7458    8314\nStep 5      -               -               -           7452    8312\n\nNote: the IRQ flag is actually effectively set three clocks in a row, starting\none clock earlier than shown. NTSC and PAL times shown for comparison.\n\n\n01.len_ctr\n----------\nTests basic length counter operation\n\n1) Passed tests\n2) Problem with length counter load or $4015\n3) Problem with length table, timing, or $4015\n4) Writing $80 to $4017 should clock length immediately\n5) Writing $00 to $4017 shouldn't clock length immediately\n6) Clearing enable bit in $4015 should clear length counter\n7) When disabled via $4015, length shouldn't allow reloading\n8) Halt bit should suspend length clocking\n\n\n02.len_table\n------------\nTests all length table entries.\n\n1) Passed\n2) Failed. Prints four bytes $II $ee $cc $02 that indicate the length load\nvalue written (ll), the value that the emulator uses ($ee), and the correct\nvalue ($cc).\n\n\n03.irq_flag\n-----------\nTests basic operation of frame irq flag.\n\n1) Tests passed\n2) Flag shouldn't be set in $4017 mode $40\n3) Flag shouldn't be set in $4017 mode $80\n4) Flag should be set in $4017 mode $00\n5) Reading flag clears it\n6) Writing $00 or $80 to $4017 doesn't affect flag\n7) Writing $40 or $c0 to $4017 clears flag\n\n\n04.clock_jitter\n---------------\nTests for APU clock jitter. Also tests basic timing of frame irq flag since\nit's needed to determine jitter. It's OK if you don't implement jitter, in\nwhich case you'll get error #5, but you can still run later tests without\nproblem.\n\n1) Passed tests\n2) Frame irq is set too soon\n3) Frame irq is set too late\n4) Even jitter not handled properly\n5) Odd jitter not handled properly\n\n\n05.len_timing_mode0\n-------------------\nTests length counter timing in mode 0.\n\n1) Passed tests\n2) First length is clocked too soon\n3) First length is clocked too late\n4) Second length is clocked too soon\n5) Second length is clocked too late\n6) Third length is clocked too soon\n7) Third length is clocked too late\n\n\n06.len_timing_mode1\n-------------------\nTests length counter timing in mode 1.\n\n1) Passed tests\n2) First length is clocked too soon\n3) First length is clocked too late\n4) Second length is clocked too soon\n5) Second length is clocked too late\n6) Third length is clocked too soon\n7) Third length is clocked too late\n\n\n07.irq_flag_timing\n------------------\nFrame interrupt flag is set three times in a row 33255 clocks after writing\n$4017 with $00.\n\n1) Success\n2) Flag first set too soon\n3) Flag first set too late\n4) Flag last set too soon \n5) Flag last set too late \n\n\n08.irq_timing\n-------------\nIRQ handler is invoked at minimum 33257 clocks after writing $00 to $4017.\n\n1) Passed tests\n2) Too soon\n3) Too late\n4) Never occurred\n\n\n10.len_halt_timing\n------------------\nChanges to length counter halt occur after clocking length, not before.\n\n1) Passed tests\n2) Length shouldn't be clocked when halted at 16628\n3) Length should be clocked when halted at 16629\n4) Length should be clocked when unhalted at 16628\n5) Length shouldn't be clocked when unhalted at 16629\n\n\n11.len_reload_timing\n--------------------\nWrite to length counter reload should be ignored when made during length\ncounter clocking and the length counter is not zero.\n\n1) Passed tests\n2) Reload just before length clock should work normally\n3) Reload just after length clock should work normally\n4) Reload during length clock when ctr = 0 should work normally\n5) Reload during length clock when ctr > 0 should be ignored\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/apu/readme.txt",
    "content": "NES APU Tests\n-------------\nThese ROMs test many aspects of the APU that are visible to the CPU.\nReally obsucre things are not tested here.\n\n\n1-len_ctr\n---------\nTests length counter operation for the four main channels\n\n2) Problem with length counter load or $4015\n3) Problem with length table, timing, or $4015\n4) Writing $80 to $4017 should clock length immediately\n5) Writing 0 to $4017 shouldn't clock length immediately\n6) Disabling via $4015 should clear length counter\n7) When disabled via $4015, length shouldn't allow reloading\n8) Halt bit should suspend length clocking\n\n\n2-len_table\n-----------\nVerifies all length table entries\n\n\n3-irq_flag\n----------\nVerifies basic operation of frame irq flag\n\n2) Flag shouldn't be set in $4017 mode $40\n3) Flag shouldn't be set in $4017 mode $80\n4) Flag should be set in $4017 mode $00\n5) Reading flag should clear it\n6) Writing $00 or $80 to $4017 shouldn't affect flag\n7) Writing $40 or $C0 to $4017 should clear flag\n\n\n4-jitter\n--------\nTests for APU clock jitter. Also tests basic timing of frame irq flag\nsince it's needed to determine jitter.\n\n3) Frame irq is set too late\n4) Even jitter not handled properly\n5) Odd jitter not handled properly\n\n\n5-len_timing\n------------\nVerifies timing of length counter clocks in both modes\n\n2) First length of mode 0 is too soon\n3) First length of mode 0 is too late\n4) Second length of mode 0 is too soon\n5) Second length of mode 0 is too late\n6) Third length of mode 0 is too soon\n7) Third length of mode 0 is too late\n8) First length of mode 1 is too soon\n9) First length of mode 1 is too late\n10) Second length of mode 1 is too soon\n11) Second length of mode 1 is too late\n12) Third length of mode 1 is too soon\n13) Third length of mode 1 is too late\n\n\n6-irq_flag_timing\n-----------------\nFrame interrupt flag is set three times in a row 29831 clocks after\nwriting $00 to $4017.\n\n3) Flag first set too late\n4) Flag last set too soon\n5) Flag last set too late \n\n\n7-dmc_basics\n------------\nVerifies basic DMC operation\n\n2) DMC isn't working well enough to test further\n3) Starting DMC should reload length from $4013\n4) Writing $10 to $4015 should restart DMC if previous sample finished\n5) Writing $10 to $4015 should not affect DMC if previous sample is\nstill playing\n6) Writing $00 to $4015 should stop current sample\n7) Changing $4013 shouldn't affect current sample length\n8) Shouldn't set DMC IRQ flag when flag is disabled\n9) Should set IRQ flag when enabled and sample ends\n10) Reading IRQ flag shouldn't clear it\n11) Writing to $4015 should clear IRQ flag\n12) Disabling IRQ flag should clear it\n13) Looped sample shouldn't end until $00 is written to $4015\n14) Looped sample shouldn't ever set IRQ flag\n15) Clearing loop flag and then setting again shouldn't stop loop\n16) Clearing loop flag should end sample once it reaches end\n17) Looped sample should reload length from $4013 each time it reaches\nend\n18) $4013=0 should give 1-byte sample\n19) There should be a one-byte buffer that's filled immediately if empty\n\n\n8-dmc_rates\n-----------\nVerifies the DMC's 16 rates\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/apu/reset.txt",
    "content": "NES APU Reset Tests\n--------------------\nThese tests verify initial APU state at power, and the effect of reset.\n\n\n4015_cleared\n------------\nAt power and reset, $4015 is cleared.\n\n2) At power, $4015 should be cleared\n3) At reset, $4015 should be cleared\n\n\n4017_timing\n-----------\nAt power, it is as if $00 were written to $4017,\nthen a 9-12 clock delay, then execution from address\nin reset vector.\n\nAt reset, same as above, except last value written\nto $4017 is written again, rather than $00.\n\nThe delay from when $00 was written to $4017 is\nprinted. Delay after NES being powered off for a\nminute is usually 9.\n\n2) Frame IRQ flag should be set later after power/reset\n3) Frame IRQ flag should be set sooner after power/reset\n\n\n4017_written\n------------\nAt power, $4017 = $00.\nAt reset, $4017 mode is unchanged, but IRQ inhibit\nflag is sometimes cleared.\n\n2) At power, $4017 should be written with $00\n3) At reset, $4017 should should be rewritten with last value written\n\n\nirq_flag_cleared\n----------------\nAt power and reset, IRQ flag is clear.\n\n2) At power, flag should be clear\n3) At reset, flag should be clear\n\n\nlen_ctrs_enabled\n----------------\nAt power and reset, length counters are enabled.\n\n2) At power, length counters should be enabled\n3) At reset, length counters should be enabled, triangle unaffected\n\n\nworks_immediately\n-----------------\nAt power and reset, $4017, $4015, and length counters work\nimmediately.\n\n2) At power, writes should work immediately\n3) At reset, writes should work immediately\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/apu/tests.json",
    "content": "[\n  {\n    \"name\": \"clock_jitter\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"dmc_basics\",\n    \"frames\": [\n      {\n        \"number\": 30,\n        \"hash\": 11992147129660783512\n      }\n    ]\n  },\n  {\n    \"name\": \"dmc_dma_2007_read\",\n    \"frames\": [\n      {\n        \"number\": 30,\n        \"hash\": 11398877419118110174\n      }\n    ]\n  },\n  {\n    \"name\": \"dmc_dma_2007_write\",\n    \"frames\": [\n      {\n        \"number\": 35,\n        \"hash\": 10066428629761289066\n      }\n    ]\n  },\n  {\n    \"name\": \"dmc_dma_4016_read\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 12668862607518231523\n      }\n    ]\n  },\n  {\n    \"name\": \"dmc_dma_double_2007_read\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 3303528692302754891\n      }\n    ]\n  },\n  {\n    \"name\": \"dmc_dma_read_write_2007\",\n    \"frames\": [\n      {\n        \"number\": 25,\n        \"hash\": 10595698041155058010\n      }\n    ]\n  },\n  {\n    \"name\": \"dmc_rates\",\n    \"frames\": [\n      {\n        \"number\": 30,\n        \"hash\": 9588761239108989359\n      }\n    ]\n  },\n  {\n    \"name\": \"dpcmletterbox\",\n    \"frames\": [\n      {\n        \"number\": 10,\n        \"hash\": 9716310909309797997\n      }\n    ]\n  },\n  {\n    \"name\": \"irq_flag\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"irq_flag_timing\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"irq_timing\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"len_ctr\",\n    \"frames\": [\n      {\n        \"number\": 25,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"len_halt_timing\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"len_reload_timing\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"len_table\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"len_timing\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 14878161860519346933\n      }\n    ]\n  },\n  {\n    \"name\": \"len_timing_mode0\",\n    \"frames\": [\n      {\n        \"number\": 23,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"len_timing_mode1\",\n    \"frames\": [\n      {\n        \"number\": 23,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"reset_len_ctrs_enabled\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"action\": {\n          \"Reset\": \"Soft\"\n        }\n      },\n      {\n        \"number\": 40,\n        \"hash\": 16014198644488276031\n      }\n    ]\n  },\n  {\n    \"name\": \"reset_timing\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"test_1\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 12486489601112107272\n      }\n    ]\n  },\n  {\n    \"name\": \"test_2\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 12486489601112107272\n      }\n    ]\n  },\n  {\n    \"name\": \"test_3\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 12486489601112107272\n      }\n    ]\n  },\n  {\n    \"name\": \"test_4\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 12486489601112107272\n      }\n    ]\n  },\n  {\n    \"name\": \"test_5\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 12486489601112107272\n      }\n    ]\n  },\n  {\n    \"name\": \"test_6\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 12486489601112107272\n      }\n    ]\n  },\n  {\n    \"name\": \"test_7\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 12486489601112107272\n      }\n    ]\n  },\n  {\n    \"name\": \"test_8\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 12486489601112107272\n      }\n    ]\n  },\n  {\n    \"name\": \"test_9\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 12486489601112107272\n      }\n    ]\n  },\n  {\n    \"name\": \"test_10\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 12486489601112107272\n      }\n    ]\n  },\n  {\n    \"name\": \"pal_clock_jitter\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"SetNesRegion\": \"Pal\"\n        }\n      },\n      {\n        \"number\": 20,\n        \"hash\": 13813501042665481329\n      }\n    ]\n  },\n  {\n    \"name\": \"pal_irq_flag_timing\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"SetNesRegion\": \"Pal\"\n        }\n      },\n      {\n        \"number\": 20,\n        \"hash\": 15283534834975948636\n      }\n    ]\n  },\n  {\n    \"name\": \"pal_len_halt_timing\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"SetNesRegion\": \"Pal\"\n        }\n      },\n      {\n        \"number\": 20,\n        \"hash\": 3223047209937034573\n      }\n    ]\n  },\n  {\n    \"name\": \"pal_len_reload_timing\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"SetNesRegion\": \"Pal\"\n        }\n      },\n      {\n        \"number\": 20,\n        \"hash\": 14496598670261025093\n      }\n    ]\n  },\n  {\n    \"name\": \"pal_len_timing_mode0\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"SetNesRegion\": \"Pal\"\n        }\n      },\n      {\n        \"number\": 20,\n        \"hash\": 15415456214152333503\n      }\n    ]\n  },\n  {\n    \"name\": \"pal_len_timing_mode1\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"SetNesRegion\": \"Pal\"\n        }\n      },\n      {\n        \"number\": 20,\n        \"hash\": 16558599236285993505\n      }\n    ]\n  },\n  {\n    \"name\": \"reset_4015_cleared\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"action\": {\n          \"Reset\": \"Soft\"\n        }\n      },\n      {\n        \"number\": 40,\n        \"hash\": 6079776310151228335\n      }\n    ]\n  },\n  {\n    \"name\": \"reset_4017_timing\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"action\": {\n          \"Reset\": \"Soft\"\n        }\n      },\n      {\n        \"number\": 40,\n        \"hash\": 158964301043339763\n      }\n    ]\n  },\n  {\n    \"name\": \"reset_4017_written\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"action\": {\n          \"Reset\": \"Soft\"\n        }\n      },\n      {\n        \"number\": 35,\n        \"action\": {\n          \"Reset\": \"Soft\"\n        }\n      },\n      {\n        \"number\": 50,\n        \"hash\": 16485921128453261162\n      }\n    ]\n  },\n  {\n    \"name\": \"reset_irq_flag_cleared\",\n    \"frames\": [\n      {\n        \"number\": 15,\n        \"action\": {\n          \"Reset\": \"Soft\"\n        }\n      },\n      {\n        \"number\": 20,\n        \"hash\": 2054987767744312757\n      }\n    ]\n  },\n  {\n    \"name\": \"reset_works_immediately\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"action\": {\n          \"Reset\": \"Soft\"\n        }\n      },\n      {\n        \"number\": 30,\n        \"hash\": 15191020418456945175\n      }\n    ]\n  },\n  {\n    \"name\": \"pal_irq_flag\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"SetNesRegion\": \"Pal\"\n        }\n      },\n      {\n        \"number\": 20,\n        \"hash\": 13747417966407999092\n      }\n    ]\n  },\n  {\n    \"name\": \"pal_irq_timing\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"SetNesRegion\": \"Pal\"\n        }\n      },\n      {\n        \"number\": 20,\n        \"hash\": 7219648777380498854\n      }\n    ]\n  },\n  {\n    \"name\": \"pal_len_ctr\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"SetNesRegion\": \"Pal\"\n        }\n      },\n      {\n        \"number\": 25,\n        \"hash\": 6833095462741382586\n      }\n    ]\n  },\n  {\n    \"name\": \"pal_len_table\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"SetNesRegion\": \"Pal\"\n        }\n      },\n      {\n        \"number\": 20,\n        \"hash\": 2970567939215216479\n      }\n    ]\n  }\n]"
  },
  {
    "path": "tetanes-core/test_roms/apu/volumes.txt",
    "content": "Volume tests for NES\n\n_____________________________________________________________________\nBackground\n\nThe NES has four tone generator channels and one digital sample\nplayback channel:\n\n1. Rectangular pulse (\"square\") wave\n2. Another square wave\n3. 32-step triangle wave\n4. Binary noise generated by a linear feedback shift register\n5. Delta pulse code modulation; also allows writes of raw LPCM to\n   the counter for use as a generic 7-bit DAC\n\nUnlike systems such as the GBA, which digitally add all channels\nbefore passing them to a DAC, the NES has a separate DAC for each\nchannel and mixes them analog.  Nonlinearities in this mixing can\ncause one channel to affect the volume of another channel.\n\nThe NES uses an unsigned DAC, meaning that each channel can generate\nonly positive signal values.  Such a DAC generates a lot of DC, and\nthe NES has a high-pass filter on its audio output to block the DC.\nDifferent emulators use different time constants on their DC filters,\nwhich human listeners generally can't perceive.  So you can't just\nmeasure the maximum voltage; you have to measure the difference\nbetween the high and low values.\n\nDifferent emulators use different amounts of headroom in the 16-bit\nrange, depending on what Famicom expansion audio chips are present.\nSo you have to compare relative volumes, not absolute volumes.\n\n_____________________________________________________________________\nThe test pattern\n\nThis program demonstrates the channel balance among implementations\nof the NES architecture.  \n\nThe pattern consists of a set of 12 tones, as close to 1000 Hz as\nthe NES allows:\n1. Channel 1, 1/8 duty\n2. Channel 1, 1/4 duty\n3. Channel 1, 1/2 duty\n4. Channel 1, 3/4 duty\n5. Channels 1 and 2, 1/8 duty\n6. Channels 1 and 2, 1/4 duty\n7. Channels 1 and 2, 1/2 duty\n8. Channels 1 and 2, 3/4 duty\n9. Channel 3\n10. Channel 4, long LFSR period\n11. Channel 4, short LFSR period\n12. Channel 5, amplitude 30\n\nWhen the user presses A on controller 1, the pattern plays three\ntimes, with channel 5 held steady at 0, 48, and 96.  The high point\nof tone 12 each time is 30 units above the level for that time,\nthat is, 30, 78, and 126 respectively.\n\n_____________________________________________________________________\nRecordings\n\nThe files in the 'recordings' folder are recordings of volumes.nes\nrun in various environments.  They were recorded at 44100 Hz and then\nencoded using OggDropXPd 1.90 (libvorbis 1.2.0) at -q 7.00.\n\n* nes-001.ogg: Nintendo Entertainment System (NTSC U/C) with PowerPak\n* nestopia.ogg: Nestopia 1.40\n* nintendulator.ogg: Nintendulator snapshot 2009-02-28\n* fceux.ogg: FCEUX 2.0.4-interim 2008-11-24\n\n_____________________________________________________________________\nLegal\n\nCopyright (c) 2009 Damian Yerrick\n\nThe program and manual are under the following license:\n\n  This work is provided 'as-is', without any express or implied\n  warranty. In no event will the authors be held liable for any\n  damages arising from the use of this work.\n\n  Permission is granted to anyone to use this work for any\n  purpose, including commercial applications, and to alter it and\n  redistribute it freely, subject to the following restrictions:\n\n   1. The origin of this work must not be misrepresented; you\n      must not claim that you wrote the original work. If you use\n      this work in a product, an acknowledgment in the product\n      documentation would be appreciated but is not required.\n   2. Altered source versions must be plainly marked as such,\n      and must not be misrepresented as being the original work.\n   3. This notice may not be removed or altered from any\n      source distribution.\n\n  The term \"source\" refers to the preferred form of a work for making\n  changes to it. \n\n"
  },
  {
    "path": "tetanes-core/test_roms/cpu/branch.txt",
    "content": "NES 6502 Branch Timing Test ROMs\n--------------------------------\nThese ROMs test timing of the branch instruction, including edge cases\nwhich an emulator might get wrong. When run on a NES they all give a\npassing result. Each ROM runs several tests and reports the result on\nscreen and by beeping a number of times. See below for the meaning of\nfailure codes for each test. THE TESTS MUST BE RUN (*AND* *PASS*) IN\nORDER, because some earlier ROMs test things that later ones assume will\nwork properly.\n\nSource code for each test is included, and most tests are clearly\ndivided into sections. Support code is also included, but it runs on a\ncustom devcart and assembler so it will require some effort to assemble.\nContact me if you'd like assistance porting them to your setup.\n\n\nBranch Timing Summary\n---------------------\nAn untaken branch takes 2 clocks. A taken branch takes 3 clocks. A taken\nbranch that crosses a page takes 4 clocks. Page crossing occurs when the\nhigh byte of the branch target address is different than the high byte\nof address of the next instruction:\n\nbranch_target:\n\t...\n\tbne branch_target\nnext_instruction:\n\tnop\n\t...\nbranch_target:\n\n\n1.Branch_Basics\n---------------\nTests branch timing basics and PPU NMI timing, which is needed for the\ntests\n\n2) NMI period is too short\n3) NMI period is too too long\n4) Branch not taken is too long\n5) Branch not taken is too short\n6) Branch taken is too long\n7) Branch taken is too short\n\n\n2.Backward_Branch\n-----------------\nTests backward (negative) branch timing.\n\n2) Branch from $E4FD to $E4FC is too long\n3) Branch from $E4FD to $E4FC is too short\n4) Branch from $E5FE to $E5FD is too long\n5) Branch from $E5FE to $E5FD is too short\n6) Branch from $E700 to $E6FF is too long\n7) Branch from $E700 to $E6FF is too short\n8) Branch from $E801 to $E800 is too long\n9) Branch from $E801 to $E800 is too short\n\n\n3.Forward_Branch\n----------------\nTests forward (positive) branch timing.\n\n2) Branch from $E5FC to $E5FF is too long\n3) Branch from $E5FC to $E5FF is too short\n4) Branch from $E6FD to $E700 is too long\n5) Branch from $E6FD to $E700 is too short\n6) Branch from $E7FE to $E801 is too long\n7) Branch from $E7FE to $E801 is too short\n8) Branch from $E8FF to $E902 is too long\n9) Branch from $E8FF to $E902 is too short\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/cpu/dummy_writes.txt",
    "content": "NES Double-Write Behavior Tests\n----------------------------------\nThese tests verify that the CPU is doing double-writes properly.\n\nDouble-write is a side effect of the NES CPU when it is executing\na read-modify-write instruction: It first reads the original value,\nthen writes back the same value, and then writes the modified value.\n\n\nFor example, the cycle by cycle listing of an absolute-addressing\ninstruction such as INC is as follows\n(from 65doc.txt by John West and Marko Mkel):\n\n     Read-Modify-Write instructions (ASL, LSR, ROL, ROR, INC, DEC,\n                                     SLO, SRE, RLA, RRA, ISB, DCP)\n\n        #  address R/W description\n       --- ------- --- ------------------------------------------\n        1    PC     R  fetch opcode, increment PC\n        2    PC     R  fetch low byte of address, increment PC\n        3    PC     R  fetch high byte of address, increment PC\n        4  address  R  read from effective address\n        5  address  W  write the value back to effective address,\n                       and do the operation on it\n        6  address  W  write the new value to effective address\n\n\nTwo sets of tests are provided:\nOne that uses OAM data ($2004) for testing, and one that\nuses PPU memory ($2007).\n\nThe OAM data testing is only valid on emulators. The actual NES console\nfails the test, because the OAM read port is not reliable on the real\nconsole.\n\nThe PPUMEM test can be used on emulators and on the real NES.\n\nThe PPUMEM test requires that the emulator implements open bus behavior\nproperly. Without open bus behavior the testing will not work as expected.\nBecause of that, an extensive set of tests is first performed for\nthe open bus behavior.\n\nTests in the OAM version:\n\n\t #2: OAM reading is too unreliable.\n\t #3: Writes to OAM should automatically increment SPRADDR\n\t #4: Reads from OAM should not automatically increment SPRADDR\n\t #5: Some opcodes failed the test.\n\t #6: OAM reads are unreliable.\n\t #7: ROM should not be writable.\n\nFail codes #2 and #6 are basically the same thing, except #2 is\ngiven if #5 also fails. If #5 passes, but the OAM read test\nfailed, #6 is given instead.\n\nExpected output in the OAM version:\n\tTEST: cpu_dummy_writes_oam\n\tThis program verifies that the\n\tCPU does 2x writes properly.\n\tAny read-modify-write opcode\n\tshould first write the origi-\n\tnal value; then the calculated\n\tvalue exactly 1 cycle later.\n\n\tRequirement: OAM memory reads\n\tMUST be reliable. This is\n\toften the case on emulators,\n\tbut NOT on the real NES.\n\tNevertheless, this test can be\n\tused to see if the CPU in the\n\temulator is built properly.\n\n\tTesting OAM.  The screen will go blank for a moment now.\n\tOK; Verifying opcodes...\n\t0E2E4E6ECEEE 1E3E5E7EDEFE \n\t0F2F4F6FCFEF 1F3F5F7FDFFF \n\t03234363C3E3 13335373D3F3 \n\t1B3B5B7BDBFB              \n\n\tPassed\n\nTests in the PPUMEM version:\n\n\t#2: Non-palette PPU memory reads should have one-byte buffer\n\t#3: A single write to $2005 must not change the address used by $2007 when vblank is on.\n\t#4: Even two writes to $2005 must not change the address used by $2007 when vblank is on.\n\t#5: A single write to $2006 must not change the address used by $2007 when vblank is on.\n\t#6: A single write to $2005 must change the address toggle for both $2005 and $2006.\n\t#7: Sequential PPU memory read does not work\n\t#8: Sequential PPU memory write does not work\n\t#9: Some opcodes failed the test.\n\t#10: Open bus behavior is wrong.\n\t#11: ROM should not be writable.\n\nExpected output in the PPUMEM version:\n\n\tTEST: cpu_dummy_writes_ppumem\n\tThis program verifies that the\n\tCPU does 2x writes properly.\n\tAny read-modify-write opcode\n\tshould first write the origi-\n\tnal value; then the calculated\n\tvalue exactly 1 cycle later.\n\n\tVerifying open bus behavior.\n\t      W- W- WR W- W- W- W- WR\n\t2000+ 0  1  2  3  4  5  6  7 \n\t  R0: 0- 0- 00 0- 0- 0- 0- 00\n\t  R1: 0- 0- 00 0- 0- 0- 0- 00\n\t  R3: 0- 0- 00 0- 0- 0- 0- 00\n\t  R5: 0- 0- 00 0- 0- 0- 0- 00\n\t  R6: 0- 0- 00 0- 0- 0- 0- 00\n\tOK; Verifying opcodes...\n\t0E2E4E6ECEEE 1E3E5E7EDEFE \n\t0F2F4F6FCFEF 1F3F5F7FDFFF \n\t03234363C3E3 13335373D3F3 \n\t1B3B5B7BDBFB              \n\n\tPassed\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe text output may include ANSI color codes, which take the form of\nan esc character ($1B), an opening bracket ('['), and a sequence of\nnumbers and semicolon characters, terminated by a non-digit character ('m').\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\n-- \nShay Green <gblargg@gmail.com>\nJoel Yliluoma <bisqwit@iki.fi>\n"
  },
  {
    "path": "tetanes-core/test_roms/cpu/exec_space.txt",
    "content": "NES Memory Execution Tests\n----------------------------------\nThese tests verify that the CPU can execute code from any possible\nmemory location, even if that is mapped as I/O space.\n\nIn addition, two obscure side effects are tested:\n\n1. The PPU open bus. Any write to PPU will update the open bus.\n   Reading from 2002 updates the low 5 bits. Reading from 2007\n   updates 8 bits. The open bus is shown in any addresss/bit\n   that the PPU does not write to. Read from 2000, you get open bus.\n   Read from 2006, ditto. Read from 2002, you get that in high 3 bits.\n   Additionally, the open bus decays automatically to zero in about one\n   second if not refreshed.\n   This test requires that a value written to $2003 can be read back\n   from $2001 within a time window of one or two frames.\n\n2. One-byte opcodes must issue a dummy read to the byte immediately\n   following that opcode. The CPU always does a fetch of the second\n   byte, before it has even begun executing the opcode in the first\n   place.\n\nAdditionally, the following PPU features must be working properly:\n\n1. PPU memory writes and reads through $2006/$2007\n2. The address high/low toggle reset at $2002\n3. A single write through $2006 must not affect the address used by $2007\n4. NMI should fire sometimes to salvage a broken program, if the JSR/JMP\n   never reaches its intended destination. (Only required in the\n   test IF the CPU and/or open bus are not working properly.)\n\nThe test is done FIVE times: Once with JSR $2001, again with JMP $2001,\nand then with RTS (with target address of $2001), and then with a JMP\nthat expects to return with an RTI opcode. Finally, with a regular\nJSR, but the return from the code is done through a BRK instruction.\n\nTests and results:\n\n\t #2: PPU memory access through $2007 does not work properly. (Use other tests to determine the exact problem.)\n\t #3: PPU open bus implementation is missing or incomplete: A write to $2003, followed by a read from $2001 should return the same value as was written.\n\t #4: The RTS at $2001 was never executed. (If NMI has not been implemented in the emulator, the symptom of this failure is that the program crashes and does not output either \"Fail\" nor \"Passed\").\n\t #5: An RTS opcode should still do a dummy fetch of the next opcode.  (The same goes for all one-byte opcodes, really.)\n\t #6: I have no idea what happened, but the test did not work as supposed to. In any case, the problem is in the PPU.\n\t #7: A jump to $2001 should never execute code from $8001 / $9001 / $A001 / $B001 / $C001 / $D001 / $E001.\n\t #8: Okay, the test passed when JSR was used, but NOT when the opcode was JMP. I definitely did not think any emulator would trigger this result.\n\t #9: Your PPU is broken in mind-defyingly random ways.\n\t #10: RTS to $2001 never returned. This message never gets displayed.\n\t #11: The test passed when JSR was used, and when JMP was used, but NOT when RTS was used. Caught ya! Paranoia wins.\n\t #12: Your PPU gave up reason at the last moment.\n\t #13: JMP to $2001 never returned. Again, this message never gets displayed.\n\t #14: An RTI opcode should still do a dummy fetch of the next opcode.  (The same goes for all one-byte opcodes, really.)\n\t #15: An RTI opcode should not destroy the PPU. Somehow that still appears to be the case here.\n\t #16: IRQ occurred uncalled\n\t #17: JSR to $2001 never returned. (Never displayed)\n\t #18: The BRK instruction should issue an automatic fetch of the byte that follows right after the BRK. (The same goes for all one-byte opcodes, but with BRK it should be a bit more obvious than with others.)\n\t #19: A BRK opcode should not destroy the PPU. Somehow that still appears to be the case here.\n\t \n\nExpected output:\n\tTEST:test_cpu_exec_space_ppuio\n\tThis program verifies that the\n\tCPU can execute code from any\n\tpossible location that it can\n\taddress, including I/O space.\n\n\tIn addition, it will be tested\n\tthat an RTS instruction does a\n\tdummy read of the byte that\n\timmediately follows the\n\tinstructions.\n\n\tJSR test OK\n\tJMP test OK\n\tRTS test OK\n\tJMP+RTI test OK\n\tBRK test OK\n\n\tPassed\n\nExpected output in the other test:\n\n\tTEST: test_cpu_exec_space_apu\n\tThis program verifies that the\n\tCPU can execute code from any\n\tpossible location that it can\n\taddress, including I/O space.\n\t\n\tIn this test, it is also\n\tverified that not only all\n\twrite-only APU I/O ports\n\treturn the open bus, but\n\talso the unallocated I/O\n\tspace in $4018..$40FF.\n\t\n\t40FF\n\tPassed\n\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe text output may include ANSI color codes, which take the form of\nan esc character ($1B), an opening bracket ('['), and a sequence of\nnumbers and semicolon characters, terminated by a non-digit character ('m').\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\n-- \nShay Green <gblargg@gmail.com>\nJoel Yliluoma <bisqwit@iki.fi>\n"
  },
  {
    "path": "tetanes-core/test_roms/cpu/instr.txt",
    "content": "NES CPU Instruction Behavior Tests\n----------------------------------\nThese tests verify most instruction behavior fairly thoroughly,\nincluding unofficial instructions. Failing instructions are listed by\ntheir opcode and name. Serious errors in behavior of basic opcodes might\ncause many false errors. These tests will NOT help you figure out what\nis wrong with your implementation of the failed instructions, simply\nwhether you are failing any, and which those are.\n\nall_instrs.nes tests (almost) all instructions, including unofficial\nones, while official_only.nes tests only official (\"documented\")\ninstructions. The *_singles/ test all instructions, but test the\nofficial ones first, so you can tell whether you pass those even if your\nemulator hangs on the unofficial ones.\n\nThe nsf_singles builds audibly report the opcodes of any failed\ninstructions before the final result.\n\n\nInternal operation\n------------------\nInstructions are tested by setting many combinations of input values for\nregisters, flags, and memory, running the instruction under test, then\nupdating a running checksum with the resulting values. After trying all\ninteresting input combinations, the checksum is compared with the\ncorrect one to find whether the instruction passed.\n\nThis approach is used for all instructions, even those that shouldn't\ncare the value of any registers or modify them. This catches an emulator\nincorrectly looking at or modifying registers in those instructions.\n\nThis approach makes it very easy to write the tests, since the\ninstructions don't have to be each coded for separately; instead, only\nthe different addressing modes need separate tests.\n\ninstrs: what opcodes to test, along with their names.\n\ninstr_template: template for instructions. First byte is replaced with\nopcode. After executing instruction and anything after, it should jump\nto instr_done.\n\noperand: where to place byte operand for instruction. This value comes\nfrom the table of values to test, using an index separate from that used\nto set the other registers before executing the instruction.\n\nset_in: things to execute before the instruction. On entry, A is value\nput in operand, and Y is index used in table.\n\ncheck_out: things to execute after the instruction.\n\nvalues2: if defined, set of values to use for operand. Default uses same\nset as for other registers.\n\ntest_values: routine to actually run the tests. test_normal does what's\ndescribed above.\n\ncorrect_checksums: list of checksums for each instruction. Generated\nwhen CALIBRATE=1 is uncommented.\n\n\nInstructions\n------------\nU = Unofficial\nX = Freezes CPU, so not tested\n? = Inconsistent/unknown behavior, so not tested\n\n00   BRK #n\n01   ORA (z,X)\n02 X KIL\n03 U SLO (z,X)\n04 U DOP z\n05   ORA z\n06   ASL z\n07 U SLO z\n08   PHP\n09   ORA #n\n0A   ASL A\n0B U AAC #n\n0C U TOP abs\n0D   ORA a\n0E   ASL a\n0F U SLO abs\n10   BPL r\n11   ORA (z),Y\n12 X KIL\n13 U SLO (z),Y\n14 U DOP z,X\n15   ORA z,X\n16   ASL z,X\n17 U SLO z,X\n18   CLC\n19   ORA a,Y\n1A U NOP\n1B U SLO abs,Y\n1C U TOP abs,X\n1D   ORA a,X\n1E   ASL a,X\n1F U SLO abs,X\n20   JSR a\n21   AND (z,X)\n22 X KIL\n23 U RLA (z,X)\n24   BIT z\n25   AND z\n26   ROL z\n27 U RLA z\n28   PLP\n29   AND #n\n2A   ROL A\n2B U AAC #n\n2C   BIT a\n2D   AND a\n2E   ROL a\n2F U RLA abs\n30   BMI r\n31   AND (z),Y\n32 X KIL\n33 U RLA (z),Y\n34 U DOP z,X\n35   AND z,X\n36   ROL z,X\n37 U RLA z,X\n38   SEC\n39   AND a,Y\n3A U NOP\n3B U RLA abs,Y\n3C U TOP abs,X\n3D   AND a,X\n3E   ROL a,X\n3F U RLA abs,X\n40   RTI\n41   EOR (z,X)\n42 X KIL\n43 U SRE (z,X)\n44 U DOP z\n45   EOR z\n46   LSR z\n47 U SRE z\n48   PHA\n49   EOR #n\n4A   LSR A\n4B U ASR #n\n4C   JMP a\n4D   EOR a\n4E   LSR a\n4F U SRE abs\n50   BVC r\n51   EOR (z),Y\n52 X KIL\n53 U SRE (z),Y\n54 U DOP z,X\n55   EOR z,X\n56   LSR z,X\n57 U SRE z,X\n58   CLI\n59   EOR a,Y\n5A U NOP\n5B U SRE abs,Y\n5C U TOP abs,X\n5D   EOR a,X\n5E   LSR a,X\n5F U SRE abs,X\n60   RTS\n61   ADC (z,X)\n62 X KIL\n63 U RRA (z,X)\n64 U DOP z\n65   ADC z\n66   ROR z\n67 U RRA z\n68   PLA\n69   ADC #n\n6A   ROR A\n6B U ARR #n\n6C   JMP (a)\n6D   ADC a\n6E   ROR a\n6F U RRA abs\n70   BVS r\n71   ADC (z),Y\n72 X KIL\n73 U RRA (z),Y\n74 U DOP z,X\n75   ADC z,X\n76   ROR z,X\n77 U RRA z,X\n78   SEI\n79   ADC a,Y\n7A U NOP\n7B U RRA abs,Y\n7C U TOP abs,X\n7D   ADC a,X\n7E   ROR a,X\n7F U RRA abs,X\n80 U DOP #n\n81   STA (z,X)\n82 U DOP #n\n83 U AAX (z,X)\n84   STY z\n85   STA z\n86   STX z\n87 U AAX z\n88   DEY\n89 U DOP #n\n8A   TXA\n8B ? XAA #n\n8C   STY a\n8D   STA a\n8E   STX a\n8F U AAX abs\n90   BCC r\n91   STA (z),Y\n92 X KIL\n93 ? AXA (z),Y\n94   STY z,X\n95   STA z,X\n96   STX z,Y\n97 U AAX z,Y\n98   TYA\n99   STA a,Y\n9A   TXS\n9B ? XAS abs,Y\n9C U SYA abs,X\n9D   STA a,X\n9E U SXA abs,Y\n9F ? AXA abs,Y\nA0   LDY #n\nA1   LDA (z,X)\nA2   LDX #n\nA3 U LAX (z,X)\nA4   LDY z\nA5   LDA z\nA6   LDX z\nA7 U LAX z\nA8   TAY\nA9   LDA #n\nAA   TAX\nAB U ATX #n\nAC   LDY a\nAD   LDA a\nAE   LDX a\nAF U LAX abs\nB0   BCS r\nB1   LDA (z),Y\nB2 X KIL\nB3 U LAX (z),Y\nB4   LDY z,X\nB5   LDA z,X\nB6   LDX z,Y\nB7 U LAX z,Y\nB8   CLV\nB9   LDA a,Y\nBA   TSX\nBB ? LAR abs,Y\nBC   LDY a,X\nBD   LDA a,X\nBE   LDX a,Y\nBF U LAX abs,Y\nC0   CPY #n\nC1   CMP (z,X)\nC2 U DOP #n\nC3 U DCP (z,X)\nC4   CPY z\nC5   CMP z\nC6   DEC z\nC7 U DCP z\nC8   INY\nC9   CMP #n\nCA   DEX\nCB U AXS #n\nCC   CPY a\nCD   CMP a\nCE   DEC a\nCF U DCP abs\nD0   BNE r\nD1   CMP (z),Y\nD2 X KIL\nD3 U DCP (z),Y\nD4 U DOP z,X\nD5   CMP z,X\nD6   DEC z,X\nD7 U DCP z,X\nD8   CLD\nD9   CMP a,Y\nDA U NOP\nDB U DCP abs,Y\nDC U TOP abs,X\nDD   CMP a,X\nDE   DEC a,X\nDF U DCP abs,X\nE0   CPX #n\nE1   SBC (z,X)\nE2 U DOP #n\nE3 U ISC (z,X)\nE4   CPX z\nE5   SBC z\nE6   INC z\nE7 U ISC z\nE8   INX\nE9   SBC #n\nEA   NOP\nEB U SBC #n\nEC   CPX a\nED   SBC a\nEE   INC a\nEF U ISC abs\nF0   BEQ r\nF1   SBC (z),Y\nF2 X KIL\nF3 U ISC (z),Y\nF4 U DOP z,X\nF5   SBC z,X\nF6   INC z,X\nF7 U ISC z,X\nF8   SED\nF9   SBC a,Y\nFA U NOP\nFB U ISC abs,Y\nFC U TOP abs,X\nFD   SBC a,X\nFE   INC a,X\nFF U ISC abs,X\n\n\nMulti-tests\n-----------\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\nWhen run, they list the subtests as they are run. The final result code\nrefers to the first sub-test that failed. For more information about any\nfailed subtests, run them individually from rom_singles/ and\nnsf_singles/.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/cpu/instr_misc.txt",
    "content": "NES CPU Instruction Behavior Misc Tests\n----------------------------------------\nThese tests verify miscellaneous instruction behavior.\n\n\n01-abs_x_wrap\n-------------\nVerifies that $FFFF wraps around to 0 for STA abs,X and LDA abs,X.\n\n\n02-branch_wrap\n--------------\nVerifies that branching past end or before beginning of RAM wraps\naround.\n\n\n03-dummy_reads\n--------------\nTests some instructions that do dummy reads before the real read/write.\nDoesn't test all instructions.\n\nTests LDA and STA with modes (ZP,X), (ZP),Y and ABS,X\nDummy reads for the following cases are tested:\n\nLDA ABS,X or (ZP),Y when carry is generated from low byte\nSTA ABS,X or (ZP),Y\nROL ABS,X always\n\n\n04-dummy_reads_apu\n------------------\nTests dummy reads for (hopefully) ALL instructions which do them,\nincluding unofficial ones. Prints opcode(s) of failed instructions.\nRequires that APU implement $4015 IRQ flag reading.\n\n\nMulti-tests\n-----------\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\nWhen run, they list the subtests as they are run. The final result code\nrefers to the first sub-test that failed. For more information about any\nfailed subtests, run them individually from rom_singles/ and\nnsf_singles/.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nText output\n-----------\nTests generally print information on screen, but also output information\nin other ways, in case the PPU doesn't work or there isn't one, as in an\nNSF or a NES emulator early in development.\n\nWhen building as an NSF, the final result is reported as a series of\nbeeps (see below). Any important diagnostic bytes are also reported as\nbeeps, before the final result.\n\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\nSee the source code for more information about a particular test and why\nit might be failing. Each test has comments anout its operation.\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\nIn addition to the other text output methods described above, NSF builds\nreport essential information bytes audibly, including the final result.\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason as listed in the\nsource code by the corresponding set_code line. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/cpu/instr_timing.txt",
    "content": "NES CPU Instruction Timing Test\n-------------------------------\nThese tests verify timing of all NES CPU instructions, except the 12\nthat freeze the CPU.\n\nThe individual tests report the opcode of any failed instructions.\ninstr_timing prints the measured and correct times. branch_timing runs\nthe branch instruction in 8 different situations: four not taken, and\nfour taken. For each of these four, the first two are for a\nnon-page-cross both negative and positive, and the second two cross a\npage. The correct times are 2 2 2 2 3 3 4 4.\n\n\nRequirements\n------------\n- Basic CPU instruction behavior\n- Basic APU length counter operation\n\n\nInternal operation\n------------------\nEach instruction is timed by setting up appropriate conditions,\nsynchronizing to the APU length counter and then loading it with 2,\nexecuting the instruction in a loop that stops once the length counter\nexpires. The number of loop iterations indicates how many clocks the\ninstruction took.\n\nMulti-tests\n-----------\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\nWhen run, they list the subtests as they are run. The final result code\nrefers to the first sub-test that failed. For more information about any\nfailed subtests, run them individually from rom_singles/ and\nnsf_singles/.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/cpu/interrupts.txt",
    "content": "NES CPU Interrupt Tests\n-----------------------\nTests behavior and timing of CPU in the presence of interrupts, both IRQ\nand NMI.\n\n\nCLI Latency Summary\n-------------------\nThe RTI instruction affects IRQ inhibition immediately. If an IRQ is\npending and an RTI is executed that clears the I flag, the CPU will\ninvoke the IRQ handler immediately after RTI finishes executing.\n\nThe CLI, SEI, and PLP instructions effectively delay changes to the I\nflag until after the next instruction. For example, if an interrupt is\npending and the I flag is currently set, executing CLI will execute the\nnext instruction before the CPU invokes the IRQ handler. This delay only\naffects inhibition, not the value of the I flag itself; CLI followed by\nPHP will leave the I flag cleared in the saved status byte on the stack\n(bit 2), as expected.\n\n\n1-cli_latency\n-------------\nTests the delay in CLI taking effect, and some basic aspects of IRQ\nhandling and the APU frame IRQ (needed by the tests). It uses the APU's\nframe IRQ and first verifies that it works well enough for the tests.\n\nThe later tests execute CLI followed by SEI and equivalent pairs of\ninstructions (CLI, PLP, where the PLP sets the I flag). These should\nonly allow at most one invocation of the IRQ handler, even if it doesn't\nacknowledge the source of the IRQ. RTI is also tested, which behaves\ndifferently. These tests also *don't* disable interrupts after the first\nIRQ, in order to test whether a pair of instructions allows only one\ninterrupt or causes continuous interrupts that block the main code from\ncontinuing.\n\n2) RTI should not adjust return address (as RTS does)\n3) APU should generate IRQ when $4017 = $00\n4) Exactly one instruction after CLI should execute before IRQ is taken\n5) CLI SEI should allow only one IRQ just after SEI\n6) In IRQ allowed by CLI SEI, I flag should be set in saved status flags\n7) CLI PLP should allow only one IRQ just after PLP\n8) PLP SEI should allow only one IRQ just after SEI\n9) PLP PLP should allow only one IRQ just after PLP\n10) CLI RTI should not allow any IRQs\n11) Unacknowledged IRQ shouldn't let any mainline code run\n12) RTI RTI shouldn't let any mainline code run\n\n\n2-nmi_and_brk\n-------------\nNMI behavior when it interrupts BRK. Occasionally fails on\nNES due to PPU-CPU synchronization.\n\nResult when run:\nNMI BRK --\n27  36  00 NMI before CLC\n26  36  00 NMI after CLC\n26  36  00 \n36  00  00 NMI interrupting BRK, with B bit set on stack\n36  00  00 \n36  00  00 \n36  00  00 \n36  00  00 \n27  36  00 NMI after SEC at beginning of IRQ handler\n27  36  00 \n\n\n3-nmi_and_irq\n-------------\nNMI behavior when it interrupts IRQ vectoring.\n\nResult when run:\nNMI IRQ\n23  00 NMI occurs before LDA #1\n21  00 NMI occurs after LDA #1 (Z flag clear)\n21  00\n20  00 NMI occurs after CLC, interrupting IRQ\n20  00\n20  00\n20  00\n20  00\n20  00\n20  00 Same result for 7 clocks before IRQ is vectored\n25  20 IRQ occurs, then NMI occurs after SEC in IRQ handler\n25  20\n\n\n4-irq_and_dma\n-------------\nHas IRQ occur at various times around sprite DMA.\nFirst column refers to what instruction IRQ occurred\nafter. Second column is time of IRQ, in CPU clocks relative\nto some arbitrary starting point.\n\n0 +0\n1 +1\n1 +2\n2 +3\n2 +4\n4 +5\n4 +6\n7 +7\n7 +8\n7 +9\n7 +10\n8 +11\n8 +12\n8 +13\n...\n8 +524\n8 +525\n8 +526\n9 +527\n\n\n5-branch_delays_irq\n-------------------\nA taken non-page-crossing branch ignores IRQ during\nits last clock, so that next instruction executes\nbefore the IRQ. Other instructions would execute the\nNMI before the next instruction.\n\nThe same occurs for NMI, though that's not tested here.\n\ntest_jmp\nT+ CK PC\n00 02 04 NOP\n01 01 04 \n02 03 07 JMP\n03 02 07 \n04 01 07 \n05 02 08 NOP\n06 01 08 \n07 03 08 JMP\n08 02 08 \n09 01 08 \n\ntest_branch_not_taken\nT+ CK PC\n00 02 04 CLC\n01 01 04 \n02 02 06 BCS\n03 01 06 \n04 02 07 NOP\n05 01 07 \n06 04 0A JMP\n07 03 0A \n08 02 0A \n09 01 0A JMP\n\ntest_branch_taken_pagecross\nT+ CK PC\n00 02 0D CLC\n01 01 0D \n02 04 00 BCC\n03 03 00 \n04 02 00 \n05 01 00 \n06 04 03 LDA $100\n07 03 03 \n08 02 03 \n09 01 03 \n\ntest_branch_taken\nT+ CK PC\n00 02 04 CLC\n01 01 04 \n02 03 07 BCC\n03 02 07 \n04 05 0A LDA $100 *** This is the special case\n05 04 0A \n06 03 0A \n07 02 0A \n08 01 0A \n09 03 0A JMP\n\nMulti-tests\n-----------\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\nWhen run, they list the subtests as they are run. The final result code\nrefers to the first sub-test that failed. For more information about any\nfailed subtests, run them individually from rom_singles/ and\nnsf_singles/.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/cpu/nestest.txt",
    "content": "$C000:4C F5 C5  JMP $C5F5                          A:00 X:00 Y:00 P:nvUbdIzc SP:FD PPU:  0,  0 CYC:7\n$C5F5:A2 00     LDX #$00                           A:00 X:00 Y:00 P:nvUbdIzc SP:FD PPU:  9,  0 CYC:10\n$C5F7:86 00     STX $00 = #$00                     A:00 X:00 Y:00 P:nvUbdIZc SP:FD PPU: 15,  0 CYC:12\n$C5F9:86 10     STX $10 = #$00                     A:00 X:00 Y:00 P:nvUbdIZc SP:FD PPU: 24,  0 CYC:15\n$C5FB:86 11     STX $11 = #$00                     A:00 X:00 Y:00 P:nvUbdIZc SP:FD PPU: 33,  0 CYC:18\n$C5FD:20 2D C7  JSR $C72D                          A:00 X:00 Y:00 P:nvUbdIZc SP:FD PPU: 42,  0 CYC:21\n$C72D:EA        NOP                                A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 60,  0 CYC:27\n$C72E:38        SEC                                A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 66,  0 CYC:29\n$C72F:B0 04     BCS $C735                          A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU: 72,  0 CYC:31\n$C735:EA        NOP                                A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU: 81,  0 CYC:34\n$C736:18        CLC                                A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU: 87,  0 CYC:36\n$C737:B0 03     BCS $C73C                          A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 93,  0 CYC:38\n$C739:4C 40 C7  JMP $C740                          A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 99,  0 CYC:40\n$C740:EA        NOP                                A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU:108,  0 CYC:43\n$C741:38        SEC                                A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU:114,  0 CYC:45\n$C742:90 03     BCC $C747                          A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU:120,  0 CYC:47\n$C744:4C 4B C7  JMP $C74B                          A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU:126,  0 CYC:49\n$C74B:EA        NOP                                A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU:135,  0 CYC:52\n$C74C:18        CLC                                A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU:141,  0 CYC:54\n$C74D:90 04     BCC $C753                          A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU:147,  0 CYC:56\n$C753:EA        NOP                                A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU:156,  0 CYC:59\n$C754:A9 00     LDA #$00                           A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU:162,  0 CYC:61\n$C756:F0 04     BEQ $C75C                          A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU:168,  0 CYC:63\n$C75C:EA        NOP                                A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU:177,  0 CYC:66\n$C75D:A9 40     LDA #$40                           A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU:183,  0 CYC:68\n$C75F:F0 03     BEQ $C764                          A:40 X:00 Y:00 P:nvUbdIzc SP:FB PPU:189,  0 CYC:70\n$C761:4C 68 C7  JMP $C768                          A:40 X:00 Y:00 P:nvUbdIzc SP:FB PPU:195,  0 CYC:72\n$C768:EA        NOP                                A:40 X:00 Y:00 P:nvUbdIzc SP:FB PPU:204,  0 CYC:75\n$C769:A9 40     LDA #$40                           A:40 X:00 Y:00 P:nvUbdIzc SP:FB PPU:210,  0 CYC:77\n$C76B:D0 04     BNE $C771                          A:40 X:00 Y:00 P:nvUbdIzc SP:FB PPU:216,  0 CYC:79\n$C771:EA        NOP                                A:40 X:00 Y:00 P:nvUbdIzc SP:FB PPU:225,  0 CYC:82\n$C772:A9 00     LDA #$00                           A:40 X:00 Y:00 P:nvUbdIzc SP:FB PPU:231,  0 CYC:84\n$C774:D0 03     BNE $C779                          A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU:237,  0 CYC:86\n$C776:4C 7D C7  JMP $C77D                          A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU:243,  0 CYC:88\n$C77D:EA        NOP                                A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU:252,  0 CYC:91\n$C77E:A9 FF     LDA #$FF                           A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU:258,  0 CYC:93\n$C780:85 01     STA $01 = #$00                     A:FF X:00 Y:00 P:NvUbdIzc SP:FB PPU:264,  0 CYC:95\n$C782:24 01     BIT $01 = #$FF                     A:FF X:00 Y:00 P:NvUbdIzc SP:FB PPU:273,  0 CYC:98\n$C784:70 04     BVS $C78A                          A:FF X:00 Y:00 P:NVUbdIzc SP:FB PPU:282,  0 CYC:101\n$C78A:EA        NOP                                A:FF X:00 Y:00 P:NVUbdIzc SP:FB PPU:291,  0 CYC:104\n$C78B:24 01     BIT $01 = #$FF                     A:FF X:00 Y:00 P:NVUbdIzc SP:FB PPU:297,  0 CYC:106\n$C78D:50 03     BVC $C792                          A:FF X:00 Y:00 P:NVUbdIzc SP:FB PPU:306,  0 CYC:109\n$C78F:4C 96 C7  JMP $C796                          A:FF X:00 Y:00 P:NVUbdIzc SP:FB PPU:312,  0 CYC:111\n$C796:EA        NOP                                A:FF X:00 Y:00 P:NVUbdIzc SP:FB PPU:321,  0 CYC:114\n$C797:A9 00     LDA #$00                           A:FF X:00 Y:00 P:NVUbdIzc SP:FB PPU:327,  0 CYC:116\n$C799:85 01     STA $01 = #$FF                     A:00 X:00 Y:00 P:nVUbdIZc SP:FB PPU:333,  0 CYC:118\n$C79B:24 01     BIT $01 = #$00                     A:00 X:00 Y:00 P:nVUbdIZc SP:FB PPU:  1,  1 CYC:121\n$C79D:50 04     BVC $C7A3                          A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 10,  1 CYC:124\n$C7A3:EA        NOP                                A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 19,  1 CYC:127\n$C7A4:24 01     BIT $01 = #$00                     A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 25,  1 CYC:129\n$C7A6:70 03     BVS $C7AB                          A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 34,  1 CYC:132\n$C7A8:4C AF C7  JMP $C7AF                          A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 40,  1 CYC:134\n$C7AF:EA        NOP                                A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 49,  1 CYC:137\n$C7B0:A9 00     LDA #$00                           A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 55,  1 CYC:139\n$C7B2:10 04     BPL $C7B8                          A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 61,  1 CYC:141\n$C7B8:EA        NOP                                A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 70,  1 CYC:144\n$C7B9:A9 80     LDA #$80                           A:00 X:00 Y:00 P:nvUbdIZc SP:FB PPU: 76,  1 CYC:146\n$C7BB:10 03     BPL $C7C0                          A:80 X:00 Y:00 P:NvUbdIzc SP:FB PPU: 82,  1 CYC:148\n$C7BD:4C D9 C7  JMP $C7D9                          A:80 X:00 Y:00 P:NvUbdIzc SP:FB PPU: 88,  1 CYC:150\n$C7D9:EA        NOP                                A:80 X:00 Y:00 P:NvUbdIzc SP:FB PPU: 97,  1 CYC:153\n$C7DA:60        RTS                                A:80 X:00 Y:00 P:NvUbdIzc SP:FB PPU:103,  1 CYC:155\n$C600:20 DB C7  JSR $C7DB                          A:80 X:00 Y:00 P:NvUbdIzc SP:FD PPU:121,  1 CYC:161\n$C7DB:EA        NOP                                A:80 X:00 Y:00 P:NvUbdIzc SP:FB PPU:139,  1 CYC:167\n$C7DC:A9 FF     LDA #$FF                           A:80 X:00 Y:00 P:NvUbdIzc SP:FB PPU:145,  1 CYC:169\n$C7DE:85 01     STA $01 = #$00                     A:FF X:00 Y:00 P:NvUbdIzc SP:FB PPU:151,  1 CYC:171\n$C7E0:24 01     BIT $01 = #$FF                     A:FF X:00 Y:00 P:NvUbdIzc SP:FB PPU:160,  1 CYC:174\n$C7E2:A9 00     LDA #$00                           A:FF X:00 Y:00 P:NVUbdIzc SP:FB PPU:169,  1 CYC:177\n$C7E4:38        SEC                                A:00 X:00 Y:00 P:nVUbdIZc SP:FB PPU:175,  1 CYC:179\n$C7E5:78        SEI                                A:00 X:00 Y:00 P:67 SP:FB PPU:181,  1 CYC:181\n$C7E6:F8        SED                                A:00 X:00 Y:00 P:67 SP:FB PPU:187,  1 CYC:183\n$C7E7:08        PHP                                A:00 X:00 Y:00 P:6F SP:FB PPU:193,  1 CYC:185\n$C7E8:68        PLA                                A:00 X:00 Y:00 P:6F SP:FA PPU:202,  1 CYC:188\n$C7E9:29 EF     AND #$EF                           A:7F X:00 Y:00 P:6D SP:FB PPU:214,  1 CYC:192\n$C7EB:C9 6F     CMP #$6F                           A:6F X:00 Y:00 P:6D SP:FB PPU:220,  1 CYC:194\n$C7ED:F0 04     BEQ $C7F3                          A:6F X:00 Y:00 P:6F SP:FB PPU:226,  1 CYC:196\n$C7F3:EA        NOP                                A:6F X:00 Y:00 P:6F SP:FB PPU:235,  1 CYC:199\n$C7F4:A9 40     LDA #$40                           A:6F X:00 Y:00 P:6F SP:FB PPU:241,  1 CYC:201\n$C7F6:85 01     STA $01 = #$FF                     A:40 X:00 Y:00 P:6D SP:FB PPU:247,  1 CYC:203\n$C7F8:24 01     BIT $01 = #$40                     A:40 X:00 Y:00 P:6D SP:FB PPU:256,  1 CYC:206\n$C7FA:D8        CLD                                A:40 X:00 Y:00 P:6D SP:FB PPU:265,  1 CYC:209\n$C7FB:A9 10     LDA #$10                           A:40 X:00 Y:00 P:65 SP:FB PPU:271,  1 CYC:211\n$C7FD:18        CLC                                A:10 X:00 Y:00 P:65 SP:FB PPU:277,  1 CYC:213\n$C7FE:08        PHP                                A:10 X:00 Y:00 P:64 SP:FB PPU:283,  1 CYC:215\n$C7FF:68        PLA                                A:10 X:00 Y:00 P:64 SP:FA PPU:292,  1 CYC:218\n$C800:29 EF     AND #$EF                           A:74 X:00 Y:00 P:64 SP:FB PPU:304,  1 CYC:222\n$C802:C9 64     CMP #$64                           A:64 X:00 Y:00 P:64 SP:FB PPU:310,  1 CYC:224\n$C804:F0 04     BEQ $C80A                          A:64 X:00 Y:00 P:67 SP:FB PPU:316,  1 CYC:226\n$C80A:EA        NOP                                A:64 X:00 Y:00 P:67 SP:FB PPU:325,  1 CYC:229\n$C80B:A9 80     LDA #$80                           A:64 X:00 Y:00 P:67 SP:FB PPU:331,  1 CYC:231\n$C80D:85 01     STA $01 = #$40                     A:80 X:00 Y:00 P:E5 SP:FB PPU:337,  1 CYC:233\n$C80F:24 01     BIT $01 = #$80                     A:80 X:00 Y:00 P:E5 SP:FB PPU:  5,  2 CYC:236\n$C811:F8        SED                                A:80 X:00 Y:00 P:A5 SP:FB PPU: 14,  2 CYC:239\n$C812:A9 00     LDA #$00                           A:80 X:00 Y:00 P:AD SP:FB PPU: 20,  2 CYC:241\n$C814:38        SEC                                A:00 X:00 Y:00 P:2F SP:FB PPU: 26,  2 CYC:243\n$C815:08        PHP                                A:00 X:00 Y:00 P:2F SP:FB PPU: 32,  2 CYC:245\n$C816:68        PLA                                A:00 X:00 Y:00 P:2F SP:FA PPU: 41,  2 CYC:248\n$C817:29 EF     AND #$EF                           A:3F X:00 Y:00 P:2D SP:FB PPU: 53,  2 CYC:252\n$C819:C9 2F     CMP #$2F                           A:2F X:00 Y:00 P:2D SP:FB PPU: 59,  2 CYC:254\n$C81B:F0 04     BEQ $C821                          A:2F X:00 Y:00 P:2F SP:FB PPU: 65,  2 CYC:256\n$C821:EA        NOP                                A:2F X:00 Y:00 P:2F SP:FB PPU: 74,  2 CYC:259\n$C822:A9 FF     LDA #$FF                           A:2F X:00 Y:00 P:2F SP:FB PPU: 80,  2 CYC:261\n$C824:48        PHA                                A:FF X:00 Y:00 P:AD SP:FB PPU: 86,  2 CYC:263\n$C825:28        PLP                                A:FF X:00 Y:00 P:AD SP:FA PPU: 95,  2 CYC:266\n$C826:D0 09     BNE $C831                          A:FF X:00 Y:00 P:EF SP:FB PPU:107,  2 CYC:270\n$C828:10 07     BPL $C831                          A:FF X:00 Y:00 P:EF SP:FB PPU:113,  2 CYC:272\n$C82A:50 05     BVC $C831                          A:FF X:00 Y:00 P:EF SP:FB PPU:119,  2 CYC:274\n$C82C:90 03     BCC $C831                          A:FF X:00 Y:00 P:EF SP:FB PPU:125,  2 CYC:276\n$C82E:4C 35 C8  JMP $C835                          A:FF X:00 Y:00 P:EF SP:FB PPU:131,  2 CYC:278\n$C835:EA        NOP                                A:FF X:00 Y:00 P:EF SP:FB PPU:140,  2 CYC:281\n$C836:A9 04     LDA #$04                           A:FF X:00 Y:00 P:EF SP:FB PPU:146,  2 CYC:283\n$C838:48        PHA                                A:04 X:00 Y:00 P:6D SP:FB PPU:152,  2 CYC:285\n$C839:28        PLP                                A:04 X:00 Y:00 P:6D SP:FA PPU:161,  2 CYC:288\n$C83A:F0 09     BEQ $C845                          A:04 X:00 Y:00 P:nvUbdIzc SP:FB PPU:173,  2 CYC:292\n$C83C:30 07     BMI $C845                          A:04 X:00 Y:00 P:nvUbdIzc SP:FB PPU:179,  2 CYC:294\n$C83E:70 05     BVS $C845                          A:04 X:00 Y:00 P:nvUbdIzc SP:FB PPU:185,  2 CYC:296\n$C840:B0 03     BCS $C845                          A:04 X:00 Y:00 P:nvUbdIzc SP:FB PPU:191,  2 CYC:298\n$C842:4C 49 C8  JMP $C849                          A:04 X:00 Y:00 P:nvUbdIzc SP:FB PPU:197,  2 CYC:300\n$C849:EA        NOP                                A:04 X:00 Y:00 P:nvUbdIzc SP:FB PPU:206,  2 CYC:303\n$C84A:F8        SED                                A:04 X:00 Y:00 P:nvUbdIzc SP:FB PPU:212,  2 CYC:305\n$C84B:A9 FF     LDA #$FF                           A:04 X:00 Y:00 P:2C SP:FB PPU:218,  2 CYC:307\n$C84D:85 01     STA $01 = #$80                     A:FF X:00 Y:00 P:AC SP:FB PPU:224,  2 CYC:309\n$C84F:24 01     BIT $01 = #$FF                     A:FF X:00 Y:00 P:AC SP:FB PPU:233,  2 CYC:312\n$C851:18        CLC                                A:FF X:00 Y:00 P:EC SP:FB PPU:242,  2 CYC:315\n$C852:A9 00     LDA #$00                           A:FF X:00 Y:00 P:EC SP:FB PPU:248,  2 CYC:317\n$C854:48        PHA                                A:00 X:00 Y:00 P:6E SP:FB PPU:254,  2 CYC:319\n$C855:A9 FF     LDA #$FF                           A:00 X:00 Y:00 P:6E SP:FA PPU:263,  2 CYC:322\n$C857:68        PLA                                A:FF X:00 Y:00 P:EC SP:FA PPU:269,  2 CYC:324\n$C858:D0 09     BNE $C863                          A:00 X:00 Y:00 P:6E SP:FB PPU:281,  2 CYC:328\n$C85A:30 07     BMI $C863                          A:00 X:00 Y:00 P:6E SP:FB PPU:287,  2 CYC:330\n$C85C:50 05     BVC $C863                          A:00 X:00 Y:00 P:6E SP:FB PPU:293,  2 CYC:332\n$C85E:B0 03     BCS $C863                          A:00 X:00 Y:00 P:6E SP:FB PPU:299,  2 CYC:334\n$C860:4C 67 C8  JMP $C867                          A:00 X:00 Y:00 P:6E SP:FB PPU:305,  2 CYC:336\n$C867:EA        NOP                                A:00 X:00 Y:00 P:6E SP:FB PPU:314,  2 CYC:339\n$C868:A9 00     LDA #$00                           A:00 X:00 Y:00 P:6E SP:FB PPU:320,  2 CYC:341\n$C86A:85 01     STA $01 = #$FF                     A:00 X:00 Y:00 P:6E SP:FB PPU:326,  2 CYC:343\n$C86C:24 01     BIT $01 = #$00                     A:00 X:00 Y:00 P:6E SP:FB PPU:335,  2 CYC:346\n$C86E:38        SEC                                A:00 X:00 Y:00 P:2E SP:FB PPU:  3,  3 CYC:349\n$C86F:A9 FF     LDA #$FF                           A:00 X:00 Y:00 P:2F SP:FB PPU:  9,  3 CYC:351\n$C871:48        PHA                                A:FF X:00 Y:00 P:AD SP:FB PPU: 15,  3 CYC:353\n$C872:A9 00     LDA #$00                           A:FF X:00 Y:00 P:AD SP:FA PPU: 24,  3 CYC:356\n$C874:68        PLA                                A:00 X:00 Y:00 P:2F SP:FA PPU: 30,  3 CYC:358\n$C875:F0 09     BEQ $C880                          A:FF X:00 Y:00 P:AD SP:FB PPU: 42,  3 CYC:362\n$C877:10 07     BPL $C880                          A:FF X:00 Y:00 P:AD SP:FB PPU: 48,  3 CYC:364\n$C879:70 05     BVS $C880                          A:FF X:00 Y:00 P:AD SP:FB PPU: 54,  3 CYC:366\n$C87B:90 03     BCC $C880                          A:FF X:00 Y:00 P:AD SP:FB PPU: 60,  3 CYC:368\n$C87D:4C 84 C8  JMP $C884                          A:FF X:00 Y:00 P:AD SP:FB PPU: 66,  3 CYC:370\n$C884:60        RTS                                A:FF X:00 Y:00 P:AD SP:FB PPU: 75,  3 CYC:373\n$C603:20 85 C8  JSR $C885                          A:FF X:00 Y:00 P:AD SP:FD PPU: 93,  3 CYC:379\n$C885:EA        NOP                                A:FF X:00 Y:00 P:AD SP:FB PPU:111,  3 CYC:385\n$C886:18        CLC                                A:FF X:00 Y:00 P:AD SP:FB PPU:117,  3 CYC:387\n$C887:A9 FF     LDA #$FF                           A:FF X:00 Y:00 P:AC SP:FB PPU:123,  3 CYC:389\n$C889:85 01     STA $01 = #$00                     A:FF X:00 Y:00 P:AC SP:FB PPU:129,  3 CYC:391\n$C88B:24 01     BIT $01 = #$FF                     A:FF X:00 Y:00 P:AC SP:FB PPU:138,  3 CYC:394\n$C88D:A9 55     LDA #$55                           A:FF X:00 Y:00 P:EC SP:FB PPU:147,  3 CYC:397\n$C88F:09 AA     ORA #$AA                           A:55 X:00 Y:00 P:6C SP:FB PPU:153,  3 CYC:399\n$C891:B0 0B     BCS $C89E                          A:FF X:00 Y:00 P:EC SP:FB PPU:159,  3 CYC:401\n$C893:10 09     BPL $C89E                          A:FF X:00 Y:00 P:EC SP:FB PPU:165,  3 CYC:403\n$C895:C9 FF     CMP #$FF                           A:FF X:00 Y:00 P:EC SP:FB PPU:171,  3 CYC:405\n$C897:D0 05     BNE $C89E                          A:FF X:00 Y:00 P:6F SP:FB PPU:177,  3 CYC:407\n$C899:50 03     BVC $C89E                          A:FF X:00 Y:00 P:6F SP:FB PPU:183,  3 CYC:409\n$C89B:4C A2 C8  JMP $C8A2                          A:FF X:00 Y:00 P:6F SP:FB PPU:189,  3 CYC:411\n$C8A2:EA        NOP                                A:FF X:00 Y:00 P:6F SP:FB PPU:198,  3 CYC:414\n$C8A3:38        SEC                                A:FF X:00 Y:00 P:6F SP:FB PPU:204,  3 CYC:416\n$C8A4:B8        CLV                                A:FF X:00 Y:00 P:6F SP:FB PPU:210,  3 CYC:418\n$C8A5:A9 00     LDA #$00                           A:FF X:00 Y:00 P:2F SP:FB PPU:216,  3 CYC:420\n$C8A7:09 00     ORA #$00                           A:00 X:00 Y:00 P:2F SP:FB PPU:222,  3 CYC:422\n$C8A9:D0 09     BNE $C8B4                          A:00 X:00 Y:00 P:2F SP:FB PPU:228,  3 CYC:424\n$C8AB:70 07     BVS $C8B4                          A:00 X:00 Y:00 P:2F SP:FB PPU:234,  3 CYC:426\n$C8AD:90 05     BCC $C8B4                          A:00 X:00 Y:00 P:2F SP:FB PPU:240,  3 CYC:428\n$C8AF:30 03     BMI $C8B4                          A:00 X:00 Y:00 P:2F SP:FB PPU:246,  3 CYC:430\n$C8B1:4C B8 C8  JMP $C8B8                          A:00 X:00 Y:00 P:2F SP:FB PPU:252,  3 CYC:432\n$C8B8:EA        NOP                                A:00 X:00 Y:00 P:2F SP:FB PPU:261,  3 CYC:435\n$C8B9:18        CLC                                A:00 X:00 Y:00 P:2F SP:FB PPU:267,  3 CYC:437\n$C8BA:24 01     BIT $01 = #$FF                     A:00 X:00 Y:00 P:2E SP:FB PPU:273,  3 CYC:439\n$C8BC:A9 55     LDA #$55                           A:00 X:00 Y:00 P:EE SP:FB PPU:282,  3 CYC:442\n$C8BE:29 AA     AND #$AA                           A:55 X:00 Y:00 P:6C SP:FB PPU:288,  3 CYC:444\n$C8C0:D0 09     BNE $C8CB                          A:00 X:00 Y:00 P:6E SP:FB PPU:294,  3 CYC:446\n$C8C2:50 07     BVC $C8CB                          A:00 X:00 Y:00 P:6E SP:FB PPU:300,  3 CYC:448\n$C8C4:B0 05     BCS $C8CB                          A:00 X:00 Y:00 P:6E SP:FB PPU:306,  3 CYC:450\n$C8C6:30 03     BMI $C8CB                          A:00 X:00 Y:00 P:6E SP:FB PPU:312,  3 CYC:452\n$C8C8:4C CF C8  JMP $C8CF                          A:00 X:00 Y:00 P:6E SP:FB PPU:318,  3 CYC:454\n$C8CF:EA        NOP                                A:00 X:00 Y:00 P:6E SP:FB PPU:327,  3 CYC:457\n$C8D0:38        SEC                                A:00 X:00 Y:00 P:6E SP:FB PPU:333,  3 CYC:459\n$C8D1:B8        CLV                                A:00 X:00 Y:00 P:6F SP:FB PPU:339,  3 CYC:461\n$C8D2:A9 F8     LDA #$F8                           A:00 X:00 Y:00 P:2F SP:FB PPU:  4,  4 CYC:463\n$C8D4:29 EF     AND #$EF                           A:F8 X:00 Y:00 P:AD SP:FB PPU: 10,  4 CYC:465\n$C8D6:90 0B     BCC $C8E3                          A:E8 X:00 Y:00 P:AD SP:FB PPU: 16,  4 CYC:467\n$C8D8:10 09     BPL $C8E3                          A:E8 X:00 Y:00 P:AD SP:FB PPU: 22,  4 CYC:469\n$C8DA:C9 E8     CMP #$E8                           A:E8 X:00 Y:00 P:AD SP:FB PPU: 28,  4 CYC:471\n$C8DC:D0 05     BNE $C8E3                          A:E8 X:00 Y:00 P:2F SP:FB PPU: 34,  4 CYC:473\n$C8DE:70 03     BVS $C8E3                          A:E8 X:00 Y:00 P:2F SP:FB PPU: 40,  4 CYC:475\n$C8E0:4C E7 C8  JMP $C8E7                          A:E8 X:00 Y:00 P:2F SP:FB PPU: 46,  4 CYC:477\n$C8E7:EA        NOP                                A:E8 X:00 Y:00 P:2F SP:FB PPU: 55,  4 CYC:480\n$C8E8:18        CLC                                A:E8 X:00 Y:00 P:2F SP:FB PPU: 61,  4 CYC:482\n$C8E9:24 01     BIT $01 = #$FF                     A:E8 X:00 Y:00 P:2E SP:FB PPU: 67,  4 CYC:484\n$C8EB:A9 5F     LDA #$5F                           A:E8 X:00 Y:00 P:EC SP:FB PPU: 76,  4 CYC:487\n$C8ED:49 AA     EOR #$AA                           A:5F X:00 Y:00 P:6C SP:FB PPU: 82,  4 CYC:489\n$C8EF:B0 0B     BCS $C8FC                          A:F5 X:00 Y:00 P:EC SP:FB PPU: 88,  4 CYC:491\n$C8F1:10 09     BPL $C8FC                          A:F5 X:00 Y:00 P:EC SP:FB PPU: 94,  4 CYC:493\n$C8F3:C9 F5     CMP #$F5                           A:F5 X:00 Y:00 P:EC SP:FB PPU:100,  4 CYC:495\n$C8F5:D0 05     BNE $C8FC                          A:F5 X:00 Y:00 P:6F SP:FB PPU:106,  4 CYC:497\n$C8F7:50 03     BVC $C8FC                          A:F5 X:00 Y:00 P:6F SP:FB PPU:112,  4 CYC:499\n$C8F9:4C 00 C9  JMP $C900                          A:F5 X:00 Y:00 P:6F SP:FB PPU:118,  4 CYC:501\n$C900:EA        NOP                                A:F5 X:00 Y:00 P:6F SP:FB PPU:127,  4 CYC:504\n$C901:38        SEC                                A:F5 X:00 Y:00 P:6F SP:FB PPU:133,  4 CYC:506\n$C902:B8        CLV                                A:F5 X:00 Y:00 P:6F SP:FB PPU:139,  4 CYC:508\n$C903:A9 70     LDA #$70                           A:F5 X:00 Y:00 P:2F SP:FB PPU:145,  4 CYC:510\n$C905:49 70     EOR #$70                           A:70 X:00 Y:00 P:2D SP:FB PPU:151,  4 CYC:512\n$C907:D0 09     BNE $C912                          A:00 X:00 Y:00 P:2F SP:FB PPU:157,  4 CYC:514\n$C909:70 07     BVS $C912                          A:00 X:00 Y:00 P:2F SP:FB PPU:163,  4 CYC:516\n$C90B:90 05     BCC $C912                          A:00 X:00 Y:00 P:2F SP:FB PPU:169,  4 CYC:518\n$C90D:30 03     BMI $C912                          A:00 X:00 Y:00 P:2F SP:FB PPU:175,  4 CYC:520\n$C90F:4C 16 C9  JMP $C916                          A:00 X:00 Y:00 P:2F SP:FB PPU:181,  4 CYC:522\n$C916:EA        NOP                                A:00 X:00 Y:00 P:2F SP:FB PPU:190,  4 CYC:525\n$C917:18        CLC                                A:00 X:00 Y:00 P:2F SP:FB PPU:196,  4 CYC:527\n$C918:24 01     BIT $01 = #$FF                     A:00 X:00 Y:00 P:2E SP:FB PPU:202,  4 CYC:529\n$C91A:A9 00     LDA #$00                           A:00 X:00 Y:00 P:EE SP:FB PPU:211,  4 CYC:532\n$C91C:69 69     ADC #$69                           A:00 X:00 Y:00 P:6E SP:FB PPU:217,  4 CYC:534\n$C91E:30 0B     BMI $C92B                          A:69 X:00 Y:00 P:2C SP:FB PPU:223,  4 CYC:536\n$C920:B0 09     BCS $C92B                          A:69 X:00 Y:00 P:2C SP:FB PPU:229,  4 CYC:538\n$C922:C9 69     CMP #$69                           A:69 X:00 Y:00 P:2C SP:FB PPU:235,  4 CYC:540\n$C924:D0 05     BNE $C92B                          A:69 X:00 Y:00 P:2F SP:FB PPU:241,  4 CYC:542\n$C926:70 03     BVS $C92B                          A:69 X:00 Y:00 P:2F SP:FB PPU:247,  4 CYC:544\n$C928:4C 2F C9  JMP $C92F                          A:69 X:00 Y:00 P:2F SP:FB PPU:253,  4 CYC:546\n$C92F:EA        NOP                                A:69 X:00 Y:00 P:2F SP:FB PPU:262,  4 CYC:549\n$C930:38        SEC                                A:69 X:00 Y:00 P:2F SP:FB PPU:268,  4 CYC:551\n$C931:F8        SED                                A:69 X:00 Y:00 P:2F SP:FB PPU:274,  4 CYC:553\n$C932:24 01     BIT $01 = #$FF                     A:69 X:00 Y:00 P:2F SP:FB PPU:280,  4 CYC:555\n$C934:A9 01     LDA #$01                           A:69 X:00 Y:00 P:ED SP:FB PPU:289,  4 CYC:558\n$C936:69 69     ADC #$69                           A:01 X:00 Y:00 P:6D SP:FB PPU:295,  4 CYC:560\n$C938:30 0B     BMI $C945                          A:6B X:00 Y:00 P:2C SP:FB PPU:301,  4 CYC:562\n$C93A:B0 09     BCS $C945                          A:6B X:00 Y:00 P:2C SP:FB PPU:307,  4 CYC:564\n$C93C:C9 6B     CMP #$6B                           A:6B X:00 Y:00 P:2C SP:FB PPU:313,  4 CYC:566\n$C93E:D0 05     BNE $C945                          A:6B X:00 Y:00 P:2F SP:FB PPU:319,  4 CYC:568\n$C940:70 03     BVS $C945                          A:6B X:00 Y:00 P:2F SP:FB PPU:325,  4 CYC:570\n$C942:4C 49 C9  JMP $C949                          A:6B X:00 Y:00 P:2F SP:FB PPU:331,  4 CYC:572\n$C949:EA        NOP                                A:6B X:00 Y:00 P:2F SP:FB PPU:340,  4 CYC:575\n$C94A:D8        CLD                                A:6B X:00 Y:00 P:2F SP:FB PPU:  5,  5 CYC:577\n$C94B:38        SEC                                A:6B X:00 Y:00 P:nvUbdIZC SP:FB PPU: 11,  5 CYC:579\n$C94C:B8        CLV                                A:6B X:00 Y:00 P:nvUbdIZC SP:FB PPU: 17,  5 CYC:581\n$C94D:A9 7F     LDA #$7F                           A:6B X:00 Y:00 P:nvUbdIZC SP:FB PPU: 23,  5 CYC:583\n$C94F:69 7F     ADC #$7F                           A:7F X:00 Y:00 P:25 SP:FB PPU: 29,  5 CYC:585\n$C951:10 0B     BPL $C95E                          A:FF X:00 Y:00 P:NVUbdIzc SP:FB PPU: 35,  5 CYC:587\n$C953:B0 09     BCS $C95E                          A:FF X:00 Y:00 P:NVUbdIzc SP:FB PPU: 41,  5 CYC:589\n$C955:C9 FF     CMP #$FF                           A:FF X:00 Y:00 P:NVUbdIzc SP:FB PPU: 47,  5 CYC:591\n$C957:D0 05     BNE $C95E                          A:FF X:00 Y:00 P:67 SP:FB PPU: 53,  5 CYC:593\n$C959:50 03     BVC $C95E                          A:FF X:00 Y:00 P:67 SP:FB PPU: 59,  5 CYC:595\n$C95B:4C 62 C9  JMP $C962                          A:FF X:00 Y:00 P:67 SP:FB PPU: 65,  5 CYC:597\n$C962:EA        NOP                                A:FF X:00 Y:00 P:67 SP:FB PPU: 74,  5 CYC:600\n$C963:18        CLC                                A:FF X:00 Y:00 P:67 SP:FB PPU: 80,  5 CYC:602\n$C964:24 01     BIT $01 = #$FF                     A:FF X:00 Y:00 P:nVUbdIZc SP:FB PPU: 86,  5 CYC:604\n$C966:A9 7F     LDA #$7F                           A:FF X:00 Y:00 P:NVUbdIzc SP:FB PPU: 95,  5 CYC:607\n$C968:69 80     ADC #$80                           A:7F X:00 Y:00 P:64 SP:FB PPU:101,  5 CYC:609\n$C96A:10 0B     BPL $C977                          A:FF X:00 Y:00 P:NvUbdIzc SP:FB PPU:107,  5 CYC:611\n$C96C:B0 09     BCS $C977                          A:FF X:00 Y:00 P:NvUbdIzc SP:FB PPU:113,  5 CYC:613\n$C96E:C9 FF     CMP #$FF                           A:FF X:00 Y:00 P:NvUbdIzc SP:FB PPU:119,  5 CYC:615\n$C970:D0 05     BNE $C977                          A:FF X:00 Y:00 P:nvUbdIZC SP:FB PPU:125,  5 CYC:617\n$C972:70 03     BVS $C977                          A:FF X:00 Y:00 P:nvUbdIZC SP:FB PPU:131,  5 CYC:619\n$C974:4C 7B C9  JMP $C97B                          A:FF X:00 Y:00 P:nvUbdIZC SP:FB PPU:137,  5 CYC:621\n$C97B:EA        NOP                                A:FF X:00 Y:00 P:nvUbdIZC SP:FB PPU:146,  5 CYC:624\n$C97C:38        SEC                                A:FF X:00 Y:00 P:nvUbdIZC SP:FB PPU:152,  5 CYC:626\n$C97D:B8        CLV                                A:FF X:00 Y:00 P:nvUbdIZC SP:FB PPU:158,  5 CYC:628\n$C97E:A9 7F     LDA #$7F                           A:FF X:00 Y:00 P:nvUbdIZC SP:FB PPU:164,  5 CYC:630\n$C980:69 80     ADC #$80                           A:7F X:00 Y:00 P:25 SP:FB PPU:170,  5 CYC:632\n$C982:D0 09     BNE $C98D                          A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU:176,  5 CYC:634\n$C984:30 07     BMI $C98D                          A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU:182,  5 CYC:636\n$C986:70 05     BVS $C98D                          A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU:188,  5 CYC:638\n$C988:90 03     BCC $C98D                          A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU:194,  5 CYC:640\n$C98A:4C 91 C9  JMP $C991                          A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU:200,  5 CYC:642\n$C991:EA        NOP                                A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU:209,  5 CYC:645\n$C992:38        SEC                                A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU:215,  5 CYC:647\n$C993:B8        CLV                                A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU:221,  5 CYC:649\n$C994:A9 9F     LDA #$9F                           A:00 X:00 Y:00 P:nvUbdIZC SP:FB PPU:227,  5 CYC:651\n$C996:F0 09     BEQ $C9A1                          A:9F X:00 Y:00 P:A5 SP:FB PPU:233,  5 CYC:653\n$C998:10 07     BPL $C9A1                          A:9F X:00 Y:00 P:A5 SP:FB PPU:239,  5 CYC:655\n$C99A:70 05     BVS $C9A1                          A:9F X:00 Y:00 P:A5 SP:FB PPU:245,  5 CYC:657\n$C99C:90 03     BCC $C9A1                          A:9F X:00 Y:00 P:A5 SP:FB PPU:251,  5 CYC:659\n$C99E:4C A5 C9  JMP $C9A5                          A:9F X:00 Y:00 P:A5 SP:FB PPU:257,  5 CYC:661\n$C9A5:EA        NOP                                A:9F X:00 Y:00 P:A5 SP:FB PPU:266,  5 CYC:664\n$C9A6:18        CLC                                A:9F X:00 Y:00 P:A5 SP:FB PPU:272,  5 CYC:666\n$C9A7:24 01     BIT $01 = #$FF                     A:9F X:00 Y:00 P:NvUbdIzc SP:FB PPU:278,  5 CYC:668\n$C9A9:A9 00     LDA #$00                           A:9F X:00 Y:00 P:NVUbdIzc SP:FB PPU:287,  5 CYC:671\n$C9AB:D0 09     BNE $C9B6                          A:00 X:00 Y:00 P:nVUbdIZc SP:FB PPU:293,  5 CYC:673\n$C9AD:30 07     BMI $C9B6                          A:00 X:00 Y:00 P:nVUbdIZc SP:FB PPU:299,  5 CYC:675\n$C9AF:50 05     BVC $C9B6                          A:00 X:00 Y:00 P:nVUbdIZc SP:FB PPU:305,  5 CYC:677\n$C9B1:B0 03     BCS $C9B6                          A:00 X:00 Y:00 P:nVUbdIZc SP:FB PPU:311,  5 CYC:679\n$C9B3:4C BA C9  JMP $C9BA                          A:00 X:00 Y:00 P:nVUbdIZc SP:FB PPU:317,  5 CYC:681\n$C9BA:EA        NOP                                A:00 X:00 Y:00 P:nVUbdIZc SP:FB PPU:326,  5 CYC:684\n$C9BB:24 01     BIT $01 = #$FF                     A:00 X:00 Y:00 P:nVUbdIZc SP:FB PPU:332,  5 CYC:686\n$C9BD:A9 40     LDA #$40                           A:00 X:00 Y:00 P:E6 SP:FB PPU:  0,  6 CYC:689\n$C9BF:C9 40     CMP #$40                           A:40 X:00 Y:00 P:64 SP:FB PPU:  6,  6 CYC:691\n$C9C1:30 09     BMI $C9CC                          A:40 X:00 Y:00 P:67 SP:FB PPU: 12,  6 CYC:693\n$C9C3:90 07     BCC $C9CC                          A:40 X:00 Y:00 P:67 SP:FB PPU: 18,  6 CYC:695\n$C9C5:D0 05     BNE $C9CC                          A:40 X:00 Y:00 P:67 SP:FB PPU: 24,  6 CYC:697\n$C9C7:50 03     BVC $C9CC                          A:40 X:00 Y:00 P:67 SP:FB PPU: 30,  6 CYC:699\n$C9C9:4C D0 C9  JMP $C9D0                          A:40 X:00 Y:00 P:67 SP:FB PPU: 36,  6 CYC:701\n$C9D0:EA        NOP                                A:40 X:00 Y:00 P:67 SP:FB PPU: 45,  6 CYC:704\n$C9D1:B8        CLV                                A:40 X:00 Y:00 P:67 SP:FB PPU: 51,  6 CYC:706\n$C9D2:C9 3F     CMP #$3F                           A:40 X:00 Y:00 P:nvUbdIZC SP:FB PPU: 57,  6 CYC:708\n$C9D4:F0 09     BEQ $C9DF                          A:40 X:00 Y:00 P:25 SP:FB PPU: 63,  6 CYC:710\n$C9D6:30 07     BMI $C9DF                          A:40 X:00 Y:00 P:25 SP:FB PPU: 69,  6 CYC:712\n$C9D8:90 05     BCC $C9DF                          A:40 X:00 Y:00 P:25 SP:FB PPU: 75,  6 CYC:714\n$C9DA:70 03     BVS $C9DF                          A:40 X:00 Y:00 P:25 SP:FB PPU: 81,  6 CYC:716\n$C9DC:4C E3 C9  JMP $C9E3                          A:40 X:00 Y:00 P:25 SP:FB PPU: 87,  6 CYC:718\n$C9E3:EA        NOP                                A:40 X:00 Y:00 P:25 SP:FB PPU: 96,  6 CYC:721\n$C9E4:C9 41     CMP #$41                           A:40 X:00 Y:00 P:25 SP:FB PPU:102,  6 CYC:723\n$C9E6:F0 07     BEQ $C9EF                          A:40 X:00 Y:00 P:NvUbdIzc SP:FB PPU:108,  6 CYC:725\n$C9E8:10 05     BPL $C9EF                          A:40 X:00 Y:00 P:NvUbdIzc SP:FB PPU:114,  6 CYC:727\n$C9EA:10 03     BPL $C9EF                          A:40 X:00 Y:00 P:NvUbdIzc SP:FB PPU:120,  6 CYC:729\n$C9EC:4C F3 C9  JMP $C9F3                          A:40 X:00 Y:00 P:NvUbdIzc SP:FB PPU:126,  6 CYC:731\n$C9F3:EA        NOP                                A:40 X:00 Y:00 P:NvUbdIzc SP:FB PPU:135,  6 CYC:734\n$C9F4:A9 80     LDA #$80                           A:40 X:00 Y:00 P:NvUbdIzc SP:FB PPU:141,  6 CYC:736\n$C9F6:C9 00     CMP #$00                           A:80 X:00 Y:00 P:NvUbdIzc SP:FB PPU:147,  6 CYC:738\n$C9F8:F0 07     BEQ $CA01                          A:80 X:00 Y:00 P:A5 SP:FB PPU:153,  6 CYC:740\n$C9FA:10 05     BPL $CA01                          A:80 X:00 Y:00 P:A5 SP:FB PPU:159,  6 CYC:742\n$C9FC:90 03     BCC $CA01                          A:80 X:00 Y:00 P:A5 SP:FB PPU:165,  6 CYC:744\n$C9FE:4C 05 CA  JMP $CA05                          A:80 X:00 Y:00 P:A5 SP:FB PPU:171,  6 CYC:746\n$CA05:EA        NOP                                A:80 X:00 Y:00 P:A5 SP:FB PPU:180,  6 CYC:749\n$CA06:C9 80     CMP #$80                           A:80 X:00 Y:00 P:A5 SP:FB PPU:186,  6 CYC:751\n$CA08:D0 07     BNE $CA11                          A:80 X:00 Y:00 P:nvUbdIZC SP:FB PPU:192,  6 CYC:753\n$CA0A:30 05     BMI $CA11                          A:80 X:00 Y:00 P:nvUbdIZC SP:FB PPU:198,  6 CYC:755\n$CA0C:90 03     BCC $CA11                          A:80 X:00 Y:00 P:nvUbdIZC SP:FB PPU:204,  6 CYC:757\n$CA0E:4C 15 CA  JMP $CA15                          A:80 X:00 Y:00 P:nvUbdIZC SP:FB PPU:210,  6 CYC:759\n$CA15:EA        NOP                                A:80 X:00 Y:00 P:nvUbdIZC SP:FB PPU:219,  6 CYC:762\n$CA16:C9 81     CMP #$81                           A:80 X:00 Y:00 P:nvUbdIZC SP:FB PPU:225,  6 CYC:764\n$CA18:B0 07     BCS $CA21                          A:80 X:00 Y:00 P:NvUbdIzc SP:FB PPU:231,  6 CYC:766\n$CA1A:F0 05     BEQ $CA21                          A:80 X:00 Y:00 P:NvUbdIzc SP:FB PPU:237,  6 CYC:768\n$CA1C:10 03     BPL $CA21                          A:80 X:00 Y:00 P:NvUbdIzc SP:FB PPU:243,  6 CYC:770\n$CA1E:4C 25 CA  JMP $CA25                          A:80 X:00 Y:00 P:NvUbdIzc SP:FB PPU:249,  6 CYC:772\n$CA25:EA        NOP                                A:80 X:00 Y:00 P:NvUbdIzc SP:FB PPU:258,  6 CYC:775\n$CA26:C9 7F     CMP #$7F                           A:80 X:00 Y:00 P:NvUbdIzc SP:FB PPU:264,  6 CYC:777\n$CA28:90 07     BCC $CA31                          A:80 X:00 Y:00 P:25 SP:FB PPU:270,  6 CYC:779\n$CA2A:F0 05     BEQ $CA31                          A:80 X:00 Y:00 P:25 SP:FB PPU:276,  6 CYC:781\n$CA2C:30 03     BMI $CA31                          A:80 X:00 Y:00 P:25 SP:FB PPU:282,  6 CYC:783\n$CA2E:4C 35 CA  JMP $CA35                          A:80 X:00 Y:00 P:25 SP:FB PPU:288,  6 CYC:785\n$CA35:EA        NOP                                A:80 X:00 Y:00 P:25 SP:FB PPU:297,  6 CYC:788\n$CA36:24 01     BIT $01 = #$FF                     A:80 X:00 Y:00 P:25 SP:FB PPU:303,  6 CYC:790\n$CA38:A0 40     LDY #$40                           A:80 X:00 Y:00 P:E5 SP:FB PPU:312,  6 CYC:793\n$CA3A:C0 40     CPY #$40                           A:80 X:00 Y:40 P:65 SP:FB PPU:318,  6 CYC:795\n$CA3C:D0 09     BNE $CA47                          A:80 X:00 Y:40 P:67 SP:FB PPU:324,  6 CYC:797\n$CA3E:30 07     BMI $CA47                          A:80 X:00 Y:40 P:67 SP:FB PPU:330,  6 CYC:799\n$CA40:90 05     BCC $CA47                          A:80 X:00 Y:40 P:67 SP:FB PPU:336,  6 CYC:801\n$CA42:50 03     BVC $CA47                          A:80 X:00 Y:40 P:67 SP:FB PPU:  1,  7 CYC:803\n$CA44:4C 4B CA  JMP $CA4B                          A:80 X:00 Y:40 P:67 SP:FB PPU:  7,  7 CYC:805\n$CA4B:EA        NOP                                A:80 X:00 Y:40 P:67 SP:FB PPU: 16,  7 CYC:808\n$CA4C:B8        CLV                                A:80 X:00 Y:40 P:67 SP:FB PPU: 22,  7 CYC:810\n$CA4D:C0 3F     CPY #$3F                           A:80 X:00 Y:40 P:nvUbdIZC SP:FB PPU: 28,  7 CYC:812\n$CA4F:F0 09     BEQ $CA5A                          A:80 X:00 Y:40 P:25 SP:FB PPU: 34,  7 CYC:814\n$CA51:30 07     BMI $CA5A                          A:80 X:00 Y:40 P:25 SP:FB PPU: 40,  7 CYC:816\n$CA53:90 05     BCC $CA5A                          A:80 X:00 Y:40 P:25 SP:FB PPU: 46,  7 CYC:818\n$CA55:70 03     BVS $CA5A                          A:80 X:00 Y:40 P:25 SP:FB PPU: 52,  7 CYC:820\n$CA57:4C 5E CA  JMP $CA5E                          A:80 X:00 Y:40 P:25 SP:FB PPU: 58,  7 CYC:822\n$CA5E:EA        NOP                                A:80 X:00 Y:40 P:25 SP:FB PPU: 67,  7 CYC:825\n$CA5F:C0 41     CPY #$41                           A:80 X:00 Y:40 P:25 SP:FB PPU: 73,  7 CYC:827\n$CA61:F0 07     BEQ $CA6A                          A:80 X:00 Y:40 P:NvUbdIzc SP:FB PPU: 79,  7 CYC:829\n$CA63:10 05     BPL $CA6A                          A:80 X:00 Y:40 P:NvUbdIzc SP:FB PPU: 85,  7 CYC:831\n$CA65:10 03     BPL $CA6A                          A:80 X:00 Y:40 P:NvUbdIzc SP:FB PPU: 91,  7 CYC:833\n$CA67:4C 6E CA  JMP $CA6E                          A:80 X:00 Y:40 P:NvUbdIzc SP:FB PPU: 97,  7 CYC:835\n$CA6E:EA        NOP                                A:80 X:00 Y:40 P:NvUbdIzc SP:FB PPU:106,  7 CYC:838\n$CA6F:A0 80     LDY #$80                           A:80 X:00 Y:40 P:NvUbdIzc SP:FB PPU:112,  7 CYC:840\n$CA71:C0 00     CPY #$00                           A:80 X:00 Y:80 P:NvUbdIzc SP:FB PPU:118,  7 CYC:842\n$CA73:F0 07     BEQ $CA7C                          A:80 X:00 Y:80 P:A5 SP:FB PPU:124,  7 CYC:844\n$CA75:10 05     BPL $CA7C                          A:80 X:00 Y:80 P:A5 SP:FB PPU:130,  7 CYC:846\n$CA77:90 03     BCC $CA7C                          A:80 X:00 Y:80 P:A5 SP:FB PPU:136,  7 CYC:848\n$CA79:4C 80 CA  JMP $CA80                          A:80 X:00 Y:80 P:A5 SP:FB PPU:142,  7 CYC:850\n$CA80:EA        NOP                                A:80 X:00 Y:80 P:A5 SP:FB PPU:151,  7 CYC:853\n$CA81:C0 80     CPY #$80                           A:80 X:00 Y:80 P:A5 SP:FB PPU:157,  7 CYC:855\n$CA83:D0 07     BNE $CA8C                          A:80 X:00 Y:80 P:nvUbdIZC SP:FB PPU:163,  7 CYC:857\n$CA85:30 05     BMI $CA8C                          A:80 X:00 Y:80 P:nvUbdIZC SP:FB PPU:169,  7 CYC:859\n$CA87:90 03     BCC $CA8C                          A:80 X:00 Y:80 P:nvUbdIZC SP:FB PPU:175,  7 CYC:861\n$CA89:4C 90 CA  JMP $CA90                          A:80 X:00 Y:80 P:nvUbdIZC SP:FB PPU:181,  7 CYC:863\n$CA90:EA        NOP                                A:80 X:00 Y:80 P:nvUbdIZC SP:FB PPU:190,  7 CYC:866\n$CA91:C0 81     CPY #$81                           A:80 X:00 Y:80 P:nvUbdIZC SP:FB PPU:196,  7 CYC:868\n$CA93:B0 07     BCS $CA9C                          A:80 X:00 Y:80 P:NvUbdIzc SP:FB PPU:202,  7 CYC:870\n$CA95:F0 05     BEQ $CA9C                          A:80 X:00 Y:80 P:NvUbdIzc SP:FB PPU:208,  7 CYC:872\n$CA97:10 03     BPL $CA9C                          A:80 X:00 Y:80 P:NvUbdIzc SP:FB PPU:214,  7 CYC:874\n$CA99:4C A0 CA  JMP $CAA0                          A:80 X:00 Y:80 P:NvUbdIzc SP:FB PPU:220,  7 CYC:876\n$CAA0:EA        NOP                                A:80 X:00 Y:80 P:NvUbdIzc SP:FB PPU:229,  7 CYC:879\n$CAA1:C0 7F     CPY #$7F                           A:80 X:00 Y:80 P:NvUbdIzc SP:FB PPU:235,  7 CYC:881\n$CAA3:90 07     BCC $CAAC                          A:80 X:00 Y:80 P:25 SP:FB PPU:241,  7 CYC:883\n$CAA5:F0 05     BEQ $CAAC                          A:80 X:00 Y:80 P:25 SP:FB PPU:247,  7 CYC:885\n$CAA7:30 03     BMI $CAAC                          A:80 X:00 Y:80 P:25 SP:FB PPU:253,  7 CYC:887\n$CAA9:4C B0 CA  JMP $CAB0                          A:80 X:00 Y:80 P:25 SP:FB PPU:259,  7 CYC:889\n$CAB0:EA        NOP                                A:80 X:00 Y:80 P:25 SP:FB PPU:268,  7 CYC:892\n$CAB1:24 01     BIT $01 = #$FF                     A:80 X:00 Y:80 P:25 SP:FB PPU:274,  7 CYC:894\n$CAB3:A2 40     LDX #$40                           A:80 X:00 Y:80 P:E5 SP:FB PPU:283,  7 CYC:897\n$CAB5:E0 40     CPX #$40                           A:80 X:40 Y:80 P:65 SP:FB PPU:289,  7 CYC:899\n$CAB7:D0 09     BNE $CAC2                          A:80 X:40 Y:80 P:67 SP:FB PPU:295,  7 CYC:901\n$CAB9:30 07     BMI $CAC2                          A:80 X:40 Y:80 P:67 SP:FB PPU:301,  7 CYC:903\n$CABB:90 05     BCC $CAC2                          A:80 X:40 Y:80 P:67 SP:FB PPU:307,  7 CYC:905\n$CABD:50 03     BVC $CAC2                          A:80 X:40 Y:80 P:67 SP:FB PPU:313,  7 CYC:907\n$CABF:4C C6 CA  JMP $CAC6                          A:80 X:40 Y:80 P:67 SP:FB PPU:319,  7 CYC:909\n$CAC6:EA        NOP                                A:80 X:40 Y:80 P:67 SP:FB PPU:328,  7 CYC:912\n$CAC7:B8        CLV                                A:80 X:40 Y:80 P:67 SP:FB PPU:334,  7 CYC:914\n$CAC8:E0 3F     CPX #$3F                           A:80 X:40 Y:80 P:nvUbdIZC SP:FB PPU:340,  7 CYC:916\n$CACA:F0 09     BEQ $CAD5                          A:80 X:40 Y:80 P:25 SP:FB PPU:  5,  8 CYC:918\n$CACC:30 07     BMI $CAD5                          A:80 X:40 Y:80 P:25 SP:FB PPU: 11,  8 CYC:920\n$CACE:90 05     BCC $CAD5                          A:80 X:40 Y:80 P:25 SP:FB PPU: 17,  8 CYC:922\n$CAD0:70 03     BVS $CAD5                          A:80 X:40 Y:80 P:25 SP:FB PPU: 23,  8 CYC:924\n$CAD2:4C D9 CA  JMP $CAD9                          A:80 X:40 Y:80 P:25 SP:FB PPU: 29,  8 CYC:926\n$CAD9:EA        NOP                                A:80 X:40 Y:80 P:25 SP:FB PPU: 38,  8 CYC:929\n$CADA:E0 41     CPX #$41                           A:80 X:40 Y:80 P:25 SP:FB PPU: 44,  8 CYC:931\n$CADC:F0 07     BEQ $CAE5                          A:80 X:40 Y:80 P:NvUbdIzc SP:FB PPU: 50,  8 CYC:933\n$CADE:10 05     BPL $CAE5                          A:80 X:40 Y:80 P:NvUbdIzc SP:FB PPU: 56,  8 CYC:935\n$CAE0:10 03     BPL $CAE5                          A:80 X:40 Y:80 P:NvUbdIzc SP:FB PPU: 62,  8 CYC:937\n$CAE2:4C E9 CA  JMP $CAE9                          A:80 X:40 Y:80 P:NvUbdIzc SP:FB PPU: 68,  8 CYC:939\n$CAE9:EA        NOP                                A:80 X:40 Y:80 P:NvUbdIzc SP:FB PPU: 77,  8 CYC:942\n$CAEA:A2 80     LDX #$80                           A:80 X:40 Y:80 P:NvUbdIzc SP:FB PPU: 83,  8 CYC:944\n$CAEC:E0 00     CPX #$00                           A:80 X:80 Y:80 P:NvUbdIzc SP:FB PPU: 89,  8 CYC:946\n$CAEE:F0 07     BEQ $CAF7                          A:80 X:80 Y:80 P:A5 SP:FB PPU: 95,  8 CYC:948\n$CAF0:10 05     BPL $CAF7                          A:80 X:80 Y:80 P:A5 SP:FB PPU:101,  8 CYC:950\n$CAF2:90 03     BCC $CAF7                          A:80 X:80 Y:80 P:A5 SP:FB PPU:107,  8 CYC:952\n$CAF4:4C FB CA  JMP $CAFB                          A:80 X:80 Y:80 P:A5 SP:FB PPU:113,  8 CYC:954\n$CAFB:EA        NOP                                A:80 X:80 Y:80 P:A5 SP:FB PPU:122,  8 CYC:957\n$CAFC:E0 80     CPX #$80                           A:80 X:80 Y:80 P:A5 SP:FB PPU:128,  8 CYC:959\n$CAFE:D0 07     BNE $CB07                          A:80 X:80 Y:80 P:nvUbdIZC SP:FB PPU:134,  8 CYC:961\n$CB00:30 05     BMI $CB07                          A:80 X:80 Y:80 P:nvUbdIZC SP:FB PPU:140,  8 CYC:963\n$CB02:90 03     BCC $CB07                          A:80 X:80 Y:80 P:nvUbdIZC SP:FB PPU:146,  8 CYC:965\n$CB04:4C 0B CB  JMP $CB0B                          A:80 X:80 Y:80 P:nvUbdIZC SP:FB PPU:152,  8 CYC:967\n$CB0B:EA        NOP                                A:80 X:80 Y:80 P:nvUbdIZC SP:FB PPU:161,  8 CYC:970\n$CB0C:E0 81     CPX #$81                           A:80 X:80 Y:80 P:nvUbdIZC SP:FB PPU:167,  8 CYC:972\n$CB0E:B0 07     BCS $CB17                          A:80 X:80 Y:80 P:NvUbdIzc SP:FB PPU:173,  8 CYC:974\n$CB10:F0 05     BEQ $CB17                          A:80 X:80 Y:80 P:NvUbdIzc SP:FB PPU:179,  8 CYC:976\n$CB12:10 03     BPL $CB17                          A:80 X:80 Y:80 P:NvUbdIzc SP:FB PPU:185,  8 CYC:978\n$CB14:4C 1B CB  JMP $CB1B                          A:80 X:80 Y:80 P:NvUbdIzc SP:FB PPU:191,  8 CYC:980\n$CB1B:EA        NOP                                A:80 X:80 Y:80 P:NvUbdIzc SP:FB PPU:200,  8 CYC:983\n$CB1C:E0 7F     CPX #$7F                           A:80 X:80 Y:80 P:NvUbdIzc SP:FB PPU:206,  8 CYC:985\n$CB1E:90 07     BCC $CB27                          A:80 X:80 Y:80 P:25 SP:FB PPU:212,  8 CYC:987\n$CB20:F0 05     BEQ $CB27                          A:80 X:80 Y:80 P:25 SP:FB PPU:218,  8 CYC:989\n$CB22:30 03     BMI $CB27                          A:80 X:80 Y:80 P:25 SP:FB PPU:224,  8 CYC:991\n$CB24:4C 2B CB  JMP $CB2B                          A:80 X:80 Y:80 P:25 SP:FB PPU:230,  8 CYC:993\n$CB2B:EA        NOP                                A:80 X:80 Y:80 P:25 SP:FB PPU:239,  8 CYC:996\n$CB2C:38        SEC                                A:80 X:80 Y:80 P:25 SP:FB PPU:245,  8 CYC:998\n$CB2D:B8        CLV                                A:80 X:80 Y:80 P:25 SP:FB PPU:251,  8 CYC:1000\n$CB2E:A2 9F     LDX #$9F                           A:80 X:80 Y:80 P:25 SP:FB PPU:257,  8 CYC:1002\n$CB30:F0 09     BEQ $CB3B                          A:80 X:9F Y:80 P:A5 SP:FB PPU:263,  8 CYC:1004\n$CB32:10 07     BPL $CB3B                          A:80 X:9F Y:80 P:A5 SP:FB PPU:269,  8 CYC:1006\n$CB34:70 05     BVS $CB3B                          A:80 X:9F Y:80 P:A5 SP:FB PPU:275,  8 CYC:1008\n$CB36:90 03     BCC $CB3B                          A:80 X:9F Y:80 P:A5 SP:FB PPU:281,  8 CYC:1010\n$CB38:4C 3F CB  JMP $CB3F                          A:80 X:9F Y:80 P:A5 SP:FB PPU:287,  8 CYC:1012\n$CB3F:EA        NOP                                A:80 X:9F Y:80 P:A5 SP:FB PPU:296,  8 CYC:1015\n$CB40:18        CLC                                A:80 X:9F Y:80 P:A5 SP:FB PPU:302,  8 CYC:1017\n$CB41:24 01     BIT $01 = #$FF                     A:80 X:9F Y:80 P:NvUbdIzc SP:FB PPU:308,  8 CYC:1019\n$CB43:A2 00     LDX #$00                           A:80 X:9F Y:80 P:NVUbdIzc SP:FB PPU:317,  8 CYC:1022\n$CB45:D0 09     BNE $CB50                          A:80 X:00 Y:80 P:nVUbdIZc SP:FB PPU:323,  8 CYC:1024\n$CB47:30 07     BMI $CB50                          A:80 X:00 Y:80 P:nVUbdIZc SP:FB PPU:329,  8 CYC:1026\n$CB49:50 05     BVC $CB50                          A:80 X:00 Y:80 P:nVUbdIZc SP:FB PPU:335,  8 CYC:1028\n$CB4B:B0 03     BCS $CB50                          A:80 X:00 Y:80 P:nVUbdIZc SP:FB PPU:  0,  9 CYC:1030\n$CB4D:4C 54 CB  JMP $CB54                          A:80 X:00 Y:80 P:nVUbdIZc SP:FB PPU:  6,  9 CYC:1032\n$CB54:EA        NOP                                A:80 X:00 Y:80 P:nVUbdIZc SP:FB PPU: 15,  9 CYC:1035\n$CB55:38        SEC                                A:80 X:00 Y:80 P:nVUbdIZc SP:FB PPU: 21,  9 CYC:1037\n$CB56:B8        CLV                                A:80 X:00 Y:80 P:67 SP:FB PPU: 27,  9 CYC:1039\n$CB57:A0 9F     LDY #$9F                           A:80 X:00 Y:80 P:nvUbdIZC SP:FB PPU: 33,  9 CYC:1041\n$CB59:F0 09     BEQ $CB64                          A:80 X:00 Y:9F P:A5 SP:FB PPU: 39,  9 CYC:1043\n$CB5B:10 07     BPL $CB64                          A:80 X:00 Y:9F P:A5 SP:FB PPU: 45,  9 CYC:1045\n$CB5D:70 05     BVS $CB64                          A:80 X:00 Y:9F P:A5 SP:FB PPU: 51,  9 CYC:1047\n$CB5F:90 03     BCC $CB64                          A:80 X:00 Y:9F P:A5 SP:FB PPU: 57,  9 CYC:1049\n$CB61:4C 68 CB  JMP $CB68                          A:80 X:00 Y:9F P:A5 SP:FB PPU: 63,  9 CYC:1051\n$CB68:EA        NOP                                A:80 X:00 Y:9F P:A5 SP:FB PPU: 72,  9 CYC:1054\n$CB69:18        CLC                                A:80 X:00 Y:9F P:A5 SP:FB PPU: 78,  9 CYC:1056\n$CB6A:24 01     BIT $01 = #$FF                     A:80 X:00 Y:9F P:NvUbdIzc SP:FB PPU: 84,  9 CYC:1058\n$CB6C:A0 00     LDY #$00                           A:80 X:00 Y:9F P:NVUbdIzc SP:FB PPU: 93,  9 CYC:1061\n$CB6E:D0 09     BNE $CB79                          A:80 X:00 Y:00 P:nVUbdIZc SP:FB PPU: 99,  9 CYC:1063\n$CB70:30 07     BMI $CB79                          A:80 X:00 Y:00 P:nVUbdIZc SP:FB PPU:105,  9 CYC:1065\n$CB72:50 05     BVC $CB79                          A:80 X:00 Y:00 P:nVUbdIZc SP:FB PPU:111,  9 CYC:1067\n$CB74:B0 03     BCS $CB79                          A:80 X:00 Y:00 P:nVUbdIZc SP:FB PPU:117,  9 CYC:1069\n$CB76:4C 7D CB  JMP $CB7D                          A:80 X:00 Y:00 P:nVUbdIZc SP:FB PPU:123,  9 CYC:1071\n$CB7D:EA        NOP                                A:80 X:00 Y:00 P:nVUbdIZc SP:FB PPU:132,  9 CYC:1074\n$CB7E:A9 55     LDA #$55                           A:80 X:00 Y:00 P:nVUbdIZc SP:FB PPU:138,  9 CYC:1076\n$CB80:A2 AA     LDX #$AA                           A:55 X:00 Y:00 P:64 SP:FB PPU:144,  9 CYC:1078\n$CB82:A0 33     LDY #$33                           A:55 X:AA Y:00 P:NVUbdIzc SP:FB PPU:150,  9 CYC:1080\n$CB84:C9 55     CMP #$55                           A:55 X:AA Y:33 P:64 SP:FB PPU:156,  9 CYC:1082\n$CB86:D0 23     BNE $CBAB                          A:55 X:AA Y:33 P:67 SP:FB PPU:162,  9 CYC:1084\n$CB88:E0 AA     CPX #$AA                           A:55 X:AA Y:33 P:67 SP:FB PPU:168,  9 CYC:1086\n$CB8A:D0 1F     BNE $CBAB                          A:55 X:AA Y:33 P:67 SP:FB PPU:174,  9 CYC:1088\n$CB8C:C0 33     CPY #$33                           A:55 X:AA Y:33 P:67 SP:FB PPU:180,  9 CYC:1090\n$CB8E:D0 1B     BNE $CBAB                          A:55 X:AA Y:33 P:67 SP:FB PPU:186,  9 CYC:1092\n$CB90:C9 55     CMP #$55                           A:55 X:AA Y:33 P:67 SP:FB PPU:192,  9 CYC:1094\n$CB92:D0 17     BNE $CBAB                          A:55 X:AA Y:33 P:67 SP:FB PPU:198,  9 CYC:1096\n$CB94:E0 AA     CPX #$AA                           A:55 X:AA Y:33 P:67 SP:FB PPU:204,  9 CYC:1098\n$CB96:D0 13     BNE $CBAB                          A:55 X:AA Y:33 P:67 SP:FB PPU:210,  9 CYC:1100\n$CB98:C0 33     CPY #$33                           A:55 X:AA Y:33 P:67 SP:FB PPU:216,  9 CYC:1102\n$CB9A:D0 0F     BNE $CBAB                          A:55 X:AA Y:33 P:67 SP:FB PPU:222,  9 CYC:1104\n$CB9C:C9 56     CMP #$56                           A:55 X:AA Y:33 P:67 SP:FB PPU:228,  9 CYC:1106\n$CB9E:F0 0B     BEQ $CBAB                          A:55 X:AA Y:33 P:NVUbdIzc SP:FB PPU:234,  9 CYC:1108\n$CBA0:E0 AB     CPX #$AB                           A:55 X:AA Y:33 P:NVUbdIzc SP:FB PPU:240,  9 CYC:1110\n$CBA2:F0 07     BEQ $CBAB                          A:55 X:AA Y:33 P:NVUbdIzc SP:FB PPU:246,  9 CYC:1112\n$CBA4:C0 34     CPY #$34                           A:55 X:AA Y:33 P:NVUbdIzc SP:FB PPU:252,  9 CYC:1114\n$CBA6:F0 03     BEQ $CBAB                          A:55 X:AA Y:33 P:NVUbdIzc SP:FB PPU:258,  9 CYC:1116\n$CBA8:4C AF CB  JMP $CBAF                          A:55 X:AA Y:33 P:NVUbdIzc SP:FB PPU:264,  9 CYC:1118\n$CBAF:A0 71     LDY #$71                           A:55 X:AA Y:33 P:NVUbdIzc SP:FB PPU:273,  9 CYC:1121\n$CBB1:20 31 F9  JSR $F931                          A:55 X:AA Y:71 P:64 SP:FB PPU:279,  9 CYC:1123\n$F931:24 01     BIT $01 = #$FF                     A:55 X:AA Y:71 P:64 SP:F9 PPU:297,  9 CYC:1129\n$F933:A9 40     LDA #$40                           A:55 X:AA Y:71 P:NVUbdIzc SP:F9 PPU:306,  9 CYC:1132\n$F935:38        SEC                                A:40 X:AA Y:71 P:64 SP:F9 PPU:312,  9 CYC:1134\n$F936:60        RTS                                A:40 X:AA Y:71 P:65 SP:F9 PPU:318,  9 CYC:1136\n$CBB4:E9 40     SBC #$40                           A:40 X:AA Y:71 P:65 SP:FB PPU:336,  9 CYC:1142\n$CBB6:20 37 F9  JSR $F937                          A:00 X:AA Y:71 P:nvUbdIZC SP:FB PPU:  1, 10 CYC:1144\n$F937:30 0B     BMI $F944                          A:00 X:AA Y:71 P:nvUbdIZC SP:F9 PPU: 19, 10 CYC:1150\n$F939:90 09     BCC $F944                          A:00 X:AA Y:71 P:nvUbdIZC SP:F9 PPU: 25, 10 CYC:1152\n$F93B:D0 07     BNE $F944                          A:00 X:AA Y:71 P:nvUbdIZC SP:F9 PPU: 31, 10 CYC:1154\n$F93D:70 05     BVS $F944                          A:00 X:AA Y:71 P:nvUbdIZC SP:F9 PPU: 37, 10 CYC:1156\n$F93F:C9 00     CMP #$00                           A:00 X:AA Y:71 P:nvUbdIZC SP:F9 PPU: 43, 10 CYC:1158\n$F941:D0 01     BNE $F944                          A:00 X:AA Y:71 P:nvUbdIZC SP:F9 PPU: 49, 10 CYC:1160\n$F943:60        RTS                                A:00 X:AA Y:71 P:nvUbdIZC SP:F9 PPU: 55, 10 CYC:1162\n$CBB9:C8        INY                                A:00 X:AA Y:71 P:nvUbdIZC SP:FB PPU: 73, 10 CYC:1168\n$CBBA:20 47 F9  JSR $F947                          A:00 X:AA Y:72 P:25 SP:FB PPU: 79, 10 CYC:1170\n$F947:B8        CLV                                A:00 X:AA Y:72 P:25 SP:F9 PPU: 97, 10 CYC:1176\n$F948:38        SEC                                A:00 X:AA Y:72 P:25 SP:F9 PPU:103, 10 CYC:1178\n$F949:A9 40     LDA #$40                           A:00 X:AA Y:72 P:25 SP:F9 PPU:109, 10 CYC:1180\n$F94B:60        RTS                                A:40 X:AA Y:72 P:25 SP:F9 PPU:115, 10 CYC:1182\n$CBBD:E9 3F     SBC #$3F                           A:40 X:AA Y:72 P:25 SP:FB PPU:133, 10 CYC:1188\n$CBBF:20 4C F9  JSR $F94C                          A:01 X:AA Y:72 P:25 SP:FB PPU:139, 10 CYC:1190\n$F94C:F0 0B     BEQ $F959                          A:01 X:AA Y:72 P:25 SP:F9 PPU:157, 10 CYC:1196\n$F94E:30 09     BMI $F959                          A:01 X:AA Y:72 P:25 SP:F9 PPU:163, 10 CYC:1198\n$F950:90 07     BCC $F959                          A:01 X:AA Y:72 P:25 SP:F9 PPU:169, 10 CYC:1200\n$F952:70 05     BVS $F959                          A:01 X:AA Y:72 P:25 SP:F9 PPU:175, 10 CYC:1202\n$F954:C9 01     CMP #$01                           A:01 X:AA Y:72 P:25 SP:F9 PPU:181, 10 CYC:1204\n$F956:D0 01     BNE $F959                          A:01 X:AA Y:72 P:nvUbdIZC SP:F9 PPU:187, 10 CYC:1206\n$F958:60        RTS                                A:01 X:AA Y:72 P:nvUbdIZC SP:F9 PPU:193, 10 CYC:1208\n$CBC2:C8        INY                                A:01 X:AA Y:72 P:nvUbdIZC SP:FB PPU:211, 10 CYC:1214\n$CBC3:20 5C F9  JSR $F95C                          A:01 X:AA Y:73 P:25 SP:FB PPU:217, 10 CYC:1216\n$F95C:A9 40     LDA #$40                           A:01 X:AA Y:73 P:25 SP:F9 PPU:235, 10 CYC:1222\n$F95E:38        SEC                                A:40 X:AA Y:73 P:25 SP:F9 PPU:241, 10 CYC:1224\n$F95F:24 01     BIT $01 = #$FF                     A:40 X:AA Y:73 P:25 SP:F9 PPU:247, 10 CYC:1226\n$F961:60        RTS                                A:40 X:AA Y:73 P:E5 SP:F9 PPU:256, 10 CYC:1229\n$CBC6:E9 41     SBC #$41                           A:40 X:AA Y:73 P:E5 SP:FB PPU:274, 10 CYC:1235\n$CBC8:20 62 F9  JSR $F962                          A:FF X:AA Y:73 P:NvUbdIzc SP:FB PPU:280, 10 CYC:1237\n$F962:B0 0B     BCS $F96F                          A:FF X:AA Y:73 P:NvUbdIzc SP:F9 PPU:298, 10 CYC:1243\n$F964:F0 09     BEQ $F96F                          A:FF X:AA Y:73 P:NvUbdIzc SP:F9 PPU:304, 10 CYC:1245\n$F966:10 07     BPL $F96F                          A:FF X:AA Y:73 P:NvUbdIzc SP:F9 PPU:310, 10 CYC:1247\n$F968:70 05     BVS $F96F                          A:FF X:AA Y:73 P:NvUbdIzc SP:F9 PPU:316, 10 CYC:1249\n$F96A:C9 FF     CMP #$FF                           A:FF X:AA Y:73 P:NvUbdIzc SP:F9 PPU:322, 10 CYC:1251\n$F96C:D0 01     BNE $F96F                          A:FF X:AA Y:73 P:nvUbdIZC SP:F9 PPU:328, 10 CYC:1253\n$F96E:60        RTS                                A:FF X:AA Y:73 P:nvUbdIZC SP:F9 PPU:334, 10 CYC:1255\n$CBCB:C8        INY                                A:FF X:AA Y:73 P:nvUbdIZC SP:FB PPU: 11, 11 CYC:1261\n$CBCC:20 72 F9  JSR $F972                          A:FF X:AA Y:74 P:25 SP:FB PPU: 17, 11 CYC:1263\n$F972:18        CLC                                A:FF X:AA Y:74 P:25 SP:F9 PPU: 35, 11 CYC:1269\n$F973:A9 80     LDA #$80                           A:FF X:AA Y:74 P:nvUbdIzc SP:F9 PPU: 41, 11 CYC:1271\n$F975:60        RTS                                A:80 X:AA Y:74 P:NvUbdIzc SP:F9 PPU: 47, 11 CYC:1273\n$CBCF:E9 00     SBC #$00                           A:80 X:AA Y:74 P:NvUbdIzc SP:FB PPU: 65, 11 CYC:1279\n$CBD1:20 76 F9  JSR $F976                          A:7F X:AA Y:74 P:65 SP:FB PPU: 71, 11 CYC:1281\n$F976:90 05     BCC $F97D                          A:7F X:AA Y:74 P:65 SP:F9 PPU: 89, 11 CYC:1287\n$F978:C9 7F     CMP #$7F                           A:7F X:AA Y:74 P:65 SP:F9 PPU: 95, 11 CYC:1289\n$F97A:D0 01     BNE $F97D                          A:7F X:AA Y:74 P:67 SP:F9 PPU:101, 11 CYC:1291\n$F97C:60        RTS                                A:7F X:AA Y:74 P:67 SP:F9 PPU:107, 11 CYC:1293\n$CBD4:C8        INY                                A:7F X:AA Y:74 P:67 SP:FB PPU:125, 11 CYC:1299\n$CBD5:20 80 F9  JSR $F980                          A:7F X:AA Y:75 P:65 SP:FB PPU:131, 11 CYC:1301\n$F980:38        SEC                                A:7F X:AA Y:75 P:65 SP:F9 PPU:149, 11 CYC:1307\n$F981:A9 81     LDA #$81                           A:7F X:AA Y:75 P:65 SP:F9 PPU:155, 11 CYC:1309\n$F983:60        RTS                                A:81 X:AA Y:75 P:E5 SP:F9 PPU:161, 11 CYC:1311\n$CBD8:E9 7F     SBC #$7F                           A:81 X:AA Y:75 P:E5 SP:FB PPU:179, 11 CYC:1317\n$CBDA:20 84 F9  JSR $F984                          A:02 X:AA Y:75 P:65 SP:FB PPU:185, 11 CYC:1319\n$F984:50 07     BVC $F98D                          A:02 X:AA Y:75 P:65 SP:F9 PPU:203, 11 CYC:1325\n$F986:90 05     BCC $F98D                          A:02 X:AA Y:75 P:65 SP:F9 PPU:209, 11 CYC:1327\n$F988:C9 02     CMP #$02                           A:02 X:AA Y:75 P:65 SP:F9 PPU:215, 11 CYC:1329\n$F98A:D0 01     BNE $F98D                          A:02 X:AA Y:75 P:67 SP:F9 PPU:221, 11 CYC:1331\n$F98C:60        RTS                                A:02 X:AA Y:75 P:67 SP:F9 PPU:227, 11 CYC:1333\n$CBDD:60        RTS                                A:02 X:AA Y:75 P:67 SP:FB PPU:245, 11 CYC:1339\n$C606:20 DE CB  JSR $CBDE                          A:02 X:AA Y:75 P:67 SP:FD PPU:263, 11 CYC:1345\n$CBDE:EA        NOP                                A:02 X:AA Y:75 P:67 SP:FB PPU:281, 11 CYC:1351\n$CBDF:A9 FF     LDA #$FF                           A:02 X:AA Y:75 P:67 SP:FB PPU:287, 11 CYC:1353\n$CBE1:85 01     STA $01 = #$FF                     A:FF X:AA Y:75 P:E5 SP:FB PPU:293, 11 CYC:1355\n$CBE3:A9 44     LDA #$44                           A:FF X:AA Y:75 P:E5 SP:FB PPU:302, 11 CYC:1358\n$CBE5:A2 55     LDX #$55                           A:44 X:AA Y:75 P:65 SP:FB PPU:308, 11 CYC:1360\n$CBE7:A0 66     LDY #$66                           A:44 X:55 Y:75 P:65 SP:FB PPU:314, 11 CYC:1362\n$CBE9:E8        INX                                A:44 X:55 Y:66 P:65 SP:FB PPU:320, 11 CYC:1364\n$CBEA:88        DEY                                A:44 X:56 Y:66 P:65 SP:FB PPU:326, 11 CYC:1366\n$CBEB:E0 56     CPX #$56                           A:44 X:56 Y:65 P:65 SP:FB PPU:332, 11 CYC:1368\n$CBED:D0 21     BNE $CC10                          A:44 X:56 Y:65 P:67 SP:FB PPU:338, 11 CYC:1370\n$CBEF:C0 65     CPY #$65                           A:44 X:56 Y:65 P:67 SP:FB PPU:  3, 12 CYC:1372\n$CBF1:D0 1D     BNE $CC10                          A:44 X:56 Y:65 P:67 SP:FB PPU:  9, 12 CYC:1374\n$CBF3:E8        INX                                A:44 X:56 Y:65 P:67 SP:FB PPU: 15, 12 CYC:1376\n$CBF4:E8        INX                                A:44 X:57 Y:65 P:65 SP:FB PPU: 21, 12 CYC:1378\n$CBF5:88        DEY                                A:44 X:58 Y:65 P:65 SP:FB PPU: 27, 12 CYC:1380\n$CBF6:88        DEY                                A:44 X:58 Y:64 P:65 SP:FB PPU: 33, 12 CYC:1382\n$CBF7:E0 58     CPX #$58                           A:44 X:58 Y:63 P:65 SP:FB PPU: 39, 12 CYC:1384\n$CBF9:D0 15     BNE $CC10                          A:44 X:58 Y:63 P:67 SP:FB PPU: 45, 12 CYC:1386\n$CBFB:C0 63     CPY #$63                           A:44 X:58 Y:63 P:67 SP:FB PPU: 51, 12 CYC:1388\n$CBFD:D0 11     BNE $CC10                          A:44 X:58 Y:63 P:67 SP:FB PPU: 57, 12 CYC:1390\n$CBFF:CA        DEX                                A:44 X:58 Y:63 P:67 SP:FB PPU: 63, 12 CYC:1392\n$CC00:C8        INY                                A:44 X:57 Y:63 P:65 SP:FB PPU: 69, 12 CYC:1394\n$CC01:E0 57     CPX #$57                           A:44 X:57 Y:64 P:65 SP:FB PPU: 75, 12 CYC:1396\n$CC03:D0 0B     BNE $CC10                          A:44 X:57 Y:64 P:67 SP:FB PPU: 81, 12 CYC:1398\n$CC05:C0 64     CPY #$64                           A:44 X:57 Y:64 P:67 SP:FB PPU: 87, 12 CYC:1400\n$CC07:D0 07     BNE $CC10                          A:44 X:57 Y:64 P:67 SP:FB PPU: 93, 12 CYC:1402\n$CC09:C9 44     CMP #$44                           A:44 X:57 Y:64 P:67 SP:FB PPU: 99, 12 CYC:1404\n$CC0B:D0 03     BNE $CC10                          A:44 X:57 Y:64 P:67 SP:FB PPU:105, 12 CYC:1406\n$CC0D:4C 14 CC  JMP $CC14                          A:44 X:57 Y:64 P:67 SP:FB PPU:111, 12 CYC:1408\n$CC14:EA        NOP                                A:44 X:57 Y:64 P:67 SP:FB PPU:120, 12 CYC:1411\n$CC15:38        SEC                                A:44 X:57 Y:64 P:67 SP:FB PPU:126, 12 CYC:1413\n$CC16:A2 69     LDX #$69                           A:44 X:57 Y:64 P:67 SP:FB PPU:132, 12 CYC:1415\n$CC18:A9 96     LDA #$96                           A:44 X:69 Y:64 P:65 SP:FB PPU:138, 12 CYC:1417\n$CC1A:24 01     BIT $01 = #$FF                     A:96 X:69 Y:64 P:E5 SP:FB PPU:144, 12 CYC:1419\n$CC1C:A0 FF     LDY #$FF                           A:96 X:69 Y:64 P:E5 SP:FB PPU:153, 12 CYC:1422\n$CC1E:C8        INY                                A:96 X:69 Y:FF P:E5 SP:FB PPU:159, 12 CYC:1424\n$CC1F:D0 3D     BNE $CC5E                          A:96 X:69 Y:00 P:67 SP:FB PPU:165, 12 CYC:1426\n$CC21:30 3B     BMI $CC5E                          A:96 X:69 Y:00 P:67 SP:FB PPU:171, 12 CYC:1428\n$CC23:90 39     BCC $CC5E                          A:96 X:69 Y:00 P:67 SP:FB PPU:177, 12 CYC:1430\n$CC25:50 37     BVC $CC5E                          A:96 X:69 Y:00 P:67 SP:FB PPU:183, 12 CYC:1432\n$CC27:C0 00     CPY #$00                           A:96 X:69 Y:00 P:67 SP:FB PPU:189, 12 CYC:1434\n$CC29:D0 33     BNE $CC5E                          A:96 X:69 Y:00 P:67 SP:FB PPU:195, 12 CYC:1436\n$CC2B:C8        INY                                A:96 X:69 Y:00 P:67 SP:FB PPU:201, 12 CYC:1438\n$CC2C:F0 30     BEQ $CC5E                          A:96 X:69 Y:01 P:65 SP:FB PPU:207, 12 CYC:1440\n$CC2E:30 2E     BMI $CC5E                          A:96 X:69 Y:01 P:65 SP:FB PPU:213, 12 CYC:1442\n$CC30:90 2C     BCC $CC5E                          A:96 X:69 Y:01 P:65 SP:FB PPU:219, 12 CYC:1444\n$CC32:50 2A     BVC $CC5E                          A:96 X:69 Y:01 P:65 SP:FB PPU:225, 12 CYC:1446\n$CC34:18        CLC                                A:96 X:69 Y:01 P:65 SP:FB PPU:231, 12 CYC:1448\n$CC35:B8        CLV                                A:96 X:69 Y:01 P:64 SP:FB PPU:237, 12 CYC:1450\n$CC36:A0 00     LDY #$00                           A:96 X:69 Y:01 P:nvUbdIzc SP:FB PPU:243, 12 CYC:1452\n$CC38:88        DEY                                A:96 X:69 Y:00 P:nvUbdIZc SP:FB PPU:249, 12 CYC:1454\n$CC39:F0 23     BEQ $CC5E                          A:96 X:69 Y:FF P:NvUbdIzc SP:FB PPU:255, 12 CYC:1456\n$CC3B:10 21     BPL $CC5E                          A:96 X:69 Y:FF P:NvUbdIzc SP:FB PPU:261, 12 CYC:1458\n$CC3D:B0 1F     BCS $CC5E                          A:96 X:69 Y:FF P:NvUbdIzc SP:FB PPU:267, 12 CYC:1460\n$CC3F:70 1D     BVS $CC5E                          A:96 X:69 Y:FF P:NvUbdIzc SP:FB PPU:273, 12 CYC:1462\n$CC41:C0 FF     CPY #$FF                           A:96 X:69 Y:FF P:NvUbdIzc SP:FB PPU:279, 12 CYC:1464\n$CC43:D0 19     BNE $CC5E                          A:96 X:69 Y:FF P:nvUbdIZC SP:FB PPU:285, 12 CYC:1466\n$CC45:18        CLC                                A:96 X:69 Y:FF P:nvUbdIZC SP:FB PPU:291, 12 CYC:1468\n$CC46:88        DEY                                A:96 X:69 Y:FF P:nvUbdIZc SP:FB PPU:297, 12 CYC:1470\n$CC47:F0 15     BEQ $CC5E                          A:96 X:69 Y:FE P:NvUbdIzc SP:FB PPU:303, 12 CYC:1472\n$CC49:10 13     BPL $CC5E                          A:96 X:69 Y:FE P:NvUbdIzc SP:FB PPU:309, 12 CYC:1474\n$CC4B:B0 11     BCS $CC5E                          A:96 X:69 Y:FE P:NvUbdIzc SP:FB PPU:315, 12 CYC:1476\n$CC4D:70 0F     BVS $CC5E                          A:96 X:69 Y:FE P:NvUbdIzc SP:FB PPU:321, 12 CYC:1478\n$CC4F:C0 FE     CPY #$FE                           A:96 X:69 Y:FE P:NvUbdIzc SP:FB PPU:327, 12 CYC:1480\n$CC51:D0 0B     BNE $CC5E                          A:96 X:69 Y:FE P:nvUbdIZC SP:FB PPU:333, 12 CYC:1482\n$CC53:C9 96     CMP #$96                           A:96 X:69 Y:FE P:nvUbdIZC SP:FB PPU:339, 12 CYC:1484\n$CC55:D0 07     BNE $CC5E                          A:96 X:69 Y:FE P:nvUbdIZC SP:FB PPU:  4, 13 CYC:1486\n$CC57:E0 69     CPX #$69                           A:96 X:69 Y:FE P:nvUbdIZC SP:FB PPU: 10, 13 CYC:1488\n$CC59:D0 03     BNE $CC5E                          A:96 X:69 Y:FE P:nvUbdIZC SP:FB PPU: 16, 13 CYC:1490\n$CC5B:4C 62 CC  JMP $CC62                          A:96 X:69 Y:FE P:nvUbdIZC SP:FB PPU: 22, 13 CYC:1492\n$CC62:EA        NOP                                A:96 X:69 Y:FE P:nvUbdIZC SP:FB PPU: 31, 13 CYC:1495\n$CC63:38        SEC                                A:96 X:69 Y:FE P:nvUbdIZC SP:FB PPU: 37, 13 CYC:1497\n$CC64:A0 69     LDY #$69                           A:96 X:69 Y:FE P:nvUbdIZC SP:FB PPU: 43, 13 CYC:1499\n$CC66:A9 96     LDA #$96                           A:96 X:69 Y:69 P:25 SP:FB PPU: 49, 13 CYC:1501\n$CC68:24 01     BIT $01 = #$FF                     A:96 X:69 Y:69 P:A5 SP:FB PPU: 55, 13 CYC:1503\n$CC6A:A2 FF     LDX #$FF                           A:96 X:69 Y:69 P:E5 SP:FB PPU: 64, 13 CYC:1506\n$CC6C:E8        INX                                A:96 X:FF Y:69 P:E5 SP:FB PPU: 70, 13 CYC:1508\n$CC6D:D0 3D     BNE $CCAC                          A:96 X:00 Y:69 P:67 SP:FB PPU: 76, 13 CYC:1510\n$CC6F:30 3B     BMI $CCAC                          A:96 X:00 Y:69 P:67 SP:FB PPU: 82, 13 CYC:1512\n$CC71:90 39     BCC $CCAC                          A:96 X:00 Y:69 P:67 SP:FB PPU: 88, 13 CYC:1514\n$CC73:50 37     BVC $CCAC                          A:96 X:00 Y:69 P:67 SP:FB PPU: 94, 13 CYC:1516\n$CC75:E0 00     CPX #$00                           A:96 X:00 Y:69 P:67 SP:FB PPU:100, 13 CYC:1518\n$CC77:D0 33     BNE $CCAC                          A:96 X:00 Y:69 P:67 SP:FB PPU:106, 13 CYC:1520\n$CC79:E8        INX                                A:96 X:00 Y:69 P:67 SP:FB PPU:112, 13 CYC:1522\n$CC7A:F0 30     BEQ $CCAC                          A:96 X:01 Y:69 P:65 SP:FB PPU:118, 13 CYC:1524\n$CC7C:30 2E     BMI $CCAC                          A:96 X:01 Y:69 P:65 SP:FB PPU:124, 13 CYC:1526\n$CC7E:90 2C     BCC $CCAC                          A:96 X:01 Y:69 P:65 SP:FB PPU:130, 13 CYC:1528\n$CC80:50 2A     BVC $CCAC                          A:96 X:01 Y:69 P:65 SP:FB PPU:136, 13 CYC:1530\n$CC82:18        CLC                                A:96 X:01 Y:69 P:65 SP:FB PPU:142, 13 CYC:1532\n$CC83:B8        CLV                                A:96 X:01 Y:69 P:64 SP:FB PPU:148, 13 CYC:1534\n$CC84:A2 00     LDX #$00                           A:96 X:01 Y:69 P:nvUbdIzc SP:FB PPU:154, 13 CYC:1536\n$CC86:CA        DEX                                A:96 X:00 Y:69 P:nvUbdIZc SP:FB PPU:160, 13 CYC:1538\n$CC87:F0 23     BEQ $CCAC                          A:96 X:FF Y:69 P:NvUbdIzc SP:FB PPU:166, 13 CYC:1540\n$CC89:10 21     BPL $CCAC                          A:96 X:FF Y:69 P:NvUbdIzc SP:FB PPU:172, 13 CYC:1542\n$CC8B:B0 1F     BCS $CCAC                          A:96 X:FF Y:69 P:NvUbdIzc SP:FB PPU:178, 13 CYC:1544\n$CC8D:70 1D     BVS $CCAC                          A:96 X:FF Y:69 P:NvUbdIzc SP:FB PPU:184, 13 CYC:1546\n$CC8F:E0 FF     CPX #$FF                           A:96 X:FF Y:69 P:NvUbdIzc SP:FB PPU:190, 13 CYC:1548\n$CC91:D0 19     BNE $CCAC                          A:96 X:FF Y:69 P:nvUbdIZC SP:FB PPU:196, 13 CYC:1550\n$CC93:18        CLC                                A:96 X:FF Y:69 P:nvUbdIZC SP:FB PPU:202, 13 CYC:1552\n$CC94:CA        DEX                                A:96 X:FF Y:69 P:nvUbdIZc SP:FB PPU:208, 13 CYC:1554\n$CC95:F0 15     BEQ $CCAC                          A:96 X:FE Y:69 P:NvUbdIzc SP:FB PPU:214, 13 CYC:1556\n$CC97:10 13     BPL $CCAC                          A:96 X:FE Y:69 P:NvUbdIzc SP:FB PPU:220, 13 CYC:1558\n$CC99:B0 11     BCS $CCAC                          A:96 X:FE Y:69 P:NvUbdIzc SP:FB PPU:226, 13 CYC:1560\n$CC9B:70 0F     BVS $CCAC                          A:96 X:FE Y:69 P:NvUbdIzc SP:FB PPU:232, 13 CYC:1562\n$CC9D:E0 FE     CPX #$FE                           A:96 X:FE Y:69 P:NvUbdIzc SP:FB PPU:238, 13 CYC:1564\n$CC9F:D0 0B     BNE $CCAC                          A:96 X:FE Y:69 P:nvUbdIZC SP:FB PPU:244, 13 CYC:1566\n$CCA1:C9 96     CMP #$96                           A:96 X:FE Y:69 P:nvUbdIZC SP:FB PPU:250, 13 CYC:1568\n$CCA3:D0 07     BNE $CCAC                          A:96 X:FE Y:69 P:nvUbdIZC SP:FB PPU:256, 13 CYC:1570\n$CCA5:C0 69     CPY #$69                           A:96 X:FE Y:69 P:nvUbdIZC SP:FB PPU:262, 13 CYC:1572\n$CCA7:D0 03     BNE $CCAC                          A:96 X:FE Y:69 P:nvUbdIZC SP:FB PPU:268, 13 CYC:1574\n$CCA9:4C B0 CC  JMP $CCB0                          A:96 X:FE Y:69 P:nvUbdIZC SP:FB PPU:274, 13 CYC:1576\n$CCB0:EA        NOP                                A:96 X:FE Y:69 P:nvUbdIZC SP:FB PPU:283, 13 CYC:1579\n$CCB1:A9 85     LDA #$85                           A:96 X:FE Y:69 P:nvUbdIZC SP:FB PPU:289, 13 CYC:1581\n$CCB3:A2 34     LDX #$34                           A:85 X:FE Y:69 P:A5 SP:FB PPU:295, 13 CYC:1583\n$CCB5:A0 99     LDY #$99                           A:85 X:34 Y:69 P:25 SP:FB PPU:301, 13 CYC:1585\n$CCB7:18        CLC                                A:85 X:34 Y:99 P:A5 SP:FB PPU:307, 13 CYC:1587\n$CCB8:24 01     BIT $01 = #$FF                     A:85 X:34 Y:99 P:NvUbdIzc SP:FB PPU:313, 13 CYC:1589\n$CCBA:A8        TAY                                A:85 X:34 Y:99 P:NVUbdIzc SP:FB PPU:322, 13 CYC:1592\n$CCBB:F0 2E     BEQ $CCEB                          A:85 X:34 Y:85 P:NVUbdIzc SP:FB PPU:328, 13 CYC:1594\n$CCBD:B0 2C     BCS $CCEB                          A:85 X:34 Y:85 P:NVUbdIzc SP:FB PPU:334, 13 CYC:1596\n$CCBF:50 2A     BVC $CCEB                          A:85 X:34 Y:85 P:NVUbdIzc SP:FB PPU:340, 13 CYC:1598\n$CCC1:10 28     BPL $CCEB                          A:85 X:34 Y:85 P:NVUbdIzc SP:FB PPU:  5, 14 CYC:1600\n$CCC3:C9 85     CMP #$85                           A:85 X:34 Y:85 P:NVUbdIzc SP:FB PPU: 11, 14 CYC:1602\n$CCC5:D0 24     BNE $CCEB                          A:85 X:34 Y:85 P:67 SP:FB PPU: 17, 14 CYC:1604\n$CCC7:E0 34     CPX #$34                           A:85 X:34 Y:85 P:67 SP:FB PPU: 23, 14 CYC:1606\n$CCC9:D0 20     BNE $CCEB                          A:85 X:34 Y:85 P:67 SP:FB PPU: 29, 14 CYC:1608\n$CCCB:C0 85     CPY #$85                           A:85 X:34 Y:85 P:67 SP:FB PPU: 35, 14 CYC:1610\n$CCCD:D0 1C     BNE $CCEB                          A:85 X:34 Y:85 P:67 SP:FB PPU: 41, 14 CYC:1612\n$CCCF:A9 00     LDA #$00                           A:85 X:34 Y:85 P:67 SP:FB PPU: 47, 14 CYC:1614\n$CCD1:38        SEC                                A:00 X:34 Y:85 P:67 SP:FB PPU: 53, 14 CYC:1616\n$CCD2:B8        CLV                                A:00 X:34 Y:85 P:67 SP:FB PPU: 59, 14 CYC:1618\n$CCD3:A8        TAY                                A:00 X:34 Y:85 P:nvUbdIZC SP:FB PPU: 65, 14 CYC:1620\n$CCD4:D0 15     BNE $CCEB                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU: 71, 14 CYC:1622\n$CCD6:90 13     BCC $CCEB                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU: 77, 14 CYC:1624\n$CCD8:70 11     BVS $CCEB                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU: 83, 14 CYC:1626\n$CCDA:30 0F     BMI $CCEB                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU: 89, 14 CYC:1628\n$CCDC:C9 00     CMP #$00                           A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU: 95, 14 CYC:1630\n$CCDE:D0 0B     BNE $CCEB                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:101, 14 CYC:1632\n$CCE0:E0 34     CPX #$34                           A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:107, 14 CYC:1634\n$CCE2:D0 07     BNE $CCEB                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:113, 14 CYC:1636\n$CCE4:C0 00     CPY #$00                           A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:119, 14 CYC:1638\n$CCE6:D0 03     BNE $CCEB                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:125, 14 CYC:1640\n$CCE8:4C EF CC  JMP $CCEF                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:131, 14 CYC:1642\n$CCEF:EA        NOP                                A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:140, 14 CYC:1645\n$CCF0:A9 85     LDA #$85                           A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:146, 14 CYC:1647\n$CCF2:A2 34     LDX #$34                           A:85 X:34 Y:00 P:A5 SP:FB PPU:152, 14 CYC:1649\n$CCF4:A0 99     LDY #$99                           A:85 X:34 Y:00 P:25 SP:FB PPU:158, 14 CYC:1651\n$CCF6:18        CLC                                A:85 X:34 Y:99 P:A5 SP:FB PPU:164, 14 CYC:1653\n$CCF7:24 01     BIT $01 = #$FF                     A:85 X:34 Y:99 P:NvUbdIzc SP:FB PPU:170, 14 CYC:1655\n$CCF9:AA        TAX                                A:85 X:34 Y:99 P:NVUbdIzc SP:FB PPU:179, 14 CYC:1658\n$CCFA:F0 2E     BEQ $CD2A                          A:85 X:85 Y:99 P:NVUbdIzc SP:FB PPU:185, 14 CYC:1660\n$CCFC:B0 2C     BCS $CD2A                          A:85 X:85 Y:99 P:NVUbdIzc SP:FB PPU:191, 14 CYC:1662\n$CCFE:50 2A     BVC $CD2A                          A:85 X:85 Y:99 P:NVUbdIzc SP:FB PPU:197, 14 CYC:1664\n$CD00:10 28     BPL $CD2A                          A:85 X:85 Y:99 P:NVUbdIzc SP:FB PPU:203, 14 CYC:1666\n$CD02:C9 85     CMP #$85                           A:85 X:85 Y:99 P:NVUbdIzc SP:FB PPU:209, 14 CYC:1668\n$CD04:D0 24     BNE $CD2A                          A:85 X:85 Y:99 P:67 SP:FB PPU:215, 14 CYC:1670\n$CD06:E0 85     CPX #$85                           A:85 X:85 Y:99 P:67 SP:FB PPU:221, 14 CYC:1672\n$CD08:D0 20     BNE $CD2A                          A:85 X:85 Y:99 P:67 SP:FB PPU:227, 14 CYC:1674\n$CD0A:C0 99     CPY #$99                           A:85 X:85 Y:99 P:67 SP:FB PPU:233, 14 CYC:1676\n$CD0C:D0 1C     BNE $CD2A                          A:85 X:85 Y:99 P:67 SP:FB PPU:239, 14 CYC:1678\n$CD0E:A9 00     LDA #$00                           A:85 X:85 Y:99 P:67 SP:FB PPU:245, 14 CYC:1680\n$CD10:38        SEC                                A:00 X:85 Y:99 P:67 SP:FB PPU:251, 14 CYC:1682\n$CD11:B8        CLV                                A:00 X:85 Y:99 P:67 SP:FB PPU:257, 14 CYC:1684\n$CD12:AA        TAX                                A:00 X:85 Y:99 P:nvUbdIZC SP:FB PPU:263, 14 CYC:1686\n$CD13:D0 15     BNE $CD2A                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:269, 14 CYC:1688\n$CD15:90 13     BCC $CD2A                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:275, 14 CYC:1690\n$CD17:70 11     BVS $CD2A                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:281, 14 CYC:1692\n$CD19:30 0F     BMI $CD2A                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:287, 14 CYC:1694\n$CD1B:C9 00     CMP #$00                           A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:293, 14 CYC:1696\n$CD1D:D0 0B     BNE $CD2A                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:299, 14 CYC:1698\n$CD1F:E0 00     CPX #$00                           A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:305, 14 CYC:1700\n$CD21:D0 07     BNE $CD2A                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:311, 14 CYC:1702\n$CD23:C0 99     CPY #$99                           A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:317, 14 CYC:1704\n$CD25:D0 03     BNE $CD2A                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:323, 14 CYC:1706\n$CD27:4C 2E CD  JMP $CD2E                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:329, 14 CYC:1708\n$CD2E:EA        NOP                                A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:338, 14 CYC:1711\n$CD2F:A9 85     LDA #$85                           A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:  3, 15 CYC:1713\n$CD31:A2 34     LDX #$34                           A:85 X:00 Y:99 P:A5 SP:FB PPU:  9, 15 CYC:1715\n$CD33:A0 99     LDY #$99                           A:85 X:34 Y:99 P:25 SP:FB PPU: 15, 15 CYC:1717\n$CD35:18        CLC                                A:85 X:34 Y:99 P:A5 SP:FB PPU: 21, 15 CYC:1719\n$CD36:24 01     BIT $01 = #$FF                     A:85 X:34 Y:99 P:NvUbdIzc SP:FB PPU: 27, 15 CYC:1721\n$CD38:98        TYA                                A:85 X:34 Y:99 P:NVUbdIzc SP:FB PPU: 36, 15 CYC:1724\n$CD39:F0 2E     BEQ $CD69                          A:99 X:34 Y:99 P:NVUbdIzc SP:FB PPU: 42, 15 CYC:1726\n$CD3B:B0 2C     BCS $CD69                          A:99 X:34 Y:99 P:NVUbdIzc SP:FB PPU: 48, 15 CYC:1728\n$CD3D:50 2A     BVC $CD69                          A:99 X:34 Y:99 P:NVUbdIzc SP:FB PPU: 54, 15 CYC:1730\n$CD3F:10 28     BPL $CD69                          A:99 X:34 Y:99 P:NVUbdIzc SP:FB PPU: 60, 15 CYC:1732\n$CD41:C9 99     CMP #$99                           A:99 X:34 Y:99 P:NVUbdIzc SP:FB PPU: 66, 15 CYC:1734\n$CD43:D0 24     BNE $CD69                          A:99 X:34 Y:99 P:67 SP:FB PPU: 72, 15 CYC:1736\n$CD45:E0 34     CPX #$34                           A:99 X:34 Y:99 P:67 SP:FB PPU: 78, 15 CYC:1738\n$CD47:D0 20     BNE $CD69                          A:99 X:34 Y:99 P:67 SP:FB PPU: 84, 15 CYC:1740\n$CD49:C0 99     CPY #$99                           A:99 X:34 Y:99 P:67 SP:FB PPU: 90, 15 CYC:1742\n$CD4B:D0 1C     BNE $CD69                          A:99 X:34 Y:99 P:67 SP:FB PPU: 96, 15 CYC:1744\n$CD4D:A0 00     LDY #$00                           A:99 X:34 Y:99 P:67 SP:FB PPU:102, 15 CYC:1746\n$CD4F:38        SEC                                A:99 X:34 Y:00 P:67 SP:FB PPU:108, 15 CYC:1748\n$CD50:B8        CLV                                A:99 X:34 Y:00 P:67 SP:FB PPU:114, 15 CYC:1750\n$CD51:98        TYA                                A:99 X:34 Y:00 P:nvUbdIZC SP:FB PPU:120, 15 CYC:1752\n$CD52:D0 15     BNE $CD69                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:126, 15 CYC:1754\n$CD54:90 13     BCC $CD69                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:132, 15 CYC:1756\n$CD56:70 11     BVS $CD69                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:138, 15 CYC:1758\n$CD58:30 0F     BMI $CD69                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:144, 15 CYC:1760\n$CD5A:C9 00     CMP #$00                           A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:150, 15 CYC:1762\n$CD5C:D0 0B     BNE $CD69                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:156, 15 CYC:1764\n$CD5E:E0 34     CPX #$34                           A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:162, 15 CYC:1766\n$CD60:D0 07     BNE $CD69                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:168, 15 CYC:1768\n$CD62:C0 00     CPY #$00                           A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:174, 15 CYC:1770\n$CD64:D0 03     BNE $CD69                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:180, 15 CYC:1772\n$CD66:4C 6D CD  JMP $CD6D                          A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:186, 15 CYC:1774\n$CD6D:EA        NOP                                A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:195, 15 CYC:1777\n$CD6E:A9 85     LDA #$85                           A:00 X:34 Y:00 P:nvUbdIZC SP:FB PPU:201, 15 CYC:1779\n$CD70:A2 34     LDX #$34                           A:85 X:34 Y:00 P:A5 SP:FB PPU:207, 15 CYC:1781\n$CD72:A0 99     LDY #$99                           A:85 X:34 Y:00 P:25 SP:FB PPU:213, 15 CYC:1783\n$CD74:18        CLC                                A:85 X:34 Y:99 P:A5 SP:FB PPU:219, 15 CYC:1785\n$CD75:24 01     BIT $01 = #$FF                     A:85 X:34 Y:99 P:NvUbdIzc SP:FB PPU:225, 15 CYC:1787\n$CD77:8A        TXA                                A:85 X:34 Y:99 P:NVUbdIzc SP:FB PPU:234, 15 CYC:1790\n$CD78:F0 2E     BEQ $CDA8                          A:34 X:34 Y:99 P:64 SP:FB PPU:240, 15 CYC:1792\n$CD7A:B0 2C     BCS $CDA8                          A:34 X:34 Y:99 P:64 SP:FB PPU:246, 15 CYC:1794\n$CD7C:50 2A     BVC $CDA8                          A:34 X:34 Y:99 P:64 SP:FB PPU:252, 15 CYC:1796\n$CD7E:30 28     BMI $CDA8                          A:34 X:34 Y:99 P:64 SP:FB PPU:258, 15 CYC:1798\n$CD80:C9 34     CMP #$34                           A:34 X:34 Y:99 P:64 SP:FB PPU:264, 15 CYC:1800\n$CD82:D0 24     BNE $CDA8                          A:34 X:34 Y:99 P:67 SP:FB PPU:270, 15 CYC:1802\n$CD84:E0 34     CPX #$34                           A:34 X:34 Y:99 P:67 SP:FB PPU:276, 15 CYC:1804\n$CD86:D0 20     BNE $CDA8                          A:34 X:34 Y:99 P:67 SP:FB PPU:282, 15 CYC:1806\n$CD88:C0 99     CPY #$99                           A:34 X:34 Y:99 P:67 SP:FB PPU:288, 15 CYC:1808\n$CD8A:D0 1C     BNE $CDA8                          A:34 X:34 Y:99 P:67 SP:FB PPU:294, 15 CYC:1810\n$CD8C:A2 00     LDX #$00                           A:34 X:34 Y:99 P:67 SP:FB PPU:300, 15 CYC:1812\n$CD8E:38        SEC                                A:34 X:00 Y:99 P:67 SP:FB PPU:306, 15 CYC:1814\n$CD8F:B8        CLV                                A:34 X:00 Y:99 P:67 SP:FB PPU:312, 15 CYC:1816\n$CD90:8A        TXA                                A:34 X:00 Y:99 P:nvUbdIZC SP:FB PPU:318, 15 CYC:1818\n$CD91:D0 15     BNE $CDA8                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:324, 15 CYC:1820\n$CD93:90 13     BCC $CDA8                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:330, 15 CYC:1822\n$CD95:70 11     BVS $CDA8                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:336, 15 CYC:1824\n$CD97:30 0F     BMI $CDA8                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:  1, 16 CYC:1826\n$CD99:C9 00     CMP #$00                           A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU:  7, 16 CYC:1828\n$CD9B:D0 0B     BNE $CDA8                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU: 13, 16 CYC:1830\n$CD9D:E0 00     CPX #$00                           A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU: 19, 16 CYC:1832\n$CD9F:D0 07     BNE $CDA8                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU: 25, 16 CYC:1834\n$CDA1:C0 99     CPY #$99                           A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU: 31, 16 CYC:1836\n$CDA3:D0 03     BNE $CDA8                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU: 37, 16 CYC:1838\n$CDA5:4C AC CD  JMP $CDAC                          A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU: 43, 16 CYC:1840\n$CDAC:EA        NOP                                A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU: 52, 16 CYC:1843\n$CDAD:BA        TSX                                A:00 X:00 Y:99 P:nvUbdIZC SP:FB PPU: 58, 16 CYC:1845\n$CDAE:8E FF 07  STX $07FF = #$00                   A:00 X:FB Y:99 P:A5 SP:FB PPU: 64, 16 CYC:1847\n$CDB1:A0 33     LDY #$33                           A:00 X:FB Y:99 P:A5 SP:FB PPU: 76, 16 CYC:1851\n$CDB3:A2 69     LDX #$69                           A:00 X:FB Y:33 P:25 SP:FB PPU: 82, 16 CYC:1853\n$CDB5:A9 84     LDA #$84                           A:00 X:69 Y:33 P:25 SP:FB PPU: 88, 16 CYC:1855\n$CDB7:18        CLC                                A:84 X:69 Y:33 P:A5 SP:FB PPU: 94, 16 CYC:1857\n$CDB8:24 01     BIT $01 = #$FF                     A:84 X:69 Y:33 P:NvUbdIzc SP:FB PPU:100, 16 CYC:1859\n$CDBA:9A        TXS                                A:84 X:69 Y:33 P:NVUbdIzc SP:FB PPU:109, 16 CYC:1862\n$CDBB:F0 32     BEQ $CDEF                          A:84 X:69 Y:33 P:NVUbdIzc SP:69 PPU:115, 16 CYC:1864\n$CDBD:10 30     BPL $CDEF                          A:84 X:69 Y:33 P:NVUbdIzc SP:69 PPU:121, 16 CYC:1866\n$CDBF:B0 2E     BCS $CDEF                          A:84 X:69 Y:33 P:NVUbdIzc SP:69 PPU:127, 16 CYC:1868\n$CDC1:50 2C     BVC $CDEF                          A:84 X:69 Y:33 P:NVUbdIzc SP:69 PPU:133, 16 CYC:1870\n$CDC3:C9 84     CMP #$84                           A:84 X:69 Y:33 P:NVUbdIzc SP:69 PPU:139, 16 CYC:1872\n$CDC5:D0 28     BNE $CDEF                          A:84 X:69 Y:33 P:67 SP:69 PPU:145, 16 CYC:1874\n$CDC7:E0 69     CPX #$69                           A:84 X:69 Y:33 P:67 SP:69 PPU:151, 16 CYC:1876\n$CDC9:D0 24     BNE $CDEF                          A:84 X:69 Y:33 P:67 SP:69 PPU:157, 16 CYC:1878\n$CDCB:C0 33     CPY #$33                           A:84 X:69 Y:33 P:67 SP:69 PPU:163, 16 CYC:1880\n$CDCD:D0 20     BNE $CDEF                          A:84 X:69 Y:33 P:67 SP:69 PPU:169, 16 CYC:1882\n$CDCF:A0 01     LDY #$01                           A:84 X:69 Y:33 P:67 SP:69 PPU:175, 16 CYC:1884\n$CDD1:A9 04     LDA #$04                           A:84 X:69 Y:01 P:65 SP:69 PPU:181, 16 CYC:1886\n$CDD3:38        SEC                                A:04 X:69 Y:01 P:65 SP:69 PPU:187, 16 CYC:1888\n$CDD4:B8        CLV                                A:04 X:69 Y:01 P:65 SP:69 PPU:193, 16 CYC:1890\n$CDD5:A2 00     LDX #$00                           A:04 X:69 Y:01 P:25 SP:69 PPU:199, 16 CYC:1892\n$CDD7:BA        TSX                                A:04 X:00 Y:01 P:nvUbdIZC SP:69 PPU:205, 16 CYC:1894\n$CDD8:F0 15     BEQ $CDEF                          A:04 X:69 Y:01 P:25 SP:69 PPU:211, 16 CYC:1896\n$CDDA:30 13     BMI $CDEF                          A:04 X:69 Y:01 P:25 SP:69 PPU:217, 16 CYC:1898\n$CDDC:90 11     BCC $CDEF                          A:04 X:69 Y:01 P:25 SP:69 PPU:223, 16 CYC:1900\n$CDDE:70 0F     BVS $CDEF                          A:04 X:69 Y:01 P:25 SP:69 PPU:229, 16 CYC:1902\n$CDE0:E0 69     CPX #$69                           A:04 X:69 Y:01 P:25 SP:69 PPU:235, 16 CYC:1904\n$CDE2:D0 0B     BNE $CDEF                          A:04 X:69 Y:01 P:nvUbdIZC SP:69 PPU:241, 16 CYC:1906\n$CDE4:C9 04     CMP #$04                           A:04 X:69 Y:01 P:nvUbdIZC SP:69 PPU:247, 16 CYC:1908\n$CDE6:D0 07     BNE $CDEF                          A:04 X:69 Y:01 P:nvUbdIZC SP:69 PPU:253, 16 CYC:1910\n$CDE8:C0 01     CPY #$01                           A:04 X:69 Y:01 P:nvUbdIZC SP:69 PPU:259, 16 CYC:1912\n$CDEA:D0 03     BNE $CDEF                          A:04 X:69 Y:01 P:nvUbdIZC SP:69 PPU:265, 16 CYC:1914\n$CDEC:4C F3 CD  JMP $CDF3                          A:04 X:69 Y:01 P:nvUbdIZC SP:69 PPU:271, 16 CYC:1916\n$CDF3:AE FF 07  LDX $07FF = #$FB                     A:04 X:69 Y:01 P:nvUbdIZC SP:69 PPU:280, 16 CYC:1919\n$CDF6:9A        TXS                                A:04 X:FB Y:01 P:A5 SP:69 PPU:292, 16 CYC:1923\n$CDF7:60        RTS                                A:04 X:FB Y:01 P:A5 SP:FB PPU:298, 16 CYC:1925\n$C609:20 F8 CD  JSR $CDF8                          A:04 X:FB Y:01 P:A5 SP:FD PPU:316, 16 CYC:1931\n$CDF8:A9 FF     LDA #$FF                           A:04 X:FB Y:01 P:A5 SP:FB PPU:334, 16 CYC:1937\n$CDFA:85 01     STA $01 = #$FF                       A:FF X:FB Y:01 P:A5 SP:FB PPU:340, 16 CYC:1939\n$CDFC:BA        TSX                                A:FF X:FB Y:01 P:A5 SP:FB PPU:  8, 17 CYC:1942\n$CDFD:8E FF 07  STX $07FF = #$FB                     A:FF X:FB Y:01 P:A5 SP:FB PPU: 14, 17 CYC:1944\n$CE00:EA        NOP                                A:FF X:FB Y:01 P:A5 SP:FB PPU: 26, 17 CYC:1948\n$CE01:A2 80     LDX #$80                           A:FF X:FB Y:01 P:A5 SP:FB PPU: 32, 17 CYC:1950\n$CE03:9A        TXS                                A:FF X:80 Y:01 P:A5 SP:FB PPU: 38, 17 CYC:1952\n$CE04:A9 33     LDA #$33                           A:FF X:80 Y:01 P:A5 SP:80 PPU: 44, 17 CYC:1954\n$CE06:48        PHA                                A:33 X:80 Y:01 P:25 SP:80 PPU: 50, 17 CYC:1956\n$CE07:A9 69     LDA #$69                           A:33 X:80 Y:01 P:25 SP:7F PPU: 59, 17 CYC:1959\n$CE09:48        PHA                                A:69 X:80 Y:01 P:25 SP:7F PPU: 65, 17 CYC:1961\n$CE0A:BA        TSX                                A:69 X:80 Y:01 P:25 SP:7E PPU: 74, 17 CYC:1964\n$CE0B:E0 7E     CPX #$7E                           A:69 X:7E Y:01 P:25 SP:7E PPU: 80, 17 CYC:1966\n$CE0D:D0 20     BNE $CE2F                          A:69 X:7E Y:01 P:nvUbdIZC SP:7E PPU: 86, 17 CYC:1968\n$CE0F:68        PLA                                A:69 X:7E Y:01 P:nvUbdIZC SP:7E PPU: 92, 17 CYC:1970\n$CE10:C9 69     CMP #$69                           A:69 X:7E Y:01 P:25 SP:7F PPU:104, 17 CYC:1974\n$CE12:D0 1B     BNE $CE2F                          A:69 X:7E Y:01 P:nvUbdIZC SP:7F PPU:110, 17 CYC:1976\n$CE14:68        PLA                                A:69 X:7E Y:01 P:nvUbdIZC SP:7F PPU:116, 17 CYC:1978\n$CE15:C9 33     CMP #$33                           A:33 X:7E Y:01 P:25 SP:80 PPU:128, 17 CYC:1982\n$CE17:D0 16     BNE $CE2F                          A:33 X:7E Y:01 P:nvUbdIZC SP:80 PPU:134, 17 CYC:1984\n$CE19:BA        TSX                                A:33 X:7E Y:01 P:nvUbdIZC SP:80 PPU:140, 17 CYC:1986\n$CE1A:E0 80     CPX #$80                           A:33 X:80 Y:01 P:A5 SP:80 PPU:146, 17 CYC:1988\n$CE1C:D0 11     BNE $CE2F                          A:33 X:80 Y:01 P:nvUbdIZC SP:80 PPU:152, 17 CYC:1990\n$CE1E:AD 80 01  LDA $0180 = #$33                     A:33 X:80 Y:01 P:nvUbdIZC SP:80 PPU:158, 17 CYC:1992\n$CE21:C9 33     CMP #$33                           A:33 X:80 Y:01 P:25 SP:80 PPU:170, 17 CYC:1996\n$CE23:D0 0A     BNE $CE2F                          A:33 X:80 Y:01 P:nvUbdIZC SP:80 PPU:176, 17 CYC:1998\n$CE25:AD 7F 01  LDA $017F = #$69                     A:33 X:80 Y:01 P:nvUbdIZC SP:80 PPU:182, 17 CYC:2000\n$CE28:C9 69     CMP #$69                           A:69 X:80 Y:01 P:25 SP:80 PPU:194, 17 CYC:2004\n$CE2A:D0 03     BNE $CE2F                          A:69 X:80 Y:01 P:nvUbdIZC SP:80 PPU:200, 17 CYC:2006\n$CE2C:4C 33 CE  JMP $CE33                          A:69 X:80 Y:01 P:nvUbdIZC SP:80 PPU:206, 17 CYC:2008\n$CE33:EA        NOP                                A:69 X:80 Y:01 P:nvUbdIZC SP:80 PPU:215, 17 CYC:2011\n$CE34:A2 80     LDX #$80                           A:69 X:80 Y:01 P:nvUbdIZC SP:80 PPU:221, 17 CYC:2013\n$CE36:9A        TXS                                A:69 X:80 Y:01 P:A5 SP:80 PPU:227, 17 CYC:2015\n$CE37:20 3D CE  JSR $CE3D                          A:69 X:80 Y:01 P:A5 SP:80 PPU:233, 17 CYC:2017\n$CE3D:BA        TSX                                A:69 X:80 Y:01 P:A5 SP:7E PPU:251, 17 CYC:2023\n$CE3E:E0 7E     CPX #$7E                           A:69 X:7E Y:01 P:25 SP:7E PPU:257, 17 CYC:2025\n$CE40:D0 19     BNE $CE5B                          A:69 X:7E Y:01 P:nvUbdIZC SP:7E PPU:263, 17 CYC:2027\n$CE42:68        PLA                                A:69 X:7E Y:01 P:nvUbdIZC SP:7E PPU:269, 17 CYC:2029\n$CE43:68        PLA                                A:39 X:7E Y:01 P:25 SP:7F PPU:281, 17 CYC:2033\n$CE44:BA        TSX                                A:CE X:7E Y:01 P:A5 SP:80 PPU:293, 17 CYC:2037\n$CE45:E0 80     CPX #$80                           A:CE X:80 Y:01 P:A5 SP:80 PPU:299, 17 CYC:2039\n$CE47:D0 12     BNE $CE5B                          A:CE X:80 Y:01 P:nvUbdIZC SP:80 PPU:305, 17 CYC:2041\n$CE49:A9 00     LDA #$00                           A:CE X:80 Y:01 P:nvUbdIZC SP:80 PPU:311, 17 CYC:2043\n$CE4B:20 4E CE  JSR $CE4E                          A:00 X:80 Y:01 P:nvUbdIZC SP:80 PPU:317, 17 CYC:2045\n$CE4E:68        PLA                                A:00 X:80 Y:01 P:nvUbdIZC SP:7E PPU:335, 17 CYC:2051\n$CE4F:C9 4D     CMP #$4D                           A:4D X:80 Y:01 P:25 SP:7F PPU:  6, 18 CYC:2055\n$CE51:D0 08     BNE $CE5B                          A:4D X:80 Y:01 P:nvUbdIZC SP:7F PPU: 12, 18 CYC:2057\n$CE53:68        PLA                                A:4D X:80 Y:01 P:nvUbdIZC SP:7F PPU: 18, 18 CYC:2059\n$CE54:C9 CE     CMP #$CE                           A:CE X:80 Y:01 P:A5 SP:80 PPU: 30, 18 CYC:2063\n$CE56:D0 03     BNE $CE5B                          A:CE X:80 Y:01 P:nvUbdIZC SP:80 PPU: 36, 18 CYC:2065\n$CE58:4C 5F CE  JMP $CE5F                          A:CE X:80 Y:01 P:nvUbdIZC SP:80 PPU: 42, 18 CYC:2067\n$CE5F:EA        NOP                                A:CE X:80 Y:01 P:nvUbdIZC SP:80 PPU: 51, 18 CYC:2070\n$CE60:A9 CE     LDA #$CE                           A:CE X:80 Y:01 P:nvUbdIZC SP:80 PPU: 57, 18 CYC:2072\n$CE62:48        PHA                                A:CE X:80 Y:01 P:A5 SP:80 PPU: 63, 18 CYC:2074\n$CE63:A9 66     LDA #$66                           A:CE X:80 Y:01 P:A5 SP:7F PPU: 72, 18 CYC:2077\n$CE65:48        PHA                                A:66 X:80 Y:01 P:25 SP:7F PPU: 78, 18 CYC:2079\n$CE66:60        RTS                                A:66 X:80 Y:01 P:25 SP:7E PPU: 87, 18 CYC:2082\n$CE67:A2 77     LDX #$77                           A:66 X:80 Y:01 P:25 SP:80 PPU:105, 18 CYC:2088\n$CE69:A0 69     LDY #$69                           A:66 X:77 Y:01 P:25 SP:80 PPU:111, 18 CYC:2090\n$CE6B:18        CLC                                A:66 X:77 Y:69 P:25 SP:80 PPU:117, 18 CYC:2092\n$CE6C:24 01     BIT $01 = #$FF                       A:66 X:77 Y:69 P:nvUbdIzc SP:80 PPU:123, 18 CYC:2094\n$CE6E:A9 83     LDA #$83                           A:66 X:77 Y:69 P:NVUbdIzc SP:80 PPU:132, 18 CYC:2097\n$CE70:20 66 CE  JSR $CE66                          A:83 X:77 Y:69 P:NVUbdIzc SP:80 PPU:138, 18 CYC:2099\n$CE66:60        RTS                                A:83 X:77 Y:69 P:NVUbdIzc SP:7E PPU:156, 18 CYC:2105\n$CE73:F0 24     BEQ $CE99                          A:83 X:77 Y:69 P:NVUbdIzc SP:80 PPU:174, 18 CYC:2111\n$CE75:10 22     BPL $CE99                          A:83 X:77 Y:69 P:NVUbdIzc SP:80 PPU:180, 18 CYC:2113\n$CE77:B0 20     BCS $CE99                          A:83 X:77 Y:69 P:NVUbdIzc SP:80 PPU:186, 18 CYC:2115\n$CE79:50 1E     BVC $CE99                          A:83 X:77 Y:69 P:NVUbdIzc SP:80 PPU:192, 18 CYC:2117\n$CE7B:C9 83     CMP #$83                           A:83 X:77 Y:69 P:NVUbdIzc SP:80 PPU:198, 18 CYC:2119\n$CE7D:D0 1A     BNE $CE99                          A:83 X:77 Y:69 P:67 SP:80 PPU:204, 18 CYC:2121\n$CE7F:C0 69     CPY #$69                           A:83 X:77 Y:69 P:67 SP:80 PPU:210, 18 CYC:2123\n$CE81:D0 16     BNE $CE99                          A:83 X:77 Y:69 P:67 SP:80 PPU:216, 18 CYC:2125\n$CE83:E0 77     CPX #$77                           A:83 X:77 Y:69 P:67 SP:80 PPU:222, 18 CYC:2127\n$CE85:D0 12     BNE $CE99                          A:83 X:77 Y:69 P:67 SP:80 PPU:228, 18 CYC:2129\n$CE87:38        SEC                                A:83 X:77 Y:69 P:67 SP:80 PPU:234, 18 CYC:2131\n$CE88:B8        CLV                                A:83 X:77 Y:69 P:67 SP:80 PPU:240, 18 CYC:2133\n$CE89:A9 00     LDA #$00                           A:83 X:77 Y:69 P:nvUbdIZC SP:80 PPU:246, 18 CYC:2135\n$CE8B:20 66 CE  JSR $CE66                          A:00 X:77 Y:69 P:nvUbdIZC SP:80 PPU:252, 18 CYC:2137\n$CE66:60        RTS                                A:00 X:77 Y:69 P:nvUbdIZC SP:7E PPU:270, 18 CYC:2143\n$CE8E:D0 09     BNE $CE99                          A:00 X:77 Y:69 P:nvUbdIZC SP:80 PPU:288, 18 CYC:2149\n$CE90:30 07     BMI $CE99                          A:00 X:77 Y:69 P:nvUbdIZC SP:80 PPU:294, 18 CYC:2151\n$CE92:90 05     BCC $CE99                          A:00 X:77 Y:69 P:nvUbdIZC SP:80 PPU:300, 18 CYC:2153\n$CE94:70 03     BVS $CE99                          A:00 X:77 Y:69 P:nvUbdIZC SP:80 PPU:306, 18 CYC:2155\n$CE96:4C 9D CE  JMP $CE9D                          A:00 X:77 Y:69 P:nvUbdIZC SP:80 PPU:312, 18 CYC:2157\n$CE9D:EA        NOP                                A:00 X:77 Y:69 P:nvUbdIZC SP:80 PPU:321, 18 CYC:2160\n$CE9E:A9 CE     LDA #$CE                           A:00 X:77 Y:69 P:nvUbdIZC SP:80 PPU:327, 18 CYC:2162\n$CEA0:48        PHA                                A:CE X:77 Y:69 P:A5 SP:80 PPU:333, 18 CYC:2164\n$CEA1:A9 AE     LDA #$AE                           A:CE X:77 Y:69 P:A5 SP:7F PPU:  1, 19 CYC:2167\n$CEA3:48        PHA                                A:AE X:77 Y:69 P:A5 SP:7F PPU:  7, 19 CYC:2169\n$CEA4:A9 65     LDA #$65                           A:AE X:77 Y:69 P:A5 SP:7E PPU: 16, 19 CYC:2172\n$CEA6:48        PHA                                A:65 X:77 Y:69 P:25 SP:7E PPU: 22, 19 CYC:2174\n$CEA7:A9 55     LDA #$55                           A:65 X:77 Y:69 P:25 SP:7D PPU: 31, 19 CYC:2177\n$CEA9:A0 88     LDY #$88                           A:55 X:77 Y:69 P:25 SP:7D PPU: 37, 19 CYC:2179\n$CEAB:A2 99     LDX #$99                           A:55 X:77 Y:88 P:A5 SP:7D PPU: 43, 19 CYC:2181\n$CEAD:40        RTI                                A:55 X:99 Y:88 P:A5 SP:7D PPU: 49, 19 CYC:2183\n$CEAE:30 35     BMI $CEE5                          A:55 X:99 Y:88 P:65 SP:80 PPU: 67, 19 CYC:2189\n$CEB0:50 33     BVC $CEE5                          A:55 X:99 Y:88 P:65 SP:80 PPU: 73, 19 CYC:2191\n$CEB2:F0 31     BEQ $CEE5                          A:55 X:99 Y:88 P:65 SP:80 PPU: 79, 19 CYC:2193\n$CEB4:90 2F     BCC $CEE5                          A:55 X:99 Y:88 P:65 SP:80 PPU: 85, 19 CYC:2195\n$CEB6:C9 55     CMP #$55                           A:55 X:99 Y:88 P:65 SP:80 PPU: 91, 19 CYC:2197\n$CEB8:D0 2B     BNE $CEE5                          A:55 X:99 Y:88 P:67 SP:80 PPU: 97, 19 CYC:2199\n$CEBA:C0 88     CPY #$88                           A:55 X:99 Y:88 P:67 SP:80 PPU:103, 19 CYC:2201\n$CEBC:D0 27     BNE $CEE5                          A:55 X:99 Y:88 P:67 SP:80 PPU:109, 19 CYC:2203\n$CEBE:E0 99     CPX #$99                           A:55 X:99 Y:88 P:67 SP:80 PPU:115, 19 CYC:2205\n$CEC0:D0 23     BNE $CEE5                          A:55 X:99 Y:88 P:67 SP:80 PPU:121, 19 CYC:2207\n$CEC2:A9 CE     LDA #$CE                           A:55 X:99 Y:88 P:67 SP:80 PPU:127, 19 CYC:2209\n$CEC4:48        PHA                                A:CE X:99 Y:88 P:E5 SP:80 PPU:133, 19 CYC:2211\n$CEC5:A9 CE     LDA #$CE                           A:CE X:99 Y:88 P:E5 SP:7F PPU:142, 19 CYC:2214\n$CEC7:48        PHA                                A:CE X:99 Y:88 P:E5 SP:7F PPU:148, 19 CYC:2216\n$CEC8:A9 87     LDA #$87                           A:CE X:99 Y:88 P:E5 SP:7E PPU:157, 19 CYC:2219\n$CECA:48        PHA                                A:87 X:99 Y:88 P:E5 SP:7E PPU:163, 19 CYC:2221\n$CECB:A9 55     LDA #$55                           A:87 X:99 Y:88 P:E5 SP:7D PPU:172, 19 CYC:2224\n$CECD:40        RTI                                A:55 X:99 Y:88 P:65 SP:7D PPU:178, 19 CYC:2226\n$CECE:10 15     BPL $CEE5                          A:55 X:99 Y:88 P:A7 SP:80 PPU:196, 19 CYC:2232\n$CED0:70 13     BVS $CEE5                          A:55 X:99 Y:88 P:A7 SP:80 PPU:202, 19 CYC:2234\n$CED2:D0 11     BNE $CEE5                          A:55 X:99 Y:88 P:A7 SP:80 PPU:208, 19 CYC:2236\n$CED4:90 0F     BCC $CEE5                          A:55 X:99 Y:88 P:A7 SP:80 PPU:214, 19 CYC:2238\n$CED6:C9 55     CMP #$55                           A:55 X:99 Y:88 P:A7 SP:80 PPU:220, 19 CYC:2240\n$CED8:D0 0B     BNE $CEE5                          A:55 X:99 Y:88 P:nvUbdIZC SP:80 PPU:226, 19 CYC:2242\n$CEDA:C0 88     CPY #$88                           A:55 X:99 Y:88 P:nvUbdIZC SP:80 PPU:232, 19 CYC:2244\n$CEDC:D0 07     BNE $CEE5                          A:55 X:99 Y:88 P:nvUbdIZC SP:80 PPU:238, 19 CYC:2246\n$CEDE:E0 99     CPX #$99                           A:55 X:99 Y:88 P:nvUbdIZC SP:80 PPU:244, 19 CYC:2248\n$CEE0:D0 03     BNE $CEE5                          A:55 X:99 Y:88 P:nvUbdIZC SP:80 PPU:250, 19 CYC:2250\n$CEE2:4C E9 CE  JMP $CEE9                          A:55 X:99 Y:88 P:nvUbdIZC SP:80 PPU:256, 19 CYC:2252\n$CEE9:AE FF 07  LDX $07FF = #$FB                     A:55 X:99 Y:88 P:nvUbdIZC SP:80 PPU:265, 19 CYC:2255\n$CEEC:9A        TXS                                A:55 X:FB Y:88 P:A5 SP:80 PPU:277, 19 CYC:2259\n$CEED:60        RTS                                A:55 X:FB Y:88 P:A5 SP:FB PPU:283, 19 CYC:2261\n$C60C:20 EE CE  JSR $CEEE                          A:55 X:FB Y:88 P:A5 SP:FD PPU:301, 19 CYC:2267\n$CEEE:A2 55     LDX #$55                           A:55 X:FB Y:88 P:A5 SP:FB PPU:319, 19 CYC:2273\n$CEF0:A0 69     LDY #$69                           A:55 X:55 Y:88 P:25 SP:FB PPU:325, 19 CYC:2275\n$CEF2:A9 FF     LDA #$FF                           A:55 X:55 Y:69 P:25 SP:FB PPU:331, 19 CYC:2277\n$CEF4:85 01     STA $01 = #$FF                       A:FF X:55 Y:69 P:A5 SP:FB PPU:337, 19 CYC:2279\n$CEF6:EA        NOP                                A:FF X:55 Y:69 P:A5 SP:FB PPU:  5, 20 CYC:2282\n$CEF7:24 01     BIT $01 = #$FF                       A:FF X:55 Y:69 P:A5 SP:FB PPU: 11, 20 CYC:2284\n$CEF9:38        SEC                                A:FF X:55 Y:69 P:E5 SP:FB PPU: 20, 20 CYC:2287\n$CEFA:A9 01     LDA #$01                           A:FF X:55 Y:69 P:E5 SP:FB PPU: 26, 20 CYC:2289\n$CEFC:4A        LSR A                              A:01 X:55 Y:69 P:65 SP:FB PPU: 32, 20 CYC:2291\n$CEFD:90 1D     BCC $CF1C                          A:00 X:55 Y:69 P:67 SP:FB PPU: 38, 20 CYC:2293\n$CEFF:D0 1B     BNE $CF1C                          A:00 X:55 Y:69 P:67 SP:FB PPU: 44, 20 CYC:2295\n$CF01:30 19     BMI $CF1C                          A:00 X:55 Y:69 P:67 SP:FB PPU: 50, 20 CYC:2297\n$CF03:50 17     BVC $CF1C                          A:00 X:55 Y:69 P:67 SP:FB PPU: 56, 20 CYC:2299\n$CF05:C9 00     CMP #$00                           A:00 X:55 Y:69 P:67 SP:FB PPU: 62, 20 CYC:2301\n$CF07:D0 13     BNE $CF1C                          A:00 X:55 Y:69 P:67 SP:FB PPU: 68, 20 CYC:2303\n$CF09:B8        CLV                                A:00 X:55 Y:69 P:67 SP:FB PPU: 74, 20 CYC:2305\n$CF0A:A9 AA     LDA #$AA                           A:00 X:55 Y:69 P:nvUbdIZC SP:FB PPU: 80, 20 CYC:2307\n$CF0C:4A        LSR A                              A:AA X:55 Y:69 P:A5 SP:FB PPU: 86, 20 CYC:2309\n$CF0D:B0 0D     BCS $CF1C                          A:55 X:55 Y:69 P:nvUbdIzc SP:FB PPU: 92, 20 CYC:2311\n$CF0F:F0 0B     BEQ $CF1C                          A:55 X:55 Y:69 P:nvUbdIzc SP:FB PPU: 98, 20 CYC:2313\n$CF11:30 09     BMI $CF1C                          A:55 X:55 Y:69 P:nvUbdIzc SP:FB PPU:104, 20 CYC:2315\n$CF13:70 07     BVS $CF1C                          A:55 X:55 Y:69 P:nvUbdIzc SP:FB PPU:110, 20 CYC:2317\n$CF15:C9 55     CMP #$55                           A:55 X:55 Y:69 P:nvUbdIzc SP:FB PPU:116, 20 CYC:2319\n$CF17:D0 03     BNE $CF1C                          A:55 X:55 Y:69 P:nvUbdIZC SP:FB PPU:122, 20 CYC:2321\n$CF19:4C 20 CF  JMP $CF20                          A:55 X:55 Y:69 P:nvUbdIZC SP:FB PPU:128, 20 CYC:2323\n$CF20:EA        NOP                                A:55 X:55 Y:69 P:nvUbdIZC SP:FB PPU:137, 20 CYC:2326\n$CF21:24 01     BIT $01 = #$FF                       A:55 X:55 Y:69 P:nvUbdIZC SP:FB PPU:143, 20 CYC:2328\n$CF23:38        SEC                                A:55 X:55 Y:69 P:E5 SP:FB PPU:152, 20 CYC:2331\n$CF24:A9 80     LDA #$80                           A:55 X:55 Y:69 P:E5 SP:FB PPU:158, 20 CYC:2333\n$CF26:0A        ASL A                              A:80 X:55 Y:69 P:E5 SP:FB PPU:164, 20 CYC:2335\n$CF27:90 1E     BCC $CF47                          A:00 X:55 Y:69 P:67 SP:FB PPU:170, 20 CYC:2337\n$CF29:D0 1C     BNE $CF47                          A:00 X:55 Y:69 P:67 SP:FB PPU:176, 20 CYC:2339\n$CF2B:30 1A     BMI $CF47                          A:00 X:55 Y:69 P:67 SP:FB PPU:182, 20 CYC:2341\n$CF2D:50 18     BVC $CF47                          A:00 X:55 Y:69 P:67 SP:FB PPU:188, 20 CYC:2343\n$CF2F:C9 00     CMP #$00                           A:00 X:55 Y:69 P:67 SP:FB PPU:194, 20 CYC:2345\n$CF31:D0 14     BNE $CF47                          A:00 X:55 Y:69 P:67 SP:FB PPU:200, 20 CYC:2347\n$CF33:B8        CLV                                A:00 X:55 Y:69 P:67 SP:FB PPU:206, 20 CYC:2349\n$CF34:38        SEC                                A:00 X:55 Y:69 P:nvUbdIZC SP:FB PPU:212, 20 CYC:2351\n$CF35:A9 55     LDA #$55                           A:00 X:55 Y:69 P:nvUbdIZC SP:FB PPU:218, 20 CYC:2353\n$CF37:0A        ASL A                              A:55 X:55 Y:69 P:25 SP:FB PPU:224, 20 CYC:2355\n$CF38:B0 0D     BCS $CF47                          A:AA X:55 Y:69 P:NvUbdIzc SP:FB PPU:230, 20 CYC:2357\n$CF3A:F0 0B     BEQ $CF47                          A:AA X:55 Y:69 P:NvUbdIzc SP:FB PPU:236, 20 CYC:2359\n$CF3C:10 09     BPL $CF47                          A:AA X:55 Y:69 P:NvUbdIzc SP:FB PPU:242, 20 CYC:2361\n$CF3E:70 07     BVS $CF47                          A:AA X:55 Y:69 P:NvUbdIzc SP:FB PPU:248, 20 CYC:2363\n$CF40:C9 AA     CMP #$AA                           A:AA X:55 Y:69 P:NvUbdIzc SP:FB PPU:254, 20 CYC:2365\n$CF42:D0 03     BNE $CF47                          A:AA X:55 Y:69 P:nvUbdIZC SP:FB PPU:260, 20 CYC:2367\n$CF44:4C 4B CF  JMP $CF4B                          A:AA X:55 Y:69 P:nvUbdIZC SP:FB PPU:266, 20 CYC:2369\n$CF4B:EA        NOP                                A:AA X:55 Y:69 P:nvUbdIZC SP:FB PPU:275, 20 CYC:2372\n$CF4C:24 01     BIT $01 = #$FF                       A:AA X:55 Y:69 P:nvUbdIZC SP:FB PPU:281, 20 CYC:2374\n$CF4E:38        SEC                                A:AA X:55 Y:69 P:E5 SP:FB PPU:290, 20 CYC:2377\n$CF4F:A9 01     LDA #$01                           A:AA X:55 Y:69 P:E5 SP:FB PPU:296, 20 CYC:2379\n$CF51:6A        ROR A                              A:01 X:55 Y:69 P:65 SP:FB PPU:302, 20 CYC:2381\n$CF52:90 1E     BCC $CF72                          A:80 X:55 Y:69 P:E5 SP:FB PPU:308, 20 CYC:2383\n$CF54:F0 1C     BEQ $CF72                          A:80 X:55 Y:69 P:E5 SP:FB PPU:314, 20 CYC:2385\n$CF56:10 1A     BPL $CF72                          A:80 X:55 Y:69 P:E5 SP:FB PPU:320, 20 CYC:2387\n$CF58:50 18     BVC $CF72                          A:80 X:55 Y:69 P:E5 SP:FB PPU:326, 20 CYC:2389\n$CF5A:C9 80     CMP #$80                           A:80 X:55 Y:69 P:E5 SP:FB PPU:332, 20 CYC:2391\n$CF5C:D0 14     BNE $CF72                          A:80 X:55 Y:69 P:67 SP:FB PPU:338, 20 CYC:2393\n$CF5E:B8        CLV                                A:80 X:55 Y:69 P:67 SP:FB PPU:  3, 21 CYC:2395\n$CF5F:18        CLC                                A:80 X:55 Y:69 P:nvUbdIZC SP:FB PPU:  9, 21 CYC:2397\n$CF60:A9 55     LDA #$55                           A:80 X:55 Y:69 P:nvUbdIZc SP:FB PPU: 15, 21 CYC:2399\n$CF62:6A        ROR A                              A:55 X:55 Y:69 P:nvUbdIzc SP:FB PPU: 21, 21 CYC:2401\n$CF63:90 0D     BCC $CF72                          A:2A X:55 Y:69 P:25 SP:FB PPU: 27, 21 CYC:2403\n$CF65:F0 0B     BEQ $CF72                          A:2A X:55 Y:69 P:25 SP:FB PPU: 33, 21 CYC:2405\n$CF67:30 09     BMI $CF72                          A:2A X:55 Y:69 P:25 SP:FB PPU: 39, 21 CYC:2407\n$CF69:70 07     BVS $CF72                          A:2A X:55 Y:69 P:25 SP:FB PPU: 45, 21 CYC:2409\n$CF6B:C9 2A     CMP #$2A                           A:2A X:55 Y:69 P:25 SP:FB PPU: 51, 21 CYC:2411\n$CF6D:D0 03     BNE $CF72                          A:2A X:55 Y:69 P:nvUbdIZC SP:FB PPU: 57, 21 CYC:2413\n$CF6F:4C 76 CF  JMP $CF76                          A:2A X:55 Y:69 P:nvUbdIZC SP:FB PPU: 63, 21 CYC:2415\n$CF76:EA        NOP                                A:2A X:55 Y:69 P:nvUbdIZC SP:FB PPU: 72, 21 CYC:2418\n$CF77:24 01     BIT $01 = #$FF                       A:2A X:55 Y:69 P:nvUbdIZC SP:FB PPU: 78, 21 CYC:2420\n$CF79:38        SEC                                A:2A X:55 Y:69 P:E5 SP:FB PPU: 87, 21 CYC:2423\n$CF7A:A9 80     LDA #$80                           A:2A X:55 Y:69 P:E5 SP:FB PPU: 93, 21 CYC:2425\n$CF7C:2A        ROL A                              A:80 X:55 Y:69 P:E5 SP:FB PPU: 99, 21 CYC:2427\n$CF7D:90 1E     BCC $CF9D                          A:01 X:55 Y:69 P:65 SP:FB PPU:105, 21 CYC:2429\n$CF7F:F0 1C     BEQ $CF9D                          A:01 X:55 Y:69 P:65 SP:FB PPU:111, 21 CYC:2431\n$CF81:30 1A     BMI $CF9D                          A:01 X:55 Y:69 P:65 SP:FB PPU:117, 21 CYC:2433\n$CF83:50 18     BVC $CF9D                          A:01 X:55 Y:69 P:65 SP:FB PPU:123, 21 CYC:2435\n$CF85:C9 01     CMP #$01                           A:01 X:55 Y:69 P:65 SP:FB PPU:129, 21 CYC:2437\n$CF87:D0 14     BNE $CF9D                          A:01 X:55 Y:69 P:67 SP:FB PPU:135, 21 CYC:2439\n$CF89:B8        CLV                                A:01 X:55 Y:69 P:67 SP:FB PPU:141, 21 CYC:2441\n$CF8A:18        CLC                                A:01 X:55 Y:69 P:nvUbdIZC SP:FB PPU:147, 21 CYC:2443\n$CF8B:A9 55     LDA #$55                           A:01 X:55 Y:69 P:nvUbdIZc SP:FB PPU:153, 21 CYC:2445\n$CF8D:2A        ROL A                              A:55 X:55 Y:69 P:nvUbdIzc SP:FB PPU:159, 21 CYC:2447\n$CF8E:B0 0D     BCS $CF9D                          A:AA X:55 Y:69 P:NvUbdIzc SP:FB PPU:165, 21 CYC:2449\n$CF90:F0 0B     BEQ $CF9D                          A:AA X:55 Y:69 P:NvUbdIzc SP:FB PPU:171, 21 CYC:2451\n$CF92:10 09     BPL $CF9D                          A:AA X:55 Y:69 P:NvUbdIzc SP:FB PPU:177, 21 CYC:2453\n$CF94:70 07     BVS $CF9D                          A:AA X:55 Y:69 P:NvUbdIzc SP:FB PPU:183, 21 CYC:2455\n$CF96:C9 AA     CMP #$AA                           A:AA X:55 Y:69 P:NvUbdIzc SP:FB PPU:189, 21 CYC:2457\n$CF98:D0 03     BNE $CF9D                          A:AA X:55 Y:69 P:nvUbdIZC SP:FB PPU:195, 21 CYC:2459\n$CF9A:4C A1 CF  JMP $CFA1                          A:AA X:55 Y:69 P:nvUbdIZC SP:FB PPU:201, 21 CYC:2461\n$CFA1:60        RTS                                A:AA X:55 Y:69 P:nvUbdIZC SP:FB PPU:210, 21 CYC:2464\n$C60F:20 A2 CF  JSR $CFA2                          A:AA X:55 Y:69 P:nvUbdIZC SP:FD PPU:228, 21 CYC:2470\n$CFA2:A5 00     LDA $00 = #$00                       A:AA X:55 Y:69 P:nvUbdIZC SP:FB PPU:246, 21 CYC:2476\n$CFA4:8D FF 07  STA $07FF = #$FB                     A:00 X:55 Y:69 P:nvUbdIZC SP:FB PPU:255, 21 CYC:2479\n$CFA7:A9 00     LDA #$00                           A:00 X:55 Y:69 P:nvUbdIZC SP:FB PPU:267, 21 CYC:2483\n$CFA9:85 80     STA $80 = #$00                       A:00 X:55 Y:69 P:nvUbdIZC SP:FB PPU:273, 21 CYC:2485\n$CFAB:A9 02     LDA #$02                           A:00 X:55 Y:69 P:nvUbdIZC SP:FB PPU:282, 21 CYC:2488\n$CFAD:85 81     STA $81 = #$00                       A:02 X:55 Y:69 P:25 SP:FB PPU:288, 21 CYC:2490\n$CFAF:A9 FF     LDA #$FF                           A:02 X:55 Y:69 P:25 SP:FB PPU:297, 21 CYC:2493\n$CFB1:85 01     STA $01 = #$FF                       A:FF X:55 Y:69 P:A5 SP:FB PPU:303, 21 CYC:2495\n$CFB3:A9 00     LDA #$00                           A:FF X:55 Y:69 P:A5 SP:FB PPU:312, 21 CYC:2498\n$CFB5:85 82     STA $82 = #$00                       A:00 X:55 Y:69 P:nvUbdIZC SP:FB PPU:318, 21 CYC:2500\n$CFB7:A9 03     LDA #$03                           A:00 X:55 Y:69 P:nvUbdIZC SP:FB PPU:327, 21 CYC:2503\n$CFB9:85 83     STA $83 = #$00                       A:03 X:55 Y:69 P:25 SP:FB PPU:333, 21 CYC:2505\n$CFBB:85 84     STA $84 = #$00                       A:03 X:55 Y:69 P:25 SP:FB PPU:  1, 22 CYC:2508\n$CFBD:A9 00     LDA #$00                           A:03 X:55 Y:69 P:25 SP:FB PPU: 10, 22 CYC:2511\n$CFBF:85 FF     STA $FF = #$00                       A:00 X:55 Y:69 P:nvUbdIZC SP:FB PPU: 16, 22 CYC:2513\n$CFC1:A9 04     LDA #$04                           A:00 X:55 Y:69 P:nvUbdIZC SP:FB PPU: 25, 22 CYC:2516\n$CFC3:85 00     STA $00 = #$00                       A:04 X:55 Y:69 P:25 SP:FB PPU: 31, 22 CYC:2518\n$CFC5:A9 5A     LDA #$5A                           A:04 X:55 Y:69 P:25 SP:FB PPU: 40, 22 CYC:2521\n$CFC7:8D 00 02  STA $0200 = #$00                     A:5A X:55 Y:69 P:25 SP:FB PPU: 46, 22 CYC:2523\n$CFCA:A9 5B     LDA #$5B                           A:5A X:55 Y:69 P:25 SP:FB PPU: 58, 22 CYC:2527\n$CFCC:8D 00 03  STA $0300 = #$00                     A:5B X:55 Y:69 P:25 SP:FB PPU: 64, 22 CYC:2529\n$CFCF:A9 5C     LDA #$5C                           A:5B X:55 Y:69 P:25 SP:FB PPU: 76, 22 CYC:2533\n$CFD1:8D 03 03  STA $0303 = #$00                     A:5C X:55 Y:69 P:25 SP:FB PPU: 82, 22 CYC:2535\n$CFD4:A9 5D     LDA #$5D                           A:5C X:55 Y:69 P:25 SP:FB PPU: 94, 22 CYC:2539\n$CFD6:8D 00 04  STA $0400 = #$00                     A:5D X:55 Y:69 P:25 SP:FB PPU:100, 22 CYC:2541\n$CFD9:A2 00     LDX #$00                           A:5D X:55 Y:69 P:25 SP:FB PPU:112, 22 CYC:2545\n$CFDB:A1 80     LDA ($80,X) @ 80 = #$0200 = #$5A       A:5D X:00 Y:69 P:nvUbdIZC SP:FB PPU:118, 22 CYC:2547\n$CFDD:C9 5A     CMP #$5A                           A:5A X:00 Y:69 P:25 SP:FB PPU:136, 22 CYC:2553\n$CFDF:D0 1F     BNE $D000                          A:5A X:00 Y:69 P:nvUbdIZC SP:FB PPU:142, 22 CYC:2555\n$CFE1:E8        INX                                A:5A X:00 Y:69 P:nvUbdIZC SP:FB PPU:148, 22 CYC:2557\n$CFE2:E8        INX                                A:5A X:01 Y:69 P:25 SP:FB PPU:154, 22 CYC:2559\n$CFE3:A1 80     LDA ($80,X) @ 82 = #$0300 = #$5B       A:5A X:02 Y:69 P:25 SP:FB PPU:160, 22 CYC:2561\n$CFE5:C9 5B     CMP #$5B                           A:5B X:02 Y:69 P:25 SP:FB PPU:178, 22 CYC:2567\n$CFE7:D0 17     BNE $D000                          A:5B X:02 Y:69 P:nvUbdIZC SP:FB PPU:184, 22 CYC:2569\n$CFE9:E8        INX                                A:5B X:02 Y:69 P:nvUbdIZC SP:FB PPU:190, 22 CYC:2571\n$CFEA:A1 80     LDA ($80,X) @ 83 = #$0303 = #$5C       A:5B X:03 Y:69 P:25 SP:FB PPU:196, 22 CYC:2573\n$CFEC:C9 5C     CMP #$5C                           A:5C X:03 Y:69 P:25 SP:FB PPU:214, 22 CYC:2579\n$CFEE:D0 10     BNE $D000                          A:5C X:03 Y:69 P:nvUbdIZC SP:FB PPU:220, 22 CYC:2581\n$CFF0:A2 00     LDX #$00                           A:5C X:03 Y:69 P:nvUbdIZC SP:FB PPU:226, 22 CYC:2583\n$CFF2:A1 FF     LDA ($FF,X) @ FF = #$0400 = #$5D       A:5C X:00 Y:69 P:nvUbdIZC SP:FB PPU:232, 22 CYC:2585\n$CFF4:C9 5D     CMP #$5D                           A:5D X:00 Y:69 P:25 SP:FB PPU:250, 22 CYC:2591\n$CFF6:D0 08     BNE $D000                          A:5D X:00 Y:69 P:nvUbdIZC SP:FB PPU:256, 22 CYC:2593\n$CFF8:A2 81     LDX #$81                           A:5D X:00 Y:69 P:nvUbdIZC SP:FB PPU:262, 22 CYC:2595\n$CFFA:A1 FF     LDA ($FF,X) @ 80 = #$0200 = #$5A       A:5D X:81 Y:69 P:A5 SP:FB PPU:268, 22 CYC:2597\n$CFFC:C9 5A     CMP #$5A                           A:5A X:81 Y:69 P:25 SP:FB PPU:286, 22 CYC:2603\n$CFFE:F0 05     BEQ $D005                          A:5A X:81 Y:69 P:nvUbdIZC SP:FB PPU:292, 22 CYC:2605\n$D005:A9 AA     LDA #$AA                           A:5A X:81 Y:69 P:nvUbdIZC SP:FB PPU:301, 22 CYC:2608\n$D007:A2 00     LDX #$00                           A:AA X:81 Y:69 P:A5 SP:FB PPU:307, 22 CYC:2610\n$D009:81 80     STA ($80,X) @ 80 = #$0200 = #$5A       A:AA X:00 Y:69 P:nvUbdIZC SP:FB PPU:313, 22 CYC:2612\n$D00B:E8        INX                                A:AA X:00 Y:69 P:nvUbdIZC SP:FB PPU:331, 22 CYC:2618\n$D00C:E8        INX                                A:AA X:01 Y:69 P:25 SP:FB PPU:337, 22 CYC:2620\n$D00D:A9 AB     LDA #$AB                           A:AA X:02 Y:69 P:25 SP:FB PPU:  2, 23 CYC:2622\n$D00F:81 80     STA ($80,X) @ 82 = #$0300 = #$5B       A:AB X:02 Y:69 P:A5 SP:FB PPU:  8, 23 CYC:2624\n$D011:E8        INX                                A:AB X:02 Y:69 P:A5 SP:FB PPU: 26, 23 CYC:2630\n$D012:A9 AC     LDA #$AC                           A:AB X:03 Y:69 P:25 SP:FB PPU: 32, 23 CYC:2632\n$D014:81 80     STA ($80,X) @ 83 = #$0303 = #$5C       A:AC X:03 Y:69 P:A5 SP:FB PPU: 38, 23 CYC:2634\n$D016:A2 00     LDX #$00                           A:AC X:03 Y:69 P:A5 SP:FB PPU: 56, 23 CYC:2640\n$D018:A9 AD     LDA #$AD                           A:AC X:00 Y:69 P:nvUbdIZC SP:FB PPU: 62, 23 CYC:2642\n$D01A:81 FF     STA ($FF,X) @ FF = #$0400 = #$5D       A:AD X:00 Y:69 P:A5 SP:FB PPU: 68, 23 CYC:2644\n$D01C:AD 00 02  LDA $0200 = #$AA                     A:AD X:00 Y:69 P:A5 SP:FB PPU: 86, 23 CYC:2650\n$D01F:C9 AA     CMP #$AA                           A:AA X:00 Y:69 P:A5 SP:FB PPU: 98, 23 CYC:2654\n$D021:D0 15     BNE $D038                          A:AA X:00 Y:69 P:nvUbdIZC SP:FB PPU:104, 23 CYC:2656\n$D023:AD 00 03  LDA $0300 = #$AB                     A:AA X:00 Y:69 P:nvUbdIZC SP:FB PPU:110, 23 CYC:2658\n$D026:C9 AB     CMP #$AB                           A:AB X:00 Y:69 P:A5 SP:FB PPU:122, 23 CYC:2662\n$D028:D0 0E     BNE $D038                          A:AB X:00 Y:69 P:nvUbdIZC SP:FB PPU:128, 23 CYC:2664\n$D02A:AD 03 03  LDA $0303 = #$AC                     A:AB X:00 Y:69 P:nvUbdIZC SP:FB PPU:134, 23 CYC:2666\n$D02D:C9 AC     CMP #$AC                           A:AC X:00 Y:69 P:A5 SP:FB PPU:146, 23 CYC:2670\n$D02F:D0 07     BNE $D038                          A:AC X:00 Y:69 P:nvUbdIZC SP:FB PPU:152, 23 CYC:2672\n$D031:AD 00 04  LDA $0400 = #$AD                     A:AC X:00 Y:69 P:nvUbdIZC SP:FB PPU:158, 23 CYC:2674\n$D034:C9 AD     CMP #$AD                           A:AD X:00 Y:69 P:A5 SP:FB PPU:170, 23 CYC:2678\n$D036:F0 05     BEQ $D03D                          A:AD X:00 Y:69 P:nvUbdIZC SP:FB PPU:176, 23 CYC:2680\n$D03D:AD FF 07  LDA $07FF = #$00                     A:AD X:00 Y:69 P:nvUbdIZC SP:FB PPU:185, 23 CYC:2683\n$D040:85 00     STA $00 = #$04                       A:00 X:00 Y:69 P:nvUbdIZC SP:FB PPU:197, 23 CYC:2687\n$D042:A9 00     LDA #$00                           A:00 X:00 Y:69 P:nvUbdIZC SP:FB PPU:206, 23 CYC:2690\n$D044:8D 00 03  STA $0300 = #$AB                     A:00 X:00 Y:69 P:nvUbdIZC SP:FB PPU:212, 23 CYC:2692\n$D047:A9 AA     LDA #$AA                           A:00 X:00 Y:69 P:nvUbdIZC SP:FB PPU:224, 23 CYC:2696\n$D049:8D 00 02  STA $0200 = #$AA                     A:AA X:00 Y:69 P:A5 SP:FB PPU:230, 23 CYC:2698\n$D04C:A2 00     LDX #$00                           A:AA X:00 Y:69 P:A5 SP:FB PPU:242, 23 CYC:2702\n$D04E:A0 5A     LDY #$5A                           A:AA X:00 Y:69 P:nvUbdIZC SP:FB PPU:248, 23 CYC:2704\n$D050:20 B6 F7  JSR $F7B6                          A:AA X:00 Y:5A P:25 SP:FB PPU:254, 23 CYC:2706\n$F7B6:18        CLC                                A:AA X:00 Y:5A P:25 SP:F9 PPU:272, 23 CYC:2712\n$F7B7:A9 FF     LDA #$FF                           A:AA X:00 Y:5A P:nvUbdIzc SP:F9 PPU:278, 23 CYC:2714\n$F7B9:85 01     STA $01 = #$FF                       A:FF X:00 Y:5A P:NvUbdIzc SP:F9 PPU:284, 23 CYC:2716\n$F7BB:24 01     BIT $01 = #$FF                       A:FF X:00 Y:5A P:NvUbdIzc SP:F9 PPU:293, 23 CYC:2719\n$F7BD:A9 55     LDA #$55                           A:FF X:00 Y:5A P:NVUbdIzc SP:F9 PPU:302, 23 CYC:2722\n$F7BF:60        RTS                                A:55 X:00 Y:5A P:64 SP:F9 PPU:308, 23 CYC:2724\n$D053:01 80     ORA ($80,X) @ 80 = #$0200 = #$AA       A:55 X:00 Y:5A P:64 SP:FB PPU:326, 23 CYC:2730\n$D055:20 C0 F7  JSR $F7C0                          A:FF X:00 Y:5A P:NVUbdIzc SP:FB PPU:  3, 24 CYC:2736\n$F7C0:B0 09     BCS $F7CB                          A:FF X:00 Y:5A P:NVUbdIzc SP:F9 PPU: 21, 24 CYC:2742\n$F7C2:10 07     BPL $F7CB                          A:FF X:00 Y:5A P:NVUbdIzc SP:F9 PPU: 27, 24 CYC:2744\n$F7C4:C9 FF     CMP #$FF                           A:FF X:00 Y:5A P:NVUbdIzc SP:F9 PPU: 33, 24 CYC:2746\n$F7C6:D0 03     BNE $F7CB                          A:FF X:00 Y:5A P:67 SP:F9 PPU: 39, 24 CYC:2748\n$F7C8:50 01     BVC $F7CB                          A:FF X:00 Y:5A P:67 SP:F9 PPU: 45, 24 CYC:2750\n$F7CA:60        RTS                                A:FF X:00 Y:5A P:67 SP:F9 PPU: 51, 24 CYC:2752\n$D058:C8        INY                                A:FF X:00 Y:5A P:67 SP:FB PPU: 69, 24 CYC:2758\n$D059:20 CE F7  JSR $F7CE                          A:FF X:00 Y:5B P:65 SP:FB PPU: 75, 24 CYC:2760\n$F7CE:38        SEC                                A:FF X:00 Y:5B P:65 SP:F9 PPU: 93, 24 CYC:2766\n$F7CF:B8        CLV                                A:FF X:00 Y:5B P:65 SP:F9 PPU: 99, 24 CYC:2768\n$F7D0:A9 00     LDA #$00                           A:FF X:00 Y:5B P:25 SP:F9 PPU:105, 24 CYC:2770\n$F7D2:60        RTS                                A:00 X:00 Y:5B P:nvUbdIZC SP:F9 PPU:111, 24 CYC:2772\n$D05C:01 82     ORA ($82,X) @ 82 = #$0300 = #$00       A:00 X:00 Y:5B P:nvUbdIZC SP:FB PPU:129, 24 CYC:2778\n$D05E:20 D3 F7  JSR $F7D3                          A:00 X:00 Y:5B P:nvUbdIZC SP:FB PPU:147, 24 CYC:2784\n$F7D3:D0 07     BNE $F7DC                          A:00 X:00 Y:5B P:nvUbdIZC SP:F9 PPU:165, 24 CYC:2790\n$F7D5:70 05     BVS $F7DC                          A:00 X:00 Y:5B P:nvUbdIZC SP:F9 PPU:171, 24 CYC:2792\n$F7D7:90 03     BCC $F7DC                          A:00 X:00 Y:5B P:nvUbdIZC SP:F9 PPU:177, 24 CYC:2794\n$F7D9:30 01     BMI $F7DC                          A:00 X:00 Y:5B P:nvUbdIZC SP:F9 PPU:183, 24 CYC:2796\n$F7DB:60        RTS                                A:00 X:00 Y:5B P:nvUbdIZC SP:F9 PPU:189, 24 CYC:2798\n$D061:C8        INY                                A:00 X:00 Y:5B P:nvUbdIZC SP:FB PPU:207, 24 CYC:2804\n$D062:20 DF F7  JSR $F7DF                          A:00 X:00 Y:5C P:25 SP:FB PPU:213, 24 CYC:2806\n$F7DF:18        CLC                                A:00 X:00 Y:5C P:25 SP:F9 PPU:231, 24 CYC:2812\n$F7E0:24 01     BIT $01 = #$FF                       A:00 X:00 Y:5C P:nvUbdIzc SP:F9 PPU:237, 24 CYC:2814\n$F7E2:A9 55     LDA #$55                           A:00 X:00 Y:5C P:E6 SP:F9 PPU:246, 24 CYC:2817\n$F7E4:60        RTS                                A:55 X:00 Y:5C P:64 SP:F9 PPU:252, 24 CYC:2819\n$D065:21 80     AND ($80,X) @ 80 = #$0200 = #$AA       A:55 X:00 Y:5C P:64 SP:FB PPU:270, 24 CYC:2825\n$D067:20 E5 F7  JSR $F7E5                          A:00 X:00 Y:5C P:nVUbdIZc SP:FB PPU:288, 24 CYC:2831\n$F7E5:D0 07     BNE $F7EE                          A:00 X:00 Y:5C P:nVUbdIZc SP:F9 PPU:306, 24 CYC:2837\n$F7E7:50 05     BVC $F7EE                          A:00 X:00 Y:5C P:nVUbdIZc SP:F9 PPU:312, 24 CYC:2839\n$F7E9:B0 03     BCS $F7EE                          A:00 X:00 Y:5C P:nVUbdIZc SP:F9 PPU:318, 24 CYC:2841\n$F7EB:30 01     BMI $F7EE                          A:00 X:00 Y:5C P:nVUbdIZc SP:F9 PPU:324, 24 CYC:2843\n$F7ED:60        RTS                                A:00 X:00 Y:5C P:nVUbdIZc SP:F9 PPU:330, 24 CYC:2845\n$D06A:C8        INY                                A:00 X:00 Y:5C P:nVUbdIZc SP:FB PPU:  7, 25 CYC:2851\n$D06B:A9 EF     LDA #$EF                           A:00 X:00 Y:5D P:64 SP:FB PPU: 13, 25 CYC:2853\n$D06D:8D 00 03  STA $0300 = #$00                     A:EF X:00 Y:5D P:NVUbdIzc SP:FB PPU: 19, 25 CYC:2855\n$D070:20 F1 F7  JSR $F7F1                          A:EF X:00 Y:5D P:NVUbdIzc SP:FB PPU: 31, 25 CYC:2859\n$F7F1:38        SEC                                A:EF X:00 Y:5D P:NVUbdIzc SP:F9 PPU: 49, 25 CYC:2865\n$F7F2:B8        CLV                                A:EF X:00 Y:5D P:E5 SP:F9 PPU: 55, 25 CYC:2867\n$F7F3:A9 F8     LDA #$F8                           A:EF X:00 Y:5D P:A5 SP:F9 PPU: 61, 25 CYC:2869\n$F7F5:60        RTS                                A:F8 X:00 Y:5D P:A5 SP:F9 PPU: 67, 25 CYC:2871\n$D073:21 82     AND ($82,X) @ 82 = #$0300 = #$EF       A:F8 X:00 Y:5D P:A5 SP:FB PPU: 85, 25 CYC:2877\n$D075:20 F6 F7  JSR $F7F6                          A:E8 X:00 Y:5D P:A5 SP:FB PPU:103, 25 CYC:2883\n$F7F6:90 09     BCC $F801                          A:E8 X:00 Y:5D P:A5 SP:F9 PPU:121, 25 CYC:2889\n$F7F8:10 07     BPL $F801                          A:E8 X:00 Y:5D P:A5 SP:F9 PPU:127, 25 CYC:2891\n$F7FA:C9 E8     CMP #$E8                           A:E8 X:00 Y:5D P:A5 SP:F9 PPU:133, 25 CYC:2893\n$F7FC:D0 03     BNE $F801                          A:E8 X:00 Y:5D P:nvUbdIZC SP:F9 PPU:139, 25 CYC:2895\n$F7FE:70 01     BVS $F801                          A:E8 X:00 Y:5D P:nvUbdIZC SP:F9 PPU:145, 25 CYC:2897\n$F800:60        RTS                                A:E8 X:00 Y:5D P:nvUbdIZC SP:F9 PPU:151, 25 CYC:2899\n$D078:C8        INY                                A:E8 X:00 Y:5D P:nvUbdIZC SP:FB PPU:169, 25 CYC:2905\n$D079:20 04 F8  JSR $F804                          A:E8 X:00 Y:5E P:25 SP:FB PPU:175, 25 CYC:2907\n$F804:18        CLC                                A:E8 X:00 Y:5E P:25 SP:F9 PPU:193, 25 CYC:2913\n$F805:24 01     BIT $01 = #$FF                       A:E8 X:00 Y:5E P:nvUbdIzc SP:F9 PPU:199, 25 CYC:2915\n$F807:A9 5F     LDA #$5F                           A:E8 X:00 Y:5E P:NVUbdIzc SP:F9 PPU:208, 25 CYC:2918\n$F809:60        RTS                                A:5F X:00 Y:5E P:64 SP:F9 PPU:214, 25 CYC:2920\n$D07C:41 80     EOR ($80,X) @ 80 = #$0200 = #$AA       A:5F X:00 Y:5E P:64 SP:FB PPU:232, 25 CYC:2926\n$D07E:20 0A F8  JSR $F80A                          A:F5 X:00 Y:5E P:NVUbdIzc SP:FB PPU:250, 25 CYC:2932\n$F80A:B0 09     BCS $F815                          A:F5 X:00 Y:5E P:NVUbdIzc SP:F9 PPU:268, 25 CYC:2938\n$F80C:10 07     BPL $F815                          A:F5 X:00 Y:5E P:NVUbdIzc SP:F9 PPU:274, 25 CYC:2940\n$F80E:C9 F5     CMP #$F5                           A:F5 X:00 Y:5E P:NVUbdIzc SP:F9 PPU:280, 25 CYC:2942\n$F810:D0 03     BNE $F815                          A:F5 X:00 Y:5E P:67 SP:F9 PPU:286, 25 CYC:2944\n$F812:50 01     BVC $F815                          A:F5 X:00 Y:5E P:67 SP:F9 PPU:292, 25 CYC:2946\n$F814:60        RTS                                A:F5 X:00 Y:5E P:67 SP:F9 PPU:298, 25 CYC:2948\n$D081:C8        INY                                A:F5 X:00 Y:5E P:67 SP:FB PPU:316, 25 CYC:2954\n$D082:A9 70     LDA #$70                           A:F5 X:00 Y:5F P:65 SP:FB PPU:322, 25 CYC:2956\n$D084:8D 00 03  STA $0300 = #$EF                     A:70 X:00 Y:5F P:65 SP:FB PPU:328, 25 CYC:2958\n$D087:20 18 F8  JSR $F818                          A:70 X:00 Y:5F P:65 SP:FB PPU:340, 25 CYC:2962\n$F818:38        SEC                                A:70 X:00 Y:5F P:65 SP:F9 PPU: 17, 26 CYC:2968\n$F819:B8        CLV                                A:70 X:00 Y:5F P:65 SP:F9 PPU: 23, 26 CYC:2970\n$F81A:A9 70     LDA #$70                           A:70 X:00 Y:5F P:25 SP:F9 PPU: 29, 26 CYC:2972\n$F81C:60        RTS                                A:70 X:00 Y:5F P:25 SP:F9 PPU: 35, 26 CYC:2974\n$D08A:41 82     EOR ($82,X) @ 82 = #$0300 = #$70       A:70 X:00 Y:5F P:25 SP:FB PPU: 53, 26 CYC:2980\n$D08C:20 1D F8  JSR $F81D                          A:00 X:00 Y:5F P:nvUbdIZC SP:FB PPU: 71, 26 CYC:2986\n$F81D:D0 07     BNE $F826                          A:00 X:00 Y:5F P:nvUbdIZC SP:F9 PPU: 89, 26 CYC:2992\n$F81F:70 05     BVS $F826                          A:00 X:00 Y:5F P:nvUbdIZC SP:F9 PPU: 95, 26 CYC:2994\n$F821:90 03     BCC $F826                          A:00 X:00 Y:5F P:nvUbdIZC SP:F9 PPU:101, 26 CYC:2996\n$F823:30 01     BMI $F826                          A:00 X:00 Y:5F P:nvUbdIZC SP:F9 PPU:107, 26 CYC:2998\n$F825:60        RTS                                A:00 X:00 Y:5F P:nvUbdIZC SP:F9 PPU:113, 26 CYC:3000\n$D08F:C8        INY                                A:00 X:00 Y:5F P:nvUbdIZC SP:FB PPU:131, 26 CYC:3006\n$D090:A9 69     LDA #$69                           A:00 X:00 Y:60 P:25 SP:FB PPU:137, 26 CYC:3008\n$D092:8D 00 02  STA $0200 = #$AA                     A:69 X:00 Y:60 P:25 SP:FB PPU:143, 26 CYC:3010\n$D095:20 29 F8  JSR $F829                          A:69 X:00 Y:60 P:25 SP:FB PPU:155, 26 CYC:3014\n$F829:18        CLC                                A:69 X:00 Y:60 P:25 SP:F9 PPU:173, 26 CYC:3020\n$F82A:24 01     BIT $01 = #$FF                       A:69 X:00 Y:60 P:nvUbdIzc SP:F9 PPU:179, 26 CYC:3022\n$F82C:A9 00     LDA #$00                           A:69 X:00 Y:60 P:NVUbdIzc SP:F9 PPU:188, 26 CYC:3025\n$F82E:60        RTS                                A:00 X:00 Y:60 P:nVUbdIZc SP:F9 PPU:194, 26 CYC:3027\n$D098:61 80     ADC ($80,X) @ 80 = #$0200 = #$69       A:00 X:00 Y:60 P:nVUbdIZc SP:FB PPU:212, 26 CYC:3033\n$D09A:20 2F F8  JSR $F82F                          A:69 X:00 Y:60 P:nvUbdIzc SP:FB PPU:230, 26 CYC:3039\n$F82F:30 09     BMI $F83A                          A:69 X:00 Y:60 P:nvUbdIzc SP:F9 PPU:248, 26 CYC:3045\n$F831:B0 07     BCS $F83A                          A:69 X:00 Y:60 P:nvUbdIzc SP:F9 PPU:254, 26 CYC:3047\n$F833:C9 69     CMP #$69                           A:69 X:00 Y:60 P:nvUbdIzc SP:F9 PPU:260, 26 CYC:3049\n$F835:D0 03     BNE $F83A                          A:69 X:00 Y:60 P:nvUbdIZC SP:F9 PPU:266, 26 CYC:3051\n$F837:70 01     BVS $F83A                          A:69 X:00 Y:60 P:nvUbdIZC SP:F9 PPU:272, 26 CYC:3053\n$F839:60        RTS                                A:69 X:00 Y:60 P:nvUbdIZC SP:F9 PPU:278, 26 CYC:3055\n$D09D:C8        INY                                A:69 X:00 Y:60 P:nvUbdIZC SP:FB PPU:296, 26 CYC:3061\n$D09E:20 3D F8  JSR $F83D                          A:69 X:00 Y:61 P:25 SP:FB PPU:302, 26 CYC:3063\n$F83D:38        SEC                                A:69 X:00 Y:61 P:25 SP:F9 PPU:320, 26 CYC:3069\n$F83E:24 01     BIT $01 = #$FF                       A:69 X:00 Y:61 P:25 SP:F9 PPU:326, 26 CYC:3071\n$F840:A9 00     LDA #$00                           A:69 X:00 Y:61 P:E5 SP:F9 PPU:335, 26 CYC:3074\n$F842:60        RTS                                A:00 X:00 Y:61 P:67 SP:F9 PPU:  0, 27 CYC:3076\n$D0A1:61 80     ADC ($80,X) @ 80 = #$0200 = #$69       A:00 X:00 Y:61 P:67 SP:FB PPU: 18, 27 CYC:3082\n$D0A3:20 43 F8  JSR $F843                          A:6A X:00 Y:61 P:nvUbdIzc SP:FB PPU: 36, 27 CYC:3088\n$F843:30 09     BMI $F84E                          A:6A X:00 Y:61 P:nvUbdIzc SP:F9 PPU: 54, 27 CYC:3094\n$F845:B0 07     BCS $F84E                          A:6A X:00 Y:61 P:nvUbdIzc SP:F9 PPU: 60, 27 CYC:3096\n$F847:C9 6A     CMP #$6A                           A:6A X:00 Y:61 P:nvUbdIzc SP:F9 PPU: 66, 27 CYC:3098\n$F849:D0 03     BNE $F84E                          A:6A X:00 Y:61 P:nvUbdIZC SP:F9 PPU: 72, 27 CYC:3100\n$F84B:70 01     BVS $F84E                          A:6A X:00 Y:61 P:nvUbdIZC SP:F9 PPU: 78, 27 CYC:3102\n$F84D:60        RTS                                A:6A X:00 Y:61 P:nvUbdIZC SP:F9 PPU: 84, 27 CYC:3104\n$D0A6:C8        INY                                A:6A X:00 Y:61 P:nvUbdIZC SP:FB PPU:102, 27 CYC:3110\n$D0A7:A9 7F     LDA #$7F                           A:6A X:00 Y:62 P:25 SP:FB PPU:108, 27 CYC:3112\n$D0A9:8D 00 02  STA $0200 = #$69                     A:7F X:00 Y:62 P:25 SP:FB PPU:114, 27 CYC:3114\n$D0AC:20 51 F8  JSR $F851                          A:7F X:00 Y:62 P:25 SP:FB PPU:126, 27 CYC:3118\n$F851:38        SEC                                A:7F X:00 Y:62 P:25 SP:F9 PPU:144, 27 CYC:3124\n$F852:B8        CLV                                A:7F X:00 Y:62 P:25 SP:F9 PPU:150, 27 CYC:3126\n$F853:A9 7F     LDA #$7F                           A:7F X:00 Y:62 P:25 SP:F9 PPU:156, 27 CYC:3128\n$F855:60        RTS                                A:7F X:00 Y:62 P:25 SP:F9 PPU:162, 27 CYC:3130\n$D0AF:61 80     ADC ($80,X) @ 80 = #$0200 = #$7F       A:7F X:00 Y:62 P:25 SP:FB PPU:180, 27 CYC:3136\n$D0B1:20 56 F8  JSR $F856                          A:FF X:00 Y:62 P:NVUbdIzc SP:FB PPU:198, 27 CYC:3142\n$F856:10 09     BPL $F861                          A:FF X:00 Y:62 P:NVUbdIzc SP:F9 PPU:216, 27 CYC:3148\n$F858:B0 07     BCS $F861                          A:FF X:00 Y:62 P:NVUbdIzc SP:F9 PPU:222, 27 CYC:3150\n$F85A:C9 FF     CMP #$FF                           A:FF X:00 Y:62 P:NVUbdIzc SP:F9 PPU:228, 27 CYC:3152\n$F85C:D0 03     BNE $F861                          A:FF X:00 Y:62 P:67 SP:F9 PPU:234, 27 CYC:3154\n$F85E:50 01     BVC $F861                          A:FF X:00 Y:62 P:67 SP:F9 PPU:240, 27 CYC:3156\n$F860:60        RTS                                A:FF X:00 Y:62 P:67 SP:F9 PPU:246, 27 CYC:3158\n$D0B4:C8        INY                                A:FF X:00 Y:62 P:67 SP:FB PPU:264, 27 CYC:3164\n$D0B5:A9 80     LDA #$80                           A:FF X:00 Y:63 P:65 SP:FB PPU:270, 27 CYC:3166\n$D0B7:8D 00 02  STA $0200 = #$7F                     A:80 X:00 Y:63 P:E5 SP:FB PPU:276, 27 CYC:3168\n$D0BA:20 64 F8  JSR $F864                          A:80 X:00 Y:63 P:E5 SP:FB PPU:288, 27 CYC:3172\n$F864:18        CLC                                A:80 X:00 Y:63 P:E5 SP:F9 PPU:306, 27 CYC:3178\n$F865:24 01     BIT $01 = #$FF                       A:80 X:00 Y:63 P:NVUbdIzc SP:F9 PPU:312, 27 CYC:3180\n$F867:A9 7F     LDA #$7F                           A:80 X:00 Y:63 P:NVUbdIzc SP:F9 PPU:321, 27 CYC:3183\n$F869:60        RTS                                A:7F X:00 Y:63 P:64 SP:F9 PPU:327, 27 CYC:3185\n$D0BD:61 80     ADC ($80,X) @ 80 = #$0200 = #$80       A:7F X:00 Y:63 P:64 SP:FB PPU:  4, 28 CYC:3191\n$D0BF:20 6A F8  JSR $F86A                          A:FF X:00 Y:63 P:NvUbdIzc SP:FB PPU: 22, 28 CYC:3197\n$F86A:10 09     BPL $F875                          A:FF X:00 Y:63 P:NvUbdIzc SP:F9 PPU: 40, 28 CYC:3203\n$F86C:B0 07     BCS $F875                          A:FF X:00 Y:63 P:NvUbdIzc SP:F9 PPU: 46, 28 CYC:3205\n$F86E:C9 FF     CMP #$FF                           A:FF X:00 Y:63 P:NvUbdIzc SP:F9 PPU: 52, 28 CYC:3207\n$F870:D0 03     BNE $F875                          A:FF X:00 Y:63 P:nvUbdIZC SP:F9 PPU: 58, 28 CYC:3209\n$F872:70 01     BVS $F875                          A:FF X:00 Y:63 P:nvUbdIZC SP:F9 PPU: 64, 28 CYC:3211\n$F874:60        RTS                                A:FF X:00 Y:63 P:nvUbdIZC SP:F9 PPU: 70, 28 CYC:3213\n$D0C2:C8        INY                                A:FF X:00 Y:63 P:nvUbdIZC SP:FB PPU: 88, 28 CYC:3219\n$D0C3:20 78 F8  JSR $F878                          A:FF X:00 Y:64 P:25 SP:FB PPU: 94, 28 CYC:3221\n$F878:38        SEC                                A:FF X:00 Y:64 P:25 SP:F9 PPU:112, 28 CYC:3227\n$F879:B8        CLV                                A:FF X:00 Y:64 P:25 SP:F9 PPU:118, 28 CYC:3229\n$F87A:A9 7F     LDA #$7F                           A:FF X:00 Y:64 P:25 SP:F9 PPU:124, 28 CYC:3231\n$F87C:60        RTS                                A:7F X:00 Y:64 P:25 SP:F9 PPU:130, 28 CYC:3233\n$D0C6:61 80     ADC ($80,X) @ 80 = #$0200 = #$80       A:7F X:00 Y:64 P:25 SP:FB PPU:148, 28 CYC:3239\n$D0C8:20 7D F8  JSR $F87D                          A:00 X:00 Y:64 P:nvUbdIZC SP:FB PPU:166, 28 CYC:3245\n$F87D:D0 07     BNE $F886                          A:00 X:00 Y:64 P:nvUbdIZC SP:F9 PPU:184, 28 CYC:3251\n$F87F:30 05     BMI $F886                          A:00 X:00 Y:64 P:nvUbdIZC SP:F9 PPU:190, 28 CYC:3253\n$F881:70 03     BVS $F886                          A:00 X:00 Y:64 P:nvUbdIZC SP:F9 PPU:196, 28 CYC:3255\n$F883:90 01     BCC $F886                          A:00 X:00 Y:64 P:nvUbdIZC SP:F9 PPU:202, 28 CYC:3257\n$F885:60        RTS                                A:00 X:00 Y:64 P:nvUbdIZC SP:F9 PPU:208, 28 CYC:3259\n$D0CB:C8        INY                                A:00 X:00 Y:64 P:nvUbdIZC SP:FB PPU:226, 28 CYC:3265\n$D0CC:A9 40     LDA #$40                           A:00 X:00 Y:65 P:25 SP:FB PPU:232, 28 CYC:3267\n$D0CE:8D 00 02  STA $0200 = #$80                     A:40 X:00 Y:65 P:25 SP:FB PPU:238, 28 CYC:3269\n$D0D1:20 89 F8  JSR $F889                          A:40 X:00 Y:65 P:25 SP:FB PPU:250, 28 CYC:3273\n$F889:24 01     BIT $01 = #$FF                       A:40 X:00 Y:65 P:25 SP:F9 PPU:268, 28 CYC:3279\n$F88B:A9 40     LDA #$40                           A:40 X:00 Y:65 P:E5 SP:F9 PPU:277, 28 CYC:3282\n$F88D:60        RTS                                A:40 X:00 Y:65 P:65 SP:F9 PPU:283, 28 CYC:3284\n$D0D4:C1 80     CMP ($80,X) @ 80 = #$0200 = #$40       A:40 X:00 Y:65 P:65 SP:FB PPU:301, 28 CYC:3290\n$D0D6:20 8E F8  JSR $F88E                          A:40 X:00 Y:65 P:67 SP:FB PPU:319, 28 CYC:3296\n$F88E:30 07     BMI $F897                          A:40 X:00 Y:65 P:67 SP:F9 PPU:337, 28 CYC:3302\n$F890:90 05     BCC $F897                          A:40 X:00 Y:65 P:67 SP:F9 PPU:  2, 29 CYC:3304\n$F892:D0 03     BNE $F897                          A:40 X:00 Y:65 P:67 SP:F9 PPU:  8, 29 CYC:3306\n$F894:50 01     BVC $F897                          A:40 X:00 Y:65 P:67 SP:F9 PPU: 14, 29 CYC:3308\n$F896:60        RTS                                A:40 X:00 Y:65 P:67 SP:F9 PPU: 20, 29 CYC:3310\n$D0D9:C8        INY                                A:40 X:00 Y:65 P:67 SP:FB PPU: 38, 29 CYC:3316\n$D0DA:48        PHA                                A:40 X:00 Y:66 P:65 SP:FB PPU: 44, 29 CYC:3318\n$D0DB:A9 3F     LDA #$3F                           A:40 X:00 Y:66 P:65 SP:FA PPU: 53, 29 CYC:3321\n$D0DD:8D 00 02  STA $0200 = #$40                     A:3F X:00 Y:66 P:65 SP:FA PPU: 59, 29 CYC:3323\n$D0E0:68        PLA                                A:3F X:00 Y:66 P:65 SP:FA PPU: 71, 29 CYC:3327\n$D0E1:20 9A F8  JSR $F89A                          A:40 X:00 Y:66 P:65 SP:FB PPU: 83, 29 CYC:3331\n$F89A:B8        CLV                                A:40 X:00 Y:66 P:65 SP:F9 PPU:101, 29 CYC:3337\n$F89B:60        RTS                                A:40 X:00 Y:66 P:25 SP:F9 PPU:107, 29 CYC:3339\n$D0E4:C1 80     CMP ($80,X) @ 80 = #$0200 = #$3F       A:40 X:00 Y:66 P:25 SP:FB PPU:125, 29 CYC:3345\n$D0E6:20 9C F8  JSR $F89C                          A:40 X:00 Y:66 P:25 SP:FB PPU:143, 29 CYC:3351\n$F89C:F0 07     BEQ $F8A5                          A:40 X:00 Y:66 P:25 SP:F9 PPU:161, 29 CYC:3357\n$F89E:30 05     BMI $F8A5                          A:40 X:00 Y:66 P:25 SP:F9 PPU:167, 29 CYC:3359\n$F8A0:90 03     BCC $F8A5                          A:40 X:00 Y:66 P:25 SP:F9 PPU:173, 29 CYC:3361\n$F8A2:70 01     BVS $F8A5                          A:40 X:00 Y:66 P:25 SP:F9 PPU:179, 29 CYC:3363\n$F8A4:60        RTS                                A:40 X:00 Y:66 P:25 SP:F9 PPU:185, 29 CYC:3365\n$D0E9:C8        INY                                A:40 X:00 Y:66 P:25 SP:FB PPU:203, 29 CYC:3371\n$D0EA:48        PHA                                A:40 X:00 Y:67 P:25 SP:FB PPU:209, 29 CYC:3373\n$D0EB:A9 41     LDA #$41                           A:40 X:00 Y:67 P:25 SP:FA PPU:218, 29 CYC:3376\n$D0ED:8D 00 02  STA $0200 = #$3F                     A:41 X:00 Y:67 P:25 SP:FA PPU:224, 29 CYC:3378\n$D0F0:68        PLA                                A:41 X:00 Y:67 P:25 SP:FA PPU:236, 29 CYC:3382\n$D0F1:C1 80     CMP ($80,X) @ 80 = #$0200 = #$41       A:40 X:00 Y:67 P:25 SP:FB PPU:248, 29 CYC:3386\n$D0F3:20 A8 F8  JSR $F8A8                          A:40 X:00 Y:67 P:NvUbdIzc SP:FB PPU:266, 29 CYC:3392\n$F8A8:F0 05     BEQ $F8AF                          A:40 X:00 Y:67 P:NvUbdIzc SP:F9 PPU:284, 29 CYC:3398\n$F8AA:10 03     BPL $F8AF                          A:40 X:00 Y:67 P:NvUbdIzc SP:F9 PPU:290, 29 CYC:3400\n$F8AC:10 01     BPL $F8AF                          A:40 X:00 Y:67 P:NvUbdIzc SP:F9 PPU:296, 29 CYC:3402\n$F8AE:60        RTS                                A:40 X:00 Y:67 P:NvUbdIzc SP:F9 PPU:302, 29 CYC:3404\n$D0F6:C8        INY                                A:40 X:00 Y:67 P:NvUbdIzc SP:FB PPU:320, 29 CYC:3410\n$D0F7:48        PHA                                A:40 X:00 Y:68 P:nvUbdIzc SP:FB PPU:326, 29 CYC:3412\n$D0F8:A9 00     LDA #$00                           A:40 X:00 Y:68 P:nvUbdIzc SP:FA PPU:335, 29 CYC:3415\n$D0FA:8D 00 02  STA $0200 = #$41                     A:00 X:00 Y:68 P:nvUbdIZc SP:FA PPU:  0, 30 CYC:3417\n$D0FD:68        PLA                                A:00 X:00 Y:68 P:nvUbdIZc SP:FA PPU: 12, 30 CYC:3421\n$D0FE:20 B2 F8  JSR $F8B2                          A:40 X:00 Y:68 P:nvUbdIzc SP:FB PPU: 24, 30 CYC:3425\n$F8B2:A9 80     LDA #$80                           A:40 X:00 Y:68 P:nvUbdIzc SP:F9 PPU: 42, 30 CYC:3431\n$F8B4:60        RTS                                A:80 X:00 Y:68 P:NvUbdIzc SP:F9 PPU: 48, 30 CYC:3433\n$D101:C1 80     CMP ($80,X) @ 80 = #$0200 = #$00       A:80 X:00 Y:68 P:NvUbdIzc SP:FB PPU: 66, 30 CYC:3439\n$D103:20 B5 F8  JSR $F8B5                          A:80 X:00 Y:68 P:A5 SP:FB PPU: 84, 30 CYC:3445\n$F8B5:F0 05     BEQ $F8BC                          A:80 X:00 Y:68 P:A5 SP:F9 PPU:102, 30 CYC:3451\n$F8B7:10 03     BPL $F8BC                          A:80 X:00 Y:68 P:A5 SP:F9 PPU:108, 30 CYC:3453\n$F8B9:90 01     BCC $F8BC                          A:80 X:00 Y:68 P:A5 SP:F9 PPU:114, 30 CYC:3455\n$F8BB:60        RTS                                A:80 X:00 Y:68 P:A5 SP:F9 PPU:120, 30 CYC:3457\n$D106:C8        INY                                A:80 X:00 Y:68 P:A5 SP:FB PPU:138, 30 CYC:3463\n$D107:48        PHA                                A:80 X:00 Y:69 P:25 SP:FB PPU:144, 30 CYC:3465\n$D108:A9 80     LDA #$80                           A:80 X:00 Y:69 P:25 SP:FA PPU:153, 30 CYC:3468\n$D10A:8D 00 02  STA $0200 = #$00                     A:80 X:00 Y:69 P:A5 SP:FA PPU:159, 30 CYC:3470\n$D10D:68        PLA                                A:80 X:00 Y:69 P:A5 SP:FA PPU:171, 30 CYC:3474\n$D10E:C1 80     CMP ($80,X) @ 80 = #$0200 = #$80       A:80 X:00 Y:69 P:A5 SP:FB PPU:183, 30 CYC:3478\n$D110:20 BF F8  JSR $F8BF                          A:80 X:00 Y:69 P:nvUbdIZC SP:FB PPU:201, 30 CYC:3484\n$F8BF:D0 05     BNE $F8C6                          A:80 X:00 Y:69 P:nvUbdIZC SP:F9 PPU:219, 30 CYC:3490\n$F8C1:30 03     BMI $F8C6                          A:80 X:00 Y:69 P:nvUbdIZC SP:F9 PPU:225, 30 CYC:3492\n$F8C3:90 01     BCC $F8C6                          A:80 X:00 Y:69 P:nvUbdIZC SP:F9 PPU:231, 30 CYC:3494\n$F8C5:60        RTS                                A:80 X:00 Y:69 P:nvUbdIZC SP:F9 PPU:237, 30 CYC:3496\n$D113:C8        INY                                A:80 X:00 Y:69 P:nvUbdIZC SP:FB PPU:255, 30 CYC:3502\n$D114:48        PHA                                A:80 X:00 Y:6A P:25 SP:FB PPU:261, 30 CYC:3504\n$D115:A9 81     LDA #$81                           A:80 X:00 Y:6A P:25 SP:FA PPU:270, 30 CYC:3507\n$D117:8D 00 02  STA $0200 = #$80                     A:81 X:00 Y:6A P:A5 SP:FA PPU:276, 30 CYC:3509\n$D11A:68        PLA                                A:81 X:00 Y:6A P:A5 SP:FA PPU:288, 30 CYC:3513\n$D11B:C1 80     CMP ($80,X) @ 80 = #$0200 = #$81       A:80 X:00 Y:6A P:A5 SP:FB PPU:300, 30 CYC:3517\n$D11D:20 C9 F8  JSR $F8C9                          A:80 X:00 Y:6A P:NvUbdIzc SP:FB PPU:318, 30 CYC:3523\n$F8C9:B0 05     BCS $F8D0                          A:80 X:00 Y:6A P:NvUbdIzc SP:F9 PPU:336, 30 CYC:3529\n$F8CB:F0 03     BEQ $F8D0                          A:80 X:00 Y:6A P:NvUbdIzc SP:F9 PPU:  1, 31 CYC:3531\n$F8CD:10 01     BPL $F8D0                          A:80 X:00 Y:6A P:NvUbdIzc SP:F9 PPU:  7, 31 CYC:3533\n$F8CF:60        RTS                                A:80 X:00 Y:6A P:NvUbdIzc SP:F9 PPU: 13, 31 CYC:3535\n$D120:C8        INY                                A:80 X:00 Y:6A P:NvUbdIzc SP:FB PPU: 31, 31 CYC:3541\n$D121:48        PHA                                A:80 X:00 Y:6B P:nvUbdIzc SP:FB PPU: 37, 31 CYC:3543\n$D122:A9 7F     LDA #$7F                           A:80 X:00 Y:6B P:nvUbdIzc SP:FA PPU: 46, 31 CYC:3546\n$D124:8D 00 02  STA $0200 = #$81                     A:7F X:00 Y:6B P:nvUbdIzc SP:FA PPU: 52, 31 CYC:3548\n$D127:68        PLA                                A:7F X:00 Y:6B P:nvUbdIzc SP:FA PPU: 64, 31 CYC:3552\n$D128:C1 80     CMP ($80,X) @ 80 = #$0200 = #$7F       A:80 X:00 Y:6B P:NvUbdIzc SP:FB PPU: 76, 31 CYC:3556\n$D12A:20 D3 F8  JSR $F8D3                          A:80 X:00 Y:6B P:25 SP:FB PPU: 94, 31 CYC:3562\n$F8D3:90 05     BCC $F8DA                          A:80 X:00 Y:6B P:25 SP:F9 PPU:112, 31 CYC:3568\n$F8D5:F0 03     BEQ $F8DA                          A:80 X:00 Y:6B P:25 SP:F9 PPU:118, 31 CYC:3570\n$F8D7:30 01     BMI $F8DA                          A:80 X:00 Y:6B P:25 SP:F9 PPU:124, 31 CYC:3572\n$F8D9:60        RTS                                A:80 X:00 Y:6B P:25 SP:F9 PPU:130, 31 CYC:3574\n$D12D:C8        INY                                A:80 X:00 Y:6B P:25 SP:FB PPU:148, 31 CYC:3580\n$D12E:A9 40     LDA #$40                           A:80 X:00 Y:6C P:25 SP:FB PPU:154, 31 CYC:3582\n$D130:8D 00 02  STA $0200 = #$7F                     A:40 X:00 Y:6C P:25 SP:FB PPU:160, 31 CYC:3584\n$D133:20 31 F9  JSR $F931                          A:40 X:00 Y:6C P:25 SP:FB PPU:172, 31 CYC:3588\n$F931:24 01     BIT $01 = #$FF                       A:40 X:00 Y:6C P:25 SP:F9 PPU:190, 31 CYC:3594\n$F933:A9 40     LDA #$40                           A:40 X:00 Y:6C P:E5 SP:F9 PPU:199, 31 CYC:3597\n$F935:38        SEC                                A:40 X:00 Y:6C P:65 SP:F9 PPU:205, 31 CYC:3599\n$F936:60        RTS                                A:40 X:00 Y:6C P:65 SP:F9 PPU:211, 31 CYC:3601\n$D136:E1 80     SBC ($80,X) @ 80 = #$0200 = #$40       A:40 X:00 Y:6C P:65 SP:FB PPU:229, 31 CYC:3607\n$D138:20 37 F9  JSR $F937                          A:00 X:00 Y:6C P:nvUbdIZC SP:FB PPU:247, 31 CYC:3613\n$F937:30 0B     BMI $F944                          A:00 X:00 Y:6C P:nvUbdIZC SP:F9 PPU:265, 31 CYC:3619\n$F939:90 09     BCC $F944                          A:00 X:00 Y:6C P:nvUbdIZC SP:F9 PPU:271, 31 CYC:3621\n$F93B:D0 07     BNE $F944                          A:00 X:00 Y:6C P:nvUbdIZC SP:F9 PPU:277, 31 CYC:3623\n$F93D:70 05     BVS $F944                          A:00 X:00 Y:6C P:nvUbdIZC SP:F9 PPU:283, 31 CYC:3625\n$F93F:C9 00     CMP #$00                           A:00 X:00 Y:6C P:nvUbdIZC SP:F9 PPU:289, 31 CYC:3627\n$F941:D0 01     BNE $F944                          A:00 X:00 Y:6C P:nvUbdIZC SP:F9 PPU:295, 31 CYC:3629\n$F943:60        RTS                                A:00 X:00 Y:6C P:nvUbdIZC SP:F9 PPU:301, 31 CYC:3631\n$D13B:C8        INY                                A:00 X:00 Y:6C P:nvUbdIZC SP:FB PPU:319, 31 CYC:3637\n$D13C:A9 3F     LDA #$3F                           A:00 X:00 Y:6D P:25 SP:FB PPU:325, 31 CYC:3639\n$D13E:8D 00 02  STA $0200 = #$40                     A:3F X:00 Y:6D P:25 SP:FB PPU:331, 31 CYC:3641\n$D141:20 47 F9  JSR $F947                          A:3F X:00 Y:6D P:25 SP:FB PPU:  2, 32 CYC:3645\n$F947:B8        CLV                                A:3F X:00 Y:6D P:25 SP:F9 PPU: 20, 32 CYC:3651\n$F948:38        SEC                                A:3F X:00 Y:6D P:25 SP:F9 PPU: 26, 32 CYC:3653\n$F949:A9 40     LDA #$40                           A:3F X:00 Y:6D P:25 SP:F9 PPU: 32, 32 CYC:3655\n$F94B:60        RTS                                A:40 X:00 Y:6D P:25 SP:F9 PPU: 38, 32 CYC:3657\n$D144:E1 80     SBC ($80,X) @ 80 = #$0200 = #$3F       A:40 X:00 Y:6D P:25 SP:FB PPU: 56, 32 CYC:3663\n$D146:20 4C F9  JSR $F94C                          A:01 X:00 Y:6D P:25 SP:FB PPU: 74, 32 CYC:3669\n$F94C:F0 0B     BEQ $F959                          A:01 X:00 Y:6D P:25 SP:F9 PPU: 92, 32 CYC:3675\n$F94E:30 09     BMI $F959                          A:01 X:00 Y:6D P:25 SP:F9 PPU: 98, 32 CYC:3677\n$F950:90 07     BCC $F959                          A:01 X:00 Y:6D P:25 SP:F9 PPU:104, 32 CYC:3679\n$F952:70 05     BVS $F959                          A:01 X:00 Y:6D P:25 SP:F9 PPU:110, 32 CYC:3681\n$F954:C9 01     CMP #$01                           A:01 X:00 Y:6D P:25 SP:F9 PPU:116, 32 CYC:3683\n$F956:D0 01     BNE $F959                          A:01 X:00 Y:6D P:nvUbdIZC SP:F9 PPU:122, 32 CYC:3685\n$F958:60        RTS                                A:01 X:00 Y:6D P:nvUbdIZC SP:F9 PPU:128, 32 CYC:3687\n$D149:C8        INY                                A:01 X:00 Y:6D P:nvUbdIZC SP:FB PPU:146, 32 CYC:3693\n$D14A:A9 41     LDA #$41                           A:01 X:00 Y:6E P:25 SP:FB PPU:152, 32 CYC:3695\n$D14C:8D 00 02  STA $0200 = #$3F                     A:41 X:00 Y:6E P:25 SP:FB PPU:158, 32 CYC:3697\n$D14F:20 5C F9  JSR $F95C                          A:41 X:00 Y:6E P:25 SP:FB PPU:170, 32 CYC:3701\n$F95C:A9 40     LDA #$40                           A:41 X:00 Y:6E P:25 SP:F9 PPU:188, 32 CYC:3707\n$F95E:38        SEC                                A:40 X:00 Y:6E P:25 SP:F9 PPU:194, 32 CYC:3709\n$F95F:24 01     BIT $01 = #$FF                       A:40 X:00 Y:6E P:25 SP:F9 PPU:200, 32 CYC:3711\n$F961:60        RTS                                A:40 X:00 Y:6E P:E5 SP:F9 PPU:209, 32 CYC:3714\n$D152:E1 80     SBC ($80,X) @ 80 = #$0200 = #$41       A:40 X:00 Y:6E P:E5 SP:FB PPU:227, 32 CYC:3720\n$D154:20 62 F9  JSR $F962                          A:FF X:00 Y:6E P:NvUbdIzc SP:FB PPU:245, 32 CYC:3726\n$F962:B0 0B     BCS $F96F                          A:FF X:00 Y:6E P:NvUbdIzc SP:F9 PPU:263, 32 CYC:3732\n$F964:F0 09     BEQ $F96F                          A:FF X:00 Y:6E P:NvUbdIzc SP:F9 PPU:269, 32 CYC:3734\n$F966:10 07     BPL $F96F                          A:FF X:00 Y:6E P:NvUbdIzc SP:F9 PPU:275, 32 CYC:3736\n$F968:70 05     BVS $F96F                          A:FF X:00 Y:6E P:NvUbdIzc SP:F9 PPU:281, 32 CYC:3738\n$F96A:C9 FF     CMP #$FF                           A:FF X:00 Y:6E P:NvUbdIzc SP:F9 PPU:287, 32 CYC:3740\n$F96C:D0 01     BNE $F96F                          A:FF X:00 Y:6E P:nvUbdIZC SP:F9 PPU:293, 32 CYC:3742\n$F96E:60        RTS                                A:FF X:00 Y:6E P:nvUbdIZC SP:F9 PPU:299, 32 CYC:3744\n$D157:C8        INY                                A:FF X:00 Y:6E P:nvUbdIZC SP:FB PPU:317, 32 CYC:3750\n$D158:A9 00     LDA #$00                           A:FF X:00 Y:6F P:25 SP:FB PPU:323, 32 CYC:3752\n$D15A:8D 00 02  STA $0200 = #$41                     A:00 X:00 Y:6F P:nvUbdIZC SP:FB PPU:329, 32 CYC:3754\n$D15D:20 72 F9  JSR $F972                          A:00 X:00 Y:6F P:nvUbdIZC SP:FB PPU:  0, 33 CYC:3758\n$F972:18        CLC                                A:00 X:00 Y:6F P:nvUbdIZC SP:F9 PPU: 18, 33 CYC:3764\n$F973:A9 80     LDA #$80                           A:00 X:00 Y:6F P:nvUbdIZc SP:F9 PPU: 24, 33 CYC:3766\n$F975:60        RTS                                A:80 X:00 Y:6F P:NvUbdIzc SP:F9 PPU: 30, 33 CYC:3768\n$D160:E1 80     SBC ($80,X) @ 80 = #$0200 = #$00       A:80 X:00 Y:6F P:NvUbdIzc SP:FB PPU: 48, 33 CYC:3774\n$D162:20 76 F9  JSR $F976                          A:7F X:00 Y:6F P:65 SP:FB PPU: 66, 33 CYC:3780\n$F976:90 05     BCC $F97D                          A:7F X:00 Y:6F P:65 SP:F9 PPU: 84, 33 CYC:3786\n$F978:C9 7F     CMP #$7F                           A:7F X:00 Y:6F P:65 SP:F9 PPU: 90, 33 CYC:3788\n$F97A:D0 01     BNE $F97D                          A:7F X:00 Y:6F P:67 SP:F9 PPU: 96, 33 CYC:3790\n$F97C:60        RTS                                A:7F X:00 Y:6F P:67 SP:F9 PPU:102, 33 CYC:3792\n$D165:C8        INY                                A:7F X:00 Y:6F P:67 SP:FB PPU:120, 33 CYC:3798\n$D166:A9 7F     LDA #$7F                           A:7F X:00 Y:70 P:65 SP:FB PPU:126, 33 CYC:3800\n$D168:8D 00 02  STA $0200 = #$00                     A:7F X:00 Y:70 P:65 SP:FB PPU:132, 33 CYC:3802\n$D16B:20 80 F9  JSR $F980                          A:7F X:00 Y:70 P:65 SP:FB PPU:144, 33 CYC:3806\n$F980:38        SEC                                A:7F X:00 Y:70 P:65 SP:F9 PPU:162, 33 CYC:3812\n$F981:A9 81     LDA #$81                           A:7F X:00 Y:70 P:65 SP:F9 PPU:168, 33 CYC:3814\n$F983:60        RTS                                A:81 X:00 Y:70 P:E5 SP:F9 PPU:174, 33 CYC:3816\n$D16E:E1 80     SBC ($80,X) @ 80 = #$0200 = #$7F       A:81 X:00 Y:70 P:E5 SP:FB PPU:192, 33 CYC:3822\n$D170:20 84 F9  JSR $F984                          A:02 X:00 Y:70 P:65 SP:FB PPU:210, 33 CYC:3828\n$F984:50 07     BVC $F98D                          A:02 X:00 Y:70 P:65 SP:F9 PPU:228, 33 CYC:3834\n$F986:90 05     BCC $F98D                          A:02 X:00 Y:70 P:65 SP:F9 PPU:234, 33 CYC:3836\n$F988:C9 02     CMP #$02                           A:02 X:00 Y:70 P:65 SP:F9 PPU:240, 33 CYC:3838\n$F98A:D0 01     BNE $F98D                          A:02 X:00 Y:70 P:67 SP:F9 PPU:246, 33 CYC:3840\n$F98C:60        RTS                                A:02 X:00 Y:70 P:67 SP:F9 PPU:252, 33 CYC:3842\n$D173:60        RTS                                A:02 X:00 Y:70 P:67 SP:FB PPU:270, 33 CYC:3848\n$C612:20 74 D1  JSR $D174                          A:02 X:00 Y:70 P:67 SP:FD PPU:288, 33 CYC:3854\n$D174:A9 55     LDA #$55                           A:02 X:00 Y:70 P:67 SP:FB PPU:306, 33 CYC:3860\n$D176:85 78     STA $78 = #$00                       A:55 X:00 Y:70 P:65 SP:FB PPU:312, 33 CYC:3862\n$D178:A9 FF     LDA #$FF                           A:55 X:00 Y:70 P:65 SP:FB PPU:321, 33 CYC:3865\n$D17A:85 01     STA $01 = #$FF                       A:FF X:00 Y:70 P:E5 SP:FB PPU:327, 33 CYC:3867\n$D17C:24 01     BIT $01 = #$FF                       A:FF X:00 Y:70 P:E5 SP:FB PPU:336, 33 CYC:3870\n$D17E:A0 11     LDY #$11                           A:FF X:00 Y:70 P:E5 SP:FB PPU:  4, 34 CYC:3873\n$D180:A2 23     LDX #$23                           A:FF X:00 Y:11 P:65 SP:FB PPU: 10, 34 CYC:3875\n$D182:A9 00     LDA #$00                           A:FF X:23 Y:11 P:65 SP:FB PPU: 16, 34 CYC:3877\n$D184:A5 78     LDA $78 = #$55                       A:00 X:23 Y:11 P:67 SP:FB PPU: 22, 34 CYC:3879\n$D186:F0 10     BEQ $D198                          A:55 X:23 Y:11 P:65 SP:FB PPU: 31, 34 CYC:3882\n$D188:30 0E     BMI $D198                          A:55 X:23 Y:11 P:65 SP:FB PPU: 37, 34 CYC:3884\n$D18A:C9 55     CMP #$55                           A:55 X:23 Y:11 P:65 SP:FB PPU: 43, 34 CYC:3886\n$D18C:D0 0A     BNE $D198                          A:55 X:23 Y:11 P:67 SP:FB PPU: 49, 34 CYC:3888\n$D18E:C0 11     CPY #$11                           A:55 X:23 Y:11 P:67 SP:FB PPU: 55, 34 CYC:3890\n$D190:D0 06     BNE $D198                          A:55 X:23 Y:11 P:67 SP:FB PPU: 61, 34 CYC:3892\n$D192:E0 23     CPX #$23                           A:55 X:23 Y:11 P:67 SP:FB PPU: 67, 34 CYC:3894\n$D194:50 02     BVC $D198                          A:55 X:23 Y:11 P:67 SP:FB PPU: 73, 34 CYC:3896\n$D196:F0 04     BEQ $D19C                          A:55 X:23 Y:11 P:67 SP:FB PPU: 79, 34 CYC:3898\n$D19C:A9 46     LDA #$46                           A:55 X:23 Y:11 P:67 SP:FB PPU: 88, 34 CYC:3901\n$D19E:24 01     BIT $01 = #$FF                       A:46 X:23 Y:11 P:65 SP:FB PPU: 94, 34 CYC:3903\n$D1A0:85 78     STA $78 = #$55                       A:46 X:23 Y:11 P:E5 SP:FB PPU:103, 34 CYC:3906\n$D1A2:F0 0A     BEQ $D1AE                          A:46 X:23 Y:11 P:E5 SP:FB PPU:112, 34 CYC:3909\n$D1A4:10 08     BPL $D1AE                          A:46 X:23 Y:11 P:E5 SP:FB PPU:118, 34 CYC:3911\n$D1A6:50 06     BVC $D1AE                          A:46 X:23 Y:11 P:E5 SP:FB PPU:124, 34 CYC:3913\n$D1A8:A5 78     LDA $78 = #$46                       A:46 X:23 Y:11 P:E5 SP:FB PPU:130, 34 CYC:3915\n$D1AA:C9 46     CMP #$46                           A:46 X:23 Y:11 P:65 SP:FB PPU:139, 34 CYC:3918\n$D1AC:F0 04     BEQ $D1B2                          A:46 X:23 Y:11 P:67 SP:FB PPU:145, 34 CYC:3920\n$D1B2:A9 55     LDA #$55                           A:46 X:23 Y:11 P:67 SP:FB PPU:154, 34 CYC:3923\n$D1B4:85 78     STA $78 = #$46                       A:55 X:23 Y:11 P:65 SP:FB PPU:160, 34 CYC:3925\n$D1B6:24 01     BIT $01 = #$FF                       A:55 X:23 Y:11 P:65 SP:FB PPU:169, 34 CYC:3928\n$D1B8:A9 11     LDA #$11                           A:55 X:23 Y:11 P:E5 SP:FB PPU:178, 34 CYC:3931\n$D1BA:A2 23     LDX #$23                           A:11 X:23 Y:11 P:65 SP:FB PPU:184, 34 CYC:3933\n$D1BC:A0 00     LDY #$00                           A:11 X:23 Y:11 P:65 SP:FB PPU:190, 34 CYC:3935\n$D1BE:A4 78     LDY $78 = #$55                       A:11 X:23 Y:00 P:67 SP:FB PPU:196, 34 CYC:3937\n$D1C0:F0 10     BEQ $D1D2                          A:11 X:23 Y:55 P:65 SP:FB PPU:205, 34 CYC:3940\n$D1C2:30 0E     BMI $D1D2                          A:11 X:23 Y:55 P:65 SP:FB PPU:211, 34 CYC:3942\n$D1C4:C0 55     CPY #$55                           A:11 X:23 Y:55 P:65 SP:FB PPU:217, 34 CYC:3944\n$D1C6:D0 0A     BNE $D1D2                          A:11 X:23 Y:55 P:67 SP:FB PPU:223, 34 CYC:3946\n$D1C8:C9 11     CMP #$11                           A:11 X:23 Y:55 P:67 SP:FB PPU:229, 34 CYC:3948\n$D1CA:D0 06     BNE $D1D2                          A:11 X:23 Y:55 P:67 SP:FB PPU:235, 34 CYC:3950\n$D1CC:E0 23     CPX #$23                           A:11 X:23 Y:55 P:67 SP:FB PPU:241, 34 CYC:3952\n$D1CE:50 02     BVC $D1D2                          A:11 X:23 Y:55 P:67 SP:FB PPU:247, 34 CYC:3954\n$D1D0:F0 04     BEQ $D1D6                          A:11 X:23 Y:55 P:67 SP:FB PPU:253, 34 CYC:3956\n$D1D6:A0 46     LDY #$46                           A:11 X:23 Y:55 P:67 SP:FB PPU:262, 34 CYC:3959\n$D1D8:24 01     BIT $01 = #$FF                       A:11 X:23 Y:46 P:65 SP:FB PPU:268, 34 CYC:3961\n$D1DA:84 78     STY $78 = #$55                       A:11 X:23 Y:46 P:E5 SP:FB PPU:277, 34 CYC:3964\n$D1DC:F0 0A     BEQ $D1E8                          A:11 X:23 Y:46 P:E5 SP:FB PPU:286, 34 CYC:3967\n$D1DE:10 08     BPL $D1E8                          A:11 X:23 Y:46 P:E5 SP:FB PPU:292, 34 CYC:3969\n$D1E0:50 06     BVC $D1E8                          A:11 X:23 Y:46 P:E5 SP:FB PPU:298, 34 CYC:3971\n$D1E2:A4 78     LDY $78 = #$46                       A:11 X:23 Y:46 P:E5 SP:FB PPU:304, 34 CYC:3973\n$D1E4:C0 46     CPY #$46                           A:11 X:23 Y:46 P:65 SP:FB PPU:313, 34 CYC:3976\n$D1E6:F0 04     BEQ $D1EC                          A:11 X:23 Y:46 P:67 SP:FB PPU:319, 34 CYC:3978\n$D1EC:24 01     BIT $01 = #$FF                       A:11 X:23 Y:46 P:67 SP:FB PPU:328, 34 CYC:3981\n$D1EE:A9 55     LDA #$55                           A:11 X:23 Y:46 P:E5 SP:FB PPU:337, 34 CYC:3984\n$D1F0:85 78     STA $78 = #$46                       A:55 X:23 Y:46 P:65 SP:FB PPU:  2, 35 CYC:3986\n$D1F2:A0 11     LDY #$11                           A:55 X:23 Y:46 P:65 SP:FB PPU: 11, 35 CYC:3989\n$D1F4:A9 23     LDA #$23                           A:55 X:23 Y:11 P:65 SP:FB PPU: 17, 35 CYC:3991\n$D1F6:A2 00     LDX #$00                           A:23 X:23 Y:11 P:65 SP:FB PPU: 23, 35 CYC:3993\n$D1F8:A6 78     LDX $78 = #$55                       A:23 X:00 Y:11 P:67 SP:FB PPU: 29, 35 CYC:3995\n$D1FA:F0 10     BEQ $D20C                          A:23 X:55 Y:11 P:65 SP:FB PPU: 38, 35 CYC:3998\n$D1FC:30 0E     BMI $D20C                          A:23 X:55 Y:11 P:65 SP:FB PPU: 44, 35 CYC:4000\n$D1FE:E0 55     CPX #$55                           A:23 X:55 Y:11 P:65 SP:FB PPU: 50, 35 CYC:4002\n$D200:D0 0A     BNE $D20C                          A:23 X:55 Y:11 P:67 SP:FB PPU: 56, 35 CYC:4004\n$D202:C0 11     CPY #$11                           A:23 X:55 Y:11 P:67 SP:FB PPU: 62, 35 CYC:4006\n$D204:D0 06     BNE $D20C                          A:23 X:55 Y:11 P:67 SP:FB PPU: 68, 35 CYC:4008\n$D206:C9 23     CMP #$23                           A:23 X:55 Y:11 P:67 SP:FB PPU: 74, 35 CYC:4010\n$D208:50 02     BVC $D20C                          A:23 X:55 Y:11 P:67 SP:FB PPU: 80, 35 CYC:4012\n$D20A:F0 04     BEQ $D210                          A:23 X:55 Y:11 P:67 SP:FB PPU: 86, 35 CYC:4014\n$D210:A2 46     LDX #$46                           A:23 X:55 Y:11 P:67 SP:FB PPU: 95, 35 CYC:4017\n$D212:24 01     BIT $01 = #$FF                       A:23 X:46 Y:11 P:65 SP:FB PPU:101, 35 CYC:4019\n$D214:86 78     STX $78 = #$55                       A:23 X:46 Y:11 P:E5 SP:FB PPU:110, 35 CYC:4022\n$D216:F0 0A     BEQ $D222                          A:23 X:46 Y:11 P:E5 SP:FB PPU:119, 35 CYC:4025\n$D218:10 08     BPL $D222                          A:23 X:46 Y:11 P:E5 SP:FB PPU:125, 35 CYC:4027\n$D21A:50 06     BVC $D222                          A:23 X:46 Y:11 P:E5 SP:FB PPU:131, 35 CYC:4029\n$D21C:A6 78     LDX $78 = #$46                       A:23 X:46 Y:11 P:E5 SP:FB PPU:137, 35 CYC:4031\n$D21E:E0 46     CPX #$46                           A:23 X:46 Y:11 P:65 SP:FB PPU:146, 35 CYC:4034\n$D220:F0 04     BEQ $D226                          A:23 X:46 Y:11 P:67 SP:FB PPU:152, 35 CYC:4036\n$D226:A9 C0     LDA #$C0                           A:23 X:46 Y:11 P:67 SP:FB PPU:161, 35 CYC:4039\n$D228:85 78     STA $78 = #$46                       A:C0 X:46 Y:11 P:E5 SP:FB PPU:167, 35 CYC:4041\n$D22A:A2 33     LDX #$33                           A:C0 X:46 Y:11 P:E5 SP:FB PPU:176, 35 CYC:4044\n$D22C:A0 88     LDY #$88                           A:C0 X:33 Y:11 P:65 SP:FB PPU:182, 35 CYC:4046\n$D22E:A9 05     LDA #$05                           A:C0 X:33 Y:88 P:E5 SP:FB PPU:188, 35 CYC:4048\n$D230:24 78     BIT $78 = #$C0                       A:05 X:33 Y:88 P:65 SP:FB PPU:194, 35 CYC:4050\n$D232:10 10     BPL $D244                          A:05 X:33 Y:88 P:E7 SP:FB PPU:203, 35 CYC:4053\n$D234:50 0E     BVC $D244                          A:05 X:33 Y:88 P:E7 SP:FB PPU:209, 35 CYC:4055\n$D236:D0 0C     BNE $D244                          A:05 X:33 Y:88 P:E7 SP:FB PPU:215, 35 CYC:4057\n$D238:C9 05     CMP #$05                           A:05 X:33 Y:88 P:E7 SP:FB PPU:221, 35 CYC:4059\n$D23A:D0 08     BNE $D244                          A:05 X:33 Y:88 P:67 SP:FB PPU:227, 35 CYC:4061\n$D23C:E0 33     CPX #$33                           A:05 X:33 Y:88 P:67 SP:FB PPU:233, 35 CYC:4063\n$D23E:D0 04     BNE $D244                          A:05 X:33 Y:88 P:67 SP:FB PPU:239, 35 CYC:4065\n$D240:C0 88     CPY #$88                           A:05 X:33 Y:88 P:67 SP:FB PPU:245, 35 CYC:4067\n$D242:F0 04     BEQ $D248                          A:05 X:33 Y:88 P:67 SP:FB PPU:251, 35 CYC:4069\n$D248:A9 03     LDA #$03                           A:05 X:33 Y:88 P:67 SP:FB PPU:260, 35 CYC:4072\n$D24A:85 78     STA $78 = #$C0                       A:03 X:33 Y:88 P:65 SP:FB PPU:266, 35 CYC:4074\n$D24C:A9 01     LDA #$01                           A:03 X:33 Y:88 P:65 SP:FB PPU:275, 35 CYC:4077\n$D24E:24 78     BIT $78 = #$03                       A:01 X:33 Y:88 P:65 SP:FB PPU:281, 35 CYC:4079\n$D250:30 08     BMI $D25A                          A:01 X:33 Y:88 P:25 SP:FB PPU:290, 35 CYC:4082\n$D252:70 06     BVS $D25A                          A:01 X:33 Y:88 P:25 SP:FB PPU:296, 35 CYC:4084\n$D254:F0 04     BEQ $D25A                          A:01 X:33 Y:88 P:25 SP:FB PPU:302, 35 CYC:4086\n$D256:C9 01     CMP #$01                           A:01 X:33 Y:88 P:25 SP:FB PPU:308, 35 CYC:4088\n$D258:F0 04     BEQ $D25E                          A:01 X:33 Y:88 P:nvUbdIZC SP:FB PPU:314, 35 CYC:4090\n$D25E:A0 7E     LDY #$7E                           A:01 X:33 Y:88 P:nvUbdIZC SP:FB PPU:323, 35 CYC:4093\n$D260:A9 AA     LDA #$AA                           A:01 X:33 Y:7E P:25 SP:FB PPU:329, 35 CYC:4095\n$D262:85 78     STA $78 = #$03                       A:AA X:33 Y:7E P:A5 SP:FB PPU:335, 35 CYC:4097\n$D264:20 B6 F7  JSR $F7B6                          A:AA X:33 Y:7E P:A5 SP:FB PPU:  3, 36 CYC:4100\n$F7B6:18        CLC                                A:AA X:33 Y:7E P:A5 SP:F9 PPU: 21, 36 CYC:4106\n$F7B7:A9 FF     LDA #$FF                           A:AA X:33 Y:7E P:NvUbdIzc SP:F9 PPU: 27, 36 CYC:4108\n$F7B9:85 01     STA $01 = #$FF                       A:FF X:33 Y:7E P:NvUbdIzc SP:F9 PPU: 33, 36 CYC:4110\n$F7BB:24 01     BIT $01 = #$FF                       A:FF X:33 Y:7E P:NvUbdIzc SP:F9 PPU: 42, 36 CYC:4113\n$F7BD:A9 55     LDA #$55                           A:FF X:33 Y:7E P:NVUbdIzc SP:F9 PPU: 51, 36 CYC:4116\n$F7BF:60        RTS                                A:55 X:33 Y:7E P:64 SP:F9 PPU: 57, 36 CYC:4118\n$D267:05 78     ORA $78 = #$AA                       A:55 X:33 Y:7E P:64 SP:FB PPU: 75, 36 CYC:4124\n$D269:20 C0 F7  JSR $F7C0                          A:FF X:33 Y:7E P:NVUbdIzc SP:FB PPU: 84, 36 CYC:4127\n$F7C0:B0 09     BCS $F7CB                          A:FF X:33 Y:7E P:NVUbdIzc SP:F9 PPU:102, 36 CYC:4133\n$F7C2:10 07     BPL $F7CB                          A:FF X:33 Y:7E P:NVUbdIzc SP:F9 PPU:108, 36 CYC:4135\n$F7C4:C9 FF     CMP #$FF                           A:FF X:33 Y:7E P:NVUbdIzc SP:F9 PPU:114, 36 CYC:4137\n$F7C6:D0 03     BNE $F7CB                          A:FF X:33 Y:7E P:67 SP:F9 PPU:120, 36 CYC:4139\n$F7C8:50 01     BVC $F7CB                          A:FF X:33 Y:7E P:67 SP:F9 PPU:126, 36 CYC:4141\n$F7CA:60        RTS                                A:FF X:33 Y:7E P:67 SP:F9 PPU:132, 36 CYC:4143\n$D26C:C8        INY                                A:FF X:33 Y:7E P:67 SP:FB PPU:150, 36 CYC:4149\n$D26D:A9 00     LDA #$00                           A:FF X:33 Y:7F P:65 SP:FB PPU:156, 36 CYC:4151\n$D26F:85 78     STA $78 = #$AA                       A:00 X:33 Y:7F P:67 SP:FB PPU:162, 36 CYC:4153\n$D271:20 CE F7  JSR $F7CE                          A:00 X:33 Y:7F P:67 SP:FB PPU:171, 36 CYC:4156\n$F7CE:38        SEC                                A:00 X:33 Y:7F P:67 SP:F9 PPU:189, 36 CYC:4162\n$F7CF:B8        CLV                                A:00 X:33 Y:7F P:67 SP:F9 PPU:195, 36 CYC:4164\n$F7D0:A9 00     LDA #$00                           A:00 X:33 Y:7F P:nvUbdIZC SP:F9 PPU:201, 36 CYC:4166\n$F7D2:60        RTS                                A:00 X:33 Y:7F P:nvUbdIZC SP:F9 PPU:207, 36 CYC:4168\n$D274:05 78     ORA $78 = #$00                       A:00 X:33 Y:7F P:nvUbdIZC SP:FB PPU:225, 36 CYC:4174\n$D276:20 D3 F7  JSR $F7D3                          A:00 X:33 Y:7F P:nvUbdIZC SP:FB PPU:234, 36 CYC:4177\n$F7D3:D0 07     BNE $F7DC                          A:00 X:33 Y:7F P:nvUbdIZC SP:F9 PPU:252, 36 CYC:4183\n$F7D5:70 05     BVS $F7DC                          A:00 X:33 Y:7F P:nvUbdIZC SP:F9 PPU:258, 36 CYC:4185\n$F7D7:90 03     BCC $F7DC                          A:00 X:33 Y:7F P:nvUbdIZC SP:F9 PPU:264, 36 CYC:4187\n$F7D9:30 01     BMI $F7DC                          A:00 X:33 Y:7F P:nvUbdIZC SP:F9 PPU:270, 36 CYC:4189\n$F7DB:60        RTS                                A:00 X:33 Y:7F P:nvUbdIZC SP:F9 PPU:276, 36 CYC:4191\n$D279:C8        INY                                A:00 X:33 Y:7F P:nvUbdIZC SP:FB PPU:294, 36 CYC:4197\n$D27A:A9 AA     LDA #$AA                           A:00 X:33 Y:80 P:A5 SP:FB PPU:300, 36 CYC:4199\n$D27C:85 78     STA $78 = #$00                       A:AA X:33 Y:80 P:A5 SP:FB PPU:306, 36 CYC:4201\n$D27E:20 DF F7  JSR $F7DF                          A:AA X:33 Y:80 P:A5 SP:FB PPU:315, 36 CYC:4204\n$F7DF:18        CLC                                A:AA X:33 Y:80 P:A5 SP:F9 PPU:333, 36 CYC:4210\n$F7E0:24 01     BIT $01 = #$FF                       A:AA X:33 Y:80 P:NvUbdIzc SP:F9 PPU:339, 36 CYC:4212\n$F7E2:A9 55     LDA #$55                           A:AA X:33 Y:80 P:NVUbdIzc SP:F9 PPU:  7, 37 CYC:4215\n$F7E4:60        RTS                                A:55 X:33 Y:80 P:64 SP:F9 PPU: 13, 37 CYC:4217\n$D281:25 78     AND $78 = #$AA                       A:55 X:33 Y:80 P:64 SP:FB PPU: 31, 37 CYC:4223\n$D283:20 E5 F7  JSR $F7E5                          A:00 X:33 Y:80 P:nVUbdIZc SP:FB PPU: 40, 37 CYC:4226\n$F7E5:D0 07     BNE $F7EE                          A:00 X:33 Y:80 P:nVUbdIZc SP:F9 PPU: 58, 37 CYC:4232\n$F7E7:50 05     BVC $F7EE                          A:00 X:33 Y:80 P:nVUbdIZc SP:F9 PPU: 64, 37 CYC:4234\n$F7E9:B0 03     BCS $F7EE                          A:00 X:33 Y:80 P:nVUbdIZc SP:F9 PPU: 70, 37 CYC:4236\n$F7EB:30 01     BMI $F7EE                          A:00 X:33 Y:80 P:nVUbdIZc SP:F9 PPU: 76, 37 CYC:4238\n$F7ED:60        RTS                                A:00 X:33 Y:80 P:nVUbdIZc SP:F9 PPU: 82, 37 CYC:4240\n$D286:C8        INY                                A:00 X:33 Y:80 P:nVUbdIZc SP:FB PPU:100, 37 CYC:4246\n$D287:A9 EF     LDA #$EF                           A:00 X:33 Y:81 P:NVUbdIzc SP:FB PPU:106, 37 CYC:4248\n$D289:85 78     STA $78 = #$AA                       A:EF X:33 Y:81 P:NVUbdIzc SP:FB PPU:112, 37 CYC:4250\n$D28B:20 F1 F7  JSR $F7F1                          A:EF X:33 Y:81 P:NVUbdIzc SP:FB PPU:121, 37 CYC:4253\n$F7F1:38        SEC                                A:EF X:33 Y:81 P:NVUbdIzc SP:F9 PPU:139, 37 CYC:4259\n$F7F2:B8        CLV                                A:EF X:33 Y:81 P:E5 SP:F9 PPU:145, 37 CYC:4261\n$F7F3:A9 F8     LDA #$F8                           A:EF X:33 Y:81 P:A5 SP:F9 PPU:151, 37 CYC:4263\n$F7F5:60        RTS                                A:F8 X:33 Y:81 P:A5 SP:F9 PPU:157, 37 CYC:4265\n$D28E:25 78     AND $78 = #$EF                       A:F8 X:33 Y:81 P:A5 SP:FB PPU:175, 37 CYC:4271\n$D290:20 F6 F7  JSR $F7F6                          A:E8 X:33 Y:81 P:A5 SP:FB PPU:184, 37 CYC:4274\n$F7F6:90 09     BCC $F801                          A:E8 X:33 Y:81 P:A5 SP:F9 PPU:202, 37 CYC:4280\n$F7F8:10 07     BPL $F801                          A:E8 X:33 Y:81 P:A5 SP:F9 PPU:208, 37 CYC:4282\n$F7FA:C9 E8     CMP #$E8                           A:E8 X:33 Y:81 P:A5 SP:F9 PPU:214, 37 CYC:4284\n$F7FC:D0 03     BNE $F801                          A:E8 X:33 Y:81 P:nvUbdIZC SP:F9 PPU:220, 37 CYC:4286\n$F7FE:70 01     BVS $F801                          A:E8 X:33 Y:81 P:nvUbdIZC SP:F9 PPU:226, 37 CYC:4288\n$F800:60        RTS                                A:E8 X:33 Y:81 P:nvUbdIZC SP:F9 PPU:232, 37 CYC:4290\n$D293:C8        INY                                A:E8 X:33 Y:81 P:nvUbdIZC SP:FB PPU:250, 37 CYC:4296\n$D294:A9 AA     LDA #$AA                           A:E8 X:33 Y:82 P:A5 SP:FB PPU:256, 37 CYC:4298\n$D296:85 78     STA $78 = #$EF                       A:AA X:33 Y:82 P:A5 SP:FB PPU:262, 37 CYC:4300\n$D298:20 04 F8  JSR $F804                          A:AA X:33 Y:82 P:A5 SP:FB PPU:271, 37 CYC:4303\n$F804:18        CLC                                A:AA X:33 Y:82 P:A5 SP:F9 PPU:289, 37 CYC:4309\n$F805:24 01     BIT $01 = #$FF                       A:AA X:33 Y:82 P:NvUbdIzc SP:F9 PPU:295, 37 CYC:4311\n$F807:A9 5F     LDA #$5F                           A:AA X:33 Y:82 P:NVUbdIzc SP:F9 PPU:304, 37 CYC:4314\n$F809:60        RTS                                A:5F X:33 Y:82 P:64 SP:F9 PPU:310, 37 CYC:4316\n$D29B:45 78     EOR $78 = #$AA                       A:5F X:33 Y:82 P:64 SP:FB PPU:328, 37 CYC:4322\n$D29D:20 0A F8  JSR $F80A                          A:F5 X:33 Y:82 P:NVUbdIzc SP:FB PPU:337, 37 CYC:4325\n$F80A:B0 09     BCS $F815                          A:F5 X:33 Y:82 P:NVUbdIzc SP:F9 PPU: 14, 38 CYC:4331\n$F80C:10 07     BPL $F815                          A:F5 X:33 Y:82 P:NVUbdIzc SP:F9 PPU: 20, 38 CYC:4333\n$F80E:C9 F5     CMP #$F5                           A:F5 X:33 Y:82 P:NVUbdIzc SP:F9 PPU: 26, 38 CYC:4335\n$F810:D0 03     BNE $F815                          A:F5 X:33 Y:82 P:67 SP:F9 PPU: 32, 38 CYC:4337\n$F812:50 01     BVC $F815                          A:F5 X:33 Y:82 P:67 SP:F9 PPU: 38, 38 CYC:4339\n$F814:60        RTS                                A:F5 X:33 Y:82 P:67 SP:F9 PPU: 44, 38 CYC:4341\n$D2A0:C8        INY                                A:F5 X:33 Y:82 P:67 SP:FB PPU: 62, 38 CYC:4347\n$D2A1:A9 70     LDA #$70                           A:F5 X:33 Y:83 P:E5 SP:FB PPU: 68, 38 CYC:4349\n$D2A3:85 78     STA $78 = #$AA                       A:70 X:33 Y:83 P:65 SP:FB PPU: 74, 38 CYC:4351\n$D2A5:20 18 F8  JSR $F818                          A:70 X:33 Y:83 P:65 SP:FB PPU: 83, 38 CYC:4354\n$F818:38        SEC                                A:70 X:33 Y:83 P:65 SP:F9 PPU:101, 38 CYC:4360\n$F819:B8        CLV                                A:70 X:33 Y:83 P:65 SP:F9 PPU:107, 38 CYC:4362\n$F81A:A9 70     LDA #$70                           A:70 X:33 Y:83 P:25 SP:F9 PPU:113, 38 CYC:4364\n$F81C:60        RTS                                A:70 X:33 Y:83 P:25 SP:F9 PPU:119, 38 CYC:4366\n$D2A8:45 78     EOR $78 = #$70                       A:70 X:33 Y:83 P:25 SP:FB PPU:137, 38 CYC:4372\n$D2AA:20 1D F8  JSR $F81D                          A:00 X:33 Y:83 P:nvUbdIZC SP:FB PPU:146, 38 CYC:4375\n$F81D:D0 07     BNE $F826                          A:00 X:33 Y:83 P:nvUbdIZC SP:F9 PPU:164, 38 CYC:4381\n$F81F:70 05     BVS $F826                          A:00 X:33 Y:83 P:nvUbdIZC SP:F9 PPU:170, 38 CYC:4383\n$F821:90 03     BCC $F826                          A:00 X:33 Y:83 P:nvUbdIZC SP:F9 PPU:176, 38 CYC:4385\n$F823:30 01     BMI $F826                          A:00 X:33 Y:83 P:nvUbdIZC SP:F9 PPU:182, 38 CYC:4387\n$F825:60        RTS                                A:00 X:33 Y:83 P:nvUbdIZC SP:F9 PPU:188, 38 CYC:4389\n$D2AD:C8        INY                                A:00 X:33 Y:83 P:nvUbdIZC SP:FB PPU:206, 38 CYC:4395\n$D2AE:A9 69     LDA #$69                           A:00 X:33 Y:84 P:A5 SP:FB PPU:212, 38 CYC:4397\n$D2B0:85 78     STA $78 = #$70                       A:69 X:33 Y:84 P:25 SP:FB PPU:218, 38 CYC:4399\n$D2B2:20 29 F8  JSR $F829                          A:69 X:33 Y:84 P:25 SP:FB PPU:227, 38 CYC:4402\n$F829:18        CLC                                A:69 X:33 Y:84 P:25 SP:F9 PPU:245, 38 CYC:4408\n$F82A:24 01     BIT $01 = #$FF                       A:69 X:33 Y:84 P:nvUbdIzc SP:F9 PPU:251, 38 CYC:4410\n$F82C:A9 00     LDA #$00                           A:69 X:33 Y:84 P:NVUbdIzc SP:F9 PPU:260, 38 CYC:4413\n$F82E:60        RTS                                A:00 X:33 Y:84 P:nVUbdIZc SP:F9 PPU:266, 38 CYC:4415\n$D2B5:65 78     ADC $78 = #$69                       A:00 X:33 Y:84 P:nVUbdIZc SP:FB PPU:284, 38 CYC:4421\n$D2B7:20 2F F8  JSR $F82F                          A:69 X:33 Y:84 P:nvUbdIzc SP:FB PPU:293, 38 CYC:4424\n$F82F:30 09     BMI $F83A                          A:69 X:33 Y:84 P:nvUbdIzc SP:F9 PPU:311, 38 CYC:4430\n$F831:B0 07     BCS $F83A                          A:69 X:33 Y:84 P:nvUbdIzc SP:F9 PPU:317, 38 CYC:4432\n$F833:C9 69     CMP #$69                           A:69 X:33 Y:84 P:nvUbdIzc SP:F9 PPU:323, 38 CYC:4434\n$F835:D0 03     BNE $F83A                          A:69 X:33 Y:84 P:nvUbdIZC SP:F9 PPU:329, 38 CYC:4436\n$F837:70 01     BVS $F83A                          A:69 X:33 Y:84 P:nvUbdIZC SP:F9 PPU:335, 38 CYC:4438\n$F839:60        RTS                                A:69 X:33 Y:84 P:nvUbdIZC SP:F9 PPU:  0, 39 CYC:4440\n$D2BA:C8        INY                                A:69 X:33 Y:84 P:nvUbdIZC SP:FB PPU: 18, 39 CYC:4446\n$D2BB:20 3D F8  JSR $F83D                          A:69 X:33 Y:85 P:A5 SP:FB PPU: 24, 39 CYC:4448\n$F83D:38        SEC                                A:69 X:33 Y:85 P:A5 SP:F9 PPU: 42, 39 CYC:4454\n$F83E:24 01     BIT $01 = #$FF                       A:69 X:33 Y:85 P:A5 SP:F9 PPU: 48, 39 CYC:4456\n$F840:A9 00     LDA #$00                           A:69 X:33 Y:85 P:E5 SP:F9 PPU: 57, 39 CYC:4459\n$F842:60        RTS                                A:00 X:33 Y:85 P:67 SP:F9 PPU: 63, 39 CYC:4461\n$D2BE:65 78     ADC $78 = #$69                       A:00 X:33 Y:85 P:67 SP:FB PPU: 81, 39 CYC:4467\n$D2C0:20 43 F8  JSR $F843                          A:6A X:33 Y:85 P:nvUbdIzc SP:FB PPU: 90, 39 CYC:4470\n$F843:30 09     BMI $F84E                          A:6A X:33 Y:85 P:nvUbdIzc SP:F9 PPU:108, 39 CYC:4476\n$F845:B0 07     BCS $F84E                          A:6A X:33 Y:85 P:nvUbdIzc SP:F9 PPU:114, 39 CYC:4478\n$F847:C9 6A     CMP #$6A                           A:6A X:33 Y:85 P:nvUbdIzc SP:F9 PPU:120, 39 CYC:4480\n$F849:D0 03     BNE $F84E                          A:6A X:33 Y:85 P:nvUbdIZC SP:F9 PPU:126, 39 CYC:4482\n$F84B:70 01     BVS $F84E                          A:6A X:33 Y:85 P:nvUbdIZC SP:F9 PPU:132, 39 CYC:4484\n$F84D:60        RTS                                A:6A X:33 Y:85 P:nvUbdIZC SP:F9 PPU:138, 39 CYC:4486\n$D2C3:C8        INY                                A:6A X:33 Y:85 P:nvUbdIZC SP:FB PPU:156, 39 CYC:4492\n$D2C4:A9 7F     LDA #$7F                           A:6A X:33 Y:86 P:A5 SP:FB PPU:162, 39 CYC:4494\n$D2C6:85 78     STA $78 = #$69                       A:7F X:33 Y:86 P:25 SP:FB PPU:168, 39 CYC:4496\n$D2C8:20 51 F8  JSR $F851                          A:7F X:33 Y:86 P:25 SP:FB PPU:177, 39 CYC:4499\n$F851:38        SEC                                A:7F X:33 Y:86 P:25 SP:F9 PPU:195, 39 CYC:4505\n$F852:B8        CLV                                A:7F X:33 Y:86 P:25 SP:F9 PPU:201, 39 CYC:4507\n$F853:A9 7F     LDA #$7F                           A:7F X:33 Y:86 P:25 SP:F9 PPU:207, 39 CYC:4509\n$F855:60        RTS                                A:7F X:33 Y:86 P:25 SP:F9 PPU:213, 39 CYC:4511\n$D2CB:65 78     ADC $78 = #$7F                       A:7F X:33 Y:86 P:25 SP:FB PPU:231, 39 CYC:4517\n$D2CD:20 56 F8  JSR $F856                          A:FF X:33 Y:86 P:NVUbdIzc SP:FB PPU:240, 39 CYC:4520\n$F856:10 09     BPL $F861                          A:FF X:33 Y:86 P:NVUbdIzc SP:F9 PPU:258, 39 CYC:4526\n$F858:B0 07     BCS $F861                          A:FF X:33 Y:86 P:NVUbdIzc SP:F9 PPU:264, 39 CYC:4528\n$F85A:C9 FF     CMP #$FF                           A:FF X:33 Y:86 P:NVUbdIzc SP:F9 PPU:270, 39 CYC:4530\n$F85C:D0 03     BNE $F861                          A:FF X:33 Y:86 P:67 SP:F9 PPU:276, 39 CYC:4532\n$F85E:50 01     BVC $F861                          A:FF X:33 Y:86 P:67 SP:F9 PPU:282, 39 CYC:4534\n$F860:60        RTS                                A:FF X:33 Y:86 P:67 SP:F9 PPU:288, 39 CYC:4536\n$D2D0:C8        INY                                A:FF X:33 Y:86 P:67 SP:FB PPU:306, 39 CYC:4542\n$D2D1:A9 80     LDA #$80                           A:FF X:33 Y:87 P:E5 SP:FB PPU:312, 39 CYC:4544\n$D2D3:85 78     STA $78 = #$7F                       A:80 X:33 Y:87 P:E5 SP:FB PPU:318, 39 CYC:4546\n$D2D5:20 64 F8  JSR $F864                          A:80 X:33 Y:87 P:E5 SP:FB PPU:327, 39 CYC:4549\n$F864:18        CLC                                A:80 X:33 Y:87 P:E5 SP:F9 PPU:  4, 40 CYC:4555\n$F865:24 01     BIT $01 = #$FF                       A:80 X:33 Y:87 P:NVUbdIzc SP:F9 PPU: 10, 40 CYC:4557\n$F867:A9 7F     LDA #$7F                           A:80 X:33 Y:87 P:NVUbdIzc SP:F9 PPU: 19, 40 CYC:4560\n$F869:60        RTS                                A:7F X:33 Y:87 P:64 SP:F9 PPU: 25, 40 CYC:4562\n$D2D8:65 78     ADC $78 = #$80                       A:7F X:33 Y:87 P:64 SP:FB PPU: 43, 40 CYC:4568\n$D2DA:20 6A F8  JSR $F86A                          A:FF X:33 Y:87 P:NvUbdIzc SP:FB PPU: 52, 40 CYC:4571\n$F86A:10 09     BPL $F875                          A:FF X:33 Y:87 P:NvUbdIzc SP:F9 PPU: 70, 40 CYC:4577\n$F86C:B0 07     BCS $F875                          A:FF X:33 Y:87 P:NvUbdIzc SP:F9 PPU: 76, 40 CYC:4579\n$F86E:C9 FF     CMP #$FF                           A:FF X:33 Y:87 P:NvUbdIzc SP:F9 PPU: 82, 40 CYC:4581\n$F870:D0 03     BNE $F875                          A:FF X:33 Y:87 P:nvUbdIZC SP:F9 PPU: 88, 40 CYC:4583\n$F872:70 01     BVS $F875                          A:FF X:33 Y:87 P:nvUbdIZC SP:F9 PPU: 94, 40 CYC:4585\n$F874:60        RTS                                A:FF X:33 Y:87 P:nvUbdIZC SP:F9 PPU:100, 40 CYC:4587\n$D2DD:C8        INY                                A:FF X:33 Y:87 P:nvUbdIZC SP:FB PPU:118, 40 CYC:4593\n$D2DE:20 78 F8  JSR $F878                          A:FF X:33 Y:88 P:A5 SP:FB PPU:124, 40 CYC:4595\n$F878:38        SEC                                A:FF X:33 Y:88 P:A5 SP:F9 PPU:142, 40 CYC:4601\n$F879:B8        CLV                                A:FF X:33 Y:88 P:A5 SP:F9 PPU:148, 40 CYC:4603\n$F87A:A9 7F     LDA #$7F                           A:FF X:33 Y:88 P:A5 SP:F9 PPU:154, 40 CYC:4605\n$F87C:60        RTS                                A:7F X:33 Y:88 P:25 SP:F9 PPU:160, 40 CYC:4607\n$D2E1:65 78     ADC $78 = #$80                       A:7F X:33 Y:88 P:25 SP:FB PPU:178, 40 CYC:4613\n$D2E3:20 7D F8  JSR $F87D                          A:00 X:33 Y:88 P:nvUbdIZC SP:FB PPU:187, 40 CYC:4616\n$F87D:D0 07     BNE $F886                          A:00 X:33 Y:88 P:nvUbdIZC SP:F9 PPU:205, 40 CYC:4622\n$F87F:30 05     BMI $F886                          A:00 X:33 Y:88 P:nvUbdIZC SP:F9 PPU:211, 40 CYC:4624\n$F881:70 03     BVS $F886                          A:00 X:33 Y:88 P:nvUbdIZC SP:F9 PPU:217, 40 CYC:4626\n$F883:90 01     BCC $F886                          A:00 X:33 Y:88 P:nvUbdIZC SP:F9 PPU:223, 40 CYC:4628\n$F885:60        RTS                                A:00 X:33 Y:88 P:nvUbdIZC SP:F9 PPU:229, 40 CYC:4630\n$D2E6:C8        INY                                A:00 X:33 Y:88 P:nvUbdIZC SP:FB PPU:247, 40 CYC:4636\n$D2E7:A9 40     LDA #$40                           A:00 X:33 Y:89 P:A5 SP:FB PPU:253, 40 CYC:4638\n$D2E9:85 78     STA $78 = #$80                       A:40 X:33 Y:89 P:25 SP:FB PPU:259, 40 CYC:4640\n$D2EB:20 89 F8  JSR $F889                          A:40 X:33 Y:89 P:25 SP:FB PPU:268, 40 CYC:4643\n$F889:24 01     BIT $01 = #$FF                       A:40 X:33 Y:89 P:25 SP:F9 PPU:286, 40 CYC:4649\n$F88B:A9 40     LDA #$40                           A:40 X:33 Y:89 P:E5 SP:F9 PPU:295, 40 CYC:4652\n$F88D:60        RTS                                A:40 X:33 Y:89 P:65 SP:F9 PPU:301, 40 CYC:4654\n$D2EE:C5 78     CMP $78 = #$40                       A:40 X:33 Y:89 P:65 SP:FB PPU:319, 40 CYC:4660\n$D2F0:20 8E F8  JSR $F88E                          A:40 X:33 Y:89 P:67 SP:FB PPU:328, 40 CYC:4663\n$F88E:30 07     BMI $F897                          A:40 X:33 Y:89 P:67 SP:F9 PPU:  5, 41 CYC:4669\n$F890:90 05     BCC $F897                          A:40 X:33 Y:89 P:67 SP:F9 PPU: 11, 41 CYC:4671\n$F892:D0 03     BNE $F897                          A:40 X:33 Y:89 P:67 SP:F9 PPU: 17, 41 CYC:4673\n$F894:50 01     BVC $F897                          A:40 X:33 Y:89 P:67 SP:F9 PPU: 23, 41 CYC:4675\n$F896:60        RTS                                A:40 X:33 Y:89 P:67 SP:F9 PPU: 29, 41 CYC:4677\n$D2F3:C8        INY                                A:40 X:33 Y:89 P:67 SP:FB PPU: 47, 41 CYC:4683\n$D2F4:48        PHA                                A:40 X:33 Y:8A P:E5 SP:FB PPU: 53, 41 CYC:4685\n$D2F5:A9 3F     LDA #$3F                           A:40 X:33 Y:8A P:E5 SP:FA PPU: 62, 41 CYC:4688\n$D2F7:85 78     STA $78 = #$40                       A:3F X:33 Y:8A P:65 SP:FA PPU: 68, 41 CYC:4690\n$D2F9:68        PLA                                A:3F X:33 Y:8A P:65 SP:FA PPU: 77, 41 CYC:4693\n$D2FA:20 9A F8  JSR $F89A                          A:40 X:33 Y:8A P:65 SP:FB PPU: 89, 41 CYC:4697\n$F89A:B8        CLV                                A:40 X:33 Y:8A P:65 SP:F9 PPU:107, 41 CYC:4703\n$F89B:60        RTS                                A:40 X:33 Y:8A P:25 SP:F9 PPU:113, 41 CYC:4705\n$D2FD:C5 78     CMP $78 = #$3F                       A:40 X:33 Y:8A P:25 SP:FB PPU:131, 41 CYC:4711\n$D2FF:20 9C F8  JSR $F89C                          A:40 X:33 Y:8A P:25 SP:FB PPU:140, 41 CYC:4714\n$F89C:F0 07     BEQ $F8A5                          A:40 X:33 Y:8A P:25 SP:F9 PPU:158, 41 CYC:4720\n$F89E:30 05     BMI $F8A5                          A:40 X:33 Y:8A P:25 SP:F9 PPU:164, 41 CYC:4722\n$F8A0:90 03     BCC $F8A5                          A:40 X:33 Y:8A P:25 SP:F9 PPU:170, 41 CYC:4724\n$F8A2:70 01     BVS $F8A5                          A:40 X:33 Y:8A P:25 SP:F9 PPU:176, 41 CYC:4726\n$F8A4:60        RTS                                A:40 X:33 Y:8A P:25 SP:F9 PPU:182, 41 CYC:4728\n$D302:C8        INY                                A:40 X:33 Y:8A P:25 SP:FB PPU:200, 41 CYC:4734\n$D303:48        PHA                                A:40 X:33 Y:8B P:A5 SP:FB PPU:206, 41 CYC:4736\n$D304:A9 41     LDA #$41                           A:40 X:33 Y:8B P:A5 SP:FA PPU:215, 41 CYC:4739\n$D306:85 78     STA $78 = #$3F                       A:41 X:33 Y:8B P:25 SP:FA PPU:221, 41 CYC:4741\n$D308:68        PLA                                A:41 X:33 Y:8B P:25 SP:FA PPU:230, 41 CYC:4744\n$D309:C5 78     CMP $78 = #$41                       A:40 X:33 Y:8B P:25 SP:FB PPU:242, 41 CYC:4748\n$D30B:20 A8 F8  JSR $F8A8                          A:40 X:33 Y:8B P:NvUbdIzc SP:FB PPU:251, 41 CYC:4751\n$F8A8:F0 05     BEQ $F8AF                          A:40 X:33 Y:8B P:NvUbdIzc SP:F9 PPU:269, 41 CYC:4757\n$F8AA:10 03     BPL $F8AF                          A:40 X:33 Y:8B P:NvUbdIzc SP:F9 PPU:275, 41 CYC:4759\n$F8AC:10 01     BPL $F8AF                          A:40 X:33 Y:8B P:NvUbdIzc SP:F9 PPU:281, 41 CYC:4761\n$F8AE:60        RTS                                A:40 X:33 Y:8B P:NvUbdIzc SP:F9 PPU:287, 41 CYC:4763\n$D30E:C8        INY                                A:40 X:33 Y:8B P:NvUbdIzc SP:FB PPU:305, 41 CYC:4769\n$D30F:48        PHA                                A:40 X:33 Y:8C P:NvUbdIzc SP:FB PPU:311, 41 CYC:4771\n$D310:A9 00     LDA #$00                           A:40 X:33 Y:8C P:NvUbdIzc SP:FA PPU:320, 41 CYC:4774\n$D312:85 78     STA $78 = #$41                       A:00 X:33 Y:8C P:nvUbdIZc SP:FA PPU:326, 41 CYC:4776\n$D314:68        PLA                                A:00 X:33 Y:8C P:nvUbdIZc SP:FA PPU:335, 41 CYC:4779\n$D315:20 B2 F8  JSR $F8B2                          A:40 X:33 Y:8C P:nvUbdIzc SP:FB PPU:  6, 42 CYC:4783\n$F8B2:A9 80     LDA #$80                           A:40 X:33 Y:8C P:nvUbdIzc SP:F9 PPU: 24, 42 CYC:4789\n$F8B4:60        RTS                                A:80 X:33 Y:8C P:NvUbdIzc SP:F9 PPU: 30, 42 CYC:4791\n$D318:C5 78     CMP $78 = #$00                       A:80 X:33 Y:8C P:NvUbdIzc SP:FB PPU: 48, 42 CYC:4797\n$D31A:20 B5 F8  JSR $F8B5                          A:80 X:33 Y:8C P:A5 SP:FB PPU: 57, 42 CYC:4800\n$F8B5:F0 05     BEQ $F8BC                          A:80 X:33 Y:8C P:A5 SP:F9 PPU: 75, 42 CYC:4806\n$F8B7:10 03     BPL $F8BC                          A:80 X:33 Y:8C P:A5 SP:F9 PPU: 81, 42 CYC:4808\n$F8B9:90 01     BCC $F8BC                          A:80 X:33 Y:8C P:A5 SP:F9 PPU: 87, 42 CYC:4810\n$F8BB:60        RTS                                A:80 X:33 Y:8C P:A5 SP:F9 PPU: 93, 42 CYC:4812\n$D31D:C8        INY                                A:80 X:33 Y:8C P:A5 SP:FB PPU:111, 42 CYC:4818\n$D31E:48        PHA                                A:80 X:33 Y:8D P:A5 SP:FB PPU:117, 42 CYC:4820\n$D31F:A9 80     LDA #$80                           A:80 X:33 Y:8D P:A5 SP:FA PPU:126, 42 CYC:4823\n$D321:85 78     STA $78 = #$00                       A:80 X:33 Y:8D P:A5 SP:FA PPU:132, 42 CYC:4825\n$D323:68        PLA                                A:80 X:33 Y:8D P:A5 SP:FA PPU:141, 42 CYC:4828\n$D324:C5 78     CMP $78 = #$80                       A:80 X:33 Y:8D P:A5 SP:FB PPU:153, 42 CYC:4832\n$D326:20 BF F8  JSR $F8BF                          A:80 X:33 Y:8D P:nvUbdIZC SP:FB PPU:162, 42 CYC:4835\n$F8BF:D0 05     BNE $F8C6                          A:80 X:33 Y:8D P:nvUbdIZC SP:F9 PPU:180, 42 CYC:4841\n$F8C1:30 03     BMI $F8C6                          A:80 X:33 Y:8D P:nvUbdIZC SP:F9 PPU:186, 42 CYC:4843\n$F8C3:90 01     BCC $F8C6                          A:80 X:33 Y:8D P:nvUbdIZC SP:F9 PPU:192, 42 CYC:4845\n$F8C5:60        RTS                                A:80 X:33 Y:8D P:nvUbdIZC SP:F9 PPU:198, 42 CYC:4847\n$D329:C8        INY                                A:80 X:33 Y:8D P:nvUbdIZC SP:FB PPU:216, 42 CYC:4853\n$D32A:48        PHA                                A:80 X:33 Y:8E P:A5 SP:FB PPU:222, 42 CYC:4855\n$D32B:A9 81     LDA #$81                           A:80 X:33 Y:8E P:A5 SP:FA PPU:231, 42 CYC:4858\n$D32D:85 78     STA $78 = #$80                       A:81 X:33 Y:8E P:A5 SP:FA PPU:237, 42 CYC:4860\n$D32F:68        PLA                                A:81 X:33 Y:8E P:A5 SP:FA PPU:246, 42 CYC:4863\n$D330:C5 78     CMP $78 = #$81                       A:80 X:33 Y:8E P:A5 SP:FB PPU:258, 42 CYC:4867\n$D332:20 C9 F8  JSR $F8C9                          A:80 X:33 Y:8E P:NvUbdIzc SP:FB PPU:267, 42 CYC:4870\n$F8C9:B0 05     BCS $F8D0                          A:80 X:33 Y:8E P:NvUbdIzc SP:F9 PPU:285, 42 CYC:4876\n$F8CB:F0 03     BEQ $F8D0                          A:80 X:33 Y:8E P:NvUbdIzc SP:F9 PPU:291, 42 CYC:4878\n$F8CD:10 01     BPL $F8D0                          A:80 X:33 Y:8E P:NvUbdIzc SP:F9 PPU:297, 42 CYC:4880\n$F8CF:60        RTS                                A:80 X:33 Y:8E P:NvUbdIzc SP:F9 PPU:303, 42 CYC:4882\n$D335:C8        INY                                A:80 X:33 Y:8E P:NvUbdIzc SP:FB PPU:321, 42 CYC:4888\n$D336:48        PHA                                A:80 X:33 Y:8F P:NvUbdIzc SP:FB PPU:327, 42 CYC:4890\n$D337:A9 7F     LDA #$7F                           A:80 X:33 Y:8F P:NvUbdIzc SP:FA PPU:336, 42 CYC:4893\n$D339:85 78     STA $78 = #$81                       A:7F X:33 Y:8F P:nvUbdIzc SP:FA PPU:  1, 43 CYC:4895\n$D33B:68        PLA                                A:7F X:33 Y:8F P:nvUbdIzc SP:FA PPU: 10, 43 CYC:4898\n$D33C:C5 78     CMP $78 = #$7F                       A:80 X:33 Y:8F P:NvUbdIzc SP:FB PPU: 22, 43 CYC:4902\n$D33E:20 D3 F8  JSR $F8D3                          A:80 X:33 Y:8F P:25 SP:FB PPU: 31, 43 CYC:4905\n$F8D3:90 05     BCC $F8DA                          A:80 X:33 Y:8F P:25 SP:F9 PPU: 49, 43 CYC:4911\n$F8D5:F0 03     BEQ $F8DA                          A:80 X:33 Y:8F P:25 SP:F9 PPU: 55, 43 CYC:4913\n$F8D7:30 01     BMI $F8DA                          A:80 X:33 Y:8F P:25 SP:F9 PPU: 61, 43 CYC:4915\n$F8D9:60        RTS                                A:80 X:33 Y:8F P:25 SP:F9 PPU: 67, 43 CYC:4917\n$D341:C8        INY                                A:80 X:33 Y:8F P:25 SP:FB PPU: 85, 43 CYC:4923\n$D342:A9 40     LDA #$40                           A:80 X:33 Y:90 P:A5 SP:FB PPU: 91, 43 CYC:4925\n$D344:85 78     STA $78 = #$7F                       A:40 X:33 Y:90 P:25 SP:FB PPU: 97, 43 CYC:4927\n$D346:20 31 F9  JSR $F931                          A:40 X:33 Y:90 P:25 SP:FB PPU:106, 43 CYC:4930\n$F931:24 01     BIT $01 = #$FF                       A:40 X:33 Y:90 P:25 SP:F9 PPU:124, 43 CYC:4936\n$F933:A9 40     LDA #$40                           A:40 X:33 Y:90 P:E5 SP:F9 PPU:133, 43 CYC:4939\n$F935:38        SEC                                A:40 X:33 Y:90 P:65 SP:F9 PPU:139, 43 CYC:4941\n$F936:60        RTS                                A:40 X:33 Y:90 P:65 SP:F9 PPU:145, 43 CYC:4943\n$D349:E5 78     SBC $78 = #$40                       A:40 X:33 Y:90 P:65 SP:FB PPU:163, 43 CYC:4949\n$D34B:20 37 F9  JSR $F937                          A:00 X:33 Y:90 P:nvUbdIZC SP:FB PPU:172, 43 CYC:4952\n$F937:30 0B     BMI $F944                          A:00 X:33 Y:90 P:nvUbdIZC SP:F9 PPU:190, 43 CYC:4958\n$F939:90 09     BCC $F944                          A:00 X:33 Y:90 P:nvUbdIZC SP:F9 PPU:196, 43 CYC:4960\n$F93B:D0 07     BNE $F944                          A:00 X:33 Y:90 P:nvUbdIZC SP:F9 PPU:202, 43 CYC:4962\n$F93D:70 05     BVS $F944                          A:00 X:33 Y:90 P:nvUbdIZC SP:F9 PPU:208, 43 CYC:4964\n$F93F:C9 00     CMP #$00                           A:00 X:33 Y:90 P:nvUbdIZC SP:F9 PPU:214, 43 CYC:4966\n$F941:D0 01     BNE $F944                          A:00 X:33 Y:90 P:nvUbdIZC SP:F9 PPU:220, 43 CYC:4968\n$F943:60        RTS                                A:00 X:33 Y:90 P:nvUbdIZC SP:F9 PPU:226, 43 CYC:4970\n$D34E:C8        INY                                A:00 X:33 Y:90 P:nvUbdIZC SP:FB PPU:244, 43 CYC:4976\n$D34F:A9 3F     LDA #$3F                           A:00 X:33 Y:91 P:A5 SP:FB PPU:250, 43 CYC:4978\n$D351:85 78     STA $78 = #$40                       A:3F X:33 Y:91 P:25 SP:FB PPU:256, 43 CYC:4980\n$D353:20 47 F9  JSR $F947                          A:3F X:33 Y:91 P:25 SP:FB PPU:265, 43 CYC:4983\n$F947:B8        CLV                                A:3F X:33 Y:91 P:25 SP:F9 PPU:283, 43 CYC:4989\n$F948:38        SEC                                A:3F X:33 Y:91 P:25 SP:F9 PPU:289, 43 CYC:4991\n$F949:A9 40     LDA #$40                           A:3F X:33 Y:91 P:25 SP:F9 PPU:295, 43 CYC:4993\n$F94B:60        RTS                                A:40 X:33 Y:91 P:25 SP:F9 PPU:301, 43 CYC:4995\n$D356:E5 78     SBC $78 = #$3F                       A:40 X:33 Y:91 P:25 SP:FB PPU:319, 43 CYC:5001\n$D358:20 4C F9  JSR $F94C                          A:01 X:33 Y:91 P:25 SP:FB PPU:328, 43 CYC:5004\n$F94C:F0 0B     BEQ $F959                          A:01 X:33 Y:91 P:25 SP:F9 PPU:  5, 44 CYC:5010\n$F94E:30 09     BMI $F959                          A:01 X:33 Y:91 P:25 SP:F9 PPU: 11, 44 CYC:5012\n$F950:90 07     BCC $F959                          A:01 X:33 Y:91 P:25 SP:F9 PPU: 17, 44 CYC:5014\n$F952:70 05     BVS $F959                          A:01 X:33 Y:91 P:25 SP:F9 PPU: 23, 44 CYC:5016\n$F954:C9 01     CMP #$01                           A:01 X:33 Y:91 P:25 SP:F9 PPU: 29, 44 CYC:5018\n$F956:D0 01     BNE $F959                          A:01 X:33 Y:91 P:nvUbdIZC SP:F9 PPU: 35, 44 CYC:5020\n$F958:60        RTS                                A:01 X:33 Y:91 P:nvUbdIZC SP:F9 PPU: 41, 44 CYC:5022\n$D35B:C8        INY                                A:01 X:33 Y:91 P:nvUbdIZC SP:FB PPU: 59, 44 CYC:5028\n$D35C:A9 41     LDA #$41                           A:01 X:33 Y:92 P:A5 SP:FB PPU: 65, 44 CYC:5030\n$D35E:85 78     STA $78 = #$3F                       A:41 X:33 Y:92 P:25 SP:FB PPU: 71, 44 CYC:5032\n$D360:20 5C F9  JSR $F95C                          A:41 X:33 Y:92 P:25 SP:FB PPU: 80, 44 CYC:5035\n$F95C:A9 40     LDA #$40                           A:41 X:33 Y:92 P:25 SP:F9 PPU: 98, 44 CYC:5041\n$F95E:38        SEC                                A:40 X:33 Y:92 P:25 SP:F9 PPU:104, 44 CYC:5043\n$F95F:24 01     BIT $01 = #$FF                       A:40 X:33 Y:92 P:25 SP:F9 PPU:110, 44 CYC:5045\n$F961:60        RTS                                A:40 X:33 Y:92 P:E5 SP:F9 PPU:119, 44 CYC:5048\n$D363:E5 78     SBC $78 = #$41                       A:40 X:33 Y:92 P:E5 SP:FB PPU:137, 44 CYC:5054\n$D365:20 62 F9  JSR $F962                          A:FF X:33 Y:92 P:NvUbdIzc SP:FB PPU:146, 44 CYC:5057\n$F962:B0 0B     BCS $F96F                          A:FF X:33 Y:92 P:NvUbdIzc SP:F9 PPU:164, 44 CYC:5063\n$F964:F0 09     BEQ $F96F                          A:FF X:33 Y:92 P:NvUbdIzc SP:F9 PPU:170, 44 CYC:5065\n$F966:10 07     BPL $F96F                          A:FF X:33 Y:92 P:NvUbdIzc SP:F9 PPU:176, 44 CYC:5067\n$F968:70 05     BVS $F96F                          A:FF X:33 Y:92 P:NvUbdIzc SP:F9 PPU:182, 44 CYC:5069\n$F96A:C9 FF     CMP #$FF                           A:FF X:33 Y:92 P:NvUbdIzc SP:F9 PPU:188, 44 CYC:5071\n$F96C:D0 01     BNE $F96F                          A:FF X:33 Y:92 P:nvUbdIZC SP:F9 PPU:194, 44 CYC:5073\n$F96E:60        RTS                                A:FF X:33 Y:92 P:nvUbdIZC SP:F9 PPU:200, 44 CYC:5075\n$D368:C8        INY                                A:FF X:33 Y:92 P:nvUbdIZC SP:FB PPU:218, 44 CYC:5081\n$D369:A9 00     LDA #$00                           A:FF X:33 Y:93 P:A5 SP:FB PPU:224, 44 CYC:5083\n$D36B:85 78     STA $78 = #$41                       A:00 X:33 Y:93 P:nvUbdIZC SP:FB PPU:230, 44 CYC:5085\n$D36D:20 72 F9  JSR $F972                          A:00 X:33 Y:93 P:nvUbdIZC SP:FB PPU:239, 44 CYC:5088\n$F972:18        CLC                                A:00 X:33 Y:93 P:nvUbdIZC SP:F9 PPU:257, 44 CYC:5094\n$F973:A9 80     LDA #$80                           A:00 X:33 Y:93 P:nvUbdIZc SP:F9 PPU:263, 44 CYC:5096\n$F975:60        RTS                                A:80 X:33 Y:93 P:NvUbdIzc SP:F9 PPU:269, 44 CYC:5098\n$D370:E5 78     SBC $78 = #$00                       A:80 X:33 Y:93 P:NvUbdIzc SP:FB PPU:287, 44 CYC:5104\n$D372:20 76 F9  JSR $F976                          A:7F X:33 Y:93 P:65 SP:FB PPU:296, 44 CYC:5107\n$F976:90 05     BCC $F97D                          A:7F X:33 Y:93 P:65 SP:F9 PPU:314, 44 CYC:5113\n$F978:C9 7F     CMP #$7F                           A:7F X:33 Y:93 P:65 SP:F9 PPU:320, 44 CYC:5115\n$F97A:D0 01     BNE $F97D                          A:7F X:33 Y:93 P:67 SP:F9 PPU:326, 44 CYC:5117\n$F97C:60        RTS                                A:7F X:33 Y:93 P:67 SP:F9 PPU:332, 44 CYC:5119\n$D375:C8        INY                                A:7F X:33 Y:93 P:67 SP:FB PPU:  9, 45 CYC:5125\n$D376:A9 7F     LDA #$7F                           A:7F X:33 Y:94 P:E5 SP:FB PPU: 15, 45 CYC:5127\n$D378:85 78     STA $78 = #$00                       A:7F X:33 Y:94 P:65 SP:FB PPU: 21, 45 CYC:5129\n$D37A:20 80 F9  JSR $F980                          A:7F X:33 Y:94 P:65 SP:FB PPU: 30, 45 CYC:5132\n$F980:38        SEC                                A:7F X:33 Y:94 P:65 SP:F9 PPU: 48, 45 CYC:5138\n$F981:A9 81     LDA #$81                           A:7F X:33 Y:94 P:65 SP:F9 PPU: 54, 45 CYC:5140\n$F983:60        RTS                                A:81 X:33 Y:94 P:E5 SP:F9 PPU: 60, 45 CYC:5142\n$D37D:E5 78     SBC $78 = #$7F                       A:81 X:33 Y:94 P:E5 SP:FB PPU: 78, 45 CYC:5148\n$D37F:20 84 F9  JSR $F984                          A:02 X:33 Y:94 P:65 SP:FB PPU: 87, 45 CYC:5151\n$F984:50 07     BVC $F98D                          A:02 X:33 Y:94 P:65 SP:F9 PPU:105, 45 CYC:5157\n$F986:90 05     BCC $F98D                          A:02 X:33 Y:94 P:65 SP:F9 PPU:111, 45 CYC:5159\n$F988:C9 02     CMP #$02                           A:02 X:33 Y:94 P:65 SP:F9 PPU:117, 45 CYC:5161\n$F98A:D0 01     BNE $F98D                          A:02 X:33 Y:94 P:67 SP:F9 PPU:123, 45 CYC:5163\n$F98C:60        RTS                                A:02 X:33 Y:94 P:67 SP:F9 PPU:129, 45 CYC:5165\n$D382:C8        INY                                A:02 X:33 Y:94 P:67 SP:FB PPU:147, 45 CYC:5171\n$D383:A9 40     LDA #$40                           A:02 X:33 Y:95 P:E5 SP:FB PPU:153, 45 CYC:5173\n$D385:85 78     STA $78 = #$7F                       A:40 X:33 Y:95 P:65 SP:FB PPU:159, 45 CYC:5175\n$D387:20 89 F8  JSR $F889                          A:40 X:33 Y:95 P:65 SP:FB PPU:168, 45 CYC:5178\n$F889:24 01     BIT $01 = #$FF                       A:40 X:33 Y:95 P:65 SP:F9 PPU:186, 45 CYC:5184\n$F88B:A9 40     LDA #$40                           A:40 X:33 Y:95 P:E5 SP:F9 PPU:195, 45 CYC:5187\n$F88D:60        RTS                                A:40 X:33 Y:95 P:65 SP:F9 PPU:201, 45 CYC:5189\n$D38A:AA        TAX                                A:40 X:33 Y:95 P:65 SP:FB PPU:219, 45 CYC:5195\n$D38B:E4 78     CPX $78 = #$40                       A:40 X:40 Y:95 P:65 SP:FB PPU:225, 45 CYC:5197\n$D38D:20 8E F8  JSR $F88E                          A:40 X:40 Y:95 P:67 SP:FB PPU:234, 45 CYC:5200\n$F88E:30 07     BMI $F897                          A:40 X:40 Y:95 P:67 SP:F9 PPU:252, 45 CYC:5206\n$F890:90 05     BCC $F897                          A:40 X:40 Y:95 P:67 SP:F9 PPU:258, 45 CYC:5208\n$F892:D0 03     BNE $F897                          A:40 X:40 Y:95 P:67 SP:F9 PPU:264, 45 CYC:5210\n$F894:50 01     BVC $F897                          A:40 X:40 Y:95 P:67 SP:F9 PPU:270, 45 CYC:5212\n$F896:60        RTS                                A:40 X:40 Y:95 P:67 SP:F9 PPU:276, 45 CYC:5214\n$D390:C8        INY                                A:40 X:40 Y:95 P:67 SP:FB PPU:294, 45 CYC:5220\n$D391:A9 3F     LDA #$3F                           A:40 X:40 Y:96 P:E5 SP:FB PPU:300, 45 CYC:5222\n$D393:85 78     STA $78 = #$40                       A:3F X:40 Y:96 P:65 SP:FB PPU:306, 45 CYC:5224\n$D395:20 9A F8  JSR $F89A                          A:3F X:40 Y:96 P:65 SP:FB PPU:315, 45 CYC:5227\n$F89A:B8        CLV                                A:3F X:40 Y:96 P:65 SP:F9 PPU:333, 45 CYC:5233\n$F89B:60        RTS                                A:3F X:40 Y:96 P:25 SP:F9 PPU:339, 45 CYC:5235\n$D398:E4 78     CPX $78 = #$3F                       A:3F X:40 Y:96 P:25 SP:FB PPU: 16, 46 CYC:5241\n$D39A:20 9C F8  JSR $F89C                          A:3F X:40 Y:96 P:25 SP:FB PPU: 25, 46 CYC:5244\n$F89C:F0 07     BEQ $F8A5                          A:3F X:40 Y:96 P:25 SP:F9 PPU: 43, 46 CYC:5250\n$F89E:30 05     BMI $F8A5                          A:3F X:40 Y:96 P:25 SP:F9 PPU: 49, 46 CYC:5252\n$F8A0:90 03     BCC $F8A5                          A:3F X:40 Y:96 P:25 SP:F9 PPU: 55, 46 CYC:5254\n$F8A2:70 01     BVS $F8A5                          A:3F X:40 Y:96 P:25 SP:F9 PPU: 61, 46 CYC:5256\n$F8A4:60        RTS                                A:3F X:40 Y:96 P:25 SP:F9 PPU: 67, 46 CYC:5258\n$D39D:C8        INY                                A:3F X:40 Y:96 P:25 SP:FB PPU: 85, 46 CYC:5264\n$D39E:A9 41     LDA #$41                           A:3F X:40 Y:97 P:A5 SP:FB PPU: 91, 46 CYC:5266\n$D3A0:85 78     STA $78 = #$3F                       A:41 X:40 Y:97 P:25 SP:FB PPU: 97, 46 CYC:5268\n$D3A2:E4 78     CPX $78 = #$41                       A:41 X:40 Y:97 P:25 SP:FB PPU:106, 46 CYC:5271\n$D3A4:20 A8 F8  JSR $F8A8                          A:41 X:40 Y:97 P:NvUbdIzc SP:FB PPU:115, 46 CYC:5274\n$F8A8:F0 05     BEQ $F8AF                          A:41 X:40 Y:97 P:NvUbdIzc SP:F9 PPU:133, 46 CYC:5280\n$F8AA:10 03     BPL $F8AF                          A:41 X:40 Y:97 P:NvUbdIzc SP:F9 PPU:139, 46 CYC:5282\n$F8AC:10 01     BPL $F8AF                          A:41 X:40 Y:97 P:NvUbdIzc SP:F9 PPU:145, 46 CYC:5284\n$F8AE:60        RTS                                A:41 X:40 Y:97 P:NvUbdIzc SP:F9 PPU:151, 46 CYC:5286\n$D3A7:C8        INY                                A:41 X:40 Y:97 P:NvUbdIzc SP:FB PPU:169, 46 CYC:5292\n$D3A8:A9 00     LDA #$00                           A:41 X:40 Y:98 P:NvUbdIzc SP:FB PPU:175, 46 CYC:5294\n$D3AA:85 78     STA $78 = #$41                       A:00 X:40 Y:98 P:nvUbdIZc SP:FB PPU:181, 46 CYC:5296\n$D3AC:20 B2 F8  JSR $F8B2                          A:00 X:40 Y:98 P:nvUbdIZc SP:FB PPU:190, 46 CYC:5299\n$F8B2:A9 80     LDA #$80                           A:00 X:40 Y:98 P:nvUbdIZc SP:F9 PPU:208, 46 CYC:5305\n$F8B4:60        RTS                                A:80 X:40 Y:98 P:NvUbdIzc SP:F9 PPU:214, 46 CYC:5307\n$D3AF:AA        TAX                                A:80 X:40 Y:98 P:NvUbdIzc SP:FB PPU:232, 46 CYC:5313\n$D3B0:E4 78     CPX $78 = #$00                       A:80 X:80 Y:98 P:NvUbdIzc SP:FB PPU:238, 46 CYC:5315\n$D3B2:20 B5 F8  JSR $F8B5                          A:80 X:80 Y:98 P:A5 SP:FB PPU:247, 46 CYC:5318\n$F8B5:F0 05     BEQ $F8BC                          A:80 X:80 Y:98 P:A5 SP:F9 PPU:265, 46 CYC:5324\n$F8B7:10 03     BPL $F8BC                          A:80 X:80 Y:98 P:A5 SP:F9 PPU:271, 46 CYC:5326\n$F8B9:90 01     BCC $F8BC                          A:80 X:80 Y:98 P:A5 SP:F9 PPU:277, 46 CYC:5328\n$F8BB:60        RTS                                A:80 X:80 Y:98 P:A5 SP:F9 PPU:283, 46 CYC:5330\n$D3B5:C8        INY                                A:80 X:80 Y:98 P:A5 SP:FB PPU:301, 46 CYC:5336\n$D3B6:A9 80     LDA #$80                           A:80 X:80 Y:99 P:A5 SP:FB PPU:307, 46 CYC:5338\n$D3B8:85 78     STA $78 = #$00                       A:80 X:80 Y:99 P:A5 SP:FB PPU:313, 46 CYC:5340\n$D3BA:E4 78     CPX $78 = #$80                       A:80 X:80 Y:99 P:A5 SP:FB PPU:322, 46 CYC:5343\n$D3BC:20 BF F8  JSR $F8BF                          A:80 X:80 Y:99 P:nvUbdIZC SP:FB PPU:331, 46 CYC:5346\n$F8BF:D0 05     BNE $F8C6                          A:80 X:80 Y:99 P:nvUbdIZC SP:F9 PPU:  8, 47 CYC:5352\n$F8C1:30 03     BMI $F8C6                          A:80 X:80 Y:99 P:nvUbdIZC SP:F9 PPU: 14, 47 CYC:5354\n$F8C3:90 01     BCC $F8C6                          A:80 X:80 Y:99 P:nvUbdIZC SP:F9 PPU: 20, 47 CYC:5356\n$F8C5:60        RTS                                A:80 X:80 Y:99 P:nvUbdIZC SP:F9 PPU: 26, 47 CYC:5358\n$D3BF:C8        INY                                A:80 X:80 Y:99 P:nvUbdIZC SP:FB PPU: 44, 47 CYC:5364\n$D3C0:A9 81     LDA #$81                           A:80 X:80 Y:9A P:A5 SP:FB PPU: 50, 47 CYC:5366\n$D3C2:85 78     STA $78 = #$80                       A:81 X:80 Y:9A P:A5 SP:FB PPU: 56, 47 CYC:5368\n$D3C4:E4 78     CPX $78 = #$81                       A:81 X:80 Y:9A P:A5 SP:FB PPU: 65, 47 CYC:5371\n$D3C6:20 C9 F8  JSR $F8C9                          A:81 X:80 Y:9A P:NvUbdIzc SP:FB PPU: 74, 47 CYC:5374\n$F8C9:B0 05     BCS $F8D0                          A:81 X:80 Y:9A P:NvUbdIzc SP:F9 PPU: 92, 47 CYC:5380\n$F8CB:F0 03     BEQ $F8D0                          A:81 X:80 Y:9A P:NvUbdIzc SP:F9 PPU: 98, 47 CYC:5382\n$F8CD:10 01     BPL $F8D0                          A:81 X:80 Y:9A P:NvUbdIzc SP:F9 PPU:104, 47 CYC:5384\n$F8CF:60        RTS                                A:81 X:80 Y:9A P:NvUbdIzc SP:F9 PPU:110, 47 CYC:5386\n$D3C9:C8        INY                                A:81 X:80 Y:9A P:NvUbdIzc SP:FB PPU:128, 47 CYC:5392\n$D3CA:A9 7F     LDA #$7F                           A:81 X:80 Y:9B P:NvUbdIzc SP:FB PPU:134, 47 CYC:5394\n$D3CC:85 78     STA $78 = #$81                       A:7F X:80 Y:9B P:nvUbdIzc SP:FB PPU:140, 47 CYC:5396\n$D3CE:E4 78     CPX $78 = #$7F                       A:7F X:80 Y:9B P:nvUbdIzc SP:FB PPU:149, 47 CYC:5399\n$D3D0:20 D3 F8  JSR $F8D3                          A:7F X:80 Y:9B P:25 SP:FB PPU:158, 47 CYC:5402\n$F8D3:90 05     BCC $F8DA                          A:7F X:80 Y:9B P:25 SP:F9 PPU:176, 47 CYC:5408\n$F8D5:F0 03     BEQ $F8DA                          A:7F X:80 Y:9B P:25 SP:F9 PPU:182, 47 CYC:5410\n$F8D7:30 01     BMI $F8DA                          A:7F X:80 Y:9B P:25 SP:F9 PPU:188, 47 CYC:5412\n$F8D9:60        RTS                                A:7F X:80 Y:9B P:25 SP:F9 PPU:194, 47 CYC:5414\n$D3D3:C8        INY                                A:7F X:80 Y:9B P:25 SP:FB PPU:212, 47 CYC:5420\n$D3D4:98        TYA                                A:7F X:80 Y:9C P:A5 SP:FB PPU:218, 47 CYC:5422\n$D3D5:AA        TAX                                A:9C X:80 Y:9C P:A5 SP:FB PPU:224, 47 CYC:5424\n$D3D6:A9 40     LDA #$40                           A:9C X:9C Y:9C P:A5 SP:FB PPU:230, 47 CYC:5426\n$D3D8:85 78     STA $78 = #$7F                       A:40 X:9C Y:9C P:25 SP:FB PPU:236, 47 CYC:5428\n$D3DA:20 DD F8  JSR $F8DD                          A:40 X:9C Y:9C P:25 SP:FB PPU:245, 47 CYC:5431\n$F8DD:24 01     BIT $01 = #$FF                       A:40 X:9C Y:9C P:25 SP:F9 PPU:263, 47 CYC:5437\n$F8DF:A0 40     LDY #$40                           A:40 X:9C Y:9C P:E5 SP:F9 PPU:272, 47 CYC:5440\n$F8E1:60        RTS                                A:40 X:9C Y:40 P:65 SP:F9 PPU:278, 47 CYC:5442\n$D3DD:C4 78     CPY $78 = #$40                       A:40 X:9C Y:40 P:65 SP:FB PPU:296, 47 CYC:5448\n$D3DF:20 E2 F8  JSR $F8E2                          A:40 X:9C Y:40 P:67 SP:FB PPU:305, 47 CYC:5451\n$F8E2:30 07     BMI $F8EB                          A:40 X:9C Y:40 P:67 SP:F9 PPU:323, 47 CYC:5457\n$F8E4:90 05     BCC $F8EB                          A:40 X:9C Y:40 P:67 SP:F9 PPU:329, 47 CYC:5459\n$F8E6:D0 03     BNE $F8EB                          A:40 X:9C Y:40 P:67 SP:F9 PPU:335, 47 CYC:5461\n$F8E8:50 01     BVC $F8EB                          A:40 X:9C Y:40 P:67 SP:F9 PPU:  0, 48 CYC:5463\n$F8EA:60        RTS                                A:40 X:9C Y:40 P:67 SP:F9 PPU:  6, 48 CYC:5465\n$D3E2:E8        INX                                A:40 X:9C Y:40 P:67 SP:FB PPU: 24, 48 CYC:5471\n$D3E3:A9 3F     LDA #$3F                           A:40 X:9D Y:40 P:E5 SP:FB PPU: 30, 48 CYC:5473\n$D3E5:85 78     STA $78 = #$40                       A:3F X:9D Y:40 P:65 SP:FB PPU: 36, 48 CYC:5475\n$D3E7:20 EE F8  JSR $F8EE                          A:3F X:9D Y:40 P:65 SP:FB PPU: 45, 48 CYC:5478\n$F8EE:B8        CLV                                A:3F X:9D Y:40 P:65 SP:F9 PPU: 63, 48 CYC:5484\n$F8EF:60        RTS                                A:3F X:9D Y:40 P:25 SP:F9 PPU: 69, 48 CYC:5486\n$D3EA:C4 78     CPY $78 = #$3F                       A:3F X:9D Y:40 P:25 SP:FB PPU: 87, 48 CYC:5492\n$D3EC:20 F0 F8  JSR $F8F0                          A:3F X:9D Y:40 P:25 SP:FB PPU: 96, 48 CYC:5495\n$F8F0:F0 07     BEQ $F8F9                          A:3F X:9D Y:40 P:25 SP:F9 PPU:114, 48 CYC:5501\n$F8F2:30 05     BMI $F8F9                          A:3F X:9D Y:40 P:25 SP:F9 PPU:120, 48 CYC:5503\n$F8F4:90 03     BCC $F8F9                          A:3F X:9D Y:40 P:25 SP:F9 PPU:126, 48 CYC:5505\n$F8F6:70 01     BVS $F8F9                          A:3F X:9D Y:40 P:25 SP:F9 PPU:132, 48 CYC:5507\n$F8F8:60        RTS                                A:3F X:9D Y:40 P:25 SP:F9 PPU:138, 48 CYC:5509\n$D3EF:E8        INX                                A:3F X:9D Y:40 P:25 SP:FB PPU:156, 48 CYC:5515\n$D3F0:A9 41     LDA #$41                           A:3F X:9E Y:40 P:A5 SP:FB PPU:162, 48 CYC:5517\n$D3F2:85 78     STA $78 = #$3F                       A:41 X:9E Y:40 P:25 SP:FB PPU:168, 48 CYC:5519\n$D3F4:C4 78     CPY $78 = #$41                       A:41 X:9E Y:40 P:25 SP:FB PPU:177, 48 CYC:5522\n$D3F6:20 FC F8  JSR $F8FC                          A:41 X:9E Y:40 P:NvUbdIzc SP:FB PPU:186, 48 CYC:5525\n$F8FC:F0 05     BEQ $F903                          A:41 X:9E Y:40 P:NvUbdIzc SP:F9 PPU:204, 48 CYC:5531\n$F8FE:10 03     BPL $F903                          A:41 X:9E Y:40 P:NvUbdIzc SP:F9 PPU:210, 48 CYC:5533\n$F900:10 01     BPL $F903                          A:41 X:9E Y:40 P:NvUbdIzc SP:F9 PPU:216, 48 CYC:5535\n$F902:60        RTS                                A:41 X:9E Y:40 P:NvUbdIzc SP:F9 PPU:222, 48 CYC:5537\n$D3F9:E8        INX                                A:41 X:9E Y:40 P:NvUbdIzc SP:FB PPU:240, 48 CYC:5543\n$D3FA:A9 00     LDA #$00                           A:41 X:9F Y:40 P:NvUbdIzc SP:FB PPU:246, 48 CYC:5545\n$D3FC:85 78     STA $78 = #$41                       A:00 X:9F Y:40 P:nvUbdIZc SP:FB PPU:252, 48 CYC:5547\n$D3FE:20 06 F9  JSR $F906                          A:00 X:9F Y:40 P:nvUbdIZc SP:FB PPU:261, 48 CYC:5550\n$F906:A0 80     LDY #$80                           A:00 X:9F Y:40 P:nvUbdIZc SP:F9 PPU:279, 48 CYC:5556\n$F908:60        RTS                                A:00 X:9F Y:80 P:NvUbdIzc SP:F9 PPU:285, 48 CYC:5558\n$D401:C4 78     CPY $78 = #$00                       A:00 X:9F Y:80 P:NvUbdIzc SP:FB PPU:303, 48 CYC:5564\n$D403:20 09 F9  JSR $F909                          A:00 X:9F Y:80 P:A5 SP:FB PPU:312, 48 CYC:5567\n$F909:F0 05     BEQ $F910                          A:00 X:9F Y:80 P:A5 SP:F9 PPU:330, 48 CYC:5573\n$F90B:10 03     BPL $F910                          A:00 X:9F Y:80 P:A5 SP:F9 PPU:336, 48 CYC:5575\n$F90D:90 01     BCC $F910                          A:00 X:9F Y:80 P:A5 SP:F9 PPU:  1, 49 CYC:5577\n$F90F:60        RTS                                A:00 X:9F Y:80 P:A5 SP:F9 PPU:  7, 49 CYC:5579\n$D406:E8        INX                                A:00 X:9F Y:80 P:A5 SP:FB PPU: 25, 49 CYC:5585\n$D407:A9 80     LDA #$80                           A:00 X:A0 Y:80 P:A5 SP:FB PPU: 31, 49 CYC:5587\n$D409:85 78     STA $78 = #$00                       A:80 X:A0 Y:80 P:A5 SP:FB PPU: 37, 49 CYC:5589\n$D40B:C4 78     CPY $78 = #$80                       A:80 X:A0 Y:80 P:A5 SP:FB PPU: 46, 49 CYC:5592\n$D40D:20 13 F9  JSR $F913                          A:80 X:A0 Y:80 P:nvUbdIZC SP:FB PPU: 55, 49 CYC:5595\n$F913:D0 05     BNE $F91A                          A:80 X:A0 Y:80 P:nvUbdIZC SP:F9 PPU: 73, 49 CYC:5601\n$F915:30 03     BMI $F91A                          A:80 X:A0 Y:80 P:nvUbdIZC SP:F9 PPU: 79, 49 CYC:5603\n$F917:90 01     BCC $F91A                          A:80 X:A0 Y:80 P:nvUbdIZC SP:F9 PPU: 85, 49 CYC:5605\n$F919:60        RTS                                A:80 X:A0 Y:80 P:nvUbdIZC SP:F9 PPU: 91, 49 CYC:5607\n$D410:E8        INX                                A:80 X:A0 Y:80 P:nvUbdIZC SP:FB PPU:109, 49 CYC:5613\n$D411:A9 81     LDA #$81                           A:80 X:A1 Y:80 P:A5 SP:FB PPU:115, 49 CYC:5615\n$D413:85 78     STA $78 = #$80                       A:81 X:A1 Y:80 P:A5 SP:FB PPU:121, 49 CYC:5617\n$D415:C4 78     CPY $78 = #$81                       A:81 X:A1 Y:80 P:A5 SP:FB PPU:130, 49 CYC:5620\n$D417:20 1D F9  JSR $F91D                          A:81 X:A1 Y:80 P:NvUbdIzc SP:FB PPU:139, 49 CYC:5623\n$F91D:B0 05     BCS $F924                          A:81 X:A1 Y:80 P:NvUbdIzc SP:F9 PPU:157, 49 CYC:5629\n$F91F:F0 03     BEQ $F924                          A:81 X:A1 Y:80 P:NvUbdIzc SP:F9 PPU:163, 49 CYC:5631\n$F921:10 01     BPL $F924                          A:81 X:A1 Y:80 P:NvUbdIzc SP:F9 PPU:169, 49 CYC:5633\n$F923:60        RTS                                A:81 X:A1 Y:80 P:NvUbdIzc SP:F9 PPU:175, 49 CYC:5635\n$D41A:E8        INX                                A:81 X:A1 Y:80 P:NvUbdIzc SP:FB PPU:193, 49 CYC:5641\n$D41B:A9 7F     LDA #$7F                           A:81 X:A2 Y:80 P:NvUbdIzc SP:FB PPU:199, 49 CYC:5643\n$D41D:85 78     STA $78 = #$81                       A:7F X:A2 Y:80 P:nvUbdIzc SP:FB PPU:205, 49 CYC:5645\n$D41F:C4 78     CPY $78 = #$7F                       A:7F X:A2 Y:80 P:nvUbdIzc SP:FB PPU:214, 49 CYC:5648\n$D421:20 27 F9  JSR $F927                          A:7F X:A2 Y:80 P:25 SP:FB PPU:223, 49 CYC:5651\n$F927:90 05     BCC $F92E                          A:7F X:A2 Y:80 P:25 SP:F9 PPU:241, 49 CYC:5657\n$F929:F0 03     BEQ $F92E                          A:7F X:A2 Y:80 P:25 SP:F9 PPU:247, 49 CYC:5659\n$F92B:30 01     BMI $F92E                          A:7F X:A2 Y:80 P:25 SP:F9 PPU:253, 49 CYC:5661\n$F92D:60        RTS                                A:7F X:A2 Y:80 P:25 SP:F9 PPU:259, 49 CYC:5663\n$D424:E8        INX                                A:7F X:A2 Y:80 P:25 SP:FB PPU:277, 49 CYC:5669\n$D425:8A        TXA                                A:7F X:A3 Y:80 P:A5 SP:FB PPU:283, 49 CYC:5671\n$D426:A8        TAY                                A:A3 X:A3 Y:80 P:A5 SP:FB PPU:289, 49 CYC:5673\n$D427:20 90 F9  JSR $F990                          A:A3 X:A3 Y:A3 P:A5 SP:FB PPU:295, 49 CYC:5675\n$F990:A2 55     LDX #$55                           A:A3 X:A3 Y:A3 P:A5 SP:F9 PPU:313, 49 CYC:5681\n$F992:A9 FF     LDA #$FF                           A:A3 X:55 Y:A3 P:25 SP:F9 PPU:319, 49 CYC:5683\n$F994:85 01     STA $01 = #$FF                       A:FF X:55 Y:A3 P:A5 SP:F9 PPU:325, 49 CYC:5685\n$F996:EA        NOP                                A:FF X:55 Y:A3 P:A5 SP:F9 PPU:334, 49 CYC:5688\n$F997:24 01     BIT $01 = #$FF                       A:FF X:55 Y:A3 P:A5 SP:F9 PPU:340, 49 CYC:5690\n$F999:38        SEC                                A:FF X:55 Y:A3 P:E5 SP:F9 PPU:  8, 50 CYC:5693\n$F99A:A9 01     LDA #$01                           A:FF X:55 Y:A3 P:E5 SP:F9 PPU: 14, 50 CYC:5695\n$F99C:60        RTS                                A:01 X:55 Y:A3 P:65 SP:F9 PPU: 20, 50 CYC:5697\n$D42A:85 78     STA $78 = #$7F                       A:01 X:55 Y:A3 P:65 SP:FB PPU: 38, 50 CYC:5703\n$D42C:46 78     LSR $78 = #$01                       A:01 X:55 Y:A3 P:65 SP:FB PPU: 47, 50 CYC:5706\n$D42E:A5 78     LDA $78 = #$00                       A:01 X:55 Y:A3 P:67 SP:FB PPU: 62, 50 CYC:5711\n$D430:20 9D F9  JSR $F99D                          A:00 X:55 Y:A3 P:67 SP:FB PPU: 71, 50 CYC:5714\n$F99D:90 1B     BCC $F9BA                          A:00 X:55 Y:A3 P:67 SP:F9 PPU: 89, 50 CYC:5720\n$F99F:D0 19     BNE $F9BA                          A:00 X:55 Y:A3 P:67 SP:F9 PPU: 95, 50 CYC:5722\n$F9A1:30 17     BMI $F9BA                          A:00 X:55 Y:A3 P:67 SP:F9 PPU:101, 50 CYC:5724\n$F9A3:50 15     BVC $F9BA                          A:00 X:55 Y:A3 P:67 SP:F9 PPU:107, 50 CYC:5726\n$F9A5:C9 00     CMP #$00                           A:00 X:55 Y:A3 P:67 SP:F9 PPU:113, 50 CYC:5728\n$F9A7:D0 11     BNE $F9BA                          A:00 X:55 Y:A3 P:67 SP:F9 PPU:119, 50 CYC:5730\n$F9A9:B8        CLV                                A:00 X:55 Y:A3 P:67 SP:F9 PPU:125, 50 CYC:5732\n$F9AA:A9 AA     LDA #$AA                           A:00 X:55 Y:A3 P:nvUbdIZC SP:F9 PPU:131, 50 CYC:5734\n$F9AC:60        RTS                                A:AA X:55 Y:A3 P:A5 SP:F9 PPU:137, 50 CYC:5736\n$D433:C8        INY                                A:AA X:55 Y:A3 P:A5 SP:FB PPU:155, 50 CYC:5742\n$D434:85 78     STA $78 = #$00                       A:AA X:55 Y:A4 P:A5 SP:FB PPU:161, 50 CYC:5744\n$D436:46 78     LSR $78 = #$AA                       A:AA X:55 Y:A4 P:A5 SP:FB PPU:170, 50 CYC:5747\n$D438:A5 78     LDA $78 = #$55                       A:AA X:55 Y:A4 P:nvUbdIzc SP:FB PPU:185, 50 CYC:5752\n$D43A:20 AD F9  JSR $F9AD                          A:55 X:55 Y:A4 P:nvUbdIzc SP:FB PPU:194, 50 CYC:5755\n$F9AD:B0 0B     BCS $F9BA                          A:55 X:55 Y:A4 P:nvUbdIzc SP:F9 PPU:212, 50 CYC:5761\n$F9AF:F0 09     BEQ $F9BA                          A:55 X:55 Y:A4 P:nvUbdIzc SP:F9 PPU:218, 50 CYC:5763\n$F9B1:30 07     BMI $F9BA                          A:55 X:55 Y:A4 P:nvUbdIzc SP:F9 PPU:224, 50 CYC:5765\n$F9B3:70 05     BVS $F9BA                          A:55 X:55 Y:A4 P:nvUbdIzc SP:F9 PPU:230, 50 CYC:5767\n$F9B5:C9 55     CMP #$55                           A:55 X:55 Y:A4 P:nvUbdIzc SP:F9 PPU:236, 50 CYC:5769\n$F9B7:D0 01     BNE $F9BA                          A:55 X:55 Y:A4 P:nvUbdIZC SP:F9 PPU:242, 50 CYC:5771\n$F9B9:60        RTS                                A:55 X:55 Y:A4 P:nvUbdIZC SP:F9 PPU:248, 50 CYC:5773\n$D43D:C8        INY                                A:55 X:55 Y:A4 P:nvUbdIZC SP:FB PPU:266, 50 CYC:5779\n$D43E:20 BD F9  JSR $F9BD                          A:55 X:55 Y:A5 P:A5 SP:FB PPU:272, 50 CYC:5781\n$F9BD:24 01     BIT $01 = #$FF                       A:55 X:55 Y:A5 P:A5 SP:F9 PPU:290, 50 CYC:5787\n$F9BF:38        SEC                                A:55 X:55 Y:A5 P:E5 SP:F9 PPU:299, 50 CYC:5790\n$F9C0:A9 80     LDA #$80                           A:55 X:55 Y:A5 P:E5 SP:F9 PPU:305, 50 CYC:5792\n$F9C2:60        RTS                                A:80 X:55 Y:A5 P:E5 SP:F9 PPU:311, 50 CYC:5794\n$D441:85 78     STA $78 = #$55                       A:80 X:55 Y:A5 P:E5 SP:FB PPU:329, 50 CYC:5800\n$D443:06 78     ASL $78 = #$80                       A:80 X:55 Y:A5 P:E5 SP:FB PPU:338, 50 CYC:5803\n$D445:A5 78     LDA $78 = #$00                       A:80 X:55 Y:A5 P:67 SP:FB PPU: 12, 51 CYC:5808\n$D447:20 C3 F9  JSR $F9C3                          A:00 X:55 Y:A5 P:67 SP:FB PPU: 21, 51 CYC:5811\n$F9C3:90 1C     BCC $F9E1                          A:00 X:55 Y:A5 P:67 SP:F9 PPU: 39, 51 CYC:5817\n$F9C5:D0 1A     BNE $F9E1                          A:00 X:55 Y:A5 P:67 SP:F9 PPU: 45, 51 CYC:5819\n$F9C7:30 18     BMI $F9E1                          A:00 X:55 Y:A5 P:67 SP:F9 PPU: 51, 51 CYC:5821\n$F9C9:50 16     BVC $F9E1                          A:00 X:55 Y:A5 P:67 SP:F9 PPU: 57, 51 CYC:5823\n$F9CB:C9 00     CMP #$00                           A:00 X:55 Y:A5 P:67 SP:F9 PPU: 63, 51 CYC:5825\n$F9CD:D0 12     BNE $F9E1                          A:00 X:55 Y:A5 P:67 SP:F9 PPU: 69, 51 CYC:5827\n$F9CF:B8        CLV                                A:00 X:55 Y:A5 P:67 SP:F9 PPU: 75, 51 CYC:5829\n$F9D0:A9 55     LDA #$55                           A:00 X:55 Y:A5 P:nvUbdIZC SP:F9 PPU: 81, 51 CYC:5831\n$F9D2:38        SEC                                A:55 X:55 Y:A5 P:25 SP:F9 PPU: 87, 51 CYC:5833\n$F9D3:60        RTS                                A:55 X:55 Y:A5 P:25 SP:F9 PPU: 93, 51 CYC:5835\n$D44A:C8        INY                                A:55 X:55 Y:A5 P:25 SP:FB PPU:111, 51 CYC:5841\n$D44B:85 78     STA $78 = #$00                       A:55 X:55 Y:A6 P:A5 SP:FB PPU:117, 51 CYC:5843\n$D44D:06 78     ASL $78 = #$55                       A:55 X:55 Y:A6 P:A5 SP:FB PPU:126, 51 CYC:5846\n$D44F:A5 78     LDA $78 = #$AA                       A:55 X:55 Y:A6 P:NvUbdIzc SP:FB PPU:141, 51 CYC:5851\n$D451:20 D4 F9  JSR $F9D4                          A:AA X:55 Y:A6 P:NvUbdIzc SP:FB PPU:150, 51 CYC:5854\n$F9D4:B0 0B     BCS $F9E1                          A:AA X:55 Y:A6 P:NvUbdIzc SP:F9 PPU:168, 51 CYC:5860\n$F9D6:F0 09     BEQ $F9E1                          A:AA X:55 Y:A6 P:NvUbdIzc SP:F9 PPU:174, 51 CYC:5862\n$F9D8:10 07     BPL $F9E1                          A:AA X:55 Y:A6 P:NvUbdIzc SP:F9 PPU:180, 51 CYC:5864\n$F9DA:70 05     BVS $F9E1                          A:AA X:55 Y:A6 P:NvUbdIzc SP:F9 PPU:186, 51 CYC:5866\n$F9DC:C9 AA     CMP #$AA                           A:AA X:55 Y:A6 P:NvUbdIzc SP:F9 PPU:192, 51 CYC:5868\n$F9DE:D0 01     BNE $F9E1                          A:AA X:55 Y:A6 P:nvUbdIZC SP:F9 PPU:198, 51 CYC:5870\n$F9E0:60        RTS                                A:AA X:55 Y:A6 P:nvUbdIZC SP:F9 PPU:204, 51 CYC:5872\n$D454:C8        INY                                A:AA X:55 Y:A6 P:nvUbdIZC SP:FB PPU:222, 51 CYC:5878\n$D455:20 E4 F9  JSR $F9E4                          A:AA X:55 Y:A7 P:A5 SP:FB PPU:228, 51 CYC:5880\n$F9E4:24 01     BIT $01 = #$FF                       A:AA X:55 Y:A7 P:A5 SP:F9 PPU:246, 51 CYC:5886\n$F9E6:38        SEC                                A:AA X:55 Y:A7 P:E5 SP:F9 PPU:255, 51 CYC:5889\n$F9E7:A9 01     LDA #$01                           A:AA X:55 Y:A7 P:E5 SP:F9 PPU:261, 51 CYC:5891\n$F9E9:60        RTS                                A:01 X:55 Y:A7 P:65 SP:F9 PPU:267, 51 CYC:5893\n$D458:85 78     STA $78 = #$AA                       A:01 X:55 Y:A7 P:65 SP:FB PPU:285, 51 CYC:5899\n$D45A:66 78     ROR $78 = #$01                       A:01 X:55 Y:A7 P:65 SP:FB PPU:294, 51 CYC:5902\n$D45C:A5 78     LDA $78 = #$80                       A:01 X:55 Y:A7 P:E5 SP:FB PPU:309, 51 CYC:5907\n$D45E:20 EA F9  JSR $F9EA                          A:80 X:55 Y:A7 P:E5 SP:FB PPU:318, 51 CYC:5910\n$F9EA:90 1C     BCC $FA08                          A:80 X:55 Y:A7 P:E5 SP:F9 PPU:336, 51 CYC:5916\n$F9EC:F0 1A     BEQ $FA08                          A:80 X:55 Y:A7 P:E5 SP:F9 PPU:  1, 52 CYC:5918\n$F9EE:10 18     BPL $FA08                          A:80 X:55 Y:A7 P:E5 SP:F9 PPU:  7, 52 CYC:5920\n$F9F0:50 16     BVC $FA08                          A:80 X:55 Y:A7 P:E5 SP:F9 PPU: 13, 52 CYC:5922\n$F9F2:C9 80     CMP #$80                           A:80 X:55 Y:A7 P:E5 SP:F9 PPU: 19, 52 CYC:5924\n$F9F4:D0 12     BNE $FA08                          A:80 X:55 Y:A7 P:67 SP:F9 PPU: 25, 52 CYC:5926\n$F9F6:B8        CLV                                A:80 X:55 Y:A7 P:67 SP:F9 PPU: 31, 52 CYC:5928\n$F9F7:18        CLC                                A:80 X:55 Y:A7 P:nvUbdIZC SP:F9 PPU: 37, 52 CYC:5930\n$F9F8:A9 55     LDA #$55                           A:80 X:55 Y:A7 P:nvUbdIZc SP:F9 PPU: 43, 52 CYC:5932\n$F9FA:60        RTS                                A:55 X:55 Y:A7 P:nvUbdIzc SP:F9 PPU: 49, 52 CYC:5934\n$D461:C8        INY                                A:55 X:55 Y:A7 P:nvUbdIzc SP:FB PPU: 67, 52 CYC:5940\n$D462:85 78     STA $78 = #$80                       A:55 X:55 Y:A8 P:NvUbdIzc SP:FB PPU: 73, 52 CYC:5942\n$D464:66 78     ROR $78 = #$55                       A:55 X:55 Y:A8 P:NvUbdIzc SP:FB PPU: 82, 52 CYC:5945\n$D466:A5 78     LDA $78 = #$2A                       A:55 X:55 Y:A8 P:25 SP:FB PPU: 97, 52 CYC:5950\n$D468:20 FB F9  JSR $F9FB                          A:2A X:55 Y:A8 P:25 SP:FB PPU:106, 52 CYC:5953\n$F9FB:90 0B     BCC $FA08                          A:2A X:55 Y:A8 P:25 SP:F9 PPU:124, 52 CYC:5959\n$F9FD:F0 09     BEQ $FA08                          A:2A X:55 Y:A8 P:25 SP:F9 PPU:130, 52 CYC:5961\n$F9FF:30 07     BMI $FA08                          A:2A X:55 Y:A8 P:25 SP:F9 PPU:136, 52 CYC:5963\n$FA01:70 05     BVS $FA08                          A:2A X:55 Y:A8 P:25 SP:F9 PPU:142, 52 CYC:5965\n$FA03:C9 2A     CMP #$2A                           A:2A X:55 Y:A8 P:25 SP:F9 PPU:148, 52 CYC:5967\n$FA05:D0 01     BNE $FA08                          A:2A X:55 Y:A8 P:nvUbdIZC SP:F9 PPU:154, 52 CYC:5969\n$FA07:60        RTS                                A:2A X:55 Y:A8 P:nvUbdIZC SP:F9 PPU:160, 52 CYC:5971\n$D46B:C8        INY                                A:2A X:55 Y:A8 P:nvUbdIZC SP:FB PPU:178, 52 CYC:5977\n$D46C:20 0A FA  JSR $FA0A                          A:2A X:55 Y:A9 P:A5 SP:FB PPU:184, 52 CYC:5979\n$FA0A:24 01     BIT $01 = #$FF                       A:2A X:55 Y:A9 P:A5 SP:F9 PPU:202, 52 CYC:5985\n$FA0C:38        SEC                                A:2A X:55 Y:A9 P:E5 SP:F9 PPU:211, 52 CYC:5988\n$FA0D:A9 80     LDA #$80                           A:2A X:55 Y:A9 P:E5 SP:F9 PPU:217, 52 CYC:5990\n$FA0F:60        RTS                                A:80 X:55 Y:A9 P:E5 SP:F9 PPU:223, 52 CYC:5992\n$D46F:85 78     STA $78 = #$2A                       A:80 X:55 Y:A9 P:E5 SP:FB PPU:241, 52 CYC:5998\n$D471:26 78     ROL $78 = #$80                       A:80 X:55 Y:A9 P:E5 SP:FB PPU:250, 52 CYC:6001\n$D473:A5 78     LDA $78 = #$01                       A:80 X:55 Y:A9 P:65 SP:FB PPU:265, 52 CYC:6006\n$D475:20 10 FA  JSR $FA10                          A:01 X:55 Y:A9 P:65 SP:FB PPU:274, 52 CYC:6009\n$FA10:90 1C     BCC $FA2E                          A:01 X:55 Y:A9 P:65 SP:F9 PPU:292, 52 CYC:6015\n$FA12:F0 1A     BEQ $FA2E                          A:01 X:55 Y:A9 P:65 SP:F9 PPU:298, 52 CYC:6017\n$FA14:30 18     BMI $FA2E                          A:01 X:55 Y:A9 P:65 SP:F9 PPU:304, 52 CYC:6019\n$FA16:50 16     BVC $FA2E                          A:01 X:55 Y:A9 P:65 SP:F9 PPU:310, 52 CYC:6021\n$FA18:C9 01     CMP #$01                           A:01 X:55 Y:A9 P:65 SP:F9 PPU:316, 52 CYC:6023\n$FA1A:D0 12     BNE $FA2E                          A:01 X:55 Y:A9 P:67 SP:F9 PPU:322, 52 CYC:6025\n$FA1C:B8        CLV                                A:01 X:55 Y:A9 P:67 SP:F9 PPU:328, 52 CYC:6027\n$FA1D:18        CLC                                A:01 X:55 Y:A9 P:nvUbdIZC SP:F9 PPU:334, 52 CYC:6029\n$FA1E:A9 55     LDA #$55                           A:01 X:55 Y:A9 P:nvUbdIZc SP:F9 PPU:340, 52 CYC:6031\n$FA20:60        RTS                                A:55 X:55 Y:A9 P:nvUbdIzc SP:F9 PPU:  5, 53 CYC:6033\n$D478:C8        INY                                A:55 X:55 Y:A9 P:nvUbdIzc SP:FB PPU: 23, 53 CYC:6039\n$D479:85 78     STA $78 = #$01                       A:55 X:55 Y:AA P:NvUbdIzc SP:FB PPU: 29, 53 CYC:6041\n$D47B:26 78     ROL $78 = #$55                       A:55 X:55 Y:AA P:NvUbdIzc SP:FB PPU: 38, 53 CYC:6044\n$D47D:A5 78     LDA $78 = #$AA                       A:55 X:55 Y:AA P:NvUbdIzc SP:FB PPU: 53, 53 CYC:6049\n$D47F:20 21 FA  JSR $FA21                          A:AA X:55 Y:AA P:NvUbdIzc SP:FB PPU: 62, 53 CYC:6052\n$FA21:B0 0B     BCS $FA2E                          A:AA X:55 Y:AA P:NvUbdIzc SP:F9 PPU: 80, 53 CYC:6058\n$FA23:F0 09     BEQ $FA2E                          A:AA X:55 Y:AA P:NvUbdIzc SP:F9 PPU: 86, 53 CYC:6060\n$FA25:10 07     BPL $FA2E                          A:AA X:55 Y:AA P:NvUbdIzc SP:F9 PPU: 92, 53 CYC:6062\n$FA27:70 05     BVS $FA2E                          A:AA X:55 Y:AA P:NvUbdIzc SP:F9 PPU: 98, 53 CYC:6064\n$FA29:C9 AA     CMP #$AA                           A:AA X:55 Y:AA P:NvUbdIzc SP:F9 PPU:104, 53 CYC:6066\n$FA2B:D0 01     BNE $FA2E                          A:AA X:55 Y:AA P:nvUbdIZC SP:F9 PPU:110, 53 CYC:6068\n$FA2D:60        RTS                                A:AA X:55 Y:AA P:nvUbdIZC SP:F9 PPU:116, 53 CYC:6070\n$D482:A9 FF     LDA #$FF                           A:AA X:55 Y:AA P:nvUbdIZC SP:FB PPU:134, 53 CYC:6076\n$D484:85 78     STA $78 = #$AA                       A:FF X:55 Y:AA P:A5 SP:FB PPU:140, 53 CYC:6078\n$D486:85 01     STA $01 = #$FF                       A:FF X:55 Y:AA P:A5 SP:FB PPU:149, 53 CYC:6081\n$D488:24 01     BIT $01 = #$FF                       A:FF X:55 Y:AA P:A5 SP:FB PPU:158, 53 CYC:6084\n$D48A:38        SEC                                A:FF X:55 Y:AA P:E5 SP:FB PPU:167, 53 CYC:6087\n$D48B:E6 78     INC $78 = #$FF                       A:FF X:55 Y:AA P:E5 SP:FB PPU:173, 53 CYC:6089\n$D48D:D0 0C     BNE $D49B                          A:FF X:55 Y:AA P:67 SP:FB PPU:188, 53 CYC:6094\n$D48F:30 0A     BMI $D49B                          A:FF X:55 Y:AA P:67 SP:FB PPU:194, 53 CYC:6096\n$D491:50 08     BVC $D49B                          A:FF X:55 Y:AA P:67 SP:FB PPU:200, 53 CYC:6098\n$D493:90 06     BCC $D49B                          A:FF X:55 Y:AA P:67 SP:FB PPU:206, 53 CYC:6100\n$D495:A5 78     LDA $78 = #$00                       A:FF X:55 Y:AA P:67 SP:FB PPU:212, 53 CYC:6102\n$D497:C9 00     CMP #$00                           A:00 X:55 Y:AA P:67 SP:FB PPU:221, 53 CYC:6105\n$D499:F0 04     BEQ $D49F                          A:00 X:55 Y:AA P:67 SP:FB PPU:227, 53 CYC:6107\n$D49F:A9 7F     LDA #$7F                           A:00 X:55 Y:AA P:67 SP:FB PPU:236, 53 CYC:6110\n$D4A1:85 78     STA $78 = #$00                       A:7F X:55 Y:AA P:65 SP:FB PPU:242, 53 CYC:6112\n$D4A3:B8        CLV                                A:7F X:55 Y:AA P:65 SP:FB PPU:251, 53 CYC:6115\n$D4A4:18        CLC                                A:7F X:55 Y:AA P:25 SP:FB PPU:257, 53 CYC:6117\n$D4A5:E6 78     INC $78 = #$7F                       A:7F X:55 Y:AA P:nvUbdIzc SP:FB PPU:263, 53 CYC:6119\n$D4A7:F0 0C     BEQ $D4B5                          A:7F X:55 Y:AA P:NvUbdIzc SP:FB PPU:278, 53 CYC:6124\n$D4A9:10 0A     BPL $D4B5                          A:7F X:55 Y:AA P:NvUbdIzc SP:FB PPU:284, 53 CYC:6126\n$D4AB:70 08     BVS $D4B5                          A:7F X:55 Y:AA P:NvUbdIzc SP:FB PPU:290, 53 CYC:6128\n$D4AD:B0 06     BCS $D4B5                          A:7F X:55 Y:AA P:NvUbdIzc SP:FB PPU:296, 53 CYC:6130\n$D4AF:A5 78     LDA $78 = #$80                       A:7F X:55 Y:AA P:NvUbdIzc SP:FB PPU:302, 53 CYC:6132\n$D4B1:C9 80     CMP #$80                           A:80 X:55 Y:AA P:NvUbdIzc SP:FB PPU:311, 53 CYC:6135\n$D4B3:F0 04     BEQ $D4B9                          A:80 X:55 Y:AA P:nvUbdIZC SP:FB PPU:317, 53 CYC:6137\n$D4B9:A9 00     LDA #$00                           A:80 X:55 Y:AA P:nvUbdIZC SP:FB PPU:326, 53 CYC:6140\n$D4BB:85 78     STA $78 = #$80                       A:00 X:55 Y:AA P:nvUbdIZC SP:FB PPU:332, 53 CYC:6142\n$D4BD:24 01     BIT $01 = #$FF                       A:00 X:55 Y:AA P:nvUbdIZC SP:FB PPU:  0, 54 CYC:6145\n$D4BF:38        SEC                                A:00 X:55 Y:AA P:E7 SP:FB PPU:  9, 54 CYC:6148\n$D4C0:C6 78     DEC $78 = #$00                       A:00 X:55 Y:AA P:E7 SP:FB PPU: 15, 54 CYC:6150\n$D4C2:F0 0C     BEQ $D4D0                          A:00 X:55 Y:AA P:E5 SP:FB PPU: 30, 54 CYC:6155\n$D4C4:10 0A     BPL $D4D0                          A:00 X:55 Y:AA P:E5 SP:FB PPU: 36, 54 CYC:6157\n$D4C6:50 08     BVC $D4D0                          A:00 X:55 Y:AA P:E5 SP:FB PPU: 42, 54 CYC:6159\n$D4C8:90 06     BCC $D4D0                          A:00 X:55 Y:AA P:E5 SP:FB PPU: 48, 54 CYC:6161\n$D4CA:A5 78     LDA $78 = #$FF                       A:00 X:55 Y:AA P:E5 SP:FB PPU: 54, 54 CYC:6163\n$D4CC:C9 FF     CMP #$FF                           A:FF X:55 Y:AA P:E5 SP:FB PPU: 63, 54 CYC:6166\n$D4CE:F0 04     BEQ $D4D4                          A:FF X:55 Y:AA P:67 SP:FB PPU: 69, 54 CYC:6168\n$D4D4:A9 80     LDA #$80                           A:FF X:55 Y:AA P:67 SP:FB PPU: 78, 54 CYC:6171\n$D4D6:85 78     STA $78 = #$FF                       A:80 X:55 Y:AA P:E5 SP:FB PPU: 84, 54 CYC:6173\n$D4D8:B8        CLV                                A:80 X:55 Y:AA P:E5 SP:FB PPU: 93, 54 CYC:6176\n$D4D9:18        CLC                                A:80 X:55 Y:AA P:A5 SP:FB PPU: 99, 54 CYC:6178\n$D4DA:C6 78     DEC $78 = #$80                       A:80 X:55 Y:AA P:NvUbdIzc SP:FB PPU:105, 54 CYC:6180\n$D4DC:F0 0C     BEQ $D4EA                          A:80 X:55 Y:AA P:nvUbdIzc SP:FB PPU:120, 54 CYC:6185\n$D4DE:30 0A     BMI $D4EA                          A:80 X:55 Y:AA P:nvUbdIzc SP:FB PPU:126, 54 CYC:6187\n$D4E0:70 08     BVS $D4EA                          A:80 X:55 Y:AA P:nvUbdIzc SP:FB PPU:132, 54 CYC:6189\n$D4E2:B0 06     BCS $D4EA                          A:80 X:55 Y:AA P:nvUbdIzc SP:FB PPU:138, 54 CYC:6191\n$D4E4:A5 78     LDA $78 = #$7F                       A:80 X:55 Y:AA P:nvUbdIzc SP:FB PPU:144, 54 CYC:6193\n$D4E6:C9 7F     CMP #$7F                           A:7F X:55 Y:AA P:nvUbdIzc SP:FB PPU:153, 54 CYC:6196\n$D4E8:F0 04     BEQ $D4EE                          A:7F X:55 Y:AA P:nvUbdIZC SP:FB PPU:159, 54 CYC:6198\n$D4EE:A9 01     LDA #$01                           A:7F X:55 Y:AA P:nvUbdIZC SP:FB PPU:168, 54 CYC:6201\n$D4F0:85 78     STA $78 = #$7F                       A:01 X:55 Y:AA P:25 SP:FB PPU:174, 54 CYC:6203\n$D4F2:C6 78     DEC $78 = #$01                       A:01 X:55 Y:AA P:25 SP:FB PPU:183, 54 CYC:6206\n$D4F4:F0 04     BEQ $D4FA                          A:01 X:55 Y:AA P:nvUbdIZC SP:FB PPU:198, 54 CYC:6211\n$D4FA:60        RTS                                A:01 X:55 Y:AA P:nvUbdIZC SP:FB PPU:207, 54 CYC:6214\n$C615:20 FB D4  JSR $D4FB                          A:01 X:55 Y:AA P:nvUbdIZC SP:FD PPU:225, 54 CYC:6220\n$D4FB:A9 55     LDA #$55                           A:01 X:55 Y:AA P:nvUbdIZC SP:FB PPU:243, 54 CYC:6226\n$D4FD:8D 78 06  STA $0678 = #$00                     A:55 X:55 Y:AA P:25 SP:FB PPU:249, 54 CYC:6228\n$D500:A9 FF     LDA #$FF                           A:55 X:55 Y:AA P:25 SP:FB PPU:261, 54 CYC:6232\n$D502:85 01     STA $01 = #$FF                       A:FF X:55 Y:AA P:A5 SP:FB PPU:267, 54 CYC:6234\n$D504:24 01     BIT $01 = #$FF                       A:FF X:55 Y:AA P:A5 SP:FB PPU:276, 54 CYC:6237\n$D506:A0 11     LDY #$11                           A:FF X:55 Y:AA P:E5 SP:FB PPU:285, 54 CYC:6240\n$D508:A2 23     LDX #$23                           A:FF X:55 Y:11 P:65 SP:FB PPU:291, 54 CYC:6242\n$D50A:A9 00     LDA #$00                           A:FF X:23 Y:11 P:65 SP:FB PPU:297, 54 CYC:6244\n$D50C:AD 78 06  LDA $0678 = #$55                     A:00 X:23 Y:11 P:67 SP:FB PPU:303, 54 CYC:6246\n$D50F:F0 10     BEQ $D521                          A:55 X:23 Y:11 P:65 SP:FB PPU:315, 54 CYC:6250\n$D511:30 0E     BMI $D521                          A:55 X:23 Y:11 P:65 SP:FB PPU:321, 54 CYC:6252\n$D513:C9 55     CMP #$55                           A:55 X:23 Y:11 P:65 SP:FB PPU:327, 54 CYC:6254\n$D515:D0 0A     BNE $D521                          A:55 X:23 Y:11 P:67 SP:FB PPU:333, 54 CYC:6256\n$D517:C0 11     CPY #$11                           A:55 X:23 Y:11 P:67 SP:FB PPU:339, 54 CYC:6258\n$D519:D0 06     BNE $D521                          A:55 X:23 Y:11 P:67 SP:FB PPU:  4, 55 CYC:6260\n$D51B:E0 23     CPX #$23                           A:55 X:23 Y:11 P:67 SP:FB PPU: 10, 55 CYC:6262\n$D51D:50 02     BVC $D521                          A:55 X:23 Y:11 P:67 SP:FB PPU: 16, 55 CYC:6264\n$D51F:F0 04     BEQ $D525                          A:55 X:23 Y:11 P:67 SP:FB PPU: 22, 55 CYC:6266\n$D525:A9 46     LDA #$46                           A:55 X:23 Y:11 P:67 SP:FB PPU: 31, 55 CYC:6269\n$D527:24 01     BIT $01 = #$FF                       A:46 X:23 Y:11 P:65 SP:FB PPU: 37, 55 CYC:6271\n$D529:8D 78 06  STA $0678 = #$55                     A:46 X:23 Y:11 P:E5 SP:FB PPU: 46, 55 CYC:6274\n$D52C:F0 0B     BEQ $D539                          A:46 X:23 Y:11 P:E5 SP:FB PPU: 58, 55 CYC:6278\n$D52E:10 09     BPL $D539                          A:46 X:23 Y:11 P:E5 SP:FB PPU: 64, 55 CYC:6280\n$D530:50 07     BVC $D539                          A:46 X:23 Y:11 P:E5 SP:FB PPU: 70, 55 CYC:6282\n$D532:AD 78 06  LDA $0678 = #$46                     A:46 X:23 Y:11 P:E5 SP:FB PPU: 76, 55 CYC:6284\n$D535:C9 46     CMP #$46                           A:46 X:23 Y:11 P:65 SP:FB PPU: 88, 55 CYC:6288\n$D537:F0 04     BEQ $D53D                          A:46 X:23 Y:11 P:67 SP:FB PPU: 94, 55 CYC:6290\n$D53D:A9 55     LDA #$55                           A:46 X:23 Y:11 P:67 SP:FB PPU:103, 55 CYC:6293\n$D53F:8D 78 06  STA $0678 = #$46                     A:55 X:23 Y:11 P:65 SP:FB PPU:109, 55 CYC:6295\n$D542:24 01     BIT $01 = #$FF                       A:55 X:23 Y:11 P:65 SP:FB PPU:121, 55 CYC:6299\n$D544:A9 11     LDA #$11                           A:55 X:23 Y:11 P:E5 SP:FB PPU:130, 55 CYC:6302\n$D546:A2 23     LDX #$23                           A:11 X:23 Y:11 P:65 SP:FB PPU:136, 55 CYC:6304\n$D548:A0 00     LDY #$00                           A:11 X:23 Y:11 P:65 SP:FB PPU:142, 55 CYC:6306\n$D54A:AC 78 06  LDY $0678 = #$55                     A:11 X:23 Y:00 P:67 SP:FB PPU:148, 55 CYC:6308\n$D54D:F0 10     BEQ $D55F                          A:11 X:23 Y:55 P:65 SP:FB PPU:160, 55 CYC:6312\n$D54F:30 0E     BMI $D55F                          A:11 X:23 Y:55 P:65 SP:FB PPU:166, 55 CYC:6314\n$D551:C0 55     CPY #$55                           A:11 X:23 Y:55 P:65 SP:FB PPU:172, 55 CYC:6316\n$D553:D0 0A     BNE $D55F                          A:11 X:23 Y:55 P:67 SP:FB PPU:178, 55 CYC:6318\n$D555:C9 11     CMP #$11                           A:11 X:23 Y:55 P:67 SP:FB PPU:184, 55 CYC:6320\n$D557:D0 06     BNE $D55F                          A:11 X:23 Y:55 P:67 SP:FB PPU:190, 55 CYC:6322\n$D559:E0 23     CPX #$23                           A:11 X:23 Y:55 P:67 SP:FB PPU:196, 55 CYC:6324\n$D55B:50 02     BVC $D55F                          A:11 X:23 Y:55 P:67 SP:FB PPU:202, 55 CYC:6326\n$D55D:F0 04     BEQ $D563                          A:11 X:23 Y:55 P:67 SP:FB PPU:208, 55 CYC:6328\n$D563:A0 46     LDY #$46                           A:11 X:23 Y:55 P:67 SP:FB PPU:217, 55 CYC:6331\n$D565:24 01     BIT $01 = #$FF                       A:11 X:23 Y:46 P:65 SP:FB PPU:223, 55 CYC:6333\n$D567:8C 78 06  STY $0678 = #$55                     A:11 X:23 Y:46 P:E5 SP:FB PPU:232, 55 CYC:6336\n$D56A:F0 0B     BEQ $D577                          A:11 X:23 Y:46 P:E5 SP:FB PPU:244, 55 CYC:6340\n$D56C:10 09     BPL $D577                          A:11 X:23 Y:46 P:E5 SP:FB PPU:250, 55 CYC:6342\n$D56E:50 07     BVC $D577                          A:11 X:23 Y:46 P:E5 SP:FB PPU:256, 55 CYC:6344\n$D570:AC 78 06  LDY $0678 = #$46                     A:11 X:23 Y:46 P:E5 SP:FB PPU:262, 55 CYC:6346\n$D573:C0 46     CPY #$46                           A:11 X:23 Y:46 P:65 SP:FB PPU:274, 55 CYC:6350\n$D575:F0 04     BEQ $D57B                          A:11 X:23 Y:46 P:67 SP:FB PPU:280, 55 CYC:6352\n$D57B:24 01     BIT $01 = #$FF                       A:11 X:23 Y:46 P:67 SP:FB PPU:289, 55 CYC:6355\n$D57D:A9 55     LDA #$55                           A:11 X:23 Y:46 P:E5 SP:FB PPU:298, 55 CYC:6358\n$D57F:8D 78 06  STA $0678 = #$46                     A:55 X:23 Y:46 P:65 SP:FB PPU:304, 55 CYC:6360\n$D582:A0 11     LDY #$11                           A:55 X:23 Y:46 P:65 SP:FB PPU:316, 55 CYC:6364\n$D584:A9 23     LDA #$23                           A:55 X:23 Y:11 P:65 SP:FB PPU:322, 55 CYC:6366\n$D586:A2 00     LDX #$00                           A:23 X:23 Y:11 P:65 SP:FB PPU:328, 55 CYC:6368\n$D588:AE 78 06  LDX $0678 = #$55                     A:23 X:00 Y:11 P:67 SP:FB PPU:334, 55 CYC:6370\n$D58B:F0 10     BEQ $D59D                          A:23 X:55 Y:11 P:65 SP:FB PPU:  5, 56 CYC:6374\n$D58D:30 0E     BMI $D59D                          A:23 X:55 Y:11 P:65 SP:FB PPU: 11, 56 CYC:6376\n$D58F:E0 55     CPX #$55                           A:23 X:55 Y:11 P:65 SP:FB PPU: 17, 56 CYC:6378\n$D591:D0 0A     BNE $D59D                          A:23 X:55 Y:11 P:67 SP:FB PPU: 23, 56 CYC:6380\n$D593:C0 11     CPY #$11                           A:23 X:55 Y:11 P:67 SP:FB PPU: 29, 56 CYC:6382\n$D595:D0 06     BNE $D59D                          A:23 X:55 Y:11 P:67 SP:FB PPU: 35, 56 CYC:6384\n$D597:C9 23     CMP #$23                           A:23 X:55 Y:11 P:67 SP:FB PPU: 41, 56 CYC:6386\n$D599:50 02     BVC $D59D                          A:23 X:55 Y:11 P:67 SP:FB PPU: 47, 56 CYC:6388\n$D59B:F0 04     BEQ $D5A1                          A:23 X:55 Y:11 P:67 SP:FB PPU: 53, 56 CYC:6390\n$D5A1:A2 46     LDX #$46                           A:23 X:55 Y:11 P:67 SP:FB PPU: 62, 56 CYC:6393\n$D5A3:24 01     BIT $01 = #$FF                       A:23 X:46 Y:11 P:65 SP:FB PPU: 68, 56 CYC:6395\n$D5A5:8E 78 06  STX $0678 = #$55                     A:23 X:46 Y:11 P:E5 SP:FB PPU: 77, 56 CYC:6398\n$D5A8:F0 0B     BEQ $D5B5                          A:23 X:46 Y:11 P:E5 SP:FB PPU: 89, 56 CYC:6402\n$D5AA:10 09     BPL $D5B5                          A:23 X:46 Y:11 P:E5 SP:FB PPU: 95, 56 CYC:6404\n$D5AC:50 07     BVC $D5B5                          A:23 X:46 Y:11 P:E5 SP:FB PPU:101, 56 CYC:6406\n$D5AE:AE 78 06  LDX $0678 = #$46                     A:23 X:46 Y:11 P:E5 SP:FB PPU:107, 56 CYC:6408\n$D5B1:E0 46     CPX #$46                           A:23 X:46 Y:11 P:65 SP:FB PPU:119, 56 CYC:6412\n$D5B3:F0 04     BEQ $D5B9                          A:23 X:46 Y:11 P:67 SP:FB PPU:125, 56 CYC:6414\n$D5B9:A9 C0     LDA #$C0                           A:23 X:46 Y:11 P:67 SP:FB PPU:134, 56 CYC:6417\n$D5BB:8D 78 06  STA $0678 = #$46                     A:C0 X:46 Y:11 P:E5 SP:FB PPU:140, 56 CYC:6419\n$D5BE:A2 33     LDX #$33                           A:C0 X:46 Y:11 P:E5 SP:FB PPU:152, 56 CYC:6423\n$D5C0:A0 88     LDY #$88                           A:C0 X:33 Y:11 P:65 SP:FB PPU:158, 56 CYC:6425\n$D5C2:A9 05     LDA #$05                           A:C0 X:33 Y:88 P:E5 SP:FB PPU:164, 56 CYC:6427\n$D5C4:2C 78 06  BIT $0678 = #$C0                     A:05 X:33 Y:88 P:65 SP:FB PPU:170, 56 CYC:6429\n$D5C7:10 10     BPL $D5D9                          A:05 X:33 Y:88 P:E7 SP:FB PPU:182, 56 CYC:6433\n$D5C9:50 0E     BVC $D5D9                          A:05 X:33 Y:88 P:E7 SP:FB PPU:188, 56 CYC:6435\n$D5CB:D0 0C     BNE $D5D9                          A:05 X:33 Y:88 P:E7 SP:FB PPU:194, 56 CYC:6437\n$D5CD:C9 05     CMP #$05                           A:05 X:33 Y:88 P:E7 SP:FB PPU:200, 56 CYC:6439\n$D5CF:D0 08     BNE $D5D9                          A:05 X:33 Y:88 P:67 SP:FB PPU:206, 56 CYC:6441\n$D5D1:E0 33     CPX #$33                           A:05 X:33 Y:88 P:67 SP:FB PPU:212, 56 CYC:6443\n$D5D3:D0 04     BNE $D5D9                          A:05 X:33 Y:88 P:67 SP:FB PPU:218, 56 CYC:6445\n$D5D5:C0 88     CPY #$88                           A:05 X:33 Y:88 P:67 SP:FB PPU:224, 56 CYC:6447\n$D5D7:F0 04     BEQ $D5DD                          A:05 X:33 Y:88 P:67 SP:FB PPU:230, 56 CYC:6449\n$D5DD:A9 03     LDA #$03                           A:05 X:33 Y:88 P:67 SP:FB PPU:239, 56 CYC:6452\n$D5DF:8D 78 06  STA $0678 = #$C0                     A:03 X:33 Y:88 P:65 SP:FB PPU:245, 56 CYC:6454\n$D5E2:A9 01     LDA #$01                           A:03 X:33 Y:88 P:65 SP:FB PPU:257, 56 CYC:6458\n$D5E4:2C 78 06  BIT $0678 = #$03                     A:01 X:33 Y:88 P:65 SP:FB PPU:263, 56 CYC:6460\n$D5E7:30 08     BMI $D5F1                          A:01 X:33 Y:88 P:25 SP:FB PPU:275, 56 CYC:6464\n$D5E9:70 06     BVS $D5F1                          A:01 X:33 Y:88 P:25 SP:FB PPU:281, 56 CYC:6466\n$D5EB:F0 04     BEQ $D5F1                          A:01 X:33 Y:88 P:25 SP:FB PPU:287, 56 CYC:6468\n$D5ED:C9 01     CMP #$01                           A:01 X:33 Y:88 P:25 SP:FB PPU:293, 56 CYC:6470\n$D5EF:F0 04     BEQ $D5F5                          A:01 X:33 Y:88 P:nvUbdIZC SP:FB PPU:299, 56 CYC:6472\n$D5F5:A0 B8     LDY #$B8                           A:01 X:33 Y:88 P:nvUbdIZC SP:FB PPU:308, 56 CYC:6475\n$D5F7:A9 AA     LDA #$AA                           A:01 X:33 Y:B8 P:A5 SP:FB PPU:314, 56 CYC:6477\n$D5F9:8D 78 06  STA $0678 = #$03                     A:AA X:33 Y:B8 P:A5 SP:FB PPU:320, 56 CYC:6479\n$D5FC:20 B6 F7  JSR $F7B6                          A:AA X:33 Y:B8 P:A5 SP:FB PPU:332, 56 CYC:6483\n$F7B6:18        CLC                                A:AA X:33 Y:B8 P:A5 SP:F9 PPU:  9, 57 CYC:6489\n$F7B7:A9 FF     LDA #$FF                           A:AA X:33 Y:B8 P:NvUbdIzc SP:F9 PPU: 15, 57 CYC:6491\n$F7B9:85 01     STA $01 = #$FF                       A:FF X:33 Y:B8 P:NvUbdIzc SP:F9 PPU: 21, 57 CYC:6493\n$F7BB:24 01     BIT $01 = #$FF                       A:FF X:33 Y:B8 P:NvUbdIzc SP:F9 PPU: 30, 57 CYC:6496\n$F7BD:A9 55     LDA #$55                           A:FF X:33 Y:B8 P:NVUbdIzc SP:F9 PPU: 39, 57 CYC:6499\n$F7BF:60        RTS                                A:55 X:33 Y:B8 P:64 SP:F9 PPU: 45, 57 CYC:6501\n$D5FF:0D 78 06  ORA $0678 = #$AA                     A:55 X:33 Y:B8 P:64 SP:FB PPU: 63, 57 CYC:6507\n$D602:20 C0 F7  JSR $F7C0                          A:FF X:33 Y:B8 P:NVUbdIzc SP:FB PPU: 75, 57 CYC:6511\n$F7C0:B0 09     BCS $F7CB                          A:FF X:33 Y:B8 P:NVUbdIzc SP:F9 PPU: 93, 57 CYC:6517\n$F7C2:10 07     BPL $F7CB                          A:FF X:33 Y:B8 P:NVUbdIzc SP:F9 PPU: 99, 57 CYC:6519\n$F7C4:C9 FF     CMP #$FF                           A:FF X:33 Y:B8 P:NVUbdIzc SP:F9 PPU:105, 57 CYC:6521\n$F7C6:D0 03     BNE $F7CB                          A:FF X:33 Y:B8 P:67 SP:F9 PPU:111, 57 CYC:6523\n$F7C8:50 01     BVC $F7CB                          A:FF X:33 Y:B8 P:67 SP:F9 PPU:117, 57 CYC:6525\n$F7CA:60        RTS                                A:FF X:33 Y:B8 P:67 SP:F9 PPU:123, 57 CYC:6527\n$D605:C8        INY                                A:FF X:33 Y:B8 P:67 SP:FB PPU:141, 57 CYC:6533\n$D606:A9 00     LDA #$00                           A:FF X:33 Y:B9 P:E5 SP:FB PPU:147, 57 CYC:6535\n$D608:8D 78 06  STA $0678 = #$AA                     A:00 X:33 Y:B9 P:67 SP:FB PPU:153, 57 CYC:6537\n$D60B:20 CE F7  JSR $F7CE                          A:00 X:33 Y:B9 P:67 SP:FB PPU:165, 57 CYC:6541\n$F7CE:38        SEC                                A:00 X:33 Y:B9 P:67 SP:F9 PPU:183, 57 CYC:6547\n$F7CF:B8        CLV                                A:00 X:33 Y:B9 P:67 SP:F9 PPU:189, 57 CYC:6549\n$F7D0:A9 00     LDA #$00                           A:00 X:33 Y:B9 P:nvUbdIZC SP:F9 PPU:195, 57 CYC:6551\n$F7D2:60        RTS                                A:00 X:33 Y:B9 P:nvUbdIZC SP:F9 PPU:201, 57 CYC:6553\n$D60E:0D 78 06  ORA $0678 = #$00                     A:00 X:33 Y:B9 P:nvUbdIZC SP:FB PPU:219, 57 CYC:6559\n$D611:20 D3 F7  JSR $F7D3                          A:00 X:33 Y:B9 P:nvUbdIZC SP:FB PPU:231, 57 CYC:6563\n$F7D3:D0 07     BNE $F7DC                          A:00 X:33 Y:B9 P:nvUbdIZC SP:F9 PPU:249, 57 CYC:6569\n$F7D5:70 05     BVS $F7DC                          A:00 X:33 Y:B9 P:nvUbdIZC SP:F9 PPU:255, 57 CYC:6571\n$F7D7:90 03     BCC $F7DC                          A:00 X:33 Y:B9 P:nvUbdIZC SP:F9 PPU:261, 57 CYC:6573\n$F7D9:30 01     BMI $F7DC                          A:00 X:33 Y:B9 P:nvUbdIZC SP:F9 PPU:267, 57 CYC:6575\n$F7DB:60        RTS                                A:00 X:33 Y:B9 P:nvUbdIZC SP:F9 PPU:273, 57 CYC:6577\n$D614:C8        INY                                A:00 X:33 Y:B9 P:nvUbdIZC SP:FB PPU:291, 57 CYC:6583\n$D615:A9 AA     LDA #$AA                           A:00 X:33 Y:BA P:A5 SP:FB PPU:297, 57 CYC:6585\n$D617:8D 78 06  STA $0678 = #$00                     A:AA X:33 Y:BA P:A5 SP:FB PPU:303, 57 CYC:6587\n$D61A:20 DF F7  JSR $F7DF                          A:AA X:33 Y:BA P:A5 SP:FB PPU:315, 57 CYC:6591\n$F7DF:18        CLC                                A:AA X:33 Y:BA P:A5 SP:F9 PPU:333, 57 CYC:6597\n$F7E0:24 01     BIT $01 = #$FF                       A:AA X:33 Y:BA P:NvUbdIzc SP:F9 PPU:339, 57 CYC:6599\n$F7E2:A9 55     LDA #$55                           A:AA X:33 Y:BA P:NVUbdIzc SP:F9 PPU:  7, 58 CYC:6602\n$F7E4:60        RTS                                A:55 X:33 Y:BA P:64 SP:F9 PPU: 13, 58 CYC:6604\n$D61D:2D 78 06  AND $0678 = #$AA                     A:55 X:33 Y:BA P:64 SP:FB PPU: 31, 58 CYC:6610\n$D620:20 E5 F7  JSR $F7E5                          A:00 X:33 Y:BA P:nVUbdIZc SP:FB PPU: 43, 58 CYC:6614\n$F7E5:D0 07     BNE $F7EE                          A:00 X:33 Y:BA P:nVUbdIZc SP:F9 PPU: 61, 58 CYC:6620\n$F7E7:50 05     BVC $F7EE                          A:00 X:33 Y:BA P:nVUbdIZc SP:F9 PPU: 67, 58 CYC:6622\n$F7E9:B0 03     BCS $F7EE                          A:00 X:33 Y:BA P:nVUbdIZc SP:F9 PPU: 73, 58 CYC:6624\n$F7EB:30 01     BMI $F7EE                          A:00 X:33 Y:BA P:nVUbdIZc SP:F9 PPU: 79, 58 CYC:6626\n$F7ED:60        RTS                                A:00 X:33 Y:BA P:nVUbdIZc SP:F9 PPU: 85, 58 CYC:6628\n$D623:C8        INY                                A:00 X:33 Y:BA P:nVUbdIZc SP:FB PPU:103, 58 CYC:6634\n$D624:A9 EF     LDA #$EF                           A:00 X:33 Y:BB P:NVUbdIzc SP:FB PPU:109, 58 CYC:6636\n$D626:8D 78 06  STA $0678 = #$AA                     A:EF X:33 Y:BB P:NVUbdIzc SP:FB PPU:115, 58 CYC:6638\n$D629:20 F1 F7  JSR $F7F1                          A:EF X:33 Y:BB P:NVUbdIzc SP:FB PPU:127, 58 CYC:6642\n$F7F1:38        SEC                                A:EF X:33 Y:BB P:NVUbdIzc SP:F9 PPU:145, 58 CYC:6648\n$F7F2:B8        CLV                                A:EF X:33 Y:BB P:E5 SP:F9 PPU:151, 58 CYC:6650\n$F7F3:A9 F8     LDA #$F8                           A:EF X:33 Y:BB P:A5 SP:F9 PPU:157, 58 CYC:6652\n$F7F5:60        RTS                                A:F8 X:33 Y:BB P:A5 SP:F9 PPU:163, 58 CYC:6654\n$D62C:2D 78 06  AND $0678 = #$EF                     A:F8 X:33 Y:BB P:A5 SP:FB PPU:181, 58 CYC:6660\n$D62F:20 F6 F7  JSR $F7F6                          A:E8 X:33 Y:BB P:A5 SP:FB PPU:193, 58 CYC:6664\n$F7F6:90 09     BCC $F801                          A:E8 X:33 Y:BB P:A5 SP:F9 PPU:211, 58 CYC:6670\n$F7F8:10 07     BPL $F801                          A:E8 X:33 Y:BB P:A5 SP:F9 PPU:217, 58 CYC:6672\n$F7FA:C9 E8     CMP #$E8                           A:E8 X:33 Y:BB P:A5 SP:F9 PPU:223, 58 CYC:6674\n$F7FC:D0 03     BNE $F801                          A:E8 X:33 Y:BB P:nvUbdIZC SP:F9 PPU:229, 58 CYC:6676\n$F7FE:70 01     BVS $F801                          A:E8 X:33 Y:BB P:nvUbdIZC SP:F9 PPU:235, 58 CYC:6678\n$F800:60        RTS                                A:E8 X:33 Y:BB P:nvUbdIZC SP:F9 PPU:241, 58 CYC:6680\n$D632:C8        INY                                A:E8 X:33 Y:BB P:nvUbdIZC SP:FB PPU:259, 58 CYC:6686\n$D633:A9 AA     LDA #$AA                           A:E8 X:33 Y:BC P:A5 SP:FB PPU:265, 58 CYC:6688\n$D635:8D 78 06  STA $0678 = #$EF                     A:AA X:33 Y:BC P:A5 SP:FB PPU:271, 58 CYC:6690\n$D638:20 04 F8  JSR $F804                          A:AA X:33 Y:BC P:A5 SP:FB PPU:283, 58 CYC:6694\n$F804:18        CLC                                A:AA X:33 Y:BC P:A5 SP:F9 PPU:301, 58 CYC:6700\n$F805:24 01     BIT $01 = #$FF                       A:AA X:33 Y:BC P:NvUbdIzc SP:F9 PPU:307, 58 CYC:6702\n$F807:A9 5F     LDA #$5F                           A:AA X:33 Y:BC P:NVUbdIzc SP:F9 PPU:316, 58 CYC:6705\n$F809:60        RTS                                A:5F X:33 Y:BC P:64 SP:F9 PPU:322, 58 CYC:6707\n$D63B:4D 78 06  EOR $0678 = #$AA                     A:5F X:33 Y:BC P:64 SP:FB PPU:340, 58 CYC:6713\n$D63E:20 0A F8  JSR $F80A                          A:F5 X:33 Y:BC P:NVUbdIzc SP:FB PPU: 11, 59 CYC:6717\n$F80A:B0 09     BCS $F815                          A:F5 X:33 Y:BC P:NVUbdIzc SP:F9 PPU: 29, 59 CYC:6723\n$F80C:10 07     BPL $F815                          A:F5 X:33 Y:BC P:NVUbdIzc SP:F9 PPU: 35, 59 CYC:6725\n$F80E:C9 F5     CMP #$F5                           A:F5 X:33 Y:BC P:NVUbdIzc SP:F9 PPU: 41, 59 CYC:6727\n$F810:D0 03     BNE $F815                          A:F5 X:33 Y:BC P:67 SP:F9 PPU: 47, 59 CYC:6729\n$F812:50 01     BVC $F815                          A:F5 X:33 Y:BC P:67 SP:F9 PPU: 53, 59 CYC:6731\n$F814:60        RTS                                A:F5 X:33 Y:BC P:67 SP:F9 PPU: 59, 59 CYC:6733\n$D641:C8        INY                                A:F5 X:33 Y:BC P:67 SP:FB PPU: 77, 59 CYC:6739\n$D642:A9 70     LDA #$70                           A:F5 X:33 Y:BD P:E5 SP:FB PPU: 83, 59 CYC:6741\n$D644:8D 78 06  STA $0678 = #$AA                     A:70 X:33 Y:BD P:65 SP:FB PPU: 89, 59 CYC:6743\n$D647:20 18 F8  JSR $F818                          A:70 X:33 Y:BD P:65 SP:FB PPU:101, 59 CYC:6747\n$F818:38        SEC                                A:70 X:33 Y:BD P:65 SP:F9 PPU:119, 59 CYC:6753\n$F819:B8        CLV                                A:70 X:33 Y:BD P:65 SP:F9 PPU:125, 59 CYC:6755\n$F81A:A9 70     LDA #$70                           A:70 X:33 Y:BD P:25 SP:F9 PPU:131, 59 CYC:6757\n$F81C:60        RTS                                A:70 X:33 Y:BD P:25 SP:F9 PPU:137, 59 CYC:6759\n$D64A:4D 78 06  EOR $0678 = #$70                     A:70 X:33 Y:BD P:25 SP:FB PPU:155, 59 CYC:6765\n$D64D:20 1D F8  JSR $F81D                          A:00 X:33 Y:BD P:nvUbdIZC SP:FB PPU:167, 59 CYC:6769\n$F81D:D0 07     BNE $F826                          A:00 X:33 Y:BD P:nvUbdIZC SP:F9 PPU:185, 59 CYC:6775\n$F81F:70 05     BVS $F826                          A:00 X:33 Y:BD P:nvUbdIZC SP:F9 PPU:191, 59 CYC:6777\n$F821:90 03     BCC $F826                          A:00 X:33 Y:BD P:nvUbdIZC SP:F9 PPU:197, 59 CYC:6779\n$F823:30 01     BMI $F826                          A:00 X:33 Y:BD P:nvUbdIZC SP:F9 PPU:203, 59 CYC:6781\n$F825:60        RTS                                A:00 X:33 Y:BD P:nvUbdIZC SP:F9 PPU:209, 59 CYC:6783\n$D650:C8        INY                                A:00 X:33 Y:BD P:nvUbdIZC SP:FB PPU:227, 59 CYC:6789\n$D651:A9 69     LDA #$69                           A:00 X:33 Y:BE P:A5 SP:FB PPU:233, 59 CYC:6791\n$D653:8D 78 06  STA $0678 = #$70                     A:69 X:33 Y:BE P:25 SP:FB PPU:239, 59 CYC:6793\n$D656:20 29 F8  JSR $F829                          A:69 X:33 Y:BE P:25 SP:FB PPU:251, 59 CYC:6797\n$F829:18        CLC                                A:69 X:33 Y:BE P:25 SP:F9 PPU:269, 59 CYC:6803\n$F82A:24 01     BIT $01 = #$FF                       A:69 X:33 Y:BE P:nvUbdIzc SP:F9 PPU:275, 59 CYC:6805\n$F82C:A9 00     LDA #$00                           A:69 X:33 Y:BE P:NVUbdIzc SP:F9 PPU:284, 59 CYC:6808\n$F82E:60        RTS                                A:00 X:33 Y:BE P:nVUbdIZc SP:F9 PPU:290, 59 CYC:6810\n$D659:6D 78 06  ADC $0678 = #$69                     A:00 X:33 Y:BE P:nVUbdIZc SP:FB PPU:308, 59 CYC:6816\n$D65C:20 2F F8  JSR $F82F                          A:69 X:33 Y:BE P:nvUbdIzc SP:FB PPU:320, 59 CYC:6820\n$F82F:30 09     BMI $F83A                          A:69 X:33 Y:BE P:nvUbdIzc SP:F9 PPU:338, 59 CYC:6826\n$F831:B0 07     BCS $F83A                          A:69 X:33 Y:BE P:nvUbdIzc SP:F9 PPU:  3, 60 CYC:6828\n$F833:C9 69     CMP #$69                           A:69 X:33 Y:BE P:nvUbdIzc SP:F9 PPU:  9, 60 CYC:6830\n$F835:D0 03     BNE $F83A                          A:69 X:33 Y:BE P:nvUbdIZC SP:F9 PPU: 15, 60 CYC:6832\n$F837:70 01     BVS $F83A                          A:69 X:33 Y:BE P:nvUbdIZC SP:F9 PPU: 21, 60 CYC:6834\n$F839:60        RTS                                A:69 X:33 Y:BE P:nvUbdIZC SP:F9 PPU: 27, 60 CYC:6836\n$D65F:C8        INY                                A:69 X:33 Y:BE P:nvUbdIZC SP:FB PPU: 45, 60 CYC:6842\n$D660:20 3D F8  JSR $F83D                          A:69 X:33 Y:BF P:A5 SP:FB PPU: 51, 60 CYC:6844\n$F83D:38        SEC                                A:69 X:33 Y:BF P:A5 SP:F9 PPU: 69, 60 CYC:6850\n$F83E:24 01     BIT $01 = #$FF                       A:69 X:33 Y:BF P:A5 SP:F9 PPU: 75, 60 CYC:6852\n$F840:A9 00     LDA #$00                           A:69 X:33 Y:BF P:E5 SP:F9 PPU: 84, 60 CYC:6855\n$F842:60        RTS                                A:00 X:33 Y:BF P:67 SP:F9 PPU: 90, 60 CYC:6857\n$D663:6D 78 06  ADC $0678 = #$69                     A:00 X:33 Y:BF P:67 SP:FB PPU:108, 60 CYC:6863\n$D666:20 43 F8  JSR $F843                          A:6A X:33 Y:BF P:nvUbdIzc SP:FB PPU:120, 60 CYC:6867\n$F843:30 09     BMI $F84E                          A:6A X:33 Y:BF P:nvUbdIzc SP:F9 PPU:138, 60 CYC:6873\n$F845:B0 07     BCS $F84E                          A:6A X:33 Y:BF P:nvUbdIzc SP:F9 PPU:144, 60 CYC:6875\n$F847:C9 6A     CMP #$6A                           A:6A X:33 Y:BF P:nvUbdIzc SP:F9 PPU:150, 60 CYC:6877\n$F849:D0 03     BNE $F84E                          A:6A X:33 Y:BF P:nvUbdIZC SP:F9 PPU:156, 60 CYC:6879\n$F84B:70 01     BVS $F84E                          A:6A X:33 Y:BF P:nvUbdIZC SP:F9 PPU:162, 60 CYC:6881\n$F84D:60        RTS                                A:6A X:33 Y:BF P:nvUbdIZC SP:F9 PPU:168, 60 CYC:6883\n$D669:C8        INY                                A:6A X:33 Y:BF P:nvUbdIZC SP:FB PPU:186, 60 CYC:6889\n$D66A:A9 7F     LDA #$7F                           A:6A X:33 Y:C0 P:A5 SP:FB PPU:192, 60 CYC:6891\n$D66C:8D 78 06  STA $0678 = #$69                     A:7F X:33 Y:C0 P:25 SP:FB PPU:198, 60 CYC:6893\n$D66F:20 51 F8  JSR $F851                          A:7F X:33 Y:C0 P:25 SP:FB PPU:210, 60 CYC:6897\n$F851:38        SEC                                A:7F X:33 Y:C0 P:25 SP:F9 PPU:228, 60 CYC:6903\n$F852:B8        CLV                                A:7F X:33 Y:C0 P:25 SP:F9 PPU:234, 60 CYC:6905\n$F853:A9 7F     LDA #$7F                           A:7F X:33 Y:C0 P:25 SP:F9 PPU:240, 60 CYC:6907\n$F855:60        RTS                                A:7F X:33 Y:C0 P:25 SP:F9 PPU:246, 60 CYC:6909\n$D672:6D 78 06  ADC $0678 = #$7F                     A:7F X:33 Y:C0 P:25 SP:FB PPU:264, 60 CYC:6915\n$D675:20 56 F8  JSR $F856                          A:FF X:33 Y:C0 P:NVUbdIzc SP:FB PPU:276, 60 CYC:6919\n$F856:10 09     BPL $F861                          A:FF X:33 Y:C0 P:NVUbdIzc SP:F9 PPU:294, 60 CYC:6925\n$F858:B0 07     BCS $F861                          A:FF X:33 Y:C0 P:NVUbdIzc SP:F9 PPU:300, 60 CYC:6927\n$F85A:C9 FF     CMP #$FF                           A:FF X:33 Y:C0 P:NVUbdIzc SP:F9 PPU:306, 60 CYC:6929\n$F85C:D0 03     BNE $F861                          A:FF X:33 Y:C0 P:67 SP:F9 PPU:312, 60 CYC:6931\n$F85E:50 01     BVC $F861                          A:FF X:33 Y:C0 P:67 SP:F9 PPU:318, 60 CYC:6933\n$F860:60        RTS                                A:FF X:33 Y:C0 P:67 SP:F9 PPU:324, 60 CYC:6935\n$D678:C8        INY                                A:FF X:33 Y:C0 P:67 SP:FB PPU:  1, 61 CYC:6941\n$D679:A9 80     LDA #$80                           A:FF X:33 Y:C1 P:E5 SP:FB PPU:  7, 61 CYC:6943\n$D67B:8D 78 06  STA $0678 = #$7F                     A:80 X:33 Y:C1 P:E5 SP:FB PPU: 13, 61 CYC:6945\n$D67E:20 64 F8  JSR $F864                          A:80 X:33 Y:C1 P:E5 SP:FB PPU: 25, 61 CYC:6949\n$F864:18        CLC                                A:80 X:33 Y:C1 P:E5 SP:F9 PPU: 43, 61 CYC:6955\n$F865:24 01     BIT $01 = #$FF                       A:80 X:33 Y:C1 P:NVUbdIzc SP:F9 PPU: 49, 61 CYC:6957\n$F867:A9 7F     LDA #$7F                           A:80 X:33 Y:C1 P:NVUbdIzc SP:F9 PPU: 58, 61 CYC:6960\n$F869:60        RTS                                A:7F X:33 Y:C1 P:64 SP:F9 PPU: 64, 61 CYC:6962\n$D681:6D 78 06  ADC $0678 = #$80                     A:7F X:33 Y:C1 P:64 SP:FB PPU: 82, 61 CYC:6968\n$D684:20 6A F8  JSR $F86A                          A:FF X:33 Y:C1 P:NvUbdIzc SP:FB PPU: 94, 61 CYC:6972\n$F86A:10 09     BPL $F875                          A:FF X:33 Y:C1 P:NvUbdIzc SP:F9 PPU:112, 61 CYC:6978\n$F86C:B0 07     BCS $F875                          A:FF X:33 Y:C1 P:NvUbdIzc SP:F9 PPU:118, 61 CYC:6980\n$F86E:C9 FF     CMP #$FF                           A:FF X:33 Y:C1 P:NvUbdIzc SP:F9 PPU:124, 61 CYC:6982\n$F870:D0 03     BNE $F875                          A:FF X:33 Y:C1 P:nvUbdIZC SP:F9 PPU:130, 61 CYC:6984\n$F872:70 01     BVS $F875                          A:FF X:33 Y:C1 P:nvUbdIZC SP:F9 PPU:136, 61 CYC:6986\n$F874:60        RTS                                A:FF X:33 Y:C1 P:nvUbdIZC SP:F9 PPU:142, 61 CYC:6988\n$D687:C8        INY                                A:FF X:33 Y:C1 P:nvUbdIZC SP:FB PPU:160, 61 CYC:6994\n$D688:20 78 F8  JSR $F878                          A:FF X:33 Y:C2 P:A5 SP:FB PPU:166, 61 CYC:6996\n$F878:38        SEC                                A:FF X:33 Y:C2 P:A5 SP:F9 PPU:184, 61 CYC:7002\n$F879:B8        CLV                                A:FF X:33 Y:C2 P:A5 SP:F9 PPU:190, 61 CYC:7004\n$F87A:A9 7F     LDA #$7F                           A:FF X:33 Y:C2 P:A5 SP:F9 PPU:196, 61 CYC:7006\n$F87C:60        RTS                                A:7F X:33 Y:C2 P:25 SP:F9 PPU:202, 61 CYC:7008\n$D68B:6D 78 06  ADC $0678 = #$80                     A:7F X:33 Y:C2 P:25 SP:FB PPU:220, 61 CYC:7014\n$D68E:20 7D F8  JSR $F87D                          A:00 X:33 Y:C2 P:nvUbdIZC SP:FB PPU:232, 61 CYC:7018\n$F87D:D0 07     BNE $F886                          A:00 X:33 Y:C2 P:nvUbdIZC SP:F9 PPU:250, 61 CYC:7024\n$F87F:30 05     BMI $F886                          A:00 X:33 Y:C2 P:nvUbdIZC SP:F9 PPU:256, 61 CYC:7026\n$F881:70 03     BVS $F886                          A:00 X:33 Y:C2 P:nvUbdIZC SP:F9 PPU:262, 61 CYC:7028\n$F883:90 01     BCC $F886                          A:00 X:33 Y:C2 P:nvUbdIZC SP:F9 PPU:268, 61 CYC:7030\n$F885:60        RTS                                A:00 X:33 Y:C2 P:nvUbdIZC SP:F9 PPU:274, 61 CYC:7032\n$D691:C8        INY                                A:00 X:33 Y:C2 P:nvUbdIZC SP:FB PPU:292, 61 CYC:7038\n$D692:A9 40     LDA #$40                           A:00 X:33 Y:C3 P:A5 SP:FB PPU:298, 61 CYC:7040\n$D694:8D 78 06  STA $0678 = #$80                     A:40 X:33 Y:C3 P:25 SP:FB PPU:304, 61 CYC:7042\n$D697:20 89 F8  JSR $F889                          A:40 X:33 Y:C3 P:25 SP:FB PPU:316, 61 CYC:7046\n$F889:24 01     BIT $01 = #$FF                       A:40 X:33 Y:C3 P:25 SP:F9 PPU:334, 61 CYC:7052\n$F88B:A9 40     LDA #$40                           A:40 X:33 Y:C3 P:E5 SP:F9 PPU:  2, 62 CYC:7055\n$F88D:60        RTS                                A:40 X:33 Y:C3 P:65 SP:F9 PPU:  8, 62 CYC:7057\n$D69A:CD 78 06  CMP $0678 = #$40                     A:40 X:33 Y:C3 P:65 SP:FB PPU: 26, 62 CYC:7063\n$D69D:20 8E F8  JSR $F88E                          A:40 X:33 Y:C3 P:67 SP:FB PPU: 38, 62 CYC:7067\n$F88E:30 07     BMI $F897                          A:40 X:33 Y:C3 P:67 SP:F9 PPU: 56, 62 CYC:7073\n$F890:90 05     BCC $F897                          A:40 X:33 Y:C3 P:67 SP:F9 PPU: 62, 62 CYC:7075\n$F892:D0 03     BNE $F897                          A:40 X:33 Y:C3 P:67 SP:F9 PPU: 68, 62 CYC:7077\n$F894:50 01     BVC $F897                          A:40 X:33 Y:C3 P:67 SP:F9 PPU: 74, 62 CYC:7079\n$F896:60        RTS                                A:40 X:33 Y:C3 P:67 SP:F9 PPU: 80, 62 CYC:7081\n$D6A0:C8        INY                                A:40 X:33 Y:C3 P:67 SP:FB PPU: 98, 62 CYC:7087\n$D6A1:48        PHA                                A:40 X:33 Y:C4 P:E5 SP:FB PPU:104, 62 CYC:7089\n$D6A2:A9 3F     LDA #$3F                           A:40 X:33 Y:C4 P:E5 SP:FA PPU:113, 62 CYC:7092\n$D6A4:8D 78 06  STA $0678 = #$40                     A:3F X:33 Y:C4 P:65 SP:FA PPU:119, 62 CYC:7094\n$D6A7:68        PLA                                A:3F X:33 Y:C4 P:65 SP:FA PPU:131, 62 CYC:7098\n$D6A8:20 9A F8  JSR $F89A                          A:40 X:33 Y:C4 P:65 SP:FB PPU:143, 62 CYC:7102\n$F89A:B8        CLV                                A:40 X:33 Y:C4 P:65 SP:F9 PPU:161, 62 CYC:7108\n$F89B:60        RTS                                A:40 X:33 Y:C4 P:25 SP:F9 PPU:167, 62 CYC:7110\n$D6AB:CD 78 06  CMP $0678 = #$3F                     A:40 X:33 Y:C4 P:25 SP:FB PPU:185, 62 CYC:7116\n$D6AE:20 9C F8  JSR $F89C                          A:40 X:33 Y:C4 P:25 SP:FB PPU:197, 62 CYC:7120\n$F89C:F0 07     BEQ $F8A5                          A:40 X:33 Y:C4 P:25 SP:F9 PPU:215, 62 CYC:7126\n$F89E:30 05     BMI $F8A5                          A:40 X:33 Y:C4 P:25 SP:F9 PPU:221, 62 CYC:7128\n$F8A0:90 03     BCC $F8A5                          A:40 X:33 Y:C4 P:25 SP:F9 PPU:227, 62 CYC:7130\n$F8A2:70 01     BVS $F8A5                          A:40 X:33 Y:C4 P:25 SP:F9 PPU:233, 62 CYC:7132\n$F8A4:60        RTS                                A:40 X:33 Y:C4 P:25 SP:F9 PPU:239, 62 CYC:7134\n$D6B1:C8        INY                                A:40 X:33 Y:C4 P:25 SP:FB PPU:257, 62 CYC:7140\n$D6B2:48        PHA                                A:40 X:33 Y:C5 P:A5 SP:FB PPU:263, 62 CYC:7142\n$D6B3:A9 41     LDA #$41                           A:40 X:33 Y:C5 P:A5 SP:FA PPU:272, 62 CYC:7145\n$D6B5:8D 78 06  STA $0678 = #$3F                     A:41 X:33 Y:C5 P:25 SP:FA PPU:278, 62 CYC:7147\n$D6B8:68        PLA                                A:41 X:33 Y:C5 P:25 SP:FA PPU:290, 62 CYC:7151\n$D6B9:CD 78 06  CMP $0678 = #$41                     A:40 X:33 Y:C5 P:25 SP:FB PPU:302, 62 CYC:7155\n$D6BC:20 A8 F8  JSR $F8A8                          A:40 X:33 Y:C5 P:NvUbdIzc SP:FB PPU:314, 62 CYC:7159\n$F8A8:F0 05     BEQ $F8AF                          A:40 X:33 Y:C5 P:NvUbdIzc SP:F9 PPU:332, 62 CYC:7165\n$F8AA:10 03     BPL $F8AF                          A:40 X:33 Y:C5 P:NvUbdIzc SP:F9 PPU:338, 62 CYC:7167\n$F8AC:10 01     BPL $F8AF                          A:40 X:33 Y:C5 P:NvUbdIzc SP:F9 PPU:  3, 63 CYC:7169\n$F8AE:60        RTS                                A:40 X:33 Y:C5 P:NvUbdIzc SP:F9 PPU:  9, 63 CYC:7171\n$D6BF:C8        INY                                A:40 X:33 Y:C5 P:NvUbdIzc SP:FB PPU: 27, 63 CYC:7177\n$D6C0:48        PHA                                A:40 X:33 Y:C6 P:NvUbdIzc SP:FB PPU: 33, 63 CYC:7179\n$D6C1:A9 00     LDA #$00                           A:40 X:33 Y:C6 P:NvUbdIzc SP:FA PPU: 42, 63 CYC:7182\n$D6C3:8D 78 06  STA $0678 = #$41                     A:00 X:33 Y:C6 P:nvUbdIZc SP:FA PPU: 48, 63 CYC:7184\n$D6C6:68        PLA                                A:00 X:33 Y:C6 P:nvUbdIZc SP:FA PPU: 60, 63 CYC:7188\n$D6C7:20 B2 F8  JSR $F8B2                          A:40 X:33 Y:C6 P:nvUbdIzc SP:FB PPU: 72, 63 CYC:7192\n$F8B2:A9 80     LDA #$80                           A:40 X:33 Y:C6 P:nvUbdIzc SP:F9 PPU: 90, 63 CYC:7198\n$F8B4:60        RTS                                A:80 X:33 Y:C6 P:NvUbdIzc SP:F9 PPU: 96, 63 CYC:7200\n$D6CA:CD 78 06  CMP $0678 = #$00                     A:80 X:33 Y:C6 P:NvUbdIzc SP:FB PPU:114, 63 CYC:7206\n$D6CD:20 B5 F8  JSR $F8B5                          A:80 X:33 Y:C6 P:A5 SP:FB PPU:126, 63 CYC:7210\n$F8B5:F0 05     BEQ $F8BC                          A:80 X:33 Y:C6 P:A5 SP:F9 PPU:144, 63 CYC:7216\n$F8B7:10 03     BPL $F8BC                          A:80 X:33 Y:C6 P:A5 SP:F9 PPU:150, 63 CYC:7218\n$F8B9:90 01     BCC $F8BC                          A:80 X:33 Y:C6 P:A5 SP:F9 PPU:156, 63 CYC:7220\n$F8BB:60        RTS                                A:80 X:33 Y:C6 P:A5 SP:F9 PPU:162, 63 CYC:7222\n$D6D0:C8        INY                                A:80 X:33 Y:C6 P:A5 SP:FB PPU:180, 63 CYC:7228\n$D6D1:48        PHA                                A:80 X:33 Y:C7 P:A5 SP:FB PPU:186, 63 CYC:7230\n$D6D2:A9 80     LDA #$80                           A:80 X:33 Y:C7 P:A5 SP:FA PPU:195, 63 CYC:7233\n$D6D4:8D 78 06  STA $0678 = #$00                     A:80 X:33 Y:C7 P:A5 SP:FA PPU:201, 63 CYC:7235\n$D6D7:68        PLA                                A:80 X:33 Y:C7 P:A5 SP:FA PPU:213, 63 CYC:7239\n$D6D8:CD 78 06  CMP $0678 = #$80                     A:80 X:33 Y:C7 P:A5 SP:FB PPU:225, 63 CYC:7243\n$D6DB:20 BF F8  JSR $F8BF                          A:80 X:33 Y:C7 P:nvUbdIZC SP:FB PPU:237, 63 CYC:7247\n$F8BF:D0 05     BNE $F8C6                          A:80 X:33 Y:C7 P:nvUbdIZC SP:F9 PPU:255, 63 CYC:7253\n$F8C1:30 03     BMI $F8C6                          A:80 X:33 Y:C7 P:nvUbdIZC SP:F9 PPU:261, 63 CYC:7255\n$F8C3:90 01     BCC $F8C6                          A:80 X:33 Y:C7 P:nvUbdIZC SP:F9 PPU:267, 63 CYC:7257\n$F8C5:60        RTS                                A:80 X:33 Y:C7 P:nvUbdIZC SP:F9 PPU:273, 63 CYC:7259\n$D6DE:C8        INY                                A:80 X:33 Y:C7 P:nvUbdIZC SP:FB PPU:291, 63 CYC:7265\n$D6DF:48        PHA                                A:80 X:33 Y:C8 P:A5 SP:FB PPU:297, 63 CYC:7267\n$D6E0:A9 81     LDA #$81                           A:80 X:33 Y:C8 P:A5 SP:FA PPU:306, 63 CYC:7270\n$D6E2:8D 78 06  STA $0678 = #$80                     A:81 X:33 Y:C8 P:A5 SP:FA PPU:312, 63 CYC:7272\n$D6E5:68        PLA                                A:81 X:33 Y:C8 P:A5 SP:FA PPU:324, 63 CYC:7276\n$D6E6:CD 78 06  CMP $0678 = #$81                     A:80 X:33 Y:C8 P:A5 SP:FB PPU:336, 63 CYC:7280\n$D6E9:20 C9 F8  JSR $F8C9                          A:80 X:33 Y:C8 P:NvUbdIzc SP:FB PPU:  7, 64 CYC:7284\n$F8C9:B0 05     BCS $F8D0                          A:80 X:33 Y:C8 P:NvUbdIzc SP:F9 PPU: 25, 64 CYC:7290\n$F8CB:F0 03     BEQ $F8D0                          A:80 X:33 Y:C8 P:NvUbdIzc SP:F9 PPU: 31, 64 CYC:7292\n$F8CD:10 01     BPL $F8D0                          A:80 X:33 Y:C8 P:NvUbdIzc SP:F9 PPU: 37, 64 CYC:7294\n$F8CF:60        RTS                                A:80 X:33 Y:C8 P:NvUbdIzc SP:F9 PPU: 43, 64 CYC:7296\n$D6EC:C8        INY                                A:80 X:33 Y:C8 P:NvUbdIzc SP:FB PPU: 61, 64 CYC:7302\n$D6ED:48        PHA                                A:80 X:33 Y:C9 P:NvUbdIzc SP:FB PPU: 67, 64 CYC:7304\n$D6EE:A9 7F     LDA #$7F                           A:80 X:33 Y:C9 P:NvUbdIzc SP:FA PPU: 76, 64 CYC:7307\n$D6F0:8D 78 06  STA $0678 = #$81                     A:7F X:33 Y:C9 P:nvUbdIzc SP:FA PPU: 82, 64 CYC:7309\n$D6F3:68        PLA                                A:7F X:33 Y:C9 P:nvUbdIzc SP:FA PPU: 94, 64 CYC:7313\n$D6F4:CD 78 06  CMP $0678 = #$7F                     A:80 X:33 Y:C9 P:NvUbdIzc SP:FB PPU:106, 64 CYC:7317\n$D6F7:20 D3 F8  JSR $F8D3                          A:80 X:33 Y:C9 P:25 SP:FB PPU:118, 64 CYC:7321\n$F8D3:90 05     BCC $F8DA                          A:80 X:33 Y:C9 P:25 SP:F9 PPU:136, 64 CYC:7327\n$F8D5:F0 03     BEQ $F8DA                          A:80 X:33 Y:C9 P:25 SP:F9 PPU:142, 64 CYC:7329\n$F8D7:30 01     BMI $F8DA                          A:80 X:33 Y:C9 P:25 SP:F9 PPU:148, 64 CYC:7331\n$F8D9:60        RTS                                A:80 X:33 Y:C9 P:25 SP:F9 PPU:154, 64 CYC:7333\n$D6FA:C8        INY                                A:80 X:33 Y:C9 P:25 SP:FB PPU:172, 64 CYC:7339\n$D6FB:A9 40     LDA #$40                           A:80 X:33 Y:CA P:A5 SP:FB PPU:178, 64 CYC:7341\n$D6FD:8D 78 06  STA $0678 = #$7F                     A:40 X:33 Y:CA P:25 SP:FB PPU:184, 64 CYC:7343\n$D700:20 31 F9  JSR $F931                          A:40 X:33 Y:CA P:25 SP:FB PPU:196, 64 CYC:7347\n$F931:24 01     BIT $01 = #$FF                       A:40 X:33 Y:CA P:25 SP:F9 PPU:214, 64 CYC:7353\n$F933:A9 40     LDA #$40                           A:40 X:33 Y:CA P:E5 SP:F9 PPU:223, 64 CYC:7356\n$F935:38        SEC                                A:40 X:33 Y:CA P:65 SP:F9 PPU:229, 64 CYC:7358\n$F936:60        RTS                                A:40 X:33 Y:CA P:65 SP:F9 PPU:235, 64 CYC:7360\n$D703:ED 78 06  SBC $0678 = #$40                     A:40 X:33 Y:CA P:65 SP:FB PPU:253, 64 CYC:7366\n$D706:20 37 F9  JSR $F937                          A:00 X:33 Y:CA P:nvUbdIZC SP:FB PPU:265, 64 CYC:7370\n$F937:30 0B     BMI $F944                          A:00 X:33 Y:CA P:nvUbdIZC SP:F9 PPU:283, 64 CYC:7376\n$F939:90 09     BCC $F944                          A:00 X:33 Y:CA P:nvUbdIZC SP:F9 PPU:289, 64 CYC:7378\n$F93B:D0 07     BNE $F944                          A:00 X:33 Y:CA P:nvUbdIZC SP:F9 PPU:295, 64 CYC:7380\n$F93D:70 05     BVS $F944                          A:00 X:33 Y:CA P:nvUbdIZC SP:F9 PPU:301, 64 CYC:7382\n$F93F:C9 00     CMP #$00                           A:00 X:33 Y:CA P:nvUbdIZC SP:F9 PPU:307, 64 CYC:7384\n$F941:D0 01     BNE $F944                          A:00 X:33 Y:CA P:nvUbdIZC SP:F9 PPU:313, 64 CYC:7386\n$F943:60        RTS                                A:00 X:33 Y:CA P:nvUbdIZC SP:F9 PPU:319, 64 CYC:7388\n$D709:C8        INY                                A:00 X:33 Y:CA P:nvUbdIZC SP:FB PPU:337, 64 CYC:7394\n$D70A:A9 3F     LDA #$3F                           A:00 X:33 Y:CB P:A5 SP:FB PPU:  2, 65 CYC:7396\n$D70C:8D 78 06  STA $0678 = #$40                     A:3F X:33 Y:CB P:25 SP:FB PPU:  8, 65 CYC:7398\n$D70F:20 47 F9  JSR $F947                          A:3F X:33 Y:CB P:25 SP:FB PPU: 20, 65 CYC:7402\n$F947:B8        CLV                                A:3F X:33 Y:CB P:25 SP:F9 PPU: 38, 65 CYC:7408\n$F948:38        SEC                                A:3F X:33 Y:CB P:25 SP:F9 PPU: 44, 65 CYC:7410\n$F949:A9 40     LDA #$40                           A:3F X:33 Y:CB P:25 SP:F9 PPU: 50, 65 CYC:7412\n$F94B:60        RTS                                A:40 X:33 Y:CB P:25 SP:F9 PPU: 56, 65 CYC:7414\n$D712:ED 78 06  SBC $0678 = #$3F                     A:40 X:33 Y:CB P:25 SP:FB PPU: 74, 65 CYC:7420\n$D715:20 4C F9  JSR $F94C                          A:01 X:33 Y:CB P:25 SP:FB PPU: 86, 65 CYC:7424\n$F94C:F0 0B     BEQ $F959                          A:01 X:33 Y:CB P:25 SP:F9 PPU:104, 65 CYC:7430\n$F94E:30 09     BMI $F959                          A:01 X:33 Y:CB P:25 SP:F9 PPU:110, 65 CYC:7432\n$F950:90 07     BCC $F959                          A:01 X:33 Y:CB P:25 SP:F9 PPU:116, 65 CYC:7434\n$F952:70 05     BVS $F959                          A:01 X:33 Y:CB P:25 SP:F9 PPU:122, 65 CYC:7436\n$F954:C9 01     CMP #$01                           A:01 X:33 Y:CB P:25 SP:F9 PPU:128, 65 CYC:7438\n$F956:D0 01     BNE $F959                          A:01 X:33 Y:CB P:nvUbdIZC SP:F9 PPU:134, 65 CYC:7440\n$F958:60        RTS                                A:01 X:33 Y:CB P:nvUbdIZC SP:F9 PPU:140, 65 CYC:7442\n$D718:C8        INY                                A:01 X:33 Y:CB P:nvUbdIZC SP:FB PPU:158, 65 CYC:7448\n$D719:A9 41     LDA #$41                           A:01 X:33 Y:CC P:A5 SP:FB PPU:164, 65 CYC:7450\n$D71B:8D 78 06  STA $0678 = #$3F                     A:41 X:33 Y:CC P:25 SP:FB PPU:170, 65 CYC:7452\n$D71E:20 5C F9  JSR $F95C                          A:41 X:33 Y:CC P:25 SP:FB PPU:182, 65 CYC:7456\n$F95C:A9 40     LDA #$40                           A:41 X:33 Y:CC P:25 SP:F9 PPU:200, 65 CYC:7462\n$F95E:38        SEC                                A:40 X:33 Y:CC P:25 SP:F9 PPU:206, 65 CYC:7464\n$F95F:24 01     BIT $01 = #$FF                       A:40 X:33 Y:CC P:25 SP:F9 PPU:212, 65 CYC:7466\n$F961:60        RTS                                A:40 X:33 Y:CC P:E5 SP:F9 PPU:221, 65 CYC:7469\n$D721:ED 78 06  SBC $0678 = #$41                     A:40 X:33 Y:CC P:E5 SP:FB PPU:239, 65 CYC:7475\n$D724:20 62 F9  JSR $F962                          A:FF X:33 Y:CC P:NvUbdIzc SP:FB PPU:251, 65 CYC:7479\n$F962:B0 0B     BCS $F96F                          A:FF X:33 Y:CC P:NvUbdIzc SP:F9 PPU:269, 65 CYC:7485\n$F964:F0 09     BEQ $F96F                          A:FF X:33 Y:CC P:NvUbdIzc SP:F9 PPU:275, 65 CYC:7487\n$F966:10 07     BPL $F96F                          A:FF X:33 Y:CC P:NvUbdIzc SP:F9 PPU:281, 65 CYC:7489\n$F968:70 05     BVS $F96F                          A:FF X:33 Y:CC P:NvUbdIzc SP:F9 PPU:287, 65 CYC:7491\n$F96A:C9 FF     CMP #$FF                           A:FF X:33 Y:CC P:NvUbdIzc SP:F9 PPU:293, 65 CYC:7493\n$F96C:D0 01     BNE $F96F                          A:FF X:33 Y:CC P:nvUbdIZC SP:F9 PPU:299, 65 CYC:7495\n$F96E:60        RTS                                A:FF X:33 Y:CC P:nvUbdIZC SP:F9 PPU:305, 65 CYC:7497\n$D727:C8        INY                                A:FF X:33 Y:CC P:nvUbdIZC SP:FB PPU:323, 65 CYC:7503\n$D728:A9 00     LDA #$00                           A:FF X:33 Y:CD P:A5 SP:FB PPU:329, 65 CYC:7505\n$D72A:8D 78 06  STA $0678 = #$41                     A:00 X:33 Y:CD P:nvUbdIZC SP:FB PPU:335, 65 CYC:7507\n$D72D:20 72 F9  JSR $F972                          A:00 X:33 Y:CD P:nvUbdIZC SP:FB PPU:  6, 66 CYC:7511\n$F972:18        CLC                                A:00 X:33 Y:CD P:nvUbdIZC SP:F9 PPU: 24, 66 CYC:7517\n$F973:A9 80     LDA #$80                           A:00 X:33 Y:CD P:nvUbdIZc SP:F9 PPU: 30, 66 CYC:7519\n$F975:60        RTS                                A:80 X:33 Y:CD P:NvUbdIzc SP:F9 PPU: 36, 66 CYC:7521\n$D730:ED 78 06  SBC $0678 = #$00                     A:80 X:33 Y:CD P:NvUbdIzc SP:FB PPU: 54, 66 CYC:7527\n$D733:20 76 F9  JSR $F976                          A:7F X:33 Y:CD P:65 SP:FB PPU: 66, 66 CYC:7531\n$F976:90 05     BCC $F97D                          A:7F X:33 Y:CD P:65 SP:F9 PPU: 84, 66 CYC:7537\n$F978:C9 7F     CMP #$7F                           A:7F X:33 Y:CD P:65 SP:F9 PPU: 90, 66 CYC:7539\n$F97A:D0 01     BNE $F97D                          A:7F X:33 Y:CD P:67 SP:F9 PPU: 96, 66 CYC:7541\n$F97C:60        RTS                                A:7F X:33 Y:CD P:67 SP:F9 PPU:102, 66 CYC:7543\n$D736:C8        INY                                A:7F X:33 Y:CD P:67 SP:FB PPU:120, 66 CYC:7549\n$D737:A9 7F     LDA #$7F                           A:7F X:33 Y:CE P:E5 SP:FB PPU:126, 66 CYC:7551\n$D739:8D 78 06  STA $0678 = #$00                     A:7F X:33 Y:CE P:65 SP:FB PPU:132, 66 CYC:7553\n$D73C:20 80 F9  JSR $F980                          A:7F X:33 Y:CE P:65 SP:FB PPU:144, 66 CYC:7557\n$F980:38        SEC                                A:7F X:33 Y:CE P:65 SP:F9 PPU:162, 66 CYC:7563\n$F981:A9 81     LDA #$81                           A:7F X:33 Y:CE P:65 SP:F9 PPU:168, 66 CYC:7565\n$F983:60        RTS                                A:81 X:33 Y:CE P:E5 SP:F9 PPU:174, 66 CYC:7567\n$D73F:ED 78 06  SBC $0678 = #$7F                     A:81 X:33 Y:CE P:E5 SP:FB PPU:192, 66 CYC:7573\n$D742:20 84 F9  JSR $F984                          A:02 X:33 Y:CE P:65 SP:FB PPU:204, 66 CYC:7577\n$F984:50 07     BVC $F98D                          A:02 X:33 Y:CE P:65 SP:F9 PPU:222, 66 CYC:7583\n$F986:90 05     BCC $F98D                          A:02 X:33 Y:CE P:65 SP:F9 PPU:228, 66 CYC:7585\n$F988:C9 02     CMP #$02                           A:02 X:33 Y:CE P:65 SP:F9 PPU:234, 66 CYC:7587\n$F98A:D0 01     BNE $F98D                          A:02 X:33 Y:CE P:67 SP:F9 PPU:240, 66 CYC:7589\n$F98C:60        RTS                                A:02 X:33 Y:CE P:67 SP:F9 PPU:246, 66 CYC:7591\n$D745:C8        INY                                A:02 X:33 Y:CE P:67 SP:FB PPU:264, 66 CYC:7597\n$D746:A9 40     LDA #$40                           A:02 X:33 Y:CF P:E5 SP:FB PPU:270, 66 CYC:7599\n$D748:8D 78 06  STA $0678 = #$7F                     A:40 X:33 Y:CF P:65 SP:FB PPU:276, 66 CYC:7601\n$D74B:20 89 F8  JSR $F889                          A:40 X:33 Y:CF P:65 SP:FB PPU:288, 66 CYC:7605\n$F889:24 01     BIT $01 = #$FF                       A:40 X:33 Y:CF P:65 SP:F9 PPU:306, 66 CYC:7611\n$F88B:A9 40     LDA #$40                           A:40 X:33 Y:CF P:E5 SP:F9 PPU:315, 66 CYC:7614\n$F88D:60        RTS                                A:40 X:33 Y:CF P:65 SP:F9 PPU:321, 66 CYC:7616\n$D74E:AA        TAX                                A:40 X:33 Y:CF P:65 SP:FB PPU:339, 66 CYC:7622\n$D74F:EC 78 06  CPX $0678 = #$40                     A:40 X:40 Y:CF P:65 SP:FB PPU:  4, 67 CYC:7624\n$D752:20 8E F8  JSR $F88E                          A:40 X:40 Y:CF P:67 SP:FB PPU: 16, 67 CYC:7628\n$F88E:30 07     BMI $F897                          A:40 X:40 Y:CF P:67 SP:F9 PPU: 34, 67 CYC:7634\n$F890:90 05     BCC $F897                          A:40 X:40 Y:CF P:67 SP:F9 PPU: 40, 67 CYC:7636\n$F892:D0 03     BNE $F897                          A:40 X:40 Y:CF P:67 SP:F9 PPU: 46, 67 CYC:7638\n$F894:50 01     BVC $F897                          A:40 X:40 Y:CF P:67 SP:F9 PPU: 52, 67 CYC:7640\n$F896:60        RTS                                A:40 X:40 Y:CF P:67 SP:F9 PPU: 58, 67 CYC:7642\n$D755:C8        INY                                A:40 X:40 Y:CF P:67 SP:FB PPU: 76, 67 CYC:7648\n$D756:A9 3F     LDA #$3F                           A:40 X:40 Y:D0 P:E5 SP:FB PPU: 82, 67 CYC:7650\n$D758:8D 78 06  STA $0678 = #$40                     A:3F X:40 Y:D0 P:65 SP:FB PPU: 88, 67 CYC:7652\n$D75B:20 9A F8  JSR $F89A                          A:3F X:40 Y:D0 P:65 SP:FB PPU:100, 67 CYC:7656\n$F89A:B8        CLV                                A:3F X:40 Y:D0 P:65 SP:F9 PPU:118, 67 CYC:7662\n$F89B:60        RTS                                A:3F X:40 Y:D0 P:25 SP:F9 PPU:124, 67 CYC:7664\n$D75E:EC 78 06  CPX $0678 = #$3F                     A:3F X:40 Y:D0 P:25 SP:FB PPU:142, 67 CYC:7670\n$D761:20 9C F8  JSR $F89C                          A:3F X:40 Y:D0 P:25 SP:FB PPU:154, 67 CYC:7674\n$F89C:F0 07     BEQ $F8A5                          A:3F X:40 Y:D0 P:25 SP:F9 PPU:172, 67 CYC:7680\n$F89E:30 05     BMI $F8A5                          A:3F X:40 Y:D0 P:25 SP:F9 PPU:178, 67 CYC:7682\n$F8A0:90 03     BCC $F8A5                          A:3F X:40 Y:D0 P:25 SP:F9 PPU:184, 67 CYC:7684\n$F8A2:70 01     BVS $F8A5                          A:3F X:40 Y:D0 P:25 SP:F9 PPU:190, 67 CYC:7686\n$F8A4:60        RTS                                A:3F X:40 Y:D0 P:25 SP:F9 PPU:196, 67 CYC:7688\n$D764:C8        INY                                A:3F X:40 Y:D0 P:25 SP:FB PPU:214, 67 CYC:7694\n$D765:A9 41     LDA #$41                           A:3F X:40 Y:D1 P:A5 SP:FB PPU:220, 67 CYC:7696\n$D767:8D 78 06  STA $0678 = #$3F                     A:41 X:40 Y:D1 P:25 SP:FB PPU:226, 67 CYC:7698\n$D76A:EC 78 06  CPX $0678 = #$41                     A:41 X:40 Y:D1 P:25 SP:FB PPU:238, 67 CYC:7702\n$D76D:20 A8 F8  JSR $F8A8                          A:41 X:40 Y:D1 P:NvUbdIzc SP:FB PPU:250, 67 CYC:7706\n$F8A8:F0 05     BEQ $F8AF                          A:41 X:40 Y:D1 P:NvUbdIzc SP:F9 PPU:268, 67 CYC:7712\n$F8AA:10 03     BPL $F8AF                          A:41 X:40 Y:D1 P:NvUbdIzc SP:F9 PPU:274, 67 CYC:7714\n$F8AC:10 01     BPL $F8AF                          A:41 X:40 Y:D1 P:NvUbdIzc SP:F9 PPU:280, 67 CYC:7716\n$F8AE:60        RTS                                A:41 X:40 Y:D1 P:NvUbdIzc SP:F9 PPU:286, 67 CYC:7718\n$D770:C8        INY                                A:41 X:40 Y:D1 P:NvUbdIzc SP:FB PPU:304, 67 CYC:7724\n$D771:A9 00     LDA #$00                           A:41 X:40 Y:D2 P:NvUbdIzc SP:FB PPU:310, 67 CYC:7726\n$D773:8D 78 06  STA $0678 = #$41                     A:00 X:40 Y:D2 P:nvUbdIZc SP:FB PPU:316, 67 CYC:7728\n$D776:20 B2 F8  JSR $F8B2                          A:00 X:40 Y:D2 P:nvUbdIZc SP:FB PPU:328, 67 CYC:7732\n$F8B2:A9 80     LDA #$80                           A:00 X:40 Y:D2 P:nvUbdIZc SP:F9 PPU:  5, 68 CYC:7738\n$F8B4:60        RTS                                A:80 X:40 Y:D2 P:NvUbdIzc SP:F9 PPU: 11, 68 CYC:7740\n$D779:AA        TAX                                A:80 X:40 Y:D2 P:NvUbdIzc SP:FB PPU: 29, 68 CYC:7746\n$D77A:EC 78 06  CPX $0678 = #$00                     A:80 X:80 Y:D2 P:NvUbdIzc SP:FB PPU: 35, 68 CYC:7748\n$D77D:20 B5 F8  JSR $F8B5                          A:80 X:80 Y:D2 P:A5 SP:FB PPU: 47, 68 CYC:7752\n$F8B5:F0 05     BEQ $F8BC                          A:80 X:80 Y:D2 P:A5 SP:F9 PPU: 65, 68 CYC:7758\n$F8B7:10 03     BPL $F8BC                          A:80 X:80 Y:D2 P:A5 SP:F9 PPU: 71, 68 CYC:7760\n$F8B9:90 01     BCC $F8BC                          A:80 X:80 Y:D2 P:A5 SP:F9 PPU: 77, 68 CYC:7762\n$F8BB:60        RTS                                A:80 X:80 Y:D2 P:A5 SP:F9 PPU: 83, 68 CYC:7764\n$D780:C8        INY                                A:80 X:80 Y:D2 P:A5 SP:FB PPU:101, 68 CYC:7770\n$D781:A9 80     LDA #$80                           A:80 X:80 Y:D3 P:A5 SP:FB PPU:107, 68 CYC:7772\n$D783:8D 78 06  STA $0678 = #$00                     A:80 X:80 Y:D3 P:A5 SP:FB PPU:113, 68 CYC:7774\n$D786:EC 78 06  CPX $0678 = #$80                     A:80 X:80 Y:D3 P:A5 SP:FB PPU:125, 68 CYC:7778\n$D789:20 BF F8  JSR $F8BF                          A:80 X:80 Y:D3 P:nvUbdIZC SP:FB PPU:137, 68 CYC:7782\n$F8BF:D0 05     BNE $F8C6                          A:80 X:80 Y:D3 P:nvUbdIZC SP:F9 PPU:155, 68 CYC:7788\n$F8C1:30 03     BMI $F8C6                          A:80 X:80 Y:D3 P:nvUbdIZC SP:F9 PPU:161, 68 CYC:7790\n$F8C3:90 01     BCC $F8C6                          A:80 X:80 Y:D3 P:nvUbdIZC SP:F9 PPU:167, 68 CYC:7792\n$F8C5:60        RTS                                A:80 X:80 Y:D3 P:nvUbdIZC SP:F9 PPU:173, 68 CYC:7794\n$D78C:C8        INY                                A:80 X:80 Y:D3 P:nvUbdIZC SP:FB PPU:191, 68 CYC:7800\n$D78D:A9 81     LDA #$81                           A:80 X:80 Y:D4 P:A5 SP:FB PPU:197, 68 CYC:7802\n$D78F:8D 78 06  STA $0678 = #$80                     A:81 X:80 Y:D4 P:A5 SP:FB PPU:203, 68 CYC:7804\n$D792:EC 78 06  CPX $0678 = #$81                     A:81 X:80 Y:D4 P:A5 SP:FB PPU:215, 68 CYC:7808\n$D795:20 C9 F8  JSR $F8C9                          A:81 X:80 Y:D4 P:NvUbdIzc SP:FB PPU:227, 68 CYC:7812\n$F8C9:B0 05     BCS $F8D0                          A:81 X:80 Y:D4 P:NvUbdIzc SP:F9 PPU:245, 68 CYC:7818\n$F8CB:F0 03     BEQ $F8D0                          A:81 X:80 Y:D4 P:NvUbdIzc SP:F9 PPU:251, 68 CYC:7820\n$F8CD:10 01     BPL $F8D0                          A:81 X:80 Y:D4 P:NvUbdIzc SP:F9 PPU:257, 68 CYC:7822\n$F8CF:60        RTS                                A:81 X:80 Y:D4 P:NvUbdIzc SP:F9 PPU:263, 68 CYC:7824\n$D798:C8        INY                                A:81 X:80 Y:D4 P:NvUbdIzc SP:FB PPU:281, 68 CYC:7830\n$D799:A9 7F     LDA #$7F                           A:81 X:80 Y:D5 P:NvUbdIzc SP:FB PPU:287, 68 CYC:7832\n$D79B:8D 78 06  STA $0678 = #$81                     A:7F X:80 Y:D5 P:nvUbdIzc SP:FB PPU:293, 68 CYC:7834\n$D79E:EC 78 06  CPX $0678 = #$7F                     A:7F X:80 Y:D5 P:nvUbdIzc SP:FB PPU:305, 68 CYC:7838\n$D7A1:20 D3 F8  JSR $F8D3                          A:7F X:80 Y:D5 P:25 SP:FB PPU:317, 68 CYC:7842\n$F8D3:90 05     BCC $F8DA                          A:7F X:80 Y:D5 P:25 SP:F9 PPU:335, 68 CYC:7848\n$F8D5:F0 03     BEQ $F8DA                          A:7F X:80 Y:D5 P:25 SP:F9 PPU:  0, 69 CYC:7850\n$F8D7:30 01     BMI $F8DA                          A:7F X:80 Y:D5 P:25 SP:F9 PPU:  6, 69 CYC:7852\n$F8D9:60        RTS                                A:7F X:80 Y:D5 P:25 SP:F9 PPU: 12, 69 CYC:7854\n$D7A4:C8        INY                                A:7F X:80 Y:D5 P:25 SP:FB PPU: 30, 69 CYC:7860\n$D7A5:98        TYA                                A:7F X:80 Y:D6 P:A5 SP:FB PPU: 36, 69 CYC:7862\n$D7A6:AA        TAX                                A:D6 X:80 Y:D6 P:A5 SP:FB PPU: 42, 69 CYC:7864\n$D7A7:A9 40     LDA #$40                           A:D6 X:D6 Y:D6 P:A5 SP:FB PPU: 48, 69 CYC:7866\n$D7A9:8D 78 06  STA $0678 = #$7F                     A:40 X:D6 Y:D6 P:25 SP:FB PPU: 54, 69 CYC:7868\n$D7AC:20 DD F8  JSR $F8DD                          A:40 X:D6 Y:D6 P:25 SP:FB PPU: 66, 69 CYC:7872\n$F8DD:24 01     BIT $01 = #$FF                       A:40 X:D6 Y:D6 P:25 SP:F9 PPU: 84, 69 CYC:7878\n$F8DF:A0 40     LDY #$40                           A:40 X:D6 Y:D6 P:E5 SP:F9 PPU: 93, 69 CYC:7881\n$F8E1:60        RTS                                A:40 X:D6 Y:40 P:65 SP:F9 PPU: 99, 69 CYC:7883\n$D7AF:CC 78 06  CPY $0678 = #$40                     A:40 X:D6 Y:40 P:65 SP:FB PPU:117, 69 CYC:7889\n$D7B2:20 E2 F8  JSR $F8E2                          A:40 X:D6 Y:40 P:67 SP:FB PPU:129, 69 CYC:7893\n$F8E2:30 07     BMI $F8EB                          A:40 X:D6 Y:40 P:67 SP:F9 PPU:147, 69 CYC:7899\n$F8E4:90 05     BCC $F8EB                          A:40 X:D6 Y:40 P:67 SP:F9 PPU:153, 69 CYC:7901\n$F8E6:D0 03     BNE $F8EB                          A:40 X:D6 Y:40 P:67 SP:F9 PPU:159, 69 CYC:7903\n$F8E8:50 01     BVC $F8EB                          A:40 X:D6 Y:40 P:67 SP:F9 PPU:165, 69 CYC:7905\n$F8EA:60        RTS                                A:40 X:D6 Y:40 P:67 SP:F9 PPU:171, 69 CYC:7907\n$D7B5:E8        INX                                A:40 X:D6 Y:40 P:67 SP:FB PPU:189, 69 CYC:7913\n$D7B6:A9 3F     LDA #$3F                           A:40 X:D7 Y:40 P:E5 SP:FB PPU:195, 69 CYC:7915\n$D7B8:8D 78 06  STA $0678 = #$40                     A:3F X:D7 Y:40 P:65 SP:FB PPU:201, 69 CYC:7917\n$D7BB:20 EE F8  JSR $F8EE                          A:3F X:D7 Y:40 P:65 SP:FB PPU:213, 69 CYC:7921\n$F8EE:B8        CLV                                A:3F X:D7 Y:40 P:65 SP:F9 PPU:231, 69 CYC:7927\n$F8EF:60        RTS                                A:3F X:D7 Y:40 P:25 SP:F9 PPU:237, 69 CYC:7929\n$D7BE:CC 78 06  CPY $0678 = #$3F                     A:3F X:D7 Y:40 P:25 SP:FB PPU:255, 69 CYC:7935\n$D7C1:20 F0 F8  JSR $F8F0                          A:3F X:D7 Y:40 P:25 SP:FB PPU:267, 69 CYC:7939\n$F8F0:F0 07     BEQ $F8F9                          A:3F X:D7 Y:40 P:25 SP:F9 PPU:285, 69 CYC:7945\n$F8F2:30 05     BMI $F8F9                          A:3F X:D7 Y:40 P:25 SP:F9 PPU:291, 69 CYC:7947\n$F8F4:90 03     BCC $F8F9                          A:3F X:D7 Y:40 P:25 SP:F9 PPU:297, 69 CYC:7949\n$F8F6:70 01     BVS $F8F9                          A:3F X:D7 Y:40 P:25 SP:F9 PPU:303, 69 CYC:7951\n$F8F8:60        RTS                                A:3F X:D7 Y:40 P:25 SP:F9 PPU:309, 69 CYC:7953\n$D7C4:E8        INX                                A:3F X:D7 Y:40 P:25 SP:FB PPU:327, 69 CYC:7959\n$D7C5:A9 41     LDA #$41                           A:3F X:D8 Y:40 P:A5 SP:FB PPU:333, 69 CYC:7961\n$D7C7:8D 78 06  STA $0678 = #$3F                     A:41 X:D8 Y:40 P:25 SP:FB PPU:339, 69 CYC:7963\n$D7CA:CC 78 06  CPY $0678 = #$41                     A:41 X:D8 Y:40 P:25 SP:FB PPU: 10, 70 CYC:7967\n$D7CD:20 FC F8  JSR $F8FC                          A:41 X:D8 Y:40 P:NvUbdIzc SP:FB PPU: 22, 70 CYC:7971\n$F8FC:F0 05     BEQ $F903                          A:41 X:D8 Y:40 P:NvUbdIzc SP:F9 PPU: 40, 70 CYC:7977\n$F8FE:10 03     BPL $F903                          A:41 X:D8 Y:40 P:NvUbdIzc SP:F9 PPU: 46, 70 CYC:7979\n$F900:10 01     BPL $F903                          A:41 X:D8 Y:40 P:NvUbdIzc SP:F9 PPU: 52, 70 CYC:7981\n$F902:60        RTS                                A:41 X:D8 Y:40 P:NvUbdIzc SP:F9 PPU: 58, 70 CYC:7983\n$D7D0:E8        INX                                A:41 X:D8 Y:40 P:NvUbdIzc SP:FB PPU: 76, 70 CYC:7989\n$D7D1:A9 00     LDA #$00                           A:41 X:D9 Y:40 P:NvUbdIzc SP:FB PPU: 82, 70 CYC:7991\n$D7D3:8D 78 06  STA $0678 = #$41                     A:00 X:D9 Y:40 P:nvUbdIZc SP:FB PPU: 88, 70 CYC:7993\n$D7D6:20 06 F9  JSR $F906                          A:00 X:D9 Y:40 P:nvUbdIZc SP:FB PPU:100, 70 CYC:7997\n$F906:A0 80     LDY #$80                           A:00 X:D9 Y:40 P:nvUbdIZc SP:F9 PPU:118, 70 CYC:8003\n$F908:60        RTS                                A:00 X:D9 Y:80 P:NvUbdIzc SP:F9 PPU:124, 70 CYC:8005\n$D7D9:CC 78 06  CPY $0678 = #$00                     A:00 X:D9 Y:80 P:NvUbdIzc SP:FB PPU:142, 70 CYC:8011\n$D7DC:20 09 F9  JSR $F909                          A:00 X:D9 Y:80 P:A5 SP:FB PPU:154, 70 CYC:8015\n$F909:F0 05     BEQ $F910                          A:00 X:D9 Y:80 P:A5 SP:F9 PPU:172, 70 CYC:8021\n$F90B:10 03     BPL $F910                          A:00 X:D9 Y:80 P:A5 SP:F9 PPU:178, 70 CYC:8023\n$F90D:90 01     BCC $F910                          A:00 X:D9 Y:80 P:A5 SP:F9 PPU:184, 70 CYC:8025\n$F90F:60        RTS                                A:00 X:D9 Y:80 P:A5 SP:F9 PPU:190, 70 CYC:8027\n$D7DF:E8        INX                                A:00 X:D9 Y:80 P:A5 SP:FB PPU:208, 70 CYC:8033\n$D7E0:A9 80     LDA #$80                           A:00 X:DA Y:80 P:A5 SP:FB PPU:214, 70 CYC:8035\n$D7E2:8D 78 06  STA $0678 = #$00                     A:80 X:DA Y:80 P:A5 SP:FB PPU:220, 70 CYC:8037\n$D7E5:CC 78 06  CPY $0678 = #$80                     A:80 X:DA Y:80 P:A5 SP:FB PPU:232, 70 CYC:8041\n$D7E8:20 13 F9  JSR $F913                          A:80 X:DA Y:80 P:nvUbdIZC SP:FB PPU:244, 70 CYC:8045\n$F913:D0 05     BNE $F91A                          A:80 X:DA Y:80 P:nvUbdIZC SP:F9 PPU:262, 70 CYC:8051\n$F915:30 03     BMI $F91A                          A:80 X:DA Y:80 P:nvUbdIZC SP:F9 PPU:268, 70 CYC:8053\n$F917:90 01     BCC $F91A                          A:80 X:DA Y:80 P:nvUbdIZC SP:F9 PPU:274, 70 CYC:8055\n$F919:60        RTS                                A:80 X:DA Y:80 P:nvUbdIZC SP:F9 PPU:280, 70 CYC:8057\n$D7EB:E8        INX                                A:80 X:DA Y:80 P:nvUbdIZC SP:FB PPU:298, 70 CYC:8063\n$D7EC:A9 81     LDA #$81                           A:80 X:DB Y:80 P:A5 SP:FB PPU:304, 70 CYC:8065\n$D7EE:8D 78 06  STA $0678 = #$80                     A:81 X:DB Y:80 P:A5 SP:FB PPU:310, 70 CYC:8067\n$D7F1:CC 78 06  CPY $0678 = #$81                     A:81 X:DB Y:80 P:A5 SP:FB PPU:322, 70 CYC:8071\n$D7F4:20 1D F9  JSR $F91D                          A:81 X:DB Y:80 P:NvUbdIzc SP:FB PPU:334, 70 CYC:8075\n$F91D:B0 05     BCS $F924                          A:81 X:DB Y:80 P:NvUbdIzc SP:F9 PPU: 11, 71 CYC:8081\n$F91F:F0 03     BEQ $F924                          A:81 X:DB Y:80 P:NvUbdIzc SP:F9 PPU: 17, 71 CYC:8083\n$F921:10 01     BPL $F924                          A:81 X:DB Y:80 P:NvUbdIzc SP:F9 PPU: 23, 71 CYC:8085\n$F923:60        RTS                                A:81 X:DB Y:80 P:NvUbdIzc SP:F9 PPU: 29, 71 CYC:8087\n$D7F7:E8        INX                                A:81 X:DB Y:80 P:NvUbdIzc SP:FB PPU: 47, 71 CYC:8093\n$D7F8:A9 7F     LDA #$7F                           A:81 X:DC Y:80 P:NvUbdIzc SP:FB PPU: 53, 71 CYC:8095\n$D7FA:8D 78 06  STA $0678 = #$81                     A:7F X:DC Y:80 P:nvUbdIzc SP:FB PPU: 59, 71 CYC:8097\n$D7FD:CC 78 06  CPY $0678 = #$7F                     A:7F X:DC Y:80 P:nvUbdIzc SP:FB PPU: 71, 71 CYC:8101\n$D800:20 27 F9  JSR $F927                          A:7F X:DC Y:80 P:25 SP:FB PPU: 83, 71 CYC:8105\n$F927:90 05     BCC $F92E                          A:7F X:DC Y:80 P:25 SP:F9 PPU:101, 71 CYC:8111\n$F929:F0 03     BEQ $F92E                          A:7F X:DC Y:80 P:25 SP:F9 PPU:107, 71 CYC:8113\n$F92B:30 01     BMI $F92E                          A:7F X:DC Y:80 P:25 SP:F9 PPU:113, 71 CYC:8115\n$F92D:60        RTS                                A:7F X:DC Y:80 P:25 SP:F9 PPU:119, 71 CYC:8117\n$D803:E8        INX                                A:7F X:DC Y:80 P:25 SP:FB PPU:137, 71 CYC:8123\n$D804:8A        TXA                                A:7F X:DD Y:80 P:A5 SP:FB PPU:143, 71 CYC:8125\n$D805:A8        TAY                                A:DD X:DD Y:80 P:A5 SP:FB PPU:149, 71 CYC:8127\n$D806:20 90 F9  JSR $F990                          A:DD X:DD Y:DD P:A5 SP:FB PPU:155, 71 CYC:8129\n$F990:A2 55     LDX #$55                           A:DD X:DD Y:DD P:A5 SP:F9 PPU:173, 71 CYC:8135\n$F992:A9 FF     LDA #$FF                           A:DD X:55 Y:DD P:25 SP:F9 PPU:179, 71 CYC:8137\n$F994:85 01     STA $01 = #$FF                       A:FF X:55 Y:DD P:A5 SP:F9 PPU:185, 71 CYC:8139\n$F996:EA        NOP                                A:FF X:55 Y:DD P:A5 SP:F9 PPU:194, 71 CYC:8142\n$F997:24 01     BIT $01 = #$FF                       A:FF X:55 Y:DD P:A5 SP:F9 PPU:200, 71 CYC:8144\n$F999:38        SEC                                A:FF X:55 Y:DD P:E5 SP:F9 PPU:209, 71 CYC:8147\n$F99A:A9 01     LDA #$01                           A:FF X:55 Y:DD P:E5 SP:F9 PPU:215, 71 CYC:8149\n$F99C:60        RTS                                A:01 X:55 Y:DD P:65 SP:F9 PPU:221, 71 CYC:8151\n$D809:8D 78 06  STA $0678 = #$7F                     A:01 X:55 Y:DD P:65 SP:FB PPU:239, 71 CYC:8157\n$D80C:4E 78 06  LSR $0678 = #$01                     A:01 X:55 Y:DD P:65 SP:FB PPU:251, 71 CYC:8161\n$D80F:AD 78 06  LDA $0678 = #$00                     A:01 X:55 Y:DD P:67 SP:FB PPU:269, 71 CYC:8167\n$D812:20 9D F9  JSR $F99D                          A:00 X:55 Y:DD P:67 SP:FB PPU:281, 71 CYC:8171\n$F99D:90 1B     BCC $F9BA                          A:00 X:55 Y:DD P:67 SP:F9 PPU:299, 71 CYC:8177\n$F99F:D0 19     BNE $F9BA                          A:00 X:55 Y:DD P:67 SP:F9 PPU:305, 71 CYC:8179\n$F9A1:30 17     BMI $F9BA                          A:00 X:55 Y:DD P:67 SP:F9 PPU:311, 71 CYC:8181\n$F9A3:50 15     BVC $F9BA                          A:00 X:55 Y:DD P:67 SP:F9 PPU:317, 71 CYC:8183\n$F9A5:C9 00     CMP #$00                           A:00 X:55 Y:DD P:67 SP:F9 PPU:323, 71 CYC:8185\n$F9A7:D0 11     BNE $F9BA                          A:00 X:55 Y:DD P:67 SP:F9 PPU:329, 71 CYC:8187\n$F9A9:B8        CLV                                A:00 X:55 Y:DD P:67 SP:F9 PPU:335, 71 CYC:8189\n$F9AA:A9 AA     LDA #$AA                           A:00 X:55 Y:DD P:nvUbdIZC SP:F9 PPU:  0, 72 CYC:8191\n$F9AC:60        RTS                                A:AA X:55 Y:DD P:A5 SP:F9 PPU:  6, 72 CYC:8193\n$D815:C8        INY                                A:AA X:55 Y:DD P:A5 SP:FB PPU: 24, 72 CYC:8199\n$D816:8D 78 06  STA $0678 = #$00                     A:AA X:55 Y:DE P:A5 SP:FB PPU: 30, 72 CYC:8201\n$D819:4E 78 06  LSR $0678 = #$AA                     A:AA X:55 Y:DE P:A5 SP:FB PPU: 42, 72 CYC:8205\n$D81C:AD 78 06  LDA $0678 = #$55                     A:AA X:55 Y:DE P:nvUbdIzc SP:FB PPU: 60, 72 CYC:8211\n$D81F:20 AD F9  JSR $F9AD                          A:55 X:55 Y:DE P:nvUbdIzc SP:FB PPU: 72, 72 CYC:8215\n$F9AD:B0 0B     BCS $F9BA                          A:55 X:55 Y:DE P:nvUbdIzc SP:F9 PPU: 90, 72 CYC:8221\n$F9AF:F0 09     BEQ $F9BA                          A:55 X:55 Y:DE P:nvUbdIzc SP:F9 PPU: 96, 72 CYC:8223\n$F9B1:30 07     BMI $F9BA                          A:55 X:55 Y:DE P:nvUbdIzc SP:F9 PPU:102, 72 CYC:8225\n$F9B3:70 05     BVS $F9BA                          A:55 X:55 Y:DE P:nvUbdIzc SP:F9 PPU:108, 72 CYC:8227\n$F9B5:C9 55     CMP #$55                           A:55 X:55 Y:DE P:nvUbdIzc SP:F9 PPU:114, 72 CYC:8229\n$F9B7:D0 01     BNE $F9BA                          A:55 X:55 Y:DE P:nvUbdIZC SP:F9 PPU:120, 72 CYC:8231\n$F9B9:60        RTS                                A:55 X:55 Y:DE P:nvUbdIZC SP:F9 PPU:126, 72 CYC:8233\n$D822:C8        INY                                A:55 X:55 Y:DE P:nvUbdIZC SP:FB PPU:144, 72 CYC:8239\n$D823:20 BD F9  JSR $F9BD                          A:55 X:55 Y:DF P:A5 SP:FB PPU:150, 72 CYC:8241\n$F9BD:24 01     BIT $01 = #$FF                       A:55 X:55 Y:DF P:A5 SP:F9 PPU:168, 72 CYC:8247\n$F9BF:38        SEC                                A:55 X:55 Y:DF P:E5 SP:F9 PPU:177, 72 CYC:8250\n$F9C0:A9 80     LDA #$80                           A:55 X:55 Y:DF P:E5 SP:F9 PPU:183, 72 CYC:8252\n$F9C2:60        RTS                                A:80 X:55 Y:DF P:E5 SP:F9 PPU:189, 72 CYC:8254\n$D826:8D 78 06  STA $0678 = #$55                     A:80 X:55 Y:DF P:E5 SP:FB PPU:207, 72 CYC:8260\n$D829:0E 78 06  ASL $0678 = #$80                     A:80 X:55 Y:DF P:E5 SP:FB PPU:219, 72 CYC:8264\n$D82C:AD 78 06  LDA $0678 = #$00                     A:80 X:55 Y:DF P:67 SP:FB PPU:237, 72 CYC:8270\n$D82F:20 C3 F9  JSR $F9C3                          A:00 X:55 Y:DF P:67 SP:FB PPU:249, 72 CYC:8274\n$F9C3:90 1C     BCC $F9E1                          A:00 X:55 Y:DF P:67 SP:F9 PPU:267, 72 CYC:8280\n$F9C5:D0 1A     BNE $F9E1                          A:00 X:55 Y:DF P:67 SP:F9 PPU:273, 72 CYC:8282\n$F9C7:30 18     BMI $F9E1                          A:00 X:55 Y:DF P:67 SP:F9 PPU:279, 72 CYC:8284\n$F9C9:50 16     BVC $F9E1                          A:00 X:55 Y:DF P:67 SP:F9 PPU:285, 72 CYC:8286\n$F9CB:C9 00     CMP #$00                           A:00 X:55 Y:DF P:67 SP:F9 PPU:291, 72 CYC:8288\n$F9CD:D0 12     BNE $F9E1                          A:00 X:55 Y:DF P:67 SP:F9 PPU:297, 72 CYC:8290\n$F9CF:B8        CLV                                A:00 X:55 Y:DF P:67 SP:F9 PPU:303, 72 CYC:8292\n$F9D0:A9 55     LDA #$55                           A:00 X:55 Y:DF P:nvUbdIZC SP:F9 PPU:309, 72 CYC:8294\n$F9D2:38        SEC                                A:55 X:55 Y:DF P:25 SP:F9 PPU:315, 72 CYC:8296\n$F9D3:60        RTS                                A:55 X:55 Y:DF P:25 SP:F9 PPU:321, 72 CYC:8298\n$D832:C8        INY                                A:55 X:55 Y:DF P:25 SP:FB PPU:339, 72 CYC:8304\n$D833:8D 78 06  STA $0678 = #$00                     A:55 X:55 Y:E0 P:A5 SP:FB PPU:  4, 73 CYC:8306\n$D836:0E 78 06  ASL $0678 = #$55                     A:55 X:55 Y:E0 P:A5 SP:FB PPU: 16, 73 CYC:8310\n$D839:AD 78 06  LDA $0678 = #$AA                     A:55 X:55 Y:E0 P:NvUbdIzc SP:FB PPU: 34, 73 CYC:8316\n$D83C:20 D4 F9  JSR $F9D4                          A:AA X:55 Y:E0 P:NvUbdIzc SP:FB PPU: 46, 73 CYC:8320\n$F9D4:B0 0B     BCS $F9E1                          A:AA X:55 Y:E0 P:NvUbdIzc SP:F9 PPU: 64, 73 CYC:8326\n$F9D6:F0 09     BEQ $F9E1                          A:AA X:55 Y:E0 P:NvUbdIzc SP:F9 PPU: 70, 73 CYC:8328\n$F9D8:10 07     BPL $F9E1                          A:AA X:55 Y:E0 P:NvUbdIzc SP:F9 PPU: 76, 73 CYC:8330\n$F9DA:70 05     BVS $F9E1                          A:AA X:55 Y:E0 P:NvUbdIzc SP:F9 PPU: 82, 73 CYC:8332\n$F9DC:C9 AA     CMP #$AA                           A:AA X:55 Y:E0 P:NvUbdIzc SP:F9 PPU: 88, 73 CYC:8334\n$F9DE:D0 01     BNE $F9E1                          A:AA X:55 Y:E0 P:nvUbdIZC SP:F9 PPU: 94, 73 CYC:8336\n$F9E0:60        RTS                                A:AA X:55 Y:E0 P:nvUbdIZC SP:F9 PPU:100, 73 CYC:8338\n$D83F:C8        INY                                A:AA X:55 Y:E0 P:nvUbdIZC SP:FB PPU:118, 73 CYC:8344\n$D840:20 E4 F9  JSR $F9E4                          A:AA X:55 Y:E1 P:A5 SP:FB PPU:124, 73 CYC:8346\n$F9E4:24 01     BIT $01 = #$FF                       A:AA X:55 Y:E1 P:A5 SP:F9 PPU:142, 73 CYC:8352\n$F9E6:38        SEC                                A:AA X:55 Y:E1 P:E5 SP:F9 PPU:151, 73 CYC:8355\n$F9E7:A9 01     LDA #$01                           A:AA X:55 Y:E1 P:E5 SP:F9 PPU:157, 73 CYC:8357\n$F9E9:60        RTS                                A:01 X:55 Y:E1 P:65 SP:F9 PPU:163, 73 CYC:8359\n$D843:8D 78 06  STA $0678 = #$AA                     A:01 X:55 Y:E1 P:65 SP:FB PPU:181, 73 CYC:8365\n$D846:6E 78 06  ROR $0678 = #$01                     A:01 X:55 Y:E1 P:65 SP:FB PPU:193, 73 CYC:8369\n$D849:AD 78 06  LDA $0678 = #$80                     A:01 X:55 Y:E1 P:E5 SP:FB PPU:211, 73 CYC:8375\n$D84C:20 EA F9  JSR $F9EA                          A:80 X:55 Y:E1 P:E5 SP:FB PPU:223, 73 CYC:8379\n$F9EA:90 1C     BCC $FA08                          A:80 X:55 Y:E1 P:E5 SP:F9 PPU:241, 73 CYC:8385\n$F9EC:F0 1A     BEQ $FA08                          A:80 X:55 Y:E1 P:E5 SP:F9 PPU:247, 73 CYC:8387\n$F9EE:10 18     BPL $FA08                          A:80 X:55 Y:E1 P:E5 SP:F9 PPU:253, 73 CYC:8389\n$F9F0:50 16     BVC $FA08                          A:80 X:55 Y:E1 P:E5 SP:F9 PPU:259, 73 CYC:8391\n$F9F2:C9 80     CMP #$80                           A:80 X:55 Y:E1 P:E5 SP:F9 PPU:265, 73 CYC:8393\n$F9F4:D0 12     BNE $FA08                          A:80 X:55 Y:E1 P:67 SP:F9 PPU:271, 73 CYC:8395\n$F9F6:B8        CLV                                A:80 X:55 Y:E1 P:67 SP:F9 PPU:277, 73 CYC:8397\n$F9F7:18        CLC                                A:80 X:55 Y:E1 P:nvUbdIZC SP:F9 PPU:283, 73 CYC:8399\n$F9F8:A9 55     LDA #$55                           A:80 X:55 Y:E1 P:nvUbdIZc SP:F9 PPU:289, 73 CYC:8401\n$F9FA:60        RTS                                A:55 X:55 Y:E1 P:nvUbdIzc SP:F9 PPU:295, 73 CYC:8403\n$D84F:C8        INY                                A:55 X:55 Y:E1 P:nvUbdIzc SP:FB PPU:313, 73 CYC:8409\n$D850:8D 78 06  STA $0678 = #$80                     A:55 X:55 Y:E2 P:NvUbdIzc SP:FB PPU:319, 73 CYC:8411\n$D853:6E 78 06  ROR $0678 = #$55                     A:55 X:55 Y:E2 P:NvUbdIzc SP:FB PPU:331, 73 CYC:8415\n$D856:AD 78 06  LDA $0678 = #$2A                     A:55 X:55 Y:E2 P:25 SP:FB PPU:  8, 74 CYC:8421\n$D859:20 FB F9  JSR $F9FB                          A:2A X:55 Y:E2 P:25 SP:FB PPU: 20, 74 CYC:8425\n$F9FB:90 0B     BCC $FA08                          A:2A X:55 Y:E2 P:25 SP:F9 PPU: 38, 74 CYC:8431\n$F9FD:F0 09     BEQ $FA08                          A:2A X:55 Y:E2 P:25 SP:F9 PPU: 44, 74 CYC:8433\n$F9FF:30 07     BMI $FA08                          A:2A X:55 Y:E2 P:25 SP:F9 PPU: 50, 74 CYC:8435\n$FA01:70 05     BVS $FA08                          A:2A X:55 Y:E2 P:25 SP:F9 PPU: 56, 74 CYC:8437\n$FA03:C9 2A     CMP #$2A                           A:2A X:55 Y:E2 P:25 SP:F9 PPU: 62, 74 CYC:8439\n$FA05:D0 01     BNE $FA08                          A:2A X:55 Y:E2 P:nvUbdIZC SP:F9 PPU: 68, 74 CYC:8441\n$FA07:60        RTS                                A:2A X:55 Y:E2 P:nvUbdIZC SP:F9 PPU: 74, 74 CYC:8443\n$D85C:C8        INY                                A:2A X:55 Y:E2 P:nvUbdIZC SP:FB PPU: 92, 74 CYC:8449\n$D85D:20 0A FA  JSR $FA0A                          A:2A X:55 Y:E3 P:A5 SP:FB PPU: 98, 74 CYC:8451\n$FA0A:24 01     BIT $01 = #$FF                       A:2A X:55 Y:E3 P:A5 SP:F9 PPU:116, 74 CYC:8457\n$FA0C:38        SEC                                A:2A X:55 Y:E3 P:E5 SP:F9 PPU:125, 74 CYC:8460\n$FA0D:A9 80     LDA #$80                           A:2A X:55 Y:E3 P:E5 SP:F9 PPU:131, 74 CYC:8462\n$FA0F:60        RTS                                A:80 X:55 Y:E3 P:E5 SP:F9 PPU:137, 74 CYC:8464\n$D860:8D 78 06  STA $0678 = #$2A                     A:80 X:55 Y:E3 P:E5 SP:FB PPU:155, 74 CYC:8470\n$D863:2E 78 06  ROL $0678 = #$80                     A:80 X:55 Y:E3 P:E5 SP:FB PPU:167, 74 CYC:8474\n$D866:AD 78 06  LDA $0678 = #$01                     A:80 X:55 Y:E3 P:65 SP:FB PPU:185, 74 CYC:8480\n$D869:20 10 FA  JSR $FA10                          A:01 X:55 Y:E3 P:65 SP:FB PPU:197, 74 CYC:8484\n$FA10:90 1C     BCC $FA2E                          A:01 X:55 Y:E3 P:65 SP:F9 PPU:215, 74 CYC:8490\n$FA12:F0 1A     BEQ $FA2E                          A:01 X:55 Y:E3 P:65 SP:F9 PPU:221, 74 CYC:8492\n$FA14:30 18     BMI $FA2E                          A:01 X:55 Y:E3 P:65 SP:F9 PPU:227, 74 CYC:8494\n$FA16:50 16     BVC $FA2E                          A:01 X:55 Y:E3 P:65 SP:F9 PPU:233, 74 CYC:8496\n$FA18:C9 01     CMP #$01                           A:01 X:55 Y:E3 P:65 SP:F9 PPU:239, 74 CYC:8498\n$FA1A:D0 12     BNE $FA2E                          A:01 X:55 Y:E3 P:67 SP:F9 PPU:245, 74 CYC:8500\n$FA1C:B8        CLV                                A:01 X:55 Y:E3 P:67 SP:F9 PPU:251, 74 CYC:8502\n$FA1D:18        CLC                                A:01 X:55 Y:E3 P:nvUbdIZC SP:F9 PPU:257, 74 CYC:8504\n$FA1E:A9 55     LDA #$55                           A:01 X:55 Y:E3 P:nvUbdIZc SP:F9 PPU:263, 74 CYC:8506\n$FA20:60        RTS                                A:55 X:55 Y:E3 P:nvUbdIzc SP:F9 PPU:269, 74 CYC:8508\n$D86C:C8        INY                                A:55 X:55 Y:E3 P:nvUbdIzc SP:FB PPU:287, 74 CYC:8514\n$D86D:8D 78 06  STA $0678 = #$01                     A:55 X:55 Y:E4 P:NvUbdIzc SP:FB PPU:293, 74 CYC:8516\n$D870:2E 78 06  ROL $0678 = #$55                     A:55 X:55 Y:E4 P:NvUbdIzc SP:FB PPU:305, 74 CYC:8520\n$D873:AD 78 06  LDA $0678 = #$AA                     A:55 X:55 Y:E4 P:NvUbdIzc SP:FB PPU:323, 74 CYC:8526\n$D876:20 21 FA  JSR $FA21                          A:AA X:55 Y:E4 P:NvUbdIzc SP:FB PPU:335, 74 CYC:8530\n$FA21:B0 0B     BCS $FA2E                          A:AA X:55 Y:E4 P:NvUbdIzc SP:F9 PPU: 12, 75 CYC:8536\n$FA23:F0 09     BEQ $FA2E                          A:AA X:55 Y:E4 P:NvUbdIzc SP:F9 PPU: 18, 75 CYC:8538\n$FA25:10 07     BPL $FA2E                          A:AA X:55 Y:E4 P:NvUbdIzc SP:F9 PPU: 24, 75 CYC:8540\n$FA27:70 05     BVS $FA2E                          A:AA X:55 Y:E4 P:NvUbdIzc SP:F9 PPU: 30, 75 CYC:8542\n$FA29:C9 AA     CMP #$AA                           A:AA X:55 Y:E4 P:NvUbdIzc SP:F9 PPU: 36, 75 CYC:8544\n$FA2B:D0 01     BNE $FA2E                          A:AA X:55 Y:E4 P:nvUbdIZC SP:F9 PPU: 42, 75 CYC:8546\n$FA2D:60        RTS                                A:AA X:55 Y:E4 P:nvUbdIZC SP:F9 PPU: 48, 75 CYC:8548\n$D879:A9 FF     LDA #$FF                           A:AA X:55 Y:E4 P:nvUbdIZC SP:FB PPU: 66, 75 CYC:8554\n$D87B:8D 78 06  STA $0678 = #$AA                     A:FF X:55 Y:E4 P:A5 SP:FB PPU: 72, 75 CYC:8556\n$D87E:85 01     STA $01 = #$FF                       A:FF X:55 Y:E4 P:A5 SP:FB PPU: 84, 75 CYC:8560\n$D880:24 01     BIT $01 = #$FF                       A:FF X:55 Y:E4 P:A5 SP:FB PPU: 93, 75 CYC:8563\n$D882:38        SEC                                A:FF X:55 Y:E4 P:E5 SP:FB PPU:102, 75 CYC:8566\n$D883:EE 78 06  INC $0678 = #$FF                     A:FF X:55 Y:E4 P:E5 SP:FB PPU:108, 75 CYC:8568\n$D886:D0 0D     BNE $D895                          A:FF X:55 Y:E4 P:67 SP:FB PPU:126, 75 CYC:8574\n$D888:30 0B     BMI $D895                          A:FF X:55 Y:E4 P:67 SP:FB PPU:132, 75 CYC:8576\n$D88A:50 09     BVC $D895                          A:FF X:55 Y:E4 P:67 SP:FB PPU:138, 75 CYC:8578\n$D88C:90 07     BCC $D895                          A:FF X:55 Y:E4 P:67 SP:FB PPU:144, 75 CYC:8580\n$D88E:AD 78 06  LDA $0678 = #$00                     A:FF X:55 Y:E4 P:67 SP:FB PPU:150, 75 CYC:8582\n$D891:C9 00     CMP #$00                           A:00 X:55 Y:E4 P:67 SP:FB PPU:162, 75 CYC:8586\n$D893:F0 04     BEQ $D899                          A:00 X:55 Y:E4 P:67 SP:FB PPU:168, 75 CYC:8588\n$D899:A9 7F     LDA #$7F                           A:00 X:55 Y:E4 P:67 SP:FB PPU:177, 75 CYC:8591\n$D89B:8D 78 06  STA $0678 = #$00                     A:7F X:55 Y:E4 P:65 SP:FB PPU:183, 75 CYC:8593\n$D89E:B8        CLV                                A:7F X:55 Y:E4 P:65 SP:FB PPU:195, 75 CYC:8597\n$D89F:18        CLC                                A:7F X:55 Y:E4 P:25 SP:FB PPU:201, 75 CYC:8599\n$D8A0:EE 78 06  INC $0678 = #$7F                     A:7F X:55 Y:E4 P:nvUbdIzc SP:FB PPU:207, 75 CYC:8601\n$D8A3:F0 0D     BEQ $D8B2                          A:7F X:55 Y:E4 P:NvUbdIzc SP:FB PPU:225, 75 CYC:8607\n$D8A5:10 0B     BPL $D8B2                          A:7F X:55 Y:E4 P:NvUbdIzc SP:FB PPU:231, 75 CYC:8609\n$D8A7:70 09     BVS $D8B2                          A:7F X:55 Y:E4 P:NvUbdIzc SP:FB PPU:237, 75 CYC:8611\n$D8A9:B0 07     BCS $D8B2                          A:7F X:55 Y:E4 P:NvUbdIzc SP:FB PPU:243, 75 CYC:8613\n$D8AB:AD 78 06  LDA $0678 = #$80                     A:7F X:55 Y:E4 P:NvUbdIzc SP:FB PPU:249, 75 CYC:8615\n$D8AE:C9 80     CMP #$80                           A:80 X:55 Y:E4 P:NvUbdIzc SP:FB PPU:261, 75 CYC:8619\n$D8B0:F0 04     BEQ $D8B6                          A:80 X:55 Y:E4 P:nvUbdIZC SP:FB PPU:267, 75 CYC:8621\n$D8B6:A9 00     LDA #$00                           A:80 X:55 Y:E4 P:nvUbdIZC SP:FB PPU:276, 75 CYC:8624\n$D8B8:8D 78 06  STA $0678 = #$80                     A:00 X:55 Y:E4 P:nvUbdIZC SP:FB PPU:282, 75 CYC:8626\n$D8BB:24 01     BIT $01 = #$FF                       A:00 X:55 Y:E4 P:nvUbdIZC SP:FB PPU:294, 75 CYC:8630\n$D8BD:38        SEC                                A:00 X:55 Y:E4 P:E7 SP:FB PPU:303, 75 CYC:8633\n$D8BE:CE 78 06  DEC $0678 = #$00                     A:00 X:55 Y:E4 P:E7 SP:FB PPU:309, 75 CYC:8635\n$D8C1:F0 0D     BEQ $D8D0                          A:00 X:55 Y:E4 P:E5 SP:FB PPU:327, 75 CYC:8641\n$D8C3:10 0B     BPL $D8D0                          A:00 X:55 Y:E4 P:E5 SP:FB PPU:333, 75 CYC:8643\n$D8C5:50 09     BVC $D8D0                          A:00 X:55 Y:E4 P:E5 SP:FB PPU:339, 75 CYC:8645\n$D8C7:90 07     BCC $D8D0                          A:00 X:55 Y:E4 P:E5 SP:FB PPU:  4, 76 CYC:8647\n$D8C9:AD 78 06  LDA $0678 = #$FF                     A:00 X:55 Y:E4 P:E5 SP:FB PPU: 10, 76 CYC:8649\n$D8CC:C9 FF     CMP #$FF                           A:FF X:55 Y:E4 P:E5 SP:FB PPU: 22, 76 CYC:8653\n$D8CE:F0 04     BEQ $D8D4                          A:FF X:55 Y:E4 P:67 SP:FB PPU: 28, 76 CYC:8655\n$D8D4:A9 80     LDA #$80                           A:FF X:55 Y:E4 P:67 SP:FB PPU: 37, 76 CYC:8658\n$D8D6:8D 78 06  STA $0678 = #$FF                     A:80 X:55 Y:E4 P:E5 SP:FB PPU: 43, 76 CYC:8660\n$D8D9:B8        CLV                                A:80 X:55 Y:E4 P:E5 SP:FB PPU: 55, 76 CYC:8664\n$D8DA:18        CLC                                A:80 X:55 Y:E4 P:A5 SP:FB PPU: 61, 76 CYC:8666\n$D8DB:CE 78 06  DEC $0678 = #$80                     A:80 X:55 Y:E4 P:NvUbdIzc SP:FB PPU: 67, 76 CYC:8668\n$D8DE:F0 0D     BEQ $D8ED                          A:80 X:55 Y:E4 P:nvUbdIzc SP:FB PPU: 85, 76 CYC:8674\n$D8E0:30 0B     BMI $D8ED                          A:80 X:55 Y:E4 P:nvUbdIzc SP:FB PPU: 91, 76 CYC:8676\n$D8E2:70 09     BVS $D8ED                          A:80 X:55 Y:E4 P:nvUbdIzc SP:FB PPU: 97, 76 CYC:8678\n$D8E4:B0 07     BCS $D8ED                          A:80 X:55 Y:E4 P:nvUbdIzc SP:FB PPU:103, 76 CYC:8680\n$D8E6:AD 78 06  LDA $0678 = #$7F                     A:80 X:55 Y:E4 P:nvUbdIzc SP:FB PPU:109, 76 CYC:8682\n$D8E9:C9 7F     CMP #$7F                           A:7F X:55 Y:E4 P:nvUbdIzc SP:FB PPU:121, 76 CYC:8686\n$D8EB:F0 04     BEQ $D8F1                          A:7F X:55 Y:E4 P:nvUbdIZC SP:FB PPU:127, 76 CYC:8688\n$D8F1:A9 01     LDA #$01                           A:7F X:55 Y:E4 P:nvUbdIZC SP:FB PPU:136, 76 CYC:8691\n$D8F3:8D 78 06  STA $0678 = #$7F                     A:01 X:55 Y:E4 P:25 SP:FB PPU:142, 76 CYC:8693\n$D8F6:CE 78 06  DEC $0678 = #$01                     A:01 X:55 Y:E4 P:25 SP:FB PPU:154, 76 CYC:8697\n$D8F9:F0 04     BEQ $D8FF                          A:01 X:55 Y:E4 P:nvUbdIZC SP:FB PPU:172, 76 CYC:8703\n$D8FF:60        RTS                                A:01 X:55 Y:E4 P:nvUbdIZC SP:FB PPU:181, 76 CYC:8706\n$C618:20 00 D9  JSR $D900                          A:01 X:55 Y:E4 P:nvUbdIZC SP:FD PPU:199, 76 CYC:8712\n$D900:A9 A3     LDA #$A3                           A:01 X:55 Y:E4 P:nvUbdIZC SP:FB PPU:217, 76 CYC:8718\n$D902:85 33     STA $33 = #$00                       A:A3 X:55 Y:E4 P:A5 SP:FB PPU:223, 76 CYC:8720\n$D904:A9 89     LDA #$89                           A:A3 X:55 Y:E4 P:A5 SP:FB PPU:232, 76 CYC:8723\n$D906:8D 00 03  STA $0300 = #$70                     A:89 X:55 Y:E4 P:A5 SP:FB PPU:238, 76 CYC:8725\n$D909:A9 12     LDA #$12                           A:89 X:55 Y:E4 P:A5 SP:FB PPU:250, 76 CYC:8729\n$D90B:8D 45 02  STA $0245 = #$00                     A:12 X:55 Y:E4 P:25 SP:FB PPU:256, 76 CYC:8731\n$D90E:A9 FF     LDA #$FF                           A:12 X:55 Y:E4 P:25 SP:FB PPU:268, 76 CYC:8735\n$D910:85 01     STA $01 = #$FF                       A:FF X:55 Y:E4 P:A5 SP:FB PPU:274, 76 CYC:8737\n$D912:A2 65     LDX #$65                           A:FF X:55 Y:E4 P:A5 SP:FB PPU:283, 76 CYC:8740\n$D914:A9 00     LDA #$00                           A:FF X:65 Y:E4 P:25 SP:FB PPU:289, 76 CYC:8742\n$D916:85 89     STA $89 = #$00                       A:00 X:65 Y:E4 P:nvUbdIZC SP:FB PPU:295, 76 CYC:8744\n$D918:A9 03     LDA #$03                           A:00 X:65 Y:E4 P:nvUbdIZC SP:FB PPU:304, 76 CYC:8747\n$D91A:85 8A     STA $8A = #$00                       A:03 X:65 Y:E4 P:25 SP:FB PPU:310, 76 CYC:8749\n$D91C:A0 00     LDY #$00                           A:03 X:65 Y:E4 P:25 SP:FB PPU:319, 76 CYC:8752\n$D91E:38        SEC                                A:03 X:65 Y:00 P:nvUbdIZC SP:FB PPU:325, 76 CYC:8754\n$D91F:A9 00     LDA #$00                           A:03 X:65 Y:00 P:nvUbdIZC SP:FB PPU:331, 76 CYC:8756\n$D921:B8        CLV                                A:00 X:65 Y:00 P:nvUbdIZC SP:FB PPU:337, 76 CYC:8758\n$D922:B1 89     LDA ($89),Y = #$0300 @ 0300 = #$89     A:00 X:65 Y:00 P:nvUbdIZC SP:FB PPU:  2, 77 CYC:8760\n$D924:F0 0C     BEQ $D932                          A:89 X:65 Y:00 P:A5 SP:FB PPU: 17, 77 CYC:8765\n$D926:90 0A     BCC $D932                          A:89 X:65 Y:00 P:A5 SP:FB PPU: 23, 77 CYC:8767\n$D928:70 08     BVS $D932                          A:89 X:65 Y:00 P:A5 SP:FB PPU: 29, 77 CYC:8769\n$D92A:C9 89     CMP #$89                           A:89 X:65 Y:00 P:A5 SP:FB PPU: 35, 77 CYC:8771\n$D92C:D0 04     BNE $D932                          A:89 X:65 Y:00 P:nvUbdIZC SP:FB PPU: 41, 77 CYC:8773\n$D92E:E0 65     CPX #$65                           A:89 X:65 Y:00 P:nvUbdIZC SP:FB PPU: 47, 77 CYC:8775\n$D930:F0 04     BEQ $D936                          A:89 X:65 Y:00 P:nvUbdIZC SP:FB PPU: 53, 77 CYC:8777\n$D936:A9 FF     LDA #$FF                           A:89 X:65 Y:00 P:nvUbdIZC SP:FB PPU: 62, 77 CYC:8780\n$D938:85 97     STA $97 = #$00                       A:FF X:65 Y:00 P:A5 SP:FB PPU: 68, 77 CYC:8782\n$D93A:85 98     STA $98 = #$00                       A:FF X:65 Y:00 P:A5 SP:FB PPU: 77, 77 CYC:8785\n$D93C:24 98     BIT $98 = #$FF                       A:FF X:65 Y:00 P:A5 SP:FB PPU: 86, 77 CYC:8788\n$D93E:A0 34     LDY #$34                           A:FF X:65 Y:00 P:E5 SP:FB PPU: 95, 77 CYC:8791\n$D940:B1 97     LDA ($97),Y = #$FFFF @ 0033 = #$A3     A:FF X:65 Y:34 P:65 SP:FB PPU:101, 77 CYC:8793\n$D942:C9 A3     CMP #$A3                           A:A3 X:65 Y:34 P:E5 SP:FB PPU:119, 77 CYC:8799\n$D944:D0 02     BNE $D948                          A:A3 X:65 Y:34 P:67 SP:FB PPU:125, 77 CYC:8801\n$D946:B0 04     BCS $D94C                          A:A3 X:65 Y:34 P:67 SP:FB PPU:131, 77 CYC:8803\n$D94C:A5 00     LDA $00 = #$00                       A:A3 X:65 Y:34 P:67 SP:FB PPU:140, 77 CYC:8806\n$D94E:48        PHA                                A:00 X:65 Y:34 P:67 SP:FB PPU:149, 77 CYC:8809\n$D94F:A9 46     LDA #$46                           A:00 X:65 Y:34 P:67 SP:FA PPU:158, 77 CYC:8812\n$D951:85 FF     STA $FF = #$00                       A:46 X:65 Y:34 P:65 SP:FA PPU:164, 77 CYC:8814\n$D953:A9 01     LDA #$01                           A:46 X:65 Y:34 P:65 SP:FA PPU:173, 77 CYC:8817\n$D955:85 00     STA $00 = #$00                       A:01 X:65 Y:34 P:65 SP:FA PPU:179, 77 CYC:8819\n$D957:A0 FF     LDY #$FF                           A:01 X:65 Y:34 P:65 SP:FA PPU:188, 77 CYC:8822\n$D959:B1 FF     LDA ($FF),Y = #$0146 @ 0245 = #$12     A:01 X:65 Y:FF P:E5 SP:FA PPU:194, 77 CYC:8824\n$D95B:C9 12     CMP #$12                           A:12 X:65 Y:FF P:65 SP:FA PPU:212, 77 CYC:8830\n$D95D:F0 04     BEQ $D963                          A:12 X:65 Y:FF P:67 SP:FA PPU:218, 77 CYC:8832\n$D963:68        PLA                                A:12 X:65 Y:FF P:67 SP:FA PPU:227, 77 CYC:8835\n$D964:85 00     STA $00 = #$01                       A:00 X:65 Y:FF P:67 SP:FB PPU:239, 77 CYC:8839\n$D966:A2 ED     LDX #$ED                           A:00 X:65 Y:FF P:67 SP:FB PPU:248, 77 CYC:8842\n$D968:A9 00     LDA #$00                           A:00 X:ED Y:FF P:E5 SP:FB PPU:254, 77 CYC:8844\n$D96A:85 33     STA $33 = #$A3                       A:00 X:ED Y:FF P:67 SP:FB PPU:260, 77 CYC:8846\n$D96C:A9 04     LDA #$04                           A:00 X:ED Y:FF P:67 SP:FB PPU:269, 77 CYC:8849\n$D96E:85 34     STA $34 = #$00                       A:04 X:ED Y:FF P:65 SP:FB PPU:275, 77 CYC:8851\n$D970:A0 00     LDY #$00                           A:04 X:ED Y:FF P:65 SP:FB PPU:284, 77 CYC:8854\n$D972:18        CLC                                A:04 X:ED Y:00 P:67 SP:FB PPU:290, 77 CYC:8856\n$D973:A9 FF     LDA #$FF                           A:04 X:ED Y:00 P:nVUbdIZc SP:FB PPU:296, 77 CYC:8858\n$D975:85 01     STA $01 = #$FF                       A:FF X:ED Y:00 P:NVUbdIzc SP:FB PPU:302, 77 CYC:8860\n$D977:24 01     BIT $01 = #$FF                       A:FF X:ED Y:00 P:NVUbdIzc SP:FB PPU:311, 77 CYC:8863\n$D979:A9 AA     LDA #$AA                           A:FF X:ED Y:00 P:NVUbdIzc SP:FB PPU:320, 77 CYC:8866\n$D97B:8D 00 04  STA $0400 = #$AD                     A:AA X:ED Y:00 P:NVUbdIzc SP:FB PPU:326, 77 CYC:8868\n$D97E:A9 55     LDA #$55                           A:AA X:ED Y:00 P:NVUbdIzc SP:FB PPU:338, 77 CYC:8872\n$D980:11 33     ORA ($33),Y = #$0400 @ 0400 = #$AA     A:55 X:ED Y:00 P:64 SP:FB PPU:  3, 78 CYC:8874\n$D982:B0 08     BCS $D98C                          A:FF X:ED Y:00 P:NVUbdIzc SP:FB PPU: 18, 78 CYC:8879\n$D984:10 06     BPL $D98C                          A:FF X:ED Y:00 P:NVUbdIzc SP:FB PPU: 24, 78 CYC:8881\n$D986:C9 FF     CMP #$FF                           A:FF X:ED Y:00 P:NVUbdIzc SP:FB PPU: 30, 78 CYC:8883\n$D988:D0 02     BNE $D98C                          A:FF X:ED Y:00 P:67 SP:FB PPU: 36, 78 CYC:8885\n$D98A:70 02     BVS $D98E                          A:FF X:ED Y:00 P:67 SP:FB PPU: 42, 78 CYC:8887\n$D98E:E8        INX                                A:FF X:ED Y:00 P:67 SP:FB PPU: 51, 78 CYC:8890\n$D98F:38        SEC                                A:FF X:EE Y:00 P:E5 SP:FB PPU: 57, 78 CYC:8892\n$D990:B8        CLV                                A:FF X:EE Y:00 P:E5 SP:FB PPU: 63, 78 CYC:8894\n$D991:A9 00     LDA #$00                           A:FF X:EE Y:00 P:A5 SP:FB PPU: 69, 78 CYC:8896\n$D993:11 33     ORA ($33),Y = #$0400 @ 0400 = #$AA     A:00 X:EE Y:00 P:nvUbdIZC SP:FB PPU: 75, 78 CYC:8898\n$D995:F0 06     BEQ $D99D                          A:AA X:EE Y:00 P:A5 SP:FB PPU: 90, 78 CYC:8903\n$D997:70 04     BVS $D99D                          A:AA X:EE Y:00 P:A5 SP:FB PPU: 96, 78 CYC:8905\n$D999:90 02     BCC $D99D                          A:AA X:EE Y:00 P:A5 SP:FB PPU:102, 78 CYC:8907\n$D99B:30 02     BMI $D99F                          A:AA X:EE Y:00 P:A5 SP:FB PPU:108, 78 CYC:8909\n$D99F:E8        INX                                A:AA X:EE Y:00 P:A5 SP:FB PPU:117, 78 CYC:8912\n$D9A0:18        CLC                                A:AA X:EF Y:00 P:A5 SP:FB PPU:123, 78 CYC:8914\n$D9A1:24 01     BIT $01 = #$FF                       A:AA X:EF Y:00 P:NvUbdIzc SP:FB PPU:129, 78 CYC:8916\n$D9A3:A9 55     LDA #$55                           A:AA X:EF Y:00 P:NVUbdIzc SP:FB PPU:138, 78 CYC:8919\n$D9A5:31 33     AND ($33),Y = #$0400 @ 0400 = #$AA     A:55 X:EF Y:00 P:64 SP:FB PPU:144, 78 CYC:8921\n$D9A7:D0 06     BNE $D9AF                          A:00 X:EF Y:00 P:nVUbdIZc SP:FB PPU:159, 78 CYC:8926\n$D9A9:50 04     BVC $D9AF                          A:00 X:EF Y:00 P:nVUbdIZc SP:FB PPU:165, 78 CYC:8928\n$D9AB:B0 02     BCS $D9AF                          A:00 X:EF Y:00 P:nVUbdIZc SP:FB PPU:171, 78 CYC:8930\n$D9AD:10 02     BPL $D9B1                          A:00 X:EF Y:00 P:nVUbdIZc SP:FB PPU:177, 78 CYC:8932\n$D9B1:E8        INX                                A:00 X:EF Y:00 P:nVUbdIZc SP:FB PPU:186, 78 CYC:8935\n$D9B2:38        SEC                                A:00 X:F0 Y:00 P:NVUbdIzc SP:FB PPU:192, 78 CYC:8937\n$D9B3:B8        CLV                                A:00 X:F0 Y:00 P:E5 SP:FB PPU:198, 78 CYC:8939\n$D9B4:A9 EF     LDA #$EF                           A:00 X:F0 Y:00 P:A5 SP:FB PPU:204, 78 CYC:8941\n$D9B6:8D 00 04  STA $0400 = #$AA                     A:EF X:F0 Y:00 P:A5 SP:FB PPU:210, 78 CYC:8943\n$D9B9:A9 F8     LDA #$F8                           A:EF X:F0 Y:00 P:A5 SP:FB PPU:222, 78 CYC:8947\n$D9BB:31 33     AND ($33),Y = #$0400 @ 0400 = #$EF     A:F8 X:F0 Y:00 P:A5 SP:FB PPU:228, 78 CYC:8949\n$D9BD:90 08     BCC $D9C7                          A:E8 X:F0 Y:00 P:A5 SP:FB PPU:243, 78 CYC:8954\n$D9BF:10 06     BPL $D9C7                          A:E8 X:F0 Y:00 P:A5 SP:FB PPU:249, 78 CYC:8956\n$D9C1:C9 E8     CMP #$E8                           A:E8 X:F0 Y:00 P:A5 SP:FB PPU:255, 78 CYC:8958\n$D9C3:D0 02     BNE $D9C7                          A:E8 X:F0 Y:00 P:nvUbdIZC SP:FB PPU:261, 78 CYC:8960\n$D9C5:50 02     BVC $D9C9                          A:E8 X:F0 Y:00 P:nvUbdIZC SP:FB PPU:267, 78 CYC:8962\n$D9C9:E8        INX                                A:E8 X:F0 Y:00 P:nvUbdIZC SP:FB PPU:276, 78 CYC:8965\n$D9CA:18        CLC                                A:E8 X:F1 Y:00 P:A5 SP:FB PPU:282, 78 CYC:8967\n$D9CB:24 01     BIT $01 = #$FF                       A:E8 X:F1 Y:00 P:NvUbdIzc SP:FB PPU:288, 78 CYC:8969\n$D9CD:A9 AA     LDA #$AA                           A:E8 X:F1 Y:00 P:NVUbdIzc SP:FB PPU:297, 78 CYC:8972\n$D9CF:8D 00 04  STA $0400 = #$EF                     A:AA X:F1 Y:00 P:NVUbdIzc SP:FB PPU:303, 78 CYC:8974\n$D9D2:A9 5F     LDA #$5F                           A:AA X:F1 Y:00 P:NVUbdIzc SP:FB PPU:315, 78 CYC:8978\n$D9D4:51 33     EOR ($33),Y = #$0400 @ 0400 = #$AA     A:5F X:F1 Y:00 P:64 SP:FB PPU:321, 78 CYC:8980\n$D9D6:B0 08     BCS $D9E0                          A:F5 X:F1 Y:00 P:NVUbdIzc SP:FB PPU:336, 78 CYC:8985\n$D9D8:10 06     BPL $D9E0                          A:F5 X:F1 Y:00 P:NVUbdIzc SP:FB PPU:  1, 79 CYC:8987\n$D9DA:C9 F5     CMP #$F5                           A:F5 X:F1 Y:00 P:NVUbdIzc SP:FB PPU:  7, 79 CYC:8989\n$D9DC:D0 02     BNE $D9E0                          A:F5 X:F1 Y:00 P:67 SP:FB PPU: 13, 79 CYC:8991\n$D9DE:70 02     BVS $D9E2                          A:F5 X:F1 Y:00 P:67 SP:FB PPU: 19, 79 CYC:8993\n$D9E2:E8        INX                                A:F5 X:F1 Y:00 P:67 SP:FB PPU: 28, 79 CYC:8996\n$D9E3:38        SEC                                A:F5 X:F2 Y:00 P:E5 SP:FB PPU: 34, 79 CYC:8998\n$D9E4:B8        CLV                                A:F5 X:F2 Y:00 P:E5 SP:FB PPU: 40, 79 CYC:9000\n$D9E5:A9 70     LDA #$70                           A:F5 X:F2 Y:00 P:A5 SP:FB PPU: 46, 79 CYC:9002\n$D9E7:8D 00 04  STA $0400 = #$AA                     A:70 X:F2 Y:00 P:25 SP:FB PPU: 52, 79 CYC:9004\n$D9EA:51 33     EOR ($33),Y = #$0400 @ 0400 = #$70     A:70 X:F2 Y:00 P:25 SP:FB PPU: 64, 79 CYC:9008\n$D9EC:D0 06     BNE $D9F4                          A:00 X:F2 Y:00 P:nvUbdIZC SP:FB PPU: 79, 79 CYC:9013\n$D9EE:70 04     BVS $D9F4                          A:00 X:F2 Y:00 P:nvUbdIZC SP:FB PPU: 85, 79 CYC:9015\n$D9F0:90 02     BCC $D9F4                          A:00 X:F2 Y:00 P:nvUbdIZC SP:FB PPU: 91, 79 CYC:9017\n$D9F2:10 02     BPL $D9F6                          A:00 X:F2 Y:00 P:nvUbdIZC SP:FB PPU: 97, 79 CYC:9019\n$D9F6:E8        INX                                A:00 X:F2 Y:00 P:nvUbdIZC SP:FB PPU:106, 79 CYC:9022\n$D9F7:18        CLC                                A:00 X:F3 Y:00 P:A5 SP:FB PPU:112, 79 CYC:9024\n$D9F8:24 01     BIT $01 = #$FF                       A:00 X:F3 Y:00 P:NvUbdIzc SP:FB PPU:118, 79 CYC:9026\n$D9FA:A9 69     LDA #$69                           A:00 X:F3 Y:00 P:E6 SP:FB PPU:127, 79 CYC:9029\n$D9FC:8D 00 04  STA $0400 = #$70                     A:69 X:F3 Y:00 P:64 SP:FB PPU:133, 79 CYC:9031\n$D9FF:A9 00     LDA #$00                           A:69 X:F3 Y:00 P:64 SP:FB PPU:145, 79 CYC:9035\n$DA01:71 33     ADC ($33),Y = #$0400 @ 0400 = #$69     A:00 X:F3 Y:00 P:nVUbdIZc SP:FB PPU:151, 79 CYC:9037\n$DA03:30 08     BMI $DA0D                          A:69 X:F3 Y:00 P:nvUbdIzc SP:FB PPU:166, 79 CYC:9042\n$DA05:B0 06     BCS $DA0D                          A:69 X:F3 Y:00 P:nvUbdIzc SP:FB PPU:172, 79 CYC:9044\n$DA07:C9 69     CMP #$69                           A:69 X:F3 Y:00 P:nvUbdIzc SP:FB PPU:178, 79 CYC:9046\n$DA09:D0 02     BNE $DA0D                          A:69 X:F3 Y:00 P:nvUbdIZC SP:FB PPU:184, 79 CYC:9048\n$DA0B:50 02     BVC $DA0F                          A:69 X:F3 Y:00 P:nvUbdIZC SP:FB PPU:190, 79 CYC:9050\n$DA0F:E8        INX                                A:69 X:F3 Y:00 P:nvUbdIZC SP:FB PPU:199, 79 CYC:9053\n$DA10:38        SEC                                A:69 X:F4 Y:00 P:A5 SP:FB PPU:205, 79 CYC:9055\n$DA11:24 01     BIT $01 = #$FF                       A:69 X:F4 Y:00 P:A5 SP:FB PPU:211, 79 CYC:9057\n$DA13:A9 00     LDA #$00                           A:69 X:F4 Y:00 P:E5 SP:FB PPU:220, 79 CYC:9060\n$DA15:71 33     ADC ($33),Y = #$0400 @ 0400 = #$69     A:00 X:F4 Y:00 P:67 SP:FB PPU:226, 79 CYC:9062\n$DA17:30 08     BMI $DA21                          A:6A X:F4 Y:00 P:nvUbdIzc SP:FB PPU:241, 79 CYC:9067\n$DA19:B0 06     BCS $DA21                          A:6A X:F4 Y:00 P:nvUbdIzc SP:FB PPU:247, 79 CYC:9069\n$DA1B:C9 6A     CMP #$6A                           A:6A X:F4 Y:00 P:nvUbdIzc SP:FB PPU:253, 79 CYC:9071\n$DA1D:D0 02     BNE $DA21                          A:6A X:F4 Y:00 P:nvUbdIZC SP:FB PPU:259, 79 CYC:9073\n$DA1F:50 02     BVC $DA23                          A:6A X:F4 Y:00 P:nvUbdIZC SP:FB PPU:265, 79 CYC:9075\n$DA23:E8        INX                                A:6A X:F4 Y:00 P:nvUbdIZC SP:FB PPU:274, 79 CYC:9078\n$DA24:38        SEC                                A:6A X:F5 Y:00 P:A5 SP:FB PPU:280, 79 CYC:9080\n$DA25:B8        CLV                                A:6A X:F5 Y:00 P:A5 SP:FB PPU:286, 79 CYC:9082\n$DA26:A9 7F     LDA #$7F                           A:6A X:F5 Y:00 P:A5 SP:FB PPU:292, 79 CYC:9084\n$DA28:8D 00 04  STA $0400 = #$69                     A:7F X:F5 Y:00 P:25 SP:FB PPU:298, 79 CYC:9086\n$DA2B:71 33     ADC ($33),Y = #$0400 @ 0400 = #$7F     A:7F X:F5 Y:00 P:25 SP:FB PPU:310, 79 CYC:9090\n$DA2D:10 08     BPL $DA37                          A:FF X:F5 Y:00 P:NVUbdIzc SP:FB PPU:325, 79 CYC:9095\n$DA2F:B0 06     BCS $DA37                          A:FF X:F5 Y:00 P:NVUbdIzc SP:FB PPU:331, 79 CYC:9097\n$DA31:C9 FF     CMP #$FF                           A:FF X:F5 Y:00 P:NVUbdIzc SP:FB PPU:337, 79 CYC:9099\n$DA33:D0 02     BNE $DA37                          A:FF X:F5 Y:00 P:67 SP:FB PPU:  2, 80 CYC:9101\n$DA35:70 02     BVS $DA39                          A:FF X:F5 Y:00 P:67 SP:FB PPU:  8, 80 CYC:9103\n$DA39:E8        INX                                A:FF X:F5 Y:00 P:67 SP:FB PPU: 17, 80 CYC:9106\n$DA3A:18        CLC                                A:FF X:F6 Y:00 P:E5 SP:FB PPU: 23, 80 CYC:9108\n$DA3B:24 01     BIT $01 = #$FF                       A:FF X:F6 Y:00 P:NVUbdIzc SP:FB PPU: 29, 80 CYC:9110\n$DA3D:A9 80     LDA #$80                           A:FF X:F6 Y:00 P:NVUbdIzc SP:FB PPU: 38, 80 CYC:9113\n$DA3F:8D 00 04  STA $0400 = #$7F                     A:80 X:F6 Y:00 P:NVUbdIzc SP:FB PPU: 44, 80 CYC:9115\n$DA42:A9 7F     LDA #$7F                           A:80 X:F6 Y:00 P:NVUbdIzc SP:FB PPU: 56, 80 CYC:9119\n$DA44:71 33     ADC ($33),Y = #$0400 @ 0400 = #$80     A:7F X:F6 Y:00 P:64 SP:FB PPU: 62, 80 CYC:9121\n$DA46:10 08     BPL $DA50                          A:FF X:F6 Y:00 P:NvUbdIzc SP:FB PPU: 77, 80 CYC:9126\n$DA48:B0 06     BCS $DA50                          A:FF X:F6 Y:00 P:NvUbdIzc SP:FB PPU: 83, 80 CYC:9128\n$DA4A:C9 FF     CMP #$FF                           A:FF X:F6 Y:00 P:NvUbdIzc SP:FB PPU: 89, 80 CYC:9130\n$DA4C:D0 02     BNE $DA50                          A:FF X:F6 Y:00 P:nvUbdIZC SP:FB PPU: 95, 80 CYC:9132\n$DA4E:50 02     BVC $DA52                          A:FF X:F6 Y:00 P:nvUbdIZC SP:FB PPU:101, 80 CYC:9134\n$DA52:E8        INX                                A:FF X:F6 Y:00 P:nvUbdIZC SP:FB PPU:110, 80 CYC:9137\n$DA53:38        SEC                                A:FF X:F7 Y:00 P:A5 SP:FB PPU:116, 80 CYC:9139\n$DA54:B8        CLV                                A:FF X:F7 Y:00 P:A5 SP:FB PPU:122, 80 CYC:9141\n$DA55:A9 80     LDA #$80                           A:FF X:F7 Y:00 P:A5 SP:FB PPU:128, 80 CYC:9143\n$DA57:8D 00 04  STA $0400 = #$80                     A:80 X:F7 Y:00 P:A5 SP:FB PPU:134, 80 CYC:9145\n$DA5A:A9 7F     LDA #$7F                           A:80 X:F7 Y:00 P:A5 SP:FB PPU:146, 80 CYC:9149\n$DA5C:71 33     ADC ($33),Y = #$0400 @ 0400 = #$80     A:7F X:F7 Y:00 P:25 SP:FB PPU:152, 80 CYC:9151\n$DA5E:D0 06     BNE $DA66                          A:00 X:F7 Y:00 P:nvUbdIZC SP:FB PPU:167, 80 CYC:9156\n$DA60:30 04     BMI $DA66                          A:00 X:F7 Y:00 P:nvUbdIZC SP:FB PPU:173, 80 CYC:9158\n$DA62:70 02     BVS $DA66                          A:00 X:F7 Y:00 P:nvUbdIZC SP:FB PPU:179, 80 CYC:9160\n$DA64:B0 02     BCS $DA68                          A:00 X:F7 Y:00 P:nvUbdIZC SP:FB PPU:185, 80 CYC:9162\n$DA68:E8        INX                                A:00 X:F7 Y:00 P:nvUbdIZC SP:FB PPU:194, 80 CYC:9165\n$DA69:24 01     BIT $01 = #$FF                       A:00 X:F8 Y:00 P:A5 SP:FB PPU:200, 80 CYC:9167\n$DA6B:A9 40     LDA #$40                           A:00 X:F8 Y:00 P:E7 SP:FB PPU:209, 80 CYC:9170\n$DA6D:8D 00 04  STA $0400 = #$80                     A:40 X:F8 Y:00 P:65 SP:FB PPU:215, 80 CYC:9172\n$DA70:D1 33     CMP ($33),Y = #$0400 @ 0400 = #$40     A:40 X:F8 Y:00 P:65 SP:FB PPU:227, 80 CYC:9176\n$DA72:30 06     BMI $DA7A                          A:40 X:F8 Y:00 P:67 SP:FB PPU:242, 80 CYC:9181\n$DA74:90 04     BCC $DA7A                          A:40 X:F8 Y:00 P:67 SP:FB PPU:248, 80 CYC:9183\n$DA76:D0 02     BNE $DA7A                          A:40 X:F8 Y:00 P:67 SP:FB PPU:254, 80 CYC:9185\n$DA78:70 02     BVS $DA7C                          A:40 X:F8 Y:00 P:67 SP:FB PPU:260, 80 CYC:9187\n$DA7C:E8        INX                                A:40 X:F8 Y:00 P:67 SP:FB PPU:269, 80 CYC:9190\n$DA7D:B8        CLV                                A:40 X:F9 Y:00 P:E5 SP:FB PPU:275, 80 CYC:9192\n$DA7E:CE 00 04  DEC $0400 = #$40                     A:40 X:F9 Y:00 P:A5 SP:FB PPU:281, 80 CYC:9194\n$DA81:D1 33     CMP ($33),Y = #$0400 @ 0400 = #$3F     A:40 X:F9 Y:00 P:25 SP:FB PPU:299, 80 CYC:9200\n$DA83:F0 06     BEQ $DA8B                          A:40 X:F9 Y:00 P:25 SP:FB PPU:314, 80 CYC:9205\n$DA85:30 04     BMI $DA8B                          A:40 X:F9 Y:00 P:25 SP:FB PPU:320, 80 CYC:9207\n$DA87:90 02     BCC $DA8B                          A:40 X:F9 Y:00 P:25 SP:FB PPU:326, 80 CYC:9209\n$DA89:50 02     BVC $DA8D                          A:40 X:F9 Y:00 P:25 SP:FB PPU:332, 80 CYC:9211\n$DA8D:E8        INX                                A:40 X:F9 Y:00 P:25 SP:FB PPU:  0, 81 CYC:9214\n$DA8E:EE 00 04  INC $0400 = #$3F                     A:40 X:FA Y:00 P:A5 SP:FB PPU:  6, 81 CYC:9216\n$DA91:EE 00 04  INC $0400 = #$40                     A:40 X:FA Y:00 P:25 SP:FB PPU: 24, 81 CYC:9222\n$DA94:D1 33     CMP ($33),Y = #$0400 @ 0400 = #$41     A:40 X:FA Y:00 P:25 SP:FB PPU: 42, 81 CYC:9228\n$DA96:F0 02     BEQ $DA9A                          A:40 X:FA Y:00 P:NvUbdIzc SP:FB PPU: 57, 81 CYC:9233\n$DA98:30 02     BMI $DA9C                          A:40 X:FA Y:00 P:NvUbdIzc SP:FB PPU: 63, 81 CYC:9235\n$DA9C:E8        INX                                A:40 X:FA Y:00 P:NvUbdIzc SP:FB PPU: 72, 81 CYC:9238\n$DA9D:A9 00     LDA #$00                           A:40 X:FB Y:00 P:NvUbdIzc SP:FB PPU: 78, 81 CYC:9240\n$DA9F:8D 00 04  STA $0400 = #$41                     A:00 X:FB Y:00 P:nvUbdIZc SP:FB PPU: 84, 81 CYC:9242\n$DAA2:A9 80     LDA #$80                           A:00 X:FB Y:00 P:nvUbdIZc SP:FB PPU: 96, 81 CYC:9246\n$DAA4:D1 33     CMP ($33),Y = #$0400 @ 0400 = #$00     A:80 X:FB Y:00 P:NvUbdIzc SP:FB PPU:102, 81 CYC:9248\n$DAA6:F0 04     BEQ $DAAC                          A:80 X:FB Y:00 P:A5 SP:FB PPU:117, 81 CYC:9253\n$DAA8:10 02     BPL $DAAC                          A:80 X:FB Y:00 P:A5 SP:FB PPU:123, 81 CYC:9255\n$DAAA:B0 02     BCS $DAAE                          A:80 X:FB Y:00 P:A5 SP:FB PPU:129, 81 CYC:9257\n$DAAE:E8        INX                                A:80 X:FB Y:00 P:A5 SP:FB PPU:138, 81 CYC:9260\n$DAAF:A0 80     LDY #$80                           A:80 X:FC Y:00 P:A5 SP:FB PPU:144, 81 CYC:9262\n$DAB1:8C 00 04  STY $0400 = #$00                     A:80 X:FC Y:80 P:A5 SP:FB PPU:150, 81 CYC:9264\n$DAB4:A0 00     LDY #$00                           A:80 X:FC Y:80 P:A5 SP:FB PPU:162, 81 CYC:9268\n$DAB6:D1 33     CMP ($33),Y = #$0400 @ 0400 = #$80     A:80 X:FC Y:00 P:nvUbdIZC SP:FB PPU:168, 81 CYC:9270\n$DAB8:D0 04     BNE $DABE                          A:80 X:FC Y:00 P:nvUbdIZC SP:FB PPU:183, 81 CYC:9275\n$DABA:30 02     BMI $DABE                          A:80 X:FC Y:00 P:nvUbdIZC SP:FB PPU:189, 81 CYC:9277\n$DABC:B0 02     BCS $DAC0                          A:80 X:FC Y:00 P:nvUbdIZC SP:FB PPU:195, 81 CYC:9279\n$DAC0:E8        INX                                A:80 X:FC Y:00 P:nvUbdIZC SP:FB PPU:204, 81 CYC:9282\n$DAC1:EE 00 04  INC $0400 = #$80                     A:80 X:FD Y:00 P:A5 SP:FB PPU:210, 81 CYC:9284\n$DAC4:D1 33     CMP ($33),Y = #$0400 @ 0400 = #$81     A:80 X:FD Y:00 P:A5 SP:FB PPU:228, 81 CYC:9290\n$DAC6:B0 04     BCS $DACC                          A:80 X:FD Y:00 P:NvUbdIzc SP:FB PPU:243, 81 CYC:9295\n$DAC8:F0 02     BEQ $DACC                          A:80 X:FD Y:00 P:NvUbdIzc SP:FB PPU:249, 81 CYC:9297\n$DACA:30 02     BMI $DACE                          A:80 X:FD Y:00 P:NvUbdIzc SP:FB PPU:255, 81 CYC:9299\n$DACE:E8        INX                                A:80 X:FD Y:00 P:NvUbdIzc SP:FB PPU:264, 81 CYC:9302\n$DACF:CE 00 04  DEC $0400 = #$81                     A:80 X:FE Y:00 P:NvUbdIzc SP:FB PPU:270, 81 CYC:9304\n$DAD2:CE 00 04  DEC $0400 = #$80                     A:80 X:FE Y:00 P:NvUbdIzc SP:FB PPU:288, 81 CYC:9310\n$DAD5:D1 33     CMP ($33),Y = #$0400 @ 0400 = #$7F     A:80 X:FE Y:00 P:nvUbdIzc SP:FB PPU:306, 81 CYC:9316\n$DAD7:90 04     BCC $DADD                          A:80 X:FE Y:00 P:25 SP:FB PPU:321, 81 CYC:9321\n$DAD9:F0 02     BEQ $DADD                          A:80 X:FE Y:00 P:25 SP:FB PPU:327, 81 CYC:9323\n$DADB:10 02     BPL $DADF                          A:80 X:FE Y:00 P:25 SP:FB PPU:333, 81 CYC:9325\n$DADF:60        RTS                                A:80 X:FE Y:00 P:25 SP:FB PPU:  1, 82 CYC:9328\n$C61B:A5 00     LDA $00 = #$00                       A:80 X:FE Y:00 P:25 SP:FD PPU: 19, 82 CYC:9334\n$C61D:85 10     STA $10 = #$00                       A:00 X:FE Y:00 P:nvUbdIZC SP:FD PPU: 28, 82 CYC:9337\n$C61F:A9 00     LDA #$00                           A:00 X:FE Y:00 P:nvUbdIZC SP:FD PPU: 37, 82 CYC:9340\n$C621:85 00     STA $00 = #$00                       A:00 X:FE Y:00 P:nvUbdIZC SP:FD PPU: 43, 82 CYC:9342\n$C623:20 E0 DA  JSR $DAE0                          A:00 X:FE Y:00 P:nvUbdIZC SP:FD PPU: 52, 82 CYC:9345\n$DAE0:A9 00     LDA #$00                           A:00 X:FE Y:00 P:nvUbdIZC SP:FB PPU: 70, 82 CYC:9351\n$DAE2:85 33     STA $33 = #$00                       A:00 X:FE Y:00 P:nvUbdIZC SP:FB PPU: 76, 82 CYC:9353\n$DAE4:A9 04     LDA #$04                           A:00 X:FE Y:00 P:nvUbdIZC SP:FB PPU: 85, 82 CYC:9356\n$DAE6:85 34     STA $34 = #$04                       A:04 X:FE Y:00 P:25 SP:FB PPU: 91, 82 CYC:9358\n$DAE8:A0 00     LDY #$00                           A:04 X:FE Y:00 P:25 SP:FB PPU:100, 82 CYC:9361\n$DAEA:A2 01     LDX #$01                           A:04 X:FE Y:00 P:nvUbdIZC SP:FB PPU:106, 82 CYC:9363\n$DAEC:24 01     BIT $01 = #$FF                       A:04 X:01 Y:00 P:25 SP:FB PPU:112, 82 CYC:9365\n$DAEE:A9 40     LDA #$40                           A:04 X:01 Y:00 P:E5 SP:FB PPU:121, 82 CYC:9368\n$DAF0:8D 00 04  STA $0400 = #$7F                     A:40 X:01 Y:00 P:65 SP:FB PPU:127, 82 CYC:9370\n$DAF3:38        SEC                                A:40 X:01 Y:00 P:65 SP:FB PPU:139, 82 CYC:9374\n$DAF4:F1 33     SBC ($33),Y = #$0400 @ 0400 = #$40     A:40 X:01 Y:00 P:65 SP:FB PPU:145, 82 CYC:9376\n$DAF6:30 0A     BMI $DB02                          A:00 X:01 Y:00 P:nvUbdIZC SP:FB PPU:160, 82 CYC:9381\n$DAF8:90 08     BCC $DB02                          A:00 X:01 Y:00 P:nvUbdIZC SP:FB PPU:166, 82 CYC:9383\n$DAFA:D0 06     BNE $DB02                          A:00 X:01 Y:00 P:nvUbdIZC SP:FB PPU:172, 82 CYC:9385\n$DAFC:70 04     BVS $DB02                          A:00 X:01 Y:00 P:nvUbdIZC SP:FB PPU:178, 82 CYC:9387\n$DAFE:C9 00     CMP #$00                           A:00 X:01 Y:00 P:nvUbdIZC SP:FB PPU:184, 82 CYC:9389\n$DB00:F0 02     BEQ $DB04                          A:00 X:01 Y:00 P:nvUbdIZC SP:FB PPU:190, 82 CYC:9391\n$DB04:E8        INX                                A:00 X:01 Y:00 P:nvUbdIZC SP:FB PPU:199, 82 CYC:9394\n$DB05:B8        CLV                                A:00 X:02 Y:00 P:25 SP:FB PPU:205, 82 CYC:9396\n$DB06:38        SEC                                A:00 X:02 Y:00 P:25 SP:FB PPU:211, 82 CYC:9398\n$DB07:A9 40     LDA #$40                           A:00 X:02 Y:00 P:25 SP:FB PPU:217, 82 CYC:9400\n$DB09:CE 00 04  DEC $0400 = #$40                     A:40 X:02 Y:00 P:25 SP:FB PPU:223, 82 CYC:9402\n$DB0C:F1 33     SBC ($33),Y = #$0400 @ 0400 = #$3F     A:40 X:02 Y:00 P:25 SP:FB PPU:241, 82 CYC:9408\n$DB0E:F0 0A     BEQ $DB1A                          A:01 X:02 Y:00 P:25 SP:FB PPU:256, 82 CYC:9413\n$DB10:30 08     BMI $DB1A                          A:01 X:02 Y:00 P:25 SP:FB PPU:262, 82 CYC:9415\n$DB12:90 06     BCC $DB1A                          A:01 X:02 Y:00 P:25 SP:FB PPU:268, 82 CYC:9417\n$DB14:70 04     BVS $DB1A                          A:01 X:02 Y:00 P:25 SP:FB PPU:274, 82 CYC:9419\n$DB16:C9 01     CMP #$01                           A:01 X:02 Y:00 P:25 SP:FB PPU:280, 82 CYC:9421\n$DB18:F0 02     BEQ $DB1C                          A:01 X:02 Y:00 P:nvUbdIZC SP:FB PPU:286, 82 CYC:9423\n$DB1C:E8        INX                                A:01 X:02 Y:00 P:nvUbdIZC SP:FB PPU:295, 82 CYC:9426\n$DB1D:A9 40     LDA #$40                           A:01 X:03 Y:00 P:25 SP:FB PPU:301, 82 CYC:9428\n$DB1F:38        SEC                                A:40 X:03 Y:00 P:25 SP:FB PPU:307, 82 CYC:9430\n$DB20:24 01     BIT $01 = #$FF                       A:40 X:03 Y:00 P:25 SP:FB PPU:313, 82 CYC:9432\n$DB22:EE 00 04  INC $0400 = #$3F                     A:40 X:03 Y:00 P:E5 SP:FB PPU:322, 82 CYC:9435\n$DB25:EE 00 04  INC $0400 = #$40                     A:40 X:03 Y:00 P:65 SP:FB PPU:340, 82 CYC:9441\n$DB28:F1 33     SBC ($33),Y = #$0400 @ 0400 = #$41     A:40 X:03 Y:00 P:65 SP:FB PPU: 17, 83 CYC:9447\n$DB2A:B0 0A     BCS $DB36                          A:FF X:03 Y:00 P:NvUbdIzc SP:FB PPU: 32, 83 CYC:9452\n$DB2C:F0 08     BEQ $DB36                          A:FF X:03 Y:00 P:NvUbdIzc SP:FB PPU: 38, 83 CYC:9454\n$DB2E:10 06     BPL $DB36                          A:FF X:03 Y:00 P:NvUbdIzc SP:FB PPU: 44, 83 CYC:9456\n$DB30:70 04     BVS $DB36                          A:FF X:03 Y:00 P:NvUbdIzc SP:FB PPU: 50, 83 CYC:9458\n$DB32:C9 FF     CMP #$FF                           A:FF X:03 Y:00 P:NvUbdIzc SP:FB PPU: 56, 83 CYC:9460\n$DB34:F0 02     BEQ $DB38                          A:FF X:03 Y:00 P:nvUbdIZC SP:FB PPU: 62, 83 CYC:9462\n$DB38:E8        INX                                A:FF X:03 Y:00 P:nvUbdIZC SP:FB PPU: 71, 83 CYC:9465\n$DB39:18        CLC                                A:FF X:04 Y:00 P:25 SP:FB PPU: 77, 83 CYC:9467\n$DB3A:A9 00     LDA #$00                           A:FF X:04 Y:00 P:nvUbdIzc SP:FB PPU: 83, 83 CYC:9469\n$DB3C:8D 00 04  STA $0400 = #$41                     A:00 X:04 Y:00 P:nvUbdIZc SP:FB PPU: 89, 83 CYC:9471\n$DB3F:A9 80     LDA #$80                           A:00 X:04 Y:00 P:nvUbdIZc SP:FB PPU:101, 83 CYC:9475\n$DB41:F1 33     SBC ($33),Y = #$0400 @ 0400 = #$00     A:80 X:04 Y:00 P:NvUbdIzc SP:FB PPU:107, 83 CYC:9477\n$DB43:90 04     BCC $DB49                          A:7F X:04 Y:00 P:65 SP:FB PPU:122, 83 CYC:9482\n$DB45:C9 7F     CMP #$7F                           A:7F X:04 Y:00 P:65 SP:FB PPU:128, 83 CYC:9484\n$DB47:F0 02     BEQ $DB4B                          A:7F X:04 Y:00 P:67 SP:FB PPU:134, 83 CYC:9486\n$DB4B:E8        INX                                A:7F X:04 Y:00 P:67 SP:FB PPU:143, 83 CYC:9489\n$DB4C:38        SEC                                A:7F X:05 Y:00 P:65 SP:FB PPU:149, 83 CYC:9491\n$DB4D:A9 7F     LDA #$7F                           A:7F X:05 Y:00 P:65 SP:FB PPU:155, 83 CYC:9493\n$DB4F:8D 00 04  STA $0400 = #$00                     A:7F X:05 Y:00 P:65 SP:FB PPU:161, 83 CYC:9495\n$DB52:A9 81     LDA #$81                           A:7F X:05 Y:00 P:65 SP:FB PPU:173, 83 CYC:9499\n$DB54:F1 33     SBC ($33),Y = #$0400 @ 0400 = #$7F     A:81 X:05 Y:00 P:E5 SP:FB PPU:179, 83 CYC:9501\n$DB56:50 06     BVC $DB5E                          A:02 X:05 Y:00 P:65 SP:FB PPU:194, 83 CYC:9506\n$DB58:90 04     BCC $DB5E                          A:02 X:05 Y:00 P:65 SP:FB PPU:200, 83 CYC:9508\n$DB5A:C9 02     CMP #$02                           A:02 X:05 Y:00 P:65 SP:FB PPU:206, 83 CYC:9510\n$DB5C:F0 02     BEQ $DB60                          A:02 X:05 Y:00 P:67 SP:FB PPU:212, 83 CYC:9512\n$DB60:E8        INX                                A:02 X:05 Y:00 P:67 SP:FB PPU:221, 83 CYC:9515\n$DB61:A9 00     LDA #$00                           A:02 X:06 Y:00 P:65 SP:FB PPU:227, 83 CYC:9517\n$DB63:A9 87     LDA #$87                           A:00 X:06 Y:00 P:67 SP:FB PPU:233, 83 CYC:9519\n$DB65:91 33     STA ($33),Y = #$0400 @ 0400 = #$7F     A:87 X:06 Y:00 P:E5 SP:FB PPU:239, 83 CYC:9521\n$DB67:AD 00 04  LDA $0400 = #$87                     A:87 X:06 Y:00 P:E5 SP:FB PPU:257, 83 CYC:9527\n$DB6A:C9 87     CMP #$87                           A:87 X:06 Y:00 P:E5 SP:FB PPU:269, 83 CYC:9531\n$DB6C:F0 02     BEQ $DB70                          A:87 X:06 Y:00 P:67 SP:FB PPU:275, 83 CYC:9533\n$DB70:E8        INX                                A:87 X:06 Y:00 P:67 SP:FB PPU:284, 83 CYC:9536\n$DB71:A9 7E     LDA #$7E                           A:87 X:07 Y:00 P:65 SP:FB PPU:290, 83 CYC:9538\n$DB73:8D 00 02  STA $0200 = #$7F                     A:7E X:07 Y:00 P:65 SP:FB PPU:296, 83 CYC:9540\n$DB76:A9 DB     LDA #$DB                           A:7E X:07 Y:00 P:65 SP:FB PPU:308, 83 CYC:9544\n$DB78:8D 01 02  STA $0201 = #$00                     A:DB X:07 Y:00 P:E5 SP:FB PPU:314, 83 CYC:9546\n$DB7B:6C 00 02  JMP ($0200) = #$DB7E                 A:DB X:07 Y:00 P:E5 SP:FB PPU:326, 83 CYC:9550\n$DB7E:A9 00     LDA #$00                           A:DB X:07 Y:00 P:E5 SP:FB PPU:  0, 84 CYC:9555\n$DB80:8D FF 02  STA $02FF = #$00                     A:00 X:07 Y:00 P:67 SP:FB PPU:  6, 84 CYC:9557\n$DB83:A9 01     LDA #$01                           A:00 X:07 Y:00 P:67 SP:FB PPU: 18, 84 CYC:9561\n$DB85:8D 00 03  STA $0300 = #$89                     A:01 X:07 Y:00 P:65 SP:FB PPU: 24, 84 CYC:9563\n$DB88:A9 03     LDA #$03                           A:01 X:07 Y:00 P:65 SP:FB PPU: 36, 84 CYC:9567\n$DB8A:8D 00 02  STA $0200 = #$7E                     A:03 X:07 Y:00 P:65 SP:FB PPU: 42, 84 CYC:9569\n$DB8D:A9 A9     LDA #$A9                           A:03 X:07 Y:00 P:65 SP:FB PPU: 54, 84 CYC:9573\n$DB8F:8D 00 01  STA $0100 = #$00                     A:A9 X:07 Y:00 P:E5 SP:FB PPU: 60, 84 CYC:9575\n$DB92:A9 55     LDA #$55                           A:A9 X:07 Y:00 P:E5 SP:FB PPU: 72, 84 CYC:9579\n$DB94:8D 01 01  STA $0101 = #$00                     A:55 X:07 Y:00 P:65 SP:FB PPU: 78, 84 CYC:9581\n$DB97:A9 60     LDA #$60                           A:55 X:07 Y:00 P:65 SP:FB PPU: 90, 84 CYC:9585\n$DB99:8D 02 01  STA $0102 = #$00                     A:60 X:07 Y:00 P:65 SP:FB PPU: 96, 84 CYC:9587\n$DB9C:A9 A9     LDA #$A9                           A:60 X:07 Y:00 P:65 SP:FB PPU:108, 84 CYC:9591\n$DB9E:8D 00 03  STA $0300 = #$01                     A:A9 X:07 Y:00 P:E5 SP:FB PPU:114, 84 CYC:9593\n$DBA1:A9 AA     LDA #$AA                           A:A9 X:07 Y:00 P:E5 SP:FB PPU:126, 84 CYC:9597\n$DBA3:8D 01 03  STA $0301 = #$00                     A:AA X:07 Y:00 P:E5 SP:FB PPU:132, 84 CYC:9599\n$DBA6:A9 60     LDA #$60                           A:AA X:07 Y:00 P:E5 SP:FB PPU:144, 84 CYC:9603\n$DBA8:8D 02 03  STA $0302 = #$00                     A:60 X:07 Y:00 P:65 SP:FB PPU:150, 84 CYC:9605\n$DBAB:20 B5 DB  JSR $DBB5                          A:60 X:07 Y:00 P:65 SP:FB PPU:162, 84 CYC:9609\n$DBB5:6C FF 02  JMP ($02FF) = #$0300                 A:60 X:07 Y:00 P:65 SP:F9 PPU:180, 84 CYC:9615\n$0300:A9 AA     LDA #$AA                           A:60 X:07 Y:00 P:65 SP:F9 PPU:195, 84 CYC:9620\n$0302:60        RTS                                A:AA X:07 Y:00 P:E5 SP:F9 PPU:201, 84 CYC:9622\n$DBAE:C9 AA     CMP #$AA                           A:AA X:07 Y:00 P:E5 SP:FB PPU:219, 84 CYC:9628\n$DBB0:F0 02     BEQ $DBB4                          A:AA X:07 Y:00 P:67 SP:FB PPU:225, 84 CYC:9630\n$DBB4:60        RTS                                A:AA X:07 Y:00 P:67 SP:FB PPU:234, 84 CYC:9633\n$C626:20 4A DF  JSR $DF4A                          A:AA X:07 Y:00 P:67 SP:FD PPU:252, 84 CYC:9639\n$DF4A:A9 89     LDA #$89                           A:AA X:07 Y:00 P:67 SP:FB PPU:270, 84 CYC:9645\n$DF4C:8D 00 03  STA $0300 = #$A9                     A:89 X:07 Y:00 P:E5 SP:FB PPU:276, 84 CYC:9647\n$DF4F:A9 A3     LDA #$A3                           A:89 X:07 Y:00 P:E5 SP:FB PPU:288, 84 CYC:9651\n$DF51:85 33     STA $33 = #$00                       A:A3 X:07 Y:00 P:E5 SP:FB PPU:294, 84 CYC:9653\n$DF53:A9 12     LDA #$12                           A:A3 X:07 Y:00 P:E5 SP:FB PPU:303, 84 CYC:9656\n$DF55:8D 45 02  STA $0245 = #$12                     A:12 X:07 Y:00 P:65 SP:FB PPU:309, 84 CYC:9658\n$DF58:A2 65     LDX #$65                           A:12 X:07 Y:00 P:65 SP:FB PPU:321, 84 CYC:9662\n$DF5A:A0 00     LDY #$00                           A:12 X:65 Y:00 P:65 SP:FB PPU:327, 84 CYC:9664\n$DF5C:38        SEC                                A:12 X:65 Y:00 P:67 SP:FB PPU:333, 84 CYC:9666\n$DF5D:A9 00     LDA #$00                           A:12 X:65 Y:00 P:67 SP:FB PPU:339, 84 CYC:9668\n$DF5F:B8        CLV                                A:00 X:65 Y:00 P:67 SP:FB PPU:  4, 85 CYC:9670\n$DF60:B9 00 03  LDA $0300,Y @ 0300 = #$89            A:00 X:65 Y:00 P:nvUbdIZC SP:FB PPU: 10, 85 CYC:9672\n$DF63:F0 0C     BEQ $DF71                          A:89 X:65 Y:00 P:A5 SP:FB PPU: 22, 85 CYC:9676\n$DF65:90 0A     BCC $DF71                          A:89 X:65 Y:00 P:A5 SP:FB PPU: 28, 85 CYC:9678\n$DF67:70 08     BVS $DF71                          A:89 X:65 Y:00 P:A5 SP:FB PPU: 34, 85 CYC:9680\n$DF69:C9 89     CMP #$89                           A:89 X:65 Y:00 P:A5 SP:FB PPU: 40, 85 CYC:9682\n$DF6B:D0 04     BNE $DF71                          A:89 X:65 Y:00 P:nvUbdIZC SP:FB PPU: 46, 85 CYC:9684\n$DF6D:E0 65     CPX #$65                           A:89 X:65 Y:00 P:nvUbdIZC SP:FB PPU: 52, 85 CYC:9686\n$DF6F:F0 04     BEQ $DF75                          A:89 X:65 Y:00 P:nvUbdIZC SP:FB PPU: 58, 85 CYC:9688\n$DF75:A9 FF     LDA #$FF                           A:89 X:65 Y:00 P:nvUbdIZC SP:FB PPU: 67, 85 CYC:9691\n$DF77:85 01     STA $01 = #$FF                       A:FF X:65 Y:00 P:A5 SP:FB PPU: 73, 85 CYC:9693\n$DF79:24 01     BIT $01 = #$FF                       A:FF X:65 Y:00 P:A5 SP:FB PPU: 82, 85 CYC:9696\n$DF7B:A0 34     LDY #$34                           A:FF X:65 Y:00 P:E5 SP:FB PPU: 91, 85 CYC:9699\n$DF7D:B9 FF FF  LDA $FFFF,Y @ 0033 = #$A3            A:FF X:65 Y:34 P:65 SP:FB PPU: 97, 85 CYC:9701\n$DF80:C9 A3     CMP #$A3                           A:A3 X:65 Y:34 P:E5 SP:FB PPU:112, 85 CYC:9706\n$DF82:D0 02     BNE $DF86                          A:A3 X:65 Y:34 P:67 SP:FB PPU:118, 85 CYC:9708\n$DF84:B0 04     BCS $DF8A                          A:A3 X:65 Y:34 P:67 SP:FB PPU:124, 85 CYC:9710\n$DF8A:A9 46     LDA #$46                           A:A3 X:65 Y:34 P:67 SP:FB PPU:133, 85 CYC:9713\n$DF8C:85 FF     STA $FF = #$46                       A:46 X:65 Y:34 P:65 SP:FB PPU:139, 85 CYC:9715\n$DF8E:A0 FF     LDY #$FF                           A:46 X:65 Y:34 P:65 SP:FB PPU:148, 85 CYC:9718\n$DF90:B9 46 01  LDA $0146,Y @ 0245 = #$12            A:46 X:65 Y:FF P:E5 SP:FB PPU:154, 85 CYC:9720\n$DF93:C9 12     CMP #$12                           A:12 X:65 Y:FF P:65 SP:FB PPU:169, 85 CYC:9725\n$DF95:F0 04     BEQ $DF9B                          A:12 X:65 Y:FF P:67 SP:FB PPU:175, 85 CYC:9727\n$DF9B:A2 39     LDX #$39                           A:12 X:65 Y:FF P:67 SP:FB PPU:184, 85 CYC:9730\n$DF9D:18        CLC                                A:12 X:39 Y:FF P:65 SP:FB PPU:190, 85 CYC:9732\n$DF9E:A9 FF     LDA #$FF                           A:12 X:39 Y:FF P:64 SP:FB PPU:196, 85 CYC:9734\n$DFA0:85 01     STA $01 = #$FF                       A:FF X:39 Y:FF P:NVUbdIzc SP:FB PPU:202, 85 CYC:9736\n$DFA2:24 01     BIT $01 = #$FF                       A:FF X:39 Y:FF P:NVUbdIzc SP:FB PPU:211, 85 CYC:9739\n$DFA4:A9 AA     LDA #$AA                           A:FF X:39 Y:FF P:NVUbdIzc SP:FB PPU:220, 85 CYC:9742\n$DFA6:8D 00 04  STA $0400 = #$87                     A:AA X:39 Y:FF P:NVUbdIzc SP:FB PPU:226, 85 CYC:9744\n$DFA9:A9 55     LDA #$55                           A:AA X:39 Y:FF P:NVUbdIzc SP:FB PPU:238, 85 CYC:9748\n$DFAB:A0 00     LDY #$00                           A:55 X:39 Y:FF P:64 SP:FB PPU:244, 85 CYC:9750\n$DFAD:19 00 04  ORA $0400,Y @ 0400 = #$AA            A:55 X:39 Y:00 P:nVUbdIZc SP:FB PPU:250, 85 CYC:9752\n$DFB0:B0 08     BCS $DFBA                          A:FF X:39 Y:00 P:NVUbdIzc SP:FB PPU:262, 85 CYC:9756\n$DFB2:10 06     BPL $DFBA                          A:FF X:39 Y:00 P:NVUbdIzc SP:FB PPU:268, 85 CYC:9758\n$DFB4:C9 FF     CMP #$FF                           A:FF X:39 Y:00 P:NVUbdIzc SP:FB PPU:274, 85 CYC:9760\n$DFB6:D0 02     BNE $DFBA                          A:FF X:39 Y:00 P:67 SP:FB PPU:280, 85 CYC:9762\n$DFB8:70 02     BVS $DFBC                          A:FF X:39 Y:00 P:67 SP:FB PPU:286, 85 CYC:9764\n$DFBC:E8        INX                                A:FF X:39 Y:00 P:67 SP:FB PPU:295, 85 CYC:9767\n$DFBD:38        SEC                                A:FF X:3A Y:00 P:65 SP:FB PPU:301, 85 CYC:9769\n$DFBE:B8        CLV                                A:FF X:3A Y:00 P:65 SP:FB PPU:307, 85 CYC:9771\n$DFBF:A9 00     LDA #$00                           A:FF X:3A Y:00 P:25 SP:FB PPU:313, 85 CYC:9773\n$DFC1:19 00 04  ORA $0400,Y @ 0400 = #$AA            A:00 X:3A Y:00 P:nvUbdIZC SP:FB PPU:319, 85 CYC:9775\n$DFC4:F0 06     BEQ $DFCC                          A:AA X:3A Y:00 P:A5 SP:FB PPU:331, 85 CYC:9779\n$DFC6:70 04     BVS $DFCC                          A:AA X:3A Y:00 P:A5 SP:FB PPU:337, 85 CYC:9781\n$DFC8:90 02     BCC $DFCC                          A:AA X:3A Y:00 P:A5 SP:FB PPU:  2, 86 CYC:9783\n$DFCA:30 02     BMI $DFCE                          A:AA X:3A Y:00 P:A5 SP:FB PPU:  8, 86 CYC:9785\n$DFCE:E8        INX                                A:AA X:3A Y:00 P:A5 SP:FB PPU: 17, 86 CYC:9788\n$DFCF:18        CLC                                A:AA X:3B Y:00 P:25 SP:FB PPU: 23, 86 CYC:9790\n$DFD0:24 01     BIT $01 = #$FF                       A:AA X:3B Y:00 P:nvUbdIzc SP:FB PPU: 29, 86 CYC:9792\n$DFD2:A9 55     LDA #$55                           A:AA X:3B Y:00 P:NVUbdIzc SP:FB PPU: 38, 86 CYC:9795\n$DFD4:39 00 04  AND $0400,Y @ 0400 = #$AA            A:55 X:3B Y:00 P:64 SP:FB PPU: 44, 86 CYC:9797\n$DFD7:D0 06     BNE $DFDF                          A:00 X:3B Y:00 P:nVUbdIZc SP:FB PPU: 56, 86 CYC:9801\n$DFD9:50 04     BVC $DFDF                          A:00 X:3B Y:00 P:nVUbdIZc SP:FB PPU: 62, 86 CYC:9803\n$DFDB:B0 02     BCS $DFDF                          A:00 X:3B Y:00 P:nVUbdIZc SP:FB PPU: 68, 86 CYC:9805\n$DFDD:10 02     BPL $DFE1                          A:00 X:3B Y:00 P:nVUbdIZc SP:FB PPU: 74, 86 CYC:9807\n$DFE1:E8        INX                                A:00 X:3B Y:00 P:nVUbdIZc SP:FB PPU: 83, 86 CYC:9810\n$DFE2:38        SEC                                A:00 X:3C Y:00 P:64 SP:FB PPU: 89, 86 CYC:9812\n$DFE3:B8        CLV                                A:00 X:3C Y:00 P:65 SP:FB PPU: 95, 86 CYC:9814\n$DFE4:A9 EF     LDA #$EF                           A:00 X:3C Y:00 P:25 SP:FB PPU:101, 86 CYC:9816\n$DFE6:8D 00 04  STA $0400 = #$AA                     A:EF X:3C Y:00 P:A5 SP:FB PPU:107, 86 CYC:9818\n$DFE9:A9 F8     LDA #$F8                           A:EF X:3C Y:00 P:A5 SP:FB PPU:119, 86 CYC:9822\n$DFEB:39 00 04  AND $0400,Y @ 0400 = #$EF            A:F8 X:3C Y:00 P:A5 SP:FB PPU:125, 86 CYC:9824\n$DFEE:90 08     BCC $DFF8                          A:E8 X:3C Y:00 P:A5 SP:FB PPU:137, 86 CYC:9828\n$DFF0:10 06     BPL $DFF8                          A:E8 X:3C Y:00 P:A5 SP:FB PPU:143, 86 CYC:9830\n$DFF2:C9 E8     CMP #$E8                           A:E8 X:3C Y:00 P:A5 SP:FB PPU:149, 86 CYC:9832\n$DFF4:D0 02     BNE $DFF8                          A:E8 X:3C Y:00 P:nvUbdIZC SP:FB PPU:155, 86 CYC:9834\n$DFF6:50 02     BVC $DFFA                          A:E8 X:3C Y:00 P:nvUbdIZC SP:FB PPU:161, 86 CYC:9836\n$DFFA:E8        INX                                A:E8 X:3C Y:00 P:nvUbdIZC SP:FB PPU:170, 86 CYC:9839\n$DFFB:18        CLC                                A:E8 X:3D Y:00 P:25 SP:FB PPU:176, 86 CYC:9841\n$DFFC:24 01     BIT $01 = #$FF                       A:E8 X:3D Y:00 P:nvUbdIzc SP:FB PPU:182, 86 CYC:9843\n$DFFE:A9 AA     LDA #$AA                           A:E8 X:3D Y:00 P:NVUbdIzc SP:FB PPU:191, 86 CYC:9846\n$E000:8D 00 04  STA $0400 = #$EF                     A:AA X:3D Y:00 P:NVUbdIzc SP:FB PPU:197, 86 CYC:9848\n$E003:A9 5F     LDA #$5F                           A:AA X:3D Y:00 P:NVUbdIzc SP:FB PPU:209, 86 CYC:9852\n$E005:59 00 04  EOR $0400,Y @ 0400 = #$AA            A:5F X:3D Y:00 P:64 SP:FB PPU:215, 86 CYC:9854\n$E008:B0 08     BCS $E012                          A:F5 X:3D Y:00 P:NVUbdIzc SP:FB PPU:227, 86 CYC:9858\n$E00A:10 06     BPL $E012                          A:F5 X:3D Y:00 P:NVUbdIzc SP:FB PPU:233, 86 CYC:9860\n$E00C:C9 F5     CMP #$F5                           A:F5 X:3D Y:00 P:NVUbdIzc SP:FB PPU:239, 86 CYC:9862\n$E00E:D0 02     BNE $E012                          A:F5 X:3D Y:00 P:67 SP:FB PPU:245, 86 CYC:9864\n$E010:70 02     BVS $E014                          A:F5 X:3D Y:00 P:67 SP:FB PPU:251, 86 CYC:9866\n$E014:E8        INX                                A:F5 X:3D Y:00 P:67 SP:FB PPU:260, 86 CYC:9869\n$E015:38        SEC                                A:F5 X:3E Y:00 P:65 SP:FB PPU:266, 86 CYC:9871\n$E016:B8        CLV                                A:F5 X:3E Y:00 P:65 SP:FB PPU:272, 86 CYC:9873\n$E017:A9 70     LDA #$70                           A:F5 X:3E Y:00 P:25 SP:FB PPU:278, 86 CYC:9875\n$E019:8D 00 04  STA $0400 = #$AA                     A:70 X:3E Y:00 P:25 SP:FB PPU:284, 86 CYC:9877\n$E01C:59 00 04  EOR $0400,Y @ 0400 = #$70            A:70 X:3E Y:00 P:25 SP:FB PPU:296, 86 CYC:9881\n$E01F:D0 06     BNE $E027                          A:00 X:3E Y:00 P:nvUbdIZC SP:FB PPU:308, 86 CYC:9885\n$E021:70 04     BVS $E027                          A:00 X:3E Y:00 P:nvUbdIZC SP:FB PPU:314, 86 CYC:9887\n$E023:90 02     BCC $E027                          A:00 X:3E Y:00 P:nvUbdIZC SP:FB PPU:320, 86 CYC:9889\n$E025:10 02     BPL $E029                          A:00 X:3E Y:00 P:nvUbdIZC SP:FB PPU:326, 86 CYC:9891\n$E029:E8        INX                                A:00 X:3E Y:00 P:nvUbdIZC SP:FB PPU:335, 86 CYC:9894\n$E02A:18        CLC                                A:00 X:3F Y:00 P:25 SP:FB PPU:  0, 87 CYC:9896\n$E02B:24 01     BIT $01 = #$FF                       A:00 X:3F Y:00 P:nvUbdIzc SP:FB PPU:  6, 87 CYC:9898\n$E02D:A9 69     LDA #$69                           A:00 X:3F Y:00 P:E6 SP:FB PPU: 15, 87 CYC:9901\n$E02F:8D 00 04  STA $0400 = #$70                     A:69 X:3F Y:00 P:64 SP:FB PPU: 21, 87 CYC:9903\n$E032:A9 00     LDA #$00                           A:69 X:3F Y:00 P:64 SP:FB PPU: 33, 87 CYC:9907\n$E034:79 00 04  ADC $0400,Y @ 0400 = #$69            A:00 X:3F Y:00 P:nVUbdIZc SP:FB PPU: 39, 87 CYC:9909\n$E037:30 08     BMI $E041                          A:69 X:3F Y:00 P:nvUbdIzc SP:FB PPU: 51, 87 CYC:9913\n$E039:B0 06     BCS $E041                          A:69 X:3F Y:00 P:nvUbdIzc SP:FB PPU: 57, 87 CYC:9915\n$E03B:C9 69     CMP #$69                           A:69 X:3F Y:00 P:nvUbdIzc SP:FB PPU: 63, 87 CYC:9917\n$E03D:D0 02     BNE $E041                          A:69 X:3F Y:00 P:nvUbdIZC SP:FB PPU: 69, 87 CYC:9919\n$E03F:50 02     BVC $E043                          A:69 X:3F Y:00 P:nvUbdIZC SP:FB PPU: 75, 87 CYC:9921\n$E043:E8        INX                                A:69 X:3F Y:00 P:nvUbdIZC SP:FB PPU: 84, 87 CYC:9924\n$E044:38        SEC                                A:69 X:40 Y:00 P:25 SP:FB PPU: 90, 87 CYC:9926\n$E045:24 01     BIT $01 = #$FF                       A:69 X:40 Y:00 P:25 SP:FB PPU: 96, 87 CYC:9928\n$E047:A9 00     LDA #$00                           A:69 X:40 Y:00 P:E5 SP:FB PPU:105, 87 CYC:9931\n$E049:79 00 04  ADC $0400,Y @ 0400 = #$69            A:00 X:40 Y:00 P:67 SP:FB PPU:111, 87 CYC:9933\n$E04C:30 08     BMI $E056                          A:6A X:40 Y:00 P:nvUbdIzc SP:FB PPU:123, 87 CYC:9937\n$E04E:B0 06     BCS $E056                          A:6A X:40 Y:00 P:nvUbdIzc SP:FB PPU:129, 87 CYC:9939\n$E050:C9 6A     CMP #$6A                           A:6A X:40 Y:00 P:nvUbdIzc SP:FB PPU:135, 87 CYC:9941\n$E052:D0 02     BNE $E056                          A:6A X:40 Y:00 P:nvUbdIZC SP:FB PPU:141, 87 CYC:9943\n$E054:50 02     BVC $E058                          A:6A X:40 Y:00 P:nvUbdIZC SP:FB PPU:147, 87 CYC:9945\n$E058:E8        INX                                A:6A X:40 Y:00 P:nvUbdIZC SP:FB PPU:156, 87 CYC:9948\n$E059:38        SEC                                A:6A X:41 Y:00 P:25 SP:FB PPU:162, 87 CYC:9950\n$E05A:B8        CLV                                A:6A X:41 Y:00 P:25 SP:FB PPU:168, 87 CYC:9952\n$E05B:A9 7F     LDA #$7F                           A:6A X:41 Y:00 P:25 SP:FB PPU:174, 87 CYC:9954\n$E05D:8D 00 04  STA $0400 = #$69                     A:7F X:41 Y:00 P:25 SP:FB PPU:180, 87 CYC:9956\n$E060:79 00 04  ADC $0400,Y @ 0400 = #$7F            A:7F X:41 Y:00 P:25 SP:FB PPU:192, 87 CYC:9960\n$E063:10 08     BPL $E06D                          A:FF X:41 Y:00 P:NVUbdIzc SP:FB PPU:204, 87 CYC:9964\n$E065:B0 06     BCS $E06D                          A:FF X:41 Y:00 P:NVUbdIzc SP:FB PPU:210, 87 CYC:9966\n$E067:C9 FF     CMP #$FF                           A:FF X:41 Y:00 P:NVUbdIzc SP:FB PPU:216, 87 CYC:9968\n$E069:D0 02     BNE $E06D                          A:FF X:41 Y:00 P:67 SP:FB PPU:222, 87 CYC:9970\n$E06B:70 02     BVS $E06F                          A:FF X:41 Y:00 P:67 SP:FB PPU:228, 87 CYC:9972\n$E06F:E8        INX                                A:FF X:41 Y:00 P:67 SP:FB PPU:237, 87 CYC:9975\n$E070:18        CLC                                A:FF X:42 Y:00 P:65 SP:FB PPU:243, 87 CYC:9977\n$E071:24 01     BIT $01 = #$FF                       A:FF X:42 Y:00 P:64 SP:FB PPU:249, 87 CYC:9979\n$E073:A9 80     LDA #$80                           A:FF X:42 Y:00 P:NVUbdIzc SP:FB PPU:258, 87 CYC:9982\n$E075:8D 00 04  STA $0400 = #$7F                     A:80 X:42 Y:00 P:NVUbdIzc SP:FB PPU:264, 87 CYC:9984\n$E078:A9 7F     LDA #$7F                           A:80 X:42 Y:00 P:NVUbdIzc SP:FB PPU:276, 87 CYC:9988\n$E07A:79 00 04  ADC $0400,Y @ 0400 = #$80            A:7F X:42 Y:00 P:64 SP:FB PPU:282, 87 CYC:9990\n$E07D:10 08     BPL $E087                          A:FF X:42 Y:00 P:NvUbdIzc SP:FB PPU:294, 87 CYC:9994\n$E07F:B0 06     BCS $E087                          A:FF X:42 Y:00 P:NvUbdIzc SP:FB PPU:300, 87 CYC:9996\n$E081:C9 FF     CMP #$FF                           A:FF X:42 Y:00 P:NvUbdIzc SP:FB PPU:306, 87 CYC:9998\n$E083:D0 02     BNE $E087                          A:FF X:42 Y:00 P:nvUbdIZC SP:FB PPU:312, 87 CYC:10000\n$E085:50 02     BVC $E089                          A:FF X:42 Y:00 P:nvUbdIZC SP:FB PPU:318, 87 CYC:10002\n$E089:E8        INX                                A:FF X:42 Y:00 P:nvUbdIZC SP:FB PPU:327, 87 CYC:10005\n$E08A:38        SEC                                A:FF X:43 Y:00 P:25 SP:FB PPU:333, 87 CYC:10007\n$E08B:B8        CLV                                A:FF X:43 Y:00 P:25 SP:FB PPU:339, 87 CYC:10009\n$E08C:A9 80     LDA #$80                           A:FF X:43 Y:00 P:25 SP:FB PPU:  4, 88 CYC:10011\n$E08E:8D 00 04  STA $0400 = #$80                     A:80 X:43 Y:00 P:A5 SP:FB PPU: 10, 88 CYC:10013\n$E091:A9 7F     LDA #$7F                           A:80 X:43 Y:00 P:A5 SP:FB PPU: 22, 88 CYC:10017\n$E093:79 00 04  ADC $0400,Y @ 0400 = #$80            A:7F X:43 Y:00 P:25 SP:FB PPU: 28, 88 CYC:10019\n$E096:D0 06     BNE $E09E                          A:00 X:43 Y:00 P:nvUbdIZC SP:FB PPU: 40, 88 CYC:10023\n$E098:30 04     BMI $E09E                          A:00 X:43 Y:00 P:nvUbdIZC SP:FB PPU: 46, 88 CYC:10025\n$E09A:70 02     BVS $E09E                          A:00 X:43 Y:00 P:nvUbdIZC SP:FB PPU: 52, 88 CYC:10027\n$E09C:B0 02     BCS $E0A0                          A:00 X:43 Y:00 P:nvUbdIZC SP:FB PPU: 58, 88 CYC:10029\n$E0A0:E8        INX                                A:00 X:43 Y:00 P:nvUbdIZC SP:FB PPU: 67, 88 CYC:10032\n$E0A1:24 01     BIT $01 = #$FF                       A:00 X:44 Y:00 P:25 SP:FB PPU: 73, 88 CYC:10034\n$E0A3:A9 40     LDA #$40                           A:00 X:44 Y:00 P:E7 SP:FB PPU: 82, 88 CYC:10037\n$E0A5:8D 00 04  STA $0400 = #$80                     A:40 X:44 Y:00 P:65 SP:FB PPU: 88, 88 CYC:10039\n$E0A8:D9 00 04  CMP $0400,Y @ 0400 = #$40            A:40 X:44 Y:00 P:65 SP:FB PPU:100, 88 CYC:10043\n$E0AB:30 06     BMI $E0B3                          A:40 X:44 Y:00 P:67 SP:FB PPU:112, 88 CYC:10047\n$E0AD:90 04     BCC $E0B3                          A:40 X:44 Y:00 P:67 SP:FB PPU:118, 88 CYC:10049\n$E0AF:D0 02     BNE $E0B3                          A:40 X:44 Y:00 P:67 SP:FB PPU:124, 88 CYC:10051\n$E0B1:70 02     BVS $E0B5                          A:40 X:44 Y:00 P:67 SP:FB PPU:130, 88 CYC:10053\n$E0B5:E8        INX                                A:40 X:44 Y:00 P:67 SP:FB PPU:139, 88 CYC:10056\n$E0B6:B8        CLV                                A:40 X:45 Y:00 P:65 SP:FB PPU:145, 88 CYC:10058\n$E0B7:CE 00 04  DEC $0400 = #$40                     A:40 X:45 Y:00 P:25 SP:FB PPU:151, 88 CYC:10060\n$E0BA:D9 00 04  CMP $0400,Y @ 0400 = #$3F            A:40 X:45 Y:00 P:25 SP:FB PPU:169, 88 CYC:10066\n$E0BD:F0 06     BEQ $E0C5                          A:40 X:45 Y:00 P:25 SP:FB PPU:181, 88 CYC:10070\n$E0BF:30 04     BMI $E0C5                          A:40 X:45 Y:00 P:25 SP:FB PPU:187, 88 CYC:10072\n$E0C1:90 02     BCC $E0C5                          A:40 X:45 Y:00 P:25 SP:FB PPU:193, 88 CYC:10074\n$E0C3:50 02     BVC $E0C7                          A:40 X:45 Y:00 P:25 SP:FB PPU:199, 88 CYC:10076\n$E0C7:E8        INX                                A:40 X:45 Y:00 P:25 SP:FB PPU:208, 88 CYC:10079\n$E0C8:EE 00 04  INC $0400 = #$3F                     A:40 X:46 Y:00 P:25 SP:FB PPU:214, 88 CYC:10081\n$E0CB:EE 00 04  INC $0400 = #$40                     A:40 X:46 Y:00 P:25 SP:FB PPU:232, 88 CYC:10087\n$E0CE:D9 00 04  CMP $0400,Y @ 0400 = #$41            A:40 X:46 Y:00 P:25 SP:FB PPU:250, 88 CYC:10093\n$E0D1:F0 02     BEQ $E0D5                          A:40 X:46 Y:00 P:NvUbdIzc SP:FB PPU:262, 88 CYC:10097\n$E0D3:30 02     BMI $E0D7                          A:40 X:46 Y:00 P:NvUbdIzc SP:FB PPU:268, 88 CYC:10099\n$E0D7:E8        INX                                A:40 X:46 Y:00 P:NvUbdIzc SP:FB PPU:277, 88 CYC:10102\n$E0D8:A9 00     LDA #$00                           A:40 X:47 Y:00 P:nvUbdIzc SP:FB PPU:283, 88 CYC:10104\n$E0DA:8D 00 04  STA $0400 = #$41                     A:00 X:47 Y:00 P:nvUbdIZc SP:FB PPU:289, 88 CYC:10106\n$E0DD:A9 80     LDA #$80                           A:00 X:47 Y:00 P:nvUbdIZc SP:FB PPU:301, 88 CYC:10110\n$E0DF:D9 00 04  CMP $0400,Y @ 0400 = #$00            A:80 X:47 Y:00 P:NvUbdIzc SP:FB PPU:307, 88 CYC:10112\n$E0E2:F0 04     BEQ $E0E8                          A:80 X:47 Y:00 P:A5 SP:FB PPU:319, 88 CYC:10116\n$E0E4:10 02     BPL $E0E8                          A:80 X:47 Y:00 P:A5 SP:FB PPU:325, 88 CYC:10118\n$E0E6:B0 02     BCS $E0EA                          A:80 X:47 Y:00 P:A5 SP:FB PPU:331, 88 CYC:10120\n$E0EA:E8        INX                                A:80 X:47 Y:00 P:A5 SP:FB PPU:340, 88 CYC:10123\n$E0EB:A0 80     LDY #$80                           A:80 X:48 Y:00 P:25 SP:FB PPU:  5, 89 CYC:10125\n$E0ED:8C 00 04  STY $0400 = #$00                     A:80 X:48 Y:80 P:A5 SP:FB PPU: 11, 89 CYC:10127\n$E0F0:A0 00     LDY #$00                           A:80 X:48 Y:80 P:A5 SP:FB PPU: 23, 89 CYC:10131\n$E0F2:D9 00 04  CMP $0400,Y @ 0400 = #$80            A:80 X:48 Y:00 P:nvUbdIZC SP:FB PPU: 29, 89 CYC:10133\n$E0F5:D0 04     BNE $E0FB                          A:80 X:48 Y:00 P:nvUbdIZC SP:FB PPU: 41, 89 CYC:10137\n$E0F7:30 02     BMI $E0FB                          A:80 X:48 Y:00 P:nvUbdIZC SP:FB PPU: 47, 89 CYC:10139\n$E0F9:B0 02     BCS $E0FD                          A:80 X:48 Y:00 P:nvUbdIZC SP:FB PPU: 53, 89 CYC:10141\n$E0FD:E8        INX                                A:80 X:48 Y:00 P:nvUbdIZC SP:FB PPU: 62, 89 CYC:10144\n$E0FE:EE 00 04  INC $0400 = #$80                     A:80 X:49 Y:00 P:25 SP:FB PPU: 68, 89 CYC:10146\n$E101:D9 00 04  CMP $0400,Y @ 0400 = #$81            A:80 X:49 Y:00 P:A5 SP:FB PPU: 86, 89 CYC:10152\n$E104:B0 04     BCS $E10A                          A:80 X:49 Y:00 P:NvUbdIzc SP:FB PPU: 98, 89 CYC:10156\n$E106:F0 02     BEQ $E10A                          A:80 X:49 Y:00 P:NvUbdIzc SP:FB PPU:104, 89 CYC:10158\n$E108:30 02     BMI $E10C                          A:80 X:49 Y:00 P:NvUbdIzc SP:FB PPU:110, 89 CYC:10160\n$E10C:E8        INX                                A:80 X:49 Y:00 P:NvUbdIzc SP:FB PPU:119, 89 CYC:10163\n$E10D:CE 00 04  DEC $0400 = #$81                     A:80 X:4A Y:00 P:nvUbdIzc SP:FB PPU:125, 89 CYC:10165\n$E110:CE 00 04  DEC $0400 = #$80                     A:80 X:4A Y:00 P:NvUbdIzc SP:FB PPU:143, 89 CYC:10171\n$E113:D9 00 04  CMP $0400,Y @ 0400 = #$7F            A:80 X:4A Y:00 P:nvUbdIzc SP:FB PPU:161, 89 CYC:10177\n$E116:90 04     BCC $E11C                          A:80 X:4A Y:00 P:25 SP:FB PPU:173, 89 CYC:10181\n$E118:F0 02     BEQ $E11C                          A:80 X:4A Y:00 P:25 SP:FB PPU:179, 89 CYC:10183\n$E11A:10 02     BPL $E11E                          A:80 X:4A Y:00 P:25 SP:FB PPU:185, 89 CYC:10185\n$E11E:E8        INX                                A:80 X:4A Y:00 P:25 SP:FB PPU:194, 89 CYC:10188\n$E11F:24 01     BIT $01 = #$FF                       A:80 X:4B Y:00 P:25 SP:FB PPU:200, 89 CYC:10190\n$E121:A9 40     LDA #$40                           A:80 X:4B Y:00 P:E5 SP:FB PPU:209, 89 CYC:10193\n$E123:8D 00 04  STA $0400 = #$7F                     A:40 X:4B Y:00 P:65 SP:FB PPU:215, 89 CYC:10195\n$E126:38        SEC                                A:40 X:4B Y:00 P:65 SP:FB PPU:227, 89 CYC:10199\n$E127:F9 00 04  SBC $0400,Y @ 0400 = #$40            A:40 X:4B Y:00 P:65 SP:FB PPU:233, 89 CYC:10201\n$E12A:30 0A     BMI $E136                          A:00 X:4B Y:00 P:nvUbdIZC SP:FB PPU:245, 89 CYC:10205\n$E12C:90 08     BCC $E136                          A:00 X:4B Y:00 P:nvUbdIZC SP:FB PPU:251, 89 CYC:10207\n$E12E:D0 06     BNE $E136                          A:00 X:4B Y:00 P:nvUbdIZC SP:FB PPU:257, 89 CYC:10209\n$E130:70 04     BVS $E136                          A:00 X:4B Y:00 P:nvUbdIZC SP:FB PPU:263, 89 CYC:10211\n$E132:C9 00     CMP #$00                           A:00 X:4B Y:00 P:nvUbdIZC SP:FB PPU:269, 89 CYC:10213\n$E134:F0 02     BEQ $E138                          A:00 X:4B Y:00 P:nvUbdIZC SP:FB PPU:275, 89 CYC:10215\n$E138:E8        INX                                A:00 X:4B Y:00 P:nvUbdIZC SP:FB PPU:284, 89 CYC:10218\n$E139:B8        CLV                                A:00 X:4C Y:00 P:25 SP:FB PPU:290, 89 CYC:10220\n$E13A:38        SEC                                A:00 X:4C Y:00 P:25 SP:FB PPU:296, 89 CYC:10222\n$E13B:A9 40     LDA #$40                           A:00 X:4C Y:00 P:25 SP:FB PPU:302, 89 CYC:10224\n$E13D:CE 00 04  DEC $0400 = #$40                     A:40 X:4C Y:00 P:25 SP:FB PPU:308, 89 CYC:10226\n$E140:F9 00 04  SBC $0400,Y @ 0400 = #$3F            A:40 X:4C Y:00 P:25 SP:FB PPU:326, 89 CYC:10232\n$E143:F0 0A     BEQ $E14F                          A:01 X:4C Y:00 P:25 SP:FB PPU:338, 89 CYC:10236\n$E145:30 08     BMI $E14F                          A:01 X:4C Y:00 P:25 SP:FB PPU:  3, 90 CYC:10238\n$E147:90 06     BCC $E14F                          A:01 X:4C Y:00 P:25 SP:FB PPU:  9, 90 CYC:10240\n$E149:70 04     BVS $E14F                          A:01 X:4C Y:00 P:25 SP:FB PPU: 15, 90 CYC:10242\n$E14B:C9 01     CMP #$01                           A:01 X:4C Y:00 P:25 SP:FB PPU: 21, 90 CYC:10244\n$E14D:F0 02     BEQ $E151                          A:01 X:4C Y:00 P:nvUbdIZC SP:FB PPU: 27, 90 CYC:10246\n$E151:E8        INX                                A:01 X:4C Y:00 P:nvUbdIZC SP:FB PPU: 36, 90 CYC:10249\n$E152:A9 40     LDA #$40                           A:01 X:4D Y:00 P:25 SP:FB PPU: 42, 90 CYC:10251\n$E154:38        SEC                                A:40 X:4D Y:00 P:25 SP:FB PPU: 48, 90 CYC:10253\n$E155:24 01     BIT $01 = #$FF                       A:40 X:4D Y:00 P:25 SP:FB PPU: 54, 90 CYC:10255\n$E157:EE 00 04  INC $0400 = #$3F                     A:40 X:4D Y:00 P:E5 SP:FB PPU: 63, 90 CYC:10258\n$E15A:EE 00 04  INC $0400 = #$40                     A:40 X:4D Y:00 P:65 SP:FB PPU: 81, 90 CYC:10264\n$E15D:F9 00 04  SBC $0400,Y @ 0400 = #$41            A:40 X:4D Y:00 P:65 SP:FB PPU: 99, 90 CYC:10270\n$E160:B0 0A     BCS $E16C                          A:FF X:4D Y:00 P:NvUbdIzc SP:FB PPU:111, 90 CYC:10274\n$E162:F0 08     BEQ $E16C                          A:FF X:4D Y:00 P:NvUbdIzc SP:FB PPU:117, 90 CYC:10276\n$E164:10 06     BPL $E16C                          A:FF X:4D Y:00 P:NvUbdIzc SP:FB PPU:123, 90 CYC:10278\n$E166:70 04     BVS $E16C                          A:FF X:4D Y:00 P:NvUbdIzc SP:FB PPU:129, 90 CYC:10280\n$E168:C9 FF     CMP #$FF                           A:FF X:4D Y:00 P:NvUbdIzc SP:FB PPU:135, 90 CYC:10282\n$E16A:F0 02     BEQ $E16E                          A:FF X:4D Y:00 P:nvUbdIZC SP:FB PPU:141, 90 CYC:10284\n$E16E:E8        INX                                A:FF X:4D Y:00 P:nvUbdIZC SP:FB PPU:150, 90 CYC:10287\n$E16F:18        CLC                                A:FF X:4E Y:00 P:25 SP:FB PPU:156, 90 CYC:10289\n$E170:A9 00     LDA #$00                           A:FF X:4E Y:00 P:nvUbdIzc SP:FB PPU:162, 90 CYC:10291\n$E172:8D 00 04  STA $0400 = #$41                     A:00 X:4E Y:00 P:nvUbdIZc SP:FB PPU:168, 90 CYC:10293\n$E175:A9 80     LDA #$80                           A:00 X:4E Y:00 P:nvUbdIZc SP:FB PPU:180, 90 CYC:10297\n$E177:F9 00 04  SBC $0400,Y @ 0400 = #$00            A:80 X:4E Y:00 P:NvUbdIzc SP:FB PPU:186, 90 CYC:10299\n$E17A:90 04     BCC $E180                          A:7F X:4E Y:00 P:65 SP:FB PPU:198, 90 CYC:10303\n$E17C:C9 7F     CMP #$7F                           A:7F X:4E Y:00 P:65 SP:FB PPU:204, 90 CYC:10305\n$E17E:F0 02     BEQ $E182                          A:7F X:4E Y:00 P:67 SP:FB PPU:210, 90 CYC:10307\n$E182:E8        INX                                A:7F X:4E Y:00 P:67 SP:FB PPU:219, 90 CYC:10310\n$E183:38        SEC                                A:7F X:4F Y:00 P:65 SP:FB PPU:225, 90 CYC:10312\n$E184:A9 7F     LDA #$7F                           A:7F X:4F Y:00 P:65 SP:FB PPU:231, 90 CYC:10314\n$E186:8D 00 04  STA $0400 = #$00                     A:7F X:4F Y:00 P:65 SP:FB PPU:237, 90 CYC:10316\n$E189:A9 81     LDA #$81                           A:7F X:4F Y:00 P:65 SP:FB PPU:249, 90 CYC:10320\n$E18B:F9 00 04  SBC $0400,Y @ 0400 = #$7F            A:81 X:4F Y:00 P:E5 SP:FB PPU:255, 90 CYC:10322\n$E18E:50 06     BVC $E196                          A:02 X:4F Y:00 P:65 SP:FB PPU:267, 90 CYC:10326\n$E190:90 04     BCC $E196                          A:02 X:4F Y:00 P:65 SP:FB PPU:273, 90 CYC:10328\n$E192:C9 02     CMP #$02                           A:02 X:4F Y:00 P:65 SP:FB PPU:279, 90 CYC:10330\n$E194:F0 02     BEQ $E198                          A:02 X:4F Y:00 P:67 SP:FB PPU:285, 90 CYC:10332\n$E198:E8        INX                                A:02 X:4F Y:00 P:67 SP:FB PPU:294, 90 CYC:10335\n$E199:A9 00     LDA #$00                           A:02 X:50 Y:00 P:65 SP:FB PPU:300, 90 CYC:10337\n$E19B:A9 87     LDA #$87                           A:00 X:50 Y:00 P:67 SP:FB PPU:306, 90 CYC:10339\n$E19D:99 00 04  STA $0400,Y @ 0400 = #$7F            A:87 X:50 Y:00 P:E5 SP:FB PPU:312, 90 CYC:10341\n$E1A0:AD 00 04  LDA $0400 = #$87                     A:87 X:50 Y:00 P:E5 SP:FB PPU:327, 90 CYC:10346\n$E1A3:C9 87     CMP #$87                           A:87 X:50 Y:00 P:E5 SP:FB PPU:339, 90 CYC:10350\n$E1A5:F0 02     BEQ $E1A9                          A:87 X:50 Y:00 P:67 SP:FB PPU:  4, 91 CYC:10352\n$E1A9:60        RTS                                A:87 X:50 Y:00 P:67 SP:FB PPU: 13, 91 CYC:10355\n$C629:20 B8 DB  JSR $DBB8                          A:87 X:50 Y:00 P:67 SP:FD PPU: 31, 91 CYC:10361\n$DBB8:A9 FF     LDA #$FF                           A:87 X:50 Y:00 P:67 SP:FB PPU: 49, 91 CYC:10367\n$DBBA:85 01     STA $01 = #$FF                       A:FF X:50 Y:00 P:E5 SP:FB PPU: 55, 91 CYC:10369\n$DBBC:A9 AA     LDA #$AA                           A:FF X:50 Y:00 P:E5 SP:FB PPU: 64, 91 CYC:10372\n$DBBE:85 33     STA $33 = #$A3                       A:AA X:50 Y:00 P:E5 SP:FB PPU: 70, 91 CYC:10374\n$DBC0:A9 BB     LDA #$BB                           A:AA X:50 Y:00 P:E5 SP:FB PPU: 79, 91 CYC:10377\n$DBC2:85 89     STA $89 = #$00                       A:BB X:50 Y:00 P:E5 SP:FB PPU: 85, 91 CYC:10379\n$DBC4:A2 00     LDX #$00                           A:BB X:50 Y:00 P:E5 SP:FB PPU: 94, 91 CYC:10382\n$DBC6:A9 66     LDA #$66                           A:BB X:00 Y:00 P:67 SP:FB PPU:100, 91 CYC:10384\n$DBC8:24 01     BIT $01 = #$FF                       A:66 X:00 Y:00 P:65 SP:FB PPU:106, 91 CYC:10386\n$DBCA:38        SEC                                A:66 X:00 Y:00 P:E5 SP:FB PPU:115, 91 CYC:10389\n$DBCB:A0 00     LDY #$00                           A:66 X:00 Y:00 P:E5 SP:FB PPU:121, 91 CYC:10391\n$DBCD:B4 33     LDY $33,X @ 33 = #$AA                A:66 X:00 Y:00 P:67 SP:FB PPU:127, 91 CYC:10393\n$DBCF:10 12     BPL $DBE3                          A:66 X:00 Y:AA P:E5 SP:FB PPU:139, 91 CYC:10397\n$DBD1:F0 10     BEQ $DBE3                          A:66 X:00 Y:AA P:E5 SP:FB PPU:145, 91 CYC:10399\n$DBD3:50 0E     BVC $DBE3                          A:66 X:00 Y:AA P:E5 SP:FB PPU:151, 91 CYC:10401\n$DBD5:90 0C     BCC $DBE3                          A:66 X:00 Y:AA P:E5 SP:FB PPU:157, 91 CYC:10403\n$DBD7:C9 66     CMP #$66                           A:66 X:00 Y:AA P:E5 SP:FB PPU:163, 91 CYC:10405\n$DBD9:D0 08     BNE $DBE3                          A:66 X:00 Y:AA P:67 SP:FB PPU:169, 91 CYC:10407\n$DBDB:E0 00     CPX #$00                           A:66 X:00 Y:AA P:67 SP:FB PPU:175, 91 CYC:10409\n$DBDD:D0 04     BNE $DBE3                          A:66 X:00 Y:AA P:67 SP:FB PPU:181, 91 CYC:10411\n$DBDF:C0 AA     CPY #$AA                           A:66 X:00 Y:AA P:67 SP:FB PPU:187, 91 CYC:10413\n$DBE1:F0 04     BEQ $DBE7                          A:66 X:00 Y:AA P:67 SP:FB PPU:193, 91 CYC:10415\n$DBE7:A2 8A     LDX #$8A                           A:66 X:00 Y:AA P:67 SP:FB PPU:202, 91 CYC:10418\n$DBE9:A9 66     LDA #$66                           A:66 X:8A Y:AA P:E5 SP:FB PPU:208, 91 CYC:10420\n$DBEB:B8        CLV                                A:66 X:8A Y:AA P:65 SP:FB PPU:214, 91 CYC:10422\n$DBEC:18        CLC                                A:66 X:8A Y:AA P:25 SP:FB PPU:220, 91 CYC:10424\n$DBED:A0 00     LDY #$00                           A:66 X:8A Y:AA P:nvUbdIzc SP:FB PPU:226, 91 CYC:10426\n$DBEF:B4 FF     LDY $FF,X @ 89 = #$BB                A:66 X:8A Y:00 P:nvUbdIZc SP:FB PPU:232, 91 CYC:10428\n$DBF1:10 12     BPL $DC05                          A:66 X:8A Y:BB P:NvUbdIzc SP:FB PPU:244, 91 CYC:10432\n$DBF3:F0 10     BEQ $DC05                          A:66 X:8A Y:BB P:NvUbdIzc SP:FB PPU:250, 91 CYC:10434\n$DBF5:70 0E     BVS $DC05                          A:66 X:8A Y:BB P:NvUbdIzc SP:FB PPU:256, 91 CYC:10436\n$DBF7:B0 0C     BCS $DC05                          A:66 X:8A Y:BB P:NvUbdIzc SP:FB PPU:262, 91 CYC:10438\n$DBF9:C0 BB     CPY #$BB                           A:66 X:8A Y:BB P:NvUbdIzc SP:FB PPU:268, 91 CYC:10440\n$DBFB:D0 08     BNE $DC05                          A:66 X:8A Y:BB P:nvUbdIZC SP:FB PPU:274, 91 CYC:10442\n$DBFD:C9 66     CMP #$66                           A:66 X:8A Y:BB P:nvUbdIZC SP:FB PPU:280, 91 CYC:10444\n$DBFF:D0 04     BNE $DC05                          A:66 X:8A Y:BB P:nvUbdIZC SP:FB PPU:286, 91 CYC:10446\n$DC01:E0 8A     CPX #$8A                           A:66 X:8A Y:BB P:nvUbdIZC SP:FB PPU:292, 91 CYC:10448\n$DC03:F0 04     BEQ $DC09                          A:66 X:8A Y:BB P:nvUbdIZC SP:FB PPU:298, 91 CYC:10450\n$DC09:24 01     BIT $01 = #$FF                       A:66 X:8A Y:BB P:nvUbdIZC SP:FB PPU:307, 91 CYC:10453\n$DC0B:38        SEC                                A:66 X:8A Y:BB P:E5 SP:FB PPU:316, 91 CYC:10456\n$DC0C:A0 44     LDY #$44                           A:66 X:8A Y:BB P:E5 SP:FB PPU:322, 91 CYC:10458\n$DC0E:A2 00     LDX #$00                           A:66 X:8A Y:44 P:65 SP:FB PPU:328, 91 CYC:10460\n$DC10:94 33     STY $33,X @ 33 = #$AA                A:66 X:00 Y:44 P:67 SP:FB PPU:334, 91 CYC:10462\n$DC12:A5 33     LDA $33 = #$44                       A:66 X:00 Y:44 P:67 SP:FB PPU:  5, 92 CYC:10466\n$DC14:90 18     BCC $DC2E                          A:44 X:00 Y:44 P:65 SP:FB PPU: 14, 92 CYC:10469\n$DC16:C9 44     CMP #$44                           A:44 X:00 Y:44 P:65 SP:FB PPU: 20, 92 CYC:10471\n$DC18:D0 14     BNE $DC2E                          A:44 X:00 Y:44 P:67 SP:FB PPU: 26, 92 CYC:10473\n$DC1A:50 12     BVC $DC2E                          A:44 X:00 Y:44 P:67 SP:FB PPU: 32, 92 CYC:10475\n$DC1C:18        CLC                                A:44 X:00 Y:44 P:67 SP:FB PPU: 38, 92 CYC:10477\n$DC1D:B8        CLV                                A:44 X:00 Y:44 P:nVUbdIZc SP:FB PPU: 44, 92 CYC:10479\n$DC1E:A0 99     LDY #$99                           A:44 X:00 Y:44 P:nvUbdIZc SP:FB PPU: 50, 92 CYC:10481\n$DC20:A2 80     LDX #$80                           A:44 X:00 Y:99 P:NvUbdIzc SP:FB PPU: 56, 92 CYC:10483\n$DC22:94 85     STY $85,X @ 05 = #$00                A:44 X:80 Y:99 P:NvUbdIzc SP:FB PPU: 62, 92 CYC:10485\n$DC24:A5 05     LDA $05 = #$99                       A:44 X:80 Y:99 P:NvUbdIzc SP:FB PPU: 74, 92 CYC:10489\n$DC26:B0 06     BCS $DC2E                          A:99 X:80 Y:99 P:NvUbdIzc SP:FB PPU: 83, 92 CYC:10492\n$DC28:C9 99     CMP #$99                           A:99 X:80 Y:99 P:NvUbdIzc SP:FB PPU: 89, 92 CYC:10494\n$DC2A:D0 02     BNE $DC2E                          A:99 X:80 Y:99 P:nvUbdIZC SP:FB PPU: 95, 92 CYC:10496\n$DC2C:50 04     BVC $DC32                          A:99 X:80 Y:99 P:nvUbdIZC SP:FB PPU:101, 92 CYC:10498\n$DC32:A0 0B     LDY #$0B                           A:99 X:80 Y:99 P:nvUbdIZC SP:FB PPU:110, 92 CYC:10501\n$DC34:A9 AA     LDA #$AA                           A:99 X:80 Y:0B P:25 SP:FB PPU:116, 92 CYC:10503\n$DC36:A2 78     LDX #$78                           A:AA X:80 Y:0B P:A5 SP:FB PPU:122, 92 CYC:10505\n$DC38:85 78     STA $78 = #$00                       A:AA X:78 Y:0B P:25 SP:FB PPU:128, 92 CYC:10507\n$DC3A:20 B6 F7  JSR $F7B6                          A:AA X:78 Y:0B P:25 SP:FB PPU:137, 92 CYC:10510\n$F7B6:18        CLC                                A:AA X:78 Y:0B P:25 SP:F9 PPU:155, 92 CYC:10516\n$F7B7:A9 FF     LDA #$FF                           A:AA X:78 Y:0B P:nvUbdIzc SP:F9 PPU:161, 92 CYC:10518\n$F7B9:85 01     STA $01 = #$FF                       A:FF X:78 Y:0B P:NvUbdIzc SP:F9 PPU:167, 92 CYC:10520\n$F7BB:24 01     BIT $01 = #$FF                       A:FF X:78 Y:0B P:NvUbdIzc SP:F9 PPU:176, 92 CYC:10523\n$F7BD:A9 55     LDA #$55                           A:FF X:78 Y:0B P:NVUbdIzc SP:F9 PPU:185, 92 CYC:10526\n$F7BF:60        RTS                                A:55 X:78 Y:0B P:64 SP:F9 PPU:191, 92 CYC:10528\n$DC3D:15 00     ORA $00,X @ 78 = #$AA                A:55 X:78 Y:0B P:64 SP:FB PPU:209, 92 CYC:10534\n$DC3F:20 C0 F7  JSR $F7C0                          A:FF X:78 Y:0B P:NVUbdIzc SP:FB PPU:221, 92 CYC:10538\n$F7C0:B0 09     BCS $F7CB                          A:FF X:78 Y:0B P:NVUbdIzc SP:F9 PPU:239, 92 CYC:10544\n$F7C2:10 07     BPL $F7CB                          A:FF X:78 Y:0B P:NVUbdIzc SP:F9 PPU:245, 92 CYC:10546\n$F7C4:C9 FF     CMP #$FF                           A:FF X:78 Y:0B P:NVUbdIzc SP:F9 PPU:251, 92 CYC:10548\n$F7C6:D0 03     BNE $F7CB                          A:FF X:78 Y:0B P:67 SP:F9 PPU:257, 92 CYC:10550\n$F7C8:50 01     BVC $F7CB                          A:FF X:78 Y:0B P:67 SP:F9 PPU:263, 92 CYC:10552\n$F7CA:60        RTS                                A:FF X:78 Y:0B P:67 SP:F9 PPU:269, 92 CYC:10554\n$DC42:C8        INY                                A:FF X:78 Y:0B P:67 SP:FB PPU:287, 92 CYC:10560\n$DC43:A9 00     LDA #$00                           A:FF X:78 Y:0C P:65 SP:FB PPU:293, 92 CYC:10562\n$DC45:85 78     STA $78 = #$AA                       A:00 X:78 Y:0C P:67 SP:FB PPU:299, 92 CYC:10564\n$DC47:20 CE F7  JSR $F7CE                          A:00 X:78 Y:0C P:67 SP:FB PPU:308, 92 CYC:10567\n$F7CE:38        SEC                                A:00 X:78 Y:0C P:67 SP:F9 PPU:326, 92 CYC:10573\n$F7CF:B8        CLV                                A:00 X:78 Y:0C P:67 SP:F9 PPU:332, 92 CYC:10575\n$F7D0:A9 00     LDA #$00                           A:00 X:78 Y:0C P:nvUbdIZC SP:F9 PPU:338, 92 CYC:10577\n$F7D2:60        RTS                                A:00 X:78 Y:0C P:nvUbdIZC SP:F9 PPU:  3, 93 CYC:10579\n$DC4A:15 00     ORA $00,X @ 78 = #$00                A:00 X:78 Y:0C P:nvUbdIZC SP:FB PPU: 21, 93 CYC:10585\n$DC4C:20 D3 F7  JSR $F7D3                          A:00 X:78 Y:0C P:nvUbdIZC SP:FB PPU: 33, 93 CYC:10589\n$F7D3:D0 07     BNE $F7DC                          A:00 X:78 Y:0C P:nvUbdIZC SP:F9 PPU: 51, 93 CYC:10595\n$F7D5:70 05     BVS $F7DC                          A:00 X:78 Y:0C P:nvUbdIZC SP:F9 PPU: 57, 93 CYC:10597\n$F7D7:90 03     BCC $F7DC                          A:00 X:78 Y:0C P:nvUbdIZC SP:F9 PPU: 63, 93 CYC:10599\n$F7D9:30 01     BMI $F7DC                          A:00 X:78 Y:0C P:nvUbdIZC SP:F9 PPU: 69, 93 CYC:10601\n$F7DB:60        RTS                                A:00 X:78 Y:0C P:nvUbdIZC SP:F9 PPU: 75, 93 CYC:10603\n$DC4F:C8        INY                                A:00 X:78 Y:0C P:nvUbdIZC SP:FB PPU: 93, 93 CYC:10609\n$DC50:A9 AA     LDA #$AA                           A:00 X:78 Y:0D P:25 SP:FB PPU: 99, 93 CYC:10611\n$DC52:85 78     STA $78 = #$00                       A:AA X:78 Y:0D P:A5 SP:FB PPU:105, 93 CYC:10613\n$DC54:20 DF F7  JSR $F7DF                          A:AA X:78 Y:0D P:A5 SP:FB PPU:114, 93 CYC:10616\n$F7DF:18        CLC                                A:AA X:78 Y:0D P:A5 SP:F9 PPU:132, 93 CYC:10622\n$F7E0:24 01     BIT $01 = #$FF                       A:AA X:78 Y:0D P:NvUbdIzc SP:F9 PPU:138, 93 CYC:10624\n$F7E2:A9 55     LDA #$55                           A:AA X:78 Y:0D P:NVUbdIzc SP:F9 PPU:147, 93 CYC:10627\n$F7E4:60        RTS                                A:55 X:78 Y:0D P:64 SP:F9 PPU:153, 93 CYC:10629\n$DC57:35 00     AND $00,X @ 78 = #$AA                A:55 X:78 Y:0D P:64 SP:FB PPU:171, 93 CYC:10635\n$DC59:20 E5 F7  JSR $F7E5                          A:00 X:78 Y:0D P:nVUbdIZc SP:FB PPU:183, 93 CYC:10639\n$F7E5:D0 07     BNE $F7EE                          A:00 X:78 Y:0D P:nVUbdIZc SP:F9 PPU:201, 93 CYC:10645\n$F7E7:50 05     BVC $F7EE                          A:00 X:78 Y:0D P:nVUbdIZc SP:F9 PPU:207, 93 CYC:10647\n$F7E9:B0 03     BCS $F7EE                          A:00 X:78 Y:0D P:nVUbdIZc SP:F9 PPU:213, 93 CYC:10649\n$F7EB:30 01     BMI $F7EE                          A:00 X:78 Y:0D P:nVUbdIZc SP:F9 PPU:219, 93 CYC:10651\n$F7ED:60        RTS                                A:00 X:78 Y:0D P:nVUbdIZc SP:F9 PPU:225, 93 CYC:10653\n$DC5C:C8        INY                                A:00 X:78 Y:0D P:nVUbdIZc SP:FB PPU:243, 93 CYC:10659\n$DC5D:A9 EF     LDA #$EF                           A:00 X:78 Y:0E P:64 SP:FB PPU:249, 93 CYC:10661\n$DC5F:85 78     STA $78 = #$AA                       A:EF X:78 Y:0E P:NVUbdIzc SP:FB PPU:255, 93 CYC:10663\n$DC61:20 F1 F7  JSR $F7F1                          A:EF X:78 Y:0E P:NVUbdIzc SP:FB PPU:264, 93 CYC:10666\n$F7F1:38        SEC                                A:EF X:78 Y:0E P:NVUbdIzc SP:F9 PPU:282, 93 CYC:10672\n$F7F2:B8        CLV                                A:EF X:78 Y:0E P:E5 SP:F9 PPU:288, 93 CYC:10674\n$F7F3:A9 F8     LDA #$F8                           A:EF X:78 Y:0E P:A5 SP:F9 PPU:294, 93 CYC:10676\n$F7F5:60        RTS                                A:F8 X:78 Y:0E P:A5 SP:F9 PPU:300, 93 CYC:10678\n$DC64:35 00     AND $00,X @ 78 = #$EF                A:F8 X:78 Y:0E P:A5 SP:FB PPU:318, 93 CYC:10684\n$DC66:20 F6 F7  JSR $F7F6                          A:E8 X:78 Y:0E P:A5 SP:FB PPU:330, 93 CYC:10688\n$F7F6:90 09     BCC $F801                          A:E8 X:78 Y:0E P:A5 SP:F9 PPU:  7, 94 CYC:10694\n$F7F8:10 07     BPL $F801                          A:E8 X:78 Y:0E P:A5 SP:F9 PPU: 13, 94 CYC:10696\n$F7FA:C9 E8     CMP #$E8                           A:E8 X:78 Y:0E P:A5 SP:F9 PPU: 19, 94 CYC:10698\n$F7FC:D0 03     BNE $F801                          A:E8 X:78 Y:0E P:nvUbdIZC SP:F9 PPU: 25, 94 CYC:10700\n$F7FE:70 01     BVS $F801                          A:E8 X:78 Y:0E P:nvUbdIZC SP:F9 PPU: 31, 94 CYC:10702\n$F800:60        RTS                                A:E8 X:78 Y:0E P:nvUbdIZC SP:F9 PPU: 37, 94 CYC:10704\n$DC69:C8        INY                                A:E8 X:78 Y:0E P:nvUbdIZC SP:FB PPU: 55, 94 CYC:10710\n$DC6A:A9 AA     LDA #$AA                           A:E8 X:78 Y:0F P:25 SP:FB PPU: 61, 94 CYC:10712\n$DC6C:85 78     STA $78 = #$EF                       A:AA X:78 Y:0F P:A5 SP:FB PPU: 67, 94 CYC:10714\n$DC6E:20 04 F8  JSR $F804                          A:AA X:78 Y:0F P:A5 SP:FB PPU: 76, 94 CYC:10717\n$F804:18        CLC                                A:AA X:78 Y:0F P:A5 SP:F9 PPU: 94, 94 CYC:10723\n$F805:24 01     BIT $01 = #$FF                       A:AA X:78 Y:0F P:NvUbdIzc SP:F9 PPU:100, 94 CYC:10725\n$F807:A9 5F     LDA #$5F                           A:AA X:78 Y:0F P:NVUbdIzc SP:F9 PPU:109, 94 CYC:10728\n$F809:60        RTS                                A:5F X:78 Y:0F P:64 SP:F9 PPU:115, 94 CYC:10730\n$DC71:55 00     EOR $00,X @ 78 = #$AA                A:5F X:78 Y:0F P:64 SP:FB PPU:133, 94 CYC:10736\n$DC73:20 0A F8  JSR $F80A                          A:F5 X:78 Y:0F P:NVUbdIzc SP:FB PPU:145, 94 CYC:10740\n$F80A:B0 09     BCS $F815                          A:F5 X:78 Y:0F P:NVUbdIzc SP:F9 PPU:163, 94 CYC:10746\n$F80C:10 07     BPL $F815                          A:F5 X:78 Y:0F P:NVUbdIzc SP:F9 PPU:169, 94 CYC:10748\n$F80E:C9 F5     CMP #$F5                           A:F5 X:78 Y:0F P:NVUbdIzc SP:F9 PPU:175, 94 CYC:10750\n$F810:D0 03     BNE $F815                          A:F5 X:78 Y:0F P:67 SP:F9 PPU:181, 94 CYC:10752\n$F812:50 01     BVC $F815                          A:F5 X:78 Y:0F P:67 SP:F9 PPU:187, 94 CYC:10754\n$F814:60        RTS                                A:F5 X:78 Y:0F P:67 SP:F9 PPU:193, 94 CYC:10756\n$DC76:C8        INY                                A:F5 X:78 Y:0F P:67 SP:FB PPU:211, 94 CYC:10762\n$DC77:A9 70     LDA #$70                           A:F5 X:78 Y:10 P:65 SP:FB PPU:217, 94 CYC:10764\n$DC79:85 78     STA $78 = #$AA                       A:70 X:78 Y:10 P:65 SP:FB PPU:223, 94 CYC:10766\n$DC7B:20 18 F8  JSR $F818                          A:70 X:78 Y:10 P:65 SP:FB PPU:232, 94 CYC:10769\n$F818:38        SEC                                A:70 X:78 Y:10 P:65 SP:F9 PPU:250, 94 CYC:10775\n$F819:B8        CLV                                A:70 X:78 Y:10 P:65 SP:F9 PPU:256, 94 CYC:10777\n$F81A:A9 70     LDA #$70                           A:70 X:78 Y:10 P:25 SP:F9 PPU:262, 94 CYC:10779\n$F81C:60        RTS                                A:70 X:78 Y:10 P:25 SP:F9 PPU:268, 94 CYC:10781\n$DC7E:55 00     EOR $00,X @ 78 = #$70                A:70 X:78 Y:10 P:25 SP:FB PPU:286, 94 CYC:10787\n$DC80:20 1D F8  JSR $F81D                          A:00 X:78 Y:10 P:nvUbdIZC SP:FB PPU:298, 94 CYC:10791\n$F81D:D0 07     BNE $F826                          A:00 X:78 Y:10 P:nvUbdIZC SP:F9 PPU:316, 94 CYC:10797\n$F81F:70 05     BVS $F826                          A:00 X:78 Y:10 P:nvUbdIZC SP:F9 PPU:322, 94 CYC:10799\n$F821:90 03     BCC $F826                          A:00 X:78 Y:10 P:nvUbdIZC SP:F9 PPU:328, 94 CYC:10801\n$F823:30 01     BMI $F826                          A:00 X:78 Y:10 P:nvUbdIZC SP:F9 PPU:334, 94 CYC:10803\n$F825:60        RTS                                A:00 X:78 Y:10 P:nvUbdIZC SP:F9 PPU:340, 94 CYC:10805\n$DC83:C8        INY                                A:00 X:78 Y:10 P:nvUbdIZC SP:FB PPU: 17, 95 CYC:10811\n$DC84:A9 69     LDA #$69                           A:00 X:78 Y:11 P:25 SP:FB PPU: 23, 95 CYC:10813\n$DC86:85 78     STA $78 = #$70                       A:69 X:78 Y:11 P:25 SP:FB PPU: 29, 95 CYC:10815\n$DC88:20 29 F8  JSR $F829                          A:69 X:78 Y:11 P:25 SP:FB PPU: 38, 95 CYC:10818\n$F829:18        CLC                                A:69 X:78 Y:11 P:25 SP:F9 PPU: 56, 95 CYC:10824\n$F82A:24 01     BIT $01 = #$FF                       A:69 X:78 Y:11 P:nvUbdIzc SP:F9 PPU: 62, 95 CYC:10826\n$F82C:A9 00     LDA #$00                           A:69 X:78 Y:11 P:NVUbdIzc SP:F9 PPU: 71, 95 CYC:10829\n$F82E:60        RTS                                A:00 X:78 Y:11 P:nVUbdIZc SP:F9 PPU: 77, 95 CYC:10831\n$DC8B:75 00     ADC $00,X @ 78 = #$69                A:00 X:78 Y:11 P:nVUbdIZc SP:FB PPU: 95, 95 CYC:10837\n$DC8D:20 2F F8  JSR $F82F                          A:69 X:78 Y:11 P:nvUbdIzc SP:FB PPU:107, 95 CYC:10841\n$F82F:30 09     BMI $F83A                          A:69 X:78 Y:11 P:nvUbdIzc SP:F9 PPU:125, 95 CYC:10847\n$F831:B0 07     BCS $F83A                          A:69 X:78 Y:11 P:nvUbdIzc SP:F9 PPU:131, 95 CYC:10849\n$F833:C9 69     CMP #$69                           A:69 X:78 Y:11 P:nvUbdIzc SP:F9 PPU:137, 95 CYC:10851\n$F835:D0 03     BNE $F83A                          A:69 X:78 Y:11 P:nvUbdIZC SP:F9 PPU:143, 95 CYC:10853\n$F837:70 01     BVS $F83A                          A:69 X:78 Y:11 P:nvUbdIZC SP:F9 PPU:149, 95 CYC:10855\n$F839:60        RTS                                A:69 X:78 Y:11 P:nvUbdIZC SP:F9 PPU:155, 95 CYC:10857\n$DC90:C8        INY                                A:69 X:78 Y:11 P:nvUbdIZC SP:FB PPU:173, 95 CYC:10863\n$DC91:20 3D F8  JSR $F83D                          A:69 X:78 Y:12 P:25 SP:FB PPU:179, 95 CYC:10865\n$F83D:38        SEC                                A:69 X:78 Y:12 P:25 SP:F9 PPU:197, 95 CYC:10871\n$F83E:24 01     BIT $01 = #$FF                       A:69 X:78 Y:12 P:25 SP:F9 PPU:203, 95 CYC:10873\n$F840:A9 00     LDA #$00                           A:69 X:78 Y:12 P:E5 SP:F9 PPU:212, 95 CYC:10876\n$F842:60        RTS                                A:00 X:78 Y:12 P:67 SP:F9 PPU:218, 95 CYC:10878\n$DC94:75 00     ADC $00,X @ 78 = #$69                A:00 X:78 Y:12 P:67 SP:FB PPU:236, 95 CYC:10884\n$DC96:20 43 F8  JSR $F843                          A:6A X:78 Y:12 P:nvUbdIzc SP:FB PPU:248, 95 CYC:10888\n$F843:30 09     BMI $F84E                          A:6A X:78 Y:12 P:nvUbdIzc SP:F9 PPU:266, 95 CYC:10894\n$F845:B0 07     BCS $F84E                          A:6A X:78 Y:12 P:nvUbdIzc SP:F9 PPU:272, 95 CYC:10896\n$F847:C9 6A     CMP #$6A                           A:6A X:78 Y:12 P:nvUbdIzc SP:F9 PPU:278, 95 CYC:10898\n$F849:D0 03     BNE $F84E                          A:6A X:78 Y:12 P:nvUbdIZC SP:F9 PPU:284, 95 CYC:10900\n$F84B:70 01     BVS $F84E                          A:6A X:78 Y:12 P:nvUbdIZC SP:F9 PPU:290, 95 CYC:10902\n$F84D:60        RTS                                A:6A X:78 Y:12 P:nvUbdIZC SP:F9 PPU:296, 95 CYC:10904\n$DC99:C8        INY                                A:6A X:78 Y:12 P:nvUbdIZC SP:FB PPU:314, 95 CYC:10910\n$DC9A:A9 7F     LDA #$7F                           A:6A X:78 Y:13 P:25 SP:FB PPU:320, 95 CYC:10912\n$DC9C:85 78     STA $78 = #$69                       A:7F X:78 Y:13 P:25 SP:FB PPU:326, 95 CYC:10914\n$DC9E:20 51 F8  JSR $F851                          A:7F X:78 Y:13 P:25 SP:FB PPU:335, 95 CYC:10917\n$F851:38        SEC                                A:7F X:78 Y:13 P:25 SP:F9 PPU: 12, 96 CYC:10923\n$F852:B8        CLV                                A:7F X:78 Y:13 P:25 SP:F9 PPU: 18, 96 CYC:10925\n$F853:A9 7F     LDA #$7F                           A:7F X:78 Y:13 P:25 SP:F9 PPU: 24, 96 CYC:10927\n$F855:60        RTS                                A:7F X:78 Y:13 P:25 SP:F9 PPU: 30, 96 CYC:10929\n$DCA1:75 00     ADC $00,X @ 78 = #$7F                A:7F X:78 Y:13 P:25 SP:FB PPU: 48, 96 CYC:10935\n$DCA3:20 56 F8  JSR $F856                          A:FF X:78 Y:13 P:NVUbdIzc SP:FB PPU: 60, 96 CYC:10939\n$F856:10 09     BPL $F861                          A:FF X:78 Y:13 P:NVUbdIzc SP:F9 PPU: 78, 96 CYC:10945\n$F858:B0 07     BCS $F861                          A:FF X:78 Y:13 P:NVUbdIzc SP:F9 PPU: 84, 96 CYC:10947\n$F85A:C9 FF     CMP #$FF                           A:FF X:78 Y:13 P:NVUbdIzc SP:F9 PPU: 90, 96 CYC:10949\n$F85C:D0 03     BNE $F861                          A:FF X:78 Y:13 P:67 SP:F9 PPU: 96, 96 CYC:10951\n$F85E:50 01     BVC $F861                          A:FF X:78 Y:13 P:67 SP:F9 PPU:102, 96 CYC:10953\n$F860:60        RTS                                A:FF X:78 Y:13 P:67 SP:F9 PPU:108, 96 CYC:10955\n$DCA6:C8        INY                                A:FF X:78 Y:13 P:67 SP:FB PPU:126, 96 CYC:10961\n$DCA7:A9 80     LDA #$80                           A:FF X:78 Y:14 P:65 SP:FB PPU:132, 96 CYC:10963\n$DCA9:85 78     STA $78 = #$7F                       A:80 X:78 Y:14 P:E5 SP:FB PPU:138, 96 CYC:10965\n$DCAB:20 64 F8  JSR $F864                          A:80 X:78 Y:14 P:E5 SP:FB PPU:147, 96 CYC:10968\n$F864:18        CLC                                A:80 X:78 Y:14 P:E5 SP:F9 PPU:165, 96 CYC:10974\n$F865:24 01     BIT $01 = #$FF                       A:80 X:78 Y:14 P:NVUbdIzc SP:F9 PPU:171, 96 CYC:10976\n$F867:A9 7F     LDA #$7F                           A:80 X:78 Y:14 P:NVUbdIzc SP:F9 PPU:180, 96 CYC:10979\n$F869:60        RTS                                A:7F X:78 Y:14 P:64 SP:F9 PPU:186, 96 CYC:10981\n$DCAE:75 00     ADC $00,X @ 78 = #$80                A:7F X:78 Y:14 P:64 SP:FB PPU:204, 96 CYC:10987\n$DCB0:20 6A F8  JSR $F86A                          A:FF X:78 Y:14 P:NvUbdIzc SP:FB PPU:216, 96 CYC:10991\n$F86A:10 09     BPL $F875                          A:FF X:78 Y:14 P:NvUbdIzc SP:F9 PPU:234, 96 CYC:10997\n$F86C:B0 07     BCS $F875                          A:FF X:78 Y:14 P:NvUbdIzc SP:F9 PPU:240, 96 CYC:10999\n$F86E:C9 FF     CMP #$FF                           A:FF X:78 Y:14 P:NvUbdIzc SP:F9 PPU:246, 96 CYC:11001\n$F870:D0 03     BNE $F875                          A:FF X:78 Y:14 P:nvUbdIZC SP:F9 PPU:252, 96 CYC:11003\n$F872:70 01     BVS $F875                          A:FF X:78 Y:14 P:nvUbdIZC SP:F9 PPU:258, 96 CYC:11005\n$F874:60        RTS                                A:FF X:78 Y:14 P:nvUbdIZC SP:F9 PPU:264, 96 CYC:11007\n$DCB3:C8        INY                                A:FF X:78 Y:14 P:nvUbdIZC SP:FB PPU:282, 96 CYC:11013\n$DCB4:20 78 F8  JSR $F878                          A:FF X:78 Y:15 P:25 SP:FB PPU:288, 96 CYC:11015\n$F878:38        SEC                                A:FF X:78 Y:15 P:25 SP:F9 PPU:306, 96 CYC:11021\n$F879:B8        CLV                                A:FF X:78 Y:15 P:25 SP:F9 PPU:312, 96 CYC:11023\n$F87A:A9 7F     LDA #$7F                           A:FF X:78 Y:15 P:25 SP:F9 PPU:318, 96 CYC:11025\n$F87C:60        RTS                                A:7F X:78 Y:15 P:25 SP:F9 PPU:324, 96 CYC:11027\n$DCB7:75 00     ADC $00,X @ 78 = #$80                A:7F X:78 Y:15 P:25 SP:FB PPU:  1, 97 CYC:11033\n$DCB9:20 7D F8  JSR $F87D                          A:00 X:78 Y:15 P:nvUbdIZC SP:FB PPU: 13, 97 CYC:11037\n$F87D:D0 07     BNE $F886                          A:00 X:78 Y:15 P:nvUbdIZC SP:F9 PPU: 31, 97 CYC:11043\n$F87F:30 05     BMI $F886                          A:00 X:78 Y:15 P:nvUbdIZC SP:F9 PPU: 37, 97 CYC:11045\n$F881:70 03     BVS $F886                          A:00 X:78 Y:15 P:nvUbdIZC SP:F9 PPU: 43, 97 CYC:11047\n$F883:90 01     BCC $F886                          A:00 X:78 Y:15 P:nvUbdIZC SP:F9 PPU: 49, 97 CYC:11049\n$F885:60        RTS                                A:00 X:78 Y:15 P:nvUbdIZC SP:F9 PPU: 55, 97 CYC:11051\n$DCBC:C8        INY                                A:00 X:78 Y:15 P:nvUbdIZC SP:FB PPU: 73, 97 CYC:11057\n$DCBD:A9 40     LDA #$40                           A:00 X:78 Y:16 P:25 SP:FB PPU: 79, 97 CYC:11059\n$DCBF:85 78     STA $78 = #$80                       A:40 X:78 Y:16 P:25 SP:FB PPU: 85, 97 CYC:11061\n$DCC1:20 89 F8  JSR $F889                          A:40 X:78 Y:16 P:25 SP:FB PPU: 94, 97 CYC:11064\n$F889:24 01     BIT $01 = #$FF                       A:40 X:78 Y:16 P:25 SP:F9 PPU:112, 97 CYC:11070\n$F88B:A9 40     LDA #$40                           A:40 X:78 Y:16 P:E5 SP:F9 PPU:121, 97 CYC:11073\n$F88D:60        RTS                                A:40 X:78 Y:16 P:65 SP:F9 PPU:127, 97 CYC:11075\n$DCC4:D5 00     CMP $00,X @ 78 = #$40                A:40 X:78 Y:16 P:65 SP:FB PPU:145, 97 CYC:11081\n$DCC6:20 8E F8  JSR $F88E                          A:40 X:78 Y:16 P:67 SP:FB PPU:157, 97 CYC:11085\n$F88E:30 07     BMI $F897                          A:40 X:78 Y:16 P:67 SP:F9 PPU:175, 97 CYC:11091\n$F890:90 05     BCC $F897                          A:40 X:78 Y:16 P:67 SP:F9 PPU:181, 97 CYC:11093\n$F892:D0 03     BNE $F897                          A:40 X:78 Y:16 P:67 SP:F9 PPU:187, 97 CYC:11095\n$F894:50 01     BVC $F897                          A:40 X:78 Y:16 P:67 SP:F9 PPU:193, 97 CYC:11097\n$F896:60        RTS                                A:40 X:78 Y:16 P:67 SP:F9 PPU:199, 97 CYC:11099\n$DCC9:C8        INY                                A:40 X:78 Y:16 P:67 SP:FB PPU:217, 97 CYC:11105\n$DCCA:48        PHA                                A:40 X:78 Y:17 P:65 SP:FB PPU:223, 97 CYC:11107\n$DCCB:A9 3F     LDA #$3F                           A:40 X:78 Y:17 P:65 SP:FA PPU:232, 97 CYC:11110\n$DCCD:85 78     STA $78 = #$40                       A:3F X:78 Y:17 P:65 SP:FA PPU:238, 97 CYC:11112\n$DCCF:68        PLA                                A:3F X:78 Y:17 P:65 SP:FA PPU:247, 97 CYC:11115\n$DCD0:20 9A F8  JSR $F89A                          A:40 X:78 Y:17 P:65 SP:FB PPU:259, 97 CYC:11119\n$F89A:B8        CLV                                A:40 X:78 Y:17 P:65 SP:F9 PPU:277, 97 CYC:11125\n$F89B:60        RTS                                A:40 X:78 Y:17 P:25 SP:F9 PPU:283, 97 CYC:11127\n$DCD3:D5 00     CMP $00,X @ 78 = #$3F                A:40 X:78 Y:17 P:25 SP:FB PPU:301, 97 CYC:11133\n$DCD5:20 9C F8  JSR $F89C                          A:40 X:78 Y:17 P:25 SP:FB PPU:313, 97 CYC:11137\n$F89C:F0 07     BEQ $F8A5                          A:40 X:78 Y:17 P:25 SP:F9 PPU:331, 97 CYC:11143\n$F89E:30 05     BMI $F8A5                          A:40 X:78 Y:17 P:25 SP:F9 PPU:337, 97 CYC:11145\n$F8A0:90 03     BCC $F8A5                          A:40 X:78 Y:17 P:25 SP:F9 PPU:  2, 98 CYC:11147\n$F8A2:70 01     BVS $F8A5                          A:40 X:78 Y:17 P:25 SP:F9 PPU:  8, 98 CYC:11149\n$F8A4:60        RTS                                A:40 X:78 Y:17 P:25 SP:F9 PPU: 14, 98 CYC:11151\n$DCD8:C8        INY                                A:40 X:78 Y:17 P:25 SP:FB PPU: 32, 98 CYC:11157\n$DCD9:48        PHA                                A:40 X:78 Y:18 P:25 SP:FB PPU: 38, 98 CYC:11159\n$DCDA:A9 41     LDA #$41                           A:40 X:78 Y:18 P:25 SP:FA PPU: 47, 98 CYC:11162\n$DCDC:85 78     STA $78 = #$3F                       A:41 X:78 Y:18 P:25 SP:FA PPU: 53, 98 CYC:11164\n$DCDE:68        PLA                                A:41 X:78 Y:18 P:25 SP:FA PPU: 62, 98 CYC:11167\n$DCDF:D5 00     CMP $00,X @ 78 = #$41                A:40 X:78 Y:18 P:25 SP:FB PPU: 74, 98 CYC:11171\n$DCE1:20 A8 F8  JSR $F8A8                          A:40 X:78 Y:18 P:NvUbdIzc SP:FB PPU: 86, 98 CYC:11175\n$F8A8:F0 05     BEQ $F8AF                          A:40 X:78 Y:18 P:NvUbdIzc SP:F9 PPU:104, 98 CYC:11181\n$F8AA:10 03     BPL $F8AF                          A:40 X:78 Y:18 P:NvUbdIzc SP:F9 PPU:110, 98 CYC:11183\n$F8AC:10 01     BPL $F8AF                          A:40 X:78 Y:18 P:NvUbdIzc SP:F9 PPU:116, 98 CYC:11185\n$F8AE:60        RTS                                A:40 X:78 Y:18 P:NvUbdIzc SP:F9 PPU:122, 98 CYC:11187\n$DCE4:C8        INY                                A:40 X:78 Y:18 P:NvUbdIzc SP:FB PPU:140, 98 CYC:11193\n$DCE5:48        PHA                                A:40 X:78 Y:19 P:nvUbdIzc SP:FB PPU:146, 98 CYC:11195\n$DCE6:A9 00     LDA #$00                           A:40 X:78 Y:19 P:nvUbdIzc SP:FA PPU:155, 98 CYC:11198\n$DCE8:85 78     STA $78 = #$41                       A:00 X:78 Y:19 P:nvUbdIZc SP:FA PPU:161, 98 CYC:11200\n$DCEA:68        PLA                                A:00 X:78 Y:19 P:nvUbdIZc SP:FA PPU:170, 98 CYC:11203\n$DCEB:20 B2 F8  JSR $F8B2                          A:40 X:78 Y:19 P:nvUbdIzc SP:FB PPU:182, 98 CYC:11207\n$F8B2:A9 80     LDA #$80                           A:40 X:78 Y:19 P:nvUbdIzc SP:F9 PPU:200, 98 CYC:11213\n$F8B4:60        RTS                                A:80 X:78 Y:19 P:NvUbdIzc SP:F9 PPU:206, 98 CYC:11215\n$DCEE:D5 00     CMP $00,X @ 78 = #$00                A:80 X:78 Y:19 P:NvUbdIzc SP:FB PPU:224, 98 CYC:11221\n$DCF0:20 B5 F8  JSR $F8B5                          A:80 X:78 Y:19 P:A5 SP:FB PPU:236, 98 CYC:11225\n$F8B5:F0 05     BEQ $F8BC                          A:80 X:78 Y:19 P:A5 SP:F9 PPU:254, 98 CYC:11231\n$F8B7:10 03     BPL $F8BC                          A:80 X:78 Y:19 P:A5 SP:F9 PPU:260, 98 CYC:11233\n$F8B9:90 01     BCC $F8BC                          A:80 X:78 Y:19 P:A5 SP:F9 PPU:266, 98 CYC:11235\n$F8BB:60        RTS                                A:80 X:78 Y:19 P:A5 SP:F9 PPU:272, 98 CYC:11237\n$DCF3:C8        INY                                A:80 X:78 Y:19 P:A5 SP:FB PPU:290, 98 CYC:11243\n$DCF4:48        PHA                                A:80 X:78 Y:1A P:25 SP:FB PPU:296, 98 CYC:11245\n$DCF5:A9 80     LDA #$80                           A:80 X:78 Y:1A P:25 SP:FA PPU:305, 98 CYC:11248\n$DCF7:85 78     STA $78 = #$00                       A:80 X:78 Y:1A P:A5 SP:FA PPU:311, 98 CYC:11250\n$DCF9:68        PLA                                A:80 X:78 Y:1A P:A5 SP:FA PPU:320, 98 CYC:11253\n$DCFA:D5 00     CMP $00,X @ 78 = #$80                A:80 X:78 Y:1A P:A5 SP:FB PPU:332, 98 CYC:11257\n$DCFC:20 BF F8  JSR $F8BF                          A:80 X:78 Y:1A P:nvUbdIZC SP:FB PPU:  3, 99 CYC:11261\n$F8BF:D0 05     BNE $F8C6                          A:80 X:78 Y:1A P:nvUbdIZC SP:F9 PPU: 21, 99 CYC:11267\n$F8C1:30 03     BMI $F8C6                          A:80 X:78 Y:1A P:nvUbdIZC SP:F9 PPU: 27, 99 CYC:11269\n$F8C3:90 01     BCC $F8C6                          A:80 X:78 Y:1A P:nvUbdIZC SP:F9 PPU: 33, 99 CYC:11271\n$F8C5:60        RTS                                A:80 X:78 Y:1A P:nvUbdIZC SP:F9 PPU: 39, 99 CYC:11273\n$DCFF:C8        INY                                A:80 X:78 Y:1A P:nvUbdIZC SP:FB PPU: 57, 99 CYC:11279\n$DD00:48        PHA                                A:80 X:78 Y:1B P:25 SP:FB PPU: 63, 99 CYC:11281\n$DD01:A9 81     LDA #$81                           A:80 X:78 Y:1B P:25 SP:FA PPU: 72, 99 CYC:11284\n$DD03:85 78     STA $78 = #$80                       A:81 X:78 Y:1B P:A5 SP:FA PPU: 78, 99 CYC:11286\n$DD05:68        PLA                                A:81 X:78 Y:1B P:A5 SP:FA PPU: 87, 99 CYC:11289\n$DD06:D5 00     CMP $00,X @ 78 = #$81                A:80 X:78 Y:1B P:A5 SP:FB PPU: 99, 99 CYC:11293\n$DD08:20 C9 F8  JSR $F8C9                          A:80 X:78 Y:1B P:NvUbdIzc SP:FB PPU:111, 99 CYC:11297\n$F8C9:B0 05     BCS $F8D0                          A:80 X:78 Y:1B P:NvUbdIzc SP:F9 PPU:129, 99 CYC:11303\n$F8CB:F0 03     BEQ $F8D0                          A:80 X:78 Y:1B P:NvUbdIzc SP:F9 PPU:135, 99 CYC:11305\n$F8CD:10 01     BPL $F8D0                          A:80 X:78 Y:1B P:NvUbdIzc SP:F9 PPU:141, 99 CYC:11307\n$F8CF:60        RTS                                A:80 X:78 Y:1B P:NvUbdIzc SP:F9 PPU:147, 99 CYC:11309\n$DD0B:C8        INY                                A:80 X:78 Y:1B P:NvUbdIzc SP:FB PPU:165, 99 CYC:11315\n$DD0C:48        PHA                                A:80 X:78 Y:1C P:nvUbdIzc SP:FB PPU:171, 99 CYC:11317\n$DD0D:A9 7F     LDA #$7F                           A:80 X:78 Y:1C P:nvUbdIzc SP:FA PPU:180, 99 CYC:11320\n$DD0F:85 78     STA $78 = #$81                       A:7F X:78 Y:1C P:nvUbdIzc SP:FA PPU:186, 99 CYC:11322\n$DD11:68        PLA                                A:7F X:78 Y:1C P:nvUbdIzc SP:FA PPU:195, 99 CYC:11325\n$DD12:D5 00     CMP $00,X @ 78 = #$7F                A:80 X:78 Y:1C P:NvUbdIzc SP:FB PPU:207, 99 CYC:11329\n$DD14:20 D3 F8  JSR $F8D3                          A:80 X:78 Y:1C P:25 SP:FB PPU:219, 99 CYC:11333\n$F8D3:90 05     BCC $F8DA                          A:80 X:78 Y:1C P:25 SP:F9 PPU:237, 99 CYC:11339\n$F8D5:F0 03     BEQ $F8DA                          A:80 X:78 Y:1C P:25 SP:F9 PPU:243, 99 CYC:11341\n$F8D7:30 01     BMI $F8DA                          A:80 X:78 Y:1C P:25 SP:F9 PPU:249, 99 CYC:11343\n$F8D9:60        RTS                                A:80 X:78 Y:1C P:25 SP:F9 PPU:255, 99 CYC:11345\n$DD17:C8        INY                                A:80 X:78 Y:1C P:25 SP:FB PPU:273, 99 CYC:11351\n$DD18:A9 40     LDA #$40                           A:80 X:78 Y:1D P:25 SP:FB PPU:279, 99 CYC:11353\n$DD1A:85 78     STA $78 = #$7F                       A:40 X:78 Y:1D P:25 SP:FB PPU:285, 99 CYC:11355\n$DD1C:20 31 F9  JSR $F931                          A:40 X:78 Y:1D P:25 SP:FB PPU:294, 99 CYC:11358\n$F931:24 01     BIT $01 = #$FF                       A:40 X:78 Y:1D P:25 SP:F9 PPU:312, 99 CYC:11364\n$F933:A9 40     LDA #$40                           A:40 X:78 Y:1D P:E5 SP:F9 PPU:321, 99 CYC:11367\n$F935:38        SEC                                A:40 X:78 Y:1D P:65 SP:F9 PPU:327, 99 CYC:11369\n$F936:60        RTS                                A:40 X:78 Y:1D P:65 SP:F9 PPU:333, 99 CYC:11371\n$DD1F:F5 00     SBC $00,X @ 78 = #$40                A:40 X:78 Y:1D P:65 SP:FB PPU: 10,100 CYC:11377\n$DD21:20 37 F9  JSR $F937                          A:00 X:78 Y:1D P:nvUbdIZC SP:FB PPU: 22,100 CYC:11381\n$F937:30 0B     BMI $F944                          A:00 X:78 Y:1D P:nvUbdIZC SP:F9 PPU: 40,100 CYC:11387\n$F939:90 09     BCC $F944                          A:00 X:78 Y:1D P:nvUbdIZC SP:F9 PPU: 46,100 CYC:11389\n$F93B:D0 07     BNE $F944                          A:00 X:78 Y:1D P:nvUbdIZC SP:F9 PPU: 52,100 CYC:11391\n$F93D:70 05     BVS $F944                          A:00 X:78 Y:1D P:nvUbdIZC SP:F9 PPU: 58,100 CYC:11393\n$F93F:C9 00     CMP #$00                           A:00 X:78 Y:1D P:nvUbdIZC SP:F9 PPU: 64,100 CYC:11395\n$F941:D0 01     BNE $F944                          A:00 X:78 Y:1D P:nvUbdIZC SP:F9 PPU: 70,100 CYC:11397\n$F943:60        RTS                                A:00 X:78 Y:1D P:nvUbdIZC SP:F9 PPU: 76,100 CYC:11399\n$DD24:C8        INY                                A:00 X:78 Y:1D P:nvUbdIZC SP:FB PPU: 94,100 CYC:11405\n$DD25:A9 3F     LDA #$3F                           A:00 X:78 Y:1E P:25 SP:FB PPU:100,100 CYC:11407\n$DD27:85 78     STA $78 = #$40                       A:3F X:78 Y:1E P:25 SP:FB PPU:106,100 CYC:11409\n$DD29:20 47 F9  JSR $F947                          A:3F X:78 Y:1E P:25 SP:FB PPU:115,100 CYC:11412\n$F947:B8        CLV                                A:3F X:78 Y:1E P:25 SP:F9 PPU:133,100 CYC:11418\n$F948:38        SEC                                A:3F X:78 Y:1E P:25 SP:F9 PPU:139,100 CYC:11420\n$F949:A9 40     LDA #$40                           A:3F X:78 Y:1E P:25 SP:F9 PPU:145,100 CYC:11422\n$F94B:60        RTS                                A:40 X:78 Y:1E P:25 SP:F9 PPU:151,100 CYC:11424\n$DD2C:F5 00     SBC $00,X @ 78 = #$3F                A:40 X:78 Y:1E P:25 SP:FB PPU:169,100 CYC:11430\n$DD2E:20 4C F9  JSR $F94C                          A:01 X:78 Y:1E P:25 SP:FB PPU:181,100 CYC:11434\n$F94C:F0 0B     BEQ $F959                          A:01 X:78 Y:1E P:25 SP:F9 PPU:199,100 CYC:11440\n$F94E:30 09     BMI $F959                          A:01 X:78 Y:1E P:25 SP:F9 PPU:205,100 CYC:11442\n$F950:90 07     BCC $F959                          A:01 X:78 Y:1E P:25 SP:F9 PPU:211,100 CYC:11444\n$F952:70 05     BVS $F959                          A:01 X:78 Y:1E P:25 SP:F9 PPU:217,100 CYC:11446\n$F954:C9 01     CMP #$01                           A:01 X:78 Y:1E P:25 SP:F9 PPU:223,100 CYC:11448\n$F956:D0 01     BNE $F959                          A:01 X:78 Y:1E P:nvUbdIZC SP:F9 PPU:229,100 CYC:11450\n$F958:60        RTS                                A:01 X:78 Y:1E P:nvUbdIZC SP:F9 PPU:235,100 CYC:11452\n$DD31:C8        INY                                A:01 X:78 Y:1E P:nvUbdIZC SP:FB PPU:253,100 CYC:11458\n$DD32:A9 41     LDA #$41                           A:01 X:78 Y:1F P:25 SP:FB PPU:259,100 CYC:11460\n$DD34:85 78     STA $78 = #$3F                       A:41 X:78 Y:1F P:25 SP:FB PPU:265,100 CYC:11462\n$DD36:20 5C F9  JSR $F95C                          A:41 X:78 Y:1F P:25 SP:FB PPU:274,100 CYC:11465\n$F95C:A9 40     LDA #$40                           A:41 X:78 Y:1F P:25 SP:F9 PPU:292,100 CYC:11471\n$F95E:38        SEC                                A:40 X:78 Y:1F P:25 SP:F9 PPU:298,100 CYC:11473\n$F95F:24 01     BIT $01 = #$FF                       A:40 X:78 Y:1F P:25 SP:F9 PPU:304,100 CYC:11475\n$F961:60        RTS                                A:40 X:78 Y:1F P:E5 SP:F9 PPU:313,100 CYC:11478\n$DD39:F5 00     SBC $00,X @ 78 = #$41                A:40 X:78 Y:1F P:E5 SP:FB PPU:331,100 CYC:11484\n$DD3B:20 62 F9  JSR $F962                          A:FF X:78 Y:1F P:NvUbdIzc SP:FB PPU:  2,101 CYC:11488\n$F962:B0 0B     BCS $F96F                          A:FF X:78 Y:1F P:NvUbdIzc SP:F9 PPU: 20,101 CYC:11494\n$F964:F0 09     BEQ $F96F                          A:FF X:78 Y:1F P:NvUbdIzc SP:F9 PPU: 26,101 CYC:11496\n$F966:10 07     BPL $F96F                          A:FF X:78 Y:1F P:NvUbdIzc SP:F9 PPU: 32,101 CYC:11498\n$F968:70 05     BVS $F96F                          A:FF X:78 Y:1F P:NvUbdIzc SP:F9 PPU: 38,101 CYC:11500\n$F96A:C9 FF     CMP #$FF                           A:FF X:78 Y:1F P:NvUbdIzc SP:F9 PPU: 44,101 CYC:11502\n$F96C:D0 01     BNE $F96F                          A:FF X:78 Y:1F P:nvUbdIZC SP:F9 PPU: 50,101 CYC:11504\n$F96E:60        RTS                                A:FF X:78 Y:1F P:nvUbdIZC SP:F9 PPU: 56,101 CYC:11506\n$DD3E:C8        INY                                A:FF X:78 Y:1F P:nvUbdIZC SP:FB PPU: 74,101 CYC:11512\n$DD3F:A9 00     LDA #$00                           A:FF X:78 Y:20 P:25 SP:FB PPU: 80,101 CYC:11514\n$DD41:85 78     STA $78 = #$41                       A:00 X:78 Y:20 P:nvUbdIZC SP:FB PPU: 86,101 CYC:11516\n$DD43:20 72 F9  JSR $F972                          A:00 X:78 Y:20 P:nvUbdIZC SP:FB PPU: 95,101 CYC:11519\n$F972:18        CLC                                A:00 X:78 Y:20 P:nvUbdIZC SP:F9 PPU:113,101 CYC:11525\n$F973:A9 80     LDA #$80                           A:00 X:78 Y:20 P:nvUbdIZc SP:F9 PPU:119,101 CYC:11527\n$F975:60        RTS                                A:80 X:78 Y:20 P:NvUbdIzc SP:F9 PPU:125,101 CYC:11529\n$DD46:F5 00     SBC $00,X @ 78 = #$00                A:80 X:78 Y:20 P:NvUbdIzc SP:FB PPU:143,101 CYC:11535\n$DD48:20 76 F9  JSR $F976                          A:7F X:78 Y:20 P:65 SP:FB PPU:155,101 CYC:11539\n$F976:90 05     BCC $F97D                          A:7F X:78 Y:20 P:65 SP:F9 PPU:173,101 CYC:11545\n$F978:C9 7F     CMP #$7F                           A:7F X:78 Y:20 P:65 SP:F9 PPU:179,101 CYC:11547\n$F97A:D0 01     BNE $F97D                          A:7F X:78 Y:20 P:67 SP:F9 PPU:185,101 CYC:11549\n$F97C:60        RTS                                A:7F X:78 Y:20 P:67 SP:F9 PPU:191,101 CYC:11551\n$DD4B:C8        INY                                A:7F X:78 Y:20 P:67 SP:FB PPU:209,101 CYC:11557\n$DD4C:A9 7F     LDA #$7F                           A:7F X:78 Y:21 P:65 SP:FB PPU:215,101 CYC:11559\n$DD4E:85 78     STA $78 = #$00                       A:7F X:78 Y:21 P:65 SP:FB PPU:221,101 CYC:11561\n$DD50:20 80 F9  JSR $F980                          A:7F X:78 Y:21 P:65 SP:FB PPU:230,101 CYC:11564\n$F980:38        SEC                                A:7F X:78 Y:21 P:65 SP:F9 PPU:248,101 CYC:11570\n$F981:A9 81     LDA #$81                           A:7F X:78 Y:21 P:65 SP:F9 PPU:254,101 CYC:11572\n$F983:60        RTS                                A:81 X:78 Y:21 P:E5 SP:F9 PPU:260,101 CYC:11574\n$DD53:F5 00     SBC $00,X @ 78 = #$7F                A:81 X:78 Y:21 P:E5 SP:FB PPU:278,101 CYC:11580\n$DD55:20 84 F9  JSR $F984                          A:02 X:78 Y:21 P:65 SP:FB PPU:290,101 CYC:11584\n$F984:50 07     BVC $F98D                          A:02 X:78 Y:21 P:65 SP:F9 PPU:308,101 CYC:11590\n$F986:90 05     BCC $F98D                          A:02 X:78 Y:21 P:65 SP:F9 PPU:314,101 CYC:11592\n$F988:C9 02     CMP #$02                           A:02 X:78 Y:21 P:65 SP:F9 PPU:320,101 CYC:11594\n$F98A:D0 01     BNE $F98D                          A:02 X:78 Y:21 P:67 SP:F9 PPU:326,101 CYC:11596\n$F98C:60        RTS                                A:02 X:78 Y:21 P:67 SP:F9 PPU:332,101 CYC:11598\n$DD58:A9 AA     LDA #$AA                           A:02 X:78 Y:21 P:67 SP:FB PPU:  9,102 CYC:11604\n$DD5A:85 33     STA $33 = #$44                       A:AA X:78 Y:21 P:E5 SP:FB PPU: 15,102 CYC:11606\n$DD5C:A9 BB     LDA #$BB                           A:AA X:78 Y:21 P:E5 SP:FB PPU: 24,102 CYC:11609\n$DD5E:85 89     STA $89 = #$BB                       A:BB X:78 Y:21 P:E5 SP:FB PPU: 30,102 CYC:11611\n$DD60:A2 00     LDX #$00                           A:BB X:78 Y:21 P:E5 SP:FB PPU: 39,102 CYC:11614\n$DD62:A0 66     LDY #$66                           A:BB X:00 Y:21 P:67 SP:FB PPU: 45,102 CYC:11616\n$DD64:24 01     BIT $01 = #$FF                       A:BB X:00 Y:66 P:65 SP:FB PPU: 51,102 CYC:11618\n$DD66:38        SEC                                A:BB X:00 Y:66 P:E5 SP:FB PPU: 60,102 CYC:11621\n$DD67:A9 00     LDA #$00                           A:BB X:00 Y:66 P:E5 SP:FB PPU: 66,102 CYC:11623\n$DD69:B5 33     LDA $33,X @ 33 = #$AA                A:00 X:00 Y:66 P:67 SP:FB PPU: 72,102 CYC:11625\n$DD6B:10 12     BPL $DD7F                          A:AA X:00 Y:66 P:E5 SP:FB PPU: 84,102 CYC:11629\n$DD6D:F0 10     BEQ $DD7F                          A:AA X:00 Y:66 P:E5 SP:FB PPU: 90,102 CYC:11631\n$DD6F:50 0E     BVC $DD7F                          A:AA X:00 Y:66 P:E5 SP:FB PPU: 96,102 CYC:11633\n$DD71:90 0C     BCC $DD7F                          A:AA X:00 Y:66 P:E5 SP:FB PPU:102,102 CYC:11635\n$DD73:C0 66     CPY #$66                           A:AA X:00 Y:66 P:E5 SP:FB PPU:108,102 CYC:11637\n$DD75:D0 08     BNE $DD7F                          A:AA X:00 Y:66 P:67 SP:FB PPU:114,102 CYC:11639\n$DD77:E0 00     CPX #$00                           A:AA X:00 Y:66 P:67 SP:FB PPU:120,102 CYC:11641\n$DD79:D0 04     BNE $DD7F                          A:AA X:00 Y:66 P:67 SP:FB PPU:126,102 CYC:11643\n$DD7B:C9 AA     CMP #$AA                           A:AA X:00 Y:66 P:67 SP:FB PPU:132,102 CYC:11645\n$DD7D:F0 04     BEQ $DD83                          A:AA X:00 Y:66 P:67 SP:FB PPU:138,102 CYC:11647\n$DD83:A2 8A     LDX #$8A                           A:AA X:00 Y:66 P:67 SP:FB PPU:147,102 CYC:11650\n$DD85:A0 66     LDY #$66                           A:AA X:8A Y:66 P:E5 SP:FB PPU:153,102 CYC:11652\n$DD87:B8        CLV                                A:AA X:8A Y:66 P:65 SP:FB PPU:159,102 CYC:11654\n$DD88:18        CLC                                A:AA X:8A Y:66 P:25 SP:FB PPU:165,102 CYC:11656\n$DD89:A9 00     LDA #$00                           A:AA X:8A Y:66 P:nvUbdIzc SP:FB PPU:171,102 CYC:11658\n$DD8B:B5 FF     LDA $FF,X @ 89 = #$BB                A:00 X:8A Y:66 P:nvUbdIZc SP:FB PPU:177,102 CYC:11660\n$DD8D:10 12     BPL $DDA1                          A:BB X:8A Y:66 P:NvUbdIzc SP:FB PPU:189,102 CYC:11664\n$DD8F:F0 10     BEQ $DDA1                          A:BB X:8A Y:66 P:NvUbdIzc SP:FB PPU:195,102 CYC:11666\n$DD91:70 0E     BVS $DDA1                          A:BB X:8A Y:66 P:NvUbdIzc SP:FB PPU:201,102 CYC:11668\n$DD93:B0 0C     BCS $DDA1                          A:BB X:8A Y:66 P:NvUbdIzc SP:FB PPU:207,102 CYC:11670\n$DD95:C9 BB     CMP #$BB                           A:BB X:8A Y:66 P:NvUbdIzc SP:FB PPU:213,102 CYC:11672\n$DD97:D0 08     BNE $DDA1                          A:BB X:8A Y:66 P:nvUbdIZC SP:FB PPU:219,102 CYC:11674\n$DD99:C0 66     CPY #$66                           A:BB X:8A Y:66 P:nvUbdIZC SP:FB PPU:225,102 CYC:11676\n$DD9B:D0 04     BNE $DDA1                          A:BB X:8A Y:66 P:nvUbdIZC SP:FB PPU:231,102 CYC:11678\n$DD9D:E0 8A     CPX #$8A                           A:BB X:8A Y:66 P:nvUbdIZC SP:FB PPU:237,102 CYC:11680\n$DD9F:F0 04     BEQ $DDA5                          A:BB X:8A Y:66 P:nvUbdIZC SP:FB PPU:243,102 CYC:11682\n$DDA5:24 01     BIT $01 = #$FF                       A:BB X:8A Y:66 P:nvUbdIZC SP:FB PPU:252,102 CYC:11685\n$DDA7:38        SEC                                A:BB X:8A Y:66 P:E5 SP:FB PPU:261,102 CYC:11688\n$DDA8:A9 44     LDA #$44                           A:BB X:8A Y:66 P:E5 SP:FB PPU:267,102 CYC:11690\n$DDAA:A2 00     LDX #$00                           A:44 X:8A Y:66 P:65 SP:FB PPU:273,102 CYC:11692\n$DDAC:95 33     STA $33,X @ 33 = #$AA                A:44 X:00 Y:66 P:67 SP:FB PPU:279,102 CYC:11694\n$DDAE:A5 33     LDA $33 = #$44                       A:44 X:00 Y:66 P:67 SP:FB PPU:291,102 CYC:11698\n$DDB0:90 18     BCC $DDCA                          A:44 X:00 Y:66 P:65 SP:FB PPU:300,102 CYC:11701\n$DDB2:C9 44     CMP #$44                           A:44 X:00 Y:66 P:65 SP:FB PPU:306,102 CYC:11703\n$DDB4:D0 14     BNE $DDCA                          A:44 X:00 Y:66 P:67 SP:FB PPU:312,102 CYC:11705\n$DDB6:50 12     BVC $DDCA                          A:44 X:00 Y:66 P:67 SP:FB PPU:318,102 CYC:11707\n$DDB8:18        CLC                                A:44 X:00 Y:66 P:67 SP:FB PPU:324,102 CYC:11709\n$DDB9:B8        CLV                                A:44 X:00 Y:66 P:nVUbdIZc SP:FB PPU:330,102 CYC:11711\n$DDBA:A9 99     LDA #$99                           A:44 X:00 Y:66 P:nvUbdIZc SP:FB PPU:336,102 CYC:11713\n$DDBC:A2 80     LDX #$80                           A:99 X:00 Y:66 P:NvUbdIzc SP:FB PPU:  1,103 CYC:11715\n$DDBE:95 85     STA $85,X @ 05 = #$99                A:99 X:80 Y:66 P:NvUbdIzc SP:FB PPU:  7,103 CYC:11717\n$DDC0:A5 05     LDA $05 = #$99                       A:99 X:80 Y:66 P:NvUbdIzc SP:FB PPU: 19,103 CYC:11721\n$DDC2:B0 06     BCS $DDCA                          A:99 X:80 Y:66 P:NvUbdIzc SP:FB PPU: 28,103 CYC:11724\n$DDC4:C9 99     CMP #$99                           A:99 X:80 Y:66 P:NvUbdIzc SP:FB PPU: 34,103 CYC:11726\n$DDC6:D0 02     BNE $DDCA                          A:99 X:80 Y:66 P:nvUbdIZC SP:FB PPU: 40,103 CYC:11728\n$DDC8:50 04     BVC $DDCE                          A:99 X:80 Y:66 P:nvUbdIZC SP:FB PPU: 46,103 CYC:11730\n$DDCE:A0 25     LDY #$25                           A:99 X:80 Y:66 P:nvUbdIZC SP:FB PPU: 55,103 CYC:11733\n$DDD0:A2 78     LDX #$78                           A:99 X:80 Y:25 P:25 SP:FB PPU: 61,103 CYC:11735\n$DDD2:20 90 F9  JSR $F990                          A:99 X:78 Y:25 P:25 SP:FB PPU: 67,103 CYC:11737\n$F990:A2 55     LDX #$55                           A:99 X:78 Y:25 P:25 SP:F9 PPU: 85,103 CYC:11743\n$F992:A9 FF     LDA #$FF                           A:99 X:55 Y:25 P:25 SP:F9 PPU: 91,103 CYC:11745\n$F994:85 01     STA $01 = #$FF                       A:FF X:55 Y:25 P:A5 SP:F9 PPU: 97,103 CYC:11747\n$F996:EA        NOP                                A:FF X:55 Y:25 P:A5 SP:F9 PPU:106,103 CYC:11750\n$F997:24 01     BIT $01 = #$FF                       A:FF X:55 Y:25 P:A5 SP:F9 PPU:112,103 CYC:11752\n$F999:38        SEC                                A:FF X:55 Y:25 P:E5 SP:F9 PPU:121,103 CYC:11755\n$F99A:A9 01     LDA #$01                           A:FF X:55 Y:25 P:E5 SP:F9 PPU:127,103 CYC:11757\n$F99C:60        RTS                                A:01 X:55 Y:25 P:65 SP:F9 PPU:133,103 CYC:11759\n$DDD5:95 00     STA $00,X @ 55 = #$00                A:01 X:55 Y:25 P:65 SP:FB PPU:151,103 CYC:11765\n$DDD7:56 00     LSR $00,X @ 55 = #$01                A:01 X:55 Y:25 P:65 SP:FB PPU:163,103 CYC:11769\n$DDD9:B5 00     LDA $00,X @ 55 = #$00                A:01 X:55 Y:25 P:67 SP:FB PPU:181,103 CYC:11775\n$DDDB:20 9D F9  JSR $F99D                          A:00 X:55 Y:25 P:67 SP:FB PPU:193,103 CYC:11779\n$F99D:90 1B     BCC $F9BA                          A:00 X:55 Y:25 P:67 SP:F9 PPU:211,103 CYC:11785\n$F99F:D0 19     BNE $F9BA                          A:00 X:55 Y:25 P:67 SP:F9 PPU:217,103 CYC:11787\n$F9A1:30 17     BMI $F9BA                          A:00 X:55 Y:25 P:67 SP:F9 PPU:223,103 CYC:11789\n$F9A3:50 15     BVC $F9BA                          A:00 X:55 Y:25 P:67 SP:F9 PPU:229,103 CYC:11791\n$F9A5:C9 00     CMP #$00                           A:00 X:55 Y:25 P:67 SP:F9 PPU:235,103 CYC:11793\n$F9A7:D0 11     BNE $F9BA                          A:00 X:55 Y:25 P:67 SP:F9 PPU:241,103 CYC:11795\n$F9A9:B8        CLV                                A:00 X:55 Y:25 P:67 SP:F9 PPU:247,103 CYC:11797\n$F9AA:A9 AA     LDA #$AA                           A:00 X:55 Y:25 P:nvUbdIZC SP:F9 PPU:253,103 CYC:11799\n$F9AC:60        RTS                                A:AA X:55 Y:25 P:A5 SP:F9 PPU:259,103 CYC:11801\n$DDDE:C8        INY                                A:AA X:55 Y:25 P:A5 SP:FB PPU:277,103 CYC:11807\n$DDDF:95 00     STA $00,X @ 55 = #$00                A:AA X:55 Y:26 P:25 SP:FB PPU:283,103 CYC:11809\n$DDE1:56 00     LSR $00,X @ 55 = #$AA                A:AA X:55 Y:26 P:25 SP:FB PPU:295,103 CYC:11813\n$DDE3:B5 00     LDA $00,X @ 55 = #$55                A:AA X:55 Y:26 P:nvUbdIzc SP:FB PPU:313,103 CYC:11819\n$DDE5:20 AD F9  JSR $F9AD                          A:55 X:55 Y:26 P:nvUbdIzc SP:FB PPU:325,103 CYC:11823\n$F9AD:B0 0B     BCS $F9BA                          A:55 X:55 Y:26 P:nvUbdIzc SP:F9 PPU:  2,104 CYC:11829\n$F9AF:F0 09     BEQ $F9BA                          A:55 X:55 Y:26 P:nvUbdIzc SP:F9 PPU:  8,104 CYC:11831\n$F9B1:30 07     BMI $F9BA                          A:55 X:55 Y:26 P:nvUbdIzc SP:F9 PPU: 14,104 CYC:11833\n$F9B3:70 05     BVS $F9BA                          A:55 X:55 Y:26 P:nvUbdIzc SP:F9 PPU: 20,104 CYC:11835\n$F9B5:C9 55     CMP #$55                           A:55 X:55 Y:26 P:nvUbdIzc SP:F9 PPU: 26,104 CYC:11837\n$F9B7:D0 01     BNE $F9BA                          A:55 X:55 Y:26 P:nvUbdIZC SP:F9 PPU: 32,104 CYC:11839\n$F9B9:60        RTS                                A:55 X:55 Y:26 P:nvUbdIZC SP:F9 PPU: 38,104 CYC:11841\n$DDE8:C8        INY                                A:55 X:55 Y:26 P:nvUbdIZC SP:FB PPU: 56,104 CYC:11847\n$DDE9:20 BD F9  JSR $F9BD                          A:55 X:55 Y:27 P:25 SP:FB PPU: 62,104 CYC:11849\n$F9BD:24 01     BIT $01 = #$FF                       A:55 X:55 Y:27 P:25 SP:F9 PPU: 80,104 CYC:11855\n$F9BF:38        SEC                                A:55 X:55 Y:27 P:E5 SP:F9 PPU: 89,104 CYC:11858\n$F9C0:A9 80     LDA #$80                           A:55 X:55 Y:27 P:E5 SP:F9 PPU: 95,104 CYC:11860\n$F9C2:60        RTS                                A:80 X:55 Y:27 P:E5 SP:F9 PPU:101,104 CYC:11862\n$DDEC:95 00     STA $00,X @ 55 = #$55                A:80 X:55 Y:27 P:E5 SP:FB PPU:119,104 CYC:11868\n$DDEE:16 00     ASL $00,X @ 55 = #$80                A:80 X:55 Y:27 P:E5 SP:FB PPU:131,104 CYC:11872\n$DDF0:B5 00     LDA $00,X @ 55 = #$00                A:80 X:55 Y:27 P:67 SP:FB PPU:149,104 CYC:11878\n$DDF2:20 C3 F9  JSR $F9C3                          A:00 X:55 Y:27 P:67 SP:FB PPU:161,104 CYC:11882\n$F9C3:90 1C     BCC $F9E1                          A:00 X:55 Y:27 P:67 SP:F9 PPU:179,104 CYC:11888\n$F9C5:D0 1A     BNE $F9E1                          A:00 X:55 Y:27 P:67 SP:F9 PPU:185,104 CYC:11890\n$F9C7:30 18     BMI $F9E1                          A:00 X:55 Y:27 P:67 SP:F9 PPU:191,104 CYC:11892\n$F9C9:50 16     BVC $F9E1                          A:00 X:55 Y:27 P:67 SP:F9 PPU:197,104 CYC:11894\n$F9CB:C9 00     CMP #$00                           A:00 X:55 Y:27 P:67 SP:F9 PPU:203,104 CYC:11896\n$F9CD:D0 12     BNE $F9E1                          A:00 X:55 Y:27 P:67 SP:F9 PPU:209,104 CYC:11898\n$F9CF:B8        CLV                                A:00 X:55 Y:27 P:67 SP:F9 PPU:215,104 CYC:11900\n$F9D0:A9 55     LDA #$55                           A:00 X:55 Y:27 P:nvUbdIZC SP:F9 PPU:221,104 CYC:11902\n$F9D2:38        SEC                                A:55 X:55 Y:27 P:25 SP:F9 PPU:227,104 CYC:11904\n$F9D3:60        RTS                                A:55 X:55 Y:27 P:25 SP:F9 PPU:233,104 CYC:11906\n$DDF5:C8        INY                                A:55 X:55 Y:27 P:25 SP:FB PPU:251,104 CYC:11912\n$DDF6:95 00     STA $00,X @ 55 = #$00                A:55 X:55 Y:28 P:25 SP:FB PPU:257,104 CYC:11914\n$DDF8:16 00     ASL $00,X @ 55 = #$55                A:55 X:55 Y:28 P:25 SP:FB PPU:269,104 CYC:11918\n$DDFA:B5 00     LDA $00,X @ 55 = #$AA                A:55 X:55 Y:28 P:NvUbdIzc SP:FB PPU:287,104 CYC:11924\n$DDFC:20 D4 F9  JSR $F9D4                          A:AA X:55 Y:28 P:NvUbdIzc SP:FB PPU:299,104 CYC:11928\n$F9D4:B0 0B     BCS $F9E1                          A:AA X:55 Y:28 P:NvUbdIzc SP:F9 PPU:317,104 CYC:11934\n$F9D6:F0 09     BEQ $F9E1                          A:AA X:55 Y:28 P:NvUbdIzc SP:F9 PPU:323,104 CYC:11936\n$F9D8:10 07     BPL $F9E1                          A:AA X:55 Y:28 P:NvUbdIzc SP:F9 PPU:329,104 CYC:11938\n$F9DA:70 05     BVS $F9E1                          A:AA X:55 Y:28 P:NvUbdIzc SP:F9 PPU:335,104 CYC:11940\n$F9DC:C9 AA     CMP #$AA                           A:AA X:55 Y:28 P:NvUbdIzc SP:F9 PPU:  0,105 CYC:11942\n$F9DE:D0 01     BNE $F9E1                          A:AA X:55 Y:28 P:nvUbdIZC SP:F9 PPU:  6,105 CYC:11944\n$F9E0:60        RTS                                A:AA X:55 Y:28 P:nvUbdIZC SP:F9 PPU: 12,105 CYC:11946\n$DDFF:C8        INY                                A:AA X:55 Y:28 P:nvUbdIZC SP:FB PPU: 30,105 CYC:11952\n$DE00:20 E4 F9  JSR $F9E4                          A:AA X:55 Y:29 P:25 SP:FB PPU: 36,105 CYC:11954\n$F9E4:24 01     BIT $01 = #$FF                       A:AA X:55 Y:29 P:25 SP:F9 PPU: 54,105 CYC:11960\n$F9E6:38        SEC                                A:AA X:55 Y:29 P:E5 SP:F9 PPU: 63,105 CYC:11963\n$F9E7:A9 01     LDA #$01                           A:AA X:55 Y:29 P:E5 SP:F9 PPU: 69,105 CYC:11965\n$F9E9:60        RTS                                A:01 X:55 Y:29 P:65 SP:F9 PPU: 75,105 CYC:11967\n$DE03:95 00     STA $00,X @ 55 = #$AA                A:01 X:55 Y:29 P:65 SP:FB PPU: 93,105 CYC:11973\n$DE05:76 00     ROR $00,X @ 55 = #$01                A:01 X:55 Y:29 P:65 SP:FB PPU:105,105 CYC:11977\n$DE07:B5 00     LDA $00,X @ 55 = #$80                A:01 X:55 Y:29 P:E5 SP:FB PPU:123,105 CYC:11983\n$DE09:20 EA F9  JSR $F9EA                          A:80 X:55 Y:29 P:E5 SP:FB PPU:135,105 CYC:11987\n$F9EA:90 1C     BCC $FA08                          A:80 X:55 Y:29 P:E5 SP:F9 PPU:153,105 CYC:11993\n$F9EC:F0 1A     BEQ $FA08                          A:80 X:55 Y:29 P:E5 SP:F9 PPU:159,105 CYC:11995\n$F9EE:10 18     BPL $FA08                          A:80 X:55 Y:29 P:E5 SP:F9 PPU:165,105 CYC:11997\n$F9F0:50 16     BVC $FA08                          A:80 X:55 Y:29 P:E5 SP:F9 PPU:171,105 CYC:11999\n$F9F2:C9 80     CMP #$80                           A:80 X:55 Y:29 P:E5 SP:F9 PPU:177,105 CYC:12001\n$F9F4:D0 12     BNE $FA08                          A:80 X:55 Y:29 P:67 SP:F9 PPU:183,105 CYC:12003\n$F9F6:B8        CLV                                A:80 X:55 Y:29 P:67 SP:F9 PPU:189,105 CYC:12005\n$F9F7:18        CLC                                A:80 X:55 Y:29 P:nvUbdIZC SP:F9 PPU:195,105 CYC:12007\n$F9F8:A9 55     LDA #$55                           A:80 X:55 Y:29 P:nvUbdIZc SP:F9 PPU:201,105 CYC:12009\n$F9FA:60        RTS                                A:55 X:55 Y:29 P:nvUbdIzc SP:F9 PPU:207,105 CYC:12011\n$DE0C:C8        INY                                A:55 X:55 Y:29 P:nvUbdIzc SP:FB PPU:225,105 CYC:12017\n$DE0D:95 00     STA $00,X @ 55 = #$80                A:55 X:55 Y:2A P:nvUbdIzc SP:FB PPU:231,105 CYC:12019\n$DE0F:76 00     ROR $00,X @ 55 = #$55                A:55 X:55 Y:2A P:nvUbdIzc SP:FB PPU:243,105 CYC:12023\n$DE11:B5 00     LDA $00,X @ 55 = #$2A                A:55 X:55 Y:2A P:25 SP:FB PPU:261,105 CYC:12029\n$DE13:20 FB F9  JSR $F9FB                          A:2A X:55 Y:2A P:25 SP:FB PPU:273,105 CYC:12033\n$F9FB:90 0B     BCC $FA08                          A:2A X:55 Y:2A P:25 SP:F9 PPU:291,105 CYC:12039\n$F9FD:F0 09     BEQ $FA08                          A:2A X:55 Y:2A P:25 SP:F9 PPU:297,105 CYC:12041\n$F9FF:30 07     BMI $FA08                          A:2A X:55 Y:2A P:25 SP:F9 PPU:303,105 CYC:12043\n$FA01:70 05     BVS $FA08                          A:2A X:55 Y:2A P:25 SP:F9 PPU:309,105 CYC:12045\n$FA03:C9 2A     CMP #$2A                           A:2A X:55 Y:2A P:25 SP:F9 PPU:315,105 CYC:12047\n$FA05:D0 01     BNE $FA08                          A:2A X:55 Y:2A P:nvUbdIZC SP:F9 PPU:321,105 CYC:12049\n$FA07:60        RTS                                A:2A X:55 Y:2A P:nvUbdIZC SP:F9 PPU:327,105 CYC:12051\n$DE16:C8        INY                                A:2A X:55 Y:2A P:nvUbdIZC SP:FB PPU:  4,106 CYC:12057\n$DE17:20 0A FA  JSR $FA0A                          A:2A X:55 Y:2B P:25 SP:FB PPU: 10,106 CYC:12059\n$FA0A:24 01     BIT $01 = #$FF                       A:2A X:55 Y:2B P:25 SP:F9 PPU: 28,106 CYC:12065\n$FA0C:38        SEC                                A:2A X:55 Y:2B P:E5 SP:F9 PPU: 37,106 CYC:12068\n$FA0D:A9 80     LDA #$80                           A:2A X:55 Y:2B P:E5 SP:F9 PPU: 43,106 CYC:12070\n$FA0F:60        RTS                                A:80 X:55 Y:2B P:E5 SP:F9 PPU: 49,106 CYC:12072\n$DE1A:95 00     STA $00,X @ 55 = #$2A                A:80 X:55 Y:2B P:E5 SP:FB PPU: 67,106 CYC:12078\n$DE1C:36 00     ROL $00,X @ 55 = #$80                A:80 X:55 Y:2B P:E5 SP:FB PPU: 79,106 CYC:12082\n$DE1E:B5 00     LDA $00,X @ 55 = #$01                A:80 X:55 Y:2B P:65 SP:FB PPU: 97,106 CYC:12088\n$DE20:20 10 FA  JSR $FA10                          A:01 X:55 Y:2B P:65 SP:FB PPU:109,106 CYC:12092\n$FA10:90 1C     BCC $FA2E                          A:01 X:55 Y:2B P:65 SP:F9 PPU:127,106 CYC:12098\n$FA12:F0 1A     BEQ $FA2E                          A:01 X:55 Y:2B P:65 SP:F9 PPU:133,106 CYC:12100\n$FA14:30 18     BMI $FA2E                          A:01 X:55 Y:2B P:65 SP:F9 PPU:139,106 CYC:12102\n$FA16:50 16     BVC $FA2E                          A:01 X:55 Y:2B P:65 SP:F9 PPU:145,106 CYC:12104\n$FA18:C9 01     CMP #$01                           A:01 X:55 Y:2B P:65 SP:F9 PPU:151,106 CYC:12106\n$FA1A:D0 12     BNE $FA2E                          A:01 X:55 Y:2B P:67 SP:F9 PPU:157,106 CYC:12108\n$FA1C:B8        CLV                                A:01 X:55 Y:2B P:67 SP:F9 PPU:163,106 CYC:12110\n$FA1D:18        CLC                                A:01 X:55 Y:2B P:nvUbdIZC SP:F9 PPU:169,106 CYC:12112\n$FA1E:A9 55     LDA #$55                           A:01 X:55 Y:2B P:nvUbdIZc SP:F9 PPU:175,106 CYC:12114\n$FA20:60        RTS                                A:55 X:55 Y:2B P:nvUbdIzc SP:F9 PPU:181,106 CYC:12116\n$DE23:C8        INY                                A:55 X:55 Y:2B P:nvUbdIzc SP:FB PPU:199,106 CYC:12122\n$DE24:95 00     STA $00,X @ 55 = #$01                A:55 X:55 Y:2C P:nvUbdIzc SP:FB PPU:205,106 CYC:12124\n$DE26:36 00     ROL $00,X @ 55 = #$55                A:55 X:55 Y:2C P:nvUbdIzc SP:FB PPU:217,106 CYC:12128\n$DE28:B5 00     LDA $00,X @ 55 = #$AA                A:55 X:55 Y:2C P:NvUbdIzc SP:FB PPU:235,106 CYC:12134\n$DE2A:20 21 FA  JSR $FA21                          A:AA X:55 Y:2C P:NvUbdIzc SP:FB PPU:247,106 CYC:12138\n$FA21:B0 0B     BCS $FA2E                          A:AA X:55 Y:2C P:NvUbdIzc SP:F9 PPU:265,106 CYC:12144\n$FA23:F0 09     BEQ $FA2E                          A:AA X:55 Y:2C P:NvUbdIzc SP:F9 PPU:271,106 CYC:12146\n$FA25:10 07     BPL $FA2E                          A:AA X:55 Y:2C P:NvUbdIzc SP:F9 PPU:277,106 CYC:12148\n$FA27:70 05     BVS $FA2E                          A:AA X:55 Y:2C P:NvUbdIzc SP:F9 PPU:283,106 CYC:12150\n$FA29:C9 AA     CMP #$AA                           A:AA X:55 Y:2C P:NvUbdIzc SP:F9 PPU:289,106 CYC:12152\n$FA2B:D0 01     BNE $FA2E                          A:AA X:55 Y:2C P:nvUbdIZC SP:F9 PPU:295,106 CYC:12154\n$FA2D:60        RTS                                A:AA X:55 Y:2C P:nvUbdIZC SP:F9 PPU:301,106 CYC:12156\n$DE2D:A9 FF     LDA #$FF                           A:AA X:55 Y:2C P:nvUbdIZC SP:FB PPU:319,106 CYC:12162\n$DE2F:95 00     STA $00,X @ 55 = #$AA                A:FF X:55 Y:2C P:A5 SP:FB PPU:325,106 CYC:12164\n$DE31:85 01     STA $01 = #$FF                       A:FF X:55 Y:2C P:A5 SP:FB PPU:337,106 CYC:12168\n$DE33:24 01     BIT $01 = #$FF                       A:FF X:55 Y:2C P:A5 SP:FB PPU:  5,107 CYC:12171\n$DE35:38        SEC                                A:FF X:55 Y:2C P:E5 SP:FB PPU: 14,107 CYC:12174\n$DE36:F6 00     INC $00,X @ 55 = #$FF                A:FF X:55 Y:2C P:E5 SP:FB PPU: 20,107 CYC:12176\n$DE38:D0 0C     BNE $DE46                          A:FF X:55 Y:2C P:67 SP:FB PPU: 38,107 CYC:12182\n$DE3A:30 0A     BMI $DE46                          A:FF X:55 Y:2C P:67 SP:FB PPU: 44,107 CYC:12184\n$DE3C:50 08     BVC $DE46                          A:FF X:55 Y:2C P:67 SP:FB PPU: 50,107 CYC:12186\n$DE3E:90 06     BCC $DE46                          A:FF X:55 Y:2C P:67 SP:FB PPU: 56,107 CYC:12188\n$DE40:B5 00     LDA $00,X @ 55 = #$00                A:FF X:55 Y:2C P:67 SP:FB PPU: 62,107 CYC:12190\n$DE42:C9 00     CMP #$00                           A:00 X:55 Y:2C P:67 SP:FB PPU: 74,107 CYC:12194\n$DE44:F0 04     BEQ $DE4A                          A:00 X:55 Y:2C P:67 SP:FB PPU: 80,107 CYC:12196\n$DE4A:A9 7F     LDA #$7F                           A:00 X:55 Y:2C P:67 SP:FB PPU: 89,107 CYC:12199\n$DE4C:95 00     STA $00,X @ 55 = #$00                A:7F X:55 Y:2C P:65 SP:FB PPU: 95,107 CYC:12201\n$DE4E:B8        CLV                                A:7F X:55 Y:2C P:65 SP:FB PPU:107,107 CYC:12205\n$DE4F:18        CLC                                A:7F X:55 Y:2C P:25 SP:FB PPU:113,107 CYC:12207\n$DE50:F6 00     INC $00,X @ 55 = #$7F                A:7F X:55 Y:2C P:nvUbdIzc SP:FB PPU:119,107 CYC:12209\n$DE52:F0 0C     BEQ $DE60                          A:7F X:55 Y:2C P:NvUbdIzc SP:FB PPU:137,107 CYC:12215\n$DE54:10 0A     BPL $DE60                          A:7F X:55 Y:2C P:NvUbdIzc SP:FB PPU:143,107 CYC:12217\n$DE56:70 08     BVS $DE60                          A:7F X:55 Y:2C P:NvUbdIzc SP:FB PPU:149,107 CYC:12219\n$DE58:B0 06     BCS $DE60                          A:7F X:55 Y:2C P:NvUbdIzc SP:FB PPU:155,107 CYC:12221\n$DE5A:B5 00     LDA $00,X @ 55 = #$80                A:7F X:55 Y:2C P:NvUbdIzc SP:FB PPU:161,107 CYC:12223\n$DE5C:C9 80     CMP #$80                           A:80 X:55 Y:2C P:NvUbdIzc SP:FB PPU:173,107 CYC:12227\n$DE5E:F0 04     BEQ $DE64                          A:80 X:55 Y:2C P:nvUbdIZC SP:FB PPU:179,107 CYC:12229\n$DE64:A9 00     LDA #$00                           A:80 X:55 Y:2C P:nvUbdIZC SP:FB PPU:188,107 CYC:12232\n$DE66:95 00     STA $00,X @ 55 = #$80                A:00 X:55 Y:2C P:nvUbdIZC SP:FB PPU:194,107 CYC:12234\n$DE68:24 01     BIT $01 = #$FF                       A:00 X:55 Y:2C P:nvUbdIZC SP:FB PPU:206,107 CYC:12238\n$DE6A:38        SEC                                A:00 X:55 Y:2C P:E7 SP:FB PPU:215,107 CYC:12241\n$DE6B:D6 00     DEC $00,X @ 55 = #$00                A:00 X:55 Y:2C P:E7 SP:FB PPU:221,107 CYC:12243\n$DE6D:F0 0C     BEQ $DE7B                          A:00 X:55 Y:2C P:E5 SP:FB PPU:239,107 CYC:12249\n$DE6F:10 0A     BPL $DE7B                          A:00 X:55 Y:2C P:E5 SP:FB PPU:245,107 CYC:12251\n$DE71:50 08     BVC $DE7B                          A:00 X:55 Y:2C P:E5 SP:FB PPU:251,107 CYC:12253\n$DE73:90 06     BCC $DE7B                          A:00 X:55 Y:2C P:E5 SP:FB PPU:257,107 CYC:12255\n$DE75:B5 00     LDA $00,X @ 55 = #$FF                A:00 X:55 Y:2C P:E5 SP:FB PPU:263,107 CYC:12257\n$DE77:C9 FF     CMP #$FF                           A:FF X:55 Y:2C P:E5 SP:FB PPU:275,107 CYC:12261\n$DE79:F0 04     BEQ $DE7F                          A:FF X:55 Y:2C P:67 SP:FB PPU:281,107 CYC:12263\n$DE7F:A9 80     LDA #$80                           A:FF X:55 Y:2C P:67 SP:FB PPU:290,107 CYC:12266\n$DE81:95 00     STA $00,X @ 55 = #$FF                A:80 X:55 Y:2C P:E5 SP:FB PPU:296,107 CYC:12268\n$DE83:B8        CLV                                A:80 X:55 Y:2C P:E5 SP:FB PPU:308,107 CYC:12272\n$DE84:18        CLC                                A:80 X:55 Y:2C P:A5 SP:FB PPU:314,107 CYC:12274\n$DE85:D6 00     DEC $00,X @ 55 = #$80                A:80 X:55 Y:2C P:NvUbdIzc SP:FB PPU:320,107 CYC:12276\n$DE87:F0 0C     BEQ $DE95                          A:80 X:55 Y:2C P:nvUbdIzc SP:FB PPU:338,107 CYC:12282\n$DE89:30 0A     BMI $DE95                          A:80 X:55 Y:2C P:nvUbdIzc SP:FB PPU:  3,108 CYC:12284\n$DE8B:70 08     BVS $DE95                          A:80 X:55 Y:2C P:nvUbdIzc SP:FB PPU:  9,108 CYC:12286\n$DE8D:B0 06     BCS $DE95                          A:80 X:55 Y:2C P:nvUbdIzc SP:FB PPU: 15,108 CYC:12288\n$DE8F:B5 00     LDA $00,X @ 55 = #$7F                A:80 X:55 Y:2C P:nvUbdIzc SP:FB PPU: 21,108 CYC:12290\n$DE91:C9 7F     CMP #$7F                           A:7F X:55 Y:2C P:nvUbdIzc SP:FB PPU: 33,108 CYC:12294\n$DE93:F0 04     BEQ $DE99                          A:7F X:55 Y:2C P:nvUbdIZC SP:FB PPU: 39,108 CYC:12296\n$DE99:A9 01     LDA #$01                           A:7F X:55 Y:2C P:nvUbdIZC SP:FB PPU: 48,108 CYC:12299\n$DE9B:95 00     STA $00,X @ 55 = #$7F                A:01 X:55 Y:2C P:25 SP:FB PPU: 54,108 CYC:12301\n$DE9D:D6 00     DEC $00,X @ 55 = #$01                A:01 X:55 Y:2C P:25 SP:FB PPU: 66,108 CYC:12305\n$DE9F:F0 04     BEQ $DEA5                          A:01 X:55 Y:2C P:nvUbdIZC SP:FB PPU: 84,108 CYC:12311\n$DEA5:A9 33     LDA #$33                           A:01 X:55 Y:2C P:nvUbdIZC SP:FB PPU: 93,108 CYC:12314\n$DEA7:85 78     STA $78 = #$7F                       A:33 X:55 Y:2C P:25 SP:FB PPU: 99,108 CYC:12316\n$DEA9:A9 44     LDA #$44                           A:33 X:55 Y:2C P:25 SP:FB PPU:108,108 CYC:12319\n$DEAB:A0 78     LDY #$78                           A:44 X:55 Y:2C P:25 SP:FB PPU:114,108 CYC:12321\n$DEAD:A2 00     LDX #$00                           A:44 X:55 Y:78 P:25 SP:FB PPU:120,108 CYC:12323\n$DEAF:38        SEC                                A:44 X:00 Y:78 P:nvUbdIZC SP:FB PPU:126,108 CYC:12325\n$DEB0:24 01     BIT $01 = #$FF                       A:44 X:00 Y:78 P:nvUbdIZC SP:FB PPU:132,108 CYC:12327\n$DEB2:B6 00     LDX $00,Y @ 78 = #$33                A:44 X:00 Y:78 P:E5 SP:FB PPU:141,108 CYC:12330\n$DEB4:90 12     BCC $DEC8                          A:44 X:33 Y:78 P:65 SP:FB PPU:153,108 CYC:12334\n$DEB6:50 10     BVC $DEC8                          A:44 X:33 Y:78 P:65 SP:FB PPU:159,108 CYC:12336\n$DEB8:30 0E     BMI $DEC8                          A:44 X:33 Y:78 P:65 SP:FB PPU:165,108 CYC:12338\n$DEBA:F0 0C     BEQ $DEC8                          A:44 X:33 Y:78 P:65 SP:FB PPU:171,108 CYC:12340\n$DEBC:E0 33     CPX #$33                           A:44 X:33 Y:78 P:65 SP:FB PPU:177,108 CYC:12342\n$DEBE:D0 08     BNE $DEC8                          A:44 X:33 Y:78 P:67 SP:FB PPU:183,108 CYC:12344\n$DEC0:C0 78     CPY #$78                           A:44 X:33 Y:78 P:67 SP:FB PPU:189,108 CYC:12346\n$DEC2:D0 04     BNE $DEC8                          A:44 X:33 Y:78 P:67 SP:FB PPU:195,108 CYC:12348\n$DEC4:C9 44     CMP #$44                           A:44 X:33 Y:78 P:67 SP:FB PPU:201,108 CYC:12350\n$DEC6:F0 04     BEQ $DECC                          A:44 X:33 Y:78 P:67 SP:FB PPU:207,108 CYC:12352\n$DECC:A9 97     LDA #$97                           A:44 X:33 Y:78 P:67 SP:FB PPU:216,108 CYC:12355\n$DECE:85 7F     STA $7F = #$00                       A:97 X:33 Y:78 P:E5 SP:FB PPU:222,108 CYC:12357\n$DED0:A9 47     LDA #$47                           A:97 X:33 Y:78 P:E5 SP:FB PPU:231,108 CYC:12360\n$DED2:A0 FF     LDY #$FF                           A:47 X:33 Y:78 P:65 SP:FB PPU:237,108 CYC:12362\n$DED4:A2 00     LDX #$00                           A:47 X:33 Y:FF P:E5 SP:FB PPU:243,108 CYC:12364\n$DED6:18        CLC                                A:47 X:00 Y:FF P:67 SP:FB PPU:249,108 CYC:12366\n$DED7:B8        CLV                                A:47 X:00 Y:FF P:nVUbdIZc SP:FB PPU:255,108 CYC:12368\n$DED8:B6 80     LDX $80,Y @ 7F = #$97                A:47 X:00 Y:FF P:nvUbdIZc SP:FB PPU:261,108 CYC:12370\n$DEDA:B0 12     BCS $DEEE                          A:47 X:97 Y:FF P:NvUbdIzc SP:FB PPU:273,108 CYC:12374\n$DEDC:70 10     BVS $DEEE                          A:47 X:97 Y:FF P:NvUbdIzc SP:FB PPU:279,108 CYC:12376\n$DEDE:10 0E     BPL $DEEE                          A:47 X:97 Y:FF P:NvUbdIzc SP:FB PPU:285,108 CYC:12378\n$DEE0:F0 0C     BEQ $DEEE                          A:47 X:97 Y:FF P:NvUbdIzc SP:FB PPU:291,108 CYC:12380\n$DEE2:E0 97     CPX #$97                           A:47 X:97 Y:FF P:NvUbdIzc SP:FB PPU:297,108 CYC:12382\n$DEE4:D0 08     BNE $DEEE                          A:47 X:97 Y:FF P:nvUbdIZC SP:FB PPU:303,108 CYC:12384\n$DEE6:C0 FF     CPY #$FF                           A:47 X:97 Y:FF P:nvUbdIZC SP:FB PPU:309,108 CYC:12386\n$DEE8:D0 04     BNE $DEEE                          A:47 X:97 Y:FF P:nvUbdIZC SP:FB PPU:315,108 CYC:12388\n$DEEA:C9 47     CMP #$47                           A:47 X:97 Y:FF P:nvUbdIZC SP:FB PPU:321,108 CYC:12390\n$DEEC:F0 04     BEQ $DEF2                          A:47 X:97 Y:FF P:nvUbdIZC SP:FB PPU:327,108 CYC:12392\n$DEF2:A9 00     LDA #$00                           A:47 X:97 Y:FF P:nvUbdIZC SP:FB PPU:336,108 CYC:12395\n$DEF4:85 7F     STA $7F = #$97                       A:00 X:97 Y:FF P:nvUbdIZC SP:FB PPU:  1,109 CYC:12397\n$DEF6:A9 47     LDA #$47                           A:00 X:97 Y:FF P:nvUbdIZC SP:FB PPU: 10,109 CYC:12400\n$DEF8:A0 FF     LDY #$FF                           A:47 X:97 Y:FF P:25 SP:FB PPU: 16,109 CYC:12402\n$DEFA:A2 69     LDX #$69                           A:47 X:97 Y:FF P:A5 SP:FB PPU: 22,109 CYC:12404\n$DEFC:18        CLC                                A:47 X:69 Y:FF P:25 SP:FB PPU: 28,109 CYC:12406\n$DEFD:B8        CLV                                A:47 X:69 Y:FF P:nvUbdIzc SP:FB PPU: 34,109 CYC:12408\n$DEFE:96 80     STX $80,Y @ 7F = #$00                A:47 X:69 Y:FF P:nvUbdIzc SP:FB PPU: 40,109 CYC:12410\n$DF00:B0 18     BCS $DF1A                          A:47 X:69 Y:FF P:nvUbdIzc SP:FB PPU: 52,109 CYC:12414\n$DF02:70 16     BVS $DF1A                          A:47 X:69 Y:FF P:nvUbdIzc SP:FB PPU: 58,109 CYC:12416\n$DF04:30 14     BMI $DF1A                          A:47 X:69 Y:FF P:nvUbdIzc SP:FB PPU: 64,109 CYC:12418\n$DF06:F0 12     BEQ $DF1A                          A:47 X:69 Y:FF P:nvUbdIzc SP:FB PPU: 70,109 CYC:12420\n$DF08:E0 69     CPX #$69                           A:47 X:69 Y:FF P:nvUbdIzc SP:FB PPU: 76,109 CYC:12422\n$DF0A:D0 0E     BNE $DF1A                          A:47 X:69 Y:FF P:nvUbdIZC SP:FB PPU: 82,109 CYC:12424\n$DF0C:C0 FF     CPY #$FF                           A:47 X:69 Y:FF P:nvUbdIZC SP:FB PPU: 88,109 CYC:12426\n$DF0E:D0 0A     BNE $DF1A                          A:47 X:69 Y:FF P:nvUbdIZC SP:FB PPU: 94,109 CYC:12428\n$DF10:C9 47     CMP #$47                           A:47 X:69 Y:FF P:nvUbdIZC SP:FB PPU:100,109 CYC:12430\n$DF12:D0 06     BNE $DF1A                          A:47 X:69 Y:FF P:nvUbdIZC SP:FB PPU:106,109 CYC:12432\n$DF14:A5 7F     LDA $7F = #$69                       A:47 X:69 Y:FF P:nvUbdIZC SP:FB PPU:112,109 CYC:12434\n$DF16:C9 69     CMP #$69                           A:69 X:69 Y:FF P:25 SP:FB PPU:121,109 CYC:12437\n$DF18:F0 04     BEQ $DF1E                          A:69 X:69 Y:FF P:nvUbdIZC SP:FB PPU:127,109 CYC:12439\n$DF1E:A9 F5     LDA #$F5                           A:69 X:69 Y:FF P:nvUbdIZC SP:FB PPU:136,109 CYC:12442\n$DF20:85 4F     STA $4F = #$00                       A:F5 X:69 Y:FF P:A5 SP:FB PPU:142,109 CYC:12444\n$DF22:A9 47     LDA #$47                           A:F5 X:69 Y:FF P:A5 SP:FB PPU:151,109 CYC:12447\n$DF24:A0 4F     LDY #$4F                           A:47 X:69 Y:FF P:25 SP:FB PPU:157,109 CYC:12449\n$DF26:24 01     BIT $01 = #$FF                       A:47 X:69 Y:4F P:25 SP:FB PPU:163,109 CYC:12451\n$DF28:A2 00     LDX #$00                           A:47 X:69 Y:4F P:E5 SP:FB PPU:172,109 CYC:12454\n$DF2A:38        SEC                                A:47 X:00 Y:4F P:67 SP:FB PPU:178,109 CYC:12456\n$DF2B:96 00     STX $00,Y @ 4F = #$F5                A:47 X:00 Y:4F P:67 SP:FB PPU:184,109 CYC:12458\n$DF2D:90 16     BCC $DF45                          A:47 X:00 Y:4F P:67 SP:FB PPU:196,109 CYC:12462\n$DF2F:50 14     BVC $DF45                          A:47 X:00 Y:4F P:67 SP:FB PPU:202,109 CYC:12464\n$DF31:30 12     BMI $DF45                          A:47 X:00 Y:4F P:67 SP:FB PPU:208,109 CYC:12466\n$DF33:D0 10     BNE $DF45                          A:47 X:00 Y:4F P:67 SP:FB PPU:214,109 CYC:12468\n$DF35:E0 00     CPX #$00                           A:47 X:00 Y:4F P:67 SP:FB PPU:220,109 CYC:12470\n$DF37:D0 0C     BNE $DF45                          A:47 X:00 Y:4F P:67 SP:FB PPU:226,109 CYC:12472\n$DF39:C0 4F     CPY #$4F                           A:47 X:00 Y:4F P:67 SP:FB PPU:232,109 CYC:12474\n$DF3B:D0 08     BNE $DF45                          A:47 X:00 Y:4F P:67 SP:FB PPU:238,109 CYC:12476\n$DF3D:C9 47     CMP #$47                           A:47 X:00 Y:4F P:67 SP:FB PPU:244,109 CYC:12478\n$DF3F:D0 04     BNE $DF45                          A:47 X:00 Y:4F P:67 SP:FB PPU:250,109 CYC:12480\n$DF41:A5 4F     LDA $4F = #$00                       A:47 X:00 Y:4F P:67 SP:FB PPU:256,109 CYC:12482\n$DF43:F0 04     BEQ $DF49                          A:00 X:00 Y:4F P:67 SP:FB PPU:265,109 CYC:12485\n$DF49:60        RTS                                A:00 X:00 Y:4F P:67 SP:FB PPU:274,109 CYC:12488\n$C62C:20 AA E1  JSR $E1AA                          A:00 X:00 Y:4F P:67 SP:FD PPU:292,109 CYC:12494\n$E1AA:A9 FF     LDA #$FF                           A:00 X:00 Y:4F P:67 SP:FB PPU:310,109 CYC:12500\n$E1AC:85 01     STA $01 = #$FF                       A:FF X:00 Y:4F P:E5 SP:FB PPU:316,109 CYC:12502\n$E1AE:A9 AA     LDA #$AA                           A:FF X:00 Y:4F P:E5 SP:FB PPU:325,109 CYC:12505\n$E1B0:8D 33 06  STA $0633 = #$00                     A:AA X:00 Y:4F P:E5 SP:FB PPU:331,109 CYC:12507\n$E1B3:A9 BB     LDA #$BB                           A:AA X:00 Y:4F P:E5 SP:FB PPU:  2,110 CYC:12511\n$E1B5:8D 89 06  STA $0689 = #$00                     A:BB X:00 Y:4F P:E5 SP:FB PPU:  8,110 CYC:12513\n$E1B8:A2 00     LDX #$00                           A:BB X:00 Y:4F P:E5 SP:FB PPU: 20,110 CYC:12517\n$E1BA:A9 66     LDA #$66                           A:BB X:00 Y:4F P:67 SP:FB PPU: 26,110 CYC:12519\n$E1BC:24 01     BIT $01 = #$FF                       A:66 X:00 Y:4F P:65 SP:FB PPU: 32,110 CYC:12521\n$E1BE:38        SEC                                A:66 X:00 Y:4F P:E5 SP:FB PPU: 41,110 CYC:12524\n$E1BF:A0 00     LDY #$00                           A:66 X:00 Y:4F P:E5 SP:FB PPU: 47,110 CYC:12526\n$E1C1:BC 33 06  LDY $0633,X @ 0633 = #$AA            A:66 X:00 Y:00 P:67 SP:FB PPU: 53,110 CYC:12528\n$E1C4:10 12     BPL $E1D8                          A:66 X:00 Y:AA P:E5 SP:FB PPU: 65,110 CYC:12532\n$E1C6:F0 10     BEQ $E1D8                          A:66 X:00 Y:AA P:E5 SP:FB PPU: 71,110 CYC:12534\n$E1C8:50 0E     BVC $E1D8                          A:66 X:00 Y:AA P:E5 SP:FB PPU: 77,110 CYC:12536\n$E1CA:90 0C     BCC $E1D8                          A:66 X:00 Y:AA P:E5 SP:FB PPU: 83,110 CYC:12538\n$E1CC:C9 66     CMP #$66                           A:66 X:00 Y:AA P:E5 SP:FB PPU: 89,110 CYC:12540\n$E1CE:D0 08     BNE $E1D8                          A:66 X:00 Y:AA P:67 SP:FB PPU: 95,110 CYC:12542\n$E1D0:E0 00     CPX #$00                           A:66 X:00 Y:AA P:67 SP:FB PPU:101,110 CYC:12544\n$E1D2:D0 04     BNE $E1D8                          A:66 X:00 Y:AA P:67 SP:FB PPU:107,110 CYC:12546\n$E1D4:C0 AA     CPY #$AA                           A:66 X:00 Y:AA P:67 SP:FB PPU:113,110 CYC:12548\n$E1D6:F0 04     BEQ $E1DC                          A:66 X:00 Y:AA P:67 SP:FB PPU:119,110 CYC:12550\n$E1DC:A2 8A     LDX #$8A                           A:66 X:00 Y:AA P:67 SP:FB PPU:128,110 CYC:12553\n$E1DE:A9 66     LDA #$66                           A:66 X:8A Y:AA P:E5 SP:FB PPU:134,110 CYC:12555\n$E1E0:B8        CLV                                A:66 X:8A Y:AA P:65 SP:FB PPU:140,110 CYC:12557\n$E1E1:18        CLC                                A:66 X:8A Y:AA P:25 SP:FB PPU:146,110 CYC:12559\n$E1E2:A0 00     LDY #$00                           A:66 X:8A Y:AA P:nvUbdIzc SP:FB PPU:152,110 CYC:12561\n$E1E4:BC FF 05  LDY $05FF,X @ 0689 = #$BB            A:66 X:8A Y:00 P:nvUbdIZc SP:FB PPU:158,110 CYC:12563\n$E1E7:10 12     BPL $E1FB                          A:66 X:8A Y:BB P:NvUbdIzc SP:FB PPU:173,110 CYC:12568\n$E1E9:F0 10     BEQ $E1FB                          A:66 X:8A Y:BB P:NvUbdIzc SP:FB PPU:179,110 CYC:12570\n$E1EB:70 0E     BVS $E1FB                          A:66 X:8A Y:BB P:NvUbdIzc SP:FB PPU:185,110 CYC:12572\n$E1ED:B0 0C     BCS $E1FB                          A:66 X:8A Y:BB P:NvUbdIzc SP:FB PPU:191,110 CYC:12574\n$E1EF:C0 BB     CPY #$BB                           A:66 X:8A Y:BB P:NvUbdIzc SP:FB PPU:197,110 CYC:12576\n$E1F1:D0 08     BNE $E1FB                          A:66 X:8A Y:BB P:nvUbdIZC SP:FB PPU:203,110 CYC:12578\n$E1F3:C9 66     CMP #$66                           A:66 X:8A Y:BB P:nvUbdIZC SP:FB PPU:209,110 CYC:12580\n$E1F5:D0 04     BNE $E1FB                          A:66 X:8A Y:BB P:nvUbdIZC SP:FB PPU:215,110 CYC:12582\n$E1F7:E0 8A     CPX #$8A                           A:66 X:8A Y:BB P:nvUbdIZC SP:FB PPU:221,110 CYC:12584\n$E1F9:F0 04     BEQ $E1FF                          A:66 X:8A Y:BB P:nvUbdIZC SP:FB PPU:227,110 CYC:12586\n$E1FF:A0 53     LDY #$53                           A:66 X:8A Y:BB P:nvUbdIZC SP:FB PPU:236,110 CYC:12589\n$E201:A9 AA     LDA #$AA                           A:66 X:8A Y:53 P:25 SP:FB PPU:242,110 CYC:12591\n$E203:A2 78     LDX #$78                           A:AA X:8A Y:53 P:A5 SP:FB PPU:248,110 CYC:12593\n$E205:8D 78 06  STA $0678 = #$00                     A:AA X:78 Y:53 P:25 SP:FB PPU:254,110 CYC:12595\n$E208:20 B6 F7  JSR $F7B6                          A:AA X:78 Y:53 P:25 SP:FB PPU:266,110 CYC:12599\n$F7B6:18        CLC                                A:AA X:78 Y:53 P:25 SP:F9 PPU:284,110 CYC:12605\n$F7B7:A9 FF     LDA #$FF                           A:AA X:78 Y:53 P:nvUbdIzc SP:F9 PPU:290,110 CYC:12607\n$F7B9:85 01     STA $01 = #$FF                       A:FF X:78 Y:53 P:NvUbdIzc SP:F9 PPU:296,110 CYC:12609\n$F7BB:24 01     BIT $01 = #$FF                       A:FF X:78 Y:53 P:NvUbdIzc SP:F9 PPU:305,110 CYC:12612\n$F7BD:A9 55     LDA #$55                           A:FF X:78 Y:53 P:NVUbdIzc SP:F9 PPU:314,110 CYC:12615\n$F7BF:60        RTS                                A:55 X:78 Y:53 P:64 SP:F9 PPU:320,110 CYC:12617\n$E20B:1D 00 06  ORA $0600,X @ 0678 = #$AA            A:55 X:78 Y:53 P:64 SP:FB PPU:338,110 CYC:12623\n$E20E:20 C0 F7  JSR $F7C0                          A:FF X:78 Y:53 P:NVUbdIzc SP:FB PPU:  9,111 CYC:12627\n$F7C0:B0 09     BCS $F7CB                          A:FF X:78 Y:53 P:NVUbdIzc SP:F9 PPU: 27,111 CYC:12633\n$F7C2:10 07     BPL $F7CB                          A:FF X:78 Y:53 P:NVUbdIzc SP:F9 PPU: 33,111 CYC:12635\n$F7C4:C9 FF     CMP #$FF                           A:FF X:78 Y:53 P:NVUbdIzc SP:F9 PPU: 39,111 CYC:12637\n$F7C6:D0 03     BNE $F7CB                          A:FF X:78 Y:53 P:67 SP:F9 PPU: 45,111 CYC:12639\n$F7C8:50 01     BVC $F7CB                          A:FF X:78 Y:53 P:67 SP:F9 PPU: 51,111 CYC:12641\n$F7CA:60        RTS                                A:FF X:78 Y:53 P:67 SP:F9 PPU: 57,111 CYC:12643\n$E211:C8        INY                                A:FF X:78 Y:53 P:67 SP:FB PPU: 75,111 CYC:12649\n$E212:A9 00     LDA #$00                           A:FF X:78 Y:54 P:65 SP:FB PPU: 81,111 CYC:12651\n$E214:8D 78 06  STA $0678 = #$AA                     A:00 X:78 Y:54 P:67 SP:FB PPU: 87,111 CYC:12653\n$E217:20 CE F7  JSR $F7CE                          A:00 X:78 Y:54 P:67 SP:FB PPU: 99,111 CYC:12657\n$F7CE:38        SEC                                A:00 X:78 Y:54 P:67 SP:F9 PPU:117,111 CYC:12663\n$F7CF:B8        CLV                                A:00 X:78 Y:54 P:67 SP:F9 PPU:123,111 CYC:12665\n$F7D0:A9 00     LDA #$00                           A:00 X:78 Y:54 P:nvUbdIZC SP:F9 PPU:129,111 CYC:12667\n$F7D2:60        RTS                                A:00 X:78 Y:54 P:nvUbdIZC SP:F9 PPU:135,111 CYC:12669\n$E21A:1D 00 06  ORA $0600,X @ 0678 = #$00            A:00 X:78 Y:54 P:nvUbdIZC SP:FB PPU:153,111 CYC:12675\n$E21D:20 D3 F7  JSR $F7D3                          A:00 X:78 Y:54 P:nvUbdIZC SP:FB PPU:165,111 CYC:12679\n$F7D3:D0 07     BNE $F7DC                          A:00 X:78 Y:54 P:nvUbdIZC SP:F9 PPU:183,111 CYC:12685\n$F7D5:70 05     BVS $F7DC                          A:00 X:78 Y:54 P:nvUbdIZC SP:F9 PPU:189,111 CYC:12687\n$F7D7:90 03     BCC $F7DC                          A:00 X:78 Y:54 P:nvUbdIZC SP:F9 PPU:195,111 CYC:12689\n$F7D9:30 01     BMI $F7DC                          A:00 X:78 Y:54 P:nvUbdIZC SP:F9 PPU:201,111 CYC:12691\n$F7DB:60        RTS                                A:00 X:78 Y:54 P:nvUbdIZC SP:F9 PPU:207,111 CYC:12693\n$E220:C8        INY                                A:00 X:78 Y:54 P:nvUbdIZC SP:FB PPU:225,111 CYC:12699\n$E221:A9 AA     LDA #$AA                           A:00 X:78 Y:55 P:25 SP:FB PPU:231,111 CYC:12701\n$E223:8D 78 06  STA $0678 = #$00                     A:AA X:78 Y:55 P:A5 SP:FB PPU:237,111 CYC:12703\n$E226:20 DF F7  JSR $F7DF                          A:AA X:78 Y:55 P:A5 SP:FB PPU:249,111 CYC:12707\n$F7DF:18        CLC                                A:AA X:78 Y:55 P:A5 SP:F9 PPU:267,111 CYC:12713\n$F7E0:24 01     BIT $01 = #$FF                       A:AA X:78 Y:55 P:NvUbdIzc SP:F9 PPU:273,111 CYC:12715\n$F7E2:A9 55     LDA #$55                           A:AA X:78 Y:55 P:NVUbdIzc SP:F9 PPU:282,111 CYC:12718\n$F7E4:60        RTS                                A:55 X:78 Y:55 P:64 SP:F9 PPU:288,111 CYC:12720\n$E229:3D 00 06  AND $0600,X @ 0678 = #$AA            A:55 X:78 Y:55 P:64 SP:FB PPU:306,111 CYC:12726\n$E22C:20 E5 F7  JSR $F7E5                          A:00 X:78 Y:55 P:nVUbdIZc SP:FB PPU:318,111 CYC:12730\n$F7E5:D0 07     BNE $F7EE                          A:00 X:78 Y:55 P:nVUbdIZc SP:F9 PPU:336,111 CYC:12736\n$F7E7:50 05     BVC $F7EE                          A:00 X:78 Y:55 P:nVUbdIZc SP:F9 PPU:  1,112 CYC:12738\n$F7E9:B0 03     BCS $F7EE                          A:00 X:78 Y:55 P:nVUbdIZc SP:F9 PPU:  7,112 CYC:12740\n$F7EB:30 01     BMI $F7EE                          A:00 X:78 Y:55 P:nVUbdIZc SP:F9 PPU: 13,112 CYC:12742\n$F7ED:60        RTS                                A:00 X:78 Y:55 P:nVUbdIZc SP:F9 PPU: 19,112 CYC:12744\n$E22F:C8        INY                                A:00 X:78 Y:55 P:nVUbdIZc SP:FB PPU: 37,112 CYC:12750\n$E230:A9 EF     LDA #$EF                           A:00 X:78 Y:56 P:64 SP:FB PPU: 43,112 CYC:12752\n$E232:8D 78 06  STA $0678 = #$AA                     A:EF X:78 Y:56 P:NVUbdIzc SP:FB PPU: 49,112 CYC:12754\n$E235:20 F1 F7  JSR $F7F1                          A:EF X:78 Y:56 P:NVUbdIzc SP:FB PPU: 61,112 CYC:12758\n$F7F1:38        SEC                                A:EF X:78 Y:56 P:NVUbdIzc SP:F9 PPU: 79,112 CYC:12764\n$F7F2:B8        CLV                                A:EF X:78 Y:56 P:E5 SP:F9 PPU: 85,112 CYC:12766\n$F7F3:A9 F8     LDA #$F8                           A:EF X:78 Y:56 P:A5 SP:F9 PPU: 91,112 CYC:12768\n$F7F5:60        RTS                                A:F8 X:78 Y:56 P:A5 SP:F9 PPU: 97,112 CYC:12770\n$E238:3D 00 06  AND $0600,X @ 0678 = #$EF            A:F8 X:78 Y:56 P:A5 SP:FB PPU:115,112 CYC:12776\n$E23B:20 F6 F7  JSR $F7F6                          A:E8 X:78 Y:56 P:A5 SP:FB PPU:127,112 CYC:12780\n$F7F6:90 09     BCC $F801                          A:E8 X:78 Y:56 P:A5 SP:F9 PPU:145,112 CYC:12786\n$F7F8:10 07     BPL $F801                          A:E8 X:78 Y:56 P:A5 SP:F9 PPU:151,112 CYC:12788\n$F7FA:C9 E8     CMP #$E8                           A:E8 X:78 Y:56 P:A5 SP:F9 PPU:157,112 CYC:12790\n$F7FC:D0 03     BNE $F801                          A:E8 X:78 Y:56 P:nvUbdIZC SP:F9 PPU:163,112 CYC:12792\n$F7FE:70 01     BVS $F801                          A:E8 X:78 Y:56 P:nvUbdIZC SP:F9 PPU:169,112 CYC:12794\n$F800:60        RTS                                A:E8 X:78 Y:56 P:nvUbdIZC SP:F9 PPU:175,112 CYC:12796\n$E23E:C8        INY                                A:E8 X:78 Y:56 P:nvUbdIZC SP:FB PPU:193,112 CYC:12802\n$E23F:A9 AA     LDA #$AA                           A:E8 X:78 Y:57 P:25 SP:FB PPU:199,112 CYC:12804\n$E241:8D 78 06  STA $0678 = #$EF                     A:AA X:78 Y:57 P:A5 SP:FB PPU:205,112 CYC:12806\n$E244:20 04 F8  JSR $F804                          A:AA X:78 Y:57 P:A5 SP:FB PPU:217,112 CYC:12810\n$F804:18        CLC                                A:AA X:78 Y:57 P:A5 SP:F9 PPU:235,112 CYC:12816\n$F805:24 01     BIT $01 = #$FF                       A:AA X:78 Y:57 P:NvUbdIzc SP:F9 PPU:241,112 CYC:12818\n$F807:A9 5F     LDA #$5F                           A:AA X:78 Y:57 P:NVUbdIzc SP:F9 PPU:250,112 CYC:12821\n$F809:60        RTS                                A:5F X:78 Y:57 P:64 SP:F9 PPU:256,112 CYC:12823\n$E247:5D 00 06  EOR $0600,X @ 0678 = #$AA            A:5F X:78 Y:57 P:64 SP:FB PPU:274,112 CYC:12829\n$E24A:20 0A F8  JSR $F80A                          A:F5 X:78 Y:57 P:NVUbdIzc SP:FB PPU:286,112 CYC:12833\n$F80A:B0 09     BCS $F815                          A:F5 X:78 Y:57 P:NVUbdIzc SP:F9 PPU:304,112 CYC:12839\n$F80C:10 07     BPL $F815                          A:F5 X:78 Y:57 P:NVUbdIzc SP:F9 PPU:310,112 CYC:12841\n$F80E:C9 F5     CMP #$F5                           A:F5 X:78 Y:57 P:NVUbdIzc SP:F9 PPU:316,112 CYC:12843\n$F810:D0 03     BNE $F815                          A:F5 X:78 Y:57 P:67 SP:F9 PPU:322,112 CYC:12845\n$F812:50 01     BVC $F815                          A:F5 X:78 Y:57 P:67 SP:F9 PPU:328,112 CYC:12847\n$F814:60        RTS                                A:F5 X:78 Y:57 P:67 SP:F9 PPU:334,112 CYC:12849\n$E24D:C8        INY                                A:F5 X:78 Y:57 P:67 SP:FB PPU: 11,113 CYC:12855\n$E24E:A9 70     LDA #$70                           A:F5 X:78 Y:58 P:65 SP:FB PPU: 17,113 CYC:12857\n$E250:8D 78 06  STA $0678 = #$AA                     A:70 X:78 Y:58 P:65 SP:FB PPU: 23,113 CYC:12859\n$E253:20 18 F8  JSR $F818                          A:70 X:78 Y:58 P:65 SP:FB PPU: 35,113 CYC:12863\n$F818:38        SEC                                A:70 X:78 Y:58 P:65 SP:F9 PPU: 53,113 CYC:12869\n$F819:B8        CLV                                A:70 X:78 Y:58 P:65 SP:F9 PPU: 59,113 CYC:12871\n$F81A:A9 70     LDA #$70                           A:70 X:78 Y:58 P:25 SP:F9 PPU: 65,113 CYC:12873\n$F81C:60        RTS                                A:70 X:78 Y:58 P:25 SP:F9 PPU: 71,113 CYC:12875\n$E256:5D 00 06  EOR $0600,X @ 0678 = #$70            A:70 X:78 Y:58 P:25 SP:FB PPU: 89,113 CYC:12881\n$E259:20 1D F8  JSR $F81D                          A:00 X:78 Y:58 P:nvUbdIZC SP:FB PPU:101,113 CYC:12885\n$F81D:D0 07     BNE $F826                          A:00 X:78 Y:58 P:nvUbdIZC SP:F9 PPU:119,113 CYC:12891\n$F81F:70 05     BVS $F826                          A:00 X:78 Y:58 P:nvUbdIZC SP:F9 PPU:125,113 CYC:12893\n$F821:90 03     BCC $F826                          A:00 X:78 Y:58 P:nvUbdIZC SP:F9 PPU:131,113 CYC:12895\n$F823:30 01     BMI $F826                          A:00 X:78 Y:58 P:nvUbdIZC SP:F9 PPU:137,113 CYC:12897\n$F825:60        RTS                                A:00 X:78 Y:58 P:nvUbdIZC SP:F9 PPU:143,113 CYC:12899\n$E25C:C8        INY                                A:00 X:78 Y:58 P:nvUbdIZC SP:FB PPU:161,113 CYC:12905\n$E25D:A9 69     LDA #$69                           A:00 X:78 Y:59 P:25 SP:FB PPU:167,113 CYC:12907\n$E25F:8D 78 06  STA $0678 = #$70                     A:69 X:78 Y:59 P:25 SP:FB PPU:173,113 CYC:12909\n$E262:20 29 F8  JSR $F829                          A:69 X:78 Y:59 P:25 SP:FB PPU:185,113 CYC:12913\n$F829:18        CLC                                A:69 X:78 Y:59 P:25 SP:F9 PPU:203,113 CYC:12919\n$F82A:24 01     BIT $01 = #$FF                       A:69 X:78 Y:59 P:nvUbdIzc SP:F9 PPU:209,113 CYC:12921\n$F82C:A9 00     LDA #$00                           A:69 X:78 Y:59 P:NVUbdIzc SP:F9 PPU:218,113 CYC:12924\n$F82E:60        RTS                                A:00 X:78 Y:59 P:nVUbdIZc SP:F9 PPU:224,113 CYC:12926\n$E265:7D 00 06  ADC $0600,X @ 0678 = #$69            A:00 X:78 Y:59 P:nVUbdIZc SP:FB PPU:242,113 CYC:12932\n$E268:20 2F F8  JSR $F82F                          A:69 X:78 Y:59 P:nvUbdIzc SP:FB PPU:254,113 CYC:12936\n$F82F:30 09     BMI $F83A                          A:69 X:78 Y:59 P:nvUbdIzc SP:F9 PPU:272,113 CYC:12942\n$F831:B0 07     BCS $F83A                          A:69 X:78 Y:59 P:nvUbdIzc SP:F9 PPU:278,113 CYC:12944\n$F833:C9 69     CMP #$69                           A:69 X:78 Y:59 P:nvUbdIzc SP:F9 PPU:284,113 CYC:12946\n$F835:D0 03     BNE $F83A                          A:69 X:78 Y:59 P:nvUbdIZC SP:F9 PPU:290,113 CYC:12948\n$F837:70 01     BVS $F83A                          A:69 X:78 Y:59 P:nvUbdIZC SP:F9 PPU:296,113 CYC:12950\n$F839:60        RTS                                A:69 X:78 Y:59 P:nvUbdIZC SP:F9 PPU:302,113 CYC:12952\n$E26B:C8        INY                                A:69 X:78 Y:59 P:nvUbdIZC SP:FB PPU:320,113 CYC:12958\n$E26C:20 3D F8  JSR $F83D                          A:69 X:78 Y:5A P:25 SP:FB PPU:326,113 CYC:12960\n$F83D:38        SEC                                A:69 X:78 Y:5A P:25 SP:F9 PPU:  3,114 CYC:12966\n$F83E:24 01     BIT $01 = #$FF                       A:69 X:78 Y:5A P:25 SP:F9 PPU:  9,114 CYC:12968\n$F840:A9 00     LDA #$00                           A:69 X:78 Y:5A P:E5 SP:F9 PPU: 18,114 CYC:12971\n$F842:60        RTS                                A:00 X:78 Y:5A P:67 SP:F9 PPU: 24,114 CYC:12973\n$E26F:7D 00 06  ADC $0600,X @ 0678 = #$69            A:00 X:78 Y:5A P:67 SP:FB PPU: 42,114 CYC:12979\n$E272:20 43 F8  JSR $F843                          A:6A X:78 Y:5A P:nvUbdIzc SP:FB PPU: 54,114 CYC:12983\n$F843:30 09     BMI $F84E                          A:6A X:78 Y:5A P:nvUbdIzc SP:F9 PPU: 72,114 CYC:12989\n$F845:B0 07     BCS $F84E                          A:6A X:78 Y:5A P:nvUbdIzc SP:F9 PPU: 78,114 CYC:12991\n$F847:C9 6A     CMP #$6A                           A:6A X:78 Y:5A P:nvUbdIzc SP:F9 PPU: 84,114 CYC:12993\n$F849:D0 03     BNE $F84E                          A:6A X:78 Y:5A P:nvUbdIZC SP:F9 PPU: 90,114 CYC:12995\n$F84B:70 01     BVS $F84E                          A:6A X:78 Y:5A P:nvUbdIZC SP:F9 PPU: 96,114 CYC:12997\n$F84D:60        RTS                                A:6A X:78 Y:5A P:nvUbdIZC SP:F9 PPU:102,114 CYC:12999\n$E275:C8        INY                                A:6A X:78 Y:5A P:nvUbdIZC SP:FB PPU:120,114 CYC:13005\n$E276:A9 7F     LDA #$7F                           A:6A X:78 Y:5B P:25 SP:FB PPU:126,114 CYC:13007\n$E278:8D 78 06  STA $0678 = #$69                     A:7F X:78 Y:5B P:25 SP:FB PPU:132,114 CYC:13009\n$E27B:20 51 F8  JSR $F851                          A:7F X:78 Y:5B P:25 SP:FB PPU:144,114 CYC:13013\n$F851:38        SEC                                A:7F X:78 Y:5B P:25 SP:F9 PPU:162,114 CYC:13019\n$F852:B8        CLV                                A:7F X:78 Y:5B P:25 SP:F9 PPU:168,114 CYC:13021\n$F853:A9 7F     LDA #$7F                           A:7F X:78 Y:5B P:25 SP:F9 PPU:174,114 CYC:13023\n$F855:60        RTS                                A:7F X:78 Y:5B P:25 SP:F9 PPU:180,114 CYC:13025\n$E27E:7D 00 06  ADC $0600,X @ 0678 = #$7F            A:7F X:78 Y:5B P:25 SP:FB PPU:198,114 CYC:13031\n$E281:20 56 F8  JSR $F856                          A:FF X:78 Y:5B P:NVUbdIzc SP:FB PPU:210,114 CYC:13035\n$F856:10 09     BPL $F861                          A:FF X:78 Y:5B P:NVUbdIzc SP:F9 PPU:228,114 CYC:13041\n$F858:B0 07     BCS $F861                          A:FF X:78 Y:5B P:NVUbdIzc SP:F9 PPU:234,114 CYC:13043\n$F85A:C9 FF     CMP #$FF                           A:FF X:78 Y:5B P:NVUbdIzc SP:F9 PPU:240,114 CYC:13045\n$F85C:D0 03     BNE $F861                          A:FF X:78 Y:5B P:67 SP:F9 PPU:246,114 CYC:13047\n$F85E:50 01     BVC $F861                          A:FF X:78 Y:5B P:67 SP:F9 PPU:252,114 CYC:13049\n$F860:60        RTS                                A:FF X:78 Y:5B P:67 SP:F9 PPU:258,114 CYC:13051\n$E284:C8        INY                                A:FF X:78 Y:5B P:67 SP:FB PPU:276,114 CYC:13057\n$E285:A9 80     LDA #$80                           A:FF X:78 Y:5C P:65 SP:FB PPU:282,114 CYC:13059\n$E287:8D 78 06  STA $0678 = #$7F                     A:80 X:78 Y:5C P:E5 SP:FB PPU:288,114 CYC:13061\n$E28A:20 64 F8  JSR $F864                          A:80 X:78 Y:5C P:E5 SP:FB PPU:300,114 CYC:13065\n$F864:18        CLC                                A:80 X:78 Y:5C P:E5 SP:F9 PPU:318,114 CYC:13071\n$F865:24 01     BIT $01 = #$FF                       A:80 X:78 Y:5C P:NVUbdIzc SP:F9 PPU:324,114 CYC:13073\n$F867:A9 7F     LDA #$7F                           A:80 X:78 Y:5C P:NVUbdIzc SP:F9 PPU:333,114 CYC:13076\n$F869:60        RTS                                A:7F X:78 Y:5C P:64 SP:F9 PPU:339,114 CYC:13078\n$E28D:7D 00 06  ADC $0600,X @ 0678 = #$80            A:7F X:78 Y:5C P:64 SP:FB PPU: 16,115 CYC:13084\n$E290:20 6A F8  JSR $F86A                          A:FF X:78 Y:5C P:NvUbdIzc SP:FB PPU: 28,115 CYC:13088\n$F86A:10 09     BPL $F875                          A:FF X:78 Y:5C P:NvUbdIzc SP:F9 PPU: 46,115 CYC:13094\n$F86C:B0 07     BCS $F875                          A:FF X:78 Y:5C P:NvUbdIzc SP:F9 PPU: 52,115 CYC:13096\n$F86E:C9 FF     CMP #$FF                           A:FF X:78 Y:5C P:NvUbdIzc SP:F9 PPU: 58,115 CYC:13098\n$F870:D0 03     BNE $F875                          A:FF X:78 Y:5C P:nvUbdIZC SP:F9 PPU: 64,115 CYC:13100\n$F872:70 01     BVS $F875                          A:FF X:78 Y:5C P:nvUbdIZC SP:F9 PPU: 70,115 CYC:13102\n$F874:60        RTS                                A:FF X:78 Y:5C P:nvUbdIZC SP:F9 PPU: 76,115 CYC:13104\n$E293:C8        INY                                A:FF X:78 Y:5C P:nvUbdIZC SP:FB PPU: 94,115 CYC:13110\n$E294:20 78 F8  JSR $F878                          A:FF X:78 Y:5D P:25 SP:FB PPU:100,115 CYC:13112\n$F878:38        SEC                                A:FF X:78 Y:5D P:25 SP:F9 PPU:118,115 CYC:13118\n$F879:B8        CLV                                A:FF X:78 Y:5D P:25 SP:F9 PPU:124,115 CYC:13120\n$F87A:A9 7F     LDA #$7F                           A:FF X:78 Y:5D P:25 SP:F9 PPU:130,115 CYC:13122\n$F87C:60        RTS                                A:7F X:78 Y:5D P:25 SP:F9 PPU:136,115 CYC:13124\n$E297:7D 00 06  ADC $0600,X @ 0678 = #$80            A:7F X:78 Y:5D P:25 SP:FB PPU:154,115 CYC:13130\n$E29A:20 7D F8  JSR $F87D                          A:00 X:78 Y:5D P:nvUbdIZC SP:FB PPU:166,115 CYC:13134\n$F87D:D0 07     BNE $F886                          A:00 X:78 Y:5D P:nvUbdIZC SP:F9 PPU:184,115 CYC:13140\n$F87F:30 05     BMI $F886                          A:00 X:78 Y:5D P:nvUbdIZC SP:F9 PPU:190,115 CYC:13142\n$F881:70 03     BVS $F886                          A:00 X:78 Y:5D P:nvUbdIZC SP:F9 PPU:196,115 CYC:13144\n$F883:90 01     BCC $F886                          A:00 X:78 Y:5D P:nvUbdIZC SP:F9 PPU:202,115 CYC:13146\n$F885:60        RTS                                A:00 X:78 Y:5D P:nvUbdIZC SP:F9 PPU:208,115 CYC:13148\n$E29D:C8        INY                                A:00 X:78 Y:5D P:nvUbdIZC SP:FB PPU:226,115 CYC:13154\n$E29E:A9 40     LDA #$40                           A:00 X:78 Y:5E P:25 SP:FB PPU:232,115 CYC:13156\n$E2A0:8D 78 06  STA $0678 = #$80                     A:40 X:78 Y:5E P:25 SP:FB PPU:238,115 CYC:13158\n$E2A3:20 89 F8  JSR $F889                          A:40 X:78 Y:5E P:25 SP:FB PPU:250,115 CYC:13162\n$F889:24 01     BIT $01 = #$FF                       A:40 X:78 Y:5E P:25 SP:F9 PPU:268,115 CYC:13168\n$F88B:A9 40     LDA #$40                           A:40 X:78 Y:5E P:E5 SP:F9 PPU:277,115 CYC:13171\n$F88D:60        RTS                                A:40 X:78 Y:5E P:65 SP:F9 PPU:283,115 CYC:13173\n$E2A6:DD 00 06  CMP $0600,X @ 0678 = #$40            A:40 X:78 Y:5E P:65 SP:FB PPU:301,115 CYC:13179\n$E2A9:20 8E F8  JSR $F88E                          A:40 X:78 Y:5E P:67 SP:FB PPU:313,115 CYC:13183\n$F88E:30 07     BMI $F897                          A:40 X:78 Y:5E P:67 SP:F9 PPU:331,115 CYC:13189\n$F890:90 05     BCC $F897                          A:40 X:78 Y:5E P:67 SP:F9 PPU:337,115 CYC:13191\n$F892:D0 03     BNE $F897                          A:40 X:78 Y:5E P:67 SP:F9 PPU:  2,116 CYC:13193\n$F894:50 01     BVC $F897                          A:40 X:78 Y:5E P:67 SP:F9 PPU:  8,116 CYC:13195\n$F896:60        RTS                                A:40 X:78 Y:5E P:67 SP:F9 PPU: 14,116 CYC:13197\n$E2AC:C8        INY                                A:40 X:78 Y:5E P:67 SP:FB PPU: 32,116 CYC:13203\n$E2AD:48        PHA                                A:40 X:78 Y:5F P:65 SP:FB PPU: 38,116 CYC:13205\n$E2AE:A9 3F     LDA #$3F                           A:40 X:78 Y:5F P:65 SP:FA PPU: 47,116 CYC:13208\n$E2B0:8D 78 06  STA $0678 = #$40                     A:3F X:78 Y:5F P:65 SP:FA PPU: 53,116 CYC:13210\n$E2B3:68        PLA                                A:3F X:78 Y:5F P:65 SP:FA PPU: 65,116 CYC:13214\n$E2B4:20 9A F8  JSR $F89A                          A:40 X:78 Y:5F P:65 SP:FB PPU: 77,116 CYC:13218\n$F89A:B8        CLV                                A:40 X:78 Y:5F P:65 SP:F9 PPU: 95,116 CYC:13224\n$F89B:60        RTS                                A:40 X:78 Y:5F P:25 SP:F9 PPU:101,116 CYC:13226\n$E2B7:DD 00 06  CMP $0600,X @ 0678 = #$3F            A:40 X:78 Y:5F P:25 SP:FB PPU:119,116 CYC:13232\n$E2BA:20 9C F8  JSR $F89C                          A:40 X:78 Y:5F P:25 SP:FB PPU:131,116 CYC:13236\n$F89C:F0 07     BEQ $F8A5                          A:40 X:78 Y:5F P:25 SP:F9 PPU:149,116 CYC:13242\n$F89E:30 05     BMI $F8A5                          A:40 X:78 Y:5F P:25 SP:F9 PPU:155,116 CYC:13244\n$F8A0:90 03     BCC $F8A5                          A:40 X:78 Y:5F P:25 SP:F9 PPU:161,116 CYC:13246\n$F8A2:70 01     BVS $F8A5                          A:40 X:78 Y:5F P:25 SP:F9 PPU:167,116 CYC:13248\n$F8A4:60        RTS                                A:40 X:78 Y:5F P:25 SP:F9 PPU:173,116 CYC:13250\n$E2BD:C8        INY                                A:40 X:78 Y:5F P:25 SP:FB PPU:191,116 CYC:13256\n$E2BE:48        PHA                                A:40 X:78 Y:60 P:25 SP:FB PPU:197,116 CYC:13258\n$E2BF:A9 41     LDA #$41                           A:40 X:78 Y:60 P:25 SP:FA PPU:206,116 CYC:13261\n$E2C1:8D 78 06  STA $0678 = #$3F                     A:41 X:78 Y:60 P:25 SP:FA PPU:212,116 CYC:13263\n$E2C4:68        PLA                                A:41 X:78 Y:60 P:25 SP:FA PPU:224,116 CYC:13267\n$E2C5:DD 00 06  CMP $0600,X @ 0678 = #$41            A:40 X:78 Y:60 P:25 SP:FB PPU:236,116 CYC:13271\n$E2C8:20 A8 F8  JSR $F8A8                          A:40 X:78 Y:60 P:NvUbdIzc SP:FB PPU:248,116 CYC:13275\n$F8A8:F0 05     BEQ $F8AF                          A:40 X:78 Y:60 P:NvUbdIzc SP:F9 PPU:266,116 CYC:13281\n$F8AA:10 03     BPL $F8AF                          A:40 X:78 Y:60 P:NvUbdIzc SP:F9 PPU:272,116 CYC:13283\n$F8AC:10 01     BPL $F8AF                          A:40 X:78 Y:60 P:NvUbdIzc SP:F9 PPU:278,116 CYC:13285\n$F8AE:60        RTS                                A:40 X:78 Y:60 P:NvUbdIzc SP:F9 PPU:284,116 CYC:13287\n$E2CB:C8        INY                                A:40 X:78 Y:60 P:NvUbdIzc SP:FB PPU:302,116 CYC:13293\n$E2CC:48        PHA                                A:40 X:78 Y:61 P:nvUbdIzc SP:FB PPU:308,116 CYC:13295\n$E2CD:A9 00     LDA #$00                           A:40 X:78 Y:61 P:nvUbdIzc SP:FA PPU:317,116 CYC:13298\n$E2CF:8D 78 06  STA $0678 = #$41                     A:00 X:78 Y:61 P:nvUbdIZc SP:FA PPU:323,116 CYC:13300\n$E2D2:68        PLA                                A:00 X:78 Y:61 P:nvUbdIZc SP:FA PPU:335,116 CYC:13304\n$E2D3:20 B2 F8  JSR $F8B2                          A:40 X:78 Y:61 P:nvUbdIzc SP:FB PPU:  6,117 CYC:13308\n$F8B2:A9 80     LDA #$80                           A:40 X:78 Y:61 P:nvUbdIzc SP:F9 PPU: 24,117 CYC:13314\n$F8B4:60        RTS                                A:80 X:78 Y:61 P:NvUbdIzc SP:F9 PPU: 30,117 CYC:13316\n$E2D6:DD 00 06  CMP $0600,X @ 0678 = #$00            A:80 X:78 Y:61 P:NvUbdIzc SP:FB PPU: 48,117 CYC:13322\n$E2D9:20 B5 F8  JSR $F8B5                          A:80 X:78 Y:61 P:A5 SP:FB PPU: 60,117 CYC:13326\n$F8B5:F0 05     BEQ $F8BC                          A:80 X:78 Y:61 P:A5 SP:F9 PPU: 78,117 CYC:13332\n$F8B7:10 03     BPL $F8BC                          A:80 X:78 Y:61 P:A5 SP:F9 PPU: 84,117 CYC:13334\n$F8B9:90 01     BCC $F8BC                          A:80 X:78 Y:61 P:A5 SP:F9 PPU: 90,117 CYC:13336\n$F8BB:60        RTS                                A:80 X:78 Y:61 P:A5 SP:F9 PPU: 96,117 CYC:13338\n$E2DC:C8        INY                                A:80 X:78 Y:61 P:A5 SP:FB PPU:114,117 CYC:13344\n$E2DD:48        PHA                                A:80 X:78 Y:62 P:25 SP:FB PPU:120,117 CYC:13346\n$E2DE:A9 80     LDA #$80                           A:80 X:78 Y:62 P:25 SP:FA PPU:129,117 CYC:13349\n$E2E0:8D 78 06  STA $0678 = #$00                     A:80 X:78 Y:62 P:A5 SP:FA PPU:135,117 CYC:13351\n$E2E3:68        PLA                                A:80 X:78 Y:62 P:A5 SP:FA PPU:147,117 CYC:13355\n$E2E4:DD 00 06  CMP $0600,X @ 0678 = #$80            A:80 X:78 Y:62 P:A5 SP:FB PPU:159,117 CYC:13359\n$E2E7:20 BF F8  JSR $F8BF                          A:80 X:78 Y:62 P:nvUbdIZC SP:FB PPU:171,117 CYC:13363\n$F8BF:D0 05     BNE $F8C6                          A:80 X:78 Y:62 P:nvUbdIZC SP:F9 PPU:189,117 CYC:13369\n$F8C1:30 03     BMI $F8C6                          A:80 X:78 Y:62 P:nvUbdIZC SP:F9 PPU:195,117 CYC:13371\n$F8C3:90 01     BCC $F8C6                          A:80 X:78 Y:62 P:nvUbdIZC SP:F9 PPU:201,117 CYC:13373\n$F8C5:60        RTS                                A:80 X:78 Y:62 P:nvUbdIZC SP:F9 PPU:207,117 CYC:13375\n$E2EA:C8        INY                                A:80 X:78 Y:62 P:nvUbdIZC SP:FB PPU:225,117 CYC:13381\n$E2EB:48        PHA                                A:80 X:78 Y:63 P:25 SP:FB PPU:231,117 CYC:13383\n$E2EC:A9 81     LDA #$81                           A:80 X:78 Y:63 P:25 SP:FA PPU:240,117 CYC:13386\n$E2EE:8D 78 06  STA $0678 = #$80                     A:81 X:78 Y:63 P:A5 SP:FA PPU:246,117 CYC:13388\n$E2F1:68        PLA                                A:81 X:78 Y:63 P:A5 SP:FA PPU:258,117 CYC:13392\n$E2F2:DD 00 06  CMP $0600,X @ 0678 = #$81            A:80 X:78 Y:63 P:A5 SP:FB PPU:270,117 CYC:13396\n$E2F5:20 C9 F8  JSR $F8C9                          A:80 X:78 Y:63 P:NvUbdIzc SP:FB PPU:282,117 CYC:13400\n$F8C9:B0 05     BCS $F8D0                          A:80 X:78 Y:63 P:NvUbdIzc SP:F9 PPU:300,117 CYC:13406\n$F8CB:F0 03     BEQ $F8D0                          A:80 X:78 Y:63 P:NvUbdIzc SP:F9 PPU:306,117 CYC:13408\n$F8CD:10 01     BPL $F8D0                          A:80 X:78 Y:63 P:NvUbdIzc SP:F9 PPU:312,117 CYC:13410\n$F8CF:60        RTS                                A:80 X:78 Y:63 P:NvUbdIzc SP:F9 PPU:318,117 CYC:13412\n$E2F8:C8        INY                                A:80 X:78 Y:63 P:NvUbdIzc SP:FB PPU:336,117 CYC:13418\n$E2F9:48        PHA                                A:80 X:78 Y:64 P:nvUbdIzc SP:FB PPU:  1,118 CYC:13420\n$E2FA:A9 7F     LDA #$7F                           A:80 X:78 Y:64 P:nvUbdIzc SP:FA PPU: 10,118 CYC:13423\n$E2FC:8D 78 06  STA $0678 = #$81                     A:7F X:78 Y:64 P:nvUbdIzc SP:FA PPU: 16,118 CYC:13425\n$E2FF:68        PLA                                A:7F X:78 Y:64 P:nvUbdIzc SP:FA PPU: 28,118 CYC:13429\n$E300:DD 00 06  CMP $0600,X @ 0678 = #$7F            A:80 X:78 Y:64 P:NvUbdIzc SP:FB PPU: 40,118 CYC:13433\n$E303:20 D3 F8  JSR $F8D3                          A:80 X:78 Y:64 P:25 SP:FB PPU: 52,118 CYC:13437\n$F8D3:90 05     BCC $F8DA                          A:80 X:78 Y:64 P:25 SP:F9 PPU: 70,118 CYC:13443\n$F8D5:F0 03     BEQ $F8DA                          A:80 X:78 Y:64 P:25 SP:F9 PPU: 76,118 CYC:13445\n$F8D7:30 01     BMI $F8DA                          A:80 X:78 Y:64 P:25 SP:F9 PPU: 82,118 CYC:13447\n$F8D9:60        RTS                                A:80 X:78 Y:64 P:25 SP:F9 PPU: 88,118 CYC:13449\n$E306:C8        INY                                A:80 X:78 Y:64 P:25 SP:FB PPU:106,118 CYC:13455\n$E307:A9 40     LDA #$40                           A:80 X:78 Y:65 P:25 SP:FB PPU:112,118 CYC:13457\n$E309:8D 78 06  STA $0678 = #$7F                     A:40 X:78 Y:65 P:25 SP:FB PPU:118,118 CYC:13459\n$E30C:20 31 F9  JSR $F931                          A:40 X:78 Y:65 P:25 SP:FB PPU:130,118 CYC:13463\n$F931:24 01     BIT $01 = #$FF                       A:40 X:78 Y:65 P:25 SP:F9 PPU:148,118 CYC:13469\n$F933:A9 40     LDA #$40                           A:40 X:78 Y:65 P:E5 SP:F9 PPU:157,118 CYC:13472\n$F935:38        SEC                                A:40 X:78 Y:65 P:65 SP:F9 PPU:163,118 CYC:13474\n$F936:60        RTS                                A:40 X:78 Y:65 P:65 SP:F9 PPU:169,118 CYC:13476\n$E30F:FD 00 06  SBC $0600,X @ 0678 = #$40            A:40 X:78 Y:65 P:65 SP:FB PPU:187,118 CYC:13482\n$E312:20 37 F9  JSR $F937                          A:00 X:78 Y:65 P:nvUbdIZC SP:FB PPU:199,118 CYC:13486\n$F937:30 0B     BMI $F944                          A:00 X:78 Y:65 P:nvUbdIZC SP:F9 PPU:217,118 CYC:13492\n$F939:90 09     BCC $F944                          A:00 X:78 Y:65 P:nvUbdIZC SP:F9 PPU:223,118 CYC:13494\n$F93B:D0 07     BNE $F944                          A:00 X:78 Y:65 P:nvUbdIZC SP:F9 PPU:229,118 CYC:13496\n$F93D:70 05     BVS $F944                          A:00 X:78 Y:65 P:nvUbdIZC SP:F9 PPU:235,118 CYC:13498\n$F93F:C9 00     CMP #$00                           A:00 X:78 Y:65 P:nvUbdIZC SP:F9 PPU:241,118 CYC:13500\n$F941:D0 01     BNE $F944                          A:00 X:78 Y:65 P:nvUbdIZC SP:F9 PPU:247,118 CYC:13502\n$F943:60        RTS                                A:00 X:78 Y:65 P:nvUbdIZC SP:F9 PPU:253,118 CYC:13504\n$E315:C8        INY                                A:00 X:78 Y:65 P:nvUbdIZC SP:FB PPU:271,118 CYC:13510\n$E316:A9 3F     LDA #$3F                           A:00 X:78 Y:66 P:25 SP:FB PPU:277,118 CYC:13512\n$E318:8D 78 06  STA $0678 = #$40                     A:3F X:78 Y:66 P:25 SP:FB PPU:283,118 CYC:13514\n$E31B:20 47 F9  JSR $F947                          A:3F X:78 Y:66 P:25 SP:FB PPU:295,118 CYC:13518\n$F947:B8        CLV                                A:3F X:78 Y:66 P:25 SP:F9 PPU:313,118 CYC:13524\n$F948:38        SEC                                A:3F X:78 Y:66 P:25 SP:F9 PPU:319,118 CYC:13526\n$F949:A9 40     LDA #$40                           A:3F X:78 Y:66 P:25 SP:F9 PPU:325,118 CYC:13528\n$F94B:60        RTS                                A:40 X:78 Y:66 P:25 SP:F9 PPU:331,118 CYC:13530\n$E31E:FD 00 06  SBC $0600,X @ 0678 = #$3F            A:40 X:78 Y:66 P:25 SP:FB PPU:  8,119 CYC:13536\n$E321:20 4C F9  JSR $F94C                          A:01 X:78 Y:66 P:25 SP:FB PPU: 20,119 CYC:13540\n$F94C:F0 0B     BEQ $F959                          A:01 X:78 Y:66 P:25 SP:F9 PPU: 38,119 CYC:13546\n$F94E:30 09     BMI $F959                          A:01 X:78 Y:66 P:25 SP:F9 PPU: 44,119 CYC:13548\n$F950:90 07     BCC $F959                          A:01 X:78 Y:66 P:25 SP:F9 PPU: 50,119 CYC:13550\n$F952:70 05     BVS $F959                          A:01 X:78 Y:66 P:25 SP:F9 PPU: 56,119 CYC:13552\n$F954:C9 01     CMP #$01                           A:01 X:78 Y:66 P:25 SP:F9 PPU: 62,119 CYC:13554\n$F956:D0 01     BNE $F959                          A:01 X:78 Y:66 P:nvUbdIZC SP:F9 PPU: 68,119 CYC:13556\n$F958:60        RTS                                A:01 X:78 Y:66 P:nvUbdIZC SP:F9 PPU: 74,119 CYC:13558\n$E324:C8        INY                                A:01 X:78 Y:66 P:nvUbdIZC SP:FB PPU: 92,119 CYC:13564\n$E325:A9 41     LDA #$41                           A:01 X:78 Y:67 P:25 SP:FB PPU: 98,119 CYC:13566\n$E327:8D 78 06  STA $0678 = #$3F                     A:41 X:78 Y:67 P:25 SP:FB PPU:104,119 CYC:13568\n$E32A:20 5C F9  JSR $F95C                          A:41 X:78 Y:67 P:25 SP:FB PPU:116,119 CYC:13572\n$F95C:A9 40     LDA #$40                           A:41 X:78 Y:67 P:25 SP:F9 PPU:134,119 CYC:13578\n$F95E:38        SEC                                A:40 X:78 Y:67 P:25 SP:F9 PPU:140,119 CYC:13580\n$F95F:24 01     BIT $01 = #$FF                       A:40 X:78 Y:67 P:25 SP:F9 PPU:146,119 CYC:13582\n$F961:60        RTS                                A:40 X:78 Y:67 P:E5 SP:F9 PPU:155,119 CYC:13585\n$E32D:FD 00 06  SBC $0600,X @ 0678 = #$41            A:40 X:78 Y:67 P:E5 SP:FB PPU:173,119 CYC:13591\n$E330:20 62 F9  JSR $F962                          A:FF X:78 Y:67 P:NvUbdIzc SP:FB PPU:185,119 CYC:13595\n$F962:B0 0B     BCS $F96F                          A:FF X:78 Y:67 P:NvUbdIzc SP:F9 PPU:203,119 CYC:13601\n$F964:F0 09     BEQ $F96F                          A:FF X:78 Y:67 P:NvUbdIzc SP:F9 PPU:209,119 CYC:13603\n$F966:10 07     BPL $F96F                          A:FF X:78 Y:67 P:NvUbdIzc SP:F9 PPU:215,119 CYC:13605\n$F968:70 05     BVS $F96F                          A:FF X:78 Y:67 P:NvUbdIzc SP:F9 PPU:221,119 CYC:13607\n$F96A:C9 FF     CMP #$FF                           A:FF X:78 Y:67 P:NvUbdIzc SP:F9 PPU:227,119 CYC:13609\n$F96C:D0 01     BNE $F96F                          A:FF X:78 Y:67 P:nvUbdIZC SP:F9 PPU:233,119 CYC:13611\n$F96E:60        RTS                                A:FF X:78 Y:67 P:nvUbdIZC SP:F9 PPU:239,119 CYC:13613\n$E333:C8        INY                                A:FF X:78 Y:67 P:nvUbdIZC SP:FB PPU:257,119 CYC:13619\n$E334:A9 00     LDA #$00                           A:FF X:78 Y:68 P:25 SP:FB PPU:263,119 CYC:13621\n$E336:8D 78 06  STA $0678 = #$41                     A:00 X:78 Y:68 P:nvUbdIZC SP:FB PPU:269,119 CYC:13623\n$E339:20 72 F9  JSR $F972                          A:00 X:78 Y:68 P:nvUbdIZC SP:FB PPU:281,119 CYC:13627\n$F972:18        CLC                                A:00 X:78 Y:68 P:nvUbdIZC SP:F9 PPU:299,119 CYC:13633\n$F973:A9 80     LDA #$80                           A:00 X:78 Y:68 P:nvUbdIZc SP:F9 PPU:305,119 CYC:13635\n$F975:60        RTS                                A:80 X:78 Y:68 P:NvUbdIzc SP:F9 PPU:311,119 CYC:13637\n$E33C:FD 00 06  SBC $0600,X @ 0678 = #$00            A:80 X:78 Y:68 P:NvUbdIzc SP:FB PPU:329,119 CYC:13643\n$E33F:20 76 F9  JSR $F976                          A:7F X:78 Y:68 P:65 SP:FB PPU:  0,120 CYC:13647\n$F976:90 05     BCC $F97D                          A:7F X:78 Y:68 P:65 SP:F9 PPU: 18,120 CYC:13653\n$F978:C9 7F     CMP #$7F                           A:7F X:78 Y:68 P:65 SP:F9 PPU: 24,120 CYC:13655\n$F97A:D0 01     BNE $F97D                          A:7F X:78 Y:68 P:67 SP:F9 PPU: 30,120 CYC:13657\n$F97C:60        RTS                                A:7F X:78 Y:68 P:67 SP:F9 PPU: 36,120 CYC:13659\n$E342:C8        INY                                A:7F X:78 Y:68 P:67 SP:FB PPU: 54,120 CYC:13665\n$E343:A9 7F     LDA #$7F                           A:7F X:78 Y:69 P:65 SP:FB PPU: 60,120 CYC:13667\n$E345:8D 78 06  STA $0678 = #$00                     A:7F X:78 Y:69 P:65 SP:FB PPU: 66,120 CYC:13669\n$E348:20 80 F9  JSR $F980                          A:7F X:78 Y:69 P:65 SP:FB PPU: 78,120 CYC:13673\n$F980:38        SEC                                A:7F X:78 Y:69 P:65 SP:F9 PPU: 96,120 CYC:13679\n$F981:A9 81     LDA #$81                           A:7F X:78 Y:69 P:65 SP:F9 PPU:102,120 CYC:13681\n$F983:60        RTS                                A:81 X:78 Y:69 P:E5 SP:F9 PPU:108,120 CYC:13683\n$E34B:FD 00 06  SBC $0600,X @ 0678 = #$7F            A:81 X:78 Y:69 P:E5 SP:FB PPU:126,120 CYC:13689\n$E34E:20 84 F9  JSR $F984                          A:02 X:78 Y:69 P:65 SP:FB PPU:138,120 CYC:13693\n$F984:50 07     BVC $F98D                          A:02 X:78 Y:69 P:65 SP:F9 PPU:156,120 CYC:13699\n$F986:90 05     BCC $F98D                          A:02 X:78 Y:69 P:65 SP:F9 PPU:162,120 CYC:13701\n$F988:C9 02     CMP #$02                           A:02 X:78 Y:69 P:65 SP:F9 PPU:168,120 CYC:13703\n$F98A:D0 01     BNE $F98D                          A:02 X:78 Y:69 P:67 SP:F9 PPU:174,120 CYC:13705\n$F98C:60        RTS                                A:02 X:78 Y:69 P:67 SP:F9 PPU:180,120 CYC:13707\n$E351:A9 AA     LDA #$AA                           A:02 X:78 Y:69 P:67 SP:FB PPU:198,120 CYC:13713\n$E353:8D 33 06  STA $0633 = #$AA                     A:AA X:78 Y:69 P:E5 SP:FB PPU:204,120 CYC:13715\n$E356:A9 BB     LDA #$BB                           A:AA X:78 Y:69 P:E5 SP:FB PPU:216,120 CYC:13719\n$E358:8D 89 06  STA $0689 = #$BB                     A:BB X:78 Y:69 P:E5 SP:FB PPU:222,120 CYC:13721\n$E35B:A2 00     LDX #$00                           A:BB X:78 Y:69 P:E5 SP:FB PPU:234,120 CYC:13725\n$E35D:A0 66     LDY #$66                           A:BB X:00 Y:69 P:67 SP:FB PPU:240,120 CYC:13727\n$E35F:24 01     BIT $01 = #$FF                       A:BB X:00 Y:66 P:65 SP:FB PPU:246,120 CYC:13729\n$E361:38        SEC                                A:BB X:00 Y:66 P:E5 SP:FB PPU:255,120 CYC:13732\n$E362:A9 00     LDA #$00                           A:BB X:00 Y:66 P:E5 SP:FB PPU:261,120 CYC:13734\n$E364:BD 33 06  LDA $0633,X @ 0633 = #$AA            A:00 X:00 Y:66 P:67 SP:FB PPU:267,120 CYC:13736\n$E367:10 12     BPL $E37B                          A:AA X:00 Y:66 P:E5 SP:FB PPU:279,120 CYC:13740\n$E369:F0 10     BEQ $E37B                          A:AA X:00 Y:66 P:E5 SP:FB PPU:285,120 CYC:13742\n$E36B:50 0E     BVC $E37B                          A:AA X:00 Y:66 P:E5 SP:FB PPU:291,120 CYC:13744\n$E36D:90 0C     BCC $E37B                          A:AA X:00 Y:66 P:E5 SP:FB PPU:297,120 CYC:13746\n$E36F:C0 66     CPY #$66                           A:AA X:00 Y:66 P:E5 SP:FB PPU:303,120 CYC:13748\n$E371:D0 08     BNE $E37B                          A:AA X:00 Y:66 P:67 SP:FB PPU:309,120 CYC:13750\n$E373:E0 00     CPX #$00                           A:AA X:00 Y:66 P:67 SP:FB PPU:315,120 CYC:13752\n$E375:D0 04     BNE $E37B                          A:AA X:00 Y:66 P:67 SP:FB PPU:321,120 CYC:13754\n$E377:C9 AA     CMP #$AA                           A:AA X:00 Y:66 P:67 SP:FB PPU:327,120 CYC:13756\n$E379:F0 04     BEQ $E37F                          A:AA X:00 Y:66 P:67 SP:FB PPU:333,120 CYC:13758\n$E37F:A2 8A     LDX #$8A                           A:AA X:00 Y:66 P:67 SP:FB PPU:  1,121 CYC:13761\n$E381:A0 66     LDY #$66                           A:AA X:8A Y:66 P:E5 SP:FB PPU:  7,121 CYC:13763\n$E383:B8        CLV                                A:AA X:8A Y:66 P:65 SP:FB PPU: 13,121 CYC:13765\n$E384:18        CLC                                A:AA X:8A Y:66 P:25 SP:FB PPU: 19,121 CYC:13767\n$E385:A9 00     LDA #$00                           A:AA X:8A Y:66 P:nvUbdIzc SP:FB PPU: 25,121 CYC:13769\n$E387:BD FF 05  LDA $05FF,X @ 0689 = #$BB            A:00 X:8A Y:66 P:nvUbdIZc SP:FB PPU: 31,121 CYC:13771\n$E38A:10 12     BPL $E39E                          A:BB X:8A Y:66 P:NvUbdIzc SP:FB PPU: 46,121 CYC:13776\n$E38C:F0 10     BEQ $E39E                          A:BB X:8A Y:66 P:NvUbdIzc SP:FB PPU: 52,121 CYC:13778\n$E38E:70 0E     BVS $E39E                          A:BB X:8A Y:66 P:NvUbdIzc SP:FB PPU: 58,121 CYC:13780\n$E390:B0 0C     BCS $E39E                          A:BB X:8A Y:66 P:NvUbdIzc SP:FB PPU: 64,121 CYC:13782\n$E392:C9 BB     CMP #$BB                           A:BB X:8A Y:66 P:NvUbdIzc SP:FB PPU: 70,121 CYC:13784\n$E394:D0 08     BNE $E39E                          A:BB X:8A Y:66 P:nvUbdIZC SP:FB PPU: 76,121 CYC:13786\n$E396:C0 66     CPY #$66                           A:BB X:8A Y:66 P:nvUbdIZC SP:FB PPU: 82,121 CYC:13788\n$E398:D0 04     BNE $E39E                          A:BB X:8A Y:66 P:nvUbdIZC SP:FB PPU: 88,121 CYC:13790\n$E39A:E0 8A     CPX #$8A                           A:BB X:8A Y:66 P:nvUbdIZC SP:FB PPU: 94,121 CYC:13792\n$E39C:F0 04     BEQ $E3A2                          A:BB X:8A Y:66 P:nvUbdIZC SP:FB PPU:100,121 CYC:13794\n$E3A2:24 01     BIT $01 = #$FF                       A:BB X:8A Y:66 P:nvUbdIZC SP:FB PPU:109,121 CYC:13797\n$E3A4:38        SEC                                A:BB X:8A Y:66 P:E5 SP:FB PPU:118,121 CYC:13800\n$E3A5:A9 44     LDA #$44                           A:BB X:8A Y:66 P:E5 SP:FB PPU:124,121 CYC:13802\n$E3A7:A2 00     LDX #$00                           A:44 X:8A Y:66 P:65 SP:FB PPU:130,121 CYC:13804\n$E3A9:9D 33 06  STA $0633,X @ 0633 = #$AA            A:44 X:00 Y:66 P:67 SP:FB PPU:136,121 CYC:13806\n$E3AC:AD 33 06  LDA $0633 = #$44                     A:44 X:00 Y:66 P:67 SP:FB PPU:151,121 CYC:13811\n$E3AF:90 1A     BCC $E3CB                          A:44 X:00 Y:66 P:65 SP:FB PPU:163,121 CYC:13815\n$E3B1:C9 44     CMP #$44                           A:44 X:00 Y:66 P:65 SP:FB PPU:169,121 CYC:13817\n$E3B3:D0 16     BNE $E3CB                          A:44 X:00 Y:66 P:67 SP:FB PPU:175,121 CYC:13819\n$E3B5:50 14     BVC $E3CB                          A:44 X:00 Y:66 P:67 SP:FB PPU:181,121 CYC:13821\n$E3B7:18        CLC                                A:44 X:00 Y:66 P:67 SP:FB PPU:187,121 CYC:13823\n$E3B8:B8        CLV                                A:44 X:00 Y:66 P:nVUbdIZc SP:FB PPU:193,121 CYC:13825\n$E3B9:A9 99     LDA #$99                           A:44 X:00 Y:66 P:nvUbdIZc SP:FB PPU:199,121 CYC:13827\n$E3BB:A2 80     LDX #$80                           A:99 X:00 Y:66 P:NvUbdIzc SP:FB PPU:205,121 CYC:13829\n$E3BD:9D 85 05  STA $0585,X @ 0605 = #$00            A:99 X:80 Y:66 P:NvUbdIzc SP:FB PPU:211,121 CYC:13831\n$E3C0:AD 05 06  LDA $0605 = #$99                     A:99 X:80 Y:66 P:NvUbdIzc SP:FB PPU:226,121 CYC:13836\n$E3C3:B0 06     BCS $E3CB                          A:99 X:80 Y:66 P:NvUbdIzc SP:FB PPU:238,121 CYC:13840\n$E3C5:C9 99     CMP #$99                           A:99 X:80 Y:66 P:NvUbdIzc SP:FB PPU:244,121 CYC:13842\n$E3C7:D0 02     BNE $E3CB                          A:99 X:80 Y:66 P:nvUbdIZC SP:FB PPU:250,121 CYC:13844\n$E3C9:50 04     BVC $E3CF                          A:99 X:80 Y:66 P:nvUbdIZC SP:FB PPU:256,121 CYC:13846\n$E3CF:A0 6D     LDY #$6D                           A:99 X:80 Y:66 P:nvUbdIZC SP:FB PPU:265,121 CYC:13849\n$E3D1:A2 6D     LDX #$6D                           A:99 X:80 Y:6D P:25 SP:FB PPU:271,121 CYC:13851\n$E3D3:20 90 F9  JSR $F990                          A:99 X:6D Y:6D P:25 SP:FB PPU:277,121 CYC:13853\n$F990:A2 55     LDX #$55                           A:99 X:6D Y:6D P:25 SP:F9 PPU:295,121 CYC:13859\n$F992:A9 FF     LDA #$FF                           A:99 X:55 Y:6D P:25 SP:F9 PPU:301,121 CYC:13861\n$F994:85 01     STA $01 = #$FF                       A:FF X:55 Y:6D P:A5 SP:F9 PPU:307,121 CYC:13863\n$F996:EA        NOP                                A:FF X:55 Y:6D P:A5 SP:F9 PPU:316,121 CYC:13866\n$F997:24 01     BIT $01 = #$FF                       A:FF X:55 Y:6D P:A5 SP:F9 PPU:322,121 CYC:13868\n$F999:38        SEC                                A:FF X:55 Y:6D P:E5 SP:F9 PPU:331,121 CYC:13871\n$F99A:A9 01     LDA #$01                           A:FF X:55 Y:6D P:E5 SP:F9 PPU:337,121 CYC:13873\n$F99C:60        RTS                                A:01 X:55 Y:6D P:65 SP:F9 PPU:  2,122 CYC:13875\n$E3D6:9D 00 06  STA $0600,X @ 0655 = #$00            A:01 X:55 Y:6D P:65 SP:FB PPU: 20,122 CYC:13881\n$E3D9:5E 00 06  LSR $0600,X @ 0655 = #$01            A:01 X:55 Y:6D P:65 SP:FB PPU: 35,122 CYC:13886\n$E3DC:BD 00 06  LDA $0600,X @ 0655 = #$00            A:01 X:55 Y:6D P:67 SP:FB PPU: 56,122 CYC:13893\n$E3DF:20 9D F9  JSR $F99D                          A:00 X:55 Y:6D P:67 SP:FB PPU: 68,122 CYC:13897\n$F99D:90 1B     BCC $F9BA                          A:00 X:55 Y:6D P:67 SP:F9 PPU: 86,122 CYC:13903\n$F99F:D0 19     BNE $F9BA                          A:00 X:55 Y:6D P:67 SP:F9 PPU: 92,122 CYC:13905\n$F9A1:30 17     BMI $F9BA                          A:00 X:55 Y:6D P:67 SP:F9 PPU: 98,122 CYC:13907\n$F9A3:50 15     BVC $F9BA                          A:00 X:55 Y:6D P:67 SP:F9 PPU:104,122 CYC:13909\n$F9A5:C9 00     CMP #$00                           A:00 X:55 Y:6D P:67 SP:F9 PPU:110,122 CYC:13911\n$F9A7:D0 11     BNE $F9BA                          A:00 X:55 Y:6D P:67 SP:F9 PPU:116,122 CYC:13913\n$F9A9:B8        CLV                                A:00 X:55 Y:6D P:67 SP:F9 PPU:122,122 CYC:13915\n$F9AA:A9 AA     LDA #$AA                           A:00 X:55 Y:6D P:nvUbdIZC SP:F9 PPU:128,122 CYC:13917\n$F9AC:60        RTS                                A:AA X:55 Y:6D P:A5 SP:F9 PPU:134,122 CYC:13919\n$E3E2:C8        INY                                A:AA X:55 Y:6D P:A5 SP:FB PPU:152,122 CYC:13925\n$E3E3:9D 00 06  STA $0600,X @ 0655 = #$00            A:AA X:55 Y:6E P:25 SP:FB PPU:158,122 CYC:13927\n$E3E6:5E 00 06  LSR $0600,X @ 0655 = #$AA            A:AA X:55 Y:6E P:25 SP:FB PPU:173,122 CYC:13932\n$E3E9:BD 00 06  LDA $0600,X @ 0655 = #$55            A:AA X:55 Y:6E P:nvUbdIzc SP:FB PPU:194,122 CYC:13939\n$E3EC:20 AD F9  JSR $F9AD                          A:55 X:55 Y:6E P:nvUbdIzc SP:FB PPU:206,122 CYC:13943\n$F9AD:B0 0B     BCS $F9BA                          A:55 X:55 Y:6E P:nvUbdIzc SP:F9 PPU:224,122 CYC:13949\n$F9AF:F0 09     BEQ $F9BA                          A:55 X:55 Y:6E P:nvUbdIzc SP:F9 PPU:230,122 CYC:13951\n$F9B1:30 07     BMI $F9BA                          A:55 X:55 Y:6E P:nvUbdIzc SP:F9 PPU:236,122 CYC:13953\n$F9B3:70 05     BVS $F9BA                          A:55 X:55 Y:6E P:nvUbdIzc SP:F9 PPU:242,122 CYC:13955\n$F9B5:C9 55     CMP #$55                           A:55 X:55 Y:6E P:nvUbdIzc SP:F9 PPU:248,122 CYC:13957\n$F9B7:D0 01     BNE $F9BA                          A:55 X:55 Y:6E P:nvUbdIZC SP:F9 PPU:254,122 CYC:13959\n$F9B9:60        RTS                                A:55 X:55 Y:6E P:nvUbdIZC SP:F9 PPU:260,122 CYC:13961\n$E3EF:C8        INY                                A:55 X:55 Y:6E P:nvUbdIZC SP:FB PPU:278,122 CYC:13967\n$E3F0:20 BD F9  JSR $F9BD                          A:55 X:55 Y:6F P:25 SP:FB PPU:284,122 CYC:13969\n$F9BD:24 01     BIT $01 = #$FF                       A:55 X:55 Y:6F P:25 SP:F9 PPU:302,122 CYC:13975\n$F9BF:38        SEC                                A:55 X:55 Y:6F P:E5 SP:F9 PPU:311,122 CYC:13978\n$F9C0:A9 80     LDA #$80                           A:55 X:55 Y:6F P:E5 SP:F9 PPU:317,122 CYC:13980\n$F9C2:60        RTS                                A:80 X:55 Y:6F P:E5 SP:F9 PPU:323,122 CYC:13982\n$E3F3:9D 00 06  STA $0600,X @ 0655 = #$55            A:80 X:55 Y:6F P:E5 SP:FB PPU:  0,123 CYC:13988\n$E3F6:1E 00 06  ASL $0600,X @ 0655 = #$80            A:80 X:55 Y:6F P:E5 SP:FB PPU: 15,123 CYC:13993\n$E3F9:BD 00 06  LDA $0600,X @ 0655 = #$00            A:80 X:55 Y:6F P:67 SP:FB PPU: 36,123 CYC:14000\n$E3FC:20 C3 F9  JSR $F9C3                          A:00 X:55 Y:6F P:67 SP:FB PPU: 48,123 CYC:14004\n$F9C3:90 1C     BCC $F9E1                          A:00 X:55 Y:6F P:67 SP:F9 PPU: 66,123 CYC:14010\n$F9C5:D0 1A     BNE $F9E1                          A:00 X:55 Y:6F P:67 SP:F9 PPU: 72,123 CYC:14012\n$F9C7:30 18     BMI $F9E1                          A:00 X:55 Y:6F P:67 SP:F9 PPU: 78,123 CYC:14014\n$F9C9:50 16     BVC $F9E1                          A:00 X:55 Y:6F P:67 SP:F9 PPU: 84,123 CYC:14016\n$F9CB:C9 00     CMP #$00                           A:00 X:55 Y:6F P:67 SP:F9 PPU: 90,123 CYC:14018\n$F9CD:D0 12     BNE $F9E1                          A:00 X:55 Y:6F P:67 SP:F9 PPU: 96,123 CYC:14020\n$F9CF:B8        CLV                                A:00 X:55 Y:6F P:67 SP:F9 PPU:102,123 CYC:14022\n$F9D0:A9 55     LDA #$55                           A:00 X:55 Y:6F P:nvUbdIZC SP:F9 PPU:108,123 CYC:14024\n$F9D2:38        SEC                                A:55 X:55 Y:6F P:25 SP:F9 PPU:114,123 CYC:14026\n$F9D3:60        RTS                                A:55 X:55 Y:6F P:25 SP:F9 PPU:120,123 CYC:14028\n$E3FF:C8        INY                                A:55 X:55 Y:6F P:25 SP:FB PPU:138,123 CYC:14034\n$E400:9D 00 06  STA $0600,X @ 0655 = #$00            A:55 X:55 Y:70 P:25 SP:FB PPU:144,123 CYC:14036\n$E403:1E 00 06  ASL $0600,X @ 0655 = #$55            A:55 X:55 Y:70 P:25 SP:FB PPU:159,123 CYC:14041\n$E406:BD 00 06  LDA $0600,X @ 0655 = #$AA            A:55 X:55 Y:70 P:NvUbdIzc SP:FB PPU:180,123 CYC:14048\n$E409:20 D4 F9  JSR $F9D4                          A:AA X:55 Y:70 P:NvUbdIzc SP:FB PPU:192,123 CYC:14052\n$F9D4:B0 0B     BCS $F9E1                          A:AA X:55 Y:70 P:NvUbdIzc SP:F9 PPU:210,123 CYC:14058\n$F9D6:F0 09     BEQ $F9E1                          A:AA X:55 Y:70 P:NvUbdIzc SP:F9 PPU:216,123 CYC:14060\n$F9D8:10 07     BPL $F9E1                          A:AA X:55 Y:70 P:NvUbdIzc SP:F9 PPU:222,123 CYC:14062\n$F9DA:70 05     BVS $F9E1                          A:AA X:55 Y:70 P:NvUbdIzc SP:F9 PPU:228,123 CYC:14064\n$F9DC:C9 AA     CMP #$AA                           A:AA X:55 Y:70 P:NvUbdIzc SP:F9 PPU:234,123 CYC:14066\n$F9DE:D0 01     BNE $F9E1                          A:AA X:55 Y:70 P:nvUbdIZC SP:F9 PPU:240,123 CYC:14068\n$F9E0:60        RTS                                A:AA X:55 Y:70 P:nvUbdIZC SP:F9 PPU:246,123 CYC:14070\n$E40C:C8        INY                                A:AA X:55 Y:70 P:nvUbdIZC SP:FB PPU:264,123 CYC:14076\n$E40D:20 E4 F9  JSR $F9E4                          A:AA X:55 Y:71 P:25 SP:FB PPU:270,123 CYC:14078\n$F9E4:24 01     BIT $01 = #$FF                       A:AA X:55 Y:71 P:25 SP:F9 PPU:288,123 CYC:14084\n$F9E6:38        SEC                                A:AA X:55 Y:71 P:E5 SP:F9 PPU:297,123 CYC:14087\n$F9E7:A9 01     LDA #$01                           A:AA X:55 Y:71 P:E5 SP:F9 PPU:303,123 CYC:14089\n$F9E9:60        RTS                                A:01 X:55 Y:71 P:65 SP:F9 PPU:309,123 CYC:14091\n$E410:9D 00 06  STA $0600,X @ 0655 = #$AA            A:01 X:55 Y:71 P:65 SP:FB PPU:327,123 CYC:14097\n$E413:7E 00 06  ROR $0600,X @ 0655 = #$01            A:01 X:55 Y:71 P:65 SP:FB PPU:  1,124 CYC:14102\n$E416:BD 00 06  LDA $0600,X @ 0655 = #$80            A:01 X:55 Y:71 P:E5 SP:FB PPU: 22,124 CYC:14109\n$E419:20 EA F9  JSR $F9EA                          A:80 X:55 Y:71 P:E5 SP:FB PPU: 34,124 CYC:14113\n$F9EA:90 1C     BCC $FA08                          A:80 X:55 Y:71 P:E5 SP:F9 PPU: 52,124 CYC:14119\n$F9EC:F0 1A     BEQ $FA08                          A:80 X:55 Y:71 P:E5 SP:F9 PPU: 58,124 CYC:14121\n$F9EE:10 18     BPL $FA08                          A:80 X:55 Y:71 P:E5 SP:F9 PPU: 64,124 CYC:14123\n$F9F0:50 16     BVC $FA08                          A:80 X:55 Y:71 P:E5 SP:F9 PPU: 70,124 CYC:14125\n$F9F2:C9 80     CMP #$80                           A:80 X:55 Y:71 P:E5 SP:F9 PPU: 76,124 CYC:14127\n$F9F4:D0 12     BNE $FA08                          A:80 X:55 Y:71 P:67 SP:F9 PPU: 82,124 CYC:14129\n$F9F6:B8        CLV                                A:80 X:55 Y:71 P:67 SP:F9 PPU: 88,124 CYC:14131\n$F9F7:18        CLC                                A:80 X:55 Y:71 P:nvUbdIZC SP:F9 PPU: 94,124 CYC:14133\n$F9F8:A9 55     LDA #$55                           A:80 X:55 Y:71 P:nvUbdIZc SP:F9 PPU:100,124 CYC:14135\n$F9FA:60        RTS                                A:55 X:55 Y:71 P:nvUbdIzc SP:F9 PPU:106,124 CYC:14137\n$E41C:C8        INY                                A:55 X:55 Y:71 P:nvUbdIzc SP:FB PPU:124,124 CYC:14143\n$E41D:9D 00 06  STA $0600,X @ 0655 = #$80            A:55 X:55 Y:72 P:nvUbdIzc SP:FB PPU:130,124 CYC:14145\n$E420:7E 00 06  ROR $0600,X @ 0655 = #$55            A:55 X:55 Y:72 P:nvUbdIzc SP:FB PPU:145,124 CYC:14150\n$E423:BD 00 06  LDA $0600,X @ 0655 = #$2A            A:55 X:55 Y:72 P:25 SP:FB PPU:166,124 CYC:14157\n$E426:20 FB F9  JSR $F9FB                          A:2A X:55 Y:72 P:25 SP:FB PPU:178,124 CYC:14161\n$F9FB:90 0B     BCC $FA08                          A:2A X:55 Y:72 P:25 SP:F9 PPU:196,124 CYC:14167\n$F9FD:F0 09     BEQ $FA08                          A:2A X:55 Y:72 P:25 SP:F9 PPU:202,124 CYC:14169\n$F9FF:30 07     BMI $FA08                          A:2A X:55 Y:72 P:25 SP:F9 PPU:208,124 CYC:14171\n$FA01:70 05     BVS $FA08                          A:2A X:55 Y:72 P:25 SP:F9 PPU:214,124 CYC:14173\n$FA03:C9 2A     CMP #$2A                           A:2A X:55 Y:72 P:25 SP:F9 PPU:220,124 CYC:14175\n$FA05:D0 01     BNE $FA08                          A:2A X:55 Y:72 P:nvUbdIZC SP:F9 PPU:226,124 CYC:14177\n$FA07:60        RTS                                A:2A X:55 Y:72 P:nvUbdIZC SP:F9 PPU:232,124 CYC:14179\n$E429:C8        INY                                A:2A X:55 Y:72 P:nvUbdIZC SP:FB PPU:250,124 CYC:14185\n$E42A:20 0A FA  JSR $FA0A                          A:2A X:55 Y:73 P:25 SP:FB PPU:256,124 CYC:14187\n$FA0A:24 01     BIT $01 = #$FF                       A:2A X:55 Y:73 P:25 SP:F9 PPU:274,124 CYC:14193\n$FA0C:38        SEC                                A:2A X:55 Y:73 P:E5 SP:F9 PPU:283,124 CYC:14196\n$FA0D:A9 80     LDA #$80                           A:2A X:55 Y:73 P:E5 SP:F9 PPU:289,124 CYC:14198\n$FA0F:60        RTS                                A:80 X:55 Y:73 P:E5 SP:F9 PPU:295,124 CYC:14200\n$E42D:9D 00 06  STA $0600,X @ 0655 = #$2A            A:80 X:55 Y:73 P:E5 SP:FB PPU:313,124 CYC:14206\n$E430:3E 00 06  ROL $0600,X @ 0655 = #$80            A:80 X:55 Y:73 P:E5 SP:FB PPU:328,124 CYC:14211\n$E433:BD 00 06  LDA $0600,X @ 0655 = #$01            A:80 X:55 Y:73 P:65 SP:FB PPU:  8,125 CYC:14218\n$E436:20 10 FA  JSR $FA10                          A:01 X:55 Y:73 P:65 SP:FB PPU: 20,125 CYC:14222\n$FA10:90 1C     BCC $FA2E                          A:01 X:55 Y:73 P:65 SP:F9 PPU: 38,125 CYC:14228\n$FA12:F0 1A     BEQ $FA2E                          A:01 X:55 Y:73 P:65 SP:F9 PPU: 44,125 CYC:14230\n$FA14:30 18     BMI $FA2E                          A:01 X:55 Y:73 P:65 SP:F9 PPU: 50,125 CYC:14232\n$FA16:50 16     BVC $FA2E                          A:01 X:55 Y:73 P:65 SP:F9 PPU: 56,125 CYC:14234\n$FA18:C9 01     CMP #$01                           A:01 X:55 Y:73 P:65 SP:F9 PPU: 62,125 CYC:14236\n$FA1A:D0 12     BNE $FA2E                          A:01 X:55 Y:73 P:67 SP:F9 PPU: 68,125 CYC:14238\n$FA1C:B8        CLV                                A:01 X:55 Y:73 P:67 SP:F9 PPU: 74,125 CYC:14240\n$FA1D:18        CLC                                A:01 X:55 Y:73 P:nvUbdIZC SP:F9 PPU: 80,125 CYC:14242\n$FA1E:A9 55     LDA #$55                           A:01 X:55 Y:73 P:nvUbdIZc SP:F9 PPU: 86,125 CYC:14244\n$FA20:60        RTS                                A:55 X:55 Y:73 P:nvUbdIzc SP:F9 PPU: 92,125 CYC:14246\n$E439:C8        INY                                A:55 X:55 Y:73 P:nvUbdIzc SP:FB PPU:110,125 CYC:14252\n$E43A:9D 00 06  STA $0600,X @ 0655 = #$01            A:55 X:55 Y:74 P:nvUbdIzc SP:FB PPU:116,125 CYC:14254\n$E43D:3E 00 06  ROL $0600,X @ 0655 = #$55            A:55 X:55 Y:74 P:nvUbdIzc SP:FB PPU:131,125 CYC:14259\n$E440:BD 00 06  LDA $0600,X @ 0655 = #$AA            A:55 X:55 Y:74 P:NvUbdIzc SP:FB PPU:152,125 CYC:14266\n$E443:20 21 FA  JSR $FA21                          A:AA X:55 Y:74 P:NvUbdIzc SP:FB PPU:164,125 CYC:14270\n$FA21:B0 0B     BCS $FA2E                          A:AA X:55 Y:74 P:NvUbdIzc SP:F9 PPU:182,125 CYC:14276\n$FA23:F0 09     BEQ $FA2E                          A:AA X:55 Y:74 P:NvUbdIzc SP:F9 PPU:188,125 CYC:14278\n$FA25:10 07     BPL $FA2E                          A:AA X:55 Y:74 P:NvUbdIzc SP:F9 PPU:194,125 CYC:14280\n$FA27:70 05     BVS $FA2E                          A:AA X:55 Y:74 P:NvUbdIzc SP:F9 PPU:200,125 CYC:14282\n$FA29:C9 AA     CMP #$AA                           A:AA X:55 Y:74 P:NvUbdIzc SP:F9 PPU:206,125 CYC:14284\n$FA2B:D0 01     BNE $FA2E                          A:AA X:55 Y:74 P:nvUbdIZC SP:F9 PPU:212,125 CYC:14286\n$FA2D:60        RTS                                A:AA X:55 Y:74 P:nvUbdIZC SP:F9 PPU:218,125 CYC:14288\n$E446:A9 FF     LDA #$FF                           A:AA X:55 Y:74 P:nvUbdIZC SP:FB PPU:236,125 CYC:14294\n$E448:9D 00 06  STA $0600,X @ 0655 = #$AA            A:FF X:55 Y:74 P:A5 SP:FB PPU:242,125 CYC:14296\n$E44B:85 01     STA $01 = #$FF                       A:FF X:55 Y:74 P:A5 SP:FB PPU:257,125 CYC:14301\n$E44D:24 01     BIT $01 = #$FF                       A:FF X:55 Y:74 P:A5 SP:FB PPU:266,125 CYC:14304\n$E44F:38        SEC                                A:FF X:55 Y:74 P:E5 SP:FB PPU:275,125 CYC:14307\n$E450:FE 00 06  INC $0600,X @ 0655 = #$FF            A:FF X:55 Y:74 P:E5 SP:FB PPU:281,125 CYC:14309\n$E453:D0 0D     BNE $E462                          A:FF X:55 Y:74 P:67 SP:FB PPU:302,125 CYC:14316\n$E455:30 0B     BMI $E462                          A:FF X:55 Y:74 P:67 SP:FB PPU:308,125 CYC:14318\n$E457:50 09     BVC $E462                          A:FF X:55 Y:74 P:67 SP:FB PPU:314,125 CYC:14320\n$E459:90 07     BCC $E462                          A:FF X:55 Y:74 P:67 SP:FB PPU:320,125 CYC:14322\n$E45B:BD 00 06  LDA $0600,X @ 0655 = #$00            A:FF X:55 Y:74 P:67 SP:FB PPU:326,125 CYC:14324\n$E45E:C9 00     CMP #$00                           A:00 X:55 Y:74 P:67 SP:FB PPU:338,125 CYC:14328\n$E460:F0 04     BEQ $E466                          A:00 X:55 Y:74 P:67 SP:FB PPU:  3,126 CYC:14330\n$E466:A9 7F     LDA #$7F                           A:00 X:55 Y:74 P:67 SP:FB PPU: 12,126 CYC:14333\n$E468:9D 00 06  STA $0600,X @ 0655 = #$00            A:7F X:55 Y:74 P:65 SP:FB PPU: 18,126 CYC:14335\n$E46B:B8        CLV                                A:7F X:55 Y:74 P:65 SP:FB PPU: 33,126 CYC:14340\n$E46C:18        CLC                                A:7F X:55 Y:74 P:25 SP:FB PPU: 39,126 CYC:14342\n$E46D:FE 00 06  INC $0600,X @ 0655 = #$7F            A:7F X:55 Y:74 P:nvUbdIzc SP:FB PPU: 45,126 CYC:14344\n$E470:F0 0D     BEQ $E47F                          A:7F X:55 Y:74 P:NvUbdIzc SP:FB PPU: 66,126 CYC:14351\n$E472:10 0B     BPL $E47F                          A:7F X:55 Y:74 P:NvUbdIzc SP:FB PPU: 72,126 CYC:14353\n$E474:70 09     BVS $E47F                          A:7F X:55 Y:74 P:NvUbdIzc SP:FB PPU: 78,126 CYC:14355\n$E476:B0 07     BCS $E47F                          A:7F X:55 Y:74 P:NvUbdIzc SP:FB PPU: 84,126 CYC:14357\n$E478:BD 00 06  LDA $0600,X @ 0655 = #$80            A:7F X:55 Y:74 P:NvUbdIzc SP:FB PPU: 90,126 CYC:14359\n$E47B:C9 80     CMP #$80                           A:80 X:55 Y:74 P:NvUbdIzc SP:FB PPU:102,126 CYC:14363\n$E47D:F0 04     BEQ $E483                          A:80 X:55 Y:74 P:nvUbdIZC SP:FB PPU:108,126 CYC:14365\n$E483:A9 00     LDA #$00                           A:80 X:55 Y:74 P:nvUbdIZC SP:FB PPU:117,126 CYC:14368\n$E485:9D 00 06  STA $0600,X @ 0655 = #$80            A:00 X:55 Y:74 P:nvUbdIZC SP:FB PPU:123,126 CYC:14370\n$E488:24 01     BIT $01 = #$FF                       A:00 X:55 Y:74 P:nvUbdIZC SP:FB PPU:138,126 CYC:14375\n$E48A:38        SEC                                A:00 X:55 Y:74 P:E7 SP:FB PPU:147,126 CYC:14378\n$E48B:DE 00 06  DEC $0600,X @ 0655 = #$00            A:00 X:55 Y:74 P:E7 SP:FB PPU:153,126 CYC:14380\n$E48E:F0 0D     BEQ $E49D                          A:00 X:55 Y:74 P:E5 SP:FB PPU:174,126 CYC:14387\n$E490:10 0B     BPL $E49D                          A:00 X:55 Y:74 P:E5 SP:FB PPU:180,126 CYC:14389\n$E492:50 09     BVC $E49D                          A:00 X:55 Y:74 P:E5 SP:FB PPU:186,126 CYC:14391\n$E494:90 07     BCC $E49D                          A:00 X:55 Y:74 P:E5 SP:FB PPU:192,126 CYC:14393\n$E496:BD 00 06  LDA $0600,X @ 0655 = #$FF            A:00 X:55 Y:74 P:E5 SP:FB PPU:198,126 CYC:14395\n$E499:C9 FF     CMP #$FF                           A:FF X:55 Y:74 P:E5 SP:FB PPU:210,126 CYC:14399\n$E49B:F0 04     BEQ $E4A1                          A:FF X:55 Y:74 P:67 SP:FB PPU:216,126 CYC:14401\n$E4A1:A9 80     LDA #$80                           A:FF X:55 Y:74 P:67 SP:FB PPU:225,126 CYC:14404\n$E4A3:9D 00 06  STA $0600,X @ 0655 = #$FF            A:80 X:55 Y:74 P:E5 SP:FB PPU:231,126 CYC:14406\n$E4A6:B8        CLV                                A:80 X:55 Y:74 P:E5 SP:FB PPU:246,126 CYC:14411\n$E4A7:18        CLC                                A:80 X:55 Y:74 P:A5 SP:FB PPU:252,126 CYC:14413\n$E4A8:DE 00 06  DEC $0600,X @ 0655 = #$80            A:80 X:55 Y:74 P:NvUbdIzc SP:FB PPU:258,126 CYC:14415\n$E4AB:F0 0D     BEQ $E4BA                          A:80 X:55 Y:74 P:nvUbdIzc SP:FB PPU:279,126 CYC:14422\n$E4AD:30 0B     BMI $E4BA                          A:80 X:55 Y:74 P:nvUbdIzc SP:FB PPU:285,126 CYC:14424\n$E4AF:70 09     BVS $E4BA                          A:80 X:55 Y:74 P:nvUbdIzc SP:FB PPU:291,126 CYC:14426\n$E4B1:B0 07     BCS $E4BA                          A:80 X:55 Y:74 P:nvUbdIzc SP:FB PPU:297,126 CYC:14428\n$E4B3:BD 00 06  LDA $0600,X @ 0655 = #$7F            A:80 X:55 Y:74 P:nvUbdIzc SP:FB PPU:303,126 CYC:14430\n$E4B6:C9 7F     CMP #$7F                           A:7F X:55 Y:74 P:nvUbdIzc SP:FB PPU:315,126 CYC:14434\n$E4B8:F0 04     BEQ $E4BE                          A:7F X:55 Y:74 P:nvUbdIZC SP:FB PPU:321,126 CYC:14436\n$E4BE:A9 01     LDA #$01                           A:7F X:55 Y:74 P:nvUbdIZC SP:FB PPU:330,126 CYC:14439\n$E4C0:9D 00 06  STA $0600,X @ 0655 = #$7F            A:01 X:55 Y:74 P:25 SP:FB PPU:336,126 CYC:14441\n$E4C3:DE 00 06  DEC $0600,X @ 0655 = #$01            A:01 X:55 Y:74 P:25 SP:FB PPU: 10,127 CYC:14446\n$E4C6:F0 04     BEQ $E4CC                          A:01 X:55 Y:74 P:nvUbdIZC SP:FB PPU: 31,127 CYC:14453\n$E4CC:A9 33     LDA #$33                           A:01 X:55 Y:74 P:nvUbdIZC SP:FB PPU: 40,127 CYC:14456\n$E4CE:8D 78 06  STA $0678 = #$7F                     A:33 X:55 Y:74 P:25 SP:FB PPU: 46,127 CYC:14458\n$E4D1:A9 44     LDA #$44                           A:33 X:55 Y:74 P:25 SP:FB PPU: 58,127 CYC:14462\n$E4D3:A0 78     LDY #$78                           A:44 X:55 Y:74 P:25 SP:FB PPU: 64,127 CYC:14464\n$E4D5:A2 00     LDX #$00                           A:44 X:55 Y:78 P:25 SP:FB PPU: 70,127 CYC:14466\n$E4D7:38        SEC                                A:44 X:00 Y:78 P:nvUbdIZC SP:FB PPU: 76,127 CYC:14468\n$E4D8:24 01     BIT $01 = #$FF                       A:44 X:00 Y:78 P:nvUbdIZC SP:FB PPU: 82,127 CYC:14470\n$E4DA:BE 00 06  LDX $0600,Y @ 0678 = #$33            A:44 X:00 Y:78 P:E5 SP:FB PPU: 91,127 CYC:14473\n$E4DD:90 12     BCC $E4F1                          A:44 X:33 Y:78 P:65 SP:FB PPU:103,127 CYC:14477\n$E4DF:50 10     BVC $E4F1                          A:44 X:33 Y:78 P:65 SP:FB PPU:109,127 CYC:14479\n$E4E1:30 0E     BMI $E4F1                          A:44 X:33 Y:78 P:65 SP:FB PPU:115,127 CYC:14481\n$E4E3:F0 0C     BEQ $E4F1                          A:44 X:33 Y:78 P:65 SP:FB PPU:121,127 CYC:14483\n$E4E5:E0 33     CPX #$33                           A:44 X:33 Y:78 P:65 SP:FB PPU:127,127 CYC:14485\n$E4E7:D0 08     BNE $E4F1                          A:44 X:33 Y:78 P:67 SP:FB PPU:133,127 CYC:14487\n$E4E9:C0 78     CPY #$78                           A:44 X:33 Y:78 P:67 SP:FB PPU:139,127 CYC:14489\n$E4EB:D0 04     BNE $E4F1                          A:44 X:33 Y:78 P:67 SP:FB PPU:145,127 CYC:14491\n$E4ED:C9 44     CMP #$44                           A:44 X:33 Y:78 P:67 SP:FB PPU:151,127 CYC:14493\n$E4EF:F0 04     BEQ $E4F5                          A:44 X:33 Y:78 P:67 SP:FB PPU:157,127 CYC:14495\n$E4F5:A9 97     LDA #$97                           A:44 X:33 Y:78 P:67 SP:FB PPU:166,127 CYC:14498\n$E4F7:8D 7F 06  STA $067F = #$00                     A:97 X:33 Y:78 P:E5 SP:FB PPU:172,127 CYC:14500\n$E4FA:A9 47     LDA #$47                           A:97 X:33 Y:78 P:E5 SP:FB PPU:184,127 CYC:14504\n$E4FC:A0 FF     LDY #$FF                           A:47 X:33 Y:78 P:65 SP:FB PPU:190,127 CYC:14506\n$E4FE:A2 00     LDX #$00                           A:47 X:33 Y:FF P:E5 SP:FB PPU:196,127 CYC:14508\n$E500:18        CLC                                A:47 X:00 Y:FF P:67 SP:FB PPU:202,127 CYC:14510\n$E501:B8        CLV                                A:47 X:00 Y:FF P:nVUbdIZc SP:FB PPU:208,127 CYC:14512\n$E502:BE 80 05  LDX $0580,Y @ 067F = #$97            A:47 X:00 Y:FF P:nvUbdIZc SP:FB PPU:214,127 CYC:14514\n$E505:B0 12     BCS $E519                          A:47 X:97 Y:FF P:NvUbdIzc SP:FB PPU:229,127 CYC:14519\n$E507:70 10     BVS $E519                          A:47 X:97 Y:FF P:NvUbdIzc SP:FB PPU:235,127 CYC:14521\n$E509:10 0E     BPL $E519                          A:47 X:97 Y:FF P:NvUbdIzc SP:FB PPU:241,127 CYC:14523\n$E50B:F0 0C     BEQ $E519                          A:47 X:97 Y:FF P:NvUbdIzc SP:FB PPU:247,127 CYC:14525\n$E50D:E0 97     CPX #$97                           A:47 X:97 Y:FF P:NvUbdIzc SP:FB PPU:253,127 CYC:14527\n$E50F:D0 08     BNE $E519                          A:47 X:97 Y:FF P:nvUbdIZC SP:FB PPU:259,127 CYC:14529\n$E511:C0 FF     CPY #$FF                           A:47 X:97 Y:FF P:nvUbdIZC SP:FB PPU:265,127 CYC:14531\n$E513:D0 04     BNE $E519                          A:47 X:97 Y:FF P:nvUbdIZC SP:FB PPU:271,127 CYC:14533\n$E515:C9 47     CMP #$47                           A:47 X:97 Y:FF P:nvUbdIZC SP:FB PPU:277,127 CYC:14535\n$E517:F0 04     BEQ $E51D                          A:47 X:97 Y:FF P:nvUbdIZC SP:FB PPU:283,127 CYC:14537\n$E51D:60        RTS                                A:47 X:97 Y:FF P:nvUbdIZC SP:FB PPU:292,127 CYC:14540\n$C62F:20 A3 C6  JSR $C6A3                          A:47 X:97 Y:FF P:nvUbdIZC SP:FD PPU:310,127 CYC:14546\n$C6A3:A0 4E     LDY #$4E                           A:47 X:97 Y:FF P:nvUbdIZC SP:FB PPU:328,127 CYC:14552\n$C6A5:A9 FF     LDA #$FF                           A:47 X:97 Y:4E P:25 SP:FB PPU:334,127 CYC:14554\n$C6A7:85 01     STA $01 = #$FF                       A:FF X:97 Y:4E P:A5 SP:FB PPU:340,127 CYC:14556\n$C6A9:20 B0 C6  JSR $C6B0                          A:FF X:97 Y:4E P:A5 SP:FB PPU:  8,128 CYC:14559\n$C6B0:A9 FF     LDA #$FF                           A:FF X:97 Y:4E P:A5 SP:F9 PPU: 26,128 CYC:14565\n$C6B2:48        PHA                                A:FF X:97 Y:4E P:A5 SP:F9 PPU: 32,128 CYC:14567\n$C6B3:A9 AA     LDA #$AA                           A:FF X:97 Y:4E P:A5 SP:F8 PPU: 41,128 CYC:14570\n$C6B5:D0 05     BNE $C6BC                          A:AA X:97 Y:4E P:A5 SP:F8 PPU: 47,128 CYC:14572\n$C6BC:28        PLP                                A:AA X:97 Y:4E P:A5 SP:F8 PPU: 56,128 CYC:14575\n$C6BD:04 A9    *NOP $A9 = #$00                       A:AA X:97 Y:4E P:EF SP:F9 PPU: 68,128 CYC:14579\n$C6BF:44 A9    *NOP $A9 = #$00                       A:AA X:97 Y:4E P:EF SP:F9 PPU: 77,128 CYC:14582\n$C6C1:64 A9    *NOP $A9 = #$00                       A:AA X:97 Y:4E P:EF SP:F9 PPU: 86,128 CYC:14585\n$C6C3:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F9 PPU: 95,128 CYC:14588\n$C6C4:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F9 PPU:101,128 CYC:14590\n$C6C5:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F9 PPU:107,128 CYC:14592\n$C6C6:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F9 PPU:113,128 CYC:14594\n$C6C7:08        PHP                                A:AA X:97 Y:4E P:EF SP:F9 PPU:119,128 CYC:14596\n$C6C8:48        PHA                                A:AA X:97 Y:4E P:EF SP:F8 PPU:128,128 CYC:14599\n$C6C9:0C A9 A9 *NOP $A9A9 = #$A9                     A:AA X:97 Y:4E P:EF SP:F7 PPU:137,128 CYC:14602\n$C6CC:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F7 PPU:149,128 CYC:14606\n$C6CD:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F7 PPU:155,128 CYC:14608\n$C6CE:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F7 PPU:161,128 CYC:14610\n$C6CF:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F7 PPU:167,128 CYC:14612\n$C6D0:08        PHP                                A:AA X:97 Y:4E P:EF SP:F7 PPU:173,128 CYC:14614\n$C6D1:48        PHA                                A:AA X:97 Y:4E P:EF SP:F6 PPU:182,128 CYC:14617\n$C6D2:14 A9    *NOP $A9,X @ 40 = #$00                A:AA X:97 Y:4E P:EF SP:F5 PPU:191,128 CYC:14620\n$C6D4:34 A9    *NOP $A9,X @ 40 = #$00                A:AA X:97 Y:4E P:EF SP:F5 PPU:203,128 CYC:14624\n$C6D6:54 A9    *NOP $A9,X @ 40 = #$00                A:AA X:97 Y:4E P:EF SP:F5 PPU:215,128 CYC:14628\n$C6D8:74 A9    *NOP $A9,X @ 40 = #$00                A:AA X:97 Y:4E P:EF SP:F5 PPU:227,128 CYC:14632\n$C6DA:D4 A9    *NOP $A9,X @ 40 = #$00                A:AA X:97 Y:4E P:EF SP:F5 PPU:239,128 CYC:14636\n$C6DC:F4 A9    *NOP $A9,X @ 40 = #$00                A:AA X:97 Y:4E P:EF SP:F5 PPU:251,128 CYC:14640\n$C6DE:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F5 PPU:263,128 CYC:14644\n$C6DF:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F5 PPU:269,128 CYC:14646\n$C6E0:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F5 PPU:275,128 CYC:14648\n$C6E1:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F5 PPU:281,128 CYC:14650\n$C6E2:08        PHP                                A:AA X:97 Y:4E P:EF SP:F5 PPU:287,128 CYC:14652\n$C6E3:48        PHA                                A:AA X:97 Y:4E P:EF SP:F4 PPU:296,128 CYC:14655\n$C6E4:1A       *NOP                                A:AA X:97 Y:4E P:EF SP:F3 PPU:305,128 CYC:14658\n$C6E5:3A       *NOP                                A:AA X:97 Y:4E P:EF SP:F3 PPU:311,128 CYC:14660\n$C6E6:5A       *NOP                                A:AA X:97 Y:4E P:EF SP:F3 PPU:317,128 CYC:14662\n$C6E7:7A       *NOP                                A:AA X:97 Y:4E P:EF SP:F3 PPU:323,128 CYC:14664\n$C6E8:DA       *NOP                                A:AA X:97 Y:4E P:EF SP:F3 PPU:329,128 CYC:14666\n$C6E9:FA       *NOP                                A:AA X:97 Y:4E P:EF SP:F3 PPU:335,128 CYC:14668\n$C6EA:80 89    *NOP #$89                           A:AA X:97 Y:4E P:EF SP:F3 PPU:  0,129 CYC:14670\n$C6EC:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F3 PPU:  6,129 CYC:14672\n$C6ED:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F3 PPU: 12,129 CYC:14674\n$C6EE:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F3 PPU: 18,129 CYC:14676\n$C6EF:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F3 PPU: 24,129 CYC:14678\n$C6F0:08        PHP                                A:AA X:97 Y:4E P:EF SP:F3 PPU: 30,129 CYC:14680\n$C6F1:48        PHA                                A:AA X:97 Y:4E P:EF SP:F2 PPU: 39,129 CYC:14683\n$C6F2:1C A9 A9 *NOP $A9A9,X @ AA40 = #$00            A:AA X:97 Y:4E P:EF SP:F1 PPU: 48,129 CYC:14686\n$C6F5:3C A9 A9 *NOP $A9A9,X @ AA40 = #$00            A:AA X:97 Y:4E P:EF SP:F1 PPU: 63,129 CYC:14691\n$C6F8:5C A9 A9 *NOP $A9A9,X @ AA40 = #$00            A:AA X:97 Y:4E P:EF SP:F1 PPU: 78,129 CYC:14696\n$C6FB:7C A9 A9 *NOP $A9A9,X @ AA40 = #$00            A:AA X:97 Y:4E P:EF SP:F1 PPU: 93,129 CYC:14701\n$C6FE:DC A9 A9 *NOP $A9A9,X @ AA40 = #$00            A:AA X:97 Y:4E P:EF SP:F1 PPU:108,129 CYC:14706\n$C701:FC A9 A9 *NOP $A9A9,X @ AA40 = #$00            A:AA X:97 Y:4E P:EF SP:F1 PPU:123,129 CYC:14711\n$C704:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F1 PPU:138,129 CYC:14716\n$C705:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F1 PPU:144,129 CYC:14718\n$C706:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F1 PPU:150,129 CYC:14720\n$C707:EA        NOP                                A:AA X:97 Y:4E P:EF SP:F1 PPU:156,129 CYC:14722\n$C708:08        PHP                                A:AA X:97 Y:4E P:EF SP:F1 PPU:162,129 CYC:14724\n$C709:48        PHA                                A:AA X:97 Y:4E P:EF SP:F0 PPU:171,129 CYC:14727\n$C70A:A2 05     LDX #$05                           A:AA X:97 Y:4E P:EF SP:EF PPU:180,129 CYC:14730\n$C70C:68        PLA                                A:AA X:05 Y:4E P:6D SP:EF PPU:186,129 CYC:14732\n$C70D:C9 55     CMP #$55                           A:AA X:05 Y:4E P:ED SP:F0 PPU:198,129 CYC:14736\n$C70F:F0 0A     BEQ $C71B                          A:AA X:05 Y:4E P:6D SP:F0 PPU:204,129 CYC:14738\n$C711:C9 AA     CMP #$AA                           A:AA X:05 Y:4E P:6D SP:F0 PPU:210,129 CYC:14740\n$C713:F0 06     BEQ $C71B                          A:AA X:05 Y:4E P:6F SP:F0 PPU:216,129 CYC:14742\n$C71B:68        PLA                                A:AA X:05 Y:4E P:6F SP:F0 PPU:225,129 CYC:14745\n$C71C:29 CB     AND #$CB                           A:FF X:05 Y:4E P:ED SP:F1 PPU:237,129 CYC:14749\n$C71E:C9 00     CMP #$00                           A:CB X:05 Y:4E P:ED SP:F1 PPU:243,129 CYC:14751\n$C720:F0 06     BEQ $C728                          A:CB X:05 Y:4E P:ED SP:F1 PPU:249,129 CYC:14753\n$C722:C9 CB     CMP #$CB                           A:CB X:05 Y:4E P:ED SP:F1 PPU:255,129 CYC:14755\n$C724:F0 02     BEQ $C728                          A:CB X:05 Y:4E P:6F SP:F1 PPU:261,129 CYC:14757\n$C728:C8        INY                                A:CB X:05 Y:4E P:6F SP:F1 PPU:270,129 CYC:14760\n$C729:CA        DEX                                A:CB X:05 Y:4F P:6D SP:F1 PPU:276,129 CYC:14762\n$C72A:D0 E0     BNE $C70C                          A:CB X:04 Y:4F P:6D SP:F1 PPU:282,129 CYC:14764\n$C70C:68        PLA                                A:CB X:04 Y:4F P:6D SP:F1 PPU:291,129 CYC:14767\n$C70D:C9 55     CMP #$55                           A:AA X:04 Y:4F P:ED SP:F2 PPU:303,129 CYC:14771\n$C70F:F0 0A     BEQ $C71B                          A:AA X:04 Y:4F P:6D SP:F2 PPU:309,129 CYC:14773\n$C711:C9 AA     CMP #$AA                           A:AA X:04 Y:4F P:6D SP:F2 PPU:315,129 CYC:14775\n$C713:F0 06     BEQ $C71B                          A:AA X:04 Y:4F P:6F SP:F2 PPU:321,129 CYC:14777\n$C71B:68        PLA                                A:AA X:04 Y:4F P:6F SP:F2 PPU:330,129 CYC:14780\n$C71C:29 CB     AND #$CB                           A:FF X:04 Y:4F P:ED SP:F3 PPU:  1,130 CYC:14784\n$C71E:C9 00     CMP #$00                           A:CB X:04 Y:4F P:ED SP:F3 PPU:  7,130 CYC:14786\n$C720:F0 06     BEQ $C728                          A:CB X:04 Y:4F P:ED SP:F3 PPU: 13,130 CYC:14788\n$C722:C9 CB     CMP #$CB                           A:CB X:04 Y:4F P:ED SP:F3 PPU: 19,130 CYC:14790\n$C724:F0 02     BEQ $C728                          A:CB X:04 Y:4F P:6F SP:F3 PPU: 25,130 CYC:14792\n$C728:C8        INY                                A:CB X:04 Y:4F P:6F SP:F3 PPU: 34,130 CYC:14795\n$C729:CA        DEX                                A:CB X:04 Y:50 P:6D SP:F3 PPU: 40,130 CYC:14797\n$C72A:D0 E0     BNE $C70C                          A:CB X:03 Y:50 P:6D SP:F3 PPU: 46,130 CYC:14799\n$C70C:68        PLA                                A:CB X:03 Y:50 P:6D SP:F3 PPU: 55,130 CYC:14802\n$C70D:C9 55     CMP #$55                           A:AA X:03 Y:50 P:ED SP:F4 PPU: 67,130 CYC:14806\n$C70F:F0 0A     BEQ $C71B                          A:AA X:03 Y:50 P:6D SP:F4 PPU: 73,130 CYC:14808\n$C711:C9 AA     CMP #$AA                           A:AA X:03 Y:50 P:6D SP:F4 PPU: 79,130 CYC:14810\n$C713:F0 06     BEQ $C71B                          A:AA X:03 Y:50 P:6F SP:F4 PPU: 85,130 CYC:14812\n$C71B:68        PLA                                A:AA X:03 Y:50 P:6F SP:F4 PPU: 94,130 CYC:14815\n$C71C:29 CB     AND #$CB                           A:FF X:03 Y:50 P:ED SP:F5 PPU:106,130 CYC:14819\n$C71E:C9 00     CMP #$00                           A:CB X:03 Y:50 P:ED SP:F5 PPU:112,130 CYC:14821\n$C720:F0 06     BEQ $C728                          A:CB X:03 Y:50 P:ED SP:F5 PPU:118,130 CYC:14823\n$C722:C9 CB     CMP #$CB                           A:CB X:03 Y:50 P:ED SP:F5 PPU:124,130 CYC:14825\n$C724:F0 02     BEQ $C728                          A:CB X:03 Y:50 P:6F SP:F5 PPU:130,130 CYC:14827\n$C728:C8        INY                                A:CB X:03 Y:50 P:6F SP:F5 PPU:139,130 CYC:14830\n$C729:CA        DEX                                A:CB X:03 Y:51 P:6D SP:F5 PPU:145,130 CYC:14832\n$C72A:D0 E0     BNE $C70C                          A:CB X:02 Y:51 P:6D SP:F5 PPU:151,130 CYC:14834\n$C70C:68        PLA                                A:CB X:02 Y:51 P:6D SP:F5 PPU:160,130 CYC:14837\n$C70D:C9 55     CMP #$55                           A:AA X:02 Y:51 P:ED SP:F6 PPU:172,130 CYC:14841\n$C70F:F0 0A     BEQ $C71B                          A:AA X:02 Y:51 P:6D SP:F6 PPU:178,130 CYC:14843\n$C711:C9 AA     CMP #$AA                           A:AA X:02 Y:51 P:6D SP:F6 PPU:184,130 CYC:14845\n$C713:F0 06     BEQ $C71B                          A:AA X:02 Y:51 P:6F SP:F6 PPU:190,130 CYC:14847\n$C71B:68        PLA                                A:AA X:02 Y:51 P:6F SP:F6 PPU:199,130 CYC:14850\n$C71C:29 CB     AND #$CB                           A:FF X:02 Y:51 P:ED SP:F7 PPU:211,130 CYC:14854\n$C71E:C9 00     CMP #$00                           A:CB X:02 Y:51 P:ED SP:F7 PPU:217,130 CYC:14856\n$C720:F0 06     BEQ $C728                          A:CB X:02 Y:51 P:ED SP:F7 PPU:223,130 CYC:14858\n$C722:C9 CB     CMP #$CB                           A:CB X:02 Y:51 P:ED SP:F7 PPU:229,130 CYC:14860\n$C724:F0 02     BEQ $C728                          A:CB X:02 Y:51 P:6F SP:F7 PPU:235,130 CYC:14862\n$C728:C8        INY                                A:CB X:02 Y:51 P:6F SP:F7 PPU:244,130 CYC:14865\n$C729:CA        DEX                                A:CB X:02 Y:52 P:6D SP:F7 PPU:250,130 CYC:14867\n$C72A:D0 E0     BNE $C70C                          A:CB X:01 Y:52 P:6D SP:F7 PPU:256,130 CYC:14869\n$C70C:68        PLA                                A:CB X:01 Y:52 P:6D SP:F7 PPU:265,130 CYC:14872\n$C70D:C9 55     CMP #$55                           A:AA X:01 Y:52 P:ED SP:F8 PPU:277,130 CYC:14876\n$C70F:F0 0A     BEQ $C71B                          A:AA X:01 Y:52 P:6D SP:F8 PPU:283,130 CYC:14878\n$C711:C9 AA     CMP #$AA                           A:AA X:01 Y:52 P:6D SP:F8 PPU:289,130 CYC:14880\n$C713:F0 06     BEQ $C71B                          A:AA X:01 Y:52 P:6F SP:F8 PPU:295,130 CYC:14882\n$C71B:68        PLA                                A:AA X:01 Y:52 P:6F SP:F8 PPU:304,130 CYC:14885\n$C71C:29 CB     AND #$CB                           A:FF X:01 Y:52 P:ED SP:F9 PPU:316,130 CYC:14889\n$C71E:C9 00     CMP #$00                           A:CB X:01 Y:52 P:ED SP:F9 PPU:322,130 CYC:14891\n$C720:F0 06     BEQ $C728                          A:CB X:01 Y:52 P:ED SP:F9 PPU:328,130 CYC:14893\n$C722:C9 CB     CMP #$CB                           A:CB X:01 Y:52 P:ED SP:F9 PPU:334,130 CYC:14895\n$C724:F0 02     BEQ $C728                          A:CB X:01 Y:52 P:6F SP:F9 PPU:340,130 CYC:14897\n$C728:C8        INY                                A:CB X:01 Y:52 P:6F SP:F9 PPU:  8,131 CYC:14900\n$C729:CA        DEX                                A:CB X:01 Y:53 P:6D SP:F9 PPU: 14,131 CYC:14902\n$C72A:D0 E0     BNE $C70C                          A:CB X:00 Y:53 P:6F SP:F9 PPU: 20,131 CYC:14904\n$C72C:60        RTS                                A:CB X:00 Y:53 P:6F SP:F9 PPU: 26,131 CYC:14906\n$C6AC:20 B7 C6  JSR $C6B7                          A:CB X:00 Y:53 P:6F SP:FB PPU: 44,131 CYC:14912\n$C6B7:A9 34     LDA #$34                           A:CB X:00 Y:53 P:6F SP:F9 PPU: 62,131 CYC:14918\n$C6B9:48        PHA                                A:34 X:00 Y:53 P:6D SP:F9 PPU: 68,131 CYC:14920\n$C6BA:A9 55     LDA #$55                           A:34 X:00 Y:53 P:6D SP:F8 PPU: 77,131 CYC:14923\n$C6BC:28        PLP                                A:55 X:00 Y:53 P:6D SP:F8 PPU: 83,131 CYC:14925\n$C6BD:04 A9    *NOP $A9 = #$00                       A:55 X:00 Y:53 P:nvUbdIzc SP:F9 PPU: 95,131 CYC:14929\n$C6BF:44 A9    *NOP $A9 = #$00                       A:55 X:00 Y:53 P:nvUbdIzc SP:F9 PPU:104,131 CYC:14932\n$C6C1:64 A9    *NOP $A9 = #$00                       A:55 X:00 Y:53 P:nvUbdIzc SP:F9 PPU:113,131 CYC:14935\n$C6C3:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F9 PPU:122,131 CYC:14938\n$C6C4:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F9 PPU:128,131 CYC:14940\n$C6C5:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F9 PPU:134,131 CYC:14942\n$C6C6:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F9 PPU:140,131 CYC:14944\n$C6C7:08        PHP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F9 PPU:146,131 CYC:14946\n$C6C8:48        PHA                                A:55 X:00 Y:53 P:nvUbdIzc SP:F8 PPU:155,131 CYC:14949\n$C6C9:0C A9 A9 *NOP $A9A9 = #$A9                     A:55 X:00 Y:53 P:nvUbdIzc SP:F7 PPU:164,131 CYC:14952\n$C6CC:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F7 PPU:176,131 CYC:14956\n$C6CD:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F7 PPU:182,131 CYC:14958\n$C6CE:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F7 PPU:188,131 CYC:14960\n$C6CF:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F7 PPU:194,131 CYC:14962\n$C6D0:08        PHP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F7 PPU:200,131 CYC:14964\n$C6D1:48        PHA                                A:55 X:00 Y:53 P:nvUbdIzc SP:F6 PPU:209,131 CYC:14967\n$C6D2:14 A9    *NOP $A9,X @ A9 = #$00                A:55 X:00 Y:53 P:nvUbdIzc SP:F5 PPU:218,131 CYC:14970\n$C6D4:34 A9    *NOP $A9,X @ A9 = #$00                A:55 X:00 Y:53 P:nvUbdIzc SP:F5 PPU:230,131 CYC:14974\n$C6D6:54 A9    *NOP $A9,X @ A9 = #$00                A:55 X:00 Y:53 P:nvUbdIzc SP:F5 PPU:242,131 CYC:14978\n$C6D8:74 A9    *NOP $A9,X @ A9 = #$00                A:55 X:00 Y:53 P:nvUbdIzc SP:F5 PPU:254,131 CYC:14982\n$C6DA:D4 A9    *NOP $A9,X @ A9 = #$00                A:55 X:00 Y:53 P:nvUbdIzc SP:F5 PPU:266,131 CYC:14986\n$C6DC:F4 A9    *NOP $A9,X @ A9 = #$00                A:55 X:00 Y:53 P:nvUbdIzc SP:F5 PPU:278,131 CYC:14990\n$C6DE:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F5 PPU:290,131 CYC:14994\n$C6DF:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F5 PPU:296,131 CYC:14996\n$C6E0:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F5 PPU:302,131 CYC:14998\n$C6E1:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F5 PPU:308,131 CYC:15000\n$C6E2:08        PHP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F5 PPU:314,131 CYC:15002\n$C6E3:48        PHA                                A:55 X:00 Y:53 P:nvUbdIzc SP:F4 PPU:323,131 CYC:15005\n$C6E4:1A       *NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F3 PPU:332,131 CYC:15008\n$C6E5:3A       *NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F3 PPU:338,131 CYC:15010\n$C6E6:5A       *NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F3 PPU:  3,132 CYC:15012\n$C6E7:7A       *NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F3 PPU:  9,132 CYC:15014\n$C6E8:DA       *NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F3 PPU: 15,132 CYC:15016\n$C6E9:FA       *NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F3 PPU: 21,132 CYC:15018\n$C6EA:80 89    *NOP #$89                           A:55 X:00 Y:53 P:nvUbdIzc SP:F3 PPU: 27,132 CYC:15020\n$C6EC:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F3 PPU: 33,132 CYC:15022\n$C6ED:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F3 PPU: 39,132 CYC:15024\n$C6EE:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F3 PPU: 45,132 CYC:15026\n$C6EF:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F3 PPU: 51,132 CYC:15028\n$C6F0:08        PHP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F3 PPU: 57,132 CYC:15030\n$C6F1:48        PHA                                A:55 X:00 Y:53 P:nvUbdIzc SP:F2 PPU: 66,132 CYC:15033\n$C6F2:1C A9 A9 *NOP $A9A9,X @ A9A9 = #$A9            A:55 X:00 Y:53 P:nvUbdIzc SP:F1 PPU: 75,132 CYC:15036\n$C6F5:3C A9 A9 *NOP $A9A9,X @ A9A9 = #$A9            A:55 X:00 Y:53 P:nvUbdIzc SP:F1 PPU: 87,132 CYC:15040\n$C6F8:5C A9 A9 *NOP $A9A9,X @ A9A9 = #$A9            A:55 X:00 Y:53 P:nvUbdIzc SP:F1 PPU: 99,132 CYC:15044\n$C6FB:7C A9 A9 *NOP $A9A9,X @ A9A9 = #$A9            A:55 X:00 Y:53 P:nvUbdIzc SP:F1 PPU:111,132 CYC:15048\n$C6FE:DC A9 A9 *NOP $A9A9,X @ A9A9 = #$A9            A:55 X:00 Y:53 P:nvUbdIzc SP:F1 PPU:123,132 CYC:15052\n$C701:FC A9 A9 *NOP $A9A9,X @ A9A9 = #$A9            A:55 X:00 Y:53 P:nvUbdIzc SP:F1 PPU:135,132 CYC:15056\n$C704:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F1 PPU:147,132 CYC:15060\n$C705:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F1 PPU:153,132 CYC:15062\n$C706:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F1 PPU:159,132 CYC:15064\n$C707:EA        NOP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F1 PPU:165,132 CYC:15066\n$C708:08        PHP                                A:55 X:00 Y:53 P:nvUbdIzc SP:F1 PPU:171,132 CYC:15068\n$C709:48        PHA                                A:55 X:00 Y:53 P:nvUbdIzc SP:F0 PPU:180,132 CYC:15071\n$C70A:A2 05     LDX #$05                           A:55 X:00 Y:53 P:nvUbdIzc SP:EF PPU:189,132 CYC:15074\n$C70C:68        PLA                                A:55 X:05 Y:53 P:nvUbdIzc SP:EF PPU:195,132 CYC:15076\n$C70D:C9 55     CMP #$55                           A:55 X:05 Y:53 P:nvUbdIzc SP:F0 PPU:207,132 CYC:15080\n$C70F:F0 0A     BEQ $C71B                          A:55 X:05 Y:53 P:nvUbdIZC SP:F0 PPU:213,132 CYC:15082\n$C71B:68        PLA                                A:55 X:05 Y:53 P:nvUbdIZC SP:F0 PPU:222,132 CYC:15085\n$C71C:29 CB     AND #$CB                           A:34 X:05 Y:53 P:25 SP:F1 PPU:234,132 CYC:15089\n$C71E:C9 00     CMP #$00                           A:00 X:05 Y:53 P:nvUbdIZC SP:F1 PPU:240,132 CYC:15091\n$C720:F0 06     BEQ $C728                          A:00 X:05 Y:53 P:nvUbdIZC SP:F1 PPU:246,132 CYC:15093\n$C728:C8        INY                                A:00 X:05 Y:53 P:nvUbdIZC SP:F1 PPU:255,132 CYC:15096\n$C729:CA        DEX                                A:00 X:05 Y:54 P:25 SP:F1 PPU:261,132 CYC:15098\n$C72A:D0 E0     BNE $C70C                          A:00 X:04 Y:54 P:25 SP:F1 PPU:267,132 CYC:15100\n$C70C:68        PLA                                A:00 X:04 Y:54 P:25 SP:F1 PPU:276,132 CYC:15103\n$C70D:C9 55     CMP #$55                           A:55 X:04 Y:54 P:25 SP:F2 PPU:288,132 CYC:15107\n$C70F:F0 0A     BEQ $C71B                          A:55 X:04 Y:54 P:nvUbdIZC SP:F2 PPU:294,132 CYC:15109\n$C71B:68        PLA                                A:55 X:04 Y:54 P:nvUbdIZC SP:F2 PPU:303,132 CYC:15112\n$C71C:29 CB     AND #$CB                           A:34 X:04 Y:54 P:25 SP:F3 PPU:315,132 CYC:15116\n$C71E:C9 00     CMP #$00                           A:00 X:04 Y:54 P:nvUbdIZC SP:F3 PPU:321,132 CYC:15118\n$C720:F0 06     BEQ $C728                          A:00 X:04 Y:54 P:nvUbdIZC SP:F3 PPU:327,132 CYC:15120\n$C728:C8        INY                                A:00 X:04 Y:54 P:nvUbdIZC SP:F3 PPU:336,132 CYC:15123\n$C729:CA        DEX                                A:00 X:04 Y:55 P:25 SP:F3 PPU:  1,133 CYC:15125\n$C72A:D0 E0     BNE $C70C                          A:00 X:03 Y:55 P:25 SP:F3 PPU:  7,133 CYC:15127\n$C70C:68        PLA                                A:00 X:03 Y:55 P:25 SP:F3 PPU: 16,133 CYC:15130\n$C70D:C9 55     CMP #$55                           A:55 X:03 Y:55 P:25 SP:F4 PPU: 28,133 CYC:15134\n$C70F:F0 0A     BEQ $C71B                          A:55 X:03 Y:55 P:nvUbdIZC SP:F4 PPU: 34,133 CYC:15136\n$C71B:68        PLA                                A:55 X:03 Y:55 P:nvUbdIZC SP:F4 PPU: 43,133 CYC:15139\n$C71C:29 CB     AND #$CB                           A:34 X:03 Y:55 P:25 SP:F5 PPU: 55,133 CYC:15143\n$C71E:C9 00     CMP #$00                           A:00 X:03 Y:55 P:nvUbdIZC SP:F5 PPU: 61,133 CYC:15145\n$C720:F0 06     BEQ $C728                          A:00 X:03 Y:55 P:nvUbdIZC SP:F5 PPU: 67,133 CYC:15147\n$C728:C8        INY                                A:00 X:03 Y:55 P:nvUbdIZC SP:F5 PPU: 76,133 CYC:15150\n$C729:CA        DEX                                A:00 X:03 Y:56 P:25 SP:F5 PPU: 82,133 CYC:15152\n$C72A:D0 E0     BNE $C70C                          A:00 X:02 Y:56 P:25 SP:F5 PPU: 88,133 CYC:15154\n$C70C:68        PLA                                A:00 X:02 Y:56 P:25 SP:F5 PPU: 97,133 CYC:15157\n$C70D:C9 55     CMP #$55                           A:55 X:02 Y:56 P:25 SP:F6 PPU:109,133 CYC:15161\n$C70F:F0 0A     BEQ $C71B                          A:55 X:02 Y:56 P:nvUbdIZC SP:F6 PPU:115,133 CYC:15163\n$C71B:68        PLA                                A:55 X:02 Y:56 P:nvUbdIZC SP:F6 PPU:124,133 CYC:15166\n$C71C:29 CB     AND #$CB                           A:34 X:02 Y:56 P:25 SP:F7 PPU:136,133 CYC:15170\n$C71E:C9 00     CMP #$00                           A:00 X:02 Y:56 P:nvUbdIZC SP:F7 PPU:142,133 CYC:15172\n$C720:F0 06     BEQ $C728                          A:00 X:02 Y:56 P:nvUbdIZC SP:F7 PPU:148,133 CYC:15174\n$C728:C8        INY                                A:00 X:02 Y:56 P:nvUbdIZC SP:F7 PPU:157,133 CYC:15177\n$C729:CA        DEX                                A:00 X:02 Y:57 P:25 SP:F7 PPU:163,133 CYC:15179\n$C72A:D0 E0     BNE $C70C                          A:00 X:01 Y:57 P:25 SP:F7 PPU:169,133 CYC:15181\n$C70C:68        PLA                                A:00 X:01 Y:57 P:25 SP:F7 PPU:178,133 CYC:15184\n$C70D:C9 55     CMP #$55                           A:55 X:01 Y:57 P:25 SP:F8 PPU:190,133 CYC:15188\n$C70F:F0 0A     BEQ $C71B                          A:55 X:01 Y:57 P:nvUbdIZC SP:F8 PPU:196,133 CYC:15190\n$C71B:68        PLA                                A:55 X:01 Y:57 P:nvUbdIZC SP:F8 PPU:205,133 CYC:15193\n$C71C:29 CB     AND #$CB                           A:34 X:01 Y:57 P:25 SP:F9 PPU:217,133 CYC:15197\n$C71E:C9 00     CMP #$00                           A:00 X:01 Y:57 P:nvUbdIZC SP:F9 PPU:223,133 CYC:15199\n$C720:F0 06     BEQ $C728                          A:00 X:01 Y:57 P:nvUbdIZC SP:F9 PPU:229,133 CYC:15201\n$C728:C8        INY                                A:00 X:01 Y:57 P:nvUbdIZC SP:F9 PPU:238,133 CYC:15204\n$C729:CA        DEX                                A:00 X:01 Y:58 P:25 SP:F9 PPU:244,133 CYC:15206\n$C72A:D0 E0     BNE $C70C                          A:00 X:00 Y:58 P:nvUbdIZC SP:F9 PPU:250,133 CYC:15208\n$C72C:60        RTS                                A:00 X:00 Y:58 P:nvUbdIZC SP:F9 PPU:256,133 CYC:15210\n$C6AF:60        RTS                                A:00 X:00 Y:58 P:nvUbdIZC SP:FB PPU:274,133 CYC:15216\n$C632:20 1E E5  JSR $E51E                          A:00 X:00 Y:58 P:nvUbdIZC SP:FD PPU:292,133 CYC:15222\n$E51E:A9 55     LDA #$55                           A:00 X:00 Y:58 P:nvUbdIZC SP:FB PPU:310,133 CYC:15228\n$E520:8D 80 05  STA $0580 = #$00                     A:55 X:00 Y:58 P:25 SP:FB PPU:316,133 CYC:15230\n$E523:A9 AA     LDA #$AA                           A:55 X:00 Y:58 P:25 SP:FB PPU:328,133 CYC:15234\n$E525:8D 32 04  STA $0432 = #$00                     A:AA X:00 Y:58 P:A5 SP:FB PPU:334,133 CYC:15236\n$E528:A9 80     LDA #$80                           A:AA X:00 Y:58 P:A5 SP:FB PPU:  5,134 CYC:15240\n$E52A:85 43     STA $43 = #$00                       A:80 X:00 Y:58 P:A5 SP:FB PPU: 11,134 CYC:15242\n$E52C:A9 05     LDA #$05                           A:80 X:00 Y:58 P:A5 SP:FB PPU: 20,134 CYC:15245\n$E52E:85 44     STA $44 = #$00                       A:05 X:00 Y:58 P:25 SP:FB PPU: 26,134 CYC:15247\n$E530:A9 32     LDA #$32                           A:05 X:00 Y:58 P:25 SP:FB PPU: 35,134 CYC:15250\n$E532:85 45     STA $45 = #$00                       A:32 X:00 Y:58 P:25 SP:FB PPU: 41,134 CYC:15252\n$E534:A9 04     LDA #$04                           A:32 X:00 Y:58 P:25 SP:FB PPU: 50,134 CYC:15255\n$E536:85 46     STA $46 = #$00                       A:04 X:00 Y:58 P:25 SP:FB PPU: 56,134 CYC:15257\n$E538:A2 03     LDX #$03                           A:04 X:00 Y:58 P:25 SP:FB PPU: 65,134 CYC:15260\n$E53A:A0 77     LDY #$77                           A:04 X:03 Y:58 P:25 SP:FB PPU: 71,134 CYC:15262\n$E53C:A9 FF     LDA #$FF                           A:04 X:03 Y:77 P:25 SP:FB PPU: 77,134 CYC:15264\n$E53E:85 01     STA $01 = #$FF                       A:FF X:03 Y:77 P:A5 SP:FB PPU: 83,134 CYC:15266\n$E540:24 01     BIT $01 = #$FF                       A:FF X:03 Y:77 P:A5 SP:FB PPU: 92,134 CYC:15269\n$E542:38        SEC                                A:FF X:03 Y:77 P:E5 SP:FB PPU:101,134 CYC:15272\n$E543:A9 00     LDA #$00                           A:FF X:03 Y:77 P:E5 SP:FB PPU:107,134 CYC:15274\n$E545:A3 40    *LAX ($40,X) @ 43 = #$0580 = 55       A:00 X:03 Y:77 P:67 SP:FB PPU:113,134 CYC:15276\n$E547:EA        NOP                                A:55 X:55 Y:77 P:65 SP:FB PPU:131,134 CYC:15282\n$E548:EA        NOP                                A:55 X:55 Y:77 P:65 SP:FB PPU:137,134 CYC:15284\n$E549:EA        NOP                                A:55 X:55 Y:77 P:65 SP:FB PPU:143,134 CYC:15286\n$E54A:EA        NOP                                A:55 X:55 Y:77 P:65 SP:FB PPU:149,134 CYC:15288\n$E54B:F0 12     BEQ $E55F                          A:55 X:55 Y:77 P:65 SP:FB PPU:155,134 CYC:15290\n$E54D:30 10     BMI $E55F                          A:55 X:55 Y:77 P:65 SP:FB PPU:161,134 CYC:15292\n$E54F:50 0E     BVC $E55F                          A:55 X:55 Y:77 P:65 SP:FB PPU:167,134 CYC:15294\n$E551:90 0C     BCC $E55F                          A:55 X:55 Y:77 P:65 SP:FB PPU:173,134 CYC:15296\n$E553:C9 55     CMP #$55                           A:55 X:55 Y:77 P:65 SP:FB PPU:179,134 CYC:15298\n$E555:D0 08     BNE $E55F                          A:55 X:55 Y:77 P:67 SP:FB PPU:185,134 CYC:15300\n$E557:E0 55     CPX #$55                           A:55 X:55 Y:77 P:67 SP:FB PPU:191,134 CYC:15302\n$E559:D0 04     BNE $E55F                          A:55 X:55 Y:77 P:67 SP:FB PPU:197,134 CYC:15304\n$E55B:C0 77     CPY #$77                           A:55 X:55 Y:77 P:67 SP:FB PPU:203,134 CYC:15306\n$E55D:F0 04     BEQ $E563                          A:55 X:55 Y:77 P:67 SP:FB PPU:209,134 CYC:15308\n$E563:A2 05     LDX #$05                           A:55 X:55 Y:77 P:67 SP:FB PPU:218,134 CYC:15311\n$E565:A0 33     LDY #$33                           A:55 X:05 Y:77 P:65 SP:FB PPU:224,134 CYC:15313\n$E567:B8        CLV                                A:55 X:05 Y:33 P:65 SP:FB PPU:230,134 CYC:15315\n$E568:18        CLC                                A:55 X:05 Y:33 P:25 SP:FB PPU:236,134 CYC:15317\n$E569:A9 00     LDA #$00                           A:55 X:05 Y:33 P:nvUbdIzc SP:FB PPU:242,134 CYC:15319\n$E56B:A3 40    *LAX ($40,X) @ 45 = #$0432 = AA       A:00 X:05 Y:33 P:nvUbdIZc SP:FB PPU:248,134 CYC:15321\n$E56D:EA        NOP                                A:AA X:AA Y:33 P:NvUbdIzc SP:FB PPU:266,134 CYC:15327\n$E56E:EA        NOP                                A:AA X:AA Y:33 P:NvUbdIzc SP:FB PPU:272,134 CYC:15329\n$E56F:EA        NOP                                A:AA X:AA Y:33 P:NvUbdIzc SP:FB PPU:278,134 CYC:15331\n$E570:EA        NOP                                A:AA X:AA Y:33 P:NvUbdIzc SP:FB PPU:284,134 CYC:15333\n$E571:F0 12     BEQ $E585                          A:AA X:AA Y:33 P:NvUbdIzc SP:FB PPU:290,134 CYC:15335\n$E573:10 10     BPL $E585                          A:AA X:AA Y:33 P:NvUbdIzc SP:FB PPU:296,134 CYC:15337\n$E575:70 0E     BVS $E585                          A:AA X:AA Y:33 P:NvUbdIzc SP:FB PPU:302,134 CYC:15339\n$E577:B0 0C     BCS $E585                          A:AA X:AA Y:33 P:NvUbdIzc SP:FB PPU:308,134 CYC:15341\n$E579:C9 AA     CMP #$AA                           A:AA X:AA Y:33 P:NvUbdIzc SP:FB PPU:314,134 CYC:15343\n$E57B:D0 08     BNE $E585                          A:AA X:AA Y:33 P:nvUbdIZC SP:FB PPU:320,134 CYC:15345\n$E57D:E0 AA     CPX #$AA                           A:AA X:AA Y:33 P:nvUbdIZC SP:FB PPU:326,134 CYC:15347\n$E57F:D0 04     BNE $E585                          A:AA X:AA Y:33 P:nvUbdIZC SP:FB PPU:332,134 CYC:15349\n$E581:C0 33     CPY #$33                           A:AA X:AA Y:33 P:nvUbdIZC SP:FB PPU:338,134 CYC:15351\n$E583:F0 04     BEQ $E589                          A:AA X:AA Y:33 P:nvUbdIZC SP:FB PPU:  3,135 CYC:15353\n$E589:A9 87     LDA #$87                           A:AA X:AA Y:33 P:nvUbdIZC SP:FB PPU: 12,135 CYC:15356\n$E58B:85 67     STA $67 = #$00                       A:87 X:AA Y:33 P:A5 SP:FB PPU: 18,135 CYC:15358\n$E58D:A9 32     LDA #$32                           A:87 X:AA Y:33 P:A5 SP:FB PPU: 27,135 CYC:15361\n$E58F:85 68     STA $68 = #$00                       A:32 X:AA Y:33 P:25 SP:FB PPU: 33,135 CYC:15363\n$E591:A0 57     LDY #$57                           A:32 X:AA Y:33 P:25 SP:FB PPU: 42,135 CYC:15366\n$E593:24 01     BIT $01 = #$FF                       A:32 X:AA Y:57 P:25 SP:FB PPU: 48,135 CYC:15368\n$E595:38        SEC                                A:32 X:AA Y:57 P:E5 SP:FB PPU: 57,135 CYC:15371\n$E596:A9 00     LDA #$00                           A:32 X:AA Y:57 P:E5 SP:FB PPU: 63,135 CYC:15373\n$E598:A7 67    *LAX $67 = #$87                       A:00 X:AA Y:57 P:67 SP:FB PPU: 69,135 CYC:15375\n$E59A:EA        NOP                                A:87 X:87 Y:57 P:E5 SP:FB PPU: 78,135 CYC:15378\n$E59B:EA        NOP                                A:87 X:87 Y:57 P:E5 SP:FB PPU: 84,135 CYC:15380\n$E59C:EA        NOP                                A:87 X:87 Y:57 P:E5 SP:FB PPU: 90,135 CYC:15382\n$E59D:EA        NOP                                A:87 X:87 Y:57 P:E5 SP:FB PPU: 96,135 CYC:15384\n$E59E:F0 12     BEQ $E5B2                          A:87 X:87 Y:57 P:E5 SP:FB PPU:102,135 CYC:15386\n$E5A0:10 10     BPL $E5B2                          A:87 X:87 Y:57 P:E5 SP:FB PPU:108,135 CYC:15388\n$E5A2:50 0E     BVC $E5B2                          A:87 X:87 Y:57 P:E5 SP:FB PPU:114,135 CYC:15390\n$E5A4:90 0C     BCC $E5B2                          A:87 X:87 Y:57 P:E5 SP:FB PPU:120,135 CYC:15392\n$E5A6:C9 87     CMP #$87                           A:87 X:87 Y:57 P:E5 SP:FB PPU:126,135 CYC:15394\n$E5A8:D0 08     BNE $E5B2                          A:87 X:87 Y:57 P:67 SP:FB PPU:132,135 CYC:15396\n$E5AA:E0 87     CPX #$87                           A:87 X:87 Y:57 P:67 SP:FB PPU:138,135 CYC:15398\n$E5AC:D0 04     BNE $E5B2                          A:87 X:87 Y:57 P:67 SP:FB PPU:144,135 CYC:15400\n$E5AE:C0 57     CPY #$57                           A:87 X:87 Y:57 P:67 SP:FB PPU:150,135 CYC:15402\n$E5B0:F0 04     BEQ $E5B6                          A:87 X:87 Y:57 P:67 SP:FB PPU:156,135 CYC:15404\n$E5B6:A0 53     LDY #$53                           A:87 X:87 Y:57 P:67 SP:FB PPU:165,135 CYC:15407\n$E5B8:B8        CLV                                A:87 X:87 Y:53 P:65 SP:FB PPU:171,135 CYC:15409\n$E5B9:18        CLC                                A:87 X:87 Y:53 P:25 SP:FB PPU:177,135 CYC:15411\n$E5BA:A9 00     LDA #$00                           A:87 X:87 Y:53 P:nvUbdIzc SP:FB PPU:183,135 CYC:15413\n$E5BC:A7 68    *LAX $68 = #$32                       A:00 X:87 Y:53 P:nvUbdIZc SP:FB PPU:189,135 CYC:15415\n$E5BE:EA        NOP                                A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:198,135 CYC:15418\n$E5BF:EA        NOP                                A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:204,135 CYC:15420\n$E5C0:EA        NOP                                A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:210,135 CYC:15422\n$E5C1:EA        NOP                                A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:216,135 CYC:15424\n$E5C2:F0 12     BEQ $E5D6                          A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:222,135 CYC:15426\n$E5C4:30 10     BMI $E5D6                          A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:228,135 CYC:15428\n$E5C6:70 0E     BVS $E5D6                          A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:234,135 CYC:15430\n$E5C8:B0 0C     BCS $E5D6                          A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:240,135 CYC:15432\n$E5CA:C9 32     CMP #$32                           A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:246,135 CYC:15434\n$E5CC:D0 08     BNE $E5D6                          A:32 X:32 Y:53 P:nvUbdIZC SP:FB PPU:252,135 CYC:15436\n$E5CE:E0 32     CPX #$32                           A:32 X:32 Y:53 P:nvUbdIZC SP:FB PPU:258,135 CYC:15438\n$E5D0:D0 04     BNE $E5D6                          A:32 X:32 Y:53 P:nvUbdIZC SP:FB PPU:264,135 CYC:15440\n$E5D2:C0 53     CPY #$53                           A:32 X:32 Y:53 P:nvUbdIZC SP:FB PPU:270,135 CYC:15442\n$E5D4:F0 04     BEQ $E5DA                          A:32 X:32 Y:53 P:nvUbdIZC SP:FB PPU:276,135 CYC:15444\n$E5DA:A9 87     LDA #$87                           A:32 X:32 Y:53 P:nvUbdIZC SP:FB PPU:285,135 CYC:15447\n$E5DC:8D 77 05  STA $0577 = #$00                     A:87 X:32 Y:53 P:A5 SP:FB PPU:291,135 CYC:15449\n$E5DF:A9 32     LDA #$32                           A:87 X:32 Y:53 P:A5 SP:FB PPU:303,135 CYC:15453\n$E5E1:8D 78 05  STA $0578 = #$00                     A:32 X:32 Y:53 P:25 SP:FB PPU:309,135 CYC:15455\n$E5E4:A0 57     LDY #$57                           A:32 X:32 Y:53 P:25 SP:FB PPU:321,135 CYC:15459\n$E5E6:24 01     BIT $01 = #$FF                       A:32 X:32 Y:57 P:25 SP:FB PPU:327,135 CYC:15461\n$E5E8:38        SEC                                A:32 X:32 Y:57 P:E5 SP:FB PPU:336,135 CYC:15464\n$E5E9:A9 00     LDA #$00                           A:32 X:32 Y:57 P:E5 SP:FB PPU:  1,136 CYC:15466\n$E5EB:AF 77 05 *LAX $0577 = #$87                     A:00 X:32 Y:57 P:67 SP:FB PPU:  7,136 CYC:15468\n$E5EE:EA        NOP                                A:87 X:87 Y:57 P:E5 SP:FB PPU: 19,136 CYC:15472\n$E5EF:EA        NOP                                A:87 X:87 Y:57 P:E5 SP:FB PPU: 25,136 CYC:15474\n$E5F0:EA        NOP                                A:87 X:87 Y:57 P:E5 SP:FB PPU: 31,136 CYC:15476\n$E5F1:EA        NOP                                A:87 X:87 Y:57 P:E5 SP:FB PPU: 37,136 CYC:15478\n$E5F2:F0 12     BEQ $E606                          A:87 X:87 Y:57 P:E5 SP:FB PPU: 43,136 CYC:15480\n$E5F4:10 10     BPL $E606                          A:87 X:87 Y:57 P:E5 SP:FB PPU: 49,136 CYC:15482\n$E5F6:50 0E     BVC $E606                          A:87 X:87 Y:57 P:E5 SP:FB PPU: 55,136 CYC:15484\n$E5F8:90 0C     BCC $E606                          A:87 X:87 Y:57 P:E5 SP:FB PPU: 61,136 CYC:15486\n$E5FA:C9 87     CMP #$87                           A:87 X:87 Y:57 P:E5 SP:FB PPU: 67,136 CYC:15488\n$E5FC:D0 08     BNE $E606                          A:87 X:87 Y:57 P:67 SP:FB PPU: 73,136 CYC:15490\n$E5FE:E0 87     CPX #$87                           A:87 X:87 Y:57 P:67 SP:FB PPU: 79,136 CYC:15492\n$E600:D0 04     BNE $E606                          A:87 X:87 Y:57 P:67 SP:FB PPU: 85,136 CYC:15494\n$E602:C0 57     CPY #$57                           A:87 X:87 Y:57 P:67 SP:FB PPU: 91,136 CYC:15496\n$E604:F0 04     BEQ $E60A                          A:87 X:87 Y:57 P:67 SP:FB PPU: 97,136 CYC:15498\n$E60A:A0 53     LDY #$53                           A:87 X:87 Y:57 P:67 SP:FB PPU:106,136 CYC:15501\n$E60C:B8        CLV                                A:87 X:87 Y:53 P:65 SP:FB PPU:112,136 CYC:15503\n$E60D:18        CLC                                A:87 X:87 Y:53 P:25 SP:FB PPU:118,136 CYC:15505\n$E60E:A9 00     LDA #$00                           A:87 X:87 Y:53 P:nvUbdIzc SP:FB PPU:124,136 CYC:15507\n$E610:AF 78 05 *LAX $0578 = #$32                     A:00 X:87 Y:53 P:nvUbdIZc SP:FB PPU:130,136 CYC:15509\n$E613:EA        NOP                                A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:142,136 CYC:15513\n$E614:EA        NOP                                A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:148,136 CYC:15515\n$E615:EA        NOP                                A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:154,136 CYC:15517\n$E616:EA        NOP                                A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:160,136 CYC:15519\n$E617:F0 12     BEQ $E62B                          A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:166,136 CYC:15521\n$E619:30 10     BMI $E62B                          A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:172,136 CYC:15523\n$E61B:70 0E     BVS $E62B                          A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:178,136 CYC:15525\n$E61D:B0 0C     BCS $E62B                          A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:184,136 CYC:15527\n$E61F:C9 32     CMP #$32                           A:32 X:32 Y:53 P:nvUbdIzc SP:FB PPU:190,136 CYC:15529\n$E621:D0 08     BNE $E62B                          A:32 X:32 Y:53 P:nvUbdIZC SP:FB PPU:196,136 CYC:15531\n$E623:E0 32     CPX #$32                           A:32 X:32 Y:53 P:nvUbdIZC SP:FB PPU:202,136 CYC:15533\n$E625:D0 04     BNE $E62B                          A:32 X:32 Y:53 P:nvUbdIZC SP:FB PPU:208,136 CYC:15535\n$E627:C0 53     CPY #$53                           A:32 X:32 Y:53 P:nvUbdIZC SP:FB PPU:214,136 CYC:15537\n$E629:F0 04     BEQ $E62F                          A:32 X:32 Y:53 P:nvUbdIZC SP:FB PPU:220,136 CYC:15539\n$E62F:A9 FF     LDA #$FF                           A:32 X:32 Y:53 P:nvUbdIZC SP:FB PPU:229,136 CYC:15542\n$E631:85 43     STA $43 = #$80                       A:FF X:32 Y:53 P:A5 SP:FB PPU:235,136 CYC:15544\n$E633:A9 04     LDA #$04                           A:FF X:32 Y:53 P:A5 SP:FB PPU:244,136 CYC:15547\n$E635:85 44     STA $44 = #$05                       A:04 X:32 Y:53 P:25 SP:FB PPU:250,136 CYC:15549\n$E637:A9 32     LDA #$32                           A:04 X:32 Y:53 P:25 SP:FB PPU:259,136 CYC:15552\n$E639:85 45     STA $45 = #$32                       A:32 X:32 Y:53 P:25 SP:FB PPU:265,136 CYC:15554\n$E63B:A9 04     LDA #$04                           A:32 X:32 Y:53 P:25 SP:FB PPU:274,136 CYC:15557\n$E63D:85 46     STA $46 = #$04                       A:04 X:32 Y:53 P:25 SP:FB PPU:280,136 CYC:15559\n$E63F:A9 55     LDA #$55                           A:04 X:32 Y:53 P:25 SP:FB PPU:289,136 CYC:15562\n$E641:8D 80 05  STA $0580 = #$55                     A:55 X:32 Y:53 P:25 SP:FB PPU:295,136 CYC:15564\n$E644:A9 AA     LDA #$AA                           A:55 X:32 Y:53 P:25 SP:FB PPU:307,136 CYC:15568\n$E646:8D 32 04  STA $0432 = #$AA                     A:AA X:32 Y:53 P:A5 SP:FB PPU:313,136 CYC:15570\n$E649:A2 03     LDX #$03                           A:AA X:32 Y:53 P:A5 SP:FB PPU:325,136 CYC:15574\n$E64B:A0 81     LDY #$81                           A:AA X:03 Y:53 P:25 SP:FB PPU:331,136 CYC:15576\n$E64D:24 01     BIT $01 = #$FF                       A:AA X:03 Y:81 P:A5 SP:FB PPU:337,136 CYC:15578\n$E64F:38        SEC                                A:AA X:03 Y:81 P:E5 SP:FB PPU:  5,137 CYC:15581\n$E650:A9 00     LDA #$00                           A:AA X:03 Y:81 P:E5 SP:FB PPU: 11,137 CYC:15583\n$E652:B3 43    *LAX ($43),Y = #$04FF @ 0580 = 55     A:00 X:03 Y:81 P:67 SP:FB PPU: 17,137 CYC:15585\n$E654:EA        NOP                                A:55 X:55 Y:81 P:65 SP:FB PPU: 35,137 CYC:15591\n$E655:EA        NOP                                A:55 X:55 Y:81 P:65 SP:FB PPU: 41,137 CYC:15593\n$E656:EA        NOP                                A:55 X:55 Y:81 P:65 SP:FB PPU: 47,137 CYC:15595\n$E657:EA        NOP                                A:55 X:55 Y:81 P:65 SP:FB PPU: 53,137 CYC:15597\n$E658:F0 12     BEQ $E66C                          A:55 X:55 Y:81 P:65 SP:FB PPU: 59,137 CYC:15599\n$E65A:30 10     BMI $E66C                          A:55 X:55 Y:81 P:65 SP:FB PPU: 65,137 CYC:15601\n$E65C:50 0E     BVC $E66C                          A:55 X:55 Y:81 P:65 SP:FB PPU: 71,137 CYC:15603\n$E65E:90 0C     BCC $E66C                          A:55 X:55 Y:81 P:65 SP:FB PPU: 77,137 CYC:15605\n$E660:C9 55     CMP #$55                           A:55 X:55 Y:81 P:65 SP:FB PPU: 83,137 CYC:15607\n$E662:D0 08     BNE $E66C                          A:55 X:55 Y:81 P:67 SP:FB PPU: 89,137 CYC:15609\n$E664:E0 55     CPX #$55                           A:55 X:55 Y:81 P:67 SP:FB PPU: 95,137 CYC:15611\n$E666:D0 04     BNE $E66C                          A:55 X:55 Y:81 P:67 SP:FB PPU:101,137 CYC:15613\n$E668:C0 81     CPY #$81                           A:55 X:55 Y:81 P:67 SP:FB PPU:107,137 CYC:15615\n$E66A:F0 04     BEQ $E670                          A:55 X:55 Y:81 P:67 SP:FB PPU:113,137 CYC:15617\n$E670:A2 05     LDX #$05                           A:55 X:55 Y:81 P:67 SP:FB PPU:122,137 CYC:15620\n$E672:A0 00     LDY #$00                           A:55 X:05 Y:81 P:65 SP:FB PPU:128,137 CYC:15622\n$E674:B8        CLV                                A:55 X:05 Y:00 P:67 SP:FB PPU:134,137 CYC:15624\n$E675:18        CLC                                A:55 X:05 Y:00 P:nvUbdIZC SP:FB PPU:140,137 CYC:15626\n$E676:A9 00     LDA #$00                           A:55 X:05 Y:00 P:nvUbdIZc SP:FB PPU:146,137 CYC:15628\n$E678:B3 45    *LAX ($45),Y = #$0432 @ 0432 = AA     A:00 X:05 Y:00 P:nvUbdIZc SP:FB PPU:152,137 CYC:15630\n$E67A:EA        NOP                                A:AA X:AA Y:00 P:NvUbdIzc SP:FB PPU:167,137 CYC:15635\n$E67B:EA        NOP                                A:AA X:AA Y:00 P:NvUbdIzc SP:FB PPU:173,137 CYC:15637\n$E67C:EA        NOP                                A:AA X:AA Y:00 P:NvUbdIzc SP:FB PPU:179,137 CYC:15639\n$E67D:EA        NOP                                A:AA X:AA Y:00 P:NvUbdIzc SP:FB PPU:185,137 CYC:15641\n$E67E:F0 12     BEQ $E692                          A:AA X:AA Y:00 P:NvUbdIzc SP:FB PPU:191,137 CYC:15643\n$E680:10 10     BPL $E692                          A:AA X:AA Y:00 P:NvUbdIzc SP:FB PPU:197,137 CYC:15645\n$E682:70 0E     BVS $E692                          A:AA X:AA Y:00 P:NvUbdIzc SP:FB PPU:203,137 CYC:15647\n$E684:B0 0C     BCS $E692                          A:AA X:AA Y:00 P:NvUbdIzc SP:FB PPU:209,137 CYC:15649\n$E686:C9 AA     CMP #$AA                           A:AA X:AA Y:00 P:NvUbdIzc SP:FB PPU:215,137 CYC:15651\n$E688:D0 08     BNE $E692                          A:AA X:AA Y:00 P:nvUbdIZC SP:FB PPU:221,137 CYC:15653\n$E68A:E0 AA     CPX #$AA                           A:AA X:AA Y:00 P:nvUbdIZC SP:FB PPU:227,137 CYC:15655\n$E68C:D0 04     BNE $E692                          A:AA X:AA Y:00 P:nvUbdIZC SP:FB PPU:233,137 CYC:15657\n$E68E:C0 00     CPY #$00                           A:AA X:AA Y:00 P:nvUbdIZC SP:FB PPU:239,137 CYC:15659\n$E690:F0 04     BEQ $E696                          A:AA X:AA Y:00 P:nvUbdIZC SP:FB PPU:245,137 CYC:15661\n$E696:A9 87     LDA #$87                           A:AA X:AA Y:00 P:nvUbdIZC SP:FB PPU:254,137 CYC:15664\n$E698:85 67     STA $67 = #$87                       A:87 X:AA Y:00 P:A5 SP:FB PPU:260,137 CYC:15666\n$E69A:A9 32     LDA #$32                           A:87 X:AA Y:00 P:A5 SP:FB PPU:269,137 CYC:15669\n$E69C:85 68     STA $68 = #$32                       A:32 X:AA Y:00 P:25 SP:FB PPU:275,137 CYC:15671\n$E69E:A0 57     LDY #$57                           A:32 X:AA Y:00 P:25 SP:FB PPU:284,137 CYC:15674\n$E6A0:24 01     BIT $01 = #$FF                       A:32 X:AA Y:57 P:25 SP:FB PPU:290,137 CYC:15676\n$E6A2:38        SEC                                A:32 X:AA Y:57 P:E5 SP:FB PPU:299,137 CYC:15679\n$E6A3:A9 00     LDA #$00                           A:32 X:AA Y:57 P:E5 SP:FB PPU:305,137 CYC:15681\n$E6A5:B7 10    *LAX $10,Y @ 67 = #$87                A:00 X:AA Y:57 P:67 SP:FB PPU:311,137 CYC:15683\n$E6A7:EA        NOP                                A:87 X:87 Y:57 P:E5 SP:FB PPU:323,137 CYC:15687\n$E6A8:EA        NOP                                A:87 X:87 Y:57 P:E5 SP:FB PPU:329,137 CYC:15689\n$E6A9:EA        NOP                                A:87 X:87 Y:57 P:E5 SP:FB PPU:335,137 CYC:15691\n$E6AA:EA        NOP                                A:87 X:87 Y:57 P:E5 SP:FB PPU:  0,138 CYC:15693\n$E6AB:F0 12     BEQ $E6BF                          A:87 X:87 Y:57 P:E5 SP:FB PPU:  6,138 CYC:15695\n$E6AD:10 10     BPL $E6BF                          A:87 X:87 Y:57 P:E5 SP:FB PPU: 12,138 CYC:15697\n$E6AF:50 0E     BVC $E6BF                          A:87 X:87 Y:57 P:E5 SP:FB PPU: 18,138 CYC:15699\n$E6B1:90 0C     BCC $E6BF                          A:87 X:87 Y:57 P:E5 SP:FB PPU: 24,138 CYC:15701\n$E6B3:C9 87     CMP #$87                           A:87 X:87 Y:57 P:E5 SP:FB PPU: 30,138 CYC:15703\n$E6B5:D0 08     BNE $E6BF                          A:87 X:87 Y:57 P:67 SP:FB PPU: 36,138 CYC:15705\n$E6B7:E0 87     CPX #$87                           A:87 X:87 Y:57 P:67 SP:FB PPU: 42,138 CYC:15707\n$E6B9:D0 04     BNE $E6BF                          A:87 X:87 Y:57 P:67 SP:FB PPU: 48,138 CYC:15709\n$E6BB:C0 57     CPY #$57                           A:87 X:87 Y:57 P:67 SP:FB PPU: 54,138 CYC:15711\n$E6BD:F0 04     BEQ $E6C3                          A:87 X:87 Y:57 P:67 SP:FB PPU: 60,138 CYC:15713\n$E6C3:A0 FF     LDY #$FF                           A:87 X:87 Y:57 P:67 SP:FB PPU: 69,138 CYC:15716\n$E6C5:B8        CLV                                A:87 X:87 Y:FF P:E5 SP:FB PPU: 75,138 CYC:15718\n$E6C6:18        CLC                                A:87 X:87 Y:FF P:A5 SP:FB PPU: 81,138 CYC:15720\n$E6C7:A9 00     LDA #$00                           A:87 X:87 Y:FF P:NvUbdIzc SP:FB PPU: 87,138 CYC:15722\n$E6C9:B7 69    *LAX $69,Y @ 68 = #$32                A:00 X:87 Y:FF P:nvUbdIZc SP:FB PPU: 93,138 CYC:15724\n$E6CB:EA        NOP                                A:32 X:32 Y:FF P:nvUbdIzc SP:FB PPU:105,138 CYC:15728\n$E6CC:EA        NOP                                A:32 X:32 Y:FF P:nvUbdIzc SP:FB PPU:111,138 CYC:15730\n$E6CD:EA        NOP                                A:32 X:32 Y:FF P:nvUbdIzc SP:FB PPU:117,138 CYC:15732\n$E6CE:EA        NOP                                A:32 X:32 Y:FF P:nvUbdIzc SP:FB PPU:123,138 CYC:15734\n$E6CF:F0 12     BEQ $E6E3                          A:32 X:32 Y:FF P:nvUbdIzc SP:FB PPU:129,138 CYC:15736\n$E6D1:30 10     BMI $E6E3                          A:32 X:32 Y:FF P:nvUbdIzc SP:FB PPU:135,138 CYC:15738\n$E6D3:70 0E     BVS $E6E3                          A:32 X:32 Y:FF P:nvUbdIzc SP:FB PPU:141,138 CYC:15740\n$E6D5:B0 0C     BCS $E6E3                          A:32 X:32 Y:FF P:nvUbdIzc SP:FB PPU:147,138 CYC:15742\n$E6D7:C9 32     CMP #$32                           A:32 X:32 Y:FF P:nvUbdIzc SP:FB PPU:153,138 CYC:15744\n$E6D9:D0 08     BNE $E6E3                          A:32 X:32 Y:FF P:nvUbdIZC SP:FB PPU:159,138 CYC:15746\n$E6DB:E0 32     CPX #$32                           A:32 X:32 Y:FF P:nvUbdIZC SP:FB PPU:165,138 CYC:15748\n$E6DD:D0 04     BNE $E6E3                          A:32 X:32 Y:FF P:nvUbdIZC SP:FB PPU:171,138 CYC:15750\n$E6DF:C0 FF     CPY #$FF                           A:32 X:32 Y:FF P:nvUbdIZC SP:FB PPU:177,138 CYC:15752\n$E6E1:F0 04     BEQ $E6E7                          A:32 X:32 Y:FF P:nvUbdIZC SP:FB PPU:183,138 CYC:15754\n$E6E7:A9 87     LDA #$87                           A:32 X:32 Y:FF P:nvUbdIZC SP:FB PPU:192,138 CYC:15757\n$E6E9:8D 87 05  STA $0587 = #$00                     A:87 X:32 Y:FF P:A5 SP:FB PPU:198,138 CYC:15759\n$E6EC:A9 32     LDA #$32                           A:87 X:32 Y:FF P:A5 SP:FB PPU:210,138 CYC:15763\n$E6EE:8D 88 05  STA $0588 = #$00                     A:32 X:32 Y:FF P:25 SP:FB PPU:216,138 CYC:15765\n$E6F1:A0 30     LDY #$30                           A:32 X:32 Y:FF P:25 SP:FB PPU:228,138 CYC:15769\n$E6F3:24 01     BIT $01 = #$FF                       A:32 X:32 Y:30 P:25 SP:FB PPU:234,138 CYC:15771\n$E6F5:38        SEC                                A:32 X:32 Y:30 P:E5 SP:FB PPU:243,138 CYC:15774\n$E6F6:A9 00     LDA #$00                           A:32 X:32 Y:30 P:E5 SP:FB PPU:249,138 CYC:15776\n$E6F8:BF 57 05 *LAX $0557,Y @ 0587 = #$87            A:00 X:32 Y:30 P:67 SP:FB PPU:255,138 CYC:15778\n$E6FB:EA        NOP                                A:87 X:87 Y:30 P:E5 SP:FB PPU:267,138 CYC:15782\n$E6FC:EA        NOP                                A:87 X:87 Y:30 P:E5 SP:FB PPU:273,138 CYC:15784\n$E6FD:EA        NOP                                A:87 X:87 Y:30 P:E5 SP:FB PPU:279,138 CYC:15786\n$E6FE:EA        NOP                                A:87 X:87 Y:30 P:E5 SP:FB PPU:285,138 CYC:15788\n$E6FF:F0 12     BEQ $E713                          A:87 X:87 Y:30 P:E5 SP:FB PPU:291,138 CYC:15790\n$E701:10 10     BPL $E713                          A:87 X:87 Y:30 P:E5 SP:FB PPU:297,138 CYC:15792\n$E703:50 0E     BVC $E713                          A:87 X:87 Y:30 P:E5 SP:FB PPU:303,138 CYC:15794\n$E705:90 0C     BCC $E713                          A:87 X:87 Y:30 P:E5 SP:FB PPU:309,138 CYC:15796\n$E707:C9 87     CMP #$87                           A:87 X:87 Y:30 P:E5 SP:FB PPU:315,138 CYC:15798\n$E709:D0 08     BNE $E713                          A:87 X:87 Y:30 P:67 SP:FB PPU:321,138 CYC:15800\n$E70B:E0 87     CPX #$87                           A:87 X:87 Y:30 P:67 SP:FB PPU:327,138 CYC:15802\n$E70D:D0 04     BNE $E713                          A:87 X:87 Y:30 P:67 SP:FB PPU:333,138 CYC:15804\n$E70F:C0 30     CPY #$30                           A:87 X:87 Y:30 P:67 SP:FB PPU:339,138 CYC:15806\n$E711:F0 04     BEQ $E717                          A:87 X:87 Y:30 P:67 SP:FB PPU:  4,139 CYC:15808\n$E717:A0 40     LDY #$40                           A:87 X:87 Y:30 P:67 SP:FB PPU: 13,139 CYC:15811\n$E719:B8        CLV                                A:87 X:87 Y:40 P:65 SP:FB PPU: 19,139 CYC:15813\n$E71A:18        CLC                                A:87 X:87 Y:40 P:25 SP:FB PPU: 25,139 CYC:15815\n$E71B:A9 00     LDA #$00                           A:87 X:87 Y:40 P:nvUbdIzc SP:FB PPU: 31,139 CYC:15817\n$E71D:BF 48 05 *LAX $0548,Y @ 0588 = #$32            A:00 X:87 Y:40 P:nvUbdIZc SP:FB PPU: 37,139 CYC:15819\n$E720:EA        NOP                                A:32 X:32 Y:40 P:nvUbdIzc SP:FB PPU: 49,139 CYC:15823\n$E721:EA        NOP                                A:32 X:32 Y:40 P:nvUbdIzc SP:FB PPU: 55,139 CYC:15825\n$E722:EA        NOP                                A:32 X:32 Y:40 P:nvUbdIzc SP:FB PPU: 61,139 CYC:15827\n$E723:EA        NOP                                A:32 X:32 Y:40 P:nvUbdIzc SP:FB PPU: 67,139 CYC:15829\n$E724:F0 12     BEQ $E738                          A:32 X:32 Y:40 P:nvUbdIzc SP:FB PPU: 73,139 CYC:15831\n$E726:30 10     BMI $E738                          A:32 X:32 Y:40 P:nvUbdIzc SP:FB PPU: 79,139 CYC:15833\n$E728:70 0E     BVS $E738                          A:32 X:32 Y:40 P:nvUbdIzc SP:FB PPU: 85,139 CYC:15835\n$E72A:B0 0C     BCS $E738                          A:32 X:32 Y:40 P:nvUbdIzc SP:FB PPU: 91,139 CYC:15837\n$E72C:C9 32     CMP #$32                           A:32 X:32 Y:40 P:nvUbdIzc SP:FB PPU: 97,139 CYC:15839\n$E72E:D0 08     BNE $E738                          A:32 X:32 Y:40 P:nvUbdIZC SP:FB PPU:103,139 CYC:15841\n$E730:E0 32     CPX #$32                           A:32 X:32 Y:40 P:nvUbdIZC SP:FB PPU:109,139 CYC:15843\n$E732:D0 04     BNE $E738                          A:32 X:32 Y:40 P:nvUbdIZC SP:FB PPU:115,139 CYC:15845\n$E734:C0 40     CPY #$40                           A:32 X:32 Y:40 P:nvUbdIZC SP:FB PPU:121,139 CYC:15847\n$E736:F0 04     BEQ $E73C                          A:32 X:32 Y:40 P:nvUbdIZC SP:FB PPU:127,139 CYC:15849\n$E73C:60        RTS                                A:32 X:32 Y:40 P:nvUbdIZC SP:FB PPU:136,139 CYC:15852\n$C635:20 3D E7  JSR $E73D                          A:32 X:32 Y:40 P:nvUbdIZC SP:FD PPU:154,139 CYC:15858\n$E73D:A9 C0     LDA #$C0                           A:32 X:32 Y:40 P:nvUbdIZC SP:FB PPU:172,139 CYC:15864\n$E73F:85 01     STA $01 = #$FF                       A:C0 X:32 Y:40 P:A5 SP:FB PPU:178,139 CYC:15866\n$E741:A9 00     LDA #$00                           A:C0 X:32 Y:40 P:A5 SP:FB PPU:187,139 CYC:15869\n$E743:8D 89 04  STA $0489 = #$00                     A:00 X:32 Y:40 P:nvUbdIZC SP:FB PPU:193,139 CYC:15871\n$E746:A9 89     LDA #$89                           A:00 X:32 Y:40 P:nvUbdIZC SP:FB PPU:205,139 CYC:15875\n$E748:85 60     STA $60 = #$00                       A:89 X:32 Y:40 P:A5 SP:FB PPU:211,139 CYC:15877\n$E74A:A9 04     LDA #$04                           A:89 X:32 Y:40 P:A5 SP:FB PPU:220,139 CYC:15880\n$E74C:85 61     STA $61 = #$00                       A:04 X:32 Y:40 P:25 SP:FB PPU:226,139 CYC:15882\n$E74E:A0 44     LDY #$44                           A:04 X:32 Y:40 P:25 SP:FB PPU:235,139 CYC:15885\n$E750:A2 17     LDX #$17                           A:04 X:32 Y:44 P:25 SP:FB PPU:241,139 CYC:15887\n$E752:A9 3E     LDA #$3E                           A:04 X:17 Y:44 P:25 SP:FB PPU:247,139 CYC:15889\n$E754:24 01     BIT $01 = #$C0                       A:3E X:17 Y:44 P:25 SP:FB PPU:253,139 CYC:15891\n$E756:18        CLC                                A:3E X:17 Y:44 P:E7 SP:FB PPU:262,139 CYC:15894\n$E757:83 49    *SAX ($49,X) @ 60 = #$0489 = 00       A:3E X:17 Y:44 P:E6 SP:FB PPU:268,139 CYC:15896\n$E759:EA        NOP                                A:3E X:17 Y:44 P:E6 SP:FB PPU:286,139 CYC:15902\n$E75A:EA        NOP                                A:3E X:17 Y:44 P:E6 SP:FB PPU:292,139 CYC:15904\n$E75B:EA        NOP                                A:3E X:17 Y:44 P:E6 SP:FB PPU:298,139 CYC:15906\n$E75C:EA        NOP                                A:3E X:17 Y:44 P:E6 SP:FB PPU:304,139 CYC:15908\n$E75D:D0 19     BNE $E778                          A:3E X:17 Y:44 P:E6 SP:FB PPU:310,139 CYC:15910\n$E75F:B0 17     BCS $E778                          A:3E X:17 Y:44 P:E6 SP:FB PPU:316,139 CYC:15912\n$E761:50 15     BVC $E778                          A:3E X:17 Y:44 P:E6 SP:FB PPU:322,139 CYC:15914\n$E763:10 13     BPL $E778                          A:3E X:17 Y:44 P:E6 SP:FB PPU:328,139 CYC:15916\n$E765:C9 3E     CMP #$3E                           A:3E X:17 Y:44 P:E6 SP:FB PPU:334,139 CYC:15918\n$E767:D0 0F     BNE $E778                          A:3E X:17 Y:44 P:67 SP:FB PPU:340,139 CYC:15920\n$E769:C0 44     CPY #$44                           A:3E X:17 Y:44 P:67 SP:FB PPU:  5,140 CYC:15922\n$E76B:D0 0B     BNE $E778                          A:3E X:17 Y:44 P:67 SP:FB PPU: 11,140 CYC:15924\n$E76D:E0 17     CPX #$17                           A:3E X:17 Y:44 P:67 SP:FB PPU: 17,140 CYC:15926\n$E76F:D0 07     BNE $E778                          A:3E X:17 Y:44 P:67 SP:FB PPU: 23,140 CYC:15928\n$E771:AD 89 04  LDA $0489 = #$16                     A:3E X:17 Y:44 P:67 SP:FB PPU: 29,140 CYC:15930\n$E774:C9 16     CMP #$16                           A:16 X:17 Y:44 P:65 SP:FB PPU: 41,140 CYC:15934\n$E776:F0 04     BEQ $E77C                          A:16 X:17 Y:44 P:67 SP:FB PPU: 47,140 CYC:15936\n$E77C:A0 44     LDY #$44                           A:16 X:17 Y:44 P:67 SP:FB PPU: 56,140 CYC:15939\n$E77E:A2 7A     LDX #$7A                           A:16 X:17 Y:44 P:65 SP:FB PPU: 62,140 CYC:15941\n$E780:A9 66     LDA #$66                           A:16 X:7A Y:44 P:65 SP:FB PPU: 68,140 CYC:15943\n$E782:38        SEC                                A:66 X:7A Y:44 P:65 SP:FB PPU: 74,140 CYC:15945\n$E783:B8        CLV                                A:66 X:7A Y:44 P:65 SP:FB PPU: 80,140 CYC:15947\n$E784:83 E6    *SAX ($E6,X) @ 60 = #$0489 = 16       A:66 X:7A Y:44 P:25 SP:FB PPU: 86,140 CYC:15949\n$E786:EA        NOP                                A:66 X:7A Y:44 P:25 SP:FB PPU:104,140 CYC:15955\n$E787:EA        NOP                                A:66 X:7A Y:44 P:25 SP:FB PPU:110,140 CYC:15957\n$E788:EA        NOP                                A:66 X:7A Y:44 P:25 SP:FB PPU:116,140 CYC:15959\n$E789:EA        NOP                                A:66 X:7A Y:44 P:25 SP:FB PPU:122,140 CYC:15961\n$E78A:F0 19     BEQ $E7A5                          A:66 X:7A Y:44 P:25 SP:FB PPU:128,140 CYC:15963\n$E78C:90 17     BCC $E7A5                          A:66 X:7A Y:44 P:25 SP:FB PPU:134,140 CYC:15965\n$E78E:70 15     BVS $E7A5                          A:66 X:7A Y:44 P:25 SP:FB PPU:140,140 CYC:15967\n$E790:30 13     BMI $E7A5                          A:66 X:7A Y:44 P:25 SP:FB PPU:146,140 CYC:15969\n$E792:C9 66     CMP #$66                           A:66 X:7A Y:44 P:25 SP:FB PPU:152,140 CYC:15971\n$E794:D0 0F     BNE $E7A5                          A:66 X:7A Y:44 P:nvUbdIZC SP:FB PPU:158,140 CYC:15973\n$E796:C0 44     CPY #$44                           A:66 X:7A Y:44 P:nvUbdIZC SP:FB PPU:164,140 CYC:15975\n$E798:D0 0B     BNE $E7A5                          A:66 X:7A Y:44 P:nvUbdIZC SP:FB PPU:170,140 CYC:15977\n$E79A:E0 7A     CPX #$7A                           A:66 X:7A Y:44 P:nvUbdIZC SP:FB PPU:176,140 CYC:15979\n$E79C:D0 07     BNE $E7A5                          A:66 X:7A Y:44 P:nvUbdIZC SP:FB PPU:182,140 CYC:15981\n$E79E:AD 89 04  LDA $0489 = #$62                     A:66 X:7A Y:44 P:nvUbdIZC SP:FB PPU:188,140 CYC:15983\n$E7A1:C9 62     CMP #$62                           A:62 X:7A Y:44 P:25 SP:FB PPU:200,140 CYC:15987\n$E7A3:F0 04     BEQ $E7A9                          A:62 X:7A Y:44 P:nvUbdIZC SP:FB PPU:206,140 CYC:15989\n$E7A9:A9 FF     LDA #$FF                           A:62 X:7A Y:44 P:nvUbdIZC SP:FB PPU:215,140 CYC:15992\n$E7AB:85 49     STA $49 = #$00                       A:FF X:7A Y:44 P:A5 SP:FB PPU:221,140 CYC:15994\n$E7AD:A0 44     LDY #$44                           A:FF X:7A Y:44 P:A5 SP:FB PPU:230,140 CYC:15997\n$E7AF:A2 AA     LDX #$AA                           A:FF X:7A Y:44 P:25 SP:FB PPU:236,140 CYC:15999\n$E7B1:A9 55     LDA #$55                           A:FF X:AA Y:44 P:A5 SP:FB PPU:242,140 CYC:16001\n$E7B3:24 01     BIT $01 = #$C0                       A:55 X:AA Y:44 P:25 SP:FB PPU:248,140 CYC:16003\n$E7B5:18        CLC                                A:55 X:AA Y:44 P:E5 SP:FB PPU:257,140 CYC:16006\n$E7B6:87 49    *SAX $49 = #$FF                       A:55 X:AA Y:44 P:NVUbdIzc SP:FB PPU:263,140 CYC:16008\n$E7B8:EA        NOP                                A:55 X:AA Y:44 P:NVUbdIzc SP:FB PPU:272,140 CYC:16011\n$E7B9:EA        NOP                                A:55 X:AA Y:44 P:NVUbdIzc SP:FB PPU:278,140 CYC:16013\n$E7BA:EA        NOP                                A:55 X:AA Y:44 P:NVUbdIzc SP:FB PPU:284,140 CYC:16015\n$E7BB:EA        NOP                                A:55 X:AA Y:44 P:NVUbdIzc SP:FB PPU:290,140 CYC:16017\n$E7BC:F0 18     BEQ $E7D6                          A:55 X:AA Y:44 P:NVUbdIzc SP:FB PPU:296,140 CYC:16019\n$E7BE:B0 16     BCS $E7D6                          A:55 X:AA Y:44 P:NVUbdIzc SP:FB PPU:302,140 CYC:16021\n$E7C0:50 14     BVC $E7D6                          A:55 X:AA Y:44 P:NVUbdIzc SP:FB PPU:308,140 CYC:16023\n$E7C2:10 12     BPL $E7D6                          A:55 X:AA Y:44 P:NVUbdIzc SP:FB PPU:314,140 CYC:16025\n$E7C4:C9 55     CMP #$55                           A:55 X:AA Y:44 P:NVUbdIzc SP:FB PPU:320,140 CYC:16027\n$E7C6:D0 0E     BNE $E7D6                          A:55 X:AA Y:44 P:67 SP:FB PPU:326,140 CYC:16029\n$E7C8:C0 44     CPY #$44                           A:55 X:AA Y:44 P:67 SP:FB PPU:332,140 CYC:16031\n$E7CA:D0 0A     BNE $E7D6                          A:55 X:AA Y:44 P:67 SP:FB PPU:338,140 CYC:16033\n$E7CC:E0 AA     CPX #$AA                           A:55 X:AA Y:44 P:67 SP:FB PPU:  3,141 CYC:16035\n$E7CE:D0 06     BNE $E7D6                          A:55 X:AA Y:44 P:67 SP:FB PPU:  9,141 CYC:16037\n$E7D0:A5 49     LDA $49 = #$00                       A:55 X:AA Y:44 P:67 SP:FB PPU: 15,141 CYC:16039\n$E7D2:C9 00     CMP #$00                           A:00 X:AA Y:44 P:67 SP:FB PPU: 24,141 CYC:16042\n$E7D4:F0 04     BEQ $E7DA                          A:00 X:AA Y:44 P:67 SP:FB PPU: 30,141 CYC:16044\n$E7DA:A9 00     LDA #$00                           A:00 X:AA Y:44 P:67 SP:FB PPU: 39,141 CYC:16047\n$E7DC:85 56     STA $56 = #$00                       A:00 X:AA Y:44 P:67 SP:FB PPU: 45,141 CYC:16049\n$E7DE:A0 58     LDY #$58                           A:00 X:AA Y:44 P:67 SP:FB PPU: 54,141 CYC:16052\n$E7E0:A2 EF     LDX #$EF                           A:00 X:AA Y:58 P:65 SP:FB PPU: 60,141 CYC:16054\n$E7E2:A9 66     LDA #$66                           A:00 X:EF Y:58 P:E5 SP:FB PPU: 66,141 CYC:16056\n$E7E4:38        SEC                                A:66 X:EF Y:58 P:65 SP:FB PPU: 72,141 CYC:16058\n$E7E5:B8        CLV                                A:66 X:EF Y:58 P:65 SP:FB PPU: 78,141 CYC:16060\n$E7E6:87 56    *SAX $56 = #$00                       A:66 X:EF Y:58 P:25 SP:FB PPU: 84,141 CYC:16062\n$E7E8:EA        NOP                                A:66 X:EF Y:58 P:25 SP:FB PPU: 93,141 CYC:16065\n$E7E9:EA        NOP                                A:66 X:EF Y:58 P:25 SP:FB PPU: 99,141 CYC:16067\n$E7EA:EA        NOP                                A:66 X:EF Y:58 P:25 SP:FB PPU:105,141 CYC:16069\n$E7EB:EA        NOP                                A:66 X:EF Y:58 P:25 SP:FB PPU:111,141 CYC:16071\n$E7EC:F0 18     BEQ $E806                          A:66 X:EF Y:58 P:25 SP:FB PPU:117,141 CYC:16073\n$E7EE:90 16     BCC $E806                          A:66 X:EF Y:58 P:25 SP:FB PPU:123,141 CYC:16075\n$E7F0:70 14     BVS $E806                          A:66 X:EF Y:58 P:25 SP:FB PPU:129,141 CYC:16077\n$E7F2:30 12     BMI $E806                          A:66 X:EF Y:58 P:25 SP:FB PPU:135,141 CYC:16079\n$E7F4:C9 66     CMP #$66                           A:66 X:EF Y:58 P:25 SP:FB PPU:141,141 CYC:16081\n$E7F6:D0 0E     BNE $E806                          A:66 X:EF Y:58 P:nvUbdIZC SP:FB PPU:147,141 CYC:16083\n$E7F8:C0 58     CPY #$58                           A:66 X:EF Y:58 P:nvUbdIZC SP:FB PPU:153,141 CYC:16085\n$E7FA:D0 0A     BNE $E806                          A:66 X:EF Y:58 P:nvUbdIZC SP:FB PPU:159,141 CYC:16087\n$E7FC:E0 EF     CPX #$EF                           A:66 X:EF Y:58 P:nvUbdIZC SP:FB PPU:165,141 CYC:16089\n$E7FE:D0 06     BNE $E806                          A:66 X:EF Y:58 P:nvUbdIZC SP:FB PPU:171,141 CYC:16091\n$E800:A5 56     LDA $56 = #$66                       A:66 X:EF Y:58 P:nvUbdIZC SP:FB PPU:177,141 CYC:16093\n$E802:C9 66     CMP #$66                           A:66 X:EF Y:58 P:25 SP:FB PPU:186,141 CYC:16096\n$E804:F0 04     BEQ $E80A                          A:66 X:EF Y:58 P:nvUbdIZC SP:FB PPU:192,141 CYC:16098\n$E80A:A9 FF     LDA #$FF                           A:66 X:EF Y:58 P:nvUbdIZC SP:FB PPU:201,141 CYC:16101\n$E80C:8D 49 05  STA $0549 = #$00                     A:FF X:EF Y:58 P:A5 SP:FB PPU:207,141 CYC:16103\n$E80F:A0 E5     LDY #$E5                           A:FF X:EF Y:58 P:A5 SP:FB PPU:219,141 CYC:16107\n$E811:A2 AF     LDX #$AF                           A:FF X:EF Y:E5 P:A5 SP:FB PPU:225,141 CYC:16109\n$E813:A9 F5     LDA #$F5                           A:FF X:AF Y:E5 P:A5 SP:FB PPU:231,141 CYC:16111\n$E815:24 01     BIT $01 = #$C0                       A:F5 X:AF Y:E5 P:A5 SP:FB PPU:237,141 CYC:16113\n$E817:18        CLC                                A:F5 X:AF Y:E5 P:E5 SP:FB PPU:246,141 CYC:16116\n$E818:8F 49 05 *SAX $0549 = #$FF                     A:F5 X:AF Y:E5 P:NVUbdIzc SP:FB PPU:252,141 CYC:16118\n$E81B:EA        NOP                                A:F5 X:AF Y:E5 P:NVUbdIzc SP:FB PPU:264,141 CYC:16122\n$E81C:EA        NOP                                A:F5 X:AF Y:E5 P:NVUbdIzc SP:FB PPU:270,141 CYC:16124\n$E81D:EA        NOP                                A:F5 X:AF Y:E5 P:NVUbdIzc SP:FB PPU:276,141 CYC:16126\n$E81E:EA        NOP                                A:F5 X:AF Y:E5 P:NVUbdIzc SP:FB PPU:282,141 CYC:16128\n$E81F:F0 19     BEQ $E83A                          A:F5 X:AF Y:E5 P:NVUbdIzc SP:FB PPU:288,141 CYC:16130\n$E821:B0 17     BCS $E83A                          A:F5 X:AF Y:E5 P:NVUbdIzc SP:FB PPU:294,141 CYC:16132\n$E823:50 15     BVC $E83A                          A:F5 X:AF Y:E5 P:NVUbdIzc SP:FB PPU:300,141 CYC:16134\n$E825:10 13     BPL $E83A                          A:F5 X:AF Y:E5 P:NVUbdIzc SP:FB PPU:306,141 CYC:16136\n$E827:C9 F5     CMP #$F5                           A:F5 X:AF Y:E5 P:NVUbdIzc SP:FB PPU:312,141 CYC:16138\n$E829:D0 0F     BNE $E83A                          A:F5 X:AF Y:E5 P:67 SP:FB PPU:318,141 CYC:16140\n$E82B:C0 E5     CPY #$E5                           A:F5 X:AF Y:E5 P:67 SP:FB PPU:324,141 CYC:16142\n$E82D:D0 0B     BNE $E83A                          A:F5 X:AF Y:E5 P:67 SP:FB PPU:330,141 CYC:16144\n$E82F:E0 AF     CPX #$AF                           A:F5 X:AF Y:E5 P:67 SP:FB PPU:336,141 CYC:16146\n$E831:D0 07     BNE $E83A                          A:F5 X:AF Y:E5 P:67 SP:FB PPU:  1,142 CYC:16148\n$E833:AD 49 05  LDA $0549 = #$A5                     A:F5 X:AF Y:E5 P:67 SP:FB PPU:  7,142 CYC:16150\n$E836:C9 A5     CMP #$A5                           A:A5 X:AF Y:E5 P:E5 SP:FB PPU: 19,142 CYC:16154\n$E838:F0 04     BEQ $E83E                          A:A5 X:AF Y:E5 P:67 SP:FB PPU: 25,142 CYC:16156\n$E83E:A9 00     LDA #$00                           A:A5 X:AF Y:E5 P:67 SP:FB PPU: 34,142 CYC:16159\n$E840:8D 56 05  STA $0556 = #$00                     A:00 X:AF Y:E5 P:67 SP:FB PPU: 40,142 CYC:16161\n$E843:A0 58     LDY #$58                           A:00 X:AF Y:E5 P:67 SP:FB PPU: 52,142 CYC:16165\n$E845:A2 B3     LDX #$B3                           A:00 X:AF Y:58 P:65 SP:FB PPU: 58,142 CYC:16167\n$E847:A9 97     LDA #$97                           A:00 X:B3 Y:58 P:E5 SP:FB PPU: 64,142 CYC:16169\n$E849:38        SEC                                A:97 X:B3 Y:58 P:E5 SP:FB PPU: 70,142 CYC:16171\n$E84A:B8        CLV                                A:97 X:B3 Y:58 P:E5 SP:FB PPU: 76,142 CYC:16173\n$E84B:8F 56 05 *SAX $0556 = #$00                     A:97 X:B3 Y:58 P:A5 SP:FB PPU: 82,142 CYC:16175\n$E84E:EA        NOP                                A:97 X:B3 Y:58 P:A5 SP:FB PPU: 94,142 CYC:16179\n$E84F:EA        NOP                                A:97 X:B3 Y:58 P:A5 SP:FB PPU:100,142 CYC:16181\n$E850:EA        NOP                                A:97 X:B3 Y:58 P:A5 SP:FB PPU:106,142 CYC:16183\n$E851:EA        NOP                                A:97 X:B3 Y:58 P:A5 SP:FB PPU:112,142 CYC:16185\n$E852:F0 19     BEQ $E86D                          A:97 X:B3 Y:58 P:A5 SP:FB PPU:118,142 CYC:16187\n$E854:90 17     BCC $E86D                          A:97 X:B3 Y:58 P:A5 SP:FB PPU:124,142 CYC:16189\n$E856:70 15     BVS $E86D                          A:97 X:B3 Y:58 P:A5 SP:FB PPU:130,142 CYC:16191\n$E858:10 13     BPL $E86D                          A:97 X:B3 Y:58 P:A5 SP:FB PPU:136,142 CYC:16193\n$E85A:C9 97     CMP #$97                           A:97 X:B3 Y:58 P:A5 SP:FB PPU:142,142 CYC:16195\n$E85C:D0 0F     BNE $E86D                          A:97 X:B3 Y:58 P:nvUbdIZC SP:FB PPU:148,142 CYC:16197\n$E85E:C0 58     CPY #$58                           A:97 X:B3 Y:58 P:nvUbdIZC SP:FB PPU:154,142 CYC:16199\n$E860:D0 0B     BNE $E86D                          A:97 X:B3 Y:58 P:nvUbdIZC SP:FB PPU:160,142 CYC:16201\n$E862:E0 B3     CPX #$B3                           A:97 X:B3 Y:58 P:nvUbdIZC SP:FB PPU:166,142 CYC:16203\n$E864:D0 07     BNE $E86D                          A:97 X:B3 Y:58 P:nvUbdIZC SP:FB PPU:172,142 CYC:16205\n$E866:AD 56 05  LDA $0556 = #$93                     A:97 X:B3 Y:58 P:nvUbdIZC SP:FB PPU:178,142 CYC:16207\n$E869:C9 93     CMP #$93                           A:93 X:B3 Y:58 P:A5 SP:FB PPU:190,142 CYC:16211\n$E86B:F0 04     BEQ $E871                          A:93 X:B3 Y:58 P:nvUbdIZC SP:FB PPU:196,142 CYC:16213\n$E871:A9 FF     LDA #$FF                           A:93 X:B3 Y:58 P:nvUbdIZC SP:FB PPU:205,142 CYC:16216\n$E873:85 49     STA $49 = #$00                       A:FF X:B3 Y:58 P:A5 SP:FB PPU:211,142 CYC:16218\n$E875:A0 FF     LDY #$FF                           A:FF X:B3 Y:58 P:A5 SP:FB PPU:220,142 CYC:16221\n$E877:A2 AA     LDX #$AA                           A:FF X:B3 Y:FF P:A5 SP:FB PPU:226,142 CYC:16223\n$E879:A9 55     LDA #$55                           A:FF X:AA Y:FF P:A5 SP:FB PPU:232,142 CYC:16225\n$E87B:24 01     BIT $01 = #$C0                       A:55 X:AA Y:FF P:25 SP:FB PPU:238,142 CYC:16227\n$E87D:18        CLC                                A:55 X:AA Y:FF P:E5 SP:FB PPU:247,142 CYC:16230\n$E87E:97 4A    *SAX $4A,Y @ 49 = #$FF                A:55 X:AA Y:FF P:NVUbdIzc SP:FB PPU:253,142 CYC:16232\n$E880:EA        NOP                                A:55 X:AA Y:FF P:NVUbdIzc SP:FB PPU:265,142 CYC:16236\n$E881:EA        NOP                                A:55 X:AA Y:FF P:NVUbdIzc SP:FB PPU:271,142 CYC:16238\n$E882:EA        NOP                                A:55 X:AA Y:FF P:NVUbdIzc SP:FB PPU:277,142 CYC:16240\n$E883:EA        NOP                                A:55 X:AA Y:FF P:NVUbdIzc SP:FB PPU:283,142 CYC:16242\n$E884:F0 18     BEQ $E89E                          A:55 X:AA Y:FF P:NVUbdIzc SP:FB PPU:289,142 CYC:16244\n$E886:B0 16     BCS $E89E                          A:55 X:AA Y:FF P:NVUbdIzc SP:FB PPU:295,142 CYC:16246\n$E888:50 14     BVC $E89E                          A:55 X:AA Y:FF P:NVUbdIzc SP:FB PPU:301,142 CYC:16248\n$E88A:10 12     BPL $E89E                          A:55 X:AA Y:FF P:NVUbdIzc SP:FB PPU:307,142 CYC:16250\n$E88C:C9 55     CMP #$55                           A:55 X:AA Y:FF P:NVUbdIzc SP:FB PPU:313,142 CYC:16252\n$E88E:D0 0E     BNE $E89E                          A:55 X:AA Y:FF P:67 SP:FB PPU:319,142 CYC:16254\n$E890:C0 FF     CPY #$FF                           A:55 X:AA Y:FF P:67 SP:FB PPU:325,142 CYC:16256\n$E892:D0 0A     BNE $E89E                          A:55 X:AA Y:FF P:67 SP:FB PPU:331,142 CYC:16258\n$E894:E0 AA     CPX #$AA                           A:55 X:AA Y:FF P:67 SP:FB PPU:337,142 CYC:16260\n$E896:D0 06     BNE $E89E                          A:55 X:AA Y:FF P:67 SP:FB PPU:  2,143 CYC:16262\n$E898:A5 49     LDA $49 = #$00                       A:55 X:AA Y:FF P:67 SP:FB PPU:  8,143 CYC:16264\n$E89A:C9 00     CMP #$00                           A:00 X:AA Y:FF P:67 SP:FB PPU: 17,143 CYC:16267\n$E89C:F0 04     BEQ $E8A2                          A:00 X:AA Y:FF P:67 SP:FB PPU: 23,143 CYC:16269\n$E8A2:A9 00     LDA #$00                           A:00 X:AA Y:FF P:67 SP:FB PPU: 32,143 CYC:16272\n$E8A4:85 56     STA $56 = #$66                       A:00 X:AA Y:FF P:67 SP:FB PPU: 38,143 CYC:16274\n$E8A6:A0 06     LDY #$06                           A:00 X:AA Y:FF P:67 SP:FB PPU: 47,143 CYC:16277\n$E8A8:A2 EF     LDX #$EF                           A:00 X:AA Y:06 P:65 SP:FB PPU: 53,143 CYC:16279\n$E8AA:A9 66     LDA #$66                           A:00 X:EF Y:06 P:E5 SP:FB PPU: 59,143 CYC:16281\n$E8AC:38        SEC                                A:66 X:EF Y:06 P:65 SP:FB PPU: 65,143 CYC:16283\n$E8AD:B8        CLV                                A:66 X:EF Y:06 P:65 SP:FB PPU: 71,143 CYC:16285\n$E8AE:97 50    *SAX $50,Y @ 56 = #$00                A:66 X:EF Y:06 P:25 SP:FB PPU: 77,143 CYC:16287\n$E8B0:EA        NOP                                A:66 X:EF Y:06 P:25 SP:FB PPU: 89,143 CYC:16291\n$E8B1:EA        NOP                                A:66 X:EF Y:06 P:25 SP:FB PPU: 95,143 CYC:16293\n$E8B2:EA        NOP                                A:66 X:EF Y:06 P:25 SP:FB PPU:101,143 CYC:16295\n$E8B3:EA        NOP                                A:66 X:EF Y:06 P:25 SP:FB PPU:107,143 CYC:16297\n$E8B4:F0 18     BEQ $E8CE                          A:66 X:EF Y:06 P:25 SP:FB PPU:113,143 CYC:16299\n$E8B6:90 16     BCC $E8CE                          A:66 X:EF Y:06 P:25 SP:FB PPU:119,143 CYC:16301\n$E8B8:70 14     BVS $E8CE                          A:66 X:EF Y:06 P:25 SP:FB PPU:125,143 CYC:16303\n$E8BA:30 12     BMI $E8CE                          A:66 X:EF Y:06 P:25 SP:FB PPU:131,143 CYC:16305\n$E8BC:C9 66     CMP #$66                           A:66 X:EF Y:06 P:25 SP:FB PPU:137,143 CYC:16307\n$E8BE:D0 0E     BNE $E8CE                          A:66 X:EF Y:06 P:nvUbdIZC SP:FB PPU:143,143 CYC:16309\n$E8C0:C0 06     CPY #$06                           A:66 X:EF Y:06 P:nvUbdIZC SP:FB PPU:149,143 CYC:16311\n$E8C2:D0 0A     BNE $E8CE                          A:66 X:EF Y:06 P:nvUbdIZC SP:FB PPU:155,143 CYC:16313\n$E8C4:E0 EF     CPX #$EF                           A:66 X:EF Y:06 P:nvUbdIZC SP:FB PPU:161,143 CYC:16315\n$E8C6:D0 06     BNE $E8CE                          A:66 X:EF Y:06 P:nvUbdIZC SP:FB PPU:167,143 CYC:16317\n$E8C8:A5 56     LDA $56 = #$66                       A:66 X:EF Y:06 P:nvUbdIZC SP:FB PPU:173,143 CYC:16319\n$E8CA:C9 66     CMP #$66                           A:66 X:EF Y:06 P:25 SP:FB PPU:182,143 CYC:16322\n$E8CC:F0 04     BEQ $E8D2                          A:66 X:EF Y:06 P:nvUbdIZC SP:FB PPU:188,143 CYC:16324\n$E8D2:60        RTS                                A:66 X:EF Y:06 P:nvUbdIZC SP:FB PPU:197,143 CYC:16327\n$C638:20 D3 E8  JSR $E8D3                          A:66 X:EF Y:06 P:nvUbdIZC SP:FD PPU:215,143 CYC:16333\n$E8D3:A0 90     LDY #$90                           A:66 X:EF Y:06 P:nvUbdIZC SP:FB PPU:233,143 CYC:16339\n$E8D5:20 31 F9  JSR $F931                          A:66 X:EF Y:90 P:A5 SP:FB PPU:239,143 CYC:16341\n$F931:24 01     BIT $01 = #$C0                       A:66 X:EF Y:90 P:A5 SP:F9 PPU:257,143 CYC:16347\n$F933:A9 40     LDA #$40                           A:66 X:EF Y:90 P:E5 SP:F9 PPU:266,143 CYC:16350\n$F935:38        SEC                                A:40 X:EF Y:90 P:65 SP:F9 PPU:272,143 CYC:16352\n$F936:60        RTS                                A:40 X:EF Y:90 P:65 SP:F9 PPU:278,143 CYC:16354\n$E8D8:EB 40    *SBC #$40                           A:40 X:EF Y:90 P:65 SP:FB PPU:296,143 CYC:16360\n$E8DA:EA        NOP                                A:00 X:EF Y:90 P:nvUbdIZC SP:FB PPU:302,143 CYC:16362\n$E8DB:EA        NOP                                A:00 X:EF Y:90 P:nvUbdIZC SP:FB PPU:308,143 CYC:16364\n$E8DC:EA        NOP                                A:00 X:EF Y:90 P:nvUbdIZC SP:FB PPU:314,143 CYC:16366\n$E8DD:EA        NOP                                A:00 X:EF Y:90 P:nvUbdIZC SP:FB PPU:320,143 CYC:16368\n$E8DE:20 37 F9  JSR $F937                          A:00 X:EF Y:90 P:nvUbdIZC SP:FB PPU:326,143 CYC:16370\n$F937:30 0B     BMI $F944                          A:00 X:EF Y:90 P:nvUbdIZC SP:F9 PPU:  3,144 CYC:16376\n$F939:90 09     BCC $F944                          A:00 X:EF Y:90 P:nvUbdIZC SP:F9 PPU:  9,144 CYC:16378\n$F93B:D0 07     BNE $F944                          A:00 X:EF Y:90 P:nvUbdIZC SP:F9 PPU: 15,144 CYC:16380\n$F93D:70 05     BVS $F944                          A:00 X:EF Y:90 P:nvUbdIZC SP:F9 PPU: 21,144 CYC:16382\n$F93F:C9 00     CMP #$00                           A:00 X:EF Y:90 P:nvUbdIZC SP:F9 PPU: 27,144 CYC:16384\n$F941:D0 01     BNE $F944                          A:00 X:EF Y:90 P:nvUbdIZC SP:F9 PPU: 33,144 CYC:16386\n$F943:60        RTS                                A:00 X:EF Y:90 P:nvUbdIZC SP:F9 PPU: 39,144 CYC:16388\n$E8E1:C8        INY                                A:00 X:EF Y:90 P:nvUbdIZC SP:FB PPU: 57,144 CYC:16394\n$E8E2:20 47 F9  JSR $F947                          A:00 X:EF Y:91 P:A5 SP:FB PPU: 63,144 CYC:16396\n$F947:B8        CLV                                A:00 X:EF Y:91 P:A5 SP:F9 PPU: 81,144 CYC:16402\n$F948:38        SEC                                A:00 X:EF Y:91 P:A5 SP:F9 PPU: 87,144 CYC:16404\n$F949:A9 40     LDA #$40                           A:00 X:EF Y:91 P:A5 SP:F9 PPU: 93,144 CYC:16406\n$F94B:60        RTS                                A:40 X:EF Y:91 P:25 SP:F9 PPU: 99,144 CYC:16408\n$E8E5:EB 3F    *SBC #$3F                           A:40 X:EF Y:91 P:25 SP:FB PPU:117,144 CYC:16414\n$E8E7:EA        NOP                                A:01 X:EF Y:91 P:25 SP:FB PPU:123,144 CYC:16416\n$E8E8:EA        NOP                                A:01 X:EF Y:91 P:25 SP:FB PPU:129,144 CYC:16418\n$E8E9:EA        NOP                                A:01 X:EF Y:91 P:25 SP:FB PPU:135,144 CYC:16420\n$E8EA:EA        NOP                                A:01 X:EF Y:91 P:25 SP:FB PPU:141,144 CYC:16422\n$E8EB:20 4C F9  JSR $F94C                          A:01 X:EF Y:91 P:25 SP:FB PPU:147,144 CYC:16424\n$F94C:F0 0B     BEQ $F959                          A:01 X:EF Y:91 P:25 SP:F9 PPU:165,144 CYC:16430\n$F94E:30 09     BMI $F959                          A:01 X:EF Y:91 P:25 SP:F9 PPU:171,144 CYC:16432\n$F950:90 07     BCC $F959                          A:01 X:EF Y:91 P:25 SP:F9 PPU:177,144 CYC:16434\n$F952:70 05     BVS $F959                          A:01 X:EF Y:91 P:25 SP:F9 PPU:183,144 CYC:16436\n$F954:C9 01     CMP #$01                           A:01 X:EF Y:91 P:25 SP:F9 PPU:189,144 CYC:16438\n$F956:D0 01     BNE $F959                          A:01 X:EF Y:91 P:nvUbdIZC SP:F9 PPU:195,144 CYC:16440\n$F958:60        RTS                                A:01 X:EF Y:91 P:nvUbdIZC SP:F9 PPU:201,144 CYC:16442\n$E8EE:C8        INY                                A:01 X:EF Y:91 P:nvUbdIZC SP:FB PPU:219,144 CYC:16448\n$E8EF:20 5C F9  JSR $F95C                          A:01 X:EF Y:92 P:A5 SP:FB PPU:225,144 CYC:16450\n$F95C:A9 40     LDA #$40                           A:01 X:EF Y:92 P:A5 SP:F9 PPU:243,144 CYC:16456\n$F95E:38        SEC                                A:40 X:EF Y:92 P:25 SP:F9 PPU:249,144 CYC:16458\n$F95F:24 01     BIT $01 = #$C0                       A:40 X:EF Y:92 P:25 SP:F9 PPU:255,144 CYC:16460\n$F961:60        RTS                                A:40 X:EF Y:92 P:E5 SP:F9 PPU:264,144 CYC:16463\n$E8F2:EB 41    *SBC #$41                           A:40 X:EF Y:92 P:E5 SP:FB PPU:282,144 CYC:16469\n$E8F4:EA        NOP                                A:FF X:EF Y:92 P:NvUbdIzc SP:FB PPU:288,144 CYC:16471\n$E8F5:EA        NOP                                A:FF X:EF Y:92 P:NvUbdIzc SP:FB PPU:294,144 CYC:16473\n$E8F6:EA        NOP                                A:FF X:EF Y:92 P:NvUbdIzc SP:FB PPU:300,144 CYC:16475\n$E8F7:EA        NOP                                A:FF X:EF Y:92 P:NvUbdIzc SP:FB PPU:306,144 CYC:16477\n$E8F8:20 62 F9  JSR $F962                          A:FF X:EF Y:92 P:NvUbdIzc SP:FB PPU:312,144 CYC:16479\n$F962:B0 0B     BCS $F96F                          A:FF X:EF Y:92 P:NvUbdIzc SP:F9 PPU:330,144 CYC:16485\n$F964:F0 09     BEQ $F96F                          A:FF X:EF Y:92 P:NvUbdIzc SP:F9 PPU:336,144 CYC:16487\n$F966:10 07     BPL $F96F                          A:FF X:EF Y:92 P:NvUbdIzc SP:F9 PPU:  1,145 CYC:16489\n$F968:70 05     BVS $F96F                          A:FF X:EF Y:92 P:NvUbdIzc SP:F9 PPU:  7,145 CYC:16491\n$F96A:C9 FF     CMP #$FF                           A:FF X:EF Y:92 P:NvUbdIzc SP:F9 PPU: 13,145 CYC:16493\n$F96C:D0 01     BNE $F96F                          A:FF X:EF Y:92 P:nvUbdIZC SP:F9 PPU: 19,145 CYC:16495\n$F96E:60        RTS                                A:FF X:EF Y:92 P:nvUbdIZC SP:F9 PPU: 25,145 CYC:16497\n$E8FB:C8        INY                                A:FF X:EF Y:92 P:nvUbdIZC SP:FB PPU: 43,145 CYC:16503\n$E8FC:20 72 F9  JSR $F972                          A:FF X:EF Y:93 P:A5 SP:FB PPU: 49,145 CYC:16505\n$F972:18        CLC                                A:FF X:EF Y:93 P:A5 SP:F9 PPU: 67,145 CYC:16511\n$F973:A9 80     LDA #$80                           A:FF X:EF Y:93 P:NvUbdIzc SP:F9 PPU: 73,145 CYC:16513\n$F975:60        RTS                                A:80 X:EF Y:93 P:NvUbdIzc SP:F9 PPU: 79,145 CYC:16515\n$E8FF:EB 00    *SBC #$00                           A:80 X:EF Y:93 P:NvUbdIzc SP:FB PPU: 97,145 CYC:16521\n$E901:EA        NOP                                A:7F X:EF Y:93 P:65 SP:FB PPU:103,145 CYC:16523\n$E902:EA        NOP                                A:7F X:EF Y:93 P:65 SP:FB PPU:109,145 CYC:16525\n$E903:EA        NOP                                A:7F X:EF Y:93 P:65 SP:FB PPU:115,145 CYC:16527\n$E904:EA        NOP                                A:7F X:EF Y:93 P:65 SP:FB PPU:121,145 CYC:16529\n$E905:20 76 F9  JSR $F976                          A:7F X:EF Y:93 P:65 SP:FB PPU:127,145 CYC:16531\n$F976:90 05     BCC $F97D                          A:7F X:EF Y:93 P:65 SP:F9 PPU:145,145 CYC:16537\n$F978:C9 7F     CMP #$7F                           A:7F X:EF Y:93 P:65 SP:F9 PPU:151,145 CYC:16539\n$F97A:D0 01     BNE $F97D                          A:7F X:EF Y:93 P:67 SP:F9 PPU:157,145 CYC:16541\n$F97C:60        RTS                                A:7F X:EF Y:93 P:67 SP:F9 PPU:163,145 CYC:16543\n$E908:C8        INY                                A:7F X:EF Y:93 P:67 SP:FB PPU:181,145 CYC:16549\n$E909:20 80 F9  JSR $F980                          A:7F X:EF Y:94 P:E5 SP:FB PPU:187,145 CYC:16551\n$F980:38        SEC                                A:7F X:EF Y:94 P:E5 SP:F9 PPU:205,145 CYC:16557\n$F981:A9 81     LDA #$81                           A:7F X:EF Y:94 P:E5 SP:F9 PPU:211,145 CYC:16559\n$F983:60        RTS                                A:81 X:EF Y:94 P:E5 SP:F9 PPU:217,145 CYC:16561\n$E90C:EB 7F    *SBC #$7F                           A:81 X:EF Y:94 P:E5 SP:FB PPU:235,145 CYC:16567\n$E90E:EA        NOP                                A:02 X:EF Y:94 P:65 SP:FB PPU:241,145 CYC:16569\n$E90F:EA        NOP                                A:02 X:EF Y:94 P:65 SP:FB PPU:247,145 CYC:16571\n$E910:EA        NOP                                A:02 X:EF Y:94 P:65 SP:FB PPU:253,145 CYC:16573\n$E911:EA        NOP                                A:02 X:EF Y:94 P:65 SP:FB PPU:259,145 CYC:16575\n$E912:20 84 F9  JSR $F984                          A:02 X:EF Y:94 P:65 SP:FB PPU:265,145 CYC:16577\n$F984:50 07     BVC $F98D                          A:02 X:EF Y:94 P:65 SP:F9 PPU:283,145 CYC:16583\n$F986:90 05     BCC $F98D                          A:02 X:EF Y:94 P:65 SP:F9 PPU:289,145 CYC:16585\n$F988:C9 02     CMP #$02                           A:02 X:EF Y:94 P:65 SP:F9 PPU:295,145 CYC:16587\n$F98A:D0 01     BNE $F98D                          A:02 X:EF Y:94 P:67 SP:F9 PPU:301,145 CYC:16589\n$F98C:60        RTS                                A:02 X:EF Y:94 P:67 SP:F9 PPU:307,145 CYC:16591\n$E915:60        RTS                                A:02 X:EF Y:94 P:67 SP:FB PPU:325,145 CYC:16597\n$C63B:20 16 E9  JSR $E916                          A:02 X:EF Y:94 P:67 SP:FD PPU:  2,146 CYC:16603\n$E916:A9 FF     LDA #$FF                           A:02 X:EF Y:94 P:67 SP:FB PPU: 20,146 CYC:16609\n$E918:85 01     STA $01 = #$C0                       A:FF X:EF Y:94 P:E5 SP:FB PPU: 26,146 CYC:16611\n$E91A:A0 95     LDY #$95                           A:FF X:EF Y:94 P:E5 SP:FB PPU: 35,146 CYC:16614\n$E91C:A2 02     LDX #$02                           A:FF X:EF Y:95 P:E5 SP:FB PPU: 41,146 CYC:16616\n$E91E:A9 47     LDA #$47                           A:FF X:02 Y:95 P:65 SP:FB PPU: 47,146 CYC:16618\n$E920:85 47     STA $47 = #$00                       A:47 X:02 Y:95 P:65 SP:FB PPU: 53,146 CYC:16620\n$E922:A9 06     LDA #$06                           A:47 X:02 Y:95 P:65 SP:FB PPU: 62,146 CYC:16623\n$E924:85 48     STA $48 = #$00                       A:06 X:02 Y:95 P:65 SP:FB PPU: 68,146 CYC:16625\n$E926:A9 EB     LDA #$EB                           A:06 X:02 Y:95 P:65 SP:FB PPU: 77,146 CYC:16628\n$E928:8D 47 06  STA $0647 = #$00                     A:EB X:02 Y:95 P:E5 SP:FB PPU: 83,146 CYC:16630\n$E92B:20 31 FA  JSR $FA31                          A:EB X:02 Y:95 P:E5 SP:FB PPU: 95,146 CYC:16634\n$FA31:24 01     BIT $01 = #$FF                       A:EB X:02 Y:95 P:E5 SP:F9 PPU:113,146 CYC:16640\n$FA33:18        CLC                                A:EB X:02 Y:95 P:E5 SP:F9 PPU:122,146 CYC:16643\n$FA34:A9 40     LDA #$40                           A:EB X:02 Y:95 P:NVUbdIzc SP:F9 PPU:128,146 CYC:16645\n$FA36:60        RTS                                A:40 X:02 Y:95 P:64 SP:F9 PPU:134,146 CYC:16647\n$E92E:C3 45    *DCP ($45,X) @ 47 = #$0647 = EB       A:40 X:02 Y:95 P:64 SP:FB PPU:152,146 CYC:16653\n$E930:EA        NOP                                A:40 X:02 Y:95 P:64 SP:FB PPU:176,146 CYC:16661\n$E931:EA        NOP                                A:40 X:02 Y:95 P:64 SP:FB PPU:182,146 CYC:16663\n$E932:EA        NOP                                A:40 X:02 Y:95 P:64 SP:FB PPU:188,146 CYC:16665\n$E933:EA        NOP                                A:40 X:02 Y:95 P:64 SP:FB PPU:194,146 CYC:16667\n$E934:20 37 FA  JSR $FA37                          A:40 X:02 Y:95 P:64 SP:FB PPU:200,146 CYC:16669\n$FA37:50 2C     BVC $FA65                          A:40 X:02 Y:95 P:64 SP:F9 PPU:218,146 CYC:16675\n$FA39:B0 2A     BCS $FA65                          A:40 X:02 Y:95 P:64 SP:F9 PPU:224,146 CYC:16677\n$FA3B:30 28     BMI $FA65                          A:40 X:02 Y:95 P:64 SP:F9 PPU:230,146 CYC:16679\n$FA3D:C9 40     CMP #$40                           A:40 X:02 Y:95 P:64 SP:F9 PPU:236,146 CYC:16681\n$FA3F:D0 24     BNE $FA65                          A:40 X:02 Y:95 P:67 SP:F9 PPU:242,146 CYC:16683\n$FA41:60        RTS                                A:40 X:02 Y:95 P:67 SP:F9 PPU:248,146 CYC:16685\n$E937:AD 47 06  LDA $0647 = #$EA                     A:40 X:02 Y:95 P:67 SP:FB PPU:266,146 CYC:16691\n$E93A:C9 EA     CMP #$EA                           A:EA X:02 Y:95 P:E5 SP:FB PPU:278,146 CYC:16695\n$E93C:F0 02     BEQ $E940                          A:EA X:02 Y:95 P:67 SP:FB PPU:284,146 CYC:16697\n$E940:C8        INY                                A:EA X:02 Y:95 P:67 SP:FB PPU:293,146 CYC:16700\n$E941:A9 00     LDA #$00                           A:EA X:02 Y:96 P:E5 SP:FB PPU:299,146 CYC:16702\n$E943:8D 47 06  STA $0647 = #$EA                     A:00 X:02 Y:96 P:67 SP:FB PPU:305,146 CYC:16704\n$E946:20 42 FA  JSR $FA42                          A:00 X:02 Y:96 P:67 SP:FB PPU:317,146 CYC:16708\n$FA42:B8        CLV                                A:00 X:02 Y:96 P:67 SP:F9 PPU:335,146 CYC:16714\n$FA43:38        SEC                                A:00 X:02 Y:96 P:nvUbdIZC SP:F9 PPU:  0,147 CYC:16716\n$FA44:A9 FF     LDA #$FF                           A:00 X:02 Y:96 P:nvUbdIZC SP:F9 PPU:  6,147 CYC:16718\n$FA46:60        RTS                                A:FF X:02 Y:96 P:A5 SP:F9 PPU: 12,147 CYC:16720\n$E949:C3 45    *DCP ($45,X) @ 47 = #$0647 = 00       A:FF X:02 Y:96 P:A5 SP:FB PPU: 30,147 CYC:16726\n$E94B:EA        NOP                                A:FF X:02 Y:96 P:nvUbdIZC SP:FB PPU: 54,147 CYC:16734\n$E94C:EA        NOP                                A:FF X:02 Y:96 P:nvUbdIZC SP:FB PPU: 60,147 CYC:16736\n$E94D:EA        NOP                                A:FF X:02 Y:96 P:nvUbdIZC SP:FB PPU: 66,147 CYC:16738\n$E94E:EA        NOP                                A:FF X:02 Y:96 P:nvUbdIZC SP:FB PPU: 72,147 CYC:16740\n$E94F:20 47 FA  JSR $FA47                          A:FF X:02 Y:96 P:nvUbdIZC SP:FB PPU: 78,147 CYC:16742\n$FA47:70 1C     BVS $FA65                          A:FF X:02 Y:96 P:nvUbdIZC SP:F9 PPU: 96,147 CYC:16748\n$FA49:D0 1A     BNE $FA65                          A:FF X:02 Y:96 P:nvUbdIZC SP:F9 PPU:102,147 CYC:16750\n$FA4B:30 18     BMI $FA65                          A:FF X:02 Y:96 P:nvUbdIZC SP:F9 PPU:108,147 CYC:16752\n$FA4D:90 16     BCC $FA65                          A:FF X:02 Y:96 P:nvUbdIZC SP:F9 PPU:114,147 CYC:16754\n$FA4F:C9 FF     CMP #$FF                           A:FF X:02 Y:96 P:nvUbdIZC SP:F9 PPU:120,147 CYC:16756\n$FA51:D0 12     BNE $FA65                          A:FF X:02 Y:96 P:nvUbdIZC SP:F9 PPU:126,147 CYC:16758\n$FA53:60        RTS                                A:FF X:02 Y:96 P:nvUbdIZC SP:F9 PPU:132,147 CYC:16760\n$E952:AD 47 06  LDA $0647 = #$FF                     A:FF X:02 Y:96 P:nvUbdIZC SP:FB PPU:150,147 CYC:16766\n$E955:C9 FF     CMP #$FF                           A:FF X:02 Y:96 P:A5 SP:FB PPU:162,147 CYC:16770\n$E957:F0 02     BEQ $E95B                          A:FF X:02 Y:96 P:nvUbdIZC SP:FB PPU:168,147 CYC:16772\n$E95B:C8        INY                                A:FF X:02 Y:96 P:nvUbdIZC SP:FB PPU:177,147 CYC:16775\n$E95C:A9 37     LDA #$37                           A:FF X:02 Y:97 P:A5 SP:FB PPU:183,147 CYC:16777\n$E95E:8D 47 06  STA $0647 = #$FF                     A:37 X:02 Y:97 P:25 SP:FB PPU:189,147 CYC:16779\n$E961:20 54 FA  JSR $FA54                          A:37 X:02 Y:97 P:25 SP:FB PPU:201,147 CYC:16783\n$FA54:24 01     BIT $01 = #$FF                       A:37 X:02 Y:97 P:25 SP:F9 PPU:219,147 CYC:16789\n$FA56:A9 F0     LDA #$F0                           A:37 X:02 Y:97 P:E5 SP:F9 PPU:228,147 CYC:16792\n$FA58:60        RTS                                A:F0 X:02 Y:97 P:E5 SP:F9 PPU:234,147 CYC:16794\n$E964:C3 45    *DCP ($45,X) @ 47 = #$0647 = 37       A:F0 X:02 Y:97 P:E5 SP:FB PPU:252,147 CYC:16800\n$E966:EA        NOP                                A:F0 X:02 Y:97 P:E5 SP:FB PPU:276,147 CYC:16808\n$E967:EA        NOP                                A:F0 X:02 Y:97 P:E5 SP:FB PPU:282,147 CYC:16810\n$E968:EA        NOP                                A:F0 X:02 Y:97 P:E5 SP:FB PPU:288,147 CYC:16812\n$E969:EA        NOP                                A:F0 X:02 Y:97 P:E5 SP:FB PPU:294,147 CYC:16814\n$E96A:20 59 FA  JSR $FA59                          A:F0 X:02 Y:97 P:E5 SP:FB PPU:300,147 CYC:16816\n$FA59:50 0A     BVC $FA65                          A:F0 X:02 Y:97 P:E5 SP:F9 PPU:318,147 CYC:16822\n$FA5B:F0 08     BEQ $FA65                          A:F0 X:02 Y:97 P:E5 SP:F9 PPU:324,147 CYC:16824\n$FA5D:10 06     BPL $FA65                          A:F0 X:02 Y:97 P:E5 SP:F9 PPU:330,147 CYC:16826\n$FA5F:90 04     BCC $FA65                          A:F0 X:02 Y:97 P:E5 SP:F9 PPU:336,147 CYC:16828\n$FA61:C9 F0     CMP #$F0                           A:F0 X:02 Y:97 P:E5 SP:F9 PPU:  1,148 CYC:16830\n$FA63:F0 02     BEQ $FA67                          A:F0 X:02 Y:97 P:67 SP:F9 PPU:  7,148 CYC:16832\n$FA67:60        RTS                                A:F0 X:02 Y:97 P:67 SP:F9 PPU: 16,148 CYC:16835\n$E96D:AD 47 06  LDA $0647 = #$36                     A:F0 X:02 Y:97 P:67 SP:FB PPU: 34,148 CYC:16841\n$E970:C9 36     CMP #$36                           A:36 X:02 Y:97 P:65 SP:FB PPU: 46,148 CYC:16845\n$E972:F0 02     BEQ $E976                          A:36 X:02 Y:97 P:67 SP:FB PPU: 52,148 CYC:16847\n$E976:C8        INY                                A:36 X:02 Y:97 P:67 SP:FB PPU: 61,148 CYC:16850\n$E977:A9 EB     LDA #$EB                           A:36 X:02 Y:98 P:E5 SP:FB PPU: 67,148 CYC:16852\n$E979:85 47     STA $47 = #$47                       A:EB X:02 Y:98 P:E5 SP:FB PPU: 73,148 CYC:16854\n$E97B:20 31 FA  JSR $FA31                          A:EB X:02 Y:98 P:E5 SP:FB PPU: 82,148 CYC:16857\n$FA31:24 01     BIT $01 = #$FF                       A:EB X:02 Y:98 P:E5 SP:F9 PPU:100,148 CYC:16863\n$FA33:18        CLC                                A:EB X:02 Y:98 P:E5 SP:F9 PPU:109,148 CYC:16866\n$FA34:A9 40     LDA #$40                           A:EB X:02 Y:98 P:NVUbdIzc SP:F9 PPU:115,148 CYC:16868\n$FA36:60        RTS                                A:40 X:02 Y:98 P:64 SP:F9 PPU:121,148 CYC:16870\n$E97E:C7 47    *DCP $47 = #$EB                       A:40 X:02 Y:98 P:64 SP:FB PPU:139,148 CYC:16876\n$E980:EA        NOP                                A:40 X:02 Y:98 P:64 SP:FB PPU:154,148 CYC:16881\n$E981:EA        NOP                                A:40 X:02 Y:98 P:64 SP:FB PPU:160,148 CYC:16883\n$E982:EA        NOP                                A:40 X:02 Y:98 P:64 SP:FB PPU:166,148 CYC:16885\n$E983:EA        NOP                                A:40 X:02 Y:98 P:64 SP:FB PPU:172,148 CYC:16887\n$E984:20 37 FA  JSR $FA37                          A:40 X:02 Y:98 P:64 SP:FB PPU:178,148 CYC:16889\n$FA37:50 2C     BVC $FA65                          A:40 X:02 Y:98 P:64 SP:F9 PPU:196,148 CYC:16895\n$FA39:B0 2A     BCS $FA65                          A:40 X:02 Y:98 P:64 SP:F9 PPU:202,148 CYC:16897\n$FA3B:30 28     BMI $FA65                          A:40 X:02 Y:98 P:64 SP:F9 PPU:208,148 CYC:16899\n$FA3D:C9 40     CMP #$40                           A:40 X:02 Y:98 P:64 SP:F9 PPU:214,148 CYC:16901\n$FA3F:D0 24     BNE $FA65                          A:40 X:02 Y:98 P:67 SP:F9 PPU:220,148 CYC:16903\n$FA41:60        RTS                                A:40 X:02 Y:98 P:67 SP:F9 PPU:226,148 CYC:16905\n$E987:A5 47     LDA $47 = #$EA                       A:40 X:02 Y:98 P:67 SP:FB PPU:244,148 CYC:16911\n$E989:C9 EA     CMP #$EA                           A:EA X:02 Y:98 P:E5 SP:FB PPU:253,148 CYC:16914\n$E98B:F0 02     BEQ $E98F                          A:EA X:02 Y:98 P:67 SP:FB PPU:259,148 CYC:16916\n$E98F:C8        INY                                A:EA X:02 Y:98 P:67 SP:FB PPU:268,148 CYC:16919\n$E990:A9 00     LDA #$00                           A:EA X:02 Y:99 P:E5 SP:FB PPU:274,148 CYC:16921\n$E992:85 47     STA $47 = #$EA                       A:00 X:02 Y:99 P:67 SP:FB PPU:280,148 CYC:16923\n$E994:20 42 FA  JSR $FA42                          A:00 X:02 Y:99 P:67 SP:FB PPU:289,148 CYC:16926\n$FA42:B8        CLV                                A:00 X:02 Y:99 P:67 SP:F9 PPU:307,148 CYC:16932\n$FA43:38        SEC                                A:00 X:02 Y:99 P:nvUbdIZC SP:F9 PPU:313,148 CYC:16934\n$FA44:A9 FF     LDA #$FF                           A:00 X:02 Y:99 P:nvUbdIZC SP:F9 PPU:319,148 CYC:16936\n$FA46:60        RTS                                A:FF X:02 Y:99 P:A5 SP:F9 PPU:325,148 CYC:16938\n$E997:C7 47    *DCP $47 = #$00                       A:FF X:02 Y:99 P:A5 SP:FB PPU:  2,149 CYC:16944\n$E999:EA        NOP                                A:FF X:02 Y:99 P:nvUbdIZC SP:FB PPU: 17,149 CYC:16949\n$E99A:EA        NOP                                A:FF X:02 Y:99 P:nvUbdIZC SP:FB PPU: 23,149 CYC:16951\n$E99B:EA        NOP                                A:FF X:02 Y:99 P:nvUbdIZC SP:FB PPU: 29,149 CYC:16953\n$E99C:EA        NOP                                A:FF X:02 Y:99 P:nvUbdIZC SP:FB PPU: 35,149 CYC:16955\n$E99D:20 47 FA  JSR $FA47                          A:FF X:02 Y:99 P:nvUbdIZC SP:FB PPU: 41,149 CYC:16957\n$FA47:70 1C     BVS $FA65                          A:FF X:02 Y:99 P:nvUbdIZC SP:F9 PPU: 59,149 CYC:16963\n$FA49:D0 1A     BNE $FA65                          A:FF X:02 Y:99 P:nvUbdIZC SP:F9 PPU: 65,149 CYC:16965\n$FA4B:30 18     BMI $FA65                          A:FF X:02 Y:99 P:nvUbdIZC SP:F9 PPU: 71,149 CYC:16967\n$FA4D:90 16     BCC $FA65                          A:FF X:02 Y:99 P:nvUbdIZC SP:F9 PPU: 77,149 CYC:16969\n$FA4F:C9 FF     CMP #$FF                           A:FF X:02 Y:99 P:nvUbdIZC SP:F9 PPU: 83,149 CYC:16971\n$FA51:D0 12     BNE $FA65                          A:FF X:02 Y:99 P:nvUbdIZC SP:F9 PPU: 89,149 CYC:16973\n$FA53:60        RTS                                A:FF X:02 Y:99 P:nvUbdIZC SP:F9 PPU: 95,149 CYC:16975\n$E9A0:A5 47     LDA $47 = #$FF                       A:FF X:02 Y:99 P:nvUbdIZC SP:FB PPU:113,149 CYC:16981\n$E9A2:C9 FF     CMP #$FF                           A:FF X:02 Y:99 P:A5 SP:FB PPU:122,149 CYC:16984\n$E9A4:F0 02     BEQ $E9A8                          A:FF X:02 Y:99 P:nvUbdIZC SP:FB PPU:128,149 CYC:16986\n$E9A8:C8        INY                                A:FF X:02 Y:99 P:nvUbdIZC SP:FB PPU:137,149 CYC:16989\n$E9A9:A9 37     LDA #$37                           A:FF X:02 Y:9A P:A5 SP:FB PPU:143,149 CYC:16991\n$E9AB:85 47     STA $47 = #$FF                       A:37 X:02 Y:9A P:25 SP:FB PPU:149,149 CYC:16993\n$E9AD:20 54 FA  JSR $FA54                          A:37 X:02 Y:9A P:25 SP:FB PPU:158,149 CYC:16996\n$FA54:24 01     BIT $01 = #$FF                       A:37 X:02 Y:9A P:25 SP:F9 PPU:176,149 CYC:17002\n$FA56:A9 F0     LDA #$F0                           A:37 X:02 Y:9A P:E5 SP:F9 PPU:185,149 CYC:17005\n$FA58:60        RTS                                A:F0 X:02 Y:9A P:E5 SP:F9 PPU:191,149 CYC:17007\n$E9B0:C7 47    *DCP $47 = #$37                       A:F0 X:02 Y:9A P:E5 SP:FB PPU:209,149 CYC:17013\n$E9B2:EA        NOP                                A:F0 X:02 Y:9A P:E5 SP:FB PPU:224,149 CYC:17018\n$E9B3:EA        NOP                                A:F0 X:02 Y:9A P:E5 SP:FB PPU:230,149 CYC:17020\n$E9B4:EA        NOP                                A:F0 X:02 Y:9A P:E5 SP:FB PPU:236,149 CYC:17022\n$E9B5:EA        NOP                                A:F0 X:02 Y:9A P:E5 SP:FB PPU:242,149 CYC:17024\n$E9B6:20 59 FA  JSR $FA59                          A:F0 X:02 Y:9A P:E5 SP:FB PPU:248,149 CYC:17026\n$FA59:50 0A     BVC $FA65                          A:F0 X:02 Y:9A P:E5 SP:F9 PPU:266,149 CYC:17032\n$FA5B:F0 08     BEQ $FA65                          A:F0 X:02 Y:9A P:E5 SP:F9 PPU:272,149 CYC:17034\n$FA5D:10 06     BPL $FA65                          A:F0 X:02 Y:9A P:E5 SP:F9 PPU:278,149 CYC:17036\n$FA5F:90 04     BCC $FA65                          A:F0 X:02 Y:9A P:E5 SP:F9 PPU:284,149 CYC:17038\n$FA61:C9 F0     CMP #$F0                           A:F0 X:02 Y:9A P:E5 SP:F9 PPU:290,149 CYC:17040\n$FA63:F0 02     BEQ $FA67                          A:F0 X:02 Y:9A P:67 SP:F9 PPU:296,149 CYC:17042\n$FA67:60        RTS                                A:F0 X:02 Y:9A P:67 SP:F9 PPU:305,149 CYC:17045\n$E9B9:A5 47     LDA $47 = #$36                       A:F0 X:02 Y:9A P:67 SP:FB PPU:323,149 CYC:17051\n$E9BB:C9 36     CMP #$36                           A:36 X:02 Y:9A P:65 SP:FB PPU:332,149 CYC:17054\n$E9BD:F0 02     BEQ $E9C1                          A:36 X:02 Y:9A P:67 SP:FB PPU:338,149 CYC:17056\n$E9C1:C8        INY                                A:36 X:02 Y:9A P:67 SP:FB PPU:  6,150 CYC:17059\n$E9C2:A9 EB     LDA #$EB                           A:36 X:02 Y:9B P:E5 SP:FB PPU: 12,150 CYC:17061\n$E9C4:8D 47 06  STA $0647 = #$36                     A:EB X:02 Y:9B P:E5 SP:FB PPU: 18,150 CYC:17063\n$E9C7:20 31 FA  JSR $FA31                          A:EB X:02 Y:9B P:E5 SP:FB PPU: 30,150 CYC:17067\n$FA31:24 01     BIT $01 = #$FF                       A:EB X:02 Y:9B P:E5 SP:F9 PPU: 48,150 CYC:17073\n$FA33:18        CLC                                A:EB X:02 Y:9B P:E5 SP:F9 PPU: 57,150 CYC:17076\n$FA34:A9 40     LDA #$40                           A:EB X:02 Y:9B P:NVUbdIzc SP:F9 PPU: 63,150 CYC:17078\n$FA36:60        RTS                                A:40 X:02 Y:9B P:64 SP:F9 PPU: 69,150 CYC:17080\n$E9CA:CF 47 06 *DCP $0647 = #$EB                     A:40 X:02 Y:9B P:64 SP:FB PPU: 87,150 CYC:17086\n$E9CD:EA        NOP                                A:40 X:02 Y:9B P:64 SP:FB PPU:105,150 CYC:17092\n$E9CE:EA        NOP                                A:40 X:02 Y:9B P:64 SP:FB PPU:111,150 CYC:17094\n$E9CF:EA        NOP                                A:40 X:02 Y:9B P:64 SP:FB PPU:117,150 CYC:17096\n$E9D0:EA        NOP                                A:40 X:02 Y:9B P:64 SP:FB PPU:123,150 CYC:17098\n$E9D1:20 37 FA  JSR $FA37                          A:40 X:02 Y:9B P:64 SP:FB PPU:129,150 CYC:17100\n$FA37:50 2C     BVC $FA65                          A:40 X:02 Y:9B P:64 SP:F9 PPU:147,150 CYC:17106\n$FA39:B0 2A     BCS $FA65                          A:40 X:02 Y:9B P:64 SP:F9 PPU:153,150 CYC:17108\n$FA3B:30 28     BMI $FA65                          A:40 X:02 Y:9B P:64 SP:F9 PPU:159,150 CYC:17110\n$FA3D:C9 40     CMP #$40                           A:40 X:02 Y:9B P:64 SP:F9 PPU:165,150 CYC:17112\n$FA3F:D0 24     BNE $FA65                          A:40 X:02 Y:9B P:67 SP:F9 PPU:171,150 CYC:17114\n$FA41:60        RTS                                A:40 X:02 Y:9B P:67 SP:F9 PPU:177,150 CYC:17116\n$E9D4:AD 47 06  LDA $0647 = #$EA                     A:40 X:02 Y:9B P:67 SP:FB PPU:195,150 CYC:17122\n$E9D7:C9 EA     CMP #$EA                           A:EA X:02 Y:9B P:E5 SP:FB PPU:207,150 CYC:17126\n$E9D9:F0 02     BEQ $E9DD                          A:EA X:02 Y:9B P:67 SP:FB PPU:213,150 CYC:17128\n$E9DD:C8        INY                                A:EA X:02 Y:9B P:67 SP:FB PPU:222,150 CYC:17131\n$E9DE:A9 00     LDA #$00                           A:EA X:02 Y:9C P:E5 SP:FB PPU:228,150 CYC:17133\n$E9E0:8D 47 06  STA $0647 = #$EA                     A:00 X:02 Y:9C P:67 SP:FB PPU:234,150 CYC:17135\n$E9E3:20 42 FA  JSR $FA42                          A:00 X:02 Y:9C P:67 SP:FB PPU:246,150 CYC:17139\n$FA42:B8        CLV                                A:00 X:02 Y:9C P:67 SP:F9 PPU:264,150 CYC:17145\n$FA43:38        SEC                                A:00 X:02 Y:9C P:nvUbdIZC SP:F9 PPU:270,150 CYC:17147\n$FA44:A9 FF     LDA #$FF                           A:00 X:02 Y:9C P:nvUbdIZC SP:F9 PPU:276,150 CYC:17149\n$FA46:60        RTS                                A:FF X:02 Y:9C P:A5 SP:F9 PPU:282,150 CYC:17151\n$E9E6:CF 47 06 *DCP $0647 = #$00                     A:FF X:02 Y:9C P:A5 SP:FB PPU:300,150 CYC:17157\n$E9E9:EA        NOP                                A:FF X:02 Y:9C P:nvUbdIZC SP:FB PPU:318,150 CYC:17163\n$E9EA:EA        NOP                                A:FF X:02 Y:9C P:nvUbdIZC SP:FB PPU:324,150 CYC:17165\n$E9EB:EA        NOP                                A:FF X:02 Y:9C P:nvUbdIZC SP:FB PPU:330,150 CYC:17167\n$E9EC:EA        NOP                                A:FF X:02 Y:9C P:nvUbdIZC SP:FB PPU:336,150 CYC:17169\n$E9ED:20 47 FA  JSR $FA47                          A:FF X:02 Y:9C P:nvUbdIZC SP:FB PPU:  1,151 CYC:17171\n$FA47:70 1C     BVS $FA65                          A:FF X:02 Y:9C P:nvUbdIZC SP:F9 PPU: 19,151 CYC:17177\n$FA49:D0 1A     BNE $FA65                          A:FF X:02 Y:9C P:nvUbdIZC SP:F9 PPU: 25,151 CYC:17179\n$FA4B:30 18     BMI $FA65                          A:FF X:02 Y:9C P:nvUbdIZC SP:F9 PPU: 31,151 CYC:17181\n$FA4D:90 16     BCC $FA65                          A:FF X:02 Y:9C P:nvUbdIZC SP:F9 PPU: 37,151 CYC:17183\n$FA4F:C9 FF     CMP #$FF                           A:FF X:02 Y:9C P:nvUbdIZC SP:F9 PPU: 43,151 CYC:17185\n$FA51:D0 12     BNE $FA65                          A:FF X:02 Y:9C P:nvUbdIZC SP:F9 PPU: 49,151 CYC:17187\n$FA53:60        RTS                                A:FF X:02 Y:9C P:nvUbdIZC SP:F9 PPU: 55,151 CYC:17189\n$E9F0:AD 47 06  LDA $0647 = #$FF                     A:FF X:02 Y:9C P:nvUbdIZC SP:FB PPU: 73,151 CYC:17195\n$E9F3:C9 FF     CMP #$FF                           A:FF X:02 Y:9C P:A5 SP:FB PPU: 85,151 CYC:17199\n$E9F5:F0 02     BEQ $E9F9                          A:FF X:02 Y:9C P:nvUbdIZC SP:FB PPU: 91,151 CYC:17201\n$E9F9:C8        INY                                A:FF X:02 Y:9C P:nvUbdIZC SP:FB PPU:100,151 CYC:17204\n$E9FA:A9 37     LDA #$37                           A:FF X:02 Y:9D P:A5 SP:FB PPU:106,151 CYC:17206\n$E9FC:8D 47 06  STA $0647 = #$FF                     A:37 X:02 Y:9D P:25 SP:FB PPU:112,151 CYC:17208\n$E9FF:20 54 FA  JSR $FA54                          A:37 X:02 Y:9D P:25 SP:FB PPU:124,151 CYC:17212\n$FA54:24 01     BIT $01 = #$FF                       A:37 X:02 Y:9D P:25 SP:F9 PPU:142,151 CYC:17218\n$FA56:A9 F0     LDA #$F0                           A:37 X:02 Y:9D P:E5 SP:F9 PPU:151,151 CYC:17221\n$FA58:60        RTS                                A:F0 X:02 Y:9D P:E5 SP:F9 PPU:157,151 CYC:17223\n$EA02:CF 47 06 *DCP $0647 = #$37                     A:F0 X:02 Y:9D P:E5 SP:FB PPU:175,151 CYC:17229\n$EA05:EA        NOP                                A:F0 X:02 Y:9D P:E5 SP:FB PPU:193,151 CYC:17235\n$EA06:EA        NOP                                A:F0 X:02 Y:9D P:E5 SP:FB PPU:199,151 CYC:17237\n$EA07:EA        NOP                                A:F0 X:02 Y:9D P:E5 SP:FB PPU:205,151 CYC:17239\n$EA08:EA        NOP                                A:F0 X:02 Y:9D P:E5 SP:FB PPU:211,151 CYC:17241\n$EA09:20 59 FA  JSR $FA59                          A:F0 X:02 Y:9D P:E5 SP:FB PPU:217,151 CYC:17243\n$FA59:50 0A     BVC $FA65                          A:F0 X:02 Y:9D P:E5 SP:F9 PPU:235,151 CYC:17249\n$FA5B:F0 08     BEQ $FA65                          A:F0 X:02 Y:9D P:E5 SP:F9 PPU:241,151 CYC:17251\n$FA5D:10 06     BPL $FA65                          A:F0 X:02 Y:9D P:E5 SP:F9 PPU:247,151 CYC:17253\n$FA5F:90 04     BCC $FA65                          A:F0 X:02 Y:9D P:E5 SP:F9 PPU:253,151 CYC:17255\n$FA61:C9 F0     CMP #$F0                           A:F0 X:02 Y:9D P:E5 SP:F9 PPU:259,151 CYC:17257\n$FA63:F0 02     BEQ $FA67                          A:F0 X:02 Y:9D P:67 SP:F9 PPU:265,151 CYC:17259\n$FA67:60        RTS                                A:F0 X:02 Y:9D P:67 SP:F9 PPU:274,151 CYC:17262\n$EA0C:AD 47 06  LDA $0647 = #$36                     A:F0 X:02 Y:9D P:67 SP:FB PPU:292,151 CYC:17268\n$EA0F:C9 36     CMP #$36                           A:36 X:02 Y:9D P:65 SP:FB PPU:304,151 CYC:17272\n$EA11:F0 02     BEQ $EA15                          A:36 X:02 Y:9D P:67 SP:FB PPU:310,151 CYC:17274\n$EA15:A9 EB     LDA #$EB                           A:36 X:02 Y:9D P:67 SP:FB PPU:319,151 CYC:17277\n$EA17:8D 47 06  STA $0647 = #$36                     A:EB X:02 Y:9D P:E5 SP:FB PPU:325,151 CYC:17279\n$EA1A:A9 48     LDA #$48                           A:EB X:02 Y:9D P:E5 SP:FB PPU:337,151 CYC:17283\n$EA1C:85 45     STA $45 = #$32                       A:48 X:02 Y:9D P:65 SP:FB PPU:  2,152 CYC:17285\n$EA1E:A9 05     LDA #$05                           A:48 X:02 Y:9D P:65 SP:FB PPU: 11,152 CYC:17288\n$EA20:85 46     STA $46 = #$04                       A:05 X:02 Y:9D P:65 SP:FB PPU: 17,152 CYC:17290\n$EA22:A0 FF     LDY #$FF                           A:05 X:02 Y:9D P:65 SP:FB PPU: 26,152 CYC:17293\n$EA24:20 31 FA  JSR $FA31                          A:05 X:02 Y:FF P:E5 SP:FB PPU: 32,152 CYC:17295\n$FA31:24 01     BIT $01 = #$FF                       A:05 X:02 Y:FF P:E5 SP:F9 PPU: 50,152 CYC:17301\n$FA33:18        CLC                                A:05 X:02 Y:FF P:E5 SP:F9 PPU: 59,152 CYC:17304\n$FA34:A9 40     LDA #$40                           A:05 X:02 Y:FF P:NVUbdIzc SP:F9 PPU: 65,152 CYC:17306\n$FA36:60        RTS                                A:40 X:02 Y:FF P:64 SP:F9 PPU: 71,152 CYC:17308\n$EA27:D3 45    *DCP ($45),Y = #$0548 @ 0647 = EB     A:40 X:02 Y:FF P:64 SP:FB PPU: 89,152 CYC:17314\n$EA29:EA        NOP                                A:40 X:02 Y:FF P:64 SP:FB PPU:113,152 CYC:17322\n$EA2A:EA        NOP                                A:40 X:02 Y:FF P:64 SP:FB PPU:119,152 CYC:17324\n$EA2B:08        PHP                                A:40 X:02 Y:FF P:64 SP:FB PPU:125,152 CYC:17326\n$EA2C:48        PHA                                A:40 X:02 Y:FF P:64 SP:FA PPU:134,152 CYC:17329\n$EA2D:A0 9E     LDY #$9E                           A:40 X:02 Y:FF P:64 SP:F9 PPU:143,152 CYC:17332\n$EA2F:68        PLA                                A:40 X:02 Y:9E P:NVUbdIzc SP:F9 PPU:149,152 CYC:17334\n$EA30:28        PLP                                A:40 X:02 Y:9E P:64 SP:FA PPU:161,152 CYC:17338\n$EA31:20 37 FA  JSR $FA37                          A:40 X:02 Y:9E P:64 SP:FB PPU:173,152 CYC:17342\n$FA37:50 2C     BVC $FA65                          A:40 X:02 Y:9E P:64 SP:F9 PPU:191,152 CYC:17348\n$FA39:B0 2A     BCS $FA65                          A:40 X:02 Y:9E P:64 SP:F9 PPU:197,152 CYC:17350\n$FA3B:30 28     BMI $FA65                          A:40 X:02 Y:9E P:64 SP:F9 PPU:203,152 CYC:17352\n$FA3D:C9 40     CMP #$40                           A:40 X:02 Y:9E P:64 SP:F9 PPU:209,152 CYC:17354\n$FA3F:D0 24     BNE $FA65                          A:40 X:02 Y:9E P:67 SP:F9 PPU:215,152 CYC:17356\n$FA41:60        RTS                                A:40 X:02 Y:9E P:67 SP:F9 PPU:221,152 CYC:17358\n$EA34:AD 47 06  LDA $0647 = #$EA                     A:40 X:02 Y:9E P:67 SP:FB PPU:239,152 CYC:17364\n$EA37:C9 EA     CMP #$EA                           A:EA X:02 Y:9E P:E5 SP:FB PPU:251,152 CYC:17368\n$EA39:F0 02     BEQ $EA3D                          A:EA X:02 Y:9E P:67 SP:FB PPU:257,152 CYC:17370\n$EA3D:A0 FF     LDY #$FF                           A:EA X:02 Y:9E P:67 SP:FB PPU:266,152 CYC:17373\n$EA3F:A9 00     LDA #$00                           A:EA X:02 Y:FF P:E5 SP:FB PPU:272,152 CYC:17375\n$EA41:8D 47 06  STA $0647 = #$EA                     A:00 X:02 Y:FF P:67 SP:FB PPU:278,152 CYC:17377\n$EA44:20 42 FA  JSR $FA42                          A:00 X:02 Y:FF P:67 SP:FB PPU:290,152 CYC:17381\n$FA42:B8        CLV                                A:00 X:02 Y:FF P:67 SP:F9 PPU:308,152 CYC:17387\n$FA43:38        SEC                                A:00 X:02 Y:FF P:nvUbdIZC SP:F9 PPU:314,152 CYC:17389\n$FA44:A9 FF     LDA #$FF                           A:00 X:02 Y:FF P:nvUbdIZC SP:F9 PPU:320,152 CYC:17391\n$FA46:60        RTS                                A:FF X:02 Y:FF P:A5 SP:F9 PPU:326,152 CYC:17393\n$EA47:D3 45    *DCP ($45),Y = #$0548 @ 0647 = 00     A:FF X:02 Y:FF P:A5 SP:FB PPU:  3,153 CYC:17399\n$EA49:EA        NOP                                A:FF X:02 Y:FF P:nvUbdIZC SP:FB PPU: 27,153 CYC:17407\n$EA4A:EA        NOP                                A:FF X:02 Y:FF P:nvUbdIZC SP:FB PPU: 33,153 CYC:17409\n$EA4B:08        PHP                                A:FF X:02 Y:FF P:nvUbdIZC SP:FB PPU: 39,153 CYC:17411\n$EA4C:48        PHA                                A:FF X:02 Y:FF P:nvUbdIZC SP:FA PPU: 48,153 CYC:17414\n$EA4D:A0 9F     LDY #$9F                           A:FF X:02 Y:FF P:nvUbdIZC SP:F9 PPU: 57,153 CYC:17417\n$EA4F:68        PLA                                A:FF X:02 Y:9F P:A5 SP:F9 PPU: 63,153 CYC:17419\n$EA50:28        PLP                                A:FF X:02 Y:9F P:A5 SP:FA PPU: 75,153 CYC:17423\n$EA51:20 47 FA  JSR $FA47                          A:FF X:02 Y:9F P:nvUbdIZC SP:FB PPU: 87,153 CYC:17427\n$FA47:70 1C     BVS $FA65                          A:FF X:02 Y:9F P:nvUbdIZC SP:F9 PPU:105,153 CYC:17433\n$FA49:D0 1A     BNE $FA65                          A:FF X:02 Y:9F P:nvUbdIZC SP:F9 PPU:111,153 CYC:17435\n$FA4B:30 18     BMI $FA65                          A:FF X:02 Y:9F P:nvUbdIZC SP:F9 PPU:117,153 CYC:17437\n$FA4D:90 16     BCC $FA65                          A:FF X:02 Y:9F P:nvUbdIZC SP:F9 PPU:123,153 CYC:17439\n$FA4F:C9 FF     CMP #$FF                           A:FF X:02 Y:9F P:nvUbdIZC SP:F9 PPU:129,153 CYC:17441\n$FA51:D0 12     BNE $FA65                          A:FF X:02 Y:9F P:nvUbdIZC SP:F9 PPU:135,153 CYC:17443\n$FA53:60        RTS                                A:FF X:02 Y:9F P:nvUbdIZC SP:F9 PPU:141,153 CYC:17445\n$EA54:AD 47 06  LDA $0647 = #$FF                     A:FF X:02 Y:9F P:nvUbdIZC SP:FB PPU:159,153 CYC:17451\n$EA57:C9 FF     CMP #$FF                           A:FF X:02 Y:9F P:A5 SP:FB PPU:171,153 CYC:17455\n$EA59:F0 02     BEQ $EA5D                          A:FF X:02 Y:9F P:nvUbdIZC SP:FB PPU:177,153 CYC:17457\n$EA5D:A0 FF     LDY #$FF                           A:FF X:02 Y:9F P:nvUbdIZC SP:FB PPU:186,153 CYC:17460\n$EA5F:A9 37     LDA #$37                           A:FF X:02 Y:FF P:A5 SP:FB PPU:192,153 CYC:17462\n$EA61:8D 47 06  STA $0647 = #$FF                     A:37 X:02 Y:FF P:25 SP:FB PPU:198,153 CYC:17464\n$EA64:20 54 FA  JSR $FA54                          A:37 X:02 Y:FF P:25 SP:FB PPU:210,153 CYC:17468\n$FA54:24 01     BIT $01 = #$FF                       A:37 X:02 Y:FF P:25 SP:F9 PPU:228,153 CYC:17474\n$FA56:A9 F0     LDA #$F0                           A:37 X:02 Y:FF P:E5 SP:F9 PPU:237,153 CYC:17477\n$FA58:60        RTS                                A:F0 X:02 Y:FF P:E5 SP:F9 PPU:243,153 CYC:17479\n$EA67:D3 45    *DCP ($45),Y = #$0548 @ 0647 = 37     A:F0 X:02 Y:FF P:E5 SP:FB PPU:261,153 CYC:17485\n$EA69:EA        NOP                                A:F0 X:02 Y:FF P:E5 SP:FB PPU:285,153 CYC:17493\n$EA6A:EA        NOP                                A:F0 X:02 Y:FF P:E5 SP:FB PPU:291,153 CYC:17495\n$EA6B:08        PHP                                A:F0 X:02 Y:FF P:E5 SP:FB PPU:297,153 CYC:17497\n$EA6C:48        PHA                                A:F0 X:02 Y:FF P:E5 SP:FA PPU:306,153 CYC:17500\n$EA6D:A0 A0     LDY #$A0                           A:F0 X:02 Y:FF P:E5 SP:F9 PPU:315,153 CYC:17503\n$EA6F:68        PLA                                A:F0 X:02 Y:A0 P:E5 SP:F9 PPU:321,153 CYC:17505\n$EA70:28        PLP                                A:F0 X:02 Y:A0 P:E5 SP:FA PPU:333,153 CYC:17509\n$EA71:20 59 FA  JSR $FA59                          A:F0 X:02 Y:A0 P:E5 SP:FB PPU:  4,154 CYC:17513\n$FA59:50 0A     BVC $FA65                          A:F0 X:02 Y:A0 P:E5 SP:F9 PPU: 22,154 CYC:17519\n$FA5B:F0 08     BEQ $FA65                          A:F0 X:02 Y:A0 P:E5 SP:F9 PPU: 28,154 CYC:17521\n$FA5D:10 06     BPL $FA65                          A:F0 X:02 Y:A0 P:E5 SP:F9 PPU: 34,154 CYC:17523\n$FA5F:90 04     BCC $FA65                          A:F0 X:02 Y:A0 P:E5 SP:F9 PPU: 40,154 CYC:17525\n$FA61:C9 F0     CMP #$F0                           A:F0 X:02 Y:A0 P:E5 SP:F9 PPU: 46,154 CYC:17527\n$FA63:F0 02     BEQ $FA67                          A:F0 X:02 Y:A0 P:67 SP:F9 PPU: 52,154 CYC:17529\n$FA67:60        RTS                                A:F0 X:02 Y:A0 P:67 SP:F9 PPU: 61,154 CYC:17532\n$EA74:AD 47 06  LDA $0647 = #$36                     A:F0 X:02 Y:A0 P:67 SP:FB PPU: 79,154 CYC:17538\n$EA77:C9 36     CMP #$36                           A:36 X:02 Y:A0 P:65 SP:FB PPU: 91,154 CYC:17542\n$EA79:F0 02     BEQ $EA7D                          A:36 X:02 Y:A0 P:67 SP:FB PPU: 97,154 CYC:17544\n$EA7D:A0 A1     LDY #$A1                           A:36 X:02 Y:A0 P:67 SP:FB PPU:106,154 CYC:17547\n$EA7F:A2 FF     LDX #$FF                           A:36 X:02 Y:A1 P:E5 SP:FB PPU:112,154 CYC:17549\n$EA81:A9 EB     LDA #$EB                           A:36 X:FF Y:A1 P:E5 SP:FB PPU:118,154 CYC:17551\n$EA83:85 47     STA $47 = #$36                       A:EB X:FF Y:A1 P:E5 SP:FB PPU:124,154 CYC:17553\n$EA85:20 31 FA  JSR $FA31                          A:EB X:FF Y:A1 P:E5 SP:FB PPU:133,154 CYC:17556\n$FA31:24 01     BIT $01 = #$FF                       A:EB X:FF Y:A1 P:E5 SP:F9 PPU:151,154 CYC:17562\n$FA33:18        CLC                                A:EB X:FF Y:A1 P:E5 SP:F9 PPU:160,154 CYC:17565\n$FA34:A9 40     LDA #$40                           A:EB X:FF Y:A1 P:NVUbdIzc SP:F9 PPU:166,154 CYC:17567\n$FA36:60        RTS                                A:40 X:FF Y:A1 P:64 SP:F9 PPU:172,154 CYC:17569\n$EA88:D7 48    *DCP $48,X @ 47 = #$EB                A:40 X:FF Y:A1 P:64 SP:FB PPU:190,154 CYC:17575\n$EA8A:EA        NOP                                A:40 X:FF Y:A1 P:64 SP:FB PPU:208,154 CYC:17581\n$EA8B:EA        NOP                                A:40 X:FF Y:A1 P:64 SP:FB PPU:214,154 CYC:17583\n$EA8C:EA        NOP                                A:40 X:FF Y:A1 P:64 SP:FB PPU:220,154 CYC:17585\n$EA8D:EA        NOP                                A:40 X:FF Y:A1 P:64 SP:FB PPU:226,154 CYC:17587\n$EA8E:20 37 FA  JSR $FA37                          A:40 X:FF Y:A1 P:64 SP:FB PPU:232,154 CYC:17589\n$FA37:50 2C     BVC $FA65                          A:40 X:FF Y:A1 P:64 SP:F9 PPU:250,154 CYC:17595\n$FA39:B0 2A     BCS $FA65                          A:40 X:FF Y:A1 P:64 SP:F9 PPU:256,154 CYC:17597\n$FA3B:30 28     BMI $FA65                          A:40 X:FF Y:A1 P:64 SP:F9 PPU:262,154 CYC:17599\n$FA3D:C9 40     CMP #$40                           A:40 X:FF Y:A1 P:64 SP:F9 PPU:268,154 CYC:17601\n$FA3F:D0 24     BNE $FA65                          A:40 X:FF Y:A1 P:67 SP:F9 PPU:274,154 CYC:17603\n$FA41:60        RTS                                A:40 X:FF Y:A1 P:67 SP:F9 PPU:280,154 CYC:17605\n$EA91:A5 47     LDA $47 = #$EA                       A:40 X:FF Y:A1 P:67 SP:FB PPU:298,154 CYC:17611\n$EA93:C9 EA     CMP #$EA                           A:EA X:FF Y:A1 P:E5 SP:FB PPU:307,154 CYC:17614\n$EA95:F0 02     BEQ $EA99                          A:EA X:FF Y:A1 P:67 SP:FB PPU:313,154 CYC:17616\n$EA99:C8        INY                                A:EA X:FF Y:A1 P:67 SP:FB PPU:322,154 CYC:17619\n$EA9A:A9 00     LDA #$00                           A:EA X:FF Y:A2 P:E5 SP:FB PPU:328,154 CYC:17621\n$EA9C:85 47     STA $47 = #$EA                       A:00 X:FF Y:A2 P:67 SP:FB PPU:334,154 CYC:17623\n$EA9E:20 42 FA  JSR $FA42                          A:00 X:FF Y:A2 P:67 SP:FB PPU:  2,155 CYC:17626\n$FA42:B8        CLV                                A:00 X:FF Y:A2 P:67 SP:F9 PPU: 20,155 CYC:17632\n$FA43:38        SEC                                A:00 X:FF Y:A2 P:nvUbdIZC SP:F9 PPU: 26,155 CYC:17634\n$FA44:A9 FF     LDA #$FF                           A:00 X:FF Y:A2 P:nvUbdIZC SP:F9 PPU: 32,155 CYC:17636\n$FA46:60        RTS                                A:FF X:FF Y:A2 P:A5 SP:F9 PPU: 38,155 CYC:17638\n$EAA1:D7 48    *DCP $48,X @ 47 = #$00                A:FF X:FF Y:A2 P:A5 SP:FB PPU: 56,155 CYC:17644\n$EAA3:EA        NOP                                A:FF X:FF Y:A2 P:nvUbdIZC SP:FB PPU: 74,155 CYC:17650\n$EAA4:EA        NOP                                A:FF X:FF Y:A2 P:nvUbdIZC SP:FB PPU: 80,155 CYC:17652\n$EAA5:EA        NOP                                A:FF X:FF Y:A2 P:nvUbdIZC SP:FB PPU: 86,155 CYC:17654\n$EAA6:EA        NOP                                A:FF X:FF Y:A2 P:nvUbdIZC SP:FB PPU: 92,155 CYC:17656\n$EAA7:20 47 FA  JSR $FA47                          A:FF X:FF Y:A2 P:nvUbdIZC SP:FB PPU: 98,155 CYC:17658\n$FA47:70 1C     BVS $FA65                          A:FF X:FF Y:A2 P:nvUbdIZC SP:F9 PPU:116,155 CYC:17664\n$FA49:D0 1A     BNE $FA65                          A:FF X:FF Y:A2 P:nvUbdIZC SP:F9 PPU:122,155 CYC:17666\n$FA4B:30 18     BMI $FA65                          A:FF X:FF Y:A2 P:nvUbdIZC SP:F9 PPU:128,155 CYC:17668\n$FA4D:90 16     BCC $FA65                          A:FF X:FF Y:A2 P:nvUbdIZC SP:F9 PPU:134,155 CYC:17670\n$FA4F:C9 FF     CMP #$FF                           A:FF X:FF Y:A2 P:nvUbdIZC SP:F9 PPU:140,155 CYC:17672\n$FA51:D0 12     BNE $FA65                          A:FF X:FF Y:A2 P:nvUbdIZC SP:F9 PPU:146,155 CYC:17674\n$FA53:60        RTS                                A:FF X:FF Y:A2 P:nvUbdIZC SP:F9 PPU:152,155 CYC:17676\n$EAAA:A5 47     LDA $47 = #$FF                       A:FF X:FF Y:A2 P:nvUbdIZC SP:FB PPU:170,155 CYC:17682\n$EAAC:C9 FF     CMP #$FF                           A:FF X:FF Y:A2 P:A5 SP:FB PPU:179,155 CYC:17685\n$EAAE:F0 02     BEQ $EAB2                          A:FF X:FF Y:A2 P:nvUbdIZC SP:FB PPU:185,155 CYC:17687\n$EAB2:C8        INY                                A:FF X:FF Y:A2 P:nvUbdIZC SP:FB PPU:194,155 CYC:17690\n$EAB3:A9 37     LDA #$37                           A:FF X:FF Y:A3 P:A5 SP:FB PPU:200,155 CYC:17692\n$EAB5:85 47     STA $47 = #$FF                       A:37 X:FF Y:A3 P:25 SP:FB PPU:206,155 CYC:17694\n$EAB7:20 54 FA  JSR $FA54                          A:37 X:FF Y:A3 P:25 SP:FB PPU:215,155 CYC:17697\n$FA54:24 01     BIT $01 = #$FF                       A:37 X:FF Y:A3 P:25 SP:F9 PPU:233,155 CYC:17703\n$FA56:A9 F0     LDA #$F0                           A:37 X:FF Y:A3 P:E5 SP:F9 PPU:242,155 CYC:17706\n$FA58:60        RTS                                A:F0 X:FF Y:A3 P:E5 SP:F9 PPU:248,155 CYC:17708\n$EABA:D7 48    *DCP $48,X @ 47 = #$37                A:F0 X:FF Y:A3 P:E5 SP:FB PPU:266,155 CYC:17714\n$EABC:EA        NOP                                A:F0 X:FF Y:A3 P:E5 SP:FB PPU:284,155 CYC:17720\n$EABD:EA        NOP                                A:F0 X:FF Y:A3 P:E5 SP:FB PPU:290,155 CYC:17722\n$EABE:EA        NOP                                A:F0 X:FF Y:A3 P:E5 SP:FB PPU:296,155 CYC:17724\n$EABF:EA        NOP                                A:F0 X:FF Y:A3 P:E5 SP:FB PPU:302,155 CYC:17726\n$EAC0:20 59 FA  JSR $FA59                          A:F0 X:FF Y:A3 P:E5 SP:FB PPU:308,155 CYC:17728\n$FA59:50 0A     BVC $FA65                          A:F0 X:FF Y:A3 P:E5 SP:F9 PPU:326,155 CYC:17734\n$FA5B:F0 08     BEQ $FA65                          A:F0 X:FF Y:A3 P:E5 SP:F9 PPU:332,155 CYC:17736\n$FA5D:10 06     BPL $FA65                          A:F0 X:FF Y:A3 P:E5 SP:F9 PPU:338,155 CYC:17738\n$FA5F:90 04     BCC $FA65                          A:F0 X:FF Y:A3 P:E5 SP:F9 PPU:  3,156 CYC:17740\n$FA61:C9 F0     CMP #$F0                           A:F0 X:FF Y:A3 P:E5 SP:F9 PPU:  9,156 CYC:17742\n$FA63:F0 02     BEQ $FA67                          A:F0 X:FF Y:A3 P:67 SP:F9 PPU: 15,156 CYC:17744\n$FA67:60        RTS                                A:F0 X:FF Y:A3 P:67 SP:F9 PPU: 24,156 CYC:17747\n$EAC3:A5 47     LDA $47 = #$36                       A:F0 X:FF Y:A3 P:67 SP:FB PPU: 42,156 CYC:17753\n$EAC5:C9 36     CMP #$36                           A:36 X:FF Y:A3 P:65 SP:FB PPU: 51,156 CYC:17756\n$EAC7:F0 02     BEQ $EACB                          A:36 X:FF Y:A3 P:67 SP:FB PPU: 57,156 CYC:17758\n$EACB:A9 EB     LDA #$EB                           A:36 X:FF Y:A3 P:67 SP:FB PPU: 66,156 CYC:17761\n$EACD:8D 47 06  STA $0647 = #$36                     A:EB X:FF Y:A3 P:E5 SP:FB PPU: 72,156 CYC:17763\n$EAD0:A0 FF     LDY #$FF                           A:EB X:FF Y:A3 P:E5 SP:FB PPU: 84,156 CYC:17767\n$EAD2:20 31 FA  JSR $FA31                          A:EB X:FF Y:FF P:E5 SP:FB PPU: 90,156 CYC:17769\n$FA31:24 01     BIT $01 = #$FF                       A:EB X:FF Y:FF P:E5 SP:F9 PPU:108,156 CYC:17775\n$FA33:18        CLC                                A:EB X:FF Y:FF P:E5 SP:F9 PPU:117,156 CYC:17778\n$FA34:A9 40     LDA #$40                           A:EB X:FF Y:FF P:NVUbdIzc SP:F9 PPU:123,156 CYC:17780\n$FA36:60        RTS                                A:40 X:FF Y:FF P:64 SP:F9 PPU:129,156 CYC:17782\n$EAD5:DB 48 05 *DCP $0548,Y @ 0647 = #$EB            A:40 X:FF Y:FF P:64 SP:FB PPU:147,156 CYC:17788\n$EAD8:EA        NOP                                A:40 X:FF Y:FF P:64 SP:FB PPU:168,156 CYC:17795\n$EAD9:EA        NOP                                A:40 X:FF Y:FF P:64 SP:FB PPU:174,156 CYC:17797\n$EADA:08        PHP                                A:40 X:FF Y:FF P:64 SP:FB PPU:180,156 CYC:17799\n$EADB:48        PHA                                A:40 X:FF Y:FF P:64 SP:FA PPU:189,156 CYC:17802\n$EADC:A0 A4     LDY #$A4                           A:40 X:FF Y:FF P:64 SP:F9 PPU:198,156 CYC:17805\n$EADE:68        PLA                                A:40 X:FF Y:A4 P:NVUbdIzc SP:F9 PPU:204,156 CYC:17807\n$EADF:28        PLP                                A:40 X:FF Y:A4 P:64 SP:FA PPU:216,156 CYC:17811\n$EAE0:20 37 FA  JSR $FA37                          A:40 X:FF Y:A4 P:64 SP:FB PPU:228,156 CYC:17815\n$FA37:50 2C     BVC $FA65                          A:40 X:FF Y:A4 P:64 SP:F9 PPU:246,156 CYC:17821\n$FA39:B0 2A     BCS $FA65                          A:40 X:FF Y:A4 P:64 SP:F9 PPU:252,156 CYC:17823\n$FA3B:30 28     BMI $FA65                          A:40 X:FF Y:A4 P:64 SP:F9 PPU:258,156 CYC:17825\n$FA3D:C9 40     CMP #$40                           A:40 X:FF Y:A4 P:64 SP:F9 PPU:264,156 CYC:17827\n$FA3F:D0 24     BNE $FA65                          A:40 X:FF Y:A4 P:67 SP:F9 PPU:270,156 CYC:17829\n$FA41:60        RTS                                A:40 X:FF Y:A4 P:67 SP:F9 PPU:276,156 CYC:17831\n$EAE3:AD 47 06  LDA $0647 = #$EA                     A:40 X:FF Y:A4 P:67 SP:FB PPU:294,156 CYC:17837\n$EAE6:C9 EA     CMP #$EA                           A:EA X:FF Y:A4 P:E5 SP:FB PPU:306,156 CYC:17841\n$EAE8:F0 02     BEQ $EAEC                          A:EA X:FF Y:A4 P:67 SP:FB PPU:312,156 CYC:17843\n$EAEC:A0 FF     LDY #$FF                           A:EA X:FF Y:A4 P:67 SP:FB PPU:321,156 CYC:17846\n$EAEE:A9 00     LDA #$00                           A:EA X:FF Y:FF P:E5 SP:FB PPU:327,156 CYC:17848\n$EAF0:8D 47 06  STA $0647 = #$EA                     A:00 X:FF Y:FF P:67 SP:FB PPU:333,156 CYC:17850\n$EAF3:20 42 FA  JSR $FA42                          A:00 X:FF Y:FF P:67 SP:FB PPU:  4,157 CYC:17854\n$FA42:B8        CLV                                A:00 X:FF Y:FF P:67 SP:F9 PPU: 22,157 CYC:17860\n$FA43:38        SEC                                A:00 X:FF Y:FF P:nvUbdIZC SP:F9 PPU: 28,157 CYC:17862\n$FA44:A9 FF     LDA #$FF                           A:00 X:FF Y:FF P:nvUbdIZC SP:F9 PPU: 34,157 CYC:17864\n$FA46:60        RTS                                A:FF X:FF Y:FF P:A5 SP:F9 PPU: 40,157 CYC:17866\n$EAF6:DB 48 05 *DCP $0548,Y @ 0647 = #$00            A:FF X:FF Y:FF P:A5 SP:FB PPU: 58,157 CYC:17872\n$EAF9:EA        NOP                                A:FF X:FF Y:FF P:nvUbdIZC SP:FB PPU: 79,157 CYC:17879\n$EAFA:EA        NOP                                A:FF X:FF Y:FF P:nvUbdIZC SP:FB PPU: 85,157 CYC:17881\n$EAFB:08        PHP                                A:FF X:FF Y:FF P:nvUbdIZC SP:FB PPU: 91,157 CYC:17883\n$EAFC:48        PHA                                A:FF X:FF Y:FF P:nvUbdIZC SP:FA PPU:100,157 CYC:17886\n$EAFD:A0 A5     LDY #$A5                           A:FF X:FF Y:FF P:nvUbdIZC SP:F9 PPU:109,157 CYC:17889\n$EAFF:68        PLA                                A:FF X:FF Y:A5 P:A5 SP:F9 PPU:115,157 CYC:17891\n$EB00:28        PLP                                A:FF X:FF Y:A5 P:A5 SP:FA PPU:127,157 CYC:17895\n$EB01:20 47 FA  JSR $FA47                          A:FF X:FF Y:A5 P:nvUbdIZC SP:FB PPU:139,157 CYC:17899\n$FA47:70 1C     BVS $FA65                          A:FF X:FF Y:A5 P:nvUbdIZC SP:F9 PPU:157,157 CYC:17905\n$FA49:D0 1A     BNE $FA65                          A:FF X:FF Y:A5 P:nvUbdIZC SP:F9 PPU:163,157 CYC:17907\n$FA4B:30 18     BMI $FA65                          A:FF X:FF Y:A5 P:nvUbdIZC SP:F9 PPU:169,157 CYC:17909\n$FA4D:90 16     BCC $FA65                          A:FF X:FF Y:A5 P:nvUbdIZC SP:F9 PPU:175,157 CYC:17911\n$FA4F:C9 FF     CMP #$FF                           A:FF X:FF Y:A5 P:nvUbdIZC SP:F9 PPU:181,157 CYC:17913\n$FA51:D0 12     BNE $FA65                          A:FF X:FF Y:A5 P:nvUbdIZC SP:F9 PPU:187,157 CYC:17915\n$FA53:60        RTS                                A:FF X:FF Y:A5 P:nvUbdIZC SP:F9 PPU:193,157 CYC:17917\n$EB04:AD 47 06  LDA $0647 = #$FF                     A:FF X:FF Y:A5 P:nvUbdIZC SP:FB PPU:211,157 CYC:17923\n$EB07:C9 FF     CMP #$FF                           A:FF X:FF Y:A5 P:A5 SP:FB PPU:223,157 CYC:17927\n$EB09:F0 02     BEQ $EB0D                          A:FF X:FF Y:A5 P:nvUbdIZC SP:FB PPU:229,157 CYC:17929\n$EB0D:A0 FF     LDY #$FF                           A:FF X:FF Y:A5 P:nvUbdIZC SP:FB PPU:238,157 CYC:17932\n$EB0F:A9 37     LDA #$37                           A:FF X:FF Y:FF P:A5 SP:FB PPU:244,157 CYC:17934\n$EB11:8D 47 06  STA $0647 = #$FF                     A:37 X:FF Y:FF P:25 SP:FB PPU:250,157 CYC:17936\n$EB14:20 54 FA  JSR $FA54                          A:37 X:FF Y:FF P:25 SP:FB PPU:262,157 CYC:17940\n$FA54:24 01     BIT $01 = #$FF                       A:37 X:FF Y:FF P:25 SP:F9 PPU:280,157 CYC:17946\n$FA56:A9 F0     LDA #$F0                           A:37 X:FF Y:FF P:E5 SP:F9 PPU:289,157 CYC:17949\n$FA58:60        RTS                                A:F0 X:FF Y:FF P:E5 SP:F9 PPU:295,157 CYC:17951\n$EB17:DB 48 05 *DCP $0548,Y @ 0647 = #$37            A:F0 X:FF Y:FF P:E5 SP:FB PPU:313,157 CYC:17957\n$EB1A:EA        NOP                                A:F0 X:FF Y:FF P:E5 SP:FB PPU:334,157 CYC:17964\n$EB1B:EA        NOP                                A:F0 X:FF Y:FF P:E5 SP:FB PPU:340,157 CYC:17966\n$EB1C:08        PHP                                A:F0 X:FF Y:FF P:E5 SP:FB PPU:  5,158 CYC:17968\n$EB1D:48        PHA                                A:F0 X:FF Y:FF P:E5 SP:FA PPU: 14,158 CYC:17971\n$EB1E:A0 A6     LDY #$A6                           A:F0 X:FF Y:FF P:E5 SP:F9 PPU: 23,158 CYC:17974\n$EB20:68        PLA                                A:F0 X:FF Y:A6 P:E5 SP:F9 PPU: 29,158 CYC:17976\n$EB21:28        PLP                                A:F0 X:FF Y:A6 P:E5 SP:FA PPU: 41,158 CYC:17980\n$EB22:20 59 FA  JSR $FA59                          A:F0 X:FF Y:A6 P:E5 SP:FB PPU: 53,158 CYC:17984\n$FA59:50 0A     BVC $FA65                          A:F0 X:FF Y:A6 P:E5 SP:F9 PPU: 71,158 CYC:17990\n$FA5B:F0 08     BEQ $FA65                          A:F0 X:FF Y:A6 P:E5 SP:F9 PPU: 77,158 CYC:17992\n$FA5D:10 06     BPL $FA65                          A:F0 X:FF Y:A6 P:E5 SP:F9 PPU: 83,158 CYC:17994\n$FA5F:90 04     BCC $FA65                          A:F0 X:FF Y:A6 P:E5 SP:F9 PPU: 89,158 CYC:17996\n$FA61:C9 F0     CMP #$F0                           A:F0 X:FF Y:A6 P:E5 SP:F9 PPU: 95,158 CYC:17998\n$FA63:F0 02     BEQ $FA67                          A:F0 X:FF Y:A6 P:67 SP:F9 PPU:101,158 CYC:18000\n$FA67:60        RTS                                A:F0 X:FF Y:A6 P:67 SP:F9 PPU:110,158 CYC:18003\n$EB25:AD 47 06  LDA $0647 = #$36                     A:F0 X:FF Y:A6 P:67 SP:FB PPU:128,158 CYC:18009\n$EB28:C9 36     CMP #$36                           A:36 X:FF Y:A6 P:65 SP:FB PPU:140,158 CYC:18013\n$EB2A:F0 02     BEQ $EB2E                          A:36 X:FF Y:A6 P:67 SP:FB PPU:146,158 CYC:18015\n$EB2E:A0 A7     LDY #$A7                           A:36 X:FF Y:A6 P:67 SP:FB PPU:155,158 CYC:18018\n$EB30:A2 FF     LDX #$FF                           A:36 X:FF Y:A7 P:E5 SP:FB PPU:161,158 CYC:18020\n$EB32:A9 EB     LDA #$EB                           A:36 X:FF Y:A7 P:E5 SP:FB PPU:167,158 CYC:18022\n$EB34:8D 47 06  STA $0647 = #$36                     A:EB X:FF Y:A7 P:E5 SP:FB PPU:173,158 CYC:18024\n$EB37:20 31 FA  JSR $FA31                          A:EB X:FF Y:A7 P:E5 SP:FB PPU:185,158 CYC:18028\n$FA31:24 01     BIT $01 = #$FF                       A:EB X:FF Y:A7 P:E5 SP:F9 PPU:203,158 CYC:18034\n$FA33:18        CLC                                A:EB X:FF Y:A7 P:E5 SP:F9 PPU:212,158 CYC:18037\n$FA34:A9 40     LDA #$40                           A:EB X:FF Y:A7 P:NVUbdIzc SP:F9 PPU:218,158 CYC:18039\n$FA36:60        RTS                                A:40 X:FF Y:A7 P:64 SP:F9 PPU:224,158 CYC:18041\n$EB3A:DF 48 05 *DCP $0548,X @ 0647 = #$EB            A:40 X:FF Y:A7 P:64 SP:FB PPU:242,158 CYC:18047\n$EB3D:EA        NOP                                A:40 X:FF Y:A7 P:64 SP:FB PPU:263,158 CYC:18054\n$EB3E:EA        NOP                                A:40 X:FF Y:A7 P:64 SP:FB PPU:269,158 CYC:18056\n$EB3F:EA        NOP                                A:40 X:FF Y:A7 P:64 SP:FB PPU:275,158 CYC:18058\n$EB40:EA        NOP                                A:40 X:FF Y:A7 P:64 SP:FB PPU:281,158 CYC:18060\n$EB41:20 37 FA  JSR $FA37                          A:40 X:FF Y:A7 P:64 SP:FB PPU:287,158 CYC:18062\n$FA37:50 2C     BVC $FA65                          A:40 X:FF Y:A7 P:64 SP:F9 PPU:305,158 CYC:18068\n$FA39:B0 2A     BCS $FA65                          A:40 X:FF Y:A7 P:64 SP:F9 PPU:311,158 CYC:18070\n$FA3B:30 28     BMI $FA65                          A:40 X:FF Y:A7 P:64 SP:F9 PPU:317,158 CYC:18072\n$FA3D:C9 40     CMP #$40                           A:40 X:FF Y:A7 P:64 SP:F9 PPU:323,158 CYC:18074\n$FA3F:D0 24     BNE $FA65                          A:40 X:FF Y:A7 P:67 SP:F9 PPU:329,158 CYC:18076\n$FA41:60        RTS                                A:40 X:FF Y:A7 P:67 SP:F9 PPU:335,158 CYC:18078\n$EB44:AD 47 06  LDA $0647 = #$EA                     A:40 X:FF Y:A7 P:67 SP:FB PPU: 12,159 CYC:18084\n$EB47:C9 EA     CMP #$EA                           A:EA X:FF Y:A7 P:E5 SP:FB PPU: 24,159 CYC:18088\n$EB49:F0 02     BEQ $EB4D                          A:EA X:FF Y:A7 P:67 SP:FB PPU: 30,159 CYC:18090\n$EB4D:C8        INY                                A:EA X:FF Y:A7 P:67 SP:FB PPU: 39,159 CYC:18093\n$EB4E:A9 00     LDA #$00                           A:EA X:FF Y:A8 P:E5 SP:FB PPU: 45,159 CYC:18095\n$EB50:8D 47 06  STA $0647 = #$EA                     A:00 X:FF Y:A8 P:67 SP:FB PPU: 51,159 CYC:18097\n$EB53:20 42 FA  JSR $FA42                          A:00 X:FF Y:A8 P:67 SP:FB PPU: 63,159 CYC:18101\n$FA42:B8        CLV                                A:00 X:FF Y:A8 P:67 SP:F9 PPU: 81,159 CYC:18107\n$FA43:38        SEC                                A:00 X:FF Y:A8 P:nvUbdIZC SP:F9 PPU: 87,159 CYC:18109\n$FA44:A9 FF     LDA #$FF                           A:00 X:FF Y:A8 P:nvUbdIZC SP:F9 PPU: 93,159 CYC:18111\n$FA46:60        RTS                                A:FF X:FF Y:A8 P:A5 SP:F9 PPU: 99,159 CYC:18113\n$EB56:DF 48 05 *DCP $0548,X @ 0647 = #$00            A:FF X:FF Y:A8 P:A5 SP:FB PPU:117,159 CYC:18119\n$EB59:EA        NOP                                A:FF X:FF Y:A8 P:nvUbdIZC SP:FB PPU:138,159 CYC:18126\n$EB5A:EA        NOP                                A:FF X:FF Y:A8 P:nvUbdIZC SP:FB PPU:144,159 CYC:18128\n$EB5B:EA        NOP                                A:FF X:FF Y:A8 P:nvUbdIZC SP:FB PPU:150,159 CYC:18130\n$EB5C:EA        NOP                                A:FF X:FF Y:A8 P:nvUbdIZC SP:FB PPU:156,159 CYC:18132\n$EB5D:20 47 FA  JSR $FA47                          A:FF X:FF Y:A8 P:nvUbdIZC SP:FB PPU:162,159 CYC:18134\n$FA47:70 1C     BVS $FA65                          A:FF X:FF Y:A8 P:nvUbdIZC SP:F9 PPU:180,159 CYC:18140\n$FA49:D0 1A     BNE $FA65                          A:FF X:FF Y:A8 P:nvUbdIZC SP:F9 PPU:186,159 CYC:18142\n$FA4B:30 18     BMI $FA65                          A:FF X:FF Y:A8 P:nvUbdIZC SP:F9 PPU:192,159 CYC:18144\n$FA4D:90 16     BCC $FA65                          A:FF X:FF Y:A8 P:nvUbdIZC SP:F9 PPU:198,159 CYC:18146\n$FA4F:C9 FF     CMP #$FF                           A:FF X:FF Y:A8 P:nvUbdIZC SP:F9 PPU:204,159 CYC:18148\n$FA51:D0 12     BNE $FA65                          A:FF X:FF Y:A8 P:nvUbdIZC SP:F9 PPU:210,159 CYC:18150\n$FA53:60        RTS                                A:FF X:FF Y:A8 P:nvUbdIZC SP:F9 PPU:216,159 CYC:18152\n$EB60:AD 47 06  LDA $0647 = #$FF                     A:FF X:FF Y:A8 P:nvUbdIZC SP:FB PPU:234,159 CYC:18158\n$EB63:C9 FF     CMP #$FF                           A:FF X:FF Y:A8 P:A5 SP:FB PPU:246,159 CYC:18162\n$EB65:F0 02     BEQ $EB69                          A:FF X:FF Y:A8 P:nvUbdIZC SP:FB PPU:252,159 CYC:18164\n$EB69:C8        INY                                A:FF X:FF Y:A8 P:nvUbdIZC SP:FB PPU:261,159 CYC:18167\n$EB6A:A9 37     LDA #$37                           A:FF X:FF Y:A9 P:A5 SP:FB PPU:267,159 CYC:18169\n$EB6C:8D 47 06  STA $0647 = #$FF                     A:37 X:FF Y:A9 P:25 SP:FB PPU:273,159 CYC:18171\n$EB6F:20 54 FA  JSR $FA54                          A:37 X:FF Y:A9 P:25 SP:FB PPU:285,159 CYC:18175\n$FA54:24 01     BIT $01 = #$FF                       A:37 X:FF Y:A9 P:25 SP:F9 PPU:303,159 CYC:18181\n$FA56:A9 F0     LDA #$F0                           A:37 X:FF Y:A9 P:E5 SP:F9 PPU:312,159 CYC:18184\n$FA58:60        RTS                                A:F0 X:FF Y:A9 P:E5 SP:F9 PPU:318,159 CYC:18186\n$EB72:DF 48 05 *DCP $0548,X @ 0647 = #$37            A:F0 X:FF Y:A9 P:E5 SP:FB PPU:336,159 CYC:18192\n$EB75:EA        NOP                                A:F0 X:FF Y:A9 P:E5 SP:FB PPU: 16,160 CYC:18199\n$EB76:EA        NOP                                A:F0 X:FF Y:A9 P:E5 SP:FB PPU: 22,160 CYC:18201\n$EB77:EA        NOP                                A:F0 X:FF Y:A9 P:E5 SP:FB PPU: 28,160 CYC:18203\n$EB78:EA        NOP                                A:F0 X:FF Y:A9 P:E5 SP:FB PPU: 34,160 CYC:18205\n$EB79:20 59 FA  JSR $FA59                          A:F0 X:FF Y:A9 P:E5 SP:FB PPU: 40,160 CYC:18207\n$FA59:50 0A     BVC $FA65                          A:F0 X:FF Y:A9 P:E5 SP:F9 PPU: 58,160 CYC:18213\n$FA5B:F0 08     BEQ $FA65                          A:F0 X:FF Y:A9 P:E5 SP:F9 PPU: 64,160 CYC:18215\n$FA5D:10 06     BPL $FA65                          A:F0 X:FF Y:A9 P:E5 SP:F9 PPU: 70,160 CYC:18217\n$FA5F:90 04     BCC $FA65                          A:F0 X:FF Y:A9 P:E5 SP:F9 PPU: 76,160 CYC:18219\n$FA61:C9 F0     CMP #$F0                           A:F0 X:FF Y:A9 P:E5 SP:F9 PPU: 82,160 CYC:18221\n$FA63:F0 02     BEQ $FA67                          A:F0 X:FF Y:A9 P:67 SP:F9 PPU: 88,160 CYC:18223\n$FA67:60        RTS                                A:F0 X:FF Y:A9 P:67 SP:F9 PPU: 97,160 CYC:18226\n$EB7C:AD 47 06  LDA $0647 = #$36                     A:F0 X:FF Y:A9 P:67 SP:FB PPU:115,160 CYC:18232\n$EB7F:C9 36     CMP #$36                           A:36 X:FF Y:A9 P:65 SP:FB PPU:127,160 CYC:18236\n$EB81:F0 02     BEQ $EB85                          A:36 X:FF Y:A9 P:67 SP:FB PPU:133,160 CYC:18238\n$EB85:60        RTS                                A:36 X:FF Y:A9 P:67 SP:FB PPU:142,160 CYC:18241\n$C63E:20 86 EB  JSR $EB86                          A:36 X:FF Y:A9 P:67 SP:FD PPU:160,160 CYC:18247\n$EB86:A9 FF     LDA #$FF                           A:36 X:FF Y:A9 P:67 SP:FB PPU:178,160 CYC:18253\n$EB88:85 01     STA $01 = #$FF                       A:FF X:FF Y:A9 P:E5 SP:FB PPU:184,160 CYC:18255\n$EB8A:A0 AA     LDY #$AA                           A:FF X:FF Y:A9 P:E5 SP:FB PPU:193,160 CYC:18258\n$EB8C:A2 02     LDX #$02                           A:FF X:FF Y:AA P:E5 SP:FB PPU:199,160 CYC:18260\n$EB8E:A9 47     LDA #$47                           A:FF X:02 Y:AA P:65 SP:FB PPU:205,160 CYC:18262\n$EB90:85 47     STA $47 = #$36                       A:47 X:02 Y:AA P:65 SP:FB PPU:211,160 CYC:18264\n$EB92:A9 06     LDA #$06                           A:47 X:02 Y:AA P:65 SP:FB PPU:220,160 CYC:18267\n$EB94:85 48     STA $48 = #$06                       A:06 X:02 Y:AA P:65 SP:FB PPU:226,160 CYC:18269\n$EB96:A9 EB     LDA #$EB                           A:06 X:02 Y:AA P:65 SP:FB PPU:235,160 CYC:18272\n$EB98:8D 47 06  STA $0647 = #$36                     A:EB X:02 Y:AA P:E5 SP:FB PPU:241,160 CYC:18274\n$EB9B:20 B1 FA  JSR $FAB1                          A:EB X:02 Y:AA P:E5 SP:FB PPU:253,160 CYC:18278\n$FAB1:24 01     BIT $01 = #$FF                       A:EB X:02 Y:AA P:E5 SP:F9 PPU:271,160 CYC:18284\n$FAB3:18        CLC                                A:EB X:02 Y:AA P:E5 SP:F9 PPU:280,160 CYC:18287\n$FAB4:A9 40     LDA #$40                           A:EB X:02 Y:AA P:NVUbdIzc SP:F9 PPU:286,160 CYC:18289\n$FAB6:60        RTS                                A:40 X:02 Y:AA P:64 SP:F9 PPU:292,160 CYC:18291\n$EB9E:E3 45    *ISB ($45,X) @ 47 = #$0647 = EB       A:40 X:02 Y:AA P:64 SP:FB PPU:310,160 CYC:18297\n$EBA0:EA        NOP                                A:53 X:02 Y:AA P:nvUbdIzc SP:FB PPU:334,160 CYC:18305\n$EBA1:EA        NOP                                A:53 X:02 Y:AA P:nvUbdIzc SP:FB PPU:340,160 CYC:18307\n$EBA2:EA        NOP                                A:53 X:02 Y:AA P:nvUbdIzc SP:FB PPU:  5,161 CYC:18309\n$EBA3:EA        NOP                                A:53 X:02 Y:AA P:nvUbdIzc SP:FB PPU: 11,161 CYC:18311\n$EBA4:20 B7 FA  JSR $FAB7                          A:53 X:02 Y:AA P:nvUbdIzc SP:FB PPU: 17,161 CYC:18313\n$FAB7:70 2D     BVS $FAE6                          A:53 X:02 Y:AA P:nvUbdIzc SP:F9 PPU: 35,161 CYC:18319\n$FAB9:B0 2B     BCS $FAE6                          A:53 X:02 Y:AA P:nvUbdIzc SP:F9 PPU: 41,161 CYC:18321\n$FABB:30 29     BMI $FAE6                          A:53 X:02 Y:AA P:nvUbdIzc SP:F9 PPU: 47,161 CYC:18323\n$FABD:C9 53     CMP #$53                           A:53 X:02 Y:AA P:nvUbdIzc SP:F9 PPU: 53,161 CYC:18325\n$FABF:D0 25     BNE $FAE6                          A:53 X:02 Y:AA P:nvUbdIZC SP:F9 PPU: 59,161 CYC:18327\n$FAC1:60        RTS                                A:53 X:02 Y:AA P:nvUbdIZC SP:F9 PPU: 65,161 CYC:18329\n$EBA7:AD 47 06  LDA $0647 = #$EC                     A:53 X:02 Y:AA P:nvUbdIZC SP:FB PPU: 83,161 CYC:18335\n$EBAA:C9 EC     CMP #$EC                           A:EC X:02 Y:AA P:A5 SP:FB PPU: 95,161 CYC:18339\n$EBAC:F0 02     BEQ $EBB0                          A:EC X:02 Y:AA P:nvUbdIZC SP:FB PPU:101,161 CYC:18341\n$EBB0:C8        INY                                A:EC X:02 Y:AA P:nvUbdIZC SP:FB PPU:110,161 CYC:18344\n$EBB1:A9 FF     LDA #$FF                           A:EC X:02 Y:AB P:A5 SP:FB PPU:116,161 CYC:18346\n$EBB3:8D 47 06  STA $0647 = #$EC                     A:FF X:02 Y:AB P:A5 SP:FB PPU:122,161 CYC:18348\n$EBB6:20 C2 FA  JSR $FAC2                          A:FF X:02 Y:AB P:A5 SP:FB PPU:134,161 CYC:18352\n$FAC2:B8        CLV                                A:FF X:02 Y:AB P:A5 SP:F9 PPU:152,161 CYC:18358\n$FAC3:38        SEC                                A:FF X:02 Y:AB P:A5 SP:F9 PPU:158,161 CYC:18360\n$FAC4:A9 FF     LDA #$FF                           A:FF X:02 Y:AB P:A5 SP:F9 PPU:164,161 CYC:18362\n$FAC6:60        RTS                                A:FF X:02 Y:AB P:A5 SP:F9 PPU:170,161 CYC:18364\n$EBB9:E3 45    *ISB ($45,X) @ 47 = #$0647 = FF       A:FF X:02 Y:AB P:A5 SP:FB PPU:188,161 CYC:18370\n$EBBB:EA        NOP                                A:FF X:02 Y:AB P:A5 SP:FB PPU:212,161 CYC:18378\n$EBBC:EA        NOP                                A:FF X:02 Y:AB P:A5 SP:FB PPU:218,161 CYC:18380\n$EBBD:EA        NOP                                A:FF X:02 Y:AB P:A5 SP:FB PPU:224,161 CYC:18382\n$EBBE:EA        NOP                                A:FF X:02 Y:AB P:A5 SP:FB PPU:230,161 CYC:18384\n$EBBF:20 C7 FA  JSR $FAC7                          A:FF X:02 Y:AB P:A5 SP:FB PPU:236,161 CYC:18386\n$FAC7:70 1D     BVS $FAE6                          A:FF X:02 Y:AB P:A5 SP:F9 PPU:254,161 CYC:18392\n$FAC9:F0 1B     BEQ $FAE6                          A:FF X:02 Y:AB P:A5 SP:F9 PPU:260,161 CYC:18394\n$FACB:10 19     BPL $FAE6                          A:FF X:02 Y:AB P:A5 SP:F9 PPU:266,161 CYC:18396\n$FACD:90 17     BCC $FAE6                          A:FF X:02 Y:AB P:A5 SP:F9 PPU:272,161 CYC:18398\n$FACF:C9 FF     CMP #$FF                           A:FF X:02 Y:AB P:A5 SP:F9 PPU:278,161 CYC:18400\n$FAD1:D0 13     BNE $FAE6                          A:FF X:02 Y:AB P:nvUbdIZC SP:F9 PPU:284,161 CYC:18402\n$FAD3:60        RTS                                A:FF X:02 Y:AB P:nvUbdIZC SP:F9 PPU:290,161 CYC:18404\n$EBC2:AD 47 06  LDA $0647 = #$00                     A:FF X:02 Y:AB P:nvUbdIZC SP:FB PPU:308,161 CYC:18410\n$EBC5:C9 00     CMP #$00                           A:00 X:02 Y:AB P:nvUbdIZC SP:FB PPU:320,161 CYC:18414\n$EBC7:F0 02     BEQ $EBCB                          A:00 X:02 Y:AB P:nvUbdIZC SP:FB PPU:326,161 CYC:18416\n$EBCB:C8        INY                                A:00 X:02 Y:AB P:nvUbdIZC SP:FB PPU:335,161 CYC:18419\n$EBCC:A9 37     LDA #$37                           A:00 X:02 Y:AC P:A5 SP:FB PPU:  0,162 CYC:18421\n$EBCE:8D 47 06  STA $0647 = #$00                     A:37 X:02 Y:AC P:25 SP:FB PPU:  6,162 CYC:18423\n$EBD1:20 D4 FA  JSR $FAD4                          A:37 X:02 Y:AC P:25 SP:FB PPU: 18,162 CYC:18427\n$FAD4:24 01     BIT $01 = #$FF                       A:37 X:02 Y:AC P:25 SP:F9 PPU: 36,162 CYC:18433\n$FAD6:38        SEC                                A:37 X:02 Y:AC P:E5 SP:F9 PPU: 45,162 CYC:18436\n$FAD7:A9 F0     LDA #$F0                           A:37 X:02 Y:AC P:E5 SP:F9 PPU: 51,162 CYC:18438\n$FAD9:60        RTS                                A:F0 X:02 Y:AC P:E5 SP:F9 PPU: 57,162 CYC:18440\n$EBD4:E3 45    *ISB ($45,X) @ 47 = #$0647 = 37       A:F0 X:02 Y:AC P:E5 SP:FB PPU: 75,162 CYC:18446\n$EBD6:EA        NOP                                A:B8 X:02 Y:AC P:A5 SP:FB PPU: 99,162 CYC:18454\n$EBD7:EA        NOP                                A:B8 X:02 Y:AC P:A5 SP:FB PPU:105,162 CYC:18456\n$EBD8:EA        NOP                                A:B8 X:02 Y:AC P:A5 SP:FB PPU:111,162 CYC:18458\n$EBD9:EA        NOP                                A:B8 X:02 Y:AC P:A5 SP:FB PPU:117,162 CYC:18460\n$EBDA:20 DA FA  JSR $FADA                          A:B8 X:02 Y:AC P:A5 SP:FB PPU:123,162 CYC:18462\n$FADA:70 0A     BVS $FAE6                          A:B8 X:02 Y:AC P:A5 SP:F9 PPU:141,162 CYC:18468\n$FADC:F0 08     BEQ $FAE6                          A:B8 X:02 Y:AC P:A5 SP:F9 PPU:147,162 CYC:18470\n$FADE:10 06     BPL $FAE6                          A:B8 X:02 Y:AC P:A5 SP:F9 PPU:153,162 CYC:18472\n$FAE0:90 04     BCC $FAE6                          A:B8 X:02 Y:AC P:A5 SP:F9 PPU:159,162 CYC:18474\n$FAE2:C9 B8     CMP #$B8                           A:B8 X:02 Y:AC P:A5 SP:F9 PPU:165,162 CYC:18476\n$FAE4:F0 02     BEQ $FAE8                          A:B8 X:02 Y:AC P:nvUbdIZC SP:F9 PPU:171,162 CYC:18478\n$FAE8:60        RTS                                A:B8 X:02 Y:AC P:nvUbdIZC SP:F9 PPU:180,162 CYC:18481\n$EBDD:AD 47 06  LDA $0647 = #$38                     A:B8 X:02 Y:AC P:nvUbdIZC SP:FB PPU:198,162 CYC:18487\n$EBE0:C9 38     CMP #$38                           A:38 X:02 Y:AC P:25 SP:FB PPU:210,162 CYC:18491\n$EBE2:F0 02     BEQ $EBE6                          A:38 X:02 Y:AC P:nvUbdIZC SP:FB PPU:216,162 CYC:18493\n$EBE6:C8        INY                                A:38 X:02 Y:AC P:nvUbdIZC SP:FB PPU:225,162 CYC:18496\n$EBE7:A9 EB     LDA #$EB                           A:38 X:02 Y:AD P:A5 SP:FB PPU:231,162 CYC:18498\n$EBE9:85 47     STA $47 = #$47                       A:EB X:02 Y:AD P:A5 SP:FB PPU:237,162 CYC:18500\n$EBEB:20 B1 FA  JSR $FAB1                          A:EB X:02 Y:AD P:A5 SP:FB PPU:246,162 CYC:18503\n$FAB1:24 01     BIT $01 = #$FF                       A:EB X:02 Y:AD P:A5 SP:F9 PPU:264,162 CYC:18509\n$FAB3:18        CLC                                A:EB X:02 Y:AD P:E5 SP:F9 PPU:273,162 CYC:18512\n$FAB4:A9 40     LDA #$40                           A:EB X:02 Y:AD P:NVUbdIzc SP:F9 PPU:279,162 CYC:18514\n$FAB6:60        RTS                                A:40 X:02 Y:AD P:64 SP:F9 PPU:285,162 CYC:18516\n$EBEE:E7 47    *ISB $47 = #$EB                       A:40 X:02 Y:AD P:64 SP:FB PPU:303,162 CYC:18522\n$EBF0:EA        NOP                                A:53 X:02 Y:AD P:nvUbdIzc SP:FB PPU:318,162 CYC:18527\n$EBF1:EA        NOP                                A:53 X:02 Y:AD P:nvUbdIzc SP:FB PPU:324,162 CYC:18529\n$EBF2:EA        NOP                                A:53 X:02 Y:AD P:nvUbdIzc SP:FB PPU:330,162 CYC:18531\n$EBF3:EA        NOP                                A:53 X:02 Y:AD P:nvUbdIzc SP:FB PPU:336,162 CYC:18533\n$EBF4:20 B7 FA  JSR $FAB7                          A:53 X:02 Y:AD P:nvUbdIzc SP:FB PPU:  1,163 CYC:18535\n$FAB7:70 2D     BVS $FAE6                          A:53 X:02 Y:AD P:nvUbdIzc SP:F9 PPU: 19,163 CYC:18541\n$FAB9:B0 2B     BCS $FAE6                          A:53 X:02 Y:AD P:nvUbdIzc SP:F9 PPU: 25,163 CYC:18543\n$FABB:30 29     BMI $FAE6                          A:53 X:02 Y:AD P:nvUbdIzc SP:F9 PPU: 31,163 CYC:18545\n$FABD:C9 53     CMP #$53                           A:53 X:02 Y:AD P:nvUbdIzc SP:F9 PPU: 37,163 CYC:18547\n$FABF:D0 25     BNE $FAE6                          A:53 X:02 Y:AD P:nvUbdIZC SP:F9 PPU: 43,163 CYC:18549\n$FAC1:60        RTS                                A:53 X:02 Y:AD P:nvUbdIZC SP:F9 PPU: 49,163 CYC:18551\n$EBF7:A5 47     LDA $47 = #$EC                       A:53 X:02 Y:AD P:nvUbdIZC SP:FB PPU: 67,163 CYC:18557\n$EBF9:C9 EC     CMP #$EC                           A:EC X:02 Y:AD P:A5 SP:FB PPU: 76,163 CYC:18560\n$EBFB:F0 02     BEQ $EBFF                          A:EC X:02 Y:AD P:nvUbdIZC SP:FB PPU: 82,163 CYC:18562\n$EBFF:C8        INY                                A:EC X:02 Y:AD P:nvUbdIZC SP:FB PPU: 91,163 CYC:18565\n$EC00:A9 FF     LDA #$FF                           A:EC X:02 Y:AE P:A5 SP:FB PPU: 97,163 CYC:18567\n$EC02:85 47     STA $47 = #$EC                       A:FF X:02 Y:AE P:A5 SP:FB PPU:103,163 CYC:18569\n$EC04:20 C2 FA  JSR $FAC2                          A:FF X:02 Y:AE P:A5 SP:FB PPU:112,163 CYC:18572\n$FAC2:B8        CLV                                A:FF X:02 Y:AE P:A5 SP:F9 PPU:130,163 CYC:18578\n$FAC3:38        SEC                                A:FF X:02 Y:AE P:A5 SP:F9 PPU:136,163 CYC:18580\n$FAC4:A9 FF     LDA #$FF                           A:FF X:02 Y:AE P:A5 SP:F9 PPU:142,163 CYC:18582\n$FAC6:60        RTS                                A:FF X:02 Y:AE P:A5 SP:F9 PPU:148,163 CYC:18584\n$EC07:E7 47    *ISB $47 = #$FF                       A:FF X:02 Y:AE P:A5 SP:FB PPU:166,163 CYC:18590\n$EC09:EA        NOP                                A:FF X:02 Y:AE P:A5 SP:FB PPU:181,163 CYC:18595\n$EC0A:EA        NOP                                A:FF X:02 Y:AE P:A5 SP:FB PPU:187,163 CYC:18597\n$EC0B:EA        NOP                                A:FF X:02 Y:AE P:A5 SP:FB PPU:193,163 CYC:18599\n$EC0C:EA        NOP                                A:FF X:02 Y:AE P:A5 SP:FB PPU:199,163 CYC:18601\n$EC0D:20 C7 FA  JSR $FAC7                          A:FF X:02 Y:AE P:A5 SP:FB PPU:205,163 CYC:18603\n$FAC7:70 1D     BVS $FAE6                          A:FF X:02 Y:AE P:A5 SP:F9 PPU:223,163 CYC:18609\n$FAC9:F0 1B     BEQ $FAE6                          A:FF X:02 Y:AE P:A5 SP:F9 PPU:229,163 CYC:18611\n$FACB:10 19     BPL $FAE6                          A:FF X:02 Y:AE P:A5 SP:F9 PPU:235,163 CYC:18613\n$FACD:90 17     BCC $FAE6                          A:FF X:02 Y:AE P:A5 SP:F9 PPU:241,163 CYC:18615\n$FACF:C9 FF     CMP #$FF                           A:FF X:02 Y:AE P:A5 SP:F9 PPU:247,163 CYC:18617\n$FAD1:D0 13     BNE $FAE6                          A:FF X:02 Y:AE P:nvUbdIZC SP:F9 PPU:253,163 CYC:18619\n$FAD3:60        RTS                                A:FF X:02 Y:AE P:nvUbdIZC SP:F9 PPU:259,163 CYC:18621\n$EC10:A5 47     LDA $47 = #$00                       A:FF X:02 Y:AE P:nvUbdIZC SP:FB PPU:277,163 CYC:18627\n$EC12:C9 00     CMP #$00                           A:00 X:02 Y:AE P:nvUbdIZC SP:FB PPU:286,163 CYC:18630\n$EC14:F0 02     BEQ $EC18                          A:00 X:02 Y:AE P:nvUbdIZC SP:FB PPU:292,163 CYC:18632\n$EC18:C8        INY                                A:00 X:02 Y:AE P:nvUbdIZC SP:FB PPU:301,163 CYC:18635\n$EC19:A9 37     LDA #$37                           A:00 X:02 Y:AF P:A5 SP:FB PPU:307,163 CYC:18637\n$EC1B:85 47     STA $47 = #$00                       A:37 X:02 Y:AF P:25 SP:FB PPU:313,163 CYC:18639\n$EC1D:20 D4 FA  JSR $FAD4                          A:37 X:02 Y:AF P:25 SP:FB PPU:322,163 CYC:18642\n$FAD4:24 01     BIT $01 = #$FF                       A:37 X:02 Y:AF P:25 SP:F9 PPU:340,163 CYC:18648\n$FAD6:38        SEC                                A:37 X:02 Y:AF P:E5 SP:F9 PPU:  8,164 CYC:18651\n$FAD7:A9 F0     LDA #$F0                           A:37 X:02 Y:AF P:E5 SP:F9 PPU: 14,164 CYC:18653\n$FAD9:60        RTS                                A:F0 X:02 Y:AF P:E5 SP:F9 PPU: 20,164 CYC:18655\n$EC20:E7 47    *ISB $47 = #$37                       A:F0 X:02 Y:AF P:E5 SP:FB PPU: 38,164 CYC:18661\n$EC22:EA        NOP                                A:B8 X:02 Y:AF P:A5 SP:FB PPU: 53,164 CYC:18666\n$EC23:EA        NOP                                A:B8 X:02 Y:AF P:A5 SP:FB PPU: 59,164 CYC:18668\n$EC24:EA        NOP                                A:B8 X:02 Y:AF P:A5 SP:FB PPU: 65,164 CYC:18670\n$EC25:EA        NOP                                A:B8 X:02 Y:AF P:A5 SP:FB PPU: 71,164 CYC:18672\n$EC26:20 DA FA  JSR $FADA                          A:B8 X:02 Y:AF P:A5 SP:FB PPU: 77,164 CYC:18674\n$FADA:70 0A     BVS $FAE6                          A:B8 X:02 Y:AF P:A5 SP:F9 PPU: 95,164 CYC:18680\n$FADC:F0 08     BEQ $FAE6                          A:B8 X:02 Y:AF P:A5 SP:F9 PPU:101,164 CYC:18682\n$FADE:10 06     BPL $FAE6                          A:B8 X:02 Y:AF P:A5 SP:F9 PPU:107,164 CYC:18684\n$FAE0:90 04     BCC $FAE6                          A:B8 X:02 Y:AF P:A5 SP:F9 PPU:113,164 CYC:18686\n$FAE2:C9 B8     CMP #$B8                           A:B8 X:02 Y:AF P:A5 SP:F9 PPU:119,164 CYC:18688\n$FAE4:F0 02     BEQ $FAE8                          A:B8 X:02 Y:AF P:nvUbdIZC SP:F9 PPU:125,164 CYC:18690\n$FAE8:60        RTS                                A:B8 X:02 Y:AF P:nvUbdIZC SP:F9 PPU:134,164 CYC:18693\n$EC29:A5 47     LDA $47 = #$38                       A:B8 X:02 Y:AF P:nvUbdIZC SP:FB PPU:152,164 CYC:18699\n$EC2B:C9 38     CMP #$38                           A:38 X:02 Y:AF P:25 SP:FB PPU:161,164 CYC:18702\n$EC2D:F0 02     BEQ $EC31                          A:38 X:02 Y:AF P:nvUbdIZC SP:FB PPU:167,164 CYC:18704\n$EC31:C8        INY                                A:38 X:02 Y:AF P:nvUbdIZC SP:FB PPU:176,164 CYC:18707\n$EC32:A9 EB     LDA #$EB                           A:38 X:02 Y:B0 P:A5 SP:FB PPU:182,164 CYC:18709\n$EC34:8D 47 06  STA $0647 = #$38                     A:EB X:02 Y:B0 P:A5 SP:FB PPU:188,164 CYC:18711\n$EC37:20 B1 FA  JSR $FAB1                          A:EB X:02 Y:B0 P:A5 SP:FB PPU:200,164 CYC:18715\n$FAB1:24 01     BIT $01 = #$FF                       A:EB X:02 Y:B0 P:A5 SP:F9 PPU:218,164 CYC:18721\n$FAB3:18        CLC                                A:EB X:02 Y:B0 P:E5 SP:F9 PPU:227,164 CYC:18724\n$FAB4:A9 40     LDA #$40                           A:EB X:02 Y:B0 P:NVUbdIzc SP:F9 PPU:233,164 CYC:18726\n$FAB6:60        RTS                                A:40 X:02 Y:B0 P:64 SP:F9 PPU:239,164 CYC:18728\n$EC3A:EF 47 06 *ISB $0647 = #$EB                     A:40 X:02 Y:B0 P:64 SP:FB PPU:257,164 CYC:18734\n$EC3D:EA        NOP                                A:53 X:02 Y:B0 P:nvUbdIzc SP:FB PPU:275,164 CYC:18740\n$EC3E:EA        NOP                                A:53 X:02 Y:B0 P:nvUbdIzc SP:FB PPU:281,164 CYC:18742\n$EC3F:EA        NOP                                A:53 X:02 Y:B0 P:nvUbdIzc SP:FB PPU:287,164 CYC:18744\n$EC40:EA        NOP                                A:53 X:02 Y:B0 P:nvUbdIzc SP:FB PPU:293,164 CYC:18746\n$EC41:20 B7 FA  JSR $FAB7                          A:53 X:02 Y:B0 P:nvUbdIzc SP:FB PPU:299,164 CYC:18748\n$FAB7:70 2D     BVS $FAE6                          A:53 X:02 Y:B0 P:nvUbdIzc SP:F9 PPU:317,164 CYC:18754\n$FAB9:B0 2B     BCS $FAE6                          A:53 X:02 Y:B0 P:nvUbdIzc SP:F9 PPU:323,164 CYC:18756\n$FABB:30 29     BMI $FAE6                          A:53 X:02 Y:B0 P:nvUbdIzc SP:F9 PPU:329,164 CYC:18758\n$FABD:C9 53     CMP #$53                           A:53 X:02 Y:B0 P:nvUbdIzc SP:F9 PPU:335,164 CYC:18760\n$FABF:D0 25     BNE $FAE6                          A:53 X:02 Y:B0 P:nvUbdIZC SP:F9 PPU:  0,165 CYC:18762\n$FAC1:60        RTS                                A:53 X:02 Y:B0 P:nvUbdIZC SP:F9 PPU:  6,165 CYC:18764\n$EC44:AD 47 06  LDA $0647 = #$EC                     A:53 X:02 Y:B0 P:nvUbdIZC SP:FB PPU: 24,165 CYC:18770\n$EC47:C9 EC     CMP #$EC                           A:EC X:02 Y:B0 P:A5 SP:FB PPU: 36,165 CYC:18774\n$EC49:F0 02     BEQ $EC4D                          A:EC X:02 Y:B0 P:nvUbdIZC SP:FB PPU: 42,165 CYC:18776\n$EC4D:C8        INY                                A:EC X:02 Y:B0 P:nvUbdIZC SP:FB PPU: 51,165 CYC:18779\n$EC4E:A9 FF     LDA #$FF                           A:EC X:02 Y:B1 P:A5 SP:FB PPU: 57,165 CYC:18781\n$EC50:8D 47 06  STA $0647 = #$EC                     A:FF X:02 Y:B1 P:A5 SP:FB PPU: 63,165 CYC:18783\n$EC53:20 C2 FA  JSR $FAC2                          A:FF X:02 Y:B1 P:A5 SP:FB PPU: 75,165 CYC:18787\n$FAC2:B8        CLV                                A:FF X:02 Y:B1 P:A5 SP:F9 PPU: 93,165 CYC:18793\n$FAC3:38        SEC                                A:FF X:02 Y:B1 P:A5 SP:F9 PPU: 99,165 CYC:18795\n$FAC4:A9 FF     LDA #$FF                           A:FF X:02 Y:B1 P:A5 SP:F9 PPU:105,165 CYC:18797\n$FAC6:60        RTS                                A:FF X:02 Y:B1 P:A5 SP:F9 PPU:111,165 CYC:18799\n$EC56:EF 47 06 *ISB $0647 = #$FF                     A:FF X:02 Y:B1 P:A5 SP:FB PPU:129,165 CYC:18805\n$EC59:EA        NOP                                A:FF X:02 Y:B1 P:A5 SP:FB PPU:147,165 CYC:18811\n$EC5A:EA        NOP                                A:FF X:02 Y:B1 P:A5 SP:FB PPU:153,165 CYC:18813\n$EC5B:EA        NOP                                A:FF X:02 Y:B1 P:A5 SP:FB PPU:159,165 CYC:18815\n$EC5C:EA        NOP                                A:FF X:02 Y:B1 P:A5 SP:FB PPU:165,165 CYC:18817\n$EC5D:20 C7 FA  JSR $FAC7                          A:FF X:02 Y:B1 P:A5 SP:FB PPU:171,165 CYC:18819\n$FAC7:70 1D     BVS $FAE6                          A:FF X:02 Y:B1 P:A5 SP:F9 PPU:189,165 CYC:18825\n$FAC9:F0 1B     BEQ $FAE6                          A:FF X:02 Y:B1 P:A5 SP:F9 PPU:195,165 CYC:18827\n$FACB:10 19     BPL $FAE6                          A:FF X:02 Y:B1 P:A5 SP:F9 PPU:201,165 CYC:18829\n$FACD:90 17     BCC $FAE6                          A:FF X:02 Y:B1 P:A5 SP:F9 PPU:207,165 CYC:18831\n$FACF:C9 FF     CMP #$FF                           A:FF X:02 Y:B1 P:A5 SP:F9 PPU:213,165 CYC:18833\n$FAD1:D0 13     BNE $FAE6                          A:FF X:02 Y:B1 P:nvUbdIZC SP:F9 PPU:219,165 CYC:18835\n$FAD3:60        RTS                                A:FF X:02 Y:B1 P:nvUbdIZC SP:F9 PPU:225,165 CYC:18837\n$EC60:AD 47 06  LDA $0647 = #$00                     A:FF X:02 Y:B1 P:nvUbdIZC SP:FB PPU:243,165 CYC:18843\n$EC63:C9 00     CMP #$00                           A:00 X:02 Y:B1 P:nvUbdIZC SP:FB PPU:255,165 CYC:18847\n$EC65:F0 02     BEQ $EC69                          A:00 X:02 Y:B1 P:nvUbdIZC SP:FB PPU:261,165 CYC:18849\n$EC69:C8        INY                                A:00 X:02 Y:B1 P:nvUbdIZC SP:FB PPU:270,165 CYC:18852\n$EC6A:A9 37     LDA #$37                           A:00 X:02 Y:B2 P:A5 SP:FB PPU:276,165 CYC:18854\n$EC6C:8D 47 06  STA $0647 = #$00                     A:37 X:02 Y:B2 P:25 SP:FB PPU:282,165 CYC:18856\n$EC6F:20 D4 FA  JSR $FAD4                          A:37 X:02 Y:B2 P:25 SP:FB PPU:294,165 CYC:18860\n$FAD4:24 01     BIT $01 = #$FF                       A:37 X:02 Y:B2 P:25 SP:F9 PPU:312,165 CYC:18866\n$FAD6:38        SEC                                A:37 X:02 Y:B2 P:E5 SP:F9 PPU:321,165 CYC:18869\n$FAD7:A9 F0     LDA #$F0                           A:37 X:02 Y:B2 P:E5 SP:F9 PPU:327,165 CYC:18871\n$FAD9:60        RTS                                A:F0 X:02 Y:B2 P:E5 SP:F9 PPU:333,165 CYC:18873\n$EC72:EF 47 06 *ISB $0647 = #$37                     A:F0 X:02 Y:B2 P:E5 SP:FB PPU: 10,166 CYC:18879\n$EC75:EA        NOP                                A:B8 X:02 Y:B2 P:A5 SP:FB PPU: 28,166 CYC:18885\n$EC76:EA        NOP                                A:B8 X:02 Y:B2 P:A5 SP:FB PPU: 34,166 CYC:18887\n$EC77:EA        NOP                                A:B8 X:02 Y:B2 P:A5 SP:FB PPU: 40,166 CYC:18889\n$EC78:EA        NOP                                A:B8 X:02 Y:B2 P:A5 SP:FB PPU: 46,166 CYC:18891\n$EC79:20 DA FA  JSR $FADA                          A:B8 X:02 Y:B2 P:A5 SP:FB PPU: 52,166 CYC:18893\n$FADA:70 0A     BVS $FAE6                          A:B8 X:02 Y:B2 P:A5 SP:F9 PPU: 70,166 CYC:18899\n$FADC:F0 08     BEQ $FAE6                          A:B8 X:02 Y:B2 P:A5 SP:F9 PPU: 76,166 CYC:18901\n$FADE:10 06     BPL $FAE6                          A:B8 X:02 Y:B2 P:A5 SP:F9 PPU: 82,166 CYC:18903\n$FAE0:90 04     BCC $FAE6                          A:B8 X:02 Y:B2 P:A5 SP:F9 PPU: 88,166 CYC:18905\n$FAE2:C9 B8     CMP #$B8                           A:B8 X:02 Y:B2 P:A5 SP:F9 PPU: 94,166 CYC:18907\n$FAE4:F0 02     BEQ $FAE8                          A:B8 X:02 Y:B2 P:nvUbdIZC SP:F9 PPU:100,166 CYC:18909\n$FAE8:60        RTS                                A:B8 X:02 Y:B2 P:nvUbdIZC SP:F9 PPU:109,166 CYC:18912\n$EC7C:AD 47 06  LDA $0647 = #$38                     A:B8 X:02 Y:B2 P:nvUbdIZC SP:FB PPU:127,166 CYC:18918\n$EC7F:C9 38     CMP #$38                           A:38 X:02 Y:B2 P:25 SP:FB PPU:139,166 CYC:18922\n$EC81:F0 02     BEQ $EC85                          A:38 X:02 Y:B2 P:nvUbdIZC SP:FB PPU:145,166 CYC:18924\n$EC85:A9 EB     LDA #$EB                           A:38 X:02 Y:B2 P:nvUbdIZC SP:FB PPU:154,166 CYC:18927\n$EC87:8D 47 06  STA $0647 = #$38                     A:EB X:02 Y:B2 P:A5 SP:FB PPU:160,166 CYC:18929\n$EC8A:A9 48     LDA #$48                           A:EB X:02 Y:B2 P:A5 SP:FB PPU:172,166 CYC:18933\n$EC8C:85 45     STA $45 = #$48                       A:48 X:02 Y:B2 P:25 SP:FB PPU:178,166 CYC:18935\n$EC8E:A9 05     LDA #$05                           A:48 X:02 Y:B2 P:25 SP:FB PPU:187,166 CYC:18938\n$EC90:85 46     STA $46 = #$05                       A:05 X:02 Y:B2 P:25 SP:FB PPU:193,166 CYC:18940\n$EC92:A0 FF     LDY #$FF                           A:05 X:02 Y:B2 P:25 SP:FB PPU:202,166 CYC:18943\n$EC94:20 B1 FA  JSR $FAB1                          A:05 X:02 Y:FF P:A5 SP:FB PPU:208,166 CYC:18945\n$FAB1:24 01     BIT $01 = #$FF                       A:05 X:02 Y:FF P:A5 SP:F9 PPU:226,166 CYC:18951\n$FAB3:18        CLC                                A:05 X:02 Y:FF P:E5 SP:F9 PPU:235,166 CYC:18954\n$FAB4:A9 40     LDA #$40                           A:05 X:02 Y:FF P:NVUbdIzc SP:F9 PPU:241,166 CYC:18956\n$FAB6:60        RTS                                A:40 X:02 Y:FF P:64 SP:F9 PPU:247,166 CYC:18958\n$EC97:F3 45    *ISB ($45),Y = #$0548 @ 0647 = EB     A:40 X:02 Y:FF P:64 SP:FB PPU:265,166 CYC:18964\n$EC99:EA        NOP                                A:53 X:02 Y:FF P:nvUbdIzc SP:FB PPU:289,166 CYC:18972\n$EC9A:EA        NOP                                A:53 X:02 Y:FF P:nvUbdIzc SP:FB PPU:295,166 CYC:18974\n$EC9B:08        PHP                                A:53 X:02 Y:FF P:nvUbdIzc SP:FB PPU:301,166 CYC:18976\n$EC9C:48        PHA                                A:53 X:02 Y:FF P:nvUbdIzc SP:FA PPU:310,166 CYC:18979\n$EC9D:A0 B3     LDY #$B3                           A:53 X:02 Y:FF P:nvUbdIzc SP:F9 PPU:319,166 CYC:18982\n$EC9F:68        PLA                                A:53 X:02 Y:B3 P:NvUbdIzc SP:F9 PPU:325,166 CYC:18984\n$ECA0:28        PLP                                A:53 X:02 Y:B3 P:nvUbdIzc SP:FA PPU:337,166 CYC:18988\n$ECA1:20 B7 FA  JSR $FAB7                          A:53 X:02 Y:B3 P:nvUbdIzc SP:FB PPU:  8,167 CYC:18992\n$FAB7:70 2D     BVS $FAE6                          A:53 X:02 Y:B3 P:nvUbdIzc SP:F9 PPU: 26,167 CYC:18998\n$FAB9:B0 2B     BCS $FAE6                          A:53 X:02 Y:B3 P:nvUbdIzc SP:F9 PPU: 32,167 CYC:19000\n$FABB:30 29     BMI $FAE6                          A:53 X:02 Y:B3 P:nvUbdIzc SP:F9 PPU: 38,167 CYC:19002\n$FABD:C9 53     CMP #$53                           A:53 X:02 Y:B3 P:nvUbdIzc SP:F9 PPU: 44,167 CYC:19004\n$FABF:D0 25     BNE $FAE6                          A:53 X:02 Y:B3 P:nvUbdIZC SP:F9 PPU: 50,167 CYC:19006\n$FAC1:60        RTS                                A:53 X:02 Y:B3 P:nvUbdIZC SP:F9 PPU: 56,167 CYC:19008\n$ECA4:AD 47 06  LDA $0647 = #$EC                     A:53 X:02 Y:B3 P:nvUbdIZC SP:FB PPU: 74,167 CYC:19014\n$ECA7:C9 EC     CMP #$EC                           A:EC X:02 Y:B3 P:A5 SP:FB PPU: 86,167 CYC:19018\n$ECA9:F0 02     BEQ $ECAD                          A:EC X:02 Y:B3 P:nvUbdIZC SP:FB PPU: 92,167 CYC:19020\n$ECAD:A0 FF     LDY #$FF                           A:EC X:02 Y:B3 P:nvUbdIZC SP:FB PPU:101,167 CYC:19023\n$ECAF:A9 FF     LDA #$FF                           A:EC X:02 Y:FF P:A5 SP:FB PPU:107,167 CYC:19025\n$ECB1:8D 47 06  STA $0647 = #$EC                     A:FF X:02 Y:FF P:A5 SP:FB PPU:113,167 CYC:19027\n$ECB4:20 C2 FA  JSR $FAC2                          A:FF X:02 Y:FF P:A5 SP:FB PPU:125,167 CYC:19031\n$FAC2:B8        CLV                                A:FF X:02 Y:FF P:A5 SP:F9 PPU:143,167 CYC:19037\n$FAC3:38        SEC                                A:FF X:02 Y:FF P:A5 SP:F9 PPU:149,167 CYC:19039\n$FAC4:A9 FF     LDA #$FF                           A:FF X:02 Y:FF P:A5 SP:F9 PPU:155,167 CYC:19041\n$FAC6:60        RTS                                A:FF X:02 Y:FF P:A5 SP:F9 PPU:161,167 CYC:19043\n$ECB7:F3 45    *ISB ($45),Y = #$0548 @ 0647 = FF     A:FF X:02 Y:FF P:A5 SP:FB PPU:179,167 CYC:19049\n$ECB9:EA        NOP                                A:FF X:02 Y:FF P:A5 SP:FB PPU:203,167 CYC:19057\n$ECBA:EA        NOP                                A:FF X:02 Y:FF P:A5 SP:FB PPU:209,167 CYC:19059\n$ECBB:08        PHP                                A:FF X:02 Y:FF P:A5 SP:FB PPU:215,167 CYC:19061\n$ECBC:48        PHA                                A:FF X:02 Y:FF P:A5 SP:FA PPU:224,167 CYC:19064\n$ECBD:A0 B4     LDY #$B4                           A:FF X:02 Y:FF P:A5 SP:F9 PPU:233,167 CYC:19067\n$ECBF:68        PLA                                A:FF X:02 Y:B4 P:A5 SP:F9 PPU:239,167 CYC:19069\n$ECC0:28        PLP                                A:FF X:02 Y:B4 P:A5 SP:FA PPU:251,167 CYC:19073\n$ECC1:20 C7 FA  JSR $FAC7                          A:FF X:02 Y:B4 P:A5 SP:FB PPU:263,167 CYC:19077\n$FAC7:70 1D     BVS $FAE6                          A:FF X:02 Y:B4 P:A5 SP:F9 PPU:281,167 CYC:19083\n$FAC9:F0 1B     BEQ $FAE6                          A:FF X:02 Y:B4 P:A5 SP:F9 PPU:287,167 CYC:19085\n$FACB:10 19     BPL $FAE6                          A:FF X:02 Y:B4 P:A5 SP:F9 PPU:293,167 CYC:19087\n$FACD:90 17     BCC $FAE6                          A:FF X:02 Y:B4 P:A5 SP:F9 PPU:299,167 CYC:19089\n$FACF:C9 FF     CMP #$FF                           A:FF X:02 Y:B4 P:A5 SP:F9 PPU:305,167 CYC:19091\n$FAD1:D0 13     BNE $FAE6                          A:FF X:02 Y:B4 P:nvUbdIZC SP:F9 PPU:311,167 CYC:19093\n$FAD3:60        RTS                                A:FF X:02 Y:B4 P:nvUbdIZC SP:F9 PPU:317,167 CYC:19095\n$ECC4:AD 47 06  LDA $0647 = #$00                     A:FF X:02 Y:B4 P:nvUbdIZC SP:FB PPU:335,167 CYC:19101\n$ECC7:C9 00     CMP #$00                           A:00 X:02 Y:B4 P:nvUbdIZC SP:FB PPU:  6,168 CYC:19105\n$ECC9:F0 02     BEQ $ECCD                          A:00 X:02 Y:B4 P:nvUbdIZC SP:FB PPU: 12,168 CYC:19107\n$ECCD:A0 FF     LDY #$FF                           A:00 X:02 Y:B4 P:nvUbdIZC SP:FB PPU: 21,168 CYC:19110\n$ECCF:A9 37     LDA #$37                           A:00 X:02 Y:FF P:A5 SP:FB PPU: 27,168 CYC:19112\n$ECD1:8D 47 06  STA $0647 = #$00                     A:37 X:02 Y:FF P:25 SP:FB PPU: 33,168 CYC:19114\n$ECD4:20 D4 FA  JSR $FAD4                          A:37 X:02 Y:FF P:25 SP:FB PPU: 45,168 CYC:19118\n$FAD4:24 01     BIT $01 = #$FF                       A:37 X:02 Y:FF P:25 SP:F9 PPU: 63,168 CYC:19124\n$FAD6:38        SEC                                A:37 X:02 Y:FF P:E5 SP:F9 PPU: 72,168 CYC:19127\n$FAD7:A9 F0     LDA #$F0                           A:37 X:02 Y:FF P:E5 SP:F9 PPU: 78,168 CYC:19129\n$FAD9:60        RTS                                A:F0 X:02 Y:FF P:E5 SP:F9 PPU: 84,168 CYC:19131\n$ECD7:F3 45    *ISB ($45),Y = #$0548 @ 0647 = 37     A:F0 X:02 Y:FF P:E5 SP:FB PPU:102,168 CYC:19137\n$ECD9:EA        NOP                                A:B8 X:02 Y:FF P:A5 SP:FB PPU:126,168 CYC:19145\n$ECDA:EA        NOP                                A:B8 X:02 Y:FF P:A5 SP:FB PPU:132,168 CYC:19147\n$ECDB:08        PHP                                A:B8 X:02 Y:FF P:A5 SP:FB PPU:138,168 CYC:19149\n$ECDC:48        PHA                                A:B8 X:02 Y:FF P:A5 SP:FA PPU:147,168 CYC:19152\n$ECDD:A0 B5     LDY #$B5                           A:B8 X:02 Y:FF P:A5 SP:F9 PPU:156,168 CYC:19155\n$ECDF:68        PLA                                A:B8 X:02 Y:B5 P:A5 SP:F9 PPU:162,168 CYC:19157\n$ECE0:28        PLP                                A:B8 X:02 Y:B5 P:A5 SP:FA PPU:174,168 CYC:19161\n$ECE1:20 DA FA  JSR $FADA                          A:B8 X:02 Y:B5 P:A5 SP:FB PPU:186,168 CYC:19165\n$FADA:70 0A     BVS $FAE6                          A:B8 X:02 Y:B5 P:A5 SP:F9 PPU:204,168 CYC:19171\n$FADC:F0 08     BEQ $FAE6                          A:B8 X:02 Y:B5 P:A5 SP:F9 PPU:210,168 CYC:19173\n$FADE:10 06     BPL $FAE6                          A:B8 X:02 Y:B5 P:A5 SP:F9 PPU:216,168 CYC:19175\n$FAE0:90 04     BCC $FAE6                          A:B8 X:02 Y:B5 P:A5 SP:F9 PPU:222,168 CYC:19177\n$FAE2:C9 B8     CMP #$B8                           A:B8 X:02 Y:B5 P:A5 SP:F9 PPU:228,168 CYC:19179\n$FAE4:F0 02     BEQ $FAE8                          A:B8 X:02 Y:B5 P:nvUbdIZC SP:F9 PPU:234,168 CYC:19181\n$FAE8:60        RTS                                A:B8 X:02 Y:B5 P:nvUbdIZC SP:F9 PPU:243,168 CYC:19184\n$ECE4:AD 47 06  LDA $0647 = #$38                     A:B8 X:02 Y:B5 P:nvUbdIZC SP:FB PPU:261,168 CYC:19190\n$ECE7:C9 38     CMP #$38                           A:38 X:02 Y:B5 P:25 SP:FB PPU:273,168 CYC:19194\n$ECE9:F0 02     BEQ $ECED                          A:38 X:02 Y:B5 P:nvUbdIZC SP:FB PPU:279,168 CYC:19196\n$ECED:A0 B6     LDY #$B6                           A:38 X:02 Y:B5 P:nvUbdIZC SP:FB PPU:288,168 CYC:19199\n$ECEF:A2 FF     LDX #$FF                           A:38 X:02 Y:B6 P:A5 SP:FB PPU:294,168 CYC:19201\n$ECF1:A9 EB     LDA #$EB                           A:38 X:FF Y:B6 P:A5 SP:FB PPU:300,168 CYC:19203\n$ECF3:85 47     STA $47 = #$38                       A:EB X:FF Y:B6 P:A5 SP:FB PPU:306,168 CYC:19205\n$ECF5:20 B1 FA  JSR $FAB1                          A:EB X:FF Y:B6 P:A5 SP:FB PPU:315,168 CYC:19208\n$FAB1:24 01     BIT $01 = #$FF                       A:EB X:FF Y:B6 P:A5 SP:F9 PPU:333,168 CYC:19214\n$FAB3:18        CLC                                A:EB X:FF Y:B6 P:E5 SP:F9 PPU:  1,169 CYC:19217\n$FAB4:A9 40     LDA #$40                           A:EB X:FF Y:B6 P:NVUbdIzc SP:F9 PPU:  7,169 CYC:19219\n$FAB6:60        RTS                                A:40 X:FF Y:B6 P:64 SP:F9 PPU: 13,169 CYC:19221\n$ECF8:F7 48    *ISB $48,X @ 47 = #$EB                A:40 X:FF Y:B6 P:64 SP:FB PPU: 31,169 CYC:19227\n$ECFA:EA        NOP                                A:53 X:FF Y:B6 P:nvUbdIzc SP:FB PPU: 49,169 CYC:19233\n$ECFB:EA        NOP                                A:53 X:FF Y:B6 P:nvUbdIzc SP:FB PPU: 55,169 CYC:19235\n$ECFC:EA        NOP                                A:53 X:FF Y:B6 P:nvUbdIzc SP:FB PPU: 61,169 CYC:19237\n$ECFD:EA        NOP                                A:53 X:FF Y:B6 P:nvUbdIzc SP:FB PPU: 67,169 CYC:19239\n$ECFE:20 B7 FA  JSR $FAB7                          A:53 X:FF Y:B6 P:nvUbdIzc SP:FB PPU: 73,169 CYC:19241\n$FAB7:70 2D     BVS $FAE6                          A:53 X:FF Y:B6 P:nvUbdIzc SP:F9 PPU: 91,169 CYC:19247\n$FAB9:B0 2B     BCS $FAE6                          A:53 X:FF Y:B6 P:nvUbdIzc SP:F9 PPU: 97,169 CYC:19249\n$FABB:30 29     BMI $FAE6                          A:53 X:FF Y:B6 P:nvUbdIzc SP:F9 PPU:103,169 CYC:19251\n$FABD:C9 53     CMP #$53                           A:53 X:FF Y:B6 P:nvUbdIzc SP:F9 PPU:109,169 CYC:19253\n$FABF:D0 25     BNE $FAE6                          A:53 X:FF Y:B6 P:nvUbdIZC SP:F9 PPU:115,169 CYC:19255\n$FAC1:60        RTS                                A:53 X:FF Y:B6 P:nvUbdIZC SP:F9 PPU:121,169 CYC:19257\n$ED01:A5 47     LDA $47 = #$EC                       A:53 X:FF Y:B6 P:nvUbdIZC SP:FB PPU:139,169 CYC:19263\n$ED03:C9 EC     CMP #$EC                           A:EC X:FF Y:B6 P:A5 SP:FB PPU:148,169 CYC:19266\n$ED05:F0 02     BEQ $ED09                          A:EC X:FF Y:B6 P:nvUbdIZC SP:FB PPU:154,169 CYC:19268\n$ED09:C8        INY                                A:EC X:FF Y:B6 P:nvUbdIZC SP:FB PPU:163,169 CYC:19271\n$ED0A:A9 FF     LDA #$FF                           A:EC X:FF Y:B7 P:A5 SP:FB PPU:169,169 CYC:19273\n$ED0C:85 47     STA $47 = #$EC                       A:FF X:FF Y:B7 P:A5 SP:FB PPU:175,169 CYC:19275\n$ED0E:20 C2 FA  JSR $FAC2                          A:FF X:FF Y:B7 P:A5 SP:FB PPU:184,169 CYC:19278\n$FAC2:B8        CLV                                A:FF X:FF Y:B7 P:A5 SP:F9 PPU:202,169 CYC:19284\n$FAC3:38        SEC                                A:FF X:FF Y:B7 P:A5 SP:F9 PPU:208,169 CYC:19286\n$FAC4:A9 FF     LDA #$FF                           A:FF X:FF Y:B7 P:A5 SP:F9 PPU:214,169 CYC:19288\n$FAC6:60        RTS                                A:FF X:FF Y:B7 P:A5 SP:F9 PPU:220,169 CYC:19290\n$ED11:F7 48    *ISB $48,X @ 47 = #$FF                A:FF X:FF Y:B7 P:A5 SP:FB PPU:238,169 CYC:19296\n$ED13:EA        NOP                                A:FF X:FF Y:B7 P:A5 SP:FB PPU:256,169 CYC:19302\n$ED14:EA        NOP                                A:FF X:FF Y:B7 P:A5 SP:FB PPU:262,169 CYC:19304\n$ED15:EA        NOP                                A:FF X:FF Y:B7 P:A5 SP:FB PPU:268,169 CYC:19306\n$ED16:EA        NOP                                A:FF X:FF Y:B7 P:A5 SP:FB PPU:274,169 CYC:19308\n$ED17:20 C7 FA  JSR $FAC7                          A:FF X:FF Y:B7 P:A5 SP:FB PPU:280,169 CYC:19310\n$FAC7:70 1D     BVS $FAE6                          A:FF X:FF Y:B7 P:A5 SP:F9 PPU:298,169 CYC:19316\n$FAC9:F0 1B     BEQ $FAE6                          A:FF X:FF Y:B7 P:A5 SP:F9 PPU:304,169 CYC:19318\n$FACB:10 19     BPL $FAE6                          A:FF X:FF Y:B7 P:A5 SP:F9 PPU:310,169 CYC:19320\n$FACD:90 17     BCC $FAE6                          A:FF X:FF Y:B7 P:A5 SP:F9 PPU:316,169 CYC:19322\n$FACF:C9 FF     CMP #$FF                           A:FF X:FF Y:B7 P:A5 SP:F9 PPU:322,169 CYC:19324\n$FAD1:D0 13     BNE $FAE6                          A:FF X:FF Y:B7 P:nvUbdIZC SP:F9 PPU:328,169 CYC:19326\n$FAD3:60        RTS                                A:FF X:FF Y:B7 P:nvUbdIZC SP:F9 PPU:334,169 CYC:19328\n$ED1A:A5 47     LDA $47 = #$00                       A:FF X:FF Y:B7 P:nvUbdIZC SP:FB PPU: 11,170 CYC:19334\n$ED1C:C9 00     CMP #$00                           A:00 X:FF Y:B7 P:nvUbdIZC SP:FB PPU: 20,170 CYC:19337\n$ED1E:F0 02     BEQ $ED22                          A:00 X:FF Y:B7 P:nvUbdIZC SP:FB PPU: 26,170 CYC:19339\n$ED22:C8        INY                                A:00 X:FF Y:B7 P:nvUbdIZC SP:FB PPU: 35,170 CYC:19342\n$ED23:A9 37     LDA #$37                           A:00 X:FF Y:B8 P:A5 SP:FB PPU: 41,170 CYC:19344\n$ED25:85 47     STA $47 = #$00                       A:37 X:FF Y:B8 P:25 SP:FB PPU: 47,170 CYC:19346\n$ED27:20 D4 FA  JSR $FAD4                          A:37 X:FF Y:B8 P:25 SP:FB PPU: 56,170 CYC:19349\n$FAD4:24 01     BIT $01 = #$FF                       A:37 X:FF Y:B8 P:25 SP:F9 PPU: 74,170 CYC:19355\n$FAD6:38        SEC                                A:37 X:FF Y:B8 P:E5 SP:F9 PPU: 83,170 CYC:19358\n$FAD7:A9 F0     LDA #$F0                           A:37 X:FF Y:B8 P:E5 SP:F9 PPU: 89,170 CYC:19360\n$FAD9:60        RTS                                A:F0 X:FF Y:B8 P:E5 SP:F9 PPU: 95,170 CYC:19362\n$ED2A:F7 48    *ISB $48,X @ 47 = #$37                A:F0 X:FF Y:B8 P:E5 SP:FB PPU:113,170 CYC:19368\n$ED2C:EA        NOP                                A:B8 X:FF Y:B8 P:A5 SP:FB PPU:131,170 CYC:19374\n$ED2D:EA        NOP                                A:B8 X:FF Y:B8 P:A5 SP:FB PPU:137,170 CYC:19376\n$ED2E:EA        NOP                                A:B8 X:FF Y:B8 P:A5 SP:FB PPU:143,170 CYC:19378\n$ED2F:EA        NOP                                A:B8 X:FF Y:B8 P:A5 SP:FB PPU:149,170 CYC:19380\n$ED30:20 DA FA  JSR $FADA                          A:B8 X:FF Y:B8 P:A5 SP:FB PPU:155,170 CYC:19382\n$FADA:70 0A     BVS $FAE6                          A:B8 X:FF Y:B8 P:A5 SP:F9 PPU:173,170 CYC:19388\n$FADC:F0 08     BEQ $FAE6                          A:B8 X:FF Y:B8 P:A5 SP:F9 PPU:179,170 CYC:19390\n$FADE:10 06     BPL $FAE6                          A:B8 X:FF Y:B8 P:A5 SP:F9 PPU:185,170 CYC:19392\n$FAE0:90 04     BCC $FAE6                          A:B8 X:FF Y:B8 P:A5 SP:F9 PPU:191,170 CYC:19394\n$FAE2:C9 B8     CMP #$B8                           A:B8 X:FF Y:B8 P:A5 SP:F9 PPU:197,170 CYC:19396\n$FAE4:F0 02     BEQ $FAE8                          A:B8 X:FF Y:B8 P:nvUbdIZC SP:F9 PPU:203,170 CYC:19398\n$FAE8:60        RTS                                A:B8 X:FF Y:B8 P:nvUbdIZC SP:F9 PPU:212,170 CYC:19401\n$ED33:A5 47     LDA $47 = #$38                       A:B8 X:FF Y:B8 P:nvUbdIZC SP:FB PPU:230,170 CYC:19407\n$ED35:C9 38     CMP #$38                           A:38 X:FF Y:B8 P:25 SP:FB PPU:239,170 CYC:19410\n$ED37:F0 02     BEQ $ED3B                          A:38 X:FF Y:B8 P:nvUbdIZC SP:FB PPU:245,170 CYC:19412\n$ED3B:A9 EB     LDA #$EB                           A:38 X:FF Y:B8 P:nvUbdIZC SP:FB PPU:254,170 CYC:19415\n$ED3D:8D 47 06  STA $0647 = #$38                     A:EB X:FF Y:B8 P:A5 SP:FB PPU:260,170 CYC:19417\n$ED40:A0 FF     LDY #$FF                           A:EB X:FF Y:B8 P:A5 SP:FB PPU:272,170 CYC:19421\n$ED42:20 B1 FA  JSR $FAB1                          A:EB X:FF Y:FF P:A5 SP:FB PPU:278,170 CYC:19423\n$FAB1:24 01     BIT $01 = #$FF                       A:EB X:FF Y:FF P:A5 SP:F9 PPU:296,170 CYC:19429\n$FAB3:18        CLC                                A:EB X:FF Y:FF P:E5 SP:F9 PPU:305,170 CYC:19432\n$FAB4:A9 40     LDA #$40                           A:EB X:FF Y:FF P:NVUbdIzc SP:F9 PPU:311,170 CYC:19434\n$FAB6:60        RTS                                A:40 X:FF Y:FF P:64 SP:F9 PPU:317,170 CYC:19436\n$ED45:FB 48 05 *ISB $0548,Y @ 0647 = #$EB            A:40 X:FF Y:FF P:64 SP:FB PPU:335,170 CYC:19442\n$ED48:EA        NOP                                A:53 X:FF Y:FF P:nvUbdIzc SP:FB PPU: 15,171 CYC:19449\n$ED49:EA        NOP                                A:53 X:FF Y:FF P:nvUbdIzc SP:FB PPU: 21,171 CYC:19451\n$ED4A:08        PHP                                A:53 X:FF Y:FF P:nvUbdIzc SP:FB PPU: 27,171 CYC:19453\n$ED4B:48        PHA                                A:53 X:FF Y:FF P:nvUbdIzc SP:FA PPU: 36,171 CYC:19456\n$ED4C:A0 B9     LDY #$B9                           A:53 X:FF Y:FF P:nvUbdIzc SP:F9 PPU: 45,171 CYC:19459\n$ED4E:68        PLA                                A:53 X:FF Y:B9 P:NvUbdIzc SP:F9 PPU: 51,171 CYC:19461\n$ED4F:28        PLP                                A:53 X:FF Y:B9 P:nvUbdIzc SP:FA PPU: 63,171 CYC:19465\n$ED50:20 B7 FA  JSR $FAB7                          A:53 X:FF Y:B9 P:nvUbdIzc SP:FB PPU: 75,171 CYC:19469\n$FAB7:70 2D     BVS $FAE6                          A:53 X:FF Y:B9 P:nvUbdIzc SP:F9 PPU: 93,171 CYC:19475\n$FAB9:B0 2B     BCS $FAE6                          A:53 X:FF Y:B9 P:nvUbdIzc SP:F9 PPU: 99,171 CYC:19477\n$FABB:30 29     BMI $FAE6                          A:53 X:FF Y:B9 P:nvUbdIzc SP:F9 PPU:105,171 CYC:19479\n$FABD:C9 53     CMP #$53                           A:53 X:FF Y:B9 P:nvUbdIzc SP:F9 PPU:111,171 CYC:19481\n$FABF:D0 25     BNE $FAE6                          A:53 X:FF Y:B9 P:nvUbdIZC SP:F9 PPU:117,171 CYC:19483\n$FAC1:60        RTS                                A:53 X:FF Y:B9 P:nvUbdIZC SP:F9 PPU:123,171 CYC:19485\n$ED53:AD 47 06  LDA $0647 = #$EC                     A:53 X:FF Y:B9 P:nvUbdIZC SP:FB PPU:141,171 CYC:19491\n$ED56:C9 EC     CMP #$EC                           A:EC X:FF Y:B9 P:A5 SP:FB PPU:153,171 CYC:19495\n$ED58:F0 02     BEQ $ED5C                          A:EC X:FF Y:B9 P:nvUbdIZC SP:FB PPU:159,171 CYC:19497\n$ED5C:A0 FF     LDY #$FF                           A:EC X:FF Y:B9 P:nvUbdIZC SP:FB PPU:168,171 CYC:19500\n$ED5E:A9 FF     LDA #$FF                           A:EC X:FF Y:FF P:A5 SP:FB PPU:174,171 CYC:19502\n$ED60:8D 47 06  STA $0647 = #$EC                     A:FF X:FF Y:FF P:A5 SP:FB PPU:180,171 CYC:19504\n$ED63:20 C2 FA  JSR $FAC2                          A:FF X:FF Y:FF P:A5 SP:FB PPU:192,171 CYC:19508\n$FAC2:B8        CLV                                A:FF X:FF Y:FF P:A5 SP:F9 PPU:210,171 CYC:19514\n$FAC3:38        SEC                                A:FF X:FF Y:FF P:A5 SP:F9 PPU:216,171 CYC:19516\n$FAC4:A9 FF     LDA #$FF                           A:FF X:FF Y:FF P:A5 SP:F9 PPU:222,171 CYC:19518\n$FAC6:60        RTS                                A:FF X:FF Y:FF P:A5 SP:F9 PPU:228,171 CYC:19520\n$ED66:FB 48 05 *ISB $0548,Y @ 0647 = #$FF            A:FF X:FF Y:FF P:A5 SP:FB PPU:246,171 CYC:19526\n$ED69:EA        NOP                                A:FF X:FF Y:FF P:A5 SP:FB PPU:267,171 CYC:19533\n$ED6A:EA        NOP                                A:FF X:FF Y:FF P:A5 SP:FB PPU:273,171 CYC:19535\n$ED6B:08        PHP                                A:FF X:FF Y:FF P:A5 SP:FB PPU:279,171 CYC:19537\n$ED6C:48        PHA                                A:FF X:FF Y:FF P:A5 SP:FA PPU:288,171 CYC:19540\n$ED6D:A0 BA     LDY #$BA                           A:FF X:FF Y:FF P:A5 SP:F9 PPU:297,171 CYC:19543\n$ED6F:68        PLA                                A:FF X:FF Y:BA P:A5 SP:F9 PPU:303,171 CYC:19545\n$ED70:28        PLP                                A:FF X:FF Y:BA P:A5 SP:FA PPU:315,171 CYC:19549\n$ED71:20 C7 FA  JSR $FAC7                          A:FF X:FF Y:BA P:A5 SP:FB PPU:327,171 CYC:19553\n$FAC7:70 1D     BVS $FAE6                          A:FF X:FF Y:BA P:A5 SP:F9 PPU:  4,172 CYC:19559\n$FAC9:F0 1B     BEQ $FAE6                          A:FF X:FF Y:BA P:A5 SP:F9 PPU: 10,172 CYC:19561\n$FACB:10 19     BPL $FAE6                          A:FF X:FF Y:BA P:A5 SP:F9 PPU: 16,172 CYC:19563\n$FACD:90 17     BCC $FAE6                          A:FF X:FF Y:BA P:A5 SP:F9 PPU: 22,172 CYC:19565\n$FACF:C9 FF     CMP #$FF                           A:FF X:FF Y:BA P:A5 SP:F9 PPU: 28,172 CYC:19567\n$FAD1:D0 13     BNE $FAE6                          A:FF X:FF Y:BA P:nvUbdIZC SP:F9 PPU: 34,172 CYC:19569\n$FAD3:60        RTS                                A:FF X:FF Y:BA P:nvUbdIZC SP:F9 PPU: 40,172 CYC:19571\n$ED74:AD 47 06  LDA $0647 = #$00                     A:FF X:FF Y:BA P:nvUbdIZC SP:FB PPU: 58,172 CYC:19577\n$ED77:C9 00     CMP #$00                           A:00 X:FF Y:BA P:nvUbdIZC SP:FB PPU: 70,172 CYC:19581\n$ED79:F0 02     BEQ $ED7D                          A:00 X:FF Y:BA P:nvUbdIZC SP:FB PPU: 76,172 CYC:19583\n$ED7D:A0 FF     LDY #$FF                           A:00 X:FF Y:BA P:nvUbdIZC SP:FB PPU: 85,172 CYC:19586\n$ED7F:A9 37     LDA #$37                           A:00 X:FF Y:FF P:A5 SP:FB PPU: 91,172 CYC:19588\n$ED81:8D 47 06  STA $0647 = #$00                     A:37 X:FF Y:FF P:25 SP:FB PPU: 97,172 CYC:19590\n$ED84:20 D4 FA  JSR $FAD4                          A:37 X:FF Y:FF P:25 SP:FB PPU:109,172 CYC:19594\n$FAD4:24 01     BIT $01 = #$FF                       A:37 X:FF Y:FF P:25 SP:F9 PPU:127,172 CYC:19600\n$FAD6:38        SEC                                A:37 X:FF Y:FF P:E5 SP:F9 PPU:136,172 CYC:19603\n$FAD7:A9 F0     LDA #$F0                           A:37 X:FF Y:FF P:E5 SP:F9 PPU:142,172 CYC:19605\n$FAD9:60        RTS                                A:F0 X:FF Y:FF P:E5 SP:F9 PPU:148,172 CYC:19607\n$ED87:FB 48 05 *ISB $0548,Y @ 0647 = #$37            A:F0 X:FF Y:FF P:E5 SP:FB PPU:166,172 CYC:19613\n$ED8A:EA        NOP                                A:B8 X:FF Y:FF P:A5 SP:FB PPU:187,172 CYC:19620\n$ED8B:EA        NOP                                A:B8 X:FF Y:FF P:A5 SP:FB PPU:193,172 CYC:19622\n$ED8C:08        PHP                                A:B8 X:FF Y:FF P:A5 SP:FB PPU:199,172 CYC:19624\n$ED8D:48        PHA                                A:B8 X:FF Y:FF P:A5 SP:FA PPU:208,172 CYC:19627\n$ED8E:A0 BB     LDY #$BB                           A:B8 X:FF Y:FF P:A5 SP:F9 PPU:217,172 CYC:19630\n$ED90:68        PLA                                A:B8 X:FF Y:BB P:A5 SP:F9 PPU:223,172 CYC:19632\n$ED91:28        PLP                                A:B8 X:FF Y:BB P:A5 SP:FA PPU:235,172 CYC:19636\n$ED92:20 DA FA  JSR $FADA                          A:B8 X:FF Y:BB P:A5 SP:FB PPU:247,172 CYC:19640\n$FADA:70 0A     BVS $FAE6                          A:B8 X:FF Y:BB P:A5 SP:F9 PPU:265,172 CYC:19646\n$FADC:F0 08     BEQ $FAE6                          A:B8 X:FF Y:BB P:A5 SP:F9 PPU:271,172 CYC:19648\n$FADE:10 06     BPL $FAE6                          A:B8 X:FF Y:BB P:A5 SP:F9 PPU:277,172 CYC:19650\n$FAE0:90 04     BCC $FAE6                          A:B8 X:FF Y:BB P:A5 SP:F9 PPU:283,172 CYC:19652\n$FAE2:C9 B8     CMP #$B8                           A:B8 X:FF Y:BB P:A5 SP:F9 PPU:289,172 CYC:19654\n$FAE4:F0 02     BEQ $FAE8                          A:B8 X:FF Y:BB P:nvUbdIZC SP:F9 PPU:295,172 CYC:19656\n$FAE8:60        RTS                                A:B8 X:FF Y:BB P:nvUbdIZC SP:F9 PPU:304,172 CYC:19659\n$ED95:AD 47 06  LDA $0647 = #$38                     A:B8 X:FF Y:BB P:nvUbdIZC SP:FB PPU:322,172 CYC:19665\n$ED98:C9 38     CMP #$38                           A:38 X:FF Y:BB P:25 SP:FB PPU:334,172 CYC:19669\n$ED9A:F0 02     BEQ $ED9E                          A:38 X:FF Y:BB P:nvUbdIZC SP:FB PPU:340,172 CYC:19671\n$ED9E:A0 BC     LDY #$BC                           A:38 X:FF Y:BB P:nvUbdIZC SP:FB PPU:  8,173 CYC:19674\n$EDA0:A2 FF     LDX #$FF                           A:38 X:FF Y:BC P:A5 SP:FB PPU: 14,173 CYC:19676\n$EDA2:A9 EB     LDA #$EB                           A:38 X:FF Y:BC P:A5 SP:FB PPU: 20,173 CYC:19678\n$EDA4:8D 47 06  STA $0647 = #$38                     A:EB X:FF Y:BC P:A5 SP:FB PPU: 26,173 CYC:19680\n$EDA7:20 B1 FA  JSR $FAB1                          A:EB X:FF Y:BC P:A5 SP:FB PPU: 38,173 CYC:19684\n$FAB1:24 01     BIT $01 = #$FF                       A:EB X:FF Y:BC P:A5 SP:F9 PPU: 56,173 CYC:19690\n$FAB3:18        CLC                                A:EB X:FF Y:BC P:E5 SP:F9 PPU: 65,173 CYC:19693\n$FAB4:A9 40     LDA #$40                           A:EB X:FF Y:BC P:NVUbdIzc SP:F9 PPU: 71,173 CYC:19695\n$FAB6:60        RTS                                A:40 X:FF Y:BC P:64 SP:F9 PPU: 77,173 CYC:19697\n$EDAA:FF 48 05 *ISB $0548,X @ 0647 = #$EB            A:40 X:FF Y:BC P:64 SP:FB PPU: 95,173 CYC:19703\n$EDAD:EA        NOP                                A:53 X:FF Y:BC P:nvUbdIzc SP:FB PPU:116,173 CYC:19710\n$EDAE:EA        NOP                                A:53 X:FF Y:BC P:nvUbdIzc SP:FB PPU:122,173 CYC:19712\n$EDAF:EA        NOP                                A:53 X:FF Y:BC P:nvUbdIzc SP:FB PPU:128,173 CYC:19714\n$EDB0:EA        NOP                                A:53 X:FF Y:BC P:nvUbdIzc SP:FB PPU:134,173 CYC:19716\n$EDB1:20 B7 FA  JSR $FAB7                          A:53 X:FF Y:BC P:nvUbdIzc SP:FB PPU:140,173 CYC:19718\n$FAB7:70 2D     BVS $FAE6                          A:53 X:FF Y:BC P:nvUbdIzc SP:F9 PPU:158,173 CYC:19724\n$FAB9:B0 2B     BCS $FAE6                          A:53 X:FF Y:BC P:nvUbdIzc SP:F9 PPU:164,173 CYC:19726\n$FABB:30 29     BMI $FAE6                          A:53 X:FF Y:BC P:nvUbdIzc SP:F9 PPU:170,173 CYC:19728\n$FABD:C9 53     CMP #$53                           A:53 X:FF Y:BC P:nvUbdIzc SP:F9 PPU:176,173 CYC:19730\n$FABF:D0 25     BNE $FAE6                          A:53 X:FF Y:BC P:nvUbdIZC SP:F9 PPU:182,173 CYC:19732\n$FAC1:60        RTS                                A:53 X:FF Y:BC P:nvUbdIZC SP:F9 PPU:188,173 CYC:19734\n$EDB4:AD 47 06  LDA $0647 = #$EC                     A:53 X:FF Y:BC P:nvUbdIZC SP:FB PPU:206,173 CYC:19740\n$EDB7:C9 EC     CMP #$EC                           A:EC X:FF Y:BC P:A5 SP:FB PPU:218,173 CYC:19744\n$EDB9:F0 02     BEQ $EDBD                          A:EC X:FF Y:BC P:nvUbdIZC SP:FB PPU:224,173 CYC:19746\n$EDBD:C8        INY                                A:EC X:FF Y:BC P:nvUbdIZC SP:FB PPU:233,173 CYC:19749\n$EDBE:A9 FF     LDA #$FF                           A:EC X:FF Y:BD P:A5 SP:FB PPU:239,173 CYC:19751\n$EDC0:8D 47 06  STA $0647 = #$EC                     A:FF X:FF Y:BD P:A5 SP:FB PPU:245,173 CYC:19753\n$EDC3:20 C2 FA  JSR $FAC2                          A:FF X:FF Y:BD P:A5 SP:FB PPU:257,173 CYC:19757\n$FAC2:B8        CLV                                A:FF X:FF Y:BD P:A5 SP:F9 PPU:275,173 CYC:19763\n$FAC3:38        SEC                                A:FF X:FF Y:BD P:A5 SP:F9 PPU:281,173 CYC:19765\n$FAC4:A9 FF     LDA #$FF                           A:FF X:FF Y:BD P:A5 SP:F9 PPU:287,173 CYC:19767\n$FAC6:60        RTS                                A:FF X:FF Y:BD P:A5 SP:F9 PPU:293,173 CYC:19769\n$EDC6:FF 48 05 *ISB $0548,X @ 0647 = #$FF            A:FF X:FF Y:BD P:A5 SP:FB PPU:311,173 CYC:19775\n$EDC9:EA        NOP                                A:FF X:FF Y:BD P:A5 SP:FB PPU:332,173 CYC:19782\n$EDCA:EA        NOP                                A:FF X:FF Y:BD P:A5 SP:FB PPU:338,173 CYC:19784\n$EDCB:EA        NOP                                A:FF X:FF Y:BD P:A5 SP:FB PPU:  3,174 CYC:19786\n$EDCC:EA        NOP                                A:FF X:FF Y:BD P:A5 SP:FB PPU:  9,174 CYC:19788\n$EDCD:20 C7 FA  JSR $FAC7                          A:FF X:FF Y:BD P:A5 SP:FB PPU: 15,174 CYC:19790\n$FAC7:70 1D     BVS $FAE6                          A:FF X:FF Y:BD P:A5 SP:F9 PPU: 33,174 CYC:19796\n$FAC9:F0 1B     BEQ $FAE6                          A:FF X:FF Y:BD P:A5 SP:F9 PPU: 39,174 CYC:19798\n$FACB:10 19     BPL $FAE6                          A:FF X:FF Y:BD P:A5 SP:F9 PPU: 45,174 CYC:19800\n$FACD:90 17     BCC $FAE6                          A:FF X:FF Y:BD P:A5 SP:F9 PPU: 51,174 CYC:19802\n$FACF:C9 FF     CMP #$FF                           A:FF X:FF Y:BD P:A5 SP:F9 PPU: 57,174 CYC:19804\n$FAD1:D0 13     BNE $FAE6                          A:FF X:FF Y:BD P:nvUbdIZC SP:F9 PPU: 63,174 CYC:19806\n$FAD3:60        RTS                                A:FF X:FF Y:BD P:nvUbdIZC SP:F9 PPU: 69,174 CYC:19808\n$EDD0:AD 47 06  LDA $0647 = #$00                     A:FF X:FF Y:BD P:nvUbdIZC SP:FB PPU: 87,174 CYC:19814\n$EDD3:C9 00     CMP #$00                           A:00 X:FF Y:BD P:nvUbdIZC SP:FB PPU: 99,174 CYC:19818\n$EDD5:F0 02     BEQ $EDD9                          A:00 X:FF Y:BD P:nvUbdIZC SP:FB PPU:105,174 CYC:19820\n$EDD9:C8        INY                                A:00 X:FF Y:BD P:nvUbdIZC SP:FB PPU:114,174 CYC:19823\n$EDDA:A9 37     LDA #$37                           A:00 X:FF Y:BE P:A5 SP:FB PPU:120,174 CYC:19825\n$EDDC:8D 47 06  STA $0647 = #$00                     A:37 X:FF Y:BE P:25 SP:FB PPU:126,174 CYC:19827\n$EDDF:20 D4 FA  JSR $FAD4                          A:37 X:FF Y:BE P:25 SP:FB PPU:138,174 CYC:19831\n$FAD4:24 01     BIT $01 = #$FF                       A:37 X:FF Y:BE P:25 SP:F9 PPU:156,174 CYC:19837\n$FAD6:38        SEC                                A:37 X:FF Y:BE P:E5 SP:F9 PPU:165,174 CYC:19840\n$FAD7:A9 F0     LDA #$F0                           A:37 X:FF Y:BE P:E5 SP:F9 PPU:171,174 CYC:19842\n$FAD9:60        RTS                                A:F0 X:FF Y:BE P:E5 SP:F9 PPU:177,174 CYC:19844\n$EDE2:FF 48 05 *ISB $0548,X @ 0647 = #$37            A:F0 X:FF Y:BE P:E5 SP:FB PPU:195,174 CYC:19850\n$EDE5:EA        NOP                                A:B8 X:FF Y:BE P:A5 SP:FB PPU:216,174 CYC:19857\n$EDE6:EA        NOP                                A:B8 X:FF Y:BE P:A5 SP:FB PPU:222,174 CYC:19859\n$EDE7:EA        NOP                                A:B8 X:FF Y:BE P:A5 SP:FB PPU:228,174 CYC:19861\n$EDE8:EA        NOP                                A:B8 X:FF Y:BE P:A5 SP:FB PPU:234,174 CYC:19863\n$EDE9:20 DA FA  JSR $FADA                          A:B8 X:FF Y:BE P:A5 SP:FB PPU:240,174 CYC:19865\n$FADA:70 0A     BVS $FAE6                          A:B8 X:FF Y:BE P:A5 SP:F9 PPU:258,174 CYC:19871\n$FADC:F0 08     BEQ $FAE6                          A:B8 X:FF Y:BE P:A5 SP:F9 PPU:264,174 CYC:19873\n$FADE:10 06     BPL $FAE6                          A:B8 X:FF Y:BE P:A5 SP:F9 PPU:270,174 CYC:19875\n$FAE0:90 04     BCC $FAE6                          A:B8 X:FF Y:BE P:A5 SP:F9 PPU:276,174 CYC:19877\n$FAE2:C9 B8     CMP #$B8                           A:B8 X:FF Y:BE P:A5 SP:F9 PPU:282,174 CYC:19879\n$FAE4:F0 02     BEQ $FAE8                          A:B8 X:FF Y:BE P:nvUbdIZC SP:F9 PPU:288,174 CYC:19881\n$FAE8:60        RTS                                A:B8 X:FF Y:BE P:nvUbdIZC SP:F9 PPU:297,174 CYC:19884\n$EDEC:AD 47 06  LDA $0647 = #$38                     A:B8 X:FF Y:BE P:nvUbdIZC SP:FB PPU:315,174 CYC:19890\n$EDEF:C9 38     CMP #$38                           A:38 X:FF Y:BE P:25 SP:FB PPU:327,174 CYC:19894\n$EDF1:F0 02     BEQ $EDF5                          A:38 X:FF Y:BE P:nvUbdIZC SP:FB PPU:333,174 CYC:19896\n$EDF5:60        RTS                                A:38 X:FF Y:BE P:nvUbdIZC SP:FB PPU:  1,175 CYC:19899\n$C641:20 F6 ED  JSR $EDF6                          A:38 X:FF Y:BE P:nvUbdIZC SP:FD PPU: 19,175 CYC:19905\n$EDF6:A9 FF     LDA #$FF                           A:38 X:FF Y:BE P:nvUbdIZC SP:FB PPU: 37,175 CYC:19911\n$EDF8:85 01     STA $01 = #$FF                       A:FF X:FF Y:BE P:A5 SP:FB PPU: 43,175 CYC:19913\n$EDFA:A0 BF     LDY #$BF                           A:FF X:FF Y:BE P:A5 SP:FB PPU: 52,175 CYC:19916\n$EDFC:A2 02     LDX #$02                           A:FF X:FF Y:BF P:A5 SP:FB PPU: 58,175 CYC:19918\n$EDFE:A9 47     LDA #$47                           A:FF X:02 Y:BF P:25 SP:FB PPU: 64,175 CYC:19920\n$EE00:85 47     STA $47 = #$38                       A:47 X:02 Y:BF P:25 SP:FB PPU: 70,175 CYC:19922\n$EE02:A9 06     LDA #$06                           A:47 X:02 Y:BF P:25 SP:FB PPU: 79,175 CYC:19925\n$EE04:85 48     STA $48 = #$06                       A:06 X:02 Y:BF P:25 SP:FB PPU: 85,175 CYC:19927\n$EE06:A9 A5     LDA #$A5                           A:06 X:02 Y:BF P:25 SP:FB PPU: 94,175 CYC:19930\n$EE08:8D 47 06  STA $0647 = #$38                     A:A5 X:02 Y:BF P:A5 SP:FB PPU:100,175 CYC:19932\n$EE0B:20 7B FA  JSR $FA7B                          A:A5 X:02 Y:BF P:A5 SP:FB PPU:112,175 CYC:19936\n$FA7B:24 01     BIT $01 = #$FF                       A:A5 X:02 Y:BF P:A5 SP:F9 PPU:130,175 CYC:19942\n$FA7D:18        CLC                                A:A5 X:02 Y:BF P:E5 SP:F9 PPU:139,175 CYC:19945\n$FA7E:A9 B3     LDA #$B3                           A:A5 X:02 Y:BF P:NVUbdIzc SP:F9 PPU:145,175 CYC:19947\n$FA80:60        RTS                                A:B3 X:02 Y:BF P:NVUbdIzc SP:F9 PPU:151,175 CYC:19949\n$EE0E:03 45    *SLO ($45,X) @ 47 = #$0647 = A5       A:B3 X:02 Y:BF P:NVUbdIzc SP:FB PPU:169,175 CYC:19955\n$EE10:EA        NOP                                A:FB X:02 Y:BF P:E5 SP:FB PPU:193,175 CYC:19963\n$EE11:EA        NOP                                A:FB X:02 Y:BF P:E5 SP:FB PPU:199,175 CYC:19965\n$EE12:EA        NOP                                A:FB X:02 Y:BF P:E5 SP:FB PPU:205,175 CYC:19967\n$EE13:EA        NOP                                A:FB X:02 Y:BF P:E5 SP:FB PPU:211,175 CYC:19969\n$EE14:20 81 FA  JSR $FA81                          A:FB X:02 Y:BF P:E5 SP:FB PPU:217,175 CYC:19971\n$FA81:50 63     BVC $FAE6                          A:FB X:02 Y:BF P:E5 SP:F9 PPU:235,175 CYC:19977\n$FA83:90 61     BCC $FAE6                          A:FB X:02 Y:BF P:E5 SP:F9 PPU:241,175 CYC:19979\n$FA85:10 5F     BPL $FAE6                          A:FB X:02 Y:BF P:E5 SP:F9 PPU:247,175 CYC:19981\n$FA87:C9 FB     CMP #$FB                           A:FB X:02 Y:BF P:E5 SP:F9 PPU:253,175 CYC:19983\n$FA89:D0 5B     BNE $FAE6                          A:FB X:02 Y:BF P:67 SP:F9 PPU:259,175 CYC:19985\n$FA8B:60        RTS                                A:FB X:02 Y:BF P:67 SP:F9 PPU:265,175 CYC:19987\n$EE17:AD 47 06  LDA $0647 = #$4A                     A:FB X:02 Y:BF P:67 SP:FB PPU:283,175 CYC:19993\n$EE1A:C9 4A     CMP #$4A                           A:4A X:02 Y:BF P:65 SP:FB PPU:295,175 CYC:19997\n$EE1C:F0 02     BEQ $EE20                          A:4A X:02 Y:BF P:67 SP:FB PPU:301,175 CYC:19999\n$EE20:C8        INY                                A:4A X:02 Y:BF P:67 SP:FB PPU:310,175 CYC:20002\n$EE21:A9 29     LDA #$29                           A:4A X:02 Y:C0 P:E5 SP:FB PPU:316,175 CYC:20004\n$EE23:8D 47 06  STA $0647 = #$4A                     A:29 X:02 Y:C0 P:65 SP:FB PPU:322,175 CYC:20006\n$EE26:20 8C FA  JSR $FA8C                          A:29 X:02 Y:C0 P:65 SP:FB PPU:334,175 CYC:20010\n$FA8C:B8        CLV                                A:29 X:02 Y:C0 P:65 SP:F9 PPU: 11,176 CYC:20016\n$FA8D:18        CLC                                A:29 X:02 Y:C0 P:25 SP:F9 PPU: 17,176 CYC:20018\n$FA8E:A9 C3     LDA #$C3                           A:29 X:02 Y:C0 P:nvUbdIzc SP:F9 PPU: 23,176 CYC:20020\n$FA90:60        RTS                                A:C3 X:02 Y:C0 P:NvUbdIzc SP:F9 PPU: 29,176 CYC:20022\n$EE29:03 45    *SLO ($45,X) @ 47 = #$0647 = 29       A:C3 X:02 Y:C0 P:NvUbdIzc SP:FB PPU: 47,176 CYC:20028\n$EE2B:EA        NOP                                A:D3 X:02 Y:C0 P:NvUbdIzc SP:FB PPU: 71,176 CYC:20036\n$EE2C:EA        NOP                                A:D3 X:02 Y:C0 P:NvUbdIzc SP:FB PPU: 77,176 CYC:20038\n$EE2D:EA        NOP                                A:D3 X:02 Y:C0 P:NvUbdIzc SP:FB PPU: 83,176 CYC:20040\n$EE2E:EA        NOP                                A:D3 X:02 Y:C0 P:NvUbdIzc SP:FB PPU: 89,176 CYC:20042\n$EE2F:20 91 FA  JSR $FA91                          A:D3 X:02 Y:C0 P:NvUbdIzc SP:FB PPU: 95,176 CYC:20044\n$FA91:70 53     BVS $FAE6                          A:D3 X:02 Y:C0 P:NvUbdIzc SP:F9 PPU:113,176 CYC:20050\n$FA93:F0 51     BEQ $FAE6                          A:D3 X:02 Y:C0 P:NvUbdIzc SP:F9 PPU:119,176 CYC:20052\n$FA95:10 4F     BPL $FAE6                          A:D3 X:02 Y:C0 P:NvUbdIzc SP:F9 PPU:125,176 CYC:20054\n$FA97:B0 4D     BCS $FAE6                          A:D3 X:02 Y:C0 P:NvUbdIzc SP:F9 PPU:131,176 CYC:20056\n$FA99:C9 D3     CMP #$D3                           A:D3 X:02 Y:C0 P:NvUbdIzc SP:F9 PPU:137,176 CYC:20058\n$FA9B:D0 49     BNE $FAE6                          A:D3 X:02 Y:C0 P:nvUbdIZC SP:F9 PPU:143,176 CYC:20060\n$FA9D:60        RTS                                A:D3 X:02 Y:C0 P:nvUbdIZC SP:F9 PPU:149,176 CYC:20062\n$EE32:AD 47 06  LDA $0647 = #$52                     A:D3 X:02 Y:C0 P:nvUbdIZC SP:FB PPU:167,176 CYC:20068\n$EE35:C9 52     CMP #$52                           A:52 X:02 Y:C0 P:25 SP:FB PPU:179,176 CYC:20072\n$EE37:F0 02     BEQ $EE3B                          A:52 X:02 Y:C0 P:nvUbdIZC SP:FB PPU:185,176 CYC:20074\n$EE3B:C8        INY                                A:52 X:02 Y:C0 P:nvUbdIZC SP:FB PPU:194,176 CYC:20077\n$EE3C:A9 37     LDA #$37                           A:52 X:02 Y:C1 P:A5 SP:FB PPU:200,176 CYC:20079\n$EE3E:8D 47 06  STA $0647 = #$52                     A:37 X:02 Y:C1 P:25 SP:FB PPU:206,176 CYC:20081\n$EE41:20 9E FA  JSR $FA9E                          A:37 X:02 Y:C1 P:25 SP:FB PPU:218,176 CYC:20085\n$FA9E:24 01     BIT $01 = #$FF                       A:37 X:02 Y:C1 P:25 SP:F9 PPU:236,176 CYC:20091\n$FAA0:38        SEC                                A:37 X:02 Y:C1 P:E5 SP:F9 PPU:245,176 CYC:20094\n$FAA1:A9 10     LDA #$10                           A:37 X:02 Y:C1 P:E5 SP:F9 PPU:251,176 CYC:20096\n$FAA3:60        RTS                                A:10 X:02 Y:C1 P:65 SP:F9 PPU:257,176 CYC:20098\n$EE44:03 45    *SLO ($45,X) @ 47 = #$0647 = 37       A:10 X:02 Y:C1 P:65 SP:FB PPU:275,176 CYC:20104\n$EE46:EA        NOP                                A:7E X:02 Y:C1 P:64 SP:FB PPU:299,176 CYC:20112\n$EE47:EA        NOP                                A:7E X:02 Y:C1 P:64 SP:FB PPU:305,176 CYC:20114\n$EE48:EA        NOP                                A:7E X:02 Y:C1 P:64 SP:FB PPU:311,176 CYC:20116\n$EE49:EA        NOP                                A:7E X:02 Y:C1 P:64 SP:FB PPU:317,176 CYC:20118\n$EE4A:20 A4 FA  JSR $FAA4                          A:7E X:02 Y:C1 P:64 SP:FB PPU:323,176 CYC:20120\n$FAA4:50 40     BVC $FAE6                          A:7E X:02 Y:C1 P:64 SP:F9 PPU:  0,177 CYC:20126\n$FAA6:F0 3E     BEQ $FAE6                          A:7E X:02 Y:C1 P:64 SP:F9 PPU:  6,177 CYC:20128\n$FAA8:30 3C     BMI $FAE6                          A:7E X:02 Y:C1 P:64 SP:F9 PPU: 12,177 CYC:20130\n$FAAA:B0 3A     BCS $FAE6                          A:7E X:02 Y:C1 P:64 SP:F9 PPU: 18,177 CYC:20132\n$FAAC:C9 7E     CMP #$7E                           A:7E X:02 Y:C1 P:64 SP:F9 PPU: 24,177 CYC:20134\n$FAAE:D0 36     BNE $FAE6                          A:7E X:02 Y:C1 P:67 SP:F9 PPU: 30,177 CYC:20136\n$FAB0:60        RTS                                A:7E X:02 Y:C1 P:67 SP:F9 PPU: 36,177 CYC:20138\n$EE4D:AD 47 06  LDA $0647 = #$6E                     A:7E X:02 Y:C1 P:67 SP:FB PPU: 54,177 CYC:20144\n$EE50:C9 6E     CMP #$6E                           A:6E X:02 Y:C1 P:65 SP:FB PPU: 66,177 CYC:20148\n$EE52:F0 02     BEQ $EE56                          A:6E X:02 Y:C1 P:67 SP:FB PPU: 72,177 CYC:20150\n$EE56:C8        INY                                A:6E X:02 Y:C1 P:67 SP:FB PPU: 81,177 CYC:20153\n$EE57:A9 A5     LDA #$A5                           A:6E X:02 Y:C2 P:E5 SP:FB PPU: 87,177 CYC:20155\n$EE59:85 47     STA $47 = #$47                       A:A5 X:02 Y:C2 P:E5 SP:FB PPU: 93,177 CYC:20157\n$EE5B:20 7B FA  JSR $FA7B                          A:A5 X:02 Y:C2 P:E5 SP:FB PPU:102,177 CYC:20160\n$FA7B:24 01     BIT $01 = #$FF                       A:A5 X:02 Y:C2 P:E5 SP:F9 PPU:120,177 CYC:20166\n$FA7D:18        CLC                                A:A5 X:02 Y:C2 P:E5 SP:F9 PPU:129,177 CYC:20169\n$FA7E:A9 B3     LDA #$B3                           A:A5 X:02 Y:C2 P:NVUbdIzc SP:F9 PPU:135,177 CYC:20171\n$FA80:60        RTS                                A:B3 X:02 Y:C2 P:NVUbdIzc SP:F9 PPU:141,177 CYC:20173\n$EE5E:07 47    *SLO $47 = #$A5                       A:B3 X:02 Y:C2 P:NVUbdIzc SP:FB PPU:159,177 CYC:20179\n$EE60:EA        NOP                                A:FB X:02 Y:C2 P:E5 SP:FB PPU:174,177 CYC:20184\n$EE61:EA        NOP                                A:FB X:02 Y:C2 P:E5 SP:FB PPU:180,177 CYC:20186\n$EE62:EA        NOP                                A:FB X:02 Y:C2 P:E5 SP:FB PPU:186,177 CYC:20188\n$EE63:EA        NOP                                A:FB X:02 Y:C2 P:E5 SP:FB PPU:192,177 CYC:20190\n$EE64:20 81 FA  JSR $FA81                          A:FB X:02 Y:C2 P:E5 SP:FB PPU:198,177 CYC:20192\n$FA81:50 63     BVC $FAE6                          A:FB X:02 Y:C2 P:E5 SP:F9 PPU:216,177 CYC:20198\n$FA83:90 61     BCC $FAE6                          A:FB X:02 Y:C2 P:E5 SP:F9 PPU:222,177 CYC:20200\n$FA85:10 5F     BPL $FAE6                          A:FB X:02 Y:C2 P:E5 SP:F9 PPU:228,177 CYC:20202\n$FA87:C9 FB     CMP #$FB                           A:FB X:02 Y:C2 P:E5 SP:F9 PPU:234,177 CYC:20204\n$FA89:D0 5B     BNE $FAE6                          A:FB X:02 Y:C2 P:67 SP:F9 PPU:240,177 CYC:20206\n$FA8B:60        RTS                                A:FB X:02 Y:C2 P:67 SP:F9 PPU:246,177 CYC:20208\n$EE67:A5 47     LDA $47 = #$4A                       A:FB X:02 Y:C2 P:67 SP:FB PPU:264,177 CYC:20214\n$EE69:C9 4A     CMP #$4A                           A:4A X:02 Y:C2 P:65 SP:FB PPU:273,177 CYC:20217\n$EE6B:F0 02     BEQ $EE6F                          A:4A X:02 Y:C2 P:67 SP:FB PPU:279,177 CYC:20219\n$EE6F:C8        INY                                A:4A X:02 Y:C2 P:67 SP:FB PPU:288,177 CYC:20222\n$EE70:A9 29     LDA #$29                           A:4A X:02 Y:C3 P:E5 SP:FB PPU:294,177 CYC:20224\n$EE72:85 47     STA $47 = #$4A                       A:29 X:02 Y:C3 P:65 SP:FB PPU:300,177 CYC:20226\n$EE74:20 8C FA  JSR $FA8C                          A:29 X:02 Y:C3 P:65 SP:FB PPU:309,177 CYC:20229\n$FA8C:B8        CLV                                A:29 X:02 Y:C3 P:65 SP:F9 PPU:327,177 CYC:20235\n$FA8D:18        CLC                                A:29 X:02 Y:C3 P:25 SP:F9 PPU:333,177 CYC:20237\n$FA8E:A9 C3     LDA #$C3                           A:29 X:02 Y:C3 P:nvUbdIzc SP:F9 PPU:339,177 CYC:20239\n$FA90:60        RTS                                A:C3 X:02 Y:C3 P:NvUbdIzc SP:F9 PPU:  4,178 CYC:20241\n$EE77:07 47    *SLO $47 = #$29                       A:C3 X:02 Y:C3 P:NvUbdIzc SP:FB PPU: 22,178 CYC:20247\n$EE79:EA        NOP                                A:D3 X:02 Y:C3 P:NvUbdIzc SP:FB PPU: 37,178 CYC:20252\n$EE7A:EA        NOP                                A:D3 X:02 Y:C3 P:NvUbdIzc SP:FB PPU: 43,178 CYC:20254\n$EE7B:EA        NOP                                A:D3 X:02 Y:C3 P:NvUbdIzc SP:FB PPU: 49,178 CYC:20256\n$EE7C:EA        NOP                                A:D3 X:02 Y:C3 P:NvUbdIzc SP:FB PPU: 55,178 CYC:20258\n$EE7D:20 91 FA  JSR $FA91                          A:D3 X:02 Y:C3 P:NvUbdIzc SP:FB PPU: 61,178 CYC:20260\n$FA91:70 53     BVS $FAE6                          A:D3 X:02 Y:C3 P:NvUbdIzc SP:F9 PPU: 79,178 CYC:20266\n$FA93:F0 51     BEQ $FAE6                          A:D3 X:02 Y:C3 P:NvUbdIzc SP:F9 PPU: 85,178 CYC:20268\n$FA95:10 4F     BPL $FAE6                          A:D3 X:02 Y:C3 P:NvUbdIzc SP:F9 PPU: 91,178 CYC:20270\n$FA97:B0 4D     BCS $FAE6                          A:D3 X:02 Y:C3 P:NvUbdIzc SP:F9 PPU: 97,178 CYC:20272\n$FA99:C9 D3     CMP #$D3                           A:D3 X:02 Y:C3 P:NvUbdIzc SP:F9 PPU:103,178 CYC:20274\n$FA9B:D0 49     BNE $FAE6                          A:D3 X:02 Y:C3 P:nvUbdIZC SP:F9 PPU:109,178 CYC:20276\n$FA9D:60        RTS                                A:D3 X:02 Y:C3 P:nvUbdIZC SP:F9 PPU:115,178 CYC:20278\n$EE80:A5 47     LDA $47 = #$52                       A:D3 X:02 Y:C3 P:nvUbdIZC SP:FB PPU:133,178 CYC:20284\n$EE82:C9 52     CMP #$52                           A:52 X:02 Y:C3 P:25 SP:FB PPU:142,178 CYC:20287\n$EE84:F0 02     BEQ $EE88                          A:52 X:02 Y:C3 P:nvUbdIZC SP:FB PPU:148,178 CYC:20289\n$EE88:C8        INY                                A:52 X:02 Y:C3 P:nvUbdIZC SP:FB PPU:157,178 CYC:20292\n$EE89:A9 37     LDA #$37                           A:52 X:02 Y:C4 P:A5 SP:FB PPU:163,178 CYC:20294\n$EE8B:85 47     STA $47 = #$52                       A:37 X:02 Y:C4 P:25 SP:FB PPU:169,178 CYC:20296\n$EE8D:20 9E FA  JSR $FA9E                          A:37 X:02 Y:C4 P:25 SP:FB PPU:178,178 CYC:20299\n$FA9E:24 01     BIT $01 = #$FF                       A:37 X:02 Y:C4 P:25 SP:F9 PPU:196,178 CYC:20305\n$FAA0:38        SEC                                A:37 X:02 Y:C4 P:E5 SP:F9 PPU:205,178 CYC:20308\n$FAA1:A9 10     LDA #$10                           A:37 X:02 Y:C4 P:E5 SP:F9 PPU:211,178 CYC:20310\n$FAA3:60        RTS                                A:10 X:02 Y:C4 P:65 SP:F9 PPU:217,178 CYC:20312\n$EE90:07 47    *SLO $47 = #$37                       A:10 X:02 Y:C4 P:65 SP:FB PPU:235,178 CYC:20318\n$EE92:EA        NOP                                A:7E X:02 Y:C4 P:64 SP:FB PPU:250,178 CYC:20323\n$EE93:EA        NOP                                A:7E X:02 Y:C4 P:64 SP:FB PPU:256,178 CYC:20325\n$EE94:EA        NOP                                A:7E X:02 Y:C4 P:64 SP:FB PPU:262,178 CYC:20327\n$EE95:EA        NOP                                A:7E X:02 Y:C4 P:64 SP:FB PPU:268,178 CYC:20329\n$EE96:20 A4 FA  JSR $FAA4                          A:7E X:02 Y:C4 P:64 SP:FB PPU:274,178 CYC:20331\n$FAA4:50 40     BVC $FAE6                          A:7E X:02 Y:C4 P:64 SP:F9 PPU:292,178 CYC:20337\n$FAA6:F0 3E     BEQ $FAE6                          A:7E X:02 Y:C4 P:64 SP:F9 PPU:298,178 CYC:20339\n$FAA8:30 3C     BMI $FAE6                          A:7E X:02 Y:C4 P:64 SP:F9 PPU:304,178 CYC:20341\n$FAAA:B0 3A     BCS $FAE6                          A:7E X:02 Y:C4 P:64 SP:F9 PPU:310,178 CYC:20343\n$FAAC:C9 7E     CMP #$7E                           A:7E X:02 Y:C4 P:64 SP:F9 PPU:316,178 CYC:20345\n$FAAE:D0 36     BNE $FAE6                          A:7E X:02 Y:C4 P:67 SP:F9 PPU:322,178 CYC:20347\n$FAB0:60        RTS                                A:7E X:02 Y:C4 P:67 SP:F9 PPU:328,178 CYC:20349\n$EE99:A5 47     LDA $47 = #$6E                       A:7E X:02 Y:C4 P:67 SP:FB PPU:  5,179 CYC:20355\n$EE9B:C9 6E     CMP #$6E                           A:6E X:02 Y:C4 P:65 SP:FB PPU: 14,179 CYC:20358\n$EE9D:F0 02     BEQ $EEA1                          A:6E X:02 Y:C4 P:67 SP:FB PPU: 20,179 CYC:20360\n$EEA1:C8        INY                                A:6E X:02 Y:C4 P:67 SP:FB PPU: 29,179 CYC:20363\n$EEA2:A9 A5     LDA #$A5                           A:6E X:02 Y:C5 P:E5 SP:FB PPU: 35,179 CYC:20365\n$EEA4:8D 47 06  STA $0647 = #$6E                     A:A5 X:02 Y:C5 P:E5 SP:FB PPU: 41,179 CYC:20367\n$EEA7:20 7B FA  JSR $FA7B                          A:A5 X:02 Y:C5 P:E5 SP:FB PPU: 53,179 CYC:20371\n$FA7B:24 01     BIT $01 = #$FF                       A:A5 X:02 Y:C5 P:E5 SP:F9 PPU: 71,179 CYC:20377\n$FA7D:18        CLC                                A:A5 X:02 Y:C5 P:E5 SP:F9 PPU: 80,179 CYC:20380\n$FA7E:A9 B3     LDA #$B3                           A:A5 X:02 Y:C5 P:NVUbdIzc SP:F9 PPU: 86,179 CYC:20382\n$FA80:60        RTS                                A:B3 X:02 Y:C5 P:NVUbdIzc SP:F9 PPU: 92,179 CYC:20384\n$EEAA:0F 47 06 *SLO $0647 = #$A5                     A:B3 X:02 Y:C5 P:NVUbdIzc SP:FB PPU:110,179 CYC:20390\n$EEAD:EA        NOP                                A:FB X:02 Y:C5 P:E5 SP:FB PPU:128,179 CYC:20396\n$EEAE:EA        NOP                                A:FB X:02 Y:C5 P:E5 SP:FB PPU:134,179 CYC:20398\n$EEAF:EA        NOP                                A:FB X:02 Y:C5 P:E5 SP:FB PPU:140,179 CYC:20400\n$EEB0:EA        NOP                                A:FB X:02 Y:C5 P:E5 SP:FB PPU:146,179 CYC:20402\n$EEB1:20 81 FA  JSR $FA81                          A:FB X:02 Y:C5 P:E5 SP:FB PPU:152,179 CYC:20404\n$FA81:50 63     BVC $FAE6                          A:FB X:02 Y:C5 P:E5 SP:F9 PPU:170,179 CYC:20410\n$FA83:90 61     BCC $FAE6                          A:FB X:02 Y:C5 P:E5 SP:F9 PPU:176,179 CYC:20412\n$FA85:10 5F     BPL $FAE6                          A:FB X:02 Y:C5 P:E5 SP:F9 PPU:182,179 CYC:20414\n$FA87:C9 FB     CMP #$FB                           A:FB X:02 Y:C5 P:E5 SP:F9 PPU:188,179 CYC:20416\n$FA89:D0 5B     BNE $FAE6                          A:FB X:02 Y:C5 P:67 SP:F9 PPU:194,179 CYC:20418\n$FA8B:60        RTS                                A:FB X:02 Y:C5 P:67 SP:F9 PPU:200,179 CYC:20420\n$EEB4:AD 47 06  LDA $0647 = #$4A                     A:FB X:02 Y:C5 P:67 SP:FB PPU:218,179 CYC:20426\n$EEB7:C9 4A     CMP #$4A                           A:4A X:02 Y:C5 P:65 SP:FB PPU:230,179 CYC:20430\n$EEB9:F0 02     BEQ $EEBD                          A:4A X:02 Y:C5 P:67 SP:FB PPU:236,179 CYC:20432\n$EEBD:C8        INY                                A:4A X:02 Y:C5 P:67 SP:FB PPU:245,179 CYC:20435\n$EEBE:A9 29     LDA #$29                           A:4A X:02 Y:C6 P:E5 SP:FB PPU:251,179 CYC:20437\n$EEC0:8D 47 06  STA $0647 = #$4A                     A:29 X:02 Y:C6 P:65 SP:FB PPU:257,179 CYC:20439\n$EEC3:20 8C FA  JSR $FA8C                          A:29 X:02 Y:C6 P:65 SP:FB PPU:269,179 CYC:20443\n$FA8C:B8        CLV                                A:29 X:02 Y:C6 P:65 SP:F9 PPU:287,179 CYC:20449\n$FA8D:18        CLC                                A:29 X:02 Y:C6 P:25 SP:F9 PPU:293,179 CYC:20451\n$FA8E:A9 C3     LDA #$C3                           A:29 X:02 Y:C6 P:nvUbdIzc SP:F9 PPU:299,179 CYC:20453\n$FA90:60        RTS                                A:C3 X:02 Y:C6 P:NvUbdIzc SP:F9 PPU:305,179 CYC:20455\n$EEC6:0F 47 06 *SLO $0647 = #$29                     A:C3 X:02 Y:C6 P:NvUbdIzc SP:FB PPU:323,179 CYC:20461\n$EEC9:EA        NOP                                A:D3 X:02 Y:C6 P:NvUbdIzc SP:FB PPU:  0,180 CYC:20467\n$EECA:EA        NOP                                A:D3 X:02 Y:C6 P:NvUbdIzc SP:FB PPU:  6,180 CYC:20469\n$EECB:EA        NOP                                A:D3 X:02 Y:C6 P:NvUbdIzc SP:FB PPU: 12,180 CYC:20471\n$EECC:EA        NOP                                A:D3 X:02 Y:C6 P:NvUbdIzc SP:FB PPU: 18,180 CYC:20473\n$EECD:20 91 FA  JSR $FA91                          A:D3 X:02 Y:C6 P:NvUbdIzc SP:FB PPU: 24,180 CYC:20475\n$FA91:70 53     BVS $FAE6                          A:D3 X:02 Y:C6 P:NvUbdIzc SP:F9 PPU: 42,180 CYC:20481\n$FA93:F0 51     BEQ $FAE6                          A:D3 X:02 Y:C6 P:NvUbdIzc SP:F9 PPU: 48,180 CYC:20483\n$FA95:10 4F     BPL $FAE6                          A:D3 X:02 Y:C6 P:NvUbdIzc SP:F9 PPU: 54,180 CYC:20485\n$FA97:B0 4D     BCS $FAE6                          A:D3 X:02 Y:C6 P:NvUbdIzc SP:F9 PPU: 60,180 CYC:20487\n$FA99:C9 D3     CMP #$D3                           A:D3 X:02 Y:C6 P:NvUbdIzc SP:F9 PPU: 66,180 CYC:20489\n$FA9B:D0 49     BNE $FAE6                          A:D3 X:02 Y:C6 P:nvUbdIZC SP:F9 PPU: 72,180 CYC:20491\n$FA9D:60        RTS                                A:D3 X:02 Y:C6 P:nvUbdIZC SP:F9 PPU: 78,180 CYC:20493\n$EED0:AD 47 06  LDA $0647 = #$52                     A:D3 X:02 Y:C6 P:nvUbdIZC SP:FB PPU: 96,180 CYC:20499\n$EED3:C9 52     CMP #$52                           A:52 X:02 Y:C6 P:25 SP:FB PPU:108,180 CYC:20503\n$EED5:F0 02     BEQ $EED9                          A:52 X:02 Y:C6 P:nvUbdIZC SP:FB PPU:114,180 CYC:20505\n$EED9:C8        INY                                A:52 X:02 Y:C6 P:nvUbdIZC SP:FB PPU:123,180 CYC:20508\n$EEDA:A9 37     LDA #$37                           A:52 X:02 Y:C7 P:A5 SP:FB PPU:129,180 CYC:20510\n$EEDC:8D 47 06  STA $0647 = #$52                     A:37 X:02 Y:C7 P:25 SP:FB PPU:135,180 CYC:20512\n$EEDF:20 9E FA  JSR $FA9E                          A:37 X:02 Y:C7 P:25 SP:FB PPU:147,180 CYC:20516\n$FA9E:24 01     BIT $01 = #$FF                       A:37 X:02 Y:C7 P:25 SP:F9 PPU:165,180 CYC:20522\n$FAA0:38        SEC                                A:37 X:02 Y:C7 P:E5 SP:F9 PPU:174,180 CYC:20525\n$FAA1:A9 10     LDA #$10                           A:37 X:02 Y:C7 P:E5 SP:F9 PPU:180,180 CYC:20527\n$FAA3:60        RTS                                A:10 X:02 Y:C7 P:65 SP:F9 PPU:186,180 CYC:20529\n$EEE2:0F 47 06 *SLO $0647 = #$37                     A:10 X:02 Y:C7 P:65 SP:FB PPU:204,180 CYC:20535\n$EEE5:EA        NOP                                A:7E X:02 Y:C7 P:64 SP:FB PPU:222,180 CYC:20541\n$EEE6:EA        NOP                                A:7E X:02 Y:C7 P:64 SP:FB PPU:228,180 CYC:20543\n$EEE7:EA        NOP                                A:7E X:02 Y:C7 P:64 SP:FB PPU:234,180 CYC:20545\n$EEE8:EA        NOP                                A:7E X:02 Y:C7 P:64 SP:FB PPU:240,180 CYC:20547\n$EEE9:20 A4 FA  JSR $FAA4                          A:7E X:02 Y:C7 P:64 SP:FB PPU:246,180 CYC:20549\n$FAA4:50 40     BVC $FAE6                          A:7E X:02 Y:C7 P:64 SP:F9 PPU:264,180 CYC:20555\n$FAA6:F0 3E     BEQ $FAE6                          A:7E X:02 Y:C7 P:64 SP:F9 PPU:270,180 CYC:20557\n$FAA8:30 3C     BMI $FAE6                          A:7E X:02 Y:C7 P:64 SP:F9 PPU:276,180 CYC:20559\n$FAAA:B0 3A     BCS $FAE6                          A:7E X:02 Y:C7 P:64 SP:F9 PPU:282,180 CYC:20561\n$FAAC:C9 7E     CMP #$7E                           A:7E X:02 Y:C7 P:64 SP:F9 PPU:288,180 CYC:20563\n$FAAE:D0 36     BNE $FAE6                          A:7E X:02 Y:C7 P:67 SP:F9 PPU:294,180 CYC:20565\n$FAB0:60        RTS                                A:7E X:02 Y:C7 P:67 SP:F9 PPU:300,180 CYC:20567\n$EEEC:AD 47 06  LDA $0647 = #$6E                     A:7E X:02 Y:C7 P:67 SP:FB PPU:318,180 CYC:20573\n$EEEF:C9 6E     CMP #$6E                           A:6E X:02 Y:C7 P:65 SP:FB PPU:330,180 CYC:20577\n$EEF1:F0 02     BEQ $EEF5                          A:6E X:02 Y:C7 P:67 SP:FB PPU:336,180 CYC:20579\n$EEF5:A9 A5     LDA #$A5                           A:6E X:02 Y:C7 P:67 SP:FB PPU:  4,181 CYC:20582\n$EEF7:8D 47 06  STA $0647 = #$6E                     A:A5 X:02 Y:C7 P:E5 SP:FB PPU: 10,181 CYC:20584\n$EEFA:A9 48     LDA #$48                           A:A5 X:02 Y:C7 P:E5 SP:FB PPU: 22,181 CYC:20588\n$EEFC:85 45     STA $45 = #$48                       A:48 X:02 Y:C7 P:65 SP:FB PPU: 28,181 CYC:20590\n$EEFE:A9 05     LDA #$05                           A:48 X:02 Y:C7 P:65 SP:FB PPU: 37,181 CYC:20593\n$EF00:85 46     STA $46 = #$05                       A:05 X:02 Y:C7 P:65 SP:FB PPU: 43,181 CYC:20595\n$EF02:A0 FF     LDY #$FF                           A:05 X:02 Y:C7 P:65 SP:FB PPU: 52,181 CYC:20598\n$EF04:20 7B FA  JSR $FA7B                          A:05 X:02 Y:FF P:E5 SP:FB PPU: 58,181 CYC:20600\n$FA7B:24 01     BIT $01 = #$FF                       A:05 X:02 Y:FF P:E5 SP:F9 PPU: 76,181 CYC:20606\n$FA7D:18        CLC                                A:05 X:02 Y:FF P:E5 SP:F9 PPU: 85,181 CYC:20609\n$FA7E:A9 B3     LDA #$B3                           A:05 X:02 Y:FF P:NVUbdIzc SP:F9 PPU: 91,181 CYC:20611\n$FA80:60        RTS                                A:B3 X:02 Y:FF P:NVUbdIzc SP:F9 PPU: 97,181 CYC:20613\n$EF07:13 45    *SLO ($45),Y = #$0548 @ 0647 = A5     A:B3 X:02 Y:FF P:NVUbdIzc SP:FB PPU:115,181 CYC:20619\n$EF09:EA        NOP                                A:FB X:02 Y:FF P:E5 SP:FB PPU:139,181 CYC:20627\n$EF0A:EA        NOP                                A:FB X:02 Y:FF P:E5 SP:FB PPU:145,181 CYC:20629\n$EF0B:08        PHP                                A:FB X:02 Y:FF P:E5 SP:FB PPU:151,181 CYC:20631\n$EF0C:48        PHA                                A:FB X:02 Y:FF P:E5 SP:FA PPU:160,181 CYC:20634\n$EF0D:A0 C8     LDY #$C8                           A:FB X:02 Y:FF P:E5 SP:F9 PPU:169,181 CYC:20637\n$EF0F:68        PLA                                A:FB X:02 Y:C8 P:E5 SP:F9 PPU:175,181 CYC:20639\n$EF10:28        PLP                                A:FB X:02 Y:C8 P:E5 SP:FA PPU:187,181 CYC:20643\n$EF11:20 81 FA  JSR $FA81                          A:FB X:02 Y:C8 P:E5 SP:FB PPU:199,181 CYC:20647\n$FA81:50 63     BVC $FAE6                          A:FB X:02 Y:C8 P:E5 SP:F9 PPU:217,181 CYC:20653\n$FA83:90 61     BCC $FAE6                          A:FB X:02 Y:C8 P:E5 SP:F9 PPU:223,181 CYC:20655\n$FA85:10 5F     BPL $FAE6                          A:FB X:02 Y:C8 P:E5 SP:F9 PPU:229,181 CYC:20657\n$FA87:C9 FB     CMP #$FB                           A:FB X:02 Y:C8 P:E5 SP:F9 PPU:235,181 CYC:20659\n$FA89:D0 5B     BNE $FAE6                          A:FB X:02 Y:C8 P:67 SP:F9 PPU:241,181 CYC:20661\n$FA8B:60        RTS                                A:FB X:02 Y:C8 P:67 SP:F9 PPU:247,181 CYC:20663\n$EF14:AD 47 06  LDA $0647 = #$4A                     A:FB X:02 Y:C8 P:67 SP:FB PPU:265,181 CYC:20669\n$EF17:C9 4A     CMP #$4A                           A:4A X:02 Y:C8 P:65 SP:FB PPU:277,181 CYC:20673\n$EF19:F0 02     BEQ $EF1D                          A:4A X:02 Y:C8 P:67 SP:FB PPU:283,181 CYC:20675\n$EF1D:A0 FF     LDY #$FF                           A:4A X:02 Y:C8 P:67 SP:FB PPU:292,181 CYC:20678\n$EF1F:A9 29     LDA #$29                           A:4A X:02 Y:FF P:E5 SP:FB PPU:298,181 CYC:20680\n$EF21:8D 47 06  STA $0647 = #$4A                     A:29 X:02 Y:FF P:65 SP:FB PPU:304,181 CYC:20682\n$EF24:20 8C FA  JSR $FA8C                          A:29 X:02 Y:FF P:65 SP:FB PPU:316,181 CYC:20686\n$FA8C:B8        CLV                                A:29 X:02 Y:FF P:65 SP:F9 PPU:334,181 CYC:20692\n$FA8D:18        CLC                                A:29 X:02 Y:FF P:25 SP:F9 PPU:340,181 CYC:20694\n$FA8E:A9 C3     LDA #$C3                           A:29 X:02 Y:FF P:nvUbdIzc SP:F9 PPU:  5,182 CYC:20696\n$FA90:60        RTS                                A:C3 X:02 Y:FF P:NvUbdIzc SP:F9 PPU: 11,182 CYC:20698\n$EF27:13 45    *SLO ($45),Y = #$0548 @ 0647 = 29     A:C3 X:02 Y:FF P:NvUbdIzc SP:FB PPU: 29,182 CYC:20704\n$EF29:EA        NOP                                A:D3 X:02 Y:FF P:NvUbdIzc SP:FB PPU: 53,182 CYC:20712\n$EF2A:EA        NOP                                A:D3 X:02 Y:FF P:NvUbdIzc SP:FB PPU: 59,182 CYC:20714\n$EF2B:08        PHP                                A:D3 X:02 Y:FF P:NvUbdIzc SP:FB PPU: 65,182 CYC:20716\n$EF2C:48        PHA                                A:D3 X:02 Y:FF P:NvUbdIzc SP:FA PPU: 74,182 CYC:20719\n$EF2D:A0 C9     LDY #$C9                           A:D3 X:02 Y:FF P:NvUbdIzc SP:F9 PPU: 83,182 CYC:20722\n$EF2F:68        PLA                                A:D3 X:02 Y:C9 P:NvUbdIzc SP:F9 PPU: 89,182 CYC:20724\n$EF30:28        PLP                                A:D3 X:02 Y:C9 P:NvUbdIzc SP:FA PPU:101,182 CYC:20728\n$EF31:20 91 FA  JSR $FA91                          A:D3 X:02 Y:C9 P:NvUbdIzc SP:FB PPU:113,182 CYC:20732\n$FA91:70 53     BVS $FAE6                          A:D3 X:02 Y:C9 P:NvUbdIzc SP:F9 PPU:131,182 CYC:20738\n$FA93:F0 51     BEQ $FAE6                          A:D3 X:02 Y:C9 P:NvUbdIzc SP:F9 PPU:137,182 CYC:20740\n$FA95:10 4F     BPL $FAE6                          A:D3 X:02 Y:C9 P:NvUbdIzc SP:F9 PPU:143,182 CYC:20742\n$FA97:B0 4D     BCS $FAE6                          A:D3 X:02 Y:C9 P:NvUbdIzc SP:F9 PPU:149,182 CYC:20744\n$FA99:C9 D3     CMP #$D3                           A:D3 X:02 Y:C9 P:NvUbdIzc SP:F9 PPU:155,182 CYC:20746\n$FA9B:D0 49     BNE $FAE6                          A:D3 X:02 Y:C9 P:nvUbdIZC SP:F9 PPU:161,182 CYC:20748\n$FA9D:60        RTS                                A:D3 X:02 Y:C9 P:nvUbdIZC SP:F9 PPU:167,182 CYC:20750\n$EF34:AD 47 06  LDA $0647 = #$52                     A:D3 X:02 Y:C9 P:nvUbdIZC SP:FB PPU:185,182 CYC:20756\n$EF37:C9 52     CMP #$52                           A:52 X:02 Y:C9 P:25 SP:FB PPU:197,182 CYC:20760\n$EF39:F0 02     BEQ $EF3D                          A:52 X:02 Y:C9 P:nvUbdIZC SP:FB PPU:203,182 CYC:20762\n$EF3D:A0 FF     LDY #$FF                           A:52 X:02 Y:C9 P:nvUbdIZC SP:FB PPU:212,182 CYC:20765\n$EF3F:A9 37     LDA #$37                           A:52 X:02 Y:FF P:A5 SP:FB PPU:218,182 CYC:20767\n$EF41:8D 47 06  STA $0647 = #$52                     A:37 X:02 Y:FF P:25 SP:FB PPU:224,182 CYC:20769\n$EF44:20 9E FA  JSR $FA9E                          A:37 X:02 Y:FF P:25 SP:FB PPU:236,182 CYC:20773\n$FA9E:24 01     BIT $01 = #$FF                       A:37 X:02 Y:FF P:25 SP:F9 PPU:254,182 CYC:20779\n$FAA0:38        SEC                                A:37 X:02 Y:FF P:E5 SP:F9 PPU:263,182 CYC:20782\n$FAA1:A9 10     LDA #$10                           A:37 X:02 Y:FF P:E5 SP:F9 PPU:269,182 CYC:20784\n$FAA3:60        RTS                                A:10 X:02 Y:FF P:65 SP:F9 PPU:275,182 CYC:20786\n$EF47:13 45    *SLO ($45),Y = #$0548 @ 0647 = 37     A:10 X:02 Y:FF P:65 SP:FB PPU:293,182 CYC:20792\n$EF49:EA        NOP                                A:7E X:02 Y:FF P:64 SP:FB PPU:317,182 CYC:20800\n$EF4A:EA        NOP                                A:7E X:02 Y:FF P:64 SP:FB PPU:323,182 CYC:20802\n$EF4B:08        PHP                                A:7E X:02 Y:FF P:64 SP:FB PPU:329,182 CYC:20804\n$EF4C:48        PHA                                A:7E X:02 Y:FF P:64 SP:FA PPU:338,182 CYC:20807\n$EF4D:A0 CA     LDY #$CA                           A:7E X:02 Y:FF P:64 SP:F9 PPU:  6,183 CYC:20810\n$EF4F:68        PLA                                A:7E X:02 Y:CA P:NVUbdIzc SP:F9 PPU: 12,183 CYC:20812\n$EF50:28        PLP                                A:7E X:02 Y:CA P:64 SP:FA PPU: 24,183 CYC:20816\n$EF51:20 A4 FA  JSR $FAA4                          A:7E X:02 Y:CA P:64 SP:FB PPU: 36,183 CYC:20820\n$FAA4:50 40     BVC $FAE6                          A:7E X:02 Y:CA P:64 SP:F9 PPU: 54,183 CYC:20826\n$FAA6:F0 3E     BEQ $FAE6                          A:7E X:02 Y:CA P:64 SP:F9 PPU: 60,183 CYC:20828\n$FAA8:30 3C     BMI $FAE6                          A:7E X:02 Y:CA P:64 SP:F9 PPU: 66,183 CYC:20830\n$FAAA:B0 3A     BCS $FAE6                          A:7E X:02 Y:CA P:64 SP:F9 PPU: 72,183 CYC:20832\n$FAAC:C9 7E     CMP #$7E                           A:7E X:02 Y:CA P:64 SP:F9 PPU: 78,183 CYC:20834\n$FAAE:D0 36     BNE $FAE6                          A:7E X:02 Y:CA P:67 SP:F9 PPU: 84,183 CYC:20836\n$FAB0:60        RTS                                A:7E X:02 Y:CA P:67 SP:F9 PPU: 90,183 CYC:20838\n$EF54:AD 47 06  LDA $0647 = #$6E                     A:7E X:02 Y:CA P:67 SP:FB PPU:108,183 CYC:20844\n$EF57:C9 6E     CMP #$6E                           A:6E X:02 Y:CA P:65 SP:FB PPU:120,183 CYC:20848\n$EF59:F0 02     BEQ $EF5D                          A:6E X:02 Y:CA P:67 SP:FB PPU:126,183 CYC:20850\n$EF5D:A0 CB     LDY #$CB                           A:6E X:02 Y:CA P:67 SP:FB PPU:135,183 CYC:20853\n$EF5F:A2 FF     LDX #$FF                           A:6E X:02 Y:CB P:E5 SP:FB PPU:141,183 CYC:20855\n$EF61:A9 A5     LDA #$A5                           A:6E X:FF Y:CB P:E5 SP:FB PPU:147,183 CYC:20857\n$EF63:85 47     STA $47 = #$6E                       A:A5 X:FF Y:CB P:E5 SP:FB PPU:153,183 CYC:20859\n$EF65:20 7B FA  JSR $FA7B                          A:A5 X:FF Y:CB P:E5 SP:FB PPU:162,183 CYC:20862\n$FA7B:24 01     BIT $01 = #$FF                       A:A5 X:FF Y:CB P:E5 SP:F9 PPU:180,183 CYC:20868\n$FA7D:18        CLC                                A:A5 X:FF Y:CB P:E5 SP:F9 PPU:189,183 CYC:20871\n$FA7E:A9 B3     LDA #$B3                           A:A5 X:FF Y:CB P:NVUbdIzc SP:F9 PPU:195,183 CYC:20873\n$FA80:60        RTS                                A:B3 X:FF Y:CB P:NVUbdIzc SP:F9 PPU:201,183 CYC:20875\n$EF68:17 48    *SLO $48,X @ 47 = #$A5                A:B3 X:FF Y:CB P:NVUbdIzc SP:FB PPU:219,183 CYC:20881\n$EF6A:EA        NOP                                A:FB X:FF Y:CB P:E5 SP:FB PPU:237,183 CYC:20887\n$EF6B:EA        NOP                                A:FB X:FF Y:CB P:E5 SP:FB PPU:243,183 CYC:20889\n$EF6C:EA        NOP                                A:FB X:FF Y:CB P:E5 SP:FB PPU:249,183 CYC:20891\n$EF6D:EA        NOP                                A:FB X:FF Y:CB P:E5 SP:FB PPU:255,183 CYC:20893\n$EF6E:20 81 FA  JSR $FA81                          A:FB X:FF Y:CB P:E5 SP:FB PPU:261,183 CYC:20895\n$FA81:50 63     BVC $FAE6                          A:FB X:FF Y:CB P:E5 SP:F9 PPU:279,183 CYC:20901\n$FA83:90 61     BCC $FAE6                          A:FB X:FF Y:CB P:E5 SP:F9 PPU:285,183 CYC:20903\n$FA85:10 5F     BPL $FAE6                          A:FB X:FF Y:CB P:E5 SP:F9 PPU:291,183 CYC:20905\n$FA87:C9 FB     CMP #$FB                           A:FB X:FF Y:CB P:E5 SP:F9 PPU:297,183 CYC:20907\n$FA89:D0 5B     BNE $FAE6                          A:FB X:FF Y:CB P:67 SP:F9 PPU:303,183 CYC:20909\n$FA8B:60        RTS                                A:FB X:FF Y:CB P:67 SP:F9 PPU:309,183 CYC:20911\n$EF71:A5 47     LDA $47 = #$4A                       A:FB X:FF Y:CB P:67 SP:FB PPU:327,183 CYC:20917\n$EF73:C9 4A     CMP #$4A                           A:4A X:FF Y:CB P:65 SP:FB PPU:336,183 CYC:20920\n$EF75:F0 02     BEQ $EF79                          A:4A X:FF Y:CB P:67 SP:FB PPU:  1,184 CYC:20922\n$EF79:C8        INY                                A:4A X:FF Y:CB P:67 SP:FB PPU: 10,184 CYC:20925\n$EF7A:A9 29     LDA #$29                           A:4A X:FF Y:CC P:E5 SP:FB PPU: 16,184 CYC:20927\n$EF7C:85 47     STA $47 = #$4A                       A:29 X:FF Y:CC P:65 SP:FB PPU: 22,184 CYC:20929\n$EF7E:20 8C FA  JSR $FA8C                          A:29 X:FF Y:CC P:65 SP:FB PPU: 31,184 CYC:20932\n$FA8C:B8        CLV                                A:29 X:FF Y:CC P:65 SP:F9 PPU: 49,184 CYC:20938\n$FA8D:18        CLC                                A:29 X:FF Y:CC P:25 SP:F9 PPU: 55,184 CYC:20940\n$FA8E:A9 C3     LDA #$C3                           A:29 X:FF Y:CC P:nvUbdIzc SP:F9 PPU: 61,184 CYC:20942\n$FA90:60        RTS                                A:C3 X:FF Y:CC P:NvUbdIzc SP:F9 PPU: 67,184 CYC:20944\n$EF81:17 48    *SLO $48,X @ 47 = #$29                A:C3 X:FF Y:CC P:NvUbdIzc SP:FB PPU: 85,184 CYC:20950\n$EF83:EA        NOP                                A:D3 X:FF Y:CC P:NvUbdIzc SP:FB PPU:103,184 CYC:20956\n$EF84:EA        NOP                                A:D3 X:FF Y:CC P:NvUbdIzc SP:FB PPU:109,184 CYC:20958\n$EF85:EA        NOP                                A:D3 X:FF Y:CC P:NvUbdIzc SP:FB PPU:115,184 CYC:20960\n$EF86:EA        NOP                                A:D3 X:FF Y:CC P:NvUbdIzc SP:FB PPU:121,184 CYC:20962\n$EF87:20 91 FA  JSR $FA91                          A:D3 X:FF Y:CC P:NvUbdIzc SP:FB PPU:127,184 CYC:20964\n$FA91:70 53     BVS $FAE6                          A:D3 X:FF Y:CC P:NvUbdIzc SP:F9 PPU:145,184 CYC:20970\n$FA93:F0 51     BEQ $FAE6                          A:D3 X:FF Y:CC P:NvUbdIzc SP:F9 PPU:151,184 CYC:20972\n$FA95:10 4F     BPL $FAE6                          A:D3 X:FF Y:CC P:NvUbdIzc SP:F9 PPU:157,184 CYC:20974\n$FA97:B0 4D     BCS $FAE6                          A:D3 X:FF Y:CC P:NvUbdIzc SP:F9 PPU:163,184 CYC:20976\n$FA99:C9 D3     CMP #$D3                           A:D3 X:FF Y:CC P:NvUbdIzc SP:F9 PPU:169,184 CYC:20978\n$FA9B:D0 49     BNE $FAE6                          A:D3 X:FF Y:CC P:nvUbdIZC SP:F9 PPU:175,184 CYC:20980\n$FA9D:60        RTS                                A:D3 X:FF Y:CC P:nvUbdIZC SP:F9 PPU:181,184 CYC:20982\n$EF8A:A5 47     LDA $47 = #$52                       A:D3 X:FF Y:CC P:nvUbdIZC SP:FB PPU:199,184 CYC:20988\n$EF8C:C9 52     CMP #$52                           A:52 X:FF Y:CC P:25 SP:FB PPU:208,184 CYC:20991\n$EF8E:F0 02     BEQ $EF92                          A:52 X:FF Y:CC P:nvUbdIZC SP:FB PPU:214,184 CYC:20993\n$EF92:C8        INY                                A:52 X:FF Y:CC P:nvUbdIZC SP:FB PPU:223,184 CYC:20996\n$EF93:A9 37     LDA #$37                           A:52 X:FF Y:CD P:A5 SP:FB PPU:229,184 CYC:20998\n$EF95:85 47     STA $47 = #$52                       A:37 X:FF Y:CD P:25 SP:FB PPU:235,184 CYC:21000\n$EF97:20 9E FA  JSR $FA9E                          A:37 X:FF Y:CD P:25 SP:FB PPU:244,184 CYC:21003\n$FA9E:24 01     BIT $01 = #$FF                       A:37 X:FF Y:CD P:25 SP:F9 PPU:262,184 CYC:21009\n$FAA0:38        SEC                                A:37 X:FF Y:CD P:E5 SP:F9 PPU:271,184 CYC:21012\n$FAA1:A9 10     LDA #$10                           A:37 X:FF Y:CD P:E5 SP:F9 PPU:277,184 CYC:21014\n$FAA3:60        RTS                                A:10 X:FF Y:CD P:65 SP:F9 PPU:283,184 CYC:21016\n$EF9A:17 48    *SLO $48,X @ 47 = #$37                A:10 X:FF Y:CD P:65 SP:FB PPU:301,184 CYC:21022\n$EF9C:EA        NOP                                A:7E X:FF Y:CD P:64 SP:FB PPU:319,184 CYC:21028\n$EF9D:EA        NOP                                A:7E X:FF Y:CD P:64 SP:FB PPU:325,184 CYC:21030\n$EF9E:EA        NOP                                A:7E X:FF Y:CD P:64 SP:FB PPU:331,184 CYC:21032\n$EF9F:EA        NOP                                A:7E X:FF Y:CD P:64 SP:FB PPU:337,184 CYC:21034\n$EFA0:20 A4 FA  JSR $FAA4                          A:7E X:FF Y:CD P:64 SP:FB PPU:  2,185 CYC:21036\n$FAA4:50 40     BVC $FAE6                          A:7E X:FF Y:CD P:64 SP:F9 PPU: 20,185 CYC:21042\n$FAA6:F0 3E     BEQ $FAE6                          A:7E X:FF Y:CD P:64 SP:F9 PPU: 26,185 CYC:21044\n$FAA8:30 3C     BMI $FAE6                          A:7E X:FF Y:CD P:64 SP:F9 PPU: 32,185 CYC:21046\n$FAAA:B0 3A     BCS $FAE6                          A:7E X:FF Y:CD P:64 SP:F9 PPU: 38,185 CYC:21048\n$FAAC:C9 7E     CMP #$7E                           A:7E X:FF Y:CD P:64 SP:F9 PPU: 44,185 CYC:21050\n$FAAE:D0 36     BNE $FAE6                          A:7E X:FF Y:CD P:67 SP:F9 PPU: 50,185 CYC:21052\n$FAB0:60        RTS                                A:7E X:FF Y:CD P:67 SP:F9 PPU: 56,185 CYC:21054\n$EFA3:A5 47     LDA $47 = #$6E                       A:7E X:FF Y:CD P:67 SP:FB PPU: 74,185 CYC:21060\n$EFA5:C9 6E     CMP #$6E                           A:6E X:FF Y:CD P:65 SP:FB PPU: 83,185 CYC:21063\n$EFA7:F0 02     BEQ $EFAB                          A:6E X:FF Y:CD P:67 SP:FB PPU: 89,185 CYC:21065\n$EFAB:A9 A5     LDA #$A5                           A:6E X:FF Y:CD P:67 SP:FB PPU: 98,185 CYC:21068\n$EFAD:8D 47 06  STA $0647 = #$6E                     A:A5 X:FF Y:CD P:E5 SP:FB PPU:104,185 CYC:21070\n$EFB0:A0 FF     LDY #$FF                           A:A5 X:FF Y:CD P:E5 SP:FB PPU:116,185 CYC:21074\n$EFB2:20 7B FA  JSR $FA7B                          A:A5 X:FF Y:FF P:E5 SP:FB PPU:122,185 CYC:21076\n$FA7B:24 01     BIT $01 = #$FF                       A:A5 X:FF Y:FF P:E5 SP:F9 PPU:140,185 CYC:21082\n$FA7D:18        CLC                                A:A5 X:FF Y:FF P:E5 SP:F9 PPU:149,185 CYC:21085\n$FA7E:A9 B3     LDA #$B3                           A:A5 X:FF Y:FF P:NVUbdIzc SP:F9 PPU:155,185 CYC:21087\n$FA80:60        RTS                                A:B3 X:FF Y:FF P:NVUbdIzc SP:F9 PPU:161,185 CYC:21089\n$EFB5:1B 48 05 *SLO $0548,Y @ 0647 = #$A5            A:B3 X:FF Y:FF P:NVUbdIzc SP:FB PPU:179,185 CYC:21095\n$EFB8:EA        NOP                                A:FB X:FF Y:FF P:E5 SP:FB PPU:200,185 CYC:21102\n$EFB9:EA        NOP                                A:FB X:FF Y:FF P:E5 SP:FB PPU:206,185 CYC:21104\n$EFBA:08        PHP                                A:FB X:FF Y:FF P:E5 SP:FB PPU:212,185 CYC:21106\n$EFBB:48        PHA                                A:FB X:FF Y:FF P:E5 SP:FA PPU:221,185 CYC:21109\n$EFBC:A0 CE     LDY #$CE                           A:FB X:FF Y:FF P:E5 SP:F9 PPU:230,185 CYC:21112\n$EFBE:68        PLA                                A:FB X:FF Y:CE P:E5 SP:F9 PPU:236,185 CYC:21114\n$EFBF:28        PLP                                A:FB X:FF Y:CE P:E5 SP:FA PPU:248,185 CYC:21118\n$EFC0:20 81 FA  JSR $FA81                          A:FB X:FF Y:CE P:E5 SP:FB PPU:260,185 CYC:21122\n$FA81:50 63     BVC $FAE6                          A:FB X:FF Y:CE P:E5 SP:F9 PPU:278,185 CYC:21128\n$FA83:90 61     BCC $FAE6                          A:FB X:FF Y:CE P:E5 SP:F9 PPU:284,185 CYC:21130\n$FA85:10 5F     BPL $FAE6                          A:FB X:FF Y:CE P:E5 SP:F9 PPU:290,185 CYC:21132\n$FA87:C9 FB     CMP #$FB                           A:FB X:FF Y:CE P:E5 SP:F9 PPU:296,185 CYC:21134\n$FA89:D0 5B     BNE $FAE6                          A:FB X:FF Y:CE P:67 SP:F9 PPU:302,185 CYC:21136\n$FA8B:60        RTS                                A:FB X:FF Y:CE P:67 SP:F9 PPU:308,185 CYC:21138\n$EFC3:AD 47 06  LDA $0647 = #$4A                     A:FB X:FF Y:CE P:67 SP:FB PPU:326,185 CYC:21144\n$EFC6:C9 4A     CMP #$4A                           A:4A X:FF Y:CE P:65 SP:FB PPU:338,185 CYC:21148\n$EFC8:F0 02     BEQ $EFCC                          A:4A X:FF Y:CE P:67 SP:FB PPU:  3,186 CYC:21150\n$EFCC:A0 FF     LDY #$FF                           A:4A X:FF Y:CE P:67 SP:FB PPU: 12,186 CYC:21153\n$EFCE:A9 29     LDA #$29                           A:4A X:FF Y:FF P:E5 SP:FB PPU: 18,186 CYC:21155\n$EFD0:8D 47 06  STA $0647 = #$4A                     A:29 X:FF Y:FF P:65 SP:FB PPU: 24,186 CYC:21157\n$EFD3:20 8C FA  JSR $FA8C                          A:29 X:FF Y:FF P:65 SP:FB PPU: 36,186 CYC:21161\n$FA8C:B8        CLV                                A:29 X:FF Y:FF P:65 SP:F9 PPU: 54,186 CYC:21167\n$FA8D:18        CLC                                A:29 X:FF Y:FF P:25 SP:F9 PPU: 60,186 CYC:21169\n$FA8E:A9 C3     LDA #$C3                           A:29 X:FF Y:FF P:nvUbdIzc SP:F9 PPU: 66,186 CYC:21171\n$FA90:60        RTS                                A:C3 X:FF Y:FF P:NvUbdIzc SP:F9 PPU: 72,186 CYC:21173\n$EFD6:1B 48 05 *SLO $0548,Y @ 0647 = #$29            A:C3 X:FF Y:FF P:NvUbdIzc SP:FB PPU: 90,186 CYC:21179\n$EFD9:EA        NOP                                A:D3 X:FF Y:FF P:NvUbdIzc SP:FB PPU:111,186 CYC:21186\n$EFDA:EA        NOP                                A:D3 X:FF Y:FF P:NvUbdIzc SP:FB PPU:117,186 CYC:21188\n$EFDB:08        PHP                                A:D3 X:FF Y:FF P:NvUbdIzc SP:FB PPU:123,186 CYC:21190\n$EFDC:48        PHA                                A:D3 X:FF Y:FF P:NvUbdIzc SP:FA PPU:132,186 CYC:21193\n$EFDD:A0 CF     LDY #$CF                           A:D3 X:FF Y:FF P:NvUbdIzc SP:F9 PPU:141,186 CYC:21196\n$EFDF:68        PLA                                A:D3 X:FF Y:CF P:NvUbdIzc SP:F9 PPU:147,186 CYC:21198\n$EFE0:28        PLP                                A:D3 X:FF Y:CF P:NvUbdIzc SP:FA PPU:159,186 CYC:21202\n$EFE1:20 91 FA  JSR $FA91                          A:D3 X:FF Y:CF P:NvUbdIzc SP:FB PPU:171,186 CYC:21206\n$FA91:70 53     BVS $FAE6                          A:D3 X:FF Y:CF P:NvUbdIzc SP:F9 PPU:189,186 CYC:21212\n$FA93:F0 51     BEQ $FAE6                          A:D3 X:FF Y:CF P:NvUbdIzc SP:F9 PPU:195,186 CYC:21214\n$FA95:10 4F     BPL $FAE6                          A:D3 X:FF Y:CF P:NvUbdIzc SP:F9 PPU:201,186 CYC:21216\n$FA97:B0 4D     BCS $FAE6                          A:D3 X:FF Y:CF P:NvUbdIzc SP:F9 PPU:207,186 CYC:21218\n$FA99:C9 D3     CMP #$D3                           A:D3 X:FF Y:CF P:NvUbdIzc SP:F9 PPU:213,186 CYC:21220\n$FA9B:D0 49     BNE $FAE6                          A:D3 X:FF Y:CF P:nvUbdIZC SP:F9 PPU:219,186 CYC:21222\n$FA9D:60        RTS                                A:D3 X:FF Y:CF P:nvUbdIZC SP:F9 PPU:225,186 CYC:21224\n$EFE4:AD 47 06  LDA $0647 = #$52                     A:D3 X:FF Y:CF P:nvUbdIZC SP:FB PPU:243,186 CYC:21230\n$EFE7:C9 52     CMP #$52                           A:52 X:FF Y:CF P:25 SP:FB PPU:255,186 CYC:21234\n$EFE9:F0 02     BEQ $EFED                          A:52 X:FF Y:CF P:nvUbdIZC SP:FB PPU:261,186 CYC:21236\n$EFED:A0 FF     LDY #$FF                           A:52 X:FF Y:CF P:nvUbdIZC SP:FB PPU:270,186 CYC:21239\n$EFEF:A9 37     LDA #$37                           A:52 X:FF Y:FF P:A5 SP:FB PPU:276,186 CYC:21241\n$EFF1:8D 47 06  STA $0647 = #$52                     A:37 X:FF Y:FF P:25 SP:FB PPU:282,186 CYC:21243\n$EFF4:20 9E FA  JSR $FA9E                          A:37 X:FF Y:FF P:25 SP:FB PPU:294,186 CYC:21247\n$FA9E:24 01     BIT $01 = #$FF                       A:37 X:FF Y:FF P:25 SP:F9 PPU:312,186 CYC:21253\n$FAA0:38        SEC                                A:37 X:FF Y:FF P:E5 SP:F9 PPU:321,186 CYC:21256\n$FAA1:A9 10     LDA #$10                           A:37 X:FF Y:FF P:E5 SP:F9 PPU:327,186 CYC:21258\n$FAA3:60        RTS                                A:10 X:FF Y:FF P:65 SP:F9 PPU:333,186 CYC:21260\n$EFF7:1B 48 05 *SLO $0548,Y @ 0647 = #$37            A:10 X:FF Y:FF P:65 SP:FB PPU: 10,187 CYC:21266\n$EFFA:EA        NOP                                A:7E X:FF Y:FF P:64 SP:FB PPU: 31,187 CYC:21273\n$EFFB:EA        NOP                                A:7E X:FF Y:FF P:64 SP:FB PPU: 37,187 CYC:21275\n$EFFC:08        PHP                                A:7E X:FF Y:FF P:64 SP:FB PPU: 43,187 CYC:21277\n$EFFD:48        PHA                                A:7E X:FF Y:FF P:64 SP:FA PPU: 52,187 CYC:21280\n$EFFE:A0 D0     LDY #$D0                           A:7E X:FF Y:FF P:64 SP:F9 PPU: 61,187 CYC:21283\n$F000:68        PLA                                A:7E X:FF Y:D0 P:NVUbdIzc SP:F9 PPU: 67,187 CYC:21285\n$F001:28        PLP                                A:7E X:FF Y:D0 P:64 SP:FA PPU: 79,187 CYC:21289\n$F002:20 A4 FA  JSR $FAA4                          A:7E X:FF Y:D0 P:64 SP:FB PPU: 91,187 CYC:21293\n$FAA4:50 40     BVC $FAE6                          A:7E X:FF Y:D0 P:64 SP:F9 PPU:109,187 CYC:21299\n$FAA6:F0 3E     BEQ $FAE6                          A:7E X:FF Y:D0 P:64 SP:F9 PPU:115,187 CYC:21301\n$FAA8:30 3C     BMI $FAE6                          A:7E X:FF Y:D0 P:64 SP:F9 PPU:121,187 CYC:21303\n$FAAA:B0 3A     BCS $FAE6                          A:7E X:FF Y:D0 P:64 SP:F9 PPU:127,187 CYC:21305\n$FAAC:C9 7E     CMP #$7E                           A:7E X:FF Y:D0 P:64 SP:F9 PPU:133,187 CYC:21307\n$FAAE:D0 36     BNE $FAE6                          A:7E X:FF Y:D0 P:67 SP:F9 PPU:139,187 CYC:21309\n$FAB0:60        RTS                                A:7E X:FF Y:D0 P:67 SP:F9 PPU:145,187 CYC:21311\n$F005:AD 47 06  LDA $0647 = #$6E                     A:7E X:FF Y:D0 P:67 SP:FB PPU:163,187 CYC:21317\n$F008:C9 6E     CMP #$6E                           A:6E X:FF Y:D0 P:65 SP:FB PPU:175,187 CYC:21321\n$F00A:F0 02     BEQ $F00E                          A:6E X:FF Y:D0 P:67 SP:FB PPU:181,187 CYC:21323\n$F00E:A0 D1     LDY #$D1                           A:6E X:FF Y:D0 P:67 SP:FB PPU:190,187 CYC:21326\n$F010:A2 FF     LDX #$FF                           A:6E X:FF Y:D1 P:E5 SP:FB PPU:196,187 CYC:21328\n$F012:A9 A5     LDA #$A5                           A:6E X:FF Y:D1 P:E5 SP:FB PPU:202,187 CYC:21330\n$F014:8D 47 06  STA $0647 = #$6E                     A:A5 X:FF Y:D1 P:E5 SP:FB PPU:208,187 CYC:21332\n$F017:20 7B FA  JSR $FA7B                          A:A5 X:FF Y:D1 P:E5 SP:FB PPU:220,187 CYC:21336\n$FA7B:24 01     BIT $01 = #$FF                       A:A5 X:FF Y:D1 P:E5 SP:F9 PPU:238,187 CYC:21342\n$FA7D:18        CLC                                A:A5 X:FF Y:D1 P:E5 SP:F9 PPU:247,187 CYC:21345\n$FA7E:A9 B3     LDA #$B3                           A:A5 X:FF Y:D1 P:NVUbdIzc SP:F9 PPU:253,187 CYC:21347\n$FA80:60        RTS                                A:B3 X:FF Y:D1 P:NVUbdIzc SP:F9 PPU:259,187 CYC:21349\n$F01A:1F 48 05 *SLO $0548,X @ 0647 = #$A5            A:B3 X:FF Y:D1 P:NVUbdIzc SP:FB PPU:277,187 CYC:21355\n$F01D:EA        NOP                                A:FB X:FF Y:D1 P:E5 SP:FB PPU:298,187 CYC:21362\n$F01E:EA        NOP                                A:FB X:FF Y:D1 P:E5 SP:FB PPU:304,187 CYC:21364\n$F01F:EA        NOP                                A:FB X:FF Y:D1 P:E5 SP:FB PPU:310,187 CYC:21366\n$F020:EA        NOP                                A:FB X:FF Y:D1 P:E5 SP:FB PPU:316,187 CYC:21368\n$F021:20 81 FA  JSR $FA81                          A:FB X:FF Y:D1 P:E5 SP:FB PPU:322,187 CYC:21370\n$FA81:50 63     BVC $FAE6                          A:FB X:FF Y:D1 P:E5 SP:F9 PPU:340,187 CYC:21376\n$FA83:90 61     BCC $FAE6                          A:FB X:FF Y:D1 P:E5 SP:F9 PPU:  5,188 CYC:21378\n$FA85:10 5F     BPL $FAE6                          A:FB X:FF Y:D1 P:E5 SP:F9 PPU: 11,188 CYC:21380\n$FA87:C9 FB     CMP #$FB                           A:FB X:FF Y:D1 P:E5 SP:F9 PPU: 17,188 CYC:21382\n$FA89:D0 5B     BNE $FAE6                          A:FB X:FF Y:D1 P:67 SP:F9 PPU: 23,188 CYC:21384\n$FA8B:60        RTS                                A:FB X:FF Y:D1 P:67 SP:F9 PPU: 29,188 CYC:21386\n$F024:AD 47 06  LDA $0647 = #$4A                     A:FB X:FF Y:D1 P:67 SP:FB PPU: 47,188 CYC:21392\n$F027:C9 4A     CMP #$4A                           A:4A X:FF Y:D1 P:65 SP:FB PPU: 59,188 CYC:21396\n$F029:F0 02     BEQ $F02D                          A:4A X:FF Y:D1 P:67 SP:FB PPU: 65,188 CYC:21398\n$F02D:C8        INY                                A:4A X:FF Y:D1 P:67 SP:FB PPU: 74,188 CYC:21401\n$F02E:A9 29     LDA #$29                           A:4A X:FF Y:D2 P:E5 SP:FB PPU: 80,188 CYC:21403\n$F030:8D 47 06  STA $0647 = #$4A                     A:29 X:FF Y:D2 P:65 SP:FB PPU: 86,188 CYC:21405\n$F033:20 8C FA  JSR $FA8C                          A:29 X:FF Y:D2 P:65 SP:FB PPU: 98,188 CYC:21409\n$FA8C:B8        CLV                                A:29 X:FF Y:D2 P:65 SP:F9 PPU:116,188 CYC:21415\n$FA8D:18        CLC                                A:29 X:FF Y:D2 P:25 SP:F9 PPU:122,188 CYC:21417\n$FA8E:A9 C3     LDA #$C3                           A:29 X:FF Y:D2 P:nvUbdIzc SP:F9 PPU:128,188 CYC:21419\n$FA90:60        RTS                                A:C3 X:FF Y:D2 P:NvUbdIzc SP:F9 PPU:134,188 CYC:21421\n$F036:1F 48 05 *SLO $0548,X @ 0647 = #$29            A:C3 X:FF Y:D2 P:NvUbdIzc SP:FB PPU:152,188 CYC:21427\n$F039:EA        NOP                                A:D3 X:FF Y:D2 P:NvUbdIzc SP:FB PPU:173,188 CYC:21434\n$F03A:EA        NOP                                A:D3 X:FF Y:D2 P:NvUbdIzc SP:FB PPU:179,188 CYC:21436\n$F03B:EA        NOP                                A:D3 X:FF Y:D2 P:NvUbdIzc SP:FB PPU:185,188 CYC:21438\n$F03C:EA        NOP                                A:D3 X:FF Y:D2 P:NvUbdIzc SP:FB PPU:191,188 CYC:21440\n$F03D:20 91 FA  JSR $FA91                          A:D3 X:FF Y:D2 P:NvUbdIzc SP:FB PPU:197,188 CYC:21442\n$FA91:70 53     BVS $FAE6                          A:D3 X:FF Y:D2 P:NvUbdIzc SP:F9 PPU:215,188 CYC:21448\n$FA93:F0 51     BEQ $FAE6                          A:D3 X:FF Y:D2 P:NvUbdIzc SP:F9 PPU:221,188 CYC:21450\n$FA95:10 4F     BPL $FAE6                          A:D3 X:FF Y:D2 P:NvUbdIzc SP:F9 PPU:227,188 CYC:21452\n$FA97:B0 4D     BCS $FAE6                          A:D3 X:FF Y:D2 P:NvUbdIzc SP:F9 PPU:233,188 CYC:21454\n$FA99:C9 D3     CMP #$D3                           A:D3 X:FF Y:D2 P:NvUbdIzc SP:F9 PPU:239,188 CYC:21456\n$FA9B:D0 49     BNE $FAE6                          A:D3 X:FF Y:D2 P:nvUbdIZC SP:F9 PPU:245,188 CYC:21458\n$FA9D:60        RTS                                A:D3 X:FF Y:D2 P:nvUbdIZC SP:F9 PPU:251,188 CYC:21460\n$F040:AD 47 06  LDA $0647 = #$52                     A:D3 X:FF Y:D2 P:nvUbdIZC SP:FB PPU:269,188 CYC:21466\n$F043:C9 52     CMP #$52                           A:52 X:FF Y:D2 P:25 SP:FB PPU:281,188 CYC:21470\n$F045:F0 02     BEQ $F049                          A:52 X:FF Y:D2 P:nvUbdIZC SP:FB PPU:287,188 CYC:21472\n$F049:C8        INY                                A:52 X:FF Y:D2 P:nvUbdIZC SP:FB PPU:296,188 CYC:21475\n$F04A:A9 37     LDA #$37                           A:52 X:FF Y:D3 P:A5 SP:FB PPU:302,188 CYC:21477\n$F04C:8D 47 06  STA $0647 = #$52                     A:37 X:FF Y:D3 P:25 SP:FB PPU:308,188 CYC:21479\n$F04F:20 9E FA  JSR $FA9E                          A:37 X:FF Y:D3 P:25 SP:FB PPU:320,188 CYC:21483\n$FA9E:24 01     BIT $01 = #$FF                       A:37 X:FF Y:D3 P:25 SP:F9 PPU:338,188 CYC:21489\n$FAA0:38        SEC                                A:37 X:FF Y:D3 P:E5 SP:F9 PPU:  6,189 CYC:21492\n$FAA1:A9 10     LDA #$10                           A:37 X:FF Y:D3 P:E5 SP:F9 PPU: 12,189 CYC:21494\n$FAA3:60        RTS                                A:10 X:FF Y:D3 P:65 SP:F9 PPU: 18,189 CYC:21496\n$F052:1F 48 05 *SLO $0548,X @ 0647 = #$37            A:10 X:FF Y:D3 P:65 SP:FB PPU: 36,189 CYC:21502\n$F055:EA        NOP                                A:7E X:FF Y:D3 P:64 SP:FB PPU: 57,189 CYC:21509\n$F056:EA        NOP                                A:7E X:FF Y:D3 P:64 SP:FB PPU: 63,189 CYC:21511\n$F057:EA        NOP                                A:7E X:FF Y:D3 P:64 SP:FB PPU: 69,189 CYC:21513\n$F058:EA        NOP                                A:7E X:FF Y:D3 P:64 SP:FB PPU: 75,189 CYC:21515\n$F059:20 A4 FA  JSR $FAA4                          A:7E X:FF Y:D3 P:64 SP:FB PPU: 81,189 CYC:21517\n$FAA4:50 40     BVC $FAE6                          A:7E X:FF Y:D3 P:64 SP:F9 PPU: 99,189 CYC:21523\n$FAA6:F0 3E     BEQ $FAE6                          A:7E X:FF Y:D3 P:64 SP:F9 PPU:105,189 CYC:21525\n$FAA8:30 3C     BMI $FAE6                          A:7E X:FF Y:D3 P:64 SP:F9 PPU:111,189 CYC:21527\n$FAAA:B0 3A     BCS $FAE6                          A:7E X:FF Y:D3 P:64 SP:F9 PPU:117,189 CYC:21529\n$FAAC:C9 7E     CMP #$7E                           A:7E X:FF Y:D3 P:64 SP:F9 PPU:123,189 CYC:21531\n$FAAE:D0 36     BNE $FAE6                          A:7E X:FF Y:D3 P:67 SP:F9 PPU:129,189 CYC:21533\n$FAB0:60        RTS                                A:7E X:FF Y:D3 P:67 SP:F9 PPU:135,189 CYC:21535\n$F05C:AD 47 06  LDA $0647 = #$6E                     A:7E X:FF Y:D3 P:67 SP:FB PPU:153,189 CYC:21541\n$F05F:C9 6E     CMP #$6E                           A:6E X:FF Y:D3 P:65 SP:FB PPU:165,189 CYC:21545\n$F061:F0 02     BEQ $F065                          A:6E X:FF Y:D3 P:67 SP:FB PPU:171,189 CYC:21547\n$F065:60        RTS                                A:6E X:FF Y:D3 P:67 SP:FB PPU:180,189 CYC:21550\n$C644:20 66 F0  JSR $F066                          A:6E X:FF Y:D3 P:67 SP:FD PPU:198,189 CYC:21556\n$F066:A9 FF     LDA #$FF                           A:6E X:FF Y:D3 P:67 SP:FB PPU:216,189 CYC:21562\n$F068:85 01     STA $01 = #$FF                       A:FF X:FF Y:D3 P:E5 SP:FB PPU:222,189 CYC:21564\n$F06A:A0 D4     LDY #$D4                           A:FF X:FF Y:D3 P:E5 SP:FB PPU:231,189 CYC:21567\n$F06C:A2 02     LDX #$02                           A:FF X:FF Y:D4 P:E5 SP:FB PPU:237,189 CYC:21569\n$F06E:A9 47     LDA #$47                           A:FF X:02 Y:D4 P:65 SP:FB PPU:243,189 CYC:21571\n$F070:85 47     STA $47 = #$6E                       A:47 X:02 Y:D4 P:65 SP:FB PPU:249,189 CYC:21573\n$F072:A9 06     LDA #$06                           A:47 X:02 Y:D4 P:65 SP:FB PPU:258,189 CYC:21576\n$F074:85 48     STA $48 = #$06                       A:06 X:02 Y:D4 P:65 SP:FB PPU:264,189 CYC:21578\n$F076:A9 A5     LDA #$A5                           A:06 X:02 Y:D4 P:65 SP:FB PPU:273,189 CYC:21581\n$F078:8D 47 06  STA $0647 = #$6E                     A:A5 X:02 Y:D4 P:E5 SP:FB PPU:279,189 CYC:21583\n$F07B:20 53 FB  JSR $FB53                          A:A5 X:02 Y:D4 P:E5 SP:FB PPU:291,189 CYC:21587\n$FB53:24 01     BIT $01 = #$FF                       A:A5 X:02 Y:D4 P:E5 SP:F9 PPU:309,189 CYC:21593\n$FB55:18        CLC                                A:A5 X:02 Y:D4 P:E5 SP:F9 PPU:318,189 CYC:21596\n$FB56:A9 B3     LDA #$B3                           A:A5 X:02 Y:D4 P:NVUbdIzc SP:F9 PPU:324,189 CYC:21598\n$FB58:60        RTS                                A:B3 X:02 Y:D4 P:NVUbdIzc SP:F9 PPU:330,189 CYC:21600\n$F07E:23 45    *RLA ($45,X) @ 47 = #$0647 = A5       A:B3 X:02 Y:D4 P:NVUbdIzc SP:FB PPU:  7,190 CYC:21606\n$F080:EA        NOP                                A:02 X:02 Y:D4 P:65 SP:FB PPU: 31,190 CYC:21614\n$F081:EA        NOP                                A:02 X:02 Y:D4 P:65 SP:FB PPU: 37,190 CYC:21616\n$F082:EA        NOP                                A:02 X:02 Y:D4 P:65 SP:FB PPU: 43,190 CYC:21618\n$F083:EA        NOP                                A:02 X:02 Y:D4 P:65 SP:FB PPU: 49,190 CYC:21620\n$F084:20 59 FB  JSR $FB59                          A:02 X:02 Y:D4 P:65 SP:FB PPU: 55,190 CYC:21622\n$FB59:50 1A     BVC $FB75                          A:02 X:02 Y:D4 P:65 SP:F9 PPU: 73,190 CYC:21628\n$FB5B:90 18     BCC $FB75                          A:02 X:02 Y:D4 P:65 SP:F9 PPU: 79,190 CYC:21630\n$FB5D:30 16     BMI $FB75                          A:02 X:02 Y:D4 P:65 SP:F9 PPU: 85,190 CYC:21632\n$FB5F:C9 02     CMP #$02                           A:02 X:02 Y:D4 P:65 SP:F9 PPU: 91,190 CYC:21634\n$FB61:D0 12     BNE $FB75                          A:02 X:02 Y:D4 P:67 SP:F9 PPU: 97,190 CYC:21636\n$FB63:60        RTS                                A:02 X:02 Y:D4 P:67 SP:F9 PPU:103,190 CYC:21638\n$F087:AD 47 06  LDA $0647 = #$4A                     A:02 X:02 Y:D4 P:67 SP:FB PPU:121,190 CYC:21644\n$F08A:C9 4A     CMP #$4A                           A:4A X:02 Y:D4 P:65 SP:FB PPU:133,190 CYC:21648\n$F08C:F0 02     BEQ $F090                          A:4A X:02 Y:D4 P:67 SP:FB PPU:139,190 CYC:21650\n$F090:C8        INY                                A:4A X:02 Y:D4 P:67 SP:FB PPU:148,190 CYC:21653\n$F091:A9 29     LDA #$29                           A:4A X:02 Y:D5 P:E5 SP:FB PPU:154,190 CYC:21655\n$F093:8D 47 06  STA $0647 = #$4A                     A:29 X:02 Y:D5 P:65 SP:FB PPU:160,190 CYC:21657\n$F096:20 64 FB  JSR $FB64                          A:29 X:02 Y:D5 P:65 SP:FB PPU:172,190 CYC:21661\n$FB64:B8        CLV                                A:29 X:02 Y:D5 P:65 SP:F9 PPU:190,190 CYC:21667\n$FB65:18        CLC                                A:29 X:02 Y:D5 P:25 SP:F9 PPU:196,190 CYC:21669\n$FB66:A9 42     LDA #$42                           A:29 X:02 Y:D5 P:nvUbdIzc SP:F9 PPU:202,190 CYC:21671\n$FB68:60        RTS                                A:42 X:02 Y:D5 P:nvUbdIzc SP:F9 PPU:208,190 CYC:21673\n$F099:23 45    *RLA ($45,X) @ 47 = #$0647 = 29       A:42 X:02 Y:D5 P:nvUbdIzc SP:FB PPU:226,190 CYC:21679\n$F09B:EA        NOP                                A:42 X:02 Y:D5 P:nvUbdIzc SP:FB PPU:250,190 CYC:21687\n$F09C:EA        NOP                                A:42 X:02 Y:D5 P:nvUbdIzc SP:FB PPU:256,190 CYC:21689\n$F09D:EA        NOP                                A:42 X:02 Y:D5 P:nvUbdIzc SP:FB PPU:262,190 CYC:21691\n$F09E:EA        NOP                                A:42 X:02 Y:D5 P:nvUbdIzc SP:FB PPU:268,190 CYC:21693\n$F09F:20 69 FB  JSR $FB69                          A:42 X:02 Y:D5 P:nvUbdIzc SP:FB PPU:274,190 CYC:21695\n$FB69:70 0A     BVS $FB75                          A:42 X:02 Y:D5 P:nvUbdIzc SP:F9 PPU:292,190 CYC:21701\n$FB6B:F0 08     BEQ $FB75                          A:42 X:02 Y:D5 P:nvUbdIzc SP:F9 PPU:298,190 CYC:21703\n$FB6D:30 06     BMI $FB75                          A:42 X:02 Y:D5 P:nvUbdIzc SP:F9 PPU:304,190 CYC:21705\n$FB6F:B0 04     BCS $FB75                          A:42 X:02 Y:D5 P:nvUbdIzc SP:F9 PPU:310,190 CYC:21707\n$FB71:C9 42     CMP #$42                           A:42 X:02 Y:D5 P:nvUbdIzc SP:F9 PPU:316,190 CYC:21709\n$FB73:F0 02     BEQ $FB77                          A:42 X:02 Y:D5 P:nvUbdIZC SP:F9 PPU:322,190 CYC:21711\n$FB77:60        RTS                                A:42 X:02 Y:D5 P:nvUbdIZC SP:F9 PPU:331,190 CYC:21714\n$F0A2:AD 47 06  LDA $0647 = #$52                     A:42 X:02 Y:D5 P:nvUbdIZC SP:FB PPU:  8,191 CYC:21720\n$F0A5:C9 52     CMP #$52                           A:52 X:02 Y:D5 P:25 SP:FB PPU: 20,191 CYC:21724\n$F0A7:F0 02     BEQ $F0AB                          A:52 X:02 Y:D5 P:nvUbdIZC SP:FB PPU: 26,191 CYC:21726\n$F0AB:C8        INY                                A:52 X:02 Y:D5 P:nvUbdIZC SP:FB PPU: 35,191 CYC:21729\n$F0AC:A9 37     LDA #$37                           A:52 X:02 Y:D6 P:A5 SP:FB PPU: 41,191 CYC:21731\n$F0AE:8D 47 06  STA $0647 = #$52                     A:37 X:02 Y:D6 P:25 SP:FB PPU: 47,191 CYC:21733\n$F0B1:20 68 FA  JSR $FA68                          A:37 X:02 Y:D6 P:25 SP:FB PPU: 59,191 CYC:21737\n$FA68:24 01     BIT $01 = #$FF                       A:37 X:02 Y:D6 P:25 SP:F9 PPU: 77,191 CYC:21743\n$FA6A:38        SEC                                A:37 X:02 Y:D6 P:E5 SP:F9 PPU: 86,191 CYC:21746\n$FA6B:A9 75     LDA #$75                           A:37 X:02 Y:D6 P:E5 SP:F9 PPU: 92,191 CYC:21748\n$FA6D:60        RTS                                A:75 X:02 Y:D6 P:65 SP:F9 PPU: 98,191 CYC:21750\n$F0B4:23 45    *RLA ($45,X) @ 47 = #$0647 = 37       A:75 X:02 Y:D6 P:65 SP:FB PPU:116,191 CYC:21756\n$F0B6:EA        NOP                                A:65 X:02 Y:D6 P:64 SP:FB PPU:140,191 CYC:21764\n$F0B7:EA        NOP                                A:65 X:02 Y:D6 P:64 SP:FB PPU:146,191 CYC:21766\n$F0B8:EA        NOP                                A:65 X:02 Y:D6 P:64 SP:FB PPU:152,191 CYC:21768\n$F0B9:EA        NOP                                A:65 X:02 Y:D6 P:64 SP:FB PPU:158,191 CYC:21770\n$F0BA:20 6E FA  JSR $FA6E                          A:65 X:02 Y:D6 P:64 SP:FB PPU:164,191 CYC:21772\n$FA6E:50 76     BVC $FAE6                          A:65 X:02 Y:D6 P:64 SP:F9 PPU:182,191 CYC:21778\n$FA70:F0 74     BEQ $FAE6                          A:65 X:02 Y:D6 P:64 SP:F9 PPU:188,191 CYC:21780\n$FA72:30 72     BMI $FAE6                          A:65 X:02 Y:D6 P:64 SP:F9 PPU:194,191 CYC:21782\n$FA74:B0 70     BCS $FAE6                          A:65 X:02 Y:D6 P:64 SP:F9 PPU:200,191 CYC:21784\n$FA76:C9 65     CMP #$65                           A:65 X:02 Y:D6 P:64 SP:F9 PPU:206,191 CYC:21786\n$FA78:D0 6C     BNE $FAE6                          A:65 X:02 Y:D6 P:67 SP:F9 PPU:212,191 CYC:21788\n$FA7A:60        RTS                                A:65 X:02 Y:D6 P:67 SP:F9 PPU:218,191 CYC:21790\n$F0BD:AD 47 06  LDA $0647 = #$6F                     A:65 X:02 Y:D6 P:67 SP:FB PPU:236,191 CYC:21796\n$F0C0:C9 6F     CMP #$6F                           A:6F X:02 Y:D6 P:65 SP:FB PPU:248,191 CYC:21800\n$F0C2:F0 02     BEQ $F0C6                          A:6F X:02 Y:D6 P:67 SP:FB PPU:254,191 CYC:21802\n$F0C6:C8        INY                                A:6F X:02 Y:D6 P:67 SP:FB PPU:263,191 CYC:21805\n$F0C7:A9 A5     LDA #$A5                           A:6F X:02 Y:D7 P:E5 SP:FB PPU:269,191 CYC:21807\n$F0C9:85 47     STA $47 = #$47                       A:A5 X:02 Y:D7 P:E5 SP:FB PPU:275,191 CYC:21809\n$F0CB:20 53 FB  JSR $FB53                          A:A5 X:02 Y:D7 P:E5 SP:FB PPU:284,191 CYC:21812\n$FB53:24 01     BIT $01 = #$FF                       A:A5 X:02 Y:D7 P:E5 SP:F9 PPU:302,191 CYC:21818\n$FB55:18        CLC                                A:A5 X:02 Y:D7 P:E5 SP:F9 PPU:311,191 CYC:21821\n$FB56:A9 B3     LDA #$B3                           A:A5 X:02 Y:D7 P:NVUbdIzc SP:F9 PPU:317,191 CYC:21823\n$FB58:60        RTS                                A:B3 X:02 Y:D7 P:NVUbdIzc SP:F9 PPU:323,191 CYC:21825\n$F0CE:27 47    *RLA $47 = #$A5                       A:B3 X:02 Y:D7 P:NVUbdIzc SP:FB PPU:  0,192 CYC:21831\n$F0D0:EA        NOP                                A:02 X:02 Y:D7 P:65 SP:FB PPU: 15,192 CYC:21836\n$F0D1:EA        NOP                                A:02 X:02 Y:D7 P:65 SP:FB PPU: 21,192 CYC:21838\n$F0D2:EA        NOP                                A:02 X:02 Y:D7 P:65 SP:FB PPU: 27,192 CYC:21840\n$F0D3:EA        NOP                                A:02 X:02 Y:D7 P:65 SP:FB PPU: 33,192 CYC:21842\n$F0D4:20 59 FB  JSR $FB59                          A:02 X:02 Y:D7 P:65 SP:FB PPU: 39,192 CYC:21844\n$FB59:50 1A     BVC $FB75                          A:02 X:02 Y:D7 P:65 SP:F9 PPU: 57,192 CYC:21850\n$FB5B:90 18     BCC $FB75                          A:02 X:02 Y:D7 P:65 SP:F9 PPU: 63,192 CYC:21852\n$FB5D:30 16     BMI $FB75                          A:02 X:02 Y:D7 P:65 SP:F9 PPU: 69,192 CYC:21854\n$FB5F:C9 02     CMP #$02                           A:02 X:02 Y:D7 P:65 SP:F9 PPU: 75,192 CYC:21856\n$FB61:D0 12     BNE $FB75                          A:02 X:02 Y:D7 P:67 SP:F9 PPU: 81,192 CYC:21858\n$FB63:60        RTS                                A:02 X:02 Y:D7 P:67 SP:F9 PPU: 87,192 CYC:21860\n$F0D7:A5 47     LDA $47 = #$4A                       A:02 X:02 Y:D7 P:67 SP:FB PPU:105,192 CYC:21866\n$F0D9:C9 4A     CMP #$4A                           A:4A X:02 Y:D7 P:65 SP:FB PPU:114,192 CYC:21869\n$F0DB:F0 02     BEQ $F0DF                          A:4A X:02 Y:D7 P:67 SP:FB PPU:120,192 CYC:21871\n$F0DF:C8        INY                                A:4A X:02 Y:D7 P:67 SP:FB PPU:129,192 CYC:21874\n$F0E0:A9 29     LDA #$29                           A:4A X:02 Y:D8 P:E5 SP:FB PPU:135,192 CYC:21876\n$F0E2:85 47     STA $47 = #$4A                       A:29 X:02 Y:D8 P:65 SP:FB PPU:141,192 CYC:21878\n$F0E4:20 64 FB  JSR $FB64                          A:29 X:02 Y:D8 P:65 SP:FB PPU:150,192 CYC:21881\n$FB64:B8        CLV                                A:29 X:02 Y:D8 P:65 SP:F9 PPU:168,192 CYC:21887\n$FB65:18        CLC                                A:29 X:02 Y:D8 P:25 SP:F9 PPU:174,192 CYC:21889\n$FB66:A9 42     LDA #$42                           A:29 X:02 Y:D8 P:nvUbdIzc SP:F9 PPU:180,192 CYC:21891\n$FB68:60        RTS                                A:42 X:02 Y:D8 P:nvUbdIzc SP:F9 PPU:186,192 CYC:21893\n$F0E7:27 47    *RLA $47 = #$29                       A:42 X:02 Y:D8 P:nvUbdIzc SP:FB PPU:204,192 CYC:21899\n$F0E9:EA        NOP                                A:42 X:02 Y:D8 P:nvUbdIzc SP:FB PPU:219,192 CYC:21904\n$F0EA:EA        NOP                                A:42 X:02 Y:D8 P:nvUbdIzc SP:FB PPU:225,192 CYC:21906\n$F0EB:EA        NOP                                A:42 X:02 Y:D8 P:nvUbdIzc SP:FB PPU:231,192 CYC:21908\n$F0EC:EA        NOP                                A:42 X:02 Y:D8 P:nvUbdIzc SP:FB PPU:237,192 CYC:21910\n$F0ED:20 69 FB  JSR $FB69                          A:42 X:02 Y:D8 P:nvUbdIzc SP:FB PPU:243,192 CYC:21912\n$FB69:70 0A     BVS $FB75                          A:42 X:02 Y:D8 P:nvUbdIzc SP:F9 PPU:261,192 CYC:21918\n$FB6B:F0 08     BEQ $FB75                          A:42 X:02 Y:D8 P:nvUbdIzc SP:F9 PPU:267,192 CYC:21920\n$FB6D:30 06     BMI $FB75                          A:42 X:02 Y:D8 P:nvUbdIzc SP:F9 PPU:273,192 CYC:21922\n$FB6F:B0 04     BCS $FB75                          A:42 X:02 Y:D8 P:nvUbdIzc SP:F9 PPU:279,192 CYC:21924\n$FB71:C9 42     CMP #$42                           A:42 X:02 Y:D8 P:nvUbdIzc SP:F9 PPU:285,192 CYC:21926\n$FB73:F0 02     BEQ $FB77                          A:42 X:02 Y:D8 P:nvUbdIZC SP:F9 PPU:291,192 CYC:21928\n$FB77:60        RTS                                A:42 X:02 Y:D8 P:nvUbdIZC SP:F9 PPU:300,192 CYC:21931\n$F0F0:A5 47     LDA $47 = #$52                       A:42 X:02 Y:D8 P:nvUbdIZC SP:FB PPU:318,192 CYC:21937\n$F0F2:C9 52     CMP #$52                           A:52 X:02 Y:D8 P:25 SP:FB PPU:327,192 CYC:21940\n$F0F4:F0 02     BEQ $F0F8                          A:52 X:02 Y:D8 P:nvUbdIZC SP:FB PPU:333,192 CYC:21942\n$F0F8:C8        INY                                A:52 X:02 Y:D8 P:nvUbdIZC SP:FB PPU:  1,193 CYC:21945\n$F0F9:A9 37     LDA #$37                           A:52 X:02 Y:D9 P:A5 SP:FB PPU:  7,193 CYC:21947\n$F0FB:85 47     STA $47 = #$52                       A:37 X:02 Y:D9 P:25 SP:FB PPU: 13,193 CYC:21949\n$F0FD:20 68 FA  JSR $FA68                          A:37 X:02 Y:D9 P:25 SP:FB PPU: 22,193 CYC:21952\n$FA68:24 01     BIT $01 = #$FF                       A:37 X:02 Y:D9 P:25 SP:F9 PPU: 40,193 CYC:21958\n$FA6A:38        SEC                                A:37 X:02 Y:D9 P:E5 SP:F9 PPU: 49,193 CYC:21961\n$FA6B:A9 75     LDA #$75                           A:37 X:02 Y:D9 P:E5 SP:F9 PPU: 55,193 CYC:21963\n$FA6D:60        RTS                                A:75 X:02 Y:D9 P:65 SP:F9 PPU: 61,193 CYC:21965\n$F100:27 47    *RLA $47 = #$37                       A:75 X:02 Y:D9 P:65 SP:FB PPU: 79,193 CYC:21971\n$F102:EA        NOP                                A:65 X:02 Y:D9 P:64 SP:FB PPU: 94,193 CYC:21976\n$F103:EA        NOP                                A:65 X:02 Y:D9 P:64 SP:FB PPU:100,193 CYC:21978\n$F104:EA        NOP                                A:65 X:02 Y:D9 P:64 SP:FB PPU:106,193 CYC:21980\n$F105:EA        NOP                                A:65 X:02 Y:D9 P:64 SP:FB PPU:112,193 CYC:21982\n$F106:20 6E FA  JSR $FA6E                          A:65 X:02 Y:D9 P:64 SP:FB PPU:118,193 CYC:21984\n$FA6E:50 76     BVC $FAE6                          A:65 X:02 Y:D9 P:64 SP:F9 PPU:136,193 CYC:21990\n$FA70:F0 74     BEQ $FAE6                          A:65 X:02 Y:D9 P:64 SP:F9 PPU:142,193 CYC:21992\n$FA72:30 72     BMI $FAE6                          A:65 X:02 Y:D9 P:64 SP:F9 PPU:148,193 CYC:21994\n$FA74:B0 70     BCS $FAE6                          A:65 X:02 Y:D9 P:64 SP:F9 PPU:154,193 CYC:21996\n$FA76:C9 65     CMP #$65                           A:65 X:02 Y:D9 P:64 SP:F9 PPU:160,193 CYC:21998\n$FA78:D0 6C     BNE $FAE6                          A:65 X:02 Y:D9 P:67 SP:F9 PPU:166,193 CYC:22000\n$FA7A:60        RTS                                A:65 X:02 Y:D9 P:67 SP:F9 PPU:172,193 CYC:22002\n$F109:A5 47     LDA $47 = #$6F                       A:65 X:02 Y:D9 P:67 SP:FB PPU:190,193 CYC:22008\n$F10B:C9 6F     CMP #$6F                           A:6F X:02 Y:D9 P:65 SP:FB PPU:199,193 CYC:22011\n$F10D:F0 02     BEQ $F111                          A:6F X:02 Y:D9 P:67 SP:FB PPU:205,193 CYC:22013\n$F111:C8        INY                                A:6F X:02 Y:D9 P:67 SP:FB PPU:214,193 CYC:22016\n$F112:A9 A5     LDA #$A5                           A:6F X:02 Y:DA P:E5 SP:FB PPU:220,193 CYC:22018\n$F114:8D 47 06  STA $0647 = #$6F                     A:A5 X:02 Y:DA P:E5 SP:FB PPU:226,193 CYC:22020\n$F117:20 53 FB  JSR $FB53                          A:A5 X:02 Y:DA P:E5 SP:FB PPU:238,193 CYC:22024\n$FB53:24 01     BIT $01 = #$FF                       A:A5 X:02 Y:DA P:E5 SP:F9 PPU:256,193 CYC:22030\n$FB55:18        CLC                                A:A5 X:02 Y:DA P:E5 SP:F9 PPU:265,193 CYC:22033\n$FB56:A9 B3     LDA #$B3                           A:A5 X:02 Y:DA P:NVUbdIzc SP:F9 PPU:271,193 CYC:22035\n$FB58:60        RTS                                A:B3 X:02 Y:DA P:NVUbdIzc SP:F9 PPU:277,193 CYC:22037\n$F11A:2F 47 06 *RLA $0647 = #$A5                     A:B3 X:02 Y:DA P:NVUbdIzc SP:FB PPU:295,193 CYC:22043\n$F11D:EA        NOP                                A:02 X:02 Y:DA P:65 SP:FB PPU:313,193 CYC:22049\n$F11E:EA        NOP                                A:02 X:02 Y:DA P:65 SP:FB PPU:319,193 CYC:22051\n$F11F:EA        NOP                                A:02 X:02 Y:DA P:65 SP:FB PPU:325,193 CYC:22053\n$F120:EA        NOP                                A:02 X:02 Y:DA P:65 SP:FB PPU:331,193 CYC:22055\n$F121:20 59 FB  JSR $FB59                          A:02 X:02 Y:DA P:65 SP:FB PPU:337,193 CYC:22057\n$FB59:50 1A     BVC $FB75                          A:02 X:02 Y:DA P:65 SP:F9 PPU: 14,194 CYC:22063\n$FB5B:90 18     BCC $FB75                          A:02 X:02 Y:DA P:65 SP:F9 PPU: 20,194 CYC:22065\n$FB5D:30 16     BMI $FB75                          A:02 X:02 Y:DA P:65 SP:F9 PPU: 26,194 CYC:22067\n$FB5F:C9 02     CMP #$02                           A:02 X:02 Y:DA P:65 SP:F9 PPU: 32,194 CYC:22069\n$FB61:D0 12     BNE $FB75                          A:02 X:02 Y:DA P:67 SP:F9 PPU: 38,194 CYC:22071\n$FB63:60        RTS                                A:02 X:02 Y:DA P:67 SP:F9 PPU: 44,194 CYC:22073\n$F124:AD 47 06  LDA $0647 = #$4A                     A:02 X:02 Y:DA P:67 SP:FB PPU: 62,194 CYC:22079\n$F127:C9 4A     CMP #$4A                           A:4A X:02 Y:DA P:65 SP:FB PPU: 74,194 CYC:22083\n$F129:F0 02     BEQ $F12D                          A:4A X:02 Y:DA P:67 SP:FB PPU: 80,194 CYC:22085\n$F12D:C8        INY                                A:4A X:02 Y:DA P:67 SP:FB PPU: 89,194 CYC:22088\n$F12E:A9 29     LDA #$29                           A:4A X:02 Y:DB P:E5 SP:FB PPU: 95,194 CYC:22090\n$F130:8D 47 06  STA $0647 = #$4A                     A:29 X:02 Y:DB P:65 SP:FB PPU:101,194 CYC:22092\n$F133:20 64 FB  JSR $FB64                          A:29 X:02 Y:DB P:65 SP:FB PPU:113,194 CYC:22096\n$FB64:B8        CLV                                A:29 X:02 Y:DB P:65 SP:F9 PPU:131,194 CYC:22102\n$FB65:18        CLC                                A:29 X:02 Y:DB P:25 SP:F9 PPU:137,194 CYC:22104\n$FB66:A9 42     LDA #$42                           A:29 X:02 Y:DB P:nvUbdIzc SP:F9 PPU:143,194 CYC:22106\n$FB68:60        RTS                                A:42 X:02 Y:DB P:nvUbdIzc SP:F9 PPU:149,194 CYC:22108\n$F136:2F 47 06 *RLA $0647 = #$29                     A:42 X:02 Y:DB P:nvUbdIzc SP:FB PPU:167,194 CYC:22114\n$F139:EA        NOP                                A:42 X:02 Y:DB P:nvUbdIzc SP:FB PPU:185,194 CYC:22120\n$F13A:EA        NOP                                A:42 X:02 Y:DB P:nvUbdIzc SP:FB PPU:191,194 CYC:22122\n$F13B:EA        NOP                                A:42 X:02 Y:DB P:nvUbdIzc SP:FB PPU:197,194 CYC:22124\n$F13C:EA        NOP                                A:42 X:02 Y:DB P:nvUbdIzc SP:FB PPU:203,194 CYC:22126\n$F13D:20 69 FB  JSR $FB69                          A:42 X:02 Y:DB P:nvUbdIzc SP:FB PPU:209,194 CYC:22128\n$FB69:70 0A     BVS $FB75                          A:42 X:02 Y:DB P:nvUbdIzc SP:F9 PPU:227,194 CYC:22134\n$FB6B:F0 08     BEQ $FB75                          A:42 X:02 Y:DB P:nvUbdIzc SP:F9 PPU:233,194 CYC:22136\n$FB6D:30 06     BMI $FB75                          A:42 X:02 Y:DB P:nvUbdIzc SP:F9 PPU:239,194 CYC:22138\n$FB6F:B0 04     BCS $FB75                          A:42 X:02 Y:DB P:nvUbdIzc SP:F9 PPU:245,194 CYC:22140\n$FB71:C9 42     CMP #$42                           A:42 X:02 Y:DB P:nvUbdIzc SP:F9 PPU:251,194 CYC:22142\n$FB73:F0 02     BEQ $FB77                          A:42 X:02 Y:DB P:nvUbdIZC SP:F9 PPU:257,194 CYC:22144\n$FB77:60        RTS                                A:42 X:02 Y:DB P:nvUbdIZC SP:F9 PPU:266,194 CYC:22147\n$F140:AD 47 06  LDA $0647 = #$52                     A:42 X:02 Y:DB P:nvUbdIZC SP:FB PPU:284,194 CYC:22153\n$F143:C9 52     CMP #$52                           A:52 X:02 Y:DB P:25 SP:FB PPU:296,194 CYC:22157\n$F145:F0 02     BEQ $F149                          A:52 X:02 Y:DB P:nvUbdIZC SP:FB PPU:302,194 CYC:22159\n$F149:C8        INY                                A:52 X:02 Y:DB P:nvUbdIZC SP:FB PPU:311,194 CYC:22162\n$F14A:A9 37     LDA #$37                           A:52 X:02 Y:DC P:A5 SP:FB PPU:317,194 CYC:22164\n$F14C:8D 47 06  STA $0647 = #$52                     A:37 X:02 Y:DC P:25 SP:FB PPU:323,194 CYC:22166\n$F14F:20 68 FA  JSR $FA68                          A:37 X:02 Y:DC P:25 SP:FB PPU:335,194 CYC:22170\n$FA68:24 01     BIT $01 = #$FF                       A:37 X:02 Y:DC P:25 SP:F9 PPU: 12,195 CYC:22176\n$FA6A:38        SEC                                A:37 X:02 Y:DC P:E5 SP:F9 PPU: 21,195 CYC:22179\n$FA6B:A9 75     LDA #$75                           A:37 X:02 Y:DC P:E5 SP:F9 PPU: 27,195 CYC:22181\n$FA6D:60        RTS                                A:75 X:02 Y:DC P:65 SP:F9 PPU: 33,195 CYC:22183\n$F152:2F 47 06 *RLA $0647 = #$37                     A:75 X:02 Y:DC P:65 SP:FB PPU: 51,195 CYC:22189\n$F155:EA        NOP                                A:65 X:02 Y:DC P:64 SP:FB PPU: 69,195 CYC:22195\n$F156:EA        NOP                                A:65 X:02 Y:DC P:64 SP:FB PPU: 75,195 CYC:22197\n$F157:EA        NOP                                A:65 X:02 Y:DC P:64 SP:FB PPU: 81,195 CYC:22199\n$F158:EA        NOP                                A:65 X:02 Y:DC P:64 SP:FB PPU: 87,195 CYC:22201\n$F159:20 6E FA  JSR $FA6E                          A:65 X:02 Y:DC P:64 SP:FB PPU: 93,195 CYC:22203\n$FA6E:50 76     BVC $FAE6                          A:65 X:02 Y:DC P:64 SP:F9 PPU:111,195 CYC:22209\n$FA70:F0 74     BEQ $FAE6                          A:65 X:02 Y:DC P:64 SP:F9 PPU:117,195 CYC:22211\n$FA72:30 72     BMI $FAE6                          A:65 X:02 Y:DC P:64 SP:F9 PPU:123,195 CYC:22213\n$FA74:B0 70     BCS $FAE6                          A:65 X:02 Y:DC P:64 SP:F9 PPU:129,195 CYC:22215\n$FA76:C9 65     CMP #$65                           A:65 X:02 Y:DC P:64 SP:F9 PPU:135,195 CYC:22217\n$FA78:D0 6C     BNE $FAE6                          A:65 X:02 Y:DC P:67 SP:F9 PPU:141,195 CYC:22219\n$FA7A:60        RTS                                A:65 X:02 Y:DC P:67 SP:F9 PPU:147,195 CYC:22221\n$F15C:AD 47 06  LDA $0647 = #$6F                     A:65 X:02 Y:DC P:67 SP:FB PPU:165,195 CYC:22227\n$F15F:C9 6F     CMP #$6F                           A:6F X:02 Y:DC P:65 SP:FB PPU:177,195 CYC:22231\n$F161:F0 02     BEQ $F165                          A:6F X:02 Y:DC P:67 SP:FB PPU:183,195 CYC:22233\n$F165:A9 A5     LDA #$A5                           A:6F X:02 Y:DC P:67 SP:FB PPU:192,195 CYC:22236\n$F167:8D 47 06  STA $0647 = #$6F                     A:A5 X:02 Y:DC P:E5 SP:FB PPU:198,195 CYC:22238\n$F16A:A9 48     LDA #$48                           A:A5 X:02 Y:DC P:E5 SP:FB PPU:210,195 CYC:22242\n$F16C:85 45     STA $45 = #$48                       A:48 X:02 Y:DC P:65 SP:FB PPU:216,195 CYC:22244\n$F16E:A9 05     LDA #$05                           A:48 X:02 Y:DC P:65 SP:FB PPU:225,195 CYC:22247\n$F170:85 46     STA $46 = #$05                       A:05 X:02 Y:DC P:65 SP:FB PPU:231,195 CYC:22249\n$F172:A0 FF     LDY #$FF                           A:05 X:02 Y:DC P:65 SP:FB PPU:240,195 CYC:22252\n$F174:20 53 FB  JSR $FB53                          A:05 X:02 Y:FF P:E5 SP:FB PPU:246,195 CYC:22254\n$FB53:24 01     BIT $01 = #$FF                       A:05 X:02 Y:FF P:E5 SP:F9 PPU:264,195 CYC:22260\n$FB55:18        CLC                                A:05 X:02 Y:FF P:E5 SP:F9 PPU:273,195 CYC:22263\n$FB56:A9 B3     LDA #$B3                           A:05 X:02 Y:FF P:NVUbdIzc SP:F9 PPU:279,195 CYC:22265\n$FB58:60        RTS                                A:B3 X:02 Y:FF P:NVUbdIzc SP:F9 PPU:285,195 CYC:22267\n$F177:33 45    *RLA ($45),Y = #$0548 @ 0647 = A5     A:B3 X:02 Y:FF P:NVUbdIzc SP:FB PPU:303,195 CYC:22273\n$F179:EA        NOP                                A:02 X:02 Y:FF P:65 SP:FB PPU:327,195 CYC:22281\n$F17A:EA        NOP                                A:02 X:02 Y:FF P:65 SP:FB PPU:333,195 CYC:22283\n$F17B:08        PHP                                A:02 X:02 Y:FF P:65 SP:FB PPU:339,195 CYC:22285\n$F17C:48        PHA                                A:02 X:02 Y:FF P:65 SP:FA PPU:  7,196 CYC:22288\n$F17D:A0 DD     LDY #$DD                           A:02 X:02 Y:FF P:65 SP:F9 PPU: 16,196 CYC:22291\n$F17F:68        PLA                                A:02 X:02 Y:DD P:E5 SP:F9 PPU: 22,196 CYC:22293\n$F180:28        PLP                                A:02 X:02 Y:DD P:65 SP:FA PPU: 34,196 CYC:22297\n$F181:20 59 FB  JSR $FB59                          A:02 X:02 Y:DD P:65 SP:FB PPU: 46,196 CYC:22301\n$FB59:50 1A     BVC $FB75                          A:02 X:02 Y:DD P:65 SP:F9 PPU: 64,196 CYC:22307\n$FB5B:90 18     BCC $FB75                          A:02 X:02 Y:DD P:65 SP:F9 PPU: 70,196 CYC:22309\n$FB5D:30 16     BMI $FB75                          A:02 X:02 Y:DD P:65 SP:F9 PPU: 76,196 CYC:22311\n$FB5F:C9 02     CMP #$02                           A:02 X:02 Y:DD P:65 SP:F9 PPU: 82,196 CYC:22313\n$FB61:D0 12     BNE $FB75                          A:02 X:02 Y:DD P:67 SP:F9 PPU: 88,196 CYC:22315\n$FB63:60        RTS                                A:02 X:02 Y:DD P:67 SP:F9 PPU: 94,196 CYC:22317\n$F184:AD 47 06  LDA $0647 = #$4A                     A:02 X:02 Y:DD P:67 SP:FB PPU:112,196 CYC:22323\n$F187:C9 4A     CMP #$4A                           A:4A X:02 Y:DD P:65 SP:FB PPU:124,196 CYC:22327\n$F189:F0 02     BEQ $F18D                          A:4A X:02 Y:DD P:67 SP:FB PPU:130,196 CYC:22329\n$F18D:A0 FF     LDY #$FF                           A:4A X:02 Y:DD P:67 SP:FB PPU:139,196 CYC:22332\n$F18F:A9 29     LDA #$29                           A:4A X:02 Y:FF P:E5 SP:FB PPU:145,196 CYC:22334\n$F191:8D 47 06  STA $0647 = #$4A                     A:29 X:02 Y:FF P:65 SP:FB PPU:151,196 CYC:22336\n$F194:20 64 FB  JSR $FB64                          A:29 X:02 Y:FF P:65 SP:FB PPU:163,196 CYC:22340\n$FB64:B8        CLV                                A:29 X:02 Y:FF P:65 SP:F9 PPU:181,196 CYC:22346\n$FB65:18        CLC                                A:29 X:02 Y:FF P:25 SP:F9 PPU:187,196 CYC:22348\n$FB66:A9 42     LDA #$42                           A:29 X:02 Y:FF P:nvUbdIzc SP:F9 PPU:193,196 CYC:22350\n$FB68:60        RTS                                A:42 X:02 Y:FF P:nvUbdIzc SP:F9 PPU:199,196 CYC:22352\n$F197:33 45    *RLA ($45),Y = #$0548 @ 0647 = 29     A:42 X:02 Y:FF P:nvUbdIzc SP:FB PPU:217,196 CYC:22358\n$F199:EA        NOP                                A:42 X:02 Y:FF P:nvUbdIzc SP:FB PPU:241,196 CYC:22366\n$F19A:EA        NOP                                A:42 X:02 Y:FF P:nvUbdIzc SP:FB PPU:247,196 CYC:22368\n$F19B:08        PHP                                A:42 X:02 Y:FF P:nvUbdIzc SP:FB PPU:253,196 CYC:22370\n$F19C:48        PHA                                A:42 X:02 Y:FF P:nvUbdIzc SP:FA PPU:262,196 CYC:22373\n$F19D:A0 DE     LDY #$DE                           A:42 X:02 Y:FF P:nvUbdIzc SP:F9 PPU:271,196 CYC:22376\n$F19F:68        PLA                                A:42 X:02 Y:DE P:NvUbdIzc SP:F9 PPU:277,196 CYC:22378\n$F1A0:28        PLP                                A:42 X:02 Y:DE P:nvUbdIzc SP:FA PPU:289,196 CYC:22382\n$F1A1:20 69 FB  JSR $FB69                          A:42 X:02 Y:DE P:nvUbdIzc SP:FB PPU:301,196 CYC:22386\n$FB69:70 0A     BVS $FB75                          A:42 X:02 Y:DE P:nvUbdIzc SP:F9 PPU:319,196 CYC:22392\n$FB6B:F0 08     BEQ $FB75                          A:42 X:02 Y:DE P:nvUbdIzc SP:F9 PPU:325,196 CYC:22394\n$FB6D:30 06     BMI $FB75                          A:42 X:02 Y:DE P:nvUbdIzc SP:F9 PPU:331,196 CYC:22396\n$FB6F:B0 04     BCS $FB75                          A:42 X:02 Y:DE P:nvUbdIzc SP:F9 PPU:337,196 CYC:22398\n$FB71:C9 42     CMP #$42                           A:42 X:02 Y:DE P:nvUbdIzc SP:F9 PPU:  2,197 CYC:22400\n$FB73:F0 02     BEQ $FB77                          A:42 X:02 Y:DE P:nvUbdIZC SP:F9 PPU:  8,197 CYC:22402\n$FB77:60        RTS                                A:42 X:02 Y:DE P:nvUbdIZC SP:F9 PPU: 17,197 CYC:22405\n$F1A4:AD 47 06  LDA $0647 = #$52                     A:42 X:02 Y:DE P:nvUbdIZC SP:FB PPU: 35,197 CYC:22411\n$F1A7:C9 52     CMP #$52                           A:52 X:02 Y:DE P:25 SP:FB PPU: 47,197 CYC:22415\n$F1A9:F0 02     BEQ $F1AD                          A:52 X:02 Y:DE P:nvUbdIZC SP:FB PPU: 53,197 CYC:22417\n$F1AD:A0 FF     LDY #$FF                           A:52 X:02 Y:DE P:nvUbdIZC SP:FB PPU: 62,197 CYC:22420\n$F1AF:A9 37     LDA #$37                           A:52 X:02 Y:FF P:A5 SP:FB PPU: 68,197 CYC:22422\n$F1B1:8D 47 06  STA $0647 = #$52                     A:37 X:02 Y:FF P:25 SP:FB PPU: 74,197 CYC:22424\n$F1B4:20 68 FA  JSR $FA68                          A:37 X:02 Y:FF P:25 SP:FB PPU: 86,197 CYC:22428\n$FA68:24 01     BIT $01 = #$FF                       A:37 X:02 Y:FF P:25 SP:F9 PPU:104,197 CYC:22434\n$FA6A:38        SEC                                A:37 X:02 Y:FF P:E5 SP:F9 PPU:113,197 CYC:22437\n$FA6B:A9 75     LDA #$75                           A:37 X:02 Y:FF P:E5 SP:F9 PPU:119,197 CYC:22439\n$FA6D:60        RTS                                A:75 X:02 Y:FF P:65 SP:F9 PPU:125,197 CYC:22441\n$F1B7:33 45    *RLA ($45),Y = #$0548 @ 0647 = 37     A:75 X:02 Y:FF P:65 SP:FB PPU:143,197 CYC:22447\n$F1B9:EA        NOP                                A:65 X:02 Y:FF P:64 SP:FB PPU:167,197 CYC:22455\n$F1BA:EA        NOP                                A:65 X:02 Y:FF P:64 SP:FB PPU:173,197 CYC:22457\n$F1BB:08        PHP                                A:65 X:02 Y:FF P:64 SP:FB PPU:179,197 CYC:22459\n$F1BC:48        PHA                                A:65 X:02 Y:FF P:64 SP:FA PPU:188,197 CYC:22462\n$F1BD:A0 DF     LDY #$DF                           A:65 X:02 Y:FF P:64 SP:F9 PPU:197,197 CYC:22465\n$F1BF:68        PLA                                A:65 X:02 Y:DF P:NVUbdIzc SP:F9 PPU:203,197 CYC:22467\n$F1C0:28        PLP                                A:65 X:02 Y:DF P:64 SP:FA PPU:215,197 CYC:22471\n$F1C1:20 6E FA  JSR $FA6E                          A:65 X:02 Y:DF P:64 SP:FB PPU:227,197 CYC:22475\n$FA6E:50 76     BVC $FAE6                          A:65 X:02 Y:DF P:64 SP:F9 PPU:245,197 CYC:22481\n$FA70:F0 74     BEQ $FAE6                          A:65 X:02 Y:DF P:64 SP:F9 PPU:251,197 CYC:22483\n$FA72:30 72     BMI $FAE6                          A:65 X:02 Y:DF P:64 SP:F9 PPU:257,197 CYC:22485\n$FA74:B0 70     BCS $FAE6                          A:65 X:02 Y:DF P:64 SP:F9 PPU:263,197 CYC:22487\n$FA76:C9 65     CMP #$65                           A:65 X:02 Y:DF P:64 SP:F9 PPU:269,197 CYC:22489\n$FA78:D0 6C     BNE $FAE6                          A:65 X:02 Y:DF P:67 SP:F9 PPU:275,197 CYC:22491\n$FA7A:60        RTS                                A:65 X:02 Y:DF P:67 SP:F9 PPU:281,197 CYC:22493\n$F1C4:AD 47 06  LDA $0647 = #$6F                     A:65 X:02 Y:DF P:67 SP:FB PPU:299,197 CYC:22499\n$F1C7:C9 6F     CMP #$6F                           A:6F X:02 Y:DF P:65 SP:FB PPU:311,197 CYC:22503\n$F1C9:F0 02     BEQ $F1CD                          A:6F X:02 Y:DF P:67 SP:FB PPU:317,197 CYC:22505\n$F1CD:A0 E0     LDY #$E0                           A:6F X:02 Y:DF P:67 SP:FB PPU:326,197 CYC:22508\n$F1CF:A2 FF     LDX #$FF                           A:6F X:02 Y:E0 P:E5 SP:FB PPU:332,197 CYC:22510\n$F1D1:A9 A5     LDA #$A5                           A:6F X:FF Y:E0 P:E5 SP:FB PPU:338,197 CYC:22512\n$F1D3:85 47     STA $47 = #$6F                       A:A5 X:FF Y:E0 P:E5 SP:FB PPU:  3,198 CYC:22514\n$F1D5:20 53 FB  JSR $FB53                          A:A5 X:FF Y:E0 P:E5 SP:FB PPU: 12,198 CYC:22517\n$FB53:24 01     BIT $01 = #$FF                       A:A5 X:FF Y:E0 P:E5 SP:F9 PPU: 30,198 CYC:22523\n$FB55:18        CLC                                A:A5 X:FF Y:E0 P:E5 SP:F9 PPU: 39,198 CYC:22526\n$FB56:A9 B3     LDA #$B3                           A:A5 X:FF Y:E0 P:NVUbdIzc SP:F9 PPU: 45,198 CYC:22528\n$FB58:60        RTS                                A:B3 X:FF Y:E0 P:NVUbdIzc SP:F9 PPU: 51,198 CYC:22530\n$F1D8:37 48    *RLA $48,X @ 47 = #$A5                A:B3 X:FF Y:E0 P:NVUbdIzc SP:FB PPU: 69,198 CYC:22536\n$F1DA:EA        NOP                                A:02 X:FF Y:E0 P:65 SP:FB PPU: 87,198 CYC:22542\n$F1DB:EA        NOP                                A:02 X:FF Y:E0 P:65 SP:FB PPU: 93,198 CYC:22544\n$F1DC:EA        NOP                                A:02 X:FF Y:E0 P:65 SP:FB PPU: 99,198 CYC:22546\n$F1DD:EA        NOP                                A:02 X:FF Y:E0 P:65 SP:FB PPU:105,198 CYC:22548\n$F1DE:20 59 FB  JSR $FB59                          A:02 X:FF Y:E0 P:65 SP:FB PPU:111,198 CYC:22550\n$FB59:50 1A     BVC $FB75                          A:02 X:FF Y:E0 P:65 SP:F9 PPU:129,198 CYC:22556\n$FB5B:90 18     BCC $FB75                          A:02 X:FF Y:E0 P:65 SP:F9 PPU:135,198 CYC:22558\n$FB5D:30 16     BMI $FB75                          A:02 X:FF Y:E0 P:65 SP:F9 PPU:141,198 CYC:22560\n$FB5F:C9 02     CMP #$02                           A:02 X:FF Y:E0 P:65 SP:F9 PPU:147,198 CYC:22562\n$FB61:D0 12     BNE $FB75                          A:02 X:FF Y:E0 P:67 SP:F9 PPU:153,198 CYC:22564\n$FB63:60        RTS                                A:02 X:FF Y:E0 P:67 SP:F9 PPU:159,198 CYC:22566\n$F1E1:A5 47     LDA $47 = #$4A                       A:02 X:FF Y:E0 P:67 SP:FB PPU:177,198 CYC:22572\n$F1E3:C9 4A     CMP #$4A                           A:4A X:FF Y:E0 P:65 SP:FB PPU:186,198 CYC:22575\n$F1E5:F0 02     BEQ $F1E9                          A:4A X:FF Y:E0 P:67 SP:FB PPU:192,198 CYC:22577\n$F1E9:C8        INY                                A:4A X:FF Y:E0 P:67 SP:FB PPU:201,198 CYC:22580\n$F1EA:A9 29     LDA #$29                           A:4A X:FF Y:E1 P:E5 SP:FB PPU:207,198 CYC:22582\n$F1EC:85 47     STA $47 = #$4A                       A:29 X:FF Y:E1 P:65 SP:FB PPU:213,198 CYC:22584\n$F1EE:20 64 FB  JSR $FB64                          A:29 X:FF Y:E1 P:65 SP:FB PPU:222,198 CYC:22587\n$FB64:B8        CLV                                A:29 X:FF Y:E1 P:65 SP:F9 PPU:240,198 CYC:22593\n$FB65:18        CLC                                A:29 X:FF Y:E1 P:25 SP:F9 PPU:246,198 CYC:22595\n$FB66:A9 42     LDA #$42                           A:29 X:FF Y:E1 P:nvUbdIzc SP:F9 PPU:252,198 CYC:22597\n$FB68:60        RTS                                A:42 X:FF Y:E1 P:nvUbdIzc SP:F9 PPU:258,198 CYC:22599\n$F1F1:37 48    *RLA $48,X @ 47 = #$29                A:42 X:FF Y:E1 P:nvUbdIzc SP:FB PPU:276,198 CYC:22605\n$F1F3:EA        NOP                                A:42 X:FF Y:E1 P:nvUbdIzc SP:FB PPU:294,198 CYC:22611\n$F1F4:EA        NOP                                A:42 X:FF Y:E1 P:nvUbdIzc SP:FB PPU:300,198 CYC:22613\n$F1F5:EA        NOP                                A:42 X:FF Y:E1 P:nvUbdIzc SP:FB PPU:306,198 CYC:22615\n$F1F6:EA        NOP                                A:42 X:FF Y:E1 P:nvUbdIzc SP:FB PPU:312,198 CYC:22617\n$F1F7:20 69 FB  JSR $FB69                          A:42 X:FF Y:E1 P:nvUbdIzc SP:FB PPU:318,198 CYC:22619\n$FB69:70 0A     BVS $FB75                          A:42 X:FF Y:E1 P:nvUbdIzc SP:F9 PPU:336,198 CYC:22625\n$FB6B:F0 08     BEQ $FB75                          A:42 X:FF Y:E1 P:nvUbdIzc SP:F9 PPU:  1,199 CYC:22627\n$FB6D:30 06     BMI $FB75                          A:42 X:FF Y:E1 P:nvUbdIzc SP:F9 PPU:  7,199 CYC:22629\n$FB6F:B0 04     BCS $FB75                          A:42 X:FF Y:E1 P:nvUbdIzc SP:F9 PPU: 13,199 CYC:22631\n$FB71:C9 42     CMP #$42                           A:42 X:FF Y:E1 P:nvUbdIzc SP:F9 PPU: 19,199 CYC:22633\n$FB73:F0 02     BEQ $FB77                          A:42 X:FF Y:E1 P:nvUbdIZC SP:F9 PPU: 25,199 CYC:22635\n$FB77:60        RTS                                A:42 X:FF Y:E1 P:nvUbdIZC SP:F9 PPU: 34,199 CYC:22638\n$F1FA:A5 47     LDA $47 = #$52                       A:42 X:FF Y:E1 P:nvUbdIZC SP:FB PPU: 52,199 CYC:22644\n$F1FC:C9 52     CMP #$52                           A:52 X:FF Y:E1 P:25 SP:FB PPU: 61,199 CYC:22647\n$F1FE:F0 02     BEQ $F202                          A:52 X:FF Y:E1 P:nvUbdIZC SP:FB PPU: 67,199 CYC:22649\n$F202:C8        INY                                A:52 X:FF Y:E1 P:nvUbdIZC SP:FB PPU: 76,199 CYC:22652\n$F203:A9 37     LDA #$37                           A:52 X:FF Y:E2 P:A5 SP:FB PPU: 82,199 CYC:22654\n$F205:85 47     STA $47 = #$52                       A:37 X:FF Y:E2 P:25 SP:FB PPU: 88,199 CYC:22656\n$F207:20 68 FA  JSR $FA68                          A:37 X:FF Y:E2 P:25 SP:FB PPU: 97,199 CYC:22659\n$FA68:24 01     BIT $01 = #$FF                       A:37 X:FF Y:E2 P:25 SP:F9 PPU:115,199 CYC:22665\n$FA6A:38        SEC                                A:37 X:FF Y:E2 P:E5 SP:F9 PPU:124,199 CYC:22668\n$FA6B:A9 75     LDA #$75                           A:37 X:FF Y:E2 P:E5 SP:F9 PPU:130,199 CYC:22670\n$FA6D:60        RTS                                A:75 X:FF Y:E2 P:65 SP:F9 PPU:136,199 CYC:22672\n$F20A:37 48    *RLA $48,X @ 47 = #$37                A:75 X:FF Y:E2 P:65 SP:FB PPU:154,199 CYC:22678\n$F20C:EA        NOP                                A:65 X:FF Y:E2 P:64 SP:FB PPU:172,199 CYC:22684\n$F20D:EA        NOP                                A:65 X:FF Y:E2 P:64 SP:FB PPU:178,199 CYC:22686\n$F20E:EA        NOP                                A:65 X:FF Y:E2 P:64 SP:FB PPU:184,199 CYC:22688\n$F20F:EA        NOP                                A:65 X:FF Y:E2 P:64 SP:FB PPU:190,199 CYC:22690\n$F210:20 6E FA  JSR $FA6E                          A:65 X:FF Y:E2 P:64 SP:FB PPU:196,199 CYC:22692\n$FA6E:50 76     BVC $FAE6                          A:65 X:FF Y:E2 P:64 SP:F9 PPU:214,199 CYC:22698\n$FA70:F0 74     BEQ $FAE6                          A:65 X:FF Y:E2 P:64 SP:F9 PPU:220,199 CYC:22700\n$FA72:30 72     BMI $FAE6                          A:65 X:FF Y:E2 P:64 SP:F9 PPU:226,199 CYC:22702\n$FA74:B0 70     BCS $FAE6                          A:65 X:FF Y:E2 P:64 SP:F9 PPU:232,199 CYC:22704\n$FA76:C9 65     CMP #$65                           A:65 X:FF Y:E2 P:64 SP:F9 PPU:238,199 CYC:22706\n$FA78:D0 6C     BNE $FAE6                          A:65 X:FF Y:E2 P:67 SP:F9 PPU:244,199 CYC:22708\n$FA7A:60        RTS                                A:65 X:FF Y:E2 P:67 SP:F9 PPU:250,199 CYC:22710\n$F213:A5 47     LDA $47 = #$6F                       A:65 X:FF Y:E2 P:67 SP:FB PPU:268,199 CYC:22716\n$F215:C9 6F     CMP #$6F                           A:6F X:FF Y:E2 P:65 SP:FB PPU:277,199 CYC:22719\n$F217:F0 02     BEQ $F21B                          A:6F X:FF Y:E2 P:67 SP:FB PPU:283,199 CYC:22721\n$F21B:A9 A5     LDA #$A5                           A:6F X:FF Y:E2 P:67 SP:FB PPU:292,199 CYC:22724\n$F21D:8D 47 06  STA $0647 = #$6F                     A:A5 X:FF Y:E2 P:E5 SP:FB PPU:298,199 CYC:22726\n$F220:A0 FF     LDY #$FF                           A:A5 X:FF Y:E2 P:E5 SP:FB PPU:310,199 CYC:22730\n$F222:20 53 FB  JSR $FB53                          A:A5 X:FF Y:FF P:E5 SP:FB PPU:316,199 CYC:22732\n$FB53:24 01     BIT $01 = #$FF                       A:A5 X:FF Y:FF P:E5 SP:F9 PPU:334,199 CYC:22738\n$FB55:18        CLC                                A:A5 X:FF Y:FF P:E5 SP:F9 PPU:  2,200 CYC:22741\n$FB56:A9 B3     LDA #$B3                           A:A5 X:FF Y:FF P:NVUbdIzc SP:F9 PPU:  8,200 CYC:22743\n$FB58:60        RTS                                A:B3 X:FF Y:FF P:NVUbdIzc SP:F9 PPU: 14,200 CYC:22745\n$F225:3B 48 05 *RLA $0548,Y @ 0647 = #$A5            A:B3 X:FF Y:FF P:NVUbdIzc SP:FB PPU: 32,200 CYC:22751\n$F228:EA        NOP                                A:02 X:FF Y:FF P:65 SP:FB PPU: 53,200 CYC:22758\n$F229:EA        NOP                                A:02 X:FF Y:FF P:65 SP:FB PPU: 59,200 CYC:22760\n$F22A:08        PHP                                A:02 X:FF Y:FF P:65 SP:FB PPU: 65,200 CYC:22762\n$F22B:48        PHA                                A:02 X:FF Y:FF P:65 SP:FA PPU: 74,200 CYC:22765\n$F22C:A0 E3     LDY #$E3                           A:02 X:FF Y:FF P:65 SP:F9 PPU: 83,200 CYC:22768\n$F22E:68        PLA                                A:02 X:FF Y:E3 P:E5 SP:F9 PPU: 89,200 CYC:22770\n$F22F:28        PLP                                A:02 X:FF Y:E3 P:65 SP:FA PPU:101,200 CYC:22774\n$F230:20 59 FB  JSR $FB59                          A:02 X:FF Y:E3 P:65 SP:FB PPU:113,200 CYC:22778\n$FB59:50 1A     BVC $FB75                          A:02 X:FF Y:E3 P:65 SP:F9 PPU:131,200 CYC:22784\n$FB5B:90 18     BCC $FB75                          A:02 X:FF Y:E3 P:65 SP:F9 PPU:137,200 CYC:22786\n$FB5D:30 16     BMI $FB75                          A:02 X:FF Y:E3 P:65 SP:F9 PPU:143,200 CYC:22788\n$FB5F:C9 02     CMP #$02                           A:02 X:FF Y:E3 P:65 SP:F9 PPU:149,200 CYC:22790\n$FB61:D0 12     BNE $FB75                          A:02 X:FF Y:E3 P:67 SP:F9 PPU:155,200 CYC:22792\n$FB63:60        RTS                                A:02 X:FF Y:E3 P:67 SP:F9 PPU:161,200 CYC:22794\n$F233:AD 47 06  LDA $0647 = #$4A                     A:02 X:FF Y:E3 P:67 SP:FB PPU:179,200 CYC:22800\n$F236:C9 4A     CMP #$4A                           A:4A X:FF Y:E3 P:65 SP:FB PPU:191,200 CYC:22804\n$F238:F0 02     BEQ $F23C                          A:4A X:FF Y:E3 P:67 SP:FB PPU:197,200 CYC:22806\n$F23C:A0 FF     LDY #$FF                           A:4A X:FF Y:E3 P:67 SP:FB PPU:206,200 CYC:22809\n$F23E:A9 29     LDA #$29                           A:4A X:FF Y:FF P:E5 SP:FB PPU:212,200 CYC:22811\n$F240:8D 47 06  STA $0647 = #$4A                     A:29 X:FF Y:FF P:65 SP:FB PPU:218,200 CYC:22813\n$F243:20 64 FB  JSR $FB64                          A:29 X:FF Y:FF P:65 SP:FB PPU:230,200 CYC:22817\n$FB64:B8        CLV                                A:29 X:FF Y:FF P:65 SP:F9 PPU:248,200 CYC:22823\n$FB65:18        CLC                                A:29 X:FF Y:FF P:25 SP:F9 PPU:254,200 CYC:22825\n$FB66:A9 42     LDA #$42                           A:29 X:FF Y:FF P:nvUbdIzc SP:F9 PPU:260,200 CYC:22827\n$FB68:60        RTS                                A:42 X:FF Y:FF P:nvUbdIzc SP:F9 PPU:266,200 CYC:22829\n$F246:3B 48 05 *RLA $0548,Y @ 0647 = #$29            A:42 X:FF Y:FF P:nvUbdIzc SP:FB PPU:284,200 CYC:22835\n$F249:EA        NOP                                A:42 X:FF Y:FF P:nvUbdIzc SP:FB PPU:305,200 CYC:22842\n$F24A:EA        NOP                                A:42 X:FF Y:FF P:nvUbdIzc SP:FB PPU:311,200 CYC:22844\n$F24B:08        PHP                                A:42 X:FF Y:FF P:nvUbdIzc SP:FB PPU:317,200 CYC:22846\n$F24C:48        PHA                                A:42 X:FF Y:FF P:nvUbdIzc SP:FA PPU:326,200 CYC:22849\n$F24D:A0 E4     LDY #$E4                           A:42 X:FF Y:FF P:nvUbdIzc SP:F9 PPU:335,200 CYC:22852\n$F24F:68        PLA                                A:42 X:FF Y:E4 P:NvUbdIzc SP:F9 PPU:  0,201 CYC:22854\n$F250:28        PLP                                A:42 X:FF Y:E4 P:nvUbdIzc SP:FA PPU: 12,201 CYC:22858\n$F251:20 69 FB  JSR $FB69                          A:42 X:FF Y:E4 P:nvUbdIzc SP:FB PPU: 24,201 CYC:22862\n$FB69:70 0A     BVS $FB75                          A:42 X:FF Y:E4 P:nvUbdIzc SP:F9 PPU: 42,201 CYC:22868\n$FB6B:F0 08     BEQ $FB75                          A:42 X:FF Y:E4 P:nvUbdIzc SP:F9 PPU: 48,201 CYC:22870\n$FB6D:30 06     BMI $FB75                          A:42 X:FF Y:E4 P:nvUbdIzc SP:F9 PPU: 54,201 CYC:22872\n$FB6F:B0 04     BCS $FB75                          A:42 X:FF Y:E4 P:nvUbdIzc SP:F9 PPU: 60,201 CYC:22874\n$FB71:C9 42     CMP #$42                           A:42 X:FF Y:E4 P:nvUbdIzc SP:F9 PPU: 66,201 CYC:22876\n$FB73:F0 02     BEQ $FB77                          A:42 X:FF Y:E4 P:nvUbdIZC SP:F9 PPU: 72,201 CYC:22878\n$FB77:60        RTS                                A:42 X:FF Y:E4 P:nvUbdIZC SP:F9 PPU: 81,201 CYC:22881\n$F254:AD 47 06  LDA $0647 = #$52                     A:42 X:FF Y:E4 P:nvUbdIZC SP:FB PPU: 99,201 CYC:22887\n$F257:C9 52     CMP #$52                           A:52 X:FF Y:E4 P:25 SP:FB PPU:111,201 CYC:22891\n$F259:F0 02     BEQ $F25D                          A:52 X:FF Y:E4 P:nvUbdIZC SP:FB PPU:117,201 CYC:22893\n$F25D:A0 FF     LDY #$FF                           A:52 X:FF Y:E4 P:nvUbdIZC SP:FB PPU:126,201 CYC:22896\n$F25F:A9 37     LDA #$37                           A:52 X:FF Y:FF P:A5 SP:FB PPU:132,201 CYC:22898\n$F261:8D 47 06  STA $0647 = #$52                     A:37 X:FF Y:FF P:25 SP:FB PPU:138,201 CYC:22900\n$F264:20 68 FA  JSR $FA68                          A:37 X:FF Y:FF P:25 SP:FB PPU:150,201 CYC:22904\n$FA68:24 01     BIT $01 = #$FF                       A:37 X:FF Y:FF P:25 SP:F9 PPU:168,201 CYC:22910\n$FA6A:38        SEC                                A:37 X:FF Y:FF P:E5 SP:F9 PPU:177,201 CYC:22913\n$FA6B:A9 75     LDA #$75                           A:37 X:FF Y:FF P:E5 SP:F9 PPU:183,201 CYC:22915\n$FA6D:60        RTS                                A:75 X:FF Y:FF P:65 SP:F9 PPU:189,201 CYC:22917\n$F267:3B 48 05 *RLA $0548,Y @ 0647 = #$37            A:75 X:FF Y:FF P:65 SP:FB PPU:207,201 CYC:22923\n$F26A:EA        NOP                                A:65 X:FF Y:FF P:64 SP:FB PPU:228,201 CYC:22930\n$F26B:EA        NOP                                A:65 X:FF Y:FF P:64 SP:FB PPU:234,201 CYC:22932\n$F26C:08        PHP                                A:65 X:FF Y:FF P:64 SP:FB PPU:240,201 CYC:22934\n$F26D:48        PHA                                A:65 X:FF Y:FF P:64 SP:FA PPU:249,201 CYC:22937\n$F26E:A0 E5     LDY #$E5                           A:65 X:FF Y:FF P:64 SP:F9 PPU:258,201 CYC:22940\n$F270:68        PLA                                A:65 X:FF Y:E5 P:NVUbdIzc SP:F9 PPU:264,201 CYC:22942\n$F271:28        PLP                                A:65 X:FF Y:E5 P:64 SP:FA PPU:276,201 CYC:22946\n$F272:20 6E FA  JSR $FA6E                          A:65 X:FF Y:E5 P:64 SP:FB PPU:288,201 CYC:22950\n$FA6E:50 76     BVC $FAE6                          A:65 X:FF Y:E5 P:64 SP:F9 PPU:306,201 CYC:22956\n$FA70:F0 74     BEQ $FAE6                          A:65 X:FF Y:E5 P:64 SP:F9 PPU:312,201 CYC:22958\n$FA72:30 72     BMI $FAE6                          A:65 X:FF Y:E5 P:64 SP:F9 PPU:318,201 CYC:22960\n$FA74:B0 70     BCS $FAE6                          A:65 X:FF Y:E5 P:64 SP:F9 PPU:324,201 CYC:22962\n$FA76:C9 65     CMP #$65                           A:65 X:FF Y:E5 P:64 SP:F9 PPU:330,201 CYC:22964\n$FA78:D0 6C     BNE $FAE6                          A:65 X:FF Y:E5 P:67 SP:F9 PPU:336,201 CYC:22966\n$FA7A:60        RTS                                A:65 X:FF Y:E5 P:67 SP:F9 PPU:  1,202 CYC:22968\n$F275:AD 47 06  LDA $0647 = #$6F                     A:65 X:FF Y:E5 P:67 SP:FB PPU: 19,202 CYC:22974\n$F278:C9 6F     CMP #$6F                           A:6F X:FF Y:E5 P:65 SP:FB PPU: 31,202 CYC:22978\n$F27A:F0 02     BEQ $F27E                          A:6F X:FF Y:E5 P:67 SP:FB PPU: 37,202 CYC:22980\n$F27E:A0 E6     LDY #$E6                           A:6F X:FF Y:E5 P:67 SP:FB PPU: 46,202 CYC:22983\n$F280:A2 FF     LDX #$FF                           A:6F X:FF Y:E6 P:E5 SP:FB PPU: 52,202 CYC:22985\n$F282:A9 A5     LDA #$A5                           A:6F X:FF Y:E6 P:E5 SP:FB PPU: 58,202 CYC:22987\n$F284:8D 47 06  STA $0647 = #$6F                     A:A5 X:FF Y:E6 P:E5 SP:FB PPU: 64,202 CYC:22989\n$F287:20 53 FB  JSR $FB53                          A:A5 X:FF Y:E6 P:E5 SP:FB PPU: 76,202 CYC:22993\n$FB53:24 01     BIT $01 = #$FF                       A:A5 X:FF Y:E6 P:E5 SP:F9 PPU: 94,202 CYC:22999\n$FB55:18        CLC                                A:A5 X:FF Y:E6 P:E5 SP:F9 PPU:103,202 CYC:23002\n$FB56:A9 B3     LDA #$B3                           A:A5 X:FF Y:E6 P:NVUbdIzc SP:F9 PPU:109,202 CYC:23004\n$FB58:60        RTS                                A:B3 X:FF Y:E6 P:NVUbdIzc SP:F9 PPU:115,202 CYC:23006\n$F28A:3F 48 05 *RLA $0548,X @ 0647 = #$A5            A:B3 X:FF Y:E6 P:NVUbdIzc SP:FB PPU:133,202 CYC:23012\n$F28D:EA        NOP                                A:02 X:FF Y:E6 P:65 SP:FB PPU:154,202 CYC:23019\n$F28E:EA        NOP                                A:02 X:FF Y:E6 P:65 SP:FB PPU:160,202 CYC:23021\n$F28F:EA        NOP                                A:02 X:FF Y:E6 P:65 SP:FB PPU:166,202 CYC:23023\n$F290:EA        NOP                                A:02 X:FF Y:E6 P:65 SP:FB PPU:172,202 CYC:23025\n$F291:20 59 FB  JSR $FB59                          A:02 X:FF Y:E6 P:65 SP:FB PPU:178,202 CYC:23027\n$FB59:50 1A     BVC $FB75                          A:02 X:FF Y:E6 P:65 SP:F9 PPU:196,202 CYC:23033\n$FB5B:90 18     BCC $FB75                          A:02 X:FF Y:E6 P:65 SP:F9 PPU:202,202 CYC:23035\n$FB5D:30 16     BMI $FB75                          A:02 X:FF Y:E6 P:65 SP:F9 PPU:208,202 CYC:23037\n$FB5F:C9 02     CMP #$02                           A:02 X:FF Y:E6 P:65 SP:F9 PPU:214,202 CYC:23039\n$FB61:D0 12     BNE $FB75                          A:02 X:FF Y:E6 P:67 SP:F9 PPU:220,202 CYC:23041\n$FB63:60        RTS                                A:02 X:FF Y:E6 P:67 SP:F9 PPU:226,202 CYC:23043\n$F294:AD 47 06  LDA $0647 = #$4A                     A:02 X:FF Y:E6 P:67 SP:FB PPU:244,202 CYC:23049\n$F297:C9 4A     CMP #$4A                           A:4A X:FF Y:E6 P:65 SP:FB PPU:256,202 CYC:23053\n$F299:F0 02     BEQ $F29D                          A:4A X:FF Y:E6 P:67 SP:FB PPU:262,202 CYC:23055\n$F29D:C8        INY                                A:4A X:FF Y:E6 P:67 SP:FB PPU:271,202 CYC:23058\n$F29E:A9 29     LDA #$29                           A:4A X:FF Y:E7 P:E5 SP:FB PPU:277,202 CYC:23060\n$F2A0:8D 47 06  STA $0647 = #$4A                     A:29 X:FF Y:E7 P:65 SP:FB PPU:283,202 CYC:23062\n$F2A3:20 64 FB  JSR $FB64                          A:29 X:FF Y:E7 P:65 SP:FB PPU:295,202 CYC:23066\n$FB64:B8        CLV                                A:29 X:FF Y:E7 P:65 SP:F9 PPU:313,202 CYC:23072\n$FB65:18        CLC                                A:29 X:FF Y:E7 P:25 SP:F9 PPU:319,202 CYC:23074\n$FB66:A9 42     LDA #$42                           A:29 X:FF Y:E7 P:nvUbdIzc SP:F9 PPU:325,202 CYC:23076\n$FB68:60        RTS                                A:42 X:FF Y:E7 P:nvUbdIzc SP:F9 PPU:331,202 CYC:23078\n$F2A6:3F 48 05 *RLA $0548,X @ 0647 = #$29            A:42 X:FF Y:E7 P:nvUbdIzc SP:FB PPU:  8,203 CYC:23084\n$F2A9:EA        NOP                                A:42 X:FF Y:E7 P:nvUbdIzc SP:FB PPU: 29,203 CYC:23091\n$F2AA:EA        NOP                                A:42 X:FF Y:E7 P:nvUbdIzc SP:FB PPU: 35,203 CYC:23093\n$F2AB:EA        NOP                                A:42 X:FF Y:E7 P:nvUbdIzc SP:FB PPU: 41,203 CYC:23095\n$F2AC:EA        NOP                                A:42 X:FF Y:E7 P:nvUbdIzc SP:FB PPU: 47,203 CYC:23097\n$F2AD:20 69 FB  JSR $FB69                          A:42 X:FF Y:E7 P:nvUbdIzc SP:FB PPU: 53,203 CYC:23099\n$FB69:70 0A     BVS $FB75                          A:42 X:FF Y:E7 P:nvUbdIzc SP:F9 PPU: 71,203 CYC:23105\n$FB6B:F0 08     BEQ $FB75                          A:42 X:FF Y:E7 P:nvUbdIzc SP:F9 PPU: 77,203 CYC:23107\n$FB6D:30 06     BMI $FB75                          A:42 X:FF Y:E7 P:nvUbdIzc SP:F9 PPU: 83,203 CYC:23109\n$FB6F:B0 04     BCS $FB75                          A:42 X:FF Y:E7 P:nvUbdIzc SP:F9 PPU: 89,203 CYC:23111\n$FB71:C9 42     CMP #$42                           A:42 X:FF Y:E7 P:nvUbdIzc SP:F9 PPU: 95,203 CYC:23113\n$FB73:F0 02     BEQ $FB77                          A:42 X:FF Y:E7 P:nvUbdIZC SP:F9 PPU:101,203 CYC:23115\n$FB77:60        RTS                                A:42 X:FF Y:E7 P:nvUbdIZC SP:F9 PPU:110,203 CYC:23118\n$F2B0:AD 47 06  LDA $0647 = #$52                     A:42 X:FF Y:E7 P:nvUbdIZC SP:FB PPU:128,203 CYC:23124\n$F2B3:C9 52     CMP #$52                           A:52 X:FF Y:E7 P:25 SP:FB PPU:140,203 CYC:23128\n$F2B5:F0 02     BEQ $F2B9                          A:52 X:FF Y:E7 P:nvUbdIZC SP:FB PPU:146,203 CYC:23130\n$F2B9:C8        INY                                A:52 X:FF Y:E7 P:nvUbdIZC SP:FB PPU:155,203 CYC:23133\n$F2BA:A9 37     LDA #$37                           A:52 X:FF Y:E8 P:A5 SP:FB PPU:161,203 CYC:23135\n$F2BC:8D 47 06  STA $0647 = #$52                     A:37 X:FF Y:E8 P:25 SP:FB PPU:167,203 CYC:23137\n$F2BF:20 68 FA  JSR $FA68                          A:37 X:FF Y:E8 P:25 SP:FB PPU:179,203 CYC:23141\n$FA68:24 01     BIT $01 = #$FF                       A:37 X:FF Y:E8 P:25 SP:F9 PPU:197,203 CYC:23147\n$FA6A:38        SEC                                A:37 X:FF Y:E8 P:E5 SP:F9 PPU:206,203 CYC:23150\n$FA6B:A9 75     LDA #$75                           A:37 X:FF Y:E8 P:E5 SP:F9 PPU:212,203 CYC:23152\n$FA6D:60        RTS                                A:75 X:FF Y:E8 P:65 SP:F9 PPU:218,203 CYC:23154\n$F2C2:3F 48 05 *RLA $0548,X @ 0647 = #$37            A:75 X:FF Y:E8 P:65 SP:FB PPU:236,203 CYC:23160\n$F2C5:EA        NOP                                A:65 X:FF Y:E8 P:64 SP:FB PPU:257,203 CYC:23167\n$F2C6:EA        NOP                                A:65 X:FF Y:E8 P:64 SP:FB PPU:263,203 CYC:23169\n$F2C7:EA        NOP                                A:65 X:FF Y:E8 P:64 SP:FB PPU:269,203 CYC:23171\n$F2C8:EA        NOP                                A:65 X:FF Y:E8 P:64 SP:FB PPU:275,203 CYC:23173\n$F2C9:20 6E FA  JSR $FA6E                          A:65 X:FF Y:E8 P:64 SP:FB PPU:281,203 CYC:23175\n$FA6E:50 76     BVC $FAE6                          A:65 X:FF Y:E8 P:64 SP:F9 PPU:299,203 CYC:23181\n$FA70:F0 74     BEQ $FAE6                          A:65 X:FF Y:E8 P:64 SP:F9 PPU:305,203 CYC:23183\n$FA72:30 72     BMI $FAE6                          A:65 X:FF Y:E8 P:64 SP:F9 PPU:311,203 CYC:23185\n$FA74:B0 70     BCS $FAE6                          A:65 X:FF Y:E8 P:64 SP:F9 PPU:317,203 CYC:23187\n$FA76:C9 65     CMP #$65                           A:65 X:FF Y:E8 P:64 SP:F9 PPU:323,203 CYC:23189\n$FA78:D0 6C     BNE $FAE6                          A:65 X:FF Y:E8 P:67 SP:F9 PPU:329,203 CYC:23191\n$FA7A:60        RTS                                A:65 X:FF Y:E8 P:67 SP:F9 PPU:335,203 CYC:23193\n$F2CC:AD 47 06  LDA $0647 = #$6F                     A:65 X:FF Y:E8 P:67 SP:FB PPU: 12,204 CYC:23199\n$F2CF:C9 6F     CMP #$6F                           A:6F X:FF Y:E8 P:65 SP:FB PPU: 24,204 CYC:23203\n$F2D1:F0 02     BEQ $F2D5                          A:6F X:FF Y:E8 P:67 SP:FB PPU: 30,204 CYC:23205\n$F2D5:60        RTS                                A:6F X:FF Y:E8 P:67 SP:FB PPU: 39,204 CYC:23208\n$C647:20 D6 F2  JSR $F2D6                          A:6F X:FF Y:E8 P:67 SP:FD PPU: 57,204 CYC:23214\n$F2D6:A9 FF     LDA #$FF                           A:6F X:FF Y:E8 P:67 SP:FB PPU: 75,204 CYC:23220\n$F2D8:85 01     STA $01 = #$FF                       A:FF X:FF Y:E8 P:E5 SP:FB PPU: 81,204 CYC:23222\n$F2DA:A0 E9     LDY #$E9                           A:FF X:FF Y:E8 P:E5 SP:FB PPU: 90,204 CYC:23225\n$F2DC:A2 02     LDX #$02                           A:FF X:FF Y:E9 P:E5 SP:FB PPU: 96,204 CYC:23227\n$F2DE:A9 47     LDA #$47                           A:FF X:02 Y:E9 P:65 SP:FB PPU:102,204 CYC:23229\n$F2E0:85 47     STA $47 = #$6F                       A:47 X:02 Y:E9 P:65 SP:FB PPU:108,204 CYC:23231\n$F2E2:A9 06     LDA #$06                           A:47 X:02 Y:E9 P:65 SP:FB PPU:117,204 CYC:23234\n$F2E4:85 48     STA $48 = #$06                       A:06 X:02 Y:E9 P:65 SP:FB PPU:123,204 CYC:23236\n$F2E6:A9 A5     LDA #$A5                           A:06 X:02 Y:E9 P:65 SP:FB PPU:132,204 CYC:23239\n$F2E8:8D 47 06  STA $0647 = #$6F                     A:A5 X:02 Y:E9 P:E5 SP:FB PPU:138,204 CYC:23241\n$F2EB:20 1D FB  JSR $FB1D                          A:A5 X:02 Y:E9 P:E5 SP:FB PPU:150,204 CYC:23245\n$FB1D:24 01     BIT $01 = #$FF                       A:A5 X:02 Y:E9 P:E5 SP:F9 PPU:168,204 CYC:23251\n$FB1F:18        CLC                                A:A5 X:02 Y:E9 P:E5 SP:F9 PPU:177,204 CYC:23254\n$FB20:A9 B3     LDA #$B3                           A:A5 X:02 Y:E9 P:NVUbdIzc SP:F9 PPU:183,204 CYC:23256\n$FB22:60        RTS                                A:B3 X:02 Y:E9 P:NVUbdIzc SP:F9 PPU:189,204 CYC:23258\n$F2EE:43 45    *SRE ($45,X) @ 47 = #$0647 = A5       A:B3 X:02 Y:E9 P:NVUbdIzc SP:FB PPU:207,204 CYC:23264\n$F2F0:EA        NOP                                A:E1 X:02 Y:E9 P:E5 SP:FB PPU:231,204 CYC:23272\n$F2F1:EA        NOP                                A:E1 X:02 Y:E9 P:E5 SP:FB PPU:237,204 CYC:23274\n$F2F2:EA        NOP                                A:E1 X:02 Y:E9 P:E5 SP:FB PPU:243,204 CYC:23276\n$F2F3:EA        NOP                                A:E1 X:02 Y:E9 P:E5 SP:FB PPU:249,204 CYC:23278\n$F2F4:20 23 FB  JSR $FB23                          A:E1 X:02 Y:E9 P:E5 SP:FB PPU:255,204 CYC:23280\n$FB23:50 50     BVC $FB75                          A:E1 X:02 Y:E9 P:E5 SP:F9 PPU:273,204 CYC:23286\n$FB25:90 4E     BCC $FB75                          A:E1 X:02 Y:E9 P:E5 SP:F9 PPU:279,204 CYC:23288\n$FB27:10 4C     BPL $FB75                          A:E1 X:02 Y:E9 P:E5 SP:F9 PPU:285,204 CYC:23290\n$FB29:C9 E1     CMP #$E1                           A:E1 X:02 Y:E9 P:E5 SP:F9 PPU:291,204 CYC:23292\n$FB2B:D0 48     BNE $FB75                          A:E1 X:02 Y:E9 P:67 SP:F9 PPU:297,204 CYC:23294\n$FB2D:60        RTS                                A:E1 X:02 Y:E9 P:67 SP:F9 PPU:303,204 CYC:23296\n$F2F7:AD 47 06  LDA $0647 = #$52                     A:E1 X:02 Y:E9 P:67 SP:FB PPU:321,204 CYC:23302\n$F2FA:C9 52     CMP #$52                           A:52 X:02 Y:E9 P:65 SP:FB PPU:333,204 CYC:23306\n$F2FC:F0 02     BEQ $F300                          A:52 X:02 Y:E9 P:67 SP:FB PPU:339,204 CYC:23308\n$F300:C8        INY                                A:52 X:02 Y:E9 P:67 SP:FB PPU: 10,205 CYC:23312\n$F301:A9 29     LDA #$29                           A:52 X:02 Y:EA P:E5 SP:FB PPU: 16,205 CYC:23314\n$F303:8D 47 06  STA $0647 = #$52                     A:29 X:02 Y:EA P:65 SP:FB PPU: 22,205 CYC:23316\n$F306:20 2E FB  JSR $FB2E                          A:29 X:02 Y:EA P:65 SP:FB PPU: 34,205 CYC:23320\n$FB2E:B8        CLV                                A:29 X:02 Y:EA P:65 SP:F9 PPU: 52,205 CYC:23326\n$FB2F:18        CLC                                A:29 X:02 Y:EA P:25 SP:F9 PPU: 58,205 CYC:23328\n$FB30:A9 42     LDA #$42                           A:29 X:02 Y:EA P:nvUbdIzc SP:F9 PPU: 64,205 CYC:23330\n$FB32:60        RTS                                A:42 X:02 Y:EA P:nvUbdIzc SP:F9 PPU: 70,205 CYC:23332\n$F309:43 45    *SRE ($45,X) @ 47 = #$0647 = 29       A:42 X:02 Y:EA P:nvUbdIzc SP:FB PPU: 88,205 CYC:23338\n$F30B:EA        NOP                                A:56 X:02 Y:EA P:25 SP:FB PPU:112,205 CYC:23346\n$F30C:EA        NOP                                A:56 X:02 Y:EA P:25 SP:FB PPU:118,205 CYC:23348\n$F30D:EA        NOP                                A:56 X:02 Y:EA P:25 SP:FB PPU:124,205 CYC:23350\n$F30E:EA        NOP                                A:56 X:02 Y:EA P:25 SP:FB PPU:130,205 CYC:23352\n$F30F:20 33 FB  JSR $FB33                          A:56 X:02 Y:EA P:25 SP:FB PPU:136,205 CYC:23354\n$FB33:70 40     BVS $FB75                          A:56 X:02 Y:EA P:25 SP:F9 PPU:154,205 CYC:23360\n$FB35:F0 3E     BEQ $FB75                          A:56 X:02 Y:EA P:25 SP:F9 PPU:160,205 CYC:23362\n$FB37:30 3C     BMI $FB75                          A:56 X:02 Y:EA P:25 SP:F9 PPU:166,205 CYC:23364\n$FB39:90 3A     BCC $FB75                          A:56 X:02 Y:EA P:25 SP:F9 PPU:172,205 CYC:23366\n$FB3B:C9 56     CMP #$56                           A:56 X:02 Y:EA P:25 SP:F9 PPU:178,205 CYC:23368\n$FB3D:D0 36     BNE $FB75                          A:56 X:02 Y:EA P:nvUbdIZC SP:F9 PPU:184,205 CYC:23370\n$FB3F:60        RTS                                A:56 X:02 Y:EA P:nvUbdIZC SP:F9 PPU:190,205 CYC:23372\n$F312:AD 47 06  LDA $0647 = #$14                     A:56 X:02 Y:EA P:nvUbdIZC SP:FB PPU:208,205 CYC:23378\n$F315:C9 14     CMP #$14                           A:14 X:02 Y:EA P:25 SP:FB PPU:220,205 CYC:23382\n$F317:F0 02     BEQ $F31B                          A:14 X:02 Y:EA P:nvUbdIZC SP:FB PPU:226,205 CYC:23384\n$F31B:C8        INY                                A:14 X:02 Y:EA P:nvUbdIZC SP:FB PPU:235,205 CYC:23387\n$F31C:A9 37     LDA #$37                           A:14 X:02 Y:EB P:A5 SP:FB PPU:241,205 CYC:23389\n$F31E:8D 47 06  STA $0647 = #$14                     A:37 X:02 Y:EB P:25 SP:FB PPU:247,205 CYC:23391\n$F321:20 40 FB  JSR $FB40                          A:37 X:02 Y:EB P:25 SP:FB PPU:259,205 CYC:23395\n$FB40:24 01     BIT $01 = #$FF                       A:37 X:02 Y:EB P:25 SP:F9 PPU:277,205 CYC:23401\n$FB42:38        SEC                                A:37 X:02 Y:EB P:E5 SP:F9 PPU:286,205 CYC:23404\n$FB43:A9 75     LDA #$75                           A:37 X:02 Y:EB P:E5 SP:F9 PPU:292,205 CYC:23406\n$FB45:60        RTS                                A:75 X:02 Y:EB P:65 SP:F9 PPU:298,205 CYC:23408\n$F324:43 45    *SRE ($45,X) @ 47 = #$0647 = 37       A:75 X:02 Y:EB P:65 SP:FB PPU:316,205 CYC:23414\n$F326:EA        NOP                                A:6E X:02 Y:EB P:65 SP:FB PPU:340,205 CYC:23422\n$F327:EA        NOP                                A:6E X:02 Y:EB P:65 SP:FB PPU:  5,206 CYC:23424\n$F328:EA        NOP                                A:6E X:02 Y:EB P:65 SP:FB PPU: 11,206 CYC:23426\n$F329:EA        NOP                                A:6E X:02 Y:EB P:65 SP:FB PPU: 17,206 CYC:23428\n$F32A:20 46 FB  JSR $FB46                          A:6E X:02 Y:EB P:65 SP:FB PPU: 23,206 CYC:23430\n$FB46:50 2D     BVC $FB75                          A:6E X:02 Y:EB P:65 SP:F9 PPU: 41,206 CYC:23436\n$FB48:F0 2B     BEQ $FB75                          A:6E X:02 Y:EB P:65 SP:F9 PPU: 47,206 CYC:23438\n$FB4A:30 29     BMI $FB75                          A:6E X:02 Y:EB P:65 SP:F9 PPU: 53,206 CYC:23440\n$FB4C:90 27     BCC $FB75                          A:6E X:02 Y:EB P:65 SP:F9 PPU: 59,206 CYC:23442\n$FB4E:C9 6E     CMP #$6E                           A:6E X:02 Y:EB P:65 SP:F9 PPU: 65,206 CYC:23444\n$FB50:D0 23     BNE $FB75                          A:6E X:02 Y:EB P:67 SP:F9 PPU: 71,206 CYC:23446\n$FB52:60        RTS                                A:6E X:02 Y:EB P:67 SP:F9 PPU: 77,206 CYC:23448\n$F32D:AD 47 06  LDA $0647 = #$1B                     A:6E X:02 Y:EB P:67 SP:FB PPU: 95,206 CYC:23454\n$F330:C9 1B     CMP #$1B                           A:1B X:02 Y:EB P:65 SP:FB PPU:107,206 CYC:23458\n$F332:F0 02     BEQ $F336                          A:1B X:02 Y:EB P:67 SP:FB PPU:113,206 CYC:23460\n$F336:C8        INY                                A:1B X:02 Y:EB P:67 SP:FB PPU:122,206 CYC:23463\n$F337:A9 A5     LDA #$A5                           A:1B X:02 Y:EC P:E5 SP:FB PPU:128,206 CYC:23465\n$F339:85 47     STA $47 = #$47                       A:A5 X:02 Y:EC P:E5 SP:FB PPU:134,206 CYC:23467\n$F33B:20 1D FB  JSR $FB1D                          A:A5 X:02 Y:EC P:E5 SP:FB PPU:143,206 CYC:23470\n$FB1D:24 01     BIT $01 = #$FF                       A:A5 X:02 Y:EC P:E5 SP:F9 PPU:161,206 CYC:23476\n$FB1F:18        CLC                                A:A5 X:02 Y:EC P:E5 SP:F9 PPU:170,206 CYC:23479\n$FB20:A9 B3     LDA #$B3                           A:A5 X:02 Y:EC P:NVUbdIzc SP:F9 PPU:176,206 CYC:23481\n$FB22:60        RTS                                A:B3 X:02 Y:EC P:NVUbdIzc SP:F9 PPU:182,206 CYC:23483\n$F33E:47 47    *SRE $47 = #$A5                       A:B3 X:02 Y:EC P:NVUbdIzc SP:FB PPU:200,206 CYC:23489\n$F340:EA        NOP                                A:E1 X:02 Y:EC P:E5 SP:FB PPU:215,206 CYC:23494\n$F341:EA        NOP                                A:E1 X:02 Y:EC P:E5 SP:FB PPU:221,206 CYC:23496\n$F342:EA        NOP                                A:E1 X:02 Y:EC P:E5 SP:FB PPU:227,206 CYC:23498\n$F343:EA        NOP                                A:E1 X:02 Y:EC P:E5 SP:FB PPU:233,206 CYC:23500\n$F344:20 23 FB  JSR $FB23                          A:E1 X:02 Y:EC P:E5 SP:FB PPU:239,206 CYC:23502\n$FB23:50 50     BVC $FB75                          A:E1 X:02 Y:EC P:E5 SP:F9 PPU:257,206 CYC:23508\n$FB25:90 4E     BCC $FB75                          A:E1 X:02 Y:EC P:E5 SP:F9 PPU:263,206 CYC:23510\n$FB27:10 4C     BPL $FB75                          A:E1 X:02 Y:EC P:E5 SP:F9 PPU:269,206 CYC:23512\n$FB29:C9 E1     CMP #$E1                           A:E1 X:02 Y:EC P:E5 SP:F9 PPU:275,206 CYC:23514\n$FB2B:D0 48     BNE $FB75                          A:E1 X:02 Y:EC P:67 SP:F9 PPU:281,206 CYC:23516\n$FB2D:60        RTS                                A:E1 X:02 Y:EC P:67 SP:F9 PPU:287,206 CYC:23518\n$F347:A5 47     LDA $47 = #$52                       A:E1 X:02 Y:EC P:67 SP:FB PPU:305,206 CYC:23524\n$F349:C9 52     CMP #$52                           A:52 X:02 Y:EC P:65 SP:FB PPU:314,206 CYC:23527\n$F34B:F0 02     BEQ $F34F                          A:52 X:02 Y:EC P:67 SP:FB PPU:320,206 CYC:23529\n$F34F:C8        INY                                A:52 X:02 Y:EC P:67 SP:FB PPU:329,206 CYC:23532\n$F350:A9 29     LDA #$29                           A:52 X:02 Y:ED P:E5 SP:FB PPU:335,206 CYC:23534\n$F352:85 47     STA $47 = #$52                       A:29 X:02 Y:ED P:65 SP:FB PPU:  0,207 CYC:23536\n$F354:20 2E FB  JSR $FB2E                          A:29 X:02 Y:ED P:65 SP:FB PPU:  9,207 CYC:23539\n$FB2E:B8        CLV                                A:29 X:02 Y:ED P:65 SP:F9 PPU: 27,207 CYC:23545\n$FB2F:18        CLC                                A:29 X:02 Y:ED P:25 SP:F9 PPU: 33,207 CYC:23547\n$FB30:A9 42     LDA #$42                           A:29 X:02 Y:ED P:nvUbdIzc SP:F9 PPU: 39,207 CYC:23549\n$FB32:60        RTS                                A:42 X:02 Y:ED P:nvUbdIzc SP:F9 PPU: 45,207 CYC:23551\n$F357:47 47    *SRE $47 = #$29                       A:42 X:02 Y:ED P:nvUbdIzc SP:FB PPU: 63,207 CYC:23557\n$F359:EA        NOP                                A:56 X:02 Y:ED P:25 SP:FB PPU: 78,207 CYC:23562\n$F35A:EA        NOP                                A:56 X:02 Y:ED P:25 SP:FB PPU: 84,207 CYC:23564\n$F35B:EA        NOP                                A:56 X:02 Y:ED P:25 SP:FB PPU: 90,207 CYC:23566\n$F35C:EA        NOP                                A:56 X:02 Y:ED P:25 SP:FB PPU: 96,207 CYC:23568\n$F35D:20 33 FB  JSR $FB33                          A:56 X:02 Y:ED P:25 SP:FB PPU:102,207 CYC:23570\n$FB33:70 40     BVS $FB75                          A:56 X:02 Y:ED P:25 SP:F9 PPU:120,207 CYC:23576\n$FB35:F0 3E     BEQ $FB75                          A:56 X:02 Y:ED P:25 SP:F9 PPU:126,207 CYC:23578\n$FB37:30 3C     BMI $FB75                          A:56 X:02 Y:ED P:25 SP:F9 PPU:132,207 CYC:23580\n$FB39:90 3A     BCC $FB75                          A:56 X:02 Y:ED P:25 SP:F9 PPU:138,207 CYC:23582\n$FB3B:C9 56     CMP #$56                           A:56 X:02 Y:ED P:25 SP:F9 PPU:144,207 CYC:23584\n$FB3D:D0 36     BNE $FB75                          A:56 X:02 Y:ED P:nvUbdIZC SP:F9 PPU:150,207 CYC:23586\n$FB3F:60        RTS                                A:56 X:02 Y:ED P:nvUbdIZC SP:F9 PPU:156,207 CYC:23588\n$F360:A5 47     LDA $47 = #$14                       A:56 X:02 Y:ED P:nvUbdIZC SP:FB PPU:174,207 CYC:23594\n$F362:C9 14     CMP #$14                           A:14 X:02 Y:ED P:25 SP:FB PPU:183,207 CYC:23597\n$F364:F0 02     BEQ $F368                          A:14 X:02 Y:ED P:nvUbdIZC SP:FB PPU:189,207 CYC:23599\n$F368:C8        INY                                A:14 X:02 Y:ED P:nvUbdIZC SP:FB PPU:198,207 CYC:23602\n$F369:A9 37     LDA #$37                           A:14 X:02 Y:EE P:A5 SP:FB PPU:204,207 CYC:23604\n$F36B:85 47     STA $47 = #$14                       A:37 X:02 Y:EE P:25 SP:FB PPU:210,207 CYC:23606\n$F36D:20 40 FB  JSR $FB40                          A:37 X:02 Y:EE P:25 SP:FB PPU:219,207 CYC:23609\n$FB40:24 01     BIT $01 = #$FF                       A:37 X:02 Y:EE P:25 SP:F9 PPU:237,207 CYC:23615\n$FB42:38        SEC                                A:37 X:02 Y:EE P:E5 SP:F9 PPU:246,207 CYC:23618\n$FB43:A9 75     LDA #$75                           A:37 X:02 Y:EE P:E5 SP:F9 PPU:252,207 CYC:23620\n$FB45:60        RTS                                A:75 X:02 Y:EE P:65 SP:F9 PPU:258,207 CYC:23622\n$F370:47 47    *SRE $47 = #$37                       A:75 X:02 Y:EE P:65 SP:FB PPU:276,207 CYC:23628\n$F372:EA        NOP                                A:6E X:02 Y:EE P:65 SP:FB PPU:291,207 CYC:23633\n$F373:EA        NOP                                A:6E X:02 Y:EE P:65 SP:FB PPU:297,207 CYC:23635\n$F374:EA        NOP                                A:6E X:02 Y:EE P:65 SP:FB PPU:303,207 CYC:23637\n$F375:EA        NOP                                A:6E X:02 Y:EE P:65 SP:FB PPU:309,207 CYC:23639\n$F376:20 46 FB  JSR $FB46                          A:6E X:02 Y:EE P:65 SP:FB PPU:315,207 CYC:23641\n$FB46:50 2D     BVC $FB75                          A:6E X:02 Y:EE P:65 SP:F9 PPU:333,207 CYC:23647\n$FB48:F0 2B     BEQ $FB75                          A:6E X:02 Y:EE P:65 SP:F9 PPU:339,207 CYC:23649\n$FB4A:30 29     BMI $FB75                          A:6E X:02 Y:EE P:65 SP:F9 PPU:  4,208 CYC:23651\n$FB4C:90 27     BCC $FB75                          A:6E X:02 Y:EE P:65 SP:F9 PPU: 10,208 CYC:23653\n$FB4E:C9 6E     CMP #$6E                           A:6E X:02 Y:EE P:65 SP:F9 PPU: 16,208 CYC:23655\n$FB50:D0 23     BNE $FB75                          A:6E X:02 Y:EE P:67 SP:F9 PPU: 22,208 CYC:23657\n$FB52:60        RTS                                A:6E X:02 Y:EE P:67 SP:F9 PPU: 28,208 CYC:23659\n$F379:A5 47     LDA $47 = #$1B                       A:6E X:02 Y:EE P:67 SP:FB PPU: 46,208 CYC:23665\n$F37B:C9 1B     CMP #$1B                           A:1B X:02 Y:EE P:65 SP:FB PPU: 55,208 CYC:23668\n$F37D:F0 02     BEQ $F381                          A:1B X:02 Y:EE P:67 SP:FB PPU: 61,208 CYC:23670\n$F381:C8        INY                                A:1B X:02 Y:EE P:67 SP:FB PPU: 70,208 CYC:23673\n$F382:A9 A5     LDA #$A5                           A:1B X:02 Y:EF P:E5 SP:FB PPU: 76,208 CYC:23675\n$F384:8D 47 06  STA $0647 = #$1B                     A:A5 X:02 Y:EF P:E5 SP:FB PPU: 82,208 CYC:23677\n$F387:20 1D FB  JSR $FB1D                          A:A5 X:02 Y:EF P:E5 SP:FB PPU: 94,208 CYC:23681\n$FB1D:24 01     BIT $01 = #$FF                       A:A5 X:02 Y:EF P:E5 SP:F9 PPU:112,208 CYC:23687\n$FB1F:18        CLC                                A:A5 X:02 Y:EF P:E5 SP:F9 PPU:121,208 CYC:23690\n$FB20:A9 B3     LDA #$B3                           A:A5 X:02 Y:EF P:NVUbdIzc SP:F9 PPU:127,208 CYC:23692\n$FB22:60        RTS                                A:B3 X:02 Y:EF P:NVUbdIzc SP:F9 PPU:133,208 CYC:23694\n$F38A:4F 47 06 *SRE $0647 = #$A5                     A:B3 X:02 Y:EF P:NVUbdIzc SP:FB PPU:151,208 CYC:23700\n$F38D:EA        NOP                                A:E1 X:02 Y:EF P:E5 SP:FB PPU:169,208 CYC:23706\n$F38E:EA        NOP                                A:E1 X:02 Y:EF P:E5 SP:FB PPU:175,208 CYC:23708\n$F38F:EA        NOP                                A:E1 X:02 Y:EF P:E5 SP:FB PPU:181,208 CYC:23710\n$F390:EA        NOP                                A:E1 X:02 Y:EF P:E5 SP:FB PPU:187,208 CYC:23712\n$F391:20 23 FB  JSR $FB23                          A:E1 X:02 Y:EF P:E5 SP:FB PPU:193,208 CYC:23714\n$FB23:50 50     BVC $FB75                          A:E1 X:02 Y:EF P:E5 SP:F9 PPU:211,208 CYC:23720\n$FB25:90 4E     BCC $FB75                          A:E1 X:02 Y:EF P:E5 SP:F9 PPU:217,208 CYC:23722\n$FB27:10 4C     BPL $FB75                          A:E1 X:02 Y:EF P:E5 SP:F9 PPU:223,208 CYC:23724\n$FB29:C9 E1     CMP #$E1                           A:E1 X:02 Y:EF P:E5 SP:F9 PPU:229,208 CYC:23726\n$FB2B:D0 48     BNE $FB75                          A:E1 X:02 Y:EF P:67 SP:F9 PPU:235,208 CYC:23728\n$FB2D:60        RTS                                A:E1 X:02 Y:EF P:67 SP:F9 PPU:241,208 CYC:23730\n$F394:AD 47 06  LDA $0647 = #$52                     A:E1 X:02 Y:EF P:67 SP:FB PPU:259,208 CYC:23736\n$F397:C9 52     CMP #$52                           A:52 X:02 Y:EF P:65 SP:FB PPU:271,208 CYC:23740\n$F399:F0 02     BEQ $F39D                          A:52 X:02 Y:EF P:67 SP:FB PPU:277,208 CYC:23742\n$F39D:C8        INY                                A:52 X:02 Y:EF P:67 SP:FB PPU:286,208 CYC:23745\n$F39E:A9 29     LDA #$29                           A:52 X:02 Y:F0 P:E5 SP:FB PPU:292,208 CYC:23747\n$F3A0:8D 47 06  STA $0647 = #$52                     A:29 X:02 Y:F0 P:65 SP:FB PPU:298,208 CYC:23749\n$F3A3:20 2E FB  JSR $FB2E                          A:29 X:02 Y:F0 P:65 SP:FB PPU:310,208 CYC:23753\n$FB2E:B8        CLV                                A:29 X:02 Y:F0 P:65 SP:F9 PPU:328,208 CYC:23759\n$FB2F:18        CLC                                A:29 X:02 Y:F0 P:25 SP:F9 PPU:334,208 CYC:23761\n$FB30:A9 42     LDA #$42                           A:29 X:02 Y:F0 P:nvUbdIzc SP:F9 PPU:340,208 CYC:23763\n$FB32:60        RTS                                A:42 X:02 Y:F0 P:nvUbdIzc SP:F9 PPU:  5,209 CYC:23765\n$F3A6:4F 47 06 *SRE $0647 = #$29                     A:42 X:02 Y:F0 P:nvUbdIzc SP:FB PPU: 23,209 CYC:23771\n$F3A9:EA        NOP                                A:56 X:02 Y:F0 P:25 SP:FB PPU: 41,209 CYC:23777\n$F3AA:EA        NOP                                A:56 X:02 Y:F0 P:25 SP:FB PPU: 47,209 CYC:23779\n$F3AB:EA        NOP                                A:56 X:02 Y:F0 P:25 SP:FB PPU: 53,209 CYC:23781\n$F3AC:EA        NOP                                A:56 X:02 Y:F0 P:25 SP:FB PPU: 59,209 CYC:23783\n$F3AD:20 33 FB  JSR $FB33                          A:56 X:02 Y:F0 P:25 SP:FB PPU: 65,209 CYC:23785\n$FB33:70 40     BVS $FB75                          A:56 X:02 Y:F0 P:25 SP:F9 PPU: 83,209 CYC:23791\n$FB35:F0 3E     BEQ $FB75                          A:56 X:02 Y:F0 P:25 SP:F9 PPU: 89,209 CYC:23793\n$FB37:30 3C     BMI $FB75                          A:56 X:02 Y:F0 P:25 SP:F9 PPU: 95,209 CYC:23795\n$FB39:90 3A     BCC $FB75                          A:56 X:02 Y:F0 P:25 SP:F9 PPU:101,209 CYC:23797\n$FB3B:C9 56     CMP #$56                           A:56 X:02 Y:F0 P:25 SP:F9 PPU:107,209 CYC:23799\n$FB3D:D0 36     BNE $FB75                          A:56 X:02 Y:F0 P:nvUbdIZC SP:F9 PPU:113,209 CYC:23801\n$FB3F:60        RTS                                A:56 X:02 Y:F0 P:nvUbdIZC SP:F9 PPU:119,209 CYC:23803\n$F3B0:AD 47 06  LDA $0647 = #$14                     A:56 X:02 Y:F0 P:nvUbdIZC SP:FB PPU:137,209 CYC:23809\n$F3B3:C9 14     CMP #$14                           A:14 X:02 Y:F0 P:25 SP:FB PPU:149,209 CYC:23813\n$F3B5:F0 02     BEQ $F3B9                          A:14 X:02 Y:F0 P:nvUbdIZC SP:FB PPU:155,209 CYC:23815\n$F3B9:C8        INY                                A:14 X:02 Y:F0 P:nvUbdIZC SP:FB PPU:164,209 CYC:23818\n$F3BA:A9 37     LDA #$37                           A:14 X:02 Y:F1 P:A5 SP:FB PPU:170,209 CYC:23820\n$F3BC:8D 47 06  STA $0647 = #$14                     A:37 X:02 Y:F1 P:25 SP:FB PPU:176,209 CYC:23822\n$F3BF:20 40 FB  JSR $FB40                          A:37 X:02 Y:F1 P:25 SP:FB PPU:188,209 CYC:23826\n$FB40:24 01     BIT $01 = #$FF                       A:37 X:02 Y:F1 P:25 SP:F9 PPU:206,209 CYC:23832\n$FB42:38        SEC                                A:37 X:02 Y:F1 P:E5 SP:F9 PPU:215,209 CYC:23835\n$FB43:A9 75     LDA #$75                           A:37 X:02 Y:F1 P:E5 SP:F9 PPU:221,209 CYC:23837\n$FB45:60        RTS                                A:75 X:02 Y:F1 P:65 SP:F9 PPU:227,209 CYC:23839\n$F3C2:4F 47 06 *SRE $0647 = #$37                     A:75 X:02 Y:F1 P:65 SP:FB PPU:245,209 CYC:23845\n$F3C5:EA        NOP                                A:6E X:02 Y:F1 P:65 SP:FB PPU:263,209 CYC:23851\n$F3C6:EA        NOP                                A:6E X:02 Y:F1 P:65 SP:FB PPU:269,209 CYC:23853\n$F3C7:EA        NOP                                A:6E X:02 Y:F1 P:65 SP:FB PPU:275,209 CYC:23855\n$F3C8:EA        NOP                                A:6E X:02 Y:F1 P:65 SP:FB PPU:281,209 CYC:23857\n$F3C9:20 46 FB  JSR $FB46                          A:6E X:02 Y:F1 P:65 SP:FB PPU:287,209 CYC:23859\n$FB46:50 2D     BVC $FB75                          A:6E X:02 Y:F1 P:65 SP:F9 PPU:305,209 CYC:23865\n$FB48:F0 2B     BEQ $FB75                          A:6E X:02 Y:F1 P:65 SP:F9 PPU:311,209 CYC:23867\n$FB4A:30 29     BMI $FB75                          A:6E X:02 Y:F1 P:65 SP:F9 PPU:317,209 CYC:23869\n$FB4C:90 27     BCC $FB75                          A:6E X:02 Y:F1 P:65 SP:F9 PPU:323,209 CYC:23871\n$FB4E:C9 6E     CMP #$6E                           A:6E X:02 Y:F1 P:65 SP:F9 PPU:329,209 CYC:23873\n$FB50:D0 23     BNE $FB75                          A:6E X:02 Y:F1 P:67 SP:F9 PPU:335,209 CYC:23875\n$FB52:60        RTS                                A:6E X:02 Y:F1 P:67 SP:F9 PPU:  0,210 CYC:23877\n$F3CC:AD 47 06  LDA $0647 = #$1B                     A:6E X:02 Y:F1 P:67 SP:FB PPU: 18,210 CYC:23883\n$F3CF:C9 1B     CMP #$1B                           A:1B X:02 Y:F1 P:65 SP:FB PPU: 30,210 CYC:23887\n$F3D1:F0 02     BEQ $F3D5                          A:1B X:02 Y:F1 P:67 SP:FB PPU: 36,210 CYC:23889\n$F3D5:A9 A5     LDA #$A5                           A:1B X:02 Y:F1 P:67 SP:FB PPU: 45,210 CYC:23892\n$F3D7:8D 47 06  STA $0647 = #$1B                     A:A5 X:02 Y:F1 P:E5 SP:FB PPU: 51,210 CYC:23894\n$F3DA:A9 48     LDA #$48                           A:A5 X:02 Y:F1 P:E5 SP:FB PPU: 63,210 CYC:23898\n$F3DC:85 45     STA $45 = #$48                       A:48 X:02 Y:F1 P:65 SP:FB PPU: 69,210 CYC:23900\n$F3DE:A9 05     LDA #$05                           A:48 X:02 Y:F1 P:65 SP:FB PPU: 78,210 CYC:23903\n$F3E0:85 46     STA $46 = #$05                       A:05 X:02 Y:F1 P:65 SP:FB PPU: 84,210 CYC:23905\n$F3E2:A0 FF     LDY #$FF                           A:05 X:02 Y:F1 P:65 SP:FB PPU: 93,210 CYC:23908\n$F3E4:20 1D FB  JSR $FB1D                          A:05 X:02 Y:FF P:E5 SP:FB PPU: 99,210 CYC:23910\n$FB1D:24 01     BIT $01 = #$FF                       A:05 X:02 Y:FF P:E5 SP:F9 PPU:117,210 CYC:23916\n$FB1F:18        CLC                                A:05 X:02 Y:FF P:E5 SP:F9 PPU:126,210 CYC:23919\n$FB20:A9 B3     LDA #$B3                           A:05 X:02 Y:FF P:NVUbdIzc SP:F9 PPU:132,210 CYC:23921\n$FB22:60        RTS                                A:B3 X:02 Y:FF P:NVUbdIzc SP:F9 PPU:138,210 CYC:23923\n$F3E7:53 45    *SRE ($45),Y = #$0548 @ 0647 = A5     A:B3 X:02 Y:FF P:NVUbdIzc SP:FB PPU:156,210 CYC:23929\n$F3E9:EA        NOP                                A:E1 X:02 Y:FF P:E5 SP:FB PPU:180,210 CYC:23937\n$F3EA:EA        NOP                                A:E1 X:02 Y:FF P:E5 SP:FB PPU:186,210 CYC:23939\n$F3EB:08        PHP                                A:E1 X:02 Y:FF P:E5 SP:FB PPU:192,210 CYC:23941\n$F3EC:48        PHA                                A:E1 X:02 Y:FF P:E5 SP:FA PPU:201,210 CYC:23944\n$F3ED:A0 F2     LDY #$F2                           A:E1 X:02 Y:FF P:E5 SP:F9 PPU:210,210 CYC:23947\n$F3EF:68        PLA                                A:E1 X:02 Y:F2 P:E5 SP:F9 PPU:216,210 CYC:23949\n$F3F0:28        PLP                                A:E1 X:02 Y:F2 P:E5 SP:FA PPU:228,210 CYC:23953\n$F3F1:20 23 FB  JSR $FB23                          A:E1 X:02 Y:F2 P:E5 SP:FB PPU:240,210 CYC:23957\n$FB23:50 50     BVC $FB75                          A:E1 X:02 Y:F2 P:E5 SP:F9 PPU:258,210 CYC:23963\n$FB25:90 4E     BCC $FB75                          A:E1 X:02 Y:F2 P:E5 SP:F9 PPU:264,210 CYC:23965\n$FB27:10 4C     BPL $FB75                          A:E1 X:02 Y:F2 P:E5 SP:F9 PPU:270,210 CYC:23967\n$FB29:C9 E1     CMP #$E1                           A:E1 X:02 Y:F2 P:E5 SP:F9 PPU:276,210 CYC:23969\n$FB2B:D0 48     BNE $FB75                          A:E1 X:02 Y:F2 P:67 SP:F9 PPU:282,210 CYC:23971\n$FB2D:60        RTS                                A:E1 X:02 Y:F2 P:67 SP:F9 PPU:288,210 CYC:23973\n$F3F4:AD 47 06  LDA $0647 = #$52                     A:E1 X:02 Y:F2 P:67 SP:FB PPU:306,210 CYC:23979\n$F3F7:C9 52     CMP #$52                           A:52 X:02 Y:F2 P:65 SP:FB PPU:318,210 CYC:23983\n$F3F9:F0 02     BEQ $F3FD                          A:52 X:02 Y:F2 P:67 SP:FB PPU:324,210 CYC:23985\n$F3FD:A0 FF     LDY #$FF                           A:52 X:02 Y:F2 P:67 SP:FB PPU:333,210 CYC:23988\n$F3FF:A9 29     LDA #$29                           A:52 X:02 Y:FF P:E5 SP:FB PPU:339,210 CYC:23990\n$F401:8D 47 06  STA $0647 = #$52                     A:29 X:02 Y:FF P:65 SP:FB PPU:  4,211 CYC:23992\n$F404:20 2E FB  JSR $FB2E                          A:29 X:02 Y:FF P:65 SP:FB PPU: 16,211 CYC:23996\n$FB2E:B8        CLV                                A:29 X:02 Y:FF P:65 SP:F9 PPU: 34,211 CYC:24002\n$FB2F:18        CLC                                A:29 X:02 Y:FF P:25 SP:F9 PPU: 40,211 CYC:24004\n$FB30:A9 42     LDA #$42                           A:29 X:02 Y:FF P:nvUbdIzc SP:F9 PPU: 46,211 CYC:24006\n$FB32:60        RTS                                A:42 X:02 Y:FF P:nvUbdIzc SP:F9 PPU: 52,211 CYC:24008\n$F407:53 45    *SRE ($45),Y = #$0548 @ 0647 = 29     A:42 X:02 Y:FF P:nvUbdIzc SP:FB PPU: 70,211 CYC:24014\n$F409:EA        NOP                                A:56 X:02 Y:FF P:25 SP:FB PPU: 94,211 CYC:24022\n$F40A:EA        NOP                                A:56 X:02 Y:FF P:25 SP:FB PPU:100,211 CYC:24024\n$F40B:08        PHP                                A:56 X:02 Y:FF P:25 SP:FB PPU:106,211 CYC:24026\n$F40C:48        PHA                                A:56 X:02 Y:FF P:25 SP:FA PPU:115,211 CYC:24029\n$F40D:A0 F3     LDY #$F3                           A:56 X:02 Y:FF P:25 SP:F9 PPU:124,211 CYC:24032\n$F40F:68        PLA                                A:56 X:02 Y:F3 P:A5 SP:F9 PPU:130,211 CYC:24034\n$F410:28        PLP                                A:56 X:02 Y:F3 P:25 SP:FA PPU:142,211 CYC:24038\n$F411:20 33 FB  JSR $FB33                          A:56 X:02 Y:F3 P:25 SP:FB PPU:154,211 CYC:24042\n$FB33:70 40     BVS $FB75                          A:56 X:02 Y:F3 P:25 SP:F9 PPU:172,211 CYC:24048\n$FB35:F0 3E     BEQ $FB75                          A:56 X:02 Y:F3 P:25 SP:F9 PPU:178,211 CYC:24050\n$FB37:30 3C     BMI $FB75                          A:56 X:02 Y:F3 P:25 SP:F9 PPU:184,211 CYC:24052\n$FB39:90 3A     BCC $FB75                          A:56 X:02 Y:F3 P:25 SP:F9 PPU:190,211 CYC:24054\n$FB3B:C9 56     CMP #$56                           A:56 X:02 Y:F3 P:25 SP:F9 PPU:196,211 CYC:24056\n$FB3D:D0 36     BNE $FB75                          A:56 X:02 Y:F3 P:nvUbdIZC SP:F9 PPU:202,211 CYC:24058\n$FB3F:60        RTS                                A:56 X:02 Y:F3 P:nvUbdIZC SP:F9 PPU:208,211 CYC:24060\n$F414:AD 47 06  LDA $0647 = #$14                     A:56 X:02 Y:F3 P:nvUbdIZC SP:FB PPU:226,211 CYC:24066\n$F417:C9 14     CMP #$14                           A:14 X:02 Y:F3 P:25 SP:FB PPU:238,211 CYC:24070\n$F419:F0 02     BEQ $F41D                          A:14 X:02 Y:F3 P:nvUbdIZC SP:FB PPU:244,211 CYC:24072\n$F41D:A0 FF     LDY #$FF                           A:14 X:02 Y:F3 P:nvUbdIZC SP:FB PPU:253,211 CYC:24075\n$F41F:A9 37     LDA #$37                           A:14 X:02 Y:FF P:A5 SP:FB PPU:259,211 CYC:24077\n$F421:8D 47 06  STA $0647 = #$14                     A:37 X:02 Y:FF P:25 SP:FB PPU:265,211 CYC:24079\n$F424:20 40 FB  JSR $FB40                          A:37 X:02 Y:FF P:25 SP:FB PPU:277,211 CYC:24083\n$FB40:24 01     BIT $01 = #$FF                       A:37 X:02 Y:FF P:25 SP:F9 PPU:295,211 CYC:24089\n$FB42:38        SEC                                A:37 X:02 Y:FF P:E5 SP:F9 PPU:304,211 CYC:24092\n$FB43:A9 75     LDA #$75                           A:37 X:02 Y:FF P:E5 SP:F9 PPU:310,211 CYC:24094\n$FB45:60        RTS                                A:75 X:02 Y:FF P:65 SP:F9 PPU:316,211 CYC:24096\n$F427:53 45    *SRE ($45),Y = #$0548 @ 0647 = 37     A:75 X:02 Y:FF P:65 SP:FB PPU:334,211 CYC:24102\n$F429:EA        NOP                                A:6E X:02 Y:FF P:65 SP:FB PPU: 17,212 CYC:24110\n$F42A:EA        NOP                                A:6E X:02 Y:FF P:65 SP:FB PPU: 23,212 CYC:24112\n$F42B:08        PHP                                A:6E X:02 Y:FF P:65 SP:FB PPU: 29,212 CYC:24114\n$F42C:48        PHA                                A:6E X:02 Y:FF P:65 SP:FA PPU: 38,212 CYC:24117\n$F42D:A0 F4     LDY #$F4                           A:6E X:02 Y:FF P:65 SP:F9 PPU: 47,212 CYC:24120\n$F42F:68        PLA                                A:6E X:02 Y:F4 P:E5 SP:F9 PPU: 53,212 CYC:24122\n$F430:28        PLP                                A:6E X:02 Y:F4 P:65 SP:FA PPU: 65,212 CYC:24126\n$F431:20 46 FB  JSR $FB46                          A:6E X:02 Y:F4 P:65 SP:FB PPU: 77,212 CYC:24130\n$FB46:50 2D     BVC $FB75                          A:6E X:02 Y:F4 P:65 SP:F9 PPU: 95,212 CYC:24136\n$FB48:F0 2B     BEQ $FB75                          A:6E X:02 Y:F4 P:65 SP:F9 PPU:101,212 CYC:24138\n$FB4A:30 29     BMI $FB75                          A:6E X:02 Y:F4 P:65 SP:F9 PPU:107,212 CYC:24140\n$FB4C:90 27     BCC $FB75                          A:6E X:02 Y:F4 P:65 SP:F9 PPU:113,212 CYC:24142\n$FB4E:C9 6E     CMP #$6E                           A:6E X:02 Y:F4 P:65 SP:F9 PPU:119,212 CYC:24144\n$FB50:D0 23     BNE $FB75                          A:6E X:02 Y:F4 P:67 SP:F9 PPU:125,212 CYC:24146\n$FB52:60        RTS                                A:6E X:02 Y:F4 P:67 SP:F9 PPU:131,212 CYC:24148\n$F434:AD 47 06  LDA $0647 = #$1B                     A:6E X:02 Y:F4 P:67 SP:FB PPU:149,212 CYC:24154\n$F437:C9 1B     CMP #$1B                           A:1B X:02 Y:F4 P:65 SP:FB PPU:161,212 CYC:24158\n$F439:F0 02     BEQ $F43D                          A:1B X:02 Y:F4 P:67 SP:FB PPU:167,212 CYC:24160\n$F43D:A0 F5     LDY #$F5                           A:1B X:02 Y:F4 P:67 SP:FB PPU:176,212 CYC:24163\n$F43F:A2 FF     LDX #$FF                           A:1B X:02 Y:F5 P:E5 SP:FB PPU:182,212 CYC:24165\n$F441:A9 A5     LDA #$A5                           A:1B X:FF Y:F5 P:E5 SP:FB PPU:188,212 CYC:24167\n$F443:85 47     STA $47 = #$1B                       A:A5 X:FF Y:F5 P:E5 SP:FB PPU:194,212 CYC:24169\n$F445:20 1D FB  JSR $FB1D                          A:A5 X:FF Y:F5 P:E5 SP:FB PPU:203,212 CYC:24172\n$FB1D:24 01     BIT $01 = #$FF                       A:A5 X:FF Y:F5 P:E5 SP:F9 PPU:221,212 CYC:24178\n$FB1F:18        CLC                                A:A5 X:FF Y:F5 P:E5 SP:F9 PPU:230,212 CYC:24181\n$FB20:A9 B3     LDA #$B3                           A:A5 X:FF Y:F5 P:NVUbdIzc SP:F9 PPU:236,212 CYC:24183\n$FB22:60        RTS                                A:B3 X:FF Y:F5 P:NVUbdIzc SP:F9 PPU:242,212 CYC:24185\n$F448:57 48    *SRE $48,X @ 47 = #$A5                A:B3 X:FF Y:F5 P:NVUbdIzc SP:FB PPU:260,212 CYC:24191\n$F44A:EA        NOP                                A:E1 X:FF Y:F5 P:E5 SP:FB PPU:278,212 CYC:24197\n$F44B:EA        NOP                                A:E1 X:FF Y:F5 P:E5 SP:FB PPU:284,212 CYC:24199\n$F44C:EA        NOP                                A:E1 X:FF Y:F5 P:E5 SP:FB PPU:290,212 CYC:24201\n$F44D:EA        NOP                                A:E1 X:FF Y:F5 P:E5 SP:FB PPU:296,212 CYC:24203\n$F44E:20 23 FB  JSR $FB23                          A:E1 X:FF Y:F5 P:E5 SP:FB PPU:302,212 CYC:24205\n$FB23:50 50     BVC $FB75                          A:E1 X:FF Y:F5 P:E5 SP:F9 PPU:320,212 CYC:24211\n$FB25:90 4E     BCC $FB75                          A:E1 X:FF Y:F5 P:E5 SP:F9 PPU:326,212 CYC:24213\n$FB27:10 4C     BPL $FB75                          A:E1 X:FF Y:F5 P:E5 SP:F9 PPU:332,212 CYC:24215\n$FB29:C9 E1     CMP #$E1                           A:E1 X:FF Y:F5 P:E5 SP:F9 PPU:338,212 CYC:24217\n$FB2B:D0 48     BNE $FB75                          A:E1 X:FF Y:F5 P:67 SP:F9 PPU:  3,213 CYC:24219\n$FB2D:60        RTS                                A:E1 X:FF Y:F5 P:67 SP:F9 PPU:  9,213 CYC:24221\n$F451:A5 47     LDA $47 = #$52                       A:E1 X:FF Y:F5 P:67 SP:FB PPU: 27,213 CYC:24227\n$F453:C9 52     CMP #$52                           A:52 X:FF Y:F5 P:65 SP:FB PPU: 36,213 CYC:24230\n$F455:F0 02     BEQ $F459                          A:52 X:FF Y:F5 P:67 SP:FB PPU: 42,213 CYC:24232\n$F459:C8        INY                                A:52 X:FF Y:F5 P:67 SP:FB PPU: 51,213 CYC:24235\n$F45A:A9 29     LDA #$29                           A:52 X:FF Y:F6 P:E5 SP:FB PPU: 57,213 CYC:24237\n$F45C:85 47     STA $47 = #$52                       A:29 X:FF Y:F6 P:65 SP:FB PPU: 63,213 CYC:24239\n$F45E:20 2E FB  JSR $FB2E                          A:29 X:FF Y:F6 P:65 SP:FB PPU: 72,213 CYC:24242\n$FB2E:B8        CLV                                A:29 X:FF Y:F6 P:65 SP:F9 PPU: 90,213 CYC:24248\n$FB2F:18        CLC                                A:29 X:FF Y:F6 P:25 SP:F9 PPU: 96,213 CYC:24250\n$FB30:A9 42     LDA #$42                           A:29 X:FF Y:F6 P:nvUbdIzc SP:F9 PPU:102,213 CYC:24252\n$FB32:60        RTS                                A:42 X:FF Y:F6 P:nvUbdIzc SP:F9 PPU:108,213 CYC:24254\n$F461:57 48    *SRE $48,X @ 47 = #$29                A:42 X:FF Y:F6 P:nvUbdIzc SP:FB PPU:126,213 CYC:24260\n$F463:EA        NOP                                A:56 X:FF Y:F6 P:25 SP:FB PPU:144,213 CYC:24266\n$F464:EA        NOP                                A:56 X:FF Y:F6 P:25 SP:FB PPU:150,213 CYC:24268\n$F465:EA        NOP                                A:56 X:FF Y:F6 P:25 SP:FB PPU:156,213 CYC:24270\n$F466:EA        NOP                                A:56 X:FF Y:F6 P:25 SP:FB PPU:162,213 CYC:24272\n$F467:20 33 FB  JSR $FB33                          A:56 X:FF Y:F6 P:25 SP:FB PPU:168,213 CYC:24274\n$FB33:70 40     BVS $FB75                          A:56 X:FF Y:F6 P:25 SP:F9 PPU:186,213 CYC:24280\n$FB35:F0 3E     BEQ $FB75                          A:56 X:FF Y:F6 P:25 SP:F9 PPU:192,213 CYC:24282\n$FB37:30 3C     BMI $FB75                          A:56 X:FF Y:F6 P:25 SP:F9 PPU:198,213 CYC:24284\n$FB39:90 3A     BCC $FB75                          A:56 X:FF Y:F6 P:25 SP:F9 PPU:204,213 CYC:24286\n$FB3B:C9 56     CMP #$56                           A:56 X:FF Y:F6 P:25 SP:F9 PPU:210,213 CYC:24288\n$FB3D:D0 36     BNE $FB75                          A:56 X:FF Y:F6 P:nvUbdIZC SP:F9 PPU:216,213 CYC:24290\n$FB3F:60        RTS                                A:56 X:FF Y:F6 P:nvUbdIZC SP:F9 PPU:222,213 CYC:24292\n$F46A:A5 47     LDA $47 = #$14                       A:56 X:FF Y:F6 P:nvUbdIZC SP:FB PPU:240,213 CYC:24298\n$F46C:C9 14     CMP #$14                           A:14 X:FF Y:F6 P:25 SP:FB PPU:249,213 CYC:24301\n$F46E:F0 02     BEQ $F472                          A:14 X:FF Y:F6 P:nvUbdIZC SP:FB PPU:255,213 CYC:24303\n$F472:C8        INY                                A:14 X:FF Y:F6 P:nvUbdIZC SP:FB PPU:264,213 CYC:24306\n$F473:A9 37     LDA #$37                           A:14 X:FF Y:F7 P:A5 SP:FB PPU:270,213 CYC:24308\n$F475:85 47     STA $47 = #$14                       A:37 X:FF Y:F7 P:25 SP:FB PPU:276,213 CYC:24310\n$F477:20 40 FB  JSR $FB40                          A:37 X:FF Y:F7 P:25 SP:FB PPU:285,213 CYC:24313\n$FB40:24 01     BIT $01 = #$FF                       A:37 X:FF Y:F7 P:25 SP:F9 PPU:303,213 CYC:24319\n$FB42:38        SEC                                A:37 X:FF Y:F7 P:E5 SP:F9 PPU:312,213 CYC:24322\n$FB43:A9 75     LDA #$75                           A:37 X:FF Y:F7 P:E5 SP:F9 PPU:318,213 CYC:24324\n$FB45:60        RTS                                A:75 X:FF Y:F7 P:65 SP:F9 PPU:324,213 CYC:24326\n$F47A:57 48    *SRE $48,X @ 47 = #$37                A:75 X:FF Y:F7 P:65 SP:FB PPU:  1,214 CYC:24332\n$F47C:EA        NOP                                A:6E X:FF Y:F7 P:65 SP:FB PPU: 19,214 CYC:24338\n$F47D:EA        NOP                                A:6E X:FF Y:F7 P:65 SP:FB PPU: 25,214 CYC:24340\n$F47E:EA        NOP                                A:6E X:FF Y:F7 P:65 SP:FB PPU: 31,214 CYC:24342\n$F47F:EA        NOP                                A:6E X:FF Y:F7 P:65 SP:FB PPU: 37,214 CYC:24344\n$F480:20 46 FB  JSR $FB46                          A:6E X:FF Y:F7 P:65 SP:FB PPU: 43,214 CYC:24346\n$FB46:50 2D     BVC $FB75                          A:6E X:FF Y:F7 P:65 SP:F9 PPU: 61,214 CYC:24352\n$FB48:F0 2B     BEQ $FB75                          A:6E X:FF Y:F7 P:65 SP:F9 PPU: 67,214 CYC:24354\n$FB4A:30 29     BMI $FB75                          A:6E X:FF Y:F7 P:65 SP:F9 PPU: 73,214 CYC:24356\n$FB4C:90 27     BCC $FB75                          A:6E X:FF Y:F7 P:65 SP:F9 PPU: 79,214 CYC:24358\n$FB4E:C9 6E     CMP #$6E                           A:6E X:FF Y:F7 P:65 SP:F9 PPU: 85,214 CYC:24360\n$FB50:D0 23     BNE $FB75                          A:6E X:FF Y:F7 P:67 SP:F9 PPU: 91,214 CYC:24362\n$FB52:60        RTS                                A:6E X:FF Y:F7 P:67 SP:F9 PPU: 97,214 CYC:24364\n$F483:A5 47     LDA $47 = #$1B                       A:6E X:FF Y:F7 P:67 SP:FB PPU:115,214 CYC:24370\n$F485:C9 1B     CMP #$1B                           A:1B X:FF Y:F7 P:65 SP:FB PPU:124,214 CYC:24373\n$F487:F0 02     BEQ $F48B                          A:1B X:FF Y:F7 P:67 SP:FB PPU:130,214 CYC:24375\n$F48B:A9 A5     LDA #$A5                           A:1B X:FF Y:F7 P:67 SP:FB PPU:139,214 CYC:24378\n$F48D:8D 47 06  STA $0647 = #$1B                     A:A5 X:FF Y:F7 P:E5 SP:FB PPU:145,214 CYC:24380\n$F490:A0 FF     LDY #$FF                           A:A5 X:FF Y:F7 P:E5 SP:FB PPU:157,214 CYC:24384\n$F492:20 1D FB  JSR $FB1D                          A:A5 X:FF Y:FF P:E5 SP:FB PPU:163,214 CYC:24386\n$FB1D:24 01     BIT $01 = #$FF                       A:A5 X:FF Y:FF P:E5 SP:F9 PPU:181,214 CYC:24392\n$FB1F:18        CLC                                A:A5 X:FF Y:FF P:E5 SP:F9 PPU:190,214 CYC:24395\n$FB20:A9 B3     LDA #$B3                           A:A5 X:FF Y:FF P:NVUbdIzc SP:F9 PPU:196,214 CYC:24397\n$FB22:60        RTS                                A:B3 X:FF Y:FF P:NVUbdIzc SP:F9 PPU:202,214 CYC:24399\n$F495:5B 48 05 *SRE $0548,Y @ 0647 = #$A5            A:B3 X:FF Y:FF P:NVUbdIzc SP:FB PPU:220,214 CYC:24405\n$F498:EA        NOP                                A:E1 X:FF Y:FF P:E5 SP:FB PPU:241,214 CYC:24412\n$F499:EA        NOP                                A:E1 X:FF Y:FF P:E5 SP:FB PPU:247,214 CYC:24414\n$F49A:08        PHP                                A:E1 X:FF Y:FF P:E5 SP:FB PPU:253,214 CYC:24416\n$F49B:48        PHA                                A:E1 X:FF Y:FF P:E5 SP:FA PPU:262,214 CYC:24419\n$F49C:A0 F8     LDY #$F8                           A:E1 X:FF Y:FF P:E5 SP:F9 PPU:271,214 CYC:24422\n$F49E:68        PLA                                A:E1 X:FF Y:F8 P:E5 SP:F9 PPU:277,214 CYC:24424\n$F49F:28        PLP                                A:E1 X:FF Y:F8 P:E5 SP:FA PPU:289,214 CYC:24428\n$F4A0:20 23 FB  JSR $FB23                          A:E1 X:FF Y:F8 P:E5 SP:FB PPU:301,214 CYC:24432\n$FB23:50 50     BVC $FB75                          A:E1 X:FF Y:F8 P:E5 SP:F9 PPU:319,214 CYC:24438\n$FB25:90 4E     BCC $FB75                          A:E1 X:FF Y:F8 P:E5 SP:F9 PPU:325,214 CYC:24440\n$FB27:10 4C     BPL $FB75                          A:E1 X:FF Y:F8 P:E5 SP:F9 PPU:331,214 CYC:24442\n$FB29:C9 E1     CMP #$E1                           A:E1 X:FF Y:F8 P:E5 SP:F9 PPU:337,214 CYC:24444\n$FB2B:D0 48     BNE $FB75                          A:E1 X:FF Y:F8 P:67 SP:F9 PPU:  2,215 CYC:24446\n$FB2D:60        RTS                                A:E1 X:FF Y:F8 P:67 SP:F9 PPU:  8,215 CYC:24448\n$F4A3:AD 47 06  LDA $0647 = #$52                     A:E1 X:FF Y:F8 P:67 SP:FB PPU: 26,215 CYC:24454\n$F4A6:C9 52     CMP #$52                           A:52 X:FF Y:F8 P:65 SP:FB PPU: 38,215 CYC:24458\n$F4A8:F0 02     BEQ $F4AC                          A:52 X:FF Y:F8 P:67 SP:FB PPU: 44,215 CYC:24460\n$F4AC:A0 FF     LDY #$FF                           A:52 X:FF Y:F8 P:67 SP:FB PPU: 53,215 CYC:24463\n$F4AE:A9 29     LDA #$29                           A:52 X:FF Y:FF P:E5 SP:FB PPU: 59,215 CYC:24465\n$F4B0:8D 47 06  STA $0647 = #$52                     A:29 X:FF Y:FF P:65 SP:FB PPU: 65,215 CYC:24467\n$F4B3:20 2E FB  JSR $FB2E                          A:29 X:FF Y:FF P:65 SP:FB PPU: 77,215 CYC:24471\n$FB2E:B8        CLV                                A:29 X:FF Y:FF P:65 SP:F9 PPU: 95,215 CYC:24477\n$FB2F:18        CLC                                A:29 X:FF Y:FF P:25 SP:F9 PPU:101,215 CYC:24479\n$FB30:A9 42     LDA #$42                           A:29 X:FF Y:FF P:nvUbdIzc SP:F9 PPU:107,215 CYC:24481\n$FB32:60        RTS                                A:42 X:FF Y:FF P:nvUbdIzc SP:F9 PPU:113,215 CYC:24483\n$F4B6:5B 48 05 *SRE $0548,Y @ 0647 = #$29            A:42 X:FF Y:FF P:nvUbdIzc SP:FB PPU:131,215 CYC:24489\n$F4B9:EA        NOP                                A:56 X:FF Y:FF P:25 SP:FB PPU:152,215 CYC:24496\n$F4BA:EA        NOP                                A:56 X:FF Y:FF P:25 SP:FB PPU:158,215 CYC:24498\n$F4BB:08        PHP                                A:56 X:FF Y:FF P:25 SP:FB PPU:164,215 CYC:24500\n$F4BC:48        PHA                                A:56 X:FF Y:FF P:25 SP:FA PPU:173,215 CYC:24503\n$F4BD:A0 F9     LDY #$F9                           A:56 X:FF Y:FF P:25 SP:F9 PPU:182,215 CYC:24506\n$F4BF:68        PLA                                A:56 X:FF Y:F9 P:A5 SP:F9 PPU:188,215 CYC:24508\n$F4C0:28        PLP                                A:56 X:FF Y:F9 P:25 SP:FA PPU:200,215 CYC:24512\n$F4C1:20 33 FB  JSR $FB33                          A:56 X:FF Y:F9 P:25 SP:FB PPU:212,215 CYC:24516\n$FB33:70 40     BVS $FB75                          A:56 X:FF Y:F9 P:25 SP:F9 PPU:230,215 CYC:24522\n$FB35:F0 3E     BEQ $FB75                          A:56 X:FF Y:F9 P:25 SP:F9 PPU:236,215 CYC:24524\n$FB37:30 3C     BMI $FB75                          A:56 X:FF Y:F9 P:25 SP:F9 PPU:242,215 CYC:24526\n$FB39:90 3A     BCC $FB75                          A:56 X:FF Y:F9 P:25 SP:F9 PPU:248,215 CYC:24528\n$FB3B:C9 56     CMP #$56                           A:56 X:FF Y:F9 P:25 SP:F9 PPU:254,215 CYC:24530\n$FB3D:D0 36     BNE $FB75                          A:56 X:FF Y:F9 P:nvUbdIZC SP:F9 PPU:260,215 CYC:24532\n$FB3F:60        RTS                                A:56 X:FF Y:F9 P:nvUbdIZC SP:F9 PPU:266,215 CYC:24534\n$F4C4:AD 47 06  LDA $0647 = #$14                     A:56 X:FF Y:F9 P:nvUbdIZC SP:FB PPU:284,215 CYC:24540\n$F4C7:C9 14     CMP #$14                           A:14 X:FF Y:F9 P:25 SP:FB PPU:296,215 CYC:24544\n$F4C9:F0 02     BEQ $F4CD                          A:14 X:FF Y:F9 P:nvUbdIZC SP:FB PPU:302,215 CYC:24546\n$F4CD:A0 FF     LDY #$FF                           A:14 X:FF Y:F9 P:nvUbdIZC SP:FB PPU:311,215 CYC:24549\n$F4CF:A9 37     LDA #$37                           A:14 X:FF Y:FF P:A5 SP:FB PPU:317,215 CYC:24551\n$F4D1:8D 47 06  STA $0647 = #$14                     A:37 X:FF Y:FF P:25 SP:FB PPU:323,215 CYC:24553\n$F4D4:20 40 FB  JSR $FB40                          A:37 X:FF Y:FF P:25 SP:FB PPU:335,215 CYC:24557\n$FB40:24 01     BIT $01 = #$FF                       A:37 X:FF Y:FF P:25 SP:F9 PPU: 12,216 CYC:24563\n$FB42:38        SEC                                A:37 X:FF Y:FF P:E5 SP:F9 PPU: 21,216 CYC:24566\n$FB43:A9 75     LDA #$75                           A:37 X:FF Y:FF P:E5 SP:F9 PPU: 27,216 CYC:24568\n$FB45:60        RTS                                A:75 X:FF Y:FF P:65 SP:F9 PPU: 33,216 CYC:24570\n$F4D7:5B 48 05 *SRE $0548,Y @ 0647 = #$37            A:75 X:FF Y:FF P:65 SP:FB PPU: 51,216 CYC:24576\n$F4DA:EA        NOP                                A:6E X:FF Y:FF P:65 SP:FB PPU: 72,216 CYC:24583\n$F4DB:EA        NOP                                A:6E X:FF Y:FF P:65 SP:FB PPU: 78,216 CYC:24585\n$F4DC:08        PHP                                A:6E X:FF Y:FF P:65 SP:FB PPU: 84,216 CYC:24587\n$F4DD:48        PHA                                A:6E X:FF Y:FF P:65 SP:FA PPU: 93,216 CYC:24590\n$F4DE:A0 FA     LDY #$FA                           A:6E X:FF Y:FF P:65 SP:F9 PPU:102,216 CYC:24593\n$F4E0:68        PLA                                A:6E X:FF Y:FA P:E5 SP:F9 PPU:108,216 CYC:24595\n$F4E1:28        PLP                                A:6E X:FF Y:FA P:65 SP:FA PPU:120,216 CYC:24599\n$F4E2:20 46 FB  JSR $FB46                          A:6E X:FF Y:FA P:65 SP:FB PPU:132,216 CYC:24603\n$FB46:50 2D     BVC $FB75                          A:6E X:FF Y:FA P:65 SP:F9 PPU:150,216 CYC:24609\n$FB48:F0 2B     BEQ $FB75                          A:6E X:FF Y:FA P:65 SP:F9 PPU:156,216 CYC:24611\n$FB4A:30 29     BMI $FB75                          A:6E X:FF Y:FA P:65 SP:F9 PPU:162,216 CYC:24613\n$FB4C:90 27     BCC $FB75                          A:6E X:FF Y:FA P:65 SP:F9 PPU:168,216 CYC:24615\n$FB4E:C9 6E     CMP #$6E                           A:6E X:FF Y:FA P:65 SP:F9 PPU:174,216 CYC:24617\n$FB50:D0 23     BNE $FB75                          A:6E X:FF Y:FA P:67 SP:F9 PPU:180,216 CYC:24619\n$FB52:60        RTS                                A:6E X:FF Y:FA P:67 SP:F9 PPU:186,216 CYC:24621\n$F4E5:AD 47 06  LDA $0647 = #$1B                     A:6E X:FF Y:FA P:67 SP:FB PPU:204,216 CYC:24627\n$F4E8:C9 1B     CMP #$1B                           A:1B X:FF Y:FA P:65 SP:FB PPU:216,216 CYC:24631\n$F4EA:F0 02     BEQ $F4EE                          A:1B X:FF Y:FA P:67 SP:FB PPU:222,216 CYC:24633\n$F4EE:A0 FB     LDY #$FB                           A:1B X:FF Y:FA P:67 SP:FB PPU:231,216 CYC:24636\n$F4F0:A2 FF     LDX #$FF                           A:1B X:FF Y:FB P:E5 SP:FB PPU:237,216 CYC:24638\n$F4F2:A9 A5     LDA #$A5                           A:1B X:FF Y:FB P:E5 SP:FB PPU:243,216 CYC:24640\n$F4F4:8D 47 06  STA $0647 = #$1B                     A:A5 X:FF Y:FB P:E5 SP:FB PPU:249,216 CYC:24642\n$F4F7:20 1D FB  JSR $FB1D                          A:A5 X:FF Y:FB P:E5 SP:FB PPU:261,216 CYC:24646\n$FB1D:24 01     BIT $01 = #$FF                       A:A5 X:FF Y:FB P:E5 SP:F9 PPU:279,216 CYC:24652\n$FB1F:18        CLC                                A:A5 X:FF Y:FB P:E5 SP:F9 PPU:288,216 CYC:24655\n$FB20:A9 B3     LDA #$B3                           A:A5 X:FF Y:FB P:NVUbdIzc SP:F9 PPU:294,216 CYC:24657\n$FB22:60        RTS                                A:B3 X:FF Y:FB P:NVUbdIzc SP:F9 PPU:300,216 CYC:24659\n$F4FA:5F 48 05 *SRE $0548,X @ 0647 = #$A5            A:B3 X:FF Y:FB P:NVUbdIzc SP:FB PPU:318,216 CYC:24665\n$F4FD:EA        NOP                                A:E1 X:FF Y:FB P:E5 SP:FB PPU:339,216 CYC:24672\n$F4FE:EA        NOP                                A:E1 X:FF Y:FB P:E5 SP:FB PPU:  4,217 CYC:24674\n$F4FF:EA        NOP                                A:E1 X:FF Y:FB P:E5 SP:FB PPU: 10,217 CYC:24676\n$F500:EA        NOP                                A:E1 X:FF Y:FB P:E5 SP:FB PPU: 16,217 CYC:24678\n$F501:20 23 FB  JSR $FB23                          A:E1 X:FF Y:FB P:E5 SP:FB PPU: 22,217 CYC:24680\n$FB23:50 50     BVC $FB75                          A:E1 X:FF Y:FB P:E5 SP:F9 PPU: 40,217 CYC:24686\n$FB25:90 4E     BCC $FB75                          A:E1 X:FF Y:FB P:E5 SP:F9 PPU: 46,217 CYC:24688\n$FB27:10 4C     BPL $FB75                          A:E1 X:FF Y:FB P:E5 SP:F9 PPU: 52,217 CYC:24690\n$FB29:C9 E1     CMP #$E1                           A:E1 X:FF Y:FB P:E5 SP:F9 PPU: 58,217 CYC:24692\n$FB2B:D0 48     BNE $FB75                          A:E1 X:FF Y:FB P:67 SP:F9 PPU: 64,217 CYC:24694\n$FB2D:60        RTS                                A:E1 X:FF Y:FB P:67 SP:F9 PPU: 70,217 CYC:24696\n$F504:AD 47 06  LDA $0647 = #$52                     A:E1 X:FF Y:FB P:67 SP:FB PPU: 88,217 CYC:24702\n$F507:C9 52     CMP #$52                           A:52 X:FF Y:FB P:65 SP:FB PPU:100,217 CYC:24706\n$F509:F0 02     BEQ $F50D                          A:52 X:FF Y:FB P:67 SP:FB PPU:106,217 CYC:24708\n$F50D:C8        INY                                A:52 X:FF Y:FB P:67 SP:FB PPU:115,217 CYC:24711\n$F50E:A9 29     LDA #$29                           A:52 X:FF Y:FC P:E5 SP:FB PPU:121,217 CYC:24713\n$F510:8D 47 06  STA $0647 = #$52                     A:29 X:FF Y:FC P:65 SP:FB PPU:127,217 CYC:24715\n$F513:20 2E FB  JSR $FB2E                          A:29 X:FF Y:FC P:65 SP:FB PPU:139,217 CYC:24719\n$FB2E:B8        CLV                                A:29 X:FF Y:FC P:65 SP:F9 PPU:157,217 CYC:24725\n$FB2F:18        CLC                                A:29 X:FF Y:FC P:25 SP:F9 PPU:163,217 CYC:24727\n$FB30:A9 42     LDA #$42                           A:29 X:FF Y:FC P:nvUbdIzc SP:F9 PPU:169,217 CYC:24729\n$FB32:60        RTS                                A:42 X:FF Y:FC P:nvUbdIzc SP:F9 PPU:175,217 CYC:24731\n$F516:5F 48 05 *SRE $0548,X @ 0647 = #$29            A:42 X:FF Y:FC P:nvUbdIzc SP:FB PPU:193,217 CYC:24737\n$F519:EA        NOP                                A:56 X:FF Y:FC P:25 SP:FB PPU:214,217 CYC:24744\n$F51A:EA        NOP                                A:56 X:FF Y:FC P:25 SP:FB PPU:220,217 CYC:24746\n$F51B:EA        NOP                                A:56 X:FF Y:FC P:25 SP:FB PPU:226,217 CYC:24748\n$F51C:EA        NOP                                A:56 X:FF Y:FC P:25 SP:FB PPU:232,217 CYC:24750\n$F51D:20 33 FB  JSR $FB33                          A:56 X:FF Y:FC P:25 SP:FB PPU:238,217 CYC:24752\n$FB33:70 40     BVS $FB75                          A:56 X:FF Y:FC P:25 SP:F9 PPU:256,217 CYC:24758\n$FB35:F0 3E     BEQ $FB75                          A:56 X:FF Y:FC P:25 SP:F9 PPU:262,217 CYC:24760\n$FB37:30 3C     BMI $FB75                          A:56 X:FF Y:FC P:25 SP:F9 PPU:268,217 CYC:24762\n$FB39:90 3A     BCC $FB75                          A:56 X:FF Y:FC P:25 SP:F9 PPU:274,217 CYC:24764\n$FB3B:C9 56     CMP #$56                           A:56 X:FF Y:FC P:25 SP:F9 PPU:280,217 CYC:24766\n$FB3D:D0 36     BNE $FB75                          A:56 X:FF Y:FC P:nvUbdIZC SP:F9 PPU:286,217 CYC:24768\n$FB3F:60        RTS                                A:56 X:FF Y:FC P:nvUbdIZC SP:F9 PPU:292,217 CYC:24770\n$F520:AD 47 06  LDA $0647 = #$14                     A:56 X:FF Y:FC P:nvUbdIZC SP:FB PPU:310,217 CYC:24776\n$F523:C9 14     CMP #$14                           A:14 X:FF Y:FC P:25 SP:FB PPU:322,217 CYC:24780\n$F525:F0 02     BEQ $F529                          A:14 X:FF Y:FC P:nvUbdIZC SP:FB PPU:328,217 CYC:24782\n$F529:C8        INY                                A:14 X:FF Y:FC P:nvUbdIZC SP:FB PPU:337,217 CYC:24785\n$F52A:A9 37     LDA #$37                           A:14 X:FF Y:FD P:A5 SP:FB PPU:  2,218 CYC:24787\n$F52C:8D 47 06  STA $0647 = #$14                     A:37 X:FF Y:FD P:25 SP:FB PPU:  8,218 CYC:24789\n$F52F:20 40 FB  JSR $FB40                          A:37 X:FF Y:FD P:25 SP:FB PPU: 20,218 CYC:24793\n$FB40:24 01     BIT $01 = #$FF                       A:37 X:FF Y:FD P:25 SP:F9 PPU: 38,218 CYC:24799\n$FB42:38        SEC                                A:37 X:FF Y:FD P:E5 SP:F9 PPU: 47,218 CYC:24802\n$FB43:A9 75     LDA #$75                           A:37 X:FF Y:FD P:E5 SP:F9 PPU: 53,218 CYC:24804\n$FB45:60        RTS                                A:75 X:FF Y:FD P:65 SP:F9 PPU: 59,218 CYC:24806\n$F532:5F 48 05 *SRE $0548,X @ 0647 = #$37            A:75 X:FF Y:FD P:65 SP:FB PPU: 77,218 CYC:24812\n$F535:EA        NOP                                A:6E X:FF Y:FD P:65 SP:FB PPU: 98,218 CYC:24819\n$F536:EA        NOP                                A:6E X:FF Y:FD P:65 SP:FB PPU:104,218 CYC:24821\n$F537:EA        NOP                                A:6E X:FF Y:FD P:65 SP:FB PPU:110,218 CYC:24823\n$F538:EA        NOP                                A:6E X:FF Y:FD P:65 SP:FB PPU:116,218 CYC:24825\n$F539:20 46 FB  JSR $FB46                          A:6E X:FF Y:FD P:65 SP:FB PPU:122,218 CYC:24827\n$FB46:50 2D     BVC $FB75                          A:6E X:FF Y:FD P:65 SP:F9 PPU:140,218 CYC:24833\n$FB48:F0 2B     BEQ $FB75                          A:6E X:FF Y:FD P:65 SP:F9 PPU:146,218 CYC:24835\n$FB4A:30 29     BMI $FB75                          A:6E X:FF Y:FD P:65 SP:F9 PPU:152,218 CYC:24837\n$FB4C:90 27     BCC $FB75                          A:6E X:FF Y:FD P:65 SP:F9 PPU:158,218 CYC:24839\n$FB4E:C9 6E     CMP #$6E                           A:6E X:FF Y:FD P:65 SP:F9 PPU:164,218 CYC:24841\n$FB50:D0 23     BNE $FB75                          A:6E X:FF Y:FD P:67 SP:F9 PPU:170,218 CYC:24843\n$FB52:60        RTS                                A:6E X:FF Y:FD P:67 SP:F9 PPU:176,218 CYC:24845\n$F53C:AD 47 06  LDA $0647 = #$1B                     A:6E X:FF Y:FD P:67 SP:FB PPU:194,218 CYC:24851\n$F53F:C9 1B     CMP #$1B                           A:1B X:FF Y:FD P:65 SP:FB PPU:206,218 CYC:24855\n$F541:F0 02     BEQ $F545                          A:1B X:FF Y:FD P:67 SP:FB PPU:212,218 CYC:24857\n$F545:60        RTS                                A:1B X:FF Y:FD P:67 SP:FB PPU:221,218 CYC:24860\n$C64A:A5 00     LDA $00 = #$00                       A:1B X:FF Y:FD P:67 SP:FD PPU:239,218 CYC:24866\n$C64C:85 11     STA $11 = #$00                       A:00 X:FF Y:FD P:67 SP:FD PPU:248,218 CYC:24869\n$C64E:A9 00     LDA #$00                           A:00 X:FF Y:FD P:67 SP:FD PPU:257,218 CYC:24872\n$C650:85 00     STA $00 = #$00                       A:00 X:FF Y:FD P:67 SP:FD PPU:263,218 CYC:24874\n$C652:20 46 F5  JSR $F546                          A:00 X:FF Y:FD P:67 SP:FD PPU:272,218 CYC:24877\n$F546:A9 FF     LDA #$FF                           A:00 X:FF Y:FD P:67 SP:FB PPU:290,218 CYC:24883\n$F548:85 01     STA $01 = #$FF                       A:FF X:FF Y:FD P:E5 SP:FB PPU:296,218 CYC:24885\n$F54A:A0 01     LDY #$01                           A:FF X:FF Y:FD P:E5 SP:FB PPU:305,218 CYC:24888\n$F54C:A2 02     LDX #$02                           A:FF X:FF Y:01 P:65 SP:FB PPU:311,218 CYC:24890\n$F54E:A9 47     LDA #$47                           A:FF X:02 Y:01 P:65 SP:FB PPU:317,218 CYC:24892\n$F550:85 47     STA $47 = #$1B                       A:47 X:02 Y:01 P:65 SP:FB PPU:323,218 CYC:24894\n$F552:A9 06     LDA #$06                           A:47 X:02 Y:01 P:65 SP:FB PPU:332,218 CYC:24897\n$F554:85 48     STA $48 = #$06                       A:06 X:02 Y:01 P:65 SP:FB PPU:338,218 CYC:24899\n$F556:A9 A5     LDA #$A5                           A:06 X:02 Y:01 P:65 SP:FB PPU:  6,219 CYC:24902\n$F558:8D 47 06  STA $0647 = #$1B                     A:A5 X:02 Y:01 P:E5 SP:FB PPU: 12,219 CYC:24904\n$F55B:20 E9 FA  JSR $FAE9                          A:A5 X:02 Y:01 P:E5 SP:FB PPU: 24,219 CYC:24908\n$FAE9:24 01     BIT $01 = #$FF                       A:A5 X:02 Y:01 P:E5 SP:F9 PPU: 42,219 CYC:24914\n$FAEB:18        CLC                                A:A5 X:02 Y:01 P:E5 SP:F9 PPU: 51,219 CYC:24917\n$FAEC:A9 B2     LDA #$B2                           A:A5 X:02 Y:01 P:NVUbdIzc SP:F9 PPU: 57,219 CYC:24919\n$FAEE:60        RTS                                A:B2 X:02 Y:01 P:NVUbdIzc SP:F9 PPU: 63,219 CYC:24921\n$F55E:63 45    *RRA ($45,X) @ 47 = #$0647 = A5       A:B2 X:02 Y:01 P:NVUbdIzc SP:FB PPU: 81,219 CYC:24927\n$F560:EA        NOP                                A:05 X:02 Y:01 P:25 SP:FB PPU:105,219 CYC:24935\n$F561:EA        NOP                                A:05 X:02 Y:01 P:25 SP:FB PPU:111,219 CYC:24937\n$F562:EA        NOP                                A:05 X:02 Y:01 P:25 SP:FB PPU:117,219 CYC:24939\n$F563:EA        NOP                                A:05 X:02 Y:01 P:25 SP:FB PPU:123,219 CYC:24941\n$F564:20 EF FA  JSR $FAEF                          A:05 X:02 Y:01 P:25 SP:FB PPU:129,219 CYC:24943\n$FAEF:70 2A     BVS $FB1B                          A:05 X:02 Y:01 P:25 SP:F9 PPU:147,219 CYC:24949\n$FAF1:90 28     BCC $FB1B                          A:05 X:02 Y:01 P:25 SP:F9 PPU:153,219 CYC:24951\n$FAF3:30 26     BMI $FB1B                          A:05 X:02 Y:01 P:25 SP:F9 PPU:159,219 CYC:24953\n$FAF5:C9 05     CMP #$05                           A:05 X:02 Y:01 P:25 SP:F9 PPU:165,219 CYC:24955\n$FAF7:D0 22     BNE $FB1B                          A:05 X:02 Y:01 P:nvUbdIZC SP:F9 PPU:171,219 CYC:24957\n$FAF9:60        RTS                                A:05 X:02 Y:01 P:nvUbdIZC SP:F9 PPU:177,219 CYC:24959\n$F567:AD 47 06  LDA $0647 = #$52                     A:05 X:02 Y:01 P:nvUbdIZC SP:FB PPU:195,219 CYC:24965\n$F56A:C9 52     CMP #$52                           A:52 X:02 Y:01 P:25 SP:FB PPU:207,219 CYC:24969\n$F56C:F0 02     BEQ $F570                          A:52 X:02 Y:01 P:nvUbdIZC SP:FB PPU:213,219 CYC:24971\n$F570:C8        INY                                A:52 X:02 Y:01 P:nvUbdIZC SP:FB PPU:222,219 CYC:24974\n$F571:A9 29     LDA #$29                           A:52 X:02 Y:02 P:25 SP:FB PPU:228,219 CYC:24976\n$F573:8D 47 06  STA $0647 = #$52                     A:29 X:02 Y:02 P:25 SP:FB PPU:234,219 CYC:24978\n$F576:20 FA FA  JSR $FAFA                          A:29 X:02 Y:02 P:25 SP:FB PPU:246,219 CYC:24982\n$FAFA:B8        CLV                                A:29 X:02 Y:02 P:25 SP:F9 PPU:264,219 CYC:24988\n$FAFB:18        CLC                                A:29 X:02 Y:02 P:25 SP:F9 PPU:270,219 CYC:24990\n$FAFC:A9 42     LDA #$42                           A:29 X:02 Y:02 P:nvUbdIzc SP:F9 PPU:276,219 CYC:24992\n$FAFE:60        RTS                                A:42 X:02 Y:02 P:nvUbdIzc SP:F9 PPU:282,219 CYC:24994\n$F579:63 45    *RRA ($45,X) @ 47 = #$0647 = 29       A:42 X:02 Y:02 P:nvUbdIzc SP:FB PPU:300,219 CYC:25000\n$F57B:EA        NOP                                A:57 X:02 Y:02 P:nvUbdIzc SP:FB PPU:324,219 CYC:25008\n$F57C:EA        NOP                                A:57 X:02 Y:02 P:nvUbdIzc SP:FB PPU:330,219 CYC:25010\n$F57D:EA        NOP                                A:57 X:02 Y:02 P:nvUbdIzc SP:FB PPU:336,219 CYC:25012\n$F57E:EA        NOP                                A:57 X:02 Y:02 P:nvUbdIzc SP:FB PPU:  1,220 CYC:25014\n$F57F:20 FF FA  JSR $FAFF                          A:57 X:02 Y:02 P:nvUbdIzc SP:FB PPU:  7,220 CYC:25016\n$FAFF:70 1A     BVS $FB1B                          A:57 X:02 Y:02 P:nvUbdIzc SP:F9 PPU: 25,220 CYC:25022\n$FB01:30 18     BMI $FB1B                          A:57 X:02 Y:02 P:nvUbdIzc SP:F9 PPU: 31,220 CYC:25024\n$FB03:B0 16     BCS $FB1B                          A:57 X:02 Y:02 P:nvUbdIzc SP:F9 PPU: 37,220 CYC:25026\n$FB05:C9 57     CMP #$57                           A:57 X:02 Y:02 P:nvUbdIzc SP:F9 PPU: 43,220 CYC:25028\n$FB07:D0 12     BNE $FB1B                          A:57 X:02 Y:02 P:nvUbdIZC SP:F9 PPU: 49,220 CYC:25030\n$FB09:60        RTS                                A:57 X:02 Y:02 P:nvUbdIZC SP:F9 PPU: 55,220 CYC:25032\n$F582:AD 47 06  LDA $0647 = #$14                     A:57 X:02 Y:02 P:nvUbdIZC SP:FB PPU: 73,220 CYC:25038\n$F585:C9 14     CMP #$14                           A:14 X:02 Y:02 P:25 SP:FB PPU: 85,220 CYC:25042\n$F587:F0 02     BEQ $F58B                          A:14 X:02 Y:02 P:nvUbdIZC SP:FB PPU: 91,220 CYC:25044\n$F58B:C8        INY                                A:14 X:02 Y:02 P:nvUbdIZC SP:FB PPU:100,220 CYC:25047\n$F58C:A9 37     LDA #$37                           A:14 X:02 Y:03 P:25 SP:FB PPU:106,220 CYC:25049\n$F58E:8D 47 06  STA $0647 = #$14                     A:37 X:02 Y:03 P:25 SP:FB PPU:112,220 CYC:25051\n$F591:20 0A FB  JSR $FB0A                          A:37 X:02 Y:03 P:25 SP:FB PPU:124,220 CYC:25055\n$FB0A:24 01     BIT $01 = #$FF                       A:37 X:02 Y:03 P:25 SP:F9 PPU:142,220 CYC:25061\n$FB0C:38        SEC                                A:37 X:02 Y:03 P:E5 SP:F9 PPU:151,220 CYC:25064\n$FB0D:A9 75     LDA #$75                           A:37 X:02 Y:03 P:E5 SP:F9 PPU:157,220 CYC:25066\n$FB0F:60        RTS                                A:75 X:02 Y:03 P:65 SP:F9 PPU:163,220 CYC:25068\n$F594:63 45    *RRA ($45,X) @ 47 = #$0647 = 37       A:75 X:02 Y:03 P:65 SP:FB PPU:181,220 CYC:25074\n$F596:EA        NOP                                A:11 X:02 Y:03 P:25 SP:FB PPU:205,220 CYC:25082\n$F597:EA        NOP                                A:11 X:02 Y:03 P:25 SP:FB PPU:211,220 CYC:25084\n$F598:EA        NOP                                A:11 X:02 Y:03 P:25 SP:FB PPU:217,220 CYC:25086\n$F599:EA        NOP                                A:11 X:02 Y:03 P:25 SP:FB PPU:223,220 CYC:25088\n$F59A:20 10 FB  JSR $FB10                          A:11 X:02 Y:03 P:25 SP:FB PPU:229,220 CYC:25090\n$FB10:70 09     BVS $FB1B                          A:11 X:02 Y:03 P:25 SP:F9 PPU:247,220 CYC:25096\n$FB12:30 07     BMI $FB1B                          A:11 X:02 Y:03 P:25 SP:F9 PPU:253,220 CYC:25098\n$FB14:90 05     BCC $FB1B                          A:11 X:02 Y:03 P:25 SP:F9 PPU:259,220 CYC:25100\n$FB16:C9 11     CMP #$11                           A:11 X:02 Y:03 P:25 SP:F9 PPU:265,220 CYC:25102\n$FB18:D0 01     BNE $FB1B                          A:11 X:02 Y:03 P:nvUbdIZC SP:F9 PPU:271,220 CYC:25104\n$FB1A:60        RTS                                A:11 X:02 Y:03 P:nvUbdIZC SP:F9 PPU:277,220 CYC:25106\n$F59D:AD 47 06  LDA $0647 = #$9B                     A:11 X:02 Y:03 P:nvUbdIZC SP:FB PPU:295,220 CYC:25112\n$F5A0:C9 9B     CMP #$9B                           A:9B X:02 Y:03 P:A5 SP:FB PPU:307,220 CYC:25116\n$F5A2:F0 02     BEQ $F5A6                          A:9B X:02 Y:03 P:nvUbdIZC SP:FB PPU:313,220 CYC:25118\n$F5A6:C8        INY                                A:9B X:02 Y:03 P:nvUbdIZC SP:FB PPU:322,220 CYC:25121\n$F5A7:A9 A5     LDA #$A5                           A:9B X:02 Y:04 P:25 SP:FB PPU:328,220 CYC:25123\n$F5A9:85 47     STA $47 = #$47                       A:A5 X:02 Y:04 P:A5 SP:FB PPU:334,220 CYC:25125\n$F5AB:20 E9 FA  JSR $FAE9                          A:A5 X:02 Y:04 P:A5 SP:FB PPU:  2,221 CYC:25128\n$FAE9:24 01     BIT $01 = #$FF                       A:A5 X:02 Y:04 P:A5 SP:F9 PPU: 20,221 CYC:25134\n$FAEB:18        CLC                                A:A5 X:02 Y:04 P:E5 SP:F9 PPU: 29,221 CYC:25137\n$FAEC:A9 B2     LDA #$B2                           A:A5 X:02 Y:04 P:NVUbdIzc SP:F9 PPU: 35,221 CYC:25139\n$FAEE:60        RTS                                A:B2 X:02 Y:04 P:NVUbdIzc SP:F9 PPU: 41,221 CYC:25141\n$F5AE:67 47    *RRA $47 = #$A5                       A:B2 X:02 Y:04 P:NVUbdIzc SP:FB PPU: 59,221 CYC:25147\n$F5B0:EA        NOP                                A:05 X:02 Y:04 P:25 SP:FB PPU: 74,221 CYC:25152\n$F5B1:EA        NOP                                A:05 X:02 Y:04 P:25 SP:FB PPU: 80,221 CYC:25154\n$F5B2:EA        NOP                                A:05 X:02 Y:04 P:25 SP:FB PPU: 86,221 CYC:25156\n$F5B3:EA        NOP                                A:05 X:02 Y:04 P:25 SP:FB PPU: 92,221 CYC:25158\n$F5B4:20 EF FA  JSR $FAEF                          A:05 X:02 Y:04 P:25 SP:FB PPU: 98,221 CYC:25160\n$FAEF:70 2A     BVS $FB1B                          A:05 X:02 Y:04 P:25 SP:F9 PPU:116,221 CYC:25166\n$FAF1:90 28     BCC $FB1B                          A:05 X:02 Y:04 P:25 SP:F9 PPU:122,221 CYC:25168\n$FAF3:30 26     BMI $FB1B                          A:05 X:02 Y:04 P:25 SP:F9 PPU:128,221 CYC:25170\n$FAF5:C9 05     CMP #$05                           A:05 X:02 Y:04 P:25 SP:F9 PPU:134,221 CYC:25172\n$FAF7:D0 22     BNE $FB1B                          A:05 X:02 Y:04 P:nvUbdIZC SP:F9 PPU:140,221 CYC:25174\n$FAF9:60        RTS                                A:05 X:02 Y:04 P:nvUbdIZC SP:F9 PPU:146,221 CYC:25176\n$F5B7:A5 47     LDA $47 = #$52                       A:05 X:02 Y:04 P:nvUbdIZC SP:FB PPU:164,221 CYC:25182\n$F5B9:C9 52     CMP #$52                           A:52 X:02 Y:04 P:25 SP:FB PPU:173,221 CYC:25185\n$F5BB:F0 02     BEQ $F5BF                          A:52 X:02 Y:04 P:nvUbdIZC SP:FB PPU:179,221 CYC:25187\n$F5BF:C8        INY                                A:52 X:02 Y:04 P:nvUbdIZC SP:FB PPU:188,221 CYC:25190\n$F5C0:A9 29     LDA #$29                           A:52 X:02 Y:05 P:25 SP:FB PPU:194,221 CYC:25192\n$F5C2:85 47     STA $47 = #$52                       A:29 X:02 Y:05 P:25 SP:FB PPU:200,221 CYC:25194\n$F5C4:20 FA FA  JSR $FAFA                          A:29 X:02 Y:05 P:25 SP:FB PPU:209,221 CYC:25197\n$FAFA:B8        CLV                                A:29 X:02 Y:05 P:25 SP:F9 PPU:227,221 CYC:25203\n$FAFB:18        CLC                                A:29 X:02 Y:05 P:25 SP:F9 PPU:233,221 CYC:25205\n$FAFC:A9 42     LDA #$42                           A:29 X:02 Y:05 P:nvUbdIzc SP:F9 PPU:239,221 CYC:25207\n$FAFE:60        RTS                                A:42 X:02 Y:05 P:nvUbdIzc SP:F9 PPU:245,221 CYC:25209\n$F5C7:67 47    *RRA $47 = #$29                       A:42 X:02 Y:05 P:nvUbdIzc SP:FB PPU:263,221 CYC:25215\n$F5C9:EA        NOP                                A:57 X:02 Y:05 P:nvUbdIzc SP:FB PPU:278,221 CYC:25220\n$F5CA:EA        NOP                                A:57 X:02 Y:05 P:nvUbdIzc SP:FB PPU:284,221 CYC:25222\n$F5CB:EA        NOP                                A:57 X:02 Y:05 P:nvUbdIzc SP:FB PPU:290,221 CYC:25224\n$F5CC:EA        NOP                                A:57 X:02 Y:05 P:nvUbdIzc SP:FB PPU:296,221 CYC:25226\n$F5CD:20 FF FA  JSR $FAFF                          A:57 X:02 Y:05 P:nvUbdIzc SP:FB PPU:302,221 CYC:25228\n$FAFF:70 1A     BVS $FB1B                          A:57 X:02 Y:05 P:nvUbdIzc SP:F9 PPU:320,221 CYC:25234\n$FB01:30 18     BMI $FB1B                          A:57 X:02 Y:05 P:nvUbdIzc SP:F9 PPU:326,221 CYC:25236\n$FB03:B0 16     BCS $FB1B                          A:57 X:02 Y:05 P:nvUbdIzc SP:F9 PPU:332,221 CYC:25238\n$FB05:C9 57     CMP #$57                           A:57 X:02 Y:05 P:nvUbdIzc SP:F9 PPU:338,221 CYC:25240\n$FB07:D0 12     BNE $FB1B                          A:57 X:02 Y:05 P:nvUbdIZC SP:F9 PPU:  3,222 CYC:25242\n$FB09:60        RTS                                A:57 X:02 Y:05 P:nvUbdIZC SP:F9 PPU:  9,222 CYC:25244\n$F5D0:A5 47     LDA $47 = #$14                       A:57 X:02 Y:05 P:nvUbdIZC SP:FB PPU: 27,222 CYC:25250\n$F5D2:C9 14     CMP #$14                           A:14 X:02 Y:05 P:25 SP:FB PPU: 36,222 CYC:25253\n$F5D4:F0 02     BEQ $F5D8                          A:14 X:02 Y:05 P:nvUbdIZC SP:FB PPU: 42,222 CYC:25255\n$F5D8:C8        INY                                A:14 X:02 Y:05 P:nvUbdIZC SP:FB PPU: 51,222 CYC:25258\n$F5D9:A9 37     LDA #$37                           A:14 X:02 Y:06 P:25 SP:FB PPU: 57,222 CYC:25260\n$F5DB:85 47     STA $47 = #$14                       A:37 X:02 Y:06 P:25 SP:FB PPU: 63,222 CYC:25262\n$F5DD:20 0A FB  JSR $FB0A                          A:37 X:02 Y:06 P:25 SP:FB PPU: 72,222 CYC:25265\n$FB0A:24 01     BIT $01 = #$FF                       A:37 X:02 Y:06 P:25 SP:F9 PPU: 90,222 CYC:25271\n$FB0C:38        SEC                                A:37 X:02 Y:06 P:E5 SP:F9 PPU: 99,222 CYC:25274\n$FB0D:A9 75     LDA #$75                           A:37 X:02 Y:06 P:E5 SP:F9 PPU:105,222 CYC:25276\n$FB0F:60        RTS                                A:75 X:02 Y:06 P:65 SP:F9 PPU:111,222 CYC:25278\n$F5E0:67 47    *RRA $47 = #$37                       A:75 X:02 Y:06 P:65 SP:FB PPU:129,222 CYC:25284\n$F5E2:EA        NOP                                A:11 X:02 Y:06 P:25 SP:FB PPU:144,222 CYC:25289\n$F5E3:EA        NOP                                A:11 X:02 Y:06 P:25 SP:FB PPU:150,222 CYC:25291\n$F5E4:EA        NOP                                A:11 X:02 Y:06 P:25 SP:FB PPU:156,222 CYC:25293\n$F5E5:EA        NOP                                A:11 X:02 Y:06 P:25 SP:FB PPU:162,222 CYC:25295\n$F5E6:20 10 FB  JSR $FB10                          A:11 X:02 Y:06 P:25 SP:FB PPU:168,222 CYC:25297\n$FB10:70 09     BVS $FB1B                          A:11 X:02 Y:06 P:25 SP:F9 PPU:186,222 CYC:25303\n$FB12:30 07     BMI $FB1B                          A:11 X:02 Y:06 P:25 SP:F9 PPU:192,222 CYC:25305\n$FB14:90 05     BCC $FB1B                          A:11 X:02 Y:06 P:25 SP:F9 PPU:198,222 CYC:25307\n$FB16:C9 11     CMP #$11                           A:11 X:02 Y:06 P:25 SP:F9 PPU:204,222 CYC:25309\n$FB18:D0 01     BNE $FB1B                          A:11 X:02 Y:06 P:nvUbdIZC SP:F9 PPU:210,222 CYC:25311\n$FB1A:60        RTS                                A:11 X:02 Y:06 P:nvUbdIZC SP:F9 PPU:216,222 CYC:25313\n$F5E9:A5 47     LDA $47 = #$9B                       A:11 X:02 Y:06 P:nvUbdIZC SP:FB PPU:234,222 CYC:25319\n$F5EB:C9 9B     CMP #$9B                           A:9B X:02 Y:06 P:A5 SP:FB PPU:243,222 CYC:25322\n$F5ED:F0 02     BEQ $F5F1                          A:9B X:02 Y:06 P:nvUbdIZC SP:FB PPU:249,222 CYC:25324\n$F5F1:C8        INY                                A:9B X:02 Y:06 P:nvUbdIZC SP:FB PPU:258,222 CYC:25327\n$F5F2:A9 A5     LDA #$A5                           A:9B X:02 Y:07 P:25 SP:FB PPU:264,222 CYC:25329\n$F5F4:8D 47 06  STA $0647 = #$9B                     A:A5 X:02 Y:07 P:A5 SP:FB PPU:270,222 CYC:25331\n$F5F7:20 E9 FA  JSR $FAE9                          A:A5 X:02 Y:07 P:A5 SP:FB PPU:282,222 CYC:25335\n$FAE9:24 01     BIT $01 = #$FF                       A:A5 X:02 Y:07 P:A5 SP:F9 PPU:300,222 CYC:25341\n$FAEB:18        CLC                                A:A5 X:02 Y:07 P:E5 SP:F9 PPU:309,222 CYC:25344\n$FAEC:A9 B2     LDA #$B2                           A:A5 X:02 Y:07 P:NVUbdIzc SP:F9 PPU:315,222 CYC:25346\n$FAEE:60        RTS                                A:B2 X:02 Y:07 P:NVUbdIzc SP:F9 PPU:321,222 CYC:25348\n$F5FA:6F 47 06 *RRA $0647 = #$A5                     A:B2 X:02 Y:07 P:NVUbdIzc SP:FB PPU:339,222 CYC:25354\n$F5FD:EA        NOP                                A:05 X:02 Y:07 P:25 SP:FB PPU: 16,223 CYC:25360\n$F5FE:EA        NOP                                A:05 X:02 Y:07 P:25 SP:FB PPU: 22,223 CYC:25362\n$F5FF:EA        NOP                                A:05 X:02 Y:07 P:25 SP:FB PPU: 28,223 CYC:25364\n$F600:EA        NOP                                A:05 X:02 Y:07 P:25 SP:FB PPU: 34,223 CYC:25366\n$F601:20 EF FA  JSR $FAEF                          A:05 X:02 Y:07 P:25 SP:FB PPU: 40,223 CYC:25368\n$FAEF:70 2A     BVS $FB1B                          A:05 X:02 Y:07 P:25 SP:F9 PPU: 58,223 CYC:25374\n$FAF1:90 28     BCC $FB1B                          A:05 X:02 Y:07 P:25 SP:F9 PPU: 64,223 CYC:25376\n$FAF3:30 26     BMI $FB1B                          A:05 X:02 Y:07 P:25 SP:F9 PPU: 70,223 CYC:25378\n$FAF5:C9 05     CMP #$05                           A:05 X:02 Y:07 P:25 SP:F9 PPU: 76,223 CYC:25380\n$FAF7:D0 22     BNE $FB1B                          A:05 X:02 Y:07 P:nvUbdIZC SP:F9 PPU: 82,223 CYC:25382\n$FAF9:60        RTS                                A:05 X:02 Y:07 P:nvUbdIZC SP:F9 PPU: 88,223 CYC:25384\n$F604:AD 47 06  LDA $0647 = #$52                     A:05 X:02 Y:07 P:nvUbdIZC SP:FB PPU:106,223 CYC:25390\n$F607:C9 52     CMP #$52                           A:52 X:02 Y:07 P:25 SP:FB PPU:118,223 CYC:25394\n$F609:F0 02     BEQ $F60D                          A:52 X:02 Y:07 P:nvUbdIZC SP:FB PPU:124,223 CYC:25396\n$F60D:C8        INY                                A:52 X:02 Y:07 P:nvUbdIZC SP:FB PPU:133,223 CYC:25399\n$F60E:A9 29     LDA #$29                           A:52 X:02 Y:08 P:25 SP:FB PPU:139,223 CYC:25401\n$F610:8D 47 06  STA $0647 = #$52                     A:29 X:02 Y:08 P:25 SP:FB PPU:145,223 CYC:25403\n$F613:20 FA FA  JSR $FAFA                          A:29 X:02 Y:08 P:25 SP:FB PPU:157,223 CYC:25407\n$FAFA:B8        CLV                                A:29 X:02 Y:08 P:25 SP:F9 PPU:175,223 CYC:25413\n$FAFB:18        CLC                                A:29 X:02 Y:08 P:25 SP:F9 PPU:181,223 CYC:25415\n$FAFC:A9 42     LDA #$42                           A:29 X:02 Y:08 P:nvUbdIzc SP:F9 PPU:187,223 CYC:25417\n$FAFE:60        RTS                                A:42 X:02 Y:08 P:nvUbdIzc SP:F9 PPU:193,223 CYC:25419\n$F616:6F 47 06 *RRA $0647 = #$29                     A:42 X:02 Y:08 P:nvUbdIzc SP:FB PPU:211,223 CYC:25425\n$F619:EA        NOP                                A:57 X:02 Y:08 P:nvUbdIzc SP:FB PPU:229,223 CYC:25431\n$F61A:EA        NOP                                A:57 X:02 Y:08 P:nvUbdIzc SP:FB PPU:235,223 CYC:25433\n$F61B:EA        NOP                                A:57 X:02 Y:08 P:nvUbdIzc SP:FB PPU:241,223 CYC:25435\n$F61C:EA        NOP                                A:57 X:02 Y:08 P:nvUbdIzc SP:FB PPU:247,223 CYC:25437\n$F61D:20 FF FA  JSR $FAFF                          A:57 X:02 Y:08 P:nvUbdIzc SP:FB PPU:253,223 CYC:25439\n$FAFF:70 1A     BVS $FB1B                          A:57 X:02 Y:08 P:nvUbdIzc SP:F9 PPU:271,223 CYC:25445\n$FB01:30 18     BMI $FB1B                          A:57 X:02 Y:08 P:nvUbdIzc SP:F9 PPU:277,223 CYC:25447\n$FB03:B0 16     BCS $FB1B                          A:57 X:02 Y:08 P:nvUbdIzc SP:F9 PPU:283,223 CYC:25449\n$FB05:C9 57     CMP #$57                           A:57 X:02 Y:08 P:nvUbdIzc SP:F9 PPU:289,223 CYC:25451\n$FB07:D0 12     BNE $FB1B                          A:57 X:02 Y:08 P:nvUbdIZC SP:F9 PPU:295,223 CYC:25453\n$FB09:60        RTS                                A:57 X:02 Y:08 P:nvUbdIZC SP:F9 PPU:301,223 CYC:25455\n$F620:AD 47 06  LDA $0647 = #$14                     A:57 X:02 Y:08 P:nvUbdIZC SP:FB PPU:319,223 CYC:25461\n$F623:C9 14     CMP #$14                           A:14 X:02 Y:08 P:25 SP:FB PPU:331,223 CYC:25465\n$F625:F0 02     BEQ $F629                          A:14 X:02 Y:08 P:nvUbdIZC SP:FB PPU:337,223 CYC:25467\n$F629:C8        INY                                A:14 X:02 Y:08 P:nvUbdIZC SP:FB PPU:  5,224 CYC:25470\n$F62A:A9 37     LDA #$37                           A:14 X:02 Y:09 P:25 SP:FB PPU: 11,224 CYC:25472\n$F62C:8D 47 06  STA $0647 = #$14                     A:37 X:02 Y:09 P:25 SP:FB PPU: 17,224 CYC:25474\n$F62F:20 0A FB  JSR $FB0A                          A:37 X:02 Y:09 P:25 SP:FB PPU: 29,224 CYC:25478\n$FB0A:24 01     BIT $01 = #$FF                       A:37 X:02 Y:09 P:25 SP:F9 PPU: 47,224 CYC:25484\n$FB0C:38        SEC                                A:37 X:02 Y:09 P:E5 SP:F9 PPU: 56,224 CYC:25487\n$FB0D:A9 75     LDA #$75                           A:37 X:02 Y:09 P:E5 SP:F9 PPU: 62,224 CYC:25489\n$FB0F:60        RTS                                A:75 X:02 Y:09 P:65 SP:F9 PPU: 68,224 CYC:25491\n$F632:6F 47 06 *RRA $0647 = #$37                     A:75 X:02 Y:09 P:65 SP:FB PPU: 86,224 CYC:25497\n$F635:EA        NOP                                A:11 X:02 Y:09 P:25 SP:FB PPU:104,224 CYC:25503\n$F636:EA        NOP                                A:11 X:02 Y:09 P:25 SP:FB PPU:110,224 CYC:25505\n$F637:EA        NOP                                A:11 X:02 Y:09 P:25 SP:FB PPU:116,224 CYC:25507\n$F638:EA        NOP                                A:11 X:02 Y:09 P:25 SP:FB PPU:122,224 CYC:25509\n$F639:20 10 FB  JSR $FB10                          A:11 X:02 Y:09 P:25 SP:FB PPU:128,224 CYC:25511\n$FB10:70 09     BVS $FB1B                          A:11 X:02 Y:09 P:25 SP:F9 PPU:146,224 CYC:25517\n$FB12:30 07     BMI $FB1B                          A:11 X:02 Y:09 P:25 SP:F9 PPU:152,224 CYC:25519\n$FB14:90 05     BCC $FB1B                          A:11 X:02 Y:09 P:25 SP:F9 PPU:158,224 CYC:25521\n$FB16:C9 11     CMP #$11                           A:11 X:02 Y:09 P:25 SP:F9 PPU:164,224 CYC:25523\n$FB18:D0 01     BNE $FB1B                          A:11 X:02 Y:09 P:nvUbdIZC SP:F9 PPU:170,224 CYC:25525\n$FB1A:60        RTS                                A:11 X:02 Y:09 P:nvUbdIZC SP:F9 PPU:176,224 CYC:25527\n$F63C:AD 47 06  LDA $0647 = #$9B                     A:11 X:02 Y:09 P:nvUbdIZC SP:FB PPU:194,224 CYC:25533\n$F63F:C9 9B     CMP #$9B                           A:9B X:02 Y:09 P:A5 SP:FB PPU:206,224 CYC:25537\n$F641:F0 02     BEQ $F645                          A:9B X:02 Y:09 P:nvUbdIZC SP:FB PPU:212,224 CYC:25539\n$F645:A9 A5     LDA #$A5                           A:9B X:02 Y:09 P:nvUbdIZC SP:FB PPU:221,224 CYC:25542\n$F647:8D 47 06  STA $0647 = #$9B                     A:A5 X:02 Y:09 P:A5 SP:FB PPU:227,224 CYC:25544\n$F64A:A9 48     LDA #$48                           A:A5 X:02 Y:09 P:A5 SP:FB PPU:239,224 CYC:25548\n$F64C:85 45     STA $45 = #$48                       A:48 X:02 Y:09 P:25 SP:FB PPU:245,224 CYC:25550\n$F64E:A9 05     LDA #$05                           A:48 X:02 Y:09 P:25 SP:FB PPU:254,224 CYC:25553\n$F650:85 46     STA $46 = #$05                       A:05 X:02 Y:09 P:25 SP:FB PPU:260,224 CYC:25555\n$F652:A0 FF     LDY #$FF                           A:05 X:02 Y:09 P:25 SP:FB PPU:269,224 CYC:25558\n$F654:20 E9 FA  JSR $FAE9                          A:05 X:02 Y:FF P:A5 SP:FB PPU:275,224 CYC:25560\n$FAE9:24 01     BIT $01 = #$FF                       A:05 X:02 Y:FF P:A5 SP:F9 PPU:293,224 CYC:25566\n$FAEB:18        CLC                                A:05 X:02 Y:FF P:E5 SP:F9 PPU:302,224 CYC:25569\n$FAEC:A9 B2     LDA #$B2                           A:05 X:02 Y:FF P:NVUbdIzc SP:F9 PPU:308,224 CYC:25571\n$FAEE:60        RTS                                A:B2 X:02 Y:FF P:NVUbdIzc SP:F9 PPU:314,224 CYC:25573\n$F657:73 45    *RRA ($45),Y = #$0548 @ 0647 = A5     A:B2 X:02 Y:FF P:NVUbdIzc SP:FB PPU:332,224 CYC:25579\n$F659:EA        NOP                                A:05 X:02 Y:FF P:25 SP:FB PPU: 15,225 CYC:25587\n$F65A:EA        NOP                                A:05 X:02 Y:FF P:25 SP:FB PPU: 21,225 CYC:25589\n$F65B:08        PHP                                A:05 X:02 Y:FF P:25 SP:FB PPU: 27,225 CYC:25591\n$F65C:48        PHA                                A:05 X:02 Y:FF P:25 SP:FA PPU: 36,225 CYC:25594\n$F65D:A0 0A     LDY #$0A                           A:05 X:02 Y:FF P:25 SP:F9 PPU: 45,225 CYC:25597\n$F65F:68        PLA                                A:05 X:02 Y:0A P:25 SP:F9 PPU: 51,225 CYC:25599\n$F660:28        PLP                                A:05 X:02 Y:0A P:25 SP:FA PPU: 63,225 CYC:25603\n$F661:20 EF FA  JSR $FAEF                          A:05 X:02 Y:0A P:25 SP:FB PPU: 75,225 CYC:25607\n$FAEF:70 2A     BVS $FB1B                          A:05 X:02 Y:0A P:25 SP:F9 PPU: 93,225 CYC:25613\n$FAF1:90 28     BCC $FB1B                          A:05 X:02 Y:0A P:25 SP:F9 PPU: 99,225 CYC:25615\n$FAF3:30 26     BMI $FB1B                          A:05 X:02 Y:0A P:25 SP:F9 PPU:105,225 CYC:25617\n$FAF5:C9 05     CMP #$05                           A:05 X:02 Y:0A P:25 SP:F9 PPU:111,225 CYC:25619\n$FAF7:D0 22     BNE $FB1B                          A:05 X:02 Y:0A P:nvUbdIZC SP:F9 PPU:117,225 CYC:25621\n$FAF9:60        RTS                                A:05 X:02 Y:0A P:nvUbdIZC SP:F9 PPU:123,225 CYC:25623\n$F664:AD 47 06  LDA $0647 = #$52                     A:05 X:02 Y:0A P:nvUbdIZC SP:FB PPU:141,225 CYC:25629\n$F667:C9 52     CMP #$52                           A:52 X:02 Y:0A P:25 SP:FB PPU:153,225 CYC:25633\n$F669:F0 02     BEQ $F66D                          A:52 X:02 Y:0A P:nvUbdIZC SP:FB PPU:159,225 CYC:25635\n$F66D:A0 FF     LDY #$FF                           A:52 X:02 Y:0A P:nvUbdIZC SP:FB PPU:168,225 CYC:25638\n$F66F:A9 29     LDA #$29                           A:52 X:02 Y:FF P:A5 SP:FB PPU:174,225 CYC:25640\n$F671:8D 47 06  STA $0647 = #$52                     A:29 X:02 Y:FF P:25 SP:FB PPU:180,225 CYC:25642\n$F674:20 FA FA  JSR $FAFA                          A:29 X:02 Y:FF P:25 SP:FB PPU:192,225 CYC:25646\n$FAFA:B8        CLV                                A:29 X:02 Y:FF P:25 SP:F9 PPU:210,225 CYC:25652\n$FAFB:18        CLC                                A:29 X:02 Y:FF P:25 SP:F9 PPU:216,225 CYC:25654\n$FAFC:A9 42     LDA #$42                           A:29 X:02 Y:FF P:nvUbdIzc SP:F9 PPU:222,225 CYC:25656\n$FAFE:60        RTS                                A:42 X:02 Y:FF P:nvUbdIzc SP:F9 PPU:228,225 CYC:25658\n$F677:73 45    *RRA ($45),Y = #$0548 @ 0647 = 29     A:42 X:02 Y:FF P:nvUbdIzc SP:FB PPU:246,225 CYC:25664\n$F679:EA        NOP                                A:57 X:02 Y:FF P:nvUbdIzc SP:FB PPU:270,225 CYC:25672\n$F67A:EA        NOP                                A:57 X:02 Y:FF P:nvUbdIzc SP:FB PPU:276,225 CYC:25674\n$F67B:08        PHP                                A:57 X:02 Y:FF P:nvUbdIzc SP:FB PPU:282,225 CYC:25676\n$F67C:48        PHA                                A:57 X:02 Y:FF P:nvUbdIzc SP:FA PPU:291,225 CYC:25679\n$F67D:A0 0B     LDY #$0B                           A:57 X:02 Y:FF P:nvUbdIzc SP:F9 PPU:300,225 CYC:25682\n$F67F:68        PLA                                A:57 X:02 Y:0B P:nvUbdIzc SP:F9 PPU:306,225 CYC:25684\n$F680:28        PLP                                A:57 X:02 Y:0B P:nvUbdIzc SP:FA PPU:318,225 CYC:25688\n$F681:20 FF FA  JSR $FAFF                          A:57 X:02 Y:0B P:nvUbdIzc SP:FB PPU:330,225 CYC:25692\n$FAFF:70 1A     BVS $FB1B                          A:57 X:02 Y:0B P:nvUbdIzc SP:F9 PPU:  7,226 CYC:25698\n$FB01:30 18     BMI $FB1B                          A:57 X:02 Y:0B P:nvUbdIzc SP:F9 PPU: 13,226 CYC:25700\n$FB03:B0 16     BCS $FB1B                          A:57 X:02 Y:0B P:nvUbdIzc SP:F9 PPU: 19,226 CYC:25702\n$FB05:C9 57     CMP #$57                           A:57 X:02 Y:0B P:nvUbdIzc SP:F9 PPU: 25,226 CYC:25704\n$FB07:D0 12     BNE $FB1B                          A:57 X:02 Y:0B P:nvUbdIZC SP:F9 PPU: 31,226 CYC:25706\n$FB09:60        RTS                                A:57 X:02 Y:0B P:nvUbdIZC SP:F9 PPU: 37,226 CYC:25708\n$F684:AD 47 06  LDA $0647 = #$14                     A:57 X:02 Y:0B P:nvUbdIZC SP:FB PPU: 55,226 CYC:25714\n$F687:C9 14     CMP #$14                           A:14 X:02 Y:0B P:25 SP:FB PPU: 67,226 CYC:25718\n$F689:F0 02     BEQ $F68D                          A:14 X:02 Y:0B P:nvUbdIZC SP:FB PPU: 73,226 CYC:25720\n$F68D:A0 FF     LDY #$FF                           A:14 X:02 Y:0B P:nvUbdIZC SP:FB PPU: 82,226 CYC:25723\n$F68F:A9 37     LDA #$37                           A:14 X:02 Y:FF P:A5 SP:FB PPU: 88,226 CYC:25725\n$F691:8D 47 06  STA $0647 = #$14                     A:37 X:02 Y:FF P:25 SP:FB PPU: 94,226 CYC:25727\n$F694:20 0A FB  JSR $FB0A                          A:37 X:02 Y:FF P:25 SP:FB PPU:106,226 CYC:25731\n$FB0A:24 01     BIT $01 = #$FF                       A:37 X:02 Y:FF P:25 SP:F9 PPU:124,226 CYC:25737\n$FB0C:38        SEC                                A:37 X:02 Y:FF P:E5 SP:F9 PPU:133,226 CYC:25740\n$FB0D:A9 75     LDA #$75                           A:37 X:02 Y:FF P:E5 SP:F9 PPU:139,226 CYC:25742\n$FB0F:60        RTS                                A:75 X:02 Y:FF P:65 SP:F9 PPU:145,226 CYC:25744\n$F697:73 45    *RRA ($45),Y = #$0548 @ 0647 = 37     A:75 X:02 Y:FF P:65 SP:FB PPU:163,226 CYC:25750\n$F699:EA        NOP                                A:11 X:02 Y:FF P:25 SP:FB PPU:187,226 CYC:25758\n$F69A:EA        NOP                                A:11 X:02 Y:FF P:25 SP:FB PPU:193,226 CYC:25760\n$F69B:08        PHP                                A:11 X:02 Y:FF P:25 SP:FB PPU:199,226 CYC:25762\n$F69C:48        PHA                                A:11 X:02 Y:FF P:25 SP:FA PPU:208,226 CYC:25765\n$F69D:A0 0C     LDY #$0C                           A:11 X:02 Y:FF P:25 SP:F9 PPU:217,226 CYC:25768\n$F69F:68        PLA                                A:11 X:02 Y:0C P:25 SP:F9 PPU:223,226 CYC:25770\n$F6A0:28        PLP                                A:11 X:02 Y:0C P:25 SP:FA PPU:235,226 CYC:25774\n$F6A1:20 10 FB  JSR $FB10                          A:11 X:02 Y:0C P:25 SP:FB PPU:247,226 CYC:25778\n$FB10:70 09     BVS $FB1B                          A:11 X:02 Y:0C P:25 SP:F9 PPU:265,226 CYC:25784\n$FB12:30 07     BMI $FB1B                          A:11 X:02 Y:0C P:25 SP:F9 PPU:271,226 CYC:25786\n$FB14:90 05     BCC $FB1B                          A:11 X:02 Y:0C P:25 SP:F9 PPU:277,226 CYC:25788\n$FB16:C9 11     CMP #$11                           A:11 X:02 Y:0C P:25 SP:F9 PPU:283,226 CYC:25790\n$FB18:D0 01     BNE $FB1B                          A:11 X:02 Y:0C P:nvUbdIZC SP:F9 PPU:289,226 CYC:25792\n$FB1A:60        RTS                                A:11 X:02 Y:0C P:nvUbdIZC SP:F9 PPU:295,226 CYC:25794\n$F6A4:AD 47 06  LDA $0647 = #$9B                     A:11 X:02 Y:0C P:nvUbdIZC SP:FB PPU:313,226 CYC:25800\n$F6A7:C9 9B     CMP #$9B                           A:9B X:02 Y:0C P:A5 SP:FB PPU:325,226 CYC:25804\n$F6A9:F0 02     BEQ $F6AD                          A:9B X:02 Y:0C P:nvUbdIZC SP:FB PPU:331,226 CYC:25806\n$F6AD:A0 0D     LDY #$0D                           A:9B X:02 Y:0C P:nvUbdIZC SP:FB PPU:340,226 CYC:25809\n$F6AF:A2 FF     LDX #$FF                           A:9B X:02 Y:0D P:25 SP:FB PPU:  5,227 CYC:25811\n$F6B1:A9 A5     LDA #$A5                           A:9B X:FF Y:0D P:A5 SP:FB PPU: 11,227 CYC:25813\n$F6B3:85 47     STA $47 = #$9B                       A:A5 X:FF Y:0D P:A5 SP:FB PPU: 17,227 CYC:25815\n$F6B5:20 E9 FA  JSR $FAE9                          A:A5 X:FF Y:0D P:A5 SP:FB PPU: 26,227 CYC:25818\n$FAE9:24 01     BIT $01 = #$FF                       A:A5 X:FF Y:0D P:A5 SP:F9 PPU: 44,227 CYC:25824\n$FAEB:18        CLC                                A:A5 X:FF Y:0D P:E5 SP:F9 PPU: 53,227 CYC:25827\n$FAEC:A9 B2     LDA #$B2                           A:A5 X:FF Y:0D P:NVUbdIzc SP:F9 PPU: 59,227 CYC:25829\n$FAEE:60        RTS                                A:B2 X:FF Y:0D P:NVUbdIzc SP:F9 PPU: 65,227 CYC:25831\n$F6B8:77 48    *RRA $48,X @ 47 = #$A5                A:B2 X:FF Y:0D P:NVUbdIzc SP:FB PPU: 83,227 CYC:25837\n$F6BA:EA        NOP                                A:05 X:FF Y:0D P:25 SP:FB PPU:101,227 CYC:25843\n$F6BB:EA        NOP                                A:05 X:FF Y:0D P:25 SP:FB PPU:107,227 CYC:25845\n$F6BC:EA        NOP                                A:05 X:FF Y:0D P:25 SP:FB PPU:113,227 CYC:25847\n$F6BD:EA        NOP                                A:05 X:FF Y:0D P:25 SP:FB PPU:119,227 CYC:25849\n$F6BE:20 EF FA  JSR $FAEF                          A:05 X:FF Y:0D P:25 SP:FB PPU:125,227 CYC:25851\n$FAEF:70 2A     BVS $FB1B                          A:05 X:FF Y:0D P:25 SP:F9 PPU:143,227 CYC:25857\n$FAF1:90 28     BCC $FB1B                          A:05 X:FF Y:0D P:25 SP:F9 PPU:149,227 CYC:25859\n$FAF3:30 26     BMI $FB1B                          A:05 X:FF Y:0D P:25 SP:F9 PPU:155,227 CYC:25861\n$FAF5:C9 05     CMP #$05                           A:05 X:FF Y:0D P:25 SP:F9 PPU:161,227 CYC:25863\n$FAF7:D0 22     BNE $FB1B                          A:05 X:FF Y:0D P:nvUbdIZC SP:F9 PPU:167,227 CYC:25865\n$FAF9:60        RTS                                A:05 X:FF Y:0D P:nvUbdIZC SP:F9 PPU:173,227 CYC:25867\n$F6C1:A5 47     LDA $47 = #$52                       A:05 X:FF Y:0D P:nvUbdIZC SP:FB PPU:191,227 CYC:25873\n$F6C3:C9 52     CMP #$52                           A:52 X:FF Y:0D P:25 SP:FB PPU:200,227 CYC:25876\n$F6C5:F0 02     BEQ $F6C9                          A:52 X:FF Y:0D P:nvUbdIZC SP:FB PPU:206,227 CYC:25878\n$F6C9:C8        INY                                A:52 X:FF Y:0D P:nvUbdIZC SP:FB PPU:215,227 CYC:25881\n$F6CA:A9 29     LDA #$29                           A:52 X:FF Y:0E P:25 SP:FB PPU:221,227 CYC:25883\n$F6CC:85 47     STA $47 = #$52                       A:29 X:FF Y:0E P:25 SP:FB PPU:227,227 CYC:25885\n$F6CE:20 FA FA  JSR $FAFA                          A:29 X:FF Y:0E P:25 SP:FB PPU:236,227 CYC:25888\n$FAFA:B8        CLV                                A:29 X:FF Y:0E P:25 SP:F9 PPU:254,227 CYC:25894\n$FAFB:18        CLC                                A:29 X:FF Y:0E P:25 SP:F9 PPU:260,227 CYC:25896\n$FAFC:A9 42     LDA #$42                           A:29 X:FF Y:0E P:nvUbdIzc SP:F9 PPU:266,227 CYC:25898\n$FAFE:60        RTS                                A:42 X:FF Y:0E P:nvUbdIzc SP:F9 PPU:272,227 CYC:25900\n$F6D1:77 48    *RRA $48,X @ 47 = #$29                A:42 X:FF Y:0E P:nvUbdIzc SP:FB PPU:290,227 CYC:25906\n$F6D3:EA        NOP                                A:57 X:FF Y:0E P:nvUbdIzc SP:FB PPU:308,227 CYC:25912\n$F6D4:EA        NOP                                A:57 X:FF Y:0E P:nvUbdIzc SP:FB PPU:314,227 CYC:25914\n$F6D5:EA        NOP                                A:57 X:FF Y:0E P:nvUbdIzc SP:FB PPU:320,227 CYC:25916\n$F6D6:EA        NOP                                A:57 X:FF Y:0E P:nvUbdIzc SP:FB PPU:326,227 CYC:25918\n$F6D7:20 FF FA  JSR $FAFF                          A:57 X:FF Y:0E P:nvUbdIzc SP:FB PPU:332,227 CYC:25920\n$FAFF:70 1A     BVS $FB1B                          A:57 X:FF Y:0E P:nvUbdIzc SP:F9 PPU:  9,228 CYC:25926\n$FB01:30 18     BMI $FB1B                          A:57 X:FF Y:0E P:nvUbdIzc SP:F9 PPU: 15,228 CYC:25928\n$FB03:B0 16     BCS $FB1B                          A:57 X:FF Y:0E P:nvUbdIzc SP:F9 PPU: 21,228 CYC:25930\n$FB05:C9 57     CMP #$57                           A:57 X:FF Y:0E P:nvUbdIzc SP:F9 PPU: 27,228 CYC:25932\n$FB07:D0 12     BNE $FB1B                          A:57 X:FF Y:0E P:nvUbdIZC SP:F9 PPU: 33,228 CYC:25934\n$FB09:60        RTS                                A:57 X:FF Y:0E P:nvUbdIZC SP:F9 PPU: 39,228 CYC:25936\n$F6DA:A5 47     LDA $47 = #$14                       A:57 X:FF Y:0E P:nvUbdIZC SP:FB PPU: 57,228 CYC:25942\n$F6DC:C9 14     CMP #$14                           A:14 X:FF Y:0E P:25 SP:FB PPU: 66,228 CYC:25945\n$F6DE:F0 02     BEQ $F6E2                          A:14 X:FF Y:0E P:nvUbdIZC SP:FB PPU: 72,228 CYC:25947\n$F6E2:C8        INY                                A:14 X:FF Y:0E P:nvUbdIZC SP:FB PPU: 81,228 CYC:25950\n$F6E3:A9 37     LDA #$37                           A:14 X:FF Y:0F P:25 SP:FB PPU: 87,228 CYC:25952\n$F6E5:85 47     STA $47 = #$14                       A:37 X:FF Y:0F P:25 SP:FB PPU: 93,228 CYC:25954\n$F6E7:20 0A FB  JSR $FB0A                          A:37 X:FF Y:0F P:25 SP:FB PPU:102,228 CYC:25957\n$FB0A:24 01     BIT $01 = #$FF                       A:37 X:FF Y:0F P:25 SP:F9 PPU:120,228 CYC:25963\n$FB0C:38        SEC                                A:37 X:FF Y:0F P:E5 SP:F9 PPU:129,228 CYC:25966\n$FB0D:A9 75     LDA #$75                           A:37 X:FF Y:0F P:E5 SP:F9 PPU:135,228 CYC:25968\n$FB0F:60        RTS                                A:75 X:FF Y:0F P:65 SP:F9 PPU:141,228 CYC:25970\n$F6EA:77 48    *RRA $48,X @ 47 = #$37                A:75 X:FF Y:0F P:65 SP:FB PPU:159,228 CYC:25976\n$F6EC:EA        NOP                                A:11 X:FF Y:0F P:25 SP:FB PPU:177,228 CYC:25982\n$F6ED:EA        NOP                                A:11 X:FF Y:0F P:25 SP:FB PPU:183,228 CYC:25984\n$F6EE:EA        NOP                                A:11 X:FF Y:0F P:25 SP:FB PPU:189,228 CYC:25986\n$F6EF:EA        NOP                                A:11 X:FF Y:0F P:25 SP:FB PPU:195,228 CYC:25988\n$F6F0:20 10 FB  JSR $FB10                          A:11 X:FF Y:0F P:25 SP:FB PPU:201,228 CYC:25990\n$FB10:70 09     BVS $FB1B                          A:11 X:FF Y:0F P:25 SP:F9 PPU:219,228 CYC:25996\n$FB12:30 07     BMI $FB1B                          A:11 X:FF Y:0F P:25 SP:F9 PPU:225,228 CYC:25998\n$FB14:90 05     BCC $FB1B                          A:11 X:FF Y:0F P:25 SP:F9 PPU:231,228 CYC:26000\n$FB16:C9 11     CMP #$11                           A:11 X:FF Y:0F P:25 SP:F9 PPU:237,228 CYC:26002\n$FB18:D0 01     BNE $FB1B                          A:11 X:FF Y:0F P:nvUbdIZC SP:F9 PPU:243,228 CYC:26004\n$FB1A:60        RTS                                A:11 X:FF Y:0F P:nvUbdIZC SP:F9 PPU:249,228 CYC:26006\n$F6F3:A5 47     LDA $47 = #$9B                       A:11 X:FF Y:0F P:nvUbdIZC SP:FB PPU:267,228 CYC:26012\n$F6F5:C9 9B     CMP #$9B                           A:9B X:FF Y:0F P:A5 SP:FB PPU:276,228 CYC:26015\n$F6F7:F0 02     BEQ $F6FB                          A:9B X:FF Y:0F P:nvUbdIZC SP:FB PPU:282,228 CYC:26017\n$F6FB:A9 A5     LDA #$A5                           A:9B X:FF Y:0F P:nvUbdIZC SP:FB PPU:291,228 CYC:26020\n$F6FD:8D 47 06  STA $0647 = #$9B                     A:A5 X:FF Y:0F P:A5 SP:FB PPU:297,228 CYC:26022\n$F700:A0 FF     LDY #$FF                           A:A5 X:FF Y:0F P:A5 SP:FB PPU:309,228 CYC:26026\n$F702:20 E9 FA  JSR $FAE9                          A:A5 X:FF Y:FF P:A5 SP:FB PPU:315,228 CYC:26028\n$FAE9:24 01     BIT $01 = #$FF                       A:A5 X:FF Y:FF P:A5 SP:F9 PPU:333,228 CYC:26034\n$FAEB:18        CLC                                A:A5 X:FF Y:FF P:E5 SP:F9 PPU:  1,229 CYC:26037\n$FAEC:A9 B2     LDA #$B2                           A:A5 X:FF Y:FF P:NVUbdIzc SP:F9 PPU:  7,229 CYC:26039\n$FAEE:60        RTS                                A:B2 X:FF Y:FF P:NVUbdIzc SP:F9 PPU: 13,229 CYC:26041\n$F705:7B 48 05 *RRA $0548,Y @ 0647 = #$A5            A:B2 X:FF Y:FF P:NVUbdIzc SP:FB PPU: 31,229 CYC:26047\n$F708:EA        NOP                                A:05 X:FF Y:FF P:25 SP:FB PPU: 52,229 CYC:26054\n$F709:EA        NOP                                A:05 X:FF Y:FF P:25 SP:FB PPU: 58,229 CYC:26056\n$F70A:08        PHP                                A:05 X:FF Y:FF P:25 SP:FB PPU: 64,229 CYC:26058\n$F70B:48        PHA                                A:05 X:FF Y:FF P:25 SP:FA PPU: 73,229 CYC:26061\n$F70C:A0 10     LDY #$10                           A:05 X:FF Y:FF P:25 SP:F9 PPU: 82,229 CYC:26064\n$F70E:68        PLA                                A:05 X:FF Y:10 P:25 SP:F9 PPU: 88,229 CYC:26066\n$F70F:28        PLP                                A:05 X:FF Y:10 P:25 SP:FA PPU:100,229 CYC:26070\n$F710:20 EF FA  JSR $FAEF                          A:05 X:FF Y:10 P:25 SP:FB PPU:112,229 CYC:26074\n$FAEF:70 2A     BVS $FB1B                          A:05 X:FF Y:10 P:25 SP:F9 PPU:130,229 CYC:26080\n$FAF1:90 28     BCC $FB1B                          A:05 X:FF Y:10 P:25 SP:F9 PPU:136,229 CYC:26082\n$FAF3:30 26     BMI $FB1B                          A:05 X:FF Y:10 P:25 SP:F9 PPU:142,229 CYC:26084\n$FAF5:C9 05     CMP #$05                           A:05 X:FF Y:10 P:25 SP:F9 PPU:148,229 CYC:26086\n$FAF7:D0 22     BNE $FB1B                          A:05 X:FF Y:10 P:nvUbdIZC SP:F9 PPU:154,229 CYC:26088\n$FAF9:60        RTS                                A:05 X:FF Y:10 P:nvUbdIZC SP:F9 PPU:160,229 CYC:26090\n$F713:AD 47 06  LDA $0647 = #$52                     A:05 X:FF Y:10 P:nvUbdIZC SP:FB PPU:178,229 CYC:26096\n$F716:C9 52     CMP #$52                           A:52 X:FF Y:10 P:25 SP:FB PPU:190,229 CYC:26100\n$F718:F0 02     BEQ $F71C                          A:52 X:FF Y:10 P:nvUbdIZC SP:FB PPU:196,229 CYC:26102\n$F71C:A0 FF     LDY #$FF                           A:52 X:FF Y:10 P:nvUbdIZC SP:FB PPU:205,229 CYC:26105\n$F71E:A9 29     LDA #$29                           A:52 X:FF Y:FF P:A5 SP:FB PPU:211,229 CYC:26107\n$F720:8D 47 06  STA $0647 = #$52                     A:29 X:FF Y:FF P:25 SP:FB PPU:217,229 CYC:26109\n$F723:20 FA FA  JSR $FAFA                          A:29 X:FF Y:FF P:25 SP:FB PPU:229,229 CYC:26113\n$FAFA:B8        CLV                                A:29 X:FF Y:FF P:25 SP:F9 PPU:247,229 CYC:26119\n$FAFB:18        CLC                                A:29 X:FF Y:FF P:25 SP:F9 PPU:253,229 CYC:26121\n$FAFC:A9 42     LDA #$42                           A:29 X:FF Y:FF P:nvUbdIzc SP:F9 PPU:259,229 CYC:26123\n$FAFE:60        RTS                                A:42 X:FF Y:FF P:nvUbdIzc SP:F9 PPU:265,229 CYC:26125\n$F726:7B 48 05 *RRA $0548,Y @ 0647 = #$29            A:42 X:FF Y:FF P:nvUbdIzc SP:FB PPU:283,229 CYC:26131\n$F729:EA        NOP                                A:57 X:FF Y:FF P:nvUbdIzc SP:FB PPU:304,229 CYC:26138\n$F72A:EA        NOP                                A:57 X:FF Y:FF P:nvUbdIzc SP:FB PPU:310,229 CYC:26140\n$F72B:08        PHP                                A:57 X:FF Y:FF P:nvUbdIzc SP:FB PPU:316,229 CYC:26142\n$F72C:48        PHA                                A:57 X:FF Y:FF P:nvUbdIzc SP:FA PPU:325,229 CYC:26145\n$F72D:A0 11     LDY #$11                           A:57 X:FF Y:FF P:nvUbdIzc SP:F9 PPU:334,229 CYC:26148\n$F72F:68        PLA                                A:57 X:FF Y:11 P:nvUbdIzc SP:F9 PPU:340,229 CYC:26150\n$F730:28        PLP                                A:57 X:FF Y:11 P:nvUbdIzc SP:FA PPU: 11,230 CYC:26154\n$F731:20 FF FA  JSR $FAFF                          A:57 X:FF Y:11 P:nvUbdIzc SP:FB PPU: 23,230 CYC:26158\n$FAFF:70 1A     BVS $FB1B                          A:57 X:FF Y:11 P:nvUbdIzc SP:F9 PPU: 41,230 CYC:26164\n$FB01:30 18     BMI $FB1B                          A:57 X:FF Y:11 P:nvUbdIzc SP:F9 PPU: 47,230 CYC:26166\n$FB03:B0 16     BCS $FB1B                          A:57 X:FF Y:11 P:nvUbdIzc SP:F9 PPU: 53,230 CYC:26168\n$FB05:C9 57     CMP #$57                           A:57 X:FF Y:11 P:nvUbdIzc SP:F9 PPU: 59,230 CYC:26170\n$FB07:D0 12     BNE $FB1B                          A:57 X:FF Y:11 P:nvUbdIZC SP:F9 PPU: 65,230 CYC:26172\n$FB09:60        RTS                                A:57 X:FF Y:11 P:nvUbdIZC SP:F9 PPU: 71,230 CYC:26174\n$F734:AD 47 06  LDA $0647 = #$14                     A:57 X:FF Y:11 P:nvUbdIZC SP:FB PPU: 89,230 CYC:26180\n$F737:C9 14     CMP #$14                           A:14 X:FF Y:11 P:25 SP:FB PPU:101,230 CYC:26184\n$F739:F0 02     BEQ $F73D                          A:14 X:FF Y:11 P:nvUbdIZC SP:FB PPU:107,230 CYC:26186\n$F73D:A0 FF     LDY #$FF                           A:14 X:FF Y:11 P:nvUbdIZC SP:FB PPU:116,230 CYC:26189\n$F73F:A9 37     LDA #$37                           A:14 X:FF Y:FF P:A5 SP:FB PPU:122,230 CYC:26191\n$F741:8D 47 06  STA $0647 = #$14                     A:37 X:FF Y:FF P:25 SP:FB PPU:128,230 CYC:26193\n$F744:20 0A FB  JSR $FB0A                          A:37 X:FF Y:FF P:25 SP:FB PPU:140,230 CYC:26197\n$FB0A:24 01     BIT $01 = #$FF                       A:37 X:FF Y:FF P:25 SP:F9 PPU:158,230 CYC:26203\n$FB0C:38        SEC                                A:37 X:FF Y:FF P:E5 SP:F9 PPU:167,230 CYC:26206\n$FB0D:A9 75     LDA #$75                           A:37 X:FF Y:FF P:E5 SP:F9 PPU:173,230 CYC:26208\n$FB0F:60        RTS                                A:75 X:FF Y:FF P:65 SP:F9 PPU:179,230 CYC:26210\n$F747:7B 48 05 *RRA $0548,Y @ 0647 = #$37            A:75 X:FF Y:FF P:65 SP:FB PPU:197,230 CYC:26216\n$F74A:EA        NOP                                A:11 X:FF Y:FF P:25 SP:FB PPU:218,230 CYC:26223\n$F74B:EA        NOP                                A:11 X:FF Y:FF P:25 SP:FB PPU:224,230 CYC:26225\n$F74C:08        PHP                                A:11 X:FF Y:FF P:25 SP:FB PPU:230,230 CYC:26227\n$F74D:48        PHA                                A:11 X:FF Y:FF P:25 SP:FA PPU:239,230 CYC:26230\n$F74E:A0 12     LDY #$12                           A:11 X:FF Y:FF P:25 SP:F9 PPU:248,230 CYC:26233\n$F750:68        PLA                                A:11 X:FF Y:12 P:25 SP:F9 PPU:254,230 CYC:26235\n$F751:28        PLP                                A:11 X:FF Y:12 P:25 SP:FA PPU:266,230 CYC:26239\n$F752:20 10 FB  JSR $FB10                          A:11 X:FF Y:12 P:25 SP:FB PPU:278,230 CYC:26243\n$FB10:70 09     BVS $FB1B                          A:11 X:FF Y:12 P:25 SP:F9 PPU:296,230 CYC:26249\n$FB12:30 07     BMI $FB1B                          A:11 X:FF Y:12 P:25 SP:F9 PPU:302,230 CYC:26251\n$FB14:90 05     BCC $FB1B                          A:11 X:FF Y:12 P:25 SP:F9 PPU:308,230 CYC:26253\n$FB16:C9 11     CMP #$11                           A:11 X:FF Y:12 P:25 SP:F9 PPU:314,230 CYC:26255\n$FB18:D0 01     BNE $FB1B                          A:11 X:FF Y:12 P:nvUbdIZC SP:F9 PPU:320,230 CYC:26257\n$FB1A:60        RTS                                A:11 X:FF Y:12 P:nvUbdIZC SP:F9 PPU:326,230 CYC:26259\n$F755:AD 47 06  LDA $0647 = #$9B                     A:11 X:FF Y:12 P:nvUbdIZC SP:FB PPU:  3,231 CYC:26265\n$F758:C9 9B     CMP #$9B                           A:9B X:FF Y:12 P:A5 SP:FB PPU: 15,231 CYC:26269\n$F75A:F0 02     BEQ $F75E                          A:9B X:FF Y:12 P:nvUbdIZC SP:FB PPU: 21,231 CYC:26271\n$F75E:A0 13     LDY #$13                           A:9B X:FF Y:12 P:nvUbdIZC SP:FB PPU: 30,231 CYC:26274\n$F760:A2 FF     LDX #$FF                           A:9B X:FF Y:13 P:25 SP:FB PPU: 36,231 CYC:26276\n$F762:A9 A5     LDA #$A5                           A:9B X:FF Y:13 P:A5 SP:FB PPU: 42,231 CYC:26278\n$F764:8D 47 06  STA $0647 = #$9B                     A:A5 X:FF Y:13 P:A5 SP:FB PPU: 48,231 CYC:26280\n$F767:20 E9 FA  JSR $FAE9                          A:A5 X:FF Y:13 P:A5 SP:FB PPU: 60,231 CYC:26284\n$FAE9:24 01     BIT $01 = #$FF                       A:A5 X:FF Y:13 P:A5 SP:F9 PPU: 78,231 CYC:26290\n$FAEB:18        CLC                                A:A5 X:FF Y:13 P:E5 SP:F9 PPU: 87,231 CYC:26293\n$FAEC:A9 B2     LDA #$B2                           A:A5 X:FF Y:13 P:NVUbdIzc SP:F9 PPU: 93,231 CYC:26295\n$FAEE:60        RTS                                A:B2 X:FF Y:13 P:NVUbdIzc SP:F9 PPU: 99,231 CYC:26297\n$F76A:7F 48 05 *RRA $0548,X @ 0647 = #$A5            A:B2 X:FF Y:13 P:NVUbdIzc SP:FB PPU:117,231 CYC:26303\n$F76D:EA        NOP                                A:05 X:FF Y:13 P:25 SP:FB PPU:138,231 CYC:26310\n$F76E:EA        NOP                                A:05 X:FF Y:13 P:25 SP:FB PPU:144,231 CYC:26312\n$F76F:EA        NOP                                A:05 X:FF Y:13 P:25 SP:FB PPU:150,231 CYC:26314\n$F770:EA        NOP                                A:05 X:FF Y:13 P:25 SP:FB PPU:156,231 CYC:26316\n$F771:20 EF FA  JSR $FAEF                          A:05 X:FF Y:13 P:25 SP:FB PPU:162,231 CYC:26318\n$FAEF:70 2A     BVS $FB1B                          A:05 X:FF Y:13 P:25 SP:F9 PPU:180,231 CYC:26324\n$FAF1:90 28     BCC $FB1B                          A:05 X:FF Y:13 P:25 SP:F9 PPU:186,231 CYC:26326\n$FAF3:30 26     BMI $FB1B                          A:05 X:FF Y:13 P:25 SP:F9 PPU:192,231 CYC:26328\n$FAF5:C9 05     CMP #$05                           A:05 X:FF Y:13 P:25 SP:F9 PPU:198,231 CYC:26330\n$FAF7:D0 22     BNE $FB1B                          A:05 X:FF Y:13 P:nvUbdIZC SP:F9 PPU:204,231 CYC:26332\n$FAF9:60        RTS                                A:05 X:FF Y:13 P:nvUbdIZC SP:F9 PPU:210,231 CYC:26334\n$F774:AD 47 06  LDA $0647 = #$52                     A:05 X:FF Y:13 P:nvUbdIZC SP:FB PPU:228,231 CYC:26340\n$F777:C9 52     CMP #$52                           A:52 X:FF Y:13 P:25 SP:FB PPU:240,231 CYC:26344\n$F779:F0 02     BEQ $F77D                          A:52 X:FF Y:13 P:nvUbdIZC SP:FB PPU:246,231 CYC:26346\n$F77D:C8        INY                                A:52 X:FF Y:13 P:nvUbdIZC SP:FB PPU:255,231 CYC:26349\n$F77E:A9 29     LDA #$29                           A:52 X:FF Y:14 P:25 SP:FB PPU:261,231 CYC:26351\n$F780:8D 47 06  STA $0647 = #$52                     A:29 X:FF Y:14 P:25 SP:FB PPU:267,231 CYC:26353\n$F783:20 FA FA  JSR $FAFA                          A:29 X:FF Y:14 P:25 SP:FB PPU:279,231 CYC:26357\n$FAFA:B8        CLV                                A:29 X:FF Y:14 P:25 SP:F9 PPU:297,231 CYC:26363\n$FAFB:18        CLC                                A:29 X:FF Y:14 P:25 SP:F9 PPU:303,231 CYC:26365\n$FAFC:A9 42     LDA #$42                           A:29 X:FF Y:14 P:nvUbdIzc SP:F9 PPU:309,231 CYC:26367\n$FAFE:60        RTS                                A:42 X:FF Y:14 P:nvUbdIzc SP:F9 PPU:315,231 CYC:26369\n$F786:7F 48 05 *RRA $0548,X @ 0647 = #$29            A:42 X:FF Y:14 P:nvUbdIzc SP:FB PPU:333,231 CYC:26375\n$F789:EA        NOP                                A:57 X:FF Y:14 P:nvUbdIzc SP:FB PPU: 13,232 CYC:26382\n$F78A:EA        NOP                                A:57 X:FF Y:14 P:nvUbdIzc SP:FB PPU: 19,232 CYC:26384\n$F78B:EA        NOP                                A:57 X:FF Y:14 P:nvUbdIzc SP:FB PPU: 25,232 CYC:26386\n$F78C:EA        NOP                                A:57 X:FF Y:14 P:nvUbdIzc SP:FB PPU: 31,232 CYC:26388\n$F78D:20 FF FA  JSR $FAFF                          A:57 X:FF Y:14 P:nvUbdIzc SP:FB PPU: 37,232 CYC:26390\n$FAFF:70 1A     BVS $FB1B                          A:57 X:FF Y:14 P:nvUbdIzc SP:F9 PPU: 55,232 CYC:26396\n$FB01:30 18     BMI $FB1B                          A:57 X:FF Y:14 P:nvUbdIzc SP:F9 PPU: 61,232 CYC:26398\n$FB03:B0 16     BCS $FB1B                          A:57 X:FF Y:14 P:nvUbdIzc SP:F9 PPU: 67,232 CYC:26400\n$FB05:C9 57     CMP #$57                           A:57 X:FF Y:14 P:nvUbdIzc SP:F9 PPU: 73,232 CYC:26402\n$FB07:D0 12     BNE $FB1B                          A:57 X:FF Y:14 P:nvUbdIZC SP:F9 PPU: 79,232 CYC:26404\n$FB09:60        RTS                                A:57 X:FF Y:14 P:nvUbdIZC SP:F9 PPU: 85,232 CYC:26406\n$F790:AD 47 06  LDA $0647 = #$14                     A:57 X:FF Y:14 P:nvUbdIZC SP:FB PPU:103,232 CYC:26412\n$F793:C9 14     CMP #$14                           A:14 X:FF Y:14 P:25 SP:FB PPU:115,232 CYC:26416\n$F795:F0 02     BEQ $F799                          A:14 X:FF Y:14 P:nvUbdIZC SP:FB PPU:121,232 CYC:26418\n$F799:C8        INY                                A:14 X:FF Y:14 P:nvUbdIZC SP:FB PPU:130,232 CYC:26421\n$F79A:A9 37     LDA #$37                           A:14 X:FF Y:15 P:25 SP:FB PPU:136,232 CYC:26423\n$F79C:8D 47 06  STA $0647 = #$14                     A:37 X:FF Y:15 P:25 SP:FB PPU:142,232 CYC:26425\n$F79F:20 0A FB  JSR $FB0A                          A:37 X:FF Y:15 P:25 SP:FB PPU:154,232 CYC:26429\n$FB0A:24 01     BIT $01 = #$FF                       A:37 X:FF Y:15 P:25 SP:F9 PPU:172,232 CYC:26435\n$FB0C:38        SEC                                A:37 X:FF Y:15 P:E5 SP:F9 PPU:181,232 CYC:26438\n$FB0D:A9 75     LDA #$75                           A:37 X:FF Y:15 P:E5 SP:F9 PPU:187,232 CYC:26440\n$FB0F:60        RTS                                A:75 X:FF Y:15 P:65 SP:F9 PPU:193,232 CYC:26442\n$F7A2:7F 48 05 *RRA $0548,X @ 0647 = #$37            A:75 X:FF Y:15 P:65 SP:FB PPU:211,232 CYC:26448\n$F7A5:EA        NOP                                A:11 X:FF Y:15 P:25 SP:FB PPU:232,232 CYC:26455\n$F7A6:EA        NOP                                A:11 X:FF Y:15 P:25 SP:FB PPU:238,232 CYC:26457\n$F7A7:EA        NOP                                A:11 X:FF Y:15 P:25 SP:FB PPU:244,232 CYC:26459\n$F7A8:EA        NOP                                A:11 X:FF Y:15 P:25 SP:FB PPU:250,232 CYC:26461\n$F7A9:20 10 FB  JSR $FB10                          A:11 X:FF Y:15 P:25 SP:FB PPU:256,232 CYC:26463\n$FB10:70 09     BVS $FB1B                          A:11 X:FF Y:15 P:25 SP:F9 PPU:274,232 CYC:26469\n$FB12:30 07     BMI $FB1B                          A:11 X:FF Y:15 P:25 SP:F9 PPU:280,232 CYC:26471\n$FB14:90 05     BCC $FB1B                          A:11 X:FF Y:15 P:25 SP:F9 PPU:286,232 CYC:26473\n$FB16:C9 11     CMP #$11                           A:11 X:FF Y:15 P:25 SP:F9 PPU:292,232 CYC:26475\n$FB18:D0 01     BNE $FB1B                          A:11 X:FF Y:15 P:nvUbdIZC SP:F9 PPU:298,232 CYC:26477\n$FB1A:60        RTS                                A:11 X:FF Y:15 P:nvUbdIZC SP:F9 PPU:304,232 CYC:26479\n$F7AC:AD 47 06  LDA $0647 = #$9B                     A:11 X:FF Y:15 P:nvUbdIZC SP:FB PPU:322,232 CYC:26485\n$F7AF:C9 9B     CMP #$9B                           A:9B X:FF Y:15 P:A5 SP:FB PPU:334,232 CYC:26489\n$F7B1:F0 02     BEQ $F7B5                          A:9B X:FF Y:15 P:nvUbdIZC SP:FB PPU:340,232 CYC:26491\n$F7B5:60        RTS                                A:9B X:FF Y:15 P:nvUbdIZC SP:FB PPU:  8,233 CYC:26494\n$C655:A5 00     LDA $00 = #$00                       A:9B X:FF Y:15 P:nvUbdIZC SP:FD PPU: 26,233 CYC:26500\n$C657:05 10     ORA $10 = #$00                       A:00 X:FF Y:15 P:nvUbdIZC SP:FD PPU: 35,233 CYC:26503\n$C659:05 11     ORA $11 = #$00                       A:00 X:FF Y:15 P:nvUbdIZC SP:FD PPU: 44,233 CYC:26506\n$C65B:F0 0E     BEQ $C66B                          A:00 X:FF Y:15 P:nvUbdIZC SP:FD PPU: 53,233 CYC:26509\n$C66B:20 89 C6  JSR $C689                          A:00 X:FF Y:15 P:nvUbdIZC SP:FD PPU: 62,233 CYC:26512\n$C689:A9 02     LDA #$02                           A:00 X:FF Y:15 P:nvUbdIZC SP:FB PPU: 80,233 CYC:26518\n"
  },
  {
    "path": "tetanes-core/test_roms/cpu/reset.txt",
    "content": "CPU Power/Reset Tests\n---------------------\nVerifies CPU register values at power, and changes that occur during\nreset. Also verifies that RAM isn't modified during reset.\n\n\nExpected behavior\n-----------------\nAt power:\n\tA, X, Y = 0\n\tP = $34\n\tS = $FD\n\nAfter reset:\n\tA, X, Y unchanged\n\tI flag set (P ORed with $04)\n\tS decremented by 3, but nothing written to stack\n\n\nMulti-tests\n-----------\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\nWhen run, they list the subtests as they are run. The final result code\nrefers to the first sub-test that failed. For more information about any\nfailed subtests, run them individually from rom_singles/ and\nnsf_singles/.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/cpu/tests.json",
    "content": "[\n  {\n    \"name\": \"branch_backward\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 14061918475422239861\n      }\n    ]\n  },\n  {\n    \"name\": \"nestest\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 10,\n        \"action\": {\n          \"Joypad\": [\"One\", \"Start\"]\n        }\n      },\n      {\n        \"number\": 27,\n        \"name\": \"valid_ops\",\n        \"hash\": 2359113843044038883\n      },\n      {\n        \"number\": 28,\n        \"action\": {\n          \"Joypad\": [\"One\", \"Select\"]\n        }\n      },\n      {\n        \"number\": 30,\n        \"action\": {\n          \"Joypad\": [\"One\", \"Start\"]\n        }\n      },\n      {\n        \"number\": 45,\n        \"name\": \"invalid_ops\",\n        \"hash\": 2651861775339687657\n      }\n    ]\n  },\n  {\n    \"name\": \"branch_basics\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 4683326619789796318\n      }\n    ]\n  },\n  {\n    \"name\": \"branch_forward\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 7096557190932891340\n      }\n    ]\n  },\n  {\n    \"name\": \"dummy_reads\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 48,\n        \"hash\": 1419558795669194341\n      }\n    ]\n  },\n  {\n    \"name\": \"dummy_writes_oam\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 330,\n        \"hash\": 17732444874837838893\n      }\n    ]\n  },\n  {\n    \"name\": \"dummy_writes_ppumem\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 235,\n        \"hash\": 16009121487930158525\n      }\n    ]\n  },\n  {\n    \"name\": \"exec_space_apu\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 309,\n        \"hash\": 12540582317851669771\n      }\n    ]\n  },\n  {\n    \"name\": \"exec_space_ppuio\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 50,\n        \"hash\": 788051276363575926\n      }\n    ]\n  },\n  {\n    \"name\": \"flag_concurrency\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 855,\n        \"hash\": 16686490019241741598\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_abs\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 120,\n        \"hash\": 12549919245884652726\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_abs_xy\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 367,\n        \"hash\": 279001387970484616\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_basics\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 2843100842160455037\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_branches\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 44,\n        \"hash\": 8530223784940472857\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_brk\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 26,\n        \"hash\": 3358184297522776232\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_imm\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 90,\n        \"hash\": 3550829661495618367\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_imp\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 110,\n        \"hash\": 1126135879873262284\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_ind_x\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 148,\n        \"hash\": 2183839236103019073\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_ind_y\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 138,\n        \"hash\": 18163414548108273019\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_jmp_jsr\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 6765162597571383868\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_misc\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 240,\n        \"hash\": 12558610457372999105\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_rti\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 14286295284764617179\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_rts\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 13839107880836985000\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_special\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 13808374822852460766\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_stack\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 168,\n        \"hash\": 13615317851392776885\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_timing\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 1305,\n        \"hash\": 16429169488661253642\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_zp\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 119,\n        \"hash\": 8836625608436215272\n      }\n    ]\n  },\n  {\n    \"name\": \"instr_zp_xy\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 261,\n        \"hash\": 13074514701701181028\n      }\n    ]\n  },\n  {\n    \"name\": \"int_branch_delays_irq\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 384,\n        \"hash\": 16263009087054765958\n      }\n    ]\n  },\n  {\n    \"name\": \"int_cli_latency\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 13339131754356242456\n      }\n    ]\n  },\n  {\n    \"name\": \"int_irq_and_dma\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 75,\n        \"hash\": 5630784201587328054\n      }\n    ]\n  },\n  {\n    \"name\": \"int_nmi_and_brk\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 114,\n        \"hash\": 10078891178501487086\n      }\n    ]\n  },\n  {\n    \"name\": \"int_nmi_and_irq\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 134,\n        \"hash\": 14942820692507785390\n      }\n    ]\n  },\n  {\n    \"name\": \"overclock\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 15,\n        \"hash\": 15555854521700294520\n      }\n    ]\n  },\n  {\n    \"name\": \"sprdma_and_dmc_dma\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 145,\n        \"hash\": 8348901162335247941\n      }\n    ]\n  },\n  {\n    \"name\": \"sprdma_and_dmc_dma_512\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 145,\n        \"hash\": 6133494704757795069\n      }\n    ]\n  },\n  {\n    \"name\": \"timing_test\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 615,\n        \"hash\": 891002094812730945\n      }\n    ]\n  },\n  {\n    \"name\": \"ram_after_reset\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 142,\n        \"action\": {\n          \"Reset\": \"Soft\"\n        }\n      },\n      {\n        \"number\": 153,\n        \"hash\": 531954482786283984\n      }\n    ]\n  },\n  {\n    \"name\": \"regs_after_reset\",\n    \"audio\": false,\n    \"frames\": [\n      {\n        \"number\": 140,\n        \"action\": {\n          \"Reset\": \"Soft\"\n        }\n      },\n      {\n        \"number\": 155,\n        \"hash\": 13935544292750434938\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "tetanes-core/test_roms/cpu/timing.txt",
    "content": "NES 6502 Timing Test\n--------------------\nThis program tests instruction timing for all official and unofficial\nNES 6502 instructions except the 8 branch instructions (Bxx) and the 12\nhalt instructions (HLT). It tests normal and page crossing cases of all\ninstructions (including instructions that should not take longer due to\na page crossing). It passes when run on an NTSC NES (it will not work on\na PAL NES due to the differing refresh rate).\n\nThe test takes up to 16 seconds to complete. If everything passes, it\nprints a message and beeps twice. If it fails, it prints an error\nmessage and beeps once.\n\nThe test can be restricted to only official instructions or test some or\nall of the unofficial instructions, in case your emulator doesn't\nemulate all the unofficial instructions. Select between these by holding\nthe following buttons on controller #1 when starting the test:\n\n(nothing)   Official instructions only\nB           Official + all unofficial instructions\nA           Official + $EB (equivalent to $E9) + unofficial NOPs:\n\n1-byte NOPs: $1A $3A $5A $7A $DA $FA\n2-byte NOPs: $04 $14 $34 $44 $54 $64 $74 $80 $82 $89 $C2 $D4 $E2 $F4\n3-byte NOPs: $0C $1C $3C $5C $7C $DC $FC\n\nThe 12 halt instructions are never tested, as they freeze the NES and\nrequire a reset:\n\nHLT: $02 $12 $22 $32 $42 $52 $62 $72 $92 $B2 $D2 $F2\n\nThe 8 branch instructions aren't tested since they have more subtle\npage-crossing behavior. Use my branch_timing_tests for these.\n\nSource code is included. Support code is also included, but it runs on a\ncustom devcart and assembler so it will require some effort to assemble.\nContact me if you'd like assistance porting them to your setup. I really\ndo plan on making my source work with ca65 eventually.\n\n\nErrors\n------\nAll instructions are first tested without a page crossing, then with a\npage crossing, allowing you to more easily debug timing problems.\n\nFAIL OP: The indicated opcode failed. If it was being timed where a page\ncrossing should occur, that will be noted. The number of clocks will be\nshown that the emulator used, and the correct number of clocks it should\nhave used.\n\nUNKNOWN ERROR: Occurs if the instruction timing fails or NMI\nunexpectedly returns. Prints the opcode and a hex value. Post to the\nNesdev forum if you get this error.\n\nBASIC TIMING WRONG: If you get this error, then the loop that tests NMI\nand basic instruction timing (below) ran too many/too few times. If this\noccurs, verify the timing of the following instructions and your PPU's\nNMI interrupt timing.\n\nloop:\n\tcpx zero-page\n\tbne stop\n\tinc zero-page\n\tbne loop\n\tinc zero-page\n\tjmp loop\nstop:\n\n\nHow the tests work\n------------------\nAll instructions are tested using a common framework which runs the\ninstruction in an infinite loop. Once the loop is eventually interrupted\nby NMI, the number of times the loop ran is cross-referenced with a\ntable to determine how many clocks the instruction used. For normal\ntiming, instructions which use some form of indexed addressing reference\naddress $0xFD and X and Y are set to 2, which is just shy of a page\ncross ($FD+2=$FF). To test page crossing timing, X and Y are set to 3,\ncausing a page crossing for relevant instructions ($FD+3=$100). Not all\ninstructions add an extra clock when a page is crossed, so this test\nreveals missing and extra page crossing penalties.\n\nSome instructions require special handling. JMP and JSR are tested by\njumping to the next instruction. RTS and RTI are handled by filling the\nstack with the value $02 and having the next instruction of the loop be\nat address $0202 (for RTI) or $0203 (for RTS, since it adds one to the\nreturn address). BRK is handled by setting the IRQ vector to $0202, the\naddress of the next instruction in the loop after BRK. The trickiness of\nthese special cases might reveal non-timing problems in an emulator.\n\n\nInstruction timing\n------------------\nThe following unofficial instructions have an extra clock added for page\ncrossing and use the indicated addressing mode:\n\nAbsolute, X: $1C $3C $5C $7C $DC $FC\nAbsolute, Y: $BB $BF\nIndirect, Y: $B3\n\nThese are the tables the test uses. Since it passes on a NES, the values\nare pretty much guaranteed correct.\n\nNo page crossing:\n\n\t0 1 2 3 4 5 6 7 8 9 A B C D E F\n\t--------------------------------\n\t7,6,0,8,3,3,5,5,3,2,2,2,4,4,6,6 | 0\n\t0,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7 | 1\n\t6,6,0,8,3,3,5,5,4,2,2,2,4,4,6,6 | 2\n\t0,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7 | 3\n\t6,6,0,8,3,3,5,5,3,2,2,2,3,4,6,6 | 4\n\t0,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7 | 5\n\t6,6,0,8,3,3,5,5,4,2,2,2,5,4,6,6 | 6\n\t0,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7 | 7\n\t2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4 | 8\n\t0,6,0,6,4,4,4,4,2,5,2,5,5,5,5,5 | 9\n\t2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4 | A\n\t0,5,0,5,4,4,4,4,2,4,2,4,4,4,4,4 | B\n\t2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6 | C\n\t0,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7 | D\n\t2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6 | E\n\t0,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7 | F\n\t\nPage crossing:\n\n\t0 1 2 3 4 5 6 7 8 9 A B C D E F\n\t--------------------------------\n\t7,6,0,8,3,3,5,5,3,2,2,2,4,4,6,6 | 0\n\t0,6,0,8,4,4,6,6,2,5,2,7,5,5,7,7 | 1\n\t6,6,0,8,3,3,5,5,4,2,2,2,4,4,6,6 | 2\n\t0,6,0,8,4,4,6,6,2,5,2,7,5,5,7,7 | 3\n\t6,6,0,8,3,3,5,5,3,2,2,2,3,4,6,6 | 4\n\t0,6,0,8,4,4,6,6,2,5,2,7,5,5,7,7 | 5\n\t6,6,0,8,3,3,5,5,4,2,2,2,5,4,6,6 | 6\n\t0,6,0,8,4,4,6,6,2,5,2,7,5,5,7,7 | 7\n\t2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4 | 8\n\t0,6,0,6,4,4,4,4,2,5,2,5,5,5,5,5 | 9\n\t2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4 | A\n\t0,6,0,6,4,4,4,4,2,5,2,5,5,5,5,5 | B\n\t2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6 | C\n\t0,6,0,8,4,4,6,6,2,5,2,7,5,5,7,7 | D\n\t2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6 | E\n\t0,6,0,8,4,4,6,6,2,5,2,7,5,5,7,7 | F\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/input/tests.json",
    "content": "[\n  {\n    \"name\": \"zapper_flip\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": \"ToggleZapperConnected\"\n      },\n      {\n        \"number\": 0,\n        \"action\": {\n          \"ZapperAim\": [\n            10,\n            10\n          ]\n        }\n      },\n      {\n        \"number\": 4,\n        \"hash\": 8615630426075156747\n      },\n      {\n        \"number\": 5,\n        \"action\": \"ZapperTrigger\"\n      },\n      {\n        \"number\": 6,\n        \"hash\": 14878161860519346933\n      },\n      {\n        \"number\": 13,\n        \"hash\": 8615630426075156747\n      }\n    ]\n  },\n  {\n    \"name\": \"zapper_light\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": \"ToggleZapperConnected\"\n      },\n      {\n        \"number\": 0,\n        \"action\": {\n          \"ZapperAim\": [\n            10,\n            10\n          ]\n        }\n      },\n      {\n        \"number\": 4,\n        \"hash\": 8615630426075156747\n      },\n      {\n        \"number\": 5,\n        \"hash\": 8615630426075156747\n      },\n      {\n        \"number\": 5,\n        \"action\": {\n          \"ZapperAim\": [\n            61,\n            61\n          ]\n        }\n      },\n      {\n        \"number\": 6,\n        \"hash\": 8615630426075156747\n      },\n      {\n        \"number\": 7,\n        \"action\": {\n          \"ZapperAim\": [\n            194,\n            178\n          ]\n        }\n      },\n      {\n        \"number\": 8,\n        \"hash\": 8615630426075156747\n      }\n    ]\n  },\n  {\n    \"name\": \"zapper_stream\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": \"ToggleZapperConnected\"\n      },\n      {\n        \"number\": 20,\n        \"hash\": 0\n      }\n    ]\n  },\n  {\n    \"name\": \"zapper_trigger\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": \"ToggleZapperConnected\"\n      },\n      {\n        \"number\": 20,\n        \"hash\": 0\n      }\n    ]\n  }\n]"
  },
  {
    "path": "tetanes-core/test_roms/mapper/m004_txrom/irq.txt",
    "content": "NTSC NES MMC3 IRQ Counter Test ROMs\n-----------------------------------\nThese ROMs test much of MMC3 IRQ counter behavior on an NTSC NES PPU.\nThey have been tested on an actual NES with on several MMC3 cartridges\nand all give a passing result. Many tests are written specifically to\ncatch likely errors in an emulator.\n\nEach ROM runs several tests and reports the result on screen and by\nbeeping a number of times. Failure codes for each ROM are listed below.\nIt's best to run the tests in order, because some earlier ROMs test\nthings that later ones assume will work properly.\n\nThe ROMs mainly test behavior by manually clocking the MMC3's IRQ\ncounter by writing to $2006 to change the current VRAM address. The last\ntwo ROMs test different revisions of the MMC3, so at most only one will\npass on a particular emulator.\n\nAll the asm source is included, and most tests are clearly divided into\nsections. The code runs on a custom devcart and assembler so it will\nrequire some effort to assemble. Contact me if you'd like assistance\nporting them to your setup.\n\n\nMMC3 Operation\n--------------\nI have fairly thoroughly tested MMC3 IRQ counter operation and found the\nfollowing behaviors that differ as described in kevtris's (draft?) MMC3\ndocumentation:\n\n- The counter can be clocked manually via bit 12 of the VRAM address\neven when $2000 = $00 (bg and sprites both use tiles from $0xxx).\n\n- The IRQ flag is not set when the counter is cleared by writing to\n$C001.\n\n- I uncovered some pathological behavior that isn't covered by the test\nROMs. If $C001 is written, the counter clocked, then $C001 written\nagain, on the next counter clock the counter will be ORed with $80\n(revision B)/frozen (revision A) and neither decremented nor reloaded.\nIf $C001 is written again at this point, on the next counter clock it\nwill be reloaded normally. I put a check in my emulator and none of the\nseveral games I tested ever caused this situation to occur, so it's\nprobably not a good idea to implement this.\n\nThe MMC3 in Crystalis (referred to here as revision A) worked as\ndescribed in kevtris's document, with the above changes. The MMC3 in\nSuper Mario Bros. 3 and Mega Man 3 (I think revision B, but I don't have\nthe special screw driver) further differed when $C000 was written with\n0:\n\n- Writing 0 to $C000 works no differently than any other value written;\nit will cause the counter to be reloaded every time it is clocked (once\nit reaches zero).\n\n- When the counter is clocked, if it's not zero, it is decremented,\notherwise it is reloaded with the last value written to $C000. *After*\ndecrementing/reloading, if the counter is zero and IRQ is enabled via\n$E001, the IRQ flag is set.\n\n\n1.Clocking\n----------\nTests counter operation. Requires support for clocking via manual\ntoggling of VRAM address.\n\n2) Counter/IRQ/A12 clocking isn't working at all\n3) Should decrement when A12 is toggled via $2006\n4) Writing to $C000 shouldn't cause reload\n5) Writing to $C001 shouldn't cause immediate reload\n6) Should reload (no decrement) on first clock after clear\n7) IRQ should be set when counter is decremented to 0\n8) IRQ should never be set when disabled\n9) Should reload when clocked when counter is 0\n\n\n2.Details\n---------\nTests counter details.\n\n2) Counter isn't working when reloaded with 255\n3) Counter should run even when IRQ is disabled\n4) Counter should run even after IRQ flag has been set\n5) IRQ should not be set when counter reloads with non-zero\n6) IRQ should not be set when counter is cleared via $C001\n7) Counter should be clocked 241 times in PPU frame\n\n\n3.A12 Clocking\n--------------\nTests clocking via bit 12 of VRAM address.\n\n2) Shouldn't be clocked when A12 doesn't change\n3) Shouldn't be clocked when A12 changes to 0\n4) Should be clocked when A12 changes to 1 via $2006 write\n5) Should be clocked when A12 changes to 1 via $2007 read\n6) Should be clocked when A12 changes to 1 via $2007 write\n\n\n4.Scanline Timing\n-----------------\nTests basic timing for scanlines 0, 1, and 240.\n\n2) Scanline 0 time is too soon\n3) Scanline 0 time is too late\n4) Scanline 1 time is too soon\n5) Scanline 1 time is too late\n6) Scanline 239 time is too soon\n7) Scanline 239 time is too late\n\n\n5.MMC3 Rev A\n------------\nTests MMC3 revision A differences (tested with Crystalis board).\n\n2) IRQ should be set when reloading to 0 after clear\n3) IRQ shouldn't occur when reloading after counter normally reaches 0\n\n\n6.MMC3 Rev B\n------------\nTests MMC3 revision B differences (tested with Super Mario Bros. 3 and\nMega Man 3 boards).\n\n2) Should reload and set IRQ every clock when reload is 0\n3) IRQ should be set when counter is 0 after reloading\n\n-- \nShay Green <hotpop.com@blargg> (swap to e-mail)\n"
  },
  {
    "path": "tetanes-core/test_roms/mapper/m004_txrom/tests.json",
    "content": "[\n  {\n    \"name\": \"a12_clocking\",\n    \"frames\": [\n      {\n        \"number\": 25,\n        \"hash\": 12040201839270376890\n      }\n    ]\n  },\n  {\n    \"name\": \"clocking\",\n    \"frames\": [\n      {\n        \"number\": 25,\n        \"hash\": 3159592045864818260\n      }\n    ]\n  },\n  {\n    \"name\": \"details\",\n    \"frames\": [\n      {\n        \"number\": 30,\n        \"hash\": 15211845500498666234\n      }\n    ]\n  },\n  {\n    \"name\": \"scanline_timing\",\n    \"frames\": [\n      {\n        \"number\": 90,\n        \"hash\": 6306567989779835575\n      }\n    ]\n  },\n  {\n    \"name\": \"big_chr_ram\",\n    \"frames\": [\n      {\n        \"number\": 10,\n        \"hash\": 5491179751774366132\n      },\n      {\n        \"number\": 11,\n        \"action\": {\n          \"Joypad\": [\"One\", \"Start\"]\n        }\n      },\n      {\n        \"number\": 80,\n        \"hash\": 14367881047247501485\n      }\n    ]\n  },\n  {\n    \"name\": \"rev_a\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"MapperRevision\": {\n            \"Mmc3\": \"A\"\n          }\n        }\n      },\n      {\n        \"number\": 30,\n        \"hash\": 7203694655519112220\n      }\n    ]\n  },\n  {\n    \"name\": \"rev_b\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"MapperRevision\": {\n            \"Mmc3\": \"BC\"\n          }\n        }\n      },\n      {\n        \"number\": 30,\n        \"hash\": 5332315838929142497\n      }\n    ]\n  }\n]\n\n"
  },
  {
    "path": "tetanes-core/test_roms/mapper/m005_exrom/tests.json",
    "content": "[\n  {\n    \"name\": \"exram\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"SetVideoFilter\": \"Ntsc\"\n        }\n      },\n      {\n        \"number\": 10,\n        \"hash\": 17551728759146689352\n      },\n      {\n        \"number\": 15,\n        \"hash\": 4756351708356043917\n      },\n      {\n        \"number\": 45,\n        \"hash\": 1380234192467300615\n      },\n      {\n        \"number\": 100,\n        \"hash\": 9477694461559011324\n      }\n    ]\n  },\n  {\n    \"name\": \"basics\",\n    \"frames\": [\n      {\n        \"number\": 10,\n        \"hash\": 13185952026495389028\n      },\n      {\n        \"number\": 11,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"A\"\n          ]\n        }\n      },\n      {\n        \"number\": 14,\n        \"name\": \"obj_table\",\n        \"hash\": 5835282650047076017\n      },\n      {\n        \"number\": 15,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"B\"\n          ]\n        }\n      },\n      {\n        \"number\": 18,\n        \"name\": \"bg_table\",\n        \"hash\": 14203633185999536969\n      },\n      {\n        \"number\": 19,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Start\"\n          ]\n        }\n      },\n      {\n        \"number\": 22,\n        \"name\": \"obj_size\",\n        \"hash\": 14397147232086588632\n      },\n      {\n        \"number\": 23,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Select\"\n          ]\n        }\n      },\n      {\n        \"number\": 26,\n        \"name\": \"exram\",\n        \"hash\": 14992356073095190656\n      },\n      {\n        \"number\": 27,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Up\"\n          ]\n        }\n      },\n      {\n        \"number\": 30,\n        \"name\": \"fill\",\n        \"hash\": 8833133164801041322\n      },\n      {\n        \"number\": 31,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Up\"\n          ]\n        }\n      },\n      {\n        \"number\": 33,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Left\"\n          ]\n        }\n      },\n      {\n        \"number\": 36,\n        \"name\": \"bank_left\",\n        \"hash\": 13357435064697061420\n      },\n      {\n        \"number\": 37,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Right\"\n          ]\n        }\n      },\n      {\n        \"number\": 39,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Right\"\n          ]\n        }\n      },\n      {\n        \"number\": 42,\n        \"name\": \"bank_right\",\n        \"hash\": 10119745483606927626\n      }\n    ]\n  }\n]"
  },
  {
    "path": "tetanes-core/test_roms/ppu/blargg_readme.txt",
    "content": "NTSC NES PPU Tests\n------------------\nThese ROMs test a few aspects of the NTSC NES PPU operation. They have been\ntested on an actual NES and all give a passing result. I wrote them to verify\nthat my NES emulator's PPU was working properly.\n\nEach ROM runs several tests and reports a result code on screen and by beeping\na number of times. A result code of 1 always indicates that all tests were\npassed; see below for the meaning of other codes for each test.\n\nThe main source code for each test is included, and most tests are clearly\ndivided into sections. Some of the common support code is included, but not\nall, since it runs on a custom setup. Contact me if you want to assemble the\ntests yourself.\n\nShay Green <hotpop.com@blargg> (swap to e-mail)\n\n\npalette_ram\n-----------\nPPU palette RAM read/write and mirroring test\n\n1) Tests passed\n2) Palette read shouldn't be buffered like other VRAM\n3) Palette write/read doesn't work\n4) Palette should be mirrored within $3f00-$3fff\n5) Write to $10 should be mirrored at $00\n6) Write to $00 should be mirrored at $10\n\n\npower_up_palette\n----------------\nReports whether initial values in palette at power-up match those\nthat my NES has. These values are probably unique to my NES.\n\n1) Palette matches\n2) Palette differs from table\n\n\nsprite_ram\n----------\nTests sprite RAM access via $2003, $2004, and $4014\n\n1) Tests passed\n2) Basic read/write doesn't work\n3) Address should increment on $2004 write\n4) Address should not increment on $2004 read\n5) Third sprite bytes should be masked with $e3 on read \n6) $4014 DMA copy doesn't work at all\n7) $4014 DMA copy should start at value in $2003 and wrap\n8) $4014 DMA copy should leave value in $2003 intact\n\n\nvbl_clear_time\n--------------\nThe VBL flag ($2002.7) is cleared by the PPU around 2270 CPU clocks\nafter NMI occurs.\n\n1) Tests passed\n2) VBL flag cleared too soon\n3) VBL flag cleared too late\n\n\nvram_access\n-----------\nTests PPU VRAM read/write and internal read buffer operation\n\n1) Tests passed\n2) VRAM reads should be delayed in a buffer\n3) Basic Write/read doesn't work\n4) Read buffer shouldn't be affected by VRAM write\n5) Read buffer shouldn't be affected by palette write\n6) Palette read should also read VRAM into read buffer\n7) \"Shadow\" VRAM read unaffected by palette transparent color mirroring\n"
  },
  {
    "path": "tetanes-core/test_roms/ppu/oam_read.txt",
    "content": "NES OAM Read Test\n-----------------\nTests OAM reading ($2004), being sure it reads the byte from OAM at the\ncurrent address in $2003. It scans OAM from 0 to $FF, testing each byte\nin sequence. It prints a '-' where it reads back from the current\naddress, and '*' where it doesn't. Each row represents 16 bytes of OAM,\n16 rows total.\n\n\nResults\n-------\nOn my NTSC front-loader NES, I get the following four general patterns\nat random after power/reset:\n\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n\noam_read\n\nPassed\n\n\n----------------\n----------------\n--------*------*\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n\n694ADBE0\noam_read\n\nFailed\n\n\n----------------\n----------------\n********--------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n----------------\n\nE9E8E60F\noam_read\n\nFailed\n\n\n****************\n*********-------\n--------*-*-*-*-\n*-*-*-*-*-*-*-*-\n*-*-*-*-*-*-*-*-\n*-*-*-*-*-*-*-*-\n***-*-*-*-*-*-*-\n*-*-*-*-*-*-*-*-\n*-*-*-*-*-*-*-*-\n*-*-*-*-*-*-*-*-\n*-*-*-*-*-*-*-*-\n*-*-*-*-*-*-*-*-\n*-*-*-*-*-*-*-*-\n*-*-*-*-*-*-*-*-\n***-*-*-*-*-*-*-\n*-*-*-*-*-*-*-*-\n\n44551956\noam_read\n\nFailed\n\n\nFlashes, clicks, other glitches\n-------------------------------\nSome tests might need to turn the screen off and on, or cause slight\naudio clicks. This does not indicate failure, and should be ignored.\nOnly the test result reported at the end is important, unless stated\notherwise.\n\n\nText output\n-----------\nTests generally print information on screen. They also output the same\ntext as a zero-terminted string beginning at $6004, allowing examination\nof output in an NSF player, or a NES emulator without a working PPU. The\ntests also work properly if the PPU doesn't set the VBL flag properly or\ndoesn't implement it at all.\n\nThe final result is displayed and also written to $6000. Before the test\nstarts, $80 is written there so you can tell when it's done. If a test\nneeds the NES to be reset, it writes $81 there (emulator should wait a\ncouple of frames after seeing $81). In addition, $DE $B0 $G1 is written\nto $6001-$6003 to allow an emulator to detect when a test is being run,\nas opposed to some other NES program. In NSF builds, the final result is\nalso reported via a series of beeps (see below).\n\nSee the source code for more information about a particular test and why\nit might be failing. Each test has comments and correct output at the\ntop.\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to avoid the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\nIn addition to the other text output methods described above, NSF builds\nreport essential information bytes audibly, including the final result.\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason as listed in the\nsource code by the corresponding set_code line. Examples:\n\nTones         Binary  Decimal  Meaning\n- - - - - - - - - - - - - - - - - - - - \nlow              0      0      passed\nlow high        01      1      failed\nlow high low   010      2      error 2\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/ppu/oam_stress.txt",
    "content": "NES OAM Stress Test\n-------------------\nThoroughly tests OAM address ($2003) and read/write ($2004). On an NTSC\nNES, this passes only for one of the four random PPU-CPU\nsynchronizations at power/reset. Test takes about 30 seconds, unless it\nfails.\n\nThis test randomly sets the address, then randomly either writes a\nrandom number of random bytes, or reads from the current address a\nrandom number of times and verifies that it matches what's expected. It\ndoes this for tens of seconds (refreshing OAM periodically so it doesn't\nfade). Once done, it verifies that all bytes in OAM match what's\nexpected.\n\nExpected behavior:\n\n$2003 write sets OAM address.\n\n$2004 write sets byte at current OAM address to byte written, then\nincrements OAM address.\n\n$2004 read gives byte at current OAM address, without modifying OAM\naddress.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nSome tests might need to turn the screen off and on, or cause slight\naudio clicks. This does not indicate failure, and should be ignored.\nOnly the test result reported at the end is important, unless stated\notherwise.\n\n\nText output\n-----------\nTests generally print information on screen. They also output the same\ntext as a zero-terminted string beginning at $6004, allowing examination\nof output in an NSF player, or a NES emulator without a working PPU. The\ntests also work properly if the PPU doesn't set the VBL flag properly or\ndoesn't implement it at all.\n\nThe final result is displayed and also written to $6000. Before the test\nstarts, $80 is written there so you can tell when it's done. If a test\nneeds the NES to be reset, it writes $81 there (emulator should wait a\ncouple of frames after seeing $81). In addition, $DE $B0 $G1 is written\nto $6001-$6003 to allow an emulator to detect when a test is being run,\nas opposed to some other NES program. In NSF builds, the final result is\nalso reported via a series of beeps (see below).\n\nSee the source code for more information about a particular test and why\nit might be failing. Each test has comments and correct output at the\ntop.\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to avoid the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\nIn addition to the other text output methods described above, NSF builds\nreport essential information bytes audibly, including the final result.\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason as listed in the\nsource code by the corresponding set_code line. Examples:\n\nTones         Binary  Decimal  Meaning\n- - - - - - - - - - - - - - - - - - - - \nlow              0      0      passed\nlow high        01      1      failed\nlow high low   010      2      error 2\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/ppu/open_bus.txt",
    "content": "NES PPU Open-Bus Test\n---------------------\nTests behavior when reading from open-bus PPU bits/registers, those bits\nthat aren't otherwise defined. Unlike other open-bus addresses, the PPU\nones are separate. Takes about 5 seconds to run.\n\nThe PPU effectively has a \"decay register\", an 8-bit register. Each bit\ncan be refreshed with a 0 or 1. If a bit isn't refreshed with a 1 for\nabout 600 milliseconds, it will decay to 0 (some decay sooner, depending\non the NES and temperature).\n\nWriting to any PPU register sets the decay register to the value\nwritten. Reading from a PPU register is more complex. The following\nshows the effect of a read from each register:\n\n\tAddr    Open-bus bits\n\t\t\t7654 3210\n\t- - - - - - - - - - - - - - - -\n\t$2000   DDDD DDDD\n\t$2001   DDDD DDDD\n\t$2002   ---D DDDD\n\t$2003   DDDD DDDD\n\t$2004   ---- ----\n\t$2005   DDDD DDDD\n\t$2006   DDDD DDDD\n\t$2007   ---- ----   non-palette\n\t\t\tDD-- ----   palette\n\nA D means that this bit reads back as whatever is in the decay register\nat that bit, and doesn't refresh the decay register at that bit. A -\nmeans that this bit reads back as defined by the PPU, and refreshes the\ndecay register at the corresponding bit.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nSome tests might need to turn the screen off and on, or cause slight\naudio clicks. This does not indicate failure, and should be ignored.\nOnly the test result reported at the end is important, unless stated\notherwise.\n\n\nText output\n-----------\nTests generally print information on screen. They also output the same\ntext as a zero-terminted string beginning at $6004, allowing examination\nof output in an NSF player, or a NES emulator without a working PPU. The\ntests also work properly if the PPU doesn't set the VBL flag properly or\ndoesn't implement it at all.\n\nThe final result is displayed and also written to $6000. Before the test\nstarts, $80 is written there so you can tell when it's done. If a test\nneeds the NES to be reset, it writes $81 there (emulator should wait a\ncouple of frames after seeing $81). In addition, $DE $B0 $G1 is written\nto $6001-$6003 to allow an emulator to detect when a test is being run,\nas opposed to some other NES program. In NSF builds, the final result is\nalso reported via a series of beeps (see below).\n\nSee the source code for more information about a particular test and why\nit might be failing. Each test has comments and correct output at the\ntop.\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to avoid the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\nIn addition to the other text output methods described above, NSF builds\nreport essential information bytes audibly, including the final result.\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason as listed in the\nsource code by the corresponding set_code line. Examples:\n\nTones         Binary  Decimal  Meaning\n- - - - - - - - - - - - - - - - - - - - \nlow              0      0      passed\nlow high        01      1      failed\nlow high low   010      2      error 2\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/ppu/read_buffer.txt",
    "content": "NES PPU Read Buffer Tests\n----------------------------------\nThis mammoth test pack tests many aspects of the NES system,\nmostly centering around the PPU $2007 read buffer.\n\nThe test will take about 20 seconds.\n\nThe program attempts to do as many tests as possible before\nreporting the result. When the screen is blanked for a long\ntime, audio is used to report progress. A low-pitched fat\ntone indicates failure; bright beeps indicate progress.\n\nIf a sub-test fails, at a certain point the list of all\nfailed tests is provided in a numeric form, and a textual\nexplanation of the first failed test is shown.\n\n\nFull list of tests performed is below.\nNote that the tests are not performed in a numerical order.\nFor example, test #47 (does palette reading work at all) is\nperformed before test #7 (does sequential palette reading work).\n\n\tTest  2 (TEST_PPUMEMORYIO):\n\n\tPPU memory I/O does not work.\n\tPossible areas of problem:\n\t- PPU not implemented\n\t- PPU memory writing ($2007)\n\t- PPU memory reading ($2007)\n\t- PPU memory area $2C00-$2FFF\n\n\tTest  3 (TEST_ONEBYTEBUFFER):\n\n\tNon-palette PPU memory reads\n\tshould have one-byte buffer.\n\n\tTest  4 (TEST_CIRAM_READ):\n\n\tCIRAM reading\n\tdoes not work.\n\n\tTest  5 (TEST_CIRAM_SEQ_READ_1):\n\n\tSequential CIRAM reading\n\twith  1-byte increment\n\tdoes not work.\n\n\tTest  6 (TEST_CIRAM_SEQ_READ_32):\n\n\tSequential CIRAM reading\n\twith 32-byte increment\n\tdoes not work.\n\n\tTest  7 (TEST_PALETTE_RAM_SEQ_READ_1):\n\n\tSequential PALETTE reading\n\twith  1-byte increment\n\tdoes not work.\n\n\tTest  8 (TEST_PALETTE_RAM_SEQ_READ_32):\n\n\tSequential PALETTE reading\n\twith 32-byte increment\n\tdoes not work.\n\n\tTest  9 (TEST_CHRROM_READ):\n\n\tCHR-ROM reading\n\tdoes not work.\n\n\tTest 10 (TEST_CHRROM_SEQ_READ_1):\n\n\tSequential CHR-ROM reading\n\twith  1-byte increment\n\tdoes not work.\n\n\tTest 11 (TEST_CHRROM_SEQ_READ_32):\n\n\tSequential CHR-ROM reading\n\twith 32-byte increment\n\tdoes not work.\n\n\tTest 12 (TEST_CIRAM_SEQ_WRITE_1):\n\n\tSequential CIRAM writes\n\twith  1-byte increment\n\tdoes not work.\n\n\tTest 13 (TEST_CIRAM_SEQ_WRITE_32):\n\n\tSequential CIRAM writes\n\twith 32-byte increment\n\tdoes not work.\n\n\tTest 14 (TEST_NTA_MIRRORING_FAIL_1NTA):\n\n\t1-nametable setup seems to\n\tbe active, even though this\n\tROM is explicitly configured\n\tfor horizontal mirroring.\n\n\tTest 15 (TEST_NTA_MIRRORING_FAIL_4NTA):\n\n\tFour-screen setup seems to\n\tbe active, even though this\n\tROM is explicitly configured\n\tfor horizontal mirroring.\n\n\tTest 16 (TEST_NTA_MIRRORING_FAIL_VERT):\n\n\tVertical mirroring seems to\n\tbe active, even though this\n\tROM is explicitly configured\n\tfor horizontal mirroring.\n\n\tTest 17 (TEST_PPU_OPEN_BUS):\n\n\tAny data that is transferred\n\tthrough PPU I/O should linger\n\tand be readable for a while\n\tin any PPU register that does\n\tnot have a read function.\n\tThis is called \"open bus\".\n\tTo minimally pass this test,\n\tyou need to at least provide\n\ta bridge between $2003(W) and\n\t$2000(R).\n\n\tTest 18 (TEST_PPU_OPEN_BUS_SHORTCUT):\n\n\tReading a write-only PPU\n\tregister should not just give\n\tthe current value of SPRADDR.\n\tThat would be a too lazy\n\tworkaround for a failed test!\n\n\tTest 19 (TEST_PPU_OPENBUS_MUST_NOT_COPY_READBUFFER):\n\n\tPPU memory read buffer is not\n\tthe open bus. Reading the bus\n\tshould repeat the last value\n\tthat was transferred, not\n\tdisclose the buffered byte.\n\n\tTest 20 (TEST_PPU_OPENBUS_FROM_WRITE2000_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2000 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 21 (TEST_PPU_OPENBUS_FROM_WRITE2001_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2001 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 22 (TEST_PPU_OPENBUS_FROM_WRITE2002_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2002 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 23 (TEST_PPU_OPENBUS_FROM_WRITE2003_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2003 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 24 (TEST_PPU_OPENBUS_FROM_WRITE2004_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2004 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 25 (TEST_PPU_OPENBUS_FROM_WRITE2005_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2005 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 26 (TEST_PPU_OPENBUS_FROM_WRITE2006_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2006 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 27 (TEST_PPU_OPENBUS_FROM_WRITE2007_MUST_NOT_WRITETO_READBUFFER):\n\n\tA write to $2007 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 28 (TEST_PPU_OPENBUS_FROM_READ2000_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2000 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 29 (TEST_PPU_OPENBUS_FROM_READ2001_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2001 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 30 (TEST_PPU_OPENBUS_FROM_READ2002_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2002 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 31 (TEST_PPU_OPENBUS_FROM_READ2003_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2003 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 32 (TEST_PPU_OPENBUS_FROM_READ2004_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2004 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 33 (TEST_PPU_OPENBUS_FROM_READ2005_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2005 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 34 (TEST_PPU_OPENBUS_FROM_READ2006_MUST_NOT_WRITETO_READBUFFER):\n\n\tA read from $2006 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 35 (TEST_PPU_OPENBUS_INDEXED):\n\n\tSTA $2000,Y with Y=7 must\n\tissue a dummy read to $2007.\n\n\tTest 36 (TEST_PPU_OPENBUS_INDEXED2):\n\n\tSTA $1FF0,Y with Y=$17 mustn't\n\tissue a dummy read to $2007.\n\n\tTest 37 (TEST_PPU_OPENBUS_FROM_READ_MIRROR_MUST_WRITETO_READBUFFER):\n\n\tA read from a mirrored copy\n\tof $2007 must act as if\n\t$2007 was read, and update\n\tthe same read buffer.\n\n\tTest 38 (TEST_PPU_READ_WITH_AND):\n\n\tThe AND instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 39 (TEST_PPU_READ_WITH_ORA):\n\n\tThe ORA instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 40 (TEST_PPU_READ_WITH_EOR):\n\n\tThe EOR instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 41 (TEST_PPU_READ_WITH_CMP):\n\n\tThe CMP instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 42 (TEST_PPU_READ_WITH_CPX):\n\n\tThe CPX instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 43 (TEST_PPU_READ_WITH_CPY):\n\n\tThe CPY instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 44 (TEST_PPU_READ_WITH_ADC):\n\n\tThe ADC instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 45 (TEST_PPU_READ_WITH_SBC):\n\n\tThe SBC instruction must be\n\tusable for reading $2007\n\tor any other I/O port.\n\n\tTest 46 (TEST_ONEBYTEBUFFER_PALETTE):\n\n\tPalette reads from PPU should\n\tnot have one-byte buffer.\n\n\tTest 47 (TEST_PALETTE_READS):\n\n\tPalette reads from PPU do not\n\tseem to be working at all.\n\n\tTest 48 (TEST_PALETTE_READS_UNRELIABLE):\n\n\tPalette reads  from PPU seem\n\tto work randomly.\n\n\tTest 49 (TEST_PALETTE_MIRRORS):\n\n\tPalette indexes $3F1x should\n\tbe mirrors of $3F0x when\n\tx is 0, 4, 8, or C.\n\n\tTest 50 (TEST_PALETTE_UNIQUE):\n\n\tIt must be possible to store\n\tunique data in each of $3F00,\n\t$3F04, $3F08 and $3F0C.\n\n\tTest 51 (TEST_PPU_PALETTE_WRAP):\n\n\tPPU addresses 3F00-3F1F\n\tshould be mirrored within\n\tthe whole 3F00-3FFF region,\n\tfor a total of 8 times.\n\n\tTest 52 (TEST_PPU_MEMORY_14BIT_A):\n\n\tFailed sub-test 1 of:\n\tThe two MSB within the PPU\n\tmemory address should be\n\tcompletely ignored in all\n\tcircumstances, effectively\n\tmirroring the 0000-3FFF\n\taddress range within the\n\twhole 0000-FFFF region,\n\tfor a total of 4 times.\n\n\tTest 53 (TEST_PPU_MEMORY_14BIT_B):\n\n\tFailed sub-test 2 of:\n\tThe two MSB within the PPU\n\tmemory address should be\n\tcompletely ignored in all\n\tcircumstances, effectively\n\tmirroring the 0000-3FFF\n\taddress range within the\n\twhole 0000-FFFF region,\n\tfor a total of 4 times.\n\n\tTest 54 (TEST_PPU_MIRROR_3000):\n\n\tPPU memory range 3000-3EFE\n\tshould be a mirror of the\n\tPPU memory range 2000-2EFE.\n\n\tTest 55 (TEST_PPU_READ_3EFF):\n\n\tSetting PPU address to 3EFF\n\tand reading $2007 twice\n\tshould give the data at\n\t$3F00, not the data at $2EFF.\n\n\tTest 56 (TEST_PPU_MIRROR_2F):\n\n\tReading PPU memory range 3Fxx\n\tshould put contents of 2Fxx\n\tinto the read buffer.\n\n\tTest 57 (TEST_PPU_SEQ_READ_WRAP):\n\n\tSetting PPU address to 3FFF\n\t& reading $2007 thrice should\n\tgive the contents of $0000.\n\n\tTest 58 (SEQ_READ_INTERNAL):\n\n\tUnexpected: VROM contents at\n\t$0000 and $1FFF read the same.\n\tThis should never happen in\n\tthis test ROM.\n\n\tTest 59 (TEST_VADDR):\n\n\tRelationship between $2005\n\tand $2006 is not implemented\n\tproperly. Here is a guide.\n\tIt explains which registers\n\tuse which parts of the address.\n\tNote that only the second\n\twrite to $2006 updates the\n\taddress really used by $2007.\n\tFEDCBA9876543210ZYX: bit pos.\n\t  ^^^^^^^^^^^^^^------ =$2007\n\tzz543210-------------- $2006#1\n\t        76543210------ $2006#2\n\t           76543210--- $2005#1\n\t 210--76543----------- $2005#2\n\t    10---------------- $2000\n\n\tTest 60 (TEST_RAM_MIRRORING):\n\n\tCPU RAM at 0000-07FF should\n\tbe mirrored 4 times, in the\n\tfollowing address ranges:\n\t- 0000-07FF\n\t- 0800-0FFF\n\t- 1000-17FF\n\t- 1800-1FFF\n\n\tTest 61 (TEST_PPUIO_MIRRORING):\n\n\tPPU I/O memory at 2000-2007\n\tshould be mirrored within the\n\twhole 2000-3FFF region, for\n\ta total of 1024 times.\n\n\tTest 62 (TEST_SPHIT_AND_VBLANK):\n\n\tSprite 0 hit flag should not\n\tread as set during vblank.\n\n\tTest 63 (TEST_SPHIT_DIRECT):\n\n\tSprite 0 hit test by poking\n\tdata directly into $2003-4\n\t^ Possible causes for failure:\n\t- $2003/$2004 not implemented\n\t- No sprite 0 hit tests\n\t- Way too long vblank period\n\n\tTest 64 (TEST_SPHIT_DIRECT_READBUFFER):\n\n\tSending 5 bytes of data into\n\t$2003 and $2004 must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 65 (TEST_SPHIT_DMA_ROM):\n\n\tSprite 0 hit test using DMA\n\t($4014) using ROM as source\n\t^ Possible causes for failure:\n\t- $4014 DMA cannot read from\n\t  anything other than RAM\n\n\tTest 66 (TEST_SPHIT_DMA_READBUFFER):\n\n\tInvoking a $4014 DMA with a\n\tnon-$20 value  must not\n\toverwrite the $2007 read\n\tbuffer.\n\n\tTest 67 (TEST_SPHIT_DMA_PPU_BUS):\n\n\tSprite 0 hit test using DMA\n\t($4014) using PPU I/O bus\n\tas source\n\t^ In this test, $4014 <- #$20.\n\t  Possible causes for failure:\n\t- DMA does not do proper reads\n\t- PPU bus does not preserve\n\t  last transferred values\n\t- $2002 read returned a value\n\t  that differs from expected\n\t- $2004 read modifies the OAM\n\n\tTest 68 (TEST_DMA_PPU_SIDEEFFECT):\n\n\tWriting $20 into $4014 should\n\tgenerate 32 reads into $2007\n\tas a side-effect, each time\n\tincrementing the PPU read\n\taddress.\n\n\tTest 69 (TEST_SPHIT_DMA_RAM):\n\n\tSprite 0 hit test using DMA.\n\tAll internal RAM pages are\n\ttested, including mirrored\n\taddresses. Failing the test\n\tmay imply faulty mirroring.\n\n\tTest 70 (TEST_CHRROM_READ_BANKED):\n\n\tCHR ROM read through $2007\n\tdoes not honor mapper 3\n\t(CNROM) bank switching\n\n\tTest 71 (TEST_CHRROM_READ_BANKED_BUFFER):\n\n\tThe $2007 read buffer should\n\tnot retroactively react to\n\tchanges in VROM mapping.\n\tWhen you read $2007, the\n\tdata is stored in a buffer\n\t(\"latch\"), and the previous\n\tcontent of the buffer is\n\treturned. It is not a delayed\n\tread request.\n\n\tTest 72 (TEST_CHRROM_WRITE):\n\n\tCHR ROM on mapper 3 (CNROM boards)\n\tmust not be writable.\n\n\tTest 73 (TEST_BUFFER_DELAY_BLANK_1FRAME):\n\n\tThe PPU read buffer should\n\tsurvive 1 frame of idle\n\twith rendering disabled.\n\n\tTest 74 (TEST_BUFFER_DELAY_BLANK_2SECONDS):\n\n\tThe PPU read buffer should\n\tsurvive 2 seconds of idle\n\twith rendering disabled.\n\n\tTest 75 (TEST_BUFFER_DELAY_INTERNAL):\n\n\tUnexpected: VROM contents at\n\t$1Bxx did not match what was\n\thardcoded into the program.\n\n\tTest 76 (TEST_BUFFER_DELAY_VISIBLE_1FRAME):\n\n\tThe PPU read buffer should\n\tsurvive 1 frame of idle\n\twith rendering enabled.\n\n\tTest 77 (TEST_BUFFER_DELAY_VISIBLE_1SECOND):\n\n\tThe PPU read buffer should\n\tsurvive 1 second of idle\n\twith rendering enabled.\n\n\tTest 78 (TEST_BUFFER_DELAY_VISIBLE_3SECONDS):\n\n\tThe PPU read buffer should\n\tsurvive 3 seconds of idle\n\twith rendering enabled.\n\n\tTest 79 (TEST_BUFFER_DELAY_VISIBLE_7SECONDS):\n\n\tThe PPU read buffer should\n\tsurvive 7 seconds of idle\n\twith rendering enabled.\n\n\n\nExpected output:\n\n  TEST:test_ppu_read_buffer      :)\n  -------------------------------\n  Testing basic PPU memory I/O.\n  Performing tests that combine\n  sprite 0 hit flag, $4014 DMA\n  and the RAM mirroring...\n  Graphical artifacts during\n  this test are OK and expected.\n  \n                Hit  No-Hit\n  Direct poke   OK   OK\n  DMA with ROM  OK   OK\n  DMA + PPU bus OK   OK\n  DMA with RAM  OK   OK\n  -------------------------------\n   This next test will take a while.\n   In order to distract you with\n   entertainment, art is provided.\n   Contemplate on the art while\n   the test is in progress.\n\n   \n  Passed\n  \nThe \":)\" should be blue/purple; the \"OK\" should be brownish\norange, and the \"Graphical artifacts\" paragraph should\nalso be brownish orange. Everything else should be white.\n\nIn the painting by Thomas Kinkade that is shown before\nthe \"Passed\" text appears, the ground should be pleasantly\ngreen.\n\n\nThe text outputted to the $6000 console is\nslightly different than the reference shown above,\nbecause parts of the text above are placed on the\nscreen directly, and for other reasons.\n\n\nBecause this ROM contains a large amount of text and\nsome graphics data, portions of the ROM had to be\ncompressed to avoid increasing the ROM size too much.\nShould one want to rebuild the ROM, a particular set\nof tools will be needed; including nasm, gcc, and php.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe text output may include ANSI color codes, which take the form of\nan esc character ($1B), an opening bracket ('['), and a sequence of\nnumbers and semicolon characters, terminated by a non-digit character ('m').\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\n-- \nJoel Yliluoma <bisqwit@iki.fi>\nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/ppu/spr_hit.txt",
    "content": "NTSC NES PPU Sprite 0 Test ROMs\n-------------------------------\nThese ROMs test much of sprite 0 hit behavior on a NTSC NES PPU. They\nhave been tested on an actual NES and all give a passing result. I wrote\nthem to verify that my NES emulator's sprite 0 hit emulation was working\nproperly.\n\nEach test ROM runs several tests and reports the result on screen and by\nbeeping a number of times. See below for the meaning of failure codes\nfor each test. It's best to run the tests in order, because some earlier\nROMs test things that later ones assume will work properly.\n\nThe main source code for each test is included, and most tests are\nclearly divided into sections. All the asm source is included, but it\nruns on a custom devcart and assembler so it will require some effort to\nassemble. Contact me if you'd assistance porting them to your setup.\n\n\n01.basics\n---------\nTests basic sprite 0 hit behavior (nothing timing related).\n\n2) Sprite hit isn't working at all\n3) Should hit even when completely behind background\n4) Should miss when background rendering is off\n5) Should miss when sprite rendering is off\n6) Should miss when all rendering is off\n7) All-transparent sprite should miss\n8) Only low two palette index bits are relevant\n9) Any non-zero palette index should hit with any other\n10) Should miss when background is all transparent\n11) Should always miss other sprites\n\n\n02.alignment\n------------\nTests alignment of sprite hit with background. Places a solid background\ntile in the middle of the screen and places the sprite on all four edges\nboth overlapping and non-overlapping.\n\n2) Basic sprite-background alignment is way off\n3) Sprite should miss left side of bg tile\n4) Sprite should hit left side of bg tile\n5) Sprite should miss right side of bg tile\n6) Sprite should hit right side of bg tile\n7) Sprite should miss top of bg tile\n8) Sprite should hit top of bg tile\n9) Sprite should miss bottom of bg tile\n10) Sprite should hit bottom of bg tile\n\n\n03.corners\n----------\nTests sprite 0 hit using a sprite with a single pixel set, for each of\nthe four corners.\n\n2) Lower-right pixel should hit\n3) Lower-left pixel should hit\n4) Upper-right pixel should hit\n5) Upper-left pixel should hit\n\n\n04.flip\n-------\nTests sprite 0 hit for single pixel sprite and background.\n\n2) Horizontal flipping doesn't work\n3) Vertical flipping doesn't work\n4) Horizontal + Vertical flipping doesn't work\n\n\n05.left_clip\n------------\nTests sprite 0 hit with regard to clipping of left 8 pixels of screen.\n\n2) Should miss when entirely in left-edge clipping\n3) Left-edge clipping occurs when $2001 is not $1e\n4) Left-edge clipping is off when $2001 = $1e\n5) Left-edge clipping blocks all hits only when X = 0\n6) Should miss; sprite pixel covered by left-edge clip\n7) Should hit; sprite pixel outside left-edge clip\n8) Should hit; sprite pixel outside left-edge clip\n\n\n06.right_edge\n-------------\nTests sprite 0 hit with regard to column 255 (ignored) and off right\nedge of screen.\n\n2) Should always miss when X = 255\n3) Should hit; sprite has pixels < 255\n4) Should miss; sprite pixel is at 255\n5) Should hit; sprite pixel is at 254\n6) Should also hit; sprite pixel is at 254\n\n\n07.screen_bottom\n----------------\nTests sprite 0 hit with regard to bottom of screen.\n\n2) Should always miss when Y >= 239\n3) Can hit when Y < 239\n4) Should always miss when Y = 255\n5) Should hit; sprite pixel is at 238\n6) Should miss; sprite pixel is at 239\n7) Should hit; sprite pixel is at 238\n\n\n08.double_height\n----------------\nTests basic sprite 0 hit double-height operation.\n\n2) Lower sprite tile should miss bottom of bg tile\n3) Lower sprite tile should hit bottom of bg tile\n3) Lower sprite tile should miss top of bg tile\n4) Lower sprite tile should hit top of bg tile\n\n\n09.timing_basics\n----------------\nTests sprite 0 hit timing to within 12 or so PPU clocks. Tests flag\ntiming for upper-left corner, upper-right corner, lower-right corner,\nand time flag is cleared (at end of VBL). Depends on proper PPU frame\nlength (less than 29781 CPU clocks).\n\n2) Upper-left corner too soon\n3) Upper-left corner too late\n4) Upper-right corner too soon\n5) Upper-right corner too late\n6) Lower-left corner too soon\n7) Lower-left corner too late\n8) Cleared at end of VBL too soon\n9) Cleared at end of VBL too late\n\n\n10.timing_order\n---------------\nTests sprite 0 hit timing for which pixel it first reports hit on. Each\ntest hits at the same location on screen, though different relative to\nthe position of the sprite.\n\n2) Upper-left corner too soon\n3) Upper-left corner too late\n4) Upper-right corner too soon\n5) Upper-right corner too late\n6) Lower-left corner too soon\n7) Lower-left corner too late\n8) Lower-right corner too soon\n9) Lower-right corner too late\n\n\n11.edge_timing\n--------------\nTests sprite 0 hit timing for which pixel it first reports hit on when\nsome pixels are under clip, or at or beyond right edge.\n\n2) Hit time shouldn't be based on pixels under left clip\n3) Hit time shouldn't be based on pixels at X=255\n4) Hit time shouldn't be based on pixels off right edge\n\n-- \nShay Green <hotpop.com@blargg> (swap to e-mail)\n"
  },
  {
    "path": "tetanes-core/test_roms/ppu/spr_overflow.txt",
    "content": "NTSC NES PPU Sprite Overflow Flag Test ROMs\n-------------------------------------------\nThese ROMs test the sprite overflow flag in bit 5 of $2002. When run on\na NES they all give a passing result. Each ROM runs several tests and\nreports the result on screen and by beeping a number of times. See below\nfor the meaning of failure codes for each test. THE TESTS MUST BE RUN\n(*AND* *PASS*) IN ORDER, because some earlier ROMs test things that\nlater ones assume will work properly.\n\nSource code for each test is included, and most tests are clearly\ndivided into sections. Support code is also included, but it runs on a\ncustom devcart and assembler so it will require some effort to assemble.\nContact me if you'd like assistance porting them to your setup.\n\n\n1.Basics\n--------\nTests basic operation of sprite overflow flag.\n\n2) Should be set when 9 sprites are on a scanline\n3) Reading $2002 shouldn't clear flag\n4) Shouldn't be cleared at the beginning of VBL\n5) Should be cleared at the end of VBL\n6) Shouldn't be set when all rendering is off\n7) Should work normally when $2001 = $08 (bg rendering only)\n8) Should work normally when $2001 = $10 (sprite rendering only)\n\n\n2.Details\n---------\nTests more detailed operation.\n\n2) Should be set even when sprites are under left clip (X = 0)\n3) Disabling rendering shouldn't clear flag\n4) Should be cleared at the end of VBL even when rendering is off\n5) Should be set when sprite Y coordinates are 239\n6) Shouldn't be set when sprite Y coordinates are 240 (off screen)\n7) Shouldn't be set when sprite Y coordinates are 255 (off screen)\n8) Should be set regardless of which sprites are involved\n9) Shouldn't be set when all scanlines have 7 or fewer sprites\n10) Double-height sprites aren't handled properly\n\n\n3.Timing\n--------\nTests timing of sprite overflow flag. The tests fail if timing is off by\nmore than a CPU clock or two.\n\n2) Cleared too late/3)too early at end of VBL\n4) Set too early/5)too late for first scanline\n6) Sprite horizontal positions should have no effect on timing\n7) Set too early/8)late for last sprites on first scanline\n9) Set too early/10)too late for last scanline\n11) Set too early/12)too late when 9th sprite # is way after 8th\n13) Overflow on second scanline occurs too early/14)too late\n\n\n4.Obscure\n---------\nTests the pathological behavior when 8 sprites are on a scanline and the\none just after the 8th is not on the scanline. In that case, the PPU\ninterprets different bytes of each following sprite as the Y coordinate.\nFor the following setup of any consecutive range of sprites (that is,\nsprite 1 below could be the PPU's 25th sprite, sprite 2 the 26th, etc.):\n\n\t1 2 3 4 5 6 7 8 9 10 11 12 13 14\n\nIf 1-8 are on the same scanline but 9 isn't, then the second byte of 10,\nthe third byte of 11, fourth byte of 12, first byte of 13, second byte\nof 14, etc. are treated as those sprites' Y coordinates for the purpose\nof determining whether overflow occurs on that scanline. This search\ncontinues until one of the (erroneously interpreted) Y coordinates\nplaces the sprite within the scanline, or all sprites have been scanned.\nRefer to the NESdevWiki for further information about this behavior. \n\n2) Checks that second byte of sprite #10 is treated as its Y \n3) Checks that third byte of sprite #11 is treated as its Y \n4) Checks that fourth byte of sprite #12 is treated as its Y \n5) Checks that first byte of sprite #13 is treated as its Y \n6) Checks that second byte of sprite #14 is treated as its Y \n7) Checks that search stops at the last sprite without overflow\n8) Same as test #2 but using a different range of sprites\n\n\n5.Emulator\n----------\nTests things that an emulator with predictive overflow flag handling is\nlikely to get wrong.\n\n2) Didn't calculate overflow when there was no $2002 read for frame\n3) Disabling rendering didn't recalculate flag time\n4) Changing sprite RAM didn't recalculate flag time\n5) Changing sprite height didn't recalculate time\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/ppu/tests.json",
    "content": "[\n  {\n    \"name\": \"ntsc_torture\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"SetVideoFilter\": \"Ntsc\"\n        }\n      },\n      {\n        \"number\": 10,\n        \"hash\": 6478544483085266183\n      },\n      {\n        \"number\": 11,\n        \"hash\": 13464778097346924262\n      }\n    ]\n  },\n  {\n    \"name\": \"oam_read\",\n    \"frames\": [\n      {\n        \"number\": 40,\n        \"hash\": 11005166623255668487\n      }\n    ]\n  },\n  {\n    \"name\": \"oam_stress\",\n    \"frames\": [\n      {\n        \"number\": 1709,\n        \"hash\": 987545031310641328\n      }\n    ]\n  },\n  {\n    \"name\": \"open_bus\",\n    \"frames\": [\n      {\n        \"number\": 260,\n        \"hash\": 11491804135423135712\n      }\n    ]\n  },\n  {\n    \"name\": \"palette_ram\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"read_buffer\",\n    \"frames\": [\n      {\n        \"number\": 1350,\n        \"hash\": 11280523076083011669\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_hit_alignment\",\n    \"frames\": [\n      {\n        \"number\": 50,\n        \"hash\": 8064486199559266391\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_hit_basics\",\n    \"frames\": [\n      {\n        \"number\": 50,\n        \"hash\": 2843100842160455037\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_hit_corners\",\n    \"frames\": [\n      {\n        \"number\": 35,\n        \"hash\": 10774006242499790677\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_hit_double_height\",\n    \"frames\": [\n      {\n        \"number\": 30,\n        \"hash\": 10299510358892663483\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_hit_flip\",\n    \"frames\": [\n      {\n        \"number\": 30,\n        \"hash\": 3446883211880305229\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_hit_left_clip\",\n    \"frames\": [\n      {\n        \"number\": 50,\n        \"hash\": 15407546689908417177\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_hit_right_edge\",\n    \"frames\": [\n      {\n        \"number\": 40,\n        \"hash\": 15660187972799557083\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_hit_screen_bottom\",\n    \"frames\": [\n      {\n        \"number\": 40,\n        \"hash\": 7853612209953886180\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_hit_timing_basics\",\n    \"frames\": [\n      {\n        \"number\": 70,\n        \"hash\": 5150672749382335376\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_hit_timing_order\",\n    \"frames\": [\n      {\n        \"number\": 75,\n        \"hash\": 14951931037155501762\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_hit_edge_timing\",\n    \"frames\": [\n      {\n        \"number\": 80,\n        \"hash\": 10213563398947658894\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_overflow_basics\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 4512213194270824995\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_overflow_details\",\n    \"frames\": [\n      {\n        \"number\": 30,\n        \"hash\": 7453336004261551379\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_overflow_emulator\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 10339336858702076057\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_overflow_obscure\",\n    \"frames\": [\n      {\n        \"number\": 22,\n        \"hash\": 7930864775209885475\n      }\n    ]\n  },\n  {\n    \"name\": \"spr_overflow_timing\",\n    \"frames\": [\n      {\n        \"number\": 141,\n        \"hash\": 14539354705593009798\n      }\n    ]\n  },\n  {\n    \"name\": \"sprite_ram\",\n    \"frames\": [\n      {\n        \"number\": 30,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"vbl_nmi_basics\",\n    \"frames\": [\n      {\n        \"number\": 150,\n        \"hash\": 16562782044648304193\n      }\n    ]\n  },\n  {\n    \"name\": \"vbl_nmi_clear_timing\",\n    \"frames\": [\n      {\n        \"number\": 120,\n        \"hash\": 11463851157594709500\n      }\n    ]\n  },\n  {\n    \"name\": \"vbl_nmi_control\",\n    \"frames\": [\n      {\n        \"number\": 35,\n        \"hash\": 16934587399174399058\n      }\n    ]\n  },\n  {\n    \"name\": \"vbl_nmi_disable\",\n    \"frames\": [\n      {\n        \"number\": 115,\n        \"hash\": 1189096324891626908\n      }\n    ]\n  },\n  {\n    \"name\": \"vbl_nmi_even_odd_frames\",\n    \"frames\": [\n      {\n        \"number\": 100,\n        \"hash\": 15425322074654675101\n      }\n    ]\n  },\n  {\n    \"name\": \"vbl_nmi_even_odd_timing\",\n    \"frames\": [\n      {\n        \"number\": 100,\n        \"hash\": 0\n      }\n    ]\n  },\n  {\n    \"name\": \"vbl_nmi_frame_basics\",\n    \"frames\": [\n      {\n        \"number\": 180,\n        \"hash\": 382282417179176914\n      }\n    ]\n  },\n  {\n    \"name\": \"vbl_nmi_off_timing\",\n    \"frames\": [\n      {\n        \"number\": 230,\n        \"hash\": 2137824251974549592\n      }\n    ]\n  },\n  {\n    \"name\": \"vbl_nmi_on_timing\",\n    \"frames\": [\n      {\n        \"number\": 210,\n        \"hash\": 6158031964237457839\n      }\n    ]\n  },\n  {\n    \"name\": \"vbl_nmi_set_time\",\n    \"frames\": [\n      {\n        \"number\": 200,\n        \"hash\": 5554918882984212301\n      }\n    ]\n  },\n  {\n    \"name\": \"vbl_nmi_suppression\",\n    \"frames\": [\n      {\n        \"number\": 165,\n        \"hash\": 11565747234567092543\n      }\n    ]\n  },\n  {\n    \"name\": \"vbl_nmi_timing\",\n    \"frames\": [\n      {\n        \"number\": 115,\n        \"hash\": 12609704643621948295\n      }\n    ]\n  },\n  {\n    \"name\": \"vbl_timing\",\n    \"frames\": [\n      {\n        \"number\": 153,\n        \"hash\": 8016893805681573651\n      }\n    ]\n  },\n  {\n    \"name\": \"vram_access\",\n    \"frames\": [\n      {\n        \"number\": 20,\n        \"hash\": 951336095730925361\n      }\n    ]\n  },\n  {\n    \"name\": \"palette\",\n    \"frames\": [\n      {\n        \"number\": 9,\n        \"name\": \"no_filter\",\n        \"hash\": 6997757400193095267\n      },\n      {\n        \"number\": 10,\n        \"action\": {\n          \"SetVideoFilter\": \"Ntsc\"\n        }\n      },\n      {\n        \"number\": 11,\n        \"name\": \"blue_green_red\",\n        \"hash\": 8071574123308103670\n      },\n      {\n        \"number\": 12,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Left\"\n          ]\n        }\n      },\n      {\n        \"number\": 15,\n        \"name\": \"green_red\",\n        \"hash\": 13623772725600068525\n      },\n      {\n        \"number\": 16,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Left\"\n          ]\n        }\n      },\n      {\n        \"number\": 18,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Up\"\n          ]\n        }\n      },\n      {\n        \"number\": 21,\n        \"name\": \"blue_red\",\n        \"hash\": 15501427923254063958\n      },\n      {\n        \"number\": 22,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Left\"\n          ]\n        }\n      },\n      {\n        \"number\": 25,\n        \"name\": \"red\",\n        \"hash\": 10357388817602375796\n      },\n      {\n        \"number\": 26,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Left\"\n          ]\n        }\n      },\n      {\n        \"number\": 28,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Up\"\n          ]\n        }\n      },\n      {\n        \"number\": 30,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Right\"\n          ]\n        }\n      },\n      {\n        \"number\": 33,\n        \"name\": \"blue_green\",\n        \"hash\": 18249686492888207164\n      },\n      {\n        \"number\": 34,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Left\"\n          ]\n        }\n      },\n      {\n        \"number\": 37,\n        \"name\": \"green\",\n        \"hash\": 14921198101292999649\n      },\n      {\n        \"number\": 38,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Left\"\n          ]\n        }\n      },\n      {\n        \"number\": 40,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Up\"\n          ]\n        }\n      },\n      {\n        \"number\": 43,\n        \"name\": \"blue\",\n        \"hash\": 3927332191806565676\n      },\n      {\n        \"number\": 44,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Left\"\n          ]\n        }\n      },\n      {\n        \"number\": 47,\n        \"name\": \"no_emphasis\",\n        \"hash\": 12673552706454460131\n      }\n    ]\n  },\n  {\n    \"name\": \"scanline\",\n    \"frames\": [\n      {\n        \"number\": 10,\n        \"hash\": 5656951408102020933\n      },\n      {\n        \"number\": 11,\n        \"hash\": 5898495566341282124\n      },\n      {\n        \"number\": 12,\n        \"hash\": 5656951408102020933\n      },\n      {\n        \"number\": 13,\n        \"hash\": 5656951408102020933\n      }\n    ]\n  },\n  {\n    \"name\": \"color\",\n    \"frames\": [\n      {\n        \"number\": 10,\n        \"hash\": 12341758549234391147\n      },\n      {\n        \"number\": 12,\n        \"hash\": 12341758549234391147\n      }\n    ]\n  },\n  {\n    \"name\": \"tv\",\n    \"frames\": [\n      {\n        \"number\": 0,\n        \"action\": {\n          \"SetVideoFilter\": \"Ntsc\"\n        }\n      },\n      {\n        \"number\": 10,\n        \"hash\": 6270529304864431060\n      },\n      {\n        \"number\": 11,\n        \"action\": {\n          \"Joypad\": [\n            \"One\",\n            \"Start\"\n          ]\n        }\n      },\n      {\n        \"number\": 14,\n        \"hash\": 18284547599417793041\n      },\n      {\n        \"number\": 15,\n        \"hash\": 16880644943449212899\n      }\n    ]\n  },\n  {\n    \"name\": \"_240pee\",\n    \"frames\": [\n      {\n        \"number\": 30,\n        \"hash\": 8240044214565782238\n      },\n      {\n        \"number\": 32,\n        \"hash\": 8240044214565782238\n      }\n    ]\n  }\n]"
  },
  {
    "path": "tetanes-core/test_roms/ppu/tv.txt",
    "content": "TV pass or fail?\n\nThis program is designed for NES and tests various aspects of the\ndisplay it is connected to.  Press the A Button to switch screens.\n\n_____________________________________________________________________\nNTSC chroma/luma crosstalk\n\nThe PPU in the PlayChoice arcade system generates RGB video, with\nred, green, and blue color information on separate cables.\n\nThe PPU in the NES generates composite video, with chroma (color)\nand luma (brightness) information carried on one cable at different\nfrequency bands.  To keep the circuit cheap, it does not perform\nproper filtering to keep the chroma from bleeding into the luma.\nThis especially has an effect on 45 degree diagonal lines.  But an\naccurate emulator must preserve the same artifacts, as games such as\nBlaster Master rely on them to create the richest color palette.\n\nThis screen displays something noticeably different on an NTSC NES\nPPU vs. the RGB PPU that most PC based NES emulators emulate.\n\nDisplay on RGB system:            Display on NTSC system:\n,---------.                       ,---------.\n|  =====  |                       |  =====  |\n|%%%%%%%%%|                       | PASS!   |\n|         |                       |         |\n| PRESS A |                       | PRESS A |\n`---------'                       `---------'\n\n_____________________________________________________________________\nPixel aspect ratio\n\nPC displays most commonly generate square pixels.  A square pixel\non an NTSC display is 7/12 of a chroma cycle wide, but the NES PPU\ndid not generate square pixels.  Instead, it generated pixels 8/12\nof a chroma cycle wide, which are somewhat wider than they are tall.\nThis made games' graphics appear stretched.  If they are displayed\nwith square pixels on a PC based emulator, graphics will not appear\nwith the intended proportions.\n\nThis screen shows three rectangles.  One is a square on NTSC NES\nand PlayChoice, one is a square on PAL NES, and one is a square\nwith square pixels.\n\n_____________________________________________________________________\nLegal\n\nCopyright 2007 Damian Yerrick\nDo not distribute this quick and dirty preview version to the public\nuntil it has been tested on an NES.\n"
  },
  {
    "path": "tetanes-core/test_roms/ppu/vbl_nmi.txt",
    "content": "NES PPU Tests\n-------------\nThese tests verify the behavior and timing of the NTSC PPU's VBL flag,\nNMI enable, and NMI interrupt. Timing is tested to an accuracy of one\nPPU clock. Note that often the NES starts up with a different value in\nthe clock divider, causing PPU timing to be slightly different and fail\nsome of the tests. These test the timings that have been most fully\ndocumented and emulated.\n\n\n01-vbl_basics\n-------------\nTests basic VBL operation and VBL period.\n\n2) VBL period is way off\n3) Reading VBL flag should clear it\n4) Writing $2002 shouldn't affect VBL flag\n5) $2002 should be mirrored at $200A\n6) $2002 should be mirrored every 8 bytes up to $2FFA\n7) VBL period is too short with BG off\n8) VBL period is too long with BG off\n\n\n02-vbl_set_time\n---------------\nVerifies time VBL flag is set.\n\nReads $2002 twice and prints VBL flags from\nthem. Test is run one PPU clock later each time,\naround the time the flag is set.\n\n00 - V\n01 - V\n02 - V\n03 - V   ; after some resets this is - -\n04 - -   ; flag setting is suppressed\n05 V -\n06 V -\n07 V -\n08 V -\n\n\n03-vbl_clear_time\n-----------------\nTests time VBL flag is cleared.\n\nReads $2002 and prints VBL flag.\nTest is run one PPU clock later each line,\naround the time the flag is cleared.\n\n00 V\n01 V\n02 V\n03 V\n04 V\n05 V\n06 -\n07 -\n08 -\n\n\n04-nmi_control\n--------------\nTests immediate NMI behavior when enabling while VBL flag is already set\n\n2) Shouldn't occur when disabled\n3) Should occur when enabled and VBL begins\n4) $2000 should be mirrored every 8 bytes\n5) Should occur immediately if enabled while VBL flag is set\n6) Shouldn't occur if enabled while VBL flag is clear\n7) Shouldn't occur again if writing $80 when already enabled\n8) Shouldn't occur again if writing $80 when already enabled 2\n9) Should occur again if enabling after disabled\n10) Should occur again if enabling after disabled 2\n11) Immediate occurence should be after NEXT instruction\n\n\n05-nmi_timing\n-------------\nTests NMI timing.\n\nPrints which instruction NMI occurred\nafter. Test is run one PPU clock later\neach line.\n\n00 4\n01 4\n02 4\n03 3\n04 3\n05 3\n06 3\n07 3\n08 3\n09 2\n\n\n06-suppression\n--------------\nTests behavior when $2002 is read near time\nVBL flag is set.\n\nReads $2002 one PPU clock later each time.\nPrints whether VBL flag read back as set, and\nwhether NMI occurred.\n\n00 - N\n01 - N\n02 - N\n03 - N  ; normal behavior\n04 - -  ; flag never set, no NMI\n05 V -  ; flag read back as set, but no NMI\n06 V -\n07 V N  ; normal behavior\n08 V N\n09 V N\n\n\n07-nmi_on_timing\n----------------\nTests NMI occurrence when enabled near time\nVBL flag is cleared.\n\nEnables NMI one PPU clock later on each line.\nPrints whether NMI occurred.\n\n00 N\n01 N\n02 N\n03 N\n04 N\n05 -\n06 -\n07 -\n08 -\n\n\n08-nmi_off_timing\n-----------------\nTests NMI occurrence when disabled near time\nVBL flag is set.\n\nDisables NMI one PPU clock later on each line.\nPrints whether NMI occurred.\n\n03 -\n04 -\n05 -\n06 -\n07 N\n08 N\n09 N\n0A N\n0B N\n0C N\n\n\n09-even_odd_frames\n------------------\nTests clock skipped on every other PPU frame when BG rendering\nis enabled.\n\nTries pattern of BG enabled/disabled during a sequence of\n5 frames, then finds how many clocks were skipped. Prints\nnumber skipped clocks to help find problems.\n\nCorrect output: 00 01 01 02\n\n\n10-even_odd_timing\n------------------\nTests timing of skipped clock every other frame\nwhen BG is enabled.\n\nOutput: 08 08 09 07 \n\n2) Clock is skipped too soon, relative to enabling BG\n3) Clock is skipped too late, relative to enabling BG\n4) Clock is skipped too soon, relative to disabling BG\n5) Clock is skipped too late, relative to disabling BG\n\nMulti-tests\n-----------\nThe NES/NSF builds in the main directory consist of multiple sub-tests.\nWhen run, they list the subtests as they are run. The final result code\nrefers to the first sub-test that failed. For more information about any\nfailed subtests, run them individually from rom_singles/ and\nnsf_singles/.\n\n\nFlashes, clicks, other glitches\n-------------------------------\nIf a test prints \"passed\", it passed, even if there were some flashes or\nodd sounds. Only a test which prints \"done\" at the end requires that you\nwatch/listen while it runs in order to determine whether it passed. Such\ntests involve things which the CPU cannot directly test.\n\n\nAlternate output\n----------------\nTests generally print information on screen, but also report the final\nresult audibly, and output text to memory, in case the PPU doesn't work\nor there isn't one, as in an NSF or a NES emulator early in development.\n\nAfter the tests are done, the final result is reported as a series of\nbeeps (see below). For NSF builds, any important diagnostic bytes are\nalso reported as beeps, before the final result.\n\n\nOutput at $6000\n---------------\nAll text output is written starting at $6004, with a zero-byte\nterminator at the end. As more text is written, the terminator is moved\nforward, so an emulator can print the current text at any time.\n\nThe test status is written to $6000. $80 means the test is running, $81\nmeans the test needs the reset button pressed, but delayed by at least\n100 msec from now. $00-$7F means the test has completed and given that\nresult code.\n\nTo allow an emulator to know when one of these tests is running and the\ndata at $6000+ is valid, as opposed to some other NES program, $DE $B0\n$G1 is written to $6001-$6003.\n\n\nAudible output\n--------------\nA byte is reported as a series of tones. The code is in binary, with a\nlow tone for 0 and a high tone for 1, and with leading zeroes skipped.\nThe first tone is always a zero. A final code of 0 means passed, 1 means\nfailure, and 2 or higher indicates a specific reason. See the source\ncode of the test for more information about the meaning of a test code.\nThey are found after the set_test macro. For example, the cause of test\ncode 3 would be found in a line containing set_test 3. Examples:\n\n\tTones         Binary  Decimal  Meaning\n\t- - - - - - - - - - - - - - - - - - - - \n\tlow              0      0      passed\n\tlow high        01      1      failed\n\tlow high low   010      2      error 2\n\n\nNSF versions\n------------\nMany NSF-based tests require that the NSF player either not interrupt\nthe init routine with the play routine, or if it does, not interrupt the\nplay routine again if it hasn't returned yet. This is because many tests\nneed to run for a while without returning.\n\nNSF versions also make periodic clicks to prevent the NSF player from\nthinking the track is silent and thus ending the track before it's done\ntesting.\n\n-- \nShay Green <gblargg@gmail.com>\n"
  },
  {
    "path": "tetanes-core/test_roms/ppu/vbl_nmi_timing.txt",
    "content": "NTSC NES PPU VBL/NMI Timing Tests\n---------------------------------\nThese ROMs test the timing of the VBL flag and NMI to an accuracy of a\nsingle PPU clock, and also check special cases. They have been tested on\nan actual NES and all give a passing result. Sometimes the NES starts up\nwith a different PPU timing that causes some of the tests to fail; these\ntests don't check that timing arrangement.\n\nEach ROM runs several tests and reports the result on screen and by\nbeeping a number of times. See below for the meaning of failure codes\nfor each test. It's best to run the tests in order, because later ROMs\ndepend on things tested by earlier ROMs and will give erroneous results\nif any earlier ones failed.\n\nSource code for each test is included, and most tests are clearly\ndivided into sections. Support code is also included, but it runs on a\ncustom devcart and assembler so it will require some effort to assemble.\nContact me if you'd like assistance porting them to your setup.\n\n\n1.frame_basics\n--------------\nTests basic VBL flag operation and general timing of PPU frames.\n\n2) VBL flag isn't being set\n3) VBL flag should be cleared after being read\n4) PPU frame with BG enabled is too short\n5) PPU frame with BG enabled is too long\n6) PPU frame with BG disabled is too short\n7) PPU frame with BG disabled is too long\n\n\n2.vbl_timing\n------------\nTests timing of VBL being set, and special case where reading VBL flag\nas it would be set causes it to not be set for that frame.\n\n2) Flag should read as clear 3 PPU clocks before VBL\n3) Flag should read as set 0 PPU clocks after VBL\n4) Flag should read as clear 2 PPU clocks before VBL\n5) Flag should read as set 1 PPU clock after VBL\n6) Flag should read as clear 1 PPU clock before VBL\n7) Flag should read as set 2 PPU clocks after VBL\n8) Reading 1 PPU clock before VBL should suppress setting\n\n\n3.even_odd_frames\n-----------------\nTest clock skipped when BG is enabled on odd PPU frames. Tests\nenable/disable BG during 5 consecutive frames, then see how many clocks\nwere skipped. Patterns are shown as XXXXX, where each X can either be B\n(BG enabled) or - (BG disabled).\n\n2) Pattern ----- should not skip any clocks\n3) Pattern BB--- should skip 1 clock\n4) Pattern B--B- (one even, one odd) should skip 1 clock\n5) Pattern -B--B (one odd, one even) should skip 1 clock\n6) Pattern BB-BB (two pairs) should skip 2 clocks\n\n\n4.vbl_clear_timing\n------------------\nTests timing of VBL flag clearing.\n\n2) Cleared 3 or more PPU clocks too early\n3) Cleared 2 PPU clocks too early\n4) Cleared 1 PPU clock too early \n5) Cleared 3 or more PPU clocks too late\n6) Cleared 2 PPU clocks too late\n7) Cleared 1 PPU clock too late\n\n\n5.nmi_suppression\n-----------------\nTests timing of NMI suppression when reading VBL flag just as it's set,\nand that this doesn't occur when reading one clock before or after.\n\n2) Reading flag 3 PPU clocks before set shouldn't suppress NMI\n3) Reading flag when it's set should suppress NMI\n4) Reading flag 3 PPU clocks after set shouldn't suppress NMI\n5) Reading flag 2 PPU clocks before set shouldn't suppress NMI\n6) Reading flag 1 PPU clock after set should suppress NMI\n7) Reading flag 4 PPU clocks after set shouldn't suppress NMI\n8) Reading flag 4 PPU clocks before set shouldn't suppress NMI\n9) Reading flag 1 PPU clock before set should suppress NMI\n10)Reading flag 2 PPU clocks after set shouldn't suppress NMI\n\n432101234\n---+?+---\n\n\n6.nmi_disable\n-------------\nTests NMI occurrence when disabling NMI just as VBL flag is set, and\njust after.\n\n2) NMI shouldn't occur when disabled 0 PPU clocks after VBL\n3) NMI should occur when disabled 3 PPU clocks after VBL\n4) NMI shouldn't occur when disabled 1 PPU clock after VBL\n5) NMI should occur when disabled 4 PPU clocks after VBL\n6) NMI shouldn't occur when disabled 1 PPU clock before VBL\n7) NMI should occur when disabled 2 PPU clocks after VBL\n\n\n7.nmi_timing\n------------\nTests timing of NMI and immediate occurrence when enabled with VBL flag\nalready set.\n\n2) NMI occurred 3 or more PPU clocks too early\n3) NMI occurred 2 PPU clocks too early\n4) NMI occurred 1 PPU clock too early\n5) NMI occurred 3 or more PPU clocks too late\n6) NMI occurred 2 PPU clocks too late\n7) NMI occurred 1 PPU clock too late\n8) NMI should occur if enabled when VBL already set\n9) NMI enabled when VBL already set should delay 1 instruction\n10)NMI should be possible multiple times in VBL\n\n-- \nShay Green <hotpop.com@blargg> (swap to e-mail)\n"
  },
  {
    "path": "tetanes-utils/Cargo.toml",
    "content": "[package]\nname = \"tetanes-utils\"\nversion.workspace = true\nedition.workspace = true\nlicense.workspace = true\nauthors.workspace = true\nreadme.workspace = true\nrepository.workspace = true\nhomepage.workspace = true\npublish = false\n\n[[bin]]\nname = \"list_boards\"\ntest = false\nbench = false\n\n[[bin]]\nname = \"generate_db\"\ntest = false\nbench = false\n\n[dependencies]\nanyhow.workspace = true\nclap.workspace = true\ntetanes-core.workspace = true\n"
  },
  {
    "path": "tetanes-utils/src/bin/generate_db.rs",
    "content": "use anyhow::Context;\nuse clap::Parser;\nuse std::{\n    env,\n    ffi::OsStr,\n    fs::File,\n    io::{BufWriter, Write},\n    path::{Path, PathBuf},\n};\nuse tetanes_core::{\n    cart::{Cart, GameInfo},\n    common::NesRegion,\n    fs,\n    mem::RamState,\n    ppu::Mirroring,\n};\n\nconst GAME_DB_TXT: &str = \"tetanes-core/game_database.txt\";\nconst GAME_DB: &str = \"tetanes-core/game_db.dat\";\n\nfn main() -> anyhow::Result<()> {\n    let opt = Opt::parse();\n    let path = opt\n        .path\n        .unwrap_or_else(|| env::current_dir().unwrap_or_default());\n    let header = \"# CRC, Region, Mapper, Sub-Mapper, ChrBanks, PrgRomBanks, PrgRamBanks, Battery, Mirroring, SubMapper, Title\";\n    if path.is_dir() {\n        let mut db_txt_file = BufWriter::new(\n            File::create(GAME_DB_TXT).with_context(|| format!(\"failed to open {GAME_DB_TXT}\"))?,\n        );\n        let mut games = path\n            .read_dir()\n            .unwrap_or_else(|err| panic!(\"unable read directory {path:?}: {err}\"))\n            .filter_map(Result::ok)\n            .filter(|f| f.path().extension() == Some(OsStr::new(\"nes\")))\n            .map(|f| f.path())\n            .map(Game::new)\n            .filter_map(Result::ok)\n            .collect::<Vec<_>>();\n        games.sort_by_key(|game| game.crc32);\n        let mut entries = Vec::with_capacity(games.len());\n        writeln!(db_txt_file, \"{header}\")?;\n        for game in &mut games {\n            apply_corrections(game);\n\n            let Game {\n                crc32,\n                region,\n                mapper,\n                submapper,\n                chr_banks,\n                prg_rom_banks,\n                prg_ram_banks,\n                battery,\n                mirroring,\n                title,\n            } = game;\n\n            writeln!(\n                db_txt_file,\n                \"  {crc32:8X}, {region}, {mapper}, {submapper}, {chr_banks}, {prg_rom_banks}, {prg_ram_banks}, {battery}, {mirroring:?}, {title:?}\",\n            )?;\n            entries.push(GameInfo {\n                crc32: *crc32,\n                region: *region,\n                mapper_num: *mapper,\n                submapper_num: *submapper,\n            });\n        }\n        fs::save(GAME_DB, &entries)?;\n    } else if path.is_file() {\n        todo!(\"adding individual games is not yet supported\");\n    }\n    Ok(())\n}\n\nfn apply_corrections(game: &mut Game) {\n    match game.crc32 {\n        // Mapper 210 games incorrectly marked as Mapper 19\n        0x808606F0 | 0x81B7F1A8 | 0xC247CC80 | 0xC47946D => {\n            // Famista '91\n            // Heisei Tensai Bakabon\n            // Family Circuit '91\n            // Chibi Maruko-chan: Uki Uki Shopping\n            // Dream Master - TODO: Missing crc\n            game.mapper = 210;\n            game.submapper = 1;\n        }\n        0x1DC0F740 | 0x429103C9 | 0x46FD7843 | 0x47232739 | 0x6EC51DE5 | 0xADFFD64F\n        | 0xD323B806 => {\n            // Famista '92\n            // Famista '93\n            // Famista '94\n            // Splatterhouse: Wanpaku Graffiti\n            // Top Striker\n            // Wagyan Land 2\n            // Wagyan Land 3\n            game.mapper = 210;\n            game.submapper = 2;\n        }\n        0x5CAA3E61 => {\n            // Death Race: <https://www.nesdev.org/wiki/INES_Mapper_144>\n            game.mapper = 144;\n        }\n        0xD1691028 => {\n            // Devil Man: <https://www.nesdev.org/wiki/INES_Mapper_154>\n            game.mirroring = Mirroring::Horizontal;\n            game.mapper = 154;\n        }\n        _ => (),\n    }\n}\n\n#[derive(Debug)]\n#[must_use]\npub struct Game {\n    crc32: u32,\n    region: NesRegion,\n    mapper: u16,\n    submapper: u8,\n    chr_banks: usize,\n    prg_rom_banks: usize,\n    prg_ram_banks: usize,\n    battery: bool,\n    mirroring: Mirroring,\n    title: String,\n}\n\nimpl Game {\n    fn new<P: AsRef<Path>>(path: P) -> anyhow::Result<Game> {\n        let path = path.as_ref();\n        let cart = Cart::from_path(path, RamState::default())?;\n        let mut crc32 = fs::compute_crc32(&cart.prg_rom);\n        if !cart.chr_rom.is_empty() {\n            crc32 = fs::compute_combine_crc32(crc32, &cart.chr_rom);\n        }\n        let filename = path.file_name().unwrap_or_default();\n        let region = match filename.to_str() {\n            Some(filename) => {\n                if filename.contains(\"Europe\") || filename.contains(\"PAL\") {\n                    NesRegion::Pal\n                } else {\n                    NesRegion::Ntsc\n                }\n            }\n            None => NesRegion::Ntsc,\n        };\n\n        let chr_size = cart.chr_size();\n        let chr_banks = chr_size / (8 * 1024);\n        let prg_rom_banks = cart.prg_rom_size / (16 * 1024);\n        let prg_ram_banks = cart.prg_ram_size / (16 * 1024);\n        let mirroring = cart.mirroring();\n\n        Ok(Game {\n            crc32,\n            region,\n            mapper: cart.mapper_num(),\n            submapper: cart.submapper_num(),\n            chr_banks,\n            prg_rom_banks,\n            prg_ram_banks,\n            battery: cart.battery_backed(),\n            mirroring,\n            title: filename.to_string_lossy().to_string(),\n        })\n    }\n}\n\n#[derive(Parser, Debug)]\n#[must_use]\nstruct Opt {\n    /// The NES ROM or a directory containing `.nes` ROM files. [default: current directory]\n    path: Option<PathBuf>,\n}\n"
  },
  {
    "path": "tetanes-utils/src/bin/list_boards.rs",
    "content": "use clap::Parser;\nuse std::{\n    env,\n    ffi::OsStr,\n    path::{Path, PathBuf},\n};\nuse tetanes_core::{cart::Cart, mem::RamState};\n\nfn main() -> anyhow::Result<()> {\n    let opt = Opt::parse();\n    let path = opt\n        .path\n        .unwrap_or_else(|| env::current_dir().unwrap_or_default());\n    let board = opt.board.map(|b| b.to_lowercase());\n    if path.is_dir() {\n        let paths: Vec<PathBuf> = path\n            .read_dir()\n            .unwrap_or_else(|err| panic!(\"unable read directory {path:?}: {err}\"))\n            .filter_map(Result::ok)\n            .filter(|f| f.path().extension() == Some(OsStr::new(\"nes\")))\n            .map(|f| f.path())\n            .collect();\n        let mut boards: Vec<String> = paths\n            .iter()\n            .map(get_mapper)\n            .filter_map(Result::ok)\n            .filter(|b| match &board {\n                Some(board) => b.to_lowercase().contains(board),\n                None => true,\n            })\n            .collect();\n        boards.sort();\n        for board in &boards {\n            println!(\"{board}\");\n        }\n    } else if path.is_file() {\n        println!(\"{}\", get_mapper(&path)?);\n    }\n    Ok(())\n}\n\nfn get_mapper<P: AsRef<Path>>(path: P) -> anyhow::Result<String> {\n    let cart = Cart::from_path(path, RamState::default())?;\n    Ok(format!(\"{:<50} {:?}\", cart.mapper_board(), cart.name()))\n}\n\n#[derive(Parser, Debug)]\n#[must_use]\nstruct Opt {\n    /// The NES ROM or a directory containing `.nes` ROM files. [default: current directory]\n    path: Option<PathBuf>,\n    /// The NES Mapper Board to filter by.\n    board: Option<String>,\n}\n"
  }
]