DO NOT MERGE MTP: Add extended operations to support in-place editing of files
MTP does not support partial writes of files (the entire file must be transferred at once). This makes it impossible to implement a FUSE file system for MTP with acceptable performance. To fix this problem, this change adds extended MTP operations to allow partial writes to files: SendPartialObject - allows writing a subset of a file, or appending to the end of a file TruncateObject - allows changing the size of a file BeginEditObject - must be called before using SendPartialObject and TruncateObject EndEditObject - commits changes to a file after it has been edited with SendPartialObject or TruncateObject We also add GetPartialObject64, which is the same as GetPartialObject but has a 64 bit offset rather than 32. Change-Id: I000930b787b00a2da0b57de9790053b2d71b86fd Signed-off-by: Mike Lockwood <lockwood@android.com>
This commit is contained in:
@ -29,6 +29,7 @@
|
||||
|
||||
#include "MtpDatabase.h"
|
||||
#include "MtpDataPacket.h"
|
||||
#include "MtpObjectInfo.h"
|
||||
#include "MtpProperty.h"
|
||||
#include "MtpStringBuffer.h"
|
||||
#include "MtpUtils.h"
|
||||
@ -138,7 +139,7 @@ public:
|
||||
MtpDataPacket& packet);
|
||||
|
||||
virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle,
|
||||
MtpDataPacket& packet);
|
||||
MtpObjectInfo& info);
|
||||
|
||||
virtual MtpResponseCode getObjectFilePath(MtpObjectHandle handle,
|
||||
MtpString& outFilePath,
|
||||
@ -746,7 +747,7 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle,
|
||||
}
|
||||
|
||||
MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle,
|
||||
MtpDataPacket& packet) {
|
||||
MtpObjectInfo& info) {
|
||||
char date[20];
|
||||
|
||||
JNIEnv* env = AndroidRuntime::getJNIEnv();
|
||||
@ -756,46 +757,27 @@ MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle,
|
||||
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
|
||||
|
||||
jint* intValues = env->GetIntArrayElements(mIntBuffer, 0);
|
||||
MtpStorageID storageID = intValues[0];
|
||||
MtpObjectFormat format = intValues[1];
|
||||
MtpObjectHandle parent = intValues[2];
|
||||
info.mStorageID = intValues[0];
|
||||
info.mFormat = intValues[1];
|
||||
info.mParent = intValues[2];
|
||||
env->ReleaseIntArrayElements(mIntBuffer, intValues, 0);
|
||||
|
||||
jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
|
||||
uint64_t size = longValues[0];
|
||||
uint64_t modified = longValues[1];
|
||||
info.mCompressedSize = (size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size);
|
||||
info.mDateModified = longValues[1];
|
||||
env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
|
||||
|
||||
// int associationType = (format == MTP_FORMAT_ASSOCIATION ?
|
||||
// info.mAssociationType = (format == MTP_FORMAT_ASSOCIATION ?
|
||||
// MTP_ASSOCIATION_TYPE_GENERIC_FOLDER :
|
||||
// MTP_ASSOCIATION_TYPE_UNDEFINED);
|
||||
int associationType = MTP_ASSOCIATION_TYPE_UNDEFINED;
|
||||
|
||||
packet.putUInt32(storageID);
|
||||
packet.putUInt16(format);
|
||||
packet.putUInt16(0); // protection status
|
||||
packet.putUInt32((size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size));
|
||||
packet.putUInt16(0); // thumb format
|
||||
packet.putUInt32(0); // thumb compressed size
|
||||
packet.putUInt32(0); // thumb pix width
|
||||
packet.putUInt32(0); // thumb pix height
|
||||
packet.putUInt32(0); // image pix width
|
||||
packet.putUInt32(0); // image pix height
|
||||
packet.putUInt32(0); // image bit depth
|
||||
packet.putUInt32(parent);
|
||||
packet.putUInt16(associationType);
|
||||
packet.putUInt32(0); // association desc
|
||||
packet.putUInt32(0); // sequence number
|
||||
info.mAssociationType = MTP_ASSOCIATION_TYPE_UNDEFINED;
|
||||
|
||||
jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
|
||||
packet.putString(str); // file name
|
||||
MtpString temp(str);
|
||||
info.mName = strdup((const char *)temp);
|
||||
env->ReleaseCharArrayElements(mStringBuffer, str, 0);
|
||||
|
||||
packet.putEmptyString();
|
||||
formatDateTime(modified, date, sizeof(date));
|
||||
packet.putString(date); // date modified
|
||||
packet.putEmptyString(); // keywords
|
||||
|
||||
checkAndClearExceptionFromCallback(env, __FUNCTION__);
|
||||
return MTP_RESPONSE_OK;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ namespace android {
|
||||
|
||||
class MtpDataPacket;
|
||||
class MtpProperty;
|
||||
class MtpObjectInfo;
|
||||
|
||||
class MtpDatabase {
|
||||
public:
|
||||
@ -81,7 +82,7 @@ public:
|
||||
MtpDataPacket& packet) = 0;
|
||||
|
||||
virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle,
|
||||
MtpDataPacket& packet) = 0;
|
||||
MtpObjectInfo& info) = 0;
|
||||
|
||||
virtual MtpResponseCode getObjectFilePath(MtpObjectHandle handle,
|
||||
MtpString& outFilePath,
|
||||
|
@ -63,6 +63,12 @@ static const CodeEntry sOperationCodes[] = {
|
||||
{ "MTP_OPERATION_GET_OBJECT_REFERENCES", 0x9810 },
|
||||
{ "MTP_OPERATION_SET_OBJECT_REFERENCES", 0x9811 },
|
||||
{ "MTP_OPERATION_SKIP", 0x9820 },
|
||||
// android extensions
|
||||
{ "MTP_OPERATION_GET_PARTIAL_OBJECT_64", 0x95C1 },
|
||||
{ "MTP_OPERATION_SEND_PARTIAL_OBJECT", 0x95C2 },
|
||||
{ "MTP_OPERATION_TRUNCATE_OBJECT", 0x95C3 },
|
||||
{ "MTP_OPERATION_BEGIN_EDIT_OBJECT", 0x95C4 },
|
||||
{ "MTP_OPERATION_END_EDIT_OBJECT", 0x95C5 },
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
#include "MtpDebug.h"
|
||||
#include "MtpDatabase.h"
|
||||
#include "MtpObjectInfo.h"
|
||||
#include "MtpProperty.h"
|
||||
#include "MtpServer.h"
|
||||
#include "MtpStorage.h"
|
||||
@ -79,6 +80,12 @@ static const MtpOperationCode kSupportedOperationCodes[] = {
|
||||
MTP_OPERATION_GET_OBJECT_REFERENCES,
|
||||
MTP_OPERATION_SET_OBJECT_REFERENCES,
|
||||
// MTP_OPERATION_SKIP,
|
||||
// Android extension for direct file IO
|
||||
MTP_OPERATION_GET_PARTIAL_OBJECT_64,
|
||||
MTP_OPERATION_SEND_PARTIAL_OBJECT,
|
||||
MTP_OPERATION_TRUNCATE_OBJECT,
|
||||
MTP_OPERATION_BEGIN_EDIT_OBJECT,
|
||||
MTP_OPERATION_END_EDIT_OBJECT,
|
||||
};
|
||||
|
||||
static const MtpEventCode kSupportedEventCodes[] = {
|
||||
@ -218,6 +225,15 @@ void MtpServer::run() {
|
||||
}
|
||||
}
|
||||
|
||||
// commit any open edits
|
||||
int count = mObjectEditList.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ObjectEdit* edit = mObjectEditList[i];
|
||||
commitEdit(edit);
|
||||
delete edit;
|
||||
}
|
||||
mObjectEditList.clear();
|
||||
|
||||
if (mSessionOpen)
|
||||
mDatabase->sessionEnded();
|
||||
}
|
||||
@ -252,6 +268,44 @@ void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) {
|
||||
}
|
||||
}
|
||||
|
||||
void MtpServer::addEditObject(MtpObjectHandle handle, MtpString& path,
|
||||
uint64_t size, MtpObjectFormat format, int fd) {
|
||||
ObjectEdit* edit = new ObjectEdit;
|
||||
edit->handle = handle;
|
||||
edit->path = path;
|
||||
edit->size = size;
|
||||
edit->format = format;
|
||||
edit->fd = fd;
|
||||
mObjectEditList.add(edit);
|
||||
}
|
||||
|
||||
MtpServer::ObjectEdit* MtpServer::getEditObject(MtpObjectHandle handle) {
|
||||
int count = mObjectEditList.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ObjectEdit* edit = mObjectEditList[i];
|
||||
if (edit->handle == handle) return edit;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void MtpServer::removeEditObject(MtpObjectHandle handle) {
|
||||
int count = mObjectEditList.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ObjectEdit* edit = mObjectEditList[i];
|
||||
if (edit->handle == handle) {
|
||||
delete edit;
|
||||
mObjectEditList.removeAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOGE("ObjectEdit not found in removeEditObject");
|
||||
}
|
||||
|
||||
void MtpServer::commitEdit(ObjectEdit* edit) {
|
||||
mDatabase->endSendObject((const char *)edit->path, edit->handle, edit->format, true);
|
||||
}
|
||||
|
||||
|
||||
bool MtpServer::handleRequest() {
|
||||
Mutex::Autolock autoLock(mMutex);
|
||||
|
||||
@ -322,7 +376,8 @@ bool MtpServer::handleRequest() {
|
||||
response = doGetObject();
|
||||
break;
|
||||
case MTP_OPERATION_GET_PARTIAL_OBJECT:
|
||||
response = doGetPartialObject();
|
||||
case MTP_OPERATION_GET_PARTIAL_OBJECT_64:
|
||||
response = doGetPartialObject(operation);
|
||||
break;
|
||||
case MTP_OPERATION_SEND_OBJECT_INFO:
|
||||
response = doSendObjectInfo();
|
||||
@ -339,6 +394,18 @@ bool MtpServer::handleRequest() {
|
||||
case MTP_OPERATION_GET_DEVICE_PROP_DESC:
|
||||
response = doGetDevicePropDesc();
|
||||
break;
|
||||
case MTP_OPERATION_SEND_PARTIAL_OBJECT:
|
||||
response = doSendPartialObject();
|
||||
break;
|
||||
case MTP_OPERATION_TRUNCATE_OBJECT:
|
||||
response = doTruncateObject();
|
||||
break;
|
||||
case MTP_OPERATION_BEGIN_EDIT_OBJECT:
|
||||
response = doBeginEditObject();
|
||||
break;
|
||||
case MTP_OPERATION_END_EDIT_OBJECT:
|
||||
response = doEndEditObject();
|
||||
break;
|
||||
default:
|
||||
LOGE("got unsupported command %s", MtpDebug::getOperationCodeName(operation));
|
||||
response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
|
||||
@ -363,7 +430,7 @@ MtpResponseCode MtpServer::doGetDeviceInfo() {
|
||||
mData.putUInt16(MTP_STANDARD_VERSION);
|
||||
mData.putUInt32(6); // MTP Vendor Extension ID
|
||||
mData.putUInt16(MTP_STANDARD_VERSION);
|
||||
string.set("microsoft.com: 1.0;");
|
||||
string.set("microsoft.com: 1.0; android.com: 1.0;");
|
||||
mData.putString(string); // MTP Extensions
|
||||
mData.putUInt16(0); //Functional Mode
|
||||
mData.putAUInt16(kSupportedOperationCodes,
|
||||
@ -601,7 +668,40 @@ MtpResponseCode MtpServer::doGetObjectInfo() {
|
||||
if (!hasStorage())
|
||||
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
|
||||
MtpObjectHandle handle = mRequest.getParameter(1);
|
||||
return mDatabase->getObjectInfo(handle, mData);
|
||||
MtpObjectInfo info(handle);
|
||||
MtpResponseCode result = mDatabase->getObjectInfo(handle, info);
|
||||
if (result == MTP_RESPONSE_OK) {
|
||||
char date[20];
|
||||
|
||||
mData.putUInt32(info.mStorageID);
|
||||
mData.putUInt16(info.mFormat);
|
||||
mData.putUInt16(info.mProtectionStatus);
|
||||
|
||||
// if object is being edited the database size may be out of date
|
||||
uint32_t size = info.mCompressedSize;
|
||||
ObjectEdit* edit = getEditObject(handle);
|
||||
if (edit)
|
||||
size = (edit->size > 0xFFFFFFFFLL ? 0xFFFFFFFF : (uint32_t)edit->size);
|
||||
mData.putUInt32(size);
|
||||
|
||||
mData.putUInt16(info.mThumbFormat);
|
||||
mData.putUInt32(info.mThumbCompressedSize);
|
||||
mData.putUInt32(info.mThumbPixWidth);
|
||||
mData.putUInt32(info.mThumbPixHeight);
|
||||
mData.putUInt32(info.mImagePixWidth);
|
||||
mData.putUInt32(info.mImagePixHeight);
|
||||
mData.putUInt32(info.mImagePixDepth);
|
||||
mData.putUInt32(info.mParent);
|
||||
mData.putUInt16(info.mAssociationType);
|
||||
mData.putUInt32(info.mAssociationDesc);
|
||||
mData.putUInt32(info.mSequenceNumber);
|
||||
mData.putString(info.mName);
|
||||
mData.putEmptyString(); // date created
|
||||
formatDateTime(info.mDateModified, date, sizeof(date));
|
||||
mData.putString(date); // date modified
|
||||
mData.putEmptyString(); // keywords
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
MtpResponseCode MtpServer::doGetObject() {
|
||||
@ -641,12 +741,22 @@ MtpResponseCode MtpServer::doGetObject() {
|
||||
return MTP_RESPONSE_OK;
|
||||
}
|
||||
|
||||
MtpResponseCode MtpServer::doGetPartialObject() {
|
||||
MtpResponseCode MtpServer::doGetPartialObject(MtpOperationCode operation) {
|
||||
if (!hasStorage())
|
||||
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
|
||||
MtpObjectHandle handle = mRequest.getParameter(1);
|
||||
uint32_t offset = mRequest.getParameter(2);
|
||||
uint32_t length = mRequest.getParameter(3);
|
||||
uint64_t offset;
|
||||
uint32_t length;
|
||||
offset = mRequest.getParameter(2);
|
||||
if (operation == MTP_OPERATION_GET_PARTIAL_OBJECT_64) {
|
||||
// android extension with 64 bit offset
|
||||
uint64_t offset2 = mRequest.getParameter(3);
|
||||
offset = offset | (offset2 << 32);
|
||||
length = mRequest.getParameter(4);
|
||||
} else {
|
||||
// standard GetPartialObject
|
||||
length = mRequest.getParameter(3);
|
||||
}
|
||||
MtpString pathBuf;
|
||||
int64_t fileLength;
|
||||
MtpObjectFormat format;
|
||||
@ -933,4 +1043,113 @@ MtpResponseCode MtpServer::doGetDevicePropDesc() {
|
||||
return MTP_RESPONSE_OK;
|
||||
}
|
||||
|
||||
MtpResponseCode MtpServer::doSendPartialObject() {
|
||||
if (!hasStorage())
|
||||
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
|
||||
MtpObjectHandle handle = mRequest.getParameter(1);
|
||||
uint64_t offset = mRequest.getParameter(2);
|
||||
uint64_t offset2 = mRequest.getParameter(3);
|
||||
offset = offset | (offset2 << 32);
|
||||
uint32_t length = mRequest.getParameter(4);
|
||||
|
||||
ObjectEdit* edit = getEditObject(handle);
|
||||
if (!edit) {
|
||||
LOGE("object not open for edit in doSendPartialObject");
|
||||
return MTP_RESPONSE_GENERAL_ERROR;
|
||||
}
|
||||
|
||||
// can't start writing past the end of the file
|
||||
if (offset > edit->size) {
|
||||
LOGD("writing past end of object, offset: %lld, edit->size: %lld", offset, edit->size);
|
||||
return MTP_RESPONSE_GENERAL_ERROR;
|
||||
}
|
||||
|
||||
// read the header
|
||||
int ret = mData.readDataHeader(mFD);
|
||||
// FIXME - check for errors here.
|
||||
|
||||
// reset so we don't attempt to send this back
|
||||
mData.reset();
|
||||
|
||||
const char* filePath = (const char *)edit->path;
|
||||
LOGV("receiving partial %s %lld %ld\n", filePath, offset, length);
|
||||
mtp_file_range mfr;
|
||||
mfr.fd = edit->fd;
|
||||
mfr.offset = offset;
|
||||
mfr.length = length;
|
||||
|
||||
// transfer the file
|
||||
ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
|
||||
LOGV("MTP_RECEIVE_FILE returned %d", ret);
|
||||
if (ret < 0) {
|
||||
mResponse.setParameter(1, 0);
|
||||
if (errno == ECANCELED)
|
||||
return MTP_RESPONSE_TRANSACTION_CANCELLED;
|
||||
else
|
||||
return MTP_RESPONSE_GENERAL_ERROR;
|
||||
}
|
||||
mResponse.setParameter(1, length);
|
||||
uint64_t end = offset + length;
|
||||
if (end > edit->size) {
|
||||
edit->size = end;
|
||||
}
|
||||
return MTP_RESPONSE_OK;
|
||||
}
|
||||
|
||||
MtpResponseCode MtpServer::doTruncateObject() {
|
||||
MtpObjectHandle handle = mRequest.getParameter(1);
|
||||
ObjectEdit* edit = getEditObject(handle);
|
||||
if (!edit) {
|
||||
LOGE("object not open for edit in doTruncateObject");
|
||||
return MTP_RESPONSE_GENERAL_ERROR;
|
||||
}
|
||||
|
||||
uint64_t offset = mRequest.getParameter(2);
|
||||
uint64_t offset2 = mRequest.getParameter(3);
|
||||
offset |= (offset2 << 32);
|
||||
if (ftruncate(edit->fd, offset) != 0) {
|
||||
return MTP_RESPONSE_GENERAL_ERROR;
|
||||
} else {
|
||||
edit->size = offset;
|
||||
return MTP_RESPONSE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
MtpResponseCode MtpServer::doBeginEditObject() {
|
||||
MtpObjectHandle handle = mRequest.getParameter(1);
|
||||
if (getEditObject(handle)) {
|
||||
LOGE("object already open for edit in doBeginEditObject");
|
||||
return MTP_RESPONSE_GENERAL_ERROR;
|
||||
}
|
||||
|
||||
MtpString path;
|
||||
int64_t fileLength;
|
||||
MtpObjectFormat format;
|
||||
int result = mDatabase->getObjectFilePath(handle, path, fileLength, format);
|
||||
if (result != MTP_RESPONSE_OK)
|
||||
return result;
|
||||
|
||||
int fd = open((const char *)path, O_RDWR | O_EXCL);
|
||||
if (fd < 0) {
|
||||
LOGE("open failed for %s in doBeginEditObject (%d)", (const char *)path, errno);
|
||||
return MTP_RESPONSE_GENERAL_ERROR;
|
||||
}
|
||||
|
||||
addEditObject(handle, path, fileLength, format, fd);
|
||||
return MTP_RESPONSE_OK;
|
||||
}
|
||||
|
||||
MtpResponseCode MtpServer::doEndEditObject() {
|
||||
MtpObjectHandle handle = mRequest.getParameter(1);
|
||||
ObjectEdit* edit = getEditObject(handle);
|
||||
if (!edit) {
|
||||
LOGE("object not open for edit in doEndEditObject");
|
||||
return MTP_RESPONSE_GENERAL_ERROR;
|
||||
}
|
||||
|
||||
commitEdit(edit);
|
||||
removeEditObject(handle);
|
||||
return MTP_RESPONSE_OK;
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
@ -65,6 +65,17 @@ private:
|
||||
|
||||
Mutex mMutex;
|
||||
|
||||
// represents an MTP object that is being edited using the android extensions
|
||||
// for direct editing (BeginEditObject, SendPartialObject, TruncateObject and EndEditObject)
|
||||
struct ObjectEdit {
|
||||
MtpObjectHandle handle;
|
||||
MtpString path;
|
||||
uint64_t size;
|
||||
MtpObjectFormat format;
|
||||
int fd;
|
||||
};
|
||||
Vector<ObjectEdit*> mObjectEditList;
|
||||
|
||||
public:
|
||||
MtpServer(int fd, MtpDatabase* database,
|
||||
int fileGroup, int filePerm, int directoryPerm);
|
||||
@ -86,6 +97,12 @@ private:
|
||||
void sendStoreRemoved(MtpStorageID id);
|
||||
void sendEvent(MtpEventCode code, uint32_t param1);
|
||||
|
||||
void addEditObject(MtpObjectHandle handle, MtpString& path,
|
||||
uint64_t size, MtpObjectFormat format, int fd);
|
||||
ObjectEdit* getEditObject(MtpObjectHandle handle);
|
||||
void removeEditObject(MtpObjectHandle handle);
|
||||
void commitEdit(ObjectEdit* edit);
|
||||
|
||||
bool handleRequest();
|
||||
|
||||
MtpResponseCode doGetDeviceInfo();
|
||||
@ -106,12 +123,16 @@ private:
|
||||
MtpResponseCode doGetObjectPropList();
|
||||
MtpResponseCode doGetObjectInfo();
|
||||
MtpResponseCode doGetObject();
|
||||
MtpResponseCode doGetPartialObject();
|
||||
MtpResponseCode doGetPartialObject(MtpOperationCode operation);
|
||||
MtpResponseCode doSendObjectInfo();
|
||||
MtpResponseCode doSendObject();
|
||||
MtpResponseCode doDeleteObject();
|
||||
MtpResponseCode doGetObjectPropDesc();
|
||||
MtpResponseCode doGetDevicePropDesc();
|
||||
MtpResponseCode doSendPartialObject();
|
||||
MtpResponseCode doTruncateObject();
|
||||
MtpResponseCode doBeginEditObject();
|
||||
MtpResponseCode doEndEditObject();
|
||||
};
|
||||
|
||||
}; // namespace android
|
||||
|
@ -391,6 +391,19 @@
|
||||
#define MTP_OPERATION_SET_OBJECT_REFERENCES 0x9811
|
||||
#define MTP_OPERATION_SKIP 0x9820
|
||||
|
||||
// Android extensions for direct file IO
|
||||
|
||||
// Same as GetPartialObject, but with 64 bit offset
|
||||
#define MTP_OPERATION_GET_PARTIAL_OBJECT_64 0x95C1
|
||||
// Same as GetPartialObject64, but copying host to device
|
||||
#define MTP_OPERATION_SEND_PARTIAL_OBJECT 0x95C2
|
||||
// Truncates file to 64 bit length
|
||||
#define MTP_OPERATION_TRUNCATE_OBJECT 0x95C3
|
||||
// Must be called before using SendPartialObject and TruncateObject
|
||||
#define MTP_OPERATION_BEGIN_EDIT_OBJECT 0x95C4
|
||||
// Called to commit changes made by SendPartialObject and TruncateObject
|
||||
#define MTP_OPERATION_END_EDIT_OBJECT 0x95C5
|
||||
|
||||
// MTP Response Codes
|
||||
#define MTP_RESPONSE_UNDEFINED 0x2000
|
||||
#define MTP_RESPONSE_OK 0x2001
|
||||
|
Reference in New Issue
Block a user