2023-09-02 02:16:06 +02:00
|
|
|
/*
|
|
|
|
* Copyright (c) Atmosphère-NX
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms and conditions of the GNU General Public License,
|
|
|
|
* version 2, as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
|
|
* more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include <haze.hpp>
|
|
|
|
#include <haze/ptp_data_builder.hpp>
|
|
|
|
#include <haze/ptp_data_parser.hpp>
|
|
|
|
#include <haze/ptp_responder_types.hpp>
|
|
|
|
|
|
|
|
namespace haze {
|
|
|
|
|
|
|
|
Result PtpResponder::GetObjectPropsSupported(PtpDataParser &dp) {
|
|
|
|
R_TRY(dp.Finalize());
|
|
|
|
|
|
|
|
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
|
|
|
|
|
|
|
/* Write information about all object properties we can support. */
|
|
|
|
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
|
|
|
R_RETURN(db.AddArray(SupportedObjectProperties, util::size(SupportedObjectProperties)));
|
|
|
|
}));
|
|
|
|
|
|
|
|
/* Write the success response. */
|
|
|
|
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
|
|
|
}
|
|
|
|
|
|
|
|
Result PtpResponder::GetObjectPropDesc(PtpDataParser &dp) {
|
|
|
|
PtpObjectPropertyCode property_code;
|
|
|
|
u16 object_format;
|
|
|
|
|
|
|
|
R_TRY(dp.Read(std::addressof(property_code)));
|
|
|
|
R_TRY(dp.Read(std::addressof(object_format)));
|
|
|
|
R_TRY(dp.Finalize());
|
|
|
|
|
|
|
|
/* Ensure we have a valid property code before continuing. */
|
|
|
|
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
|
|
|
|
|
|
|
|
/* Begin writing information about the property code. */
|
|
|
|
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
|
|
|
|
|
|
|
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
|
|
|
R_TRY(db.Add(property_code));
|
|
|
|
|
|
|
|
/* Each property code corresponds to a different pattern, which contains the data type, */
|
|
|
|
/* whether the property can be set for an object, and the default value of the property. */
|
|
|
|
switch (property_code) {
|
|
|
|
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
|
|
|
|
{
|
|
|
|
R_TRY(db.Add(PtpDataTypeCode_U128));
|
|
|
|
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
|
|
|
|
R_TRY(db.Add<u128>(0));
|
|
|
|
}
|
|
|
|
case PtpObjectPropertyCode_ObjectSize:
|
|
|
|
{
|
|
|
|
R_TRY(db.Add(PtpDataTypeCode_U64));
|
|
|
|
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
|
|
|
|
R_TRY(db.Add<u64>(0));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PtpObjectPropertyCode_StorageId:
|
|
|
|
case PtpObjectPropertyCode_ParentObject:
|
|
|
|
{
|
|
|
|
R_TRY(db.Add(PtpDataTypeCode_U32));
|
|
|
|
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
|
|
|
|
R_TRY(db.Add(StorageId_SdmcFs));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PtpObjectPropertyCode_ObjectFormat:
|
|
|
|
{
|
|
|
|
R_TRY(db.Add(PtpDataTypeCode_U16));
|
|
|
|
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
|
|
|
|
R_TRY(db.Add(PtpObjectFormatCode_Undefined));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PtpObjectPropertyCode_ObjectFileName:
|
|
|
|
{
|
|
|
|
R_TRY(db.Add(PtpDataTypeCode_String));
|
|
|
|
R_TRY(db.Add(PtpPropertyGetSetFlag_GetSet));
|
|
|
|
R_TRY(db.AddString(""));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
HAZE_UNREACHABLE_DEFAULT_CASE();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Group code is a required part of the response, but doesn't seem to be used for anything. */
|
|
|
|
R_TRY(db.Add(PtpPropertyGroupCode_Default));
|
|
|
|
|
|
|
|
/* We don't use the form flag. */
|
|
|
|
R_TRY(db.Add(PtpPropertyFormFlag_None));
|
|
|
|
|
|
|
|
R_SUCCEED();
|
|
|
|
}));
|
|
|
|
|
|
|
|
/* Write the success response. */
|
|
|
|
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
|
|
|
}
|
|
|
|
|
|
|
|
Result PtpResponder::GetObjectPropValue(PtpDataParser &dp) {
|
|
|
|
u32 object_id;
|
|
|
|
PtpObjectPropertyCode property_code;
|
|
|
|
|
|
|
|
R_TRY(dp.Read(std::addressof(object_id)));
|
|
|
|
R_TRY(dp.Read(std::addressof(property_code)));
|
|
|
|
R_TRY(dp.Finalize());
|
|
|
|
|
|
|
|
/* Ensure we have a valid property code before continuing. */
|
|
|
|
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
|
|
|
|
|
|
|
|
/* Check if we know about the object. If we don't, it's an error. */
|
|
|
|
auto * const obj = m_object_database.GetObjectById(object_id);
|
|
|
|
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
|
|
|
|
|
|
|
/* Define helper for getting the object type. */
|
|
|
|
const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) {
|
|
|
|
R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type));
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Define helper for getting the object size. */
|
|
|
|
const auto GetObjectSize = [&] (s64 *out_size) {
|
|
|
|
*out_size = 0;
|
|
|
|
|
|
|
|
/* Check if this is a directory. */
|
|
|
|
FsDirEntryType entry_type;
|
|
|
|
R_TRY(GetObjectType(std::addressof(entry_type)));
|
|
|
|
|
|
|
|
/* If it is, we're done. */
|
|
|
|
R_SUCCEED_IF(entry_type == FsDirEntryType_Dir);
|
|
|
|
|
|
|
|
/* Otherwise, open as a file. */
|
|
|
|
FsFile file;
|
|
|
|
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
|
|
|
|
|
|
|
/* Ensure we maintain a clean state on exit. */
|
|
|
|
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
|
|
|
|
|
|
|
R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size));
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Begin writing the requested object property. */
|
|
|
|
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
|
|
|
|
|
|
|
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
|
|
|
switch (property_code) {
|
|
|
|
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
|
|
|
|
{
|
|
|
|
R_TRY(db.Add<u128>(object_id));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PtpObjectPropertyCode_ObjectSize:
|
|
|
|
{
|
|
|
|
s64 size;
|
|
|
|
R_TRY(GetObjectSize(std::addressof(size)));
|
|
|
|
R_TRY(db.Add<u64>(size));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PtpObjectPropertyCode_StorageId:
|
|
|
|
{
|
|
|
|
R_TRY(db.Add(StorageId_SdmcFs));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PtpObjectPropertyCode_ParentObject:
|
|
|
|
{
|
|
|
|
R_TRY(db.Add(obj->GetParentId()));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PtpObjectPropertyCode_ObjectFormat:
|
|
|
|
{
|
|
|
|
FsDirEntryType entry_type;
|
|
|
|
R_TRY(GetObjectType(std::addressof(entry_type)));
|
|
|
|
R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PtpObjectPropertyCode_ObjectFileName:
|
|
|
|
{
|
|
|
|
R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
HAZE_UNREACHABLE_DEFAULT_CASE();
|
|
|
|
}
|
|
|
|
|
|
|
|
R_SUCCEED();
|
|
|
|
}));
|
|
|
|
|
|
|
|
/* Write the success response. */
|
|
|
|
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
|
|
|
}
|
|
|
|
|
2023-10-14 02:59:36 +02:00
|
|
|
Result PtpResponder::GetObjectPropList(PtpDataParser &dp) {
|
|
|
|
u32 object_id;
|
|
|
|
u32 object_format;
|
|
|
|
s32 property_code;
|
|
|
|
s32 group_code;
|
|
|
|
s32 depth;
|
|
|
|
|
|
|
|
R_TRY(dp.Read(std::addressof(object_id)));
|
|
|
|
R_TRY(dp.Read(std::addressof(object_format)));
|
|
|
|
R_TRY(dp.Read(std::addressof(property_code)));
|
|
|
|
R_TRY(dp.Read(std::addressof(group_code)));
|
|
|
|
R_TRY(dp.Read(std::addressof(depth)));
|
|
|
|
R_TRY(dp.Finalize());
|
|
|
|
|
|
|
|
/* Ensure format is unspecified. */
|
|
|
|
R_UNLESS(object_format == 0, haze::ResultInvalidArgument());
|
|
|
|
|
|
|
|
/* Ensure we have a valid property code. */
|
|
|
|
R_UNLESS(property_code == -1 || IsSupportedObjectPropertyCode(PtpObjectPropertyCode(property_code)), haze::ResultUnknownPropertyCode());
|
|
|
|
|
|
|
|
/* Ensure group code is the default. */
|
|
|
|
R_UNLESS(group_code == PtpPropertyGroupCode_Default, haze::ResultGroupSpecified());
|
|
|
|
|
|
|
|
/* Ensure depth is 0. */
|
|
|
|
R_UNLESS(depth == 0, haze::ResultDepthSpecified());
|
|
|
|
|
|
|
|
/* Check if we know about the object. If we don't, it's an error. */
|
|
|
|
auto * const obj = m_object_database.GetObjectById(object_id);
|
|
|
|
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
|
|
|
|
|
|
|
|
/* Define helper for getting the object type. */
|
|
|
|
const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) {
|
|
|
|
R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type));
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Define helper for getting the object size. */
|
|
|
|
const auto GetObjectSize = [&] (s64 *out_size) {
|
|
|
|
*out_size = 0;
|
|
|
|
|
|
|
|
/* Check if this is a directory. */
|
|
|
|
FsDirEntryType entry_type;
|
|
|
|
R_TRY(GetObjectType(std::addressof(entry_type)));
|
|
|
|
|
|
|
|
/* If it is, we're done. */
|
|
|
|
R_SUCCEED_IF(entry_type == FsDirEntryType_Dir);
|
|
|
|
|
|
|
|
/* Otherwise, open as a file. */
|
|
|
|
FsFile file;
|
|
|
|
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
|
|
|
|
|
|
|
|
/* Ensure we maintain a clean state on exit. */
|
|
|
|
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
|
|
|
|
|
|
|
|
R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size));
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Define helper for determining if the property should be included. */
|
|
|
|
const auto ShouldIncludeProperty = [&] (PtpObjectPropertyCode code) {
|
|
|
|
/* If all properties were requested, or it was the requested property, we should include the property. */
|
|
|
|
return property_code == -1 || code == property_code;
|
|
|
|
};
|
|
|
|
|
2023-10-16 23:31:09 +02:00
|
|
|
/* Determine how many output elements we will report. */
|
|
|
|
u32 num_output_elements = 0;
|
|
|
|
for (const auto obj_property : SupportedObjectProperties) {
|
|
|
|
if (ShouldIncludeProperty(obj_property)) {
|
|
|
|
num_output_elements++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-14 02:59:36 +02:00
|
|
|
/* Begin writing the requested object properties. */
|
|
|
|
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
|
|
|
|
|
|
|
|
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
|
2023-10-16 23:31:09 +02:00
|
|
|
/* Report the number of elements. */
|
|
|
|
R_TRY(db.Add(num_output_elements));
|
|
|
|
|
2023-10-14 02:59:36 +02:00
|
|
|
for (const auto obj_property : SupportedObjectProperties) {
|
|
|
|
if (!ShouldIncludeProperty(obj_property)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write the object handle. */
|
|
|
|
R_TRY(db.Add<u32>(object_id));
|
|
|
|
|
|
|
|
/* Write the property code. */
|
|
|
|
R_TRY(db.Add<u16>(obj_property));
|
|
|
|
|
|
|
|
/* Write the property value. */
|
|
|
|
switch (obj_property) {
|
|
|
|
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
|
|
|
|
{
|
|
|
|
R_TRY(db.Add(PtpDataTypeCode_U128));
|
|
|
|
R_TRY(db.Add<u128>(object_id));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PtpObjectPropertyCode_ObjectSize:
|
|
|
|
{
|
|
|
|
s64 size;
|
|
|
|
R_TRY(GetObjectSize(std::addressof(size)));
|
|
|
|
R_TRY(db.Add(PtpDataTypeCode_U64));
|
|
|
|
R_TRY(db.Add<u64>(size));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PtpObjectPropertyCode_StorageId:
|
|
|
|
{
|
|
|
|
R_TRY(db.Add(PtpDataTypeCode_U32));
|
|
|
|
R_TRY(db.Add(StorageId_SdmcFs));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PtpObjectPropertyCode_ParentObject:
|
|
|
|
{
|
|
|
|
R_TRY(db.Add(PtpDataTypeCode_U32));
|
|
|
|
R_TRY(db.Add(obj->GetParentId()));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PtpObjectPropertyCode_ObjectFormat:
|
|
|
|
{
|
|
|
|
FsDirEntryType entry_type;
|
|
|
|
R_TRY(GetObjectType(std::addressof(entry_type)));
|
|
|
|
R_TRY(db.Add(PtpDataTypeCode_U16));
|
|
|
|
R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PtpObjectPropertyCode_ObjectFileName:
|
|
|
|
{
|
|
|
|
R_TRY(db.Add(PtpDataTypeCode_String));
|
|
|
|
R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
HAZE_UNREACHABLE_DEFAULT_CASE();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
R_SUCCEED();
|
|
|
|
}));
|
|
|
|
|
|
|
|
/* Write the success response. */
|
|
|
|
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
|
|
|
|
}
|
|
|
|
|
2023-09-02 02:16:06 +02:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|