mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-09-25 12:53:19 +02:00
troposphere: add haze MTP server
This commit is contained in:
parent
e9b28ab4b1
commit
6a46d3a3e9
@ -1,4 +1,4 @@
|
||||
APPLICATIONS := daybreak reboot_to_payload
|
||||
APPLICATIONS := daybreak haze reboot_to_payload
|
||||
|
||||
SUBFOLDERS := $(APPLICATIONS)
|
||||
|
||||
|
226
troposphere/haze/Makefile
Normal file
226
troposphere/haze/Makefile
Normal 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
BIN
troposphere/haze/icon.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
2
troposphere/haze/icon.svg
Normal file
2
troposphere/haze/icon.svg
Normal 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="b" x1="23.249" x2="11.852" y1="45.658" y2="96.793" gradientTransform="scale(1.4434 .69282)" gradientUnits="userSpaceOnUse"><stop id="stop1479" stop-color="#90B9D9" offset="0"/><stop id="stop1481" stop-color="#554BBA" 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" xlink:href="#b"/><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"/><linearGradient id="linearGradient5199" x1="36.532" x2="36.532" y1="39.898" y2="80.671" gradientTransform="scale(1.0484 .95387)" gradientUnits="userSpaceOnUse" xlink:href="#a"/><linearGradient id="linearGradient5201" x1="23.249" x2="11.852" y1="45.658" y2="96.793" gradientTransform="scale(1.4434 .69282)" gradientUnits="userSpaceOnUse" xlink:href="#b"/></defs><rect id="rect6884" width="182" height="182" ry="0" fill="#37394c" stop-color="#000000"/><rect id="rect1998" x="80.151" y="98.821" width="21.697" height="14.613" fill="url(#linearGradient2147)" stop-color="#000000"/><g id="g1496" transform="matrix(1.4611 0 0 1.4611 32.1 18.644)" fill-rule="evenodd" stroke-width=".68442"><path id="path1492" d="m66.043 63.522c-4.4024-7.6092-14.404-17.621-25.731-24.185-10.448-6.0551-21.562-6.537-29.778-7.3067l22.406-28.406c3.1989-4.0555 9.0925-4.7599 13.164-1.5735 0.58763 0.45991 1.1179 0.98817 1.5797 1.5735l31.603 40.065c1.3355 1.6931 1.3355 4.0764 0 5.7695l-9.6889 12.283c-0.88287 1.1172-2.1972 1.7421-3.5551 1.7797z" fill="url(#linearGradient5199)"/><path id="path1494" d="m55.327 59.799c-4.7798-1.3155-9.815-2.0184-15.015-2.0184-6.0585 0-11.893 0.95408-17.361 2.7196-2.4521 0.79178-4.8304 1.7467-7.1221 2.8521-1.7676 0.44364-3.6705-0.17473-4.8293-1.6438l-9.6621-12.249c-1.3355-1.6931-1.3355-4.0764 0-5.7695l9.5093-12.056c0.13415-8.21e-4 0.2684-0.0012 0.40276-0.0012 23.698 0 44.398 12.786 55.527 31.811-0.60115 0.11208-1.2256 0.10755-1.8375-0.02238-3.0587-1.486-6.2728-2.7038-9.6115-3.6227z" fill="url(#linearGradient5201)"/></g><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)"/></svg>
|
After Width: | Height: | Size: 4.2 KiB |
26
troposphere/haze/include/haze.hpp
Normal file
26
troposphere/haze/include/haze.hpp
Normal 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/filesystem_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>
|
29
troposphere/haze/include/haze/assert.hpp
Normal file
29
troposphere/haze/include/haze/assert.hpp
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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) \
|
||||
{ \
|
||||
if (const bool __tmp_haze_assert_val = static_cast<bool>(expr); (!__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)); \
|
||||
}
|
47
troposphere/haze/include/haze/async_usb_server.hpp
Normal file
47
troposphere/haze/include/haze/async_usb_server.hpp
Normal 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
|
||||
|
||||
#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 {
|
||||
return this->TransferPacketImpl(true, page, size, out_size_transferred);
|
||||
}
|
||||
|
||||
Result WritePacket(void *page, u32 size) const {
|
||||
u32 size_transferred;
|
||||
return this->TransferPacketImpl(false, page, size, std::addressof(size_transferred));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
42
troposphere/haze/include/haze/common.hpp
Normal file
42
troposphere/haze/include/haze/common.hpp
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 <algorithm>
|
||||
#include <array>
|
||||
#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>
|
||||
|
||||
namespace haze {
|
||||
|
||||
using namespace ::ams::literals;
|
||||
using namespace ::ams;
|
||||
|
||||
using Result = ::ams::Result;
|
||||
|
||||
static constexpr size_t UsbBulkPacketBufferSize = 1_MB;
|
||||
|
||||
}
|
150
troposphere/haze/include/haze/console_main_loop.hpp
Normal file
150
troposphere/haze/include/haze/console_main_loop.hpp
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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 = 33333333;
|
||||
|
||||
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) {
|
||||
Waiter cancel_waiter = waiterForUEvent(std::addressof(m_cancel_event));
|
||||
Result rc = waitObjects(std::addressof(idx), std::addressof(cancel_waiter), 1, FrameDelayNs);
|
||||
|
||||
if (R_SUCCEEDED(rc)) break;
|
||||
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) {
|
||||
m_reactor = reactor;
|
||||
m_object_heap = object_heap;
|
||||
|
||||
AppletType applet_type = appletGetAppletType();
|
||||
m_is_applet_mode = applet_type != AppletType_Application && applet_type != AppletType_SystemApplication;
|
||||
|
||||
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. */
|
||||
R_TRY(threadCreate(std::addressof(m_thread), ConsoleMainLoop::Run, this, nullptr, 0x1000, 0x2b, -2));
|
||||
|
||||
/* 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() {
|
||||
ueventSignal(std::addressof(m_cancel_event));
|
||||
|
||||
HAZE_R_ABORT_UNLESS(threadWaitForExit(std::addressof(m_thread)));
|
||||
|
||||
HAZE_R_ABORT_UNLESS(threadClose(std::addressof(m_thread)));
|
||||
|
||||
m_reactor->RemoveConsumer(this);
|
||||
}
|
||||
|
||||
private:
|
||||
void RedrawConsole() {
|
||||
u32 heap_used = m_object_heap->GetSizeUsed();
|
||||
u32 heap_total = m_object_heap->GetSizeTotal();
|
||||
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;
|
||||
}
|
||||
|
||||
m_last_heap_used = heap_used;
|
||||
m_last_heap_total = heap_total;
|
||||
|
||||
const char *used_unit = "B";
|
||||
if (heap_used > 1024) { heap_used >>= 10; used_unit = "KiB"; }
|
||||
if (heap_used > 1024) { heap_used >>= 10; used_unit = "MiB"; }
|
||||
|
||||
const char *total_unit = "B";
|
||||
if (heap_total > 1024) { heap_total >>= 10; total_unit = "KiB"; }
|
||||
if (heap_total > 1024) { heap_total >>= 10; total_unit = "MiB"; }
|
||||
|
||||
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) {
|
||||
printf("\n" CONSOLE_ESC(38;5;196m) "Applet Mode" CONSOLE_ESC(0m) "\n");
|
||||
}
|
||||
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
|
||||
protected:
|
||||
void ProcessEvent() override {
|
||||
this->RedrawConsole();
|
||||
|
||||
/* Check buttons. */
|
||||
padUpdate(std::addressof(m_pad));
|
||||
|
||||
if (padGetButtonsDown(std::addressof(m_pad)) & HidNpadButton_Plus) {
|
||||
m_reactor->RequestStop();
|
||||
}
|
||||
|
||||
/* Pump applet event loop. */
|
||||
if (!appletMainLoop()) {
|
||||
m_reactor->RequestStop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
57
troposphere/haze/include/haze/event_reactor.hpp
Normal file
57
troposphere/haze/include/haze/event_reactor.hpp
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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[MAX_WAIT_OBJECTS];
|
||||
Waiter m_waiters[MAX_WAIT_OBJECTS];
|
||||
|
||||
s32 m_num_wait_objects;
|
||||
bool m_stop_requested;
|
||||
|
||||
public:
|
||||
constexpr explicit EventReactor() : m_consumers(), m_waiters(), m_num_wait_objects(), m_stop_requested() { /* ... */ }
|
||||
|
||||
bool AddConsumer(EventConsumer *consumer, Waiter waiter);
|
||||
void RemoveConsumer(EventConsumer *consumer);
|
||||
|
||||
public:
|
||||
void RequestStop() { m_stop_requested = true; }
|
||||
bool GetStopRequested() const { return m_stop_requested; }
|
||||
|
||||
public:
|
||||
template <typename... Args>
|
||||
Result WaitFor(s32 *out_arg_waiter, Args &&... arg_waiters) {
|
||||
const Waiter arg_waiter_array[] = {arg_waiters...};
|
||||
return this->WaitForImpl(out_arg_waiter, sizeof...(Args), arg_waiter_array);
|
||||
}
|
||||
|
||||
private:
|
||||
Result WaitForImpl(s32 *out_arg_waiter, s32 num_arg_waiters, const Waiter *arg_waiters);
|
||||
};
|
||||
|
||||
}
|
123
troposphere/haze/include/haze/filesystem_proxy.hpp
Normal file
123
troposphere/haze/include/haze/filesystem_proxy.hpp
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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) {
|
||||
Result rc = func(std::forward<Args>(args)...);
|
||||
|
||||
R_UNLESS(!m_reactor->GetStopRequested(), haze::ResultStopRequested());
|
||||
|
||||
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 OpenFile(const char *path, u32 mode, FsFile *out_file) {
|
||||
R_RETURN(this->ForwardResult(fsFsOpenFile, m_filesystem, path, mode, out_file));
|
||||
}
|
||||
|
||||
Result FileGetSize(FsFile *file, s64 *out_size) {
|
||||
R_RETURN(this->ForwardResult(fsFileGetSize, file, out_size));
|
||||
}
|
||||
|
||||
Result FileSetSize(FsFile *file, s64 size) {
|
||||
R_RETURN(this->ForwardResult(fsFileSetSize, file, size));
|
||||
}
|
||||
|
||||
Result FileRead(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 FileWrite(FsFile *file, s64 off, const void *buf, u64 write_size, u32 option) {
|
||||
R_RETURN(this->ForwardResult(fsFileWrite, file, off, buf, write_size, option));
|
||||
}
|
||||
|
||||
void FileClose(FsFile *file) {
|
||||
fsFileClose(file);
|
||||
}
|
||||
|
||||
Result CreateDirectory(const char* path) {
|
||||
R_RETURN(this->ForwardResult(fsFsCreateDirectory, m_filesystem, path));
|
||||
}
|
||||
|
||||
Result DeleteDirectory(const char* path) {
|
||||
R_RETURN(this->ForwardResult(fsFsDeleteDirectory, m_filesystem, path));
|
||||
}
|
||||
|
||||
Result OpenDirectory(const char *path, u32 mode, FsDir *out_dir) {
|
||||
R_RETURN(this->ForwardResult(fsFsOpenDirectory, m_filesystem, path, mode, out_dir));
|
||||
}
|
||||
|
||||
Result DirectoryRead(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 DirectoryGetEntryCount(FsDir *d, s64 *out_count) {
|
||||
R_RETURN(this->ForwardResult(fsDirGetEntryCount, d, out_count));
|
||||
}
|
||||
|
||||
void DirectoryClose(FsDir *d) {
|
||||
fsDirClose(d);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
267
troposphere/haze/include/haze/ptp.hpp
Normal file
267
troposphere/haze/include/haze/ptp.hpp
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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 size_t PtpUsbBulkHighSpeedMaxPacketLength = 0x200;
|
||||
constexpr size_t PtpUsbBulkHeaderLength = 2 * sizeof(uint32_t) + 2 * sizeof(uint16_t);
|
||||
constexpr size_t PtpStringMaxLength = 255;
|
||||
|
||||
enum PtpUsbBulkContainerType : u16 {
|
||||
PtpUsbBulkContainerType_Undefined = 0,
|
||||
PtpUsbBulkContainerType_Command = 1,
|
||||
PtpUsbBulkContainerType_Data = 2,
|
||||
PtpUsbBulkContainerType_Response = 3,
|
||||
PtpUsbBulkContainerType_Event = 4,
|
||||
};
|
||||
|
||||
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_MtpInvalid_ObjectPropCode = 0xA801,
|
||||
PtpResponseCode_MtpInvalid_ObjectProp_Format = 0xA802,
|
||||
PtpResponseCode_MtpInvalid_ObjectProp_Value = 0xA803,
|
||||
PtpResponseCode_MtpInvalid_ObjectReference = 0xA804,
|
||||
PtpResponseCode_MtpInvalid_Dataset = 0xA806,
|
||||
PtpResponseCode_MtpSpecification_By_Group_Unsupported = 0xA807,
|
||||
PtpResponseCode_MtpSpecification_By_Depth_Unsupported = 0xA808,
|
||||
PtpResponseCode_MtpObject_Too_Large = 0xA809,
|
||||
PtpResponseCode_MtpObjectProp_Not_Supported = 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 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 = 0x0,
|
||||
PtpAssociationType_GenericFolder = 0x1,
|
||||
};
|
||||
|
||||
enum PtpGetObjectHandles : u32 {
|
||||
PtpGetObjectHandles_AllFormats = 0x0,
|
||||
PtpGetObjectHandles_AllAssocs = 0x0,
|
||||
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 {
|
||||
uint32_t length;
|
||||
uint16_t type;
|
||||
uint16_t code;
|
||||
uint32_t trans_id;
|
||||
};
|
||||
static_assert(sizeof(PtpUsbBulkContainer) == PtpUsbBulkHeaderLength);
|
||||
|
||||
struct PtpNewObjectInfo {
|
||||
uint32_t storage_id;
|
||||
uint32_t parent_object_id;
|
||||
uint32_t object_id;
|
||||
};
|
||||
}
|
166
troposphere/haze/include/haze/ptp_data_builder.hpp
Normal file
166
troposphere/haze/include/haze/ptp_data_builder.hpp
Normal file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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;
|
||||
size_t m_transmitted_size;
|
||||
size_t m_offset;
|
||||
u8 *m_data;
|
||||
bool m_disabled;
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
constexpr static size_t Strlen(const T *str) {
|
||||
const T *s = str;
|
||||
for (; *s; ++s) {
|
||||
}
|
||||
return s - str;
|
||||
}
|
||||
|
||||
Result Flush() {
|
||||
ON_SCOPE_EXIT {
|
||||
m_transmitted_size += m_offset;
|
||||
m_offset = 0;
|
||||
};
|
||||
|
||||
if (m_disabled) {
|
||||
R_SUCCEED();
|
||||
} else {
|
||||
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 (m_transmitted_size % PtpUsbBulkHighSpeedMaxPacketLength == 0) {
|
||||
/* 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. */
|
||||
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 we cannot write more bytes now, flush. */
|
||||
if (m_offset == haze::UsbBulkPacketBufferSize) {
|
||||
R_TRY(this->Flush());
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Result Add(T value) {
|
||||
const auto bytes = std::bit_cast<std::array<u8, sizeof(T)>>(value);
|
||||
|
||||
R_RETURN(this->AddBuffer(bytes.data(), bytes.size()));
|
||||
}
|
||||
|
||||
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) {
|
||||
m_disabled = true;
|
||||
R_TRY(func());
|
||||
R_TRY(this->Commit());
|
||||
|
||||
const size_t data_size = m_transmitted_size;
|
||||
|
||||
m_transmitted_size = 0;
|
||||
m_offset = 0;
|
||||
m_disabled = false;
|
||||
|
||||
R_TRY(this->AddDataHeader(request, data_size));
|
||||
R_TRY(func());
|
||||
R_TRY(this->Commit());
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Result AddString(const T *str) {
|
||||
u8 len = static_cast<u8>(std::min(Strlen(str), PtpStringMaxLength - 1));
|
||||
if (len > 0) {
|
||||
/* Write 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 *ary, u32 count) {
|
||||
R_TRY(this->Add<u32>(count));
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
R_TRY(this->Add<T>(ary[i]));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
112
troposphere/haze/include/haze/ptp_data_parser.hpp
Normal file
112
troposphere/haze/include/haze/ptp_data_parser.hpp
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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_eos;
|
||||
|
||||
private:
|
||||
Result Flush() {
|
||||
R_UNLESS(!m_eos, haze::ResultEndOfTransmission());
|
||||
|
||||
m_received_size = 0;
|
||||
m_offset = 0;
|
||||
|
||||
ON_SCOPE_EXIT { m_eos = 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_eos() { /* ... */ }
|
||||
|
||||
Result Finalize() {
|
||||
/* Read until the transmission completes. */
|
||||
while (true) {
|
||||
Result rc = this->Flush();
|
||||
|
||||
R_SUCCEED_IF(m_eos || haze::ResultEndOfTransmission::Includes(rc));
|
||||
R_TRY(rc);
|
||||
}
|
||||
}
|
||||
|
||||
Result ReadBuffer(u8 *buffer, size_t count, size_t *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) {
|
||||
size_t read_count;
|
||||
std::array<u8, sizeof(T)> bytes = {};
|
||||
|
||||
R_TRY(this->ReadBuffer(bytes.data(), bytes.size(), std::addressof(read_count)));
|
||||
|
||||
out_t = std::bit_cast<T>(bytes);
|
||||
|
||||
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(len));
|
||||
|
||||
/* Read characters one by one. */
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
u16 chr;
|
||||
R_TRY(this->Read(chr));
|
||||
|
||||
*out_string++ = static_cast<char>(chr);
|
||||
}
|
||||
|
||||
/* Write null terminator. */
|
||||
*out_string++ = 0;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
99
troposphere/haze/include/haze/ptp_object_database.hpp
Normal file
99
troposphere/haze/include/haze/ptp_object_database.hpp
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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 {
|
||||
|
||||
class PtpObjectDatabase {
|
||||
public:
|
||||
struct ObjectNode {
|
||||
util::IntrusiveRedBlackTreeNode m_object_name_to_id_node;
|
||||
util::IntrusiveRedBlackTreeNode m_object_id_to_name_node;
|
||||
u32 m_parent_id;
|
||||
u32 m_object_id;
|
||||
char m_name[];
|
||||
|
||||
explicit ObjectNode(char *name, u32 parent_id, u32 object_id) : m_object_name_to_id_node(), m_object_id_to_name_node(), m_parent_id(parent_id), m_object_id(object_id) { /* ... */ }
|
||||
|
||||
const char *GetName() const { return m_name; }
|
||||
u32 GetParentId() const { return m_parent_id; }
|
||||
u32 GetObjectId() const { return m_object_id; }
|
||||
|
||||
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, ObjectNode> || std::same_as<T, RedBlackKeyType>)
|
||||
static constexpr int Compare(const T &lhs, const ObjectNode &rhs) {
|
||||
return std::strcmp(lhs.GetName(), rhs.GetName());
|
||||
}
|
||||
};
|
||||
|
||||
struct IdComparator {
|
||||
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, ObjectNode> || std::same_as<T, RedBlackKeyType>)
|
||||
static constexpr int Compare(const T &lhs, const ObjectNode &rhs) {
|
||||
return lhs.GetObjectId() - rhs.GetObjectId();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
private:
|
||||
using ObjectNameToIdTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&ObjectNode::m_object_name_to_id_node>;
|
||||
using ObjectNameToIdTree = ObjectNameToIdTreeTraits::TreeType<ObjectNode::NameComparator>;
|
||||
|
||||
using ObjectIdToNameTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&ObjectNode::m_object_id_to_name_node>;
|
||||
using ObjectIdToNameTree = ObjectIdToNameTreeTraits::TreeType<ObjectNode::IdComparator>;
|
||||
|
||||
PtpObjectHeap *m_object_heap;
|
||||
ObjectNameToIdTree m_name_to_object_id;
|
||||
ObjectIdToNameTree m_object_id_to_name;
|
||||
u32 m_next_object_id;
|
||||
|
||||
public:
|
||||
constexpr explicit PtpObjectDatabase() : m_object_heap(), m_name_to_object_id(), m_object_id_to_name(), m_next_object_id() { /* ... */ }
|
||||
|
||||
void Initialize(PtpObjectHeap *object_heap);
|
||||
void Finalize();
|
||||
|
||||
public:
|
||||
/* Object database API. */
|
||||
Result AddObjectId(const char *parent_name, const char *name, u32 *out_object_id, u32 parent_id, u32 desired_object_id = 0);
|
||||
void RemoveObjectId(u32 object_id);
|
||||
ObjectNode *GetObject(u32 object_id);
|
||||
};
|
||||
|
||||
}
|
128
troposphere/haze/include/haze/ptp_object_heap.hpp
Normal file
128
troposphere/haze/include/haze/ptp_object_heap.hpp
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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 arena 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;
|
||||
|
||||
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 GetSizeTotal() const {
|
||||
return m_heap_block_size * NumHeapBlocks;
|
||||
}
|
||||
|
||||
constexpr size_t GetSizeUsed() 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 {
|
||||
if (this->GetNextAddress() + n < this->GetNextAddress()) return false;
|
||||
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) {
|
||||
if (n + 7 < n) return nullptr;
|
||||
|
||||
/* Round up the amount to a multiple of 8. */
|
||||
n = (n + 7) & ~7ull;
|
||||
|
||||
/* 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. */
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
constexpr T *New(Args&&... args) {
|
||||
T *t = static_cast<T *>(this->Allocate(sizeof(T)));
|
||||
std::construct_at(t, std::forward<Args>(args)...);
|
||||
return t;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr void Delete(T *t) {
|
||||
std::destroy_at(t);
|
||||
this->Deallocate(t, sizeof(T));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
71
troposphere/haze/include/haze/ptp_responder.hpp
Normal file
71
troposphere/haze/include/haze/ptp_responder.hpp
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 HandleRequest();
|
||||
|
||||
private:
|
||||
/* Request handling. */
|
||||
Result HandleRequestImpl();
|
||||
Result HandleCommandRequest(PtpDataParser &dp);
|
||||
void ForceCloseSession();
|
||||
|
||||
template <typename Data>
|
||||
Result WriteResponse(PtpResponseCode code, Data &&data);
|
||||
Result WriteResponse(PtpResponseCode code);
|
||||
|
||||
/* 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);
|
||||
};
|
||||
|
||||
}
|
39
troposphere/haze/include/haze/results.hpp
Normal file
39
troposphere/haze/include/haze/results.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
/* Please 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(EndOfTransmission, 5);
|
||||
R_DEFINE_ERROR_RESULT(UnknownPacketType, 6);
|
||||
R_DEFINE_ERROR_RESULT(SessionNotOpen, 7);
|
||||
R_DEFINE_ERROR_RESULT(OutOfMemory, 8);
|
||||
R_DEFINE_ERROR_RESULT(ObjectNotFound, 9);
|
||||
R_DEFINE_ERROR_RESULT(StorageNotFound, 10);
|
||||
R_DEFINE_ERROR_RESULT(OperationNotSupported, 11);
|
||||
R_DEFINE_ERROR_RESULT(UnknownRequestType, 12);
|
||||
R_DEFINE_ERROR_RESULT(GeneralFailure, 13);
|
||||
|
||||
}
|
51
troposphere/haze/include/haze/usb_session.hpp
Normal file
51
troposphere/haze/include/haze/usb_session.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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,
|
||||
UsbSessionEndpoint_Write,
|
||||
UsbSessionEndpoint_Interrupt,
|
||||
UsbSessionEndpoint_Count,
|
||||
};
|
||||
|
||||
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, size_t size, u32 *out_urb_id);
|
||||
Result GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_transferred_size);
|
||||
Result SetZeroLengthTermination(bool enable);
|
||||
};
|
||||
|
||||
}
|
62
troposphere/haze/source/async_usb_server.cpp
Normal file
62
troposphere/haze/source/async_usb_server.cpp
Normal 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 {
|
||||
|
||||
static constinit UsbSession s_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(s_usb_session.Initialize(interface_info, id_vendor, id_product));
|
||||
R_TRY(s_usb_session.SetZeroLengthTermination(false));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void AsyncUsbServer::Finalize() {
|
||||
s_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 (!s_usb_session.GetConfigured()) {
|
||||
R_TRY(m_reactor->WaitFor(std::addressof(waiter_idx), waiterForEvent(usbDsGetStateChangeEvent())));
|
||||
R_TRY(eventClear(usbDsGetStateChangeEvent()));
|
||||
|
||||
R_THROW(haze::ResultNotConfigured());
|
||||
}
|
||||
|
||||
UsbSessionEndpoint ep = read ? UsbSessionEndpoint_Read : UsbSessionEndpoint_Write;
|
||||
R_TRY(s_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(s_usb_session.GetCompletionEvent(ep))));
|
||||
|
||||
/* Return what we transferred. */
|
||||
R_RETURN(s_usb_session.GetTransferResult(ep, urb_id, out_size_transferred));
|
||||
}
|
||||
|
||||
}
|
76
troposphere/haze/source/event_reactor.cpp
Normal file
76
troposphere/haze/source/event_reactor.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 <= MAX_WAIT_OBJECTS);
|
||||
|
||||
/* 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 input_index = 0, output_index = 0;
|
||||
|
||||
/* Remove the consumer. */
|
||||
for (; 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, s32 num_arg_waiters, const Waiter *arg_waiters) {
|
||||
HAZE_ASSERT(m_num_wait_objects + num_arg_waiters <= MAX_WAIT_OBJECTS);
|
||||
|
||||
while (true) {
|
||||
R_UNLESS(!m_stop_requested, haze::ResultStopRequested());
|
||||
|
||||
/* 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, -1));
|
||||
|
||||
/* If this refers to a waiter in the argument list, 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
42
troposphere/haze/source/main.cpp
Normal file
42
troposphere/haze/source/main.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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[]) {
|
||||
haze::PtpObjectHeap ptp_object_heap;
|
||||
|
||||
haze::EventReactor event_reactor;
|
||||
haze::PtpResponder ptp_responder;
|
||||
haze::ConsoleMainLoop console_main_loop;
|
||||
|
||||
consoleInit(NULL);
|
||||
appletSetAutoSleepDisabled(true);
|
||||
|
||||
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));
|
||||
|
||||
while (!event_reactor.GetStopRequested()) {
|
||||
ptp_responder.HandleRequest();
|
||||
}
|
||||
|
||||
console_main_loop.Finalize();
|
||||
ptp_responder.Finalize();
|
||||
|
||||
appletSetAutoSleepDisabled(false);
|
||||
consoleExit(NULL);
|
||||
return 0;
|
||||
}
|
103
troposphere/haze/source/ptp_object_database.cpp
Normal file
103
troposphere/haze/source/ptp_object_database.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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_to_object_id));
|
||||
std::construct_at(std::addressof(m_object_id_to_name));
|
||||
|
||||
m_next_object_id = 1;
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::Finalize() {
|
||||
std::destroy_at(std::addressof(m_object_id_to_name));
|
||||
std::destroy_at(std::addressof(m_name_to_object_id));
|
||||
|
||||
m_next_object_id = 0;
|
||||
|
||||
m_object_heap->Finalize();
|
||||
m_object_heap = nullptr;
|
||||
}
|
||||
|
||||
Result PtpObjectDatabase::AddObjectId(const char *parent_name, const char *name, u32 *out_object_id, u32 parent_id, u32 desired_object_id) {
|
||||
ObjectNode *node = nullptr;
|
||||
|
||||
/* Calculate length of the name. */
|
||||
const size_t parent_name_len = std::strlen(parent_name);
|
||||
const size_t name_len = std::strlen(name) + 1;
|
||||
const size_t alloc_len = parent_name_len + 1 + name_len;
|
||||
|
||||
/* Allocate memory for the node. */
|
||||
node = reinterpret_cast<ObjectNode *>(m_object_heap->Allocate(sizeof(ObjectNode) + alloc_len));
|
||||
R_UNLESS(node != nullptr, haze::ResultOutOfMemory());
|
||||
|
||||
/* Ensure we maintain a clean state on failure. */
|
||||
auto guard = SCOPE_GUARD { m_object_heap->Deallocate(node, sizeof(ObjectNode) + alloc_len); };
|
||||
|
||||
/* Take ownership of the name. */
|
||||
std::strncpy(node->m_name, parent_name, parent_name_len + 1);
|
||||
node->m_name[parent_name_len] = '/';
|
||||
std::strncpy(node->m_name + parent_name_len + 1, name, name_len);
|
||||
|
||||
/* Check if an object with this name already exists. If it does, we can just return it here. */
|
||||
auto it = m_name_to_object_id.find_key(node->m_name);
|
||||
if (it != m_name_to_object_id.end()) {
|
||||
if (out_object_id) *out_object_id = it->GetObjectId();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* Persist the reference to the node. */
|
||||
guard.Cancel();
|
||||
|
||||
/* Insert node into trees. */
|
||||
node->m_parent_id = parent_id;
|
||||
node->m_object_id = desired_object_id == 0 ? m_next_object_id++ : desired_object_id;
|
||||
m_name_to_object_id.insert(*node);
|
||||
m_object_id_to_name.insert(*node);
|
||||
|
||||
/* Set output. */
|
||||
if (out_object_id) *out_object_id = node->GetObjectId();
|
||||
|
||||
/* We succeeded. */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::RemoveObjectId(u32 object_id) {
|
||||
/* Find in forward mapping. */
|
||||
auto it = m_object_id_to_name.find_key(object_id);
|
||||
if (it == m_object_id_to_name.end()) return;
|
||||
|
||||
/* Free the node. */
|
||||
ObjectNode *node = std::addressof(*it);
|
||||
m_object_id_to_name.erase(m_object_id_to_name.iterator_to(*node));
|
||||
m_name_to_object_id.erase(m_name_to_object_id.iterator_to(*node));
|
||||
m_object_heap->Deallocate(node, sizeof(ObjectNode) + std::strlen(node->GetName()) + 1);
|
||||
}
|
||||
|
||||
PtpObjectDatabase::ObjectNode *PtpObjectDatabase::GetObject(u32 object_id) {
|
||||
/* Find in forward mapping. */
|
||||
auto it = m_object_id_to_name.find_key(object_id);
|
||||
if (it == m_object_id_to_name.end()) return nullptr;
|
||||
|
||||
return std::addressof(*it);
|
||||
}
|
||||
|
||||
}
|
66
troposphere/haze/source/ptp_object_heap.cpp
Normal file
66
troposphere/haze/source/ptp_object_heap.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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 LibnxReservedMem = 20_MB;
|
||||
|
||||
}
|
||||
|
||||
void PtpObjectHeap::Initialize() {
|
||||
size_t mem_used = 0;
|
||||
|
||||
/* Skip re-initialization if we are currently initialized. */
|
||||
if (m_heap_block_size != 0) return;
|
||||
|
||||
/* Estimate how much memory we can reserve. */
|
||||
HAZE_R_ABORT_UNLESS(svcGetInfo(std::addressof(mem_used), InfoType_UsedMemorySize, CUR_PROCESS_HANDLE, 0));
|
||||
HAZE_ASSERT(mem_used > LibnxReservedMem);
|
||||
mem_used -= LibnxReservedMem;
|
||||
|
||||
/* Take the rest for ourselves. */
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
698
troposphere/haze/source/ptp_responder.cpp
Normal file
698
troposphere/haze/source/ptp_responder.cpp
Normal file
@ -0,0 +1,698 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
#define ARRAY_LEN(x) (sizeof(x)/sizeof(x[0]))
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
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 = "Switch";
|
||||
constexpr auto MtpDeviceVersion = "1.0.0";
|
||||
constexpr auto MtpDeviceSerialNumber = "SerialNumber";
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
constexpr PtpEventCode SupportedEventCodes[] = {};
|
||||
constexpr PtpDevicePropertyCode SupportedPropertyCodes[] = {};
|
||||
constexpr PtpObjectFormatCode SupportedCaptureFormats[] = {};
|
||||
constexpr PtpObjectFormatCode SupportedPlaybackFormats[] = {};
|
||||
|
||||
constexpr 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 s64 DirectoryReadSize = 32;
|
||||
constexpr u64 FsBufferSize = haze::UsbBulkPacketBufferSize;
|
||||
|
||||
static constinit char s_filename_str[PtpStringMaxLength + 1];
|
||||
static constinit char s_capture_date_str[PtpStringMaxLength + 1];
|
||||
static constinit char s_modification_date_str[PtpStringMaxLength + 1];
|
||||
static constinit char s_keywords_str[PtpStringMaxLength + 1];
|
||||
|
||||
static constinit FsDirectoryEntry s_dir_entries[DirectoryReadSize] = {};
|
||||
static constinit u8 s_fs_buffer[FsBufferSize] = {};
|
||||
|
||||
alignas(0x1000) static constinit u8 s_bulk_write_buffer[haze::UsbBulkPacketBufferSize] = {};
|
||||
alignas(0x1000) static constinit u8 s_bulk_read_buffer[haze::UsbBulkPacketBufferSize] = {};
|
||||
}
|
||||
|
||||
Result PtpResponder::Initialize(EventReactor *reactor, PtpObjectHeap *object_heap) {
|
||||
m_object_heap = object_heap;
|
||||
|
||||
/* 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::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::ResultStorageNotFound) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_InvalidStorageId));
|
||||
}
|
||||
R_CATCH(haze::ResultObjectNotFound) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_InvalidObjectHandle));
|
||||
}
|
||||
R_CATCH(fs::ResultPathNotFound, fs::ResultPathAlreadyExists, fs::ResultTargetLocked, fs::ResultDirectoryNotEmpty, fs::ResultNotEnoughFreeSpaceSdCard) {
|
||||
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
|
||||
}
|
||||
R_CATCH_ALL() {
|
||||
R_THROW(haze::ResultGeneralFailure());
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result PtpResponder::HandleRequestImpl() {
|
||||
PtpDataParser dp(s_bulk_read_buffer, std::addressof(m_usb_server));
|
||||
R_TRY(dp.Read(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;
|
||||
default: R_THROW(haze::ResultOperationNotSupported());
|
||||
}
|
||||
}
|
||||
|
||||
void PtpResponder::ForceCloseSession() {
|
||||
if (m_session_open) {
|
||||
m_session_open = false;
|
||||
m_object_database.Finalize();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Data>
|
||||
Result PtpResponder::WriteResponse(PtpResponseCode code, Data &&data) {
|
||||
PtpDataBuilder db(s_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
R_TRY(db.AddResponseHeader(m_request_header, code, sizeof(Data)));
|
||||
R_TRY(db.Add(data));
|
||||
R_RETURN(db.Commit());
|
||||
}
|
||||
|
||||
Result PtpResponder::WriteResponse(PtpResponseCode code) {
|
||||
PtpDataBuilder db(s_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
R_TRY(db.AddResponseHeader(m_request_header, code, 0));
|
||||
R_RETURN(db.Commit());
|
||||
}
|
||||
|
||||
Result PtpResponder::GetDeviceInfo(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(s_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
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, ARRAY_LEN(SupportedOperationCodes)));
|
||||
R_TRY(db.AddArray(SupportedEventCodes, ARRAY_LEN(SupportedEventCodes)));
|
||||
R_TRY(db.AddArray(SupportedPropertyCodes, ARRAY_LEN(SupportedPropertyCodes)));
|
||||
R_TRY(db.AddArray(SupportedCaptureFormats, ARRAY_LEN(SupportedCaptureFormats)));
|
||||
R_TRY(db.AddArray(SupportedPlaybackFormats, ARRAY_LEN(SupportedPlaybackFormats)));
|
||||
R_TRY(db.AddString(MtpDeviceManufacturer));
|
||||
R_TRY(db.AddString(MtpDeviceModel));
|
||||
R_TRY(db.AddString(MtpDeviceVersion));
|
||||
R_TRY(db.AddString(MtpDeviceSerialNumber));
|
||||
|
||||
R_SUCCEED();
|
||||
}));
|
||||
|
||||
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 with hardcoded storage IDs. */
|
||||
m_session_open = true;
|
||||
m_object_database.Initialize(m_object_heap);
|
||||
R_TRY(m_object_database.AddObjectId("", "", nullptr, PtpGetObjectHandles_RootParent, StorageId_SdmcFs));
|
||||
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::CloseSession(PtpDataParser &dp) {
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
this->ForceCloseSession();
|
||||
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetStorageIds(PtpDataParser &dp) {
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
PtpDataBuilder db(s_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
||||
R_RETURN(db.AddArray(SupportedStorageIds, ARRAY_LEN(SupportedStorageIds)));
|
||||
}));
|
||||
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetStorageInfo(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(s_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(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::ResultStorageNotFound());
|
||||
}
|
||||
|
||||
/* Write the result. */
|
||||
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();
|
||||
}));
|
||||
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObjectHandles(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(s_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(storage_id));
|
||||
R_TRY(dp.Read(object_format_code));
|
||||
R_TRY(dp.Read(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 *fileobj = m_object_database.GetObject(association_object_handle);
|
||||
R_UNLESS(fileobj != nullptr, haze::ResultObjectNotFound());
|
||||
|
||||
/* Try to read the object as a directory. */
|
||||
FsDir dir;
|
||||
R_TRY(m_fs.OpenDirectory(fileobj->GetName(), FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, std::addressof(dir)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.DirectoryClose(std::addressof(dir)); };
|
||||
|
||||
/* Count how many entries are in the directory. */
|
||||
s64 entry_count = 0;
|
||||
R_TRY(m_fs.DirectoryGetEntryCount(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)));
|
||||
|
||||
/* 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.DirectoryRead(std::addressof(dir), std::addressof(read_count), DirectoryReadSize, s_dir_entries));
|
||||
|
||||
/* Write to output. */
|
||||
for (s64 i = 0; i < read_count; i++) {
|
||||
u32 handle;
|
||||
R_TRY(m_object_database.AddObjectId(fileobj->GetName(), s_dir_entries[i].name, std::addressof(handle), fileobj->GetObjectId()));
|
||||
R_TRY(db.Add(handle));
|
||||
}
|
||||
|
||||
/* If we read fewer than the batch size, we're done. */
|
||||
if (read_count < DirectoryReadSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
R_TRY(db.Commit());
|
||||
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObjectInfo(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(s_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Get the object ID the client requested info for. */
|
||||
u32 object_id;
|
||||
R_TRY(dp.Read(object_id));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto *fileobj = m_object_database.GetObject(object_id);
|
||||
R_UNLESS(fileobj != nullptr, haze::ResultObjectNotFound());
|
||||
|
||||
/* 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(fileobj->GetName(), std::addressof(entry_type)));
|
||||
|
||||
/* Get the size of the file. */
|
||||
s64 size = 0;
|
||||
if (entry_type == FsDirEntryType_File) {
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(fileobj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.FileClose(std::addressof(file)); };
|
||||
|
||||
R_TRY(m_fs.FileGetSize(std::addressof(file), std::addressof(size)));
|
||||
}
|
||||
|
||||
object_info.filename = std::strrchr(fileobj->GetName(), '/') + 1;
|
||||
object_info.object_compressed_size = size;
|
||||
object_info.parent_object = fileobj->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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}));
|
||||
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::GetObject(PtpDataParser &dp) {
|
||||
PtpDataBuilder db(s_bulk_write_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Get the object ID the client requested. */
|
||||
u32 object_id;
|
||||
R_TRY(dp.Read(object_id));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto *fileobj = m_object_database.GetObject(object_id);
|
||||
R_UNLESS(fileobj != nullptr, haze::ResultObjectNotFound());
|
||||
|
||||
/* Lock the object as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(fileobj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.FileClose(std::addressof(file)); };
|
||||
|
||||
/* Get the file's size. */
|
||||
s64 size = 0, offset = 0;
|
||||
R_TRY(m_fs.FileGetSize(std::addressof(file), std::addressof(size)));
|
||||
|
||||
/* Send the header and file size. */
|
||||
R_TRY(db.AddDataHeader(m_request_header, size));
|
||||
|
||||
while (true) {
|
||||
/* Get the next batch. */
|
||||
size_t bytes_read;
|
||||
R_TRY(m_fs.FileRead(std::addressof(file), offset, s_fs_buffer, FsBufferSize, FsReadOption_None, std::addressof(bytes_read)));
|
||||
|
||||
offset += bytes_read;
|
||||
|
||||
/* Write to output. */
|
||||
R_TRY(db.AddBuffer(s_fs_buffer, bytes_read));
|
||||
|
||||
/* If we read fewer bytes than the batch size, we're done. */
|
||||
if (bytes_read < FsBufferSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
R_TRY(db.Commit());
|
||||
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::SendObjectInfo(PtpDataParser &rdp) {
|
||||
u32 storage_id, parent_object;
|
||||
R_TRY(rdp.Read(storage_id));
|
||||
R_TRY(rdp.Read(parent_object));
|
||||
R_TRY(rdp.Finalize());
|
||||
|
||||
PtpDataParser dp(s_bulk_read_buffer, std::addressof(m_usb_server));
|
||||
PtpObjectInfo info(DefaultObjectInfo);
|
||||
|
||||
/* Ensure that we have a data header. */
|
||||
PtpUsbBulkContainer data_header;
|
||||
R_TRY(dp.Read(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(info.storage_id));
|
||||
R_TRY(dp.Read(info.object_format));
|
||||
R_TRY(dp.Read(info.protection_status));
|
||||
R_TRY(dp.Read(info.object_compressed_size));
|
||||
R_TRY(dp.Read(info.thumb_format));
|
||||
R_TRY(dp.Read(info.thumb_compressed_size));
|
||||
R_TRY(dp.Read(info.thumb_width));
|
||||
R_TRY(dp.Read(info.thumb_height));
|
||||
R_TRY(dp.Read(info.image_width));
|
||||
R_TRY(dp.Read(info.image_height));
|
||||
R_TRY(dp.Read(info.image_depth));
|
||||
R_TRY(dp.Read(info.parent_object));
|
||||
R_TRY(dp.Read(info.association_type));
|
||||
R_TRY(dp.Read(info.association_desc));
|
||||
R_TRY(dp.Read(info.sequence_number));
|
||||
R_TRY(dp.ReadString(s_filename_str));
|
||||
R_TRY(dp.ReadString(s_capture_date_str));
|
||||
R_TRY(dp.ReadString(s_modification_date_str));
|
||||
R_TRY(dp.ReadString(s_keywords_str));
|
||||
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 *parentobj = m_object_database.GetObject(parent_object);
|
||||
R_UNLESS(parentobj != nullptr, haze::ResultObjectNotFound());
|
||||
|
||||
/* 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;
|
||||
|
||||
R_TRY(m_object_database.AddObjectId(parentobj->GetName(), s_filename_str, std::addressof(new_object_info.object_id), parentobj->GetObjectId()));
|
||||
|
||||
/* Ensure we maintain a clean state on failure. */
|
||||
ON_RESULT_FAILURE { m_object_database.RemoveObjectId(new_object_info.object_id); };
|
||||
|
||||
/* Get the object name we just built. */
|
||||
auto *fileobj = m_object_database.GetObject(new_object_info.object_id);
|
||||
R_UNLESS(fileobj != nullptr, haze::ResultGeneralFailure());
|
||||
|
||||
/* Create the object on the filesystem. */
|
||||
if (info.association_type == PtpAssociationType_GenericFolder) {
|
||||
R_TRY(m_fs.CreateDirectory(fileobj->GetName()));
|
||||
m_send_object_id = 0;
|
||||
} else {
|
||||
R_TRY(m_fs.CreateFile(fileobj->GetName(), 0, 0));
|
||||
m_send_object_id = new_object_info.object_id;
|
||||
}
|
||||
|
||||
/* We succeeded. */
|
||||
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(s_bulk_read_buffer, std::addressof(m_usb_server));
|
||||
|
||||
/* Ensure that we have a data header. */
|
||||
PtpUsbBulkContainer data_header;
|
||||
R_TRY(dp.Read(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 *fileobj = m_object_database.GetObject(m_send_object_id);
|
||||
R_UNLESS(fileobj != nullptr, haze::ResultObjectNotFound());
|
||||
|
||||
/* Lock the object as a file. */
|
||||
FsFile file;
|
||||
R_TRY(m_fs.OpenFile(fileobj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)));
|
||||
|
||||
/* Ensure we maintain a clean state on exit. */
|
||||
ON_SCOPE_EXIT { m_fs.FileClose(std::addressof(file)); };
|
||||
|
||||
/* Truncate the file after locking for write. */
|
||||
s64 offset = 0;
|
||||
R_TRY(m_fs.FileSetSize(std::addressof(file), 0));
|
||||
|
||||
/* Begin writing. */
|
||||
while (true) {
|
||||
/* Read as many bytes as we can. */
|
||||
size_t bytes_received;
|
||||
Result rc = dp.ReadBuffer(s_fs_buffer, FsBufferSize, std::addressof(bytes_received));
|
||||
|
||||
/* Write to the file. */
|
||||
R_TRY(m_fs.FileWrite(std::addressof(file), offset, s_fs_buffer, bytes_received, 0));
|
||||
|
||||
offset += bytes_received;
|
||||
|
||||
/* If we received fewer bytes than the batch size, we're done. */
|
||||
if (haze::ResultEndOfTransmission::Includes(rc)) {
|
||||
break;
|
||||
}
|
||||
|
||||
R_TRY(rc);
|
||||
}
|
||||
|
||||
/* We succeeded. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
|
||||
Result PtpResponder::DeleteObject(PtpDataParser &dp) {
|
||||
u32 object_id;
|
||||
R_TRY(dp.Read(object_id));
|
||||
R_TRY(dp.Finalize());
|
||||
|
||||
/* Check if we know about the object. If we don't, it's an error. */
|
||||
auto *fileobj = m_object_database.GetObject(object_id);
|
||||
R_UNLESS(fileobj != nullptr, haze::ResultObjectNotFound());
|
||||
|
||||
/* Figure out what type of object this is. */
|
||||
FsDirEntryType entry_type;
|
||||
R_TRY(m_fs.GetEntryType(fileobj->GetName(), std::addressof(entry_type)));
|
||||
|
||||
/* Remove the object from the filesystem. */
|
||||
if (entry_type == FsDirEntryType_Dir) {
|
||||
R_TRY(m_fs.DeleteDirectory(fileobj->GetName()));
|
||||
} else {
|
||||
R_TRY(m_fs.DeleteFile(fileobj->GetName()));
|
||||
}
|
||||
|
||||
/* Remove the object from tracking. */
|
||||
m_object_database.RemoveObjectId(fileobj->GetObjectId());
|
||||
|
||||
/* We succeeded. */
|
||||
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
||||
}
|
||||
}
|
253
troposphere/haze/source/usb_session.cpp
Normal file
253
troposphere/haze/source/usb_session.cpp
Normal file
@ -0,0 +1,253 @@
|
||||
/*
|
||||
* 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 {
|
||||
|
||||
static constexpr 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 = 0x4,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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. */
|
||||
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), 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)) {
|
||||
u8 iManufacturer, iProduct, iSerialNumber;
|
||||
static const u16 supported_langs[1] = {0x0409};
|
||||
|
||||
R_TRY(usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, sizeof(supported_langs)/sizeof(u16)));
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iManufacturer), "Nintendo"));
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iProduct), "Nintendo Switch"));
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iSerialNumber), "SerialNumber"));
|
||||
|
||||
// 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)));
|
||||
}
|
||||
|
||||
R_TRY(hosversionAtLeast(5,0,0) ? this->Initialize5x(info) : this->Initialize1x(info));
|
||||
|
||||
if (hosversionAtLeast(5,0,0)) {
|
||||
R_TRY(usbDsEnable());
|
||||
}
|
||||
|
||||
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, size_t size, u32 *out_urb_id) {
|
||||
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();
|
||||
}
|
||||
|
||||
Result UsbSession::SetZeroLengthTermination(bool enable) {
|
||||
R_RETURN(usbDsEndpoint_SetZlt(m_endpoints[UsbSessionEndpoint_Write], enable));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user