From 887574d49e9dae4cd40756cf671ee9ad736d627f Mon Sep 17 00:00:00 2001 From: Devin <70994866+RetroGamer02@users.noreply.github.com> Date: Fri, 4 Nov 2022 06:57:54 -0400 Subject: [PATCH 01/17] Initial commit --- Makefile | 19 +- enhancements/60fps.patch | 4 +- libctru/include/3ds.h | 152 +++ libctru/include/3ds/3dslink.h | 34 + libctru/include/3ds/allocator/linear.h | 47 + libctru/include/3ds/allocator/mappable.h | 27 + libctru/include/3ds/allocator/vram.h | 71 + libctru/include/3ds/applets/error.h | 91 ++ libctru/include/3ds/applets/miiselector.h | 188 +++ libctru/include/3ds/applets/swkbd.h | 321 +++++ libctru/include/3ds/archive.h | 43 + libctru/include/3ds/asminc.h | 21 + libctru/include/3ds/console.h | 176 +++ libctru/include/3ds/env.h | 73 ++ libctru/include/3ds/errf.h | 122 ++ libctru/include/3ds/exheader.h | 199 +++ libctru/include/3ds/font.h | 236 ++++ libctru/include/3ds/gdbhio.h | 29 + libctru/include/3ds/gdbhio_dev.h | 37 + libctru/include/3ds/gfx.h | 180 +++ libctru/include/3ds/gpu/enums.h | 504 ++++++++ libctru/include/3ds/gpu/gpu.h | 119 ++ libctru/include/3ds/gpu/gx.h | 206 +++ libctru/include/3ds/gpu/registers.h | 767 +++++++++++ libctru/include/3ds/gpu/shaderProgram.h | 120 ++ libctru/include/3ds/gpu/shbin.h | 130 ++ libctru/include/3ds/ipc.h | 118 ++ libctru/include/3ds/mii.h | 159 +++ libctru/include/3ds/ndsp/channel.h | 235 ++++ libctru/include/3ds/ndsp/ndsp.h | 204 +++ libctru/include/3ds/os.h | 349 +++++ libctru/include/3ds/result.h | 191 +++ libctru/include/3ds/romfs.h | 93 ++ libctru/include/3ds/services/ac.h | 130 ++ libctru/include/3ds/services/am.h | 538 ++++++++ libctru/include/3ds/services/ampxi.h | 35 + libctru/include/3ds/services/apt.h | 568 ++++++++ libctru/include/3ds/services/boss.h | 180 +++ libctru/include/3ds/services/cam.h | 703 ++++++++++ libctru/include/3ds/services/cfgnor.h | 53 + libctru/include/3ds/services/cfgu.h | 220 ++++ libctru/include/3ds/services/csnd.h | 433 +++++++ libctru/include/3ds/services/dsp.h | 188 +++ libctru/include/3ds/services/frd.h | 261 ++++ libctru/include/3ds/services/fs.h | 1089 ++++++++++++++++ libctru/include/3ds/services/fspxi.h | 613 +++++++++ libctru/include/3ds/services/fsreg.h | 63 + libctru/include/3ds/services/gspgpu.h | 264 ++++ libctru/include/3ds/services/gsplcd.h | 72 ++ libctru/include/3ds/services/hid.h | 217 ++++ libctru/include/3ds/services/httpc.h | 301 +++++ libctru/include/3ds/services/ir.h | 93 ++ libctru/include/3ds/services/irrst.h | 65 + libctru/include/3ds/services/loader.h | 43 + libctru/include/3ds/services/mcuhwc.h | 85 ++ libctru/include/3ds/services/mic.h | 128 ++ libctru/include/3ds/services/mvd.h | 168 +++ libctru/include/3ds/services/ndm.h | 148 +++ libctru/include/3ds/services/news.h | 91 ++ libctru/include/3ds/services/nfc.h | 220 ++++ libctru/include/3ds/services/nim.h | 151 +++ libctru/include/3ds/services/ns.h | 51 + libctru/include/3ds/services/nwmext.h | 13 + libctru/include/3ds/services/pmapp.h | 123 ++ libctru/include/3ds/services/pmdbg.h | 41 + libctru/include/3ds/services/ps.h | 116 ++ libctru/include/3ds/services/ptmgets.h | 25 + libctru/include/3ds/services/ptmsets.h | 25 + libctru/include/3ds/services/ptmsysm.h | 132 ++ libctru/include/3ds/services/ptmu.h | 53 + libctru/include/3ds/services/pxidev.h | 79 ++ libctru/include/3ds/services/pxipm.h | 42 + libctru/include/3ds/services/qtm.h | 57 + libctru/include/3ds/services/soc.h | 148 +++ libctru/include/3ds/services/srvpm.h | 45 + libctru/include/3ds/services/sslc.h | 251 ++++ libctru/include/3ds/services/uds.h | 368 ++++++ libctru/include/3ds/services/y2r.h | 500 +++++++ libctru/include/3ds/srv.h | 141 ++ libctru/include/3ds/svc.h | 1440 +++++++++++++++++++++ libctru/include/3ds/synchronization.h | 344 +++++ libctru/include/3ds/thread.h | 120 ++ libctru/include/3ds/types.h | 79 ++ libctru/include/3ds/util/decompress.h | 238 ++++ libctru/include/3ds/util/rbtree.h | 149 +++ libctru/include/3ds/util/utf.h | 155 +++ libctru/include/arpa/inet.h | 39 + libctru/include/c3d/attribs.h | 16 + libctru/include/c3d/base.h | 46 + libctru/include/c3d/buffers.h | 21 + libctru/include/c3d/effect.h | 15 + libctru/include/c3d/fog.h | 39 + libctru/include/c3d/framebuffer.h | 69 + libctru/include/c3d/light.h | 149 +++ libctru/include/c3d/lightlut.h | 35 + libctru/include/c3d/maths.h | 791 +++++++++++ libctru/include/c3d/mtxstack.h | 24 + libctru/include/c3d/proctex.h | 125 ++ libctru/include/c3d/renderqueue.h | 79 ++ libctru/include/c3d/texenv.h | 98 ++ libctru/include/c3d/texture.h | 183 +++ libctru/include/c3d/types.h | 81 ++ libctru/include/c3d/uniforms.h | 78 ++ libctru/include/citro3d.h | 34 + libctru/include/netdb.h | 78 ++ libctru/include/netinet/in.h | 45 + libctru/include/netinet/tcp.h | 9 + libctru/include/poll.h | 29 + libctru/include/sys/ioctl.h | 13 + libctru/include/sys/select.h | 1 + libctru/include/sys/socket.h | 89 ++ libctru/include/tex3ds.h | 215 +++ src/pc/audio/audio_3ds.c | 13 +- src/pc/gfx/gfx_3ds.c | 4 +- tools/Makefile | 2 +- tools/audiofile/Makefile | 2 +- tools/audiofile/audiofile.cpp | 5 +- tools/sdk-tools/tabledesign/Makefile | 2 +- 118 files changed, 19530 insertions(+), 19 deletions(-) create mode 100644 libctru/include/3ds.h create mode 100644 libctru/include/3ds/3dslink.h create mode 100644 libctru/include/3ds/allocator/linear.h create mode 100644 libctru/include/3ds/allocator/mappable.h create mode 100644 libctru/include/3ds/allocator/vram.h create mode 100644 libctru/include/3ds/applets/error.h create mode 100644 libctru/include/3ds/applets/miiselector.h create mode 100644 libctru/include/3ds/applets/swkbd.h create mode 100644 libctru/include/3ds/archive.h create mode 100644 libctru/include/3ds/asminc.h create mode 100644 libctru/include/3ds/console.h create mode 100644 libctru/include/3ds/env.h create mode 100644 libctru/include/3ds/errf.h create mode 100644 libctru/include/3ds/exheader.h create mode 100644 libctru/include/3ds/font.h create mode 100644 libctru/include/3ds/gdbhio.h create mode 100644 libctru/include/3ds/gdbhio_dev.h create mode 100644 libctru/include/3ds/gfx.h create mode 100644 libctru/include/3ds/gpu/enums.h create mode 100644 libctru/include/3ds/gpu/gpu.h create mode 100644 libctru/include/3ds/gpu/gx.h create mode 100644 libctru/include/3ds/gpu/registers.h create mode 100644 libctru/include/3ds/gpu/shaderProgram.h create mode 100644 libctru/include/3ds/gpu/shbin.h create mode 100644 libctru/include/3ds/ipc.h create mode 100644 libctru/include/3ds/mii.h create mode 100644 libctru/include/3ds/ndsp/channel.h create mode 100644 libctru/include/3ds/ndsp/ndsp.h create mode 100644 libctru/include/3ds/os.h create mode 100644 libctru/include/3ds/result.h create mode 100644 libctru/include/3ds/romfs.h create mode 100644 libctru/include/3ds/services/ac.h create mode 100644 libctru/include/3ds/services/am.h create mode 100644 libctru/include/3ds/services/ampxi.h create mode 100644 libctru/include/3ds/services/apt.h create mode 100644 libctru/include/3ds/services/boss.h create mode 100644 libctru/include/3ds/services/cam.h create mode 100644 libctru/include/3ds/services/cfgnor.h create mode 100644 libctru/include/3ds/services/cfgu.h create mode 100644 libctru/include/3ds/services/csnd.h create mode 100644 libctru/include/3ds/services/dsp.h create mode 100644 libctru/include/3ds/services/frd.h create mode 100644 libctru/include/3ds/services/fs.h create mode 100644 libctru/include/3ds/services/fspxi.h create mode 100644 libctru/include/3ds/services/fsreg.h create mode 100644 libctru/include/3ds/services/gspgpu.h create mode 100644 libctru/include/3ds/services/gsplcd.h create mode 100644 libctru/include/3ds/services/hid.h create mode 100644 libctru/include/3ds/services/httpc.h create mode 100644 libctru/include/3ds/services/ir.h create mode 100644 libctru/include/3ds/services/irrst.h create mode 100644 libctru/include/3ds/services/loader.h create mode 100644 libctru/include/3ds/services/mcuhwc.h create mode 100644 libctru/include/3ds/services/mic.h create mode 100644 libctru/include/3ds/services/mvd.h create mode 100644 libctru/include/3ds/services/ndm.h create mode 100644 libctru/include/3ds/services/news.h create mode 100644 libctru/include/3ds/services/nfc.h create mode 100644 libctru/include/3ds/services/nim.h create mode 100644 libctru/include/3ds/services/ns.h create mode 100644 libctru/include/3ds/services/nwmext.h create mode 100644 libctru/include/3ds/services/pmapp.h create mode 100644 libctru/include/3ds/services/pmdbg.h create mode 100644 libctru/include/3ds/services/ps.h create mode 100644 libctru/include/3ds/services/ptmgets.h create mode 100644 libctru/include/3ds/services/ptmsets.h create mode 100644 libctru/include/3ds/services/ptmsysm.h create mode 100644 libctru/include/3ds/services/ptmu.h create mode 100644 libctru/include/3ds/services/pxidev.h create mode 100644 libctru/include/3ds/services/pxipm.h create mode 100644 libctru/include/3ds/services/qtm.h create mode 100644 libctru/include/3ds/services/soc.h create mode 100644 libctru/include/3ds/services/srvpm.h create mode 100644 libctru/include/3ds/services/sslc.h create mode 100644 libctru/include/3ds/services/uds.h create mode 100644 libctru/include/3ds/services/y2r.h create mode 100644 libctru/include/3ds/srv.h create mode 100644 libctru/include/3ds/svc.h create mode 100644 libctru/include/3ds/synchronization.h create mode 100644 libctru/include/3ds/thread.h create mode 100644 libctru/include/3ds/types.h create mode 100644 libctru/include/3ds/util/decompress.h create mode 100644 libctru/include/3ds/util/rbtree.h create mode 100644 libctru/include/3ds/util/utf.h create mode 100644 libctru/include/arpa/inet.h create mode 100644 libctru/include/c3d/attribs.h create mode 100644 libctru/include/c3d/base.h create mode 100644 libctru/include/c3d/buffers.h create mode 100644 libctru/include/c3d/effect.h create mode 100644 libctru/include/c3d/fog.h create mode 100644 libctru/include/c3d/framebuffer.h create mode 100644 libctru/include/c3d/light.h create mode 100644 libctru/include/c3d/lightlut.h create mode 100644 libctru/include/c3d/maths.h create mode 100644 libctru/include/c3d/mtxstack.h create mode 100644 libctru/include/c3d/proctex.h create mode 100644 libctru/include/c3d/renderqueue.h create mode 100644 libctru/include/c3d/texenv.h create mode 100644 libctru/include/c3d/texture.h create mode 100644 libctru/include/c3d/types.h create mode 100644 libctru/include/c3d/uniforms.h create mode 100644 libctru/include/citro3d.h create mode 100644 libctru/include/netdb.h create mode 100644 libctru/include/netinet/in.h create mode 100644 libctru/include/netinet/tcp.h create mode 100644 libctru/include/poll.h create mode 100644 libctru/include/sys/ioctl.h create mode 100644 libctru/include/sys/select.h create mode 100644 libctru/include/sys/socket.h create mode 100644 libctru/include/tex3ds.h diff --git a/Makefile b/Makefile index f9bf60a18c..c8ba797fb9 100644 --- a/Makefile +++ b/Makefile @@ -495,10 +495,11 @@ ifeq ($(TARGET_WEB),1) PLATFORM_LDFLAGS := -lm -no-pie -s TOTAL_MEMORY=20MB -g4 --source-map-base http://localhost:8080/ -s "EXTRA_EXPORTED_RUNTIME_METHODS=['callMain']" endif ifeq ($(TARGET_N3DS),1) - CTRULIB := $(DEVKITPRO)/libctru + #CTRULIB := $(DEVKITPRO)/libctru + CTRULIB := $(CURDIR)/libctru LIBDIRS := $(CTRULIB) export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) - PLATFORM_CFLAGS := -mtp=soft -DTARGET_N3DS -DARM11 -DosGetTime=n64_osGetTime -D_3DS -march=armv6k -mtune=mpcore -mfloat-abi=hard -mword-relocations -fomit-frame-pointer -ffast-math $(foreach dir,$(LIBDIRS),-I$(dir)/include) + PLATFORM_CFLAGS := -mtp=soft -DTARGET_N3DS -DARM11 -DosGetTime=n64_osGetTime -D_3DS -D__3DS__ -march=armv6k -mtune=mpcore -mfloat-abi=hard -mword-relocations -fomit-frame-pointer -ffast-math $(foreach dir,$(LIBDIRS),-I$(dir)/include) PLATFORM_LDFLAGS := $(LIBPATHS) -lcitro3d -lctru -lm -specs=3dsx.specs -g -marm -mthumb-interwork -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft ifeq ($(DISABLE_AUDIO),1) PLATFORM_CFLAGS += -DDISABLE_AUDIO @@ -545,8 +546,8 @@ else endif CC_CHECK := $(CC) -fsyntax-only -fsigned-char $(INCLUDE_CFLAGS) -Wall -Wextra -Wno-format-security -D_LANGUAGE_C $(VERSION_CFLAGS) $(MATCH_CFLAGS) $(PLATFORM_CFLAGS) $(GFX_CFLAGS) $(GRUCODE_CFLAGS) -CFLAGS := $(OPT_FLAGS) $(INCLUDE_CFLAGS) -D_LANGUAGE_C $(VERSION_CFLAGS) $(MATCH_CFLAGS) $(PLATFORM_CFLAGS) $(GFX_CFLAGS) $(GRUCODE_CFLAGS) $(MARCH_FLAGS) -fno-strict-aliasing -fwrapv - +CFLAGS := -O3 $(INCLUDE_CFLAGS) -D_LANGUAGE_C $(VERSION_CFLAGS) $(MATCH_CFLAGS) $(PLATFORM_CFLAGS) $(GFX_CFLAGS) $(GRUCODE_CFLAGS) $(MARCH_FLAGS) -fno-strict-aliasing -fwrapv +#$(OPT_FLAGS) ASFLAGS := -I include -I $(BUILD_DIR) $(VERSION_ASFLAGS) LDFLAGS := $(PLATFORM_LDFLAGS) $(GFX_LDFLAGS) @@ -901,15 +902,15 @@ $(ELF): $(O_FILES) $(MIO0_FILES:.mio0=.o) $(SOUND_OBJ_FILES) $(ULTRA_O_FILES) $( $(LD) -L $(BUILD_DIR) -o $@ $(O_FILES) $(BUILD_DIR)/src/pc/gfx/shader.shbin.o $(MINIMAP_T3X_O) $(SOUND_OBJ_FILES) $(ULTRA_O_FILES) $(GODDARD_O_FILES) $(LDFLAGS) $(EXE): $(ELF) - 3dsxtool $< $@ --smdh=$(BUILD_DIR)/$(SMDH_ICON) + C:\\devkitPro\\tools\\bin\\3dsxtool $< $@ --smdh=$(BUILD_DIR)/$(SMDH_ICON) $(CIA): $(ELF) @echo "Generating $@, please wait..." - makerom -f cia -o "$@" -rsf 3ds/template.rsf -target t -elf "$<" -icon 3ds/icon.icn -banner 3ds/banner.bnr + C:\\devkitPro\\tools\\bin\\makerom -f cia -o "$@" -rsf 3ds/template.rsf -target t -elf "$<" -icon 3ds/icon.icn -banner 3ds/banner.bnr # stolen from /opt/devkitpro/devkitARM/base_tools define bin2o - bin2s -a 4 -H $(BUILD_DIR)/$(MINIMAP_TEXTURES)/`(echo $( $(BUILD_DIR)/$@ %.smdh: %.png - smdhtool --create "$(SMDH_TITLE)" "$(SMDH_DESCRIPTION)" "$(SMDH_AUTHOR)" $< $(BUILD_DIR)/$@ + C:\\devkitPro\\tools\\bin\\smdhtool --create "$(SMDH_TITLE)" "$(SMDH_DESCRIPTION)" "$(SMDH_AUTHOR)" $< $(BUILD_DIR)/$@ else $(EXE): $(O_FILES) $(MIO0_FILES:.mio0=.o) $(SOUND_OBJ_FILES) $(ULTRA_O_FILES) $(GODDARD_O_FILES) diff --git a/enhancements/60fps.patch b/enhancements/60fps.patch index 0deb2e0f4e..b695f223e2 100644 --- a/enhancements/60fps.patch +++ b/enhancements/60fps.patch @@ -6,8 +6,8 @@ index 619f84d..fa11f9a 100644 CTRULIB := $(DEVKITPRO)/libctru LIBDIRS := $(CTRULIB) export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) -- PLATFORM_CFLAGS := -mtp=soft -DTARGET_N3DS -DARM11 -DosGetTime=n64_osGetTime -D_3DS -march=armv6k -mtune=mpcore -mfloat-abi=hard -mword-relocations -fomit-frame-pointer -ffast-math $(foreach dir,$(LIBDIRS),-I$(dir)/include) -+ PLATFORM_CFLAGS := -mtp=soft -DTARGET_N3DS -D_60FPS_PATCH -DARM11 -DosGetTime=n64_osGetTime -D_3DS -march=armv6k -mtune=mpcore -mfloat-abi=hard -mword-relocations -fomit-frame-pointer -ffast-math $(foreach dir,$(LIBDIRS),-I$(dir)/include) +- PLATFORM_CFLAGS := -mtp=soft -DTARGET_N3DS -DARM11 -DosGetTime=n64_osGetTime -D_3DS -D__3DS__ -march=armv6k -mtune=mpcore -mfloat-abi=hard -mword-relocations -fomit-frame-pointer -ffast-math $(foreach dir,$(LIBDIRS),-I$(dir)/include) ++ PLATFORM_CFLAGS := -mtp=soft -DTARGET_N3DS -D_60FPS_PATCH -DARM11 -DosGetTime=n64_osGetTime -D_3DS -D__3DS__ -march=armv6k -mtune=mpcore -mfloat-abi=hard -mword-relocations -fomit-frame-pointer -ffast-math $(foreach dir,$(LIBDIRS),-I$(dir)/include) PLATFORM_LDFLAGS := $(LIBPATHS) -lcitro3d -lctru -lm -specs=3dsx.specs -g -marm -mthumb-interwork -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft ifeq ($(DISABLE_AUDIO),1) PLATFORM_CFLAGS += -DDISABLE_AUDIO diff --git a/libctru/include/3ds.h b/libctru/include/3ds.h new file mode 100644 index 0000000000..9aeb842d62 --- /dev/null +++ b/libctru/include/3ds.h @@ -0,0 +1,152 @@ +/** + * @file 3ds.h + * @brief Central 3DS header. Includes all others. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_3DS) && !defined(__3DS__) +#warning "Please update your Makefile and replace -DARM11 -D_3DS with -D__3DS__" +#define __3DS__ +#endif + +//might be missing some +#include <3ds/types.h> +#include <3ds/result.h> +#include <3ds/ipc.h> +#include <3ds/svc.h> +#include <3ds/exheader.h> +#include <3ds/srv.h> +#include <3ds/errf.h> +#include <3ds/os.h> +#include <3ds/synchronization.h> +#include <3ds/thread.h> +#include <3ds/gfx.h> +#include <3ds/console.h> +#include <3ds/env.h> +#include <3ds/util/decompress.h> +#include <3ds/util/utf.h> + +#include <3ds/allocator/linear.h> +#include <3ds/allocator/mappable.h> +#include <3ds/allocator/vram.h> + +#include <3ds/services/ac.h> +#include <3ds/services/am.h> +#include <3ds/services/ampxi.h> +#include <3ds/services/apt.h> +#include <3ds/services/boss.h> +#include <3ds/services/cam.h> +#include <3ds/services/cfgnor.h> +#include <3ds/services/cfgu.h> +#include <3ds/services/csnd.h> +#include <3ds/services/dsp.h> +#include <3ds/services/fs.h> +#include <3ds/services/fspxi.h> +#include <3ds/services/fsreg.h> +#include <3ds/services/frd.h> +#include <3ds/services/gspgpu.h> +#include <3ds/services/gsplcd.h> +#include <3ds/services/hid.h> +#include <3ds/services/irrst.h> +#include <3ds/services/sslc.h> +#include <3ds/services/httpc.h> +#include <3ds/services/uds.h> +#include <3ds/services/ndm.h> +#include <3ds/services/nim.h> +#include <3ds/services/nwmext.h> +#include <3ds/services/ir.h> +#include <3ds/services/ns.h> +#include <3ds/services/pmapp.h> +#include <3ds/services/pmdbg.h> +#include <3ds/services/ps.h> +#include <3ds/services/ptmu.h> +#include <3ds/services/ptmsysm.h> +#include <3ds/services/ptmgets.h> +#include <3ds/services/ptmsets.h> +#include <3ds/services/pxidev.h> +#include <3ds/services/pxipm.h> +#include <3ds/services/soc.h> +#include <3ds/services/mic.h> +#include <3ds/services/mvd.h> +#include <3ds/services/nfc.h> +#include <3ds/services/news.h> +#include <3ds/services/qtm.h> +#include <3ds/services/srvpm.h> +#include <3ds/services/loader.h> +#include <3ds/services/y2r.h> +#include <3ds/services/mcuhwc.h> + +#include <3ds/gpu/gx.h> +#include <3ds/gpu/gpu.h> +#include <3ds/gpu/shbin.h> +#include <3ds/gpu/shaderProgram.h> + +#include <3ds/ndsp/ndsp.h> +#include <3ds/ndsp/channel.h> + +#include <3ds/applets/swkbd.h> +#include <3ds/applets/error.h> + +#include <3ds/applets/miiselector.h> + +#include <3ds/archive.h> +#include <3ds/romfs.h> +#include <3ds/font.h> +#include <3ds/mii.h> + +#include <3ds/gdbhio_dev.h> +#include <3ds/3dslink.h> + +#ifdef __cplusplus +} +#endif +/** + * @example app_launch/source/main.c + * @example audio/filters/source/main.c + * @example audio/mic/source/main.c + * @example audio/streaming/source/main.c + * @example camera/image/source/main.c + * @example camera/video/source/main.c + * @example get_system_language/source/main.c + * @example graphics/bitmap/24bit-color/source/main.c + * @example graphics/gpu/both_screens/source/main.c + * @example graphics/gpu/fragment_light/source/main.c + * @example graphics/gpu/geoshader/source/main.c + * @example graphics/gpu/gpusprites/source/main.c + * @example graphics/gpu/immediate/source/main.c + * @example graphics/gpu/lenny/source/main.c + * @example graphics/gpu/loop_subdivision/source/main.c + * @example graphics/gpu/mipmap_fog/source/main.c + * @example graphics/gpu/particles/source/main.c + * @example graphics/gpu/proctex/source/main.c + * @example graphics/gpu/simple_tri/source/main.c + * @example graphics/gpu/textured_cube/source/main.c + * @example graphics/gpu/toon_shading/source/main.c + * @example graphics/printing/both-screen-text/source/main.c + * @example graphics/printing/colored-text/source/main.c + * @example graphics/printing/hello-world/source/main.c + * @example graphics/printing/multiple-windows-text/source/main.c + * @example graphics/printing/system-font/source/main.c + * @example input/read-controls/source/main.c + * @example input/software-keyboard/source/main.c + * @example input/touch-screen/source/main.c + * @example libapplet_launch/source/main.c + * @example mvd/source/main.c + * @example network/boss/source/main.c + * @example network/http/source/main.c + * @example network/http_post/source/main.c + * @example network/sockets/source/sockets.c + * @example network/sslc/source/ssl.c + * @example network/uds/source/uds.c + * @example nfc/source/main.c + * @example qtm/source/main.c + * @example romfs/source/main.c + * @example sdmc/source/main.c + * @example threads/event/source/main.c + * @example threads/thread-basic/source/main.c + * @example time/rtc/source/main.c + */ diff --git a/libctru/include/3ds/3dslink.h b/libctru/include/3ds/3dslink.h new file mode 100644 index 0000000000..234f941f4b --- /dev/null +++ b/libctru/include/3ds/3dslink.h @@ -0,0 +1,34 @@ +/** + * @file 3dslink.h + * @brief Netloader (3dslink) utilities + */ + +#pragma once + +#include + +struct in_addr; + +/// Address of the host connected through 3dslink +extern struct in_addr __3dslink_host; + +#define LINK3DS_COMM_PORT 17491 ///< 3dslink TCP server port + +/** + * @brief Connects to the 3dslink host, setting up an output stream. + * @param[in] redirStdout Whether to redirect stdout to nxlink output. + * @param[in] redirStderr Whether to redirect stderr to nxlink output. + * @return Socket fd on success, negative number on failure. + * @note The socket should be closed with close() during application cleanup. + */ +int link3dsConnectToHost(bool redirStdout, bool redirStderr); + +/// Same as \ref link3dsConnectToHost but redirecting both stdout/stderr. +static inline int link3dsStdio(void) { + return link3dsConnectToHost(true, true); +} + +/// Same as \ref link3dsConnectToHost but redirecting only stderr. +static inline int link3dsStdioForDebug(void) { + return link3dsConnectToHost(false, true); +} diff --git a/libctru/include/3ds/allocator/linear.h b/libctru/include/3ds/allocator/linear.h new file mode 100644 index 0000000000..1e99367622 --- /dev/null +++ b/libctru/include/3ds/allocator/linear.h @@ -0,0 +1,47 @@ +/** + * @file linear.h + * @brief Linear memory allocator. + */ +#pragma once + +/** + * @brief Allocates a 0x80-byte aligned buffer. + * @param size Size of the buffer to allocate. + * @return The allocated buffer. + */ +void* linearAlloc(size_t size); + +/** + * @brief Allocates a buffer aligned to the given size. + * @param size Size of the buffer to allocate. + * @param alignment Alignment to use. + * @return The allocated buffer. + */ +void* linearMemAlign(size_t size, size_t alignment); + +/** + * @brief Reallocates a buffer. + * Note: Not implemented yet. + * @param mem Buffer to reallocate. + * @param size Size of the buffer to allocate. + * @return The reallocated buffer. + */ +void* linearRealloc(void* mem, size_t size); + +/** + * @brief Retrieves the allocated size of a buffer. + * @return The size of the buffer. + */ +size_t linearGetSize(void* mem); + +/** + * @brief Frees a buffer. + * @param mem Buffer to free. + */ +void linearFree(void* mem); + +/** + * @brief Gets the current linear free space. + * @return The current linear free space. + */ +u32 linearSpaceFree(void); diff --git a/libctru/include/3ds/allocator/mappable.h b/libctru/include/3ds/allocator/mappable.h new file mode 100644 index 0000000000..fb57dc1ac3 --- /dev/null +++ b/libctru/include/3ds/allocator/mappable.h @@ -0,0 +1,27 @@ +/** + * @file mappable.h + * @brief Mappable memory allocator. + */ +#pragma once + +#include <3ds/types.h> + +/** + * @brief Initializes the mappable allocator. + * @param addrMin Minimum address. + * @param addrMax Maxium address. + */ +void mappableInit(u32 addrMin, u32 addrMax); + +/** + * @brief Finds a mappable memory area. + * @param size Size of the area to find. + * @return The mappable area. + */ +void* mappableAlloc(size_t size); + +/** + * @brief Frees a mappable area (stubbed). + * @param mem Mappable area to free. + */ +void mappableFree(void* mem); diff --git a/libctru/include/3ds/allocator/vram.h b/libctru/include/3ds/allocator/vram.h new file mode 100644 index 0000000000..a1e593a056 --- /dev/null +++ b/libctru/include/3ds/allocator/vram.h @@ -0,0 +1,71 @@ +/** + * @file vram.h + * @brief VRAM allocator. + */ +#pragma once + +typedef enum vramAllocPos +{ + VRAM_ALLOC_A = BIT(0), + VRAM_ALLOC_B = BIT(1), + VRAM_ALLOC_ANY = VRAM_ALLOC_A | VRAM_ALLOC_B, +} vramAllocPos; + +/** + * @brief Allocates a 0x80-byte aligned buffer. + * @param size Size of the buffer to allocate. + * @return The allocated buffer. + */ +void* vramAlloc(size_t size); + +/** + * @brief Allocates a 0x80-byte aligned buffer in the given VRAM bank. + * @param size Size of the buffer to allocate. + * @param pos VRAM bank to use (see \ref vramAllocPos). + * @return The allocated buffer. + */ +void* vramAllocAt(size_t size, vramAllocPos pos); + +/** + * @brief Allocates a buffer aligned to the given size. + * @param size Size of the buffer to allocate. + * @param alignment Alignment to use. + * @return The allocated buffer. + */ +void* vramMemAlign(size_t size, size_t alignment); + +/** + * @brief Allocates a buffer aligned to the given size in the given VRAM bank. + * @param size Size of the buffer to allocate. + * @param alignment Alignment to use. + * @param pos VRAM bank to use (see \ref vramAllocPos). + * @return The allocated buffer. + */ +void* vramMemAlignAt(size_t size, size_t alignment, vramAllocPos pos); + +/** + * @brief Reallocates a buffer. + * Note: Not implemented yet. + * @param mem Buffer to reallocate. + * @param size Size of the buffer to allocate. + * @return The reallocated buffer. + */ +void* vramRealloc(void* mem, size_t size); + +/** + * @brief Retrieves the allocated size of a buffer. + * @return The size of the buffer. + */ +size_t vramGetSize(void* mem); + +/** + * @brief Frees a buffer. + * @param mem Buffer to free. + */ +void vramFree(void* mem); + +/** + * @brief Gets the current VRAM free space. + * @return The current VRAM free space. + */ +u32 vramSpaceFree(void); diff --git a/libctru/include/3ds/applets/error.h b/libctru/include/3ds/applets/error.h new file mode 100644 index 0000000000..97d1a5ddbd --- /dev/null +++ b/libctru/include/3ds/applets/error.h @@ -0,0 +1,91 @@ +/** + * @file error.h + * @brief Error applet. + */ +#pragma once +#include <3ds/types.h> + + enum +{ + ERROR_LANGUAGE_FLAG = 0x100, /// +#include <3ds/mii.h> + +/// Magic value needed to launch the applet. +#define MIISELECTOR_MAGIC 0x13DE28CF + +/// Maximum length of title to be displayed at the top of the Mii selector applet +#define MIISELECTOR_TITLE_LEN 64 + +/// Number of Guest Miis available for selection +#define MIISELECTOR_GUESTMII_SLOTS 6 + +/// Maximum number of user Miis available for selection +#define MIISELECTOR_USERMII_SLOTS 100 + +/// Parameter structure passed to AppletEd +typedef struct +{ + u8 enable_cancel_button; ///< Enables canceling of selection if nonzero. + u8 enable_selecting_guests; ///< Makes Guets Miis selectable if nonzero. + u8 show_on_top_screen; ///< Shows applet on top screen if nonzero, + ///< otherwise show it on the bottom screen. + u8 _unk0x3[5]; ///< @private + u16 title[MIISELECTOR_TITLE_LEN]; ///< UTF16-LE string displayed at the top of the applet. If + ///< set to the empty string, a default title is displayed. + u8 _unk0x88[4]; ///< @private + u8 show_guest_page; ///< If nonzero, the applet shows a page with Guest + ///< Miis on launch. + u8 _unk0x8D[3]; ///< @private + u32 initial_index; ///< Index of the initially selected Mii. If + ///< @ref MiiSelectorConf.show_guest_page is + ///< set, this is the index of a Guest Mii, + ///< otherwise that of a user Mii. + u8 mii_guest_whitelist[MIISELECTOR_GUESTMII_SLOTS]; ///< Each byte set to a nonzero value + ///< enables its corresponding Guest + ///< Mii to be enabled for selection. + u8 mii_whitelist[MIISELECTOR_USERMII_SLOTS]; ///< Each byte set to a nonzero value enables + ///< its corresponding user Mii to be enabled + ///< for selection. + u16 _unk0xFE; ///< @private + u32 magic; ///< Will be set to @ref MIISELECTOR_MAGIC before launching the + ///< applet. +} MiiSelectorConf; + +/// Maximum length of the localized name of a Guest Mii +#define MIISELECTOR_GUESTMII_NAME_LEN 12 + +/// Structure written by AppletEd +typedef struct +{ + u32 no_mii_selected; ///< 0 if a Mii was selected, 1 if the selection was + ///< canceled. + u32 guest_mii_was_selected; ///< 1 if a Guest Mii was selected, 0 otherwise. + u32 guest_mii_index; ///< Index of the selected Guest Mii, + ///< 0xFFFFFFFF if no guest was selected. + MiiData mii; ///< Data of selected Mii. + u16 _pad0x68; ///< @private + u16 checksum; ///< Checksum of the returned Mii data. + ///< Stored as a big-endian value; use + ///< @ref miiSelectorChecksumIsValid to + ///< verify. + u16 guest_mii_name[MIISELECTOR_GUESTMII_NAME_LEN]; ///< Localized name of a Guest Mii, + ///< if one was selected (UTF16-LE + ///< string). Zeroed otherwise. +} MiiSelectorReturn; + +/// AppletEd options +enum +{ + MIISELECTOR_CANCEL = BIT(0), ///< Show the cancel button + MIISELECTOR_GUESTS = BIT(1), ///< Make Guets Miis selectable + MIISELECTOR_TOP = BIT(2), ///< Show AppletEd on top screen + MIISELECTOR_GUESTSTART = BIT(3), ///< Start on guest page +}; + +/** + * @brief Initialize Mii selector config + * @param conf Pointer to Miiselector config. + */ +void miiSelectorInit(MiiSelectorConf *conf); + +/** + * @brief Launch the Mii selector library applet + * + * @param conf Configuration determining how the applet should behave + */ +void miiSelectorLaunch(const MiiSelectorConf *conf, MiiSelectorReturn* returnbuf); + +/** + * @brief Sets title of the Mii selector library applet + * + * @param conf Pointer to miiSelector configuration + * @param text Title text of Mii selector + */ +void miiSelectorSetTitle(MiiSelectorConf *conf, const char* text); + +/** + * @brief Specifies which special options are enabled in the Mii selector + * + * @param conf Pointer to miiSelector configuration + * @param options Options bitmask + */ +void miiSelectorSetOptions(MiiSelectorConf *conf, u32 options); + +/** + * @brief Specifies which guest Miis will be selectable + * + * @param conf Pointer to miiSelector configuration + * @param index Index of the guest Miis that will be whitelisted. + * @ref MIISELECTOR_GUESTMII_SLOTS can be used to whitelist all the guest Miis. + */ +void miiSelectorWhitelistGuestMii(MiiSelectorConf *conf, u32 index); + +/** + * @brief Specifies which guest Miis will be unselectable + * + * @param conf Pointer to miiSelector configuration + * @param index Index of the guest Miis that will be blacklisted. + * @ref MIISELECTOR_GUESTMII_SLOTS can be used to blacklist all the guest Miis. + */ +void miiSelectorBlacklistGuestMii(MiiSelectorConf *conf, u32 index); + +/** + * @brief Specifies which user Miis will be selectable + * + * @param conf Pointer to miiSelector configuration + * @param index Index of the user Miis that will be whitelisted. + * @ref MIISELECTOR_USERMII_SLOTS can be used to whitlist all the user Miis + */ +void miiSelectorWhitelistUserMii(MiiSelectorConf *conf, u32 index); + +/** + * @brief Specifies which user Miis will be selectable + * + * @param conf Pointer to miiSelector configuration + * @param index Index of the user Miis that will be blacklisted. + * @ref MIISELECTOR_USERMII_SLOTS can be used to blacklist all the user Miis + */ +void miiSelectorBlacklistUserMii(MiiSelectorConf *conf, u32 index); + +/** + * @brief Specifies which Mii the cursor should start from + * + * @param conf Pointer to miiSelector configuration + * @param index Indexed number of the Mii that the cursor will start on. + * If there is no mii with that index, the the cursor will start at the Mii + * with the index 0 (the personal Mii). + */ +static inline void miiSelectorSetInitialIndex(MiiSelectorConf *conf, u32 index) +{ + conf->initial_index = index; +} + +/** + * @brief Get Mii name + * + * @param returnbuf Pointer to miiSelector return + * @param out String containing a Mii's name + * @param max_size Size of string. Since UTF8 characters range in size from 1-3 bytes + * (assuming that no non-BMP characters are used), this value should be 36 (or 30 if you are not + * dealing with guest miis). + */ +void miiSelectorReturnGetName(const MiiSelectorReturn *returnbuf, char* out, size_t max_size); + +/** + * @brief Get Mii Author + * + * @param returnbuf Pointer to miiSelector return + * @param out String containing a Mii's author + * @param max_size Size of string. Since UTF8 characters range in size from 1-3 bytes + * (assuming that no non-BMP characters are used), this value should be 30. + */ +void miiSelectorReturnGetAuthor(const MiiSelectorReturn *returnbuf, char* out, size_t max_size); + +/** + * @brief Verifies that the Mii data returned from the applet matches its + * checksum + * + * @param returnbuf Buffer filled by Mii selector applet + * @return `true` if `returnbuf->checksum` is the same as the one computed from `returnbuf` + */ +bool miiSelectorChecksumIsValid(const MiiSelectorReturn *returnbuf); diff --git a/libctru/include/3ds/applets/swkbd.h b/libctru/include/3ds/applets/swkbd.h new file mode 100644 index 0000000000..1b205a9f0c --- /dev/null +++ b/libctru/include/3ds/applets/swkbd.h @@ -0,0 +1,321 @@ +/** + * @file swkbd.h + * @brief Software keyboard applet. + */ +#pragma once +#include <3ds/types.h> + +/// Keyboard types. +typedef enum +{ + SWKBD_TYPE_NORMAL = 0, ///< Normal keyboard with several pages (QWERTY/accents/symbol/mobile) + SWKBD_TYPE_QWERTY, ///< QWERTY keyboard only. + SWKBD_TYPE_NUMPAD, ///< Number pad. + SWKBD_TYPE_WESTERN, ///< On JPN systems, a text keyboard without Japanese input capabilities, otherwise same as SWKBD_TYPE_NORMAL. +} SwkbdType; + +/// Accepted input types. +typedef enum +{ + SWKBD_ANYTHING = 0, ///< All inputs are accepted. + SWKBD_NOTEMPTY, ///< Empty inputs are not accepted. + SWKBD_NOTEMPTY_NOTBLANK, ///< Empty or blank inputs (consisting solely of whitespace) are not accepted. + SWKBD_NOTBLANK_NOTEMPTY = SWKBD_NOTEMPTY_NOTBLANK, + SWKBD_NOTBLANK, ///< Blank inputs (consisting solely of whitespace) are not accepted, but empty inputs are. + SWKBD_FIXEDLEN, ///< The input must have a fixed length (specified by maxTextLength in swkbdInit). +} SwkbdValidInput; + +/// Keyboard dialog buttons. +typedef enum +{ + SWKBD_BUTTON_LEFT = 0, ///< Left button (usually Cancel) + SWKBD_BUTTON_MIDDLE, ///< Middle button (usually I Forgot) + SWKBD_BUTTON_RIGHT, ///< Right button (usually OK) + SWKBD_BUTTON_CONFIRM = SWKBD_BUTTON_RIGHT, + SWKBD_BUTTON_NONE, ///< No button (returned by swkbdInputText in special cases) +} SwkbdButton; + +/// Keyboard password modes. +typedef enum +{ + SWKBD_PASSWORD_NONE = 0, ///< Characters are not concealed. + SWKBD_PASSWORD_HIDE, ///< Characters are concealed immediately. + SWKBD_PASSWORD_HIDE_DELAY, ///< Characters are concealed a second after they've been typed. +} SwkbdPasswordMode; + +/// Keyboard input filtering flags. +enum +{ + SWKBD_FILTER_DIGITS = BIT(0), ///< Disallow the use of more than a certain number of digits (0 or more) + SWKBD_FILTER_AT = BIT(1), ///< Disallow the use of the @ sign. + SWKBD_FILTER_PERCENT = BIT(2), ///< Disallow the use of the % sign. + SWKBD_FILTER_BACKSLASH = BIT(3), ///< Disallow the use of the \ sign. + SWKBD_FILTER_PROFANITY = BIT(4), ///< Disallow profanity using Nintendo's profanity filter. + SWKBD_FILTER_CALLBACK = BIT(5), ///< Use a callback in order to check the input. +}; + +/// Keyboard features. +enum +{ + SWKBD_PARENTAL = BIT(0), ///< Parental PIN mode. + SWKBD_DARKEN_TOP_SCREEN = BIT(1), ///< Darken the top screen when the keyboard is shown. + SWKBD_PREDICTIVE_INPUT = BIT(2), ///< Enable predictive input (necessary for Kanji input in JPN systems). + SWKBD_MULTILINE = BIT(3), ///< Enable multiline input. + SWKBD_FIXED_WIDTH = BIT(4), ///< Enable fixed-width mode. + SWKBD_ALLOW_HOME = BIT(5), ///< Allow the usage of the HOME button. + SWKBD_ALLOW_RESET = BIT(6), ///< Allow the usage of a software-reset combination. + SWKBD_ALLOW_POWER = BIT(7), ///< Allow the usage of the POWER button. + SWKBD_DEFAULT_QWERTY = BIT(9), ///< Default to the QWERTY page when the keyboard is shown. +}; + +/// Keyboard filter callback return values. +typedef enum +{ + SWKBD_CALLBACK_OK = 0, ///< Specifies that the input is valid. + SWKBD_CALLBACK_CLOSE, ///< Displays an error message, then closes the keyboard. + SWKBD_CALLBACK_CONTINUE, ///< Displays an error message and continues displaying the keyboard. +} SwkbdCallbackResult; + +/// Keyboard return values. +typedef enum +{ + SWKBD_NONE = -1, ///< Dummy/unused. + SWKBD_INVALID_INPUT = -2, ///< Invalid parameters to swkbd. + SWKBD_OUTOFMEM = -3, ///< Out of memory. + + SWKBD_D0_CLICK = 0, ///< The button was clicked in 1-button dialogs. + SWKBD_D1_CLICK0, ///< The left button was clicked in 2-button dialogs. + SWKBD_D1_CLICK1, ///< The right button was clicked in 2-button dialogs. + SWKBD_D2_CLICK0, ///< The left button was clicked in 3-button dialogs. + SWKBD_D2_CLICK1, ///< The middle button was clicked in 3-button dialogs. + SWKBD_D2_CLICK2, ///< The right button was clicked in 3-button dialogs. + + SWKBD_HOMEPRESSED = 10, ///< The HOME button was pressed. + SWKBD_RESETPRESSED, ///< The soft-reset key combination was pressed. + SWKBD_POWERPRESSED, ///< The POWER button was pressed. + + SWKBD_PARENTAL_OK = 20, ///< The parental PIN was verified successfully. + SWKBD_PARENTAL_FAIL, ///< The parental PIN was incorrect. + + SWKBD_BANNED_INPUT = 30, ///< The filter callback returned SWKBD_CALLBACK_CLOSE. +} SwkbdResult; + +/// Maximum dictionary word length, in UTF-16 code units. +#define SWKBD_MAX_WORD_LEN 40 +/// Maximum button text length, in UTF-16 code units. +#define SWKBD_MAX_BUTTON_TEXT_LEN 16 +/// Maximum hint text length, in UTF-16 code units. +#define SWKBD_MAX_HINT_TEXT_LEN 64 +/// Maximum filter callback error message length, in UTF-16 code units. +#define SWKBD_MAX_CALLBACK_MSG_LEN 256 + +/// Keyboard dictionary word for predictive input. +typedef struct +{ + u16 reading[SWKBD_MAX_WORD_LEN+1]; ///< Reading of the word (that is, the string that needs to be typed). + u16 word[SWKBD_MAX_WORD_LEN+1]; ///< Spelling of the word. + u8 language; ///< Language the word applies to. + bool all_languages; ///< Specifies if the word applies to all languages. +} SwkbdDictWord; + +/// Keyboard filter callback function. +typedef SwkbdCallbackResult (* SwkbdCallbackFn)(void* user, const char** ppMessage, const char* text, size_t textlen); +/// Keyboard status data. +typedef struct { u32 data[0x11]; } SwkbdStatusData; +/// Keyboard predictive input learning data. +typedef struct { u32 data[0x291B]; } SwkbdLearningData; + +/// Internal libctru book-keeping structure for software keyboards. +typedef struct +{ + const char* initial_text; + const SwkbdDictWord* dict; + SwkbdStatusData* status_data; + SwkbdLearningData* learning_data; + SwkbdCallbackFn callback; + void* callback_user; +} SwkbdExtra; + +/// Software keyboard parameter structure, it shouldn't be modified directly. +typedef struct +{ + int type; + int num_buttons_m1; + int valid_input; + int password_mode; + int is_parental_screen; + int darken_top_screen; + u32 filter_flags; + u32 save_state_flags; + u16 max_text_len; + u16 dict_word_count; + u16 max_digits; + u16 button_text[3][SWKBD_MAX_BUTTON_TEXT_LEN+1]; + u16 numpad_keys[2]; + u16 hint_text[SWKBD_MAX_HINT_TEXT_LEN+1]; + bool predictive_input; + bool multiline; + bool fixed_width; + bool allow_home; + bool allow_reset; + bool allow_power; + bool unknown; // XX: what is this supposed to do? "communicateWithOtherRegions" + bool default_qwerty; + bool button_submits_text[4]; + u16 language; // XX: not working? supposedly 0 = use system language, CFG_Language+1 = pick language + int initial_text_offset; + int dict_offset; + int initial_status_offset; + int initial_learning_offset; + size_t shared_memory_size; + u32 version; + SwkbdResult result; + int status_offset; + int learning_offset; + int text_offset; + u16 text_length; + int callback_result; + u16 callback_msg[SWKBD_MAX_CALLBACK_MSG_LEN+1]; + bool skip_at_check; + union + { + u8 reserved[171]; + SwkbdExtra extra; + }; +} SwkbdState; + +/** + * @brief Initializes software keyboard status. + * @param swkbd Pointer to swkbd state. + * @param type Keyboard type. + * @param numButtons Number of dialog buttons to display (1, 2 or 3). + * @param maxTextLength Maximum number of UTF-16 code units that input text can have (or -1 to let libctru use a big default). + */ +void swkbdInit(SwkbdState* swkbd, SwkbdType type, int numButtons, int maxTextLength); + +/** + * @brief Configures password mode in a software keyboard. + * @param swkbd Pointer to swkbd state. + * @param mode Password mode. + */ +static inline void swkbdSetPasswordMode(SwkbdState* swkbd, SwkbdPasswordMode mode) +{ + swkbd->password_mode = mode; +} + +/** + * @brief Configures input validation in a software keyboard. + * @param swkbd Pointer to swkbd state. + * @param validInput Specifies which inputs are valid. + * @param filterFlags Bitmask specifying which characters are disallowed (filtered). + * @param maxDigits In case digits are disallowed, specifies how many digits are allowed at maximum in input strings (0 completely restricts digit input). + */ +static inline void swkbdSetValidation(SwkbdState* swkbd, SwkbdValidInput validInput, u32 filterFlags, int maxDigits) +{ + swkbd->valid_input = validInput; + swkbd->filter_flags = filterFlags; + swkbd->max_digits = (filterFlags & SWKBD_FILTER_DIGITS) ? maxDigits : 0; +} + +/** + * @brief Configures what characters will the two bottom keys in a numpad produce. + * @param swkbd Pointer to swkbd state. + * @param left Unicode codepoint produced by the leftmost key in the bottom row (0 hides the key). + * @param left Unicode codepoint produced by the rightmost key in the bottom row (0 hides the key). + */ +static inline void swkbdSetNumpadKeys(SwkbdState* swkbd, int left, int right) +{ + swkbd->numpad_keys[0] = left; + swkbd->numpad_keys[1] = right; +} + +/** + * @brief Specifies which special features are enabled in a software keyboard. + * @param swkbd Pointer to swkbd state. + * @param features Feature bitmask. + */ +void swkbdSetFeatures(SwkbdState* swkbd, u32 features); + +/** + * @brief Sets the hint text of a software keyboard (that is, the help text that is displayed when the textbox is empty). + * @param swkbd Pointer to swkbd state. + * @param text Hint text. + */ +void swkbdSetHintText(SwkbdState* swkbd, const char* text); + +/** + * @brief Configures a dialog button in a software keyboard. + * @param swkbd Pointer to swkbd state. + * @param button Specifies which button to configure. + * @param text Button text. + * @param submit Specifies whether pushing the button will submit the text or discard it. + */ +void swkbdSetButton(SwkbdState* swkbd, SwkbdButton button, const char* text, bool submit); + +/** + * @brief Sets the initial text that a software keyboard will display on launch. + * @param swkbd Pointer to swkbd state. + * @param text Initial text. + */ +void swkbdSetInitialText(SwkbdState* swkbd, const char* text); + +/** + * @brief Configures a word in a predictive dictionary for use with a software keyboard. + * @param word Pointer to dictionary word structure. + * @param reading Reading of the word, that is, the sequence of characters that need to be typed to trigger the word in the predictive input system. + * @param text Spelling of the word, that is, the actual characters that will be produced when the user decides to select the word. + */ +void swkbdSetDictWord(SwkbdDictWord* word, const char* reading, const char* text); + +/** + * @brief Sets the custom word dictionary to be used with the predictive input system of a software keyboard. + * @param swkbd Pointer to swkbd state. + * @param dict Pointer to dictionary words. + * @param wordCount Number of words in the dictionary. + */ +void swkbdSetDictionary(SwkbdState* swkbd, const SwkbdDictWord* dict, int wordCount); + +/** + * @brief Configures software keyboard internal status management. + * @param swkbd Pointer to swkbd state. + * @param data Pointer to internal status structure (can be in, out or both depending on the other parameters). + * @param in Specifies whether the data should be read from the structure when the keyboard is launched. + * @param out Specifies whether the data should be written to the structure when the keyboard is closed. + */ +void swkbdSetStatusData(SwkbdState* swkbd, SwkbdStatusData* data, bool in, bool out); + +/** + * @brief Configures software keyboard predictive input learning data management. + * @param swkbd Pointer to swkbd state. + * @param data Pointer to learning data structure (can be in, out or both depending on the other parameters). + * @param in Specifies whether the data should be read from the structure when the keyboard is launched. + * @param out Specifies whether the data should be written to the structure when the keyboard is closed. + */ +void swkbdSetLearningData(SwkbdState* swkbd, SwkbdLearningData* data, bool in, bool out); + +/** + * @brief Configures a custom function to be used to check the validity of input when it is submitted in a software keyboard. + * @param swkbd Pointer to swkbd state. + * @param callback Filter callback function. + * @param user Custom data to be passed to the callback function. + */ +void swkbdSetFilterCallback(SwkbdState* swkbd, SwkbdCallbackFn callback, void* user); + +/** + * @brief Launches a software keyboard in order to input text. + * @param swkbd Pointer to swkbd state. + * @param buf Pointer to output buffer which will hold the inputted text. + * @param bufsize Maximum number of UTF-8 code units that the buffer can hold (including null terminator). + * @return The identifier of the dialog button that was pressed, or SWKBD_BUTTON_NONE if a different condition was triggered - in that case use swkbdGetResult to check the condition. + */ +SwkbdButton swkbdInputText(SwkbdState* swkbd, char* buf, size_t bufsize); + +/** + * @brief Retrieves the result condition of a software keyboard after it has been used. + * @param swkbd Pointer to swkbd state. + * @return The result value. + */ +static inline SwkbdResult swkbdGetResult(SwkbdState* swkbd) +{ + return swkbd->result; +} diff --git a/libctru/include/3ds/archive.h b/libctru/include/3ds/archive.h new file mode 100644 index 0000000000..6475600600 --- /dev/null +++ b/libctru/include/3ds/archive.h @@ -0,0 +1,43 @@ +/** + * @file archive.h + * @brief FS_Archive driver + */ +#pragma once + +#include + +#include <3ds/types.h> +#include <3ds/services/fs.h> + +#define ARCHIVE_DIRITER_MAGIC 0x68637261 /* "arch" */ + +/*! Open directory struct */ +typedef struct +{ + u32 magic; /*! "arch" */ + Handle fd; /*! CTRU handle */ + ssize_t index; /*! Current entry index */ + size_t size; /*! Current batch size */ + FS_DirectoryEntry entry_data[32]; /*! Temporary storage for reading entries */ +} archive_dir_t; + +/// Mounts the SD +Result archiveMountSdmc(void); + +/// Mounts and opens an archive as deviceName +/// Returns either an archive open error code, or -1 for generic failure +Result archiveMount(FS_ArchiveID archiveID, FS_Path archivePath, const char *deviceName); + +/// Uses FSUSER_ControlArchive with control action ARCHIVE_ACTION_COMMIT_SAVE_DATA on the opened archive. Not done automatically at unmount. +/// Returns -1 if the specified device is not found +Result archiveCommitSaveData(const char *deviceName); + +/// Unmounts the specified device, closing its archive in the process +/// Returns -1 if the specified device was not found +Result archiveUnmount(const char *deviceName); + +/// Unmounts all devices and cleans up any resources used by the driver +Result archiveUnmountAll(void); + +/// Get a file's mtime +Result archive_getmtime(const char *name, u64 *mtime); diff --git a/libctru/include/3ds/asminc.h b/libctru/include/3ds/asminc.h new file mode 100644 index 0000000000..ff6103a402 --- /dev/null +++ b/libctru/include/3ds/asminc.h @@ -0,0 +1,21 @@ +#pragma once + +#if !__ASSEMBLER__ + #error This header file is only for use in assembly files! +#endif // !__ASSEMBLER__ + +.macro BEGIN_ASM_FUNC name, linkage=global, section=text + .section .\section\().\name, "ax", %progbits + .align 2 + .\linkage \name + .type \name, %function + .func \name + .cfi_sections .debug_frame + .cfi_startproc + \name: +.endm + +.macro END_ASM_FUNC + .cfi_endproc + .endfunc +.endm diff --git a/libctru/include/3ds/console.h b/libctru/include/3ds/console.h new file mode 100644 index 0000000000..29104ef852 --- /dev/null +++ b/libctru/include/3ds/console.h @@ -0,0 +1,176 @@ +/** + * @file console.h + * @brief 3ds stdio support. + * + * Provides stdio integration for printing to the 3DS screen as well as debug print + * functionality provided by stderr. + * + * General usage is to initialize the console by: + * @code + * consoleDemoInit() + * @endcode + * or to customize the console usage by: + * @code + * consoleInit() + * @endcode + */ +#pragma once + +#include <3ds/types.h> +#include <3ds/gfx.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define CONSOLE_ESC(x) "\x1b[" #x +#define CONSOLE_RESET CONSOLE_ESC(0m) +#define CONSOLE_BLACK CONSOLE_ESC(30m) +#define CONSOLE_RED CONSOLE_ESC(31;1m) +#define CONSOLE_GREEN CONSOLE_ESC(32;1m) +#define CONSOLE_YELLOW CONSOLE_ESC(33;1m) +#define CONSOLE_BLUE CONSOLE_ESC(34;1m) +#define CONSOLE_MAGENTA CONSOLE_ESC(35;1m) +#define CONSOLE_CYAN CONSOLE_ESC(36;1m) +#define CONSOLE_WHITE CONSOLE_ESC(37;1m) + +/// A callback for printing a character. +typedef bool(*ConsolePrint)(void* con, int c); + +/// A font struct for the console. +typedef struct ConsoleFont +{ + u8* gfx; ///< A pointer to the font graphics + u16 asciiOffset; ///< Offset to the first valid character in the font table + u16 numChars; ///< Number of characters in the font graphics +}ConsoleFont; + +/** + * @brief Console structure used to store the state of a console render context. + * + * Default values from consoleGetDefault(); + * @code + * PrintConsole defaultConsole = + * { + * //Font: + * { + * (u8*)default_font_bin, //font gfx + * 0, //first ascii character in the set + * 128, //number of characters in the font set + * }, + * 0,0, //cursorX cursorY + * 0,0, //prevcursorX prevcursorY + * 40, //console width + * 30, //console height + * 0, //window x + * 0, //window y + * 32, //window width + * 24, //window height + * 3, //tab size + * 0, //font character offset + * 0, //print callback + * false //console initialized + * }; + * @endcode + */ +typedef struct PrintConsole +{ + ConsoleFont font; ///< Font of the console + + u16 *frameBuffer; ///< Framebuffer address + + int cursorX; ///< Current X location of the cursor (as a tile offset by default) + int cursorY; ///< Current Y location of the cursor (as a tile offset by default) + + int prevCursorX; ///< Internal state + int prevCursorY; ///< Internal state + + int consoleWidth; ///< Width of the console hardware layer in characters + int consoleHeight; ///< Height of the console hardware layer in characters + + int windowX; ///< Window X location in characters (not implemented) + int windowY; ///< Window Y location in characters (not implemented) + int windowWidth; ///< Window width in characters (not implemented) + int windowHeight; ///< Window height in characters (not implemented) + + int tabSize; ///< Size of a tab + u16 fg; ///< Foreground color + u16 bg; ///< Background color + int flags; ///< Reverse/bright flags + + ConsolePrint PrintChar; ///< Callback for printing a character. Should return true if it has handled rendering the graphics (else the print engine will attempt to render via tiles). + + bool consoleInitialised; ///< True if the console is initialized +}PrintConsole; + +#define CONSOLE_COLOR_BOLD (1<<0) ///< Bold text +#define CONSOLE_COLOR_FAINT (1<<1) ///< Faint text +#define CONSOLE_ITALIC (1<<2) ///< Italic text +#define CONSOLE_UNDERLINE (1<<3) ///< Underlined text +#define CONSOLE_BLINK_SLOW (1<<4) ///< Slow blinking text +#define CONSOLE_BLINK_FAST (1<<5) ///< Fast blinking text +#define CONSOLE_COLOR_REVERSE (1<<6) ///< Reversed color text +#define CONSOLE_CONCEAL (1<<7) ///< Concealed text +#define CONSOLE_CROSSED_OUT (1<<8) ///< Crossed out text +#define CONSOLE_FG_CUSTOM (1<<9) ///< Foreground custom color +#define CONSOLE_BG_CUSTOM (1<<10) ///< Background custom color + +/// Console debug devices supported by libnds. +typedef enum { + debugDevice_NULL, ///< Swallows prints to stderr + debugDevice_SVC, ///< Outputs stderr debug statements using svcOutputDebugString, which can then be captured by interactive debuggers + debugDevice_CONSOLE, ///< Directs stderr debug statements to 3DS console window + debugDevice_3DMOO = debugDevice_SVC, +} debugDevice; + +/** + * @brief Loads the font into the console. + * @param console Pointer to the console to update, if NULL it will update the current console. + * @param font The font to load. + */ +void consoleSetFont(PrintConsole* console, ConsoleFont* font); + +/** + * @brief Sets the print window. + * @param console Console to set, if NULL it will set the current console window. + * @param x X location of the window. + * @param y Y location of the window. + * @param width Width of the window. + * @param height Height of the window. + */ +void consoleSetWindow(PrintConsole* console, int x, int y, int width, int height); + +/** + * @brief Gets a pointer to the console with the default values. + * This should only be used when using a single console or without changing the console that is returned, otherwise use consoleInit(). + * @return A pointer to the console with the default values. + */ +PrintConsole* consoleGetDefault(void); + +/** + * @brief Make the specified console the render target. + * @param console A pointer to the console struct (must have been initialized with consoleInit(PrintConsole* console)). + * @return A pointer to the previous console. + */ +PrintConsole *consoleSelect(PrintConsole* console); + +/** + * @brief Initialise the console. + * @param screen The screen to use for the console. + * @param console A pointer to the console data to initialize (if it's NULL, the default console will be used). + * @return A pointer to the current console. + */ +PrintConsole* consoleInit(gfxScreen_t screen, PrintConsole* console); + +/** + * @brief Initializes debug console output on stderr to the specified device. + * @param device The debug device (or devices) to output debug print statements to. + */ +void consoleDebugInit(debugDevice device); + +/// Clears the screen by using iprintf("\x1b[2J"); +void consoleClear(void); + +#ifdef __cplusplus +} +#endif diff --git a/libctru/include/3ds/env.h b/libctru/include/3ds/env.h new file mode 100644 index 0000000000..cdd9eb89dc --- /dev/null +++ b/libctru/include/3ds/env.h @@ -0,0 +1,73 @@ +/** + * @file env.h + * @brief Homebrew environment information. + */ +#pragma once + +/// System run-flags. +enum { + RUNFLAG_APTWORKAROUND = BIT(0), ///< Use APT workaround. + RUNFLAG_APTREINIT = BIT(1), ///< Reinitialize APT. + RUNFLAG_APTCHAINLOAD = BIT(2), ///< Chainload APT on return. +}; + +/** + * @brief Gets whether the application was launched from a homebrew environment. + * @return Whether the application was launched from a homebrew environment. + */ +static inline bool envIsHomebrew(void) { + extern void* __service_ptr; + return __service_ptr != NULL; +} + +/** + * @brief Retrieves a handle from the environment handle list. + * @param name Name of the handle. + * @return The retrieved handle. + */ +Handle envGetHandle(const char* name); + +/** + * @brief Gets the environment-recommended app ID to use with APT. + * @return The APT app ID. + */ +static inline u32 envGetAptAppId(void) { + extern u32 __apt_appid; + return __apt_appid; +} + +/** + * @brief Gets the size of the application heap. + * @return The application heap size. + */ +static inline u32 envGetHeapSize(void) { + extern u32 __ctru_heap_size; + return __ctru_heap_size; +} + +/** + * @brief Gets the size of the linear heap. + * @return The linear heap size. + */ +static inline u32 envGetLinearHeapSize(void) { + extern u32 __ctru_linear_heap_size; + return __ctru_linear_heap_size; +} + +/** + * @brief Gets the environment argument list. + * @return The argument list. + */ +static inline const char* envGetSystemArgList(void) { + extern const char* __system_arglist; + return __system_arglist; +} + +/** + * @brief Gets the environment run flags. + * @return The run flags. + */ +static inline u32 envGetSystemRunFlags(void) { + extern u32 __system_runflags; + return __system_runflags; +} diff --git a/libctru/include/3ds/errf.h b/libctru/include/3ds/errf.h new file mode 100644 index 0000000000..f1c10b53a9 --- /dev/null +++ b/libctru/include/3ds/errf.h @@ -0,0 +1,122 @@ +/** + * @file errf.h + * @brief Error Display API + */ + +#pragma once + +#include <3ds/types.h> + +/// Types of errors that can be thrown by err:f. +typedef enum { + ERRF_ERRTYPE_GENERIC = 0, ///< For generic errors. Shows miscellaneous info. + ERRF_ERRTYPE_MEM_CORRUPT = 1, ///< Same output as generic, but informs the user that "the System Memory has been damaged". + ERRF_ERRTYPE_CARD_REMOVED = 2, ///< Displays the "The Game Card was removed." message. + ERRF_ERRTYPE_EXCEPTION = 3, ///< For exceptions, or more specifically 'crashes'. union data should be exception_data. + ERRF_ERRTYPE_FAILURE = 4, ///< For general failure. Shows a message. union data should have a string set in failure_mesg + ERRF_ERRTYPE_LOGGED = 5, ///< Outputs logs to NAND in some cases. +} ERRF_ErrType; + +/// Types of 'Exceptions' thrown for ERRF_ERRTYPE_EXCEPTION +typedef enum { + ERRF_EXCEPTION_PREFETCH_ABORT = 0, ///< Prefetch Abort + ERRF_EXCEPTION_DATA_ABORT = 1, ///< Data abort + ERRF_EXCEPTION_UNDEFINED = 2, ///< Undefined instruction + ERRF_EXCEPTION_VFP = 3, ///< VFP (floating point) exception. +} ERRF_ExceptionType; + +typedef struct { + ERRF_ExceptionType type; ///< Type of the exception. One of the ERRF_EXCEPTION_* values. + u8 reserved[3]; + u32 fsr; ///< ifsr (prefetch abort) / dfsr (data abort) + u32 far; ///< pc = ifar (prefetch abort) / dfar (data abort) + u32 fpexc; + u32 fpinst; + u32 fpinst2; +} ERRF_ExceptionInfo; + +typedef struct { + ERRF_ExceptionInfo excep; ///< Exception info struct + CpuRegisters regs; ///< CPU register dump. +} ERRF_ExceptionData; + +typedef struct { + ERRF_ErrType type; ///< Type, one of the ERRF_ERRTYPE_* enum + u8 revHigh; ///< High revison ID + u16 revLow; ///< Low revision ID + u32 resCode; ///< Result code + u32 pcAddr; ///< PC address at exception + u32 procId; ///< Process ID. + u64 titleId; ///< Title ID. + u64 appTitleId; ///< Application Title ID. + union { + ERRF_ExceptionData exception_data; ///< Data for when type is ERRF_ERRTYPE_EXCEPTION + char failure_mesg[0x60]; ///< String for when type is ERRF_ERRTYPE_FAILURE + } data; ///< The different types of data for errors. +} ERRF_FatalErrInfo; + +/// Initializes ERR:f. Unless you plan to call ERRF_Throw yourself, do not use this. +Result errfInit(void); + +/// Exits ERR:f. Unless you plan to call ERRF_Throw yourself, do not use this. +void errfExit(void); + +/** + * @brief Gets the current err:f API session handle. + * @return The current err:f API session handle. + */ +Handle *errfGetSessionHandle(void); + +/** + * @brief Throws a system error and possibly results in ErrDisp triggering. + * @param[in] error Error to throw. + * + * After performing this, the system may panic and need to be rebooted. Extra information will be displayed on the + * top screen with a developer console or the proper patches in a CFW applied. + * + * The error may not be shown and execution aborted until errfExit(void) is called. + * + * You may wish to use ERRF_ThrowResult() or ERRF_ThrowResultWithMessage() instead of + * constructing the ERRF_FatalErrInfo struct yourself. + */ +Result ERRF_Throw(const ERRF_FatalErrInfo* error); + +/** + * @brief Throws a system error with the given Result code. + * @param[in] failure Result code to throw. + * + * This calls ERRF_Throw() with error type ERRF_ERRTYPE_GENERIC and fills in the required data. + * + * This function \em does fill in the address where this function was called from. + * + * See https://3dbrew.org/wiki/ERR:Throw#Generic for expected top screen output + * on development units/patched ErrDisp. + */ +Result ERRF_ThrowResult(Result failure); + +/** + * @brief Throws a system error with the given Result code and message. + * @param[in] failure Result code to throw. + * @param[in] message The message to display. + * + * This calls ERRF_Throw() with error type ERRF_ERRTYPE_FAILURE and fills in the required data. + * + * This function does \em not fill in the address where this function was called from because it + * would not be displayed. + * + * The message is only displayed on development units/patched ErrDisp. + * + * See https://3dbrew.org/wiki/ERR:Throw#Result_Failure for expected top screen output + * on development units/patched ErrDisp. + */ +Result ERRF_ThrowResultWithMessage(Result failure, const char* message); + +/** + * @brief Handles an exception using ErrDisp. + * @param excep Exception information + * @param regs CPU registers + * + * You might want to clear ENVINFO's bit0 to be able to see any debugging information. + * @sa threadOnException + */ +void ERRF_ExceptionHandler(ERRF_ExceptionInfo* excep, CpuRegisters* regs) __attribute__((noreturn)); diff --git a/libctru/include/3ds/exheader.h b/libctru/include/3ds/exheader.h new file mode 100644 index 0000000000..9fd2baa2f6 --- /dev/null +++ b/libctru/include/3ds/exheader.h @@ -0,0 +1,199 @@ +/** + * @file exheader.h + * @brief NCCH extended header definitions. + */ +#pragma once + +#include <3ds/types.h> + +/// ARM9 descriptor flags +enum +{ + ARM9DESC_MOUNT_NAND = BIT(0), ///< Mount "nand:/" + ARM9DESC_MOUNT_NANDRO_RW = BIT(1), ///< Mount nand:/ro/ as read-write + ARM9DESC_MOUNT_TWLN = BIT(2), ///< Mount "twln:/" + ARM9DESC_MOUNT_WNAND = BIT(3), ///< Mount "wnand:/" + ARM9DESC_MOUNT_CARDSPI = BIT(4), ///< Mount "cardspi:/" + ARM9DESC_USE_SDIF3 = BIT(5), ///< Use SDIF3 + ARM9DESC_CREATE_SEED = BIT(6), ///< Create seed (movable.sed) + ARM9DESC_USE_CARD_SPI = BIT(7), ///< Use card SPI, required by multiple pxi:dev commands + ARM9DESC_SD_APPLICATION = BIT(8), ///< SD application (not checked) + ARM9DESC_MOUNT_SDMC_RW = BIT(9), ///< Mount "sdmc:/" as read-write +}; + +/// Filesystem access flags +enum +{ + FSACCESS_CATEGORY_SYSTEM_APPLICATION = BIT(0), ///< Category "system application" + FSACCESS_CATEGORY_HARDWARE_CHECK = BIT(1), ///< Category "hardware check" + FSACCESS_CATEGORY_FILESYSTEM_TOOL = BIT(2), ///< Category "filesystem tool" + FSACCESS_DEBUG = BIT(3), ///< Debug + FSACCESS_TWLCARD_BACKUP = BIT(4), ///< TWLCARD backup + FSACCESS_TWLNAND_DATA = BIT(5), ///< TWLNAND data + FSACCESS_BOSS = BIT(6), ///< BOSS (SpotPass) + FSACCESS_SDMC_RW = BIT(7), ///< SDMC (read-write) + FSACCESS_CORE = BIT(8), ///< Core + FSACCESS_NANDRO_RO = BIT(9), ///< nand:/ro/ (read-only) + FSACCESS_NANDRW = BIT(10), ///< nand:/rw/ + FSACCESS_NANDRO_RW = BIT(11), ///< nand:/ro/ (read-write) + FSACCESS_CATEGORY_SYSTEM_SETTINGS = BIT(12), ///< Category "System Settings" + FSACCESS_CARDBOARD = BIT(13), ///< Cardboard (System Transfer) + FSACCESS_EXPORT_IMPORT_IVS = BIT(14), ///< Export/Import IVs (movable.sed) + FSACCESS_SDMC_WO = BIT(15), ///< SDMC (write-only) + FSACCESS_SWITCH_CLEANUP = BIT(16), ///< "Switch cleanup" (3.0+) + FSACCESS_SAVEDATA_MOVE = BIT(17), ///< Savedata move (5.0+) + FSACCESS_SHOP = BIT(18), ///< Shop (5.0+) + FSACCESS_SHELL = BIT(19), ///< Shop (5.0+) + FSACCESS_CATEGORY_HOME_MENU = BIT(20), ///< Category "Home Menu" (6.0+) + FSACCESS_SEEDDB = BIT(21), ///< Seed DB (9.6+) +}; + +/// The resource limit category of a title +typedef enum +{ + RESLIMIT_CATEGORY_APPLICATION = 0, ///< Regular application + RESLIMIT_CATEGORY_SYS_APPLET = 1, ///< System applet + RESLIMIT_CATEGORY_LIB_APPLET = 2, ///< Library applet + RESLIMIT_CATEGORY_OTHER = 3, ///< System modules running inside the BASE memregion +} ResourceLimitCategory; + +/// The system mode a title should be launched under +typedef enum +{ + SYSMODE_O3DS_PROD = 0, ///< 64MB of usable application memory + SYSMODE_N3DS_PROD = 1, ///< 124MB of usable application memory. Unusable on O3DS + SYSMODE_DEV1 = 2, ///< 97MB/178MB of usable application memory + SYSMODE_DEV2 = 3, ///< 80MB/124MB of usable application memory + SYSMODE_DEV3 = 4, ///< 72MB of usable application memory. Same as "Prod" on N3DS + SYSMODE_DEV4 = 5, ///< 32MB of usable application memory. Same as "Prod" on N3DS +} SystemMode; + + +/// The system info flags and remaster version of a title +typedef struct +{ + u8 reserved[5]; ///< Reserved + bool compress_exefs_code : 1; ///< Whether the ExeFS's .code section is compressed + bool is_sd_application : 1; ///< Whether the title is meant to be used on an SD card + u16 remaster_version; ///< Remaster version +} ExHeader_SystemInfoFlags; + +/// Information about a title's section +typedef struct +{ + u32 address; ///< The address of the section + u32 num_pages; ///< The number of pages the section occupies + u32 size; ///< The size of the section +} ExHeader_CodeSectionInfo; + +/// The name of a title and infomation about its section +typedef struct +{ + char name[8]; ///< Title name + ExHeader_SystemInfoFlags flags; ///< System info flags, see @ref ExHeader_SystemInfoFlags + ExHeader_CodeSectionInfo text; ///< .text section info, see @ref ExHeader_CodeSectionInfo + u32 stack_size; ///< Stack size + ExHeader_CodeSectionInfo rodata; ///< .rodata section info, see @ref ExHeader_CodeSectionInfo + u32 reserved; ///< Reserved + ExHeader_CodeSectionInfo data; ///< .data section info, see @ref ExHeader_CodeSectionInfo + u32 bss_size; ///< .bss section size +} ExHeader_CodeSetInfo; + +/// The savedata size and jump ID of a title +typedef struct +{ + u64 savedata_size; ///< Savedata size + u64 jump_id; ///< Jump ID + u8 reserved[0x30]; ///< Reserved +} ExHeader_SystemInfo; + +/// The code set info, dependencies and system info of a title (SCI) +typedef struct +{ + ExHeader_CodeSetInfo codeset_info; ///< Code set info, see @ref ExHeader_CodeSetInfo + u64 dependencies[48]; ///< Title IDs of the titles that this program depends on + ExHeader_SystemInfo system_info; ///< System info, see @ref ExHeader_SystemInfo +} ExHeader_SystemControlInfo; + +/// The ARM11 filesystem info of a title +typedef struct +{ + u64 extdata_id; ///< Extdata ID + u32 system_savedata_ids[2]; ///< IDs of the system savedata accessible by the title + u64 accessible_savedata_ids; ///< IDs of the savedata accessible by the title, 20 bits each, followed by "Use other variation savedata" + u32 fs_access_info; ///< FS access flags + u32 reserved : 24; ///< Reserved + bool no_romfs : 1; ///< Don't use any RomFS + bool use_extended_savedata_access : 1; ///< Use the "extdata_id" field to store 3 additional accessible savedata IDs +} ExHeader_Arm11StorageInfo; + +/// The CPU-related and memory-layout-related info of a title +typedef struct +{ + u32 core_version; ///< The low title ID of the target firmware + bool use_cpu_clockrate_804MHz : 1; ///< Whether to start the title with the 804MHz clock rate + bool enable_l2c : 1; ///< Whether to start the title with the L2C-310 enabled enabled + u8 flag1_unused : 6; ///< Unused + SystemMode n3ds_system_mode : 4; ///< The system mode to use on N3DS + u8 flag2_unused : 4; ///< Unused + u8 ideal_processor : 2; ///< The ideal processor to start the title on + u8 affinity_mask : 2; ///< The affinity mask of the title + SystemMode o3ds_system_mode : 4; ///< The system mode to use on N3DS + u8 priority; ///< The priority of the title's main thread +} ExHeader_Arm11CoreInfo; + +/// The ARM11 system-local capabilities of a title +typedef struct +{ + u64 title_id; ///< Title ID + ExHeader_Arm11CoreInfo core_info; ///< Core info, see @ref ExHeader_Arm11CoreInfo + u16 reslimits[16]; ///< Resource limit descriptors, only "CpuTime" (first byte) sems to be used + ExHeader_Arm11StorageInfo storage_info; ///< Storage info, see @ref ExHeader_Arm11StorageInfo + char service_access[34][8]; ///< List of the services the title has access to. Limited to 32 prior to system version 9.3 + u8 reserved[15]; ///< Reserved + ResourceLimitCategory reslimit_category; ///< Resource limit category, see @ref ExHeader_Arm11SystemLocalCapabilities +} ExHeader_Arm11SystemLocalCapabilities; + +/// The ARM11 kernel capabilities of a title +typedef struct +{ + u32 descriptors[28]; ///< ARM11 kernel descriptors, see 3dbrew + u8 reserved[16]; ///< Reserved +} ExHeader_Arm11KernelCapabilities; + +/// The ARM9 access control of a title +typedef struct +{ + u8 descriptors[15]; ///< Process9 FS descriptors, see 3dbrew + u8 descriptor_version; ///< Descriptor version +} ExHeader_Arm9AccessControl; + +/// The access control information of a title +typedef struct +{ + ExHeader_Arm11SystemLocalCapabilities local_caps; ///< ARM11 system-local capabilities, see @ref ExHeader_Arm11SystemLocalCapabilities + ExHeader_Arm11KernelCapabilities kernel_caps; ///< ARM11 kernel capabilities, see @ref ExHeader_Arm11SystemLocalCapabilities + ExHeader_Arm9AccessControl access_control; ///< ARM9 access control, see @ref ExHeader_Arm9AccessControl +} ExHeader_AccessControlInfo; + +/// Main extended header data, as returned by PXIPM, Loader and FSREG service commands +typedef struct +{ + ExHeader_SystemControlInfo sci; ///< System control info, see @ref ExHeader_SystemControlInfo + ExHeader_AccessControlInfo aci; ///< Access control info, see @ref ExHeader_AccessControlInfo +} ExHeader_Info; + +/// Extended header access descriptor +typedef struct +{ + u8 signature[0x100]; ///< The signature of the access descriptor (RSA-2048-SHA256) + u8 ncchModulus[0x100]; ///< The modulus used for the above signature, with 65537 as public exponent + ExHeader_AccessControlInfo acli; ///< This is compared for equality with the first ACI by Process9, see @ref ExHeader_AccessControlInfo +} ExHeader_AccessDescriptor; + +/// The NCCH Extended Header of a title +typedef struct +{ + ExHeader_Info info; ///< Main extended header data, see @ref ExHeader_Info + ExHeader_AccessDescriptor access_descriptor; ///< Access descriptor, see @ref ExHeader_AccessDescriptor +} ExHeader; diff --git a/libctru/include/3ds/font.h b/libctru/include/3ds/font.h new file mode 100644 index 0000000000..a60fbada8f --- /dev/null +++ b/libctru/include/3ds/font.h @@ -0,0 +1,236 @@ +/** + * @file font.h + * @brief Shared font support. + */ +#pragma once +#include <3ds/types.h> + +///@name Data types +///@{ + +/// Character width information structure. +typedef struct +{ + s8 left; ///< Horizontal offset to draw the glyph with. + u8 glyphWidth; ///< Width of the glyph. + u8 charWidth; ///< Width of the character, that is, horizontal distance to advance. +} charWidthInfo_s; + +/// Font texture sheet information. +typedef struct +{ + u8 cellWidth; ///< Width of a glyph cell. + u8 cellHeight; ///< Height of a glyph cell. + u8 baselinePos; ///< Vertical position of the baseline. + u8 maxCharWidth; ///< Maximum character width. + + u32 sheetSize; ///< Size in bytes of a texture sheet. + u16 nSheets; ///< Number of texture sheets. + u16 sheetFmt; ///< GPU texture format (GPU_TEXCOLOR). + + u16 nRows; ///< Number of glyphs per row per sheet. + u16 nLines; ///< Number of glyph rows per sheet. + + u16 sheetWidth; ///< Texture sheet width. + u16 sheetHeight; ///< Texture sheet height. + u8* sheetData; ///< Pointer to texture sheet data. +} TGLP_s; + +/// Font character width information block type. +typedef struct tag_CWDH_s CWDH_s; + +/// Font character width information block structure. +struct tag_CWDH_s +{ + u16 startIndex; ///< First Unicode codepoint the block applies to. + u16 endIndex; ///< Last Unicode codepoint the block applies to. + CWDH_s* next; ///< Pointer to the next block. + + charWidthInfo_s widths[0]; ///< Table of character width information structures. +}; + +/// Font character map methods. +enum +{ + CMAP_TYPE_DIRECT = 0, ///< Identity mapping. + CMAP_TYPE_TABLE = 1, ///< Mapping using a table. + CMAP_TYPE_SCAN = 2, ///< Mapping using a list of mapped characters. +}; + +/// Font character map type. +typedef struct tag_CMAP_s CMAP_s; + +/// Font character map structure. +struct tag_CMAP_s +{ + u16 codeBegin; ///< First Unicode codepoint the block applies to. + u16 codeEnd; ///< Last Unicode codepoint the block applies to. + u16 mappingMethod; ///< Mapping method. + u16 reserved; + CMAP_s* next; ///< Pointer to the next map. + + union + { + u16 indexOffset; ///< For CMAP_TYPE_DIRECT: index of the first glyph. + u16 indexTable[0]; ///< For CMAP_TYPE_TABLE: table of glyph indices. + /// For CMAP_TYPE_SCAN: Mapping data. + struct + { + u16 nScanEntries; ///< Number of pairs. + /// Mapping pairs. + struct + { + u16 code; ///< Unicode codepoint. + u16 glyphIndex; ///< Mapped glyph index. + } scanEntries[0]; + }; + }; +}; + +/// Font information structure. +typedef struct +{ + u32 signature; ///< Signature (FINF). + u32 sectionSize; ///< Section size. + + u8 fontType; ///< Font type + u8 lineFeed; ///< Line feed vertical distance. + u16 alterCharIndex; ///< Glyph index of the replacement character. + charWidthInfo_s defaultWidth; ///< Default character width information. + u8 encoding; ///< Font encoding (?) + + TGLP_s* tglp; ///< Pointer to texture sheet information. + CWDH_s* cwdh; ///< Pointer to the first character width information block. + CMAP_s* cmap; ///< Pointer to the first character map. + + u8 height; ///< Font height. + u8 width; ///< Font width. + u8 ascent; ///< Font ascent. + u8 padding; +} FINF_s; + +/// Font structure. +typedef struct +{ + u32 signature; ///< Signature (CFNU). + u16 endianness; ///< Endianness constant (0xFEFF). + u16 headerSize; ///< Header size. + u32 version; ///< Format version. + u32 fileSize; ///< File size. + u32 nBlocks; ///< Number of blocks. + + FINF_s finf; ///< Font information. +} CFNT_s; + +/// Font glyph position structure. +typedef struct +{ + int sheetIndex; ///< Texture sheet index to use to render the glyph. + float xOffset; ///< Horizontal offset to draw the glyph width. + float xAdvance; ///< Horizontal distance to advance after drawing the glyph. + float width; ///< Glyph width. + /// Texture coordinates to use to render the glyph. + struct + { + float left, top, right, bottom; + } texcoord; + /// Vertex coordinates to use to render the glyph. + struct + { + float left, top, right, bottom; + } vtxcoord; +} fontGlyphPos_s; + +/// Flags for use with fontCalcGlyphPos. +enum +{ + GLYPH_POS_CALC_VTXCOORD = BIT(0), ///< Calculates vertex coordinates in addition to texture coordinates. + GLYPH_POS_AT_BASELINE = BIT(1), ///< Position the glyph at the baseline instead of at the top-left corner. + GLYPH_POS_Y_POINTS_UP = BIT(2), ///< Indicates that the Y axis points up instead of down. +}; + +///@} + +///@name Initialization and basic operations +///@{ + +/// Ensures the shared system font is mapped. +Result fontEnsureMapped(void); + +/** + * @brief Fixes the pointers internal to a just-loaded font + * @param font Font to fix + * @remark Should never be run on the system font, and only once on any other font. + */ +void fontFixPointers(CFNT_s* font); + +/// Gets the currently loaded system font +static inline CFNT_s* fontGetSystemFont(void) +{ + extern CFNT_s* g_sharedFont; + if (!g_sharedFont) + fontEnsureMapped(); + return g_sharedFont; +} + +/** + * @brief Retrieves the font information structure of a font. + * @param font Pointer to font structure. If NULL, the shared system font is used. + */ +static inline FINF_s* fontGetInfo(CFNT_s* font) +{ + if (!font) + font = fontGetSystemFont(); + return &font->finf; +} + +/** + * @brief Retrieves the texture sheet information of a font. + * @param font Pointer to font structure. If NULL, the shared system font is used. + */ +static inline TGLP_s* fontGetGlyphInfo(CFNT_s* font) +{ + if (!font) + font = fontGetSystemFont(); + return fontGetInfo(font)->tglp; +} + +/** + * @brief Retrieves the pointer to texture data for the specified texture sheet. + * @param font Pointer to font structure. If NULL, the shared system font is used. + * @param sheetIndex Index of the texture sheet. + */ +static inline void* fontGetGlyphSheetTex(CFNT_s* font, int sheetIndex) +{ + if (!font) + font = fontGetSystemFont(); + TGLP_s* tglp = fontGetGlyphInfo(font); + return &tglp->sheetData[sheetIndex*tglp->sheetSize]; +} + +/** + * @brief Retrieves the glyph index of the specified Unicode codepoint. + * @param font Pointer to font structure. If NULL, the shared system font is used. + * @param codePoint Unicode codepoint. + */ +int fontGlyphIndexFromCodePoint(CFNT_s* font, u32 codePoint); + +/** + * @brief Retrieves character width information of the specified glyph. + * @param font Pointer to font structure. If NULL, the shared system font is used. + * @param glyphIndex Index of the glyph. + */ +charWidthInfo_s* fontGetCharWidthInfo(CFNT_s* font, int glyphIndex); + +/** + * @brief Calculates position information for the specified glyph. + * @param out Output structure in which to write the information. + * @param font Pointer to font structure. If NULL, the shared system font is used. + * @param glyphIndex Index of the glyph. + * @param flags Calculation flags (see GLYPH_POS_* flags). + * @param scaleX Scale factor to apply horizontally. + * @param scaleY Scale factor to apply vertically. + */ +void fontCalcGlyphPos(fontGlyphPos_s* out, CFNT_s* font, int glyphIndex, u32 flags, float scaleX, float scaleY); + +///@} diff --git a/libctru/include/3ds/gdbhio.h b/libctru/include/3ds/gdbhio.h new file mode 100644 index 0000000000..1b0656ab11 --- /dev/null +++ b/libctru/include/3ds/gdbhio.h @@ -0,0 +1,29 @@ +/** + * @file gdbhio.h + * @brief Luma3DS GDB HIO (called File I/O in GDB documentation) functions. + */ + +#pragma once + +#include +#include +#include + +#define GDBHIO_STDIN_FILENO 0 +#define GDBHIO_STDOUT_FILENO 1 +#define GDBHIO_STDERR_FILENO 2 + +int gdbHioOpen(const char *pathname, int flags, mode_t mode); +int gdbHioClose(int fd); +int gdbHioRead(int fd, void *buf, unsigned int count); +int gdbHioWrite(int fd, const void *buf, unsigned int count); +off_t gdbHioLseek(int fd, off_t offset, int flag); +int gdbHioRename(const char *oldpath, const char *newpath); +int gdbHioUnlink(const char *pathname); +int gdbHioStat(const char *pathname, struct stat *st); +int gdbHioFstat(int fd, struct stat *st); +int gdbHioGettimeofday(struct timeval *tv, void *tz); +int gdbHioIsatty(int fd); + +///< Host I/O 'system' function, requires 'set remote system-call-allowed 1'. +int gdbHioSystem(const char *command); diff --git a/libctru/include/3ds/gdbhio_dev.h b/libctru/include/3ds/gdbhio_dev.h new file mode 100644 index 0000000000..44fd5dccbc --- /dev/null +++ b/libctru/include/3ds/gdbhio_dev.h @@ -0,0 +1,37 @@ +/** + * @file gdbhio_dev.h + * @brief Luma3DS GDB HIO (called File I/O in GDB documentation) devoptab wrapper. + */ + +#pragma once + +#include + +struct timeval; + +///< Initializes the GDB HIO devoptab wrapper, returns 0 on success, -1 on failure. +int gdbHioDevInit(void); + +///< Deinitializes the GDB HIO devoptab wrapper. +void gdbHioDevExit(void); + +///< Returns a file descriptor mapping to the GDB client console's standard input stream. +int gdbHioDevGetStdin(void); + +///< Returns a file descriptor mapping to the GDB client console's standard output stream. +int gdbHioDevGetStdout(void); + +///< Returns a file descriptor mapping to the GDB client console's standard error stream. +int gdbHioDevGetStderr(void); + +///< Redirects 0 to 3 of the application's standard streams to GDB client console's. Returns -1, -2, or -3, resp., on failure; 0 on success. +int gdbHioDevRedirectStdStreams(bool in, bool out, bool err); + +///< GDB HIO POSIX function gettimeofday. +int gdbHioDevGettimeofday(struct timeval *tv, void *tz); + +///< GDB HIO POSIX function isatty. +int gdbHioDevIsatty(int fd); + +///< GDB HIO POSIX function system. Requires 'set remote system-call-allowed 1'. +int gdbHioDevSystem(const char *command); diff --git a/libctru/include/3ds/gfx.h b/libctru/include/3ds/gfx.h new file mode 100644 index 0000000000..9d65b052e6 --- /dev/null +++ b/libctru/include/3ds/gfx.h @@ -0,0 +1,180 @@ +/** + * @file gfx.h + * @brief Simple framebuffer API + * + * This API provides basic functionality needed to bring up framebuffers for both screens, + * as well as managing display mode (stereoscopic 3D) and double buffering. + * It is mainly an abstraction over the gsp service. + * + * Please note that the 3DS uses *portrait* screens rotated 90 degrees counterclockwise. + * Width/height refer to the physical dimensions of the screen; that is, the top screen + * is 240 pixels wide and 400 pixels tall; while the bottom screen is 240x320. + */ +#pragma once + +#include <3ds/types.h> +#include <3ds/services/gspgpu.h> + +/// Converts red, green, and blue components to packed RGB565. +#define RGB565(r,g,b) (((b)&0x1f)|(((g)&0x3f)<<5)|(((r)&0x1f)<<11)) + +/// Converts packed RGB8 to packed RGB565. +#define RGB8_to_565(r,g,b) (((b)>>3)&0x1f)|((((g)>>2)&0x3f)<<5)|((((r)>>3)&0x1f)<<11) + +/// Screen IDs. +typedef enum { + GFX_TOP = GSP_SCREEN_TOP, ///< Top screen + GFX_BOTTOM = GSP_SCREEN_BOTTOM, ///< Bottom screen +} gfxScreen_t; + +/** + * @brief Top screen framebuffer side. + * + * This is only meaningful when stereoscopic 3D is enabled on the top screen. + * In any other case, use \ref GFX_LEFT. + */ +typedef enum { + GFX_LEFT = 0, ///< Left eye framebuffer + GFX_RIGHT = 1, ///< Right eye framebuffer +} gfx3dSide_t; + +///@name Initialization and deinitialization +///@{ + +/** + * @brief Initializes the LCD framebuffers with default parameters + * This is equivalent to calling: @code gfxInit(GSP_BGR8_OES,GSP_BGR8_OES,false); @endcode + */ +void gfxInitDefault(void); + +/** + * @brief Initializes the LCD framebuffers. + * @param topFormat The format of the top screen framebuffers. + * @param bottomFormat The format of the bottom screen framebuffers. + * @param vramBuffers Whether to allocate the framebuffers in VRAM. + * + * This function allocates memory for the framebuffers in the specified memory region. + * Initially, stereoscopic 3D is disabled and double buffering is enabled. + * + * @note This function internally calls \ref gspInit. + */ +void gfxInit(GSPGPU_FramebufferFormat topFormat, GSPGPU_FramebufferFormat bottomFormat, bool vrambuffers); + +/** + * @brief Deinitializes and frees the LCD framebuffers. + * @note This function internally calls \ref gspExit. + */ +void gfxExit(void); + +///@} + +///@name Control +///@{ + +/** + * @brief Enables or disables the 3D stereoscopic effect on the top screen. + * @param enable Pass true to enable, false to disable. + * @note Stereoscopic 3D is disabled by default. + */ +void gfxSet3D(bool enable); + +/** + * @brief Retrieves the status of the 3D stereoscopic effect on the top screen. + * @return true if 3D enabled, false otherwise. + */ +bool gfxIs3D(void); + +/** + * @brief Retrieves the status of the 800px (double-height) high resolution display mode of the top screen. + * @return true if wide mode enabled, false otherwise. + */ +bool gfxIsWide(void); + +/** + * @brief Enables or disables the 800px (double-height) high resolution display mode of the top screen. + * @param enable Pass true to enable, false to disable. + * @note Wide mode is disabled by default. + * @note Wide and stereoscopic 3D modes are mutually exclusive. + * @note In wide mode pixels are not square, since scanlines are half as tall as they normally are. + * @warning Wide mode does not work on Old 2DS consoles (however it does work on New 2DS XL consoles). + */ +void gfxSetWide(bool enable); + +/** + * @brief Changes the pixel format of a screen. + * @param screen Screen ID (see \ref gfxScreen_t) + * @param format Pixel format (see \ref GSPGPU_FramebufferFormat) + * @note If the currently allocated framebuffers are too small for the specified format, + * they are freed and new ones are reallocated. + */ +void gfxSetScreenFormat(gfxScreen_t screen, GSPGPU_FramebufferFormat format); + +/** + * @brief Retrieves the current pixel format of a screen. + * @param screen Screen ID (see \ref gfxScreen_t) + * @return Pixel format (see \ref GSPGPU_FramebufferFormat) + */ +GSPGPU_FramebufferFormat gfxGetScreenFormat(gfxScreen_t screen); + +/** + * @brief Enables or disables double buffering on a screen. + * @param screen Screen ID (see \ref gfxScreen_t) + * @param enable Pass true to enable, false to disable. + * @note Double buffering is enabled by default. + */ +void gfxSetDoubleBuffering(gfxScreen_t screen, bool enable); + +///@} + +///@name Rendering and presentation +///@{ + +/** + * @brief Retrieves the framebuffer of the specified screen to which graphics should be rendered. + * @param screen Screen ID (see \ref gfxScreen_t) + * @param side Framebuffer side (see \ref gfx3dSide_t) (pass \ref GFX_LEFT if not using stereoscopic 3D) + * @param width Pointer that will hold the width of the framebuffer in pixels. + * @param height Pointer that will hold the height of the framebuffer in pixels. + * @return A pointer to the current framebuffer of the chosen screen. + * + * Please remember that the returned pointer will change every frame if double buffering is enabled. + */ +u8* gfxGetFramebuffer(gfxScreen_t screen, gfx3dSide_t side, u16* width, u16* height); + +/** + * @brief Flushes the data cache for the current framebuffers. + * @warning This is **only used during software rendering**. Since this function has significant overhead, + * it is preferred to call this only once per frame, after all software rendering is completed. + */ +void gfxFlushBuffers(void); + +/** + * @brief Updates the configuration of the specified screen, swapping the buffers if double buffering is enabled. + * @param scr Screen ID (see \ref gfxScreen_t) + * @param hasStereo For the top screen in 3D mode: true if the framebuffer contains individual images + * for both eyes, or false if the left image should be duplicated to the right eye. + * @note Previously rendered content will be displayed on the screen after the next VBlank. + * @note This function is still useful even if double buffering is disabled, as it must be used to commit configuration changes. + * @warning Only call this once per screen per frame, otherwise graphical glitches will occur + * since this API does not implement triple buffering. + */ +void gfxScreenSwapBuffers(gfxScreen_t scr, bool hasStereo); + +/** + * @brief Same as \ref gfxScreenSwapBuffers, but with hasStereo set to true. + * @param scr Screen ID (see \ref gfxScreen_t) + * @param immediate This parameter no longer has any effect and is thus ignored. + * @deprecated This function has been superseded by \ref gfxScreenSwapBuffers, please use that instead. + */ +DEPRECATED void gfxConfigScreen(gfxScreen_t scr, bool immediate); + +/** + * @brief Updates the configuration of both screens. + * @note This function is equivalent to: \code gfxScreenSwapBuffers(GFX_TOP,true); gfxScreenSwapBuffers(GFX_BOTTOM,true); \endcode + */ +void gfxSwapBuffers(void); + +/// Same as \ref gfxSwapBuffers (formerly different). +void gfxSwapBuffersGpu(void); + +///@} diff --git a/libctru/include/3ds/gpu/enums.h b/libctru/include/3ds/gpu/enums.h new file mode 100644 index 0000000000..76ac488cf4 --- /dev/null +++ b/libctru/include/3ds/gpu/enums.h @@ -0,0 +1,504 @@ +/** + * @file enums.h + * @brief GPU enumeration values. + */ +#pragma once + +/// Creates a texture magnification filter parameter from a @ref GPU_TEXTURE_FILTER_PARAM +#define GPU_TEXTURE_MAG_FILTER(v) (((v)&0x1)<<1) +/// Creates a texture minification filter parameter from a @ref GPU_TEXTURE_FILTER_PARAM +#define GPU_TEXTURE_MIN_FILTER(v) (((v)&0x1)<<2) +/// Creates a texture mipmap filter parameter from a @ref GPU_TEXTURE_FILTER_PARAM +#define GPU_TEXTURE_MIP_FILTER(v) (((v)&0x1)<<24) +/// Creates a texture wrap S parameter from a @ref GPU_TEXTURE_WRAP_PARAM +#define GPU_TEXTURE_WRAP_S(v) (((v)&0x3)<<12) +/// Creates a texture wrap T parameter from a @ref GPU_TEXTURE_WRAP_PARAM +#define GPU_TEXTURE_WRAP_T(v) (((v)&0x3)<<8) +/// Creates a texture mode parameter from a @ref GPU_TEXTURE_MODE_PARAM +#define GPU_TEXTURE_MODE(v) (((v)&0x7)<<28) +/// Texture parameter indicating ETC1 texture. +#define GPU_TEXTURE_ETC1_PARAM BIT(5) +/// Texture parameter indicating shadow texture. +#define GPU_TEXTURE_SHADOW_PARAM BIT(20) + +/// Creates a combiner buffer write configuration. +#define GPU_TEV_BUFFER_WRITE_CONFIG(stage0, stage1, stage2, stage3) ((stage0) | ((stage1) << 1) | ((stage2) << 2) | ((stage3) << 3)) + +/// Texture filters. +typedef enum +{ + GPU_NEAREST = 0x0, ///< Nearest-neighbor interpolation. + GPU_LINEAR = 0x1, ///< Linear interpolation. +} GPU_TEXTURE_FILTER_PARAM; + +/// Texture wrap modes. +typedef enum +{ + GPU_CLAMP_TO_EDGE = 0x0, ///< Clamps to edge. + GPU_CLAMP_TO_BORDER = 0x1, ///< Clamps to border. + GPU_REPEAT = 0x2, ///< Repeats texture. + GPU_MIRRORED_REPEAT = 0x3, ///< Repeats with mirrored texture. +} GPU_TEXTURE_WRAP_PARAM; + +/// Texture modes. +typedef enum +{ + GPU_TEX_2D = 0x0, ///< 2D texture + GPU_TEX_CUBE_MAP = 0x1, ///< Cube map + GPU_TEX_SHADOW_2D = 0x2, ///< 2D Shadow texture + GPU_TEX_PROJECTION = 0x3, ///< Projection texture + GPU_TEX_SHADOW_CUBE = 0x4, ///< Shadow cube map + GPU_TEX_DISABLED = 0x5, ///< Disabled +} GPU_TEXTURE_MODE_PARAM; + +/// Supported texture units. +typedef enum +{ + GPU_TEXUNIT0 = 0x1, ///< Texture unit 0. + GPU_TEXUNIT1 = 0x2, ///< Texture unit 1. + GPU_TEXUNIT2 = 0x4, ///< Texture unit 2. +} GPU_TEXUNIT; + +/// Supported texture formats. +typedef enum +{ + GPU_RGBA8 = 0x0, ///< 8-bit Red + 8-bit Green + 8-bit Blue + 8-bit Alpha + GPU_RGB8 = 0x1, ///< 8-bit Red + 8-bit Green + 8-bit Blue + GPU_RGBA5551 = 0x2, ///< 5-bit Red + 5-bit Green + 5-bit Blue + 1-bit Alpha + GPU_RGB565 = 0x3, ///< 5-bit Red + 6-bit Green + 5-bit Blue + GPU_RGBA4 = 0x4, ///< 4-bit Red + 4-bit Green + 4-bit Blue + 4-bit Alpha + GPU_LA8 = 0x5, ///< 8-bit Luminance + 8-bit Alpha + GPU_HILO8 = 0x6, ///< 8-bit Hi + 8-bit Lo + GPU_L8 = 0x7, ///< 8-bit Luminance + GPU_A8 = 0x8, ///< 8-bit Alpha + GPU_LA4 = 0x9, ///< 4-bit Luminance + 4-bit Alpha + GPU_L4 = 0xA, ///< 4-bit Luminance + GPU_A4 = 0xB, ///< 4-bit Alpha + GPU_ETC1 = 0xC, ///< ETC1 texture compression + GPU_ETC1A4 = 0xD, ///< ETC1 texture compression + 4-bit Alpha +} GPU_TEXCOLOR; + +/// Texture faces. +typedef enum +{ + GPU_TEXFACE_2D = 0, ///< 2D face + GPU_POSITIVE_X = 0, ///< +X face + GPU_NEGATIVE_X = 1, ///< -X face + GPU_POSITIVE_Y = 2, ///< +Y face + GPU_NEGATIVE_Y = 3, ///< -Y face + GPU_POSITIVE_Z = 4, ///< +Z face + GPU_NEGATIVE_Z = 5, ///< -Z face +} GPU_TEXFACE; + +/// Procedural texture clamp modes. +typedef enum +{ + GPU_PT_CLAMP_TO_ZERO = 0, ///< Clamp to zero. + GPU_PT_CLAMP_TO_EDGE = 1, ///< Clamp to edge. + GPU_PT_REPEAT = 2, ///< Symmetrical repeat. + GPU_PT_MIRRORED_REPEAT = 3, ///< Mirrored repeat. + GPU_PT_PULSE = 4, ///< Pulse. +} GPU_PROCTEX_CLAMP; + +/// Procedural texture mapping functions. +typedef enum +{ + GPU_PT_U = 0, ///< U + GPU_PT_U2 = 1, ///< U2 + GPU_PT_V = 2, ///< V + GPU_PT_V2 = 3, ///< V2 + GPU_PT_ADD = 4, ///< U+V + GPU_PT_ADD2 = 5, ///< U2+V2 + GPU_PT_SQRT2 = 6, ///< sqrt(U2+V2) + GPU_PT_MIN = 7, ///< min + GPU_PT_MAX = 8, ///< max + GPU_PT_RMAX = 9, ///< rmax +} GPU_PROCTEX_MAPFUNC; + +/// Procedural texture shift values. +typedef enum +{ + GPU_PT_NONE = 0, ///< No shift. + GPU_PT_ODD = 1, ///< Odd shift. + GPU_PT_EVEN = 2, ///< Even shift. +} GPU_PROCTEX_SHIFT; + +/// Procedural texture filter values. +typedef enum +{ + GPU_PT_NEAREST = 0, ///< Nearest-neighbor + GPU_PT_LINEAR = 1, ///< Linear interpolation + GPU_PT_NEAREST_MIP_NEAREST = 2, ///< Nearest-neighbor with mipmap using nearest-neighbor + GPU_PT_LINEAR_MIP_NEAREST = 3, ///< Linear interpolation with mipmap using nearest-neighbor + GPU_PT_NEAREST_MIP_LINEAR = 4, ///< Nearest-neighbor with mipmap using linear interpolation + GPU_PT_LINEAR_MIP_LINEAR = 5, ///< Linear interpolation with mipmap using linear interpolation +} GPU_PROCTEX_FILTER; + +/// Procedural texture LUT IDs. +typedef enum +{ + GPU_LUT_NOISE = 0, ///< Noise table + GPU_LUT_RGBMAP = 2, ///< RGB mapping function table + GPU_LUT_ALPHAMAP = 3, ///< Alpha mapping function table + GPU_LUT_COLOR = 4, ///< Color table + GPU_LUT_COLORDIF = 5, ///< Color difference table +} GPU_PROCTEX_LUTID; + +/// Supported color buffer formats. +typedef enum +{ + GPU_RB_RGBA8 = 0, ///< 8-bit Red + 8-bit Green + 8-bit Blue + 8-bit Alpha + GPU_RB_RGB8 = 1, ///< 8-bit Red + 8-bit Green + 8-bit Blue + GPU_RB_RGBA5551 = 2, ///< 5-bit Red + 5-bit Green + 5-bit Blue + 1-bit Alpha + GPU_RB_RGB565 = 3, ///< 5-bit Red + 6-bit Green + 5-bit Blue + GPU_RB_RGBA4 = 4, ///< 4-bit Red + 4-bit Green + 4-bit Blue + 4-bit Alpha +} GPU_COLORBUF; + +/// Supported depth buffer formats. +typedef enum +{ + GPU_RB_DEPTH16 = 0, ///< 16-bit Depth + GPU_RB_DEPTH24 = 2, ///< 24-bit Depth + GPU_RB_DEPTH24_STENCIL8 = 3, ///< 24-bit Depth + 8-bit Stencil +} GPU_DEPTHBUF; + +/// Test functions. +typedef enum +{ + GPU_NEVER = 0, ///< Never pass. + GPU_ALWAYS = 1, ///< Always pass. + GPU_EQUAL = 2, ///< Pass if equal. + GPU_NOTEQUAL = 3, ///< Pass if not equal. + GPU_LESS = 4, ///< Pass if less than. + GPU_LEQUAL = 5, ///< Pass if less than or equal. + GPU_GREATER = 6, ///< Pass if greater than. + GPU_GEQUAL = 7, ///< Pass if greater than or equal. +} GPU_TESTFUNC; + +/// Early depth test functions. +typedef enum +{ + GPU_EARLYDEPTH_GEQUAL = 0, ///< Pass if greater than or equal. + GPU_EARLYDEPTH_GREATER = 1, ///< Pass if greater than. + GPU_EARLYDEPTH_LEQUAL = 2, ///< Pass if less than or equal. + GPU_EARLYDEPTH_LESS = 3, ///< Pass if less than. +} GPU_EARLYDEPTHFUNC; + +/// Gas depth functions. +typedef enum +{ + GPU_GAS_NEVER = 0, ///< Never pass (0). + GPU_GAS_ALWAYS = 1, ///< Always pass (1). + GPU_GAS_GREATER = 2, ///< Pass if greater than (1-X). + GPU_GAS_LESS = 3, ///< Pass if less than (X). +} GPU_GASDEPTHFUNC; + +/// Converts \ref GPU_TESTFUNC into \ref GPU_GASDEPTHFUNC. +#define GPU_MAKEGASDEPTHFUNC(n) (GPU_GASDEPTHFUNC)((0xAF02>>((int)(n)<<1))&3) + +/// Scissor test modes. +typedef enum +{ + GPU_SCISSOR_DISABLE = 0, ///< Disable. + GPU_SCISSOR_INVERT = 1, ///< Exclude pixels inside the scissor box. + // 2 is the same as 0 + GPU_SCISSOR_NORMAL = 3, ///< Exclude pixels outside of the scissor box. +} GPU_SCISSORMODE; + +/// Stencil operations. +typedef enum +{ + GPU_STENCIL_KEEP = 0, ///< Keep old value. (old_stencil) + GPU_STENCIL_ZERO = 1, ///< Zero. (0) + GPU_STENCIL_REPLACE = 2, ///< Replace value. (ref) + GPU_STENCIL_INCR = 3, ///< Increment value. (old_stencil + 1 saturated to [0, 255]) + GPU_STENCIL_DECR = 4, ///< Decrement value. (old_stencil - 1 saturated to [0, 255]) + GPU_STENCIL_INVERT = 5, ///< Invert value. (~old_stencil) + GPU_STENCIL_INCR_WRAP = 6, ///< Increment value. (old_stencil + 1) + GPU_STENCIL_DECR_WRAP = 7, ///< Decrement value. (old_stencil - 1) +} GPU_STENCILOP; + +/// Pixel write mask. +typedef enum +{ + GPU_WRITE_RED = 0x01, ///< Write red. + GPU_WRITE_GREEN = 0x02, ///< Write green. + GPU_WRITE_BLUE = 0x04, ///< Write blue. + GPU_WRITE_ALPHA = 0x08, ///< Write alpha. + GPU_WRITE_DEPTH = 0x10, ///< Write depth. + + GPU_WRITE_COLOR = 0x0F, ///< Write all color components. + GPU_WRITE_ALL = 0x1F, ///< Write all components. +} GPU_WRITEMASK; + +/// Blend modes. +typedef enum +{ + GPU_BLEND_ADD = 0, ///< Add colors. + GPU_BLEND_SUBTRACT = 1, ///< Subtract colors. + GPU_BLEND_REVERSE_SUBTRACT = 2, ///< Reverse-subtract colors. + GPU_BLEND_MIN = 3, ///< Use the minimum color. + GPU_BLEND_MAX = 4, ///< Use the maximum color. +} GPU_BLENDEQUATION; + +/// Blend factors. +typedef enum +{ + GPU_ZERO = 0, ///< Zero. + GPU_ONE = 1, ///< One. + GPU_SRC_COLOR = 2, ///< Source color. + GPU_ONE_MINUS_SRC_COLOR = 3, ///< Source color - 1. + GPU_DST_COLOR = 4, ///< Destination color. + GPU_ONE_MINUS_DST_COLOR = 5, ///< Destination color - 1. + GPU_SRC_ALPHA = 6, ///< Source alpha. + GPU_ONE_MINUS_SRC_ALPHA = 7, ///< Source alpha - 1. + GPU_DST_ALPHA = 8, ///< Destination alpha. + GPU_ONE_MINUS_DST_ALPHA = 9, ///< Destination alpha - 1. + GPU_CONSTANT_COLOR = 10, ///< Constant color. + GPU_ONE_MINUS_CONSTANT_COLOR = 11, ///< Constant color - 1. + GPU_CONSTANT_ALPHA = 12, ///< Constant alpha. + GPU_ONE_MINUS_CONSTANT_ALPHA = 13, ///< Constant alpha - 1. + GPU_SRC_ALPHA_SATURATE = 14, ///< Saturated alpha. +} GPU_BLENDFACTOR; + +/// Logical operations. +typedef enum +{ + GPU_LOGICOP_CLEAR = 0, ///< Clear. + GPU_LOGICOP_AND = 1, ///< Bitwise AND. + GPU_LOGICOP_AND_REVERSE = 2, ///< Reverse bitwise AND. + GPU_LOGICOP_COPY = 3, ///< Copy. + GPU_LOGICOP_SET = 4, ///< Set. + GPU_LOGICOP_COPY_INVERTED = 5, ///< Inverted copy. + GPU_LOGICOP_NOOP = 6, ///< No operation. + GPU_LOGICOP_INVERT = 7, ///< Invert. + GPU_LOGICOP_NAND = 8, ///< Bitwise NAND. + GPU_LOGICOP_OR = 9, ///< Bitwise OR. + GPU_LOGICOP_NOR = 10, ///< Bitwise NOR. + GPU_LOGICOP_XOR = 11, ///< Bitwise XOR. + GPU_LOGICOP_EQUIV = 12, ///< Equivalent. + GPU_LOGICOP_AND_INVERTED = 13, ///< Inverted bitwise AND. + GPU_LOGICOP_OR_REVERSE = 14, ///< Reverse bitwise OR. + GPU_LOGICOP_OR_INVERTED = 15, ///< Inverted bitwize OR. +} GPU_LOGICOP; + +/// Fragment operation modes. +typedef enum +{ + GPU_FRAGOPMODE_GL = 0, ///< OpenGL mode. + GPU_FRAGOPMODE_GAS_ACC = 1, ///< Gas mode (?). + GPU_FRAGOPMODE_SHADOW = 3, ///< Shadow mode (?). +} GPU_FRAGOPMODE; + +/// Supported component formats. +typedef enum +{ + GPU_BYTE = 0, ///< 8-bit byte. + GPU_UNSIGNED_BYTE = 1, ///< 8-bit unsigned byte. + GPU_SHORT = 2, ///< 16-bit short. + GPU_FLOAT = 3, ///< 32-bit float. +} GPU_FORMATS; + +/// Cull modes. +typedef enum +{ + GPU_CULL_NONE = 0, ///< Disabled. + GPU_CULL_FRONT_CCW = 1, ///< Front, counter-clockwise. + GPU_CULL_BACK_CCW = 2, ///< Back, counter-clockwise. +} GPU_CULLMODE; + +/// Creates a VBO attribute parameter from its index, size, and format. +#define GPU_ATTRIBFMT(i, n, f) (((((n)-1)<<2)|((f)&3))<<((i)*4)) + +/// Texture combiner sources. +typedef enum +{ + GPU_PRIMARY_COLOR = 0x00, ///< Primary color. + GPU_FRAGMENT_PRIMARY_COLOR = 0x01, ///< Primary fragment color. + GPU_FRAGMENT_SECONDARY_COLOR = 0x02, ///< Secondary fragment color. + GPU_TEXTURE0 = 0x03, ///< Texture unit 0. + GPU_TEXTURE1 = 0x04, ///< Texture unit 1. + GPU_TEXTURE2 = 0x05, ///< Texture unit 2. + GPU_TEXTURE3 = 0x06, ///< Texture unit 3. + GPU_PREVIOUS_BUFFER = 0x0D, ///< Previous buffer. + GPU_CONSTANT = 0x0E, ///< Constant value. + GPU_PREVIOUS = 0x0F, ///< Previous value. +} GPU_TEVSRC; + +/// Texture RGB combiner operands. +typedef enum +{ + GPU_TEVOP_RGB_SRC_COLOR = 0x00, ///< Source color. + GPU_TEVOP_RGB_ONE_MINUS_SRC_COLOR = 0x01, ///< Source color - 1. + GPU_TEVOP_RGB_SRC_ALPHA = 0x02, ///< Source alpha. + GPU_TEVOP_RGB_ONE_MINUS_SRC_ALPHA = 0x03, ///< Source alpha - 1. + GPU_TEVOP_RGB_SRC_R = 0x04, ///< Source red. + GPU_TEVOP_RGB_ONE_MINUS_SRC_R = 0x05, ///< Source red - 1. + GPU_TEVOP_RGB_0x06 = 0x06, ///< Unknown. + GPU_TEVOP_RGB_0x07 = 0x07, ///< Unknown. + GPU_TEVOP_RGB_SRC_G = 0x08, ///< Source green. + GPU_TEVOP_RGB_ONE_MINUS_SRC_G = 0x09, ///< Source green - 1. + GPU_TEVOP_RGB_0x0A = 0x0A, ///< Unknown. + GPU_TEVOP_RGB_0x0B = 0x0B, ///< Unknown. + GPU_TEVOP_RGB_SRC_B = 0x0C, ///< Source blue. + GPU_TEVOP_RGB_ONE_MINUS_SRC_B = 0x0D, ///< Source blue - 1. + GPU_TEVOP_RGB_0x0E = 0x0E, ///< Unknown. + GPU_TEVOP_RGB_0x0F = 0x0F, ///< Unknown. +} GPU_TEVOP_RGB; + +/// Texture Alpha combiner operands. +typedef enum +{ + GPU_TEVOP_A_SRC_ALPHA = 0x00, ///< Source alpha. + GPU_TEVOP_A_ONE_MINUS_SRC_ALPHA = 0x01, ///< Source alpha - 1. + GPU_TEVOP_A_SRC_R = 0x02, ///< Source red. + GPU_TEVOP_A_ONE_MINUS_SRC_R = 0x03, ///< Source red - 1. + GPU_TEVOP_A_SRC_G = 0x04, ///< Source green. + GPU_TEVOP_A_ONE_MINUS_SRC_G = 0x05, ///< Source green - 1. + GPU_TEVOP_A_SRC_B = 0x06, ///< Source blue. + GPU_TEVOP_A_ONE_MINUS_SRC_B = 0x07, ///< Source blue - 1. +} GPU_TEVOP_A; + +/// Texture combiner functions. +typedef enum +{ + GPU_REPLACE = 0x00, ///< Replace. + GPU_MODULATE = 0x01, ///< Modulate. + GPU_ADD = 0x02, ///< Add. + GPU_ADD_SIGNED = 0x03, ///< Signed add. + GPU_INTERPOLATE = 0x04, ///< Interpolate. + GPU_SUBTRACT = 0x05, ///< Subtract. + GPU_DOT3_RGB = 0x06, ///< Dot3. RGB only. + GPU_MULTIPLY_ADD = 0x08, ///< Multiply then add. + GPU_ADD_MULTIPLY = 0x09, ///< Add then multiply. +} GPU_COMBINEFUNC; + +/// Texture scale factors. +typedef enum +{ + GPU_TEVSCALE_1 = 0x0, ///< 1x + GPU_TEVSCALE_2 = 0x1, ///< 2x + GPU_TEVSCALE_4 = 0x2, ///< 4x +} GPU_TEVSCALE; + +/// Creates a texture combiner source parameter from three sources. +#define GPU_TEVSOURCES(a,b,c) (((a))|((b)<<4)|((c)<<8)) +/// Creates a texture combiner operand parameter from three operands. +#define GPU_TEVOPERANDS(a,b,c) (((a))|((b)<<4)|((c)<<8)) + +/// Creates a light environment layer configuration parameter. +#define GPU_LIGHT_ENV_LAYER_CONFIG(n) ((n)+((n)==7)) +/// Light shadow disable bits in GPUREG_LIGHT_CONFIG1. +#define GPU_LC1_SHADOWBIT(n) BIT(n) +/// Light spot disable bits in GPUREG_LIGHT_CONFIG1. +#define GPU_LC1_SPOTBIT(n) BIT((n)+8) +/// LUT disable bits in GPUREG_LIGHT_CONFIG1. +#define GPU_LC1_LUTBIT(n) BIT((n)+16) +/// Light distance attenuation disable bits in GPUREG_LIGHT_CONFIG1. +#define GPU_LC1_ATTNBIT(n) BIT((n)+24) +/// Creates a light permutation parameter. +#define GPU_LIGHTPERM(i,n) ((n) << ((i)*4)) +/// Creates a light LUT input parameter. +#define GPU_LIGHTLUTINPUT(i,n) ((n) << ((i)*4)) +/// Creates a light LUT index parameter. +#define GPU_LIGHTLUTIDX(c,i,o) ((o) | ((i) << 8) | ((c) << 11)) +/// Creates a light color parameter from red, green, and blue components. +#define GPU_LIGHTCOLOR(r,g,b) (((b) & 0xFF) | (((g) << 10) & 0xFF) | (((r) << 20) & 0xFF)) + +/// Fresnel options. +typedef enum +{ + GPU_NO_FRESNEL = 0, ///< None. + GPU_PRI_ALPHA_FRESNEL = 1, ///< Primary alpha. + GPU_SEC_ALPHA_FRESNEL = 2, ///< Secondary alpha. + GPU_PRI_SEC_ALPHA_FRESNEL = 3, ///< Primary and secondary alpha. +} GPU_FRESNELSEL; + +/// Bump map modes. +typedef enum +{ + GPU_BUMP_NOT_USED = 0, ///< Disabled. + GPU_BUMP_AS_BUMP = 1, ///< Bump as bump mapping. + GPU_BUMP_AS_TANG = 2, ///< Bump as tangent/normal mapping. +} GPU_BUMPMODE; + +/// LUT IDs. +typedef enum +{ + GPU_LUT_D0 = 0, ///< D0 LUT. + GPU_LUT_D1 = 1, ///< D1 LUT. + GPU_LUT_SP = 2, ///< Spotlight LUT. + GPU_LUT_FR = 3, ///< Fresnel LUT. + GPU_LUT_RB = 4, ///< Reflection-Blue LUT. + GPU_LUT_RG = 5, ///< Reflection-Green LUT. + GPU_LUT_RR = 6, ///< Reflection-Red LUT. + GPU_LUT_DA = 7, ///< Distance attenuation LUT. +} GPU_LIGHTLUTID; + +/// LUT inputs. +typedef enum +{ + GPU_LUTINPUT_NH = 0, ///< Normal*HalfVector + GPU_LUTINPUT_VH = 1, ///< View*HalfVector + GPU_LUTINPUT_NV = 2, ///< Normal*View + GPU_LUTINPUT_LN = 3, ///< LightVector*Normal + GPU_LUTINPUT_SP = 4, ///< -LightVector*SpotlightVector + GPU_LUTINPUT_CP = 5, ///< cosine of phi +} GPU_LIGHTLUTINPUT; + +/// LUT scalers. +typedef enum +{ + GPU_LUTSCALER_1x = 0, ///< 1x scale. + GPU_LUTSCALER_2x = 1, ///< 2x scale. + GPU_LUTSCALER_4x = 2, ///< 4x scale. + GPU_LUTSCALER_8x = 3, ///< 8x scale. + GPU_LUTSCALER_0_25x = 6, ///< 0.25x scale. + GPU_LUTSCALER_0_5x = 7, ///< 0.5x scale. +} GPU_LIGHTLUTSCALER; + +/// LUT selection. +typedef enum +{ + GPU_LUTSELECT_COMMON = 0, ///< LUTs that are common to all lights. + GPU_LUTSELECT_SP = 1, ///< Spotlight LUT. + GPU_LUTSELECT_DA = 2, ///< Distance attenuation LUT. +} GPU_LIGHTLUTSELECT; + +/// Fog modes. +typedef enum +{ + GPU_NO_FOG = 0, ///< Fog/Gas unit disabled. + GPU_FOG = 5, ///< Fog/Gas unit configured in Fog mode. + GPU_GAS = 7, ///< Fog/Gas unit configured in Gas mode. +} GPU_FOGMODE; + +/// Gas shading density source values. +typedef enum +{ + GPU_PLAIN_DENSITY = 0, ///< Plain density. + GPU_DEPTH_DENSITY = 1, ///< Depth density. +} GPU_GASMODE; + +/// Gas color LUT inputs. +typedef enum +{ + GPU_GAS_DENSITY = 0, ///< Gas density used as input. + GPU_GAS_LIGHT_FACTOR = 1, ///< Light factor used as input. +} GPU_GASLUTINPUT; + +/// Supported primitives. +typedef enum +{ + GPU_TRIANGLES = 0x0000, ///< Triangles. + GPU_TRIANGLE_STRIP = 0x0100, ///< Triangle strip. + GPU_TRIANGLE_FAN = 0x0200, ///< Triangle fan. + GPU_GEOMETRY_PRIM = 0x0300, ///< Geometry shader primitive. +} GPU_Primitive_t; + +/// Shader types. +typedef enum +{ + GPU_VERTEX_SHADER = 0x0, ///< Vertex shader. + GPU_GEOMETRY_SHADER = 0x1, ///< Geometry shader. +} GPU_SHADER_TYPE; diff --git a/libctru/include/3ds/gpu/gpu.h b/libctru/include/3ds/gpu/gpu.h new file mode 100644 index 0000000000..e3bfb4478f --- /dev/null +++ b/libctru/include/3ds/gpu/gpu.h @@ -0,0 +1,119 @@ +/** + * @file gpu.h + * @brief Barebones GPU communications driver. + */ +#pragma once + +#include "registers.h" +#include "enums.h" + +/// Creates a GPU command header from its write increments, mask, and register. +#define GPUCMD_HEADER(incremental, mask, reg) (((incremental)<<31)|(((mask)&0xF)<<16)|((reg)&0x3FF)) + +extern u32* gpuCmdBuf; ///< GPU command buffer. +extern u32 gpuCmdBufSize; ///< GPU command buffer size. +extern u32 gpuCmdBufOffset; ///< GPU command buffer offset. + +/** + * @brief Sets the GPU command buffer to use. + * @param adr Pointer to the command buffer. + * @param size Size of the command buffer. + * @param offset Offset of the command buffer. + */ +static inline void GPUCMD_SetBuffer(u32* adr, u32 size, u32 offset) +{ + gpuCmdBuf=adr; + gpuCmdBufSize=size; + gpuCmdBufOffset=offset; +} + +/** + * @brief Sets the offset of the GPU command buffer. + * @param offset Offset of the command buffer. + */ +static inline void GPUCMD_SetBufferOffset(u32 offset) +{ + gpuCmdBufOffset=offset; +} + +/** + * @brief Gets the current GPU command buffer. + * @param addr Pointer to output the command buffer to. + * @param size Pointer to output the size (in words) of the command buffer to. + * @param offset Pointer to output the offset of the command buffer to. + */ +static inline void GPUCMD_GetBuffer(u32** addr, u32* size, u32* offset) +{ + if(addr)*addr=gpuCmdBuf; + if(size)*size=gpuCmdBufSize; + if(offset)*offset=gpuCmdBufOffset; +} + +/** + * @brief Adds raw GPU commands to the current command buffer. + * @param cmd Buffer containing commands to add. + * @param size Size of the buffer. + */ +void GPUCMD_AddRawCommands(const u32* cmd, u32 size); + +/** + * @brief Adds a GPU command to the current command buffer. + * @param header Header of the command. + * @param param Parameters of the command. + * @param paramlength Size of the parameter buffer. + */ +void GPUCMD_Add(u32 header, const u32* param, u32 paramlength); + +/** + * @brief Splits the current GPU command buffer. + * @param addr Pointer to output the command buffer to. + * @param size Pointer to output the size (in words) of the command buffer to. + */ +void GPUCMD_Split(u32** addr, u32* size); + +/** + * @brief Converts a 32-bit float to a 16-bit float. + * @param f Float to convert. + * @return The converted float. + */ +u32 f32tof16(float f); + +/** + * @brief Converts a 32-bit float to a 20-bit float. + * @param f Float to convert. + * @return The converted float. + */ +u32 f32tof20(float f); + +/** + * @brief Converts a 32-bit float to a 24-bit float. + * @param f Float to convert. + * @return The converted float. + */ +u32 f32tof24(float f); + +/** + * @brief Converts a 32-bit float to a 31-bit float. + * @param f Float to convert. + * @return The converted float. + */ +u32 f32tof31(float f); + +/// Adds a command with a single parameter to the current command buffer. +static inline void GPUCMD_AddSingleParam(u32 header, u32 param) +{ + GPUCMD_Add(header, ¶m, 1); +} + +/// Adds a masked register write to the current command buffer. +#define GPUCMD_AddMaskedWrite(reg, mask, val) GPUCMD_AddSingleParam(GPUCMD_HEADER(0, (mask), (reg)), (val)) +/// Adds a register write to the current command buffer. +#define GPUCMD_AddWrite(reg, val) GPUCMD_AddMaskedWrite((reg), 0xF, (val)) +/// Adds multiple masked register writes to the current command buffer. +#define GPUCMD_AddMaskedWrites(reg, mask, vals, num) GPUCMD_Add(GPUCMD_HEADER(0, (mask), (reg)), (vals), (num)) +/// Adds multiple register writes to the current command buffer. +#define GPUCMD_AddWrites(reg, vals, num) GPUCMD_AddMaskedWrites((reg), 0xF, (vals), (num)) +/// Adds multiple masked incremental register writes to the current command buffer. +#define GPUCMD_AddMaskedIncrementalWrites(reg, mask, vals, num) GPUCMD_Add(GPUCMD_HEADER(1, (mask), (reg)), (vals), (num)) +/// Adds multiple incremental register writes to the current command buffer. +#define GPUCMD_AddIncrementalWrites(reg, vals, num) GPUCMD_AddMaskedIncrementalWrites((reg), 0xF, (vals), (num)) diff --git a/libctru/include/3ds/gpu/gx.h b/libctru/include/3ds/gpu/gx.h new file mode 100644 index 0000000000..da32c98bfa --- /dev/null +++ b/libctru/include/3ds/gpu/gx.h @@ -0,0 +1,206 @@ +/** + * @file gx.h + * @brief GX commands. + */ +#pragma once + +/** + * @brief Creates a buffer dimension parameter from width and height values. + * @param w buffer width for GX_DisplayTransfer, linesize for GX_TextureCopy + * @param h buffer height for GX_DisplayTransfer, gap for GX_TextureCopy + */ +#define GX_BUFFER_DIM(w, h) (((h)<<16)|((w)&0xFFFF)) + +/** + * @brief Supported transfer pixel formats. + * @sa GSPGPU_FramebufferFormat + */ +typedef enum +{ + GX_TRANSFER_FMT_RGBA8 = 0, ///< 8-bit Red + 8-bit Green + 8-bit Blue + 8-bit Alpha + GX_TRANSFER_FMT_RGB8 = 1, ///< 8-bit Red + 8-bit Green + 8-bit Blue + GX_TRANSFER_FMT_RGB565 = 2, ///< 5-bit Red + 6-bit Green + 5-bit Blue + GX_TRANSFER_FMT_RGB5A1 = 3, ///< 5-bit Red + 5-bit Green + 5-bit Blue + 1-bit Alpha + GX_TRANSFER_FMT_RGBA4 = 4 ///< 4-bit Red + 4-bit Green + 4-bit Blue + 4-bit Alpha +} GX_TRANSFER_FORMAT; + +/** + * @brief Anti-aliasing modes + * + * Please remember that the framebuffer is sideways. + * Hence if you activate 2x1 anti-aliasing the destination dimensions are w = 240*2 and h = 400 + */ +typedef enum +{ + GX_TRANSFER_SCALE_NO = 0, ///< No anti-aliasing + GX_TRANSFER_SCALE_X = 1, ///< 2x1 anti-aliasing + GX_TRANSFER_SCALE_XY = 2, ///< 2x2 anti-aliasing +} GX_TRANSFER_SCALE; + +/// GX transfer control flags +typedef enum +{ + GX_FILL_TRIGGER = 0x001, ///< Trigger the PPF event + GX_FILL_FINISHED = 0x002, ///< Indicates if the memory fill is complete. You should not use it when requesting a transfer. + GX_FILL_16BIT_DEPTH = 0x000, ///< The buffer has a 16 bit per pixel depth + GX_FILL_24BIT_DEPTH = 0x100, ///< The buffer has a 24 bit per pixel depth + GX_FILL_32BIT_DEPTH = 0x200, ///< The buffer has a 32 bit per pixel depth +} GX_FILL_CONTROL; + +/// Creates a transfer vertical flip flag. +#define GX_TRANSFER_FLIP_VERT(x) ((x)<<0) +/// Creates a transfer tiled output flag. +#define GX_TRANSFER_OUT_TILED(x) ((x)<<1) +/// Creates a transfer raw copy flag. +#define GX_TRANSFER_RAW_COPY(x) ((x)<<3) +/// Creates a transfer input format flag. +#define GX_TRANSFER_IN_FORMAT(x) ((x)<<8) +/// Creates a transfer output format flag. +#define GX_TRANSFER_OUT_FORMAT(x) ((x)<<12) +/// Creates a transfer scaling flag. +#define GX_TRANSFER_SCALING(x) ((x)<<24) + +/// Updates gas additive blend results. +#define GX_CMDLIST_UPDATE_GAS_ACC BIT(0) +/// Flushes the command list. +#define GX_CMDLIST_FLUSH BIT(1) + +/// GX command entry +typedef union +{ + u32 data[8]; ///< Raw command data + struct + { + u8 type; ///< Command type + u8 unk1; + u8 unk2; + u8 unk3; + u32 args[7]; ///< Command arguments + }; +} gxCmdEntry_s; + +/// GX command queue structure +typedef struct tag_gxCmdQueue_s +{ + gxCmdEntry_s* entries; ///< Pointer to array of GX command entries + u16 maxEntries; ///< Capacity of the command array + u16 numEntries; ///< Number of commands in the queue + u16 curEntry; ///< Index of the first pending command to be submitted to GX + u16 lastEntry; ///< Number of commands completed by GX + void (* callback)(struct tag_gxCmdQueue_s*); ///< User callback + void* user; ///< Data for user callback +} gxCmdQueue_s; + +/** + * @brief Clears a GX command queue. + * @param queue The GX command queue. + */ +void gxCmdQueueClear(gxCmdQueue_s* queue); + +/** + * @brief Adds a command to a GX command queue. + * @param queue The GX command queue. + * @param entry The GX command to add. + */ +void gxCmdQueueAdd(gxCmdQueue_s* queue, const gxCmdEntry_s* entry); + +/** + * @brief Runs a GX command queue, causing it to begin processing incoming commands as they arrive. + * @param queue The GX command queue. + */ +void gxCmdQueueRun(gxCmdQueue_s* queue); + +/** + * @brief Stops a GX command queue from processing incoming commands. + * @param queue The GX command queue. + */ +void gxCmdQueueStop(gxCmdQueue_s* queue); + +/** + * @brief Waits for a GX command queue to finish executing pending commands. + * @param queue The GX command queue. + * @param timeout Optional timeout (in nanoseconds) to wait (specify -1 for no timeout). + * @return false if timeout expired, true otherwise. + */ +bool gxCmdQueueWait(gxCmdQueue_s* queue, s64 timeout); + +/** + * @brief Sets the completion callback for a GX command queue. + * @param queue The GX command queue. + * @param callback The completion callback. + * @param user User data. + */ +static inline void gxCmdQueueSetCallback(gxCmdQueue_s* queue, void (* callback)(gxCmdQueue_s*), void* user) +{ + queue->callback = callback; + queue->user = user; +} + +/** + * @brief Selects a command queue to which GX_* functions will add commands instead of immediately submitting them to GX. + * @param queue The GX command queue. (Pass NULL to remove the bound command queue) + */ +void GX_BindQueue(gxCmdQueue_s* queue); + +/** + * @brief Requests a DMA. + * @param src Source to DMA from. + * @param dst Destination to DMA to. + * @param length Length of data to transfer. + */ +Result GX_RequestDma(u32* src, u32* dst, u32 length); + +/** + * @brief Processes a GPU command list. + * @param buf0a Command list address. + * @param buf0s Command list size. + * @param flags Flags to process with. + */ +Result GX_ProcessCommandList(u32* buf0a, u32 buf0s, u8 flags); + +/** + * @brief Fills the memory of two buffers with the given values. + * @param buf0a Start address of the first buffer. + * @param buf0v Dimensions of the first buffer. + * @param buf0e End address of the first buffer. + * @param control0 Value to fill the first buffer with. + * @param buf1a Start address of the second buffer. + * @param buf1v Dimensions of the second buffer. + * @param buf1e End address of the second buffer. + * @param control1 Value to fill the second buffer with. + */ +Result GX_MemoryFill(u32* buf0a, u32 buf0v, u32* buf0e, u16 control0, u32* buf1a, u32 buf1v, u32* buf1e, u16 control1); + +/** + * @brief Initiates a display transfer. + * @note The PPF event will be signaled on completion. + * @param inadr Address of the input. + * @param indim Dimensions of the input. + * @param outadr Address of the output. + * @param outdim Dimensions of the output. + * @param flags Flags to transfer with. + */ +Result GX_DisplayTransfer(u32* inadr, u32 indim, u32* outadr, u32 outdim, u32 flags); + +/** + * @brief Initiates a texture copy. + * @note The PPF event will be signaled on completion. + * @param inadr Address of the input. + * @param indim Dimensions of the input. + * @param outadr Address of the output. + * @param outdim Dimensions of the output. + * @param size Size of the data to transfer. + * @param flags Flags to transfer with. + */ +Result GX_TextureCopy(u32* inadr, u32 indim, u32* outadr, u32 outdim, u32 size, u32 flags); + +/** + * @brief Flushes the cache regions of three buffers. (This command cannot be queued in a GX command queue) + * @param buf0a Address of the first buffer. + * @param buf0s Size of the first buffer. + * @param buf1a Address of the second buffer. + * @param buf1s Size of the second buffer. + * @param buf2a Address of the third buffer. + * @param buf2s Size of the third buffer. + */ +Result GX_FlushCacheRegions(u32* buf0a, u32 buf0s, u32* buf1a, u32 buf1s, u32* buf2a, u32 buf2s); diff --git a/libctru/include/3ds/gpu/registers.h b/libctru/include/3ds/gpu/registers.h new file mode 100644 index 0000000000..e9443cec69 --- /dev/null +++ b/libctru/include/3ds/gpu/registers.h @@ -0,0 +1,767 @@ +/** + * @file registers.h + * @description GPU registers. + */ +#pragma once + +///@name Miscellaneous registers (0x000-0x03F) +///@{ +#define GPUREG_0000 0x0000 ///< Unknown. +#define GPUREG_0001 0x0001 ///< Unknown. +#define GPUREG_0002 0x0002 ///< Unknown. +#define GPUREG_0003 0x0003 ///< Unknown. +#define GPUREG_0004 0x0004 ///< Unknown. +#define GPUREG_0005 0x0005 ///< Unknown. +#define GPUREG_0006 0x0006 ///< Unknown. +#define GPUREG_0007 0x0007 ///< Unknown. +#define GPUREG_0008 0x0008 ///< Unknown. +#define GPUREG_0009 0x0009 ///< Unknown. +#define GPUREG_000A 0x000A ///< Unknown. +#define GPUREG_000B 0x000B ///< Unknown. +#define GPUREG_000C 0x000C ///< Unknown. +#define GPUREG_000D 0x000D ///< Unknown. +#define GPUREG_000E 0x000E ///< Unknown. +#define GPUREG_000F 0x000F ///< Unknown. +#define GPUREG_FINALIZE 0x0010 ///< Used to finalize GPU drawing. +#define GPUREG_0011 0x0011 ///< Unknown. +#define GPUREG_0012 0x0012 ///< Unknown. +#define GPUREG_0013 0x0013 ///< Unknown. +#define GPUREG_0014 0x0014 ///< Unknown. +#define GPUREG_0015 0x0015 ///< Unknown. +#define GPUREG_0016 0x0016 ///< Unknown. +#define GPUREG_0017 0x0017 ///< Unknown. +#define GPUREG_0018 0x0018 ///< Unknown. +#define GPUREG_0019 0x0019 ///< Unknown. +#define GPUREG_001A 0x001A ///< Unknown. +#define GPUREG_001B 0x001B ///< Unknown. +#define GPUREG_001C 0x001C ///< Unknown. +#define GPUREG_001D 0x001D ///< Unknown. +#define GPUREG_001E 0x001E ///< Unknown. +#define GPUREG_001F 0x001F ///< Unknown. +#define GPUREG_0020 0x0020 ///< Unknown. +#define GPUREG_0021 0x0021 ///< Unknown. +#define GPUREG_0022 0x0022 ///< Unknown. +#define GPUREG_0023 0x0023 ///< Unknown. +#define GPUREG_0024 0x0024 ///< Unknown. +#define GPUREG_0025 0x0025 ///< Unknown. +#define GPUREG_0026 0x0026 ///< Unknown. +#define GPUREG_0027 0x0027 ///< Unknown. +#define GPUREG_0028 0x0028 ///< Unknown. +#define GPUREG_0029 0x0029 ///< Unknown. +#define GPUREG_002A 0x002A ///< Unknown. +#define GPUREG_002B 0x002B ///< Unknown. +#define GPUREG_002C 0x002C ///< Unknown. +#define GPUREG_002D 0x002D ///< Unknown. +#define GPUREG_002E 0x002E ///< Unknown. +#define GPUREG_002F 0x002F ///< Unknown. +#define GPUREG_0030 0x0030 ///< Unknown. +#define GPUREG_0031 0x0031 ///< Unknown. +#define GPUREG_0032 0x0032 ///< Unknown. +#define GPUREG_0033 0x0033 ///< Unknown. +#define GPUREG_0034 0x0034 ///< Unknown. +#define GPUREG_0035 0x0035 ///< Unknown. +#define GPUREG_0036 0x0036 ///< Unknown. +#define GPUREG_0037 0x0037 ///< Unknown. +#define GPUREG_0038 0x0038 ///< Unknown. +#define GPUREG_0039 0x0039 ///< Unknown. +#define GPUREG_003A 0x003A ///< Unknown. +#define GPUREG_003B 0x003B ///< Unknown. +#define GPUREG_003C 0x003C ///< Unknown. +#define GPUREG_003D 0x003D ///< Unknown. +#define GPUREG_003E 0x003E ///< Unknown. +#define GPUREG_003F 0x003F ///< Unknown. +///@} + +///@name Rasterizer registers (0x040-0x07F) +///@{ +#define GPUREG_FACECULLING_CONFIG 0x0040 ///< Face culling configuration. +#define GPUREG_VIEWPORT_WIDTH 0x0041 ///< Viewport width. +#define GPUREG_VIEWPORT_INVW 0x0042 ///< Inverted viewport width. +#define GPUREG_VIEWPORT_HEIGHT 0x0043 ///< Viewport height. +#define GPUREG_VIEWPORT_INVH 0x0044 ///< Inverted viewport height. +#define GPUREG_0045 0x0045 ///< Unknown +#define GPUREG_0046 0x0046 ///< Unknown +#define GPUREG_FRAGOP_CLIP 0x0047 ///< Unknown +#define GPUREG_FRAGOP_CLIP_DATA0 0x0048 ///< Unknown +#define GPUREG_FRAGOP_CLIP_DATA1 0x0049 ///< Unknown +#define GPUREG_FRAGOP_CLIP_DATA2 0x004A ///< Unknown +#define GPUREG_FRAGOP_CLIP_DATA3 0x004B ///< Unknown +#define GPUREG_004C 0x004C ///< Unknown +#define GPUREG_DEPTHMAP_SCALE 0x004D ///< Depth map scale. +#define GPUREG_DEPTHMAP_OFFSET 0x004E ///< Depth map offset. +#define GPUREG_SH_OUTMAP_TOTAL 0x004F ///< Shader output map total. +#define GPUREG_SH_OUTMAP_O0 0x0050 ///< Shader output map 0. +#define GPUREG_SH_OUTMAP_O1 0x0051 ///< Shader output map 1. +#define GPUREG_SH_OUTMAP_O2 0x0052 ///< Shader output map 2. +#define GPUREG_SH_OUTMAP_O3 0x0053 ///< Shader output map 3. +#define GPUREG_SH_OUTMAP_O4 0x0054 ///< Shader output map 4. +#define GPUREG_SH_OUTMAP_O5 0x0055 ///< Shader output map 5. +#define GPUREG_SH_OUTMAP_O6 0x0056 ///< Shader output map 6. +#define GPUREG_0057 0x0057 ///< Unknown +#define GPUREG_0058 0x0058 ///< Unknown +#define GPUREG_0059 0x0059 ///< Unknown +#define GPUREG_005A 0x005A ///< Unknown +#define GPUREG_005B 0x005B ///< Unknown +#define GPUREG_005C 0x005C ///< Unknown +#define GPUREG_005D 0x005D ///< Unknown +#define GPUREG_005E 0x005E ///< Unknown +#define GPUREG_005F 0x005F ///< Unknown +#define GPUREG_0060 0x0060 ///< Unknown +#define GPUREG_EARLYDEPTH_FUNC 0x0061 ///< Unknown +#define GPUREG_EARLYDEPTH_TEST1 0x0062 ///< Unknown +#define GPUREG_EARLYDEPTH_CLEAR 0x0063 ///< Unknown +#define GPUREG_SH_OUTATTR_MODE 0x0064 ///< Shader output attributes mode. +#define GPUREG_SCISSORTEST_MODE 0x0065 ///< Scissor test mode. +#define GPUREG_SCISSORTEST_POS 0x0066 ///< Scissor test position. +#define GPUREG_SCISSORTEST_DIM 0x0067 ///< Scissor text dimensions. +#define GPUREG_VIEWPORT_XY 0x0068 ///< Viewport X and Y. +#define GPUREG_0069 0x0069 ///< Unknown +#define GPUREG_EARLYDEPTH_DATA 0x006A ///< Unknown +#define GPUREG_006B 0x006B ///< Unknown +#define GPUREG_006C 0x006C ///< Unknown +#define GPUREG_DEPTHMAP_ENABLE 0x006D ///< Depth map enable. +#define GPUREG_RENDERBUF_DIM 0x006E ///< Renderbuffer dimensions. +#define GPUREG_SH_OUTATTR_CLOCK 0x006F ///< Shader output attributes clock enable. +#define GPUREG_0070 0x0070 ///< Unknown +#define GPUREG_0071 0x0071 ///< Unknown +#define GPUREG_0072 0x0072 ///< Unknown +#define GPUREG_0073 0x0073 ///< Unknown +#define GPUREG_0074 0x0074 ///< Unknown +#define GPUREG_0075 0x0075 ///< Unknown +#define GPUREG_0076 0x0076 ///< Unknown +#define GPUREG_0077 0x0077 ///< Unknown +#define GPUREG_0078 0x0078 ///< Unknown +#define GPUREG_0079 0x0079 ///< Unknown +#define GPUREG_007A 0x007A ///< Unknown +#define GPUREG_007B 0x007B ///< Unknown +#define GPUREG_007C 0x007C ///< Unknown +#define GPUREG_007D 0x007D ///< Unknown +#define GPUREG_007E 0x007E ///< Unknown +#define GPUREG_007F 0x007F ///< Unknown +///@} + +///@name Texturing registers (0x080-0x0FF) +///@{ +#define GPUREG_TEXUNIT_CONFIG 0x0080 ///< Texture unit configuration. +#define GPUREG_TEXUNIT0_BORDER_COLOR 0x0081 ///< Texture unit 0 border color. +#define GPUREG_TEXUNIT0_DIM 0x0082 ///< Texture unit 0 dimensions. +#define GPUREG_TEXUNIT0_PARAM 0x0083 ///< Texture unit 0 parameters. +#define GPUREG_TEXUNIT0_LOD 0x0084 ///< Texture unit 0 LOD. +#define GPUREG_TEXUNIT0_ADDR1 0x0085 ///< Texture unit 0 address. +#define GPUREG_TEXUNIT0_ADDR2 0x0086 ///< Unknown. +#define GPUREG_TEXUNIT0_ADDR3 0x0087 ///< Unknown. +#define GPUREG_TEXUNIT0_ADDR4 0x0088 ///< Unknown. +#define GPUREG_TEXUNIT0_ADDR5 0x0089 ///< Unknown. +#define GPUREG_TEXUNIT0_ADDR6 0x008A ///< Unknown. +#define GPUREG_TEXUNIT0_SHADOW 0x008B ///< Unknown. +#define GPUREG_008C 0x008C ///< Unknown. +#define GPUREG_008D 0x008D ///< Unknown. +#define GPUREG_TEXUNIT0_TYPE 0x008E ///< Texture unit 0 type. +#define GPUREG_LIGHTING_ENABLE0 0x008F ///< Lighting toggle. +#define GPUREG_0090 0x0090 ///< Unknown. +#define GPUREG_TEXUNIT1_BORDER_COLOR 0x0091 ///< Texture unit 1 border color. +#define GPUREG_TEXUNIT1_DIM 0x0092 ///< Texture unit 1 dimensions. +#define GPUREG_TEXUNIT1_PARAM 0x0093 ///< Texture unit 1 parameters. +#define GPUREG_TEXUNIT1_LOD 0x0094 ///< Texture unit 1 LOD. +#define GPUREG_TEXUNIT1_ADDR 0x0095 ///< Texture unit 1 address. +#define GPUREG_TEXUNIT1_TYPE 0x0096 ///< Texture unit 1 type. +#define GPUREG_0097 0x0097 ///< Unknown. +#define GPUREG_0098 0x0098 ///< Unknown. +#define GPUREG_TEXUNIT2_BORDER_COLOR 0x0099 ///< Texture unit 2 border color. +#define GPUREG_TEXUNIT2_DIM 0x009A ///< Texture unit 2 dimensions. +#define GPUREG_TEXUNIT2_PARAM 0x009B ///< Texture unit 2 parameters. +#define GPUREG_TEXUNIT2_LOD 0x009C ///< Texture unit 2 LOD. +#define GPUREG_TEXUNIT2_ADDR 0x009D ///< Texture unit 2 address. +#define GPUREG_TEXUNIT2_TYPE 0x009E ///< Texture unit 2 type. +#define GPUREG_009F 0x009F ///< Unknown. +#define GPUREG_00A0 0x00A0 ///< Unknown. +#define GPUREG_00A1 0x00A1 ///< Unknown. +#define GPUREG_00A2 0x00A2 ///< Unknown. +#define GPUREG_00A3 0x00A3 ///< Unknown. +#define GPUREG_00A4 0x00A4 ///< Unknown. +#define GPUREG_00A5 0x00A5 ///< Unknown. +#define GPUREG_00A6 0x00A6 ///< Unknown. +#define GPUREG_00A7 0x00A7 ///< Unknown. +#define GPUREG_TEXUNIT3_PROCTEX0 0x00A8 ///< Unknown. +#define GPUREG_TEXUNIT3_PROCTEX1 0x00A9 ///< Unknown. +#define GPUREG_TEXUNIT3_PROCTEX2 0x00AA ///< Unknown. +#define GPUREG_TEXUNIT3_PROCTEX3 0x00AB ///< Unknown. +#define GPUREG_TEXUNIT3_PROCTEX4 0x00A ///< Unknown. +#define GPUREG_TEXUNIT3_PROCTEX5 0x00D ///< Unknown. +#define GPUREG_00AE 0x00AE ///< Unknown. +#define GPUREG_PROCTEX_LUT 0x00AF ///< Unknown. +#define GPUREG_PROCTEX_LUT_DATA0 0x00B0 ///< Unknown. +#define GPUREG_PROCTEX_LUT_DATA1 0x00B1 ///< Unknown. +#define GPUREG_PROCTEX_LUT_DATA2 0x00B2 ///< Unknown. +#define GPUREG_PROCTEX_LUT_DATA3 0x00B3 ///< Unknown. +#define GPUREG_PROCTEX_LUT_DATA4 0x00B4 ///< Unknown. +#define GPUREG_PROCTEX_LUT_DATA5 0x00B5 ///< Unknown. +#define GPUREG_PROCTEX_LUT_DATA6 0x00B6 ///< Unknown. +#define GPUREG_PROCTEX_LUT_DATA7 0x00B7 ///< Unknown. +#define GPUREG_00B8 0x00B8 ///< Unknown. +#define GPUREG_00B9 0x00B9 ///< Unknown. +#define GPUREG_00BA 0x00BA ///< Unknown. +#define GPUREG_00BB 0x00BB ///< Unknown. +#define GPUREG_00BC 0x00BC ///< Unknown. +#define GPUREG_00BD 0x00BD ///< Unknown. +#define GPUREG_00BE 0x00BE ///< Unknown. +#define GPUREG_00BF 0x00BF ///< Unknown. +#define GPUREG_TEXENV0_SOURCE 0x00C0 ///< Texture env 0 source. +#define GPUREG_TEXENV0_OPERAND 0x00C1 ///< Texture env 0 operand. +#define GPUREG_TEXENV0_COMBINER 0x00C2 ///< Texture env 0 combiner. +#define GPUREG_TEXENV0_COLOR 0x00C3 ///< Texture env 0 color. +#define GPUREG_TEXENV0_SCALE 0x00C4 ///< Texture env 0 scale. +#define GPUREG_00C5 0x00C5 ///< Unknown. +#define GPUREG_00C6 0x00C6 ///< Unknown. +#define GPUREG_00C7 0x00C7 ///< Unknown. +#define GPUREG_TEXENV1_SOURCE 0x00C8 ///< Texture env 1 source. +#define GPUREG_TEXENV1_OPERAND 0x00C9 ///< Texture env 1 operand. +#define GPUREG_TEXENV1_COMBINER 0x00CA ///< Texture env 1 combiner. +#define GPUREG_TEXENV1_COLOR 0x00CB ///< Texture env 1 color. +#define GPUREG_TEXENV1_SCALE 0x00CC ///< Texture env 1 scale. +#define GPUREG_00CD 0x00CD ///< Unknown. +#define GPUREG_00CE 0x00CE ///< Unknown. +#define GPUREG_00CF 0x00CF ///< Unknown. +#define GPUREG_TEXENV2_SOURCE 0x00D0 ///< Texture env 2 source. +#define GPUREG_TEXENV2_OPERAND 0x00D1 ///< Texture env 2 operand. +#define GPUREG_TEXENV2_COMBINER 0x00D2 ///< Texture env 2 combiner. +#define GPUREG_TEXENV2_COLOR 0x00D3 ///< Texture env 2 color. +#define GPUREG_TEXENV2_SCALE 0x00D4 ///< Texture env 2 scale. +#define GPUREG_00D5 0x00D5 ///< Unknown. +#define GPUREG_00D6 0x00D6 ///< Unknown. +#define GPUREG_00D7 0x00D7 ///< Unknown. +#define GPUREG_TEXENV3_SOURCE 0x00D8 ///< Texture env 3 source. +#define GPUREG_TEXENV3_OPERAND 0x00D9 ///< Texture env 3 operand. +#define GPUREG_TEXENV3_COMBINER 0x00DA ///< Texture env 3 combiner. +#define GPUREG_TEXENV3_COLOR 0x00DB ///< Texture env 3 color. +#define GPUREG_TEXENV3_SCALE 0x00DC ///< Texture env 3 scale. +#define GPUREG_00DD 0x00DD ///< Unknown. +#define GPUREG_00DE 0x00DE ///< Unknown. +#define GPUREG_00DF 0x00DF ///< Unknown. +#define GPUREG_TEXENV_UPDATE_BUFFER 0x00E0 ///< Texture env buffer update flag. +#define GPUREG_FOG_COLOR 0x00E1 ///< Unknown. +#define GPUREG_00E2 0x00E2 ///< Unknown. +#define GPUREG_00E3 0x00E3 ///< Unknown. +#define GPUREG_GAS_ATTENUATION 0x00E4 ///< Unknown. +#define GPUREG_GAS_ACCMAX 0x00E5 ///< Unknown. +#define GPUREG_FOG_LUT_INDEX 0x00E6 ///< Unknown. +#define GPUREG_00E7 0x00E7 ///< Unknown. +#define GPUREG_FOG_LUT_DATA0 0x00E8 ///< Unknown. +#define GPUREG_FOG_LUT_DATA1 0x00E9 ///< Unknown. +#define GPUREG_FOG_LUT_DATA2 0x00EA ///< Unknown. +#define GPUREG_FOG_LUT_DATA3 0x00EB ///< Unknown. +#define GPUREG_FOG_LUT_DATA4 0x00EC ///< Unknown. +#define GPUREG_FOG_LUT_DATA5 0x00ED ///< Unknown. +#define GPUREG_FOG_LUT_DATA6 0x00EE ///< Unknown. +#define GPUREG_FOG_LUT_DATA7 0x00EF ///< Unknown. +#define GPUREG_TEXENV4_SOURCE 0x00F0 ///< Texture env 4 source. +#define GPUREG_TEXENV4_OPERAND 0x00F1 ///< Texture env 4 operand. +#define GPUREG_TEXENV4_COMBINER 0x00F2 ///< Texture env 4 combiner. +#define GPUREG_TEXENV4_COLOR 0x00F3 ///< Texture env 4 color. +#define GPUREG_TEXENV4_SCALE 0x00F4 ///< Texture env 4 scale. +#define GPUREG_00F5 0x00F5 ///< Unknown. +#define GPUREG_00F6 0x00F6 ///< Unknown. +#define GPUREG_00F7 0x00F7 ///< Unknown. +#define GPUREG_TEXENV5_SOURCE 0x00F8 ///< Texture env 5 source. +#define GPUREG_TEXENV5_OPERAND 0x00F9 ///< Texture env 5 operand. +#define GPUREG_TEXENV5_COMBINER 0x00FA ///< Texture env 5 combiner. +#define GPUREG_TEXENV5_COLOR 0x00FB ///< Texture env 5 color. +#define GPUREG_TEXENV5_SCALE 0x00FC ///< Texture env 5 scale. +#define GPUREG_TEXENV_BUFFER_COLOR 0x00FD ///< Texture env buffer color. +#define GPUREG_00FE 0x00FE ///< Unknown. +#define GPUREG_00FF 0x00FF ///< Unknown. +///@} + +///@name Framebuffer registers (0x100-0x13F) +///@{ +#define GPUREG_COLOR_OPERATION 0x0100 ///< Configures fragment operation and blend mode. +#define GPUREG_BLEND_FUNC 0x0101 ///< Blend function configuration. +#define GPUREG_LOGIC_OP 0x0102 ///< Logical operator configuration. +#define GPUREG_BLEND_COLOR 0x0103 ///< Blend color. +#define GPUREG_FRAGOP_ALPHA_TEST 0x0104 ///< Alpha test configuration. +#define GPUREG_STENCIL_TEST 0x0105 ///< Stencil test configuration. +#define GPUREG_STENCIL_OP 0x0106 ///< Stencil test operation. +#define GPUREG_DEPTH_COLOR_MASK 0x0107 ///< Depth test and color mask configuration. +#define GPUREG_0108 0x0108 ///< Unknown. +#define GPUREG_0109 0x0109 ///< Unknown. +#define GPUREG_010A 0x010A ///< Unknown. +#define GPUREG_010B 0x010B ///< Unknown. +#define GPUREG_010C 0x010C ///< Unknown. +#define GPUREG_010D 0x010D ///< Unknown. +#define GPUREG_010E 0x010E ///< Unknown. +#define GPUREG_010F 0x010F ///< Unknown. +#define GPUREG_FRAMEBUFFER_INVALIDATE 0x0110 ///< Invalidates the frame buffer. +#define GPUREG_FRAMEBUFFER_FLUSH 0x0111 ///< Flushes the frame buffer. +#define GPUREG_COLORBUFFER_READ 0x0112 ///< Reads from the color buffer. +#define GPUREG_COLORBUFFER_WRITE 0x0113 ///< Writes to the color buffer. +#define GPUREG_DEPTHBUFFER_READ 0x0114 ///< Reads from the depth buffer. +#define GPUREG_DEPTHBUFFER_WRITE 0x0115 ///< Writes to the depth buffer. +#define GPUREG_DEPTHBUFFER_FORMAT 0x0116 ///< Depth buffer format. +#define GPUREG_COLORBUFFER_FORMAT 0x0117 ///< Color buffer format. +#define GPUREG_EARLYDEPTH_TEST2 0x0118 ///< Unknown. +#define GPUREG_0119 0x0119 ///< Unknown. +#define GPUREG_011A 0x011A ///< Unknown. +#define GPUREG_FRAMEBUFFER_BLOCK32 0x011B ///< Frame buffer block 32. +#define GPUREG_DEPTHBUFFER_LOC 0x011C ///< Depth buffer location. +#define GPUREG_COLORBUFFER_LOC 0x011D ///< Color buffer location. +#define GPUREG_FRAMEBUFFER_DIM 0x011E ///< Frame buffer dimensions. +#define GPUREG_011F 0x011F ///< Unknown. +#define GPUREG_GAS_LIGHT_XY 0x0120 ///< Unknown. +#define GPUREG_GAS_LIGHT_Z 0x0121 ///< Unknown. +#define GPUREG_GAS_LIGHT_Z_COLOR 0x0122 ///< Unknown. +#define GPUREG_GAS_LUT_INDEX 0x0123 ///< Unknown. +#define GPUREG_GAS_LUT_DATA 0x0124 ///< Unknown. +#define GPUREG_GAS_ACCMAX_FEEDBACK 0x0125 ///< Unknown. +#define GPUREG_GAS_DELTAZ_DEPTH 0x0126 ///< Unknown. +#define GPUREG_0127 0x0127 ///< Unknown. +#define GPUREG_0128 0x0128 ///< Unknown. +#define GPUREG_0129 0x0129 ///< Unknown. +#define GPUREG_012A 0x012A ///< Unknown. +#define GPUREG_012B 0x012B ///< Unknown. +#define GPUREG_012C 0x012C ///< Unknown. +#define GPUREG_012D 0x012D ///< Unknown. +#define GPUREG_012E 0x012E ///< Unknown. +#define GPUREG_012F 0x012F ///< Unknown. +#define GPUREG_FRAGOP_SHADOW 0x0130 ///< Unknown. +#define GPUREG_0131 0x0131 ///< Unknown. +#define GPUREG_0132 0x0132 ///< Unknown. +#define GPUREG_0133 0x0133 ///< Unknown. +#define GPUREG_0134 0x0134 ///< Unknown. +#define GPUREG_0135 0x0135 ///< Unknown. +#define GPUREG_0136 0x0136 ///< Unknown. +#define GPUREG_0137 0x0137 ///< Unknown. +#define GPUREG_0138 0x0138 ///< Unknown. +#define GPUREG_0139 0x0139 ///< Unknown. +#define GPUREG_013A 0x013A ///< Unknown. +#define GPUREG_013B 0x013B ///< Unknown. +#define GPUREG_013C 0x013C ///< Unknown. +#define GPUREG_013D 0x013D ///< Unknown. +#define GPUREG_013E 0x013E ///< Unknown. +#define GPUREG_013F 0x013F ///< Unknown. +///@} + +///@name Fragment lighting registers (0x140-0x1FF) +///@{ +#define GPUREG_LIGHT0_SPECULAR0 0x0140 ///< Light 0 specular lighting. +#define GPUREG_LIGHT0_SPECULAR1 0x0141 ///< Light 0 specular lighting. +#define GPUREG_LIGHT0_DIFFUSE 0x0142 ///< Light 0 diffuse lighting. +#define GPUREG_LIGHT0_AMBIENT 0x0143 ///< Light 0 ambient lighting. +#define GPUREG_LIGHT0_XY 0x0144 ///< Light 0 X and Y. +#define GPUREG_LIGHT0_Z 0x0145 ///< Light 0 Z. +#define GPUREG_LIGHT0_SPOTDIR_XY 0x0146 ///< Light 0 spotlight direction X and Y. +#define GPUREG_LIGHT0_SPOTDIR_Z 0x0147 ///< Light 0 spotlight direction Z. +#define GPUREG_0148 0x0148 ///< Unknown. +#define GPUREG_LIGHT0_CONFIG 0x0149 ///< Light 0 configuration. +#define GPUREG_LIGHT0_ATTENUATION_BIAS 0x014A ///< Light 0 attenuation bias. +#define GPUREG_LIGHT0_ATTENUATION_SCALE 0x014B ///< Light 0 attenuation scale. +#define GPUREG_014C 0x014C ///< Unknown. +#define GPUREG_014D 0x014D ///< Unknown. +#define GPUREG_014E 0x014E ///< Unknown. +#define GPUREG_014F 0x014F ///< Unknown. +#define GPUREG_LIGHT1_SPECULAR0 0x0150 ///< Light 1 specular lighting. +#define GPUREG_LIGHT1_SPECULAR1 0x0151 ///< Light 1 specular lighting. +#define GPUREG_LIGHT1_DIFFUSE 0x0152 ///< Light 1 diffuse lighting. +#define GPUREG_LIGHT1_AMBIENT 0x0153 ///< Light 1 ambient lighting. +#define GPUREG_LIGHT1_XY 0x0154 ///< Light 1 X and Y. +#define GPUREG_LIGHT1_Z 0x0155 ///< Light 1 Z. +#define GPUREG_LIGHT1_SPOTDIR_XY 0x0156 ///< Light 1 spotlight direction X and Y. +#define GPUREG_LIGHT1_SPOTDIR_Z 0x0157 ///< Light 1 spotlight direction Z. +#define GPUREG_0158 0x0158 ///< Unknown. +#define GPUREG_LIGHT1_CONFIG 0x0159 ///< Light 1 configuration. +#define GPUREG_LIGHT1_ATTENUATION_BIAS 0x015A ///< Light 1 attenuation bias. +#define GPUREG_LIGHT1_ATTENUATION_SCALE 0x015B ///< Light 1 attenuation scale. +#define GPUREG_015C 0x015C ///< Unknown. +#define GPUREG_015D 0x015D ///< Unknown. +#define GPUREG_015E 0x015E ///< Unknown. +#define GPUREG_015F 0x015F ///< Unknown. +#define GPUREG_LIGHT2_SPECULAR0 0x0160 ///< Light 2 specular lighting. +#define GPUREG_LIGHT2_SPECULAR1 0x0161 ///< Light 2 specular lighting. +#define GPUREG_LIGHT2_DIFFUSE 0x0162 ///< Light 2 diffuse lighting. +#define GPUREG_LIGHT2_AMBIENT 0x0163 ///< Light 2 ambient lighting. +#define GPUREG_LIGHT2_XY 0x0164 ///< Light 2 X and Y. +#define GPUREG_LIGHT2_Z 0x0165 ///< Light 2 Z. +#define GPUREG_LIGHT2_SPOTDIR_XY 0x0166 ///< Light 2 spotlight direction X and Y. +#define GPUREG_LIGHT2_SPOTDIR_Z 0x0167 ///< Light 2 spotlight direction Z. +#define GPUREG_0168 0x0168 ///< Unknown. +#define GPUREG_LIGHT2_CONFIG 0x0169 ///< Light 2 configuration. +#define GPUREG_LIGHT2_ATTENUATION_BIAS 0x016A ///< Light 2 attenuation bias. +#define GPUREG_LIGHT2_ATTENUATION_SCALE 0x016B ///< Light 2 attenuation scale. +#define GPUREG_016C 0x016C ///< Unknown. +#define GPUREG_016D 0x016D ///< Unknown. +#define GPUREG_016E 0x016E ///< Unknown. +#define GPUREG_016F 0x016F ///< Unknown. +#define GPUREG_LIGHT3_SPECULAR0 0x0170 ///< Light 3 specular lighting. +#define GPUREG_LIGHT3_SPECULAR1 0x0171 ///< Light 3 specular lighting. +#define GPUREG_LIGHT3_DIFFUSE 0x0172 ///< Light 3 diffuse lighting. +#define GPUREG_LIGHT3_AMBIENT 0x0173 ///< Light 3 ambient lighting. +#define GPUREG_LIGHT3_XY 0x0174 ///< Light 3 X and Y. +#define GPUREG_LIGHT3_Z 0x0175 ///< Light 3 Z. +#define GPUREG_LIGHT3_SPOTDIR_XY 0x0176 ///< Light 3 spotlight direction X and Y. +#define GPUREG_LIGHT3_SPOTDIR_Z 0x0177 ///< Light 3 spotlight direction Z. +#define GPUREG_0178 0x0178 ///< Unknown. +#define GPUREG_LIGHT3_CONFIG 0x0179 ///< Light 3 configuration. +#define GPUREG_LIGHT3_ATTENUATION_BIAS 0x017A ///< Light 3 attenuation bias. +#define GPUREG_LIGHT3_ATTENUATION_SCALE 0x017B ///< Light 3 attenuation scale. +#define GPUREG_017C 0x017C ///< Unknown. +#define GPUREG_017D 0x017D ///< Unknown. +#define GPUREG_017E 0x017E ///< Unknown. +#define GPUREG_017F 0x017F ///< Unknown. +#define GPUREG_LIGHT4_SPECULAR0 0x0180 ///< Light 4 specular lighting. +#define GPUREG_LIGHT4_SPECULAR1 0x0181 ///< Light 4 specular lighting. +#define GPUREG_LIGHT4_DIFFUSE 0x0182 ///< Light 4 diffuse lighting. +#define GPUREG_LIGHT4_AMBIENT 0x0183 ///< Light 4 ambient lighting. +#define GPUREG_LIGHT4_XY 0x0184 ///< Light 4 X and Y. +#define GPUREG_LIGHT4_Z 0x0185 ///< Light 4 Z. +#define GPUREG_LIGHT4_SPOTDIR_XY 0x0186 ///< Light 4 spotlight direction X and Y. +#define GPUREG_LIGHT4_SPOTDIR_Z 0x0187 ///< Light 4 spotlight direction Z. +#define GPUREG_0188 0x0188 ///< Unknown. +#define GPUREG_LIGHT4_CONFIG 0x0189 ///< Light 4 configuration. +#define GPUREG_LIGHT4_ATTENUATION_BIAS 0x018A ///< Light 4 attenuation bias. +#define GPUREG_LIGHT4_ATTENUATION_SCALE 0x018B ///< Light 4 attenuation scale. +#define GPUREG_018C 0x018C ///< Unknown. +#define GPUREG_018D 0x018D ///< Unknown. +#define GPUREG_018E 0x018E ///< Unknown. +#define GPUREG_018F 0x018F ///< Unknown. +#define GPUREG_LIGHT5_SPECULAR0 0x0190 ///< Light 5 specular lighting. +#define GPUREG_LIGHT5_SPECULAR1 0x0191 ///< Light 5 specular lighting. +#define GPUREG_LIGHT5_DIFFUSE 0x0192 ///< Light 5 diffuse lighting. +#define GPUREG_LIGHT5_AMBIENT 0x0193 ///< Light 5 ambient lighting. +#define GPUREG_LIGHT5_XY 0x0194 ///< Light 5 X and Y. +#define GPUREG_LIGHT5_Z 0x0195 ///< Light 5 Z. +#define GPUREG_LIGHT5_SPOTDIR_XY 0x0196 ///< Light 5 spotlight direction X and Y. +#define GPUREG_LIGHT5_SPOTDIR_Z 0x0197 ///< Light 5 spotlight direction Z. +#define GPUREG_0198 0x0198 ///< Unknown. +#define GPUREG_LIGHT5_CONFIG 0x0199 ///< Light 5 configuration. +#define GPUREG_LIGHT5_ATTENUATION_BIAS 0x019A ///< Light 5 attenuation bias. +#define GPUREG_LIGHT5_ATTENUATION_SCALE 0x019B ///< Light 5 attenuation scale. +#define GPUREG_019C 0x019C ///< Unknown. +#define GPUREG_019D 0x019D ///< Unknown. +#define GPUREG_019E 0x019E ///< Unknown. +#define GPUREG_019F 0x019F ///< Unknown. +#define GPUREG_LIGHT6_SPECULAR0 0x01A0 ///< Light 6 specular lighting. +#define GPUREG_LIGHT6_SPECULAR1 0x01A1 ///< Light 6 specular lighting. +#define GPUREG_LIGHT6_DIFFUSE 0x01A2 ///< Light 6 diffuse lighting. +#define GPUREG_LIGHT6_AMBIENT 0x01A3 ///< Light 6 ambient lighting. +#define GPUREG_LIGHT6_XY 0x01A4 ///< Light 6 X and Y. +#define GPUREG_LIGHT6_Z 0x01A5 ///< Light 6 Z. +#define GPUREG_LIGHT6_SPOTDIR_XY 0x01A6 ///< Light 6 spotlight direction X and Y. +#define GPUREG_LIGHT6_SPOTDIR_Z 0x01A7 ///< Light 6 spotlight direction Z. +#define GPUREG_01A8 0x01A8 ///< Unknown. +#define GPUREG_LIGHT6_CONFIG 0x01A9 ///< Light 6 configuration. +#define GPUREG_LIGHT6_ATTENUATION_BIAS 0x01AA ///< Light 6 attenuation bias. +#define GPUREG_LIGHT6_ATTENUATION_SCALE 0x01AB ///< Light 6 attenuation scale. +#define GPUREG_01AC 0x01AC ///< Unknown. +#define GPUREG_01AD 0x01AD ///< Unknown. +#define GPUREG_01AE 0x01AE ///< Unknown. +#define GPUREG_01AF 0x01AF ///< Unknown. +#define GPUREG_LIGHT7_SPECULAR0 0x01B0 ///< Light 7 specular lighting. +#define GPUREG_LIGHT7_SPECULAR1 0x01B1 ///< Light 7 specular lighting. +#define GPUREG_LIGHT7_DIFFUSE 0x01B2 ///< Light 7 diffuse lighting. +#define GPUREG_LIGHT7_AMBIENT 0x01B3 ///< Light 7 ambient lighting. +#define GPUREG_LIGHT7_XY 0x01B4 ///< Light 7 X and Y. +#define GPUREG_LIGHT7_Z 0x01B5 ///< Light 7 Z. +#define GPUREG_LIGHT7_SPOTDIR_XY 0x01B6 ///< Light 7 spotlight direction X and Y. +#define GPUREG_LIGHT7_SPOTDIR_Z 0x01B7 ///< Light 7 spotlight direction Z. +#define GPUREG_01B8 0x01B8 ///< Unknown. +#define GPUREG_LIGHT7_CONFIG 0x01B9 ///< Light 7 configuration. +#define GPUREG_LIGHT7_ATTENUATION_BIAS 0x01BA ///< Light 7 attenuation bias. +#define GPUREG_LIGHT7_ATTENUATION_SCALE 0x01BB ///< Light 7 attenuation scale. +#define GPUREG_01BC 0x01BC ///< Unknown. +#define GPUREG_01BD 0x01BD ///< Unknown. +#define GPUREG_01BE 0x01BE ///< Unknown. +#define GPUREG_01BF 0x01BF ///< Unknown. +#define GPUREG_LIGHTING_AMBIENT 0x01C0 ///< Ambient lighting. +#define GPUREG_01C1 0x01C1 ///< Unknown. +#define GPUREG_LIGHTING_NUM_LIGHTS 0x01C2 ///< Number of lights. +#define GPUREG_LIGHTING_CONFIG0 0x01C3 ///< Lighting configuration. +#define GPUREG_LIGHTING_CONFIG1 0x01C4 ///< Lighting configuration. +#define GPUREG_LIGHTING_LUT_INDEX 0x01C5 ///< LUT index. +#define GPUREG_LIGHTING_ENABLE1 0x01C6 ///< Lighting toggle. +#define GPUREG_01C7 0x01C7 ///< Unknown. +#define GPUREG_LIGHTING_LUT_DATA0 0x01C8 ///< LUT data 0. +#define GPUREG_LIGHTING_LUT_DATA1 0x01C9 ///< LUT data 1. +#define GPUREG_LIGHTING_LUT_DATA2 0x01CA ///< LUT data 2. +#define GPUREG_LIGHTING_LUT_DATA3 0x01CB ///< LUT data 3. +#define GPUREG_LIGHTING_LUT_DATA4 0x01CC ///< LUT data 4. +#define GPUREG_LIGHTING_LUT_DATA5 0x01CD ///< LUT data 5. +#define GPUREG_LIGHTING_LUT_DATA6 0x01CE ///< LUT data 6. +#define GPUREG_LIGHTING_LUT_DATA7 0x01CF ///< LUT data 7. +#define GPUREG_LIGHTING_LUTINPUT_ABS 0x01D0 ///< LUT input abs. +#define GPUREG_LIGHTING_LUTINPUT_SELECT 0x01D1 ///< LUT input selector. +#define GPUREG_LIGHTING_LUTINPUT_SCALE 0x01D2 ///< LUT input scale. +#define GPUREG_01D3 0x01D3 ///< Unknown. +#define GPUREG_01D4 0x01D4 ///< Unknown. +#define GPUREG_01D5 0x01D5 ///< Unknown. +#define GPUREG_01D6 0x01D6 ///< Unknown. +#define GPUREG_01D7 0x01D7 ///< Unknown. +#define GPUREG_01D8 0x01D8 ///< Unknown. +#define GPUREG_LIGHTING_LIGHT_PERMUTATION 0x01D9 ///< Light permutation. +#define GPUREG_01DA 0x01DA ///< Unknown. +#define GPUREG_01DB 0x01DB ///< Unknown. +#define GPUREG_01DC 0x01DC ///< Unknown. +#define GPUREG_01DD 0x01DD ///< Unknown. +#define GPUREG_01DE 0x01DE ///< Unknown. +#define GPUREG_01DF 0x01DF ///< Unknown. +#define GPUREG_01E0 0x01E0 ///< Unknown. +#define GPUREG_01E1 0x01E1 ///< Unknown. +#define GPUREG_01E2 0x01E2 ///< Unknown. +#define GPUREG_01E3 0x01E3 ///< Unknown. +#define GPUREG_01E4 0x01E4 ///< Unknown. +#define GPUREG_01E5 0x01E5 ///< Unknown. +#define GPUREG_01E6 0x01E6 ///< Unknown. +#define GPUREG_01E7 0x01E7 ///< Unknown. +#define GPUREG_01E8 0x01E8 ///< Unknown. +#define GPUREG_01E9 0x01E9 ///< Unknown. +#define GPUREG_01EA 0x01EA ///< Unknown. +#define GPUREG_01EB 0x01EB ///< Unknown. +#define GPUREG_01EC 0x01EC ///< Unknown. +#define GPUREG_01ED 0x01ED ///< Unknown. +#define GPUREG_01EE 0x01EE ///< Unknown. +#define GPUREG_01EF 0x01EF ///< Unknown. +#define GPUREG_01F0 0x01F0 ///< Unknown. +#define GPUREG_01F1 0x01F1 ///< Unknown. +#define GPUREG_01F2 0x01F2 ///< Unknown. +#define GPUREG_01F3 0x01F3 ///< Unknown. +#define GPUREG_01F4 0x01F4 ///< Unknown. +#define GPUREG_01F5 0x01F5 ///< Unknown. +#define GPUREG_01F6 0x01F6 ///< Unknown. +#define GPUREG_01F7 0x01F7 ///< Unknown. +#define GPUREG_01F8 0x01F8 ///< Unknown. +#define GPUREG_01F9 0x01F9 ///< Unknown. +#define GPUREG_01FA 0x01FA ///< Unknown. +#define GPUREG_01FB 0x01FB ///< Unknown. +#define GPUREG_01FC 0x01FC ///< Unknown. +#define GPUREG_01FD 0x01FD ///< Unknown. +#define GPUREG_01FE 0x01FE ///< Unknown. +#define GPUREG_01FF 0x01FF ///< Unknown. +///@} + +///@name Geometry pipeline registers (0x200-0x27F) +///@{ +#define GPUREG_ATTRIBBUFFERS_LOC 0x0200 ///< Attribute buffers location. +#define GPUREG_ATTRIBBUFFERS_FORMAT_LOW 0x0201 ///< Attribute buffers format low. +#define GPUREG_ATTRIBBUFFERS_FORMAT_HIGH 0x0202 ///< Attribute buffers format high. +#define GPUREG_ATTRIBBUFFER0_OFFSET 0x0203 ///< Attribute buffers 0 offset. +#define GPUREG_ATTRIBBUFFER0_CONFIG1 0x0204 ///< Attribute buffers 0 configuration. +#define GPUREG_ATTRIBBUFFER0_CONFIG2 0x0205 ///< Attribute buffers 0 configuration. +#define GPUREG_ATTRIBBUFFER1_OFFSET 0x0206 ///< Attribute buffers 1 offset. +#define GPUREG_ATTRIBBUFFER1_CONFIG1 0x0207 ///< Attribute buffers 1 configuration. +#define GPUREG_ATTRIBBUFFER1_CONFIG2 0x0208 ///< Attribute buffers 1 configuration. +#define GPUREG_ATTRIBBUFFER2_OFFSET 0x0209 ///< Attribute buffers 2 offset. +#define GPUREG_ATTRIBBUFFER2_CONFIG1 0x020A ///< Attribute buffers 2 configuration. +#define GPUREG_ATTRIBBUFFER2_CONFIG2 0x020B ///< Attribute buffers 2 configuration. +#define GPUREG_ATTRIBBUFFER3_OFFSET 0x020C ///< Attribute buffers 3 offset. +#define GPUREG_ATTRIBBUFFER3_CONFIG1 0x020D ///< Attribute buffers 3 configuration. +#define GPUREG_ATTRIBBUFFER3_CONFIG2 0x020E ///< Attribute buffers 3 configuration. +#define GPUREG_ATTRIBBUFFER4_OFFSET 0x020F ///< Attribute buffers 4 offset. +#define GPUREG_ATTRIBBUFFER4_CONFIG1 0x0210 ///< Attribute buffers 4 configuration. +#define GPUREG_ATTRIBBUFFER4_CONFIG2 0x0211 ///< Attribute buffers 4 configuration. +#define GPUREG_ATTRIBBUFFER5_OFFSET 0x0212 ///< Attribute buffers 5 offset. +#define GPUREG_ATTRIBBUFFER5_CONFIG1 0x0213 ///< Attribute buffers 5 configuration. +#define GPUREG_ATTRIBBUFFER5_CONFIG2 0x0214 ///< Attribute buffers 5 configuration. +#define GPUREG_ATTRIBBUFFER6_OFFSET 0x0215 ///< Attribute buffers 6 offset. +#define GPUREG_ATTRIBBUFFER6_CONFIG1 0x0216 ///< Attribute buffers 6 configuration. +#define GPUREG_ATTRIBBUFFER6_CONFIG2 0x0217 ///< Attribute buffers 6 configuration. +#define GPUREG_ATTRIBBUFFER7_OFFSET 0x0218 ///< Attribute buffers 7 offset. +#define GPUREG_ATTRIBBUFFER7_CONFIG1 0x0219 ///< Attribute buffers 7 configuration. +#define GPUREG_ATTRIBBUFFER7_CONFIG2 0x021A ///< Attribute buffers 7 configuration. +#define GPUREG_ATTRIBBUFFER8_OFFSET 0x021B ///< Attribute buffers 8 offset. +#define GPUREG_ATTRIBBUFFER8_CONFIG1 0x021C ///< Attribute buffers 8 configuration. +#define GPUREG_ATTRIBBUFFER8_CONFIG2 0x021D ///< Attribute buffers 8 configuration. +#define GPUREG_ATTRIBBUFFER9_OFFSET 0x021E ///< Attribute buffers 9 offset. +#define GPUREG_ATTRIBBUFFER9_CONFIG1 0x021F ///< Attribute buffers 9 configuration. +#define GPUREG_ATTRIBBUFFER9_CONFIG2 0x0220 ///< Attribute buffers 9 configuration. +#define GPUREG_ATTRIBBUFFERA_OFFSET 0x0221 ///< Attribute buffers A offset. +#define GPUREG_ATTRIBBUFFERA_CONFIG1 0x0222 ///< Attribute buffers A configuration. +#define GPUREG_ATTRIBBUFFERA_CONFIG2 0x0223 ///< Attribute buffers A configuration. +#define GPUREG_ATTRIBBUFFERB_OFFSET 0x0224 ///< Attribute buffers B offset. +#define GPUREG_ATTRIBBUFFERB_CONFIG1 0x0225 ///< Attribute buffers B configuration. +#define GPUREG_ATTRIBBUFFERB_CONFIG2 0x0226 ///< Attribute buffers B configuration. +#define GPUREG_INDEXBUFFER_CONFIG 0x0227 ///< Index buffer configuration. +#define GPUREG_NUMVERTICES 0x0228 ///< Number of vertices. +#define GPUREG_GEOSTAGE_CONFIG 0x0229 ///< Geometry stage configuration. +#define GPUREG_VERTEX_OFFSET 0x022A ///< Vertex offset. +#define GPUREG_022B 0x022B ///< Unknown. +#define GPUREG_022C 0x022C ///< Unknown. +#define GPUREG_POST_VERTEX_CACHE_NUM 0x022D ///< Unknown. +#define GPUREG_DRAWARRAYS 0x022E ///< Draw arrays trigger. +#define GPUREG_DRAWELEMENTS 0x022F ///< Draw arrays elements. +#define GPUREG_0230 0x0230 ///< Unknown. +#define GPUREG_VTX_FUNC 0x0231 ///< Unknown. +#define GPUREG_FIXEDATTRIB_INDEX 0x0232 ///< Fixed attribute index. +#define GPUREG_FIXEDATTRIB_DATA0 0x0233 ///< Fixed attribute data 0. +#define GPUREG_FIXEDATTRIB_DATA1 0x0234 ///< Fixed attribute data 1. +#define GPUREG_FIXEDATTRIB_DATA2 0x0235 ///< Fixed attribute data 2. +#define GPUREG_0236 0x0236 ///< Unknown. +#define GPUREG_0237 0x0237 ///< Unknown. +#define GPUREG_CMDBUF_SIZE0 0x0238 ///< Command buffer size 0. +#define GPUREG_CMDBUF_SIZE1 0x0239 ///< Command buffer size 1. +#define GPUREG_CMDBUF_ADDR0 0x023A ///< Command buffer address 0. +#define GPUREG_CMDBUF_ADDR1 0x023B ///< Command buffer address 1. +#define GPUREG_CMDBUF_JUMP0 0x023C ///< Command buffer jump 0. +#define GPUREG_CMDBUF_JUMP1 0x023D ///< Command buffer jump 1. +#define GPUREG_023E 0x023E ///< Unknown. +#define GPUREG_023F 0x023F ///< Unknown. +#define GPUREG_0240 0x0240 ///< Unknown. +#define GPUREG_0241 0x0241 ///< Unknown. +#define GPUREG_VSH_NUM_ATTR 0x0242 ///< Unknown. +#define GPUREG_0243 0x0243 ///< Unknown. +#define GPUREG_VSH_COM_MODE 0x0244 ///< Unknown. +#define GPUREG_START_DRAW_FUNC0 0x0245 ///< Unknown. +#define GPUREG_0246 0x0246 ///< Unknown. +#define GPUREG_0247 0x0247 ///< Unknown. +#define GPUREG_0248 0x0248 ///< Unknown. +#define GPUREG_0249 0x0249 ///< Unknown. +#define GPUREG_VSH_OUTMAP_TOTAL1 0x024A ///< Unknown. +#define GPUREG_024B 0x024B ///< Unknown. +#define GPUREG_024C 0x024C ///< Unknown. +#define GPUREG_024D 0x024D ///< Unknown. +#define GPUREG_024E 0x024E ///< Unknown. +#define GPUREG_024F 0x024F ///< Unknown. +#define GPUREG_0250 0x0250 ///< Unknown. +#define GPUREG_VSH_OUTMAP_TOTAL2 0x0251 ///< Unknown. +#define GPUREG_GSH_MISC0 0x0252 ///< Unknown. +#define GPUREG_GEOSTAGE_CONFIG2 0x0253 ///< Unknown. +#define GPUREG_GSH_MISC1 0x0254 ///< Unknown. +#define GPUREG_0255 0x0255 ///< Unknown. +#define GPUREG_0256 0x0256 ///< Unknown. +#define GPUREG_0257 0x0257 ///< Unknown. +#define GPUREG_0258 0x0258 ///< Unknown. +#define GPUREG_0259 0x0259 ///< Unknown. +#define GPUREG_025A 0x025A ///< Unknown. +#define GPUREG_025B 0x025B ///< Unknown. +#define GPUREG_025C 0x025C ///< Unknown. +#define GPUREG_025D 0x025D ///< Unknown. +#define GPUREG_PRIMITIVE_CONFIG 0x025E ///< Primitive configuration. +#define GPUREG_RESTART_PRIMITIVE 0x025F ///< Restart primitive flag. +#define GPUREG_0260 0x0260 ///< Unknown. +#define GPUREG_0261 0x0261 ///< Unknown. +#define GPUREG_0262 0x0262 ///< Unknown. +#define GPUREG_0263 0x0263 ///< Unknown. +#define GPUREG_0264 0x0264 ///< Unknown. +#define GPUREG_0265 0x0265 ///< Unknown. +#define GPUREG_0266 0x0266 ///< Unknown. +#define GPUREG_0267 0x0267 ///< Unknown. +#define GPUREG_0268 0x0268 ///< Unknown. +#define GPUREG_0269 0x0269 ///< Unknown. +#define GPUREG_026A 0x026A ///< Unknown. +#define GPUREG_026B 0x026B ///< Unknown. +#define GPUREG_026C 0x026C ///< Unknown. +#define GPUREG_026D 0x026D ///< Unknown. +#define GPUREG_026E 0x026E ///< Unknown. +#define GPUREG_026F 0x026F ///< Unknown. +#define GPUREG_0270 0x0270 ///< Unknown. +#define GPUREG_0271 0x0271 ///< Unknown. +#define GPUREG_0272 0x0272 ///< Unknown. +#define GPUREG_0273 0x0273 ///< Unknown. +#define GPUREG_0274 0x0274 ///< Unknown. +#define GPUREG_0275 0x0275 ///< Unknown. +#define GPUREG_0276 0x0276 ///< Unknown. +#define GPUREG_0277 0x0277 ///< Unknown. +#define GPUREG_0278 0x0278 ///< Unknown. +#define GPUREG_0279 0x0279 ///< Unknown. +#define GPUREG_027A 0x027A ///< Unknown. +#define GPUREG_027B 0x027B ///< Unknown. +#define GPUREG_027C 0x027C ///< Unknown. +#define GPUREG_027D 0x027D ///< Unknown. +#define GPUREG_027E 0x027E ///< Unknown. +#define GPUREG_027F 0x027F ///< Unknown. +///@} + +///@name Geometry shader registers (0x280-0x2AF) +///@{ +#define GPUREG_GSH_BOOLUNIFORM 0x0280 ///< Geometry shader bool uniforms. +#define GPUREG_GSH_INTUNIFORM_I0 0x0281 ///< Geometry shader integer uniform 0. +#define GPUREG_GSH_INTUNIFORM_I1 0x0282 ///< Geometry shader integer uniform 1. +#define GPUREG_GSH_INTUNIFORM_I2 0x0283 ///< Geometry shader integer uniform 2. +#define GPUREG_GSH_INTUNIFORM_I3 0x0284 ///< Geometry shader integer uniform 3. +#define GPUREG_0285 0x0285 ///< Unknown. +#define GPUREG_0286 0x0286 ///< Unknown. +#define GPUREG_0287 0x0287 ///< Unknown. +#define GPUREG_0288 0x0288 ///< Unknown. +#define GPUREG_GSH_INPUTBUFFER_CONFIG 0x0289 ///< Geometry shader input buffer configuration. +#define GPUREG_GSH_ENTRYPOINT 0x028A ///< Geometry shader entry point. +#define GPUREG_GSH_ATTRIBUTES_PERMUTATION_LOW 0x028B ///< Geometry shader attribute permutations low. +#define GPUREG_GSH_ATTRIBUTES_PERMUTATION_HIGH 0x028C ///< Geometry shader attribute permutations high. +#define GPUREG_GSH_OUTMAP_MASK 0x028D ///< Geometry shader output map mask. +#define GPUREG_028E 0x028E ///< Unknown. +#define GPUREG_GSH_CODETRANSFER_END 0x028F ///< Geometry shader code transfer end trigger. +#define GPUREG_GSH_FLOATUNIFORM_CONFIG 0x0290 ///< Geometry shader float uniform configuration. +#define GPUREG_GSH_FLOATUNIFORM_DATA 0x0291 ///< Geometry shader float uniform data. +#define GPUREG_0299 0x0299 ///< Unknown. +#define GPUREG_029A 0x029A ///< Unknown. +#define GPUREG_GSH_CODETRANSFER_CONFIG 0x029B ///< Geometry shader code transfer configuration. +#define GPUREG_GSH_CODETRANSFER_DATA 0x029C ///< Geometry shader code transfer data. +#define GPUREG_02A4 0x02A4 ///< Unknown. +#define GPUREG_GSH_OPDESCS_CONFIG 0x02A5 ///< Geometry shader operand description configuration. +#define GPUREG_GSH_OPDESCS_DATA 0x02A6 ///< Geometry shader operand description data. +#define GPUREG_02AE 0x02AE ///< Unknown. +#define GPUREG_02AF 0x02AF ///< Unknown. +///@} + +///@name Vertex shader registers (0x2B0-0x2DF) +///@{ +#define GPUREG_VSH_BOOLUNIFORM 0x02B0 ///< Vertex shader bool uniforms. +#define GPUREG_VSH_INTUNIFORM_I0 0x02B1 ///< Vertex shader integer uniform 0. +#define GPUREG_VSH_INTUNIFORM_I1 0x02B2 ///< Vertex shader integer uniform 1. +#define GPUREG_VSH_INTUNIFORM_I2 0x02B3 ///< Vertex shader integer uniform 2. +#define GPUREG_VSH_INTUNIFORM_I3 0x02B4 ///< Vertex shader integer uniform 3. +#define GPUREG_02B5 0x02B5 ///< Unknown. +#define GPUREG_02B6 0x02B6 ///< Unknown. +#define GPUREG_02B7 0x02B7 ///< Unknown. +#define GPUREG_02B8 0x02B8 ///< Unknown. +#define GPUREG_VSH_INPUTBUFFER_CONFIG 0x02B9 ///< Vertex shader input buffer configuration. +#define GPUREG_VSH_ENTRYPOINT 0x02BA ///< Vertex shader entry point. +#define GPUREG_VSH_ATTRIBUTES_PERMUTATION_LOW 0x02BB ///< Vertex shader attribute permutations low. +#define GPUREG_VSH_ATTRIBUTES_PERMUTATION_HIGH 0x02BC ///< Vertex shader attribute permutations high. +#define GPUREG_VSH_OUTMAP_MASK 0x02BD ///< Vertex shader output map mask. +#define GPUREG_02BE 0x02BE ///< Unknown. +#define GPUREG_VSH_CODETRANSFER_END 0x02BF ///< Vertex shader code transfer end trigger. +#define GPUREG_VSH_FLOATUNIFORM_CONFIG 0x02C0 ///< Vertex shader float uniform configuration. +#define GPUREG_VSH_FLOATUNIFORM_DATA 0x02C1 ///< Vertex shader float uniform data. +#define GPUREG_02C9 0x02C9 ///< Unknown. +#define GPUREG_02CA 0x02CA ///< Unknown. +#define GPUREG_VSH_CODETRANSFER_CONFIG 0x02CB ///< Vertex shader code transfer configuration. +#define GPUREG_VSH_CODETRANSFER_DATA 0x02CC ///< Vertex shader code transfer data. +#define GPUREG_02D4 0x02D4 ///< Unknown. +#define GPUREG_VSH_OPDESCS_CONFIG 0x02D5 ///< Vertex shader operand description configuration. +#define GPUREG_VSH_OPDESCS_DATA 0x02D6 ///< Vertex shader operand description data. +#define GPUREG_02DE 0x02DE ///< Unknown. +#define GPUREG_02DF 0x02DF ///< Unknown. +///@} + +///@name Unknown registers (0x2E0-0x2FF) +///@{ +#define GPUREG_02E0 0x02E0 ///< Unknown. +#define GPUREG_02E1 0x02E1 ///< Unknown. +#define GPUREG_02E2 0x02E2 ///< Unknown. +#define GPUREG_02E3 0x02E3 ///< Unknown. +#define GPUREG_02E4 0x02E4 ///< Unknown. +#define GPUREG_02E5 0x02E5 ///< Unknown. +#define GPUREG_02E6 0x02E6 ///< Unknown. +#define GPUREG_02E7 0x02E7 ///< Unknown. +#define GPUREG_02E8 0x02E8 ///< Unknown. +#define GPUREG_02E9 0x02E9 ///< Unknown. +#define GPUREG_02EA 0x02EA ///< Unknown. +#define GPUREG_02EB 0x02EB ///< Unknown. +#define GPUREG_02EC 0x02EC ///< Unknown. +#define GPUREG_02ED 0x02ED ///< Unknown. +#define GPUREG_02EE 0x02EE ///< Unknown. +#define GPUREG_02EF 0x02EF ///< Unknown. +#define GPUREG_02F0 0x02F0 ///< Unknown. +#define GPUREG_02F1 0x02F1 ///< Unknown. +#define GPUREG_02F2 0x02F2 ///< Unknown. +#define GPUREG_02F3 0x02F3 ///< Unknown. +#define GPUREG_02F4 0x02F4 ///< Unknown. +#define GPUREG_02F5 0x02F5 ///< Unknown. +#define GPUREG_02F6 0x02F6 ///< Unknown. +#define GPUREG_02F7 0x02F7 ///< Unknown. +#define GPUREG_02F8 0x02F8 ///< Unknown. +#define GPUREG_02F9 0x02F9 ///< Unknown. +#define GPUREG_02FA 0x02FA ///< Unknown. +#define GPUREG_02FB 0x02FB ///< Unknown. +#define GPUREG_02FC 0x02FC ///< Unknown. +#define GPUREG_02FD 0x02FD ///< Unknown. +#define GPUREG_02FE 0x02FE ///< Unknown. +#define GPUREG_02FF 0x02FF ///< Unknown. +///@} diff --git a/libctru/include/3ds/gpu/shaderProgram.h b/libctru/include/3ds/gpu/shaderProgram.h new file mode 100644 index 0000000000..303c49dfc7 --- /dev/null +++ b/libctru/include/3ds/gpu/shaderProgram.h @@ -0,0 +1,120 @@ +/** + * @file shaderProgram.h + * @brief Functions for working with shaders. + */ +#pragma once + +#include <3ds/types.h> +#include <3ds/gpu/shbin.h> + +/// 24-bit float uniforms. +typedef struct +{ + u32 id; ///< Uniform ID. + u32 data[3]; ///< Uniform data. +}float24Uniform_s; + +/// Describes an instance of either a vertex or geometry shader. +typedef struct +{ + DVLE_s* dvle; ///< Shader DVLE. + u16 boolUniforms; ///< Boolean uniforms. + u16 boolUniformMask; ///< Used boolean uniform mask. + u32 intUniforms[4]; ///< Integer uniforms. + float24Uniform_s* float24Uniforms; ///< 24-bit float uniforms. + u8 intUniformMask; ///< Used integer uniform mask. + u8 numFloat24Uniforms; ///< Float uniform count. +}shaderInstance_s; + +/// Describes an instance of a full shader program. +typedef struct +{ + shaderInstance_s* vertexShader; ///< Vertex shader. + shaderInstance_s* geometryShader; ///< Geometry shader. + u32 geoShaderInputPermutation[2]; ///< Geometry shader input permutation. + u8 geoShaderInputStride; ///< Geometry shader input stride. +}shaderProgram_s; + +/** + * @brief Initializes a shader instance. + * @param si Shader instance to initialize. + * @param dvle DVLE to initialize the shader instance with. + */ +Result shaderInstanceInit(shaderInstance_s* si, DVLE_s* dvle); + +/** + * @brief Frees a shader instance. + * @param si Shader instance to free. + */ +Result shaderInstanceFree(shaderInstance_s* si); + +/** + * @brief Sets a bool uniform of a shader. + * @param si Shader instance to use. + * @param id ID of the bool uniform. + * @param value Value to set. + */ +Result shaderInstanceSetBool(shaderInstance_s* si, int id, bool value); + +/** + * @brief Gets a bool uniform of a shader. + * @param si Shader instance to use. + * @param id ID of the bool uniform. + * @param value Pointer to output the value to. + */ +Result shaderInstanceGetBool(shaderInstance_s* si, int id, bool* value); + +/** + * @brief Gets the location of a shader's uniform. + * @param si Shader instance to use. + * @param name Name of the uniform. + */ +s8 shaderInstanceGetUniformLocation(shaderInstance_s* si, const char* name); + +/** + * @brief Initializes a shader program. + * @param sp Shader program to initialize. + */ +Result shaderProgramInit(shaderProgram_s* sp); + +/** + * @brief Frees a shader program. + * @param sp Shader program to free. + */ +Result shaderProgramFree(shaderProgram_s* sp); + +/** + * @brief Sets the vertex shader of a shader program. + * @param sp Shader program to use. + * @param dvle Vertex shader to set. + */ +Result shaderProgramSetVsh(shaderProgram_s* sp, DVLE_s* dvle); + +/** + * @brief Sets the geometry shader of a shader program. + * @param sp Shader program to use. + * @param dvle Geometry shader to set. + * @param stride Input stride of the shader (pass 0 to match the number of outputs of the vertex shader). + */ +Result shaderProgramSetGsh(shaderProgram_s* sp, DVLE_s* dvle, u8 stride); + +/** + * @brief Configures the permutation of the input attributes of the geometry shader of a shader program. + * @param sp Shader program to use. + * @param permutation Attribute permutation to use. + */ +Result shaderProgramSetGshInputPermutation(shaderProgram_s* sp, u64 permutation); + +/** + * @brief Configures the shader units to use the specified shader program. + * @param sp Shader program to use. + * @param sendVshCode When true, the vertex shader's code and operand descriptors are uploaded. + * @param sendGshCode When true, the geometry shader's code and operand descriptors are uploaded. + */ +Result shaderProgramConfigure(shaderProgram_s* sp, bool sendVshCode, bool sendGshCode); + +/** + * @brief Same as shaderProgramConfigure, but always loading code/operand descriptors and uploading DVLE constants afterwards. + * @param sp Shader program to use. + */ +Result shaderProgramUse(shaderProgram_s* sp); diff --git a/libctru/include/3ds/gpu/shbin.h b/libctru/include/3ds/gpu/shbin.h new file mode 100644 index 0000000000..e331beb7db --- /dev/null +++ b/libctru/include/3ds/gpu/shbin.h @@ -0,0 +1,130 @@ +/** + * @file shbin.h + * @brief Shader binary support. + */ +#pragma once + +#include <3ds/gpu/gpu.h> + +/// DVLE type. +typedef enum{ + VERTEX_SHDR=GPU_VERTEX_SHADER, ///< Vertex shader. + GEOMETRY_SHDR=GPU_GEOMETRY_SHADER ///< Geometry shader. +}DVLE_type; + +/// Constant type. +typedef enum{ + DVLE_CONST_BOOL=0x0, ///< Bool. + DVLE_CONST_u8=0x1, ///< Unsigned 8-bit integer. + DVLE_CONST_FLOAT24=0x2, ///< 24-bit float. +}DVLE_constantType; + +/// Output attribute. +typedef enum{ + RESULT_POSITION = 0x0, ///< Position. + RESULT_NORMALQUAT = 0x1, ///< Normal Quaternion. + RESULT_COLOR = 0x2, ///< Color. + RESULT_TEXCOORD0 = 0x3, ///< Texture coordinate 0. + RESULT_TEXCOORD0W = 0x4, ///< Texture coordinate 0 W. + RESULT_TEXCOORD1 = 0x5, ///< Texture coordinate 1. + RESULT_TEXCOORD2 = 0x6, ///< Texture coordinate 2. + RESULT_VIEW = 0x8, ///< View. + RESULT_DUMMY = 0x9, ///< Dummy attribute (used as passthrough for geometry shader input). +}DVLE_outputAttribute_t; + +/// Geometry shader operation modes. +typedef enum +{ + GSH_POINT = 0, ///< Point processing mode. + GSH_VARIABLE_PRIM = 1, ///< Variable-size primitive processing mode. + GSH_FIXED_PRIM = 2, ///< Fixed-size primitive processing mode. +} DVLE_geoShaderMode; + +/// DVLP data. +typedef struct{ + u32 codeSize; ///< Code size. + u32* codeData; ///< Code data. + u32 opdescSize; ///< Operand description size. + u32* opcdescData; ///< Operand description data. +}DVLP_s; + +/// DVLE constant entry data. +typedef struct{ + u16 type; ///< Constant type. See @ref DVLE_constantType + u16 id; ///< Constant ID. + u32 data[4]; ///< Constant data. +}DVLE_constEntry_s; + +/// DVLE output entry data. +typedef struct{ + u16 type; ///< Output type. See @ref DVLE_outputAttribute_t + u16 regID; ///< Output register ID. + u8 mask; ///< Output mask. + u8 unk[3]; ///< Unknown. +}DVLE_outEntry_s; + +/// DVLE uniform entry data. +typedef struct{ + u32 symbolOffset; ///< Symbol offset. + u16 startReg; ///< Start register. + u16 endReg; ///< End register. +}DVLE_uniformEntry_s; + +/// DVLE data. +typedef struct{ + DVLE_type type; ///< DVLE type. + bool mergeOutmaps; ///< true = merge vertex/geometry shader outmaps ('dummy' output attribute is present). + DVLE_geoShaderMode gshMode; ///< Geometry shader operation mode. + u8 gshFixedVtxStart; ///< Starting float uniform register number for storing the fixed-size primitive vertex array. + u8 gshVariableVtxNum; ///< Number of fully-defined vertices in the variable-size primitive vertex array. + u8 gshFixedVtxNum; ///< Number of vertices in the fixed-size primitive vertex array. + DVLP_s* dvlp; ///< Contained DVLPs. + u32 mainOffset; ///< Offset of the start of the main function. + u32 endmainOffset; ///< Offset of the end of the main function. + u32 constTableSize; ///< Constant table size. + DVLE_constEntry_s* constTableData; ///< Constant table data. + u32 outTableSize; ///< Output table size. + DVLE_outEntry_s* outTableData; ///< Output table data. + u32 uniformTableSize; ///< Uniform table size. + DVLE_uniformEntry_s* uniformTableData; ///< Uniform table data. + char* symbolTableData; ///< Symbol table data. + u8 outmapMask; ///< Output map mask. + u32 outmapData[8]; ///< Output map data. + u32 outmapMode; ///< Output map mode. + u32 outmapClock; ///< Output map attribute clock. +}DVLE_s; + +/// DVLB data. +typedef struct{ + u32 numDVLE; ///< DVLE count. + DVLP_s DVLP; ///< Primary DVLP. + DVLE_s* DVLE; ///< Contained DVLE. +}DVLB_s; + +/** + * @brief Parses a shader binary. + * @param shbinData Shader binary data. + * @param shbinSize Shader binary size. + * @return The parsed shader binary. + */ +DVLB_s* DVLB_ParseFile(u32* shbinData, u32 shbinSize); + +/** + * @brief Frees shader binary data. + * @param dvlb DVLB to free. + */ +void DVLB_Free(DVLB_s* dvlb); + +/** + * @brief Gets a uniform register index from a shader. + * @param dvle Shader to get the register from. + * @param name Name of the register. + * @return The uniform register index. + */ +s8 DVLE_GetUniformRegister(DVLE_s* dvle, const char* name); + +/** + * @brief Generates a shader output map. + * @param dvle Shader to generate an output map for. + */ +void DVLE_GenerateOutmap(DVLE_s* dvle); diff --git a/libctru/include/3ds/ipc.h b/libctru/include/3ds/ipc.h new file mode 100644 index 0000000000..e3a44b45f3 --- /dev/null +++ b/libctru/include/3ds/ipc.h @@ -0,0 +1,118 @@ +/** + * @file ipc.h + * @brief Inter Process Communication helpers + */ +#pragma once + +#include <3ds/types.h> + +/// IPC buffer access rights. +typedef enum +{ + IPC_BUFFER_R = BIT(1), ///< Readable + IPC_BUFFER_W = BIT(2), ///< Writable + IPC_BUFFER_RW = IPC_BUFFER_R | IPC_BUFFER_W ///< Readable and Writable +} IPC_BufferRights; + +/** + * @brief Creates a command header to be used for IPC + * @param command_id ID of the command to create a header for. + * @param normal_params Size of the normal parameters in words. Up to 63. + * @param translate_params Size of the translate parameters in words. Up to 63. + * @return The created IPC header. + * + * Normal parameters are sent directly to the process while the translate parameters might go through modifications and checks by the kernel. + * The translate parameters are described by headers generated with the IPC_Desc_* functions. + * + * @note While #normal_params is equivalent to the number of normal parameters, #translate_params includes the size occupied by the translate parameters headers. + */ +static inline u32 IPC_MakeHeader(u16 command_id, unsigned normal_params, unsigned translate_params) +{ + return ((u32) command_id << 16) | (((u32) normal_params & 0x3F) << 6) | (((u32) translate_params & 0x3F) << 0); +} + +/** + * @brief Creates a header to share handles + * @param number The number of handles following this header. Max 64. + * @return The created shared handles header. + * + * The #number next values are handles that will be shared between the two processes. + * + * @note Zero values will have no effect. + */ +static inline u32 IPC_Desc_SharedHandles(unsigned number) +{ + return ((u32)(number - 1) << 26); +} + +/** + * @brief Creates the header to transfer handle ownership + * @param number The number of handles following this header. Max 64. + * @return The created handle transfer header. + * + * The #number next values are handles that will be duplicated and closed by the other process. + * + * @note Zero values will have no effect. + */ +static inline u32 IPC_Desc_MoveHandles(unsigned number) +{ + return ((u32)(number - 1) << 26) | 0x10; +} + +/** + * @brief Returns the code to ask the kernel to fill the handle with the current process ID. + * @return The code to request the current process ID. + * + * The next value is a placeholder that will be replaced by the current process ID by the kernel. + */ +static inline u32 IPC_Desc_CurProcessId(void) +{ + return 0x20; +} + +static inline DEPRECATED u32 IPC_Desc_CurProcessHandle(void) +{ + return IPC_Desc_CurProcessId(); +} + +/** + * @brief Creates a header describing a static buffer. + * @param size Size of the buffer. Max ?0x03FFFF?. + * @param buffer_id The Id of the buffer. Max 0xF. + * @return The created static buffer header. + * + * The next value is a pointer to the buffer. It will be copied to TLS offset 0x180 + static_buffer_id*8. + */ +static inline u32 IPC_Desc_StaticBuffer(size_t size, unsigned buffer_id) +{ + return (size << 14) | ((buffer_id & 0xF) << 10) | 0x2; +} + +/** + * @brief Creates a header describing a buffer to be sent over PXI. + * @param size Size of the buffer. Max 0x00FFFFFF. + * @param buffer_id The Id of the buffer. Max 0xF. + * @param is_read_only true if the buffer is read-only. If false, the buffer is considered to have read-write access. + * @return The created PXI buffer header. + * + * The next value is a phys-address of a table located in the BASE memregion. + */ +static inline u32 IPC_Desc_PXIBuffer(size_t size, unsigned buffer_id, bool is_read_only) +{ + u8 type = 0x4; + if(is_read_only)type = 0x6; + return (size << 8) | ((buffer_id & 0xF) << 4) | type; +} + +/** + * @brief Creates a header describing a buffer from the main memory. + * @param size Size of the buffer. Max 0x0FFFFFFF. + * @param rights The rights of the buffer for the destination process. + * @return The created buffer header. + * + * The next value is a pointer to the buffer. + */ +static inline u32 IPC_Desc_Buffer(size_t size, IPC_BufferRights rights) +{ + return (size << 4) | 0x8 | rights; +} diff --git a/libctru/include/3ds/mii.h b/libctru/include/3ds/mii.h new file mode 100644 index 0000000000..692ceccfc6 --- /dev/null +++ b/libctru/include/3ds/mii.h @@ -0,0 +1,159 @@ +/** + * @file mii.h + * @brief Shared Mii struct. + * + * @see https://www.3dbrew.org/wiki/Mii#Mii_format + */ +#pragma once + +#include <3ds/types.h> + +/// Shared Mii struct +typedef struct +{ + u8 magic; ///< Always 3? + + /// Mii options + struct + { + bool allow_copying : 1; ///< True if copying is allowed + bool is_private_name : 1; ///< Private name? + u8 region_lock : 2; ///< Region lock (0=no lock, 1=JPN, 2=USA, 3=EUR) + u8 char_set : 2; ///< Character set (0=JPN+USA+EUR, 1=CHN, 2=KOR, 3=TWN) + } mii_options; + + /// Mii position in Mii selector or Mii maker + struct + { + u8 page_index : 4; ///< Page index of Mii + u8 slot_index : 4; ///< Slot offset of Mii on its Page + } mii_pos; + + /// Console Identity + struct + { + u8 unknown0 : 4; ///< Mabye padding (always seems to be 0)? + u8 origin_console : 3; ///< Console that the Mii was created on (1=WII, 2=DSI, 3=3DS) + } console_identity; + + u64 system_id; ///< Identifies the system that the Mii was created on (Determines pants) + u32 mii_id; ///< ID of Mii + u8 mac[6]; ///< Creator's system's full MAC address + u8 pad[2]; ///< Padding + + /// Mii details + struct { + bool sex : 1; ///< Sex of Mii (False=Male, True=Female) + u16 bday_month : 4; ///< Month of Mii's birthday + u16 bday_day : 5; ///< Day of Mii's birthday + u16 shirt_color : 4; ///< Color of Mii's shirt + bool favorite : 1; ///< Whether the Mii is one of your 10 favorite Mii's + } mii_details; + + u16 mii_name[10]; ///< Name of Mii (Encoded using UTF16) + u8 height; ///< How tall the Mii is + u8 width; ///< How wide the Mii is + + /// Face style + struct + { + bool disable_sharing : 1; ///< Whether or not Sharing of the Mii is allowed + u8 shape : 4; ///< Face shape + u8 skinColor : 3; ///< Color of skin + } face_style; + + /// Face details + struct + { + u8 wrinkles : 4; + u8 makeup : 4; + } face_details; + + u8 hair_style; + + /// Hair details + struct + { + u8 color : 3; + bool flip : 1; + } hair_details; + + /// Eye details + struct + { + u32 style : 6; + u32 color : 3; + u32 scale : 4; + u32 yscale : 3; + u32 rotation : 5; + u32 xspacing : 4; + u32 yposition : 5; + } eye_details; + + /// Eyebrow details + struct + { + u32 style : 5; + u32 color : 3; + u32 scale : 4; + u32 yscale : 3; + u32 pad : 1; + u32 rotation : 5; + u32 xspacing : 4; + u32 yposition : 5; + } eyebrow_details; + + /// Nose details + struct + { + u16 style : 5; + u16 scale : 4; + u16 yposition : 5; + } nose_details; + + /// Mouth details + struct + { + u16 style : 6; + u16 color : 3; + u16 scale : 4; + u16 yscale : 3; + } mouth_details; + + /// Mustache details + struct + { + u16 mouth_yposition : 5; + u16 mustach_style : 3; + u16 pad : 2; + } mustache_details; + + /// Beard details + struct + { + u16 style : 3; + u16 color : 3; + u16 scale : 4; + u16 ypos : 5; + } beard_details; + + /// Glasses details + struct + { + u16 style : 4; + u16 color : 3; + u16 scale : 4; + u16 ypos : 5; + } glasses_details; + + /// Mole details + struct + { + bool enable : 1; + u16 scale : 5; + u16 xpos : 5; + u16 ypos : 5; + } mole_details; + + u16 author_name[10]; ///< Name of Mii's author (Encoded using UTF16) +} PACKED MiiData; diff --git a/libctru/include/3ds/ndsp/channel.h b/libctru/include/3ds/ndsp/channel.h new file mode 100644 index 0000000000..d3c81b49a5 --- /dev/null +++ b/libctru/include/3ds/ndsp/channel.h @@ -0,0 +1,235 @@ +/** + * @file channel.h + * @brief Functions for interacting with DSP audio channels. + */ +#pragma once + +///@name Data types +///@{ +/// Supported sample encodings. +enum +{ + NDSP_ENCODING_PCM8 = 0, ///< PCM8 + NDSP_ENCODING_PCM16, ///< PCM16 + NDSP_ENCODING_ADPCM, ///< DSPADPCM (GameCube format) +}; + +/// Specifies the number of channels used in a sample. +#define NDSP_CHANNELS(n) ((u32)(n) & 3) +/// Specifies the encoding used in a sample. +#define NDSP_ENCODING(n) (((u32)(n) & 3) << 2) + +/// Channel format flags for use with ndspChnSetFormat. +enum +{ + NDSP_FORMAT_MONO_PCM8 = NDSP_CHANNELS(1) | NDSP_ENCODING(NDSP_ENCODING_PCM8), ///< Buffer contains Mono PCM8. + NDSP_FORMAT_MONO_PCM16 = NDSP_CHANNELS(1) | NDSP_ENCODING(NDSP_ENCODING_PCM16), ///< Buffer contains Mono PCM16. + NDSP_FORMAT_MONO_ADPCM = NDSP_CHANNELS(1) | NDSP_ENCODING(NDSP_ENCODING_ADPCM), ///< Buffer contains Mono ADPCM. + NDSP_FORMAT_STEREO_PCM8 = NDSP_CHANNELS(2) | NDSP_ENCODING(NDSP_ENCODING_PCM8), ///< Buffer contains Stereo PCM8. + NDSP_FORMAT_STEREO_PCM16 = NDSP_CHANNELS(2) | NDSP_ENCODING(NDSP_ENCODING_PCM16), ///< Buffer contains Stereo PCM16. + + NDSP_FORMAT_PCM8 = NDSP_FORMAT_MONO_PCM8, ///< (Alias) Buffer contains Mono PCM8. + NDSP_FORMAT_PCM16 = NDSP_FORMAT_MONO_PCM16, ///< (Alias) Buffer contains Mono PCM16. + NDSP_FORMAT_ADPCM = NDSP_FORMAT_MONO_ADPCM, ///< (Alias) Buffer contains Mono ADPCM. + + // Flags + NDSP_FRONT_BYPASS = BIT(4), ///< Front bypass. + NDSP_3D_SURROUND_PREPROCESSED = BIT(6), ///< (?) Unknown, under research +}; + +/// Interpolation types. +typedef enum +{ + NDSP_INTERP_POLYPHASE = 0, ///< Polyphase interpolation + NDSP_INTERP_LINEAR = 1, ///< Linear interpolation + NDSP_INTERP_NONE = 2, ///< No interpolation +} ndspInterpType; + +///@} + +///@name Basic channel operation +///@{ +/** + * @brief Resets a channel. + * @param id ID of the channel (0..23). + */ +void ndspChnReset(int id); + +/** + * @brief Initializes the parameters of a channel. + * @param id ID of the channel (0..23). + */ +void ndspChnInitParams(int id); + +/** + * @brief Checks whether a channel is currently playing. + * @param id ID of the channel (0..23). + * @return Whether the channel is currently playing. + */ +bool ndspChnIsPlaying(int id); + +/** + * @brief Gets the current sample position of a channel. + * @param id ID of the channel (0..23). + * @return The channel's sample position. + */ +u32 ndspChnGetSamplePos(int id); + +/** + * @brief Gets the sequence ID of the wave buffer that is currently playing in a channel. + * @param id ID of the channel (0..23). + * @return The sequence ID of the wave buffer. + */ +u16 ndspChnGetWaveBufSeq(int id); + +/** + * @brief Checks whether a channel is currently paused. + * @param id ID of the channel (0..23). + * @return Whether the channel is currently paused. + */ +bool ndspChnIsPaused(int id); + +/** + * @brief Sets the pause status of a channel. + * @param id ID of the channel (0..23). + * @param paused Whether the channel is to be paused (true) or unpaused (false). + */ +void ndspChnSetPaused(int id, bool paused); + +///@} + +///@name Configuration +///@{ +/** + * @brief Sets the format of a channel. + * @param id ID of the channel (0..23). + * @param format Format to use. + */ +void ndspChnSetFormat(int id, u16 format); + +/** + * @brief Sets the interpolation type of a channel. + * @param id ID of the channel (0..23). + * @param type Interpolation type to use. + */ +void ndspChnSetInterp(int id, ndspInterpType type); + +/** + * @brief Sets the sample rate of a channel. + * @param id ID of the channel (0..23). + * @param rate Sample rate to use. + */ +void ndspChnSetRate(int id, float rate); + +/** + * @brief Sets the mix parameters (volumes) of a channel. + * @param id ID of the channel (0..23). + * @param mix Mix parameters to use. Working hypothesis: + * - 0: Front left volume. + * - 1: Front right volume. + * - 2: Back left volume: + * - 3: Back right volume: + * - 4..7: Same as 0..3, but for auxiliary output 0. + * - 8..11: Same as 0..3, but for auxiliary output 1. + */ +void ndspChnSetMix(int id, float mix[12]); + +/** + * @brief Sets the DSPADPCM coefficients of a channel. + * @param id ID of the channel (0..23). + * @param coefs DSPADPCM coefficients to use. + */ +void ndspChnSetAdpcmCoefs(int id, u16 coefs[16]); +///@} + +///@name Wave buffers +///@{ +/** + * @brief Clears the wave buffer queue of a channel and stops playback. + * @param id ID of the channel (0..23). + */ +void ndspChnWaveBufClear(int id); + +/** + * @brief Adds a wave buffer to the wave buffer queue of a channel. + * @remark If the channel's wave buffer queue was empty before the use of this function, playback is started. + * @param id ID of the channel (0..23). + * @param buf Wave buffer to add. + */ +void ndspChnWaveBufAdd(int id, ndspWaveBuf* buf); +///@} + +///@name IIR filters +///@{ +/** + * @brief Configures whether the IIR monopole filter of a channel is enabled. + * @param id ID of the channel (0..23). + * @param enable Whether to enable the IIR monopole filter. + */ +void ndspChnIirMonoSetEnable(int id, bool enable); +/** + * @brief Manually sets up the parameters on monopole filter + * @param id ID of the channel (0..23). + * @param enable Whether to enable the IIR monopole filter. + */ +bool ndspChnIirMonoSetParamsCustomFilter(int id, float a0, float a1, float b0); +/** + * @brief Sets the monopole to be a low pass filter. (Note: This is a lower-quality filter than the biquad one.) + * @param id ID of the channel (0..23). + * @param f0 Low pass cut-off frequency. + */ +bool ndspChnIirMonoSetParamsLowPassFilter(int id, float f0); +/** + * @brief Sets the monopole to be a high pass filter. (Note: This is a lower-quality filter than the biquad one.) + * @param id ID of the channel (0..23). + * @param f0 High pass cut-off frequency. + */ +bool ndspChnIirMonoSetParamsHighPassFilter(int id, float f0); +/** + * @brief Configures whether the IIR biquad filter of a channel is enabled. + * @param id ID of the channel (0..23). + * @param enable Whether to enable the IIR biquad filter. + */ +void ndspChnIirBiquadSetEnable(int id, bool enable); +/** + * @brief Manually sets up the parameters of the biquad filter + * @param id ID of the channel (0..23). + */ +bool ndspChnIirBiquadSetParamsCustomFilter(int id, float a0, float a1, float a2, float b0, float b1, float b2); +/** + * @brief Sets the biquad to be a low pass filter. + * @param id ID of the channel (0..23). + * @param f0 Low pass cut-off frequency. + * @param Q "Quality factor", typically should be sqrt(2)/2 (i.e. 0.7071). + */ +bool ndspChnIirBiquadSetParamsLowPassFilter(int id, float f0, float Q); +/** + * @brief Sets the biquad to be a high pass filter. + * @param id ID of the channel (0..23). + * @param f0 High pass cut-off frequency. + * @param Q "Quality factor", typically should be sqrt(2)/2 (i.e. 0.7071). + */ +bool ndspChnIirBiquadSetParamsHighPassFilter(int id, float f0, float Q); +/** + * @brief Sets the biquad to be a band pass filter. + * @param id ID of the channel (0..23). + * @param f0 Mid-frequency. + * @param Q "Quality factor", typically should be sqrt(2)/2 (i.e. 0.7071). + */ +bool ndspChnIirBiquadSetParamsBandPassFilter(int id, float f0, float Q); +/** + * @brief Sets the biquad to be a notch filter. + * @param id ID of the channel (0..23). + * @param f0 Notch frequency. + * @param Q "Quality factor", typically should be sqrt(2)/2 (i.e. 0.7071). + */ +bool ndspChnIirBiquadSetParamsNotchFilter(int id, float f0, float Q); +/** + * @brief Sets the biquad to be a peaking equalizer. + * @param id ID of the channel (0..23). + * @param f0 Central frequency. + * @param Q "Quality factor", typically should be sqrt(2)/2 (i.e. 0.7071). + * @param gain Amount of gain (raw value = 10 ^ dB/40) + */ +bool ndspChnIirBiquadSetParamsPeakingEqualizer(int id, float f0, float Q, float gain); +///@} diff --git a/libctru/include/3ds/ndsp/ndsp.h b/libctru/include/3ds/ndsp/ndsp.h new file mode 100644 index 0000000000..928d3ee18c --- /dev/null +++ b/libctru/include/3ds/ndsp/ndsp.h @@ -0,0 +1,204 @@ +/** + * @file ndsp.h + * @brief Interface for Nintendo's default DSP component. + */ +#pragma once + +#include <3ds/os.h> + +#define NDSP_SAMPLE_RATE (SYSCLOCK_SOC / 512.0) + +///@name Data types +///@{ +/// Sound output modes. +typedef enum +{ + NDSP_OUTPUT_MONO = 0, ///< Mono sound + NDSP_OUTPUT_STEREO = 1, ///< Stereo sound + NDSP_OUTPUT_SURROUND = 2, ///< 3D Surround sound +} ndspOutputMode; + +// Clipping modes. +typedef enum +{ + NDSP_CLIP_NORMAL = 0, ///< "Normal" clipping mode (?) + NDSP_CLIP_SOFT = 1, ///< "Soft" clipping mode (?) +} ndspClippingMode; + +// Surround speaker positions. +typedef enum +{ + NDSP_SPKPOS_SQUARE = 0, ///>24) + +/// Retrieves the minor version from a packed system version. +#define GET_VERSION_MINOR(version) (((version)>>16)&0xFF) + +/// Retrieves the revision version from a packed system version. +#define GET_VERSION_REVISION(version) (((version)>> 8)&0xFF) + +#define OS_HEAP_AREA_BEGIN 0x08000000 ///< Start of the heap area in the virtual address space +#define OS_HEAP_AREA_END 0x0E000000 ///< End of the heap area in the virtual address space + +#define OS_MAP_AREA_BEGIN 0x10000000 ///< Start of the mappable area in the virtual address space +#define OS_MAP_AREA_END 0x14000000 ///< End of the mappable area in the virtual address space + +#define OS_OLD_FCRAM_VADDR 0x14000000 ///< Old pre-8.x linear FCRAM mapping virtual address +#define OS_OLD_FCRAM_PADDR 0x20000000 ///< Old pre-8.x linear FCRAM mapping physical address +#define OS_OLD_FCRAM_SIZE 0x8000000 ///< Old pre-8.x linear FCRAM mapping size (128 MiB) + +#define OS_QTMRAM_VADDR 0x1E800000 ///< New3DS QTM memory virtual address +#define OS_QTMRAM_PADDR 0x1F000000 ///< New3DS QTM memory physical address +#define OS_QTMRAM_SIZE 0x400000 ///< New3DS QTM memory size (4 MiB; last 128 KiB reserved by kernel) + +#define OS_MMIO_VADDR 0x1EC00000 ///< Memory mapped IO range virtual address +#define OS_MMIO_PADDR 0x10100000 ///< Memory mapped IO range physical address +#define OS_MMIO_SIZE 0x400000 ///< Memory mapped IO range size (4 MiB) + +#define OS_VRAM_VADDR 0x1F000000 ///< VRAM virtual address +#define OS_VRAM_PADDR 0x18000000 ///< VRAM physical address +#define OS_VRAM_SIZE 0x600000 ///< VRAM size (6 MiB) + +#define OS_DSPRAM_VADDR 0x1FF00000 ///< DSP memory virtual address +#define OS_DSPRAM_PADDR 0x1FF00000 ///< DSP memory physical address +#define OS_DSPRAM_SIZE 0x80000 ///< DSP memory size (512 KiB) + +#define OS_KERNELCFG_VADDR 0x1FF80000 ///< Kernel configuration page virtual address +#define OS_SHAREDCFG_VADDR 0x1FF81000 ///< Shared system configuration page virtual address + +#define OS_FCRAM_VADDR 0x30000000 ///< Linear FCRAM mapping virtual address +#define OS_FCRAM_PADDR 0x20000000 ///< Linear FCRAM mapping physical address +#define OS_FCRAM_SIZE 0x10000000 ///< Linear FCRAM mapping size (256 MiB) + +#define OS_KernelConfig ((osKernelConfig_s const*)OS_KERNELCFG_VADDR) ///< Pointer to the kernel configuration page (see \ref osKernelConfig_s) +#define OS_SharedConfig ((osSharedConfig_s*)OS_SHAREDCFG_VADDR) ///< Pointer to the shared system configuration page (see \ref osSharedConfig_s) + +/// Kernel configuration page (read-only). +typedef struct +{ + u32 kernel_ver; + u32 update_flag; + u64 ns_tid; + u32 kernel_syscore_ver; + u8 env_info; + u8 unit_info; + u8 boot_env; + u8 unk_0x17; + u32 kernel_ctrsdk_ver; + u32 unk_0x1c; + u32 firmlaunch_flags; + u8 unk_0x24[0xc]; + u32 app_memtype; + u8 unk_0x34[0xc]; + u32 memregion_sz[3]; + u8 unk_0x4c[0x14]; + u32 firm_ver; + u32 firm_syscore_ver; + u32 firm_ctrsdk_ver; +} osKernelConfig_s; + +/// Time reference information struct (filled in by PTM). +typedef struct +{ + u64 value_ms; ///< Milliseconds elapsed since January 1900 when this structure was last updated + u64 value_tick; ///< System ticks elapsed since boot when this structure was last updated + s64 sysclock_hz;///< System clock frequency in Hz adjusted using RTC measurements (usually around \ref SYSCLOCK_ARM11) + s64 drift_ms; ///< Measured time drift of the system clock (according to the RTC) in milliseconds since the last update +} osTimeRef_s; + +/// Shared system configuration page structure (read-only or read-write depending on exheader). +typedef struct +{ + vu32 timeref_cnt; + u8 running_hw; + u8 mcu_hwinfo; + u8 unk_0x06[0x1A]; + volatile osTimeRef_s timeref[2]; + u8 wifi_macaddr[6]; + vu8 wifi_strength; + vu8 network_state; + u8 unk_0x68[0x18]; + volatile float slider_3d; + vu8 led_3d; + vu8 led_battery; + vu8 unk_flag; + u8 unk_0x87; + u8 unk_0x88[0x18]; + vu64 menu_tid; + vu64 cur_menu_tid; + u8 unk_0xB0[0x10]; + vu8 headset_connected; +} osSharedConfig_s; + +/// Tick counter. +typedef struct +{ + u64 elapsed; ///< Elapsed CPU ticks between measurements. + u64 reference; ///< Point in time used as reference. +} TickCounter; + +/// OS_VersionBin. Format of the system version: "..-" +typedef struct +{ + u8 build; + u8 minor; + u8 mainver;//"major" in CVER, NUP version in NVer. + u8 reserved_x3; + char region;//"ASCII character for the system version region" + u8 reserved_x5[0x3]; +} OS_VersionBin; + +/** + * @brief Converts an address from virtual (process) memory to physical memory. + * @param vaddr Input virtual address. + * @return The corresponding physical address. + * It is sometimes required by services or when using the GPU command buffer. + */ +u32 osConvertVirtToPhys(const void* vaddr); + +/** + * @brief Converts 0x14* vmem to 0x30*. + * @param vaddr Input virtual address. + * @return The corresponding address in the 0x30* range, the input address if it's already within the new vmem, or 0 if it's outside of both ranges. + */ +void* osConvertOldLINEARMemToNew(const void* vaddr); + +/** + * @brief Retrieves basic information about a service error. + * @param error Error to retrieve information about. + * @return A string containing a summary of an error. + * + * This can be used to get some details about an error returned by a service call. + */ +const char* osStrError(Result error); + +/** + * @brief Gets the system's FIRM version. + * @return The system's FIRM version. + * + * This can be used to compare system versions easily with @ref SYSTEM_VERSION. + */ +static inline u32 osGetFirmVersion(void) +{ + return OS_KernelConfig->firm_ver &~ 0xFF; +} + +/** + * @brief Gets the system's kernel version. + * @return The system's kernel version. + * + * This can be used to compare system versions easily with @ref SYSTEM_VERSION. + * + * @code + * if(osGetKernelVersion() > SYSTEM_VERSION(2,46,0)) printf("You are running 9.0 or higher\n"); + * @endcode + */ +static inline u32 osGetKernelVersion(void) +{ + return OS_KernelConfig->kernel_ver &~ 0xFF; +} + +/// Gets the system's "core version" (2 on NATIVE_FIRM, 3 on SAFE_FIRM, etc.) +static inline u32 osGetSystemCoreVersion(void) +{ + return OS_KernelConfig->kernel_syscore_ver; +} + +/// Gets the system's memory layout ID (0-5 on Old 3DS, 6-8 on New 3DS) +static inline u32 osGetApplicationMemType(void) +{ + return OS_KernelConfig->app_memtype; +} + +/** + * @brief Gets the size of the specified memory region. + * @param region Memory region to check. + * @return The size of the memory region, in bytes. + */ +static inline u32 osGetMemRegionSize(MemRegion region) +{ + if(region == MEMREGION_ALL) { + return osGetMemRegionSize(MEMREGION_APPLICATION) + osGetMemRegionSize(MEMREGION_SYSTEM) + osGetMemRegionSize(MEMREGION_BASE); + } else { + return OS_KernelConfig->memregion_sz[region-1]; + } +} + +/** + * @brief Gets the number of used bytes within the specified memory region. + * @param region Memory region to check. + * @return The number of used bytes of memory. + */ +static inline u32 osGetMemRegionUsed(MemRegion region) +{ + s64 mem_used; + svcGetSystemInfo(&mem_used, 0, region); + return mem_used; +} + +/** + * @brief Gets the number of free bytes within the specified memory region. + * @param region Memory region to check. + * @return The number of free bytes of memory. + */ +static inline u32 osGetMemRegionFree(MemRegion region) +{ + return osGetMemRegionSize(region) - osGetMemRegionUsed(region); +} + +/** + * @brief Reads the latest reference timepoint published by PTM. + * @return Structure (see \ref osTimeRef_s). + */ +osTimeRef_s osGetTimeRef(void); + +/** + * @brief Gets the current time. + * @return The number of milliseconds since 1st Jan 1900 00:00. + */ +u64 osGetTime(void); + +/** + * @brief Starts a tick counter. + * @param cnt The tick counter. + */ +static inline void osTickCounterStart(TickCounter* cnt) +{ + cnt->reference = svcGetSystemTick(); +} + +/** + * @brief Updates the elapsed time in a tick counter. + * @param cnt The tick counter. + */ +static inline void osTickCounterUpdate(TickCounter* cnt) +{ + u64 now = svcGetSystemTick(); + cnt->elapsed = now - cnt->reference; + cnt->reference = now; +} + +/** + * @brief Reads the elapsed time in a tick counter. + * @param cnt The tick counter. + * @return The number of milliseconds elapsed. + */ +double osTickCounterRead(const TickCounter* cnt); + +/** + * @brief Gets the current Wifi signal strength. + * @return The current Wifi signal strength. + * + * Valid values are 0-3: + * - 0 means the signal strength is terrible or the 3DS is disconnected from + * all networks. + * - 1 means the signal strength is bad. + * - 2 means the signal strength is decent. + * - 3 means the signal strength is good. + * + * Values outside the range of 0-3 should never be returned. + * + * These values correspond with the number of wifi bars displayed by Home Menu. + */ +static inline u8 osGetWifiStrength(void) +{ + return OS_SharedConfig->wifi_strength; +} + +/** + * @brief Gets the state of the 3D slider. + * @return The state of the 3D slider (0.0~1.0) + */ +static inline float osGet3DSliderState(void) +{ + return OS_SharedConfig->slider_3d; +} + +/** + * @brief Checks whether a headset is connected. + * @return true or false. + */ +static inline bool osIsHeadsetConnected(void) +{ + return OS_SharedConfig->headset_connected != 0; +} + +/** + * @brief Configures the New 3DS speedup. + * @param enable Specifies whether to enable or disable the speedup. + */ +void osSetSpeedupEnable(bool enable); + +/** + * @brief Gets the NAND system-version stored in NVer/CVer. + * @param nver_versionbin Output OS_VersionBin structure for the data read from NVer. + * @param cver_versionbin Output OS_VersionBin structure for the data read from CVer. + * @return The result-code. This value can be positive if opening "romfs:/version.bin" fails with stdio, since errno would be returned in that case. In some cases the error can be special negative values as well. + */ +Result osGetSystemVersionData(OS_VersionBin *nver_versionbin, OS_VersionBin *cver_versionbin); + +/** + * @brief This is a wrapper for osGetSystemVersionData. + * @param nver_versionbin Optional output OS_VersionBin structure for the data read from NVer, can be NULL. + * @param cver_versionbin Optional output OS_VersionBin structure for the data read from CVer, can be NULL. + * @param sysverstr Output string where the printed system-version will be written, in the same format displayed by the System Settings title. + * @param sysverstr_maxsize Max size of the above string buffer, *including* NULL-terminator. + * @return See osGetSystemVersionData. + */ +Result osGetSystemVersionDataString(OS_VersionBin *nver_versionbin, OS_VersionBin *cver_versionbin, char *sysverstr, u32 sysverstr_maxsize); diff --git a/libctru/include/3ds/result.h b/libctru/include/3ds/result.h new file mode 100644 index 0000000000..74cbaa9903 --- /dev/null +++ b/libctru/include/3ds/result.h @@ -0,0 +1,191 @@ +/** + * @file result.h + * @brief 3DS result code tools + */ +#pragma once +#include "types.h" + +/// Checks whether a result code indicates success. +#define R_SUCCEEDED(res) ((res)>=0) +/// Checks whether a result code indicates failure. +#define R_FAILED(res) ((res)<0) +/// Returns the level of a result code. +#define R_LEVEL(res) (((res)>>27)&0x1F) +/// Returns the summary of a result code. +#define R_SUMMARY(res) (((res)>>21)&0x3F) +/// Returns the module ID of a result code. +#define R_MODULE(res) (((res)>>10)&0xFF) +/// Returns the description of a result code. +#define R_DESCRIPTION(res) ((res)&0x3FF) + +/// Builds a result code from its constituent components. +#define MAKERESULT(level,summary,module,description) \ + ((((level)&0x1F)<<27) | (((summary)&0x3F)<<21) | (((module)&0xFF)<<10) | ((description)&0x3FF)) + +/// Result code level values. +enum +{ + // >= 0 + RL_SUCCESS = 0, + RL_INFO = 1, + + // < 0 + RL_FATAL = 0x1F, + RL_RESET = RL_FATAL - 1, + RL_REINITIALIZE = RL_FATAL - 2, + RL_USAGE = RL_FATAL - 3, + RL_PERMANENT = RL_FATAL - 4, + RL_TEMPORARY = RL_FATAL - 5, + RL_STATUS = RL_FATAL - 6, +}; + +/// Result code summary values. +enum +{ + RS_SUCCESS = 0, + RS_NOP = 1, + RS_WOULDBLOCK = 2, + RS_OUTOFRESOURCE = 3, + RS_NOTFOUND = 4, + RS_INVALIDSTATE = 5, + RS_NOTSUPPORTED = 6, + RS_INVALIDARG = 7, + RS_WRONGARG = 8, + RS_CANCELED = 9, + RS_STATUSCHANGED = 10, + RS_INTERNAL = 11, + RS_INVALIDRESVAL = 63, +}; + +/// Result code module values. +enum +{ + RM_COMMON = 0, + RM_KERNEL = 1, + RM_UTIL = 2, + RM_FILE_SERVER = 3, + RM_LOADER_SERVER = 4, + RM_TCB = 5, + RM_OS = 6, + RM_DBG = 7, + RM_DMNT = 8, + RM_PDN = 9, + RM_GSP = 10, + RM_I2C = 11, + RM_GPIO = 12, + RM_DD = 13, + RM_CODEC = 14, + RM_SPI = 15, + RM_PXI = 16, + RM_FS = 17, + RM_DI = 18, + RM_HID = 19, + RM_CAM = 20, + RM_PI = 21, + RM_PM = 22, + RM_PM_LOW = 23, + RM_FSI = 24, + RM_SRV = 25, + RM_NDM = 26, + RM_NWM = 27, + RM_SOC = 28, + RM_LDR = 29, + RM_ACC = 30, + RM_ROMFS = 31, + RM_AM = 32, + RM_HIO = 33, + RM_UPDATER = 34, + RM_MIC = 35, + RM_FND = 36, + RM_MP = 37, + RM_MPWL = 38, + RM_AC = 39, + RM_HTTP = 40, + RM_DSP = 41, + RM_SND = 42, + RM_DLP = 43, + RM_HIO_LOW = 44, + RM_CSND = 45, + RM_SSL = 46, + RM_AM_LOW = 47, + RM_NEX = 48, + RM_FRIENDS = 49, + RM_RDT = 50, + RM_APPLET = 51, + RM_NIM = 52, + RM_PTM = 53, + RM_MIDI = 54, + RM_MC = 55, + RM_SWC = 56, + RM_FATFS = 57, + RM_NGC = 58, + RM_CARD = 59, + RM_CARDNOR = 60, + RM_SDMC = 61, + RM_BOSS = 62, + RM_DBM = 63, + RM_CONFIG = 64, + RM_PS = 65, + RM_CEC = 66, + RM_IR = 67, + RM_UDS = 68, + RM_PL = 69, + RM_CUP = 70, + RM_GYROSCOPE = 71, + RM_MCU = 72, + RM_NS = 73, + RM_NEWS = 74, + RM_RO = 75, + RM_GD = 76, + RM_CARD_SPI = 77, + RM_EC = 78, + RM_WEB_BROWSER = 79, + RM_TEST = 80, + RM_ENC = 81, + RM_PIA = 82, + RM_ACT = 83, + RM_VCTL = 84, + RM_OLV = 85, + RM_NEIA = 86, + RM_NPNS = 87, + RM_AVD = 90, + RM_L2B = 91, + RM_MVD = 92, + RM_NFC = 93, + RM_UART = 94, + RM_SPM = 95, + RM_QTM = 96, + RM_NFP = 97, + RM_APPLICATION = 254, + RM_INVALIDRESVAL = 255, +}; + +/// Result code generic description values. +enum +{ + RD_SUCCESS = 0, + RD_INVALID_RESULT_VALUE = 0x3FF, + RD_TIMEOUT = RD_INVALID_RESULT_VALUE - 1, + RD_OUT_OF_RANGE = RD_INVALID_RESULT_VALUE - 2, + RD_ALREADY_EXISTS = RD_INVALID_RESULT_VALUE - 3, + RD_CANCEL_REQUESTED = RD_INVALID_RESULT_VALUE - 4, + RD_NOT_FOUND = RD_INVALID_RESULT_VALUE - 5, + RD_ALREADY_INITIALIZED = RD_INVALID_RESULT_VALUE - 6, + RD_NOT_INITIALIZED = RD_INVALID_RESULT_VALUE - 7, + RD_INVALID_HANDLE = RD_INVALID_RESULT_VALUE - 8, + RD_INVALID_POINTER = RD_INVALID_RESULT_VALUE - 9, + RD_INVALID_ADDRESS = RD_INVALID_RESULT_VALUE - 10, + RD_NOT_IMPLEMENTED = RD_INVALID_RESULT_VALUE - 11, + RD_OUT_OF_MEMORY = RD_INVALID_RESULT_VALUE - 12, + RD_MISALIGNED_SIZE = RD_INVALID_RESULT_VALUE - 13, + RD_MISALIGNED_ADDRESS = RD_INVALID_RESULT_VALUE - 14, + RD_BUSY = RD_INVALID_RESULT_VALUE - 15, + RD_NO_DATA = RD_INVALID_RESULT_VALUE - 16, + RD_INVALID_COMBINATION = RD_INVALID_RESULT_VALUE - 17, + RD_INVALID_ENUM_VALUE = RD_INVALID_RESULT_VALUE - 18, + RD_INVALID_SIZE = RD_INVALID_RESULT_VALUE - 19, + RD_ALREADY_DONE = RD_INVALID_RESULT_VALUE - 20, + RD_NOT_AUTHORIZED = RD_INVALID_RESULT_VALUE - 21, + RD_TOO_LARGE = RD_INVALID_RESULT_VALUE - 22, + RD_INVALID_SELECTION = RD_INVALID_RESULT_VALUE - 23, +}; diff --git a/libctru/include/3ds/romfs.h b/libctru/include/3ds/romfs.h new file mode 100644 index 0000000000..5892d29043 --- /dev/null +++ b/libctru/include/3ds/romfs.h @@ -0,0 +1,93 @@ +/** + * @file romfs.h + * @brief RomFS driver. + */ +#pragma once + +#include <3ds/types.h> +#include <3ds/services/fs.h> + +/// RomFS header. +typedef struct +{ + u32 headerSize; ///< Size of the header. + u32 dirHashTableOff; ///< Offset of the directory hash table. + u32 dirHashTableSize; ///< Size of the directory hash table. + u32 dirTableOff; ///< Offset of the directory table. + u32 dirTableSize; ///< Size of the directory table. + u32 fileHashTableOff; ///< Offset of the file hash table. + u32 fileHashTableSize; ///< Size of the file hash table. + u32 fileTableOff; ///< Offset of the file table. + u32 fileTableSize; ///< Size of the file table. + u32 fileDataOff; ///< Offset of the file data. +} romfs_header; + +/// RomFS directory. +typedef struct +{ + u32 parent; ///< Offset of the parent directory. + u32 sibling; ///< Offset of the next sibling directory. + u32 childDir; ///< Offset of the first child directory. + u32 childFile; ///< Offset of the first file. + u32 nextHash; ///< Directory hash table pointer. + u32 nameLen; ///< Name length. + u16 name[]; ///< Name. (UTF-16) +} romfs_dir; + +/// RomFS file. +typedef struct +{ + u32 parent; ///< Offset of the parent directory. + u32 sibling; ///< Offset of the next sibling file. + u64 dataOff; ///< Offset of the file's data. + u64 dataSize; ///< Length of the file's data. + u32 nextHash; ///< File hash table pointer. + u32 nameLen; ///< Name length. + u16 name[]; ///< Name. (UTF-16) +} romfs_file; + +/** + * @brief Mounts the Application's RomFS. + * @param name Device mount name. + * @remark This function is intended to be used to access one's own RomFS. + * If the application is running as 3DSX, it mounts the embedded RomFS section inside the 3DSX. + * If on the other hand it's an NCCH, it behaves identically to \ref romfsMountFromCurrentProcess. + */ +Result romfsMountSelf(const char *name); + +/** + * @brief Mounts RomFS from an open file. + * @param fd FSFILE handle of the RomFS image. + * @param offset Offset of the RomFS within the file. + * @param name Device mount name. + */ +Result romfsMountFromFile(Handle fd, u32 offset, const char *name); + +/** + * @brief Mounts RomFS using the current process host program RomFS. + * @param name Device mount name. + */ +Result romfsMountFromCurrentProcess(const char *name); + +/** + * @brief Mounts RomFS from the specified title. + * @param tid Title ID + * @param mediatype Mediatype + * @param name Device mount name. + */ +Result romfsMountFromTitle(u64 tid, FS_MediaType mediatype, const char* name); + +/// Unmounts the RomFS device. +Result romfsUnmount(const char *name); + +/// Wrapper for \ref romfsMountSelf with the default "romfs" device name. +static inline Result romfsInit(void) +{ + return romfsMountSelf("romfs"); +} + +/// Wrapper for \ref romfsUnmount with the default "romfs" device name. +static inline Result romfsExit(void) +{ + return romfsUnmount("romfs"); +} diff --git a/libctru/include/3ds/services/ac.h b/libctru/include/3ds/services/ac.h new file mode 100644 index 0000000000..bc1ad79c87 --- /dev/null +++ b/libctru/include/3ds/services/ac.h @@ -0,0 +1,130 @@ +/** + * @file ac.h + * @brief AC service. + */ +#pragma once + +/// Wifi security modes. +typedef enum { + AC_OPEN = 0, ///< Open authentication. + AC_WEP_40BIT = 1, ///< WEP 40-bit authentication. + AC_WEP_104BIT = 2, ///< WEP 104-bit authentication. + AC_WEP_128BIT = 3, ///< WEP 128-bit authentication. + AC_WPA_TKIP = 4, ///< WPA TKIP authentication. + AC_WPA2_TKIP = 5, ///< WPA2 TKIP authentication. + AC_WPA_AES = 6, ///< WPA AES authentication. + AC_WPA2_AES = 7, ///< WPA2 AES authentication. +} acSecurityMode; + +/// Struct to contain the data for connecting to a Wifi network from a stored slot. +typedef struct { + u8 reserved[0x200]; +} acuConfig; + +/// Initializes AC. +Result acInit(void); + +/// Exits AC. +void acExit(void); + +/// Waits for the system to connect to the internet. +Result acWaitInternetConnection(void); + +/** + * @brief Gets the connected Wifi status. + * @param out Pointer to output the connected Wifi status to. (0 = not connected, 1 = O3DS Internet, 2 = N3DS Internet) + */ +Result ACU_GetWifiStatus(u32 *out); + +/** + * @brief Gets the connected Wifi status. + * @param out Pointer to output the connected Wifi status to. (1 = not connected, 3 = connected) + */ +Result ACU_GetStatus(u32 *out); + +/** + * @brief Gets the connected Wifi security mode. + * @param mode Pointer to output the connected Wifi security mode to. (0 = Open Authentication, 1 = WEP 40-bit, 2 = WEP 104-bit, 3 = WEP 128-bit, 4 = WPA TKIP, 5 = WPA2 TKIP, 6 = WPA AES, 7 = WPA2 AES) + */ +Result ACU_GetSecurityMode(acSecurityMode *mode); + +/** + * @brief Gets the connected Wifi SSID. + * @param SSID Pointer to output the connected Wifi SSID to. + */ +Result ACU_GetSSID(char *SSID); + +/** + * @brief Gets the connected Wifi SSID length. + * @param out Pointer to output the connected Wifi SSID length to. + */ +Result ACU_GetSSIDLength(u32 *out); + +/** + * @brief Determines whether proxy is enabled for the connected network. + * @param enable Pointer to output the proxy status to. + */ +Result ACU_GetProxyEnable(bool *enable); + +/** + * @brief Gets the connected network's proxy port. + * @param out Pointer to output the proxy port to. + */ +Result ACU_GetProxyPort(u32 *out); + +/** + * @brief Gets the connected network's proxy username. + * @param username Pointer to output the proxy username to. (The size must be at least 0x20-bytes) + */ +Result ACU_GetProxyUserName(char *username); + +/** + * @brief Gets the connected network's proxy password. + * @param password Pointer to output the proxy password to. (The size must be at least 0x20-bytes) + */ +Result ACU_GetProxyPassword(char *password); + +/** + * @brief Gets the last error to occur during a connection. + * @param errorCode Pointer to output the error code to. + */ +Result ACU_GetLastErrorCode(u32* errorCode); + +/** + * @brief Gets the last detailed error to occur during a connection. + * @param errorCode Pointer to output the error code to. + */ +Result ACU_GetLastDetailErrorCode(u32* errorCode); + +/** + * @brief Prepares a buffer to hold the configuration data to start a connection. + * @param config Pointer to an acuConfig struct to contain the data. + */ +Result ACU_CreateDefaultConfig(acuConfig* config); + +/** + * @brief Sets something that makes the connection reliable. + * @param config Pointer to an acuConfig struct used with ACU_CreateDefaultConfig previously. + * @param area Always 2 ? + */ +Result ACU_SetNetworkArea(acuConfig* config, u8 area); + +/** + * @brief Sets the slot to use when connecting. + * @param config Pointer to an acuConfig struct used with ACU_CreateDefaultConfig previously. + * @param type Allowed slots flag. BIT(0) for slot 1, BIT(1) for slot 2, BIT(2) for slot 3. + */ +Result ACU_SetAllowApType(acuConfig* config, u8 type); + +/** + * @brief Sets something that makes the connection reliable. + * @param config Pointer to an acuConfig struct used with ACU_CreateDefaultConfig previously. + */ +Result ACU_SetRequestEulaVersion(acuConfig* config); + +/** + * @brief Starts the connection procedure. + * @param config Pointer to an acuConfig struct used with ACU_CreateDefaultConfig previously. + * @param connectionHandle Handle created with svcCreateEvent to wait on until the connection succeeds or fails. + */ +Result ACU_ConnectAsync(const acuConfig* config, Handle connectionHandle); diff --git a/libctru/include/3ds/services/am.h b/libctru/include/3ds/services/am.h new file mode 100644 index 0000000000..1d83942b45 --- /dev/null +++ b/libctru/include/3ds/services/am.h @@ -0,0 +1,538 @@ +/** + * @file am.h + * @brief AM (Application Manager) service. + */ +#pragma once + +#include <3ds/services/fs.h> + +/// Contains basic information about a title. +typedef struct +{ + u64 titleID; ///< The title's ID. + u64 size; ///< The title's installed size. + u16 version; ///< The title's version. + u8 unk[6]; ///< Unknown title data. +} AM_TitleEntry; + +/// Pending title status mask values. +enum +{ + AM_STATUS_MASK_INSTALLING = BIT(0), ///< Titles currently installing. + AM_STATUS_MASK_AWAITING_FINALIZATION = BIT(1) ///< Titles awaiting finalization. +}; + +/// Pending title status values. +typedef enum +{ + AM_STATUS_ABORTED = 0x0002, ///< Install aborted. + AM_STATUS_SAVED = 0x0003, ///< Title saved, but not installed. + AM_STATUS_INSTALL_IN_PROGRESS = 0x0802, ///< Install in progress. + AM_STATUS_AWAITING_FINALIZATION = 0x0803 ///< Awaiting finalization. +} AM_InstallStatus; + +// Contains basic information about a pending title. +typedef struct +{ + u64 titleId; ///< Title ID + u16 version; ///< Version + u16 status; ///< @ref AM_InstallStatus + u32 titleType; ///< Title Type + u8 unk[0x8]; ///< Unknown +} AM_PendingTitleEntry; + +/// Pending title deletion flags. +enum +{ + AM_DELETE_PENDING_NON_SYSTEM = BIT(0), ///< Non-system titles. + AM_DELETE_PENDING_SYSTEM = BIT(1) ///< System titles. +}; + +/// Information about the TWL NAND partition. +typedef struct { + u64 capacity; ///< Total capacity. + u64 freeSpace; ///< Total free space. + u64 titlesCapacity; ///< Capacity for titles. + u64 titlesFreeSpace; ///< Free space for titles. +} AM_TWLPartitionInfo; + +/// Contains information about a title's content. +typedef struct { + u16 index; ///< Index of the content in the title. + u16 type; ///< ? + u32 contentId; ///< ID of the content in the title. + u64 size; ///< Size of the content in the title. + u8 flags; ///< @ref AM_ContentInfoFlags + u8 padding[7]; ///< Padding +} AM_ContentInfo; + +/// Title ContentInfo flags. +typedef enum +{ + AM_CONTENT_DOWNLOADED = BIT(0), ///< ? + AM_CONTENT_OWNED = BIT(1) ///< ? +} AM_ContentInfoFlags; + +/// Initializes AM. This doesn't initialize with "am:app", see amAppInit(). +Result amInit(void); + +/// Initializes AM with a service which has access to the amapp-commands. This should only be used when using the amapp commands, not non-amapp AM commands. +Result amAppInit(void); + +/// Exits AM. +void amExit(void); + +/// Gets the current AM session handle. +Handle *amGetSessionHandle(void); + +/** + * @brief Gets the number of titles for a given media type. + * @param mediatype Media type to get titles from. + * @param[out] count Pointer to write the title count to. + */ +Result AM_GetTitleCount(FS_MediaType mediatype, u32 *count); + +/** + * @brief Gets a list of title IDs present in a mediatype. + * @param[out] titlesRead Pointer to output the number of read titles to. + * @param mediatype Media type to get titles from. + * @param titleCount Number of title IDs to get. + * @param titleIds Buffer to output the retrieved title IDs to. + */ +Result AM_GetTitleList(u32* titlesRead, FS_MediaType mediatype, u32 titleCount, u64 *titleIds); + +/** + * @brief Gets a list of details about installed titles. + * @param mediatype Media type to get titles from. + * @param titleCount Number of titles to list. + * @param titleIds List of title IDs to retrieve details for. + * @param titleInfo Buffer to write AM_TitleEntry's to. + */ +Result AM_GetTitleInfo(FS_MediaType mediatype, u32 titleCount, u64 *titleIds, AM_TitleEntry *titleInfo); + +/** + * @brief Gets the number of tickets installed on the system. + * @param[out] count Pointer to output the ticket count to. + */ +Result AM_GetTicketCount(u32 *count); + +/** + * @brief Gets a list of tickets installed on the system. + * @param[out] ticketsRead Pointer to output the number of read tickets to. + * @param ticketCount Number of tickets to read. + * @param skip Number of tickets to skip. + * @param ticketIds Buffer to output the retrieved ticket IDs to. + */ +Result AM_GetTicketList(u32 *ticketsRead, u32 ticketCount, u32 skip, u64 *ticketIds); + +/** + * @brief Gets the number of pending titles on this system. + * @param[out] count Pointer to output the pending title count to. + * @param mediatype Media type of pending titles to count. + * @param statusMask Bit mask of status values to include. + */ +Result AM_GetPendingTitleCount(u32 *count, FS_MediaType mediatype, u32 statusMask); + +/** + * @brief Gets a list of pending titles on this system. + * @param[out] titlesRead Pointer to output the number of read pending titles to. + * @param titleCount Number of pending titles to read. + * @param mediatype Media type of pending titles to list. + * @param statusMask Bit mask of status values to include. + * @param titleIds Buffer to output the retrieved pending title IDs to. + */ +Result AM_GetPendingTitleList(u32 *titlesRead, u32 titleCount, FS_MediaType mediatype, u32 statusMask, u64 *titleIds); + +/** + * @brief Gets information about pending titles on this system. + * @param titleCount Number of pending titles to read. + * @param mediatype Media type of pending titles to get information on. + * @param titleIds IDs of the titles to get information about. + * @param titleInfo Buffer to output the retrieved pending title info to. + */ +Result AM_GetPendingTitleInfo(u32 titleCount, FS_MediaType mediatype, u64 *titleIds, AM_PendingTitleEntry *titleInfo); + +/** + * @brief Gets a 32-bit device-specific ID. + * @param deviceID Pointer to write the device ID to. + */ +Result AM_GetDeviceId(u32 *deviceID); + +/** + * @brief Exports DSiWare to the specified filepath. + * @param titleID TWL titleID. + * @param operation DSiWare operation type. + * @param workbuf Work buffer. + * @param workbuf_size Work buffer size, must be >=0x20000. + * @param filepath UTF-8 filepath(converted to UTF-16 internally). + */ +Result AM_ExportTwlBackup(u64 titleID, u8 operation, void* workbuf, u32 workbuf_size, const char *filepath); + +/** + * @brief Imports DSiWare from the specified file. + * @param filehandle FSUSER file handle. + * @param operation DSiWare operation type. + * @param buffer Work buffer. + * @param size Buffer size, must be >=0x20000. + */ +Result AM_ImportTwlBackup(Handle filehandle, u8 operation, void* buffer, u32 size); + +/** + * @brief Reads info from the specified DSiWare export file. This can only be used with DSiWare exported with certain operation value(s). + * @param filehandle FSUSER file handle. + * @param outinfo Output info buffer. + * @param outinfo_size Output info buffer size. + * @param workbuf Work buffer. + * @param workbuf_size Work buffer size. + * @param banner Output banner buffer. + * @param banner_size Output banner buffer size. + */ +Result AM_ReadTwlBackupInfo(Handle filehandle, void* outinfo, u32 outinfo_size, void* workbuf, u32 workbuf_size, void* banner, u32 banner_size); + +/** + * @brief Retrieves information about the NAND TWL partition. + * @param[out] info Pointer to output the TWL partition info to. + */ +Result AM_GetTWLPartitionInfo(AM_TWLPartitionInfo *info); + +/** + * @brief Initializes the CIA install process, returning a handle to write CIA data to. + * @param mediatype Media type to install the CIA to. + * @param[out] ciaHandle Pointer to write the CIA handle to. + */ +Result AM_StartCiaInstall(FS_MediaType mediatype, Handle *ciaHandle); + +/** + * @brief Initializes the CIA install process for Download Play CIAs, returning a handle to write CIA data to. + * @param[out] ciaHandle Pointer to write the CIA handle to. + */ +Result AM_StartDlpChildCiaInstall(Handle *ciaHandle); + +/** + * @brief Aborts the CIA install process. + * @param ciaHandle CIA handle to cancel. + */ +Result AM_CancelCIAInstall(Handle ciaHandle); + +/** + * @brief Finalizes the CIA install process. + * @param ciaHandle CIA handle to finalize. + */ +Result AM_FinishCiaInstall(Handle ciaHandle); + +/** + * @brief Finalizes the CIA install process without committing the title to title.db or tmp*.db. + * @param ciaHandle CIA handle to finalize. + */ +Result AM_FinishCiaInstallWithoutCommit(Handle ciaHandle); + +/** + * @brief Commits installed CIAs. + * @param mediaType Location of the titles to finalize. + * @param titleCount Number of titles to finalize. + * @param temp Whether the titles being finalized are in the temporary database. + * @param titleIds Title IDs to finalize. + */ +Result AM_CommitImportPrograms(FS_MediaType mediaType, u32 titleCount, bool temp, const u64* titleIds); + +/** + * @brief Deletes a title. + * @param mediatype Media type to delete from. + * @param titleID ID of the title to delete. + */ +Result AM_DeleteTitle(FS_MediaType mediatype, u64 titleID); + +/** + * @brief Deletes a title, provided that it is not a system title. + * @param mediatype Media type to delete from. + * @param titleID ID of the title to delete. + */ +Result AM_DeleteAppTitle(FS_MediaType mediatype, u64 titleID); + +/** + * @brief Deletes a ticket. + * @param titleID ID of the ticket to delete. + */ +Result AM_DeleteTicket(u64 ticketId); + +/** + * @brief Deletes a pending title. + * @param mediatype Media type to delete from. + * @param titleId ID of the pending title to delete. + */ +Result AM_DeletePendingTitle(FS_MediaType mediatype, u64 titleId); + +/** + * @brief Deletes pending titles. + * @param mediatype Media type to delete from. + * @param flags Flags used to select pending titles. + */ +Result AM_DeletePendingTitles(FS_MediaType mediatype, u32 flags); + +/** + * @brief Deletes all pending titles. + * @param mediatype Media type to delete from. + */ +Result AM_DeleteAllPendingTitles(FS_MediaType mediatype); + +/// Installs the current NATIVE_FIRM title to NAND (firm0:/ & firm1:/) +Result AM_InstallNativeFirm(void); + +/** + * @brief Installs a NATIVE_FIRM title to NAND. Accepts 0004013800000002 or 0004013820000002 (N3DS). + * @param titleID Title ID of the NATIVE_FIRM to install. + */ +Result AM_InstallFirm(u64 titleID); + +/** + * @brief Gets the product code of a title. + * @param mediatype Media type of the title. + * @param titleID ID of the title. + * @param[out] productCode Pointer to output the product code to. (length = 16) + */ +Result AM_GetTitleProductCode(FS_MediaType mediatype, u64 titleId, char *productCode); + +/** + * @brief Gets the ext data ID of a title. + * @param[out] extDataId Pointer to output the ext data ID to. + * @param mediatype Media type of the title. + * @param titleID ID of the title. + */ +Result AM_GetTitleExtDataId(u64 *extDataId, FS_MediaType mediatype, u64 titleId); + +/** + * @brief Gets an AM_TitleEntry instance for a CIA file. + * @param mediatype Media type that this CIA would be installed to. + * @param[out] titleEntry Pointer to write the AM_TitleEntry instance to. + * @param fileHandle Handle of the CIA file. + */ +Result AM_GetCiaFileInfo(FS_MediaType mediatype, AM_TitleEntry *titleEntry, Handle fileHandle); + +/** + * @brief Gets the SMDH icon data of a CIA file. + * @param icon Buffer to store the icon data in. Must be of size 0x36C0 bytes. + * @param fileHandle Handle of the CIA file. + */ +Result AM_GetCiaIcon(void *icon, Handle fileHandle); + +/** + * @brief Gets the title ID dependency list of a CIA file. + * @param dependencies Buffer to store dependency title IDs in. Must be of size 0x300 bytes. + * @param fileHandle Handle of the CIA file. + */ +Result AM_GetCiaDependencies(u64 *dependencies, Handle fileHandle); + +/** + * @brief Gets the meta section offset of a CIA file. + * @param[out] metaOffset Pointer to output the meta section offset to. + * @param fileHandle Handle of the CIA file. + */ +Result AM_GetCiaMetaOffset(u64 *metaOffset, Handle fileHandle); + +/** + * @brief Gets the core version of a CIA file. + * @param[out] coreVersion Pointer to output the core version to. + * @param fileHandle Handle of the CIA file. + */ +Result AM_GetCiaCoreVersion(u32 *coreVersion, Handle fileHandle); + +/** + * @brief Gets the free space, in bytes, required to install a CIA file. + * @param[out] requiredSpace Pointer to output the required free space to. + * @param mediaType Media type to check free space needed to install to. + * @param fileHandle Handle of the CIA file. + */ +Result AM_GetCiaRequiredSpace(u64 *requiredSpace, FS_MediaType mediaType, Handle fileHandle); + +/** + * @brief Gets the full meta section of a CIA file. + * @param meta Buffer to store the meta section in. + * @param size Size of the buffer. Must be greater than or equal to the actual section data's size. + * @param fileHandle Handle of the CIA file. + */ +Result AM_GetCiaMetaSection(void *meta, u32 size, Handle fileHandle); + +/** + * @brief Initializes the external (SD) title database. + * @param overwrite Overwrites the database if it already exists. + */ +Result AM_InitializeExternalTitleDatabase(bool overwrite); + +/** + * @brief Queries whether the external title database is available. + * @param[out] available Pointer to output the availability status to. + */ +Result AM_QueryAvailableExternalTitleDatabase(bool* available); + +/** + * @brief Begins installing a ticket. + * @param[out] ticketHandle Pointer to output a handle to write ticket data to. + */ +Result AM_InstallTicketBegin(Handle *ticketHandle); + +/** + * @brief Aborts installing a ticket. + * @param ticketHandle Handle of the installation to abort. + */ +Result AM_InstallTicketAbort(Handle ticketHandle); + +/** + * @brief Finishes installing a ticket. + * @param ticketHandle Handle of the installation to finalize. + */ +Result AM_InstallTicketFinish(Handle ticketHandle); + +/** + * @brief Begins installing a title. + * @param mediaType Destination to install to. + * @param titleId ID of the title to install. + * @param unk Unknown. (usually false) + */ +Result AM_InstallTitleBegin(FS_MediaType mediaType, u64 titleId, bool unk); + +/// Stops installing a title, generally to be resumed later. +Result AM_InstallTitleStop(void); + +/** + * @brief Resumes installing a title. + * @param mediaType Destination to install to. + * @param titleId ID of the title to install. + */ +Result AM_InstallTitleResume(FS_MediaType mediaType, u64 titleId); + +/// Aborts installing a title. +Result AM_InstallTitleAbort(void); + +/// Finishes installing a title. +Result AM_InstallTitleFinish(void); + +/** + * @brief Commits installed titles. + * @param mediaType Location of the titles to finalize. + * @param titleCount Number of titles to finalize. + * @param temp Whether the titles being finalized are in the temporary database. + * @param titleIds Title IDs to finalize. + */ +Result AM_CommitImportTitles(FS_MediaType mediaType, u32 titleCount, bool temp, const u64* titleIds); + +/** + * @brief Begins installing a TMD. + * @param[out] tmdHandle Pointer to output a handle to write TMD data to. + */ +Result AM_InstallTmdBegin(Handle *tmdHandle); + +/** + * @brief Aborts installing a TMD. + * @param tmdHandle Handle of the installation to abort. + */ +Result AM_InstallTmdAbort(Handle tmdHandle); + +/** + * @brief Finishes installing a TMD. + * @param tmdHandle Handle of the installation to finalize. + * @param unk Unknown. (usually true) + */ +Result AM_InstallTmdFinish(Handle tmdHandle, bool unk); + +/** + * @brief Prepares to import title contents. + * @param contentCount Number of contents to be imported. + * @param contentIndices Indices of the contents to be imported. + */ +Result AM_CreateImportContentContexts(u32 contentCount, u16* contentIndices); + +/** + * @brief Begins installing title content. + * @param[out] contentHandle Pointer to output a handle to write content data to. + * @param index Index of the content to install. + */ +Result AM_InstallContentBegin(Handle *contentHandle, u16 index); + +/** + * @brief Stops installing title content, generally to be resumed later. + * @param contentHandle Handle of the installation to abort. + */ +Result AM_InstallContentStop(Handle contentHandle); + +/** + * @brief Resumes installing title content. + * @param[out] contentHandle Pointer to output a handle to write content data to. + * @param[out] resumeOffset Pointer to write the offset to resume content installation at to. + * @param index Index of the content to install. + */ +Result AM_InstallContentResume(Handle *contentHandle, u64* resumeOffset, u16 index); + +/** + * @brief Cancels installing title content. + * @param contentHandle Handle of the installation to finalize. + */ +Result AM_InstallContentCancel(Handle contentHandle); + +/** + * @brief Finishes installing title content. + * @param contentHandle Handle of the installation to finalize. + */ +Result AM_InstallContentFinish(Handle contentHandle); + +/** + * @brief Imports up to four certificates into the ticket certificate chain. + * @param cert1Size Size of the first certificate. + * @param cert1 Data of the first certificate. + * @param cert2Size Size of the second certificate. + * @param cert2 Data of the second certificate. + * @param cert3Size Size of the third certificate. + * @param cert3 Data of the third certificate. + * @param cert4Size Size of the fourth certificate. + * @param cert4 Data of the fourth certificate. + */ +Result AM_ImportCertificates(u32 cert1Size, void* cert1, u32 cert2Size, void* cert2, u32 cert3Size, void* cert3, u32 cert4Size, void* cert4); + +/** + * @brief Imports a certificate into the ticket certificate chain. + * @param certSize Size of the certificate. + * @param cert Data of the certificate. + */ +Result AM_ImportCertificate(u32 certSize, void* cert); + +/** + * @brief Commits installed titles, and updates FIRM if necessary. + * @param mediaType Location of the titles to finalize. + * @param titleCount Number of titles to finalize. + * @param temp Whether the titles being finalized are in the temporary database. + * @param titleIds Title IDs to finalize. + */ +Result AM_CommitImportTitlesAndUpdateFirmwareAuto(FS_MediaType mediaType, u32 titleCount, bool temp, u64* titleIds); + +/// Resets play count of all installed demos by deleting their launch info. +Result AM_DeleteAllDemoLaunchInfos(void); + +/// Deletes temporary titles. +Result AM_DeleteAllTemporaryTitles(void); + +/** + * @brief Deletes all expired titles. + * @param mediatype Media type to delete from. + */ +Result AM_DeleteAllExpiredTitles(FS_MediaType mediatype); + +/// Deletes all TWL titles. +Result AM_DeleteAllTwlTitles(void); + +/** + * @brief Gets the number of content index installed under the specified DLC title. + * @param[out] count Pointer to output the number of content indices to. + * @param mediatype Media type of the title. + * @param titleID Title ID to retrieve the count for (high-id is 0x0004008C). + */ +Result AMAPP_GetDLCContentInfoCount(u32* count, FS_MediaType mediatype, u64 titleID); + +/** + * @brief Gets content infos installed under the specified DLC title. + * @param[out] contentInfoRead Pointer to output the number of content infos read to. + * @param mediatype Media type of the title. + * @param titleID Title ID to retrieve the content infos for (high-id is 0x0004008C). + * @param contentInfoCount Number of content infos to retrieve. + * @param offset Offset from the first content index the count starts at. + * @param[out] contentInfos Pointer to output the content infos read to. + */ +Result AMAPP_ListDLCContentInfos(u32* contentInfoRead, FS_MediaType mediatype, u64 titleID, u32 contentInfoCount, u32 offset, AM_ContentInfo* contentInfos); diff --git a/libctru/include/3ds/services/ampxi.h b/libctru/include/3ds/services/ampxi.h new file mode 100644 index 0000000000..952ec589b8 --- /dev/null +++ b/libctru/include/3ds/services/ampxi.h @@ -0,0 +1,35 @@ +/** + * @file ampxi.h + * @brief AMPXI service. This is normally not accessible to userland apps. https://3dbrew.org/wiki/Application_Manager_Services_PXI + */ +#pragma once + +/** + * @brief Initializes AMPXI. + * @param servhandle Optional service session handle to use for AMPXI, if zero srvGetServiceHandle() will be used. + */ +Result ampxiInit(Handle servhandle); + +/// Exits AMPXI. +void ampxiExit(void); + +/** + * @brief Writes a TWL save-file to NAND. https://www.3dbrew.org/wiki/AMPXI:WriteTWLSavedata + * @param titleid ID of the TWL title. + * @param buffer Savedata buffer ptr. + * @param size Size of the savedata buffer. + * @param image_filepos Filepos to use for writing the data to the NAND savedata file. + * @param section_type https://www.3dbrew.org/wiki/AMPXI:WriteTWLSavedata + * @param operation https://3dbrew.org/wiki/AM:ImportDSiWare + */ +Result AMPXI_WriteTWLSavedata(u64 titleid, u8 *buffer, u32 size, u32 image_filepos, u8 section_type, u8 operation); + +/** + * @brief Finalizes title installation. https://3dbrew.org/wiki/AMPXI:InstallTitlesFinish + * @param mediaType Mediatype of the titles to finalize. + * @param db Which title database to use. + * @param size Size of the savedata buffer. + * @param titlecount Total titles. + * @param tidlist List of titleIDs. + */ +Result AMPXI_InstallTitlesFinish(FS_MediaType mediaType, u8 db, u32 titlecount, u64 *tidlist); diff --git a/libctru/include/3ds/services/apt.h b/libctru/include/3ds/services/apt.h new file mode 100644 index 0000000000..6f9f6d6f77 --- /dev/null +++ b/libctru/include/3ds/services/apt.h @@ -0,0 +1,568 @@ +/** + * @file apt.h + * @brief APT (Applet) service. + */ +#pragma once + +/** + * @brief NS Application IDs. + * + * Retrieved from http://3dbrew.org/wiki/NS_and_APT_Services#AppIDs + */ +typedef enum { + APPID_NONE = 0, + APPID_HOMEMENU = 0x101, ///< Home Menu + APPID_CAMERA = 0x110, ///< Camera applet + APPID_FRIENDS_LIST = 0x112, ///< Friends List applet + APPID_GAME_NOTES = 0x113, ///< Game Notes applet + APPID_WEB = 0x114, ///< Internet Browser + APPID_INSTRUCTION_MANUAL = 0x115, ///< Instruction Manual applet + APPID_NOTIFICATIONS = 0x116, ///< Notifications applet + APPID_MIIVERSE = 0x117, ///< Miiverse applet (olv) + APPID_MIIVERSE_POSTING = 0x118, ///< Miiverse posting applet (solv3) + APPID_AMIIBO_SETTINGS = 0x119, ///< Amiibo settings applet (cabinet) + APPID_APPLICATION = 0x300, ///< Application + APPID_ESHOP = 0x301, ///< eShop (tiger) + APPID_SOFTWARE_KEYBOARD = 0x401, ///< Software Keyboard + APPID_APPLETED = 0x402, ///< appletEd + APPID_PNOTE_AP = 0x404, ///< PNOTE_AP + APPID_SNOTE_AP = 0x405, ///< SNOTE_AP + APPID_ERROR = 0x406, ///< error + APPID_MINT = 0x407, ///< mint + APPID_EXTRAPAD = 0x408, ///< extrapad + APPID_MEMOLIB = 0x409, ///< memolib +} NS_APPID; + +/// APT applet position. +typedef enum { + APTPOS_NONE = -1, ///< No position specified. + APTPOS_APP = 0, ///< Application. + APTPOS_APPLIB = 1, ///< Application library (?). + APTPOS_SYS = 2, ///< System applet. + APTPOS_SYSLIB = 3, ///< System library (?). + APTPOS_RESIDENT = 4, ///< Resident applet. +} APT_AppletPos; + +typedef u8 APT_AppletAttr; + +struct PtmWakeEvents; + +/// Create an APT_AppletAttr bitfield from its components. +static inline APT_AppletAttr aptMakeAppletAttr(APT_AppletPos pos, bool manualGpuRights, bool manualDspRights) +{ + return (pos&7) | (manualGpuRights ? BIT(3) : 0) | (manualDspRights ? BIT(4) : 0); +} + +/// APT query reply. +typedef enum { + APTREPLY_REJECT = 0, + APTREPLY_ACCEPT = 1, + APTREPLY_LATER = 2, +} APT_QueryReply; + +/// APT signals. +typedef enum { + APTSIGNAL_NONE = 0, ///< No signal received. + APTSIGNAL_HOMEBUTTON = 1, ///< HOME button pressed. + APTSIGNAL_HOMEBUTTON2 = 2, ///< HOME button pressed (again?). + APTSIGNAL_SLEEP_QUERY = 3, ///< Prepare to enter sleep mode. + APTSIGNAL_SLEEP_CANCEL = 4, ///< Triggered when ptm:s GetShellStatus() returns 5. + APTSIGNAL_SLEEP_ENTER = 5, ///< Enter sleep mode. + APTSIGNAL_SLEEP_WAKEUP = 6, ///< Wake from sleep mode. + APTSIGNAL_SHUTDOWN = 7, ///< Shutdown. + APTSIGNAL_POWERBUTTON = 8, ///< POWER button pressed. + APTSIGNAL_POWERBUTTON2 = 9, ///< POWER button cleared (?). + APTSIGNAL_TRY_SLEEP = 10, ///< System sleeping (?). + APTSIGNAL_ORDERTOCLOSE = 11, ///< Order to close (such as when an error happens?). +} APT_Signal; + +/// APT commands. +typedef enum { + APTCMD_NONE = 0, ///< No command received. + APTCMD_WAKEUP = 1, ///< Applet should wake up. + APTCMD_REQUEST = 2, ///< Source applet sent us a parameter. + APTCMD_RESPONSE = 3, ///< Target applet replied to our parameter. + APTCMD_EXIT = 4, ///< Exit (??) + APTCMD_MESSAGE = 5, ///< Message (??) + APTCMD_HOMEBUTTON_ONCE = 6, ///< HOME button pressed once. + APTCMD_HOMEBUTTON_TWICE = 7, ///< HOME button pressed twice (double-pressed). + APTCMD_DSP_SLEEP = 8, ///< DSP should sleep (manual DSP rights related?). + APTCMD_DSP_WAKEUP = 9, ///< DSP should wake up (manual DSP rights related?). + APTCMD_WAKEUP_EXIT = 10, ///< Applet wakes up due to a different applet exiting. + APTCMD_WAKEUP_PAUSE = 11, ///< Applet wakes up after being paused through HOME menu. + APTCMD_WAKEUP_CANCEL = 12, ///< Applet wakes up due to being cancelled. + APTCMD_WAKEUP_CANCELALL = 13, ///< Applet wakes up due to all applets being cancelled. + APTCMD_WAKEUP_POWERBUTTON = 14, ///< Applet wakes up due to POWER button being pressed (?). + APTCMD_WAKEUP_JUMPTOHOME = 15, ///< Applet wakes up and is instructed to jump to HOME menu (?). + APTCMD_SYSAPPLET_REQUEST = 16, ///< Request for sysapplet (?). + APTCMD_WAKEUP_LAUNCHAPP = 17, ///< Applet wakes up and is instructed to launch another applet (?). +} APT_Command; + +/// APT capture buffer information. +typedef struct +{ + u32 size; + u32 is3D; + struct + { + u32 leftOffset; + u32 rightOffset; + u32 format; + } top, bottom; +} aptCaptureBufInfo; + +/// APT hook types. +typedef enum { + APTHOOK_ONSUSPEND = 0, ///< App suspended. + APTHOOK_ONRESTORE, ///< App restored. + APTHOOK_ONSLEEP, ///< App sleeping. + APTHOOK_ONWAKEUP, ///< App waking up. + APTHOOK_ONEXIT, ///< App exiting. + + APTHOOK_COUNT, ///< Number of APT hook types. +} APT_HookType; + +/// APT hook function. +typedef void (*aptHookFn)(APT_HookType hook, void* param); + +/// APT hook cookie. +typedef struct tag_aptHookCookie +{ + struct tag_aptHookCookie* next; ///< Next cookie. + aptHookFn callback; ///< Hook callback. + void* param; ///< Callback parameter. +} aptHookCookie; + +/// APT message callback. +typedef void (*aptMessageCb)(void* user, NS_APPID sender, void* msg, size_t msgsize); + +/// Initializes APT. +Result aptInit(void); + +/// Exits APT. +void aptExit(void); + +/** + * @brief Sends an APT command through IPC, taking care of locking, opening and closing an APT session. + * @param aptcmdbuf Pointer to command buffer (should have capacity for at least 16 words). + */ +Result aptSendCommand(u32* aptcmdbuf); + +/// Returns true if the application is currently in the foreground. +bool aptIsActive(void); + +/// Returns true if the system has told the application to close. +bool aptShouldClose(void); + +/// Returns true if the system can enter sleep mode while the application is active. +bool aptIsSleepAllowed(void); + +/// Configures whether the system can enter sleep mode while the application is active. +void aptSetSleepAllowed(bool allowed); + +/// Handles incoming sleep mode requests. +void aptHandleSleep(void); + +/// Returns true if the user can press the HOME button to jump back to the HOME menu while the application is active. +bool aptIsHomeAllowed(void); + +/// Configures whether the user can press the HOME button to jump back to the HOME menu while the application is active. +void aptSetHomeAllowed(bool allowed); + +/// Returns true if the system requires the application to jump back to the HOME menu. +bool aptShouldJumpToHome(void); + +/// Returns true if there is an incoming HOME button press rejected by the policy set by \ref aptSetHomeAllowed (use this to show a "no HOME allowed" icon). +bool aptCheckHomePressRejected(void); + +/// \deprecated Alias for \ref aptCheckHomePressRejected. +static inline DEPRECATED bool aptIsHomePressed(void) +{ + return aptCheckHomePressRejected(); +} + +/// Jumps back to the HOME menu. +void aptJumpToHomeMenu(void); + +/// Handles incoming jump-to-HOME requests. +static inline void aptHandleJumpToHome(void) +{ + if (aptShouldJumpToHome()) + aptJumpToHomeMenu(); +} + +/** + * @brief Main function which handles sleep mode and HOME/power buttons - call this at the beginning of every frame. + * @return true if the application should keep running, false otherwise (see \ref aptShouldClose). + */ +bool aptMainLoop(void); + +/** + * @brief Sets up an APT status hook. + * @param cookie Hook cookie to use. + * @param callback Function to call when APT's status changes. + * @param param User-defined parameter to pass to the callback. + */ +void aptHook(aptHookCookie* cookie, aptHookFn callback, void* param); + +/** + * @brief Removes an APT status hook. + * @param cookie Hook cookie to remove. + */ +void aptUnhook(aptHookCookie* cookie); + +/** + * @brief Sets the function to be called when an APT message from another applet is received. + * @param callback Callback function. + * @param user User-defined data to be passed to the callback. + */ +void aptSetMessageCallback(aptMessageCb callback, void* user); + +/** + * @brief Launches a library applet. + * @param appId ID of the applet to launch. + * @param buf Input/output buffer that contains launch parameters on entry and result data on exit. + * @param bufsize Size of the buffer. + * @param handle Handle to pass to the library applet. + */ +void aptLaunchLibraryApplet(NS_APPID appId, void* buf, size_t bufsize, Handle handle); + +/// Clears the chainloader state. +void aptClearChainloader(void); + +/** + * @brief Configures the chainloader to launch a specific application. + * @param programID ID of the program to chainload to. + * @param mediatype Media type of the program to chainload to. + */ +void aptSetChainloader(u64 programID, u8 mediatype); + +/// Configures the chainloader to relaunch the current application (i.e. soft-reset) +void aptSetChainloaderToSelf(void); + +/** + * @brief Gets an APT lock handle. + * @param flags Flags to use. + * @param lockHandle Pointer to output the lock handle to. + */ +Result APT_GetLockHandle(u16 flags, Handle* lockHandle); + +/** + * @brief Initializes an application's registration with APT. + * @param appId ID of the application. + * @param attr Attributes of the application. + * @param signalEvent Pointer to output the signal event handle to. + * @param resumeEvent Pointer to output the resume event handle to. + */ +Result APT_Initialize(NS_APPID appId, APT_AppletAttr attr, Handle* signalEvent, Handle* resumeEvent); + +/** + * @brief Terminates an application's registration with APT. + * @param appID ID of the application. + */ +Result APT_Finalize(NS_APPID appId); + +/// Asynchronously resets the hardware. +Result APT_HardwareResetAsync(void); + +/** + * @brief Enables APT. + * @param attr Attributes of the application. + */ +Result APT_Enable(APT_AppletAttr attr); + +/** + * @brief Gets applet management info. + * @param inpos Requested applet position. + * @param outpos Pointer to output the position of the current applet to. + * @param req_appid Pointer to output the AppID of the applet at the requested position to. + * @param menu_appid Pointer to output the HOME menu AppID to. + * @param active_appid Pointer to output the AppID of the currently active applet to. + */ +Result APT_GetAppletManInfo(APT_AppletPos inpos, APT_AppletPos* outpos, NS_APPID* req_appid, NS_APPID* menu_appid, NS_APPID* active_appid); + +/** + * @brief Gets the menu's app ID. + * @return The menu's app ID. + */ +static inline NS_APPID aptGetMenuAppID(void) +{ + NS_APPID menu_appid = APPID_NONE; + APT_GetAppletManInfo(APTPOS_NONE, NULL, NULL, &menu_appid, NULL); + return menu_appid; +} + +/** + * @brief Gets an applet's information. + * @param appID AppID of the applet. + * @param pProgramID Pointer to output the program ID to. + * @param pMediaType Pointer to output the media type to. + * @param pRegistered Pointer to output the registration status to. + * @param pLoadState Pointer to output the load state to. + * @param pAttributes Pointer to output the applet atrributes to. + */ +Result APT_GetAppletInfo(NS_APPID appID, u64* pProgramID, u8* pMediaType, bool* pRegistered, bool* pLoadState, APT_AppletAttr* pAttributes); + +/** + * @brief Gets an applet's program information. + * @param id ID of the applet. + * @param flags Flags to use when retreiving the information. + * @param titleversion Pointer to output the applet's title version to. + * + * Flags: + * - 0x01: Use AM_ListTitles with NAND media type. + * - 0x02: Use AM_ListTitles with SDMC media type. + * - 0x04: Use AM_ListTitles with GAMECARD media type. + * - 0x10: Input ID is an app ID. Must be set if 0x20 is not. + * - 0x20: Input ID is a program ID. Must be set if 0x10 is not. + * - 0x100: Sets program ID high to 0x00040000, else it is 0x00040010. Only used when 0x20 is set. + */ +Result APT_GetAppletProgramInfo(u32 id, u32 flags, u16 *titleversion); + +/** + * @brief Gets the current application's program ID. + * @param pProgramID Pointer to output the program ID to. + */ +Result APT_GetProgramID(u64* pProgramID); + +/// Prepares to jump to the home menu. +Result APT_PrepareToJumpToHomeMenu(void); + +/** + * @brief Jumps to the home menu. + * @param param Parameters to jump with. + * @param Size of the parameter buffer. + * @param handle Handle to pass. + */ +Result APT_JumpToHomeMenu(const void* param, size_t paramSize, Handle handle); + +/** + * @brief Prepares to jump to an application. + * @param exiting Specifies whether the applet is exiting. + */ +Result APT_PrepareToJumpToApplication(bool exiting); + +/** + * @brief Jumps to an application. + * @param param Parameters to jump with. + * @param Size of the parameter buffer. + * @param handle Handle to pass. + */ +Result APT_JumpToApplication(const void* param, size_t paramSize, Handle handle); + +/** + * @brief Gets whether an application is registered. + * @param appID ID of the application. + * @param out Pointer to output the registration state to. + */ +Result APT_IsRegistered(NS_APPID appID, bool* out); + +/** + * @brief Inquires as to whether a signal has been received. + * @param appID ID of the application. + * @param signalType Pointer to output the signal type to. + */ +Result APT_InquireNotification(u32 appID, APT_Signal* signalType); + +/** + * @brief Requests to enter sleep mode, and later sets wake events if allowed to. + * @param wakeEvents The wake events. Limited to "shell" (bit 1) for the PDN wake events part + * and "shell opened", "shell closed" and "HOME button pressed" for the MCU interrupts part. + */ +Result APT_SleepSystem(const struct PtmWakeEvents *wakeEvents); + +/** + * @brief Notifies an application to wait. + * @param appID ID of the application. + */ +Result APT_NotifyToWait(NS_APPID appID); + +/** + * @brief Calls an applet utility function. + * @param id Utility function to call. + * @param out Pointer to write output data to. + * @param outSize Size of the output buffer. + * @param in Pointer to the input data. + * @param inSize Size of the input buffer. + */ +Result APT_AppletUtility(int id, void* out, size_t outSize, const void* in, size_t inSize); + +/// Sleeps if shell is closed (?). +Result APT_SleepIfShellClosed(void); + +/** + * @brief Locks a transition (?). + * @param transition Transition ID. + * @param flag Flag (?) + */ +Result APT_LockTransition(u32 transition, bool flag); + +/** + * @brief Tries to lock a transition (?). + * @param transition Transition ID. + * @param succeeded Pointer to output whether the lock was successfully applied. + */ +Result APT_TryLockTransition(u32 transition, bool* succeeded); + +/** + * @brief Unlocks a transition (?). + * @param transition Transition ID. + */ +Result APT_UnlockTransition(u32 transition); + +/** + * @brief Glances at a receieved parameter without removing it from the queue. + * @param appID AppID of the application. + * @param buffer Buffer to receive to. + * @param bufferSize Size of the buffer. + * @param sender Pointer to output the sender's AppID to. + * @param command Pointer to output the command ID to. + * @param actualSize Pointer to output the actual received data size to. + * @param parameter Pointer to output the parameter handle to. + */ +Result APT_GlanceParameter(NS_APPID appID, void* buffer, size_t bufferSize, NS_APPID* sender, APT_Command* command, size_t* actualSize, Handle* parameter); + +/** + * @brief Receives a parameter. + * @param appID AppID of the application. + * @param buffer Buffer to receive to. + * @param bufferSize Size of the buffer. + * @param sender Pointer to output the sender's AppID to. + * @param command Pointer to output the command ID to. + * @param actualSize Pointer to output the actual received data size to. + * @param parameter Pointer to output the parameter handle to. + */ +Result APT_ReceiveParameter(NS_APPID appID, void* buffer, size_t bufferSize, NS_APPID* sender, APT_Command* command, size_t* actualSize, Handle* parameter); + +/** + * @brief Sends a parameter. + * @param source AppID of the source application. + * @param dest AppID of the destination application. + * @param command Command to send. + * @param buffer Buffer to send. + * @param bufferSize Size of the buffer. + * @param parameter Parameter handle to pass. + */ +Result APT_SendParameter(NS_APPID source, NS_APPID dest, APT_Command command, const void* buffer, u32 bufferSize, Handle parameter); + +/** + * @brief Cancels a parameter which matches the specified source and dest AppIDs. + * @param source AppID of the source application (use APPID_NONE to disable the check). + * @param dest AppID of the destination application (use APPID_NONE to disable the check). + * @param success Pointer to output true if a parameter was cancelled, or false otherwise. + */ +Result APT_CancelParameter(NS_APPID source, NS_APPID dest, bool* success); + +/** + * @brief Sends capture buffer information. + * @param captureBuf Capture buffer information to send. + */ +Result APT_SendCaptureBufferInfo(const aptCaptureBufInfo* captureBuf); + +/** + * @brief Replies to a sleep query. + * @param appID ID of the application. + * @param reply Query reply value. + */ +Result APT_ReplySleepQuery(NS_APPID appID, APT_QueryReply reply); + +/** + * @brief Replies that a sleep notification has been completed. + * @param appID ID of the application. + */ +Result APT_ReplySleepNotificationComplete(NS_APPID appID); + +/** + * @brief Prepares to close the application. + * @param cancelPreload Whether applet preloads should be cancelled. + */ +Result APT_PrepareToCloseApplication(bool cancelPreload); + +/** + * @brief Closes the application. + * @param param Parameters to close with. + * @param paramSize Size of param. + * @param handle Handle to pass. + */ +Result APT_CloseApplication(const void* param, size_t paramSize, Handle handle); + +/** + * @brief Sets the application's CPU time limit. + * @param percent CPU time limit percentage to set. + */ +Result APT_SetAppCpuTimeLimit(u32 percent); + +/** + * @brief Gets the application's CPU time limit. + * @param percent Pointer to output the CPU time limit percentage to. + */ +Result APT_GetAppCpuTimeLimit(u32 *percent); + +/** + * @brief Checks whether the system is a New 3DS. + * @param out Pointer to write the New 3DS flag to. + */ +Result APT_CheckNew3DS(bool* out); + +/** + * @brief Prepares for an applicaton jump. + * @param flags Flags to use. + * @param programID ID of the program to jump to. + * @param mediatype Media type of the program to jump to. + */ +Result APT_PrepareToDoApplicationJump(u8 flags, u64 programID, u8 mediatype); + +/** + * @brief Performs an application jump. + * @param param Parameter buffer. + * @param paramSize Size of parameter buffer. + * @param hmac HMAC buffer (should be 0x20 bytes long). + */ +Result APT_DoApplicationJump(const void* param, size_t paramSize, const void* hmac); + +/** + * @brief Prepares to start a library applet. + * @param appID AppID of the applet to start. + */ +Result APT_PrepareToStartLibraryApplet(NS_APPID appID); + +/** + * @brief Starts a library applet. + * @param appID AppID of the applet to launch. + * @param param Buffer containing applet parameters. + * @param paramsize Size of the buffer. + * @param handle Handle to pass to the applet. + */ +Result APT_StartLibraryApplet(NS_APPID appID, const void* param, size_t paramSize, Handle handle); + +/** + * @brief Prepares to start a system applet. + * @param appID AppID of the applet to start. + */ +Result APT_PrepareToStartSystemApplet(NS_APPID appID); + +/** + * @brief Starts a system applet. + * @param appID AppID of the applet to launch. + * @param param Buffer containing applet parameters. + * @param paramSize Size of the parameter buffer. + * @param handle Handle to pass to the applet. + */ +Result APT_StartSystemApplet(NS_APPID appID, const void* param, size_t paramSize, Handle handle); + +/** + * @brief Retrieves the shared system font. + * @brief fontHandle Pointer to write the handle of the system font memory block to. + * @brief mapAddr Pointer to write the mapping address of the system font memory block to. + */ +Result APT_GetSharedFont(Handle* fontHandle, u32* mapAddr); + +/** + * @brief Receives the deliver (launch) argument + * @param param Parameter buffer. + * @param paramSize Size of parameter buffer. + * @param hmac HMAC buffer (should be 0x20 bytes long). + * @param sender Pointer to output the sender's AppID to. + * @param received Pointer to output whether an argument was received to. + */ +Result APT_ReceiveDeliverArg(const void* param, size_t paramSize, const void* hmac, u64* sender, bool* received); diff --git a/libctru/include/3ds/services/boss.h b/libctru/include/3ds/services/boss.h new file mode 100644 index 0000000000..10f9202aca --- /dev/null +++ b/libctru/include/3ds/services/boss.h @@ -0,0 +1,180 @@ +/** + * @file boss.h + * @brief BOSS service, see also: https://www.3dbrew.org/wiki/BOSS_Services + */ +#pragma once + +/// BOSS context. +typedef struct +{ + u32 property[0x7]; + + char url[0x200]; + + u32 property_x8; + u8 property_x9; + + u8 property_xa[0x100]; + + u8 property_xb[0x200]; + + char property_xd[0x360];//Additonal optional HTTP request headers. + + u32 property_xe; + + u32 property_xf[0xc>>2]; + + u8 property_x10; + u8 property_x11; + u8 property_x12; + u32 property_x13; + u32 property_x14; + + u8 property_x15[0x40]; + + u32 property_x16; + + u32 property_x3b; + + u8 property_x3e[0x200]; +} bossContext; + +/// BOSS task status. +typedef enum { + BOSSTASKSTATUS_STARTED = 0x2, + BOSSTASKSTATUS_ERROR = 0x7, +} bossTaskStatus; + +/// Type values for bossGetNsDataHeaderInfo(). +typedef enum { + bossNsDataHeaderInfoType_ContentSize = 0x3 /// Size of the content. +} bossNsDataHeaderInfoTypes; + +/// Size of the output data for bossGetNsDataHeaderInfo(). +typedef enum { + bossNsDataHeaderInfoTypeSize_ContentSize = 0x4 ///Type2 +} bossNsDataHeaderInfoTypeSizes; + +/** + * @brief Initializes BOSS. + * @param programID programID to use, 0 for the current process. Only used when BOSSP is available without *hax payload. + * @param force_user When true, just use bossU instead of trying to initialize with bossP first. + */ +Result bossInit(u64 programID, bool force_user); + +/** + * @brief Run the InitializeSession service cmd. This is mainly for changing the programID associated with the current BOSS session. + * @param programID programID to use, 0 for the current process. + */ +Result bossReinit(u64 programID); + +/// Exits BOSS. +void bossExit(void); + +/// Returns the BOSS session handle. +Handle bossGetSessionHandle(void); + +/** + * @brief Set the content data storage location. + * @param extdataID u64 extdataID, must have the high word set to the shared-extdata value when it's for NAND. + * @param boss_size Probably the max size in the extdata which BOSS can use. + * @param mediaType Roughly the same as FS mediatype. + */ +Result bossSetStorageInfo(u64 extdataID, u32 boss_size, u8 mediaType); + +/** + * @brief Unregister the content data storage location, which includes unregistering the BOSS-session programID with BOSS. + */ +Result bossUnregisterStorage(void); + +/** + * @brief Register a task. + * @param taskID BOSS taskID. + * @param unk0 Unknown, usually zero. + * @param unk1 Unknown, usually zero. + */ +Result bossRegisterTask(const char *taskID, u8 unk0, u8 unk1); + +/** + * @brief Send a property. + * @param PropertyID PropertyID + * @param buf Input buffer data. + * @param size Buffer size. + */ +Result bossSendProperty(u16 PropertyID, const void* buf, u32 size); + +/** + * @brief Deletes the content file for the specified NsDataId. + * @param NsDataId NsDataId + */ +Result bossDeleteNsData(u32 NsDataId); + +/** + * @brief Gets header info for the specified NsDataId. + * @param NsDataId NsDataId + * @param type Type of data to load. + * @param buffer Output buffer. + * @param size Output buffer size. + */ +Result bossGetNsDataHeaderInfo(u32 NsDataId, u8 type, void* buffer, u32 size); + +/** + * @brief Reads data from the content for the specified NsDataId. + * @param NsDataId NsDataId + * @param offset Offset in the content. + * @param buffer Output buffer. + * @param size Output buffer size. + * @param transfer_total Optional output actual read size, can be NULL. + * @param unk_out Optional unknown output, can be NULL. + */ +Result bossReadNsData(u32 NsDataId, u64 offset, void* buffer, u32 size, u32 *transfer_total, u32 *unk_out); + +/** + * @brief Starts a task soon after running this command. + * @param taskID BOSS taskID. + */ +Result bossStartTaskImmediate(const char *taskID); + +/** + * @brief Similar to bossStartTaskImmediate? + * @param taskID BOSS taskID. + */ +Result bossStartBgImmediate(const char *taskID); + +/** + * @brief Deletes a task by using CancelTask and UnregisterTask internally. + * @param taskID BOSS taskID. + * @param unk Unknown, usually zero? + */ +Result bossDeleteTask(const char *taskID, u32 unk); + +/** + * @brief Returns task state. + * @param taskID BOSS taskID. + * @param inval Unknown, normally 0? + * @param status Output status, see bossTaskStatus. + * @param out1 Output field. + * @param out2 Output field. + */ +Result bossGetTaskState(const char *taskID, s8 inval, u8 *status, u32 *out1, u8 *out2); + +/** + * @brief This loads the current state of PropertyID 0x0 for the specified task. + * @param taskID BOSS taskID. + */ +Result bossGetTaskProperty0(const char *taskID, u8 *out); + +/** + * @brief Setup a BOSS context with the default config. + * @param bossContext BOSS context. + * @param seconds_interval Interval in seconds for running the task automatically. + * @param url Task URL. + */ +void bossSetupContextDefault(bossContext *ctx, u32 seconds_interval, const char *url); + +/** + * @brief Sends the config stored in the context. Used before registering a task. + * @param bossContext BOSS context. + */ +Result bossSendContextConfig(bossContext *ctx); + diff --git a/libctru/include/3ds/services/cam.h b/libctru/include/3ds/services/cam.h new file mode 100644 index 0000000000..501533960b --- /dev/null +++ b/libctru/include/3ds/services/cam.h @@ -0,0 +1,703 @@ +/** + * @file cam.h + * @brief CAM service for using the 3DS's front and back cameras. + */ +#pragma once + +#include <3ds/services/y2r.h> +#include <3ds/types.h> + +/// Camera connection target ports. +enum { + PORT_NONE = 0x0, ///< No port. + PORT_CAM1 = BIT(0), ///< CAM1 port. + PORT_CAM2 = BIT(1), ///< CAM2 port. + + // Port combinations. + PORT_BOTH = PORT_CAM1 | PORT_CAM2, ///< Both ports. +}; + +/// Camera combinations. +enum { + SELECT_NONE = 0x0, ///< No camera. + SELECT_OUT1 = BIT(0), ///< Outer camera 1. + SELECT_IN1 = BIT(1), ///< Inner camera 1. + SELECT_OUT2 = BIT(2), ///< Outer camera 2. + + // Camera combinations. + SELECT_IN1_OUT1 = SELECT_OUT1 | SELECT_IN1, ///< Outer camera 1 and inner camera 1. + SELECT_OUT1_OUT2 = SELECT_OUT1 | SELECT_OUT2, ///< Both outer cameras. + SELECT_IN1_OUT2 = SELECT_IN1 | SELECT_OUT2, ///< Inner camera 1 and outer camera 2. + SELECT_ALL = SELECT_OUT1 | SELECT_IN1 | SELECT_OUT2, ///< All cameras. +}; + +/// Camera contexts. +typedef enum { + CONTEXT_NONE = 0x0, ///< No context. + CONTEXT_A = BIT(0), ///< Context A. + CONTEXT_B = BIT(1), ///< Context B. + + // Context combinations. + CONTEXT_BOTH = CONTEXT_A | CONTEXT_B, ///< Both contexts. +} CAMU_Context; + +/// Ways to flip the camera image. +typedef enum { + FLIP_NONE = 0x0, ///< No flip. + FLIP_HORIZONTAL = 0x1, ///< Horizontal flip. + FLIP_VERTICAL = 0x2, ///< Vertical flip. + FLIP_REVERSE = 0x3, ///< Reverse flip. +} CAMU_Flip; + +/// Camera image resolutions. +typedef enum { + SIZE_VGA = 0x0, ///< VGA size. (640x480) + SIZE_QVGA = 0x1, ///< QVGA size. (320x240) + SIZE_QQVGA = 0x2, ///< QQVGA size. (160x120) + SIZE_CIF = 0x3, ///< CIF size. (352x288) + SIZE_QCIF = 0x4, ///< QCIF size. (176x144) + SIZE_DS_LCD = 0x5, ///< DS LCD size. (256x192) + SIZE_DS_LCDx4 = 0x6, ///< DS LCD x4 size. (512x384) + SIZE_CTR_TOP_LCD = 0x7, ///< CTR Top LCD size. (400x240) + + // Alias for bottom screen to match top screen naming. + SIZE_CTR_BOTTOM_LCD = SIZE_QVGA, ///< CTR Bottom LCD size. (320x240) +} CAMU_Size; + +/// Camera capture frame rates. +typedef enum { + FRAME_RATE_15 = 0x0, ///< 15 FPS. + FRAME_RATE_15_TO_5 = 0x1, ///< 15-5 FPS. + FRAME_RATE_15_TO_2 = 0x2, ///< 15-2 FPS. + FRAME_RATE_10 = 0x3, ///< 10 FPS. + FRAME_RATE_8_5 = 0x4, ///< 8.5 FPS. + FRAME_RATE_5 = 0x5, ///< 5 FPS. + FRAME_RATE_20 = 0x6, ///< 20 FPS. + FRAME_RATE_20_TO_5 = 0x7, ///< 20-5 FPS. + FRAME_RATE_30 = 0x8, ///< 30 FPS. + FRAME_RATE_30_TO_5 = 0x9, ///< 30-5 FPS. + FRAME_RATE_15_TO_10 = 0xA, ///< 15-10 FPS. + FRAME_RATE_20_TO_10 = 0xB, ///< 20-10 FPS. + FRAME_RATE_30_TO_10 = 0xC, ///< 30-10 FPS. +} CAMU_FrameRate; + +/// Camera white balance modes. +typedef enum { + WHITE_BALANCE_AUTO = 0x0, ///< Auto white balance. + WHITE_BALANCE_3200K = 0x1, ///< 3200K white balance. + WHITE_BALANCE_4150K = 0x2, ///< 4150K white balance. + WHITE_BALANCE_5200K = 0x3, ///< 5200K white balance. + WHITE_BALANCE_6000K = 0x4, ///< 6000K white balance. + WHITE_BALANCE_7000K = 0x5, ///< 7000K white balance. + + // White balance aliases. + WHITE_BALANCE_NORMAL = WHITE_BALANCE_AUTO, // Normal white balance. (AUTO) + WHITE_BALANCE_TUNGSTEN = WHITE_BALANCE_3200K, // Tungsten white balance. (3200K) + WHITE_BALANCE_WHITE_FLUORESCENT_LIGHT = WHITE_BALANCE_4150K, // Fluorescent white balance. (4150K) + WHITE_BALANCE_DAYLIGHT = WHITE_BALANCE_5200K, // Daylight white balance. (5200K) + WHITE_BALANCE_CLOUDY = WHITE_BALANCE_6000K, // Cloudy white balance. (6000K) + WHITE_BALANCE_HORIZON = WHITE_BALANCE_6000K, // Horizon white balance. (6000K) + WHITE_BALANCE_SHADE = WHITE_BALANCE_7000K, // Shade white balance. (7000K) +} CAMU_WhiteBalance; + +/// Camera photo modes. +typedef enum { + PHOTO_MODE_NORMAL = 0x0, ///< Normal mode. + PHOTO_MODE_PORTRAIT = 0x1, ///< Portrait mode. + PHOTO_MODE_LANDSCAPE = 0x2, ///< Landscape mode. + PHOTO_MODE_NIGHTVIEW = 0x3, ///< Night mode. + PHOTO_MODE_LETTER = 0x4, ///< Letter mode. +} CAMU_PhotoMode; + +/// Camera special effects. +typedef enum { + EFFECT_NONE = 0x0, ///< No effects. + EFFECT_MONO = 0x1, ///< Mono effect. + EFFECT_SEPIA = 0x2, ///< Sepia effect. + EFFECT_NEGATIVE = 0x3, ///< Negative effect. + EFFECT_NEGAFILM = 0x4, ///< Negative film effect. + EFFECT_SEPIA01 = 0x5, ///< Sepia effect. +} CAMU_Effect; + +/// Camera contrast patterns. +typedef enum { + CONTRAST_PATTERN_01 = 0x0, ///< Pattern 1. + CONTRAST_PATTERN_02 = 0x1, ///< Pattern 2. + CONTRAST_PATTERN_03 = 0x2, ///< Pattern 3. + CONTRAST_PATTERN_04 = 0x3, ///< Pattern 4. + CONTRAST_PATTERN_05 = 0x4, ///< Pattern 5. + CONTRAST_PATTERN_06 = 0x5, ///< Pattern 6. + CONTRAST_PATTERN_07 = 0x6, ///< Pattern 7. + CONTRAST_PATTERN_08 = 0x7, ///< Pattern 8. + CONTRAST_PATTERN_09 = 0x8, ///< Pattern 9. + CONTRAST_PATTERN_10 = 0x9, ///< Pattern 10. + CONTRAST_PATTERN_11 = 0xA, ///< Pattern 11. + + // Contrast aliases. + CONTRAST_LOW = CONTRAST_PATTERN_05, ///< Low contrast. (5) + CONTRAST_NORMAL = CONTRAST_PATTERN_06, ///< Normal contrast. (6) + CONTRAST_HIGH = CONTRAST_PATTERN_07, ///< High contrast. (7) +} CAMU_Contrast; + +/// Camera lens correction modes. +typedef enum { + LENS_CORRECTION_OFF = 0x0, ///< No lens correction. + LENS_CORRECTION_ON_70 = 0x1, ///< Edge-to-center brightness ratio of 70. + LENS_CORRECTION_ON_90 = 0x2, ///< Edge-to-center brightness ratio of 90. + + // Lens correction aliases. + LENS_CORRECTION_DARK = LENS_CORRECTION_OFF, ///< Dark lens correction. (OFF) + LENS_CORRECTION_NORMAL = LENS_CORRECTION_ON_70, ///< Normal lens correction. (70) + LENS_CORRECTION_BRIGHT = LENS_CORRECTION_ON_90, ///< Bright lens correction. (90) +} CAMU_LensCorrection; + +/// Camera image output formats. +typedef enum { + OUTPUT_YUV_422 = 0x0, ///< YUV422 + OUTPUT_RGB_565 = 0x1, ///< RGB565 +} CAMU_OutputFormat; + +/// Camera shutter sounds. +typedef enum { + SHUTTER_SOUND_TYPE_NORMAL = 0x0, ///< Normal shutter sound. + SHUTTER_SOUND_TYPE_MOVIE = 0x1, ///< Shutter sound to begin a movie. + SHUTTER_SOUND_TYPE_MOVIE_END = 0x2, ///< Shutter sound to end a movie. +} CAMU_ShutterSoundType; + +/// Image quality calibration data. +typedef struct { + s16 aeBaseTarget; ///< Auto exposure base target brightness. + s16 kRL; ///< Left color correction matrix red normalization coefficient. + s16 kGL; ///< Left color correction matrix green normalization coefficient. + s16 kBL; ///< Left color correction matrix blue normalization coefficient. + s16 ccmPosition; ///< Color correction matrix position. + u16 awbCcmL9Right; ///< Right camera, left color correction matrix red/green gain. + u16 awbCcmL9Left; ///< Left camera, left color correction matrix red/green gain. + u16 awbCcmL10Right; ///< Right camera, left color correction matrix blue/green gain. + u16 awbCcmL10Left; ///< Left camera, left color correction matrix blue/green gain. + u16 awbX0Right; ///< Right camera, color correction matrix position threshold. + u16 awbX0Left; ///< Left camera, color correction matrix position threshold. +} CAMU_ImageQualityCalibrationData; + +/// Stereo camera calibration data. +typedef struct { + u8 isValidRotationXY; ///< #bool Whether the X and Y rotation data is valid. + u8 padding[3]; ///< Padding. (Aligns isValidRotationXY to 4 bytes) + float scale; ///< Scale to match the left camera image with the right. + float rotationZ; ///< Z axis rotation to match the left camera image with the right. + float translationX; ///< X axis translation to match the left camera image with the right. + float translationY; ///< Y axis translation to match the left camera image with the right. + float rotationX; ///< X axis rotation to match the left camera image with the right. + float rotationY; ///< Y axis rotation to match the left camera image with the right. + float angleOfViewRight; ///< Right camera angle of view. + float angleOfViewLeft; ///< Left camera angle of view. + float distanceToChart; ///< Distance between cameras and measurement chart. + float distanceCameras; ///< Distance between left and right cameras. + s16 imageWidth; ///< Image width. + s16 imageHeight; ///< Image height. + u8 reserved[16]; ///< Reserved for future use. (unused) +} CAMU_StereoCameraCalibrationData; + +/// Batch camera configuration for use without a context. +typedef struct { + u8 camera; ///< Selected camera. + s8 exposure; ///< Camera exposure. + u8 whiteBalance; ///< #CAMU_WhiteBalance Camera white balance. + s8 sharpness; ///< Camera sharpness. + u8 autoExposureOn; ///< #bool Whether to automatically determine the proper exposure. + u8 autoWhiteBalanceOn; ///< #bool Whether to automatically determine the white balance mode. + u8 frameRate; ///< #CAMU_FrameRate Camera frame rate. + u8 photoMode; ///< #CAMU_PhotoMode Camera photo mode. + u8 contrast; ///< #CAMU_Contrast Camera contrast. + u8 lensCorrection; ///< #CAMU_LensCorrection Camera lens correction. + u8 noiseFilterOn; ///< #bool Whether to enable the camera's noise filter. + u8 padding; ///< Padding. (Aligns last 3 fields to 4 bytes) + s16 autoExposureWindowX; ///< X of the region to use for auto exposure. + s16 autoExposureWindowY; ///< Y of the region to use for auto exposure. + s16 autoExposureWindowWidth; ///< Width of the region to use for auto exposure. + s16 autoExposureWindowHeight; ///< Height of the region to use for auto exposure. + s16 autoWhiteBalanceWindowX; ///< X of the region to use for auto white balance. + s16 autoWhiteBalanceWindowY; ///< Y of the region to use for auto white balance. + s16 autoWhiteBalanceWindowWidth; ///< Width of the region to use for auto white balance. + s16 autoWhiteBalanceWindowHeight; ///< Height of the region to use for auto white balance. +} CAMU_PackageParameterCameraSelect; + +/// Batch camera configuration for use with a context. +typedef struct { + u8 camera; ///< Selected camera. + u8 context; ///< #CAMU_Context Selected context. + u8 flip; ///< #CAMU_Flip Camera image flip mode. + u8 effect; ///< #CAMU_Effect Camera image special effects. + u8 size; ///< #CAMU_Size Camera image resolution. +} CAMU_PackageParameterContext; + +/// Batch camera configuration for use with a context and with detailed size information. +typedef struct { + u8 camera; ///< Selected camera. + u8 context; ///< #CAMU_Context Selected context. + u8 flip; ///< #CAMU_Flip Camera image flip mode. + u8 effect; ///< #CAMU_Effect Camera image special effects. + s16 width; ///< Image width. + s16 height; ///< Image height. + s16 cropX0; ///< First crop point X. + s16 cropY0; ///< First crop point Y. + s16 cropX1; ///< Second crop point X. + s16 cropY1; ///< Second crop point Y. +} CAMU_PackageParameterContextDetail; + +/** + * @brief Initializes the cam service. + * + * This will internally get the handle of the service, and on success call CAMU_DriverInitialize. + */ +Result camInit(void); + +/** + * @brief Closes the cam service. + * + * This will internally call CAMU_DriverFinalize and close the handle of the service. + */ +void camExit(void); + +/** + * Begins capture on the specified camera port. + * @param port Port to begin capture on. + */ +Result CAMU_StartCapture(u32 port); + +/** + * Terminates capture on the specified camera port. + * @param port Port to terminate capture on. + */ +Result CAMU_StopCapture(u32 port); + +/** + * @brief Gets whether the specified camera port is busy. + * @param busy Pointer to output the busy state to. + * @param port Port to check. + */ +Result CAMU_IsBusy(bool* busy, u32 port); + +/** + * @brief Clears the buffer and error flags of the specified camera port. + * @param port Port to clear. + */ +Result CAMU_ClearBuffer(u32 port); + +/** + * @brief Gets a handle to the event signaled on vsync interrupts. + * @param event Pointer to output the event handle to. + * @param port Port to use. + */ +Result CAMU_GetVsyncInterruptEvent(Handle* event, u32 port); + +/** + * @brief Gets a handle to the event signaled on camera buffer errors. + * @param event Pointer to output the event handle to. + * @param port Port to use. + */ +Result CAMU_GetBufferErrorInterruptEvent(Handle* event, u32 port); + +/** + * @brief Initiates the process of receiving a camera frame. + * @param event Pointer to output the completion event handle to. + * @param dst Buffer to write data to. + * @param port Port to receive from. + * @param imageSize Size of the image to receive. + * @param transferUnit Transfer unit to use when receiving. + */ +Result CAMU_SetReceiving(Handle* event, void* dst, u32 port, u32 imageSize, s16 transferUnit); + +/** + * @brief Gets whether the specified camera port has finished receiving image data. + * @param finishedReceiving Pointer to output the receiving status to. + * @param port Port to check. + */ +Result CAMU_IsFinishedReceiving(bool* finishedReceiving, u32 port); + +/** + * @brief Sets the number of lines to transfer into an image buffer. + * @param port Port to use. + * @param lines Lines to transfer. + * @param width Width of the image. + * @param height Height of the image. + */ +Result CAMU_SetTransferLines(u32 port, s16 lines, s16 width, s16 height); + +/** + * @brief Gets the maximum number of lines that can be saved to an image buffer. + * @param maxLines Pointer to write the maximum number of lines to. + * @param width Width of the image. + * @param height Height of the image. + */ +Result CAMU_GetMaxLines(s16* maxLines, s16 width, s16 height); + +/** + * @brief Sets the number of bytes to transfer into an image buffer. + * @param port Port to use. + * @param bytes Bytes to transfer. + * @param width Width of the image. + * @param height Height of the image. + */ +Result CAMU_SetTransferBytes(u32 port, u32 bytes, s16 width, s16 height); + +/** + * @brief Gets the number of bytes to transfer into an image buffer. + * @param transferBytes Pointer to write the number of bytes to. + * @param port Port to use. + */ +Result CAMU_GetTransferBytes(u32* transferBytes, u32 port); + +/** + * @brief Gets the maximum number of bytes that can be saved to an image buffer. + * @param maxBytes Pointer to write the maximum number of bytes to. + * @param width Width of the image. + * @param height Height of the image. + */ +Result CAMU_GetMaxBytes(u32* maxBytes, s16 width, s16 height); + +/** + * @brief Sets whether image trimming is enabled. + * @param port Port to use. + * @param trimming Whether image trimming is enabled. + */ +Result CAMU_SetTrimming(u32 port, bool trimming); + +/** + * @brief Gets whether image trimming is enabled. + * @param trimming Pointer to output the trim state to. + * @param port Port to use. + */ +Result CAMU_IsTrimming(bool* trimming, u32 port); + +/** + * @brief Sets the parameters used for trimming images. + * @param port Port to use. + * @param xStart Start X coordinate. + * @param yStart Start Y coordinate. + * @param xEnd End X coordinate. + * @param yEnd End Y coordinate. + */ +Result CAMU_SetTrimmingParams(u32 port, s16 xStart, s16 yStart, s16 xEnd, s16 yEnd); + +/** + * @brief Gets the parameters used for trimming images. + * @param xStart Pointer to write the start X coordinate to. + * @param yStart Pointer to write the start Y coordinate to. + * @param xEnd Pointer to write the end X coordinate to. + * @param yEnd Pointer to write the end Y coordinate to. + * @param port Port to use. + */ +Result CAMU_GetTrimmingParams(s16* xStart, s16* yStart, s16* xEnd, s16* yEnd, u32 port); + +/** + * @brief Sets the parameters used for trimming images, relative to the center of the image. + * @param port Port to use. + * @param trimWidth Trim width. + * @param trimHeight Trim height. + * @param camWidth Camera width. + * @param camHeight Camera height. + */ +Result CAMU_SetTrimmingParamsCenter(u32 port, s16 trimWidth, s16 trimHeight, s16 camWidth, s16 camHeight); + +/** + * @brief Activates the specified camera. + * @param select Camera to use. + */ +Result CAMU_Activate(u32 select); + +/** + * @brief Switches the specified camera's active context. + * @param select Camera to use. + * @param context Context to use. + */ +Result CAMU_SwitchContext(u32 select, CAMU_Context context); + +/** + * @brief Sets the exposure value of the specified camera. + * @param select Camera to use. + * @param exposure Exposure value to use. + */ +Result CAMU_SetExposure(u32 select, s8 exposure); + +/** + * @brief Sets the white balance mode of the specified camera. + * @param select Camera to use. + * @param whiteBalance White balance mode to use. + */ +Result CAMU_SetWhiteBalance(u32 select, CAMU_WhiteBalance whiteBalance); + +/** + * @brief Sets the white balance mode of the specified camera. + * TODO: Explain "without base up"? + * @param select Camera to use. + * @param whiteBalance White balance mode to use. + */ +Result CAMU_SetWhiteBalanceWithoutBaseUp(u32 select, CAMU_WhiteBalance whiteBalance); + +/** + * @brief Sets the sharpness of the specified camera. + * @param select Camera to use. + * @param sharpness Sharpness to use. + */ +Result CAMU_SetSharpness(u32 select, s8 sharpness); + +/** + * @brief Sets whether auto exposure is enabled on the specified camera. + * @param select Camera to use. + * @param autoWhiteBalance Whether auto exposure is enabled. + */ +Result CAMU_SetAutoExposure(u32 select, bool autoExposure); + +/** + * @brief Gets whether auto exposure is enabled on the specified camera. + * @param autoExposure Pointer to output the auto exposure state to. + * @param select Camera to use. + */ +Result CAMU_IsAutoExposure(bool* autoExposure, u32 select); + +/** + * @brief Sets whether auto white balance is enabled on the specified camera. + * @param select Camera to use. + * @param autoWhiteBalance Whether auto white balance is enabled. + */ +Result CAMU_SetAutoWhiteBalance(u32 select, bool autoWhiteBalance); + +/** + * @brief Gets whether auto white balance is enabled on the specified camera. + * @param autoWhiteBalance Pointer to output the auto white balance state to. + * @param select Camera to use. + */ +Result CAMU_IsAutoWhiteBalance(bool* autoWhiteBalance, u32 select); + +/** + * @brief Flips the image of the specified camera in the specified context. + * @param select Camera to use. + * @param flip Flip mode to use. + * @param context Context to use. + */ +Result CAMU_FlipImage(u32 select, CAMU_Flip flip, CAMU_Context context); + +/** + * @brief Sets the image resolution of the given camera in the given context, in detail. + * @param select Camera to use. + * @param width Width to use. + * @param height Height to use. + * @param cropX0 First crop point X. + * @param cropY0 First crop point Y. + * @param cropX1 Second crop point X. + * @param cropY1 Second crop point Y. + * @param context Context to use. + */ +Result CAMU_SetDetailSize(u32 select, s16 width, s16 height, s16 cropX0, s16 cropY0, s16 cropX1, s16 cropY1, CAMU_Context context); + +/** + * @brief Sets the image resolution of the given camera in the given context. + * @param select Camera to use. + * @param size Size to use. + * @param context Context to use. + */ +Result CAMU_SetSize(u32 select, CAMU_Size size, CAMU_Context context); + +/** + * @brief Sets the frame rate of the given camera. + * @param select Camera to use. + * @param frameRate Frame rate to use. + */ +Result CAMU_SetFrameRate(u32 select, CAMU_FrameRate frameRate); + +/** + * @brief Sets the photo mode of the given camera. + * @param select Camera to use. + * @param photoMode Photo mode to use. + */ +Result CAMU_SetPhotoMode(u32 select, CAMU_PhotoMode photoMode); + +/** + * @brief Sets the special effects of the given camera in the given context. + * @param select Camera to use. + * @param effect Effect to use. + * @param context Context to use. + */ +Result CAMU_SetEffect(u32 select, CAMU_Effect effect, CAMU_Context context); + +/** + * @brief Sets the contrast mode of the given camera. + * @param select Camera to use. + * @param contrast Contrast mode to use. + */ +Result CAMU_SetContrast(u32 select, CAMU_Contrast contrast); + +/** + * @brief Sets the lens correction mode of the given camera. + * @param select Camera to use. + * @param lensCorrection Lens correction mode to use. + */ +Result CAMU_SetLensCorrection(u32 select, CAMU_LensCorrection lensCorrection); + +/** + * @brief Sets the output format of the given camera in the given context. + * @param select Camera to use. + * @param format Format to output. + * @param context Context to use. + */ +Result CAMU_SetOutputFormat(u32 select, CAMU_OutputFormat format, CAMU_Context context); + +/** + * @brief Sets the region to base auto exposure off of for the specified camera. + * @param select Camera to use. + * @param x X of the region. + * @param y Y of the region. + * @param width Width of the region. + * @param height Height of the region. + */ +Result CAMU_SetAutoExposureWindow(u32 select, s16 x, s16 y, s16 width, s16 height); + +/** + * @brief Sets the region to base auto white balance off of for the specified camera. + * @param select Camera to use. + * @param x X of the region. + * @param y Y of the region. + * @param width Width of the region. + * @param height Height of the region. + */ +Result CAMU_SetAutoWhiteBalanceWindow(u32 select, s16 x, s16 y, s16 width, s16 height); + +/** + * @brief Sets whether the specified camera's noise filter is enabled. + * @param select Camera to use. + * @param noiseFilter Whether the noise filter is enabled. + */ +Result CAMU_SetNoiseFilter(u32 select, bool noiseFilter); + +/** + * @brief Synchronizes the specified cameras' vsync timing. + * @param select1 First camera. + * @param select2 Second camera. + */ +Result CAMU_SynchronizeVsyncTiming(u32 select1, u32 select2); + +/** + * @brief Gets the vsync timing record of the specified camera for the specified number of signals. + * @param timing Pointer to write timing data to. (size "past * sizeof(s64)") + * @param port Port to use. + * @param past Number of past timings to retrieve. + */ +Result CAMU_GetLatestVsyncTiming(s64* timing, u32 port, u32 past); + +/** + * @brief Gets the specified camera's stereo camera calibration data. + * @param data Pointer to output the stereo camera data to. + */ +Result CAMU_GetStereoCameraCalibrationData(CAMU_StereoCameraCalibrationData* data); + +/** + * @brief Sets the specified camera's stereo camera calibration data. + * @param data Data to set. + */ +Result CAMU_SetStereoCameraCalibrationData(CAMU_StereoCameraCalibrationData data); + +/** + * @brief Writes to the specified I2C register of the specified camera. + * @param select Camera to write to. + * @param addr Address to write to. + * @param data Data to write. + */ +Result CAMU_WriteRegisterI2c(u32 select, u16 addr, u16 data); + +/** + * @brief Writes to the specified MCU variable of the specified camera. + * @param select Camera to write to. + * @param addr Address to write to. + * @param data Data to write. + */ +Result CAMU_WriteMcuVariableI2c(u32 select, u16 addr, u16 data); + +/** + * @brief Reads the specified I2C register of the specified camera. + * @param data Pointer to read data to. + * @param select Camera to read from. + * @param addr Address to read. + */ +Result CAMU_ReadRegisterI2cExclusive(u16* data, u32 select, u16 addr); + +/** + * @brief Reads the specified MCU variable of the specified camera. + * @param data Pointer to read data to. + * @param select Camera to read from. + * @param addr Address to read. + */ +Result CAMU_ReadMcuVariableI2cExclusive(u16* data, u32 select, u16 addr); + +/** + * @brief Sets the specified camera's image quality calibration data. + * @param data Data to set. + */ +Result CAMU_SetImageQualityCalibrationData(CAMU_ImageQualityCalibrationData data); + +/** + * @brief Gets the specified camera's image quality calibration data. + * @param data Pointer to write the quality data to. + */ +Result CAMU_GetImageQualityCalibrationData(CAMU_ImageQualityCalibrationData* data); + +/** + * @brief Configures a camera with pre-packaged configuration data without a context. + * @param Parameter to use. + */ +Result CAMU_SetPackageParameterWithoutContext(CAMU_PackageParameterCameraSelect param); + +/** + * @brief Configures a camera with pre-packaged configuration data with a context. + * @param Parameter to use. + */ +Result CAMU_SetPackageParameterWithContext(CAMU_PackageParameterContext param); + +/** + * @brief Configures a camera with pre-packaged configuration data without a context and extra resolution details. + * @param Parameter to use. + */ +Result CAMU_SetPackageParameterWithContextDetail(CAMU_PackageParameterContextDetail param); + +/** + * @brief Gets the Y2R coefficient applied to image data by the camera. + * @param coefficient Pointer to output the Y2R coefficient to. + */ +Result CAMU_GetSuitableY2rStandardCoefficient(Y2RU_StandardCoefficient* coefficient); + +/** + * @brief Plays the specified shutter sound. + * @param sound Shutter sound to play. + */ +Result CAMU_PlayShutterSound(CAMU_ShutterSoundType sound); + +/// Initializes the camera driver. +Result CAMU_DriverInitialize(void); + +/// Finalizes the camera driver. +Result CAMU_DriverFinalize(void); + +/** + * @brief Gets the current activated camera. + * @param select Pointer to output the current activated camera to. + */ +Result CAMU_GetActivatedCamera(u32* select); + +/** + * @brief Gets the current sleep camera. + * @param select Pointer to output the current sleep camera to. + */ +Result CAMU_GetSleepCamera(u32* select); + +/** + * @brief Sets the current sleep camera. + * @param select Camera to set. + */ +Result CAMU_SetSleepCamera(u32 select); + +/** + * @brief Sets whether to enable synchronization of left and right camera brightnesses. + * @param brightnessSynchronization Whether to enable brightness synchronization. + */ +Result CAMU_SetBrightnessSynchronization(bool brightnessSynchronization); + diff --git a/libctru/include/3ds/services/cfgnor.h b/libctru/include/3ds/services/cfgnor.h new file mode 100644 index 0000000000..526aac1177 --- /dev/null +++ b/libctru/include/3ds/services/cfgnor.h @@ -0,0 +1,53 @@ +/** + * @file cfgnor.h + * @brief CFGNOR service. + */ +#pragma once + +/** + * @brief Initializes CFGNOR. + * @param value Unknown, usually 1. + */ +Result cfgnorInit(u8 value); + +/// Exits CFGNOR +void cfgnorExit(void); + +/** + * @brief Dumps the NOR flash. + * @param buf Buffer to dump to. + * @param size Size of the buffer. + */ +Result cfgnorDumpFlash(u32 *buf, u32 size); + +/** + * @brief Writes the NOR flash. + * @param buf Buffer to write from. + * @param size Size of the buffer. + */ +Result cfgnorWriteFlash(u32 *buf, u32 size); + +/** + * @brief Initializes the CFGNOR session. + * @param value Unknown, usually 1. + */ +Result CFGNOR_Initialize(u8 value); + +/// Shuts down the CFGNOR session. +Result CFGNOR_Shutdown(void); + +/** + * @brief Reads data from NOR. + * @param offset Offset to read from. + * @param buf Buffer to read data to. + * @param size Size of the buffer. + */ +Result CFGNOR_ReadData(u32 offset, u32 *buf, u32 size); + +/** + * @brief Writes data to NOR. + * @param offset Offset to write to. + * @param buf Buffer to write data from. + * @param size Size of the buffer. + */ +Result CFGNOR_WriteData(u32 offset, u32 *buf, u32 size); diff --git a/libctru/include/3ds/services/cfgu.h b/libctru/include/3ds/services/cfgu.h new file mode 100644 index 0000000000..364dbc8bfb --- /dev/null +++ b/libctru/include/3ds/services/cfgu.h @@ -0,0 +1,220 @@ +/** + * @file cfgu.h + * @brief CFGU (Configuration) Service + */ +#pragma once +#include <3ds/types.h> + +/// Configuration region values. +typedef enum +{ + CFG_REGION_JPN = 0, ///< Japan + CFG_REGION_USA = 1, ///< USA + CFG_REGION_EUR = 2, ///< Europe + CFG_REGION_AUS = 3, ///< Australia + CFG_REGION_CHN = 4, ///< China + CFG_REGION_KOR = 5, ///< Korea + CFG_REGION_TWN = 6, ///< Taiwan +} CFG_Region; + +/// Configuration language values. +typedef enum +{ + CFG_LANGUAGE_JP = 0, ///< Japanese + CFG_LANGUAGE_EN = 1, ///< English + CFG_LANGUAGE_FR = 2, ///< French + CFG_LANGUAGE_DE = 3, ///< German + CFG_LANGUAGE_IT = 4, ///< Italian + CFG_LANGUAGE_ES = 5, ///< Spanish + CFG_LANGUAGE_ZH = 6, ///< Simplified Chinese + CFG_LANGUAGE_KO = 7, ///< Korean + CFG_LANGUAGE_NL = 8, ///< Dutch + CFG_LANGUAGE_PT = 9, ///< Portugese + CFG_LANGUAGE_RU = 10, ///< Russian + CFG_LANGUAGE_TW = 11, ///< Traditional Chinese +} CFG_Language; + +// Configuration system model values. +typedef enum +{ + CFG_MODEL_3DS = 0, ///< Old 3DS (CTR) + CFG_MODEL_3DSXL = 1, ///< Old 3DS XL (SPR) + CFG_MODEL_N3DS = 2, ///< New 3DS (KTR) + CFG_MODEL_2DS = 3, ///< Old 2DS (FTR) + CFG_MODEL_N3DSXL = 4, ///< New 3DS XL (RED) + CFG_MODEL_N2DSXL = 5, ///< New 2DS XL (JAN) +} CFG_SystemModel; + +/// Initializes CFGU. +Result cfguInit(void); + +/// Exits CFGU. +void cfguExit(void); + +/** + * @brief Gets the system's region from secure info. + * @param region Pointer to output the region to. (see @ref CFG_Region) + */ +Result CFGU_SecureInfoGetRegion(u8* region); + +/** + * @brief Generates a console-unique hash. + * @param appIDSalt Salt to use. + * @param hash Pointer to output the hash to. + */ +Result CFGU_GenHashConsoleUnique(u32 appIDSalt, u64* hash); + +/** + * @brief Gets whether the system's region is Canada or USA. + * @param value Pointer to output the result to. (0 = no, 1 = yes) + */ +Result CFGU_GetRegionCanadaUSA(u8* value); + +/** + * @brief Gets the system's model. + * @param model Pointer to output the model to. (see @ref CFG_SystemModel) + */ +Result CFGU_GetSystemModel(u8* model); + +/** + * @brief Gets whether the system is a 2DS. + * @param value Pointer to output the result to. (0 = yes, 1 = no) + */ +Result CFGU_GetModelNintendo2DS(u8* value); + +/** + * @brief Gets a string representing a country code. + * @param code Country code to use. + * @param string Pointer to output the string to. + */ +Result CFGU_GetCountryCodeString(u16 code, u16* string); + +/** + * @brief Gets a country code ID from its string. + * @param string String to use. + * @param code Pointer to output the country code to. + */ +Result CFGU_GetCountryCodeID(u16 string, u16* code); + +/** + * @brief Checks if NFC (code name: fangate) is supported. + * @param isSupported pointer to the output the result to. + */ +Result CFGU_IsNFCSupported(bool* isSupported); + +/** + * @brief Gets a config info block with flags = 2. + * @param size Size of the data to retrieve. + * @param blkID ID of the block to retrieve. + * @param outData Pointer to write the block data to. + */ +Result CFGU_GetConfigInfoBlk2(u32 size, u32 blkID, void* outData); + +/** + * @brief Gets a config info block with flags = 4. + * @param size Size of the data to retrieve. + * @param blkID ID of the block to retrieve. + * @param outData Pointer to write the block data to. + */ +Result CFG_GetConfigInfoBlk4(u32 size, u32 blkID, void* outData); + +/** + * @brief Gets a config info block with flags = 8. + * @param size Size of the data to retrieve. + * @param blkID ID of the block to retrieve. + * @param outData Pointer to write the block data to. + */ +Result CFG_GetConfigInfoBlk8(u32 size, u32 blkID, void* outData); + +/** + * @brief Sets a config info block with flags = 4. + * @param size Size of the data to retrieve. + * @param blkID ID of the block to retrieve. + * @param inData Pointer to block data to write. + */ +Result CFG_SetConfigInfoBlk4(u32 size, u32 blkID, const void* inData); + +/** + * @brief Sets a config info block with flags = 8. + * @param size Size of the data to retrieve. + * @param blkID ID of the block to retrieve. + * @param inData Pointer to block data to write. + */ +Result CFG_SetConfigInfoBlk8(u32 size, u32 blkID, const void* inData); + + +/** + * @brief Writes the CFG buffer in memory to the savegame in NAND. + */ +Result CFG_UpdateConfigSavegame(void); + +/** + * @brief Gets the system's language. + * @param language Pointer to write the language to. (see @ref CFG_Language) + */ +Result CFGU_GetSystemLanguage(u8* language); + +/** + * @brief Deletes the NAND LocalFriendCodeSeed file, then recreates it using the LocalFriendCodeSeed data stored in memory. + */ +Result CFGI_RestoreLocalFriendCodeSeed(void); + +/** + * @brief Deletes the NAND SecureInfo file, then recreates it using the SecureInfo data stored in memory. + */ +Result CFGI_RestoreSecureInfo(void); + +/** + * @brief Deletes the "config" file stored in the NAND Config_Savegame. + */ +Result CFGI_DeleteConfigSavefile(void); + +/** + * @brief Formats Config_Savegame. + */ +Result CFGI_FormatConfig(void); + +/** + * @brief Clears parental controls + */ +Result CFGI_ClearParentalControls(void); + +/** + * @brief Verifies the RSA signature for the LocalFriendCodeSeed data already stored in memory. + */ +Result CFGI_VerifySigLocalFriendCodeSeed(void); + +/** + * @brief Verifies the RSA signature for the SecureInfo data already stored in memory. + */ +Result CFGI_VerifySigSecureInfo(void); + +/** + * @brief Gets the system's serial number. + * @param serial Pointer to output the serial to. (This is normally 0xF) + */ +Result CFGI_SecureInfoGetSerialNumber(u8 *serial); + +/** + * @brief Gets the 0x110-byte buffer containing the data for the LocalFriendCodeSeed. + * @param data Pointer to output the buffer. (The size must be at least 0x110-bytes) + */ +Result CFGI_GetLocalFriendCodeSeedData(u8 *data); + +/** + * @brief Gets the 64-bit local friend code seed. + * @param seed Pointer to write the friend code seed to. + */ +Result CFGI_GetLocalFriendCodeSeed(u64* seed); + +/** + * @brief Gets the 0x11-byte data following the SecureInfo signature. + * @param data Pointer to output the buffer. (The size must be at least 0x11-bytes) + */ +Result CFGI_GetSecureInfoData(u8 *data); + +/** + * @brief Gets the 0x100-byte RSA-2048 SecureInfo signature. + * @param data Pointer to output the buffer. (The size must be at least 0x100-bytes) + */ +Result CFGI_GetSecureInfoSignature(u8 *data); diff --git a/libctru/include/3ds/services/csnd.h b/libctru/include/3ds/services/csnd.h new file mode 100644 index 0000000000..1f8cf37666 --- /dev/null +++ b/libctru/include/3ds/services/csnd.h @@ -0,0 +1,433 @@ +/** + * @file csnd.h + * @brief CSND service. Usage of this service is deprecated in favor of NDSP. + */ +#pragma once + +#include <3ds/types.h> + +/// Maximum number of CSND channels. +#define CSND_NUM_CHANNELS 32 + +/// Creates a CSND timer value from a sample rate. +#define CSND_TIMER(n) (0x3FEC3FC / ((u32)(n))) + +/** + * @brief Converts a vol-pan pair into a left/right volume pair used by the hardware. + * @param vol Volume to use. + * @param pan Pan to use. + * @return A left/right volume pair for use by hardware. + */ +static inline u32 CSND_VOL(float vol, float pan) +{ + float rpan; + u32 lvol, rvol; + + if (vol < 0.0f) vol = 0.0f; + else if (vol > 1.0f) vol = 1.0f; + + rpan = (pan+1) / 2; + if (rpan < 0.0f) rpan = 0.0f; + else if (rpan > 1.0f) rpan = 1.0f; + + lvol = vol*(1-rpan) * 0x8000; + rvol = vol*rpan * 0x8000; + return lvol | (rvol << 16); +} + +/// CSND encodings. +enum +{ + CSND_ENCODING_PCM8 = 0, ///< PCM8 + CSND_ENCODING_PCM16, ///< PCM16 + CSND_ENCODING_ADPCM, ///< IMA-ADPCM + CSND_ENCODING_PSG, ///< PSG (Similar to DS?) +}; + +/// CSND loop modes. +enum +{ + CSND_LOOPMODE_MANUAL = 0, ///< Manual loop. + CSND_LOOPMODE_NORMAL, ///< Normal loop. + CSND_LOOPMODE_ONESHOT, ///< Do not loop. + CSND_LOOPMODE_NORELOAD, ///< Don't reload. +}; + +/// Creates a sound channel value from a channel number. +#define SOUND_CHANNEL(n) ((u32)(n) & 0x1F) + +/// Creates a sound format value from an encoding. +#define SOUND_FORMAT(n) ((u32)(n) << 12) + +/// Creates a sound loop mode value from a loop mode. +#define SOUND_LOOPMODE(n) ((u32)(n) << 10) + +/// Sound flags. +enum +{ + SOUND_LINEAR_INTERP = BIT(6), ///< Linear interpolation. + SOUND_REPEAT = SOUND_LOOPMODE(CSND_LOOPMODE_NORMAL), ///< Repeat the sound. + SOUND_ONE_SHOT = SOUND_LOOPMODE(CSND_LOOPMODE_ONESHOT), ///< Play the sound once. + SOUND_FORMAT_8BIT = SOUND_FORMAT(CSND_ENCODING_PCM8), ///< PCM8 + SOUND_FORMAT_16BIT = SOUND_FORMAT(CSND_ENCODING_PCM16), ///< PCM16 + SOUND_FORMAT_ADPCM = SOUND_FORMAT(CSND_ENCODING_ADPCM), ///< ADPCM + SOUND_FORMAT_PSG = SOUND_FORMAT(CSND_ENCODING_PSG), ///< PSG + SOUND_ENABLE = BIT(14), ///< Enable sound. +}; + +/// Capture modes. +enum +{ + CAPTURE_REPEAT = 0, ///< Repeat capture. + CAPTURE_ONE_SHOT = BIT(0), ///< Capture once. + CAPTURE_FORMAT_16BIT = 0, ///< PCM16 + CAPTURE_FORMAT_8BIT = BIT(1), ///< PCM8 + CAPTURE_ENABLE = BIT(15), ///< Enable capture. +}; + +/// Duty cycles for a PSG channel. +typedef enum +{ + DutyCycle_0 = 7, ///< 0.0% duty cycle + DutyCycle_12 = 0, ///< 12.5% duty cycle + DutyCycle_25 = 1, ///< 25.0% duty cycle + DutyCycle_37 = 2, ///< 37.5% duty cycle + DutyCycle_50 = 3, ///< 50.0% duty cycle + DutyCycle_62 = 4, ///< 62.5% duty cycle + DutyCycle_75 = 5, ///< 75.0% duty cycle + DutyCycle_87 = 6 ///< 87.5% duty cycle +} CSND_DutyCycle; + +/// Channel info. +typedef union +{ + u32 value[3]; ///< Raw values. + struct + { + u8 active; ///< Channel active. + u8 _pad1; ///< Padding. + u16 _pad2; ///< Padding. + s16 adpcmSample; ///< Current ADPCM sample. + u8 adpcmIndex; ///< Current ADPCM index. + u8 _pad3; ///< Padding. + u32 unknownZero; ///< Unknown. + }; +} CSND_ChnInfo; + +/// Capture info. +typedef union +{ + u32 value[2]; ///< Raw values. + struct + { + u8 active; ///< Capture active. + u8 _pad1; ///< Padding. + u16 _pad2; ///< Padding. + u32 unknownZero; ///< Unknown. + }; +} CSND_CapInfo; + +// See here regarding CSND shared-mem commands, etc: http://3dbrew.org/wiki/CSND_Shared_Memory + +extern vu32* csndSharedMem; ///< CSND shared memory. +extern u32 csndSharedMemSize; ///< CSND shared memory size. +extern u32 csndChannels; ///< Bitmask of channels that are allowed for usage. + +/** + * @brief Acquires a capture unit. + * @param capUnit Pointer to output the capture unit to. + */ +Result CSND_AcquireCapUnit(u32* capUnit); + +/** + * @brief Releases a capture unit. + * @param capUnit Capture unit to release. + */ +Result CSND_ReleaseCapUnit(u32 capUnit); + +/** + * @brief Flushes the data cache of a memory region. + * @param adr Address of the memory region. + * @param size Size of the memory region. + */ +Result CSND_FlushDataCache(const void* adr, u32 size); + +/** + * @brief Stores the data cache of a memory region. + * @param adr Address of the memory region. + * @param size Size of the memory region. + */ +Result CSND_StoreDataCache(const void* adr, u32 size); + +/** + * @brief Invalidates the data cache of a memory region. + * @param adr Address of the memory region. + * @param size Size of the memory region. + */ +Result CSND_InvalidateDataCache(const void* adr, u32 size); + +/** + * @brief Resets CSND. + * Note: Currently breaks sound, don't use for now! + */ +Result CSND_Reset(void); + +/// Initializes CSND. +Result csndInit(void); + +/// Exits CSND. +void csndExit(void); + +/** + * @brief Adds a command to the list, returning a buffer to write arguments to. + * @param cmdid ID of the command to add. + * @return A buffer to write command arguments to. + */ +u32* csndAddCmd(int cmdid); + +/** + * @brief Adds a command to the list, copying its arguments from a buffer. + * @param cmdid ID of the command to add. + * @param cmdparams Buffer containing the command's parameters. + */ +void csndWriteCmd(int cmdid, u8* cmdparams); + +/** + * @brief Executes pending CSND commands. + * @param waitDone Whether to wait until the commands have finished executing. + */ +Result csndExecCmds(bool waitDone); + +/** + * @brief Sets a channel's play state, resetting registers on stop. + * @param channel Channel to use. + * @param value Play state to set. + */ +void CSND_SetPlayStateR(u32 channel, u32 value); + +/** + * @brief Sets a channel's play state. + * @param channel Channel to use. + * @param value Play state to set. + */ +void CSND_SetPlayState(u32 channel, u32 value); + +/** + * @brief Sets a channel's encoding. + * @param channel Channel to use. + * @param value Encoding to set. + */ +void CSND_SetEncoding(u32 channel, u32 value); + +/** + * @brief Sets the data of a channel's block. + * @param channel Channel to use. + * @param block Block to set. + * @param physaddr Physical address to set the block to. + * @param size Size of the block. + */ +void CSND_SetBlock(u32 channel, int block, u32 physaddr, u32 size); + +/** + * @brief Sets whether to loop a channel. + * @param channel Channel to use. + * @param value Whether to loop the channel. + */ +void CSND_SetLooping(u32 channel, u32 value); + +/** + * @brief Sets bit 7 of a channel. + * @param channel Channel to use. + * @param set Value to set. + */ +void CSND_SetBit7(u32 channel, bool set); + +/** + * @brief Sets whether a channel should use interpolation. + * @param channel Channel to use. + * @param interp Whether to use interpolation. + */ +void CSND_SetInterp(u32 channel, bool interp); + +/** + * @brief Sets a channel's duty. + * @param channel Channel to use. + * @param duty Duty to set. + */ +void CSND_SetDuty(u32 channel, CSND_DutyCycle duty); + +/** + * @brief Sets a channel's timer. + * @param channel Channel to use. + * @param timer Timer to set. + */ +void CSND_SetTimer(u32 channel, u32 timer); + +/** + * @brief Sets a channel's volume. + * @param channel Channel to use. + * @param chnVolumes Channel volume data to set. + * @param capVolumes Capture volume data to set. + */ +void CSND_SetVol(u32 channel, u32 chnVolumes, u32 capVolumes); + +/** + * @brief Sets a channel's ADPCM state. + * @param channel Channel to use. + * @param block Current block. + * @param sample Current sample. + * @param index Current index. + */ +void CSND_SetAdpcmState(u32 channel, int block, int sample, int index); + +/** + * @brief Sets a whether channel's ADPCM data should be reloaded when the second block is played. + * @param channel Channel to use. + * @param reload Whether to reload ADPCM data. + */ +void CSND_SetAdpcmReload(u32 channel, bool reload); + +/** + * @brief Sets CSND's channel registers. + * @param flags Flags to set. + * @param physaddr0 Physical address of the first buffer to play. + * @param physaddr1 Physical address of the second buffer to play. + * @param totalbytesize Total size of the data to play. + * @param chnVolumes Channel volume data. + * @param capVolumes Capture volume data. + */ +void CSND_SetChnRegs(u32 flags, u32 physaddr0, u32 physaddr1, u32 totalbytesize, u32 chnVolumes, u32 capVolumes); + +/** + * @brief Sets CSND's PSG channel registers. + * @param flags Flags to set. + * @param chnVolumes Channel volume data. + * @param capVolumes Capture volume data. + * @param duty Duty value to set. + */ +void CSND_SetChnRegsPSG(u32 flags, u32 chnVolumes, u32 capVolumes, CSND_DutyCycle duty); + +/** + * @brief Sets CSND's noise channel registers. + * @param flags Flags to set. + * @param chnVolumes Channel volume data. + * @param capVolumes Capture volume data. + */ +void CSND_SetChnRegsNoise(u32 flags, u32 chnVolumes, u32 capVolumes); + +/** + * @brief Sets whether a capture unit is enabled. + * @param capUnit Capture unit to use. + * @param enable Whether to enable the capture unit. + */ +void CSND_CapEnable(u32 capUnit, bool enable); + +/** + * @brief Sets whether a capture unit should repeat. + * @param capUnit Capture unit to use. + * @param repeat Whether the capture unit should repeat. + */ +void CSND_CapSetRepeat(u32 capUnit, bool repeat); + +/** + * @brief Sets a capture unit's format. + * @param capUnit Capture unit to use. + * @param eightbit Format to use. + */ +void CSND_CapSetFormat(u32 capUnit, bool eightbit); + +/** + * @brief Sets a capture unit's second bit. + * @param capUnit Capture unit to use. + * @param set Value to set. + */ +void CSND_CapSetBit2(u32 capUnit, bool set); + +/** + * @brief Sets a capture unit's timer. + * @param capUnit Capture unit to use. + * @param timer Timer to set. + */ +void CSND_CapSetTimer(u32 capUnit, u32 timer); + +/** + * @brief Sets a capture unit's buffer. + * @param capUnit Capture unit to use. + * @param addr Buffer address to use. + * @param size Size of the buffer. + */ +void CSND_CapSetBuffer(u32 capUnit, u32 addr, u32 size); + +/** + * @brief Sets a capture unit's capture registers. + * @param capUnit Capture unit to use. + * @param flags Capture unit flags. + * @param addr Capture unit buffer address. + * @param size Buffer size. + */ +void CSND_SetCapRegs(u32 capUnit, u32 flags, u32 addr, u32 size); + +/** + * @brief Sets up DSP flags. + * @param waitDone Whether to wait for completion. + */ +Result CSND_SetDspFlags(bool waitDone); + +/** + * @brief Updates CSND information. + * @param waitDone Whether to wait for completion. + */ +Result CSND_UpdateInfo(bool waitDone); + +/** + * @brief Plays a sound. + * @param chn Channel to play the sound on. + * @param flags Flags containing information about the sound. + * @param sampleRate Sample rate of the sound. + * @param vol The volume, ranges from 0.0 to 1.0 included. + * @param pan The pan, ranges from -1.0 to 1.0 included. + * @param data0 First block of sound data. + * @param data1 Second block of sound data. This is the block that will be looped over. + * @param size Size of the sound data. + * + * In this implementation if the loop mode is used, data1 must be in the range [data0 ; data0 + size]. Sound will be played once from data0 to data0 + size and then loop between data1 and data0+size. + */ +Result csndPlaySound(int chn, u32 flags, u32 sampleRate, float vol, float pan, void* data0, void* data1, u32 size); + +/** + * @brief Gets CSND's DSP flags. + * Note: Requires previous CSND_UpdateInfo() + * @param outSemFlags Pointer to write semaphore flags to. + * @param outIrqFlags Pointer to write interrupt flags to. + */ +void csndGetDspFlags(u32* outSemFlags, u32* outIrqFlags); + +/** + * @brief Gets a channel's information. + * Note: Requires previous CSND_UpdateInfo() + * @param channel Channel to get information for. + * @return The channel's information. + */ +CSND_ChnInfo* csndGetChnInfo(u32 channel); + +/** + * @brief Gets a capture unit's information. + * Note: Requires previous CSND_UpdateInfo() + * @param capUnit Capture unit to get information for. + * @return The capture unit's information. + */ +CSND_CapInfo* csndGetCapInfo(u32 capUnit); + +/** + * @brief Gets a channel's state. + * @param channel Channel to get the state of. + * @param out Pointer to output channel information to. + */ +Result csndGetState(u32 channel, CSND_ChnInfo* out); + +/** + * @brief Gets whether a channel is playing. + * @param channel Channel to check. + * @param status Pointer to output the channel status to. + */ +Result csndIsPlaying(u32 channel, u8* status); diff --git a/libctru/include/3ds/services/dsp.h b/libctru/include/3ds/services/dsp.h new file mode 100644 index 0000000000..0241cc9e44 --- /dev/null +++ b/libctru/include/3ds/services/dsp.h @@ -0,0 +1,188 @@ +/** + * @file dsp.h + * @brief DSP Service to access the DSP processor commands (sound) + * + * The DSP has access to the Linear memory region, and to the DSP memory region if allowed in the exheader. + */ +#pragma once +#include <3ds/types.h> + +/// DSP interrupt types. +typedef enum +{ + DSP_INTERRUPT_PIPE = 2 ///< Pipe interrupt. +} DSP_InterruptType; + +/// DSP hook types. +typedef enum +{ + DSPHOOK_ONSLEEP = 0, ///< DSP is going to sleep. + DSPHOOK_ONWAKEUP = 1, ///< DSP is waking up. + DSPHOOK_ONCANCEL = 2, ///< DSP was sleeping and the app was cancelled. +} DSP_HookType; + +/// DSP hook function. +typedef void (* dspHookFn)(DSP_HookType hook); + +/// DSP hook cookie. +typedef struct tag_dspHookCookie +{ + struct tag_dspHookCookie* next; ///< Next cookie. + dspHookFn callback; ///< Hook callback. +} dspHookCookie; + +/** + * @brief Initializes the dsp service. + * + * Call this before calling any DSP_* function. + * @note This will also unload any previously loaded DSP binary. + * It is done this way since you have to provide your binary when the 3DS leaves sleep mode anyway. + */ +Result dspInit(void); + +/** + * @brief Closes the dsp service. + * @note This will also unload the DSP binary. + */ +void dspExit(void); + +/// Returns true if a component is loaded, false otherwise. +bool dspIsComponentLoaded(void); + +/** + * @brief Sets up a DSP status hook. + * @param cookie Hook cookie to use. + * @param callback Function to call when DSP's status changes. + */ +void dspHook(dspHookCookie* cookie, dspHookFn callback); + +/** + * @brief Removes a DSP status hook. + * @param cookie Hook cookie to remove. + */ +void dspUnhook(dspHookCookie* cookie); + +/** + * @brief Checks if a headphone is inserted. + * @param is_inserted Pointer to output the insertion status to. + */ +Result DSP_GetHeadphoneStatus(bool* is_inserted); + +/** + * @brief Flushes the cache + * @param address Beginning of the memory range to flush, inside the Linear or DSP memory regions + * @param size Size of the memory range to flush + * + * Flushes the cache for the specified memory range and invalidates the cache + */ +Result DSP_FlushDataCache(const void* address, u32 size); + +/** + * @brief Invalidates the cache + * @param address Beginning of the memory range to invalidate, inside the Linear or DSP memory regions + * @param size Size of the memory range to flush + * + * Invalidates the cache for the specified memory range + */ +Result DSP_InvalidateDataCache(const void* address, u32 size); + +/** + * @brief Retrieves the handle of the DSP semaphore. + * @param semaphore Pointer to output the semaphore to. + */ +Result DSP_GetSemaphoreHandle(Handle* semaphore); + +/** + * @brief Sets the DSP hardware semaphore value. + * @param value Value to set. + */ +Result DSP_SetSemaphore(u16 value); + +/** + * @brief Masks the DSP hardware semaphore value. + * @param mask Mask to apply. + */ +Result DSP_SetSemaphoreMask(u16 mask); + +/** + * @brief Loads a DSP binary and starts the DSP + * @param component The program file address in memory + * @param size The size of the program + * @param prog_mask DSP memory block related ? Default is 0xff. + * @param data_mask DSP memory block related ? Default is 0xff. + * @param is_loaded Indicates if the DSP was succesfully loaded. + * + * @note The binary must be signed (http://3dbrew.org/wiki/DSP_Binary) + * @note Seems to be called when the 3ds leaves the Sleep mode + */ +Result DSP_LoadComponent(const void* component, u32 size, u16 prog_mask, u16 data_mask, bool* is_loaded); + +///Stops the DSP by unloading the binary. +Result DSP_UnloadComponent(void); + +/** + * @brief Registers an event handle with the DSP through IPC + * @param handle Event handle to register. + * @param interrupt The type of interrupt that will trigger the event. Usual value is DSP_INTERRUPT_PIPE. + * @param channel The pipe channel. Usual value is 2 + * + * @note It is possible that interrupt are inverted + */ +Result DSP_RegisterInterruptEvents(Handle handle, u32 interrupt, u32 channel); + +/** + * @brief Reads a pipe if possible. + * @param channel unknown. Usually 2 + * @param peer unknown. Usually 0 + * @param buffer The buffer that will store the values read from the pipe + * @param length Length of the buffer + * @param length_read Number of bytes read by the command + */ +Result DSP_ReadPipeIfPossible(u32 channel, u32 peer, void* buffer, u16 length, u16* length_read); + +/** + * @brief Writes to a pipe. + * @param channel unknown. Usually 2 + * @param buffer The message to send to the DSP process + * @param length Length of the message + */ +Result DSP_WriteProcessPipe(u32 channel, const void* buffer, u32 length); + +/** + * @brief Converts a DSP memory address to a virtual address usable by the process. + * @param dsp_address Address to convert. + * @param arm_address Pointer to output the converted address to. + */ +Result DSP_ConvertProcessAddressFromDspDram(u32 dsp_address, u32* arm_address); + +/** + * @brief Reads a DSP register + * @param regNo Offset of the hardware register, base address is 0x1EC40000 + * @param value Pointer to read the register value to. + */ +Result DSP_RecvData(u16 regNo, u16* value); + +/** + * @brief Checks if you can read a DSP register + * @param regNo Offset of the hardware register, base address is 0x1EC40000 + * @param is_ready Pointer to write the ready status to. + * + * @warning This call might hang if the data is not ready. See @ref DSP_SendDataIsEmpty. + */ +Result DSP_RecvDataIsReady(u16 regNo, bool* is_ready); + +/** + * @brief Writes to a DSP register + * @param regNo Offset of the hardware register, base address is 0x1EC40000 + * @param value Value to write. + * + * @warning This call might hang if the SendData is not empty. See @ref DSP_SendDataIsEmpty. + */ +Result DSP_SendData(u16 regNo, u16 value); + +/** + * @brief Checks if you can write to a DSP register ? + * @param regNo Offset of the hardware register, base address is 0x1EC40000 + * @param is_empty Pointer to write the empty status to. + */ +Result DSP_SendDataIsEmpty(u16 regNo, bool* is_empty); diff --git a/libctru/include/3ds/services/frd.h b/libctru/include/3ds/services/frd.h new file mode 100644 index 0000000000..933aacb5f9 --- /dev/null +++ b/libctru/include/3ds/services/frd.h @@ -0,0 +1,261 @@ +/** + * @file frd.h + * @brief Friend Services + */ +#pragma once +#include <3ds/mii.h> + +#define FRIEND_SCREEN_NAME_SIZE 0xB ///< 11-byte UTF-16 screen name +#define FRIEND_COMMENT_SIZE 0x21 ///< 33-byte UTF-16 comment +#define FRIEND_LIST_SIZE 0x64 ///< 100 (Max number of friends) + +#pragma pack(push, 1) + +/// Friend key data +typedef struct +{ + u32 principalId; + u32 padding; + u64 localFriendCode; +} FriendKey; + +/// Friend Title data +typedef struct +{ + u64 tid; + u32 version; + u32 unk; +} TitleData; + +/// Friend profile data +typedef struct +{ + u8 region; ///< The region code for the hardware. + u8 country; ///< Country code. + u8 area; ///< Area code. + u8 language; ///< Language code. + u8 platform; ///< Platform code. + u32 padding; +} FriendProfile; + +/// Game Description structure +typedef struct +{ + TitleData data; + u16 desc[128]; +} GameDescription; + +/// Friend Notification Event structure +typedef struct +{ + u8 type; + u8 padding3[3]; + u32 padding; + FriendKey key; +} NotificationEvent; + +#pragma pack(pop) + +/// Enum to use with FRD_GetNotificationEvent +typedef enum +{ + USER_WENT_ONLINE = 1, ///< Self went online + USER_WENT_OFFLINE, ///< Self went offline + FRIEND_WENT_ONLINE, ///< Friend Went Online + FRIEND_UPDATED_PRESENCE, ///< Friend Presence changed + FRIEND_UPDATED_MII, ///< Friend Mii changed + FRIEND_UPDATED_PROFILE, ///< Friend Profile changed + FRIEND_WENT_OFFLINE, ///< Friend went offline + FRIEND_REGISTERED_USER, ///< Friend registered self as friend + FRIEND_SENT_INVITATION ///< Friend Sent invitation +} NotificationTypes; + +/// Initializes FRD service. +Result frdInit(void); + +/// Exists FRD. +void frdExit(void); + +/// Get FRD handle. +Handle *frdGetSessionHandle(void); +/** + * @brief Gets the login status of the current user. + * @param state Pointer to write the current user's login status to. + */ +Result FRDU_HasLoggedIn(bool *state); + +/** + * @brief Gets the online status of the current user. + * @param state Pointer to write the current user's online status to. + */ +Result FRDU_IsOnline(bool *state); + +/// Logs out of Nintendo's friend server. +Result FRD_Logout(void); + +/** + * @brief Log in to Nintendo's friend server. + * @param event Event to signal when Login is done. + */ +Result FRD_Login(Handle event); + +/** + * @brief Gets the current user's friend key. + * @param key Pointer to write the current user's friend key to. + */ +Result FRD_GetMyFriendKey(FriendKey *key); + +/** + * @brief Gets the current user's privacy information. + * @param isPublicMode Determines whether friends are notified of the current user's online status. + * @param isShowGameName Determines whether friends are notified of the application that the current user is running. + * @param isShowPlayedGame Determiens whether to display the current user's game history. + */ +Result FRD_GetMyPreference(bool *isPublicMode, bool *isShowGameName, bool *isShowPlayedGame); + +/** + * @brief Gets the current user's profile information. + * @param profile Pointer to write the current user's profile information to. + */ +Result FRD_GetMyProfile(FriendProfile *profile); + +/** + * @brief Gets the current user's screen name. + * @param name Pointer to write the current user's screen name to. + * @param max_size Max size of the screen name. + */ +Result FRD_GetMyScreenName(char *name, size_t max_size); + +/** + * @brief Gets the current user's Mii data. + * @param mii Pointer to write the current user's mii data to. + */ +Result FRD_GetMyMii(MiiData *mii); + +/** + * @brief Gets the current user's playing game. + * @param titleId Pointer to write the current user's playing game to. + */ +Result FRD_GetMyPlayingGame(u64 *titleId); + +/** + * @brief Gets the current user's favourite game. + * @param titleId Pointer to write the title ID of current user's favourite game to. + */ +Result FRD_GetMyFavoriteGame(u64 *titleId); + +/** + * @brief Gets the current user's comment on their friend profile. + * @param comment Pointer to write the current user's comment to. + * @param max_size Max size of the comment. + */ +Result FRD_GetMyComment(char *comment, size_t max_size); + +/** + * @brief Gets the current user's friend key list. + * @param friendKeyList Pointer to write the friend key list to. + * @param num Stores the number of friend keys obtained. + * @param offset The index of the friend key to start with. + * @param size Size of the friend key list. (FRIEND_LIST_SIZE) + */ +Result FRD_GetFriendKeyList(FriendKey *friendKeyList, u32 *num, u32 offset, u32 size); + +/** + * @brief Gets the current user's friends' Mii data. + * @param miiDataList Pointer to write Mii data to. + * @param friendKeyList Pointer to FriendKeys. + * @param size Number of Friendkeys. + */ +Result FRD_GetFriendMii(MiiData *miiDataList, const FriendKey *friendKeyList, size_t size); + +/** + * @brief Get the current user's friends' profile data. + * @param profile Pointer to write profile data to. + * @param friendKeyList Pointer to FriendKeys. + * @param size Number of FriendKeys. + */ +Result FRD_GetFriendProfile(FriendProfile *profile, const FriendKey *friendKeyList, size_t size); + +/** + * @brief Get the current user's friends' playing game. + * @param desc Pointer to write Game Description data to. + * @param friendKeyList Pointer to FriendKeys, + * @param size Number Of FriendKeys. + */ +Result FRD_GetFriendPlayingGame(GameDescription *desc, const FriendKey *friendKeyList, size_t size); + +/** + * @brief Get the current user's friends' favourite game. + * @param desc Pointer to write Game Description data to. + * @param friendKeyList Pointer to FriendKeys, + * @param count Number Of FriendKeys. + */ +Result FRD_GetFriendFavouriteGame(GameDescription *desc, const FriendKey *friendKeyList, u32 count); + +/** + * @brief Gets whether a friend key is included in the current user's friend list. + * @param friendKeyList Pointer to a list of friend keys. + * @param isFromList Pointer to a write the friendship status to. + */ +Result FRD_IsInFriendList(FriendKey *friendKeyList, bool *isFromList); + +/** + * @brief Updates the game mode description string. + * @param desc Pointer to write the game mode description to. + */ +Result FRD_UpdateGameModeDescription(const char *desc); + +/** + * @brief Event which is signaled when friend login states change. + * @param event event which will be signaled. + */ +Result FRD_AttachToEventNotification(Handle event); + +/** + * @brief Get Latest Event Notification + * @param event Pointer to write recieved notification event struct to. + * @param count Number of events + * @param recievedNotifCount Number of notification reccieved. + */ +Result FRD_GetEventNotification(NotificationEvent *event, u32 count, u32 *recievedNotifCount); + +/** + * @brief Returns the friend code using the given principal ID. + * @param principalId The principal ID being used. + * @param friendCode Pointer to write the friend code to. + */ +Result FRD_PrincipalIdToFriendCode(u32 principalId, u64 *friendCode); + +/** + * @brief Returns the principal ID using the given friend code. + * @param friendCode The friend code being used. + * @param principalId Pointer to write the principal ID to. + */ +Result FRD_FriendCodeToPrincipalId(u64 friendCode, u32 *principalId); + +/** + * @brief Checks if the friend code is valid. + * @param friendCode The friend code being used. + * @param isValid Pointer to write the validity of the friend code to. + */ +Result FRD_IsValidFriendCode(u64 friendCode, bool *isValid); + +/** + * @brief Sets the Friend API to use a specific SDK version. + * @param sdkVer The SDK version needed to be used. + */ +Result FRD_SetClientSdkVersion(u32 sdkVer); + +/** + * @brief Add a Friend online. + * @param event Event signaled when friend is registered. + * @param principalId PrincipalId of the friend to add. + */ +Result FRD_AddFriendOnline(Handle event, u32 principalId); + +/** + * @brief Remove a Friend. + * @param principalId PrinipalId of the friend code to remove. + * @param localFriendCode LocalFriendCode of the friend code to remove. + */ +Result FRD_RemoveFriend(u32 principalId, u64 localFriendCode); diff --git a/libctru/include/3ds/services/fs.h b/libctru/include/3ds/services/fs.h new file mode 100644 index 0000000000..5a3956d399 --- /dev/null +++ b/libctru/include/3ds/services/fs.h @@ -0,0 +1,1089 @@ +/** + * @file fs.h + * @brief Filesystem Services + */ +#pragma once + +#include <3ds/types.h> + +/// Open flags. +enum +{ + FS_OPEN_READ = BIT(0), ///< Open for reading. + FS_OPEN_WRITE = BIT(1), ///< Open for writing. + FS_OPEN_CREATE = BIT(2), ///< Create file. +}; + +/// Write flags. +enum +{ + FS_WRITE_FLUSH = BIT(0), ///< Flush. + FS_WRITE_UPDATE_TIME = BIT(8), ///< Update file timestamp. +}; + +/// Attribute flags. +enum +{ + FS_ATTRIBUTE_DIRECTORY = BIT(0), ///< Directory. + FS_ATTRIBUTE_HIDDEN = BIT(8), ///< Hidden. + FS_ATTRIBUTE_ARCHIVE = BIT(16), ///< Archive. + FS_ATTRIBUTE_READ_ONLY = BIT(24), ///< Read-only. +}; + +/// Media types. +typedef enum +{ + MEDIATYPE_NAND = 0, ///< NAND. + MEDIATYPE_SD = 1, ///< SD card. + MEDIATYPE_GAME_CARD = 2, ///< Game card. +} FS_MediaType; + +/// System media types. +typedef enum +{ + SYSTEM_MEDIATYPE_CTR_NAND = 0, ///< CTR NAND. + SYSTEM_MEDIATYPE_TWL_NAND = 1, ///< TWL NAND. + SYSTEM_MEDIATYPE_SD = 2, ///< SD card. + SYSTEM_MEDIATYPE_TWL_PHOTO = 3, ///< TWL Photo. +} FS_SystemMediaType; + +/// Archive IDs. +typedef enum +{ + ARCHIVE_ROMFS = 0x00000003, ///< RomFS archive. + ARCHIVE_SAVEDATA = 0x00000004, ///< Save data archive. + ARCHIVE_EXTDATA = 0x00000006, ///< Ext data archive. + ARCHIVE_SHARED_EXTDATA = 0x00000007, ///< Shared ext data archive. + ARCHIVE_SYSTEM_SAVEDATA = 0x00000008, ///< System save data archive. + ARCHIVE_SDMC = 0x00000009, ///< SDMC archive. + ARCHIVE_SDMC_WRITE_ONLY = 0x0000000A, ///< Write-only SDMC archive. + ARCHIVE_BOSS_EXTDATA = 0x12345678, ///< BOSS ext data archive. + ARCHIVE_CARD_SPIFS = 0x12345679, ///< Card SPI FS archive. + ARCHIVE_EXTDATA_AND_BOSS_EXTDATA = 0x1234567B, ///< Ext data and BOSS ext data archive. + ARCHIVE_SYSTEM_SAVEDATA2 = 0x1234567C, ///< System save data archive. + ARCHIVE_NAND_RW = 0x1234567D, ///< Read-write NAND archive. + ARCHIVE_NAND_RO = 0x1234567E, ///< Read-only NAND archive. + ARCHIVE_NAND_RO_WRITE_ACCESS = 0x1234567F, ///< Read-only write access NAND archive. + ARCHIVE_SAVEDATA_AND_CONTENT = 0x2345678A, ///< User save data and ExeFS/RomFS archive. + ARCHIVE_SAVEDATA_AND_CONTENT2 = 0x2345678E, ///< User save data and ExeFS/RomFS archive (only ExeFS for fs:LDR). + ARCHIVE_NAND_CTR_FS = 0x567890AB, ///< NAND CTR FS archive. + ARCHIVE_TWL_PHOTO = 0x567890AC, ///< TWL PHOTO archive. + ARCHIVE_TWL_SOUND = 0x567890AD, ///< TWL SOUND archive. + ARCHIVE_NAND_TWL_FS = 0x567890AE, ///< NAND TWL FS archive. + ARCHIVE_NAND_W_FS = 0x567890AF, ///< NAND W FS archive. + ARCHIVE_GAMECARD_SAVEDATA = 0x567890B1, ///< Game card save data archive. + ARCHIVE_USER_SAVEDATA = 0x567890B2, ///< User save data archive. + ARCHIVE_DEMO_SAVEDATA = 0x567890B4, ///< Demo save data archive. +} FS_ArchiveID; + +/// Path types. +typedef enum +{ + PATH_INVALID = 0, ///< Invalid path. + PATH_EMPTY = 1, ///< Empty path. + PATH_BINARY = 2, ///< Binary path. Meaning is per-archive. + PATH_ASCII = 3, ///< ASCII text path. + PATH_UTF16 = 4, ///< UTF-16 text path. +} FS_PathType; + +/// Secure value slot. +typedef enum +{ + SECUREVALUE_SLOT_SD = 0x1000, ///< SD application. +} FS_SecureValueSlot; + +/// Card SPI baud rate. +typedef enum +{ + BAUDRATE_512KHZ = 0, ///< 512KHz. + BAUDRATE_1MHZ = 1, ///< 1MHz. + BAUDRATE_2MHZ = 2, ///< 2MHz. + BAUDRATE_4MHZ = 3, ///< 4MHz. + BAUDRATE_8MHZ = 4, ///< 8MHz. + BAUDRATE_16MHZ = 5, ///< 16MHz. +} FS_CardSpiBaudRate; + +/// Card SPI bus mode. +typedef enum +{ + BUSMODE_1BIT = 0, ///< 1-bit. + BUSMODE_4BIT = 1, ///< 4-bit. +} FS_CardSpiBusMode; + +/// Card SPI bus mode. +typedef enum +{ + SPECIALCONTENT_UPDATE = 1, ///< Update. + SPECIALCONTENT_MANUAL = 2, ///< Manual. + SPECIALCONTENT_DLP_CHILD = 3, ///< DLP child. +} FS_SpecialContentType; + +typedef enum +{ + CARD_CTR = 0, ///< CTR card. + CARD_TWL = 1, ///< TWL card. +} FS_CardType; + +/// FS control actions. +typedef enum +{ + FS_ACTION_UNKNOWN = 0, +} FS_Action; + +/// Archive control actions. +typedef enum +{ + ARCHIVE_ACTION_COMMIT_SAVE_DATA = 0, ///< Commits save data changes. No inputs/outputs. + ARCHIVE_ACTION_GET_TIMESTAMP = 1, ///< Retrieves a file's last-modified timestamp. In: "u16*, UTF-16 Path", Out: "u64, Time Stamp". + ARCHIVE_ACTION_UNKNOWN = 0x789D, //< Unknown action; calls FSPXI command 0x56. In: "FS_Path instance", Out: "u32[4], Unknown" +} FS_ArchiveAction; + +/// Secure save control actions. +typedef enum +{ + SECURESAVE_ACTION_DELETE = 0, ///< Deletes a save's secure value. In: "u64, ((SecureValueSlot << 32) | (TitleUniqueId << 8) | TitleVariation)", Out: "u8, Value Existed" + SECURESAVE_ACTION_FORMAT = 1, ///< Formats a save. No inputs/outputs. +} FS_SecureSaveAction; + +/// File control actions. +typedef enum +{ + FILE_ACTION_UNKNOWN = 0, +} FS_FileAction; + +/// Directory control actions. +typedef enum +{ + DIRECTORY_ACTION_UNKNOWN = 0, +} FS_DirectoryAction; + +/// Directory entry. +typedef struct +{ + u16 name[0x106]; ///< UTF-16 directory name. + char shortName[0x0A]; ///< File name. + char shortExt[0x04]; ///< File extension. + u8 valid; ///< Valid flag. (Always 1) + u8 reserved; ///< Reserved. + u32 attributes; ///< Attributes. + u64 fileSize; ///< File size. +} FS_DirectoryEntry; + +/// Archive resource information. +typedef struct +{ + u32 sectorSize; ///< Size of each sector, in bytes. + u32 clusterSize; ///< Size of each cluster, in bytes. + u32 totalClusters; ///< Total number of clusters. + u32 freeClusters; ///< Number of free clusters. +} FS_ArchiveResource; + +/// Program information. +typedef struct +{ + u64 programId; ///< Program ID. + FS_MediaType mediaType : 8; ///< Media type. + u8 padding[7]; ///< Padding. +} FS_ProgramInfo; + +/// Product information. +typedef struct +{ + char productCode[0x10]; ///< Product code. + char companyCode[0x2]; ///< Company code. + u16 remasterVersion; ///< Remaster version. +} FS_ProductInfo; + +/// Integrity verification seed. +typedef struct +{ + u8 aesCbcMac[0x10]; ///< AES-CBC MAC over a SHA256 hash, which hashes the first 0x110-bytes of the cleartext SEED. + u8 movableSed[0x120]; ///< The "nand/private/movable.sed", encrypted with AES-CTR using the above MAC for the counter. +} FS_IntegrityVerificationSeed; + +/// Ext save data information. +typedef struct PACKED +{ + FS_MediaType mediaType : 8; ///< Media type. + u8 unknown; ///< Unknown. + u16 reserved1; ///< Reserved. + u64 saveId; ///< Save ID. + u32 reserved2; ///< Reserved. +} FS_ExtSaveDataInfo; + +/// System save data information. +typedef struct +{ + FS_MediaType mediaType : 8; ///< Media type. + u8 unknown; ///< Unknown. + u16 reserved; ///< Reserved. + u32 saveId; ///< Save ID. +} FS_SystemSaveDataInfo; + +/// Device move context. +typedef struct +{ + u8 ivs[0x10]; ///< IVs. + u8 encryptParameter[0x10]; ///< Encrypt parameter. +} FS_DeviceMoveContext; + +/// Filesystem path data, detailing the specific target of an operation. +typedef struct +{ + FS_PathType type; ///< FS path type. + u32 size; ///< FS path size. + const void* data; ///< Pointer to FS path data. +} FS_Path; + +/// SDMC/NAND speed information +typedef struct +{ + bool highSpeedModeEnabled; ///< Whether or not High Speed Mode is enabled. + bool usesHighestClockRate; ///< Whether or not a clock divider of 2 is being used. + u16 sdClkCtrl; ///< The value of the SD_CLK_CTRL register. +} FS_SdMmcSpeedInfo; + +/// Filesystem archive handle, providing access to a filesystem's contents. +typedef u64 FS_Archive; + +/// Initializes FS. +Result fsInit(void); + +/// Exits FS. +void fsExit(void); + +/** + * @brief Sets the FSUSER session to use in the current thread. + * @param session The handle of the FSUSER session to use. + */ +void fsUseSession(Handle session); + +/// Disables the FSUSER session override in the current thread. +void fsEndUseSession(void); + +/** + * @brief Exempts an archive from using alternate FS session handles provided with @ref fsUseSession + * Instead, the archive will use the default FS session handle, opened with @ref srvGetSessionHandle + * @param archive Archive to exempt. + */ +void fsExemptFromSession(FS_Archive archive); + +/** + * @brief Unexempts an archive from using alternate FS session handles provided with @ref fsUseSession + * @param archive Archive to remove from the exemption list. + */ +void fsUnexemptFromSession(FS_Archive archive); + +/** + * @brief Creates an FS_Path instance. + * @param type Type of path. + * @param path Path to use. + * @return The created FS_Path instance. + */ +FS_Path fsMakePath(FS_PathType type, const void* path); + +/** + * @brief Gets the current FS session handle. + * @return The current FS session handle. + */ +Handle* fsGetSessionHandle(void); + +/** + * @brief Performs a control operation on the filesystem. + * @param action Action to perform. + * @param input Buffer to read input from. + * @param inputSize Size of the input. + * @param output Buffer to write output to. + * @param outputSize Size of the output. + */ +Result FSUSER_Control(FS_Action action, void* input, u32 inputSize, void* output, u32 outputSize); + +/** + * @brief Initializes a FSUSER session. + * @param session The handle of the FSUSER session to initialize. + */ +Result FSUSER_Initialize(Handle session); + +/** + * @brief Opens a file. + * @param out Pointer to output the file handle to. + * @param archive Archive containing the file. + * @param path Path of the file. + * @param openFlags Flags to open the file with. + * @param attributes Attributes of the file. + */ +Result FSUSER_OpenFile(Handle* out, FS_Archive archive, FS_Path path, u32 openFlags, u32 attributes); + +/** + * @brief Opens a file directly, bypassing the requirement of an opened archive handle. + * @param out Pointer to output the file handle to. + * @param archiveId ID of the archive containing the file. + * @param archivePath Path of the archive containing the file. + * @param filePath Path of the file. + * @param openFlags Flags to open the file with. + * @param attributes Attributes of the file. + */ +Result FSUSER_OpenFileDirectly(Handle* out, FS_ArchiveID archiveId, FS_Path archivePath, FS_Path filePath, u32 openFlags, u32 attributes); + +/** + * @brief Deletes a file. + * @param archive Archive containing the file. + * @param path Path of the file. + */ +Result FSUSER_DeleteFile(FS_Archive archive, FS_Path path); + +/** + * @brief Renames a file. + * @param srcArchive Archive containing the source file. + * @param srcPath Path of the source file. + * @param dstArchive Archive containing the destination file. + * @param dstPath Path of the destination file. + */ +Result FSUSER_RenameFile(FS_Archive srcArchive, FS_Path srcPath, FS_Archive dstArchive, FS_Path dstPath); + +/** + * @brief Deletes a directory, failing if it is not empty. + * @param archive Archive containing the directory. + * @param path Path of the directory. + */ +Result FSUSER_DeleteDirectory(FS_Archive archive, FS_Path path); + +/** + * @brief Deletes a directory, also deleting its contents. + * @param archive Archive containing the directory. + * @param path Path of the directory. + */ +Result FSUSER_DeleteDirectoryRecursively(FS_Archive archive, FS_Path path); + +/** + * @brief Creates a file. + * @param archive Archive to create the file in. + * @param path Path of the file. + * @param attributes Attributes of the file. + * @param fileSize Size of the file. + */ +Result FSUSER_CreateFile(FS_Archive archive, FS_Path path, u32 attributes, u64 fileSize); + +/** + * @brief Creates a directory + * @param archive Archive to create the directory in. + * @param path Path of the directory. + * @param attributes Attributes of the directory. + */ +Result FSUSER_CreateDirectory(FS_Archive archive, FS_Path path, u32 attributes); + +/** + * @brief Renames a directory. + * @param srcArchive Archive containing the source directory. + * @param srcPath Path of the source directory. + * @param dstArchive Archive containing the destination directory. + * @param dstPath Path of the destination directory. + */ +Result FSUSER_RenameDirectory(FS_Archive srcArchive, FS_Path srcPath, FS_Archive dstArchive, FS_Path dstPath); + +/** + * @brief Opens a directory. + * @param out Pointer to output the directory handle to. + * @param archive Archive containing the directory. + * @param path Path of the directory. + */ +Result FSUSER_OpenDirectory(Handle *out, FS_Archive archive, FS_Path path); + +/** + * @brief Opens an archive. + * @param archive Pointer to output the opened archive to. + * @param id ID of the archive. + * @param path Path of the archive. + */ +Result FSUSER_OpenArchive(FS_Archive* archive, FS_ArchiveID id, FS_Path path); + +/** + * @brief Performs a control operation on an archive. + * @param archive Archive to control. + * @param action Action to perform. + * @param input Buffer to read input from. + * @param inputSize Size of the input. + * @param output Buffer to write output to. + * @param outputSize Size of the output. + */ +Result FSUSER_ControlArchive(FS_Archive archive, FS_ArchiveAction action, void* input, u32 inputSize, void* output, u32 outputSize); + +/** + * @brief Closes an archive. + * @param archive Archive to close. + */ +Result FSUSER_CloseArchive(FS_Archive archive); + +/** + * @brief Gets the number of free bytes within an archive. + * @param freeBytes Pointer to output the free bytes to. + * @param archive Archive to check. + */ +Result FSUSER_GetFreeBytes(u64* freeBytes, FS_Archive archive); + +/** + * @brief Gets the inserted card type. + * @param type Pointer to output the card type to. + */ +Result FSUSER_GetCardType(FS_CardType* type); + +/** + * @brief Gets the SDMC archive resource information. + * @param archiveResource Pointer to output the archive resource information to. + */ +Result FSUSER_GetSdmcArchiveResource(FS_ArchiveResource* archiveResource); + +/** + * @brief Gets the NAND archive resource information. + * @param archiveResource Pointer to output the archive resource information to. + */ +Result FSUSER_GetNandArchiveResource(FS_ArchiveResource* archiveResource); + +/** + * @brief Gets the last SDMC fatfs error. + * @param error Pointer to output the error to. + */ +Result FSUSER_GetSdmcFatfsError(u32* error); + +/** + * @brief Gets whether an SD card is detected. + * @param detected Pointer to output the detection status to. + */ +Result FSUSER_IsSdmcDetected(bool *detected); + +/** + * @brief Gets whether the SD card is writable. + * @param writable Pointer to output the writable status to. + */ +Result FSUSER_IsSdmcWritable(bool *writable); + +/** + * @brief Gets the SDMC CID. + * @param out Pointer to output the CID to. + * @param length Length of the CID buffer. (should be 0x10) + */ +Result FSUSER_GetSdmcCid(u8* out, u32 length); + +/** + * @brief Gets the NAND CID. + * @param out Pointer to output the CID to. + * @param length Length of the CID buffer. (should be 0x10) + */ +Result FSUSER_GetNandCid(u8* out, u32 length); + +/** + * @brief Gets the SDMC speed info. + * @param speedInfo Pointer to output the speed info to. + */ +Result FSUSER_GetSdmcSpeedInfo(FS_SdMmcSpeedInfo *speedInfo); + +/** + * @brief Gets the NAND speed info. + * @param speedInfo Pointer to output the speed info to. + */ +Result FSUSER_GetNandSpeedInfo(FS_SdMmcSpeedInfo *speedInfo); + +/** + * @brief Gets the SDMC log. + * @param out Pointer to output the log to. + * @param length Length of the log buffer. + */ +Result FSUSER_GetSdmcLog(u8* out, u32 length); + +/** + * @brief Gets the NAND log. + * @param out Pointer to output the log to. + * @param length Length of the log buffer. + */ +Result FSUSER_GetNandLog(u8* out, u32 length); + +/// Clears the SDMC log. +Result FSUSER_ClearSdmcLog(void); + +/// Clears the NAND log. +Result FSUSER_ClearNandLog(void); + +/** + * @brief Gets whether a card is inserted. + * @param inserted Pointer to output the insertion status to. + */ +Result FSUSER_CardSlotIsInserted(bool* inserted); + +/** + * @brief Powers on the card slot. + * @param status Pointer to output the power status to. + */ +Result FSUSER_CardSlotPowerOn(bool* status); + +/** + * @brief Powers off the card slot. + * @param status Pointer to output the power status to. + */ +Result FSUSER_CardSlotPowerOff(bool* status); + +/** + * @brief Gets the card's power status. + * @param status Pointer to output the power status to. + */ +Result FSUSER_CardSlotGetCardIFPowerStatus(bool* status); + +/** + * @brief Executes a CARDNOR direct command. + * @param commandId ID of the command. + */ +Result FSUSER_CardNorDirectCommand(u8 commandId); + +/** + * @brief Executes a CARDNOR direct command with an address. + * @param commandId ID of the command. + * @param address Address to provide. + */ +Result FSUSER_CardNorDirectCommandWithAddress(u8 commandId, u32 address); + +/** + * @brief Executes a CARDNOR direct read. + * @param commandId ID of the command. + * @param size Size of the output buffer. + * @param output Output buffer. + */ +Result FSUSER_CardNorDirectRead(u8 commandId, u32 size, void* output); + +/** + * @brief Executes a CARDNOR direct read with an address. + * @param commandId ID of the command. + * @param address Address to provide. + * @param size Size of the output buffer. + * @param output Output buffer. + */ +Result FSUSER_CardNorDirectReadWithAddress(u8 commandId, u32 address, u32 size, void* output); + +/** + * @brief Executes a CARDNOR direct write. + * @param commandId ID of the command. + * @param size Size of the input buffer. + * @param output Input buffer. + */ +Result FSUSER_CardNorDirectWrite(u8 commandId, u32 size, const void* input); + +/** + * @brief Executes a CARDNOR direct write with an address. + * @param commandId ID of the command. + * @param address Address to provide. + * @param size Size of the input buffer. + * @param input Input buffer. + */ +Result FSUSER_CardNorDirectWriteWithAddress(u8 commandId, u32 address, u32 size, const void* input); + +/** + * @brief Executes a CARDNOR 4xIO direct read. + * @param commandId ID of the command. + * @param address Address to provide. + * @param size Size of the output buffer. + * @param output Output buffer. + */ +Result FSUSER_CardNorDirectRead_4xIO(u8 commandId, u32 address, u32 size, void* output); + +/** + * @brief Executes a CARDNOR direct CPU write without verify. + * @param address Address to provide. + * @param size Size of the input buffer. + * @param output Input buffer. + */ +Result FSUSER_CardNorDirectCpuWriteWithoutVerify(u32 address, u32 size, const void* input); + +/** + * @brief Executes a CARDNOR direct sector erase without verify. + * @param address Address to provide. + */ +Result FSUSER_CardNorDirectSectorEraseWithoutVerify(u32 address); + +/** + * @brief Gets a process's product info. + * @param info Pointer to output the product info to. + * @param processId ID of the process. + */ +Result FSUSER_GetProductInfo(FS_ProductInfo* info, u32 processId); + +/** + * @brief Gets a process's program launch info. + * @param info Pointer to output the program launch info to. + * @param processId ID of the process. + */ +Result FSUSER_GetProgramLaunchInfo(FS_ProgramInfo* info, u32 processId); + +/** + * @brief Sets the CARDSPI baud rate. + * @param baudRate Baud rate to set. + */ +Result FSUSER_SetCardSpiBaudRate(FS_CardSpiBaudRate baudRate); + +/** + * @brief Sets the CARDSPI bus mode. + * @param busMode Bus mode to set. + */ +Result FSUSER_SetCardSpiBusMode(FS_CardSpiBusMode busMode); + +/// Sends initialization info to ARM9. +Result FSUSER_SendInitializeInfoTo9(void); + +/** + * @brief Gets a special content's index. + * @param index Pointer to output the index to. + * @param mediaType Media type of the special content. + * @param programId Program ID owning the special content. + * @param type Type of special content. + */ +Result FSUSER_GetSpecialContentIndex(u16* index, FS_MediaType mediaType, u64 programId, FS_SpecialContentType type); + +/** + * @brief Gets the legacy ROM header of a program. + * @param mediaType Media type of the program. + * @param programId ID of the program. + * @param header Pointer to output the legacy ROM header to. (size = 0x3B4) + */ +Result FSUSER_GetLegacyRomHeader(FS_MediaType mediaType, u64 programId, void* header); + +/** + * @brief Gets the legacy banner data of a program. + * @param mediaType Media type of the program. + * @param programId ID of the program. + * @param header Pointer to output the legacy banner data to. (size = 0x23C0) + */ +Result FSUSER_GetLegacyBannerData(FS_MediaType mediaType, u64 programId, void* banner); + +/** + * @brief Checks a process's authority to access a save data archive. + * @param access Pointer to output the access status to. + * @param mediaType Media type of the save data. + * @param saveId ID of the save data. + * @param processId ID of the process to check. + */ +Result FSUSER_CheckAuthorityToAccessExtSaveData(bool* access, FS_MediaType mediaType, u64 saveId, u32 processId); + +/** + * @brief Queries the total quota size of a save data archive. + * @param quotaSize Pointer to output the quota size to. + * @param directories Number of directories. + * @param files Number of files. + * @param fileSizeCount Number of file sizes to provide. + * @param fileSizes File sizes to provide. + */ +Result FSUSER_QueryTotalQuotaSize(u64* quotaSize, u32 directories, u32 files, u32 fileSizeCount, u64* fileSizes); + +/** + * @brief Abnegates an access right. + * @param accessRight Access right to abnegate. + */ +Result FSUSER_AbnegateAccessRight(u32 accessRight); + +/// Deletes the 3DS SDMC root. +Result FSUSER_DeleteSdmcRoot(void); + +/// Deletes all ext save data on the NAND. +Result FSUSER_DeleteAllExtSaveDataOnNand(void); + +/// Initializes the CTR file system. +Result FSUSER_InitializeCtrFileSystem(void); + +/// Creates the FS seed. +Result FSUSER_CreateSeed(void); + +/** + * @brief Retrieves archive format info. + * @param totalSize Pointer to output the total size to. + * @param directories Pointer to output the number of directories to. + * @param files Pointer to output the number of files to. + * @param duplicateData Pointer to output whether to duplicate data to. + * @param archiveId ID of the archive. + * @param path Path of the archive. + */ +Result FSUSER_GetFormatInfo(u32* totalSize, u32* directories, u32* files, bool* duplicateData, FS_ArchiveID archiveId, FS_Path path); + +/** + * @brief Gets the legacy ROM header of a program. + * @param headerSize Size of the ROM header. + * @param mediaType Media type of the program. + * @param programId ID of the program. + * @param header Pointer to output the legacy ROM header to. + */ +Result FSUSER_GetLegacyRomHeader2(u32 headerSize, FS_MediaType mediaType, u64 programId, void* header); + +/** + * @brief Gets the CTR SDMC root path. + * @param out Pointer to output the root path to. + * @param length Length of the output buffer. + */ +Result FSUSER_GetSdmcCtrRootPath(u8* out, u32 length); + +/** + * @brief Gets an archive's resource information. + * @param archiveResource Pointer to output the archive resource information to. + * @param mediaType System media type to check. + */ +Result FSUSER_GetArchiveResource(FS_ArchiveResource* archiveResource, FS_SystemMediaType mediaType); + +/** + * @brief Exports the integrity verification seed. + * @param seed Pointer to output the seed to. + */ +Result FSUSER_ExportIntegrityVerificationSeed(FS_IntegrityVerificationSeed* seed); + +/** + * @brief Imports an integrity verification seed. + * @param seed Seed to import. + */ +Result FSUSER_ImportIntegrityVerificationSeed(FS_IntegrityVerificationSeed* seed); + +/** + * @brief Formats save data. + * @param archiveId ID of the save data archive. + * @param path Path of the save data. + * @param blocks Size of the save data in blocks. (512 bytes) + * @param directories Number of directories. + * @param files Number of files. + * @param directoryBuckets Directory hash tree bucket count. + * @param fileBuckets File hash tree bucket count. + * @param duplicateData Whether to store an internal duplicate of the data. + */ +Result FSUSER_FormatSaveData(FS_ArchiveID archiveId, FS_Path path, u32 blocks, u32 directories, u32 files, u32 directoryBuckets, u32 fileBuckets, bool duplicateData); + +/** + * @brief Gets the legacy sub banner data of a program. + * @param bannerSize Size of the banner. + * @param mediaType Media type of the program. + * @param programId ID of the program. + * @param header Pointer to output the legacy sub banner data to. + */ +Result FSUSER_GetLegacySubBannerData(u32 bannerSize, FS_MediaType mediaType, u64 programId, void* banner); + +/** + * @brief Hashes the given data and outputs a SHA256 hash. + * @param data Pointer to the data to be hashed. + * @param inputSize The size of the data. + * @param hash Hash output pointer. + */ +Result FSUSER_UpdateSha256Context(const void* data, u32 inputSize, u8* hash); + +/** + * @brief Reads from a special file. + * @param bytesRead Pointer to output the number of bytes read to. + * @param fileOffset Offset of the file. + * @param size Size of the buffer. + * @param data Buffer to read to. + */ +Result FSUSER_ReadSpecialFile(u32* bytesRead, u64 fileOffset, u32 size, void* data); + +/** + * @brief Gets the size of a special file. + * @param fileSize Pointer to output the size to. + */ +Result FSUSER_GetSpecialFileSize(u64* fileSize); + +/** + * @brief Creates ext save data. + * @param info Info of the save data. + * @param directories Number of directories. + * @param files Number of files. + * @param sizeLimit Size limit of the save data. + * @param smdhSize Size of the save data's SMDH data. + * @param smdh SMDH data. + */ +Result FSUSER_CreateExtSaveData(FS_ExtSaveDataInfo info, u32 directories, u32 files, u64 sizeLimit, u32 smdhSize, u8* smdh); + +/** + * @brief Deletes ext save data. + * @param info Info of the save data. + */ +Result FSUSER_DeleteExtSaveData(FS_ExtSaveDataInfo info); + +/** + * @brief Reads the SMDH icon of ext save data. + * @param bytesRead Pointer to output the number of bytes read to. + * @param info Info of the save data. + * @param smdhSize Size of the save data SMDH. + * @param smdh Pointer to output SMDH data to. + */ +Result FSUSER_ReadExtSaveDataIcon(u32* bytesRead, FS_ExtSaveDataInfo info, u32 smdhSize, u8* smdh); + +/** + * @brief Gets an ext data archive's block information. + * @param totalBlocks Pointer to output the total blocks to. + * @param freeBlocks Pointer to output the free blocks to. + * @param blockSize Pointer to output the block size to. + * @param info Info of the save data. + */ +Result FSUSER_GetExtDataBlockSize(u64* totalBlocks, u64* freeBlocks, u32* blockSize, FS_ExtSaveDataInfo info); + +/** + * @brief Enumerates ext save data. + * @param idsWritten Pointer to output the number of IDs written to. + * @param idsSize Size of the IDs buffer. + * @param mediaType Media type to enumerate over. + * @param idSize Size of each ID element. + * @param shared Whether to enumerate shared ext save data. + * @param ids Pointer to output IDs to. + */ +Result FSUSER_EnumerateExtSaveData(u32* idsWritten, u32 idsSize, FS_MediaType mediaType, u32 idSize, bool shared, u8* ids); + +/** + * @brief Creates system save data. + * @param info Info of the save data. + * @param totalSize Total size of the save data. + * @param blockSize Block size of the save data. (usually 0x1000) + * @param directories Number of directories. + * @param files Number of files. + * @param directoryBuckets Directory hash tree bucket count. + * @param fileBuckets File hash tree bucket count. + * @param duplicateData Whether to store an internal duplicate of the data. + */ +Result FSUSER_CreateSystemSaveData(FS_SystemSaveDataInfo info, u32 totalSize, u32 blockSize, u32 directories, u32 files, u32 directoryBuckets, u32 fileBuckets, bool duplicateData); + +/** + * @brief Deletes system save data. + * @param info Info of the save data. + */ +Result FSUSER_DeleteSystemSaveData(FS_SystemSaveDataInfo info); + +/** + * @brief Initiates a device move as the source device. + * @param context Pointer to output the context to. + */ +Result FSUSER_StartDeviceMoveAsSource(FS_DeviceMoveContext* context); + +/** + * @brief Initiates a device move as the destination device. + * @param context Context to use. + * @param clear Whether to clear the device's data first. + */ +Result FSUSER_StartDeviceMoveAsDestination(FS_DeviceMoveContext context, bool clear); + +/** + * @brief Sets an archive's priority. + * @param archive Archive to use. + * @param priority Priority to set. + */ +Result FSUSER_SetArchivePriority(FS_Archive archive, u32 priority); + +/** + * @brief Gets an archive's priority. + * @param priority Pointer to output the priority to. + * @param archive Archive to use. + */ +Result FSUSER_GetArchivePriority(u32* priority, FS_Archive archive); + +/** + * @brief Configures CTRCARD latency emulation. + * @param latency Latency to apply, in milliseconds. + * @param emulateEndurance Whether to emulate card endurance. + */ +Result FSUSER_SetCtrCardLatencyParameter(u64 latency, bool emulateEndurance); + +/** + * @brief Toggles cleaning up invalid save data. + * @param enable Whether to enable cleaning up invalid save data. + */ +Result FSUSER_SwitchCleanupInvalidSaveData(bool enable); + +/** + * @brief Enumerates system save data. + * @param idsWritten Pointer to output the number of IDs written to. + * @param idsSize Size of the IDs buffer. + * @param ids Pointer to output IDs to. + */ +Result FSUSER_EnumerateSystemSaveData(u32* idsWritten, u32 idsSize, u32* ids); + +/** + * @brief Initializes a FSUSER session with an SDK version. + * @param session The handle of the FSUSER session to initialize. + * @param version SDK version to initialize with. + */ +Result FSUSER_InitializeWithSdkVersion(Handle session, u32 version); + +/** + * @brief Sets the file system priority. + * @param priority Priority to set. + */ +Result FSUSER_SetPriority(u32 priority); + +/** + * @brief Gets the file system priority. + * @param priority Pointer to output the priority to. + */ +Result FSUSER_GetPriority(u32* priority); + +/** + * @brief Sets the save data secure value. + * @param value Secure value to set. + * @param slot Slot of the secure value. + * @param titleUniqueId Unique ID of the title. (default = 0) + * @param titleVariation Variation of the title. (default = 0) + */ +Result FSUSER_SetSaveDataSecureValue(u64 value, FS_SecureValueSlot slot, u32 titleUniqueId, u8 titleVariation); + +/** + * @brief Gets the save data secure value. + * @param exists Pointer to output whether the secure value exists to. + * @param value Pointer to output the secure value to. + * @param slot Slot of the secure value. + * @param titleUniqueId Unique ID of the title. (default = 0) + * @param titleVariation Variation of the title. (default = 0) + */ +Result FSUSER_GetSaveDataSecureValue(bool* exists, u64* value, FS_SecureValueSlot slot, u32 titleUniqueId, u8 titleVariation); + +/** + * @brief Performs a control operation on a secure save. + * @param action Action to perform. + * @param input Buffer to read input from. + * @param inputSize Size of the input. + * @param output Buffer to write output to. + * @param outputSize Size of the output. + */ +Result FSUSER_ControlSecureSave(FS_SecureSaveAction action, void* input, u32 inputSize, void* output, u32 outputSize); + +/** + * @brief Gets the media type of the current application. + * @param mediaType Pointer to output the media type to. + */ +Result FSUSER_GetMediaType(FS_MediaType* mediaType); + +/** + * @brief Performs a control operation on a file. + * @param handle Handle of the file. + * @param action Action to perform. + * @param input Buffer to read input from. + * @param inputSize Size of the input. + * @param output Buffer to write output to. + * @param outputSize Size of the output. + */ +Result FSFILE_Control(Handle handle, FS_FileAction action, void* input, u32 inputSize, void* output, u32 outputSize); + +/** + * @brief Opens a handle to a sub-section of a file. + * @param handle Handle of the file. + * @param subFile Pointer to output the sub-file to. + * @param offset Offset of the sub-section. + * @param size Size of the sub-section. + */ +Result FSFILE_OpenSubFile(Handle handle, Handle* subFile, u64 offset, u64 size); + +/** + * @brief Reads from a file. + * @param handle Handle of the file. + * @param bytesRead Pointer to output the number of bytes read to. + * @param offset Offset to read from. + * @param buffer Buffer to read to. + * @param size Size of the buffer. + */ +Result FSFILE_Read(Handle handle, u32* bytesRead, u64 offset, void* buffer, u32 size); + +/** + * @brief Writes to a file. + * @param handle Handle of the file. + * @param bytesWritten Pointer to output the number of bytes written to. + * @param offset Offset to write to. + * @param buffer Buffer to write from. + * @param size Size of the buffer. + * @param flags Flags to use when writing. + */ +Result FSFILE_Write(Handle handle, u32* bytesWritten, u64 offset, const void* buffer, u32 size, u32 flags); + +/** + * @brief Gets the size of a file. + * @param handle Handle of the file. + * @param size Pointer to output the size to. + */ +Result FSFILE_GetSize(Handle handle, u64* size); + +/** + * @brief Sets the size of a file. + * @param handle Handle of the file. + * @param size Size to set. + */ +Result FSFILE_SetSize(Handle handle, u64 size); + +/** + * @brief Gets the attributes of a file. + * @param handle Handle of the file. + * @param attributes Pointer to output the attributes to. + */ +Result FSFILE_GetAttributes(Handle handle, u32* attributes); + +/** + * @brief Sets the attributes of a file. + * @param handle Handle of the file. + * @param attributes Attributes to set. + */ +Result FSFILE_SetAttributes(Handle handle, u32 attributes); + +/** + * @brief Closes a file. + * @param handle Handle of the file. + */ +Result FSFILE_Close(Handle handle); + +/** + * @brief Flushes a file's contents. + * @param handle Handle of the file. + */ +Result FSFILE_Flush(Handle handle); + +/** + * @brief Sets a file's priority. + * @param handle Handle of the file. + * @param priority Priority to set. + */ +Result FSFILE_SetPriority(Handle handle, u32 priority); + +/** + * @brief Gets a file's priority. + * @param handle Handle of the file. + * @param priority Pointer to output the priority to. + */ +Result FSFILE_GetPriority(Handle handle, u32* priority); + +/** + * @brief Opens a duplicate handle to a file. + * @param handle Handle of the file. + * @param linkFile Pointer to output the link handle to. + */ +Result FSFILE_OpenLinkFile(Handle handle, Handle* linkFile); + +/** + * @brief Performs a control operation on a directory. + * @param handle Handle of the directory. + * @param action Action to perform. + * @param input Buffer to read input from. + * @param inputSize Size of the input. + * @param output Buffer to write output to. + * @param outputSize Size of the output. + */ +Result FSDIR_Control(Handle handle, FS_DirectoryAction action, void* input, u32 inputSize, void* output, u32 outputSize); + +/** + * @brief Reads one or more directory entries. + * @param handle Handle of the directory. + * @param entriesRead Pointer to output the number of entries read to. + * @param entryCount Number of entries to read. + * @param entryOut Pointer to output directory entries to. + */ +Result FSDIR_Read(Handle handle, u32* entriesRead, u32 entryCount, FS_DirectoryEntry* entries); + +/** + * @brief Closes a directory. + * @param handle Handle of the directory. + */ +Result FSDIR_Close(Handle handle); + +/** + * @brief Sets a directory's priority. + * @param handle Handle of the directory. + * @param priority Priority to set. + */ +Result FSDIR_SetPriority(Handle handle, u32 priority); + +/** + * @brief Gets a directory's priority. + * @param handle Handle of the directory. + * @param priority Pointer to output the priority to. + */ +Result FSDIR_GetPriority(Handle handle, u32* priority); diff --git a/libctru/include/3ds/services/fspxi.h b/libctru/include/3ds/services/fspxi.h new file mode 100644 index 0000000000..5644d1193a --- /dev/null +++ b/libctru/include/3ds/services/fspxi.h @@ -0,0 +1,613 @@ +/** + * @file fspxi.h + * @brief Service interface for PxiFS services. This is normally not accessible to userland apps. https://3dbrew.org/wiki/Filesystem_services_PXI + */ +#pragma once + +#include <3ds/services/fs.h> +#include <3ds/types.h> + +typedef u64 FSPXI_Archive; +typedef u64 FSPXI_File; +typedef u64 FSPXI_Directory; + +/** + * @brief Opens a file. + * @param out Pointer to output the file handle to. + * @param archive Archive containing the file. + * @param path Path of the file. + * @param flags Flags to open the file with. + * @param attributes Attributes of the file. + */ +Result FSPXI_OpenFile(Handle serviceHandle, FSPXI_File* out, FSPXI_Archive archive, FS_Path path, u32 flags, u32 attributes); + +/** + * @brief Deletes a file. + * @param archive Archive containing the file. + * @param path Path of the file. + */ +Result FSPXI_DeleteFile(Handle serviceHandle, FSPXI_Archive archive, FS_Path path); + +/** + * @brief Renames a file. + * @param srcArchive Archive containing the source file. + * @param srcPath Path of the source file. + * @param dstArchive Archive containing the destination file. + * @param dstPath Path of the destination file. + */ +Result FSPXI_RenameFile(Handle serviceHandle, FSPXI_Archive srcArchive, FS_Path srcPath, FSPXI_Archive dstArchive, FS_Path dstPath); + +/** + * @brief Deletes a directory. + * @param archive Archive containing the directory. + * @param path Path of the directory. + */ +Result FSPXI_DeleteDirectory(Handle serviceHandle, FSPXI_Archive archive, FS_Path path); + +/** + * @brief Creates a file. + * @param archive Archive to create the file in. + * @param path Path of the file. + * @param attributes Attributes of the file. + * @param size Size of the file. + */ +Result FSPXI_CreateFile(Handle serviceHandle, FSPXI_Archive archive, FS_Path path, u32 attributes, u64 fileSize); + +/** + * @brief Creates a directory. + * @param archive Archive to create the directory in. + * @param path Path of the directory. + * @param attributes Attributes of the directory. + */ +Result FSPXI_CreateDirectory(Handle serviceHandle, FSPXI_Archive archive, FS_Path path, u32 attributes); + +/** + * @brief Renames a directory. + * @param srcArchive Archive containing the source directory. + * @param srcPath Path of the source directory. + * @param dstArchive Archive containing the destination directory. + * @param dstPath Path of the destination directory. + */ +Result FSPXI_RenameDirectory(Handle serviceHandle, FSPXI_Archive srcArchive, FS_Path srcPath, FSPXI_Archive dstArchive, FS_Path dstPath); + +/** + * @brief Opens a directory. + * @param out Pointer to output the directory handle to. + * @param archive Archive containing the directory. + * @param path Path of the directory. + */ +Result FSPXI_OpenDirectory(Handle serviceHandle, FSPXI_Directory* out, FSPXI_Archive archive, FS_Path path); + +/** + * @brief Reads from a file. + * @param file File to read from. + * @param bytesRead Pointer to output the number of read bytes to. + * @param offset Offset to read from. + * @param buffer Buffer to read to. + * @param size Size of the buffer. + */ +Result FSPXI_ReadFile(Handle serviceHandle, FSPXI_File file, u32* bytesRead, u64 offset, void* buffer, u32 size); + +/** + * @brief Calculate SHA256 of a file. + * @param file File to calculate the hash of. + * @param buffer Buffer to output the hash to. + * @param size Size of the buffer. + */ +Result FSPXI_CalculateFileHashSHA256(Handle serviceHandle, FSPXI_File file, void* buffer, u32 size); + +/** + * @brief Writes to a file. + * @param file File to write to. + * @param bytesWritten Pointer to output the number of bytes written to. + * @param offset Offset to write to. + * @param buffer Buffer to write from. + * @param size Size of the buffer. + * @param flags Flags to use when writing. + */ +Result FSPXI_WriteFile(Handle serviceHandle, FSPXI_File file, u32* bytesWritten, u64 offset, const void* buffer, u32 size, u32 flags); + +/** + * @brief Calculates the MAC used in a DISA/DIFF header? + * @param file Unsure + * @param inBuffer 0x100-byte DISA/DIFF input buffer. + * @param inSize Size of inBuffer. + * @param outBuffer Buffer to write MAC to. + * @param outSize Size of outBuffer. + */ +Result FSPXI_CalcSavegameMAC(Handle serviceHandle, FSPXI_File file, const void* inBuffer, u32 inSize, void* outBuffer, u32 outSize); + +/** + * @brief Get size of a file + * @param file File to get the size of. + * @param size Pointer to output size to. + */ +Result FSPXI_GetFileSize(Handle serviceHandle, FSPXI_File file, u64* size); + +/** + * @brief Set size of a file + * @param file File to set the size of + * @param size Size to set the file to + */ +Result FSPXI_SetFileSize(Handle serviceHandle, FSPXI_File file, u64 size); + +/** + * @brief Close a file + * @param file File to close + */ +Result FSPXI_CloseFile(Handle serviceHandle, FSPXI_File file); + +/** + * @brief Reads one or more directory entries. + * @param directory Directory to read from. + * @param entriesRead Pointer to output the number of entries read to. + * @param entryCount Number of entries to read. + * @param entryOut Pointer to output directory entries to. + */ +Result FSPXI_ReadDirectory(Handle serviceHandle, FSPXI_Directory directory, u32* entriesRead, u32 entryCount, FS_DirectoryEntry* entries); + +/** + * @brief Close a directory + * @param directory Directory to close. + */ +Result FSPXI_CloseDirectory(Handle serviceHandle, FSPXI_Directory directory); + +/** + * @brief Opens an archive. + * @param archive Pointer to output the opened archive to. + * @param id ID of the archive. + * @param path Path of the archive. + */ +Result FSPXI_OpenArchive(Handle serviceHandle, FSPXI_Archive* archive, FS_ArchiveID archiveID, FS_Path path); + +/** + * @brief Checks if the archive contains a file at path. + * @param archive Archive to check. + * @param out Pointer to output existence to. + * @param path Path to check for file + */ +Result FSPXI_HasFile(Handle serviceHandle, FSPXI_Archive archive, bool* out, FS_Path path); + +/** + * @brief Checks if the archive contains a directory at path. + * @param archive Archive to check. + * @param out Pointer to output existence to. + * @param path Path to check for directory + */ +Result FSPXI_HasDirectory(Handle serviceHandle, FSPXI_Archive archive, bool* out, FS_Path path); + +/** + * @brief Commits an archive's save data. + * @param archive Archive to commit. + * @param id Archive action sent by FSUSER_ControlArchive. Must not be 0 or 0x789D + * @remark Unsure why id is sent. This appears to be the default action for FSUSER_ControlArchive, with every action other than 0 and 0x789D being sent to this command. + */ +Result FSPXI_CommitSaveData(Handle serviceHandle, FSPXI_Archive archive, u32 id); + +/** + * @brief Close an archive + * @param archive Archive to close. + */ +Result FSPXI_CloseArchive(Handle serviceHandle, FSPXI_Archive archive); + +/** + * @brief Unknown 0x17. Appears to be an "is archive handle valid" command? + * @param archive Archive handle to check validity of. + * @param out Pointer to output validity to. + */ +Result FSPXI_Unknown0x17(Handle serviceHandle, FSPXI_Archive archive, bool* out); + +/** + * @brief Gets the inserted card type. + * @param out Pointer to output the card type to. + */ +Result FSPXI_GetCardType(Handle serviceHandle, FS_CardType* out); + +/** + * @brief Gets the SDMC archive resource information. + * @param out Pointer to output the archive resource information to. + */ +Result FSPXI_GetSdmcArchiveResource(Handle serviceHandle, FS_ArchiveResource* out); + +/** + * @brief Gets the NAND archive resource information. + * @param out Pointer to output the archive resource information to. + */ +Result FSPXI_GetNandArchiveResource(Handle serviceHandle, FS_ArchiveResource* out); + +/** + * @brief Gets the error code from the SDMC FatFS driver + * @param out Pointer to output the error code to + */ +Result FSPXI_GetSdmcFatFsError(Handle serviceHandle, u32* out); + +/** + * @brief Gets whether PXIFS0 detects the SD + * @param out Pointer to output the detection status to + */ +Result FSPXI_IsSdmcDetected(Handle serviceHandle, bool* out); + +/** + * @brief Gets whether PXIFS0 can write to the SD + * @param out Pointer to output the writable status to + */ +Result FSPXI_IsSdmcWritable(Handle serviceHandle, bool* out); + +/** + * @brief Gets the SDMC CID + * @param out Buffer to output the CID to. + * @param size Size of buffer. + */ +Result FSPXI_GetSdmcCid(Handle serviceHandle, void* out, u32 size); + +/** + * @brief Gets the NAND CID + * @param out Buffer to output the CID to. + * @param size Size of buffer. + */ +Result FSPXI_GetNandCid(Handle serviceHandle, void* out, u32 size); + +/** + * @brief Gets the SDMC speed info + * @param out Buffer to output the speed info to. + */ +Result FSPXI_GetSdmcSpeedInfo(Handle serviceHandle, FS_SdMmcSpeedInfo* out); + +/** + * @brief Gets the NAND speed info + * @param out Buffer to output the speed info to. + */ +Result FSPXI_GetNandSpeedInfo(Handle serviceHandle, FS_SdMmcSpeedInfo* out); + +/** + * @brief Gets the SDMC log + * @param out Buffer to output the log to. + * @param size Size of buffer. + */ +Result FSPXI_GetSdmcLog(Handle serviceHandle, void* out, u32 size); + +/** + * @brief Gets the NAND log + * @param out Buffer to output the log to. + * @param size Size of buffer. + */ +Result FSPXI_GetNandLog(Handle serviceHandle, void* out, u32 size); + +/// Clears the SDMC log +Result FSPXI_ClearSdmcLog(Handle serviceHandle); + +/// Clears the NAND log +Result FSPXI_ClearNandLog(Handle serviceHandle); + +/** + * @brief Gets whether a card is inserted. + * @param inserted Pointer to output the insertion status to. + */ +Result FSPXI_CardSlotIsInserted(Handle serviceHandle, bool* inserted); + +/** + * @brief Powers on the card slot. + * @param status Pointer to output the power status to. + */ +Result FSPXI_CardSlotPowerOn(Handle serviceHandle, bool* status); + +/** + * @brief Powers off the card slot. + * @param status Pointer to output the power status to. + */ +Result FSPXI_CardSlotPowerOff(Handle serviceHandle, bool* status); + +/** + * @brief Gets the card's power status. + * @param status Pointer to output the power status to. + */ +Result FSPXI_CardSlotGetCardIFPowerStatus(Handle serviceHandle, bool* status); + +/** + * @brief Executes a CARDNOR direct command. + * @param commandId ID of the command. + */ +Result FSPXI_CardNorDirectCommand(Handle serviceHandle, u8 commandId); + +/** + * @brief Executes a CARDNOR direct command with an address. + * @param commandId ID of the command. + * @param address Address to provide. + */ +Result FSPXI_CardNorDirectCommandWithAddress(Handle serviceHandle, u8 commandId, u32 address); + +/** + * @brief Executes a CARDNOR direct read. + * @param commandId ID of the command. + * @param size Size of the output buffer. + * @param output Output buffer. + */ +Result FSPXI_CardNorDirectRead(Handle serviceHandle, u8 commandId, u32 size, void* output); + +/** + * @brief Executes a CARDNOR direct read with an address. + * @param commandId ID of the command. + * @param address Address to provide. + * @param size Size of the output buffer. + * @param output Output buffer. + */ +Result FSPXI_CardNorDirectReadWithAddress(Handle serviceHandle, u8 commandId, u32 address, u32 size, void* output); + +/** + * @brief Executes a CARDNOR direct write. + * @param commandId ID of the command. + * @param size Size of the input buffer. + * @param output Input buffer. + * @remark Stubbed in latest firmware, since ?.?.? + */ +Result FSPXI_CardNorDirectWrite(Handle serviceHandle, u8 commandId, u32 size, const void* input); + +/** + * @brief Executes a CARDNOR direct write with an address. + * @param commandId ID of the command. + * @param address Address to provide. + * @param size Size of the input buffer. + * @param input Input buffer. + */ +Result FSPXI_CardNorDirectWriteWithAddress(Handle serviceHandle, u8 commandId, u32 address, u32 size, const void* input); + +/** + * @brief Executes a CARDNOR 4xIO direct read. + * @param commandId ID of the command. + * @param address Address to provide. + * @param size Size of the output buffer. + * @param output Output buffer. + */ +Result FSPXI_CardNorDirectRead_4xIO(Handle serviceHandle, u8 commandId, u32 address, u32 size, void* output); + +/** + * @brief Executes a CARDNOR direct CPU write without verify. + * @param address Address to provide. + * @param size Size of the input buffer. + * @param output Input buffer. + */ +Result FSPXI_CardNorDirectCpuWriteWithoutVerify(Handle serviceHandle, u32 address, u32 size, const void* input); + +/** + * @brief Executes a CARDNOR direct sector erase without verify. + * @param address Address to provide. + */ +Result FSPXI_CardNorDirectSectorEraseWithoutVerify(Handle serviceHandle, u32 address); + +/** + * @brief Gets an NCCH's product info + * @param info Pointer to output the product info to. + * @param archive Open NCCH content archive + */ +Result FSPXI_GetProductInfo(Handle serviceHandle, FS_ProductInfo* info, FSPXI_Archive archive); + +/** + * @brief Sets the CARDSPI baud rate. + * @param baudRate Baud rate to set. + */ +Result FSPXI_SetCardSpiBaudrate(Handle serviceHandle, FS_CardSpiBaudRate baudRate); + +/** + * @brief Sets the CARDSPI bus mode. + * @param busMode Bus mode to set. + */ +Result FSPXI_SetCardSpiBusMode(Handle serviceHandle, FS_CardSpiBusMode busMode); + +/** + * @brief Sends initialization info to ARM9 + * @param unk FS sends *(0x1FF81086) + */ +Result FSPXI_SendInitializeInfoTo9(Handle serviceHandle, u8 unk); + +/** + * @brief Creates ext save data. + * @param info Info of the save data. + */ +Result FSPXI_CreateExtSaveData(Handle serviceHandle, FS_ExtSaveDataInfo info); + +/** + * @brief Deletes ext save data. + * @param info Info of the save data. + */ +Result FSPXI_DeleteExtSaveData(Handle serviceHandle, FS_ExtSaveDataInfo info); + +/** + * @brief Enumerates ext save data. + * @param idsWritten Pointer to output the number of IDs written to. + * @param idsSize Size of the IDs buffer. + * @param mediaType Media type to enumerate over. + * @param idSize Size of each ID element. + * @param shared Whether to enumerate shared ext save data. + * @param ids Pointer to output IDs to. + */ +Result FSPXI_EnumerateExtSaveData(Handle serviceHandle, u32* idsWritten, u32 idsSize, FS_MediaType mediaType, u32 idSize, bool shared, u8* ids); + +/** + * @brief Gets a special content's index. + * @param index Pointer to output the index to. + * @param mediaType Media type of the special content. + * @param programId Program ID owning the special content. + * @param type Type of special content. + */ +Result FSPXI_GetSpecialContentIndex(Handle serviceHandle, u16* index, FS_MediaType mediaType, u64 programId, FS_SpecialContentType type); + +/** + * @brief Gets the legacy ROM header of a program. + * @param mediaType Media type of the program. + * @param programId ID of the program. + * @param header Pointer to output the legacy ROM header to. (size = 0x3B4) + */ +Result FSPXI_GetLegacyRomHeader(Handle serviceHandle, FS_MediaType mediaType, u64 programId, void* header); + +/** + * @brief Gets the legacy banner data of a program. + * @param mediaType Media type of the program. + * @param programId ID of the program. + * @param banner Pointer to output the legacy banner data to. (size = 0x23C0) + * @param unk Unknown. Always 1? + */ +Result FSPXI_GetLegacyBannerData(Handle serviceHandle, FS_MediaType mediaType, u64 programId, void* banner, u8 unk); + +/** + * @brief Formats the CARDNOR device. + * @param unk Unknown. Transaction? + */ +Result FSPXI_FormatCardNorDevice(Handle serviceHandle, u32 unk); + +/// Deletes the 3DS SDMC root. +Result FSPXI_DeleteSdmcRoot(Handle serviceHandle); + +/// Deletes all ext save data on the NAND. +Result FSPXI_DeleteAllExtSaveDataOnNand(Handle serviceHandle); + +/// Initializes the CTR file system. +Result FSPXI_InitializeCtrFilesystem(Handle serviceHandle); + +/// Creates the FS seed. +Result FSPXI_CreateSeed(Handle serviceHandle); + +/** + * @brief Gets the CTR SDMC root path. + * @param out Pointer to output the root path to. + * @param length Length of the output buffer in bytes. + */ +Result FSPXI_GetSdmcCtrRootPath(Handle serviceHandle, u16* out, u32 length); + +/** + * @brief Gets an archive's resource information. + * @param archiveResource Pointer to output the archive resource information to. + * @param mediaType System media type to check. + */ +Result FSPXI_GetArchiveResource(Handle serviceHandle, FS_ArchiveResource* archiveResource, FS_SystemMediaType mediaType); + +/** + * @brief Exports the integrity verification seed. + * @param seed Pointer to output the seed to. + */ +Result FSPXI_ExportIntegrityVerificationSeed(Handle serviceHandle, FS_IntegrityVerificationSeed* seed); + +/** + * @brief Imports an integrity verification seed. + * @param seed Seed to import. + */ +Result FSPXI_ImportIntegrityVerificationSeed(Handle serviceHandle, const FS_IntegrityVerificationSeed* seed); + +/** + * @brief Gets the legacy sub banner data of a program. + * @param bannerSize Size of the banner. + * @param mediaType Media type of the program. + * @param programId ID of the program. + * @param header Pointer to output the legacy sub banner data to. + */ +Result FSPXI_GetLegacySubBannerData(Handle serviceHandle, u32 bannerSize, FS_MediaType mediaType, u64 programId, void* banner); + +/** + * @brief Generates random bytes. Uses same code as PSPXI_GenerateRandomBytes + * @param buf Buffer to output random bytes to. + * @param size Size of buffer. + */ +Result FSPXI_GenerateRandomBytes(Handle serviceHandle, void* buffer, u32 size); + +/** + * @brief Gets the last modified time of a file in an archive. + * @param archive The archive that contains the file. + * @param out The pointer to write the timestamp to. + * @param path The UTF-16 path of the file. + * @param size The size of the path. + */ +Result FSPXI_GetFileLastModified(Handle serviceHandle, FSPXI_Archive archive, u64* out, const u16* path, u32 size); + +/** + * @brief Reads from a special file. + * @param bytesRead Pointer to output the number of bytes read to. + * @param fileOffset Offset of the file. + * @param size Size of the buffer. + * @param data Buffer to read to. + */ +Result FSPXI_ReadSpecialFile(Handle serviceHandle, u32* bytesRead, u64 fileOffset, u32 size, void* data); + +/** + * @brief Gets the size of a special file. + * @param fileSize Pointer to output the size to. + */ +Result FSPXI_GetSpecialFileSize(Handle serviceHandle, u64* fileSize); + +/** + * @brief Initiates a device move as the source device. + * @param context Pointer to output the context to. + */ +Result FSPXI_StartDeviceMoveAsSource(Handle serviceHandle, FS_DeviceMoveContext* context); + +/** + * @brief Initiates a device move as the destination device. + * @param context Context to use. + * @param clear Whether to clear the device's data first. + */ +Result FSPXI_StartDeviceMoveAsDestination(Handle serviceHandle, FS_DeviceMoveContext context, bool clear); + +/** + * @brief Reads data and stores SHA256 hashes of blocks + * @param file File to read from. + * @param bytesRead Pointer to output the number of read bytes to. + * @param offset Offset to read from. + * @param readBuffer Pointer to store read data in. + * @param readBufferSize Size of readBuffer. + * @param hashtable Pointer to store SHA256 hashes in. + * @param hashtableSize Size of hashtable. + * @param unk Unknown. Always 0x00001000? Possibly block size? + */ +Result FSPXI_ReadFileSHA256(Handle serviceHandle, FSPXI_File file, u32* bytesRead, u64 offset, void* readBuffer, u32 readBufferSize, void* hashtable, u32 hashtableSize, u32 unk); + +/** + * @brief Assumedly writes data and stores SHA256 hashes of blocks + * @param file File to write to. + * @param bytesWritten Pointer to output the number of written bytes to. + * @param offset Offset to write to. + * @param writeBuffer Buffer to write from. + * @param writeBufferSize Size of writeBuffer. + * @param hashtable Pointer to store SHA256 hashes in. + * @param hashtableSize Size of hashtable + * @param unk1 Unknown. Might match with ReadFileSHA256's unknown? + * @param unk2 Unknown. Might match with ReadFileSHA256's unknown? + */ +Result FSPXI_WriteFileSHA256(Handle serviceHandle, FSPXI_File file, u32* bytesWritten, u64 offset, const void* writeBuffer, u32 writeBufferSize, void* hashtable, u32 hashtableSize, u32 unk1, u32 unk2); + +/** + * @brief Configures CTRCARD latency emulation. + * @param latency Latency to apply. + */ +Result FSPXI_SetCtrCardLatencyParameter(Handle serviceHandle, u64 latency); + +/** + * @brief Sets the file system priority. + * @param priority Priority to set. + */ +Result FSPXI_SetPriority(Handle serviceHandle, u32 priority); + +/** + * @brief Toggles cleaning up invalid save data. + * @param enable Whether to enable cleaning up invalid save data. + */ +Result FSPXI_SwitchCleanupInvalidSaveData(Handle serviceHandle, bool enable); + +/** + * @brief Enumerates system save data. + * @param idsWritten Pointer to output the number of IDs written to. + * @param idsSize Size of the IDs buffer. + * @param ids Pointer to output IDs to. + */ +Result FSPXI_EnumerateSystemSaveData(Handle serviceHandle, u32* idsWritten, u32 idsSize, u32* ids); + +/** + * @brief Reads the NAND report. + * @param unk Unknown + * @param buffer Buffer to write the report to. + * @param size Size of buffer + */ +Result FSPXI_ReadNandReport(Handle serviceHandle, void* buffer, u32 size, u32 unk); + +/** + * @brief Unknown command 0x56 + * @remark Called by FSUSER_ControlArchive with ArchiveAction 0x789D + */ +Result FSPXI_Unknown0x56(Handle serviceHandle, u32 out[4], FS_Archive archive, FS_Path path); diff --git a/libctru/include/3ds/services/fsreg.h b/libctru/include/3ds/services/fsreg.h new file mode 100644 index 0000000000..5e81d8008f --- /dev/null +++ b/libctru/include/3ds/services/fsreg.h @@ -0,0 +1,63 @@ +/** + * @file fsReg.h + * @brief Filesystem registry service + */ + +#pragma once + +#include <3ds/exheader.h> +#include <3ds/services/fs.h> + +/// Initializes fs:REG. +Result fsRegInit(void); + +/// Exits fs:REG. +void fsRegExit(void); + +/** + * @brief Gets the current fs:REG session handle. + * @return The current fs:REG session handle. + */ +Handle *fsRegGetSessionHandle(void); + +/** + * @brief Registers a program's storage information. + * @param pid The Process ID of the program. + * @param programHandle The program handle. + * @param programInfo Information about the program. + * @param storageInfo Storage information to register. + */ +Result FSREG_Register(u32 pid, u64 programHandle, const FS_ProgramInfo *programInfo, const ExHeader_Arm11StorageInfo *storageInfo); + +/** + * @brief Unregisters a program's storage information. + * @param pid The Process ID of the program. + */ +Result FSREG_Unregister(u32 pid); + +/** + * @brief Retrives the exheader information set(s) (SCI+ACI) about a program. + * @param exheaderInfos[out] Pointer to the output exheader information set(s). + * @param maxNumEntries The maximum number of entries. + * @param programHandle The program handle. + */ +Result FSREG_GetProgramInfo(ExHeader_Info *exheaderInfos, u32 maxNumEntries, u64 programHandle); + +/** + * @brief Loads a program. + * @param programHandle[out] Pointer to the output the program handle to. + * @param programInfo Information about the program to load. + */ +Result FSREG_LoadProgram(u64 *programHandle, const FS_ProgramInfo *programInfo); + +/** + * @brief Unloads a program. + * @param programHandle The program handle. + */ +Result FSREG_UnloadProgram(u64 programHandle); + +/** + * @brief Checks if a program has been loaded by fs:REG. + * @param programHandle The program handle. + */ +Result FSREG_CheckHostLoadId(u64 programHandle); diff --git a/libctru/include/3ds/services/gspgpu.h b/libctru/include/3ds/services/gspgpu.h new file mode 100644 index 0000000000..29ca298afa --- /dev/null +++ b/libctru/include/3ds/services/gspgpu.h @@ -0,0 +1,264 @@ +/** + * @file gspgpu.h + * @brief GSPGPU service. + */ +#pragma once + +#define GSP_SCREEN_TOP 0 ///< ID of the top screen. +#define GSP_SCREEN_BOTTOM 1 ///< ID of the bottom screen. +#define GSP_SCREEN_WIDTH 240 ///< Width of the top/bottom screens. +#define GSP_SCREEN_HEIGHT_TOP 400 ///< Height of the top screen. +#define GSP_SCREEN_HEIGHT_TOP_2X 800 ///< Height of the top screen (2x). +#define GSP_SCREEN_HEIGHT_BOTTOM 320 ///< Height of the bottom screen. + +/// Framebuffer information. +typedef struct +{ + u32 active_framebuf; ///< Active framebuffer. (0 = first, 1 = second) + u32 *framebuf0_vaddr; ///< Framebuffer virtual address, for the main screen this is the 3D left framebuffer. + u32 *framebuf1_vaddr; ///< For the main screen: 3D right framebuffer address. + u32 framebuf_widthbytesize; ///< Value for 0x1EF00X90, controls framebuffer width. + u32 format; ///< Framebuffer format, this u16 is written to the low u16 for LCD register 0x1EF00X70. + u32 framebuf_dispselect; ///< Value for 0x1EF00X78, controls which framebuffer is displayed. + u32 unk; ///< Unknown. +} GSPGPU_FramebufferInfo; + +/// Framebuffer format. +typedef enum +{ + GSP_RGBA8_OES=0, ///< RGBA8. (4 bytes) + GSP_BGR8_OES=1, ///< BGR8. (3 bytes) + GSP_RGB565_OES=2, ///< RGB565. (2 bytes) + GSP_RGB5_A1_OES=3, ///< RGB5A1. (2 bytes) + GSP_RGBA4_OES=4 ///< RGBA4. (2 bytes) +} GSPGPU_FramebufferFormat; + +/// Capture info entry. +typedef struct +{ + u32 *framebuf0_vaddr; ///< Left framebuffer. + u32 *framebuf1_vaddr; ///< Right framebuffer. + u32 format; ///< Framebuffer format. + u32 framebuf_widthbytesize; ///< Framebuffer pitch. +} GSPGPU_CaptureInfoEntry; + +/// Capture info. +typedef struct +{ + GSPGPU_CaptureInfoEntry screencapture[2]; ///< Capture info entries, one for each screen. +} GSPGPU_CaptureInfo; + +/// GSPGPU events. +typedef enum +{ + GSPGPU_EVENT_PSC0 = 0, ///< Memory fill completed. + GSPGPU_EVENT_PSC1, ///< TODO + GSPGPU_EVENT_VBlank0, ///< TODO + GSPGPU_EVENT_VBlank1, ///< TODO + GSPGPU_EVENT_PPF, ///< Display transfer finished. + GSPGPU_EVENT_P3D, ///< Command list processing finished. + GSPGPU_EVENT_DMA, ///< TODO + + GSPGPU_EVENT_MAX, ///< Used to know how many events there are. +} GSPGPU_Event; + +/** + * @brief Gets the number of bytes per pixel for the specified format. + * @param format See \ref GSPGPU_FramebufferFormat. + * @return Bytes per pixel. + */ +static inline unsigned gspGetBytesPerPixel(GSPGPU_FramebufferFormat format) +{ + switch (format) + { + case GSP_RGBA8_OES: + return 4; + default: + case GSP_BGR8_OES: + return 3; + case GSP_RGB565_OES: + case GSP_RGB5_A1_OES: + case GSP_RGBA4_OES: + return 2; + } +} + +/// Initializes GSPGPU. +Result gspInit(void); + +/// Exits GSPGPU. +void gspExit(void); + +/// Returns true if the application currently has GPU rights. +bool gspHasGpuRight(void); + +/** + * @brief Presents a buffer to the specified screen. + * @param screen Screen ID (see \ref GSP_SCREEN_TOP and \ref GSP_SCREEN_BOTTOM) + * @param swap Specifies which set of framebuffer registers to configure and activate (0 or 1) + * @param fb_a Pointer to the framebuffer (in stereo mode: left eye) + * @param fb_b Pointer to the secondary framebuffer (only used in stereo mode for the right eye, otherwise pass the same as fb_a) + * @param stride Stride in bytes between scanlines + * @param mode Mode configuration to be written to LCD register + * @return true if a buffer had already been presented to the screen but not processed yet by GSP, false otherwise. + * @note The most recently presented buffer is processed and configured during the specified screen's next VBlank event. + */ +void gspPresentBuffer(unsigned screen, unsigned swap, const void* fb_a, const void* fb_b, u32 stride, u32 mode); + +/** + * @brief Returns true if a prior \ref gspPresentBuffer command is still pending to be processed by GSP. + * @param screen Screen ID (see \ref GSP_SCREEN_TOP and \ref GSP_SCREEN_BOTTOM) + */ +bool gspIsPresentPending(unsigned screen); + +/** + * @brief Configures a callback to run when a GSPGPU event occurs. + * @param id ID of the event. + * @param cb Callback to run. + * @param data Data to be passed to the callback. + * @param oneShot When true, the callback is only executed once. When false, the callback is executed every time the event occurs. + */ +void gspSetEventCallback(GSPGPU_Event id, ThreadFunc cb, void* data, bool oneShot); + +/** + * @brief Waits for a GSPGPU event to occur. + * @param id ID of the event. + * @param nextEvent Whether to discard the current event and wait for the next event. + */ +void gspWaitForEvent(GSPGPU_Event id, bool nextEvent); + +/** + * @brief Waits for any GSPGPU event to occur. + * @return The ID of the event that occurred. + * + * The function returns immediately if there are unprocessed events at the time of call. + */ +GSPGPU_Event gspWaitForAnyEvent(void); + +/// Waits for PSC0 +#define gspWaitForPSC0() gspWaitForEvent(GSPGPU_EVENT_PSC0, false) + +/// Waits for PSC1 +#define gspWaitForPSC1() gspWaitForEvent(GSPGPU_EVENT_PSC1, false) + +/// Waits for VBlank. +#define gspWaitForVBlank() gspWaitForVBlank0() + +/// Waits for VBlank0. +#define gspWaitForVBlank0() gspWaitForEvent(GSPGPU_EVENT_VBlank0, true) + +/// Waits for VBlank1. +#define gspWaitForVBlank1() gspWaitForEvent(GSPGPU_EVENT_VBlank1, true) + +/// Waits for PPF. +#define gspWaitForPPF() gspWaitForEvent(GSPGPU_EVENT_PPF, false) + +/// Waits for P3D. +#define gspWaitForP3D() gspWaitForEvent(GSPGPU_EVENT_P3D, false) + +/// Waits for DMA. +#define gspWaitForDMA() gspWaitForEvent(GSPGPU_EVENT_DMA, false) + +/** + * @brief Submits a GX command. + * @param gxCommand GX command to execute. + */ +Result gspSubmitGxCommand(const u32 gxCommand[0x8]); + +/** + * @brief Acquires GPU rights. + * @param flags Flags to acquire with. + */ +Result GSPGPU_AcquireRight(u8 flags); + +/// Releases GPU rights. +Result GSPGPU_ReleaseRight(void); + +/** + * @brief Retrieves display capture info. + * @param captureinfo Pointer to output capture info to. + */ +Result GSPGPU_ImportDisplayCaptureInfo(GSPGPU_CaptureInfo* captureinfo); + +/// Saves the VRAM sys area. +Result GSPGPU_SaveVramSysArea(void); + +/// Resets the GPU +Result GSPGPU_ResetGpuCore(void); + +/// Restores the VRAM sys area. +Result GSPGPU_RestoreVramSysArea(void); + +/** + * @brief Sets whether to force the LCD to black. + * @param flags Whether to force the LCD to black. (0 = no, non-zero = yes) + */ +Result GSPGPU_SetLcdForceBlack(u8 flags); + +/** + * @brief Updates a screen's framebuffer state. + * @param screenid ID of the screen to update. + * @param framebufinfo Framebuffer information to update with. + */ +Result GSPGPU_SetBufferSwap(u32 screenid, const GSPGPU_FramebufferInfo* framebufinfo); + +/** + * @brief Flushes memory from the data cache. + * @param adr Address to flush. + * @param size Size of the memory to flush. + */ +Result GSPGPU_FlushDataCache(const void* adr, u32 size); + +/** + * @brief Invalidates memory in the data cache. + * @param adr Address to invalidate. + * @param size Size of the memory to invalidate. + */ +Result GSPGPU_InvalidateDataCache(const void* adr, u32 size); + +/** + * @brief Writes to GPU hardware registers. + * @param regAddr Register address to write to. + * @param data Data to write. + * @param size Size of the data to write. + */ +Result GSPGPU_WriteHWRegs(u32 regAddr, const u32* data, u8 size); + +/** + * @brief Writes to GPU hardware registers with a mask. + * @param regAddr Register address to write to. + * @param data Data to write. + * @param datasize Size of the data to write. + * @param maskdata Data of the mask. + * @param masksize Size of the mask. + */ +Result GSPGPU_WriteHWRegsWithMask(u32 regAddr, const u32* data, u8 datasize, const u32* maskdata, u8 masksize); + +/** + * @brief Reads from GPU hardware registers. + * @param regAddr Register address to read from. + * @param data Buffer to read data to. + * @param size Size of the buffer. + */ +Result GSPGPU_ReadHWRegs(u32 regAddr, u32* data, u8 size); + +/** + * @brief Registers the interrupt relay queue. + * @param eventHandle Handle of the GX command event. + * @param flags Flags to register with. + * @param outMemHandle Pointer to output the shared memory handle to. + * @param threadID Pointer to output the GSP thread ID to. + */ +Result GSPGPU_RegisterInterruptRelayQueue(Handle eventHandle, u32 flags, Handle* outMemHandle, u8* threadID); + +/// Unregisters the interrupt relay queue. +Result GSPGPU_UnregisterInterruptRelayQueue(void); + +/// Triggers a handling of commands written to shared memory. +Result GSPGPU_TriggerCmdReqQueue(void); + +/** + * @brief Sets 3D_LEDSTATE to the input state value. + * @param disable False = 3D LED enable, true = 3D LED disable. + */ +Result GSPGPU_SetLedForceOff(bool disable); diff --git a/libctru/include/3ds/services/gsplcd.h b/libctru/include/3ds/services/gsplcd.h new file mode 100644 index 0000000000..8e52d8e4bc --- /dev/null +++ b/libctru/include/3ds/services/gsplcd.h @@ -0,0 +1,72 @@ +/** + * @file gsplcd.h + * @brief GSPLCD service. + */ +#pragma once +#include <3ds/types.h> +#include <3ds/services/gspgpu.h> + +/// LCD screens. +enum +{ + GSPLCD_SCREEN_TOP = BIT(GSP_SCREEN_TOP), ///< Top screen. + GSPLCD_SCREEN_BOTTOM = BIT(GSP_SCREEN_BOTTOM), ///< Bottom screen. + GSPLCD_SCREEN_BOTH = GSPLCD_SCREEN_TOP | GSPLCD_SCREEN_BOTTOM, ///< Both screens. +}; + +/// Initializes GSPLCD. +Result gspLcdInit(void); + +/// Exits GSPLCD. +void gspLcdExit(void); + +/// Powers on both backlights. +Result GSPLCD_PowerOnAllBacklights(void); + +/// Powers off both backlights. +Result GSPLCD_PowerOffAllBacklights(void); + +/** + * @brief Powers on the backlight. + * @param screen Screen to power on. + */ +Result GSPLCD_PowerOnBacklight(u32 screen); + +/** + * @brief Powers off the backlight. + * @param screen Screen to power off. + */ +Result GSPLCD_PowerOffBacklight(u32 screen); + +/** + * @brief Sets 3D_LEDSTATE to the input state value. + * @param disable False = 3D LED enable, true = 3D LED disable. + */ +Result GSPLCD_SetLedForceOff(bool disable); + +/** + * @brief Gets the LCD screens' vendors. Stubbed on old 3ds. + * @param vendor Pointer to output the screen vendors to. + */ +Result GSPLCD_GetVendors(u8 *vendors); + +/** + * @brief Gets the LCD screens' brightness. Stubbed on old 3ds. + * @param screen Screen to get the brightness value of. + * @param brightness Brightness value returned. + */ +Result GSPLCD_GetBrightness(u32 screen, u32 *brightness); + +/** + * @brief Sets the LCD screens' brightness. + * @param screen Screen to set the brightness value of. + * @param brightness Brightness value set. + */ +Result GSPLCD_SetBrightness(u32 screen, u32 brightness); + +/** + * @brief Sets the LCD screens' raw brightness. + * @param screen Screen to set the brightness value of. + * @param brightness Brightness value set. + */ +Result GSPLCD_SetBrightnessRaw(u32 screen, u32 brightness); \ No newline at end of file diff --git a/libctru/include/3ds/services/hid.h b/libctru/include/3ds/services/hid.h new file mode 100644 index 0000000000..34510c0252 --- /dev/null +++ b/libctru/include/3ds/services/hid.h @@ -0,0 +1,217 @@ +/** + * @file hid.h + * @brief HID service. + */ +#pragma once + +//See also: http://3dbrew.org/wiki/HID_Services http://3dbrew.org/wiki/HID_Shared_Memory + +/// Key values. +enum +{ + KEY_A = BIT(0), ///< A + KEY_B = BIT(1), ///< B + KEY_SELECT = BIT(2), ///< Select + KEY_START = BIT(3), ///< Start + KEY_DRIGHT = BIT(4), ///< D-Pad Right + KEY_DLEFT = BIT(5), ///< D-Pad Left + KEY_DUP = BIT(6), ///< D-Pad Up + KEY_DDOWN = BIT(7), ///< D-Pad Down + KEY_R = BIT(8), ///< R + KEY_L = BIT(9), ///< L + KEY_X = BIT(10), ///< X + KEY_Y = BIT(11), ///< Y + KEY_ZL = BIT(14), ///< ZL (New 3DS only) + KEY_ZR = BIT(15), ///< ZR (New 3DS only) + KEY_TOUCH = BIT(20), ///< Touch (Not actually provided by HID) + KEY_CSTICK_RIGHT = BIT(24), ///< C-Stick Right (New 3DS only) + KEY_CSTICK_LEFT = BIT(25), ///< C-Stick Left (New 3DS only) + KEY_CSTICK_UP = BIT(26), ///< C-Stick Up (New 3DS only) + KEY_CSTICK_DOWN = BIT(27), ///< C-Stick Down (New 3DS only) + KEY_CPAD_RIGHT = BIT(28), ///< Circle Pad Right + KEY_CPAD_LEFT = BIT(29), ///< Circle Pad Left + KEY_CPAD_UP = BIT(30), ///< Circle Pad Up + KEY_CPAD_DOWN = BIT(31), ///< Circle Pad Down + + // Generic catch-all directions + KEY_UP = KEY_DUP | KEY_CPAD_UP, ///< D-Pad Up or Circle Pad Up + KEY_DOWN = KEY_DDOWN | KEY_CPAD_DOWN, ///< D-Pad Down or Circle Pad Down + KEY_LEFT = KEY_DLEFT | KEY_CPAD_LEFT, ///< D-Pad Left or Circle Pad Left + KEY_RIGHT = KEY_DRIGHT | KEY_CPAD_RIGHT, ///< D-Pad Right or Circle Pad Right +}; + +/// Touch position. +typedef struct +{ + u16 px; ///< Touch X + u16 py; ///< Touch Y +} touchPosition; + +/// Circle Pad position. +typedef struct +{ + s16 dx; ///< Pad X + s16 dy; ///< Pad Y +} circlePosition; + +/// Accelerometer vector. +typedef struct +{ + s16 x; ///< Accelerometer X + s16 y; ///< Accelerometer Y + s16 z; ///< Accelerometer Z +} accelVector; + +/// Gyroscope angular rate. +typedef struct +{ + s16 x; ///< Roll + s16 z; ///< Yaw + s16 y; ///< Pitch +} angularRate; + +/// HID events. +typedef enum +{ + HIDEVENT_PAD0 = 0, ///< Event signaled by HID-module, when the sharedmem+0(PAD/circle-pad)/+0xA8(touch-screen) region was updated. + HIDEVENT_PAD1, ///< Event signaled by HID-module, when the sharedmem+0(PAD/circle-pad)/+0xA8(touch-screen) region was updated. + HIDEVENT_Accel, ///< Event signaled by HID-module, when the sharedmem accelerometer state was updated. + HIDEVENT_Gyro, ///< Event signaled by HID-module, when the sharedmem gyroscope state was updated. + HIDEVENT_DebugPad, ///< Event signaled by HID-module, when the sharedmem DebugPad state was updated. + + HIDEVENT_MAX, ///< Used to know how many events there are. +} HID_Event; + +extern Handle hidMemHandle; ///< HID shared memory handle. +extern vu32* hidSharedMem; ///< HID shared memory. + +/// Initializes HID. +Result hidInit(void); + +/// Exits HID. +void hidExit(void); + +/** + * @brief Sets the key repeat parameters for @ref hidKeysRepeat. + * @param delay Initial delay. + * @param interval Repeat interval. + */ +void hidSetRepeatParameters(u32 delay, u32 interval); + +/// Scans HID for input data. +void hidScanInput(void); + +/** + * @brief Returns a bitmask of held buttons. + * Individual buttons can be extracted using binary AND. + * @return 32-bit bitmask of held buttons (1+ frames). + */ +u32 hidKeysHeld(void); + +/** + * @brief Returns a bitmask of newly pressed buttons, this frame. + * Individual buttons can be extracted using binary AND. + * @return 32-bit bitmask of newly pressed buttons. + */ +u32 hidKeysDown(void); + +/** + * @brief Returns a bitmask of newly pressed or repeated buttons, this frame. + * Individual buttons can be extracted using binary AND. + * @return 32-bit bitmask of newly pressed or repeated buttons. + */ +u32 hidKeysDownRepeat(void); + +/** + * @brief Returns a bitmask of newly released buttons, this frame. + * Individual buttons can be extracted using binary AND. + * @return 32-bit bitmask of newly released buttons. + */ +u32 hidKeysUp(void); + +/** + * @brief Reads the current touch position. + * @param pos Pointer to output the touch position to. + */ +void hidTouchRead(touchPosition* pos); + +/** + * @brief Reads the current circle pad position. + * @param pos Pointer to output the circle pad position to. + */ +void hidCircleRead(circlePosition* pos); + +/** + * @brief Reads the current accelerometer data. + * @param vector Pointer to output the accelerometer data to. + */ +void hidAccelRead(accelVector* vector); + +/** + * @brief Reads the current gyroscope data. + * @param rate Pointer to output the gyroscope data to. + */ +void hidGyroRead(angularRate* rate); + +/** + * @brief Waits for an HID event. + * @param id ID of the event. + * @param nextEvent Whether to discard the current event and wait for the next event. + */ +void hidWaitForEvent(HID_Event id, bool nextEvent); + +/** + * @brief Waits for any HID or IRRST event. + * @param nextEvents Whether to discard the current events and wait for the next events. + * @param cancelEvent Optional additional handle to wait on, otherwise 0. + * @param timeout Timeout. + */ +Result hidWaitForAnyEvent(bool nextEvents, Handle cancelEvent, s64 timeout); + +/// Compatibility macro for hidScanInput. +#define scanKeys hidScanInput +/// Compatibility macro for hidKeysHeld. +#define keysHeld hidKeysHeld +/// Compatibility macro for hidKeysDown. +#define keysDown hidKeysDown +/// Compatibility macro for hidKeysUp. +#define keysUp hidKeysUp +/// Compatibility macro for hidTouchRead. +#define touchRead hidTouchRead +/// Compatibility macro for hidCircleRead. +#define circleRead hidCircleRead + +/** + * @brief Gets the handles for HID operation. + * @param outMemHandle Pointer to output the shared memory handle to. + * @param eventpad0 Pointer to output the pad 0 event handle to. + * @param eventpad1 Pointer to output the pad 1 event handle to. + * @param eventaccel Pointer to output the accelerometer event handle to. + * @param eventgyro Pointer to output the gyroscope event handle to. + * @param eventdebugpad Pointer to output the debug pad event handle to. + */ +Result HIDUSER_GetHandles(Handle* outMemHandle, Handle *eventpad0, Handle *eventpad1, Handle *eventaccel, Handle *eventgyro, Handle *eventdebugpad); + +/// Enables the accelerometer. +Result HIDUSER_EnableAccelerometer(void); + +/// Disables the accelerometer. +Result HIDUSER_DisableAccelerometer(void); + +/// Enables the gyroscope. +Result HIDUSER_EnableGyroscope(void); + +/// Disables the gyroscope. +Result HIDUSER_DisableGyroscope(void); + +/** + * @brief Gets the gyroscope raw to dps coefficient. + * @param coeff Pointer to output the coefficient to. + */ +Result HIDUSER_GetGyroscopeRawToDpsCoefficient(float *coeff); + +/** + * @brief Gets the current volume slider value. (0-63) + * @param volume Pointer to write the volume slider value to. + */ +Result HIDUSER_GetSoundVolume(u8 *volume); diff --git a/libctru/include/3ds/services/httpc.h b/libctru/include/3ds/services/httpc.h new file mode 100644 index 0000000000..481c8c315d --- /dev/null +++ b/libctru/include/3ds/services/httpc.h @@ -0,0 +1,301 @@ +/** + * @file httpc.h + * @brief HTTP service. + */ +#pragma once + +/// HTTP context. +typedef struct { + Handle servhandle; ///< Service handle. + u32 httphandle; ///< HTTP handle. +} httpcContext; + +/// HTTP request method. +typedef enum { + HTTPC_METHOD_GET = 0x1, + HTTPC_METHOD_POST = 0x2, + HTTPC_METHOD_HEAD = 0x3, + HTTPC_METHOD_PUT = 0x4, + HTTPC_METHOD_DELETE = 0x5 +} HTTPC_RequestMethod; + +/// HTTP request status. +typedef enum { + HTTPC_STATUS_REQUEST_IN_PROGRESS = 0x5, ///< Request in progress. + HTTPC_STATUS_DOWNLOAD_READY = 0x7 ///< Download ready. +} HTTPC_RequestStatus; + +/// HTTP KeepAlive option. +typedef enum { + HTTPC_KEEPALIVE_DISABLED = 0x0, + HTTPC_KEEPALIVE_ENABLED = 0x1 +} HTTPC_KeepAlive; + +/// Result code returned when a download is pending. +#define HTTPC_RESULTCODE_DOWNLOADPENDING 0xd840a02b + +// Result code returned when asked about a non-existing header. +#define HTTPC_RESULTCODE_NOTFOUND 0xd840a028 + +// Result code returned when any timeout function times out. +#define HTTPC_RESULTCODE_TIMEDOUT 0xd820a069 + +/// Initializes HTTPC. For HTTP GET the sharedmem_size can be zero. The sharedmem contains data which will be later uploaded for HTTP POST. sharedmem_size should be aligned to 0x1000-bytes. +Result httpcInit(u32 sharedmem_size); + +/// Exits HTTPC. +void httpcExit(void); + +/** + * @brief Opens a HTTP context. + * @param context Context to open. + * @param url URL to connect to. + * @param use_defaultproxy Whether the default proxy should be used (0 for default) + */ +Result httpcOpenContext(httpcContext *context, HTTPC_RequestMethod method, const char* url, u32 use_defaultproxy); + +/** + * @brief Closes a HTTP context. + * @param context Context to close. + */ +Result httpcCloseContext(httpcContext *context); + +/** + * @brief Cancels a HTTP connection. + * @param context Context to close. + */ +Result httpcCancelConnection(httpcContext *context); + +/** + * @brief Adds a request header field to a HTTP context. + * @param context Context to use. + * @param name Name of the field. + * @param value Value of the field. + */ +Result httpcAddRequestHeaderField(httpcContext *context, const char* name, const char* value); + +/** + * @brief Adds a POST form field to a HTTP context. + * @param context Context to use. + * @param name Name of the field. + * @param value Value of the field. + */ +Result httpcAddPostDataAscii(httpcContext *context, const char* name, const char* value); + +/** + * @brief Adds a POST form field with binary data to a HTTP context. + * @param context Context to use. + * @param name Name of the field. + * @param value The binary data to pass as a value. + * @param len Length of the binary data which has been passed. + */ +Result httpcAddPostDataBinary(httpcContext *context, const char* name, const u8* value, u32 len); + +/** + * @brief Adds a POST body to a HTTP context. + * @param context Context to use. + * @param data The data to be passed as raw into the body of the post request. + * @param len Length of data passed by data param. + */ +Result httpcAddPostDataRaw(httpcContext *context, const u32* data, u32 len); + +/** + * @brief Begins a HTTP request. + * @param context Context to use. + */ +Result httpcBeginRequest(httpcContext *context); + +/** + * @brief Receives data from a HTTP context. + * @param context Context to use. + * @param buffer Buffer to receive data to. + * @param size Size of the buffer. + */ +Result httpcReceiveData(httpcContext *context, u8* buffer, u32 size); + +/** + * @brief Receives data from a HTTP context with a timeout value. + * @param context Context to use. + * @param buffer Buffer to receive data to. + * @param size Size of the buffer. + * @param timeout Maximum time in nanoseconds to wait for a reply. + */ +Result httpcReceiveDataTimeout(httpcContext *context, u8* buffer, u32 size, u64 timeout); + +/** + * @brief Gets the request state of a HTTP context. + * @param context Context to use. + * @param out Pointer to output the HTTP request state to. + */ +Result httpcGetRequestState(httpcContext *context, HTTPC_RequestStatus* out); + +/** + * @brief Gets the download size state of a HTTP context. + * @param context Context to use. + * @param downloadedsize Pointer to output the downloaded size to. + * @param contentsize Pointer to output the total content size to. + */ +Result httpcGetDownloadSizeState(httpcContext *context, u32* downloadedsize, u32* contentsize); + +/** + * @brief Gets the response code of the HTTP context. + * @param context Context to get the response code of. + * @param out Pointer to write the response code to. + */ +Result httpcGetResponseStatusCode(httpcContext *context, u32* out); + +/** + * @brief Gets the response code of the HTTP context with a timeout value. + * @param context Context to get the response code of. + * @param out Pointer to write the response code to. + * @param timeout Maximum time in nanoseconds to wait for a reply. + */ +Result httpcGetResponseStatusCodeTimeout(httpcContext *context, u32* out, u64 timeout); + +/** + * @brief Gets a response header field from a HTTP context. + * @param context Context to use. + * @param name Name of the field. + * @param value Pointer to output the value of the field to. + * @param valuebuf_maxsize Maximum size of the value buffer. + */ +Result httpcGetResponseHeader(httpcContext *context, const char* name, char* value, u32 valuebuf_maxsize); + +/** + * @brief Adds a trusted RootCA cert to a HTTP context. + * @param context Context to use. + * @param cert Pointer to DER cert. + * @param certsize Size of the DER cert. + */ +Result httpcAddTrustedRootCA(httpcContext *context, const u8 *cert, u32 certsize); + +/** + * @brief Adds a default RootCA cert to a HTTP context. + * @param context Context to use. + * @param certID ID of the cert to add, see sslc.h. + */ +Result httpcAddDefaultCert(httpcContext *context, SSLC_DefaultRootCert certID); + +/** + * @brief Sets the RootCertChain for a HTTP context. + * @param context Context to use. + * @param RootCertChain_contexthandle Contexthandle for the RootCertChain. + */ +Result httpcSelectRootCertChain(httpcContext *context, u32 RootCertChain_contexthandle); + +/** + * @brief Sets the ClientCert for a HTTP context. + * @param context Context to use. + * @param cert Pointer to DER cert. + * @param certsize Size of the DER cert. + * @param privk Pointer to the DER private key. + * @param privk_size Size of the privk. + */ +Result httpcSetClientCert(httpcContext *context, const u8 *cert, u32 certsize, const u8 *privk, u32 privk_size); + +/** + * @brief Sets the default clientcert for a HTTP context. + * @param context Context to use. + * @param certID ID of the cert to add, see sslc.h. + */ +Result httpcSetClientCertDefault(httpcContext *context, SSLC_DefaultClientCert certID); + +/** + * @brief Sets the ClientCert contexthandle for a HTTP context. + * @param context Context to use. + * @param ClientCert_contexthandle Contexthandle for the ClientCert. + */ +Result httpcSetClientCertContext(httpcContext *context, u32 ClientCert_contexthandle); + +/** + * @brief Sets SSL options for the context. + * The HTTPC SSL option bits are the same as those defined in sslc.h + * @param context Context to set flags on. + * @param options SSL option flags. + */ +Result httpcSetSSLOpt(httpcContext *context, u32 options); + +/** + * @brief Sets the SSL options which will be cleared for the context. + * The HTTPC SSL option bits are the same as those defined in sslc.h + * @param context Context to clear flags on. + * @param options SSL option flags. + */ +Result httpcSetSSLClearOpt(httpcContext *context, u32 options); + +/** + * @brief Creates a RootCertChain. Up to 2 RootCertChains can be created under this user-process. + * @param RootCertChain_contexthandle Output RootCertChain contexthandle. + */ +Result httpcCreateRootCertChain(u32 *RootCertChain_contexthandle); + +/** + * @brief Destroy a RootCertChain. + * @param RootCertChain_contexthandle RootCertChain to use. + */ +Result httpcDestroyRootCertChain(u32 RootCertChain_contexthandle); + +/** + * @brief Adds a RootCA cert to a RootCertChain. + * @param RootCertChain_contexthandle RootCertChain to use. + * @param cert Pointer to DER cert. + * @param certsize Size of the DER cert. + * @param cert_contexthandle Optional output ptr for the cert contexthandle(this can be NULL). + */ +Result httpcRootCertChainAddCert(u32 RootCertChain_contexthandle, const u8 *cert, u32 certsize, u32 *cert_contexthandle); + +/** + * @brief Adds a default RootCA cert to a RootCertChain. + * @param RootCertChain_contexthandle RootCertChain to use. + * @param certID ID of the cert to add, see sslc.h. + * @param cert_contexthandle Optional output ptr for the cert contexthandle(this can be NULL). + */ +Result httpcRootCertChainAddDefaultCert(u32 RootCertChain_contexthandle, SSLC_DefaultRootCert certID, u32 *cert_contexthandle); + +/** + * @brief Removes a cert from a RootCertChain. + * @param RootCertChain_contexthandle RootCertChain to use. + * @param cert_contexthandle Contexthandle of the cert to remove. + */ +Result httpcRootCertChainRemoveCert(u32 RootCertChain_contexthandle, u32 cert_contexthandle); + +/** + * @brief Opens a ClientCert-context. Up to 2 ClientCert-contexts can be open under this user-process. + * @param cert Pointer to DER cert. + * @param certsize Size of the DER cert. + * @param privk Pointer to the DER private key. + * @param privk_size Size of the privk. + * @param ClientCert_contexthandle Output ClientCert context handle. + */ +Result httpcOpenClientCertContext(const u8 *cert, u32 certsize, const u8 *privk, u32 privk_size, u32 *ClientCert_contexthandle); + +/** + * @brief Opens a ClientCert-context with a default clientclient. Up to 2 ClientCert-contexts can be open under this user-process. + * @param certID ID of the cert to add, see sslc.h. + * @param ClientCert_contexthandle Output ClientCert context handle. + */ +Result httpcOpenDefaultClientCertContext(SSLC_DefaultClientCert certID, u32 *ClientCert_contexthandle); + +/** + * @brief Closes a ClientCert context. + * @param ClientCert_contexthandle ClientCert context to use. + */ +Result httpcCloseClientCertContext(u32 ClientCert_contexthandle); + +/** + * @brief Downloads data from the HTTP context into a buffer. + * The *entire* content must be downloaded before using httpcCloseContext(), otherwise httpcCloseContext() will hang. + * @param context Context to download data from. + * @param buffer Buffer to write data to. + * @param size Size of the buffer. + * @param downloadedsize Pointer to write the size of the downloaded data to. + */ +Result httpcDownloadData(httpcContext *context, u8* buffer, u32 size, u32 *downloadedsize); + +/** + * @brief Sets Keep-Alive for the context. + * @param context Context to set the KeepAlive flag on. + * @param option HTTPC_KeepAlive option. + */ +Result httpcSetKeepAlive(httpcContext *context, HTTPC_KeepAlive option); + diff --git a/libctru/include/3ds/services/ir.h b/libctru/include/3ds/services/ir.h new file mode 100644 index 0000000000..952131637f --- /dev/null +++ b/libctru/include/3ds/services/ir.h @@ -0,0 +1,93 @@ +/** + * @file ir.h + * @brief IR service. + */ +#pragma once + +/** + * @brief Initializes IRU. + * The permissions for the specified memory is set to RO. This memory must be already mapped. + * @param sharedmem_addr Address of the shared memory block to use. + * @param sharedmem_size Size of the shared memory block. + */ +Result iruInit(u32 *sharedmem_addr, u32 sharedmem_size); + +/// Shuts down IRU. +void iruExit(void); + +/** + * @brief Gets the IRU service handle. + * @return The IRU service handle. + */ +Handle iruGetServHandle(void); + +/** + * @brief Sends IR data. + * @param buf Buffer to send data from. + * @param size Size of the buffer. + * @param wait Whether to wait for the data to be sent. + */ +Result iruSendData(u8 *buf, u32 size, bool wait); + +/** + * @brief Receives IR data. + * @param buf Buffer to receive data to. + * @param size Size of the buffer. + * @param flag Flags to receive data with. + * @param transfercount Pointer to output the number of bytes read to. + * @param wait Whether to wait for the data to be received. + */ +Result iruRecvData(u8 *buf, u32 size, u8 flag, u32 *transfercount, bool wait); + +/// Initializes the IR session. +Result IRU_Initialize(void); + +/// Shuts down the IR session. +Result IRU_Shutdown(void); + +/** + * @brief Begins sending data. + * @param buf Buffer to send. + * @param size Size of the buffer. + */ +Result IRU_StartSendTransfer(u8 *buf, u32 size); + +/// Waits for a send operation to complete. +Result IRU_WaitSendTransfer(void); + +/** + * @brief Begins receiving data. + * @param size Size of the data to receive. + * @param flag Flags to use when receiving. + */ +Result IRU_StartRecvTransfer(u32 size, u8 flag); + +/** + * @brief Waits for a receive operation to complete. + * @param transfercount Pointer to output the number of bytes read to. + */ +Result IRU_WaitRecvTransfer(u32 *transfercount); + +/** + * @brief Sets the IR bit rate. + * @param value Bit rate to set. + */ +Result IRU_SetBitRate(u8 value); + +/** + * @brief Gets the IR bit rate. + * @param out Pointer to write the bit rate to. + */ +Result IRU_GetBitRate(u8 *out); + +/** + * @brief Sets the IR LED state. + * @param value IR LED state to set. + */ +Result IRU_SetIRLEDState(u32 value); + +/** + * @brief Gets the IR LED state. + * @param out Pointer to write the IR LED state to. + */ +Result IRU_GetIRLEDRecvState(u32 *out); diff --git a/libctru/include/3ds/services/irrst.h b/libctru/include/3ds/services/irrst.h new file mode 100644 index 0000000000..e643801567 --- /dev/null +++ b/libctru/include/3ds/services/irrst.h @@ -0,0 +1,65 @@ +/** + * @file irrst.h + * @brief IRRST service. + */ +#pragma once + +//See also: http://3dbrew.org/wiki/IR_Services http://3dbrew.org/wiki/IRRST_Shared_Memory + +#include "3ds/services/hid.h" // for circlePosition definition + +/// IRRST's shared memory handle. +extern Handle irrstMemHandle; + +/// IRRST's shared memory. +extern vu32* irrstSharedMem; + +/// IRRST's state update event +extern Handle irrstEvent; + +/// Initializes IRRST. +Result irrstInit(void); + +/// Exits IRRST. +void irrstExit(void); + +/// Scans IRRST for input. +void irrstScanInput(void); + +/** + * @brief Gets IRRST's held keys. + * @return IRRST's held keys. + */ +u32 irrstKeysHeld(void); + +/** + * @brief Reads the current c-stick position. + * @param pos Pointer to output the current c-stick position to. + */ +void irrstCstickRead(circlePosition* pos); + +/** + * @brief Waits for the IRRST input event to trigger. + * @param nextEvent Whether to discard the current event and wait until the next event. + */ +void irrstWaitForEvent(bool nextEvent); + +/// Macro for irrstCstickRead. +#define hidCstickRead irrstCstickRead + +/** + * @brief Gets the shared memory and event handles for IRRST. + * @param outMemHandle Pointer to write the shared memory handle to. + * @param outEventHandle Pointer to write the event handle to. + */ +Result IRRST_GetHandles(Handle* outMemHandle, Handle* outEventHandle); + +/** + * @brief Initializes IRRST. + * @param unk1 Unknown. + * @param unk2 Unknown. + */ +Result IRRST_Initialize(u32 unk1, u8 unk2); + +/// Shuts down IRRST. +Result IRRST_Shutdown(void); diff --git a/libctru/include/3ds/services/loader.h b/libctru/include/3ds/services/loader.h new file mode 100644 index 0000000000..d939e07e06 --- /dev/null +++ b/libctru/include/3ds/services/loader.h @@ -0,0 +1,43 @@ +/** + * @file loader.h + * @brief LOADER Service + */ + +#pragma once + +#include <3ds/exheader.h> +#include <3ds/services/fs.h> + +/// Initializes LOADER. +Result loaderInit(void); + +/// Exits LOADER. +void loaderExit(void); + +/** + * @brief Loads a program and returns a process handle to the newly created process. + * @param[out] process Pointer to output the process handle to. + * @param programHandle The handle of the program to load. + */ +Result LOADER_LoadProcess(Handle* process, u64 programHandle); + +/** + * @brief Registers a program (along with its update). + * @param[out] programHandle Pointer to output the program handle to. + * @param programInfo The program info. + * @param programInfo The program update info. + */ +Result LOADER_RegisterProgram(u64* programHandle, const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate); + +/** + * @brief Unregisters a program (along with its update). + * @param programHandle The handle of the program to unregister. + */ +Result LOADER_UnregisterProgram(u64 programHandle); + +/** + * @brief Retrives a program's main NCCH extended header info (SCI + ACI, see @ref ExHeader_Info). + * @param[out] exheaderInfo Pointer to output the main NCCH extended header info. + * @param programHandle The handle of the program to unregister + */ +Result LOADER_GetProgramInfo(ExHeader_Info* exheaderInfo, u64 programHandle); diff --git a/libctru/include/3ds/services/mcuhwc.h b/libctru/include/3ds/services/mcuhwc.h new file mode 100644 index 0000000000..6fbdfcf62f --- /dev/null +++ b/libctru/include/3ds/services/mcuhwc.h @@ -0,0 +1,85 @@ +/** + * @file mcuhwc.h + * @brief mcuHwc service. + */ +#pragma once + +typedef enum +{ + LED_NORMAL = 1, ///< The normal mode of the led + LED_SLEEP_MODE, ///< The led pulses slowly as it does in the sleep mode + LED_OFF, ///< Switch off power led + LED_RED, ///< Red state of the led + LED_BLUE, ///< Blue state of the led + LED_BLINK_RED, ///< Blinking red state of power led and notification led +}powerLedState; + +/// Initializes mcuHwc. +Result mcuHwcInit(void); + +/// Exits mcuHwc. +void mcuHwcExit(void); + +/** + * @brief Reads data from an i2c device3 register + * @param reg Register number. See https://www.3dbrew.org/wiki/I2C_Registers#Device_3 for more info + * @param data Pointer to write the data to. + * @param size Size of data to be read + */ +Result MCUHWC_ReadRegister(u8 reg, void *data, u32 size); + +/** + * @brief Writes data to a i2c device3 register + * @param reg Register number. See https://www.3dbrew.org/wiki/I2C_Registers#Device_3 for more info + * @param data Pointer to write the data to. + * @param size Size of data to be written + */ +Result MCUHWC_WriteRegister(u8 reg, const void *data, u32 size); + +/** + * @brief Gets the battery voltage + * @param voltage Pointer to write the battery voltage to. + */ +Result MCUHWC_GetBatteryVoltage(u8 *voltage); + +/** + * @brief Gets the battery level + * @param level Pointer to write the current battery level to. + */ +Result MCUHWC_GetBatteryLevel(u8 *level); + +/** + * @brief Gets the sound slider level + * @param level Pointer to write the slider level to. + */ +Result MCUHWC_GetSoundSliderLevel(u8 *level); + +/** + * @brief Sets Wifi LED state + * @param state State of Wifi LED. (True/False) + */ +Result MCUHWC_SetWifiLedState(bool state); + +/** + * @brief Sets Power LED state + * @param state powerLedState State of power LED. + */ +Result MCUHWC_SetPowerLedState(powerLedState state); + +/** + * @brief Gets 3d slider level + * @param level Pointer to write 3D slider level to. + */ +Result MCUHWC_Get3dSliderLevel(u8 *level); + +/** + * @brief Gets the major MCU firmware version + * @param out Pointer to write the major firmware version to. + */ +Result MCUHWC_GetFwVerHigh(u8 *out); + +/** + * @brief Gets the minor MCU firmware version + * @param out Pointer to write the minor firmware version to. + */ +Result MCUHWC_GetFwVerLow(u8 *out); diff --git a/libctru/include/3ds/services/mic.h b/libctru/include/3ds/services/mic.h new file mode 100644 index 0000000000..2d10a27f13 --- /dev/null +++ b/libctru/include/3ds/services/mic.h @@ -0,0 +1,128 @@ +/** + * @file mic.h + * @brief MIC (Microphone) service. + */ +#pragma once + +/// Microphone audio encodings. +typedef enum +{ + MICU_ENCODING_PCM8 = 0, ///< Unsigned 8-bit PCM. + MICU_ENCODING_PCM16 = 1, ///< Unsigned 16-bit PCM. + MICU_ENCODING_PCM8_SIGNED = 2, ///< Signed 8-bit PCM. + MICU_ENCODING_PCM16_SIGNED = 3, ///< Signed 16-bit PCM. +} MICU_Encoding; + +/// Microphone audio sampling rates. +typedef enum +{ + MICU_SAMPLE_RATE_32730 = 0, ///< 32728.498 Hz + MICU_SAMPLE_RATE_16360 = 1, ///< 16364.479 Hz + MICU_SAMPLE_RATE_10910 = 2, ///< 10909.499 Hz + MICU_SAMPLE_RATE_8180 = 3, ///< 8182.1245 Hz +} MICU_SampleRate; + +/** + * @brief Initializes MIC. + * @param size Shared memory buffer to write audio data to. Must be aligned to 0x1000 bytes. + * @param handle Size of the shared memory buffer. + */ +Result micInit(u8* buffer, u32 bufferSize); + +/// Exits MIC. +void micExit(void); + +/** + * @brief Gets the size of the sample data area within the shared memory buffer. + * @return The sample data's size. + */ +u32 micGetSampleDataSize(void); + +/** + * @brief Gets the offset within the shared memory buffer of the last sample written. + * @return The last sample's offset. + */ +u32 micGetLastSampleOffset(void); + +/** + * @brief Maps MIC shared memory. + * @param size Size of the shared memory. + * @param handle Handle of the shared memory. + */ +Result MICU_MapSharedMem(u32 size, Handle handle); + +/// Unmaps MIC shared memory. +Result MICU_UnmapSharedMem(void); + +/** + * @brief Begins sampling microphone input. + * @param encoding Encoding of outputted audio. + * @param sampleRate Sample rate of outputted audio. + * @param sharedMemAudioOffset Offset to write audio data to in the shared memory buffer. + * @param sharedMemAudioSize Size of audio data to write to the shared memory buffer. This should be at most "bufferSize - 4". + * @param loop Whether to loop back to the beginning of the buffer when the end is reached. + */ +Result MICU_StartSampling(MICU_Encoding encoding, MICU_SampleRate sampleRate, u32 offset, u32 size, bool loop); + +/** + * @brief Adjusts the configuration of the current sampling session. + * @param sampleRate Sample rate of outputted audio. + */ +Result MICU_AdjustSampling(MICU_SampleRate sampleRate); + +/// Stops sampling microphone input. +Result MICU_StopSampling(void); + +/** + * @brief Gets whether microphone input is currently being sampled. + * @param sampling Pointer to output the sampling state to. + */ +Result MICU_IsSampling(bool* sampling); + +/** + * @brief Gets an event handle triggered when the shared memory buffer is full. + * @param handle Pointer to output the event handle to. + */ +Result MICU_GetEventHandle(Handle* handle); + +/** + * @brief Sets the microphone's gain. + * @param gain Gain to set. + */ +Result MICU_SetGain(u8 gain); + +/** + * @brief Gets the microphone's gain. + * @param gain Pointer to output the current gain to. + */ +Result MICU_GetGain(u8* gain); + +/** + * @brief Sets whether the microphone is powered on. + * @param power Whether the microphone is powered on. + */ +Result MICU_SetPower(bool power); + +/** + * @brief Gets whether the microphone is powered on. + * @param power Pointer to output the power state to. + */ +Result MICU_GetPower(bool* power); + +/** + * @brief Sets whether to clamp microphone input. + * @param clamp Whether to clamp microphone input. + */ +Result MICU_SetClamp(bool clamp); + +/** + * @brief Gets whether to clamp microphone input. + * @param clamp Pointer to output the clamp state to. + */ +Result MICU_GetClamp(bool* clamp); + +/** + * @brief Sets whether to allow sampling when the shell is closed. + * @param allowShellClosed Whether to allow sampling when the shell is closed. + */ +Result MICU_SetAllowShellClosed(bool allowShellClosed); diff --git a/libctru/include/3ds/services/mvd.h b/libctru/include/3ds/services/mvd.h new file mode 100644 index 0000000000..4ccf5c94ec --- /dev/null +++ b/libctru/include/3ds/services/mvd.h @@ -0,0 +1,168 @@ +/** + * @file mvd.h + * @brief MVD service. + */ +#pragma once + +//New3DS-only, see also: http://3dbrew.org/wiki/MVD_Services + +///These values are the data returned as "result-codes" by MVDSTD. +#define MVD_STATUS_OK 0x17000 +#define MVD_STATUS_PARAMSET 0x17001 ///"Returned after processing NAL-unit parameter-sets." +#define MVD_STATUS_BUSY 0x17002 +#define MVD_STATUS_FRAMEREADY 0x17003 +#define MVD_STATUS_INCOMPLETEPROCESSING 0x17004 ///"Returned when not all of the input NAL-unit buffer was processed." +#define MVD_STATUS_NALUPROCFLAG 0x17007 ///See here: https://www.3dbrew.org/wiki/MVDSTD:ProcessNALUnit + +///This can be used to check whether mvdstdProcessVideoFrame() was successful. +#define MVD_CHECKNALUPROC_SUCCESS(x) (x==MVD_STATUS_OK || x==MVD_STATUS_PARAMSET || x==MVD_STATUS_FRAMEREADY || x==MVD_STATUS_INCOMPLETEPROCESSING || x==MVD_STATUS_NALUPROCFLAG) + +/// Default input size for mvdstdInit(). This is what the New3DS Internet Browser uses, from the MVDSTD:CalculateWorkBufSize output. +#define MVD_DEFAULT_WORKBUF_SIZE 0x9006C8 + +/// Processing mode. +typedef enum { + MVDMODE_COLORFORMATCONV, ///< Converting color formats. + MVDMODE_VIDEOPROCESSING ///< Processing video. +} MVDSTD_Mode; + +/// Input format. +typedef enum { + MVD_INPUT_YUYV422 = 0x00010001, ///< YUYV422 + MVD_INPUT_H264 = 0x00020001 ///< H264 +} MVDSTD_InputFormat; + +/// Output format. +typedef enum { + MVD_OUTPUT_YUYV422 = 0x00010001, ///< YUYV422 + MVD_OUTPUT_BGR565 = 0x00040002, ///< BGR565 + MVD_OUTPUT_RGB565 = 0x00040004 ///< RGB565 +} MVDSTD_OutputFormat; + +/// Processing configuration. +typedef struct { + MVDSTD_InputFormat input_type; ///< Input type. + u32 unk_x04; ///< Unknown. + u32 unk_x08; ///< Unknown. Referred to as "H264 range" in SKATER. + u32 inwidth; ///< Input width. + u32 inheight; ///< Input height. + u32 physaddr_colorconv_indata; ///< Physical address of color conversion input data. + u32 physaddr_colorconv_unk0; ///< Physical address used with color conversion. + u32 physaddr_colorconv_unk1; ///< Physical address used with color conversion. + u32 physaddr_colorconv_unk2; ///< Physical address used with color conversion. + u32 physaddr_colorconv_unk3; ///< Physical address used with color conversion. + u32 unk_x28[0x18>>2]; ///< Unknown. + u32 enable_cropping; ///< Enables cropping with the input image when non-zero via the following 4 words. + u32 input_crop_x_pos; + u32 input_crop_y_pos; + u32 input_crop_height; + u32 input_crop_width; + u32 unk_x54; ///< Unknown. + MVDSTD_OutputFormat output_type; ///< Output type. + u32 outwidth; ///< Output width. + u32 outheight; ///< Output height. + u32 physaddr_outdata0; ///< Physical address of output data. + u32 physaddr_outdata1; ///< Additional physical address for output data, only used when the output format type is value 0x00020001. + u32 unk_x6c[0x98>>2]; ///< Unknown. + u32 flag_x104; ///< This enables using the following 4 words when non-zero. + u32 output_x_pos; ///< Output X position in the output buffer. + u32 output_y_pos; ///< Same as above except for the Y pos. + u32 output_width_override; ///< Used for aligning the output width when larger than the output width. Overrides the output width when smaller than the output width. + u32 output_height_override; ///< Same as output_width_override except for the output height. + u32 unk_x118; +} MVDSTD_Config; + +typedef struct { + u32 end_vaddr;//"End-address of the processed NAL-unit(internal MVD heap vaddr)." + u32 end_physaddr;//"End-address of the processed NAL-unit(physaddr following the input physaddr)." + u32 remaining_size;//"Total remaining unprocessed input data. Buffer_end_pos=bufsize-." +} MVDSTD_ProcessNALUnitOut; + +typedef struct { + void* outdata0;//Linearmem vaddr equivalent to config *_outdata0. + void* outdata1;//Linearmem vaddr equivalent to config *_outdata1. +} MVDSTD_OutputBuffersEntry; + +typedef struct { + u32 total_entries;//Total actual used entries below. + MVDSTD_OutputBuffersEntry entries[17]; +} MVDSTD_OutputBuffersEntryList; + +/// This can be used to override the default input values for MVDSTD commands during initialization with video-processing. The default for these fields are all-zero, except for cmd1b_inval which is 1. See also here: https://www.3dbrew.org/wiki/MVD_Services +typedef struct { + s8 cmd5_inval0, cmd5_inval1, cmd5_inval2; + u32 cmd5_inval3; + + u8 cmd1b_inval; +} MVDSTD_InitStruct; + +/** + * @brief Initializes MVDSTD. + * @param mode Mode to initialize MVDSTD to. + * @param input_type Type of input to process. + * @param output_type Type of output to produce. + * @param size Size of the work buffer, MVD_DEFAULT_WORKBUF_SIZE can be used for this. Only used when type == MVDMODE_VIDEOPROCESSING. + * @param initstruct Optional MVDSTD_InitStruct, this should be NULL normally. + */ +Result mvdstdInit(MVDSTD_Mode mode, MVDSTD_InputFormat input_type, MVDSTD_OutputFormat output_type, u32 size, MVDSTD_InitStruct *initstruct); + +/// Shuts down MVDSTD. +void mvdstdExit(void); + +/** + * @brief Generates a default MVDSTD configuration. + * @param config Pointer to output the generated config to. + * @param input_width Input width. + * @param input_height Input height. + * @param output_width Output width. + * @param output_height Output height. + * @param vaddr_colorconv_indata Virtual address of the color conversion input data. + * @param vaddr_outdata0 Virtual address of the output data. + * @param vaddr_outdata1 Additional virtual address for output data, only used when the output format type is value 0x00020001. + */ +void mvdstdGenerateDefaultConfig(MVDSTD_Config*config, u32 input_width, u32 input_height, u32 output_width, u32 output_height, u32 *vaddr_colorconv_indata, u32 *vaddr_outdata0, u32 *vaddr_outdata1); + +/** + * @brief Run color-format-conversion. + * @param config Pointer to the configuration to use. + */ +Result mvdstdConvertImage(MVDSTD_Config* config); + +/** + * @brief Processes a video frame(specifically a NAL-unit). + * @param inbuf_vaddr Input NAL-unit starting with the 3-byte "00 00 01" prefix. Must be located in linearmem. + * @param size Size of the input buffer. + * @param flag See here regarding this input flag: https://www.3dbrew.org/wiki/MVDSTD:ProcessNALUnit + * @param out Optional output MVDSTD_ProcessNALUnitOut structure. + */ +Result mvdstdProcessVideoFrame(void* inbuf_vaddr, size_t size, u32 flag, MVDSTD_ProcessNALUnitOut *out); + +/** + * @brief Renders the video frame. + * @param config Optional pointer to the configuration to use. When NULL, MVDSTD_SetConfig() should have been used previously for this video. + * @param wait When true, wait for rendering to finish. When false, you can manually call this function repeatedly until it stops returning MVD_STATUS_BUSY. + */ +Result mvdstdRenderVideoFrame(MVDSTD_Config* config, bool wait); + +/** + * @brief Sets the current configuration of MVDSTD. + * @param config Pointer to the configuration to set. + */ +Result MVDSTD_SetConfig(MVDSTD_Config* config); + +/** + * @brief New3DS Internet Browser doesn't use this. Once done, rendered frames will be written to the output buffers specified by the entrylist instead of the output specified by configuration. See here: https://www.3dbrew.org/wiki/MVDSTD:SetupOutputBuffers + * @param entrylist Input entrylist. + * @param bufsize Size of each buffer from the entrylist. + */ +Result mvdstdSetupOutputBuffers(MVDSTD_OutputBuffersEntryList *entrylist, u32 bufsize); + +/** + * @brief New3DS Internet Browser doesn't use this. This overrides the entry0 output buffers originally setup by mvdstdSetupOutputBuffers(). See also here: https://www.3dbrew.org/wiki/MVDSTD:OverrideOutputBuffers + * @param cur_outdata0 Linearmem vaddr. The current outdata0 for this entry must match this value. + * @param cur_outdata1 Linearmem vaddr. The current outdata1 for this entry must match this value. + * @param new_outdata0 Linearmem vaddr. This is the new address to use for outaddr0. + * @param new_outdata1 Linearmem vaddr. This is the new address to use for outaddr1. + */ +Result mvdstdOverrideOutputBuffers(void* cur_outdata0, void* cur_outdata1, void* new_outdata0, void* new_outdata1); + diff --git a/libctru/include/3ds/services/ndm.h b/libctru/include/3ds/services/ndm.h new file mode 100644 index 0000000000..eb4c80dfbd --- /dev/null +++ b/libctru/include/3ds/services/ndm.h @@ -0,0 +1,148 @@ +/** + * @file ndm.h + * @brief NDMU service. https://3dbrew.org/wiki/NDM_Services + */ +#pragma once + +/// Exclusive states. +typedef enum { + NDM_EXCLUSIVE_STATE_NONE = 0, + NDM_EXCLUSIVE_STATE_INFRASTRUCTURE = 1, + NDM_EXCLUSIVE_STATE_LOCAL_COMMUNICATIONS = 2, + NDM_EXCLUSIVE_STATE_STREETPASS = 3, + NDM_EXCLUSIVE_STATE_STREETPASS_DATA = 4, +} ndmExclusiveState; + +/// Current states. +typedef enum { + NDM_STATE_INITIAL = 0, + NDM_STATE_SUSPENDED = 1, + NDM_STATE_INFRASTRUCTURE_CONNECTING = 2, + NDM_STATE_INFRASTRUCTURE_CONNECTED = 3, + NDM_STATE_INFRASTRUCTURE_WORKING = 4, + NDM_STATE_INFRASTRUCTURE_SUSPENDING = 5, + NDM_STATE_INFRASTRUCTURE_FORCE_SUSPENDING = 6, + NDM_STATE_INFRASTRUCTURE_DISCONNECTING = 7, + NDM_STATE_INFRASTRUCTURE_FORCE_DISCONNECTING = 8, + NDM_STATE_CEC_WORKING = 9, + NDM_STATE_CEC_FORCE_SUSPENDING = 10, + NDM_STATE_CEC_SUSPENDING = 11, +} ndmState; + +// Daemons. +typedef enum { + NDM_DAEMON_CEC = 0, + NDM_DAEMON_BOSS = 1, + NDM_DAEMON_NIM = 2, + NDM_DAEMON_FRIENDS = 3, +} ndmDaemon; + +/// Used to specify multiple daemons. +typedef enum { + NDM_DAEMON_MASK_CEC = BIT(NDM_DAEMON_CEC), + NDM_DAEMON_MASK_BOSS = BIT(NDM_DAEMON_BOSS), + NDM_DAEMON_MASK_NIM = BIT(NDM_DAEMON_NIM), + NDM_DAEMON_MASK_FRIENDS = BIT(NDM_DAEMON_FRIENDS), + NDM_DAEMON_MASK_BACKGROUOND = NDM_DAEMON_MASK_CEC | NDM_DAEMON_MASK_BOSS | NDM_DAEMON_MASK_NIM, + NDM_DAEMON_MASK_ALL = NDM_DAEMON_MASK_CEC | NDM_DAEMON_MASK_BOSS | NDM_DAEMON_MASK_NIM | NDM_DAEMON_MASK_FRIENDS, + NDM_DAEMON_MASK_DEFAULT = NDM_DAEMON_MASK_CEC | NDM_DAEMON_MASK_FRIENDS, +} ndmDaemonMask; + +// Daemon status. +typedef enum { + NDM_DAEMON_STATUS_BUSY = 0, + NDM_DAEMON_STATUS_IDLE = 1, + NDM_DAEMON_STATUS_SUSPENDING = 2, + NDM_DAEMON_STATUS_SUSPENDED = 3, +} ndmDaemonStatus; + +/// Initializes ndmu. +Result ndmuInit(void); + +/// Exits ndmu. +void ndmuExit(void); + +/** + * @brief Sets the network daemon to an exclusive state. + * @param state State specified in the ndmExclusiveState enumerator. + */ +Result NDMU_EnterExclusiveState(ndmExclusiveState state); + +/// Cancels an exclusive state for the network daemon. +Result NDMU_LeaveExclusiveState(void); + +/** + * @brief Returns the exclusive state for the network daemon. + * @param state Pointer to write the exclsuive state to. + */ +Result NDMU_GetExclusiveState(ndmExclusiveState *state); + +/// Locks the exclusive state. +Result NDMU_LockState(void); + +/// Unlocks the exclusive state. +Result NDMU_UnlockState(void); + +/** + * @brief Suspends network daemon. + * @param mask The specified daemon. + */ +Result NDMU_SuspendDaemons(ndmDaemonMask mask); + +/** + * @brief Resumes network daemon. + * @param mask The specified daemon. + */ +Result NDMU_ResumeDaemons(ndmDaemonMask mask); + +/** + * @brief Suspends scheduling for all network daemons. + * @param flag 0 = Wait for completion, 1 = Perform in background. + */ +Result NDMU_SuspendScheduler(u32 flag); + +/// Resumes daemon scheduling. +Result NDMU_ResumeScheduler(void); + +/** + * @brief Returns the current state for the network daemon. + * @param state Pointer to write the current state to. + */ +Result NDMU_GetCurrentState(ndmState *state); + +/** + * @brief Returns the daemon state. + * @param state Pointer to write the daemons state to. + */ +Result NDMU_QueryStatus(ndmDaemonStatus *status); + +/** + * @brief Sets the scan interval. + * @param interval Value to set the scan interval to. + */ +Result NDMU_SetScanInterval(u32 interval); + +/** + * @brief Returns the scan interval. + * @param interval Pointer to write the interval value to. + */ +Result NDMU_GetScanInterval(u32 *interval); + +/** + * @brief Returns the retry interval. + * @param interval Pointer to write the interval value to. + */ +Result NDMU_GetRetryInterval(u32 *interval); + +/// Reverts network daemon to defaults. +Result NDMU_ResetDaemons(void); + +/** + * @brief Gets the current default daemon bit mask. + * @param interval Pointer to write the default daemon mask value to. The default value is (DAEMONMASK_CEC | DAEMONMASK_FRIENDS) + */ +Result NDMU_GetDefaultDaemons(ndmDaemonMask *mask); + +/// Clears half awake mac filter. +Result NDMU_ClearMacFilter(void); + diff --git a/libctru/include/3ds/services/news.h b/libctru/include/3ds/services/news.h new file mode 100644 index 0000000000..6403f1beb7 --- /dev/null +++ b/libctru/include/3ds/services/news.h @@ -0,0 +1,91 @@ +/** + * @file news.h + * @brief NEWS (Notification) service. + */ +#pragma once + +/// Notification header data. +typedef struct { + bool dataSet; + bool unread; + bool enableJPEG; + bool isSpotPass; + bool isOptedOut; + u8 unkData[3]; + u64 processID; + u8 unkData2[8]; + u64 jumpParam; + u8 unkData3[8]; + u64 time; + u16 title[32]; +} NotificationHeader; + +/// Initializes NEWS. +Result newsInit(void); + +/// Exits NEWS. +void newsExit(void); + +/** + * @brief Adds a notification to the home menu Notifications applet. + * @param title UTF-16 title of the notification. + * @param titleLength Number of characters in the title, not including the null-terminator. + * @param message UTF-16 message of the notification, or NULL for no message. + * @param messageLength Number of characters in the message, not including the null-terminator. + * @param image Data of the image to show in the notification, or NULL for no image. + * @param imageSize Size of the image data in bytes. + * @param jpeg Whether the image is a JPEG or not. + */ +Result NEWS_AddNotification(const u16* title, u32 titleLength, const u16* message, u32 messageLength, const void* imageData, u32 imageSize, bool jpeg); + +/** + * @brief Gets current total notifications number. + * @param num Pointer where total number will be saved. + */ +Result NEWS_GetTotalNotifications(u32* num); + +/** + * @brief Sets a custom header for a specific notification. + * @param news_id Identification number of the notification. + * @param header Pointer to notification header to set. + */ +Result NEWS_SetNotificationHeader(u32 news_id, const NotificationHeader* header); + +/** + * @brief Gets the header of a specific notification. + * @param news_id Identification number of the notification. + * @param header Pointer where header of the notification will be saved. + */ +Result NEWS_GetNotificationHeader(u32 news_id, NotificationHeader* header); + +/** + * @brief Sets a custom message for a specific notification. + * @param news_id Identification number of the notification. + * @param message Pointer to UTF-16 message to set. + * @param size Size of message to set. + */ +Result NEWS_SetNotificationMessage(u32 news_id, const u16* message, u32 size); + +/** + * @brief Gets the message of a specific notification. + * @param news_id Identification number of the notification. + * @param message Pointer where UTF-16 message of the notification will be saved. + * @param size Pointer where size of the message data will be saved in bytes. + */ +Result NEWS_GetNotificationMessage(u32 news_id, u16* message, u32* size); + +/** + * @brief Sets a custom image for a specific notification. + * @param news_id Identification number of the notification. + * @param buffer Pointer to MPO image to set. + * @param size Size of the MPO image to set. + */ +Result NEWS_SetNotificationImage(u32 news_id, const void* buffer, u32 size); + +/** + * @brief Gets the image of a specific notification. + * @param news_id Identification number of the notification. + * @param buffer Pointer where MPO image of the notification will be saved. + * @param size Pointer where size of the image data will be saved in bytes. + */ +Result NEWS_GetNotificationImage(u32 news_id, void* buffer, u32* size); \ No newline at end of file diff --git a/libctru/include/3ds/services/nfc.h b/libctru/include/3ds/services/nfc.h new file mode 100644 index 0000000000..8fa9bf25a5 --- /dev/null +++ b/libctru/include/3ds/services/nfc.h @@ -0,0 +1,220 @@ +/** + * @file nfc.h + * @brief NFC service. This can only be used with system-version >=9.3.0-X. + */ +#pragma once + +/// This is returned when the current state is invalid for this command. +#define NFC_ERR_INVALID_STATE 0xC8A17600 + +/// This is returned by nfcOpenAppData() when the appdata is uninitialized since nfcInitializeWriteAppData() wasn't used previously. +#define NFC_ERR_APPDATA_UNINITIALIZED 0xC8A17620 + +/// This is returned by nfcGetAmiiboSettings() when the amiibo wasn't setup by the amiibo Settings applet. +#define NFC_ERR_AMIIBO_NOTSETUP 0xC8A17628 + +/// This is returned by nfcOpenAppData() when the input AppID doesn't match the actual amiibo AppID. +#define NFC_ERR_APPID_MISMATCH 0xC8A17638 + +/// "Returned for HMAC-hash mismatch(data corruption), with HMAC-calculation input_buffer_size=0x34." +#define NFC_ERR_DATACORRUPTION0 0xC8C1760C + +/// HMAC-hash mismatch with input_buffer_size=0x1DF, see here: https://www.3dbrew.org/wiki/Amiibo +#define NFC_ERR_DATACORRUPTION1 0xC8A17618 + +/// This can be used for nfcStartScanning(). +#define NFC_STARTSCAN_DEFAULTINPUT 0 + +/// NFC operation type. +typedef enum { + NFC_OpType_1 = 1, /// Unknown. + NFC_OpType_NFCTag = 2, /// This is the default. + NFC_OpType_RawNFC = 3 /// Use Raw NFC tag commands. Only available with >=10.0.0-X. +} NFC_OpType; + +typedef enum { + NFC_TagState_Uninitialized = 0, /// nfcInit() was not used yet. + NFC_TagState_ScanningStopped = 1, /// Not currently scanning for NFC tags. Set by nfcStopScanning() and nfcInit(), when successful. + NFC_TagState_Scanning = 2, /// Currently scanning for NFC tags. Set by nfcStartScanning() when successful. + NFC_TagState_InRange = 3, /// NFC tag is in range. The state automatically changes to this when the state was previously value 2, without using any NFC service commands. + NFC_TagState_OutOfRange = 4, /// NFC tag is now out of range, where the NFC tag was previously in range. This occurs automatically without using any NFC service commands. Once this state is entered, it won't automatically change to anything else when the tag is moved in range again. Hence, if you want to keep doing tag scanning after this, you must stop+start scanning. + NFC_TagState_DataReady = 5 /// NFC tag data was successfully loaded. This is set by nfcLoadAmiiboData() when successful. +} NFC_TagState; + +/// Bit4-7 are always clear with nfcGetAmiiboSettings() due to "& 0xF". +enum { + NFC_amiiboFlag_Setup = BIT(4), /// This indicates that the amiibo was setup with amiibo Settings. nfcGetAmiiboSettings() will return an all-zero struct when this is not set. + NFC_amiiboFlag_AppDataSetup = BIT(5) /// This indicates that the AppData was previously initialized via nfcInitializeWriteAppData(), that function can't be used again with this flag already set. +}; + +typedef struct { + u16 id_offset_size;/// "u16 size/offset of the below ID data. Normally this is 0x7. When this is <=10, this field is the size of the below ID data. When this is >10, this is the offset of the 10-byte ID data, relative to structstart+4+. It's unknown in what cases this 10-byte ID data is used." + u8 unk_x2;//"Unknown u8, normally 0x0." + u8 unk_x3;//"Unknown u8, normally 0x2." + u8 id[0x28];//"ID data. When the above size field is 0x7, this is the 7-byte NFC tag UID, followed by all-zeros." +} NFC_TagInfo; + +/// AmiiboSettings structure, see also here: https://3dbrew.org/wiki/NFC:GetAmiiboSettings +typedef struct { + u8 mii[0x60];/// "Owner Mii." + u16 nickname[11];/// "UTF-16BE Amiibo nickname." + u8 flags;/// "This is plaintext_amiibosettingsdata[0] & 0xF." See also the NFC_amiiboFlag enums. + u8 countrycodeid;/// "This is plaintext_amiibosettingsdata[1]." "Country Code ID, from the system which setup this amiibo." + u16 setupdate_year; + u8 setupdate_month; + u8 setupdate_day; + u8 unk_x7c[0x2c];//Normally all-zero? +} NFC_AmiiboSettings; + +/// AmiiboConfig structure, see also here: https://3dbrew.org/wiki/NFC:GetAmiiboConfig +typedef struct { + u16 lastwritedate_year; + u8 lastwritedate_month; + u8 lastwritedate_day; + u16 write_counter; + u8 characterID[3];/// the first element is the collection ID, the second the character in this collection, the third the variant + u8 series;/// ID of the series + u16 amiiboID;/// ID shared by all exact same amiibo. Some amiibo are only distinguished by this one like regular SMB Series Mario and the gold one + u8 type;/// Type of amiibo 0 = figure, 1 = card, 2 = plush + u8 pagex4_byte3; + u16 appdata_size;/// "NFC module writes hard-coded u8 value 0xD8 here. This is the size of the Amiibo AppData, apps can use this with the AppData R/W commands. ..." + u8 zeros[0x30];/// "Unused / reserved: this is cleared by NFC module but never written after that." +} NFC_AmiiboConfig; + +/// Used by nfcInitializeWriteAppData() internally, see also here: https://3dbrew.org/wiki/NFC:GetAppDataInitStruct +typedef struct { + u8 data_x0[0xC]; + u8 data_xc[0x30];/// "The data starting at struct offset 0xC is the 0x30-byte struct used by NFC:InitializeWriteAppData, sent by the user-process." +} NFC_AppDataInitStruct; + +/// Used by nfcWriteAppData() internally, see also: https://3dbrew.org/wiki/NFC:WriteAppData +typedef struct { + u8 id[10];//7-byte UID normally. + u8 id_size; + u8 unused_xb[0x15]; +} NFC_AppDataWriteStruct; + +/** + * @brief Initializes NFC. + * @param type See the NFC_OpType enum. + */ +Result nfcInit(NFC_OpType type); + +/** + * @brief Shuts down NFC. + */ +void nfcExit(void); + +/** + * @brief Gets the NFC service handle. + * @return The NFC service handle. + */ +Handle nfcGetSessionHandle(void); + +/** + * @brief Starts scanning for NFC tags. + * @param inval Unknown. See NFC_STARTSCAN_DEFAULTINPUT. + */ +Result nfcStartScanning(u16 inval); + +/** + * @brief Stops scanning for NFC tags. + */ +void nfcStopScanning(void); + +/** + * @brief Read amiibo NFC data and load in memory. + */ +Result nfcLoadAmiiboData(void); + +/** + * @brief If the tagstate is valid(NFC_TagState_DataReady or 6), it then sets the current tagstate to NFC_TagState_InRange. + */ +Result nfcResetTagScanState(void); + +/** + * @brief This writes the amiibo data stored in memory to the actual amiibo data storage(which is normally the NFC data pages). This can only be used if NFC_LoadAmiiboData() was used previously. + */ +Result nfcUpdateStoredAmiiboData(void); + +/** + * @brief Returns the current NFC tag state. + * @param state Pointer to write NFC tag state. + */ +Result nfcGetTagState(NFC_TagState *state); + +/** + * @brief Returns the current TagInfo. + * @param out Pointer to write the output TagInfo. + */ +Result nfcGetTagInfo(NFC_TagInfo *out); + +/** + * @brief Opens the appdata, when the amiibo appdata was previously initialized. This must be used before reading/writing the appdata. See also: https://3dbrew.org/wiki/NFC:OpenAppData + * @param amiibo_appid Amiibo AppID. See here: https://www.3dbrew.org/wiki/Amiibo + */ +Result nfcOpenAppData(u32 amiibo_appid); + +/** + * @brief This initializes the appdata using the specified input, when the appdata previously wasn't initialized. If the appdata is already initialized, you must first use the amiibo Settings applet menu option labeled "Delete amiibo Game Data". This automatically writes the amiibo data into the actual data storage(normally NFC data pages). See also nfcWriteAppData(). + * @param amiibo_appid amiibo AppID. See also nfcOpenAppData(). + * @param buf Input buffer. + * @param size Buffer size. + */ +Result nfcInitializeWriteAppData(u32 amiibo_appid, const void *buf, size_t size); + +/** + * @brief Reads the appdata. The size must be >=0xD8-bytes, but the actual used size is hard-coded to 0xD8. Note that areas of appdata which were never written to by applications are uninitialized in this output buffer. + * @param buf Output buffer. + * @param size Buffer size. + */ +Result nfcReadAppData(void *buf, size_t size); + +/** + * @brief Writes the appdata, after nfcOpenAppData() was used successfully. The size should be <=0xD8-bytes. See also: https://3dbrew.org/wiki/NFC:WriteAppData + * @param buf Input buffer. + * @param size Buffer size. + * @param taginfo TagInfo from nfcGetTagInfo(). + */ +Result nfcWriteAppData(const void *buf, size_t size, NFC_TagInfo *taginfo); + +/** + * @brief Returns the current AmiiboSettings. + * @param out Pointer to write the output AmiiboSettings. + */ +Result nfcGetAmiiboSettings(NFC_AmiiboSettings *out); + +/** + * @brief Returns the current AmiiboConfig. + * @param out Pointer to write the output AmiiboConfig. + */ +Result nfcGetAmiiboConfig(NFC_AmiiboConfig *out); + +/** + * @brief Starts scanning for NFC tags when initialized with NFC_OpType_RawNFC. See also: https://www.3dbrew.org/wiki/NFC:StartOtherTagScanning + * @param unk0 Same as nfcStartScanning() input. + * @param unk1 Unknown. + */ +Result nfcStartOtherTagScanning(u16 unk0, u32 unk1); + +/** + * @brief This sends a raw NFC command to the tag. This can only be used when initialized with NFC_OpType_RawNFC, and when the TagState is NFC_TagState_InRange. See also: https://www.3dbrew.org/wiki/NFC:SendTagCommand + * @param inbuf Input buffer. + * @param insize Size of the input buffer. + * @param outbuf Output buffer. + * @param outsize Size of the output buffer. + * @param actual_transfer_size Optional output ptr to write the actual output-size to, can be NULL. + * @param microseconds Timing-related field in microseconds. + */ +Result nfcSendTagCommand(const void *inbuf, size_t insize, void *outbuf, size_t outsize, size_t *actual_transfer_size, u64 microseconds); + +/** + * @brief Unknown. This can only be used when initialized with NFC_OpType_RawNFC, and when the TagState is NFC_TagState_InRange. + */ +Result nfcCmd21(void); + +/** + * @brief Unknown. This can only be used when initialized with NFC_OpType_RawNFC, and when the TagState is NFC_TagState_InRange. + */ +Result nfcCmd22(void); + diff --git a/libctru/include/3ds/services/nim.h b/libctru/include/3ds/services/nim.h new file mode 100644 index 0000000000..b8b72f147c --- /dev/null +++ b/libctru/include/3ds/services/nim.h @@ -0,0 +1,151 @@ +/** + * @file nim.h + * @brief NIM (network installation management) service. + * + * This service is used to download and install titles from Nintendo's CDN. + * + * We differentiate between two different kinds of downloads: + * + * - "active" downloads, which are downloads started with @ref NIMS_StartDownload or @ref NIMS_StartDownloadSimple. The process must keep polling the current status using @ref NIMS_GetProgress. + * - "tasks", which are downloads started with @ref NIMS_RegisterTask. These are only processed in sleep mode. + */ +#pragma once + +// FS_MediaType +#include <3ds/services/fs.h> + +/// Mode that NIM downloads/installs a title with. +typedef enum +{ + IM_DEFAULT = 0, ///< Initial installation + IM_UNKNOWN1, ///< Unknown + IM_UNKNOWN2, ///< Unknown + IM_REINSTALL, ///< Reinstall currently installed title; use this if the title is already installed (including updates) +} NIM_InstallationMode; + +/// Current state of a NIM download/installation. +typedef enum +{ + DS_NOT_INITIALIZED = 0, ///< Download not yet initialized + DS_INITIALIZED, ///< Download initialized + DS_DOWNLOAD_TMD, ///< Downloading and installing TMD + DS_PREPARE_SAVE_DATA, ///< Initializing save data + DS_DOWNLOAD_CONTENTS, ///< Downloading and installing contents + DS_WAIT_COMMIT, ///< Waiting before calling AM_CommitImportTitles + DS_COMMITTING, ///< Running AM_CommitImportTitles + DS_FINISHED, ///< Title installation finished + DS_VERSION_ERROR, ///< (unknown error regarding title version) + DS_CREATE_CONTEXT, ///< Creating the .ctx file? + DS_CANNOT_RECOVER, ///< Irrecoverable error encountered (e.g. out of space) + DS_INVALID, ///< Invalid state +} NIM_DownloadState; + +/// Input configuration for NIM download/installation tasks. +typedef struct +{ + u64 titleId; ///< Title ID + u32 version; ///< Title version + u32 unknown_0; ///< Always 0 + u8 ratingAge; ///< Age for the HOME Menu parental controls + u8 mediaType; ///< Media type, see @ref FS_MediaType enum + u8 padding[2]; ///< Padding + u32 unknown_1; ///< Unknown input, seems to be always 0 +} NIM_TitleConfig; + +/// Output struct for NIM downloads/installations in progress. +typedef struct +{ + u32 state; ///< State, see NIM_DownloadState enum + Result lastResult; ///< Last result code in NIM + u64 downloadedSize; ///< Amount of bytes that have been downloaded + u64 totalSize; ///< Amount of bytes that need to be downloaded in total +} NIM_TitleProgress; + +/** + * @brief Initializes nim:s. This uses networking and is blocking. + * @param buffer A buffer for internal use. It must be at least 0x20000 bytes long. + * @param buffer_len Length of the passed buffer. + */ +Result nimsInit(void *buffer, size_t buffer_len); + +/** + * @brief Initializes nim:s with the given TIN. This uses networking and is blocking. + * @param buffer A buffer for internal use. It must be at least 0x20000 bytes long. + * @param buffer_len Length of the passed buffer. + * @param TIN The TIN to initialize nim:s with. If you do not know what a TIN is or why you would want to change it, use @ref nimsInit instead. + */ +Result nimsInitWithTIN(void *buffer, size_t buffer_len, const char *TIN); + +/// Exits nim:s. +void nimsExit(void); + +/// Gets the current nim:s session handle. +Handle *nimsGetSessionHandle(void); + +/** + * @brief Sets an attribute. + * @param attr Name of the attribute. + * @param val Value of the attribute. + */ +Result NIMS_SetAttribute(const char *attr, const char *val); + +/** + * @brief Checks if nim wants a system update. + * @param want_update Set to true if a system update is required. Can be NULL. + */ +Result NIMS_WantUpdate(bool *want_update); + +/** + * @brief Makes a TitleConfig struct for use with @ref NIMS_RegisterTask, @ref NIMS_StartDownload or @ref NIMS_StartDownloadSimple. + * @param cfg Struct to initialize. + * @param titleId Title ID to download and install. + * @param version Version of the title to download and install. + * @param ratingAge Age for which the title is aged; used by parental controls in HOME Menu. + * @param mediaType Media type of the title to download and install. + */ +void NIMS_MakeTitleConfig(NIM_TitleConfig *cfg, u64 titleId, u32 version, u8 ratingAge, FS_MediaType mediaType); + +/** + * @brief Registers a background download task with NIM. These are processed in sleep mode only. + * @param cfg Title config to use. See @ref NIMS_MakeTitleConfig. + * @param name Name of the title in UTF-8. Will be displayed on the HOME Menu. Maximum 73 characters. + * @param maker Name of the maker/publisher in UTF-8. Will be displayed on the HOME Menu. Maximum 37 characters. + */ +Result NIMS_RegisterTask(const NIM_TitleConfig *cfg, const char *name, const char *maker); + +/** + * @brief Checks whether a background download task for the given title is registered with NIM. + * @param titleId Title ID to check for. + * @param registered Whether there is a background download task registered. + */ +Result NIMS_IsTaskRegistered(u64 titleId, bool *registered); + +/** + * @brief Unregisters a background download task. + * @param titleId Title ID whose background download task to cancel. + */ +Result NIMS_UnregisterTask(u64 titleId); + +/** + * @brief Starts an active download with NIM. Progress can be checked with @ref NIMS_GetProcess. Do not exit the process while a download is in progress without calling @ref NIMS_CancelDownload. + * @param cfg Title config to use. See @ref NIMS_MakeTitleConfig. + * @param mode The installation mode to use. See @ref NIM_InstallationMode. + */ +Result NIMS_StartDownload(const NIM_TitleConfig *cfg, NIM_InstallationMode mode); + +/** + * @brief Starts an active download with NIM with default installation mode; cannot reinstall titles. Progress can be checked with @ref NIMS_GetProcess. Do not exit the process while a download is in progress without calling @ref NIMS_CancelDownload. + * @param cfg Title config to use. See @ref NIMS_MakeTitleConfig. + */ +Result NIMS_StartDownloadSimple(const NIM_TitleConfig *cfg); + +/** + * @brief Checks the status of the current active download. + * @param tp Title progress struct to write to. See @ref NIM_TitleProgress. + */ +Result NIMS_GetProgress(NIM_TitleProgress *tp); + +/** + * @brief Cancels the current active download with NIM. + */ +Result NIMS_CancelDownload(void); diff --git a/libctru/include/3ds/services/ns.h b/libctru/include/3ds/services/ns.h new file mode 100644 index 0000000000..1a40bf0a5f --- /dev/null +++ b/libctru/include/3ds/services/ns.h @@ -0,0 +1,51 @@ +/** + * @file ns.h + * @brief NS (Nintendo Shell) service. + */ +#pragma once + +/// Initializes NS. +Result nsInit(void); + +/// Exits NS. +void nsExit(void); + +/** + * @brief Launches a title and the required firmware (only if necessary). + * @param titleid ID of the title to launch, 0 for gamecard, JPN System Settings' titleID for System Settings. + */ +Result NS_LaunchFIRM(u64 titleid); + +/** + * @brief Launches a title. + * @param titleid ID of the title to launch, or 0 for gamecard. + * @param launch_flags Flags used when launching the title. + * @param procid Pointer to write the process ID of the launched title to. + */ +Result NS_LaunchTitle(u64 titleid, u32 launch_flags, u32 *procid); + +/// Terminates the application from which this function is called +Result NS_TerminateTitle(void); +/** + * @brief Launches a title and the required firmware. + * @param titleid ID of the title to launch, 0 for gamecard. + * @param flags Flags for firm-launch. bit0: require an application title-info structure in FIRM paramters to be specified via FIRM parameters. bit1: if clear, NS will check certain Configuration Memory fields. + */ +Result NS_LaunchApplicationFIRM(u64 titleid, u32 flags); + +/** + * @brief Reboots to a title. + * @param mediatype Mediatype of the title. + * @param titleid ID of the title to launch. + */ +Result NS_RebootToTitle(u8 mediatype, u64 titleid); + +/** + * @brief Terminates the process with the specified titleid. + * @param titleid ID of the title to terminate. + * @param timeout Timeout in nanoseconds. Pass 0 if not required. + */ +Result NS_TerminateProcessTID(u64 titleid, u64 timeout); + +/// Reboots the system +Result NS_RebootSystem(void); \ No newline at end of file diff --git a/libctru/include/3ds/services/nwmext.h b/libctru/include/3ds/services/nwmext.h new file mode 100644 index 0000000000..f6697c639b --- /dev/null +++ b/libctru/include/3ds/services/nwmext.h @@ -0,0 +1,13 @@ +#pragma once + +// Initializes NWMEXT. +Result nwmExtInit(void); + +// Exits NWMEXT. +void nwmExtExit(void); + +/** + * @brief Turns wireless on or off. + * @param enableWifi True enables it, false disables it. + */ +Result NWMEXT_ControlWirelessEnabled(bool enableWifi); diff --git a/libctru/include/3ds/services/pmapp.h b/libctru/include/3ds/services/pmapp.h new file mode 100644 index 0000000000..12ae8bf821 --- /dev/null +++ b/libctru/include/3ds/services/pmapp.h @@ -0,0 +1,123 @@ +/** + * @file pmapp.h + * @brief PM (Process Manager) application service. + */ +#pragma once + +#include <3ds/services/fs.h> +#include <3ds/exheader.h> + +/// Launch flags for PM launch commands. +enum { + PMLAUNCHFLAG_NORMAL_APPLICATION = BIT(0), + PMLAUNCHFLAG_LOAD_DEPENDENCIES = BIT(1), + PMLAUNCHFLAG_NOTIFY_TERMINATION = BIT(2), + PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION = BIT(3), + PMLAUNCHFLAG_TERMINATION_NOTIFICATION_MASK = 0xF0, + PMLAUNCHFLAG_FORCE_USE_O3DS_APP_MEM = BIT(8), ///< Forces the usage of the O3DS system mode app memory setting even if N3DS system mode is not "Legacy". Dev4 and Dev5 not supported. N3DS only. + PMLAUNCHFLAG_FORCE_USE_O3DS_MAX_APP_MEM = BIT(9), ///< In conjunction with the above, forces the 96MB app memory setting. N3DS only. + PMLAUNCHFLAG_USE_UPDATE_TITLE = BIT(16), +}; + +/// Initializes pm:app. +Result pmAppInit(void); + +/// Exits pm:app. +void pmAppExit(void); + +/** + * @brief Gets the current pm:app session handle. + * @return The current pm:app session handle. + */ +Handle *pmAppGetSessionHandle(void); + +/** + * @brief Launches a title. + * @param programInfo Program information of the title. + * @param launchFlags Flags to launch the title with. + */ +Result PMAPP_LaunchTitle(const FS_ProgramInfo *programInfo, u32 launchFlags); + +/** + * @brief Launches a title, applying patches. + * @param programInfo Program information of the title. + * @param programInfoUpdate Program information of the update title. + * @param launchFlags Flags to launch the title with. + */ +Result PMAPP_LaunchTitleUpdate(const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate, u32 launchFlags); + +/** + * @brief Gets a title's ExHeader Arm11CoreInfo and SystemInfo flags. + * @param[out] outCoreInfo Pointer to write the ExHeader Arm11CoreInfo to. + * @param[out] outSiFlags Pointer to write the ExHeader SystemInfo flags to. + * @param programInfo Program information of the title. + */ +Result PMAPP_GetTitleExheaderFlags(ExHeader_Arm11CoreInfo* outCoreInfo, ExHeader_SystemInfoFlags* outSiFlags, const FS_ProgramInfo *programInfo); + +/** + * @brief Sets the current FIRM launch parameters. + * @param size Size of the FIRM launch parameter buffer. + * @param in Buffer to retrieve the launch parameters from. + */ +Result PMAPP_SetFIRMLaunchParams(u32 size, const void* in); + +/** + * @brief Gets the current FIRM launch parameters. + * @param size Size of the FIRM launch parameter buffer. + * @param[out] out Buffer to write the launch parameters to. + */ +Result PMAPP_GetFIRMLaunchParams(void *out, u32 size); + +/** + * @brief Sets the current FIRM launch parameters. + * @param firmTidLow Low Title ID of the FIRM title to launch. + * @param size Size of the FIRM launch parameter buffer. + * @param in Buffer to retrieve the launch parameters from. + */ +Result PMAPP_LaunchFIRMSetParams(u32 firmTidLow, u32 size, const void* in); + +/** + * @brief Terminate most processes, to prepare for a reboot or a shutdown. + * @param timeout Time limit in ns for process termination, after which the remaining processes are killed. + */ +Result PMAPP_PrepareForReboot(s64 timeout); + +/** + * @brief Terminates the current Application + * @param timeout Timeout in nanoseconds + */ +Result PMAPP_TerminateCurrentApplication(s64 timeout); + +/** + * @brief Terminates the processes having the specified titleId. + * @param titleId Title ID of the processes to terminate + * @param timeout Timeout in nanoseconds + */ +Result PMAPP_TerminateTitle(u64 titleId, s64 timeout); + +/** + * @brief Terminates the specified process + * @param pid Process-ID of the process to terminate + * @param timeout Timeout in nanoseconds + */ +Result PMAPP_TerminateProcess(u32 pid, s64 timeout); + +/** + * @brief Unregisters a process + * @param tid TitleID of the process to unregister + */ +Result PMAPP_UnregisterProcess(u64 tid); + +/** + * @brief Sets the APPLICATION cputime reslimit. + * @param cpuTime Reslimit value. + * @note cpuTime can be no higher than reslimitdesc[0] & 0x7F in exheader (or 80 if the latter is 0). + */ +Result PMAPP_SetAppResourceLimit(s64 cpuTime); + +/** + * @brief Gets the APPLICATION cputime reslimit. + * @param[out] cpuTime Pointer to write the reslimit value to. + */ +Result PMAPP_GetAppResourceLimit(s64 *outCpuTime); + diff --git a/libctru/include/3ds/services/pmdbg.h b/libctru/include/3ds/services/pmdbg.h new file mode 100644 index 0000000000..18a5962696 --- /dev/null +++ b/libctru/include/3ds/services/pmdbg.h @@ -0,0 +1,41 @@ +/** + * @file pmdbg.h + * @brief PM (Process Manager) debug service. + */ +#pragma once + +#include <3ds/services/pmapp.h> + +/// Initializes pm:dbg. +Result pmDbgInit(void); + +/// Exits pm:dbg. +void pmDbgExit(void); + +/** + * @brief Gets the current pm:dbg session handle. + * @return The current pm:dbg session handle. + */ +Handle *pmDbgGetSessionHandle(void); + +/** + * @brief Enqueues an application for debug after setting cpuTime to 0, and returns a debug handle to it. + * If another process was enqueued, this just calls @ref RunQueuedProcess instead. + * @param[out] Pointer to output the debug handle to. + * @param programInfo Program information of the title. + * @param launchFlags Flags to launch the title with. + */ +Result PMDBG_LaunchAppDebug(Handle *outDebug, const FS_ProgramInfo *programInfo, u32 launchFlags); + +/** + * @brief Launches an application for debug after setting cpuTime to 0. + * @param programInfo Program information of the title. + * @param launchFlags Flags to launch the title with. + */ +Result PMDBG_LaunchApp(const FS_ProgramInfo *programInfo, u32 launchFlags); + +/** + * @brief Runs the queued process and returns a debug handle to it. + * @param[out] Pointer to output the debug handle to. + */ +Result PMDBG_RunQueuedProcess(Handle *outDebug); diff --git a/libctru/include/3ds/services/ps.h b/libctru/include/3ds/services/ps.h new file mode 100644 index 0000000000..4ec3a780a9 --- /dev/null +++ b/libctru/include/3ds/services/ps.h @@ -0,0 +1,116 @@ +/** + * @file ps.h + * @brief PS service. + */ +#pragma once + +/// PS AES algorithms. +typedef enum +{ + PS_ALGORITHM_CBC_ENC, ///< CBC encryption. + PS_ALGORITHM_CBC_DEC, ///< CBC decryption. + PS_ALGORITHM_CTR_ENC, ///< CTR encryption. + PS_ALGORITHM_CTR_DEC, ///< CTR decryption(same as PS_ALGORITHM_CTR_ENC). + PS_ALGORITHM_CCM_ENC, ///< CCM encryption. + PS_ALGORITHM_CCM_DEC, ///< CCM decryption. +} PS_AESAlgorithm; + +/// PS key slots. +typedef enum +{ + PS_KEYSLOT_0D, ///< Key slot 0x0D. + PS_KEYSLOT_2D, ///< Key slot 0x2D. + PS_KEYSLOT_31, ///< Key slot 0x31. + PS_KEYSLOT_38, ///< Key slot 0x38. + PS_KEYSLOT_32, ///< Key slot 0x32. + PS_KEYSLOT_39_DLP, ///< Key slot 0x39. (DLP) + PS_KEYSLOT_2E, ///< Key slot 0x2E. + PS_KEYSLOT_INVALID, ///< Invalid key slot. + PS_KEYSLOT_36, ///< Key slot 0x36. + PS_KEYSLOT_39_NFC ///< Key slot 0x39. (NFC) +} PS_AESKeyType; + +/// RSA context. +typedef struct { + u8 modulo[0x100]; + u8 exponent[0x100]; + u32 rsa_bitsize;//The signature byte size is rsa_bitsize>>3. + u32 unk;//Normally zero? +} psRSAContext; + +/// Initializes PS. +Result psInit(void); + +/** + * @brief Initializes PS with the specified session handle. + * @param handle Session handle. + */ +Result psInitHandle(Handle handle); + +/// Exits PS. +void psExit(void); + +/// Returns the PS session handle. +Handle psGetSessionHandle(void); + +/** + * @brief Signs a RSA signature. + * @param hash SHA256 hash to sign. + * @param ctx RSA context. + * @param signature RSA signature. + */ +Result PS_SignRsaSha256(u8 *hash, psRSAContext *ctx, u8 *signature); + +/** + * @brief Verifies a RSA signature. + * @param hash SHA256 hash to compare with. + * @param ctx RSA context. + * @param signature RSA signature. + */ +Result PS_VerifyRsaSha256(u8 *hash, psRSAContext *ctx, u8 *signature); + +/** + * @brief Encrypts/Decrypts AES data. Does not support AES CCM. + * @param size Size of the data. + * @param in Input buffer. + * @param out Output buffer. + * @param aes_algo AES algorithm to use. + * @param key_type Key type to use. + * @param iv Pointer to the CTR/IV. The output CTR/IV is also written here. + */ +Result PS_EncryptDecryptAes(u32 size, u8* in, u8* out, PS_AESAlgorithm aes_algo, PS_AESKeyType key_type, u8* iv); + +/** + * @brief Encrypts/Decrypts signed AES CCM data. + * When decrypting, if the MAC is invalid, 0xC9010401 is returned. After encrypting the MAC is located at inputbufptr. + * @param in Input buffer. + * @param in_size Size of the input buffer. Must include MAC size when decrypting. + * @param out Output buffer. + * @param out_size Size of the output buffer. Must include MAC size when encrypting. + * @param data_len Length of the data to be encrypted/decrypted. + * @param mac_data_len Length of the MAC data. + * @param mac_len Length of the MAC. + * @param aes_algo AES algorithm to use. + * @param key_type Key type to use. + * @param nonce Pointer to the nonce. + */ +Result PS_EncryptSignDecryptVerifyAesCcm(u8* in, u32 in_size, u8* out, u32 out_size, u32 data_len, u32 mac_data_len, u32 mac_len, PS_AESAlgorithm aes_algo, PS_AESKeyType key_type, u8* nonce); + +/** + * @brief Gets the 64-bit console friend code seed. + * @param seed Pointer to write the friend code seed to. + */ +Result PS_GetLocalFriendCodeSeed(u64* seed); + +/** + * @brief Gets the 32-bit device ID. + * @param device_id Pointer to write the device ID to. + */ +Result PS_GetDeviceId(u32* device_id); + +/** + * @brief Generates cryptographically secure random bytes. + * @param out Pointer to the buffer to write the bytes to. + * @param len Number of bytes to write. + */ +Result PS_GenerateRandomBytes(void* out, size_t len); diff --git a/libctru/include/3ds/services/ptmgets.h b/libctru/include/3ds/services/ptmgets.h new file mode 100644 index 0000000000..4464dc81a8 --- /dev/null +++ b/libctru/include/3ds/services/ptmgets.h @@ -0,0 +1,25 @@ +/** + * @file ptmgets.h + * @brief PTMGETS service. + */ +#pragma once + +#include <3ds/types.h> + +/// Initializes PTMGETS. +Result ptmGetsInit(void); + +/// Exits PTMGETS. +void ptmGetsExit(void); + +/** + * @brief Gets a pointer to the current ptm:gets session handle. + * @return A pointer to the current ptm:gets session handle. + */ +Handle *ptmGetsGetSessionHandle(void); + +/** + * @brief Gets the system time. + * @param[out] outMsY2k The pointer to write the number of milliseconds since 01/01/2000 to. + */ +Result PTMGETS_GetSystemTime(s64 *outMsY2k); diff --git a/libctru/include/3ds/services/ptmsets.h b/libctru/include/3ds/services/ptmsets.h new file mode 100644 index 0000000000..f92b58c03e --- /dev/null +++ b/libctru/include/3ds/services/ptmsets.h @@ -0,0 +1,25 @@ +/** + * @file ptmsets.h + * @brief PTMSETS service. + */ +#pragma once + +#include <3ds/types.h> + +/// Initializes PTMSETS. +Result ptmSetsInit(void); + +/// Exits PTMSETS. +void ptmSetsExit(void); + +/** + * @brief Gets a pointer to the current ptm:sets session handle. + * @return A pointer to the current ptm:sets session handle. + */ +Handle *ptmSetsGetSessionHandle(void); + +/** + * @brief Sets the system time. + * @param msY2k The number of milliseconds since 01/01/2000. + */ +Result PTMSETS_SetSystemTime(s64 msY2k); diff --git a/libctru/include/3ds/services/ptmsysm.h b/libctru/include/3ds/services/ptmsysm.h new file mode 100644 index 0000000000..253ca3d5ab --- /dev/null +++ b/libctru/include/3ds/services/ptmsysm.h @@ -0,0 +1,132 @@ +/** + * @file ptmsysm.h + * @brief PTMSYSM service. + */ +#pragma once + +#include <3ds/types.h> + +/// PDN wake events and MCU interrupts to select, combined with those of other processes +typedef struct PtmWakeEvents { + u32 pdn_wake_events; ///< Written to PDN_WAKE_EVENTS. Don't select bit26 (MCU), PTM will do it automatically. + u32 mcu_interupt_mask; ///< MCU interrupts to check when a MCU wake event happens. +} PtmWakeEvents; + +typedef struct { + PtmWakeEvents exit_sleep_events; ///< Wake events for which the system should fully wake up. + PtmWakeEvents continue_sleep_events; ///< Wake events for which the system should return to sleep. +} PtmSleepConfig; + +enum { + // Sleep FSM notification IDs + PTMNOTIFID_SLEEP_REQUESTED = 0x101, ///< @ref PTMSYSM_RequestSleep has been called (ack = 3) + PTMNOTIFID_SLEEP_DENIED = 0x102, ///< The sleep request has been denied by @ref PTMSYSM_ReplyToSleepQuery(true) (no ack required). + PTMNOTIFID_SLEEP_ALLOWED = 0x103, ///< The sleep request has been allowed by @ref PTMSYSM_ReplyToSleepQuery(false) (ack = 1). + PTMNOTIFID_GOING_TO_SLEEP = 0x104, ///< All processes not having "RunnableOnSleep" have been paused & the system is about to go to sleep (ack = 0). + PTMNOTIFID_FULLY_WAKING_UP = 0x105, ///< The system has been woken up, and the paused processes are about to be unpaused (ack = 1). + PTMNOTIFID_FULLY_AWAKE = 0x106, ///< The system is fully awake (no ack required). + PTMNOTIFID_HALF_AWAKE = 0x107, ///< The system has been woken up but is about to go to sleep again (ack = 2). + + PTMNOTIFID_SHUTDOWN = 0x108, ///< The system is about to power off or reboot. + + PTMNOTIFID_BATTERY_VERY_LOW = 0x211, ///< The battery level has reached 5% or below. + PTMNOTIFID_BATTERY_LOW = 0x212, ///< The battery level has reached 10% or below. +}; + +/// See @ref PTMSYSM_NotifySleepPreparationComplete. Corresponds to the number of potentially remaning notifs. until sleep/wakeup. +static inline s32 ptmSysmGetNotificationAckValue(u32 id) +{ + static const s32 values[] = { 3, -1, 1, 0, 0, -1, 2 }; + if (id < PTMNOTIFID_SLEEP_REQUESTED || id > PTMNOTIFID_HALF_AWAKE) + return -1; + return values[id - PTMNOTIFID_SLEEP_REQUESTED]; +} + +/// Initializes ptm:sysm. +Result ptmSysmInit(void); + +/// Exits ptm:sysm. +void ptmSysmExit(void); + +/** + * @brief Gets a pointer to the current ptm:sysm session handle. + * @return A pointer to the current ptm:sysm session handle. + */ +Handle *ptmSysmGetSessionHandle(void); + +/// Requests to enter sleep mode. +Result PTMSYSM_RequestSleep(void); + +/** + * @brief Accepts or denies the incoming sleep mode request. + * @param deny Whether or not to deny the sleep request. + * @note If deny = false, this is equivalent to calling @ref PTMSYSM_NotifySleepPreparationComplete(3) + */ +Result PTMSYSM_ReplyToSleepQuery(bool deny); + +/** + * @brief Acknowledges the current sleep notification and advance the internal sleep mode FSM. All subscribers must reply. + * @param ackValue Use @ref ptmSysmGetNotificationAckValue + * @note @ref PTMNOTIFID_SLEEP_DENIED and @ref PTMNOTIFID_FULLY_AWAKE don't require this. + */ +Result PTMSYSM_NotifySleepPreparationComplete(s32 ackValue); + +/** + * @brief Sets the wake events (two sets: when to fully wake up and when to return to sleep). + * @param sleepConfig Pointer to the two sets of wake events. + * @note Can only be called just before acknowledging @ref PTMNOTIFID_GOING_TO_SLEEP or @ref PTMNOTIFID_HALF_AWAKE. + */ +Result PTMSYSM_SetWakeEvents(const PtmSleepConfig *sleepConfig); + +/** + * @brief Gets the wake reason (only the first applicable wake event is taken into account). + * @param sleepConfig Pointer to the two sets of wake events. Only the relevant set will be filled. + */ +Result PTMSYSM_GetWakeReason(PtmSleepConfig *outSleepConfig); + +/// Cancels the "half-awake" state and fully wakes up the 3DS after some delay. +Result PTMSYSM_Awaken(void); + +/** + * @brief Sets the user time by updating the user time offset. + * @param msY2k The number of milliseconds since 01/01/2000. + */ +Result PTMSYSM_SetUserTime(s64 msY2k); + +/// Invalidates the "system time" (cfg block 0x30002) +Result PTMSYSM_InvalidateSystemTime(void); + +/** + * @brief Reads the time and date coming from the RTC and converts the result. + * @param[out] outMsY2k The pointer to write the number of milliseconds since 01/01/2000 to. + */ +Result PTMSYSM_GetRtcTime(s64 *outMsY2k); + +/** + * @brief Writes the time and date coming to the RTC, after conversion. + * @param msY2k The number of milliseconds since 01/01/2000. + */ +Result PTMSYSM_SetRtcTime(s64 msY2k); + +/** + * @brief Returns 1 if it's a New 3DS, otherwise 0. + */ +Result PTMSYSM_CheckNew3DS(void); + +/** + * @brief Configures the New 3DS' CPU clock speed and L2 cache. + * @param value Bit0: enable higher clock, Bit1: enable L2 cache. + */ +Result PTMSYSM_ConfigureNew3DSCPU(u8 value); + +/** + * @brief Trigger a hardware system shutdown via the MCU. + * @param timeout: timeout passed to PMApp:ShutdownAsync (PrepareForReboot). + */ +Result PTMSYSM_ShutdownAsync(u64 timeout); + +/** + * @brief Trigger a hardware system reboot via the MCU. + * @param timeout: timeout passed to PMApp:ShutdownAsync (PrepareForReboot). + */ +Result PTMSYSM_RebootAsync(u64 timeout); diff --git a/libctru/include/3ds/services/ptmu.h b/libctru/include/3ds/services/ptmu.h new file mode 100644 index 0000000000..cb987e53bd --- /dev/null +++ b/libctru/include/3ds/services/ptmu.h @@ -0,0 +1,53 @@ +/** + * @file ptmu.h + * @brief PTMU service. + */ +#pragma once + +/// Initializes PTMU. +Result ptmuInit(void); + +/// Exits PTMU. +void ptmuExit(void); + +/** + * @brief Gets a pointer to the current ptm:u session handle. + * @return A pointer to the current ptm:u session handle. + */ +Handle *ptmuGetSessionHandle(void); + +/** + * @brief Gets the system's current shell state. + * @param out Pointer to write the current shell state to. (0 = closed, 1 = open) + */ +Result PTMU_GetShellState(u8 *out); + +/** + * @brief Gets the system's current battery level. + * @param out Pointer to write the current battery level to. (0-5) + */ +Result PTMU_GetBatteryLevel(u8 *out); + +/** + * @brief Gets the system's current battery charge state. + * @param out Pointer to write the current battery charge state to. (0 = not charging, 1 = charging) + */ +Result PTMU_GetBatteryChargeState(u8 *out); + +/** + * @brief Gets the system's current pedometer state. + * @param out Pointer to write the current pedometer state to. (0 = not counting, 1 = counting) + */ +Result PTMU_GetPedometerState(u8 *out); + +/** + * @brief Gets the pedometer's total step count. + * @param steps Pointer to write the total step count to. + */ +Result PTMU_GetTotalStepCount(u32 *steps); + +/** + * @brief Gets whether the adapter is plugged in or not + * @param out Pointer to write the adapter state to. + */ +Result PTMU_GetAdapterState(bool *out); diff --git a/libctru/include/3ds/services/pxidev.h b/libctru/include/3ds/services/pxidev.h new file mode 100644 index 0000000000..96144e88f3 --- /dev/null +++ b/libctru/include/3ds/services/pxidev.h @@ -0,0 +1,79 @@ +/** + * @file pxidev.h + * @brief Gamecard PXI service. + */ +#pragma once + +#include <3ds/services/fs.h> + +/// Card SPI wait operation type. +typedef enum { + WAIT_NONE = 0, ///< Do not wait. + WAIT_SLEEP = 1, ///< Sleep for the specified number of nanoseconds. + WAIT_IREQ_RETURN = 2, ///< Wait for IREQ, return if timeout. + WAIT_IREQ_CONTINUE = 3 ///< Wait for IREQ, continue if timeout. +} PXIDEV_WaitType; + +/// Card SPI register deassertion type. +typedef enum { + DEASSERT_NONE = 0, ///< Do not deassert. + DEASSERT_BEFORE_WAIT = 1, ///< Deassert before waiting. + DEASSERT_AFTER_WAIT = 2 ///< Deassert after waiting. +} PXIDEV_DeassertType; + +/// Card SPI transfer buffer. +typedef struct { + void* ptr; ///< Data pointer. + u32 size; ///< Data size. + u8 transferOption; ///< Transfer options. See @ref pxiDevMakeTransferOption + u64 waitOperation; ///< Wait operation. See @ref pxiDevMakeWaitOperation +} PXIDEV_SPIBuffer; + +/// Initializes pxi:dev. +Result pxiDevInit(void); + +/// Shuts down pxi:dev. +void pxiDevExit(void); + +/** + * @brief Creates a packed card SPI transfer option value. + * @param baudRate Baud rate to use when transferring. + * @param busMode Bus mode to use when transferring. + * @return A packed card SPI transfer option value. + */ +static inline u8 pxiDevMakeTransferOption(FS_CardSpiBaudRate baudRate, FS_CardSpiBusMode busMode) +{ + return (baudRate & 0x3F) | ((busMode & 0x3) << 6); +} + +/** + * @brief Creates a packed card SPI wait operation value. + * @param waitType Type of wait to perform. + * @param deassertType Type of register deassertion to perform. + * @param timeout Timeout, in nanoseconds, to wait, if applicable. + * @return A packed card SPI wait operation value. + */ +static inline u64 pxiDevMakeWaitOperation(PXIDEV_WaitType waitType, PXIDEV_DeassertType deassertType, u64 timeout) +{ + return (waitType & 0xF) | ((deassertType & 0xF) << 4) | ((timeout & 0xFFFFFFFFFFFFFF) << 8); +} + +/** + * @brief Performs multiple card SPI writes and reads. + * @param header Header to lead the transfers with. Must be, at most, 8 bytes in size. + * @param writeBuffer1 Buffer to make first transfer from. + * @param readBuffer1 Buffer to receive first response to. + * @param writeBuffer2 Buffer to make second transfer from. + * @param readBuffer2 Buffer to receive second response to. + * @param footer Footer to follow the transfers with. Must be, at most, 8 bytes in size. Wait operation is unused. + */ +Result PXIDEV_SPIMultiWriteRead(PXIDEV_SPIBuffer* header, PXIDEV_SPIBuffer* writeBuffer1, PXIDEV_SPIBuffer* readBuffer1, PXIDEV_SPIBuffer* writeBuffer2, PXIDEV_SPIBuffer* readBuffer2, PXIDEV_SPIBuffer* footer); + +/** + * @brief Performs a single card SPI write and read. + * @param bytesRead Pointer to output the number of bytes received to. + * @param initialWaitOperation Wait operation to perform before transferring data. + * @param writeBuffer Buffer to transfer data from. + * @param readBuffer Buffer to receive data to. + */ +Result PXIDEV_SPIWriteRead(u32* bytesRead, u64 initialWaitOperation, PXIDEV_SPIBuffer* writeBuffer, PXIDEV_SPIBuffer* readBuffer); diff --git a/libctru/include/3ds/services/pxipm.h b/libctru/include/3ds/services/pxipm.h new file mode 100644 index 0000000000..8b877c6215 --- /dev/null +++ b/libctru/include/3ds/services/pxipm.h @@ -0,0 +1,42 @@ +/** + * @file pxipm.h + * @brief Process Manager PXI service + */ + +#pragma once + +#include <3ds/exheader.h> +#include <3ds/services/fs.h> + +/// Initializes PxiPM. +Result pxiPmInit(void); + +/// Exits PxiPM. +void pxiPmExit(void); + +/** + * @brief Gets the current PxiPM session handle. + * @return The current PxiPM session handle. + */ +Handle *pxiPmGetSessionHandle(void); + +/** + * @brief Retrives the exheader information set(s) (SCI+ACI) about a program. + * @param exheaderInfos[out] Pointer to the output exheader information set. + * @param programHandle The program handle. + */ +Result PXIPM_GetProgramInfo(ExHeader_Info *exheaderInfo, u64 programHandle); + +/** + * @brief Loads a program and registers it to Process9. + * @param programHandle[out] Pointer to the output the program handle to. + * @param programInfo Information about the program to load. + * @param updateInfo Information about the program update to load. + */ +Result PXIPM_RegisterProgram(u64 *programHandle, const FS_ProgramInfo *programInfo, const FS_ProgramInfo *updateInfo); + +/** + * @brief Unloads a program and unregisters it from Process9. + * @param programHandle The program handle. + */ +Result PXIPM_UnregisterProgram(u64 programHandle); diff --git a/libctru/include/3ds/services/qtm.h b/libctru/include/3ds/services/qtm.h new file mode 100644 index 0000000000..363fc2f255 --- /dev/null +++ b/libctru/include/3ds/services/qtm.h @@ -0,0 +1,57 @@ +/** + * @file qtm.h + * @brief QTM service. + */ +#pragma once + +//See also: http://3dbrew.org/wiki/QTM_Services + +/// Head tracking coordinate pair. +typedef struct { + float x; ///< X coordinate. + float y; ///< Y coordinate. +} QTM_HeadTrackingInfoCoord; + +/// Head tracking info. +typedef struct { + u8 flags[5]; ///< Flags. + u8 padding[3]; ///< Padding. + float floatdata_x08; ///< Unknown. Not used by System_Settings. + QTM_HeadTrackingInfoCoord coords0[4]; ///< Head coordinates. + u32 unk_x2c[5]; ///< Unknown. Not used by System_Settings. +} QTM_HeadTrackingInfo; + +/// Initializes QTM. +Result qtmInit(void); + +/// Exits QTM. +void qtmExit(void); + +/** + * @brief Checks whether QTM is initialized. + * @return Whether QTM is initialized. + */ +bool qtmCheckInitialized(void); + +/** + * @brief Checks whether a head is fully detected. + * @param info Tracking info to check. + */ +bool qtmCheckHeadFullyDetected(QTM_HeadTrackingInfo *info); + +/** + * @brief Converts QTM coordinates to screen coordinates. + * @param coord Coordinates to convert. + * @param screen_width Width of the screen. Can be NULL to use the default value for the top screen. + * @param screen_height Height of the screen. Can be NULL to use the default value for the top screen. + * @param x Pointer to output the screen X coordinate to. + * @param y Pointer to output the screen Y coordinate to. + */ +Result qtmConvertCoordToScreen(QTM_HeadTrackingInfoCoord *coord, float *screen_width, float *screen_height, u32 *x, u32 *y); + +/** + * @brief Gets the current head tracking info. + * @param val Normally 0. + * @param out Pointer to write head tracking info to. + */ +Result QTM_GetHeadTrackingInfo(u64 val, QTM_HeadTrackingInfo* out); diff --git a/libctru/include/3ds/services/soc.h b/libctru/include/3ds/services/soc.h new file mode 100644 index 0000000000..1a716244b3 --- /dev/null +++ b/libctru/include/3ds/services/soc.h @@ -0,0 +1,148 @@ +/** + * @file soc.h + * @brief SOC service for sockets communications + * + * After initializing this service you will be able to use system calls from netdb.h, sys/socket.h etc. + */ +#pragma once +#include +#include + +/// The config level to be used with @ref SOCU_GetNetworkOpt +#define SOL_CONFIG 0xfffe + +/// Options to be used with @ref SOCU_GetNetworkOpt +typedef enum +{ + NETOPT_MAC_ADDRESS = 0x1004, ///< The mac address of the interface (u32 mac[6]) + NETOPT_ARP_TABLE = 0x3002, ///< The ARP table @see SOCU_ARPTableEntry + NETOPT_IP_INFO = 0x4003, ///< The cureent IP setup @see SOCU_IPInfo + NETOPT_IP_MTU = 0x4004, ///< The value of the IP MTU (u32) + NETOPT_ROUTING_TABLE = 0x4006, ///< The routing table @see SOCU_RoutingTableEntry + NETOPT_UDP_NUMBER = 0x8002, ///< The number of sockets in the UDP table (u32) + NETOPT_UDP_TABLE = 0x8003, ///< The table of opened UDP sockets @see SOCU_UDPTableEntry + NETOPT_TCP_NUMBER = 0x9002, ///< The number of sockets in the TCP table (u32) + NETOPT_TCP_TABLE = 0x9003, ///< The table of opened TCP sockets @see SOCU_TCPTableEntry + NETOPT_DNS_TABLE = 0xB003, ///< The table of the DNS servers @see SOCU_DNSTableEntry -- Returns a buffer of size 336 but only 2 entries are set ? + NETOPT_DHCP_LEASE_TIME = 0xC001, ///< The DHCP lease time remaining, in seconds +} NetworkOpt; + +/// One entry of the ARP table retrieved by using @ref SOCU_GetNetworkOpt and @ref NETOPT_ARP_TABLE +typedef struct +{ + u32 unk0; // often 2 ? state ? + struct in_addr ip; ///< The IPv4 address associated to the entry + u8 mac[6]; ///< The MAC address of associated to the entry + u8 padding[2]; +} SOCU_ARPTableEntry; + +/// Structure returned by @ref SOCU_GetNetworkOpt when using @ref NETOPT_IP_INFO +typedef struct +{ + struct in_addr ip; ///< Current IPv4 address + struct in_addr netmask; ///< Current network mask + struct in_addr broadcast; ///< Current network broadcast address +} SOCU_IPInfo; + +// Linux netstat flags +// NOTE : there are probably other flags supported, if you can forge ICMP requests please check for D and M flags + +/** The route uses a gateway */ +#define ROUTING_FLAG_G 0x01 + +/// One entry of the routing table retrieved by using @ref SOCU_GetNetworkOpt and @ref NETOPT_ROUTING_TABLE +typedef struct +{ + struct in_addr dest_ip; ///< Destination IP address of the route + struct in_addr netmask; ///< Mask used for this route + struct in_addr gateway; ///< Gateway address to reach the network + u32 flags; ///< Linux netstat flags @see ROUTING_FLAG_G + u64 time; ///< number of milliseconds since 1st Jan 1900 00:00. +} SOCU_RoutingTableEntry; + +/// One entry of the UDP sockets table retrieved by using @ref SOCU_GetNetworkOpt and @ref NETOPT_UDP_TABLE +typedef struct +{ + struct sockaddr_storage local; ///< Local address information + struct sockaddr_storage remote; ///< Remote address information +} SOCU_UDPTableEntry; + +///@name TCP states +///@{ +#define TCP_STATE_CLOSED 1 +#define TCP_STATE_LISTEN 2 +#define TCP_STATE_ESTABLISHED 5 +#define TCP_STATE_FINWAIT1 6 +#define TCP_STATE_FINWAIT2 7 +#define TCP_STATE_CLOSE_WAIT 8 +#define TCP_STATE_LAST_ACK 9 +#define TCP_STATE_TIME_WAIT 11 +///@} + +/// One entry of the TCP sockets table retrieved by using @ref SOCU_GetNetworkOpt and @ref NETOPT_TCP_TABLE +typedef struct +{ + u32 state; ///< @see TCP states defines + struct sockaddr_storage local; ///< Local address information + struct sockaddr_storage remote; ///< Remote address information +} SOCU_TCPTableEntry; + +/// One entry of the DNS servers table retrieved by using @ref SOCU_GetNetworkOpt and @ref NETOPT_DNS_TABLE +typedef struct +{ + u32 family; /// Family of the address of the DNS server + struct in_addr ip; /// IP of the DNS server + u8 padding[12]; // matches the length required for IPv6 addresses +} SOCU_DNSTableEntry; + + +/** + * @brief Initializes the SOC service. + * @param context_addr Address of a page-aligned (0x1000) buffer to be used. + * @param context_size Size of the buffer, a multiple of 0x1000. + * @note The specified context buffer can no longer be accessed by the process which called this function, since the userland permissions for this block are set to no-access. + */ +Result socInit(u32 *context_addr, u32 context_size); + +/** + * @brief Closes the soc service. + * @note You need to call this in order to be able to use the buffer again. + */ +Result socExit(void); + +// this is supposed to be in unistd.h but newlib only puts it for cygwin, waiting for newlib patch from dkA +/** + * @brief Gets the system's host ID. + * @return The system's host ID. + */ +long gethostid(void); + +// this is supposed to be in unistd.h but newlib only puts it for cygwin, waiting for newlib patch from dkA +int gethostname(char *name, size_t namelen); + +int SOCU_ShutdownSockets(void); + +int SOCU_CloseSockets(void); + +/** + * @brief Retrieves information from the network configuration. Similar to getsockopt(). + * @param level Only value allowed seems to be @ref SOL_CONFIG + * @param optname The option to be retrieved + * @param optval Will contain the output of the command + * @param optlen Size of the optval buffer, will be updated to hold the size of the output + * @return 0 if successful. -1 if failed, and errno will be set accordingly. Can also return a system error code. + */ +int SOCU_GetNetworkOpt(int level, NetworkOpt optname, void * optval, socklen_t * optlen); + +/** + * @brief Gets the system's IP address, netmask, and subnet broadcast + * @return error + */ +int SOCU_GetIPInfo(struct in_addr *ip, struct in_addr *netmask, struct in_addr *broadcast); + +/** + * @brief Adds a global socket. + * @param sockfd The socket fd. + * @return error + */ +int SOCU_AddGlobalSocket(int sockfd); diff --git a/libctru/include/3ds/services/srvpm.h b/libctru/include/3ds/services/srvpm.h new file mode 100644 index 0000000000..767481a020 --- /dev/null +++ b/libctru/include/3ds/services/srvpm.h @@ -0,0 +1,45 @@ +/** + * @file srvpm.h + * @brief srv:pm service. + */ +#pragma once + +/// Initializes srv:pm and the service API. +Result srvPmInit(void); + +/// Exits srv:pm and the service API. +void srvPmExit(void); + +/** + * @brief Gets the current srv:pm session handle. + * @return The current srv:pm session handle. + */ +Handle *srvPmGetSessionHandle(void); + +/** + * @brief Publishes a notification to a process. + * @param notificationId ID of the notification. + * @param process Process to publish to. + */ +Result SRVPM_PublishToProcess(u32 notificationId, Handle process); + +/** + * @brief Publishes a notification to all processes. + * @param notificationId ID of the notification. + */ +Result SRVPM_PublishToAll(u32 notificationId); + +/** + * @brief Registers a process with SRV. + * @param pid ID of the process. + * @param count Number of services within the service access control data. + * @param serviceAccessControlList Service Access Control list. + */ +Result SRVPM_RegisterProcess(u32 pid, u32 count, const char (*serviceAccessControlList)[8]); + +/** + * @brief Unregisters a process with SRV. + * @param pid ID of the process. + */ +Result SRVPM_UnregisterProcess(u32 pid); + diff --git a/libctru/include/3ds/services/sslc.h b/libctru/include/3ds/services/sslc.h new file mode 100644 index 0000000000..9a86f2a1ca --- /dev/null +++ b/libctru/include/3ds/services/sslc.h @@ -0,0 +1,251 @@ +/** + * @file sslc.h + * @brief SSLC(TLS) service. https://3dbrew.org/wiki/SSL_Services + */ +#pragma once + +/// sslc context. +typedef struct { + Handle servhandle; ///< Service handle. + u32 sslchandle; ///< SSLC handle. + Handle sharedmem_handle; +} sslcContext; + +typedef enum { + SSLC_DefaultRootCert_Nintendo_CA = 0x1, //"Nintendo CA" + SSLC_DefaultRootCert_Nintendo_CA_G2 = 0x2, //"Nintendo CA - G2" + SSLC_DefaultRootCert_Nintendo_CA_G3 = 0x3, //"Nintendo CA - G3" + SSLC_DefaultRootCert_Nintendo_Class2_CA = 0x4, //"Nintendo Class 2 CA" + SSLC_DefaultRootCert_Nintendo_Class2_CA_G2 = 0x5, //"Nintendo Class 2 CA - G2" + SSLC_DefaultRootCert_Nintendo_Class2_CA_G3 = 0x6, //"Nintendo Class 2 CA - G3" + SSLC_DefaultRootCert_CyberTrust = 0x7, //"GTE CyberTrust Global Root" + SSLC_DefaultRootCert_AddTrust_External_CA = 0x8, //"AddTrust External CA Root" + SSLC_DefaultRootCert_COMODO = 0x9, //"COMODO RSA Certification Authority" + SSLC_DefaultRootCert_USERTrust = 0xA, //"USERTrust RSA Certification Authority" + SSLC_DefaultRootCert_DigiCert_EV = 0xB //"DigiCert High Assurance EV Root CA" +} SSLC_DefaultRootCert; + +typedef enum { + SSLC_DefaultClientCert_ClCertA = 0x40 +} SSLC_DefaultClientCert; + +/// sslc options. https://www.3dbrew.org/wiki/SSL_Services#SSLOpt +enum { + SSLCOPT_Default = 0, + SSLCOPT_DisableVerify = BIT(9), // "Disables server cert verification when set." + SSLCOPT_TLSv10 = BIT(11) // "Use TLSv1.0." +}; + +/// Initializes SSLC. Normally session_handle should be 0. When non-zero this will use the specified handle for the main-service-session without using the Initialize command, instead of using srvGetServiceHandle. +Result sslcInit(Handle session_handle); + +/// Exits SSLC. +void sslcExit(void); + +/** + * @brief Creates a RootCertChain. + * @param RootCertChain_contexthandle Output contexthandle. + */ +Result sslcCreateRootCertChain(u32 *RootCertChain_contexthandle); + +/** + * @brief Destroys a RootCertChain. + * @param RootCertChain_contexthandle RootCertChain contexthandle. + */ +Result sslcDestroyRootCertChain(u32 RootCertChain_contexthandle); + +/** + * @brief Adds a trusted RootCA cert to a RootCertChain. + * @param RootCertChain_contexthandle RootCertChain to use. + * @param cert Pointer to the DER cert. + * @param certsize Size of the DER cert. + */ +Result sslcAddTrustedRootCA(u32 RootCertChain_contexthandle, const u8 *cert, u32 certsize, u32 *cert_contexthandle); + +/** + * @brief Adds a default RootCA cert to a RootCertChain. + * @param RootCertChain_contexthandle RootCertChain to use. + * @param certID ID of the cert to add. + * @param cert_contexthandle Optional, the cert contexthandle can be written here. + */ +Result sslcRootCertChainAddDefaultCert(u32 RootCertChain_contexthandle, SSLC_DefaultRootCert certID, u32 *cert_contexthandle); + +/** + * @brief Removes the specified cert from the RootCertChain. + * @param RootCertChain_contexthandle RootCertChain to use. + * @param cert_contexthandle Cert contexthandle to remove from the RootCertChain. + */ +Result sslcRootCertChainRemoveCert(u32 RootCertChain_contexthandle, u32 cert_contexthandle); + +/** + * @brief Creates an unknown CertChain. + * @param CertChain_contexthandle Output contexthandle. + */ +Result sslcCreate8CertChain(u32 *CertChain_contexthandle); + +/** + * @brief Destroys a CertChain from sslcCreate8CertChain(). + * @param CertChain_contexthandle CertChain contexthandle. + */ +Result sslcDestroy8CertChain(u32 CertChain_contexthandle); + +/** + * @brief Adds a cert to a CertChain from sslcCreate8CertChain(). + * @param CertChain_contexthandle CertChain to use. + * @param cert Pointer to the cert. + * @param certsize Size of the cert. + */ +Result sslc8CertChainAddCert(u32 CertChain_contexthandle, const u8 *cert, u32 certsize, u32 *cert_contexthandle); + +/** + * @brief Adds a default cert to a CertChain from sslcCreate8CertChain(). Not actually usable since no certIDs are implemented in SSL-module for this. + * @param CertChain_contexthandle CertChain to use. + * @param certID ID of the cert to add. + * @param cert_contexthandle Optional, the cert contexthandle can be written here. + */ +Result sslc8CertChainAddDefaultCert(u32 CertChain_contexthandle, u8 certID, u32 *cert_contexthandle); + +/** + * @brief Removes the specified cert from the CertChain from sslcCreate8CertChain(). + * @param CertChain_contexthandle CertChain to use. + * @param cert_contexthandle Cert contexthandle to remove from the CertChain. + */ +Result sslc8CertChainRemoveCert(u32 CertChain_contexthandle, u32 cert_contexthandle); + +/** + * @brief Opens a new ClientCert-context. + * @param cert Pointer to the DER cert. + * @param certsize Size of the DER cert. + * @param key Pointer to the DER key. + * @param keysize Size of the DER key. + * @param ClientCert_contexthandle Output contexthandle. + */ +Result sslcOpenClientCertContext(const u8 *cert, u32 certsize, const u8 *key, u32 keysize, u32 *ClientCert_contexthandle); + +/** + * @brief Opens a ClientCert-context with a default certID. + * @param certID ID of the ClientCert to use. + * @param ClientCert_contexthandle Output contexthandle. + */ +Result sslcOpenDefaultClientCertContext(SSLC_DefaultClientCert certID, u32 *ClientCert_contexthandle); + +/** + * @brief Closes the specified ClientCert-context. + * @param ClientCert_contexthandle ClientCert-context to use. + */ +Result sslcCloseClientCertContext(u32 ClientCert_contexthandle); + +/** + * @brief This uses ps:ps SeedRNG internally. + */ +Result sslcSeedRNG(void); + +/** + * @brief This uses ps:ps GenerateRandomData internally. + * @param buf Output buffer. + * @param size Output size. + */ +Result sslcGenerateRandomData(u8 *buf, u32 size); + +/** + * @brief Creates a sslc context. + * @param context sslc context. + * @param sockfd Socket fd, this code automatically uses the required SOC command before using the actual sslc command. + * @param input_opt Input sslc options bitmask. + * @param hostname Server hostname. + */ +Result sslcCreateContext(sslcContext *context, int sockfd, u32 input_opt, const char *hostname); + +/* + * @brief Destroys a sslc context. The associated sockfd must be closed manually. + * @param context sslc context. + */ +Result sslcDestroyContext(sslcContext *context); + +/* + * @brief Starts the TLS connection. If successful, this will not return until the connection is ready for data-transfer via sslcRead/sslcWrite. + * @param context sslc context. + * @param internal_retval Optional ptr where the internal_retval will be written. The value is only copied to here by this function when no error occurred. + * @param out Optional ptr where an output u32 will be written. The value is only copied to here by this function when no error occurred. + */ +Result sslcStartConnection(sslcContext *context, int *internal_retval, u32 *out); + +/* + * @brief Receive data over the network connection. + * @param context sslc context. + * @param buf Output buffer. + * @param len Size to receive. + * @param peek When true, this is equivalent to setting the recv() MSG_PEEK flag. + * @return When this isn't an error-code, this is the total transferred data size. + */ +Result sslcRead(sslcContext *context, void *buf, size_t len, bool peek); + +/* + * @brief Send data over the network connection. + * @param context sslc context. + * @param buf Input buffer. + * @param len Size to send. + * @return When this isn't an error-code, this is the total transferred data size. + */ +Result sslcWrite(sslcContext *context, const void *buf, size_t len); + +/* + * @brief Set the RootCertChain for the specified sslc context. + * @param context sslc context. + * @param handle RootCertChain contexthandle. + */ +Result sslcContextSetRootCertChain(sslcContext *context, u32 handle); + +/* + * @brief Set the ClientCert-context for the specified sslc context. + * @param context sslc context. + * @param handle ClientCert contexthandle. + */ +Result sslcContextSetClientCert(sslcContext *context, u32 handle); + +/* + * @brief Set the context for a CertChain from sslcCreate8CertChain(), for the specified sslc context. This needs updated once it's known what this context is for. + * @param context sslc context. + * @param handle contexthandle. + */ +Result sslcContextSetHandle8(sslcContext *context, u32 handle); + +/* + * @brief Clears the options field bits for the context using the specified bitmask. + * @param context sslc context. + * @param bitmask opt bitmask. + */ +Result sslcContextClearOpt(sslcContext *context, u32 bitmask); + +/* + * @brief This copies two strings from context state to the specified output buffers. Each string is only copied if it was successfully loaded. The maxsizes include the nul-terminator. This can only be used if sslcStartConnection() was already used successfully. + * @param context sslc context. + * @param outprotocols Output buffer for a string containing all protocol versions supported by SSL-module. + * @param outprotocols_maxsize Max size of the above output buffer. + * @param outcipher Output buffer for a string containing the cipher suite currently being used. + * @param outcipher_maxsize Max size of the above output buffer. + */ +Result sslcContextGetProtocolCipher(sslcContext *context, char *outprotocols, u32 outprotocols_maxsize, char *outcipher, u32 outcipher_maxsize); + +/* + * @brief This loads an u32 from the specified context state. This needs updated once it's known what this field is for. + * @param context sslc context. + * @param out Output ptr to write the value to. + */ +Result sslcContextGetState(sslcContext *context, u32 *out); + +/* + * @brief This initializes sharedmem for the specified context. + * @param context sslc context. + * @param buf Sharedmem buffer with address aligned to 0x1000-bytes. + * @param size Sharedmem size aligned to 0x1000-bytes. + */ +Result sslcContextInitSharedmem(sslcContext *context, u8 *buf, u32 size); + +/* + * @brief This loads the specified cert. This needs updated once it's known what the cert format is and what the cert is used for later. + * @param buf Input cert. + * @param size Cert size. + */ +Result sslcAddCert(sslcContext *context, const u8 *buf, u32 size); + diff --git a/libctru/include/3ds/services/uds.h b/libctru/include/3ds/services/uds.h new file mode 100644 index 0000000000..a2ecc17b6b --- /dev/null +++ b/libctru/include/3ds/services/uds.h @@ -0,0 +1,368 @@ +/** + * @file uds.h + * @brief UDS(NWMUDS) local-WLAN service. https://3dbrew.org/wiki/NWM_Services + */ +#pragma once + +/// Maximum number of nodes(devices) that can be connected to the network. +#define UDS_MAXNODES 16 + +/// Broadcast value for NetworkNodeID / alias for all NetworkNodeIDs. +#define UDS_BROADCAST_NETWORKNODEID 0xFFFF + +/// NetworkNodeID for the host(the first node). +#define UDS_HOST_NETWORKNODEID 0x1 + +/// Default recv_buffer_size that can be used for udsBind() input / code which uses udsBind() internally. +#define UDS_DEFAULT_RECVBUFSIZE 0x2E30 + +/// Max size of user data-frames. +#define UDS_DATAFRAME_MAXSIZE 0x5C6 + +/// Check whether a fatal udsSendTo error occured(some error(s) from udsSendTo() can be ignored, but the frame won't be sent when that happens). +#define UDS_CHECK_SENDTO_FATALERROR(x) (R_FAILED(x) && x!=0xC86113F0) + +/// Node info struct. +typedef struct { + u64 uds_friendcodeseed;//UDS version of the FriendCodeSeed. + + union { + u8 usercfg[0x18];//This is the first 0x18-bytes from this config block: https://www.3dbrew.org/wiki/Config_Savegame#0x000A0000_Block + + struct { + u16 username[10]; + + u16 unk_x1c;//Unknown, normally zero. Set to 0x0 with the output from udsScanBeacons(). + u8 flag;//"u8 flag, unknown. Originates from the u16 bitmask in the beacon node-list header. This flag is normally 0 since that bitmask is normally 0?" + u8 pad_x1f;//Unknown, normally zero. + }; + }; + + //The rest of this is initialized by NWM-module. + u16 NetworkNodeID; + u16 pad_x22;//Unknown, normally zero? + u32 word_x24;//Normally zero? +} udsNodeInfo; + +/// Connection status struct. +typedef struct { + u32 status; + u32 unk_x4; + u16 cur_NetworkNodeID;//"u16 NetworkNodeID for this device." + u16 unk_xa; + u32 unk_xc[0x20>>2]; + + u8 total_nodes; + u8 max_nodes; + u16 node_bitmask;//"This is a bitmask of NetworkNodeIDs: bit0 for NetworkNodeID 0x1(host), bit1 for NetworkNodeID 0x2(first original client), and so on." +} udsConnectionStatus; + +/// Network struct stored as big-endian. +typedef struct { + u8 host_macaddress[6]; + u8 channel;//Wifi channel for this network. If you want to create a network on a specific channel instead of the system selecting it, you can set this to a non-zero channel value. + u8 pad_x7; + + u8 initialized_flag;//Must be non-zero otherwise NWM-module will use zeros internally instead of the actual field data, for most/all(?) of the fields in this struct. + + u8 unk_x9[3]; + + u8 oui_value[3];//"This is the OUI value for use with the beacon tags. Normally this is 001F32." + u8 oui_type;//"OUI type (21/0x15)" + + u32 wlancommID;//Unique local-WLAN communications ID for each application. + u8 id8;//Additional ID that can be used by the application for different types of networks. + u8 unk_x15; + + u16 attributes;//See the UDSNETATTR enum values below. + + u32 networkID; + + u8 total_nodes; + u8 max_nodes; + u8 unk_x1e; + u8 unk_x1f; + u8 unk_x20[0x1f]; + + u8 appdata_size; + u8 appdata[0xc8]; +} udsNetworkStruct; + +typedef struct { + u32 BindNodeID; + Handle event; + bool spectator; +} udsBindContext; + +/// General NWM input structure used for AP scanning. +typedef struct { + u16 unk_x0; + u16 unk_x2; + u16 unk_x4; + u16 unk_x6; + + u8 mac_address[6]; + + u8 unk_xe[0x26];//Not initialized by dlp. +} nwmScanInputStruct; + +/// General NWM output structure from AP scanning. +typedef struct { + u32 maxsize;//"Max output size, from the command request." + u32 size;//"Total amount of output data written relative to struct+0. 0xC when there's no entries." + u32 total_entries;//"Total entries, 0 for none. " + + //The entries start here. +} nwmBeaconDataReplyHeader; + +/// General NWM output structure from AP scanning, for each entry. +typedef struct { + u32 size;//"Size of this entire entry. The next entry starts at curentry_startoffset+curentry_size." + u8 unk_x4; + u8 channel;//Wifi channel for the AP. + u8 unk_x6; + u8 unk_x7; + u8 mac_address[6];//"AP MAC address." + u8 unk_xe[6]; + u32 unk_x14; + u32 val_x1c;//"Value 0x1C(size of this header and/or offset to the actual beacon data)." + + //The actual beacon data starts here. +} nwmBeaconDataReplyEntry; + +/// Output structure generated from host scanning output. +typedef struct { + nwmBeaconDataReplyEntry datareply_entry; + udsNetworkStruct network; + udsNodeInfo nodes[UDS_MAXNODES]; +} udsNetworkScanInfo; + +enum { + UDSNETATTR_DisableConnectSpectators = BIT(0), //When set new Spectators are not allowed to connect. + UDSNETATTR_DisableConnectClients = BIT(1), //When set new Clients are not allowed to connect. + UDSNETATTR_x4 = BIT(2), //Unknown what this bit is for. + UDSNETATTR_Default = BIT(15), //Unknown what this bit is for. +}; + +enum { + UDS_SENDFLAG_Default = BIT(0), //Unknown what this bit is for. + UDS_SENDFLAG_Broadcast = BIT(1) //When set, broadcast the data frame via the destination MAC address even when UDS_BROADCAST_NETWORKNODEID isn't used. +}; + +typedef enum { + UDSCONTYPE_Client = 0x1, + UDSCONTYPE_Spectator = 0x2 +} udsConnectionType; + +/** + * @brief Initializes UDS. + * @param sharedmem_size This must be 0x1000-byte aligned. + * @param username Optional custom UTF-8 username(converted to UTF-16 internally) that other nodes on the UDS network can use. If not set the username from system-config is used. Max len is 10 characters without NUL-terminator. + */ +Result udsInit(size_t sharedmem_size, const char *username); + +/// Exits UDS. +void udsExit(void); + +/** + * @brief Generates a NodeInfo struct with data loaded from system-config. + * @param nodeinfo Output NodeInfo struct. + * @param username If set, this is the UTF-8 string to convert for use in the struct. Max len is 10 characters without NUL-terminator. + */ +Result udsGenerateNodeInfo(udsNodeInfo *nodeinfo, const char *username); + +/** + * @brief Loads the UTF-16 username stored in the input NodeInfo struct, converted to UTF-8. + * @param nodeinfo Input NodeInfo struct. + * @param username This is the output UTF-8 string. Max len is 10 characters without NUL-terminator. + */ +Result udsGetNodeInfoUsername(const udsNodeInfo *nodeinfo, char *username); + +/** + * @brief Checks whether a NodeInfo struct was initialized by NWM-module(not any output from udsGenerateNodeInfo()). + * @param nodeinfo Input NodeInfo struct. + */ +bool udsCheckNodeInfoInitialized(const udsNodeInfo *nodeinfo); + +/** + * @brief Generates a default NetworkStruct for creating networks. + * @param network The output struct. + * @param wlancommID Unique local-WLAN communications ID for each application. + * @param id8 Additional ID that can be used by the application for different types of networks. + * @param max_nodes Maximum number of nodes(devices) that can be connected to the network, including the host. + */ +void udsGenerateDefaultNetworkStruct(udsNetworkStruct *network, u32 wlancommID, u8 id8, u8 max_nodes); + +/** + * @brief Scans for networks via beacon-scanning. + * @param outbuf Buffer which will be used by the beacon-scanning command and for the data parsing afterwards. Normally there's no need to use the contents of this buffer once this function returns. + * @param maxsize Max size of the buffer. + * @Param networks Ptr where the allocated udsNetworkScanInfo array buffer is written. The allocsize is sizeof(udsNetworkScanInfo)*total_networks. + * @Param total_networks Total number of networks stored under the networks buffer. + * @param wlancommID Unique local-WLAN communications ID for each application. + * @param id8 Additional ID that can be used by the application for different types of networks. + * @param host_macaddress When set, this code will only return network info from the specified host MAC address. + * @connected When not connected to a network this *must* be false. When connected to a network this *must* be true. + */ +Result udsScanBeacons(void *outbuf, size_t maxsize, udsNetworkScanInfo **networks, size_t *total_networks, u32 wlancommID, u8 id8, const u8 *host_macaddress, bool connected); + +/** + * @brief This can be used by the host to set the appdata contained in the broadcasted beacons. + * @param buf Appdata buffer. + * @param size Size of the input appdata. + */ +Result udsSetApplicationData(const void *buf, size_t size); + +/** + * @brief This can be used while on a network(host/client) to get the appdata from the current beacon. + * @param buf Appdata buffer. + * @param size Max size of the output buffer. + * @param actual_size If set, the actual size of the appdata written into the buffer is stored here. + */ +Result udsGetApplicationData(void *buf, size_t size, size_t *actual_size); + +/** + * @brief This can be used with a NetworkStruct, from udsScanBeacons() mainly, for getting the appdata. + * @param buf Appdata buffer. + * @param size Max size of the output buffer. + * @param actual_size If set, the actual size of the appdata written into the buffer is stored here. + */ +Result udsGetNetworkStructApplicationData(const udsNetworkStruct *network, void *buf, size_t size, size_t *actual_size); + +/** + * @brief Create a bind. + * @param bindcontext The output bind context. + * @param NetworkNodeID This is the NetworkNodeID which this bind can receive data from. + * @param spectator False for a regular bind, true for a spectator. + * @param data_channel This is an arbitrary value to use for data-frame filtering. This bind will only receive data frames which contain a matching data_channel value, which was specified by udsSendTo(). The data_channel must be non-zero. + * @param recv_buffer_size Size of the buffer under sharedmem used for temporarily storing received data-frames which are then loaded by udsPullPacket(). The system requires this to be >=0x5F4. UDS_DEFAULT_RECVBUFSIZE can be used for this. + */ +Result udsBind(udsBindContext *bindcontext, u16 NetworkNodeID, bool spectator, u8 data_channel, u32 recv_buffer_size); + +/** + * @brief Remove a bind. + * @param bindcontext The bind context. + */ +Result udsUnbind(udsBindContext *bindcontext); + +/** + * @brief Waits for the bind event to occur, or checks if the event was signaled. This event is signaled every time new data is available via udsPullPacket(). + * @return Always true. However if wait=false, this will return false if the event wasn't signaled. + * @param bindcontext The bind context. + * @param nextEvent Whether to discard the current event and wait for the next event. + * @param wait When true this will not return until the event is signaled. When false this checks if the event was signaled without waiting for it. + */ +bool udsWaitDataAvailable(const udsBindContext *bindcontext, bool nextEvent, bool wait); + +/** + * @brief Receives data over the network. This data is loaded from the recv_buffer setup by udsBind(). When a node disconnects, this will still return data from that node until there's no more frames from that node in the recv_buffer. + * @param bindcontext Bind context. + * @param buf Output receive buffer. + * @param size Size of the buffer. + * @param actual_size If set, the actual size written into the output buffer is stored here. This is zero when no data was received. + * @param src_NetworkNodeID If set, the source NetworkNodeID is written here. This is zero when no data was received. + */ +Result udsPullPacket(const udsBindContext *bindcontext, void *buf, size_t size, size_t *actual_size, u16 *src_NetworkNodeID); + +/** + * @brief Sends data over the network. + * @param dst_NetworkNodeID Destination NetworkNodeID. + * @param data_channel See udsBind(). + * @param flags Send flags, see the UDS_SENDFLAG enum values. + * @param buf Input send buffer. + * @param size Size of the buffer. + */ +Result udsSendTo(u16 dst_NetworkNodeID, u8 data_channel, u8 flags, const void *buf, size_t size); + +/** + * @brief Gets the wifi channel currently being used. + * @param channel Output channel. + */ +Result udsGetChannel(u8 *channel); + +/** + * @brief Starts hosting a new network. + * @param network The NetworkStruct, you can use udsGenerateDefaultNetworkStruct() for generating this. + * @param passphrase Raw input passphrase buffer. + * @param passphrase_size Size of the passphrase buffer. + * @param context Optional output bind context which will be created for this host, with NetworkNodeID=UDS_BROADCAST_NETWORKNODEID. + * @param data_channel This is the data_channel value which will be passed to udsBind() internally. + * @param recv_buffer_size This is the recv_buffer_size value which will be passed to udsBind() internally. + */ +Result udsCreateNetwork(const udsNetworkStruct *network, const void *passphrase, size_t passphrase_size, udsBindContext *context, u8 data_channel, u32 recv_buffer_size); + +/** + * @brief Connect to a network. + * @param network The NetworkStruct, you can use udsScanBeacons() for this. + * @param passphrase Raw input passphrase buffer. + * @param passphrase_size Size of the passphrase buffer. + * @param context Optional output bind context which will be created for this host. + * @param recv_NetworkNodeID This is the NetworkNodeID passed to udsBind() internally. + * @param connection_type Type of connection, see the udsConnectionType enum values. + * @param data_channel This is the data_channel value which will be passed to udsBind() internally. + * @param recv_buffer_size This is the recv_buffer_size value which will be passed to udsBind() internally. + */ +Result udsConnectNetwork(const udsNetworkStruct *network, const void *passphrase, size_t passphrase_size, udsBindContext *context, u16 recv_NetworkNodeID, udsConnectionType connection_type, u8 data_channel, u32 recv_buffer_size); + +/** + * @brief Stop hosting the network. + */ +Result udsDestroyNetwork(void); + +/** + * @brief Disconnect this client device from the network. + */ +Result udsDisconnectNetwork(void); + +/** + * @brief This can be used by the host to force-disconnect client(s). + * @param NetworkNodeID Target NetworkNodeID. UDS_BROADCAST_NETWORKNODEID can be used to disconnect all clients. + */ +Result udsEjectClient(u16 NetworkNodeID); + +/** + * @brief This can be used by the host to force-disconnect the spectators. Afterwards new spectators will not be allowed to connect until udsAllowSpectators() is used. + */ +Result udsEjectSpectator(void); + +/** + * @brief This can be used by the host to update the network attributes. If bitmask 0x4 is clear in the input bitmask, this clears that bit in the value before actually writing the value into state. Normally you should use the below wrapper functions. + * @param bitmask Bitmask to clear/set in the attributes. See the UDSNETATTR enum values. + * @param flag When false, bit-clear, otherwise bit-set. + */ +Result udsUpdateNetworkAttribute(u16 bitmask, bool flag); + +/** + * @brief This uses udsUpdateNetworkAttribute() for (un)blocking new connections to this host. + * @param block When true, block the specified connection types(bitmask set). Otherwise allow them(bitmask clear). + * @param clients When true, (un)block regular clients. + * @param flag When true, update UDSNETATTR_x4. Normally this should be false. + */ +Result udsSetNewConnectionsBlocked(bool block, bool clients, bool flag); + +/** + * @brief This uses udsUpdateNetworkAttribute() for unblocking new spectator connections to this host. See udsEjectSpectator() for blocking new spectators. + */ +Result udsAllowSpectators(void); + +/** + * @brief This loads the current ConnectionStatus struct. + * @param output Output ConnectionStatus struct. + */ +Result udsGetConnectionStatus(udsConnectionStatus *output); + +/** + * @brief Waits for the ConnectionStatus event to occur, or checks if the event was signaled. This event is signaled when the data from udsGetConnectionStatus() was updated internally. + * @return Always true. However if wait=false, this will return false if the event wasn't signaled. + * @param nextEvent Whether to discard the current event and wait for the next event. + * @param wait When true this will not return until the event is signaled. When false this checks if the event was signaled without waiting for it. + */ +bool udsWaitConnectionStatusEvent(bool nextEvent, bool wait); + +/** + * @brief This loads a NodeInfo struct for the specified NetworkNodeID. The broadcast alias can't be used with this. + * @param NetworkNodeID Target NetworkNodeID. + * @param output Output NodeInfo struct. + */ +Result udsGetNodeInformation(u16 NetworkNodeID, udsNodeInfo *output); + diff --git a/libctru/include/3ds/services/y2r.h b/libctru/include/3ds/services/y2r.h new file mode 100644 index 0000000000..635cdbf1cb --- /dev/null +++ b/libctru/include/3ds/services/y2r.h @@ -0,0 +1,500 @@ +/** + * @file y2r.h + * @brief Y2R service for hardware YUV->RGB conversions + */ +#pragma once +#include <3ds/types.h> + +/** + * @brief Input color formats + * + * For the 16-bit per component formats, bits 15-8 are padding and 7-0 contains the value. + */ +typedef enum +{ + INPUT_YUV422_INDIV_8 = 0x0, ///< 8-bit per component, planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples).\n Usually named YUV422P. + INPUT_YUV420_INDIV_8 = 0x1, ///< 8-bit per component, planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples).\n Usually named YUV420P. + INPUT_YUV422_INDIV_16 = 0x2, ///< 16-bit per component, planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per 2x1 Y samples).\n Usually named YUV422P16. + INPUT_YUV420_INDIV_16 = 0x3, ///< 16-bit per component, planar YUV 4:2:0, 24bpp, (1 Cr & Cb sample per 2x2 Y samples).\n Usually named YUV420P16. + INPUT_YUV422_BATCH = 0x4, ///< 8-bit per component, packed YUV 4:2:2, 16bpp, (Y0 Cb Y1 Cr).\n Usually named YUYV422. +} Y2RU_InputFormat; + +/** + * @brief Output color formats + * + * Those are the same as the framebuffer and GPU texture formats. + */ +typedef enum +{ + OUTPUT_RGB_32 = 0x0, ///< 32-bit RGBA8888. The alpha component is the 8-bit value set by @ref Y2RU_SetAlpha + OUTPUT_RGB_24 = 0x1, ///< 24-bit RGB888. + OUTPUT_RGB_16_555 = 0x2, ///< 16-bit RGBA5551. The alpha bit is the 7th bit of the alpha value set by @ref Y2RU_SetAlpha + OUTPUT_RGB_16_565 = 0x3, ///< 16-bit RGB565. +} Y2RU_OutputFormat; + +/// Rotation to be applied to the output. +typedef enum +{ + ROTATION_NONE = 0x0, ///< No rotation. + ROTATION_CLOCKWISE_90 = 0x1, ///< Clockwise 90 degrees. + ROTATION_CLOCKWISE_180 = 0x2, ///< Clockwise 180 degrees. + ROTATION_CLOCKWISE_270 = 0x3, ///< Clockwise 270 degrees. +} Y2RU_Rotation; + +/** + * @brief Block alignment of output + * + * Defines the way the output will be laid out in memory. + */ +typedef enum +{ + BLOCK_LINE = 0x0, ///< The result buffer will be laid out in linear format, the usual way. + BLOCK_8_BY_8 = 0x1, ///< The result will be stored as 8x8 blocks in Z-order.\n Useful for textures since it is the format used by the PICA200. +} Y2RU_BlockAlignment; + +/** + * @brief Coefficients of the YUV->RGB conversion formula. + * + * A set of coefficients configuring the RGB to YUV conversion. Coefficients 0-4 are unsigned 2.8 + * fixed pointer numbers representing entries on the conversion matrix, while coefficient 5-7 are + * signed 11.5 fixed point numbers added as offsets to the RGB result. + * + * The overall conversion process formula is: + * @code + * R = trunc((rgb_Y * Y + r_V * V) + 0.75 + r_offset) + * G = trunc((rgb_Y * Y - g_U * U - g_V * V) + 0.75 + g_offset) + * B = trunc((rgb_Y * Y + b_U * U ) + 0.75 + b_offset) + * @endcode + */ +typedef struct +{ + u16 rgb_Y; ///< RGB per unit Y. + u16 r_V; ///< Red per unit V. + u16 g_V; ///< Green per unit V. + u16 g_U; ///< Green per unit U. + u16 b_U; ///< Blue per unit U. + u16 r_offset; ///< Red offset. + u16 g_offset; ///< Green offset. + u16 b_offset; ///< Blue offset. +} Y2RU_ColorCoefficients; + +/** + * @brief Preset conversion coefficients based on ITU standards for the YUV->RGB formula. + * + * For more details refer to @ref Y2RU_ColorCoefficients + */ +typedef enum +{ + COEFFICIENT_ITU_R_BT_601 = 0x0, ///< Coefficients from the ITU-R BT.601 standard with PC ranges. + COEFFICIENT_ITU_R_BT_709 = 0x1, ///< Coefficients from the ITU-R BT.709 standard with PC ranges. + COEFFICIENT_ITU_R_BT_601_SCALING = 0x2, ///< Coefficients from the ITU-R BT.601 standard with TV ranges. + COEFFICIENT_ITU_R_BT_709_SCALING = 0x3, ///< Coefficients from the ITU-R BT.709 standard with TV ranges. +} Y2RU_StandardCoefficient; + +/** + * @brief Structure used to configure all parameters at once. + * + * You can send a batch of configuration parameters using this structure and @ref Y2RU_SetConversionParams. + */ +typedef struct +{ + Y2RU_InputFormat input_format : 8; ///< Value passed to @ref Y2RU_SetInputFormat + Y2RU_OutputFormat output_format : 8; ///< Value passed to @ref Y2RU_SetOutputFormat + Y2RU_Rotation rotation : 8; ///< Value passed to @ref Y2RU_SetRotation + Y2RU_BlockAlignment block_alignment : 8; ///< Value passed to @ref Y2RU_SetBlockAlignment + s16 input_line_width; ///< Value passed to @ref Y2RU_SetInputLineWidth + s16 input_lines; ///< Value passed to @ref Y2RU_SetInputLines + Y2RU_StandardCoefficient standard_coefficient : 8; ///< Value passed to @ref Y2RU_SetStandardCoefficient + u8 unused; ///< Unused. + u16 alpha; ///< Value passed to @ref Y2RU_SetAlpha +} Y2RU_ConversionParams; + +/// Dithering weights. +typedef struct +{ + u16 w0_xEven_yEven; ///< Weight 0 for even X, even Y. + u16 w0_xOdd_yEven; ///< Weight 0 for odd X, even Y. + u16 w0_xEven_yOdd; ///< Weight 0 for even X, odd Y. + u16 w0_xOdd_yOdd; ///< Weight 0 for odd X, odd Y. + u16 w1_xEven_yEven; ///< Weight 1 for even X, even Y. + u16 w1_xOdd_yEven; ///< Weight 1 for odd X, even Y. + u16 w1_xEven_yOdd; ///< Weight 1 for even X, odd Y. + u16 w1_xOdd_yOdd; ///< Weight 1 for odd X, odd Y. + u16 w2_xEven_yEven; ///< Weight 2 for even X, even Y. + u16 w2_xOdd_yEven; ///< Weight 2 for odd X, even Y. + u16 w2_xEven_yOdd; ///< Weight 2 for even X, odd Y. + u16 w2_xOdd_yOdd; ///< Weight 2 for odd X, odd Y. + u16 w3_xEven_yEven; ///< Weight 3 for even X, even Y. + u16 w3_xOdd_yEven; ///< Weight 3 for odd X, even Y. + u16 w3_xEven_yOdd; ///< Weight 3 for even X, odd Y. + u16 w3_xOdd_yOdd; ///< Weight 3 for odd X, odd Y. +} Y2RU_DitheringWeightParams; + +/** + * @brief Initializes the y2r service. + * + * This will internally get the handle of the service, and on success call Y2RU_DriverInitialize. + */ +Result y2rInit(void); + +/** + * @brief Closes the y2r service. + * + * This will internally call Y2RU_DriverFinalize and close the handle of the service. + */ +void y2rExit(void); + +/** + * @brief Used to configure the input format. + * @param format Input format to use. + * + * @note Prefer using @ref Y2RU_SetConversionParams if you have to set multiple parameters. + */ +Result Y2RU_SetInputFormat(Y2RU_InputFormat format); + +/** + * @brief Gets the configured input format. + * @param format Pointer to output the input format to. + */ +Result Y2RU_GetInputFormat(Y2RU_InputFormat* format); + +/** + * @brief Used to configure the output format. + * @param format Output format to use. + * + * @note Prefer using @ref Y2RU_SetConversionParams if you have to set multiple parameters. + */ +Result Y2RU_SetOutputFormat(Y2RU_OutputFormat format); + +/** + * @brief Gets the configured output format. + * @param format Pointer to output the output format to. + */ +Result Y2RU_GetOutputFormat(Y2RU_OutputFormat* format); + +/** + * @brief Used to configure the rotation of the output. + * @param rotation Rotation to use. + * + * It seems to apply the rotation per batch of 8 lines, so the output will be (height/8) images of size 8 x width. + * + * @note Prefer using @ref Y2RU_SetConversionParams if you have to set multiple parameters. + */ +Result Y2RU_SetRotation(Y2RU_Rotation rotation); + +/** + * @brief Gets the configured rotation. + * @param rotation Pointer to output the rotation to. + */ +Result Y2RU_GetRotation(Y2RU_Rotation* rotation); + +/** + * @brief Used to configure the alignment of the output buffer. + * @param alignment Alignment to use. + * + * @note Prefer using @ref Y2RU_SetConversionParams if you have to set multiple parameters. + */ +Result Y2RU_SetBlockAlignment(Y2RU_BlockAlignment alignment); + +/** + * @brief Gets the configured alignment. + * @param alignment Pointer to output the alignment to. + */ +Result Y2RU_GetBlockAlignment(Y2RU_BlockAlignment* alignment); + +/** + * @brief Sets whether to use spacial dithering. + * @param enable Whether to use spacial dithering. + */ +Result Y2RU_SetSpacialDithering(bool enable); + +/** + * @brief Gets whether to use spacial dithering. + * @param enable Pointer to output the spacial dithering state to. + */ +Result Y2RU_GetSpacialDithering(bool* enabled); + +/** + * @brief Sets whether to use temporal dithering. + * @param enable Whether to use temporal dithering. + */ +Result Y2RU_SetTemporalDithering(bool enable); + +/** + * @brief Gets whether to use temporal dithering. + * @param enable Pointer to output the temporal dithering state to. + */ +Result Y2RU_GetTemporalDithering(bool* enabled); + + +/** + * @brief Used to configure the width of the image. + * @param line_width Width of the image in pixels. Must be a multiple of 8, up to 1024. + * + * @note Prefer using @ref Y2RU_SetConversionParams if you have to set multiple parameters. + */ +Result Y2RU_SetInputLineWidth(u16 line_width); + +/** + * @brief Gets the configured input line width. + * @param line_width Pointer to output the line width to. + */ +Result Y2RU_GetInputLineWidth(u16* line_width); + +/** + * @brief Used to configure the height of the image. + * @param num_lines Number of lines to be converted. + * + * A multiple of 8 seems to be preferred. + * If using the @ref BLOCK_8_BY_8 mode, it must be a multiple of 8. + * + * @note Prefer using @ref Y2RU_SetConversionParams if you have to set multiple parameters. + */ +Result Y2RU_SetInputLines(u16 num_lines); + +/** + * @brief Gets the configured number of input lines. + * @param num_lines Pointer to output the input lines to. + */ +Result Y2RU_GetInputLines(u16* num_lines); + +/** + * @brief Used to configure the color conversion formula. + * @param coefficients Coefficients to use. + * + * See @ref Y2RU_ColorCoefficients for more information about the coefficients. + * + * @note Prefer using @ref Y2RU_SetConversionParams if you have to set multiple parameters. + */ +Result Y2RU_SetCoefficients(const Y2RU_ColorCoefficients* coefficients); + +/** + * @brief Gets the configured color coefficients. + * @param num_lines Pointer to output the coefficients to. + */ +Result Y2RU_GetCoefficients(Y2RU_ColorCoefficients* coefficients); + +/** + * @brief Used to configure the color conversion formula with ITU stantards coefficients. + * @param coefficient Standard coefficient to use. + * + * See @ref Y2RU_ColorCoefficients for more information about the coefficients. + * + * @note Prefer using @ref Y2RU_SetConversionParams if you have to set multiple parameters. + */ +Result Y2RU_SetStandardCoefficient(Y2RU_StandardCoefficient coefficient); + +/** + * @brief Gets the color coefficient parameters of a standard coefficient. + * @param coefficients Pointer to output the coefficients to. + * @param standardCoeff Standard coefficient to check. + */ +Result Y2RU_GetStandardCoefficient(Y2RU_ColorCoefficients* coefficients, Y2RU_StandardCoefficient standardCoeff); + +/** + * @brief Used to configure the alpha value of the output. + * @param alpha 8-bit value to be used for the output when the format requires it. + * + * @note Prefer using @ref Y2RU_SetConversionParams if you have to set multiple parameters. + */ +Result Y2RU_SetAlpha(u16 alpha); + +/** + * @brief Gets the configured output alpha value. + * @param alpha Pointer to output the alpha value to. + */ +Result Y2RU_GetAlpha(u16* alpha); + +/** + * @brief Used to enable the end of conversion interrupt. + * @param should_interrupt Enables the interrupt if true, disable it if false. + * + * It is possible to fire an interrupt when the conversion is finished, and that the DMA is done copying the data. + * This interrupt will then be used to fire an event. See @ref Y2RU_GetTransferEndEvent. + * By default the interrupt is enabled. + * + * @note It seems that the event can be fired too soon in some cases, depending the transfer_unit size.\n Please see the note at @ref Y2RU_SetReceiving + */ +Result Y2RU_SetTransferEndInterrupt(bool should_interrupt); + +/** + * @brief Gets whether the transfer end interrupt is enabled. + * @param should_interrupt Pointer to output the interrupt state to. + */ +Result Y2RU_GetTransferEndInterrupt(bool* should_interrupt); + +/** + * @brief Gets an handle to the end of conversion event. + * @param end_event Pointer to the event handle to be set to the end of conversion event. It isn't necessary to create or close this handle. + * + * To enable this event you have to use @code{C} Y2RU_SetTransferEndInterrupt(true);@endcode + * The event will be triggered when the corresponding interrupt is fired. + * + * @note It is recommended to use a timeout when waiting on this event, as it sometimes (but rarely) isn't triggered. + */ +Result Y2RU_GetTransferEndEvent(Handle* end_event); + +/** + * @brief Configures the Y plane buffer. + * @param src_buf A pointer to the beginning of your Y data buffer. + * @param image_size The total size of the data buffer. + * @param transfer_unit Specifies the size of 1 DMA transfer. Usually set to 1 line. This has to be a divisor of image_size. + * @param transfer_gap Specifies the gap (offset) to be added after each transfer. Can be used to convert images with stride or only a part of it. + * + * @warning transfer_unit+transfer_gap must be less than 32768 (0x8000) + * + * This specifies the Y data buffer for the planar input formats (INPUT_YUV42*_INDIV_*). + * The actual transfer will only happen after calling @ref Y2RU_StartConversion. + */ +Result Y2RU_SetSendingY(const void* src_buf, u32 image_size, s16 transfer_unit, s16 transfer_gap); + +/** + * @brief Configures the U plane buffer. + * @param src_buf A pointer to the beginning of your Y data buffer. + * @param image_size The total size of the data buffer. + * @param transfer_unit Specifies the size of 1 DMA transfer. Usually set to 1 line. This has to be a divisor of image_size. + * @param transfer_gap Specifies the gap (offset) to be added after each transfer. Can be used to convert images with stride or only a part of it. + * + * @warning transfer_unit+transfer_gap must be less than 32768 (0x8000) + * + * This specifies the U data buffer for the planar input formats (INPUT_YUV42*_INDIV_*). + * The actual transfer will only happen after calling @ref Y2RU_StartConversion. + */ +Result Y2RU_SetSendingU(const void* src_buf, u32 image_size, s16 transfer_unit, s16 transfer_gap); + +/** + * @brief Configures the V plane buffer. + * @param src_buf A pointer to the beginning of your Y data buffer. + * @param image_size The total size of the data buffer. + * @param transfer_unit Specifies the size of 1 DMA transfer. Usually set to 1 line. This has to be a divisor of image_size. + * @param transfer_gap Specifies the gap (offset) to be added after each transfer. Can be used to convert images with stride or only a part of it. + * + * @warning transfer_unit+transfer_gap must be less than 32768 (0x8000) + * + * This specifies the V data buffer for the planar input formats (INPUT_YUV42*_INDIV_*). + * The actual transfer will only happen after calling @ref Y2RU_StartConversion. + */ +Result Y2RU_SetSendingV(const void* src_buf, u32 image_size, s16 transfer_unit, s16 transfer_gap); + +/** + * @brief Configures the YUYV source buffer. + * @param src_buf A pointer to the beginning of your Y data buffer. + * @param image_size The total size of the data buffer. + * @param transfer_unit Specifies the size of 1 DMA transfer. Usually set to 1 line. This has to be a divisor of image_size. + * @param transfer_gap Specifies the gap (offset) to be added after each transfer. Can be used to convert images with stride or only a part of it. + * + * @warning transfer_unit+transfer_gap must be less than 32768 (0x8000) + * + * This specifies the YUYV data buffer for the packed input format @ref INPUT_YUV422_BATCH. + * The actual transfer will only happen after calling @ref Y2RU_StartConversion. + */ +Result Y2RU_SetSendingYUYV(const void* src_buf, u32 image_size, s16 transfer_unit, s16 transfer_gap); + +/** + * @brief Configures the destination buffer. + * @param src_buf A pointer to the beginning of your destination buffer in FCRAM + * @param image_size The total size of the data buffer. + * @param transfer_unit Specifies the size of 1 DMA transfer. Usually set to 1 line. This has to be a divisor of image_size. + * @param transfer_gap Specifies the gap (offset) to be added after each transfer. Can be used to convert images with stride or only a part of it. + * + * This specifies the destination buffer of the conversion. + * The actual transfer will only happen after calling @ref Y2RU_StartConversion. + * The buffer does NOT need to be allocated in the linear heap. + * + * @warning transfer_unit+transfer_gap must be less than 32768 (0x8000) + * + * @note + * It seems that depending on the size of the image and of the transfer unit,\n + * it is possible for the end of conversion interrupt to be triggered right after the conversion began.\n + * One line as transfer_unit seems to trigger this issue for 400x240, setting to 2/4/8 lines fixes it. + * + * @note Setting a transfer_unit of 4 or 8 lines seems to bring the best results in terms of speed for a 400x240 image. + */ +Result Y2RU_SetReceiving(void* dst_buf, u32 image_size, s16 transfer_unit, s16 transfer_gap); + +/** + * @brief Checks if the DMA has finished sending the Y buffer. + * @param is_done Pointer to the boolean that will hold the result. + * + * True if the DMA has finished transferring the Y plane, false otherwise. To be used with @ref Y2RU_SetSendingY. + */ +Result Y2RU_IsDoneSendingY(bool* is_done); + +/** + * @brief Checks if the DMA has finished sending the U buffer. + * @param is_done Pointer to the boolean that will hold the result. + * + * True if the DMA has finished transferring the U plane, false otherwise. To be used with @ref Y2RU_SetSendingU. + */ +Result Y2RU_IsDoneSendingU(bool* is_done); + +/** + * @brief Checks if the DMA has finished sending the V buffer. + * @param is_done Pointer to the boolean that will hold the result. + * + * True if the DMA has finished transferring the V plane, false otherwise. To be used with @ref Y2RU_SetSendingV. + */ +Result Y2RU_IsDoneSendingV(bool* is_done); + +/** + * @brief Checks if the DMA has finished sending the YUYV buffer. + * @param is_done Pointer to the boolean that will hold the result. + * + * True if the DMA has finished transferring the YUYV buffer, false otherwise. To be used with @ref Y2RU_SetSendingYUYV. + */ +Result Y2RU_IsDoneSendingYUYV(bool* is_done); + +/** + * @brief Checks if the DMA has finished sending the converted result. + * @param is_done Pointer to the boolean that will hold the result. + * + * True if the DMA has finished transferring data to your destination buffer, false otherwise. + */ +Result Y2RU_IsDoneReceiving(bool* is_done); + +/** + * @brief Configures the dithering weight parameters. + * @param params Dithering weight parameters to use. + */ +Result Y2RU_SetDitheringWeightParams(const Y2RU_DitheringWeightParams* params); + +/** + * @brief Gets the configured dithering weight parameters. + * @param params Pointer to output the dithering weight parameters to. + */ +Result Y2RU_GetDitheringWeightParams(Y2RU_DitheringWeightParams* params); + +/** + * @brief Sets all of the parameters of Y2RU_ConversionParams at once. + * @param params Conversion parameters to set. + * + * Faster than calling the individual value through Y2R_Set* because only one system call is made. + */ +Result Y2RU_SetConversionParams(const Y2RU_ConversionParams* params); + +/// Starts the conversion process +Result Y2RU_StartConversion(void); + +/// Cancels the conversion +Result Y2RU_StopConversion(void); + +/** + * @brief Checks if the conversion and DMA transfer are finished. + * @param is_busy Pointer to output the busy state to. + * + * This can have the same problems as the event and interrupt. See @ref Y2RU_SetTransferEndInterrupt. + */ +Result Y2RU_IsBusyConversion(bool* is_busy); + +/** + * @brief Checks whether Y2R is ready to be used. + * @param ping Pointer to output the ready status to. + */ +Result Y2RU_PingProcess(u8* ping); + +/// Initializes the Y2R driver. +Result Y2RU_DriverInitialize(void); + +/// Terminates the Y2R driver. +Result Y2RU_DriverFinalize(void); + diff --git a/libctru/include/3ds/srv.h b/libctru/include/3ds/srv.h new file mode 100644 index 0000000000..d575697124 --- /dev/null +++ b/libctru/include/3ds/srv.h @@ -0,0 +1,141 @@ +/** + * @file srv.h + * @brief Service API. + */ +#pragma once + +/// Initializes the service API. +Result srvInit(void); + +/// Exits the service API. +void srvExit(void); + +/** + * @brief Makes srvGetServiceHandle non-blocking for the current thread (or blocking, the default), in case of unavailable (full) requested services. + * @param blocking Whether srvGetServiceHandle should be non-blocking. + * srvGetServiceHandle will always block if the service hasn't been registered yet, + * use srvIsServiceRegistered to check whether that is the case or not. + */ +void srvSetBlockingPolicy(bool nonBlocking); + +/** + * @brief Gets the current service API session handle. + * @return The current service API session handle. + */ +Handle *srvGetSessionHandle(void); + +/** + * @brief Retrieves a service handle, retrieving from the environment handle list if possible. + * @param out Pointer to write the handle to. + * @param name Name of the service. + * @return 0 if no error occured, + * 0xD8E06406 if the caller has no right to access the service, + * 0xD0401834 if the requested service port is full and srvGetServiceHandle is non-blocking (see @ref srvSetBlockingPolicy). + */ +Result srvGetServiceHandle(Handle* out, const char* name); + +/// Registers the current process as a client to the service API. +Result srvRegisterClient(void); + +/** + * @brief Enables service notificatios, returning a notification semaphore. + * @param semaphoreOut Pointer to output the notification semaphore to. + */ +Result srvEnableNotification(Handle* semaphoreOut); + +/** + * @brief Registers the current process as a service. + * @param out Pointer to write the service handle to. + * @param name Name of the service. + * @param maxSessions Maximum number of sessions the service can handle. + */ +Result srvRegisterService(Handle* out, const char* name, int maxSessions); + +/** + * @brief Unregisters the current process as a service. + * @param name Name of the service. + */ +Result srvUnregisterService(const char* name); + +/** + * @brief Retrieves a service handle. + * @param out Pointer to output the handle to. + * @param name Name of the service. + * * @return 0 if no error occured, + * 0xD8E06406 if the caller has no right to access the service, + * 0xD0401834 if the requested service port is full and srvGetServiceHandle is non-blocking (see @ref srvSetBlockingPolicy). + */ +Result srvGetServiceHandleDirect(Handle* out, const char* name); + +/** + * @brief Registers a port. + * @param name Name of the port. + * @param clientHandle Client handle of the port. + */ +Result srvRegisterPort(const char* name, Handle clientHandle); + +/** + * @brief Unregisters a port. + * @param name Name of the port. + */ +Result srvUnregisterPort(const char* name); + +/** + * @brief Retrieves a port handle. + * @param out Pointer to output the handle to. + * @param name Name of the port. + */ +Result srvGetPort(Handle* out, const char* name); + +/** + * @brief Waits for a port to be registered. + * @param name Name of the port to wait for registration. + */ +Result srvWaitForPortRegistered(const char* name); + +/** + * @brief Subscribes to a notification. + * @param notificationId ID of the notification. + */ +Result srvSubscribe(u32 notificationId); + +/** + * @brief Unsubscribes from a notification. + * @param notificationId ID of the notification. + */ +Result srvUnsubscribe(u32 notificationId); + +/** + * @brief Receives a notification. + * @param notificationIdOut Pointer to output the ID of the received notification to. + */ +Result srvReceiveNotification(u32* notificationIdOut); + +/** + * @brief Publishes a notification to subscribers. + * @param notificationId ID of the notification. + * @param flags Flags to publish with. (bit 0 = only fire if not fired, bit 1 = do not report an error if there are more than 16 pending notifications) + */ +Result srvPublishToSubscriber(u32 notificationId, u32 flags); + +/** + * @brief Publishes a notification to subscribers and retrieves a list of all processes that were notified. + * @param processIdCountOut Pointer to output the number of process IDs to. + * @param processIdsOut Pointer to output the process IDs to. Should have size "60 * sizeof(u32)". + * @param notificationId ID of the notification. + */ +Result srvPublishAndGetSubscriber(u32* processIdCountOut, u32* processIdsOut, u32 notificationId); + +/** + * @brief Checks whether a service is registered. + * @param registeredOut Pointer to output the registration status to. + * @param name Name of the service to check. + */ +Result srvIsServiceRegistered(bool* registeredOut, const char* name); + +/** + * @brief Checks whether a port is registered. + * @param registeredOut Pointer to output the registration status to. + * @param name Name of the port to check. + */ +Result srvIsPortRegistered(bool* registeredOut, const char* name); diff --git a/libctru/include/3ds/svc.h b/libctru/include/3ds/svc.h new file mode 100644 index 0000000000..5718305320 --- /dev/null +++ b/libctru/include/3ds/svc.h @@ -0,0 +1,1440 @@ +/** + * @file svc.h + * @brief Syscall wrappers. + */ +#pragma once + +#include "types.h" + +/// Pseudo handle for the current process +#define CUR_PROCESS_HANDLE 0xFFFF8001 + +///@name Memory management +///@{ + +/** + * @brief @ref svcControlMemory operation flags + * + * The lowest 8 bits are the operation + */ +typedef enum { + MEMOP_FREE = 1, ///< Memory un-mapping + MEMOP_RESERVE = 2, ///< Reserve memory + MEMOP_ALLOC = 3, ///< Memory mapping + MEMOP_MAP = 4, ///< Mirror mapping + MEMOP_UNMAP = 5, ///< Mirror unmapping + MEMOP_PROT = 6, ///< Change protection + + MEMOP_REGION_APP = 0x100, ///< APPLICATION memory region. + MEMOP_REGION_SYSTEM = 0x200, ///< SYSTEM memory region. + MEMOP_REGION_BASE = 0x300, ///< BASE memory region. + + MEMOP_OP_MASK = 0xFF, ///< Operation bitmask. + MEMOP_REGION_MASK = 0xF00, ///< Region bitmask. + MEMOP_LINEAR_FLAG = 0x10000, ///< Flag for linear memory operations + + MEMOP_ALLOC_LINEAR = MEMOP_LINEAR_FLAG | MEMOP_ALLOC, ///< Allocates linear memory. +} MemOp; + +/// The state of a memory block. +typedef enum { + MEMSTATE_FREE = 0, ///< Free memory + MEMSTATE_RESERVED = 1, ///< Reserved memory + MEMSTATE_IO = 2, ///< I/O memory + MEMSTATE_STATIC = 3, ///< Static memory + MEMSTATE_CODE = 4, ///< Code memory + MEMSTATE_PRIVATE = 5, ///< Private memory + MEMSTATE_SHARED = 6, ///< Shared memory + MEMSTATE_CONTINUOUS = 7, ///< Continuous memory + MEMSTATE_ALIASED = 8, ///< Aliased memory + MEMSTATE_ALIAS = 9, ///< Alias memory + MEMSTATE_ALIASCODE = 10, ///< Aliased code memory + MEMSTATE_LOCKED = 11, ///< Locked memory +} MemState; + +/// Memory permission flags +typedef enum { + MEMPERM_READ = 1, ///< Readable + MEMPERM_WRITE = 2, ///< Writable + MEMPERM_EXECUTE = 4, ///< Executable + MEMPERM_READWRITE = MEMPERM_READ | MEMPERM_WRITE, ///< Readable and writable + MEMPERM_READEXECUTE = MEMPERM_READ | MEMPERM_EXECUTE, ///< Readable and executable + MEMPERM_DONTCARE = 0x10000000, ///< Don't care +} MemPerm; + +/// Memory regions. +typedef enum +{ + MEMREGION_ALL = 0, ///< All regions. + MEMREGION_APPLICATION = 1, ///< APPLICATION memory. + MEMREGION_SYSTEM = 2, ///< SYSTEM memory. + MEMREGION_BASE = 3, ///< BASE memory. +} MemRegion; + +/// Memory information. +typedef struct { + u32 base_addr; ///< Base address. + u32 size; ///< Size. + u32 perm; ///< Memory permissions. See @ref MemPerm + u32 state; ///< Memory state. See @ref MemState +} MemInfo; + +/// Memory page information. +typedef struct { + u32 flags; ///< Page flags. +} PageInfo; + +/// Arbitration modes. +typedef enum { + ARBITRATION_SIGNAL = 0, ///< Signal #value threads for wake-up. + ARBITRATION_WAIT_IF_LESS_THAN = 1, ///< If the memory at the address is strictly lower than #value, then wait for signal. + ARBITRATION_DECREMENT_AND_WAIT_IF_LESS_THAN = 2, ///< If the memory at the address is strictly lower than #value, then decrement it and wait for signal. + ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT = 3, ///< If the memory at the address is strictly lower than #value, then wait for signal or timeout. + ARBITRATION_DECREMENT_AND_WAIT_IF_LESS_THAN_TIMEOUT = 4, ///< If the memory at the address is strictly lower than #value, then decrement it and wait for signal or timeout. +} ArbitrationType; + +/// Special value to signal all the threads +#define ARBITRATION_SIGNAL_ALL (-1) + +///@} + +///@name Multithreading +///@{ + +/// Reset types (for use with events and timers) +typedef enum { + RESET_ONESHOT = 0, ///< When the primitive is signaled, it will wake up exactly one thread and will clear itself automatically. + RESET_STICKY = 1, ///< When the primitive is signaled, it will wake up all threads and it won't clear itself automatically. + RESET_PULSE = 2, ///< Only meaningful for timers: same as ONESHOT but it will periodically signal the timer instead of just once. +} ResetType; + +/// Types of thread info. +typedef enum { + THREADINFO_TYPE_UNKNOWN ///< Unknown. +} ThreadInfoType; + +/// Types of resource limit +typedef enum { + RESLIMIT_PRIORITY = 0, ///< Thread priority + RESLIMIT_COMMIT = 1, ///< Quantity of allocatable memory + RESLIMIT_THREAD = 2, ///< Number of threads + RESLIMIT_EVENT = 3, ///< Number of events + RESLIMIT_MUTEX = 4, ///< Number of mutexes + RESLIMIT_SEMAPHORE = 5, ///< Number of semaphores + RESLIMIT_TIMER = 6, ///< Number of timers + RESLIMIT_SHAREDMEMORY = 7, ///< Number of shared memory objects, see @ref svcCreateMemoryBlock + RESLIMIT_ADDRESSARBITER = 8, ///< Number of address arbiters + RESLIMIT_CPUTIME = 9, ///< CPU time. Value expressed in percentage regular until it reaches 90. + + RESLIMIT_BIT = BIT(31), ///< Forces enum size to be 32 bits +} ResourceLimitType; + +/// Pseudo handle for the current thread +#define CUR_THREAD_HANDLE 0xFFFF8000 + +///@} + +///@name Device drivers +///@{ + +/// DMA transfer state. +typedef enum { + DMASTATE_STARTING = 0, ///< DMA transfer involving at least one device is starting and has not reached DMAWFP yet. + DMASTATE_WFP_DST = 1, ///< DMA channel is in WFP state for the destination device (2nd loop iteration onwards). + DMASTATE_WFP_SRC = 2, ///< DMA channel is in WFP state for the source device (2nd loop iteration onwards). + DMASTATE_RUNNING = 3, ///< DMA transfer is running. + DMASTATE_DONE = 4, ///< DMA transfer is done. +} DmaState; + +/// Configuration flags for \ref DmaConfig. +enum { + DMACFG_SRC_IS_DEVICE = BIT(0), ///< DMA source is a device/peripheral. Address will not auto-increment. + DMACFG_DST_IS_DEVICE = BIT(1), ///< DMA destination is a device/peripheral. Address will not auto-increment. + DMACFG_WAIT_AVAILABLE = BIT(2), ///< Make \ref svcStartInterProcessDma wait for the channel to be unlocked. + DMACFG_KEEP_LOCKED = BIT(3), ///< Keep the channel locked after the transfer. Required for \ref svcRestartDma. + DMACFG_USE_SRC_CONFIG = BIT(6), ///< Use the provided source device configuration even if the DMA source is not a device. + DMACFG_USE_DST_CONFIG = BIT(7), ///< Use the provided destination device configuration even if the DMA destination is not a device. +}; + +/// Configuration flags for \ref svcRestartDma. +enum { + DMARST_UNLOCK = BIT(0), ///< Unlock the channel after transfer. + DMARST_RESUME_DEVICE = BIT(1), ///< Replace DMAFLUSHP instructions by NOP (they may not be regenerated even if this flag is not set). +}; + +/** + * @brief Device configuration structure, part of \ref DmaConfig. + * @note + * - if (and only if) src/dst is a device, then src/dst won't be auto-incremented. + * - the kernel uses DMAMOV instead of DMAADNH, when having to decrement (possibly working around an erratum); + * this forces all loops to be unrolled -- you need to keep that in mind when using negative increments, as the kernel + * uses a limit of 100 DMA instruction bytes per channel. + */ +typedef struct { + s8 deviceId; ///< DMA device ID. + s8 allowedAlignments; ///< Mask of allowed access alignments (8, 4, 2, 1). + s16 burstSize; ///< Number of bytes transferred in a burst loop. Can be 0 (in which case the max allowed alignment is used as unit). + s16 transferSize; ///< Number of bytes transferred in a "transfer" loop (made of burst loops). + s16 burstStride; ///< Burst loop stride, can be <= 0. + s16 transferStride; ///< "Transfer" loop stride, can be <= 0. +} DmaDeviceConfig; + +/// Configuration stucture for \ref svcStartInterProcessDma. +typedef struct { + s8 channelId; ///< Channel ID (Arm11: 0-7, Arm9: 0-1). Use -1 to auto-assign to a free channel (Arm11: 3-7, Arm9: 0-1). + s8 endianSwapSize; ///< Endian swap size (can be 0). + u8 flags; ///< DMACFG_* flags. + u8 _padding; + DmaDeviceConfig srcCfg; ///< Source device configuration, read if \ref DMACFG_SRC_IS_DEVICE and/or \ref DMACFG_USE_SRC_CONFIG are set. + DmaDeviceConfig dstCfg; ///< Destination device configuration, read if \ref DMACFG_SRC_IS_DEVICE and/or \ref DMACFG_USE_SRC_CONFIG are set. +} DmaConfig; + +///@} + +///@name Debugging +///@{ + +/// Operations for \ref svcControlPerformanceCounter +typedef enum { + PERFCOUNTEROP_ENABLE = 0, ///< Enable and lock perfmon. functionality. + PERFCOUNTEROP_DISABLE = 1, ///< Disable and forcibly unlock perfmon. functionality. + PERFCOUNTEROP_GET_VALUE = 2, ///< Get the value of a counter register. + PERFCOUNTEROP_SET_VALUE = 3, ///< Set the value of a counter register. + PERFCOUNTEROP_GET_OVERFLOW_FLAGS = 4, ///< Get the overflow flags for all CP15 and SCU counters. + PERFCOUNTEROP_RESET = 5, ///< Reset the value and/or overflow flags of selected counters. + PERFCOUNTEROP_GET_EVENT = 6, ///< Get the event ID associated to a particular counter. + PERFCOUNTEROP_SET_EVENT = 7, ///< Set the event ID associated to a paritcular counter. + PERFCOUNTEROP_SET_VIRTUAL_COUNTER_ENABLED = 8, ///< (Dis)allow the kernel to track counter overflows and to use 64-bit counter values. +} PerfCounterOperation; + +/// Performance counter register IDs (CP15 and SCU). +typedef enum +{ + // CP15 registers: + PERFCOUNTERREG_CORE_BASE = 0, + PERFCOUNTERREG_CORE_COUNT_REG_0 = PERFCOUNTERREG_CORE_BASE, ///< CP15 PMN0. + PERFCOUNTERREG_CORE_COUNT_REG_1, ///< CP15 PMN1. + PERFCOUNTERREG_CORE_CYCLE_COUNTER, ///< CP15 CCNT. + + // SCU registers + PERFCOUNTERREG_SCU_BASE = 0x10, + PERFCOUNTERREG_SCU_0 = PERFCOUNTERREG_SCU_BASE, ///< SCU MN0. + PERFCOUNTERREG_SCU_1, ///< SCU MN1. + PERFCOUNTERREG_SCU_2, ///< SCU MN2. + PERFCOUNTERREG_SCU_3, ///< SCU MN3. + PERFCOUNTERREG_SCU_4, ///< SCU MN4. Prod-N3DS only. IRQ line missing. + PERFCOUNTERREG_SCU_5, ///< SCU MN5. Prod-N3DS only. IRQ line missing. + PERFCOUNTERREG_SCU_6, ///< SCU MN6. Prod-N3DS only. IRQ line missing. + PERFCOUNTERREG_SCU_7, ///< SCU MN7. Prod-N3DS only. IRQ line missing. +} PerfCounterRegister; + +/** + * @brief Performance counter event IDs (CP15 or SCU). + * + * @note Refer to: + * - CP15: https://developer.arm.com/documentation/ddi0360/e/control-coprocessor-cp15/register-descriptions/c15--performance-monitor-control-register--pmnc- + * - SCU: https://developer.arm.com/documentation/ddi0360/e/mpcore-private-memory-region/about-the-mpcore-private-memory-region/performance-monitor-event-registers + */ +typedef enum +{ + // Core events: + PERFCOUNTEREVT_CORE_BASE = 0x0, + PERFCOUNTEREVT_CORE_INST_CACHE_MISS = PERFCOUNTEREVT_CORE_BASE, + PERFCOUNTEREVT_CORE_STALL_BY_LACK_OF_INST, + PERFCOUNTEREVT_CORE_STALL_BY_DATA_HAZARD, + PERFCOUNTEREVT_CORE_INST_MICRO_TLB_MISS, + PERFCOUNTEREVT_CORE_DATA_MICRO_TLB_MISS, + PERFCOUNTEREVT_CORE_BRANCH_INST, + PERFCOUNTEREVT_CORE_BRANCH_NOT_PREDICTED, + PERFCOUNTEREVT_CORE_BRANCH_MISS_PREDICTED, + PERFCOUNTEREVT_CORE_INST_EXECUTED, + PERFCOUNTEREVT_CORE_FOLDED_INST_EXECUTED, + PERFCOUNTEREVT_CORE_DATA_CACHE_READ, + PERFCOUNTEREVT_CORE_DATA_CACHE_READ_MISS, + PERFCOUNTEREVT_CORE_DATA_CACHE_WRITE, + PERFCOUNTEREVT_CORE_DATA_CACHE_WRITE_MISS, + PERFCOUNTEREVT_CORE_DATA_CACHE_LINE_EVICTION, + PERFCOUNTEREVT_CORE_PC_CHANGED, + PERFCOUNTEREVT_CORE_MAIN_TLB_MISS, + PERFCOUNTEREVT_CORE_EXTERNAL_REQUEST, + PERFCOUNTEREVT_CORE_STALL_BY_LSU_FULL, + PERFCOUNTEREVT_CORE_STORE_BUFFER_DRAIN, + PERFCOUNTEREVT_CORE_MERGE_IN_STORE_BUFFER, + PERFCOUNTEREVT_CORE_CYCLE_COUNT = PERFCOUNTEREVT_CORE_BASE + 0xFF, ///< One cycle elapsed. + PERFCOUNTEREVT_CORE_CYCLE_COUNT_64 = PERFCOUNTEREVT_CORE_BASE + 0xFFF, ///< 64 cycles elapsed. + + + PERFCOUNTEREVT_SCU_BASE = 0x1000, + PERFCOUNTEREVT_SCU_DISABLED = PERFCOUNTEREVT_SCU_BASE, + PERFCOUNTEREVT_SCU_LINEFILL_MISS_FROM_CORE0, + PERFCOUNTEREVT_SCU_LINEFILL_MISS_FROM_CORE1, + PERFCOUNTEREVT_SCU_LINEFILL_MISS_FROM_CORE2, + PERFCOUNTEREVT_SCU_LINEFILL_MISS_FROM_CORE3, + PERFCOUNTEREVT_SCU_LINEFILL_HIT_FROM_CORE0, + PERFCOUNTEREVT_SCU_LINEFILL_HIT_FROM_CORE1, + PERFCOUNTEREVT_SCU_LINEFILL_HIT_FROM_CORE2, + PERFCOUNTEREVT_SCU_LINEFILL_HIT_FROM_CORE3, + PERFCOUNTEREVT_SCU_LINE_MISSING_FROM_CORE0, + PERFCOUNTEREVT_SCU_LINE_MISSING_FROM_CORE1, + PERFCOUNTEREVT_SCU_LINE_MISSING_FROM_CORE2, + PERFCOUNTEREVT_SCU_LINE_MISSING_FROM_CORE3, + PERFCOUNTEREVT_SCU_LINE_MIGRATION, + PERFCOUNTEREVT_SCU_READ_BUSY_PORT0, + PERFCOUNTEREVT_SCU_READ_BUSY_PORT1, + PERFCOUNTEREVT_SCU_WRITE_BUSY_PORT0, + PERFCOUNTEREVT_SCU_WRITE_BUSY_PORT1, + PERFCOUNTEREVT_SCU_EXTERNAL_READ, + PERFCOUNTEREVT_SCU_EXTERNAL_WRITE, + PERFCOUNTEREVT_SCU_CYCLE_COUNT = PERFCOUNTEREVT_SCU_BASE + 0x1F, +} PerfCounterEvent; + +/// Event relating to the attachment of a process. +typedef struct { + u64 program_id; ///< ID of the program. + char process_name[8]; ///< Name of the process. + u32 process_id; ///< ID of the process. + u32 other_flags; ///< Always 0 +} AttachProcessEvent; + +/// Reasons for an exit process event. +typedef enum { + EXITPROCESS_EVENT_EXIT = 0, ///< Process exited either normally or due to an uncaught exception. + EXITPROCESS_EVENT_TERMINATE = 1, ///< Process has been terminated by @ref svcTerminateProcess. + EXITPROCESS_EVENT_DEBUG_TERMINATE = 2, ///< Process has been terminated by @ref svcTerminateDebugProcess. +} ExitProcessEventReason; + +/// Event relating to the exiting of a process. +typedef struct { + ExitProcessEventReason reason; ///< Reason for exiting. See @ref ExitProcessEventReason +} ExitProcessEvent; + +/// Event relating to the attachment of a thread. +typedef struct { + u32 creator_thread_id; ///< ID of the creating thread. + u32 thread_local_storage; ///< Thread local storage. + u32 entry_point; ///< Entry point of the thread. +} AttachThreadEvent; + +/// Reasons for an exit thread event. +typedef enum { + EXITTHREAD_EVENT_EXIT = 0, ///< Thread exited. + EXITTHREAD_EVENT_TERMINATE = 1, ///< Thread terminated. + EXITTHREAD_EVENT_EXIT_PROCESS = 2, ///< Process exited either normally or due to an uncaught exception. + EXITTHREAD_EVENT_TERMINATE_PROCESS = 3, ///< Process has been terminated by @ref svcTerminateProcess. +} ExitThreadEventReason; + +/// Event relating to the exiting of a thread. +typedef struct { + ExitThreadEventReason reason; ///< Reason for exiting. See @ref ExitThreadEventReason +} ExitThreadEvent; + +/// Reasons for a user break. +typedef enum { + USERBREAK_PANIC = 0, ///< Panic. + USERBREAK_ASSERT = 1, ///< Assertion failed. + USERBREAK_USER = 2, ///< User related. + USERBREAK_LOAD_RO = 3, ///< Load RO. + USERBREAK_UNLOAD_RO = 4, ///< Unload RO. +} UserBreakType; + +/// Reasons for an exception event. +typedef enum { + EXCEVENT_UNDEFINED_INSTRUCTION = 0, ///< Undefined instruction. + EXCEVENT_PREFETCH_ABORT = 1, ///< Prefetch abort. + EXCEVENT_DATA_ABORT = 2, ///< Data abort (other than the below kind). + EXCEVENT_UNALIGNED_DATA_ACCESS = 3, ///< Unaligned data access. + EXCEVENT_ATTACH_BREAK = 4, ///< Attached break. + EXCEVENT_STOP_POINT = 5, ///< Stop point reached. + EXCEVENT_USER_BREAK = 6, ///< User break occurred. + EXCEVENT_DEBUGGER_BREAK = 7, ///< Debugger break occurred. + EXCEVENT_UNDEFINED_SYSCALL = 8, ///< Undefined syscall. +} ExceptionEventType; + +/// Event relating to fault exceptions (CPU exceptions other than stop points and undefined syscalls). +typedef struct { + u32 fault_information; ///< FAR (for DATA ABORT / UNALIGNED DATA ACCESS), attempted syscall or 0 +} FaultExceptionEvent; + +/// Stop point types +typedef enum { + STOPPOINT_SVC_FF = 0, ///< See @ref SVC_STOP_POINT. + STOPPOINT_BREAKPOINT = 1, ///< Breakpoint. + STOPPOINT_WATCHPOINT = 2, ///< Watchpoint. +} StopPointType; + +/// Event relating to stop points +typedef struct { + StopPointType type; ///< Stop point type, see @ref StopPointType. + u32 fault_information; ///< FAR for Watchpoints, otherwise 0. +} StopPointExceptionEvent; + +/// Event relating to @ref svcBreak +typedef struct { + UserBreakType type; ///< User break type, see @ref UserBreakType. + u32 croInfo; ///< For LOAD_RO and UNLOAD_RO. + u32 croInfoSize; ///< For LOAD_RO and UNLOAD_RO. +} UserBreakExceptionEvent; + +/// Event relating to @ref svcBreakDebugProcess +typedef struct { + s32 thread_ids[4]; ///< IDs of the attached process's threads that were running on each core at the time of the @ref svcBreakDebugProcess call, or -1 (only the first 2 values are meaningful on O3DS). +} DebuggerBreakExceptionEvent; + +/// Event relating to exceptions. +typedef struct { + ExceptionEventType type; ///< Type of event. See @ref ExceptionEventType. + u32 address; ///< Address of the exception. + union { + FaultExceptionEvent fault; ///< Fault exception event data. + StopPointExceptionEvent stop_point; ///< Stop point exception event data. + UserBreakExceptionEvent user_break; ///< User break exception event data. + DebuggerBreakExceptionEvent debugger_break; ///< Debugger break exception event data + }; +} ExceptionEvent; + +/// Event relating to the scheduler. +typedef struct { + u64 clock_tick; ///< Clock tick that the event occurred. +} ScheduleInOutEvent; + +/// Event relating to syscalls. +typedef struct { + u64 clock_tick; ///< Clock tick that the event occurred. + u32 syscall; ///< Syscall sent/received. +} SyscallInOutEvent; + +/// Event relating to debug output. +typedef struct { + u32 string_addr; ///< Address of the outputted string. + u32 string_size; ///< Size of the outputted string. +} OutputStringEvent; + +/// Event relating to the mapping of memory. +typedef struct { + u32 mapped_addr; ///< Mapped address. + u32 mapped_size; ///< Mapped size. + MemPerm memperm; ///< Memory permissions. See @ref MemPerm. + MemState memstate; ///< Memory state. See @ref MemState. +} MapEvent; + +/// Debug event type. +typedef enum { + DBGEVENT_ATTACH_PROCESS = 0, ///< Process attached event. + DBGEVENT_ATTACH_THREAD = 1, ///< Thread attached event. + DBGEVENT_EXIT_THREAD = 2, ///< Thread exit event. + DBGEVENT_EXIT_PROCESS = 3, ///< Process exit event. + DBGEVENT_EXCEPTION = 4, ///< Exception event. + DBGEVENT_DLL_LOAD = 5, ///< DLL load event. + DBGEVENT_DLL_UNLOAD = 6, ///< DLL unload event. + DBGEVENT_SCHEDULE_IN = 7, ///< Schedule in event. + DBGEVENT_SCHEDULE_OUT = 8, ///< Schedule out event. + DBGEVENT_SYSCALL_IN = 9, ///< Syscall in event. + DBGEVENT_SYSCALL_OUT = 10, ///< Syscall out event. + DBGEVENT_OUTPUT_STRING = 11, ///< Output string event. + DBGEVENT_MAP = 12, ///< Map event. +} DebugEventType; + +/// Information about a debug event. +typedef struct { + DebugEventType type; ///< Type of event. See @ref DebugEventType + u32 thread_id; ///< ID of the thread. + u32 flags; ///< Flags. Bit0 means that @ref svcContinueDebugEvent needs to be called for this event (except for EXIT PROCESS events, where this flag is disregarded). + u8 remnants[4]; ///< Always 0. + union { + AttachProcessEvent attach_process; ///< Process attachment event data. + AttachThreadEvent attach_thread; ///< Thread attachment event data. + ExitThreadEvent exit_thread; ///< Thread exit event data. + ExitProcessEvent exit_process; ///< Process exit event data. + ExceptionEvent exception; ///< Exception event data. + /* DLL_LOAD and DLL_UNLOAD do not seem to possess any event data */ + ScheduleInOutEvent scheduler; ///< Schedule in/out event data. + SyscallInOutEvent syscall; ///< Syscall in/out event data. + OutputStringEvent output_string; ///< Output string event data. + MapEvent map; ///< Map event data. + }; +} DebugEventInfo; + +/// Debug flags for an attached process, set by @ref svcContinueDebugEvent +typedef enum { + DBG_INHIBIT_USER_CPU_EXCEPTION_HANDLERS = BIT(0), ///< Inhibit user-defined CPU exception handlers (including watchpoints and breakpoints, regardless of any @ref svcKernelSetState call). + DBG_SIGNAL_FAULT_EXCEPTION_EVENTS = BIT(1), ///< Signal fault exception events. See @ref FaultExceptionEvent. + DBG_SIGNAL_SCHEDULE_EVENTS = BIT(2), ///< Signal schedule in/out events. See @ref ScheduleInOutEvent. + DBG_SIGNAL_SYSCALL_EVENTS = BIT(3), ///< Signal syscall in/out events. See @ref SyscallInOutEvent. + DBG_SIGNAL_MAP_EVENTS = BIT(4), ///< Signal map events. See @ref MapEvent. +} DebugFlags; + +typedef struct { + CpuRegisters cpu_registers; ///< CPU registers. + FpuRegisters fpu_registers; ///< FPU registers. +} ThreadContext; + +/// Control flags for @ref svcGetDebugThreadContext and @ref svcSetDebugThreadContext +typedef enum { + THREADCONTEXT_CONTROL_CPU_GPRS = BIT(0), ///< Control r0-r12. + THREADCONTEXT_CONTROL_CPU_SPRS = BIT(1), ///< Control sp, lr, pc, cpsr. + THREADCONTEXT_CONTROL_FPU_GPRS = BIT(2), ///< Control d0-d15 (or s0-s31). + THREADCONTEXT_CONTROL_FPU_SPRS = BIT(3), ///< Control fpscr, fpexc. + + THREADCONTEXT_CONTROL_CPU_REGS = BIT(0) | BIT(1), ///< Control r0-r12, sp, lr, pc, cpsr. + THREADCONTEXT_CONTROL_FPU_REGS = BIT(2) | BIT(3), ///< Control d0-d15, fpscr, fpexc. + + THREADCONTEXT_CONTROL_ALL = BIT(0) | BIT(1) | BIT(2) | BIT(3), ///< Control all of the above. +} ThreadContextControlFlags; + +/// Thread parameter field for @ref svcGetDebugThreadParameter +typedef enum { + DBGTHREAD_PARAMETER_PRIORITY = 0, ///< Thread priority. + DBGTHREAD_PARAMETER_SCHEDULING_MASK_LOW = 1, ///< Low scheduling mask. + DBGTHREAD_PARAMETER_CPU_IDEAL = 2, ///< Ideal processor. + DBGTHREAD_PARAMETER_CPU_CREATOR = 3, ///< Processor that created the threod. +} DebugThreadParameter; + +///@} + +///@name Processes +///@{ + +/// Information on address space for process. All sizes are in pages (0x1000 bytes) +typedef struct { + u8 name[8]; ///< ASCII name of codeset + u16 unk1; + u16 unk2; + u32 unk3; + u32 text_addr; ///< .text start address + u32 text_size; ///< .text number of pages + u32 ro_addr; ///< .rodata start address + u32 ro_size; ///< .rodata number of pages + u32 rw_addr; ///< .data, .bss start address + u32 rw_size; ///< .data number of pages + u32 text_size_total; ///< total pages for .text (aligned) + u32 ro_size_total; ///< total pages for .rodata (aligned) + u32 rw_size_total; ///< total pages for .data, .bss (aligned) + u32 unk4; + u64 program_id; ///< Program ID +} CodeSetInfo; + +/// Information for the main thread of a process. +typedef struct +{ + int priority; ///< Priority of the main thread. + u32 stack_size; ///< Size of the stack of the main thread. + int argc; ///< Unused on retail kernel. + u16* argv; ///< Unused on retail kernel. + u16* envp; ///< Unused on retail kernel. +} StartupInfo; + +///@} + +/** + * @brief Gets the thread local storage buffer. + * @return The thread local storage bufger. + */ +static inline void* getThreadLocalStorage(void) +{ + void* ret; + __asm__ ("mrc p15, 0, %[data], c13, c0, 3" : [data] "=r" (ret)); + return ret; +} + +/** + * @brief Gets the thread command buffer. + * @return The thread command bufger. + */ +static inline u32* getThreadCommandBuffer(void) +{ + return (u32*)((u8*)getThreadLocalStorage() + 0x80); +} + +/** + * @brief Gets the thread static buffer. + * @return The thread static bufger. + */ +static inline u32* getThreadStaticBuffers(void) +{ + return (u32*)((u8*)getThreadLocalStorage() + 0x180); +} + +///@name Device drivers +///@{ + +/// Writes the default DMA device config that the kernel uses when DMACFG_*_IS_DEVICE and DMACFG_*_USE_CFG are not set +static inline void dmaDeviceConfigInitDefault(DmaDeviceConfig *cfg) +{ + // Kernel uses this default instance if _IS_DEVICE and _USE_CFG are not set + *cfg = (DmaDeviceConfig) { + .deviceId = -1, + .allowedAlignments = 8 | 4 | 2 | 1, + .burstSize = 0x80, + .transferSize = 0, + .burstStride = 0x80, + .transferStride = 0, + }; +} + +/// Initializes a \ref DmaConfig instance with sane defaults for RAM<>RAM tranfers +static inline void dmaConfigInitDefault(DmaConfig *cfg) +{ + *cfg = (DmaConfig) { + .channelId = -1, + .endianSwapSize = 0, + .flags = DMACFG_WAIT_AVAILABLE, + ._padding = 0, + .srcCfg = {}, + .dstCfg = {}, + }; +} + +///@} + +///@name Memory management +///@{ +/** + * @brief Controls memory mapping + * @param[out] addr_out The virtual address resulting from the operation. Usually the same as addr0. + * @param addr0 The virtual address to be used for the operation. + * @param addr1 The virtual address to be (un)mirrored by @p addr0 when using @ref MEMOP_MAP or @ref MEMOP_UNMAP. + * It has to be pointing to a RW memory. + * Use NULL if the operation is @ref MEMOP_FREE or @ref MEMOP_ALLOC. + * @param size The requested size for @ref MEMOP_ALLOC and @ref MEMOP_ALLOC_LINEAR. + * @param op Operation flags. See @ref MemOp. + * @param perm A combination of @ref MEMPERM_READ and @ref MEMPERM_WRITE. Using MEMPERM_EXECUTE will return an error. + * Value 0 is used when unmapping memory. + * + * If a memory is mapped for two or more addresses, you have to use MEMOP_UNMAP before being able to MEMOP_FREE it. + * MEMOP_MAP will fail if @p addr1 was already mapped to another address. + * + * More information is available at http://3dbrew.org/wiki/SVC#Memory_Mapping. + * + * @sa svcControlProcessMemory + */ +Result svcControlMemory(u32* addr_out, u32 addr0, u32 addr1, u32 size, MemOp op, MemPerm perm); + +/** + * @brief Controls the memory mapping of a process + * @param addr0 The virtual address to map + * @param addr1 The virtual address to be mapped by @p addr0 + * @param type Only operations @ref MEMOP_MAP, @ref MEMOP_UNMAP and @ref MEMOP_PROT are allowed. + * + * This is the only SVC which allows mapping executable memory. + * Using @ref MEMOP_PROT will change the memory permissions of an already mapped memory. + * + * @note The pseudo handle for the current process is not supported by this service call. + * @sa svcControlProcess + */ +Result svcControlProcessMemory(Handle process, u32 addr0, u32 addr1, u32 size, u32 type, u32 perm); + +/** + * @brief Creates a block of shared memory + * @param[out] memblock Pointer to store the handle of the block + * @param addr Address of the memory to map, page-aligned. So its alignment must be 0x1000. + * @param size Size of the memory to map, a multiple of 0x1000. + * @param my_perm Memory permissions for the current process + * @param other_perm Memory permissions for the other processes + * + * @note The shared memory block, and its rights, are destroyed when the handle is closed. + */ +Result svcCreateMemoryBlock(Handle* memblock, u32 addr, u32 size, MemPerm my_perm, MemPerm other_perm); + +/** + * @brief Maps a block of shared memory + * @param memblock Handle of the block + * @param addr Address of the memory to map, page-aligned. So its alignment must be 0x1000. + * @param my_perm Memory permissions for the current process + * @param other_perm Memory permissions for the other processes + * + * @note The shared memory block, and its rights, are destroyed when the handle is closed. + */ +Result svcMapMemoryBlock(Handle memblock, u32 addr, MemPerm my_perm, MemPerm other_perm); + +/** + * @brief Maps a block of process memory, starting from address 0x00100000. + * @param process Handle of the process. + * @param destAddress Address of the block of memory to map, in the current (destination) process. + * @param size Size of the block of memory to map (truncated to a multiple of 0x1000 bytes). + */ +Result svcMapProcessMemory(Handle process, u32 destAddress, u32 size); + +/** + * @brief Unmaps a block of process memory, starting from address 0x00100000. + * @param process Handle of the process. + * @param destAddress Address of the block of memory to unmap, in the current (destination) process. + * @param size Size of the block of memory to unmap (truncated to a multiple of 0x1000 bytes). + */ +Result svcUnmapProcessMemory(Handle process, u32 destAddress, u32 size); + +/** + * @brief Unmaps a block of shared memory + * @param memblock Handle of the block + * @param addr Address of the memory to unmap, page-aligned. So its alignment must be 0x1000. + */ +Result svcUnmapMemoryBlock(Handle memblock, u32 addr); + +/** + * @brief Queries memory information. + * @param[out] info Pointer to output memory info to. + * @param out Pointer to output page info to. + * @param addr Virtual memory address to query. + */ +Result svcQueryMemory(MemInfo* info, PageInfo* out, u32 addr); + +/** + * @brief Queries process memory information. + * @param[out] info Pointer to output memory info to. + * @param[out] out Pointer to output page info to. + * @param process Process to query memory from. + * @param addr Virtual memory address to query. + */ +Result svcQueryProcessMemory(MemInfo* info, PageInfo* out, Handle process, u32 addr); + +///@} + + +///@name Process management +///@{ +/** + * @brief Gets the handle of a process. + * @param[out] process The handle of the process + * @param processId The ID of the process to open + */ +Result svcOpenProcess(Handle* process, u32 processId); + +/// Exits the current process. +void __attribute__((noreturn)) svcExitProcess(); + +/** + * @brief Terminates a process. + * @param process Handle of the process to terminate. + */ +Result svcTerminateProcess(Handle process); + +/** + * @brief Gets information about a process. + * @param[out] out Pointer to output process info to. + * @param process Handle of the process to get information about. + * @param type Type of information to retreieve. + */ +Result svcGetProcessInfo(s64* out, Handle process, u32 type); + +/** + * @brief Gets the ID of a process. + * @param[out] out Pointer to output the process ID to. + * @param handle Handle of the process to get the ID of. + */ +Result svcGetProcessId(u32 *out, Handle handle); + +/** + * @brief Gets a list of running processes. + * @param[out] processCount Pointer to output the process count to. + * @param[out] processIds Pointer to output the process IDs to. + * @param processIdMaxCount Maximum number of process IDs. + */ +Result svcGetProcessList(s32* processCount, u32* processIds, s32 processIdMaxCount); + +/** + * @brief Gets a list of the threads of a process. + * @param[out] threadCount Pointer to output the thread count to. + * @param[out] threadIds Pointer to output the thread IDs to. + * @param threadIdMaxCount Maximum number of thread IDs. + * @param process Process handle to list the threads of. + */ +Result svcGetThreadList(s32* threadCount, u32* threadIds, s32 threadIdMaxCount, Handle process); + +/** + * @brief Creates a port. + * @param[out] portServer Pointer to output the port server handle to. + * @param[out] portClient Pointer to output the port client handle to. + * @param name Name of the port. + * @param maxSessions Maximum number of sessions that can connect to the port. + */ +Result svcCreatePort(Handle* portServer, Handle* portClient, const char* name, s32 maxSessions); + +/** + * @brief Connects to a port. + * @param[out] out Pointer to output the port handle to. + * @param portName Name of the port. + */ +Result svcConnectToPort(volatile Handle* out, const char* portName); + +/** + * @brief Sets up virtual address space for a new process + * @param[out] out Pointer to output the code set handle to. + * @param info Description for setting up the addresses + * @param code_ptr Pointer to .text in shared memory + * @param ro_ptr Pointer to .rodata in shared memory + * @param data_ptr Pointer to .data in shared memory + */ +Result svcCreateCodeSet(Handle* out, const CodeSetInfo *info, void* code_ptr, void* ro_ptr, void* data_ptr); + +/** + * @brief Sets up virtual address space for a new process + * @param[out] out Pointer to output the process handle to. + * @param codeset Codeset created for this process + * @param arm11kernelcaps ARM11 Kernel Capabilities from exheader + * @param arm11kernelcaps_num Number of kernel capabilities + */ +Result svcCreateProcess(Handle* out, Handle codeset, const u32 *arm11kernelcaps, u32 arm11kernelcaps_num); + +/** + * @brief Gets a process's affinity mask. + * @param[out] affinitymask Pointer to store the affinity masks. + * @param process Handle of the process. + * @param processorcount Number of processors. + */ +Result svcGetProcessAffinityMask(u8* affinitymask, Handle process, s32 processorcount); + +/** + * @brief Sets a process's affinity mask. + * @param process Handle of the process. + * @param affinitymask Pointer to retrieve the affinity masks from. + * @param processorcount Number of processors. + */ +Result svcSetProcessAffinityMask(Handle process, const u8* affinitymask, s32 processorcount); + +/** + * Gets a process's ideal processor. + * @param[out] processorid Pointer to store the ID of the process's ideal processor. + * @param process Handle of the process. + */ +Result svcGetProcessIdealProcessor(s32 *processorid, Handle process); + +/** + * Sets a process's ideal processor. + * @param process Handle of the process. + * @param processorid ID of the process's ideal processor. + */ +Result svcSetProcessIdealProcessor(Handle process, s32 processorid); + +/** + * Launches the main thread of the process. + * @param process Handle of the process. + * @param info Pointer to a StartupInfo structure describing information for the main thread. + */ +Result svcRun(Handle process, const StartupInfo* info); + +///@} + +///@name Multithreading +///@{ +/** + * @brief Creates a new thread. + * @param[out] thread The thread handle + * @param entrypoint The function that will be called first upon thread creation + * @param arg The argument passed to @p entrypoint + * @param stack_top The top of the thread's stack. Must be 0x8 bytes mem-aligned. + * @param thread_priority Low values gives the thread higher priority. + * For userland apps, this has to be within the range [0x18;0x3F] + * @param processor_id The id of the processor the thread should be ran on. Those are labelled starting from 0. + * For old 3ds it has to be <2, and for new 3DS <4. + * Value -1 means all CPUs and -2 read from the Exheader. + * + * The processor with ID 1 is the system processor. + * To enable multi-threading on this core you need to call APT_SetAppCpuTimeLimit at least once with a non-zero value. + * + * Since a thread is considered as a waitable object, you can use @ref svcWaitSynchronization + * and @ref svcWaitSynchronizationN to join with it. + * + * @note The kernel will clear the @p stack_top's address low 3 bits to make sure it is 0x8-bytes aligned. + */ +Result svcCreateThread(Handle* thread, ThreadFunc entrypoint, u32 arg, u32* stack_top, s32 thread_priority, s32 processor_id); + +/** + * @brief Gets the handle of a thread. + * @param[out] thread The handle of the thread + * @param process The ID of the process linked to the thread + */ +Result svcOpenThread(Handle* thread,Handle process, u32 threadId); + +/** + * @brief Exits the current thread. + * + * This will trigger a state change and hence release all @ref svcWaitSynchronization operations. + * It means that you can join a thread by calling @code svcWaitSynchronization(threadHandle,yourtimeout); @endcode + */ +void svcExitThread(void) __attribute__((noreturn)); + +/** + * @brief Puts the current thread to sleep. + * @param ns The minimum number of nanoseconds to sleep for. + */ +void svcSleepThread(s64 ns); + +/// Retrieves the priority of a thread. +Result svcGetThreadPriority(s32 *out, Handle handle); + +/** + * @brief Changes the priority of a thread + * @param prio For userland apps, this has to be within the range [0x18;0x3F] + * + * Low values gives the thread higher priority. + */ +Result svcSetThreadPriority(Handle thread, s32 prio); + +/** + * @brief Gets a thread's affinity mask. + * @param[out] affinitymask Pointer to output the affinity masks to. + * @param thread Handle of the thread. + * @param processorcount Number of processors. + */ +Result svcGetThreadAffinityMask(u8* affinitymask, Handle thread, s32 processorcount); + +/** + * @brief Sets a thread's affinity mask. + * @param thread Handle of the thread. + * @param affinitymask Pointer to retrieve the affinity masks from. + * @param processorcount Number of processors. + */ +Result svcSetThreadAffinityMask(Handle thread, const u8* affinitymask, s32 processorcount); + +/** + * @brief Gets a thread's ideal processor. + * @param[out] processorid Pointer to output the ID of the thread's ideal processor to. + * @param thread Handle of the thread. + */ +Result svcGetThreadIdealProcessor(s32* processorid, Handle thread); + +/** + * Sets a thread's ideal processor. + * @param thread Handle of the thread. + * @param processorid ID of the thread's ideal processor. + */ +Result svcSetThreadIdealProcessor(Handle thread, s32 processorid); + +/** + * @brief Returns the ID of the processor the current thread is running on. + * @sa svcCreateThread + */ +s32 svcGetProcessorID(void); + +/** + * @brief Gets the ID of a thread. + * @param[out] out Pointer to output the thread ID of the thread @p handle to. + * @param handle Handle of the thread. + */ +Result svcGetThreadId(u32 *out, Handle handle); + +/** + * @brief Gets the resource limit set of a process. + * @param[out] resourceLimit Pointer to output the resource limit set handle to. + * @param process Process to get the resource limits of. + */ +Result svcGetResourceLimit(Handle* resourceLimit, Handle process); + +/** + * @brief Gets the value limits of a resource limit set. + * @param[out] values Pointer to output the value limits to. + * @param resourceLimit Resource limit set to use. + * @param names Resource limit names to get the limits of. + * @param nameCount Number of resource limit names. + */ +Result svcGetResourceLimitLimitValues(s64* values, Handle resourceLimit, ResourceLimitType* names, s32 nameCount); + +/** + * @brief Gets the values of a resource limit set. + * @param[out] values Pointer to output the values to. + * @param resourceLimit Resource limit set to use. + * @param names Resource limit names to get the values of. + * @param nameCount Number of resource limit names. + */ +Result svcGetResourceLimitCurrentValues(s64* values, Handle resourceLimit, ResourceLimitType* names, s32 nameCount); + +/** + * @brief Sets the resource limit set of a process. + * @param process Process to set the resource limit set to. + * @param resourceLimit Resource limit set handle. + */ +Result svcSetProcessResourceLimits(Handle process, Handle resourceLimit); + +/** + * @brief Creates a resource limit set. + * @param[out] resourceLimit Pointer to output the resource limit set handle to. + */ +Result svcCreateResourceLimit(Handle* resourceLimit); + +/** + * @brief Sets the value limits of a resource limit set. + * @param resourceLimit Resource limit set to use. + * @param names Resource limit names to set the limits of. + * @param values Value limits to set. The high 32 bits of RESLIMIT_COMMIT are used to + set APPMEMALLOC in configuration memory, otherwise those bits are unused. + * @param nameCount Number of resource limit names. + */ +Result svcSetResourceLimitValues(Handle resourceLimit, const ResourceLimitType* names, const s64* values, s32 nameCount); + +/** + * @brief Gets the process ID of a thread. + * @param[out] out Pointer to output the process ID of the thread @p handle to. + * @param handle Handle of the thread. + * @sa svcOpenProcess + */ +Result svcGetProcessIdOfThread(u32 *out, Handle handle); + +/** + * @brief Checks if a thread handle is valid. + * This requests always return an error when called, it only checks if the handle is a thread or not. + * @return 0xD8E007ED (BAD_ENUM) if the Handle is a Thread Handle + * @return 0xD8E007F7 (BAD_HANDLE) if it isn't. + */ +Result svcGetThreadInfo(s64* out, Handle thread, ThreadInfoType type); +///@} + + +///@name Synchronization +///@{ +/** + * @brief Creates a mutex. + * @param[out] mutex Pointer to output the handle of the created mutex to. + * @param initially_locked Whether the mutex should be initially locked. + */ +Result svcCreateMutex(Handle* mutex, bool initially_locked); + +/** + * @brief Releases a mutex. + * @param handle Handle of the mutex. + */ +Result svcReleaseMutex(Handle handle); + +/** + * @brief Creates a semaphore. + * @param[out] semaphore Pointer to output the handle of the created semaphore to. + * @param initial_count Initial count of the semaphore. + * @param max_count Maximum count of the semaphore. + */ +Result svcCreateSemaphore(Handle* semaphore, s32 initial_count, s32 max_count); + +/** + * @brief Releases a semaphore. + * @param[out] count Pointer to output the current count of the semaphore to. + * @param semaphore Handle of the semaphore. + * @param release_count Number to increase the semaphore count by. + */ +Result svcReleaseSemaphore(s32* count, Handle semaphore, s32 release_count); + +/** + * @brief Creates an event handle. + * @param[out] event Pointer to output the created event handle to. + * @param reset_type Type of reset the event uses (RESET_ONESHOT/RESET_STICKY). + */ +Result svcCreateEvent(Handle* event, ResetType reset_type); + +/** + * @brief Signals an event. + * @param handle Handle of the event to signal. + */ +Result svcSignalEvent(Handle handle); + +/** + * @brief Clears an event. + * @param handle Handle of the event to clear. + */ +Result svcClearEvent(Handle handle); + +/** + * @brief Waits for synchronization on a handle. + * @param handle Handle to wait on. + * @param nanoseconds Maximum nanoseconds to wait for. + */ +Result svcWaitSynchronization(Handle handle, s64 nanoseconds); + +/** + * @brief Waits for synchronization on multiple handles. + * @param[out] out Pointer to output the index of the synchronized handle to. + * @param handles Handles to wait on. + * @param handles_num Number of handles. + * @param wait_all Whether to wait for synchronization on all handles. + * @param nanoseconds Maximum nanoseconds to wait for. + */ +Result svcWaitSynchronizationN(s32* out, const Handle* handles, s32 handles_num, bool wait_all, s64 nanoseconds); + +/** + * @brief Creates an address arbiter + * @param[out] mutex Pointer to output the handle of the created address arbiter to. + * @sa svcArbitrateAddress + */ +Result svcCreateAddressArbiter(Handle *arbiter); + +/** + * @brief Arbitrate an address, can be used for synchronization + * @param arbiter Handle of the arbiter + * @param addr A pointer to a s32 value. + * @param type Type of action to be performed by the arbiter + * @param value Number of threads to signal if using @ref ARBITRATION_SIGNAL, or the value used for comparison. + * @param timeout_ns Optional timeout in nanoseconds when using TIMEOUT actions, ignored otherwise. If not needed, use \ref svcArbitrateAddressNoTimeout instead. + * @note Usage of this syscall entails an implicit Data Memory Barrier (dmb). + * @warning Please use \ref syncArbitrateAddressWithTimeout instead. + */ +Result svcArbitrateAddress(Handle arbiter, u32 addr, ArbitrationType type, s32 value, s64 timeout_ns); + +/** + * @brief Same as \ref svcArbitrateAddress but with the timeout_ns parameter undefined. + * @param arbiter Handle of the arbiter + * @param addr A pointer to a s32 value. + * @param type Type of action to be performed by the arbiter + * @param value Number of threads to signal if using @ref ARBITRATION_SIGNAL, or the value used for comparison. + * @note Usage of this syscall entails an implicit Data Memory Barrier (dmb). + * @warning Please use \ref syncArbitrateAddress instead. + */ +Result svcArbitrateAddressNoTimeout(Handle arbiter, u32 addr, ArbitrationType type, s32 value); + +/** + * @brief Sends a synchronized request to a session handle. + * @param session Handle of the session. + */ +Result svcSendSyncRequest(Handle session); + +/** + * @brief Connects to a port via a handle. + * @param[out] clientSession Pointer to output the client session handle to. + * @param clientPort Port client endpoint to connect to. + */ +Result svcCreateSessionToPort(Handle* clientSession, Handle clientPort); + +/** + * @brief Creates a linked pair of session endpoints. + * @param[out] serverSession Pointer to output the created server endpoint handle to. + * @param[out] clientSession Pointer to output the created client endpoint handle to. + */ +Result svcCreateSession(Handle* serverSession, Handle* clientSession); + +/** + * @brief Accepts a session. + * @param[out] session Pointer to output the created session handle to. + * @param port Handle of the port to accept a session from. + */ +Result svcAcceptSession(Handle* session, Handle port); + +/** + * @brief Replies to and receives a new request. + * @param index Pointer to the index of the request. + * @param handles Session handles to receive requests from. + * @param handleCount Number of handles. + * @param replyTarget Handle of the session to reply to. + */ +Result svcReplyAndReceive(s32* index, const Handle* handles, s32 handleCount, Handle replyTarget); + +///@} + +///@name Time +///@{ +/** + * @brief Creates a timer. + * @param[out] timer Pointer to output the handle of the created timer to. + * @param reset_type Type of reset to perform on the timer. + */ +Result svcCreateTimer(Handle* timer, ResetType reset_type); + +/** + * @brief Sets a timer. + * @param timer Handle of the timer to set. + * @param initial Initial value of the timer. + * @param interval Interval of the timer. + */ +Result svcSetTimer(Handle timer, s64 initial, s64 interval); + +/** + * @brief Cancels a timer. + * @param timer Handle of the timer to cancel. + */ +Result svcCancelTimer(Handle timer); + +/** + * @brief Clears a timer. + * @param timer Handle of the timer to clear. + */ +Result svcClearTimer(Handle timer); + +/** + * @brief Gets the current system tick. + * @return The current system tick. + */ +u64 svcGetSystemTick(void); +///@} + +///@name System +///@{ +/** + * @brief Closes a handle. + * @param handle Handle to close. + */ +Result svcCloseHandle(Handle handle); + +/** + * @brief Duplicates a handle. + * @param[out] out Pointer to output the duplicated handle to. + * @param original Handle to duplicate. + */ +Result svcDuplicateHandle(Handle* out, Handle original); + +/** + * @brief Gets a handle info. + * @param[out] out Pointer to output the handle info to. + * @param handle Handle to get the info for. + * @param param Parameter clarifying the handle info type. + */ +Result svcGetHandleInfo(s64* out, Handle handle, u32 param); + +/** + * @brief Gets the system info. + * @param[out] out Pointer to output the system info to. + * @param type Type of system info to retrieve. + * @param param Parameter clarifying the system info type. + */ +Result svcGetSystemInfo(s64* out, u32 type, s32 param); + +/** + * @brief Sets the current kernel state. + * @param type Type of state to set (the other parameters depend on it). + */ +Result svcKernelSetState(u32 type, ...); +///@} + +///@name Device drivers +///@{ + +/** + * @brief Binds an event or semaphore handle to an ARM11 interrupt. + * @param interruptId Interrupt identfier (see https://www.3dbrew.org/wiki/ARM11_Interrupts). + * @param eventOrSemaphore Event or semaphore handle to bind to the given interrupt. + * @param priority Priority of the interrupt for the current process. + * @param isManualClear Indicates whether the interrupt has to be manually cleared or not (= level-high active). + */ +Result svcBindInterrupt(u32 interruptId, Handle eventOrSemaphore, s32 priority, bool isManualClear); + +/** + * @brief Unbinds an event or semaphore handle from an ARM11 interrupt. + * @param interruptId Interrupt identfier, see (see https://www.3dbrew.org/wiki/ARM11_Interrupts). + * @param eventOrSemaphore Event or semaphore handle to unbind from the given interrupt. + */ +Result svcUnbindInterrupt(u32 interruptId, Handle eventOrSemaphore); + +/** + * @brief Invalidates a process's data cache. + * @param process Handle of the process. + * @param addr Address to invalidate. + * @param size Size of the memory to invalidate. + */ +Result svcInvalidateProcessDataCache(Handle process, u32 addr, u32 size); + +/** + * @brief Cleans a process's data cache. + * @param process Handle of the process. + * @param addr Address to clean. + * @param size Size of the memory to clean. + */ +Result svcStoreProcessDataCache(Handle process, u32 addr, u32 size); + +/** + * @brief Flushes (cleans and invalidates) a process's data cache. + * @param process Handle of the process. + * @param addr Address to flush. + * @param size Size of the memory to flush. + */ +Result svcFlushProcessDataCache(Handle process, u32 addr, u32 size); + +/** + * @brief Begins an inter-process DMA transfer. + * @param[out] dma Pointer to output the handle of the DMA channel object to. + * @param dstProcess Destination process handle. + * @param dstAddr Address in the destination process to write data to. + * @param srcProcess Source process handle. + * @param srcAddr Address in the source to read data from. + * @param size Size of the data to transfer. + * @param cfg Configuration structure. + * @note The handle is signaled when the transfer finishes. + */ +Result svcStartInterProcessDma(Handle *dma, Handle dstProcess, u32 dstAddr, Handle srcProcess, u32 srcAddr, u32 size, const DmaConfig *cfg); + +/** + * @brief Stops an inter-process DMA transfer. + * @param dma Handle of the DMA channel object. + */ +Result svcStopDma(Handle dma); + +/** + * @brief Gets the state of an inter-process DMA transfer. + * @param[out] state Pointer to output the state of the DMA transfer to. + * @param dma Handle of the DMA channel object. + */ +Result svcGetDmaState(DmaState *state, Handle dma); + +/** + * @brief Restarts a DMA transfer, using the same configuration as before. + * @param[out] state Pointer to output the state of the DMA transfer to. + * @param dma Handle of the DMA channel object. + * @param dstAddr Address in the destination process to write data to. + * @param srcAddr Address in the source to read data from. + * @param size Size of the data to transfer. + * @param flags Restart flags, \ref DMARST_UNLOCK and/or \ref DMARST_RESUME_DEVICE. + * @note The first transfer has to be configured with \ref DMACFG_KEEP_LOCKED. + */ +Result svcRestartDma(Handle dma, u32 dstAddr, u32 srcAddr, u32 size, s8 flags); + +/** + * @brief Sets the GPU protection register to restrict the range of the GPU DMA. 11.3+ only. + * @param useApplicationRestriction Whether to use the register value used for APPLICATION titles. + */ +Result svcSetGpuProt(bool useApplicationRestriction); + +/** + * @brief Enables or disables Wi-Fi. 11.4+ only. + * @param enabled Whether to enable or disable Wi-Fi. + */ +Result svcSetWifiEnabled(bool enabled); + +///@} + +///@name Debugging +///@{ +/** + * @brief Breaks execution. + * @param breakReason Reason for breaking. + */ +void svcBreak(UserBreakType breakReason); + +/** + * @brief Breaks execution (LOAD_RO and UNLOAD_RO). + * @param breakReason Debug reason for breaking. + * @param croInfo Library information. + * @param croInfoSize Size of the above structure. + */ +void svcBreakRO(UserBreakType breakReason, const void* croInfo, u32 croInfoSize) __asm__("svcBreak"); + +/** + * @brief Outputs a debug string. + * @param str String to output. + * @param length Length of the string to output, needs to be positive. + */ +Result svcOutputDebugString(const char* str, s32 length); + + +/** + * @brief Controls performance monitoring on the CP15 interface and the SCU. + * The meaning of the parameters depend on the operation. + * @param[out] out Output. + * @param op Operation, see details. + * @param param1 First parameter. + * @param param2 Second parameter. + * @details The operations are the following: + * - \ref PERFCOUNTEROP_ENABLE (void) -> void, tries to enable and lock perfmon. functionality. + * - \ref PERFCOUNTEROP_DISABLE (void) -> void, disable and forcibly unlocks perfmon. functionality. + * - \ref PERFCOUNTEROP_GET_VALUE (\ref PerfCounterRegister reg) -> u64, gets the value of a particular counter register. + * - \ref PERFCOUNTEROP_SET_VALUE (\ref PerfCounterRegister reg, u64 value) -> void, sets the value of a particular counter register. + * - \ref PERFCOUNTEROP_GET_OVERFLOW_FLAGS (void) -> u32, gets the overflow flags of all CP15 and SCU registers. + * - Format is a bitfield of \ref PerfCounterRegister. + * - \ref PERFCOUNTEROP_RESET (u32 valueResetMask, u32 overflowFlagResetMask) -> void, resets the value and/or + * overflow flags of selected registers. + * - Format is two bitfields of \ref PerfCounterRegister. + * - \ref PERFCOUNTEROP_GET_EVENT (\ref PerfCounterRegister reg) -> \ref PerfCounterEvent, gets the event associated + * to a particular counter register. + * - \ref PERFCOUNTEROP_SET_EVENT (\ref PerfCounterRegister reg, \ref PerfCounterEvent) -> void, sets the event associated + * to a particular counter register. + * - \ref PERFCOUNTEROP_SET_VIRTUAL_COUNTER_ENABLED (bool enabled) -> void, (dis)allows the kernel to track counter overflows + * and to use 64-bit counter values. + */ +Result svcControlPerformanceCounter(u64 *out, PerfCounterOperation op, u32 param1, u64 param2); + +/** + * @brief Creates a debug handle for an active process. + * @param[out] debug Pointer to output the created debug handle to. + * @param processId ID of the process to debug. + */ +Result svcDebugActiveProcess(Handle* debug, u32 processId); + +/** + * @brief Breaks a debugged process. + * @param debug Debug handle of the process. + */ +Result svcBreakDebugProcess(Handle debug); + +/** + * @brief Terminates a debugged process. + * @param debug Debug handle of the process. + */ +Result svcTerminateDebugProcess(Handle debug); + +/** + * @brief Gets the current debug event of a debugged process. + * @param[out] info Pointer to output the debug event information to. + * @param debug Debug handle of the process. + */ +Result svcGetProcessDebugEvent(DebugEventInfo* info, Handle debug); + +/** + * @brief Continues the current debug event of a debugged process (not necessarily the same as @ref svcGetProcessDebugEvent). + * @param debug Debug handle of the process. + * @param flags Flags to continue with, see @ref DebugFlags. + */ +Result svcContinueDebugEvent(Handle debug, DebugFlags flags); + +/** + * @brief Fetches the saved registers of a thread, either inactive or awaiting @ref svcContinueDebugEvent, belonging to a debugged process. + * @param[out] context Values of the registers to fetch, see @ref ThreadContext. + * @param debug Debug handle of the parent process. + * @param threadId ID of the thread to fetch the saved registers of. + * @param controlFlags Which registers to fetch, see @ref ThreadContextControlFlags. + */ +Result svcGetDebugThreadContext(ThreadContext* context, Handle debug, u32 threadId, ThreadContextControlFlags controlFlags); + +/** + * @brief Updates the saved registers of a thread, either inactive or awaiting @ref svcContinueDebugEvent, belonging to a debugged process. + * @param debug Debug handle of the parent process. + * @param threadId ID of the thread to update the saved registers of. + * @param context Values of the registers to update, see @ref ThreadContext. + * @param controlFlags Which registers to update, see @ref ThreadContextControlFlags. + */ +Result svcSetDebugThreadContext(Handle debug, u32 threadId, ThreadContext* context, ThreadContextControlFlags controlFlags); + +/** + * @brief Queries memory information of a debugged process. + * @param[out] info Pointer to output memory info to. + * @param[out] out Pointer to output page info to. + * @param debug Debug handle of the process to query memory from. + * @param addr Virtual memory address to query. + */ +Result svcQueryDebugProcessMemory(MemInfo* info, PageInfo* out, Handle debug, u32 addr); + +/** + * @brief Reads from a debugged process's memory. + * @param buffer Buffer to read data to. + * @param debug Debug handle of the process. + * @param addr Address to read from. + * @param size Size of the memory to read. + */ +Result svcReadProcessMemory(void* buffer, Handle debug, u32 addr, u32 size); + +/** + * @brief Writes to a debugged process's memory. + * @param debug Debug handle of the process. + * @param buffer Buffer to write data from. + * @param addr Address to write to. + * @param size Size of the memory to write. + */ +Result svcWriteProcessMemory(Handle debug, const void* buffer, u32 addr, u32 size); + +/** + * @brief Sets an hardware breakpoint or watchpoint. This is an interface to the BRP/WRP registers, see http://infocenter.arm.com/help/topic/com.arm.doc.ddi0360f/CEGEBGFC.html . + * @param registerId range 0..5 = breakpoints (BRP0-5), 0x100..0x101 = watchpoints (WRP0-1). The previous stop point for the register is disabled. + * @param control Value of the control regiser. + * @param value Value of the value register: either and address (if bit21 of control is clear) or the debug handle of a process to fetch the context ID of. + */ +Result svcSetHardwareBreakPoint(s32 registerId, u32 control, u32 value); + +/** + * @brief Gets a debugged thread's parameter. + * @param[out] unused Unused. + * @param[out] out Output value. + * @param debug Debug handle of the process. + * @param threadId ID of the thread + * @param parameter Parameter to fetch, see @ref DebugThreadParameter. + */ +Result svcGetDebugThreadParam(s64* unused, u32* out, Handle debug, u32 threadId, DebugThreadParameter parameter); + +///@} + +/** + * @brief Executes a function in supervisor mode. + * @param callback Function to execute. + */ +Result svcBackdoor(s32 (*callback)(void)); + +/// Stop point, does nothing if the process is not attached (as opposed to 'bkpt' instructions) +#define SVC_STOP_POINT __asm__ volatile("svc 0xFF"); diff --git a/libctru/include/3ds/synchronization.h b/libctru/include/3ds/synchronization.h new file mode 100644 index 0000000000..d8edfdc392 --- /dev/null +++ b/libctru/include/3ds/synchronization.h @@ -0,0 +1,344 @@ +/** + * @file synchronization.h + * @brief Provides synchronization locks. + */ +#pragma once +#include +#include <3ds/svc.h> + +/// A light lock. +typedef _LOCK_T LightLock; + +/// A recursive lock. +typedef _LOCK_RECURSIVE_T RecursiveLock; + +/// A condition variable. +typedef s32 CondVar; + +/// A light event. +typedef struct +{ + s32 state; ///< State of the event: -2=cleared sticky, -1=cleared oneshot, 0=signaled oneshot, 1=signaled sticky + LightLock lock; ///< Lock used for sticky timer operation +} LightEvent; + +/// A light semaphore. +typedef struct +{ + s32 current_count; ///< The current release count of the semaphore + s16 num_threads_acq; ///< Number of threads concurrently acquiring the semaphore + s16 max_count; ///< The maximum release count of the semaphore +} LightSemaphore; + +/// Performs a Data Synchronization Barrier operation. +static inline void __dsb(void) +{ + __asm__ __volatile__("mcr p15, 0, %[val], c7, c10, 4" :: [val] "r" (0) : "memory"); +} + +/// Performs a Data Memory Barrier operation. +static inline void __dmb(void) +{ + __asm__ __volatile__("mcr p15, 0, %[val], c7, c10, 5" :: [val] "r" (0) : "memory"); +} + +/// Performs a clrex operation. +static inline void __clrex(void) +{ + __asm__ __volatile__("clrex" ::: "memory"); +} + +/** + * @brief Performs a ldrex operation. + * @param addr Address to perform the operation on. + * @return The resulting value. + */ +static inline s32 __ldrex(s32* addr) +{ + s32 val; + __asm__ __volatile__("ldrex %[val], %[addr]" : [val] "=r" (val) : [addr] "Q" (*addr)); + return val; +} + +/** + * @brief Performs a strex operation. + * @param addr Address to perform the operation on. + * @param val Value to store. + * @return Whether the operation was successful. + */ +static inline bool __strex(s32* addr, s32 val) +{ + bool res; + __asm__ __volatile__("strex %[res], %[val], %[addr]" : [res] "=&r" (res) : [val] "r" (val), [addr] "Q" (*addr)); + return res; +} + +/** + * @brief Performs a ldrexh operation. + * @param addr Address to perform the operation on. + * @return The resulting value. + */ +static inline u16 __ldrexh(u16* addr) +{ + u16 val; + __asm__ __volatile__("ldrexh %[val], %[addr]" : [val] "=r" (val) : [addr] "Q" (*addr)); + return val; +} + +/** + * @brief Performs a strexh operation. + * @param addr Address to perform the operation on. + * @param val Value to store. + * @return Whether the operation was successful. + */ +static inline bool __strexh(u16* addr, u16 val) +{ + bool res; + __asm__ __volatile__("strexh %[res], %[val], %[addr]" : [res] "=&r" (res) : [val] "r" (val), [addr] "Q" (*addr)); + return res; +} + +/** + * @brief Performs a ldrexb operation. + * @param addr Address to perform the operation on. + * @return The resulting value. + */ +static inline u8 __ldrexb(u8* addr) +{ + u8 val; + __asm__ __volatile__("ldrexb %[val], %[addr]" : [val] "=r" (val) : [addr] "Q" (*addr)); + return val; +} + +/** + * @brief Performs a strexb operation. + * @param addr Address to perform the operation on. + * @param val Value to store. + * @return Whether the operation was successful. + */ +static inline bool __strexb(u8* addr, u8 val) +{ + bool res; + __asm__ __volatile__("strexb %[res], %[val], %[addr]" : [res] "=&r" (res) : [val] "r" (val), [addr] "Q" (*addr)); + return res; +} + +/// Performs an atomic pre-increment operation. +#define AtomicIncrement(ptr) __atomic_add_fetch((u32*)(ptr), 1, __ATOMIC_SEQ_CST) +/// Performs an atomic pre-decrement operation. +#define AtomicDecrement(ptr) __atomic_sub_fetch((u32*)(ptr), 1, __ATOMIC_SEQ_CST) +/// Performs an atomic post-increment operation. +#define AtomicPostIncrement(ptr) __atomic_fetch_add((u32*)(ptr), 1, __ATOMIC_SEQ_CST) +/// Performs an atomic post-decrement operation. +#define AtomicPostDecrement(ptr) __atomic_fetch_sub((u32*)(ptr), 1, __ATOMIC_SEQ_CST) +/// Performs an atomic swap operation. +#define AtomicSwap(ptr, value) __atomic_exchange_n((u32*)(ptr), (value), __ATOMIC_SEQ_CST) + +/** + * @brief Function used to implement user-mode synchronization primitives. + * @param addr Pointer to a signed 32-bit value whose address will be used to identify waiting threads. + * @param type Type of action to be performed by the arbiter + * @param value Number of threads to signal if using @ref ARBITRATION_SIGNAL, or the value used for comparison. + * + * This will perform an arbitration based on #type. The comparisons are done between #value and the value at the address #addr. + * + * @code + * s32 val=0; + * // Does *nothing* since val >= 0 + * syncArbitrateAddress(&val,ARBITRATION_WAIT_IF_LESS_THAN,0); + * @endcode + * + * @note Usage of this function entails an implicit Data Memory Barrier (dmb). + */ +Result syncArbitrateAddress(s32* addr, ArbitrationType type, s32 value); + +/** + * @brief Function used to implement user-mode synchronization primitives (with timeout). + * @param addr Pointer to a signed 32-bit value whose address will be used to identify waiting threads. + * @param type Type of action to be performed by the arbiter (must use \ref ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT or \ref ARBITRATION_DECREMENT_AND_WAIT_IF_LESS_THAN_TIMEOUT) + * @param value Number of threads to signal if using @ref ARBITRATION_SIGNAL, or the value used for comparison. + * + * This will perform an arbitration based on #type. The comparisons are done between #value and the value at the address #addr. + * + * @code + * s32 val=0; + * // Thread will wait for a signal or wake up after 10000000 nanoseconds because val < 1. + * syncArbitrateAddressWithTimeout(&val,ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT,1,10000000LL); + * @endcode + * + * @note Usage of this function entails an implicit Data Memory Barrier (dmb). + */ +Result syncArbitrateAddressWithTimeout(s32* addr, ArbitrationType type, s32 value, s64 timeout_ns); + +/** + * @brief Initializes a light lock. + * @param lock Pointer to the lock. + */ +void LightLock_Init(LightLock* lock); + +/** + * @brief Locks a light lock. + * @param lock Pointer to the lock. + */ +void LightLock_Lock(LightLock* lock); + +/** + * @brief Attempts to lock a light lock. + * @param lock Pointer to the lock. + * @return Zero on success, non-zero on failure. + */ +int LightLock_TryLock(LightLock* lock); + +/** + * @brief Unlocks a light lock. + * @param lock Pointer to the lock. + */ +void LightLock_Unlock(LightLock* lock); + +/** + * @brief Initializes a recursive lock. + * @param lock Pointer to the lock. + */ +void RecursiveLock_Init(RecursiveLock* lock); + +/** + * @brief Locks a recursive lock. + * @param lock Pointer to the lock. + */ +void RecursiveLock_Lock(RecursiveLock* lock); + +/** + * @brief Attempts to lock a recursive lock. + * @param lock Pointer to the lock. + * @return Zero on success, non-zero on failure. + */ +int RecursiveLock_TryLock(RecursiveLock* lock); + +/** + * @brief Unlocks a recursive lock. + * @param lock Pointer to the lock. + */ +void RecursiveLock_Unlock(RecursiveLock* lock); + +/** + * @brief Initializes a condition variable. + * @param cv Pointer to the condition variable. + */ +void CondVar_Init(CondVar* cv); + +/** + * @brief Waits on a condition variable. + * @param cv Pointer to the condition variable. + * @param lock Pointer to the lock to atomically unlock/relock during the wait. + */ +void CondVar_Wait(CondVar* cv, LightLock* lock); + +/** + * @brief Waits on a condition variable with a timeout. + * @param cv Pointer to the condition variable. + * @param lock Pointer to the lock to atomically unlock/relock during the wait. + * @param timeout_ns Timeout in nanoseconds. + * @return Zero on success, non-zero on failure. + */ +int CondVar_WaitTimeout(CondVar* cv, LightLock* lock, s64 timeout_ns); + +/** + * @brief Wakes up threads waiting on a condition variable. + * @param cv Pointer to the condition variable. + * @param num_threads Maximum number of threads to wake up (or \ref ARBITRATION_SIGNAL_ALL to wake them all). + */ +void CondVar_WakeUp(CondVar* cv, s32 num_threads); + +/** + * @brief Wakes up a single thread waiting on a condition variable. + * @param cv Pointer to the condition variable. + */ +static inline void CondVar_Signal(CondVar* cv) +{ + CondVar_WakeUp(cv, 1); +} + +/** + * @brief Wakes up all threads waiting on a condition variable. + * @param cv Pointer to the condition variable. + */ +static inline void CondVar_Broadcast(CondVar* cv) +{ + CondVar_WakeUp(cv, ARBITRATION_SIGNAL_ALL); +} + +/** + * @brief Initializes a light event. + * @param event Pointer to the event. + * @param reset_type Type of reset the event uses (RESET_ONESHOT/RESET_STICKY). + */ +void LightEvent_Init(LightEvent* event, ResetType reset_type); + +/** + * @brief Clears a light event. + * @param event Pointer to the event. + */ +void LightEvent_Clear(LightEvent* event); + +/** + * @brief Wakes up threads waiting on a sticky light event without signaling it. If the event had been signaled before, it is cleared instead. + * @param event Pointer to the event. + */ +void LightEvent_Pulse(LightEvent* event); + +/** + * @brief Signals a light event, waking up threads waiting on it. + * @param event Pointer to the event. + */ +void LightEvent_Signal(LightEvent* event); + +/** + * @brief Attempts to wait on a light event. + * @param event Pointer to the event. + * @return Non-zero if the event was signaled, zero otherwise. + */ +int LightEvent_TryWait(LightEvent* event); + +/** + * @brief Waits on a light event. + * @param event Pointer to the event. + */ +void LightEvent_Wait(LightEvent* event); + +/** + * @brief Waits on a light event until either the event is signaled or the timeout is reached. + * @param event Pointer to the event. + * @param timeout_ns Timeout in nanoseconds. + * @return Non-zero on timeout, zero otherwise. + */ +int LightEvent_WaitTimeout(LightEvent* event, s64 timeout_ns); + +/** + * @brief Initializes a light semaphore. + * @param event Pointer to the semaphore. + * @param max_count Initial count of the semaphore. + * @param max_count Maximum count of the semaphore. + */ +void LightSemaphore_Init(LightSemaphore* semaphore, s16 initial_count, s16 max_count); + +/** + * @brief Acquires a light semaphore. + * @param semaphore Pointer to the semaphore. + * @param count Acquire count + */ +void LightSemaphore_Acquire(LightSemaphore* semaphore, s32 count); + +/** + * @brief Attempts to acquire a light semaphore. + * @param semaphore Pointer to the semaphore. + * @param count Acquire count + * @return Zero on success, non-zero on failure + */ +int LightSemaphore_TryAcquire(LightSemaphore* semaphore, s32 count); + +/** + * @brief Releases a light semaphore. + * @param semaphore Pointer to the semaphore. + * @param count Release count + */ +void LightSemaphore_Release(LightSemaphore* semaphore, s32 count); diff --git a/libctru/include/3ds/thread.h b/libctru/include/3ds/thread.h new file mode 100644 index 0000000000..68c99ebf54 --- /dev/null +++ b/libctru/include/3ds/thread.h @@ -0,0 +1,120 @@ +/** + * @file thread.h + * @brief Provides functions to use threads. + */ +#pragma once +#include <3ds/types.h> +#include <3ds/result.h> +#include <3ds/synchronization.h> +#include <3ds/svc.h> +#include <3ds/errf.h> + +/// Makes the exception handler reuse the stack of the faulting thread as-is +#define RUN_HANDLER_ON_FAULTING_STACK ((void*)1) + +/// Makes the exception handler push the exception data on its stack +#define WRITE_DATA_TO_HANDLER_STACK NULL + +/// Makes the exception handler push the exception data on the stack of the faulting thread +#define WRITE_DATA_TO_FAULTING_STACK ((ERRF_ExceptionData*)1) + +/// libctru thread handle type +typedef struct Thread_tag* Thread; + +/// Exception handler type, necessarily an ARM function that does not return. +typedef void (*ExceptionHandler)(ERRF_ExceptionInfo* excep, CpuRegisters* regs); + +/** + * @brief Creates a new libctru thread. + * @param entrypoint The function that will be called first upon thread creation + * @param arg The argument passed to @p entrypoint + * @param stack_size The size of the stack that will be allocated for the thread (will be rounded to a multiple of 8 bytes) + * @param prio Low values gives the thread higher priority. + * For userland apps, this has to be within the range [0x18;0x3F]. + * The main thread usually has a priority of 0x30, but not always. Use svcGetThreadPriority() if you need + * to create a thread with a priority that is explicitly greater or smaller than that of the main thread. + * @param core_id The ID of the processor the thread should be ran on. Processor IDs are labeled starting from 0. + * On Old3DS it must be <2, and on New3DS it must be <4. + * Pass -1 to execute the thread on all CPUs and -2 to execute the thread on the default CPU (read from the Exheader). + * @param detached When set to true, the thread is automatically freed when it finishes. + * @return The libctru thread handle on success, NULL on failure. + * + * - Processor #0 is the application core. It is always possible to create a thread on this core. + * - Processor #1 is the system core. If APT_SetAppCpuTimeLimit is used, it is possible to create a single thread on this core. + * - Processor #2 is New3DS exclusive. Normal applications can create threads on this core if the exheader kernel flags bitmask has 0x2000 set. + * - Processor #3 is New3DS exclusive. Normal applications cannot create threads on this core. + * - Processes in the BASE memory region can always create threads on processors #2 and #3. + * + * @note Default exit code of a thread is 0. + * @warning @ref svcExitThread should never be called from the thread, use @ref threadExit instead. + */ +Thread threadCreate(ThreadFunc entrypoint, void* arg, size_t stack_size, int prio, int core_id, bool detached); + +/** + * @brief Retrieves the OS thread handle of a libctru thread. + * @param thread libctru thread handle + * @return OS thread handle + */ +Handle threadGetHandle(Thread thread); + +/** + * @brief Retrieves the exit code of a finished libctru thread. + * @param thread libctru thread handle + * @return Exit code + */ +int threadGetExitCode(Thread thread); + +/** + * @brief Frees a finished libctru thread. + * @param thread libctru thread handle + * @remarks This function should not be called if the thread is detached, as it is freed automatically when it finishes. + */ +void threadFree(Thread thread); + +/** + * @brief Waits for a libctru thread to finish (or returns immediately if it is already finished). + * @param thread libctru thread handle + * @param timeout_ns Timeout in nanoseconds. Pass U64_MAX if a timeout isn't desired + */ +Result threadJoin(Thread thread, u64 timeout_ns); + +/** + * @brief Changes a thread's status from attached to detached. + * @param thread libctru thread handle + */ +void threadDetach(Thread thread); + +/** + * @brief Retrieves the libctru thread handle of the current thread. + * @return libctru thread handle of the current thread, or NULL for the main thread + */ +Thread threadGetCurrent(void); + +/** + * @brief Exits the current libctru thread with an exit code (not usable from the main thread). + * @param rc Exit code + */ +void threadExit(int rc) __attribute__((noreturn)); + +/** + * @brief Sets the exception handler for the current thread. Called from the main thread, this sets the default handler. + * @param handler The exception handler, necessarily an ARM function that does not return + * @param stack_top A pointer to the top of the stack that will be used by the handler. See also @ref RUN_HANDLER_ON_FAULTING_STACK + * @param exception_data A pointer to the buffer that will contain the exception data. + See also @ref WRITE_DATA_TO_HANDLER_STACK and @ref WRITE_DATA_TO_FAULTING_STACK + * + * To have CPU exceptions reported through this mechanism, it is normally necessary that UNITINFO is set to a non-zero value when Kernel11 starts, + * and this mechanism is also controlled by @ref svcKernelSetState type 6, see 3dbrew. + * + * VFP exceptions are always reported this way even if the process is being debugged using the debug SVCs. + * + * The current thread need not be a libctru thread. + */ +static inline void threadOnException(ExceptionHandler handler, void* stack_top, ERRF_ExceptionData* exception_data) +{ + u8* tls = (u8*)getThreadLocalStorage(); + + *(u32*)(tls + 0x40) = (u32)handler; + *(u32*)(tls + 0x44) = (u32)stack_top; + *(u32*)(tls + 0x48) = (u32)exception_data; +} diff --git a/libctru/include/3ds/types.h b/libctru/include/3ds/types.h new file mode 100644 index 0000000000..eed876a7b7 --- /dev/null +++ b/libctru/include/3ds/types.h @@ -0,0 +1,79 @@ +/** + * @file types.h + * @brief Various system types. + */ +#pragma once + +#include +#include +#include + +/// The maximum value of a u64. +#define U64_MAX UINT64_MAX + +/// would be nice if newlib had this already +#ifndef SSIZE_MAX +#ifdef SIZE_MAX +#define SSIZE_MAX ((SIZE_MAX) >> 1) +#endif +#endif + +typedef uint8_t u8; ///< 8-bit unsigned integer +typedef uint16_t u16; ///< 16-bit unsigned integer +typedef uint32_t u32; ///< 32-bit unsigned integer +typedef uint64_t u64; ///< 64-bit unsigned integer + +typedef int8_t s8; ///< 8-bit signed integer +typedef int16_t s16; ///< 16-bit signed integer +typedef int32_t s32; ///< 32-bit signed integer +typedef int64_t s64; ///< 64-bit signed integer + +typedef volatile u8 vu8; ///< 8-bit volatile unsigned integer. +typedef volatile u16 vu16; ///< 16-bit volatile unsigned integer. +typedef volatile u32 vu32; ///< 32-bit volatile unsigned integer. +typedef volatile u64 vu64; ///< 64-bit volatile unsigned integer. + +typedef volatile s8 vs8; ///< 8-bit volatile signed integer. +typedef volatile s16 vs16; ///< 16-bit volatile signed integer. +typedef volatile s32 vs32; ///< 32-bit volatile signed integer. +typedef volatile s64 vs64; ///< 64-bit volatile signed integer. + +typedef u32 Handle; ///< Resource handle. +typedef s32 Result; ///< Function result. +typedef void (*ThreadFunc)(void *); ///< Thread entrypoint function. +typedef void (*voidfn)(void); + +/// Creates a bitmask from a bit number. +#define BIT(n) (1U<<(n)) + +/// Aligns a struct (and other types?) to m, making sure that the size of the struct is a multiple of m. +#define ALIGN(m) __attribute__((aligned(m))) +/// Packs a struct (and other types?) so it won't include padding bytes. +#define PACKED __attribute__((packed)) + +#ifndef LIBCTRU_NO_DEPRECATION +/// Flags a function as deprecated. +#define DEPRECATED __attribute__ ((deprecated)) +#else +/// Flags a function as deprecated. +#define DEPRECATED +#endif + +/// Structure representing CPU registers +typedef struct { + u32 r[13]; ///< r0-r12. + u32 sp; ///< sp. + u32 lr; ///< lr. + u32 pc; ///< pc. May need to be adjusted. + u32 cpsr; ///< cpsr. +} CpuRegisters; + +/// Structure representing FPU registers +typedef struct { + union { + struct PACKED { double d[16]; }; ///< d0-d15. + float s[32]; ///< s0-s31. + }; + u32 fpscr; ///< fpscr. + u32 fpexc; ///< fpexc. +} FpuRegisters; diff --git a/libctru/include/3ds/util/decompress.h b/libctru/include/3ds/util/decompress.h new file mode 100644 index 0000000000..9538903d1a --- /dev/null +++ b/libctru/include/3ds/util/decompress.h @@ -0,0 +1,238 @@ +/** + * @file decompress.h + * @brief Decompression functions. + */ +#pragma once + +#include +#include +#include + +/** @brief Compression types */ +typedef enum +{ + DECOMPRESS_DUMMY = 0x00, ///< Dummy compression + DECOMPRESS_LZSS = 0x10, ///< LZSS/LZ10 compression + DECOMPRESS_LZ10 = 0x10, ///< LZSS/LZ10 compression + DECOMPRESS_LZ11 = 0x11, ///< LZ11 compression + DECOMPRESS_HUFF1 = 0x21, ///< Huffman compression with 1-bit data + DECOMPRESS_HUFF2 = 0x22, ///< Huffman compression with 2-bit data + DECOMPRESS_HUFF3 = 0x23, ///< Huffman compression with 3-bit data + DECOMPRESS_HUFF4 = 0x24, ///< Huffman compression with 4-bit data + DECOMPRESS_HUFF5 = 0x25, ///< Huffman compression with 5-bit data + DECOMPRESS_HUFF6 = 0x26, ///< Huffman compression with 6-bit data + DECOMPRESS_HUFF7 = 0x27, ///< Huffman compression with 7-bit data + DECOMPRESS_HUFF8 = 0x28, ///< Huffman compression with 8-bit data + DECOMPRESS_HUFF = 0x28, ///< Huffman compression with 8-bit data + DECOMPRESS_RLE = 0x30, ///< Run-length encoding compression +} decompressType; + +/** @brief I/O vector */ +typedef struct +{ + void *data; ///< I/O buffer + size_t size; ///< Buffer size +} decompressIOVec; + +/** @brief Data callback */ +typedef ssize_t (*decompressCallback)(void *userdata, void *buffer, + size_t size); + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief Decompression callback for file descriptors + * @param[in] userdata Address of file descriptor + * @param[in] buffer Buffer to write into + * @param[in] size Size to read from file descriptor + * @returns Number of bytes read + */ +ssize_t decompressCallback_FD(void *userdata, void *buffer, size_t size); + +/** @brief Decompression callback for stdio FILE* + * @param[in] userdata FILE* + * @param[in] buffer Buffer to write into + * @param[in] size Size to read from file descriptor + * @returns Number of bytes read + */ +ssize_t decompressCallback_Stdio(void *userdata, void *buffer, size_t size); + +/** @brief Decode decompression header + * @param[out] type Decompression type + * @param[out] size Decompressed size + * @param[in] callback Data callback (see decompressV()) + * @param[in] userdata User data passed to callback (see decompressV()) + * @param[in] insize Size of userdata (see decompressV()) + * @returns Bytes consumed + * @retval -1 error + */ +ssize_t decompressHeader(decompressType *type, size_t *size, + decompressCallback callback, void *userdata, + size_t insize); + +/** @brief Decompress data + * @param[in] iov Output vector + * @param[in] iovcnt Number of buffers + * @param[in] callback Data callback (see note) + * @param[in] userdata User data passed to callback (see note) + * @param[in] insize Size of userdata (see note) + * @returns Whether succeeded + * + * @note If callback is null, userdata is a pointer to memory to read from, + * and insize is the size of that data. If callback is not null, + * userdata is passed to callback to fetch more data, and insize is + * unused. + */ +bool decompressV(const decompressIOVec *iov, size_t iovcnt, + decompressCallback callback, void *userdata, size_t insize); + +/** @brief Decompress data + * @param[in] output Output buffer + * @param[in] size Output size limit + * @param[in] callback Data callback (see decompressV()) + * @param[in] userdata User data passed to callback (see decompressV()) + * @param[in] insize Size of userdata (see decompressV()) + * @returns Whether succeeded + */ +static inline bool +decompress(void *output, size_t size, decompressCallback callback, + void *userdata, size_t insize) +{ + decompressIOVec iov; + iov.data = output; + iov.size = size; + + return decompressV(&iov, 1, callback, userdata, insize); +} + +/** @brief Decompress LZSS/LZ10 + * @param[in] iov Output vector + * @param[in] iovcnt Number of buffers + * @param[in] callback Data callback (see decompressV()) + * @param[in] userdata User data passed to callback (see decompressV()) + * @param[in] insize Size of userdata (see decompressV()) + * @returns Whether succeeded + */ +bool decompressV_LZSS(const decompressIOVec *iov, size_t iovcnt, + decompressCallback callback, void *userdata, + size_t insize); + +/** @brief Decompress LZSS/LZ10 + * @param[in] output Output buffer + * @param[in] size Output size limit + * @param[in] callback Data callback (see decompressV()) + * @param[in] userdata User data passed to callback (see decompressV()) + * @param[in] insize Size of userdata (see decompressV()) + * @returns Whether succeeded + */ +static inline bool +decompress_LZSS(void *output, size_t size, decompressCallback callback, + void *userdata, size_t insize) +{ + decompressIOVec iov; + iov.data = output; + iov.size = size; + + return decompressV_LZSS(&iov, 1, callback, userdata, insize); +} + +/** @brief Decompress LZ11 + * @param[in] iov Output vector + * @param[in] iovcnt Number of buffers + * @param[in] callback Data callback (see decompressV()) + * @param[in] userdata User data passed to callback (see decompressV()) + * @param[in] insize Size of userdata (see decompressV()) + * @returns Whether succeeded + */ +bool decompressV_LZ11(const decompressIOVec *iov, size_t iovcnt, + decompressCallback callback, void *userdata, + size_t insize); + +/** @brief Decompress LZ11 + * @param[in] output Output buffer + * @param[in] size Output size limit + * @param[in] callback Data callback (see decompressV()) + * @param[in] userdata User data passed to callback (see decompressV()) + * @param[in] insize Size of userdata (see decompressV()) + * @returns Whether succeeded + */ +static inline bool +decompress_LZ11(void *output, size_t size, decompressCallback callback, + void *userdata, size_t insize) +{ + decompressIOVec iov; + iov.data = output; + iov.size = size; + + return decompressV_LZ11(&iov, 1, callback, userdata, insize); +} + +/** @brief Decompress Huffman + * @param[in] bits Data size in bits (usually 4 or 8) + * @param[in] iov Output vector + * @param[in] iovcnt Number of buffers + * @param[in] callback Data callback (see decompressV()) + * @param[in] userdata User data passed to callback (see decompressV()) + * @param[in] insize Size of userdata (see decompressV()) + * @returns Whether succeeded + */ +bool decompressV_Huff(size_t bits, const decompressIOVec *iov, size_t iovcnt, + decompressCallback callback, void *userdata, + size_t insize); + +/** @brief Decompress Huffman + * @param[in] bits Data size in bits (usually 4 or 8) + * @param[in] output Output buffer + * @param[in] size Output size limit + * @param[in] callback Data callback (see decompressV()) + * @param[in] userdata User data passed to callback (see decompressV()) + * @param[in] insize Size of userdata (see decompressV()) + * @returns Whether succeeded + */ +static inline bool +decompress_Huff(size_t bits, void *output, size_t size, + decompressCallback callback, void *userdata, size_t insize) +{ + decompressIOVec iov; + iov.data = output; + iov.size = size; + + return decompressV_Huff(bits, &iov, 1, callback, userdata, insize); +} + +/** @brief Decompress run-length encoding + * @param[in] iov Output vector + * @param[in] iovcnt Number of buffers + * @param[in] callback Data callback (see decompressV()) + * @param[in] userdata User data passed to callback (see decompressV()) + * @param[in] insize Size of userdata (see decompressV()) + * @returns Whether succeeded + */ +bool decompressV_RLE(const decompressIOVec *iov, size_t iovcnt, + decompressCallback callback, void *userdata, + size_t insize); + +/** @brief Decompress run-length encoding + * @param[in] output Output buffer + * @param[in] size Output size limit + * @param[in] callback Data callback (see decompressV()) + * @param[in] userdata User data passed to callback (see decompressV()) + * @param[in] insize Size of userdata (see decompressV()) + * @returns Whether succeeded + */ +static inline bool +decompress_RLE(void *output, size_t size, decompressCallback callback, + void *userdata, size_t insize) +{ + decompressIOVec iov; + iov.data = output; + iov.size = size; + + return decompressV_RLE(&iov, 1, callback, userdata, insize); +} + +#ifdef __cplusplus +} +#endif diff --git a/libctru/include/3ds/util/rbtree.h b/libctru/include/3ds/util/rbtree.h new file mode 100644 index 0000000000..08778b932e --- /dev/null +++ b/libctru/include/3ds/util/rbtree.h @@ -0,0 +1,149 @@ +/** + * @file rbtree.h + * @brief Red-black trees. + */ +#pragma once + +#include +#include + +/// Retrieves an rbtree item. +#define rbtree_item(ptr, type, member) \ + ((type*)(((char*)ptr) - offsetof(type, member))) + +typedef struct rbtree rbtree_t; ///< rbtree type. +typedef struct rbtree_node rbtree_node_t; ///< rbtree node type. + +typedef void (*rbtree_node_destructor_t)(rbtree_node_t *Node); ///< rbtree node destructor. +typedef int (*rbtree_node_comparator_t)(const rbtree_node_t *lhs, + const rbtree_node_t *rhs); ///< rbtree node comparator. + +/// An rbtree node. +struct rbtree_node +{ + uintptr_t parent_color; ///< Parent color. + rbtree_node_t *child[2]; ///< Node children. +}; + +/// An rbtree. +struct rbtree +{ + rbtree_node_t *root; ///< Root node. + rbtree_node_comparator_t comparator; ///< Node comparator. + size_t size; ///< Size. +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initializes an rbtree. + * @param tree Pointer to the tree. + * @param comparator Comparator to use. + */ +void +rbtree_init(rbtree_t *tree, + rbtree_node_comparator_t comparator); + +/** + * @brief Gets whether an rbtree is empty + * @param tree Pointer to the tree. + * @return A non-zero value if the tree is not empty. + */ +int +rbtree_empty(const rbtree_t *tree); + +/** + * @brief Gets the size of an rbtree. + * @param tree Pointer to the tree. + */ +size_t +rbtree_size(const rbtree_t *tree); + +/** + * @brief Inserts a node into an rbtree. + * @param tree Pointer to the tree. + * @param node Pointer to the node. + * @return The inserted node. + */ +__attribute__((warn_unused_result)) +rbtree_node_t* +rbtree_insert(rbtree_t *tree, + rbtree_node_t *node); + +/** + * @brief Inserts multiple nodes into an rbtree. + * @param tree Pointer to the tree. + * @param node Pointer to the nodes. + */ +void +rbtree_insert_multi(rbtree_t *tree, + rbtree_node_t *node); + +/** + * @brief Finds a node within an rbtree. + * @param tree Pointer to the tree. + * @param node Pointer to the node. + * @return The located node. + */ +rbtree_node_t* +rbtree_find(const rbtree_t *tree, + const rbtree_node_t *node); + +/** + * @brief Gets the minimum node of an rbtree. + * @param tree Pointer to the tree. + * @return The minimum node. + */ +rbtree_node_t* +rbtree_min(const rbtree_t *tree); + +/** + * @brief Gets the maximum node of an rbtree. + * @param tree Pointer to the tree. + * @return The maximum node. + */ +rbtree_node_t* +rbtree_max(const rbtree_t *tree); + +/** + * @brief Gets the next node from an rbtree node. + * @param node Pointer to the node. + * @return The next node. + */ +rbtree_node_t* +rbtree_node_next(const rbtree_node_t *node); + +/** + * @brief Gets the previous node from an rbtree node. + * @param node Pointer to the node. + * @return The previous node. + */ +rbtree_node_t* +rbtree_node_prev(const rbtree_node_t *node); + +/** + * @brief Removes a node from an rbtree. + * @param tree Pointer to the tree. + * @param node Pointer to the node. + * @param destructor Destructor to use when removing the node. + * @return The removed node. + */ +rbtree_node_t* +rbtree_remove(rbtree_t *tree, + rbtree_node_t *node, + rbtree_node_destructor_t destructor); + +/** + * @brief Clears an rbtree. + * @param tree Pointer to the tree. + * @param destructor Destructor to use when clearing the tree's nodes. + */ +void +rbtree_clear(rbtree_t *tree, + rbtree_node_destructor_t destructor); + +#ifdef __cplusplus +} +#endif diff --git a/libctru/include/3ds/util/utf.h b/libctru/include/3ds/util/utf.h new file mode 100644 index 0000000000..c4046660f2 --- /dev/null +++ b/libctru/include/3ds/util/utf.h @@ -0,0 +1,155 @@ +/** + * @file utf.h + * @brief UTF conversion functions. + */ +#pragma once + +#include +#include + +/** Convert a UTF-8 sequence into a UTF-32 codepoint + * + * @param[out] out Output codepoint + * @param[in] in Input sequence + * + * @returns number of input code units consumed + * @returns -1 for error + */ +ssize_t decode_utf8 (uint32_t *out, const uint8_t *in); + +/** Convert a UTF-16 sequence into a UTF-32 codepoint + * + * @param[out] out Output codepoint + * @param[in] in Input sequence + * + * @returns number of input code units consumed + * @returns -1 for error + */ +ssize_t decode_utf16(uint32_t *out, const uint16_t *in); + +/** Convert a UTF-32 codepoint into a UTF-8 sequence + * + * @param[out] out Output sequence + * @param[in] in Input codepoint + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out must be able to store 4 code units + */ +ssize_t encode_utf8 (uint8_t *out, uint32_t in); + +/** Convert a UTF-32 codepoint into a UTF-16 sequence + * + * @param[out] out Output sequence + * @param[in] in Input codepoint + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out must be able to store 2 code units + */ +ssize_t encode_utf16(uint16_t *out, uint32_t in); + +/** Convert a UTF-8 sequence into a UTF-16 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf8_to_utf16(uint16_t *out, const uint8_t *in, size_t len); + +/** Convert a UTF-8 sequence into a UTF-32 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf8_to_utf32(uint32_t *out, const uint8_t *in, size_t len); + +/** Convert a UTF-16 sequence into a UTF-8 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf16_to_utf8(uint8_t *out, const uint16_t *in, size_t len); + +/** Convert a UTF-16 sequence into a UTF-32 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf16_to_utf32(uint32_t *out, const uint16_t *in, size_t len); + +/** Convert a UTF-32 sequence into a UTF-8 sequence + * + * Fills the output buffer up to \a len code units. + * Returns the number of code units that the input would produce; + * if it returns greater than \a len, the output has been + * truncated. + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf32_to_utf8(uint8_t *out, const uint32_t *in, size_t len); + +/** Convert a UTF-32 sequence into a UTF-16 sequence + * + * @param[out] out Output sequence + * @param[in] in Input sequence (null-terminated) + * @param[in] len Output length + * + * @returns number of output code units produced + * @returns -1 for error + * + * @note \a out is not null-terminated + */ +ssize_t utf32_to_utf16(uint16_t *out, const uint32_t *in, size_t len); diff --git a/libctru/include/arpa/inet.h b/libctru/include/arpa/inet.h new file mode 100644 index 0000000000..5c27b1e047 --- /dev/null +++ b/libctru/include/arpa/inet.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +static inline uint32_t htonl(uint32_t hostlong) +{ + return __builtin_bswap32(hostlong); +} + +static inline uint16_t htons(uint16_t hostshort) +{ + return __builtin_bswap16(hostshort); +} + +static inline uint32_t ntohl(uint32_t netlong) +{ + return __builtin_bswap32(netlong); +} + +static inline uint16_t ntohs(uint16_t netshort) +{ + return __builtin_bswap16(netshort); +} + +#ifdef __cplusplus +extern "C" { +#endif + + in_addr_t inet_addr(const char *cp); + int inet_aton(const char *cp, struct in_addr *inp); + char* inet_ntoa(struct in_addr in); + + const char *inet_ntop(int af, const void * src, char * dst, socklen_t size); + int inet_pton(int af, const char * src, void * dst); + +#ifdef __cplusplus +} +#endif diff --git a/libctru/include/c3d/attribs.h b/libctru/include/c3d/attribs.h new file mode 100644 index 0000000000..71d404d684 --- /dev/null +++ b/libctru/include/c3d/attribs.h @@ -0,0 +1,16 @@ +#pragma once +#include "types.h" + +typedef struct +{ + u32 flags[2]; + u64 permutation; + int attrCount; +} C3D_AttrInfo; + +void AttrInfo_Init(C3D_AttrInfo* info); +int AttrInfo_AddLoader(C3D_AttrInfo* info, int regId, GPU_FORMATS format, int count); +int AttrInfo_AddFixed(C3D_AttrInfo* info, int regId); + +C3D_AttrInfo* C3D_GetAttrInfo(void); +void C3D_SetAttrInfo(C3D_AttrInfo* info); diff --git a/libctru/include/c3d/base.h b/libctru/include/c3d/base.h new file mode 100644 index 0000000000..97796c6b86 --- /dev/null +++ b/libctru/include/c3d/base.h @@ -0,0 +1,46 @@ +#pragma once +#include "buffers.h" +#include "maths.h" + +#define C3D_DEFAULT_CMDBUF_SIZE 0x40000 + +enum +{ + C3D_UNSIGNED_BYTE = 0, + C3D_UNSIGNED_SHORT = 1, +}; + +bool C3D_Init(size_t cmdBufSize); +void C3D_Fini(void); + +float C3D_GetCmdBufUsage(void); + +void C3D_BindProgram(shaderProgram_s* program); + +void C3D_SetViewport(u32 x, u32 y, u32 w, u32 h); +void C3D_SetScissor(GPU_SCISSORMODE mode, u32 left, u32 top, u32 right, u32 bottom); + +void C3D_DrawArrays(GPU_Primitive_t primitive, int first, int size); +void C3D_DrawElements(GPU_Primitive_t primitive, int count, int type, const void* indices); + +// Immediate-mode vertex submission +void C3D_ImmDrawBegin(GPU_Primitive_t primitive); +void C3D_ImmSendAttrib(float x, float y, float z, float w); +void C3D_ImmDrawEnd(void); + +static inline void C3D_ImmDrawRestartPrim(void) +{ + GPUCMD_AddWrite(GPUREG_RESTART_PRIMITIVE, 1); +} + +// Fixed vertex attributes +C3D_FVec* C3D_FixedAttribGetWritePtr(int id); + +static inline void C3D_FixedAttribSet(int id, float x, float y, float z, float w) +{ + C3D_FVec* ptr = C3D_FixedAttribGetWritePtr(id); + ptr->x = x; + ptr->y = y; + ptr->z = z; + ptr->w = w; +} diff --git a/libctru/include/c3d/buffers.h b/libctru/include/c3d/buffers.h new file mode 100644 index 0000000000..fe49dcbf5c --- /dev/null +++ b/libctru/include/c3d/buffers.h @@ -0,0 +1,21 @@ +#pragma once +#include "types.h" + +typedef struct +{ + u32 offset; + u32 flags[2]; +} C3D_BufCfg; + +typedef struct +{ + u32 base_paddr; + int bufCount; + C3D_BufCfg buffers[12]; +} C3D_BufInfo; + +void BufInfo_Init(C3D_BufInfo* info); +int BufInfo_Add(C3D_BufInfo* info, const void* data, ptrdiff_t stride, int attribCount, u64 permutation); + +C3D_BufInfo* C3D_GetBufInfo(void); +void C3D_SetBufInfo(C3D_BufInfo* info); diff --git a/libctru/include/c3d/effect.h b/libctru/include/c3d/effect.h new file mode 100644 index 0000000000..26e36173b6 --- /dev/null +++ b/libctru/include/c3d/effect.h @@ -0,0 +1,15 @@ +#pragma once +#include "types.h" + +void C3D_DepthMap(bool bIsZBuffer, float zScale, float zOffset); +void C3D_CullFace(GPU_CULLMODE mode); +void C3D_StencilTest(bool enable, GPU_TESTFUNC function, int ref, int inputMask, int writeMask); +void C3D_StencilOp(GPU_STENCILOP sfail, GPU_STENCILOP dfail, GPU_STENCILOP pass); +void C3D_BlendingColor(u32 color); +void C3D_EarlyDepthTest(bool enable, GPU_EARLYDEPTHFUNC function, u32 ref); +void C3D_DepthTest(bool enable, GPU_TESTFUNC function, GPU_WRITEMASK writemask); +void C3D_AlphaTest(bool enable, GPU_TESTFUNC function, int ref); +void C3D_AlphaBlend(GPU_BLENDEQUATION colorEq, GPU_BLENDEQUATION alphaEq, GPU_BLENDFACTOR srcClr, GPU_BLENDFACTOR dstClr, GPU_BLENDFACTOR srcAlpha, GPU_BLENDFACTOR dstAlpha); +void C3D_ColorLogicOp(GPU_LOGICOP op); +void C3D_FragOpMode(GPU_FRAGOPMODE mode); +void C3D_FragOpShadow(float scale, float bias); diff --git a/libctru/include/c3d/fog.h b/libctru/include/c3d/fog.h new file mode 100644 index 0000000000..8541af2711 --- /dev/null +++ b/libctru/include/c3d/fog.h @@ -0,0 +1,39 @@ +#pragma once +#include "types.h" +#include + +typedef struct +{ + u32 data[128]; +} C3D_FogLut; + +typedef struct +{ + u32 diff[8]; + u32 color[8]; +} C3D_GasLut; + +static inline float FogLut_CalcZ(float depth, float near, float far) +{ + return far*near/(depth*(far-near)+near); +} + +void FogLut_FromArray(C3D_FogLut* lut, const float data[256]); +void FogLut_Exp(C3D_FogLut* lut, float density, float gradient, float near, float far); + +void C3D_FogGasMode(GPU_FOGMODE fogMode, GPU_GASMODE gasMode, bool zFlip); +void C3D_FogColor(u32 color); +void C3D_FogLutBind(C3D_FogLut* lut); + +void GasLut_FromArray(C3D_GasLut* lut, const u32 data[9]); + +void C3D_GasBeginAcc(void); +void C3D_GasDeltaZ(float value); + +void C3D_GasAccMax(float value); +void C3D_GasAttn(float value); +void C3D_GasLightPlanar(float min, float max, float attn); +void C3D_GasLightView(float min, float max, float attn); +void C3D_GasLightDirection(float dotp); +void C3D_GasLutInput(GPU_GASLUTINPUT input); +void C3D_GasLutBind(C3D_GasLut* lut); diff --git a/libctru/include/c3d/framebuffer.h b/libctru/include/c3d/framebuffer.h new file mode 100644 index 0000000000..af5c9b9fdb --- /dev/null +++ b/libctru/include/c3d/framebuffer.h @@ -0,0 +1,69 @@ +#pragma once +#include "texture.h" + +typedef struct +{ + void* colorBuf; + void* depthBuf; + u16 width; + u16 height; + GPU_COLORBUF colorFmt; + GPU_DEPTHBUF depthFmt; + bool block32; + u8 colorMask : 4; + u8 depthMask : 4; +} C3D_FrameBuf; + +// Flags for C3D_FrameBufClear +typedef enum +{ + C3D_CLEAR_COLOR = BIT(0), + C3D_CLEAR_DEPTH = BIT(1), + C3D_CLEAR_ALL = C3D_CLEAR_COLOR | C3D_CLEAR_DEPTH, +} C3D_ClearBits; + +u32 C3D_CalcColorBufSize(u32 width, u32 height, GPU_COLORBUF fmt); +u32 C3D_CalcDepthBufSize(u32 width, u32 height, GPU_DEPTHBUF fmt); + +C3D_FrameBuf* C3D_GetFrameBuf(void); +void C3D_SetFrameBuf(C3D_FrameBuf* fb); +void C3D_FrameBufTex(C3D_FrameBuf* fb, C3D_Tex* tex, GPU_TEXFACE face, int level); +void C3D_FrameBufClear(C3D_FrameBuf* fb, C3D_ClearBits clearBits, u32 clearColor, u32 clearDepth); +void C3D_FrameBufTransfer(C3D_FrameBuf* fb, gfxScreen_t screen, gfx3dSide_t side, u32 transferFlags); + +static inline void C3D_FrameBufAttrib(C3D_FrameBuf* fb, u16 width, u16 height, bool block32) +{ + fb->width = width; + fb->height = height; + fb->block32 = block32; +} + +static inline void C3D_FrameBufColor(C3D_FrameBuf* fb, void* buf, GPU_COLORBUF fmt) +{ + if (buf) + { + fb->colorBuf = buf; + fb->colorFmt = fmt; + fb->colorMask = 0xF; + } else + { + fb->colorBuf = NULL; + fb->colorFmt = GPU_RB_RGBA8; + fb->colorMask = 0; + } +} + +static inline void C3D_FrameBufDepth(C3D_FrameBuf* fb, void* buf, GPU_DEPTHBUF fmt) +{ + if (buf) + { + fb->depthBuf = buf; + fb->depthFmt = fmt; + fb->depthMask = fmt == GPU_RB_DEPTH24_STENCIL8 ? 0x3 : 0x2; + } else + { + fb->depthBuf = NULL; + fb->depthFmt = GPU_RB_DEPTH24; + fb->depthMask = 0; + } +} diff --git a/libctru/include/c3d/light.h b/libctru/include/c3d/light.h new file mode 100644 index 0000000000..3a477a1e4d --- /dev/null +++ b/libctru/include/c3d/light.h @@ -0,0 +1,149 @@ +#pragma once +#include "lightlut.h" +#include "maths.h" + +//----------------------------------------------------------------------------- +// Material +//----------------------------------------------------------------------------- + +typedef struct +{ + float ambient[3]; + float diffuse[3]; + float specular0[3]; + float specular1[3]; + float emission[3]; +} C3D_Material; + +//----------------------------------------------------------------------------- +// Light environment +//----------------------------------------------------------------------------- + +// Forward declarations +typedef struct C3D_Light_t C3D_Light; +typedef struct C3D_LightEnv_t C3D_LightEnv; + +typedef struct +{ + u32 abs, select, scale; +} C3D_LightLutInputConf; + +typedef struct +{ + u32 ambient; + u32 numLights; + u32 config[2]; + C3D_LightLutInputConf lutInput; + u32 permutation; +} C3D_LightEnvConf; + +enum +{ + C3DF_LightEnv_Dirty = BIT(0), + C3DF_LightEnv_MtlDirty = BIT(1), + C3DF_LightEnv_LCDirty = BIT(2), + +#define C3DF_LightEnv_IsCP(n) BIT(18+(n)) +#define C3DF_LightEnv_IsCP_Any (0xFF<<18) +#define C3DF_LightEnv_LutDirty(n) BIT(26+(n)) +#define C3DF_LightEnv_LutDirtyAll (0x3F<<26) +}; + +struct C3D_LightEnv_t +{ + u32 flags; + C3D_LightLut* luts[6]; + float ambient[3]; + C3D_Light* lights[8]; + C3D_LightEnvConf conf; + C3D_Material material; +}; + +void C3D_LightEnvInit(C3D_LightEnv* env); +void C3D_LightEnvBind(C3D_LightEnv* env); + +void C3D_LightEnvMaterial(C3D_LightEnv* env, const C3D_Material* mtl); +void C3D_LightEnvAmbient(C3D_LightEnv* env, float r, float g, float b); +void C3D_LightEnvLut(C3D_LightEnv* env, GPU_LIGHTLUTID lutId, GPU_LIGHTLUTINPUT input, bool negative, C3D_LightLut* lut); + +enum +{ + GPU_SHADOW_PRIMARY = BIT(16), + GPU_SHADOW_SECONDARY = BIT(17), + GPU_INVERT_SHADOW = BIT(18), + GPU_SHADOW_ALPHA = BIT(19), +}; + +void C3D_LightEnvFresnel(C3D_LightEnv* env, GPU_FRESNELSEL selector); +void C3D_LightEnvBumpMode(C3D_LightEnv* env, GPU_BUMPMODE mode); +void C3D_LightEnvBumpSel(C3D_LightEnv* env, int texUnit); +void C3D_LightEnvShadowMode(C3D_LightEnv* env, u32 mode); +void C3D_LightEnvShadowSel(C3D_LightEnv* env, int texUnit); +void C3D_LightEnvClampHighlights(C3D_LightEnv* env, bool clamp); + +//----------------------------------------------------------------------------- +// Light +//----------------------------------------------------------------------------- + +typedef struct +{ + u32 specular0, specular1, diffuse, ambient; +} C3D_LightMatConf; + +typedef struct +{ + C3D_LightMatConf material; + u16 position[3]; u16 padding0; + u16 spotDir[3]; u16 padding1; + u32 padding2; + u32 config; + u32 distAttnBias, distAttnScale; +} C3D_LightConf; + +enum +{ + C3DF_Light_Enabled = BIT(0), + C3DF_Light_Dirty = BIT(1), + C3DF_Light_MatDirty = BIT(2), + //C3DF_Light_Shadow = BIT(3), + //C3DF_Light_Spot = BIT(4), + //C3DF_Light_DistAttn = BIT(5), + + C3DF_Light_SPDirty = BIT(14), + C3DF_Light_DADirty = BIT(15), +}; + +struct C3D_Light_t +{ + u16 flags, id; + C3D_LightEnv* parent; + C3D_LightLut *lut_SP, *lut_DA; + float ambient[3]; + float diffuse[3]; + float specular0[3]; + float specular1[3]; + C3D_LightConf conf; +}; + +int C3D_LightInit(C3D_Light* light, C3D_LightEnv* env); +void C3D_LightEnable(C3D_Light* light, bool enable); +void C3D_LightTwoSideDiffuse(C3D_Light* light, bool enable); +void C3D_LightGeoFactor(C3D_Light* light, int id, bool enable); +void C3D_LightAmbient(C3D_Light* light, float r, float g, float b); +void C3D_LightDiffuse(C3D_Light* light, float r, float g, float b); +void C3D_LightSpecular0(C3D_Light* light, float r, float g, float b); +void C3D_LightSpecular1(C3D_Light* light, float r, float g, float b); +void C3D_LightPosition(C3D_Light* light, C3D_FVec* pos); +void C3D_LightShadowEnable(C3D_Light* light, bool enable); +void C3D_LightSpotEnable(C3D_Light* light, bool enable); +void C3D_LightSpotDir(C3D_Light* light, float x, float y, float z); +void C3D_LightSpotLut(C3D_Light* light, C3D_LightLut* lut); +void C3D_LightDistAttnEnable(C3D_Light* light, bool enable); +void C3D_LightDistAttn(C3D_Light* light, C3D_LightLutDA* lut); + +static inline void C3D_LightColor(C3D_Light* light, float r, float g, float b) +{ + C3D_LightDiffuse(light, r, g, b); + C3D_LightSpecular0(light, r, g, b); + C3D_LightSpecular1(light, r, g, b); +} diff --git a/libctru/include/c3d/lightlut.h b/libctru/include/c3d/lightlut.h new file mode 100644 index 0000000000..5905ba95ce --- /dev/null +++ b/libctru/include/c3d/lightlut.h @@ -0,0 +1,35 @@ +#pragma once +#include "types.h" +#include + +typedef struct +{ + u32 data[256]; +} C3D_LightLut; + +typedef struct +{ + C3D_LightLut lut; + float bias, scale; +} C3D_LightLutDA; + +typedef float (* C3D_LightLutFunc)(float x, float param); +typedef float (* C3D_LightLutFuncDA)(float dist, float arg0, float arg1); + +static inline float quadratic_dist_attn(float dist, float linear, float quad) +{ + return 1.0f / (1.0f + linear*dist + quad*dist*dist); +} + +static inline float spot_step(float angle, float cutoff) +{ + return angle >= cutoff ? 1.0f : 0.0f; +} + +void LightLut_FromArray(C3D_LightLut* lut, float* data); +void LightLut_FromFunc(C3D_LightLut* lut, C3D_LightLutFunc func, float param, bool negative); +void LightLutDA_Create(C3D_LightLutDA* lut, C3D_LightLutFuncDA func, float from, float to, float arg0, float arg1); + +#define LightLut_Phong(lut, shininess) LightLut_FromFunc((lut), powf, (shininess), false) +#define LightLut_Spotlight(lut, angle) LightLut_FromFunc((lut), spot_step, cosf(angle), true) +#define LightLutDA_Quadratic(lut, from, to, linear, quad) LightLutDA_Create((lut), quadratic_dist_attn, (from), (to), (linear), (quad)) diff --git a/libctru/include/c3d/maths.h b/libctru/include/c3d/maths.h new file mode 100644 index 0000000000..f1b4f865b3 --- /dev/null +++ b/libctru/include/c3d/maths.h @@ -0,0 +1,791 @@ +#pragma once +#include "types.h" +#include +#include + +/** + * @addtogroup math_support + * @brief Implementations of matrix, vector, and quaternion operations. + * @{ + */ + +/** + * The one true circumference-to-radius ratio. + * See http://tauday.com/tau-manifesto + */ +#define M_TAU (2*M_PI) + +/** + * @brief Convert an angle from revolutions to radians + * @param[in] _angle Proportion of a full revolution + * @return Angle in radians + */ +#define C3D_Angle(_angle) ((_angle)*M_TAU) + +/** + * @brief Convert an angle from degrees to radians + * @param[in] _angle Angle in degrees + * @return Angle in radians + */ +#define C3D_AngleFromDegrees(_angle) ((_angle)*M_TAU/360.0f) + +#define C3D_AspectRatioTop (400.0f / 240.0f) ///< Aspect ratio for 3DS top screen +#define C3D_AspectRatioBot (320.0f / 240.0f) ///< Aspect ratio for 3DS bottom screen + +/** + * @name Vector Math + * @{ + */ + +/** + * @brief Create a new FVec4 + * @param[in] x X-component + * @param[in] y Y-component + * @param[in] z Z-component + * @param[in] w W-component + * @return New FVec4 + */ +static inline C3D_FVec FVec4_New(float x, float y, float z, float w) +{ + return (C3D_FVec){{ w, z, y, x }}; +} + +/** + * @brief Add two FVec4s + * @param[in] lhs Augend + * @param[in] rhs Addend + * @return lhs+rhs (sum) + */ +static inline C3D_FVec FVec4_Add(C3D_FVec lhs, C3D_FVec rhs) +{ + // component-wise addition + return FVec4_New(lhs.x+rhs.x, lhs.y+rhs.y, lhs.z+rhs.z, lhs.w+rhs.w); +} + +/** + * @brief Subtract two FVec4s + * @param[in] lhs Minuend + * @param[in] rhs Subtrahend + * @return lhs-rhs (difference) + */ +static inline C3D_FVec FVec4_Subtract(C3D_FVec lhs, C3D_FVec rhs) +{ + // component-wise subtraction + return FVec4_New(lhs.x-rhs.x, lhs.y-rhs.y, lhs.z-rhs.z, lhs.w-rhs.w); +} + +/** + * @brief Negate a FVec4 + * @note This is equivalent to `FVec4_Scale(v, -1)` + * @param[in] v Vector to negate + * @return -v + */ +static inline C3D_FVec FVec4_Negate(C3D_FVec v) +{ + // component-wise negation + return FVec4_New(-v.x, -v.y, -v.z, -v.w); +} + +/** + * @brief Scale a FVec4 + * @param[in] v Vector to scale + * @param[in] s Scale factor + * @return v*s + */ +static inline C3D_FVec FVec4_Scale(C3D_FVec v, float s) +{ + // component-wise scaling + return FVec4_New(v.x*s, v.y*s, v.z*s, v.w*s); +} + +/** + * @brief Perspective divide + * @param[in] v Vector to divide + * @return v/v.w + */ +static inline C3D_FVec FVec4_PerspDivide(C3D_FVec v) +{ + // divide by w + return FVec4_New(v.x/v.w, v.y/v.w, v.z/v.w, 1.0f); +} + +/** + * @brief Dot product of two FVec4s + * @param[in] lhs Left-side FVec4 + * @param[in] rhs Right-side FVec4 + * @return lhs∙rhs + */ +static inline float FVec4_Dot(C3D_FVec lhs, C3D_FVec rhs) +{ + // A∙B = sum of component-wise products + return lhs.x*rhs.x + lhs.y*rhs.y + lhs.z*rhs.z + lhs.w*rhs.w; +} + +/** + * @brief Magnitude of a FVec4 + * @param[in] v Vector + * @return ‖v‖ + */ +static inline float FVec4_Magnitude(C3D_FVec v) +{ + // ‖v‖ = √(v∙v) + return sqrtf(FVec4_Dot(v,v)); +} + +/** + * @brief Normalize a FVec4 + * @param[in] v FVec4 to normalize + * @return v/‖v‖ + */ +static inline C3D_FVec FVec4_Normalize(C3D_FVec v) +{ + // get vector magnitude + float m = FVec4_Magnitude(v); + + // scale by inverse magnitude to get a unit vector + return FVec4_New(v.x/m, v.y/m, v.z/m, v.w/m); +} + +/** + * @brief Create a new FVec3 + * @param[in] x X-component + * @param[in] y Y-component + * @param[in] z Z-component + * @return New FVec3 + */ +static inline C3D_FVec FVec3_New(float x, float y, float z) +{ + return FVec4_New(x, y, z, 0.0f); +} + +/** + * @brief Dot product of two FVec3s + * @param[in] lhs Left-side FVec3 + * @param[in] rhs Right-side FVec3 + * @return lhs∙rhs + */ +static inline float FVec3_Dot(C3D_FVec lhs, C3D_FVec rhs) +{ + // A∙B = sum of component-wise products + return lhs.x*rhs.x + lhs.y*rhs.y + lhs.z*rhs.z; +} + +/** + * @brief Magnitude of a FVec3 + * @param[in] v Vector + * @return ‖v‖ + */ +static inline float FVec3_Magnitude(C3D_FVec v) +{ + // ‖v‖ = √(v∙v) + return sqrtf(FVec3_Dot(v,v)); +} + +/** + * @brief Normalize a FVec3 + * @param[in] v FVec3 to normalize + * @return v/‖v‖ + */ +static inline C3D_FVec FVec3_Normalize(C3D_FVec v) +{ + // get vector magnitude + float m = FVec3_Magnitude(v); + + // scale by inverse magnitude to get a unit vector + return FVec3_New(v.x/m, v.y/m, v.z/m); +} + +/** + * @brief Add two FVec3s + * @param[in] lhs Augend + * @param[in] rhs Addend + * @return lhs+rhs (sum) + */ +static inline C3D_FVec FVec3_Add(C3D_FVec lhs, C3D_FVec rhs) +{ + // component-wise addition + return FVec3_New(lhs.x+rhs.x, lhs.y+rhs.y, lhs.z+rhs.z); +} + +/** + * @brief Subtract two FVec3s + * @param[in] lhs Minuend + * @param[in] rhs Subtrahend + * @return lhs-rhs (difference) + */ +static inline C3D_FVec FVec3_Subtract(C3D_FVec lhs, C3D_FVec rhs) +{ + // component-wise subtraction + return FVec3_New(lhs.x-rhs.x, lhs.y-rhs.y, lhs.z-rhs.z); +} + +/** + * @brief Distance between two 3D points + * @param[in] lhs Relative origin + * @param[in] rhs Relative point of interest + * @return ‖lhs-rhs‖ + */ +static inline float FVec3_Distance(C3D_FVec lhs, C3D_FVec rhs) +{ + // distance = ‖lhs-rhs‖ + return FVec3_Magnitude(FVec3_Subtract(lhs, rhs)); +} + +/** + * @brief Scale a FVec3 + * @param[in] v Vector to scale + * @param[in] s Scale factor + * @return v*s + */ +static inline C3D_FVec FVec3_Scale(C3D_FVec v, float s) +{ + // component-wise scaling + return FVec3_New(v.x*s, v.y*s, v.z*s); +} + +/** + * @brief Negate a FVec3 + * @note This is equivalent to `FVec3_Scale(v, -1)` + * @param[in] v Vector to negate + * @return -v + */ +static inline C3D_FVec FVec3_Negate(C3D_FVec v) +{ + // component-wise negation + return FVec3_New(-v.x, -v.y, -v.z); +} + +/** + * @brief Cross product of two FVec3s + * @note This returns a pseudo-vector which is perpendicular to the plane + * spanned by the two input vectors. + * @param[in] lhs Left-side FVec3 + * @param[in] rhs Right-side FVec3 + * @return lhs×rhs + */ +static inline C3D_FVec FVec3_Cross(C3D_FVec lhs, C3D_FVec rhs) +{ + // A×B = (AyBz - AzBy, AzBx - AxBz, AxBy - AyBx) + return FVec3_New(lhs.y*rhs.z - lhs.z*rhs.y, lhs.z*rhs.x - lhs.x*rhs.z, lhs.x*rhs.y - lhs.y*rhs.x); +} +/** @} */ + +/** + * @name Matrix Math + * @note All matrices are 4x4 unless otherwise noted. + * @{ + */ + +/** + * @brief Zero matrix + * @param[out] out Matrix to zero + */ +static inline void Mtx_Zeros(C3D_Mtx* out) +{ + memset(out, 0, sizeof(*out)); +} + +/** + * @brief Copy a matrix + * @param[out] out Output matrix + * @param[in] in Input matrix + */ +static inline void Mtx_Copy(C3D_Mtx* out, const C3D_Mtx* in) +{ + *out = *in; +} + +/** + * @brief Creates a matrix with the diagonal using the given parameters. + * @param[out] out Output matrix. + * @param[in] x The X component. + * @param[in] y The Y component. + * @param[in] z The Z component. + * @param[in] w The W component. + */ +static inline void Mtx_Diagonal(C3D_Mtx* out, float x, float y, float z, float w) +{ + Mtx_Zeros(out); + out->r[0].x = x; + out->r[1].y = y; + out->r[2].z = z; + out->r[3].w = w; +} + +/** + * @brief Identity matrix + * @param[out] out Matrix to fill + */ +static inline void Mtx_Identity(C3D_Mtx* out) +{ + Mtx_Diagonal(out, 1.0f, 1.0f, 1.0f, 1.0f); +} + +/** + *@brief Transposes the matrix. Row => Column, and vice versa. + *@param[in,out] out Output matrix. + */ +void Mtx_Transpose(C3D_Mtx* out); + +/** + * @brief Matrix addition + * @param[out] out Output matrix. + * @param[in] lhs Left matrix. + * @param[in] rhs Right matrix. + * @return lhs+rhs (sum) + */ +static inline void Mtx_Add(C3D_Mtx* out, const C3D_Mtx* lhs, const C3D_Mtx* rhs) +{ + for (int i = 0; i < 16; i++) + out->m[i] = lhs->m[i] + rhs->m[i]; +} + +/** + * @brief Matrix subtraction + * @param[out] out Output matrix. + * @param[in] lhs Left matrix. + * @param[in] rhs Right matrix. + * @return lhs-rhs (difference) + */ +static inline void Mtx_Subtract(C3D_Mtx* out, const C3D_Mtx* lhs, const C3D_Mtx* rhs) +{ + for (int i = 0; i < 16; i++) + out->m[i] = lhs->m[i] - rhs->m[i]; +} + +/** + * @brief Multiply two matrices + * @param[out] out Output matrix + * @param[in] a Multiplicand + * @param[in] b Multiplier + */ +void Mtx_Multiply(C3D_Mtx* out, const C3D_Mtx* a, const C3D_Mtx* b); + +/** + * @brief Inverse a matrix + * @param[in,out] out Matrix to inverse + * @retval 0.0f Degenerate matrix (no inverse) + * @return determinant + */ +float Mtx_Inverse(C3D_Mtx* out); + +/** + * @brief Multiply 3x3 matrix by a FVec3 + * @param[in] mtx Matrix + * @param[in] v Vector + * @return mtx*v (product) + */ +C3D_FVec Mtx_MultiplyFVec3(const C3D_Mtx* mtx, C3D_FVec v); + +/** + * @brief Multiply 4x4 matrix by a FVec4 + * @param[in] mtx Matrix + * @param[in] v Vector + * @return mtx*v (product) + */ +C3D_FVec Mtx_MultiplyFVec4(const C3D_Mtx* mtx, C3D_FVec v); + +/** + * @brief Multiply 4x3 matrix by a FVec3 + * @param[in] mtx Matrix + * @param[in] v Vector + * @return mtx*v (product) + */ +static inline C3D_FVec Mtx_MultiplyFVecH(const C3D_Mtx* mtx, C3D_FVec v) +{ + v.w = 1.0f; + + return Mtx_MultiplyFVec4(mtx, v); +} + +/** + * @brief Get 4x4 matrix equivalent to Quaternion + * @param[out] m Output matrix + * @param[in] q Input Quaternion + */ +void Mtx_FromQuat(C3D_Mtx* m, C3D_FQuat q); +/** @} */ + +/** + * @name 3D Transformation Matrix Math + * @note bRightSide is used to determine which side to perform the transformation. + * With an input matrix A and a transformation matrix B, bRightSide being + * true yields AB, while being false yield BA. + * @{ + */ + +/** + * @brief 3D translation + * @param[in,out] mtx Matrix to translate + * @param[in] x X component to translate + * @param[in] y Y component to translate + * @param[in] z Z component to translate + * @param[in] bRightSide Whether to transform from the right side + */ +void Mtx_Translate(C3D_Mtx* mtx, float x, float y, float z, bool bRightSide); + +/** + * @brief 3D Scale + * @param[in,out] mtx Matrix to scale + * @param[in] x X component to scale + * @param[in] y Y component to scale + * @param[in] z Z component to scale + */ +void Mtx_Scale(C3D_Mtx* mtx, float x, float y, float z); + +/** + * @brief 3D Rotation + * @param[in,out] mtx Matrix to rotate + * @param[in] axis Axis about which to rotate + * @param[in] angle Radians to rotate + * @param[in] bRightSide Whether to transform from the right side + */ +void Mtx_Rotate(C3D_Mtx* mtx, C3D_FVec axis, float angle, bool bRightSide); + +/** + * @brief 3D Rotation about the X axis + * @param[in,out] mtx Matrix to rotate + * @param[in] angle Radians to rotate + * @param[in] bRightSide Whether to transform from the right side + */ +void Mtx_RotateX(C3D_Mtx* mtx, float angle, bool bRightSide); + +/** + * @brief 3D Rotation about the Y axis + * @param[in,out] mtx Matrix to rotate + * @param[in] angle Radians to rotate + * @param[in] bRightSide Whether to transform from the right side + */ +void Mtx_RotateY(C3D_Mtx* mtx, float angle, bool bRightSide); + +/** + * @brief 3D Rotation about the Z axis + * @param[in,out] mtx Matrix to rotate + * @param[in] angle Radians to rotate + * @param[in] bRightSide Whether to transform from the right side + */ +void Mtx_RotateZ(C3D_Mtx* mtx, float angle, bool bRightSide); +/** @} */ + +/** + * @name 3D Projection Matrix Math + * @{ + */ + +/** + * @brief Orthogonal projection + * @param[out] mtx Output matrix + * @param[in] left Left clip plane (X=left) + * @param[in] right Right clip plane (X=right) + * @param[in] bottom Bottom clip plane (Y=bottom) + * @param[in] top Top clip plane (Y=top) + * @param[in] near Near clip plane (Z=near) + * @param[in] far Far clip plane (Z=far) + * @param[in] isLeftHanded Whether to build a LH projection + * @sa Mtx_OrthoTilt + */ +void Mtx_Ortho(C3D_Mtx* mtx, float left, float right, float bottom, float top, float near, float far, bool isLeftHanded); + +/** + * @brief Perspective projection + * @param[out] mtx Output matrix + * @param[in] fovy Vertical field of view in radians + * @param[in] aspect Aspect ration of projection plane (width/height) + * @param[in] near Near clip plane (Z=near) + * @param[in] far Far clip plane (Z=far) + * @param[in] isLeftHanded Whether to build a LH projection + * @sa Mtx_PerspTilt + * @sa Mtx_PerspStereo + * @sa Mtx_PerspStereoTilt + */ +void Mtx_Persp(C3D_Mtx* mtx, float fovy, float aspect, float near, float far, bool isLeftHanded); + +/** + * @brief Stereo perspective projection + * @note Typically you will use iod to mean the distance between the eyes. Plug + * in -iod for the left eye and iod for the right eye. + * @note The focal length is defined by screen. If objects are further than this, + * they will appear to be inside the screen. If objects are closer than this, + * they will appear to pop out of the screen. Objects at this distance appear + * to be at the screen. + * @param[out] mtx Output matrix + * @param[in] fovy Vertical field of view in radians + * @param[in] aspect Aspect ration of projection plane (width/height) + * @param[in] near Near clip plane (Z=near) + * @param[in] far Far clip plane (Z=far) + * @param[in] iod Interocular distance + * @param[in] screen Focal length + * @param[in] isLeftHanded Whether to build a LH projection + * @sa Mtx_Persp + * @sa Mtx_PerspTilt + * @sa Mtx_PerspStereoTilt + */ +void Mtx_PerspStereo(C3D_Mtx* mtx, float fovy, float aspect, float near, float far, float iod, float screen, bool isLeftHanded); + +/** + * @brief Orthogonal projection, tilted to account for the 3DS screen rotation + * @param[out] mtx Output matrix + * @param[in] left Left clip plane (X=left) + * @param[in] right Right clip plane (X=right) + * @param[in] bottom Bottom clip plane (Y=bottom) + * @param[in] top Top clip plane (Y=top) + * @param[in] near Near clip plane (Z=near) + * @param[in] far Far clip plane (Z=far) + * @param[in] isLeftHanded Whether to build a LH projection + * @sa Mtx_Ortho + */ +void Mtx_OrthoTilt(C3D_Mtx* mtx, float left, float right, float bottom, float top, float near, float far, bool isLeftHanded); + +/** + * @brief Perspective projection, tilted to account for the 3DS screen rotation + * @param[out] mtx Output matrix + * @param[in] fovy Vertical field of view in radians + * @param[in] aspect Aspect ration of projection plane (width/height) + * @param[in] near Near clip plane (Z=near) + * @param[in] far Far clip plane (Z=far) + * @param[in] isLeftHanded Whether to build a LH projection + * @sa Mtx_Persp + * @sa Mtx_PerspStereo + * @sa Mtx_PerspStereoTilt + */ +void Mtx_PerspTilt(C3D_Mtx* mtx, float fovy, float aspect, float near, float far, bool isLeftHanded); + +/** + * @brief Stereo perspective projection, tilted to account for the 3DS screen rotation + * @note See the notes for @ref Mtx_PerspStereo + * @param[out] mtx Output matrix + * @param[in] fovy Vertical field of view in radians + * @param[in] aspect Aspect ration of projection plane (width/height) + * @param[in] near Near clip plane (Z=near) + * @param[in] far Far clip plane (Z=far) + * @param[in] iod Interocular distance + * @param[in] screen Focal length + * @param[in] isLeftHanded Whether to build a LH projection + * @sa Mtx_Persp + * @sa Mtx_PerspTilt + * @sa Mtx_PerspStereo + */ +void Mtx_PerspStereoTilt(C3D_Mtx* mtx, float fovy, float aspect, float near, float far, float iod, float screen, bool isLeftHanded); + +/** + * @brief Look-At matrix, based on DirectX implementation + * @note See https://msdn.microsoft.com/en-us/library/windows/desktop/bb205342 + * @param[out] out Output matrix. + * @param[in] cameraPosition Position of the intended camera in 3D space. + * @param[in] cameraTarget Position of the intended target the camera is supposed to face in 3D space. + * @param[in] cameraUpVector The vector that points straight up depending on the camera's "Up" direction. + * @param[in] isLeftHanded Whether to build a LH projection + */ +void Mtx_LookAt(C3D_Mtx* out, C3D_FVec cameraPosition, C3D_FVec cameraTarget, C3D_FVec cameraUpVector, bool isLeftHanded); +/** @} */ + +/** + * @name Quaternion Math + * @{ + */ + +/** + * @brief Create a new Quaternion + * @param[in] i I-component + * @param[in] j J-component + * @param[in] k K-component + * @param[in] r Real component + * @return New Quaternion + */ +#define Quat_New(i,j,k,r) FVec4_New(i,j,k,r) + +/** + * @brief Negate a Quaternion + * @note This is equivalent to `Quat_Scale(v, -1)` + * @param[in] q Quaternion to negate + * @return -q + */ +#define Quat_Negate(q) FVec4_Negate(q) + +/** + * @brief Add two Quaternions + * @param[in] lhs Augend + * @param[in] rhs Addend + * @return lhs+rhs (sum) + */ +#define Quat_Add(lhs,rhs) FVec4_Add(lhs,rhs) + +/** + * @brief Subtract two Quaternions + * @param[in] lhs Minuend + * @param[in] rhs Subtrahend + * @return lhs-rhs (difference) + */ +#define Quat_Subtract(lhs,rhs) FVec4_Subtract(lhs,rhs) + +/** + * @brief Scale a Quaternion + * @param[in] q Quaternion to scale + * @param[in] s Scale factor + * @return q*s + */ +#define Quat_Scale(q,s) FVec4_Scale(q,s) + +/** + * @brief Normalize a Quaternion + * @param[in] q Quaternion to normalize + * @return q/‖q‖ + */ +#define Quat_Normalize(q) FVec4_Normalize(q) + +/** + * @brief Dot product of two Quaternions + * @param[in] lhs Left-side Quaternion + * @param[in] rhs Right-side Quaternion + * @return lhs∙rhs + */ +#define Quat_Dot(lhs,rhs) FVec4_Dot(lhs,rhs) + +/** + * @brief Multiply two Quaternions + * @param[in] lhs Multiplicand + * @param[in] rhs Multiplier + * @return lhs*rhs + */ +C3D_FQuat Quat_Multiply(C3D_FQuat lhs, C3D_FQuat rhs); + +/** + * @brief Raise Quaternion to a power + * @note If p is 0, this returns the identity Quaternion. + * If p is 1, this returns q. + * @param[in] q Base Quaternion + * @param[in] p Power + * @return qp + */ +C3D_FQuat Quat_Pow(C3D_FQuat q, float p); + +/** + * @brief Cross product of Quaternion and FVec3 + * @param[in] q Base Quaternion + * @param[in] v Vector to cross + * @return q×v + */ +C3D_FVec Quat_CrossFVec3(C3D_FQuat q, C3D_FVec v); + +/** + * @brief 3D Rotation + * @param[in] q Quaternion to rotate + * @param[in] axis Axis about which to rotate + * @param[in] r Radians to rotate + * @param[in] bRightSide Whether to transform from the right side + * @return Rotated Quaternion + */ +C3D_FQuat Quat_Rotate(C3D_FQuat q, C3D_FVec axis, float r, bool bRightSide); + +/** + * @brief 3D Rotation about the X axis + * @param[in] q Quaternion to rotate + * @param[in] r Radians to rotate + * @param[in] bRightSide Whether to transform from the right side + * @return Rotated Quaternion + */ +C3D_FQuat Quat_RotateX(C3D_FQuat q, float r, bool bRightSide); + +/** + * @brief 3D Rotation about the Y axis + * @param[in] q Quaternion to rotate + * @param[in] r Radians to rotate + * @param[in] bRightSide Whether to transform from the right side + * @return Rotated Quaternion + */ +C3D_FQuat Quat_RotateY(C3D_FQuat q, float r, bool bRightSide); + +/** + * @brief 3D Rotation about the Z axis + * @param[in] q Quaternion to rotate + * @param[in] r Radians to rotate + * @param[in] bRightSide Whether to transform from the right side + * @return Rotated Quaternion + */ +C3D_FQuat Quat_RotateZ(C3D_FQuat q, float r, bool bRightSide); + +/** + * @brief Get Quaternion equivalent to 4x4 matrix + * @note If the matrix is orthogonal or special orthogonal, where determinant(matrix) = +1.0f, then the matrix can be converted. + * @param[in] m Input Matrix + * @return Generated Quaternion + */ +C3D_FQuat Quat_FromMtx(const C3D_Mtx* m); + +/** + * @brief Identity Quaternion + * @return Identity Quaternion + */ +static inline C3D_FQuat Quat_Identity(void) +{ + // r=1, i=j=k=0 + return Quat_New(0.0f, 0.0f, 0.0f, 1.0f); +} + +/** + * @brief Quaternion conjugate + * @param[in] q Quaternion of which to get conjugate + * @return q* + */ +static inline C3D_FQuat Quat_Conjugate(C3D_FQuat q) +{ + // q* = q.r - q.i - q.j - q.k + return Quat_New(-q.i, -q.j, -q.k, q.r); +} + +/** + * @brief Quaternion inverse + * @note This is equivalent to `Quat_Pow(v, -1)` + * @param[in] q Quaternion of which to get inverse + * @return q-1 + */ +static inline C3D_FQuat Quat_Inverse(C3D_FQuat q) +{ + // q^-1 = (q.r - q.i - q.j - q.k) / (q.r^2 + q.i^2 + q.j^2 + q.k^2) + // = q* / (q∙q) + C3D_FQuat c = Quat_Conjugate(q); + float d = Quat_Dot(q, q); + return Quat_New(c.i/d, c.j/d, c.k/d, c.r/d); +} + +/** + * @brief Cross product of FVec3 and Quaternion + * @param[in] v Base FVec3 + * @param[in] q Quaternion to cross + * @return v×q + */ +static inline C3D_FVec FVec3_CrossQuat(C3D_FVec v, C3D_FQuat q) +{ + // v×q = (q^-1)×v + return Quat_CrossFVec3(Quat_Inverse(q), v); +} + +/** + * @brief Converting Pitch, Yaw, and Roll to Quaternion equivalent + * @param[in] pitch The pitch angle in radians. + * @param[in] yaw The yaw angle in radians. + * @param[in] roll The roll angle in radians. + * @param[in] bRightSide Whether to transform from the right side + * @return C3D_FQuat The Quaternion equivalent with the pitch, yaw, and roll (in that order) orientations applied. + */ +C3D_FQuat Quat_FromPitchYawRoll(float pitch, float yaw, float roll, bool bRightSide); + +/** + * @brief Quaternion Look-At + * @param[in] source C3D_FVec Starting position. Origin of rotation. + * @param[in] target C3D_FVec Target position to orient towards. + * @param[in] forwardVector C3D_FVec The Up vector. + * @param[in] upVector C3D_FVec The Up vector. + * @return Quaternion rotation. + */ +C3D_FQuat Quat_LookAt(C3D_FVec source, C3D_FVec target, C3D_FVec forwardVector, C3D_FVec upVector); + +/** + * @brief Quaternion, created from a given axis and angle in radians. + * @param[in] axis C3D_FVec The axis to rotate around at. + * @param[in] angle float The angle to rotate. Unit: Radians + * @return Quaternion rotation based on the axis and angle. Axis doesn't have to be orthogonal. + */ +C3D_FQuat Quat_FromAxisAngle(C3D_FVec axis, float angle); +/** @} */ +/** @} */ diff --git a/libctru/include/c3d/mtxstack.h b/libctru/include/c3d/mtxstack.h new file mode 100644 index 0000000000..1de0c254b4 --- /dev/null +++ b/libctru/include/c3d/mtxstack.h @@ -0,0 +1,24 @@ +#pragma once +#include "maths.h" + +#define C3D_MTXSTACK_SIZE 8 + +typedef struct +{ + C3D_Mtx m[C3D_MTXSTACK_SIZE]; + int pos; + u8 unifType, unifPos, unifLen; + bool isDirty; +} C3D_MtxStack; + +static inline C3D_Mtx* MtxStack_Cur(C3D_MtxStack* stk) +{ + stk->isDirty = true; + return &stk->m[stk->pos]; +} + +void MtxStack_Init(C3D_MtxStack* stk); +void MtxStack_Bind(C3D_MtxStack* stk, GPU_SHADER_TYPE unifType, int unifPos, int unifLen); +C3D_Mtx* MtxStack_Push(C3D_MtxStack* stk); +C3D_Mtx* MtxStack_Pop(C3D_MtxStack* stk); +void MtxStack_Update(C3D_MtxStack* stk); diff --git a/libctru/include/c3d/proctex.h b/libctru/include/c3d/proctex.h new file mode 100644 index 0000000000..3abf7ab120 --- /dev/null +++ b/libctru/include/c3d/proctex.h @@ -0,0 +1,125 @@ +#pragma once +#include "types.h" + +typedef struct +{ + u32 color[256]; + u32 diff[256]; +} C3D_ProcTexColorLut; + +typedef struct +{ + union + { + u32 proctex0; + struct + { + u32 uClamp : 3; + u32 vClamp : 3; + u32 rgbFunc : 4; + u32 alphaFunc : 4; + bool alphaSeparate : 1; + bool enableNoise : 1; + u32 uShift : 2; + u32 vShift : 2; + u32 lodBiasLow : 8; + }; + }; + union + { + u32 proctex1; + struct + { + u16 uNoiseAmpl; + u16 uNoisePhase; + }; + }; + union + { + u32 proctex2; + struct + { + u16 vNoiseAmpl; + u16 vNoisePhase; + }; + }; + union + { + u32 proctex3; + struct + { + u16 uNoiseFreq; + u16 vNoiseFreq; + }; + }; + union + { + u32 proctex4; + struct + { + u32 minFilter : 3; + u32 unknown1 : 8; + u32 width : 8; + u32 lodBiasHigh : 8; + }; + }; + union + { + u32 proctex5; + struct + { + u32 offset : 8; + u32 unknown2 : 24; + }; + }; +} C3D_ProcTex; + +enum +{ + C3D_ProcTex_U = BIT(0), + C3D_ProcTex_V = BIT(1), + C3D_ProcTex_UV = C3D_ProcTex_U | C3D_ProcTex_V, +}; + +void C3D_ProcTexInit(C3D_ProcTex* pt, int offset, int length); +void C3D_ProcTexNoiseCoefs(C3D_ProcTex* pt, int mode, float amplitude, float frequency, float phase); +void C3D_ProcTexLodBias(C3D_ProcTex* pt, float bias); +void C3D_ProcTexBind(int texCoordId, C3D_ProcTex* pt); + +// GPU_LUT_NOISE, GPU_LUT_RGBMAP, GPU_LUT_ALPHAMAP +typedef u32 C3D_ProcTexLut[128]; +void C3D_ProcTexLutBind(GPU_PROCTEX_LUTID id, C3D_ProcTexLut* lut); +void ProcTexLut_FromArray(C3D_ProcTexLut* lut, const float in[129]); + +void C3D_ProcTexColorLutBind(C3D_ProcTexColorLut* lut); +void ProcTexColorLut_Write(C3D_ProcTexColorLut* out, const u32* in, int offset, int length); + +static inline void C3D_ProcTexClamp(C3D_ProcTex* pt, GPU_PROCTEX_CLAMP u, GPU_PROCTEX_CLAMP v) +{ + pt->uClamp = u; + pt->vClamp = v; +} + +static inline void C3D_ProcTexCombiner(C3D_ProcTex* pt, bool separate, GPU_PROCTEX_MAPFUNC rgb, GPU_PROCTEX_MAPFUNC alpha) +{ + pt->alphaSeparate = separate; + pt->rgbFunc = rgb; + if (separate) + pt->alphaFunc = alpha; +} + +static inline void C3D_ProcTexNoiseEnable(C3D_ProcTex* pt, bool enable) +{ + pt->enableNoise = enable; +} + +static inline void C3D_ProcTexShift(C3D_ProcTex* pt, GPU_PROCTEX_SHIFT u, GPU_PROCTEX_SHIFT v) +{ + pt->uShift = u; + pt->vShift = v; +} + +static inline void C3D_ProcTexFilter(C3D_ProcTex* pt, GPU_PROCTEX_FILTER min) +{ + pt->minFilter = min; +} diff --git a/libctru/include/c3d/renderqueue.h b/libctru/include/c3d/renderqueue.h new file mode 100644 index 0000000000..6eedd1ecd6 --- /dev/null +++ b/libctru/include/c3d/renderqueue.h @@ -0,0 +1,79 @@ +#pragma once +#include "framebuffer.h" + +typedef struct C3D_RenderTarget_tag C3D_RenderTarget; + +struct C3D_RenderTarget_tag +{ + C3D_RenderTarget *next, *prev; + C3D_FrameBuf frameBuf; + + bool used; + bool ownsColor, ownsDepth; + + bool linked; + gfxScreen_t screen; + gfx3dSide_t side; + u32 transferFlags; +}; + +// Flags for C3D_FrameBegin +enum +{ + C3D_FRAME_SYNCDRAW = BIT(0), // Perform C3D_FrameSync before checking the GPU status + C3D_FRAME_NONBLOCK = BIT(1), // Return false instead of waiting if the GPU is busy +}; + +float C3D_FrameRate(float fps); +void C3D_FrameSync(void); +u32 C3D_FrameCounter(int id); + +bool C3D_FrameBegin(u8 flags); +bool C3D_FrameDrawOn(C3D_RenderTarget* target); +void C3D_FrameSplit(u8 flags); +void C3D_FrameEnd(u8 flags); + +void C3D_FrameEndHook(void (* hook)(void*), void* param); + +float C3D_GetDrawingTime(void); +float C3D_GetProcessingTime(void); + +#if defined(__GNUC__) && !defined(__cplusplus) +typedef union __attribute__((__transparent_union__)) +{ + int __i; + GPU_DEPTHBUF __e; +} C3D_DEPTHTYPE; +#else +union C3D_DEPTHTYPE +{ +private: + int __i; + GPU_DEPTHBUF __e; +public: + C3D_DEPTHTYPE(GPU_DEPTHBUF e) : __e(e) {} + C3D_DEPTHTYPE(int i) : __i(-1) { (void)i; } +}; +#endif + +#define C3D_DEPTHTYPE_OK(_x) ((_x).__i >= 0) +#define C3D_DEPTHTYPE_VAL(_x) ((_x).__e) + +C3D_RenderTarget* C3D_RenderTargetCreate(int width, int height, GPU_COLORBUF colorFmt, C3D_DEPTHTYPE depthFmt); +C3D_RenderTarget* C3D_RenderTargetCreateFromTex(C3D_Tex* tex, GPU_TEXFACE face, int level, C3D_DEPTHTYPE depthFmt); +void C3D_RenderTargetDelete(C3D_RenderTarget* target); +void C3D_RenderTargetSetOutput(C3D_RenderTarget* target, gfxScreen_t screen, gfx3dSide_t side, u32 transferFlags); + +static inline void C3D_RenderTargetDetachOutput(C3D_RenderTarget* target) +{ + C3D_RenderTargetSetOutput(NULL, target->screen, target->side, 0); +} + +static inline void C3D_RenderTargetClear(C3D_RenderTarget* target, C3D_ClearBits clearBits, u32 clearColor, u32 clearDepth) +{ + C3D_FrameBufClear(&target->frameBuf, clearBits, clearColor, clearDepth); +} + +void C3D_SyncDisplayTransfer(u32* inadr, u32 indim, u32* outadr, u32 outdim, u32 flags); +void C3D_SyncTextureCopy(u32* inadr, u32 indim, u32* outadr, u32 outdim, u32 size, u32 flags); +void C3D_SyncMemoryFill(u32* buf0a, u32 buf0v, u32* buf0e, u16 control0, u32* buf1a, u32 buf1v, u32* buf1e, u16 control1); diff --git a/libctru/include/c3d/texenv.h b/libctru/include/c3d/texenv.h new file mode 100644 index 0000000000..9a42d1bdaf --- /dev/null +++ b/libctru/include/c3d/texenv.h @@ -0,0 +1,98 @@ +#pragma once +#include "types.h" + +typedef struct +{ + u16 srcRgb, srcAlpha; + union + { + u32 opAll; + struct { u32 opRgb:12, opAlpha:12; }; + }; + u16 funcRgb, funcAlpha; + u32 color; + u16 scaleRgb, scaleAlpha; +} C3D_TexEnv; + +typedef enum +{ + C3D_RGB = BIT(0), + C3D_Alpha = BIT(1), + C3D_Both = C3D_RGB | C3D_Alpha, +} C3D_TexEnvMode; + +C3D_TexEnv* C3D_GetTexEnv(int id); +void C3D_SetTexEnv(int id, C3D_TexEnv* env); +void C3D_DirtyTexEnv(C3D_TexEnv* env); + +void C3D_TexEnvBufUpdate(int mode, int mask); +void C3D_TexEnvBufColor(u32 color); + +static inline void C3D_TexEnvInit(C3D_TexEnv* env) +{ + env->srcRgb = GPU_TEVSOURCES(GPU_PREVIOUS, 0, 0); + env->srcAlpha = env->srcRgb; + env->opAll = 0; + env->funcRgb = GPU_REPLACE; + env->funcAlpha = env->funcRgb; + env->color = 0xFFFFFFFF; + env->scaleRgb = GPU_TEVSCALE_1; + env->scaleAlpha = GPU_TEVSCALE_1; +} + +#ifdef __cplusplus +#define _C3D_DEFAULT(x) = x +#else +#define _C3D_DEFAULT(x) +#endif + +static inline void C3D_TexEnvSrc(C3D_TexEnv* env, C3D_TexEnvMode mode, + GPU_TEVSRC s1, + GPU_TEVSRC s2 _C3D_DEFAULT(GPU_PRIMARY_COLOR), + GPU_TEVSRC s3 _C3D_DEFAULT(GPU_PRIMARY_COLOR)) +{ + int param = GPU_TEVSOURCES((int)s1, (int)s2, (int)s3); + if ((int)mode & C3D_RGB) + env->srcRgb = param; + if ((int)mode & C3D_Alpha) + env->srcAlpha = param; +} + +static inline void C3D_TexEnvOpRgb(C3D_TexEnv* env, + GPU_TEVOP_RGB o1, + GPU_TEVOP_RGB o2 _C3D_DEFAULT(GPU_TEVOP_RGB_SRC_COLOR), + GPU_TEVOP_RGB o3 _C3D_DEFAULT(GPU_TEVOP_RGB_SRC_COLOR)) +{ + env->opRgb = GPU_TEVOPERANDS((int)o1, (int)o2, (int)o3); +} + +static inline void C3D_TexEnvOpAlpha(C3D_TexEnv* env, + GPU_TEVOP_A o1, + GPU_TEVOP_A o2 _C3D_DEFAULT(GPU_TEVOP_A_SRC_ALPHA), + GPU_TEVOP_A o3 _C3D_DEFAULT(GPU_TEVOP_A_SRC_ALPHA)) +{ + env->opAlpha = GPU_TEVOPERANDS((int)o1, (int)o2, (int)o3); +} + +static inline void C3D_TexEnvFunc(C3D_TexEnv* env, C3D_TexEnvMode mode, GPU_COMBINEFUNC param) +{ + if ((int)mode & C3D_RGB) + env->funcRgb = param; + if ((int)mode & C3D_Alpha) + env->funcAlpha = param; +} + +static inline void C3D_TexEnvColor(C3D_TexEnv* env, u32 color) +{ + env->color = color; +} + +static inline void C3D_TexEnvScale(C3D_TexEnv* env, int mode, GPU_TEVSCALE param) +{ + if (mode & C3D_RGB) + env->scaleRgb = param; + if (mode & C3D_Alpha) + env->scaleAlpha = param; +} + +#undef _C3D_DEFAULT diff --git a/libctru/include/c3d/texture.h b/libctru/include/c3d/texture.h new file mode 100644 index 0000000000..f5d2e35625 --- /dev/null +++ b/libctru/include/c3d/texture.h @@ -0,0 +1,183 @@ +#pragma once +#include "types.h" + +typedef struct +{ + void* data[6]; +} C3D_TexCube; + +typedef struct +{ + union + { + void* data; + C3D_TexCube* cube; + }; + + GPU_TEXCOLOR fmt : 4; + size_t size : 28; + + union + { + u32 dim; + struct + { + u16 height; + u16 width; + }; + }; + + u32 param; + u32 border; + union + { + u32 lodParam; + struct + { + u16 lodBias; + u8 maxLevel; + u8 minLevel; + }; + }; +} C3D_Tex; + +typedef struct ALIGN(8) +{ + u16 width; + u16 height; + u8 maxLevel : 4; + GPU_TEXCOLOR format : 4; + GPU_TEXTURE_MODE_PARAM type : 3; + bool onVram : 1; +} C3D_TexInitParams; + +bool C3D_TexInitWithParams(C3D_Tex* tex, C3D_TexCube* cube, C3D_TexInitParams p); +void C3D_TexLoadImage(C3D_Tex* tex, const void* data, GPU_TEXFACE face, int level); +void C3D_TexGenerateMipmap(C3D_Tex* tex, GPU_TEXFACE face); +void C3D_TexBind(int unitId, C3D_Tex* tex); +void C3D_TexFlush(C3D_Tex* tex); +void C3D_TexDelete(C3D_Tex* tex); + +void C3D_TexShadowParams(bool perspective, float bias); + +static inline int C3D_TexCalcMaxLevel(u32 width, u32 height) +{ + return (31-__builtin_clz(width < height ? width : height)) - 3; // avoid sizes smaller than 8 +} + +static inline u32 C3D_TexCalcLevelSize(u32 size, int level) +{ + return size >> (2*level); +} + +static inline u32 C3D_TexCalcTotalSize(u32 size, int maxLevel) +{ + /* + S = s + sr + sr^2 + sr^3 + ... + sr^n + Sr = sr + sr^2 + sr^3 + ... + sr^(n+1) + S-Sr = s - sr^(n+1) + S(1-r) = s(1 - r^(n+1)) + S = s (1 - r^(n+1)) / (1-r) + + r = 1/4 + 1-r = 3/4 + + S = 4s (1 - (1/4)^(n+1)) / 3 + S = 4s (1 - 1/4^(n+1)) / 3 + S = (4/3) (s - s/4^(n+1)) + S = (4/3) (s - s/(1<<(2n+2))) + S = (4/3) (s - s>>(2n+2)) + */ + return (size - C3D_TexCalcLevelSize(size,maxLevel+1)) * 4 / 3; +} + +static inline bool C3D_TexInit(C3D_Tex* tex, u16 width, u16 height, GPU_TEXCOLOR format) +{ + return C3D_TexInitWithParams(tex, NULL, + (C3D_TexInitParams){ width, height, 0, format, GPU_TEX_2D, false }); +} + +static inline bool C3D_TexInitMipmap(C3D_Tex* tex, u16 width, u16 height, GPU_TEXCOLOR format) +{ + return C3D_TexInitWithParams(tex, NULL, + (C3D_TexInitParams){ width, height, (u8)C3D_TexCalcMaxLevel(width, height), format, GPU_TEX_2D, false }); +} + +static inline bool C3D_TexInitCube(C3D_Tex* tex, C3D_TexCube* cube, u16 width, u16 height, GPU_TEXCOLOR format) +{ + return C3D_TexInitWithParams(tex, cube, + (C3D_TexInitParams){ width, height, 0, format, GPU_TEX_CUBE_MAP, false }); +} + +static inline bool C3D_TexInitVRAM(C3D_Tex* tex, u16 width, u16 height, GPU_TEXCOLOR format) +{ + return C3D_TexInitWithParams(tex, NULL, + (C3D_TexInitParams){ width, height, 0, format, GPU_TEX_2D, true }); +} + +static inline bool C3D_TexInitShadow(C3D_Tex* tex, u16 width, u16 height) +{ + return C3D_TexInitWithParams(tex, NULL, + (C3D_TexInitParams){ width, height, 0, GPU_RGBA8, GPU_TEX_SHADOW_2D, true }); +} + +static inline bool C3D_TexInitShadowCube(C3D_Tex* tex, C3D_TexCube* cube, u16 width, u16 height) +{ + return C3D_TexInitWithParams(tex, cube, + (C3D_TexInitParams){ width, height, 0, GPU_RGBA8, GPU_TEX_SHADOW_CUBE, true }); +} + +static inline GPU_TEXTURE_MODE_PARAM C3D_TexGetType(C3D_Tex* tex) +{ + return (GPU_TEXTURE_MODE_PARAM)((tex->param>>28)&0x7); +} + +static inline void* C3D_TexGetImagePtr(C3D_Tex* tex, void* data, int level, u32* size) +{ + if (size) *size = level >= 0 ? C3D_TexCalcLevelSize(tex->size, level) : C3D_TexCalcTotalSize(tex->size, tex->maxLevel); + if (!level) return data; + return (u8*)data + (level > 0 ? C3D_TexCalcTotalSize(tex->size, level-1) : 0); +} + +static inline void* C3D_Tex2DGetImagePtr(C3D_Tex* tex, int level, u32* size) +{ + return C3D_TexGetImagePtr(tex, tex->data, level, size); +} + +static inline void* C3D_TexCubeGetImagePtr(C3D_Tex* tex, GPU_TEXFACE face, int level, u32* size) +{ + return C3D_TexGetImagePtr(tex, tex->cube->data[face], level, size); +} + +static inline void C3D_TexUpload(C3D_Tex* tex, const void* data) +{ + C3D_TexLoadImage(tex, data, GPU_TEXFACE_2D, 0); +} + +static inline void C3D_TexSetFilter(C3D_Tex* tex, GPU_TEXTURE_FILTER_PARAM magFilter, GPU_TEXTURE_FILTER_PARAM minFilter) +{ + tex->param &= ~(GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR)); + tex->param |= GPU_TEXTURE_MAG_FILTER(magFilter) | GPU_TEXTURE_MIN_FILTER(minFilter); +} + +static inline void C3D_TexSetFilterMipmap(C3D_Tex* tex, GPU_TEXTURE_FILTER_PARAM filter) +{ + tex->param &= ~GPU_TEXTURE_MIP_FILTER(GPU_LINEAR); + tex->param |= GPU_TEXTURE_MIP_FILTER(filter); +} + +static inline void C3D_TexSetWrap(C3D_Tex* tex, GPU_TEXTURE_WRAP_PARAM wrapS, GPU_TEXTURE_WRAP_PARAM wrapT) +{ + tex->param &= ~(GPU_TEXTURE_WRAP_S(3) | GPU_TEXTURE_WRAP_T(3)); + tex->param |= GPU_TEXTURE_WRAP_S(wrapS) | GPU_TEXTURE_WRAP_T(wrapT); +} + +static inline void C3D_TexSetLodBias(C3D_Tex* tex, float lodBias) +{ + int iLodBias = (int)(lodBias*0x100); + if (iLodBias > 0xFFF) + iLodBias = 0xFFF; + else if (iLodBias < -0x1000) + iLodBias = -0x1000; + tex->lodBias = iLodBias & 0x1FFF; +} diff --git a/libctru/include/c3d/types.h b/libctru/include/c3d/types.h new file mode 100644 index 0000000000..bfabbf887e --- /dev/null +++ b/libctru/include/c3d/types.h @@ -0,0 +1,81 @@ +#pragma once +#ifdef _3DS +#include <3ds.h> +#else +#include +#include +typedef uint8_t u8; +typedef uint32_t u32; +#endif + +#ifndef CITRO3D_NO_DEPRECATION +#define C3D_DEPRECATED __attribute__ ((deprecated)) +#else +#define C3D_DEPRECATED +#endif + +typedef u32 C3D_IVec; + +static inline C3D_IVec IVec_Pack(u8 x, u8 y, u8 z, u8 w) +{ + return (u32)x | ((u32)y << 8) | ((u32)z << 16) | ((u32)w << 24); +} + +/** + * @defgroup math_support Math Support Library + * @brief Implementations of matrix, vector, and quaternion operations. + * @{ + */ + +/** + * @struct C3D_FVec + * @brief Float vector + * + * Matches PICA layout + */ +typedef union +{ + /** + * @brief Vector access + */ + struct + { + float w; ///< W-component + float z; ///< Z-component + float y; ///< Y-component + float x; ///< X-component + }; + + /** + * @brief Quaternion access + */ + struct + { + float r; ///< Real component + float k; ///< K-component + float j; ///< J-component + float i; ///< I-component + }; + + /** + * @brief Raw access + */ + float c[4]; +} C3D_FVec; + +/** + * @struct C3D_FQuat + * @brief Float quaternion. See @ref C3D_FVec. + */ +typedef C3D_FVec C3D_FQuat; + +/** + * @struct C3D_Mtx + * @brief Row-major 4x4 matrix + */ +typedef union +{ + C3D_FVec r[4]; ///< Rows are vectors + float m[4*4]; ///< Raw access +} C3D_Mtx; +/** @} */ diff --git a/libctru/include/c3d/uniforms.h b/libctru/include/c3d/uniforms.h new file mode 100644 index 0000000000..9e50153a5c --- /dev/null +++ b/libctru/include/c3d/uniforms.h @@ -0,0 +1,78 @@ +#pragma once +#include "maths.h" + +#define C3D_FVUNIF_COUNT 96 +#define C3D_IVUNIF_COUNT 4 + +extern C3D_FVec C3D_FVUnif[2][C3D_FVUNIF_COUNT]; +extern C3D_IVec C3D_IVUnif[2][C3D_IVUNIF_COUNT]; +extern u16 C3D_BoolUnifs[2]; + +extern bool C3D_FVUnifDirty[2][C3D_FVUNIF_COUNT]; +extern bool C3D_IVUnifDirty[2][C3D_IVUNIF_COUNT]; +extern bool C3D_BoolUnifsDirty[2]; + +static inline C3D_FVec* C3D_FVUnifWritePtr(GPU_SHADER_TYPE type, int id, int size) +{ + int i; + for (i = 0; i < size; i ++) + C3D_FVUnifDirty[type][id+i] = true; + return &C3D_FVUnif[type][id]; +} + +static inline C3D_IVec* C3D_IVUnifWritePtr(GPU_SHADER_TYPE type, int id) +{ + id -= 0x60; + C3D_IVUnifDirty[type][id] = true; + return &C3D_IVUnif[type][id]; +} + +static inline void C3D_FVUnifMtxNx4(GPU_SHADER_TYPE type, int id, const C3D_Mtx* mtx, int num) +{ + int i; + C3D_FVec* ptr = C3D_FVUnifWritePtr(type, id, num); + for (i = 0; i < num; i ++) + ptr[i] = mtx->r[i]; // Struct copy. +} + +static inline void C3D_FVUnifMtx4x4(GPU_SHADER_TYPE type, int id, const C3D_Mtx* mtx) +{ + C3D_FVUnifMtxNx4(type, id, mtx, 4); +} + +static inline void C3D_FVUnifMtx3x4(GPU_SHADER_TYPE type, int id, const C3D_Mtx* mtx) +{ + C3D_FVUnifMtxNx4(type, id, mtx, 3); +} + +static inline void C3D_FVUnifMtx2x4(GPU_SHADER_TYPE type, int id, const C3D_Mtx* mtx) +{ + C3D_FVUnifMtxNx4(type, id, mtx, 2); +} + +static inline void C3D_FVUnifSet(GPU_SHADER_TYPE type, int id, float x, float y, float z, float w) +{ + C3D_FVec* ptr = C3D_FVUnifWritePtr(type, id, 1); + ptr->x = x; + ptr->y = y; + ptr->z = z; + ptr->w = w; +} + +static inline void C3D_IVUnifSet(GPU_SHADER_TYPE type, int id, int x, int y, int z, int w) +{ + C3D_IVec* ptr = C3D_IVUnifWritePtr(type, id); + *ptr = IVec_Pack(x, y, z, w); +} + +static inline void C3D_BoolUnifSet(GPU_SHADER_TYPE type, int id, bool value) +{ + id -= 0x68; + C3D_BoolUnifsDirty[type] = true; + if (value) + C3D_BoolUnifs[type] |= BIT(id); + else + C3D_BoolUnifs[type] &= ~BIT(id); +} + +void C3D_UpdateUniforms(GPU_SHADER_TYPE type); diff --git a/libctru/include/citro3d.h b/libctru/include/citro3d.h new file mode 100644 index 0000000000..80e5d47de8 --- /dev/null +++ b/libctru/include/citro3d.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef CITRO3D_BUILD +#error "This header file is only for external users of citro3d." +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include "c3d/types.h" + +#include "c3d/maths.h" +#include "c3d/mtxstack.h" + +#include "c3d/uniforms.h" +#include "c3d/attribs.h" +#include "c3d/buffers.h" +#include "c3d/base.h" + +#include "c3d/texenv.h" +#include "c3d/effect.h" +#include "c3d/texture.h" +#include "c3d/proctex.h" +#include "c3d/light.h" +#include "c3d/lightlut.h" +#include "c3d/fog.h" + +#include "c3d/framebuffer.h" +#include "c3d/renderqueue.h" + +#ifdef __cplusplus +} +#endif diff --git a/libctru/include/netdb.h b/libctru/include/netdb.h new file mode 100644 index 0000000000..836195aab6 --- /dev/null +++ b/libctru/include/netdb.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +#define HOST_NOT_FOUND 1 +#define NO_DATA 2 +#define NO_ADDRESS NO_DATA +#define NO_RECOVERY 3 +#define TRY_AGAIN 4 + +struct hostent { + char *h_name; + char **h_aliases; + int h_addrtype; + int h_length; + char **h_addr_list; + char *h_addr; +}; + + +#define AI_PASSIVE 0x01 +#define AI_CANONNAME 0x02 +#define AI_NUMERICHOST 0x04 +#define AI_NUMERICSERV 0x00 /* probably 0x08 but services names are never resolved */ + +// doesn't apply to 3ds +#define AI_ADDRCONFIG 0x00 + + +#define NI_MAXHOST 1025 +#define NI_MAXSERV 32 + +#define NI_NOFQDN 0x01 +#define NI_NUMERICHOST 0x02 +#define NI_NAMEREQD 0x04 +#define NI_NUMERICSERV 0x00 /* probably 0x08 but services names are never resolved */ +#define NI_DGRAM 0x00 /* probably 0x10 but services names are never resolved */ + +#define EAI_FAMILY (-303) +#define EAI_MEMORY (-304) +#define EAI_NONAME (-305) +#define EAI_SOCKTYPE (-307) + +struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + char *ai_canonname; + struct sockaddr *ai_addr; + struct addrinfo *ai_next; +}; + +#ifdef __cplusplus +extern "C" { +#endif + + extern int h_errno; + struct hostent* gethostbyname(const char *name); + struct hostent* gethostbyaddr(const void *addr, socklen_t len, int type); + void herror(const char *s); + const char* hstrerror(int err); + + int getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, socklen_t hostlen, + char *serv, socklen_t servlen, int flags); + + int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, + struct addrinfo **res); + + void freeaddrinfo(struct addrinfo *ai); + + const char *gai_strerror(int ecode); +#ifdef __cplusplus +} +#endif diff --git a/libctru/include/netinet/in.h b/libctru/include/netinet/in.h new file mode 100644 index 0000000000..eabc4a95e5 --- /dev/null +++ b/libctru/include/netinet/in.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +#define INADDR_LOOPBACK 0x7f000001 +#define INADDR_ANY 0x00000000 +#define INADDR_BROADCAST 0xFFFFFFFF +#define INADDR_NONE 0xFFFFFFFF + +#define INET_ADDRSTRLEN 16 + +/* + * Protocols (See RFC 1700 and the IANA) + */ +#define IPPROTO_IP 0 /* dummy for IP */ +#define IPPROTO_UDP 17 /* user datagram protocol */ +#define IPPROTO_TCP 6 /* tcp */ + +#define IP_TOS 7 +#define IP_TTL 8 +#define IP_MULTICAST_LOOP 9 +#define IP_MULTICAST_TTL 10 +#define IP_ADD_MEMBERSHIP 11 +#define IP_DROP_MEMBERSHIP 12 + +typedef uint16_t in_port_t; +typedef uint32_t in_addr_t; + +struct in_addr { + in_addr_t s_addr; +}; + +struct sockaddr_in { + sa_family_t sin_family; + in_port_t sin_port; + struct in_addr sin_addr; + unsigned char sin_zero[8]; +}; + +/* Request struct for multicast socket ops */ +struct ip_mreq { + struct in_addr imr_multiaddr; /* IP multicast address of group */ + struct in_addr imr_interface; /* local IP address of interface */ +}; diff --git a/libctru/include/netinet/tcp.h b/libctru/include/netinet/tcp.h new file mode 100644 index 0000000000..3619f94b2f --- /dev/null +++ b/libctru/include/netinet/tcp.h @@ -0,0 +1,9 @@ +#pragma once + +#define SOL_TCP 6 /* TCP level */ + +enum{ + _CTRU_TCP_OPT = 0x2000, /* Flag for tcp opt values */ + TCP_NODELAY = 1 | _CTRU_TCP_OPT, /* Don't delay send to coalesce packets */ + TCP_MAXSEG = 2 | _CTRU_TCP_OPT, +}; diff --git a/libctru/include/poll.h b/libctru/include/poll.h new file mode 100644 index 0000000000..8b34a7a868 --- /dev/null +++ b/libctru/include/poll.h @@ -0,0 +1,29 @@ +#pragma once + +#include <3ds/types.h> + +#define POLLIN 0x01 +#define POLLPRI 0x02 +#define POLLHUP 0x04 // unknown ??? +#define POLLERR 0x08 // probably +#define POLLOUT 0x10 +#define POLLNVAL 0x20 + +typedef u32 nfds_t; + +struct pollfd +{ + int fd; + int events; + int revents; +}; + +#ifdef __cplusplus +extern "C" { +#endif + + int poll(struct pollfd *fds, nfds_t nfsd, int timeout); + +#ifdef __cplusplus +} +#endif diff --git a/libctru/include/sys/ioctl.h b/libctru/include/sys/ioctl.h new file mode 100644 index 0000000000..bbd3f83a44 --- /dev/null +++ b/libctru/include/sys/ioctl.h @@ -0,0 +1,13 @@ +#pragma once + +#define FIONBIO 1 + +#ifdef __cplusplus +extern "C" { +#endif + + int ioctl(int fd, int request, ...); + +#ifdef __cplusplus +} +#endif diff --git a/libctru/include/sys/select.h b/libctru/include/sys/select.h new file mode 100644 index 0000000000..f6f6154730 --- /dev/null +++ b/libctru/include/sys/select.h @@ -0,0 +1 @@ +#include_next \ No newline at end of file diff --git a/libctru/include/sys/socket.h b/libctru/include/sys/socket.h new file mode 100644 index 0000000000..8f6d78ed48 --- /dev/null +++ b/libctru/include/sys/socket.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include + +#define SOL_SOCKET 0xFFFF + +#define PF_UNSPEC 0 +#define PF_INET 2 +#define PF_INET6 23 + +#define AF_UNSPEC PF_UNSPEC +#define AF_INET PF_INET +#define AF_INET6 PF_INET6 + +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 + +// any flags > 0x4 causes send/recv to return EOPNOTSUPP +#define MSG_OOB 0x0001 +#define MSG_PEEK 0x0002 +#define MSG_DONTWAIT 0x0004 +#define MSG_DONTROUTE 0x0000 // ??? +#define MSG_WAITALL 0x0000 // ??? +#define MSG_MORE 0x0000 // ??? +#define MSG_NOSIGNAL 0x0000 // there are no signals + +#define SHUT_RD 0 +#define SHUT_WR 1 +#define SHUT_RDWR 2 + +/* + * SOL_SOCKET options + */ +#define SO_REUSEADDR 0x0004 // reuse address +#define SO_LINGER 0x0080 // linger (no effect?) +#define SO_OOBINLINE 0x0100 // out-of-band data inline (no effect?) +#define SO_SNDBUF 0x1001 // send buffer size +#define SO_RCVBUF 0x1002 // receive buffer size +#define SO_SNDLOWAT 0x1003 // send low-water mark (no effect?) +#define SO_RCVLOWAT 0x1004 // receive low-water mark +#define SO_TYPE 0x1008 // get socket type +#define SO_ERROR 0x1009 // get socket error + +#define SO_BROADCAST 0x0000 // unrequired, included for compatibility + +typedef uint32_t socklen_t; +typedef uint16_t sa_family_t; + +struct sockaddr { + sa_family_t sa_family; + char sa_data[]; +}; + +// biggest size on 3ds is 0x1C (sockaddr_in6) +struct sockaddr_storage { + sa_family_t ss_family; + char __ss_padding[26]; +}; + +struct linger { + int l_onoff; + int l_linger; +}; + +#ifdef __cplusplus +extern "C" { +#endif + + int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); + int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); + int closesocket(int sockfd); + int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); + int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen); + int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); + int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); + int listen(int sockfd, int backlog); + ssize_t recv(int sockfd, void *buf, size_t len, int flags); + ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); + ssize_t send(int sockfd, const void *buf, size_t len, int flags); + ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); + int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); + int shutdown(int sockfd, int how); + int socket(int domain, int type, int protocol); + int sockatmark(int sockfd); + +#ifdef __cplusplus +} +#endif diff --git a/libctru/include/tex3ds.h b/libctru/include/tex3ds.h new file mode 100644 index 0000000000..18a1363df1 --- /dev/null +++ b/libctru/include/tex3ds.h @@ -0,0 +1,215 @@ +/*------------------------------------------------------------------------------ + * Copyright (c) 2017 + * Michael Theall (mtheall) + * + * This file is part of citro3d. + * + * 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. + *----------------------------------------------------------------------------*/ +/** @file tex3ds.h + * @brief tex3ds support + */ +#pragma once +#ifdef CITRO3D_BUILD +#include "c3d/texture.h" +#else +#include +#endif + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Subtexture + * @note If top > bottom, the subtexture is rotated 1/4 revolution counter-clockwise + */ +typedef struct Tex3DS_SubTexture +{ + u16 width; ///< Sub-texture width (pixels) + u16 height; ///< Sub-texture height (pixels) + float left; ///< Left u-coordinate + float top; ///< Top v-coordinate + float right; ///< Right u-coordinate + float bottom; ///< Bottom v-coordinate +} Tex3DS_SubTexture; + +/** @brief Texture */ +typedef struct Tex3DS_Texture_s* Tex3DS_Texture; + +/** @brief Import Tex3DS texture + * @param[in] input Input data + * @param[in] insize Size of the input data + * @param[out] tex citro3d texture + * @param[out] texcube citro3d texcube + * @param[in] vram Whether to store textures in VRAM + * @returns Tex3DS texture + */ +Tex3DS_Texture Tex3DS_TextureImport(const void* input, size_t insize, C3D_Tex* tex, C3D_TexCube* texcube, bool vram); + +/** @brief Import Tex3DS texture + * + * @description + * For example, use this if you want to import from a large file without + * pulling the entire file into memory. + * + * @param[out] tex citro3d texture + * @param[out] texcube citro3d texcube + * @param[in] vram Whether to store textures in VRAM + * @param[in] callback Data callback + * @param[in] userdata User data passed to callback + * @returns Tex3DS texture + */ +Tex3DS_Texture Tex3DS_TextureImportCallback(C3D_Tex* tex, C3D_TexCube* texcube, bool vram, decompressCallback callback, void* userdata); + +/** @brief Import Tex3DS texture + * + * Starts reading at the current file descriptor's offset. The file + * descriptor's position is left at the end of the decoded data. On error, the + * file descriptor's position is indeterminate. + * + * @param[in] fd Open file descriptor + * @param[out] tex citro3d texture + * @param[out] texcube citro3d texcube + * @param[in] vram Whether to store textures in VRAM + * @returns Tex3DS texture + */ +Tex3DS_Texture Tex3DS_TextureImportFD(int fd, C3D_Tex* tex, C3D_TexCube* texcube, bool vram); + +/** @brief Import Tex3DS texture + * + * Starts reading at the current file stream's offset. The file stream's + * position is left at the end of the decoded data. On error, the file + * stream's position is indeterminate. + * + * @param[in] fp Open file stream + * @param[out] tex citro3d texture + * @param[out] texcube citro3d texcube + * @param[in] vram Whether to store textures in VRAM + * @returns Tex3DS texture + */ +Tex3DS_Texture Tex3DS_TextureImportStdio(FILE* fp, C3D_Tex* tex, C3D_TexCube* texcube, bool vram); + +/** @brief Get number of subtextures + * @param[in] texture Tex3DS texture + * @returns Number of subtextures + */ +size_t Tex3DS_GetNumSubTextures(const Tex3DS_Texture texture); + +/** @brief Get subtexture + * @param[in] texture Tex3DS texture + * @param[in] index Subtexture index + * @returns Subtexture info + */ +const Tex3DS_SubTexture* Tex3DS_GetSubTexture(const Tex3DS_Texture texture, size_t index); + +/** @brief Check if subtexture is rotated + * @param[in] subtex Subtexture to check + * @returns whether subtexture is rotated + */ +static inline bool +Tex3DS_SubTextureRotated(const Tex3DS_SubTexture* subtex) +{ + return subtex->top < subtex->bottom; +} + +/** @brief Get bottom-left texcoords + * @param[in] subtex Subtexture + * @param[out] u u-coordinate + * @param[out] v v-coordinate + */ +static inline void +Tex3DS_SubTextureBottomLeft(const Tex3DS_SubTexture* subtex, float* u, float* v) +{ + if (!Tex3DS_SubTextureRotated(subtex)) + { + *u = subtex->left; + *v = subtex->bottom; + } else + { + *u = subtex->bottom; + *v = subtex->left; + } +} + +/** @brief Get bottom-right texcoords + * @param[in] subtex Subtexture + * @param[out] u u-coordinate + * @param[out] v v-coordinate + */ +static inline void +Tex3DS_SubTextureBottomRight(const Tex3DS_SubTexture* subtex, float* u, float* v) +{ + if (!Tex3DS_SubTextureRotated(subtex)) + { + *u = subtex->right; + *v = subtex->bottom; + } else + { + *u = subtex->bottom; + *v = subtex->right; + } +} + +/** @brief Get top-left texcoords + * @param[in] subtex Subtexture + * @param[out] u u-coordinate + * @param[out] v v-coordinate + */ +static inline void +Tex3DS_SubTextureTopLeft(const Tex3DS_SubTexture* subtex, float* u, float* v) +{ + if (!Tex3DS_SubTextureRotated(subtex)) + { + *u = subtex->left; + *v = subtex->top; + } else + { + *u = subtex->top; + *v = subtex->left; + } +} + +/** @brief Get top-right texcoords + * @param[in] subtex Subtexture + * @param[out] u u-coordinate + * @param[out] v v-coordinate + */ +static inline void +Tex3DS_SubTextureTopRight(const Tex3DS_SubTexture* subtex, float* u, float* v) +{ + if (!Tex3DS_SubTextureRotated(subtex)) + { + *u = subtex->right; + *v = subtex->top; + } else + { + *u = subtex->top; + *v = subtex->right; + } +} + +/** @brief Free Tex3DS texture + * @param[in] texture Tex3DS texture to free + */ +void Tex3DS_TextureFree(Tex3DS_Texture texture); + +#ifdef __cplusplus +} +#endif diff --git a/src/pc/audio/audio_3ds.c b/src/pc/audio/audio_3ds.c index 473f960225..de79c7bf36 100644 --- a/src/pc/audio/audio_3ds.c +++ b/src/pc/audio/audio_3ds.c @@ -116,17 +116,26 @@ static bool audio_3ds_init() LightEvent_Init(&s_event_main, RESET_ONESHOT); s32 prio = 0; - svcGetThreadPriority(&prio, CUR_THREAD_HANDLE); + //svcGetThreadPriority(&prio, CUR_THREAD_HANDLE); int cpu; if (is_new_n3ds()) + { cpu = 2; // n3ds 3rd core + prio = 0x18; + } else if (R_SUCCEEDED(APT_SetAppCpuTimeLimit(80))) + { cpu = 1; // o3ds 2nd core (system) + prio = 0x18; + } else + { cpu = 0; // better to have choppy sound than no sound? + prio = 0x19; + } - threadId = threadCreate(audio_3ds_loop, 0, 128 * 1024, prio - 1, cpu, true); + threadId = threadCreate(audio_3ds_loop, 0, 64 * 1024, prio, cpu, true); //prio - 1 if (threadId) printf("Created audio thread on core %i\n", cpu); diff --git a/src/pc/gfx/gfx_3ds.c b/src/pc/gfx/gfx_3ds.c index 4525cc8abf..5a8177032e 100644 --- a/src/pc/gfx/gfx_3ds.c +++ b/src/pc/gfx/gfx_3ds.c @@ -54,8 +54,8 @@ static void initialise_screens() { C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); - bool useAA = gfx_config.useAA; - bool useWide = gfx_config.useWide && n3ds_model != 3; // old 2DS does not support 800px + bool useAA = false; //= gfx_config.useAA; + bool useWide = false; //gfx_config.useWide && n3ds_model != 3; // old 2DS does not support 800px u32 transferFlags = DISPLAY_TRANSFER_FLAGS; diff --git a/tools/Makefile b/tools/Makefile index 2c13b884f1..fd0c932f65 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -10,7 +10,7 @@ CXX_PROGRAMS += armips endif ifeq ($(OS),Windows_NT) -ARMIPS_FLAGS := -municode +#ARMIPS_FLAGS := -municode endif default: all diff --git a/tools/audiofile/Makefile b/tools/audiofile/Makefile index 20d22a45f6..70828fe157 100644 --- a/tools/audiofile/Makefile +++ b/tools/audiofile/Makefile @@ -4,7 +4,7 @@ libaudiofile.a: audiofile.o ar rcs libaudiofile.a audiofile.o audiofile.o: audiofile.cpp audiofile.h aupvlist.h - $(CXX) -std=c++11 -O2 -I. -c audiofile.cpp + $(CXX) -std=gnu++11 -O3 -ffast-math -I. -c audiofile.cpp clean: rm -f audiofile.o libaudiofile.a diff --git a/tools/audiofile/audiofile.cpp b/tools/audiofile/audiofile.cpp index 85b89687f4..6dbf234344 100644 --- a/tools/audiofile/audiofile.cpp +++ b/tools/audiofile/audiofile.cpp @@ -10182,9 +10182,12 @@ static const InstrumentSetup _af_default_instrumentsetup = static const TrackSetup _af_default_tracksetup = { + //The Samplerate + //44100.0 + //22050.0 0, { - 44100.0, + 32728.5, AF_SAMPFMT_TWOSCOMP, 16, _AF_BYTEORDER_NATIVE, diff --git a/tools/sdk-tools/tabledesign/Makefile b/tools/sdk-tools/tabledesign/Makefile index a06d70af3e..f7cf6eb069 100644 --- a/tools/sdk-tools/tabledesign/Makefile +++ b/tools/sdk-tools/tabledesign/Makefile @@ -6,7 +6,7 @@ IRIX_CC := $(QEMU_IRIX) -silent -L $(IRIX_ROOT) $(IRIX_ROOT)/usr/bin/cc IRIX_CFLAGS := -fullwarn -Wab,-r4300_mul -Xcpluscomm -mips1 -O2 NATIVE_CC := gcc -NATIVE_CFLAGS := -Wall -Wno-uninitialized -O2 +NATIVE_CFLAGS := -Wall -Wno-uninitialized -O3 LDFLAGS := -lm -laudiofile From b675f04966947c82ead0627d9dd1948946eca8dc Mon Sep 17 00:00:00 2001 From: Devin <70994866+RetroGamer02@users.noreply.github.com> Date: Fri, 4 Nov 2022 07:00:24 -0400 Subject: [PATCH 02/17] Remove personal bin path --- Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index c8ba797fb9..256c0a3293 100644 --- a/Makefile +++ b/Makefile @@ -902,15 +902,15 @@ $(ELF): $(O_FILES) $(MIO0_FILES:.mio0=.o) $(SOUND_OBJ_FILES) $(ULTRA_O_FILES) $( $(LD) -L $(BUILD_DIR) -o $@ $(O_FILES) $(BUILD_DIR)/src/pc/gfx/shader.shbin.o $(MINIMAP_T3X_O) $(SOUND_OBJ_FILES) $(ULTRA_O_FILES) $(GODDARD_O_FILES) $(LDFLAGS) $(EXE): $(ELF) - C:\\devkitPro\\tools\\bin\\3dsxtool $< $@ --smdh=$(BUILD_DIR)/$(SMDH_ICON) + 3dsxtool $< $@ --smdh=$(BUILD_DIR)/$(SMDH_ICON) $(CIA): $(ELF) @echo "Generating $@, please wait..." - C:\\devkitPro\\tools\\bin\\makerom -f cia -o "$@" -rsf 3ds/template.rsf -target t -elf "$<" -icon 3ds/icon.icn -banner 3ds/banner.bnr + makerom -f cia -o "$@" -rsf 3ds/template.rsf -target t -elf "$<" -icon 3ds/icon.icn -banner 3ds/banner.bnr # stolen from /opt/devkitpro/devkitARM/base_tools define bin2o - C:\\devkitPro\\tools\\bin\\bin2s -a 4 -H $(BUILD_DIR)/$(MINIMAP_TEXTURES)/`(echo $( $(BUILD_DIR)/$@ %.smdh: %.png - C:\\devkitPro\\tools\\bin\\smdhtool --create "$(SMDH_TITLE)" "$(SMDH_DESCRIPTION)" "$(SMDH_AUTHOR)" $< $(BUILD_DIR)/$@ + smdhtool --create "$(SMDH_TITLE)" "$(SMDH_DESCRIPTION)" "$(SMDH_AUTHOR)" $< $(BUILD_DIR)/$@ else $(EXE): $(O_FILES) $(MIO0_FILES:.mio0=.o) $(SOUND_OBJ_FILES) $(ULTRA_O_FILES) $(GODDARD_O_FILES) From f90e1f8ddfd593275471b728f1b72e656181953e Mon Sep 17 00:00:00 2001 From: Devin <70994866+RetroGamer02@users.noreply.github.com> Date: Fri, 4 Nov 2022 07:03:38 -0400 Subject: [PATCH 03/17] Add missing lib files --- libctru/lib/libcitro3d.a | Bin 0 -> 701082 bytes libctru/lib/libcitro3dd.a | Bin 0 -> 677286 bytes libctru/lib/libctru.a | Bin 0 -> 4392186 bytes libctru/lib/libctrud.a | Bin 0 -> 3775182 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 libctru/lib/libcitro3d.a create mode 100644 libctru/lib/libcitro3dd.a create mode 100644 libctru/lib/libctru.a create mode 100644 libctru/lib/libctrud.a diff --git a/libctru/lib/libcitro3d.a b/libctru/lib/libcitro3d.a new file mode 100644 index 0000000000000000000000000000000000000000..c00eba03a797d41da3d2fc72dc8697ebf124ecc6 GIT binary patch literal 701082 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
bVWX} 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 z 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ʣ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

N;(&jL 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 Iak1S02N5mm%K!iX literal 0 HcmV?d00001 diff --git a/libctru/lib/libcitro3dd.a b/libctru/lib/libcitro3dd.a new file mode 100644 index 0000000000000000000000000000000000000000..c12ef19b376b463519732f877fa87e48054b7998 GIT binary patch literal 677286 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≪)!XXnE~Zw;EM#G~MCg$;cky>i89Tx_ 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