mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-07-04 00:22:15 +02:00
Compare commits
4 Commits
ba91f070e8
...
159f8d384b
Author | SHA1 | Date | |
---|---|---|---|
|
159f8d384b | ||
|
92a8c8eb88 | ||
|
9e0daff46e | ||
|
6b72dbd22d |
@ -28,6 +28,7 @@ namespace ams::pm::dmnt {
|
|||||||
Result GetProcessId(os::ProcessId *out_process_id, const ncm::ProgramId program_id);
|
Result GetProcessId(os::ProcessId *out_process_id, const ncm::ProgramId program_id);
|
||||||
Result GetApplicationProcessId(os::ProcessId *out_process_id);
|
Result GetApplicationProcessId(os::ProcessId *out_process_id);
|
||||||
Result HookToCreateApplicationProcess(os::NativeHandle *out_handle);
|
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);
|
Result AtmosphereGetProcessInfo(os::NativeHandle *out_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id);
|
||||||
|
|
||||||
#if defined(ATMOSPHERE_OS_HORIZON)
|
#if defined(ATMOSPHERE_OS_HORIZON)
|
||||||
|
@ -44,6 +44,13 @@ namespace ams::pm::dmnt {
|
|||||||
R_SUCCEED();
|
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) {
|
Result AtmosphereGetProcessInfo(os::NativeHandle *out_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id) {
|
||||||
*out_handle = os::InvalidNativeHandle;
|
*out_handle = os::InvalidNativeHandle;
|
||||||
*out_loc = {};
|
*out_loc = {};
|
||||||
|
@ -2105,9 +2105,24 @@ namespace ams::dmnt {
|
|||||||
ParsePrefix(command, "0x");
|
ParsePrefix(command, "0x");
|
||||||
|
|
||||||
/* Decode program id. */
|
/* Decode program id. */
|
||||||
const u64 program_id = DecodeHex(command);
|
const ncm::ProgramId program_id(DecodeHex(command));
|
||||||
|
|
||||||
AppendReplyFormat(reply_cur, reply_end, "[TODO] wait for program id 0x%lx\n", program_id);
|
/* 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);
|
||||||
} else {
|
} else {
|
||||||
std::memcpy(m_reply_cur, command, std::strlen(command) + 1);
|
std::memcpy(m_reply_cur, command, std::strlen(command) + 1);
|
||||||
AppendReplyFormat(reply_cur, reply_end, "Unknown command `%s`\n", m_reply_cur);
|
AppendReplyFormat(reply_cur, reply_end, "Unknown command `%s`\n", m_reply_cur);
|
||||||
|
@ -42,6 +42,4 @@ namespace haze {
|
|||||||
|
|
||||||
using Result = ::ams::Result;
|
using Result = ::ams::Result;
|
||||||
|
|
||||||
static constexpr u32 UsbBulkPacketBufferSize = 1_MB;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,11 @@ namespace haze {
|
|||||||
PtpOperationCode_GetFilesystemManifest = 0x1023,
|
PtpOperationCode_GetFilesystemManifest = 0x1023,
|
||||||
PtpOperationCode_GetStreamInfo = 0x1024,
|
PtpOperationCode_GetStreamInfo = 0x1024,
|
||||||
PtpOperationCode_GetStream = 0x1025,
|
PtpOperationCode_GetStream = 0x1025,
|
||||||
|
PtpOperationCode_AndroidGetPartialObject64 = 0x95c1,
|
||||||
|
PtpOperationCode_AndroidSendPartialObject = 0x95c2,
|
||||||
|
PtpOperationCode_AndroidTruncateObject = 0x95c3,
|
||||||
|
PtpOperationCode_AndroidBeginEditObject = 0x95c4,
|
||||||
|
PtpOperationCode_AndroidEndEditObject = 0x95c5,
|
||||||
PtpOperationCode_MtpGetObjectPropsSupported = 0x9801,
|
PtpOperationCode_MtpGetObjectPropsSupported = 0x9801,
|
||||||
PtpOperationCode_MtpGetObjectPropDesc = 0x9802,
|
PtpOperationCode_MtpGetObjectPropDesc = 0x9802,
|
||||||
PtpOperationCode_MtpGetObjectPropValue = 0x9803,
|
PtpOperationCode_MtpGetObjectPropValue = 0x9803,
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include <haze/async_usb_server.hpp>
|
#include <haze/async_usb_server.hpp>
|
||||||
#include <haze/ptp_object_heap.hpp>
|
#include <haze/ptp_object_heap.hpp>
|
||||||
#include <haze/ptp_object_database.hpp>
|
#include <haze/ptp_object_database.hpp>
|
||||||
|
#include <haze/ptp_responder_types.hpp>
|
||||||
|
|
||||||
namespace haze {
|
namespace haze {
|
||||||
|
|
||||||
@ -30,12 +31,13 @@ namespace haze {
|
|||||||
FileSystemProxy m_fs;
|
FileSystemProxy m_fs;
|
||||||
PtpUsbBulkContainer m_request_header;
|
PtpUsbBulkContainer m_request_header;
|
||||||
PtpObjectHeap *m_object_heap;
|
PtpObjectHeap *m_object_heap;
|
||||||
|
PtpBuffers* m_buffers;
|
||||||
u32 m_send_object_id;
|
u32 m_send_object_id;
|
||||||
bool m_session_open;
|
bool m_session_open;
|
||||||
|
|
||||||
PtpObjectDatabase m_object_database;
|
PtpObjectDatabase m_object_database;
|
||||||
public:
|
public:
|
||||||
constexpr explicit PtpResponder() : m_usb_server(), m_fs(), m_request_header(), m_object_heap(), m_send_object_id(), m_session_open(), m_object_database() { /* ... */ }
|
constexpr explicit PtpResponder() : m_usb_server(), m_fs(), m_request_header(), m_object_heap(), m_buffers(), m_send_object_id(), m_session_open(), m_object_database() { /* ... */ }
|
||||||
|
|
||||||
Result Initialize(EventReactor *reactor, PtpObjectHeap *object_heap);
|
Result Initialize(EventReactor *reactor, PtpObjectHeap *object_heap);
|
||||||
void Finalize();
|
void Finalize();
|
||||||
@ -48,10 +50,14 @@ namespace haze {
|
|||||||
Result HandleCommandRequest(PtpDataParser &dp);
|
Result HandleCommandRequest(PtpDataParser &dp);
|
||||||
void ForceCloseSession();
|
void ForceCloseSession();
|
||||||
|
|
||||||
template <typename Data>
|
Result WriteResponse(PtpResponseCode code, const void* data, size_t size);
|
||||||
Result WriteResponse(PtpResponseCode code, Data &&data);
|
|
||||||
Result WriteResponse(PtpResponseCode code);
|
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. */
|
/* PTP operations. */
|
||||||
Result GetDeviceInfo(PtpDataParser &dp);
|
Result GetDeviceInfo(PtpDataParser &dp);
|
||||||
Result OpenSession(PtpDataParser &dp);
|
Result OpenSession(PtpDataParser &dp);
|
||||||
@ -65,6 +71,13 @@ namespace haze {
|
|||||||
Result SendObject(PtpDataParser &dp);
|
Result SendObject(PtpDataParser &dp);
|
||||||
Result DeleteObject(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. */
|
/* MTP operations. */
|
||||||
Result GetObjectPropsSupported(PtpDataParser &dp);
|
Result GetObjectPropsSupported(PtpDataParser &dp);
|
||||||
Result GetObjectPropDesc(PtpDataParser &dp);
|
Result GetObjectPropDesc(PtpDataParser &dp);
|
||||||
|
182
troposphere/haze/include/haze/ptp_responder_types.hpp
Normal file
182
troposphere/haze/include/haze/ptp_responder_types.hpp
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* 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_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];
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -37,5 +37,6 @@ namespace haze {
|
|||||||
R_DEFINE_ERROR_RESULT(UnknownRequestType, 13);
|
R_DEFINE_ERROR_RESULT(UnknownRequestType, 13);
|
||||||
R_DEFINE_ERROR_RESULT(UnknownPropertyCode, 14);
|
R_DEFINE_ERROR_RESULT(UnknownPropertyCode, 14);
|
||||||
R_DEFINE_ERROR_RESULT(InvalidPropertyValue, 15);
|
R_DEFINE_ERROR_RESULT(InvalidPropertyValue, 15);
|
||||||
|
R_DEFINE_ERROR_RESULT(InvalidArgument, 16);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,167 +16,22 @@
|
|||||||
#include <haze.hpp>
|
#include <haze.hpp>
|
||||||
#include <haze/ptp_data_builder.hpp>
|
#include <haze/ptp_data_builder.hpp>
|
||||||
#include <haze/ptp_data_parser.hpp>
|
#include <haze/ptp_data_parser.hpp>
|
||||||
|
#include <haze/ptp_responder_types.hpp>
|
||||||
|
|
||||||
namespace haze {
|
namespace haze {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr UsbCommsInterfaceInfo MtpInterfaceInfo = {
|
PtpBuffers *GetBuffers() {
|
||||||
.bInterfaceClass = 0x06,
|
static constinit PtpBuffers buffers = {};
|
||||||
.bInterfaceSubClass = 0x01,
|
return std::addressof(buffers);
|
||||||
.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) {
|
Result PtpResponder::Initialize(EventReactor *reactor, PtpObjectHeap *object_heap) {
|
||||||
m_object_heap = object_heap;
|
m_object_heap = object_heap;
|
||||||
|
m_buffers = GetBuffers();
|
||||||
|
|
||||||
/* Configure fs proxy. */
|
/* Configure fs proxy. */
|
||||||
m_fs.Initialize(reactor, fsdevGetDeviceFileSystem("sdmc"));
|
m_fs.Initialize(reactor, fsdevGetDeviceFileSystem("sdmc"));
|
||||||
@ -236,6 +91,9 @@ namespace haze {
|
|||||||
R_CATCH(haze::ResultInvalidPropertyValue) {
|
R_CATCH(haze::ResultInvalidPropertyValue) {
|
||||||
R_TRY(this->WriteResponse(PtpResponseCode_MtpInvalidObjectPropValue));
|
R_TRY(this->WriteResponse(PtpResponseCode_MtpInvalidObjectPropValue));
|
||||||
}
|
}
|
||||||
|
R_CATCH(haze::ResultInvalidArgument) {
|
||||||
|
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
|
||||||
|
}
|
||||||
R_CATCH_MODULE(fs) {
|
R_CATCH_MODULE(fs) {
|
||||||
/* Errors from fs are typically recoverable. */
|
/* Errors from fs are typically recoverable. */
|
||||||
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
|
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
|
||||||
@ -246,7 +104,7 @@ namespace haze {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result PtpResponder::HandleRequestImpl() {
|
Result PtpResponder::HandleRequestImpl() {
|
||||||
PtpDataParser dp(g_bulk_read_buffer, std::addressof(m_usb_server));
|
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
|
||||||
R_TRY(dp.Read(std::addressof(m_request_header)));
|
R_TRY(dp.Read(std::addressof(m_request_header)));
|
||||||
|
|
||||||
switch (m_request_header.type) {
|
switch (m_request_header.type) {
|
||||||
@ -276,6 +134,11 @@ namespace haze {
|
|||||||
case PtpOperationCode_MtpGetObjectPropDesc: R_RETURN(this->GetObjectPropDesc(dp)); break;
|
case PtpOperationCode_MtpGetObjectPropDesc: R_RETURN(this->GetObjectPropDesc(dp)); break;
|
||||||
case PtpOperationCode_MtpGetObjectPropValue: R_RETURN(this->GetObjectPropValue(dp)); break;
|
case PtpOperationCode_MtpGetObjectPropValue: R_RETURN(this->GetObjectPropValue(dp)); break;
|
||||||
case PtpOperationCode_MtpSetObjectPropValue: R_RETURN(this->SetObjectPropValue(dp)); break;
|
case PtpOperationCode_MtpSetObjectPropValue: R_RETURN(this->SetObjectPropValue(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());
|
default: R_THROW(haze::ResultOperationNotSupported());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,756 +150,17 @@ namespace haze {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Data>
|
Result PtpResponder::WriteResponse(PtpResponseCode code, const void* data, size_t size) {
|
||||||
Result PtpResponder::WriteResponse(PtpResponseCode code, Data &&data) {
|
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, size));
|
||||||
R_TRY(db.AddResponseHeader(m_request_header, code, sizeof(Data)));
|
R_TRY(db.AddBuffer(reinterpret_cast<const u8*>(data), size));
|
||||||
R_TRY(db.Add(data));
|
|
||||||
R_RETURN(db.Commit());
|
R_RETURN(db.Commit());
|
||||||
}
|
}
|
||||||
|
|
||||||
Result PtpResponder::WriteResponse(PtpResponseCode code) {
|
Result PtpResponder::WriteResponse(PtpResponseCode code) {
|
||||||
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
|
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
||||||
R_TRY(db.AddResponseHeader(m_request_header, code, 0));
|
R_TRY(db.AddResponseHeader(m_request_header, code, 0));
|
||||||
R_RETURN(db.Commit());
|
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
203
troposphere/haze/source/ptp_responder_android_operations.cpp
Normal file
203
troposphere/haze/source/ptp_responder_android_operations.cpp
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
/*
|
||||||
|
* 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
280
troposphere/haze/source/ptp_responder_mtp_operations.cpp
Normal file
280
troposphere/haze/source/ptp_responder_mtp_operations.cpp
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
/*
|
||||||
|
* 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());
|
||||||
|
|
||||||
|
/* 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(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::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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
507
troposphere/haze/source/ptp_responder_ptp_operations.cpp
Normal file
507
troposphere/haze/source/ptp_responder_ptp_operations.cpp
Normal file
@ -0,0 +1,507 @@
|
|||||||
|
/*
|
||||||
|
* 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user