troposphere: add haze MTP server (#2095)

* troposphere: add haze MTP server

* haze: adjust banner, new lines between class sections, single-statement if

* haze: remove additional newlines between sections

* haze: avoid use of reference out parameter

* haze: console_main_loop: style

* haze: event_reactor, file_system_proxy, ptp: style

* haze: ptp_data_builder, ptp_object_database, ptp_object_heap, results, usb_session: style

* haze: event_reactor, ptp_data_parser, async_usb_server, ptp_object_database, ptp_object_heap: style

* haze: ptp_responder: style

* haze: usb_session: style

* haze: use svc defs from vapours

* haze: misc comments

* haze: fix pointer overflow check

* haze: fix copy paste error

* haze: use alignment, invalidate cached use values in console

* haze: fix stray hex constant

* haze: misc style

* haze: fixes for windows

* haze: add GetObjectPropsSupported, GetObjectPropDesc, GetObjectPropValue

* haze: add SetObjectPropValue

* haze: improve object database API

* haze: improve WriteVariableLengthData readability

* haze: fix directory renames on windows

* haze: ptp_object_database: fix collation

* haze: event_reactor: fix size validation

* haze: ptp_responder: improve documentation

* haze: ptp_responder: avoid unnecessary fs interaction in GetObjectPropValue

* haze: ptp_responder: fix object deletion on windows

* haze: tear down sessions on suspension

* haze: prefer more specific data types

* haze: misc

* haze: fix usb 3.0 support

* haze: improve host-to-device file transfer performance

* haze: report device serial

* haze: move static_assert to requires, fix alignment
This commit is contained in:
liamwhite 2023-04-17 17:08:05 -04:00 committed by GitHub
parent e9b28ab4b1
commit 3b662122f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 3674 additions and 1 deletions

View file

@ -1,4 +1,4 @@
APPLICATIONS := daybreak reboot_to_payload
APPLICATIONS := daybreak haze reboot_to_payload
SUBFOLDERS := $(APPLICATIONS)

226
troposphere/haze/Makefile Normal file
View file

@ -0,0 +1,226 @@
#---------------------------------------------------------------------------------
.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
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++20
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)
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: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
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
else
$(OUTPUT).nro : $(OUTPUT).elf
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
#---------------------------------------------------------------------------------------

BIN
troposphere/haze/icon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

@ -0,0 +1,2 @@
<?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>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,26 @@
/*
* 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/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>

View file

@ -0,0 +1,32 @@
/*
* 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)

View file

@ -0,0 +1,44 @@
/*
* 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)));
}
};
}

View file

@ -0,0 +1,47 @@
/*
* 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_OS_HORIZON
#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;
static constexpr u32 UsbBulkPacketBufferSize = 1_MB;
}

View file

@ -0,0 +1,249 @@
/*
* 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(38;5;196m) "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);
}
};
}

View file

@ -0,0 +1,52 @@
/*
* 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);
};
}

View file

@ -0,0 +1,131 @@
/*
* 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);
}
};
}

View file

@ -0,0 +1,488 @@
/*
* 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_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;
};
}

View file

@ -0,0 +1,174 @@
/*
* 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();
}
};
}

View file

@ -0,0 +1,114 @@
/*
* 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();
}
};
}

View file

@ -0,0 +1,107 @@
/*
* 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);
};
}

View file

@ -0,0 +1,124 @@
/*
* 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) {
/* 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. */
/* ... */
}
};
}

View file

@ -0,0 +1,75 @@
/*
* 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>
namespace haze {
class PtpDataParser;
class PtpResponder final {
private:
AsyncUsbServer m_usb_server;
FileSystemProxy m_fs;
PtpUsbBulkContainer m_request_header;
PtpObjectHeap *m_object_heap;
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_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();
template <typename Data>
Result WriteResponse(PtpResponseCode code, Data &&data);
Result WriteResponse(PtpResponseCode code);
/* 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);
/* MTP operations. */
Result GetObjectPropsSupported(PtpDataParser &dp);
Result GetObjectPropDesc(PtpDataParser &dp);
Result GetObjectPropValue(PtpDataParser &dp);
Result SetObjectPropValue(PtpDataParser &dp);
};
}

View file

@ -0,0 +1,41 @@
/*
* 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);
}

View file

@ -0,0 +1,48 @@
/*
* 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);
};
}

View file

@ -0,0 +1,62 @@
/*
* 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));
}
}

View file

@ -0,0 +1,78 @@
/*
* 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();
}
}
}

View file

@ -0,0 +1,25 @@
/*
* 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) {
/* Run the application. */
haze::ConsoleMainLoop::RunApplication();
/* Return to the loader. */
return 0;
}

View file

@ -0,0 +1,152 @@
/*
* 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;
}
}
}

View file

@ -0,0 +1,69 @@
/*
* 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 20MiB for use by libnx. */
static constexpr size_t LibnxReservedMemorySize = 20_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;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,268 @@
/*
* 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)));
/* Get the device serial number. */
SetSysSerialNumber serial;
R_TRY(setsysInitialize());
R_TRY(setsysGetSerialNumber(std::addressof(serial)));
setsysExit();
/* 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), serial.number));
/* 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;
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();
}
}