mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-11-26 13:52:21 +00:00
haze: implement android operations
This commit is contained in:
parent
9e0daff46e
commit
92a8c8eb88
6 changed files with 230 additions and 1 deletions
|
@ -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,
|
||||||
|
|
|
@ -71,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);
|
||||||
|
|
|
@ -57,6 +57,11 @@ namespace haze {
|
||||||
PtpOperationCode_MtpGetObjectPropDesc,
|
PtpOperationCode_MtpGetObjectPropDesc,
|
||||||
PtpOperationCode_MtpGetObjectPropValue,
|
PtpOperationCode_MtpGetObjectPropValue,
|
||||||
PtpOperationCode_MtpSetObjectPropValue,
|
PtpOperationCode_MtpSetObjectPropValue,
|
||||||
|
PtpOperationCode_AndroidGetPartialObject64,
|
||||||
|
PtpOperationCode_AndroidSendPartialObject,
|
||||||
|
PtpOperationCode_AndroidTruncateObject,
|
||||||
|
PtpOperationCode_AndroidBeginEditObject,
|
||||||
|
PtpOperationCode_AndroidEndEditObject,
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr const PtpEventCode SupportedEventCodes[] = { /* ... */ };
|
constexpr const PtpEventCode SupportedEventCodes[] = { /* ... */ };
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace haze {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
PtpBuffers* GetBuffers() {
|
PtpBuffers *GetBuffers() {
|
||||||
static constinit PtpBuffers buffers = {};
|
static constinit PtpBuffers buffers = {};
|
||||||
return std::addressof(buffers);
|
return std::addressof(buffers);
|
||||||
}
|
}
|
||||||
|
@ -91,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));
|
||||||
|
@ -131,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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue