Initial commit

This commit is contained in:
Gericom
2025-11-22 11:08:28 +01:00
commit 9cf3ffbfcf
358 changed files with 58350 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.bin binary

59
.github/workflows/nightly.yml vendored Normal file
View File

@@ -0,0 +1,59 @@
name: Build Pico Loader
on:
push:
branches: ["develop"]
paths-ignore:
- 'README.md'
pull_request:
branches: ["develop"]
paths-ignore:
- 'README.md'
workflow_dispatch:
jobs:
pico_loader:
strategy:
matrix:
platform: [
"ACE3DS",
"AK2",
"AKRPG",
"DSPICO",
"DSTT",
"G003",
"M3DS",
"R4",
"R4iDSN",
"SUPERCARD"
]
runs-on: ubuntu-latest
container: skylyrac/blocksds:slim-v1.13.1
name: Build Pico Loader
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
DOTNET_NOLOGO: true
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
submodules: true
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.x
- name: Run build script
run: |
make PICO_PLATFORM=${{ matrix.platform }}
mv picoLoader7.bin data/picoLoader7.bin
mv picoLoader9_${{ matrix.platform }}.bin data/picoLoader9.bin
- name: Publish build to GH Actions
uses: actions/upload-artifact@v4
with:
path: |
data/aplist.bin
data/savelist.bin
data/picoLoader7.bin
data/picoLoader9.bin
name: Pico Loader for ${{ matrix.platform }}

52
.gitignore vendored Normal file
View File

@@ -0,0 +1,52 @@
# Ignore build directories #
############################
Debug/
Release/
build/
out/
# Compiled source #
###################
*.com
*.class
*.dll
*.d
*.map
*.o
*.so
# Packages #
############
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
# Logs and databases #
######################
*.log
*.sql
*.sqlite
# OS generated files #
######################
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
*.nds
*.elf
*.a
picoLoader*.bin
.vscode/
data/aplist.bin
data/savelist.bin

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "libs/libtwl"]
path = libs/libtwl
url = https://github.com/Gericom/libtwl.git

17
LICENSE.txt Normal file
View File

@@ -0,0 +1,17 @@
Copyright (c) 2025 LNH team
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

28
Makefile Normal file
View File

@@ -0,0 +1,28 @@
.PHONY: loader9 loader7 clean
all: checklibtwl loader9 loader7 apList saveList
PICO_PLATFORM ?= DSPICO
checklibtwl:
$(MAKE) -C libs/libtwl
loader9: checklibtwl
$(MAKE) -f Makefile.arm9 PLATFORM=$(PICO_PLATFORM)
loader7: checklibtwl
$(MAKE) -f Makefile.arm7
picoLoaderConverter:
dotnet build tools/PicoLoaderConverter/PicoLoaderConverter.sln
apList: picoLoaderConverter data/aplist.csv
dotnet tools/PicoLoaderConverter/PicoLoaderConverter/bin/Debug/net9.0/PicoLoaderConverter.dll aplist -i data/aplist.csv -o data/aplist.bin
saveList: picoLoaderConverter data/savelist.csv
dotnet tools/PicoLoaderConverter/PicoLoaderConverter/bin/Debug/net9.0/PicoLoaderConverter.dll savelist -i data/savelist.csv -o data/savelist.bin
clean:
$(MAKE) -f Makefile.arm7 clean
$(MAKE) -f Makefile.arm9 clean
rm -rf build

192
Makefile.arm7 Normal file
View File

@@ -0,0 +1,192 @@
# SPDX-License-Identifier: CC0-1.0
#
# SPDX-FileContributor: Antonio Niño Díaz, 2023
export BLOCKSDS ?= /opt/blocksds/core
export BLOCKSDSEXT ?= /opt/blocksds/external
export LIBTWL ?= $(shell pwd)/libs/libtwl
export WONDERFUL_TOOLCHAIN ?= /opt/wonderful
ARM_NONE_EABI_PATH ?= $(WONDERFUL_TOOLCHAIN)/toolchain/gcc-arm-none-eabi/bin/
# Source code paths
# -----------------
SOURCEDIRS := arm7/source common
INCLUDEDIRS := arm7/source common
BINDIRS :=
# Defines passed to all files
# ---------------------------
DEFINES := -DLIBTWL_ARM7 -DARM7
# Libraries
# ---------
LIBS := -ltwl7
LIBDIRS := $(BLOCKSDS)/libs/libnds \
$(LIBTWL)/libtwl7 $(LIBTWL)/common $(LIBTWL)
# Build artifacts
# -----------------
NAME := picoLoader7
BUILDDIR := build/$(NAME)
BIN := $(NAME).bin
ELF := build/$(NAME).elf
DUMP := build/$(NAME).dump
MAP := build/$(NAME).map
# Tools
# -----
PREFIX := $(ARM_NONE_EABI_PATH)arm-none-eabi-
CC := $(PREFIX)gcc
CXX := $(PREFIX)g++
LD := $(PREFIX)gcc
OBJCOPY := $(PREFIX)objcopy
OBJDUMP := $(PREFIX)objdump
MKDIR := mkdir
RM := rm -rf
# Verbose flag
# ------------
ifeq ($(VERBOSE),1)
V :=
else
V := @
endif
# Source files
# ------------
ifneq ($(BINDIRS),)
SOURCES_BIN := $(shell find -L $(BINDIRS) -name "*.bin")
INCLUDEDIRS += $(addprefix $(BUILDDIR)/,$(BINDIRS))
endif
SOURCES_S := $(shell find -L $(SOURCEDIRS) -name "*.s")
SOURCES_C := $(shell find -L $(SOURCEDIRS) -name "*.c")
SOURCES_CPP := $(shell find -L $(SOURCEDIRS) -name "*.cpp")
# Compiler and linker flags
# -------------------------
ARCH := -mthumb -mthumb-interwork -mcpu=arm7tdmi
WARNFLAGS := -Wall
ifeq ($(SOURCES_CPP),)
LIBS += -lc
else
LIBS += -lstdc++ -lc
endif
INCLUDEFLAGS := $(foreach path,$(INCLUDEDIRS),-I$(path)) \
$(foreach path,$(LIBDIRS),-I$(path)/include)
LIBDIRSFLAGS := $(foreach path,$(LIBDIRS),-L$(path)/lib)
ASFLAGS += -x assembler-with-cpp $(DEFINES) $(INCLUDEFLAGS) \
$(ARCH) -ffunction-sections -fdata-sections
CFLAGS += -std=gnu17 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \
$(ARCH) -O2 -ffunction-sections -fdata-sections
CXXFLAGS += -std=gnu++23 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \
$(ARCH) -O2 -ffunction-sections -fdata-sections \
-fno-exceptions -fno-rtti \
-fno-threadsafe-statics \
-Wno-volatile -Wsuggest-override \
-Werror=suggest-override -Werror=return-type
LDFLAGS := $(ARCH) $(LIBDIRSFLAGS) $(DEFINES) \
-Wl,-Map,$(MAP),--gc-sections,--no-warn-rwx-segments,--entry=loaderMain \
-Wl,--start-group $(LIBS) -Wl,--end-group -T arm7/loader7.ld -nostartfiles
# Intermediate build files
# ------------------------
OBJS_ASSETS := $(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_BIN)))
HEADERS_ASSETS := $(patsubst %.bin,%_bin.h,$(addprefix $(BUILDDIR)/,$(SOURCES_BIN)))
OBJS_SOURCES := $(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_S))) \
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_C))) \
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_CPP)))
OBJS := $(OBJS_ASSETS) $(OBJS_SOURCES)
DEPS := $(OBJS:.o=.d)
# Targets
# -------
.PHONY: all clean dump
all: $(BIN)
$(BIN): $(ELF)
@echo " OBJCOPY.7 $@"
$(V)$(OBJCOPY) -O binary $< $@
$(ELF): $(OBJS)
@echo " LD.7 $@"
$(V)$(LD) -o $@ $(OBJS) $(LDFLAGS)
$(DUMP): $(ELF)
@echo " OBJDUMP.7 $@"
$(V)$(OBJDUMP) -h -C -S $< > $@
dump: $(DUMP)
clean:
@echo " CLEAN.7"
$(V)$(RM) picoLoader7*.bin $(ELF) $(DUMP) $(MAP) $(BUILDDIR)
# Rules
# -----
$(BUILDDIR)/%.s.o : %.s
@echo " AS.7 $<"
@$(MKDIR) -p $(@D)
$(V)$(CC) $(ASFLAGS) -MMD -MP -c -o $@ $<
$(BUILDDIR)/%.c.o : %.c
@echo " CC.7 $<"
@$(MKDIR) -p $(@D)
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $@ $<
$(BUILDDIR)/%.arm.c.o : %.arm.c
@echo " CC.7 $<"
@$(MKDIR) -p $(@D)
$(V)$(CC) $(CFLAGS) -MMD -MP -marm -mlong-calls -c -o $@ $<
$(BUILDDIR)/%.cpp.o : %.cpp
@echo " CXX.7 $<"
@$(MKDIR) -p $(@D)
$(V)$(CXX) $(CXXFLAGS) -MMD -MP -c -o $@ $<
$(BUILDDIR)/%.arm.cpp.o : %.arm.cpp
@echo " CXX.7 $<"
@$(MKDIR) -p $(@D)
$(V)$(CXX) $(CXXFLAGS) -MMD -MP -marm -mlong-calls -c -o $@ $<
$(BUILDDIR)/%.bin.o $(BUILDDIR)/%_bin.h : %.bin
@echo " BIN2C.7 $<"
@$(MKDIR) -p $(@D)
$(V)$(BLOCKSDS)/tools/bin2c/bin2c $< $(@D)
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(BUILDDIR)/$*.bin.o $(BUILDDIR)/$*_bin.c
# All assets must be built before the source code
# -----------------------------------------------
$(SOURCES_S) $(SOURCES_C) $(SOURCES_CPP): $(HEADERS_ASSETS)
# Include dependency files if they exist
# --------------------------------------
-include $(DEPS)

243
Makefile.arm9 Normal file
View File

@@ -0,0 +1,243 @@
# SPDX-License-Identifier: CC0-1.0
#
# SPDX-FileContributor: Antonio Niño Díaz, 2023
export BLOCKSDS ?= /opt/blocksds/core
export BLOCKSDSEXT ?= /opt/blocksds/external
export LIBTWL ?= $(shell pwd)/libs/libtwl
export WONDERFUL_TOOLCHAIN ?= /opt/wonderful
ARM_NONE_EABI_PATH ?= $(WONDERFUL_TOOLCHAIN)/toolchain/gcc-arm-none-eabi/bin/
# Source code paths
# -----------------
SOURCEDIRS := arm9/source common
INCLUDEDIRS := arm9/source common
GFXDIRS :=
BINDIRS := arm9/data
AUDIODIRS :=
# Defines passed to all files
# ---------------------------
PLATFORM ?= DSPICO
DEFINES := -DLIBTWL_ARM9 -DARM9 -DPICO_LOADER_TARGET_$(PLATFORM)
# Libraries
# ---------
LIBS := -ltwl9
LIBDIRS := $(BLOCKSDS)/libs/libnds \
$(LIBTWL)/libtwl9 $(LIBTWL)/common $(LIBTWL)
# Build artifacts
# ---------------
NAME := picoLoader9_$(PLATFORM)
BUILDDIR := build/$(NAME)
BIN := $(NAME).bin
ELF := build/$(NAME).elf
DUMP := build/$(NAME).dump
MAP := build/$(NAME).map
SOUNDBANKDIR := $(BUILDDIR)/maxmod
# Tools
# -----
PREFIX := $(ARM_NONE_EABI_PATH)arm-none-eabi-
CC := $(PREFIX)gcc
CXX := $(PREFIX)g++
LD := $(PREFIX)gcc
OBJCOPY := $(PREFIX)objcopy
OBJDUMP := $(PREFIX)objdump
MKDIR := mkdir
RM := rm -rf
# Verbose flag
# ------------
ifeq ($(VERBOSE),1)
V :=
else
V := @
endif
# Source files
# ------------
ifneq ($(BINDIRS),)
SOURCES_BIN := $(shell find -L $(BINDIRS) -name "*.bin")
SOURCES_NFT2 := $(shell find -L $(BINDIRS) -name "*.nft2")
INCLUDEDIRS += $(addprefix $(BUILDDIR)/,$(BINDIRS))
endif
ifneq ($(GFXDIRS),)
SOURCES_PNG := $(shell find -L $(GFXDIRS) -name "*.png")
INCLUDEDIRS += $(addprefix $(BUILDDIR)/,$(GFXDIRS))
endif
ifneq ($(AUDIODIRS),)
SOURCES_AUDIO := $(shell find -L $(AUDIODIRS) -regex '.*\.\(it\|mod\|s3m\|wav\|xm\)')
ifneq ($(SOURCES_AUDIO),)
INCLUDEDIRS += $(SOUNDBANKDIR)
endif
endif
SOURCES_S := $(shell find -L $(SOURCEDIRS) -name "*.s")
SOURCES_C := $(shell find -L $(SOURCEDIRS) -name "*.c")
SOURCES_CPP := $(shell find -L $(SOURCEDIRS) -name "*.cpp")
# Compiler and linker flags
# -------------------------
ARCH := -mthumb -mthumb-interwork -mcpu=arm946e-s+nofp
WARNFLAGS := -Wall
ifeq ($(SOURCES_CPP),)
LIBS += -lc
else
LIBS += -lstdc++ -lc
endif
INCLUDEFLAGS := $(foreach path,$(INCLUDEDIRS),-I$(path)) \
$(foreach path,$(LIBDIRS),-I$(path)/include)
LIBDIRSFLAGS := $(foreach path,$(LIBDIRS),-L$(path)/lib)
ASFLAGS += -x assembler-with-cpp $(DEFINES) $(INCLUDEFLAGS) \
$(ARCH) -ffunction-sections -fdata-sections
CFLAGS += -std=gnu17 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \
$(ARCH) -O2 -ffunction-sections -fdata-sections
CXXFLAGS += -std=gnu++23 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \
$(ARCH) -O2 -ffunction-sections -fdata-sections \
-fno-exceptions -fno-rtti \
-fno-threadsafe-statics -Wno-volatile \
-Wsuggest-override -Werror=suggest-override
LDFLAGS := $(ARCH) $(LIBDIRSFLAGS) $(DEFINES) -nostartfiles \
-Wl,-Map,$(MAP),--gc-sections,--use-blx,--no-warn-rwx-segments,--entry=loaderMain \
-Wl,--start-group $(LIBS) -Wl,--end-group -T arm9/loader9.ld
# Intermediate build files
# ------------------------
OBJS_ASSETS := $(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_BIN))) \
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_NFT2))) \
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_PNG)))
HEADERS_ASSETS := $(patsubst %.bin,%_bin.h,$(addprefix $(BUILDDIR)/,$(SOURCES_BIN))) \
$(patsubst %.nft2,%_nft2.h,$(addprefix $(BUILDDIR)/,$(SOURCES_NFT2))) \
$(patsubst %.png,%.h,$(addprefix $(BUILDDIR)/,$(SOURCES_PNG)))
ifneq ($(SOURCES_AUDIO),)
OBJS_ASSETS += $(SOUNDBANKDIR)/soundbank.c.o
HEADERS_ASSETS += $(SOUNDBANKDIR)/soundbank.h
endif
OBJS_SOURCES := $(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_S))) \
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_C))) \
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_CPP)))
OBJS := $(OBJS_ASSETS) $(OBJS_SOURCES)
DEPS := $(OBJS:.o=.d)
# Targets
# -------
.PHONY: all clean dump
all: $(BIN)
$(BIN): $(ELF)
@echo " OBJCOPY.9 $@"
$(V)$(OBJCOPY) -O binary $< $@
$(ELF): $(OBJS)
@echo " LD.9 $@"
$(V)$(LD) -o $@ $(OBJS) $(LDFLAGS)
$(DUMP): $(ELF)
@echo " OBJDUMP.9 $@"
$(V)$(OBJDUMP) -h -C -S $< > $@
dump: $(DUMP)
clean:
@echo " CLEAN.9"
$(V)$(RM) picoLoader9*.bin $(ELF) $(DUMP) $(MAP) $(BUILDDIR)
# Rules
# -----
$(BUILDDIR)/%.s.o : %.s
@echo " AS.9 $<"
@$(MKDIR) -p $(@D)
$(V)$(CC) $(ASFLAGS) -MMD -MP -c -o $@ $<
$(BUILDDIR)/%.c.o : %.c
@echo " CC.9 $<"
@$(MKDIR) -p $(@D)
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $@ $<
$(BUILDDIR)/%.arm.c.o : %.arm.c
@echo " CC.9 $<"
@$(MKDIR) -p $(@D)
$(V)$(CC) $(CFLAGS) -MMD -MP -marm -mlong-calls -c -o $@ $<
$(BUILDDIR)/%.cpp.o : %.cpp
@echo " CXX.9 $<"
@$(MKDIR) -p $(@D)
$(V)$(CXX) $(CXXFLAGS) -MMD -MP -c -o $@ $<
$(BUILDDIR)/%.arm.cpp.o : %.arm.cpp
@echo " CXX.9 $<"
@$(MKDIR) -p $(@D)
$(V)$(CXX) $(CXXFLAGS) -MMD -MP -marm -mlong-calls -c -o $@ $<
$(BUILDDIR)/%.bin.o $(BUILDDIR)/%_bin.h : %.bin
@echo " BIN2C.9 $<"
@$(MKDIR) -p $(@D)
$(V)$(BLOCKSDS)/tools/bin2c/bin2c $< $(@D)
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(BUILDDIR)/$*.bin.o $(BUILDDIR)/$*_bin.c
$(BUILDDIR)/%.nft2.o $(BUILDDIR)/%_nft2.h : %.nft2
@echo " BIN2C.9 $<"
@$(MKDIR) -p $(@D)
$(V)$(BLOCKSDS)/tools/bin2c/bin2c $< $(@D)
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(BUILDDIR)/$*.nft2.o $(BUILDDIR)/$*_nft2.c
$(BUILDDIR)/%.png.o $(BUILDDIR)/%.h : %.png %.grit
@echo " GRIT.9 $<"
@$(MKDIR) -p $(@D)
$(V)$(BLOCKSDS)/tools/grit/grit $< -ftc -W1 -o$(BUILDDIR)/$*
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(BUILDDIR)/$*.png.o $(BUILDDIR)/$*.c
$(V)touch $(BUILDDIR)/$*.png.o $(BUILDDIR)/$*.h
$(SOUNDBANKDIR)/soundbank.h: $(SOURCES_AUDIO)
@echo " MMUTIL $^"
@$(MKDIR) -p $(@D)
@$(BLOCKSDS)/tools/mmutil/mmutil $^ -d \
-o$(SOUNDBANKDIR)/soundbank.bin -h$(SOUNDBANKDIR)/soundbank.h
$(SOUNDBANKDIR)/soundbank.c.o: $(SOUNDBANKDIR)/soundbank.h
@echo " BIN2C soundbank.bin"
$(V)$(BLOCKSDS)/tools/bin2c/bin2c $(SOUNDBANKDIR)/soundbank.bin \
$(SOUNDBANKDIR)
@echo " CC.9 soundbank_bin.c"
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(SOUNDBANKDIR)/soundbank.c.o \
$(SOUNDBANKDIR)/soundbank_bin.c
# All assets must be built before the source code
# -----------------------------------------------
$(SOURCES_S) $(SOURCES_C) $(SOURCES_CPP): $(HEADERS_ASSETS)
# Include dependency files if they exist
# --------------------------------------
-include $(DEPS)

106
README.md Normal file
View File

@@ -0,0 +1,106 @@
# Pico Loader
Pico Loader is a homebrew and retail DS(i) rom loader supporting a variety of platforms (see below).
## Features
- Supports both homebrew and retail DS(i) roms
- Supports DSiWare and redirects NAND to the flashcard SD card (acting as "emunand", see below for how to setup)
- Supports DS roms with an encrypted secure area if a DS arm7 bios is present at `/_pico/biosnds7.rom`
- Supports a wide range of platforms, including popular flashcards and the DSpico
- Built-in patches for DS Protect
- Fast loading
Note that Pico Loader can currently not run retail roms from the DSi SD card. Homebrew is supported, however.
Return to loader is also currently not supported yet.
## Supported platforms
> [!CAUTION]
> Using the wrong platform could damage your flashcard!
Note that there can be some game compatibility differences between different platforms.
| PICO_PLATFORM | Description | DMA |
| ------------- | ---------------------------------------------------------------------------------------------- | --- |
| ACE3DS | Ace3DS+, Gateway 3DS (blue), r4isdhc.com.cn carts, r4isdhc.hk carts 2020+, various derivatives | ✅ |
| AK2 | Acekard 2, 2.1, 2i, r4ids.cn, various derivatives | ❌ |
| AKRPG | Acekard RPG SD card | ❌ |
| DSPICO | DSpico | ✅ |
| DSTT | DSTT, SuperCard DSONE SDHC, r4isdhc.com carts 2014+, r4i-sdhc.com carts, various derivatives | ❌ |
| G003 | M3i Zero (GMP-Z003) | ✅ |
| ISNITRO | Supports the IS-NITRO-EMULATOR through agb semihosting. | ❌ |
| M3DS | M3 DS Real, M3i Zero, iTouchDS, r4rts.com, r4isdhc.com RTS (black) | ❌ |
| MELONDS | Melon DS support for testing purposes only. | ❌ |
| R4 | Original R4DS (non-SDHC), M3 DS Simply | ❌ |
| R4iDSN | r4idsn.com | ❌ |
| SUPERCARD | SuperCard (Slot-2 flashcart) | ❌ |
The DMA column indicates whether DMA card reads are implemented for the platform . Without DMA card reads, some games can have cache related issues.<br>
Note that there are still SDK versions and variants for which Pico Loader does not yet support DMA card reads.
## Setup & Configuration
We recommend using WSL (Windows Subsystem for Linux), or MSYS2 to compile this repository.
The steps provided will assume you already have one of those environments set up.
1. Install [BlocksDS](https://blocksds.skylyrac.net/docs/setup/options/)
2. Install [.NET 9.0](https://learn.microsoft.com/en-us/dotnet/core/install/linux-ubuntu-install?tabs=dotnet9&pivots=os-linux-ubuntu-2404) for your system (note: this link points to the instructions for Ubuntu, but links for most OS'es are available on the same page)
## Compiling
1. Run `make`
- By default this compiles for the DSpico platform. To specify a different platform use `make PICO_PLATFORM=PLATFORM`, for example `make PICO_PLATFORM=R4`. See the table above for the supported platforms.
2. To use Pico Loader, create a `_pico` folder in the root of your flashcard SD card and copy the following files to it:
- `picoLoader7.bin`
- `picoLoader9.bin` (the version for your platform)
- `aplist.bin` (generated in the `data` folder of the repo)
- `savelist.bin` (generated in the `data` folder of the repo)
## Emunand
When running DSiWare, Pico Loader redirects NAND to the flashcard SD card. This requires the following files and folders, obtained from a DSi nand dump, in the root of your flashcard SD card:
- `photo` - The photo partition of nand will be redirected to this folder
- `shared1`
- `TWLCFG0.dat`
- `TWLCFG1.dat`
- `shared2`
- `launcher`
- `wrap.bin`
- `sys`
- `log`
- `product.log`
- `shop.log`
- `sysmenu.log`
- `cert.sys`
- `dev.kp`
- `HWID.sgn`
- `HWINFO_N.dat`
- `HWINFO_S.dat`
- `TWLFontTable.dat`
## How to use Pico Loader from homebrew
On the arm9:
1. Map VRAM blocks A, B, C and D to LCDC
2. Load `picoLoader9.bin` to `0x06800000` (VRAM A and B)
3. Load `picoLoader7.bin` to `0x06840000` (VRAM C and D)
4. Setup the header of picoLoader7 to specify what should be loaded. See `pload_header7_t` in [include/picoLoader7.h](include/picoLoader7.h).
- Caution: VRAM does not support byte writes!
5. Disable irqs and dma
6. Ensure the cache is flushed
7. Map VRAM C and D to arm7
8. Request the arm7 to boot into picoLoader7
- Arm7: Disable sound, irqs and dma and jump to the `entryPoint` specified in the picoLoader7 header. Note that after mapping the VRAM to arm7, it appears at `0x06000000` on the arm7 side.
9. Arm9 jump to `0x06800000`
Note that vram must be executable on the arm9.
## License
This project is licensed under the Zlib license. For details, see `LICENSE.txt`.
Additional licenses may apply to the project. For details, see the `license` directory.
## Contributors
- [@Gericom](https://github.com/Gericom)
- [@lifehackerhansol](https://github.com/lifehackerhansol)
- [@Dartz150](https://github.com/Dartz150)
- [@XLuma](https://github.com/XLuma)
- [@edo9300](https://github.com/edo9300)
- [@Tcm0](https://github.com/Tcm0)
- [@RocketRobz](https://github.com/RocketRobz)

209
arm7/loader7.ld Normal file
View File

@@ -0,0 +1,209 @@
/*--------------------------------------------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public License,
v. 2.0. If a copy of the MPL was not distributed with this file, You can
obtain one at https://mozilla.org/MPL/2.0/.
--------------------------------------------------------------------------------*/
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
MEMORY {
vram : ORIGIN = 0x06000000, LENGTH = 256K
}
__heap_end = ORIGIN(vram) + LENGTH(vram);
SECTIONS
{
.crt0 :
{
__text_start = . ;
KEEP (build/picoLoader7/arm7/source/header.cpp.o(.crt0))
KEEP (*(.crt0))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram = 0xff
.text : /* ALIGN (4): */
{
KEEP (*(SORT_NONE(.init)))
*(.text.*)
*(.stub)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
*(.gnu.linkonce.t*)
__glue_start = ABSOLUTE(.);
*(.glue_7)
*(.glue_7t)
__glue_end = ABSOLUTE(.);
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram = 0xff
.fini :
{
KEEP (*(.fini))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram =0xff
__text_end = . ;
.rodata :
{
*(.rodata)
*all.rodata*(*)
*(.roda)
*(.rodata.*)
*(.gnu.linkonce.r*)
SORT(CONSTRUCTORS)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram = 0xff
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >vram
__exidx_start = .;
.ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } >vram
__exidx_end = .;
/* Ensure the __preinit_array_start label is properly aligned. We
could instead move the label definition inside the section, but
the linker would then create the section even if it turns out to
be empty, which isn't pretty. */
. = ALIGN(32 / 8);
.init_array :
{
PROVIDE (__preinit_array_start = .);
PROVIDE (__bothinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE (__preinit_array_end = .);
PROVIDE (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE (__init_array_end = .);
PROVIDE (__bothinit_array_end = .);
} >vram
.fini_array :
{
PROVIDE (__fini_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
/* Required by pico-exitprocs.c. */
KEEP (*(.fini_array*))
PROVIDE (__fini_array_end = .);
} >vram
.ctors :
{
/* gcc uses crtbegin.o to find the start of the constructors, so
we make sure it is first. Because this is a wildcard, it
doesn't matter if the user does not actually link against
crtbegin.o; the linker won't look for a file to match a
wildcard. The wildcard also means that it doesn't matter which
directory crtbegin.o is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram = 0xff
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram = 0xff
.eh_frame :
{
KEEP (*(.eh_frame))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram = 0xff
.gcc_except_table :
{
*(.gcc_except_table)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram = 0xff
.jcr : { KEEP (*(.jcr)) } >vram = 0
__got_start = . ;
.got :
{
*(.got.plt)
*(.got)
*(.rel.got)
} >vram = 0
__got_end = . ;
.data ALIGN(4) : {
__data_start = ABSOLUTE(.);
*(.data)
*(.data.*)
*(.gnu.linkonce.d*)
CONSTRUCTORS
. = ALIGN(4);
__data_end = ABSOLUTE(.) ;
} >vram = 0xff
__data_end = . ;
.bss ALIGN(4) :
{
__bss_start = ABSOLUTE(.);
__bss_start__ = ABSOLUTE(.);
*(.dynbss)
*(.gnu.linkonce.b*)
*(.bss*)
*(COMMON)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram
__bss_end = . ;
__bss_end__ = . ;
__bss_size = __bss_end - __bss_start;
__heap_start = . ;
_end = . ;
__end__ = . ;
PROVIDE (end = _end);
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
.stack 0x80000 : { _stack = .; *(.stack) }
/* These must appear regardless of . */
}

3
arm7/source/clearFast.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
extern "C" void clearFast(void* dst, u32 length);

29
arm7/source/clearFast.s Normal file
View File

@@ -0,0 +1,29 @@
.text
.thumb
.global clearFast
.type clearFast, %function
clearFast:
push {r4-r7,lr}
mov r12, sp
mov sp, r0
add sp, r1
mov r1, #0
mov r2, #0
mov r3, #0
mov r4, #0
mov r5, #0
mov r6, #0
mov r7, #0
mov lr, r7
1:
push {r1-r7,lr}
push {r1-r7,lr}
cmp sp, r0
bne 1b
mov sp, r12
pop {r4-r7}
pop {r3}
bx r3
.end

23
arm7/source/common.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <nds/ndstypes.h>
#include "fat/ff.h"
#ifdef __cplusplus
#include "header.h"
#include "logger/ILogger.h"
extern ILogger* gLogger;
#define MAX_COMPILED_LOG_LEVEL LogLevel::All
#define LOG_FATAL(...) if (LogLevel::Fatal < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Fatal, __VA_ARGS__)
#define LOG_ERROR(...) if (LogLevel::Error < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Error, __VA_ARGS__)
#define LOG_WARNING(...) if (LogLevel::Warning < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Warning, __VA_ARGS__)
#define LOG_INFO(...) if (LogLevel::Info < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Info, __VA_ARGS__)
#define LOG_DEBUG(...) if (LogLevel::Debug < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Debug, __VA_ARGS__)
#define LOG_TRACE(...) if (LogLevel::Trace < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Trace, __VA_ARGS__)
#endif
extern FATFS gFatFs;

View File

@@ -0,0 +1,61 @@
#include <nds.h>
#include "picoAgbAdapter.h"
#include "Environment.h"
u32 Environment::_flags;
static bool detectIsNitroEmulator()
{
u32 agbMemoryAddress = *(vu32*)0x027FFF7C;
if (agbMemoryAddress < 0x08000000 || agbMemoryAddress >= 0x0A000000)
return false;
// u32 monitorRomLoadAddress = *(vu32*)0x027FFF68;
// if (monitorRomLoadAddress < 0x02000000 || monitorRomLoadAddress >= 0x02800000)
// return false;
return true;
}
static bool detectNocashPrintSuppport()
{
u32 nocashIdentifier = *(vu32*)0x04FFFA00;
return nocashIdentifier == 0x67246F6E; //no$g
}
static bool detectPicoAgbAdapter()
{
REG_EXMEMSTAT &= ~0xFF;
return PICO_AGB_IDENTIFIER == PICO_AGB_IDENTIFIER_VALUE;
}
void Environment::Initialize(bool dsiMode)
{
_flags = ENVIRONMENT_FLAGS_NONE;
if (dsiMode)
{
_flags |= ENVIRONMENT_FLAGS_DSI_MODE;
}
else
{
if (detectIsNitroEmulator())
{
_flags |= ENVIRONMENT_FLAGS_IS_NITRO_EMULATOR;
_flags |= ENVIRONMENT_FLAGS_JTAG_SEMIHOSTING;
REG_EXMEMSTAT &= ~0xFF;
u32 agbMemoryAddress = *(vu32*)0x027FFF7C;
if (*(vu32*)(agbMemoryAddress + 0x100) == 0x44495349) //ISID
_flags |= ENVIRONMENT_FLAGS_AGB_SEMIHOSTING;
}
else
{
if (detectPicoAgbAdapter())
_flags |= ENVIRONMENT_FLAGS_PICO_AGB_ADAPTER;
}
}
if (!(_flags & ENVIRONMENT_FLAGS_IS_NITRO_EMULATOR))
{
if (detectNocashPrintSuppport())
_flags |= ENVIRONMENT_FLAGS_NOCASH_PRINT;
}
}

View File

@@ -0,0 +1,32 @@
#pragma once
class Environment
{
enum EnvironmentFlags : u32
{
ENVIRONMENT_FLAGS_NONE = 0,
ENVIRONMENT_FLAGS_DSI_MODE = (1 << 0),
ENVIRONMENT_FLAGS_NOCASH_PRINT = (1 << 1),
ENVIRONMENT_FLAGS_IS_NITRO_EMULATOR = (1 << 2),
ENVIRONMENT_FLAGS_JTAG_SEMIHOSTING = (1 << 3),
ENVIRONMENT_FLAGS_AGB_SEMIHOSTING = (1 << 4),
ENVIRONMENT_FLAGS_DLDI = (1 << 5),
ENVIRONMENT_FLAGS_ARGV = (1 << 6),
ENVIRONMENT_FLAGS_PICO_AGB_ADAPTER = (1 << 7)
};
static u32 _flags;
public:
static void Initialize(bool dsiMode);
static inline bool IsDsiMode() { return _flags & ENVIRONMENT_FLAGS_DSI_MODE; }
static inline bool SupportsNocashPrint() { return _flags & ENVIRONMENT_FLAGS_NOCASH_PRINT; }
static inline bool IsIsNitroEmulator() { return _flags & ENVIRONMENT_FLAGS_IS_NITRO_EMULATOR; }
static inline bool SupportsJtagSemihosting() { return _flags & ENVIRONMENT_FLAGS_JTAG_SEMIHOSTING; }
static inline bool SupportsAgbSemihosting() { return _flags & ENVIRONMENT_FLAGS_AGB_SEMIHOSTING; }
static inline bool SupportsDldi() { return _flags & ENVIRONMENT_FLAGS_DLDI; }
static inline bool SupportsArgv() { return _flags & ENVIRONMENT_FLAGS_ARGV; }
static inline bool HasPicoAgbAdapter() { return _flags & ENVIRONMENT_FLAGS_PICO_AGB_ADAPTER; }
};

26
arm7/source/crt0.s Normal file
View File

@@ -0,0 +1,26 @@
.section ".crt0", "ax"
.arm
.global _start
.type _start, %function
_start:
// disable irqs
ldr r0,= 0x04000208
strb r0, [r0]
// clear bss
ldr r0,= __bss_start
ldr r1,= __bss_end
cmp r0, r1
beq bss_done
mov r2, #0
1:
str r2, [r0], #4
cmp r0, r1
bne 1b
bss_done:
ldr sp,= 0x0380FD80
b loaderMain
.pool
.end

View File

@@ -0,0 +1,17 @@
#include "common.h"
#include <stdarg.h>
#include <libtwl/i2c/i2cMcu.h>
#include "ipc.h"
#include "ipcCommands.h"
#include "core/mini-printf.h"
#include "ErrorDisplay.h"
void ErrorDisplay::PrintError(const char* errorFormat, ...)
{
va_list va;
va_start(va, errorFormat);
mini_vsnprintf((char*)0x02000000, 1024, errorFormat, va);
sendToArm9(IPC_COMMAND_ARM9_DISPLAY_ERROR);
va_end(va);
while (true);
}

View File

@@ -0,0 +1,11 @@
#pragma once
/// @brief Class for displaying critical errors on screen.
class ErrorDisplay
{
public:
/// @brief Formats and sends a critical error message to the arm9 to display it on screen.
/// @note This function does not return.
/// @param errorFormat The error message to format.
void PrintError(const char* errorFormat, ...);
};

205
arm7/source/fat/diskio.cpp Normal file
View File

@@ -0,0 +1,205 @@
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2016 */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various exsisting */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*/
#include <nds/ndstypes.h>
#include <nds/disc_io.h>
#include <string.h>
#include <libtwl/rtos/rtosIrq.h>
#include "core/Environment.h"
// #include <libtwl/rtos/rtosIrq.h>
// #include <libtwl/rtos/rtosEvent.h>
#include "ff.h" /* Obtains integer types */
#include "diskio.h" /* Declarations of disk functions */
#include "dldi.h"
#include "../mmc/sdmmc.h"
// #include "core/semihosting.h"
/* Definitions of physical drive number for each drive */
#define DEV_FAT 0 //dldi
#define DEV_SD 1 //dsi sd
#define DEV_PC 2 //image on pc via semihosting
#define DEV_PC2 3 //image on pc via agb semihosting
// static int sPcFileHandle;
// static rtos_event_t sSemihostingCommandDoneEvent;
//extern "C" int sdmmc_sd_startup();
// extern FN_MEDIUM_STARTUP _DLDI_startup_ptr;
// extern FN_MEDIUM_READSECTORS _DLDI_readSectors_ptr;
// extern FN_MEDIUM_WRITESECTORS _DLDI_writeSectors_ptr;
/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/
extern "C" DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
return 0;
}
// static void gbaIrq(u32 mask)
// {
// rtos_signalEvent(&sSemihostingCommandDoneEvent);
// }
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive */
/*-----------------------------------------------------------------------*/
extern "C" DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
if (pdrv == DEV_FAT)
{
return 0;
}
else if (Environment::IsDsiMode() && pdrv == DEV_SD)
{
pico_SDMMC_init(SDMMC_DEV_CARD);
return 0;
}
else if (pdrv == DEV_PC)
{
// sPcFileHandle = sh_openFile("d:\\Emulators\\No$Debugger 3.0\\DSI-1.SD", SH_OPEN_MODE_R_PLUS_B);
return 0;
}
else if (pdrv == DEV_PC2)
{
// rtos_createEvent(&sSemihostingCommandDoneEvent);
// rtos_disableIrqMask(RTOS_IRQ_GBA_IREQ);
// rtos_ackIrqMask(RTOS_IRQ_GBA_IREQ);
// rtos_setIrqFunc(RTOS_IRQ_GBA_IREQ, gbaIrq);
// rtos_enableIrqMask(RTOS_IRQ_GBA_IREQ);
return 0;
}
return STA_NOINIT;
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
extern "C" DRESULT disk_read (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
DWORD sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
)
{
if (pdrv == DEV_FAT)
{
dldi_readSectors(buff, sector, count);
return RES_OK;
}
else if (Environment::IsDsiMode() && pdrv == DEV_SD)
{
pico_SDMMC_readSectors(SDMMC_DEV_CARD, sector, buff, count);
return RES_OK;
}
else if (pdrv == DEV_PC)
{
// sh_seekFile(sPcFileHandle, sector * 512);
// sh_readFile(sPcFileHandle, buff, count * 512);
// return RES_OK;
}
else if (pdrv == DEV_PC2)
{
// rtos_clearEvent(&sSemihostingCommandDoneEvent);
u32 agbMem = *(u32*)0x027FFF7C;
*(vu16*)(agbMem + 0x10002) = 1;
*(vu32*)(agbMem + 0x10004) = sector;
*(vu32*)(agbMem + 0x10008) = count;
rtos_disableIrqMask(RTOS_IRQ_GBA_IREQ);
rtos_ackIrqMask(RTOS_IRQ_GBA_IREQ);
*(vu16*)(agbMem + 0x10000) = 0x55;
while (!(rtos_getIrqFlags() & RTOS_IRQ_GBA_IREQ));
// rtos_waitEvent(&sSemihostingCommandDoneEvent, false, true);
if (*(vu16*)(agbMem + 0x10000) != 0xAA)
return RES_ERROR;
memcpy(buff, (const void*)(agbMem + 0x10020), count * 512);
return RES_OK;
}
return RES_PARERR;
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
extern "C" DRESULT disk_write (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
DWORD sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
)
{
if (pdrv == DEV_FAT)
{
dldi_writeSectors(buff, sector, count);
return RES_OK;
}
else if (Environment::IsDsiMode() && pdrv == DEV_SD)
{
pico_SDMMC_writeSectors(SDMMC_DEV_CARD, sector, buff, count);
return RES_OK;
}
else if (pdrv == DEV_PC)
{
// sh_seekFile(sPcFileHandle, sector * 512);
// sh_writeFile(sPcFileHandle, buff, count * 512);
// return RES_OK;
}
else if (pdrv == DEV_PC2)
{
// rtos_clearEvent(&sSemihostingCommandDoneEvent);
u32 agbMem = *(u32*)0x027FFF7C;
memcpy((void*)(agbMem + 0x10020), buff, count * 512);
*(vu16*)(agbMem + 0x10002) = 2;
*(vu32*)(agbMem + 0x10004) = sector;
*(vu32*)(agbMem + 0x10008) = count;
rtos_disableIrqMask(RTOS_IRQ_GBA_IREQ);
rtos_ackIrqMask(RTOS_IRQ_GBA_IREQ);
*(vu16*)(agbMem + 0x10000) = 0x55;
while (!(rtos_getIrqFlags() & RTOS_IRQ_GBA_IREQ));
// rtos_waitEvent(&sSemihostingCommandDoneEvent, false, true);
if (*(vu16*)(agbMem + 0x10000) != 0xAA)
return RES_ERROR;
return RES_OK;
}
return RES_PARERR;
}
#endif
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/
extern "C" DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
return RES_OK;
}

77
arm7/source/fat/diskio.h Normal file
View File

@@ -0,0 +1,77 @@
/*-----------------------------------------------------------------------/
/ Low level disk interface modlue include file (C)ChaN, 2014 /
/-----------------------------------------------------------------------*/
#ifndef _DISKIO_DEFINED
#define _DISKIO_DEFINED
#ifdef __cplusplus
extern "C" {
#endif
/* Status of Disk Functions */
typedef BYTE DSTATUS;
/* Results of Disk Functions */
typedef enum {
RES_OK = 0, /* 0: Successful */
RES_ERROR, /* 1: R/W Error */
RES_WRPRT, /* 2: Write Protected */
RES_NOTRDY, /* 3: Not Ready */
RES_PARERR /* 4: Invalid Parameter */
} DRESULT;
/*---------------------------------------*/
/* Prototypes for disk control functions */
DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
/* Disk Status Bits (DSTATUS) */
#define STA_NOINIT 0x01 /* Drive not initialized */
#define STA_NODISK 0x02 /* No medium in the drive */
#define STA_PROTECT 0x04 /* Write protected */
/* Command code for disk_ioctrl fucntion */
/* Generic command (Used by FatFs) */
#define CTRL_SYNC 0 /* Complete pending write process (needed at FF_FS_READONLY == 0) */
#define GET_SECTOR_COUNT 1 /* Get media size (needed at FF_USE_MKFS == 1) */
#define GET_SECTOR_SIZE 2 /* Get sector size (needed at FF_MAX_SS != FF_MIN_SS) */
#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at FF_USE_MKFS == 1) */
#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at FF_USE_TRIM == 1) */
/* Generic command (Not used by FatFs) */
#define CTRL_POWER 5 /* Get/Set power status */
#define CTRL_LOCK 6 /* Lock/Unlock media removal */
#define CTRL_EJECT 7 /* Eject media */
#define CTRL_FORMAT 8 /* Create physical format on the media */
/* MMC/SDC specific ioctl command */
#define MMC_GET_TYPE 10 /* Get card type */
#define MMC_GET_CSD 11 /* Get CSD */
#define MMC_GET_CID 12 /* Get CID */
#define MMC_GET_OCR 13 /* Get OCR */
#define MMC_GET_SDSTAT 14 /* Get SD status */
#define ISDIO_READ 55 /* Read data form SD iSDIO register */
#define ISDIO_WRITE 56 /* Write data to SD iSDIO register */
#define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */
/* ATA/CF specific ioctl command */
#define ATA_GET_REV 20 /* Get F/W revision */
#define ATA_GET_MODEL 21 /* Get model name */
#define ATA_GET_SN 22 /* Get serial number */
#ifdef __cplusplus
}
#endif
#endif

97
arm7/source/fat/dldi.cpp Normal file
View File

@@ -0,0 +1,97 @@
#include "common.h"
#include <string.h>
#include "ipc.h"
#include "ipcCommands.h"
#include "loader/DldiDriver.h"
static u8 sDldiBuffer[16 * 1024] alignas(32);
static DldiDriver sDldiDriver = DldiDriver((dldi_header_t*)sDldiBuffer);
[[gnu::target("thumb")]]
static bool readSectorsWithPatchCode(u32 sector, u32 count, void* buffer)
{
typedef void (*patch_code_read_sd_sectors_t)(u32 srcSector, void* dst, u32 sectorCount);
(*(patch_code_read_sd_sectors_t*)0x037F8000)(sector, buffer, count);
return true;
}
[[gnu::target("thumb")]]
static bool writeSectorsWithPatchCode(u32 sector, u32 count, const void* buffer)
{
typedef void (*patch_code_write_sd_sectors_t)(u32 dstSector, const void* src, u32 sectorCount);
(*(patch_code_write_sd_sectors_t*)0x037F8004)(sector, buffer, count);
return true;
}
bool dldi_init()
{
auto driver = (const dldi_header_t*)gLoaderHeader.dldiDriver;
if (!driver || driver->dldiMagic != DLDI_MAGIC || driver->driverMagic == DLDI_DRIVER_MAGIC_NONE)
{
LOG_DEBUG("No dldi driver found\n");
// Need to initialize before getting the patch code
sendToArm9(IPC_COMMAND_ARM9_INITIALIZE_SD_CARD);
if (!receiveFromArm9())
{
LOG_ERROR("Sd card initialization failed\n");
return false;
}
// Try to get the patch code
sendToArm9(IPC_COMMAND_ARM9_GET_SD_FUNCTIONS);
if (!receiveFromArm9())
{
LOG_ERROR("Getting patch code failed\n");
return false;
}
LOG_DEBUG("Using patch code sd read/write\n");
((dldi_header_t*)sDldiBuffer)->readSectorsFuncAddress = (u32)readSectorsWithPatchCode;
((dldi_header_t*)sDldiBuffer)->writeSectorsFuncAddress = (u32)writeSectorsWithPatchCode;
}
else
{
u32 driverSize = 1 << driver->driverSize;
if (driverSize > sizeof(sDldiBuffer))
{
LOG_ERROR("Not enough space for dldi driver of size %d\n", driverSize);
return false;
}
memcpy(sDldiBuffer, driver, driverSize);
sDldiDriver.Relocate();
sDldiDriver.PrepareForUse();
if (!sDldiDriver.Startup())
{
LOG_ERROR("DLDI startup failed\n");
return false;
}
sendToArm9(IPC_COMMAND_ARM9_INITIALIZE_SD_CARD);
if (!receiveFromArm9())
{
LOG_ERROR("Sd card initialization failed\n");
return false;
}
}
return true;
}
extern "C" bool dldi_readSectors(void* buffer, u32 sector, u32 count)
{
return sDldiDriver.ReadSectors(sector, count, buffer);
}
extern "C" bool dldi_writeSectors(const void* buffer, u32 sector, u32 count)
{
return sDldiDriver.WriteSectors(sector, count, buffer);
}
bool dldi_patchTo(dldi_header_t* stub)
{
return sDldiDriver.PatchTo(stub);
}

16
arm7/source/fat/dldi.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "loader/dldiHeader.h"
bool dldi_init();
bool dldi_patchTo(dldi_header_t* stub);
#ifdef __cplusplus
extern "C" {
#endif
bool dldi_readSectors(void* buffer, u32 sector, u32 count);
bool dldi_writeSectors(const void* buffer, u32 sector, u32 count);
#ifdef __cplusplus
}
#endif

6593
arm7/source/fat/ff.c Normal file

File diff suppressed because it is too large Load Diff

412
arm7/source/fat/ff.h Normal file
View File

@@ -0,0 +1,412 @@
/*----------------------------------------------------------------------------/
/ FatFs - Generic FAT Filesystem module R0.13c /
/-----------------------------------------------------------------------------/
/
/ Copyright (C) 2018, ChaN, all right reserved.
/
/ FatFs module is an open source software. Redistribution and use of FatFs in
/ source and binary forms, with or without modification, are permitted provided
/ that the following condition is met:
/ 1. Redistributions of source code must retain the above copyright notice,
/ this condition and the following disclaimer.
/
/ This software is provided by the copyright holder and contributors "AS IS"
/ and any warranties related to this software are DISCLAIMED.
/ The copyright owner or contributors be NOT LIABLE for any damages caused
/ by use of this software.
/
/----------------------------------------------------------------------------*/
#ifndef FF_DEFINED
#define FF_DEFINED 86604 /* Revision ID */
#ifdef __cplusplus
extern "C" {
#endif
#include "ffconf.h" /* FatFs configuration options */
#if FF_DEFINED != FFCONF_DEF
#error Wrong configuration file (ffconf.h).
#endif
/* Integer types used for FatFs API */
#if defined(_WIN32) /* Main development platform */
#define FF_INTDEF 2
#include <windows.h>
typedef unsigned __int64 QWORD;
#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */
#define FF_INTDEF 2
#include <stdint.h>
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef uint16_t WORD; /* 16-bit unsigned integer */
typedef uint16_t WCHAR; /* 16-bit unsigned integer */
typedef uint32_t DWORD; /* 32-bit unsigned integer */
typedef uint64_t QWORD; /* 64-bit unsigned integer */
#else /* Earlier than C99 */
#define FF_INTDEF 1
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef unsigned short WORD; /* 16-bit unsigned integer */
typedef unsigned short WCHAR; /* 16-bit unsigned integer */
typedef unsigned long DWORD; /* 32-bit unsigned integer */
#endif
#include "math.h"
/* Definitions of volume management */
#if FF_MULTI_PARTITION /* Multiple partition configuration */
typedef struct {
BYTE pd; /* Physical drive number */
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
} PARTITION;
extern PARTITION VolToPart[]; /* Volume - Partition resolution table */
#endif
#if FF_STR_VOLUME_ID
#ifndef FF_VOLUME_STRS
extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */
#endif
#endif
/* Type of path name strings on FatFs API */
#ifndef _INC_TCHAR
#define _INC_TCHAR
#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */
typedef WCHAR TCHAR;
#define _T(x) L ## x
#define _TEXT(x) L ## x
#elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */
typedef char TCHAR;
#define _T(x) u8 ## x
#define _TEXT(x) u8 ## x
#elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */
typedef DWORD TCHAR;
#define _T(x) U ## x
#define _TEXT(x) U ## x
#elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3)
#error Wrong FF_LFN_UNICODE setting
#else /* ANSI/OEM code in SBCS/DBCS */
typedef char TCHAR;
#define _T(x) x
#define _TEXT(x) x
#endif
#endif
/* Type of file size variables */
#if FF_FS_EXFAT
#if FF_INTDEF != 2
#error exFAT feature wants C99 or later
#endif
typedef QWORD FSIZE_t;
#else
typedef DWORD FSIZE_t;
#endif
/* Filesystem object structure (FATFS) */
typedef struct {
BYTE fs_type; /* Filesystem type (0:not mounted) */
BYTE pdrv; /* Associated physical drive */
BYTE n_fats; /* Number of FATs (1 or 2) */
BYTE wflag; /* win[] flag (b0:dirty) */
BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */
WORD id; /* Volume mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
WORD csize; /* Cluster size [sectors] */
#if FF_MAX_SS != FF_MIN_SS
WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */
#endif
#if FF_USE_LFN
WCHAR* lfnbuf; /* LFN working buffer */
#endif
#if FF_FS_EXFAT
BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */
#endif
#if FF_FS_REENTRANT
FF_SYNC_t sobj; /* Identifier of sync object */
#endif
#if !FF_FS_READONLY
DWORD last_clst; /* Last allocated cluster */
DWORD free_clst; /* Number of free clusters */
#endif
#if FF_FS_RPATH
DWORD cdir; /* Current directory start cluster (0:root) */
#if FF_FS_EXFAT
DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */
DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */
DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */
#endif
#endif
DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */
DWORD fsize; /* Size of an FAT [sectors] */
DWORD volbase; /* Volume base sector */
DWORD fatbase; /* FAT base sector */
DWORD dirbase; /* Root directory base sector/cluster */
DWORD database; /* Data base sector */
#if FF_FS_EXFAT
DWORD bitbase; /* Allocation bitmap base sector */
#endif
DWORD winsect; /* Current sector appearing in the win[] */
BYTE win[FF_MAX_SS] __attribute__((aligned(32))); /* Disk access window for Directory, FAT (and file data at tiny cfg) */
} FATFS __attribute__((aligned(32)));
/* Object ID and allocation information (FFOBJID) */
typedef struct {
FATFS* fs; /* Pointer to the hosting volume of this object */
WORD id; /* Hosting volume mount ID */
BYTE attr; /* Object attribute */
BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */
FSIZE_t objsize; /* Object size (valid when sclust != 0) */
#if FF_FS_EXFAT
DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */
DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */
DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */
DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */
#endif
#if FF_FS_LOCK
UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */
#endif
} FFOBJID;
/* File object structure (FIL) */
typedef struct {
FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */
BYTE flag; /* File status flags */
BYTE err; /* Abort flag (error code) */
FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */
DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */
DWORD sect; /* Sector number appearing in buf[] (0:invalid) */
#if !FF_FS_READONLY
DWORD dir_sect; /* Sector number containing the directory entry (not used at exFAT) */
BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */
#endif
#if FF_USE_FASTSEEK
DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */
#endif
#if !FF_FS_TINY
BYTE buf[FF_MAX_SS] __attribute__((aligned(32))); /* File private data read/write window */
#endif
} FIL __attribute__((aligned(32)));
/* Directory object structure (DIR) */
typedef struct {
FFOBJID obj; /* Object identifier */
DWORD dptr; /* Current read/write offset */
DWORD clust; /* Current cluster */
DWORD sect; /* Current sector (0:Read operation has terminated) */
BYTE* dir; /* Pointer to the directory item in the win[] */
BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */
#if FF_USE_LFN
DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
#endif
#if FF_USE_FIND
const TCHAR* pat; /* Pointer to the name matching pattern */
#endif
} DIR;
/* File information structure (FILINFO) */
typedef struct {
FSIZE_t fsize; /* File size */
WORD fdate; /* Modified date */
WORD ftime; /* Modified time */
BYTE fattrib; /* File attribute */
DWORD fdirsect;
DWORD fdiroffs;
DWORD fclust;
#if FF_USE_LFN
TCHAR altname[FF_SFN_BUF + 1];/* Altenative file name */
TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */
#else
TCHAR fname[12 + 1]; /* File name */
#endif
} FILINFO;
/* File function return code (FRESULT) */
typedef enum {
FR_OK = 0, /* (0) Succeeded */
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
FR_INT_ERR, /* (2) Assertion failed */
FR_NOT_READY, /* (3) The physical drive cannot work */
FR_NO_FILE, /* (4) Could not find the file */
FR_NO_PATH, /* (5) Could not find the path */
FR_INVALID_NAME, /* (6) The path name format is invalid */
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
FR_EXIST, /* (8) Access denied due to prohibited access */
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
FR_NOT_ENABLED, /* (12) The volume has no work area */
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
} FRESULT;
/*--------------------------------------------------------------*/
/* FatFs module application interface */
DWORD f_clst2sect(FATFS* fs, DWORD clst);
DWORD f_getFat(FIL* fp, DWORD clst);
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
FRESULT f_close (FIL* fp); /* Close an open file object */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */
FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */
FRESULT f_truncate (FIL* fp); /* Truncate the file */
FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */
FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
FRESULT f_closedir (DIR* dp); /* Close an open directory */
FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */
FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */
FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */
FRESULT f_chdir (const TCHAR* path); /* Change current directory */
FRESULT f_chdrive (const TCHAR* path); /* Change current drive */
FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
FRESULT f_setlabel (const TCHAR* label); /* Set volume label */
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
FRESULT f_expand (FIL* fp, FSIZE_t szf, BYTE opt); /* Allocate a contiguous block to the file */
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
FRESULT f_mkfs (const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len); /* Create a FAT volume */
FRESULT f_fdisk (BYTE pdrv, const DWORD* szt, void* work); /* Divide a physical drive into some partitions */
FRESULT f_setcp (WORD cp); /* Set current code page */
int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */
int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))
#define f_error(fp) ((fp)->err)
#define f_tell(fp) ((fp)->fptr)
#define f_size(fp) ((fp)->obj.objsize)
#define f_rewind(fp) f_lseek((fp), 0)
#define f_rewinddir(dp) f_readdir((dp), 0)
#define f_rmdir(path) f_unlink(path)
#define f_unmount(path) f_mount(0, path, 0)
#ifndef EOF
#define EOF (-1)
#endif
/*--------------------------------------------------------------*/
/* Additional user defined functions */
/* RTC function */
#if !FF_FS_READONLY && !FF_FS_NORTC
DWORD get_fattime (void);
#endif
/* LFN support functions */
#if FF_USE_LFN >= 1 /* Code conversion (defined in unicode.c) */
WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */
WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */
DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */
#endif
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
void* ff_memalloc (UINT msize); /* Allocate memory block */
void ff_memfree (void* mblock); /* Free memory block */
#endif
/* Sync functions */
#if FF_FS_REENTRANT
int ff_cre_syncobj (BYTE vol, FF_SYNC_t* sobj); /* Create a sync object */
int ff_req_grant (FF_SYNC_t sobj); /* Lock sync object */
void ff_rel_grant (FF_SYNC_t sobj); /* Unlock sync object */
int ff_del_syncobj (FF_SYNC_t sobj); /* Delete a sync object */
#endif
/*--------------------------------------------------------------*/
/* Flags and offset address */
/* File access mode and open method flags (3rd argument of f_open) */
#define FA_READ 0x01
#define FA_WRITE 0x02
#define FA_OPEN_EXISTING 0x00
#define FA_CREATE_NEW 0x04
#define FA_CREATE_ALWAYS 0x08
#define FA_OPEN_ALWAYS 0x10
#define FA_OPEN_APPEND 0x30
/* Fast seek controls (2nd argument of f_lseek) */
#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
/* Format options (2nd argument of f_mkfs) */
#define FM_FAT 0x01
#define FM_FAT32 0x02
#define FM_EXFAT 0x04
#define FM_ANY 0x07
#define FM_SFD 0x08
/* Filesystem type (FATFS.fs_type) */
#define FS_FAT12 1
#define FS_FAT16 2
#define FS_FAT32 3
#define FS_EXFAT 4
/* File attribute bits for directory entry (FILINFO.fattrib) */
#define AM_RDO 0x01 /* Read only */
#define AM_HID 0x02 /* Hidden */
#define AM_SYS 0x04 /* System */
#define AM_DIR 0x10 /* Directory */
#define AM_ARC 0x20 /* Archive */
#ifdef __cplusplus
}
#endif
#endif /* FF_DEFINED */

288
arm7/source/fat/ffconf.h Normal file
View File

@@ -0,0 +1,288 @@
/*---------------------------------------------------------------------------/
/ FatFs Functional Configurations
/---------------------------------------------------------------------------*/
#define FFCONF_DEF 86604 /* Revision ID */
/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_READONLY 0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/ and optional writing functions as well. */
#define FF_FS_MINIMIZE 0
/* This option defines minimization level to remove some basic API functions.
/
/ 0: Basic functions are fully enabled.
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/ are removed.
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/ 3: f_lseek() function is removed in addition to 2. */
#define FF_USE_STRFUNC 0
/* This option switches string functions, f_gets(), f_putc(), f_puts() and f_printf().
/
/ 0: Disable string functions.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion. */
#define FF_USE_FIND 0
/* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
#define FF_USE_MKFS 0
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
#define FF_USE_FASTSEEK 1
/* This option switches fast seek function. (0:Disable or 1:Enable) */
#define FF_USE_EXPAND 0
/* This option switches f_expand function. (0:Disable or 1:Enable) */
#define FF_USE_CHMOD 0
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
#define FF_USE_LABEL 0
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/ (0:Disable or 1:Enable) */
#define FF_USE_FORWARD 0
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/
#define FF_CODE_PAGE 437 //932
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect code page setting can cause a file open failure.
/
/ 437 - U.S.
/ 720 - Arabic
/ 737 - Greek
/ 771 - KBL
/ 775 - Baltic
/ 850 - Latin 1
/ 852 - Latin 2
/ 855 - Cyrillic
/ 857 - Turkish
/ 860 - Portuguese
/ 861 - Icelandic
/ 862 - Hebrew
/ 863 - Canadian French
/ 864 - Arabic
/ 865 - Nordic
/ 866 - Russian
/ 869 - Greek 2
/ 932 - Japanese (DBCS)
/ 936 - Simplified Chinese (DBCS)
/ 949 - Korean (DBCS)
/ 950 - Traditional Chinese (DBCS)
/ 0 - Include all code pages above and configured by f_setcp()
*/
#define FF_USE_LFN 1
#define FF_MAX_LFN 255
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/ 0: Disable LFN. FF_MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
/ be in range of 12 to 255. It is recommended to be set 255 to fully support LFN
/ specification.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
/ ff_memfree() in ffsystem.c, need to be added to the project. */
#define FF_LFN_UNICODE 2
/* This option switches the character encoding on the API when LFN is enabled.
/
/ 0: ANSI/OEM in current CP (TCHAR = char)
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
/ 2: Unicode in UTF-8 (TCHAR = char)
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
/
/ Also behavior of string I/O functions will be affected by this option.
/ When LFN is not enabled, this option has no effect. */
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
/* This set of options defines size of file name members in the FILINFO structure
/ which is used to read out directory items. These values should be suffcient for
/ the file names to read. The maximum possible length of the read file name depends
/ on character encoding. When LFN is not enabled, these options have no effect. */
#define FF_STRF_ENCODE 3
/* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(),
/ f_putc(), f_puts and f_printf() convert the character encoding in it.
/ This option selects assumption of character encoding ON THE FILE to be
/ read/written via those functions.
/
/ 0: ANSI/OEM in current CP
/ 1: Unicode in UTF-16LE
/ 2: Unicode in UTF-16BE
/ 3: Unicode in UTF-8
*/
#define FF_FS_RPATH 2
/* This option configures support for relative path.
/
/ 0: Disable relative path and remove related functions.
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
/ 2: f_getcwd() function is available in addition to 1.
*/
/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/
#define FF_VOLUMES 4
/* Number of volumes (logical drives) to be used. (1-10) */
#define FF_STR_VOLUME_ID 1
#define FF_VOLUME_STRS "fat","sd","pc","pc2"//"RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
/ not defined, a user defined volume string table needs to be defined as:
/
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
*/
#define FF_MULTI_PARTITION 0
/* This option switches support for multiple volumes on the physical drive.
/ By default (0), each logical drive number is bound to the same physical drive
/ number and only an FAT volume found on the physical drive will be mounted.
/ When this function is enabled (1), each logical drive number can be bound to
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/ funciton will be available. */
#define FF_MIN_SS 512
#define FF_MAX_SS 512
/* This set of options configures the range of sector size to be supported. (512,
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk. But a larger value may be required for on-board flash memory and some
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
/ for variable sector size mode and disk_ioctl() function needs to implement
/ GET_SECTOR_SIZE command. */
#define FF_USE_TRIM 0
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
/ disk_ioctl() function. */
#define FF_FS_NOFSINFO 0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/ bit0=0: Use free cluster count in the FSINFO if available.
/ bit0=1: Do not trust free cluster count in the FSINFO.
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/
/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_TINY 0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
/ Instead of private sector buffer eliminated from the file object, common sector
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
#define FF_FS_EXFAT 0
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
#define FF_FS_NORTC 1
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2018
/* The option FF_FS_NORTC switches timestamp functiton. If the system does not have
/ any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable
/ the timestamp function. Every object modified by FatFs will have a fixed timestamp
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/ These options have no effect at read-only configuration (FF_FS_READONLY = 1). */
#define FF_FS_LOCK 0
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
/ is 1.
/
/ 0: Disable file lock function. To avoid volume corruption, application program
/ should avoid illegal open, remove and rename to the open objects.
/ >0: Enable file lock function. The value defines how many files/sub-directories
/ can be opened simultaneously under file lock control. Note that the file
/ lock control is independent of re-entrancy. */
/* #include <somertos.h> // O/S definitions */
#define FF_FS_REENTRANT 0
#define FF_FS_TIMEOUT 1000
#define FF_SYNC_t HANDLE
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this function.
/
/ 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
/ function, must be added to the project. Samples are available in
/ option/syscall.c.
/
/ The FF_FS_TIMEOUT defines timeout period in unit of time tick.
/ The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be
/ included somewhere in the scope of ff.h. */
/*--- End of configuration options ---*/

170
arm7/source/fat/ffsystem.c Normal file
View File

@@ -0,0 +1,170 @@
/*------------------------------------------------------------------------*/
/* Sample Code of OS Dependent Functions for FatFs */
/* (C)ChaN, 2018 */
/*------------------------------------------------------------------------*/
#include "ff.h"
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
/*------------------------------------------------------------------------*/
/* Allocate a memory block */
/*------------------------------------------------------------------------*/
void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */
UINT msize /* Number of bytes to allocate */
)
{
return malloc(msize); /* Allocate a new memory block with POSIX API */
}
/*------------------------------------------------------------------------*/
/* Free a memory block */
/*------------------------------------------------------------------------*/
void ff_memfree (
void* mblock /* Pointer to the memory block to free (nothing to do if null) */
)
{
free(mblock); /* Free the memory block with POSIX API */
}
#endif
#if FF_FS_REENTRANT /* Mutal exclusion */
/*------------------------------------------------------------------------*/
/* Create a Synchronization Object */
/*------------------------------------------------------------------------*/
/* This function is called in f_mount() function to create a new
/ synchronization object for the volume, such as semaphore and mutex.
/ When a 0 is returned, the f_mount() function fails with FR_INT_ERR.
*/
//const osMutexDef_t Mutex[FF_VOLUMES]; /* Table of CMSIS-RTOS mutex */
int ff_cre_syncobj ( /* 1:Function succeeded, 0:Could not create the sync object */
BYTE vol, /* Corresponding volume (logical drive number) */
FF_SYNC_t* sobj /* Pointer to return the created sync object */
)
{
/* Win32 */
*sobj = CreateMutex(NULL, FALSE, NULL);
return (int)(*sobj != INVALID_HANDLE_VALUE);
/* uITRON */
// T_CSEM csem = {TA_TPRI,1,1};
// *sobj = acre_sem(&csem);
// return (int)(*sobj > 0);
/* uC/OS-II */
// OS_ERR err;
// *sobj = OSMutexCreate(0, &err);
// return (int)(err == OS_NO_ERR);
/* FreeRTOS */
// *sobj = xSemaphoreCreateMutex();
// return (int)(*sobj != NULL);
/* CMSIS-RTOS */
// *sobj = osMutexCreate(&Mutex[vol]);
// return (int)(*sobj != NULL);
}
/*------------------------------------------------------------------------*/
/* Delete a Synchronization Object */
/*------------------------------------------------------------------------*/
/* This function is called in f_mount() function to delete a synchronization
/ object that created with ff_cre_syncobj() function. When a 0 is returned,
/ the f_mount() function fails with FR_INT_ERR.
*/
int ff_del_syncobj ( /* 1:Function succeeded, 0:Could not delete due to an error */
FF_SYNC_t sobj /* Sync object tied to the logical drive to be deleted */
)
{
/* Win32 */
return (int)CloseHandle(sobj);
/* uITRON */
// return (int)(del_sem(sobj) == E_OK);
/* uC/OS-II */
// OS_ERR err;
// OSMutexDel(sobj, OS_DEL_ALWAYS, &err);
// return (int)(err == OS_NO_ERR);
/* FreeRTOS */
// vSemaphoreDelete(sobj);
// return 1;
/* CMSIS-RTOS */
// return (int)(osMutexDelete(sobj) == osOK);
}
/*------------------------------------------------------------------------*/
/* Request Grant to Access the Volume */
/*------------------------------------------------------------------------*/
/* This function is called on entering file functions to lock the volume.
/ When a 0 is returned, the file function fails with FR_TIMEOUT.
*/
int ff_req_grant ( /* 1:Got a grant to access the volume, 0:Could not get a grant */
FF_SYNC_t sobj /* Sync object to wait */
)
{
/* Win32 */
return (int)(WaitForSingleObject(sobj, FF_FS_TIMEOUT) == WAIT_OBJECT_0);
/* uITRON */
// return (int)(wai_sem(sobj) == E_OK);
/* uC/OS-II */
// OS_ERR err;
// OSMutexPend(sobj, FF_FS_TIMEOUT, &err));
// return (int)(err == OS_NO_ERR);
/* FreeRTOS */
// return (int)(xSemaphoreTake(sobj, FF_FS_TIMEOUT) == pdTRUE);
/* CMSIS-RTOS */
// return (int)(osMutexWait(sobj, FF_FS_TIMEOUT) == osOK);
}
/*------------------------------------------------------------------------*/
/* Release Grant to Access the Volume */
/*------------------------------------------------------------------------*/
/* This function is called on leaving file functions to unlock the volume.
*/
void ff_rel_grant (
FF_SYNC_t sobj /* Sync object to be signaled */
)
{
/* Win32 */
ReleaseMutex(sobj);
/* uITRON */
// sig_sem(sobj);
/* uC/OS-II */
// OSMutexPost(sobj);
/* FreeRTOS */
// xSemaphoreGive(sobj);
/* CMSIS-RTOS */
// osMutexRelease(sobj);
}
#endif

15597
arm7/source/fat/ffunicode.c Normal file

File diff suppressed because it is too large Load Diff

102
arm7/source/globalHeap.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "common.h"
#include <string.h>
#include "core/heap/tlsf.h"
#include "globalHeap.h"
static tlsf_t sHeap;
extern "C" void* malloc(size_t size)
{
return tlsf_malloc(sHeap, size);
}
extern "C" void* _malloc_r(struct _reent *, size_t size)
{
return malloc(size);
}
extern "C" void free(void* ptr)
{
tlsf_free(sHeap, ptr);
}
extern "C" void _free_r(struct _reent *, void* ptr)
{
free(ptr);
}
extern "C" void* realloc(void* ptr, size_t size)
{
return tlsf_realloc(sHeap, ptr, size);
}
extern "C" void* memalign(size_t alignment, size_t size)
{
return tlsf_memalign(sHeap, alignment, size);
}
void* operator new(std::size_t blocksize)
{
return malloc(blocksize);
}
void* operator new(std::size_t size, std::align_val_t al)
{
return memalign(static_cast<std::size_t>(al), size);
}
void* operator new[](std::size_t blocksize)
{
return malloc(blocksize);
}
void* operator new[](std::size_t size, std::align_val_t al)
{
return memalign(static_cast<std::size_t>(al), size);
}
void operator delete(void* ptr)
{
return free(ptr);
}
void operator delete(void* ptr, std::align_val_t align)
{
return free(ptr);
}
void operator delete(void* ptr, std::size_t size, std::align_val_t align)
{
return free(ptr);
}
void operator delete[](void* ptr)
{
return free(ptr);
}
void operator delete[](void* ptr, std::align_val_t align)
{
return free(ptr);
}
void operator delete[](void* ptr, std::size_t size, std::align_val_t align)
{
return free(ptr);
}
extern u8 __heap_start;
extern u8 __heap_end;
[[gnu::target("thumb"), gnu::optimize("Os")]]
void heap_init()
{
u32 heapStart = (u32)&__heap_start;
heapStart = (heapStart + 31) & ~31;
u32 heapEnd = (u32)&__heap_end;
heapEnd = heapEnd & ~31;
u32 tlsfSize = tlsf_size();
memset((void*)heapStart, 0, tlsfSize);
memset((u8*)heapStart + tlsfSize, 0xA5, heapEnd - heapStart - tlsfSize);
sHeap = tlsf_create_with_pool((void*)heapStart, heapEnd - heapStart);
}

16
arm7/source/globalHeap.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
void* operator new(std::size_t blocksize) noexcept;
void* operator new(std::size_t size, std::align_val_t al) noexcept;
void* operator new[](std::size_t blocksize) noexcept;
void* operator new[](std::size_t size, std::align_val_t al) noexcept;
void operator delete(void* ptr) noexcept;
void operator delete(void* ptr, std::align_val_t align) noexcept;
void operator delete(void* ptr, std::size_t size, std::align_val_t align) noexcept;
void operator delete[](void* ptr) noexcept;
void operator delete[](void* ptr, std::align_val_t align) noexcept;
void operator delete[](void* ptr, std::size_t size, std::align_val_t align) noexcept;
constexpr std::align_val_t cache_align { 32 };
void heap_init();

14
arm7/source/header.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include "common.h"
#include "header.h"
extern "C" void _start();
extern u8 __bss_start[];
extern u8 __bss_size[];
[[gnu::section(".crt0")]]
[[gnu::used]]
pload_header7_t gLoaderHeader
{
.entryPoint = (void*)&_start,
.apiVersion = PICO_LOADER_API_VERSION
};

4
arm7/source/header.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
#include "../../include/picoLoader7.h"
extern pload_header7_t gLoaderHeader;

15
arm7/source/ipc.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include <libtwl/ipc/ipcFifo.h>
#include "ipcCommands.h"
static inline void sendToArm9(u32 value)
{
while (ipc_isSendFifoFull());
ipc_sendWordDirect(value);
}
static inline u32 receiveFromArm9()
{
while (ipc_isRecvFifoEmpty());
return ipc_recvWordDirect();
}

View File

@@ -0,0 +1,24 @@
#include "common.h"
#include "ApListFactory.h"
ApList* ApListFactory::CreateFromFile(const TCHAR *path)
{
FIL file;
if (f_open(&file, path, FA_OPEN_EXISTING | FA_READ) != FR_OK)
{
LOG_FATAL("Failed to open ap list file\n");
return nullptr;
}
const u32 entryCount = f_size(&file) / sizeof(ApListEntry);
auto entries = std::make_unique_for_overwrite<ApListEntry[]>(entryCount);
UINT bytesRead = 0;
FRESULT result = f_read(&file, entries.get(), entryCount * sizeof(ApListEntry), &bytesRead);
if (result != FR_OK || bytesRead != entryCount * sizeof(ApListEntry))
{
LOG_FATAL("Failed to read ap list file\n");
return nullptr;
}
f_close(&file);
return new ApList(std::move(entries), entryCount);
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "ApList.h"
/// @brief Factory for creating \see ApList instances.
class ApListFactory
{
public:
/// @brief Creates an \see ApList instance from the file at the given \p path.
/// @param path The ap list file path.
/// @return A pointer to the constructed \see ApList instance, or \c nullptr if construction failed.
ApList* CreateFromFile(const TCHAR* path);
};

View File

@@ -0,0 +1,151 @@
#include "common.h"
#include <libtwl/dma/dmaNitro.h>
#include <libtwl/dma/dmaTwl.h>
#include <libtwl/gfx/gfxStatus.h>
#include <libtwl/rtos/rtosIrq.h>
#include <libtwl/sio/sio.h>
#include <libtwl/sound/sound.h>
#include <libtwl/sound/soundCapture.h>
#include <libtwl/sound/soundChannel.h>
#include <libtwl/sys/sysPower.h>
#include <libtwl/timer/timer.h>
#include "SdmmcDefinitions.h"
#include "core/Environment.h"
#include "Arm7IoRegisterClearer.h"
void Arm7IoRegisterClearer::ClearIoRegisters() const
{
REG_IME = 0;
REG_IE = 0;
if (Environment::IsDsiMode())
{
ClearTwlIoRegisters();
}
ClearNtrIoRegisters();
}
void Arm7IoRegisterClearer::ClearNtrIoRegisters() const
{
REG_DISPSTAT = 0;
REG_TM0CNT_H = 0; // timer 0
REG_TM0CNT_L = 0;
REG_TM1CNT_H = 0; // timer 1
REG_TM1CNT_L = 0;
REG_TM2CNT_H = 0; // timer 2
REG_TM2CNT_L = 0;
REG_TM3CNT_H = 0; // timer 3
REG_TM3CNT_L = 0;
REG_DMA0CNT = 0; // dma 0
REG_DMA0SAD = 0;
REG_DMA0DAD = 0;
REG_DMA1CNT = 0; // dma 1
REG_DMA1SAD = 0;
REG_DMA1DAD = 0;
REG_DMA2CNT = 0; // dma 2
REG_DMA2SAD = 0;
REG_DMA2DAD = 0;
REG_DMA3CNT = 0; // dma 3
REG_DMA3SAD = 0;
REG_DMA3DAD = 0;
REG_RCNT0_L = 0;
for (int i = 0; i < 16; i++)
{
REG_SOUNDxCNT(i) = 0;
REG_SOUNDxSAD(i) = 0;
REG_SOUNDxTMR(i) = 0;
REG_SOUNDxPNT(i) = 0;
REG_SOUNDxLEN(i) = 0;
}
REG_SOUNDCNT = 0;
REG_SNDCAP0CNT = 0;
REG_SNDCAP1CNT = 0;
REG_SNDCAP0DAD = 0;
REG_SNDCAP0LEN = 0;
REG_SNDCAP1DAD = 0;
REG_SNDCAP1LEN = 0;
sys_setWifiPower(false);
sys_setSoundPower(true);
}
void Arm7IoRegisterClearer::ClearTwlIoRegisters() const
{
REG_IE2 = 0;
REG_NDMA0SAD = 0;
REG_NDMA0DAD = 0;
REG_NDMA0TCNT = 0;
REG_NDMA0WCNT = 0;
REG_NDMA0BCNT = 0;
REG_NDMA0FDATA = 0;
REG_NDMA0CNT = 0;
REG_NDMA1SAD = 0;
REG_NDMA1DAD = 0;
REG_NDMA1TCNT = 0;
REG_NDMA1WCNT = 0;
REG_NDMA1BCNT = 0;
REG_NDMA1FDATA = 0;
REG_NDMA1CNT = 0;
REG_NDMA2SAD = 0;
REG_NDMA2DAD = 0;
REG_NDMA2TCNT = 0;
REG_NDMA2WCNT = 0;
REG_NDMA2BCNT = 0;
REG_NDMA2FDATA = 0;
REG_NDMA2CNT = 0;
REG_NDMA3SAD = 0;
REG_NDMA3DAD = 0;
REG_NDMA3TCNT = 0;
REG_NDMA3WCNT = 0;
REG_NDMA3BCNT = 0;
REG_NDMA3FDATA = 0;
REG_NDMA3CNT = 0;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL32) &= 0xF7FFu;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL32) &= 0xEFFFu;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL32) |= 0x402u;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL) = (*(vu16*)(SDMMC_BASE + REG_SDDATACTL) & 0xFFDD) | 2;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL32) &= 0xFFFFu;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL) &= 0xFFDFu;
*(vu16*)(SDMMC_BASE + REG_SDBLKLEN32) = 512;
*(vu16*)(SDMMC_BASE + REG_SDBLKCOUNT32) = 1;
*(vu16*)(SDMMC_BASE + REG_SDRESET) &= 0xFFFEu;
*(vu16*)(SDMMC_BASE + REG_SDRESET) |= 1u;
*(vu16*)(SDMMC_BASE + REG_SDIRMASK0) |= TMIO_MASK_ALL;
*(vu16*)(SDMMC_BASE + REG_SDIRMASK1) |= TMIO_MASK_ALL>>16;
*(vu16*)(SDMMC_BASE + REG_SDSTATUS0) = 0;
*(vu16*)(SDMMC_BASE + REG_SDSTATUS1) = 0;
*(vu16*)(SDMMC_BASE + 0x0fc) |= 0xDBu; //SDCTL_RESERVED7
*(vu16*)(SDMMC_BASE + 0x0fe) |= 0xDBu; //SDCTL_RESERVED8
*(vu16*)(SDMMC_BASE + REG_SDPORTSEL) &= 0xFFFCu;
*(vu16*)(SDMMC_BASE + REG_SDCLKCTL) = 0x20;
*(vu16*)(SDMMC_BASE + REG_SDOPT) = 0x40EE;
*(vu16*)(SDMMC_BASE + REG_SDPORTSEL) &= 0xFFFCu;
*(vu16*)(SDMMC_BASE + REG_SDBLKLEN) = 512;
*(vu16*)(SDMMC_BASE + REG_SDSTOP) = 0;
*(vu16*)(0x04004A00 + REG_SDDATACTL32) &= 0xF7FFu;
*(vu16*)(0x04004A00 + REG_SDDATACTL32) &= 0xEFFFu;
*(vu16*)(0x04004A00 + REG_SDDATACTL32) |= 0x402u;
*(vu16*)(0x04004A00 + REG_SDDATACTL) = (*(vu16*)(SDMMC_BASE + REG_SDDATACTL) & 0xFFDD) | 2;
*(vu16*)(0x04004A00 + REG_SDDATACTL32) &= 0xFFFFu;
*(vu16*)(0x04004A00 + REG_SDDATACTL) &= 0xFFDFu;
*(vu16*)(0x04004A00 + REG_SDBLKLEN32) = 512;
*(vu16*)(0x04004A00 + REG_SDBLKCOUNT32) = 1;
*(vu16*)(0x04004A00 + REG_SDRESET) &= 0xFFFEu;
*(vu16*)(0x04004A00 + REG_SDRESET) |= 1u;
*(vu16*)(0x04004A00 + REG_SDIRMASK0) |= TMIO_MASK_ALL;
*(vu16*)(0x04004A00 + REG_SDIRMASK1) |= TMIO_MASK_ALL>>16;
*(vu16*)(0x04004A00 + REG_SDSTATUS0) = 0;
*(vu16*)(0x04004A00 + REG_SDSTATUS1) = 0;
*(vu16*)(0x04004A00 + 0x0fc) |= 0xDBu; //SDCTL_RESERVED7
*(vu16*)(0x04004A00 + 0x0fe) |= 0xDBu; //SDCTL_RESERVED8
*(vu16*)(0x04004A00 + REG_SDPORTSEL) &= 0xFFFCu;
*(vu16*)(0x04004A00 + REG_SDCLKCTL) = 0x20;
*(vu16*)(0x04004A00 + REG_SDOPT) = 0x40EE;
*(vu16*)(0x04004A00 + REG_SDPORTSEL) &= 0xFFFCu;
*(vu16*)(0x04004A00 + REG_SDBLKLEN) = 512;
*(vu16*)(0x04004A00 + REG_SDSTOP) = 0;
}

View File

@@ -0,0 +1,13 @@
#pragma once
/// @brief Class for clearing the arm9 IO registers.
class Arm7IoRegisterClearer
{
public:
/// @brief Clears the arm7 IO registers.
void ClearIoRegisters() const;
private:
void ClearNtrIoRegisters() const;
void ClearTwlIoRegisters() const;
};

View File

@@ -0,0 +1,113 @@
#include "common.h"
#include <bit>
#include "Blowfish.h"
void Blowfish::Encrypt(const void* src, void* dst, u32 length) const
{
const u32* src32 = (const u32*)src;
u32* dst32 = (u32*)dst;
for (u32 i = 0; i < (length / 4); i += 2)
{
u64 value = src32[i] | ((u64)src32[i + 1] << 32);
value = Encrypt(value);
dst32[i] = value & 0xFFFFFFFFu;
dst32[i + 1] = value >> 32;
}
}
u64 Blowfish::Encrypt(u64 value) const
{
u32 y = value & 0xFFFFFFFFu;
u32 x = value >> 32;
for (u32 i = 0; i < 16; i++)
{
u32 z = _keyTable.pTable[i] ^ x;
u32 a = _keyTable.sBoxes[0][(z >> 24) & 0xFF];
u32 b = _keyTable.sBoxes[1][(z >> 16) & 0xFF];
u32 c = _keyTable.sBoxes[2][(z >> 8) & 0xFF];
u32 d = _keyTable.sBoxes[3][z & 0xFF];
x = (d + (c ^ (b + a))) ^ y;
y = z;
}
return (x ^ _keyTable.pTable[16]) | ((u64)(y ^ _keyTable.pTable[17]) << 32);
}
void Blowfish::Decrypt(const void* src, void* dst, u32 length) const
{
const u32* src32 = (const u32*)src;
u32* dst32 = (u32*)dst;
for (u32 i = 0; i < (length / 4); i += 2)
{
u64 value = src32[i] | ((u64)src32[i + 1] << 32);
value = Decrypt(value);
dst32[i] = value & 0xFFFFFFFFu;
dst32[i + 1] = value >> 32;
}
}
u64 Blowfish::Decrypt(u64 value) const
{
u32 y = value & 0xFFFFFFFFu;
u32 x = value >> 32;
for (u32 i = 17; i >= 2; i--)
{
u32 z = _keyTable.pTable[i] ^ x;
u32 a = _keyTable.sBoxes[0][(z >> 24) & 0xFF];
u32 b = _keyTable.sBoxes[1][(z >> 16) & 0xFF];
u32 c = _keyTable.sBoxes[2][(z >> 8) & 0xFF];
u32 d = _keyTable.sBoxes[3][z & 0xFF];
x = (d + (c ^ (b + a))) ^ y;
y = z;
}
return (x ^ _keyTable.pTable[1]) | ((u64)(y ^ _keyTable.pTable[0]) << 32);
}
void Blowfish::TransformTable(u32 idCode, int level, int modulo)
{
u32 keyCode[3] = { idCode, idCode >> 1, idCode << 1 };
if (level >= 1)
{
ApplyKeyCode(&keyCode[0], modulo);
}
if (level >= 2)
{
ApplyKeyCode(&keyCode[0], modulo);
}
keyCode[1] <<= 1;
keyCode[2] >>= 1;
if (level >= 3)
{
ApplyKeyCode(&keyCode[0], modulo);
}
}
void Blowfish::ApplyKeyCode(u32* keyCode, int modulo)
{
Encrypt(&keyCode[1], &keyCode[1], 8);
Encrypt(&keyCode[0], &keyCode[0], 8);
const u32 reversedKeyCode[3] =
{
std::byteswap(keyCode[0]),
std::byteswap(keyCode[1]),
std::byteswap(keyCode[2])
};
int keyCodeIndex = 0;
for (u32 i = 0; i < BLOWFISH_PTABLE_ENTRY_COUNT; i++)
{
_keyTable.pTable[i] ^= reversedKeyCode[keyCodeIndex];
if (++keyCodeIndex == (modulo >> 2))
{
keyCodeIndex = 0;
}
}
u64 scratch = 0;
u64* keyTable = (u64*)&_keyTable;
for (u32 i = 0; i < (sizeof(KeyTable) / 8); i++)
{
scratch = Encrypt(scratch);
keyTable[i] = (scratch >> 32) | ((scratch & 0xFFFFFFFFu) << 32);
}
}

View File

@@ -0,0 +1,67 @@
#pragma once
#include <string.h>
#define BLOWFISH_PTABLE_ENTRY_COUNT 18
#define BLOWFISH_SBOX_COUNT 4
#define BLOWFISH_SBOX_ENTRY_COUNT 256
/// @brief Class for blowfish encryption and decryption.
class Blowfish
{
public:
/// @brief Struct representing a blowfish key table.
struct KeyTable
{
u32 pTable[BLOWFISH_PTABLE_ENTRY_COUNT];
u32 sBoxes[BLOWFISH_SBOX_COUNT][BLOWFISH_SBOX_ENTRY_COUNT];
};
static_assert(sizeof(KeyTable) == 0x1048, "Invalid size of Blowfish::KeyTable.");
/// @brief Constructs an instance of \see Blowfish using the given \p keyTable.
/// @param keyTable The key table to use. A copy will be made into the constructed class.
explicit Blowfish(const KeyTable* keyTable)
: Blowfish(keyTable->pTable, keyTable->sBoxes) { }
/// @brief Constructs an instance of \see Blowfish using the given \p pTable and \p sBoxes.
/// @param pTable The p table to use. A copy will be made into the constructed class.
/// @param sBoxes The s boxes to use. A copy will be made into the constructed class.
Blowfish(const void* pTable, const void* sBoxes)
{
memcpy(_keyTable.pTable, pTable, sizeof(_keyTable.pTable));
memcpy(_keyTable.sBoxes, sBoxes, sizeof(_keyTable.sBoxes));
}
/// @brief Encrypts the given \p length from \p src to \p dst.
/// @param src The source buffer.
/// @param dst The encrypted destination buffer.
/// @param length The length of the data to encrypt. Must be a multiple of 8.
void Encrypt(const void* src, void* dst, u32 length) const;
/// @brief Encrypts the given 64-bit \p value and returns the result.
/// @param value The 64-bit value to encrypt.
/// @return The encrypted result.
u64 Encrypt(u64 value) const;
/// @brief Drcrypts the given \p length from \p src to \p dst.
/// @param src The encrypted source buffer.
/// @param dst The decrypted destination buffer.
/// @param length The length of the data to encrypt. Must be a multiple of 8.
void Decrypt(const void* src, void* dst, u32 length) const;
/// @brief Decrypts the given 64-bit \p value and returns the result.
/// @param value The 64-bit value to decrypt.
/// @return The decrypted result.
u64 Decrypt(u64 value) const;
/// @brief Transforms the key table by the given \p idCode, \p level and \p modulo.
/// @param idCode The id code to use.
/// @param level The transform level to use.
/// @param modulo The modulo to use.
void TransformTable(u32 idCode, int level, int modulo);
private:
void ApplyKeyCode(u32* keyCode, int modulo);
KeyTable _keyTable;
};

View File

@@ -0,0 +1,14 @@
#pragma once
/// @brief The Pico Loader boot mode.
enum class BootMode
{
/// @brief Boot a retail or homebrew rom.
Normal,
/// @brief Reboot a retail rom that used OS_ResetSystem.
SdkResetSystem,
/// @brief Boot a multiboot rom that is already loaded into memory.
Multiboot
};

View File

@@ -0,0 +1,107 @@
#include "common.h"
#include <string.h>
#include "SaveList.h"
#include "SaveListFactory.h"
#include "fileInfo.h"
#include "CardSaveArranger.h"
#define SAVE_LIST_PATH "/_pico/savelist.bin"
#define DEFAULT_SAVE_SIZE (512 * 1024)
#define SAVE_FILL_VALUE 0xFF
bool CardSaveArranger::SetupCardSave(u32 gameCode, const TCHAR* savePath) const
{
SaveList* saveList = SaveListFactory().CreateFromFile(SAVE_LIST_PATH);
u32 saveSize = DEFAULT_SAVE_SIZE;
if (saveList)
{
const auto saveListEntry = saveList->FindEntry(gameCode);
if (!saveListEntry)
{
LOG_WARNING("Game code %c%c%c%c not found in save list\n",
gameCode & 0xFF, (gameCode >> 8) & 0xFF, (gameCode >> 16) & 0xFF, gameCode >> 24);
}
else
{
saveSize = saveListEntry->GetSaveSize();
saveListEntry->Dump();
}
delete saveList;
}
if (saveSize == 0)
{
return true;
}
auto file = std::make_unique<FIL>();
LOG_DEBUG("Save file: %s\n", savePath);
if (f_open(file.get(), savePath, FA_OPEN_ALWAYS | FA_READ | FA_WRITE) != FR_OK)
{
LOG_FATAL("Failed to open or create save file\n");
return false;
}
u32 initialSize = f_size(file.get());
if (initialSize < saveSize)
{
if (f_lseek(file.get(), saveSize) != FR_OK ||
f_lseek(file.get(), initialSize) != FR_OK)
{
LOG_FATAL("Failed to expand save file\n");
return false;
}
auto ffBuffer = std::make_unique<u8[]>(512);
memset(ffBuffer.get(), SAVE_FILL_VALUE, 512);
u32 offset = initialSize;
// Align to 512 bytes
if ((offset & 511) != 0)
{
u32 remainingTo512 = 512 - (offset & 511);
UINT bytesWritten = 0;
if (f_write(file.get(), ffBuffer.get(), remainingTo512, &bytesWritten) != FR_OK ||
bytesWritten != remainingTo512)
{
LOG_FATAL("Failed to expand save file\n");
return false;
}
offset += remainingTo512;
}
// Write in 512-byte blocks
while (offset < saveSize)
{
UINT bytesWritten = 0;
if (f_write(file.get(), ffBuffer.get(), 512, &bytesWritten) != FR_OK ||
bytesWritten != 512)
{
LOG_FATAL("Failed to expand save file\n");
return false;
}
offset += 512;
}
}
DWORD* clusterTab = (DWORD*)SHARED_SAVE_FILE_INFO->clusterMap;
clusterTab[0] = sizeof(SHARED_SAVE_FILE_INFO->clusterMap) / sizeof(u32);
file->cltbl = clusterTab;
FRESULT seekResult = f_lseek(file.get(), CREATE_LINKMAP);
if (seekResult != FR_OK)
{
LOG_FATAL("Failed to make save cluster table. Result: %d\n", seekResult);
return false;
}
SHARED_SAVE_FILE_INFO->clusterShift = __builtin_ctz(file->obj.fs->csize);
SHARED_SAVE_FILE_INFO->database = file->obj.fs->database;
SHARED_SAVE_FILE_INFO->clusterMask = file->obj.fs->csize - 1;
LOG_DEBUG("Made save cluster table\n");
if (f_close(file.get()) != FR_OK)
{
LOG_FATAL("Failed to close save file\n");
return false;
}
return true;
}

View File

@@ -0,0 +1,12 @@
#pragma once
/// @brief Class for setting up the save file for retail card roms.
class CardSaveArranger
{
public:
/// @brief Sets up the save file at \p savePath for a retail card rom with the given \p gameCode.
/// @param gameCode The game code of the retail card rom.
/// @param savePath The desired save file path.
/// @return \c true when setting up the save was successful, or \c false otherwise.
bool SetupCardSave(u32 gameCode, const TCHAR* savePath) const;
};

View File

@@ -0,0 +1,356 @@
#include "common.h"
#include <libtwl/i2c/i2cMcu.h>
#include <libtwl/sound/twlI2s.h>
#include <libtwl/sound/sound.h>
#include <libtwl/spi/spiCodec.h>
#include <libtwl/spi/spiPmic.h>
#include <libtwl/sys/twlScfg.h>
#include "ipc.h"
#include "gameCode.h"
#include "DSMode.h"
void DSMode::SwitchToDSMode(u32 gameCode) const
{
SwitchToDSTouchAndSoundMode(gameCode);
mcu_writeReg(MCU_REG_MODE, 0);
*(vu16*)0x04004C04 |= (1 << 8); // ntr wifi
REG_SCFG_A9ROM = SCFG_A9ROM_DISABLE_SECURE | SCFG_A9ROM_NITRO;
REG_SCFG_A7ROM = SCFG_A7ROM_DISABLE_SECURE | SCFG_A7ROM_NITRO | SCFG_A7ROM_DISABLE_FUSE;
REG_SCFG_EXT = 0x12A03000u;
sendToArm9(IPC_COMMAND_ARM9_SWITCH_TO_DS_MODE);
receiveFromArm9();
LOG_DEBUG("Switched to ds mode\n");
}
void DSMode::SwitchToDSTouchAndSoundMode(u32 gameCode) const
{
REG_I2SCNT = I2SCNT_MIX_RATIO_DSP_0_NITRO_8 | I2SCNT_FREQUENCY_32728_HZ;
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_PLL_J, 21);
}
REG_I2SCNT |= I2SCNT_ENABLE;
SwitchCodecToDSMode(gameCode);
REG_SOUNDCNT = SOUNDCNT_MASTER_ENABLE | SOUNDCNT_MASTER_VOLUME(0x7F);
LOG_DEBUG("Switched to ds touch and sound mode\n");
}
void DSMode::SwitchCodecToDSMode(u32 gameCode) const
{
// 0xAC: special setting (when found special gamecode)
// 0xA7: normal setting (for any other gamecodes)
u8 volLevel = ShouldUseVolumeFix(gameCode) ? 0xAC : 0xA7;
// Touchscreen
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_GPI3_PIN_CONTROL, 0x00);
codec_readRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC);
}
codec_setPage(CODEC_PAGE_3);
{
codec_readRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1);
}
codec_setPage(CODEC_PAGE_0);
{
codec_readRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP);
}
codec_setPage(CODEC_PAGE_1);
{
codec_readRegister(CODEC_REG_PAGE1_HPL_DRIVER);
codec_readRegister(CODEC_REG_PAGE1_SPL_DRIVER);
codec_readRegister(CODEC_REG_PAGE1_MICBIAS);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_VOLUME_CONTROL_FINE_ADJUST, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_DAC_VOLUME_CONTROL, 0x0C);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_HPL, 0xFF);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_HPR, 0xFF);
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_SPL, 0x7F);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_SPR, 0x7F);
codec_writeRegister(CODEC_REG_PAGE1_HPL_DRIVER, 0x4A);
codec_writeRegister(CODEC_REG_PAGE1_HPR_DRIVER, 0x4A);
codec_writeRegister(CODEC_REG_PAGE1_SPL_DRIVER, 0x10);
codec_writeRegister(CODEC_REG_PAGE1_SPR_DRIVER, 0x10);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x00);
}
codec_setPage(CODEC_PAGE_3);
{
codec_readRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1);
codec_writeRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1, 0x98);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_DAC_L_DAC_R_OUTPUT_MIXER_ROUTING, 0x00);
codec_writeRegister(CODEC_REG_PAGE1_HEADPHONE_DRIVERS, 0x14);
codec_writeRegister(CODEC_REG_PAGE1_CLASS_D_SPEAKER_AMPLIFIER, 0x14);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP, 0x00);
codec_readRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL);
codec_writeRegister(CODEC_REG_PAGE0_PLL_P_R, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_DAC_MDAC_VAL, 0x02);
codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_ADC_MADC_VAL, 0x02);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_MICBIAS, 0x00);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_GPI3_PIN_CONTROL, 0x60);
codec_writeRegister(CODEC_REG_PAGE0_RESET, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_GPI1_GPI2_PIN_CONTROL, 0x66);
}
codec_setPage(CODEC_PAGE_1);
{
codec_readRegister(CODEC_REG_PAGE1_CLASS_D_SPEAKER_AMPLIFIER);
codec_writeRegister(CODEC_REG_PAGE1_CLASS_D_SPEAKER_AMPLIFIER, 0x10);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_CLOCK_GEN_MUXING, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x81);
codec_writeRegister(CODEC_REG_PAGE0_ADC_MADC_VAL, 0x82);
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x82);
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_CLOCK_GEN_MUXING, 0x03);
codec_writeRegister(CODEC_REG_PAGE0_PLL_P_R, 0xA1);
codec_writeRegister(CODEC_REG_PAGE0_PLL_J, 0x15);
codec_writeRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_DAC_MDAC_VAL, 0x83);
codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_ADC_MADC_VAL, 0x83);
}
codec_setPage(CODEC_PAGE_3);
{
codec_readRegister(CODEC_REG_PAGE3_SCAN_MODE_TIMER_CLOCK);
codec_writeRegister(CODEC_REG_PAGE3_SCAN_MODE_TIMER_CLOCK, 0x08);
}
codec_setPage(CODEC_PAGE_4);
{
codec_writeRegister(0x08, 0x7F);
codec_writeRegister(0x09, 0xE1);
codec_writeRegister(0x0A, 0x80);
codec_writeRegister(0x0B, 0x1F);
codec_writeRegister(0x0C, 0x7F);
codec_writeRegister(0x0D, 0xC1);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_DAC_LEFT_VOLUME_CONTROL, 0x08);
codec_writeRegister(CODEC_REG_PAGE0_DAC_RIGHT_VOLUME_CONTROL, 0x08);
codec_writeRegister(CODEC_REG_PAGE0_GPI3_PIN_CONTROL, 0x00);
}
codec_setPage(CODEC_PAGE_4);
{
codec_writeRegister(0x08, 0x7F);
codec_writeRegister(0x09, 0xE1);
codec_writeRegister(0x0A, 0x80);
codec_writeRegister(0x0B, 0x1F);
codec_writeRegister(0x0C, 0x7F);
codec_writeRegister(0x0D, 0xC1);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_MIC_PGA, 0x2B);
codec_writeRegister(CODEC_REG_PAGE1_DELTA_SIGMA_MONO_ADC_CHANNEL_FINE_GAIN_INPUT_SELECTION_FOR_P_TERMINAL, 0x40);
codec_writeRegister(CODEC_REG_PAGE1_ADC_INPUT_SELECTION_FOR_M_TERMINAL, 0x40);
codec_writeRegister(CODEC_REG_PAGE1_INPUT_CM_SETTINGS, 0x60);
}
codec_setPage(CODEC_PAGE_0);
{
codec_readRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL, 0x02);
codec_readRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL, 0x10);
codec_readRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL, 0x40);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_HP_OUTPUT_DRIVERS_POP_REMOVAL_SETTINGS, 0x20);
codec_writeRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL, 0xF0);
}
codec_setPage(CODEC_PAGE_0);
{
codec_readRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC);
codec_readRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP);
codec_writeRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP, 0xD4);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_DAC_L_DAC_R_OUTPUT_MIXER_ROUTING, 0x44);
codec_writeRegister(CODEC_REG_PAGE1_HEADPHONE_DRIVERS, 0xD4);
codec_writeRegister(CODEC_REG_PAGE1_HPL_DRIVER, 0x4E);
codec_writeRegister(CODEC_REG_PAGE1_HPR_DRIVER, 0x4E);
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_HPL, 0x9E);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_HPR, 0x9E);
codec_writeRegister(CODEC_REG_PAGE1_CLASS_D_SPEAKER_AMPLIFIER, 0xD4);
codec_writeRegister(CODEC_REG_PAGE1_SPL_DRIVER, 0x14);
codec_writeRegister(CODEC_REG_PAGE1_SPR_DRIVER, 0x14);
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_SPL, 0xA7);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_SPR, 0xA7);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_DAC_VOLUME_CONTROL, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_GPI3_PIN_CONTROL, 0x60);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_SPL, volLevel);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_SPR, volLevel);
codec_writeRegister(CODEC_REG_PAGE1_MICBIAS, 0x03);
}
codec_setPage(CODEC_PAGE_3);
{
codec_writeRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_2, 0x00);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_HP_OUTPUT_DRIVERS_POP_REMOVAL_SETTINGS, 0x20);
codec_writeRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL, 0xF0);
codec_readRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL);
codec_writeRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL, 0x00);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_VOLUME_CONTROL_FINE_ADJUST, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x00);
// Set remaining values
codec_writeRegister(0x03, 0x44);
codec_writeRegister(CODEC_REG_PAGE0_DAC_DOSR_VAL_MSB, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_DAC_DOSR_VAL_LSB, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_DAC_IDAC_VAL, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_DAC_MINIDSP_INTERPOLATION, 0x08);
codec_writeRegister(CODEC_REG_PAGE0_ADC_AOSR_VAL, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_ADC_IDAC_VAL, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_ADC_MINIDSP_DECIMATION, 0x04);
codec_writeRegister(CODEC_REG_PAGE0_CLKOUT_M_VAL, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_BCLK_N_VAL, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_ADC_FLAG_REGISTER, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_GPIO1_IN_OUT_PIN_CONTROL, 0x34);
codec_writeRegister(CODEC_REG_PAGE0_GPIO2_IN_OUT_PIN_CONTROL, 0x32);
codec_writeRegister(CODEC_REG_PAGE0_SDOUT_OUT_PIN_CONTROL, 0x12);
codec_writeRegister(CODEC_REG_PAGE0_SDIN_IN_PIN_CONTROL, 0x03);
codec_writeRegister(CODEC_REG_PAGE0_MISO_OUT_PIN_CONTROL, 0x02);
codec_writeRegister(CODEC_REG_PAGE0_SCLK_IN_PIN_CONTROL, 0x03);
codec_writeRegister(CODEC_REG_PAGE0_DAC_INSTRUCTION_SET, 0x19);
codec_writeRegister(CODEC_REG_PAGE0_ADC_INSTRUCTION_SET, 0x05);
codec_writeRegister(CODEC_REG_PAGE0_DRC_CONTROL_1, 0x0F);
codec_writeRegister(CODEC_REG_PAGE0_DRC_CONTROL_2, 0x38);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_LENGTH_MSB, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_LENGTH_MID, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_LENGTH_LSB, 0xEE);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_SIN_MSB, 0x10);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_SIN_LSB, 0xD8);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_COS_MSB, 0x7E);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_COS_LSB, 0xE3);
codec_writeRegister(CODEC_REG_PAGE0_AGC_MAXIMUM_GAIN, 0x7F);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL, 0xD2);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_GAIN, 0x2C);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL, 0x70);
codec_writeRegister(CODEC_REG_PAGE1_HP_DRIVER_CONTROL, 0x20);
}
// Finish up!
codec_setPage(CODEC_PAGE_3);
{
codec_readRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1);
codec_writeRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1, 0x98);
}
codec_setPage(CODEC_PAGE_255);
{
codec_writeRegister(CODEC_REG_PAGE255_BACKWARDS_COMPATIBILITY_MODE, CODEC_PAGE255_BACKWARDS_COMPATIBILITY_MODE_ON);
}
pmic_setAmplifierEnable(true);
}
bool DSMode::ShouldUseVolumeFix(u32 gameCode) const
{
switch (gameCode & 0xFFFFFF)
{
case GAMECODE_NO_REGION("A3T"):
case GAMECODE_NO_REGION("A4U"):
case GAMECODE_NO_REGION("A5H"):
case GAMECODE_NO_REGION("A5I"):
case GAMECODE_NO_REGION("A8N"):
case GAMECODE_NO_REGION("ABJ"):
case GAMECODE_NO_REGION("ABN"):
case GAMECODE_NO_REGION("ABX"):
case GAMECODE_NO_REGION("ACC"):
case GAMECODE_NO_REGION("ACL"):
case GAMECODE_NO_REGION("ACZ"):
case GAMECODE_NO_REGION("ADA"):
case GAMECODE_NO_REGION("AHD"):
case GAMECODE_NO_REGION("AJU"):
case GAMECODE_NO_REGION("AKA"):
case GAMECODE_NO_REGION("AKE"):
case GAMECODE_NO_REGION("ALH"):
case GAMECODE_NO_REGION("AMH"):
case GAMECODE_NO_REGION("AN9"):
case GAMECODE_NO_REGION("ANR"):
case GAMECODE_NO_REGION("APA"):
case GAMECODE_NO_REGION("APY"):
case GAMECODE_NO_REGION("ART"):
case GAMECODE_NO_REGION("AV2"):
case GAMECODE_NO_REGION("AV3"):
case GAMECODE_NO_REGION("AV4"):
case GAMECODE_NO_REGION("AV5"):
case GAMECODE_NO_REGION("AV6"):
case GAMECODE_NO_REGION("AVI"):
case GAMECODE_NO_REGION("AVT"):
case GAMECODE_NO_REGION("AWH"):
case GAMECODE_NO_REGION("AWY"):
case GAMECODE_NO_REGION("AXB"):
case GAMECODE_NO_REGION("AXJ"):
case GAMECODE_NO_REGION("AY7"):
case GAMECODE_NO_REGION("AYK"):
case GAMECODE_NO_REGION("AZW"):
case GAMECODE_NO_REGION("CPU"):
case GAMECODE_NO_REGION("YB2"):
case GAMECODE_NO_REGION("YB3"):
case GAMECODE_NO_REGION("YBO"):
case GAMECODE_NO_REGION("YCH"):
case GAMECODE_NO_REGION("YCQ"):
case GAMECODE_NO_REGION("YFE"):
case GAMECODE_NO_REGION("YFS"):
case GAMECODE_NO_REGION("YG8"):
case GAMECODE_NO_REGION("YGD"):
case GAMECODE_NO_REGION("YKR"):
case GAMECODE_NO_REGION("YNZ"):
case GAMECODE_NO_REGION("YO9"):
case GAMECODE_NO_REGION("YON"):
case GAMECODE_NO_REGION("YRM"):
case GAMECODE_NO_REGION("YT3"):
case GAMECODE_NO_REGION("YW2"):
case GAMECODE_NO_REGION("YYK"):
{
return true;
}
default:
{
return false;
}
}
}

View File

@@ -0,0 +1,18 @@
#pragma once
/// @brief Class handling switching from DSi to DS mode.
class DSMode
{
public:
/// @brief Switches to DS mode for the game with the given \p gameCode.
/// @param gameCode The game code of the game to switch to DS mode for.
void SwitchToDSMode(u32 gameCode) const;
/// @brief Switches CODEC to DS mode for the game with the given \p gameCode.
/// @param gameCode The game code of the game to switch CODEC to DS mode for.
void SwitchToDSTouchAndSoundMode(u32 gameCode) const;
private:
void SwitchCodecToDSMode(u32 gameCode) const;
bool ShouldUseVolumeFix(u32 gameCode) const;
};

View File

@@ -0,0 +1,106 @@
#include "common.h"
#include <string.h>
#include "DldiDriver.h"
void DldiDriver::Relocate(u32 targetAddress)
{
u32 currentAddress = _driver->driverStartAddress;
if (currentAddress == targetAddress)
return;
u32 currentEndAddress = _driver->driverEndAddress;
if (_driver->fixFlags & DLDI_FIX_ALL)
{
LOG_WARNING("Dldi driver uses FIX_ALL flag");
u32* ptr = (u32*)((u8*)_driver + sizeof(dldi_header_t));
u32 size = currentEndAddress - currentAddress;
for (u32 i = 0; i < size; i += 4)
{
u32 word = *ptr;
if (currentAddress <= word && word < currentEndAddress)
*ptr = word - currentAddress + targetAddress;
ptr++;
}
}
else
{
// note that we are not going to do those fixes when we did
// FIX_ALL already, since it covers them already
if (_driver->fixFlags & DLDI_FIX_GLUE)
{
u32* ptr = (u32*)((u8*)_driver + _driver->glueStartAddress - currentAddress);
u32 size = _driver->glueEndAddress - _driver->glueStartAddress;
for (u32 i = 0; i < size; i += 4)
{
u32 word = *ptr;
if (currentAddress <= word && word < currentEndAddress)
*ptr = word - currentAddress + targetAddress;
ptr++;
}
}
if (_driver->fixFlags & DLDI_FIX_GOT)
{
u32* ptr = (u32*)((u8*)_driver + _driver->gotStartAddress - currentAddress);
u32 size = _driver->gotEndAddress - _driver->gotStartAddress;
for (u32 i = 0; i < size; i += 4)
{
u32 word = *ptr;
if (currentAddress <= word && word < currentEndAddress)
*ptr = word - currentAddress + targetAddress;
ptr++;
}
}
}
_driver->driverStartAddress = targetAddress;
_driver->driverEndAddress = _driver->driverEndAddress + targetAddress - currentAddress;
_driver->glueStartAddress = _driver->glueStartAddress + targetAddress - currentAddress;
_driver->glueEndAddress = _driver->glueEndAddress + targetAddress - currentAddress;
_driver->gotStartAddress = _driver->gotStartAddress + targetAddress - currentAddress;
_driver->gotEndAddress = _driver->gotEndAddress + targetAddress - currentAddress;
_driver->bssStartAddress = _driver->bssStartAddress + targetAddress - currentAddress;
_driver->bssEndAddress = _driver->bssEndAddress + targetAddress - currentAddress;
_driver->startupFuncAddress = _driver->startupFuncAddress + targetAddress - currentAddress;
_driver->isInsertedFuncAddress = _driver->isInsertedFuncAddress + targetAddress - currentAddress;
_driver->readSectorsFuncAddress = _driver->readSectorsFuncAddress + targetAddress - currentAddress;
_driver->writeSectorsFuncAddress = _driver->writeSectorsFuncAddress + targetAddress - currentAddress;
_driver->clearStatusFuncAddress = _driver->clearStatusFuncAddress + targetAddress - currentAddress;
_driver->shutdownFuncAddress = _driver->shutdownFuncAddress + targetAddress - currentAddress;
}
void DldiDriver::PrepareForUse()
{
if (_driver->fixFlags & DLDI_FIX_BSS)
{
memset((u8*)_driver + _driver->bssStartAddress - _driver->driverStartAddress, 0,
_driver->bssEndAddress - _driver->bssStartAddress);
}
}
bool DldiDriver::PatchTo(dldi_header_t* stub)
{
if (_driver->dldiMagic != DLDI_MAGIC)
return false;
u32 stubSize = stub->stubSize;
if (stubSize < _driver->driverSize)
{
LOG_ERROR("Dldi stub of size %d is not large enough for driver of size %d\n",
stubSize, _driver->driverSize);
return false;
}
u32 targetAddress = stub->driverStartAddress;
u32 driverSize = 1 << _driver->driverSize;
memcpy(stub, _driver, driverSize);
stub->stubSize = stubSize;
auto newDriver = DldiDriver(stub);
newDriver.Relocate(targetAddress);
newDriver.PrepareForUse();
return true;
}

View File

@@ -0,0 +1,86 @@
#pragma once
#include "dldiHeader.h"
/// @brief Class representing a DLDI driver.
class DldiDriver
{
dldi_header_t* _driver;
public:
explicit DldiDriver(dldi_header_t* driver)
: _driver(driver) { }
/// @brief Returns whether the driver has been relocated to its current ram address.
/// @return True if the driver is relocated to its current ram address, or false otherwise.
bool IsRelocated() const
{
return _driver->driverStartAddress == (u32)_driver;
}
/// @brief Relocates the driver to its current ram address.
void Relocate()
{
Relocate((u32)_driver);
}
/// @brief Relocates the driver to the given targetAddress.
/// @param targetAddress The address to relocate the driver to.
void Relocate(u32 targetAddress);
/// @brief Clears the bss area of the driver if needed, making it ready for use.
/// This method should only be used after placing the driver in a memory region
/// with enough space and relocating it.
void PrepareForUse();
/// @brief Patches this dldi driver into the given stub.
/// @param stub The stub to patch the dldi driver into.
/// @return \c true if patching was successful, or \c false otherwise.
bool PatchTo(dldi_header_t* stub);
/// @brief Calls the startup function of the DLDI driver and returns the result.
/// @return \c true if startup was successful, or \c false otherwise.
bool Startup()
{
return ((dldi_startup_func_t)_driver->startupFuncAddress)();
}
/// @brief Calls the is inserted function of the DLDI driver and returns the result.
/// @return \c true if the storage medium is inserted, or \c false otherwise.
bool IsInserted()
{
return ((dldi_inserted_func_t)_driver->isInsertedFuncAddress)();
}
/// @brief Reads sectors using this DLDI driver.
/// @param sector The sector to start reading at.
/// @param count The number of sectors to read.
/// @param dst The destination buffer.
/// @return \c true if reading was successful, or \c false otherwise.
bool ReadSectors(u32 sector, u32 count, void* dst)
{
return ((dldi_read_func_t)_driver->readSectorsFuncAddress)(sector, count, dst);
}
/// @brief Writes sectors using this DLDI driver.
/// @param sector The sector to start writing at.
/// @param count The number of sectors to write.
/// @param src The source buffer.
/// @return \c true if writing was successful, or \c false otherwise.
bool WriteSectors(u32 sector, u32 count, const void* src)
{
return ((dldi_write_func_t)_driver->writeSectorsFuncAddress)(sector, count, src);
}
/// @brief Calls the clear status function of the DLDI driver and returns the result.
/// @return \c true if clear status was successful, or \c false otherwise.
bool ClearStatus()
{
return ((dldi_clearstatus_func_t)_driver->clearStatusFuncAddress)();
}
/// @brief Calls the shutdown function of the DLDI driver and returns the result.
/// @return \c true if shutdown was successful, or \c false otherwise.
bool Shutdown()
{
return ((dldi_shutdown_func_t)_driver->shutdownFuncAddress)();
}
};

View File

@@ -0,0 +1,39 @@
#pragma once
#define DSI_DEVICELIST_ENTRY_FLAGS_DRIVE_SDMC 0
#define DSI_DEVICELIST_ENTRY_FLAGS_DRIVE_NAND 1
#define DSI_DEVICELIST_ENTRY_FLAGS_TYPE_PHYSICAL (0 << 3)
#define DSI_DEVICELIST_ENTRY_FLAGS_TYPE_FILE (1 << 3)
#define DSI_DEVICELIST_ENTRY_FLAGS_TYPE_FOLDER (2 << 3)
#define DSI_DEVICELIST_ENTRY_FLAGS_PARTITION_FIRST (0 << 5)
#define DSI_DEVICELIST_ENTRY_FLAGS_PARTITION_SECOND (1 << 5)
#define DSI_DEVICELIST_ENTRY_FLAGS_ENCRYPTED (1 << 7)
#define DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_READ (1 << 1)
#define DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_WRITE (1 << 2)
/// @brief Struct representing a DSi device list entry.
struct dsi_devicelist_entry_t
{
char driveLetter;
u8 flags;
u8 accessRights;
u8 padding;
char deviceName[16];
char path[64];
};
static_assert(sizeof(dsi_devicelist_entry_t) == 0x54, "Invalid dsi_devicelist_entry_t size.");
/// @brief Struct representing a DSi device list.
struct dsi_devicelist_t
{
dsi_devicelist_entry_t deviceList[11];
u8 padding[0x24];
char appFileName[64];
};
static_assert(sizeof(dsi_devicelist_t) == 0x400, "Invalid dsi_devicelist_t size.");

View File

@@ -0,0 +1,235 @@
#include "common.h"
#include <string.h>
#include <memory>
#include "DsiWareSaveArranger.h"
bool DsiWareSaveArranger::SetupDsiWareSave(const TCHAR* romPath, const nds_header_twl_t& romHeader, DsiWareSaveResult& result) const
{
char path[256];
strcpy(path, romPath);
if (!CreateDeviceListPath(path, result.romFilePath))
{
return false;
}
if (romHeader.twlPrivateSavSize != 0)
{
char* extension = strrchr(path, '.');
if (!extension)
extension = &path[strlen(path)];
extension[0] = '.';
extension[1] = 'p';
extension[2] = 'r';
extension[3] = 'v';
extension[4] = 0;
if (!SetupDsiWareSaveFile(path, romHeader.twlPrivateSavSize) ||
!CreateDeviceListPath(path, result.privateSavePath))
{
return false;
}
}
if (romHeader.twlPublicSavSize != 0)
{
strcpy(path, romPath);
char* extension = strrchr(path, '.');
if (!extension)
extension = &path[strlen(path)];
extension[0] = '.';
extension[1] = 'p';
extension[2] = 'u';
extension[3] = 'b';
extension[4] = 0;
if (!SetupDsiWareSaveFile(path, romHeader.twlPublicSavSize) ||
!CreateDeviceListPath(path, result.publicSavePath))
{
return false;
}
}
return true;
}
bool DsiWareSaveArranger::SetupDsiWareSaveFile(const TCHAR* savePath, u32 saveSize) const
{
if (saveSize == 0)
{
return true;
}
auto file = std::make_unique<FIL>();
if (f_open(file.get(), savePath, FA_OPEN_ALWAYS | FA_READ | FA_WRITE) != FR_OK)
{
LOG_FATAL("Failed to open or create save file\n");
return false;
}
u32 initialSize = f_size(file.get());
if (initialSize < saveSize)
{
if (f_lseek(file.get(), saveSize) != FR_OK ||
f_lseek(file.get(), 0) != FR_OK)
{
LOG_FATAL("Failed to create private save file\n");
return false;
}
auto fatHeader = CreateFatHeader(saveSize);
UINT bytesWritten = 0;
if (f_write(file.get(), fatHeader.get(), sizeof(fat_header_t), &bytesWritten) != FR_OK ||
bytesWritten != sizeof(fat_header_t))
{
LOG_FATAL("Failed to format private save file\n");
return false;
}
memset(fatHeader.get(), 0, 512);
while (f_tell(file.get()) < saveSize)
{
bytesWritten = 0;
if (f_write(file.get(), fatHeader.get(), 512, &bytesWritten) != FR_OK ||
bytesWritten != 512)
{
LOG_FATAL("Failed to format private save file\n");
return false;
}
}
}
f_close(file.get());
return true;
}
std::unique_ptr<DsiWareSaveArranger::fat_header_t> DsiWareSaveArranger::CreateFatHeader(u32 saveSize) const
{
// based on https://github.com/Epicpkmn11/NTM/blob/master/arm9/src/sav.c
const u32 maxSectors = saveSize >> 9;
u32 sectorCount = 1;
u32 secPerTrk = 1;
u32 numHeads = 1;
u32 sectorCountNext = 0;
while (sectorCountNext <= maxSectors)
{
sectorCountNext = secPerTrk * (numHeads + 1) * (numHeads + 1);
if (sectorCountNext <= maxSectors)
{
numHeads++;
sectorCount = sectorCountNext;
secPerTrk++;
sectorCountNext = secPerTrk * numHeads * numHeads;
if (sectorCountNext <= maxSectors)
{
sectorCount = sectorCountNext;
}
}
}
sectorCountNext = (secPerTrk + 1) * numHeads * numHeads;
if (sectorCountNext <= maxSectors)
{
secPerTrk++;
sectorCount = sectorCountNext;
}
u32 sectorsPerCluster;
u32 totalClusters;
if (sectorCount > 8192)
{
sectorsPerCluster = 8;
totalClusters = (sectorCount + 7) >> 3;
}
else if (sectorCount > 1024)
{
sectorsPerCluster = 4;
totalClusters = (sectorCount + 3) >> 2;
}
else
{
sectorsPerCluster = 1;
totalClusters = sectorCount;
}
u32 fatSizeInBytes = ((totalClusters + 1) >> 1) * 3; // 2 sectors -> 3 byte
auto fatHeader = std::make_unique<fat_header_t>();
fatHeader->jumpInstruction[0] = 0xE9;
fatHeader->jumpInstruction[1] = 0;
fatHeader->jumpInstruction[2] = 0;
memcpy(fatHeader->oemName, "MSWIN4.1", 8);
fatHeader->bytesPerSector = 512;
fatHeader->sectorsPerCluster = sectorsPerCluster;
fatHeader->reservedSectors = 1;
fatHeader->numberOfFats = 2;
fatHeader->rootEntries = saveSize < 0x8C000 ? 32 : 512;
fatHeader->totalSectorsSmall = sectorCount;
fatHeader->mediaType = 0xF8; // "hard drive"
fatHeader->fatSectorCount = (fatSizeInBytes + 511) >> 9;
fatHeader->sectorsPerTrack = secPerTrk;
fatHeader->numberOfHeads = numHeads;
fatHeader->hiddenSectors = 0;
fatHeader->totalSectorsLarge = 0;
fatHeader->diskNumber = 5;
fatHeader->BS_Reserved1 = 0;
fatHeader->bootSignature = 0x29;
fatHeader->volumeSerialNumber = 0x12345678;
memcpy(fatHeader->volumeLabel, "VOLUMELABEL", 11);
memcpy(fatHeader->fileSystemType, "FAT12 ", 8);
memset(fatHeader->bootCode, 0, sizeof(fatHeader->bootCode));
fatHeader->endOfSectorMarker = 0xAA55;
return fatHeader;
}
bool DsiWareSaveArranger::CreateDeviceListPath(TCHAR* savePath, char* deviceListPath) const
{
auto fileInfo = std::make_unique<FILINFO>();
strcpy(deviceListPath, "nand:/");
char* shortPath = deviceListPath + 6;
char* currentPathSegment = strchr(savePath, '/');
do
{
currentPathSegment = strchr(currentPathSegment + 1, '/');
if (currentPathSegment)
{
*currentPathSegment = 0;
}
if (f_stat(savePath, fileInfo.get()) != FR_OK)
{
LOG_DEBUG("Failed to create short path\n");
return false;
}
LOG_DEBUG("%s\n", savePath);
// Use fname when altname is empty to handle short filenames.
const char* nameToUse = fileInfo->altname[0]
? fileInfo->altname
: fileInfo->fname;
u32 length = strlen(nameToUse);
if (shortPath + length - deviceListPath >= 64)
{
LOG_DEBUG("Path too long\n");
return false;
}
strcpy(shortPath, nameToUse);
shortPath += length;
if (currentPathSegment)
{
if (shortPath + 1 - deviceListPath >= 64)
{
LOG_DEBUG("Path too long\n");
return false;
}
*shortPath++ = '/';
*currentPathSegment = '/';
}
} while (currentPathSegment);
LOG_DEBUG("%s\n", deviceListPath);
return true;
}

View File

@@ -0,0 +1,65 @@
#pragma once
#include <memory>
#include "fat/ff.h"
#include "ndsHeader.h"
/// @brief Struct holding the result of setting up DSiWare save data.
struct DsiWareSaveResult
{
/// @brief The short filename path of the private save file.
char privateSavePath[64];
/// @brief The short filename path of the public save file.
char publicSavePath[64];
/// @brief The short filename path of rom file.
char romFilePath[64];
};
/// @brief Class for setting up the save files for DSiWare roms.
class DsiWareSaveArranger
{
public:
/// @brief Sets up the save files for a DSiWare rom with the given \p romPath and \p romHeader.
/// @param romPath The path to the DSiWare rom to setup the save files for.
/// @param romHeader The header of the DSiWare rom.
/// @param result Struct in which the resulting paths are returned.
/// @return \c true when setting up the save was successful, or \c false otherwise.
bool SetupDsiWareSave(const TCHAR* romPath, const nds_header_twl_t& romHeader, DsiWareSaveResult& result) const;
private:
#pragma pack(push, 1)
/// @brief Struct representing a FAT header.
struct fat_header_t
{
u8 jumpInstruction[3];
u8 oemName[8];
u16 bytesPerSector;
u8 sectorsPerCluster;
u16 reservedSectors;
u8 numberOfFats;
u16 rootEntries;
u16 totalSectorsSmall;
u8 mediaType;
u16 fatSectorCount;
u16 sectorsPerTrack;
u16 numberOfHeads;
u32 hiddenSectors;
u32 totalSectorsLarge;
u8 diskNumber;
u8 BS_Reserved1;
u8 bootSignature;
u32 volumeSerialNumber;
u8 volumeLabel[11];
u8 fileSystemType[8];
u8 bootCode[448];
u16 endOfSectorMarker;
};
#pragma pack(pop)
static_assert(sizeof(fat_header_t) == 512, "Invalid size of fat_header_t.");
bool SetupDsiWareSaveFile(const TCHAR* savePath, u32 saveSize) const;
std::unique_ptr<fat_header_t> CreateFatHeader(u32 saveSize) const;
bool CreateDeviceListPath(TCHAR* savePath, char* deviceListPath) const;
};

View File

@@ -0,0 +1,39 @@
#pragma once
#include "common.h"
template <typename T, u32 PatternLength>
class InverseKmpMatcher
{
const T* _pattern;
u8 _prefixFunc[PatternLength];
public:
consteval InverseKmpMatcher(const T(&pattern)[PatternLength])
: _pattern(pattern)
{
_prefixFunc[0] = 0;
int k = 0;
for (u32 q = 1; q < PatternLength; q++)
{
while (k > 0 && pattern[k] != pattern[q])
k = _prefixFunc[k - 1];
if (pattern[k] == pattern[q])
k++;
_prefixFunc[q] = k;
}
}
int FindFirstOccurance(const T* data, u32 length) const
{
int q = 0;
for (u32 i = 0; i < length; i++)
{
while (q > 0 && ~_pattern[q] != data[i])
q = _prefixFunc[q - 1];
if (~_pattern[q] == data[i])
q++;
if (q == PatternLength)
return i - q + 1;
}
return -1;
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
#pragma once
#include "common.h"
#include "ndsHeader.h"
#include "DsiWareSaveArranger.h"
#include "BootMode.h"
struct dsi_devicelist_entry_t;
/// @brief Class for loading DS(i) roms.
class NdsLoader
{
public:
/// @brief Sets the \p romPath.
/// @param romPath The rom path.
void SetRomPath(const TCHAR* romPath)
{
_romPath = romPath;
}
/// @brief Sets the \p savePath to use.
/// @param savePath The save path to use.
void SetSavePath(const TCHAR* savePath)
{
_savePath = savePath;
}
/// @brief Sets the argv arguments to pass to the rom.
/// @param arguments The argv arguments.
/// @param argumentsLength The length of the argv arguments.
void SetArguments(const char* arguments, u32 argumentsLength)
{
_arguments = arguments;
_argumentsLength = argumentsLength;
}
/// @brief Loads the rom according to the specified \p bootMode.
/// @param bootMode The boot mode.
void Load(BootMode bootMode);
private:
FIL _romFile;
const TCHAR* _romPath;
const TCHAR* _savePath;
u32 _argumentsLength = 0;
const char* _arguments = nullptr;
nds_header_twl_t _romHeader;
DsiWareSaveResult _dsiwareSaveResult;
bool IsCloneBootRom(u32 romOffset);
void ApplyArm7Patches();
void SetupSharedMemory(u32 cardId, u32 agbMem, u32 resetParam, u32 romOffset, u32 bootType);
void LoadFirmwareUserSettings();
bool ShouldAttemptDldiPatch();
void ClearMainMemory();
void CreateRomClusterTable();
bool TryLoadRomHeader(u32 romOffset);
void HandleCardSave();
void HandleAntiPiracy();
void RemapWram();
bool TryLoadArm9();
bool TryLoadArm9i();
bool TryDecryptArm9i();
bool TryDecryptArm7i();
bool TryLoadArm7();
bool TryLoadArm7i();
void HandleDldiPatching();
void StartRom(BootMode bootMode);
void SetupTwlConfig();
void SetDeviceListEntry(dsi_devicelist_entry_t& deviceListEntry,
char driveLetter, const char* deviceName, const char* path, u8 flags, u8 accessRights);
void SetupDsiDeviceList();
void InsertArgv();
bool TrySetupDsiWareSave();
bool TryDecryptSecureArea();
};

View File

@@ -0,0 +1,60 @@
#include "common.h"
#include <memory>
#include "PicoLoaderArranger.h"
#define PICO_LOADER_9_PATH "/_pico/picoLoader9.bin"
#define PICO_LOADER_7_PATH "/_pico/picoLoader7.bin"
bool PicoLoaderArranger::SetupPicoLoaderInfo(loader_info_t* info) const
{
auto file = std::make_unique<FIL>();
if (f_open(file.get(), PICO_LOADER_9_PATH, FA_OPEN_EXISTING | FA_READ) != FR_OK)
{
LOG_FATAL("Failed to open picoLoader9.bin\n");
return false;
}
DWORD* clusterTab = (DWORD*)info->clusterMap9;
clusterTab[0] = sizeof(info->clusterMap9) / sizeof(u32);
file->cltbl = clusterTab;
FRESULT seekResult = f_lseek(file.get(), CREATE_LINKMAP);
if (seekResult != FR_OK)
{
LOG_FATAL("Failed to make picoLoader9 cluster table. Result: %d\n", seekResult);
return false;
}
info->clusterShift = __builtin_ctz(file->obj.fs->csize);
info->picoLoaderBootDrive = gLoaderHeader.bootDrive;
info->database = file->obj.fs->database;
if (f_close(file.get()) != FR_OK)
{
LOG_FATAL("Failed to close picoLoader9 file\n");
return false;
}
if (f_open(file.get(), PICO_LOADER_7_PATH, FA_OPEN_EXISTING | FA_READ) != FR_OK)
{
LOG_FATAL("Failed to open picoLoader7.bin\n");
return false;
}
clusterTab = (DWORD*)info->clusterMap7;
clusterTab[0] = sizeof(info->clusterMap7) / sizeof(u32);
file->cltbl = clusterTab;
seekResult = f_lseek(file.get(), CREATE_LINKMAP);
if (seekResult != FR_OK)
{
LOG_FATAL("Failed to make picoLoader7 cluster table. Result: %d\n", seekResult);
return false;
}
if (f_close(file.get()) != FR_OK)
{
LOG_FATAL("Failed to close picoLoader7 file\n");
return false;
}
return true;
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "LoaderInfo.h"
/// @brief Class for setting up \see loader_info_t.
class PicoLoaderArranger
{
public:
/// @brief Sets up the given \p info for being able to reload Pico Loader at a later time.
/// @param info The \see loader_info_t struct to fill.
/// @return \c true when setting up was successful, or \c false otherwise.
bool SetupPicoLoaderInfo(loader_info_t* info) const;
};

View File

@@ -0,0 +1,22 @@
#include "common.h"
#include <algorithm>
#include "SaveList.h"
const SaveListEntry* SaveList::FindEntry(u32 gameCode)
{
if (_count != 0)
{
const auto gameEntry = std::lower_bound(_entries.get(), _entries.get() + _count, gameCode,
[] (const SaveListEntry& entry, u32 value)
{
return entry.GetGameCode() < value;
});
if (gameEntry != _entries.get() + _count && gameEntry->GetGameCode() == gameCode)
{
return gameEntry;
}
}
return nullptr;
}

View File

@@ -0,0 +1,74 @@
#pragma once
#include <memory>
#include "common.h"
#include "CardSaveType.h"
/// @brief Class representing a save list entry.
class SaveListEntry
{
u32 gameCode;
u8 saveType; // see CardSaveType
u8 saveSize; // 0 or 1 << x
u8 reserved[2]; // for possible future use
public:
u32 GetGameCode() const { return gameCode; }
CardSaveType GetSaveType() const { return static_cast<CardSaveType>(saveType); }
u32 GetSaveSize() const { return saveSize == 0 ? 0 : (1u << saveSize); }
void Dump() const
{
const char* saveType;
switch (GetSaveType())
{
case CardSaveType::None:
{
saveType = "none";
break;
}
case CardSaveType::Eeprom:
{
saveType = "eeprom";
break;
}
case CardSaveType::Flash:
{
saveType = "flash";
break;
}
case CardSaveType::Nand:
{
saveType = "nand";
break;
}
default:
{
saveType = "unknown";
break;
}
}
LOG_DEBUG("%c%c%c%c - %s - 0x%X\n",
gameCode & 0xFF, (gameCode >> 8) & 0xFF, (gameCode >> 16) & 0xFF, gameCode >> 24,
saveType,
GetSaveSize());
}
};
static_assert(sizeof(SaveListEntry) == 8, "Invalid sizeof(SaveListEntry)");
/// @brief Class representing a save list.
class SaveList
{
public:
SaveList(std::unique_ptr<const SaveListEntry[]> entries, u32 count)
: _entries(std::move(entries)), _count(count) { }
/// @brief Attempts to find the save list entry for the given \p gameCode.
/// @param gameCode The game code to search for.
/// @return A pointer to the \see SaveListEntry when found, or \c nullptr otherwise.
const SaveListEntry* FindEntry(u32 gameCode);
private:
std::unique_ptr<const SaveListEntry[]> _entries;
u32 _count;
};

View File

@@ -0,0 +1,24 @@
#include "common.h"
#include "SaveListFactory.h"
SaveList* SaveListFactory::CreateFromFile(const TCHAR *path)
{
FIL file;
if (f_open(&file, path, FA_OPEN_EXISTING | FA_READ) != FR_OK)
{
LOG_FATAL("Failed to open save list file\n");
return nullptr;
}
const u32 entryCount = f_size(&file) / sizeof(SaveListEntry);
auto entries = std::make_unique_for_overwrite<SaveListEntry[]>(entryCount);
UINT bytesRead = 0;
FRESULT result = f_read(&file, entries.get(), entryCount * sizeof(SaveListEntry), &bytesRead);
if (result != FR_OK || bytesRead != entryCount * sizeof(SaveListEntry))
{
LOG_FATAL("Failed to read save list file\n");
return nullptr;
}
f_close(&file);
return new SaveList(std::move(entries), entryCount);
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "SaveList.h"
/// @brief Factory for creating \see SaveList instances.
class SaveListFactory
{
public:
/// @brief Creates a \see SaveList instance from the file at the given \p path.
/// @param path The save list file path.
/// @return A pointer to the constructed \see SaveList instance, or \c nullptr if construction failed.
SaveList* CreateFromFile(const TCHAR* path);
};

View File

@@ -0,0 +1,104 @@
#pragma once
#include <nds/ndstypes.h>
#define DATA32_SUPPORT
#define SDMMC_BASE 0x04004800
#define REG_SDCMD 0x00
#define REG_SDPORTSEL 0x02
#define REG_SDCMDARG 0x04
#define REG_SDCMDARG0 0x04
#define REG_SDCMDARG1 0x06
#define REG_SDSTOP 0x08
#define REG_SDRESP 0x0c
#define REG_SDBLKCOUNT 0x0a
#define REG_SDRESP0 0x0c
#define REG_SDRESP1 0x0e
#define REG_SDRESP2 0x10
#define REG_SDRESP3 0x12
#define REG_SDRESP4 0x14
#define REG_SDRESP5 0x16
#define REG_SDRESP6 0x18
#define REG_SDRESP7 0x1a
#define REG_SDSTATUS0 0x1c
#define REG_SDSTATUS1 0x1e
#define REG_SDIRMASK0 0x20
#define REG_SDIRMASK1 0x22
#define REG_SDCLKCTL 0x24
#define REG_SDBLKLEN 0x26
#define REG_SDOPT 0x28
#define REG_SDFIFO 0x30
#define REG_SDDATACTL 0xd8
#define REG_SDRESET 0xe0
#define REG_SDPROTECTED 0xf6 //bit 0 determines if sd is protected or not?
#define REG_SDDATACTL32 0x100
#define REG_SDBLKLEN32 0x104
#define REG_SDBLKCOUNT32 0x108
#define REG_SDFIFO32 0x10C
#define REG_CLK_AND_WAIT_CTL 0x138
#define REG_RESET_SDIO 0x1e0
//The below defines are from linux kernel drivers/mmc tmio_mmc.h.
/* Definitions for values the CTRL_STATUS register can take. */
#define TMIO_STAT0_CMDRESPEND 0x0001
#define TMIO_STAT0_DATAEND 0x0004
#define TMIO_STAT0_CARD_REMOVE 0x0008
#define TMIO_STAT0_CARD_INSERT 0x0010
#define TMIO_STAT0_SIGSTATE 0x0020
#define TMIO_STAT0_WRPROTECT 0x0080
#define TMIO_STAT0_CARD_REMOVE_A 0x0100
#define TMIO_STAT0_CARD_INSERT_A 0x0200
#define TMIO_STAT0_SIGSTATE_A 0x0400
#define TMIO_STAT1_CMD_IDX_ERR 0x0001
#define TMIO_STAT1_CRCFAIL 0x0002
#define TMIO_STAT1_STOPBIT_ERR 0x0004
#define TMIO_STAT1_DATATIMEOUT 0x0008
#define TMIO_STAT1_RXOVERFLOW 0x0010
#define TMIO_STAT1_TXUNDERRUN 0x0020
#define TMIO_STAT1_CMDTIMEOUT 0x0040
#define TMIO_STAT1_RXRDY 0x0100
#define TMIO_STAT1_TXRQ 0x0200
#define TMIO_STAT1_ILL_FUNC 0x2000
#define TMIO_STAT1_CMD_BUSY 0x4000
#define TMIO_STAT1_ILL_ACCESS 0x8000
#define SDMC_NORMAL 0x00000000
#define SDMC_ERR_COMMAND 0x00000001
#define SDMC_ERR_CRC 0x00000002
#define SDMC_ERR_END 0x00000004
#define SDMC_ERR_TIMEOUT 0x00000008
#define SDMC_ERR_FIFO_OVF 0x00000010
#define SDMC_ERR_FIFO_UDF 0x00000020
#define SDMC_ERR_WP 0x00000040
#define SDMC_ERR_ABORT 0x00000080
#define SDMC_ERR_FPGA_TIMEOUT 0x00000100
#define SDMC_ERR_PARAM 0x00000200
#define SDMC_ERR_R1_STATUS 0x00000800
#define SDMC_ERR_NUM_WR_SECTORS 0x00001000
#define SDMC_ERR_RESET 0x00002000
#define SDMC_ERR_ILA 0x00004000
#define SDMC_ERR_INFO_DETECT 0x00008000
#define SDMC_STAT_ERR_UNKNOWN 0x00080000
#define SDMC_STAT_ERR_CC 0x00100000
#define SDMC_STAT_ERR_ECC_FAILED 0x00200000
#define SDMC_STAT_ERR_CRC 0x00800000
#define SDMC_STAT_ERR_OTHER 0xf9c70008
#define TMIO_MASK_ALL 0x837f031d
#define TMIO_MASK_GW (TMIO_STAT1_ILL_ACCESS | TMIO_STAT1_CMDTIMEOUT | TMIO_STAT1_TXUNDERRUN | TMIO_STAT1_RXOVERFLOW | \
TMIO_STAT1_DATATIMEOUT | TMIO_STAT1_STOPBIT_ERR | TMIO_STAT1_CRCFAIL | TMIO_STAT1_CMD_IDX_ERR)
#define TMIO_MASK_READOP (TMIO_STAT1_RXRDY | TMIO_STAT1_DATAEND)
#define TMIO_MASK_WRITEOP (TMIO_STAT1_TXRQ | TMIO_STAT1_DATAEND)

View File

@@ -0,0 +1,153 @@
#include "common.h"
#include <libtwl/sys/twlScfg.h>
#include <libtwl/sys/twlFuse.h>
#include <libtwl/dma/dmaTwl.h>
#include "TwlAes.h"
#define KEY_SLOT_MODULE 0
#define KEY_SLOT_NAND 3
#define MODULE_KEYX_NINT 0x746E694E // Nint
#define MODULE_KEYX_ENDO 0x6F646E65 // endo
#define NAND_KEYX_DSI_XOR_LO 0x24EE6906
#define NAND_KEYX_DSI_XOR_HI 0xE65B601D
#define NAND_KEYX_3DS_NINT 0x544E494E // NINT
#define NAND_KEYX_3DS_ENDO 0x4F444E45 // ENDO
#define NAND_KEYY_WORD_3 0xE1A00005 // mov r0, r5
#define DMA_CHANNEL_AES_OUT 0
#define DMA_CHANNEL_AES_IN 1
void TwlAes::SetupAes(const nds_header_twl_t* romHeader) const
{
// ensure AES is enabled
REG_SCFG_EXT |= SCFG_EXT_AES;
REG_SCFG_CLK |= SCFG_CLK_AES;
REG_AES_CNT = 0;
aes_reset();
aes_reset();
aes_waitKeyBusy();
SetupModuleKeyXY(romHeader);
SetupNandKeyX();
aes_waitKeyBusy();
(&REG_AES_SEED0)[KEY_SLOT_NAND * 3].words[3] = NAND_KEYY_WORD_3;
}
void TwlAes::DecryptModuleAes(void* data, u32 length, const aes_u128_t* iv) const
{
REG_AES_CNT = 0;
aes_reset();
aes_reset();
aes_waitKeyBusy();
aes_setKeySlot(0);
aes_waitKeyBusy();
u32 offset = 0;
while (length > 0)
{
REG_AES_CNT = 0;
aes_reset();
u32 blockLength = std::min<u32>(length, 0xFFFF0u);
auto ctr = *iv;
((u64*)&ctr)[1] += __builtin_add_overflow(((u64*)&ctr)[0], offset >> 4, &((u64*)&ctr)[0]);
aes_setInitializationVector(&ctr);
aes_setPayloadBlockCount(blockLength >> 4);
LOG_DEBUG("%p\n", (u8*)data + offset);
dma_twl_config_t inputDmaConfig
{
.src = (u8*)data + offset,
.dst = (void*)&REG_AES_IFIFO,
.totalWordCount = blockLength >> 2,
.wordCount = 4,
.blockInterval = NDMABCNT_INTERVAL(8),
.fillData = 0,
.control = NDMACNT_DST_MODE_FIXED | NDMACNT_SRC_MODE_INCREMENT |
NDMACNT_PHYSICAL_COUNT_4 | NDMACNT_MODE_AES_IN | NDMACNT_ENABLE
};
dma_twlSetParams(DMA_CHANNEL_AES_IN, &inputDmaConfig);
dma_twl_config_t outputDmaConfig
{
.src = (const void*)&REG_AES_OFIFO,
.dst = (u8*)data + offset,
.totalWordCount = blockLength >> 2,
.wordCount = 4,
.blockInterval = NDMABCNT_INTERVAL(8),
.fillData = 0,
.control = NDMACNT_SRC_MODE_FIXED | NDMACNT_DST_MODE_INCREMENT |
NDMACNT_PHYSICAL_COUNT_4 | NDMACNT_MODE_AES_OUT | NDMACNT_ENABLE
};
dma_twlSetParams(DMA_CHANNEL_AES_OUT, &outputDmaConfig);
aes_start(
AES_CNT_INPUT_FIFO_DMA_SIZE_4_BYTES |
AES_CNT_OUTPUT_FIFO_DMA_SIZE_4_BYTES |
AES_CNT_MODE_CTR);
dma_twlWait(DMA_CHANNEL_AES_OUT);
aes_waitBusy();
offset += blockLength;
length -= blockLength;
}
}
void TwlAes::SetupModuleKeyXY(const nds_header_twl_t* romHeader) const
{
if ((romHeader->ntrHeader.twlFlags & (1 << 2)) || (romHeader->twlFlags2 & (1 << 7)))
{
// debug
aes_setKey(KEY_SLOT_MODULE, (const aes_u128_t*)romHeader);
}
else
{
// retail
aes_u128_t keyX { .words =
{
MODULE_KEYX_NINT,
MODULE_KEYX_ENDO,
romHeader->ntrHeader.gameCode,
__builtin_bswap32(romHeader->ntrHeader.gameCode)
}};
aes_setKeyXY(KEY_SLOT_MODULE, &keyX, (const aes_u128_t*)romHeader->arm9iSha1Hmac);
}
}
void TwlAes::SetupNandKeyX() const
{
if ((REG_SCFG_A7ROM & SCFG_A7ROM_DISABLE_FUSE) || (REG_FUSE_VERIFY & FUSE_VERIFY_ERROR))
{
// No access to the console id register. We'll assume nand key x is already setup.
return;
}
u32 consoleIdLo = REG_FUSE_ID0;
u32 consoleIdHi = REG_FUSE_ID1;
aes_u128_t keyX;
keyX.words[0] = consoleIdLo;
if (consoleIdLo & 0x80000000)
{
// 3DS
keyX.words[1] = NAND_KEYX_3DS_NINT;
keyX.words[2] = NAND_KEYX_3DS_ENDO;
}
else
{
// DSi
keyX.words[1] = consoleIdLo ^ NAND_KEYX_DSI_XOR_LO;
keyX.words[2] = consoleIdHi ^ NAND_KEYX_DSI_XOR_HI;
}
keyX.words[3] = consoleIdHi;
aes_setKeyX(KEY_SLOT_NAND, &keyX);
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include <libtwl/aes/aes.h>
#include "ndsHeader.h"
/// @brief Class handling AES crypto for DSi roms.
class TwlAes
{
public:
/// @brief Sets up AES for the DSi rom with the given \p romHeader.
/// @param romHeader The header of the DSi rom to setup AES for.
void SetupAes(const nds_header_twl_t* romHeader) const;
/// @brief Performs modcrypt decryption.
/// @param data The data to decrypt.
/// @param length The length of the data to decrypt.
/// @param iv The AES initialization vector.
void DecryptModuleAes(void* data, u32 length, const aes_u128_t* iv) const;
private:
void SetupModuleKeyXY(const nds_header_twl_t* romHeader) const;
void SetupNandKeyX() const;
};

View File

@@ -0,0 +1,52 @@
#pragma once
#include "common.h"
#define DLDI_MAGIC 0xBF8DA5ED
#define DLDI_DRIVER_MAGIC_NONE 0x49444C44
#define DLDI_FIX_ALL (1 << 0)
#define DLDI_FIX_GLUE (1 << 1)
#define DLDI_FIX_GOT (1 << 2)
#define DLDI_FIX_BSS (1 << 3)
#define DLDI_FEATURE_CANREAD (1 << 0)
#define DLDI_FEATURE_CANWRITE (1 << 1)
#define DLDI_FEATURE_SLOT_GBA (1 << 4)
#define DLDI_FEATURE_SLOT_NDS (1 << 5)
typedef bool (*dldi_startup_func_t)(void);
typedef bool (*dldi_inserted_func_t)(void);
typedef bool (*dldi_read_func_t)(u32 sector, u32 count, void* dst);
typedef bool (*dldi_write_func_t)(u32 sector, u32 count, const void* src);
typedef bool (*dldi_clearstatus_func_t)(void);
typedef bool (*dldi_shutdown_func_t)(void);
/// @brief Struct representing a DLDI header.
struct dldi_header_t
{
u32 dldiMagic;
u8 dldiString[8];
u8 dldiVersion;
u8 driverSize;
u8 fixFlags;
u8 stubSize;
u8 driverName[0x30];
u32 driverStartAddress;
u32 driverEndAddress;
u32 glueStartAddress;
u32 glueEndAddress;
u32 gotStartAddress;
u32 gotEndAddress;
u32 bssStartAddress;
u32 bssEndAddress;
u32 driverMagic;
u32 featureFlags;
u32 startupFuncAddress;
u32 isInsertedFuncAddress;
u32 readSectorsFuncAddress;
u32 writeSectorsFuncAddress;
u32 clearStatusFuncAddress;
u32 shutdownFuncAddress;
};
static_assert(sizeof(dldi_header_t) == 0x80, "Size of dldi_header_t incorrect");

View File

@@ -0,0 +1,43 @@
#include <nds.h>
#include "NitroEmulatorOutputStream.h"
#define ISND_DBGINFO_ADDRESS 0x027FFF60
#define ISND_DBGINFO_AGB_ADDR_OFFSET 0x1C
#define ISND_DBGINFO_AGB_ADDR (*(u32*)(ISND_DBGINFO_ADDRESS + ISND_DBGINFO_AGB_ADDR_OFFSET))
#define ISND_AGB_PRINT_ARM9_WRITE_PTR_OFFSET 0x90
#define ISND_AGB_PRINT_ARM7_WRITE_PTR_OFFSET 0x92
#define ISND_AGB_PRINT_ARM9_READ_PTR_OFFSET 0x94
#define ISND_AGB_PRINT_ARM7_READ_PTR_OFFSET 0x96
#define ISND_AGB_SOMETHING_OFFSET 0xFE
#define ISND_AGB_PRINT_ARM9_RING_OFFSET 0x8000
#define ISND_AGB_PRINT_ARM7_RING_OFFSET 0xC000
#define ISND_AGB_PRINT_RING_LENGTH 0x4000
NitroEmulatorOutputStream::NitroEmulatorOutputStream()
{
*(vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_SOMETHING_OFFSET) = 0x202;
}
void NitroEmulatorOutputStream::Write(const char* str)
{
char c;
vu16* ring = (vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_PRINT_ARM9_RING_OFFSET);
u32 writePtr = *(vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_PRINT_ARM9_WRITE_PTR_OFFSET);
u32 readPtr = *(vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_PRINT_ARM9_READ_PTR_OFFSET);
while ((c = *str++) != 0)
{
u32 newWritePtr = (writePtr + 1) & (ISND_AGB_PRINT_RING_LENGTH - 1);
while (newWritePtr == readPtr)
{
*(vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_PRINT_ARM9_WRITE_PTR_OFFSET) = writePtr;
readPtr = *(vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_PRINT_ARM9_READ_PTR_OFFSET);
}
if (writePtr & 1)
ring[writePtr >> 1] = (ring[writePtr >> 1] & 0xFF) | (c << 8);
else
ring[writePtr >> 1] = (ring[writePtr >> 1] & 0xFF00) | c;
writePtr = newWritePtr;
}
*(vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_PRINT_ARM9_WRITE_PTR_OFFSET) = writePtr;
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "logger/IOutputStream.h"
class NitroEmulatorOutputStream : public IOutputStream
{
public:
NitroEmulatorOutputStream();
void Write(const char* str) override;
void Flush() override { }
};

View File

@@ -0,0 +1,16 @@
#pragma once
#include "logger/IOutputStream.h"
#define REG_NOCASH_STRING_OUT (*(vu32*)0x04FFFA10)
#define REG_NOCASH_CHAR_OUT (*(vu32*)0x04FFFA1C)
class NocashOutputStream : public IOutputStream
{
public:
void Write(const char* str) override
{
REG_NOCASH_STRING_OUT = (u32)str;
}
void Flush() override { }
};

View File

@@ -0,0 +1,26 @@
#pragma once
#include "core/mini-printf.h"
#include <memory>
#include "logger/ILogger.h"
#include "logger/IOutputStream.h"
class PlainLogger : public ILogger
{
LogLevel _maxLogLevel;
std::unique_ptr<IOutputStream> _outputStream;
char _logBuffer[512];
public:
PlainLogger(LogLevel maxLogLevel, std::unique_ptr<IOutputStream> outputStream)
: _maxLogLevel(maxLogLevel), _outputStream(std::move(outputStream))
{ }
void LogV(LogLevel level, const char* fmt, va_list vlist) override
{
if (level > _maxLogLevel)
return;
mini_vsnprintf(_logBuffer, sizeof(_logBuffer), fmt, vlist);
_outputStream->Write(_logBuffer);
}
};

229
arm7/source/main.cpp Normal file
View File

@@ -0,0 +1,229 @@
#include "common.h"
#include <memory>
#include <string.h>
#include <libtwl/ipc/ipcFifo.h>
#include <libtwl/ipc/ipcSync.h>
#include <libtwl/i2c/i2cMcu.h>
#include <libtwl/sio/sioRtc.h>
#include <libtwl/sound/sound.h>
#include <libtwl/sound/soundChannel.h>
#include <libtwl/sound/soundCapture.h>
#include "core/Environment.h"
#include "logger/NitroEmulatorOutputStream.h"
#include "logger/PicoAgbAdapterOutputStream.h"
#include "logger/NocashOutputStream.h"
#include "logger/NullLogger.h"
#include "logger/PlainLogger.h"
#include "fat/dldi.h"
#include "loader/NdsLoader.h"
#include "sharedMemory.h"
#include "ndsHeader.h"
#include "globalHeap.h"
#include "mmc/tmio.h"
#define HANDSHAKE_PART0 0xA
#define HANDSHAKE_PART1 0xB
#define HANDSHAKE_PART2 0xC
#define HANDSHAKE_PART3 0xD
ILogger* gLogger;
FATFS gFatFs;
static NdsLoader sLoader;
static void initLogger()
{
std::unique_ptr<IOutputStream> outputStream;
if (Environment::IsIsNitroEmulator() && Environment::SupportsAgbSemihosting())
{
outputStream = std::make_unique<NitroEmulatorOutputStream>();
}
// else if (Environment::HasPicoAgbAdapter())
// {
// outputStream = std::make_unique<PicoAgbAdapterOutputStream>();
// }
else if (Environment::SupportsNocashPrint())
{
outputStream = std::make_unique<NocashOutputStream>();
}
else
{
gLogger = new NullLogger();
return;
}
gLogger = new PlainLogger(LogLevel::All, std::move(outputStream));
}
static bool mountDldi()
{
FRESULT res = f_mount(&gFatFs, "fat:", 1);
if (res != FR_OK)
{
LOG_ERROR("dldi mount failed: %d\n", res);
return false;
}
f_chdrive("fat:");
return true;
}
static bool mountDsiSd()
{
FRESULT res = f_mount(&gFatFs, "sd:", 1);
if (res != FR_OK)
{
LOG_ERROR("dsi sd mount failed: %d\n", res);
return false;
}
f_chdrive("sd:");
return true;
}
static bool mountAgbSemihosting()
{
FRESULT res = f_mount(&gFatFs, "pc2:", 1);
if (res != FR_OK)
{
LOG_ERROR("pc2 sd mount failed: %d\n", res);
return false;
}
f_chdrive("pc2:");
return true;
}
extern "C" void __libc_init_array();
static void handleSavePath()
{
if (gLoaderHeader.loadParams.savePath[0] == 0)
{
char* savePath = (char*)gLoaderHeader.loadParams.savePath;
strcpy(savePath, gLoaderHeader.loadParams.romPath);
char* extension = strrchr(savePath, '.');
if (!extension)
extension = &savePath[strlen(savePath)];
extension[0] = '.';
extension[1] = 's';
extension[2] = 'a';
extension[3] = 'v';
extension[4] = 0;
}
sLoader.SetSavePath(gLoaderHeader.loadParams.savePath);
}
static void clearSoundRegisters()
{
REG_SOUNDCNT = 0;
REG_SNDCAP0CNT = 0;
REG_SNDCAP1CNT = 0;
for (int i = 0; i < 16; i++)
{
REG_SOUNDxCNT(i) = 0;
REG_SOUNDxSAD(i) = 0;
REG_SOUNDxTMR(i) = 0;
REG_SOUNDxPNT(i) = 0;
REG_SOUNDxLEN(i) = 0;
}
}
static void initIpc()
{
ipc_clearSendFifo();
ipc_ackFifoError();
ipc_disableRecvFifoNotEmptyIrq();
ipc_enableFifo();
while (!ipc_isRecvFifoEmpty())
{
ipc_recvWordDirect();
}
ipc_setArm7SyncBits(HANDSHAKE_PART0);
while (ipc_getArm9SyncBits() != HANDSHAKE_PART0);
ipc_setArm7SyncBits(HANDSHAKE_PART1);
while (ipc_getArm9SyncBits() != HANDSHAKE_PART1);
ipc_setArm7SyncBits(HANDSHAKE_PART2);
while (ipc_getArm9SyncBits() != HANDSHAKE_PART2);
ipc_setArm7SyncBits(HANDSHAKE_PART3);
while (ipc_getArm9SyncBits() == HANDSHAKE_PART2);
}
extern "C" void loaderMain()
{
__libc_init_array();
clearSoundRegisters();
rtos_initIrq();
rtos_startMainThread();
initIpc();
bool dsiMode = ipc_getArm9SyncBits() == 1;
Environment::Initialize(dsiMode);
heap_init();
initLogger();
rtc_init(); // ensure rtc irqs are disabled
LOG_DEBUG("Pico Loader ARM7 started\n");
if (Environment::IsDsiMode())
{
// Let the mcu handle the power button
mcu_writeReg(MCU_REG_MODE, 0);
TMIO_init();
}
memset(&gFatFs, 0, sizeof(gFatFs));
bool multiboot = (gLoaderHeader.bootDrive & PLOAD_BOOT_DRIVE_MULTIBOOT_FLAG) != 0;
gLoaderHeader.bootDrive &= ~PLOAD_BOOT_DRIVE_MULTIBOOT_FLAG;
switch (gLoaderHeader.bootDrive)
{
case PLOAD_BOOT_DRIVE_DLDI:
{
if (dldi_init())
{
mountDldi();
}
break;
}
case PLOAD_BOOT_DRIVE_DSI_SD:
{
if (Environment::IsDsiMode())
{
mountDsiSd();
}
break;
}
case PLOAD_BOOT_DRIVE_AGB_SEMIHOSTING:
{
if (Environment::SupportsAgbSemihosting())
{
mountAgbSemihosting();
}
break;
}
}
if (multiboot)
{
LOG_DEBUG("Multiboot\n");
sLoader.Load(BootMode::Multiboot);
}
else if (((nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.romHeader)->arm7EntryAddress == (u32)gLoaderHeader.entryPoint)
{
LOG_DEBUG("Retail soft reset detected\n");
u32 originalArm7EntryAddress = ((nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.cardRomHeader)->arm7EntryAddress;
((nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.romHeader)->arm7EntryAddress = originalArm7EntryAddress;
sLoader.Load(BootMode::SdkResetSystem);
}
else
{
sLoader.SetRomPath(gLoaderHeader.loadParams.romPath);
handleSavePath();
sLoader.SetArguments(gLoaderHeader.loadParams.arguments, gLoaderHeader.loadParams.argumentsLength);
sLoader.Load(BootMode::Normal);
}
while (true);
}

235
arm7/source/mmc/mmc_spec.h Normal file
View File

@@ -0,0 +1,235 @@
#pragma once
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 profi200
// Based on JEDEC eMMC Card Product Standard V4.41.
#include "tmio.h"
// Controller specific macros. Add controller specific bits here.
// MMC_CMD_[response type]_[transfer type]
// Transfer type: R = read, W = write.
#define MMC_CMD_NONE(id) (CMD_RESP_NONE | (id))
#define MMC_CMD_R1(id) (CMD_RESP_R1 | (id))
#define MMC_CMD_R1b(id) (CMD_RESP_R1b | (id))
#define MMC_CMD_R2(id) (CMD_RESP_R2 | (id))
#define MMC_CMD_R3(id) (CMD_RESP_R3 | (id))
#define MMC_CMD_R4(id) (CMD_RESP_R4 | (id))
#define MMC_CMD_R5(id) (CMD_RESP_R5 | (id))
#define MMC_CMD_R1_R(id) (CMD_DATA_R | CMD_DATA_EN | CMD_RESP_R1 | (id))
#define MMC_CMD_R1_W(id) (CMD_DATA_W | CMD_DATA_EN | CMD_RESP_R1 | (id))
// Basic commands and read-stream command (class 0 and class 1).
#define MMC_GO_IDLE_STATE MMC_CMD_NONE(0u) // -, [31:0] 0x00000000 GO_IDLE_STATE, 0xF0F0F0F0 GO_PRE_IDLE_STATE, 0xFFFFFFFA BOOT_INITIATION.
#define MMC_SEND_OP_COND MMC_CMD_R3(1u) // R3, [31:0] OCR with-out busy.
#define MMC_ALL_SEND_CID MMC_CMD_R2(2u) // R2, [31:0] stuff bits.
#define MMC_SET_RELATIVE_ADDR MMC_CMD_R1(3u) // R1, [31:16] RCA [15:0] stuff bits.
#define MMC_SET_DSR MMC_CMD_NONE(4u) // -, [31:16] DSR [15:0] stuff bits.
#define MMC_SLEEP_AWAKE MMC_CMD_R1b(5u) // R1b, [31:16] RCA [15] Sleep/Awake [14:0] stuff bits.
#define MMC_SWITCH MMC_CMD_R1b(6u) // R1b, [31:26] Set to 0 [25:24] Access [23:16] Index [15:8] Value [7:3] Set to 0 [2:0] Cmd Set.
#define MMC_SELECT_CARD MMC_CMD_R1b(7u) // R1/R1b, [31:16] RCA [15:0] stuff bits. Note: "R1b while selecting from Disconnected State to Programming State."
#define MMC_DESELECT_CARD MMC_CMD_NONE(7u) // -, [31:16] RCA [15:0] stuff bits.
#define MMC_SEND_EXT_CSD MMC_CMD_R1_R(8u) // R1, [31:0] stuff bits.
#define MMC_SEND_CSD MMC_CMD_R2(9u) // R2, [31:16] RCA [15:0] stuff bits.
#define MMC_SEND_CID MMC_CMD_R2(10u) // R2, [31:16] RCA [15:0] stuff bits.
#define MMC_READ_DAT_UNTIL_STOP MMC_CMD_R1_R(11u) // R1, [31:0] data address.
#define MMC_STOP_TRANSMISSION MMC_CMD_R1b(12u) // R1/R1b, [31:16] RCA [15:1] stuff bits [0] HPI. Note: "RCA in CMD12 is used only if HPI bit is set." Note 2: "R1 for read cases and R1b for write cases."
#define MMC_SEND_STATUS MMC_CMD_R1(13u) // R1, [31:16] RCA [15:1] stuff bits [0] HPI.
#define MMC_BUSTEST_R MMC_CMD_R1_R(14u) // R1, [31:0] stuff bits.
#define MMC_GO_INACTIVE_STATE MMC_CMD_NONE(15u) // -, [31:16] RCA [15:0] stuff bits.
#define MMC_BUSTEST_W MMC_CMD_R1_W(19u) // R1, [31:0] stuff bits.
// Block-oriented read commands (class 2).
#define MMC_SET_BLOCKLEN MMC_CMD_R1(16u) // R1, [31:0] block length.
#define MMC_READ_SINGLE_BLOCK MMC_CMD_R1_R(17u) // R1, [31:0] data address.
#define MMC_READ_MULTIPLE_BLOCK MMC_CMD_R1_R(18u) // R1, [31:0] data address.
// Stream write commands (class 3).
#define MMC_WRITE_DAT_UNTIL_STOP MMC_CMD_R1_W(20u) // R1, [31:0] data address.
// Block-oriented write commands (class 4).
#define MMC_SET_BLOCK_COUNT MMC_CMD_R1(23u) // R1, [31] Reliable Write Request [30:16] set to 0 [15:0] number of blocks.
#define MMC_WRITE_BLOCK MMC_CMD_R1_W(24u) // R1, [31:0] data address.
#define MMC_WRITE_MULTIPLE_BLOCK MMC_CMD_R1_W(25u) // R1, [31:0] data address.
#define MMC_PROGRAM_CID MMC_CMD_R1_W(26u) // R1, [31:0] stuff bits.
#define MMC_PROGRAM_CSD MMC_CMD_R1_W(27u) // R1, [31:0] stuff bits.
// Block-oriented write protection commands (class 6).
#define MMC_SET_WRITE_PROT MMC_CMD_R1b(28u) // R1b, [31:0] data address.
#define MMC_CLR_WRITE_PROT MMC_CMD_R1b(29u) // R1b, [31:0] data address.
#define MMC_SEND_WRITE_PROT MMC_CMD_R1_R(30u) // R1, [31:0] write protect data address.
#define MMC_SEND_WRITE_PROT_TYPE MMC_CMD_R1_R(31u) // R1, [31:0] write protect data address.
// Erase commands (class 5).
#define MMC_ERASE_GROUP_START MMC_CMD_R1(35u) // R1, [31:0] data address.
#define MMC_ERASE_GROUP_END MMC_CMD_R1(36u) // R1, [31:0] data address.
#define MMC_ERASE MMC_CMD_R1b(38u) // R1b, [31] Secure request [30:16] set to 0 [15] Force Garbage Collect request [14:1] set to 0 [0] Identify Write block for Erase.
// I/O mode commands (class 9).
#define MMC_FAST_IO MMC_CMD_R4(39u) // R4, [31:16] RCA [15:15] register write flag [14:8] register address [7:0] register data.
#define MMC_GO_IRQ_STATE MMC_CMD_R5(40u) // R5, [31:0] stuff bits.
// Lock card commands (class 7).
#define MMC_LOCK_UNLOCK MMC_CMD_R1_W(42u) // R1, [31:0] stuff bits.
// Application-specific commands (class 8).
#define MMC_APP_CMD MMC_CMD_R1(55u) // R1, [31:16] RCA [15:0] stuff bits.
#define MMC_GEN_CMD_R MMC_CMD_R1_R(56u) // R1, [31:1] stuff bits [0] RD/WR = 1.
#define MMC_GEN_CMD_W MMC_CMD_R1_W(56u) // R1, [31:1] stuff bits [0] RD/WR = 0.
// 7.13 Card status.
// Type:
// E: Error bit.
// S: Status bit.
// R: Detected and set for the actual command response.
// X: Detected and set during command execution. The host can get the status by issuing a command with R1 response.
//
// Clear Condition:
// A: These bits are persistent, they are set and cleared in accordance with the card status.
// B: These bits are cleared as soon as the response (reporting the error) is sent out.
#define MMC_R1_APP_CMD (1u<<5) // S R A, The card will expect ACMD, or indication that the command has been interpreted as ACMD.
#define MMC_R1_URGENT_BKOPS (1u<<6) // S R A, If set, device needs to perform backgroundoperations urgently. Host can check EXT_CSD field BKOPS_STATUS for the detailed level.
#define MMC_R1_SWITCH_ERROR (1u<<7) // E X B, If set, the card did not switch to the expected mode as requested by the SWITCH command.
#define MMC_R1_READY_FOR_DATA (1u<<8) // S R A, Corresponds to buffer empty signalling on the bus.
#define MMC_R1_STATE_IDLE (0u<<9) // S R A
#define MMC_R1_STATE_READY (1u<<9) // S R A
#define MMC_R1_STATE_IDENT (2u<<9) // S R A
#define MMC_R1_STATE_STBY (3u<<9) // S R A
#define MMC_R1_STATE_TRAN (4u<<9) // S R A
#define MMC_R1_STATE_DATA (5u<<9) // S R A
#define MMC_R1_STATE_RCV (6u<<9) // S R A
#define MMC_R1_STATE_PRG (7u<<9) // S R A
#define MMC_R1_STATE_DIS (8u<<9) // S R A
#define MMC_R1_STATE_BTST (9u<<9) // S R A
#define MMC_R1_STATE_SLP (10u<<9) // S R A
#define MMC_R1_ERASE_RESET (1u<<13) // E R B, An erase sequence was cleared before executing because an out of erase sequence command was received (commands other than CMD35, CMD36, CMD38 or CMD13.
#define MMC_R1_WP_ERASE_SKIP (1u<<15) // E X B, Only partial address space was erased due to existing write protected blocks.
#define MMC_R1_CXD_OVERWRITE (1u<<16) // E X B, Can be either one of the following errors: - The CID register has been already written and can not be overwritten - The read only section of the CSD does not match the card content. - An attempt to reverse the copy (set as original) or permanent WP (unprotected) bits was made.
#define MMC_R1_OVERRUN (1u<<17) // E X B, The card could not sustain data programming in stream write mode.
#define MMC_R1_UNDERRUN (1u<<18) // E X B, The card could not sustain data transfer in stream read mode.
#define MMC_R1_ERROR (1u<<19) // E X B, (Undefined by the standard) A generic card error related to the (and detected during) execution of the last host command (e.g. read or write failures).
#define MMC_R1_CC_ERROR (1u<<20) // E R B, (Undefined by the standard) A card error occurred, which is not related to the host command.
#define MMC_R1_CARD_ECC_FAILED (1u<<21) // E X B, Card internal ECC was applied but failed to correct the data.
#define MMC_R1_ILLEGAL_COMMAND (1u<<22) // E R B, Command not legal for the card state.
#define MMC_R1_COM_CRC_ERROR (1u<<23) // E R B, The CRC check of the previous command failed.
#define MMC_R1_LOCK_UNLOCK_FAILED (1u<<24) // E X B, Set when a sequence or password error has been detected in lock/unlock card command.
#define MMC_R1_CARD_IS_LOCKED (1u<<25) // S R A, When set, signals that the card is locked by the host.
#define MMC_R1_WP_VIOLATION (1u<<26) // E X B, Attempt to program a write protected block.
#define MMC_R1_ERASE_PARAM (1u<<27) // E X B, An invalid selection of erase groups for erase occurred.
#define MMC_R1_ERASE_SEQ_ERROR (1u<<28) // E R B, An error in the sequence of erase commands occurred.
#define MMC_R1_BLOCK_LEN_ERROR (1u<<29) // E R B, Either the argument of a SET_BLOCKLEN command exceeds the maximum value allowed for the card, or the previously defined block length is illegal for the current command (e.g. the host issues a write command, the current block length is smaller than the cards maximum and write partial blocks is not allowed).
#define MMC_R1_ADDRESS_MISALIGN (1u<<30) // E R/X B, The command s address argument (in accordance with the currently set block length) positions the first data block misaligned to the card physical blocks. A multiple block read/write operation (although started with a valid address/blocklength combination) is attempting to read or write a data block which does not align with the physical blocks of the card.
#define MMC_R1_ADDRESS_OUT_OF_RANGE (1u<<31) // E R/X B, The commands address argument was out of the allowed range for this card. A multiple block or stream read/write operation is (although started in a valid address) attempting to read or write beyond the card capacity.
#define MMC_R1_ERR_ALL (MMC_R1_ADDRESS_OUT_OF_RANGE | MMC_R1_ADDRESS_MISALIGN | \
MMC_R1_BLOCK_LEN_ERROR | MMC_R1_ERASE_SEQ_ERROR | \
MMC_R1_ERASE_PARAM | MMC_R1_WP_VIOLATION | MMC_R1_LOCK_UNLOCK_FAILED | \
MMC_R1_COM_CRC_ERROR | MMC_R1_ILLEGAL_COMMAND | MMC_R1_CARD_ECC_FAILED | \
MMC_R1_CC_ERROR | MMC_R1_ERROR | MMC_R1_UNDERRUN | MMC_R1_OVERRUN | \
MMC_R1_CXD_OVERWRITE | MMC_R1_WP_ERASE_SKIP | MMC_R1_ERASE_RESET | \
MMC_R1_SWITCH_ERROR)
// 8.1 OCR register.
// Same bits for CMD1 argument.
#define MMC_OCR_1_7_1_95V (1u<<7) // 1.701.95V.
#define MMC_OCR_2_0_2_1V (1u<<8) // 2.0-2.1V.
#define MMC_OCR_2_1_2_2V (1u<<9) // 2.1-2.2V.
#define MMC_OCR_2_2_2_3V (1u<<10) // 2.2-2.3V.
#define MMC_OCR_2_3_2_4V (1u<<11) // 2.3-2.4V.
#define MMC_OCR_2_4_2_5V (1u<<12) // 2.4-2.5V.
#define MMC_OCR_2_5_2_6V (1u<<13) // 2.5-2.6V.
#define MMC_OCR_2_6_2_7V (1u<<14) // 2.6-2.7V.
#define MMC_OCR_2_7_2_8V (1u<<15) // 2.7-2.8V.
#define MMC_OCR_2_8_2_9V (1u<<16) // 2.8-2.9V.
#define MMC_OCR_2_9_3_0V (1u<<17) // 2.9-3.0V.
#define MMC_OCR_3_0_3_1V (1u<<18) // 3.0-3.1V.
#define MMC_OCR_3_1_3_2V (1u<<19) // 3.1-3.2V.
#define MMC_OCR_3_2_3_3V (1u<<20) // 3.2-3.3V.
#define MMC_OCR_3_3_3_4V (1u<<21) // 3.3-3.4V.
#define MMC_OCR_3_4_3_5V (1u<<22) // 3.4-3.5V.
#define MMC_OCR_3_5_3_6V (1u<<23) // 3.5-3.6V.
#define MMC_OCR_BYTE_MODE (0u<<29) // Access mode = byte mode.
#define MMC_OCR_SECT_MODE (2u<<29) // Access mode = sector mode.
#define MMC_OCR_READY (1u<<31) // Card power up status bit (busy). 0 = busy.
// 7.6.1 Command sets and extended settings.
#define MMC_SWITCH_ACC_CMD_SET (0u)
#define MMC_SWITCH_ACC_SET_BITS (1u)
#define MMC_SWITCH_ACC_CLR_BITS (2u)
#define MMC_SWITCH_ACC_WR_BYTE (3u)
#define MMC_SWITCH_ARG(acc, idx, val, cmdSet) (((acc)&3u)<<24 | ((idx)&0xFFu)<<16 | ((val)&0xFFu)<<8 | ((cmdSet)&7u))
// 8.4 Extended CSD register.
// size in bytes, access, description.
#define EXT_CSD_SEC_BAD_BLK_MGMNT (134u) // 1, R/W, Bad Block Management mode.
#define EXT_CSD_ENH_START_ADDR (136u) // 4, R/W, Enhanced User Data Start Address.
#define EXT_CSD_ENH_SIZE_MULT (140u) // 3, R/W, Enhanced User Data Area Size.
#define EXT_CSD_GP_SIZE_MULT (143u) // 12, R/W, General Purpose Partition Size.
#define EXT_CSD_PARTITION_SETTING_COMPLETED (155u) // 1, R/W, Paritioning Setting.
#define EXT_CSD_PARTITIONS_ATTRIBUTE (156u) // 1, R/W, Partitions attribute.
#define EXT_CSD_MAX_ENH_SIZE_MULT (157u) // 3, R, Max Enhanced Area Size.
#define EXT_CSD_PARTITIONING_SUPPORT (160u) // 1, R, Partitioning Support.
#define EXT_CSD_HPI_MGMT (161u) // 1, R/W/E_P, HPI management.
#define EXT_CSD_RST_n_FUNCTION (162u) // 1, R/W, H/W reset function.
#define EXT_CSD_BKOPS_EN (163u) // 1, R/W, Enable background operations handshake.
#define EXT_CSD_BKOPS_START (164u) // 1, W/E_P, Manually start background operations.
#define EXT_CSD_WR_REL_PARAM (166u) // 1, R, Write reliability parameter register.
#define EXT_CSD_WR_REL_SET (167u) // 1, R/W, Write reliability setting register.
#define EXT_CSD_RPMB_SIZE_MULT (168u) // 1, R, RPMB Size.
#define EXT_CSD_FW_CONFIG (169u) // 1, R/W, FW configuration.
#define EXT_CSD_USER_WP (171u) // 1, R/W, R/W/C_P & R/W/E_P, User area write protection register.
#define EXT_CSD_BOOT_WP (173u) // 1, R/W & R/W/C_P, Boot area write protection register.
#define EXT_CSD_ERASE_GROUP_DEF (175u) // 1, R/W/E_P, High-density erase group definition.
#define EXT_CSD_BOOT_BUS_WIDTH (177u) // 1, R/W/E, Boot bus width1.
#define EXT_CSD_BOOT_CONFIG_PROT (178u) // 1, R/W & R/W/C_P, Boot config protection.
#define EXT_CSD_PARTITION_CONFIG (179u) // 1, R/W/E & R/W/E_P, Partition configuration.
#define EXT_CSD_ERASED_MEM_CONT (181u) // 1, R, Erased memory content.
#define EXT_CSD_BUS_WIDTH (183u) // 1, W/E_P, Bus width mode.
#define EXT_CSD_HS_TIMING (185u) // 1, R/W/E_P, High-speed interface timing.
#define EXT_CSD_POWER_CLASS (187u) // 1, R/W/E_P, Power class.
#define EXT_CSD_CMD_SET_REV (189u) // 1, R, Command set revision.
#define EXT_CSD_CMD_SET (191u) // 1, R/W/E_P, Command set.
#define EXT_CSD_EXT_CSD_REV (192u) // 1, R, Extended CSD revision.
#define EXT_CSD_CSD_STRUCTURE (194u) // 1, R, CSD structure version.
#define EXT_CSD_CARD_TYPE (196u) // 1, R, Card type.
#define EXT_CSD_OUT_OF_INTERRUPT_TIME (198u) // 1, R, Out-of-interrupt busy timing.
#define EXT_CSD_PARTITION_SWITCH_TIME (199u) // 1, R, Partition switching timing.
#define EXT_CSD_PWR_CL_52_195 (200u) // 1, R, Power class for 52MHz at 1.95V.
#define EXT_CSD_PWR_CL_26_195 (201u) // 1, R, Power class for 26MHz at 1.95V.
#define EXT_CSD_PWR_CL_52_360 (202u) // 1, R, Power class for 52MHz at 3.6V.
#define EXT_CSD_PWR_CL_26_360 (203u) // 1, R, Power class for 26MHz at 3.6V.
#define EXT_CSD_MIN_PERF_R_4_26 (205u) // 1, R, Minimum Read Performance for 4bit at 26MHz.
#define EXT_CSD_MIN_PERF_W_4_26 (206u) // 1, R, Minimum Write Performance for 4bit at 26MHz.
#define EXT_CSD_MIN_PERF_R_8_26_4_52 (207u) // 1, R, Minimum Read Performance for 8bit at 26MHz, for 4bit at 52MHz.
#define EXT_CSD_MIN_PERF_W_8_26_4_52 (208u) // 1, R, Minimum Write Performance for 8bit at 26MHz, for 4bit at 52MHz.
#define EXT_CSD_MIN_PERF_R_8_52 (209u) // 1, R, Minimum Read Performance for 8bit at 52MHz.
#define EXT_CSD_MIN_PERF_W_8_52 (210u) // 1, R, Minimum Write Performance for 8bit at 52MHz.
#define EXT_CSD_SEC_COUNT (212u) // 4, R, Sector Count.
#define EXT_CSD_S_A_TIMEOUT (217u) // 1, R, Sleep/awake timeout.
#define EXT_CSD_S_C_VCCQ (219u) // 1, R, Sleep current (VCCQ).
#define EXT_CSD_S_C_VCC (220u) // 1, R, Sleep current (VCC).
#define EXT_CSD_HC_WP_GRP_SIZE (221u) // 1, R, High-capacity write protect group size.
#define EXT_CSD_REL_WR_SEC_C (222u) // 1, R, Reliable write sector count.
#define EXT_CSD_ERASE_TIMEOUT_MULT (223u) // 1, R, High-capacity erase timeout.
#define EXT_CSD_HC_ERASE_GRP_SIZE (224u) // 1, R, High-capacity erase unit size.
#define EXT_CSD_ACC_SIZE (225u) // 1, R, Access size.
#define EXT_CSD_BOOT_SIZE_MULTI (226u) // 1, R, Boot partition size.
#define EXT_CSD_BOOT_INFO (228u) // 1, R, Boot information.
#define EXT_CSD_SEC_TRIM_MULT (229u) // 1, R, Secure TRIM Multiplier.
#define EXT_CSD_SEC_ERASE_MULT (230u) // 1, R, Secure Erase Multiplier.
#define EXT_CSD_SEC_FEATURE_SUPPORT (231u) // 1, R, Secure Feature support.
#define EXT_CSD_TRIM_MULT (232u) // 1, R, TRIM Multiplier.
#define EXT_CSD_MIN_PERF_DDR_R_8_52 (234u) // 1, R, Minimum Read Performance for 8bit at 52MHz in DDR mode.
#define EXT_CSD_MIN_PERF_DDR_W_8_52 (235u) // 1, R, Minimum Write Performance for 8bit at 52MHz in DDR mode.
#define EXT_CSD_PWR_CL_DDR_52_195 (238u) // 1, R, Power class for 52MHz, DDR at 1.95V.
#define EXT_CSD_PWR_CL_DDR_52_360 (239u) // 1, R, Power class for 52MHz, DDR at 3.6V.
#define EXT_CSD_INI_TIMEOUT_AP (241u) // 1, R, 1st initialization time after partitioning.
#define EXT_CSD_CORRECTLY_PRG_SECTORS_NUM (242u) // 4, R, Number of correctly programmed sectors.
#define EXT_CSD_BKOPS_STATUS (246u) // 1, R, Background operations status.
#define EXT_CSD_BKOPS_SUPPORT (502u) // 1, R, Background operations support.
#define EXT_CSD_HPI_FEATURES (503u) // 1, R, HPI features.
#define EXT_CSD_S_CMD_SET (504u) // 1, R, Supported Command Sets.

192
arm7/source/mmc/sd_spec.h Normal file
View File

@@ -0,0 +1,192 @@
#pragma once
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 profi200
// Based on SD specification version 8.00.
#include "tmio.h"
// Controller specific macros. Add controller specific bits here.
// SD_[command type]_[response type]_[transfer type]
// Command type: CMD = regular command, ACMD = Application-Specific Command.
// Transfer type: R = read, W = write.
#define SD_CMD_NONE(id) (CMD_RESP_NONE | (id))
#define SD_CMD_R1(id) (CMD_RESP_R1 | (id))
#define SD_CMD_R1b(id) (CMD_RESP_R1b | (id))
#define SD_CMD_R2(id) (CMD_RESP_R2 | (id))
#define SD_CMD_R6(id) (CMD_RESP_R6 | (id))
#define SD_CMD_R7(id) (CMD_RESP_R7 | (id))
#define SD_CMD_R1_R(id) (CMD_DATA_R | CMD_DATA_EN | CMD_RESP_R1 | (id))
#define SD_CMD_R1_W(id) (CMD_DATA_W | CMD_DATA_EN | CMD_RESP_R1 | (id))
#define SD_ACMD_R1(id) (CMD_RESP_R1 | CMD_ACMD | (id))
#define SD_ACMD_R3(id) (CMD_RESP_R3 | CMD_ACMD | (id))
#define SD_ACMD_R1_R(id) (CMD_DATA_R | CMD_DATA_EN | CMD_RESP_R1 | CMD_ACMD | (id))
// Basic Commands (class 0).
#define SD_GO_IDLE_STATE SD_CMD_NONE(0u) // -, [31:0] stuff bits.
#define SD_ALL_SEND_CID SD_CMD_R2(2u) // R2, [31:0] stuff bits.
#define SD_SEND_RELATIVE_ADDR SD_CMD_R6(3u) // R6, [31:0] stuff bits.
#define SD_SET_DSR SD_CMD_NONE(4u) // -, [31:16] DSR [15:0] stuff bits.
#define SD_SELECT_CARD SD_CMD_R1b(7u) // R1b, [31:16] RCA [15:0] stuff bits.
#define SD_DESELECT_CARD SD_CMD_NONE(7u) // -, [31:16] RCA [15:0] stuff bits.
#define SD_SEND_IF_COND SD_CMD_R7(8u) // R7, [31:12] reserved bits [11:8] supply voltage (VHS) [7:0] check pattern.
#define SD_SEND_CSD SD_CMD_R2(9u) // R2, [31:16] RCA [15:0] stuff bits.
#define SD_SEND_CID SD_CMD_R2(10u) // R2, [31:16] RCA [15:0] stuff bits.
#define SD_VOLTAGE_SWITCH SD_CMD_R1(11u) // R1, [31:0] reserved bits (all 0).
#define SD_STOP_TRANSMISSION SD_CMD_R1b(12u) // R1b, [31:0] stuff bits.
#define SD_SEND_STATUS SD_CMD_R1(13u) // R1, [31:16] RCA [15] Send Task Status Register [14:0] stuff bits.
#define SD_SEND_TASK_STATUS SD_CMD_R1(13u) // R1, [31:16] RCA [15] Send Task Status Register [14:0] stuff bits.
#define SD_GO_INACTIVE_STATE SD_CMD_NONE(15u) // -, [31:16] RCA [15:0] reserved bits.
// Block-Oriented Read Commands (class 2).
#define SD_SET_BLOCKLEN SD_CMD_R1(16u) // R1, [31:0] block length.
#define SD_READ_SINGLE_BLOCK SD_CMD_R1_R(17u) // R1, [31:0] data address.
#define SD_READ_MULTIPLE_BLOCK SD_CMD_R1_R(18u) // R1, [31:0] data address.
#define SD_SEND_TUNING_BLOCK SD_CMD_R1_R(19u) // R1, [31:0] reserved bits (all 0).
#define SD_SPEED_CLASS_CONTROL SD_CMD_R1b(20u) // R1b, [31:28] Speed Class Control [27:0] See command description.
#define SD_ADDRESS_EXTENSION SD_CMD_R1(22u) // R1, [31:6] reserved bits (all 0) [5:0] extended address.
#define SD_SET_BLOCK_COUNT SD_CMD_R1(23u) // R1, [31:0] Block Count.
// Block-Oriented Write Commands (class 4).
// SET_BLOCKLEN
// SPEED_CLASS_CONTROL
// ADDRESS_EXTENSION
// SET_BLOCK_COUNT
#define SD_WRITE_BLOCK SD_CMD_R1_W(24u) // R1, [31:0] data address.
#define SD_WRITE_MULTIPLE_BLOCK SD_CMD_R1_W(25u) // R1, [31:0] data address.
#define SD_PROGRAM_CSD SD_CMD_R1_W(27u) // R1, [31:0] stuff bits.
// Block Oriented Write Protection Commands (class 6).
#define SD_SET_WRITE_PROT SD_CMD_R1b(28u) // R1b, [31:0] data address.
#define SD_CLR_WRITE_PROT SD_CMD_R1b(29u) // R1b, [31:0] data address.
#define SD_SEND_WRITE_PROT SD_CMD_R1_R(30u) // R1, [31:0] write protect data address.
// Erase Commands (class 5).
#define SD_ERASE_WR_BLK_START SD_CMD_R1(32u) // R1, [31:0] data address.
#define SD_ERASE_WR_BLK_END SD_CMD_R1(33u) // R1, [31:0] data address.
#define SD_ERASE SD_CMD_R1b(38u) // R1b, [31:0] Erase Function.
// Lock Card (class 7).
// SET_BLOCKLEN
// Command 40 "Defined by DPS Spec.".
#define SD_LOCK_UNLOCK SD_CMD_R1_W(42u) // R1, [31:0] Reserved bits (Set all 0).
// Application-Specific Commands (class 8).
#define SD_APP_CMD SD_CMD_R1(55u) // R1, [31:16] RCA [15:0] stuff bits.
#define SD_GEN_CMD_R SD_CMD_R1_R(56u) // R1, [31:1] stuff bits. [0]: RD/WR = 1.
#define SD_GEN_CMD_W SD_CMD_R1_W(56u) // R1, [31:1] stuff bits. [0]: RD/WR = 0.
// Application Specific Commands used/reserved by SD Memory Card.
#define SD_APP_SET_BUS_WIDTH SD_ACMD_R1(6u) // R1, [31:2] stuff bits [1:0] bus width.
#define SD_APP_SD_STATUS SD_ACMD_R1_R(13u) // R1, [31:0] stuff bits.
#define SD_APP_SEND_NUM_WR_BLOCKS SD_ACMD_R1_R(22u) // R1, [31:0] stuff bits.
#define SD_APP_SET_WR_BLK_ERASE_COUNT SD_ACMD_R1(23u) // R1, [31:23] stuff bits [22:0] Number of blocks.
#define SD_APP_SD_SEND_OP_COND SD_ACMD_R3(41u) // R3, [31] reserved bit [30] HCS (OCR[30]) [29] reserved for eSD [28] XPC [27:25] reserved bits [24] S18R [23:0] VDD Voltage Window (OCR[23:0]).
#define SD_APP_SET_CLR_CARD_DETECT SD_ACMD_R1(42u) // R1, [31:1] stuff bits [0] set_cd.
#define SD_APP_SEND_SCR SD_ACMD_R1_R(51u) // R1, [31:0] stuff bits.
// Switch Function Commands (class 10).
#define SD_SWITCH_FUNC SD_CMD_R1_R(6u) // R1, [31] Mode 0: Check function 1: Switch function [30:24] reserved (All '0') [23:20] reserved for function group 6 (0h or Fh) [19:16] reserved for function group 5 (0h or Fh) [15:12] function group 4 for PowerLimit [11:8] function group 3 for Drive Strength [7:4] function group 2 for Command System [3:0] function group 1 for Access Mode.
// Function Extension Commands (class 11).
#define SD_READ_EXTR_SINGLE SD_CMD_R1_R(48u) // R1, [31] MIO0: Memory, 1: I/O [30:27] FNO[26] Reserved (=0) [25:9] ADDR [8:0] LEN.
#define SD_WRITE_EXTR_SINGLE SD_CMD_R1_W(49u) // R1, [31] MIO0: Memory, 1: I/O [30:27] FNO [26] MW [25:9] ADDR [8:0] LEN/MASK.
#define SD_READ_EXTR_MULTI SD_CMD_R1_R(58u) // R1, [31] MIO0: Memory, 1: I/O [30:27] FNO [26] BUS0: 512B, 1: 32KB [25:9] ADDR [8:0] BUC.
#define SD_WRITE_EXTR_MULTI SD_CMD_R1_W(59u) // R1, [31] MIO0: Memory, 1: I/O [30:27] FNO [26] BUS0: 512B, 1: 32KB [25:9] ADDR [8:0] BUC.
// Command Queue Function Commands (class 1).
#define SD_Q_MANAGEMENT SD_CMD_R1b(43u) // R1b, [31:21] Reserved [20:16]: Task ID [3:0]: Operation Code (Abort tasks etc.).
#define SD_Q_TASK_INFO_A SD_CMD_R1(44u) // R1, [31] Reserved [30] Direction [29:24] Extended Address [23] Priority [22:21] Reserved [20:16] Task ID [15:0] Number of Blocks.
#define SD_Q_TASK_INFO_B SD_CMD_R1(45u) // R1, [31:0] Start block address.
#define SD_Q_RD_TASK SD_CMD_R1_R(46u) // R1, [31:21] Reserved [20:16] Task ID [15:0] Reserved.
#define SD_Q_WR_TASK SD_CMD_R1_W(47u) // R1, [31:21] Reserved [20:16] Task ID [15:0] Reserved.
// 4.10.1 Card Status.
// Type:
// E: Error bit.
// S: Status bit.
// R: Detected and set for the actual command response.
// X: Detected and set during command execution. The host can get the status by issuing a command with R1 response.
//
// Clear Condition:
// A: According to the card current status.
// B: Always related to the previous command. Reception of a valid command will clear it (with a delay of one command).
// C: Clear by read.
#define SD_R1_AKE_SEQ_ERROR (1u<<3) // E R C, Error in the sequence of the authentication process.
#define SD_R1_APP_CMD (1u<<5) // S R C, The card will expect ACMD, or an indication that the command has been interpreted as ACMD.
#define SD_R1_FX_EVENT (1u<<6) // S X A, ExtensionFunctions may set this bit to get host to deal with events.
#define SD_R1_READY_FOR_DATA (1u<<8) // S X A, Corresponds to buffer empty signaling on the bus.
#define SD_R1_STATE_IDLE (0u<<9) // S X B
#define SD_R1_STATE_READY (1u<<9) // S X B
#define SD_R1_STATE_IDENT (2u<<9) // S X B
#define SD_R1_STATE_STBY (3u<<9) // S X B
#define SD_R1_STATE_TRAN (4u<<9) // S X B
#define SD_R1_STATE_DATA (5u<<9) // S X B
#define SD_R1_STATE_RCV (6u<<9) // S X B
#define SD_R1_STATE_PRG (7u<<9) // S X B
#define SD_R1_STATE_DIS (8u<<9) // S X B
#define SD_R1_ERASE_RESET (1u<<13) // S R C, An erase sequence was cleared before executing because an out of erase sequence command was received.
#define SD_R1_CARD_ECC_DISABLED (1u<<14) // S X A, The command has been executed without using the internal ECC.
#define SD_R1_WP_ERASE_SKIP (1u<<15) // E R X C, Set when only partial address space was erased due to existing write protected blocks or the temporary or permanent write protected cardwas erased.
#define SD_R1_CSD_OVERWRITE (1u<<16) // E R X C, Can be either one of the following errors: -The read only section of the CSD does not match the card content. -An attempt to reverse the copy (set as original) or permanent WP (unprotected) bits was made.
// 17 reserved for DEFERRED_RESPONSE (Refer to eSD Addendum)
#define SD_R1_ERROR (1u<<19) // E R X C, A general or an unknown error occurred during the operation.
#define SD_R1_CC_ERROR (1u<<20) // E R X C, Internal card controller error:
#define SD_R1_CARD_ECC_FAILED (1u<<21) // E R X C, Card internal ECC was applied but failed to correct the data.
#define SD_R1_ILLEGAL_COMMAND (1u<<22) // E R B, Command not legal for the card state.
#define SD_R1_COM_CRC_ERROR (1u<<23) // E R B, The CRC check of the previous command failed.
#define SD_R1_LOCK_UNLOCK_FAILED (1u<<24) // E R X C, Set when a sequence or password error has been detected in lock/unlock card command.
#define SD_R1_CARD_IS_LOCKED (1u<<25) // S X A, When set, signals that the card is locked by the host.
#define SD_R1_WP_VIOLATION (1u<<26) // E R X C, Set when the host attempts to write to a protected block or to thetemporary or permanent write protected card.
#define SD_R1_ERASE_PARAM (1u<<27) // E R X C, An invalid selection of write-blocks for erase occurred.
#define SD_R1_ERASE_SEQ_ERROR (1u<<28) // E R C, An error in the sequence of erase commands occurred.
#define SD_R1_BLOCK_LEN_ERROR (1u<<29) // E R X C, The transferred block length is not allowed for this card, or the number of transferred bytes does not match the block length.
#define SD_R1_ADDRESS_ERROR (1u<<30) // E R X C, A misaligned address which did not match the block length was used in the command.
#define SD_R1_OUT_OF_RANGE (1u<<31) // E R X C, The command's argument was out of the allowed range for this card.
#define SD_R1_ERR_ALL (SD_R1_OUT_OF_RANGE | SD_R1_ADDRESS_ERROR | SD_R1_BLOCK_LEN_ERROR | \
SD_R1_ERASE_SEQ_ERROR | SD_R1_ERASE_PARAM | SD_R1_WP_VIOLATION | \
SD_R1_LOCK_UNLOCK_FAILED | SD_R1_COM_CRC_ERROR | SD_R1_ILLEGAL_COMMAND | \
SD_R1_CARD_ECC_FAILED | SD_R1_CC_ERROR | SD_R1_ERROR | \
SD_R1_CSD_OVERWRITE | SD_R1_WP_ERASE_SKIP | SD_R1_AKE_SEQ_ERROR)
// Argument bits for SEND_IF_COND (CMD8).
#define SD_CMD8_CHK_PATT (0xAAu) // Check pattern.
#define SD_CMD8_VHS_2_7_3_6V (1u<<8) // Voltage supplied (VHS) 2.7-3.6V.
#define SD_CMD8_PCIe (1u<<12) // PCIe Avail-ability.
#define SD_CMD8_PCIe_1_2V (1u<<13) // PCIe 1.2V Support.
// 5.1 OCR register.
#define SD_OCR_2_7_2_8V (1u<<15) // 2.7-2.8V.
#define SD_OCR_2_8_2_9V (1u<<16) // 2.8-2.9V.
#define SD_OCR_2_9_3_0V (1u<<17) // 2.9-3.0V.
#define SD_OCR_3_0_3_1V (1u<<18) // 3.0-3.1V.
#define SD_OCR_3_1_3_2V (1u<<19) // 3.1-3.2V.
#define SD_OCR_3_2_3_3V (1u<<20) // 3.2-3.3V.
#define SD_OCR_3_3_3_4V (1u<<21) // 3.3-3.4V.
#define SD_OCR_3_4_3_5V (1u<<22) // 3.4-3.5V.
#define SD_OCR_3_5_3_6V (1u<<23) // 3.5-3.6V.
#define SD_OCR_S18A (1u<<24) // S18A: Switching to 1.8V Accepted. 0b: Continues current voltage signaling, 1b: Ready for switching signal voltage.
#define SD_OCR_CO2T (1u<<27) // Over 2TB Card. CCS must also be 1 if this is 1.
#define SD_OCR_UHS_II (1u<<29) // UHS-II Card Status. 0b: Non UHS-II Card, 1b: UHS-II Card.
#define SD_OCR_CCS (1u<<30) // Card Capacity Status. 0b: SDSC, 1b: SDHC or SDXC.
#define SD_OCR_READY (1u<<31) // Busy Status. 0b: On Initialization, 1b: Initialization Complete.
// Argument bits for SEND_OP_COND (ACMD41).
// For voltage bits see OCR register above.
#define SD_ACMD41_S18R (1u<<24) // S18R: Switching to 1.8V Request. 0b: Use current signal voltage, 1b: Switch to 1.8V signal voltage.
#define SD_ACMD41_HO2T (1u<<27) // Over 2TB Supported Host. HCS must also be 1 if this is 1.
#define SD_ACMD41_XPC (1u<<28) // SDXC Power Control. 0b: Power Saving, 1b: Maximum Performance.
#define SD_ACMD41_HCS (1u<<30) // Host Capacity Support. 0b: SDSC Only Host, 1b: SDHC or SDXC Supported.
// 4.3.10 Switch Function Command.
// mode: 0 = check function, 1 = set function
// pwr: Function group 4 Power Limit.
// driver: Function group 3 Driver Strength.
// cmd: Function group 2 Command system.
// acc: Function group 1 Access mode.
#define SD_SWITCH_FUNC_ARG(mode, pwr, driver, cmd, acc) ((mode)<<31 | 0xFFu<<16 | ((pwr)&0xFu)<<12 | ((driver)&0xFu)<<8 | ((cmd)&0xFu)<<4 | ((acc)&0xFu))

751
arm7/source/mmc/sdmmc.c Normal file
View File

@@ -0,0 +1,751 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 profi200
#include <stdalign.h>
#include <string.h>
#include <nds/ndstypes.h>
#include "mmc/sdmmc.h" // Includes types.h.
#include "tmio.h"
#include "mmc/mmc_spec.h"
#include "mmc/sd_spec.h"
// Note on INIT_CLOCK:
// 400 kHz is allowed by the specs. 523 kHz has been proven to work reliably
// for SD cards and eMMC but very early MMCs can fail at init.
// We lose about 5 ms of time on init by using 261 kHz.
#define INIT_CLOCK (400000u) // Maximum 400 kHz.
#define DEFAULT_CLOCK (20000000u) // Maximum 20 MHz.
#define HS_CLOCK (50000000u) // Maximum 50 MHz.
// ARM7 timer clock = controller clock = CPU clock.
// swiDelay() doesn't seem to be cycle accurate meaning
// one cycle is 4 (?) CPU cycles.
#define SLEEP_MS_FUNC(ms) swi_waitByLoop(8378 * (ms))
#define MMC_OCR_VOLT_MASK (MMC_OCR_3_2_3_3V) // We support 3.3V only.
#define SD_OCR_VOLT_MASK (SD_OCR_3_2_3_3V) // We support 3.3V only.
#define SD_IF_COND_ARG (SD_CMD8_VHS_2_7_3_6V | SD_CMD8_CHK_PATT)
#define SD_OP_COND_ARG (SD_OCR_VOLT_MASK) // We support 100 mA and 3.3V. Without HCS bit.
#define MMC_OP_COND_ARG (MMC_OCR_SECT_MODE | MMC_OCR_VOLT_MASK) // We support sector addressing and 3.3V.
// Note: DEV_TYPE_NONE must be zero.
enum
{
// Device types.
DEV_TYPE_NONE = 0u, // Unitialized/no device.
DEV_TYPE_MMC = 1u, // (e)MMC.
DEV_TYPE_MMCHC = 2u, // High capacity (e)MMC (>2 GB).
DEV_TYPE_SDSC = 3u, // SDSC.
DEV_TYPE_SDHC = 4u, // SDHC, SDXC.
DEV_TYPE_SDUC = 5u // SDUC.
};
#define IS_DEV_MMC(dev) ((dev) < DEV_TYPE_SDSC)
typedef struct
{
TmioPort port;
u8 type; // Device type. 0 = none, 1 = (e)MMC, 2 = High capacity (e)MMC,
// 3 = SDSC, 4 = SDHC/SDXC, 5 = SDUC.
u8 prot; // Protection bits. Each bit 1 = protected.
// Bit 0 SD card slider, bit 1 temporary write protection (CSD),
// bit 2 permanent write protection (CSD) and bit 3 password protection.
u16 rca; // Relative Card Address (RCA).
u16 ccc; // (e)MMC/SD command class support from CSD. One per bit starting at 0.
u32 sectors; // Size in 512 byte units.
u32 status; // R1 card status on error. Only updated on errors.
// Cached card infos.
u32 cid[4]; // Raw CID without the CRC.
} SdmmcDev;
static SdmmcDev g_devs[2] = {0};
static u32 sendAppCmd(TmioPort *const port, const u16 cmd, const u32 arg, const u32 rca)
{
// Send app CMD. Same CMD for (e)MMC/SD.
// TODO: Check the APP_CMD bit in the response?
// Linux does it but is it really necessary? SD spec 4.3.9.1.
u32 res = TMIO_sendCommand(port, MMC_APP_CMD, rca);
if(res == 0)
{
res = TMIO_sendCommand(port, cmd, arg);
}
return res;
}
static u32 goIdleState(TmioPort *const port)
{
// Enter idle state before we start the init procedure.
// Works from all but inactive state. CMD is the same for (e)MMC/SD.
// For (e)MMC there are optional init paths:
// arg = 0x00000000 -> GO_IDLE_STATE.
// arg = 0xF0F0F0F0 -> GO_PRE_IDLE_STATE.
// arg = 0xFFFFFFFA -> BOOT_INITIATION.
u32 res = TMIO_sendCommand(port, MMC_GO_IDLE_STATE, 0);
if(res != 0) return SDMMC_ERR_GO_IDLE_STATE;
return SDMMC_ERR_NONE;
}
static u32 initIdleState(TmioPort *const port, u8 *const devTypeOut)
{
// Tell the card what interfaces and voltages we support.
// Only SD v2 and up will respond. (e)MMC won't respond.
u32 res = TMIO_sendCommand(port, SD_SEND_IF_COND, SD_IF_COND_ARG);
if(res == 0)
{
// If the card supports the interfaces and voltages
// it should echo back the check pattern and set the
// support bits.
// Since we don't support anything but the
// standard SD interface at 3.3V we can check
// the whole response at once.
if(port->resp[0] != SD_IF_COND_ARG) return SDMMC_ERR_IF_COND_RESP;
}
else if(res != STATUS_ERR_CMD_TIMEOUT) return SDMMC_ERR_SEND_IF_COND; // Card responded but an error occured.
// Send the first app CMD. If this times out it's (e)MMC.
// If SEND_IF_COND timed out tell the SD card we are a v1 host.
const u32 opCondArg = SD_OP_COND_ARG | (res<<8 ^ SD_ACMD41_HCS); // Caution! Controller specific hack.
u8 devType = DEV_TYPE_SDSC;
res = sendAppCmd(port, SD_APP_SD_SEND_OP_COND, opCondArg, 0);
if(res == STATUS_ERR_CMD_TIMEOUT) devType = DEV_TYPE_MMC; // Continue with (e)MMC init.
else if(res != 0) return SDMMC_ERR_SEND_OP_COND; // Unknown error.
if(devType == DEV_TYPE_MMC) // (e)MMC.
{
// Loop until a timeout of 1 second or the card is ready.
u32 tries = 200;
u32 ocr;
while(1)
{
res = TMIO_sendCommand(port, MMC_SEND_OP_COND, MMC_OP_COND_ARG);
if(res != 0) return SDMMC_ERR_SEND_OP_COND;
ocr = port->resp[0];
if(!--tries || (ocr & MMC_OCR_READY)) break;
// Linux uses 10 ms but the card doesn't become ready faster
// when polling with delay. Use 5 ms as compromise so not much
// time is wasted when the card becomes ready in the middle of the delay.
SLEEP_MS_FUNC(5);
}
// (e)MMC didn't finish init within 1 second.
if(tries == 0) return SDMMC_ERR_OP_COND_TMOUT;
// Check if the (e)MMC supports the voltage and if it's high capacity.
if(!(ocr & MMC_OCR_VOLT_MASK)) return SDMMC_ERR_VOLT_SUPPORT; // Voltage not supported.
if(ocr & MMC_OCR_SECT_MODE) devType = DEV_TYPE_MMCHC; // 7.4.3.
}
else // SD card.
{
// Loop until a timeout of 1 second or the card is ready.
u32 tries = 200;
u32 ocr;
while(1)
{
ocr = port->resp[0];
if(!--tries || (ocr & SD_OCR_READY)) break;
// Linux uses 10 ms but the card doesn't become ready faster
// when polling with delay. Use 5 ms as compromise so not much
// time is wasted when the card becomes ready in the middle of the delay.
SLEEP_MS_FUNC(5);
res = sendAppCmd(port, SD_APP_SD_SEND_OP_COND, opCondArg, 0);
if(res != 0) return SDMMC_ERR_SEND_OP_COND;
}
// SD card didn't finish init within 1 second.
if(tries == 0) return SDMMC_ERR_OP_COND_TMOUT;
if(!(ocr & SD_OCR_VOLT_MASK)) return SDMMC_ERR_VOLT_SUPPORT; // Voltage not supported.
if(ocr & SD_OCR_CCS) devType = DEV_TYPE_SDHC;
}
*devTypeOut = devType;
return SDMMC_ERR_NONE;
}
static u32 initReadyState(SdmmcDev *const dev)
{
TmioPort *const port = &dev->port;
// SD card voltage switch sequence goes here if supported.
// Get the CID. CMD is the same for (e)MMC/SD.
u32 res = TMIO_sendCommand(port, MMC_ALL_SEND_CID, 0);
if(res != 0) return SDMMC_ERR_ALL_SEND_CID;
memcpy(dev->cid, port->resp, 16);
return SDMMC_ERR_NONE;
}
static u32 initIdentState(SdmmcDev *const dev, const u8 devType, u32 *const rcaOut)
{
TmioPort *const port = &dev->port;
u32 rca;
if(IS_DEV_MMC(devType)) // (e)MMC.
{
// Set the RCA of the (e)MMC to 1. 0 is reserved.
// The RCA is in the upper 16 bits of the argument.
rca = 1;
u32 res = TMIO_sendCommand(port, MMC_SET_RELATIVE_ADDR, rca<<16);
if(res != 0) return SDMMC_ERR_SET_SEND_RCA;
}
else // SD card.
{
// Ask the SD card to send its RCA.
u32 res = TMIO_sendCommand(port, SD_SEND_RELATIVE_ADDR, 0);
if(res != 0) return SDMMC_ERR_SET_SEND_RCA;
// RCA in upper 16 bits. Discards lower status bits of R6 response.
rca = port->resp[0]>>16;
}
dev->rca = rca;
*rcaOut = rca<<16;
return SDMMC_ERR_NONE;
}
// Based on UNSTUFF_BITS from linux/drivers/mmc/core/sd.c.
// Extracts up to 32 bits from a u32[4] array.
static inline u32 extractBits(const u32 resp[4], const u32 start, const u32 size)
{
const u32 mask = (size < 32 ? 1u<<size : 0u) - 1;
const u32 off = 3 - (start / 32);
const u32 shift = start & 31u;
u32 res = resp[off]>>shift;
if(size + shift > 32)
res |= resp[off - 1]<<((32u - shift) & 31u);
return res & mask;
}
static void parseCsd(SdmmcDev *const dev, const u8 devType, u8 *const spec_vers_out)
{
// Note: The MSBs are in csd[0].
const u32 *const csd = dev->port.resp;
const u8 structure = extractBits(csd, 126, 2); // [127:126]
*spec_vers_out = extractBits(csd, 122, 4); // [125:122] All 0 for SD cards.
dev->ccc = extractBits(csd, 84, 12); // [95:84]
u32 sectors = 0;
if(structure == 0 || devType == DEV_TYPE_MMC) // structure = 0 is CSD version 1.0.
{
const u32 read_bl_len = extractBits(csd, 80, 4); // [83:80]
const u32 c_size = extractBits(csd, 62, 12); // [73:62]
const u32 c_size_mult = extractBits(csd, 47, 3); // [49:47]
// For SD cards with CSD 1.0 and <=2 GB (e)MMC this calculation is used.
// Note: READ_BL_LEN is at least 9.
// Modified/simplified to calculate sectors instead of bytes.
sectors = (c_size + 1)<<(c_size_mult + 2 + read_bl_len - 9);
}
else if(devType != DEV_TYPE_MMCHC)
{
// SD CSD version 3.0 format.
// For version 2.0 this is 22 bits however the upper bits
// are reserved and zero filled so this is fine.
const u32 c_size = extractBits(csd, 48, 28); // [75:48]
// Calculation for SD cards with CSD >1.0.
sectors = (c_size + 1)<<10;
}
// Else for high capacity (e)MMC the sectors will be read later from EXT_CSD.
dev->sectors = sectors;
// Parse temporary and permanent write protection bits.
u8 prot = extractBits(csd, 12, 1)<<1; // [12:12] Not checked by Linux.
prot |= extractBits(csd, 13, 1)<<2; // [13:13]
dev->prot |= prot;
}
static u32 initStandbyState(SdmmcDev *const dev, const u8 devType, const u32 rca, u8 *const spec_vers_out)
{
TmioPort *const port = &dev->port;
// Get the CSD. CMD is the same for (e)MMC/SD.
u32 res = TMIO_sendCommand(port, MMC_SEND_CSD, rca);
if(res != 0) return SDMMC_ERR_SEND_CSD;
parseCsd(dev, devType, spec_vers_out);
// CMD is the same for (e)MMC/SD however both R1 and R1b responses are used.
// We assume R1b and hope it doesn't time out.
res = TMIO_sendCommand(port, MMC_SELECT_CARD, rca);
if(res != 0) return SDMMC_ERR_SELECT_CARD;
// The SD card spec mentions that we should check the lock bit in the
// response to CMD7 to identify cards requiring a password to unlock.
// Same seems to apply for (e)MMC.
// Same bit for (e)MMC/SD R1 card status.
dev->prot |= (port->resp[0] & MMC_R1_CARD_IS_LOCKED)>>22; // Bit 3.
return SDMMC_ERR_NONE;
}
// TODO: Set the timeout based on clock speed (Tmio uses SDCLK for timeouts).
// The tmio driver sets a sane default but we should calculate it anyway.
static u32 initTranState(SdmmcDev *const dev, const u8 devType, const u32 rca, const u8 spec_vers)
{
TmioPort *const port = &dev->port;
if(IS_DEV_MMC(devType)) // (e)MMC.
{
// EXT_CSD, non-1 bit bus width and HS timing are only
// supported by (e)MMC SPEC_VERS 4.1 and higher.
if(spec_vers > 3) // Version 4.14.24.3 or higher.
{
// The (e)MMC spec says to check the card status after a SWITCH CMD (7.6.1).
// I think we can get away without checking this because support for HS timing
// and 4 bit bus width is mandatory for this spec version. If the card is
// non-standard we will encounter errors on the next CMD anyway.
// Switch to 4 bit bus mode.
const u32 busWidthArg = MMC_SWITCH_ARG(MMC_SWITCH_ACC_WR_BYTE, EXT_CSD_BUS_WIDTH, 1, 0);
u32 res = TMIO_sendCommand(port, MMC_SWITCH, busWidthArg);
if(res != 0) return SDMMC_ERR_SET_BUS_WIDTH;
TMIO_setBusWidth(port, 4);
// We should also check in the EXT_CSD the power budget for the card.
// Nintendo seems to leave it on default (no change).
if(devType == DEV_TYPE_MMCHC)
{
// Note: The EXT_CSD is normally read before touching HS timing and bus width.
// We can take advantage of the faster data transfer with this order.
alignas(4) u8 ext_csd[512];
TMIO_setBuffer(port, (u32*)ext_csd, 1);
res = TMIO_sendCommand(port, MMC_SEND_EXT_CSD, 0);
if(res != 0) return SDMMC_ERR_SEND_EXT_CSD;
// Get sector count from EXT_CSD only if sector addressing is used because
// byte addressed (e)MMC may set sector count to 0.
dev->sectors = ext_csd[EXT_CSD_SEC_COUNT + 3]<<24 | ext_csd[EXT_CSD_SEC_COUNT + 2]<<16 |
ext_csd[EXT_CSD_SEC_COUNT + 1]<<8 | ext_csd[EXT_CSD_SEC_COUNT + 0];
}
}
}
else // SD card.
{
// Remove DAT3 pull-up. Linux doesn't do it but the SD spec recommends it.
u32 res = sendAppCmd(port, SD_APP_SET_CLR_CARD_DETECT, 0, rca); // arg = 0 removes the pull-up.
if(res != 0) return SDMMC_ERR_SET_CLR_CD;
// Switch to 4 bit bus mode.
res = sendAppCmd(port, SD_APP_SET_BUS_WIDTH, 2, rca); // arg = 2 is 4 bit bus width.
if(res != 0) return SDMMC_ERR_SET_BUS_WIDTH;
TMIO_setBusWidth(port, 4);
}
// SD: The description for CMD SET_BLOCKLEN says 512 bytes is the default.
// (e)MMC: The description for READ_BL_LEN (CSD) says 512 bytes is the default.
// So it's not required to set the block length.
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_init(const u8 devNum)
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
SdmmcDev *const dev = &g_devs[devNum];
if(dev->type != DEV_TYPE_NONE) return SDMMC_ERR_INITIALIZED;
// Check SD card write protection slider.
if(devNum == SDMMC_DEV_CARD)
dev->prot = !TMIO_cardWritable();
// Init port, enable clock output and wait 74 clocks.
TmioPort *const port = &dev->port;
TMIO_initPort(port, devNum);
TMIO_powerupSequence(port); // Setup continuous clock and wait 74 clocks.
u32 res = goIdleState(port);
if(res != SDMMC_ERR_NONE) return res;
// (e)MMC/SD now in idle state (idle).
u8 devType;
res = initIdleState(port, &devType);
if(res != SDMMC_ERR_NONE) return res;
// Stop clock at idle, init clock.
TMIO_setClock(port, INIT_CLOCK);
// (e)MMC/SD now in ready state (ready).
res = initReadyState(dev);
if(res != SDMMC_ERR_NONE) return res;
// (e)MMC/SD now in identification state (ident).
u32 rca;
res = initIdentState(dev, devType, &rca);
if(res != SDMMC_ERR_NONE) return res;
// (e)MMC/SD now in stand-by state (stby).
// Maximum at this point would be 20 MHz for (e)MMC and 25 for SD.
// SD: We can increase the clock after end of identification state.
// TODO: eMMC spec section 7.6
// "Until the contents of the CSD register is known by the host,
// the fPP clock rate must remain at fOD. (See Section 12.7 on page 176.)"
// Since the absolute minimum clock rate is 20 MHz and we are in push-pull
// mode already can we cheat and switch to <=20 MHz before getting the CSD?
// Note: This seems to be working just fine in all tests.
TMIO_setClock(port, DEFAULT_CLOCK);
u8 spec_vers;
res = initStandbyState(dev, devType, rca, &spec_vers);
if(res != SDMMC_ERR_NONE) return res;
// (e)MMC/SD now in transfer state (tran).
res = initTranState(dev, devType, rca, spec_vers);
if(res != SDMMC_ERR_NONE) return res;
// Only set dev type on successful init.
dev->type = devType;
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_setSleepMode(const u8 devNum, const bool enabled)
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
SdmmcDev *const dev = &g_devs[devNum];
TmioPort *const port = &dev->port;
const u32 rca = (u32)dev->rca<<16;
const u8 devType = dev->type;
if(enabled)
{
// Deselect card to go back to stand-by state.
// CMD is the same for (e)MMC/SD.
u32 res = TMIO_sendCommand(port, MMC_DESELECT_CARD, 0);
if(res != 0) return SDMMC_ERR_SELECT_CARD;
// Only (e)MMC can go into true sleep mode.
if(IS_DEV_MMC(devType))
{
// Switch (e)MMC into sleep mode.
res = TMIO_sendCommand(port, MMC_SLEEP_AWAKE, rca | 1u<<15);
if(res != 0) return SDMMC_ERR_SLEEP_AWAKE;
// TODO: Power down eMMC. This is confirmed working on 3DS.
}
}
else
{
if(IS_DEV_MMC(devType))
{
// TODO: Power up eMMC. This is confirmed working on 3DS.
// Wake (e)MMC up from sleep mode.
u32 res = TMIO_sendCommand(port, MMC_SLEEP_AWAKE, rca);
if(res != 0) return SDMMC_ERR_SLEEP_AWAKE;
}
// Select card to go back to transfer state.
// CMD is the same for (e)MMC/SD.
u32 res = TMIO_sendCommand(port, MMC_SELECT_CARD, rca);
if(res != 0) return SDMMC_ERR_SELECT_CARD;
}
return SDMMC_ERR_NONE;
}
// TODO: Is there any "best practice" way of deinitializing cards?
// Kick the card back into idle state maybe?
// Linux seems to deselect cards on "suspend".
u32 pico_SDMMC_deinit(const u8 devNum)
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
memset(&g_devs[devNum], 0, sizeof(SdmmcDev));
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_lockUnlock(const u8 devNum, const u8 mode, const u8 *const pwd, const u8 pwdLen)
{
// Password length is maximum 16 bytes except when replacing a password.
if(devNum > SDMMC_MAX_DEV_NUM || pwdLen > 32) return SDMMC_ERR_INVAL_PARAM;
// Set block length on (e)MMC/SD side and host.
// Same CMD for (e)MMC/SD.
SdmmcDev *const dev = &g_devs[devNum];
TmioPort *const port = &dev->port;
const u32 blockLen = (mode != SDMMC_LK_ERASE ? 2 + pwdLen : 1);
u32 res = TMIO_sendCommand(port, MMC_SET_BLOCKLEN, blockLen);
if(res != 0) return SDMMC_ERR_SET_BLOCKLEN;
TMIO_setBlockLen(port, blockLen);
do
{
// Prepare lock/unlock data block.
alignas(4) u8 buf[36] = {0}; // Size multiple of 4 (TMIO driver limitation).
buf[0] = mode;
buf[1] = pwdLen;
memcpy(&buf[2], pwd, pwdLen);
// Dirty hack to extend the data timeout to a bit over 4 minutes with TMIO controller.
// We need 3 minutes minimum for erase.
const u16 clk_ctrl_backup = port->sd_clk_ctrl;
TMIO_setClock(port, 130913);
// Note: Command class 7 support is mandatory for (e)MMC. Not for SD cards until 2.00.
// Same CMD for (e)MMC/SD.
TMIO_setBuffer(port, (u32*)buf, 1);
res = TMIO_sendCommand(port, MMC_LOCK_UNLOCK, 0);
port->sd_clk_ctrl = clk_ctrl_backup; // Undo the data timeout hack.
if(res != 0)
{
res = SDMMC_ERR_LOCK_UNLOCK;
break;
}
// Restore default block length and get the R1 status.
// Same CMD for (e)MMC/SD.
res = TMIO_sendCommand(port, MMC_SET_BLOCKLEN, 512);
if(res != 0)
{
res = SDMMC_ERR_SET_BLOCKLEN;
break;
}
TMIO_setBlockLen(port, 512);
// Check if lock/unlock worked.
// Same bit for (e)MMC/SD R1 card status.
const u32 status = port->resp[0];
if(status & MMC_R1_LOCK_UNLOCK_FAILED)
res = SDMMC_ERR_LOCK_UNLOCK_FAIL;
// Update lock status.
const u8 prot = dev->prot & ~(1u<<3);
dev->prot = prot | (status>>22 & 1u<<3);
} while(0);
return res;
}
// People should not mess with the state which is the reason
// why the struct is not exposed directly.
static_assert(sizeof(SdmmcDev) == 64, "Wrong SDMMC dev export/import size.");
u32 pico_SDMMC_exportDevState(const u8 devNum, u8 devOut[64])
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
// Check if the device is initialized.
const SdmmcDev *const dev = &g_devs[devNum];
if(dev->type == DEV_TYPE_NONE) return SDMMC_ERR_NO_CARD;
memcpy(devOut, dev, 64);
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_importDevState(const u8 devNum, const u8 devIn[64])
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
// Make sure there is a card inserted.
if(devNum == SDMMC_DEV_CARD && !TMIO_cardDetected()) return SDMMC_ERR_NO_CARD;
// Check if the device is initialized.
SdmmcDev *const dev = &g_devs[devNum];
if(dev->type != DEV_TYPE_NONE) return SDMMC_ERR_INITIALIZED;
memcpy(dev, devIn, 64);
// Update write protection slider state just in case.
dev->prot |= !TMIO_cardWritable();
return SDMMC_ERR_NONE;
}
// TODO: Less controller dependent code.
u32 pico_SDMMC_getDevInfo(const u8 devNum, SdmmcInfo *const infoOut)
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
const SdmmcDev *const dev = &g_devs[devNum];
const TmioPort *const port = &dev->port;
infoOut->type = dev->type;
infoOut->prot = dev->prot;
infoOut->rca = dev->rca;
infoOut->sectors = dev->sectors;
const u32 clkSetting = port->sd_clk_ctrl & 0xFFu;
infoOut->clock = TMIO_HCLK / (clkSetting ? clkSetting<<2 : 2);
memcpy(infoOut->cid, dev->cid, 16);
infoOut->ccc = dev->ccc;
infoOut->busWidth = (port->sd_option & OPTION_BUS_WIDTH1 ? 1 : 4);
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_getCid(const u8 devNum, u32 cidOut[4])
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
if(cidOut != NULL) memcpy(cidOut, g_devs[devNum].cid, 16);
return SDMMC_ERR_NONE;
}
/*#include "fatfs/ff.h" // Needed for the "byte" type used in diskio.h.
#include "fatfs/diskio.h"
u8 SDMMC_getDiskStatus(const u8 devNum)
{
if(devNum > SDMMC_MAX_DEV_NUM) return STA_NODISK | STA_NOINIT;
u8 status = 0;
if(devNum == SDMMC_DEV_CARD)
status = (TMIO_cardDetected() == true ? 0 : STA_NODISK | STA_NOINIT);
const SdmmcDev *const dev = &g_devs[devNum];
status |= (dev->prot != 0 ? STA_PROTECT : 0);
if(dev->type == DEV_TYPE_NONE)
status |= STA_NOINIT;*/
// "Not valid if STA_NODISK is set."
/*if(status & STA_NODISK)
status &= ~STA_PROTECT;*/
// return status;
//}
u32 pico_SDMMC_getSectors(const u8 devNum)
{
if(devNum > SDMMC_MAX_DEV_NUM) return 0;
return g_devs[devNum].sectors;
}
static u32 updateStatus(SdmmcDev *const dev, const bool stopTransmission)
{
TmioPort *const port = &dev->port;
// MMC_STOP_TRANSMISSION: Same CMD for (e)MMC/SD. Relies on the driver returning a proper response.
// MMC_SEND_STATUS: Same CMD for (e)MMC/SD but the argument format differs slightly.
u32 res;
if(stopTransmission) res = TMIO_sendCommand(port, MMC_STOP_TRANSMISSION, 0);
else res = TMIO_sendCommand(port, MMC_SEND_STATUS, (u32)dev->rca<<16);
dev->status = (res == 0 ? port->resp[0] : 0); // Don't update the status with stale data.
return res;
}
// Note: On multi-block read from the last 2 sectors there are no errors reported by the controller
// however the R1 card status may report ADDRESS_OUT_OF_RANGE on next(?) status read.
// This error is normal for (e)MMC and can be ignored.
u32 pico_SDMMC_readSectors(const u8 devNum, u32 sect, void *const buf, const u16 count)
{
if(devNum > SDMMC_MAX_DEV_NUM || count == 0) return SDMMC_ERR_INVAL_PARAM;
// Check if the device is initialized.
SdmmcDev *const dev = &g_devs[devNum];
const u8 devType = dev->type;
if(devType == DEV_TYPE_NONE) return SDMMC_ERR_NO_CARD;
// Set destination buffer and sector count.
TmioPort *const port = &dev->port;
TMIO_setBuffer(port, buf, count);
// Read a single 512 bytes block. Same CMD for (e)MMC/SD.
// Read multiple 512 bytes blocks. Same CMD for (e)MMC/SD.
const u16 readCmd = (count == 1 ? MMC_READ_SINGLE_BLOCK : MMC_READ_MULTIPLE_BLOCK);
if(devType == DEV_TYPE_MMC || devType == DEV_TYPE_SDSC) sect *= 512; // Byte addressing.
u32 res = TMIO_sendCommand(port, readCmd, sect);
if(res != 0)
{
// On error in the middle of multi-block reads the card will be stuck
// in data state and we need to send STOP_TRANSMISSION to bring it
// back to tran state.
// Otherwise for single-block reads just update the status.
updateStatus(dev, count > 1);
return SDMMC_ERR_SECT_RW;
}
return SDMMC_ERR_NONE;
}
// Note: On multi-block write to the last 2 sectors there are no errors reported by the controller
// however the R1 card status may report ADDRESS_OUT_OF_RANGE on next(?) status read.
// This error is normal for (e)MMC and can be ignored.
u32 pico_SDMMC_writeSectors(const u8 devNum, u32 sect, const void *const buf, const u16 count)
{
if(devNum > SDMMC_MAX_DEV_NUM || count == 0) return SDMMC_ERR_INVAL_PARAM;
// Check if the device is initialized.
SdmmcDev *const dev = &g_devs[devNum];
const u8 devType = dev->type;
if(devType == DEV_TYPE_NONE) return SDMMC_ERR_NO_CARD;
// Check if the device is write protected.
if(dev->prot != 0) return SDMMC_ERR_WRITE_PROT;
// Set source buffer and sector count.
TmioPort *const port = &dev->port;
TMIO_setBuffer(port, (void*)buf, count);
// Write a single 512 bytes block. Same CMD for (e)MMC/SD.
// Write multiple 512 bytes blocks. Same CMD for (e)MMC/SD.
const u16 writeCmd = (count == 1 ? MMC_WRITE_BLOCK : MMC_WRITE_MULTIPLE_BLOCK);
if(devType == DEV_TYPE_MMC || devType == DEV_TYPE_SDSC) sect *= 512; // Byte addressing.
const u32 res = TMIO_sendCommand(port, writeCmd, sect);
if(res != 0)
{
// On error in the middle of multi-block writes the card will be stuck
// in data state and we need to send STOP_TRANSMISSION to bring it
// back to tran state.
// Otherwise for single-block writes just update the status.
updateStatus(dev, count > 1);
return SDMMC_ERR_SECT_RW;
}
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_sendCommand(const u8 devNum, MmcCommand *const mmcCmd)
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
SdmmcDev *const dev = &g_devs[devNum];
TmioPort *const port = &dev->port;
TMIO_setBlockLen(port, mmcCmd->blkLen);
TMIO_setBuffer(port, mmcCmd->buf, mmcCmd->count);
const u32 res = TMIO_sendCommand(port, mmcCmd->cmd, mmcCmd->arg);
TMIO_setBlockLen(port, 512); // Restore default block length.
if(res != 0)
{
updateStatus(dev, false);
return SDMMC_ERR_SEND_CMD;
}
memcpy(mmcCmd->resp, port->resp, 16);
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_getLastR1error(const u8 devNum)
{
if(devNum > SDMMC_MAX_DEV_NUM) return 0;
SdmmcDev *const dev = &g_devs[devNum];
const u32 status = dev->status;
dev->status = 0;
return status;
}

240
arm7/source/mmc/sdmmc.h Normal file
View File

@@ -0,0 +1,240 @@
#pragma once
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 profi200
// Possible error codes for most of the functions below.
enum
{
SDMMC_ERR_NONE = 0u, // No error.
SDMMC_ERR_INVAL_PARAM = 1u, // Invalid parameter.
SDMMC_ERR_INITIALIZED = 2u, // The device is already initialized.
SDMMC_ERR_GO_IDLE_STATE = 3u, // GO_IDLE_STATE CMD error.
SDMMC_ERR_SEND_IF_COND = 4u, // SEND_IF_COND CMD error.
SDMMC_ERR_IF_COND_RESP = 5u, // IF_COND response pattern mismatch or unsupported voltage.
SDMMC_ERR_SEND_OP_COND = 6u, // SEND_OP_COND CMD error.
SDMMC_ERR_OP_COND_TMOUT = 7u, // Card initialization timeout.
SDMMC_ERR_VOLT_SUPPORT = 8u, // Voltage not supported.
SDMMC_ERR_ALL_SEND_CID = 9u, // ALL_SEND_CID CMD error.
SDMMC_ERR_SET_SEND_RCA = 10u, // SET/SEND_RELATIVE_ADDR CMD error.
SDMMC_ERR_SEND_CSD = 11u, // SEND_CSD CMD error.
SDMMC_ERR_SELECT_CARD = 12u, // SELECT_CARD CMD error.
SDMMC_ERR_LOCKED = 13u, // Card is locked with a password.
SDMMC_ERR_SEND_EXT_CSD = 14u, // SEND_EXT_CSD CMD error.
SDMMC_ERR_SWITCH_HS = 15u, // Error on switching to high speed mode.
SDMMC_ERR_SET_CLR_CD = 16u, // SET_CLR_CARD_DETECT CMD error.
SDMMC_ERR_SET_BUS_WIDTH = 17u, // Error on switching to a different bus width.
SDMMC_ERR_SEND_STATUS = 18u, // SEND_STATUS CMD error.
SDMMC_ERR_CARD_STATUS = 19u, // The card returned an error via its status.
SDMMC_ERR_NO_CARD = 20u, // Card unitialized or not inserted.
SDMMC_ERR_SECT_RW = 21u, // Sector read/write error.
SDMMC_ERR_WRITE_PROT = 22u, // The card is write protected.
SDMMC_ERR_SEND_CMD = 23u, // An error occured while sending a custom CMD via SDMMC_sendCommand().
SDMMC_ERR_SET_BLOCKLEN = 24u, // SET_BLOCKLEN CMD error.
SDMMC_ERR_LOCK_UNLOCK = 25u, // LOCK_UNLOCK CMD error.
SDMMC_ERR_LOCK_UNLOCK_FAIL = 26u, // Lock/unlock operation failed (R1 status).
SDMMC_ERR_SLEEP_AWAKE = 27u // (e)MMC SLEEP_AWAKE CMD error.
};
// (e)MMC/SD device numbers.
enum
{
SDMMC_DEV_CARD = 0u, // SD card/MMC.
SDMMC_DEV_eMMC = 1u, // Builtin eMMC.
// Alias for internal use only.
SDMMC_MAX_DEV_NUM = SDMMC_DEV_eMMC
};
// Bit definition for SdmmcInfo.prot.
// Each bit 1 = protected.
#define SDMMC_PROT_SLIDER (1u) // SD card write protection slider.
#define SDMMC_PROT_TEMP (1u<<1) // Temporary write protection (CSD).
#define SDMMC_PROT_PERM (1u<<2) // Permanent write protection (CSD).
#define SDMMC_PROT_PASSWORD (1u<<3) // (e)MMC/SD card is password protected.
typedef struct
{
u8 type; // 0 = none, 1 = (e)MMC, 2 = High capacity (e)MMC, 3 = SDSC, 4 = SDHC/SDXC, 5 = SDUC.
u8 prot; // See SDMMC_PROT_... defines above for details.
u16 rca; // Relative Card Address (RCA).
u32 sectors; // Size in 512 byte units.
u32 clock; // The current clock frequency in Hz.
u32 cid[4]; // Raw CID without the CRC.
u16 ccc; // (e)MMC/SD command class support from CSD. One per bit starting at 0.
u8 busWidth; // The current bus width used to talk to the card.
} SdmmcInfo;
typedef struct
{
u16 cmd; // Command. T̲h̲e̲ ̲f̲o̲r̲m̲a̲t̲ ̲i̲s̲ ̲c̲o̲n̲t̲r̲o̲l̲l̲e̲r̲ ̲s̲p̲e̲c̲i̲f̲i̲c̲!̲
u32 arg; // Command argument.
u32 resp[4]; // Card response. Length depends on command.
u32 *buf; // In/out data buffer.
u16 blkLen; // Block length. Usually 512.
u16 count; // Number of blkSize blocks to transfer.
} MmcCommand;
// Mode bits for SDMMC_lockUnlock().
#define SDMMC_LK_CLR_PWD (1u<<1) // Clear password.
#define SDMMC_LK_UNLOCK (0u) // Unlock.
#define SDMMC_LK_LOCK (1u<<2) // Lock.
#define SDMMC_LK_ERASE (1u<<3) // Force erase a locked (e)MMC/SD card.
#define SDMMC_LK_COP (1u<<4) // SD cards only. Card Ownership Protection operation.
#ifdef __cplusplus
extern "C"{
#endif
/**
* @brief Initializes a (e)MMC/SD card device.
*
* @param[in] devNum The device to initialize.
*
* @return Returns SDMMC_ERR_NONE on success or
* one of the errors listed above on failure.
*/
u32 pico_SDMMC_init(const u8 devNum);
/**
* @brief Switches a (e)MMC/SD card device between sleep/awake mode.
* Note that SD cards don't have a true sleep mode.
*
* @param[in] devNum The device.
* @param[in] enabled The mode. true to enable sleep and false to wake up.
*
* @return Returns SDMMC_ERR_NONE on success or
* one of the errors listed above on failure.
*/
u32 pico_SDMMC_setSleepMode(const u8 devNum, const bool enabled);
/**
* @brief Deinitializes a (e)MMC/SD card device.
*
* @param[in] devNum The device to deinitialize.
*
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_INVAL_PARAM on failure.
*/
u32 pico_SDMMC_deinit(const u8 devNum);
/**
* @brief Manage password protection for a (e)MMC/SD card device.
*
* @param[in] devNum The device.
* @param[in] mode The mode of operation. See defines above.
* @param[in] pwd The password buffer pointer.
* @param[in] pwdLen The password length. Maximum 32 for password replace. Otherwise 16.
*
* @return Returns SDMMC_ERR_NONE on success or
* one of the errors listed above on failure.
*/
u32 pico_SDMMC_lockUnlock(const u8 devNum, const u8 mode, const u8 *const pwd, const u8 pwdLen);
/**
* @brief Exports the internal device state for fast init (bootloaders ect.).
*
* @param[in] devNum The device state to export.
* @param devOut A pointer to a u8[60] array.
*
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_INVAL_PARAM/SDMMC_ERR_NO_CARD on failure.
*/
u32 pico_SDMMC_exportDevState(const u8 devNum, u8 devOut[64]);
/**
* @brief Imports a device state for fast init (bootloaders ect.).
* The state should be validated for example with a checksum.
*
* @param[in] devNum The device state to import.
* @param[in] devIn A pointer to a u8[60] array.
*
* @return Returns SDMMC_ERR_NONE on success or
* SDMMC_ERR_INVAL_PARAM/SDMMC_ERR_NO_CARD/SDMMC_ERR_INITIALIZED on failure.
*/
u32 pico_SDMMC_importDevState(const u8 devNum, const u8 devIn[64]);
/**
* @brief Outputs infos about a (e)MMC/SD card device.
*
* @param[in] devNum The device.
* @param infoOut A pointer to a SdmmcInfo struct.
*
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_INVAL_PARAM on failure.
*/
u32 pico_SDMMC_getDevInfo(const u8 devNum, SdmmcInfo *const infoOut);
/**
* @brief Outputs the CID of a (e)MMC/SD card device.
*
* @param[in] devNum The device.
* @param cidOut A u32[4] pointer for storing the CID.
*
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_INVAL_PARAM on failure.
*/
u32 pico_SDMMC_getCid(const u8 devNum, u32 cidOut[4]);
/**
* @brief Returns the DSTATUS bits of a (e)MMC/SD card device. See FatFs diskio.h.
*
* @param[in] devNum The device.
*
* @return Returns the DSTATUS bits or STA_NODISK | STA_NOINIT on failure.
*/
//u8 SDMMC_getDiskStatus(const u8 devNum);
/**
* @brief Outputs the number of sectors for a (e)MMC/SD card device.
*
* @param[in] devNum The device.
*
* @return Returns the number of sectors or 0 on failure.
*/
u32 pico_SDMMC_getSectors(const u8 devNum);
/**
* @brief Reads one or more sectors from a (e)MMC/SD card device.
*
* @param[in] devNum The device.
* @param[in] sect The start sector.
* @param buf The output buffer pointer. NULL for DMA.
* @param[in] count The number of sectors to read.
*
* @return Returns SDMMC_ERR_NONE on success or
* one of the errors listed above on failure.
*/
u32 pico_SDMMC_readSectors(const u8 devNum, u32 sect, void *const buf, const u16 count);
/**
* @brief Writes one or more sectors to a (e)MMC/SD card device.
*
* @param[in] devNum The device.
* @param[in] sect The start sector.
* @param[in] buf The input buffer pointer. NULL for DMA.
* @param[in] count The count
*
* @return Returns SDMMC_ERR_NONE on success or
* one of the errors listed above on failure.
*/
u32 pico_SDMMC_writeSectors(const u8 devNum, u32 sect, const void *const buf, const u16 count);
/**
* @brief Sends a custom command to a (e)MMC/SD card device.
*
* @param[in] devNum The device.
* @param cmd MMC command struct pointer (see above).
*
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_SEND_CMD on failure.
*/
u32 pico_SDMMC_sendCommand(const u8 devNum, MmcCommand *const mmcCmd);
/**
* @brief Returns the R1 card status for a previously failed read/write/custom command.
*
* @param[in] devNum The device.
*
* @return Returns the R1 card status or 0 if there was either no command error or invalid devNum.
*/
u32 pico_SDMMC_getLastR1error(const u8 devNum);
// TODO: TRIM/erase support.
#ifdef __cplusplus
}
#endif

279
arm7/source/mmc/tmio.c Normal file
View File

@@ -0,0 +1,279 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 profi200
#include <stdatomic.h>
#include "tmio.h"
// Using atomic load/store produces better code than volatile
// but still ensures that the status is always read from memory.
#define GET_STATUS(ptr) atomic_load_explicit((ptr), memory_order_relaxed)
#define SET_STATUS(ptr, val) atomic_store_explicit((ptr), (val), memory_order_relaxed)
// ARM7 timer clock = controller clock = CPU clock.
// swiDelay() doesn't seem to be cycle accurate meaning
// one cycle is 4 (?) CPU cycles.
#define INIT_DELAY_FUNC() swi_waitByLoop(TMIO_CLK2DIV(400000u) * 74 / 4)
static u32 g_status[2] = {0};
static rtos_event_t sSdEvent;
__attribute__((always_inline)) static inline u8 port2Controller(const u8 portNum)
{
return portNum / 2;
}
static void tmio1Isr(u32 irqMask) // SD/eMMC.
{
Tmio *const regs = getTmioRegs(0);
g_status[0] |= regs->sd_status;
regs->sd_status = STATUS_CMD_BUSY; // Never acknowledge STATUS_CMD_BUSY.
rtos_signalEvent(&sSdEvent);
// TODO: Some kind of event to notify the main loop for remove/insert.
}
static void tmio2Isr(u32 irqMask) // WiFi SDIO.
{
Tmio *const regs = getTmioRegs(1);
g_status[1] |= regs->sd_status;
regs->sd_status = STATUS_CMD_BUSY; // Never acknowledge STATUS_CMD_BUSY.
}
void TMIO_init(void)
{
rtos_createEvent(&sSdEvent);
// Register ISR and enable IRQs.
rtos_setIrq2Func(RTOS_IRQ2_SDMMC, tmio1Isr);
rtos_setIrq2Func(RTOS_IRQ2_SDIO, tmio2Isr);
rtos_enableIrq2Mask(RTOS_IRQ2_SDMMC);
rtos_enableIrq2Mask(RTOS_IRQ2_SDIO);
// Reset all controllers.
for(u32 i = 0; i < 2; i++)
{
// Setup 32 bit FIFO.
Tmio *const regs = getTmioRegs(i);
regs->sd_fifo32_cnt = FIFO32_CLEAR | FIFO32_EN;
regs->sd_blocklen32 = 512;
regs->sd_blockcount32 = 1;
regs->dma_ext_mode = DMA_EXT_DMA_MODE;
// Reset. Unlike similar controllers no delay is needed.
// Resets the following regs:
// REG_SD_STOP, REG_SD_RESP0-7, REG_SD_STATUS1-2, REG_SD_ERR_STATUS1-2,
// REG_SD_CLK_CTRL, REG_SD_OPTION, REG_SDIO_STATUS.
regs->soft_rst = SOFT_RST_RST;
regs->soft_rst = SOFT_RST_NORST;
regs->sd_portsel = PORTSEL_P0;
regs->sd_blockcount = 1;
regs->sd_status_mask = STATUS_MASK_DEFAULT;
regs->sd_clk_ctrl = SD_CLK_DEFAULT;
regs->sd_blocklen = 512;
regs->sd_option = OPTION_BUS_WIDTH1 | OPTION_UNK14 | OPTION_DEFAULT_TIMINGS;
regs->ext_cdet_mask = EXT_CDET_MASK_ALL;
regs->ext_cdet_dat3_mask = EXT_CDET_DAT3_MASK_ALL;
// Disable SDIO.
regs->sdio_mode = 0;
regs->sdio_status_mask = SDIO_STATUS_MASK_ALL;
regs->ext_sdio_irq = EXT_SDIO_IRQ_MASK_ALL;
}
}
void TMIO_deinit(void)
{
rtos_disableIrq2Mask(RTOS_IRQ2_SDMMC);
rtos_setIrq2Func(RTOS_IRQ2_SDMMC, NULL);
rtos_disableIrq2Mask(RTOS_IRQ2_SDIO);
rtos_setIrq2Func(RTOS_IRQ2_SDIO, NULL);
// Mask all IRQs.
for(u32 i = 0; i < 2; i++)
{
// 32 bit FIFO IRQs.
Tmio *const regs = getTmioRegs(i);
regs->sd_fifo32_cnt = 0; // FIFO and all IRQs disabled/masked.
// Regular IRQs.
regs->sd_status_mask = STATUS_MASK_ALL;
// SDIO IRQs.
regs->sdio_status_mask = SDIO_STATUS_MASK_ALL;
}
}
void TMIO_initPort(TmioPort *const port, const u8 portNum)
{
// Reset port state.
port->portNum = portNum;
port->sd_clk_ctrl = SD_CLK_DEFAULT;
port->sd_blocklen = 512;
port->sd_option = OPTION_BUS_WIDTH1 | OPTION_UNK14 | OPTION_DEFAULT_TIMINGS;
}
// TODO: What if we get rid of setPort() and only use one port per controller?
static void setPort(Tmio *const regs, const TmioPort *const port)
{
// TODO: Can we somehow prevent all these reg writes each time?
// Maybe some kind of dirty flag + active port check?
regs->sd_portsel = port->portNum % 2u;
regs->sd_clk_ctrl = port->sd_clk_ctrl;
const u16 blocklen = port->sd_blocklen;
regs->sd_blocklen = blocklen;
regs->sd_option = port->sd_option;
regs->sd_blocklen32 = blocklen;
}
bool TMIO_cardDetected(void)
{
return getTmioRegs(0)->sd_status & STATUS_DETECT;
}
bool TMIO_cardWritable(void)
{
return getTmioRegs(0)->sd_status & STATUS_NO_WRPROT;
}
void TMIO_powerupSequence(TmioPort *const port)
{
port->sd_clk_ctrl = SD_CLK_EN | SD_CLK_DEFAULT;
setPort(getTmioRegs(port2Controller(port->portNum)), port);
INIT_DELAY_FUNC();
}
static void getResponse(const Tmio *const regs, TmioPort *const port, const u16 cmd)
{
// We could check for response type none as well but it's not worth it.
if((cmd & CMD_RESP_MASK) != CMD_RESP_R2)
{
port->resp[0] = regs->sd_resp[0];
}
else // 136 bit R2 responses need special treatment...
{
u32 resp[4];
for(u32 i = 0; i < 4; i++) resp[i] = regs->sd_resp[i];
port->resp[0] = resp[3]<<8 | resp[2]>>24;
port->resp[1] = resp[2]<<8 | resp[1]>>24;
port->resp[2] = resp[1]<<8 | resp[0]>>24;
port->resp[3] = resp[0]<<8; // TODO: Add the missing CRC7 and bit 0?
}
}
// Note: Using STATUS_DATA_END to detect transfer end doesn't work reliably
// because STATUS_DATA_END fires before we even read anything from FIFO
// on single block read transfer.
static void doCpuTransfer(Tmio *const regs, const u16 cmd, u8 *buf, const u32 *const statusPtr)
{
const u32 blockLen = regs->sd_blocklen;
u32 blockCount = regs->sd_blockcount;
vu32 *const fifo = getTmioFifo(regs);
if(cmd & CMD_DATA_R)
{
while((GET_STATUS(statusPtr) & STATUS_MASK_ERR) == 0 && blockCount > 0)
{
if(regs->sd_fifo32_cnt & FIFO32_FULL) // RX ready.
{
const u8 *const blockEnd = buf + blockLen;
do
{
if((uintptr_t)buf % 4 == 0)
{
*((u32*)buf) = *fifo;
}
else
{
const u32 tmp = *fifo;
buf[0] = tmp;
buf[1] = tmp>>8;
buf[2] = tmp>>16;
buf[3] = tmp>>24;
}
buf += 4;
} while(buf < blockEnd);
blockCount--;
}
else rtos_waitEvent(&sSdEvent, false, true);
}
}
else
{
// TODO: Write first block ahead of time?
// gbatek Command/Param/Response/Data at bottom of page.
while((GET_STATUS(statusPtr) & STATUS_MASK_ERR) == 0 && blockCount > 0)
{
if(!(regs->sd_fifo32_cnt & FIFO32_NOT_EMPTY)) // TX request.
{
const u8 *const blockEnd = buf + blockLen;
do
{
if((uintptr_t)buf % 4 == 0)
{
*fifo = *((u32*)buf);
}
else
{
u32 tmp = buf[0];
tmp |= (u32)buf[1]<<8;
tmp |= (u32)buf[2]<<16;
tmp |= (u32)buf[3]<<24;
*fifo = tmp;
}
buf += 4;
} while(buf < blockEnd);
blockCount--;
}
else rtos_waitEvent(&sSdEvent, false, true);
}
}
}
u32 TMIO_sendCommand(TmioPort *const port, const u16 cmd, const u32 arg)
{
const u8 controller = port2Controller(port->portNum);
Tmio *const regs = getTmioRegs(controller);
// Clear status before sending another command.
u32 *const statusPtr = &g_status[controller];
SET_STATUS(statusPtr, 0);
setPort(regs, port);
const u16 blocks = port->blocks;
regs->sd_blockcount = blocks; // sd_blockcount32 doesn't need to be set.
regs->sd_stop = STOP_AUTO_STOP; // Auto STOP_TRANSMISSION (CMD12) on multi-block transfer.
regs->sd_arg = arg;
// We don't need FIFO IRQs when using DMA. buf = NULL means DMA.
u8 *buf = port->buf;
u16 f32Cnt = FIFO32_CLEAR | FIFO32_EN;
if(buf != NULL) f32Cnt |= (cmd & CMD_DATA_R ? FIFO32_FULL_IE : FIFO32_NOT_EMPTY_IE);
regs->sd_fifo32_cnt = f32Cnt;
regs->sd_cmd = (blocks > 1 ? CMD_MULTI_DATA | cmd : cmd); // Start.
// TODO: Benchmark if this order is ideal?
// Response end comes immediately after the
// command so we need to check before __wfi().
// On error response end still fires.
while((GET_STATUS(statusPtr) & STATUS_RESP_END) == 0) rtos_waitEvent(&sSdEvent, false, true);
getResponse(regs, port, cmd);
if((cmd & CMD_DATA_EN) != 0)
{
// If we have to transfer data do so now.
if(buf != NULL) doCpuTransfer(regs, cmd, buf, statusPtr);
// Wait for data end if needed.
// On error data end still fires.
while((GET_STATUS(statusPtr) & STATUS_DATA_END) == 0) rtos_waitEvent(&sSdEvent, false, true);
}
// STATUS_CMD_BUSY is no longer set at this point.
return GET_STATUS(statusPtr) & STATUS_MASK_ERR;
}

414
arm7/source/mmc/tmio.h Normal file
View File

@@ -0,0 +1,414 @@
#pragma once
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 profi200
#include <assert.h>
#include <stddef.h>
#include <nds/ndstypes.h>
#include <libtwl/sys/swi.h>
#include <libtwl/rtos/rtosIrq.h>
#include <libtwl/rtos/rtosEvent.h>
// For simplicity we will name the accessible 2 controllers 1 and 2.
// The real controller number is in the comment.
#define TMIO1_REGS_BASE (0x04004800u) // Controller 1.
#define TMIO2_REGS_BASE (0x04004A00u) // Controller 2.
#define TMIO_HCLK (33513982u) // In Hz.
typedef struct
{
vu16 sd_cmd; // 0x000
vu16 sd_portsel; // 0x002
vu32 sd_arg; // 0x004 SD_ARG0 and SD_ARG1 combined.
vu16 sd_stop; // 0x008
vu16 sd_blockcount; // 0x00A
const vu32 sd_resp[4]; // 0x00C SD_RESP0-7 16 bit reg pairs combined.
vu32 sd_status; // 0x01C SD_STATUS1 and SD_STATUS2 combined.
vu32 sd_status_mask; // 0x020 SD_STATUS1_MASK and SD_STATUS2_MASK combined.
vu16 sd_clk_ctrl; // 0x024
vu16 sd_blocklen; // 0x026
vu16 sd_option; // 0x028 Card detect timer, data timeout and bus width.
u8 _0x2a[2];
const vu32 sd_err_status; // 0x02C SD_ERR_STATUS1 and SD_ERR_STATUS2 combined.
vu16 sd_fifo; // 0x030
u8 _0x32[2];
vu16 sdio_mode; // 0x034
vu16 sdio_status; // 0x036
vu16 sdio_status_mask; // 0x038
u8 _0x3a[0x9e];
vu16 dma_ext_mode; // 0x0D8
u8 _0xda[6];
vu16 soft_rst; // 0x0E0
const vu16 revision; // 0x0E2 Controller version/revision?
u8 _0xe4[0xe];
vu16 unkF2; // 0x0F2 Power related? Default 0. Other values do nothing?
vu16 ext_sdio_irq; // 0x0F4 Port 1/2/3 SDIO IRQ control.
const vu16 ext_wrprot; // 0x0F6 Apparently for eMMC.
vu16 ext_cdet; // 0x0F8 Card detect status.
vu16 ext_cdet_dat3; // 0x0FA DAT3 card detect status.
vu16 ext_cdet_mask; // 0x0FC Card detect mask (IRQ).
vu16 ext_cdet_dat3_mask; // 0x0FE DAT3 card detect mask (IRQ).
vu16 sd_fifo32_cnt; // 0x100
u8 _0x102[2];
vu16 sd_blocklen32; // 0x104
u8 _0x106[2];
vu16 sd_blockcount32; // 0x108
u8 _0x10a[2];
vu32 sd_fifo32; // 0x10C Note: This is in the FIFO region on ARM11 (3DS).
} Tmio;
#ifdef __cplusplus
extern "C"
{
#endif
static_assert(offsetof(Tmio, sd_fifo32) == 0x10C, "Error: Member sd_fifo32 of Tmio is not at offset 0x10C!");
__attribute__((always_inline)) static inline Tmio* getTmioRegs(const u8 controller)
{
return (controller == 0 ? (Tmio*)TMIO1_REGS_BASE : (Tmio*)TMIO2_REGS_BASE);
}
__attribute__((always_inline)) static inline vu32* getTmioFifo(Tmio *const regs)
{
return &regs->sd_fifo32;
}
#ifdef __cplusplus
}
#endif
// REG_SD_CMD
// Auto response supported commands:
// CMD0, CMD2, CMD3 (only SD?), CMD7 (only select?), CMD9, CMD10, CMD12, CMD13,
// CMD16, CMD17, CMD18, CMD25, CMD28, CMD55, ACMD6, ACMD23, ACMD42, ACMD51.
//
// When using auto response leave bits 11-13 unset (zero).
// Bit 0-5 command index.
#define CMD_ACMD (1u<<6) // Application command.
#define CMD_RESP_AUTO (0u) // Response type auto. Only works with certain commands.
#define CMD_RESP_NONE (3u<<8) // Response type none.
#define CMD_RESP_R1 (4u<<8) // Response type R1 48 bit.
#define CMD_RESP_R5 (CMD_RESP_R1) // Response type R5 48 bit.
#define CMD_RESP_R6 (CMD_RESP_R1) // Response type R6 48 bit.
#define CMD_RESP_R7 (CMD_RESP_R1) // Response type R7 48 bit.
#define CMD_RESP_R1b (5u<<8) // Response type R1b 48 bit + busy.
#define CMD_RESP_R5b (CMD_RESP_R1b) // Response type R5b 48 bit + busy.
#define CMD_RESP_R2 (6u<<8) // Response type R2 136 bit.
#define CMD_RESP_R3 (7u<<8) // Response type R3 48 bit OCR without CRC.
#define CMD_RESP_R4 (CMD_RESP_R3) // Response type R4 48 bit OCR without CRC.
#define CMD_RESP_MASK (CMD_RESP_R3)
#define CMD_DATA_EN (1u<<11) // Data transfer enable.
#define CMD_DATA_R (1u<<12) // Data transfer direction read.
#define CMD_DATA_W (0u) // Data transfer direction write.
#define CMD_MULTI_DATA (1u<<13) // Multi block transfer (auto STOP_TRANSMISSION).
#define CMD_SEC_SDIO (1u<<14) // Security/SDIO command.
// REG_SD_PORTSEL
#define PORTSEL_P0 (0u) // Controller port 0.
#define PORTSEL_P1 (1u) // Controller port 1.
#define PORTSEL_P2 (2u) // Controller port 2.
#define PORTSEL_P3 (3u) // Controller port 3.
#define PORTSEL_MASK (PORTSEL_P3)
// Bit 8-9 number of supported ports?
#define PORTSEL_UNK10 (1u<<10) // Unknown writable bit 10?
// REG_SD_STOP
#define STOP_STOP (1u) // Abort data transfer and send STOP_TRANSMISSION CMD.
#define STOP_AUTO_STOP (1u<<8) // Automatically send STOP_TRANSMISSION on multi-block transfer end.
// REG_SD_STATUS1/2 Write 0 to acknowledge a bit.
// REG_SD_STATUS1/2_MASK (M) = Maskable bit. 1 = disabled.
// Unmaskable bits act as status only, don't trigger IRQs and can't be acknowledged.
#define STATUS_RESP_END (1u) // (M) Response end.
#define STATUS_DATA_END (1u<<2) // (M) Data transfer end (triggers after last block).
#define STATUS_REMOVE (1u<<3) // (M) Card got removed.
#define STATUS_INSERT (1u<<4) // (M) Card got inserted. Set at the same time as DETECT.
#define STATUS_DETECT (1u<<5) // Card detect status (SD_OPTION detection timer). 1 = inserted.
#define STATUS_NO_WRPROT (1u<<7) // Write protection slider unlocked (low).
#define STATUS_DAT3_REMOVE (1u<<8) // (M) Card DAT3 got removed (low).
#define STATUS_DAT3_INSERT (1u<<9) // (M) Card DAT3 got inserted (high).
#define STATUS_DAT3_DETECT (1u<<10) // Card DAT3 status. 1 = inserted.
#define STATUS_ERR_CMD_IDX (1u<<16) // (M) Bad CMD index in response.
#define STATUS_ERR_CRC (1u<<17) // (M) Bad CRC in response.
#define STATUS_ERR_STOP_BIT (1u<<18) // (M) Stop bit error. Failed to recognize response frame end?
#define STATUS_ERR_DATA_TIMEOUT (1u<<19) // (M) Response data timeout.
#define STATUS_ERR_RX_OVERF (1u<<20) // (M) Receive FIFO overflow.
#define STATUS_ERR_TX_UNDERF (1u<<21) // (M) Send FIFO underflow.
#define STATUS_ERR_CMD_TIMEOUT (1u<<22) // (M) Response start bit timeout.
#define STATUS_SD_BUSY (1u<<23) // SD card signals busy if this bit is 0 (DAT0 held low).
#define STATUS_RX_RDY (1u<<24) // (M) FIFO ready for read.
#define STATUS_TX_REQ (1u<<25) // (M) FIFO write request.
// Bit 27 is maskable. Purpose unknown.
// Bit 29 exists (not maskable). Signals when clock divider changes are allowed?
#define STATUS_CMD_BUSY (1u<<30) // Command register busy.
#define STATUS_ERR_ILL_ACC (1u<<31) // (M) Illegal access error. TODO: What does that mean?
#define STATUS_MASK_ALL (0xFFFFFFFFu)
#define STATUS_MASK_DEFAULT ((1u<<27) | STATUS_TX_REQ | STATUS_RX_RDY | \
STATUS_DAT3_INSERT | STATUS_DAT3_REMOVE)
#define STATUS_MASK_ERR (STATUS_ERR_ILL_ACC | STATUS_ERR_CMD_TIMEOUT | STATUS_ERR_TX_UNDERF | \
STATUS_ERR_RX_OVERF | STATUS_ERR_DATA_TIMEOUT | STATUS_ERR_STOP_BIT | \
STATUS_ERR_CRC | STATUS_ERR_CMD_IDX)
// REG_SD_CLK_CTRL
#define SD_CLK_DIV_2 (0u) // Clock divider 2.
#define SD_CLK_DIV_4 (1u) // Clock divider 4.
#define SD_CLK_DIV_8 (1u<<1) // Clock divider 8.
#define SD_CLK_DIV_16 (1u<<2) // Clock divider 16.
#define SD_CLK_DIV_32 (1u<<3) // Clock divider 32.
#define SD_CLK_DIV_64 (1u<<4) // Clock divider 64.
#define SD_CLK_DIV_128 (1u<<5) // Clock divider 128.
#define SD_CLK_DIV_256 (1u<<6) // Clock divider 256.
#define SD_CLK_DIV_512 (1u<<7) // Clock divider 512.
#define SD_CLK_EN (1u<<8) // Clock enable.
#define SD_CLK_PWR_SAVE (1u<<9) // Disables clock on idle.
// Bit 10 is writable... at least according to gbatek (can't confirm). Purpose unknown.
// Outputs the matching divider for clk.
// Shift the output right by 2 to get the value for REG_SD_CLK_CTRL.
#define TMIO_CLK2DIV(clk) \
({ \
u32 __shift = 1; \
while((clk) < TMIO_HCLK>>__shift) ++__shift; \
1u<<__shift; \
})
// Clock off by default.
// Nearest possible for 400 kHz is 261.827984375 kHz.
#define SD_CLK_DEFAULT (TMIO_CLK2DIV(400000)>>2)
// REG_SD_OPTION
// Note on card detection time:
// The card detection timer starts only on inserting cards (including cold boot with inserted card)
// and when mapping ports between controllers. Card power doesn't have any effect on the timer.
//
// Bit 0-3 card detect timer 0x400<<x HCLKs. 0xF timer test (0x100 HCLKs).
// Bit 4-7 data timeout 0x2000<<x SDCLKs. 0xF timeout test (0x100 SDCLKs).
#define OPTION_UNK14 (1u<<14) // "no C2 module" What the fuck is a C2 module?
#define OPTION_BUS_WIDTH4 (0u) // 4 bit bus width.
#define OPTION_BUS_WIDTH1 (1u<<15) // 1 bit bus width.
// Card detect time: 0x400<<8 / 33513982 = 0.007821929 seconds.
// Data timeout: 0x2000<<11 / (33513982 / 2) = 1.001206959 seconds.
#define OPTION_DEFAULT_TIMINGS (11u<<4 | 8u)
// REG_SD_ERR_STATUS1/2 Write 0 to acknowledge a bit.
// TODO: Are all of these actually supported on this controller?
#define ERR_RESP_CMD_IDX (1u) // Manual command index error in response.
#define ERR_RESP_CMD12_IDX (1u<<1) // Auto command index error in response.
#define ERR_RESP_STOP_BIT (1u<<2) // Manual command response stop bit error.
#define ERR_RESP_STOP_BIT_CMD12 (1u<<3) // Auto command response stop bit error.
#define ERR_STOP_BIT_DATA_READ (1u<<4) // Stop bit error in read data.
#define ERR_STOP_BIT_WR_CRC (1u<<5) // Stop bit error for write CRC status. What the hell does that mean?
#define ERR_CMD_RESP_CRC (1u<<8) // Manual command response CRC error.
#define ERR_CMD12_RESP_CRC (1u<<9) // Auto command response CRC error.
#define ERR_DATA_READ_CRC (1u<<10) // CRC error for read data.
#define ERR_WR_CRC_STAT (1u<<11) // "CRC error for Write CRC status for a write command". What the hell does that mean?
// Bit 13 always 1.
#define ERR_CMD_RESP_TMOUT (1u<<16) // Manual command response timeout.
#define ERR_CMD12_RESP_TMOUT (1u<<17) // Auto command response timeout.
// TODO: Add the correct remaining ones.
// REG_SDIO_MODE
#define SDIO_MODE_SDIO_IRQ_EN (1u) // SDIO IRQ enable (DAT1 low).
#define SDIO_MODE_UNK2_EN (1u<<2) // IRQ on "read wait" requests?
#define SDIO_MODE_UNK8 (1u<<8) // Aborts command and data transfer?
#define SDIO_MODE_UNK9 (1u<<9) // Aborts command but not data transfer? CMD52 related.
// REG_SDIO_STATUS Write 0 to acknowledge a bit.
// REG_SDIO_STATUS_MASK (M) = Maskable bit. 1 = disabled.
#define SDIO_STATUS_SDIO_IRQ (1u) // (M) SDIO IRQ (DAT1 low).
#define SDIO_STATUS_UNK1_IRQ (1u<<1) // (M) IRQ once CMD52 can be used after abort?
#define SDIO_STATUS_UNK2_IRQ (1u<<2) // (M) Related to SDIO_MODE_UNK2_EN?
#define SDIO_STATUS_UNK14_IRQ (1u<<14) // (M) Related to SDIO_MODE_UNK9?
#define SDIO_STATUS_UNK15_IRQ (1u<<15) // (M) Related to SDIO_MODE_UNK2_EN?
#define SDIO_STATUS_MASK_ALL (0xFFFFu)
// REG_DMA_EXT_MODE
#define DMA_EXT_CPU_MODE (0u) // Disables DMA requests. Actually also turns off the 32 bit FIFO.
#define DMA_EXT_DMA_MODE (1u<<1) // Enables DMA requests.
#define DMA_EXT_UNK5 (1u<<5) // "Buffer status mode"?
// REG_SOFT_RST
#define SOFT_RST_RST (0u) // Reset.
#define SOFT_RST_NORST (1u) // No reset.
// REG_EXT_SDIO_IRQ
#define EXT_SDIO_IRQ_P1 (1u) // Port 1 SDIO IRQ (DAT1 low). Write 0 to acknowledge.
#define EXT_SDIO_IRQ_P2 (1u<<1) // Port 2 SDIO IRQ (DAT1 low). Write 0 to acknowledge.
#define EXT_SDIO_IRQ_P3 (1u<<2) // Port 3 SDIO IRQ (DAT1 low). Write 0 to acknowledge.
#define EXT_SDIO_IRQ_P1_EN (1u<<4) // Port 1 SDIO IRQ enable (controller).
#define EXT_SDIO_IRQ_P2_EN (1u<<5) // Port 2 SDIO IRQ enable (controller).
#define EXT_SDIO_IRQ_P3_EN (1u<<6) // Port 3 SDIO IRQ enable (controller).
#define EXT_SDIO_IRQ_P1_MASK (1u<<8) // Port 1 SDIO IRQ mask. 1 = disable IRQ (CPU).
#define EXT_SDIO_IRQ_P2_MASK (1u<<9) // Port 2 SDIO IRQ mask. 1 = disable IRQ (CPU).
#define EXT_SDIO_IRQ_P3_MASK (1u<<10) // Port 3 SDIO IRQ mask. 1 = disable IRQ (CPU).
#define EXT_SDIO_IRQ_MASK_ALL (EXT_SDIO_IRQ_P3_MASK | EXT_SDIO_IRQ_P2_MASK | EXT_SDIO_IRQ_P1_MASK)
// REG_EXT_WRPROT Each bit 1 = write protected unlike SD_STATUS.
#define EXT_WRPROT_P1 (1u)
#define EXT_WRPROT_P2 (1u<<1)
#define EXT_WRPROT_P3 (1u<<2)
// REG_EXT_CDET Acknowledgeable?
// REG_EXT_CDET_MASK (M) = Maskable bit. 1 = disabled (no IRQ).
#define EXT_CDET_P1_REMOVE (1u) // (M) Port 1 card got removed.
#define EXT_CDET_P1_INSERT (1u<<1) // (M) Port 1 card got inserted. TODO: With detection timer?
#define EXT_CDET_P1_DETECT (1u<<2) // Port 1 card detect status. 1 = inserted. TODO: With detection timer?
#define EXT_CDET_P2_REMOVE (1u<<3) // (M) Port 2 card got removed.
#define EXT_CDET_P2_INSERT (1u<<4) // (M) Port 2 card got inserted. TODO: With detection timer?
#define EXT_CDET_P2_DETECT (1u<<5) // Port 2 card detect status. 1 = inserted. TODO: With detection timer?
#define EXT_CDET_P3_REMOVE (1u<<6) // (M) Port 3 card got removed.
#define EXT_CDET_P3_INSERT (1u<<7) // (M) Port 3 card got inserted. TODO: With detection timer?
#define EXT_CDET_P3_DETECT (1u<<8) // Port 3 card detect status. 1 = inserted. TODO: With detection timer?
#define EXT_CDET_MASK_ALL (0xFFFFu)
// REG_EXT_CDET_DAT3 Acknowledgeable?
// REG_EXT_CDET_DAT3_MASK (M) = Maskable bit. 1 = disabled (no IRQ).
#define EXT_CDET_DAT3_P1_REMOVE (1u) // (M) Port 1 card DAT3 got removed (low).
#define EXT_CDET_DAT3_P1_INSERT (1u<<1) // (M) Port 1 card DAT3 got inserted (high).
#define EXT_CDET_DAT3_P1_DETECT (1u<<2) // Port 1 card DAT3 status. 1 = inserted.
#define EXT_CDET_DAT3_P2_REMOVE (1u<<3) // (M) Port 2 card DAT3 got removed (low).
#define EXT_CDET_DAT3_P2_INSERT (1u<<4) // (M) Port 2 card DAT3 got inserted (high).
#define EXT_CDET_DAT3_P2_DETECT (1u<<5) // Port 2 card DAT3 status. 1 = inserted.
#define EXT_CDET_DAT3_P3_REMOVE (1u<<6) // (M) Port 3 card DAT3 got removed (low).
#define EXT_CDET_DAT3_P3_INSERT (1u<<7) // (M) Port 3 card DAT3 got inserted (high).
#define EXT_CDET_DAT3_P3_DETECT (1u<<8) // Port 3 card DAT3 status. 1 = inserted.
#define EXT_CDET_DAT3_MASK_ALL (0xFFFFu)
// REG_SD_FIFO32_CNT
// Bit 0 unknown, non-writable.
#define FIFO32_EN (1u<<1) // Enables the 32 bit FIFO.
#define FIFO32_FULL (1u<<8) // FIFO is full.
#define FIFO32_NOT_EMPTY (1u<<9) // FIFO is not empty. Inverted bit. 0 means empty.
#define FIFO32_CLEAR (1u<<10) // Clears the FIFO.
#define FIFO32_FULL_IE (1u<<11) // FIFO full IRQ enable.
#define FIFO32_NOT_EMPTY_IE (1u<<12) // FIFO not empty IRQ enable.
typedef struct
{
u8 portNum;
u16 sd_clk_ctrl;
u16 sd_blocklen; // Also sd_blocklen32.
u16 sd_option;
void *buf;
u16 blocks;
u32 resp[4]; // Little endian, MSB first.
} TmioPort;
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Initializes the tmio driver.
*/
void TMIO_init(void);
/**
* @brief Deinitializes the tmio driver.
*/
void TMIO_deinit(void);
/**
* @brief Initializes a tmio port to defaults.
*
* @param port A pointer to the port struct.
* @param[in] portNum The port number.
*/
void TMIO_initPort(TmioPort *const port, const u8 portNum);
/**
* @brief Checks if a MMC/SD card is inserted.
*
* @return Returns true if a card is inserted.
*/
bool TMIO_cardDetected(void);
/**
* @brief Checks if the write protect slider is set to locked.
*
* @return Returns true if the card is unlocked.
*/
bool TMIO_cardWritable(void);
/**
* @brief Handles the device specific powerup sequence including the 74 clocks.
*
* @param port A pointer to the port struct.
*/
void TMIO_powerupSequence(TmioPort *const port);
/**
* @brief Sends a command.
*
* @param port A pointer to the port struct.
* @param[in] cmd The command.
* @param[in] arg The argument for the command.
*
* @return Returns 0 on success otherwise see REG_SD_STATUS1/2 bits.
*/
u32 TMIO_sendCommand(TmioPort *const port, const u16 cmd, const u32 arg);
/**
* @brief Sets the clock for a tmio port.
*
* @param port A pointer to the port struct.
* @param[in] clk The target clock in Hz.
*/
__attribute__((always_inline)) static inline void TMIO_setClock(TmioPort *const port, const u32 clk)
{
port->sd_clk_ctrl = SD_CLK_PWR_SAVE | SD_CLK_EN | TMIO_CLK2DIV(clk)>>2;
}
/**
* @brief Sets the transfer block length for a tmio port.
*
* @param port A pointer to the port struct.
* @param[in] blockLen The block length. Caution: Provide a buffer with multiple of 4 size regardless of block length.
*/
__attribute__((always_inline)) static inline void TMIO_setBlockLen(TmioPort *const port, u16 blockLen)
{
if(blockLen > 512) blockLen = 512;
port->sd_blocklen = blockLen;
}
/**
* @brief Sets the bus width for a tmio port.
*
* @param port A pointer to the port struct.
* @param[in] width The bus width.
*/
__attribute__((always_inline)) static inline void TMIO_setBusWidth(TmioPort *const port, const u8 width)
{
port->sd_option = (width == 4 ? OPTION_BUS_WIDTH4 : OPTION_BUS_WIDTH1) |
OPTION_UNK14 | OPTION_DEFAULT_TIMINGS;
}
/**
* @brief Sets a transfer buffer for a tmio port.
*
* @param port A pointer to the port struct.
* @param buf The buffer pointer.
* @param[in] blocks The number of blocks to transfer.
*/
__attribute__((always_inline)) static inline void TMIO_setBuffer(TmioPort *const port, void *buf, const u16 blocks)
{
port->buf = buf;
port->blocks = blocks;
}
#ifdef __cplusplus
}
#endif

BIN
arm9/data/font.nft2 Normal file

Binary file not shown.

245
arm9/loader9.ld Normal file
View File

@@ -0,0 +1,245 @@
/*--------------------------------------------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public License,
v. 2.0. If a copy of the MPL was not distributed with this file, You can
obtain one at https://mozilla.org/MPL/2.0/.
--------------------------------------------------------------------------------*/
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
MEMORY
{
itcm : ORIGIN = 0x01000000, LENGTH = 32K
vram : ORIGIN = 0x06800000, LENGTH = 128K
dtcm : ORIGIN = 0x07800000, LENGTH = 16K
}
PHDRS
{
main PT_LOAD FLAGS(7);
itcm PT_LOAD FLAGS(7);
dtcm PT_LOAD FLAGS(7);
}
__heap_end = ORIGIN(vram) + LENGTH(vram);
SECTIONS
{
.crt0 :
{
__text_start = . ;
KEEP (*(.crt0))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.text : /* ALIGN (4): */
{
*(EXCLUDE_FILE(*.itcm* *.vectors* *.twl*) .text)
*(EXCLUDE_FILE(*.itcm* *.vectors* *.twl*) .stub)
*(EXCLUDE_FILE(*.itcm* *.vectors* *.twl*) .text.*)
KEEP (*(SORT_NONE(.init)))
*(.text.*)
/* __patch_cardireadcard_start = .; */
/* KEEP (*(.patch_cardireadcard)) */
/* __patch_cardireadcard_end = .; */
*(.stub)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
*(.gnu.linkonce.t*)
__glue_start = ABSOLUTE(.);
*(.glue_7)
*(.glue_7t)
__glue_end = ABSOLUTE(.);
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.fini :
{
KEEP (*(.fini))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main =0xff
__text_end = . ;
.rodata :
{
*(.rodata)
*all.rodata*(*)
*(.roda)
*(.rodata.*)
*(.gnu.linkonce.r*)
SORT(CONSTRUCTORS)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >vram :main
__exidx_start = .;
.ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } >vram :main
__exidx_end = .;
/* Ensure the __preinit_array_start label is properly aligned. We
could instead move the label definition inside the section, but
the linker would then create the section even if it turns out to
be empty, which isn't pretty. */
. = ALIGN(32 / 8);
.init_array :
{
PROVIDE (__preinit_array_start = .);
PROVIDE (__bothinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE (__preinit_array_end = .);
PROVIDE (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE (__init_array_end = .);
PROVIDE (__bothinit_array_end = .);
} >vram :main
.fini_array :
{
PROVIDE (__fini_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
/* Required by pico-exitprocs.c. */
KEEP (*(.fini_array*))
PROVIDE (__fini_array_end = .);
} >vram :main
.ctors :
{
/* gcc uses crtbegin.o to find the start of the constructors, so
we make sure it is first. Because this is a wildcard, it
doesn't matter if the user does not actually link against
crtbegin.o; the linker won't look for a file to match a
wildcard. The wildcard also means that it doesn't matter which
directory crtbegin.o is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.eh_frame :
{
KEEP (*(.eh_frame))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.gcc_except_table :
{
*(.gcc_except_table)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.jcr : { KEEP (*(.jcr)) } >vram = 0
__got_start = . ;
.got :
{
*(.got.plt)
*(.got)
*(.rel.got)
} >vram :main = 0
__got_end = . ;
.data ALIGN(4) : {
__data_start = ABSOLUTE(.);
*(.data)
*(.data.*)
*(.gnu.linkonce.d*)
CONSTRUCTORS
. = ALIGN(4);
__data_end = ABSOLUTE(.) ;
} >vram :main = 0xff
__data_end = . ;
__bss_vma = . ;
.dtcm :
{
__dtcm_lma = LOADADDR(.dtcm);
__dtcm_start = ABSOLUTE(.);
*(.dtcm)
*(.dtcm.*)
. = ALIGN(4);
__dtcm_end = ABSOLUTE(.);
} >dtcm AT>vram :dtcm = 0xff
.itcm :
{
__itcm_lma = LOADADDR(.itcm);
__itcm_start = ABSOLUTE(.);
*(.itcm)
*.itcm*(.text .stub .text.*)
. = ALIGN(4);
__itcm_end = ABSOLUTE(.);
} >itcm AT>vram :itcm = 0xff
.bss __bss_vma (NOLOAD):
{
__bss_start = ABSOLUTE(.);
__bss_start__ = ABSOLUTE(.);
*(.dynbss)
*(.gnu.linkonce.b*)
*(.bss*)
*(COMMON)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :NONE
__bss_end = . ;
__bss_end__ = . ;
__heap_start = . ;
_end = . ;
__end__ = . ;
PROVIDE (end = _end);
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
.stack 0x80000 : { _stack = .; *(.stack) }
/* These must appear regardless of . */
}

146
arm9/source/Arm7Patcher.cpp Normal file
View File

@@ -0,0 +1,146 @@
#include "common.h"
#include <libtwl/mem/memNtrWram.h>
#include "ModuleParamsLocator.h"
#include "SdkVersion.h"
#include "sharedMemory.h"
#include "gameCode.h"
#include "cache.h"
#include "errorDisplay/ErrorDisplay.h"
#include "patches/PatchCollection.h"
#include "patches/PatchContext.h"
#include "patches/platform/LoaderPlatform.h"
#include "patches/arm7/sdk2to4/CardiTaskThreadPatch.h"
#include "patches/arm7/sdk5/CardiDoTaskFromArm9Patch.h"
#include "patches/arm7/OsGetInitArenaLoPatch.h"
#include "patches/arm7/DisableArm7WramClearPatch.h"
#include "patches/arm7/sdk5/Sdk5DsiSdCardRedirectPatch.h"
#include "patches/arm7/PokemonDownloaderArm7Patch.h"
#include "Arm7Patcher.h"
void* Arm7Patcher::ApplyPatches(const LoaderPlatform* loaderPlatform) const
{
auto romHeader = (const nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.romHeader;
auto twlRomHeader = (const nds_header_twl_t*)TWL_SHARED_MEMORY->twlRomHeader;
ModuleParamsLocator moduleParamsLocator;
auto moduleParams = moduleParamsLocator.FindModuleParams(romHeader);
SdkVersion sdkVersion = moduleParams ? moduleParams->sdkVersion : 0u;
if (!moduleParams && romHeader->gameCode == GAMECODE("AS2E"))
{
// Spider-Man 2 (USA) is probably the only game without module params
sdkVersion = 0x02004F50;
}
PatchCollection patchCollection;
LOG_DEBUG("Arm7 region: 0x%x - 0x%x\n", romHeader->arm7LoadAddress, romHeader->arm7LoadAddress + romHeader->arm7Size);
PatchContext patchContext
{
(void*)romHeader->arm7LoadAddress,
romHeader->arm7Size,
(romHeader->IsTwlRom()) ? (void*)twlRomHeader->arm7iLoadAddress : nullptr,
(romHeader->IsTwlRom()) ? twlRomHeader->arm7iSize : 0,
sdkVersion,
romHeader->gameCode,
loaderPlatform
};
void* patchSpaceStart = nullptr;
if (sdkVersion != 0)
{
mem_setNtrWramMapping(MEM_NTR_WRAM_ARM9, MEM_NTR_WRAM_ARM9);
auto arm7ArenaPatch = new OsGetInitArenaLoPatch();
patchCollection.AddPatch(arm7ArenaPatch);
if (romHeader->unitCode == 0) // seems only present on NITRO, not on HYBRID or LIMITED
patchCollection.AddPatch(new DisableArm7WramClearPatch());
if (sdkVersion.IsTwlSdk())
{
if (gIsDsiMode && romHeader->IsTwlRom() && twlRomHeader->IsDsiWare())
{
patchCollection.AddPatch(new Sdk5DsiSdCardRedirectPatch());
}
else
{
patchCollection.AddPatch(new CardiDoTaskFromArm9Patch());
}
}
else
{
patchCollection.AddPatch(new CardiTaskThreadPatch());
}
if (*(vu32*)0x02FFF00C == GAMECODE("ADAJ") &&
romHeader->arm9LoadAddress == 0x02004000 &&
romHeader->arm9EntryAddress == 0x02004800)
{
// pokemon downloader
patchCollection.AddPatch(new PokemonDownloaderArm7Patch());
}
if (arm7ArenaPatch->FindPatchTarget(patchContext))
{
const u32 arm7PatchSpaceSize = 0x800;
void* privateWramHeapStart = arm7ArenaPatch->GetArm7PrivateWramArenaLo();
if (0x0380F780 - (u32)privateWramHeapStart - 0x2100 >= arm7PatchSpaceSize)
{
patchSpaceStart = privateWramHeapStart;
arm7ArenaPatch->SetArm7PrivateWramArenaLo((u8*)patchSpaceStart + arm7PatchSpaceSize);
}
else
{
LOG_DEBUG("Arm 7 patches placed in main memory\n");
u32 mainMemoryArenaLo = (u32)arm7ArenaPatch->GetMainMemoryArenaLo();
if (gIsDsiMode && romHeader->unitCode == 0)
{
patchSpaceStart = (void*)(mainMemoryArenaLo & ~0xC00000); // 0x023...
}
else
{
patchSpaceStart = (void*)(mainMemoryArenaLo | 0x800000); // make sure it ends up in the right place while in 16MB mode
}
arm7ArenaPatch->SetMainMemoryArenaLo((u8*)mainMemoryArenaLo + arm7PatchSpaceSize);
}
patchContext.GetPatchHeap().AddFreeSpace(patchSpaceStart, arm7PatchSpaceSize);
}
// The arm7 patcher uses the fact that the ntr wram is mirrored over the entire 03 region.
// Since the reserved patch space is smaller than the ntr wram, it will never overlap.
// As a result, the patcher can use arm7 addresses for placing patches.
// The arm7 will copy the patch data to the actual arm7 location afterwards.
// If in DSi mode and the rom is a DSi rom, temporarily disable twl wram to let ntr wram cover the entire 03 region.
u32 mbk6 = 0;
u32 mbk7 = 0;
u32 mbk8 = 0;
if (gIsDsiMode && romHeader->IsTwlRom())
{
mbk6 = REG_MBK6;
mbk7 = REG_MBK7;
mbk8 = REG_MBK8;
REG_MBK6 = 0;
REG_MBK7 = 0;
REG_MBK8 = 0;
}
if (!patchCollection.TryPerformPatches(patchContext))
{
ErrorDisplay().PrintError("Failed to apply arm7 patches.");
}
dc_flushAll();
dc_drainWriteBuffer();
ic_invalidateAll();
// If in DSi mode and the rom is a DSi rom, restore twl wram.
if (gIsDsiMode && romHeader->IsTwlRom())
{
REG_MBK6 = mbk6;
REG_MBK7 = mbk7;
REG_MBK8 = mbk8;
}
mem_setNtrWramMapping(MEM_NTR_WRAM_ARM7, MEM_NTR_WRAM_ARM7);
}
else
{
LOG_DEBUG("Module params not found!\n");
}
return (u32)patchSpaceStart < 0x03000000 ? nullptr : patchSpaceStart;
}

13
arm9/source/Arm7Patcher.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
class LoaderPlatform;
/// @brief Class for patching the arm7 of retail roms.
class Arm7Patcher
{
public:
/// @brief Applies arm7 patches using the given \p loaderPlatform.
/// @param loaderPlatform The loader platform to use.
/// @return A pointer to the patch space in IWRAM, or \c nullptr if the patches have been placed in main memory.
void* ApplyPatches(const LoaderPlatform* loaderPlatform) const;
};

View File

@@ -0,0 +1,179 @@
#include "common.h"
#include <libtwl/gfx/gfx3d.h>
#include <libtwl/gfx/gfxWindow.h>
#include <libtwl/rtos/rtosIrq.h>
#include <libtwl/ipc/ipcFifo.h>
#include <libtwl/ipc/ipcSync.h>
#include <libtwl/timer/timer.h>
#include <libtwl/dma/dmaNitro.h>
#include <libtwl/dma/dmaTwl.h>
#include <libtwl/mem/memVram.h>
#include <libtwl/card/card.h>
#include "Arm9IoRegisterClearer.h"
void Arm9IoRegisterClearer::ClearNtrIoRegisters(bool isSdkResetSystem) const
{
REG_IME = 0;
REG_IE = 0;
// Important! Make sure that all sources of interrupts are disabled as much as possible.
// It is possible to accidentally trigger an irq too early in games if bits are set in REG_IF
// while it doesn't expect that.
ClearGraphicsRegisters(isSdkResetSystem); // vblank, hblank, vcount, gxfifo
ClearTimerRegisters(); // timer 0, timer 1, timer 2, timer 3
ClearNtrDmaRegisters(); // dma 0, dma 1, dma 2, dma 3
REG_MCCNT0 = 0; // rom transfer
ipc_disableRecvFifoNotEmptyIrq();
ipc_disableSendFifoEmptyIrq();
ipc_disableArm7Irq();
ipc_clearSendFifo();
ipc_disableFifo();
REG_KEYCNT = 0;
}
void Arm9IoRegisterClearer::ClearTwlIoRegisters() const
{
REG_NDMA0SAD = 0;
REG_NDMA0DAD = 0;
REG_NDMA0TCNT = 0;
REG_NDMA0WCNT = 0;
REG_NDMA0BCNT = 0;
REG_NDMA0FDATA = 0;
REG_NDMA0CNT = 0;
REG_NDMA1SAD = 0;
REG_NDMA1DAD = 0;
REG_NDMA1TCNT = 0;
REG_NDMA1WCNT = 0;
REG_NDMA1BCNT = 0;
REG_NDMA1FDATA = 0;
REG_NDMA1CNT = 0;
REG_NDMA2SAD = 0;
REG_NDMA2DAD = 0;
REG_NDMA2TCNT = 0;
REG_NDMA2WCNT = 0;
REG_NDMA2BCNT = 0;
REG_NDMA2FDATA = 0;
REG_NDMA2CNT = 0;
REG_NDMA3SAD = 0;
REG_NDMA3DAD = 0;
REG_NDMA3TCNT = 0;
REG_NDMA3WCNT = 0;
REG_NDMA3BCNT = 0;
REG_NDMA3FDATA = 0;
REG_NDMA3CNT = 0;
}
void Arm9IoRegisterClearer::ClearGraphicsRegisters(bool isSdkResetSystem) const
{
REG_POWERCNT = 0x820F;
REG_DISPSTAT = 0; // vblank, hblank, vcount
if (!isSdkResetSystem)
{
REG_DISPCNT = 0;
REG_DISPCNT_SUB = 0;
REG_BG0CNT = 0;
REG_BG0CNT_SUB = 0;
REG_BG1CNT = 0;
REG_BG1CNT_SUB = 0;
REG_BG2CNT = 0;
REG_BG2CNT_SUB = 0;
REG_BG3CNT = 0;
REG_BG3CNT_SUB = 0;
REG_BG0HOFS = 0;
REG_BG0HOFS_SUB = 0;
REG_BG0VOFS = 0;
REG_BG0VOFS_SUB = 0;
REG_BG1HOFS = 0;
REG_BG1HOFS_SUB = 0;
REG_BG1VOFS = 0;
REG_BG1VOFS_SUB = 0;
REG_BG2HOFS = 0;
REG_BG2HOFS_SUB = 0;
REG_BG2VOFS = 0;
REG_BG2VOFS_SUB = 0;
REG_BG3HOFS = 0;
REG_BG3HOFS_SUB = 0;
REG_BG3VOFS = 0;
REG_BG3VOFS_SUB = 0;
REG_BG2PA = 0;
REG_BG2PB = 0;
REG_BG2PC = 0;
REG_BG2PD = 0;
REG_BG3PA = 0;
REG_BG3PB = 0;
REG_BG3PC = 0;
REG_BG3PD = 0;
REG_BG2PA_SUB = 0;
REG_BG2PB_SUB = 0;
REG_BG2PC_SUB = 0;
REG_BG2PD_SUB = 0;
REG_BG3PA_SUB = 0;
REG_BG3PB_SUB = 0;
REG_BG3PC_SUB = 0;
REG_BG3PD_SUB = 0;
gfx_setWindow0(0, 0, 0, 0);
gfx_setWindow1(0, 0, 0, 0);
gfx_setSubWindow0(0, 0, 0, 0);
gfx_setSubWindow1(0, 0, 0, 0);
REG_WININ = 0;
REG_WININ_SUB = 0;
REG_WINOUT = 0;
REG_WINOUT_SUB = 0;
REG_MOSAIC = 0;
REG_MOSAIC_SUB = 0;
REG_BLDCNT = 0;
REG_BLDCNT_SUB = 0;
REG_BLDALPHA = 0;
REG_BLDALPHA_SUB = 0;
REG_BLDY = 0;
REG_BLDY_SUB = 0;
REG_MASTER_BRIGHT = 0;
REG_MASTER_BRIGHT_SUB = 0;
}
REG_DISP3DCNT = 0;
REG_DISPCAPCNT = 0;
REG_GXSTAT = 0; // gxfifo
REG_CLEAR_COLOR = 0;
REG_CLEAR_DEPTH = 0x7FFF;
REG_DISP_1DOT_DEPTH = 0x7FFF;
REG_CLRIMAGE_OFFSET = 0;
REG_FOG_COLOR = 0;
REG_FOG_OFFSET = 0;
REG_ALPHA_TEST_REF = 0;
// VRAM A used for arm9 code
mem_setVramBMapping(MEM_VRAM_AB_NONE);
// VRAM C used for arm7 code
mem_setVramDMapping(MEM_VRAM_D_NONE);
mem_setVramEMapping(MEM_VRAM_E_NONE);
mem_setVramFMapping(MEM_VRAM_FG_NONE);
mem_setVramGMapping(MEM_VRAM_FG_NONE);
mem_setVramHMapping(MEM_VRAM_H_NONE);
mem_setVramIMapping(MEM_VRAM_I_NONE);
}
void Arm9IoRegisterClearer::ClearTimerRegisters() const
{
REG_TM0CNT_H = 0; // timer 0
REG_TM0CNT_L = 0;
REG_TM1CNT_H = 0; // timer 1
REG_TM1CNT_L = 0;
REG_TM2CNT_H = 0; // timer 2
REG_TM2CNT_L = 0;
REG_TM3CNT_H = 0; // timer 3
REG_TM3CNT_L = 0;
}
void Arm9IoRegisterClearer::ClearNtrDmaRegisters() const
{
REG_DMA0CNT = 0; // dma 0
REG_DMA0SAD = 0;
REG_DMA0DAD = 0;
REG_DMA1CNT = 0; // dma 1
REG_DMA1SAD = 0;
REG_DMA1DAD = 0;
REG_DMA2CNT = 0; // dma 2
REG_DMA2SAD = 0;
REG_DMA2DAD = 0;
REG_DMA3CNT = 0; // dma 3
REG_DMA3SAD = 0;
REG_DMA3DAD = 0;
}

View File

@@ -0,0 +1,19 @@
#pragma once
/// @brief Class for clearing the arm9 IO registers.
class Arm9IoRegisterClearer
{
public:
/// @brief Clears the arm9 ntr IO registers.
/// When \p isSdkResetSystem is \c true, some IO registers are not cleared to avoid graphical glitches.
/// @param isSdkResetSystem \c true if the clear is performed context of OS_ResetSystem, or \c false otherwise.
void ClearNtrIoRegisters(bool isSdkResetSystem) const;
/// @brief Clears the arm9 twl IO registers.
void ClearTwlIoRegisters() const;
private:
void ClearGraphicsRegisters(bool isSdkResetSystem) const;
void ClearTimerRegisters() const;
void ClearNtrDmaRegisters() const;
};

336
arm9/source/Arm9Patcher.cpp Normal file
View File

@@ -0,0 +1,336 @@
#include "common.h"
#include "ModuleParamsLocator.h"
#include "SdkVersion.h"
#include "patches/PatchCollection.h"
#include "patches/PatchContext.h"
#include "sharedMemory.h"
#include "patches/arm9/sdk2to4/CardiReadCardPatch.h"
#include "patches/arm9/sdk2to4/CardiTryReadCardDmaPatch.h"
#include "patches/arm9/sdk5/CardiIsRomDmaAvailablePatch.h"
#include "patches/arm9/sdk5/CardiReadCardWithHashInternalAsyncPatch.h"
#include "patches/arm9/sdk5/CardiReadRomWithCpuPatch.h"
#include "patches/arm9/CardiReadRomIdCorePatch.h"
#include "patches/arm9/OSResetSystemPatch.h"
#include "patches/arm9/PokemonDownloaderArm9Patch.h"
#include "patches/arm9/OverlayPatches/FsStartOverlayHookPatch.h"
#include "patches/arm9/OverlayPatches/DSProtectPatches/DSProtectOverlayPatch.h"
#include "patches/arm9/OverlayPatches/PokemonBw1/PokemonBw1IrApPatch.h"
#include "patches/arm9/OverlayPatches/PokemonBw2/PokemonBw2IrApPatch.h"
#include "patches/arm9/OverlayPatches/GoldenSunDarkDawn/GoldenSunDarkDawnOverlayHookPatch.h"
#include "SecureSysCallsUnusedSpaceLocator.h"
#include "fastSearch.h"
#include "gameCode.h"
#include "cache.h"
#include "ApList.h"
#include "patches/platform/LoaderPlatform.h"
#include "errorDisplay/ErrorDisplay.h"
#include "Arm9Patcher.h"
#define PARENT_SECTION_START 0x02001000
#define PARENT_SECTION_END 0x02003000
#define REQUIRED_PATCH_HEAP_SPACE 0x500
typedef void (*uncompress_func_t)(void* compressedEnd);
static const u32 sMiiUncompressBackwardPatternOld[] = { 0xE3500000, 0x0A000025, 0xE92D00F0, 0xE9100006 }; // mkds beta; version 0x2012774
static const u32 sMiiUncompressBackwardPatternOld2[] = { 0xE3500000, 0x0A00002B, 0xE92D00F0, 0xE9100006 }; // asterix & obelix xxl 2; version 0x3017531
static const u32 sMiiUncompressBackwardPattern[] = { 0xE3500000, 0x0A000027, 0xE92D00F0, 0xE9100006 }; // mkds
static const u32 sMiiUncompressBackwardPatternHybrid[] = { 0xE3500000, 0x0A000029, 0xE92D01F0, 0xE9100006 };
void Arm9Patcher::ApplyPatches(const LoaderPlatform* loaderPlatform, const ApListEntry* apListEntry,
bool isCloneBootRom, const loader_info_t* loaderInfo) const
{
auto romHeader = (const nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.romHeader;
auto twlRomHeader = (const nds_header_twl_t*)TWL_SHARED_MEMORY->twlRomHeader;
ModuleParamsLocator moduleParamsLocator;
auto moduleParams = moduleParamsLocator.FindModuleParams(romHeader);
u32 arm9Size = romHeader->arm9Size;
u32 arm9iSize = romHeader->IsTwlRom() ? twlRomHeader->arm9iSize : 0;
SdkVersion sdkVersion = moduleParams ? moduleParams->sdkVersion : 0u;
u32 compressedEnd = 0;
PatchCollection patchCollection;
if (moduleParams)
{
LOG_DEBUG("Module params found at 0x%p\n", moduleParams);
LOG_DEBUG("Sdk version: 0x%x\n", moduleParams->sdkVersion);
const u32* miiUncompressBackward = nullptr;
if (moduleParams->compressedEnd)
{
const u32* miiUncompressBackwardPattern;
if (sdkVersion <= 0x2017532)
miiUncompressBackwardPattern = sMiiUncompressBackwardPatternOld;
else
miiUncompressBackwardPattern = sMiiUncompressBackwardPattern;
miiUncompressBackward = fastSearch16((const u32*)romHeader->arm9LoadAddress, 0x1000, miiUncompressBackwardPattern);
if (!sdkVersion.IsTwlSdk() && !miiUncompressBackward)
miiUncompressBackward = fastSearch16((const u32*)romHeader->arm9LoadAddress, 0x1000, sMiiUncompressBackwardPatternOld2);
if (sdkVersion.IsTwlSdk() && !miiUncompressBackward)
miiUncompressBackward = fastSearch16((const u32*)romHeader->arm9LoadAddress, 0x1000, sMiiUncompressBackwardPatternHybrid);
if (miiUncompressBackward)
{
arm9Size = moduleParams->compressedEnd + *(u32*)(moduleParams->compressedEnd - 4) - romHeader->arm9LoadAddress;
((uncompress_func_t)miiUncompressBackward)((void*)moduleParams->compressedEnd);
compressedEnd = moduleParams->compressedEnd;
moduleParams->compressedEnd = 0;
}
else
{
LOG_DEBUG("MIi_UncompressBackward not found\n");
}
}
if (gIsDsiMode && romHeader->IsTwlRom())
{
auto arm9iModuleParams = (module_params_twl_t*)(romHeader->arm9LoadAddress + twlRomHeader->arm9iModuleParamsAddress);
if (arm9iModuleParams->magicBigEndian == MODULE_PARAMS_TWL_MAGIC_BE &&
arm9iModuleParams->magicLittleEndian == MODULE_PARAMS_TWL_MAGIC_LE)
{
if (arm9iModuleParams->compressedEnd)
{
LOG_DEBUG("Compressed arm9i found\n");
if (miiUncompressBackward)
{
arm9iSize = arm9iModuleParams->compressedEnd + *(u32*)(arm9iModuleParams->compressedEnd - 4) - twlRomHeader->arm9iLoadAddress;
((uncompress_func_t)miiUncompressBackward)((void*)arm9iModuleParams->compressedEnd);
arm9iModuleParams->compressedEnd = 0;
LOG_DEBUG("Decompressed arm9i\n");
}
else
{
LOG_DEBUG("Could not decompress arm9i\n");
}
}
}
}
}
else
{
LOG_DEBUG("Module params not found!\n");
if (romHeader->gameCode == GAMECODE("AS2E"))
{
// Spider-Man 2 (USA) is probably the only game without module params
sdkVersion = 0x02004F50;
}
}
LOG_DEBUG("Arm9 region: 0x%x - 0x%x\n", romHeader->arm9LoadAddress, romHeader->arm9LoadAddress + arm9Size);
PatchContext patchContext
{
(void*)romHeader->arm9LoadAddress,
arm9Size,
romHeader->IsTwlRom() ? (void*)twlRomHeader->arm9iLoadAddress : nullptr,
arm9iSize,
sdkVersion,
romHeader->gameCode,
loaderPlatform
};
if (sdkVersion != 0)
{
if (*(vu32*)0x02FFF00C == GAMECODE("ADAJ") &&
romHeader->arm9LoadAddress == 0x02004000 &&
romHeader->arm9EntryAddress == 0x02004800)
{
// pokemon downloader
patchContext.GetPatchHeap().AddFreeSpace((void*)0x023FF160, 0x6A0);
patchCollection.AddPatch(new PokemonDownloaderArm9Patch(loaderInfo));
}
else
{
u32 availableParentSize = 0;
if (isCloneBootRom)
{
availableParentSize = GetAvailableParentSectionSpace();
LOG_DEBUG("0x%X bytes available in .parent section\n", availableParentSize);
}
if (availableParentSize >= REQUIRED_PATCH_HEAP_SPACE)
{
patchContext.GetPatchHeap().AddFreeSpace(
(void*)(PARENT_SECTION_END - REQUIRED_PATCH_HEAP_SPACE),
REQUIRED_PATCH_HEAP_SPACE);
LOG_DEBUG("Placing patches in .parent section\n");
}
else
{
SecureSysCallsUnusedSpaceLocator secureSysCallsUnusedSpaceLocator;
secureSysCallsUnusedSpaceLocator.FindUnusedSpace(romHeader, patchContext.GetPatchHeap());
}
}
if (sdkVersion.IsTwlSdk())
{
if (!(romHeader->IsTwlRom() && twlRomHeader->IsDsiWare()))
{
// if ((romHeader->unitCode & 3) != 3)
{
patchCollection.AddPatch(new CardiIsRomDmaAvailablePatch());
}
patchCollection.AddPatch(new CardiReadRomWithCpuPatch());
if (gIsDsiMode && (romHeader->unitCode & 2))
{
patchCollection.AddPatch(new CardiReadCardWithHashInternalAsyncPatch());
}
}
}
else
{
patchCollection.AddPatch(new CardiReadCardPatch());
patchCollection.AddPatch(new CardiTryReadCardDmaPatch());
}
patchCollection.AddPatch(new CardiReadRomIdCorePatch());
patchCollection.AddPatch(new OSResetSystemPatch(loaderInfo));
OverlayHookPatch* overlayHookPatch;
if (romHeader->gameCode == GAMECODE("BO5P") ||
romHeader->gameCode == GAMECODE("BO5E") ||
romHeader->gameCode == GAMECODE("BO5J"))
{
overlayHookPatch = new GoldenSunDarkDawnOverlayHookPatch();
overlayHookPatch->AddOverlayPatch(new DSProtectOverlayPatch(334, 0, DSProtectVersion::v2_01, ~0u));
overlayHookPatch->AddOverlayPatch(new DSProtectOverlayPatch(335, 0, DSProtectVersion::v2_01s, ~0u));
}
else
{
overlayHookPatch = new FsStartOverlayHookPatch();
if (apListEntry)
{
u32 regularOverlayId = apListEntry->GetRegularOverlayId();
if (regularOverlayId != AP_LIST_OVERLAY_ID_INVALID)
{
if (regularOverlayId == AP_LIST_OVERLAY_ID_STATIC_ARM9)
{
LOG_WARNING("Patching DSProtect in main memory currently not supported\n");
}
else
{
overlayHookPatch->AddOverlayPatch(new DSProtectOverlayPatch(
regularOverlayId, apListEntry->GetRegularOffset(),
apListEntry->GetDSProtectVersion(), apListEntry->GetDSProtectFunctionMask()));
}
}
u32 sOverlayId = apListEntry->GetSOverlayId();
if (sOverlayId != AP_LIST_OVERLAY_ID_INVALID)
{
if (sOverlayId == AP_LIST_OVERLAY_ID_STATIC_ARM9)
{
LOG_WARNING("Patching DSProtect in main memory currently not supported\n");
}
else
{
auto version = apListEntry->GetDSProtectVersion();
if (version < DSProtectVersion::v2_00s)
{
version = (DSProtectVersion)((u32)version - (u32)DSProtectVersion::v2_00 + (u32)DSProtectVersion::v2_00s);
}
overlayHookPatch->AddOverlayPatch(new DSProtectOverlayPatch(
sOverlayId, apListEntry->GetSOffset(), version, ~0u));
}
}
}
switch (romHeader->gameCode)
{
// Pokemon Black & White
case GAMECODE("IRAD"):
case GAMECODE("IRAF"):
case GAMECODE("IRAI"):
case GAMECODE("IRAJ"):
case GAMECODE("IRAK"):
case GAMECODE("IRAO"):
case GAMECODE("IRAS"):
case GAMECODE("IRBD"):
case GAMECODE("IRBF"):
case GAMECODE("IRBI"):
case GAMECODE("IRBJ"):
case GAMECODE("IRBK"):
case GAMECODE("IRBO"):
case GAMECODE("IRBS"):
{
overlayHookPatch->AddOverlayPatch(new PokemonBw1IrApPatch());
break;
}
// Pokemon Black & White 2
// todo: IRDJ and IREJ have two revisions and the first one seems to be different
case GAMECODE("IRDD"):
case GAMECODE("IRDF"):
case GAMECODE("IRDI"):
case GAMECODE("IRDK"):
case GAMECODE("IRDO"):
case GAMECODE("IRDS"):
case GAMECODE("IRED"):
case GAMECODE("IREF"):
case GAMECODE("IREI"):
case GAMECODE("IREK"):
case GAMECODE("IREO"):
case GAMECODE("IRES"):
{
overlayHookPatch->AddOverlayPatch(new PokemonBw2IrApPatch());
break;
}
}
}
patchCollection.AddPatch(overlayHookPatch);
if (moduleParams && compressedEnd != 0)
{
AddRestoreCompressedEndPatch(
patchContext,
romHeader->arm9AutoLoadDoneHookAddress,
&moduleParams->compressedEnd,
compressedEnd);
}
}
if (!patchCollection.TryPerformPatches(patchContext))
{
ErrorDisplay().PrintError("Failed to apply arm9 patches.");
}
dc_flushAll();
dc_drainWriteBuffer();
ic_invalidateAll();
}
void Arm9Patcher::AddRestoreCompressedEndPatch(PatchContext& patchContext,
u32 arm9AutoLoadDoneHookAddress, u32* moduleParamsCompressedEnd, u32 originalCompressedEndValue) const
{
// Restore compressedEnd after first boot.
// This is necessary to not break cloneboot.
const u32 compressedEndFixCode[] =
{
0xE59F0014, // ldr r0,= moduleParamsCompressedEnd
0xE59F1014, // ldr r1,= originalCompressedEndValue
0xE5801000, // str r1, [r0]
0xE59F0010, // ldr r0,= arm9AutoLoadDoneHookAddress
0xE59F1000, // ldr r1, ret
0xE5801000, // str r1, [r0]
0xE12FFF1E, // ret: bx lr
(u32)moduleParamsCompressedEnd,
originalCompressedEndValue,
arm9AutoLoadDoneHookAddress
};
void* fixDst = patchContext.GetPatchHeap().Alloc(sizeof(compressedEndFixCode));
memcpy(fixDst, compressedEndFixCode, sizeof(compressedEndFixCode));
*(u32*)arm9AutoLoadDoneHookAddress = 0xEA000000u | ((((int)fixDst - (int)arm9AutoLoadDoneHookAddress - 8) >> 2) & 0xFFFFFF);
}
u32 Arm9Patcher::GetAvailableParentSectionSpace() const
{
u32 availableParentSize = 0;
for (u32 ptr = PARENT_SECTION_END; ptr > PARENT_SECTION_START; ptr -= 32)
{
u32* segment = (u32*)(ptr - 32);
if (segment[0] != 0 || segment[1] != 0 || segment[2] != 0 || segment[3] != 0 ||
segment[4] != 0 || segment[5] != 0 || segment[6] != 0 || segment[7] != 0)
{
break;
}
availableParentSize += 32;
}
return availableParentSize;
}

24
arm9/source/Arm9Patcher.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include "LoaderInfo.h"
class ApListEntry;
class LoaderPlatform;
class PatchContext;
/// @brief Class for patching the arm9 of retail roms.
class Arm9Patcher
{
public:
/// @brief Applies arm9 patches using the given \p loaderPlatform.
/// @param loaderPlatform The loader platform to use.
/// @param apListEntry The AP list entry for the rom being loaded, or \c nullptr if there is none.
/// @param isCloneBootRom \c true if the rom being loaded is a clone boot rom, or \c false otherwise.
/// @param loaderInfo The loader info to use.
void ApplyPatches(const LoaderPlatform* loaderPlatform, const ApListEntry* apListEntry,
bool isCloneBootRom, const loader_info_t* loaderInfo) const;
private:
void AddRestoreCompressedEndPatch(PatchContext& patchContext,
u32 arm9AutoLoadDoneHookAddress, u32* moduleParamsCompressedEnd, u32 originalCompressedEndValue) const;
u32 GetAvailableParentSectionSpace() const;
};

View File

@@ -0,0 +1,59 @@
#include "common.h"
#include "ModuleParamsLocator.h"
module_params_ntr_t* ModuleParamsLocator::FindModuleParams(const nds_header_ntr_t* romHeader)
{
//todo: static footer
module_params_ntr_t* moduleParams = nullptr;
if (romHeader->arm9ModuleParamsAddress != 0)
{
moduleParams = (module_params_ntr_t*)(romHeader->arm9LoadAddress + romHeader->arm9ModuleParamsAddress);
if (moduleParams->magicBigEndian != MODULE_PARAMS_NTR_MAGIC_BE ||
moduleParams->magicLittleEndian != MODULE_PARAMS_NTR_MAGIC_LE)
{
// try again by subtracting arm9 rom address
moduleParams = (module_params_ntr_t*)((u8*)moduleParams - romHeader->arm9RomOffset);
if (moduleParams->magicBigEndian != MODULE_PARAMS_NTR_MAGIC_BE ||
moduleParams->magicLittleEndian != MODULE_PARAMS_NTR_MAGIC_LE)
{
LOG_DEBUG("Module params not found at header specified offset 0x%x\n", romHeader->arm9ModuleParamsAddress);
moduleParams = nullptr;
}
}
}
else
{
LOG_DEBUG("Header specifies no module params address\n");
}
if (!moduleParams)
{
// try searching for the module params as not all roms have the address in the header
// it should be within the first 0x1000 bytes of the arm9
for (u32 i = 0; i < 0x1000; i += 4)
{
u32* ptr = (u32*)(romHeader->arm9LoadAddress + i);
if (ptr[0] == MODULE_PARAMS_NTR_MAGIC_BE && ptr[1] == MODULE_PARAMS_NTR_MAGIC_LE)
{
moduleParams = (module_params_ntr_t*)((u8*)ptr + 8 - sizeof(module_params_ntr_t));
break;
}
}
}
if (!moduleParams)
{
// as a last resort, scan the entire arm9 binary
for (u32 i = 0; i < romHeader->arm9Size; i += 4)
{
u32* ptr = (u32*)(romHeader->arm9LoadAddress + i);
if (ptr[0] == MODULE_PARAMS_NTR_MAGIC_BE && ptr[1] == MODULE_PARAMS_NTR_MAGIC_LE)
{
moduleParams = (module_params_ntr_t*)((u8*)ptr + 8 - sizeof(module_params_ntr_t));
break;
}
}
}
return moduleParams;
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "ndsHeader.h"
#include "moduleParams.h"
/// @brief Class for finding the module params of a retail arm9.
class ModuleParamsLocator
{
public:
/// @brief Searches for the module params of the arm9 of the rom with the given \p romHeader.
/// @param romHeader The header of the rom.
/// @return A pointer to the found module params, or \c nullptr if the module params could not be found.
module_params_ntr_t* FindModuleParams(const nds_header_ntr_t* romHeader);
};

View File

@@ -0,0 +1,136 @@
#include "common.h"
#include "gameCode.h"
#include <algorithm>
#include "patches/PatchHeap.h"
#include "thumbInstructions.h"
#include "SecureSysCallsUnusedSpaceLocator.h"
#define THUMB_MOVS_R0_R1 THUMB_MOVS_REG(0, 1)
#define THUMB_MOVS_R2_0 THUMB_MOVS_IMM(2, 0)
static const u16 sSvcSoftResetPattern[] = { THUMB_SVC(0), THUMB_BX_LR };
static const u16 sSvcWaitByLoopPattern[] = { THUMB_SVC(3), THUMB_BX_LR };
static const u16 sSvcWaitIntrPattern[] = { THUMB_MOVS_R2_0, THUMB_SVC(4), THUMB_BX_LR };
static const u16 sSvcWaitVBlankIntrPattern[] = { THUMB_MOVS_R2_0, THUMB_SVC(5), THUMB_BX_LR };
static const u16 sSvcHaltPattern[] = { THUMB_SVC(6), THUMB_BX_LR };
static const u16 sSvcDivPattern[] = { THUMB_SVC(9), THUMB_BX_LR };
static const u16 sSvcDivRemPattern[] = { THUMB_SVC(9), THUMB_MOVS_R0_R1, THUMB_BX_LR };
static const u16 sSvcCpuSetPattern[] = { THUMB_SVC(0xB), THUMB_BX_LR };
static const u16 sSvcCpuSetFastPattern[] = { THUMB_SVC(0xC), THUMB_BX_LR };
static const u16 sSvcSqrtPattern[] = { THUMB_SVC(0xD), THUMB_BX_LR };
static const u16 sSvcGetCrc16Pattern[] = { THUMB_SVC(0xE), THUMB_BX_LR };
static const u16 sSvcIsMainMemExpandedPattern[] = { THUMB_SVC(0xF), THUMB_BX_LR };
static const u16 sSvcUnpackBitsPattern[] = { THUMB_SVC(0x10), THUMB_BX_LR };
static const u16 sSvcUncompressLz8Pattern[] = { THUMB_SVC(0x11), THUMB_BX_LR };
static const u16 sSvcUncompressLz16FromDevicePattern[] = { THUMB_SVC(0x12), THUMB_BX_LR };
static const u16 sSvcUncompressHuffmanFromDevicePattern[] = { THUMB_SVC(0x13), THUMB_BX_LR };
static const u16 sSvcUncompressRl8Pattern[] = { THUMB_SVC(0x14), THUMB_BX_LR };
static const u16 sSvcUncompressRl16FromDevicePattern[] = { THUMB_SVC(0x15), THUMB_BX_LR };
struct svc_pattern_t
{
const u16* pattern;
u32 length;
};
static const std::array<const svc_pattern_t, 18> sSvcPatterns
{
svc_pattern_t { sSvcSoftResetPattern, sizeof(sSvcSoftResetPattern) },
svc_pattern_t { sSvcWaitByLoopPattern, sizeof(sSvcWaitByLoopPattern) },
svc_pattern_t { sSvcWaitIntrPattern, sizeof(sSvcWaitIntrPattern) },
svc_pattern_t { sSvcWaitVBlankIntrPattern, sizeof(sSvcWaitVBlankIntrPattern) },
svc_pattern_t { sSvcHaltPattern, sizeof(sSvcHaltPattern) },
svc_pattern_t { sSvcDivPattern, sizeof(sSvcDivPattern) },
svc_pattern_t { sSvcDivRemPattern, sizeof(sSvcDivRemPattern) },
svc_pattern_t { sSvcCpuSetPattern, sizeof(sSvcCpuSetPattern) },
svc_pattern_t { sSvcCpuSetFastPattern, sizeof(sSvcCpuSetFastPattern) },
svc_pattern_t { sSvcSqrtPattern, sizeof(sSvcSqrtPattern) },
svc_pattern_t { sSvcGetCrc16Pattern, sizeof(sSvcGetCrc16Pattern) },
svc_pattern_t { sSvcIsMainMemExpandedPattern, sizeof(sSvcIsMainMemExpandedPattern) },
svc_pattern_t { sSvcUnpackBitsPattern, sizeof(sSvcUnpackBitsPattern) },
svc_pattern_t { sSvcUncompressLz8Pattern, sizeof(sSvcUncompressLz8Pattern) },
svc_pattern_t { sSvcUncompressLz16FromDevicePattern, sizeof(sSvcUncompressLz16FromDevicePattern) },
svc_pattern_t { sSvcUncompressHuffmanFromDevicePattern, sizeof(sSvcUncompressHuffmanFromDevicePattern) },
svc_pattern_t { sSvcUncompressRl8Pattern, sizeof(sSvcUncompressRl8Pattern) },
svc_pattern_t { sSvcUncompressRl16FromDevicePattern, sizeof(sSvcUncompressRl16FromDevicePattern) }
};
const u16* SecureSysCallsUnusedSpaceLocator::FindPattern(const u16* data, u32 length, const u16* pattern, u32 patternLength) const
{
length >>= 1;
patternLength >>= 1;
for (u32 i = 0; i < length - patternLength; i++)
{
bool ok = true;
for (u32 j = 0; j < patternLength; j++)
{
if (data[i + j] != pattern[j])
{
ok = false;
break;
}
}
if (ok)
return &data[i];
}
return nullptr;
}
void SecureSysCallsUnusedSpaceLocator::FindUnusedSpace(const nds_header_ntr_t* romHeader, PatchHeap& patchHeap) const
{
if (romHeader->arm9RomOffset != 0x4000)
return;
u32 secureStart = romHeader->arm9LoadAddress;
if (*(u32*)(secureStart + 0x800) == 0x4770DF00)
{
// secure area for development purposes has this area empty
patchHeap.AddFreeSpace((void*)secureStart, 0x800);
LOG_DEBUG("Added free space starting at 0x%x with size 0x%x\n", secureStart, 0x800);
return;
}
std::array<svc_pattern_t, sSvcPatterns.size()> patternLocations;
for (u32 i = 0; i < sSvcPatterns.size(); i++)
{
patternLocations[i].pattern = FindPattern((const u16*)secureStart, 0x800, sSvcPatterns[i].pattern, sSvcPatterns[i].length);
patternLocations[i].length = sSvcPatterns[i].length;
}
std::qsort(patternLocations.begin(), patternLocations.size(), sizeof(svc_pattern_t), [](const void* a, const void* b)
{
const auto cmp = static_cast<const svc_pattern_t*>(a)->pattern <=> static_cast<const svc_pattern_t*>(b)->pattern;
if (cmp < 0)
{
return -1;
}
if (cmp > 0)
{
return 1;
}
return 0;
});
u32 current = secureStart;
for (u32 i = 0; i < sSvcPatterns.size(); i++)
{
if (!patternLocations[i].pattern)
continue;
u32 freeSpaceEnd = (u32)patternLocations[i].pattern & ~3;
if (current < freeSpaceEnd)
{
u32 freeSpace = freeSpaceEnd - current;
patchHeap.AddFreeSpace((void*)current, freeSpace);
LOG_DEBUG("Added free space starting at 0x%x with size 0x%x\n", current, freeSpace);
}
current = ((u32)patternLocations[i].pattern + patternLocations[i].length + 3) & ~3;
}
if (current < secureStart + 0x800)
{
u32 freeSpace = secureStart + 0x800 - current;
patchHeap.AddFreeSpace((void*)current, freeSpace);
LOG_DEBUG("Added free space starting at 0x%x with size 0x%x\n", current, freeSpace);
}
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include "ndsHeader.h"
class PatchHeap;
/// @brief Class for finding the unused space between the system calls in the arm9 secure area.
class SecureSysCallsUnusedSpaceLocator
{
public:
/// @brief Searches for the unused space between the system calls in the arm9 secure area of the rom
/// with the given \p romHeader and adds the unused space to the given \p patchHeap.
/// @param romHeader The rom header.
/// @param patchHeap The patch heap to add the unused space to.
void FindUnusedSpace(const nds_header_ntr_t* romHeader, PatchHeap& patchHeap) const;
private:
const u16* FindPattern(const u16* data, u32 length, const u16* pattern, u32 patternLength) const;
};

9
arm9/source/arm9Clock.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
enum class ScfgArm9Clock
{
Nitro67MHz = 0,
Twl134MHz = 1
};
extern "C" void scfg_setArm9Clock(ScfgArm9Clock arm9Clock);

32
arm9/source/arm9Clock.s Normal file
View File

@@ -0,0 +1,32 @@
.section ".itcm", "ax"
.arm
#define REG_SCFG_CLK 0x04004004
#define SCFG_CLK_CPU_SPEED 1
.global scfg_setArm9Clock
.type scfg_setArm9Clock, %function
scfg_setArm9Clock:
ldr r3,= REG_SCFG_CLK
ldrh r2, [r3]
and r1, r2, #SCFG_CLK_CPU_SPEED
cmp r1, r0
bxeq lr // requested speed already set
mrs r12, cpsr
orr r1, r12, #0xC0 // disable irq and fiq
msr cpsr, r1
bic r2, r2, #SCFG_CLK_CPU_SPEED
orr r2, r2, r0
strh r2, [r3]
// allow the clock switch to stabilize
mov r0, #8
1:
subs r0, r0, #1
bne 1b
msr cpsr, r12
bx lr

26
arm9/source/cache.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/// @brief Invalidates the entire instruction cache.
extern void ic_invalidateAll(void);
/// @brief Drains the write buffer.
extern void dc_drainWriteBuffer(void);
/// @brief Invalidates the entire data cache.
extern void dc_invalidateAll(void);
/// @brief Flushes the entire data cache.
extern void dc_flushAll(void);
/// @brief Invalidates the data cache in the given range.
/// @param ptr A pointer to the memory block to invalidate. Should be 32-byte aligned.
/// @param byteCount The number of bytes to invalidate. Will be rounded up to 32-byte multiples.
extern void dc_invalidateRange(void* ptr, u32 byteCount);
#ifdef __cplusplus
}
#endif

64
arm9/source/cache.s Normal file
View File

@@ -0,0 +1,64 @@
.text
.arm
// ARM DDI 0201D, page 3-11
.global dc_flushAll
.type dc_flushAll, %function
dc_flushAll:
// Temp register to set to 0. Needed for write buffer drain
mov r3, #0
// Initialize segment counter outer_loop
mov r1, #0
outer_loop:
// Initialize line counter inner_loop
mov r0, #0
inner_loop:
orr r2, r1, r0 // Generate segment and line address
mcr p15, 0, r3, c7, c10, 4 // Drain write buffer. See errata ARM946-PRDC-000592 5.0, section 4.8
mcr p15, 0, r2, c7, c14, 2 // Clean and flush the line
add r0, r0, #0x20 // Increment to next line
cmp r0, #0x400 // (data cache size / entries)
bne inner_loop
add r1, r1, #0x40000000 // Increment segment counter
cmp r1, #0x0
bne outer_loop
bx lr
.global dc_invalidateRange
.type dc_invalidateRange, %function
dc_invalidateRange:
add r1, r1, r0
bic r0, r0, #0x1F
1:
mcr p15, 0, r0, c7, c6, 1
add r0, r0, #32
cmp r0, r1
blt 1b
bx lr
.global dc_drainWriteBuffer
.type dc_drainWriteBuffer, %function
dc_drainWriteBuffer:
mov r0, #0
mcr p15, 0, r0, c7, c10, 4
bx lr
.global dc_invalidateAll
.type dc_invalidateAll, %function
dc_invalidateAll:
mov r0, #0
mcr p15, 0, r0, c7, c6, 0
bx lr
.global ic_invalidateAll
.type ic_invalidateAll, %function
ic_invalidateAll:
mov r0, #0
mcr p15, 0, r0, c7, c5, 0
bx lr
.pool
.end

22
arm9/source/common.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <nds.h>
extern u16 gIsDsiMode;
#ifdef __cplusplus
#include "globalHeap.h"
#include "logger/ILogger.h"
extern ILogger* gLogger;
#define MAX_COMPILED_LOG_LEVEL LogLevel::All
#define LOG_FATAL(...) if (LogLevel::Fatal < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Fatal, __VA_ARGS__)
#define LOG_ERROR(...) if (LogLevel::Error < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Error, __VA_ARGS__)
#define LOG_WARNING(...) if (LogLevel::Warning < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Warning, __VA_ARGS__)
#define LOG_INFO(...) if (LogLevel::Info < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Info, __VA_ARGS__)
#define LOG_DEBUG(...) if (LogLevel::Debug < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Debug, __VA_ARGS__)
#define LOG_TRACE(...) if (LogLevel::Trace < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Trace, __VA_ARGS__)
#endif

118
arm9/source/crt0.s Normal file
View File

@@ -0,0 +1,118 @@
.section ".crt0", "ax"
.arm
.global _start
.type _start, %function
_start:
// disable irqs
ldr r0,= 0x04000208
strb r0, [r0]
// configure cp15
// disable itcm, dtcm, caches and mpu
ldr r0,= 0x00002078
mcr p15, 0, r0, c1, c0
mov r0, #0
// invalidate entire icache
mcr p15, 0, r0, c7, c5, 0
// invalidate entire dcache
mcr p15, 0, r0, c7, c6, 0
// drain write buffer
mcr p15, 0, r0, c7, c10, 4
// move dtcm in place
ldr r0,= __dtcm_start + 0xA
mcr p15, 0, r0, c9, c1, 0
// setup itcm to cover the first 32MB of memory
mov r0, #0x20
mcr p15, 0, r0, c9, c1, 1
// mpu region 0: IO, Palette, VRAM, OAM (64 MB)
ldr r0,= ((1 | (25 << 1)) + 0x04000000)
mcr p15, 0, r0, c6, c0, 0
// mpu region 1: Main Memory + TWL WRAM (32 MB)
ldr r0,= ((1 | (24 << 1)) + 0x02000000)
mcr p15, 0, r0, c6, c1, 0
// mpu region 2: GBA slot
ldr r0,= ((1 | (24 << 1)) + 0x08000000)
mcr p15, 0, r0, c6, c2, 0
// mpu region 3: Disabled
mov r0, #0
mcr p15, 0, r0, c6, c3, 0
// mpu region 4: Disabled
mov r0, #0
mcr p15, 0, r0, c6, c4, 0
// mpu region 5: ITCM (32 KB)
ldr r0,= ((1 | (14 << 1)) + __itcm_start)
mcr p15, 0, r0, c6, c5, 0
// mpu region 6: DTCM (16 KB)
ldr r0,= ((1 | (13 << 1)) + __dtcm_start)
mcr p15, 0, r0, c6, c6, 0
// mpu region 7: LCDC VRAM A (128 KB)
ldr r0,= (1 | (16 << 1) | 0x06800000)
mcr p15, 0, r0, c6, c7, 0
// data permissions
ldr r0,= 0x33300333
mcr p15, 0, r0, c5, c0, 2
// code permissions
ldr r0,= 0x30300330
mcr p15, 0, r0, c5, c0, 3
// dcache
ldr r0,= 0b10000010
mcr p15, 0, r0, c2, c0, 0
// icache
ldr r0,= 0b10000010
mcr p15, 0, r0, c2, c0, 1
// write buffer
ldr r0,= 0b10000010
mcr p15, 0, r0, c3, c0, 0
// turn back on itcm, dtcm, cache and mpu
// keep data cache off
ldr r0,= 0x00057079 //0x0005707D
mcr p15, 0, r0, c1, c0
// copy itcm in place
ldr r0,= __itcm_lma
ldr r2,= __itcm_start
ldr r1,= __itcm_end
subs r1, r1, r2
beq itcm_done
1:
ldmia r0!, {r3-r10}
stmia r2!, {r3-r10}
subs r1, #0x20
bgt 1b
itcm_done:
// copy dtcm in place
ldr r0,= __dtcm_lma
ldr r2,= __dtcm_start
ldr r1,= __dtcm_end
subs r1, r1, r2
beq dtcm_done
1:
ldmia r0!, {r3-r10}
stmia r2!, {r3-r10}
subs r1, #0x20
bgt 1b
dtcm_done:
// clear bss
ldr r0,= __bss_start
ldr r1,= __bss_end
cmp r0, r1
beq bss_done
mov r2, #0
1:
str r2, [r0], #4
cmp r0, r1
bne 1b
bss_done:
msr cpsr_c, #0x13 // svc
ldr sp,= __dtcm_start + 0x3FC0
msr cpsr_c, #0x12 // irq
ldr sp,= __dtcm_start + 0x3F80
msr cpsr_c, #0x1F // sys
ldr sp,= __dtcm_start + 0x2F7C
b loaderMain
.pool
.end

View File

@@ -0,0 +1,48 @@
#include "common.h"
#include <libtwl/mem/memVram.h>
#include <libtwl/gfx/gfx.h>
#include <libtwl/gfx/gfxStatus.h>
#include <libtwl/gfx/gfxPalette.h>
#include <libtwl/gfx/gfxBackground.h>
#include "nitroFont2.h"
#include "font_nft2.h"
#include "ErrorDisplay.h"
void ErrorDisplay::PrintError(const char* errorString)
{
mem_setVramEMapping(MEM_VRAM_E_MAIN_BG_00000);
auto textBuffer = (u8*)0x02100000;
memset(textBuffer, 0, 256 * 192);
nft2_unpack((nft2_header_t*)font_nft2);
nft2_string_render_params_t renderParams =
{
x: 0,
y: 0,
width: 256,
height: 192
};
nft2_renderString((const nft2_header_t*)font_nft2, errorString, textBuffer, 256, &renderParams);
memcpy((void*)GFX_BG_MAIN, textBuffer, 256 * 192);
while (gfx_getVCount() != 191);
while (gfx_getVCount() == 191);
// 4 bit grayscale palette
for (int i = 0; i < 16; i++)
{
int gray = i * 2 + (i == 0 ? 0 : 1);
GFX_PLTT_BG_MAIN[i] = gray | (gray << 5) | (gray << 10);
}
REG_BG3PA = 256;
REG_BG3PB = 0;
REG_BG3PC = 0;
REG_BG3PD = 256;
REG_BG3X = 0;
REG_BG3Y = 0;
REG_BG3CNT = (1 << 7) | (1 << 14);
REG_BLDCNT = 0;
REG_DISPCNT = 3 | (1 << 11) | (1 << 16);
REG_MASTER_BRIGHT = 0;
GFX_PLTT_BG_SUB[0] = 0;
REG_MASTER_BRIGHT_SUB = 0x8010;
REG_DISPCNT_SUB = 0x10000;
while (true);
}

View File

@@ -0,0 +1,11 @@
#pragma once
/// @brief Class for displaying a critical error message.
class ErrorDisplay
{
public:
/// @brief Displays the given \p errorString and loops.
/// @note This function does not return.
/// @param errorString The error string to display.
void PrintError(const char* errorString);
};

View File

@@ -0,0 +1,104 @@
#include "common.h"
#include "nitroFont2.h"
bool nft2_unpack(nft2_header_t* font)
{
if (font->signature != NFT2_SIGNATURE)
return false;
font->glyphInfoPtr = (const nft2_glyph_t*)((u32)font + (u32)font->glyphInfoPtr);
font->charMapPtr = (const nft2_char_map_entry_t*)((u32)font + (u32)font->charMapPtr);
font->glyphDataPtr = (const u8*)((u32)font + (u32)font->glyphDataPtr);
return true;
}
int nft2_findGlyphIdxForCharacter(const nft2_header_t* font, u16 character)
{
const nft2_char_map_entry_t* charMapEntry = font->charMapPtr;
while (charMapEntry->count > 0)
{
if (charMapEntry->startChar <= character && character < charMapEntry->startChar + charMapEntry->count)
return charMapEntry->glyphs[character - charMapEntry->startChar];
charMapEntry = (const nft2_char_map_entry_t*)((u32)charMapEntry + 4 + 2 * charMapEntry->count);
}
return 0;
}
static inline void renderGlyph(const nft2_header_t* font, const nft2_glyph_t* glyph,
int xPos, int yPos, int width, int height, u8* dst, u32 stride)
{
int yOffset = glyph->spacingTop;
u32 xStart = xPos < 0 ? -xPos : 0;
u32 yStart = yPos + yOffset < 0 ? -(yPos + yOffset) : 0;
int xEnd = glyph->glyphWidth;
if (xPos + xEnd > width)
{
// by returning we only render complete glyphs
return;
// old code for rendering partial glyphs
// xEnd = width - xPos;
}
int yEnd = glyph->glyphHeight;
if (yPos + yOffset + yEnd > height)
yEnd = height - (yPos + yOffset); // allow partial glyphs in the vertical direction
const u8* glyphData = &font->glyphDataPtr[glyph->dataOffset];
glyphData += yStart * ((glyph->glyphWidth + 1) >> 1);
for (int y = yStart; y < yEnd; y++)
{
for (int x = xStart; x < xEnd; x++)
{
u32 data = glyphData[x >> 1];
if ((x & 1) == 0)
data &= 0xF;
else
data >>= 4;
if (data == 0)
continue;
u32 finalX = x + xPos;
u32 finalY = y + yPos + yOffset;
dst[finalY * stride + finalX] = data;
}
glyphData += (glyph->glyphWidth + 1) >> 1;
}
}
ITCM_CODE void nft2_renderString(const nft2_header_t* font, const char* string, u8* dst, u32 stride,
nft2_string_render_params_t* renderParams)
{
int xPos = renderParams->x;
int yPos = renderParams->y;
u32 textWidth = 0;
while (true)
{
char c = *string++;
if (c == 0)
break;
if (c == '\n')
{
xPos = renderParams->x;
yPos += font->ascend + font->descend + 1;
if (yPos >= (int)renderParams->height)
break;
continue;
}
int glyphIdx = nft2_findGlyphIdxForCharacter(font, c);
const nft2_glyph_t* glyph = &font->glyphInfoPtr[glyphIdx];
xPos += glyph->spacingLeft;
renderGlyph(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
xPos += glyph->glyphWidth;
if (xPos > (int)textWidth)
textWidth = xPos;
xPos += glyph->spacingRight;
}
renderParams->textWidth = textWidth;
}

View File

@@ -0,0 +1,62 @@
#pragma once
#define NFT2_SIGNATURE 0x3254464E
struct nft2_glyph_t
{
u32 dataOffset : 24;
u32 glyphWidth : 8;
s8 spacingLeft;
s8 spacingRight;
u8 glyphHeight;
s8 spacingTop;
};
struct nft2_char_map_entry_t
{
u16 count;
u16 startChar;
u16 glyphs[1];
};
struct nft2_header_t
{
u32 signature;
const nft2_glyph_t* glyphInfoPtr;
const nft2_char_map_entry_t* charMapPtr;
const u8* glyphDataPtr;
u8 ascend;
u8 descend;
u16 glyphCount;
};
struct nft2_string_render_params_t
{
int x;
int y;
u32 width;
u32 height;
u32 textWidth;
};
/// @brief Prepares the ntf2 data of the given \p font for runtime use.
/// Call this method once after loading a font file.
/// @param font The font to prepare.
/// @return True if preparing was successful, or false otherwise.
bool nft2_unpack(nft2_header_t* font);
/// @brief Finds the glyph index in the given \p font that corresponds to the given \p character.
/// @param font The font the find the glyph index in.
/// @param character The character to find the glyph index for.
/// @return The glyph index if found, or 0 otherwise.
int nft2_findGlyphIdxForCharacter(const nft2_header_t* font, u16 character);
/// @brief Renders the given \p string with the given \p font to the \p dst buffer
/// with the given \p stride and \p renderParams.
/// @param font The font to use.
/// @param string The string to render.
/// @param dst The destination buffer.
/// @param stride The stride of the destination buffer.
/// @param renderParams The render params.
void nft2_renderString(const nft2_header_t* font, const char* string, u8* dst,
u32 stride, nft2_string_render_params_t* renderParams);

3
arm9/source/fastClear.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
extern "C" void fastClear(void* dst, u32 length);

22
arm9/source/fastClear.s Normal file
View File

@@ -0,0 +1,22 @@
.section ".itcm", "ax"
.arm
// r0: dst
// r1: size (multiple of 32)
.global fastClear
.type fastClear, %function
fastClear:
push {r4-r8,lr}
mov r2, #0
mov r3, #0
mov r4, #0
mov r5, #0
mov r6, #0
mov r7, #0
mov r8, #0
mov lr, #0
1:
subs r1, r1, #32
stmgeia r0!, {r2-r8,lr}
bgt 1b
pop {r4-r8,pc}

3
arm9/source/fastSearch.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
extern "C" const u32* fastSearch16(const u32* data, u32 length, const u32* pattern);

141
arm9/source/fastSearch.s Normal file
View File

@@ -0,0 +1,141 @@
.section ".itcm", "ax"
.arm
// r0: data
// r1: size
// r2: pattern (4 words, each word should be unique)
.global fastSearch16
.type fastSearch16, %function
fastSearch16:
push {r4-r11,lr}
add r1, r1, r0
sub r1, r1, #44 // 32 bytes to load at once + 12 bytes after it
ldmia r2, {r2-r5}
1:
ldmia r0!, {r6-r12,lr}
cmp r6, r2
cmpne r7, r2
cmpne r8, r2
cmpne r9, r2
cmpne r10, r2
cmpne r11, r2
cmpne r12, r2
cmpne lr, r2
beq fastSearch16_firstWordMatch
fastSearch16_continueFastSearch:
cmp r0, r1
ble 1b
// only need to handle the last couple of words now
add r1, #(44 - 16)
2:
ldr r6, [r0], #4
cmp r6, r2
beq fastSearch16_firstWordMatchLast
fastSearch16_continueLastSearch:
cmp r0, r1
ble 2b
mov r0, #0
pop {r4-r11,pc}
fastSearch16_firstWordMatchLast:
ldr r6, [r0], #4
cmp r6, r3
ldreq r6, [r0], #4
cmpeq r6, r4
ldreq r6, [r0], #4
cmpeq r6, r5
bne fastSearch16_continueLastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch:
cmp r6, r2
beq fastSearch16_firstWordMatch_r6
cmpne r7, r2
beq fastSearch16_firstWordMatch_r7
cmpne r8, r2
beq fastSearch16_firstWordMatch_r8
cmpne r9, r2
beq fastSearch16_firstWordMatch_r9
cmpne r10, r2
beq fastSearch16_firstWordMatch_r10
cmpne r11, r2
beq fastSearch16_firstWordMatch_r11
cmpne r12, r2
beq fastSearch16_firstWordMatch_r12
fastSearch16_firstWordMatch_lr:
ldr r6, [r0], #4
cmp r6, r3
ldreq r6, [r0], #4
cmpeq r6, r4
ldreq r6, [r0], #4
cmpeq r6, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r6:
sub r0, r0, #16
cmp r7, r3
cmpeq r8, r4
cmpeq r9, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r7:
sub r0, r0, #12
cmp r8, r3
cmpeq r9, r4
cmpeq r10, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r8:
sub r0, r0, #8
cmp r9, r3
cmpeq r10, r4
cmpeq r11, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r9:
sub r0, r0, #4
cmp r10, r3
cmpeq r11, r4
cmpeq r12, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r10:
cmp r11, r3
cmpeq r12, r4
cmpeq lr, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r11:
cmp r12, r3
cmpeq lr, r4
ldreq r6, [r0], #4
cmpeq r6, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r12:
cmp lr, r3
ldreq r6, [r0], #4
cmpeq r6, r4
ldreq r6, [r0], #4
cmpeq r6, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}

102
arm9/source/globalHeap.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "common.h"
#include <string.h>
#include "core/heap/tlsf.h"
#include "globalHeap.h"
static tlsf_t sHeap;
extern "C" void* malloc(size_t size)
{
return tlsf_malloc(sHeap, size);
}
extern "C" void* _malloc_r(struct _reent *, size_t size)
{
return malloc(size);
}
extern "C" void free(void* ptr)
{
tlsf_free(sHeap, ptr);
}
extern "C" void _free_r(struct _reent *, void* ptr)
{
free(ptr);
}
extern "C" void* realloc(void* ptr, size_t size)
{
return tlsf_realloc(sHeap, ptr, size);
}
extern "C" void* memalign(size_t alignment, size_t size)
{
return tlsf_memalign(sHeap, alignment, size);
}
void* operator new(std::size_t blocksize)
{
return malloc(blocksize);
}
void* operator new(std::size_t size, std::align_val_t al)
{
return memalign(static_cast<std::size_t>(al), size);
}
void* operator new[](std::size_t blocksize)
{
return malloc(blocksize);
}
void* operator new[](std::size_t size, std::align_val_t al)
{
return memalign(static_cast<std::size_t>(al), size);
}
void operator delete(void* ptr)
{
return free(ptr);
}
void operator delete(void* ptr, std::align_val_t align)
{
return free(ptr);
}
void operator delete(void* ptr, std::size_t size, std::align_val_t align)
{
return free(ptr);
}
void operator delete[](void* ptr)
{
return free(ptr);
}
void operator delete[](void* ptr, std::align_val_t align)
{
return free(ptr);
}
void operator delete[](void* ptr, std::size_t size, std::align_val_t align)
{
return free(ptr);
}
extern u8 __heap_start;
extern u8 __heap_end;
[[gnu::target("thumb"), gnu::optimize("Os")]]
void heap_init()
{
u32 heapStart = (u32)&__heap_start;
heapStart = (heapStart + 31) & ~31;
u32 heapEnd = (u32)&__heap_end;
heapEnd = heapEnd & ~31;
u32 tlsfSize = tlsf_size();
memset((void*)heapStart, 0, tlsfSize);
memset((u8*)heapStart + tlsfSize, 0xA5, heapEnd - heapStart - tlsfSize);
sHeap = tlsf_create_with_pool((void*)heapStart, heapEnd - heapStart);
}

16
arm9/source/globalHeap.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
void* operator new(std::size_t blocksize) noexcept;
void* operator new(std::size_t size, std::align_val_t al) noexcept;
void* operator new[](std::size_t blocksize) noexcept;
void* operator new[](std::size_t size, std::align_val_t al) noexcept;
void operator delete(void* ptr) noexcept;
void operator delete(void* ptr, std::align_val_t align) noexcept;
void operator delete(void* ptr, std::size_t size, std::align_val_t align) noexcept;
void operator delete[](void* ptr) noexcept;
void operator delete[](void* ptr, std::align_val_t align) noexcept;
void operator delete[](void* ptr, std::size_t size, std::align_val_t align) noexcept;
constexpr std::align_val_t cache_align { 32 };
void heap_init();

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