Merge branch 'master' into i2s

This commit is contained in:
CasualPokePlayer 2025-08-02 15:30:46 -07:00 committed by GitHub
commit 975de7fe11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
320 changed files with 19877 additions and 12182 deletions

View File

@ -4,15 +4,17 @@ on:
push:
branches:
- master
- ci/vcpkg-update
- ci/*
pull_request:
branches:
- master
env:
VCPKG_COMMIT: 2ad004460f5db4d3b66f62f5799ff66c265c4b5d
MELONDS_GIT_BRANCH: ${{ github.ref }}
MELONDS_GIT_HASH: ${{ github.sha }}
MELONDS_BUILD_PROVIDER: GitHub Actions
# MELONDS_VERSION_SUFFIX: " RC"
jobs:
build-macos:
@ -33,7 +35,7 @@ jobs:
- name: Set up vcpkg
uses: lukka/run-vcpkg@v11
with:
vcpkgGitCommitId: 10b7a178346f3f0abef60cecd5130e295afd8da4
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT }}
- name: Build
uses: lukka/run-cmake@v10
with:

View File

@ -4,6 +4,7 @@ on:
push:
branches:
- master
- ci/*
pull_request:
branches:
- master
@ -12,80 +13,51 @@ env:
MELONDS_GIT_BRANCH: ${{ github.ref }}
MELONDS_GIT_HASH: ${{ github.sha }}
MELONDS_BUILD_PROVIDER: GitHub Actions
# MELONDS_VERSION_SUFFIX: " RC"
jobs:
build-x86_64:
name: x86_64
runs-on: ubuntu-22.04
build:
continue-on-error: true
strategy:
matrix:
arch:
- runner: ubuntu-22.04
name: x86_64
- runner: ubuntu-22.04-arm
name: aarch64
name: ${{ matrix.arch.name }}
runs-on: ${{ matrix.arch.runner }}
steps:
- uses: actions/checkout@v4
name: Check out sources
- name: Install dependencies
run: |
sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list
sudo apt update
sudo apt install --allow-downgrades cmake ninja-build extra-cmake-modules libpcap0.8-dev libsdl2-dev libenet-dev \
qt6-{base,base-private,multimedia}-dev libqt6svg6-dev libarchive-dev libzstd-dev libfuse2
- name: Configure
run: cmake -B build -G Ninja -DUSE_QT6=ON -DCMAKE_INSTALL_PREFIX=/usr -DMELONDS_EMBED_BUILD_INFO=ON
run: cmake -B build -G Ninja -DCMAKE_INSTALL_PREFIX=/usr -DMELONDS_EMBED_BUILD_INFO=ON
- name: Build
run: |
cmake --build build
DESTDIR=AppDir cmake --install build
- uses: actions/upload-artifact@v4
with:
name: melonDS-ubuntu-x86_64
name: melonDS-ubuntu-${{ matrix.arch.name }}
path: AppDir/usr/bin/melonDS
- name: Fetch AppImage tools
run: |
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-${{ matrix.arch.name }}.AppImage
wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-${{ matrix.arch.name }}.AppImage
chmod a+x linuxdeploy-*.AppImage
- name: Build the AppImage
env:
QMAKE: /usr/lib/qt6/bin/qmake
run: |
./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt --output appimage
./linuxdeploy-${{ matrix.arch.name }}.AppImage --appdir AppDir --plugin qt --output appimage
- uses: actions/upload-artifact@v4
with:
name: melonDS-appimage-x86_64
name: melonDS-appimage-${{ matrix.arch.name }}
path: melonDS*.AppImage
build-aarch64:
name: aarch64
runs-on: ubuntu-latest
container: ubuntu:22.04
steps:
- name: Prepare system
shell: bash
run: |
dpkg --add-architecture arm64
sh -c "sed \"s|^deb \([a-z\.:/]*\) \([a-z\-]*\) \(.*\)$|deb [arch=amd64] \1 \2 \3\ndeb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports \2 \3|\" /etc/apt/sources.list > /etc/apt/sources.list.new"
rm /etc/apt/sources.list
mv /etc/apt/sources.list{.new,}
apt update
apt -y full-upgrade
apt -y install git {gcc-12,g++-12}-aarch64-linux-gnu cmake ninja-build extra-cmake-modules \
{libsdl2,qt6-{base,base-private,multimedia},libqt6svg6,libarchive,libzstd,libenet}-dev:arm64 \
pkg-config dpkg-dev
- name: Check out source
uses: actions/checkout@v4
- name: Configure
shell: bash
run: |
cmake -B build -G Ninja \
-DPKG_CONFIG_EXECUTABLE=/usr/bin/aarch64-linux-gnu-pkg-config \
-DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc-12 \
-DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++-12 \
-DUSE_QT6=ON \
-DMELONDS_EMBED_BUILD_INFO=ON
- name: Build
shell: bash
run: |
cmake --build build
- uses: actions/upload-artifact@v4
with:
name: melonDS-ubuntu-aarch64
path: build/melonDS

View File

@ -10,9 +10,11 @@ on:
- master
env:
VCPKG_COMMIT: 2ad004460f5db4d3b66f62f5799ff66c265c4b5d
MELONDS_GIT_BRANCH: ${{ github.ref }}
MELONDS_GIT_HASH: ${{ github.sha }}
MELONDS_BUILD_PROVIDER: GitHub Actions
# MELONDS_VERSION_SUFFIX: " RC"
jobs:
build:
@ -32,7 +34,7 @@ jobs:
- name: Set up vcpkg
uses: lukka/run-vcpkg@v11
with:
vcpkgGitCommitId: 10b7a178346f3f0abef60cecd5130e295afd8da4
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT }}
- name: Configure
run: cmake --preset=release-mingw-x86_64 -DMELONDS_EMBED_BUILD_INFO=ON
- name: Build

81
BUILD.md Normal file
View File

@ -0,0 +1,81 @@
# Building melonDS
* [Linux](#linux)
* [Windows](#windows)
* [macOS](#macos)
## Linux
1. Install dependencies:
* Ubuntu:
* All versions: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev libarchive-dev libenet-dev libzstd-dev`
* 24.04: `sudo apt install qt6-{base,base-private,multimedia,svg}-dev`
* 22.04: `sudo apt install qtbase6-dev qtbase6-private-dev qtmultimedia6-dev libqt6svg6-dev`
* Older versions: `sudo apt install qtbase5-dev qtbase5-private-dev qtmultimedia5-dev libqt5svg5-dev`
Also add `-DUSE_QT6=OFF` to the first CMake command below.
* Fedora: `sudo dnf install gcc-c++ cmake extra-cmake-modules SDL2-devel libarchive-devel enet-devel libzstd-devel qt6-{qtbase,qtbase-private,qtmultimedia,qtsvg}-devel wayland-devel`
* Arch Linux: `sudo pacman -S base-devel cmake extra-cmake-modules git libpcap sdl2 qt6-{base,multimedia,svg} libarchive enet zstd`
2. Download the melonDS repository and prepare:
```bash
git clone https://github.com/melonDS-emu/melonDS
cd melonDS
```
3. Compile:
```bash
cmake -B build
cmake --build build -j$(nproc --all)
```
## Windows
1. Install [MSYS2](https://www.msys2.org/)
2. Open the MSYS2 terminal from the Start menu:
* For x64 systems (most common), use **MSYS2 UCRT64**
* For ARM64 systems, use **MSYS2 CLANGARM64**
3. Update the packages using `pacman -Syu` and reopen the same terminal if it asks you to
4. Install git and clone the repository
```bash
pacman -S git
git clone https://github.com/melonDS-emu/melonDS
cd melonDS
```
5. Install dependencies:
Replace `<prefix>` below with `mingw-w64-ucrt-x86_64` on x64 systems, or `mingw-w64-clang-aarch64` on ARM64 systems.
```bash
pacman -S <prefix>-{toolchain,cmake,SDL2,libarchive,enet,zstd}
```
6. Install Qt and configure the build directory
* Dynamic builds (with DLLs)
1. Install Qt: `pacman -S <prefix>-{qt6-base,qt6-svg,qt6-multimedia,qt6-svg,qt6-tools}`
2. Set up the build directory with `cmake -B build`
* Static builds (without DLLs, standalone executable)
1. Install Qt: `pacman -S <prefix>-qt5-static`
(Note: As of writing, the `qt6-static` package does not work.)
2. Set up the build directory with `cmake -B build -DBUILD_STATIC=ON -DUSE_QT6=OFF -DCMAKE_PREFIX_PATH=$MSYSTEM_PREFIX/qt5-static`
7. Compile: `cmake --build build`
If everything went well, melonDS should now be in the `build` folder. For dynamic builds, you may need to run melonDS from the MSYS2 terminal in order for it to find the required DLLs.
## macOS
1. Install the [Homebrew Package Manager](https://brew.sh)
2. Install dependencies: `brew install git pkg-config cmake sdl2 qt@6 libarchive enet zstd`
3. Download the melonDS repository and prepare:
```zsh
git clone https://github.com/melonDS-emu/melonDS
cd melonDS
```
4. Compile:
```zsh
cmake -B build -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)"
cmake --build build -j$(sysctl -n hw.logicalcpu)
```
If everything went well, melonDS.app should now be in the `build` directory.
### Self-contained app bundle
If you want an app bundle that can be distributed to other computers without needing to install dependencies through Homebrew, you can additionally run `
../tools/mac-libs.rb .` after the build is completed, or add `-DMACOS_BUNDLE_LIBS=ON` to the first CMake command.
## Nix (macOS/Linux)
melonDS provides a Nix flake with support for both macOS and Linux. The [Nix package manager](https://nixos.org) needs to be installed to use it.
* To run melonDS, just type `nix run github:melonDS-emu/melonDS`.
* To get a shell for development, clone the melonDS repository and type `nix develop` in its directory.

View File

@ -9,6 +9,7 @@ set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/DefaultBuildFlags.cmake")
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")
option(USE_VCPKG "Use vcpkg for dependency packages" OFF)
if (USE_VCPKG)
@ -16,7 +17,7 @@ if (USE_VCPKG)
endif()
project(melonDS
VERSION 0.9.5
VERSION 1.0
DESCRIPTION "DS emulator, sorta"
HOMEPAGE_URL "https://melonds.kuribo64.net"
LANGUAGES C CXX)
@ -29,8 +30,6 @@ include(CheckIPOSupported)
include(SetupCCache)
include(Sanitizers)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version")
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)

View File

@ -27,10 +27,6 @@
"binaryDir": "${sourceDir}/build/release-mingw-x86_64",
"generator": "Ninja",
"cacheVariables": {
"USE_QT6": {
"type": "BOOL",
"value": "ON"
},
"BUILD_STATIC": {
"type": "BOOL",
"value": "ON"

View File

@ -2,7 +2,7 @@
<h2 align="center"><b>melonDS</b></h2>
<p align="center">
<a href="http://melonds.kuribo64.net/" alt="melonDS website"><img src="https://img.shields.io/badge/website-melonds.kuribo64.net-%2331352e.svg"></a>
<a href="http://melonds.kuribo64.net/downloads.php" alt="Release: 0.9.5"><img src="https://img.shields.io/badge/release-0.9.5-%235c913b.svg"></a>
<a href="http://melonds.kuribo64.net/downloads.php" alt="Release: 1.0"><img src="https://img.shields.io/badge/release-1.0-%235c913b.svg"></a>
<a href="https://www.gnu.org/licenses/gpl-3.0" alt="License: GPLv3"><img src="https://img.shields.io/badge/License-GPL%20v3-%23ff554d.svg"></a>
<a href="https://kiwiirc.com/client/irc.badnik.net/?nick=IRC-Source_?#melonds" alt="IRC channel: #melonds"><img src="https://img.shields.io/badge/IRC%20chat-%23melonds-%23dd2e44.svg"></a>
<a href="https://discord.gg/pAMAtExcqV" alt="Discord"><img src="https://img.shields.io/badge/Discord-Kuribo64-7289da?logo=discord&logoColor=white"></a>
@ -32,75 +32,7 @@ DS BIOS dumps from a DSi or 3DS can be used with no compatibility issues. DSi BI
As for the rest, the interface should be pretty straightforward. If you have a question, don't hesitate to ask, though!
## How to build
### Linux
1. Install dependencies:
* Ubuntu 22.04: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qtbase5-dev qtbase5-private-dev qtmultimedia5-dev libqt5svg5-dev libarchive-dev libenet-dev libzstd-dev`
* Older Ubuntu: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtbase5-private-dev qtmultimedia5-dev libqt5svg5-dev libarchive-dev libenet-dev libzstd-dev`
* Arch Linux: `sudo pacman -S base-devel cmake extra-cmake-modules git libpcap sdl2 qt5-base qt5-multimedia qt5-svg libarchive enet zstd`
3. Download the melonDS repository and prepare:
```bash
git clone https://github.com/melonDS-emu/melonDS
cd melonDS
```
3. Compile:
```bash
cmake -B build
cmake --build build -j$(nproc --all)
```
### Windows
1. Install [MSYS2](https://www.msys2.org/)
2. Open the **MSYS2 MinGW 64-bit** terminal
3. Update the packages using `pacman -Syu` and reopen the terminal if it asks you to
4. Install git to clone the repository
```bash
pacman -S git
```
5. Download the melonDS repository and prepare:
```bash
git clone https://github.com/melonDS-emu/melonDS
cd melonDS
```
#### Dynamic builds (with DLLs)
5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-base,qt5-svg,qt5-multimedia,qt5-svg,qt5-tools,libarchive,enet,zstd}`
6. Compile:
```bash
cmake -B build
cmake --build build
cd build
../tools/msys-dist.sh
```
If everything went well, melonDS and the libraries it needs should now be in the `dist` folder.
#### Static builds (without DLLs, standalone executable)
5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-static,libarchive,enet,zstd}`
6. Compile:
```bash
cmake -B build -DBUILD_STATIC=ON -DCMAKE_PREFIX_PATH=/mingw64/qt5-static
cmake --build build
```
If everything went well, melonDS should now be in the `build` folder.
### macOS
1. Install the [Homebrew Package Manager](https://brew.sh)
2. Install dependencies: `brew install git pkg-config cmake sdl2 qt@6 libarchive enet zstd`
3. Download the melonDS repository and prepare:
```zsh
git clone https://github.com/melonDS-emu/melonDS
cd melonDS
```
4. Compile:
```zsh
cmake -B build -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)"
cmake --build build -j$(sysctl -n hw.logicalcpu)
```
If everything went well, melonDS.app should now be in the `build` directory.
#### Self-contained app bundle
If you want an app bundle that can be distributed to other computers without needing to install dependencies through Homebrew, you can additionally run `
../tools/mac-libs.rb .` after the build is completed, or add `-DMACOS_BUNDLE_LIBS=ON` to the first CMake command.
See [BUILD.md](./BUILD.md) for build instructions.
## TODO LIST

View File

@ -9,7 +9,8 @@ if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}")
endif()
FetchContent_Declare(vcpkg
GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git"
GIT_TAG 2024.10.21
GIT_TAG 2ad004460f5db4d3b66f62f5799ff66c265c4b5d
EXCLUDE_FROM_ALL
SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg")
FetchContent_MakeAvailable(vcpkg)
endif()
@ -19,11 +20,7 @@ set(VCPKG_OVERLAY_TRIPLETS "${CMAKE_SOURCE_DIR}/cmake/overlay-triplets")
option(USE_RECOMMENDED_TRIPLETS "Use the recommended triplets that are used for official builds" ON)
# Duplicated here because it needs to be set before project()
if (NOT WIN32)
option(USE_QT6 "Build using Qt 6 instead of 5" ON)
else()
option(USE_QT6 "Build using Qt 6 instead of 5" OFF)
endif()
option(USE_QT6 "Use Qt 6 instead of Qt 5" ON)
# Since the Linux build pulls in glib anyway, we can just use upstream libslirp
if (UNIX AND NOT APPLE)

View File

@ -7,3 +7,9 @@ endif()
string(REPLACE "-O2" "-O3" CMAKE_C_FLAGS_RELEASE_INIT "${CMAKE_C_FLAGS_RELEASE_INIT}")
string(REPLACE "-O2" "-O3" CMAKE_CXX_FLAGS_RELEASE_INIT "${CMAKE_CXX_FLAGS_RELEASE_INIT}")
# Building with LTO causes an internal compiler error in GCC 15.1.0
if (CMAKE_CXX_COMPILER_ID STREQUAL GNU AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15.0 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 15.1.1)
set(ENABLE_LTO_RELEASE OFF CACHE BOOL "Enable LTO for release builds" FORCE)
set(ENABLE_LTO OFF CACHE BOOL "Enable LTO" FORCE)
endif()

View File

@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1729665710,
"narHash": "sha256-AlcmCXJZPIlO5dmFzV3V2XF6x/OpNWUV8Y/FMPGd8Z4=",
"lastModified": 1739020877,
"narHash": "sha256-mIvECo/NNdJJ/bXjNqIh8yeoSjVLAuDuTUzAo7dzs8Y=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2768c7d042a37de65bb1b5b3268fc987e534c49d",
"rev": "a79cfe0ebd24952b580b1cf08cd906354996d547",
"type": "github"
},
"original": {

View File

@ -19,9 +19,9 @@
then sourceInfo.dirtyShortRev
else sourceInfo.shortRev;
melonDS = pkgs.qt6.qtbase.stdenv.mkDerivation {
melonDS = pkgs.stdenv.mkDerivation {
pname = "melonDS";
version = "0.9.5-${shortRevision}";
version = "1.0-${shortRevision}";
src = ./.;
nativeBuildInputs = with pkgs; [
@ -74,8 +74,11 @@
drv = self.packages.${system}.default;
};
devShells = {
default = pkgs.mkShell.override { stdenv = pkgs.qt6.qtbase.stdenv; } {
default = pkgs.mkShell {
inputsFrom = [ self.packages.${system}.default ];
packages = with pkgs; [
qt6.qttools
];
};
# Shell for building static melonDS release builds with vcpkg
@ -92,7 +95,13 @@
libtool
ninja
pkg-config
python3
];
# Undo the SDK setup done by nixpkgs so we can use AppleClang
shellHook = ''
unset DEVELOPER_DIR SDKROOT MACOSX_DEPLOYMENT_TARGET
'';
};
};
}

15
melonDLDI/Makefile Normal file
View File

@ -0,0 +1,15 @@
AS = arm-none-eabi-as
LD = arm-none-eabi-ld
OBJCOPY = arm-none-eabi-objcopy
BIN = melonDLDI
all:
$(AS) $(BIN).s -o $(BIN).o
$(LD) $(BIN).o -Ttext 0xBF800000 -e 0xBF800000 -o $(BIN).elf
$(OBJCOPY) -O binary $(BIN).elf $(BIN).bin
xxd -i -n $(BIN) -c 16 $(BIN).bin $(BIN).h
clean:
rm -f $(BIN).h $(BIN).bin $(BIN).elf $(BIN).o

172
melonDLDI/melonDLDI.s Executable file
View File

@ -0,0 +1,172 @@
.arm
.text
.align 2
_start:
.word 0xBF8DA5ED
.string " Chishm"
.byte 1
.byte 9 @ size
.byte 0
.byte 0
.string "melonDS DLDI driver"
.align 6, 0
.word _start, melon_end
.word 0, 0
.word 0, 0
.word 0, 0
.ascii "MELN"
.word 0x23
.word melon_startup
.word melon_isInserted
.word melon_readSectors
.word melon_writeSectors
.word melon_clearStatus
.word melon_shutdown
.align 2
melon_startup:
mov r0, #1
bx lr
melon_isInserted:
mov r0, #1
bx lr
@ r0=cmd r1=sector r2=out (0=none)
_sendcmd:
mov r12, #0x04000000
add r12, r12, #0x1A0
@ init
mov r3, #0x8000
strh r3, [r12]
@ set cmd
strb r0, [r12, #0x8]
strb r1, [r12, #0xC]
mov r1, r1, lsr #8
strb r1, [r12, #0xB]
mov r1, r1, lsr #8
strb r1, [r12, #0xA]
mov r1, r1, lsr #8
strb r1, [r12, #0x9]
mov r1, r1, lsr #8
strb r1, [r12, #0xD]
strh r1, [r12, #0xE]
@ send
mov r3, #0xA0000000
orr r3, r3, r0, lsl #30
cmp r2, #0
orrne r3, r3, #0x01000000 @ block size
orr r3, r3, #0x00400000 @ KEY2
str r3, [r12, #0x4]
mov r3, #0x04100000
tst r0, #0x01
bne __send_write
@ receive data
tst r2, #0x3
bne __read_unal_loop
__read_busyloop:
ldr r0, [r12, #0x4]
tst r0, #0x80000000
bxeq lr
tst r0, #0x00800000
ldrne r1, [r3, #0x10] @ load data
strne r1, [r2], #4
b __read_busyloop
__read_unal_loop:
ldr r0, [r12, #0x4]
tst r0, #0x80000000
bxeq lr
tst r0, #0x00800000
beq __read_unal_loop
ldr r1, [r3, #0x10] @ load data
strb r1, [r2], #1
mov r1, r1, lsr #8
strb r1, [r2], #1
mov r1, r1, lsr #8
strb r1, [r2], #1
mov r1, r1, lsr #8
strb r1, [r2], #1
b __read_unal_loop
@ send data
__send_write:
mov r1, #0
tst r2, #0x3
bne __write_unal_loop
__write_busyloop:
ldr r0, [r12, #0x4]
tst r0, #0x80000000
bxeq lr
tst r0, #0x00800000
ldrne r1, [r2], #4
strne r1, [r3, #0x10] @ store data
b __write_busyloop
__write_unal_loop:
ldr r0, [r12, #0x4]
tst r0, #0x80000000
bxeq lr
tst r0, #0x00800000
beq __write_unal_loop
ldrb r1, [r2], #1
ldrb r0, [r2], #1
orr r1, r1, r0, lsl #8
ldrb r0, [r2], #1
orr r1, r1, r0, lsl #16
ldrb r0, [r2], #1
orr r1, r1, r0, lsl #24
str r1, [r3, #0x10] @ store data
b __write_unal_loop
@ r0=sector r1=numsectors r2=out
melon_readSectors:
stmdb sp!, {r3-r6, lr}
mov r4, r0
mov r5, r1
mov r6, #0
_readloop:
mov r0, #0xC0
add r1, r4, r6
bl _sendcmd
add r6, r6, #1
cmp r6, r5
bcc _readloop
ldmia sp!, {r3-r6, lr}
mov r0, #1
bx lr
@ r0=sector r1=numsectors r2=out
melon_writeSectors:
stmdb sp!, {r3-r6, lr}
mov r4, r0
mov r5, r1
mov r6, #0
_writeloop:
mov r0, #0xC1
add r1, r4, r6
bl _sendcmd
add r6, r6, #1
cmp r6, r5
bcc _writeloop
ldmia sp!, {r3-r6, lr}
mov r0, #1
bx lr
melon_clearStatus:
mov r0, #1
bx lr
melon_shutdown:
mov r0, #1
bx lr
melon_end:

BIN
res/icon/melon_192x192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

@ -2,6 +2,6 @@
<RCC version="1.0">
<qresource>
<file alias="melon-icon">icon/melon_256x256.png</file>
<file alias="melon-logo">melon.svg</file>
<file alias="melon-logo">melon384.png</file>
</qresource>
</RCC>

View File

@ -6,8 +6,8 @@
//include version information in .exe, modify these values to match your needs
1 VERSIONINFO
FILEVERSION ${melonDS_VERSION_MAJOR},${melonDS_VERSION_MINOR},${melonDS_VERSION_PATCH},0
PRODUCTVERSION ${melonDS_VERSION_MAJOR},${melonDS_VERSION_MINOR},${melonDS_VERSION_PATCH},0
FILEVERSION ${MELON_RC_VERSION}
PRODUCTVERSION ${MELON_RC_VERSION}
FILETYPE VFT_APP
{
BLOCK "StringFileInfo"
@ -18,7 +18,7 @@ FILETYPE VFT_APP
VALUE "FileVersion", "${melonDS_VERSION}"
VALUE "FileDescription", "melonDS emulator"
VALUE "InternalName", "SDnolem"
VALUE "LegalCopyright", "2016-2023 melonDS team"
VALUE "LegalCopyright", "2016-2025 melonDS team"
VALUE "LegalTrademarks", ""
VALUE "OriginalFilename", "melonDS.exe"
VALUE "ProductName", "melonDS"

BIN
res/melon384.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
res/melon512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -58,7 +58,8 @@ void AREngine::RunCheat(const ARCode& arcode)
for (;;)
{
if (code >= &arcode.Code[arcode.Code.size()])
if (code > &arcode.Code[arcode.Code.size() - 1])
// If the instruction pointer is past the end of the cheat code...
break;
u32 a = *code++;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -109,21 +109,13 @@ const u32 ARM::ConditionTable[16] =
ARM::ARM(u32 num, bool jit, std::optional<GDBArgs> gdb, melonDS::NDS& nds) :
#ifdef GDBSTUB_ENABLED
GdbStub(this, gdb ? (num ? gdb->PortARM7 : gdb->PortARM9) : 0),
BreakOnStartup(gdb ? (num ? gdb->ARM7BreakOnStartup : gdb->ARM9BreakOnStartup) : false),
GdbStub(this),
BreakOnStartup(false),
#endif
Num(num), // well uh
NDS(nds)
{
#ifdef GDBSTUB_ENABLED
if (gdb
#ifdef JIT_ENABLED
&& !jit // TODO: Should we support toggling the GdbStub without destroying the ARM?
#endif
)
GdbStub.Init();
IsSingleStep = false;
#endif
SetGdbArgs(jit ? std::nullopt : gdb);
}
ARM::~ARM()
@ -148,6 +140,20 @@ ARMv5::~ARMv5()
// DTCM is owned by Memory, not going to delete it
}
void ARM::SetGdbArgs(std::optional<GDBArgs> gdb)
{
#ifdef GDBSTUB_ENABLED
GdbStub.Close();
if (gdb)
{
int port = Num ? gdb->PortARM7 : gdb->PortARM9;
GdbStub.Init(port);
BreakOnStartup = Num ? gdb->ARM7BreakOnStartup : gdb->ARM9BreakOnStartup;
}
IsSingleStep = false;
#endif
}
void ARM::Reset()
{
Cycles = 0;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -68,6 +68,8 @@ public:
ARM(u32 num, bool jit, std::optional<GDBArgs> gdb, NDS& nds);
virtual ~ARM(); // destroy shit
void SetGdbArgs(std::optional<GDBArgs> gdb);
virtual void Reset();
virtual void DoSavestate(Savestate* file);
@ -323,6 +325,7 @@ public:
u32 CP15Control;
u32 RNGSeed;
u32 TraceProcessID;
u32 DTCMSetting, ITCMSetting;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -30,6 +30,7 @@
#include "ARMJIT_Internal.h"
#include "ARMJIT_Memory.h"
#include "ARMJIT_Compiler.h"
#include "ARMJIT_Global.h"
#include "ARMInterpreter_ALU.h"
#include "ARMInterpreter_LoadStore.h"
@ -467,6 +468,16 @@ InterpreterFunc InterpretTHUMB[ARMInstrInfo::tk_Count] =
};
#undef F
ARMJIT::ARMJIT(melonDS::NDS& nds, std::optional<JITArgs> jit) noexcept :
NDS(nds),
Memory(nds),
JITCompiler(nds),
MaxBlockSize(jit.has_value() ? std::clamp(jit->MaxBlockSize, 1u, 32u) : 32),
LiteralOptimizations(jit.has_value() ? jit->LiteralOptimizations : false),
BranchOptimizations(jit.has_value() ? jit->BranchOptimizations : false),
FastMemory((jit.has_value() ? jit->FastMemory : false) && ARMJIT_Memory::IsFastMemSupported())
{}
void ARMJIT::RetireJitBlock(JitBlock* block) noexcept
{
auto it = RestoreCandidates.find(block->InstrHash);
@ -483,6 +494,7 @@ void ARMJIT::RetireJitBlock(JitBlock* block) noexcept
void ARMJIT::SetJITArgs(JITArgs args) noexcept
{
args.FastMemory = args.FastMemory && ARMJIT_Memory::IsFastMemSupported();
args.MaxBlockSize = std::clamp(args.MaxBlockSize, 1u, 32u);
if (MaxBlockSize != args.MaxBlockSize
@ -499,36 +511,22 @@ void ARMJIT::SetJITArgs(JITArgs args) noexcept
void ARMJIT::SetMaxBlockSize(int size) noexcept
{
size = std::clamp(size, 1, 32);
if (size != MaxBlockSize)
ResetBlockCache();
MaxBlockSize = size;
SetJITArgs(JITArgs{static_cast<unsigned>(size), LiteralOptimizations, LiteralOptimizations, FastMemory});
}
void ARMJIT::SetLiteralOptimizations(bool enabled) noexcept
{
if (LiteralOptimizations != enabled)
ResetBlockCache();
LiteralOptimizations = enabled;
SetJITArgs(JITArgs{static_cast<unsigned>(MaxBlockSize), enabled, BranchOptimizations, FastMemory});
}
void ARMJIT::SetBranchOptimizations(bool enabled) noexcept
{
if (BranchOptimizations != enabled)
ResetBlockCache();
BranchOptimizations = enabled;
SetJITArgs(JITArgs{static_cast<unsigned>(MaxBlockSize), LiteralOptimizations, enabled, FastMemory});
}
void ARMJIT::SetFastMemory(bool enabled) noexcept
{
if (FastMemory != enabled)
ResetBlockCache();
FastMemory = enabled;
SetJITArgs(JITArgs{static_cast<unsigned>(MaxBlockSize), LiteralOptimizations, BranchOptimizations, enabled});
}
void ARMJIT::CompileBlock(ARM* cpu) noexcept
@ -918,7 +916,7 @@ void ARMJIT::CompileBlock(ARM* cpu) noexcept
AddressRange* region = CodeMemRegions[addressRanges[j] >> 27];
if (!PageContainsCode(&region[(addressRanges[j] & 0x7FFF000) / 512]))
if (!PageContainsCode(&region[(addressRanges[j] & 0x7FFF000 & ~(Memory.PageSize - 1)) / 512], Memory.PageSize))
Memory.SetCodeProtection(addressRanges[j] >> 27, addressRanges[j] & 0x7FFFFFF, true);
AddressRange* range = &region[(addressRanges[j] & 0x7FFFFFF) / 512];
@ -971,7 +969,7 @@ void ARMJIT::InvalidateByAddr(u32 localAddr) noexcept
range->Blocks.Remove(i);
if (range->Blocks.Length == 0
&& !PageContainsCode(&region[(localAddr & 0x7FFF000) / 512]))
&& !PageContainsCode(&region[(localAddr & 0x7FFF000 & ~(Memory.PageSize - 1)) / 512], Memory.PageSize))
{
Memory.SetCodeProtection(localAddr >> 27, localAddr & 0x7FFFFFF, false);
}
@ -1005,7 +1003,7 @@ void ARMJIT::InvalidateByAddr(u32 localAddr) noexcept
if (otherRange->Blocks.Length == 0)
{
if (!PageContainsCode(&otherRegion[(addr & 0x7FFF000) / 512]))
if (!PageContainsCode(&otherRegion[(addr & 0x7FFF000 & ~(Memory.PageSize - 1)) / 512], Memory.PageSize))
Memory.SetCodeProtection(addr >> 27, addr & 0x7FFFFFF, false);
otherRange->Code = 0;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -44,15 +44,7 @@ class JitBlock;
class ARMJIT
{
public:
ARMJIT(melonDS::NDS& nds, std::optional<JITArgs> jit) noexcept :
NDS(nds),
Memory(nds),
JITCompiler(nds),
MaxBlockSize(jit.has_value() ? std::clamp(jit->MaxBlockSize, 1u, 32u) : 32),
LiteralOptimizations(jit.has_value() ? jit->LiteralOptimizations : false),
BranchOptimizations(jit.has_value() ? jit->BranchOptimizations : false),
FastMemory(jit.has_value() ? jit->FastMemory : false)
{}
ARMJIT(melonDS::NDS& nds, std::optional<JITArgs> jit) noexcept;
~ARMJIT() noexcept;
void InvalidateByAddr(u32) noexcept;
void CheckAndInvalidateWVRAM(int) noexcept;
@ -80,6 +72,7 @@ private:
bool LiteralOptimizations = false;
bool BranchOptimizations = false;
bool FastMemory = false;
public:
melonDS::NDS& NDS;
TinyVector<u32> InvalidLiterals {};

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team, RSDuck
Copyright 2016-2025 melonDS team, RSDuck
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team, RSDuck
Copyright 2016-2025 melonDS team, RSDuck
This file is part of melonDS.
@ -83,7 +83,7 @@ void Compiler::Comp_JumpTo(u32 addr, bool forceNonConstantCycles)
// doesn't matter if we put garbage in the MSbs there
if (addr & 0x2)
{
cpu9->CodeRead32(addr-2, true) >> 16;
cpu9->CodeRead32(addr-2, true);
cycles += cpu9->CodeCycles;
cpu9->CodeRead32(addr+2, false);
cycles += CurCPU->CodeCycles;
@ -437,4 +437,4 @@ void Compiler::T_Comp_BL_Merged()
Comp_JumpTo(target);
}
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team, RSDuck
Copyright 2016-2025 melonDS team, RSDuck
This file is part of melonDS.
@ -22,17 +22,7 @@
#include "../ARMInterpreter.h"
#include "../ARMJIT.h"
#include "../NDS.h"
#if defined(__SWITCH__)
#include <switch.h>
extern char __start__;
#elif defined(_WIN32)
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif
#include "../ARMJIT_Global.h"
#include <stdlib.h>
@ -66,11 +56,6 @@ const int RegisterCache<Compiler, ARM64Reg>::NativeRegsAvailable = 15;
const BitSet32 CallerSavedPushRegs({W8, W9, W10, W11, W12, W13, W14, W15});
const int JitMemSize = 16 * 1024 * 1024;
#ifndef __SWITCH__
u8 JitMem[JitMemSize];
#endif
void Compiler::MovePC()
{
ADD(MapReg(15), MapReg(15), Thumb ? 2 : 4);
@ -260,29 +245,13 @@ Compiler::Compiler(melonDS::NDS& nds) : Arm64Gen::ARM64XEmitter(), NDS(nds)
SetCodeBase((u8*)JitRWStart, (u8*)JitRXStart);
JitMemMainSize = JitMemSize;
#else
#ifdef _WIN32
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
ARMJIT_Global::Init();
u64 pageSize = (u64)sysInfo.dwPageSize;
#else
u64 pageSize = sysconf(_SC_PAGE_SIZE);
#endif
u8* pageAligned = (u8*)(((u64)JitMem & ~(pageSize - 1)) + pageSize);
u64 alignedSize = (((u64)JitMem + sizeof(JitMem)) & ~(pageSize - 1)) - (u64)pageAligned;
CodeMemBase = ARMJIT_Global::AllocateCodeMem();
nds.JIT.JitEnableWrite();
#if defined(_WIN32)
DWORD dummy;
VirtualProtect(pageAligned, alignedSize, PAGE_EXECUTE_READWRITE, &dummy);
#elif defined(__APPLE__)
pageAligned = (u8*)mmap(NULL, 1024*1024*16, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_JIT,-1, 0);
nds.JIT.JitEnableWrite();
#else
mprotect(pageAligned, alignedSize, PROT_EXEC | PROT_READ | PROT_WRITE);
#endif
SetCodeBase(pageAligned, pageAligned);
JitMemMainSize = alignedSize;
SetCodeBase(reinterpret_cast<u8*>(CodeMemBase), reinterpret_cast<u8*>(CodeMemBase));
JitMemMainSize = ARMJIT_Global::CodeMemorySliceSize;
#endif
SetCodePtr(0);
@ -493,6 +462,9 @@ Compiler::~Compiler()
free(JitRWBase);
}
#endif
ARMJIT_Global::FreeCodeMem(CodeMemBase);
ARMJIT_Global::DeInit();
}
void Compiler::LoadCycles()

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team, RSDuck
Copyright 2016-2025 melonDS team, RSDuck
This file is part of melonDS.
@ -275,6 +275,7 @@ public:
void* JitRWStart;
void* JitRXStart;
#endif
void* CodeMemBase;
void* ReadBanked, *WriteBanked;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team, RSDuck
Copyright 2016-2025 melonDS team, RSDuck
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team, RSDuck
Copyright 2016-2025 melonDS team, RSDuck
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team, RSDuck
Copyright 2016-2025 melonDS team, RSDuck
This file is part of melonDS.

126
src/ARMJIT_Global.cpp Normal file
View File

@ -0,0 +1,126 @@
#include "ARMJIT_Global.h"
#include "ARMJIT_Memory.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdint.h>
#include <mutex>
namespace melonDS
{
namespace ARMJIT_Global
{
std::mutex globalMutex;
#if defined(__APPLE__) && defined(__aarch64__)
#define APPLE_AARCH64
#endif
#ifndef APPLE_AARCH64
static constexpr size_t NumCodeMemSlices = 4;
static constexpr size_t CodeMemoryAlignedSize = NumCodeMemSlices * CodeMemorySliceSize;
// I haven't heard of pages larger than 16 KB
u8 CodeMemory[CodeMemoryAlignedSize + 16*1024];
u32 AvailableCodeMemSlices = (1 << NumCodeMemSlices) - 1;
u8* GetAlignedCodeMemoryStart()
{
return reinterpret_cast<u8*>((reinterpret_cast<intptr_t>(CodeMemory) + (16*1024-1)) & ~static_cast<intptr_t>(16*1024-1));
}
#endif
int RefCounter = 0;
void* AllocateCodeMem()
{
std::lock_guard guard(globalMutex);
#ifndef APPLE_AARCH64
if (AvailableCodeMemSlices)
{
int slice = __builtin_ctz(AvailableCodeMemSlices);
AvailableCodeMemSlices &= ~(1 << slice);
//printf("allocating slice %d\n", slice);
return &GetAlignedCodeMemoryStart()[slice * CodeMemorySliceSize];
}
#endif
// allocate
#ifdef _WIN32
return VirtualAlloc(nullptr, CodeMemorySliceSize, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE);
#elif defined(APPLE_AARCH64)
return mmap(NULL, CodeMemorySliceSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_JIT,-1, 0);
#else
//printf("mmaping...\n");
return mmap(nullptr, CodeMemorySliceSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
#endif
}
void FreeCodeMem(void* codeMem)
{
std::lock_guard guard(globalMutex);
#ifndef APPLE_AARCH64
for (int i = 0; i < NumCodeMemSlices; i++)
{
if (codeMem == &GetAlignedCodeMemoryStart()[CodeMemorySliceSize * i])
{
//printf("freeing slice\n");
AvailableCodeMemSlices |= 1 << i;
return;
}
}
#endif
#ifdef _WIN32
VirtualFree(codeMem, CodeMemorySliceSize, MEM_RELEASE|MEM_DECOMMIT);
#else
munmap(codeMem, CodeMemorySliceSize);
#endif
}
void Init()
{
std::lock_guard guard(globalMutex);
RefCounter++;
if (RefCounter == 1)
{
#ifdef _WIN32
DWORD dummy;
VirtualProtect(GetAlignedCodeMemoryStart(), CodeMemoryAlignedSize, PAGE_EXECUTE_READWRITE, &dummy);
#elif defined(APPLE_AARCH64)
// Apple aarch64 always uses dynamic allocation
#else
mprotect(GetAlignedCodeMemoryStart(), CodeMemoryAlignedSize, PROT_EXEC | PROT_READ | PROT_WRITE);
#endif
ARMJIT_Memory::RegisterFaultHandler();
}
}
void DeInit()
{
std::lock_guard guard(globalMutex);
RefCounter--;
if (RefCounter == 0)
{
ARMJIT_Memory::UnregisterFaultHandler();
}
}
}
}

44
src/ARMJIT_Global.h Normal file
View File

@ -0,0 +1,44 @@
/*
Copyright 2016-2025 melonDS team
This file is part of melonDS.
melonDS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#ifndef ARMJIT_GLOBAL_H
#define ARMJIT_GLOBAL_H
#include "types.h"
#include <stdlib.h>
namespace melonDS
{
namespace ARMJIT_Global
{
static constexpr size_t CodeMemorySliceSize = 1024*1024*32;
void Init();
void DeInit();
void* AllocateCodeMem();
void FreeCodeMem(void* codeMem);
}
}
#endif

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team, RSDuck
Copyright 2016-2025 melonDS team, RSDuck
This file is part of melonDS.
@ -85,9 +85,9 @@ typedef void (*InterpreterFunc)(ARM* cpu);
extern InterpreterFunc InterpretARM[];
extern InterpreterFunc InterpretTHUMB[];
inline bool PageContainsCode(const AddressRange* range)
inline bool PageContainsCode(const AddressRange* range, u32 pageSize)
{
for (int i = 0; i < 8; i++)
for (int i = 0; i < pageSize / 512; i++)
{
if (range[i].Blocks.Length > 0)
return true;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -39,6 +39,7 @@
#include "ARMJIT_Internal.h"
#include "ARMJIT_Compiler.h"
#include "ARMJIT_Global.h"
#include "DSi.h"
#include "GPU.h"
@ -100,6 +101,9 @@
namespace melonDS
{
static constexpr u64 AddrSpaceSize = 0x100000000;
static constexpr u64 VirtmemAreaSize = AddrSpaceSize * 2 + MemoryTotalSize;
using Platform::Log;
using Platform::LogLevel;
@ -152,6 +156,15 @@ void __libnx_exception_handler(ThreadExceptionDump* ctx)
#elif defined(_WIN32)
static LPVOID ExceptionHandlerHandle = nullptr;
static HMODULE KernelBaseDll = nullptr;
using VirtualAlloc2Type = PVOID WINAPI (*)(HANDLE Process, PVOID BaseAddress, SIZE_T Size, ULONG AllocationType, ULONG PageProtection, MEM_EXTENDED_PARAMETER* ExtendedParameters, ULONG ParameterCount);
using MapViewOfFile3Type = PVOID WINAPI (*)(HANDLE FileMapping, HANDLE Process, PVOID BaseAddress, ULONG64 Offset, SIZE_T ViewSize, ULONG AllocationType, ULONG PageProtection, MEM_EXTENDED_PARAMETER* ExtendedParameters, ULONG ParameterCount);
static VirtualAlloc2Type virtualAlloc2Ptr;
static MapViewOfFile3Type mapViewOfFile3Ptr;
LONG ARMJIT_Memory::ExceptionHandler(EXCEPTION_POINTERS* exceptionInfo)
{
if (exceptionInfo->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)
@ -170,6 +183,7 @@ LONG ARMJIT_Memory::ExceptionHandler(EXCEPTION_POINTERS* exceptionInfo)
return EXCEPTION_CONTINUE_EXECUTION;
}
Log(LogLevel::Debug, "it all returns to nothing\n");
return EXCEPTION_CONTINUE_SEARCH;
}
@ -261,18 +275,61 @@ enum
memstate_MappedProtected,
};
#define CHECK_ALIGNED(value) assert(((value) & (PageSize-1)) == 0)
bool ARMJIT_Memory::MapIntoRange(u32 addr, u32 num, u32 offset, u32 size) noexcept
{
CHECK_ALIGNED(addr);
CHECK_ALIGNED(offset);
CHECK_ALIGNED(size);
u8* dst = (u8*)(num == 0 ? FastMem9Start : FastMem7Start) + addr;
#ifdef __SWITCH__
Result r = (svcMapProcessMemory(dst, envGetOwnProcessHandle(),
(u64)(MemoryBaseCodeMem + offset), size));
return R_SUCCEEDED(r);
#elif defined(_WIN32)
bool r = MapViewOfFileEx(MemoryFile, FILE_MAP_READ | FILE_MAP_WRITE, 0, offset, size, dst) == dst;
return r;
uintptr_t uintptrDst = reinterpret_cast<uintptr_t>(dst);
for (auto it = VirtmemPlaceholders.begin(); it != VirtmemPlaceholders.end(); it++)
{
if (uintptrDst >= it->Start && uintptrDst+size <= it->Start+it->Size)
{
//Log(LogLevel::Debug, "found mapping %llx %llx %llx %llx\n", uintptrDst, size, it->Start, it->Size);
// we split this place holder so that we have a fitting place holder for the mapping
if (uintptrDst != it->Start || size != it->Size)
{
if (!VirtualFree(dst, size, MEM_RELEASE|MEM_PRESERVE_PLACEHOLDER))
{
Log(LogLevel::Debug, "VirtualFree failed with %x\n", GetLastError());
return false;
}
}
VirtmemPlaceholder splitPlaceholder = *it;
VirtmemPlaceholders.erase(it);
if (uintptrDst > splitPlaceholder.Start)
{
//Log(LogLevel::Debug, "splitting on the left %llx\n", uintptrDst - splitPlaceholder.Start);
VirtmemPlaceholders.push_back({splitPlaceholder.Start, uintptrDst - splitPlaceholder.Start});
}
if (uintptrDst+size < splitPlaceholder.Start+splitPlaceholder.Size)
{
//Log(LogLevel::Debug, "splitting on the right %llx\n", (splitPlaceholder.Start+splitPlaceholder.Size)-(uintptrDst+size));
VirtmemPlaceholders.push_back({uintptrDst+size, (splitPlaceholder.Start+splitPlaceholder.Size)-(uintptrDst+size)});
}
if (!mapViewOfFile3Ptr(MemoryFile, nullptr, dst, offset, size, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0))
{
Log(LogLevel::Debug, "MapViewOfFile3 failed with %x\n", GetLastError());
return false;
}
return true;
}
}
Log(LogLevel::Debug, "no mapping at all found??? %p %x %p\n", dst, size, MemoryBase);
return false;
#else
return mmap(dst, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, MemoryFile, offset) != MAP_FAILED;
#endif
@ -280,21 +337,68 @@ bool ARMJIT_Memory::MapIntoRange(u32 addr, u32 num, u32 offset, u32 size) noexce
bool ARMJIT_Memory::UnmapFromRange(u32 addr, u32 num, u32 offset, u32 size) noexcept
{
CHECK_ALIGNED(addr);
CHECK_ALIGNED(offset);
CHECK_ALIGNED(size);
u8* dst = (u8*)(num == 0 ? FastMem9Start : FastMem7Start) + addr;
#ifdef __SWITCH__
Result r = svcUnmapProcessMemory(dst, envGetOwnProcessHandle(),
(u64)(MemoryBaseCodeMem + offset), size);
return R_SUCCEEDED(r);
#elif defined(_WIN32)
return UnmapViewOfFile(dst);
if (!UnmapViewOfFileEx(dst, MEM_PRESERVE_PLACEHOLDER))
{
Log(LogLevel::Debug, "UnmapViewOfFileEx failed %x\n", GetLastError());
return false;
}
uintptr_t uintptrDst = reinterpret_cast<uintptr_t>(dst);
uintptr_t coalesceStart = uintptrDst;
size_t coalesceSize = size;
for (auto it = VirtmemPlaceholders.begin(); it != VirtmemPlaceholders.end();)
{
if (it->Start+it->Size == uintptrDst)
{
//Log(LogLevel::Debug, "Coalescing to the left\n");
coalesceStart = it->Start;
coalesceSize += it->Size;
it = VirtmemPlaceholders.erase(it);
}
else if (it->Start == uintptrDst+size)
{
//Log(LogLevel::Debug, "Coalescing to the right\n");
coalesceSize += it->Size;
it = VirtmemPlaceholders.erase(it);
}
else
{
it++;
}
}
if (coalesceStart != uintptrDst || coalesceSize != size)
{
if (!VirtualFree(reinterpret_cast<void*>(coalesceStart), coalesceSize, MEM_RELEASE|MEM_COALESCE_PLACEHOLDERS))
return false;
}
VirtmemPlaceholders.push_back({coalesceStart, coalesceSize});
//Log(LogLevel::Debug, "Adding coalesced region %llx %llx", coalesceStart, coalesceSize);
return true;
#else
return munmap(dst, size) == 0;
return mmap(dst, size, PROT_NONE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0) != MAP_FAILED;
#endif
}
#ifndef __SWITCH__
void ARMJIT_Memory::SetCodeProtectionRange(u32 addr, u32 size, u32 num, int protection) noexcept
{
CHECK_ALIGNED(addr);
CHECK_ALIGNED(size);
u8* dst = (u8*)(num == 0 ? FastMem9Start : FastMem7Start) + addr;
#if defined(_WIN32)
DWORD winProtection, oldProtection;
@ -305,6 +409,10 @@ void ARMJIT_Memory::SetCodeProtectionRange(u32 addr, u32 size, u32 num, int prot
else
winProtection = PAGE_READWRITE;
bool success = VirtualProtect(dst, size, winProtection, &oldProtection);
if (!success)
{
Log(LogLevel::Debug, "VirtualProtect failed with %x\n", GetLastError());
}
assert(success);
#else
int posixProt;
@ -335,14 +443,14 @@ void ARMJIT_Memory::Mapping::Unmap(int region, melonDS::NDS& nds) noexcept
else
{
u32 segmentOffset = offset;
u8 status = statuses[(Addr + offset) >> 12];
while (statuses[(Addr + offset) >> 12] == status
u8 status = statuses[(Addr + offset) >> PageShift];
while (statuses[(Addr + offset) >> PageShift] == status
&& offset < Size
&& (!skipDTCM || Addr + offset != dtcmStart))
{
assert(statuses[(Addr + offset) >> 12] != memstate_Unmapped);
statuses[(Addr + offset) >> 12] = memstate_Unmapped;
offset += 0x1000;
assert(statuses[(Addr + offset) >> PageShift] != memstate_Unmapped);
statuses[(Addr + offset) >> PageShift] = memstate_Unmapped;
offset += PageSize;
}
#ifdef __SWITCH__
@ -358,7 +466,6 @@ void ARMJIT_Memory::Mapping::Unmap(int region, melonDS::NDS& nds) noexcept
}
#ifndef __SWITCH__
#ifndef _WIN32
u32 dtcmEnd = dtcmStart + dtcmSize;
if (Num == 0
&& dtcmEnd >= Addr
@ -378,7 +485,6 @@ void ARMJIT_Memory::Mapping::Unmap(int region, melonDS::NDS& nds) noexcept
}
}
else
#endif
{
bool succeded = nds.JIT.Memory.UnmapFromRange(Addr, Num, OffsetsPerRegion[region] + LocalOffset, Size);
assert(succeded);
@ -388,7 +494,7 @@ void ARMJIT_Memory::Mapping::Unmap(int region, melonDS::NDS& nds) noexcept
void ARMJIT_Memory::SetCodeProtection(int region, u32 offset, bool protect) noexcept
{
offset &= ~0xFFF;
offset &= ~(PageSize - 1);
//printf("set code protection %d %x %d\n", region, offset, protect);
for (int i = 0; i < Mappings[region].Length; i++)
@ -406,9 +512,9 @@ void ARMJIT_Memory::SetCodeProtection(int region, u32 offset, bool protect) noex
u8* states = (u8*)(mapping.Num == 0 ? MappingStatus9 : MappingStatus7);
//printf("%x %d %x %x %x %d\n", effectiveAddr, mapping.Num, mapping.Addr, mapping.LocalOffset, mapping.Size, states[effectiveAddr >> 12]);
assert(states[effectiveAddr >> 12] == (protect ? memstate_MappedRW : memstate_MappedProtected));
states[effectiveAddr >> 12] = protect ? memstate_MappedProtected : memstate_MappedRW;
//printf("%x %d %x %x %x %d\n", effectiveAddr, mapping.Num, mapping.Addr, mapping.LocalOffset, mapping.Size, states[effectiveAddr >> PageShift]);
assert(states[effectiveAddr >> PageShift] == (protect ? memstate_MappedRW : memstate_MappedProtected));
states[effectiveAddr >> PageShift] = protect ? memstate_MappedProtected : memstate_MappedRW;
#if defined(__SWITCH__)
bool success;
@ -418,7 +524,7 @@ void ARMJIT_Memory::SetCodeProtection(int region, u32 offset, bool protect) noex
success = MapIntoRange(effectiveAddr, mapping.Num, OffsetsPerRegion[region] + offset, 0x1000);
assert(success);
#else
SetCodeProtectionRange(effectiveAddr, 0x1000, mapping.Num, protect ? 1 : 2);
SetCodeProtectionRange(effectiveAddr, PageSize, mapping.Num, protect ? 1 : 2);
#endif
}
}
@ -543,11 +649,19 @@ bool ARMJIT_Memory::MapAtAddress(u32 addr) noexcept
u32 dtcmSize = ~NDS.ARM9.DTCMMask + 1;
u32 dtcmEnd = dtcmStart + dtcmSize;
#ifndef __SWITCH__
#ifndef _WIN32
if (num == 0
&& dtcmEnd >= mirrorStart
&& dtcmStart < mirrorStart + mirrorSize)
{
if (dtcmSize < PageSize)
{
// we could technically mask out the DTCM by setting a hole to access permissions
// but realistically there isn't much of a point in mapping less than 16kb of DTCM
// so it isn't worth more complex support
Log(LogLevel::Info, "DTCM size smaller than 16kb skipping mapping entirely");
return false;
}
bool success;
if (dtcmStart > mirrorStart)
{
@ -562,7 +676,6 @@ bool ARMJIT_Memory::MapAtAddress(u32 addr) noexcept
}
}
else
#endif
{
bool succeded = MapIntoRange(mirrorStart, num, OffsetsPerRegion[region] + memoryOffset, mirrorSize);
assert(succeded);
@ -579,22 +692,19 @@ bool ARMJIT_Memory::MapAtAddress(u32 addr) noexcept
{
if (skipDTCM && mirrorStart + offset == dtcmStart)
{
#ifdef _WIN32
SetCodeProtectionRange(dtcmStart, dtcmSize, 0, 0);
#endif
offset += dtcmSize;
}
else
{
u32 sectionOffset = offset;
bool hasCode = isExecutable && PageContainsCode(&range[offset / 512]);
bool hasCode = isExecutable && PageContainsCode(&range[offset / 512], PageSize);
while (offset < mirrorSize
&& (!isExecutable || PageContainsCode(&range[offset / 512]) == hasCode)
&& (!isExecutable || PageContainsCode(&range[offset / 512], PageSize) == hasCode)
&& (!skipDTCM || mirrorStart + offset != NDS.ARM9.DTCMBase))
{
assert(states[(mirrorStart + offset) >> 12] == memstate_Unmapped);
states[(mirrorStart + offset) >> 12] = hasCode ? memstate_MappedProtected : memstate_MappedRW;
offset += 0x1000;
assert(states[(mirrorStart + offset) >> PageShift] == memstate_Unmapped);
states[(mirrorStart + offset) >> PageShift] = hasCode ? memstate_MappedProtected : memstate_MappedRW;
offset += PageSize;
}
u32 sectionSize = offset - sectionOffset;
@ -624,6 +734,86 @@ bool ARMJIT_Memory::MapAtAddress(u32 addr) noexcept
return true;
}
u32 ARMJIT_Memory::PageSize = 0;
u32 ARMJIT_Memory::PageShift = 0;
bool ARMJIT_Memory::IsFastMemSupported()
{
#ifdef __APPLE__
return false;
#else
static bool initialised = false;
static bool isSupported = false;
if (!initialised)
{
#ifdef _WIN32
ARMJIT_Global::Init();
isSupported = virtualAlloc2Ptr != nullptr;
ARMJIT_Global::DeInit();
PageSize = RegularPageSize;
#else
PageSize = sysconf(_SC_PAGESIZE);
isSupported = PageSize == RegularPageSize || PageSize == LargePageSize;
#endif
PageShift = __builtin_ctz(PageSize);
initialised = true;
}
return isSupported;
#endif
}
void ARMJIT_Memory::RegisterFaultHandler()
{
#ifdef _WIN32
ExceptionHandlerHandle = AddVectoredExceptionHandler(1, ExceptionHandler);
KernelBaseDll = LoadLibrary("KernelBase.dll");
if (KernelBaseDll)
{
virtualAlloc2Ptr = reinterpret_cast<VirtualAlloc2Type>(GetProcAddress(KernelBaseDll, "VirtualAlloc2"));
mapViewOfFile3Ptr = reinterpret_cast<MapViewOfFile3Type>(GetProcAddress(KernelBaseDll, "MapViewOfFile3"));
}
if (!virtualAlloc2Ptr)
{
Log(LogLevel::Error, "Could not load new Windows virtual memory functions, fast memory is disabled.\n");
}
#else
struct sigaction sa;
sa.sa_handler = nullptr;
sa.sa_sigaction = &SigsegvHandler;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, &OldSaSegv);
#ifdef __APPLE__
sigaction(SIGBUS, &sa, &OldSaBus);
#endif
#endif
}
void ARMJIT_Memory::UnregisterFaultHandler()
{
#ifdef _WIN32
if (ExceptionHandlerHandle)
{
RemoveVectoredExceptionHandler(ExceptionHandlerHandle);
ExceptionHandlerHandle = nullptr;
}
if (KernelBaseDll)
{
FreeLibrary(KernelBaseDll);
KernelBaseDll = nullptr;
}
#else
sigaction(SIGSEGV, &OldSaSegv, nullptr);
#ifdef __APPLE__
sigaction(SIGBUS, &OldSaBus, nullptr);
#endif
#endif
}
bool ARMJIT_Memory::FaultHandler(FaultDescription& faultDesc, melonDS::NDS& nds)
{
if (nds.JIT.JITCompiler.IsJITFault(faultDesc.FaultPC))
@ -632,7 +822,7 @@ bool ARMJIT_Memory::FaultHandler(FaultDescription& faultDesc, melonDS::NDS& nds)
u8* memStatus = nds.CurCPU == 0 ? nds.JIT.Memory.MappingStatus9 : nds.JIT.Memory.MappingStatus7;
if (memStatus[faultDesc.EmulatedFaultAddr >> 12] == memstate_Unmapped)
if (memStatus[faultDesc.EmulatedFaultAddr >> PageShift] == memstate_Unmapped)
rewriteToSlowPath = !nds.JIT.Memory.MapAtAddress(faultDesc.EmulatedFaultAddr);
if (rewriteToSlowPath)
@ -643,10 +833,9 @@ bool ARMJIT_Memory::FaultHandler(FaultDescription& faultDesc, melonDS::NDS& nds)
return false;
}
const u64 AddrSpaceSize = 0x100000000;
ARMJIT_Memory::ARMJIT_Memory(melonDS::NDS& nds) : NDS(nds)
{
ARMJIT_Global::Init();
#if defined(__SWITCH__)
MemoryBase = (u8*)aligned_alloc(0x1000, MemoryTotalSize);
virtmemLock();
@ -671,33 +860,27 @@ ARMJIT_Memory::ARMJIT_Memory(melonDS::NDS& nds) : NDS(nds)
u8* basePtr = MemoryBaseCodeMem;
#elif defined(_WIN32)
ExceptionHandlerHandle = AddVectoredExceptionHandler(1, ExceptionHandler);
if (virtualAlloc2Ptr)
{
MemoryFile = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, MemoryTotalSize, nullptr);
MemoryFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MemoryTotalSize, NULL);
MemoryBase = reinterpret_cast<u8*>(virtualAlloc2Ptr(nullptr, nullptr, VirtmemAreaSize,
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
PAGE_NOACCESS,
nullptr, 0));
// split off placeholder and map base mapping
VirtualFree(MemoryBase, MemoryTotalSize, MEM_RELEASE|MEM_PRESERVE_PLACEHOLDER);
mapViewOfFile3Ptr(MemoryFile, nullptr, MemoryBase, 0, MemoryTotalSize, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0);
MemoryBase = (u8*)VirtualAlloc(NULL, AddrSpaceSize*4, MEM_RESERVE, PAGE_READWRITE);
VirtualFree(MemoryBase, 0, MEM_RELEASE);
// this is incredible hacky
// but someone else is trying to go into our address space!
// Windows will very likely give them virtual memory starting at the same address
// as it is giving us now.
// That's why we don't use this address, but instead 4gb inwards
// I know this is terrible
FastMem9Start = MemoryBase + AddrSpaceSize;
FastMem7Start = MemoryBase + AddrSpaceSize*2;
MemoryBase = MemoryBase + AddrSpaceSize*3;
MapViewOfFileEx(MemoryFile, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, MemoryTotalSize, MemoryBase);
VirtmemPlaceholders.push_back({reinterpret_cast<uintptr_t>(MemoryBase)+MemoryTotalSize, AddrSpaceSize*2});
}
else
{
// old Windows version
MemoryBase = new u8[MemoryTotalSize];
}
#else
// this used to be allocated with three different mmaps
// The idea was to give the OS more freedom where to position the buffers,
// but something was bad about this so instead we take this vmem eating monster
// which seems to work better.
MemoryBase = (u8*)mmap(NULL, AddrSpaceSize*4, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
munmap(MemoryBase, AddrSpaceSize*4);
FastMem9Start = MemoryBase;
FastMem7Start = MemoryBase + AddrSpaceSize;
MemoryBase = MemoryBase + AddrSpaceSize*2;
MemoryBase = (u8*)mmap(nullptr, VirtmemAreaSize, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
#if defined(__ANDROID__)
Libandroid = Platform::DynamicLibrary_Load("libandroid.so");
@ -717,7 +900,7 @@ ARMJIT_Memory::ARMJIT_Memory(melonDS::NDS& nds) : NDS(nds)
}
#else
char fastmemPidName[snprintf(NULL, 0, "/melondsfastmem%d", getpid()) + 1];
sprintf(fastmemPidName, "/melondsfastmem%d", getpid());
snprintf(fastmemPidName, sizeof(fastmemPidName), "/melondsfastmem%d", getpid());
MemoryFile = shm_open(fastmemPidName, O_RDWR | O_CREAT | O_EXCL, 0600);
if (MemoryFile == -1)
{
@ -730,20 +913,10 @@ ARMJIT_Memory::ARMJIT_Memory(melonDS::NDS& nds) : NDS(nds)
Log(LogLevel::Error, "Failed to allocate memory using ftruncate! (%s)", strerror(errno));
}
struct sigaction sa;
sa.sa_handler = nullptr;
sa.sa_sigaction = &SigsegvHandler;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, &OldSaSegv);
#ifdef __APPLE__
sigaction(SIGBUS, &sa, &OldSaBus);
#endif
mmap(MemoryBase, MemoryTotalSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, MemoryFile, 0);
u8* basePtr = MemoryBase;
#endif
FastMem9Start = MemoryBase+MemoryTotalSize;
FastMem7Start = static_cast<u8*>(FastMem9Start)+AddrSpaceSize;
}
ARMJIT_Memory::~ARMJIT_Memory() noexcept
@ -764,34 +937,36 @@ ARMJIT_Memory::~ARMJIT_Memory() noexcept
free(MemoryBase);
MemoryBase = nullptr;
#elif defined(_WIN32)
if (MemoryBase)
if (virtualAlloc2Ptr)
{
bool viewUnmapped = UnmapViewOfFile(MemoryBase);
assert(viewUnmapped);
MemoryBase = nullptr;
FastMem9Start = nullptr;
FastMem7Start = nullptr;
}
if (MemoryBase)
{
bool viewUnmapped = UnmapViewOfFileEx(MemoryBase, MEM_PRESERVE_PLACEHOLDER);
assert(viewUnmapped);
bool viewCoalesced = VirtualFree(MemoryBase, VirtmemAreaSize, MEM_RELEASE|MEM_COALESCE_PLACEHOLDERS);
assert(viewCoalesced);
bool freeEverything = VirtualFree(MemoryBase, 0, MEM_RELEASE);
assert(freeEverything);
if (MemoryFile)
{
CloseHandle(MemoryFile);
MemoryFile = INVALID_HANDLE_VALUE;
}
MemoryBase = nullptr;
FastMem9Start = nullptr;
FastMem7Start = nullptr;
}
if (ExceptionHandlerHandle)
if (MemoryFile)
{
CloseHandle(MemoryFile);
MemoryFile = INVALID_HANDLE_VALUE;
}
}
else
{
RemoveVectoredExceptionHandler(ExceptionHandlerHandle);
ExceptionHandlerHandle = nullptr;
delete[] MemoryBase;
}
#else
sigaction(SIGSEGV, &OldSaSegv, nullptr);
#ifdef __APPLE__
sigaction(SIGBUS, &OldSaBus, nullptr);
#endif
if (MemoryBase)
{
munmap(MemoryBase, MemoryTotalSize);
munmap(MemoryBase, VirtmemAreaSize);
MemoryBase = nullptr;
FastMem9Start = nullptr;
FastMem7Start = nullptr;
@ -803,6 +978,8 @@ ARMJIT_Memory::~ARMJIT_Memory() noexcept
MemoryFile = -1;
}
Log(LogLevel::Info, "unmappinged everything\n");
#if defined(__ANDROID__)
if (Libandroid)
{
@ -812,6 +989,8 @@ ARMJIT_Memory::~ARMJIT_Memory() noexcept
#endif
#endif
ARMJIT_Global::DeInit();
}
void ARMJIT_Memory::Reset() noexcept
@ -834,17 +1013,6 @@ void ARMJIT_Memory::Reset() noexcept
bool ARMJIT_Memory::IsFastmemCompatible(int region) const noexcept
{
#ifdef _WIN32
/*
TODO: with some hacks, the smaller shared WRAM regions
could be mapped in some occaisons as well
*/
if (region == memregion_DTCM
|| region == memregion_SharedWRAM
|| region == memregion_NewSharedWRAM_B
|| region == memregion_NewSharedWRAM_C)
return false;
#endif
return OffsetsPerRegion[region] != UINT32_MAX;
}
@ -1432,4 +1600,4 @@ void* ARMJIT_Memory::GetFuncForAddr(ARM* cpu, u32 addr, bool store, int size) co
}
return NULL;
}
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -23,12 +23,14 @@
#include "MemConstants.h"
#ifdef JIT_ENABLED
# include <mutex>
# include "TinyVector.h"
# include "ARM.h"
# if defined(__SWITCH__)
# include <switch.h>
# elif defined(_WIN32)
#include <windows.h>
# include <vector>
# include <windows.h>
# else
# include <sys/mman.h>
# include <sys/stat.h>
@ -48,23 +50,22 @@ class Compiler;
class ARMJIT;
#endif
static constexpr u32 LargePageSize = 0x4000;
static constexpr u32 RegularPageSize = 0x1000;
constexpr u32 RoundUp(u32 size) noexcept
{
#ifdef _WIN32
return (size + 0xFFFF) & ~0xFFFF;
#else
return size;
#endif
return (size + LargePageSize - 1) & ~(LargePageSize - 1);
}
const u32 MemBlockMainRAMOffset = 0;
const u32 MemBlockSWRAMOffset = RoundUp(MainRAMMaxSize);
const u32 MemBlockARM7WRAMOffset = MemBlockSWRAMOffset + RoundUp(SharedWRAMSize);
const u32 MemBlockDTCMOffset = MemBlockARM7WRAMOffset + RoundUp(ARM7WRAMSize);
const u32 MemBlockNWRAM_AOffset = MemBlockDTCMOffset + RoundUp(DTCMPhysicalSize);
const u32 MemBlockNWRAM_BOffset = MemBlockNWRAM_AOffset + RoundUp(NWRAMSize);
const u32 MemBlockNWRAM_COffset = MemBlockNWRAM_BOffset + RoundUp(NWRAMSize);
const u32 MemoryTotalSize = MemBlockNWRAM_COffset + RoundUp(NWRAMSize);
static constexpr u32 MemBlockMainRAMOffset = 0;
static constexpr u32 MemBlockSWRAMOffset = RoundUp(MainRAMMaxSize);
static constexpr u32 MemBlockARM7WRAMOffset = MemBlockSWRAMOffset + RoundUp(SharedWRAMSize);
static constexpr u32 MemBlockDTCMOffset = MemBlockARM7WRAMOffset + RoundUp(ARM7WRAMSize);
static constexpr u32 MemBlockNWRAM_AOffset = MemBlockDTCMOffset + RoundUp(DTCMPhysicalSize);
static constexpr u32 MemBlockNWRAM_BOffset = MemBlockNWRAM_AOffset + RoundUp(NWRAMSize);
static constexpr u32 MemBlockNWRAM_COffset = MemBlockNWRAM_BOffset + RoundUp(NWRAMSize);
static constexpr u32 MemoryTotalSize = MemBlockNWRAM_COffset + RoundUp(NWRAMSize);
class ARMJIT_Memory
{
@ -137,6 +138,14 @@ public:
bool IsFastmemCompatible(int region) const noexcept;
void* GetFuncForAddr(ARM* cpu, u32 addr, bool store, int size) const noexcept;
bool MapAtAddress(u32 addr) noexcept;
static bool IsFastMemSupported();
static void RegisterFaultHandler();
static void UnregisterFaultHandler();
static u32 PageSize;
static u32 PageShift;
private:
friend class Compiler;
struct Mapping
@ -162,14 +171,22 @@ private:
void* FastMem9Start;
void* FastMem7Start;
u8* MemoryBase = nullptr;
#if defined(__SWITCH__)
VirtmemReservation* FastMem9Reservation, *FastMem7Reservation;
u8* MemoryBaseCodeMem;
#elif defined(_WIN32)
struct VirtmemPlaceholder
{
uintptr_t Start;
size_t Size;
};
std::vector<VirtmemPlaceholder> VirtmemPlaceholders;
static LONG ExceptionHandler(EXCEPTION_POINTERS* exceptionInfo);
HANDLE MemoryFile = INVALID_HANDLE_VALUE;
LPVOID ExceptionHandlerHandle = nullptr;
#else
static void SigsegvHandler(int sig, siginfo_t* info, void* rawContext);
int MemoryFile = -1;
#endif

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team, RSDuck
Copyright 2016-2025 melonDS team, RSDuck
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -176,9 +176,9 @@ void Compiler::Comp_JumpTo(Gen::X64Reg addr, bool restoreCPSR)
else
MOV(32, R(ABI_PARAM3), Imm32(true)); // what a waste
if (Num == 0)
CALL((void*)&ARMv5JumpToTrampoline);
ABI_CallFunction(ARMv5JumpToTrampoline);
else
CALL((void*)&ARMv4JumpToTrampoline);
ABI_CallFunction(ARMv4JumpToTrampoline);
PopRegs(restoreCPSR, true);

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -21,19 +21,13 @@
#include "../ARMJIT.h"
#include "../ARMInterpreter.h"
#include "../NDS.h"
#include "../ARMJIT_Global.h"
#include <assert.h>
#include <stdarg.h>
#include "../dolphin/CommonFuncs.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif
using namespace Gen;
using namespace Common;
@ -222,46 +216,21 @@ void Compiler::A_Comp_MSR()
MOV(32, R(ABI_PARAM3), R(RCPSR));
MOV(32, R(ABI_PARAM2), R(RSCRATCH3));
MOV(64, R(ABI_PARAM1), R(RCPU));
CALL((void*)&UpdateModeTrampoline);
ABI_CallFunction(UpdateModeTrampoline);
PopRegs(true, true);
}
}
}
/*
We'll repurpose this .bss memory
*/
u8 CodeMemory[1024 * 1024 * 32];
Compiler::Compiler(melonDS::NDS& nds) : XEmitter(), NDS(nds)
{
{
#ifdef _WIN32
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
ARMJIT_Global::Init();
u64 pageSize = (u64)sysInfo.dwPageSize;
#else
u64 pageSize = sysconf(_SC_PAGE_SIZE);
#endif
CodeMemBase = static_cast<u8*>(ARMJIT_Global::AllocateCodeMem());
CodeMemSize = ARMJIT_Global::CodeMemorySliceSize;
u8* pageAligned = (u8*)(((u64)CodeMemory & ~(pageSize - 1)) + pageSize);
u64 alignedSize = (((u64)CodeMemory + sizeof(CodeMemory)) & ~(pageSize - 1)) - (u64)pageAligned;
#ifdef _WIN32
DWORD dummy;
VirtualProtect(pageAligned, alignedSize, PAGE_EXECUTE_READWRITE, &dummy);
#elif defined(__APPLE__)
pageAligned = (u8*)mmap(NULL, 1024*1024*32, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS ,-1, 0);
#else
mprotect(pageAligned, alignedSize, PROT_EXEC | PROT_READ | PROT_WRITE);
#endif
ResetStart = pageAligned;
CodeMemSize = alignedSize;
}
ResetStart = CodeMemBase;
Reset();
@ -475,6 +444,13 @@ Compiler::Compiler(melonDS::NDS& nds) : XEmitter(), NDS(nds)
FarSize = (ResetStart + CodeMemSize) - FarStart;
}
Compiler::~Compiler()
{
ARMJIT_Global::FreeCodeMem(CodeMemBase);
ARMJIT_Global::DeInit();
}
void Compiler::LoadCPSR()
{
assert(!CPSRDirty);
@ -684,7 +660,7 @@ void Compiler::Comp_SpecialBranchBehaviour(bool taken)
if (ConstantCycles)
ADD(32, MDisp(RCPU, offsetof(ARM, Cycles)), Imm32(ConstantCycles));
JMP((u8*)&ARM_Ret, true);
ABI_TailCall(ARM_Ret);
}
}
@ -846,7 +822,7 @@ JitBlockEntry Compiler::CompileBlock(ARM* cpu, bool thumb, FetchedInstr instrs[]
if (ConstantCycles)
ADD(32, MDisp(RCPU, offsetof(ARM, Cycles)), Imm32(ConstantCycles));
JMP((u8*)ARM_Ret, true);
ABI_TailCall(ARM_Ret);
#ifdef JIT_PROFILING_ENABLED
CreateMethod("JIT_Block_%d_%d_%08X", (void*)res, Num, Thumb, instrs[0].Addr);

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -84,6 +84,7 @@ class Compiler : public Gen::XEmitter
{
public:
explicit Compiler(melonDS::NDS& nds);
~Compiler();
void Reset();
@ -256,6 +257,7 @@ public:
std::unordered_map<u8*, LoadStorePatch> LoadStorePatches {};
u8* CodeMemBase;
u8* ResetStart {};
u32 CodeMemSize {};

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team, RSDuck
Copyright 2016-2025 melonDS team, RSDuck
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -316,24 +316,24 @@ void Compiler::Comp_MemAccess(int rd, int rn, const Op2& op2, int size, int flag
{
switch (size | NDS.ConsoleType)
{
case 32: CALL((void*)&SlowWrite9<u32, 0>); break;
case 16: CALL((void*)&SlowWrite9<u16, 0>); break;
case 8: CALL((void*)&SlowWrite9<u8, 0>); break;
case 33: CALL((void*)&SlowWrite9<u32, 1>); break;
case 17: CALL((void*)&SlowWrite9<u16, 1>); break;
case 9: CALL((void*)&SlowWrite9<u8, 1>); break;
case 32: ABI_CallFunction(SlowWrite9<u32, 0>); break;
case 16: ABI_CallFunction(SlowWrite9<u16, 0>); break;
case 8: ABI_CallFunction(&SlowWrite9<u8, 0>); break;
case 33: ABI_CallFunction(&SlowWrite9<u32, 1>); break;
case 17: ABI_CallFunction(&SlowWrite9<u16, 1>); break;
case 9: ABI_CallFunction(&SlowWrite9<u8, 1>); break;
}
}
else
{
switch (size | NDS.ConsoleType)
{
case 32: CALL((void*)&SlowRead9<u32, 0>); break;
case 16: CALL((void*)&SlowRead9<u16, 0>); break;
case 8: CALL((void*)&SlowRead9<u8, 0>); break;
case 33: CALL((void*)&SlowRead9<u32, 1>); break;
case 17: CALL((void*)&SlowRead9<u16, 1>); break;
case 9: CALL((void*)&SlowRead9<u8, 1>); break;
case 32: ABI_CallFunction(&SlowRead9<u32, 0>); break;
case 16: ABI_CallFunction(&SlowRead9<u16, 0>); break;
case 8: ABI_CallFunction(&SlowRead9<u8, 0>); break;
case 33: ABI_CallFunction(&SlowRead9<u32, 1>); break;
case 17: ABI_CallFunction(&SlowRead9<u16, 1>); break;
case 9: ABI_CallFunction(&SlowRead9<u8, 1>); break;
}
}
}
@ -347,24 +347,24 @@ void Compiler::Comp_MemAccess(int rd, int rn, const Op2& op2, int size, int flag
switch (size | NDS.ConsoleType)
{
case 32: CALL((void*)&SlowWrite7<u32, 0>); break;
case 16: CALL((void*)&SlowWrite7<u16, 0>); break;
case 8: CALL((void*)&SlowWrite7<u8, 0>); break;
case 33: CALL((void*)&SlowWrite7<u32, 1>); break;
case 17: CALL((void*)&SlowWrite7<u16, 1>); break;
case 9: CALL((void*)&SlowWrite7<u8, 1>); break;
case 32: ABI_CallFunction(&SlowWrite7<u32, 0>); break;
case 16: ABI_CallFunction(&SlowWrite7<u16, 0>); break;
case 8: ABI_CallFunction(&SlowWrite7<u8, 0>); break;
case 33: ABI_CallFunction(&SlowWrite7<u32, 1>); break;
case 17: ABI_CallFunction(&SlowWrite7<u16, 1>); break;
case 9: ABI_CallFunction(&SlowWrite7<u8, 1>); break;
}
}
else
{
switch (size | NDS.ConsoleType)
{
case 32: CALL((void*)&SlowRead7<u32, 0>); break;
case 16: CALL((void*)&SlowRead7<u16, 0>); break;
case 8: CALL((void*)&SlowRead7<u8, 0>); break;
case 33: CALL((void*)&SlowRead7<u32, 1>); break;
case 17: CALL((void*)&SlowRead7<u16, 1>); break;
case 9: CALL((void*)&SlowRead7<u8, 1>); break;
case 32: ABI_CallFunction(&SlowRead7<u32, 0>); break;
case 16: ABI_CallFunction(&SlowRead7<u16, 0>); break;
case 8: ABI_CallFunction(&SlowRead7<u8, 0>); break;
case 33: ABI_CallFunction(&SlowRead7<u32, 1>); break;
case 17: ABI_CallFunction(&SlowRead7<u16, 1>); break;
case 9: ABI_CallFunction(&SlowRead7<u8, 1>); break;
}
}
}
@ -526,10 +526,10 @@ s32 Compiler::Comp_MemAccessBlock(int rn, BitSet16 regs, bool store, bool preinc
switch (Num * 2 | NDS.ConsoleType)
{
case 0: CALL((void*)&SlowBlockTransfer9<false, 0>); break;
case 1: CALL((void*)&SlowBlockTransfer9<false, 1>); break;
case 2: CALL((void*)&SlowBlockTransfer7<false, 0>); break;
case 3: CALL((void*)&SlowBlockTransfer7<false, 1>); break;
case 0: ABI_CallFunction(&SlowBlockTransfer9<false, 0>); break;
case 1: ABI_CallFunction(&SlowBlockTransfer9<false, 1>); break;
case 2: ABI_CallFunction(&SlowBlockTransfer7<false, 0>); break;
case 3: ABI_CallFunction(&SlowBlockTransfer7<false, 1>); break;
}
PopRegs(false, false);
@ -630,10 +630,10 @@ s32 Compiler::Comp_MemAccessBlock(int rn, BitSet16 regs, bool store, bool preinc
switch (Num * 2 | NDS.ConsoleType)
{
case 0: CALL((void*)&SlowBlockTransfer9<true, 0>); break;
case 1: CALL((void*)&SlowBlockTransfer9<true, 1>); break;
case 2: CALL((void*)&SlowBlockTransfer7<true, 0>); break;
case 3: CALL((void*)&SlowBlockTransfer7<true, 1>); break;
case 0: ABI_CallFunction(&SlowBlockTransfer9<true, 0>); break;
case 1: ABI_CallFunction(&SlowBlockTransfer9<true, 1>); break;
case 2: ABI_CallFunction(&SlowBlockTransfer7<true, 0>); break;
case 3: ABI_CallFunction(&SlowBlockTransfer7<true, 1>); break;
}
ADD(64, R(RSP), stackAlloc <= INT8_MAX ? Imm8(stackAlloc) : Imm32(stackAlloc));

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team, RSDuck
Copyright 2016-2025 melonDS team, RSDuck
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -85,18 +85,6 @@ struct GDBArgs
/// New fields here should have default values if possible.
struct NDSArgs
{
/// NDS ROM to install.
/// Defaults to nullptr, which means no cart.
/// Should be populated with the desired save data beforehand,
/// including an SD card if applicable.
std::unique_ptr<NDSCart::CartCommon> NDSROM = nullptr;
/// GBA ROM to install.
/// Defaults to nullptr, which means no cart.
/// Should be populated with the desired save data beforehand.
/// Ignored in DSi mode.
std::unique_ptr<GBACart::CartCommon> GBAROM = nullptr;
/// NDS ARM9 BIOS to install.
/// Defaults to FreeBIOS, which is not compatible with DSi mode.
std::unique_ptr<ARM9BIOSImage> ARM9BIOS = std::make_unique<ARM9BIOSImage>(bios_arm9_bin);

View File

@ -31,6 +31,7 @@ add_library(core STATIC
FATStorage.cpp
FIFO.h
GBACart.cpp
GBACartMotionPak.cpp
GPU.cpp
GPU2D.cpp
GPU2D_Soft.cpp
@ -98,8 +99,15 @@ if (ENABLE_JIT)
ARMJIT.cpp
ARMJIT_Memory.cpp
ARMJIT_Global.cpp
dolphin/CommonFuncs.cpp)
if (WIN32)
# Required for memory mapping-related functions introduced in Windows 8
target_compile_definitions(core PRIVATE -D_WIN32_WINNT=_WIN32_WINNT_WIN8)
target_link_libraries(core PRIVATE onecore)
endif()
if (ARCHITECTURE STREQUAL x86_64)
target_sources(core PRIVATE
@ -128,6 +136,8 @@ if (ENABLE_JIT)
endif()
endif()
target_include_directories(core INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
set(MELONDS_VERSION_SUFFIX "$ENV{MELONDS_VERSION_SUFFIX}" CACHE STRING "Suffix to add to displayed melonDS version")
option(MELONDS_EMBED_BUILD_INFO "Embed detailed build info into the binary" OFF)
set(MELONDS_GIT_BRANCH "$ENV{MELONDS_GIT_BRANCH}" CACHE STRING "The Git branch used for this build")
@ -179,13 +189,14 @@ endif()
if (WIN32)
target_link_libraries(core PRIVATE ole32 comctl32 wsock32 ws2_32)
target_compile_definitions(core PUBLIC WIN32_LEAN_AND_MEAN NOMINMAX)
elseif(NOT APPLE AND NOT HAIKU)
check_library_exists(rt shm_open "" NEED_LIBRT)
if (NEED_LIBRT)
target_link_libraries(core PRIVATE rt)
endif()
elseif(HAIKU)
target_link_libraries(core PRIVATE network)
target_link_libraries(core PRIVATE network)
endif()
if (ENABLE_JIT_PROFILING)

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -44,6 +44,7 @@ void ARMv5::CP15Reset()
CP15Control = 0x2078; // dunno
RNGSeed = 44203;
TraceProcessID = 0;
DTCMSetting = 0;
ITCMSetting = 0;
@ -643,6 +644,10 @@ void ARMv5::CP15Write(u32 id, u32 val)
UpdateITCMSetting();
return;
case 0xD01:
TraceProcessID = val;
return;
case 0xF00:
//printf("cache debug index register %08X\n", val);
return;
@ -760,6 +765,9 @@ u32 ARMv5::CP15Read(u32 id) const
return DTCMSetting;
case 0x911:
return ITCMSetting;
case 0xD01:
return TraceProcessID;
}
if ((id & 0xF00) == 0xF00) // test/debug shit?

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -75,8 +75,6 @@ const u32 NDMAModes[] =
DSi(
DSiArgs {
NDSArgs {
nullptr,
nullptr,
bios_arm9_bin,
bios_arm7_bin,
Firmware(0),
@ -117,6 +115,8 @@ DSi::DSi(DSiArgs&& args, void* userdata) noexcept :
NWRAM_A = JIT.Memory.GetNWRAM_A();
NWRAM_B = JIT.Memory.GetNWRAM_B();
NWRAM_C = JIT.Memory.GetNWRAM_C();
SetFullBIOSBoot(args.FullBIOSBoot);
}
DSi::~DSi() noexcept
@ -180,6 +180,8 @@ void DSi::Reset()
// LCD init flag
GPU.DispStat[0] |= (1<<6);
GPU.DispStat[1] |= (1<<6);
UpdateVRAMTimings();
}
void DSi::Stop(Platform::StopReason reason)
@ -292,12 +294,14 @@ void DSi::DoSavestateExtra(Savestate* file)
NDMAs[i].DoSavestate(file);
AES.DoSavestate(file);
CamModule.DoSavestate(file);
DSP.DoSavestate(file);
I2C.DoSavestate(file);
I2S.DoSavestate(file);
CamModule.DoSavestate(file);
SDMMC.DoSavestate(file);
SDIO.DoSavestate(file);
UpdateVRAMTimings();
}
void DSi::SetCartInserted(bool inserted)
@ -681,6 +685,8 @@ void DSi::SetupDirectBoot()
ARM9.CP15Write(0x671, 0x02FFC01B);
ARM9.CP15Write(0x910, 0x0E00000A);
ARM9.CP15Write(0x911, 0x00000020);
UpdateVRAMTimings();
}
void DSi::SoftReset()
@ -735,10 +741,11 @@ void DSi::SoftReset()
SCFG_RST = 0;
DSP.SetRstLine(false);
// LCD init flag
GPU.DispStat[0] |= (1<<6);
GPU.DispStat[1] |= (1<<6);
UpdateVRAMTimings();
}
bool DSi::LoadNAND()
@ -1270,6 +1277,20 @@ void DSi::MapNWRAMRange(u32 cpu, u32 num, u32 val)
}
}
void DSi::UpdateVRAMTimings()
{
if (SCFG_EXT[0] & (1<<13))
{
SetARM9RegionTimings(0x06000, 0x07000, Mem9_VRAM, 32, 1, 1);
SetARM7RegionTimings(0x06000, 0x07000, Mem7_VRAM, 32, 1, 1);
}
else
{
SetARM9RegionTimings(0x06000, 0x07000, Mem9_VRAM, 16, 1, 1);
SetARM7RegionTimings(0x06000, 0x07000, Mem7_VRAM, 16, 1, 1);
}
}
void DSi::ApplyNewRAMSize(u32 size)
{
switch (size)
@ -2583,6 +2604,8 @@ void DSi::ARM9IOWrite32(u32 addr, u32 val)
//if (newram != oldram)
// NDS::ScheduleEvent(NDS::Event_DSi_RAMSizeChange, false, 512*512*512, ApplyNewRAMSize, newram);
Log(LogLevel::Debug, "from %08X, ARM7 %08X, %08X\n", NDS::GetPC(0), NDS::GetPC(1), ARM7.R[1]);
UpdateVRAMTimings();
}
return;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -100,6 +100,8 @@ public:
void MapNWRAM_C(u32 num, u8 val);
void MapNWRAMRange(u32 cpu, u32 num, u32 val);
void UpdateVRAMTimings();
u8 ARM9Read8(u32 addr) override;
u16 ARM9Read16(u32 addr) override;
u32 ARM9Read32(u32 addr) override;

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -90,6 +90,9 @@ void DSi_AES::Reset()
*(u32*)&KeyX[1][8] = (u32)(consoleid >> 32) ^ 0xC80C4B72;
*(u32*)&KeyX[1][12] = (u32)consoleid;
// slot 2: For 'Tad'
std::memcpy(KeyX[2], &DSi.ARM9iBIOS[0x8B8C], 0x10);
// slot 3: console-unique eMMC crypto
*(u32*)&KeyX[3][0] = (u32)consoleid;
*(u32*)&KeyX[3][4] = (u32)consoleid ^ 0x24EE6906;
@ -575,4 +578,4 @@ void DSi_AES::WriteKeyY(u32 slot, u32 offset, u32 val, u32 mask)
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -34,14 +34,17 @@ using Platform::LogLevel;
// namely, how long cameras take to process frames
// camera IRQ is fired at roughly 15FPS with default config
const u32 DSi_CamModule::kIRQInterval = 1120000; // ~30 FPS
const u32 DSi_CamModule::kTransferStart = 60000;
// camera IRQ marks camera VBlank
// each scanline takes roughly 3173 cycles
const u32 DSi_CamModule::kIRQInterval = 2234248; // ~15 FPS
const u32 DSi_CamModule::kScanlineTime = 3173;
const u32 DSi_CamModule::kTransferStart = DSi_CamModule::kIRQInterval - (DSi_CamModule::kScanlineTime * 480);
DSi_CamModule::DSi_CamModule(melonDS::DSi& dsi) : DSi(dsi)
{
DSi.RegisterEventFunc(Event_DSi_CamIRQ, 0, MemberEventFunc(DSi_CamModule, IRQ));
DSi.RegisterEventFunc(Event_DSi_CamTransfer, 0, MemberEventFunc(DSi_CamModule, TransferScanline));
DSi.RegisterEventFuncs(Event_DSi_CamIRQ, this, {MakeEventThunk(DSi_CamModule, IRQ)});
DSi.RegisterEventFuncs(Event_DSi_CamTransfer, this, {MakeEventThunk(DSi_CamModule, TransferScanline)});
Camera0 = DSi.I2C.GetOuterCamera();
Camera1 = DSi.I2C.GetInnerCamera();
@ -52,8 +55,8 @@ DSi_CamModule::~DSi_CamModule()
Camera0 = nullptr;
Camera1 = nullptr;
DSi.UnregisterEventFunc(Event_DSi_CamIRQ, 0);
DSi.UnregisterEventFunc(Event_DSi_CamTransfer, 0);
DSi.UnregisterEventFuncs(Event_DSi_CamIRQ);
DSi.UnregisterEventFuncs(Event_DSi_CamTransfer);
}
void DSi_CamModule::Reset()
@ -64,12 +67,15 @@ void DSi_CamModule::Reset()
CropStart = 0;
CropEnd = 0;
memset(DataBuffer, 0, 512*sizeof(u32));
BufferReadPos = 0;
BufferWritePos = 0;
Transferring = false;
memset(PixelBuffer, 0, sizeof(PixelBuffer));
CurPixelBuffer = 0;
BufferNumLines = 0;
CurCamera = nullptr;
// TODO: ideally this should be started when a camera is active
// instead of just being a constant thing
DSi.ScheduleEvent(Event_DSi_CamIRQ, false, kIRQInterval, 0, 0);
}
@ -86,9 +92,30 @@ void DSi_CamModule::DoSavestate(Savestate* file)
file->Var16(&ModuleCnt);
file->Var16(&Cnt);
/*file->VarArray(FrameBuffer, sizeof(FrameBuffer));
file->Var32(&TransferPos);
file->Var32(&FrameLength);*/
file->Var32(&CropStart);
file->Var32(&CropEnd);
file->Bool32(&Transferring);
file->VarArray(&PixelBuffer[0].Data, 512);
file->Var32(&PixelBuffer[0].ReadPos);
file->Var32(&PixelBuffer[0].WritePos);
file->VarArray(&PixelBuffer[1].Data, 512);
file->Var32(&PixelBuffer[1].ReadPos);
file->Var32(&PixelBuffer[1].WritePos);
file->Var8(&CurPixelBuffer);
file->Var32(&BufferNumLines);
if (!file->Saving)
{
DSi_Camera* activecam = nullptr;
if (Camera0->IsActivated()) activecam = Camera0;
else if (Camera1->IsActivated()) activecam = Camera1;
CurCamera = activecam;
}
}
@ -108,14 +135,8 @@ void DSi_CamModule::IRQ(u32 param)
if (Cnt & (1<<11))
DSi.SetIRQ(0, IRQ_DSi_Camera);
if (Cnt & (1<<15))
{
BufferReadPos = 0;
BufferWritePos = 0;
BufferNumLines = 0;
CurCamera = activecam;
DSi.ScheduleEvent(Event_DSi_CamTransfer, false, kTransferStart, 0, 0);
}
CurCamera = activecam;
DSi.ScheduleEvent(Event_DSi_CamTransfer, false, kTransferStart, 0, 0);
}
DSi.ScheduleEvent(Event_DSi_CamIRQ, true, kIRQInterval, 0, 0);
@ -123,17 +144,35 @@ void DSi_CamModule::IRQ(u32 param)
void DSi_CamModule::TransferScanline(u32 line)
{
u32* dstbuf = &DataBuffer[BufferWritePos];
int maxlen = 512 - BufferWritePos;
if (Cnt & (1<<4))
{
Transferring = false;
return;
}
if (line == 0)
{
if (!(Cnt & (1<<15)))
return;
BufferNumLines = 0;
Transferring = true;
}
sPixelBuffer* buffer = &PixelBuffer[CurPixelBuffer];
u32* dstbuf = &buffer->Data[buffer->WritePos];
int maxlen = 512 - buffer->WritePos;
u32 tmpbuf[512];
int datalen = CurCamera->TransferScanline(tmpbuf, 512);
int lines_next;
int datalen = CurCamera->TransferScanline(tmpbuf, 512, lines_next);
u32 numscan;
// TODO: must be tweaked such that each block has enough time to transfer
u32 delay = datalen*4 + 16;
u32 delay = lines_next * kScanlineTime;
int copystart = 0;
int copylen = datalen;
bool line_last = false;
if (Cnt & (1<<14))
{
@ -143,10 +182,8 @@ void DSi_CamModule::TransferScanline(u32 line)
int yend = (CropEnd >> 16) & 0x1FF;
if (line < ystart || line > yend)
{
if (!CurCamera->TransferDone())
DSi.ScheduleEvent(Event_DSi_CamTransfer, false, delay, 0, line+1);
return;
if (line == yend+1) line_last = true;
goto skip_line;
}
int xstart = (CropStart >> 1) & 0x1FF;
@ -164,7 +201,6 @@ void DSi_CamModule::TransferScanline(u32 line)
if (copylen > maxlen)
{
copylen = maxlen;
Cnt |= (1<<4);
}
if (Cnt & (1<<13))
@ -206,27 +242,70 @@ void DSi_CamModule::TransferScanline(u32 line)
memcpy(dstbuf, &tmpbuf[copystart], copylen*sizeof(u32));
}
u32 numscan = Cnt & 0x000F;
buffer->WritePos += copylen;
if (buffer->WritePos > 512) buffer->WritePos = 512;
numscan = Cnt & 0x000F;
if (BufferNumLines >= numscan)
{
BufferReadPos = 0; // checkme
BufferWritePos = 0;
BufferNumLines = 0;
DSi.CheckNDMAs(0, 0x0B);
SwapPixelBuffers();
}
else
{
BufferWritePos += copylen;
if (BufferWritePos > 512) BufferWritePos = 512;
BufferNumLines++;
}
if (CurCamera->TransferDone())
skip_line:
bool done = CurCamera->TransferDone();
if (done || line_last)
{
// when the frame is finished, transfer any remaining data if needed
// (if the frame height isn't a multiple of the DMA interval)
if (BufferNumLines > 0)
{
BufferNumLines = 0;
SwapPixelBuffers();
}
}
if (done)
{
Transferring = false;
return;
}
DSi.ScheduleEvent(Event_DSi_CamTransfer, false, delay, 0, line+1);
}
void DSi_CamModule::SwapPixelBuffers()
{
// pixel buffers are swapped every time a buffer is filled (ie. when the DMA interval is reached)
// the swap fails if the other buffer isn't empty
sPixelBuffer* otherbuf = &PixelBuffer[CurPixelBuffer ^ 1];
if (otherbuf->ReadPos < otherbuf->WritePos)
{
// overrun
Cnt |= (1<<4);
Transferring = false;
}
else
{
PixelBuffer[CurPixelBuffer].ReadPos = 0;
otherbuf->WritePos = 0;
CurPixelBuffer ^= 1;
DSi.CheckNDMAs(0, 0x0B);
}
}
bool DSi_CamModule::IsTransferring()
{
if (Cnt & (1<<15)) return true;
if (Transferring) return true;
return false;
}
u8 DSi_CamModule::Read8(u32 addr)
{
@ -241,7 +320,7 @@ u16 DSi_CamModule::Read16(u32 addr)
switch (addr)
{
case 0x04004200: return ModuleCnt;
case 0x04004202: return Cnt;
case 0x04004202: return Cnt | (Transferring ? (1<<15) : 0);
}
Log(LogLevel::Debug, "unknown DSi cam read16 %08X\n", addr);
@ -254,14 +333,14 @@ u32 DSi_CamModule::Read32(u32 addr)
{
case 0x04004204:
{
u32 ret = DataBuffer[BufferReadPos];
if (Cnt & (1<<15))
{
if (BufferReadPos < 511)
BufferReadPos++;
// CHECKME!!!!
// also presumably we should set bit4 in Cnt if there's no new data to be read
}
sPixelBuffer* buffer = &PixelBuffer[CurPixelBuffer ^ 1];
u32 ret;
if (buffer->ReadPos < buffer->WritePos)
ret = buffer->Data[buffer->ReadPos++];
else if (buffer->ReadPos > 0)
ret = buffer->Data[buffer->ReadPos - 1];
else
ret = buffer->Data[0];
return ret;
}
@ -296,6 +375,7 @@ void DSi_CamModule::Write16(u32 addr, u16 val)
// CHECKME
Cnt = 0;
Transferring = false;
}
if ((ModuleCnt & (1<<5)) && !(oldcnt & (1<<5)))
@ -307,12 +387,9 @@ void DSi_CamModule::Write16(u32 addr, u16 val)
case 0x04004202:
{
// TODO: during a transfer, clearing bit15 does not reflect immediately
// maybe it needs to finish the trasnfer or atleast the current block
// checkme
u16 oldmask;
if (Cnt & 0x8000)
if (IsTransferring())
{
val &= 0x8F20;
oldmask = 0x601F;
@ -327,32 +404,26 @@ void DSi_CamModule::Write16(u32 addr, u16 val)
if (val & (1<<5))
{
Cnt &= ~(1<<4);
BufferReadPos = 0;
BufferWritePos = 0;
}
if ((val & (1<<15)) && !(Cnt & (1<<15)))
{
// start transfer
//DSi::CheckNDMAs(0, 0x0B);
memset(PixelBuffer, 0, sizeof(PixelBuffer));
CurPixelBuffer = 0;
}
}
return;
case 0x04004210:
if (Cnt & (1<<15)) return;
if (IsTransferring()) return;
CropStart = (CropStart & 0x01FF0000) | (val & 0x03FE);
return;
case 0x04004212:
if (Cnt & (1<<15)) return;
if (IsTransferring()) return;
CropStart = (CropStart & 0x03FE) | ((val & 0x01FF) << 16);
return;
case 0x04004214:
if (Cnt & (1<<15)) return;
if (IsTransferring()) return;
CropEnd = (CropEnd & 0x01FF0000) | (val & 0x03FE);
return;
case 0x04004216:
if (Cnt & (1<<15)) return;
if (IsTransferring()) return;
CropEnd = (CropEnd & 0x03FE) | ((val & 0x01FF) << 16);
return;
}
@ -365,11 +436,11 @@ void DSi_CamModule::Write32(u32 addr, u32 val)
switch (addr)
{
case 0x04004210:
if (Cnt & (1<<15)) return;
if (IsTransferring()) return;
CropStart = val & 0x01FF03FE;
return;
case 0x04004214:
if (Cnt & (1<<15)) return;
if (IsTransferring()) return;
CropEnd = val & 0x01FF03FE;
return;
}
@ -429,6 +500,7 @@ void DSi_Camera::Reset()
// default state is preview mode (checkme)
MCURegs[0x2104] = 3;
InternalY = 0;
TransferY = 0;
memset(FrameBuffer, 0, (640*480/2)*sizeof(u32));
}
@ -449,6 +521,7 @@ bool DSi_Camera::IsActivated() const
void DSi_Camera::StartTransfer()
{
InternalY = 0;
TransferY = 0;
u8 state = MCURegs[0x2104];
@ -482,9 +555,11 @@ bool DSi_Camera::TransferDone() const
return TransferY >= FrameHeight;
}
int DSi_Camera::TransferScanline(u32* buffer, int maxlen)
int DSi_Camera::TransferScanline(u32* buffer, int maxlen, int& nlines)
{
if (TransferY >= FrameHeight)
nlines = 0;
if ((TransferY >= FrameHeight) || (InternalY >= 480))
return 0;
if (FrameWidth > 640 || FrameHeight > 480 ||
@ -500,7 +575,7 @@ int DSi_Camera::TransferScanline(u32* buffer, int maxlen)
// TODO: non-YUV pixel formats and all
int retlen = FrameWidth >> 1;
int sy = (TransferY * 480) / FrameHeight;
int sy = InternalY;
if (FrameReadMode & (1<<1))
sy = 479 - sy;
@ -529,7 +604,15 @@ int DSi_Camera::TransferScanline(u32* buffer, int maxlen)
}
}
TransferY++;
// determine how many scanlines we're skipping until the next scanline
int oldy = TransferY;
do
{
InternalY++;
TransferY = (InternalY * FrameHeight) / 480;
nlines++;
}
while ((TransferY == oldy) && (InternalY < 480));
return retlen;
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -44,7 +44,7 @@ public:
bool TransferDone() const;
// lengths in words
int TransferScanline(u32* buffer, int maxlen);
int TransferScanline(u32* buffer, int maxlen, int& nlines);
void Acquire() override;
u8 Read(bool last) override;
@ -77,6 +77,7 @@ private:
u16 FrameWidth, FrameHeight;
u16 FrameReadMode, FrameFormat;
int InternalY;
int TransferY;
u32 FrameBuffer[640*480/2]; // YUYV framebuffer, two pixels per word
};
@ -117,14 +118,26 @@ private:
u32 CropStart, CropEnd;
// pixel data buffer holds a maximum of 512 words, regardless of how long scanlines are
u32 DataBuffer[512];
u32 BufferReadPos, BufferWritePos;
bool Transferring;
// pixel data buffers hold a maximum of 512 words, regardless of how long scanlines are
typedef struct
{
u32 Data[512];
u32 ReadPos, WritePos;
} sPixelBuffer;
sPixelBuffer PixelBuffer[2];
u8 CurPixelBuffer;
u32 BufferNumLines;
DSi_Camera* CurCamera;
static const u32 kIRQInterval;
static const u32 kScanlineTime;
static const u32 kTransferStart;
void SwapPixelBuffers();
bool IsTransferring();
};
}

View File

@ -109,7 +109,7 @@ void DSi_DSP::AudioCb(std::array<s16, 2> frame)
DSi_DSP::DSi_DSP(melonDS::DSi& dsi) : DSi(dsi)
{
DSi.RegisterEventFunc(Event_DSi_DSP, 0, MemberEventFunc(DSi_DSP, DSPCatchUpU32));
DSi.RegisterEventFuncs(Event_DSi_DSP, this, {MakeEventThunk(DSi_DSP, DSPCatchUpU32)});
TeakraCore = new Teakra::Teakra();
SCFG_RST = false;
@ -156,7 +156,7 @@ DSi_DSP::~DSi_DSP()
//PDATAWriteFifo = NULL;
TeakraCore = NULL;
DSi.UnregisterEventFunc(Event_DSi_DSP, 0);
DSi.UnregisterEventFuncs(Event_DSi_DSP);
}
void DSi_DSP::Reset()
@ -321,7 +321,7 @@ void DSi_DSP::PDataDMAFetch()
}
void DSi_DSP::PDataDMAStart()
{
switch ((DSP_PSTS & (3<<2)) >> 2)
switch ((DSP_PCFG & (3<<2)) >> 2)
{
case 0: PDataDMALen = 1; break;
case 1: PDataDMALen = 8; break;
@ -346,7 +346,7 @@ void DSi_DSP::PDataDMACancel()
}
u16 DSi_DSP::PDataDMAReadMMIO()
{
u16 ret;
u16 ret = 0; // TODO: is this actually 0, or just open bus?
if (!PDATAReadFifo.IsEmpty())
ret = PDATAReadFifo.Read();
@ -360,15 +360,9 @@ u16 DSi_DSP::PDataDMAReadMMIO()
for (int i = 0; i < left; ++i)
PDataDMAFetch();
ret = PDATAReadFifo.Read();
}
else
{
// ah, crap
ret = 0; // TODO: is this actually 0, or just open bus?
}
// TODO only trigger IRQ if enabled!!
if (!PDATAReadFifo.IsEmpty() || PDATAReadFifo.IsFull())
DSi.SetIRQ(0, IRQ_DSi_DSP);

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -189,20 +189,18 @@ void NANDImage::SetupFATCrypto(AES_ctx* ctx, u32 ctr)
u8 iv[16];
memcpy(iv, FATIV.data(), sizeof(iv));
u32 res;
res = iv[15] + (ctr & 0xFF);
iv[15] = (res & 0xFF);
res = iv[14] + ((ctr >> 8) & 0xFF) + (res >> 8);
iv[14] = (res & 0xFF);
res = iv[13] + ((ctr >> 16) & 0xFF) + (res >> 8);
iv[13] = (res & 0xFF);
res = iv[12] + (ctr >> 24) + (res >> 8);
iv[12] = (res & 0xFF);
iv[11] += (res >> 8);
for (int i = 10; i >= 0; i--)
{
if (iv[i+1] == 0) iv[i]++;
else break;
u8 ctr_value[16] = {0};
ctr_value[15] = ctr & 0xFF;
ctr_value[14] = (ctr >> 8) & 0xFF;
ctr_value[13] = (ctr >> 16) & 0xFF;
ctr_value[12] = (ctr >> 24) & 0xFF;
unsigned carry = 0;
for (unsigned i = 0; i < 16; i ++) {
unsigned j = 15-i;
unsigned x = iv[j] + ctr_value[j] + carry;
carry = x >= 0x100;
iv[j] = x;
}
AES_init_ctx_iv(ctx, FATKey.data(), iv);

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -132,7 +132,6 @@ void DSi_NDMA::WriteCnt(u32 val)
// TODO: unsupported start modes:
// * timers (00-03)
// * camera (ARM9 0B)
// * microphone (ARM7 0C)
// * NDS-wifi?? (ARM7 07, likely not working)
@ -270,11 +269,18 @@ void DSi_NDMA::Run9()
if ((StartMode & 0x1F) == 0x10) // CHECKME
{
// no repeat
Cnt &= ~(1<<31);
if (Cnt & (1<<30)) DSi.SetIRQ(0, IRQ_DSi_NDMA0 + Num);
}
else if (!(Cnt & (1<<29)))
else if (Cnt & (1<<29))
{
// repeat infinitely
if (Cnt & (1<<30)) DSi.SetIRQ(0, IRQ_DSi_NDMA0 + Num);
}
else
{
// repeat until total count is reached
if (TotalRemCount == 0)
{
Cnt &= ~(1<<31);
@ -359,11 +365,18 @@ void DSi_NDMA::Run7()
if ((StartMode & 0x1F) == 0x10) // CHECKME
{
// no repeat
Cnt &= ~(1<<31);
if (Cnt & (1<<30)) DSi.SetIRQ(1, IRQ_DSi_NDMA0 + Num);
}
else if (!(Cnt & (1<<29)))
else if (Cnt & (1<<29))
{
// repeat infinitely
if (Cnt & (1<<30)) DSi.SetIRQ(1, IRQ_DSi_NDMA0 + Num);
}
else
{
// repeat until total count is reached
if (TotalRemCount == 0)
{
Cnt &= ~(1<<31);

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -31,7 +31,7 @@ using Platform::Log;
using Platform::LogLevel;
const u8 CIS0[256] =
u8 CIS0[256] =
{
0x01, 0x03, 0xD9, 0x01, 0xFF,
0x20, 0x04, 0x71, 0x02, 0x00, 0x02,
@ -70,7 +70,7 @@ const u8 CIS0[256] =
0x00, 0x00, 0x00
};
const u8 CIS1[256] =
u8 CIS1[256] =
{
0x20, 0x04, 0x71, 0x02, 0x00, 0x02,
0x21, 0x02, 0x0C, 0x00,
@ -134,7 +134,7 @@ DSi_NWifi::DSi_NWifi(melonDS::DSi& dsi, DSi_SDHost* host) :
},
DSi(dsi)
{
DSi.RegisterEventFunc(Event_DSi_NWifi, 0, MemberEventFunc(DSi_NWifi, MSTimer));
DSi.RegisterEventFuncs(Event_DSi_NWifi, this, {MakeEventThunk(DSi_NWifi, MSTimer)});
// this seems to control whether the firmware upload is done
EEPROMReady = 0;
@ -144,7 +144,7 @@ DSi_NWifi::~DSi_NWifi()
{
DSi.CancelEvent(Event_DSi_NWifi);
DSi.UnregisterEventFunc(Event_DSi_NWifi, 0);
DSi.UnregisterEventFuncs(Event_DSi_NWifi);
}
void DSi_NWifi::Reset()
@ -201,6 +201,9 @@ void DSi_NWifi::Reset()
break;
}
CIS0[9] = ChipID >= 0x0D000000;
CIS1[4] = CIS0[9];
memset(EEPROM, 0, 0x400);
*(u32*)&EEPROM[0x000] = 0x300;
@ -227,6 +230,8 @@ void DSi_NWifi::Reset()
BeaconTimer = 0x10A2220ULL;
ConnectionStatus = 0;
SendBSSInfo = true;
DSi.CancelEvent(Event_DSi_NWifi);
}
@ -1001,7 +1006,7 @@ void DSi_NWifi::WMI_Command()
}
// checkme
ScanTimer = scantime*5;
ScanTimer = scantime*8;
}
break;
@ -1036,6 +1041,7 @@ void DSi_NWifi::WMI_Command()
// TODO: store it somewhere
Log(LogLevel::Debug, "WMI: set probed SSID: id=%d, flags=%02X, len=%d, SSID=%s\n", id, flags, len, ssid);
SendBSSInfo = flags == 0 || strcmp(ssid, WifiAP::APName) == 0;
}
break;
@ -1405,6 +1411,11 @@ void DSi_NWifi::SendWMIAck(u8 ep)
void DSi_NWifi::SendWMIBSSInfo(u8 type, u8* data, u32 len)
{
if (!SendBSSInfo) {
Log(LogLevel::Info, "NWifi: melonAP filtered, not sending WMI BSSINFO event\n");
return;
}
if (!Mailbox[8].CanFit(6+len+2+16))
{
Log(LogLevel::Error, "NWifi: !! not enough space in RX buffer for WMI BSSINFO event\n");

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -147,6 +147,8 @@ private:
u32 ConnectionStatus;
u8 LANBuffer[2048];
bool SendBSSInfo;
};
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -64,10 +64,9 @@ enum
DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi, DSi_NAND::NANDImage&& nand, std::optional<FATStorage>&& sdcard) noexcept : DSi(dsi), Num(0)
{
DSi.RegisterEventFunc( Event_DSi_SDMMCTransfer,
Transfer_TX, MemberEventFunc(DSi_SDHost, FinishTX));
DSi.RegisterEventFunc( Event_DSi_SDMMCTransfer,
Transfer_RX, MemberEventFunc(DSi_SDHost, FinishRX));
DSi.RegisterEventFuncs(Event_DSi_SDMMCTransfer, this,
{MakeEventThunk(DSi_SDHost, FinishTX),
MakeEventThunk(DSi_SDHost, FinishRX)});
Ports[0] = sdcard ? std::make_unique<DSi_MMCStorage>(DSi, this, std::move(*sdcard)) : nullptr;
sdcard = std::nullopt; // to ensure that sdcard isn't left with a moved-from object
@ -77,10 +76,9 @@ DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi, DSi_NAND::NANDImage&& nand, std::optio
// Creates an SDIO host
DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi) noexcept : DSi(dsi), Num(1)
{
DSi.RegisterEventFunc(Event_DSi_SDIOTransfer ,
Transfer_TX, MemberEventFunc(DSi_SDHost, FinishTX));
DSi.RegisterEventFunc(Event_DSi_SDIOTransfer,
Transfer_RX, MemberEventFunc(DSi_SDHost, FinishRX));
DSi.RegisterEventFuncs(Event_DSi_SDIOTransfer, this,
{MakeEventThunk(DSi_SDHost, FinishTX),
MakeEventThunk(DSi_SDHost, FinishRX)});
Ports[0] = std::make_unique<DSi_NWifi>(DSi, this);
Ports[1] = nullptr;
@ -88,10 +86,7 @@ DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi) noexcept : DSi(dsi), Num(1)
DSi_SDHost::~DSi_SDHost()
{
DSi.UnregisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer,
Transfer_TX);
DSi.UnregisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer,
Transfer_RX);
DSi.UnregisterEventFuncs(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer);
// unique_ptr's destructor will clean up the ports
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -242,8 +242,6 @@ u16 CartGame::ROMRead(u32 addr) const
case 0xC8: return GPIO.control;
}
}
else
return 0;
}
// CHECKME: does ROM mirror?
@ -539,6 +537,57 @@ CartGameSolarSensor::CartGameSolarSensor(std::unique_ptr<u8[]>&& rom, u32 len, s
{
}
std::unique_ptr<CartGameSolarSensor> CreateFakeSolarSensorROM(const char* gamecode, const NDSCart::CartCommon& cart, void* userdata) noexcept
{
return CreateFakeSolarSensorROM(gamecode, cart.GetHeader().NintendoLogo, userdata);
}
std::unique_ptr<CartGameSolarSensor> CreateFakeSolarSensorROM(const char* gamecode, const GBACart::CartGame& cart, void* userdata) noexcept
{
return CreateFakeSolarSensorROM(gamecode, cart.GetHeader().NintendoLogo, userdata);
}
std::unique_ptr<CartGameSolarSensor> CreateFakeSolarSensorROM(const char* gamecode, const u8* logo, void* userdata) noexcept
{
if (!gamecode)
return nullptr;
if (strnlen(gamecode, sizeof(GBAHeader::GameCode)) > sizeof(GBAHeader::GameCode))
return nullptr;
bool solarsensor = false;
for (const char* i : SOLAR_SENSOR_GAMECODES)
{
if (strcmp(gamecode, i) == 0) {
solarsensor = true;
break;
}
}
if (!solarsensor)
return nullptr;
// just 256 bytes; we don't need a whole ROM!
constexpr size_t FAKE_BOKTAI_ROM_LENGTH = 0x100;
std::unique_ptr<u8[]> rom = std::make_unique<u8[]>(FAKE_BOKTAI_ROM_LENGTH);
// create a fake ROM
GBAHeader& header = *reinterpret_cast<GBAHeader*>(rom.get());
memcpy(header.Title, BOKTAI_STUB_TITLE, strnlen(BOKTAI_STUB_TITLE, sizeof(header.Title)));
memcpy(header.GameCode, gamecode, strnlen(gamecode, sizeof(header.GameCode)));
header.FixedValue = 0x96;
if (logo)
{
memcpy(header.NintendoLogo, logo, sizeof(header.NintendoLogo));
}
else
{
memset(header.NintendoLogo, 0xFF, sizeof(header.NintendoLogo));
}
return std::make_unique<CartGameSolarSensor>(std::move(rom), FAKE_BOKTAI_ROM_LENGTH, nullptr, 0, userdata);
}
const int CartGameSolarSensor::kLuxLevels[11] = {0, 5, 11, 18, 27, 42, 62, 84, 109, 139, 183};
void CartGameSolarSensor::Reset()
@ -724,6 +773,27 @@ void CartRumblePak::ROMWrite(u32 addr, u16 val)
}
}
CartGuitarGrip::CartGuitarGrip(void* userdata) :
CartCommon(GuitarGrip),
UserData(userdata)
{
}
CartGuitarGrip::~CartGuitarGrip() = default;
u16 CartGuitarGrip::ROMRead(u32 addr) const
{
return 0xF9FF;
}
u8 CartGuitarGrip::SRAMRead(u32 addr)
{
return ~((Platform::Addon_KeyDown(Platform::KeyGuitarGripGreen, UserData) ? 0x40 : 0)
| (Platform::Addon_KeyDown(Platform::KeyGuitarGripRed, UserData) ? 0x20 : 0)
| (Platform::Addon_KeyDown(Platform::KeyGuitarGripYellow, UserData) ? 0x10 : 0)
| (Platform::Addon_KeyDown(Platform::KeyGuitarGripBlue, UserData) ? 0x08 : 0));
}
GBACartSlot::GBACartSlot(melonDS::NDS& nds, std::unique_ptr<CartCommon>&& cart) noexcept : NDS(nds), Cart(std::move(cart))
{
}
@ -832,6 +902,47 @@ std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen
return cart;
}
std::unique_ptr<CartCommon> LoadAddon(int type, void* userdata)
{
std::unique_ptr<CartCommon> cart;
switch (type)
{
case GBAAddon_RAMExpansion:
cart = std::make_unique<CartRAMExpansion>();
break;
case GBAAddon_RumblePak:
cart = std::make_unique<CartRumblePak>(userdata);
break;
case GBAAddon_SolarSensorBoktai1:
// US Boktai 1
cart = CreateFakeSolarSensorROM("U3IE", nullptr, userdata);
break;
case GBAAddon_SolarSensorBoktai2:
// US Boktai 2
cart = CreateFakeSolarSensorROM("U32E", nullptr, userdata);
break;
case GBAAddon_SolarSensorBoktai3:
// JP Boktai 3
cart = CreateFakeSolarSensorROM("U33J", nullptr, userdata);
break;
case GBAAddon_MotionPakHomebrew:
cart = std::make_unique<CartMotionPakHomebrew>(userdata);
break;
case GBAAddon_MotionPakRetail:
cart = std::make_unique<CartMotionPakRetail>(userdata);
break;
case GBAAddon_GuitarGrip:
cart = std::make_unique<CartGuitarGrip>(userdata);
break;
default:
Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type);
return nullptr;
}
cart->Reset();
return cart;
}
void GBACartSlot::SetCart(std::unique_ptr<CartCommon>&& cart) noexcept
{
Cart = std::move(cart);
@ -864,23 +975,6 @@ void GBACartSlot::SetSaveMemory(const u8* savedata, u32 savelen) noexcept
}
}
void GBACartSlot::LoadAddon(void* userdata, int type) noexcept
{
switch (type)
{
case GBAAddon_RAMExpansion:
Cart = std::make_unique<CartRAMExpansion>();
break;
case GBAAddon_RumblePak:
Cart = std::make_unique<CartRumblePak>(userdata);
break;
default:
Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type);
return;
}
}
std::unique_ptr<CartCommon> GBACartSlot::EjectCart() noexcept
{
return std::move(Cart);

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -33,8 +33,30 @@ enum CartType
GameSolarSensor = 0x102,
RAMExpansion = 0x201,
RumblePak = 0x202,
MotionPakHomebrew = 0x203,
MotionPakRetail = 0x204,
GuitarGrip = 0x205,
};
// See https://problemkaputt.de/gbatek.htm#gbacartridgeheader for details
struct GBAHeader
{
u32 EntryPoint;
u8 NintendoLogo[156]; // must be valid
char Title[12];
char GameCode[4];
char MakerCode[2];
u8 FixedValue; // must be 0x96
u8 MainUnitCode;
u8 DeviceType;
u8 Reserved0[7];
u8 SoftwareVersion;
u8 ComplementCheck;
u8 Reserved1[2];
};
static_assert(sizeof(GBAHeader) == 192, "GBAHeader should be 192 bytes");
// CartCommon -- base code shared by all cart types
class CartCommon
{
@ -91,6 +113,8 @@ public:
[[nodiscard]] const u8* GetROM() const override { return ROM.get(); }
[[nodiscard]] u32 GetROMLength() const override { return ROMLength; }
[[nodiscard]] const GBAHeader& GetHeader() const noexcept { return *reinterpret_cast<const GBAHeader*>(ROM.get()); }
[[nodiscard]] GBAHeader& GetHeader() noexcept { return *reinterpret_cast<GBAHeader*>(ROM.get()); }
u8* GetSaveMemory() const override;
u32 GetSaveMemoryLength() const override;
@ -211,11 +235,68 @@ private:
u16 RumbleState = 0;
};
// CartGuitarGrip -- DS Guitar Grip (used in various NDS games)
class CartGuitarGrip : public CartCommon
{
public:
CartGuitarGrip(void* userdata);
~CartGuitarGrip() override;
u16 ROMRead(u32 addr) const override;
u8 SRAMRead(u32 addr) override;
private:
void* UserData;
};
// CartMotionPakHomebrew -- DS Motion Pak (Homebrew)
class CartMotionPakHomebrew : public CartCommon
{
public:
CartMotionPakHomebrew(void* userdata);
~CartMotionPakHomebrew() override;
void Reset() override;
void DoSavestate(Savestate* file) override;
u16 ROMRead(u32 addr) const override;
u8 SRAMRead(u32 addr) override;
private:
void* UserData;
u16 ShiftVal = 0;
};
// CartMotionPakRetail -- DS Motion Pack (Retail)
class CartMotionPakRetail : public CartCommon
{
public:
CartMotionPakRetail(void* userdata);
~CartMotionPakRetail() override;
void Reset() override;
void DoSavestate(Savestate* file) override;
u16 ROMRead(u32 addr) const override;
u8 SRAMRead(u32 addr) override;
private:
void* UserData;
u8 Value;
u8 Step = 16;
};
// possible inputs for GBA carts that might accept user input
enum
{
Input_SolarSensorDown = 0,
Input_SolarSensorUp,
Input_GuitarGripGreen,
Input_GuitarGripRed,
Input_GuitarGripYellow,
Input_GuitarGripBlue,
};
class GBACartSlot
@ -241,8 +322,6 @@ public:
[[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); }
[[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); }
void LoadAddon(void* userdata, int type) noexcept;
/// @return The cart that was in the cart slot if any,
/// or \c nullptr if the cart slot was empty.
std::unique_ptr<CartCommon> EjectCart() noexcept;
@ -309,6 +388,25 @@ std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen, const u8* sr
/// or \c nullptr if there was an error.
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::unique_ptr<u8[]>&& sramdata, u32 sramlen, void* userdata = nullptr);
std::unique_ptr<CartCommon> LoadAddon(int type, void* userdata);
/// Creates a solar sensor-enabled GBA cart without needing a real Boktai ROM.
/// This enables the solar sensor to be used in supported games.
/// Will not contain any SRAM.
/// @param gamecode
/// @param logo The Nintendo logo data embedded in the headers for GBA ROMs and NDS ROMs.
/// Required for the cart to be recognized as a valid GBA cart.
/// Overloads that accept cart objects directly exist as well.
/// If not provided, then it will have to be patched with equivalent data
/// from a real ROM (NDS or GBA) before booting the emulator.
/// @param userdata Optional user data to associate with the cart.
/// @return A CartGameSolarSensor if the ROM was created successfully,
/// or nullptr if any argument is wrong (e.g. an incorrect game code).
std::unique_ptr<CartGameSolarSensor> CreateFakeSolarSensorROM(const char* gamecode, const u8* logo, void* userdata = nullptr) noexcept;
std::unique_ptr<CartGameSolarSensor> CreateFakeSolarSensorROM(const char* gamecode, const NDSCart::CartCommon& cart, void* userdata = nullptr) noexcept;
std::unique_ptr<CartGameSolarSensor> CreateFakeSolarSensorROM(const char* gamecode, const GBACart::CartGame& cart, void* userdata = nullptr) noexcept;
constexpr const char* BOKTAI_STUB_TITLE = "BOKTAI STUB";
}
#endif // GBACART_H

196
src/GBACartMotionPak.cpp Normal file
View File

@ -0,0 +1,196 @@
/*
Copyright 2016-2024 melonDS team
This file is part of melonDS.
melonDS is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <assert.h>
#include "NDS.h"
#include "GBACart.h"
#include "Platform.h"
#include <algorithm>
#include "math.h"
namespace melonDS
{
using Platform::Log;
using Platform::LogLevel;
namespace GBACart
{
CartMotionPakHomebrew::CartMotionPakHomebrew(void* userdata) :
CartCommon(MotionPakHomebrew),
UserData(userdata)
{
}
CartMotionPakHomebrew::~CartMotionPakHomebrew() = default;
void CartMotionPakHomebrew::Reset()
{
ShiftVal = 0;
}
void CartMotionPakHomebrew::DoSavestate(Savestate* file)
{
CartCommon::DoSavestate(file);
file->Var16(&ShiftVal);
}
u16 CartMotionPakHomebrew::ROMRead(u32 addr) const
{
// CHECKME: Does this apply to the homebrew cart as well?
return 0xFCFF;
}
static int AccelerationToMotionPak(float accel)
{
const float GRAVITY_M_S2 = 9.80665f;
return std::clamp(
(int) ((accel / (5 * GRAVITY_M_S2) + 0.5) * 4096),
0, 4095
);
}
static int AccelerationToMotionPakRetail(float accel)
{
const float GRAVITY_M_S2 = 9.80665f;
return std::clamp(
(int) ((accel / (5 * GRAVITY_M_S2) + 0.5) * 256),
0, 254
);
}
static int RotationToMotionPak(float rot)
{
const float DEGREES_PER_RAD = 180 / M_PI;
const float COUNTS_PER_DEG_PER_SEC = 0.825;
const int CENTER = 1680;
return std::clamp(
(int) ((rot * DEGREES_PER_RAD * COUNTS_PER_DEG_PER_SEC) + CENTER + 0.5),
0, 4095
);
}
u8 CartMotionPakHomebrew::SRAMRead(u32 addr)
{
// CHECKME: SRAM address mask
addr &= 0xFFFF;
switch (addr)
{
case 0:
// Read next byte
break;
case 2:
// Read X acceleration
ShiftVal = AccelerationToMotionPak(Platform::Addon_MotionQuery(Platform::MotionAccelerationX, UserData)) << 4;
// CHECKME: First byte returned when reading acceleration/rotation
return 0;
case 4:
// Read Y acceleration
ShiftVal = AccelerationToMotionPak(Platform::Addon_MotionQuery(Platform::MotionAccelerationY, UserData)) << 4;
return 0;
case 6:
// Read Z acceleration
ShiftVal = AccelerationToMotionPak(Platform::Addon_MotionQuery(Platform::MotionAccelerationZ, UserData)) << 4;
return 0;
case 8:
// Read Z rotation
// CHECKME: This is a guess, compare with real hardware
ShiftVal = RotationToMotionPak(-Platform::Addon_MotionQuery(Platform::MotionRotationZ, UserData)) << 4;
return 0;
case 10:
// Identify cart
ShiftVal = 0xF00F;
return 0;
case 12:
case 14:
case 16:
case 18:
// Read/enable analog inputs
//
// These are not connected by defualt and require do-it-yourself cart
// modification, so there is no reason to emulate them.
ShiftVal = 0;
break;
}
// Read high byte from the emulated shift register
u8 val = ShiftVal >> 8;
ShiftVal <<= 8;
return val;
}
CartMotionPakRetail::CartMotionPakRetail(void* userdata) :
CartCommon(MotionPakRetail),
UserData(userdata)
{
}
CartMotionPakRetail::~CartMotionPakRetail() = default;
void CartMotionPakRetail::Reset()
{
Value = 0;
Step = 16;
}
void CartMotionPakRetail::DoSavestate(Savestate* file)
{
CartCommon::DoSavestate(file);
file->Var8(&Value);
file->Var8(&Step);
}
u16 CartMotionPakRetail::ROMRead(u32 addr) const
{
// A9-A8 is pulled low on a real Motion Pack.
return 0xFCFF;
}
u8 CartMotionPakRetail::SRAMRead(u32 addr)
{
switch (Step)
{
case 0: // Synchronization - read 0xFF
Value = 0xFF;
break;
case 4: // X acceleration
Value = AccelerationToMotionPakRetail(Platform::Addon_MotionQuery(Platform::MotionAccelerationX, UserData));
break;
case 8: // Y acceleration
Value = AccelerationToMotionPakRetail(Platform::Addon_MotionQuery(Platform::MotionAccelerationY, UserData));
break;
case 12: // Z acceleration
Value = AccelerationToMotionPakRetail(Platform::Addon_MotionQuery(Platform::MotionAccelerationZ, UserData));
break;
case 16: // Synchronization - read 0b00
Step = 0;
return 0;
}
int shift = 6 - ((Step & 3) * 2);
Step++;
return (Value >> shift) & 0x03;
}
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -70,10 +70,13 @@ GPU::GPU(melonDS::NDS& nds, std::unique_ptr<Renderer3D>&& renderer3d, std::uniqu
GPU3D(nds, renderer3d ? std::move(renderer3d) : std::make_unique<SoftRenderer>()),
GPU2D_Renderer(renderer2d ? std::move(renderer2d) : std::make_unique<GPU2D::SoftRenderer>(*this))
{
NDS.RegisterEventFunc(Event_LCD, LCD_StartHBlank, MemberEventFunc(GPU, StartHBlank));
NDS.RegisterEventFunc(Event_LCD, LCD_StartScanline, MemberEventFunc(GPU, StartScanline));
NDS.RegisterEventFunc(Event_LCD, LCD_FinishFrame, MemberEventFunc(GPU, FinishFrame));
NDS.RegisterEventFunc(Event_DisplayFIFO, 0, MemberEventFunc(GPU, DisplayFIFO));
NDS.RegisterEventFuncs(Event_LCD, this,
{
MakeEventThunk(GPU, StartHBlank),
MakeEventThunk(GPU, StartScanline),
MakeEventThunk(GPU, FinishFrame)
});
NDS.RegisterEventFuncs(Event_DisplayFIFO, this, {MakeEventThunk(GPU, DisplayFIFO)});
InitFramebuffers();
}
@ -82,10 +85,8 @@ GPU::~GPU() noexcept
{
// All unique_ptr fields are automatically cleaned up
NDS.UnregisterEventFunc(Event_LCD, LCD_StartHBlank);
NDS.UnregisterEventFunc(Event_LCD, LCD_StartScanline);
NDS.UnregisterEventFunc(Event_LCD, LCD_FinishFrame);
NDS.UnregisterEventFunc(Event_DisplayFIFO, 0);
NDS.UnregisterEventFuncs(Event_LCD);
NDS.UnregisterEventFuncs(Event_DisplayFIFO);
}
void GPU::ResetVRAMCache() noexcept

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2024 melonDS team
Copyright 2016-2025 melonDS team
This file is part of melonDS.
@ -914,6 +914,9 @@ void SoftRenderer::DrawBG_3D()
template<bool mosaic, SoftRenderer::DrawPixel drawPixel>
void SoftRenderer::DrawBG_Text(u32 line, u32 bgnum)
{
// workaround for backgrounds missing on aarch64 with lto build
asm volatile ("" : : : "memory");
u16 bgcnt = CurUnit->BGCnt[bgnum];
u32 tilesetaddr, tilemapaddr;

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