mirror of
https://github.com/LNH-team/pico-loader.git
synced 2026-06-02 01:06:50 +02:00
Initial commit
This commit is contained in:
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.bin binary
|
||||||
59
.github/workflows/nightly.yml
vendored
Normal file
59
.github/workflows/nightly.yml
vendored
Normal 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
52
.gitignore
vendored
Normal 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
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "libs/libtwl"]
|
||||||
|
path = libs/libtwl
|
||||||
|
url = https://github.com/Gericom/libtwl.git
|
||||||
17
LICENSE.txt
Normal file
17
LICENSE.txt
Normal 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
28
Makefile
Normal 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
192
Makefile.arm7
Normal 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
243
Makefile.arm9
Normal 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
106
README.md
Normal 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
209
arm7/loader7.ld
Normal 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
3
arm7/source/clearFast.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
extern "C" void clearFast(void* dst, u32 length);
|
||||||
29
arm7/source/clearFast.s
Normal file
29
arm7/source/clearFast.s
Normal 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
23
arm7/source/common.h
Normal 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;
|
||||||
61
arm7/source/core/Environment.cpp
Normal file
61
arm7/source/core/Environment.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
arm7/source/core/Environment.h
Normal file
32
arm7/source/core/Environment.h
Normal 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
26
arm7/source/crt0.s
Normal 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
|
||||||
17
arm7/source/errorDisplay/ErrorDisplay.cpp
Normal file
17
arm7/source/errorDisplay/ErrorDisplay.cpp
Normal 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);
|
||||||
|
}
|
||||||
11
arm7/source/errorDisplay/ErrorDisplay.h
Normal file
11
arm7/source/errorDisplay/ErrorDisplay.h
Normal 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
205
arm7/source/fat/diskio.cpp
Normal 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
77
arm7/source/fat/diskio.h
Normal 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
97
arm7/source/fat/dldi.cpp
Normal 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
16
arm7/source/fat/dldi.h
Normal 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
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
412
arm7/source/fat/ff.h
Normal 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
288
arm7/source/fat/ffconf.h
Normal 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
170
arm7/source/fat/ffsystem.c
Normal 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
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
102
arm7/source/globalHeap.cpp
Normal 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
16
arm7/source/globalHeap.h
Normal 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
14
arm7/source/header.cpp
Normal 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
4
arm7/source/header.h
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../../include/picoLoader7.h"
|
||||||
|
|
||||||
|
extern pload_header7_t gLoaderHeader;
|
||||||
15
arm7/source/ipc.h
Normal file
15
arm7/source/ipc.h
Normal 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();
|
||||||
|
}
|
||||||
24
arm7/source/loader/ApListFactory.cpp
Normal file
24
arm7/source/loader/ApListFactory.cpp
Normal 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);
|
||||||
|
}
|
||||||
12
arm7/source/loader/ApListFactory.h
Normal file
12
arm7/source/loader/ApListFactory.h
Normal 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);
|
||||||
|
};
|
||||||
151
arm7/source/loader/Arm7IoRegisterClearer.cpp
Normal file
151
arm7/source/loader/Arm7IoRegisterClearer.cpp
Normal 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;
|
||||||
|
}
|
||||||
13
arm7/source/loader/Arm7IoRegisterClearer.h
Normal file
13
arm7/source/loader/Arm7IoRegisterClearer.h
Normal 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;
|
||||||
|
};
|
||||||
113
arm7/source/loader/Blowfish.cpp
Normal file
113
arm7/source/loader/Blowfish.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
67
arm7/source/loader/Blowfish.h
Normal file
67
arm7/source/loader/Blowfish.h
Normal 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;
|
||||||
|
};
|
||||||
14
arm7/source/loader/BootMode.h
Normal file
14
arm7/source/loader/BootMode.h
Normal 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
|
||||||
|
};
|
||||||
107
arm7/source/loader/CardSaveArranger.cpp
Normal file
107
arm7/source/loader/CardSaveArranger.cpp
Normal 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;
|
||||||
|
}
|
||||||
12
arm7/source/loader/CardSaveArranger.h
Normal file
12
arm7/source/loader/CardSaveArranger.h
Normal 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;
|
||||||
|
};
|
||||||
356
arm7/source/loader/DSMode.cpp
Normal file
356
arm7/source/loader/DSMode.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
arm7/source/loader/DSMode.h
Normal file
18
arm7/source/loader/DSMode.h
Normal 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;
|
||||||
|
};
|
||||||
106
arm7/source/loader/DldiDriver.cpp
Normal file
106
arm7/source/loader/DldiDriver.cpp
Normal 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;
|
||||||
|
}
|
||||||
86
arm7/source/loader/DldiDriver.h
Normal file
86
arm7/source/loader/DldiDriver.h
Normal 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)();
|
||||||
|
}
|
||||||
|
};
|
||||||
39
arm7/source/loader/DsiDeviceList.h
Normal file
39
arm7/source/loader/DsiDeviceList.h
Normal 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.");
|
||||||
235
arm7/source/loader/DsiWareSaveArranger.cpp
Normal file
235
arm7/source/loader/DsiWareSaveArranger.cpp
Normal 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;
|
||||||
|
}
|
||||||
65
arm7/source/loader/DsiWareSaveArranger.h
Normal file
65
arm7/source/loader/DsiWareSaveArranger.h
Normal 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;
|
||||||
|
};
|
||||||
39
arm7/source/loader/InverseKmpMatcher.h
Normal file
39
arm7/source/loader/InverseKmpMatcher.h
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
1007
arm7/source/loader/NdsLoader.cpp
Normal file
1007
arm7/source/loader/NdsLoader.cpp
Normal file
File diff suppressed because it is too large
Load Diff
75
arm7/source/loader/NdsLoader.h
Normal file
75
arm7/source/loader/NdsLoader.h
Normal 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();
|
||||||
|
};
|
||||||
60
arm7/source/loader/PicoLoaderArranger.cpp
Normal file
60
arm7/source/loader/PicoLoaderArranger.cpp
Normal 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;
|
||||||
|
}
|
||||||
12
arm7/source/loader/PicoLoaderArranger.h
Normal file
12
arm7/source/loader/PicoLoaderArranger.h
Normal 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;
|
||||||
|
};
|
||||||
22
arm7/source/loader/SaveList.cpp
Normal file
22
arm7/source/loader/SaveList.cpp
Normal 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;
|
||||||
|
}
|
||||||
74
arm7/source/loader/SaveList.h
Normal file
74
arm7/source/loader/SaveList.h
Normal 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;
|
||||||
|
};
|
||||||
24
arm7/source/loader/SaveListFactory.cpp
Normal file
24
arm7/source/loader/SaveListFactory.cpp
Normal 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);
|
||||||
|
}
|
||||||
12
arm7/source/loader/SaveListFactory.h
Normal file
12
arm7/source/loader/SaveListFactory.h
Normal 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);
|
||||||
|
};
|
||||||
104
arm7/source/loader/SdmmcDefinitions.h
Normal file
104
arm7/source/loader/SdmmcDefinitions.h
Normal 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)
|
||||||
153
arm7/source/loader/TwlAes.cpp
Normal file
153
arm7/source/loader/TwlAes.cpp
Normal 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();
|
||||||
|
(®_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*)®_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*)®_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);
|
||||||
|
}
|
||||||
22
arm7/source/loader/TwlAes.h
Normal file
22
arm7/source/loader/TwlAes.h
Normal 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;
|
||||||
|
};
|
||||||
52
arm7/source/loader/dldiHeader.h
Normal file
52
arm7/source/loader/dldiHeader.h
Normal 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");
|
||||||
43
arm7/source/logger/NitroEmulatorOutputStream.cpp
Normal file
43
arm7/source/logger/NitroEmulatorOutputStream.cpp
Normal 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;
|
||||||
|
}
|
||||||
11
arm7/source/logger/NitroEmulatorOutputStream.h
Normal file
11
arm7/source/logger/NitroEmulatorOutputStream.h
Normal 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 { }
|
||||||
|
};
|
||||||
16
arm7/source/logger/NocashOutputStream.h
Normal file
16
arm7/source/logger/NocashOutputStream.h
Normal 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 { }
|
||||||
|
};
|
||||||
26
arm7/source/logger/PlainLogger.h
Normal file
26
arm7/source/logger/PlainLogger.h
Normal 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
229
arm7/source/main.cpp
Normal 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
235
arm7/source/mmc/mmc_spec.h
Normal 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 card’s 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 command’s 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.70–1.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
192
arm7/source/mmc/sd_spec.h
Normal 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
751
arm7/source/mmc/sdmmc.c
Normal 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.1–4.2–4.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
240
arm7/source/mmc/sdmmc.h
Normal 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
279
arm7/source/mmc/tmio.c
Normal 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
414
arm7/source/mmc/tmio.h
Normal 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 ®s->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
BIN
arm9/data/font.nft2
Normal file
Binary file not shown.
245
arm9/loader9.ld
Normal file
245
arm9/loader9.ld
Normal 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
146
arm9/source/Arm7Patcher.cpp
Normal 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
13
arm9/source/Arm7Patcher.h
Normal 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;
|
||||||
|
};
|
||||||
179
arm9/source/Arm9IoRegisterClearer.cpp
Normal file
179
arm9/source/Arm9IoRegisterClearer.cpp
Normal 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;
|
||||||
|
}
|
||||||
19
arm9/source/Arm9IoRegisterClearer.h
Normal file
19
arm9/source/Arm9IoRegisterClearer.h
Normal 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
336
arm9/source/Arm9Patcher.cpp
Normal 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
24
arm9/source/Arm9Patcher.h
Normal 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;
|
||||||
|
};
|
||||||
59
arm9/source/ModuleParamsLocator.cpp
Normal file
59
arm9/source/ModuleParamsLocator.cpp
Normal 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;
|
||||||
|
}
|
||||||
13
arm9/source/ModuleParamsLocator.h
Normal file
13
arm9/source/ModuleParamsLocator.h
Normal 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);
|
||||||
|
};
|
||||||
136
arm9/source/SecureSysCallsUnusedSpaceLocator.cpp
Normal file
136
arm9/source/SecureSysCallsUnusedSpaceLocator.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
arm9/source/SecureSysCallsUnusedSpaceLocator.h
Normal file
18
arm9/source/SecureSysCallsUnusedSpaceLocator.h
Normal 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
9
arm9/source/arm9Clock.h
Normal 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
32
arm9/source/arm9Clock.s
Normal 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
26
arm9/source/cache.h
Normal 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
64
arm9/source/cache.s
Normal 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
22
arm9/source/common.h
Normal 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
118
arm9/source/crt0.s
Normal 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
|
||||||
48
arm9/source/errorDisplay/ErrorDisplay.cpp
Normal file
48
arm9/source/errorDisplay/ErrorDisplay.cpp
Normal 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);
|
||||||
|
}
|
||||||
11
arm9/source/errorDisplay/ErrorDisplay.h
Normal file
11
arm9/source/errorDisplay/ErrorDisplay.h
Normal 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);
|
||||||
|
};
|
||||||
104
arm9/source/errorDisplay/nitroFont2.cpp
Normal file
104
arm9/source/errorDisplay/nitroFont2.cpp
Normal 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;
|
||||||
|
}
|
||||||
62
arm9/source/errorDisplay/nitroFont2.h
Normal file
62
arm9/source/errorDisplay/nitroFont2.h
Normal 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
3
arm9/source/fastClear.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
extern "C" void fastClear(void* dst, u32 length);
|
||||||
22
arm9/source/fastClear.s
Normal file
22
arm9/source/fastClear.s
Normal 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
3
arm9/source/fastSearch.h
Normal 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
141
arm9/source/fastSearch.s
Normal 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
102
arm9/source/globalHeap.cpp
Normal 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
16
arm9/source/globalHeap.h
Normal 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
Reference in New Issue
Block a user