Compare commits

..

39 Commits

Author SHA1 Message Date
Michael Scire
d638b70a62 erpt: amend min-version for latest CreateReportWithAttachments 2023-10-12 08:52:39 -07:00
Michael Scire
73825cbb6d loader: add usb3 patches for 17.0.0 2023-10-12 08:43:27 -07:00
Michael Scire
337fcf07a4 ncm: work around change in Nintendo save handling behavior
Static save files do not require an entry in the save data indexer to mount.
Prior to 17.0.0, save data files were considered static if userid was 0.
In 17.0.0+, only 8000000000000000 is static.

However, some users using cfw do not have an entry for 8000000000000120 in the indexer,
for various reasons (but mostly manual nand-restore, I think). Thus, on boot of 17.0.0+,
FS will say 8000000000000120 is not present (not in indexer), and NCM will create it anew.

The 8000000000000120 save will then be empty, and then the firmware can't boot.

To workaround this, logic has been re-enabled on 17.0.0+ for building the content meta database.
Thus, if the user encounters this error, the 8000000000000120 save will be emptied, but then
it will be automatically reconstructed, fixing the problem.
2023-10-12 08:17:05 -07:00
Michael Scire
89a7b2df35 bpc.mitm/exo: support pmic reboot/shutdown on mariko (thanks @CTCaer) 2023-10-11 18:50:38 -07:00
Michael Scire
0569392faf erpt: remove deprecated fields, they didn't actually change IDs, just the mapping between id and name table index 2023-10-11 17:48:40 -07:00
Michael Scire
cefedb4055 emummc: fix offsets for 17.0.0 2023-10-11 16:59:46 -07:00
Michael Scire
19e0fc7966 fusee: support parsing 17.0.0+ INI 2023-10-11 14:01:00 -07:00
Michael Scire
0099f8dd22 exo: fix up new titlekey option extents 2023-10-11 13:36:40 -07:00
Michael Scire
c03e15df71 jpegdec: stop bundling (TODO post-prerelease) 2023-10-11 13:32:46 -07:00
Michael Scire
618569faef erpt: Add basic (TODO-impl post-prerelease) support for 17.0.0 changes 2023-10-11 13:32:22 -07:00
Michael Scire
db66509564 fs: update OpenCodeFileSystem abi for 17.0.0 2023-10-11 12:45:11 -07:00
Michael Scire
13ee9a83cd ncm: update for new 17.0.0 apis 2023-10-11 12:31:37 -07:00
Michael Scire
c73520180c emummc: update for 17.0.0 2023-10-11 11:25:52 -07:00
Michael Scire
3079578ece exo/spl: Add new EsCommonKeyType 2023-10-11 11:03:17 -07:00
Michael Scire
1060eda6ca fusee/exo: implement the usual changes for new firmware support 2023-10-11 10:55:34 -07:00
Michael Scire
bb825d6a72 kern: fix assert usage in process load 2023-10-11 10:20:47 -07:00
Michael Scire
82fde6e11d kern: bump supported version to 17.x 2023-10-11 10:13:48 -07:00
Michael Scire
baceaf9f84 kern: fix operation type enum-value whoops 2023-10-11 10:12:54 -07:00
Michael Scire
7728efce67 kern: implement support for applying relr relocations 2023-10-11 10:12:20 -07:00
Michael Scire
bc6d207469 kern: split Process/Thread exit to separate WorkerTaskManagers 2023-10-11 09:57:58 -07:00
Michael Scire
e61f20ce18 kern: split out GetInstructionDataUserMode in exception handler 2023-10-11 09:51:40 -07:00
Michael Scire
c8ff437971 kern: Add special-case for InvalidateProcessDataCache on current process 2023-10-11 09:37:45 -07:00
Michael Scire
46ccb6d71a kern: KPageTable: remove MapFirst operation, replace with MapFirstGroup 2023-10-11 09:32:23 -07:00
Michael Scire
a991bb2f5b kern: note OnFinalize calls in KPageTable::Finalize 2023-10-11 09:16:52 -07:00
Michael Scire
4805a8cfd7 kern: implement new default application system resource field in KProcess 2023-10-11 09:13:59 -07:00
Michael Scire
c9ff97f041 kern: update KMemoryRegionType values for new ids + SecureUnknown region 2023-10-11 08:52:46 -07:00
Michael Scire
b8b04d1bf3 kern: KSupervisorPageTable now checks wxn instead of setting it 2023-10-11 08:41:36 -07:00
Michael Scire
c82d363682 kern: KPageTable::Initialize no longer takes unused process id 2023-10-11 08:29:07 -07:00
Michael Scire
d020426fe2 kern: implement PermissionLock, update KPageTableBase attribute/alignment checks 2023-10-11 07:59:37 -07:00
Michael Scire
adfe8b933e kern: KPageTableBase::CheckMemoryState now invokes a helper 2023-10-11 05:11:50 -07:00
Michael Scire
5aba96d029 kern: update KMemoryState, remove bijection (separate IoRegister/IoMemory) 2023-10-11 05:00:23 -07:00
Michael Scire
545ddaf92c kern: update initial process load logic to do per-segment mapping/decompression 2023-10-11 04:26:30 -07:00
Michael Scire
c81d787dc3 kern: clear new pages in init page allocator, not init page table 2023-10-11 03:27:45 -07:00
Michael Scire
b9a4c2bdba kern: add speculation barriers after eret 2023-10-11 03:21:06 -07:00
Michael Scire
9e99f36d7c kern: remove unnecessary fields from InitArgs (0x80 -> 0x40) 2023-10-11 03:10:08 -07:00
Michael Scire
3c33647f6d kern: on second thought, move vectors back to end of text 2023-10-11 03:01:52 -07:00
Michael Scire
3f619a1fef kern/ldr: move crt0 into .rodata 2023-10-11 02:59:41 -07:00
Michael Scire
2a060d2645 kern: pass ini1 size from loader to kernel, remove slab memset from init0 2023-10-11 02:02:49 -07:00
Michael Scire
937ae17f19 utils: update erpt script 2023-10-10 22:45:59 -07:00
28 changed files with 934 additions and 2075 deletions

View File

@@ -1,29 +1,4 @@
# Changelog
## 1.6.1
+ An improved solution to [the problem that would cause consoles which had previously re-built their SYSTEM partition to brick on update-to-17.0.0](https://gist.github.com/SciresM/2ddb708c812ed585c4d99f54e25205ff) was added.
+ In particular, booting atmosphère will now automatically detect the problem and unbrick any consoles which have fallen into this state.
+ Some improvements were made to `haze`, including:
+ Performance was greatly improved:
+ Support was added for GetObjectPropList, which decreases the amount of requests made by ~8x.
+ Haze now performs rendering on the GPU, freeing up the CPU to respond to requests in a more timely manner.
+ An issue was fixed with how `haze` configures `bMaxPacketSize0` which improves support for USB3.
+ General system stability improvements to enhance the user's experience.
## 1.6.0
+ Basic support was added for 17.0.0.
+ The console should boot and atmosphère should be fully functional. However, not all modules have been fully updated to reflect the latest changes.
+ There shouldn't be anything user visible resulting from this, but it will be addressed in a soon-to-come atmosphère update.
+ `exosphère` was updated to reflect the latest official secure monitor behavior.
+ `mesosphère` was updated to reflect the latest official kernel behavior.
+ `ncm` was updated to reflect the latest official behavior.
+ `erpt` was partially updated to support the latest official behavior.
+ Atmosphere's gdbstub now supports waiting to attach to a specific program id on launch (as opposed to any application).
+ The monitor command for this is `monitor wait <hex program id>`, where program id can optionally have an `0x` prefix.
+ Support was added to `haze` for editing files in-place and performing 64-bit transfers (files larger than 4 GB).
+ `bpc.mitm` was enabled on Mariko units, and now triggers pmic-based shutdowns/reboots (thanks @CTCaer).
+ This should cause the console to no longer wake ~15 seconds after shutdown on Mariko.
+ A number of minor issues were fixed and improvements were made, including:
+ A workaround was added for a change in 17.0.0 that would cause consoles which had previously re-built their SYSTEM partition to brick on update-to-17.0.0.
+ General system stability improvements to enhance the user's experience.
## 1.5.5
+ Support was added for 16.1.0.
+ General system stability improvements to enhance the user's experience.

4
emummc/.gitrepo vendored
View File

@@ -6,7 +6,7 @@
[subrepo]
remote = https://github.com/m4xw/emummc
branch = develop
commit = 9513a5412057b1f1bc44ed8e717c57c726763a88
parent = e4d08ae0c5342cdb0875d164522a63ec9d233052
commit = 30205111ee375bef96f0f76cb6a3130a2f0fc85c
parent = 81e9154a52a976f85317bddd0131426599d26a62
method = merge
cmdver = 0.4.1

View File

@@ -6,7 +6,7 @@
[subrepo]
remote = https://github.com/Atmosphere-NX/Atmosphere-libs
branch = master
commit = 965e05b3cc5ea74de7e3071c3554135e828ce42e
parent = 183f3e0d7e0c620d213be53c66d9156f55389be2
commit = c3dc418a28e390bc57426016aa2c9e7e87d7a584
parent = e488b6ee478f5b3a0380e75e4b468e1e4b1d816f
method = merge
cmdver = 0.4.1

View File

@@ -583,7 +583,7 @@ namespace ams::kern {
/* Check memory state. */
const KProcessAddress last_addr = addr + size - 1;
KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(addr);
R_TRY(this->CheckMemoryState(out_state, out_perm, out_attr, out_blocks_needed, it, last_addr, state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr));
R_TRY(this->CheckMemoryState(out_state, out_perm, out_attr, out_blocks_needed, it, last_addr, state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr))
/* If the start address isn't aligned, we need a block. */
if (out_blocks_needed != nullptr && util::AlignDown(GetInteger(addr), PageSize) != it->GetAddress()) {

View File

@@ -239,12 +239,8 @@ namespace ams::ncm {
Result InitializeIntegratedContentMetaDatabaseRoot(IntegratedContentMetaDatabaseRoot *out, const IntegratedContentStorageConfig *config, size_t root_idx, size_t root_count);
Result BuildContentMetaDatabase(StorageId storage_id);
Result BuildContentMetaDatabaseImpl(StorageId storage_id);
Result ImportContentMetaDatabase(StorageId storage_id, bool from_signed_partition);
Result ImportContentMetaDatabaseImpl(ContentMetaDatabaseRoot *root, const char *import_mount_name);
private:
/* Helpers for unofficial functionality. */
bool IsNeedRebuildSystemContentMetaDatabase();
public:
/* Actual commands. */
Result CreateContentStorage(StorageId storage_id);

View File

@@ -28,7 +28,6 @@ namespace ams::pm::dmnt {
Result GetProcessId(os::ProcessId *out_process_id, const ncm::ProgramId program_id);
Result GetApplicationProcessId(os::ProcessId *out_process_id);
Result HookToCreateApplicationProcess(os::NativeHandle *out_handle);
Result HookToCreateProcess(os::NativeHandle *out_handle, const ncm::ProgramId program_id);
Result AtmosphereGetProcessInfo(os::NativeHandle *out_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id);
#if defined(ATMOSPHERE_OS_HORIZON)

View File

@@ -307,10 +307,6 @@ namespace ams::fssrv {
AMS_ABORT("TODO");
}
Result FileSystemProxyImpl::GetProgramId(ams::sf::Out<ncm::ProgramId> out_program_id, const fssrv::sf::FspPath &path, fs::ContentAttributes attr) {
AMS_ABORT("TODO");
}
Result FileSystemProxyImpl::GetRightsIdByPath(ams::sf::Out<fs::RightsId> out, const fssrv::sf::FspPath &path) {
AMS_ABORT("TODO");
}

View File

@@ -784,31 +784,27 @@ namespace ams::ncm {
}
Result ContentManagerImpl::BuildContentMetaDatabase(StorageId storage_id) {
if (hos::GetVersion() < hos::Version_5_0_0) {
/* On < 5.0.0, perform an actual build of the database. */
R_RETURN(this->BuildContentMetaDatabaseImpl(storage_id));
/* NOTE: we build on 17.0.0+, to work around a change in Nintendo save handling behavior. */
if (hos::GetVersion() < hos::Version_5_0_0 || hos::GetVersion() >= hos::Version_17_0_0) {
/* Temporarily activate the database. */
R_TRY(this->ActivateContentMetaDatabase(storage_id));
ON_SCOPE_EXIT { this->InactivateContentMetaDatabase(storage_id); };
/* Open the content meta database and storage. */
ContentMetaDatabase meta_db;
ContentStorage storage;
R_TRY(ncm::OpenContentMetaDatabase(std::addressof(meta_db), storage_id));
R_TRY(ncm::OpenContentStorage(std::addressof(storage), storage_id));
/* Create a builder, and build. */
ContentMetaDatabaseBuilder builder(std::addressof(meta_db));
R_RETURN(builder.BuildFromStorage(std::addressof(storage)));
} else {
/* On 5.0.0+, building just performs an import. */
R_RETURN(this->ImportContentMetaDatabase(storage_id, false));
}
}
Result ContentManagerImpl::BuildContentMetaDatabaseImpl(StorageId storage_id) {
/* Temporarily activate the database. */
R_TRY(this->ActivateContentMetaDatabase(storage_id));
ON_SCOPE_EXIT { this->InactivateContentMetaDatabase(storage_id); };
/* Open the content meta database and storage. */
ContentMetaDatabase meta_db;
ContentStorage storage;
R_TRY(ncm::OpenContentMetaDatabase(std::addressof(meta_db), storage_id));
R_TRY(ncm::OpenContentStorage(std::addressof(storage), storage_id));
/* Create a builder, and build. */
ContentMetaDatabaseBuilder builder(std::addressof(meta_db));
R_RETURN(builder.BuildFromStorage(std::addressof(storage)));
}
Result ContentManagerImpl::ImportContentMetaDatabase(StorageId storage_id, bool from_signed_partition) {
/* Only support importing BuiltInSystem. */
AMS_ABORT_UNLESS(storage_id == StorageId::BuiltInSystem);
@@ -836,31 +832,6 @@ namespace ams::ncm {
R_SUCCEED();
}
bool ContentManagerImpl::IsNeedRebuildSystemContentMetaDatabase() {
/* TODO: Should hos::GetVersion() >= hos::Version_17_0_0 be checked? */
/* If we do not actually have a content meta db, we should re-build. */
if (R_FAILED(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem))) {
return true;
}
/* We have a content meta db. Temporarily, activate it. */
if (R_FAILED(this->ActivateContentMetaDatabase(StorageId::BuiltInSystem))) {
return true;
}
ON_SCOPE_EXIT { this->InactivateContentMetaDatabase(StorageId::BuiltInSystem); };
/* Open the content meta db. */
ContentMetaDatabase meta_db;
R_ABORT_UNLESS(ncm::OpenContentMetaDatabase(std::addressof(meta_db), StorageId::BuiltInSystem));
/* List the meta db's contents. */
const auto list_count = meta_db.ListContentMeta(nullptr, 0);
/* We need to rebuild if the db has zero entries. */
return list_count.total == 0;
}
Result ContentManagerImpl::Initialize(const ContentManagerConfig &config) {
/* Initialize based on whether integrated content is enabled. */
if (config.IsIntegratedSystemContentEnabled()) {
@@ -971,26 +942,13 @@ namespace ams::ncm {
}
R_TRY(this->ActivateContentStorage(StorageId::BuiltInSystem));
/* NOTE: This logic is unofficial. */
/* Beginning with 17.0.0+, save management behavior changed. The primary symptom of this is either verify fail */
/* or an empty kvs, both of which we can fix by performing a rebuild. */
if (this->IsNeedRebuildSystemContentMetaDatabase()) {
/* Clean up the system content meta database, to ensure creation can succeed. */
this->CleanupContentMetaDatabase(StorageId::BuiltInSystem);
/* Create the content meta database. */
R_TRY(this->CreateContentMetaDatabase(StorageId::BuiltInSystem));
/* Rebuild the content meta database. */
R_TRY(this->BuildContentMetaDatabaseImpl(StorageId::BuiltInSystem));
}
/* Setup the content meta database for system. */
if (R_FAILED(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem))) {
R_TRY(this->CreateContentMetaDatabase(StorageId::BuiltInSystem));
/* Try to build or import a database, depending on our configuration. */
if (manager_config.ShouldBuildDatabase()) {
/* NOTE: To work around a change in save management behavior in 17.0.0+, we build the database if needed. */
if (manager_config.ShouldBuildDatabase() || hos::GetVersion() >= hos::Version_17_0_0) {
/* If we should build the database, do so. */
R_TRY(this->BuildContentMetaDatabase(StorageId::BuiltInSystem));
R_TRY(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem));

View File

@@ -44,13 +44,6 @@ namespace ams::pm::dmnt {
R_SUCCEED();
}
Result HookToCreateProcess(os::NativeHandle *out_handle, const ncm::ProgramId program_id) {
Event evt;
R_TRY(pmdmntHookToCreateProcess(std::addressof(evt), static_cast<u64>(program_id)));
*out_handle = evt.revent;
R_SUCCEED();
}
Result AtmosphereGetProcessInfo(os::NativeHandle *out_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id) {
*out_handle = os::InvalidNativeHandle;
*out_loc = {};

View File

@@ -17,7 +17,7 @@
#define ATMOSPHERE_RELEASE_VERSION_MAJOR 1
#define ATMOSPHERE_RELEASE_VERSION_MINOR 6
#define ATMOSPHERE_RELEASE_VERSION_MICRO 1
#define ATMOSPHERE_RELEASE_VERSION_MICRO 0
#define ATMOSPHERE_RELEASE_VERSION ATMOSPHERE_RELEASE_VERSION_MAJOR, ATMOSPHERE_RELEASE_VERSION_MINOR, ATMOSPHERE_RELEASE_VERSION_MICRO

View File

@@ -2105,24 +2105,9 @@ namespace ams::dmnt {
ParsePrefix(command, "0x");
/* Decode program id. */
const ncm::ProgramId program_id(DecodeHex(command));
const u64 program_id = DecodeHex(command);
/* Wait for the process to be created. */
{
/* Get hook to creation of process with program id. */
os::NativeHandle h;
R_ABORT_UNLESS(pm::dmnt::HookToCreateProcess(std::addressof(h), program_id));
/* Wait for event. */
os::SystemEvent hook_event(h, true, os::InvalidNativeHandle, false, os::EventClearMode_AutoClear);
hook_event.Wait();
}
/* Get process id. */
R_ABORT_UNLESS(pm::dmnt::GetProcessId(std::addressof(m_wait_process_id), program_id));
/* Note that we're attaching. */
AppendReplyFormat(reply_cur, reply_end, "Send `attach 0x%lx` to attach.\n", m_wait_process_id.value);
AppendReplyFormat(reply_cur, reply_end, "[TODO] wait for program id 0x%lx\n", program_id);
} else {
std::memcpy(m_reply_cur, command, std::strlen(command) + 1);
AppendReplyFormat(reply_cur, reply_end, "Unknown command `%s`\n", m_reply_cur);

View File

@@ -42,10 +42,7 @@ BUILD := build
SOURCES := source
DATA := data
INCLUDES := include ../../libraries/libvapours/include
ROMFS := romfs
# Output folders for autogenerated files in romfs
OUT_SHADERS := shaders
#ROMFS := romfs
APP_TITLE := USB File Transfer
APP_AUTHOR := Atmosphere-NX
@@ -66,7 +63,7 @@ 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 := -ldeko3d -lnx -lm
LIBS := -lnx
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
@@ -93,7 +90,6 @@ export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
GLSLFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.glsl)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
@@ -159,61 +155,19 @@ ifneq ($(APP_TITLEID),)
export NACPFLAGS += --titleid=$(APP_TITLEID)
endif
ifneq ($(strip $(ROMFS)),)
ROMFS_TARGETS :=
ROMFS_FOLDERS :=
ifneq ($(strip $(OUT_SHADERS)),)
ROMFS_SHADERS := $(ROMFS)/$(OUT_SHADERS)
ROMFS_TARGETS += $(patsubst %.glsl, $(ROMFS_SHADERS)/%.dksh, $(GLSLFILES))
ROMFS_FOLDERS += $(ROMFS_SHADERS)
endif
export ROMFS_DEPS := $(foreach file,$(ROMFS_TARGETS),$(CURDIR)/$(file))
ifneq ($(ROMFS),)
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(ROMFS_TARGETS) | $(BUILD)
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
ifneq ($(strip $(ROMFS_TARGETS)),)
$(ROMFS_TARGETS): | $(ROMFS_FOLDERS)
$(ROMFS_FOLDERS):
@mkdir -p $@
$(ROMFS_SHADERS)/%_vsh.dksh: %_vsh.glsl
@echo {vert} $(notdir $<)
@uam -s vert -o $@ $<
$(ROMFS_SHADERS)/%_tcsh.dksh: %_tcsh.glsl
@echo {tess_ctrl} $(notdir $<)
@uam -s tess_ctrl -o $@ $<
$(ROMFS_SHADERS)/%_tesh.dksh: %_tesh.glsl
@echo {tess_eval} $(notdir $<)
@uam -s tess_eval -o $@ $<
$(ROMFS_SHADERS)/%_gsh.dksh: %_gsh.glsl
@echo {geom} $(notdir $<)
@uam -s geom -o $@ $<
$(ROMFS_SHADERS)/%_fsh.dksh: %_fsh.glsl
@echo {frag} $(notdir $<)
@uam -s frag -o $@ $<
$(ROMFS_SHADERS)/%.dksh: %.glsl
@echo {comp} $(notdir $<)
@uam -s comp -o $@ $<
endif
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@@ -238,9 +192,9 @@ ifeq ($(strip $(APP_JSON)),)
all : $(OUTPUT).nro
ifeq ($(strip $(NO_NACP)),)
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS)
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp
else
$(OUTPUT).nro : $(OUTPUT).elf $(ROMFS_DEPS)
$(OUTPUT).nro : $(OUTPUT).elf
endif
else

View File

@@ -42,4 +42,6 @@ namespace haze {
using Result = ::ams::Result;
static constexpr u32 UsbBulkPacketBufferSize = 1_MB;
}

View File

@@ -143,7 +143,7 @@ namespace haze {
if (m_is_applet_mode) {
/* Print "Applet Mode" in red text. */
printf("\n" CONSOLE_ESC(31;1m) "Applet Mode" CONSOLE_ESC(0m) "\n");
printf("\n" CONSOLE_ESC(38;5;196m) "Applet Mode" CONSOLE_ESC(0m) "\n");
}
consoleUpdate(nullptr);

View File

@@ -72,11 +72,6 @@ namespace haze {
PtpOperationCode_GetFilesystemManifest = 0x1023,
PtpOperationCode_GetStreamInfo = 0x1024,
PtpOperationCode_GetStream = 0x1025,
PtpOperationCode_AndroidGetPartialObject64 = 0x95c1,
PtpOperationCode_AndroidSendPartialObject = 0x95c2,
PtpOperationCode_AndroidTruncateObject = 0x95c3,
PtpOperationCode_AndroidBeginEditObject = 0x95c4,
PtpOperationCode_AndroidEndEditObject = 0x95c5,
PtpOperationCode_MtpGetObjectPropsSupported = 0x9801,
PtpOperationCode_MtpGetObjectPropDesc = 0x9802,
PtpOperationCode_MtpGetObjectPropValue = 0x9803,

View File

@@ -111,14 +111,6 @@ namespace haze {
}
constexpr void Deallocate(void *p, size_t n) {
/* Check for overflow in alignment. */
if (!util::CanAddWithoutOverflow(n, alignof(u64) - 1)) {
return;
}
/* Align the amount to satisfy allocation for u64. */
n = util::AlignUp(n, alignof(u64));
/* If the pointer was the last allocation, return the memory to the heap. */
if (static_cast<u8 *>(p) + n == this->GetNextAddress()) {
m_next_address = this->GetNextAddress() - n;

View File

@@ -19,7 +19,6 @@
#include <haze/async_usb_server.hpp>
#include <haze/ptp_object_heap.hpp>
#include <haze/ptp_object_database.hpp>
#include <haze/ptp_responder_types.hpp>
namespace haze {
@@ -31,13 +30,12 @@ namespace haze {
FileSystemProxy m_fs;
PtpUsbBulkContainer m_request_header;
PtpObjectHeap *m_object_heap;
PtpBuffers* m_buffers;
u32 m_send_object_id;
bool m_session_open;
PtpObjectDatabase m_object_database;
public:
constexpr explicit PtpResponder() : m_usb_server(), m_fs(), m_request_header(), m_object_heap(), m_buffers(), m_send_object_id(), m_session_open(), m_object_database() { /* ... */ }
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();
@@ -50,14 +48,10 @@ namespace haze {
Result HandleCommandRequest(PtpDataParser &dp);
void ForceCloseSession();
Result WriteResponse(PtpResponseCode code, const void* data, size_t size);
template <typename Data>
Result WriteResponse(PtpResponseCode code, Data &&data);
Result WriteResponse(PtpResponseCode code);
template <typename Data> requires (util::is_pod<Data>::value)
Result WriteResponse(PtpResponseCode code, const Data &data) {
R_RETURN(this->WriteResponse(code, std::addressof(data), sizeof(data)));
}
/* PTP operations. */
Result GetDeviceInfo(PtpDataParser &dp);
Result OpenSession(PtpDataParser &dp);
@@ -71,19 +65,11 @@ namespace haze {
Result SendObject(PtpDataParser &dp);
Result DeleteObject(PtpDataParser &dp);
/* Android operations. */
Result GetPartialObject64(PtpDataParser &dp);
Result SendPartialObject(PtpDataParser &dp);
Result TruncateObject(PtpDataParser &dp);
Result BeginEditObject(PtpDataParser &dp);
Result EndEditObject(PtpDataParser &dp);
/* MTP operations. */
Result GetObjectPropsSupported(PtpDataParser &dp);
Result GetObjectPropDesc(PtpDataParser &dp);
Result GetObjectPropValue(PtpDataParser &dp);
Result SetObjectPropValue(PtpDataParser &dp);
Result GetObjectPropList(PtpDataParser &dp);
};
}

View File

@@ -1,183 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <haze.hpp>
namespace haze {
constexpr UsbCommsInterfaceInfo MtpInterfaceInfo = {
.bInterfaceClass = 0x06,
.bInterfaceSubClass = 0x01,
.bInterfaceProtocol = 0x01,
};
/* This is a VID:PID recognized by libmtp. */
constexpr u16 SwitchMtpIdVendor = 0x057e;
constexpr u16 SwitchMtpIdProduct = 0x201d;
/* Constants used for MTP GetDeviceInfo response. */
constexpr u16 MtpStandardVersion = 100;
constexpr u32 MtpVendorExtensionId = 6;
constexpr auto MtpVendorExtensionDesc = "microsoft.com: 1.0;";
constexpr u16 MtpFunctionalModeDefault = 0;
constexpr auto MtpDeviceManufacturer = "Nintendo";
constexpr auto MtpDeviceModel = "Nintendo Switch";
enum StorageId : u32 {
StorageId_SdmcFs = 0xffffffffu - 1,
};
constexpr PtpOperationCode SupportedOperationCodes[] = {
PtpOperationCode_GetDeviceInfo,
PtpOperationCode_OpenSession,
PtpOperationCode_CloseSession,
PtpOperationCode_GetStorageIds,
PtpOperationCode_GetStorageInfo,
PtpOperationCode_GetObjectHandles,
PtpOperationCode_GetObjectInfo,
PtpOperationCode_GetObject,
PtpOperationCode_SendObjectInfo,
PtpOperationCode_SendObject,
PtpOperationCode_DeleteObject,
PtpOperationCode_MtpGetObjectPropsSupported,
PtpOperationCode_MtpGetObjectPropDesc,
PtpOperationCode_MtpGetObjectPropValue,
PtpOperationCode_MtpSetObjectPropValue,
PtpOperationCode_MtpGetObjPropList,
PtpOperationCode_AndroidGetPartialObject64,
PtpOperationCode_AndroidSendPartialObject,
PtpOperationCode_AndroidTruncateObject,
PtpOperationCode_AndroidBeginEditObject,
PtpOperationCode_AndroidEndEditObject,
};
constexpr const PtpEventCode SupportedEventCodes[] = { /* ... */ };
constexpr const PtpDevicePropertyCode SupportedDeviceProperties[] = { /* ... */ };
constexpr const PtpObjectFormatCode SupportedCaptureFormats[] = { /* ... */ };
constexpr const PtpObjectFormatCode SupportedPlaybackFormats[] = {
PtpObjectFormatCode_Undefined,
PtpObjectFormatCode_Association,
};
constexpr const PtpObjectPropertyCode SupportedObjectProperties[] = {
PtpObjectPropertyCode_StorageId,
PtpObjectPropertyCode_ObjectFormat,
PtpObjectPropertyCode_ObjectSize,
PtpObjectPropertyCode_ObjectFileName,
PtpObjectPropertyCode_ParentObject,
PtpObjectPropertyCode_PersistentUniqueObjectIdentifier,
};
constexpr bool IsSupportedObjectPropertyCode(PtpObjectPropertyCode c) {
for (size_t i = 0; i < util::size(SupportedObjectProperties); i++) {
if (SupportedObjectProperties[i] == c) {
return true;
}
}
return false;
}
constexpr const StorageId SupportedStorageIds[] = {
StorageId_SdmcFs,
};
struct PtpStorageInfo {
PtpStorageType storage_type;
PtpFilesystemType filesystem_type;
PtpAccessCapability access_capability;
u64 max_capacity;
u64 free_space_in_bytes;
u32 free_space_in_images;
const char *storage_description;
const char *volume_label;
};
constexpr PtpStorageInfo DefaultStorageInfo = {
.storage_type = PtpStorageType_FixedRam,
.filesystem_type = PtpFilesystemType_GenericHierarchical,
.access_capability = PtpAccessCapability_ReadWrite,
.max_capacity = 0,
.free_space_in_bytes = 0,
.free_space_in_images = 0,
.storage_description = "",
.volume_label = "",
};
struct PtpObjectInfo {
StorageId storage_id;
PtpObjectFormatCode object_format;
PtpProtectionStatus protection_status;
u32 object_compressed_size;
u16 thumb_format;
u32 thumb_compressed_size;
u32 thumb_width;
u32 thumb_height;
u32 image_width;
u32 image_height;
u32 image_depth;
u32 parent_object;
PtpAssociationType association_type;
u32 association_desc;
u32 sequence_number;
const char *filename;
const char *capture_date;
const char *modification_date;
const char *keywords;
};
constexpr PtpObjectInfo DefaultObjectInfo = {
.storage_id = StorageId_SdmcFs,
.object_format = {},
.protection_status = PtpProtectionStatus_NoProtection,
.object_compressed_size = 0,
.thumb_format = 0,
.thumb_compressed_size = 0,
.thumb_width = 0,
.thumb_height = 0,
.image_width = 0,
.image_height = 0,
.image_depth = 0,
.parent_object = PtpGetObjectHandles_RootParent,
.association_type = PtpAssociationType_Undefined,
.association_desc = 0,
.sequence_number = 0,
.filename = nullptr,
.capture_date = "",
.modification_date = "",
.keywords = "",
};
constexpr u32 UsbBulkPacketBufferSize = 1_MB;
constexpr u64 FsBufferSize = UsbBulkPacketBufferSize;
constexpr s64 DirectoryReadSize = 32;
struct PtpBuffers {
char filename_string_buffer[PtpStringMaxLength + 1];
char capture_date_string_buffer[PtpStringMaxLength + 1];
char modification_date_string_buffer[PtpStringMaxLength + 1];
char keywords_string_buffer[PtpStringMaxLength + 1];
FsDirectoryEntry file_system_entry_buffer[DirectoryReadSize];
u8 file_system_data_buffer[FsBufferSize];
alignas(4_KB) u8 usb_bulk_write_buffer[UsbBulkPacketBufferSize];
alignas(4_KB) u8 usb_bulk_read_buffer[UsbBulkPacketBufferSize];
};
}

View File

@@ -37,8 +37,5 @@ namespace haze {
R_DEFINE_ERROR_RESULT(UnknownRequestType, 13);
R_DEFINE_ERROR_RESULT(UnknownPropertyCode, 14);
R_DEFINE_ERROR_RESULT(InvalidPropertyValue, 15);
R_DEFINE_ERROR_RESULT(InvalidArgument, 16);
R_DEFINE_ERROR_RESULT(GroupSpecified, 17);
R_DEFINE_ERROR_RESULT(DepthSpecified, 18);
}

View File

@@ -1,15 +0,0 @@
#version 460
layout (location = 0) noperspective in vec3 inTexCoord;
layout (location = 1) flat in vec4 inFrontPal;
layout (location = 2) flat in vec4 inBackPal;
layout (location = 0) out vec4 outColor;
layout (binding = 0) uniform sampler2DArray tileset;
void main()
{
float value = texture(tileset, inTexCoord).r;
outColor = mix(inBackPal, inFrontPal, value);
}

View File

@@ -1,35 +0,0 @@
#version 460
layout (location = 0) in float inTileId;
layout (location = 1) in uvec2 inColorId;
layout (location = 0) out vec3 outTexCoord;
layout (location = 1) out vec4 outFrontPal;
layout (location = 2) out vec4 outBackPal;
layout (std140, binding = 0) uniform Config
{
vec4 dimensions;
vec4 vertices[3];
vec4 palettes[24];
} u;
void main()
{
float id = float(gl_InstanceID);
float tileRow = floor(id / u.dimensions.z);
float tileCol = id - tileRow * u.dimensions.z;
vec2 basePos;
basePos.x = 2.0 * (tileCol + 0.5) / u.dimensions.z - 1.0;
basePos.y = 2.0 * (1.0 - (tileRow + 0.5) / u.dimensions.w) - 1.0;
vec2 vtxData = u.vertices[gl_VertexID].xy;
vec2 scale = vec2(1.0) / u.dimensions.zw;
gl_Position.xy = vtxData * scale + basePos;
gl_Position.zw = vec2(0.5, 1.0);
outTexCoord = vec3(u.vertices[gl_VertexID].zw, inTileId);
outFrontPal = u.palettes[inColorId.x];
outBackPal = u.palettes[inColorId.y];
}

View File

@@ -1,487 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/iosupport.h>
#include <switch.h>
#include <deko3d.h>
// Define the desired number of framebuffers
#define FB_NUM 2
// Define the size of the memory block that will hold code
#define CODEMEMSIZE (64*1024)
// Define the size of the memory block that will hold command lists
#define CMDMEMSIZE (64*1024)
#define NUM_IMAGE_SLOTS 1
#define NUM_SAMPLER_SLOTS 1
typedef struct {
float pos[2];
float tex[2];
} VertexDef;
typedef struct {
float red;
float green;
float blue;
float alpha;
} PaletteColor;
typedef struct {
float dimensions[4];
VertexDef vertices[3];
PaletteColor palettes[24];
} ConsoleConfig;
static const VertexDef g_vertexData[3] = {
{ { 0.0f, +1.0f }, { 0.5f, 0.0f, } },
{ { -1.0f, -1.0f }, { 0.0f, 1.0f, } },
{ { +1.0f, -1.0f }, { 1.0f, 1.0f, } },
};
static const PaletteColor g_paletteData[24] = {
{ 0.0f, 0.0f, 0.0f, 0.0f }, // black
{ 0.5f, 0.0f, 0.0f, 1.0f }, // red
{ 0.0f, 0.5f, 0.0f, 1.0f }, // green
{ 0.5f, 0.5f, 0.0f, 1.0f }, // yellow
{ 0.0f, 0.0f, 0.5f, 1.0f }, // blue
{ 0.5f, 0.0f, 0.5f, 1.0f }, // magenta
{ 0.0f, 0.5f, 0.5f, 1.0f }, // cyan
{ 0.75f, 0.75f, 0.75f, 1.0f }, // white
{ 0.5f, 0.5f, 0.5f, 1.0f }, // bright black
{ 1.0f, 0.0f, 0.0f, 1.0f }, // bright red
{ 0.0f, 1.0f, 0.0f, 1.0f }, // bright green
{ 1.0f, 1.0f, 0.0f, 1.0f }, // bright yellow
{ 0.0f, 0.0f, 1.0f, 1.0f }, // bright blue
{ 1.0f, 0.0f, 1.0f, 1.0f }, // bright magenta
{ 0.0f, 1.0f, 1.0f, 1.0f }, // bright cyan
{ 1.0f, 1.0f, 1.0f, 1.0f }, // bright white
{ 0.0f, 0.0f, 0.0f, 0.0f }, // faint black
{ 0.25f, 0.0f, 0.0f, 1.0f }, // faint red
{ 0.0f, 0.25f, 0.0f, 1.0f }, // faint green
{ 0.25f, 0.25f, 0.0f, 1.0f }, // faint yellow
{ 0.0f, 0.0f, 0.25f, 1.0f }, // faint blue
{ 0.25f, 0.0f, 0.25f, 1.0f }, // faint magenta
{ 0.0f, 0.25f, 0.25f, 1.0f }, // faint cyan
{ 0.375f, 0.375f, 0.375f, 1.0f }, // faint white
};
typedef struct {
uint16_t tileId;
uint8_t frontPal;
uint8_t backPal;
} ConsoleChar;
static const DkVtxAttribState g_attribState[] = {
{ .bufferId=0, .isFixed=0, .offset=offsetof(ConsoleChar,tileId), .size=DkVtxAttribSize_1x16, .type=DkVtxAttribType_Uscaled, .isBgra=0 },
{ .bufferId=0, .isFixed=0, .offset=offsetof(ConsoleChar,frontPal), .size=DkVtxAttribSize_2x8, .type=DkVtxAttribType_Uint, .isBgra=0 },
};
static const DkVtxBufferState g_vtxbufState[] = {
{ .stride=sizeof(ConsoleChar), .divisor=1 },
};
struct GpuRenderer {
ConsoleRenderer base;
bool initialized;
DkDevice device;
DkQueue queue;
DkMemBlock imageMemBlock;
DkMemBlock codeMemBlock;
DkMemBlock dataMemBlock;
DkSwapchain swapchain;
DkImage framebuffers[FB_NUM];
DkImage tileset;
ConsoleChar* charBuf;
uint32_t codeMemOffset;
DkShader vertexShader;
DkShader fragmentShader;
DkCmdBuf cmdbuf;
DkCmdList cmdsBindFramebuffer[FB_NUM];
DkCmdList cmdsRender;
DkFence lastRenderFence;
};
static struct GpuRenderer* GpuRenderer(PrintConsole* con)
{
return (struct GpuRenderer*)con->renderer;
}
static void GpuRenderer_destroy(struct GpuRenderer* r)
{
// Make sure the queue is idle before destroying anything
dkQueueWaitIdle(r->queue);
// Destroy all the resources we've created
dkQueueDestroy(r->queue);
dkCmdBufDestroy(r->cmdbuf);
dkSwapchainDestroy(r->swapchain);
dkMemBlockDestroy(r->dataMemBlock);
dkMemBlockDestroy(r->codeMemBlock);
dkMemBlockDestroy(r->imageMemBlock);
dkDeviceDestroy(r->device);
// Clear out all state
memset(&r->initialized, 0, sizeof(*r) - offsetof(struct GpuRenderer, initialized));
}
// Simple function for loading a shader from the filesystem
static void GpuRenderer_loadShader(struct GpuRenderer* r, DkShader* pShader, const char* path)
{
// Open the file, and retrieve its size
FILE* f = fopen(path, "rb");
fseek(f, 0, SEEK_END);
uint32_t size = ftell(f);
rewind(f);
// Look for a spot in the code memory block for loading this shader. Note that
// we are just using a simple incremental offset; this isn't a general purpose
// allocation algorithm.
uint32_t codeOffset = r->codeMemOffset;
r->codeMemOffset += (size + DK_SHADER_CODE_ALIGNMENT - 1) &~ (DK_SHADER_CODE_ALIGNMENT - 1);
// Read the file into memory, and close the file
fread((uint8_t*)dkMemBlockGetCpuAddr(r->codeMemBlock) + codeOffset, size, 1, f);
fclose(f);
// Initialize the user provided shader object with the code we've just loaded
DkShaderMaker shaderMaker;
dkShaderMakerDefaults(&shaderMaker, r->codeMemBlock, codeOffset);
dkShaderInitialize(pShader, &shaderMaker);
}
static bool GpuRenderer_init(PrintConsole* con)
{
struct GpuRenderer* r = GpuRenderer(con);
if (r->initialized) {
// We're already initialized
return true;
}
// Create the deko3d device, which is the root object
DkDeviceMaker deviceMaker;
dkDeviceMakerDefaults(&deviceMaker);
r->device = dkDeviceCreate(&deviceMaker);
// Create the queue
DkQueueMaker queueMaker;
dkQueueMakerDefaults(&queueMaker, r->device);
queueMaker.flags = DkQueueFlags_Graphics;
r->queue = dkQueueCreate(&queueMaker);
// Calculate required width/height for the framebuffers
u32 width = con->font.tileWidth * con->consoleWidth;
u32 height = con->font.tileHeight * con->consoleHeight;
u32 totalConSize = con->consoleWidth * con->consoleHeight;
// Calculate layout for the framebuffers
DkImageLayoutMaker imageLayoutMaker;
dkImageLayoutMakerDefaults(&imageLayoutMaker, r->device);
imageLayoutMaker.flags = DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression;
imageLayoutMaker.format = DkImageFormat_RGBA8_Unorm;
imageLayoutMaker.dimensions[0] = width;
imageLayoutMaker.dimensions[1] = height;
// Calculate layout for the framebuffers
DkImageLayout framebufferLayout;
dkImageLayoutInitialize(&framebufferLayout, &imageLayoutMaker);
// Calculate layout for the tileset
dkImageLayoutMakerDefaults(&imageLayoutMaker, r->device);
imageLayoutMaker.type = DkImageType_2DArray;
imageLayoutMaker.format = DkImageFormat_R32_Float;
imageLayoutMaker.dimensions[0] = con->font.tileWidth;
imageLayoutMaker.dimensions[1] = con->font.tileHeight;
imageLayoutMaker.dimensions[2] = con->font.numChars;
// Calculate layout for the tileset
DkImageLayout tilesetLayout;
dkImageLayoutInitialize(&tilesetLayout, &imageLayoutMaker);
// Retrieve necessary size and alignment for the framebuffers
uint32_t framebufferSize = dkImageLayoutGetSize(&framebufferLayout);
uint32_t framebufferAlign = dkImageLayoutGetAlignment(&framebufferLayout);
framebufferSize = (framebufferSize + framebufferAlign - 1) &~ (framebufferAlign - 1);
// Retrieve necessary size and alignment for the tileset
uint32_t tilesetSize = dkImageLayoutGetSize(&tilesetLayout);
uint32_t tilesetAlign = dkImageLayoutGetAlignment(&tilesetLayout);
tilesetSize = (tilesetSize + tilesetAlign - 1) &~ (tilesetAlign - 1);
// Create a memory block that will host the framebuffers and the tileset
DkMemBlockMaker memBlockMaker;
dkMemBlockMakerDefaults(&memBlockMaker, r->device, FB_NUM*framebufferSize + tilesetSize);
memBlockMaker.flags = DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image;
r->imageMemBlock = dkMemBlockCreate(&memBlockMaker);
// Initialize the framebuffers with the layout and backing memory we've just created
DkImage const* swapchainImages[FB_NUM];
for (unsigned i = 0; i < FB_NUM; i ++) {
swapchainImages[i] = &r->framebuffers[i];
dkImageInitialize(&r->framebuffers[i], &framebufferLayout, r->imageMemBlock, i*framebufferSize);
}
// Create a swapchain out of the framebuffers we've just initialized
DkSwapchainMaker swapchainMaker;
dkSwapchainMakerDefaults(&swapchainMaker, r->device, nwindowGetDefault(), swapchainImages, FB_NUM);
r->swapchain = dkSwapchainCreate(&swapchainMaker);
// Initialize the tileset
dkImageInitialize(&r->tileset, &tilesetLayout, r->imageMemBlock, FB_NUM*framebufferSize);
// Create a memory block onto which we will load shader code
dkMemBlockMakerDefaults(&memBlockMaker, r->device, CODEMEMSIZE);
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code;
r->codeMemBlock = dkMemBlockCreate(&memBlockMaker);
r->codeMemOffset = 0;
// Load our shaders (both vertex and fragment)
romfsInit();
GpuRenderer_loadShader(r, &r->vertexShader, "romfs:/shaders/console_vsh.dksh");
GpuRenderer_loadShader(r, &r->fragmentShader, "romfs:/shaders/console_fsh.dksh");
// Generate the descriptors
struct {
DkImageDescriptor images[NUM_IMAGE_SLOTS];
DkSamplerDescriptor samplers[NUM_SAMPLER_SLOTS];
} descriptors;
// Generate a image descriptor for the tileset
DkImageView tilesetView;
dkImageViewDefaults(&tilesetView, &r->tileset);
dkImageDescriptorInitialize(&descriptors.images[0], &tilesetView, false, false);
// Generate a sampler descriptor for the tileset
DkSampler sampler;
dkSamplerDefaults(&sampler);
sampler.wrapMode[0] = DkWrapMode_ClampToEdge;
sampler.wrapMode[1] = DkWrapMode_ClampToEdge;
sampler.minFilter = DkFilter_Nearest;
sampler.magFilter = DkFilter_Nearest;
dkSamplerDescriptorInitialize(&descriptors.samplers[0], &sampler);
uint32_t descriptorsOffset = CMDMEMSIZE;
uint32_t configOffset = (descriptorsOffset + sizeof(descriptors) + DK_UNIFORM_BUF_ALIGNMENT - 1) &~ (DK_UNIFORM_BUF_ALIGNMENT - 1);
uint32_t configSize = (sizeof(ConsoleConfig) + DK_UNIFORM_BUF_ALIGNMENT - 1) &~ (DK_UNIFORM_BUF_ALIGNMENT - 1);
uint32_t charBufOffset = configOffset + configSize;
uint32_t charBufSize = totalConSize * sizeof(ConsoleChar);
// Create a memory block which will be used for recording command lists using a command buffer
dkMemBlockMakerDefaults(&memBlockMaker, r->device,
(charBufOffset + charBufSize + DK_MEMBLOCK_ALIGNMENT - 1) &~ (DK_MEMBLOCK_ALIGNMENT - 1)
);
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached;
r->dataMemBlock = dkMemBlockCreate(&memBlockMaker);
// Create a command buffer object
DkCmdBufMaker cmdbufMaker;
dkCmdBufMakerDefaults(&cmdbufMaker, r->device);
r->cmdbuf = dkCmdBufCreate(&cmdbufMaker);
// Feed our memory to the command buffer so that we can start recording commands
dkCmdBufAddMemory(r->cmdbuf, r->dataMemBlock, 0, CMDMEMSIZE);
// Create a temporary buffer that will hold the tileset
dkMemBlockMakerDefaults(&memBlockMaker, r->device,
(sizeof(float)*con->font.tileWidth*con->font.tileHeight*con->font.numChars + DK_MEMBLOCK_ALIGNMENT - 1) &~ (DK_MEMBLOCK_ALIGNMENT - 1)
);
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached;
DkMemBlock scratchMemBlock = dkMemBlockCreate(&memBlockMaker);
float* scratchMem = (float*)dkMemBlockGetCpuAddr(scratchMemBlock);
// Unpack 1bpp tileset into a texture image the GPU can read
unsigned packedTileWidth = (con->font.tileWidth+7)/8;
for (unsigned tile = 0; tile < con->font.numChars; tile ++) {
const uint8_t* data = (const uint8_t*)con->font.gfx + con->font.tileHeight*packedTileWidth*tile;
for (unsigned y = 0; y < con->font.tileHeight; y ++) {
const uint8_t* row = &data[packedTileWidth*(y+1)];
uint8_t c = 0;
for (unsigned x = 0; x < con->font.tileWidth; x ++) {
if (!(x & 7))
c = *--row;
*scratchMem++ = (c & 0x80) ? 1.0f : 0.0f;
c <<= 1;
}
}
}
// Set up configuration
DkGpuAddr configAddr = dkMemBlockGetGpuAddr(r->dataMemBlock) + configOffset;
ConsoleConfig consoleConfig = {};
consoleConfig.dimensions[0] = width;
consoleConfig.dimensions[1] = height;
consoleConfig.dimensions[2] = con->consoleWidth;
consoleConfig.dimensions[3] = con->consoleHeight;
memcpy(consoleConfig.vertices, g_vertexData, sizeof(g_vertexData));
memcpy(consoleConfig.palettes, g_paletteData, sizeof(g_paletteData));
// Generate a temporary command list for uploading stuff and run it
DkGpuAddr descriptorSet = dkMemBlockGetGpuAddr(r->dataMemBlock) + descriptorsOffset;
DkCopyBuf copySrc = { dkMemBlockGetGpuAddr(scratchMemBlock), 0, 0 };
DkImageRect copyDst = { 0, 0, 0, con->font.tileWidth, con->font.tileHeight, con->font.numChars };
dkCmdBufPushData(r->cmdbuf, descriptorSet, &descriptors, sizeof(descriptors));
dkCmdBufPushConstants(r->cmdbuf, configAddr, configSize, 0, sizeof(consoleConfig), &consoleConfig);
dkCmdBufBindImageDescriptorSet(r->cmdbuf, descriptorSet, NUM_IMAGE_SLOTS);
dkCmdBufBindSamplerDescriptorSet(r->cmdbuf, descriptorSet + NUM_IMAGE_SLOTS*sizeof(DkImageDescriptor), NUM_SAMPLER_SLOTS);
dkCmdBufCopyBufferToImage(r->cmdbuf, &copySrc, &tilesetView, &copyDst, 0);
dkQueueSubmitCommands(r->queue, dkCmdBufFinishList(r->cmdbuf));
dkQueueFlush(r->queue);
dkQueueWaitIdle(r->queue);
dkCmdBufClear(r->cmdbuf);
// Destroy the scratch memory block since we don't need it anymore
dkMemBlockDestroy(scratchMemBlock);
// Retrieve the address of the character buffer
DkGpuAddr charBufAddr = dkMemBlockGetGpuAddr(r->dataMemBlock) + charBufOffset;
r->charBuf = (ConsoleChar*)((uint8_t*)dkMemBlockGetCpuAddr(r->dataMemBlock) + charBufOffset);
memset(r->charBuf, 0, charBufSize);
// Generate a command list for each framebuffer, which will bind each of them as a render target
for (unsigned i = 0; i < FB_NUM; i ++) {
DkImageView imageView;
dkImageViewDefaults(&imageView, &r->framebuffers[i]);
dkCmdBufBindRenderTarget(r->cmdbuf, &imageView, NULL);
r->cmdsBindFramebuffer[i] = dkCmdBufFinishList(r->cmdbuf);
}
// Declare structs that will be used for binding state
DkViewport viewport = { 0.0f, 0.0f, (float)width, (float)height, 0.0f, 1.0f };
DkScissor scissor = { 0, 0, width, height };
DkShader const* shaders[] = { &r->vertexShader, &r->fragmentShader };
DkRasterizerState rasterizerState;
DkColorState colorState;
DkColorWriteState colorWriteState;
// Initialize state structs with the deko3d defaults
dkRasterizerStateDefaults(&rasterizerState);
dkColorStateDefaults(&colorState);
dkColorWriteStateDefaults(&colorWriteState);
rasterizerState.fillRectangleEnable = true;
colorState.alphaCompareOp = DkCompareOp_Greater;
// Generate the main rendering command list
dkCmdBufSetViewports(r->cmdbuf, 0, &viewport, 1);
dkCmdBufSetScissors(r->cmdbuf, 0, &scissor, 1);
//dkCmdBufClearColorFloat(r->cmdbuf, 0, DkColorMask_RGBA, 0.125f, 0.294f, 0.478f, 0.0f);
dkCmdBufClearColorFloat(r->cmdbuf, 0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f);
dkCmdBufBindShaders(r->cmdbuf, DkStageFlag_GraphicsMask, shaders, sizeof(shaders)/sizeof(shaders[0]));
dkCmdBufBindRasterizerState(r->cmdbuf, &rasterizerState);
dkCmdBufBindColorState(r->cmdbuf, &colorState);
dkCmdBufBindColorWriteState(r->cmdbuf, &colorWriteState);
dkCmdBufBindUniformBuffer(r->cmdbuf, DkStage_Vertex, 0, configAddr, configSize);
dkCmdBufBindTexture(r->cmdbuf, DkStage_Fragment, 0, dkMakeTextureHandle(0, 0));
dkCmdBufBindVtxAttribState(r->cmdbuf, g_attribState, sizeof(g_attribState)/sizeof(g_attribState[0]));
dkCmdBufBindVtxBufferState(r->cmdbuf, g_vtxbufState, sizeof(g_vtxbufState)/sizeof(g_vtxbufState[0]));
dkCmdBufBindVtxBuffer(r->cmdbuf, 0, charBufAddr, charBufSize);
dkCmdBufSetAlphaRef(r->cmdbuf, 0.0f);
dkCmdBufDraw(r->cmdbuf, DkPrimitive_Triangles, 3, totalConSize, 0, 0);
r->cmdsRender = dkCmdBufFinishList(r->cmdbuf);
r->initialized = true;
return true;
}
static void GpuRenderer_deinit(PrintConsole* con)
{
struct GpuRenderer* r = GpuRenderer(con);
if (r->initialized) {
GpuRenderer_destroy(r);
}
}
static void GpuRenderer_drawChar(PrintConsole* con, int x, int y, int c)
{
struct GpuRenderer* r = GpuRenderer(con);
int writingColor = con->fg;
int screenColor = con->bg;
if (con->flags & CONSOLE_COLOR_BOLD) {
writingColor += 8;
} else if (con->flags & CONSOLE_COLOR_FAINT) {
writingColor += 16;
}
if (con->flags & CONSOLE_COLOR_REVERSE) {
int tmp = writingColor;
writingColor = screenColor;
screenColor = tmp;
}
// Wait for the fence
dkFenceWait(&r->lastRenderFence, UINT64_MAX);
ConsoleChar* pos = &r->charBuf[y*con->consoleWidth+x];
pos->tileId = c;
pos->frontPal = writingColor;
pos->backPal = screenColor;
}
static void GpuRenderer_scrollWindow(PrintConsole* con)
{
struct GpuRenderer* r = GpuRenderer(con);
// Wait for the fence
dkFenceWait(&r->lastRenderFence, UINT64_MAX);
// Perform the scrolling
for (int y = 0; y < con->windowHeight-1; y ++) {
memcpy(
&r->charBuf[(con->windowY+y+0)*con->consoleWidth + con->windowX],
&r->charBuf[(con->windowY+y+1)*con->consoleWidth + con->windowX],
sizeof(ConsoleChar)*con->windowWidth);
}
}
static void GpuRenderer_flushAndSwap(PrintConsole* con)
{
struct GpuRenderer* r = GpuRenderer(con);
// Acquire a framebuffer from the swapchain (and wait for it to be available)
int slot = dkQueueAcquireImage(r->queue, r->swapchain);
// Run the command list that binds said framebuffer as a render target
dkQueueSubmitCommands(r->queue, r->cmdsBindFramebuffer[slot]);
// Run the main rendering command list
dkQueueSubmitCommands(r->queue, r->cmdsRender);
// Signal the fence
dkQueueSignalFence(r->queue, &r->lastRenderFence, false);
// Now that we are done rendering, present it to the screen
dkQueuePresentImage(r->queue, r->swapchain, slot);
}
static struct GpuRenderer s_gpuRenderer =
{
{
GpuRenderer_init,
GpuRenderer_deinit,
GpuRenderer_drawChar,
GpuRenderer_scrollWindow,
GpuRenderer_flushAndSwap,
}
};
ConsoleRenderer* getDefaultConsoleRenderer(void)
{
return &s_gpuRenderer.base;
}

View File

@@ -19,8 +19,8 @@ namespace haze {
namespace {
/* Allow 30MiB for use by libnx. */
static constexpr size_t LibnxReservedMemorySize = 30_MB;
/* Allow 20MiB for use by libnx. */
static constexpr size_t LibnxReservedMemorySize = 20_MB;
}

View File

@@ -16,22 +16,167 @@
#include <haze.hpp>
#include <haze/ptp_data_builder.hpp>
#include <haze/ptp_data_parser.hpp>
#include <haze/ptp_responder_types.hpp>
namespace haze {
namespace {
PtpBuffers *GetBuffers() {
static constinit PtpBuffers buffers = {};
return std::addressof(buffers);
constexpr UsbCommsInterfaceInfo MtpInterfaceInfo = {
.bInterfaceClass = 0x06,
.bInterfaceSubClass = 0x01,
.bInterfaceProtocol = 0x01,
};
/* This is a VID:PID recognized by libmtp. */
constexpr u16 SwitchMtpIdVendor = 0x057e;
constexpr u16 SwitchMtpIdProduct = 0x201d;
/* Constants used for MTP GetDeviceInfo response. */
constexpr u16 MtpStandardVersion = 100;
constexpr u32 MtpVendorExtensionId = 6;
constexpr auto MtpVendorExtensionDesc = "microsoft.com: 1.0;";
constexpr u16 MtpFunctionalModeDefault = 0;
constexpr auto MtpDeviceManufacturer = "Nintendo";
constexpr auto MtpDeviceModel = "Nintendo Switch";
enum StorageId : u32 {
StorageId_SdmcFs = 0xffffffffu - 1,
};
constexpr PtpOperationCode SupportedOperationCodes[] = {
PtpOperationCode_GetDeviceInfo,
PtpOperationCode_OpenSession,
PtpOperationCode_CloseSession,
PtpOperationCode_GetStorageIds,
PtpOperationCode_GetStorageInfo,
PtpOperationCode_GetObjectHandles,
PtpOperationCode_GetObjectInfo,
PtpOperationCode_GetObject,
PtpOperationCode_SendObjectInfo,
PtpOperationCode_SendObject,
PtpOperationCode_DeleteObject,
PtpOperationCode_MtpGetObjectPropsSupported,
PtpOperationCode_MtpGetObjectPropDesc,
PtpOperationCode_MtpGetObjectPropValue,
PtpOperationCode_MtpSetObjectPropValue,
};
constexpr const PtpEventCode SupportedEventCodes[] = { /* ... */ };
constexpr const PtpDevicePropertyCode SupportedDeviceProperties[] = { /* ... */ };
constexpr const PtpObjectFormatCode SupportedCaptureFormats[] = { /* ... */ };
constexpr const PtpObjectFormatCode SupportedPlaybackFormats[] = {
PtpObjectFormatCode_Undefined,
PtpObjectFormatCode_Association,
};
constexpr const PtpObjectPropertyCode SupportedObjectProperties[] = {
PtpObjectPropertyCode_StorageId,
PtpObjectPropertyCode_ObjectFormat,
PtpObjectPropertyCode_ObjectSize,
PtpObjectPropertyCode_ObjectFileName,
PtpObjectPropertyCode_ParentObject,
PtpObjectPropertyCode_PersistentUniqueObjectIdentifier,
};
constexpr bool IsSupportedObjectPropertyCode(PtpObjectPropertyCode c) {
for (size_t i = 0; i < util::size(SupportedObjectProperties); i++) {
if (SupportedObjectProperties[i] == c) {
return true;
}
}
return false;
}
constexpr const StorageId SupportedStorageIds[] = {
StorageId_SdmcFs,
};
struct PtpStorageInfo {
PtpStorageType storage_type;
PtpFilesystemType filesystem_type;
PtpAccessCapability access_capability;
u64 max_capacity;
u64 free_space_in_bytes;
u32 free_space_in_images;
const char *storage_description;
const char *volume_label;
};
constexpr PtpStorageInfo DefaultStorageInfo = {
.storage_type = PtpStorageType_FixedRam,
.filesystem_type = PtpFilesystemType_GenericHierarchical,
.access_capability = PtpAccessCapability_ReadWrite,
.max_capacity = 0,
.free_space_in_bytes = 0,
.free_space_in_images = 0,
.storage_description = "",
.volume_label = "",
};
struct PtpObjectInfo {
StorageId storage_id;
PtpObjectFormatCode object_format;
PtpProtectionStatus protection_status;
u32 object_compressed_size;
u16 thumb_format;
u32 thumb_compressed_size;
u32 thumb_width;
u32 thumb_height;
u32 image_width;
u32 image_height;
u32 image_depth;
u32 parent_object;
PtpAssociationType association_type;
u32 association_desc;
u32 sequence_number;
const char *filename;
const char *capture_date;
const char *modification_date;
const char *keywords;
};
constexpr PtpObjectInfo DefaultObjectInfo = {
.storage_id = StorageId_SdmcFs,
.object_format = {},
.protection_status = PtpProtectionStatus_NoProtection,
.object_compressed_size = 0,
.thumb_format = 0,
.thumb_compressed_size = 0,
.thumb_width = 0,
.thumb_height = 0,
.image_width = 0,
.image_height = 0,
.image_depth = 0,
.parent_object = PtpGetObjectHandles_RootParent,
.association_type = PtpAssociationType_Undefined,
.association_desc = 0,
.sequence_number = 0,
.filename = nullptr,
.capture_date = "",
.modification_date = "",
.keywords = "",
};
constexpr s64 DirectoryReadSize = 32;
constexpr u64 FsBufferSize = haze::UsbBulkPacketBufferSize;
constinit char g_filename_str[PtpStringMaxLength + 1] = {};
constinit char g_capture_date_str[PtpStringMaxLength + 1] = {};
constinit char g_modification_date_str[PtpStringMaxLength + 1] = {};
constinit char g_keywords_str[PtpStringMaxLength + 1] = {};
constinit FsDirectoryEntry g_dir_entries[DirectoryReadSize] = {};
constinit u8 g_fs_buffer[FsBufferSize] = {};
alignas(4_KB) constinit u8 g_bulk_write_buffer[haze::UsbBulkPacketBufferSize] = {};
alignas(4_KB) constinit u8 g_bulk_read_buffer[haze::UsbBulkPacketBufferSize] = {};
}
Result PtpResponder::Initialize(EventReactor *reactor, PtpObjectHeap *object_heap) {
m_object_heap = object_heap;
m_buffers = GetBuffers();
/* Configure fs proxy. */
m_fs.Initialize(reactor, fsdevGetDeviceFileSystem("sdmc"));
@@ -91,15 +236,6 @@ namespace haze {
R_CATCH(haze::ResultInvalidPropertyValue) {
R_TRY(this->WriteResponse(PtpResponseCode_MtpInvalidObjectPropValue));
}
R_CATCH(haze::ResultGroupSpecified) {
R_TRY(this->WriteResponse(PtpResponseCode_MtpSpecificationByGroupUnsupported));
}
R_CATCH(haze::ResultDepthSpecified) {
R_TRY(this->WriteResponse(PtpResponseCode_MtpSpecificationByDepthUnsupported));
}
R_CATCH(haze::ResultInvalidArgument) {
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
}
R_CATCH_MODULE(fs) {
/* Errors from fs are typically recoverable. */
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
@@ -110,7 +246,7 @@ namespace haze {
}
Result PtpResponder::HandleRequestImpl() {
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
PtpDataParser dp(g_bulk_read_buffer, std::addressof(m_usb_server));
R_TRY(dp.Read(std::addressof(m_request_header)));
switch (m_request_header.type) {
@@ -140,12 +276,6 @@ namespace haze {
case PtpOperationCode_MtpGetObjectPropDesc: R_RETURN(this->GetObjectPropDesc(dp)); break;
case PtpOperationCode_MtpGetObjectPropValue: R_RETURN(this->GetObjectPropValue(dp)); break;
case PtpOperationCode_MtpSetObjectPropValue: R_RETURN(this->SetObjectPropValue(dp)); break;
case PtpOperationCode_MtpGetObjPropList: R_RETURN(this->GetObjectPropList(dp)); break;
case PtpOperationCode_AndroidGetPartialObject64: R_RETURN(this->GetPartialObject64(dp)); break;
case PtpOperationCode_AndroidSendPartialObject: R_RETURN(this->SendPartialObject(dp)); break;
case PtpOperationCode_AndroidTruncateObject: R_RETURN(this->TruncateObject(dp)); break;
case PtpOperationCode_AndroidBeginEditObject: R_RETURN(this->BeginEditObject(dp)); break;
case PtpOperationCode_AndroidEndEditObject: R_RETURN(this->EndEditObject(dp)); break;
default: R_THROW(haze::ResultOperationNotSupported());
}
}
@@ -157,17 +287,756 @@ namespace haze {
}
}
Result PtpResponder::WriteResponse(PtpResponseCode code, const void* data, size_t size) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.AddResponseHeader(m_request_header, code, size));
R_TRY(db.AddBuffer(reinterpret_cast<const u8*>(data), size));
template <typename Data>
Result PtpResponder::WriteResponse(PtpResponseCode code, Data &&data) {
PtpDataBuilder db(g_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(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
PtpDataBuilder db(g_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(g_bulk_write_buffer, std::addressof(m_usb_server));
/* Write the device info data. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
R_TRY(db.Add(MtpStandardVersion));
R_TRY(db.Add(MtpVendorExtensionId));
R_TRY(db.Add(MtpStandardVersion));
R_TRY(db.AddString(MtpVendorExtensionDesc));
R_TRY(db.Add(MtpFunctionalModeDefault));
R_TRY(db.AddArray(SupportedOperationCodes, util::size(SupportedOperationCodes)));
R_TRY(db.AddArray(SupportedEventCodes, util::size(SupportedEventCodes)));
R_TRY(db.AddArray(SupportedDeviceProperties, util::size(SupportedDeviceProperties)));
R_TRY(db.AddArray(SupportedCaptureFormats, util::size(SupportedCaptureFormats)));
R_TRY(db.AddArray(SupportedPlaybackFormats, util::size(SupportedPlaybackFormats)));
R_TRY(db.AddString(MtpDeviceManufacturer));
R_TRY(db.AddString(MtpDeviceModel));
R_TRY(db.AddString(GetFirmwareVersion()));
R_TRY(db.AddString(GetSerialNumber()));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::OpenSession(PtpDataParser &dp) {
R_TRY(dp.Finalize());
/* Close, if we're already open. */
this->ForceCloseSession();
/* Initialize the database. */
m_session_open = true;
m_object_database.Initialize(m_object_heap);
/* Create the root storages. */
PtpObject *object;
R_TRY(m_object_database.CreateOrFindObject("", "", PtpGetObjectHandles_RootParent, std::addressof(object)));
/* Register the root storages. */
m_object_database.RegisterObject(object, StorageId_SdmcFs);
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::CloseSession(PtpDataParser &dp) {
R_TRY(dp.Finalize());
this->ForceCloseSession();
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetStorageIds(PtpDataParser &dp) {
R_TRY(dp.Finalize());
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
/* Write the storage ID array. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
R_RETURN(db.AddArray(SupportedStorageIds, util::size(SupportedStorageIds)));
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetStorageInfo(PtpDataParser &dp) {
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
PtpStorageInfo storage_info(DefaultStorageInfo);
/* Get the storage ID the client requested information for. */
u32 storage_id;
R_TRY(dp.Read(std::addressof(storage_id)));
R_TRY(dp.Finalize());
/* Get the info from fs. */
switch (storage_id) {
case StorageId_SdmcFs:
{
s64 total_space, free_space;
R_TRY(m_fs.GetTotalSpace("/", std::addressof(total_space)));
R_TRY(m_fs.GetFreeSpace("/", std::addressof(free_space)));
storage_info.max_capacity = total_space;
storage_info.free_space_in_bytes = free_space;
storage_info.free_space_in_images = 0;
storage_info.storage_description = "SD Card";
}
break;
default:
R_THROW(haze::ResultInvalidStorageId());
}
/* Write the storage info data. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
R_TRY(db.Add(storage_info.storage_type));
R_TRY(db.Add(storage_info.filesystem_type));
R_TRY(db.Add(storage_info.access_capability));
R_TRY(db.Add(storage_info.max_capacity));
R_TRY(db.Add(storage_info.free_space_in_bytes));
R_TRY(db.Add(storage_info.free_space_in_images));
R_TRY(db.AddString(storage_info.storage_description));
R_TRY(db.AddString(storage_info.volume_label));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectHandles(PtpDataParser &dp) {
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID the client requested enumeration for. */
u32 storage_id, object_format_code, association_object_handle;
R_TRY(dp.Read(std::addressof(storage_id)));
R_TRY(dp.Read(std::addressof(object_format_code)));
R_TRY(dp.Read(std::addressof(association_object_handle)));
R_TRY(dp.Finalize());
/* Handle top-level requests. */
if (storage_id == PtpGetObjectHandles_AllStorage) {
storage_id = StorageId_SdmcFs;
}
/* Rewrite requests for enumerating storage directories. */
if (association_object_handle == PtpGetObjectHandles_RootParent) {
association_object_handle = storage_id;
}
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(association_object_handle);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Try to read the object as a directory. */
FsDir dir;
R_TRY(m_fs.OpenDirectory(obj->GetName(), FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, std::addressof(dir)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseDirectory(std::addressof(dir)); };
/* Count how many entries are in the directory. */
s64 entry_count = 0;
R_TRY(m_fs.GetDirectoryEntryCount(std::addressof(dir), std::addressof(entry_count)));
/* Begin writing. */
R_TRY(db.AddDataHeader(m_request_header, sizeof(u32) + (entry_count * sizeof(u32))));
R_TRY(db.Add(static_cast<u32>(entry_count)));
/* Enumerate the directory, writing results to the data builder as we progress. */
/* TODO: How should we handle the directory contents changing during enumeration? */
/* Is this even feasible to handle? */
while (true) {
/* Get the next batch. */
s64 read_count = 0;
R_TRY(m_fs.ReadDirectory(std::addressof(dir), std::addressof(read_count), DirectoryReadSize, g_dir_entries));
/* Write to output. */
for (s64 i = 0; i < read_count; i++) {
u32 handle;
R_TRY(m_object_database.CreateAndRegisterObjectId(obj->GetName(), g_dir_entries[i].name, obj->GetObjectId(), std::addressof(handle)));
R_TRY(db.Add(handle));
}
/* If we read fewer than the batch size, we're done. */
if (read_count < DirectoryReadSize) {
break;
}
}
/* Flush the data response. */
R_TRY(db.Commit());
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectInfo(PtpDataParser &dp) {
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID the client requested info for. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Build info about the object. */
PtpObjectInfo object_info(DefaultObjectInfo);
if (object_id == StorageId_SdmcFs) {
/* The SD Card directory has some special properties. */
object_info.object_format = PtpObjectFormatCode_Association;
object_info.association_type = PtpAssociationType_GenericFolder;
object_info.filename = "SD Card";
} else {
/* Figure out what type of object this is. */
FsDirEntryType entry_type;
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
/* Get the size, if we are requesting info about a file. */
s64 size = 0;
if (entry_type == FsDirEntryType_File) {
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size)));
}
object_info.filename = std::strrchr(obj->GetName(), '/') + 1;
object_info.object_compressed_size = size;
object_info.parent_object = obj->GetParentId();
if (entry_type == FsDirEntryType_Dir) {
object_info.object_format = PtpObjectFormatCode_Association;
object_info.association_type = PtpAssociationType_GenericFolder;
} else {
object_info.object_format = PtpObjectFormatCode_Undefined;
object_info.association_type = PtpAssociationType_Undefined;
}
}
/* Write the object info data. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
R_TRY(db.Add(object_info.storage_id));
R_TRY(db.Add(object_info.object_format));
R_TRY(db.Add(object_info.protection_status));
R_TRY(db.Add(object_info.object_compressed_size));
R_TRY(db.Add(object_info.thumb_format));
R_TRY(db.Add(object_info.thumb_compressed_size));
R_TRY(db.Add(object_info.thumb_width));
R_TRY(db.Add(object_info.thumb_height));
R_TRY(db.Add(object_info.image_width));
R_TRY(db.Add(object_info.image_height));
R_TRY(db.Add(object_info.image_depth));
R_TRY(db.Add(object_info.parent_object));
R_TRY(db.Add(object_info.association_type));
R_TRY(db.Add(object_info.association_desc));
R_TRY(db.Add(object_info.sequence_number));
R_TRY(db.AddString(object_info.filename));
R_TRY(db.AddString(object_info.capture_date));
R_TRY(db.AddString(object_info.modification_date));
R_TRY(db.AddString(object_info.keywords));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObject(PtpDataParser &dp) {
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID the client requested. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Get the file's size. */
s64 size = 0;
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size)));
/* Send the header and file size. */
R_TRY(db.AddDataHeader(m_request_header, size));
/* Begin reading the file, writing data to the builder as we progress. */
s64 offset = 0;
while (true) {
/* Get the next batch. */
u64 bytes_read;
R_TRY(m_fs.ReadFile(std::addressof(file), offset, g_fs_buffer, FsBufferSize, FsReadOption_None, std::addressof(bytes_read)));
offset += bytes_read;
/* Write to output. */
R_TRY(db.AddBuffer(g_fs_buffer, bytes_read));
/* If we read fewer bytes than the batch size, we're done. */
if (bytes_read < FsBufferSize) {
break;
}
}
/* Flush the data response. */
R_TRY(db.Commit());
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::SendObjectInfo(PtpDataParser &rdp) {
/* Get the storage ID and parent object and flush the request packet. */
u32 storage_id, parent_object;
R_TRY(rdp.Read(std::addressof(storage_id)));
R_TRY(rdp.Read(std::addressof(parent_object)));
R_TRY(rdp.Finalize());
PtpDataParser dp(g_bulk_read_buffer, std::addressof(m_usb_server));
PtpObjectInfo info(DefaultObjectInfo);
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Read in the object info. */
R_TRY(dp.Read(std::addressof(info.storage_id)));
R_TRY(dp.Read(std::addressof(info.object_format)));
R_TRY(dp.Read(std::addressof(info.protection_status)));
R_TRY(dp.Read(std::addressof(info.object_compressed_size)));
R_TRY(dp.Read(std::addressof(info.thumb_format)));
R_TRY(dp.Read(std::addressof(info.thumb_compressed_size)));
R_TRY(dp.Read(std::addressof(info.thumb_width)));
R_TRY(dp.Read(std::addressof(info.thumb_height)));
R_TRY(dp.Read(std::addressof(info.image_width)));
R_TRY(dp.Read(std::addressof(info.image_height)));
R_TRY(dp.Read(std::addressof(info.image_depth)));
R_TRY(dp.Read(std::addressof(info.parent_object)));
R_TRY(dp.Read(std::addressof(info.association_type)));
R_TRY(dp.Read(std::addressof(info.association_desc)));
R_TRY(dp.Read(std::addressof(info.sequence_number)));
R_TRY(dp.ReadString(g_filename_str));
R_TRY(dp.ReadString(g_capture_date_str));
R_TRY(dp.ReadString(g_modification_date_str));
R_TRY(dp.ReadString(g_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 * const parentobj = m_object_database.GetObjectById(parent_object);
R_UNLESS(parentobj != nullptr, haze::ResultInvalidObjectId());
/* Make a new object with the intended name. */
PtpNewObjectInfo new_object_info;
new_object_info.storage_id = StorageId_SdmcFs;
new_object_info.parent_object_id = parent_object == storage_id ? 0 : parent_object;
/* Create the object in the database. */
PtpObject *obj;
R_TRY(m_object_database.CreateOrFindObject(parentobj->GetName(), g_filename_str, parentobj->GetObjectId(), std::addressof(obj)));
/* Ensure we maintain a clean state on failure. */
ON_RESULT_FAILURE { m_object_database.DeleteObject(obj); };
/* Register the object with a new ID. */
m_object_database.RegisterObject(obj);
new_object_info.object_id = obj->GetObjectId();
/* Create the object on the filesystem. */
if (info.object_format == PtpObjectFormatCode_Association) {
R_TRY(m_fs.CreateDirectory(obj->GetName()));
m_send_object_id = 0;
} else {
R_TRY(m_fs.CreateFile(obj->GetName(), 0, 0));
m_send_object_id = new_object_info.object_id;
}
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok, new_object_info));
}
Result PtpResponder::SendObject(PtpDataParser &rdp) {
/* Reset SendObject object ID on exit. */
ON_SCOPE_EXIT { m_send_object_id = 0; };
R_TRY(rdp.Finalize());
PtpDataParser dp(g_bulk_read_buffer, std::addressof(m_usb_server));
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(m_send_object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Truncate the file after locking for write. */
s64 offset = 0;
R_TRY(m_fs.SetFileSize(std::addressof(file), 0));
/* Expand to the needed size. */
if (data_header.length > sizeof(PtpUsbBulkContainer)) {
R_TRY(m_fs.SetFileSize(std::addressof(file), data_header.length - sizeof(PtpUsbBulkContainer)));
}
/* Begin writing to the filesystem. */
while (true) {
/* Read as many bytes as we can. */
u32 bytes_received;
const Result read_res = dp.ReadBuffer(g_fs_buffer, FsBufferSize, std::addressof(bytes_received));
/* Write to the file. */
R_TRY(m_fs.WriteFile(std::addressof(file), offset, g_fs_buffer, bytes_received, 0));
offset += bytes_received;
/* If we received fewer bytes than the batch size, we're done. */
if (haze::ResultEndOfTransmission::Includes(read_res)) {
break;
}
R_TRY(read_res);
}
/* Truncate the file to the received size. */
R_TRY(m_fs.SetFileSize(std::addressof(file), offset));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::DeleteObject(PtpDataParser &dp) {
/* Get the object ID and flush the request packet. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Disallow deleting the storage root. */
R_UNLESS(object_id != StorageId_SdmcFs, haze::ResultInvalidObjectId());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Figure out what type of object this is. */
FsDirEntryType entry_type;
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
/* Remove the object from the filesystem. */
if (entry_type == FsDirEntryType_Dir) {
R_TRY(m_fs.DeleteDirectoryRecursively(obj->GetName()));
} else {
R_TRY(m_fs.DeleteFile(obj->GetName()));
}
/* Remove the object from the database. */
m_object_database.DeleteObject(obj);
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectPropsSupported(PtpDataParser &dp) {
R_TRY(dp.Finalize());
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
/* Write information about all object properties we can support. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
R_RETURN(db.AddArray(SupportedObjectProperties, util::size(SupportedObjectProperties)));
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectPropDesc(PtpDataParser &dp) {
PtpObjectPropertyCode property_code;
u16 object_format;
R_TRY(dp.Read(std::addressof(property_code)));
R_TRY(dp.Read(std::addressof(object_format)));
R_TRY(dp.Finalize());
/* Ensure we have a valid property code before continuing. */
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
/* Begin writing information about the property code. */
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
R_TRY(db.Add(property_code));
/* Each property code corresponds to a different pattern, which contains the data type, */
/* whether the property can be set for an object, and the default value of the property. */
switch (property_code) {
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
{
R_TRY(db.Add(PtpDataTypeCode_U128));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add<u128>(0));
}
case PtpObjectPropertyCode_ObjectSize:
{
R_TRY(db.Add(PtpDataTypeCode_U64));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add<u64>(0));
}
break;
case PtpObjectPropertyCode_StorageId:
case PtpObjectPropertyCode_ParentObject:
{
R_TRY(db.Add(PtpDataTypeCode_U32));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add(StorageId_SdmcFs));
}
break;
case PtpObjectPropertyCode_ObjectFormat:
{
R_TRY(db.Add(PtpDataTypeCode_U16));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add(PtpObjectFormatCode_Undefined));
}
break;
case PtpObjectPropertyCode_ObjectFileName:
{
R_TRY(db.Add(PtpDataTypeCode_String));
R_TRY(db.Add(PtpPropertyGetSetFlag_GetSet));
R_TRY(db.AddString(""));
}
break;
HAZE_UNREACHABLE_DEFAULT_CASE();
}
/* Group code is a required part of the response, but doesn't seem to be used for anything. */
R_TRY(db.Add(PtpPropertyGroupCode_Default));
/* We don't use the form flag. */
R_TRY(db.Add(PtpPropertyFormFlag_None));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectPropValue(PtpDataParser &dp) {
u32 object_id;
PtpObjectPropertyCode property_code;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Read(std::addressof(property_code)));
R_TRY(dp.Finalize());
/* Disallow renaming the storage root. */
R_UNLESS(object_id != StorageId_SdmcFs, haze::ResultInvalidObjectId());
/* Ensure we have a valid property code before continuing. */
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Define helper for getting the object type. */
const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) {
R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type));
};
/* Define helper for getting the object size. */
const auto GetObjectSize = [&] (s64 *out_size) {
*out_size = 0;
/* Check if this is a directory. */
FsDirEntryType entry_type;
R_TRY(GetObjectType(std::addressof(entry_type)));
/* If it is, we're done. */
R_SUCCEED_IF(entry_type == FsDirEntryType_Dir);
/* Otherwise, open as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size));
};
/* Begin writing the requested object property. */
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
switch (property_code) {
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
{
R_TRY(db.Add<u128>(object_id));
}
break;
case PtpObjectPropertyCode_ObjectSize:
{
s64 size;
R_TRY(GetObjectSize(std::addressof(size)));
R_TRY(db.Add<u64>(size));
}
break;
case PtpObjectPropertyCode_StorageId:
{
R_TRY(db.Add(StorageId_SdmcFs));
}
break;
case PtpObjectPropertyCode_ParentObject:
{
R_TRY(db.Add(obj->GetParentId()));
}
break;
case PtpObjectPropertyCode_ObjectFormat:
{
FsDirEntryType entry_type;
R_TRY(GetObjectType(std::addressof(entry_type)));
R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association));
}
break;
case PtpObjectPropertyCode_ObjectFileName:
{
R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1));
}
break;
HAZE_UNREACHABLE_DEFAULT_CASE();
}
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::SetObjectPropValue(PtpDataParser &rdp) {
u32 object_id;
PtpObjectPropertyCode property_code;
R_TRY(rdp.Read(std::addressof(object_id)));
R_TRY(rdp.Read(std::addressof(property_code)));
R_TRY(rdp.Finalize());
PtpDataParser dp(g_bulk_read_buffer, std::addressof(m_usb_server));
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Ensure we have a valid property code before continuing. */
R_UNLESS(property_code == PtpObjectPropertyCode_ObjectFileName, haze::ResultUnknownPropertyCode());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* We are reading a file name. */
R_TRY(dp.ReadString(g_filename_str));
R_TRY(dp.Finalize());
/* Ensure we can actually process the new name. */
const bool is_empty = g_filename_str[0] == '\x00';
const bool contains_slashes = std::strchr(g_filename_str, '/') != nullptr;
R_UNLESS(!is_empty && !contains_slashes, haze::ResultInvalidPropertyValue());
/* Add a new object in the database with the new name. */
PtpObject *newobj;
{
/* Find the last path separator in the existing object name. */
char *pathsep = std::strrchr(obj->m_name, '/');
HAZE_ASSERT(pathsep != nullptr);
/* Temporarily mark the path separator as null to facilitate processing. */
*pathsep = '\x00';
ON_SCOPE_EXIT { *pathsep = '/'; };
R_TRY(m_object_database.CreateOrFindObject(obj->GetName(), g_filename_str, obj->GetParentId(), std::addressof(newobj)));
}
{
/* Ensure we maintain a clean state on failure. */
ON_RESULT_FAILURE {
if (!newobj->GetIsRegistered()) {
/* Only delete if the object was not registered. */
/* Otherwise, we would remove an object that still exists. */
m_object_database.DeleteObject(newobj);
}
};
/* Get the old object type. */
FsDirEntryType entry_type;
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
/* Attempt to rename the object on the filesystem. */
if (entry_type == FsDirEntryType_Dir) {
R_TRY(m_fs.RenameDirectory(obj->GetName(), newobj->GetName()));
} else {
R_TRY(m_fs.RenameFile(obj->GetName(), newobj->GetName()));
}
}
/* Unregister and free the old object. */
m_object_database.DeleteObject(obj);
/* Register the new object. */
m_object_database.RegisterObject(newobj, object_id);
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
}

View File

@@ -1,203 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <haze.hpp>
#include <haze/ptp_data_builder.hpp>
#include <haze/ptp_data_parser.hpp>
#include <haze/ptp_responder_types.hpp>
namespace haze {
Result PtpResponder::GetPartialObject64(PtpDataParser &dp) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID, offset, and size for the file we want to read. */
u32 object_id, size;
u64 offset;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Read(std::addressof(offset)));
R_TRY(dp.Read(std::addressof(size)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Get the file's size. */
s64 file_size = 0;
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(file_size)));
/* Ensure the requested offset and size are within range. */
R_UNLESS(offset + size > offset, haze::ResultInvalidArgument());
R_UNLESS(static_cast<u64>(file_size) <= offset + size, haze::ResultInvalidArgument());
/* Send the header and data size. */
R_TRY(db.AddDataHeader(m_request_header, size));
/* Begin reading the file, writing data to the builder as we progress. */
s64 size_remaining = size;
while (true) {
/* Get the next batch. */
u64 bytes_to_read = std::min<s64>(FsBufferSize, size_remaining);
u64 bytes_read;
R_TRY(m_fs.ReadFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_to_read, FsReadOption_None, std::addressof(bytes_read)));
size_remaining -= bytes_read;
offset += bytes_read;
/* Write to output. */
R_TRY(db.AddBuffer(m_buffers->file_system_data_buffer, bytes_read));
/* If we read fewer bytes than the batch size, or have read enough data, we're done. */
if (bytes_read < FsBufferSize || size_remaining == 0) {
break;
}
}
/* Flush the data response. */
R_TRY(db.Commit());
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::SendPartialObject(PtpDataParser &rdp) {
/* Get the object ID, offset, and size for the file we want to write. */
u32 object_id, size;
u64 offset;
R_TRY(rdp.Read(std::addressof(object_id)));
R_TRY(rdp.Read(std::addressof(size)));
R_TRY(rdp.Read(std::addressof(offset)));
R_TRY(rdp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(m_send_object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Get the file's size. */
s64 file_size = 0;
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(file_size)));
/* Ensure the requested offset and size are within range. */
R_UNLESS(offset + size > offset, haze::ResultInvalidArgument());
R_UNLESS(static_cast<u64>(file_size) <= offset, haze::ResultInvalidArgument());
/* Prepare a data parser for the data we are about to receive. */
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Begin writing to the filesystem. */
s64 size_remaining = size;
while (true) {
/* Read as many bytes as we can. */
u32 bytes_received;
const Result read_res = dp.ReadBuffer(m_buffers->file_system_data_buffer, FsBufferSize, std::addressof(bytes_received));
/* Write to the file. */
u32 bytes_to_write = std::min<s64>(size_remaining, bytes_received);
R_TRY(m_fs.WriteFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_to_write, 0));
size_remaining -= bytes_to_write;
offset += bytes_to_write;
/* If we received fewer bytes than the batch size, or have written enough data, we're done. */
if (haze::ResultEndOfTransmission::Includes(read_res) || size_remaining == 0) {
break;
}
R_TRY(read_res);
}
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::TruncateObject(PtpDataParser &dp) {
/* Get the object ID and size for the file we want to truncate. */
u32 object_id;
u64 size;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Read(std::addressof(size)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Truncate the file. */
R_TRY(m_fs.SetFileSize(std::addressof(file), size));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::BeginEditObject(PtpDataParser &dp) {
/* Get the object ID we are going to begin editing. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* We don't implement transactions, so write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::EndEditObject(PtpDataParser &dp) {
/* Get the object ID we are going to finish editing. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* We don't implement transactions, so write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
}

View File

@@ -1,407 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <haze.hpp>
#include <haze/ptp_data_builder.hpp>
#include <haze/ptp_data_parser.hpp>
#include <haze/ptp_responder_types.hpp>
namespace haze {
Result PtpResponder::GetObjectPropsSupported(PtpDataParser &dp) {
R_TRY(dp.Finalize());
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Write information about all object properties we can support. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
R_RETURN(db.AddArray(SupportedObjectProperties, util::size(SupportedObjectProperties)));
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectPropDesc(PtpDataParser &dp) {
PtpObjectPropertyCode property_code;
u16 object_format;
R_TRY(dp.Read(std::addressof(property_code)));
R_TRY(dp.Read(std::addressof(object_format)));
R_TRY(dp.Finalize());
/* Ensure we have a valid property code before continuing. */
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
/* Begin writing information about the property code. */
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
R_TRY(db.Add(property_code));
/* Each property code corresponds to a different pattern, which contains the data type, */
/* whether the property can be set for an object, and the default value of the property. */
switch (property_code) {
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
{
R_TRY(db.Add(PtpDataTypeCode_U128));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add<u128>(0));
}
case PtpObjectPropertyCode_ObjectSize:
{
R_TRY(db.Add(PtpDataTypeCode_U64));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add<u64>(0));
}
break;
case PtpObjectPropertyCode_StorageId:
case PtpObjectPropertyCode_ParentObject:
{
R_TRY(db.Add(PtpDataTypeCode_U32));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add(StorageId_SdmcFs));
}
break;
case PtpObjectPropertyCode_ObjectFormat:
{
R_TRY(db.Add(PtpDataTypeCode_U16));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add(PtpObjectFormatCode_Undefined));
}
break;
case PtpObjectPropertyCode_ObjectFileName:
{
R_TRY(db.Add(PtpDataTypeCode_String));
R_TRY(db.Add(PtpPropertyGetSetFlag_GetSet));
R_TRY(db.AddString(""));
}
break;
HAZE_UNREACHABLE_DEFAULT_CASE();
}
/* Group code is a required part of the response, but doesn't seem to be used for anything. */
R_TRY(db.Add(PtpPropertyGroupCode_Default));
/* We don't use the form flag. */
R_TRY(db.Add(PtpPropertyFormFlag_None));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectPropValue(PtpDataParser &dp) {
u32 object_id;
PtpObjectPropertyCode property_code;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Read(std::addressof(property_code)));
R_TRY(dp.Finalize());
/* Ensure we have a valid property code before continuing. */
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Define helper for getting the object type. */
const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) {
R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type));
};
/* Define helper for getting the object size. */
const auto GetObjectSize = [&] (s64 *out_size) {
*out_size = 0;
/* Check if this is a directory. */
FsDirEntryType entry_type;
R_TRY(GetObjectType(std::addressof(entry_type)));
/* If it is, we're done. */
R_SUCCEED_IF(entry_type == FsDirEntryType_Dir);
/* Otherwise, open as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size));
};
/* Begin writing the requested object property. */
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
switch (property_code) {
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
{
R_TRY(db.Add<u128>(object_id));
}
break;
case PtpObjectPropertyCode_ObjectSize:
{
s64 size;
R_TRY(GetObjectSize(std::addressof(size)));
R_TRY(db.Add<u64>(size));
}
break;
case PtpObjectPropertyCode_StorageId:
{
R_TRY(db.Add(StorageId_SdmcFs));
}
break;
case PtpObjectPropertyCode_ParentObject:
{
R_TRY(db.Add(obj->GetParentId()));
}
break;
case PtpObjectPropertyCode_ObjectFormat:
{
FsDirEntryType entry_type;
R_TRY(GetObjectType(std::addressof(entry_type)));
R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association));
}
break;
case PtpObjectPropertyCode_ObjectFileName:
{
R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1));
}
break;
HAZE_UNREACHABLE_DEFAULT_CASE();
}
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectPropList(PtpDataParser &dp) {
u32 object_id;
u32 object_format;
s32 property_code;
s32 group_code;
s32 depth;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Read(std::addressof(object_format)));
R_TRY(dp.Read(std::addressof(property_code)));
R_TRY(dp.Read(std::addressof(group_code)));
R_TRY(dp.Read(std::addressof(depth)));
R_TRY(dp.Finalize());
/* Ensure format is unspecified. */
R_UNLESS(object_format == 0, haze::ResultInvalidArgument());
/* Ensure we have a valid property code. */
R_UNLESS(property_code == -1 || IsSupportedObjectPropertyCode(PtpObjectPropertyCode(property_code)), haze::ResultUnknownPropertyCode());
/* Ensure group code is the default. */
R_UNLESS(group_code == PtpPropertyGroupCode_Default, haze::ResultGroupSpecified());
/* Ensure depth is 0. */
R_UNLESS(depth == 0, haze::ResultDepthSpecified());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Define helper for getting the object type. */
const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) {
R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type));
};
/* Define helper for getting the object size. */
const auto GetObjectSize = [&] (s64 *out_size) {
*out_size = 0;
/* Check if this is a directory. */
FsDirEntryType entry_type;
R_TRY(GetObjectType(std::addressof(entry_type)));
/* If it is, we're done. */
R_SUCCEED_IF(entry_type == FsDirEntryType_Dir);
/* Otherwise, open as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size));
};
/* Define helper for determining if the property should be included. */
const auto ShouldIncludeProperty = [&] (PtpObjectPropertyCode code) {
/* If all properties were requested, or it was the requested property, we should include the property. */
return property_code == -1 || code == property_code;
};
/* Begin writing the requested object properties. */
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
for (const auto obj_property : SupportedObjectProperties) {
if (!ShouldIncludeProperty(obj_property)) {
continue;
}
/* Write the object handle. */
R_TRY(db.Add<u32>(object_id));
/* Write the property code. */
R_TRY(db.Add<u16>(obj_property));
/* Write the property value. */
switch (obj_property) {
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
{
R_TRY(db.Add(PtpDataTypeCode_U128));
R_TRY(db.Add<u128>(object_id));
}
break;
case PtpObjectPropertyCode_ObjectSize:
{
s64 size;
R_TRY(GetObjectSize(std::addressof(size)));
R_TRY(db.Add(PtpDataTypeCode_U64));
R_TRY(db.Add<u64>(size));
}
break;
case PtpObjectPropertyCode_StorageId:
{
R_TRY(db.Add(PtpDataTypeCode_U32));
R_TRY(db.Add(StorageId_SdmcFs));
}
break;
case PtpObjectPropertyCode_ParentObject:
{
R_TRY(db.Add(PtpDataTypeCode_U32));
R_TRY(db.Add(obj->GetParentId()));
}
break;
case PtpObjectPropertyCode_ObjectFormat:
{
FsDirEntryType entry_type;
R_TRY(GetObjectType(std::addressof(entry_type)));
R_TRY(db.Add(PtpDataTypeCode_U16));
R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association));
}
break;
case PtpObjectPropertyCode_ObjectFileName:
{
R_TRY(db.Add(PtpDataTypeCode_String));
R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1));
}
break;
HAZE_UNREACHABLE_DEFAULT_CASE();
}
}
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::SetObjectPropValue(PtpDataParser &rdp) {
u32 object_id;
PtpObjectPropertyCode property_code;
R_TRY(rdp.Read(std::addressof(object_id)));
R_TRY(rdp.Read(std::addressof(property_code)));
R_TRY(rdp.Finalize());
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Ensure we have a valid property code before continuing. */
R_UNLESS(property_code == PtpObjectPropertyCode_ObjectFileName, haze::ResultUnknownPropertyCode());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* We are reading a file name. */
R_TRY(dp.ReadString(m_buffers->filename_string_buffer));
R_TRY(dp.Finalize());
/* Ensure we can actually process the new name. */
const bool is_empty = m_buffers->filename_string_buffer[0] == '\x00';
const bool contains_slashes = std::strchr(m_buffers->filename_string_buffer, '/') != nullptr;
R_UNLESS(!is_empty && !contains_slashes, haze::ResultInvalidPropertyValue());
/* Add a new object in the database with the new name. */
PtpObject *newobj;
{
/* Find the last path separator in the existing object name. */
char *pathsep = std::strrchr(obj->m_name, '/');
HAZE_ASSERT(pathsep != nullptr);
/* Temporarily mark the path separator as null to facilitate processing. */
*pathsep = '\x00';
ON_SCOPE_EXIT { *pathsep = '/'; };
R_TRY(m_object_database.CreateOrFindObject(obj->GetName(), m_buffers->filename_string_buffer, obj->GetParentId(), std::addressof(newobj)));
}
{
/* Ensure we maintain a clean state on failure. */
ON_RESULT_FAILURE {
if (!newobj->GetIsRegistered()) {
/* Only delete if the object was not registered. */
/* Otherwise, we would remove an object that still exists. */
m_object_database.DeleteObject(newobj);
}
};
/* Get the old object type. */
FsDirEntryType entry_type;
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
/* Attempt to rename the object on the filesystem. */
if (entry_type == FsDirEntryType_Dir) {
R_TRY(m_fs.RenameDirectory(obj->GetName(), newobj->GetName()));
} else {
R_TRY(m_fs.RenameFile(obj->GetName(), newobj->GetName()));
}
}
/* Unregister and free the old object. */
m_object_database.DeleteObject(obj);
/* Register the new object. */
m_object_database.RegisterObject(newobj, object_id);
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
}

View File

@@ -1,507 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <haze.hpp>
#include <haze/ptp_data_builder.hpp>
#include <haze/ptp_data_parser.hpp>
#include <haze/ptp_responder_types.hpp>
namespace haze {
Result PtpResponder::GetDeviceInfo(PtpDataParser &dp) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Write the device info data. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
R_TRY(db.Add(MtpStandardVersion));
R_TRY(db.Add(MtpVendorExtensionId));
R_TRY(db.Add(MtpStandardVersion));
R_TRY(db.AddString(MtpVendorExtensionDesc));
R_TRY(db.Add(MtpFunctionalModeDefault));
R_TRY(db.AddArray(SupportedOperationCodes, util::size(SupportedOperationCodes)));
R_TRY(db.AddArray(SupportedEventCodes, util::size(SupportedEventCodes)));
R_TRY(db.AddArray(SupportedDeviceProperties, util::size(SupportedDeviceProperties)));
R_TRY(db.AddArray(SupportedCaptureFormats, util::size(SupportedCaptureFormats)));
R_TRY(db.AddArray(SupportedPlaybackFormats, util::size(SupportedPlaybackFormats)));
R_TRY(db.AddString(MtpDeviceManufacturer));
R_TRY(db.AddString(MtpDeviceModel));
R_TRY(db.AddString(GetFirmwareVersion()));
R_TRY(db.AddString(GetSerialNumber()));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::OpenSession(PtpDataParser &dp) {
R_TRY(dp.Finalize());
/* Close, if we're already open. */
this->ForceCloseSession();
/* Initialize the database. */
m_session_open = true;
m_object_database.Initialize(m_object_heap);
/* Create the root storages. */
PtpObject *object;
R_TRY(m_object_database.CreateOrFindObject("", "", PtpGetObjectHandles_RootParent, std::addressof(object)));
/* Register the root storages. */
m_object_database.RegisterObject(object, StorageId_SdmcFs);
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::CloseSession(PtpDataParser &dp) {
R_TRY(dp.Finalize());
this->ForceCloseSession();
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetStorageIds(PtpDataParser &dp) {
R_TRY(dp.Finalize());
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Write the storage ID array. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
R_RETURN(db.AddArray(SupportedStorageIds, util::size(SupportedStorageIds)));
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetStorageInfo(PtpDataParser &dp) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
PtpStorageInfo storage_info(DefaultStorageInfo);
/* Get the storage ID the client requested information for. */
u32 storage_id;
R_TRY(dp.Read(std::addressof(storage_id)));
R_TRY(dp.Finalize());
/* Get the info from fs. */
switch (storage_id) {
case StorageId_SdmcFs:
{
s64 total_space, free_space;
R_TRY(m_fs.GetTotalSpace("/", std::addressof(total_space)));
R_TRY(m_fs.GetFreeSpace("/", std::addressof(free_space)));
storage_info.max_capacity = total_space;
storage_info.free_space_in_bytes = free_space;
storage_info.free_space_in_images = 0;
storage_info.storage_description = "SD Card";
}
break;
default:
R_THROW(haze::ResultInvalidStorageId());
}
/* Write the storage info data. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
R_TRY(db.Add(storage_info.storage_type));
R_TRY(db.Add(storage_info.filesystem_type));
R_TRY(db.Add(storage_info.access_capability));
R_TRY(db.Add(storage_info.max_capacity));
R_TRY(db.Add(storage_info.free_space_in_bytes));
R_TRY(db.Add(storage_info.free_space_in_images));
R_TRY(db.AddString(storage_info.storage_description));
R_TRY(db.AddString(storage_info.volume_label));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectHandles(PtpDataParser &dp) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID the client requested enumeration for. */
u32 storage_id, object_format_code, association_object_handle;
R_TRY(dp.Read(std::addressof(storage_id)));
R_TRY(dp.Read(std::addressof(object_format_code)));
R_TRY(dp.Read(std::addressof(association_object_handle)));
R_TRY(dp.Finalize());
/* Handle top-level requests. */
if (storage_id == PtpGetObjectHandles_AllStorage) {
storage_id = StorageId_SdmcFs;
}
/* Rewrite requests for enumerating storage directories. */
if (association_object_handle == PtpGetObjectHandles_RootParent) {
association_object_handle = storage_id;
}
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(association_object_handle);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Try to read the object as a directory. */
FsDir dir;
R_TRY(m_fs.OpenDirectory(obj->GetName(), FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, std::addressof(dir)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseDirectory(std::addressof(dir)); };
/* Count how many entries are in the directory. */
s64 entry_count = 0;
R_TRY(m_fs.GetDirectoryEntryCount(std::addressof(dir), std::addressof(entry_count)));
/* Begin writing. */
R_TRY(db.AddDataHeader(m_request_header, sizeof(u32) + (entry_count * sizeof(u32))));
R_TRY(db.Add(static_cast<u32>(entry_count)));
/* Enumerate the directory, writing results to the data builder as we progress. */
/* TODO: How should we handle the directory contents changing during enumeration? */
/* Is this even feasible to handle? */
while (true) {
/* Get the next batch. */
s64 read_count = 0;
R_TRY(m_fs.ReadDirectory(std::addressof(dir), std::addressof(read_count), DirectoryReadSize, m_buffers->file_system_entry_buffer));
/* Write to output. */
for (s64 i = 0; i < read_count; i++) {
const char *name = m_buffers->file_system_entry_buffer[i].name;
u32 handle;
R_TRY(m_object_database.CreateAndRegisterObjectId(obj->GetName(), name, obj->GetObjectId(), std::addressof(handle)));
R_TRY(db.Add(handle));
}
/* If we read fewer than the batch size, we're done. */
if (read_count < DirectoryReadSize) {
break;
}
}
/* Flush the data response. */
R_TRY(db.Commit());
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectInfo(PtpDataParser &dp) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID the client requested info for. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Build info about the object. */
PtpObjectInfo object_info(DefaultObjectInfo);
if (object_id == StorageId_SdmcFs) {
/* The SD Card directory has some special properties. */
object_info.object_format = PtpObjectFormatCode_Association;
object_info.association_type = PtpAssociationType_GenericFolder;
object_info.filename = "SD Card";
} else {
/* Figure out what type of object this is. */
FsDirEntryType entry_type;
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
/* Get the size, if we are requesting info about a file. */
s64 size = 0;
if (entry_type == FsDirEntryType_File) {
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size)));
}
object_info.filename = std::strrchr(obj->GetName(), '/') + 1;
object_info.object_compressed_size = size;
object_info.parent_object = obj->GetParentId();
if (entry_type == FsDirEntryType_Dir) {
object_info.object_format = PtpObjectFormatCode_Association;
object_info.association_type = PtpAssociationType_GenericFolder;
} else {
object_info.object_format = PtpObjectFormatCode_Undefined;
object_info.association_type = PtpAssociationType_Undefined;
}
}
/* Write the object info data. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
R_TRY(db.Add(object_info.storage_id));
R_TRY(db.Add(object_info.object_format));
R_TRY(db.Add(object_info.protection_status));
R_TRY(db.Add(object_info.object_compressed_size));
R_TRY(db.Add(object_info.thumb_format));
R_TRY(db.Add(object_info.thumb_compressed_size));
R_TRY(db.Add(object_info.thumb_width));
R_TRY(db.Add(object_info.thumb_height));
R_TRY(db.Add(object_info.image_width));
R_TRY(db.Add(object_info.image_height));
R_TRY(db.Add(object_info.image_depth));
R_TRY(db.Add(object_info.parent_object));
R_TRY(db.Add(object_info.association_type));
R_TRY(db.Add(object_info.association_desc));
R_TRY(db.Add(object_info.sequence_number));
R_TRY(db.AddString(object_info.filename));
R_TRY(db.AddString(object_info.capture_date));
R_TRY(db.AddString(object_info.modification_date));
R_TRY(db.AddString(object_info.keywords));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObject(PtpDataParser &dp) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID the client requested. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Get the file's size. */
s64 size = 0;
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size)));
/* Send the header and file size. */
R_TRY(db.AddDataHeader(m_request_header, size));
/* Begin reading the file, writing data to the builder as we progress. */
s64 offset = 0;
while (true) {
/* Get the next batch. */
u64 bytes_read;
R_TRY(m_fs.ReadFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, FsBufferSize, FsReadOption_None, std::addressof(bytes_read)));
offset += bytes_read;
/* Write to output. */
R_TRY(db.AddBuffer(m_buffers->file_system_data_buffer, bytes_read));
/* If we read fewer bytes than the batch size, we're done. */
if (bytes_read < FsBufferSize) {
break;
}
}
/* Flush the data response. */
R_TRY(db.Commit());
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::SendObjectInfo(PtpDataParser &rdp) {
/* Get the storage ID and parent object and flush the request packet. */
u32 storage_id, parent_object;
R_TRY(rdp.Read(std::addressof(storage_id)));
R_TRY(rdp.Read(std::addressof(parent_object)));
R_TRY(rdp.Finalize());
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
PtpObjectInfo info(DefaultObjectInfo);
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Read in the object info. */
R_TRY(dp.Read(std::addressof(info.storage_id)));
R_TRY(dp.Read(std::addressof(info.object_format)));
R_TRY(dp.Read(std::addressof(info.protection_status)));
R_TRY(dp.Read(std::addressof(info.object_compressed_size)));
R_TRY(dp.Read(std::addressof(info.thumb_format)));
R_TRY(dp.Read(std::addressof(info.thumb_compressed_size)));
R_TRY(dp.Read(std::addressof(info.thumb_width)));
R_TRY(dp.Read(std::addressof(info.thumb_height)));
R_TRY(dp.Read(std::addressof(info.image_width)));
R_TRY(dp.Read(std::addressof(info.image_height)));
R_TRY(dp.Read(std::addressof(info.image_depth)));
R_TRY(dp.Read(std::addressof(info.parent_object)));
R_TRY(dp.Read(std::addressof(info.association_type)));
R_TRY(dp.Read(std::addressof(info.association_desc)));
R_TRY(dp.Read(std::addressof(info.sequence_number)));
R_TRY(dp.ReadString(m_buffers->filename_string_buffer));
R_TRY(dp.ReadString(m_buffers->capture_date_string_buffer));
R_TRY(dp.ReadString(m_buffers->modification_date_string_buffer));
R_TRY(dp.ReadString(m_buffers->keywords_string_buffer));
R_TRY(dp.Finalize());
/* Rewrite requests for creating in storage directories. */
if (parent_object == PtpGetObjectHandles_RootParent) {
parent_object = storage_id;
}
/* Check if we know about the parent object. If we don't, it's an error. */
auto * const parentobj = m_object_database.GetObjectById(parent_object);
R_UNLESS(parentobj != nullptr, haze::ResultInvalidObjectId());
/* Make a new object with the intended name. */
PtpNewObjectInfo new_object_info;
new_object_info.storage_id = StorageId_SdmcFs;
new_object_info.parent_object_id = parent_object == storage_id ? 0 : parent_object;
/* Create the object in the database. */
PtpObject *obj;
R_TRY(m_object_database.CreateOrFindObject(parentobj->GetName(), m_buffers->filename_string_buffer, parentobj->GetObjectId(), std::addressof(obj)));
/* Ensure we maintain a clean state on failure. */
ON_RESULT_FAILURE { m_object_database.DeleteObject(obj); };
/* Register the object with a new ID. */
m_object_database.RegisterObject(obj);
new_object_info.object_id = obj->GetObjectId();
/* Create the object on the filesystem. */
if (info.object_format == PtpObjectFormatCode_Association) {
R_TRY(m_fs.CreateDirectory(obj->GetName()));
m_send_object_id = 0;
} else {
R_TRY(m_fs.CreateFile(obj->GetName(), 0, 0));
m_send_object_id = new_object_info.object_id;
}
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok, new_object_info));
}
Result PtpResponder::SendObject(PtpDataParser &rdp) {
/* Reset SendObject object ID on exit. */
ON_SCOPE_EXIT { m_send_object_id = 0; };
R_TRY(rdp.Finalize());
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(m_send_object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Truncate the file after locking for write. */
s64 offset = 0;
R_TRY(m_fs.SetFileSize(std::addressof(file), 0));
/* Expand to the needed size. */
if (data_header.length > sizeof(PtpUsbBulkContainer)) {
R_TRY(m_fs.SetFileSize(std::addressof(file), data_header.length - sizeof(PtpUsbBulkContainer)));
}
/* Begin writing to the filesystem. */
while (true) {
/* Read as many bytes as we can. */
u32 bytes_received;
const Result read_res = dp.ReadBuffer(m_buffers->file_system_data_buffer, FsBufferSize, std::addressof(bytes_received));
/* Write to the file. */
R_TRY(m_fs.WriteFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_received, 0));
offset += bytes_received;
/* If we received fewer bytes than the batch size, we're done. */
if (haze::ResultEndOfTransmission::Includes(read_res)) {
break;
}
R_TRY(read_res);
}
/* Truncate the file to the received size. */
R_TRY(m_fs.SetFileSize(std::addressof(file), offset));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::DeleteObject(PtpDataParser &dp) {
/* Get the object ID and flush the request packet. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Disallow deleting the storage root. */
R_UNLESS(object_id != StorageId_SdmcFs, haze::ResultInvalidObjectId());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Figure out what type of object this is. */
FsDirEntryType entry_type;
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
/* Remove the object from the filesystem. */
if (entry_type == FsDirEntryType_Dir) {
R_TRY(m_fs.DeleteDirectoryRecursively(obj->GetName()));
} else {
R_TRY(m_fs.DeleteFile(obj->GetName()));
}
/* Remove the object from the database. */
m_object_database.DeleteObject(obj);
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
}

View File

@@ -191,7 +191,6 @@ namespace haze {
R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, std::addressof(device_descriptor)));
device_descriptor.bcdUSB = 0x0300;
device_descriptor.bMaxPacketSize0 = 0x09;
R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, std::addressof(device_descriptor)));
/* Binary Object Store */