Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"
This reverts commit 15b7df8ef1.
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
APPLICATIONS := daybreak haze reboot_to_payload
|
||||
|
||||
SUBFOLDERS := $(APPLICATIONS)
|
||||
|
||||
TOPTARGETS := all clean
|
||||
|
||||
$(TOPTARGETS): $(SUBFOLDERS)
|
||||
|
||||
$(SUBFOLDERS):
|
||||
$(MAKE) -C $@ $(MAKECMDGOALS)
|
||||
|
||||
.PHONY: $(TOPTARGETS) $(SUBFOLDERS)
|
||||
@@ -1,282 +0,0 @@
|
||||
#---------------------------------------------------------------------------------
|
||||
.SUFFIXES:
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
ifeq ($(strip $(DEVKITPRO)),)
|
||||
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
|
||||
endif
|
||||
|
||||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
# SOURCES is a list of directories containing source code
|
||||
# DATA is a list of directories containing data files
|
||||
# INCLUDES is a list of directories containing header files
|
||||
# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional)
|
||||
#
|
||||
# NO_ICON: if set to anything, do not use icon.
|
||||
# NO_NACP: if set to anything, no .nacp file is generated.
|
||||
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
|
||||
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
|
||||
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
|
||||
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
|
||||
# ICON is the filename of the icon (.jpg), relative to the project folder.
|
||||
# If not set, it attempts to use one of the following (in this order):
|
||||
# - <Project name>.jpg
|
||||
# - icon.jpg
|
||||
# - <libnx folder>/default_icon.jpg
|
||||
#
|
||||
# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder.
|
||||
# If not set, it attempts to use one of the following (in this order):
|
||||
# - <Project name>.json
|
||||
# - config.json
|
||||
# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead
|
||||
# of a homebrew executable (.nro). This is intended to be used for sysmodules.
|
||||
# NACP building is skipped as well.
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := daybreak
|
||||
BUILD := build
|
||||
SOURCES := source nanovg/shaders
|
||||
DATA := data
|
||||
INCLUDES := include ../include
|
||||
ROMFS := romfs
|
||||
|
||||
# Output folders for autogenerated files in romfs
|
||||
OUT_SHADERS := shaders
|
||||
|
||||
APP_TITLE := Daybreak
|
||||
APP_AUTHOR := Atmosphere-NX
|
||||
APP_VERSION := 1.0.0
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
||||
|
||||
CFLAGS := -g -Wall -O2 -ffunction-sections \
|
||||
$(ARCH) $(DEFINES)
|
||||
|
||||
CFLAGS += $(INCLUDE) -D__SWITCH__
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -std=gnu++23 -fno-exceptions -fno-rtti
|
||||
|
||||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
|
||||
LIBS := -lnanovg -ldeko3d -lnx
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
# include and lib
|
||||
#---------------------------------------------------------------------------------
|
||||
LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/nanovg/
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# no real need to edit anything past this point unless you need to add additional
|
||||
# rules for different file extensions
|
||||
#---------------------------------------------------------------------------------
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||
export TOPDIR := $(CURDIR)
|
||||
|
||||
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
|
||||
|
||||
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||
|
||||
SUBFOLDERS := nanovg
|
||||
|
||||
TOPTARGETS := all clean
|
||||
|
||||
$(TOPTARGETS): $(SUBFOLDERS)
|
||||
|
||||
$(SUBFOLDERS):
|
||||
$(MAKE) -C $@ $(MAKECMDGOALS)
|
||||
|
||||
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||
GLSLFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.glsl)))
|
||||
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# use CXX for linking C++ projects, CC for standard C
|
||||
#---------------------------------------------------------------------------------
|
||||
ifeq ($(strip $(CPPFILES)),)
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CC)
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CXX)
|
||||
#---------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
|
||||
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
||||
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
||||
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||
|
||||
ifneq ($(strip $(ROMFS)),)
|
||||
ROMFS_TARGETS :=
|
||||
ROMFS_FOLDERS :=
|
||||
ifneq ($(strip $(OUT_SHADERS)),)
|
||||
ROMFS_SHADERS := $(ROMFS)/$(OUT_SHADERS)
|
||||
ROMFS_TARGETS += $(patsubst %.glsl, $(ROMFS_SHADERS)/%.dksh, $(GLSLFILES))
|
||||
ROMFS_FOLDERS += $(ROMFS_SHADERS)
|
||||
endif
|
||||
|
||||
export ROMFS_DEPS := $(foreach file,$(ROMFS_TARGETS),$(CURDIR)/$(file))
|
||||
endif
|
||||
|
||||
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||
-I$(CURDIR)/$(BUILD)
|
||||
|
||||
ifeq ($(strip $(CONFIG_JSON)),)
|
||||
jsons := $(wildcard *.json)
|
||||
ifneq (,$(findstring $(TARGET).json,$(jsons)))
|
||||
export APP_JSON := $(TOPDIR)/$(TARGET).json
|
||||
else
|
||||
ifneq (,$(findstring config.json,$(jsons)))
|
||||
export APP_JSON := $(TOPDIR)/config.json
|
||||
endif
|
||||
endif
|
||||
else
|
||||
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(ICON)),)
|
||||
icons := $(wildcard *.jpg)
|
||||
ifneq (,$(findstring $(TARGET).jpg,$(icons)))
|
||||
export APP_ICON := $(TOPDIR)/$(TARGET).jpg
|
||||
else
|
||||
ifneq (,$(findstring icon.jpg,$(icons)))
|
||||
export APP_ICON := $(TOPDIR)/icon.jpg
|
||||
endif
|
||||
endif
|
||||
else
|
||||
export APP_ICON := $(TOPDIR)/$(ICON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(NO_ICON)),)
|
||||
export NROFLAGS += --icon=$(APP_ICON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(NO_NACP)),)
|
||||
export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp
|
||||
endif
|
||||
|
||||
ifneq ($(APP_TITLEID),)
|
||||
export NACPFLAGS += --titleid=$(APP_TITLEID)
|
||||
endif
|
||||
|
||||
ifneq ($(ROMFS),)
|
||||
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
|
||||
endif
|
||||
|
||||
.PHONY: $(TOPTARGETS) $(SUBFOLDERS) all clean
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
all: $(ROMFS_TARGETS) | $(BUILD)
|
||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||
|
||||
$(BUILD):
|
||||
@mkdir -p $@
|
||||
|
||||
ifneq ($(strip $(ROMFS_TARGETS)),)
|
||||
|
||||
$(ROMFS_TARGETS): | $(ROMFS_FOLDERS)
|
||||
|
||||
$(ROMFS_FOLDERS):
|
||||
@mkdir -p $@
|
||||
|
||||
$(ROMFS_SHADERS)/%_vsh.dksh: %_vsh.glsl
|
||||
@echo {vert} $(notdir $<)
|
||||
@uam -s vert -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%_tcsh.dksh: %_tcsh.glsl
|
||||
@echo {tess_ctrl} $(notdir $<)
|
||||
@uam -s tess_ctrl -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%_tesh.dksh: %_tesh.glsl
|
||||
@echo {tess_eval} $(notdir $<)
|
||||
@uam -s tess_eval -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%_gsh.dksh: %_gsh.glsl
|
||||
@echo {geom} $(notdir $<)
|
||||
@uam -s geom -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%_fsh.dksh: %_fsh.glsl
|
||||
@echo {frag} $(notdir $<)
|
||||
@uam -s frag -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%.dksh: %.glsl
|
||||
@echo {comp} $(notdir $<)
|
||||
@uam -s comp -o $@ $<
|
||||
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
clean:
|
||||
@echo clean ...
|
||||
ifeq ($(strip $(APP_JSON)),)
|
||||
@rm -fr $(BUILD) $(ROMFS_FOLDERS) $(TARGET).nro $(TARGET).nacp $(TARGET).elf
|
||||
else
|
||||
@rm -fr $(BUILD) $(ROMFS_FOLDERS) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf
|
||||
endif
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
.PHONY: all
|
||||
|
||||
DEPENDS := $(OFILES:.o=.d)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# main targets
|
||||
#---------------------------------------------------------------------------------
|
||||
ifeq ($(strip $(APP_JSON)),)
|
||||
|
||||
all : $(OUTPUT).nro
|
||||
|
||||
ifeq ($(strip $(NO_NACP)),)
|
||||
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS)
|
||||
else
|
||||
$(OUTPUT).nro : $(OUTPUT).elf $(ROMFS_DEPS)
|
||||
endif
|
||||
|
||||
else
|
||||
|
||||
all : $(OUTPUT).nsp
|
||||
|
||||
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
|
||||
|
||||
$(OUTPUT).nso : $(OUTPUT).elf
|
||||
|
||||
endif
|
||||
|
||||
$(OUTPUT).elf : $(OFILES)
|
||||
|
||||
$(OFILES_SRC) : $(HFILES_BIN)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# you need a rule like this for each extension you use as binary data
|
||||
#---------------------------------------------------------------------------------
|
||||
%.bin.o %_bin.h : %.bin
|
||||
#---------------------------------------------------------------------------------
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
-include $(DEPENDS)
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------------
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
@@ -1,92 +0,0 @@
|
||||
# deko3d shaders
|
||||
*.dksh
|
||||
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
*.lst
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.lz4
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Switch Executables
|
||||
*.nso
|
||||
*.nro
|
||||
*.nacp
|
||||
*.npdm
|
||||
*.pfs0
|
||||
*.nsp
|
||||
*.kip
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
# Distribution files
|
||||
*.tgz
|
||||
*.zip
|
||||
*.bz2
|
||||
|
||||
# IDA binaries
|
||||
*.id0
|
||||
*.id1
|
||||
*.id2
|
||||
*.idb
|
||||
*.i64
|
||||
*.nam
|
||||
*.til
|
||||
|
||||
# Compiled python files.
|
||||
*.pyc
|
||||
|
||||
.**/
|
||||
|
||||
# NOTE: make sure to make exceptions to this pattern when needed!
|
||||
*.bin
|
||||
*.enc
|
||||
|
||||
**/out
|
||||
**/build
|
||||
@@ -1,12 +0,0 @@
|
||||
; DO NOT EDIT (unless you know what you are doing)
|
||||
;
|
||||
; This subdirectory is a git "subrepo", and this file is maintained by the
|
||||
; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
|
||||
;
|
||||
[subrepo]
|
||||
remote = https://github.com/Adubbz/nanovg-deko3d.git
|
||||
branch = master
|
||||
commit = a8c9778aff08420b5b4af7b54bef5d4f3b5ac568
|
||||
parent = 797e3651d5e425231dd7f252489338e38872b116
|
||||
method = merge
|
||||
cmdver = 0.4.3
|
||||
@@ -1,18 +0,0 @@
|
||||
Copyright (c) 2020 Adubbz, Mikko Mononen memon@inside.org
|
||||
|
||||
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.
|
||||
@@ -1,170 +0,0 @@
|
||||
#---------------------------------------------------------------------------------
|
||||
.SUFFIXES:
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
ifeq ($(strip $(DEVKITPRO)),)
|
||||
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
|
||||
endif
|
||||
|
||||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
# SOURCES is a list of directories containing source code
|
||||
# DATA is a list of directories containing data files
|
||||
# INCLUDES is a list of directories containing header files
|
||||
# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional)
|
||||
#
|
||||
# NO_ICON: if set to anything, do not use icon.
|
||||
# NO_NACP: if set to anything, no .nacp file is generated.
|
||||
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
|
||||
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
|
||||
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
|
||||
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
|
||||
# ICON is the filename of the icon (.jpg), relative to the project folder.
|
||||
# If not set, it attempts to use one of the following (in this order):
|
||||
# - <Project name>.jpg
|
||||
# - icon.jpg
|
||||
# - <libnx folder>/default_icon.jpg
|
||||
#
|
||||
# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder.
|
||||
# If not set, it attempts to use one of the following (in this order):
|
||||
# - <Project name>.json
|
||||
# - config.json
|
||||
# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead
|
||||
# of a homebrew executable (.nro). This is intended to be used for sysmodules.
|
||||
# NACP building is skipped as well.
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := libnanovg
|
||||
BUILD := build
|
||||
SOURCES := source source/framework
|
||||
INCLUDES := include include/nanovg include/nanovg/framework
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
||||
|
||||
CFLAGS := -g -Wall -O2 -ffunction-sections \
|
||||
$(ARCH) $(DEFINES)
|
||||
|
||||
CFLAGS += -Wno-misleading-indentation -Wno-use-after-free -Wno-unused-function
|
||||
|
||||
CFLAGS += $(INCLUDE) -D__SWITCH__
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -std=gnu++17 -fno-exceptions -fno-rtti
|
||||
|
||||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
|
||||
#LIBS := -ldeko3dd -lglad -lEGL -lglapi -ldrm_nouveau
|
||||
LIBS := -ldeko3d
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
# include and lib
|
||||
#---------------------------------------------------------------------------------
|
||||
LIBDIRS := $(PORTLIBS) $(LIBNX)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# no real need to edit anything past this point unless you need to add additional
|
||||
# rules for different file extensions
|
||||
#---------------------------------------------------------------------------------
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||
export TOPDIR := $(CURDIR)
|
||||
|
||||
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
|
||||
|
||||
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||
|
||||
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||
GLSLFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.glsl)))
|
||||
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# use CXX for linking C++ projects, CC for standard C
|
||||
#---------------------------------------------------------------------------------
|
||||
ifeq ($(strip $(CPPFILES)),)
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CC)
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CXX)
|
||||
#---------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
|
||||
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
||||
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
||||
|
||||
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||
-I$(CURDIR)/$(BUILD)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
all: lib/$(TARGET).a
|
||||
|
||||
lib:
|
||||
@[ -d $@ ] || mkdir -p $@
|
||||
|
||||
release:
|
||||
@[ -d $@ ] || mkdir -p $@
|
||||
|
||||
lib/$(TARGET).a : lib release $(SOURCES) $(INCLUDES)
|
||||
@$(MAKE) BUILD=release OUTPUT=$(CURDIR)/$@ \
|
||||
BUILD_CFLAGS="-DNDEBUG=1 -O2" \
|
||||
DEPSDIR=$(CURDIR)/release \
|
||||
--no-print-directory -C release \
|
||||
-f $(CURDIR)/Makefile
|
||||
|
||||
dist-bin: all
|
||||
@tar --exclude=*~ -cjf $(TARGET).tar.bz2 include lib
|
||||
|
||||
dist-src:
|
||||
@tar --exclude=*~ -cjf $(TARGET)-src.tar.bz2 include source Makefile
|
||||
|
||||
dist: dist-src dist-bin
|
||||
#---------------------------------------------------------------------------------
|
||||
clean:
|
||||
@echo clean ...
|
||||
@rm -fr release lib *.bz2
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
|
||||
DEPENDS := $(OFILES:.o=.d)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# main targets
|
||||
#---------------------------------------------------------------------------------
|
||||
$(OUTPUT) : $(OFILES)
|
||||
|
||||
$(OFILES) : $(GCH_FILES)
|
||||
|
||||
$(OFILES_SRC) : $(HFILES_BIN)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# you need a rule like this for each extension you use as binary data
|
||||
#---------------------------------------------------------------------------------
|
||||
%.bin.o %_bin.h : %.bin
|
||||
#---------------------------------------------------------------------------------
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
-include $(DEPENDS)
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------------
|
||||
@@ -1,18 +0,0 @@
|
||||
NanoVG for Deko3D
|
||||
==========
|
||||
|
||||
NanoVG is small antialiased vector graphics rendering library. This is a port to [deko3d](https://github.com/devkitPro/deko3d), a low level 3D graphics API targetting the Nvidia Tegra X1 found inside the Nintendo Switch.
|
||||
|
||||
## Example
|
||||
An example of using this library can be found [here](https://github.com/Adubbz/nanovg-deko3d-example).
|
||||
|
||||
## License
|
||||
The library is licensed under [zlib license](LICENSE).
|
||||
|
||||
Dependencies:
|
||||
- fincs' deko3d framework is licensed under [zlib license](source/framework/LICENSE).
|
||||
|
||||
## Links
|
||||
The original [nanovg project](https://github.com/memononen/nanovg).
|
||||
Uses [stb_truetype](http://nothings.org) for font rendering.
|
||||
Uses [stb_image](http://nothings.org) for image loading.
|
||||
@@ -1,697 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2013 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#ifndef NANOVG_H
|
||||
#define NANOVG_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define NVG_PI 3.14159265358979323846264338327f
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4201) // nonstandard extension used : nameless struct/union
|
||||
#endif
|
||||
|
||||
typedef struct NVGcontext NVGcontext;
|
||||
|
||||
struct NVGcolor {
|
||||
union {
|
||||
float rgba[4];
|
||||
struct {
|
||||
float r,g,b,a;
|
||||
};
|
||||
};
|
||||
};
|
||||
typedef struct NVGcolor NVGcolor;
|
||||
|
||||
struct NVGpaint {
|
||||
float xform[6];
|
||||
float extent[2];
|
||||
float radius;
|
||||
float feather;
|
||||
NVGcolor innerColor;
|
||||
NVGcolor outerColor;
|
||||
int image;
|
||||
};
|
||||
typedef struct NVGpaint NVGpaint;
|
||||
|
||||
enum NVGwinding {
|
||||
NVG_CCW = 1, // Winding for solid shapes
|
||||
NVG_CW = 2, // Winding for holes
|
||||
};
|
||||
|
||||
enum NVGsolidity {
|
||||
NVG_SOLID = 1, // CCW
|
||||
NVG_HOLE = 2, // CW
|
||||
};
|
||||
|
||||
enum NVGlineCap {
|
||||
NVG_BUTT,
|
||||
NVG_ROUND,
|
||||
NVG_SQUARE,
|
||||
NVG_BEVEL,
|
||||
NVG_MITER,
|
||||
};
|
||||
|
||||
enum NVGalign {
|
||||
// Horizontal align
|
||||
NVG_ALIGN_LEFT = 1<<0, // Default, align text horizontally to left.
|
||||
NVG_ALIGN_CENTER = 1<<1, // Align text horizontally to center.
|
||||
NVG_ALIGN_RIGHT = 1<<2, // Align text horizontally to right.
|
||||
// Vertical align
|
||||
NVG_ALIGN_TOP = 1<<3, // Align text vertically to top.
|
||||
NVG_ALIGN_MIDDLE = 1<<4, // Align text vertically to middle.
|
||||
NVG_ALIGN_BOTTOM = 1<<5, // Align text vertically to bottom.
|
||||
NVG_ALIGN_BASELINE = 1<<6, // Default, align text vertically to baseline.
|
||||
};
|
||||
|
||||
enum NVGblendFactor {
|
||||
NVG_ZERO = 1<<0,
|
||||
NVG_ONE = 1<<1,
|
||||
NVG_SRC_COLOR = 1<<2,
|
||||
NVG_ONE_MINUS_SRC_COLOR = 1<<3,
|
||||
NVG_DST_COLOR = 1<<4,
|
||||
NVG_ONE_MINUS_DST_COLOR = 1<<5,
|
||||
NVG_SRC_ALPHA = 1<<6,
|
||||
NVG_ONE_MINUS_SRC_ALPHA = 1<<7,
|
||||
NVG_DST_ALPHA = 1<<8,
|
||||
NVG_ONE_MINUS_DST_ALPHA = 1<<9,
|
||||
NVG_SRC_ALPHA_SATURATE = 1<<10,
|
||||
};
|
||||
|
||||
enum NVGcompositeOperation {
|
||||
NVG_SOURCE_OVER,
|
||||
NVG_SOURCE_IN,
|
||||
NVG_SOURCE_OUT,
|
||||
NVG_ATOP,
|
||||
NVG_DESTINATION_OVER,
|
||||
NVG_DESTINATION_IN,
|
||||
NVG_DESTINATION_OUT,
|
||||
NVG_DESTINATION_ATOP,
|
||||
NVG_LIGHTER,
|
||||
NVG_COPY,
|
||||
NVG_XOR,
|
||||
};
|
||||
|
||||
struct NVGcompositeOperationState {
|
||||
int srcRGB;
|
||||
int dstRGB;
|
||||
int srcAlpha;
|
||||
int dstAlpha;
|
||||
};
|
||||
typedef struct NVGcompositeOperationState NVGcompositeOperationState;
|
||||
|
||||
struct NVGglyphPosition {
|
||||
const char* str; // Position of the glyph in the input string.
|
||||
float x; // The x-coordinate of the logical glyph position.
|
||||
float minx, maxx; // The bounds of the glyph shape.
|
||||
};
|
||||
typedef struct NVGglyphPosition NVGglyphPosition;
|
||||
|
||||
struct NVGtextRow {
|
||||
const char* start; // Pointer to the input text where the row starts.
|
||||
const char* end; // Pointer to the input text where the row ends (one past the last character).
|
||||
const char* next; // Pointer to the beginning of the next row.
|
||||
float width; // Logical width of the row.
|
||||
float minx, maxx; // Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.
|
||||
};
|
||||
typedef struct NVGtextRow NVGtextRow;
|
||||
|
||||
enum NVGimageFlags {
|
||||
NVG_IMAGE_GENERATE_MIPMAPS = 1<<0, // Generate mipmaps during creation of the image.
|
||||
NVG_IMAGE_REPEATX = 1<<1, // Repeat image in X direction.
|
||||
NVG_IMAGE_REPEATY = 1<<2, // Repeat image in Y direction.
|
||||
NVG_IMAGE_FLIPY = 1<<3, // Flips (inverses) image in Y direction when rendered.
|
||||
NVG_IMAGE_PREMULTIPLIED = 1<<4, // Image data has premultiplied alpha.
|
||||
NVG_IMAGE_NEAREST = 1<<5, // Image interpolation is Nearest instead Linear
|
||||
};
|
||||
|
||||
// Begin drawing a new frame
|
||||
// Calls to nanovg drawing API should be wrapped in nvgBeginFrame() & nvgEndFrame()
|
||||
// nvgBeginFrame() defines the size of the window to render to in relation currently
|
||||
// set viewport (i.e. glViewport on GL backends). Device pixel ration allows to
|
||||
// control the rendering on Hi-DPI devices.
|
||||
// For example, GLFW returns two dimension for an opened window: window size and
|
||||
// frame buffer size. In that case you would set windowWidth/Height to the window size
|
||||
// devicePixelRatio to: frameBufferWidth / windowWidth.
|
||||
void nvgBeginFrame(NVGcontext* ctx, float windowWidth, float windowHeight, float devicePixelRatio);
|
||||
|
||||
// Cancels drawing the current frame.
|
||||
void nvgCancelFrame(NVGcontext* ctx);
|
||||
|
||||
// Ends drawing flushing remaining render state.
|
||||
void nvgEndFrame(NVGcontext* ctx);
|
||||
|
||||
//
|
||||
// Composite operation
|
||||
//
|
||||
// The composite operations in NanoVG are modeled after HTML Canvas API, and
|
||||
// the blend func is based on OpenGL (see corresponding manuals for more info).
|
||||
// The colors in the blending state have premultiplied alpha.
|
||||
|
||||
// Sets the composite operation. The op parameter should be one of NVGcompositeOperation.
|
||||
void nvgGlobalCompositeOperation(NVGcontext* ctx, int op);
|
||||
|
||||
// Sets the composite operation with custom pixel arithmetic. The parameters should be one of NVGblendFactor.
|
||||
void nvgGlobalCompositeBlendFunc(NVGcontext* ctx, int sfactor, int dfactor);
|
||||
|
||||
// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. The parameters should be one of NVGblendFactor.
|
||||
void nvgGlobalCompositeBlendFuncSeparate(NVGcontext* ctx, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha);
|
||||
|
||||
//
|
||||
// Color utils
|
||||
//
|
||||
// Colors in NanoVG are stored as unsigned ints in ABGR format.
|
||||
|
||||
// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f).
|
||||
NVGcolor nvgRGB(unsigned char r, unsigned char g, unsigned char b);
|
||||
|
||||
// Returns a color value from red, green, blue values. Alpha will be set to 1.0f.
|
||||
NVGcolor nvgRGBf(float r, float g, float b);
|
||||
|
||||
|
||||
// Returns a color value from red, green, blue and alpha values.
|
||||
NVGcolor nvgRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
|
||||
|
||||
// Returns a color value from red, green, blue and alpha values.
|
||||
NVGcolor nvgRGBAf(float r, float g, float b, float a);
|
||||
|
||||
|
||||
// Linearly interpolates from color c0 to c1, and returns resulting color value.
|
||||
NVGcolor nvgLerpRGBA(NVGcolor c0, NVGcolor c1, float u);
|
||||
|
||||
// Sets transparency of a color value.
|
||||
NVGcolor nvgTransRGBA(NVGcolor c0, unsigned char a);
|
||||
|
||||
// Sets transparency of a color value.
|
||||
NVGcolor nvgTransRGBAf(NVGcolor c0, float a);
|
||||
|
||||
// Returns color value specified by hue, saturation and lightness.
|
||||
// HSL values are all in range [0..1], alpha will be set to 255.
|
||||
NVGcolor nvgHSL(float h, float s, float l);
|
||||
|
||||
// Returns color value specified by hue, saturation and lightness and alpha.
|
||||
// HSL values are all in range [0..1], alpha in range [0..255]
|
||||
NVGcolor nvgHSLA(float h, float s, float l, unsigned char a);
|
||||
|
||||
//
|
||||
// State Handling
|
||||
//
|
||||
// NanoVG contains state which represents how paths will be rendered.
|
||||
// The state contains transform, fill and stroke styles, text and font styles,
|
||||
// and scissor clipping.
|
||||
|
||||
// Pushes and saves the current render state into a state stack.
|
||||
// A matching nvgRestore() must be used to restore the state.
|
||||
void nvgSave(NVGcontext* ctx);
|
||||
|
||||
// Pops and restores current render state.
|
||||
void nvgRestore(NVGcontext* ctx);
|
||||
|
||||
// Resets current render state to default values. Does not affect the render state stack.
|
||||
void nvgReset(NVGcontext* ctx);
|
||||
|
||||
//
|
||||
// Render styles
|
||||
//
|
||||
// Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern.
|
||||
// Solid color is simply defined as a color value, different kinds of paints can be created
|
||||
// using nvgLinearGradient(), nvgBoxGradient(), nvgRadialGradient() and nvgImagePattern().
|
||||
//
|
||||
// Current render style can be saved and restored using nvgSave() and nvgRestore().
|
||||
|
||||
// Sets whether to draw antialias for nvgStroke() and nvgFill(). It's enabled by default.
|
||||
void nvgShapeAntiAlias(NVGcontext* ctx, int enabled);
|
||||
|
||||
// Sets current stroke style to a solid color.
|
||||
void nvgStrokeColor(NVGcontext* ctx, NVGcolor color);
|
||||
|
||||
// Sets current stroke style to a paint, which can be a one of the gradients or a pattern.
|
||||
void nvgStrokePaint(NVGcontext* ctx, NVGpaint paint);
|
||||
|
||||
// Sets current fill style to a solid color.
|
||||
void nvgFillColor(NVGcontext* ctx, NVGcolor color);
|
||||
|
||||
// Sets current fill style to a paint, which can be a one of the gradients or a pattern.
|
||||
void nvgFillPaint(NVGcontext* ctx, NVGpaint paint);
|
||||
|
||||
// Sets the miter limit of the stroke style.
|
||||
// Miter limit controls when a sharp corner is beveled.
|
||||
void nvgMiterLimit(NVGcontext* ctx, float limit);
|
||||
|
||||
// Sets the stroke width of the stroke style.
|
||||
void nvgStrokeWidth(NVGcontext* ctx, float size);
|
||||
|
||||
// Sets how the end of the line (cap) is drawn,
|
||||
// Can be one of: NVG_BUTT (default), NVG_ROUND, NVG_SQUARE.
|
||||
void nvgLineCap(NVGcontext* ctx, int cap);
|
||||
|
||||
// Sets how sharp path corners are drawn.
|
||||
// Can be one of NVG_MITER (default), NVG_ROUND, NVG_BEVEL.
|
||||
void nvgLineJoin(NVGcontext* ctx, int join);
|
||||
|
||||
// Sets the transparency applied to all rendered shapes.
|
||||
// Already transparent paths will get proportionally more transparent as well.
|
||||
void nvgGlobalAlpha(NVGcontext* ctx, float alpha);
|
||||
|
||||
//
|
||||
// Transforms
|
||||
//
|
||||
// The paths, gradients, patterns and scissor region are transformed by an transformation
|
||||
// matrix at the time when they are passed to the API.
|
||||
// The current transformation matrix is a affine matrix:
|
||||
// [sx kx tx]
|
||||
// [ky sy ty]
|
||||
// [ 0 0 1]
|
||||
// Where: sx,sy define scaling, kx,ky skewing, and tx,ty translation.
|
||||
// The last row is assumed to be 0,0,1 and is not stored.
|
||||
//
|
||||
// Apart from nvgResetTransform(), each transformation function first creates
|
||||
// specific transformation matrix and pre-multiplies the current transformation by it.
|
||||
//
|
||||
// Current coordinate system (transformation) can be saved and restored using nvgSave() and nvgRestore().
|
||||
|
||||
// Resets current transform to a identity matrix.
|
||||
void nvgResetTransform(NVGcontext* ctx);
|
||||
|
||||
// Premultiplies current coordinate system by specified matrix.
|
||||
// The parameters are interpreted as matrix as follows:
|
||||
// [a c e]
|
||||
// [b d f]
|
||||
// [0 0 1]
|
||||
void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f);
|
||||
|
||||
// Translates current coordinate system.
|
||||
void nvgTranslate(NVGcontext* ctx, float x, float y);
|
||||
|
||||
// Rotates current coordinate system. Angle is specified in radians.
|
||||
void nvgRotate(NVGcontext* ctx, float angle);
|
||||
|
||||
// Skews the current coordinate system along X axis. Angle is specified in radians.
|
||||
void nvgSkewX(NVGcontext* ctx, float angle);
|
||||
|
||||
// Skews the current coordinate system along Y axis. Angle is specified in radians.
|
||||
void nvgSkewY(NVGcontext* ctx, float angle);
|
||||
|
||||
// Scales the current coordinate system.
|
||||
void nvgScale(NVGcontext* ctx, float x, float y);
|
||||
|
||||
// Stores the top part (a-f) of the current transformation matrix in to the specified buffer.
|
||||
// [a c e]
|
||||
// [b d f]
|
||||
// [0 0 1]
|
||||
// There should be space for 6 floats in the return buffer for the values a-f.
|
||||
void nvgCurrentTransform(NVGcontext* ctx, float* xform);
|
||||
|
||||
|
||||
// The following functions can be used to make calculations on 2x3 transformation matrices.
|
||||
// A 2x3 matrix is represented as float[6].
|
||||
|
||||
// Sets the transform to identity matrix.
|
||||
void nvgTransformIdentity(float* dst);
|
||||
|
||||
// Sets the transform to translation matrix matrix.
|
||||
void nvgTransformTranslate(float* dst, float tx, float ty);
|
||||
|
||||
// Sets the transform to scale matrix.
|
||||
void nvgTransformScale(float* dst, float sx, float sy);
|
||||
|
||||
// Sets the transform to rotate matrix. Angle is specified in radians.
|
||||
void nvgTransformRotate(float* dst, float a);
|
||||
|
||||
// Sets the transform to skew-x matrix. Angle is specified in radians.
|
||||
void nvgTransformSkewX(float* dst, float a);
|
||||
|
||||
// Sets the transform to skew-y matrix. Angle is specified in radians.
|
||||
void nvgTransformSkewY(float* dst, float a);
|
||||
|
||||
// Sets the transform to the result of multiplication of two transforms, of A = A*B.
|
||||
void nvgTransformMultiply(float* dst, const float* src);
|
||||
|
||||
// Sets the transform to the result of multiplication of two transforms, of A = B*A.
|
||||
void nvgTransformPremultiply(float* dst, const float* src);
|
||||
|
||||
// Sets the destination to inverse of specified transform.
|
||||
// Returns 1 if the inverse could be calculated, else 0.
|
||||
int nvgTransformInverse(float* dst, const float* src);
|
||||
|
||||
// Transform a point by given transform.
|
||||
void nvgTransformPoint(float* dstx, float* dsty, const float* xform, float srcx, float srcy);
|
||||
|
||||
// Converts degrees to radians and vice versa.
|
||||
float nvgDegToRad(float deg);
|
||||
float nvgRadToDeg(float rad);
|
||||
|
||||
//
|
||||
// Images
|
||||
//
|
||||
// NanoVG allows you to load jpg, png, psd, tga, pic and gif files to be used for rendering.
|
||||
// In addition you can upload your own image. The image loading is provided by stb_image.
|
||||
// The parameter imageFlags is combination of flags defined in NVGimageFlags.
|
||||
|
||||
// Creates image by loading it from the disk from specified file name.
|
||||
// Returns handle to the image.
|
||||
int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags);
|
||||
|
||||
// Creates image by loading it from the specified chunk of memory.
|
||||
// Returns handle to the image.
|
||||
int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata);
|
||||
|
||||
// Creates image from specified image data.
|
||||
// Returns handle to the image.
|
||||
int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data);
|
||||
|
||||
// Updates image data specified by image handle.
|
||||
void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data);
|
||||
|
||||
// Returns the dimensions of a created image.
|
||||
void nvgImageSize(NVGcontext* ctx, int image, int* w, int* h);
|
||||
|
||||
// Deletes created image.
|
||||
void nvgDeleteImage(NVGcontext* ctx, int image);
|
||||
|
||||
//
|
||||
// Paints
|
||||
//
|
||||
// NanoVG supports four types of paints: linear gradient, box gradient, radial gradient and image pattern.
|
||||
// These can be used as paints for strokes and fills.
|
||||
|
||||
// Creates and returns a linear gradient. Parameters (sx,sy)-(ex,ey) specify the start and end coordinates
|
||||
// of the linear gradient, icol specifies the start color and ocol the end color.
|
||||
// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
|
||||
NVGpaint nvgLinearGradient(NVGcontext* ctx, float sx, float sy, float ex, float ey,
|
||||
NVGcolor icol, NVGcolor ocol);
|
||||
|
||||
// Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering
|
||||
// drop shadows or highlights for boxes. Parameters (x,y) define the top-left corner of the rectangle,
|
||||
// (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
|
||||
// the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient.
|
||||
// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
|
||||
NVGpaint nvgBoxGradient(NVGcontext* ctx, float x, float y, float w, float h,
|
||||
float r, float f, NVGcolor icol, NVGcolor ocol);
|
||||
|
||||
// Creates and returns a radial gradient. Parameters (cx,cy) specify the center, inr and outr specify
|
||||
// the inner and outer radius of the gradient, icol specifies the start color and ocol the end color.
|
||||
// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
|
||||
NVGpaint nvgRadialGradient(NVGcontext* ctx, float cx, float cy, float inr, float outr,
|
||||
NVGcolor icol, NVGcolor ocol);
|
||||
|
||||
// Creates and returns an image patter. Parameters (ox,oy) specify the left-top location of the image pattern,
|
||||
// (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render.
|
||||
// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint().
|
||||
NVGpaint nvgImagePattern(NVGcontext* ctx, float ox, float oy, float ex, float ey,
|
||||
float angle, int image, float alpha);
|
||||
|
||||
//
|
||||
// Scissoring
|
||||
//
|
||||
// Scissoring allows you to clip the rendering into a rectangle. This is useful for various
|
||||
// user interface cases like rendering a text edit or a timeline.
|
||||
|
||||
// Sets the current scissor rectangle.
|
||||
// The scissor rectangle is transformed by the current transform.
|
||||
void nvgScissor(NVGcontext* ctx, float x, float y, float w, float h);
|
||||
|
||||
// Intersects current scissor rectangle with the specified rectangle.
|
||||
// The scissor rectangle is transformed by the current transform.
|
||||
// Note: in case the rotation of previous scissor rect differs from
|
||||
// the current one, the intersection will be done between the specified
|
||||
// rectangle and the previous scissor rectangle transformed in the current
|
||||
// transform space. The resulting shape is always rectangle.
|
||||
void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h);
|
||||
|
||||
// Reset and disables scissoring.
|
||||
void nvgResetScissor(NVGcontext* ctx);
|
||||
|
||||
//
|
||||
// Paths
|
||||
//
|
||||
// Drawing a new shape starts with nvgBeginPath(), it clears all the currently defined paths.
|
||||
// Then you define one or more paths and sub-paths which describe the shape. The are functions
|
||||
// to draw common shapes like rectangles and circles, and lower level step-by-step functions,
|
||||
// which allow to define a path curve by curve.
|
||||
//
|
||||
// NanoVG uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise
|
||||
// winding and holes should have counter clockwise order. To specify winding of a path you can
|
||||
// call nvgPathWinding(). This is useful especially for the common shapes, which are drawn CCW.
|
||||
//
|
||||
// Finally you can fill the path using current fill style by calling nvgFill(), and stroke it
|
||||
// with current stroke style by calling nvgStroke().
|
||||
//
|
||||
// The curve segments and sub-paths are transformed by the current transform.
|
||||
|
||||
// Clears the current path and sub-paths.
|
||||
void nvgBeginPath(NVGcontext* ctx);
|
||||
|
||||
// Starts new sub-path with specified point as first point.
|
||||
void nvgMoveTo(NVGcontext* ctx, float x, float y);
|
||||
|
||||
// Adds line segment from the last point in the path to the specified point.
|
||||
void nvgLineTo(NVGcontext* ctx, float x, float y);
|
||||
|
||||
// Adds cubic bezier segment from last point in the path via two control points to the specified point.
|
||||
void nvgBezierTo(NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y);
|
||||
|
||||
// Adds quadratic bezier segment from last point in the path via a control point to the specified point.
|
||||
void nvgQuadTo(NVGcontext* ctx, float cx, float cy, float x, float y);
|
||||
|
||||
// Adds an arc segment at the corner defined by the last path point, and two specified points.
|
||||
void nvgArcTo(NVGcontext* ctx, float x1, float y1, float x2, float y2, float radius);
|
||||
|
||||
// Closes current sub-path with a line segment.
|
||||
void nvgClosePath(NVGcontext* ctx);
|
||||
|
||||
// Sets the current sub-path winding, see NVGwinding and NVGsolidity.
|
||||
void nvgPathWinding(NVGcontext* ctx, int dir);
|
||||
|
||||
// Creates new circle arc shaped sub-path. The arc center is at cx,cy, the arc radius is r,
|
||||
// and the arc is drawn from angle a0 to a1, and swept in direction dir (NVG_CCW, or NVG_CW).
|
||||
// Angles are specified in radians.
|
||||
void nvgArc(NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, int dir);
|
||||
|
||||
// Creates new rectangle shaped sub-path.
|
||||
void nvgRect(NVGcontext* ctx, float x, float y, float w, float h);
|
||||
|
||||
// Creates new rounded rectangle shaped sub-path.
|
||||
void nvgRoundedRect(NVGcontext* ctx, float x, float y, float w, float h, float r);
|
||||
|
||||
// Creates new rounded rectangle shaped sub-path with varying radii for each corner.
|
||||
void nvgRoundedRectVarying(NVGcontext* ctx, float x, float y, float w, float h, float radTopLeft, float radTopRight, float radBottomRight, float radBottomLeft);
|
||||
|
||||
// Creates new ellipse shaped sub-path.
|
||||
void nvgEllipse(NVGcontext* ctx, float cx, float cy, float rx, float ry);
|
||||
|
||||
// Creates new circle shaped sub-path.
|
||||
void nvgCircle(NVGcontext* ctx, float cx, float cy, float r);
|
||||
|
||||
// Fills the current path with current fill style.
|
||||
void nvgFill(NVGcontext* ctx);
|
||||
|
||||
// Fills the current path with current stroke style.
|
||||
void nvgStroke(NVGcontext* ctx);
|
||||
|
||||
|
||||
//
|
||||
// Text
|
||||
//
|
||||
// NanoVG allows you to load .ttf files and use the font to render text.
|
||||
//
|
||||
// The appearance of the text can be defined by setting the current text style
|
||||
// and by specifying the fill color. Common text and font settings such as
|
||||
// font size, letter spacing and text align are supported. Font blur allows you
|
||||
// to create simple text effects such as drop shadows.
|
||||
//
|
||||
// At render time the font face can be set based on the font handles or name.
|
||||
//
|
||||
// Font measure functions return values in local space, the calculations are
|
||||
// carried in the same resolution as the final rendering. This is done because
|
||||
// the text glyph positions are snapped to the nearest pixels sharp rendering.
|
||||
//
|
||||
// The local space means that values are not rotated or scale as per the current
|
||||
// transformation. For example if you set font size to 12, which would mean that
|
||||
// line height is 16, then regardless of the current scaling and rotation, the
|
||||
// returned line height is always 16. Some measures may vary because of the scaling
|
||||
// since aforementioned pixel snapping.
|
||||
//
|
||||
// While this may sound a little odd, the setup allows you to always render the
|
||||
// same way regardless of scaling. I.e. following works regardless of scaling:
|
||||
//
|
||||
// const char* txt = "Text me up.";
|
||||
// nvgTextBounds(vg, x,y, txt, NULL, bounds);
|
||||
// nvgBeginPath(vg);
|
||||
// nvgRoundedRect(vg, bounds[0],bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]);
|
||||
// nvgFill(vg);
|
||||
//
|
||||
// Note: currently only solid color fill is supported for text.
|
||||
|
||||
// Creates font by loading it from the disk from specified file name.
|
||||
// Returns handle to the font.
|
||||
int nvgCreateFont(NVGcontext* ctx, const char* name, const char* filename);
|
||||
|
||||
// fontIndex specifies which font face to load from a .ttf/.ttc file.
|
||||
int nvgCreateFontAtIndex(NVGcontext* ctx, const char* name, const char* filename, const int fontIndex);
|
||||
|
||||
// Creates font by loading it from the specified memory chunk.
|
||||
// Returns handle to the font.
|
||||
int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData);
|
||||
|
||||
// fontIndex specifies which font face to load from a .ttf/.ttc file.
|
||||
int nvgCreateFontMemAtIndex(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData, const int fontIndex);
|
||||
|
||||
// Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found.
|
||||
int nvgFindFont(NVGcontext* ctx, const char* name);
|
||||
|
||||
// Adds a fallback font by handle.
|
||||
int nvgAddFallbackFontId(NVGcontext* ctx, int baseFont, int fallbackFont);
|
||||
|
||||
// Adds a fallback font by name.
|
||||
int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallbackFont);
|
||||
|
||||
// Resets fallback fonts by handle.
|
||||
void nvgResetFallbackFontsId(NVGcontext* ctx, int baseFont);
|
||||
|
||||
// Resets fallback fonts by name.
|
||||
void nvgResetFallbackFonts(NVGcontext* ctx, const char* baseFont);
|
||||
|
||||
// Sets the font size of current text style.
|
||||
void nvgFontSize(NVGcontext* ctx, float size);
|
||||
|
||||
// Sets the blur of current text style.
|
||||
void nvgFontBlur(NVGcontext* ctx, float blur);
|
||||
|
||||
// Sets the letter spacing of current text style.
|
||||
void nvgTextLetterSpacing(NVGcontext* ctx, float spacing);
|
||||
|
||||
// Sets the proportional line height of current text style. The line height is specified as multiple of font size.
|
||||
void nvgTextLineHeight(NVGcontext* ctx, float lineHeight);
|
||||
|
||||
// Sets the text align of current text style, see NVGalign for options.
|
||||
void nvgTextAlign(NVGcontext* ctx, int align);
|
||||
|
||||
// Sets the font face based on specified id of current text style.
|
||||
void nvgFontFaceId(NVGcontext* ctx, int font);
|
||||
|
||||
// Sets the font face based on specified name of current text style.
|
||||
void nvgFontFace(NVGcontext* ctx, const char* font);
|
||||
|
||||
// Draws text string at specified location. If end is specified only the sub-string up to the end is drawn.
|
||||
float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end);
|
||||
|
||||
// Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn.
|
||||
// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
|
||||
// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
|
||||
void nvgTextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end);
|
||||
|
||||
// Measures the specified text string. Parameter bounds should be a pointer to float[4],
|
||||
// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]
|
||||
// Returns the horizontal advance of the measured text (i.e. where the next character should drawn).
|
||||
// Measured values are returned in local coordinate space.
|
||||
float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds);
|
||||
|
||||
// Measures the specified multi-text string. Parameter bounds should be a pointer to float[4],
|
||||
// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]
|
||||
// Measured values are returned in local coordinate space.
|
||||
void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds);
|
||||
|
||||
// Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used.
|
||||
// Measured values are returned in local coordinate space.
|
||||
int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, const char* end, NVGglyphPosition* positions, int maxPositions);
|
||||
|
||||
// Returns the vertical metrics based on the current text style.
|
||||
// Measured values are returned in local coordinate space.
|
||||
void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* lineh);
|
||||
|
||||
// Breaks the specified text into lines. If end is specified only the sub-string will be used.
|
||||
// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.
|
||||
// Words longer than the max width are slit at nearest character (i.e. no hyphenation).
|
||||
int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows);
|
||||
|
||||
//
|
||||
// Internal Render API
|
||||
//
|
||||
enum NVGtexture {
|
||||
NVG_TEXTURE_ALPHA = 0x01,
|
||||
NVG_TEXTURE_RGBA = 0x02,
|
||||
};
|
||||
|
||||
struct NVGscissor {
|
||||
float xform[6];
|
||||
float extent[2];
|
||||
};
|
||||
typedef struct NVGscissor NVGscissor;
|
||||
|
||||
struct NVGvertex {
|
||||
float x,y,u,v;
|
||||
};
|
||||
typedef struct NVGvertex NVGvertex;
|
||||
|
||||
struct NVGpath {
|
||||
int first;
|
||||
int count;
|
||||
unsigned char closed;
|
||||
int nbevel;
|
||||
NVGvertex* fill;
|
||||
int nfill;
|
||||
NVGvertex* stroke;
|
||||
int nstroke;
|
||||
int winding;
|
||||
int convex;
|
||||
};
|
||||
typedef struct NVGpath NVGpath;
|
||||
|
||||
struct NVGparams {
|
||||
void* userPtr;
|
||||
int edgeAntiAlias;
|
||||
int (*renderCreate)(void* uptr);
|
||||
int (*renderCreateTexture)(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data);
|
||||
int (*renderDeleteTexture)(void* uptr, int image);
|
||||
int (*renderUpdateTexture)(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data);
|
||||
int (*renderGetTextureSize)(void* uptr, int image, int* w, int* h);
|
||||
void (*renderViewport)(void* uptr, float width, float height, float devicePixelRatio);
|
||||
void (*renderCancel)(void* uptr);
|
||||
void (*renderFlush)(void* uptr);
|
||||
void (*renderFill)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, const float* bounds, const NVGpath* paths, int npaths);
|
||||
void (*renderStroke)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, float strokeWidth, const NVGpath* paths, int npaths);
|
||||
void (*renderTriangles)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, const NVGvertex* verts, int nverts, float fringe);
|
||||
void (*renderDelete)(void* uptr);
|
||||
};
|
||||
typedef struct NVGparams NVGparams;
|
||||
|
||||
// Constructor and destructor, called by the render back-end.
|
||||
NVGcontext* nvgCreateInternal(NVGparams* params);
|
||||
void nvgDeleteInternal(NVGcontext* ctx);
|
||||
|
||||
NVGparams* nvgInternalParams(NVGcontext* ctx);
|
||||
|
||||
// Debug function to dump cached path data.
|
||||
void nvgDebugDumpPathCache(NVGcontext* ctx);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#define NVG_NOTUSED(v) for (;;) { (void)(1 ? (void)0 : ( (void)(v) ) ); break; }
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // NANOVG_H
|
||||
@@ -1,208 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <deko3d.hpp>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
#include "framework/CDescriptorSet.h"
|
||||
#include "framework/CMemPool.h"
|
||||
#include "framework/CShader.h"
|
||||
#include "framework/CCmdMemRing.h"
|
||||
#include "nanovg.h"
|
||||
|
||||
// Create flags
|
||||
enum NVGcreateFlags {
|
||||
// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA).
|
||||
NVG_ANTIALIAS = 1<<0,
|
||||
// Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little
|
||||
// slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once.
|
||||
NVG_STENCIL_STROKES = 1<<1,
|
||||
// Flag indicating that additional debug checks are done.
|
||||
NVG_DEBUG = 1<<2,
|
||||
};
|
||||
|
||||
enum DKNVGuniformLoc
|
||||
{
|
||||
DKNVG_LOC_VIEWSIZE,
|
||||
DKNVG_LOC_TEX,
|
||||
DKNVG_LOC_FRAG,
|
||||
DKNVG_MAX_LOCS
|
||||
};
|
||||
|
||||
enum VKNVGshaderType {
|
||||
NSVG_SHADER_FILLGRAD,
|
||||
NSVG_SHADER_FILLIMG,
|
||||
NSVG_SHADER_SIMPLE,
|
||||
NSVG_SHADER_IMG
|
||||
};
|
||||
|
||||
struct DKNVGtextureDescriptor {
|
||||
int width, height;
|
||||
int type;
|
||||
int flags;
|
||||
};
|
||||
|
||||
struct DKNVGblend {
|
||||
int srcRGB;
|
||||
int dstRGB;
|
||||
int srcAlpha;
|
||||
int dstAlpha;
|
||||
};
|
||||
|
||||
enum DKNVGcallType {
|
||||
DKNVG_NONE = 0,
|
||||
DKNVG_FILL,
|
||||
DKNVG_CONVEXFILL,
|
||||
DKNVG_STROKE,
|
||||
DKNVG_TRIANGLES,
|
||||
};
|
||||
|
||||
struct DKNVGcall {
|
||||
int type;
|
||||
int image;
|
||||
int pathOffset;
|
||||
int pathCount;
|
||||
int triangleOffset;
|
||||
int triangleCount;
|
||||
int uniformOffset;
|
||||
DKNVGblend blendFunc;
|
||||
};
|
||||
|
||||
struct DKNVGpath {
|
||||
int fillOffset;
|
||||
int fillCount;
|
||||
int strokeOffset;
|
||||
int strokeCount;
|
||||
};
|
||||
|
||||
struct DKNVGfragUniforms {
|
||||
float scissorMat[12]; // matrices are actually 3 vec4s
|
||||
float paintMat[12];
|
||||
struct NVGcolor innerCol;
|
||||
struct NVGcolor outerCol;
|
||||
float scissorExt[2];
|
||||
float scissorScale[2];
|
||||
float extent[2];
|
||||
float radius;
|
||||
float feather;
|
||||
float strokeMult;
|
||||
float strokeThr;
|
||||
int texType;
|
||||
int type;
|
||||
};
|
||||
|
||||
namespace nvg {
|
||||
class DkRenderer;
|
||||
}
|
||||
|
||||
struct DKNVGcontext {
|
||||
nvg::DkRenderer *renderer;
|
||||
float view[2];
|
||||
int fragSize;
|
||||
int flags;
|
||||
// Per frame buffers
|
||||
DKNVGcall* calls;
|
||||
int ccalls;
|
||||
int ncalls;
|
||||
DKNVGpath* paths;
|
||||
int cpaths;
|
||||
int npaths;
|
||||
struct NVGvertex* verts;
|
||||
int cverts;
|
||||
int nverts;
|
||||
unsigned char* uniforms;
|
||||
int cuniforms;
|
||||
int nuniforms;
|
||||
};
|
||||
|
||||
namespace nvg {
|
||||
|
||||
class Texture {
|
||||
private:
|
||||
const int m_id;
|
||||
dk::Image m_image;
|
||||
dk::ImageDescriptor m_image_descriptor;
|
||||
CMemPool::Handle m_image_mem;
|
||||
DKNVGtextureDescriptor m_texture_descriptor;
|
||||
public:
|
||||
Texture(int id);
|
||||
~Texture();
|
||||
|
||||
void Initialize(CMemPool &image_pool, CMemPool &scratch_pool, dk::Device device, dk::Queue transfer_queue, int type, int w, int h, int image_flags, const u8 *data);
|
||||
void Update(CMemPool &image_pool, CMemPool &scratch_pool, dk::Device device, dk::Queue transfer_queue, int type, int w, int h, int image_flags, const u8 *data);
|
||||
|
||||
int GetId();
|
||||
const DKNVGtextureDescriptor &GetDescriptor();
|
||||
|
||||
dk::Image &GetImage();
|
||||
dk::ImageDescriptor &GetImageDescriptor();
|
||||
};
|
||||
|
||||
class DkRenderer {
|
||||
private:
|
||||
enum SamplerType : u8 {
|
||||
SamplerType_MipFilter = 1 << 0,
|
||||
SamplerType_Nearest = 1 << 1,
|
||||
SamplerType_RepeatX = 1 << 2,
|
||||
SamplerType_RepeatY = 1 << 3,
|
||||
SamplerType_Total = 0x10,
|
||||
};
|
||||
private:
|
||||
static constexpr size_t DynamicCmdSize = 0x20000;
|
||||
static constexpr size_t FragmentUniformSize = sizeof(DKNVGfragUniforms) + 4 - sizeof(DKNVGfragUniforms) % 4;
|
||||
static constexpr size_t MaxImages = 0x1000;
|
||||
|
||||
/* From the application. */
|
||||
u32 m_view_width;
|
||||
u32 m_view_height;
|
||||
dk::Device m_device;
|
||||
dk::Queue m_queue;
|
||||
CMemPool &m_image_mem_pool;
|
||||
CMemPool &m_code_mem_pool;
|
||||
CMemPool &m_data_mem_pool;
|
||||
|
||||
/* State. */
|
||||
dk::UniqueCmdBuf m_dyn_cmd_buf;
|
||||
CCmdMemRing<1> m_dyn_cmd_mem;
|
||||
std::optional<CMemPool::Handle> m_vertex_buffer;
|
||||
CShader m_vertex_shader;
|
||||
CShader m_fragment_shader;
|
||||
CMemPool::Handle m_view_uniform_buffer;
|
||||
CMemPool::Handle m_frag_uniform_buffer;
|
||||
|
||||
u32 m_next_texture_id = 1;
|
||||
std::vector<std::shared_ptr<Texture>> m_textures;
|
||||
CDescriptorSet<MaxImages> m_image_descriptor_set;
|
||||
CDescriptorSet<SamplerType_Total> m_sampler_descriptor_set;
|
||||
std::array<int, MaxImages> m_image_descriptor_mappings;
|
||||
int m_last_image_descriptor = 0;
|
||||
|
||||
int AcquireImageDescriptor(std::shared_ptr<Texture> texture, int image);
|
||||
void FreeImageDescriptor(int image);
|
||||
void SetUniforms(const DKNVGcontext &ctx, int offset, int image);
|
||||
|
||||
void UpdateVertexBuffer(const void *data, size_t size);
|
||||
|
||||
void DrawFill(const DKNVGcontext &ctx, const DKNVGcall &call);
|
||||
void DrawConvexFill(const DKNVGcontext &ctx, const DKNVGcall &call);
|
||||
void DrawStroke(const DKNVGcontext &ctx, const DKNVGcall &call);
|
||||
void DrawTriangles(const DKNVGcontext &ctx, const DKNVGcall &call);
|
||||
|
||||
std::shared_ptr<Texture> FindTexture(int id);
|
||||
public:
|
||||
DkRenderer(unsigned int view_width, unsigned int view_height, dk::Device device, dk::Queue queue, CMemPool &image_mem_pool, CMemPool &code_mem_pool, CMemPool &data_mem_pool);
|
||||
~DkRenderer();
|
||||
|
||||
int Create(DKNVGcontext &ctx);
|
||||
int CreateTexture(const DKNVGcontext &ctx, int type, int w, int h, int image_flags, const u8 *data);
|
||||
int DeleteTexture(const DKNVGcontext &ctx, int id);
|
||||
int UpdateTexture(const DKNVGcontext &ctx, int id, int x, int y, int w, int h, const u8 *data);
|
||||
int GetTextureSize(const DKNVGcontext &ctx, int id, int *w, int *h);
|
||||
const DKNVGtextureDescriptor *GetTextureDescriptor(const DKNVGcontext &ctx, int id);
|
||||
|
||||
void Flush(DKNVGcontext &ctx);
|
||||
};
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** CApplication.h: Wrapper class containing common application boilerplate
|
||||
*/
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
class CApplication
|
||||
{
|
||||
protected:
|
||||
virtual void onFocusState(AppletFocusState) { }
|
||||
virtual void onOperationMode(AppletOperationMode) { }
|
||||
virtual bool onFrame(u64) { return true; }
|
||||
|
||||
public:
|
||||
CApplication();
|
||||
~CApplication();
|
||||
|
||||
void run();
|
||||
|
||||
static constexpr void chooseFramebufferSize(uint32_t& width, uint32_t& height, AppletOperationMode mode);
|
||||
};
|
||||
|
||||
constexpr void CApplication::chooseFramebufferSize(uint32_t& width, uint32_t& height, AppletOperationMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
default:
|
||||
case AppletOperationMode_Handheld:
|
||||
width = 1280;
|
||||
height = 720;
|
||||
break;
|
||||
case AppletOperationMode_Console:
|
||||
width = 1920;
|
||||
height = 1080;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** CCmdMemRing.h: Memory provider class for dynamic command buffers
|
||||
*/
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "CMemPool.h"
|
||||
|
||||
template <unsigned NumSlices>
|
||||
class CCmdMemRing
|
||||
{
|
||||
static_assert(NumSlices > 0, "Need a non-zero number of slices...");
|
||||
CMemPool::Handle m_mem;
|
||||
unsigned m_curSlice;
|
||||
dk::Fence m_fences[NumSlices];
|
||||
public:
|
||||
CCmdMemRing() : m_mem{}, m_curSlice{}, m_fences{} { }
|
||||
~CCmdMemRing()
|
||||
{
|
||||
m_mem.destroy();
|
||||
}
|
||||
|
||||
bool allocate(CMemPool& pool, uint32_t sliceSize)
|
||||
{
|
||||
sliceSize = (sliceSize + DK_CMDMEM_ALIGNMENT - 1) &~ (DK_CMDMEM_ALIGNMENT - 1);
|
||||
m_mem = pool.allocate(NumSlices*sliceSize);
|
||||
return m_mem;
|
||||
}
|
||||
|
||||
void begin(dk::CmdBuf cmdbuf)
|
||||
{
|
||||
// Clear/reset the command buffer, which also destroys all command list handles
|
||||
// (but remember: it does *not* in fact destroy the command data)
|
||||
cmdbuf.clear();
|
||||
|
||||
// Wait for the current slice of memory to be available, and feed it to the command buffer
|
||||
uint32_t sliceSize = m_mem.getSize() / NumSlices;
|
||||
m_fences[m_curSlice].wait();
|
||||
|
||||
// Feed the memory to the command buffer
|
||||
cmdbuf.addMemory(m_mem.getMemBlock(), m_mem.getOffset() + m_curSlice * sliceSize, sliceSize);
|
||||
}
|
||||
|
||||
DkCmdList end(dk::CmdBuf cmdbuf)
|
||||
{
|
||||
// Signal the fence corresponding to the current slice; so that in the future when we want
|
||||
// to use it again, we can wait for the completion of the commands we've just submitted
|
||||
// (and as such we don't overwrite in-flight command data with new one)
|
||||
cmdbuf.signalFence(m_fences[m_curSlice]);
|
||||
|
||||
// Advance the current slice counter; wrapping around when we reach the end
|
||||
m_curSlice = (m_curSlice + 1) % NumSlices;
|
||||
|
||||
// Finish off the command list, returning it to the caller
|
||||
return cmdbuf.finishList();
|
||||
}
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** CDescriptorSet.h: Image/Sampler descriptor set class
|
||||
*/
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "CMemPool.h"
|
||||
|
||||
template <unsigned NumDescriptors>
|
||||
class CDescriptorSet
|
||||
{
|
||||
static_assert(NumDescriptors > 0, "Need a non-zero number of descriptors...");
|
||||
static_assert(sizeof(DkImageDescriptor) == sizeof(DkSamplerDescriptor), "shouldn't happen");
|
||||
static_assert(DK_IMAGE_DESCRIPTOR_ALIGNMENT == DK_SAMPLER_DESCRIPTOR_ALIGNMENT, "shouldn't happen");
|
||||
static constexpr size_t DescriptorSize = sizeof(DkImageDescriptor);
|
||||
static constexpr size_t DescriptorAlign = DK_IMAGE_DESCRIPTOR_ALIGNMENT;
|
||||
|
||||
CMemPool::Handle m_mem;
|
||||
public:
|
||||
CDescriptorSet() : m_mem{} { }
|
||||
~CDescriptorSet()
|
||||
{
|
||||
m_mem.destroy();
|
||||
}
|
||||
|
||||
bool allocate(CMemPool& pool)
|
||||
{
|
||||
m_mem = pool.allocate(NumDescriptors*DescriptorSize, DescriptorAlign);
|
||||
return m_mem;
|
||||
}
|
||||
|
||||
void bindForImages(dk::CmdBuf cmdbuf)
|
||||
{
|
||||
cmdbuf.bindImageDescriptorSet(m_mem.getGpuAddr(), NumDescriptors);
|
||||
}
|
||||
|
||||
void bindForSamplers(dk::CmdBuf cmdbuf)
|
||||
{
|
||||
cmdbuf.bindSamplerDescriptorSet(m_mem.getGpuAddr(), NumDescriptors);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void update(dk::CmdBuf cmdbuf, uint32_t id, T const& descriptor)
|
||||
{
|
||||
static_assert(sizeof(T) == DescriptorSize);
|
||||
cmdbuf.pushData(m_mem.getGpuAddr() + id*DescriptorSize, &descriptor, DescriptorSize);
|
||||
}
|
||||
|
||||
template <typename T, size_t N>
|
||||
void update(dk::CmdBuf cmdbuf, uint32_t id, std::array<T, N> const& descriptors)
|
||||
{
|
||||
static_assert(sizeof(T) == DescriptorSize);
|
||||
cmdbuf.pushData(m_mem.getGpuAddr() + id*DescriptorSize, descriptors.data(), descriptors.size()*DescriptorSize);
|
||||
}
|
||||
|
||||
#ifdef DK_HPP_SUPPORT_VECTOR
|
||||
template <typename T, typename Allocator = std::allocator<T>>
|
||||
void update(dk::CmdBuf cmdbuf, uint32_t id, std::vector<T,Allocator> const& descriptors)
|
||||
{
|
||||
static_assert(sizeof(T) == DescriptorSize);
|
||||
cmdbuf.pushData(m_mem.getGpuAddr() + id*DescriptorSize, descriptors.data(), descriptors.size()*DescriptorSize);
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
void update(dk::CmdBuf cmdbuf, uint32_t id, std::initializer_list<T const> const& descriptors)
|
||||
{
|
||||
static_assert(sizeof(T) == DescriptorSize);
|
||||
cmdbuf.pushData(m_mem.getGpuAddr() + id*DescriptorSize, descriptors.data(), descriptors.size()*DescriptorSize);
|
||||
}
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** CExternalImage.h: Utility class for loading images from the filesystem
|
||||
*/
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "CMemPool.h"
|
||||
|
||||
class CExternalImage
|
||||
{
|
||||
dk::Image m_image;
|
||||
dk::ImageDescriptor m_descriptor;
|
||||
CMemPool::Handle m_mem;
|
||||
public:
|
||||
CExternalImage() : m_image{}, m_descriptor{}, m_mem{} { }
|
||||
~CExternalImage()
|
||||
{
|
||||
m_mem.destroy();
|
||||
}
|
||||
|
||||
constexpr operator bool() const
|
||||
{
|
||||
return m_mem;
|
||||
}
|
||||
|
||||
constexpr dk::Image& get()
|
||||
{
|
||||
return m_image;
|
||||
}
|
||||
|
||||
constexpr dk::ImageDescriptor const& getDescriptor() const
|
||||
{
|
||||
return m_descriptor;
|
||||
}
|
||||
|
||||
bool load(CMemPool& imagePool, CMemPool& scratchPool, dk::Device device, dk::Queue transferQueue, const char* path, uint32_t width, uint32_t height, DkImageFormat format, uint32_t flags = 0);
|
||||
};
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** CIntrusiveList.h: Intrusive doubly-linked list helper class
|
||||
*/
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
template <typename T>
|
||||
struct CIntrusiveListNode
|
||||
{
|
||||
T *m_next, *m_prev;
|
||||
|
||||
constexpr CIntrusiveListNode() : m_next{}, m_prev{} { }
|
||||
constexpr operator bool() const { return m_next || m_prev; }
|
||||
};
|
||||
|
||||
template <typename T, CIntrusiveListNode<T> T::* node_ptr>
|
||||
class CIntrusiveList
|
||||
{
|
||||
T *m_first, *m_last;
|
||||
|
||||
public:
|
||||
constexpr CIntrusiveList() : m_first{}, m_last{} { }
|
||||
|
||||
constexpr T* first() const { return m_first; }
|
||||
constexpr T* last() const { return m_last; }
|
||||
constexpr bool empty() const { return !m_first; }
|
||||
constexpr void clear() { m_first = m_last = nullptr; }
|
||||
|
||||
constexpr bool isLinked(T* obj) const { return obj->*node_ptr || m_first == obj; }
|
||||
constexpr T* prev(T* obj) const { return (obj->*node_ptr).m_prev; }
|
||||
constexpr T* next(T* obj) const { return (obj->*node_ptr).m_next; }
|
||||
|
||||
void add(T* obj)
|
||||
{
|
||||
return addBefore(nullptr, obj);
|
||||
}
|
||||
|
||||
void addBefore(T* pos, T* obj)
|
||||
{
|
||||
auto& node = obj->*node_ptr;
|
||||
node.m_next = pos;
|
||||
node.m_prev = pos ? (pos->*node_ptr).m_prev : m_last;
|
||||
|
||||
if (pos)
|
||||
(pos->*node_ptr).m_prev = obj;
|
||||
else
|
||||
m_last = obj;
|
||||
|
||||
if (node.m_prev)
|
||||
(node.m_prev->*node_ptr).m_next = obj;
|
||||
else
|
||||
m_first = obj;
|
||||
}
|
||||
|
||||
void addAfter(T* pos, T* obj)
|
||||
{
|
||||
auto& node = obj->*node_ptr;
|
||||
node.m_next = pos ? (pos->*node_ptr).m_next : m_first;
|
||||
node.m_prev = pos;
|
||||
|
||||
if (pos)
|
||||
(pos->*node_ptr).m_next = obj;
|
||||
else
|
||||
m_first = obj;
|
||||
|
||||
if (node.m_next)
|
||||
(node.m_next->*node_ptr).m_prev = obj;
|
||||
else
|
||||
m_last = obj;
|
||||
}
|
||||
|
||||
T* pop()
|
||||
{
|
||||
T* ret = m_first;
|
||||
if (ret)
|
||||
{
|
||||
m_first = (ret->*node_ptr).m_next;
|
||||
if (m_first)
|
||||
(m_first->*node_ptr).m_prev = nullptr;
|
||||
else
|
||||
m_last = nullptr;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void remove(T* obj)
|
||||
{
|
||||
auto& node = obj->*node_ptr;
|
||||
if (node.m_prev)
|
||||
{
|
||||
(node.m_prev->*node_ptr).m_next = node.m_next;
|
||||
if (node.m_next)
|
||||
(node.m_next->*node_ptr).m_prev = node.m_prev;
|
||||
else
|
||||
m_last = node.m_prev;
|
||||
} else
|
||||
{
|
||||
m_first = node.m_next;
|
||||
if (m_first)
|
||||
(m_first->*node_ptr).m_prev = nullptr;
|
||||
else
|
||||
m_last = nullptr;
|
||||
}
|
||||
|
||||
node.m_next = node.m_prev = 0;
|
||||
}
|
||||
|
||||
template <typename L>
|
||||
void iterate(L lambda) const
|
||||
{
|
||||
T* next = nullptr;
|
||||
for (T* cur = m_first; cur; cur = next)
|
||||
{
|
||||
next = (cur->*node_ptr).m_next;
|
||||
lambda(cur);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,250 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** CIntrusiveTree.h: Intrusive red-black tree helper class
|
||||
*/
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
struct CIntrusiveTreeNode
|
||||
{
|
||||
enum Color
|
||||
{
|
||||
Red,
|
||||
Black,
|
||||
};
|
||||
|
||||
enum Leaf
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
};
|
||||
|
||||
private:
|
||||
uintptr_t m_parent_color;
|
||||
CIntrusiveTreeNode* m_children[2];
|
||||
|
||||
public:
|
||||
constexpr CIntrusiveTreeNode() : m_parent_color{}, m_children{} { }
|
||||
|
||||
constexpr CIntrusiveTreeNode* getParent() const
|
||||
{
|
||||
return reinterpret_cast<CIntrusiveTreeNode*>(m_parent_color &~ 1);
|
||||
}
|
||||
|
||||
void setParent(CIntrusiveTreeNode* parent)
|
||||
{
|
||||
m_parent_color = (m_parent_color & 1) | reinterpret_cast<uintptr_t>(parent);
|
||||
}
|
||||
|
||||
constexpr Color getColor() const
|
||||
{
|
||||
return static_cast<Color>(m_parent_color & 1);
|
||||
}
|
||||
|
||||
void setColor(Color color)
|
||||
{
|
||||
m_parent_color = (m_parent_color &~ 1) | static_cast<uintptr_t>(color);
|
||||
}
|
||||
|
||||
constexpr CIntrusiveTreeNode*& child(Leaf leaf)
|
||||
{
|
||||
return m_children[leaf];
|
||||
}
|
||||
|
||||
constexpr CIntrusiveTreeNode* const& child(Leaf leaf) const
|
||||
{
|
||||
return m_children[leaf];
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
|
||||
constexpr bool isRed() const { return getColor() == Red; }
|
||||
constexpr bool isBlack() const { return getColor() == Black; }
|
||||
void setRed() { setColor(Red); }
|
||||
void setBlack() { setColor(Black); }
|
||||
|
||||
constexpr CIntrusiveTreeNode*& left() { return child(Left); }
|
||||
constexpr CIntrusiveTreeNode*& right() { return child(Right); }
|
||||
constexpr CIntrusiveTreeNode* const& left() const { return child(Left); }
|
||||
constexpr CIntrusiveTreeNode* const& right() const { return child(Right); }
|
||||
};
|
||||
|
||||
NX_CONSTEXPR CIntrusiveTreeNode::Leaf operator!(CIntrusiveTreeNode::Leaf val) noexcept
|
||||
{
|
||||
return static_cast<CIntrusiveTreeNode::Leaf>(!static_cast<unsigned>(val));
|
||||
}
|
||||
|
||||
class CIntrusiveTreeBase
|
||||
{
|
||||
using N = CIntrusiveTreeNode;
|
||||
|
||||
void rotate(N* node, N::Leaf leaf);
|
||||
void recolor(N* parent, N* node);
|
||||
protected:
|
||||
N* m_root;
|
||||
|
||||
constexpr CIntrusiveTreeBase() : m_root{} { }
|
||||
|
||||
N* walk(N* node, N::Leaf leaf) const;
|
||||
void insert(N* node, N* parent);
|
||||
void remove(N* node);
|
||||
|
||||
N* minmax(N::Leaf leaf) const
|
||||
{
|
||||
N* p = m_root;
|
||||
if (!p)
|
||||
return nullptr;
|
||||
while (p->child(leaf))
|
||||
p = p->child(leaf);
|
||||
return p;
|
||||
}
|
||||
|
||||
template <typename H>
|
||||
N*& navigate(N*& node, N*& parent, N::Leaf leafOnEqual, H helm) const
|
||||
{
|
||||
node = nullptr;
|
||||
parent = nullptr;
|
||||
|
||||
N** point = const_cast<N**>(&m_root);
|
||||
while (*point)
|
||||
{
|
||||
int direction = helm(*point);
|
||||
parent = *point;
|
||||
if (direction < 0)
|
||||
point = &(*point)->left();
|
||||
else if (direction > 0)
|
||||
point = &(*point)->right();
|
||||
else
|
||||
{
|
||||
node = *point;
|
||||
point = &(*point)->child(leafOnEqual);
|
||||
}
|
||||
}
|
||||
return *point;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ClassT, typename MemberT>
|
||||
constexpr ClassT* parent_obj(MemberT* member, MemberT ClassT::* ptr)
|
||||
{
|
||||
union whatever
|
||||
{
|
||||
MemberT ClassT::* ptr;
|
||||
intptr_t offset;
|
||||
};
|
||||
// This is technically UB, but basically every compiler worth using admits it as an extension
|
||||
return (ClassT*)((intptr_t)member - whatever{ptr}.offset);
|
||||
}
|
||||
|
||||
template <
|
||||
typename T,
|
||||
CIntrusiveTreeNode T::* node_ptr,
|
||||
typename Comparator = std::less<>
|
||||
>
|
||||
class CIntrusiveTree final : protected CIntrusiveTreeBase
|
||||
{
|
||||
using N = CIntrusiveTreeNode;
|
||||
|
||||
static constexpr T* toType(N* m)
|
||||
{
|
||||
return m ? parent_obj(m, node_ptr) : nullptr;
|
||||
}
|
||||
|
||||
static constexpr N* toNode(T* m)
|
||||
{
|
||||
return m ? &(m->*node_ptr) : nullptr;
|
||||
}
|
||||
|
||||
template <typename A, typename B>
|
||||
static int compare(A const& a, B const& b)
|
||||
{
|
||||
Comparator comp;
|
||||
if (comp(a, b))
|
||||
return -1;
|
||||
if (comp(b, a))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr CIntrusiveTree() : CIntrusiveTreeBase{} { }
|
||||
|
||||
T* first() const { return toType(minmax(N::Left)); }
|
||||
T* last() const { return toType(minmax(N::Right)); }
|
||||
bool empty() const { return m_root != nullptr; }
|
||||
void clear() { m_root = nullptr; }
|
||||
|
||||
T* prev(T* node) const { return toType(walk(toNode(node), N::Left)); }
|
||||
T* next(T* node) const { return toType(walk(toNode(node), N::Right)); }
|
||||
|
||||
enum SearchMode
|
||||
{
|
||||
Exact = 0,
|
||||
LowerBound = 1,
|
||||
UpperBound = 2,
|
||||
};
|
||||
|
||||
template <typename Lambda>
|
||||
T* search(SearchMode mode, Lambda lambda) const
|
||||
{
|
||||
N *node, *parent;
|
||||
N*& point = navigate(node, parent,
|
||||
mode != UpperBound ? N::Left : N::Right,
|
||||
[&lambda](N* curnode) { return lambda(toType(curnode)); });
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
default:
|
||||
case Exact:
|
||||
break;
|
||||
case LowerBound:
|
||||
if (!node && parent)
|
||||
{
|
||||
if (&parent->left() == &point)
|
||||
node = parent;
|
||||
else
|
||||
node = walk(parent, N::Right);
|
||||
}
|
||||
break;
|
||||
case UpperBound:
|
||||
if (node)
|
||||
node = walk(node, N::Right);
|
||||
else if (parent)
|
||||
{
|
||||
if (&parent->right() == &point)
|
||||
node = walk(parent, N::Right);
|
||||
else
|
||||
node = parent;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return toType(node);
|
||||
}
|
||||
|
||||
template <typename K>
|
||||
T* find(K const& key, SearchMode mode = Exact) const
|
||||
{
|
||||
return search(mode, [&key](T* obj) { return compare(key, *obj); });
|
||||
}
|
||||
|
||||
T* insert(T* obj, bool allow_dupes = false)
|
||||
{
|
||||
N *node, *parent;
|
||||
N*& point = navigate(node, parent, N::Right,
|
||||
[obj](N* curnode) { return compare(*obj, *toType(curnode)); });
|
||||
|
||||
if (node && !allow_dupes)
|
||||
return toType(node);
|
||||
|
||||
point = toNode(obj);
|
||||
CIntrusiveTreeBase::insert(point, parent);
|
||||
return obj;
|
||||
}
|
||||
|
||||
void remove(T* obj)
|
||||
{
|
||||
CIntrusiveTreeBase::remove(toNode(obj));
|
||||
}
|
||||
};
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** CMemPool.h: Pooled dynamic memory allocation manager class
|
||||
*/
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "CIntrusiveList.h"
|
||||
#include "CIntrusiveTree.h"
|
||||
|
||||
class CMemPool
|
||||
{
|
||||
dk::Device m_dev;
|
||||
uint32_t m_flags;
|
||||
uint32_t m_blockSize;
|
||||
|
||||
struct Block
|
||||
{
|
||||
CIntrusiveListNode<Block> m_node;
|
||||
dk::MemBlock m_obj;
|
||||
void* m_cpuAddr;
|
||||
DkGpuAddr m_gpuAddr;
|
||||
|
||||
constexpr void* cpuOffset(uint32_t offset) const
|
||||
{
|
||||
return m_cpuAddr ? ((u8*)m_cpuAddr + offset) : nullptr;
|
||||
}
|
||||
|
||||
constexpr DkGpuAddr gpuOffset(uint32_t offset) const
|
||||
{
|
||||
return m_gpuAddr != DK_GPU_ADDR_INVALID ? (m_gpuAddr + offset) : DK_GPU_ADDR_INVALID;
|
||||
}
|
||||
};
|
||||
|
||||
CIntrusiveList<Block, &Block::m_node> m_blocks;
|
||||
|
||||
struct Slice
|
||||
{
|
||||
CIntrusiveListNode<Slice> m_node;
|
||||
CIntrusiveTreeNode m_treenode;
|
||||
CMemPool* m_pool;
|
||||
Block* m_block;
|
||||
uint32_t m_start;
|
||||
uint32_t m_end;
|
||||
|
||||
constexpr uint32_t getSize() const { return m_end - m_start; }
|
||||
constexpr bool canCoalesce(Slice const& rhs) const { return m_pool == rhs.m_pool && m_block == rhs.m_block && m_end == rhs.m_start; }
|
||||
|
||||
constexpr bool operator<(Slice const& rhs) const { return getSize() < rhs.getSize(); }
|
||||
constexpr bool operator<(uint32_t rhs) const { return getSize() < rhs; }
|
||||
};
|
||||
|
||||
friend constexpr bool operator<(uint32_t lhs, Slice const& rhs);
|
||||
|
||||
CIntrusiveList<Slice, &Slice::m_node> m_memMap, m_sliceHeap;
|
||||
CIntrusiveTree<Slice, &Slice::m_treenode> m_freeList;
|
||||
|
||||
Slice* _newSlice();
|
||||
void _deleteSlice(Slice*);
|
||||
|
||||
void _destroy(Slice* slice);
|
||||
|
||||
public:
|
||||
static constexpr uint32_t DefaultBlockSize = 0x800000;
|
||||
class Handle
|
||||
{
|
||||
Slice* m_slice;
|
||||
public:
|
||||
constexpr Handle(Slice* slice = nullptr) : m_slice{slice} { }
|
||||
constexpr operator bool() const { return m_slice != nullptr; }
|
||||
constexpr operator Slice*() const { return m_slice; }
|
||||
constexpr bool operator!() const { return !m_slice; }
|
||||
constexpr bool operator==(Handle const& rhs) const { return m_slice == rhs.m_slice; }
|
||||
constexpr bool operator!=(Handle const& rhs) const { return m_slice != rhs.m_slice; }
|
||||
|
||||
void destroy()
|
||||
{
|
||||
if (m_slice)
|
||||
{
|
||||
m_slice->m_pool->_destroy(m_slice);
|
||||
m_slice = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr dk::MemBlock getMemBlock() const
|
||||
{
|
||||
return m_slice->m_block->m_obj;
|
||||
}
|
||||
|
||||
constexpr uint32_t getOffset() const
|
||||
{
|
||||
return m_slice->m_start;
|
||||
}
|
||||
|
||||
constexpr uint32_t getSize() const
|
||||
{
|
||||
return m_slice->getSize();
|
||||
}
|
||||
|
||||
constexpr void* getCpuAddr() const
|
||||
{
|
||||
return m_slice->m_block->cpuOffset(m_slice->m_start);
|
||||
}
|
||||
|
||||
constexpr DkGpuAddr getGpuAddr() const
|
||||
{
|
||||
return m_slice->m_block->gpuOffset(m_slice->m_start);
|
||||
}
|
||||
};
|
||||
|
||||
CMemPool(dk::Device dev, uint32_t flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, uint32_t blockSize = DefaultBlockSize) :
|
||||
m_dev{dev}, m_flags{flags}, m_blockSize{blockSize}, m_blocks{}, m_memMap{}, m_sliceHeap{}, m_freeList{} { }
|
||||
~CMemPool();
|
||||
|
||||
Handle allocate(uint32_t size, uint32_t alignment = DK_CMDMEM_ALIGNMENT);
|
||||
};
|
||||
|
||||
constexpr bool operator<(uint32_t lhs, CMemPool::Slice const& rhs)
|
||||
{
|
||||
return lhs < rhs.getSize();
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** CShader.h: Utility class for loading shaders from the filesystem
|
||||
*/
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "CMemPool.h"
|
||||
|
||||
class CShader
|
||||
{
|
||||
dk::Shader m_shader;
|
||||
CMemPool::Handle m_codemem;
|
||||
public:
|
||||
CShader() : m_shader{}, m_codemem{} { }
|
||||
~CShader()
|
||||
{
|
||||
m_codemem.destroy();
|
||||
}
|
||||
|
||||
constexpr operator bool() const
|
||||
{
|
||||
return m_codemem;
|
||||
}
|
||||
|
||||
constexpr operator dk::Shader const*() const
|
||||
{
|
||||
return &m_shader;
|
||||
}
|
||||
|
||||
bool load(CMemPool& pool, const char* path);
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** FileLoader.h: Helpers for loading data from the filesystem directly into GPU memory
|
||||
*/
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "CMemPool.h"
|
||||
|
||||
CMemPool::Handle LoadFile(CMemPool& pool, const char* path, uint32_t alignment = DK_CMDMEM_ALIGNMENT);
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** common.h: Common includes
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
#include <deko3d.hpp>
|
||||
@@ -1,158 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
#ifndef NANOVG_GL_UTILS_H
|
||||
#define NANOVG_GL_UTILS_H
|
||||
|
||||
#ifdef USE_OPENGL
|
||||
|
||||
struct NVGLUframebuffer {
|
||||
NVGcontext* ctx;
|
||||
GLuint fbo;
|
||||
GLuint rbo;
|
||||
GLuint texture;
|
||||
int image;
|
||||
};
|
||||
typedef struct NVGLUframebuffer NVGLUframebuffer;
|
||||
|
||||
// Helper function to create GL frame buffer to render to.
|
||||
void nvgluBindFramebuffer(NVGLUframebuffer* fb);
|
||||
NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags);
|
||||
void nvgluDeleteFramebuffer(NVGLUframebuffer* fb);
|
||||
|
||||
#endif // NANOVG_GL_UTILS_H
|
||||
|
||||
#ifdef NANOVG_GL_IMPLEMENTATION
|
||||
|
||||
#if defined(NANOVG_GL3) || defined(NANOVG_GLES2) || defined(NANOVG_GLES3)
|
||||
// FBO is core in OpenGL 3>.
|
||||
# define NANOVG_FBO_VALID 1
|
||||
#elif defined(NANOVG_GL2)
|
||||
// On OS X including glext defines FBO on GL2 too.
|
||||
# ifdef __APPLE__
|
||||
# include <OpenGL/glext.h>
|
||||
# define NANOVG_FBO_VALID 1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
static GLint defaultFBO = -1;
|
||||
|
||||
NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags)
|
||||
{
|
||||
#ifdef NANOVG_FBO_VALID
|
||||
GLint defaultFBO;
|
||||
GLint defaultRBO;
|
||||
NVGLUframebuffer* fb = NULL;
|
||||
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
|
||||
glGetIntegerv(GL_RENDERBUFFER_BINDING, &defaultRBO);
|
||||
|
||||
fb = (NVGLUframebuffer*)malloc(sizeof(NVGLUframebuffer));
|
||||
if (fb == NULL) goto error;
|
||||
memset(fb, 0, sizeof(NVGLUframebuffer));
|
||||
|
||||
fb->image = nvgCreateImageRGBA(ctx, w, h, imageFlags | NVG_IMAGE_FLIPY | NVG_IMAGE_PREMULTIPLIED, NULL);
|
||||
|
||||
#if defined NANOVG_GL2
|
||||
fb->texture = nvglImageHandleGL2(ctx, fb->image);
|
||||
#elif defined NANOVG_GL3
|
||||
fb->texture = nvglImageHandleGL3(ctx, fb->image);
|
||||
#elif defined NANOVG_GLES2
|
||||
fb->texture = nvglImageHandleGLES2(ctx, fb->image);
|
||||
#elif defined NANOVG_GLES3
|
||||
fb->texture = nvglImageHandleGLES3(ctx, fb->image);
|
||||
#endif
|
||||
|
||||
fb->ctx = ctx;
|
||||
|
||||
// frame buffer object
|
||||
glGenFramebuffers(1, &fb->fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fb->fbo);
|
||||
|
||||
// render buffer object
|
||||
glGenRenderbuffers(1, &fb->rbo);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, fb->rbo);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, w, h);
|
||||
|
||||
// combine all
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo);
|
||||
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
#ifdef GL_DEPTH24_STENCIL8
|
||||
// If GL_STENCIL_INDEX8 is not supported, try GL_DEPTH24_STENCIL8 as a fallback.
|
||||
// Some graphics cards require a depth buffer along with a stencil.
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo);
|
||||
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
#endif // GL_DEPTH24_STENCIL8
|
||||
goto error;
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO);
|
||||
return fb;
|
||||
error:
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO);
|
||||
nvgluDeleteFramebuffer(fb);
|
||||
return NULL;
|
||||
#else
|
||||
NVG_NOTUSED(ctx);
|
||||
NVG_NOTUSED(w);
|
||||
NVG_NOTUSED(h);
|
||||
NVG_NOTUSED(imageFlags);
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
void nvgluBindFramebuffer(NVGLUframebuffer* fb)
|
||||
{
|
||||
#ifdef NANOVG_FBO_VALID
|
||||
if (defaultFBO == -1) glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fb != NULL ? fb->fbo : defaultFBO);
|
||||
#else
|
||||
NVG_NOTUSED(fb);
|
||||
#endif
|
||||
}
|
||||
|
||||
void nvgluDeleteFramebuffer(NVGLUframebuffer* fb)
|
||||
{
|
||||
#ifdef NANOVG_FBO_VALID
|
||||
if (fb == NULL) return;
|
||||
if (fb->fbo != 0)
|
||||
glDeleteFramebuffers(1, &fb->fbo);
|
||||
if (fb->rbo != 0)
|
||||
glDeleteRenderbuffers(1, &fb->rbo);
|
||||
if (fb->image >= 0)
|
||||
nvgDeleteImage(fb->ctx, fb->image);
|
||||
fb->ctx = NULL;
|
||||
fb->fbo = 0;
|
||||
fb->rbo = 0;
|
||||
fb->texture = 0;
|
||||
fb->image = -1;
|
||||
free(fb);
|
||||
#else
|
||||
NVG_NOTUSED(fb);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // NANOVG_GL_IMPLEMENTATION
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,520 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "nanovg.h"
|
||||
#include "nanovg/dk_renderer.hpp"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static int dknvg__maxi(int a, int b) { return a > b ? a : b; }
|
||||
|
||||
static const DKNVGtextureDescriptor* dknvg__findTexture(DKNVGcontext* dk, int id) {
|
||||
return dk->renderer->GetTextureDescriptor(*dk, id);
|
||||
}
|
||||
|
||||
static int dknvg__renderCreate(void* uptr)
|
||||
{
|
||||
DKNVGcontext *dk = (DKNVGcontext*)uptr;
|
||||
return dk->renderer->Create(*dk);
|
||||
}
|
||||
|
||||
static int dknvg__renderCreateTexture(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data)
|
||||
{
|
||||
DKNVGcontext *dk = (DKNVGcontext*)uptr;
|
||||
return dk->renderer->CreateTexture(*dk, type, w, h, imageFlags, data);
|
||||
}
|
||||
|
||||
static int dknvg__renderDeleteTexture(void* uptr, int image) {
|
||||
DKNVGcontext *dk = (DKNVGcontext*)uptr;
|
||||
return dk->renderer->DeleteTexture(*dk, image);
|
||||
}
|
||||
|
||||
static int dknvg__renderUpdateTexture(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data) {
|
||||
DKNVGcontext *dk = (DKNVGcontext*)uptr;
|
||||
return dk->renderer->UpdateTexture(*dk, image, x, y, w, h, data);
|
||||
}
|
||||
|
||||
static int dknvg__renderGetTextureSize(void* uptr, int image, int* w, int* h) {
|
||||
DKNVGcontext *dk = (DKNVGcontext*)uptr;
|
||||
return dk->renderer->GetTextureSize(*dk, image, w, h);
|
||||
}
|
||||
|
||||
static void dknvg__xformToMat3x4(float* m3, float* t) {
|
||||
m3[0] = t[0];
|
||||
m3[1] = t[1];
|
||||
m3[2] = 0.0f;
|
||||
m3[3] = 0.0f;
|
||||
m3[4] = t[2];
|
||||
m3[5] = t[3];
|
||||
m3[6] = 0.0f;
|
||||
m3[7] = 0.0f;
|
||||
m3[8] = t[4];
|
||||
m3[9] = t[5];
|
||||
m3[10] = 1.0f;
|
||||
m3[11] = 0.0f;
|
||||
}
|
||||
|
||||
static NVGcolor dknvg__premulColor(NVGcolor c) {
|
||||
c.r *= c.a;
|
||||
c.g *= c.a;
|
||||
c.b *= c.a;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int dknvg__convertPaint(DKNVGcontext* dk, DKNVGfragUniforms* frag, NVGpaint* paint,
|
||||
NVGscissor* scissor, float width, float fringe, float strokeThr)
|
||||
{
|
||||
const DKNVGtextureDescriptor *tex = NULL;
|
||||
float invxform[6];
|
||||
|
||||
memset(frag, 0, sizeof(*frag));
|
||||
|
||||
frag->innerCol = dknvg__premulColor(paint->innerColor);
|
||||
frag->outerCol = dknvg__premulColor(paint->outerColor);
|
||||
|
||||
if (scissor->extent[0] < -0.5f || scissor->extent[1] < -0.5f) {
|
||||
memset(frag->scissorMat, 0, sizeof(frag->scissorMat));
|
||||
frag->scissorExt[0] = 1.0f;
|
||||
frag->scissorExt[1] = 1.0f;
|
||||
frag->scissorScale[0] = 1.0f;
|
||||
frag->scissorScale[1] = 1.0f;
|
||||
} else {
|
||||
nvgTransformInverse(invxform, scissor->xform);
|
||||
dknvg__xformToMat3x4(frag->scissorMat, invxform);
|
||||
frag->scissorExt[0] = scissor->extent[0];
|
||||
frag->scissorExt[1] = scissor->extent[1];
|
||||
frag->scissorScale[0] = sqrtf(scissor->xform[0]*scissor->xform[0] + scissor->xform[2]*scissor->xform[2]) / fringe;
|
||||
frag->scissorScale[1] = sqrtf(scissor->xform[1]*scissor->xform[1] + scissor->xform[3]*scissor->xform[3]) / fringe;
|
||||
}
|
||||
|
||||
memcpy(frag->extent, paint->extent, sizeof(frag->extent));
|
||||
frag->strokeMult = (width*0.5f + fringe*0.5f) / fringe;
|
||||
frag->strokeThr = strokeThr;
|
||||
|
||||
if (paint->image != 0) {
|
||||
tex = dknvg__findTexture(dk, paint->image);
|
||||
if (tex == NULL) return 0;
|
||||
if ((tex->flags & NVG_IMAGE_FLIPY) != 0) {
|
||||
float m1[6], m2[6];
|
||||
nvgTransformTranslate(m1, 0.0f, frag->extent[1] * 0.5f);
|
||||
nvgTransformMultiply(m1, paint->xform);
|
||||
nvgTransformScale(m2, 1.0f, -1.0f);
|
||||
nvgTransformMultiply(m2, m1);
|
||||
nvgTransformTranslate(m1, 0.0f, -frag->extent[1] * 0.5f);
|
||||
nvgTransformMultiply(m1, m2);
|
||||
nvgTransformInverse(invxform, m1);
|
||||
} else {
|
||||
nvgTransformInverse(invxform, paint->xform);
|
||||
}
|
||||
frag->type = NSVG_SHADER_FILLIMG;
|
||||
|
||||
if (tex->type == NVG_TEXTURE_RGBA)
|
||||
frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0 : 1;
|
||||
else
|
||||
frag->texType = 2;
|
||||
// printf("frag->texType = %d\n", frag->texType);
|
||||
} else {
|
||||
frag->type = NSVG_SHADER_FILLGRAD;
|
||||
frag->radius = paint->radius;
|
||||
frag->feather = paint->feather;
|
||||
nvgTransformInverse(invxform, paint->xform);
|
||||
}
|
||||
|
||||
dknvg__xformToMat3x4(frag->paintMat, invxform);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static DKNVGfragUniforms* nvg__fragUniformPtr(DKNVGcontext* dk, int i);
|
||||
|
||||
static void dknvg__renderViewport(void* uptr, float width, float height, float devicePixelRatio)
|
||||
{
|
||||
NVG_NOTUSED(devicePixelRatio);
|
||||
DKNVGcontext* dk = (DKNVGcontext*)uptr;
|
||||
dk->view[0] = width;
|
||||
dk->view[1] = height;
|
||||
}
|
||||
|
||||
static void dknvg__renderCancel(void* uptr) {
|
||||
DKNVGcontext* dk = (DKNVGcontext*)uptr;
|
||||
dk->nverts = 0;
|
||||
dk->npaths = 0;
|
||||
dk->ncalls = 0;
|
||||
dk->nuniforms = 0;
|
||||
}
|
||||
|
||||
static int dknvg_convertBlendFuncFactor(int factor) {
|
||||
switch (factor) {
|
||||
case NVG_ZERO:
|
||||
return DkBlendFactor_Zero;
|
||||
case NVG_ONE:
|
||||
return DkBlendFactor_One;
|
||||
case NVG_SRC_COLOR:
|
||||
return DkBlendFactor_SrcColor;
|
||||
case NVG_ONE_MINUS_SRC_COLOR:
|
||||
return DkBlendFactor_InvSrcColor;
|
||||
case NVG_DST_COLOR:
|
||||
return DkBlendFactor_DstColor;
|
||||
case NVG_ONE_MINUS_DST_COLOR:
|
||||
return DkBlendFactor_InvDstColor;
|
||||
case NVG_SRC_ALPHA:
|
||||
return DkBlendFactor_SrcAlpha;
|
||||
case NVG_ONE_MINUS_SRC_ALPHA:
|
||||
return DkBlendFactor_InvSrcAlpha;
|
||||
case NVG_DST_ALPHA:
|
||||
return DkBlendFactor_DstAlpha;
|
||||
case NVG_ONE_MINUS_DST_ALPHA:
|
||||
return DkBlendFactor_InvDstAlpha;
|
||||
case NVG_SRC_ALPHA_SATURATE:
|
||||
return DkBlendFactor_SrcAlphaSaturate;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static DKNVGblend dknvg__blendCompositeOperation(NVGcompositeOperationState op) {
|
||||
DKNVGblend blend;
|
||||
blend.srcRGB = dknvg_convertBlendFuncFactor(op.srcRGB);
|
||||
blend.dstRGB = dknvg_convertBlendFuncFactor(op.dstRGB);
|
||||
blend.srcAlpha = dknvg_convertBlendFuncFactor(op.srcAlpha);
|
||||
blend.dstAlpha = dknvg_convertBlendFuncFactor(op.dstAlpha);
|
||||
|
||||
if (blend.srcRGB == -1 || blend.dstRGB == -1 || blend.srcAlpha == -1 || blend.dstAlpha == -1) {
|
||||
blend.srcRGB = DkBlendFactor_One;
|
||||
blend.dstRGB = DkBlendFactor_InvSrcAlpha;
|
||||
blend.srcAlpha = DkBlendFactor_One;
|
||||
blend.dstAlpha = DkBlendFactor_InvSrcAlpha;
|
||||
}
|
||||
return blend;
|
||||
}
|
||||
|
||||
static void dknvg__renderFlush(void* uptr) {
|
||||
DKNVGcontext *dk = (DKNVGcontext*)uptr;
|
||||
dk->renderer->Flush(*dk);
|
||||
}
|
||||
|
||||
static int dknvg__maxVertCount(const NVGpath* paths, int npaths) {
|
||||
int i, count = 0;
|
||||
for (i = 0; i < npaths; i++) {
|
||||
count += paths[i].nfill;
|
||||
count += paths[i].nstroke;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static DKNVGcall* dknvg__allocCall(DKNVGcontext* dk)
|
||||
{
|
||||
DKNVGcall* ret = NULL;
|
||||
if (dk->ncalls+1 > dk->ccalls) {
|
||||
DKNVGcall* calls;
|
||||
int ccalls = dknvg__maxi(dk->ncalls+1, 128) + dk->ccalls/2; // 1.5x Overallocate
|
||||
calls = (DKNVGcall*)realloc(dk->calls, sizeof(DKNVGcall) * ccalls);
|
||||
if (calls == NULL) return NULL;
|
||||
dk->calls = calls;
|
||||
dk->ccalls = ccalls;
|
||||
}
|
||||
ret = &dk->calls[dk->ncalls++];
|
||||
memset(ret, 0, sizeof(DKNVGcall));
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dknvg__allocPaths(DKNVGcontext* dk, int n)
|
||||
{
|
||||
int ret = 0;
|
||||
if (dk->npaths+n > dk->cpaths) {
|
||||
DKNVGpath* paths;
|
||||
int cpaths = dknvg__maxi(dk->npaths + n, 128) + dk->cpaths/2; // 1.5x Overallocate
|
||||
paths = (DKNVGpath*)realloc(dk->paths, sizeof(DKNVGpath) * cpaths);
|
||||
if (paths == NULL) return -1;
|
||||
dk->paths = paths;
|
||||
dk->cpaths = cpaths;
|
||||
}
|
||||
ret = dk->npaths;
|
||||
dk->npaths += n;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dknvg__allocVerts(DKNVGcontext* dk, int n)
|
||||
{
|
||||
int ret = 0;
|
||||
if (dk->nverts+n > dk->cverts) {
|
||||
NVGvertex* verts;
|
||||
int cverts = dknvg__maxi(dk->nverts + n, 4096) + dk->cverts/2; // 1.5x Overallocate
|
||||
verts = (NVGvertex*)realloc(dk->verts, sizeof(NVGvertex) * cverts);
|
||||
if (verts == NULL) return -1;
|
||||
dk->verts = verts;
|
||||
dk->cverts = cverts;
|
||||
}
|
||||
ret = dk->nverts;
|
||||
dk->nverts += n;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dknvg__allocFragUniforms(DKNVGcontext* dk, int n)
|
||||
{
|
||||
int ret = 0, structSize = dk->fragSize;
|
||||
if (dk->nuniforms+n > dk->cuniforms) {
|
||||
unsigned char* uniforms;
|
||||
int cuniforms = dknvg__maxi(dk->nuniforms+n, 128) + dk->cuniforms/2; // 1.5x Overallocate
|
||||
uniforms = (unsigned char*)realloc(dk->uniforms, structSize * cuniforms);
|
||||
if (uniforms == NULL) return -1;
|
||||
dk->uniforms = uniforms;
|
||||
dk->cuniforms = cuniforms;
|
||||
}
|
||||
ret = dk->nuniforms * structSize;
|
||||
dk->nuniforms += n;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static DKNVGfragUniforms* nvg__fragUniformPtr(DKNVGcontext* dk, int i)
|
||||
{
|
||||
return (DKNVGfragUniforms*)&dk->uniforms[i];
|
||||
}
|
||||
|
||||
static void dknvg__vset(NVGvertex* vtx, float x, float y, float u, float v)
|
||||
{
|
||||
vtx->x = x;
|
||||
vtx->y = y;
|
||||
vtx->u = u;
|
||||
vtx->v = v;
|
||||
}
|
||||
|
||||
static void dknvg__renderFill(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe,
|
||||
const float* bounds, const NVGpath* paths, int npaths)
|
||||
{
|
||||
DKNVGcontext* dk = (DKNVGcontext*)uptr;
|
||||
DKNVGcall* call = dknvg__allocCall(dk);
|
||||
NVGvertex* quad;
|
||||
DKNVGfragUniforms* frag;
|
||||
int i, maxverts, offset;
|
||||
|
||||
if (call == NULL) return;
|
||||
|
||||
call->type = DKNVG_FILL;
|
||||
call->triangleCount = 4;
|
||||
call->pathOffset = dknvg__allocPaths(dk, npaths);
|
||||
if (call->pathOffset == -1) goto error;
|
||||
call->pathCount = npaths;
|
||||
call->image = paint->image;
|
||||
call->blendFunc = dknvg__blendCompositeOperation(compositeOperation);
|
||||
|
||||
if (npaths == 1 && paths[0].convex)
|
||||
{
|
||||
call->type = DKNVG_CONVEXFILL;
|
||||
call->triangleCount = 0; // Bounding box fill quad not needed for convex fill
|
||||
}
|
||||
|
||||
// Allocate vertices for all the paths.
|
||||
maxverts = dknvg__maxVertCount(paths, npaths) + call->triangleCount;
|
||||
offset = dknvg__allocVerts(dk, maxverts);
|
||||
if (offset == -1) goto error;
|
||||
|
||||
for (i = 0; i < npaths; i++) {
|
||||
DKNVGpath* copy = &dk->paths[call->pathOffset + i];
|
||||
const NVGpath* path = &paths[i];
|
||||
memset(copy, 0, sizeof(DKNVGpath));
|
||||
if (path->nfill > 0) {
|
||||
copy->fillOffset = offset;
|
||||
copy->fillCount = path->nfill;
|
||||
memcpy(&dk->verts[offset], path->fill, sizeof(NVGvertex) * path->nfill);
|
||||
offset += path->nfill;
|
||||
}
|
||||
if (path->nstroke > 0) {
|
||||
copy->strokeOffset = offset;
|
||||
copy->strokeCount = path->nstroke;
|
||||
memcpy(&dk->verts[offset], path->stroke, sizeof(NVGvertex) * path->nstroke);
|
||||
offset += path->nstroke;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup uniforms for draw calls
|
||||
if (call->type == DKNVG_FILL) {
|
||||
// Quad
|
||||
call->triangleOffset = offset;
|
||||
quad = &dk->verts[call->triangleOffset];
|
||||
dknvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f);
|
||||
dknvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f);
|
||||
dknvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f);
|
||||
dknvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f);
|
||||
|
||||
call->uniformOffset = dknvg__allocFragUniforms(dk, 2);
|
||||
if (call->uniformOffset == -1) goto error;
|
||||
// Simple shader for stencil
|
||||
frag = nvg__fragUniformPtr(dk, call->uniformOffset);
|
||||
memset(frag, 0, sizeof(*frag));
|
||||
frag->strokeThr = -1.0f;
|
||||
frag->type = NSVG_SHADER_SIMPLE;
|
||||
// Fill shader
|
||||
dknvg__convertPaint(dk, nvg__fragUniformPtr(dk, call->uniformOffset + dk->fragSize), paint, scissor, fringe, fringe, -1.0f);
|
||||
} else {
|
||||
call->uniformOffset = dknvg__allocFragUniforms(dk, 1);
|
||||
if (call->uniformOffset == -1) goto error;
|
||||
// Fill shader
|
||||
dknvg__convertPaint(dk, nvg__fragUniformPtr(dk, call->uniformOffset), paint, scissor, fringe, fringe, -1.0f);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
// We get here if call alloc was ok, but something else is not.
|
||||
// Roll back the last call to prevent drawing it.
|
||||
if (dk->ncalls > 0) dk->ncalls--;
|
||||
}
|
||||
|
||||
static void dknvg__renderStroke(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe,
|
||||
float strokeWidth, const NVGpath* paths, int npaths)
|
||||
{
|
||||
DKNVGcontext* dk = (DKNVGcontext*)uptr;
|
||||
DKNVGcall* call = dknvg__allocCall(dk);
|
||||
int i, maxverts, offset;
|
||||
|
||||
if (call == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
call->type = DKNVG_STROKE;
|
||||
call->pathOffset = dknvg__allocPaths(dk, npaths);
|
||||
if (call->pathOffset == -1) goto error;
|
||||
call->pathCount = npaths;
|
||||
call->image = paint->image;
|
||||
call->blendFunc = dknvg__blendCompositeOperation(compositeOperation);
|
||||
|
||||
// Allocate vertices for all the paths.
|
||||
maxverts = dknvg__maxVertCount(paths, npaths);
|
||||
offset = dknvg__allocVerts(dk, maxverts);
|
||||
if (offset == -1) goto error;
|
||||
|
||||
for (i = 0; i < npaths; i++) {
|
||||
DKNVGpath* copy = &dk->paths[call->pathOffset + i];
|
||||
const NVGpath* path = &paths[i];
|
||||
memset(copy, 0, sizeof(DKNVGpath));
|
||||
if (path->nstroke) {
|
||||
copy->strokeOffset = offset;
|
||||
copy->strokeCount = path->nstroke;
|
||||
memcpy(&dk->verts[offset], path->stroke, sizeof(NVGvertex) * path->nstroke);
|
||||
offset += path->nstroke;
|
||||
}
|
||||
}
|
||||
|
||||
if (dk->flags & NVG_STENCIL_STROKES) {
|
||||
// Fill shader
|
||||
call->uniformOffset = dknvg__allocFragUniforms(dk, 2);
|
||||
if (call->uniformOffset == -1) goto error;
|
||||
|
||||
dknvg__convertPaint(dk, nvg__fragUniformPtr(dk, call->uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
|
||||
dknvg__convertPaint(dk, nvg__fragUniformPtr(dk, call->uniformOffset + dk->fragSize), paint, scissor, strokeWidth, fringe, 1.0f - 0.5f/255.0f);
|
||||
} else {
|
||||
// Fill shader
|
||||
call->uniformOffset = dknvg__allocFragUniforms(dk, 1);
|
||||
if (call->uniformOffset == -1) goto error;
|
||||
|
||||
dknvg__convertPaint(dk, nvg__fragUniformPtr(dk, call->uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
// We get here if call alloc was ok, but something else is not.
|
||||
// Roll back the last call to prevent drawing it.
|
||||
if (dk->ncalls > 0) dk->ncalls--;
|
||||
}
|
||||
|
||||
static void dknvg__renderTriangles(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor,
|
||||
const NVGvertex* verts, int nverts, float fringe)
|
||||
{
|
||||
DKNVGcontext* dk = (DKNVGcontext*)uptr;
|
||||
DKNVGcall* call = dknvg__allocCall(dk);
|
||||
DKNVGfragUniforms* frag;
|
||||
|
||||
if (call == NULL) return;
|
||||
|
||||
call->type = DKNVG_TRIANGLES;
|
||||
call->image = paint->image;
|
||||
call->blendFunc = dknvg__blendCompositeOperation(compositeOperation);
|
||||
|
||||
// Allocate vertices for all the paths.
|
||||
call->triangleOffset = dknvg__allocVerts(dk, nverts);
|
||||
if (call->triangleOffset == -1) goto error;
|
||||
call->triangleCount = nverts;
|
||||
|
||||
memcpy(&dk->verts[call->triangleOffset], verts, sizeof(NVGvertex) * nverts);
|
||||
|
||||
// Fill shader
|
||||
call->uniformOffset = dknvg__allocFragUniforms(dk, 1);
|
||||
if (call->uniformOffset == -1) goto error;
|
||||
frag = nvg__fragUniformPtr(dk, call->uniformOffset);
|
||||
dknvg__convertPaint(dk, frag, paint, scissor, 1.0f, fringe, -1.0f);
|
||||
frag->type = NSVG_SHADER_IMG;
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
// We get here if call alloc was ok, but something else is not.
|
||||
// Roll back the last call to prevent drawing it.
|
||||
if (dk->ncalls > 0) dk->ncalls--;
|
||||
}
|
||||
|
||||
static void dknvg__renderDelete(void* uptr) {
|
||||
DKNVGcontext* dk = (DKNVGcontext*)uptr;
|
||||
if (dk == NULL) return;
|
||||
|
||||
free(dk->paths);
|
||||
free(dk->verts);
|
||||
free(dk->uniforms);
|
||||
free(dk->calls);
|
||||
|
||||
free(dk);
|
||||
}
|
||||
|
||||
NVGcontext* nvgCreateDk(nvg::DkRenderer *renderer, int flags) {
|
||||
NVGparams params;
|
||||
NVGcontext* ctx = NULL;
|
||||
DKNVGcontext* dk = (DKNVGcontext*)malloc(sizeof(DKNVGcontext));
|
||||
if (dk == NULL) goto error;
|
||||
memset(dk, 0, sizeof(DKNVGcontext));
|
||||
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
params.renderCreate = dknvg__renderCreate;
|
||||
params.renderCreateTexture = dknvg__renderCreateTexture;
|
||||
params.renderDeleteTexture = dknvg__renderDeleteTexture;
|
||||
params.renderUpdateTexture = dknvg__renderUpdateTexture;
|
||||
params.renderGetTextureSize = dknvg__renderGetTextureSize;
|
||||
params.renderViewport = dknvg__renderViewport;
|
||||
params.renderCancel = dknvg__renderCancel;
|
||||
params.renderFlush = dknvg__renderFlush;
|
||||
params.renderFill = dknvg__renderFill;
|
||||
params.renderStroke = dknvg__renderStroke;
|
||||
params.renderTriangles = dknvg__renderTriangles;
|
||||
params.renderDelete = dknvg__renderDelete;
|
||||
params.userPtr = dk;
|
||||
params.edgeAntiAlias = flags & NVG_ANTIALIAS ? 1 : 0;
|
||||
|
||||
dk->renderer = renderer;
|
||||
dk->flags = flags;
|
||||
|
||||
ctx = nvgCreateInternal(¶ms);
|
||||
if (ctx == NULL) goto error;
|
||||
|
||||
return ctx;
|
||||
|
||||
error:
|
||||
// 'dk' is freed by nvgDeleteInternal.
|
||||
if (ctx != NULL) nvgDeleteInternal(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void nvgDeleteDk(NVGcontext* ctx)
|
||||
{
|
||||
nvgDeleteInternal(ctx);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,83 +0,0 @@
|
||||
#version 460
|
||||
|
||||
layout(binding = 0) uniform sampler2D tex;
|
||||
|
||||
layout(std140, binding = 0) uniform frag {
|
||||
mat3 scissorMat;
|
||||
mat3 paintMat;
|
||||
vec4 innerCol;
|
||||
vec4 outerCol;
|
||||
vec2 scissorExt;
|
||||
vec2 scissorScale;
|
||||
vec2 extent;
|
||||
float radius;
|
||||
float feather;
|
||||
float strokeMult;
|
||||
float strokeThr;
|
||||
int texType;
|
||||
int type;
|
||||
};
|
||||
|
||||
layout(location = 0) in vec2 ftcoord;
|
||||
layout(location = 1) in vec2 fpos;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
float sdroundrect(vec2 pt, vec2 ext, float rad) {
|
||||
vec2 ext2 = ext - vec2(rad,rad);
|
||||
vec2 d = abs(pt) - ext2;
|
||||
return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad;
|
||||
}
|
||||
|
||||
// Scissoring
|
||||
float scissorMask(vec2 p) {
|
||||
vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt);
|
||||
sc = vec2(0.5,0.5) - sc * scissorScale;
|
||||
return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0);
|
||||
}
|
||||
|
||||
// Stroke - from [0..1] to clipped pyramid, where the slope is 1px.
|
||||
float strokeMask() {
|
||||
return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult) * min(1.0, ftcoord.y);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
vec4 result;
|
||||
float scissor = scissorMask(fpos);
|
||||
float strokeAlpha = strokeMask();
|
||||
|
||||
if (strokeAlpha < strokeThr) discard;
|
||||
|
||||
if (type == 0) { // Gradient
|
||||
// Calculate gradient color using box gradient
|
||||
vec2 pt = (paintMat * vec3(fpos,1.0)).xy;
|
||||
float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0);
|
||||
vec4 color = mix(innerCol,outerCol,d);
|
||||
// Combine alpha
|
||||
color *= strokeAlpha * scissor;
|
||||
result = color;
|
||||
} else if (type == 1) { // Image
|
||||
// Calculate color fron texture
|
||||
vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent;
|
||||
vec4 color = texture(tex, pt);
|
||||
|
||||
if (texType == 1) color = vec4(color.xyz*color.w,color.w);
|
||||
if (texType == 2) color = vec4(color.x);
|
||||
// Apply color tint and alpha.
|
||||
color *= innerCol;
|
||||
// Combine alpha
|
||||
color *= strokeAlpha * scissor;
|
||||
result = color;
|
||||
} else if (type == 2) { // Stencil fill
|
||||
result = vec4(1,1,1,1);
|
||||
} else if (type == 3) { // Textured tris
|
||||
|
||||
vec4 color = texture(tex, ftcoord);
|
||||
|
||||
if (texType == 1) color = vec4(color.xyz*color.w,color.w);
|
||||
if (texType == 2) color = vec4(color.x);
|
||||
color *= scissor;
|
||||
result = color * innerCol;
|
||||
}
|
||||
|
||||
outColor = result;
|
||||
};
|
||||
@@ -1,76 +0,0 @@
|
||||
#version 460
|
||||
|
||||
layout(binding = 0) uniform sampler2D tex;
|
||||
|
||||
layout(std140, binding = 0) uniform frag {
|
||||
mat3 scissorMat;
|
||||
mat3 paintMat;
|
||||
vec4 innerCol;
|
||||
vec4 outerCol;
|
||||
vec2 scissorExt;
|
||||
vec2 scissorScale;
|
||||
vec2 extent;
|
||||
float radius;
|
||||
float feather;
|
||||
float strokeMult;
|
||||
float strokeThr;
|
||||
int texType;
|
||||
int type;
|
||||
};
|
||||
|
||||
layout(location = 0) in vec2 ftcoord;
|
||||
layout(location = 1) in vec2 fpos;
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
float sdroundrect(vec2 pt, vec2 ext, float rad) {
|
||||
vec2 ext2 = ext - vec2(rad,rad);
|
||||
vec2 d = abs(pt) - ext2;
|
||||
return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad;
|
||||
}
|
||||
|
||||
// Scissoring
|
||||
float scissorMask(vec2 p) {
|
||||
vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt);
|
||||
sc = vec2(0.5,0.5) - sc * scissorScale;
|
||||
return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
vec4 result;
|
||||
float scissor = scissorMask(fpos);
|
||||
float strokeAlpha = 1.0;
|
||||
|
||||
if (type == 0) { // Gradient
|
||||
// Calculate gradient color using box gradient
|
||||
vec2 pt = (paintMat * vec3(fpos,1.0)).xy;
|
||||
float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0);
|
||||
vec4 color = mix(innerCol,outerCol,d);
|
||||
// Combine alpha
|
||||
color *= strokeAlpha * scissor;
|
||||
result = color;
|
||||
} else if (type == 1) { // Image
|
||||
// Calculate color fron texture
|
||||
vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent;
|
||||
vec4 color = texture(tex, pt);
|
||||
|
||||
if (texType == 1) color = vec4(color.xyz*color.w,color.w);
|
||||
if (texType == 2) color = vec4(color.x);
|
||||
// Apply color tint and alpha.
|
||||
color *= innerCol;
|
||||
// Combine alpha
|
||||
color *= strokeAlpha * scissor;
|
||||
result = color;
|
||||
} else if (type == 2) { // Stencil fill
|
||||
result = vec4(1,1,1,1);
|
||||
} else if (type == 3) { // Textured tris
|
||||
|
||||
vec4 color = texture(tex, ftcoord);
|
||||
|
||||
if (texType == 1) color = vec4(color.xyz*color.w,color.w);
|
||||
if (texType == 2) color = vec4(color.x);
|
||||
color *= scissor;
|
||||
result = color * innerCol;
|
||||
}
|
||||
|
||||
outColor = result;
|
||||
};
|
||||
@@ -1,17 +0,0 @@
|
||||
#version 460
|
||||
|
||||
layout (location = 0) in vec2 vertex;
|
||||
layout (location = 1) in vec2 tcoord;
|
||||
layout (location = 0) out vec2 ftcoord;
|
||||
layout (location = 1) out vec2 fpos;
|
||||
|
||||
layout (std140, binding = 0) uniform View
|
||||
{
|
||||
vec2 size;
|
||||
} view;
|
||||
|
||||
void main(void) {
|
||||
ftcoord = tcoord;
|
||||
fpos = vertex;
|
||||
gl_Position = vec4(2.0*vertex.x/view.size.x - 1.0, 1.0 - 2.0*vertex.y/view.size.y, 0, 1);
|
||||
};
|
||||
@@ -1,545 +0,0 @@
|
||||
#include "dk_renderer.hpp"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <switch.h>
|
||||
|
||||
#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES /* Enforces GLSL std140/std430 alignment rules for glm types. */
|
||||
#define GLM_FORCE_INTRINSICS /* Enables usage of SIMD CPU instructions (requiring the above as well). */
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
namespace nvg {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array VertexBufferState = { DkVtxBufferState{sizeof(NVGvertex), 0}, };
|
||||
|
||||
constexpr std::array VertexAttribState = {
|
||||
DkVtxAttribState{0, 0, offsetof(NVGvertex, x), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0},
|
||||
DkVtxAttribState{0, 0, offsetof(NVGvertex, u), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0},
|
||||
};
|
||||
|
||||
struct View {
|
||||
glm::vec2 size;
|
||||
};
|
||||
|
||||
void UpdateImage(dk::Image &image, CMemPool &scratchPool, dk::Device device, dk::Queue transferQueue, int type, int x, int y, int w, int h, const u8 *data) {
|
||||
/* Do not proceed if no data is provided upfront. */
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Allocate memory from the pool for the image. */
|
||||
const size_t imageSize = type == NVG_TEXTURE_RGBA ? w * h * 4 : w * h;
|
||||
CMemPool::Handle tempimgmem = scratchPool.allocate(imageSize, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT);
|
||||
memcpy(tempimgmem.getCpuAddr(), data, imageSize);
|
||||
|
||||
dk::UniqueCmdBuf tempcmdbuf = dk::CmdBufMaker{device}.create();
|
||||
CMemPool::Handle tempcmdmem = scratchPool.allocate(DK_MEMBLOCK_ALIGNMENT);
|
||||
tempcmdbuf.addMemory(tempcmdmem.getMemBlock(), tempcmdmem.getOffset(), tempcmdmem.getSize());
|
||||
|
||||
dk::ImageView imageView{image};
|
||||
tempcmdbuf.copyBufferToImage({ tempimgmem.getGpuAddr() }, imageView, { static_cast<uint32_t>(x), static_cast<uint32_t>(y), 0, static_cast<uint32_t>(w), static_cast<uint32_t>(h), 1 });
|
||||
|
||||
transferQueue.submitCommands(tempcmdbuf.finishList());
|
||||
transferQueue.waitIdle();
|
||||
|
||||
/* Destroy temp mem. */
|
||||
tempcmdmem.destroy();
|
||||
tempimgmem.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Texture::Texture(int id) : m_id(id) { /* ... */ }
|
||||
|
||||
Texture::~Texture() {
|
||||
m_image_mem.destroy();
|
||||
}
|
||||
|
||||
void Texture::Initialize(CMemPool &image_pool, CMemPool &scratch_pool, dk::Device device, dk::Queue queue, int type, int w, int h, int image_flags, const u8 *data) {
|
||||
m_texture_descriptor = {
|
||||
.width = w,
|
||||
.height = h,
|
||||
.type = type,
|
||||
.flags = image_flags,
|
||||
};
|
||||
|
||||
/* Create an image layout. */
|
||||
dk::ImageLayout layout;
|
||||
auto layout_maker = dk::ImageLayoutMaker{device}.setFlags(0).setDimensions(w, h);
|
||||
if (type == NVG_TEXTURE_RGBA) {
|
||||
layout_maker.setFormat(DkImageFormat_RGBA8_Unorm);
|
||||
} else {
|
||||
layout_maker.setFormat(DkImageFormat_R8_Unorm);
|
||||
}
|
||||
layout_maker.initialize(layout);
|
||||
|
||||
/* Initialize image. */
|
||||
m_image_mem = image_pool.allocate(layout.getSize(), layout.getAlignment());
|
||||
m_image.initialize(layout, m_image_mem.getMemBlock(), m_image_mem.getOffset());
|
||||
m_image_descriptor.initialize(m_image);
|
||||
|
||||
/* Only update the image if the data isn't null. */
|
||||
if (data != nullptr) {
|
||||
UpdateImage(m_image, scratch_pool, device, queue, type, 0, 0, w, h, data);
|
||||
}
|
||||
}
|
||||
|
||||
int Texture::GetId() {
|
||||
return m_id;
|
||||
}
|
||||
|
||||
const DKNVGtextureDescriptor &Texture::GetDescriptor() {
|
||||
return m_texture_descriptor;
|
||||
}
|
||||
|
||||
dk::Image &Texture::GetImage() {
|
||||
return m_image;
|
||||
}
|
||||
|
||||
dk::ImageDescriptor &Texture::GetImageDescriptor() {
|
||||
return m_image_descriptor;
|
||||
}
|
||||
|
||||
DkRenderer::DkRenderer(unsigned int view_width, unsigned int view_height, dk::Device device, dk::Queue queue, CMemPool &image_mem_pool, CMemPool &code_mem_pool, CMemPool &data_mem_pool) :
|
||||
m_view_width(view_width), m_view_height(view_height), m_device(device), m_queue(queue), m_image_mem_pool(image_mem_pool), m_code_mem_pool(code_mem_pool), m_data_mem_pool(data_mem_pool), m_image_descriptor_mappings({0})
|
||||
{
|
||||
/* Create a dynamic command buffer and allocate memory for it. */
|
||||
m_dyn_cmd_buf = dk::CmdBufMaker{m_device}.create();
|
||||
m_dyn_cmd_mem.allocate(m_data_mem_pool, DynamicCmdSize);
|
||||
|
||||
m_image_descriptor_set.allocate(m_data_mem_pool);
|
||||
m_sampler_descriptor_set.allocate(m_data_mem_pool);
|
||||
|
||||
m_view_uniform_buffer = m_data_mem_pool.allocate(sizeof(View), DK_UNIFORM_BUF_ALIGNMENT);
|
||||
m_frag_uniform_buffer = m_data_mem_pool.allocate(sizeof(FragmentUniformSize), DK_UNIFORM_BUF_ALIGNMENT);
|
||||
|
||||
/* Create and bind preset samplers. */
|
||||
dk::UniqueCmdBuf init_cmd_buf = dk::CmdBufMaker{m_device}.create();
|
||||
CMemPool::Handle init_cmd_mem = m_data_mem_pool.allocate(DK_MEMBLOCK_ALIGNMENT);
|
||||
init_cmd_buf.addMemory(init_cmd_mem.getMemBlock(), init_cmd_mem.getOffset(), init_cmd_mem.getSize());
|
||||
|
||||
for (u8 i = 0; i < SamplerType_Total; i++) {
|
||||
const DkFilter filter = (i & SamplerType_Nearest) ? DkFilter_Nearest : DkFilter_Linear;
|
||||
const DkMipFilter mip_filter = (i & SamplerType_Nearest) ? DkMipFilter_Nearest : DkMipFilter_Linear;
|
||||
const DkWrapMode u_wrap_mode = (i & SamplerType_RepeatX) ? DkWrapMode_Repeat : DkWrapMode_ClampToEdge;
|
||||
const DkWrapMode v_wrap_mode = (i & SamplerType_RepeatY) ? DkWrapMode_Repeat : DkWrapMode_ClampToEdge;
|
||||
|
||||
auto sampler = dk::Sampler{};
|
||||
auto sampler_descriptor = dk::SamplerDescriptor{};
|
||||
sampler.setFilter(filter, filter, (i & SamplerType_MipFilter) ? mip_filter : DkMipFilter_None);
|
||||
sampler.setWrapMode(u_wrap_mode, v_wrap_mode);
|
||||
sampler_descriptor.initialize(sampler);
|
||||
m_sampler_descriptor_set.update(init_cmd_buf, i, sampler_descriptor);
|
||||
}
|
||||
|
||||
/* Flush the descriptor cache. */
|
||||
init_cmd_buf.barrier(DkBarrier_None, DkInvalidateFlags_Descriptors);
|
||||
|
||||
m_sampler_descriptor_set.bindForSamplers(init_cmd_buf);
|
||||
m_image_descriptor_set.bindForImages(init_cmd_buf);
|
||||
|
||||
m_queue.submitCommands(init_cmd_buf.finishList());
|
||||
m_queue.waitIdle();
|
||||
|
||||
init_cmd_mem.destroy();
|
||||
init_cmd_buf.destroy();
|
||||
}
|
||||
|
||||
DkRenderer::~DkRenderer() {
|
||||
if (m_vertex_buffer) {
|
||||
m_vertex_buffer->destroy();
|
||||
}
|
||||
|
||||
m_view_uniform_buffer.destroy();
|
||||
m_frag_uniform_buffer.destroy();
|
||||
m_textures.clear();
|
||||
}
|
||||
|
||||
int DkRenderer::AcquireImageDescriptor(std::shared_ptr<Texture> texture, int image) {
|
||||
int free_image_descriptor = m_last_image_descriptor + 1;
|
||||
int mapping = 0;
|
||||
|
||||
for (int desc = 0; desc <= m_last_image_descriptor; desc++) {
|
||||
mapping = m_image_descriptor_mappings[desc];
|
||||
|
||||
/* We've found the image descriptor requested. */
|
||||
if (mapping == image) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
/* Update the free image descriptor. */
|
||||
if (mapping == 0 && free_image_descriptor == m_last_image_descriptor + 1) {
|
||||
free_image_descriptor = desc;
|
||||
}
|
||||
}
|
||||
|
||||
/* No descriptors are free. */
|
||||
if (free_image_descriptor >= static_cast<int>(MaxImages)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Update descriptor sets. */
|
||||
m_image_descriptor_set.update(m_dyn_cmd_buf, free_image_descriptor, texture->GetImageDescriptor());
|
||||
|
||||
/* Flush the descriptor cache. */
|
||||
m_dyn_cmd_buf.barrier(DkBarrier_None, DkInvalidateFlags_Descriptors);
|
||||
|
||||
/* Update the map. */
|
||||
m_image_descriptor_mappings[free_image_descriptor] = image;
|
||||
m_last_image_descriptor = free_image_descriptor;
|
||||
return free_image_descriptor;
|
||||
}
|
||||
|
||||
void DkRenderer::FreeImageDescriptor(int image) {
|
||||
for (int desc = 0; desc <= m_last_image_descriptor; desc++) {
|
||||
if (m_image_descriptor_mappings[desc] == image) {
|
||||
m_image_descriptor_mappings[desc] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DkRenderer::UpdateVertexBuffer(const void *data, size_t size) {
|
||||
/* Destroy the existing vertex buffer if it is too small. */
|
||||
if (m_vertex_buffer && m_vertex_buffer->getSize() < size) {
|
||||
m_vertex_buffer->destroy();
|
||||
m_vertex_buffer.reset();
|
||||
}
|
||||
|
||||
/* Create a new buffer if needed. */
|
||||
if (!m_vertex_buffer) {
|
||||
m_vertex_buffer = m_data_mem_pool.allocate(size);
|
||||
}
|
||||
|
||||
/* Copy data to the vertex buffer if it exists. */
|
||||
if (m_vertex_buffer) {
|
||||
memcpy(m_vertex_buffer->getCpuAddr(), data, size);
|
||||
}
|
||||
}
|
||||
|
||||
void DkRenderer::SetUniforms(const DKNVGcontext &ctx, int offset, int image) {
|
||||
m_dyn_cmd_buf.pushConstants(m_frag_uniform_buffer.getGpuAddr(), m_frag_uniform_buffer.getSize(), 0, ctx.fragSize, ctx.uniforms + offset);
|
||||
m_dyn_cmd_buf.bindUniformBuffer(DkStage_Fragment, 0, m_frag_uniform_buffer.getGpuAddr(), m_frag_uniform_buffer.getSize());
|
||||
|
||||
/* Attempt to find a texture. */
|
||||
const auto texture = this->FindTexture(image);
|
||||
if (texture == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Acquire an image descriptor. */
|
||||
const int image_desc_id = this->AcquireImageDescriptor(texture, image);
|
||||
if (image_desc_id == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int image_flags = texture->GetDescriptor().flags;
|
||||
uint32_t sampler_id = 0;
|
||||
|
||||
if (image_flags & NVG_IMAGE_GENERATE_MIPMAPS) sampler_id |= SamplerType_MipFilter;
|
||||
if (image_flags & NVG_IMAGE_NEAREST) sampler_id |= SamplerType_Nearest;
|
||||
if (image_flags & NVG_IMAGE_REPEATX) sampler_id |= SamplerType_RepeatX;
|
||||
if (image_flags & NVG_IMAGE_REPEATY) sampler_id |= SamplerType_RepeatY;
|
||||
|
||||
m_dyn_cmd_buf.bindTextures(DkStage_Fragment, 0, dkMakeTextureHandle(image_desc_id, sampler_id));
|
||||
}
|
||||
|
||||
void DkRenderer::DrawFill(const DKNVGcontext &ctx, const DKNVGcall &call) {
|
||||
DKNVGpath *paths = &ctx.paths[call.pathOffset];
|
||||
int npaths = call.pathCount;
|
||||
|
||||
/* Set the stencils to be used. */
|
||||
m_dyn_cmd_buf.setStencil(DkFace_FrontAndBack, 0xFF, 0x0, 0xFF);
|
||||
|
||||
/* Set the depth stencil state. */
|
||||
auto depth_stencil_state = dk::DepthStencilState{}
|
||||
.setStencilTestEnable(true)
|
||||
.setStencilFrontCompareOp(DkCompareOp_Always)
|
||||
.setStencilFrontFailOp(DkStencilOp_Keep)
|
||||
.setStencilFrontDepthFailOp(DkStencilOp_Keep)
|
||||
.setStencilFrontPassOp(DkStencilOp_IncrWrap)
|
||||
.setStencilBackCompareOp(DkCompareOp_Always)
|
||||
.setStencilBackFailOp(DkStencilOp_Keep)
|
||||
.setStencilBackDepthFailOp(DkStencilOp_Keep)
|
||||
.setStencilBackPassOp(DkStencilOp_DecrWrap);
|
||||
m_dyn_cmd_buf.bindDepthStencilState(depth_stencil_state);
|
||||
|
||||
/* Configure for shape drawing. */
|
||||
m_dyn_cmd_buf.bindColorWriteState(dk::ColorWriteState{}.setMask(0, 0));
|
||||
this->SetUniforms(ctx, call.uniformOffset, 0);
|
||||
m_dyn_cmd_buf.bindRasterizerState(dk::RasterizerState{}.setCullMode(DkFace_None));
|
||||
|
||||
/* Draw vertices. */
|
||||
for (int i = 0; i < npaths; i++) {
|
||||
m_dyn_cmd_buf.draw(DkPrimitive_TriangleFan, paths[i].fillCount, 1, paths[i].fillOffset, 0);
|
||||
}
|
||||
|
||||
m_dyn_cmd_buf.bindColorWriteState(dk::ColorWriteState{});
|
||||
this->SetUniforms(ctx, call.uniformOffset + ctx.fragSize, call.image);
|
||||
m_dyn_cmd_buf.bindRasterizerState(dk::RasterizerState{});
|
||||
|
||||
if (ctx.flags & NVG_ANTIALIAS) {
|
||||
/* Configure stencil anti-aliasing. */
|
||||
depth_stencil_state
|
||||
.setStencilFrontCompareOp(DkCompareOp_Equal)
|
||||
.setStencilFrontFailOp(DkStencilOp_Keep)
|
||||
.setStencilFrontDepthFailOp(DkStencilOp_Keep)
|
||||
.setStencilFrontPassOp(DkStencilOp_Keep)
|
||||
.setStencilBackCompareOp(DkCompareOp_Equal)
|
||||
.setStencilBackFailOp(DkStencilOp_Keep)
|
||||
.setStencilBackDepthFailOp(DkStencilOp_Keep)
|
||||
.setStencilBackPassOp(DkStencilOp_Keep);
|
||||
m_dyn_cmd_buf.bindDepthStencilState(depth_stencil_state);
|
||||
|
||||
/* Draw fringes. */
|
||||
for (int i = 0; i < npaths; i++) {
|
||||
m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, paths[i].strokeCount, 1, paths[i].strokeOffset, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Configure and draw fill. */
|
||||
depth_stencil_state
|
||||
.setStencilFrontCompareOp(DkCompareOp_NotEqual)
|
||||
.setStencilFrontFailOp(DkStencilOp_Zero)
|
||||
.setStencilFrontDepthFailOp(DkStencilOp_Zero)
|
||||
.setStencilFrontPassOp(DkStencilOp_Zero)
|
||||
.setStencilBackCompareOp(DkCompareOp_NotEqual)
|
||||
.setStencilBackFailOp(DkStencilOp_Zero)
|
||||
.setStencilBackDepthFailOp(DkStencilOp_Zero)
|
||||
.setStencilBackPassOp(DkStencilOp_Zero);
|
||||
m_dyn_cmd_buf.bindDepthStencilState(depth_stencil_state);
|
||||
|
||||
m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, call.triangleCount, 1, call.triangleOffset, 0);
|
||||
|
||||
/* Reset the depth stencil state to default. */
|
||||
m_dyn_cmd_buf.bindDepthStencilState(dk::DepthStencilState{});
|
||||
}
|
||||
|
||||
void DkRenderer::DrawConvexFill(const DKNVGcontext &ctx, const DKNVGcall &call) {
|
||||
DKNVGpath *paths = &ctx.paths[call.pathOffset];
|
||||
int npaths = call.pathCount;
|
||||
|
||||
this->SetUniforms(ctx, call.uniformOffset, call.image);
|
||||
|
||||
for (int i = 0; i < npaths; i++) {
|
||||
m_dyn_cmd_buf.draw(DkPrimitive_TriangleFan, paths[i].fillCount, 1, paths[i].fillOffset, 0);
|
||||
|
||||
/* Draw fringes. */
|
||||
if (paths[i].strokeCount > 0) {
|
||||
m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, paths[i].strokeCount, 1, paths[i].strokeOffset, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DkRenderer::DrawStroke(const DKNVGcontext &ctx, const DKNVGcall &call) {
|
||||
DKNVGpath* paths = &ctx.paths[call.pathOffset];
|
||||
int npaths = call.pathCount;
|
||||
|
||||
if (ctx.flags & NVG_STENCIL_STROKES) {
|
||||
/* Set the stencil to be used. */
|
||||
m_dyn_cmd_buf.setStencil(DkFace_Front, 0xFF, 0x0, 0xFF);
|
||||
|
||||
/* Configure for filling the stroke base without overlap. */
|
||||
auto depth_stencil_state = dk::DepthStencilState{}
|
||||
.setStencilTestEnable(true)
|
||||
.setStencilFrontCompareOp(DkCompareOp_Equal)
|
||||
.setStencilFrontFailOp(DkStencilOp_Keep)
|
||||
.setStencilFrontDepthFailOp(DkStencilOp_Keep)
|
||||
.setStencilFrontPassOp(DkStencilOp_Incr);
|
||||
m_dyn_cmd_buf.bindDepthStencilState(depth_stencil_state);
|
||||
this->SetUniforms(ctx, call.uniformOffset + ctx.fragSize, call.image);
|
||||
|
||||
/* Draw vertices. */
|
||||
for (int i = 0; i < npaths; i++) {
|
||||
m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, paths[i].strokeCount, 1, paths[i].strokeOffset, 0);
|
||||
}
|
||||
|
||||
/* Configure for drawing anti-aliased pixels. */
|
||||
depth_stencil_state.setStencilFrontPassOp(DkStencilOp_Keep);
|
||||
m_dyn_cmd_buf.bindDepthStencilState(depth_stencil_state);
|
||||
this->SetUniforms(ctx, call.uniformOffset, call.image);
|
||||
|
||||
/* Draw vertices. */
|
||||
for (int i = 0; i < npaths; i++) {
|
||||
m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, paths[i].strokeCount, 1, paths[i].strokeOffset, 0);
|
||||
}
|
||||
|
||||
/* Configure for clearing the stencil buffer. */
|
||||
depth_stencil_state
|
||||
.setStencilTestEnable(true)
|
||||
.setStencilFrontCompareOp(DkCompareOp_Always)
|
||||
.setStencilFrontFailOp(DkStencilOp_Zero)
|
||||
.setStencilFrontDepthFailOp(DkStencilOp_Zero)
|
||||
.setStencilFrontPassOp(DkStencilOp_Zero);
|
||||
m_dyn_cmd_buf.bindDepthStencilState(depth_stencil_state);
|
||||
|
||||
/* Draw vertices. */
|
||||
for (int i = 0; i < npaths; i++) {
|
||||
m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, paths[i].strokeCount, 1, paths[i].strokeOffset, 0);
|
||||
}
|
||||
|
||||
/* Reset the depth stencil state to default. */
|
||||
m_dyn_cmd_buf.bindDepthStencilState(dk::DepthStencilState{});
|
||||
} else {
|
||||
this->SetUniforms(ctx, call.uniformOffset, call.image);
|
||||
|
||||
/* Draw vertices. */
|
||||
for (int i = 0; i < npaths; i++) {
|
||||
m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, paths[i].strokeCount, 1, paths[i].strokeOffset, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DkRenderer::DrawTriangles(const DKNVGcontext &ctx, const DKNVGcall &call) {
|
||||
this->SetUniforms(ctx, call.uniformOffset, call.image);
|
||||
m_dyn_cmd_buf.draw(DkPrimitive_Triangles, call.triangleCount, 1, call.triangleOffset, 0);
|
||||
}
|
||||
|
||||
int DkRenderer::Create(DKNVGcontext &ctx) {
|
||||
m_vertex_shader.load(m_code_mem_pool, "romfs:/shaders/fill_vsh.dksh");
|
||||
|
||||
/* Load the appropriate fragment shader depending on whether AA is enabled. */
|
||||
if (ctx.flags & NVG_ANTIALIAS) {
|
||||
m_fragment_shader.load(m_code_mem_pool, "romfs:/shaders/fill_aa_fsh.dksh");
|
||||
} else {
|
||||
m_fragment_shader.load(m_code_mem_pool, "romfs:/shaders/fill_fsh.dksh");
|
||||
}
|
||||
|
||||
/* Set the size of fragment uniforms. */
|
||||
ctx.fragSize = FragmentUniformSize;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::shared_ptr<Texture> DkRenderer::FindTexture(int id) {
|
||||
for (auto it = m_textures.begin(); it != m_textures.end(); it++) {
|
||||
if ((*it)->GetId() == id) {
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int DkRenderer::CreateTexture(const DKNVGcontext &ctx, int type, int w, int h, int image_flags, const unsigned char* data) {
|
||||
const auto texture_id = m_next_texture_id++;
|
||||
auto texture = std::make_shared<Texture>(texture_id);
|
||||
texture->Initialize(m_image_mem_pool, m_data_mem_pool, m_device, m_queue, type, w, h, image_flags, data);
|
||||
m_textures.push_back(texture);
|
||||
return texture->GetId();
|
||||
}
|
||||
|
||||
int DkRenderer::DeleteTexture(const DKNVGcontext &ctx, int image) {
|
||||
bool found = false;
|
||||
|
||||
for (auto it = m_textures.begin(); it != m_textures.end();) {
|
||||
/* Remove textures with the given id. */
|
||||
if ((*it)->GetId() == image) {
|
||||
it = m_textures.erase(it);
|
||||
found = true;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
/* Free any used image descriptors. */
|
||||
this->FreeImageDescriptor(image);
|
||||
return found;
|
||||
}
|
||||
|
||||
int DkRenderer::UpdateTexture(const DKNVGcontext &ctx, int image, int x, int y, int w, int h, const unsigned char *data) {
|
||||
const std::shared_ptr<Texture> texture = this->FindTexture(image);
|
||||
|
||||
/* Could not find a texture. */
|
||||
if (texture == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const DKNVGtextureDescriptor &tex_desc = texture->GetDescriptor();
|
||||
if (tex_desc.type == NVG_TEXTURE_RGBA) {
|
||||
data += y * tex_desc.width*4;
|
||||
} else {
|
||||
data += y * tex_desc.width;
|
||||
}
|
||||
x = 0;
|
||||
w = tex_desc.width;
|
||||
|
||||
UpdateImage(texture->GetImage(), m_data_mem_pool, m_device, m_queue, tex_desc.type, x, y, w, h, data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DkRenderer::GetTextureSize(const DKNVGcontext &ctx, int image, int *w, int *h) {
|
||||
const auto descriptor = this->GetTextureDescriptor(ctx, image);
|
||||
if (descriptor == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
*w = descriptor->width;
|
||||
*h = descriptor->height;
|
||||
return 1;
|
||||
}
|
||||
|
||||
const DKNVGtextureDescriptor *DkRenderer::GetTextureDescriptor(const DKNVGcontext &ctx, int id) {
|
||||
for (auto it = m_textures.begin(); it != m_textures.end(); it++) {
|
||||
if ((*it)->GetId() == id) {
|
||||
return &(*it)->GetDescriptor();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DkRenderer::Flush(DKNVGcontext &ctx) {
|
||||
if (ctx.ncalls > 0) {
|
||||
/* Prepare dynamic command buffer. */
|
||||
m_dyn_cmd_mem.begin(m_dyn_cmd_buf);
|
||||
|
||||
/* Update buffers with data. */
|
||||
this->UpdateVertexBuffer(ctx.verts, ctx.nverts * sizeof(NVGvertex));
|
||||
|
||||
/* Enable blending. */
|
||||
m_dyn_cmd_buf.bindColorState(dk::ColorState{}.setBlendEnable(0, true));
|
||||
|
||||
/* Setup. */
|
||||
m_dyn_cmd_buf.bindShaders(DkStageFlag_GraphicsMask, { m_vertex_shader, m_fragment_shader });
|
||||
m_dyn_cmd_buf.bindVtxAttribState(VertexAttribState);
|
||||
m_dyn_cmd_buf.bindVtxBufferState(VertexBufferState);
|
||||
m_dyn_cmd_buf.bindVtxBuffer(0, m_vertex_buffer->getGpuAddr(), m_vertex_buffer->getSize());
|
||||
|
||||
/* Push the view size to the uniform buffer and bind it. */
|
||||
const auto view = View{glm::vec2{m_view_width, m_view_height}};
|
||||
m_dyn_cmd_buf.pushConstants(m_view_uniform_buffer.getGpuAddr(), m_view_uniform_buffer.getSize(), 0, sizeof(view), &view);
|
||||
m_dyn_cmd_buf.bindUniformBuffer(DkStage_Vertex, 0, m_view_uniform_buffer.getGpuAddr(), m_view_uniform_buffer.getSize());
|
||||
|
||||
/* Iterate over calls. */
|
||||
for (int i = 0; i < ctx.ncalls; i++) {
|
||||
const DKNVGcall &call = ctx.calls[i];
|
||||
|
||||
/* Perform blending. */
|
||||
m_dyn_cmd_buf.bindBlendStates(0, { dk::BlendState{}.setFactors(static_cast<DkBlendFactor>(call.blendFunc.srcRGB), static_cast<DkBlendFactor>(call.blendFunc.dstRGB), static_cast<DkBlendFactor>(call.blendFunc.srcAlpha), static_cast<DkBlendFactor>(call.blendFunc.dstRGB)) });
|
||||
|
||||
if (call.type == DKNVG_FILL) {
|
||||
this->DrawFill(ctx, call);
|
||||
} else if (call.type == DKNVG_CONVEXFILL) {
|
||||
this->DrawConvexFill(ctx, call);
|
||||
} else if (call.type == DKNVG_STROKE) {
|
||||
this->DrawStroke(ctx, call);
|
||||
} else if (call.type == DKNVG_TRIANGLES) {
|
||||
this->DrawTriangles(ctx, call);
|
||||
}
|
||||
}
|
||||
|
||||
m_queue.submitCommands(m_dyn_cmd_mem.end(m_dyn_cmd_buf));
|
||||
}
|
||||
|
||||
/* Reset calls. */
|
||||
ctx.nverts = 0;
|
||||
ctx.npaths = 0;
|
||||
ctx.ncalls = 0;
|
||||
ctx.nuniforms = 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** CApplication.cpp: Wrapper class containing common application boilerplate
|
||||
*/
|
||||
#include "CApplication.h"
|
||||
|
||||
CApplication::CApplication()
|
||||
{
|
||||
appletLockExit();
|
||||
appletSetFocusHandlingMode(AppletFocusHandlingMode_NoSuspend);
|
||||
}
|
||||
|
||||
CApplication::~CApplication()
|
||||
{
|
||||
appletSetFocusHandlingMode(AppletFocusHandlingMode_SuspendHomeSleep);
|
||||
appletUnlockExit();
|
||||
}
|
||||
|
||||
void CApplication::run()
|
||||
{
|
||||
u64 tick_ref = armGetSystemTick();
|
||||
u64 tick_saved = tick_ref;
|
||||
bool focused = appletGetFocusState() == AppletFocusState_InFocus;
|
||||
|
||||
onOperationMode(appletGetOperationMode());
|
||||
|
||||
for (;;)
|
||||
{
|
||||
u32 msg = 0;
|
||||
Result rc = appletGetMessage(&msg);
|
||||
if (R_SUCCEEDED(rc))
|
||||
{
|
||||
bool should_close = !appletProcessMessage(msg);
|
||||
if (should_close)
|
||||
return;
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case AppletMessage_FocusStateChanged:
|
||||
{
|
||||
bool old_focused = focused;
|
||||
AppletFocusState state = appletGetFocusState();
|
||||
focused = state == AppletFocusState_InFocus;
|
||||
|
||||
onFocusState(state);
|
||||
if (focused == old_focused)
|
||||
break;
|
||||
if (focused)
|
||||
{
|
||||
appletSetFocusHandlingMode(AppletFocusHandlingMode_NoSuspend);
|
||||
tick_ref += armGetSystemTick() - tick_saved;
|
||||
}
|
||||
else
|
||||
{
|
||||
tick_saved = armGetSystemTick();
|
||||
appletSetFocusHandlingMode(AppletFocusHandlingMode_SuspendHomeSleepNotify);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AppletMessage_OperationModeChanged:
|
||||
onOperationMode(appletGetOperationMode());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (focused && !onFrame(armTicksToNs(armGetSystemTick() - tick_ref)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** CExternalImage.cpp: Utility class for loading images from the filesystem
|
||||
*/
|
||||
#include "CExternalImage.h"
|
||||
#include "FileLoader.h"
|
||||
|
||||
bool CExternalImage::load(CMemPool& imagePool, CMemPool& scratchPool, dk::Device device, dk::Queue transferQueue, const char* path, uint32_t width, uint32_t height, DkImageFormat format, uint32_t flags)
|
||||
{
|
||||
CMemPool::Handle tempimgmem = LoadFile(scratchPool, path, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT);
|
||||
if (!tempimgmem)
|
||||
return false;
|
||||
|
||||
dk::UniqueCmdBuf tempcmdbuf = dk::CmdBufMaker{device}.create();
|
||||
CMemPool::Handle tempcmdmem = scratchPool.allocate(DK_MEMBLOCK_ALIGNMENT);
|
||||
tempcmdbuf.addMemory(tempcmdmem.getMemBlock(), tempcmdmem.getOffset(), tempcmdmem.getSize());
|
||||
|
||||
dk::ImageLayout layout;
|
||||
dk::ImageLayoutMaker{device}
|
||||
.setFlags(flags)
|
||||
.setFormat(format)
|
||||
.setDimensions(width, height)
|
||||
.initialize(layout);
|
||||
|
||||
m_mem = imagePool.allocate(layout.getSize(), layout.getAlignment());
|
||||
m_image.initialize(layout, m_mem.getMemBlock(), m_mem.getOffset());
|
||||
m_descriptor.initialize(m_image);
|
||||
|
||||
dk::ImageView imageView{m_image};
|
||||
tempcmdbuf.copyBufferToImage({ tempimgmem.getGpuAddr() }, imageView, { 0, 0, 0, width, height, 1 });
|
||||
transferQueue.submitCommands(tempcmdbuf.finishList());
|
||||
transferQueue.waitIdle();
|
||||
|
||||
tempcmdmem.destroy();
|
||||
tempimgmem.destroy();
|
||||
return true;
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** CIntrusiveTree.cpp: Intrusive red-black tree helper class
|
||||
*/
|
||||
#include "CIntrusiveTree.h"
|
||||
|
||||
// This red-black tree implementation is mostly based on mtheall's work,
|
||||
// which can be found here:
|
||||
// https://github.com/smealum/ctrulib/tree/master/libctru/source/util/rbtree
|
||||
|
||||
void CIntrusiveTreeBase::rotate(N* node, N::Leaf leaf)
|
||||
{
|
||||
N *tmp = node->child(leaf);
|
||||
N *parent = node->getParent();
|
||||
|
||||
node->child(leaf) = tmp->child(!leaf);
|
||||
if (tmp->child(!leaf))
|
||||
tmp->child(!leaf)->setParent(node);
|
||||
|
||||
tmp->child(!leaf) = node;
|
||||
tmp->setParent(parent);
|
||||
|
||||
if (parent)
|
||||
{
|
||||
if (node == parent->child(!leaf))
|
||||
parent->child(!leaf) = tmp;
|
||||
else
|
||||
parent->child(leaf) = tmp;
|
||||
}
|
||||
else
|
||||
m_root = tmp;
|
||||
|
||||
node->setParent(tmp);
|
||||
}
|
||||
|
||||
void CIntrusiveTreeBase::recolor(N* parent, N* node)
|
||||
{
|
||||
N *sibling;
|
||||
|
||||
while ((!node || node->isBlack()) && node != m_root)
|
||||
{
|
||||
N::Leaf leaf = node == parent->left() ? N::Right : N::Left;
|
||||
sibling = parent->child(leaf);
|
||||
|
||||
if (sibling->isRed())
|
||||
{
|
||||
sibling->setBlack();
|
||||
parent->setRed();
|
||||
rotate(parent, leaf);
|
||||
sibling = parent->child(leaf);
|
||||
}
|
||||
|
||||
N::Color clr[2];
|
||||
clr[N::Left] = sibling->left() ? sibling->left()->getColor() : N::Black;
|
||||
clr[N::Right] = sibling->right() ? sibling->right()->getColor() : N::Black;
|
||||
|
||||
if (clr[N::Left] == N::Black && clr[N::Right] == N::Black)
|
||||
{
|
||||
sibling->setRed();
|
||||
node = parent;
|
||||
parent = node->getParent();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (clr[leaf] == N::Black)
|
||||
{
|
||||
sibling->child(!leaf)->setBlack();
|
||||
sibling->setRed();
|
||||
rotate(sibling, !leaf);
|
||||
sibling = parent->child(leaf);
|
||||
}
|
||||
|
||||
sibling->setColor(parent->getColor());
|
||||
parent->setBlack();
|
||||
sibling->child(leaf)->setBlack();
|
||||
rotate(parent, leaf);
|
||||
|
||||
node = m_root;
|
||||
}
|
||||
}
|
||||
|
||||
if (node)
|
||||
node->setBlack();
|
||||
}
|
||||
|
||||
auto CIntrusiveTreeBase::walk(N* node, N::Leaf leaf) const -> N*
|
||||
{
|
||||
if (node->child(leaf))
|
||||
{
|
||||
node = node->child(leaf);
|
||||
while (node->child(!leaf))
|
||||
node = node->child(!leaf);
|
||||
}
|
||||
else
|
||||
{
|
||||
N *parent = node->getParent();
|
||||
while (parent && node == parent->child(leaf))
|
||||
{
|
||||
node = parent;
|
||||
parent = node->getParent();
|
||||
}
|
||||
node = parent;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void CIntrusiveTreeBase::insert(N* node, N* parent)
|
||||
{
|
||||
node->left() = node->right() = nullptr;
|
||||
node->setParent(parent);
|
||||
node->setRed();
|
||||
|
||||
while ((parent = node->getParent()) && parent->isRed())
|
||||
{
|
||||
N *grandparent = parent->getParent();
|
||||
N::Leaf leaf = parent == grandparent->left() ? N::Right : N::Left;
|
||||
N *uncle = grandparent->child(leaf);
|
||||
|
||||
if (uncle && uncle->isRed())
|
||||
{
|
||||
uncle->setBlack();
|
||||
parent->setBlack();
|
||||
grandparent->setRed();
|
||||
|
||||
node = grandparent;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parent->child(leaf) == node)
|
||||
{
|
||||
rotate(parent, leaf);
|
||||
|
||||
N* tmp = parent;
|
||||
parent = node;
|
||||
node = tmp;
|
||||
}
|
||||
|
||||
parent->setBlack();
|
||||
grandparent->setRed();
|
||||
rotate(grandparent, !leaf);
|
||||
}
|
||||
}
|
||||
|
||||
m_root->setBlack();
|
||||
}
|
||||
|
||||
void CIntrusiveTreeBase::remove(N* node)
|
||||
{
|
||||
N::Color color;
|
||||
N *child, *parent;
|
||||
|
||||
if (node->left() && node->right())
|
||||
{
|
||||
N *old = node;
|
||||
|
||||
node = node->right();
|
||||
while (node->left())
|
||||
node = node->left();
|
||||
|
||||
parent = old->getParent();
|
||||
if (parent)
|
||||
{
|
||||
if (parent->left() == old)
|
||||
parent->left() = node;
|
||||
else
|
||||
parent->right() = node;
|
||||
}
|
||||
else
|
||||
m_root = node;
|
||||
|
||||
child = node->right();
|
||||
parent = node->getParent();
|
||||
color = node->getColor();
|
||||
|
||||
if (parent == old)
|
||||
parent = node;
|
||||
else
|
||||
{
|
||||
if (child)
|
||||
child->setParent(parent);
|
||||
parent->left() = child;
|
||||
|
||||
node->right() = old->right();
|
||||
old->right()->setParent(node);
|
||||
}
|
||||
|
||||
node->setParent(old->getParent());
|
||||
node->setColor(old->getColor());
|
||||
node->left() = old->left();
|
||||
old->left()->setParent(node);
|
||||
}
|
||||
else
|
||||
{
|
||||
child = node->left() ? node->right() : node->left();
|
||||
parent = node->getParent();
|
||||
color = node->getColor();
|
||||
|
||||
if (child)
|
||||
child->setParent(parent);
|
||||
if (parent)
|
||||
{
|
||||
if (parent->left() == node)
|
||||
parent->left() = child;
|
||||
else
|
||||
parent->right() = child;
|
||||
}
|
||||
else
|
||||
m_root = child;
|
||||
}
|
||||
|
||||
if (color == N::Black)
|
||||
recolor(parent, child);
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** CMemPool.cpp: Pooled dynamic memory allocation manager class
|
||||
*/
|
||||
#include "CMemPool.h"
|
||||
|
||||
inline auto CMemPool::_newSlice() -> Slice*
|
||||
{
|
||||
Slice* ret = m_sliceHeap.pop();
|
||||
if (!ret) ret = (Slice*)::malloc(sizeof(Slice));
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline void CMemPool::_deleteSlice(Slice* s)
|
||||
{
|
||||
if (!s) return;
|
||||
m_sliceHeap.add(s);
|
||||
}
|
||||
|
||||
CMemPool::~CMemPool()
|
||||
{
|
||||
m_memMap.iterate([](Slice* s) { ::free(s); });
|
||||
m_sliceHeap.iterate([](Slice* s) { ::free(s); });
|
||||
m_blocks.iterate([](Block* blk) {
|
||||
blk->m_obj.destroy();
|
||||
::free(blk);
|
||||
});
|
||||
}
|
||||
|
||||
auto CMemPool::allocate(uint32_t size, uint32_t alignment) -> Handle
|
||||
{
|
||||
if (!size) return nullptr;
|
||||
if (alignment & (alignment - 1)) return nullptr;
|
||||
size = (size + alignment - 1) &~ (alignment - 1);
|
||||
#ifdef DEBUG_CMEMPOOL
|
||||
printf("Allocating size=%u alignment=0x%x\n", size, alignment);
|
||||
{
|
||||
Slice* temp = /*m_freeList*/m_memMap.first();
|
||||
while (temp)
|
||||
{
|
||||
printf("-- blk %p | 0x%08x-0x%08x | %s used\n", temp->m_block, temp->m_start, temp->m_end, temp->m_pool ? " " : "not");
|
||||
temp = /*m_freeList*/m_memMap.next(temp);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t start_offset = 0;
|
||||
uint32_t end_offset = 0;
|
||||
Slice* slice = m_freeList.find(size, decltype(m_freeList)::LowerBound);
|
||||
while (slice)
|
||||
{
|
||||
#ifdef DEBUG_CMEMPOOL
|
||||
printf(" * Checking slice 0x%x - 0x%x\n", slice->m_start, slice->m_end);
|
||||
#endif
|
||||
start_offset = (slice->m_start + alignment - 1) &~ (alignment - 1);
|
||||
end_offset = start_offset + size;
|
||||
if (end_offset <= slice->m_end)
|
||||
break;
|
||||
slice = m_freeList.next(slice);
|
||||
}
|
||||
|
||||
if (!slice)
|
||||
{
|
||||
Block* blk = (Block*)::malloc(sizeof(Block));
|
||||
if (!blk)
|
||||
return nullptr;
|
||||
|
||||
uint32_t unusableSize = (m_flags & DkMemBlockFlags_Code) ? DK_SHADER_CODE_UNUSABLE_SIZE : 0;
|
||||
uint32_t blkSize = m_blockSize - unusableSize;
|
||||
blkSize = size > blkSize ? size : blkSize;
|
||||
blkSize = (blkSize + unusableSize + DK_MEMBLOCK_ALIGNMENT - 1) &~ (DK_MEMBLOCK_ALIGNMENT - 1);
|
||||
#ifdef DEBUG_CMEMPOOL
|
||||
printf(" ! Allocating block of size 0x%x\n", blkSize);
|
||||
#endif
|
||||
blk->m_obj = dk::MemBlockMaker{m_dev, blkSize}.setFlags(m_flags).create();
|
||||
if (!blk->m_obj)
|
||||
{
|
||||
::free(blk);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
slice = _newSlice();
|
||||
if (!slice)
|
||||
{
|
||||
blk->m_obj.destroy();
|
||||
::free(blk);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
slice->m_pool = nullptr;
|
||||
slice->m_block = blk;
|
||||
slice->m_start = 0;
|
||||
slice->m_end = blkSize - unusableSize;
|
||||
m_memMap.add(slice);
|
||||
|
||||
blk->m_cpuAddr = blk->m_obj.getCpuAddr();
|
||||
blk->m_gpuAddr = blk->m_obj.getGpuAddr();
|
||||
m_blocks.add(blk);
|
||||
|
||||
start_offset = 0;
|
||||
end_offset = size;
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef DEBUG_CMEMPOOL
|
||||
printf(" * found it\n");
|
||||
#endif
|
||||
m_freeList.remove(slice);
|
||||
}
|
||||
|
||||
if (start_offset != slice->m_start)
|
||||
{
|
||||
Slice* t = _newSlice();
|
||||
if (!t) goto _bad;
|
||||
t->m_pool = nullptr;
|
||||
t->m_block = slice->m_block;
|
||||
t->m_start = slice->m_start;
|
||||
t->m_end = start_offset;
|
||||
#ifdef DEBUG_CMEMPOOL
|
||||
printf("-> subdivide left: %08x-%08x\n", t->m_start, t->m_end);
|
||||
#endif
|
||||
m_memMap.addBefore(slice, t);
|
||||
m_freeList.insert(t, true);
|
||||
slice->m_start = start_offset;
|
||||
}
|
||||
|
||||
if (end_offset != slice->m_end)
|
||||
{
|
||||
Slice* t = _newSlice();
|
||||
if (!t) goto _bad;
|
||||
t->m_pool = nullptr;
|
||||
t->m_block = slice->m_block;
|
||||
t->m_start = end_offset;
|
||||
t->m_end = slice->m_end;
|
||||
#ifdef DEBUG_CMEMPOOL
|
||||
printf("-> subdivide right: %08x-%08x\n", t->m_start, t->m_end);
|
||||
#endif
|
||||
m_memMap.addAfter(slice, t);
|
||||
m_freeList.insert(t, true);
|
||||
slice->m_end = end_offset;
|
||||
}
|
||||
|
||||
slice->m_pool = this;
|
||||
return slice;
|
||||
|
||||
_bad:
|
||||
m_freeList.insert(slice, true);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CMemPool::_destroy(Slice* slice)
|
||||
{
|
||||
slice->m_pool = nullptr;
|
||||
|
||||
Slice* left = m_memMap.prev(slice);
|
||||
Slice* right = m_memMap.next(slice);
|
||||
|
||||
if (left && left->canCoalesce(*slice))
|
||||
{
|
||||
slice->m_start = left->m_start;
|
||||
m_freeList.remove(left);
|
||||
m_memMap.remove(left);
|
||||
_deleteSlice(left);
|
||||
}
|
||||
|
||||
if (right && slice->canCoalesce(*right))
|
||||
{
|
||||
slice->m_end = right->m_end;
|
||||
m_freeList.remove(right);
|
||||
m_memMap.remove(right);
|
||||
_deleteSlice(right);
|
||||
}
|
||||
|
||||
m_freeList.insert(slice, true);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** CShader.cpp: Utility class for loading shaders from the filesystem
|
||||
*/
|
||||
#include "CShader.h"
|
||||
|
||||
struct DkshHeader
|
||||
{
|
||||
uint32_t magic; // DKSH_MAGIC
|
||||
uint32_t header_sz; // sizeof(DkshHeader)
|
||||
uint32_t control_sz;
|
||||
uint32_t code_sz;
|
||||
uint32_t programs_off;
|
||||
uint32_t num_programs;
|
||||
};
|
||||
|
||||
bool CShader::load(CMemPool& pool, const char* path)
|
||||
{
|
||||
FILE* f;
|
||||
DkshHeader hdr;
|
||||
void* ctrlmem;
|
||||
|
||||
m_codemem.destroy();
|
||||
|
||||
f = fopen(path, "rb");
|
||||
if (!f) return false;
|
||||
|
||||
if (!fread(&hdr, sizeof(hdr), 1, f))
|
||||
goto _fail0;
|
||||
|
||||
ctrlmem = malloc(hdr.control_sz);
|
||||
if (!ctrlmem)
|
||||
goto _fail0;
|
||||
|
||||
rewind(f);
|
||||
if (!fread(ctrlmem, hdr.control_sz, 1, f))
|
||||
goto _fail1;
|
||||
|
||||
m_codemem = pool.allocate(hdr.code_sz, DK_SHADER_CODE_ALIGNMENT);
|
||||
if (!m_codemem)
|
||||
goto _fail1;
|
||||
|
||||
if (!fread(m_codemem.getCpuAddr(), hdr.code_sz, 1, f))
|
||||
goto _fail2;
|
||||
|
||||
dk::ShaderMaker{m_codemem.getMemBlock(), m_codemem.getOffset()}
|
||||
.setControl(ctrlmem)
|
||||
.setProgramId(0)
|
||||
.initialize(m_shader);
|
||||
|
||||
free(ctrlmem);
|
||||
fclose(f);
|
||||
return true;
|
||||
|
||||
_fail2:
|
||||
m_codemem.destroy();
|
||||
_fail1:
|
||||
free(ctrlmem);
|
||||
_fail0:
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
** Sample Framework for deko3d Applications
|
||||
** FileLoader.cpp: Helpers for loading data from the filesystem directly into GPU memory
|
||||
*/
|
||||
#include "FileLoader.h"
|
||||
|
||||
CMemPool::Handle LoadFile(CMemPool& pool, const char* path, uint32_t alignment)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return nullptr;
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
uint32_t fsize = ftell(f);
|
||||
rewind(f);
|
||||
|
||||
CMemPool::Handle mem = pool.allocate(fsize, alignment);
|
||||
if (!mem)
|
||||
{
|
||||
fclose(f);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
fread(mem.getCpuAddr(), fsize, 1, f);
|
||||
fclose(f);
|
||||
|
||||
return mem;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
Copyright (C) 2020 fincs
|
||||
|
||||
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 diff suppressed because it is too large
Load Diff
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <switch.h>
|
||||
#include <string.h>
|
||||
#include "ams_su.h"
|
||||
#include "service_guard.h"
|
||||
|
||||
static Service g_amssuSrv;
|
||||
static TransferMemory g_tmem;
|
||||
|
||||
NX_GENERATE_SERVICE_GUARD(amssu);
|
||||
|
||||
Result _amssuInitialize(void) {
|
||||
return smGetService(&g_amssuSrv, "ams:su");
|
||||
}
|
||||
|
||||
void _amssuCleanup(void) {
|
||||
serviceClose(&g_amssuSrv);
|
||||
tmemClose(&g_tmem);
|
||||
}
|
||||
|
||||
Service *amssuGetServiceSession(void) {
|
||||
return &g_amssuSrv;
|
||||
}
|
||||
|
||||
Result amssuGetUpdateInformation(AmsSuUpdateInformation *out, const char *path) {
|
||||
char send_path[FS_MAX_PATH] = {0};
|
||||
strncpy(send_path, path, FS_MAX_PATH-1);
|
||||
send_path[FS_MAX_PATH-1] = 0;
|
||||
|
||||
return serviceDispatchOut(&g_amssuSrv, 0, *out,
|
||||
.buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize },
|
||||
.buffers = { { send_path, FS_MAX_PATH } },
|
||||
);
|
||||
}
|
||||
|
||||
Result amssuValidateUpdate(AmsSuUpdateValidationInfo *out, const char *path) {
|
||||
char send_path[FS_MAX_PATH] = {0};
|
||||
strncpy(send_path, path, FS_MAX_PATH-1);
|
||||
send_path[FS_MAX_PATH-1] = 0;
|
||||
|
||||
return serviceDispatchOut(&g_amssuSrv, 1, *out,
|
||||
.buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize },
|
||||
.buffers = { { send_path, FS_MAX_PATH } },
|
||||
);
|
||||
}
|
||||
|
||||
Result amssuSetupUpdate(void *buffer, size_t size, const char *path, bool exfat) {
|
||||
Result rc = 0;
|
||||
|
||||
if (buffer == NULL) {
|
||||
rc = tmemCreate(&g_tmem, size, Perm_None);
|
||||
} else {
|
||||
rc = tmemCreateFromMemory(&g_tmem, buffer, size, Perm_None);
|
||||
}
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
char send_path[FS_MAX_PATH] = {0};
|
||||
strncpy(send_path, path, FS_MAX_PATH-1);
|
||||
send_path[FS_MAX_PATH-1] = 0;
|
||||
|
||||
const struct {
|
||||
u8 exfat;
|
||||
u64 size;
|
||||
} in = { exfat, g_tmem.size };
|
||||
|
||||
rc = serviceDispatchIn(&g_amssuSrv, 2, in,
|
||||
.in_num_handles = 1,
|
||||
.in_handles = { g_tmem.handle },
|
||||
.buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize },
|
||||
.buffers = { { send_path, FS_MAX_PATH } },
|
||||
);
|
||||
if (R_FAILED((rc))) {
|
||||
tmemClose(&g_tmem);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result amssuSetupUpdateWithVariation(void *buffer, size_t size, const char *path, bool exfat, u32 variation) {
|
||||
Result rc = 0;
|
||||
|
||||
if (buffer == NULL) {
|
||||
rc = tmemCreate(&g_tmem, size, Perm_None);
|
||||
} else {
|
||||
rc = tmemCreateFromMemory(&g_tmem, buffer, size, Perm_None);
|
||||
}
|
||||
if (R_FAILED(rc)) return rc;
|
||||
|
||||
char send_path[FS_MAX_PATH] = {0};
|
||||
strncpy(send_path, path, FS_MAX_PATH-1);
|
||||
send_path[FS_MAX_PATH-1] = 0;
|
||||
|
||||
const struct {
|
||||
u8 exfat;
|
||||
u32 variation;
|
||||
u64 size;
|
||||
} in = { exfat, variation, g_tmem.size };
|
||||
|
||||
rc = serviceDispatchIn(&g_amssuSrv, 3, in,
|
||||
.in_num_handles = 1,
|
||||
.in_handles = { g_tmem.handle },
|
||||
.buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize },
|
||||
.buffers = { { send_path, FS_MAX_PATH } },
|
||||
);
|
||||
if (R_FAILED((rc))) {
|
||||
tmemClose(&g_tmem);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result amssuRequestPrepareUpdate(AsyncResult *a) {
|
||||
memset(a, 0, sizeof(*a));
|
||||
|
||||
Handle event = INVALID_HANDLE;
|
||||
Result rc = serviceDispatch(&g_amssuSrv, 4,
|
||||
.out_num_objects = 1,
|
||||
.out_objects = &a->s,
|
||||
.out_handle_attrs = { SfOutHandleAttr_HipcCopy },
|
||||
.out_handles = &event,
|
||||
);
|
||||
|
||||
if (R_SUCCEEDED(rc))
|
||||
eventLoadRemote(&a->event, event, false);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result amssuGetPrepareUpdateProgress(NsSystemUpdateProgress *out) {
|
||||
return serviceDispatchOut(&g_amssuSrv, 5, *out);
|
||||
}
|
||||
|
||||
Result amssuHasPreparedUpdate(bool *out) {
|
||||
u8 outval = 0;
|
||||
Result rc = serviceDispatchOut(&g_amssuSrv, 6, outval);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (out) *out = outval & 1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result amssuApplyPreparedUpdate() {
|
||||
return serviceDispatch(&g_amssuSrv, 7);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
u32 version;
|
||||
bool exfat_supported;
|
||||
u32 num_firmware_variations;
|
||||
u32 firmware_variation_ids[16];
|
||||
} AmsSuUpdateInformation;
|
||||
|
||||
typedef struct {
|
||||
Result result;
|
||||
Result exfat_result;
|
||||
NcmContentMetaKey invalid_key;
|
||||
NcmContentId invalid_content_id;
|
||||
} AmsSuUpdateValidationInfo;
|
||||
|
||||
Result amssuInitialize();
|
||||
void amssuExit();
|
||||
Service *amssuGetServiceSession(void);
|
||||
|
||||
Result amssuGetUpdateInformation(AmsSuUpdateInformation *out, const char *path);
|
||||
Result amssuValidateUpdate(AmsSuUpdateValidationInfo *out, const char *path);
|
||||
Result amssuSetupUpdate(void *buffer, size_t size, const char *path, bool exfat);
|
||||
Result amssuSetupUpdateWithVariation(void *buffer, size_t size, const char *path, bool exfat, u32 variation);
|
||||
Result amssuRequestPrepareUpdate(AsyncResult *a);
|
||||
Result amssuGetPrepareUpdateProgress(NsSystemUpdateProgress *out);
|
||||
Result amssuHasPreparedUpdate(bool *out);
|
||||
Result amssuApplyPreparedUpdate();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#define DBK_ABORT_UNLESS(expr) \
|
||||
if (!static_cast<bool>(expr)) { \
|
||||
std::abort(); \
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <optional>
|
||||
#include <switch.h>
|
||||
#include <nanovg.h>
|
||||
#include <nanovg_dk.h>
|
||||
#include <nanovg/framework/CApplication.h>
|
||||
#include "ui.hpp"
|
||||
#include "ams_su.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void userAppInit(void) {
|
||||
Result rc = 0;
|
||||
|
||||
if (R_FAILED(rc = romfsInit())) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = spsmInitialize())) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = plInitialize(PlServiceType_User))) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = splInitialize())) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = nsInitialize())) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = hiddbgInitialize())) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void userAppExit(void) {
|
||||
hiddbgExit();
|
||||
nsExit();
|
||||
splExit();
|
||||
plExit();
|
||||
spsmExit();
|
||||
romfsExit();
|
||||
amssuExit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr u32 FramebufferWidth = 1280;
|
||||
static constexpr u32 FramebufferHeight = 720;
|
||||
|
||||
}
|
||||
|
||||
class Daybreak : public CApplication {
|
||||
private:
|
||||
static constexpr unsigned NumFramebuffers = 2;
|
||||
static constexpr unsigned StaticCmdSize = 0x1000;
|
||||
|
||||
dk::UniqueDevice m_device;
|
||||
dk::UniqueQueue m_queue;
|
||||
dk::UniqueSwapchain m_swapchain;
|
||||
|
||||
std::optional<CMemPool> m_pool_images;
|
||||
std::optional<CMemPool> m_pool_code;
|
||||
std::optional<CMemPool> m_pool_data;
|
||||
|
||||
dk::UniqueCmdBuf m_cmd_buf;
|
||||
DkCmdList m_render_cmdlist;
|
||||
|
||||
dk::Image m_depth_buffer;
|
||||
CMemPool::Handle m_depth_buffer_mem;
|
||||
dk::Image m_framebuffers[NumFramebuffers];
|
||||
CMemPool::Handle m_framebuffers_mem[NumFramebuffers];
|
||||
DkCmdList m_framebuffer_cmdlists[NumFramebuffers];
|
||||
|
||||
std::optional<nvg::DkRenderer> m_renderer;
|
||||
NVGcontext *m_vg;
|
||||
int m_standard_font;
|
||||
public:
|
||||
Daybreak() {
|
||||
Result rc = 0;
|
||||
|
||||
/* Create the deko3d device. */
|
||||
m_device = dk::DeviceMaker{}.create();
|
||||
|
||||
/* Create the main queue. */
|
||||
m_queue = dk::QueueMaker{m_device}.setFlags(DkQueueFlags_Graphics).create();
|
||||
|
||||
/* Create the memory pools. */
|
||||
m_pool_images.emplace(m_device, DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image, 16*1024*1024);
|
||||
m_pool_code.emplace(m_device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code, 128*1024);
|
||||
m_pool_data.emplace(m_device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, 1*1024*1024);
|
||||
|
||||
/* Create the static command buffer and feed it freshly allocated memory. */
|
||||
m_cmd_buf = dk::CmdBufMaker{m_device}.create();
|
||||
CMemPool::Handle cmdmem = m_pool_data->allocate(StaticCmdSize);
|
||||
m_cmd_buf.addMemory(cmdmem.getMemBlock(), cmdmem.getOffset(), cmdmem.getSize());
|
||||
|
||||
/* Create the framebuffer resources. */
|
||||
this->CreateFramebufferResources();
|
||||
|
||||
m_renderer.emplace(FramebufferWidth, FramebufferHeight, m_device, m_queue, *m_pool_images, *m_pool_code, *m_pool_data);
|
||||
m_vg = nvgCreateDk(&*m_renderer, NVG_ANTIALIAS | NVG_STENCIL_STROKES);
|
||||
|
||||
|
||||
PlFontData font;
|
||||
if (R_FAILED(rc = plGetSharedFontByType(&font, PlSharedFontType_Standard))) {
|
||||
fatalThrow(rc);
|
||||
}
|
||||
|
||||
m_standard_font = nvgCreateFontMem(m_vg, "switch-standard", static_cast<u8 *>(font.address), font.size, 0);
|
||||
}
|
||||
|
||||
~Daybreak() {
|
||||
/* Destroy the framebuffer resources. This should be done first. */
|
||||
this->DestroyFramebufferResources();
|
||||
|
||||
/* Cleanup vg. */
|
||||
nvgDeleteDk(m_vg);
|
||||
|
||||
/* Destroy the renderer. */
|
||||
m_renderer.reset();
|
||||
}
|
||||
private:
|
||||
void CreateFramebufferResources() {
|
||||
/* Create layout for the depth buffer. */
|
||||
dk::ImageLayout layout_depth_buffer;
|
||||
dk::ImageLayoutMaker{m_device}
|
||||
.setFlags(DkImageFlags_UsageRender | DkImageFlags_HwCompression)
|
||||
.setFormat(DkImageFormat_S8)
|
||||
.setDimensions(FramebufferWidth, FramebufferHeight)
|
||||
.initialize(layout_depth_buffer);
|
||||
|
||||
/* Create the depth buffer. */
|
||||
m_depth_buffer_mem = m_pool_images->allocate(layout_depth_buffer.getSize(), layout_depth_buffer.getAlignment());
|
||||
m_depth_buffer.initialize(layout_depth_buffer, m_depth_buffer_mem.getMemBlock(), m_depth_buffer_mem.getOffset());
|
||||
|
||||
/* Create layout for the framebuffers. */
|
||||
dk::ImageLayout layout_framebuffer;
|
||||
dk::ImageLayoutMaker{m_device}
|
||||
.setFlags(DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression)
|
||||
.setFormat(DkImageFormat_RGBA8_Unorm)
|
||||
.setDimensions(FramebufferWidth, FramebufferHeight)
|
||||
.initialize(layout_framebuffer);
|
||||
|
||||
/* Create the framebuffers. */
|
||||
std::array<DkImage const*, NumFramebuffers> fb_array;
|
||||
const u64 fb_size = layout_framebuffer.getSize();
|
||||
const u32 fb_align = layout_framebuffer.getAlignment();
|
||||
|
||||
for (unsigned int i = 0; i < NumFramebuffers; i++) {
|
||||
/* Allocate a framebuffer. */
|
||||
m_framebuffers_mem[i] = m_pool_images->allocate(fb_size, fb_align);
|
||||
m_framebuffers[i].initialize(layout_framebuffer, m_framebuffers_mem[i].getMemBlock(), m_framebuffers_mem[i].getOffset());
|
||||
|
||||
/* Generate a command list that binds it. */
|
||||
dk::ImageView color_target{ m_framebuffers[i] }, depth_target{ m_depth_buffer };
|
||||
m_cmd_buf.bindRenderTargets(&color_target, &depth_target);
|
||||
m_framebuffer_cmdlists[i] = m_cmd_buf.finishList();
|
||||
|
||||
/* Fill in the array for use later by the swapchain creation code. */
|
||||
fb_array[i] = &m_framebuffers[i];
|
||||
}
|
||||
|
||||
/* Create the swapchain using the framebuffers. */
|
||||
m_swapchain = dk::SwapchainMaker{m_device, nwindowGetDefault(), fb_array}.create();
|
||||
|
||||
/* Generate the main rendering cmdlist. */
|
||||
this->RecordStaticCommands();
|
||||
}
|
||||
|
||||
void DestroyFramebufferResources() {
|
||||
/* Return early if we have nothing to destroy. */
|
||||
if (!m_swapchain) return;
|
||||
|
||||
/* Make sure the queue is idle before destroying anything. */
|
||||
m_queue.waitIdle();
|
||||
|
||||
/* Clear the static cmdbuf, destroying the static cmdlists in the process. */
|
||||
m_cmd_buf.clear();
|
||||
|
||||
/* Destroy the swapchain. */
|
||||
m_swapchain.destroy();
|
||||
|
||||
/* Destroy the framebuffers. */
|
||||
for (unsigned int i = 0; i < NumFramebuffers; i ++) {
|
||||
m_framebuffers_mem[i].destroy();
|
||||
}
|
||||
|
||||
/* Destroy the depth buffer. */
|
||||
m_depth_buffer_mem.destroy();
|
||||
}
|
||||
|
||||
void RecordStaticCommands() {
|
||||
/* Initialize state structs with deko3d defaults. */
|
||||
dk::RasterizerState rasterizer_state;
|
||||
dk::ColorState color_state;
|
||||
dk::ColorWriteState color_write_state;
|
||||
|
||||
/* Configure the viewport and scissor. */
|
||||
m_cmd_buf.setViewports(0, { { 0.0f, 0.0f, FramebufferWidth, FramebufferHeight, 0.0f, 1.0f } });
|
||||
m_cmd_buf.setScissors(0, { { 0, 0, FramebufferWidth, FramebufferHeight } });
|
||||
|
||||
/* Clear the color and depth buffers. */
|
||||
m_cmd_buf.clearColor(0, DkColorMask_RGBA, 0.f, 0.f, 0.f, 1.0f);
|
||||
m_cmd_buf.clearDepthStencil(true, 1.0f, 0xFF, 0);
|
||||
|
||||
/* Bind required state. */
|
||||
m_cmd_buf.bindRasterizerState(rasterizer_state);
|
||||
m_cmd_buf.bindColorState(color_state);
|
||||
m_cmd_buf.bindColorWriteState(color_write_state);
|
||||
|
||||
m_render_cmdlist = m_cmd_buf.finishList();
|
||||
}
|
||||
|
||||
void Render(u64 ns) {
|
||||
/* Acquire a framebuffer from the swapchain (and wait for it to be available). */
|
||||
int slot = m_queue.acquireImage(m_swapchain);
|
||||
|
||||
/* Run the command list that attaches said framebuffer to the queue. */
|
||||
m_queue.submitCommands(m_framebuffer_cmdlists[slot]);
|
||||
|
||||
/* Run the main rendering command list. */
|
||||
m_queue.submitCommands(m_render_cmdlist);
|
||||
|
||||
nvgBeginFrame(m_vg, FramebufferWidth, FramebufferHeight, 1.0f);
|
||||
dbk::RenderMenu(m_vg, ns);
|
||||
nvgEndFrame(m_vg);
|
||||
|
||||
/* Now that we are done rendering, present it to the screen. */
|
||||
m_queue.presentImage(m_swapchain, slot);
|
||||
}
|
||||
|
||||
public:
|
||||
bool onFrame(u64 ns) override {
|
||||
dbk::UpdateMenu(ns);
|
||||
this->Render(ns);
|
||||
return !dbk::IsExitRequested();
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
/* Initialize the menu. */
|
||||
if (argc > 1)
|
||||
dbk::InitializeMenu(FramebufferWidth, FramebufferHeight, argv[1]);
|
||||
else
|
||||
dbk::InitializeMenu(FramebufferWidth, FramebufferHeight);
|
||||
|
||||
Daybreak daybreak;
|
||||
daybreak.run();
|
||||
return 0;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
#include <switch/types.h>
|
||||
#include <switch/result.h>
|
||||
#include <switch/kernel/mutex.h>
|
||||
#include <switch/sf/service.h>
|
||||
#include <switch/services/sm.h>
|
||||
|
||||
typedef struct ServiceGuard {
|
||||
Mutex mutex;
|
||||
u32 refCount;
|
||||
} ServiceGuard;
|
||||
|
||||
NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g)
|
||||
{
|
||||
mutexLock(&g->mutex);
|
||||
return (g->refCount++) == 0;
|
||||
}
|
||||
|
||||
NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void))
|
||||
{
|
||||
if (R_FAILED(rc)) {
|
||||
cleanupFunc();
|
||||
--g->refCount;
|
||||
}
|
||||
mutexUnlock(&g->mutex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void))
|
||||
{
|
||||
mutexLock(&g->mutex);
|
||||
if (g->refCount && (--g->refCount) == 0)
|
||||
cleanupFunc();
|
||||
mutexUnlock(&g->mutex);
|
||||
}
|
||||
|
||||
#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \
|
||||
\
|
||||
static ServiceGuard g_##name##Guard; \
|
||||
NX_INLINE Result _##name##Initialize _paramdecl; \
|
||||
static void _##name##Cleanup(void); \
|
||||
\
|
||||
Result name##Initialize _paramdecl \
|
||||
{ \
|
||||
Result rc = 0; \
|
||||
if (serviceGuardBeginInit(&g_##name##Guard)) \
|
||||
rc = _##name##Initialize _parampass; \
|
||||
return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \
|
||||
} \
|
||||
\
|
||||
void name##Exit(void) \
|
||||
{ \
|
||||
serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \
|
||||
}
|
||||
|
||||
#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ())
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,272 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <nanovg.h>
|
||||
#include <switch.h>
|
||||
#include "ams_su.h"
|
||||
|
||||
namespace dbk {
|
||||
|
||||
struct Button {
|
||||
static constexpr u32 InvalidButtonId = -1;
|
||||
|
||||
u32 id;
|
||||
bool selected;
|
||||
bool enabled;
|
||||
char text[256];
|
||||
float x;
|
||||
float y;
|
||||
float w;
|
||||
float h;
|
||||
|
||||
inline bool IsPositionInBounds(float x, float y) {
|
||||
return x >= this->x && y >= this->y && x < (this->x + this->w) && y < (this->y + this->h);
|
||||
}
|
||||
};
|
||||
|
||||
enum class Direction {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
Invalid,
|
||||
};
|
||||
|
||||
class Menu {
|
||||
protected:
|
||||
static constexpr size_t MaxButtons = 32;
|
||||
static constexpr size_t LogBufferSize = 0x1000;
|
||||
protected:
|
||||
std::array<std::optional<Button>, MaxButtons> m_buttons;
|
||||
const std::shared_ptr<Menu> m_prev_menu;
|
||||
char m_log_buffer[LogBufferSize];
|
||||
protected:
|
||||
void AddButton(u32 id, const char *text, float x, float y, float w, float h);
|
||||
void SetButtonSelected(u32 id, bool selected);
|
||||
void DeselectAllButtons();
|
||||
void SetButtonEnabled(u32 id, bool enabled);
|
||||
|
||||
Button *GetButton(u32 id);
|
||||
Button *GetSelectedButton();
|
||||
Button *GetClosestButtonToSelection(Direction direction);
|
||||
Button *GetTouchedButton();
|
||||
Button *GetActivatedButton();
|
||||
|
||||
void UpdateButtons();
|
||||
void DrawButtons(NVGcontext *vg, u64 ns);
|
||||
|
||||
void LogText(const char *format, ...);
|
||||
public:
|
||||
Menu(std::shared_ptr<Menu> prev_menu) : m_buttons({}), m_prev_menu(prev_menu), m_log_buffer{} { /* ... */ }
|
||||
|
||||
std::shared_ptr<Menu> GetPrevMenu();
|
||||
virtual void Update(u64 ns) = 0;
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) = 0;
|
||||
};
|
||||
|
||||
class AlertMenu : public Menu {
|
||||
protected:
|
||||
static constexpr float WindowWidth = 600.0f;
|
||||
static constexpr float WindowHeight = 214.0f;
|
||||
static constexpr float TitleGap = 90.0f;
|
||||
static constexpr float SubTextHeight = 24.0f;
|
||||
protected:
|
||||
char m_text[0x100];
|
||||
char m_subtext[0x100];
|
||||
char m_result_text[0x20];
|
||||
Result m_rc;
|
||||
public:
|
||||
AlertMenu(std::shared_ptr<Menu> prev_menu, const char *text, const char *subtext, Result rc = 0);
|
||||
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) override;
|
||||
};
|
||||
|
||||
class ErrorMenu : public AlertMenu {
|
||||
private:
|
||||
static constexpr u32 ExitButtonId = 0;
|
||||
public:
|
||||
ErrorMenu(const char *text, const char *subtext, Result rc = 0);
|
||||
|
||||
virtual void Update(u64 ns) override;
|
||||
};
|
||||
|
||||
class WarningMenu : public AlertMenu {
|
||||
private:
|
||||
static constexpr u32 BackButtonId = 0;
|
||||
static constexpr u32 ContinueButtonId = 1;
|
||||
private:
|
||||
const std::shared_ptr<Menu> m_next_menu;
|
||||
public:
|
||||
WarningMenu(std::shared_ptr<Menu> prev_menu, std::shared_ptr<Menu> next_menu, const char *text, const char *subtext, Result rc = 0);
|
||||
|
||||
virtual void Update(u64 ns) override;
|
||||
};
|
||||
|
||||
class MainMenu : public Menu {
|
||||
private:
|
||||
static constexpr u32 InstallButtonId = 0;
|
||||
static constexpr u32 ExitButtonId = 1;
|
||||
|
||||
static constexpr float WindowWidth = 400.0f;
|
||||
static constexpr float WindowHeight = 240.0f;
|
||||
static constexpr float TitleGap = 90.0f;
|
||||
public:
|
||||
MainMenu();
|
||||
|
||||
virtual void Update(u64 ns) override;
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) override;
|
||||
};
|
||||
|
||||
class FileMenu : public Menu {
|
||||
private:
|
||||
struct FileEntry {
|
||||
char name[FS_MAX_PATH];
|
||||
};
|
||||
private:
|
||||
static constexpr size_t MaxFileRows = 11;
|
||||
|
||||
static constexpr float WindowWidth = 1200.0f;
|
||||
static constexpr float WindowHeight = 680.0f;
|
||||
static constexpr float TitleGap = 90.0f;
|
||||
static constexpr float TextBackgroundOffset = 20.0f;
|
||||
static constexpr float FileRowHeight = 40.0f;
|
||||
static constexpr float FileRowGap = 10.0f;
|
||||
static constexpr float FileRowHorizontalInset = 10.0f;
|
||||
static constexpr float FileListHeight = MaxFileRows * (FileRowHeight + FileRowGap);
|
||||
private:
|
||||
char m_root[FS_MAX_PATH];
|
||||
std::vector<FileEntry> m_file_entries;
|
||||
u32 m_current_index;
|
||||
float m_scroll_offset;
|
||||
float m_touch_start_scroll_offset;
|
||||
bool m_touch_finalize_selection;
|
||||
|
||||
Result PopulateFileEntries();
|
||||
bool IsSelectionVisible();
|
||||
void ScrollToSelection();
|
||||
bool IsEntryTouched(u32 i);
|
||||
void UpdateTouches();
|
||||
void FinalizeSelection();
|
||||
public:
|
||||
FileMenu(std::shared_ptr<Menu> prev_menu, const char *root);
|
||||
|
||||
virtual void Update(u64 ns) override;
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) override;
|
||||
};
|
||||
|
||||
class ValidateUpdateMenu : public Menu {
|
||||
private:
|
||||
static constexpr u32 BackButtonId = 0;
|
||||
static constexpr u32 ContinueButtonId = 1;
|
||||
|
||||
static constexpr float WindowWidth = 600.0f;
|
||||
static constexpr float WindowHeight = 600.0f;
|
||||
static constexpr float TitleGap = 90.0f;
|
||||
static constexpr float TextAreaHeight = 410.0f;
|
||||
private:
|
||||
AmsSuUpdateInformation m_update_info;
|
||||
AmsSuUpdateValidationInfo m_validation_info;
|
||||
bool m_has_drawn;
|
||||
bool m_has_info;
|
||||
bool m_has_validated;
|
||||
|
||||
Result GetUpdateInformation();
|
||||
void ValidateUpdate();
|
||||
public:
|
||||
ValidateUpdateMenu(std::shared_ptr<Menu> prev_menu);
|
||||
|
||||
virtual void Update(u64 ns) override;
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) override;
|
||||
};
|
||||
|
||||
class ChooseResetMenu : public Menu {
|
||||
private:
|
||||
static constexpr u32 ResetToFactorySettingsButtonId = 0;
|
||||
static constexpr u32 PreserveSettingsButtonId = 1;
|
||||
|
||||
static constexpr float WindowWidth = 600.0f;
|
||||
static constexpr float WindowHeight = 170.0f;
|
||||
static constexpr float TitleGap = 90.0f;
|
||||
public:
|
||||
ChooseResetMenu(std::shared_ptr<Menu> prev_menu);
|
||||
|
||||
virtual void Update(u64 ns) override;
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) override;
|
||||
};
|
||||
|
||||
class ChooseExfatMenu : public Menu {
|
||||
private:
|
||||
static constexpr u32 Fat32ButtonId = 0;
|
||||
static constexpr u32 ExFatButtonId = 1;
|
||||
|
||||
static constexpr float WindowWidth = 600.0f;
|
||||
static constexpr float WindowHeight = 170.0f;
|
||||
static constexpr float TitleGap = 90.0f;
|
||||
public:
|
||||
ChooseExfatMenu(std::shared_ptr<Menu> prev_menu);
|
||||
|
||||
virtual void Update(u64 ns) override;
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) override;
|
||||
};
|
||||
|
||||
class InstallUpdateMenu : public Menu {
|
||||
private:
|
||||
enum class InstallState {
|
||||
NeedsDraw,
|
||||
NeedsSetup,
|
||||
NeedsPrepare,
|
||||
AwaitingPrepare,
|
||||
NeedsApply,
|
||||
AwaitingReboot,
|
||||
};
|
||||
private:
|
||||
static constexpr u32 ShutdownButtonId = 0;
|
||||
static constexpr u32 RebootButtonId = 1;
|
||||
|
||||
static constexpr float WindowWidth = 600.0f;
|
||||
static constexpr float WindowHeight = 600.0f;
|
||||
static constexpr float TitleGap = 120.0f;
|
||||
static constexpr float ProgressTextHeight = 20.0f;
|
||||
static constexpr float ProgressBarHeight = 30.0f;
|
||||
static constexpr float TextAreaHeight = 320.0f;
|
||||
|
||||
static constexpr size_t UpdateTaskBufferSize = 0x100000;
|
||||
private:
|
||||
InstallState m_install_state;
|
||||
AsyncResult m_prepare_result;
|
||||
float m_progress_percent;
|
||||
|
||||
void MarkForReboot();
|
||||
Result TransitionUpdateState();
|
||||
public:
|
||||
InstallUpdateMenu(std::shared_ptr<Menu> prev_menu);
|
||||
|
||||
virtual void Update(u64 ns) override;
|
||||
virtual void Draw(NVGcontext *vg, u64 ns) override;
|
||||
};
|
||||
|
||||
bool InitializeMenu(u32 screen_width, u32 screen_height);
|
||||
bool InitializeMenu(u32 screen_width, u32 screen_height, const char *update_path);
|
||||
void UpdateMenu(u64 ns);
|
||||
void RenderMenu(NVGcontext *vg, u64 ns);
|
||||
bool IsExitRequested();
|
||||
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "ui_util.hpp"
|
||||
#include <cstdio>
|
||||
#include <math.h>
|
||||
#include <cstring>
|
||||
|
||||
namespace dbk {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char *SwitchStandardFont = "switch-standard";
|
||||
constexpr float WindowCornerRadius = 20.0f;
|
||||
constexpr float TextAreaCornerRadius = 10.0f;
|
||||
constexpr float ButtonCornerRaidus = 3.0f;
|
||||
|
||||
NVGcolor GetSelectionRGB2(u64 ns) {
|
||||
/* Calculate the rgb values for the breathing colour effect. */
|
||||
const double t = static_cast<double>(ns) / 1'000'000'000.0d;
|
||||
const float d = -0.5 * cos(3.0f*t) + 0.5f;
|
||||
const int r2 = 83 + (float)(128 - 83) * (d * 0.7f + 0.3f);
|
||||
const int g2 = 71 + (float)(126 - 71) * (d * 0.7f + 0.3f);
|
||||
const int b2 = 185 + (float)(230 - 185) * (d * 0.7f + 0.3f);
|
||||
return nvgRGB(r2, g2, b2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DrawStar(NVGcontext *vg, float x, float y, float width) {
|
||||
nvgBeginPath(vg);
|
||||
nvgEllipse(vg, x, y, width, width * 3.0f);
|
||||
nvgEllipse(vg, x, y, width * 3.0f, width);
|
||||
nvgFillColor(vg, nvgRGB(65, 71, 115));
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
void DrawBackground(NVGcontext *vg, float w, float h) {
|
||||
/* Draw the background gradient. */
|
||||
const NVGpaint bg_paint = nvgLinearGradient(vg, w / 2.0f, 0, w / 2.0f, h + 20.0f, nvgRGB(20, 24, 50), nvgRGB(46, 57, 127));
|
||||
nvgBeginPath(vg);
|
||||
nvgRect(vg, 0, 0, w, h);
|
||||
nvgFillPaint(vg, bg_paint);
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
void DrawWindow(NVGcontext *vg, const char *title, float x, float y, float w, float h) {
|
||||
/* Draw the window background. */
|
||||
const NVGpaint window_bg_paint = nvgLinearGradient(vg, x + w / 2.0f, y, x + w / 2.0f, y + h + h / 4.0f, nvgRGB(255, 255, 255), nvgRGB(188, 214, 234));
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(vg, x, y, w, h, WindowCornerRadius);
|
||||
nvgFillPaint(vg, window_bg_paint);
|
||||
nvgFill(vg);
|
||||
|
||||
/* Draw the shadow surrounding the window. */
|
||||
NVGpaint shadowPaint = nvgBoxGradient(vg, x, y + 2, w, h, WindowCornerRadius * 2, 10, nvgRGBA(0, 0, 0, 128), nvgRGBA(0, 0, 0, 0));
|
||||
nvgBeginPath(vg);
|
||||
nvgRect(vg, x - 10, y - 10, w + 20, h + 30);
|
||||
nvgRoundedRect(vg, x, y, w, h, WindowCornerRadius);
|
||||
nvgPathWinding(vg, NVG_HOLE);
|
||||
nvgFillPaint(vg, shadowPaint);
|
||||
nvgFill(vg);
|
||||
|
||||
/* Setup the font. */
|
||||
nvgFontSize(vg, std::min(32.0f, -(strlen(title)*0.5f) + 47.0f));
|
||||
nvgFontFace(vg, SwitchStandardFont);
|
||||
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
|
||||
nvgFillColor(vg, nvgRGB(0, 0, 0));
|
||||
|
||||
/* Draw the title. */
|
||||
const float tw = nvgTextBounds(vg, 0, 0, title, nullptr, nullptr);
|
||||
nvgText(vg, x + w * 0.5f - tw * 0.5f, y + 40.0f, title, nullptr);
|
||||
}
|
||||
|
||||
void DrawButton(NVGcontext *vg, const char *text, float x, float y, float w, float h, ButtonStyle style, u64 ns) {
|
||||
/* Fill the background if selected. */
|
||||
if (style == ButtonStyle::StandardSelected || style == ButtonStyle::FileSelectSelected) {
|
||||
NVGpaint bg_paint = nvgLinearGradient(vg, x, y + h / 2.0f, x + w, y + h / 2.0f, nvgRGB(83, 71, 185), GetSelectionRGB2(ns));
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(vg, x, y, w, h, ButtonCornerRaidus);
|
||||
nvgFillPaint(vg, bg_paint);
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
/* Draw the shadow surrounding the button. */
|
||||
if (style == ButtonStyle::Standard || style == ButtonStyle::StandardSelected || style == ButtonStyle::StandardDisabled || style == ButtonStyle::FileSelectSelected) {
|
||||
const unsigned char shadow_color = style == ButtonStyle::Standard ? 128 : 64;
|
||||
NVGpaint shadow_paint = nvgBoxGradient(vg, x, y, w, h, ButtonCornerRaidus, 5, nvgRGBA(0, 0, 0, shadow_color), nvgRGBA(0, 0, 0, 0));
|
||||
nvgBeginPath(vg);
|
||||
nvgRect(vg, x - 10, y - 10, w + 20, h + 30);
|
||||
nvgRoundedRect(vg, x, y, w, h, ButtonCornerRaidus);
|
||||
nvgPathWinding(vg, NVG_HOLE);
|
||||
nvgFillPaint(vg, shadow_paint);
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
/* Setup the font. */
|
||||
nvgFontSize(vg, 20.0f);
|
||||
nvgFontFace(vg, SwitchStandardFont);
|
||||
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
|
||||
|
||||
/* Set the text colour. */
|
||||
if (style == ButtonStyle::StandardSelected || style == ButtonStyle::FileSelectSelected) {
|
||||
nvgFillColor(vg, nvgRGB(255, 255, 255));
|
||||
} else {
|
||||
const unsigned char alpha = style == ButtonStyle::StandardDisabled ? 64 : 255;
|
||||
nvgFillColor(vg, nvgRGBA(0, 0, 0, alpha));
|
||||
}
|
||||
|
||||
/* Draw the button text. */
|
||||
const float tw = nvgTextBounds(vg, 0, 0, text, nullptr, nullptr);
|
||||
|
||||
if (style == ButtonStyle::Standard || style == ButtonStyle::StandardSelected || style == ButtonStyle::StandardDisabled) {
|
||||
nvgText(vg, x + w * 0.5f - tw * 0.5f, y + h * 0.5f, text, nullptr);
|
||||
} else {
|
||||
nvgText(vg, x + 10.0f, y + h * 0.5f, text, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawTextBackground(NVGcontext *vg, float x, float y, float w, float h) {
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(vg, x, y, w, h, TextAreaCornerRadius);
|
||||
nvgFillColor(vg, nvgRGBA(0, 0, 0, 16));
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
void DrawText(NVGcontext *vg, float x, float y, float w, const char *text) {
|
||||
nvgFontSize(vg, 20.0f);
|
||||
nvgFontFace(vg, SwitchStandardFont);
|
||||
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
|
||||
nvgFillColor(vg, nvgRGB(0, 0, 0));
|
||||
|
||||
const float tw = nvgTextBounds(vg, 0, 0, text, nullptr, nullptr);
|
||||
nvgText(vg, x + w * 0.5f - tw * 0.5f, y, text, nullptr);
|
||||
}
|
||||
|
||||
void DrawProgressText(NVGcontext *vg, float x, float y, float progress) {
|
||||
char progress_text[32] = {};
|
||||
snprintf(progress_text, sizeof(progress_text)-1, "%d%% complete", static_cast<int>(progress * 100.0f));
|
||||
|
||||
nvgFontSize(vg, 24.0f);
|
||||
nvgFontFace(vg, SwitchStandardFont);
|
||||
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
|
||||
nvgFillColor(vg, nvgRGB(0, 0, 0));
|
||||
nvgText(vg, x, y, progress_text, nullptr);
|
||||
}
|
||||
|
||||
void DrawProgressBar(NVGcontext *vg, float x, float y, float w, float h, float progress) {
|
||||
/* Draw the progress bar background. */
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(vg, x, y, w, h, WindowCornerRadius);
|
||||
nvgFillColor(vg, nvgRGBA(0, 0, 0, 128));
|
||||
nvgFill(vg);
|
||||
|
||||
/* Draw the progress bar fill. */
|
||||
if (progress > 0.0f) {
|
||||
NVGpaint progress_fill_paint = nvgLinearGradient(vg, x, y + 0.5f * h, x + w, y + 0.5f * h, nvgRGB(83, 71, 185), nvgRGB(128, 126, 230));
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(vg, x, y, WindowCornerRadius + (w - WindowCornerRadius) * progress, h, WindowCornerRadius);
|
||||
nvgFillPaint(vg, progress_fill_paint);
|
||||
nvgFill(vg);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawTextBlock(NVGcontext *vg, const char *text, float x, float y, float w, float h) {
|
||||
/* Save state and scissor. */
|
||||
nvgSave(vg);
|
||||
nvgScissor(vg, x, y, w, h);
|
||||
|
||||
/* Configure the text. */
|
||||
nvgFontSize(vg, 18.0f);
|
||||
nvgFontFace(vg, SwitchStandardFont);
|
||||
nvgTextLineHeight(vg, 1.3f);
|
||||
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
|
||||
nvgFillColor(vg, nvgRGB(0, 0, 0));
|
||||
|
||||
/* Determine the bounds of the text box. */
|
||||
float bounds[4];
|
||||
nvgTextBoxBounds(vg, 0, 0, w, text, nullptr, bounds);
|
||||
|
||||
/* Adjust the y to only show the last part of the text that fits. */
|
||||
float y_adjustment = 0.0f;
|
||||
if (bounds[3] > h) {
|
||||
y_adjustment = bounds[3] - h;
|
||||
}
|
||||
|
||||
/* Draw the text box and restore state. */
|
||||
nvgTextBox(vg, x, y - y_adjustment, w, text, nullptr);
|
||||
nvgRestore(vg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <nanovg.h>
|
||||
#include <switch.h>
|
||||
|
||||
namespace dbk {
|
||||
|
||||
enum class ButtonStyle {
|
||||
Standard,
|
||||
StandardSelected,
|
||||
StandardDisabled,
|
||||
FileSelect,
|
||||
FileSelectSelected,
|
||||
};
|
||||
|
||||
void DrawStar(NVGcontext *vg, float x, float y, float width);
|
||||
void DrawBackground(NVGcontext *vg, float w, float h);
|
||||
void DrawWindow(NVGcontext *vg, const char *title, float x, float y, float w, float h);
|
||||
void DrawButton(NVGcontext *vg, const char *text, float x, float y, float w, float h, ButtonStyle style, u64 ns);
|
||||
void DrawTextBackground(NVGcontext *vg, float x, float y, float w, float h);
|
||||
void DrawText(NVGcontext *vg, float x, float y, float w, const char *text);
|
||||
void DrawProgressText(NVGcontext *vg, float x, float y, float progress);
|
||||
void DrawProgressBar(NVGcontext *vg, float x, float y, float w, float h, float progress);
|
||||
void DrawTextBlock(NVGcontext *vg, const char *text, float x, float y, float w, float h);
|
||||
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
#---------------------------------------------------------------------------------
|
||||
.SUFFIXES:
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
ifeq ($(strip $(DEVKITPRO)),)
|
||||
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
|
||||
endif
|
||||
|
||||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
# SOURCES is a list of directories containing source code
|
||||
# DATA is a list of directories containing data files
|
||||
# INCLUDES is a list of directories containing header files
|
||||
# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional)
|
||||
#
|
||||
# NO_ICON: if set to anything, do not use icon.
|
||||
# NO_NACP: if set to anything, no .nacp file is generated.
|
||||
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
|
||||
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
|
||||
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
|
||||
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
|
||||
# ICON is the filename of the icon (.jpg), relative to the project folder.
|
||||
# If not set, it attempts to use one of the following (in this order):
|
||||
# - <Project name>.jpg
|
||||
# - icon.jpg
|
||||
# - <libnx folder>/default_icon.jpg
|
||||
#
|
||||
# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder.
|
||||
# If not set, it attempts to use one of the following (in this order):
|
||||
# - <Project name>.json
|
||||
# - config.json
|
||||
# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead
|
||||
# of a homebrew executable (.nro). This is intended to be used for sysmodules.
|
||||
# NACP building is skipped as well.
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := $(notdir $(CURDIR))
|
||||
BUILD := build
|
||||
SOURCES := source
|
||||
DATA := data
|
||||
INCLUDES := include ../../libraries/libvapours/include
|
||||
ROMFS := romfs
|
||||
|
||||
# Output folders for autogenerated files in romfs
|
||||
OUT_SHADERS := shaders
|
||||
|
||||
APP_TITLE := USB File Transfer
|
||||
APP_AUTHOR := Atmosphere-NX
|
||||
APP_VERSION := 1.0.0
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
||||
|
||||
CFLAGS := -g -Wall -O2 -ffunction-sections \
|
||||
$(ARCH) $(DEFINES)
|
||||
|
||||
CFLAGS += $(INCLUDE) -D__SWITCH__
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++23
|
||||
|
||||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
|
||||
LIBS := -ldeko3d -lnx -lm
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
# include and lib
|
||||
#---------------------------------------------------------------------------------
|
||||
LIBDIRS := $(PORTLIBS) $(LIBNX)
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# no real need to edit anything past this point unless you need to add additional
|
||||
# rules for different file extensions
|
||||
#---------------------------------------------------------------------------------
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||
export TOPDIR := $(CURDIR)
|
||||
|
||||
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
|
||||
|
||||
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||
|
||||
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||
GLSLFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.glsl)))
|
||||
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# use CXX for linking C++ projects, CC for standard C
|
||||
#---------------------------------------------------------------------------------
|
||||
ifeq ($(strip $(CPPFILES)),)
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CC)
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CXX)
|
||||
#---------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
|
||||
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
||||
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
||||
|
||||
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||
-I$(CURDIR)/$(BUILD)
|
||||
|
||||
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||
|
||||
ifeq ($(strip $(CONFIG_JSON)),)
|
||||
jsons := $(wildcard *.json)
|
||||
ifneq (,$(findstring $(TARGET).json,$(jsons)))
|
||||
export APP_JSON := $(TOPDIR)/$(TARGET).json
|
||||
else
|
||||
ifneq (,$(findstring config.json,$(jsons)))
|
||||
export APP_JSON := $(TOPDIR)/config.json
|
||||
endif
|
||||
endif
|
||||
else
|
||||
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(ICON)),)
|
||||
icons := $(wildcard *.jpg)
|
||||
ifneq (,$(findstring $(TARGET).jpg,$(icons)))
|
||||
export APP_ICON := $(TOPDIR)/$(TARGET).jpg
|
||||
else
|
||||
ifneq (,$(findstring icon.jpg,$(icons)))
|
||||
export APP_ICON := $(TOPDIR)/icon.jpg
|
||||
endif
|
||||
endif
|
||||
else
|
||||
export APP_ICON := $(TOPDIR)/$(ICON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(NO_ICON)),)
|
||||
export NROFLAGS += --icon=$(APP_ICON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(NO_NACP)),)
|
||||
export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp
|
||||
endif
|
||||
|
||||
ifneq ($(APP_TITLEID),)
|
||||
export NACPFLAGS += --titleid=$(APP_TITLEID)
|
||||
endif
|
||||
|
||||
ifneq ($(strip $(ROMFS)),)
|
||||
ROMFS_TARGETS :=
|
||||
ROMFS_FOLDERS :=
|
||||
ifneq ($(strip $(OUT_SHADERS)),)
|
||||
ROMFS_SHADERS := $(ROMFS)/$(OUT_SHADERS)
|
||||
ROMFS_TARGETS += $(patsubst %.glsl, $(ROMFS_SHADERS)/%.dksh, $(GLSLFILES))
|
||||
ROMFS_FOLDERS += $(ROMFS_SHADERS)
|
||||
endif
|
||||
|
||||
export ROMFS_DEPS := $(foreach file,$(ROMFS_TARGETS),$(CURDIR)/$(file))
|
||||
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
|
||||
endif
|
||||
|
||||
.PHONY: $(BUILD) clean all
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
all: $(ROMFS_TARGETS) | $(BUILD)
|
||||
|
||||
$(BUILD):
|
||||
@[ -d $@ ] || mkdir -p $@
|
||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||
|
||||
ifneq ($(strip $(ROMFS_TARGETS)),)
|
||||
|
||||
$(ROMFS_TARGETS): | $(ROMFS_FOLDERS)
|
||||
|
||||
$(ROMFS_FOLDERS):
|
||||
@mkdir -p $@
|
||||
|
||||
$(ROMFS_SHADERS)/%_vsh.dksh: %_vsh.glsl
|
||||
@echo {vert} $(notdir $<)
|
||||
@uam -s vert -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%_tcsh.dksh: %_tcsh.glsl
|
||||
@echo {tess_ctrl} $(notdir $<)
|
||||
@uam -s tess_ctrl -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%_tesh.dksh: %_tesh.glsl
|
||||
@echo {tess_eval} $(notdir $<)
|
||||
@uam -s tess_eval -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%_gsh.dksh: %_gsh.glsl
|
||||
@echo {geom} $(notdir $<)
|
||||
@uam -s geom -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%_fsh.dksh: %_fsh.glsl
|
||||
@echo {frag} $(notdir $<)
|
||||
@uam -s frag -o $@ $<
|
||||
|
||||
$(ROMFS_SHADERS)/%.dksh: %.glsl
|
||||
@echo {comp} $(notdir $<)
|
||||
@uam -s comp -o $@ $<
|
||||
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
clean:
|
||||
@echo clean ...
|
||||
ifeq ($(strip $(APP_JSON)),)
|
||||
@rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf
|
||||
else
|
||||
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf
|
||||
endif
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
.PHONY: all
|
||||
|
||||
DEPENDS := $(OFILES:.o=.d)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# main targets
|
||||
#---------------------------------------------------------------------------------
|
||||
ifeq ($(strip $(APP_JSON)),)
|
||||
|
||||
all : $(OUTPUT).nro
|
||||
|
||||
ifeq ($(strip $(NO_NACP)),)
|
||||
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS)
|
||||
else
|
||||
$(OUTPUT).nro : $(OUTPUT).elf $(ROMFS_DEPS)
|
||||
endif
|
||||
|
||||
else
|
||||
|
||||
all : $(OUTPUT).nsp
|
||||
|
||||
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
|
||||
|
||||
$(OUTPUT).nso : $(OUTPUT).elf
|
||||
|
||||
endif
|
||||
|
||||
$(OUTPUT).elf : $(OFILES)
|
||||
|
||||
$(OFILES_SRC) : $(HFILES_BIN)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# you need a rule like this for each extension you use as binary data
|
||||
#---------------------------------------------------------------------------------
|
||||
%.bin.o %_bin.h : %.bin
|
||||
#---------------------------------------------------------------------------------
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
-include $(DEPENDS)
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------------
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.2 KiB |
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="svg1500" width="182" height="182" version="1.1" viewBox="0 0 182 182" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs id="defs1484"><linearGradient id="a" x1="36.532" x2="36.532" y1="39.898" y2="80.671" gradientTransform="scale(1.0484 .95387)" gradientUnits="userSpaceOnUse"><stop id="stop1474" stop-color="#fff" offset="0"/><stop id="stop1476" stop-color="#9FC1DB" offset="1"/></linearGradient><linearGradient id="linearGradient2139" x1="451.42" x2="426.88" y1="93.955" y2="145.12" gradientTransform="matrix(1.4611 0 0 1.4611 -557.72 -38.455)" gradientUnits="userSpaceOnUse"><stop id="stop1479" stop-color="#90B9D9" offset="0"/><stop id="stop1481" stop-color="#554BBA" offset="1"/></linearGradient><linearGradient id="linearGradient2147" x1="436.58" x2="451.74" y1="96.001" y2="108.43" gradientTransform="matrix(1.4611 0 0 1.4611 -557.72 -34.137)" gradientUnits="userSpaceOnUse" xlink:href="#a"/><linearGradient id="linearGradient5197" x1="438.54" x2="455.23" y1="117.18" y2="124.38" gradientTransform="matrix(1.5947 0 0 1.5947 -617.05 -51.062)" gradientUnits="userSpaceOnUse" xlink:href="#a"/></defs><rect id="rect6884" width="182" height="182" ry="0" fill="#37394c" stop-color="#000000"/><g id="g1716" transform="matrix(1.8352 0 0 1.8352 -75.998 -148.92)" stroke-width=".54491"><rect id="rect1998" x="80.151" y="91.556" width="21.697" height="21.879" ry="1.4596" fill="url(#linearGradient2147)" stop-color="#000000"/><path id="rect1828" d="m107.41 151.77v-41.952c0-1.4858-1.1967-2.6824-2.6825-2.6824h-27.447c-1.4858 0-2.6825 1.1967-2.6825 2.6824v41.952c0 13.67 13.523 9.3797 13.523 23.423v10.594h5.7652v-10.594c0-14.043 13.523-9.7531 13.523-23.423" fill="url(#linearGradient2139)" stop-color="#000000"/><path id="path1334" d="m91 116.2-2.5153 4.3556h1.7943v22.263l-4.5798-4.3348c-0.2957-0.3689-0.50311-0.85159-0.51463-1.3481 0-2.0086-5.2e-4 -3.2015-8.82e-4 -3.6405 0.84792-0.29761 1.46-1.0971 1.46-2.0475 0-1.2023-0.97564-2.178-2.1784-2.178-1.2033 0-2.1786 0.97563-2.1786 2.178 0 0.9504 0.61169 1.7499 1.4589 2.0475l-6.01e-4 3.5979c0 0.97511 0.53498 1.9969 1.1622 2.6472-0.01854-0.0177-0.0384-0.0363 3.53e-4 8.9e-4 0.01589 0.0143 4.8585 4.5991 4.8585 4.5991 0.29527 0.36811 0.50139 0.85054 0.51324 1.3467v2.5185c-1.6637 0.33382-2.9172 1.803-2.9172 3.5654 0 2.0093 1.6288 3.6381 3.6375 3.6381 2.0093 0 3.6382-1.6288 3.6382-3.6381 0-1.7627-1.2545-3.2319-2.9197-3.5657v-2.4743c0-6e-3 3.53e-4 -0.0121 0-0.0191v-5.4728c0.01209-0.49523 0.21903-0.97704 0.51464-1.3448 0 0 4.8427-4.5843 4.8583-4.5985 0.03901-0.0367 0.01854-0.0184 3.54e-4 -3.5e-4 0.62708-0.65035 1.1617-1.6726 1.1617-2.6478l-7.86e-4 -3.4673h1.4604v-4.3568h-4.3563v4.3568h1.4585s-8.83e-4 0.91324-8.83e-4 3.5098c-0.01095 0.49662-0.21857 0.9798-0.51427 1.3486l-4.5807 4.3358v-16.818h1.7972z" fill="url(#linearGradient5197)" stroke-width=".54491"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <haze/async_usb_server.hpp>
|
||||
#include <haze/common.hpp>
|
||||
#include <haze/device_properties.hpp>
|
||||
#include <haze/event_reactor.hpp>
|
||||
#include <haze/file_system_proxy.hpp>
|
||||
#include <haze/ptp.hpp>
|
||||
#include <haze/ptp_object_database.hpp>
|
||||
#include <haze/ptp_object_heap.hpp>
|
||||
#include <haze/ptp_responder.hpp>
|
||||
#include <haze/usb_session.hpp>
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define HAZE_ASSERT(expr) \
|
||||
{ \
|
||||
const bool __tmp_haze_assert_val = static_cast<bool>(expr); \
|
||||
if (AMS_UNLIKELY(!__tmp_haze_assert_val)) { \
|
||||
svcBreak(BreakReason_Assert, 0, 0); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define HAZE_R_ABORT_UNLESS(res_expr) \
|
||||
{ \
|
||||
const auto _tmp_r_abort_rc = (res_expr); \
|
||||
HAZE_ASSERT(R_SUCCEEDED(_tmp_r_abort_rc)); \
|
||||
}
|
||||
|
||||
#define HAZE_UNREACHABLE_DEFAULT_CASE() default: HAZE_ASSERT(false)
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <haze/common.hpp>
|
||||
#include <haze/event_reactor.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
class AsyncUsbServer final {
|
||||
private:
|
||||
EventReactor *m_reactor;
|
||||
public:
|
||||
constexpr explicit AsyncUsbServer() : m_reactor() { /* ... */ }
|
||||
|
||||
Result Initialize(const UsbCommsInterfaceInfo *interface_info, u16 id_vendor, u16 id_product, EventReactor *reactor);
|
||||
void Finalize();
|
||||
private:
|
||||
Result TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred) const;
|
||||
public:
|
||||
Result ReadPacket(void *page, u32 size, u32 *out_size_transferred) const {
|
||||
R_RETURN(this->TransferPacketImpl(true, page, size, out_size_transferred));
|
||||
}
|
||||
|
||||
Result WritePacket(void *page, u32 size) const {
|
||||
u32 size_transferred;
|
||||
R_RETURN(this->TransferPacketImpl(false, page, size, std::addressof(size_transferred)));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define ATMOSPHERE_IS_TROPOSPHERE
|
||||
#define ATMOSPHERE_OS_HORIZON
|
||||
#define ATMOSPHERE_BOARD_NINTENDO_NX
|
||||
#define ATMOSPHERE_ARCH_ARM64
|
||||
#define ATMOSPHERE_ARCH_ARM_V8A
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <bit>
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <switch.h>
|
||||
#include <haze/results.hpp>
|
||||
#include <haze/assert.hpp>
|
||||
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/svc/svc_common.hpp>
|
||||
#include <vapours/svc/svc_types_common.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
using namespace ::ams::literals;
|
||||
using namespace ::ams;
|
||||
|
||||
using Result = ::ams::Result;
|
||||
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <haze/event_reactor.hpp>
|
||||
#include <haze/ptp_object_heap.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
class ConsoleMainLoop : public EventConsumer {
|
||||
private:
|
||||
static constexpr size_t FrameDelayNs = 33'333'333;
|
||||
private:
|
||||
EventReactor *m_reactor;
|
||||
PtpObjectHeap *m_object_heap;
|
||||
|
||||
PadState m_pad;
|
||||
|
||||
Thread m_thread;
|
||||
UEvent m_event;
|
||||
UEvent m_cancel_event;
|
||||
|
||||
u32 m_last_heap_used;
|
||||
u32 m_last_heap_total;
|
||||
bool m_is_applet_mode;
|
||||
private:
|
||||
static void Run(void *arg) {
|
||||
static_cast<ConsoleMainLoop *>(arg)->Run();
|
||||
}
|
||||
|
||||
void Run() {
|
||||
int idx;
|
||||
|
||||
while (true) {
|
||||
/* Wait for up to 1 frame delay time to be cancelled. */
|
||||
Waiter cancel_waiter = waiterForUEvent(std::addressof(m_cancel_event));
|
||||
Result rc = waitObjects(std::addressof(idx), std::addressof(cancel_waiter), 1, FrameDelayNs);
|
||||
|
||||
/* Finish if we were cancelled. */
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Otherwise, signal the console update event. */
|
||||
if (svc::ResultTimedOut::Includes(rc)) {
|
||||
ueventSignal(std::addressof(m_event));
|
||||
}
|
||||
}
|
||||
}
|
||||
public:
|
||||
explicit ConsoleMainLoop() : m_reactor(), m_pad(), m_thread(), m_event(), m_cancel_event(), m_last_heap_used(), m_last_heap_total(), m_is_applet_mode() { /* ... */ }
|
||||
|
||||
Result Initialize(EventReactor *reactor, PtpObjectHeap *object_heap) {
|
||||
/* Register event reactor and heap. */
|
||||
m_reactor = reactor;
|
||||
m_object_heap = object_heap;
|
||||
|
||||
/* Set cached use amounts to invalid values. */
|
||||
m_last_heap_used = 0xffffffffu;
|
||||
m_last_heap_total = 0xffffffffu;
|
||||
|
||||
/* Get whether we are launched in applet mode. */
|
||||
AppletType applet_type = appletGetAppletType();
|
||||
m_is_applet_mode = applet_type != AppletType_Application && applet_type != AppletType_SystemApplication;
|
||||
|
||||
/* Initialize events. */
|
||||
ueventCreate(std::addressof(m_event), true);
|
||||
ueventCreate(std::addressof(m_cancel_event), true);
|
||||
|
||||
/* Set up pad inputs to allow exiting the program. */
|
||||
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
|
||||
padInitializeAny(std::addressof(m_pad));
|
||||
|
||||
/* Create the delay thread with higher priority than the main thread (which runs at priority 0x2c). */
|
||||
R_TRY(threadCreate(std::addressof(m_thread), ConsoleMainLoop::Run, this, nullptr, 4_KB, 0x2b, svc::IdealCoreUseProcessValue));
|
||||
|
||||
/* Ensure we close the thread on failure. */
|
||||
ON_RESULT_FAILURE { threadClose(std::addressof(m_thread)); };
|
||||
|
||||
/* Connect ourselves to the event loop. */
|
||||
R_UNLESS(m_reactor->AddConsumer(this, waiterForUEvent(std::addressof(m_event))), haze::ResultRegistrationFailed());
|
||||
|
||||
/* Start the delay thread. */
|
||||
R_RETURN(threadStart(std::addressof(m_thread)));
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
/* Signal the delay thread to shut down. */
|
||||
ueventSignal(std::addressof(m_cancel_event));
|
||||
|
||||
/* Wait for the delay thread to exit and close it. */
|
||||
HAZE_R_ABORT_UNLESS(threadWaitForExit(std::addressof(m_thread)));
|
||||
|
||||
HAZE_R_ABORT_UNLESS(threadClose(std::addressof(m_thread)));
|
||||
|
||||
/* Disconnect from the event loop.*/
|
||||
m_reactor->RemoveConsumer(this);
|
||||
}
|
||||
private:
|
||||
void RedrawConsole() {
|
||||
/* Get use amounts from the heap. */
|
||||
u32 heap_used = m_object_heap->GetUsedSize();
|
||||
u32 heap_total = m_object_heap->GetTotalSize();
|
||||
u32 heap_pct = heap_total > 0 ? static_cast<u32>((heap_used * 100ul) / heap_total) : 0;
|
||||
|
||||
if (heap_used == m_last_heap_used && heap_total == m_last_heap_total) {
|
||||
/* If usage didn't change, skip redrawing the console. */
|
||||
/* This provides a substantial performance improvement in file transfer speed. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Update cached use amounts. */
|
||||
m_last_heap_used = heap_used;
|
||||
m_last_heap_total = heap_total;
|
||||
|
||||
/* Determine units to use for printing to the console. */
|
||||
const char *used_unit = "B";
|
||||
if (heap_used >= 1_KB) { heap_used >>= 10; used_unit = "KiB"; }
|
||||
if (heap_used >= (1_MB / 1_KB)) { heap_used >>= 10; used_unit = "MiB"; }
|
||||
|
||||
const char *total_unit = "B";
|
||||
if (heap_total >= 1_KB) { heap_total >>= 10; total_unit = "KiB"; }
|
||||
if (heap_total >= (1_MB / 1_KB)) { heap_total >>= 10; total_unit = "MiB"; }
|
||||
|
||||
/* Draw the console UI. */
|
||||
consoleClear();
|
||||
printf("USB File Transfer\n\n");
|
||||
printf("Connect console to computer. Press [+] to exit.\n");
|
||||
printf("Heap used: %u %s / %u %s (%u%%)\n", heap_used, used_unit, heap_total, total_unit, heap_pct);
|
||||
|
||||
if (m_is_applet_mode) {
|
||||
/* Print "Applet Mode" in red text. */
|
||||
printf("\n" CONSOLE_ESC(31;1m) "Applet Mode" CONSOLE_ESC(0m) "\n");
|
||||
}
|
||||
|
||||
consoleUpdate(nullptr);
|
||||
}
|
||||
protected:
|
||||
void ProcessEvent() override {
|
||||
/* Update the console. */
|
||||
this->RedrawConsole();
|
||||
|
||||
/* Check buttons. */
|
||||
padUpdate(std::addressof(m_pad));
|
||||
|
||||
/* If the plus button is held, request immediate exit. */
|
||||
if (padGetButtonsDown(std::addressof(m_pad)) & HidNpadButton_Plus) {
|
||||
m_reactor->SetResult(haze::ResultStopRequested());
|
||||
}
|
||||
|
||||
/* Pump applet events, and check if exit was requested. */
|
||||
if (!appletMainLoop()) {
|
||||
m_reactor->SetResult(haze::ResultStopRequested());
|
||||
}
|
||||
|
||||
/* Check if focus was lost. */
|
||||
if (appletGetFocusState() == AppletFocusState_Background) {
|
||||
m_reactor->SetResult(haze::ResultFocusLost());
|
||||
}
|
||||
}
|
||||
private:
|
||||
static bool SuspendAndWaitForFocus() {
|
||||
/* Enable suspension with resume notification. */
|
||||
appletSetFocusHandlingMode(AppletFocusHandlingMode_SuspendHomeSleepNotify);
|
||||
|
||||
/* Pump applet events. */
|
||||
while (appletMainLoop()) {
|
||||
/* Check if focus was regained. */
|
||||
if (appletGetFocusState() != AppletFocusState_Background) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Exit was requested. */
|
||||
return false;
|
||||
}
|
||||
public:
|
||||
static void RunApplication() {
|
||||
/* Declare the object heap, to hold the database for an active session. */
|
||||
PtpObjectHeap ptp_object_heap;
|
||||
|
||||
/* Declare the event reactor, and components which use it. */
|
||||
EventReactor event_reactor;
|
||||
PtpResponder ptp_responder;
|
||||
ConsoleMainLoop console_main_loop;
|
||||
|
||||
/* Initialize the console.*/
|
||||
consoleInit(nullptr);
|
||||
|
||||
while (true) {
|
||||
/* Disable suspension. */
|
||||
appletSetFocusHandlingMode(AppletFocusHandlingMode_NoSuspend);
|
||||
|
||||
/* Declare result from serving to use. */
|
||||
Result rc;
|
||||
{
|
||||
/* Ensure we don't go to sleep while transferring files. */
|
||||
appletSetAutoSleepDisabled(true);
|
||||
|
||||
/* Clear the event reactor. */
|
||||
event_reactor.SetResult(ResultSuccess());
|
||||
|
||||
/* Configure the PTP responder and console main loop. */
|
||||
ptp_responder.Initialize(std::addressof(event_reactor), std::addressof(ptp_object_heap));
|
||||
console_main_loop.Initialize(std::addressof(event_reactor), std::addressof(ptp_object_heap));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT {
|
||||
/* Finalize the console main loop and PTP responder. */
|
||||
console_main_loop.Finalize();
|
||||
ptp_responder.Finalize();
|
||||
|
||||
/* Restore auto sleep setting. */
|
||||
appletSetAutoSleepDisabled(false);
|
||||
};
|
||||
|
||||
/* Begin processing requests. */
|
||||
rc = ptp_responder.LoopProcess();
|
||||
}
|
||||
|
||||
/* If focus was lost, try to pump the applet main loop until we receive focus again. */
|
||||
if (haze::ResultFocusLost::Includes(rc) && SuspendAndWaitForFocus()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Otherwise, enable suspension and finish. */
|
||||
appletSetFocusHandlingMode(AppletFocusHandlingMode_SuspendHomeSleep);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Finalize the console. */
|
||||
consoleExit(nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace haze {
|
||||
|
||||
Result LoadDeviceProperties();
|
||||
|
||||
const char *GetSerialNumber();
|
||||
|
||||
const char *GetFirmwareVersion();
|
||||
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <haze/common.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
class EventConsumer {
|
||||
public:
|
||||
virtual ~EventConsumer() = default;
|
||||
virtual void ProcessEvent() = 0;
|
||||
};
|
||||
|
||||
class EventReactor {
|
||||
private:
|
||||
EventConsumer *m_consumers[svc::ArgumentHandleCountMax];
|
||||
Waiter m_waiters[svc::ArgumentHandleCountMax];
|
||||
s32 m_num_wait_objects;
|
||||
Result m_result;
|
||||
public:
|
||||
constexpr explicit EventReactor() : m_consumers(), m_waiters(), m_num_wait_objects(), m_result(ResultSuccess()) { /* ... */ }
|
||||
|
||||
bool AddConsumer(EventConsumer *consumer, Waiter waiter);
|
||||
void RemoveConsumer(EventConsumer *consumer);
|
||||
public:
|
||||
void SetResult(Result r) { m_result = r; }
|
||||
Result GetResult() const { return m_result; }
|
||||
public:
|
||||
template <typename... Args> requires (sizeof...(Args) > 0)
|
||||
Result WaitFor(s32 *out_arg_waiter, Args &&... arg_waiters) {
|
||||
const Waiter arg_waiter_array[] = { arg_waiters... };
|
||||
return this->WaitForImpl(out_arg_waiter, arg_waiter_array, sizeof...(Args));
|
||||
}
|
||||
private:
|
||||
Result WaitForImpl(s32 *out_arg_waiter, const Waiter *arg_waiters, s32 num_arg_waiters);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <haze/common.hpp>
|
||||
#include <haze/event_reactor.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
class FileSystemProxy final {
|
||||
private:
|
||||
EventReactor *m_reactor;
|
||||
FsFileSystem *m_filesystem;
|
||||
public:
|
||||
constexpr explicit FileSystemProxy() : m_reactor(), m_filesystem() { /* ... */ }
|
||||
|
||||
void Initialize(EventReactor *reactor, FsFileSystem *fs) {
|
||||
HAZE_ASSERT(fs != nullptr);
|
||||
|
||||
m_reactor = reactor;
|
||||
m_filesystem = fs;
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
m_reactor = nullptr;
|
||||
m_filesystem = nullptr;
|
||||
}
|
||||
private:
|
||||
template <typename F, typename... Args>
|
||||
Result ForwardResult(F func, Args &&... args) {
|
||||
/* Perform the method call, collecting its result. */
|
||||
const Result rc = func(std::forward<Args>(args)...);
|
||||
|
||||
/* If the event loop was stopped, return that here. */
|
||||
R_TRY(m_reactor->GetResult());
|
||||
|
||||
/* Otherwise, return the call result. */
|
||||
R_RETURN(rc);
|
||||
}
|
||||
public:
|
||||
Result GetTotalSpace(const char *path, s64 *out) {
|
||||
R_RETURN(this->ForwardResult(fsFsGetTotalSpace, m_filesystem, path, out));
|
||||
}
|
||||
|
||||
Result GetFreeSpace(const char *path, s64 *out) {
|
||||
R_RETURN(this->ForwardResult(fsFsGetFreeSpace, m_filesystem, path, out));
|
||||
}
|
||||
|
||||
Result GetEntryType(const char *path, FsDirEntryType *out_entry_type) {
|
||||
R_RETURN(this->ForwardResult(fsFsGetEntryType, m_filesystem, path, out_entry_type));
|
||||
}
|
||||
|
||||
Result CreateFile(const char* path, s64 size, u32 option) {
|
||||
R_RETURN(this->ForwardResult(fsFsCreateFile, m_filesystem, path, size, option));
|
||||
}
|
||||
|
||||
Result DeleteFile(const char* path) {
|
||||
R_RETURN(this->ForwardResult(fsFsDeleteFile, m_filesystem, path));
|
||||
}
|
||||
|
||||
Result RenameFile(const char *old_path, const char *new_path) {
|
||||
R_RETURN(this->ForwardResult(fsFsRenameFile, m_filesystem, old_path, new_path));
|
||||
}
|
||||
|
||||
Result OpenFile(const char *path, u32 mode, FsFile *out_file) {
|
||||
R_RETURN(this->ForwardResult(fsFsOpenFile, m_filesystem, path, mode, out_file));
|
||||
}
|
||||
|
||||
Result GetFileSize(FsFile *file, s64 *out_size) {
|
||||
R_RETURN(this->ForwardResult(fsFileGetSize, file, out_size));
|
||||
}
|
||||
|
||||
Result SetFileSize(FsFile *file, s64 size) {
|
||||
R_RETURN(this->ForwardResult(fsFileSetSize, file, size));
|
||||
}
|
||||
|
||||
Result ReadFile(FsFile *file, s64 off, void *buf, u64 read_size, u32 option, u64 *out_bytes_read) {
|
||||
R_RETURN(this->ForwardResult(fsFileRead, file, off, buf, read_size, option, out_bytes_read));
|
||||
}
|
||||
|
||||
Result WriteFile(FsFile *file, s64 off, const void *buf, u64 write_size, u32 option) {
|
||||
R_RETURN(this->ForwardResult(fsFileWrite, file, off, buf, write_size, option));
|
||||
}
|
||||
|
||||
void CloseFile(FsFile *file) {
|
||||
fsFileClose(file);
|
||||
}
|
||||
|
||||
Result CreateDirectory(const char* path) {
|
||||
R_RETURN(this->ForwardResult(fsFsCreateDirectory, m_filesystem, path));
|
||||
}
|
||||
|
||||
Result DeleteDirectoryRecursively(const char* path) {
|
||||
R_RETURN(this->ForwardResult(fsFsDeleteDirectoryRecursively, m_filesystem, path));
|
||||
}
|
||||
|
||||
Result RenameDirectory(const char *old_path, const char *new_path) {
|
||||
R_RETURN(this->ForwardResult(fsFsRenameDirectory, m_filesystem, old_path, new_path));
|
||||
}
|
||||
|
||||
Result OpenDirectory(const char *path, u32 mode, FsDir *out_dir) {
|
||||
R_RETURN(this->ForwardResult(fsFsOpenDirectory, m_filesystem, path, mode, out_dir));
|
||||
}
|
||||
|
||||
Result ReadDirectory(FsDir *d, s64 *out_total_entries, size_t max_entries, FsDirectoryEntry *buf) {
|
||||
R_RETURN(this->ForwardResult(fsDirRead, d, out_total_entries, max_entries, buf));
|
||||
}
|
||||
|
||||
Result GetDirectoryEntryCount(FsDir *d, s64 *out_count) {
|
||||
R_RETURN(this->ForwardResult(fsDirGetEntryCount, d, out_count));
|
||||
}
|
||||
|
||||
void CloseDirectory(FsDir *d) {
|
||||
fsDirClose(d);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,493 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
* Copyright (c) libmtp project
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <haze/common.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
constexpr inline u32 PtpUsbBulkHighSpeedMaxPacketLength = 0x200;
|
||||
constexpr inline u32 PtpUsbBulkSuperSpeedMaxPacketLength = 0x400;
|
||||
constexpr inline u32 PtpUsbBulkHeaderLength = 2 * sizeof(u32) + 2 * sizeof(u16);
|
||||
constexpr inline u32 PtpStringMaxLength = 255;
|
||||
|
||||
enum PtpUsbBulkContainerType : u16 {
|
||||
PtpUsbBulkContainerType_Undefined = 0x0000,
|
||||
PtpUsbBulkContainerType_Command = 0x0001,
|
||||
PtpUsbBulkContainerType_Data = 0x0002,
|
||||
PtpUsbBulkContainerType_Response = 0x0003,
|
||||
PtpUsbBulkContainerType_Event = 0x0004,
|
||||
};
|
||||
|
||||
enum PtpOperationCode : u16 {
|
||||
PtpOperationCode_Undefined = 0x1000,
|
||||
PtpOperationCode_GetDeviceInfo = 0x1001,
|
||||
PtpOperationCode_OpenSession = 0x1002,
|
||||
PtpOperationCode_CloseSession = 0x1003,
|
||||
PtpOperationCode_GetStorageIds = 0x1004,
|
||||
PtpOperationCode_GetStorageInfo = 0x1005,
|
||||
PtpOperationCode_GetNumObjects = 0x1006,
|
||||
PtpOperationCode_GetObjectHandles = 0x1007,
|
||||
PtpOperationCode_GetObjectInfo = 0x1008,
|
||||
PtpOperationCode_GetObject = 0x1009,
|
||||
PtpOperationCode_GetThumb = 0x100a,
|
||||
PtpOperationCode_DeleteObject = 0x100b,
|
||||
PtpOperationCode_SendObjectInfo = 0x100c,
|
||||
PtpOperationCode_SendObject = 0x100d,
|
||||
PtpOperationCode_InitiateCapture = 0x100e,
|
||||
PtpOperationCode_FormatStore = 0x100f,
|
||||
PtpOperationCode_ResetDevice = 0x1010,
|
||||
PtpOperationCode_SelfTest = 0x1011,
|
||||
PtpOperationCode_SetObjectProtection = 0x1012,
|
||||
PtpOperationCode_PowerDown = 0x1013,
|
||||
PtpOperationCode_GetDevicePropDesc = 0x1014,
|
||||
PtpOperationCode_GetDevicePropValue = 0x1015,
|
||||
PtpOperationCode_SetDevicePropValue = 0x1016,
|
||||
PtpOperationCode_ResetDevicePropValue = 0x1017,
|
||||
PtpOperationCode_TerminateOpenCapture = 0x1018,
|
||||
PtpOperationCode_MoveObject = 0x1019,
|
||||
PtpOperationCode_CopyObject = 0x101a,
|
||||
PtpOperationCode_GetPartialObject = 0x101b,
|
||||
PtpOperationCode_InitiateOpenCapture = 0x101c,
|
||||
PtpOperationCode_StartEnumHandles = 0x101d,
|
||||
PtpOperationCode_EnumHandles = 0x101e,
|
||||
PtpOperationCode_StopEnumHandles = 0x101f,
|
||||
PtpOperationCode_GetVendorExtensionMaps = 0x1020,
|
||||
PtpOperationCode_GetVendorDeviceInfo = 0x1021,
|
||||
PtpOperationCode_GetResizedImageObject = 0x1022,
|
||||
PtpOperationCode_GetFilesystemManifest = 0x1023,
|
||||
PtpOperationCode_GetStreamInfo = 0x1024,
|
||||
PtpOperationCode_GetStream = 0x1025,
|
||||
PtpOperationCode_AndroidGetPartialObject64 = 0x95c1,
|
||||
PtpOperationCode_AndroidSendPartialObject = 0x95c2,
|
||||
PtpOperationCode_AndroidTruncateObject = 0x95c3,
|
||||
PtpOperationCode_AndroidBeginEditObject = 0x95c4,
|
||||
PtpOperationCode_AndroidEndEditObject = 0x95c5,
|
||||
PtpOperationCode_MtpGetObjectPropsSupported = 0x9801,
|
||||
PtpOperationCode_MtpGetObjectPropDesc = 0x9802,
|
||||
PtpOperationCode_MtpGetObjectPropValue = 0x9803,
|
||||
PtpOperationCode_MtpSetObjectPropValue = 0x9804,
|
||||
PtpOperationCode_MtpGetObjPropList = 0x9805,
|
||||
PtpOperationCode_MtpSetObjPropList = 0x9806,
|
||||
PtpOperationCode_MtpGetInterdependendPropdesc = 0x9807,
|
||||
PtpOperationCode_MtpSendObjectPropList = 0x9808,
|
||||
PtpOperationCode_MtpGetObjectReferences = 0x9810,
|
||||
PtpOperationCode_MtpSetObjectReferences = 0x9811,
|
||||
PtpOperationCode_MtpUpdateDeviceFirmware = 0x9812,
|
||||
PtpOperationCode_MtpSkip = 0x9820,
|
||||
};
|
||||
|
||||
enum PtpResponseCode : u16 {
|
||||
PtpResponseCode_Undefined = 0x2000,
|
||||
PtpResponseCode_Ok = 0x2001,
|
||||
PtpResponseCode_GeneralError = 0x2002,
|
||||
PtpResponseCode_SessionNotOpen = 0x2003,
|
||||
PtpResponseCode_InvalidTransactionId = 0x2004,
|
||||
PtpResponseCode_OperationNotSupported = 0x2005,
|
||||
PtpResponseCode_ParameterNotSupported = 0x2006,
|
||||
PtpResponseCode_IncompleteTransfer = 0x2007,
|
||||
PtpResponseCode_InvalidStorageId = 0x2008,
|
||||
PtpResponseCode_InvalidObjectHandle = 0x2009,
|
||||
PtpResponseCode_DevicePropNotSupported = 0x200a,
|
||||
PtpResponseCode_InvalidObjectFormatCode = 0x200b,
|
||||
PtpResponseCode_StoreFull = 0x200c,
|
||||
PtpResponseCode_ObjectWriteProtected = 0x200d,
|
||||
PtpResponseCode_StoreReadOnly = 0x200e,
|
||||
PtpResponseCode_AccessDenied = 0x200f,
|
||||
PtpResponseCode_NoThumbnailPresent = 0x2010,
|
||||
PtpResponseCode_SelfTestFailed = 0x2011,
|
||||
PtpResponseCode_PartialDeletion = 0x2012,
|
||||
PtpResponseCode_StoreNotAvailable = 0x2013,
|
||||
PtpResponseCode_SpecificationByFormatUnsupported = 0x2014,
|
||||
PtpResponseCode_NoValidObjectInfo = 0x2015,
|
||||
PtpResponseCode_InvalidCodeFormat = 0x2016,
|
||||
PtpResponseCode_UnknownVendorCode = 0x2017,
|
||||
PtpResponseCode_CaptureAlreadyTerminated = 0x2018,
|
||||
PtpResponseCode_DeviceBusy = 0x2019,
|
||||
PtpResponseCode_InvalidParentObject = 0x201a,
|
||||
PtpResponseCode_InvalidDevicePropFormat = 0x201b,
|
||||
PtpResponseCode_InvalidDevicePropValue = 0x201c,
|
||||
PtpResponseCode_InvalidParameter = 0x201d,
|
||||
PtpResponseCode_SessionAlreadyOpened = 0x201e,
|
||||
PtpResponseCode_TransactionCanceled = 0x201f,
|
||||
PtpResponseCode_SpecificationOfDestinationUnsupported = 0x2020,
|
||||
PtpResponseCode_InvalidEnumHandle = 0x2021,
|
||||
PtpResponseCode_NoStreamEnabled = 0x2022,
|
||||
PtpResponseCode_InvalidDataSet = 0x2023,
|
||||
PtpResponseCode_MtpUndefined = 0xa800,
|
||||
PtpResponseCode_MtpInvalidObjectPropCode = 0xa801,
|
||||
PtpResponseCode_MtpInvalidObjectPropFormat = 0xa802,
|
||||
PtpResponseCode_MtpInvalidObjectPropValue = 0xa803,
|
||||
PtpResponseCode_MtpInvalidObjectReference = 0xa804,
|
||||
PtpResponseCode_MtpInvalidDataset = 0xa806,
|
||||
PtpResponseCode_MtpSpecificationByGroupUnsupported = 0xa807,
|
||||
PtpResponseCode_MtpSpecificationByDepthUnsupported = 0xa808,
|
||||
PtpResponseCode_MtpObjectTooLarge = 0xa809,
|
||||
PtpResponseCode_MtpObjectPropNotSupported = 0xa80a,
|
||||
};
|
||||
|
||||
enum PtpEventCode : u16 {
|
||||
PtpEventCode_Undefined = 0x4000,
|
||||
PtpEventCode_CancelTransaction = 0x4001,
|
||||
PtpEventCode_ObjectAdded = 0x4002,
|
||||
PtpEventCode_ObjectRemoved = 0x4003,
|
||||
PtpEventCode_StoreAdded = 0x4004,
|
||||
PtpEventCode_StoreRemoved = 0x4005,
|
||||
PtpEventCode_DevicePropChanged = 0x4006,
|
||||
PtpEventCode_ObjectInfoChanged = 0x4007,
|
||||
PtpEventCode_DeviceInfoChanged = 0x4008,
|
||||
PtpEventCode_RequestObjectTransfer = 0x4009,
|
||||
PtpEventCode_StoreFull = 0x400a,
|
||||
PtpEventCode_DeviceReset = 0x400b,
|
||||
PtpEventCode_StorageInfoChanged = 0x400c,
|
||||
PtpEventCode_CaptureComplete = 0x400d,
|
||||
PtpEventCode_UnreportedStatus = 0x400e,
|
||||
};
|
||||
|
||||
enum PtpDataTypeCode : u16 {
|
||||
PtpDataTypeCode_Undefined = 0x0000,
|
||||
PtpDataTypeCode_S8 = 0x0001,
|
||||
PtpDataTypeCode_U8 = 0x0002,
|
||||
PtpDataTypeCode_S16 = 0x0003,
|
||||
PtpDataTypeCode_U16 = 0x0004,
|
||||
PtpDataTypeCode_S32 = 0x0005,
|
||||
PtpDataTypeCode_U32 = 0x0006,
|
||||
PtpDataTypeCode_S64 = 0x0007,
|
||||
PtpDataTypeCode_U64 = 0x0008,
|
||||
PtpDataTypeCode_S128 = 0x0009,
|
||||
PtpDataTypeCode_U128 = 0x000a,
|
||||
|
||||
PtpDataTypeCode_ArrayMask = (1u << 14),
|
||||
|
||||
PtpDataTypeCode_S8Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_S8,
|
||||
PtpDataTypeCode_U8Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_U8,
|
||||
PtpDataTypeCode_S16Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_S16,
|
||||
PtpDataTypeCode_U16Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_U16,
|
||||
PtpDataTypeCode_S32Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_S32,
|
||||
PtpDataTypeCode_U32Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_U32,
|
||||
PtpDataTypeCode_S64Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_S64,
|
||||
PtpDataTypeCode_U64Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_U64,
|
||||
PtpDataTypeCode_S128Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_S128,
|
||||
PtpDataTypeCode_U128Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_U128,
|
||||
|
||||
PtpDataTypeCode_String = 0xffff,
|
||||
};
|
||||
|
||||
enum PtpPropertyGetSetFlag : u8 {
|
||||
PtpPropertyGetSetFlag_Get = 0x00,
|
||||
PtpPropertyGetSetFlag_GetSet = 0x01,
|
||||
};
|
||||
|
||||
enum PtpPropertyGroupCode : u32 {
|
||||
PtpPropertyGroupCode_Default = 0x00000000,
|
||||
};
|
||||
|
||||
enum PtpPropertyFormFlag : u8 {
|
||||
PtpPropertyFormFlag_None = 0x00,
|
||||
PtpPropertyFormFlag_Range = 0x01,
|
||||
PtpPropertyFormFlag_Enumeration = 0x02,
|
||||
PtpPropertyFormFlag_DateTime = 0x03,
|
||||
PtpPropertyFormFlag_FixedLengthArray = 0x04,
|
||||
PtpPropertyFormFlag_RegularExpression = 0x05,
|
||||
PtpPropertyFormFlag_ByteArray = 0x06,
|
||||
PtpPropertyFormFlag_LongString = 0xff,
|
||||
};
|
||||
|
||||
enum PtpObjectPropertyCode : u16 {
|
||||
PtpObjectPropertyCode_StorageId = 0xdc01,
|
||||
PtpObjectPropertyCode_ObjectFormat = 0xdc02,
|
||||
PtpObjectPropertyCode_ProtectionStatus = 0xdc03,
|
||||
PtpObjectPropertyCode_ObjectSize = 0xdc04,
|
||||
PtpObjectPropertyCode_AssociationType = 0xdc05,
|
||||
PtpObjectPropertyCode_AssociationDesc = 0xdc06,
|
||||
PtpObjectPropertyCode_ObjectFileName = 0xdc07,
|
||||
PtpObjectPropertyCode_DateCreated = 0xdc08,
|
||||
PtpObjectPropertyCode_DateModified = 0xdc09,
|
||||
PtpObjectPropertyCode_Keywords = 0xdc0a,
|
||||
PtpObjectPropertyCode_ParentObject = 0xdc0b,
|
||||
PtpObjectPropertyCode_AllowedFolderContents = 0xdc0c,
|
||||
PtpObjectPropertyCode_Hidden = 0xdc0d,
|
||||
PtpObjectPropertyCode_SystemObject = 0xdc0e,
|
||||
PtpObjectPropertyCode_PersistentUniqueObjectIdentifier = 0xdc41,
|
||||
PtpObjectPropertyCode_SyncId = 0xdc42,
|
||||
PtpObjectPropertyCode_PropertyBag = 0xdc43,
|
||||
PtpObjectPropertyCode_Name = 0xdc44,
|
||||
PtpObjectPropertyCode_CreatedBy = 0xdc45,
|
||||
PtpObjectPropertyCode_Artist = 0xdc46,
|
||||
PtpObjectPropertyCode_DateAuthored = 0xdc47,
|
||||
PtpObjectPropertyCode_Description = 0xdc48,
|
||||
PtpObjectPropertyCode_UrlReference = 0xdc49,
|
||||
PtpObjectPropertyCode_LanguageLocale = 0xdc4a,
|
||||
PtpObjectPropertyCode_CopyrightInformation = 0xdc4b,
|
||||
PtpObjectPropertyCode_Source = 0xdc4c,
|
||||
PtpObjectPropertyCode_OriginLocation = 0xdc4d,
|
||||
PtpObjectPropertyCode_DateAdded = 0xdc4e,
|
||||
PtpObjectPropertyCode_NonConsumable = 0xdc4f,
|
||||
PtpObjectPropertyCode_CorruptOrUnplayable = 0xdc50,
|
||||
PtpObjectPropertyCode_ProducerSerialNumber = 0xdc51,
|
||||
PtpObjectPropertyCode_RepresentativeSampleFormat = 0xdc81,
|
||||
PtpObjectPropertyCode_RepresentativeSampleSize = 0xdc82,
|
||||
PtpObjectPropertyCode_RepresentativeSampleHeight = 0xdc83,
|
||||
PtpObjectPropertyCode_RepresentativeSampleWidth = 0xdc84,
|
||||
PtpObjectPropertyCode_RepresentativeSampleDuration = 0xdc85,
|
||||
PtpObjectPropertyCode_RepresentativeSampleData = 0xdc86,
|
||||
PtpObjectPropertyCode_Width = 0xdc87,
|
||||
PtpObjectPropertyCode_Height = 0xdc88,
|
||||
PtpObjectPropertyCode_Duration = 0xdc89,
|
||||
PtpObjectPropertyCode_Rating = 0xdc8a,
|
||||
PtpObjectPropertyCode_Track = 0xdc8b,
|
||||
PtpObjectPropertyCode_Genre = 0xdc8c,
|
||||
PtpObjectPropertyCode_Credits = 0xdc8d,
|
||||
PtpObjectPropertyCode_Lyrics = 0xdc8e,
|
||||
PtpObjectPropertyCode_SubscriptionContentId = 0xdc8f,
|
||||
PtpObjectPropertyCode_ProducedBy = 0xdc90,
|
||||
PtpObjectPropertyCode_UseCount = 0xdc91,
|
||||
PtpObjectPropertyCode_SkipCount = 0xdc92,
|
||||
PtpObjectPropertyCode_LastAccessed = 0xdc93,
|
||||
PtpObjectPropertyCode_ParentalRating = 0xdc94,
|
||||
PtpObjectPropertyCode_MetaGenre = 0xdc95,
|
||||
PtpObjectPropertyCode_Composer = 0xdc96,
|
||||
PtpObjectPropertyCode_EffectiveRating = 0xdc97,
|
||||
PtpObjectPropertyCode_Subtitle = 0xdc98,
|
||||
PtpObjectPropertyCode_OriginalReleaseDate = 0xdc99,
|
||||
PtpObjectPropertyCode_AlbumName = 0xdc9a,
|
||||
PtpObjectPropertyCode_AlbumArtist = 0xdc9b,
|
||||
PtpObjectPropertyCode_Mood = 0xdc9c,
|
||||
PtpObjectPropertyCode_DrmStatus = 0xdc9d,
|
||||
PtpObjectPropertyCode_SubDescription = 0xdc9e,
|
||||
PtpObjectPropertyCode_IsCropped = 0xdcd1,
|
||||
PtpObjectPropertyCode_IsColorCorrected = 0xdcd2,
|
||||
PtpObjectPropertyCode_ImageBitDepth = 0xdcd3,
|
||||
PtpObjectPropertyCode_Fnumber = 0xdcd4,
|
||||
PtpObjectPropertyCode_ExposureTime = 0xdcd5,
|
||||
PtpObjectPropertyCode_ExposureIndex = 0xdcd6,
|
||||
PtpObjectPropertyCode_DisplayName = 0xdce0,
|
||||
PtpObjectPropertyCode_BodyText = 0xdce1,
|
||||
PtpObjectPropertyCode_Subject = 0xdce2,
|
||||
PtpObjectPropertyCode_Priority = 0xdce3,
|
||||
PtpObjectPropertyCode_GivenName = 0xdd00,
|
||||
PtpObjectPropertyCode_MiddleNames = 0xdd01,
|
||||
PtpObjectPropertyCode_FamilyName = 0xdd02,
|
||||
PtpObjectPropertyCode_Prefix = 0xdd03,
|
||||
PtpObjectPropertyCode_Suffix = 0xdd04,
|
||||
PtpObjectPropertyCode_PhoneticGivenName = 0xdd05,
|
||||
PtpObjectPropertyCode_PhoneticFamilyName = 0xdd06,
|
||||
PtpObjectPropertyCode_EmailPrimary = 0xdd07,
|
||||
PtpObjectPropertyCode_EmailPersonal1 = 0xdd08,
|
||||
PtpObjectPropertyCode_EmailPersonal2 = 0xdd09,
|
||||
PtpObjectPropertyCode_EmailBusiness1 = 0xdd0a,
|
||||
PtpObjectPropertyCode_EmailBusiness2 = 0xdd0b,
|
||||
PtpObjectPropertyCode_EmailOthers = 0xdd0c,
|
||||
PtpObjectPropertyCode_PhoneNumberPrimary = 0xdd0d,
|
||||
PtpObjectPropertyCode_PhoneNumberPersonal = 0xdd0e,
|
||||
PtpObjectPropertyCode_PhoneNumberPersonal2 = 0xdd0f,
|
||||
PtpObjectPropertyCode_PhoneNumberBusiness = 0xdd10,
|
||||
PtpObjectPropertyCode_PhoneNumberBusiness2 = 0xdd11,
|
||||
PtpObjectPropertyCode_PhoneNumberMobile = 0xdd12,
|
||||
PtpObjectPropertyCode_PhoneNumberMobile2 = 0xdd13,
|
||||
PtpObjectPropertyCode_FaxNumberPrimary = 0xdd14,
|
||||
PtpObjectPropertyCode_FaxNumberPersonal = 0xdd15,
|
||||
PtpObjectPropertyCode_FaxNumberBusiness = 0xdd16,
|
||||
PtpObjectPropertyCode_PagerNumber = 0xdd17,
|
||||
PtpObjectPropertyCode_PhoneNumberOthers = 0xdd18,
|
||||
PtpObjectPropertyCode_PrimaryWebAddress = 0xdd19,
|
||||
PtpObjectPropertyCode_PersonalWebAddress = 0xdd1a,
|
||||
PtpObjectPropertyCode_BusinessWebAddress = 0xdd1b,
|
||||
PtpObjectPropertyCode_InstantMessengerAddress = 0xdd1c,
|
||||
PtpObjectPropertyCode_InstantMessengerAddress2 = 0xdd1d,
|
||||
PtpObjectPropertyCode_InstantMessengerAddress3 = 0xdd1e,
|
||||
PtpObjectPropertyCode_PostalAddressPersonalFull = 0xdd1f,
|
||||
PtpObjectPropertyCode_PostalAddressPersonalFullLine1 = 0xdd20,
|
||||
PtpObjectPropertyCode_PostalAddressPersonalFullLine2 = 0xdd21,
|
||||
PtpObjectPropertyCode_PostalAddressPersonalFullCity = 0xdd22,
|
||||
PtpObjectPropertyCode_PostalAddressPersonalFullRegion = 0xdd23,
|
||||
PtpObjectPropertyCode_PostalAddressPersonalFullPostalCode = 0xdd24,
|
||||
PtpObjectPropertyCode_PostalAddressPersonalFullCountry = 0xdd25,
|
||||
PtpObjectPropertyCode_PostalAddressBusinessFull = 0xdd26,
|
||||
PtpObjectPropertyCode_PostalAddressBusinessLine1 = 0xdd27,
|
||||
PtpObjectPropertyCode_PostalAddressBusinessLine2 = 0xdd28,
|
||||
PtpObjectPropertyCode_PostalAddressBusinessCity = 0xdd29,
|
||||
PtpObjectPropertyCode_PostalAddressBusinessRegion = 0xdd2a,
|
||||
PtpObjectPropertyCode_PostalAddressBusinessPostalCode = 0xdd2b,
|
||||
PtpObjectPropertyCode_PostalAddressBusinessCountry = 0xdd2c,
|
||||
PtpObjectPropertyCode_PostalAddressOtherFull = 0xdd2d,
|
||||
PtpObjectPropertyCode_PostalAddressOtherLine1 = 0xdd2e,
|
||||
PtpObjectPropertyCode_PostalAddressOtherLine2 = 0xdd2f,
|
||||
PtpObjectPropertyCode_PostalAddressOtherCity = 0xdd30,
|
||||
PtpObjectPropertyCode_PostalAddressOtherRegion = 0xdd31,
|
||||
PtpObjectPropertyCode_PostalAddressOtherPostalCode = 0xdd32,
|
||||
PtpObjectPropertyCode_PostalAddressOtherCountry = 0xdd33,
|
||||
PtpObjectPropertyCode_OrganizationName = 0xdd34,
|
||||
PtpObjectPropertyCode_PhoneticOrganizationName = 0xdd35,
|
||||
PtpObjectPropertyCode_Role = 0xdd36,
|
||||
PtpObjectPropertyCode_Birthdate = 0xdd37,
|
||||
PtpObjectPropertyCode_MessageTo = 0xdd40,
|
||||
PtpObjectPropertyCode_MessageCC = 0xdd41,
|
||||
PtpObjectPropertyCode_MessageBCC = 0xdd42,
|
||||
PtpObjectPropertyCode_MessageRead = 0xdd43,
|
||||
PtpObjectPropertyCode_MessageReceivedTime = 0xdd44,
|
||||
PtpObjectPropertyCode_MessageSender = 0xdd45,
|
||||
PtpObjectPropertyCode_ActivityBeginTime = 0xdd50,
|
||||
PtpObjectPropertyCode_ActivityEndTime = 0xdd51,
|
||||
PtpObjectPropertyCode_ActivityLocation = 0xdd52,
|
||||
PtpObjectPropertyCode_ActivityRequiredAttendees = 0xdd54,
|
||||
PtpObjectPropertyCode_ActivityOptionalAttendees = 0xdd55,
|
||||
PtpObjectPropertyCode_ActivityResources = 0xdd56,
|
||||
PtpObjectPropertyCode_ActivityAccepted = 0xdd57,
|
||||
PtpObjectPropertyCode_Owner = 0xdd5d,
|
||||
PtpObjectPropertyCode_Editor = 0xdd5e,
|
||||
PtpObjectPropertyCode_Webmaster = 0xdd5f,
|
||||
PtpObjectPropertyCode_UrlSource = 0xdd60,
|
||||
PtpObjectPropertyCode_UrlDestination = 0xdd61,
|
||||
PtpObjectPropertyCode_TimeBookmark = 0xdd62,
|
||||
PtpObjectPropertyCode_ObjectBookmark = 0xdd63,
|
||||
PtpObjectPropertyCode_ByteBookmark = 0xdd64,
|
||||
PtpObjectPropertyCode_LastBuildDate = 0xdd70,
|
||||
PtpObjectPropertyCode_TimetoLive = 0xdd71,
|
||||
PtpObjectPropertyCode_MediaGuid = 0xdd72,
|
||||
PtpObjectPropertyCode_TotalBitRate = 0xde91,
|
||||
PtpObjectPropertyCode_BitRateType = 0xde92,
|
||||
PtpObjectPropertyCode_SampleRate = 0xde93,
|
||||
PtpObjectPropertyCode_NumberOfChannels = 0xde94,
|
||||
PtpObjectPropertyCode_AudioBitDepth = 0xde95,
|
||||
PtpObjectPropertyCode_ScanDepth = 0xde97,
|
||||
PtpObjectPropertyCode_AudioWaveCodec = 0xde99,
|
||||
PtpObjectPropertyCode_AudioBitRate = 0xde9a,
|
||||
PtpObjectPropertyCode_VideoFourCcCodec = 0xde9b,
|
||||
PtpObjectPropertyCode_VideoBitRate = 0xde9c,
|
||||
PtpObjectPropertyCode_FramesPerThousandSeconds = 0xde9d,
|
||||
PtpObjectPropertyCode_KeyFrameDistance = 0xde9e,
|
||||
PtpObjectPropertyCode_BufferSize = 0xde9f,
|
||||
PtpObjectPropertyCode_EncodingQuality = 0xdea0,
|
||||
PtpObjectPropertyCode_EncodingProfile = 0xdea1,
|
||||
PtpObjectPropertyCode_BuyFlag = 0xd901,
|
||||
};
|
||||
|
||||
enum PtpDevicePropertyCode : u16 {
|
||||
PtpDevicePropertyCode_Undefined = 0x5000,
|
||||
PtpDevicePropertyCode_BatteryLevel = 0x5001,
|
||||
PtpDevicePropertyCode_FunctionalMode = 0x5002,
|
||||
PtpDevicePropertyCode_ImageSize = 0x5003,
|
||||
PtpDevicePropertyCode_CompressionSetting = 0x5004,
|
||||
PtpDevicePropertyCode_WhiteBalance = 0x5005,
|
||||
PtpDevicePropertyCode_RgbGain = 0x5006,
|
||||
PtpDevicePropertyCode_FNumber = 0x5007,
|
||||
PtpDevicePropertyCode_FocalLength = 0x5008,
|
||||
PtpDevicePropertyCode_FocusDistance = 0x5009,
|
||||
PtpDevicePropertyCode_FocusMode = 0x500a,
|
||||
PtpDevicePropertyCode_ExposureMeteringMode = 0x500b,
|
||||
PtpDevicePropertyCode_FlashMode = 0x500c,
|
||||
PtpDevicePropertyCode_ExposureTime = 0x500d,
|
||||
PtpDevicePropertyCode_ExposureProgramMode = 0x500e,
|
||||
PtpDevicePropertyCode_ExposureIndex = 0x500f,
|
||||
PtpDevicePropertyCode_ExposureBiasCompensation = 0x5010,
|
||||
PtpDevicePropertyCode_DateTime = 0x5011,
|
||||
PtpDevicePropertyCode_CaptureDelay = 0x5012,
|
||||
PtpDevicePropertyCode_StillCaptureMode = 0x5013,
|
||||
PtpDevicePropertyCode_Contrast = 0x5014,
|
||||
PtpDevicePropertyCode_Sharpness = 0x5015,
|
||||
PtpDevicePropertyCode_DigitalZoom = 0x5016,
|
||||
PtpDevicePropertyCode_EffectMode = 0x5017,
|
||||
PtpDevicePropertyCode_BurstNumber = 0x5018,
|
||||
PtpDevicePropertyCode_BurstInterval = 0x5019,
|
||||
PtpDevicePropertyCode_TimelapseNumber = 0x501a,
|
||||
PtpDevicePropertyCode_TimelapseInterval = 0x501b,
|
||||
PtpDevicePropertyCode_FocusMeteringMode = 0x501c,
|
||||
PtpDevicePropertyCode_UploadUrl = 0x501d,
|
||||
PtpDevicePropertyCode_Artist = 0x501e,
|
||||
PtpDevicePropertyCode_CopyrightInfo = 0x501f,
|
||||
PtpDevicePropertyCode_SupportedStreams = 0x5020,
|
||||
PtpDevicePropertyCode_EnabledStreams = 0x5021,
|
||||
PtpDevicePropertyCode_VideoFormat = 0x5022,
|
||||
PtpDevicePropertyCode_VideoResolution = 0x5023,
|
||||
PtpDevicePropertyCode_VideoQuality = 0x5024,
|
||||
PtpDevicePropertyCode_VideoFrameRate = 0x5025,
|
||||
PtpDevicePropertyCode_VideoContrast = 0x5026,
|
||||
PtpDevicePropertyCode_VideoBrightness = 0x5027,
|
||||
PtpDevicePropertyCode_AudioFormat = 0x5028,
|
||||
PtpDevicePropertyCode_AudioBitrate = 0x5029,
|
||||
PtpDevicePropertyCode_AudioSamplingRate = 0x502a,
|
||||
PtpDevicePropertyCode_AudioBitPerSample = 0x502b,
|
||||
PtpDevicePropertyCode_AudioVolume = 0x502c,
|
||||
};
|
||||
|
||||
enum PtpObjectFormatCode : u16 {
|
||||
PtpObjectFormatCode_Undefined = 0x3000,
|
||||
PtpObjectFormatCode_Association = 0x3001,
|
||||
PtpObjectFormatCode_Defined = 0x3800,
|
||||
PtpObjectFormatCode_MtpMediaCard = 0xb211,
|
||||
};
|
||||
|
||||
enum PtpAssociationType : u16 {
|
||||
PtpAssociationType_Undefined = 0x0000,
|
||||
PtpAssociationType_GenericFolder = 0x0001,
|
||||
};
|
||||
|
||||
enum PtpGetObjectHandles : u32 {
|
||||
PtpGetObjectHandles_AllFormats = 0x00000000,
|
||||
PtpGetObjectHandles_AllAssocs = 0x00000000,
|
||||
PtpGetObjectHandles_AllStorage = 0xffffffff,
|
||||
PtpGetObjectHandles_RootParent = 0xffffffff,
|
||||
};
|
||||
|
||||
enum PtpStorageType : u16 {
|
||||
PtpStorageType_Undefined = 0x0000,
|
||||
PtpStorageType_FixedRom = 0x0001,
|
||||
PtpStorageType_RemovableRom = 0x0002,
|
||||
PtpStorageType_FixedRam = 0x0003,
|
||||
PtpStorageType_RemovableRam = 0x0004,
|
||||
};
|
||||
|
||||
enum PtpFilesystemType : u16 {
|
||||
PtpFilesystemType_Undefined = 0x0000,
|
||||
PtpFilesystemType_GenericFlat = 0x0001,
|
||||
PtpFilesystemType_GenericHierarchical = 0x0002,
|
||||
PtpFilesystemType_Dcf = 0x0003,
|
||||
};
|
||||
|
||||
enum PtpAccessCapability : u16 {
|
||||
PtpAccessCapability_ReadWrite = 0x0000,
|
||||
PtpAccessCapability_ReadOnly = 0x0001,
|
||||
PtpAccessCapability_ReadOnlyWithObjectDeletion = 0x0002,
|
||||
};
|
||||
|
||||
enum PtpProtectionStatus : u16 {
|
||||
PtpProtectionStatus_NoProtection = 0x0000,
|
||||
PtpProtectionStatus_ReadOnly = 0x0001,
|
||||
PtpProtectionStatus_MtpReadOnlyData = 0x8002,
|
||||
PtpProtectionStatus_MtpNonTransferableData = 0x8003,
|
||||
};
|
||||
|
||||
enum PtpThumbFormat : u16 {
|
||||
PtpThumbFormat_Undefined = 0x0000,
|
||||
};
|
||||
|
||||
struct PtpUsbBulkContainer {
|
||||
u32 length;
|
||||
u16 type;
|
||||
u16 code;
|
||||
u32 trans_id;
|
||||
};
|
||||
static_assert(sizeof(PtpUsbBulkContainer) == PtpUsbBulkHeaderLength);
|
||||
|
||||
struct PtpNewObjectInfo {
|
||||
u32 storage_id;
|
||||
u32 parent_object_id;
|
||||
u32 object_id;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <haze/async_usb_server.hpp>
|
||||
#include <haze/common.hpp>
|
||||
#include <haze/ptp.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
class PtpDataBuilder final {
|
||||
private:
|
||||
AsyncUsbServer *m_server;
|
||||
u32 m_transmitted_size;
|
||||
u32 m_offset;
|
||||
u8 *m_data;
|
||||
bool m_disabled;
|
||||
private:
|
||||
Result Flush() {
|
||||
ON_SCOPE_EXIT {
|
||||
m_transmitted_size += m_offset;
|
||||
m_offset = 0;
|
||||
};
|
||||
|
||||
/* If we're disabled, we have nothing to do. */
|
||||
R_SUCCEED_IF(m_disabled);
|
||||
|
||||
/* Otherwise, we should write our buffered data. */
|
||||
R_RETURN(m_server->WritePacket(m_data, m_offset));
|
||||
}
|
||||
public:
|
||||
constexpr explicit PtpDataBuilder(void *data, AsyncUsbServer *server) : m_server(server), m_transmitted_size(), m_offset(), m_data(static_cast<u8 *>(data)), m_disabled() { /* ... */ }
|
||||
|
||||
Result Commit() {
|
||||
if (m_offset > 0) {
|
||||
/* If there is remaining data left to write, write it now. */
|
||||
R_TRY(this->Flush());
|
||||
}
|
||||
|
||||
if (util::IsAligned(m_transmitted_size, PtpUsbBulkHighSpeedMaxPacketLength)) {
|
||||
/* If the transmission size was a multiple of wMaxPacketSize, send a zero length packet. */
|
||||
R_TRY(this->Flush());
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result AddBuffer(const u8 *buffer, u32 count) {
|
||||
while (count > 0) {
|
||||
/* Calculate how many bytes we can write now. */
|
||||
const u32 write_size = std::min<u32>(count, haze::UsbBulkPacketBufferSize - m_offset);
|
||||
|
||||
/* Write this number of bytes. */
|
||||
std::memcpy(m_data + m_offset, buffer, write_size);
|
||||
m_offset += write_size;
|
||||
buffer += write_size;
|
||||
count -= write_size;
|
||||
|
||||
/* If our buffer is full, flush it. */
|
||||
if (m_offset == haze::UsbBulkPacketBufferSize) {
|
||||
R_TRY(this->Flush());
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Result Add(T value) {
|
||||
u8 bytes[sizeof(T)];
|
||||
|
||||
std::memcpy(bytes, std::addressof(value), sizeof(T));
|
||||
|
||||
R_RETURN(this->AddBuffer(bytes, sizeof(T)));
|
||||
}
|
||||
|
||||
Result AddDataHeader(PtpUsbBulkContainer &request, u32 data_size) {
|
||||
R_TRY(this->Add<u32>(PtpUsbBulkHeaderLength + data_size));
|
||||
R_TRY(this->Add<u16>(PtpUsbBulkContainerType_Data));
|
||||
R_TRY(this->Add<u16>(request.code));
|
||||
R_TRY(this->Add<u32>(request.trans_id));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result AddResponseHeader(PtpUsbBulkContainer &request, PtpResponseCode code, u32 params_size) {
|
||||
R_TRY(this->Add<u32>(PtpUsbBulkHeaderLength + params_size));
|
||||
R_TRY(this->Add<u16>(PtpUsbBulkContainerType_Response));
|
||||
R_TRY(this->Add<u16>(code));
|
||||
R_TRY(this->Add<u32>(request.trans_id));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
Result WriteVariableLengthData(PtpUsbBulkContainer &request, F &&func) {
|
||||
HAZE_ASSERT(m_offset == 0 && m_transmitted_size == 0);
|
||||
|
||||
/* Declare how many bytes the data will require to write. */
|
||||
u32 data_size = 0;
|
||||
{
|
||||
/* Temporarily disable writing to calculate the size.*/
|
||||
m_disabled = true;
|
||||
|
||||
ON_SCOPE_EXIT {
|
||||
/* Report how many bytes were required. */
|
||||
data_size = m_transmitted_size;
|
||||
|
||||
/* On exit, enable writing and reset sizes. */
|
||||
m_transmitted_size = 0;
|
||||
m_disabled = false;
|
||||
m_offset = 0;
|
||||
};
|
||||
|
||||
R_TRY(func());
|
||||
R_TRY(this->Commit());
|
||||
}
|
||||
|
||||
/* Actually copy and write the data. */
|
||||
R_TRY(this->AddDataHeader(request, data_size));
|
||||
R_TRY(func());
|
||||
R_TRY(this->Commit());
|
||||
|
||||
/* We succeeded. */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Result AddString(const T *str) {
|
||||
/* Use one less than the maximum string length for maximum length with null terminator. */
|
||||
const u8 len = static_cast<u8>(std::min<s32>(util::Strlen(str), PtpStringMaxLength - 1));
|
||||
|
||||
if (len > 0) {
|
||||
/* Length is padded by null terminator for non-empty strings. */
|
||||
R_TRY(this->Add<u8>(len + 1));
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
R_TRY(this->Add<u16>(str[i]));
|
||||
}
|
||||
|
||||
R_TRY(this->Add<u16>(0));
|
||||
} else {
|
||||
R_TRY(this->Add<u8>(len));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Result AddArray(const T *arr, u32 count) {
|
||||
R_TRY(this->Add<u32>(count));
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
R_TRY(this->Add<T>(arr[i]));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <haze/async_usb_server.hpp>
|
||||
#include <haze/common.hpp>
|
||||
#include <haze/ptp.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
class PtpDataParser final {
|
||||
private:
|
||||
AsyncUsbServer *m_server;
|
||||
u32 m_received_size;
|
||||
u32 m_offset;
|
||||
u8 *m_data;
|
||||
bool m_eot;
|
||||
private:
|
||||
Result Flush() {
|
||||
R_UNLESS(!m_eot, haze::ResultEndOfTransmission());
|
||||
|
||||
m_received_size = 0;
|
||||
m_offset = 0;
|
||||
|
||||
ON_SCOPE_EXIT {
|
||||
/* End of transmission occurs when receiving a bulk transfer less than the buffer size. */
|
||||
/* PTP uses zero-length termination, so zero is a possible size to receive. */
|
||||
m_eot = m_received_size < haze::UsbBulkPacketBufferSize;
|
||||
};
|
||||
|
||||
R_RETURN(m_server->ReadPacket(m_data, haze::UsbBulkPacketBufferSize, std::addressof(m_received_size)));
|
||||
}
|
||||
public:
|
||||
constexpr explicit PtpDataParser(void *data, AsyncUsbServer *server) : m_server(server), m_received_size(), m_offset(), m_data(static_cast<u8 *>(data)), m_eot() { /* ... */ }
|
||||
|
||||
Result Finalize() {
|
||||
/* Read until the transmission completes. */
|
||||
while (true) {
|
||||
Result rc = this->Flush();
|
||||
|
||||
R_SUCCEED_IF(m_eot || haze::ResultEndOfTransmission::Includes(rc));
|
||||
R_TRY(rc);
|
||||
}
|
||||
}
|
||||
|
||||
Result ReadBuffer(u8 *buffer, u32 count, u32 *out_read_count) {
|
||||
*out_read_count = 0;
|
||||
|
||||
while (count > 0) {
|
||||
/* If we cannot read more bytes now, flush. */
|
||||
if (m_offset == m_received_size) {
|
||||
R_TRY(this->Flush());
|
||||
}
|
||||
|
||||
/* Calculate how many bytes we can read now. */
|
||||
u32 read_size = std::min<u32>(count, m_received_size - m_offset);
|
||||
|
||||
/* Read this number of bytes. */
|
||||
std::memcpy(buffer + *out_read_count, m_data + m_offset, read_size);
|
||||
*out_read_count += read_size;
|
||||
m_offset += read_size;
|
||||
count -= read_size;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Result Read(T *out_t) {
|
||||
u32 read_count;
|
||||
u8 bytes[sizeof(T)];
|
||||
|
||||
R_TRY(this->ReadBuffer(bytes, sizeof(T), std::addressof(read_count)));
|
||||
|
||||
std::memcpy(out_t, bytes, sizeof(T));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* NOTE: out_string must contain room for 256 bytes. */
|
||||
/* The result will be null-terminated on successful completion. */
|
||||
Result ReadString(char *out_string) {
|
||||
u8 len;
|
||||
R_TRY(this->Read(std::addressof(len)));
|
||||
|
||||
/* Read characters one by one. */
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
u16 chr;
|
||||
R_TRY(this->Read(std::addressof(chr)));
|
||||
|
||||
*out_string++ = static_cast<char>(chr);
|
||||
}
|
||||
|
||||
/* Write null terminator. */
|
||||
*out_string++ = '\x00';
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <haze/common.hpp>
|
||||
#include <haze/ptp_object_heap.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
struct PtpObject {
|
||||
public:
|
||||
util::IntrusiveRedBlackTreeNode m_name_node;
|
||||
util::IntrusiveRedBlackTreeNode m_object_id_node;
|
||||
u32 m_parent_id;
|
||||
u32 m_object_id;
|
||||
char m_name[];
|
||||
public:
|
||||
const char *GetName() const { return m_name; }
|
||||
u32 GetParentId() const { return m_parent_id; }
|
||||
u32 GetObjectId() const { return m_object_id; }
|
||||
public:
|
||||
bool GetIsRegistered() const { return m_object_id != 0; }
|
||||
void Register(u32 object_id) { m_object_id = object_id; }
|
||||
void Unregister() { m_object_id = 0; }
|
||||
public:
|
||||
struct NameComparator {
|
||||
struct RedBlackKeyType {
|
||||
const char *m_name;
|
||||
|
||||
constexpr RedBlackKeyType(const char *name) : m_name(name) { /* ... */ }
|
||||
|
||||
constexpr const char *GetName() const {
|
||||
return m_name;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> requires (std::same_as<T, PtpObject> || std::same_as<T, RedBlackKeyType>)
|
||||
static constexpr int Compare(const T &lhs, const PtpObject &rhs) {
|
||||
/* All SD card filesystems supported by fs are case-insensitive and case-preserving. */
|
||||
/* Account for that in collation here. */
|
||||
return strcasecmp(lhs.GetName(), rhs.GetName());
|
||||
}
|
||||
};
|
||||
|
||||
struct ObjectIdComparator {
|
||||
struct RedBlackKeyType {
|
||||
u32 m_object_id;
|
||||
|
||||
constexpr RedBlackKeyType(u32 object_id) : m_object_id(object_id) { /* ... */ }
|
||||
|
||||
constexpr u32 GetObjectId() const {
|
||||
return m_object_id;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> requires (std::same_as<T, PtpObject> || std::same_as<T, RedBlackKeyType>)
|
||||
static constexpr int Compare(const T &lhs, const PtpObject &rhs) {
|
||||
return lhs.GetObjectId() - rhs.GetObjectId();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
class PtpObjectDatabase {
|
||||
private:
|
||||
using ObjectNameTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&PtpObject::m_name_node>;
|
||||
using ObjectNameTree = ObjectNameTreeTraits::TreeType<PtpObject::NameComparator>;
|
||||
|
||||
using ObjectIdTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&PtpObject::m_object_id_node>;
|
||||
using ObjectIdTree = ObjectIdTreeTraits::TreeType<PtpObject::ObjectIdComparator>;
|
||||
|
||||
PtpObjectHeap *m_object_heap;
|
||||
ObjectNameTree m_name_tree;
|
||||
ObjectIdTree m_object_id_tree;
|
||||
u32 m_next_object_id;
|
||||
public:
|
||||
constexpr explicit PtpObjectDatabase() : m_object_heap(), m_name_tree(), m_object_id_tree(), m_next_object_id() { /* ... */ }
|
||||
|
||||
void Initialize(PtpObjectHeap *object_heap);
|
||||
void Finalize();
|
||||
public:
|
||||
/* Object database API. */
|
||||
Result CreateOrFindObject(const char *parent_name, const char *name, u32 parent_id, PtpObject **out_object);
|
||||
void RegisterObject(PtpObject *object, u32 desired_id = 0);
|
||||
void UnregisterObject(PtpObject *object);
|
||||
void DeleteObject(PtpObject *obj);
|
||||
|
||||
Result CreateAndRegisterObjectId(const char *parent_name, const char *name, u32 parent_id, u32 *out_object_id);
|
||||
public:
|
||||
PtpObject *GetObjectById(u32 object_id);
|
||||
PtpObject *GetObjectByName(const char *name);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <haze/common.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
/* This simple linear allocator implementation allows us to rapidly reclaim the entire object graph. */
|
||||
/* This is critical for maintaining interactivity when a session is closed. */
|
||||
class PtpObjectHeap {
|
||||
private:
|
||||
static constexpr size_t NumHeapBlocks = 2;
|
||||
private:
|
||||
void *m_heap_blocks[NumHeapBlocks];
|
||||
void *m_next_address;
|
||||
u32 m_heap_block_size;
|
||||
u32 m_current_heap_block;
|
||||
public:
|
||||
constexpr explicit PtpObjectHeap() : m_heap_blocks(), m_next_address(), m_heap_block_size(), m_current_heap_block() { /* ... */ }
|
||||
|
||||
void Initialize();
|
||||
void Finalize();
|
||||
public:
|
||||
constexpr size_t GetTotalSize() const {
|
||||
return m_heap_block_size * NumHeapBlocks;
|
||||
}
|
||||
|
||||
constexpr size_t GetUsedSize() const {
|
||||
return (m_heap_block_size * m_current_heap_block) + this->GetNextAddress() - this->GetFirstAddress();
|
||||
}
|
||||
private:
|
||||
constexpr u8 *GetNextAddress() const { return static_cast<u8 *>(m_next_address); }
|
||||
constexpr u8 *GetFirstAddress() const { return static_cast<u8 *>(m_heap_blocks[m_current_heap_block]); }
|
||||
|
||||
constexpr u8 *GetCurrentBlockEnd() const {
|
||||
return this->GetFirstAddress() + m_heap_block_size;
|
||||
}
|
||||
|
||||
constexpr bool AllocationIsPossible(size_t n) const {
|
||||
return n <= m_heap_block_size;
|
||||
}
|
||||
|
||||
constexpr bool AllocationIsSatisfyable(size_t n) const {
|
||||
/* Check for overflow. */
|
||||
if (!util::CanAddWithoutOverflow(reinterpret_cast<uintptr_t>(this->GetNextAddress()), n)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check if we would exceed the size of the current block. */
|
||||
if (this->GetNextAddress() + n > this->GetCurrentBlockEnd()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr bool AdvanceToNextBlock() {
|
||||
if (m_current_heap_block < NumHeapBlocks - 1) {
|
||||
m_next_address = m_heap_blocks[++m_current_heap_block];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr void *AllocateFromCurrentBlock(size_t n) {
|
||||
void *result = this->GetNextAddress();
|
||||
|
||||
m_next_address = this->GetNextAddress() + n;
|
||||
|
||||
return result;
|
||||
}
|
||||
public:
|
||||
template <typename T = void>
|
||||
constexpr T *Allocate(size_t n) {
|
||||
/* Check for overflow in alignment. */
|
||||
if (!util::CanAddWithoutOverflow(n, alignof(u64) - 1)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Align the amount to satisfy allocation for u64. */
|
||||
n = util::AlignUp(n, alignof(u64));
|
||||
|
||||
/* Check if the allocation is possible. */
|
||||
if (!this->AllocationIsPossible(n)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* If the allocation is not satisfyable now, we might be able to satisfy it on the next block. */
|
||||
/* However, if the next block would be empty, we won't be able to satisfy the request. */
|
||||
if (!this->AllocationIsSatisfyable(n) && !this->AdvanceToNextBlock()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Allocate the memory. */
|
||||
return static_cast<T *>(this->AllocateFromCurrentBlock(n));
|
||||
}
|
||||
|
||||
constexpr void Deallocate(void *p, size_t n) {
|
||||
/* Check for overflow in alignment. */
|
||||
if (!util::CanAddWithoutOverflow(n, alignof(u64) - 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Align the amount to satisfy allocation for u64. */
|
||||
n = util::AlignUp(n, alignof(u64));
|
||||
|
||||
/* If the pointer was the last allocation, return the memory to the heap. */
|
||||
if (static_cast<u8 *>(p) + n == this->GetNextAddress()) {
|
||||
m_next_address = this->GetNextAddress() - n;
|
||||
}
|
||||
|
||||
/* Otherwise, do nothing. */
|
||||
/* ... */
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <haze/common.hpp>
|
||||
#include <haze/async_usb_server.hpp>
|
||||
#include <haze/ptp_object_heap.hpp>
|
||||
#include <haze/ptp_object_database.hpp>
|
||||
#include <haze/ptp_responder_types.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
class PtpDataParser;
|
||||
|
||||
class PtpResponder final {
|
||||
private:
|
||||
AsyncUsbServer m_usb_server;
|
||||
FileSystemProxy m_fs;
|
||||
PtpUsbBulkContainer m_request_header;
|
||||
PtpObjectHeap *m_object_heap;
|
||||
PtpBuffers* m_buffers;
|
||||
u32 m_send_object_id;
|
||||
bool m_session_open;
|
||||
|
||||
PtpObjectDatabase m_object_database;
|
||||
public:
|
||||
constexpr explicit PtpResponder() : m_usb_server(), m_fs(), m_request_header(), m_object_heap(), m_buffers(), m_send_object_id(), m_session_open(), m_object_database() { /* ... */ }
|
||||
|
||||
Result Initialize(EventReactor *reactor, PtpObjectHeap *object_heap);
|
||||
void Finalize();
|
||||
public:
|
||||
Result LoopProcess();
|
||||
private:
|
||||
/* Request handling. */
|
||||
Result HandleRequest();
|
||||
Result HandleRequestImpl();
|
||||
Result HandleCommandRequest(PtpDataParser &dp);
|
||||
void ForceCloseSession();
|
||||
|
||||
Result WriteResponse(PtpResponseCode code, const void* data, size_t size);
|
||||
Result WriteResponse(PtpResponseCode code);
|
||||
|
||||
template <typename Data> requires (util::is_pod<Data>::value)
|
||||
Result WriteResponse(PtpResponseCode code, const Data &data) {
|
||||
R_RETURN(this->WriteResponse(code, std::addressof(data), sizeof(data)));
|
||||
}
|
||||
|
||||
/* PTP operations. */
|
||||
Result GetDeviceInfo(PtpDataParser &dp);
|
||||
Result OpenSession(PtpDataParser &dp);
|
||||
Result CloseSession(PtpDataParser &dp);
|
||||
Result GetStorageIds(PtpDataParser &dp);
|
||||
Result GetStorageInfo(PtpDataParser &dp);
|
||||
Result GetObjectHandles(PtpDataParser &dp);
|
||||
Result GetObjectInfo(PtpDataParser &dp);
|
||||
Result GetObject(PtpDataParser &dp);
|
||||
Result SendObjectInfo(PtpDataParser &dp);
|
||||
Result SendObject(PtpDataParser &dp);
|
||||
Result DeleteObject(PtpDataParser &dp);
|
||||
|
||||
/* Android operations. */
|
||||
Result GetPartialObject64(PtpDataParser &dp);
|
||||
Result SendPartialObject(PtpDataParser &dp);
|
||||
Result TruncateObject(PtpDataParser &dp);
|
||||
Result BeginEditObject(PtpDataParser &dp);
|
||||
Result EndEditObject(PtpDataParser &dp);
|
||||
|
||||
/* MTP operations. */
|
||||
Result GetObjectPropsSupported(PtpDataParser &dp);
|
||||
Result GetObjectPropDesc(PtpDataParser &dp);
|
||||
Result GetObjectPropValue(PtpDataParser &dp);
|
||||
Result SetObjectPropValue(PtpDataParser &dp);
|
||||
Result GetObjectPropList(PtpDataParser &dp);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
constexpr UsbCommsInterfaceInfo MtpInterfaceInfo = {
|
||||
.bInterfaceClass = 0x06,
|
||||
.bInterfaceSubClass = 0x01,
|
||||
.bInterfaceProtocol = 0x01,
|
||||
};
|
||||
|
||||
/* This is a VID:PID recognized by libmtp. */
|
||||
constexpr u16 SwitchMtpIdVendor = 0x057e;
|
||||
constexpr u16 SwitchMtpIdProduct = 0x201d;
|
||||
|
||||
/* Constants used for MTP GetDeviceInfo response. */
|
||||
constexpr u16 MtpStandardVersion = 100;
|
||||
constexpr u32 MtpVendorExtensionId = 6;
|
||||
constexpr auto MtpVendorExtensionDesc = "microsoft.com: 1.0;";
|
||||
constexpr u16 MtpFunctionalModeDefault = 0;
|
||||
constexpr auto MtpDeviceManufacturer = "Nintendo";
|
||||
constexpr auto MtpDeviceModel = "Nintendo Switch";
|
||||
|
||||
enum StorageId : u32 {
|
||||
StorageId_SdmcFs = 0xffffffffu - 1,
|
||||
};
|
||||
|
||||
constexpr PtpOperationCode SupportedOperationCodes[] = {
|
||||
PtpOperationCode_GetDeviceInfo,
|
||||
PtpOperationCode_OpenSession,
|
||||
PtpOperationCode_CloseSession,
|
||||
PtpOperationCode_GetStorageIds,
|
||||
PtpOperationCode_GetStorageInfo,
|
||||
PtpOperationCode_GetObjectHandles,
|
||||
PtpOperationCode_GetObjectInfo,
|
||||
PtpOperationCode_GetObject,
|
||||
PtpOperationCode_SendObjectInfo,
|
||||
PtpOperationCode_SendObject,
|
||||
PtpOperationCode_DeleteObject,
|
||||
PtpOperationCode_MtpGetObjectPropsSupported,
|
||||
PtpOperationCode_MtpGetObjectPropDesc,
|
||||
PtpOperationCode_MtpGetObjectPropValue,
|
||||
PtpOperationCode_MtpSetObjectPropValue,
|
||||
PtpOperationCode_MtpGetObjPropList,
|
||||
PtpOperationCode_AndroidGetPartialObject64,
|
||||
PtpOperationCode_AndroidSendPartialObject,
|
||||
PtpOperationCode_AndroidTruncateObject,
|
||||
PtpOperationCode_AndroidBeginEditObject,
|
||||
PtpOperationCode_AndroidEndEditObject,
|
||||
};
|
||||
|
||||
constexpr const PtpEventCode SupportedEventCodes[] = { /* ... */ };
|
||||
constexpr const PtpDevicePropertyCode SupportedDeviceProperties[] = { /* ... */ };
|
||||
constexpr const PtpObjectFormatCode SupportedCaptureFormats[] = { /* ... */ };
|
||||
|
||||
constexpr const PtpObjectFormatCode SupportedPlaybackFormats[] = {
|
||||
PtpObjectFormatCode_Undefined,
|
||||
PtpObjectFormatCode_Association,
|
||||
};
|
||||
|
||||
constexpr const PtpObjectPropertyCode SupportedObjectProperties[] = {
|
||||
PtpObjectPropertyCode_StorageId,
|
||||
PtpObjectPropertyCode_ObjectFormat,
|
||||
PtpObjectPropertyCode_ObjectSize,
|
||||
PtpObjectPropertyCode_ObjectFileName,
|
||||
PtpObjectPropertyCode_ParentObject,
|
||||
PtpObjectPropertyCode_PersistentUniqueObjectIdentifier,
|
||||
};
|
||||
|
||||
constexpr bool IsSupportedObjectPropertyCode(PtpObjectPropertyCode c) {
|
||||
for (size_t i = 0; i < util::size(SupportedObjectProperties); i++) {
|
||||
if (SupportedObjectProperties[i] == c) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr const StorageId SupportedStorageIds[] = {
|
||||
StorageId_SdmcFs,
|
||||
};
|
||||
|
||||
struct PtpStorageInfo {
|
||||
PtpStorageType storage_type;
|
||||
PtpFilesystemType filesystem_type;
|
||||
PtpAccessCapability access_capability;
|
||||
u64 max_capacity;
|
||||
u64 free_space_in_bytes;
|
||||
u32 free_space_in_images;
|
||||
const char *storage_description;
|
||||
const char *volume_label;
|
||||
};
|
||||
|
||||
constexpr PtpStorageInfo DefaultStorageInfo = {
|
||||
.storage_type = PtpStorageType_FixedRam,
|
||||
.filesystem_type = PtpFilesystemType_GenericHierarchical,
|
||||
.access_capability = PtpAccessCapability_ReadWrite,
|
||||
.max_capacity = 0,
|
||||
.free_space_in_bytes = 0,
|
||||
.free_space_in_images = 0,
|
||||
.storage_description = "",
|
||||
.volume_label = "",
|
||||
};
|
||||
|
||||
struct PtpObjectInfo {
|
||||
StorageId storage_id;
|
||||
PtpObjectFormatCode object_format;
|
||||
PtpProtectionStatus protection_status;
|
||||
u32 object_compressed_size;
|
||||
u16 thumb_format;
|
||||
u32 thumb_compressed_size;
|
||||
u32 thumb_width;
|
||||
u32 thumb_height;
|
||||
u32 image_width;
|
||||
u32 image_height;
|
||||
u32 image_depth;
|
||||
u32 parent_object;
|
||||
PtpAssociationType association_type;
|
||||
u32 association_desc;
|
||||
u32 sequence_number;
|
||||
const char *filename;
|
||||
const char *capture_date;
|
||||
const char *modification_date;
|
||||
const char *keywords;
|
||||
};
|
||||
|
||||
constexpr PtpObjectInfo DefaultObjectInfo = {
|
||||
.storage_id = StorageId_SdmcFs,
|
||||
.object_format = {},
|
||||
.protection_status = PtpProtectionStatus_NoProtection,
|
||||
.object_compressed_size = 0,
|
||||
.thumb_format = 0,
|
||||
.thumb_compressed_size = 0,
|
||||
.thumb_width = 0,
|
||||
.thumb_height = 0,
|
||||
.image_width = 0,
|
||||
.image_height = 0,
|
||||
.image_depth = 0,
|
||||
.parent_object = PtpGetObjectHandles_RootParent,
|
||||
.association_type = PtpAssociationType_Undefined,
|
||||
.association_desc = 0,
|
||||
.sequence_number = 0,
|
||||
.filename = nullptr,
|
||||
.capture_date = "",
|
||||
.modification_date = "",
|
||||
.keywords = "",
|
||||
};
|
||||
|
||||
constexpr u32 UsbBulkPacketBufferSize = 1_MB;
|
||||
constexpr u64 FsBufferSize = UsbBulkPacketBufferSize;
|
||||
constexpr s64 DirectoryReadSize = 32;
|
||||
|
||||
struct PtpBuffers {
|
||||
char filename_string_buffer[PtpStringMaxLength + 1];
|
||||
char capture_date_string_buffer[PtpStringMaxLength + 1];
|
||||
char modification_date_string_buffer[PtpStringMaxLength + 1];
|
||||
char keywords_string_buffer[PtpStringMaxLength + 1];
|
||||
|
||||
FsDirectoryEntry file_system_entry_buffer[DirectoryReadSize];
|
||||
u8 file_system_data_buffer[FsBufferSize];
|
||||
|
||||
alignas(4_KB) u8 usb_bulk_write_buffer[UsbBulkPacketBufferSize];
|
||||
alignas(4_KB) u8 usb_bulk_read_buffer[UsbBulkPacketBufferSize];
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <vapours/results.hpp>
|
||||
|
||||
/* NOTE: These results are all custom, and not official. */
|
||||
R_DEFINE_NAMESPACE_RESULT_MODULE(haze, 420);
|
||||
|
||||
namespace haze {
|
||||
|
||||
R_DEFINE_ERROR_RESULT(RegistrationFailed, 1);
|
||||
R_DEFINE_ERROR_RESULT(NotConfigured, 2);
|
||||
R_DEFINE_ERROR_RESULT(TransferFailed, 3);
|
||||
R_DEFINE_ERROR_RESULT(StopRequested, 4);
|
||||
R_DEFINE_ERROR_RESULT(FocusLost, 5);
|
||||
R_DEFINE_ERROR_RESULT(EndOfTransmission, 6);
|
||||
R_DEFINE_ERROR_RESULT(UnknownPacketType, 7);
|
||||
R_DEFINE_ERROR_RESULT(SessionNotOpen, 8);
|
||||
R_DEFINE_ERROR_RESULT(OutOfMemory, 9);
|
||||
R_DEFINE_ERROR_RESULT(InvalidObjectId, 10);
|
||||
R_DEFINE_ERROR_RESULT(InvalidStorageId, 11);
|
||||
R_DEFINE_ERROR_RESULT(OperationNotSupported, 12);
|
||||
R_DEFINE_ERROR_RESULT(UnknownRequestType, 13);
|
||||
R_DEFINE_ERROR_RESULT(UnknownPropertyCode, 14);
|
||||
R_DEFINE_ERROR_RESULT(InvalidPropertyValue, 15);
|
||||
R_DEFINE_ERROR_RESULT(InvalidArgument, 16);
|
||||
R_DEFINE_ERROR_RESULT(GroupSpecified, 17);
|
||||
R_DEFINE_ERROR_RESULT(DepthSpecified, 18);
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <haze/common.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
enum UsbSessionEndpoint {
|
||||
UsbSessionEndpoint_Read = 0,
|
||||
UsbSessionEndpoint_Write = 1,
|
||||
UsbSessionEndpoint_Interrupt = 2,
|
||||
UsbSessionEndpoint_Count = 3,
|
||||
};
|
||||
|
||||
class UsbSession {
|
||||
private:
|
||||
UsbDsInterface *m_interface;
|
||||
UsbDsEndpoint *m_endpoints[UsbSessionEndpoint_Count];
|
||||
private:
|
||||
Result Initialize1x(const UsbCommsInterfaceInfo *info);
|
||||
Result Initialize5x(const UsbCommsInterfaceInfo *info);
|
||||
public:
|
||||
constexpr explicit UsbSession() : m_interface(), m_endpoints() { /* ... */ }
|
||||
|
||||
Result Initialize(const UsbCommsInterfaceInfo *info, u16 id_vendor, u16 id_product);
|
||||
void Finalize();
|
||||
|
||||
bool GetConfigured() const;
|
||||
Event *GetCompletionEvent(UsbSessionEndpoint ep) const;
|
||||
Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id);
|
||||
Result GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_transferred_size);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
constinit UsbSession g_usb_session;
|
||||
|
||||
}
|
||||
|
||||
Result AsyncUsbServer::Initialize(const UsbCommsInterfaceInfo *interface_info, u16 id_vendor, u16 id_product, EventReactor *reactor) {
|
||||
m_reactor = reactor;
|
||||
|
||||
/* Set up a new USB session. */
|
||||
R_TRY(g_usb_session.Initialize(interface_info, id_vendor, id_product));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void AsyncUsbServer::Finalize() {
|
||||
g_usb_session.Finalize();
|
||||
}
|
||||
|
||||
Result AsyncUsbServer::TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred) const {
|
||||
u32 urb_id;
|
||||
s32 waiter_idx;
|
||||
|
||||
/* If we're not configured yet, wait to become configured first. */
|
||||
if (!g_usb_session.GetConfigured()) {
|
||||
R_TRY(m_reactor->WaitFor(std::addressof(waiter_idx), waiterForEvent(usbDsGetStateChangeEvent())));
|
||||
R_TRY(eventClear(usbDsGetStateChangeEvent()));
|
||||
|
||||
R_THROW(haze::ResultNotConfigured());
|
||||
}
|
||||
|
||||
/* Select the appropriate endpoint and begin a transfer. */
|
||||
UsbSessionEndpoint ep = read ? UsbSessionEndpoint_Read : UsbSessionEndpoint_Write;
|
||||
R_TRY(g_usb_session.TransferAsync(ep, page, size, std::addressof(urb_id)));
|
||||
|
||||
/* Try to wait for the event. */
|
||||
R_TRY(m_reactor->WaitFor(std::addressof(waiter_idx), waiterForEvent(g_usb_session.GetCompletionEvent(ep))));
|
||||
|
||||
/* Return what we transferred. */
|
||||
R_RETURN(g_usb_session.GetTransferResult(ep, urb_id, out_size_transferred));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#version 460
|
||||
|
||||
layout (location = 0) noperspective in vec3 inTexCoord;
|
||||
layout (location = 1) flat in vec4 inFrontPal;
|
||||
layout (location = 2) flat in vec4 inBackPal;
|
||||
|
||||
layout (location = 0) out vec4 outColor;
|
||||
|
||||
layout (binding = 0) uniform sampler2DArray tileset;
|
||||
|
||||
void main()
|
||||
{
|
||||
float value = texture(tileset, inTexCoord).r;
|
||||
outColor = mix(inBackPal, inFrontPal, value);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
#version 460
|
||||
|
||||
layout (location = 0) in float inTileId;
|
||||
layout (location = 1) in uvec2 inColorId;
|
||||
|
||||
layout (location = 0) out vec3 outTexCoord;
|
||||
layout (location = 1) out vec4 outFrontPal;
|
||||
layout (location = 2) out vec4 outBackPal;
|
||||
|
||||
layout (std140, binding = 0) uniform Config
|
||||
{
|
||||
vec4 dimensions;
|
||||
vec4 vertices[3];
|
||||
vec4 palettes[24];
|
||||
} u;
|
||||
|
||||
void main()
|
||||
{
|
||||
float id = float(gl_InstanceID);
|
||||
float tileRow = floor(id / u.dimensions.z);
|
||||
float tileCol = id - tileRow * u.dimensions.z;
|
||||
|
||||
vec2 basePos;
|
||||
basePos.x = 2.0 * (tileCol + 0.5) / u.dimensions.z - 1.0;
|
||||
basePos.y = 2.0 * (1.0 - (tileRow + 0.5) / u.dimensions.w) - 1.0;
|
||||
|
||||
vec2 vtxData = u.vertices[gl_VertexID].xy;
|
||||
vec2 scale = vec2(1.0) / u.dimensions.zw;
|
||||
gl_Position.xy = vtxData * scale + basePos;
|
||||
gl_Position.zw = vec2(0.5, 1.0);
|
||||
|
||||
outTexCoord = vec3(u.vertices[gl_VertexID].zw, inTileId);
|
||||
outFrontPal = u.palettes[inColorId.x];
|
||||
outBackPal = u.palettes[inColorId.y];
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
constinit SetSysSerialNumber g_serial_number = {};
|
||||
constinit SetSysFirmwareVersion g_firmware_version = {};
|
||||
|
||||
}
|
||||
|
||||
Result LoadDeviceProperties() {
|
||||
/* Initialize set:sys. */
|
||||
R_TRY(setsysInitialize());
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { setsysExit(); };
|
||||
|
||||
/* Get the serial number and firmware version. */
|
||||
R_TRY(setsysGetSerialNumber(std::addressof(g_serial_number)));
|
||||
R_TRY(setsysGetFirmwareVersion(std::addressof(g_firmware_version)));
|
||||
|
||||
/* We succeeded. */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
const char *GetSerialNumber() {
|
||||
return g_serial_number.number;
|
||||
}
|
||||
|
||||
const char *GetFirmwareVersion() {
|
||||
return g_firmware_version.display_version;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
bool EventReactor::AddConsumer(EventConsumer *consumer, Waiter waiter) {
|
||||
HAZE_ASSERT(m_num_wait_objects + 1 <= svc::ArgumentHandleCountMax);
|
||||
|
||||
/* Add to the end of the list. */
|
||||
m_consumers[m_num_wait_objects] = consumer;
|
||||
m_waiters[m_num_wait_objects] = waiter;
|
||||
m_num_wait_objects++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EventReactor::RemoveConsumer(EventConsumer *consumer) {
|
||||
s32 output_index = 0;
|
||||
|
||||
/* Remove the consumer. */
|
||||
for (s32 input_index = 0; input_index < m_num_wait_objects; input_index++) {
|
||||
if (m_consumers[input_index] == consumer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (output_index != input_index) {
|
||||
m_consumers[output_index] = m_consumers[input_index];
|
||||
m_waiters[output_index] = m_waiters[input_index];
|
||||
}
|
||||
|
||||
output_index++;
|
||||
}
|
||||
|
||||
m_num_wait_objects = output_index;
|
||||
}
|
||||
|
||||
Result EventReactor::WaitForImpl(s32 *out_arg_waiter, const Waiter *arg_waiters, s32 num_arg_waiters) {
|
||||
HAZE_ASSERT(0 < num_arg_waiters && num_arg_waiters <= svc::ArgumentHandleCountMax);
|
||||
HAZE_ASSERT(m_num_wait_objects + num_arg_waiters <= svc::ArgumentHandleCountMax);
|
||||
|
||||
while (true) {
|
||||
/* Check if we should wait for an event. */
|
||||
R_TRY(m_result);
|
||||
|
||||
/* Insert waiters from argument list. */
|
||||
for (s32 i = 0; i < num_arg_waiters; i++) {
|
||||
m_waiters[i + m_num_wait_objects] = arg_waiters[i];
|
||||
}
|
||||
|
||||
s32 idx;
|
||||
HAZE_R_ABORT_UNLESS(waitObjects(std::addressof(idx), m_waiters, m_num_wait_objects + num_arg_waiters, svc::WaitInfinite));
|
||||
|
||||
/* If a waiter in the argument list was signaled, return it. */
|
||||
if (idx >= m_num_wait_objects) {
|
||||
*out_arg_waiter = idx - m_num_wait_objects;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* Otherwise, process the event as normal. */
|
||||
m_consumers[idx]->ProcessEvent();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,487 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/iosupport.h>
|
||||
|
||||
#include <switch.h>
|
||||
#include <deko3d.h>
|
||||
|
||||
// Define the desired number of framebuffers
|
||||
#define FB_NUM 2
|
||||
|
||||
// Define the size of the memory block that will hold code
|
||||
#define CODEMEMSIZE (64*1024)
|
||||
|
||||
// Define the size of the memory block that will hold command lists
|
||||
#define CMDMEMSIZE (64*1024)
|
||||
|
||||
#define NUM_IMAGE_SLOTS 1
|
||||
#define NUM_SAMPLER_SLOTS 1
|
||||
|
||||
typedef struct {
|
||||
float pos[2];
|
||||
float tex[2];
|
||||
} VertexDef;
|
||||
|
||||
typedef struct {
|
||||
float red;
|
||||
float green;
|
||||
float blue;
|
||||
float alpha;
|
||||
} PaletteColor;
|
||||
|
||||
typedef struct {
|
||||
float dimensions[4];
|
||||
VertexDef vertices[3];
|
||||
PaletteColor palettes[24];
|
||||
} ConsoleConfig;
|
||||
|
||||
static const VertexDef g_vertexData[3] = {
|
||||
{ { 0.0f, +1.0f }, { 0.5f, 0.0f, } },
|
||||
{ { -1.0f, -1.0f }, { 0.0f, 1.0f, } },
|
||||
{ { +1.0f, -1.0f }, { 1.0f, 1.0f, } },
|
||||
};
|
||||
|
||||
static const PaletteColor g_paletteData[24] = {
|
||||
{ 0.0f, 0.0f, 0.0f, 0.0f }, // black
|
||||
{ 0.5f, 0.0f, 0.0f, 1.0f }, // red
|
||||
{ 0.0f, 0.5f, 0.0f, 1.0f }, // green
|
||||
{ 0.5f, 0.5f, 0.0f, 1.0f }, // yellow
|
||||
{ 0.0f, 0.0f, 0.5f, 1.0f }, // blue
|
||||
{ 0.5f, 0.0f, 0.5f, 1.0f }, // magenta
|
||||
{ 0.0f, 0.5f, 0.5f, 1.0f }, // cyan
|
||||
{ 0.75f, 0.75f, 0.75f, 1.0f }, // white
|
||||
|
||||
{ 0.5f, 0.5f, 0.5f, 1.0f }, // bright black
|
||||
{ 1.0f, 0.0f, 0.0f, 1.0f }, // bright red
|
||||
{ 0.0f, 1.0f, 0.0f, 1.0f }, // bright green
|
||||
{ 1.0f, 1.0f, 0.0f, 1.0f }, // bright yellow
|
||||
{ 0.0f, 0.0f, 1.0f, 1.0f }, // bright blue
|
||||
{ 1.0f, 0.0f, 1.0f, 1.0f }, // bright magenta
|
||||
{ 0.0f, 1.0f, 1.0f, 1.0f }, // bright cyan
|
||||
{ 1.0f, 1.0f, 1.0f, 1.0f }, // bright white
|
||||
|
||||
{ 0.0f, 0.0f, 0.0f, 0.0f }, // faint black
|
||||
{ 0.25f, 0.0f, 0.0f, 1.0f }, // faint red
|
||||
{ 0.0f, 0.25f, 0.0f, 1.0f }, // faint green
|
||||
{ 0.25f, 0.25f, 0.0f, 1.0f }, // faint yellow
|
||||
{ 0.0f, 0.0f, 0.25f, 1.0f }, // faint blue
|
||||
{ 0.25f, 0.0f, 0.25f, 1.0f }, // faint magenta
|
||||
{ 0.0f, 0.25f, 0.25f, 1.0f }, // faint cyan
|
||||
{ 0.375f, 0.375f, 0.375f, 1.0f }, // faint white
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint16_t tileId;
|
||||
uint8_t frontPal;
|
||||
uint8_t backPal;
|
||||
} ConsoleChar;
|
||||
|
||||
static const DkVtxAttribState g_attribState[] = {
|
||||
{ .bufferId=0, .isFixed=0, .offset=offsetof(ConsoleChar,tileId), .size=DkVtxAttribSize_1x16, .type=DkVtxAttribType_Uscaled, .isBgra=0 },
|
||||
{ .bufferId=0, .isFixed=0, .offset=offsetof(ConsoleChar,frontPal), .size=DkVtxAttribSize_2x8, .type=DkVtxAttribType_Uint, .isBgra=0 },
|
||||
};
|
||||
|
||||
static const DkVtxBufferState g_vtxbufState[] = {
|
||||
{ .stride=sizeof(ConsoleChar), .divisor=1 },
|
||||
};
|
||||
|
||||
struct GpuRenderer {
|
||||
ConsoleRenderer base;
|
||||
|
||||
bool initialized;
|
||||
|
||||
DkDevice device;
|
||||
DkQueue queue;
|
||||
|
||||
DkMemBlock imageMemBlock;
|
||||
DkMemBlock codeMemBlock;
|
||||
DkMemBlock dataMemBlock;
|
||||
|
||||
DkSwapchain swapchain;
|
||||
DkImage framebuffers[FB_NUM];
|
||||
DkImage tileset;
|
||||
ConsoleChar* charBuf;
|
||||
|
||||
uint32_t codeMemOffset;
|
||||
DkShader vertexShader;
|
||||
DkShader fragmentShader;
|
||||
|
||||
DkCmdBuf cmdbuf;
|
||||
DkCmdList cmdsBindFramebuffer[FB_NUM];
|
||||
DkCmdList cmdsRender;
|
||||
|
||||
DkFence lastRenderFence;
|
||||
};
|
||||
|
||||
static struct GpuRenderer* GpuRenderer(PrintConsole* con)
|
||||
{
|
||||
return (struct GpuRenderer*)con->renderer;
|
||||
}
|
||||
|
||||
static void GpuRenderer_destroy(struct GpuRenderer* r)
|
||||
{
|
||||
// Make sure the queue is idle before destroying anything
|
||||
dkQueueWaitIdle(r->queue);
|
||||
|
||||
// Destroy all the resources we've created
|
||||
dkQueueDestroy(r->queue);
|
||||
dkCmdBufDestroy(r->cmdbuf);
|
||||
dkSwapchainDestroy(r->swapchain);
|
||||
dkMemBlockDestroy(r->dataMemBlock);
|
||||
dkMemBlockDestroy(r->codeMemBlock);
|
||||
dkMemBlockDestroy(r->imageMemBlock);
|
||||
dkDeviceDestroy(r->device);
|
||||
|
||||
// Clear out all state
|
||||
memset(&r->initialized, 0, sizeof(*r) - offsetof(struct GpuRenderer, initialized));
|
||||
}
|
||||
|
||||
// Simple function for loading a shader from the filesystem
|
||||
static void GpuRenderer_loadShader(struct GpuRenderer* r, DkShader* pShader, const char* path)
|
||||
{
|
||||
// Open the file, and retrieve its size
|
||||
FILE* f = fopen(path, "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
uint32_t size = ftell(f);
|
||||
rewind(f);
|
||||
|
||||
// Look for a spot in the code memory block for loading this shader. Note that
|
||||
// we are just using a simple incremental offset; this isn't a general purpose
|
||||
// allocation algorithm.
|
||||
uint32_t codeOffset = r->codeMemOffset;
|
||||
r->codeMemOffset += (size + DK_SHADER_CODE_ALIGNMENT - 1) &~ (DK_SHADER_CODE_ALIGNMENT - 1);
|
||||
|
||||
// Read the file into memory, and close the file
|
||||
fread((uint8_t*)dkMemBlockGetCpuAddr(r->codeMemBlock) + codeOffset, size, 1, f);
|
||||
fclose(f);
|
||||
|
||||
// Initialize the user provided shader object with the code we've just loaded
|
||||
DkShaderMaker shaderMaker;
|
||||
dkShaderMakerDefaults(&shaderMaker, r->codeMemBlock, codeOffset);
|
||||
dkShaderInitialize(pShader, &shaderMaker);
|
||||
}
|
||||
|
||||
static bool GpuRenderer_init(PrintConsole* con)
|
||||
{
|
||||
struct GpuRenderer* r = GpuRenderer(con);
|
||||
|
||||
if (r->initialized) {
|
||||
// We're already initialized
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create the deko3d device, which is the root object
|
||||
DkDeviceMaker deviceMaker;
|
||||
dkDeviceMakerDefaults(&deviceMaker);
|
||||
r->device = dkDeviceCreate(&deviceMaker);
|
||||
|
||||
// Create the queue
|
||||
DkQueueMaker queueMaker;
|
||||
dkQueueMakerDefaults(&queueMaker, r->device);
|
||||
queueMaker.flags = DkQueueFlags_Graphics;
|
||||
r->queue = dkQueueCreate(&queueMaker);
|
||||
|
||||
// Calculate required width/height for the framebuffers
|
||||
u32 width = con->font.tileWidth * con->consoleWidth;
|
||||
u32 height = con->font.tileHeight * con->consoleHeight;
|
||||
u32 totalConSize = con->consoleWidth * con->consoleHeight;
|
||||
|
||||
// Calculate layout for the framebuffers
|
||||
DkImageLayoutMaker imageLayoutMaker;
|
||||
dkImageLayoutMakerDefaults(&imageLayoutMaker, r->device);
|
||||
imageLayoutMaker.flags = DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression;
|
||||
imageLayoutMaker.format = DkImageFormat_RGBA8_Unorm;
|
||||
imageLayoutMaker.dimensions[0] = width;
|
||||
imageLayoutMaker.dimensions[1] = height;
|
||||
|
||||
// Calculate layout for the framebuffers
|
||||
DkImageLayout framebufferLayout;
|
||||
dkImageLayoutInitialize(&framebufferLayout, &imageLayoutMaker);
|
||||
|
||||
// Calculate layout for the tileset
|
||||
dkImageLayoutMakerDefaults(&imageLayoutMaker, r->device);
|
||||
imageLayoutMaker.type = DkImageType_2DArray;
|
||||
imageLayoutMaker.format = DkImageFormat_R32_Float;
|
||||
imageLayoutMaker.dimensions[0] = con->font.tileWidth;
|
||||
imageLayoutMaker.dimensions[1] = con->font.tileHeight;
|
||||
imageLayoutMaker.dimensions[2] = con->font.numChars;
|
||||
|
||||
// Calculate layout for the tileset
|
||||
DkImageLayout tilesetLayout;
|
||||
dkImageLayoutInitialize(&tilesetLayout, &imageLayoutMaker);
|
||||
|
||||
// Retrieve necessary size and alignment for the framebuffers
|
||||
uint32_t framebufferSize = dkImageLayoutGetSize(&framebufferLayout);
|
||||
uint32_t framebufferAlign = dkImageLayoutGetAlignment(&framebufferLayout);
|
||||
framebufferSize = (framebufferSize + framebufferAlign - 1) &~ (framebufferAlign - 1);
|
||||
|
||||
// Retrieve necessary size and alignment for the tileset
|
||||
uint32_t tilesetSize = dkImageLayoutGetSize(&tilesetLayout);
|
||||
uint32_t tilesetAlign = dkImageLayoutGetAlignment(&tilesetLayout);
|
||||
tilesetSize = (tilesetSize + tilesetAlign - 1) &~ (tilesetAlign - 1);
|
||||
|
||||
// Create a memory block that will host the framebuffers and the tileset
|
||||
DkMemBlockMaker memBlockMaker;
|
||||
dkMemBlockMakerDefaults(&memBlockMaker, r->device, FB_NUM*framebufferSize + tilesetSize);
|
||||
memBlockMaker.flags = DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image;
|
||||
r->imageMemBlock = dkMemBlockCreate(&memBlockMaker);
|
||||
|
||||
// Initialize the framebuffers with the layout and backing memory we've just created
|
||||
DkImage const* swapchainImages[FB_NUM];
|
||||
for (unsigned i = 0; i < FB_NUM; i ++) {
|
||||
swapchainImages[i] = &r->framebuffers[i];
|
||||
dkImageInitialize(&r->framebuffers[i], &framebufferLayout, r->imageMemBlock, i*framebufferSize);
|
||||
}
|
||||
|
||||
// Create a swapchain out of the framebuffers we've just initialized
|
||||
DkSwapchainMaker swapchainMaker;
|
||||
dkSwapchainMakerDefaults(&swapchainMaker, r->device, nwindowGetDefault(), swapchainImages, FB_NUM);
|
||||
r->swapchain = dkSwapchainCreate(&swapchainMaker);
|
||||
|
||||
// Initialize the tileset
|
||||
dkImageInitialize(&r->tileset, &tilesetLayout, r->imageMemBlock, FB_NUM*framebufferSize);
|
||||
|
||||
// Create a memory block onto which we will load shader code
|
||||
dkMemBlockMakerDefaults(&memBlockMaker, r->device, CODEMEMSIZE);
|
||||
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code;
|
||||
r->codeMemBlock = dkMemBlockCreate(&memBlockMaker);
|
||||
r->codeMemOffset = 0;
|
||||
|
||||
// Load our shaders (both vertex and fragment)
|
||||
romfsInit();
|
||||
GpuRenderer_loadShader(r, &r->vertexShader, "romfs:/shaders/console_vsh.dksh");
|
||||
GpuRenderer_loadShader(r, &r->fragmentShader, "romfs:/shaders/console_fsh.dksh");
|
||||
|
||||
// Generate the descriptors
|
||||
struct {
|
||||
DkImageDescriptor images[NUM_IMAGE_SLOTS];
|
||||
DkSamplerDescriptor samplers[NUM_SAMPLER_SLOTS];
|
||||
} descriptors;
|
||||
|
||||
// Generate a image descriptor for the tileset
|
||||
DkImageView tilesetView;
|
||||
dkImageViewDefaults(&tilesetView, &r->tileset);
|
||||
dkImageDescriptorInitialize(&descriptors.images[0], &tilesetView, false, false);
|
||||
|
||||
// Generate a sampler descriptor for the tileset
|
||||
DkSampler sampler;
|
||||
dkSamplerDefaults(&sampler);
|
||||
sampler.wrapMode[0] = DkWrapMode_ClampToEdge;
|
||||
sampler.wrapMode[1] = DkWrapMode_ClampToEdge;
|
||||
sampler.minFilter = DkFilter_Nearest;
|
||||
sampler.magFilter = DkFilter_Nearest;
|
||||
dkSamplerDescriptorInitialize(&descriptors.samplers[0], &sampler);
|
||||
|
||||
uint32_t descriptorsOffset = CMDMEMSIZE;
|
||||
uint32_t configOffset = (descriptorsOffset + sizeof(descriptors) + DK_UNIFORM_BUF_ALIGNMENT - 1) &~ (DK_UNIFORM_BUF_ALIGNMENT - 1);
|
||||
uint32_t configSize = (sizeof(ConsoleConfig) + DK_UNIFORM_BUF_ALIGNMENT - 1) &~ (DK_UNIFORM_BUF_ALIGNMENT - 1);
|
||||
|
||||
uint32_t charBufOffset = configOffset + configSize;
|
||||
uint32_t charBufSize = totalConSize * sizeof(ConsoleChar);
|
||||
|
||||
// Create a memory block which will be used for recording command lists using a command buffer
|
||||
dkMemBlockMakerDefaults(&memBlockMaker, r->device,
|
||||
(charBufOffset + charBufSize + DK_MEMBLOCK_ALIGNMENT - 1) &~ (DK_MEMBLOCK_ALIGNMENT - 1)
|
||||
);
|
||||
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached;
|
||||
r->dataMemBlock = dkMemBlockCreate(&memBlockMaker);
|
||||
|
||||
// Create a command buffer object
|
||||
DkCmdBufMaker cmdbufMaker;
|
||||
dkCmdBufMakerDefaults(&cmdbufMaker, r->device);
|
||||
r->cmdbuf = dkCmdBufCreate(&cmdbufMaker);
|
||||
|
||||
// Feed our memory to the command buffer so that we can start recording commands
|
||||
dkCmdBufAddMemory(r->cmdbuf, r->dataMemBlock, 0, CMDMEMSIZE);
|
||||
|
||||
// Create a temporary buffer that will hold the tileset
|
||||
dkMemBlockMakerDefaults(&memBlockMaker, r->device,
|
||||
(sizeof(float)*con->font.tileWidth*con->font.tileHeight*con->font.numChars + DK_MEMBLOCK_ALIGNMENT - 1) &~ (DK_MEMBLOCK_ALIGNMENT - 1)
|
||||
);
|
||||
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached;
|
||||
DkMemBlock scratchMemBlock = dkMemBlockCreate(&memBlockMaker);
|
||||
float* scratchMem = (float*)dkMemBlockGetCpuAddr(scratchMemBlock);
|
||||
|
||||
// Unpack 1bpp tileset into a texture image the GPU can read
|
||||
unsigned packedTileWidth = (con->font.tileWidth+7)/8;
|
||||
for (unsigned tile = 0; tile < con->font.numChars; tile ++) {
|
||||
const uint8_t* data = (const uint8_t*)con->font.gfx + con->font.tileHeight*packedTileWidth*tile;
|
||||
for (unsigned y = 0; y < con->font.tileHeight; y ++) {
|
||||
const uint8_t* row = &data[packedTileWidth*(y+1)];
|
||||
uint8_t c = 0;
|
||||
for (unsigned x = 0; x < con->font.tileWidth; x ++) {
|
||||
if (!(x & 7))
|
||||
c = *--row;
|
||||
*scratchMem++ = (c & 0x80) ? 1.0f : 0.0f;
|
||||
c <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up configuration
|
||||
DkGpuAddr configAddr = dkMemBlockGetGpuAddr(r->dataMemBlock) + configOffset;
|
||||
ConsoleConfig consoleConfig = {};
|
||||
consoleConfig.dimensions[0] = width;
|
||||
consoleConfig.dimensions[1] = height;
|
||||
consoleConfig.dimensions[2] = con->consoleWidth;
|
||||
consoleConfig.dimensions[3] = con->consoleHeight;
|
||||
memcpy(consoleConfig.vertices, g_vertexData, sizeof(g_vertexData));
|
||||
memcpy(consoleConfig.palettes, g_paletteData, sizeof(g_paletteData));
|
||||
|
||||
// Generate a temporary command list for uploading stuff and run it
|
||||
DkGpuAddr descriptorSet = dkMemBlockGetGpuAddr(r->dataMemBlock) + descriptorsOffset;
|
||||
DkCopyBuf copySrc = { dkMemBlockGetGpuAddr(scratchMemBlock), 0, 0 };
|
||||
DkImageRect copyDst = { 0, 0, 0, con->font.tileWidth, con->font.tileHeight, con->font.numChars };
|
||||
dkCmdBufPushData(r->cmdbuf, descriptorSet, &descriptors, sizeof(descriptors));
|
||||
dkCmdBufPushConstants(r->cmdbuf, configAddr, configSize, 0, sizeof(consoleConfig), &consoleConfig);
|
||||
dkCmdBufBindImageDescriptorSet(r->cmdbuf, descriptorSet, NUM_IMAGE_SLOTS);
|
||||
dkCmdBufBindSamplerDescriptorSet(r->cmdbuf, descriptorSet + NUM_IMAGE_SLOTS*sizeof(DkImageDescriptor), NUM_SAMPLER_SLOTS);
|
||||
dkCmdBufCopyBufferToImage(r->cmdbuf, ©Src, &tilesetView, ©Dst, 0);
|
||||
dkQueueSubmitCommands(r->queue, dkCmdBufFinishList(r->cmdbuf));
|
||||
dkQueueFlush(r->queue);
|
||||
dkQueueWaitIdle(r->queue);
|
||||
dkCmdBufClear(r->cmdbuf);
|
||||
|
||||
// Destroy the scratch memory block since we don't need it anymore
|
||||
dkMemBlockDestroy(scratchMemBlock);
|
||||
|
||||
// Retrieve the address of the character buffer
|
||||
DkGpuAddr charBufAddr = dkMemBlockGetGpuAddr(r->dataMemBlock) + charBufOffset;
|
||||
r->charBuf = (ConsoleChar*)((uint8_t*)dkMemBlockGetCpuAddr(r->dataMemBlock) + charBufOffset);
|
||||
memset(r->charBuf, 0, charBufSize);
|
||||
|
||||
// Generate a command list for each framebuffer, which will bind each of them as a render target
|
||||
for (unsigned i = 0; i < FB_NUM; i ++) {
|
||||
DkImageView imageView;
|
||||
dkImageViewDefaults(&imageView, &r->framebuffers[i]);
|
||||
dkCmdBufBindRenderTarget(r->cmdbuf, &imageView, NULL);
|
||||
r->cmdsBindFramebuffer[i] = dkCmdBufFinishList(r->cmdbuf);
|
||||
}
|
||||
|
||||
// Declare structs that will be used for binding state
|
||||
DkViewport viewport = { 0.0f, 0.0f, (float)width, (float)height, 0.0f, 1.0f };
|
||||
DkScissor scissor = { 0, 0, width, height };
|
||||
DkShader const* shaders[] = { &r->vertexShader, &r->fragmentShader };
|
||||
DkRasterizerState rasterizerState;
|
||||
DkColorState colorState;
|
||||
DkColorWriteState colorWriteState;
|
||||
|
||||
// Initialize state structs with the deko3d defaults
|
||||
dkRasterizerStateDefaults(&rasterizerState);
|
||||
dkColorStateDefaults(&colorState);
|
||||
dkColorWriteStateDefaults(&colorWriteState);
|
||||
|
||||
rasterizerState.fillRectangleEnable = true;
|
||||
colorState.alphaCompareOp = DkCompareOp_Greater;
|
||||
|
||||
// Generate the main rendering command list
|
||||
dkCmdBufSetViewports(r->cmdbuf, 0, &viewport, 1);
|
||||
dkCmdBufSetScissors(r->cmdbuf, 0, &scissor, 1);
|
||||
//dkCmdBufClearColorFloat(r->cmdbuf, 0, DkColorMask_RGBA, 0.125f, 0.294f, 0.478f, 0.0f);
|
||||
dkCmdBufClearColorFloat(r->cmdbuf, 0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||
dkCmdBufBindShaders(r->cmdbuf, DkStageFlag_GraphicsMask, shaders, sizeof(shaders)/sizeof(shaders[0]));
|
||||
dkCmdBufBindRasterizerState(r->cmdbuf, &rasterizerState);
|
||||
dkCmdBufBindColorState(r->cmdbuf, &colorState);
|
||||
dkCmdBufBindColorWriteState(r->cmdbuf, &colorWriteState);
|
||||
dkCmdBufBindUniformBuffer(r->cmdbuf, DkStage_Vertex, 0, configAddr, configSize);
|
||||
dkCmdBufBindTexture(r->cmdbuf, DkStage_Fragment, 0, dkMakeTextureHandle(0, 0));
|
||||
dkCmdBufBindVtxAttribState(r->cmdbuf, g_attribState, sizeof(g_attribState)/sizeof(g_attribState[0]));
|
||||
dkCmdBufBindVtxBufferState(r->cmdbuf, g_vtxbufState, sizeof(g_vtxbufState)/sizeof(g_vtxbufState[0]));
|
||||
dkCmdBufBindVtxBuffer(r->cmdbuf, 0, charBufAddr, charBufSize);
|
||||
dkCmdBufSetAlphaRef(r->cmdbuf, 0.0f);
|
||||
dkCmdBufDraw(r->cmdbuf, DkPrimitive_Triangles, 3, totalConSize, 0, 0);
|
||||
r->cmdsRender = dkCmdBufFinishList(r->cmdbuf);
|
||||
|
||||
r->initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void GpuRenderer_deinit(PrintConsole* con)
|
||||
{
|
||||
struct GpuRenderer* r = GpuRenderer(con);
|
||||
|
||||
if (r->initialized) {
|
||||
GpuRenderer_destroy(r);
|
||||
}
|
||||
}
|
||||
|
||||
static void GpuRenderer_drawChar(PrintConsole* con, int x, int y, int c)
|
||||
{
|
||||
struct GpuRenderer* r = GpuRenderer(con);
|
||||
|
||||
int writingColor = con->fg;
|
||||
int screenColor = con->bg;
|
||||
|
||||
if (con->flags & CONSOLE_COLOR_BOLD) {
|
||||
writingColor += 8;
|
||||
} else if (con->flags & CONSOLE_COLOR_FAINT) {
|
||||
writingColor += 16;
|
||||
}
|
||||
|
||||
if (con->flags & CONSOLE_COLOR_REVERSE) {
|
||||
int tmp = writingColor;
|
||||
writingColor = screenColor;
|
||||
screenColor = tmp;
|
||||
}
|
||||
|
||||
// Wait for the fence
|
||||
dkFenceWait(&r->lastRenderFence, UINT64_MAX);
|
||||
|
||||
ConsoleChar* pos = &r->charBuf[y*con->consoleWidth+x];
|
||||
pos->tileId = c;
|
||||
pos->frontPal = writingColor;
|
||||
pos->backPal = screenColor;
|
||||
}
|
||||
|
||||
static void GpuRenderer_scrollWindow(PrintConsole* con)
|
||||
{
|
||||
struct GpuRenderer* r = GpuRenderer(con);
|
||||
|
||||
// Wait for the fence
|
||||
dkFenceWait(&r->lastRenderFence, UINT64_MAX);
|
||||
|
||||
// Perform the scrolling
|
||||
for (int y = 0; y < con->windowHeight-1; y ++) {
|
||||
memcpy(
|
||||
&r->charBuf[(con->windowY+y+0)*con->consoleWidth + con->windowX],
|
||||
&r->charBuf[(con->windowY+y+1)*con->consoleWidth + con->windowX],
|
||||
sizeof(ConsoleChar)*con->windowWidth);
|
||||
}
|
||||
}
|
||||
|
||||
static void GpuRenderer_flushAndSwap(PrintConsole* con)
|
||||
{
|
||||
struct GpuRenderer* r = GpuRenderer(con);
|
||||
|
||||
// Acquire a framebuffer from the swapchain (and wait for it to be available)
|
||||
int slot = dkQueueAcquireImage(r->queue, r->swapchain);
|
||||
|
||||
// Run the command list that binds said framebuffer as a render target
|
||||
dkQueueSubmitCommands(r->queue, r->cmdsBindFramebuffer[slot]);
|
||||
|
||||
// Run the main rendering command list
|
||||
dkQueueSubmitCommands(r->queue, r->cmdsRender);
|
||||
|
||||
// Signal the fence
|
||||
dkQueueSignalFence(r->queue, &r->lastRenderFence, false);
|
||||
|
||||
// Now that we are done rendering, present it to the screen
|
||||
dkQueuePresentImage(r->queue, r->swapchain, slot);
|
||||
}
|
||||
|
||||
static struct GpuRenderer s_gpuRenderer =
|
||||
{
|
||||
{
|
||||
GpuRenderer_init,
|
||||
GpuRenderer_deinit,
|
||||
GpuRenderer_drawChar,
|
||||
GpuRenderer_scrollWindow,
|
||||
GpuRenderer_flushAndSwap,
|
||||
}
|
||||
};
|
||||
|
||||
ConsoleRenderer* getDefaultConsoleRenderer(void)
|
||||
{
|
||||
return &s_gpuRenderer.base;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
#include <haze/console_main_loop.hpp>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
/* Load device firmware version and serial number. */
|
||||
HAZE_R_ABORT_UNLESS(haze::LoadDeviceProperties());
|
||||
|
||||
/* Run the application. */
|
||||
haze::ConsoleMainLoop::RunApplication();
|
||||
|
||||
/* Return to the loader. */
|
||||
return 0;
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
void PtpObjectDatabase::Initialize(PtpObjectHeap *object_heap) {
|
||||
m_object_heap = object_heap;
|
||||
m_object_heap->Initialize();
|
||||
|
||||
std::construct_at(std::addressof(m_name_tree));
|
||||
std::construct_at(std::addressof(m_object_id_tree));
|
||||
|
||||
m_next_object_id = 1;
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::Finalize() {
|
||||
std::destroy_at(std::addressof(m_object_id_tree));
|
||||
std::destroy_at(std::addressof(m_name_tree));
|
||||
|
||||
m_next_object_id = 0;
|
||||
|
||||
m_object_heap->Finalize();
|
||||
m_object_heap = nullptr;
|
||||
}
|
||||
|
||||
Result PtpObjectDatabase::CreateOrFindObject(const char *parent_name, const char *name, u32 parent_id, PtpObject **out_object) {
|
||||
constexpr auto separator = "/";
|
||||
|
||||
/* Calculate length of the new name with null terminator. */
|
||||
const size_t parent_name_len = util::Strlen(parent_name);
|
||||
const size_t separator_len = util::Strlen(separator);
|
||||
const size_t name_len = util::Strlen(name);
|
||||
const size_t terminator_len = 1;
|
||||
const size_t alloc_len = sizeof(PtpObject) + parent_name_len + separator_len + name_len + terminator_len;
|
||||
|
||||
/* Allocate memory for the object. */
|
||||
PtpObject * const object = m_object_heap->Allocate<PtpObject>(alloc_len);
|
||||
R_UNLESS(object != nullptr, haze::ResultOutOfMemory());
|
||||
|
||||
/* Build the object name. */
|
||||
std::strncpy(object->m_name, parent_name, parent_name_len + terminator_len);
|
||||
std::strncpy(object->m_name + parent_name_len, separator, separator_len + terminator_len);
|
||||
std::strncpy(object->m_name + parent_name_len + separator_len, name, name_len + terminator_len);
|
||||
|
||||
{
|
||||
/* Ensure we maintain a clean state on failure. */
|
||||
auto guard = SCOPE_GUARD { m_object_heap->Deallocate(object, alloc_len); };
|
||||
|
||||
/* Check if an object with this name already exists. If it does, we can just return it here. */
|
||||
if (auto * const existing = this->GetObjectByName(object->GetName()); existing != nullptr) {
|
||||
*out_object = existing;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* Persist the reference to the object. */
|
||||
guard.Cancel();
|
||||
}
|
||||
|
||||
/* Set object properties. */
|
||||
object->m_parent_id = parent_id;
|
||||
object->m_object_id = 0;
|
||||
|
||||
/* Set output. */
|
||||
*out_object = object;
|
||||
|
||||
/* We succeeded. */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::RegisterObject(PtpObject *object, u32 desired_id) {
|
||||
/* If the object is already registered, skip registration. */
|
||||
if (object->GetIsRegistered()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set desired object ID. */
|
||||
if (desired_id == 0) {
|
||||
desired_id = m_next_object_id++;
|
||||
}
|
||||
|
||||
/* Insert object into trees. */
|
||||
object->Register(desired_id);
|
||||
m_object_id_tree.insert(*object);
|
||||
m_name_tree.insert(*object);
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::UnregisterObject(PtpObject *object) {
|
||||
/* If the object is not registered, skip trying to unregister. */
|
||||
if (!object->GetIsRegistered()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove object from trees. */
|
||||
m_object_id_tree.erase(m_object_id_tree.iterator_to(*object));
|
||||
m_name_tree.erase(m_name_tree.iterator_to(*object));
|
||||
object->Unregister();
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::DeleteObject(PtpObject *object) {
|
||||
/* Unregister the object as required. */
|
||||
this->UnregisterObject(object);
|
||||
|
||||
/* Free the object. */
|
||||
m_object_heap->Deallocate(object, sizeof(PtpObject) + std::strlen(object->GetName()) + 1);
|
||||
}
|
||||
|
||||
Result PtpObjectDatabase::CreateAndRegisterObjectId(const char *parent_name, const char *name, u32 parent_id, u32 *out_object_id) {
|
||||
/* Try to create the object. */
|
||||
PtpObject *object;
|
||||
R_TRY(this->CreateOrFindObject(parent_name, name, parent_id, std::addressof(object)));
|
||||
|
||||
/* We succeeded, so register it. */
|
||||
this->RegisterObject(object);
|
||||
|
||||
/* Set the output ID. */
|
||||
*out_object_id = object->GetObjectId();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
PtpObject *PtpObjectDatabase::GetObjectById(u32 object_id) {
|
||||
/* Find in ID mapping. */
|
||||
if (auto it = m_object_id_tree.find_key(object_id); it != m_object_id_tree.end()) {
|
||||
return std::addressof(*it);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
PtpObject *PtpObjectDatabase::GetObjectByName(const char *name) {
|
||||
/* Find in name mapping. */
|
||||
if (auto it = m_name_tree.find_key(name); it != m_name_tree.end()) {
|
||||
return std::addressof(*it);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Allow 30MiB for use by libnx. */
|
||||
static constexpr size_t LibnxReservedMemorySize = 30_MB;
|
||||
|
||||
}
|
||||
|
||||
void PtpObjectHeap::Initialize() {
|
||||
/* If we're already initialized, skip re-initialization. */
|
||||
if (m_heap_block_size != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Estimate how much memory we can reserve. */
|
||||
size_t mem_used = 0;
|
||||
HAZE_R_ABORT_UNLESS(svcGetInfo(std::addressof(mem_used), InfoType_UsedMemorySize, svc::CurrentProcess, 0));
|
||||
HAZE_ASSERT(mem_used > LibnxReservedMemorySize);
|
||||
mem_used -= LibnxReservedMemorySize;
|
||||
|
||||
/* Calculate size of blocks. */
|
||||
m_heap_block_size = mem_used / NumHeapBlocks;
|
||||
HAZE_ASSERT(m_heap_block_size > 0);
|
||||
|
||||
/* Allocate the memory. */
|
||||
for (size_t i = 0; i < NumHeapBlocks; i++) {
|
||||
m_heap_blocks[i] = std::malloc(m_heap_block_size);
|
||||
HAZE_ASSERT(m_heap_blocks[i] != nullptr);
|
||||
}
|
||||
|
||||
/* Set the address to allocate from. */
|
||||
m_next_address = m_heap_blocks[0];
|
||||
}
|
||||
|
||||
void PtpObjectHeap::Finalize() {
|
||||
if (m_heap_block_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Tear down the heap, allowing a subsequent call to Initialize() if desired. */
|
||||
for (size_t i = 0; i < NumHeapBlocks; i++) {
|
||||
std::free(m_heap_blocks[i]);
|
||||
m_heap_blocks[i] = nullptr;
|
||||
}
|
||||
|
||||
m_next_address = nullptr;
|
||||
m_heap_block_size = 0;
|
||||
m_current_heap_block = 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
#include <haze/ptp_data_builder.hpp>
|
||||
#include <haze/ptp_data_parser.hpp>
|
||||
#include <haze/ptp_responder_types.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
PtpBuffers *GetBuffers() {
|
||||
static constinit PtpBuffers buffers = {};
|
||||
return std::addressof(buffers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result PtpResponder::Initialize(EventReactor *reactor, PtpObjectHeap *object_heap) {
|
||||
m_object_heap = object_heap;
|
||||
m_buffers = GetBuffers();
|
||||
|
||||
/* Configure fs proxy. */
|
||||
m_fs.Initialize(reactor, fsdevGetDeviceFileSystem("sdmc"));
|
||||
|
||||
R_RETURN(m_usb_server.Initialize(std::addressof(MtpInterfaceInfo), SwitchMtpIdVendor, SwitchMtpIdProduct, reactor));
|
||||
}
|
||||
|
||||
void PtpResponder::Finalize() {
|
||||
m_usb_server.Finalize();
|
||||
m_fs.Finalize();
|
||||
}
|
||||
|
||||
Result PtpResponder::LoopProcess() {
|
||||
while (true) {
|
||||
/* Try to handle a request. */
|
||||
R_TRY_CATCH(this->HandleRequest()) {
|
||||
R_CATCH(haze::ResultStopRequested, haze::ResultFocusLost) {
|
||||
/* If we encountered a stop condition, we're done.*/
|
||||
R_THROW(R_CURRENT_RESULT);
|
||||
}
|
||||
R_CATCH_ALL() {
|
||||
/* On other failures, try to handle another request. */
|
||||
continue;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* Otherwise, handle the next request. */
|
||||
/* ... */
|
||||
}
|
||||
}
|
||||
|
||||
Result PtpResponder::HandleRequest() {
|
||||
ON_RESULT_FAILURE {
|
||||
/* For general failure modes, the failure is unrecoverable. Close the session. */
|
||||
this->ForceCloseSession();
|
||||
};
|
||||
|
||||
R_TRY_CATCH(this->HandleRequestImpl()) {
|
||||
R_CATCH(haze::ResultUnknownRequestType) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
|
||||
}
|
||||
R_CATCH(haze::ResultSessionNotOpen) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_SessionNotOpen));
|
||||
}
|
||||
R_CATCH(haze::ResultOperationNotSupported) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_OperationNotSupported));
|
||||
}
|
||||
R_CATCH(haze::ResultInvalidStorageId) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_InvalidStorageId));
|
||||
}
|
||||
R_CATCH(haze::ResultInvalidObjectId) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_InvalidObjectHandle));
|
||||
}
|
||||
R_CATCH(haze::ResultUnknownPropertyCode) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_MtpObjectPropNotSupported));
|
||||
}
|
||||
R_CATCH(haze::ResultInvalidPropertyValue) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_MtpInvalidObjectPropValue));
|
||||
}
|
||||
R_CATCH(haze::ResultGroupSpecified) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_MtpSpecificationByGroupUnsupported));
|
||||
}
|
||||
R_CATCH(haze::ResultDepthSpecified) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_MtpSpecificationByDepthUnsupported));
|
||||
}
|
||||
R_CATCH(haze::ResultInvalidArgument) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
|
||||
}
|
||||
R_CATCH_MODULE(fs) {
|
||||
/* Errors from fs are typically recoverable. */
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result PtpResponder::HandleRequestImpl() {
|
||||
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
|
||||
R_TRY(dp.Read(std::addressof(m_request_header)));
|
||||
|
||||
switch (m_request_header.type) {
|
||||
case PtpUsbBulkContainerType_Command: R_RETURN(this->HandleCommandRequest(dp));
|
||||
default: R_THROW(haze::ResultUnknownRequestType());
|
||||
}
|
||||
}
|
||||
|
||||
Result PtpResponder::HandleCommandRequest(PtpDataParser &dp) {
|
||||
if (!m_session_open && m_request_header.code != PtpOperationCode_OpenSession && m_request_header.code != PtpOperationCode_GetDeviceInfo) {
|
||||
R_THROW(haze::ResultSessionNotOpen());
|
||||
}
|
||||
|
||||
switch (m_request_header.code) {
|
||||
case PtpOperationCode_GetDeviceInfo: R_RETURN(this->GetDeviceInfo(dp)); break;
|
||||
case PtpOperationCode_OpenSession: R_RETURN(this->OpenSession(dp)); break;
|
||||
case PtpOperationCode_CloseSession: R_RETURN(this->CloseSession(dp)); break;
|
||||
case PtpOperationCode_GetStorageIds: R_RETURN(this->GetStorageIds(dp)); break;
|
||||
case PtpOperationCode_GetStorageInfo: R_RETURN(this->GetStorageInfo(dp)); break;
|
||||
case PtpOperationCode_GetObjectHandles: R_RETURN(this->GetObjectHandles(dp)); break;
|
||||
case PtpOperationCode_GetObjectInfo: R_RETURN(this->GetObjectInfo(dp)); break;
|
||||
case PtpOperationCode_GetObject: R_RETURN(this->GetObject(dp)); break;
|
||||
case PtpOperationCode_SendObjectInfo: R_RETURN(this->SendObjectInfo(dp)); break;
|
||||
case PtpOperationCode_SendObject: R_RETURN(this->SendObject(dp)); break;
|
||||
case PtpOperationCode_DeleteObject: R_RETURN(this->DeleteObject(dp)); break;
|
||||
case PtpOperationCode_MtpGetObjectPropsSupported: R_RETURN(this->GetObjectPropsSupported(dp)); break;
|
||||
case PtpOperationCode_MtpGetObjectPropDesc: R_RETURN(this->GetObjectPropDesc(dp)); break;
|
||||
case PtpOperationCode_MtpGetObjectPropValue: R_RETURN(this->GetObjectPropValue(dp)); break;
|
||||
case PtpOperationCode_MtpSetObjectPropValue: R_RETURN(this->SetObjectPropValue(dp)); break;
|
||||
case PtpOperationCode_MtpGetObjPropList: R_RETURN(this->GetObjectPropList(dp)); break;
|
||||
case PtpOperationCode_AndroidGetPartialObject64: R_RETURN(this->GetPartialObject64(dp)); break;
|
||||
case PtpOperationCode_AndroidSendPartialObject: R_RETURN(this->SendPartialObject(dp)); break;
|
||||
case PtpOperationCode_AndroidTruncateObject: R_RETURN(this->TruncateObject(dp)); break;
|
||||
case PtpOperationCode_AndroidBeginEditObject: R_RETURN(this->BeginEditObject(dp)); break;
|
||||
case PtpOperationCode_AndroidEndEditObject: R_RETURN(this->EndEditObject(dp)); break;
|
||||
default: R_THROW(haze::ResultOperationNotSupported());
|
||||
}
|
||||
}
|
||||
|
||||
void PtpResponder::ForceCloseSession() {
|
||||
if (m_session_open) {
|
||||
m_session_open = false;
|
||||
m_object_database.Finalize();
|
||||
}
|
||||
}
|
||||
|
||||
Result PtpResponder::WriteResponse(PtpResponseCode code, const void* data, size_t size) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
R_TRY(db.AddResponseHeader(m_request_header, code, size));
|
||||
R_TRY(db.AddBuffer(reinterpret_cast<const u8*>(data), size));
|
||||
R_RETURN(db.Commit());
|
||||
}
|
||||
|
||||
Result PtpResponder::WriteResponse(PtpResponseCode code) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
R_TRY(db.AddResponseHeader(m_request_header, code, 0));
|
||||
R_RETURN(db.Commit());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
#include <haze/ptp_data_builder.hpp>
|
||||
#include <haze/ptp_data_parser.hpp>
|
||||
#include <haze/ptp_responder_types.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
Result PtpResponder::GetPartialObject64(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Get the object ID, offset, and size for the file we want to read. */
|
||||
u32 object_id, size;
|
||||
u64 offset;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Read(std::addressof(offset)));
|
||||
R_TRY(dp.Read(std::addressof(size)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Lock the object as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
/* Get the file's size. */
|
||||
s64 file_size = 0;
|
||||
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(file_size)));
|
||||
|
||||
/* Ensure the requested offset and size are within range. */
|
||||
R_UNLESS(offset + size > offset, haze::ResultInvalidArgument());
|
||||
R_UNLESS(static_cast<u64>(file_size) <= offset + size, haze::ResultInvalidArgument());
|
||||
|
||||
/* Send the header and data size. */
|
||||
R_TRY(db.AddDataHeader(m_request_header, size));
|
||||
|
||||
/* Begin reading the file, writing data to the builder as we progress. */
|
||||
s64 size_remaining = size;
|
||||
while (true) {
|
||||
/* Get the next batch. */
|
||||
u64 bytes_to_read = std::min<s64>(FsBufferSize, size_remaining);
|
||||
u64 bytes_read;
|
||||
|
||||
R_TRY(m_fs.ReadFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_to_read, FsReadOption_None, std::addressof(bytes_read)));
|
||||
|
||||
size_remaining -= bytes_read;
|
||||
offset += bytes_read;
|
||||
|
||||
/* Write to output. */
|
||||
R_TRY(db.AddBuffer(m_buffers->file_system_data_buffer, bytes_read));
|
||||
|
||||
/* If we read fewer bytes than the batch size, or have read enough data, we're done. */
|
||||
if (bytes_read < FsBufferSize || size_remaining == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush the data response. */
|
||||
R_TRY(db.Commit());
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::SendPartialObject(PtpDataParser &rdp) {
|
||||
/* Get the object ID, offset, and size for the file we want to write. */
|
||||
u32 object_id, size;
|
||||
u64 offset;
|
||||
R_TRY(rdp.Read(std::addressof(object_id)));
|
||||
R_TRY(rdp.Read(std::addressof(size)));
|
||||
R_TRY(rdp.Read(std::addressof(offset)));
|
||||
R_TRY(rdp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(m_send_object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Lock the object as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
/* Get the file's size. */
|
||||
s64 file_size = 0;
|
||||
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(file_size)));
|
||||
|
||||
/* Ensure the requested offset and size are within range. */
|
||||
R_UNLESS(offset + size > offset, haze::ResultInvalidArgument());
|
||||
R_UNLESS(static_cast<u64>(file_size) <= offset, haze::ResultInvalidArgument());
|
||||
|
||||
/* Prepare a data parser for the data we are about to receive. */
|
||||
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Ensure we have a data header. */
|
||||
PtpUsbBulkContainer data_header;
|
||||
R_TRY(dp.Read(std::addressof(data_header)));
|
||||
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
|
||||
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
|
||||
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
|
||||
|
||||
/* Begin writing to the filesystem. */
|
||||
s64 size_remaining = size;
|
||||
while (true) {
|
||||
/* Read as many bytes as we can. */
|
||||
u32 bytes_received;
|
||||
const Result read_res = dp.ReadBuffer(m_buffers->file_system_data_buffer, FsBufferSize, std::addressof(bytes_received));
|
||||
|
||||
/* Write to the file. */
|
||||
u32 bytes_to_write = std::min<s64>(size_remaining, bytes_received);
|
||||
R_TRY(m_fs.WriteFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_to_write, 0));
|
||||
|
||||
size_remaining -= bytes_to_write;
|
||||
offset += bytes_to_write;
|
||||
|
||||
/* If we received fewer bytes than the batch size, or have written enough data, we're done. */
|
||||
if (haze::ResultEndOfTransmission::Includes(read_res) || size_remaining == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
R_TRY(read_res);
|
||||
}
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::TruncateObject(PtpDataParser &dp) {
|
||||
/* Get the object ID and size for the file we want to truncate. */
|
||||
u32 object_id;
|
||||
u64 size;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Read(std::addressof(size)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Lock the object as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
/* Truncate the file. */
|
||||
R_TRY(m_fs.SetFileSize(std::addressof(file), size));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::BeginEditObject(PtpDataParser &dp) {
|
||||
/* Get the object ID we are going to begin editing. */
|
||||
u32 object_id;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* We don't implement transactions, so write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::EndEditObject(PtpDataParser &dp) {
|
||||
/* Get the object ID we are going to finish editing. */
|
||||
u32 object_id;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* We don't implement transactions, so write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,418 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
#include <haze/ptp_data_builder.hpp>
|
||||
#include <haze/ptp_data_parser.hpp>
|
||||
#include <haze/ptp_responder_types.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
Result PtpResponder::GetObjectPropsSupported(PtpDataParser &dp) {
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Write information about all object properties we can support. */
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
||||
R_RETURN(db.AddArray(SupportedObjectProperties, util::size(SupportedObjectProperties)));
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObjectPropDesc(PtpDataParser &dp) {
|
||||
PtpObjectPropertyCode property_code;
|
||||
u16 object_format;
|
||||
|
||||
R_TRY(dp.Read(std::addressof(property_code)));
|
||||
R_TRY(dp.Read(std::addressof(object_format)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Ensure we have a valid property code before continuing. */
|
||||
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
|
||||
|
||||
/* Begin writing information about the property code. */
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
||||
R_TRY(db.Add(property_code));
|
||||
|
||||
/* Each property code corresponds to a different pattern, which contains the data type, */
|
||||
/* whether the property can be set for an object, and the default value of the property. */
|
||||
switch (property_code) {
|
||||
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U128));
|
||||
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
|
||||
R_TRY(db.Add<u128>(0));
|
||||
}
|
||||
case PtpObjectPropertyCode_ObjectSize:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U64));
|
||||
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
|
||||
R_TRY(db.Add<u64>(0));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_StorageId:
|
||||
case PtpObjectPropertyCode_ParentObject:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U32));
|
||||
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
|
||||
R_TRY(db.Add(StorageId_SdmcFs));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectFormat:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U16));
|
||||
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
|
||||
R_TRY(db.Add(PtpObjectFormatCode_Undefined));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectFileName:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_String));
|
||||
R_TRY(db.Add(PtpPropertyGetSetFlag_GetSet));
|
||||
R_TRY(db.AddString(""));
|
||||
}
|
||||
break;
|
||||
HAZE_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
/* Group code is a required part of the response, but doesn't seem to be used for anything. */
|
||||
R_TRY(db.Add(PtpPropertyGroupCode_Default));
|
||||
|
||||
/* We don't use the form flag. */
|
||||
R_TRY(db.Add(PtpPropertyFormFlag_None));
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObjectPropValue(PtpDataParser &dp) {
|
||||
u32 object_id;
|
||||
PtpObjectPropertyCode property_code;
|
||||
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Read(std::addressof(property_code)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Ensure we have a valid property code before continuing. */
|
||||
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Define helper for getting the object type. */
|
||||
const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) {
|
||||
R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type));
|
||||
};
|
||||
|
||||
/* Define helper for getting the object size. */
|
||||
const auto GetObjectSize = [&] (s64 *out_size) {
|
||||
*out_size = 0;
|
||||
|
||||
/* Check if this is a directory. */
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(GetObjectType(std::addressof(entry_type)));
|
||||
|
||||
/* If it is, we're done. */
|
||||
R_SUCCEED_IF(entry_type == FsDirEntryType_Dir);
|
||||
|
||||
/* Otherwise, open as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size));
|
||||
};
|
||||
|
||||
/* Begin writing the requested object property. */
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
||||
switch (property_code) {
|
||||
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
|
||||
{
|
||||
R_TRY(db.Add<u128>(object_id));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectSize:
|
||||
{
|
||||
s64 size;
|
||||
R_TRY(GetObjectSize(std::addressof(size)));
|
||||
R_TRY(db.Add<u64>(size));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_StorageId:
|
||||
{
|
||||
R_TRY(db.Add(StorageId_SdmcFs));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ParentObject:
|
||||
{
|
||||
R_TRY(db.Add(obj->GetParentId()));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectFormat:
|
||||
{
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(GetObjectType(std::addressof(entry_type)));
|
||||
R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectFileName:
|
||||
{
|
||||
R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1));
|
||||
}
|
||||
break;
|
||||
HAZE_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObjectPropList(PtpDataParser &dp) {
|
||||
u32 object_id;
|
||||
u32 object_format;
|
||||
s32 property_code;
|
||||
s32 group_code;
|
||||
s32 depth;
|
||||
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Read(std::addressof(object_format)));
|
||||
R_TRY(dp.Read(std::addressof(property_code)));
|
||||
R_TRY(dp.Read(std::addressof(group_code)));
|
||||
R_TRY(dp.Read(std::addressof(depth)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Ensure format is unspecified. */
|
||||
R_UNLESS(object_format == 0, haze::ResultInvalidArgument());
|
||||
|
||||
/* Ensure we have a valid property code. */
|
||||
R_UNLESS(property_code == -1 || IsSupportedObjectPropertyCode(PtpObjectPropertyCode(property_code)), haze::ResultUnknownPropertyCode());
|
||||
|
||||
/* Ensure group code is the default. */
|
||||
R_UNLESS(group_code == PtpPropertyGroupCode_Default, haze::ResultGroupSpecified());
|
||||
|
||||
/* Ensure depth is 0. */
|
||||
R_UNLESS(depth == 0, haze::ResultDepthSpecified());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Define helper for getting the object type. */
|
||||
const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) {
|
||||
R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type));
|
||||
};
|
||||
|
||||
/* Define helper for getting the object size. */
|
||||
const auto GetObjectSize = [&] (s64 *out_size) {
|
||||
*out_size = 0;
|
||||
|
||||
/* Check if this is a directory. */
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(GetObjectType(std::addressof(entry_type)));
|
||||
|
||||
/* If it is, we're done. */
|
||||
R_SUCCEED_IF(entry_type == FsDirEntryType_Dir);
|
||||
|
||||
/* Otherwise, open as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size));
|
||||
};
|
||||
|
||||
/* Define helper for determining if the property should be included. */
|
||||
const auto ShouldIncludeProperty = [&] (PtpObjectPropertyCode code) {
|
||||
/* If all properties were requested, or it was the requested property, we should include the property. */
|
||||
return property_code == -1 || code == property_code;
|
||||
};
|
||||
|
||||
/* Determine how many output elements we will report. */
|
||||
u32 num_output_elements = 0;
|
||||
for (const auto obj_property : SupportedObjectProperties) {
|
||||
if (ShouldIncludeProperty(obj_property)) {
|
||||
num_output_elements++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Begin writing the requested object properties. */
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
||||
/* Report the number of elements. */
|
||||
R_TRY(db.Add(num_output_elements));
|
||||
|
||||
for (const auto obj_property : SupportedObjectProperties) {
|
||||
if (!ShouldIncludeProperty(obj_property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Write the object handle. */
|
||||
R_TRY(db.Add<u32>(object_id));
|
||||
|
||||
/* Write the property code. */
|
||||
R_TRY(db.Add<u16>(obj_property));
|
||||
|
||||
/* Write the property value. */
|
||||
switch (obj_property) {
|
||||
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U128));
|
||||
R_TRY(db.Add<u128>(object_id));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectSize:
|
||||
{
|
||||
s64 size;
|
||||
R_TRY(GetObjectSize(std::addressof(size)));
|
||||
R_TRY(db.Add(PtpDataTypeCode_U64));
|
||||
R_TRY(db.Add<u64>(size));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_StorageId:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U32));
|
||||
R_TRY(db.Add(StorageId_SdmcFs));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ParentObject:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_U32));
|
||||
R_TRY(db.Add(obj->GetParentId()));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectFormat:
|
||||
{
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(GetObjectType(std::addressof(entry_type)));
|
||||
R_TRY(db.Add(PtpDataTypeCode_U16));
|
||||
R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association));
|
||||
}
|
||||
break;
|
||||
case PtpObjectPropertyCode_ObjectFileName:
|
||||
{
|
||||
R_TRY(db.Add(PtpDataTypeCode_String));
|
||||
R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1));
|
||||
}
|
||||
break;
|
||||
HAZE_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::SetObjectPropValue(PtpDataParser &rdp) {
|
||||
u32 object_id;
|
||||
PtpObjectPropertyCode property_code;
|
||||
|
||||
R_TRY(rdp.Read(std::addressof(object_id)));
|
||||
R_TRY(rdp.Read(std::addressof(property_code)));
|
||||
R_TRY(rdp.Finalize());
|
||||
|
||||
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Ensure we have a data header. */
|
||||
PtpUsbBulkContainer data_header;
|
||||
R_TRY(dp.Read(std::addressof(data_header)));
|
||||
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
|
||||
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
|
||||
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
|
||||
|
||||
/* Ensure we have a valid property code before continuing. */
|
||||
R_UNLESS(property_code == PtpObjectPropertyCode_ObjectFileName, haze::ResultUnknownPropertyCode());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* We are reading a file name. */
|
||||
R_TRY(dp.ReadString(m_buffers->filename_string_buffer));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Ensure we can actually process the new name. */
|
||||
const bool is_empty = m_buffers->filename_string_buffer[0] == '\x00';
|
||||
const bool contains_slashes = std::strchr(m_buffers->filename_string_buffer, '/') != nullptr;
|
||||
R_UNLESS(!is_empty && !contains_slashes, haze::ResultInvalidPropertyValue());
|
||||
|
||||
/* Add a new object in the database with the new name. */
|
||||
PtpObject *newobj;
|
||||
{
|
||||
/* Find the last path separator in the existing object name. */
|
||||
char *pathsep = std::strrchr(obj->m_name, '/');
|
||||
HAZE_ASSERT(pathsep != nullptr);
|
||||
|
||||
/* Temporarily mark the path separator as null to facilitate processing. */
|
||||
*pathsep = '\x00';
|
||||
ON_SCOPE_EXIT { *pathsep = '/'; };
|
||||
|
||||
R_TRY(m_object_database.CreateOrFindObject(obj->GetName(), m_buffers->filename_string_buffer, obj->GetParentId(), std::addressof(newobj)));
|
||||
}
|
||||
|
||||
{
|
||||
/* Ensure we maintain a clean state on failure. */
|
||||
ON_RESULT_FAILURE {
|
||||
if (!newobj->GetIsRegistered()) {
|
||||
/* Only delete if the object was not registered. */
|
||||
/* Otherwise, we would remove an object that still exists. */
|
||||
m_object_database.DeleteObject(newobj);
|
||||
}
|
||||
};
|
||||
|
||||
/* Get the old object type. */
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
|
||||
|
||||
/* Attempt to rename the object on the filesystem. */
|
||||
if (entry_type == FsDirEntryType_Dir) {
|
||||
R_TRY(m_fs.RenameDirectory(obj->GetName(), newobj->GetName()));
|
||||
} else {
|
||||
R_TRY(m_fs.RenameFile(obj->GetName(), newobj->GetName()));
|
||||
}
|
||||
}
|
||||
|
||||
/* Unregister and free the old object. */
|
||||
m_object_database.DeleteObject(obj);
|
||||
|
||||
/* Register the new object. */
|
||||
m_object_database.RegisterObject(newobj, object_id);
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,507 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
#include <haze/ptp_data_builder.hpp>
|
||||
#include <haze/ptp_data_parser.hpp>
|
||||
#include <haze/ptp_responder_types.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
Result PtpResponder::GetDeviceInfo(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Write the device info data. */
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
|
||||
R_TRY(db.Add(MtpStandardVersion));
|
||||
R_TRY(db.Add(MtpVendorExtensionId));
|
||||
R_TRY(db.Add(MtpStandardVersion));
|
||||
R_TRY(db.AddString(MtpVendorExtensionDesc));
|
||||
R_TRY(db.Add(MtpFunctionalModeDefault));
|
||||
R_TRY(db.AddArray(SupportedOperationCodes, util::size(SupportedOperationCodes)));
|
||||
R_TRY(db.AddArray(SupportedEventCodes, util::size(SupportedEventCodes)));
|
||||
R_TRY(db.AddArray(SupportedDeviceProperties, util::size(SupportedDeviceProperties)));
|
||||
R_TRY(db.AddArray(SupportedCaptureFormats, util::size(SupportedCaptureFormats)));
|
||||
R_TRY(db.AddArray(SupportedPlaybackFormats, util::size(SupportedPlaybackFormats)));
|
||||
R_TRY(db.AddString(MtpDeviceManufacturer));
|
||||
R_TRY(db.AddString(MtpDeviceModel));
|
||||
R_TRY(db.AddString(GetFirmwareVersion()));
|
||||
R_TRY(db.AddString(GetSerialNumber()));
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::OpenSession(PtpDataParser &dp) {
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Close, if we're already open. */
|
||||
this->ForceCloseSession();
|
||||
|
||||
/* Initialize the database. */
|
||||
m_session_open = true;
|
||||
m_object_database.Initialize(m_object_heap);
|
||||
|
||||
/* Create the root storages. */
|
||||
PtpObject *object;
|
||||
R_TRY(m_object_database.CreateOrFindObject("", "", PtpGetObjectHandles_RootParent, std::addressof(object)));
|
||||
|
||||
/* Register the root storages. */
|
||||
m_object_database.RegisterObject(object, StorageId_SdmcFs);
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::CloseSession(PtpDataParser &dp) {
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
this->ForceCloseSession();
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetStorageIds(PtpDataParser &dp) {
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Write the storage ID array. */
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
||||
R_RETURN(db.AddArray(SupportedStorageIds, util::size(SupportedStorageIds)));
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetStorageInfo(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
PtpStorageInfo storage_info(DefaultStorageInfo);
|
||||
|
||||
/* Get the storage ID the client requested information for. */
|
||||
u32 storage_id;
|
||||
R_TRY(dp.Read(std::addressof(storage_id)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Get the info from fs. */
|
||||
switch (storage_id) {
|
||||
case StorageId_SdmcFs:
|
||||
{
|
||||
s64 total_space, free_space;
|
||||
R_TRY(m_fs.GetTotalSpace("/", std::addressof(total_space)));
|
||||
R_TRY(m_fs.GetFreeSpace("/", std::addressof(free_space)));
|
||||
|
||||
storage_info.max_capacity = total_space;
|
||||
storage_info.free_space_in_bytes = free_space;
|
||||
storage_info.free_space_in_images = 0;
|
||||
storage_info.storage_description = "SD Card";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
R_THROW(haze::ResultInvalidStorageId());
|
||||
}
|
||||
|
||||
/* Write the storage info data. */
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
|
||||
R_TRY(db.Add(storage_info.storage_type));
|
||||
R_TRY(db.Add(storage_info.filesystem_type));
|
||||
R_TRY(db.Add(storage_info.access_capability));
|
||||
R_TRY(db.Add(storage_info.max_capacity));
|
||||
R_TRY(db.Add(storage_info.free_space_in_bytes));
|
||||
R_TRY(db.Add(storage_info.free_space_in_images));
|
||||
R_TRY(db.AddString(storage_info.storage_description));
|
||||
R_TRY(db.AddString(storage_info.volume_label));
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObjectHandles(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Get the object ID the client requested enumeration for. */
|
||||
u32 storage_id, object_format_code, association_object_handle;
|
||||
R_TRY(dp.Read(std::addressof(storage_id)));
|
||||
R_TRY(dp.Read(std::addressof(object_format_code)));
|
||||
R_TRY(dp.Read(std::addressof(association_object_handle)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Handle top-level requests. */
|
||||
if (storage_id == PtpGetObjectHandles_AllStorage) {
|
||||
storage_id = StorageId_SdmcFs;
|
||||
}
|
||||
|
||||
/* Rewrite requests for enumerating storage directories. */
|
||||
if (association_object_handle == PtpGetObjectHandles_RootParent) {
|
||||
association_object_handle = storage_id;
|
||||
}
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(association_object_handle);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Try to read the object as a directory. */
|
||||
FsDir dir;
|
||||
R_TRY(m_fs.OpenDirectory(obj->GetName(), FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, std::addressof(dir)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseDirectory(std::addressof(dir)); };
|
||||
|
||||
/* Count how many entries are in the directory. */
|
||||
s64 entry_count = 0;
|
||||
R_TRY(m_fs.GetDirectoryEntryCount(std::addressof(dir), std::addressof(entry_count)));
|
||||
|
||||
/* Begin writing. */
|
||||
R_TRY(db.AddDataHeader(m_request_header, sizeof(u32) + (entry_count * sizeof(u32))));
|
||||
R_TRY(db.Add(static_cast<u32>(entry_count)));
|
||||
|
||||
/* Enumerate the directory, writing results to the data builder as we progress. */
|
||||
/* TODO: How should we handle the directory contents changing during enumeration? */
|
||||
/* Is this even feasible to handle? */
|
||||
while (true) {
|
||||
/* Get the next batch. */
|
||||
s64 read_count = 0;
|
||||
R_TRY(m_fs.ReadDirectory(std::addressof(dir), std::addressof(read_count), DirectoryReadSize, m_buffers->file_system_entry_buffer));
|
||||
|
||||
/* Write to output. */
|
||||
for (s64 i = 0; i < read_count; i++) {
|
||||
const char *name = m_buffers->file_system_entry_buffer[i].name;
|
||||
u32 handle;
|
||||
|
||||
R_TRY(m_object_database.CreateAndRegisterObjectId(obj->GetName(), name, obj->GetObjectId(), std::addressof(handle)));
|
||||
R_TRY(db.Add(handle));
|
||||
}
|
||||
|
||||
/* If we read fewer than the batch size, we're done. */
|
||||
if (read_count < DirectoryReadSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush the data response. */
|
||||
R_TRY(db.Commit());
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObjectInfo(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Get the object ID the client requested info for. */
|
||||
u32 object_id;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Build info about the object. */
|
||||
PtpObjectInfo object_info(DefaultObjectInfo);
|
||||
|
||||
if (object_id == StorageId_SdmcFs) {
|
||||
/* The SD Card directory has some special properties. */
|
||||
object_info.object_format = PtpObjectFormatCode_Association;
|
||||
object_info.association_type = PtpAssociationType_GenericFolder;
|
||||
object_info.filename = "SD Card";
|
||||
} else {
|
||||
/* Figure out what type of object this is. */
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
|
||||
|
||||
/* Get the size, if we are requesting info about a file. */
|
||||
s64 size = 0;
|
||||
if (entry_type == FsDirEntryType_File) {
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size)));
|
||||
}
|
||||
|
||||
object_info.filename = std::strrchr(obj->GetName(), '/') + 1;
|
||||
object_info.object_compressed_size = size;
|
||||
object_info.parent_object = obj->GetParentId();
|
||||
|
||||
if (entry_type == FsDirEntryType_Dir) {
|
||||
object_info.object_format = PtpObjectFormatCode_Association;
|
||||
object_info.association_type = PtpAssociationType_GenericFolder;
|
||||
} else {
|
||||
object_info.object_format = PtpObjectFormatCode_Undefined;
|
||||
object_info.association_type = PtpAssociationType_Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the object info data. */
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
|
||||
R_TRY(db.Add(object_info.storage_id));
|
||||
R_TRY(db.Add(object_info.object_format));
|
||||
R_TRY(db.Add(object_info.protection_status));
|
||||
R_TRY(db.Add(object_info.object_compressed_size));
|
||||
R_TRY(db.Add(object_info.thumb_format));
|
||||
R_TRY(db.Add(object_info.thumb_compressed_size));
|
||||
R_TRY(db.Add(object_info.thumb_width));
|
||||
R_TRY(db.Add(object_info.thumb_height));
|
||||
R_TRY(db.Add(object_info.image_width));
|
||||
R_TRY(db.Add(object_info.image_height));
|
||||
R_TRY(db.Add(object_info.image_depth));
|
||||
R_TRY(db.Add(object_info.parent_object));
|
||||
R_TRY(db.Add(object_info.association_type));
|
||||
R_TRY(db.Add(object_info.association_desc));
|
||||
R_TRY(db.Add(object_info.sequence_number));
|
||||
R_TRY(db.AddString(object_info.filename));
|
||||
R_TRY(db.AddString(object_info.capture_date));
|
||||
R_TRY(db.AddString(object_info.modification_date));
|
||||
R_TRY(db.AddString(object_info.keywords));
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObject(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Get the object ID the client requested. */
|
||||
u32 object_id;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Lock the object as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
/* Get the file's size. */
|
||||
s64 size = 0;
|
||||
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size)));
|
||||
|
||||
/* Send the header and file size. */
|
||||
R_TRY(db.AddDataHeader(m_request_header, size));
|
||||
|
||||
/* Begin reading the file, writing data to the builder as we progress. */
|
||||
s64 offset = 0;
|
||||
while (true) {
|
||||
/* Get the next batch. */
|
||||
u64 bytes_read;
|
||||
R_TRY(m_fs.ReadFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, FsBufferSize, FsReadOption_None, std::addressof(bytes_read)));
|
||||
|
||||
offset += bytes_read;
|
||||
|
||||
/* Write to output. */
|
||||
R_TRY(db.AddBuffer(m_buffers->file_system_data_buffer, bytes_read));
|
||||
|
||||
/* If we read fewer bytes than the batch size, we're done. */
|
||||
if (bytes_read < FsBufferSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush the data response. */
|
||||
R_TRY(db.Commit());
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::SendObjectInfo(PtpDataParser &rdp) {
|
||||
/* Get the storage ID and parent object and flush the request packet. */
|
||||
u32 storage_id, parent_object;
|
||||
R_TRY(rdp.Read(std::addressof(storage_id)));
|
||||
R_TRY(rdp.Read(std::addressof(parent_object)));
|
||||
R_TRY(rdp.Finalize());
|
||||
|
||||
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
|
||||
PtpObjectInfo info(DefaultObjectInfo);
|
||||
|
||||
/* Ensure we have a data header. */
|
||||
PtpUsbBulkContainer data_header;
|
||||
R_TRY(dp.Read(std::addressof(data_header)));
|
||||
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
|
||||
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
|
||||
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
|
||||
|
||||
/* Read in the object info. */
|
||||
R_TRY(dp.Read(std::addressof(info.storage_id)));
|
||||
R_TRY(dp.Read(std::addressof(info.object_format)));
|
||||
R_TRY(dp.Read(std::addressof(info.protection_status)));
|
||||
R_TRY(dp.Read(std::addressof(info.object_compressed_size)));
|
||||
R_TRY(dp.Read(std::addressof(info.thumb_format)));
|
||||
R_TRY(dp.Read(std::addressof(info.thumb_compressed_size)));
|
||||
R_TRY(dp.Read(std::addressof(info.thumb_width)));
|
||||
R_TRY(dp.Read(std::addressof(info.thumb_height)));
|
||||
R_TRY(dp.Read(std::addressof(info.image_width)));
|
||||
R_TRY(dp.Read(std::addressof(info.image_height)));
|
||||
R_TRY(dp.Read(std::addressof(info.image_depth)));
|
||||
R_TRY(dp.Read(std::addressof(info.parent_object)));
|
||||
R_TRY(dp.Read(std::addressof(info.association_type)));
|
||||
R_TRY(dp.Read(std::addressof(info.association_desc)));
|
||||
R_TRY(dp.Read(std::addressof(info.sequence_number)));
|
||||
R_TRY(dp.ReadString(m_buffers->filename_string_buffer));
|
||||
R_TRY(dp.ReadString(m_buffers->capture_date_string_buffer));
|
||||
R_TRY(dp.ReadString(m_buffers->modification_date_string_buffer));
|
||||
R_TRY(dp.ReadString(m_buffers->keywords_string_buffer));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Rewrite requests for creating in storage directories. */
|
||||
if (parent_object == PtpGetObjectHandles_RootParent) {
|
||||
parent_object = storage_id;
|
||||
}
|
||||
|
||||
/* Check if we know about the parent object. If we don't, it's an error. */
|
||||
auto * const parentobj = m_object_database.GetObjectById(parent_object);
|
||||
R_UNLESS(parentobj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Make a new object with the intended name. */
|
||||
PtpNewObjectInfo new_object_info;
|
||||
new_object_info.storage_id = StorageId_SdmcFs;
|
||||
new_object_info.parent_object_id = parent_object == storage_id ? 0 : parent_object;
|
||||
|
||||
/* Create the object in the database. */
|
||||
PtpObject *obj;
|
||||
R_TRY(m_object_database.CreateOrFindObject(parentobj->GetName(), m_buffers->filename_string_buffer, parentobj->GetObjectId(), std::addressof(obj)));
|
||||
|
||||
/* Ensure we maintain a clean state on failure. */
|
||||
ON_RESULT_FAILURE { m_object_database.DeleteObject(obj); };
|
||||
|
||||
/* Register the object with a new ID. */
|
||||
m_object_database.RegisterObject(obj);
|
||||
new_object_info.object_id = obj->GetObjectId();
|
||||
|
||||
/* Create the object on the filesystem. */
|
||||
if (info.object_format == PtpObjectFormatCode_Association) {
|
||||
R_TRY(m_fs.CreateDirectory(obj->GetName()));
|
||||
m_send_object_id = 0;
|
||||
} else {
|
||||
R_TRY(m_fs.CreateFile(obj->GetName(), 0, 0));
|
||||
m_send_object_id = new_object_info.object_id;
|
||||
}
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok, new_object_info));
|
||||
}
|
||||
|
||||
Result PtpResponder::SendObject(PtpDataParser &rdp) {
|
||||
/* Reset SendObject object ID on exit. */
|
||||
ON_SCOPE_EXIT { m_send_object_id = 0; };
|
||||
|
||||
R_TRY(rdp.Finalize());
|
||||
|
||||
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Ensure we have a data header. */
|
||||
PtpUsbBulkContainer data_header;
|
||||
R_TRY(dp.Read(std::addressof(data_header)));
|
||||
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
|
||||
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
|
||||
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(m_send_object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Lock the object as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
||||
|
||||
/* Truncate the file after locking for write. */
|
||||
s64 offset = 0;
|
||||
R_TRY(m_fs.SetFileSize(std::addressof(file), 0));
|
||||
|
||||
/* Expand to the needed size. */
|
||||
if (data_header.length > sizeof(PtpUsbBulkContainer)) {
|
||||
R_TRY(m_fs.SetFileSize(std::addressof(file), data_header.length - sizeof(PtpUsbBulkContainer)));
|
||||
}
|
||||
|
||||
/* Begin writing to the filesystem. */
|
||||
while (true) {
|
||||
/* Read as many bytes as we can. */
|
||||
u32 bytes_received;
|
||||
const Result read_res = dp.ReadBuffer(m_buffers->file_system_data_buffer, FsBufferSize, std::addressof(bytes_received));
|
||||
|
||||
/* Write to the file. */
|
||||
R_TRY(m_fs.WriteFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_received, 0));
|
||||
|
||||
offset += bytes_received;
|
||||
|
||||
/* If we received fewer bytes than the batch size, we're done. */
|
||||
if (haze::ResultEndOfTransmission::Includes(read_res)) {
|
||||
break;
|
||||
}
|
||||
|
||||
R_TRY(read_res);
|
||||
}
|
||||
|
||||
/* Truncate the file to the received size. */
|
||||
R_TRY(m_fs.SetFileSize(std::addressof(file), offset));
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::DeleteObject(PtpDataParser &dp) {
|
||||
/* Get the object ID and flush the request packet. */
|
||||
u32 object_id;
|
||||
R_TRY(dp.Read(std::addressof(object_id)));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Disallow deleting the storage root. */
|
||||
R_UNLESS(object_id != StorageId_SdmcFs, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto * const obj = m_object_database.GetObjectById(object_id);
|
||||
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
||||
|
||||
/* Figure out what type of object this is. */
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
|
||||
|
||||
/* Remove the object from the filesystem. */
|
||||
if (entry_type == FsDirEntryType_Dir) {
|
||||
R_TRY(m_fs.DeleteDirectoryRecursively(obj->GetName()));
|
||||
} else {
|
||||
R_TRY(m_fs.DeleteFile(obj->GetName()));
|
||||
}
|
||||
|
||||
/* Remove the object from the database. */
|
||||
m_object_database.DeleteObject(obj);
|
||||
|
||||
/* Write the success response. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const u32 DefaultInterfaceNumber = 0;
|
||||
|
||||
}
|
||||
|
||||
Result UsbSession::Initialize1x(const UsbCommsInterfaceInfo *info) {
|
||||
struct usb_interface_descriptor interface_descriptor = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = DefaultInterfaceNumber,
|
||||
.bInterfaceClass = info->bInterfaceClass,
|
||||
.bInterfaceSubClass = info->bInterfaceSubClass,
|
||||
.bInterfaceProtocol = info->bInterfaceProtocol,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_in = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_out = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_OUT,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_interrupt = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_INTERRUPT,
|
||||
.wMaxPacketSize = 0x18,
|
||||
.bInterval = 0x4,
|
||||
};
|
||||
|
||||
/* Set up interface. */
|
||||
R_TRY(usbDsGetDsInterface(std::addressof(m_interface), std::addressof(interface_descriptor), "usb"));
|
||||
|
||||
/* Set up endpoints. */
|
||||
R_TRY(usbDsInterface_GetDsEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Write]), std::addressof(endpoint_descriptor_in)));
|
||||
R_TRY(usbDsInterface_GetDsEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Read]), std::addressof(endpoint_descriptor_out)));
|
||||
R_TRY(usbDsInterface_GetDsEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Interrupt]), std::addressof(endpoint_descriptor_interrupt)));
|
||||
|
||||
R_RETURN(usbDsInterface_EnableInterface(m_interface));
|
||||
}
|
||||
|
||||
Result UsbSession::Initialize5x(const UsbCommsInterfaceInfo *info) {
|
||||
struct usb_interface_descriptor interface_descriptor = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = DefaultInterfaceNumber,
|
||||
.bNumEndpoints = 3,
|
||||
.bInterfaceClass = info->bInterfaceClass,
|
||||
.bInterfaceSubClass = info->bInterfaceSubClass,
|
||||
.bInterfaceProtocol = info->bInterfaceProtocol,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_in = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_out = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_OUT,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_interrupt = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_INTERRUPT,
|
||||
.wMaxPacketSize = 0x18,
|
||||
.bInterval = 0x6,
|
||||
};
|
||||
|
||||
struct usb_ss_endpoint_companion_descriptor endpoint_companion = {
|
||||
.bLength = sizeof(struct usb_ss_endpoint_companion_descriptor),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION,
|
||||
.bMaxBurst = 0x0f,
|
||||
.bmAttributes = 0x00,
|
||||
.wBytesPerInterval = 0x00,
|
||||
};
|
||||
|
||||
struct usb_ss_endpoint_companion_descriptor endpoint_companion_interrupt = {
|
||||
.bLength = sizeof(struct usb_ss_endpoint_companion_descriptor),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION,
|
||||
.bMaxBurst = 0x00,
|
||||
.bmAttributes = 0x00,
|
||||
.wBytesPerInterval = 0x00,
|
||||
};
|
||||
|
||||
R_TRY(usbDsRegisterInterface(std::addressof(m_interface)));
|
||||
|
||||
u8 iInterface;
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iInterface), "MTP"));
|
||||
|
||||
interface_descriptor.bInterfaceNumber = m_interface->interface_index;
|
||||
interface_descriptor.iInterface = iInterface;
|
||||
endpoint_descriptor_in.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1;
|
||||
endpoint_descriptor_out.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1;
|
||||
endpoint_descriptor_interrupt.bEndpointAddress += interface_descriptor.bInterfaceNumber + 2;
|
||||
|
||||
/* High speed config. */
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(interface_descriptor), USB_DT_INTERFACE_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(endpoint_descriptor_in), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(endpoint_descriptor_out), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(endpoint_descriptor_interrupt), USB_DT_ENDPOINT_SIZE));
|
||||
|
||||
/* Super speed config. */
|
||||
endpoint_descriptor_in.wMaxPacketSize = PtpUsbBulkSuperSpeedMaxPacketLength;
|
||||
endpoint_descriptor_out.wMaxPacketSize = PtpUsbBulkSuperSpeedMaxPacketLength;
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(interface_descriptor), USB_DT_INTERFACE_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_descriptor_in), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_companion), USB_DT_SS_ENDPOINT_COMPANION_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_descriptor_out), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_companion), USB_DT_SS_ENDPOINT_COMPANION_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_descriptor_interrupt), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_companion_interrupt), USB_DT_SS_ENDPOINT_COMPANION_SIZE));
|
||||
|
||||
/* Set up endpoints. */
|
||||
R_TRY(usbDsInterface_RegisterEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Write]), endpoint_descriptor_in.bEndpointAddress));
|
||||
R_TRY(usbDsInterface_RegisterEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Read]), endpoint_descriptor_out.bEndpointAddress));
|
||||
R_TRY(usbDsInterface_RegisterEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Interrupt]), endpoint_descriptor_interrupt.bEndpointAddress));
|
||||
|
||||
R_RETURN(usbDsInterface_EnableInterface(m_interface));
|
||||
}
|
||||
|
||||
Result UsbSession::Initialize(const UsbCommsInterfaceInfo *info, u16 id_vendor, u16 id_product) {
|
||||
R_TRY(usbDsInitialize());
|
||||
|
||||
if (hosversionAtLeast(5, 0, 0)) {
|
||||
/* Report language as US English. */
|
||||
static const u16 supported_langs[1] = { 0x0409 };
|
||||
R_TRY(usbDsAddUsbLanguageStringDescriptor(nullptr, supported_langs, util::size(supported_langs)));
|
||||
|
||||
/* Report strings. */
|
||||
u8 iManufacturer, iProduct, iSerialNumber;
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iManufacturer), "Nintendo"));
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iProduct), "Nintendo Switch"));
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iSerialNumber), GetSerialNumber()));
|
||||
|
||||
/* Send device descriptors */
|
||||
struct usb_device_descriptor device_descriptor = {
|
||||
.bLength = USB_DT_DEVICE_SIZE,
|
||||
.bDescriptorType = USB_DT_DEVICE,
|
||||
.bcdUSB = 0x0200,
|
||||
.bDeviceClass = 0x00,
|
||||
.bDeviceSubClass = 0x00,
|
||||
.bDeviceProtocol = 0x00,
|
||||
.bMaxPacketSize0 = 0x40,
|
||||
.idVendor = id_vendor,
|
||||
.idProduct = id_product,
|
||||
.bcdDevice = 0x0100,
|
||||
.iManufacturer = iManufacturer,
|
||||
.iProduct = iProduct,
|
||||
.iSerialNumber = iSerialNumber,
|
||||
.bNumConfigurations = 0x01
|
||||
};
|
||||
R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, std::addressof(device_descriptor)));
|
||||
|
||||
device_descriptor.bcdUSB = 0x0300;
|
||||
device_descriptor.bMaxPacketSize0 = 0x09;
|
||||
R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, std::addressof(device_descriptor)));
|
||||
|
||||
/* Binary Object Store */
|
||||
u8 bos[0x16] = {
|
||||
0x05, /* .bLength */
|
||||
USB_DT_BOS, /* .bDescriptorType */
|
||||
0x16, 0x00, /* .wTotalLength */
|
||||
0x02, /* .bNumDeviceCaps */
|
||||
|
||||
/* USB 2.0 */
|
||||
0x07, /* .bLength */
|
||||
USB_DT_DEVICE_CAPABILITY, /* .bDescriptorType */
|
||||
0x02, /* .bDevCapabilityType */
|
||||
0x02, 0x00, 0x00, 0x00, /* .bmAttributes */
|
||||
|
||||
/* USB 3.0 */
|
||||
0x0a, /* .bLength */
|
||||
USB_DT_DEVICE_CAPABILITY, /* .bDescriptorType */
|
||||
0x03, /* .bDevCapabilityType */
|
||||
0x00, /* .bmAttributes */
|
||||
0x0c, 0x00, /* .wSpeedSupported */
|
||||
0x03, /* .bFunctionalitySupport */
|
||||
0x00, /* .bU1DevExitLat */
|
||||
0x00, 0x00 /* .bU2DevExitLat */
|
||||
};
|
||||
R_TRY(usbDsSetBinaryObjectStore(bos, sizeof(bos)));
|
||||
}
|
||||
|
||||
if (hosversionAtLeast(5, 0, 0)) {
|
||||
R_TRY(this->Initialize5x(info));
|
||||
R_TRY(usbDsEnable());
|
||||
} else {
|
||||
R_TRY(this->Initialize1x(info));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void UsbSession::Finalize() {
|
||||
usbDsExit();
|
||||
}
|
||||
|
||||
bool UsbSession::GetConfigured() const {
|
||||
UsbState usb_state;
|
||||
|
||||
HAZE_R_ABORT_UNLESS(usbDsGetState(std::addressof(usb_state)));
|
||||
|
||||
return usb_state == UsbState_Configured;
|
||||
}
|
||||
|
||||
Event *UsbSession::GetCompletionEvent(UsbSessionEndpoint ep) const {
|
||||
return std::addressof(m_endpoints[ep]->CompletionEvent);
|
||||
}
|
||||
|
||||
Result UsbSession::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) {
|
||||
R_RETURN(usbDsEndpoint_PostBufferAsync(m_endpoints[ep], buffer, size, out_urb_id));
|
||||
}
|
||||
|
||||
Result UsbSession::GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_transferred_size) {
|
||||
UsbDsReportData report_data;
|
||||
|
||||
R_TRY(eventClear(std::addressof(m_endpoints[ep]->CompletionEvent)));
|
||||
R_TRY(usbDsEndpoint_GetReportData(m_endpoints[ep], std::addressof(report_data)));
|
||||
R_TRY(usbDsParseReportData(std::addressof(report_data), urb_id, nullptr, out_transferred_size));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
#---------------------------------------------------------------------------------
|
||||
.SUFFIXES:
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
ifeq ($(strip $(DEVKITPRO)),)
|
||||
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
|
||||
endif
|
||||
|
||||
TOPDIR ?= $(CURDIR)
|
||||
include $(DEVKITPRO)/libnx/switch_rules
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# TARGET is the name of the output
|
||||
# BUILD is the directory where object files & intermediate files will be placed
|
||||
# SOURCES is a list of directories containing source code
|
||||
# DATA is a list of directories containing data files
|
||||
# INCLUDES is a list of directories containing header files
|
||||
# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm".
|
||||
# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional)
|
||||
#
|
||||
# NO_ICON: if set to anything, do not use icon.
|
||||
# NO_NACP: if set to anything, no .nacp file is generated.
|
||||
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
|
||||
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
|
||||
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
|
||||
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
|
||||
# ICON is the filename of the icon (.jpg), relative to the project folder.
|
||||
# If not set, it attempts to use one of the following (in this order):
|
||||
# - <Project name>.jpg
|
||||
# - icon.jpg
|
||||
# - <libnx folder>/default_icon.jpg
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := $(notdir $(CURDIR))
|
||||
BUILD := build
|
||||
SOURCES := source
|
||||
DATA := data
|
||||
INCLUDES := include
|
||||
EXEFS_SRC := exefs_src
|
||||
APP_TITLE := Reboot to Payload
|
||||
APP_AUTHOR := Atmosphere-NX
|
||||
APP_VERSION := 1.0.0
|
||||
#ROMFS := romfs
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE
|
||||
|
||||
CFLAGS := -g -Wall -O2 -ffunction-sections \
|
||||
$(ARCH) $(DEFINES)
|
||||
|
||||
CFLAGS += $(INCLUDE) -D__SWITCH__
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
|
||||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
|
||||
LIBS := -lnx
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
# include and lib
|
||||
#---------------------------------------------------------------------------------
|
||||
LIBDIRS := $(PORTLIBS) $(LIBNX)
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# no real need to edit anything past this point unless you need to add additional
|
||||
# rules for different file extensions
|
||||
#---------------------------------------------------------------------------------
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||
export TOPDIR := $(CURDIR)
|
||||
|
||||
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
|
||||
|
||||
export DEPSDIR := $(CURDIR)/$(BUILD)
|
||||
|
||||
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# use CXX for linking C++ projects, CC for standard C
|
||||
#---------------------------------------------------------------------------------
|
||||
ifeq ($(strip $(CPPFILES)),)
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CC)
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
#---------------------------------------------------------------------------------
|
||||
export LD := $(CXX)
|
||||
#---------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
|
||||
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
||||
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
||||
|
||||
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||
-I$(CURDIR)/$(BUILD)
|
||||
|
||||
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||
|
||||
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
|
||||
|
||||
ifeq ($(strip $(ICON)),)
|
||||
icons := $(wildcard *.jpg)
|
||||
ifneq (,$(findstring $(TARGET).jpg,$(icons)))
|
||||
export APP_ICON := $(TOPDIR)/$(TARGET).jpg
|
||||
else
|
||||
ifneq (,$(findstring icon.jpg,$(icons)))
|
||||
export APP_ICON := $(TOPDIR)/icon.jpg
|
||||
endif
|
||||
endif
|
||||
else
|
||||
export APP_ICON := $(TOPDIR)/$(ICON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(NO_ICON)),)
|
||||
export NROFLAGS += --icon=$(APP_ICON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(NO_NACP)),)
|
||||
export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp
|
||||
endif
|
||||
|
||||
ifneq ($(APP_TITLEID),)
|
||||
export NACPFLAGS += --titleid=$(APP_TITLEID)
|
||||
endif
|
||||
|
||||
ifneq ($(ROMFS),)
|
||||
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
|
||||
endif
|
||||
|
||||
.PHONY: $(BUILD) clean all
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
all: $(BUILD)
|
||||
|
||||
$(BUILD):
|
||||
@[ -d $@ ] || mkdir -p $@
|
||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
clean:
|
||||
@echo clean ...
|
||||
@rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
.PHONY: all
|
||||
|
||||
DEPENDS := $(OFILES:.o=.d)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# main targets
|
||||
#---------------------------------------------------------------------------------
|
||||
all : $(OUTPUT).nro
|
||||
|
||||
ifeq ($(strip $(NO_NACP)),)
|
||||
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp
|
||||
else
|
||||
$(OUTPUT).nro : $(OUTPUT).elf
|
||||
endif
|
||||
|
||||
$(OUTPUT).elf : $(OFILES)
|
||||
|
||||
$(OFILES_SRC) : $(HFILES_BIN)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# you need a rule like this for each extension you use as binary data
|
||||
#---------------------------------------------------------------------------------
|
||||
%.bin.o %_bin.h : %.bin
|
||||
#---------------------------------------------------------------------------------
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
-include $(DEPENDS)
|
||||
|
||||
#---------------------------------------------------------------------------------------
|
||||
endif
|
||||
#---------------------------------------------------------------------------------------
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <switch.h>
|
||||
#include <string.h>
|
||||
#include "ams_bpc.h"
|
||||
#include "service_guard.h"
|
||||
|
||||
static Service g_amsBpcSrv;
|
||||
|
||||
NX_GENERATE_SERVICE_GUARD(amsBpc);
|
||||
|
||||
Result _amsBpcInitialize(void) {
|
||||
Handle h;
|
||||
Result rc = svcConnectToNamedPort(&h, "bpc:ams"); /* TODO: ams:bpc */
|
||||
if (R_SUCCEEDED(rc)) serviceCreate(&g_amsBpcSrv, h);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void _amsBpcCleanup(void) {
|
||||
serviceClose(&g_amsBpcSrv);
|
||||
}
|
||||
|
||||
Service *amsBpcGetServiceSession(void) {
|
||||
return &g_amsBpcSrv;
|
||||
}
|
||||
|
||||
Result amsBpcSetRebootPayload(const void *src, size_t src_size) {
|
||||
return serviceDispatch(&g_amsBpcSrv, 65001,
|
||||
.buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcMapAlias },
|
||||
.buffers = { { src, src_size } },
|
||||
);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
Result amsBpcInitialize();
|
||||
void amsBpcExit();
|
||||
Service *amsBpcGetServiceSession(void);
|
||||
|
||||
Result amsBpcSetRebootPayload(const void *src, size_t src_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,96 +0,0 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <switch.h>
|
||||
#include "ams_bpc.h"
|
||||
|
||||
#define IRAM_PAYLOAD_MAX_SIZE 0x24000
|
||||
static u8 g_reboot_payload[IRAM_PAYLOAD_MAX_SIZE];
|
||||
|
||||
void userAppExit(void)
|
||||
{
|
||||
amsBpcExit();
|
||||
setsysExit();
|
||||
spsmExit();
|
||||
}
|
||||
|
||||
static void reboot_to_payload(void) {
|
||||
Result rc = amsBpcSetRebootPayload(g_reboot_payload, IRAM_PAYLOAD_MAX_SIZE);
|
||||
if (R_FAILED(rc)) {
|
||||
printf("Failed to set reboot payload: 0x%x\n", rc);
|
||||
}
|
||||
else {
|
||||
spsmShutdown(true);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
consoleInit(NULL);
|
||||
|
||||
padConfigureInput(8, HidNpadStyleSet_NpadStandard);
|
||||
|
||||
PadState pad;
|
||||
padInitializeAny(&pad);
|
||||
|
||||
Result rc = 0;
|
||||
bool can_reboot = true;
|
||||
|
||||
if (R_FAILED(rc = setsysInitialize())) {
|
||||
printf("Failed to initialize set:sys: 0x%x\n", rc);
|
||||
can_reboot = false;
|
||||
}
|
||||
else {
|
||||
SetSysProductModel model;
|
||||
setsysGetProductModel(&model);
|
||||
if (model != SetSysProductModel_Nx && model != SetSysProductModel_Copper) {
|
||||
printf("Reboot to payload cannot be used on a Mariko system\n");
|
||||
can_reboot = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (can_reboot && R_FAILED(rc = spsmInitialize())) {
|
||||
printf("Failed to initialize spsm: 0x%x\n", rc);
|
||||
can_reboot = false;
|
||||
}
|
||||
|
||||
if (can_reboot) {
|
||||
smExit(); //Required to connect to ams:bpc
|
||||
if R_FAILED(rc = amsBpcInitialize()) {
|
||||
printf("Failed to initialize ams:bpc: 0x%x\n", rc);
|
||||
can_reboot = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (can_reboot) {
|
||||
FILE *f = fopen("sdmc:/atmosphere/reboot_payload.bin", "rb");
|
||||
if (f == NULL) {
|
||||
printf("Failed to open atmosphere/reboot_payload.bin!\n");
|
||||
can_reboot = false;
|
||||
} else {
|
||||
fread(g_reboot_payload, 1, sizeof(g_reboot_payload), f);
|
||||
fclose(f);
|
||||
printf("Press [-] to reboot to payload\n");
|
||||
}
|
||||
}
|
||||
|
||||
printf("Press [L] to exit\n");
|
||||
|
||||
// Main loop
|
||||
while(appletMainLoop())
|
||||
{
|
||||
padUpdate(&pad);
|
||||
u64 kDown = padGetButtonsDown(&pad);
|
||||
|
||||
if (can_reboot && (kDown & HidNpadButton_Minus)) {
|
||||
reboot_to_payload();
|
||||
}
|
||||
if (kDown & HidNpadButton_L) { break; } // break in order to return to hbmenu
|
||||
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
consoleExit(NULL);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
#include <switch/types.h>
|
||||
#include <switch/result.h>
|
||||
#include <switch/kernel/mutex.h>
|
||||
#include <switch/sf/service.h>
|
||||
#include <switch/services/sm.h>
|
||||
|
||||
typedef struct ServiceGuard {
|
||||
Mutex mutex;
|
||||
u32 refCount;
|
||||
} ServiceGuard;
|
||||
|
||||
NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g)
|
||||
{
|
||||
mutexLock(&g->mutex);
|
||||
return (g->refCount++) == 0;
|
||||
}
|
||||
|
||||
NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void))
|
||||
{
|
||||
if (R_FAILED(rc)) {
|
||||
cleanupFunc();
|
||||
--g->refCount;
|
||||
}
|
||||
mutexUnlock(&g->mutex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void))
|
||||
{
|
||||
mutexLock(&g->mutex);
|
||||
if (g->refCount && (--g->refCount) == 0)
|
||||
cleanupFunc();
|
||||
mutexUnlock(&g->mutex);
|
||||
}
|
||||
|
||||
#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \
|
||||
\
|
||||
static ServiceGuard g_##name##Guard; \
|
||||
NX_INLINE Result _##name##Initialize _paramdecl; \
|
||||
static void _##name##Cleanup(void); \
|
||||
\
|
||||
Result name##Initialize _paramdecl \
|
||||
{ \
|
||||
Result rc = 0; \
|
||||
if (serviceGuardBeginInit(&g_##name##Guard)) \
|
||||
rc = _##name##Initialize _parampass; \
|
||||
return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \
|
||||
} \
|
||||
\
|
||||
void name##Exit(void) \
|
||||
{ \
|
||||
serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \
|
||||
}
|
||||
|
||||
#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ())
|
||||
Reference in New Issue
Block a user