commit 5d6f67c612fd5f4d593209ccdffd023d42b43988 Author: Gericom Date: Sat Nov 22 17:21:45 2025 +0100 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2227abb --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.bin binary \ No newline at end of file diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..38022c5 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,37 @@ +name: Build Pico Launcher + +on: + push: + branches: ["develop"] + paths-ignore: + - 'README.md' + pull_request: + branches: ["develop"] + paths-ignore: + - 'README.md' + workflow_dispatch: + +jobs: + pico_launcher: + runs-on: ubuntu-latest + container: skylyrac/blocksds:slim-v1.13.1 + name: Build Pico Launcher + env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1 + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + submodules: true + - name: Run build script + run: | + make + - name: Publish build to GH Actions + uses: actions/upload-artifact@v4 + with: + path: | + _pico/ + LAUNCHER.nds + name: Pico Launcher diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77bce19 --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# 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 +.vscode/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ea93230 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libs/libtwl"] + path = libs/libtwl + url = https://github.com/Gericom/libtwl.git diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..59dc7d2 --- /dev/null +++ b/LICENSE.txt @@ -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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9b4a847 --- /dev/null +++ b/Makefile @@ -0,0 +1,110 @@ +# SPDX-License-Identifier: CC0-1.0 +# +# SPDX-FileContributor: Antonio Niño Díaz, 2023 + +BLOCKSDS ?= /opt/blocksds/core +BLOCKSDSEXT ?= /opt/blocksds/external + +export LIBTWL ?= $(shell pwd)/libs/libtwl + +# User config +# =========== + +NAME := LAUNCHER + +GAME_TITLE := Pico Launcher +GAME_AUTHOR := LNH team +GAME_ICON := icon.bmp + +# DLDI and internal SD slot of DSi +# -------------------------------- + +# Root folder of the SD image +SDROOT := sdroot +# Name of the generated image it "DSi-1.sd" for no$gba in DSi mode +SDIMAGE := image.bin + +# Source code paths +# ----------------- + +# A single directory that is the root of NitroFS: +NITROFSDIR := + +# Tools +# ----- + +MAKE := make +RM := rm -rf + +# Verbose flag +# ------------ + +ifeq ($(VERBOSE),1) +V := +else +V := @ +endif + +# Directories +# ----------- + +ARM9DIR := arm9 +ARM7DIR := arm7 + +# Build artfacts +# -------------- + +ROM := $(NAME).nds + +# Targets +# ------- + +.PHONY: all clean arm9 arm7 dldipatch sdimage checklibtwl + +all: $(ROM) + +clean: + @echo " CLEAN" + $(V)$(MAKE) -f Makefile.arm9 clean --no-print-directory + $(V)$(MAKE) -f Makefile.arm7 clean --no-print-directory + $(V)$(RM) $(ROM) build $(SDIMAGE) + +arm9: checklibtwl + $(V)+$(MAKE) -f Makefile.arm9 --no-print-directory + +arm7: checklibtwl + $(V)+$(MAKE) -f Makefile.arm7 --no-print-directory + +checklibtwl: + $(MAKE) -C $(LIBTWL) + +ifneq ($(strip $(NITROFSDIR)),) +# Additional arguments for ndstool +NDSTOOL_ARGS := -d $(NITROFSDIR) + +# Make the NDS ROM depend on the filesystem only if it is needed +$(ROM): $(NITROFSDIR) +endif + +# Combine the title strings +ifeq ($(strip $(GAME_SUBTITLE)),) + GAME_FULL_TITLE := $(GAME_TITLE);$(GAME_AUTHOR) +else + GAME_FULL_TITLE := $(GAME_TITLE);$(GAME_SUBTITLE);$(GAME_AUTHOR) +endif + +$(ROM): arm9 arm7 + @echo " NDSTOOL $@" + $(V)$(BLOCKSDS)/tools/ndstool/ndstool -c $@ \ + -7 build/arm7.elf -9 build/arm9.elf \ + -b $(GAME_ICON) "$(GAME_FULL_TITLE)" \ + $(NDSTOOL_ARGS) + +sdimage: + @echo " MKFATIMG $(SDIMAGE) $(SDROOT)" + $(V)$(BLOCKSDS)/tools/mkfatimg/mkfatimg -t $(SDROOT) $(SDIMAGE) + +dldipatch: $(ROM) + @echo " DLDIPATCH $(ROM)" + $(V)$(BLOCKSDS)/tools/dldipatch/dldipatch patch \ + $(BLOCKSDS)/sys/dldi_r4/r4tf.dldi $(ROM) diff --git a/Makefile.arm7 b/Makefile.arm7 new file mode 100644 index 0000000..335ce01 --- /dev/null +++ b/Makefile.arm7 @@ -0,0 +1,189 @@ +# 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 + +# Libraries +# --------- + +LIBS := -ltwl7 -lnds7 +LIBDIRS := $(BLOCKSDS)/libs/libnds \ + $(LIBTWL)/libtwl7 $(LIBTWL)/common $(LIBTWL) + +# Build artifacts +# ----------------- + +NAME := arm7 +BUILDDIR := build/$(NAME) +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 +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 := -marm -mthumb-interwork -mcpu=arm7tdmi + +SPECS := arm7/dldi_ds_arm7.specs + +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 \ + -specs=$(SPECS) + +CFLAGS += -std=gnu17 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \ + $(ARCH) -O2 -ffunction-sections -fdata-sections \ + -specs=$(SPECS) + +CXXFLAGS += -std=gnu++23 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \ + $(ARCH) -O2 -ffunction-sections -fdata-sections \ + -fno-exceptions -fno-rtti \ + -fno-threadsafe-statics \ + -Wsuggest-override -Werror=suggest-override \ + -specs=$(SPECS) + +LDFLAGS := $(ARCH) $(LIBDIRSFLAGS) -Wl,-Map,$(MAP),--gc-sections $(DEFINES) \ + -Wl,--start-group $(LIBS) -Wl,--end-group -specs=$(SPECS) + +# 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: $(ELF) + +$(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) $(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) diff --git a/Makefile.arm9 b/Makefile.arm9 new file mode 100644 index 0000000..f743e63 --- /dev/null +++ b/Makefile.arm9 @@ -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 := arm9/gfx +BINDIRS := arm9/data +AUDIODIRS := + +# Defines passed to all files +# --------------------------- + +DEFINES := -DLIBTWL_ARM9 + +# Libraries +# --------- + +LIBS := -ltwl9 -lnds9 +LIBDIRS := $(BLOCKSDS)/libs/libnds \ + $(LIBTWL)/libtwl9 $(LIBTWL)/common $(LIBTWL) + +# Build artifacts +# --------------- + +NAME := arm9 +BUILDDIR := build/$(NAME) +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 +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 := -marm -mthumb-interwork -mcpu=arm946e-s+nofp + +SPECS := $(BLOCKSDS)/sys/crts/ds_arm9.specs + +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 \ + -specs=$(SPECS) + +CFLAGS += -std=gnu17 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \ + $(ARCH) -O2 -ffunction-sections -fdata-sections \ + -fno-devirtualize-speculatively \ + -Werror=return-type \ + -specs=$(SPECS) + +CXXFLAGS += -std=gnu++23 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \ + $(ARCH) -O2 -ffunction-sections -fdata-sections \ + -fno-exceptions -fno-rtti \ + -fno-devirtualize-speculatively \ + -Werror=return-type \ + -fno-threadsafe-statics \ + -Wno-volatile -Wsuggest-override -Werror=suggest-override \ + -specs=$(SPECS) + +LDFLAGS := $(ARCH) $(LIBDIRSFLAGS) -Wl,-Map,$(MAP),--gc-sections,--use-blx $(DEFINES) \ + -Wl,--start-group $(LIBS) -Wl,--end-group -specs=$(SPECS) + +# 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: $(ELF) + +$(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) $(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) diff --git a/README.md b/README.md new file mode 100644 index 0000000..6a7c1a5 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +# Pico Launcher +This repository contains Pico Launcher, which is a front-end for [Pico Loader](https://github.com/LNH-team/pico-loader). + +![Horizontal display mode with custom theme](docs/images/HorizontalCustom.png) +![Banner list display mode](docs/images/List.png) +![Coverflow display mode](docs/images/Coverflow.png) + +## Features +- Can load homebrew and retail games using [Pico Loader](https://github.com/LNH-team/pico-loader). +- Various display modes + - Horizontal and vertical icon grid + - Banner list + - Coverflow +- [File associations](docs/FileAssociations.md) +- [Covers](docs/Covers.md) +- [Material Design 3 and custom themes](docs/Themes.md) +- Support for background music (see [Themes](docs/Themes.md)) + +General usage documentation can be found here: [Usage](docs/Usage.md). + +## 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/) + +## Compiling + +1. Run `make` + +The launcher can be found in the root directory under the name `LAUNCHER.nds`. + +2. Copy `LAUNCHER.nds` to your SD card. + - If you are using DSpico, rename to `_picoboot.nds` and place it in the root of your SD card. +3. Copy the `_pico` pico folder to the root of your SD card. + +> [!NOTE] +> To use Pico Launcher, the Pico Loader files (`aplist.bin`, `savelist.bin`, `picoLoader7.bin` and `picoLoader9.bin`) must also be present in the `/_pico` folder on your SD card. + +For DSpico the final directory structure will look like this: +``` +. +├── _pico +│ ├── themes +│ │ ├── material +│ │ │ └── theme.json +│ │ └── raspberry +│ │ ├── bannerListCell.bin +│ │ ├── bannerListCellPltt.bin +│ │ ├── bannerListCellSelected.bin +│ │ ├── bannerListCellSelectedPltt.bin +│ │ ├── bottombg.bin +│ │ ├── gridcell.bin +│ │ ├── gridcellPltt.bin +│ │ ├── gridcellSelected.bin +│ │ ├── gridcellSelectedPltt.bin +│ │ ├── scrim.bin +│ │ ├── scrimPltt.bin +│ │ ├── theme.json +│ │ └── topbg.bin +│ ├── aplist.bin +│ ├── savelist.bin +│ ├── picoLoader7.bin +│ └── picoLoader9.bin +└── _picoboot.nds +``` +Note: If you want to play DSiWare on the DSpico, additional files are required. See the [Pico Loader](https://github.com/LNH-team/pico-loader) readme for more information. + +## License + +Icons by [icons8](https://icons8.com/) + +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) +- [@XLuma](https://github.com/XLuma) +- [@Dartz150](https://github.com/Dartz150) +- [@lifehackerhansol](https://github.com/lifehackerhansol) diff --git a/_pico/themes/material/theme.json b/_pico/themes/material/theme.json new file mode 100644 index 0000000..b037198 --- /dev/null +++ b/_pico/themes/material/theme.json @@ -0,0 +1,12 @@ +{ + "type": "material", + "name": "Material Design 3", + "description": "Theme based on Google's Material Design 3.", + "author": "Gericom", + "primaryColor": { + "r": 138, + "g": 217, + "b": 255 + }, + "darkTheme": false +} \ No newline at end of file diff --git a/_pico/themes/raspberry/bannerListCell.bin b/_pico/themes/raspberry/bannerListCell.bin new file mode 100644 index 0000000..d6b7cd5 Binary files /dev/null and b/_pico/themes/raspberry/bannerListCell.bin differ diff --git a/_pico/themes/raspberry/bannerListCellPltt.bin b/_pico/themes/raspberry/bannerListCellPltt.bin new file mode 100644 index 0000000..1a2c743 Binary files /dev/null and b/_pico/themes/raspberry/bannerListCellPltt.bin differ diff --git a/_pico/themes/raspberry/bannerListCellSelected.bin b/_pico/themes/raspberry/bannerListCellSelected.bin new file mode 100644 index 0000000..dd80db4 Binary files /dev/null and b/_pico/themes/raspberry/bannerListCellSelected.bin differ diff --git a/_pico/themes/raspberry/bannerListCellSelectedPltt.bin b/_pico/themes/raspberry/bannerListCellSelectedPltt.bin new file mode 100644 index 0000000..77f1130 Binary files /dev/null and b/_pico/themes/raspberry/bannerListCellSelectedPltt.bin differ diff --git a/_pico/themes/raspberry/bottombg.bin b/_pico/themes/raspberry/bottombg.bin new file mode 100644 index 0000000..02f9deb --- /dev/null +++ b/_pico/themes/raspberry/bottombg.bin @@ -0,0 +1,641 @@ +S4Sų5UU5͵ɔT4334ҨӨҨҨҨ10QqQPQQ͒űűűőq0/pP/o//01356t3wɵ6wΕӬ԰SSs⓽ӬԬӬssRRQ1115sQ3r22Q111Q0100000/p3tpϨθ񬱨pO/NO...ONNN..---M----, -,,,,KlK,,,,,,+,,,++,+ +-ljˬpjPˬ̨4Sɳ345U45ʹɕt43ӬҨ01QQ00Qr0ͱűŒq00/0//O///Oq36Sŗ޴ŶɴŖ86w8V3Ӭ԰t33ӬӬӬrsRRq11QrRRsQQr1Q110100pPPq5pP/N/N.....NN-...-MM-.,-- -,,,,,LL,,,Kll,,,,+,, ,+Mk O֬jR444T55ѵɵɔS331ҨӬҨ000QQQP0QqR1ɑőɑQ0qo00O/.op/P5USҵɶɶҗޖvu԰ӬӰ4ӬӬԨrRQRQQ00Q22RRQRQ10P11000/01RqЬpPɳop...N....n..NnMNϨMMLM- -,L ,,LKKL,,,,+,L+,l, lk ɌʤˤS334StT4͵ɴɔT433RѨҬҬѬ000QQ10Q11ұőqőq0P0P//Q3USɶ5uڔŴԬrqrӬRRQQ1011QRR12rRQ10QOQ001pҸҬQ0P1ON...--...-...N -Pm-L-M- , ,,,,,m+,,+,L+,,,,,l+,Mlk 쬬ʨlmk̰-jk3S3SSSTST4ʹɵɴɵɵɵɔtT323Ѩ00qQPPQQɑűőpp/pO/QPvrxڕŶɕŵɵŕŕ6ҕuT5ttӬ󬓤󰓤rQR2QQRr1010s1rQQP1q01Pq0Po//O/NON..--..-.-.-..-nMLL,Ψ,Lm,,, ,, ,,,LL+,,,LL,LLMk̨MLkK+鬌Ln,ˬ3R2sޓtSSS3ɔŕŵɴŔŔŔS3򰲨Q00PQQ00q01͐őp/0P//00001044ΔUuuutuŵUTwtɔtӨQ2QQ10110QRQQ1100Op/0/////.ONNN-..-.-..---.- M,M-,L,ML,,,,,,+,,,,,,,,KlKL,*쬌̨2ҋMSڭɌkŬ/ JLSִsS3ѵɴɔŔŴŔŔsS3ҬѬѨ񬰤qqP000qPQ0Q1ҰűŐp0/00/////000000QRuTuɕŔŵ5Tҕ6֔ʹStӬӬ󬲤QQq2Q0100111R000o0/O/..///NN.....------N,--,, ,-,lKk,K, ,,,,+,,,LKLlL,+묌.nkJ˨ɭO.Oq 3sSڳsɵɴŴŴŔɔŔs3ҬѨѨqq2p00Q0Qq1QPɱŰő0//OO/////P/0ppPPQ1uSttUtֵ޶޵ŴŔtuӬӨҬrrrqqQ2Q2q00100Ҥ00/UPPNo/./O.N..N-..-N-.N------N,--,-,L,l,lL,, ,, ,,,L,,,l,,,,kꬋ kK,r//.pɌ + )IS3sss2ʹ͵ʹɴɔŴɔɴt3ѬѨѬѨѨqqqp0P1100pqQ2ɰűŰp//OPOOP///O///0OPPPQ3S5ғtŕ5ɖtuɔtӬ԰ҨӬ򬒠rrrrrR111Q1Q0000oPOOOON...oo....-...NM------- , , ,,-,,L,,,L+,,,,,,KLL,KL+L+K̬kkJkPPKˬLqޑ)s333ͲɴɴɴɴɵɴɴtrѬѨѨѬѬѨФѨqSůq0P00p1qڑڑQɑŰq//PopP/O0///P///POPPpppPP2Rt65U4S4VUw3ӬӬҤqrrQrqQ1110111P0000ɳųp/P/NNNN.Opn.--N.M.--MM- - ,,L ,,,,,,,KL,LlkL+,L++,+,+,+,kʬ./1Ү+Kŋ//s͓sɴʹɴɴɴ͵ɔtSҬѨѨѨѨѬѨѨѬѨѬѨ1PP0Qp0pڑqq1ҰŰűp//OOO/O0/P/pOOpppppPpppp0s42ŵu8VU4vWڵUӬ򨒤qQr21111100qqO14ғ00Ѩp0////NN./N.-N.O-nN--M-- ---. ,LlL, -,,,,,LKlLK̤ KL+KK,L,,*kN1ˬ0JpPmҋ*ŬɌksœͳɓRsʹɴs2ҬѬѨѬѬѨѬѨѬѨѨѬѨШ1Q/֐oQqQPڑޑpQ2ͰŰŐP00///ppOPPOppŐŐőppɑQ2βSuŕ6ַ57⓽55UsӬSTӨqRrr1111100poP0R91q0Фp0/P/.O..N..N.n-- .-.---- -N. ,l,-,,,,,,,,,lK,LKLK˨MjKKK+KK,+kkNlLO-+/ //O֊stœųɔɓrSRsɴ֔3ҬҬѨѬѬѬѬѨѨШѨѨШQQְpŐҐڲq1ɱűɰŰp0//0PPpPͰűŏppɐŏŰɑŏpŰS֑tu64tUsTӨ44ҨqQqQq01Ҥ/ЬX񬐠ϨѨ///OONNO-....MN..--n-M--- NMNMNLM,,,,,,LK,,LLKK,LKNjKKKKkK++ʨ jj +͍r ͊sstsssS23SŴɴʹYZs2ҬѨѨѬѨѨѬѨѨШѬpёoͰɰŰP֑QɐűŐp0//POPPooɰŏpŐŐŰɰɐɱS4Ttŕŵ3ҔŔSޓtӬrӨӤq110Q0P4ҕ޵O/oNONO/OONO.......N.MM -N--, -- .pɮnLLlL,,,,+L+L,,,+, ,+K.K+KK++ 0,kkPґ-. k KSssSrR322sŴɴɴSXYYRҬѬҬѨҬѬѬѨШШp֑޲OҳrҰqڲqɱőŐP0OPoPpppŰɰɐŏɱɰҰŐ2RT3SSTUu6ҴɔT7SҨu󬳨ԬӨqqqq1s2/1QШѬ/P/..NN.oNNO....oN.-NMm-.- -------N.PnML,,,,+,LL,K,kL+,+, ,++NjKKKK+,*MkS̨֫lo oͫkɨSSsSSS22SŴɔɓs8YҨҬҨѨѨѬѨѬѬЬqOqޑO/4/֑޲QҰɱŐpp0/PoppŐ1ұSޓ2ʷuTɶ͵͕utSSs3ӬӨrqrQ0PsP11Q1Ru摹QpP//..../.ON....NN.MnNn------- --. nҮŬL,,L,,,KK,K+,l+,KΤk,,,+kNKkk+++ ȨL +* ˨*.N-oҊ͍ 3SssR322sŴɓų͔޵3QҬҰѨҬѬѬѨѬѬЬQ1OPpp/.PPP֑PѱɑqppP0/oŐŰŰŐŰQQQ110128ʱS2TuɵɔŕɕtTSsT4Ӭ33tӨqqqQ010041rӬSűoO/0//...../NNNN..--.NM-M--. ----,- nmQ0ү̬mLLLL+,, +,,,+,lj+,++,+ KKK+,+J騍L΍ʨʤK ΐҫ OҌ +33SS23SŔɓŒŒQѬѬШЬЬ1ɑޑpOpO.o5wޱ4ww5PɑŐpqpPP/pŰůŰɱr00qPP0QQQRQ00QR2stVtutŔtuŔTsͳ33ҤҬӨӬrRqqR0Q00P1QҨpⰤpp/o///....../NoNoMM-.N-NN---------- - ΰMlLLLL,,KL+,,+LͤlkKlKK++쨋KK+++*ɤ )LLO ͬ΋0K222212SssssrRQ1222R2ѬШp1ppNO654WޱPɰőŐŐŰppop0QQPPqP0qڲpQQ0QֳqSδ1STTTTSsStTӬus󬲤󰲤rҨQ1000QrTwtQqP0OO/0.O....nN...NM...---N-M------- -- nMLmLlLL,,,+kL,,,,L+KJK+lKk+++JjKKK *) j Opklm222Ssr111QsrrrŒrrQQѬ0ɑޱoڐwwW45wޑpҰɰɲɰΑpͰQqQrڑޑqޑp޲p/QΓRғړ343ɴRɔ򬲨Ҭ11001pqP0POO////....-N--N..-. .-.- ----- --- L/MLLL,L,L+,,,+++,+,++K+,+++KkK,++KK+++ ȠQꬪP.ҰOj+ʨʤ + 222RsŲRRRrŲɒɲͲɒqR0pڱޑW5wx5wޑޑޒұ0QҰŰůͱɰɰɯ0PPQ163prڳQޒsQ1p4W4tSTTqq1000qrɖ0vvҰPqPO/P//.Q....-mnM.NM---- -M--M-------- LLL-,,,LL,,,,,,,,+++KNk++,+++++++* jKK+++i,kLLkP o֐-* +묪RR22rr2rRrŲ͒qQϬ0PֱޱV4wVVVpQQ1ͱ0p0ҐůQڐŰɰ0pqޒq0q56ސڲ4O.0ғmҰU2ttTtStSҬqqtqq0/0񬲽r/OpON/..--..-N-------------,- -,- OmmlL,LLLLLK,+,K,+,+,+KkK ++K+K++++JJKK+ jmklLk- ґj+ Kk΍ +QRRrŲ3R2ͲrŲ͒q1ЬϨґޱ5VVVV5qͱɱɰɰPڑɰɓ͏ůPqppqڑޒPR5ޑޱޑpִ6PT4sSҰҰҰ3tSuҬSqpҬ2ҬѬQ0/QőTTѨOP0//p.NN./-.-..-...------------ --  ,-,nΨlLl,,,,LLL,,+,+,++++KK+++++KK+++*+*J++++ɨ kK n 1Β* + llKRQQq23S2ѲɒŒɲѲͲɒQQЬϨpPڲ5Nְ5wVqɐŐŐŐŐŐŰppqR1ҏq޳PpP1pސޑp/ҳ.PpP֓ޑ4RSҲӬҬӰҬҬӬ3SqӬQpҬqP/OPPPpP1uڶҬP/0/O.N.N.......-. N--M- -- - - ,- ,,쨭lLlLL,,,L+K,,+,++,++,lKL+,KK++K++*++J JK+ +m̨kllLKl/Ҭ+ɭQQQ2222ͲrŒŒɲͲɒŒrQ1ЬϨШϬO/ґn,p51͐qpŐɯrpPʹqr޲Pqqڑڳ632ҬҬӬҬҬҬҬҬҬҬҬrpppҬT򰱤pQqѨr޴Ѥp//O.......N. .--N--- - L,LKklLL,,,,,,,+, ,,,+ +++++K++++K+++++*KKK++Ŏ˨lk̨ +qެŭklQQqqű͒ɒrqqrqQ1ЬϨϨϨϨΨq.Nowx0pppOpŐŰŰR01Pֱޒ޲P655qW޳rr3ҬҬҬҬҬҨҬҬҬҨqQrpPs򰱨ҨsTSW1Ѥq4ҐpOO//...-. ...M --.-. . - - -- -,-̨,,LLL,L,,,,L+L,+,,+,++++++++++K*K++*K++KKKK*+) 2nk/ҬŌkk0QQqqŲɲɑőrqrqQ111ϬϨϨϨϨϨϨo0q/.pUVp͐PPppɯɱ6ps511//pڑqPqWO/P5/RRQ0rVִ򰲨ѬҬҬҬҬѨqpp2Ŕ򰲤򬱨ѨШѬҬ/op//.........M- - -- M- .- ,LnpשּׁL,LL,,,,,,,,K,,L+++,,Kl *+*++++JKlJK+JKK*JljJ+J1֌ kkpڐ/ŭŭ00PQqpqqQqqrq1ϨЬϨϨϨϨϨ/Op4T/oppŐ͐UOPpWQ11Q./0ҳ///OPp5r֖ӰRpѨҬҬѨѨҬqqpRҬҨQpPӬO0/PO/pO/.N-..... .N. N----MMMMmϨ0P/ͨOLL+LLL+,,,, ,+,+,++,,JjL,,,JKK++++*++KJ*K+*Jkjj**IŌ+kjjMkk010QqrQ1ЬϨЬϨϨϬЬϨϨ/pұwWOҰŰŐŰŐpŰŏŐPޱ5w2r1֒/PQ֐ůP֑ޓpop55oڵTUֵRSҨҨҬѨҬҬѬҬqqճs132Tpq0q0//oO//..N-........---.ΨϬlllKL,,,+L,,,,,,,,,,K,, ,,kk KkkK+JK+JKkJJ*K+++KKKKkJK+* +I+jJJik͌K/1QQrQ0ЬϨϬϨЬЬϬ//4xPͰŐɰooŐo͐4pɭpV5627-Psڒ0T֒ڒp޳RRS֒1RҬѬ򬱨ѨѨѤqqppѬ5Uo00///rON...nM...-NNM..ϬpOmMmm-L-L,,+,LKl+,, , ,, +, ,,Nklk+KKjKKKKKKK*KJ+++K,KKKKJK+ ***K Jj)pګŬϬϬϬQqrrQϨϨϨϨШϨϬϬϬϨϬpOwwp0ɰŰpOoŏͰ0pW5V1Ғ, Ѵ͓0p6pڲpڑOp޳6RUЬѨ񬱤3PpPOѨRsSsRѤP/0oN/oШ.N-N..-n./.mnNnNQqnmMMM-,L,,mLLLLLL K𰌠,,+K,+,+KlKKm+K+kkkKKJKJ+JJkJLKKKK+KJ**+Ȥl鬫JJIN֐OϨϨϬϨ0QQQPϨϨϨϨϨϬЬЬϨϨϬϬ/oޱ55PͰɰɐoOOooѐސPqp6Vp.Nֱͳ͓ɰ/O֐pֲ/6rqPrR򰱨ҬpPoOOpѨѨҬҬoOOO.OONPN.....-M.NϰΨ/NMnPpάmmMML,LL,,,L,+,+,+,,MOkK+,+MkkKKKJK+KkJ+* K*jKkkK+KJ*++J訋ɨjj Ό ϨϬ0//PQQP0ϬϬϬϨϨЬϨϬϬ/oN֏ްޱΏŐŐpONNɎpOq/ґ5VOp5wRɑSOO5.o֑ P/p/3ɓ2ҬRѨpoppOoOSҬѬP//O//...NON....-.3.Ϩ/..O.p-MMlL,L,,,LL,+,,,+ LŮK,K+++ͤlkkKKKkKK+K+kJ+KKjkKKk kkKJj+K**kɤ騫ʼnK,ʨjjj00P0/00PP0ϬЬϨϨϨϨϬϨϬϬ/OҏްڰoɯooonҏO/֒qpұ5.p5wwo5WpqOqP0ɒqqrڏS3QpoppPoOOpѬ򰱤pooOOON.....0M....--..N.OmɶN/...-nͨmM-MLM,,LKL,KL,,+, ,kKl+KK+,+KKJK+KKKKJ+K+KJK+kljKkkk̤kjJ+KJ+++* ɍkjjjj00ppp0000/0ϬϬϨϬϬϬ/PppoopoɏnoqҏqPpPڲP4o ҐWxww56ސPڑP֬ڲɓ0ɭrQpШ1q31񬐠ppPOO.PO/N./.O.M...M...-.-άɵΨnN-.M.M-mMLLL,L,L,KL++,K,,, ,LK+K+K+++KkkkJJKJJK+*kJ+K+KKKkkάlkkk+J*+J*J* ) ,jkkjji0PoŐp////0/0/ϬϨϬϨϬ0PpoON.O/ͮɯ0֑ŐҲP޳/MұN֐55vx55V//qqPqQ0/ɮQүpϨ4Ҳ23ҬpœpOONNѨoOo񬯠.N/N....Om-.....NN S֑nM..N---mmmlLM,,+L,,,++++,,,kLK+L++++K++++K*K++KJKJJK+KJKk+KkͨͨkKkJK*JJ*KK*)J .jjjjjjOpŰɐO/00000ϬϬϬϨϨ0OpŐůO4UoN.Oo֑ޑpڑڲqO/֑UOqs1Ppo-oڐސްް55UVwwV5U5/P/000үnoŎ,k2R0ɱTs֔oStsoOOoѨoooOMNO...NN...O-.... SSP0NM-MM,--ml.͠L,,,L,,,,+L,+,,LlK+,+,K*K++K+*K+K*JJK*++J+JOjk/KKJkJkJ*+**+**jhɯjjjipŐŐŐp/0//0/ϬϨϨ/0PpŰɐ͐55opo.p޲/֍/5/rQ/Pqo-Noֱoڰ544UUV5P֓1OnmoP0p/rɯ1ѬONOonoNoNoNNN.N..o..N ..ϨpϬnMM---- ,l-LL,,-,,,,,,+,, LLk+L+,*j+*+K+K++K++K+J*JJJJJJJ쬫̨jJkKJJJJ*JJ***IIq +jjjpŐoO/////ΨΨά/PpŰűɰqopޱo֐5VV65ѭ͑uPQ0֑ޑڑސON-ұ-ҏ4vV34VޑQگŏ/ΒޑQrRS/0/SsRR1ʰSRШoonNnOOno../.NNN./.l -./QqϨnШM------ m ,,-,,,,,,,L+,,,+KllK+K+jK*K*++KJ+JK*JKJJ*+JKKK+.-KKkJKJJJ)JJJJJ+*)i訋ʤjJooOO.///////άά/OpppŐŐɱɰPO/.OڰސڐV665ҍŭ.4OP//P.OOo N ΰvW5.5޳nɔ02Ғrts3ґppU/򰓽nooNn/Ьn...--. .-...0ϨomM- ,-,- -M,-,,,,L,,,LLK,+,+LKkL,LKkKKK+++KK+JJ+KKJKJJK**J*kj jjkJ+JJJJ*JJJJkjJ***jIi*jooOOOO////////OppŐŐŐŐɰůOֱV65ްޱ.͌ToPPON.NO pɰxvޱ//Ү7Q0Q01stڵStO0O/ponnnN..N-.  ..Ͻ -/.O.nNϬq0M.- - -- - mιL-, ,,,,, ,KL,++,lKkK,KK*jK+K+K*K+K+K+*JKJJJJ+K+*J+K jjJKJ*JJKJJJJjiJ*JJJjJ+jjoooooOOO.N/NOO//.//PpőoppŏɯŎŎNֱV6.ͭɬɰvqp//Pp-pO αސŪɱUwwOOO oqQ70Ɍq֓srPrűPQon񬏠nnoNnN..- .-..On/M-- - -,,LNl,, ,,,,,,,+L+,,+ +L+KL*KJ++JK+KJ+J+KK+KJKJJKJ*+K+JkKKJkjJJJJJJJJiJJJI***JiJɤɨɠjooonnnonoooooooŏnnOOoopppo0ɯɎ/ֱ55p. .NU4ޑOOppڑސ޲pɱ޲p֑֫OְvvUpPp/P֒W6rrΓͱp0Qpɓ񬰤poШnШonnoNNn..  - .QM../oqnnnΤLM,- ,Lll,-LM, -,,,,,,,+,,+,+,,+,+JjK*++KKKKJJJKJ*JJK+KJKJK+++*J+J*KKJjJJJJJJJjJJJJJJJKiJɤɤŏŏŏŏnnŮɮŮɯɏopŏon0PrɮŮŎ/ұ .Nֱ . vppO.Oֱޱpɑސ⊽oO4qp pɲڳrr֓t2͒11QSs0P/PPooOoononnoM--..NqnMMOO33ήΨNMM---,MML,,,,kL , ,,,,,,,+++,, ,+jKKK+KJJ+K+kK+KJ+KKJKJK*JJKJ+JjJ*JJJJJJJjjJJIJJ)K*JIiůɏŏŎ0PPPPqڑp/͎ŏ21qQڑQͮŮn.ְ5-ΐڲ͐VUޑn/-OҐ޲/ҫ.oqֲhn͐ڐN/ҒPҳWޒڳrֱrQ͑rsttѬr3o쨮ЬoѬnnnnnnN.nΤ,pq֭Mno4tگYQnmM-M-mmL,,, ,L,, , ,,,,, +,K,,k jK+++K+kJKK+JJJ+KJ*JK+KJKJJJJJJPMKJJJJJJJJjkJJJJIJJJJ)jɯɮ/0PQQڐqޑޑ޲0͓Q֑PqڲOɯmnmұq. ΐޱ.wޱ-/ֱrr ʑ0iޱ..p/qrɒųɓnqqqŐPtڐ4ͤMnШnonmnon/.-mmmŕ03ΔϬΨ1mmMmmmL,,, ,+L, , , , ++,LK++KKK++*+K+JJJ+KKJK*+KJJK*KK*+JKJ*JJii*KJJJ*J)JJJIJJJIJ*JJjJj/Opޑޑޱo-Ґ/ͮmMmpOoֱNpw5 NpڪOғO΋/rHWoPPO/QPpqڐP0TTsSr3֯0pr֐noononN֫pmnnnSpΨmϬΨQnlLMmlLLL,-,,ͤ, LmlLkk,,kK+,++++*K+KJ*JJJ+J++JJKJ*KJ+***KJ*J+*KJJJJJ)JJJJJJJ*JJ*JJjiȤ.OOޑ޲޲55O p6oo/ҭn.MM.ҲOOҰ/ -p։ڴPrO/qWw/qqp֓OҳڑqP/ϰ3Ұ0qϬm3 ŭoonooNϨop0.nmmnP/ϬmmMnmmMmΨmMMMllLLLL,LLlͬoͬLLLKkkͬkKK+++++KJ*JJ+J+J+++KJ+Kk**JJ+**+**J*JJ**J*JIIJJI*JJJJiiipސޱ66p..ґW45O/͍N--p. .ҐNp޲O,PqQҫqqPqҋwVr//ғޑֳpڒڑ-qάά QPoΏΏ4֭nononNop0.mmΨlMMmmLMΤLLLmmMllLKLLKLLSڏmllllŋk̤kkkKK+J*+KK*JJK***J*JJKJJjJJ****J**JJ*IJ***JI*****JJii56nN֐޲6xWVO.ή.N-, NҐڱޑN֬k0oO Ūp.pғɒrqiWX/Q֑ڳ.rOֲo֑ڑʓͬάͨ.OϬ24ҍnoonmɐޑޏmΨmMMmmmLMΤlLlMmmLlLLLL,LLO-̬lLlMLlKlkkkKKJKKJJ*JKK+J**KKJjJJJiǬIJ*JJJJJJJJIJJJ*J*J+JJJiij665ސޱ޲VO.ͮNN--M-Ґڑڑޑp-+Ů P. qڳʪ7 //QPy7q0Pq ʲOβp֑ڑޱ.oP֎̨ͬͨ άάű3qnnnnnШnnΨŎϬΨmmlmאַlLmmLmLϨLlmlmlllllLL,kko.ﰌά0kkLKKkklJkkJK*JJJJ*JjJjij*JJIIjIJJJ)IJJJIJJJJ*KJJijjIiijiiiU6ޱWvPɎnN.N.M pN-ŮŌɭ/O OPҪX ҒQq֫XXqQW7޳.ɐސڱoN֐ +ͨ -N.ͰPpЬnnnnnmnmnΨΨΨϬmmmlmLLmϨlmlmlLlLLL,lL̨̨oo/OqllKKKKk+KKkjJJ*J+JʤijjjK*JJJJ +/ +JJJJJ*JJKJKJJKIiȤiiiiiiiޱwW4/ɮŎnNnNNoŬ ΐސޏ. P֌m/N5px,O.PqxP07X/Nְސo--o-ɫKͨ -NnpRrΨnnnmommmmMΨΨmnommmmlLmllllmlllLlLLLL̨ŕ//llKLKKKK+Kjkj*JJJJJijjjiJ**JIiJJJJ*JIJJJJjjȤȤȤijiijiiʤ6O ɯoN.o.O- . qɎLlk/Ґ XxM./Β/8qŒWP.ΰMN--n ͬͬͨMnmN31TֳNmmmnnmmmnNmmmΨlůM̨mllmlmlmmllLLllMLlpQml/KKK+KK*+KJjJJJKJjjjjɤiJJJJ*JJJ*JJJJijȤ砨iiiijʨޏoޏpސްްސސoڏڐސ.NOO/ͯON.NN...-.֑Lmk p45 O Pq֑q/ʓ77OɏnN- . - ΫK쬭 mͮMrްP1ҭmmnnMmmmmmmmnMmΨ-N.ͬlllmlmllllLlllllmO/OoOmllkLlklKKLKKK*+J+KkjjJJ)jiJjj)ȠɤI*KJ+*JJJJIjiȤȤȠȤǤȤȤjiiijoNN.....OppoNMM--ұpoP/o.MnN.OooOonkLɫ/O֐4UPN֒pִ.-W6OpڰNo . .jJ +-P,qQl0mnmmMMMMmNmnnmmMmm̬ͤͨͬO. mllmlllllllɲﰮqLmKlKKkKkKKKJKKJKKKJ*J*jiji(ȤȤɨIJJJJJJJiHȤȤȤiiiʤji-- --֏pO/ͭm/pp/NNoڐo αO֋l./-U5P Β8q .p֒pNWonֱ,NN-- ūjjJJ,qɍqڲޑڬ ͬnnMML˨̨mmNM ̬ͨάͬllmllllmmlά0kLlLLKKKKKlKKKKJKJ++JKkJIJJJji鬧ȤǠǤȤiJJJJJJjiiȤǤǤǤȤȤihiiiJhhiɮɯɯ -.-.MN֐O /p֒p OoON ΐɬ  pvvV.qXOO-Ҳp-Ґ.ūoڏ-ҏo- N ɪIJ*J0ō/5qɎެL++쨭̨˨ + + +* + JMlmͤͨ쬭̨ͬlllLlllPϬlllLl+LK+KLKKK+KK*KKKKKkkkJJIJjjJjjȤȤ褧Ǥ計JJJJIjiiiȤǤȤȤȤȤiiIiiiijꨯɯɯɯ .ONONoNooONN ..Opp- -M N-NNp͉VV5OҪO66..p.ҐNpOOְNoo. O., ŊjIIkJҌqڬ ,ΫKʨ +IHII)))))*)+̨̨̨ͨͨͨͬlllͨPͤΨllllKK+,KKKKKKJK*KJ+JKKKJkjJJJJiJjjJjjɤhiǤ計IJJIii ƠȤȤɤȠiiiIIiʨ --MnNNNNNNNNNopOoqޑoO. - Non֐p/ҫŏ5p OֱXp/-ڲO αO.ґ-Oū Oo -ɉjJJJɋ JŊjJJjJII)II))IjŊjiJJ+ + ̨ͨlklllllklKK+KKKJKKKK+KKK+KJJJJkkkjJJIJJJJJIiJIiiȤȨȤiiIiiiiǤȤȤhIIjij-,Mnoްްްޑޏooopoo֑޲oNnoon-.--ұPɊp4 p7O oO Ώ ŰNPkJɐڐNo ʫjJJjŬŊ ūjjkjiII)))Iij)**KK ̤ͬkkllkllkkkllͨmlllkKK+KKKKJKKKJK+KKK*KJkjkkkjjJJIJJIJJJjɤhȠhjijiiɤǠȤȠhIIIȤooopooڑޱpڐڐޱo - ΐnڰQj΋.ұ޲pP/ PON ΑWWŬppڍKko֐.ҫjJJJkŬj ҪK +*jjjjJI)ijI )I + I*ͨͨͨkklͨkllͨάllLlkkKKK+KKkKjKKKKKKJJKKKjkkjjKjJJJJJJJJIjɠiȠȤIJjiiihȠǠȤI ސonNNMMNnڏޱ4465V5ū ooްL,ґQ1.ɪ poֳ-WV50ΌL+NڱpjjIKū Ϋj+ ̨̨ +*JJJIjjI(iI養jjjɤIJͨͨͨlά̨ͬͬͬlKkΨllllKkKKKJ+KJkJKK*JKJKKjkKKkjkjkJJjiJIiJiJjjjHȤȤȤȤȠIIiiiiȠǤǤǤg許nN--Nn֏ޏޱ6WVwW4N ΏnnސnM-,pͪŊʼn /ґ.αW4ڒŭl ސoūjjjJJ+*.ҫK , + ) IjjIʤkkkkj +̨̨̤ͬ ,̬̤ͬͨlllllllllLKlKlKJK*KKKJKJKKKJKKJKKjjJkjjJjJjJJIJJJIIIIJjIJiIijhiIiIIIiiiȤǤȤǠƠްްސޏސްޱ55UVޏڏگްސN- onMnMMNNOpiGGhGū/pڲN OVqڲ, /ڐŪ** + +jɋok jJ)))(Ijijjꬋkjkjjj +쬬-͌ ̨̨kkkkllllllllkKLKJKkKKKKKKkKKJJJKJkjkkkKkjkkkjjJJiJJIJJ*IIjjiIIIJIJ*))iIiIiihȤǠސް޲45ޏگڰްޏ-- ,NޏސސM, Nɪ N֐OOx޲5nN +-NֱŪk +Kkok* lk - ΫJI)))))) +ꨫkkkȨ̨ -, ̨̬˨˨kkkkkllllklKLkkkKKKKkJkKkJjJJkjkkkjkkkjjjjkIjIJJJJIJIJiIiIII))))))IIIIIiihȠȠ礏ސޱްonn֎nگްޏnNM-MoްސMnn֐oNOONҲڳ7N.Α7q5ڱP0, N֐-Ҫj* */kj j ΐnְ L + + ) + +ˤʤ ( 쬬-Mk+ ++ˤkKKjKklllkLkKKkKKkkkkkkjkjkkkkjkjkjjkjjIJJjJJJJJjiiiII)))II)IIHIIHIihiiȤǠǤ礱ޑސڏnmMMMmmnMMNML,,MNoڏoڎonmN-Mnְڑޱޱޱ6r.ґX.NޱpN-ΐ4 LNNo֪j* **kkJJ Jjmo֏OKˤ) +) +   (  ̨LL,Ґo֊JJ + +I* +̤kKJKJkkkllkKKKkkkkkjkjjjjjjKJkjjjjjJIIJJJIIJJIiIII)J)IIIIihiIIHIiiihȤ..No֑ޱސnNL,MM֎ڎnnmmnn֏ڏڰްޱ5V֑ޏ ʑڐڱޱޱްڲސN֐ڰ40έ.n֐ ƪjJ)**kIO֋JJ** + +j,--l˨ˤ˨ )))()(()((((() ++Kŏn֪I*) III +˨ˤkjJKJJKkkkKKkkkkjjjjJjjkjkkjjjjjKjjjjjJJiIJIIIJJJIIJ)I)II)JIiiihIiIiHhiiǠ礲qOoN --NҐڱ6VސVpp֑pڏM ,Mn֯NjjIJ*kI/J*J** +JImְ j +*)()))()( (((((((() *+-ҏڲ ΊIJIIiJˤˤkjJjJJJJJJkkkkJKKJjkKjkJjKKjJJkjJJkJjJjjJJjJIJ*IJIIJJIIj)IIIIihhiiiiihhhǠOֲ6oMN, ,M֏ڑڲ5444555ސoo., oֱސOOoo֐ڱސ-nnڎnMMnooڏڐڐްޏڰސon֏nN֏ڰްޏސްޏ ŪijJj)//***J))*J)In**jjiII))))()((((((())H IJIIjj *ˤˤˤˤkkjjjjJjjjjjJJJjjjjjJjKJjJJKKJJJJJJJJ*JJJJJjJJjIIIiiiȤhhhȠ椪 .Α7WWސnN-,,-MMNnnnoڏڎon, + + + MNn֏ڰސnoN-Mnnn֐onooopڐomN,,MLM-moֱްގnnnnnNNŪjjPjIJII*)JIIInJiIII)((((((((((((((()((( IJIij)j*ʤjjjjjjjiijjjiijjjijjjjiijjijiJiJJJJ)*JIJJJIjjiiȠǤǤȨȤȤǤȤȤȤǤȤǤȠȤȤǤǤǠǤȤȤǤhǠ6o, -oڱVWxW5ްޑްޏڐޏޏޏoonONMML--+ -L,MNN-,-NnonooڏoڏonmNN֏ڐڏooonMNnN֐o..ҫJIjjIiiIjiJmn֊iiIII))IH))I(((((()(((((())( ūiJIIjIII +ˠkjjjjjjjjjijijiiiiiiiiiiiiiiiijiiIIjIIJ)IjiIiiȤȤǤȤȨȤȤȤǤȤȤȠȤǠǠǤǤǤȤǠgWXxyVo- N֐ޱްޱސްoonooڐސޏonOnM--,, ,MmNNNnnoگޱoM,--MMҎN- ɋjjjjjjiijjIiIIIII)HI()))()((()())()HiiJII)iIJj+ +ʤˤkjjjjkjj˨jjijiiȤȤȤȤȤȨǠȤǠǠǤǤǠǤȤǤȤǠǤǤ56WW5onoooڏڏnmnNNL-,,,, ,-,-MNMn֏oڏon֐oooڏOM.---,-MMNnoڏڏސސڏoڎonNM-, Mҏm ŪŪjjjjjjjjiII)IIII)I))I)))))))) ))IijiIIHJI))IJj* +ꨪkkjkjjjj+K +ɤʨʬɬʰ + ++++LLKkllkKK+ +ɨȤȤȤȤȤȤȤȤȠȤǠȤȤȤȤȤȤǠȤǤǠǤǤȤǤ,,,MMmn֏ڏڰްޏ֏nMMMM,,, ,NoooonNMM, ,----MnNnڏگސސސoN- - -MN-, ,ŪjjjjjjjjjIJII))**** + + + + +jJII)JjJI))IIJjIJ* ꨊjjjjjʨ+K +ɨʬ ꬩʬʬʰ ++LLlō͍ͬͭͮѮѭѮɋK* ȤȤȤȤȤȤǠȤȤȤȤȤȤȨǤȨǤȤǤȤǤǤǤ -, , +,MNn֏ޱ޲ޱޱސڐސpڐސސސڏڐސސސސޏooڏoڎڏononNNnooڏڏڐޱo. -M-- --M--M,, ,-N- ŪkjjkjJKJ*JK+* +ʨʨʤʤʤʤɤʤɤʨʨɨʨ ****JjIjJ))))*)J*) +ʨjj++ +ɬ + + + + ++LllŌɌɌɭͭͭ.--.-, ɪj))ɨȤȤȤȤǤȨȤȤȨȤǤȨȨȤȤȤǠmMNMLMNnNnMNM-,,-,,----,-MMM--M-,,--M-,,---N,MMNNNMNn֏o.ɫūūūŬŬɬŬɬ-MMNnn- .MMMNMMMNnڎ- -NM-ūjK + KkK +KJ, + ʨʤʤʤʤʨ + + +*)JJ** + + *****ʨˬˬʨ˨ˬʬ ++**JKKkkkkkɬɬ ..N..N-NNMNnNMnNMM- Ɋi() Ȩ ȤȤnNMmMMnNMMMnnMMMMNNN-MNNm֏ڏޏonN,,, + ,,, ,- ɫū Oo.-,MMڏڏڐޏڏN -NpޏޏoڎnN- ɫūK ˨˨Lk+ ++ʤʤʤʤʨɤʨ + +* +* +***)*********+++Kkkkjkŋūū -.-..NNNNNNnnnnnnnڏnڎڎnڏnnڎmmn ΫiI)()()( )  ( )(() mnڎnnnNMMmnnoڏoڰޱonnnnnnnnM,,- , ,,NMMNNN--,- - - -N֏ްޏڏoڎnN -oڰޏnnN- ɬjKjūj+˨ ͬ+ +kK ʤ˨ʤʨʨʨ + + + + + + + + +JkJJjjjjkjkjkkŋŬū -,-MMNmMmnnnnnnnnގnڏގڎގڏnڏޏޏގގގnnm-ŊIHH(((())(((((((()()(I)())I)()( )謏ގonMMN-MnnnڰްoN-,-.MN.NONNNNNM.--- --.--M-MnooڏޏooonM-MNM- kk --noM ,--- ͫŋJKJk +*+Kkk ʨ +++**** ****jkjŋūūūū ,,L-MMmnmnڎnnڎڎnڎnގޏnގޏޏޏގޏޏގޏޏnڎގnmM- ɉiHHH((((((((((((I()(I))II(II())HI)))(())nn-NM-NM,NoڐޱޱN, ---.MNNNNNNM-- - - -- , - ,-- ,-- άŋl+K+k NoNM--.. ɫkJjJK+K++LKlLl jlkk++ + +kKkkkkkjjjkjū ,,,,MMMNmnnڎގގڏގގڏގގގڏޏޏޏޏގޏޏގڏޏnڎnnnnM, ɉIHHH(HHG(HH(H(HH(HHIHHIiIIIIIII)IHIII)HII)onM---N.Nnoڰްo- .MNN------- - ɋK+++*Jj-nN----, ūŬjkkkkllŬ ͬŋKK++K,,LLŬūūŪūūūū + +, ,,,,,,LMMmnmڎڎޏގގޏގޏޏޏޏގޏޏoޯnڍomnMmML+, ũhHHG(HG(HHGHGHHHHhHIiHiIiiiiIIIIIIII)IIIIiIN- - -.NNnooڏޱnM -------, ɫkkKKKkkū LN-- - ɫūŋ ŬŬŬŌllll- , , , ,,, ,+,, +,L,,MLmMMnڎnڏގڏnnnڍmLMLLL , ŪihiHGH'G(G'G'H(GHH'HHHhHhiiiiiiiiijIIJiIIIIiiijio--  ...NooڐڏްްޏoN -, , , ɫkŋ -,-- ɬŬŬŬ - ɬɬ -NNNNnMM, + , ,,,,L,,,,M,,LM, ŪũihhGHHHH'HHG'('''''G'''G'H'HHHHiiiiiiiiiIiiIIiIiiU5. ..NOooN. , , , , --.- - NonNoڎڎގmMn+, + + + +ũihhhGgHGGHH'G('(G(''&&'G''''''(G(G(H(HHGhihŪŪɩiiiIiiiii5O - , , , - ,- ū ---.NNNN....-M-Nnoڏސްޯްޯގmm-, + hghHGGGHHGG&'G(''''&'G''''&''G''''''''''''''''GHHHhhiũŪɪŊjijiiij/ - -,, --, ɫūūūū ......N.NNnNnNnMNnoڏްްގM,,ɨũhghhhGhghHGhGGGGHGHGGG'''''GG'''&(&'&'G''&&'&'''''&'''''''''G'HGGHHh ,,,, ũŊi- - - - ,,, - ---, --,--, ɬūūūŋŬ ./OoOO.NNONnonooonoڏްm, ŪũghhhhHgHgGGGGGG'HGGG'hGh'GHHG''GG''G&'&&&'&'&&''''''''&'&''''''''''GGHGHHHHhh ,LMmmڎMmML, ɪŪ5oN- - , .-M--M , -,,-M-MN-- , ,--- ɫŬūūŋ .oڏޏpoޏooonoގonޏڏޏޯސްn,, ũŪhiHhgHHGGGHGGGgGGG'hGGGGGHGGGgHGGGGFG''''&''&''&&'''G&'&'''''''''''G(G'HGHHHgh ,MڎMڎޯޯގގnm- ɪŪVv4o. -,-MnnMMN-M--- , MMMMMNN--- , , ɬūūj.Ooڐސްސޏސޏޏސޏޏޏޏސ44nL,+ũGhhhGGHgHHHGGHGGG'HGGGHhHGGHhGGGHhGG'GG'G''G&&&''G'''&&'&&'''''G'HGGGHHhGhh ,MmގޏގnM ŪʼnwUU4N .--M֏nnڎmNMM- ,LMMMnMnMMM,- ɫkkjkkNoސޱްްޏްް33444ޏnL, ũňhhhghHGhGHhGGgGHHgHghhhGhhHhhhgghGhG'GGHG&'&&'&'&''&''&''''&'''F'G'(GHHHHhhh ,Lmڎޯޏm, ɪŪʼnʼnřwU4NN.- -MnڏގnڎnnnN-- -MMmMnMMM-,, ɫŋkkkjk-N֏ްޯޏގޯ444ގM, + ũŨhhhhhhhgghhhhhhhghghhgghggGGHHGGF&FF'&'&G&G&&(&&'''''''G'G'HhhhHũ Mmڎnl ŪŪŊxvUo.NN-... -NmڎގnڎnnnmM- --MmMmMMMM, ūjkkjkŬ Nڏް3ޏގڏ343MlM,+ ++ + ɩũhhhhhhhghghgGGGGGG'G'G&'&''&'&'''''&GGHGGhhhɩ ,LMnޯ2m, ɪwT4nonooOOoN..  -MnڎޏގގޏmnnM- ,,LMMMMM,,, ɫkjkjjkū--nְ444ޏnڏޏް3ގގmMm+L,,, +ũňʼnhghhgHGgGh&GGHH&FGGGh&HGGGGGGHHghh ,Mmmڏޯ3334ގm,ɪŪijwvU4ްްްoon. -MmڎޏnڎnڎnnM- --MMML- , ɫūkkJJjjkū -mڏڰ444ޏގnnڏްގmmmLML+,++ ɩŨũũʼnũŪhhhghGhghggGGGhGhhhGhhHhhŪ LLmmڎޯޯ33333333ގM,ɪʼnjiiWuU44N-   - .MNnoڏnޏޏnnMM- -,- -, ūjkJkJJKjj ,Mnڏ43nnnnڏޯޏގڎގގmmm,l,+K ++ + + + + ũhgghŪ ,Lmnڎޏޯ3T34TS4S33m- ɊijIIwvVvUUT44N  ... --MNNonڏޏnnnmNL - ɫŋkjKJJJ*KJjk ,MnoڰޯގnnMnoޯޯގގnڎmMlMl++++,,+ ++++ +++, + + + + + + + M +ũũŨũũ ,Lmnڎޯޯ334TT4T333ލM ɪjjIII5443434444333U454oN-- -.-N.NM.M.MNNNnnnnnnnMMM ͫɬ ūūjkJJ*****Jj ,-nڎڏڰޯްޯnnmMnmmڏޯޯޯޯޏގڮގmmm֎m+L,LL+L,LLLLmLnm,LLM,+,+,++,LL,LLmnM-LL-, ũũ + ,+mmڍگޯް334T4UTT33ގM ͫŊiJ)I)333444TTTUU43oNOnnNNNnoooononooڏoononMM-- ɫūɫɫūūŊkjJjJJ****)JK -MNoڏޏޯޏޏnnMM-MMMmڎoޯޯްޯްސޯڎގnގmmMlmKLKLLLLmnmmmڏޏގގގnmmLLMLmMMMnnmڎnގގގގmmL, +ũ + + + + ++ + + +Lmnގڏޯ233T44T443mL,ɪjiJ)))(354UT54UUU43ސސޏNnM,- ɬɫūūūŊkjkjJj***))**JJ -MMnڎoڎڐޏޏޏޏoގonnM--- ,,Mmm֎ڏޏޏޯޏޏގڏގގnmmmmLMLMLLlmmڎޏnڮޏޏގnڏގnގmmmmmڎnnnގގގޯގޯޯoޏmmm++ + + + +** +*+,+,,+,,++ +,MLmڎڏ44T433ޏM,ɫjJI)() 3T54UUU5U4on-- ɫūɋjkkkjjkjjjJJJI***)*JJ ,,--MNMNMNMMNM--- ,,Mmmnnnڎnnڎmnmm֎LmLmKmmKm֎mnnڎޏގގޏޏޏnڎmnnڍmnmmڍލގڎގޮޯnmM,+ + + + + ++ **KKKLKlLLM+,L,L,,Lmmnڮް33333L,ͪŊKII)  3444544U444544T5U5UUUU54nM- ŬūūŋjjjjjjjjJJJ)))*IJj , - - ,,MMMMNMMmmmMLmM+LLlLMMmnmnl֎ގڎڎnnڏގmmmmnmڎnmڍnڎގގގޏmLL + + + +,++*LKKmmLmLmLLmL,L,Lmmmڏޯ2m, ɪJ) 33444444334UUUUVvVvwwvvvvUU53nM- ɬɫŋŋjjjjjjkjjjJIJI)IIjjjūɫ --,LLMM,,L-,,,LLLLLllmLmnmmnmmڍnmmmmmmnmmMmnmnmڎޮޯޮގmL+ + +, + ++,+LLmlLmڍލmmmMmMLLlLmmnގޯޯޮm, ɋjJ) Ȩ34434TUVvWvwxxwwwvvV53ލM- ɫŋŋjjjjjijjJJJJJIjjjūūŪūŪŪū ,,,K,LMLmLLmLlLLlMmMLmMmMMMMMMMMmmڍnگޯޯގލmL,+ +*,+KLLmlLmmڍmmmnڍmlmLmLmlmڍڎگްޯގM-,ŋj** Ȩ4T45UUvvvwywvuU3ގMM- ūŋjjjjjiiiiiIJIijijjijiiiiiijjjiŪŪ ,,,,,-,,,+L,,LLLLMLMM,LLMLL,L,MmmڍڏގޏޮޏޮގލmL, +,L,LLMmmmnnmގmmmmlmLMlMmnގޏޯްގގގnML,ɫŋjJ ȨȨ3T4TuVuvvvwwwvvU4nL- ɫŪjjjiijjjjiijiijji +,+,+,,+ + +,,,,-,,,, , ,,,,LMnڎڏޮޮޯޯޯގގMLL, +++++LLLlMmmmmmmmmmmMMMMlMmMnڎގޯްގގލnMM,ɫj*) ȨȨɨȨȨȤȤ3455UUVvwwvwwwvuT3nnnM ŪŪŪŪi ,+ + + ,,Mnmڎڏގޯޯޏގގml,+, + LL+LLMmLMmmmmlmmmmMLLMLmmnڎގޯޯޯޯޯޏnnM-, ɋJ** ȨȨȤȨȤȨȨȨ444UVVvwvwwvvU53ޏnNM, ŪŪŪũ , , ,+ , ,, , ,,MmڎޮޏޯޯޏގmlL++ + ++,+,LLLmMmmmmmLMMLLLL,L,MLmmnڎޏގޮޏގގnmM ɫkJ* ȤȤȤȤȨȨȨȤ33444U5UUvvvwwwxwwxwwvvVU542ޏnmM, ɪŪŪũũũũũ ,, ,,,,+ + + , MMnڎޯޯގmmL+ + + + L+,,LLLLL,,,,,, + ,,,,M-MMNMM- ͬŋkjJ* + ȨȤȨȤǤȤȤǤȨȤȤȤȨUUUUUVUvvvVvvwwwvvUUU433ގޏnmML ŪŪŪŪũŪũũŪ ,,, MMLL,,, , ,+ ,, ɫŪŪŪŪŪ ,MM֎ڏޯގލmL + +,++ ɋjJ) ȨȤȤȤȤȤȤȨȤȨȨȤȨȨȨVvvwwwwWwxwxwwvwVwUUU4U534ގnmML,+ ũŪũɩũ + ,M,,,M,,+ ,, ++ Ū ,-nnڎޏޯގML+ + ɊjI) ɨɨȤȤȨȤȤȤȨȨȨȨȨȨȨǤȨwwwwwwwwwwvwvwwvvvUvUU4T43ޯގގޏmmM,, Ūũ , ,,MMMLM,,L,, ,+, ūūū,-nڎڏޮnm, ūūūūŪūūūūūūūūūūūŪŪŪɪjJ) ɨȤȤȤȨȤȨȨǤȨȤȨȨȬȨǨȬ謘wwwvwvvvvvVvUUuU4UTޯޯގގnڎmmLL,+ + + +ũ ,-L-MMM,M , , ŪjijjIjijjjū -nڎگގMK+ŪũŪŪjɫɊI)ȤȨȨȨȨǨȨȨȬȰ )wvwUvUUvUUUUUUU4T33ޯޮڎގnڎmmmMLL+ ŪŪ ++LMLM,,,++ ŪŪiiJiIJIiJJjJJjjjk -M֎ڎޯޏގmL ũũũhhjjjjjjjjjjjkjkjjjjjjjjjjɫj)ȨȤǤȨǨǨȨȰ  )))IJIwwuvUVVUU6uUU5UTT34ްޯޏގڎގގލڎڍmmMmMmLLL, +Ū ++ +,,MMM, , ŪjjiIJIJJIJ)JJ*)JJKkjj -MnڏޯގmM ũũňhhhiiiiiijjIjjjjjJjjjjJJJJJJIJJI i) ȨȨȨǨ) *IIiIiwvvuvUUVvUUvUuU4434ޏnmmmmlmmMLMLLML,, + +ũŪũũũ ,,,-,,, ŪjjJJJ)J*))* +* +) + +***JJj MnڏޯޯގmM+ ũŪʼnhihiIiiIiJIJJIIJJjIJJIIJI))))*IIɊI ȬȨȨȨ ))JJiiiiŊʼnũŊŹvvvvuvUUVvVUuUUT43nmLLML,L,+,,,,+, ŪũũɪɩũũŪŪŪŪŪŪ -, -, ɫūŋjjJJJ)*)) + + + + + + +**K -Mnڎگޏnm, ɪŪʼniihhihIIIIJIIIJJJII)*)I)))) ) *I ͊I ))IjjŪŊŪɪɪəwwwvvVuUuVUuvUUu5TT4nLLL,,,,+, ɪŪŪŪũŪũŪ ɪūjJJ*) +) + + + + +)Jj MnnڎޏޯޯޯޯޯޏގގڎL, ɪũʼnhiiiHII)JIIII)I)I*)) ) )i ͫj )IIIŊŊŊŪɩ͹wwwwVvVvuVVUvvVvvVUUU33ޏnLmL,+,+,+ ũɩŪɪũŪŊ ūŪjjKJJ* +* + + +*Jk,-MnnnڏގگގގޏލnM, +ɩiihIiIIHI))I))J)*))))  )I j ) I(ihŊŪ ҙwwwvvvvVUuVvvvVVuvuUU43nnMLLL,,,, ɪũɪũŪŪŊjūūjKJ*** + + + ɨʨʬ + + +j,-MMnnڎގގޏގnڍnmL + ũhiiiHIH(JII))I))**)) + I Ɋ* )(HIiŊŪū ҘxxwvvvvvvvUVvUvVvvvvvvVUT43nmMM,L,+, ɪɪɪŪŪŪŪŪjjjjjjjjɬɬŬŋKJK*+ +* + + +ɨʬʬʬ)*Kj MMmmnnڎގnnnmmM+ ɨihiH)III)))I)))))))))) )j Ŋ))(Iji ,, ,M,֘xwwwwwwwvVvVuUUvvuVVvvvvUU44ގmmMLL,LL,, ɩŪɪŪɪŪūɪŪjjkJjjkJjjŋŬŬŋJK+ + + + + + + +ʬɨʨ +*j -MMnnnmnmnmmLL ŪʼniiIIH))))) )I))*)*))) ɨȨȨ )I ͪI))Iiiūɫ ,,,,,,,LLLژxxwwwwwvVvVvUUUUvVVUuvvUUUUUTUnnMmLLM,,, ɪɪŪŪŪɪŪūŪɪŪŪŊjjkJjjJjJjjJjkkkŌkkkK* + + + + + ɨɨ +*Jkū ,-MMMnnnmmmmL,, ũũiiI))I))()) )))) * ȨȨȨ )ŊI))IiiŪ ,,,L,LLLLmMޘwwwwwvvwwwUvvvUUUUVv5vUUUUT45ގmNMM,L,L,+ ŪɪŪŪŪūūŪŊŪūūūūŪūūŊjjkjjJJJJJJJKJKJKkkkkkkkKK** + + + + + + +ɨ + + +*k ---NMnMmnMMLM,, ũʼnjiII)(())) ) )))) ) ɨȨȨȨȨȨȨ(II ()Iii ,L-LMMLmlnlmmژwvwwvwUwVUv4UUUUTUUUU5TT4ޏnMmMnLML,, ͪŪɫɪŊɫŋūūūūūūŋūūŊŊŊkkjJJJJJJJ*KJ**K*kJ+KkKkJkKJ*+* + + + + + + + + + +*KJj ---LMMLMM-,, ɪŪiji(H))) ( )))) ) ȬȨȨɨȨȨȨɨ)JJjɪ ,,,LlLlmmmMmmmlڸxwwwvwVvUUU4U54T54UU433nڏmmMMmLMM,, ɪŪūɪūɊūŋūŊŋjjjjjjkkJJKJ**J+**KKJKJKKKKKKK++ ++ + + + + + + + + + + *Jɬ ----,--,, ɫŪŊiII)) ) ) + ) ) ȨɨȤȤȨȨȨ ))IIjū ,,LMMmLmmmmލmmnޙwwvvUVUU44434443ގnڎmmmMMM,M-, ūɫɫɫūŊŋūūŊŋŊŊŊŋŋŋjjkkkjJkJKJJ**J****+JKKKKJKKKKK*** * + + + + + + + + + + +*Kj -, ɪŊiI)())) ))) ȨȨȨɨȨɨȨȨ  IIj ,LLMLmmmmmڍmގލmڙwwwvvuU5U444333ޏގގmmnMmLnM,L, ɫɪɫūŪūɫūŪŪɪŊūŊŊkjjkkjjjJjkJJ*+JJ**J*JJJKJKKKKKKKKkK+* + *+ ++* * + + + + + + + + +* +K ŊŊŪŊIH))   )))  ȨȬȨȨȤȨȤɨɨȨȨ )IJjŊ +,LMMmmmmmmmmmڎmژwwwvvVuUTUT443ޏގގmnnmmmMm,mM,+ ɪŪɫūūɫɫūɫūūŋūūŊŊŋjkkkkjkjJJKJ+J*JK***JKJ+JKkkkkKKKKKK+*+ +++++++++ ++ + + + + + + + + + + +JkɫɫūŪiiI(( ) )  ɨȤȨȨȨȨɨɨȤ )IIj ,LLMmlmmڍmmmmmmlڹwvvvvUU44433ޏގގލnmnnmnMnMMMM, ɪɫɫūɫūɫɫūɫūɫŪūŋŋŋjjjkjkJKJKKK*JKJ*JJ**KKKkKkkkkklkKkKKk*KK+*KK**K+* +*** + + + + + + + + + + + + +*JjŬɫɫŪŋjjII) ) ) ) )) ɬȨȨȨȨȨȤȤȨ ))Jjū ,,,LLMmmMmmmmMML֙wwvvwvVuUU5T443ޯގnnڎnnmLmnmnMmLL ɫɫūɫɫɫɫūŬŋūŋūŊjŊkŊkjjjjkkjJKKJ*KJ+**+KKJKkKkkkkkkkkkkKK+KK+KKKKJKK**+**+* + +* +* + + + + + + +JJŋūŌūŬɫūŬūūūūūūŊjijII( *) )*(  ȨȨȨȨȨȨȤȨȨ )JJ ,LLLLMLMML,L,+,ҙxxwvvwUUuUT5443ޏސގގގގގގoގnmnmnnnMM,+ ɫɫɫɫɪūɫŋūŪŋŋkjŋkkkkjKjKKJJJK*KJ*KKJkkkkkŋlkkkkkJkkkkKkKKKKJJ*JJJJ+*J**** + + + * + *JJjūŋŋŊŋŊjjiI))( ))) ) ) ) ɨȨȨȨɨȨȤ )*i ,+,,,L,, +ɘwvvVUUU5T43ްްޏޏގޮގގڏnnڎnnnnnnmL,+ ɫɫɬɬɫɋūŋŋŌkŊkkjkkKkJKJJKKJkJKkkkkŬūŌūŌkŋkkkkkkjjkKkjjJkKKK****) + ++ JJkjkjjjŋjjiiJI)))* )))))I )( )) ɨȨȨȤȨȨȨȨ ))JjjŪ ɩũŘwwvvVUT4T3ްޯްޯޏޏޏގގގގޏnގnڎmNM,,,, ͫɬ͋ūūɫŋŬɌŋŋŋŌkkjkKKJJJKKKKjkkkūŬūɫūŌŋūŌŬŬūŬŬūŌŋkkkjkkkkjKJJJ***)*J*jjjjjjjjjijjjjjiJII)))) )) ))))JH))) ))) ȬȨɨȨɨȨȤɨɨȨȨȨ *IjŪɪʼniwwvwvvvUU443ްޯޯޯްڏގޏޏގޏnnmmNM-M,-, ͫͬɬͬɬɫɬɫŬɬɬɋŋŋŋkkkkkjjkkkŋŬŬŬɬŬūŬŬŬŬɬɬɬɬūŋūūūūūūūŋŋūūūŋjkkjJJJ)*IIJjjJjIjIjJIjIII)HII*)) )*)*I**))I))))))) ɨȨȤɨɨȤȨ ))JjjŪūūɪūɫŪŊʼniiiiivvvvvuUUT4ޯޯޯޏޏގnnnmnnNM,,, -,------- ͫɬɬɬɫŌŋŋkkkkkŋūūūūɬūɬŬūūɫɫɫūūūūūjjjjjJIJI*IJJjJJIJiIIJIIJII*))*****JJJIIII)IIH)()()  ȨȨȨȨȨȨȨɨȨȨɨȨ )IijjjjjiiIIIIHIIIUUvuUVUT443ްޯޯޏޏޏޏގގގnnmnMM-,--MMNNMNN.M-,-- ͫŬɫŋŋŋūūūūūūɫɫūūŋŊjjiJJJjIIJiIIIIII)))))))*)*)**JJJJjJIJIIIIII)I)) ȨȨȨɨȨȤɨȨȨɨȨ  )I*I)JI)II)))(I)H)HI4TTUUUTU43ްްޯޏޏޯގޏޏޏnnmmMMMnNnnooގnNnNNM-M - - - -----,--- ɫ ŪŊŊŊijiiijiJIII(II))))I**)*JJJjjkjjjJIiJiIIJI)*)) ɨɨȨȨȨȨɨȨȨɬ  ) ( ) ( (()(33TUUuU44ޯޏގޏގnގnmmnnڎޏޏޯޯޏڏޏnnMM--,- --,-- -- -N-NNNnNNNMM- ɫ , , , ,-,,,-, ūŪŪŊijiiiIiIHII)I))I)I))JJjkkŊjiiIiiiII)) ɬȨȨɨɨȤɨȨȤɨȨȨ    ) )H(3TU54444ޯޯonڎގޏްޏޏnnmM--M------.N.NNnnnnގޏގޏnގNMM-, ,, ,-,-L-MMMMMMMML--, ŪŪŪŪʼniiiIIIII))I)I)JIJjŋŋŊjijjiiiJJ)* ɨɨȨȨɨȤȨȨɨȤȨȨɨȨɨȨ   ( (((()3455UTT3ްޯގޏޏޯްޏގonnNNMN-M--MNMNnnڎގޏޯޯގގnnMM,- , , --,,-M,MMMmnڍnmnnnMmMLL-,-, ŪŊiiiIhIII))IIJJjjūūūɫɫūŊjIJJIjjII)) ɬɨɬɨɨȤɨɨȨɬȨɨȨɬ       (344T4T4ްޏޏޏnnMMNNNnnNnnڏޏްްޏnnnMM-, , ,,L-LLmmڍڎގގގގޏގގڎڎnnmmMMMM,,, ŪŪŪŪŪʼniiiihIIIIIijūūūūɊJI*IIijjI**) + ɬɨȨȨȨɬ    (3444343ޯސޏޏގڎގMnnnnnnڎޏޯްޯޏގnnnnM, , ,-M,MLmڎގގގގگޯޯގގnڍmnLM-,,, ŪũũiihiIijūɬɫɬ ͫjJ*)*JJijjJJ) ) ɬɨɬȨɨɬȨȨɬ    34344ްޯޯޏޯޏonnnmnoޏގޯޯޏގnmmM,, LLLLlmmގގޮޏޮގގnڎmnmmMM,, ũũʼniIiIiūɬ ͫj** **JjjjiJ*) + ɬȨȨɬȨɬ )   3433ްްްސޯގގގޏގޏްގގގnmm,-+ ,L-LmLmڍގގޮޮގޏnގڍnnmmMLL + +ŪũhiiiŊJ)** *JJkjJ**) + +ɬɬȨȨȨ )    ްޯްޏޏގގޯޏޯޯޯގގގގnmML,, +,+,KLLlmڎލڎޏޯގޯޮގޏޏގގގmڍMmMLL+ ũũũũũũii ͫūjJ* + + *JjŋjJ***) + + ȨȨɬȨɬȨ ))  ޯޯޯޏްްޏޯޯގގmnMM+,++,,,,LMlLllڍލގnڮގގޯގްޮގގڍڎގmmLL,+ +ũũi,M ūūŋjjJ)** + **JkŋjJJ)*) + + + ɨɨȨȨ) )) ) ޯޯޯޯޯޯޮގnmmLLL,L,LLmmlllڍގnڏގڎގޯڎޯޯޯގގmڍmmmLL++ + +ũũũʼn MnLɫŪŪjJKK*J* + +*Kjūūjj*J*** + + + + ɬɬɬɬ )) )  ްޯސޯޮލmmMLlMMmmmmmލލmڎލگގގޯގڏޯޯގގގގڍmmML,, +ͪŪɩũũ,֎ޏM ɫŋjjJjjjKJK** +*JkɫūkKJ***)* + ɨȨȨɨ )) ) ) ( ްްްޯޯޯޯްޯޯޯގnmmmmލmmڍڎލlڎmڏڎގގڎޏގޮޯޯޯގގލގmڍmLL,++ ɪɪ nޯMŊJJjjkkkkJkK*+**KkūūūkkJ** )* + + + + ȨȨȨȬɨ ) ))  ގޏޯޯޯޯޏޯޯް23ޯޮލގލޮޮގގގލގڏޮޏޮޮގގޮޮލڮޯޯގގmnmL,++ + mڰn ΫɋkjJkkjkKJ+JKkŬɬūjKJJ*)* +*) + + ȨɬȨɨ )) +) ) 3mގޯޯޯޯޯޯ333333ްގޮލޮޮޯޮޯޮގގގގގގڎގޮޯގڮގޮލލmLL++ + +ɪɪ nn ΫŋkjjkŋūŋkkKKKKkɫŋJJJ* +* + + ɨɬɨȨ)) )  Mڍޯޮޯޯ3334333ްޯޯޮޏޮޮޭގލލڮޮޮގލގޮޮލލmmmLL++ + + m-ͫŋŋkūŋūŌŋŋkkkKKKkɫŋjjJJ) +*) + + + + ȨɬȨɨȬ *) )  mmڎޯގގޏޯ2333333333ޯޯޮޯޮޏޮޯގޯޮލگލލގލގmLLL, +ɩŪ,ҎMͫŋkŋŌūŬɬŋŬŋklLkKkɫɫŋkJJ**** + + + + ɨɬɬɨ )) mnگގޯޯްޮގޯޯޯ333TT3T432322ޯގގގޯޭގލގޮގޮޮޯޮޯޮލڎގmmM++++ɩũũ,֎ޯ ɫŋŋūŬɬɬɌŋkkkKkjlɫɋjjJJ* + + + + + +ɬɬȨɬ )  mگގޯޯ2433ST4T33332ޯڎޮޮޭޮڮޮޭޯޮޯްޯޮގގގmmmL+++ũũŪŪL֎- ͬɫŬŋŬɫŬɬɬɋŋkkKkkͫŋJJJ** + + + + + ɬɨɨȨ ) ) ގގޯގގޯ233343334342222ޮޯޮގޭގޮގގޮޮޮޭޮޮޯޮޮލގmڎގlLLL,+ +ũɪũ-֎ޯnͬɫŋŋŬŬɬɬɬɋkkkkKkkɋŋkjK*** + ) + + ɬɨȨȨȨ ) )  Ȭލގޯޮޯޯ233334STS4343232ޮގޮގޮޮގޯގޯޮޯޯޯގޏގmmlll+ + +ɩɩũũɩ ,mگ- ɌŬŌūɋūɬɬɋŌkkklKk ͫɋŊJJ** +* +* + + ɨɨɨɬ) )  ȬȬގލގޯޯޮޯޯ223333344S3332222222ްޮޯޯޯڮޮޮޮޮޮޮގގލލlllL*+ + +ɩũũũ nގM ɬɫūŋŬɬɬɬɋŋlklKKkkɌŋjj)J** + + + ɬȨɨȨɨ   Ȭ33nލގޯޯޮގޯ2333334SS3322222ޯޮޮގޮޮޮޮޮޮޮޮޮޭގގގލlmLll + + ũũũɩ L֎ޯn ɬɬɫŌŬɬɫɬɬɋklkkKK ͫɋŋKJJ* +* + + + ȨɨɨɨȨ ) ) Ȭ32Uގmގޯޯގގޮޮގޮ2333334333332223ޮޮޮޮލޮޯޮގޮލڍލmllL++* + +ɨŨŨɈ ,,ڎގn- ͬɌŬūɫŬɬɬŋŋkklKKKlɫɋŋJJ**** + + + ȨɨȨɨȨ   ȬȨ323343ލގޮގޏގގޯ3333333S333222ޮޮޯޮޮގގޮޮޮގލmڌmlKL+ +ɨɩũ ++ ,,mڏ- ɫɬūŬŬɫɬɬŋlklkkKkɫkjJ* + +* + + + ɬɨɨȨ 23343ލnގޯގގގޮ22333333S32ްޮޯޮޯޯޮޮޯގޮޮޮގގލލlmlLKK + ɨŨŨŨɩ ,LڎގM. ͬɌɋŋŬɬɫɬŋlkkkKKKkɬɫɋkJK* + * + + ɬɨɨȨɬ  Ȭ3T3nގޯގޮގޯގޮގޯ233333333ގޯޮޯޭގގޮތڎލmڍklKK* ++ͩũŨ + L-nn ͬɫŋŌŌŬŬɬūŋkkLKKKKkɫɋk+*** + + + + ɬɬȨɬ   343ގގޯގޮޏޯ2233333333333222ޯޮޯގޮޮގލލގލlmKLKK+ + +Ũũũɩ +, , ,Lmގޏ- ɬɋŋŌŬŌɌŋkkkKKKKkɫŋkj*** +* + + + ȨɨȨȨ  Ȩ333ލގޯގޮޮޯ333333332ޮޯޮޯޮގޮޮޮގގލލލmލlmLKK++ + Ũɩɩ ,,+-MnmN ɫŌŋkŋŋŌŋklkKKKKKkɬɪkkKJ* +* + + +ȨȨɨɨȨ  23ގގޯޯޯޮޮޮޮޮ22233333333ޯޮލގޭޮލލލڍmmllKK+ + + +ɨŨũ , , MLnNڎ ͬɌŋŋlŌlkLkKK+KKKɬɫŋkKJ** + + + + ɬȨȨȨȨ   Ȩ43ގގޯޮޮޯ3322233232223ޮގޮގގލލmmލlLkl+++ + +ɩ , L+,LNmmڰM ͬɌkkkkkklkLJKKLKKKͫūŋkKJ** + + + +ɨɨɨȨȨȨ 23ގގގޯ223223232ޯޮގޮޭލލڎލڍލmlLmK++) + ,+, MMnL֯n ͫɌkkkkkkkkKKJKKK+KkͬɫŋjKJ*** + + ȨȨɤɨ Ȭ3ގ23223332222ޮލގގޮޭލmڍllLlkLL+ + +ͨɩ L , ,MNMm, ɬljkkkkklkkKKKK*KKKlɬɋŋkK* +* + +* ȨȨȨ 謎2ޯ12332332ޯޮގޮޮލڍmmލmlllLKK*++ ɩɩɩ ,,,,,MMMMڰM ɬɋkkkkkkkKK*KK++*KKɬɋkkK** + + + + + + ȨɨȨȨ  謍ޮ3232222232ޮޯޮގގޭmڍmllLlLK+++  +ũŪ M+,+,nMLڎްn ͬɬkkjkkkllkKKKJ++K+KkŬɬɬɫɋkJ*J + + + ɬȨȨȨȬ mލگ34332233ޯޮގޭލލލmތlmLllKL+*+ + + +ɩʼnŊL,,,+Lm,MگMɬŋkkKkKkkkKKK+K++K+KŋūɬɫŋkkJ*** +* + + +ȨɨȤȨȨmڍޮ3TUT4322ޮ3ޮޮޮގލލލڍlllllK+K+ + + + + ͨʼn L,L LMMMڏn ͬɌŋkKKlkklkKJ++++++*KɫŬɫŋkJK** * + + +ȨȨȨȨȨɬlmڍޮޯ3TUuTT4333222ޮޮ2ޮޮގގޮލlڌmllkKKK+J + + Ŋʼn -,M,,Mm,nگ,ɫkkkkkkkkkKKKK*+K+KKkūŬɫŋŋkJ*** + + + + ɨɨȨȬmnڎގޯTTuuuTTS4322222323ޮޮޮޮޭޯޮލޮލގލڍlڍllL*l+K + ++ *+ + +ɉʼn,L,M+,n,L֏ޏmͭɋkKKkKkklkKK+*++K+KKlŬɫɋŋkJJ*** + + + + + + ȨɨɨȨȨɬȬmmmڍޮ2TUTUutUUU32333233232333ޮތޮޭތޮޮޮޮޮޮޮޮޮޮޯޮޮޮޮލލޮތڎތlmlڌKKK**+* + + + + + +ɩʼniL,-,,MM,nڏn ͬɌŋkKKkKkkKKK++*++++JkŋūŋjkK + + + + + + +ȬȨȨȨɬɬmލڮގޮ3TTuUUUvT3232333333322233333332ޮޮޮގޮޭޮڭޏލڮޯޮޮޮގޭޮގލލlڍlmlKL**+ * L + +ͩɊii ,,L,LNLMnގ-ͬɌŋkKKkkkkKKJ+++*++*KkɫɌŋkJJJ + + +* + ɬȨȨȨȬȨmލގޮޯ44utvvvT22333343443S32322323S3332ޮޭތڮޮޮޯlޮޮmڮލڭޮޮޮޮޮޮޯޯޮޮލޮލގlڍllڌkKKJK* + + ++, ++ ͉ijŪ -+L,,M-,nޏN ɬɬkjkKKKkKKKK+*+++*++KūŋŋŋkJJ** + + + + + ȨɬȨɬȨގޯޮޯ44TuvvuT222333333ST4343423223333SS111ޭލޮލގڍގޯڮޭލڮގލލlڭލލލlڮޮތڭލޮޮޮޮގޮޮޮލޮލޮލlڌތތލllKlK*+** + + + +ɪiij,,,-+MM,Mnގ-ͬɫŌkkkKkkKkkKK+*++++++kkŬūŋjjK** + + + + + + ɨȨȨȨȨȨɨޯ3STUvuU32232333T3343TT43322222233333222ޮލޮޮޭޮޭލޭގެmڍڎތލlڌڍڍލڍڍޭލލތڎލڮޮޮޯޮޮގގޭލލލލތlmkkkLK+* +*) +ɩŧ M +++ɪihi ,,,,,NMLnގMɬɋkkkKkKkKkK+++ +* ++*klūŋkjKJ** + + + + + + +ȨɬȨȨȨȨȨȨޯ23TuuUS22233334T34TT3S43332222233333222ޮލޭލޭލލލڭެެތmڍlmڌڍmڌڌލލތڌڮލڍގޭڮޮޯޮޮޮޮޭޭލޮލލmڍmތmkKlKK +*+ + +ɨɨŨũ +,+ + ͊ɉij,,,L,MM+Mڏm-ͫɬŌkkKKkkKLKKK+++* +++JkŋjjJK* + + +* + + + ȨȨɨȨȨȨȨ3TUuT322333333S3TSTTTSTS33322233323S3S32ޮގޮڍެގތlڌލmڍލl֍ll֍ތmllڌތތތލލލގޭޮޮޮޮޯޮޮލލڮލލތލލތlmklKKL*++ +* ɨŨŨ +L , ͪɉii L,,MLMMLnގMͬɌklkKkKkKKKK+* +*+ +++KŋkjK**+ +* + + + + + ɬɨɨȤȨȨ3S4TT4222333343S3T4TTT44S433232223332222ޭލޭޮޭklڍڌlkkڍތkLڌlڋތlތތލލގޭލގޭޮޮޯޯޮޯޯގޮޮގޮގޮޮލލތލlmތmތllLlk+K* ++ɨŧňL ,+ ͪɉhjj,M,-,,M,MڏM-ͬɬɫŋKkKkkKkKK+++ ++* ++KkŋŋjjJ* + + + + + + + +ɨɨȨȨȨ343S43222323333343TTST4TSTT333323333333322ޮޭޭލތmތlڌllkmڋkklڌkkllmڌllڍގލڍލލގލލލގޮޭޭޮޮޮޮޯޮޮޮލޮޮގލڮލލތގmlLllLKKK+K * + +ͨɧŨňũ+, + +ͪɊiii -+,M-MLLnn-ͬͬɌŌkKKKKkKKK+++ +* ++++KkkŋkKJ** + + + + + + + + +ȬȤȨȨȨ2S333233332333434STST3T4T4SS33232223323222ގލލޭތڍl֋lk֌jkڌmlkkmllKlތklڌڍލލlލޭڍތڍލގލލލޯގޯޮޮލޭޮޮڮގލލގލލڎލލlڌތlmkllKkKKK*+ + +) ɨŨŨňh m+ ,, ɪɉhij,,,M,L-,MnM ɫɬɫɋkkKkkKKKkK+ * + + ++ +++kkkjJJ* + + + + + + + +ɬɬɬȨȨȨ323323333333TS44S4TTTTT33S32232332332ޮޭmڮlڍllk֌lڌKlKkڍlllkڍklkklތlmތmڍlmmڍތލގލڎޭތޮڍގޭލޮޭޭmڎލލڍڍލލmlތlllllklKl+KK+* ɨňL- ++ ͪɊhii ,,+MLM,,nn,ͬɫɌŋkkKKKKKKKK+* + +* + +KKkkkkJ+* + + + + + + + ȨȨȨ2232232232323334333TTT4T4T4TT333332233222ޮލڎތދkڌlڌKlklmKlllڍkLkllKlKڌllڌlލލތލlތlڌmmڍލލލލڍލތލލލލތލތڍލlڍތmlmlkmlklLkKK+J) + + +ɨŨŨhhhh m, ,, ɪɉiij L,,M,L,Mnn ɬɬɬŋlkkKKkKKKKK + + +*KklkK** + + + + + + +ɬȬȨ22133323333333S3T444TS3TTTT43232322ޭޮތmڌތkڌmkllllKklllKklLkllllllڍllllލlllڌڎlڍllmތmllލލލllڍlڍލlތlmlllllKLKKK*+* +* +ɨŨŨŇghhhhLM ,, ,Ɋiij ,,,ML-,,mn,ɫɬɌŋkKKKKKkKK+++ + + +*++KKkkKK* + + + + + + + +Ȩ2332222333323334334SSTS44TTTSSS3332222ޮޮޮލތޭތލlklKllkKkkLKlkKLlllLKllllkllllllڌmkڌތތڍތތތތތlmlތތlލlތlllllKkLLKKK+K+* + +) ɨŨŧŇhhghghh m +,, ͩɊHjj ,,+--,,,LnM ͬɬɌŌkkKKKkKKKK++ + + ++ + +*+KKKk+*** + + + + + + + +32333333333443TTTT44T43332222ޯލލތllKklkkLlkKLkkKlkKKKlKLLLllKlkklllllڍkLllllڌklkތތlllkllklKlLKLKKK+KK+*** + ɨŨgghgghggh,M ,L ++ͪɪiij,,,+ML,,+Nn,ͬɬɫɬɋŋkkJKKKKJK+*+ + + + + + +**KKJ+J + + + + + + + + ɬ \ No newline at end of file diff --git a/_pico/themes/raspberry/gridcell.bin b/_pico/themes/raspberry/gridcell.bin new file mode 100644 index 0000000..3d4f900 Binary files /dev/null and b/_pico/themes/raspberry/gridcell.bin differ diff --git a/_pico/themes/raspberry/gridcellPltt.bin b/_pico/themes/raspberry/gridcellPltt.bin new file mode 100644 index 0000000..1a2c743 Binary files /dev/null and b/_pico/themes/raspberry/gridcellPltt.bin differ diff --git a/_pico/themes/raspberry/gridcellSelected.bin b/_pico/themes/raspberry/gridcellSelected.bin new file mode 100644 index 0000000..053530c Binary files /dev/null and b/_pico/themes/raspberry/gridcellSelected.bin differ diff --git a/_pico/themes/raspberry/gridcellSelectedPltt.bin b/_pico/themes/raspberry/gridcellSelectedPltt.bin new file mode 100644 index 0000000..77f1130 Binary files /dev/null and b/_pico/themes/raspberry/gridcellSelectedPltt.bin differ diff --git a/_pico/themes/raspberry/scrim.bin b/_pico/themes/raspberry/scrim.bin new file mode 100644 index 0000000..29b3971 Binary files /dev/null and b/_pico/themes/raspberry/scrim.bin differ diff --git a/_pico/themes/raspberry/scrimPltt.bin b/_pico/themes/raspberry/scrimPltt.bin new file mode 100644 index 0000000..a6ee191 Binary files /dev/null and b/_pico/themes/raspberry/scrimPltt.bin differ diff --git a/_pico/themes/raspberry/theme.json b/_pico/themes/raspberry/theme.json new file mode 100644 index 0000000..70c8774 --- /dev/null +++ b/_pico/themes/raspberry/theme.json @@ -0,0 +1,12 @@ +{ + "type": "custom", + "name": "Raspberry", + "description": "Theme based on raspberries.", + "author": "Gericom", + "primaryColor": { + "r": 138, + "g": 217, + "b": 255 + }, + "darkTheme": false +} \ No newline at end of file diff --git a/_pico/themes/raspberry/topbg.bin b/_pico/themes/raspberry/topbg.bin new file mode 100644 index 0000000..c6da440 --- /dev/null +++ b/_pico/themes/raspberry/topbg.bin @@ -0,0 +1,20 @@ +ĜĠĜĠĠĜĜĠĜŠĠŠŠŠŠ&G'HHHHHHH(G''&''hIjjjijiiH'&HijijiHh'&&'&&'''ĠĜĠĜĠĜĠŠ&&&'&&&&&&&''&''&''&'&&&&&&&&'&''ĜĜĠĜĠĠĠĜĜĠŠĜĜĠŠĠĠŠ&'(HHHHIHHG(''&&'GHijjjiH'&HIkjhH&''&&&&'&&'&ĜŜĜ&&&&&'&'&&&&&&&&''&''''&''&&&&'&&&&&'''&&''''&ĠĠĜĠĠĠŠŠĠ''HHHHHHHHHHG'&'GGIhjjjHH&'hikkjh'&'''''''''&&&&&'&'&&'&&&'&''&&&&&'&&'G'&'''&'&'''&'&'&'&'&''&&'''G'ĜĠŠĠĠŠ'(HHHHHHIHHHH''&''GHIijiihG'&&Fii'&&&'''''&&&&'&&&&'&'&&&&&&'&&'''&'&''&&&&'FGG&''&&'''''G''''GG'G&G''&''&'&&&'G&''&'G''''HGGG&ĠĠĠŠ&(hHHhIHHHIHHH&&&''(GHhiiiiiii('''&'hjF&&&&&&&&'&&&''&''&''&''&&&&&&&&&'&'''&'&&&&&&''G'&''F&'G&''GG'HGGGGGGG''''&''F&'G'(G'G'GGGGGHHHHGŠ&'GHhIIiiIihiiGG&'''HHIIHIiiiiihHHG'G'GHh&&&&&&&&&&&''&'F'''GG'GG''&'&&&'&&&%&%&&&'&&''&'&&''&'&''&'''GH(GG''GF'G'GG(GGHGGGGGGG'GGGF'''&GGGG'GGGGHGHHhHHH&%&&&&&'GHiiiiiiiiiIhG''''H(HiIiiiiiiHHIHGHG'Gg̹G&&'G'&&&&&'G&'G'G'GGGG'GG'GG'''&'&&&&&&&&'&&'&''&&'''G''FG&G''GGGGGGHGGGG'GGGG'GGHGHHGHHGGGG'GGG&GGGGHGHGGGGGhHhhihh&%&&&&&&&&&&&'&&&GGhhiiiiiihHGGGHHIIIjiijjjjjihiHGHHG'hg&G'G'GFGGG'GGGGGHGhhGGGGGG'GG&'F'&&&&&&&&&&'G&&G'G'G'GG'GGGFGGGGGGGGGhghHHGGHGGGGGGHGHGGHhHgGHGGG'GGGGGGHHGHhGHHHgGhiihiH'&&&&&&&&&'&&'&&&&&'&&GGhiiijiih'GHHHIIIjjjiiijihHHGHG'HhhhG&FhFFGGGHGGHhGgHhgGHHHhGGGHGGG''&'&&'&&&&F&%&&&&&&'G''GGGGGGGGGGGGG'GHGGGGHhhHhghHGHHHghGhghGhggHhGGGGhGGGGFGGhHhHhhghhhhihHG&&'&&'&&&G&&&'&FG'F''F'GGHhihHHHHIIJijjjjjjiiIIiihGhGGGGFFGGGGGgGgHHghhHhhhhhhhhhHhgHHGGGGGG'GGFG&&&&&&&&&'GG'GGGGGGgGHhGhGGgGgHgghghhhhHhghhhhhhghhghhhhhGhGhGHGhgGGGGhghhhhhhhhhhHGG'%&&&&&&&&&&&&&&&&''F'&G''GGGGG&G'GF'FGGhhhhHhIIIJjJkJkkjjjJjjiiihhhhhhghggGhghHhhhhhhhhhhihhhhhhhhghHhHGGGGGGGG&''F&&&&F'GGGGGGgHhGhhhhhhHhHhhgHhhHhhhhhhhhhhhhhhhhhhhhhhhhhghhhgGHhGhHhhhhiii%%&&&&&&&&&'&&&&&&'&&&&&&F'&'FGGGGGGGGGGGGGGGGGGHghihIiiJJjKjkjkkJkkjjjijiiihhhhhhgh˽˹˽˹hghhhhhhhiiiiihhhgghGhGGGhGgGG&&G&G''&GGgghHghhhhhhghhhhghhhghhhihhhhihihhhhhhHhhhhhhghghhghhh&&&&%%&&&&&&&&'&'&&&&'&'&&&'F&'&&G'GGFG'GGGGHGGHGgGHGGgGGgghiiIjjjjjkjkkkKjkjijiiihhg˽˹˽˽˽˽ghhhhhhhghgHGhGgGGGGGGGGGGHghhghhhhhhhhhhhhhhhhhhhhghhhhghhhhh&&%&&&&&&&&&&&&&&'&'&G&&''&'G'&&''&G&&GF'G'GGGGGGHgGgGGhgGgghhhhjjjjkkkkkjhʽ˽ʽhhihhhhhhGhHGgGGGgGHgHghhihhiihhhhh&&&&&&&&&&&&&&&&&&&&&&&%&&&&F&F'FGF'GGGGGGGGGFG'FGGGGGGGGgHGghghGhghh˽˽˽ʽʽʽʽʽʽʽʽjhhhhhhhghghhgHhghghh˽&&F&&F&&&&&&&&&&&&&&&&&F&&&'G''GGGGGFGGGGGGGGGGGGGGGGhGhGhghghhghh˽˽̽˽ʽʽʽ˽˽˽˽˽ ˽ʽ˽˽ʽɽʽʽʽʽʽʽʽʽʽʽʽʽʽɽhhhhhhhhhhʽGFGFF''F&FF'&&F'&&FF'&&F'FGFGGGGGGGGGGgGgHgGggGgHgggghGhhhghhhʽʽ˽./ͽ˽ ........ ˽ʽʽʽ˽ʽʽʽʽʽ˽ʽ˽˽ʽʽ˽˽˽ʽʽʽighhhʽʽʽʽʽ ʽG''GG'FGG&GFG&FFGGFGF'GFGG'GFGGgGggGhghhHghhghghggghgghhhhhhh/QQqQQ0нϽν./OOPPPPPO0//0/0////˽˽˽˽̽̽˽ʽʽɽɽʽʽʽʽʽʽʽʽʽʽʽɽʽʽʽʽʽʽʽʽʽʽʽʽʽɽʽ - -. ɽɽʽGGFGFFGGGGFGGFGGGF'GGhGGGgGgHGGgHhhhhhghghghhghhhhhhh /QQrrsQ1нpPOO./0qqQqQQ0000000000/0/˽˽ ʽʽ ʽʽʽʽʽʽʽʽɽʽʽʽɽʽ .- - .-ʽɽʽɽʽʽɽɽʽɽʽ GGgGGGGGFgGGGgGFFGGGgGgghghghgghhhhhhghhh ./PQQRΓғғsRʱQ0/00Q1qrrrrQ1 -..M...-, NOOO. ʽɽʽ N.N..N-Nʽʽɽʽɽɽ gGgHgggggFGGgGgGgGgGhghhggghhhhhh .OQRsRrsғҔ֓sʑQP12rsrRQRRRR1qQqq, ʽ .-..N..N..-NoOoOoN ʽʽʽ ON.NNN.N--gghgggGhgghGGGgggGgggghhhh .OQrsssRRsғ֓3rQq1SΔ֔֔֓sR1rrrR1ŰqQ11Qp ,.NONONONONOnpppppOpN ONoNNNNo-,FGhhgghgggggghhgghghh NOqss֓t֓sRRSs212sҴڴ֔֔֓s22rrғsrrqrrrP0/0ő1Ѩ1r ,--OOOoOOnOOOOpҐҐpҐpppo--,-- ooOoonOo, hhhghghghhg .Ors֓֔֔֔ڔtsœ32sҕڔִڔ֓S2SSrsrRrrrqQQ0Qғֳ֓r1ɲr򨒠QQ01q2q , --,--Ooooooopooooΐ֑֐֑֑֐Ґ֐p--MM--, + + MΐooooҐN Qνhhhňhh-OQr֓֔ڔִ֕֔֔T3ɴ33Tt֔֔tS33S3rSSR222ŒqγڳRS򨑜Q211RqR0- - - , + ,,-,--M-noҏopҐҐp֏Ґ֐oΏpڑ֑֑֑֐ґ֐NMNMMM-,,,,,,,, , , , , , ,, ,, , , , ,oҐ֐ppo, ʴ0ƫũ .1RssҔڔ֕ڵڔ֔T4ɵŔ4TTTtT334SST3ųsrRQrɔړRɳS󨓠Q2R2RrR21/.N.M.----M--MMMNNNnҐp֐֐֐Ґ֐Ґ֐֑֐p֐ֱڱڑֱֱֱNMnNnnnMM--,M-no,-M,M-M--M---M,----,---,,-, ,,, , , ,,,,,, -nN,, , , ++ ,ʳګh /1SST֔u֕uuU5ɵuUtTtT444T4ΓT3333r3tڳڴڴڴڴޓSsŴœsRRrrr򬓽212QPNONNNNNNNNMNnnnNnҐ֐֑֐ְֱ֑֑֐ְ֐֐֐֐ڳޱڐnNnnnnnNMMMMMҏґooMMNNNMMMMMMMMMM-M-MML--M----,,,,,,,,,,,-,,,,,,+,, ,,, ,,,, , , ,,,,,,,,,,,,G 0455555ɵu5UUT44T4T4TssRs֔ڴ޴ړRRS3s3RsŔ3Ҩ33RRRrqOoOOnOnnonno֎noְְֱ֑ڱڱڱڱڱڱֱڱڰ֑֐ڏ֏֏֏noo֎o֏oonnNnMmnҐڐnnnNmNnNMnMnMNMMNMMNMMMMMM-MMLM---,,MMM,-,-,---,,,,,, ,,,,,,,,,,,,+,,,,,,,,,,,Ωũ-/2Ŷv65vu54ɔ44ts3SsS2ɓs3󬳤ӬTɵŔssSt5tsr֓֒֓ڒڑ֑֑ڐڱڑڑڱڰڏ֏o֏֑ڰڱڲڱڱڲڱڱڱڱڱڰڏ֏ڐ֏֐ڏ֏o֏ڏ֏֏onnno֎nnnҏo֏nonn֎nNnNnnNnnnNnMNnmMNMMNMMMMMMMMMMMMMMLMMMLM,,,,,-,L-L-,---,M,L,LLMMM,MLMũ P2ŶŶŶŶŷŷŶvVvv5ɵŴu4ԬԬ343TsT4ӤssststŵŔŴŴ5ҕڵ޴޴޲ڲڒ޲޲ڲ޲ޱڑگڏڰڱ޲ڲސڏگڏ֐ڏ֏ڐڰڰڐڐڏڏoo֏no֏ڏ֏֏ڐڏڏ֏֏ڏo֏֎o֎n֎nnnnnnonNnnnnnnmMMNMNMMMMMmMMMMMMMMMMLMMMMMMLMMMLL-MMMMMLMMLmMMM O176ŶŶŶŷŷW666w6ɵuu͕UԨԨԨԬԨTS34334SԬTttuTҔڔڕttږڵޱޱްڰڰސڰڰڰޯڰڐڰڰڐڏڏ֏ڏڏڏڐڰڰްڏڐڐڏگڐڏڐڏڏڏ֏ڏn֏ڏ֏ڎon֏nڎnnnnNmnnmNnNnmnMnnMmnMMMMMLMMMMMMMMMMMMMMMMMMMmmmnnnmN + P3WwW7ŷŗwŖw6vWɕuUuɕuUլtttu44STԨ5UT788޵޴޵޴ްްްڰޯްްڰްڰްޏگڐگڐڰްްڰޱްްڰްްްްްްڐگڰگڏڏڏڏ֏ڏ֏ڏn֏n֏nn֎nnnnnnnmnmnnnnnmNmmmmMmmMMmmmMmmmmMnmmmnMnnnnmnn֎n + + + + Os6W֘ژڸx8ɗwWwŘw687vuɖ766Ε5ըvuUT4444433tըsU֔8YX8UV5Uv޴ޱްޱްޱޱްްڰްްްޱޱޱްޱްްްگޯگڐڏڏڏڏگڏڏ֏ڏڏڎڎڎnnnnoonnnڎnnnnnnnmnmnmmnmnnmmnnnmnnnn֎oڎ֏n֏ڏڏڏn + + , + + + + MqtVɶXXɷxWWwww7Ͷv6җw7W֗ژwVV5554887sssu7ɶŖWuڵޱްްްްޱޱްްޯްްޯڰޯڏڏگޏڏڏڏڏnonoڎ֎֎ڎoڎoڏnnڎnڎn֎nnnmnnnn֎nnnnnnnnڍ֎n֏ڎڏޏگڏڏڏڏڏ + + +,,+,,,+,, o֔uW͗Ŷx9y͸wxXXwXxvɸ9җwWζ6֬֬VV654tu443X8ޔut5Uږ6W76vV֖޵ްޯްޯڰްޯޯޏگޏڏڏޏڏޯڏڏڏڏڏڎڎڏnڎڎڏnn֎nnnڎnnnnڎڎڎnڎnڏڎڎڎڎnڏޏޏگްޯڰްްޯް + ++,,+,,,,,,,,,, ,, oruw8wŘ:::YɘxxXXX8x͸vW:ɗV6լըTU5453T54tYy8TTUtuuUuuVڶ6u֤֨֨֨֨Vwڵްްްްޯްޯگޯޏگڏگޏޯގڏڏڎڏڏڎڎoڏڎڎڎnڎڎڎoڎڎڎnڎڏޏڎڎڎڎޏڏڎڏޯޯްްްްޱޱްޱޱ + + + ,,,-L,-L,,L+,+,+L,,+, + , + Nqڕwx8ҘWɹ:Z9ҸxxxXX8XŸɷwxvvwWW6v6WwږږޖvUUԨt4Utv5544X7tT͵U55uvږ66vVVvvWږްްޯްްްޯޏޏڏڏڏڏޏگޏڏގڏڎڎڏڏڎڏޏޏڎڎڏڏڎڏގڎڏޏڏޏڏޏڏޯޯްްްްްޱ ,,LLLlMLLMMm3LL,L,,,,, ,, ,,+ .qtޗx8xWəZ:yxxXXX8ŸwxҷW76Ŗ9:9:99:85UըV555U4uڳŔ6uޕV5ԬWWvV6VvVWްްޯްޯްޯޯޯޏޏޯޏޏޏޏޯޏޏޏޏޏޯޯޏޏޏޯޏޯޏޏޯޏޯޯް ,+,,,lMMMMMMlMMگwv3mLML-,L,+ ,, , ,osޗwxҸXXYXֹyxX9ٰذw8x8wvZ{{99:9Vu5uuWv5uڶҕ5լԨu7Η77767Vw׬W͗ް5Vްްޯޯްޯޏޯޯޯޯޏޏޏޯޏޏޏޯޏޯޯޯޯޯޯޯް,+ ,LLmMMmMmLmnmmwLMMLL,,L,,,,,+,Mqޕww8xXYxyڙyљx98ٰyy8w͗W76:{{{9Wڗޘxv6Vڗ޸޶55vޕޖvVհԬVͷ7776wרw73xx7ްްޯޯޯޯޯޯޯޏޯޯޯޯޯޏޯްް++,,LlMMMmmmmMmnmMLmڎڏگnlMMMLL,LL,L,,, oޓwwYљYXYy͙yx8Yx88XW7V79{z9xV֗޸w޸w6֬6Vwxڹ֖VWޗޖޗv6͕5utuլW7פW777Wwx343443mڰްްްޯްޯްްޯޯްޯް,,+LMMmmMmmmmnmmmmmmmmnMmMLML,,L,,,,L+,puޗX9͙X9YyyYxyřřXYججYX8װ֬67:Y9WwژWVwVXy77V76V޵u5͵ŕuuTTU6wWרV8777w7η33434xްްޯޯޯް,K-LmmMmmmmmmnmnmnmmnmmmmMLLMML,L,M,,,MqޖwYY8YYYx89YřXYٰجXYyX֬7:xxxwޘwV6WvvWwwwv֬vҙW765v7xyYv5U4tT4TT77רV6766xڴ343443yްްޯޯްޯޯް޶޷޶,,LmmMmmmmnmnnnnmmnmmmnmnMMmLLM-L,LL,,nrޖxX͸y99XXYXXYyجX8ٰYY8հθxxWXW6wװVV6666VvVvVWyXXXU֕UtT43U7׬W67676VWΗ3333333yްްޯް޷,LMmMmmmmnmnnnnmnmmnnMnnmmMMMLMMMML,++nړޗxXѸY99YYY8Yx9جx88yYŗޘxw67ҷw7wV6666566VU6VVרV7֖vɵVڕ֕TհԬxְլTS4v666666X3334433ޯްް޷޷޸޷,LmmmmnmnnmmmmmnnnmnmnmnnmLMMML,ML-,,,oڔޗx9͹y89889Y8X7xY86Wޗޘ͘W7w7vVVUV6655655vVu֬װְլWӨְըtS4666833334333333ްޗ޷ޗ޷,LmMmmmmmnnnnmnnnmnnnnnmmmmMLMMM,LL+,LoڕޗxYy89جبx87٬X9Θxޘޘޙ͘7WW7V֨vvvUVU554vuդuUuuTTwsլըT3u֨6W׬vV֬wx4434433޸޷ޘ޷޸LLMmnmnmnnmnmnڍnnmnmmnmnmMmMMLLM-M,,+Lpޖx9ə9جججwX78x8W7ֹwxڹɷ7wWvW6׬vvuU565555tvuUUu655U5T3STըըS44vVwWŷY֙343433޷޷ޗLMmmmmmmmnnnmnnnnnmnmnmmnmMLMmMLLM,,+M֒ޕޘy9ə9٬جججwX787xxxޘޘwڷŸwV7wwv֬֬vVvu55544UuU55uV5U5T43R7Β34tըt334VW8y8xX8Y֘3333333337޸ޗޘ޷,LmmmmmmnmnmnmmmmmmnNmnMnMMMmLMMMLL+,ҐޓޗyYəY٬٬wXW7777͘޸xxW7͗ŘWvW͗ŖV֬vuT45TTU555TuըT33RT33TԠS3uuɖ8y޷޸Xy433433ޕuږ޷޸LMmmnmnmnmnnnnnnnnnnmmnڍmmMMMLMMLM,,nڒ޶y9ͺy9٬wWW77͸X8wVјw6ɖŗV7֬vU4UTUVVVԨr323334s43UUvT5UwwޗwxW33433333t5VW֖ڶޕޘMLmmnmmmnnmnmnmnnnmnnnmnmMMMMMmMML,Mڱy9y8www777ιxXWVwڷw8ژ8ɷvw7֬װ֤֨45455tTStXWW22334T3TլutU5445tVvww7W3343443uuU5V5V55v޵޷MMLmnmnmnnnmnnnnnnmnmmnnmMMMMMMMMLM֐x9͙y8٬wWW776wŘy8XvYZw7xX7͗W66666դu4TUӤⳠ323TլլԬԬ5tVwwW7Ww3343344433uUUUvV͵tt6ҖLLmmnnnnnmmnmnnnmmnmnnmnMMMLMMnLML֏޲Yəy9xwWWV76řޘҷWV8yWWX77wXwVvvVv5֤U43uը4Ԩ222TԨլԬӬ46Vvwwwږ334433vuuT֕uUs3Ttv޸ޘޘLMMlmmnnmnmnnnnmnmmmnmnmMMMmMMMLLoޱzZҹŹəY8جxxwWW766Wxww9YX78X7Xɶv6Vը֬ըU4tvvդvִr23ԨԬssssUVvږv޶333443333UtږޕڵT͔2StVַޗޘޘ޷ޘLLLMmnmnnmnnmmmmnnnnnmmmMLmMMlMMnڱz:ɹřX99بxxxxxwWW77xޙWWWx8XxwWڗW׬ר֬7U6vu547v֗wVΔӬR2stttssRRSsrsrSRtU֕޵44444444444ޔsړ޴ޓޓ1ɒrVַޘޘޘ,LLLMMmnnnmnnnnnmnnڍmmMMMMMmLLLM֐޲zҹřYYبxXWwww766XژWW777ř8ַŷ9vvvU6vVvV5v8V77W777ִѨ2rӬUutTTsSSRRRRSrRRSRSsԬ4Vu444444444ޒޑޱްޑ1ͲU֗޷ޗxwxwwww,LLLLMmmmnmnnmnmmnnnmmmMLmmMmLLoڲ:yyX88XxwwW676֬8ҙw76vVvwWvvtU565tUVҷޕXx567X4Q1ӬTuŖɖuӰR3RSRS2222R2RSRsr45֔444444444443ޑQ5֗޷ޘޘwwWwWW,L-LMMMMmmnnnnmnnmnmnmmMMmLMmML֐޳:yy898بxxwW6ι7wwŗŷŗv666UU66V5V44tv6җvTyY4Ҕtuŵ7WڴҬӬ3ŕɶvӬR2322222212222RS4Ҕ33444444433TT3qRUw޷ޗޗޘxwWXX7W8,,LLLLMLLMMmMmnMmmMmnڏޯޏގmMmMLNڱޘyښ޺:љyY98بxwxw76vyX֗Vvͷͷɘw65655T4WWuzYSSStӬ55t78SҬ3UuTӨ222222222122RSsu5Ҕ34444434443UUڗޘww8787,+,,MMLLMLMlMMlmmMmڏޯްmmLLڏ޳ޗZZzںޚޚZҚřXYبwxwwV66VXWwX8888җ55vv3St3ԬsssS484rRQqrӬv42212122rR2s5֔4444444443VvwwxW8ҎގMLLMM,MMMMMMLMMLmְޏޯޏޯޯlMMְ޸y:ZZZ:əyY9بwxxxww7767ŗYXYztVVuT654TŖUTΖ޶4SsSsssSs4Ҩ111QrrԬ5Ԭ2211122ssTڴ434444444438Vw6wX7͎nڎL,,LMMMLMMMLLMMڏޯޯޯޯޯޯmLmڰx9Źɺ͹yX9XWWWWW777Vx87֗8yzz:XVT55t4TTɘ԰tTSrStsTuQ01P1QrӬԬӨQR0Ѳ23RsTѕ444445443vwwX75uڵnnڎLL,-L-MLLLMMMML֯ޯޏޯޯޯޯޏޏmLnڑxҙYyźɹ͹Źyy8جبW6666wYvVֵ7yYڷɗWwxUuvs44wږwڕ԰sSSRRRRRR2RssӬu0011RRsӬӨ1012SssԬt5֔4444444444Wvw77vxzzznmL,LL,L-M-MLMLMMگޯޯޯޯޏޯޏڍL֎ޱYy9XYyyřŹŹřřy98جxVz6w8vڗ6ҕyڗ7x8uT͸VִհSsSsSsr2Ss2R5SSs011RRӬ󬱠Q222Rsu4444444548XX޸wW7V7Z9nnL,,-,M-LMMM-MLLL֏ގގޏޏڎޮޏޏmM֏޲xYy999YYyyyxYX8جy8667Vv9y7wڷW7Ҙx6ҷɶŕ7Ҷs3͸wӰԬtsr2RRr2111R22SsӬQ1qӨ4ҨqQ22222RsVڶ4445455444XXxx7޸ޘxXV֗8X9,+L,,,,LL,LLMLMMMMnگގޮޏގگڏLlڏ޳xYyY8899YyzxyyY89Wu67u͸ޙYޗ8X֬֬v6VvŖu34t԰TRS22RR2R1111QrsSr5q0pӰTѨQ123St5Ҷڲ444455455544Xyyޘwژޘޘޘ޸޸ޘޘޘޘWWXyz8XL,,,,,,L,L,LLLMLMLL֏ޯޏڏގڰޏklmڰޘy9řY9Y8YXyYyYX98רجvW6U9U5Tyv9UUհŗ3tuT5tRsrrQQ121Q100qRSrӬ40QrPrQrqӬ4uQ233Sw55455545444T87tu޷xxxwxxxޘxxxޘywXwXzX8޹ޙ޹,,L,-,-,-,,M,,L-MMLLmڎގގތލޯZ9ɺřyY9YYX9Y89بwwV688ޖ޵Y7׬uUWԨyu4ӬT԰v1RQQQR1110QS2rr4tupQ000Q1qҬTq2223233s7ֶ54555UVV5544TvڗޘXwXWXXWxXxXxWWWYyxxڹXy89͙YL,,,,L,L,-M,,MMLM-M-LlڎZɺzy9Y9899ججw7wV78Yޕ:ζ7VuvuvUVW76ְ5ӬvURrQQR1111010RRRTŴ2p01000RT3S3rRssss3sUҗ޷Xy555556VUU6V55vwvV5WwژX7W77877777WWWֶy8XXֹx9ιɹɹyy9L,M,M,,,-,,L,L-LLMLm֏z:͹ɹyYY9998ججبw7WX5u67Xyװut5uVɵ5uwVtstԬӨԬRR11111111111RQRSWШO/PPST2򰲬Ұ3򰒠SSsSs4Wږޗ޷޶555656666555sɶŷUԬ4XWW76Vvڗx8WҘڸιŹxxY8LLL-,,,,LL,LL,-,,MnZɺřyyY88ببפVV8Wvvw֘x֨֨UuUutլ֬uuUըttTTԬԬUrQQQR1110100111QR4r4xVSO0P򬑨qqӰ򬳤332333s6Vwwޗ޶ޖ޶޶56556656555W77ζutX7VږXxW6ҘəřxYXmLML,L,,,,L,,,,MMҏzɺřyyY8ججببw7V89suW޸׬vҶu4uTUs64UT4utt3uŴQR1Q111011010QQQQӬ3sV6֓oPqPq0QrqrӬҬs333T66WVvږޖޖ޷5565655555Wtږޖvv6337ҕWWxW֘ɘyxxX8m,,,,,,,,,L,,M֐:əyxYX9רبW7W98޷57җ68װ׬v7wwլuUլ5SuuլU4uuutӨԬ111111Q00Q0000PQ2SrppQ00000PQqqӬҨ223RU77֖޶ڗ޷555Vޱޒ޲ޓs4ҴuW9W7ҘZ[θřX8ج,,,,,L,,,-Mڰ޹y޺ޚޚZ͹əX8Y888جججר7vV88ڷɖΗ87հW65U44U5t5T32111111101101P5/PQҬtɰP0/000/pP11QҬr3R3RS33RԬUŵŶ6v޶޶ޗޖޗ5ްސްސ1ֲőRxzyWW[zwYxxxY8ج٬M,,,,,,,,,֐޲yzYYZzY9͹x8x78ججרW788v7vɗvxw57֬֬vլ4u5T4uST11111111100QqrӬҬS͔p/PP00P1QqҨsSsrUvUT5uږ޷ޖ޷ޖ޶64T{y888XWX9ҘX8ٰجذmL,,, +,,oޱޘޙY9͘Yججببw7Y7Ҙ5җشw7ŗ֬ԬTtSuu44T44T222r1r10110Р0QQrsrQrq00/0P000PQqҨ43SS3Ԭ55u6֖ޖ޷޷޷3SU{yX88yyޘZZ8ɚw7ذذذٰx,,,, ,,nڑޔޘޘxڸyɸŸŸŸ͹ɘxXذذجج8vWҴ֗7uv6tW֬ըuT3T44t44ըrR2RrUr1RQrqo4oPqQrr1QQqtŔo0/000000QTɴɴɵ͔U55UU֖ޗޗޖޖ޷޶ޕ޶UwYޙYyژxθx89ָ8ذ+,,,, M֏ޒvwxxXژ޹yXXyxŸŸɘxX8׬ذ׬׬׬ج76wֶ9v7Ҹ5t65w֨5լT443U333t3sSr2110otƏ000qQrQqpqqs10///0/0QqҬ3ɴɳŵuԬsTVږޖ޷ޖޖvޖvwޖuޖޕޕ޵ޕ5vwZ޺yX֙ޙXҸyxxιXX9ذذɗm,,+,,,oquWX8Xڙ:ֹy988XyX8׬׬׬װ7͸87ww79޷u6uv5֬Ԩ4443u4tS3svڶrR0ortϜ0010QP0/0QqQpҬSSoO/n/000QrҨ2sSRSŴussvږޗޗvvvvvvVVVuvuޔ޵ޔޔx:޺޺yyڙx9xxX8ذ6,,,,,,Moޓw7XڹYҹy8XX87׬ְw8WwwޗxɸxW779wڵŸŵ4u֬WԨWըUTu2TޕU4ζtTp00OtO00P1P00PPPPPPQ3////Ф/0PҬ2Ѭ24s4җޖޗޖwvWVVVV5665Uuuޕ޴ޕttyY9޺ޚzzڙ޺ޚyxڙX͘xxXذ89xذx5l,-,-,,oޑv7Xڹy9͹y8ذجװw֘889xڷҷW8ZU8ҵt׬6ըX44333Q6Ҷőt3tŔS/0P0Tpn010P//O00/q0pQ//QO/pҬҬtTrԬTvڗޖޗvVV77655utޕޔޕޕޔsy{zZ:޺ޚޚzZZzښYyڙx8͘X88X7ذXVL+,L,,,MpsVwYڹxѹyװجذװX9Zz{zzY9x޷vvW֔u7uvu45ttt33S4sҬӨSœPrO00oP//O0///00PPpqҬ/////0򬑤qpqqqSɶUԨҖޖޖޗvW65Utuޔڔޔtޔ89Z{:[޺zZ;:ZZ:y88ҘŘx88׬W6+K,M,-L,,nڑtXxW8֘޹XїXج׬Xڸ::Zzzz9zyYWW6WV͕լwwW֔vרVVT42tֲ򬒠r2QRRӬUғ2O0O0/0O////0P/000qQPq////ѤS򬑠qpqPqpӰS7V͖ޗޖޗwv665UTtuڔޔޔޔޔttޔ[;ޛ޺ޚ[;9ҙXXҷxxY8٬جججWx7L-L,L,LMM֏ړv7Ҹ8Y8ɘ8׬װ׬׬װx8x:ZZZ9y9֙9xxYZx֘vuVuu65ԠU2u沤221110pѨӬ5p0000//O/O////0/000PQp//0.r2qqpQqҰsvږ޷ޖvVV64UUTTtutTsޔޚZZzںޙzښښ[;ɺź:9yڙXɹɘ88׬ججvLLL,LMMLMpޔV8֘x88͸XذxX֘9X9ə9899YzZwWwѷլ׬xVVvv֤WԠt323SΖ4sQr110P0011q33Q00/P0///////P0//PP00Pp/oQŴ񨐜ppPPOPpPpqtɶv7֗ڗޖޗޗvVV55444UTTTTTڶYY:999ZZz֛[:ŹzYyyy9978جبW67xM,LM-MLMnڑu7X8YYɸɘx787XX8XXY9XX޸89ZY:y7ͷV֬WvuVWUVvw54t4VҐ5Αr11Q000011QQP//PP0O/////N//P0///100qO/O𨓹qpPO//000QpPQqStUV6ҖwvwvvvUU5sStx9yzYZy֚{:źŚY9Y֙ڙyXɘW7X8بWWYxWLMLMLMMMnޒuҸXyyxX7XxxŘŸɸ98XXڸ8֗8ͷլwWUVu454T43Q6ғ򬓤r1QQ00000000q00/P////O/O////00p0pp//S2///////////PPPPpStԨ6vvvvVuuU֔5T͸޺{:řyY8yyyYҘ87x8ww7787LMMLMLmMosVҗ8yYјWWxŸɸ9ҷɹXҸ9ޘذy8yޘwwֵv5ը7V35u4446ҔШ1rqR1P110010p//0////OO//0/P/0p1PQPp//Ф/////0//0/0PPPQQSҵ56UUUuu޴W4ɴҖXָŘz[{:ŚyX8Y88Ҙ͸بwv6ͷMLLMMMMmos6X88͗WWΘŗyXڹXXy9xXxޗw7Ҷv5v֨5Vv54T3QRwRSrӬQQr0q000/0000/0////.OOO/O///P00P0qPoФѨ////0//0//00001QV6͕65Uڕ޵RTɴŴŴUҘ{{:Źy98Ҹ8W8XwwVwvLMMMMmMMpsҷxŸx77ͷŘxW7ҸҸəxxyYجXxxҘw7ɷvVwv5WְT5xvը֤u5TSQѨ԰00P010QosϠ00P//////O/O/O//O0/P0PPqPp2.///0///0/001ҬwVѵUW5uڕޔsTUUUɵɵŴŴ5֗Ҹ[Zzźy99xҸŘ7WxxwW6ŖVMmLmMMLmڏTҗww׬887w͗wwW7yޙXwWޘyxژYYذw7XvڷW6WUְ67Ԭuu45443ůqRQRQQPPP001P0/p0/0p///./.O././/O0POQ0qPPQ2SS2///P////0/0011rt56͖v6wWW5UuuޕޕޕuɴŴwژ޸yyz:źyY99Y͙X7ŗXxx77״x7W6MMMMmMMnpSwwWجװ֬7͗W777WҘxڸ8X9W7xyYx7y8xx޷Vv7w75V67uwuuU5Tt/ϠQ6QQPPP0/P000/ѽO//0PO//O///O////OO0/0PPPP0OѬ2SSSШN/////00000000RT4S66Wxx֗w6WVV֗ޖޗޖUɴ7XXXy9͸::9ŹźřyYYY99جج٬ذXɗw6جwWXwMmMMmMnmpSww7wװ7WŖɷɖvVWWwxޙx8WژY89ذ7v7xX8ҷΗŷ5455WuVv6T31půШTQr11000P0//00Op0/0O/Op//NOOO///O/p/Q/p0/q22N///000010rӬtV6677w7676wڷޖږ5εɴ͹ɘŹYYYY99جج٨بxXXװ7װW7WvMMmMmmNmoSv֨w7w6vŖŗŖwvvWxYڷ8X7vڹ9٬wWɘږŷŗڶUUV744T֬V5Vv55321/Q撽ϤqR101Q10//00//0//P///////O.//../O0p///Ѭ3O///00/010rsV667W767Ww֖WVͷXɹxYŹŹyYY9XببجججXwذ׬׬76VmMmMMmnno3v֨w6W6vuvvvwڸ֗׬ɘ9XҙVζ͵77֬uSuU4211qpQTRœҨ000//0/000////O//./O../N/OPO//NѬqoopѨ32O///0/1Qrrrrrr6W67WWWwWW6wWX8y:9ΙyY989ججWvw֬x7WVMmmmmnmno3ɖV7W6vvŖŖŖw:;x׬{{zޙXWX7ҵX6޷ޖ7հŕ4t44U451ppPqҬpoQsp000/////0//P0/0///0PO/.O//ONN///..O././.ѬoOOppҨS///000/01rRqQRR56ɶ7WxxW7ҷ779ɹXYzzY9ɹY9yYYجxV778V׬WmnmnMnnmoR͖v77רVŖɶɖw::xx{yx7WWy76Xwְvut33U43332rqppQqѤ01100//0/00//O/P/0/////PO///PO/N/OOOO......nϨUNoOOp///0/00/00011qRQRQQQRҵtuW7ɕu77x9͸XYͻ{͙YYYY9ببxXXx7777Wװ׬7nmMmnmnnnQҵv7Vװ7vɶWڸxw׬ذY֙ŘX77WWV6WWuu4Tt43444SqqҬҨѨqpШP1P01Q0P/////0////0//0/p///P.//O.//N.N..O...nnNO.NO/PѨOP//0O/000QqqQqQQR22RT4uɶŕU4UV7ֶ689ҹXx:һyyκyY89بWWw777677wר׬װ׬7nmnnnmnnmpҵ6vvͶ7֗Xxججج8x͵VUv54uְUwtT33333210Ѭ4q1qӰQ1q00///O///O///0////////P///////./N/N/././..oOO.NN.../P/ppѨo/0/qqPqqQqQQ111rt󴳨3ɴŔvv544uW7ַ7֬:ΙYy9z9ҹXřyX9جبX7WWW776V77פx֨׬xnnnmnmnڎmo2v֬77ͶɕŶwڹXwججج77X7UVw6uְvvVvTU4TT433331110Q򰲤ӰPP0P0O0//OO////P///OOOp//0P//../O/O..ON...oOO......O.O/Pqp0//0PQQqQq111tSRrtT44TWҸרYɹXY9͙͘yY9ججWW7WvW776V76v֠נ֨׬ְnmmnnnnnnnQɕvɶŖɕŕї޹wxxޘޙxַw7װYְְ5֬5tU55U4434321qPPQ󴓨q0000P00/00/0///0/0/O//P/O////////.O..NN/.......///..NNOPQPp//////01QqqQ11tr1P884T4T4WxװyXY͸ŘyyxYبW77ww77667V֤֠6nnmnnڎnnnڎoU׬vŶɕŕ7֘޹W778xy8xx77W֬V6ְ6֬֨VըUS4t3433233qqQ1Qr1000P0/00////P//0//OPO/////P//.O........n.///......N-O/00p./0000111Q3TQ11RTŵŖŖv6WX׬׬׬YyWZyYxX9W6Vללv6766666v֨פ6nmnmnnڎڎnnnQu6vɶŖŶ7֙x77YXW׬xW7w6լVt6U4wT45u44t3333RrœŲRrqrQ00/P/0/0O/P/p///OO.O//O/p/OONN.....-.nNNN.NO-..-/.NO/PPp񬰨O//0Rq111P2tŶͶŗu7wҸ׬ذYXY4XxyyY8جV76w֔66666פnڎnnnnnnnnnoUVuɵ7Wx8ѶW78wxxw7X77w7Vuְְ6UT4tTT3S3333а5T3qQPpQr0000/P00///0/O//0/p/OO/O////O.O./.......OoONNO.NN.-....OOPO0񰰬ѬѬo/010Q111RsӬUuvv56ָ8Ҙ788Yx6YyŘXxy8جببV66W6666667Uפw5nnnڎnnڎڎnڎڎnp͕ɕ7і7Ww8ѷxWWWXW6լ7uԨ33TUTT4St3322Ҵɰ3TS4r3PQqQPQOq/0O///OO///O//////O/P.O...OoN.. .NnNnnONM.N........OOO0pѬѬѬo/000000Qqq112qӬԬu6zZxɘxX88X8y8XxX8جب٨V76666666666666656հVnnnnnڎnڎnڎoގn1͵7ɸɶw787רwWWW7W7667ְtU33tTT3433331Rζ3QQ0Qq00P0q/p00//0//0/////PO/Oq//O... MРnN..-аpNO.NNNNN..N.oOPO00ѬѬѬѬ񬱨p0p񬑤010QҰҬ11RհԬu7w:ŹY8Y88xX9بxלw766V6V5V5U656mnڎގnnnnڎoڎnڎpіŖxw68WwW7776W׬x76vtִTwլT2st233433322ѨUsųɲ4rP0000PQ0P0/PP/0/OO//P/P0/O/OOOO-. РM.NϬϨoON.M-N-...-NNrO//OP񬐨opѬѬ򬐤OŵɐPqҬQ112RԬԬTXxWWWW7789ɹX88׬8X88بww66VVVVV56VV6666tWֶɎnnڎڎڎގnڏގoڏޏp3ɗwY֙WvY7֖֗vw7WwvW76մST4tT233Sլ333t32QѤqTQ1p1Q0///0O0/Q0//OPPO///P///pONNN... -rmNNnoo/.-NN-NnoNNNNo/P/0poOoШҰsSqPҬҬҬP2222RWw79xX͸ɗŸŘ7Y8׬׬׬جרxw766VVU5VUVU5666665xڵnڎnnnڎnڎnڏޏoڎp4ɖuəx66WvwҸVWWv66լv6ְSS333SSS322qШҬ11R1qP0/OP0//P00P0/O/////O/O//OO/-..-.- -  -Q...OON..-....-.N../P/o񰯨OOOOopoOҰѬoP/ҰҬҬҰ򰱤r112R77ҖwŗxwXWwwXXX׬׬جwWw66666V5UUU65556VҎnڎڎڎڎnڎގڏnڏޏq5ѵɖvwVvXҙ8ҷ6wWɸWvw7wWԬհttUtts33333S3330S1rΒp0qQ/p/////////O/PO0PO/Np/P/....-. .. -/.N......-NN...O/.PPNOnoOoNpOppP00032ѬѬ23STS3󰳬ӬT͗WVWVwWWW7֬x8ج׬װ8XŸŘجww7777677675565UU5V555Uunގnގoގގڏޏڏޏޏޏr͕UU5VxژΗwɗWWִ667WVլհլְ333333333S22Ѥ3r0q10P0/P////P////OO///p//O..M.o.... -.--- lN.NN..--.-M.N-.O.p/񬯨onnOO/pppPqPO/P2S񬱨Ѩ2SStut3TUvUVv78ɸɘX7׬׬887w8xwwږѸ776555U65656uvҵnnڎގڎڎގڏޏޏޏޏޏSֶU5xYw6ŷw׬w7WְVttTtT333333332321qѨpQPPP0p//////////O/N/O./...-. ...-.-.OM-.  oΤ......NN......OP//ЬϬooN.//NOpPppO/pPqҬҬ33T3UɵuW8ҹy8֬׬װ{:TTvx׬w76667666655U555555ړڎnڎnڎގڎڏޏގڏޏސSѕU58xvŷWvwW6UլWWutt4T33SS3333S222R00/qQ0PO////OOOO/////.//....--../-.-NN-.MRMN...M-N...N/NN/././/OOpqppѬpO/P0PQpѬ3T8ɖ7͘Y87X8׬֬99u3S8ŷxר766פXv5555555445հSnڎގގގnގޏޏޏޏޏڰSu5555VɷvլvvV6vUTըuWԬtְSS33333332oѬOҬOҬpq/P/0P/OPONO../../... -N..--Nn--.Nn-...- ..oo.-...NonN/N...N/O/OOPpqѨO//00PpppqqɶSz66ͷŗ7X88֘T֕Y{WY5776՜65555545462oގގڎޏގޏޏޯޏޏޯޑ4U5U655wŷVV6U66հtTvޖ޶ɵ6St333s33SS333S2R񬯠pOpoOPp/p//OO////O./O/./. -.MN-n-.-N-N.. nm ..-.- -.-.../QϤOnnO./...NNO//OP//0//OPpŕ64VҗvwvvW7W8ֶv6wwx֤876655455555ҎڎnގޏޏޏގޯޯޯޏޱTڴ4VVV6ըvv665U5uuޔլUtSTT333343SSR220nS//NNp/P/OpOoO/O/OO///. .--MmM-.-.nN.N.....,n,M0-  ../.nNNNNN..N.NOO/O/0PPO00/00PpRvuҬuvWVW68֘WWɚZڗW͘wXxɘ5U555554544tusڏގޏޏޏޮޏޯޏگޯްް4ڴ4ԬVV6V6u5us6֬լլTU3T3SSS332s20qopO./p/P/pOpP/0NPo./O./MMNomMNN........- MϤΠl  ...OΤONNnnN..N.NMpO/O/0PO/0/OOֲ22t԰Uu5ӬҬ6V66V66WW67ֹxwXxW455555455T5ڕ޵ִޯޏޏޯޏޏޏޯްްޱ4ڴ4Ԭհ5V65utuU555Ŵųvޔ԰s2TtS3ըSS3333R21/02pp/O/0///P////////.//.﬎-N.--.--.....N- n1M.. ...n﨎.NNnN...NNNNOOOPPPpPPOpPQuհUѵ5756VWVwլ68xڙ޸޷WW8җX5554454v5tޏޮޯޯޯޯޯޯޏޯްޱTڳԬU6VըըtuuuuU6Uuɓ޵5ִuRS2ԬԨTTtt3sSR3232Rq00oѨOOO/pOp//000///O./// nN.MΤ.-.-.M...-N.-.  mѽOΨN- ..nNN.MNN..NNNNN./OOPpPppϬЬааЬЬ1ɑrųV޶uڵ56V6w67wx8x7W{75545444vɖTŔޏޯޏޏޯޯޯްްSڴԬU5uvըլuTUuu65ŕ3sStҵwTԬSSR3s֨tTssR32qpOOOPN..OO///0////////./..0m.n. .....--MN-..-  .m-pmN-.- ..nNNMNN.....N../.N/OШpͱЬQаpqq6wZ޵5԰56V76VwV6W78X77wҘ7W443Ŵ43ޯޯޯޯްޯް1ҳTŔԤttttT44ԨsѬѬ2qœVsRR111112ԨRsR12110pq/ϤnШ.2n..-.N..P...o...-..- lN.L,.- ,-,,-L-- -- - -- MmmmmLL-----N------.M..-N...NoЬo/ɐOő.55Vִ34TuɕU54Uvw6հ7w7֗U6VɶͷԨ3VvœWޯޯްޯް7~~}]34tŵVwްްޏ7|<]\<\޻޻޺޺޺:޺[;[[;:|<<\|\|==<}]]\|]]===]]==}}޼=VѴUUڼ{<<\]<޼<<޻޻޻޻\;޹;:[;:z|}<\}}=<\]<]<===}]<]=\]\}޼]3567V5ސM{;\\\<<;޻;:޺;;|\}<<\}}<<<<=\<]=\}}==<=<]]]}|=ޞqtŶV65N{\<\]<<<޺<[;:::[\\<<\]\<<<<\<]=<<]]=\<]]\==\<}]]<\޼޼޾VvVڰN[;\|]<<;;;[;[޺:[{;;\|}\<\<<\||}<\]<\==<}\<]<<=\]]}==]=}=Δ85vַ5M{;<]\<<<޻[;|\|;|޺;|[Z{;<\];<|||Z=\|<==]}\\]}<<]]<=]<}<\<|\2ΕV6vڗ5wN{[\\\<<޼<<[{{޺[[|[;[||<\||||}\|\<<<\}|<|\tuUU֗vuָVn{{;<;<<<<޻|[;޹{{};;||\\<<|}|}<<<<==};\\twl_iwram AT>twl_ewram :arm7i + + .twl_bss ALIGN(4) (NOLOAD) : + { + __twl_bss_start__ = .; + *(.twl_bss .twl_bss.*) + *(.twl.bss .twl.bss.*) + *.twl.*(.dynbss) + *.twl.*(.gnu.linkonce.b*) + *.twl.*(.bss*) + *.twl.*(COMMON) + . = ALIGN(4); + __twl_bss_end__ = .; + } >twl_iwram :NONE + + .twl_noinit ALIGN(4) (NOLOAD): + { + __twl_noinit_start__ = ABSOLUTE(.); + *(.twl_noinit .twl_noinit.*) + *(.twl.noinit .twl.noinit.*) + *.twl*(.noinit) + *.twl*(.noinit.*) + *.twl*(.gnu.linkonce.n.*) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + __twl_noinit_end__ = ABSOLUTE(.); + __twl_end__ = ABSOLUTE(.); + } >twl_iwram :NONE + + .crt0 : + { + KEEP (*(.crt0)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >ewram :crt0 + + .text : + { + __arm7_lma__ = LOADADDR(.text); + __arm7_start__ = .; + KEEP (*(SORT_NONE(.init))) + *(.plt) + *(.text .stub .text.* .gnu.linkonce.t.*) + KEEP (*(.text.*personality*)) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + *(.glue_7t) *(.glue_7) *(.v4_bx) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram :arm7 + + .fini : + { + KEEP (*(.fini)) + } >iwram AT>ewram + + .rodata : + { + *(.rodata) + *all.rodata*(*) + *(.roda) + *(.rodata.*) + *(.gnu.linkonce.r*) + SORT(CONSTRUCTORS) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >iwram AT>ewram + + .ARM.exidx : { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } >iwram AT>ewram + + /* 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 = .); + } >iwram AT>ewram + .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 = .); + } >iwram AT>ewram + + .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 (*crtbegin?.o(.ctors)) + /* We don't want to include the .ctor section from + the crtend.o file until after the sorted ctors. + The .ctor section from the crtend file contains the + end of ctors marker and it must be last */ + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*crtbegin?.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .eh_frame : + { + KEEP (*(.eh_frame)) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + + .gcc_except_table : + { + *(.gcc_except_table .gcc_except_table.*) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + } >iwram AT>ewram + .got : { *(.got.plt) *(.got) } >iwram AT>ewram + + .data ALIGN(4) : { + __data_start = ABSOLUTE(.); + *(.data) + *(.data.*) + *(.gnu.linkonce.d*) + CONSTRUCTORS + . = ALIGN(4); + } >iwram AT>ewram + + .tdata ALIGN(4) : + { + __tdata_start = ABSOLUTE(.); + *(.tdata .tdata.* .gnu.linkonce.td.*) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + __tdata_end = ABSOLUTE(.); + __data_end = . ; + } >iwram AT>ewram + + __tdata_size = __tdata_end - __tdata_start ; + + .tbss ALIGN(4) (NOLOAD) : + { + __tbss_start = ABSOLUTE(.); + *(.tbss .tbss.* .gnu.linkonce.tb.*) + *(.tcommon) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + __tbss_end = ABSOLUTE(.); + } >iwram AT>ewram + + __tbss_size = __tbss_end - __tbss_start ; + + .bss ALIGN(4) (NOLOAD) : + { + __arm7_end__ = .; + __bss_start = ABSOLUTE(.); + __bss_start__ = ABSOLUTE(.); + *(.dynbss) + *(.gnu.linkonce.b*) + *(.bss*) + *(COMMON) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + __bss_end__ = ABSOLUTE(.); + } >iwram + + .noinit (NOLOAD): + { + __noinit_start = ABSOLUTE(.); + *(.noinit .noinit.* .gnu.linkonce.n.*) + . = ALIGN(4); /* REQUIRED. LD is flaky without it. */ + __noinit_end = ABSOLUTE(.); + } >iwram + + /* Space reserved for the thread local storage of main() */ + .tls ALIGN(4) (NOLOAD) : + { + __tls_start = ABSOLUTE(.); + . = . + __tdata_size + __tbss_size; + __tls_end = ABSOLUTE(.); + __end__ = ABSOLUTE(.); + } >iwram + + __tls_size = __tls_end - __tls_start; + + HIDDEN(__arm7_size__ = __arm7_end__ - __arm7_start__); + HIDDEN(__arm7i_size__ = __arm7i_end__ - __arm7i_start__); + HIDDEN(__bss_size__ = __bss_end__ - __bss_start__); + HIDDEN(__twl_bss_size__ = __twl_bss_end__ - __twl_bss_start__); + + /* 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 (INFO) : { *(.comment); LINKER_VERSION; } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* 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 .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .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) } + /* DWARF 3. */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF 5. */ + .debug_addr 0 : { *(.debug_addr) } + .debug_line_str 0 : { *(.debug_line_str) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_macro 0 : { *(.debug_macro) } + .debug_names 0 : { *(.debug_names) } + .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_str_offsets 0 : { *(.debug_str_offsets) } + .debug_sup 0 : { *(.debug_sup) } + .ARM.attributes 0 : { KEEP (*(.ARM.attributes)) KEEP (*(.gnu.attributes)) } + .note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) } + /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } +} diff --git a/arm7/dldi_ds_arm7.specs b/arm7/dldi_ds_arm7.specs new file mode 100644 index 0000000..47a284a --- /dev/null +++ b/arm7/dldi_ds_arm7.specs @@ -0,0 +1,23 @@ +%include + +%rename cc1plus blocksds_cc1plus +%rename cpp blocksds_cpp +%rename link blocksds_link + +*cpp: +-D__NDS__ -D__BLOCKSDS__ -DARM7 %(blocksds_cpp) + +*cc1_cpu: +-mcpu=arm7tdmi + +*cc1plus: +%(cpp) %(blocksds_cc1plus) + +*link: +%(blocksds_link) -T arm7/dldi_ds_arm7.ld --gc-sections --no-warn-rwx-segments + +*startfile: +%:getenv(BLOCKSDS /sys/crts/ds_arm7_crt0%O) + +*lib: +%(libgcc) diff --git a/arm7/source/Arm7State.h b/arm7/source/Arm7State.h new file mode 100644 index 0000000..b306ac5 --- /dev/null +++ b/arm7/source/Arm7State.h @@ -0,0 +1,8 @@ +#pragma once + +/// @brief Enum representing the arm7 state. +enum class Arm7State +{ + Idle, + ExitRequested +}; diff --git a/arm7/source/ExitMode.h b/arm7/source/ExitMode.h new file mode 100644 index 0000000..6c62d4a --- /dev/null +++ b/arm7/source/ExitMode.h @@ -0,0 +1,12 @@ +#pragma once + +/// @brief Enum representing the exit mode of launcher. +enum class ExitMode +{ + /// @brief Reset the system (DSi mode only). + Reset, + /// @brief Power off the system. + PowerOff, + /// @brief Launch an application through pico loader. + PicoLoader +}; diff --git a/arm7/source/common.h b/arm7/source/common.h new file mode 100644 index 0000000..c717672 --- /dev/null +++ b/arm7/source/common.h @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/arm7/source/dldi.s b/arm7/source/dldi.s new file mode 100644 index 0000000..28d95fa --- /dev/null +++ b/arm7/source/dldi.s @@ -0,0 +1,16 @@ +.global _dldi_start +.equ _dldi_start, 0x037F8000 +.global _io_dldi +.equ _io_dldi, (_dldi_start + 0x60) +.global _DLDI_startup_ptr +.equ _DLDI_startup_ptr, (_io_dldi + 0x8) +.global _DLDI_isInserted_ptr +.equ _DLDI_isInserted_ptr, (_io_dldi + 0xC) +.global _DLDI_readSectors_ptr +.equ _DLDI_readSectors_ptr, (_io_dldi + 0x10) +.global _DLDI_writeSectors_ptr +.equ _DLDI_writeSectors_ptr, (_io_dldi + 0x14) +.global _DLDI_clearStatus_ptr +.equ _DLDI_clearStatus_ptr, (_io_dldi + 0x18) +.global _DLDI_shutdown_ptr +.equ _DLDI_shutdown_ptr, (_io_dldi + 0x1C) \ No newline at end of file diff --git a/arm7/source/ipcServices/DldiIpcService.cpp b/arm7/source/ipcServices/DldiIpcService.cpp new file mode 100644 index 0000000..9315a2d --- /dev/null +++ b/arm7/source/ipcServices/DldiIpcService.cpp @@ -0,0 +1,46 @@ +#include "common.h" +#include +#include +#include "DldiIpcService.h" + +extern FN_MEDIUM_STARTUP _DLDI_startup_ptr; +extern FN_MEDIUM_READSECTORS _DLDI_readSectors_ptr; +extern FN_MEDIUM_WRITESECTORS _DLDI_writeSectors_ptr; + +void DldiIpcService::HandleMessage(u32 data) +{ + auto cmd = reinterpret_cast(data << 2); + switch (cmd->cmd) + { + case DLDI_IPC_CMD_SETUP: + SetupDldi(cmd); + break; + + case DLDI_IPC_CMD_READ_SECTORS: + ReadSectors(cmd); + break; + + case DLDI_IPC_CMD_WRITE_SECTORS: + WriteSectors(cmd); + break; + } +} + +void DldiIpcService::SetupDldi(const dldi_ipc_cmd_t* cmd) const +{ + memcpy((void*)0x037F8000, cmd->buffer, 16 * 1024); + bool result = _DLDI_startup_ptr(); + SendResponseMessage(result); +} + +void DldiIpcService::ReadSectors(const dldi_ipc_cmd_t* cmd) const +{ + _DLDI_readSectors_ptr(cmd->sector, cmd->count, cmd->buffer); + SendResponseMessage(0); +} + +void DldiIpcService::WriteSectors(const dldi_ipc_cmd_t* cmd) const +{ + _DLDI_writeSectors_ptr(cmd->sector, cmd->count, cmd->buffer); + SendResponseMessage(0); +} diff --git a/arm7/source/ipcServices/DldiIpcService.h b/arm7/source/ipcServices/DldiIpcService.h new file mode 100644 index 0000000..d5732f0 --- /dev/null +++ b/arm7/source/ipcServices/DldiIpcService.h @@ -0,0 +1,19 @@ +#pragma once +#include "ipc/ThreadIpcService.h" +#include "dldiIpcCommand.h" +#include "ipcChannels.h" + +class DldiIpcService : public ThreadIpcService +{ + u32 _threadStack[128]; + + void SetupDldi(const dldi_ipc_cmd_t* cmd) const; + void ReadSectors(const dldi_ipc_cmd_t* cmd) const; + void WriteSectors(const dldi_ipc_cmd_t* cmd) const; + +public: + DldiIpcService() + : ThreadIpcService(IPC_CHANNEL_DLDI, 6, _threadStack, sizeof(_threadStack)) { } + + void HandleMessage(u32 data) override; +}; diff --git a/arm7/source/ipcServices/DsiSdIpcService.h b/arm7/source/ipcServices/DsiSdIpcService.h new file mode 100644 index 0000000..24620fd --- /dev/null +++ b/arm7/source/ipcServices/DsiSdIpcService.h @@ -0,0 +1,19 @@ +#pragma once +#include "ipc/ThreadIpcService.h" +#include "dsiSdIpcCommand.h" +#include "ipcChannels.h" + +class DsiSdIpcService : public ThreadIpcService +{ + u32 _threadStack[128]; + + void ReadSectors(const dsisd_ipc_cmd_t* cmd) const; + void WriteSectors(const dsisd_ipc_cmd_t* cmd) const; + +public: + DsiSdIpcService() + : ThreadIpcService(IPC_CHANNEL_DSI_SD, 5, _threadStack, sizeof(_threadStack)) { } + + void Start() override; + void HandleMessage(u32 data) override; +}; diff --git a/arm7/source/ipcServices/DsiSdIpcService.twl.cpp b/arm7/source/ipcServices/DsiSdIpcService.twl.cpp new file mode 100644 index 0000000..f97a753 --- /dev/null +++ b/arm7/source/ipcServices/DsiSdIpcService.twl.cpp @@ -0,0 +1,36 @@ +#include +#include "../mmc/sdmmc.h" +#include "DsiSdIpcService.h" + +void DsiSdIpcService::Start() +{ + pico_SDMMC_init(SDMMC_DEV_CARD); + ThreadIpcService::Start(); +} + +void DsiSdIpcService::HandleMessage(u32 data) +{ + auto cmd = reinterpret_cast(data << 2); + switch (cmd->cmd) + { + case DSI_SD_IPC_CMD_READ_SECTORS: + ReadSectors(cmd); + break; + + case DSI_SD_IPC_CMD_WRITE_SECTORS: + WriteSectors(cmd); + break; + } +} + +void DsiSdIpcService::ReadSectors(const dsisd_ipc_cmd_t* cmd) const +{ + pico_SDMMC_readSectors(SDMMC_DEV_CARD, cmd->sector, cmd->buffer, cmd->count); + SendResponseMessage(0); +} + +void DsiSdIpcService::WriteSectors(const dsisd_ipc_cmd_t* cmd) const +{ + pico_SDMMC_writeSectors(SDMMC_DEV_CARD, cmd->sector, cmd->buffer, cmd->count); + SendResponseMessage(0); +} diff --git a/arm7/source/ipcServices/RtcIpcService.cpp b/arm7/source/ipcServices/RtcIpcService.cpp new file mode 100644 index 0000000..6013125 --- /dev/null +++ b/arm7/source/ipcServices/RtcIpcService.cpp @@ -0,0 +1,9 @@ +#include "common.h" +#include +#include "RtcIpcService.h" + +void RtcIpcService::HandleMessage(u32 data) +{ + rtc_readDateTime(reinterpret_cast(data << 2)); + SendResponseMessage(1); +} diff --git a/arm7/source/ipcServices/RtcIpcService.h b/arm7/source/ipcServices/RtcIpcService.h new file mode 100644 index 0000000..afe5420 --- /dev/null +++ b/arm7/source/ipcServices/RtcIpcService.h @@ -0,0 +1,14 @@ +#pragma once +#include "ipc/ThreadIpcService.h" +#include "ipcChannels.h" + +class RtcIpcService : public ThreadIpcService +{ + u32 _threadStack[128]; + +public: + RtcIpcService() + : ThreadIpcService(IPC_CHANNEL_RTC, 10, _threadStack, sizeof(_threadStack)) { } + + void HandleMessage(u32 data) override; +}; diff --git a/arm7/source/ipcServices/SoundIpcService.cpp b/arm7/source/ipcServices/SoundIpcService.cpp new file mode 100644 index 0000000..96bd176 --- /dev/null +++ b/arm7/source/ipcServices/SoundIpcService.cpp @@ -0,0 +1,49 @@ +#include "common.h" +#include +#include "soundIpcCommand.h" +#include "SoundIpcService.h" + +void SoundIpcService::OnMessageReceived(u32 data) +{ + const u32* commandList = reinterpret_cast(data); + u32 cmdCount = *commandList++; + for (u32 i = 0; i < cmdCount; i++) + { + u32 cmdValue = *commandList++; + u32 cmd = cmdValue & 0xFF; + u32 cmdArg = cmdValue >> 8; + switch (cmd) + { + case SND_IPC_CMD_START_CHANNELS: + { + u32 channelsMask = cmdArg; + for (u32 j = 0; j < 16; j++) + { + if (channelsMask & (1u << j)) + snd_startChannel(j); + } + break; + } + case SND_IPC_CMD_STOP_CHANNELS: + { + u32 channelsMask = cmdArg; + for (u32 j = 0; j < 16; j++) + { + if (channelsMask & (1u << j)) + snd_stopChannel(j); + } + break; + } + case SND_IPC_CMD_SETUP_CHANNEL: + { + u32 channel = cmdArg; + REG_SOUNDxSAD(channel) = *commandList++; + REG_SOUNDxTMR(channel) = *commandList++; + REG_SOUNDxPNT(channel) = *commandList++; + REG_SOUNDxLEN(channel) = *commandList++; + REG_SOUNDxCNT(channel) = *commandList++; + break; + } + } + } +} diff --git a/arm7/source/ipcServices/SoundIpcService.h b/arm7/source/ipcServices/SoundIpcService.h new file mode 100644 index 0000000..861a0e9 --- /dev/null +++ b/arm7/source/ipcServices/SoundIpcService.h @@ -0,0 +1,12 @@ +#pragma once +#include "ipc/IpcService.h" +#include "ipcChannels.h" + +class SoundIpcService : public IpcService +{ +public: + SoundIpcService() + : IpcService(IPC_CHANNEL_SOUND) { } + + void OnMessageReceived(u32 data) override; +}; diff --git a/arm7/source/main.cpp b/arm7/source/main.cpp new file mode 100644 index 0000000..9090ec9 --- /dev/null +++ b/arm7/source/main.cpp @@ -0,0 +1,241 @@ +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "logger/PlainLogger.h" +#include "logger/NocashOutputStream.h" +#include "logger/NullLogger.h" +#include "logger/ThreadSafeLogger.h" +#include "picoLoaderBootstrap.h" +#include "sharedMemory.h" +#include "ipcServices/DsiSdIpcService.h" +#include "ipcServices/DldiIpcService.h" +#include "ipcServices/SoundIpcService.h" +#include "ipcServices/RtcIpcService.h" +#include "ExitMode.h" +#include "Arm7State.h" +#include "mmc/tmio.h" + +static NocashOutputStream sNocashOutputStream; +static PlainLogger sPlainLogger = PlainLogger(LogLevel::All, std::unique_ptr(&sNocashOutputStream)); +static ThreadSafeLogger sThreadSafeLogger = ThreadSafeLogger(std::unique_ptr(&sPlainLogger)); + +static DsiSdIpcService sDsiSdIpcService; +static DldiIpcService sDldiIpcService; +static SoundIpcService sSoundIpcService; +static RtcIpcService sRtcIpcService; + +ILogger* gLogger = &sThreadSafeLogger; + +static rtos_event_t sVBlankEvent; +static ExitMode sExitMode; +static Arm7State sState; +static volatile u8 sMcuIrqFlag = false; + +static void vblankIrq(u32 irqMask) +{ + rtos_signalEvent(&sVBlankEvent); +} + +static void vcountIrq(u32 irqMask) +{ + SHARED_KEY_XY = REG_RCNT0_H; +} + +static void mcuIrq(u32 irq2Mask) +{ + sMcuIrqFlag = true; +} + +static void checkMcuIrq(void) +{ + // mcu only exists in DSi mode + if (isDSiMode()) + { + // check and ack the flag atomically + if (mem_swapByte(false, &sMcuIrqFlag)) + { + // check the irq mask + u32 irqMask = mcu_getIrqMask(); + if (irqMask & MCU_IRQ_RESET) + { + // power button was released + sExitMode = ExitMode::Reset; + sState = Arm7State::ExitRequested; + } + else if (irqMask & MCU_IRQ_POWER_OFF) + { + // power button was held long to trigger a power off + sExitMode = ExitMode::PowerOff; + sState = Arm7State::ExitRequested; + } + } + } +} + +static void initializeVBlankIrq() +{ + rtos_createEvent(&sVBlankEvent); + rtos_setIrqFunc(RTOS_IRQ_VBLANK, vblankIrq); + rtos_enableIrqMask(RTOS_IRQ_VBLANK); + gfx_setVBlankIrqEnabled(true); +} + +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 initializeArm7() +{ + rtos_initIrq(); + rtos_startMainThread(); + ipc_initFifoSystem(); + + clearSoundRegisters(); + + pmic_setAmplifierEnable(true); + sys_setSoundPower(true); + + readUserSettings(); + pmic_setPowerLedBlink(PMIC_CONTROL_POWER_LED_BLINK_NONE); + + sio_setGpioSiIrq(false); + sio_setGpioMode(RCNT0_L_MODE_GPIO); + + rtc_init(); + + if (isDSiMode()) + { + TMIO_init(); + sDsiSdIpcService.Start(); + } + + sDldiIpcService.Start(); + pload_init(); + + snd_setMasterVolume(127); + snd_setMasterEnable(true); + sSoundIpcService.Start(); + sRtcIpcService.Start(); + + gfx_setVCountMatchLine(96); + rtos_setIrqFunc(RTOS_IRQ_VCOUNT, vcountIrq); + rtos_enableIrqMask(RTOS_IRQ_VCOUNT); + gfx_setVCountMatchIrqEnabled(true); + + initializeVBlankIrq(); + + if (isDSiMode()) + { + rtos_setIrq2Func(RTOS_IRQ2_MCU, mcuIrq); + rtos_enableIrq2Mask(RTOS_IRQ2_MCU); + } + + ipc_setArm7SyncBits(7); +} + +static void updateArm7IdleState() +{ + if (pload_shouldStart()) + { + sExitMode = ExitMode::PicoLoader; + sState = Arm7State::ExitRequested; + } + else + { + checkMcuIrq(); + } + + if (sState == Arm7State::ExitRequested) + { + snd_setMasterVolume(0); // mute sound + } +} + +static bool performExit(ExitMode exitMode) +{ + switch (exitMode) + { + case ExitMode::Reset: + { + mcu_setWarmBootFlag(true); + mcu_hardReset(); + break; + } + case ExitMode::PowerOff: + { + pmic_shutdown(); + break; + } + case ExitMode::PicoLoader: + { + pload_start(); + break; + } + } + + while (true); // wait infinitely for exit +} + +static void updateArm7ExitRequestedState() +{ + performExit(sExitMode); +} + +static void updateArm7() +{ + switch (sState) + { + case Arm7State::Idle: + { + updateArm7IdleState(); + break; + } + case Arm7State::ExitRequested: + { + updateArm7ExitRequestedState(); + break; + } + } +} + +int main() +{ + sState = Arm7State::Idle; + initializeArm7(); + + while (true) + { + rtos_waitEvent(&sVBlankEvent, true, true); + updateArm7(); + } + + return 0; +} diff --git a/arm7/source/mmc/mmc_spec.h b/arm7/source/mmc/mmc_spec.h new file mode 100644 index 0000000..b6adebd --- /dev/null +++ b/arm7/source/mmc/mmc_spec.h @@ -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. diff --git a/arm7/source/mmc/sd_spec.h b/arm7/source/mmc/sd_spec.h new file mode 100644 index 0000000..808895a --- /dev/null +++ b/arm7/source/mmc/sd_spec.h @@ -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)) diff --git a/arm7/source/mmc/sdmmc.h b/arm7/source/mmc/sdmmc.h new file mode 100644 index 0000000..1eed04e --- /dev/null +++ b/arm7/source/mmc/sdmmc.h @@ -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 diff --git a/arm7/source/mmc/sdmmc.twl.c b/arm7/source/mmc/sdmmc.twl.c new file mode 100644 index 0000000..82482bf --- /dev/null +++ b/arm7/source/mmc/sdmmc.twl.c @@ -0,0 +1,751 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2023 profi200 + +#include +#include +#include +#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<>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; +} diff --git a/arm7/source/mmc/tmio.h b/arm7/source/mmc/tmio.h new file mode 100644 index 0000000..a1d34df --- /dev/null +++ b/arm7/source/mmc/tmio.h @@ -0,0 +1,414 @@ +#pragma once + +// SPDX-License-Identifier: MIT +// Copyright (c) 2023 profi200 + +#include +#include +#include +#include +#include +#include + + +// 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<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 diff --git a/arm7/source/mmc/tmio.twl.c b/arm7/source/mmc/tmio.twl.c new file mode 100644 index 0000000..d1bc160 --- /dev/null +++ b/arm7/source/mmc/tmio.twl.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2023 profi200 + +#include +#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; +} diff --git a/arm7/source/picoLoaderBootstrap.cpp b/arm7/source/picoLoaderBootstrap.cpp new file mode 100644 index 0000000..65dd595 --- /dev/null +++ b/arm7/source/picoLoaderBootstrap.cpp @@ -0,0 +1,39 @@ +#include "common.h" +#include +#include +#include +#include "ipcChannels.h" +#include "picoLoader7.h" +#include "picoLoaderBootstrap.h" + +typedef void (*pico_loader_7_func_t)(void); + +static volatile bool sShouldStart = false; + +static void ipcMessageHandler(u32 channel, u32 data, void* arg) +{ + if (data == 1) + { + sShouldStart = true; + } +} + +void pload_init() +{ + ipc_setChannelHandler(IPC_CHANNEL_LOADER, ipcMessageHandler, nullptr); +} + +bool pload_shouldStart() +{ + return sShouldStart; +} + +void pload_start() +{ + snd_setMasterEnable(false); + rtos_disableIrqs(); + REG_IME = 0; + auto header7 = (pload_header7_t*)0x06000000; + header7->dldiDriver = (void*)0x037F8000; + ((pico_loader_7_func_t)header7->entryPoint)(); +} \ No newline at end of file diff --git a/arm7/source/picoLoaderBootstrap.h b/arm7/source/picoLoaderBootstrap.h new file mode 100644 index 0000000..daff948 --- /dev/null +++ b/arm7/source/picoLoaderBootstrap.h @@ -0,0 +1,5 @@ +#pragma once + +void pload_init(); +bool pload_shouldStart(); +void pload_start(); diff --git a/arm9/data/NotoSansJP-Medium-10.nft2 b/arm9/data/NotoSansJP-Medium-10.nft2 new file mode 100644 index 0000000..d0c8fa6 Binary files /dev/null and b/arm9/data/NotoSansJP-Medium-10.nft2 differ diff --git a/arm9/data/NotoSansJP-Medium-11.nft2 b/arm9/data/NotoSansJP-Medium-11.nft2 new file mode 100644 index 0000000..fdde0ae Binary files /dev/null and b/arm9/data/NotoSansJP-Medium-11.nft2 differ diff --git a/arm9/data/NotoSansJP-Medium-7_5.nft2 b/arm9/data/NotoSansJP-Medium-7_5.nft2 new file mode 100644 index 0000000..94e5ade Binary files /dev/null and b/arm9/data/NotoSansJP-Medium-7_5.nft2 differ diff --git a/arm9/data/NotoSansJP-Regular-10.nft2 b/arm9/data/NotoSansJP-Regular-10.nft2 new file mode 100644 index 0000000..4bba372 Binary files /dev/null and b/arm9/data/NotoSansJP-Regular-10.nft2 differ diff --git a/arm9/gfx/backIcon.grit b/arm9/gfx/backIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/backIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/backIcon.png b/arm9/gfx/backIcon.png new file mode 100644 index 0000000..5ad0cf1 Binary files /dev/null and b/arm9/gfx/backIcon.png differ diff --git a/arm9/gfx/bannerListIcon.grit b/arm9/gfx/bannerListIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/bannerListIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/bannerListIcon.png b/arm9/gfx/bannerListIcon.png new file mode 100644 index 0000000..f85b1fd Binary files /dev/null and b/arm9/gfx/bannerListIcon.png differ diff --git a/arm9/gfx/bannerListItemBg0.grit b/arm9/gfx/bannerListItemBg0.grit new file mode 100644 index 0000000..60becf7 --- /dev/null +++ b/arm9/gfx/bannerListItemBg0.grit @@ -0,0 +1,5 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 diff --git a/arm9/gfx/bannerListItemBg0.png b/arm9/gfx/bannerListItemBg0.png new file mode 100644 index 0000000..f2bd2da Binary files /dev/null and b/arm9/gfx/bannerListItemBg0.png differ diff --git a/arm9/gfx/bannerListItemBg1.grit b/arm9/gfx/bannerListItemBg1.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/bannerListItemBg1.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/bannerListItemBg1.png b/arm9/gfx/bannerListItemBg1.png new file mode 100644 index 0000000..85dbcfd Binary files /dev/null and b/arm9/gfx/bannerListItemBg1.png differ diff --git a/arm9/gfx/bannerListItemBg2.grit b/arm9/gfx/bannerListItemBg2.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/bannerListItemBg2.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/bannerListItemBg2.png b/arm9/gfx/bannerListItemBg2.png new file mode 100644 index 0000000..2cea087 Binary files /dev/null and b/arm9/gfx/bannerListItemBg2.png differ diff --git a/arm9/gfx/bottomSheetBg.grit b/arm9/gfx/bottomSheetBg.grit new file mode 100644 index 0000000..d6fc647 --- /dev/null +++ b/arm9/gfx/bottomSheetBg.grit @@ -0,0 +1,18 @@ +# tile format +-gt + +# tile reduction by tiles, palette and hflip/vflip +-mRtpf +-ma2 + +# graphics bit depth is 4 (16 color) +-gB4 + +-p +-pn16 + +# map layout standard bg format +-mLs + +#-gzl +#-mzl \ No newline at end of file diff --git a/arm9/gfx/bottomSheetBg.png b/arm9/gfx/bottomSheetBg.png new file mode 100644 index 0000000..8432e13 Binary files /dev/null and b/arm9/gfx/bottomSheetBg.png differ diff --git a/arm9/gfx/carouselMask.grit b/arm9/gfx/carouselMask.grit new file mode 100644 index 0000000..409fe40 --- /dev/null +++ b/arm9/gfx/carouselMask.grit @@ -0,0 +1,3 @@ +-gb +-gB8 +-p! \ No newline at end of file diff --git a/arm9/gfx/carouselMask.png b/arm9/gfx/carouselMask.png new file mode 100644 index 0000000..6837562 Binary files /dev/null and b/arm9/gfx/carouselMask.png differ diff --git a/arm9/gfx/chipFilled.grit b/arm9/gfx/chipFilled.grit new file mode 100644 index 0000000..b37877e --- /dev/null +++ b/arm9/gfx/chipFilled.grit @@ -0,0 +1,5 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 \ No newline at end of file diff --git a/arm9/gfx/chipFilled.png b/arm9/gfx/chipFilled.png new file mode 100644 index 0000000..1c9d48c Binary files /dev/null and b/arm9/gfx/chipFilled.png differ diff --git a/arm9/gfx/coverflowIcon.grit b/arm9/gfx/coverflowIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/coverflowIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/coverflowIcon.png b/arm9/gfx/coverflowIcon.png new file mode 100644 index 0000000..6fcf8da Binary files /dev/null and b/arm9/gfx/coverflowIcon.png differ diff --git a/arm9/gfx/folderCover.grit b/arm9/gfx/folderCover.grit new file mode 100644 index 0000000..9fa72ea --- /dev/null +++ b/arm9/gfx/folderCover.grit @@ -0,0 +1,3 @@ +-gb +-gB8 +-p \ No newline at end of file diff --git a/arm9/gfx/folderCover.png b/arm9/gfx/folderCover.png new file mode 100644 index 0000000..9f83135 Binary files /dev/null and b/arm9/gfx/folderCover.png differ diff --git a/arm9/gfx/gamesIcon.grit b/arm9/gfx/gamesIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/gamesIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/gamesIcon.png b/arm9/gfx/gamesIcon.png new file mode 100644 index 0000000..4546d8f Binary files /dev/null and b/arm9/gfx/gamesIcon.png differ diff --git a/arm9/gfx/hGridIcon.grit b/arm9/gfx/hGridIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/hGridIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/hGridIcon.png b/arm9/gfx/hGridIcon.png new file mode 100644 index 0000000..e719b04 Binary files /dev/null and b/arm9/gfx/hGridIcon.png differ diff --git a/arm9/gfx/heartIcon.grit b/arm9/gfx/heartIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/heartIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/heartIcon.png b/arm9/gfx/heartIcon.png new file mode 100644 index 0000000..0910ae0 Binary files /dev/null and b/arm9/gfx/heartIcon.png differ diff --git a/arm9/gfx/iconButtonSelector.grit b/arm9/gfx/iconButtonSelector.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/iconButtonSelector.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/iconButtonSelector.png b/arm9/gfx/iconButtonSelector.png new file mode 100644 index 0000000..9c07318 Binary files /dev/null and b/arm9/gfx/iconButtonSelector.png differ diff --git a/arm9/gfx/iconButtonSelectorTexture.grit b/arm9/gfx/iconButtonSelectorTexture.grit new file mode 100644 index 0000000..409fe40 --- /dev/null +++ b/arm9/gfx/iconButtonSelectorTexture.grit @@ -0,0 +1,3 @@ +-gb +-gB8 +-p! \ No newline at end of file diff --git a/arm9/gfx/iconButtonSelectorTexture.png b/arm9/gfx/iconButtonSelectorTexture.png new file mode 100644 index 0000000..d9be09f Binary files /dev/null and b/arm9/gfx/iconButtonSelectorTexture.png differ diff --git a/arm9/gfx/iconCell.grit b/arm9/gfx/iconCell.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/iconCell.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/iconCell.png b/arm9/gfx/iconCell.png new file mode 100644 index 0000000..426ef89 Binary files /dev/null and b/arm9/gfx/iconCell.png differ diff --git a/arm9/gfx/iconCell2.grit b/arm9/gfx/iconCell2.grit new file mode 100644 index 0000000..60becf7 --- /dev/null +++ b/arm9/gfx/iconCell2.grit @@ -0,0 +1,5 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 diff --git a/arm9/gfx/iconCell2.png b/arm9/gfx/iconCell2.png new file mode 100644 index 0000000..dfeacf0 Binary files /dev/null and b/arm9/gfx/iconCell2.png differ diff --git a/arm9/gfx/iconCell3.grit b/arm9/gfx/iconCell3.grit new file mode 100644 index 0000000..60becf7 --- /dev/null +++ b/arm9/gfx/iconCell3.grit @@ -0,0 +1,5 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 diff --git a/arm9/gfx/iconCell3.png b/arm9/gfx/iconCell3.png new file mode 100644 index 0000000..91026fa Binary files /dev/null and b/arm9/gfx/iconCell3.png differ diff --git a/arm9/gfx/largeDSCardIcon.grit b/arm9/gfx/largeDSCardIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/largeDSCardIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/largeDSCardIcon.png b/arm9/gfx/largeDSCardIcon.png new file mode 100644 index 0000000..6e16e96 Binary files /dev/null and b/arm9/gfx/largeDSCardIcon.png differ diff --git a/arm9/gfx/largeFileIcon.grit b/arm9/gfx/largeFileIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/largeFileIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/largeFileIcon.png b/arm9/gfx/largeFileIcon.png new file mode 100644 index 0000000..13b37b7 Binary files /dev/null and b/arm9/gfx/largeFileIcon.png differ diff --git a/arm9/gfx/largeFolderIcon.grit b/arm9/gfx/largeFolderIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/largeFolderIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/largeFolderIcon.png b/arm9/gfx/largeFolderIcon.png new file mode 100644 index 0000000..ee5d6b2 Binary files /dev/null and b/arm9/gfx/largeFolderIcon.png differ diff --git a/arm9/gfx/listIcon.grit b/arm9/gfx/listIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/listIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/listIcon.png b/arm9/gfx/listIcon.png new file mode 100644 index 0000000..6b21634 Binary files /dev/null and b/arm9/gfx/listIcon.png differ diff --git a/arm9/gfx/mainBg.grit b/arm9/gfx/mainBg.grit new file mode 100644 index 0000000..3a8e213 --- /dev/null +++ b/arm9/gfx/mainBg.grit @@ -0,0 +1,13 @@ +# tile format +-gt + +# tile reduction +-mR8 + +# graphics bit depth is 8 (256 color) +-gB8 + +-p! + +# map layout standard bg format +-mLs \ No newline at end of file diff --git a/arm9/gfx/mainBg.png b/arm9/gfx/mainBg.png new file mode 100644 index 0000000..26f640e Binary files /dev/null and b/arm9/gfx/mainBg.png differ diff --git a/arm9/gfx/moviesIcon.grit b/arm9/gfx/moviesIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/moviesIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/moviesIcon.png b/arm9/gfx/moviesIcon.png new file mode 100644 index 0000000..8e00a59 Binary files /dev/null and b/arm9/gfx/moviesIcon.png differ diff --git a/arm9/gfx/musicIcon.grit b/arm9/gfx/musicIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/musicIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/musicIcon.png b/arm9/gfx/musicIcon.png new file mode 100644 index 0000000..c9848f1 Binary files /dev/null and b/arm9/gfx/musicIcon.png differ diff --git a/arm9/gfx/picturesIcon.grit b/arm9/gfx/picturesIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/picturesIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/picturesIcon.png b/arm9/gfx/picturesIcon.png new file mode 100644 index 0000000..a4f442b Binary files /dev/null and b/arm9/gfx/picturesIcon.png differ diff --git a/arm9/gfx/recentIcon.grit b/arm9/gfx/recentIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/recentIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/recentIcon.png b/arm9/gfx/recentIcon.png new file mode 100644 index 0000000..483f1c5 Binary files /dev/null and b/arm9/gfx/recentIcon.png differ diff --git a/arm9/gfx/scrim.grit b/arm9/gfx/scrim.grit new file mode 100644 index 0000000..d159148 --- /dev/null +++ b/arm9/gfx/scrim.grit @@ -0,0 +1,18 @@ +# tile format +-gt + +# tile reduction by tiles, palette and hflip/vflip +-mRtpf +-mp1 + +# graphics bit depth is 4 (16 color) +-gB4 + +-p +-pn16 + +# map layout standard bg format +-mLs + +#-gzl +#-mzl \ No newline at end of file diff --git a/arm9/gfx/scrim.png b/arm9/gfx/scrim.png new file mode 100644 index 0000000..ef058c1 Binary files /dev/null and b/arm9/gfx/scrim.png differ diff --git a/arm9/gfx/settingsIcon.grit b/arm9/gfx/settingsIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/settingsIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/settingsIcon.png b/arm9/gfx/settingsIcon.png new file mode 100644 index 0000000..4f2ae9c Binary files /dev/null and b/arm9/gfx/settingsIcon.png differ diff --git a/arm9/gfx/smallHeartIcon.grit b/arm9/gfx/smallHeartIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/smallHeartIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/smallHeartIcon.png b/arm9/gfx/smallHeartIcon.png new file mode 100644 index 0000000..c56fdce Binary files /dev/null and b/arm9/gfx/smallHeartIcon.png differ diff --git a/arm9/gfx/smallHeartIconFilled.grit b/arm9/gfx/smallHeartIconFilled.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/smallHeartIconFilled.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/smallHeartIconFilled.png b/arm9/gfx/smallHeartIconFilled.png new file mode 100644 index 0000000..40134f9 Binary files /dev/null and b/arm9/gfx/smallHeartIconFilled.png differ diff --git a/arm9/gfx/sortNameAscendingIcon.grit b/arm9/gfx/sortNameAscendingIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/sortNameAscendingIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/sortNameAscendingIcon.png b/arm9/gfx/sortNameAscendingIcon.png new file mode 100644 index 0000000..87340c4 Binary files /dev/null and b/arm9/gfx/sortNameAscendingIcon.png differ diff --git a/arm9/gfx/sortNameDescendingIcon.grit b/arm9/gfx/sortNameDescendingIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/sortNameDescendingIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/sortNameDescendingIcon.png b/arm9/gfx/sortNameDescendingIcon.png new file mode 100644 index 0000000..dc1e226 Binary files /dev/null and b/arm9/gfx/sortNameDescendingIcon.png differ diff --git a/arm9/gfx/splashTop.grit b/arm9/gfx/splashTop.grit new file mode 100644 index 0000000..f6ca668 --- /dev/null +++ b/arm9/gfx/splashTop.grit @@ -0,0 +1,6 @@ +-gt +-gB8 +-mRtpf +-mp0 +-p +-mLs \ No newline at end of file diff --git a/arm9/gfx/splashTop.png b/arm9/gfx/splashTop.png new file mode 100644 index 0000000..ef2f32c Binary files /dev/null and b/arm9/gfx/splashTop.png differ diff --git a/arm9/gfx/unknownCover.grit b/arm9/gfx/unknownCover.grit new file mode 100644 index 0000000..9fa72ea --- /dev/null +++ b/arm9/gfx/unknownCover.grit @@ -0,0 +1,3 @@ +-gb +-gB8 +-p \ No newline at end of file diff --git a/arm9/gfx/unknownCover.png b/arm9/gfx/unknownCover.png new file mode 100644 index 0000000..7759eb4 Binary files /dev/null and b/arm9/gfx/unknownCover.png differ diff --git a/arm9/gfx/unknownIcon.grit b/arm9/gfx/unknownIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/unknownIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/unknownIcon.png b/arm9/gfx/unknownIcon.png new file mode 100644 index 0000000..f842904 Binary files /dev/null and b/arm9/gfx/unknownIcon.png differ diff --git a/arm9/gfx/vGridIcon.grit b/arm9/gfx/vGridIcon.grit new file mode 100644 index 0000000..9392dda --- /dev/null +++ b/arm9/gfx/vGridIcon.grit @@ -0,0 +1,7 @@ +# tile format +-gt + +# graphics bit depth is 4 (16 color) +-gB4 + +-p! \ No newline at end of file diff --git a/arm9/gfx/vGridIcon.png b/arm9/gfx/vGridIcon.png new file mode 100644 index 0000000..e6c11e4 Binary files /dev/null and b/arm9/gfx/vGridIcon.png differ diff --git a/arm9/source/App.cpp b/arm9/source/App.cpp new file mode 100644 index 0000000..1099f68 --- /dev/null +++ b/arm9/source/App.cpp @@ -0,0 +1,519 @@ +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "animation/Animator.h" +#include "gui/materialDesign.h" +#include "themes/material/MaterialColorSchemeFactory.h" +#include "core/math/ColorConverter.h" +#include "core/math/RgbMixer.h" +#include "gui/GraphicsContext.h" +#include "romBrowser/views/ChipView.h" +#include "picoLoaderBootstrap.h" +#include "romBrowser/DisplayMode/RomBrowserDisplayModeFactory.h" +#include "romBrowser/Theme/Material/MaterialThemeFileIconFactory.h" +#include "romBrowser/views/NdsGameDetailsBottomSheetView.h" +#include "romBrowser/views/DisplaySettingsBottomSheetView.h" +#include "bgm/AudioStreamPlayer.h" +#include "bgm/BgmService.h" +#include "themes/ThemeInfoFactory.h" +#include "themes/ThemeFactory.h" +#include "gui/Gx.h" +#include "splashTop.h" +#include "App.h" + +#define SPLASH_FRAMES 44 + +App::App(IAppSettingsService& appSettingsService, IBgmService& bgmService) + : _mainObjPltt(GFX_PLTT_OBJ_MAIN) + , _mainObjVram(GFX_OBJ_MAIN) + , _mainObjDialogVram(GFX_OBJ_MAIN, 128 * 1024) + , _subObjVram(GFX_OBJ_SUB) + , _textureVram((vu16*)0x06860000) + , _texturePaletteVram((vu16*)0x6880000) + , _mainVramContext(nullptr, &_mainObjVram, &_textureVram, &_texturePaletteVram) + , _subVramContext(nullptr, &_subObjVram, nullptr, nullptr) + , _appSettingsService(appSettingsService) + , _bgmService(bgmService) + , _inputProvider(&_inputSource) + , _inputRepeater(&_inputProvider, + InputKey::DpadLeft | InputKey::DpadRight | InputKey::DpadUp | InputKey::DpadDown, + 25, 8) + , _romBrowserController(&appSettingsService, &_ioTaskQueue, &_bgTaskQueue) + , _displaySettingsBottomSheetViewModel(&_romBrowserController) + , _romBrowserBottomScreenViewModel(&_romBrowserController) + , _dialogPresenter(&_focusManager, &_mainObjDialogVram) { } + +void App::InitVramMapping() const +{ + mem_setVramAMapping(MEM_VRAM_AB_TEX_SLOT_1); + mem_setVramBMapping(MEM_VRAM_AB_MAIN_OBJ_00000); + mem_setVramCMapping(MEM_VRAM_C_SUB_BG_00000); + mem_setVramDMapping(MEM_VRAM_D_TEX_SLOT_0); + mem_setVramEMapping(MEM_VRAM_E_TEX_PLTT_SLOT_0123); + mem_setVramFMapping(MEM_VRAM_FG_MAIN_BG_00000); + mem_setVramGMapping(MEM_VRAM_FG_MAIN_BG_04000); + mem_setVramHMapping(MEM_VRAM_H_SUB_BG_EXT_PLTT_SLOT_0123); + mem_setVramIMapping(MEM_VRAM_I_SUB_OBJ_00000); +} + +void App::DisplaySplashScreen() const +{ + dma_ntrCopy32(3, splashTopTiles, GFX_BG_SUB, splashTopTilesLen); + dma_ntrCopy32(3, splashTopMap, (u8*)GFX_BG_SUB + 0x3000, splashTopMapLen); + mem_setVramHMapping(MEM_VRAM_H_LCDC); + dma_ntrCopy32(3, splashTopPal, (void*)0x0689A000, splashTopPalLen); + mem_setVramHMapping(MEM_VRAM_H_SUB_BG_EXT_PLTT_SLOT_0123); + + VBlank::Wait(); + + sys_setMainEngineToBottomScreen(); + REG_DISPCNT_SUB = 0x40211015; + REG_BG1HOFS_SUB = 0; + REG_BG1VOFS_SUB = 0; + REG_BG1CNT_SUB = 0x0680; + REG_DISPCNT_SUB |= 1 << 9; + REG_BLDCNT_SUB = 0x3D42; + REG_BLDALPHA_SUB = 0x10; + REG_MASTER_BRIGHT_SUB = 0; +} + +void App::LoadTheme() +{ + ThemeInfoFactory themeInfoFactory; + auto themeInfo = themeInfoFactory.CreateFromThemeFolder(_appSettingsService.GetAppSettings().theme); + if (!themeInfo) + { + LOG_DEBUG("Failed to load theme '%s'. Using fallback theme.\n", _appSettingsService.GetAppSettings().theme.GetString()); + themeInfo = themeInfoFactory.CreateFallbackTheme(); + } + _theme = ThemeFactory().CreateFromThemeInfo(themeInfo.get()); + themeInfo.reset(); + _theme->LoadRomBrowserResources(_mainVramContext, _subVramContext); + _topBackground = _theme->CreateRomBrowserTopBackground(); + _topBackground->LoadResources(*_theme, _subVramContext); + _bottomBackground = _theme->CreateRomBrowserBottomBackground(); + _bottomBackground->LoadResources(*_theme, _mainVramContext); + + _materialThemeFileIconFactory = std::make_unique( + &_theme->GetMaterialColorScheme(), _theme->GetFontRepository()); +} + +void App::VCountIrq() +{ + _mainObjPltt.VCount(); +} + +void App::Run() +{ + InitVramMapping(); + DisplaySplashScreen(); + gx_init(); + + _chipViewVram = ChipView::UploadGraphics(_mainObjVram); + _iconButtonViewVram = IconButton2DView::UploadGraphics(_mainObjVram); + + mem_setVramEMapping(MEM_VRAM_E_LCDC); + _rgb6Palette.UploadGraphics(_mainVramContext); + mem_setVramEMapping(MEM_VRAM_E_TEX_PLTT_SLOT_0123); + + _dialogPresenter.InitVram(); + + LoadTheme(); + + _ioTaskQueue.StartThread(1, _ioTaskThreadStack, sizeof(_ioTaskThreadStack)); + _bgTaskQueue.StartThread(2, _bgTaskThreadStack, sizeof(_bgTaskThreadStack)); + + StoreVramState(_vramStateBeforeMakeBottomScreenView); + + _romBrowserBottomScreenView = std::make_unique( + &_romBrowserBottomScreenViewModel, + RomBrowserDisplayModeFactory().GetRomBrowserDisplayMode( + _romBrowserController.GetRomBrowserDisplaySettings().layout), + _materialThemeFileIconFactory.get(), + _theme->GetRomBrowserViewFactory(), + &_vblankTextureLoader); + _romBrowserBottomScreenView->InitVram(_mainVramContext); + + StoreVramState(_vramStateAfterMakeBottomScreenView); + + const auto& materialColorScheme = _theme->GetMaterialColorScheme(); + + auto scrimBlendColor = Rgb<8, 8, 8>( + materialColorScheme.inverseOnSurface.r + (materialColorScheme.scrim.r - materialColorScheme.inverseOnSurface.r) * 5 / 16, + materialColorScheme.inverseOnSurface.g + (materialColorScheme.scrim.g - materialColorScheme.inverseOnSurface.g) * 5 / 16, + materialColorScheme.inverseOnSurface.b + (materialColorScheme.scrim.b - materialColorScheme.inverseOnSurface.b) * 5 / 16); + + RgbMixer::MakeGradientPalette((u16*)GFX_PLTT_BG_MAIN, scrimBlendColor, materialColorScheme.GetColor(md::sys::color::surfaceContainerLow)); + + GFX_PLTT_BG_MAIN[0] = ColorConverter::ToGBGR565(materialColorScheme.inverseOnSurface); + GFX_PLTT_BG_MAIN[31] = ColorConverter::ToGBGR565(materialColorScheme.scrim); + REG_DISPCNT = 0x211F1B; + REG_BG0HOFS = 0; + REG_BG0VOFS = 0; + REG_BG0CNT = 3; + + Gx::MtxMode(GX_MTX_MODE_PROJECTION); + mtx43_t orthoMtx = + { + 2048, 0, 0, + 0, -21845, 0, + 0, 0, 4096 >> 5, + -4096, 4096, 0 + }; + Gx::MtxLoad43(&orthoMtx); + + _vcountIrqStarted = false; + rtos_disableIrqMask(RTOS_IRQ_VCOUNT); + rtos_setIrqFunc(RTOS_IRQ_VCOUNT, [] (u32 mask) { ((App*)gProcessManager.GetRunningProcess())->VCountIrq(); }); + + LOG_DEBUG("Amount of main obj vram used: %d\n", _mainObjVram.GetState()); + + _ioTaskQueue.Enqueue([this] (const vu8& cancelRequested) + { + _bgmService.StartBgmFromConfig(); + return TaskResult::Completed(); + }); + + _fadeAnimator = Animator(16, 0, 16, &md::sys::motion::easing::linear); + + MainLoop(); + + _bgmService.StopBgm(); + rtos_disableIrqMask(RTOS_IRQ_VCOUNT); + rtos_setIrqFunc(RTOS_IRQ_VCOUNT, nullptr); +} + +void App::MainLoop() +{ + bool fadeIn = true; + int fadeWaitFrames = SPLASH_FRAMES; + while (true) + { + Update(); + Draw(); + VBlank::Wait(); + VBlank(); + if (_exit) + { + bool fadeComplete = _fadeAnimator.Update(); + REG_MASTER_BRIGHT = 0x4000 | _fadeAnimator.GetValue(); + REG_MASTER_BRIGHT_SUB = 0x4000 | _fadeAnimator.GetValue(); + if (fadeComplete) + { + break; + } + } + else if (fadeIn) + { + if (fadeWaitFrames) + { + fadeWaitFrames--; + REG_BLDALPHA_SUB = 16; + REG_MASTER_BRIGHT = 0x4010; + } + else + { + bool fadeComplete = _fadeAnimator.Update(); + if (fadeComplete) + { + fadeIn = false; + REG_BLDCNT_SUB = 0; + REG_DISPCNT_SUB &= ~(1 << 9); + REG_MASTER_BRIGHT = 0; + } + else + { + int fade = _fadeAnimator.GetValue(); + REG_BLDALPHA_SUB = ((16 - fade) << 8) | fade; + REG_MASTER_BRIGHT = 0x4000 | fade; + } + } + } + } +} + +void App::Exit() +{ + _fadeAnimator.Goto(16, 16, &md::sys::motion::easing::linear); + _exit = true; +} + +void App::HandleTrigger(RomBrowserStateTrigger trigger, RomBrowserState newState) +{ + switch (trigger) + { + case RomBrowserStateTrigger::None: + case RomBrowserStateTrigger::Launch: + { + break; + } + case RomBrowserStateTrigger::ShowGameInfo: + { + HandleShowGameInfoTrigger(); + break; + } + case RomBrowserStateTrigger::HideGameInfo: + { + HandleHideGameInfoTrigger(); + break; + } + case RomBrowserStateTrigger::ShowDisplaySettings: + { + HandleShowDisplaySettingsTrigger(); + break; + } + case RomBrowserStateTrigger::HideDisplaySettings: + { + HandleHideDisplaySettingsTrigger(); + break; + } + case RomBrowserStateTrigger::Navigate: + { + HandleNavigateTrigger(); + break; + } + case RomBrowserStateTrigger::FolderLoadDone: + { + HandleFolderLoadDoneTrigger(); + break; + } + case RomBrowserStateTrigger::ChangeDisplayMode: + { + _changeDisplayMode = true; + break; + } + } +} + +void App::HandleShowGameInfoTrigger() +{ + auto gameInfoDialog = std::make_unique( + &_romBrowserController, &_theme->GetMaterialColorScheme(), _theme->GetFontRepository()); + gameInfoDialog->SetGraphics(_chipViewVram); + _dialogPresenter.ShowDialog(std::move(gameInfoDialog)); +} + +void App::HandleHideGameInfoTrigger() +{ + _dialogPresenter.CloseDialog(); + if (!_dialogPresenter.GetOldFocus()) + _romBrowserBottomScreenView->Focus(_focusManager); +} + +void App::HandleShowDisplaySettingsTrigger() +{ + auto displaySettingsDialog = std::make_unique( + &_displaySettingsBottomSheetViewModel, &_theme->GetMaterialColorScheme(), _theme->GetFontRepository()); + displaySettingsDialog->SetGraphics(_iconButtonViewVram); + _dialogPresenter.ShowDialog(std::move(displaySettingsDialog)); +} + +void App::HandleHideDisplaySettingsTrigger() +{ + _dialogPresenter.CloseDialog(); + if (!_dialogPresenter.GetOldFocus()) + _romBrowserBottomScreenView->Focus(_focusManager); +} + +void App::HandleNavigateTrigger() +{ + if (!_romBrowserBottomScreenView->IsAppBarFocused(_focusManager)) + _focusManager.Unfocus(); +} + +void App::HandleFolderLoadDoneTrigger() +{ + _romBrowserTopScreenView.reset(); + RestoreVramState(_vramStateAfterMakeBottomScreenView); + auto displayMode = RomBrowserDisplayModeFactory().GetRomBrowserDisplayMode( + _romBrowserController.GetRomBrowserDisplaySettings().layout); + _romBrowserTopScreenView = std::make_unique( + _romBrowserController.GetRomBrowserViewModel(), + displayMode, + _materialThemeFileIconFactory.get(), + _theme->GetRomBrowserViewFactory()); + _romBrowserTopScreenView->InitVram(_subVramContext); + _romBrowserBottomScreenView->RomBrowserViewModelInvalidated(_mainVramContext); + if (!_focusManager.GetCurrentFocus()) + _romBrowserBottomScreenView->Focus(_focusManager); +} + +void App::HandleChangeDisplayModeTrigger(RomBrowserState newState) +{ + _dialogPresenter.ClearOldFocus(); + RestoreVramState(_vramStateBeforeMakeBottomScreenView); + auto displayMode = RomBrowserDisplayModeFactory().GetRomBrowserDisplayMode( + _romBrowserController.GetRomBrowserDisplaySettings().layout); + _romBrowserBottomScreenView = std::make_unique( + &_romBrowserBottomScreenViewModel, + displayMode, + _materialThemeFileIconFactory.get(), + _theme->GetRomBrowserViewFactory(), + &_vblankTextureLoader); + _romBrowserBottomScreenView->InitVram(_mainVramContext); + StoreVramState(_vramStateAfterMakeBottomScreenView); + _romBrowserTopScreenView = std::make_unique( + _romBrowserController.GetRomBrowserViewModel(), + displayMode, + _materialThemeFileIconFactory.get(), + _theme->GetRomBrowserViewFactory()); + _romBrowserTopScreenView->InitVram(_subVramContext); + _romBrowserBottomScreenView->RomBrowserViewModelInvalidated(_mainVramContext); + if (newState == RomBrowserState::Browser) + _romBrowserBottomScreenView->Focus(_focusManager); +} + +bool App::IsRomBrowserVisible() const +{ + const auto& stateMachine = _romBrowserController.GetStateMachine(); + auto curState = stateMachine.GetCurrentState(); + return curState == RomBrowserState::Browser + || curState == RomBrowserState::GameInfo + || curState == RomBrowserState::DisplaySettings + || curState == RomBrowserState::Launching; +} + +void App::Update() +{ + const auto& stateMachine = _romBrowserController.GetStateMachine(); + _romBrowserController.Update(); + auto curState = stateMachine.GetCurrentState(); + if (_changeDisplayMode) + { + HandleChangeDisplayModeTrigger(curState); + _changeDisplayMode = false; + } + if (stateMachine.HasStateChanged()) + { + HandleTrigger(stateMachine.GetLastTrigger(), curState); + } + + bool isRomBrowserVisible = IsRomBrowserVisible(); + if (isRomBrowserVisible && !_exit && curState != RomBrowserState::Launching) + { + _focusManager.Update(_inputRepeater); + } + + if (_topBackground) + _topBackground->Update(); + if (_bottomBackground) + _bottomBackground->Update(); + + _dialogPresenter.Update(); + + _romBrowserBottomScreenView->Update(); + if (isRomBrowserVisible) + { + _romBrowserTopScreenView->Update(); + _romBrowserController.GetRomBrowserViewModel()->SetIconFrameCounter( + _romBrowserController.GetRomBrowserViewModel()->GetIconFrameCounter() + 1); + } +} + +void App::Draw() +{ + gx_reset(); + Gx::Viewport(0, 0, 255, 191); + Gx::MtxMode(GX_MTX_MODE_POSITION_VECTOR); + Gx::MtxIdentity(); + + GraphicsContext mainGraphicsContext + { + &_mainOam, + &_mainObjPltt, + &_rgb6Palette + }; + GraphicsContext subGraphicsContext + { + &_subOam, + &_subObjPltt, + nullptr + }; + + _mainOam.Clear(); + _subOam.Clear(); + _mainObjPltt.Reset(); + _subObjPltt.Reset(); + mainGraphicsContext.SetPriority(3); + subGraphicsContext.SetPriority(2); + + if (_topBackground) + _topBackground->Draw(subGraphicsContext); + if (_bottomBackground) + _bottomBackground->Draw(mainGraphicsContext); + + if (!_changeDisplayMode && IsRomBrowserVisible()) + { + _romBrowserTopScreenView->Draw(subGraphicsContext); + } + + _dialogPresenter.ApplyClipArea(mainGraphicsContext); + if (!_changeDisplayMode) + { + _romBrowserBottomScreenView->Draw(mainGraphicsContext); + } + mainGraphicsContext.ResetClipArea(); + + _dialogPresenter.Draw(mainGraphicsContext); + + _mainObjPltt.EndOfFrame(); + + Gx::SwapBuffers(GX_XLU_SORT_MANUAL, GX_DEPTH_MODE_Z); +} + +void App::VBlank() +{ + dma_ntrStopDirect(0); // stop hblank dma + _inputProvider.Sample(); + _inputRepeater.Update(); + _mainOam.Apply(GFX_OAM_MAIN); + _subOam.Apply(GFX_OAM_SUB); + _subObjPltt.Apply(GFX_PLTT_OBJ_SUB); + + if (!_vcountIrqStarted) + { + rtos_ackIrqMask(RTOS_IRQ_VCOUNT); + rtos_enableIrqMask(RTOS_IRQ_VCOUNT); + _vcountIrqStarted = true; + } + _mainObjPltt.VBlank(); + + if (_topBackground) + _topBackground->VBlank(); + if (_bottomBackground) + _bottomBackground->VBlank(); + + _dialogPresenter.VBlank(); + + if (IsRomBrowserVisible()) + { + _romBrowserTopScreenView->VBlank(); + } + _romBrowserBottomScreenView->VBlank(); + + _vblankTextureLoader.VBlank(); +} + +void App::StoreVramState(VramState& vramState) const +{ + vramState._mainObjVramState = _mainObjVram.GetState(); + vramState._texVramState = _textureVram.GetState(); + vramState._texPlttVramState = _texturePaletteVram.GetState(); + vramState._subObjVramState = _subObjVram.GetState(); +} + +void App::RestoreVramState(const VramState& vramState) +{ + _mainObjVram.SetState(vramState._mainObjVramState); + _textureVram.SetState(vramState._texVramState); + _texturePaletteVram.SetState(vramState._texPlttVramState); + _subObjVram.SetState(vramState._subObjVramState); +} diff --git a/arm9/source/App.h b/arm9/source/App.h new file mode 100644 index 0000000..fe7ced3 --- /dev/null +++ b/arm9/source/App.h @@ -0,0 +1,129 @@ +#pragma once +#include "common.h" +#include +#include "services/settings/IAppSettingsService.h" +#include "bgm/IBgmService.h" +#include "services/process/IProcess.h" +#include "gui/SimplePaletteManager.h" +#include "gui/AdvancedPaletteManager.h" +#include "gui/OamManager.h" +#include "gui/VramContext.h" +#include "gui/AscendingStackVramManager.h" +#include "gui/DescendingStackVramManager.h" +#include "material/scheme/scheme.h" +#include "gui/input/PadInputSource.h" +#include "gui/input/SampledInputProvider.h" +#include "gui/input/InputRepeater.h" +#include "gui/VBlankTextureLoader.h" +#include "gui/Rgb6Palette.h" +#include "core/task/TaskQueue.h" +#include "themes/material/MaterialColorScheme.h" +#include "romBrowser/viewModels/RomBrowserBottomScreenViewModel.h" +#include "romBrowser/viewModels/DisplaySettingsViewModel.h" +#include "romBrowser/views/RomBrowserBottomScreenView.h" +#include "romBrowser/views/RomBrowserTopScreenView.h" +#include "romBrowser/views/IconButton2DView.h" +#include "romBrowser/views/ChipView.h" +#include "romBrowser/Theme/Material/MaterialThemeFileIconFactory.h" +#include "romBrowser/RomBrowserController.h" +#include "DialogPresenter.h" +#include "themes/ITheme.h" +#include "core/SharedPtr.h" +#include "animation/Animator.h" + +class alignas(32) App : public IProcess +{ +public: + App(IAppSettingsService& appSettingsService, IBgmService& bgmService); + + void Run() override; + void Exit() override; + +private: + struct VramState + { + u32 _subObjVramState; + u32 _mainObjVramState; + u32 _texVramState; + u32 _texPlttVramState; + }; + + AdvancedPaletteManager<64> _mainObjPltt; + OamManager _mainOam; + AscendingStackVramManager _mainObjVram; + DescendingStackVramManager _mainObjDialogVram; + OamManager _subOam; + SimplePaletteManager _subObjPltt; + AscendingStackVramManager _subObjVram; + AscendingStackVramManager _textureVram; + AscendingStackVramManager _texturePaletteVram; + VBlankTextureLoader _vblankTextureLoader; + VramContext _mainVramContext; + VramContext _subVramContext; + Rgb6Palette _rgb6Palette; + Animator _fadeAnimator; + + TaskQueue<32, 32> _ioTaskQueue; + u32 _ioTaskThreadStack[2048 / 4]; + TaskQueue<32, 32> _bgTaskQueue; + u32 _bgTaskThreadStack[2048 / 4]; + + std::unique_ptr _theme; + std::unique_ptr _topBackground; + std::unique_ptr _bottomBackground; + + IAppSettingsService& _appSettingsService; + IBgmService& _bgmService; + volatile bool _exit = false; + + PadInputSource _inputSource; + SampledInputProvider _inputProvider; + InputRepeater _inputRepeater; + + std::unique_ptr _romBrowserBottomScreenView; + std::unique_ptr _romBrowserTopScreenView; + + RomBrowserController _romBrowserController; + + DisplaySettingsViewModel _displaySettingsBottomSheetViewModel; + + FocusManager _focusManager; + + std::unique_ptr _materialThemeFileIconFactory; + + RomBrowserBottomScreenViewModel _romBrowserBottomScreenViewModel; + + DialogPresenter _dialogPresenter; + + VramState _vramStateBeforeMakeBottomScreenView; + VramState _vramStateAfterMakeBottomScreenView; + bool _changeDisplayMode = false; + + ChipView::VramToken _chipViewVram; + IconButton2DView::VramToken _iconButtonViewVram; + + bool _vcountIrqStarted = false; + + void InitVramMapping() const; + void DisplaySplashScreen() const; + void LoadTheme(); + void VCountIrq(); + void HandleTrigger(RomBrowserStateTrigger trigger, RomBrowserState newState); + void HandleShowGameInfoTrigger(); + void HandleHideGameInfoTrigger(); + void HandleShowDisplaySettingsTrigger(); + void HandleHideDisplaySettingsTrigger(); + void HandleNavigateTrigger(); + void HandleFolderLoadDoneTrigger(); + void HandleChangeDisplayModeTrigger(RomBrowserState newState); + + bool IsRomBrowserVisible() const; + + void MainLoop(); + void Update(); + void Draw(); + void VBlank(); + + void StoreVramState(VramState& vramState) const; + void RestoreVramState(const VramState& vramState); +}; \ No newline at end of file diff --git a/arm9/source/DialogPresenter.cpp b/arm9/source/DialogPresenter.cpp new file mode 100644 index 0000000..263ea25 --- /dev/null +++ b/arm9/source/DialogPresenter.cpp @@ -0,0 +1,155 @@ +#include "common.h" +#include +#include +#include "gui/StackVramManager.h" +#include "gui/VramContext.h" +#include "bottomSheetBg.h" +#include "scrim.h" +#include "gui/materialDesign.h" +#include "gui/GraphicsContext.h" +#include "DialogPresenter.h" + +DialogPresenter::DialogPresenter(FocusManager* focusManager, StackVramManager* vramManager) + : _focusManager(focusManager), _vramManager(vramManager) + , _scrimAnimator(0), _yAnimator(192) +{ + _baseVramState = _vramManager->GetState(); +} + +void DialogPresenter::ShowDialog(std::unique_ptr dialog) +{ + if (!_nextDialog) + _nextDialog = std::move(dialog); +} + +void DialogPresenter::CloseDialog() +{ + if (!_currentDialog || _curState != State::BottomSheetVisible) + return; + + _newState = State::BottomSheetClosing; +} + +void DialogPresenter::Update() +{ + if (_curState != _newState) + { + _curState = _newState; + switch (_curState) + { + case State::BottomSheetVisible: + { + _scrimAnimator.Goto(5, md::sys::motion::duration::short2, + &md::sys::motion::easing::linear); + _yAnimator.Goto(32, md::sys::motion::duration::long2, + &md::sys::motion::easing::emphasizedDecelerate); + _oldFocus = _focusManager->GetCurrentFocus(); + _currentDialog->Focus(*_focusManager); + break; + } + case State::BottomSheetClosing: + { + _scrimAnimator.Goto(0, md::sys::motion::duration::short4, + &md::sys::motion::easing::emphasizedAccelerate); + _yAnimator.Goto(192, md::sys::motion::duration::short4, + &md::sys::motion::easing::emphasizedAccelerate); + if (_oldFocus) + { + _focusManager->Focus(_oldFocus); + _oldFocus = nullptr; + } + break; + } + default: + { + break; + } + } + } + switch (_curState) + { + case State::Idle: + { + if (!_currentDialog && _nextDialog) + { + _currentDialog = std::move(_nextDialog); + _initVram = true; + _newState = State::BottomSheetVisible; + } + break; + } + case State::BottomSheetVisible: + { + if (!_yAnimator.IsFinished()) + _yAnimator.Update(); + break; + } + case State::BottomSheetClosing: + { + if (!_yAnimator.IsFinished()) + { + _yAnimator.Update(); + } + else + { + _newState = State::Idle; + _currentDialog.reset(); + } + break; + } + } + if (_currentDialog) + { + _currentDialog->SetPosition(_currentDialog->GetPosition().x, _yAnimator.GetValue()); + _currentDialog->Update(); + } +} + +void DialogPresenter::ApplyClipArea(GraphicsContext& graphicsContext) const +{ + if (_currentDialog) + { + graphicsContext.SetClipArea(_currentDialog->GetFullyCoveredArea(), true); + } +} + +void DialogPresenter::VBlank() +{ + REG_BG1VOFS = -_yAnimator.GetValue(); + + if (_initVram && _currentDialog) + { + _vramManager->SetState(_baseVramState); + _currentDialog->InitVram(VramContext(nullptr, _vramManager, nullptr, nullptr)); + _initVram = false; + } + + if (_currentDialog) + _currentDialog->VBlank(); + + if (!_scrimAnimator.IsFinished()) + { + _scrimAnimator.Update(); + int scrimBlend = _scrimAnimator.GetValue(); + REG_BLDALPHA = ((16 - scrimBlend) << 8) | scrimBlend; + } +} + +void DialogPresenter::InitVram() +{ + dma_ntrCopy32(3, scrimTiles, (vu8*)BG_GFX, scrimTilesLen); + dma_ntrCopy32(3, bottomSheetBgTiles, (vu8*)BG_GFX + 64, bottomSheetBgTilesLen); + dma_ntrCopy32(3, bottomSheetBgMap, (vu8*)BG_GFX + 0x4000, bottomSheetBgMapLen); + dma_ntrCopy32(3, scrimMap, (vu8*)BG_GFX + 0x5000, scrimMapLen); + + REG_BG1CNT = BG_32x64 | BG_PRIORITY_1 | BG_COLOR_16 | BG_MAP_BASE(8) | BG_TILE_BASE(0); + REG_BG1HOFS = 0; + REG_BG1VOFS = 0; + + REG_BG2CNT = BG_32x32 | BG_PRIORITY_2 | BG_COLOR_16 | BG_MAP_BASE(10) | BG_TILE_BASE(0); + REG_BG2HOFS = 0; + REG_BG2VOFS = 0; + + REG_BLDCNT = 0x3944; + REG_BLDALPHA = (16 << 8) | 0; +} \ No newline at end of file diff --git a/arm9/source/DialogPresenter.h b/arm9/source/DialogPresenter.h new file mode 100644 index 0000000..7a22c03 --- /dev/null +++ b/arm9/source/DialogPresenter.h @@ -0,0 +1,76 @@ +#pragma once +#include +#include "animation/Animator.h" +#include "gui/views/DialogView.h" + +class StackVramManager; +class FocusManager; + +/// @brief Class for displaying dialogs. +class DialogPresenter +{ +public: + DialogPresenter(FocusManager* focusManager, StackVramManager* vramManager); + + /// @brief Requests to show the given dialog. + /// @param dialog The dialog to show. + void ShowDialog(std::unique_ptr dialog); + + /// @brief Closes the current dialog. + void CloseDialog(); + + /// @brief Updates the dialog presenter. + void Update(); + + /// @brief Applies the clip area of the currently displayed dialog, + /// or does nothing if no dialog is being shown. + /// @param graphicsContext The graphics context to apply to. + void ApplyClipArea(GraphicsContext& graphicsContext) const; + + /// @brief If a dialog is currently being shown, draws the dialog. + /// @param graphicsContext The graphics context to use. + void Draw(GraphicsContext& graphicsContext) + { + if (_currentDialog) + _currentDialog->Draw(graphicsContext); + } + + /// @brief Performs vblank processes for the displayed dialog. + void VBlank(); + + /// @brief Initializes vram that is needed for showing dialogs. + void InitVram(); + + /// @brief Clears the focus that was stored when a dialog was opened. + void ClearOldFocus() + { + _oldFocus = nullptr; + } + + /// @brief Gets the focus that was stored when a dialog was opened. + /// @return The view that was focused when the current dialog was opened. + constexpr View* GetOldFocus() const + { + return _oldFocus; + } + +private: + enum class State + { + Idle, + BottomSheetVisible, + BottomSheetClosing + }; + + FocusManager* _focusManager; + StackVramManager* _vramManager; + u32 _baseVramState; + std::unique_ptr _currentDialog; + std::unique_ptr _nextDialog; + bool _initVram = false; + View* _oldFocus = nullptr; + Animator _scrimAnimator; + Animator _yAnimator; + State _curState = State::Idle; + State _newState = State::Idle; +}; diff --git a/arm9/source/PicoLoaderProcess.cpp b/arm9/source/PicoLoaderProcess.cpp new file mode 100644 index 0000000..acb281e --- /dev/null +++ b/arm9/source/PicoLoaderProcess.cpp @@ -0,0 +1,13 @@ +#include "common.h" +#include +#include "picoLoaderBootstrap.h" +#include "PicoLoaderProcess.h" + +void PicoLoaderProcess::Run() +{ + REG_MASTER_BRIGHT = 0x401F; + REG_MASTER_BRIGHT_SUB = 0x401F; + REG_DISPCNT = 0; + REG_DISPCNT_SUB = 0; + pload_start(); +} diff --git a/arm9/source/PicoLoaderProcess.h b/arm9/source/PicoLoaderProcess.h new file mode 100644 index 0000000..c83d3eb --- /dev/null +++ b/arm9/source/PicoLoaderProcess.h @@ -0,0 +1,13 @@ +#pragma once +#include "services/process/IProcess.h" + +class PicoLoaderProcess : public IProcess +{ +public: + void Run() override; + + void Exit() override + { + // This process never exits + } +}; diff --git a/arm9/source/VBlank.cpp b/arm9/source/VBlank.cpp new file mode 100644 index 0000000..1a25e7f --- /dev/null +++ b/arm9/source/VBlank.cpp @@ -0,0 +1,4 @@ +#include "common.h" +#include "VBlank.h" + +rtos_event_t VBlank::sEvent; diff --git a/arm9/source/VBlank.h b/arm9/source/VBlank.h new file mode 100644 index 0000000..9cc86ec --- /dev/null +++ b/arm9/source/VBlank.h @@ -0,0 +1,25 @@ +#pragma once +#include + +/// @brief Helper class for waiting for vblank. +class VBlank +{ +public: + static void Init() + { + rtos_createEvent(&sEvent); + } + + static void Wait(bool waitNew = true, bool clearAfter = true) + { + rtos_waitEvent(&sEvent, waitNew, clearAfter); + } + + static void NotifyIrq() + { + rtos_signalEvent(&sEvent); + } + +private: + static rtos_event_t sEvent; +}; \ No newline at end of file diff --git a/arm9/source/animation/Animator.cpp b/arm9/source/animation/Animator.cpp new file mode 100644 index 0000000..7bf5d5f --- /dev/null +++ b/arm9/source/animation/Animator.cpp @@ -0,0 +1,35 @@ +#include "common.h" +#include "core/math/fixed.h" +#include "Animator.h" + +template <> +bool Animator::Update() +{ + if (++_frame >= _duration) + { + _frame = _duration; + _value = _to; + return true; + } + + auto relativePos = _curve->Compute(_frame * _invDuration); + _value = _from + (relativePos.LongMul(_to - _from) + 0.5).Int(); + + return false; +} + +template <> +bool Animator>::Update() +{ + if (++_frame >= _duration) + { + _frame = _duration; + _value = _to; + return true; + } + + auto relativePos = _curve->Compute(_frame * _invDuration); + _value = _from + fix32<12>(relativePos.LongMul(_to - _from)); + + return false; +} diff --git a/arm9/source/animation/Animator.h b/arm9/source/animation/Animator.h new file mode 100644 index 0000000..a567550 --- /dev/null +++ b/arm9/source/animation/Animator.h @@ -0,0 +1,50 @@ +#pragma once +#include "Curve.h" + +template +class Animator +{ +public: + constexpr Animator() + : _from(), _to(), _value(), _duration(0), _frame(0), _curve(nullptr) { } + + /// @brief Constructs an Animator with the given initialValue. + /// @param initialValue The initial value for the animator. + constexpr Animator(const T& initialValue) + : _from(initialValue), _to(initialValue), _value(initialValue), _duration(0), _frame(0), _curve(nullptr) { } + + /// @brief Constructs an Animator that animates from from to to with the given duration and curve. + /// @param from The start value. + /// @param to The end value. + /// @param duration The duration of the animation. + /// @param curve The curve to use for the animation. + constexpr Animator(const T& from, const T& to, u32 duration, const Curve* curve) + : _from(from), _to(to), _value(_from), _duration(duration), _invDuration(fix32<26>(1) / duration), _frame(0), _curve(curve) { } + + bool Update(); + + constexpr const T& GetValue() const { return _value; } + constexpr const T& GetTargetValue() const { return _to; } + constexpr u32 GetDuration() const { return _duration; } + constexpr u32 GetFrame() const { return _frame; } + constexpr bool IsFinished() const { return _frame == _duration; } + + /// @brief Restarts this animator from the current value to a new target + /// with the given duration and curve. + /// @param target The new target value. + /// @param duration The duration of the animation. + /// @param curve The curve to use. + void Goto(const T& target, u32 duration, const Curve* curve) + { + *this = Animator(_value, target, duration, curve); + } + +private: + T _from; + T _to; + T _value; + u32 _duration; + fix32<26> _invDuration; + u32 _frame; + const Curve* _curve; +}; diff --git a/arm9/source/animation/CubicBezierCurve.h b/arm9/source/animation/CubicBezierCurve.h new file mode 100644 index 0000000..e1d7b17 --- /dev/null +++ b/arm9/source/animation/CubicBezierCurve.h @@ -0,0 +1,48 @@ +#pragma once +#include "Curve.h" + +class CubicBezierCurve : public Curve +{ + using fx = fix16<13>; + + const fx _cx; + const fx _bx; + const fx _ax; + + const fx _cy; + const fx _by; + const fx _ay; + + [[gnu::noinline]] + static constexpr fix32<26> SampleCurve(fx ax, fx bx, fx cx, fx t) + { + return ((ax * t + bx) * t + cx).LongMul(t); + } + +public: + constexpr CubicBezierCurve(fx p1x, fx p1y, fx p2x, fx p2y) + : _cx(3 * p1x) + , _bx(3 * (p2x - p1x) - _cx) + , _ax(1 - _cx - _bx) + , _cy(3 * p1y) + , _by(3 * (p2y - p1y) - _cy) + , _ay(1 - _cy - _by) { } + + [[gnu::noinline]] + fix32<26> Compute(fix32<26> t) const override + { + fx start = 0.0; + fx end = 1.0; + while (true) + { + const fx midpoint = (start + end) >> 1; + const fix32<26> estimate = SampleCurve(_ax, _bx, _cx, midpoint); + if ((t - estimate).Abs() < 0.001) + return SampleCurve(_ay, _by, _cy, midpoint); + if (estimate < t) + start = midpoint; + else + end = midpoint; + } + } +}; \ No newline at end of file diff --git a/arm9/source/animation/Curve.h b/arm9/source/animation/Curve.h new file mode 100644 index 0000000..a3dc035 --- /dev/null +++ b/arm9/source/animation/Curve.h @@ -0,0 +1,8 @@ +#pragma once +#include "common.h" + +class Curve +{ +public: + virtual fix32<26> Compute(fix32<26> t) const = 0; +}; \ No newline at end of file diff --git a/arm9/source/animation/Interpolator.h b/arm9/source/animation/Interpolator.h new file mode 100644 index 0000000..ed48c97 --- /dev/null +++ b/arm9/source/animation/Interpolator.h @@ -0,0 +1,20 @@ +#pragma once +#include "common.h" + +class Interpolator +{ +public: + template + static constexpr T InterpolateLinear(T a, T b, T t) + { + return a + (b - a) * t; + } + + template + static constexpr fixed InterpolateCubic( + fixed cf0, fixed cf1, + fixed cf2, fixed cf3, fixed t) + { + return ((((cf0 * t + cf1) * t) + cf2) * t) + cf3; + } +}; \ No newline at end of file diff --git a/arm9/source/animation/LinearCurve.h b/arm9/source/animation/LinearCurve.h new file mode 100644 index 0000000..49b9ff8 --- /dev/null +++ b/arm9/source/animation/LinearCurve.h @@ -0,0 +1,11 @@ +#pragma once +#include "Curve.h" + +class LinearCurve : public Curve +{ +public: + fix32<26> Compute(fix32<26> t) const override + { + return t; + } +}; \ No newline at end of file diff --git a/arm9/source/animation/ThreePointCubicBezierCurve.h b/arm9/source/animation/ThreePointCubicBezierCurve.h new file mode 100644 index 0000000..f27df63 --- /dev/null +++ b/arm9/source/animation/ThreePointCubicBezierCurve.h @@ -0,0 +1,48 @@ +#pragma once +#include "Curve.h" +#include "CubicBezierCurve.h" + +class ThreePointCubicBezierCurve : public Curve +{ + using fx = fix16<13>; + + fix32<26> _midPointX; + fix32<26> _midPointY; + fix32<26> _invMidPointX; + fix32<26> _invOneMinusMidPointX; + + const CubicBezierCurve _firstCurve; + const CubicBezierCurve _secondCurve; + +public: + constexpr ThreePointCubicBezierCurve( + fx a1x, fx a1y, fx b1x, fx b1y, + fx midpointx, fx midpointy, + fx a2x, fx a2y, fx b2x, fx b2y) + : _midPointX(midpointx) + , _midPointY(midpointy) + , _invMidPointX(fix32<26>(1) / midpointx) + , _invOneMinusMidPointX(fix32<26>(1) / (1 - midpointx)) + , _firstCurve( + a1x / midpointx, + a1y / midpointy, + b1x / midpointx, + b1y / midpointy) + , _secondCurve( + (a2x - midpointx) / (1 - midpointx), + (a2y - midpointy) / (1 - midpointy), + (b2x - midpointx) / (1 - midpointx), + (b2y - midpointy) / (1 - midpointy)) { } + + [[gnu::noinline]] + fix32<26> Compute(fix32<26> t) const override + { + if (t < _midPointX) + return _firstCurve.Compute(t * _invMidPointX) * _midPointY; + else + { + fix32<26> scaledT = (t - _midPointX) * _invOneMinusMidPointX; + return _secondCurve.Compute(scaledT) * (1 - _midPointY) + _midPointY; + } + } +}; \ No newline at end of file diff --git a/arm9/source/bgm/AudioStreamPlayer.cpp b/arm9/source/bgm/AudioStreamPlayer.cpp new file mode 100644 index 0000000..fa8c241 --- /dev/null +++ b/arm9/source/bgm/AudioStreamPlayer.cpp @@ -0,0 +1,140 @@ +#include "common.h" +#include +#include +#include +#include +#include "ipcChannels.h" +#include "AudioStreamPlayer.h" + +/// @brief Hardware timer used for tracking audio blocks. +#define AUDIO_STREAM_PLAYER_TIMER 3 + +/// @brief Number of blocks that are not overwritten +/// between the read and write pointer to prevent +/// touching the current playing position. +#define AUDIO_STREAM_PLAYER_SAFETY_BLOCKS 4 + +/// @brief Priority of the stream thread. +#define AUDIO_STREAM_PLAYER_THREAD_PRIORITY 15 + +AudioStreamPlayer* AudioStreamPlayer::sCurrentPlayer = nullptr; + +AudioStreamPlayer::AudioStreamPlayer() +{ + rtos_createMutex(&_mutex); + rtos_createEvent(&_event); + DC_FlushRange(&_soundStopCmdList, sizeof(_soundStopCmdList)); +} + +bool AudioStreamPlayer::StartPlaybackIntern(std::unique_ptr audioStream) +{ + if (_isPlaying) + StopPlaybackIntern(); + + _audioStream = std::move(audioStream); + + // fill buffer + _readBlock = 0; + _writeBlock = 0; + for (u32 i = 0; i < AUDIO_STREAM_PLAYER_RING_BLOCKS - 1; i++) + { + FillRingBlock(i); + _writeBlock++; + } + + u32 rate = _audioStream->GetSampleRate(); + u32 timer = -((33513982 + rate) / (rate * 2)); + _soundStartCmdList.leftSetup.timer = timer; + _soundStartCmdList.rightSetup.timer = timer; + DC_FlushRange(&_soundStartCmdList, sizeof(_soundStartCmdList)); + + sCurrentPlayer = this; + + rtos_disableIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER)); + rtos_ackIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER)); + rtos_setIrqFunc(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER), [] (u32 irqMask) + { + u32 readBlock = sCurrentPlayer->_readBlock; + if (++readBlock == AUDIO_STREAM_PLAYER_RING_BLOCKS) + readBlock = 0; + sCurrentPlayer->_readBlock = readBlock; + rtos_signalEvent(&sCurrentPlayer->_event); + }); + tmr_configure(AUDIO_STREAM_PLAYER_TIMER, TMCNT_H_CLK_SYS_DIV_256, timer << 1, true); + + rtos_createThread(&_thread, AUDIO_STREAM_PLAYER_THREAD_PRIORITY, [] (void* arg) + { + static_cast(arg)->ThreadMain(); + }, this, _threadStack, sizeof(_threadStack)); + + tmr_start(AUDIO_STREAM_PLAYER_TIMER); + rtos_enableIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER)); + + _isPlaying = true; + rtos_wakeupThread(&_thread); + ipc_sendFifoMessage(IPC_CHANNEL_SOUND, (u32)&_soundStartCmdList); + + return true; +} + +void AudioStreamPlayer::StopPlaybackIntern() +{ + if (!_isPlaying) + return; + + _isPlaying = false; + rtos_wakeupThread(&_thread); + ipc_sendFifoMessage(IPC_CHANNEL_SOUND, (u32)&_soundStopCmdList); + tmr_stop(AUDIO_STREAM_PLAYER_TIMER); + rtos_disableIrqMask(RTOS_IRQ_TIMER(AUDIO_STREAM_PLAYER_TIMER)); + rtos_joinThread(&_thread); + sCurrentPlayer = nullptr; + _audioStream.reset(); +} + +void AudioStreamPlayer::ThreadMain() +{ + do + { + bool doUpdate = true; + while(_isPlaying && doUpdate) + { + rtos_lockMutex(&_mutex); + { + u32 writeBlock = _writeBlock; + int freeBlocks = _readBlock - writeBlock - 1; + if (freeBlocks < 0) + freeBlocks += AUDIO_STREAM_PLAYER_RING_BLOCKS; + + if (freeBlocks > AUDIO_STREAM_PLAYER_SAFETY_BLOCKS) + { + FillRingBlock(writeBlock); + if (++writeBlock == AUDIO_STREAM_PLAYER_RING_BLOCKS) + writeBlock = 0; + _writeBlock = writeBlock; + } + else + { + doUpdate = false; + } + } + rtos_unlockMutex(&_mutex); + } + + if (!_isPlaying) + break; + + rtos_waitEvent(&_event, false, true); + } while(_isPlaying); +} + +void AudioStreamPlayer::FillRingBlock(u32 block) +{ + s16* blockPtrL = &_audioRingL[block][0]; + s16* blockPtrR = &_audioRingR[block][0]; + + _audioStream->ReadSamples(blockPtrL, blockPtrR, AUDIO_STREAM_PLAYER_BLOCK_SAMPLES); + + DC_FlushRange(&blockPtrL, AUDIO_STREAM_PLAYER_BLOCK_SAMPLES * sizeof(s16)); + DC_FlushRange(&blockPtrR, AUDIO_STREAM_PLAYER_BLOCK_SAMPLES * sizeof(s16)); +} diff --git a/arm9/source/bgm/AudioStreamPlayer.h b/arm9/source/bgm/AudioStreamPlayer.h new file mode 100644 index 0000000..7e9bb93 --- /dev/null +++ b/arm9/source/bgm/AudioStreamPlayer.h @@ -0,0 +1,106 @@ +#pragma once +#include "common.h" +#include +#include +#include +#include +#include <../../libtwl7/include/libtwl/sound/soundChannel.h> +#include "fat/File.h" +#include "soundIpcCommand.h" +#include "IAudioStreamPlayer.h" + +#define AUDIO_STREAM_PLAYER_RING_BLOCKS 32 +#define AUDIO_STREAM_PLAYER_BLOCK_SAMPLES 256 + +/// @brief Class implementing an audio stream player. +class alignas(32) AudioStreamPlayer : public IAudioStreamPlayer +{ +public: + AudioStreamPlayer(); + + ~AudioStreamPlayer() + { + StopPlayback(); + } + + bool StartPlayback(std::unique_ptr audioStream) override + { + bool result; + rtos_lockMutex(&_mutex); + { + result = StartPlaybackIntern(std::move(audioStream)); + } + rtos_unlockMutex(&_mutex); + return result; + } + + void StopPlayback() override + { + rtos_lockMutex(&_mutex); + { + StopPlaybackIntern(); + } + rtos_unlockMutex(&_mutex); + } + +private: + struct alignas(32) SoundStartCmdList + { + u32 cmdCount; + snd_ipc_cmd_setup_channel_t leftSetup; + snd_ipc_cmd_setup_channel_t rightSetup; + u32 startChannels; + }; + + struct alignas(32) SoundStopCmdList + { + u32 cmdCount; + u32 stopChannels; + }; + + s16 _audioRingL[AUDIO_STREAM_PLAYER_RING_BLOCKS][AUDIO_STREAM_PLAYER_BLOCK_SAMPLES] alignas(32); + s16 _audioRingR[AUDIO_STREAM_PLAYER_RING_BLOCKS][AUDIO_STREAM_PLAYER_BLOCK_SAMPLES] alignas(32); + SoundStartCmdList _soundStartCmdList alignas(32) + { + 3, + { + SND_IPC_CMD_SETUP_CHANNEL, + 0, + _audioRingL, + 0, + 0, + sizeof(_audioRingL) >> 2, + SOUNDCNT_VOLUME(127) | SOUNDCNT_MODE_LOOP | SOUNDCNT_FORMAT_PCM16 + }, + { + SND_IPC_CMD_SETUP_CHANNEL, + 1, + _audioRingR, + 0, + 0, + sizeof(_audioRingR) >> 2, + SOUNDCNT_VOLUME(127) | SOUNDCNT_PAN(127) | SOUNDCNT_MODE_LOOP | SOUNDCNT_FORMAT_PCM16 + }, + (0b11 << 8) | SND_IPC_CMD_START_CHANNELS + }; + SoundStopCmdList _soundStopCmdList alignas(32) + { + 1, + (0b11 << 8) | SND_IPC_CMD_STOP_CHANNELS + }; + u32 _threadStack[2048 / sizeof(u32)] alignas(32); + rtos_mutex_t _mutex; + rtos_thread_t _thread; + rtos_event_t _event; + volatile bool _isPlaying = false; + volatile u8 _readBlock; + volatile u8 _writeBlock; + std::unique_ptr _audioStream; + + static AudioStreamPlayer* sCurrentPlayer; + + bool StartPlaybackIntern(std::unique_ptr audioStream); + void StopPlaybackIntern(); + void ThreadMain(); + void FillRingBlock(u32 block); +}; diff --git a/arm9/source/bgm/BcstmAudioStream.cpp b/arm9/source/bgm/BcstmAudioStream.cpp new file mode 100644 index 0000000..ac92956 --- /dev/null +++ b/arm9/source/bgm/BcstmAudioStream.cpp @@ -0,0 +1,327 @@ +#include "common.h" +#include +#include +#include +#include "BcstmAudioStream.h" + +bool BcstmAudioStream::Open(const TCHAR* filePath) +{ + if (_audioFile.Open(filePath, FA_OPEN_EXISTING | FA_READ) != FR_OK) + return false; + + return TryLoadBcstm(); +} + +bool BcstmAudioStream::Open(const FastFileRef& fastFileRef) +{ + _audioFile.Open(fastFileRef, FA_OPEN_EXISTING | FA_READ); + return TryLoadBcstm(); +} + +bool BcstmAudioStream::TryLoadBcstm() +{ + if (_audioFile.CreateClusterTable(_clusterTable, sizeof(_clusterTable)) != FR_OK) + return false; + + bcstm_header_t header; + u32 bytesRead = 0; + if (_audioFile.Read(&header, sizeof(bcstm_header_t), bytesRead) != FR_OK || + bytesRead != sizeof(bcstm_header_t)) + { + return false; + } + + if (_audioFile.Seek(header.infoBlockRef.offset) != FR_OK) + return false; + + bcstm_info_t info; + if (_audioFile.Read(&info, sizeof(bcstm_info_t), bytesRead) != FR_OK || + bytesRead != sizeof(bcstm_info_t)) + { + return false; + } + + if (_audioFile.Seek(header.infoBlockRef.offset + 8 + info.streamInfoRef.offset) != FR_OK) + return false; + + if (_audioFile.Read(&_streamInfo, sizeof(bcstm_info_stream_t), bytesRead) != FR_OK || + bytesRead != sizeof(bcstm_info_stream_t)) + { + return false; + } + + _dataOffset = header.dataBlockRef.offset + 8 + _streamInfo.dataRef.offset; + + _channels = std::min(_streamInfo.nrChannels, 2); + + if (_streamInfo.format == BCSTM_FORMAT_DSP_ADPCM) + { + u32 tableSize = sizeof(bcstm_ref_table_t) + sizeof(bcstm_ref_t) * (_streamInfo.nrChannels - 1); + auto channelInfoRefTab = std::unique_ptr(new u8[tableSize]); + + if (_audioFile.Seek(header.infoBlockRef.offset + 8 + info.channelInfoRef.offset) != FR_OK) + return false; + + if (_audioFile.Read(channelInfoRefTab.get(), tableSize, bytesRead) != FR_OK || + bytesRead != tableSize) + { + return false; + } + + auto channelInfoRefTabPtr = reinterpret_cast(channelInfoRefTab.get()); + for (u32 i = 0; i < _channels; i++) + { + u32 offset = header.infoBlockRef.offset + 8 + info.channelInfoRef.offset + channelInfoRefTabPtr->references[i].offset; + if (_audioFile.Seek(offset) != FR_OK) + return false; + + bcstm_info_channel_t channel; + if (_audioFile.Read(&channel, sizeof(bcstm_info_channel_t), bytesRead) != FR_OK || + bytesRead != sizeof(bcstm_info_channel_t)) + { + return false; + } + + if (_audioFile.Seek(offset + channel.codecInfoRef.offset) != FR_OK) + return false; + + if (_audioFile.Read(&_dspAdpcmInfo[i], sizeof(bcstm_dspadpcm_t), bytesRead) != FR_OK || + bytesRead != sizeof(bcstm_dspadpcm_t)) + { + return false; + } + } + } + + for (u32 i = 0; i < _channels; i++) + { + _adpcmBlocks[i] = std::unique_ptr(new(cache_align) u8[_streamInfo.blockSize]); + if (_streamInfo.format == BCSTM_FORMAT_DSP_ADPCM) + { + _dspAdpcmContexts[i].coefTable = _dspAdpcmInfo[i].coefs; + _dspAdpcmContexts[i].frameData[12] = _dspAdpcmInfo[i].context.last2; + _dspAdpcmContexts[i].frameData[13] = _dspAdpcmInfo[i].context.last1; + } + } + + _blockNumber = 0; + _sampleNumberInBlock = 0; + + if (_streamInfo.loop) + { + _loopBlockNumber = _streamInfo.loopStart / _streamInfo.blockSampleCount; + _loopBlockStartSample = _streamInfo.loopStart % _streamInfo.blockSampleCount; + + _loopEndBlockNumber = (_streamInfo.loopEnd - 1) / _streamInfo.blockSampleCount; + _loopEndBlockEndSample = (_streamInfo.loopEnd - 1) % _streamInfo.blockSampleCount; + } + + return true; +} + +void BcstmAudioStream::Close() +{ + _audioFile.Close(); + _adpcmBlocks[0].reset(); + _adpcmBlocks[1].reset(); +} + +void BcstmAudioStream::ReadSamples(s16* left, s16* right, u32 count) +{ + u32 remaining = count; + s16* curLeft = left; + s16* curRight = right; + do + { + if (_sampleNumberInBlock == 0) + FetchBlock(); + + u32 totalSamplesInBlock = GetTotalSamplesInCurrentBlock(); + u32 samplesToDecode = std::min(totalSamplesInBlock - _sampleNumberInBlock, remaining); + DecodeAdpcmSamples(_dspAdpcmContexts[0], curLeft, samplesToDecode); + if (_channels == 2) + DecodeAdpcmSamples(_dspAdpcmContexts[1], curRight, samplesToDecode); + curLeft += samplesToDecode; + curRight += samplesToDecode; + remaining -= samplesToDecode; + _sampleNumberInBlock += samplesToDecode; + if (_sampleNumberInBlock == totalSamplesInBlock) + { + _blockNumber++; + _sampleNumberInBlock = 0; + } + } while (remaining != 0); + + // when mono, copy the samples from the left to the right channel + if (_channels == 1) + memcpy(right, left, count * sizeof(s16)); +} + +u32 BcstmAudioStream::GetTotalSamplesInCurrentBlock() +{ + u32 totalSamplesInBlock = _blockNumber == _streamInfo.nrBlocks - 1 ? _streamInfo.lastBlockSampleCount : _streamInfo.blockSampleCount; + if (_streamInfo.loop && _blockNumber == _loopEndBlockNumber) + totalSamplesInBlock = _loopEndBlockEndSample + 1; + return totalSamplesInBlock; +} + +void BcstmAudioStream::FetchBlock() +{ + bool looped = false; + if (_blockNumber == _streamInfo.nrBlocks || (_streamInfo.loop && _blockNumber == _loopEndBlockNumber + 1)) + { + looped = true; + _blockNumber = _streamInfo.loop ? _loopBlockNumber : 0; + } + + // fetch block + u32 blockOffset = _dataOffset + _streamInfo.blockSize * (_blockNumber * _streamInfo.nrChannels); + if (_audioFile.Seek(blockOffset) != FR_OK) + return; + + for (u32 i = 0; i < _channels; i++) + { + u32 blockSize = _blockNumber == _streamInfo.nrBlocks - 1 + ? _streamInfo.lastBlockPaddedSize : _streamInfo.blockSize; + u32 bytesRead = 0; + if (_audioFile.Read(_adpcmBlocks[i].get(), blockSize, bytesRead) != FR_OK || + bytesRead != blockSize) + { + return; + } + _dspAdpcmContexts[i].SetData(_adpcmBlocks[i].get()); + } + + if (looped) + { + _sampleNumberInBlock = _loopBlockStartSample; + if (_streamInfo.format == BCSTM_FORMAT_DSP_ADPCM) + { + if (_blockNumber == 0) + { + for (u32 i = 0; i < _channels; i++) + { + _dspAdpcmContexts[i].frameData[12] = _dspAdpcmInfo[i].context.last2; + _dspAdpcmContexts[i].frameData[13] = _dspAdpcmInfo[i].context.last1; + } + } + else + { + u32 frameOffset = _loopBlockStartSample % 14u; + u32 frame = _loopBlockStartSample / 14u; + for (u32 i = 0; i < _channels; i++) + { + _dspAdpcmContexts[i].SetData(_adpcmBlocks[i].get() + frame * 8); + if (frameOffset != 0) + { + DecodeLoopAdpcmFrame( + _dspAdpcmContexts[i], + _dspAdpcmInfo[i].loopContext.predictorAndScale, + _dspAdpcmInfo[i].loopContext.last1, + _dspAdpcmInfo[i].loopContext.last2, + _loopBlockStartSample % 14u); + } + else + { + _dspAdpcmContexts[i].frameData[12] = _dspAdpcmInfo[i].loopContext.last2; + _dspAdpcmContexts[i].frameData[13] = _dspAdpcmInfo[i].loopContext.last1; + } + } + } + } + } +} + +extern "C" void dspadpcm_decode(DspAdpcmContext* context); + +void BcstmAudioStream::DecodeAdpcmSamples(DspAdpcmContext& context, s16* dst, u32 count) +{ + do + { + if (context.frameIdx == 0) + dspadpcm_decode(&context); + u32 toCopy = std::min(14 - context.frameIdx, count); + memcpy(dst, &context.frameData[context.frameIdx], toCopy * sizeof(s16)); + context.frameIdx += toCopy; + if (context.frameIdx == 14) + context.frameIdx = 0; + count -= toCopy; + dst += toCopy; + } while (count != 0); +} + +// ITCM_CODE void BcstmAudioStream::DecodeAdpcmFrame(DspAdpcmContext& context) +// { +// const u8* data = context.data; +// u32 scaleCoef = *data++; +// u32 scale = (scaleCoef & 0xF) + 11; +// u32 coefIdx = scaleCoef >> 4; +// s16 coef1 = context.coefTable[coefIdx * 2]; +// s16 coef2 = context.coefTable[coefIdx * 2 + 1]; +// s16 last2 = context.frameData[12]; +// s16 last1 = context.frameData[13]; + +// for (u32 j = 0; j < 7; j++) +// { +// u32 dataValue = *data++; +// int high = (int)(dataValue << 24) >> 28; +// int val = ((high << scale) + (coef1 * last1 + coef2 * last2) + 1024) >> 11; +// int sample = std::clamp(val, -0x8000, 0x7FFF); +// context.frameData[j * 2] = sample; +// last2 = last1; +// last1 = sample; + +// int low = (int)(dataValue << 28) >> 28; +// val = ((low << scale) + (coef1 * last1 + coef2 * last2) + 1024) >> 11; +// sample = std::clamp(val, -0x8000, 0x7FFF); +// context.frameData[j * 2 + 1] = sample; +// last2 = last1; +// last1 = sample; +// } + +// context.frameIdx = 0; +// context.data = data; +// } + +ITCM_CODE void BcstmAudioStream::DecodeLoopAdpcmFrame( + DspAdpcmContext& context, u32 scaleCoef, s16 last1, s16 last2, u32 sampleOffset) +{ + const u8* data = context.data; + data += 2 + (sampleOffset >> 1); + u32 scale = (scaleCoef & 0xF) + 11; + u32 coefIdx = scaleCoef >> 4; + s16 coef1 = context.coefTable[coefIdx * 2]; + s16 coef2 = context.coefTable[coefIdx * 2 + 1]; + if ((int)sampleOffset - 2 >= 0) + context.frameData[sampleOffset - 2] = last2; + if ((int)sampleOffset - 1 >= 0) + context.frameData[sampleOffset - 1] = last1; + + for (u32 j = sampleOffset; j < 14; j += 2) + { + u32 dataValue = *data++; + if ((j & 1) == 0) + { + int high = (int)(dataValue << 24) >> 28; + int val = ((high << scale) + (coef1 * last1 + coef2 * last2) + 1024) >> 11; + int sample = std::clamp(val, -0x8000, 0x7FFF); + context.frameData[j] = sample; + last2 = last1; + last1 = sample; + } + else + { + j--; + } + + int low = (int)(dataValue << 28) >> 28; + int val = ((low << scale) + (coef1 * last1 + coef2 * last2) + 1024) >> 11; + int sample = std::clamp(val, -0x8000, 0x7FFF); + context.frameData[j + 1] = sample; + last2 = last1; + last1 = sample; + } + + context.frameIdx = sampleOffset; + context.data = data; +} diff --git a/arm9/source/bgm/BcstmAudioStream.h b/arm9/source/bgm/BcstmAudioStream.h new file mode 100644 index 0000000..08129b6 --- /dev/null +++ b/arm9/source/bgm/BcstmAudioStream.h @@ -0,0 +1,66 @@ +#pragma once +#include "common.h" +#include +#include "fat/File.h" +#include "IAudioStream.h" +#include "bcstm.h" + +struct DspAdpcmContext +{ + const s16* coefTable; + const u8* data; + s16 frameData[14]; + u8 frameIdx; + + void SetData(const u8* data) + { + this->data = data; + frameIdx = 0; + } +}; + +class alignas(32) BcstmAudioStream : public IAudioStream +{ +public: + /// @brief Opens the given file. Call this function before + /// using ReadSamples. + /// @param filePath The path of the file to open. + /// @return True if the file was successfully opened, or false otherwise. + bool Open(const TCHAR* filePath); + + /// @brief Opens the given file. Call this function before + /// using ReadSamples. + /// @param filePath The path of the file to open. + /// @return True if the file was successfully opened, or false otherwise. + bool Open(const FastFileRef& fastFileRef); + + void Close() override; + void ReadSamples(s16* left, s16* right, u32 count) override; + + u32 GetSampleRate() const override + { + return _streamInfo.sampleRate; + } + +private: + File _audioFile alignas(32); + DWORD _clusterTable[1024] alignas(32); + bcstm_info_stream_t _streamInfo; + bcstm_dspadpcm_t _dspAdpcmInfo[2]; + u32 _dataOffset; + std::unique_ptr _adpcmBlocks[2]; + u32 _channels; + u32 _blockNumber; + u32 _sampleNumberInBlock; + u32 _loopBlockNumber; + u32 _loopEndBlockNumber; + u32 _loopBlockStartSample; + u32 _loopEndBlockEndSample; + DspAdpcmContext _dspAdpcmContexts[2]; + + bool TryLoadBcstm(); + u32 GetTotalSamplesInCurrentBlock(); + void FetchBlock(); + void DecodeAdpcmSamples(DspAdpcmContext& context, s16* dst, u32 count); + void DecodeLoopAdpcmFrame(DspAdpcmContext& context, u32 scaleCoef, s16 last1, s16 last2, u32 sampleOffset); +}; diff --git a/arm9/source/bgm/BgmService.cpp b/arm9/source/bgm/BgmService.cpp new file mode 100644 index 0000000..54e86af --- /dev/null +++ b/arm9/source/bgm/BgmService.cpp @@ -0,0 +1,44 @@ +#include "common.h" +#include "core/mini-printf.h" +#include "Pcm16FileAudioStream.h" +#include "BcstmAudioStream.h" +#include "romBrowser/SdFolder.h" +#include "romBrowser/SdFolderFactory.h" +#include "romBrowser/FileType/NullFileTypeProvider.h" +#include "BgmService.h" + +bool BgmService::StartBgm(const TCHAR* filePath) +{ + auto stream = std::make_unique(); + if (!stream->Open(filePath)) + return false; + + return _audioStreamPlayer->StartPlayback(std::move(stream)); +} + +void BgmService::StartBgmFromConfig() +{ + TCHAR pathBuffer[128]; + mini_snprintf(pathBuffer, sizeof(pathBuffer), "/_pico/themes/%s/bgm", _appSettingsService.GetAppSettings().theme.GetString()); + NullFileTypeProvider fileTypeProvider; + auto bgmFolder = SdFolderFactory(&fileTypeProvider).CreateFromPath(pathBuffer); + if (!bgmFolder || bgmFolder->GetFileCount() == 0) + { + StopBgm(); + return; + } + u32 bgmToPlay = _randomGenerator.NextU32(bgmFolder->GetFileCount()); + auto stream = std::make_unique(); + if (!stream->Open(bgmFolder->GetFiles()[bgmToPlay]->GetFastFileRef())) + { + StopBgm(); + return; + } + + _audioStreamPlayer->StartPlayback(std::move(stream)); +} + +void BgmService::StopBgm() +{ + _audioStreamPlayer->StopPlayback(); +} diff --git a/arm9/source/bgm/BgmService.h b/arm9/source/bgm/BgmService.h new file mode 100644 index 0000000..d39390f --- /dev/null +++ b/arm9/source/bgm/BgmService.h @@ -0,0 +1,28 @@ +#pragma once +#include +#include "IAudioStreamPlayer.h" +#include "services/settings/IAppSettingsService.h" +#include "rng/RandomGenerator.h" +#include "IBgmService.h" + +/// @brief Class implementing a background music service. +class BgmService : public IBgmService +{ +public: + constexpr BgmService( + std::unique_ptr audioStreamPlayer, + IAppSettingsService& appSettingsService, + RandomGenerator& randomGenerator) + : _audioStreamPlayer(std::move(audioStreamPlayer)) + , _appSettingsService(appSettingsService) + , _randomGenerator(randomGenerator) { } + + bool StartBgm(const TCHAR* filePath) override; + void StartBgmFromConfig() override; + void StopBgm() override; + +private: + std::unique_ptr _audioStreamPlayer; + IAppSettingsService& _appSettingsService; + RandomGenerator& _randomGenerator; +}; diff --git a/arm9/source/bgm/IAudioStream.h b/arm9/source/bgm/IAudioStream.h new file mode 100644 index 0000000..6dd1d2c --- /dev/null +++ b/arm9/source/bgm/IAudioStream.h @@ -0,0 +1,24 @@ +#pragma once + +/// @brief Interface for an audio stream. +class IAudioStream +{ +public: + virtual ~IAudioStream() = 0; + + /// @brief Returns the sample rate of the audio stream in Hertz. + /// @return The sample rate in Hertz. + virtual u32 GetSampleRate() const = 0; + + /// @brief Reads the given number of samples from the audio stream + /// to the given buffers for the left and right channel. + /// @param left The left channel buffer. + /// @param right The right channel buffer. + /// @param count The number of samples to read. + virtual void ReadSamples(s16* left, s16* right, u32 count) = 0; + + /// @brief Closes the audio stream. + virtual void Close() = 0; +}; + +inline IAudioStream::~IAudioStream() { } diff --git a/arm9/source/bgm/IAudioStreamPlayer.h b/arm9/source/bgm/IAudioStreamPlayer.h new file mode 100644 index 0000000..eaf518e --- /dev/null +++ b/arm9/source/bgm/IAudioStreamPlayer.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include "IAudioStream.h" + +/// @brief Interface for playback of audio streams. +class IAudioStreamPlayer +{ +public: + virtual ~IAudioStreamPlayer() = 0; + + /// @brief Starts playback of the given audio stream. + /// @param audioStream The stream to play. + /// @return True if playback was successfully started, or false otherwise. + virtual bool StartPlayback(std::unique_ptr audioStream) = 0; + + /// @brief Stops playback of the currently playing audio stream. + virtual void StopPlayback() = 0; +}; + +inline IAudioStreamPlayer::~IAudioStreamPlayer() { } diff --git a/arm9/source/bgm/IBgmService.h b/arm9/source/bgm/IBgmService.h new file mode 100644 index 0000000..b04e0c8 --- /dev/null +++ b/arm9/source/bgm/IBgmService.h @@ -0,0 +1,22 @@ +#pragma once +#include "fat/ff.h" + +/// @brief Interface for a background music service. +class IBgmService +{ +public: + virtual ~IBgmService() = 0; + + /// @brief Starts playback of the given file. + /// @param filePath The file to play. + /// @return True if playback was successfully started, or false otherwise. + virtual bool StartBgm(const TCHAR* filePath) = 0; + + /// @brief Starts playback of the background music according to the app config. + virtual void StartBgmFromConfig() = 0; + + /// @brief If currently playing, stops playback. + virtual void StopBgm() = 0; +}; + +inline IBgmService::~IBgmService() { } diff --git a/arm9/source/bgm/Pcm16FileAudioStream.cpp b/arm9/source/bgm/Pcm16FileAudioStream.cpp new file mode 100644 index 0000000..9b77764 --- /dev/null +++ b/arm9/source/bgm/Pcm16FileAudioStream.cpp @@ -0,0 +1,26 @@ +#include "common.h" +#include +#include "Pcm16FileAudioStream.h" + +bool Pcm16FileAudioStream::Open(const TCHAR* filePath) +{ + if (_audioFile.Open(filePath, FA_OPEN_EXISTING | FA_READ) != FR_OK) + return false; + + if (_audioFile.CreateClusterTable(_clusterTable, sizeof(_clusterTable)) != FR_OK) + return false; + + return true; +} + +void Pcm16FileAudioStream::Close() +{ + _audioFile.Close(); +} + +void Pcm16FileAudioStream::ReadSamples(s16 *left, s16 *right, u32 count) +{ + u32 bytesRead = 0; + _audioFile.Read(left, count * sizeof(s16), bytesRead); + memcpy(right, left, count * sizeof(s16)); +} diff --git a/arm9/source/bgm/Pcm16FileAudioStream.h b/arm9/source/bgm/Pcm16FileAudioStream.h new file mode 100644 index 0000000..ff955d0 --- /dev/null +++ b/arm9/source/bgm/Pcm16FileAudioStream.h @@ -0,0 +1,33 @@ +#pragma once +#include "common.h" +#include "fat/File.h" +#include "IAudioStream.h" + +/// @brief Raw mono 16 bit pcm audio file stream. +/// Assumes a sample rate of 32728 Hz. +class alignas(32) Pcm16FileAudioStream : public IAudioStream +{ +public: + /// @brief Opens the given file. Call this function before + /// using ReadSamples. + /// @param filePath The path of the file to open. + /// @return True if the file was successfully opened, or false otherwise. + bool Open(const TCHAR* filePath); + + void Close() override; + void ReadSamples(s16* left, s16* right, u32 count) override; + + u32 GetSampleRate() const override + { + return 32728; + } + + ~Pcm16FileAudioStream() + { + Close(); + } + +private: + File _audioFile alignas(32); + DWORD _clusterTable[1024] alignas(32); +}; diff --git a/arm9/source/bgm/bcstm.h b/arm9/source/bgm/bcstm.h new file mode 100644 index 0000000..d2324be --- /dev/null +++ b/arm9/source/bgm/bcstm.h @@ -0,0 +1,102 @@ +#pragma once +#include "common.h" + +#define BCSTM_FORMAT_DSP_ADPCM 2 + +struct bcstm_ref_t +{ + u16 typeId; + u16 padding; + u32 offset; +}; + +struct bcstm_sized_ref_t +{ + u16 typeId; + u16 padding; + u32 offset; + u32 size; +}; + +struct bcstm_ref_table_t +{ + u32 count; + bcstm_ref_t references[1]; +}; + +struct bcstm_header_t +{ + u32 signature; + u16 endianness; + u16 headerSize; + u32 version; + u32 fileSize; + u16 nrBlocks; + u16 reserved; + bcstm_sized_ref_t infoBlockRef; + bcstm_sized_ref_t seekBlockRef; + bcstm_sized_ref_t dataBlockRef; +}; + +struct bcstm_info_stream_t +{ + u8 format; + u8 loop; + u8 nrChannels; + u8 padding; + u32 sampleRate; + u32 loopStart; + u32 loopEnd; + u32 nrBlocks; + u32 blockSize; + u32 blockSampleCount; + u32 lastBlockSize; + u32 lastBlockSampleCount; + u32 lastBlockPaddedSize; + u32 seekEntrySize; + u32 seekInterval; + bcstm_ref_t dataRef; +}; + +struct bcstm_info_channel_t +{ + bcstm_ref_t codecInfoRef; +}; + +struct bcstm_dspadpcm_context_t +{ + u8 predictorAndScale; + u8 reserved; + s16 last1; + s16 last2; +}; + +struct alignas(4) bcstm_dspadpcm_t +{ + s16 coefs[16]; + bcstm_dspadpcm_context_t context; + bcstm_dspadpcm_context_t loopContext; +}; + +struct bcstm_info_t +{ + u32 signature; + u32 sectionSize; + bcstm_ref_t streamInfoRef; + bcstm_ref_t trackInfoRef; + bcstm_ref_t channelInfoRef; +}; + +struct bcstm_seek_t +{ + u32 signature; + u32 sectionSize; + u8 seekData[1]; +}; + +struct bcstm_data_t +{ + u32 signature; + u32 sectionSize; + u8 data[1]; +}; diff --git a/arm9/source/bgm/dspAdpcm.s b/arm9/source/bgm/dspAdpcm.s new file mode 100644 index 0000000..37ccbe0 --- /dev/null +++ b/arm9/source/bgm/dspAdpcm.s @@ -0,0 +1,55 @@ +.section ".itcm", "ax" +.arm + +// r0 = context pointer +.global dspadpcm_decode +.type dspadpcm_decode, %function +dspadpcm_decode: + stmfd sp!, {r4, r5, r6, r7, r8, lr} + + mov lr, #1024 + ldr r5,= #0x7FFFFFFF + + ldmia r0!, {r1, r2} // context->coefTable, context->data + strb lr, [r0, #(36 - 8)] // context->frameIdx = 0 + add r2, r2, #8 + str r2, [r0, #(4 - 8)] // context->data += 8 + ldrb r3, [r2, #-8]! + ldr r7, [r0, #(32 - 8)] // r7 = last1_last2 + + and r8, r3, #0xF0 + ldr r6, [r1, r8, lsr #2] // r6 = coef2_coef1 + + and r1, r3, #0xF + add r1, r1, #11 // r1 = scale + + mov r4, #7 +sample_loop: + ldrsb r3, [r2, #1]! + smlatb r8, r6, r7, lr // r8 = coef2 * last2 + 1024 + smlabt r8, r6, r7, r8 // r8 += coef1 * last1 + mov r3, r3, ror #4 + add r8, r8, r3, lsl r1// r8 += nibble << scale + + mov r12, r8, lsl #5 + cmp r8, r12, asr #5 + eorne r12, r5, r8, asr #31 + + smlatt r7, r6, r7, lr + smlabt r7, r6, r12, r7 + mov r3, r3, asr #28 + add r7, r7, r3, lsl r1 + + mov r8, r7, lsl #5 + cmp r7, r8, asr #5 + eorne r8, r5, r7, asr #31 + + bic r8, r8, r5, lsr #15 + orr r7, r8, r12, lsr #16 + + str r7, [r0], #4 + + subs r4, r4, #1 + bne sample_loop + + ldmfd sp!, {r4, r5, r6, r7, r8, pc} \ No newline at end of file diff --git a/arm9/source/common.h b/arm9/source/common.h new file mode 100644 index 0000000..02c59ab --- /dev/null +++ b/arm9/source/common.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include "fat/ff.h" + +#ifdef __cplusplus + +#include "core/math/fixed.h" +#include "core/math/SinTable.h" +#include "globalHeap.h" +#include "services/process/ProcessManager.h" +#include "VBlank.h" +#include "logger/ILogger.h" +#include "core/TickCounter.h" +#include "rng/RandomGenerator.h" + +extern ProcessManager gProcessManager; +extern ILogger* gLogger; +extern RandomGenerator* gRandomGenerator; + +#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; diff --git a/arm9/source/core/BitVector.h b/arm9/source/core/BitVector.h new file mode 100644 index 0000000..0189da6 --- /dev/null +++ b/arm9/source/core/BitVector.h @@ -0,0 +1,53 @@ +#pragma once +#include + +template +class BitVector +{ +public: + BitVector() + { + Clear(); + } + + u32 Get(u32 idx) const + { + return (_flags[idx >> 5] >> (idx & 0x1F)) & 1; + } + + void Set(u32 idx, u32 value) + { + u32 f = _flags[idx >> 5]; + if (value) + f |= 1 << (idx & 0x1F); + else + f &= ~(1 << (idx & 0x1F)); + _flags[idx >> 5] = f; + } + + void Clear() + { + _flags.fill(0); + } + + [[gnu::noinline]] + constexpr int FindFirstZero() + { + u32 globalBitNr = 0; + for (const u32 bits : _flags) + { + if (bits == ~0u) + { + globalBitNr += 32; + continue; + } + u32 invBits = ~bits; + globalBitNr += 31 - __builtin_clz(-invBits & invBits); + return globalBitNr >= Length ? -1 : globalBitNr; + } + return -1; + } + +private: + std::array _flags; +}; \ No newline at end of file diff --git a/arm9/source/core/Environment.cpp b/arm9/source/core/Environment.cpp new file mode 100644 index 0000000..e53a1bb --- /dev/null +++ b/arm9/source/core/Environment.cpp @@ -0,0 +1,64 @@ +#include +#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() +{ + vu8* identifier = (vu8*)0x04FFFA00; + // melon ds only seems to implement this for 8 bit reads + u32 nocashIdentifier = identifier[0] | (identifier[1] << 8) | (identifier[2] << 16) | (identifier[3] << 24); + return nocashIdentifier == 0x67246F6E // no$g + || nocashIdentifier == 0x6F6C656D; // melo +} + +static bool detectPicoAgbAdapter() +{ + REG_EXMEMCNT &= ~0xFF; + return PICO_AGB_IDENTIFIER == PICO_AGB_IDENTIFIER_VALUE; +} + +void Environment::Initialize() +{ + _flags = ENVIRONMENT_FLAGS_NONE; + if (isDSiMode()) + { + _flags |= ENVIRONMENT_FLAGS_DSI_MODE; + } + else + { + if (detectIsNitroEmulator()) + { + _flags |= ENVIRONMENT_FLAGS_IS_NITRO_EMULATOR; + _flags |= ENVIRONMENT_FLAGS_JTAG_SEMIHOSTING; + + REG_EXMEMCNT &= ~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; + } +} \ No newline at end of file diff --git a/arm9/source/core/Environment.h b/arm9/source/core/Environment.h new file mode 100644 index 0000000..0a04919 --- /dev/null +++ b/arm9/source/core/Environment.h @@ -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(); + + 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; } +}; \ No newline at end of file diff --git a/arm9/source/core/LinkedList.h b/arm9/source/core/LinkedList.h new file mode 100644 index 0000000..af46181 --- /dev/null +++ b/arm9/source/core/LinkedList.h @@ -0,0 +1,189 @@ +#pragma once +#include "LinkedListLink.h" + +/// @brief Class implementing a linked list with the link inside the list items. +/// @tparam T The type of list items. +/// @tparam member The link inside the items type. +template +class LinkedList +{ +public: + /// @brief Linked list iterator. + class Iterator + { + LinkedListLink* _itemLink; + + public: + /// @brief Creates an iterator pointing to the given item. + /// @param itemLink The link of the item this iterator points to. + explicit Iterator(LinkedListLink* itemLink) + : _itemLink(itemLink) { } + + /// @brief Checks whether this iterator is inequal to rhs. + /// @param rhs The iterator to compare to. + /// @return True if the iterators are inequal, or false otherwise. + constexpr bool operator!=(Iterator rhs) const + { + return _itemLink != rhs._itemLink; + } + + /// @brief Gets the list item this iterator points to. + /// @return A reference to the list item. + constexpr T& operator*() const + { + return *LinkToItem(_itemLink); + } + + /// @brief Advances this iterator to the next list item. + void operator++() + { + _itemLink = _itemLink->next; + } + }; + + /// @brief Inserts the given item at the head of the list. + /// @param item The item to insert. + void InsertHead(T* item) + { + LinkedListLink* link = &(item->*member); + link->next = _head; + link->prev = nullptr; + if (_head) + { + _head->prev = link; + } + _head = link; + if (!_count++) + { + _tail = link; + } + } + + /// @brief Inserts the given item at the tail of the list. + /// @param item The item to insert. + void InsertTail(T* item) + { + LinkedListLink* link = &(item->*member); + link->next = nullptr; + link->prev = _tail; + if (_tail) + { + _tail->next = link; + } + _tail = link; + if (!_count++) + { + _head = link; + } + } + + /// @brief Inserts the given item after the other item. + /// @param item The item to insert. + /// @param after The item after which will be inserted. + void InsertAfter(T* item, T* after) + { + LinkedListLink* itemLink = &(item->*member); + LinkedListLink* afterLink = &(after->*member); + itemLink->next = afterLink->next; + itemLink->prev = afterLink; + if (afterLink->next) + { + afterLink->next->prev = itemLink; + } + _count++; + } + + /// @brief Inserts the given item before the other item. + /// @param item The item to insert. + /// @param after The item before which will be inserted. + void InsertBefore(T* item, T* before) + { + LinkedListLink* itemLink = &(item->*member); + LinkedListLink* beforeLink = &(before->*member); + itemLink->next = beforeLink; + itemLink->prev = beforeLink->prev; + if (beforeLink->prev) + { + beforeLink->prev->next = itemLink; + } + _count++; + } + + /// @brief Removes the given item from the list. + /// @param item The item to remove. + void Remove(T* item) + { + LinkedListLink* itemLink = &(item->*member); + if (!itemLink->prev) + { + _head = itemLink->next; + } + else + { + itemLink->prev->next = itemLink->next; + } + + if (!itemLink->next) + { + _tail = itemLink->prev; + } + else + { + itemLink->next->prev = itemLink->prev; + } + itemLink->prev = nullptr; + itemLink->next = nullptr; + _count--; + } + + /// @brief Gets the head of the list. + /// @return The head of the list, or null if the list is empty. + constexpr T* GetHead() const { return _head ? LinkToItem(_head) : nullptr; } + + /// @brief Gets the tail of the list. + /// @return The tail of the list, or null if the list is empty. + constexpr T* GetTail() const { return _tail ? LinkToItem(_tail) : nullptr; } + + /// @brief Gets the item that comes after the given item. + /// @param item The item to get the next for. + /// @return The next item, or null if item is the last item in the list. + constexpr T* GetNext(T* item) const + { + LinkedListLink* itemLink = &(item->*member); + return itemLink->next ? LinkToItem(itemLink->next) : nullptr; + } + + /// @brief Gets the item that comes before the given item. + /// @param item The item to get the previous for. + /// @return The previous item, or null if item is the first item in the list. + constexpr T* GetPrevious(T* item) const + { + LinkedListLink* itemLink = &(item->*member); + return itemLink->prev ? LinkToItem(itemLink->prev) : nullptr; + } + + /// @brief Gets the amount of items in the list. + /// @return The amount of items in the list. + constexpr u32 GetCount() const { return _count; } + + /// @brief Gets an iterator to the start of the list. + /// @return An iterator to start of the list. + constexpr Iterator begin() const { return Iterator(_head); } + + /// @brief Gets an iterator to the end of the list. + /// @return An iterator to end of the list. + constexpr Iterator end() const { return Iterator(nullptr); } + +private: + LinkedListLink* _head = nullptr; + LinkedListLink* _tail = nullptr; + u32 _count = 0; + + /// @brief Converts a list link pointer to an item pointer. + /// @param link The list link pointer. + /// @return The item pointer. + static constexpr T* LinkToItem(LinkedListLink* link) + { + return (T*)(((u8*)link)-((size_t)&(((T*)nullptr)->*member))); + } +}; \ No newline at end of file diff --git a/arm9/source/core/LinkedListLink.h b/arm9/source/core/LinkedListLink.h new file mode 100644 index 0000000..b1afb73 --- /dev/null +++ b/arm9/source/core/LinkedListLink.h @@ -0,0 +1,11 @@ +#pragma once + +/// @brief Link for a linked list. +struct LinkedListLink +{ + /// @brief Pointer to the next list link in the list. + LinkedListLink* next = nullptr; + + /// @brief Pointer to the previous list link in the list. + LinkedListLink* prev = nullptr; +}; diff --git a/arm9/source/core/SharedPtr.h b/arm9/source/core/SharedPtr.h new file mode 100644 index 0000000..839839e --- /dev/null +++ b/arm9/source/core/SharedPtr.h @@ -0,0 +1,163 @@ +#pragma once + +static inline u32 arm_getCpsr() +{ + u32 cpsr; + asm volatile("mrs %0, cpsr" : "=r" (cpsr)); + return cpsr; +} + +static inline void arm_setCpsrControl(u32 cpsrControl) +{ + asm volatile("msr cpsr_c, %0" :: "r" (cpsrControl) : "cc"); +} + +static inline u32 arm_disableIrqs(void) +{ + u32 oldCpsr = arm_getCpsr(); + arm_setCpsrControl(oldCpsr | 0x80); + return oldCpsr; +} + +static inline void arm_restoreIrqs(u32 oldCpsr) +{ + arm_setCpsrControl(oldCpsr); +} + +template +class SharedPtr +{ + T* _pointer; + vu32* _refCount; + +public: + SharedPtr() + : _pointer(nullptr), _refCount(nullptr) { (void)sizeof(T); } + + explicit SharedPtr(T* pointer) + : _pointer(pointer), _refCount(pointer ? new u32(1) : nullptr) { (void)sizeof(T); } + + SharedPtr(const SharedPtr& other) + { + u32 irq = arm_disableIrqs(); + _pointer = other._pointer; + _refCount = other._refCount; + if (_pointer) + { + (*_refCount)++; + } + arm_restoreIrqs(irq); + } + + SharedPtr(SharedPtr&& other) + : _pointer(other._pointer), _refCount(other._refCount) + { + other._pointer = nullptr; + other._refCount = nullptr; + } + + ~SharedPtr() + { + Reset(); + } + + [[gnu::noinline]] + SharedPtr& operator=(const SharedPtr& other) + { + u32 irq = arm_disableIrqs(); + T* pointer = _pointer; + if (pointer) + { + vu32* refCount = _refCount; + u32 newValue = *refCount - 1; + *refCount = newValue; + _pointer = other._pointer; + _refCount = other._refCount; + if (_pointer) + { + (*_refCount)++; + } + arm_restoreIrqs(irq); + if (newValue == 0) + { + delete pointer; + delete refCount; + } + } + else + { + _pointer = other._pointer; + _refCount = other._refCount; + if (_pointer) + { + (*_refCount)++; + } + arm_restoreIrqs(irq); + } + return *this; + } + + [[gnu::noinline]] + SharedPtr& operator=(SharedPtr&& other) + { + u32 irq = arm_disableIrqs(); + T* pointer = _pointer; + if (pointer) + { + vu32* refCount = _refCount; + u32 newValue = *refCount - 1; + *refCount = newValue; + _pointer = other._pointer; + _refCount = other._refCount; + other._pointer = nullptr; + other._refCount = nullptr; + arm_restoreIrqs(irq); + if (newValue == 0) + { + delete pointer; + delete refCount; + } + } + else + { + _pointer = other._pointer; + _refCount = other._refCount; + other._pointer = nullptr; + other._refCount = nullptr; + arm_restoreIrqs(irq); + } + return *this; + } + + [[gnu::noinline]] + void Reset() + { + u32 irq = arm_disableIrqs(); + T* pointer = _pointer; + if (pointer) + { + vu32* refCount = _refCount; + u32 newValue = *refCount - 1; + *refCount = newValue; + _pointer = nullptr; + _refCount = nullptr; + arm_restoreIrqs(irq); + if (newValue == 0) + { + delete pointer; + delete refCount; + } + } + else + { + arm_restoreIrqs(irq); + } + } + + constexpr T& operator*() const { return *_pointer; } + constexpr T* operator->() const { return _pointer; } + + constexpr T* GetPointer() const { return _pointer; } + constexpr u32 GetRefCount() const { return _refCount ? *_refCount : 0; } + constexpr bool IsValid() const { return _pointer; } +}; diff --git a/arm9/source/core/String.h b/arm9/source/core/String.h new file mode 100644 index 0000000..7130034 --- /dev/null +++ b/arm9/source/core/String.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include "StringUtil.h" + +template +class String +{ + CharType _buffer[MaxLength + 1]; + +public: + String() + { + _buffer[0] = 0; + } + + template>> + String(const T* const & str) + { + StringUtil::Copy(_buffer, str, MaxLength + 1); + } + + explicit String(const char* str) + { + StringUtil::Copy(_buffer, str, MaxLength + 1); + } + + template + constexpr String(const CharType(&str)[N]) + { + if (N * sizeof(CharType) <= sizeof(_buffer)) + memcpy(_buffer, str, N * sizeof(CharType)); + else + { + memcpy(_buffer, str, MaxLength * sizeof(CharType)); + _buffer[MaxLength] = 0; + } + } + + template>> + void operator=(const T* const & str) + { + StringUtil::Copy(_buffer, str, MaxLength + 1); + } + + template + constexpr void operator=(const CharType(&str)[N]) + { + if (N * sizeof(CharType) <= sizeof(_buffer)) + memcpy(_buffer, str, N * sizeof(CharType)); + else + { + memcpy(_buffer, str, MaxLength * sizeof(CharType)); + _buffer[MaxLength] = 0; + } + } + + constexpr operator const CharType*() const { return _buffer; } + + constexpr const CharType* GetString() const { return _buffer; } +}; \ No newline at end of file diff --git a/arm9/source/core/StringUtil.cpp b/arm9/source/core/StringUtil.cpp new file mode 100644 index 0000000..f3b4c7a --- /dev/null +++ b/arm9/source/core/StringUtil.cpp @@ -0,0 +1,104 @@ +#include "common.h" +#include +#include "StringUtil.h" + +u32 StringUtil::Copy(char* dst, const char* src, u32 dstLength) +{ + if (!dst || dstLength == 0) + { + return 0; + } + + u32 i = 0; + if (src) + { + for (; i < dstLength - 1; i++) + { + char c = src[i]; + dst[i] = c; + if (c == 0) + { + return i; + } + } + } + + dst[i] = 0; + return i; +} + +u32 StringUtil::Copy(char16_t* dst, const char16_t* src, u32 dstLength) +{ + if (!dst || dstLength == 0) + { + return 0; + } + + u32 i = 0; + if (src) + { + for (; i < dstLength - 1; i++) + { + char16_t c = src[i]; + dst[i] = c; + if (c == 0) + { + return i; + } + } + } + + dst[i] = 0; + return i; +} + +u32 StringUtil::Copy(char16_t* dst, const char* src, u32 dstLength) +{ + if (!dst || dstLength == 0) + { + return 0; + } + + u32 i = 0; + if (src) + { + for (; i < dstLength - 1; i++) + { + char c0 = *src++; + + // decode UTF-8 + if ((c0 & 0x80) == 0) + { + // 1 byte + dst[i] = c0 & 0x7F; + } + else if ((c0 & 0xE0) == 0xC0) + { + // 2 bytes + char c1 = *src++; + dst[i] = ((c0 & 0x1F) << 6) | (c1 & 0x3F); + } + else if ((c0 & 0xF0) == 0xE0) + { + // 3 bytes + char c1 = *src++; + char c2 = *src++; + dst[i] = ((c0 & 0x0F) << 12) | ((c1 & 0x3F) << 6) | (c2 & 0x3F); + } + else + { + // 4 bytes; not supported + src += 3; + dst[i] = '?'; // substitute with question mark + } + + if (c0 == 0) + { + return i; + } + } + } + + dst[i] = 0; + return i; +} diff --git a/arm9/source/core/StringUtil.h b/arm9/source/core/StringUtil.h new file mode 100644 index 0000000..b09a2e0 --- /dev/null +++ b/arm9/source/core/StringUtil.h @@ -0,0 +1,32 @@ +#pragma once + +class StringUtil +{ +public: + /// @brief Copies the null terminated string in src to the + /// dst buffer of size dstSize. If dstSize > 0 the dst + /// buffer is guarenteed to be null terminated, even + /// if src is truncated to fit in dst. + /// @param dst The destination buffer. + /// @param src The source string (null terminated). + /// @param dstLength The length of the destination buffer in characters. + static u32 Copy(char* dst, const char* src, u32 dstLength); + + /// @brief Copies the null terminated string in src to the + /// dst buffer of size dstSize. If dstSize > 0 the dst + /// buffer is guarenteed to be null terminated, even + /// if src is truncated to fit in dst. + /// @param dst The destination buffer. + /// @param src The source string (null terminated). + /// @param dstLength The length of the destination buffer in char16_t characters. + static u32 Copy(char16_t* dst, const char16_t* src, u32 dstLength); + + /// @brief Copies the null terminated string in src to the + /// dst buffer of size dstSize. If dstSize > 0 the dst + /// buffer is guarenteed to be null terminated, even + /// if src is truncated to fit in dst. + /// @param dst The destination buffer. + /// @param src The source string (null terminated). + /// @param dstLength The length of the destination buffer in char16_t characters. + static u32 Copy(char16_t* dst, const char* src, u32 dstLength); +}; diff --git a/arm9/source/core/di.h b/arm9/source/core/di.h new file mode 100644 index 0000000..1b82dfd --- /dev/null +++ b/arm9/source/core/di.h @@ -0,0 +1,3362 @@ +// +// Copyright (c) 2012-2020 Kris Jusiak (kris at jusiak dot net) +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once +#if (__cplusplus < 201305L && _MSC_VER < 1900) +#error "[Boost::ext].DI requires C++14 support (Clang-3.4+, GCC-5.1+, MSVC-2015+)" +#else +#define BOOST_DI_VERSION 1'2'0 +#define BOOST_DI_NAMESPACE_BEGIN \ + namespace boost { \ + inline namespace ext { \ + namespace di { \ + inline namespace v1_2_0 { +#define BOOST_DI_NAMESPACE_END \ + } \ + } \ + } \ + } +#if !defined(BOOST_DI_CFG_DIAGNOSTICS_LEVEL) +#define BOOST_DI_CFG_DIAGNOSTICS_LEVEL 1 +#endif +#if defined(BOOST_DI_CFG_FWD) +BOOST_DI_CFG_FWD +#endif +#define __BOOST_DI_COMPILER(arg, ...) __BOOST_DI_COMPILER_IMPL(arg, __VA_ARGS__) +#define __BOOST_DI_COMPILER_IMPL(arg, ...) arg##__VA_ARGS__ +#if defined(__clang__) +#define __CLANG__ __BOOST_DI_COMPILER(__clang_major__, __clang_minor__) +#define __BOOST_DI_UNUSED __attribute__((unused)) +#define __BOOST_DI_DEPRECATED(...) [[deprecated(__VA_ARGS__)]] +#define __BOOST_DI_TYPE_WKND(T) +#define __BOOST_DI_ACCESS_WKND private +#define __BOOST_DI_VARIABLE_TEMPLATE_INIT_WKND \ + {} +#elif defined(__GNUC__) +#define __GCC__ +#define __BOOST_DI_UNUSED __attribute__((unused)) +#define __BOOST_DI_DEPRECATED(...) [[deprecated(__VA_ARGS__)]] +#define __BOOST_DI_TYPE_WKND(T) +#define __BOOST_DI_ACCESS_WKND private +#define __BOOST_DI_VARIABLE_TEMPLATE_INIT_WKND \ + {} +#elif defined(_MSC_VER) +#define __MSVC__ +#if !defined(__has_include) +#define __has_include(...) 0 +#endif +#define __BOOST_DI_UNUSED +#define __BOOST_DI_DEPRECATED(...) __declspec(deprecated(__VA_ARGS__)) +#define __BOOST_DI_TYPE_WKND(T) (T &&) +#define __BOOST_DI_ACCESS_WKND public +#define __BOOST_DI_VARIABLE_TEMPLATE_INIT_WKND +#endif +#if !defined(__has_builtin) +#define __has_builtin(...) 0 +#endif +#if !defined(__has_extension) +#define __has_extension(...) 0 +#endif +#if defined(__CLANG__) +#if (!BOOST_DI_CFG_DIAGNOSTICS_LEVEL) +#pragma clang diagnostic error "-Wdeprecated-declarations" +#else +#pragma clang diagnostic warning "-Wdeprecated-declarations" +#endif +#pragma clang diagnostic push +#pragma clang diagnostic error "-Wundefined-inline" +#pragma clang diagnostic error "-Wundefined-internal" +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#elif defined(__GCC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic error "-Wdeprecated-declarations" +#if (__GNUC__ < 6) +#pragma GCC diagnostic error "-Werror" +#endif +#elif defined(__MSVC__) +#pragma warning(disable : 4503) +#pragma warning(disable : 4822) +#pragma warning(disable : 4505) +#endif +#if defined(_LIBCPP_VERSION) +#define NAMESPACE_STD_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD { +#else +#define NAMESPACE_STD_BEGIN namespace std { +#endif +#if defined(_LIBCPP_VERSION) +#define NAMESPACE_STD_END _LIBCPP_END_NAMESPACE_STD +#else +#define NAMESPACE_STD_END } +#endif +#if __has_include(<__config>) +#include <__config> +#endif +#if __has_include() +#include +#else +NAMESPACE_STD_BEGIN +template +class shared_ptr; +template +class weak_ptr; +template +class unique_ptr; +NAMESPACE_STD_END +#endif +#if __has_include() +#include +#else +NAMESPACE_STD_BEGIN +template +class vector; +NAMESPACE_STD_END +#endif +#if __has_include() +#include +#else +NAMESPACE_STD_BEGIN +template +class set; +NAMESPACE_STD_END +#endif +#if __has_include() +#include +#else +NAMESPACE_STD_BEGIN +template +class initializer_list; +NAMESPACE_STD_END +#endif +#if __has_include() +#include +#else +NAMESPACE_STD_BEGIN +template +class tuple; +NAMESPACE_STD_END +#endif +#if __has_include() +#include +#else +NAMESPACE_STD_BEGIN +template +class move_iterator; +NAMESPACE_STD_END +#endif +#if __has_include() +#include +#else +NAMESPACE_STD_BEGIN +template +struct char_traits; +NAMESPACE_STD_END +#endif +// clang-format off +#if __has_include() +// clang-format on +#include +#else +namespace boost { +template +class shared_ptr; +} +#endif +BOOST_DI_NAMESPACE_BEGIN +struct _ { + _(...) {} +}; +namespace aux { +using swallow = int[]; +template +using owner = T; +template +struct valid { + using type = int; +}; +template +using valid_t = typename valid::type; +template +struct type {}; +struct none_type {}; +template +struct non_type {}; +template +struct always { + static constexpr auto value = true; +}; +template +struct never { + static constexpr auto value = false; +}; +template +struct identity { + using type = T; +}; +template +struct type_list { + using type = type_list; +}; +template +struct bool_list { + using type = bool_list; +}; +template +struct pair { + using type = pair; + using first = T1; + using second = T2; +}; +template +struct inherit : Ts... { + using type = inherit; +}; +template +struct join { + using type = type_list<>; +}; +template +struct join { + using type = T; +}; +template +struct join, type_list, Ts...> : join, Ts...> {}; +template +struct join, type_list, type_list, type_list, type_list, type_list, + type_list, type_list, type_list, type_list, type_list, type_list, + type_list, type_list, type_list, type_list, type_list, Us...> + : join, + Us...> {}; +template +using join_t = typename join::type; +template +struct index_sequence { + using type = index_sequence; +}; +#if __has_builtin(__make_integer_seq) +template +struct integer_sequence { + using type = index_sequence; +}; +template +struct make_index_sequence_impl { + using type = typename __make_integer_seq::type; +}; +#else +template +struct build_index_sequence; +template +struct build_index_sequence, index_sequence> { + using type = index_sequence; +}; +template +struct make_index_sequence_impl { + using type = typename build_index_sequence::type, + typename make_index_sequence_impl::type>::type; +}; +template <> +struct make_index_sequence_impl<0> : index_sequence<> {}; +template <> +struct make_index_sequence_impl<1> : index_sequence<0> {}; +template <> +struct make_index_sequence_impl<2> : index_sequence<0, 1> {}; +template <> +struct make_index_sequence_impl<3> : index_sequence<0, 1, 2> {}; +template <> +struct make_index_sequence_impl<4> : index_sequence<0, 1, 2, 3> {}; +template <> +struct make_index_sequence_impl<5> : index_sequence<0, 1, 2, 3, 4> {}; +template <> +struct make_index_sequence_impl<6> : index_sequence<0, 1, 2, 3, 4, 5> {}; +template <> +struct make_index_sequence_impl<7> : index_sequence<0, 1, 2, 3, 4, 5, 6> {}; +template <> +struct make_index_sequence_impl<8> : index_sequence<0, 1, 2, 3, 4, 5, 6, 7> {}; +template <> +struct make_index_sequence_impl<9> : index_sequence<0, 1, 2, 3, 4, 5, 6, 7, 8> {}; +template <> +struct make_index_sequence_impl<10> : index_sequence<0, 1, 2, 3, 4, 5, 6, 7, 8, 9> {}; +#endif +template +using make_index_sequence = typename make_index_sequence_impl::type; +} +namespace placeholders { +__BOOST_DI_UNUSED static const struct arg { } _{}; } +template +struct named {}; +struct no_name { + constexpr auto operator()() const noexcept { return ""; } +}; +template +struct ctor_traits; +template +struct self {}; +struct ignore_policies {}; +namespace core { +template +struct any_type_fwd; +template +struct any_type_ref_fwd; +template +struct any_type_1st_fwd; +template +struct any_type_1st_ref_fwd; +struct dependency_base {}; +struct injector_base {}; +template +struct dependency__ : T { + using T::create; + using T::is_referable; + using T::try_create; +}; +template +struct injector__ : T { + using T::cfg; + using T::create_impl; + using T::create_successful_impl; +#if defined(__MSVC__) + template + using is_creatable = typename T::template is_creatable; + template + using try_create = typename T::template try_create; +#else + using T::is_creatable; + using T::try_create; +#endif +}; +template +struct array; +struct deduced {}; +struct none {}; +template +class dependency; +} +namespace scopes { +class deduce; +class instance; +class singleton; +class unique; +} +#define __BOOST_DI_REQUIRES(...) typename ::boost::ext::di::v1_2_0::aux::enable_if<__VA_ARGS__, int>::type +#define __BOOST_DI_REQUIRES_MSG(...) typename ::boost::ext::di::v1_2_0::aux::concept_check<__VA_ARGS__>::type +namespace aux { +template +T&& declval(); +template +struct integral_constant { + using type = integral_constant; + static constexpr T value = V; +}; +using true_type = integral_constant; +using false_type = integral_constant; +template +struct conditional { + using type = T; +}; +template +struct conditional { + using type = F; +}; +template +using conditional_t = typename conditional::type; +template +struct enable_if {}; +template +struct enable_if { + using type = T; +}; +template +using enable_if_t = typename enable_if::type; +template +struct concept_check { + static_assert(T::value, "constraint not satisfied"); +}; +template <> +struct concept_check { + using type = int; +}; +template +struct remove_reference { + using type = T; +}; +template +struct remove_reference { + using type = T; +}; +template +struct remove_reference { + using type = T; +}; +template +using remove_reference_t = typename remove_reference::type; +template +struct remove_pointer { + using type = T; +}; +template +struct remove_pointer { + using type = T; +}; +template +using remove_pointer_t = typename remove_pointer::type; +template +struct remove_smart_ptr { + using type = T; +}; +template +struct remove_smart_ptr> { + using type = T; +}; +template +struct remove_smart_ptr> { + using type = T; +}; +template +struct remove_smart_ptr> { + using type = T; +}; +template +struct remove_smart_ptr> { + using type = T; +}; +template +using remove_smart_ptr_t = typename remove_smart_ptr::type; +template +struct remove_qualifiers { + using type = T; +}; +template +struct remove_qualifiers { + using type = T; +}; +template +struct remove_qualifiers { + using type = T; +}; +template +struct remove_qualifiers { + using type = T; +}; +template +struct remove_qualifiers { + using type = T; +}; +template +struct remove_qualifiers { + using type = T; +}; +template +struct remove_qualifiers { + using type = T; +}; +template +struct remove_qualifiers { + using type = T; +}; +template +struct remove_qualifiers { + using type = T; +}; +template +struct remove_qualifiers { + using type = T; +}; +template +using remove_qualifiers_t = typename remove_qualifiers::type; +template +struct remove_extent { + using type = T; +}; +template +struct remove_extent { + using type = T; +}; +template +using remove_extent_t = typename remove_extent::type; +template +struct deref_type { + using type = T; +}; +template +struct deref_type> { + using type = remove_qualifiers_t::type>; +}; +template +struct deref_type> { + using type = remove_qualifiers_t::type>; +}; +template +struct deref_type> { + using type = remove_qualifiers_t::type>; +}; +template +struct deref_type> { + using type = remove_qualifiers_t::type>; +}; +template +struct deref_type> { + using type = core::array::type>>; +}; +template +struct deref_type> { + using type = core::array::type>>; +}; +template +struct deref_type> { + using type = core::array::type>>; +}; +template +using decay_t = typename deref_type>::type; +template +struct is_same : false_type {}; +template +struct is_same : true_type {}; +template +struct is_base_of : integral_constant {}; +template +struct is_class : integral_constant {}; +template +struct is_abstract : integral_constant {}; +template +struct is_polymorphic : integral_constant {}; +template +struct is_final : integral_constant {}; +template +using is_valid_expr = true_type; +#if __has_extension(is_constructible) && !((__clang_major__ == 3) && (__clang_minor__ == 5)) +template +using is_constructible = integral_constant; +#else +template +decltype(void(T(declval()...)), true_type{}) test_is_constructible(int); +template +false_type test_is_constructible(...); +template +struct is_constructible : decltype(test_is_constructible(0)) {}; +#endif +template +using is_constructible_t = typename is_constructible::type; +template +decltype(void(T{declval()...}), true_type{}) test_is_braces_constructible(int); +template +false_type test_is_braces_constructible(...); +template +using is_braces_constructible = decltype(test_is_braces_constructible(0)); +template +using is_braces_constructible_t = typename is_braces_constructible::type; +#if defined(__MSVC__) +template +struct is_copy_constructible : integral_constant {}; +template +struct is_default_constructible : integral_constant {}; +#else +template +using is_copy_constructible = is_constructible; +template +using is_default_constructible = is_constructible; +#endif +#if defined(__CLANG__) || defined(__MSVC__) +template +struct is_convertible : integral_constant {}; +#else +struct test_is_convertible__ { + template + static void test(T); +}; +template (declval()))> +true_type test_is_convertible(int); +template +false_type test_is_convertible(...); +template +using is_convertible = decltype(test_is_convertible(0)); +#endif +template > +using is_narrowed = integral_constant::value && !is_class::value && !is_same::value>; +template +struct is_array : false_type {}; +template +struct is_array : true_type {}; +template +true_type is_complete_impl(int); +template +false_type is_complete_impl(...); +template +struct is_complete : decltype(is_complete_impl(0)) {}; +template +is_base_of is_a_impl(int); +template +false_type is_a_impl(...); +template +struct is_a : decltype(is_a_impl(0)) {}; +template +struct is_unique_impl; +template +struct not_unique : false_type { + using type = not_unique; +}; +template <> +struct not_unique<> : true_type { + using type = not_unique; +}; +template +struct is_unique_impl : not_unique<> {}; +template +struct is_unique_impl + : conditional_t, T1>::value, not_unique, is_unique_impl>, Ts...>> {}; +template +using is_unique = is_unique_impl; +template +struct unique; +template +struct unique, T, Ts...> : conditional_t, inherit...>>::value, + unique, Ts...>, unique, Ts...>> {}; +template +struct unique> : type_list {}; +template +using unique_t = typename unique, Ts...>::type; +false_type has_shared_ptr__(...); +#if !defined(BOOST_DI_DISABLE_SHARED_PTR_DEDUCTION) +template +auto has_shared_ptr__(T &&) -> is_valid_expr{})>; +#endif +template +decltype(::boost::ext::di::v1_2_0::aux::declval().operator()(::boost::ext::di::v1_2_0::aux::declval()...), + ::boost::ext::di::v1_2_0::aux::true_type()) +is_invocable_impl(int); +template +::boost::ext::di::v1_2_0::aux::false_type is_invocable_impl(...); +template +struct is_invocable : decltype(is_invocable_impl(0)) {}; +struct callable_base_impl { + void operator()(...) {} +}; +template +struct callable_base : callable_base_impl, + aux::conditional_t::value && !aux::is_final::value, T, aux::none_type> {}; +template +aux::false_type is_callable_impl(T*, aux::non_type* = 0); +aux::true_type is_callable_impl(...); +template +struct is_callable : decltype(is_callable_impl((callable_base*)0)) {}; +template +struct is_empty_expr : false_type {}; +template +#if defined(__MSVC__) +struct is_empty_expr()())>> : integral_constant { +}; +#else +struct is_empty_expr()), decltype(declval()())>> : true_type { +}; +#endif +template +struct function_traits; +template +struct function_traits { + using result_type = R; + using args = type_list; +}; +template +struct function_traits { + using result_type = R; + using args = type_list; +}; +template +struct function_traits { + using result_type = R; + using args = type_list; +}; +template +struct function_traits { + using result_type = R; + using args = type_list; +}; +template +using function_traits_t = typename function_traits::args; +} +namespace core { +template ::type> +struct bindings_impl; +template +struct bindings_impl { + using type = typename T::deps; +}; +template +struct bindings_impl { + using type = aux::type_list; +}; +#if defined(__MSVC__) +template +struct bindings : aux::join_t::type...> {}; +template +using bindings_t = typename bindings::type; +#else +template +using bindings_t = aux::join_t::type...>; +#endif +} +namespace concepts { +template +struct type_ { + template + struct named { + struct is_bound_more_than_once : aux::false_type {}; + }; + struct is_bound_more_than_once : aux::false_type {}; + struct is_neither_a_dependency_nor_an_injector : aux::false_type {}; + struct has_disallowed_qualifiers : aux::false_type {}; + struct is_abstract : +#if (BOOST_DI_CFG_DIAGNOSTICS_LEVEL >= 2) + // clang-format off + decltype( + T{} + ), +// clang-format on +#endif + aux::false_type { + }; + template + struct is_not_related_to : aux::false_type {}; +}; +template +struct any_of : aux::false_type {}; +template +struct is_supported + : aux::is_same::value...>, + aux::bool_list<(aux::is_constructible::value && + (aux::is_a::value || + aux::is_a::value || aux::is_empty_expr::value))...>> { +}; +template +struct get_not_supported; +template +struct get_not_supported { + using type = T; +}; +template +struct get_not_supported + : aux::conditional::value || aux::is_a::value, + typename get_not_supported::type, T> {}; +template +struct is_unique; +template +struct unique_dependency : aux::type {}; +template +struct unique_dependency::value)> + : aux::pair, typename T::priority> {}; +template +struct is_unique> : aux::is_unique::type...> {}; +template +struct get_is_unique_error_impl : aux::true_type {}; +template +struct get_is_unique_error_impl, TPriority>>> { + using type = typename type_::template named::is_bound_more_than_once; +}; +template +struct get_is_unique_error_impl, TPriority>>> { + using type = typename type_::is_bound_more_than_once; +}; +template +struct get_is_unique_error_impl> { + using type = typename type_::is_bound_more_than_once; +}; +template +struct get_is_unique_error; +template +struct get_is_unique_error> + : get_is_unique_error_impl::type...>::type> {}; +template +using boundable_bindings = + aux::conditional_t::value, typename get_is_unique_error>::type, + typename type_::type>::is_neither_a_dependency_nor_an_injector>; +template +struct get_any_of_error : aux::conditional::value...>, + aux::bool_list::value...>>::value, + aux::true_type, any_of> {}; +template +struct is_related { + static constexpr auto value = true; +}; +template +struct is_related { + static constexpr auto value = + aux::is_base_of::value || (aux::is_convertible::value && !aux::is_narrowed::value); +}; +template +struct is_abstract { + static constexpr auto value = false; +}; +template +struct is_abstract { + static constexpr auto value = aux::is_abstract::value; +}; +auto boundable_impl(any_of<> &&) -> aux::true_type; +template +auto boundable_impl(any_of &&) + -> aux::conditional_t>::value, decltype(boundable_impl(aux::declval>())), + typename type_::has_disallowed_qualifiers>; +template +using boundable_impl__ = aux::conditional_t< + is_related::value && aux::is_complete::value, I, T>::value, + aux::conditional_t::value, T>::value, typename type_::is_abstract, aux::true_type>, + typename type_::template is_not_related_to>; +template +auto boundable_impl(I&&, T &&) -> aux::conditional_t>::value || !aux::is_complete::value, + boundable_impl__, typename type_::has_disallowed_qualifiers>; +template +auto boundable_impl(I&&, T&&, aux::valid<> &&) + -> aux::conditional_t::value && aux::is_complete::value, I, T>::value, aux::true_type, + typename type_::template is_not_related_to>; +template +auto boundable_impl(I* [], T &&) -> aux::conditional_t>::value, boundable_impl__, + typename type_::has_disallowed_qualifiers>; +template +auto boundable_impl(I[], T &&) -> aux::conditional_t>::value, boundable_impl__, + typename type_::has_disallowed_qualifiers>; +template +auto boundable_impl(aux::type_list &&) -> boundable_bindings; +template +auto boundable_impl(concepts::any_of&&, T &&) -> + typename get_any_of_error(), aux::declval()))...>::type; +template +auto boundable_impl(aux::type &&) -> typename get_is_unique_error_impl::type>::type; +aux::true_type boundable_impl(...); +template +struct boundable__ { + using type = decltype(boundable_impl(aux::declval()...)); +}; +template +using boundable = typename boundable__::type; +} +namespace type_traits { +struct stack {}; +struct heap {}; +template +struct memory_traits { + using type = stack; +}; +template +struct memory_traits { + using type = heap; +}; +template +struct memory_traits { + using type = typename memory_traits::type; +}; +template +struct memory_traits> { + using type = heap; +}; +template +struct memory_traits> { + using type = heap; +}; +template +struct memory_traits> { + using type = heap; +}; +template +struct memory_traits> { + using type = heap; +}; +template +struct memory_traits::value)> { + using type = heap; +}; +} +namespace concepts { +template +struct scope { + struct is_referable {}; + struct try_create {}; + struct create {}; + template + struct requires_ : aux::false_type {}; +}; +template +struct scope__ { + template + struct scope { + template + using is_referable = aux::true_type; + template + T try_create(const TProvider&); + template + T create(const TProvider&); + }; +}; +template +struct config__ { + template + struct scope_traits { + using type = scope__; + }; + template + struct memory_traits { + using type = type_traits::heap; + }; +}; +template +struct provider__ { + using config = config__; + template + aux::conditional_t::value, T, T*> try_get(const TMemory& = {}) const; + template + T* get(const TMemory& = {}) const { + return nullptr; + } + config& cfg() const; +}; +template +typename scope::template requires_::is_referable, typename scope<_, _>::try_create, + typename scope<_, _>::create> +scopable_impl(...); +template +auto scopable_impl(T &&) + -> aux::is_valid_expr::template is_referable<_, config__<_>>, + decltype(T::template scope<_, _>::template try_create<_, _>(provider__<_>{})), + decltype(aux::declval>().template create<_, _>(provider__<_>{}))>; +template +struct scopable__ { + using type = decltype(scopable_impl(aux::declval())); +}; +template +using scopable = typename scopable__::type; +} +namespace core { +template > +struct pool; +template +using pool_t = pool>; +template +struct pool> : TArgs... { + template + explicit pool(Ts... args) noexcept : Ts(static_cast(args))... {} + template + pool(const aux::type_list&, TPool p) noexcept : pool(static_cast(p)...) { + (void)p; + } + template + pool& operator=(T&& other) noexcept { + (void)aux::swallow{0, (static_cast(*this).operator=(static_cast(other)), 0)...}; + return *this; + } +}; +} +#if !defined(BOOST_DI_CFG_CTOR_LIMIT_SIZE) +#define BOOST_DI_CFG_CTOR_LIMIT_SIZE 10 +#endif +namespace type_traits { +template +struct is_injectable : ::boost::ext::di::v1_2_0::aux::false_type {}; +template +struct is_injectable> + : ::boost::ext::di::v1_2_0::aux::true_type {}; +struct direct {}; +struct uniform {}; +template +using get = T; +template