Преглед на файлове

Merge mtp from https://github.com/Alex4386/f0-mtp

Willy-JL преди 1 година
родител
ревизия
af49f393f5

+ 1 - 0
mtp/.gitignore

@@ -1,6 +1,7 @@
 dist/*
 .vscode
 .clang-format
+.clangd
 .editorconfig
 .env
 .ufbt

+ 0 - 1
mtp/README.md

@@ -41,7 +41,6 @@ Here are some things you should know before using this application:
 >
 > - **Corrupt the SD Card filesystem** due to current implementation's limitation.
 >   (If you know how to fix this issue, feel free to give me a PR!, quick `chkdsk` will fix the corruption though)
-> - This _might_ be due to [flipperzero-firmware#3452](https://github.com/flipperdevices/flipperzero-firmware/issues/3452), but not throughly tested.
 
 > [!WARNING]  
 > **DO NOT** use `UNICODE` characters in the file/directory names.  

+ 223 - 0
mtp/src/scenes/mtp/device_props.c

@@ -0,0 +1,223 @@
+#include "device_props.h"
+#include "utils.h"
+#include "usb_desc.h"
+#include "mtp_support.h"
+#include <power/power_service/power.h>
+
+int GetDevicePropValueInternal(uint32_t prop_code, uint8_t* buffer) {
+    uint8_t* ptr = buffer;
+    uint16_t length;
+
+    switch(prop_code) {
+    case 0xd402: {
+        const char* deviceName = furi_hal_version_get_name_ptr();
+        if(deviceName == NULL) {
+            deviceName = "Flipper Zero";
+        }
+
+        WriteMTPString(ptr, deviceName, &length);
+        ptr += length;
+        break;
+    }
+    case 0x5001: {
+        // Battery level
+        Power* power = furi_record_open(RECORD_POWER);
+        FURI_LOG_I("MTP", "Getting battery level");
+        if(power == NULL) {
+            *(uint8_t*)ptr = 0x00;
+        } else {
+            PowerInfo info;
+            power_get_info(power, &info);
+
+            FURI_LOG_I("MTP", "Battery level: %d", info.charge);
+
+            *(uint8_t*)ptr = info.charge;
+            furi_record_close(RECORD_POWER);
+        }
+        ptr += sizeof(uint8_t);
+        break;
+    }
+    default:
+        // Unsupported property
+        break;
+    }
+
+    return ptr - buffer;
+}
+
+int GetDevicePropDescInternal(uint32_t prop_code, uint8_t* buffer) {
+    uint8_t* ptr = buffer;
+    uint16_t length;
+
+    switch(prop_code) {
+    case 0xd402:
+        // Device friendly name
+        *(uint16_t*)ptr = prop_code;
+        ptr += 2;
+
+        // type is string
+        *(uint16_t*)ptr = 0xffff;
+        ptr += 2;
+
+        // read-only
+        *(uint8_t*)ptr = 0x00;
+        ptr += 1;
+
+        length = GetDevicePropValueInternal(prop_code, ptr);
+        ptr += length;
+
+        length = GetDevicePropValueInternal(prop_code, ptr);
+        ptr += length;
+
+        // no-form
+        *(uint8_t*)ptr = 0x00;
+        ptr += 1;
+        break;
+    case 0x5001:
+        // Device friendly name
+        *(uint16_t*)ptr = prop_code;
+        ptr += 2;
+
+        // type is uint8
+        *(uint16_t*)ptr = 0x0002;
+        ptr += 2;
+
+        // read-only
+        *(uint8_t*)ptr = 0x00;
+        ptr += 1;
+
+        length = GetDevicePropValueInternal(prop_code, ptr);
+        ptr += length;
+
+        length = GetDevicePropValueInternal(prop_code, ptr);
+        ptr += length;
+
+        // no-form
+        *(uint8_t*)ptr = 0x00;
+        ptr += 1;
+        break;
+
+    default:
+        // Unsupported property
+        break;
+    }
+
+    return ptr - buffer;
+}
+
+void GetDevicePropValue(AppMTP* mtp, uint32_t transaction_id, uint32_t prop_code) {
+    uint8_t* response = malloc(sizeof(uint8_t) * MTP_BUFFER_SIZE);
+    int length = GetDevicePropValueInternal(prop_code, response);
+    send_mtp_response_buffer(
+        mtp, MTP_TYPE_DATA, MTP_OP_GET_DEVICE_PROP_VALUE, transaction_id, response, length);
+    send_mtp_response_buffer(mtp, MTP_TYPE_RESPONSE, MTP_RESP_OK, transaction_id, NULL, 0);
+    free(response);
+}
+
+void GetDevicePropDesc(AppMTP* mtp, uint32_t transaction_id, uint32_t prop_code) {
+    uint8_t* response = malloc(sizeof(uint8_t) * MTP_BUFFER_SIZE);
+    int length = GetDevicePropDescInternal(prop_code, response);
+    send_mtp_response_buffer(
+        mtp, MTP_TYPE_DATA, MTP_OP_GET_DEVICE_PROP_DESC, transaction_id, response, length);
+    send_mtp_response_buffer(mtp, MTP_TYPE_RESPONSE, MTP_RESP_OK, transaction_id, NULL, 0);
+    free(response);
+}
+
+int BuildDeviceInfo(uint8_t* buffer) {
+    uint8_t* ptr = buffer;
+    uint16_t length;
+
+    // Standard version
+    *(uint16_t*)ptr = 100;
+    ptr += sizeof(uint16_t);
+
+    // Vendor extension ID
+    *(uint32_t*)ptr = MTP_VENDOR_EXTENSION_ID;
+    ptr += sizeof(uint32_t);
+
+    // Vendor extension version
+    *(uint16_t*)ptr = MTP_VENDOR_EXTENSION_VERSION;
+    ptr += sizeof(uint16_t);
+
+    // Vendor extension description
+    WriteMTPString(ptr, "microsoft.com: 1.0;", &length);
+    ptr += length;
+
+    // Functional mode
+    FURI_LOG_I("MTP", "MTP Functional Mode");
+    *(uint16_t*)ptr = MTP_FUNCTIONAL_MODE;
+    ptr += sizeof(uint16_t);
+
+    // Operations supported
+    FURI_LOG_I("MTP", "Writing Operation Supported");
+    length = sizeof(supported_operations) / sizeof(uint16_t);
+    *(uint32_t*)ptr = length; // Number of supported operations
+    FURI_LOG_I("MTP", "Supported Operations: %d", length);
+    ptr += sizeof(uint32_t);
+
+    for(int i = 0; i < length; i++) {
+        FURI_LOG_I("MTP", "Operation: %04x", supported_operations[i]);
+        *(uint16_t*)ptr = supported_operations[i];
+        ptr += sizeof(uint16_t);
+    }
+
+    // Supported events (example, add as needed)
+    FURI_LOG_I("MTP", "Supported Events");
+    *(uint32_t*)ptr = 0; // Number of supported events
+    ptr += sizeof(uint32_t);
+
+    FURI_LOG_I("MTP", "Supported Device Properties");
+    length = sizeof(supported_device_properties) / sizeof(uint16_t);
+    *(uint32_t*)ptr = length; // Number of supported device properties
+    ptr += sizeof(uint32_t);
+
+    for(int i = 0; i < length; i++) {
+        FURI_LOG_I("MTP", "Supported Device Props");
+        *(uint16_t*)ptr = supported_device_properties[i];
+        ptr += sizeof(uint16_t);
+    }
+
+    // Supported capture formats (example, add as needed)
+    *(uint32_t*)ptr = 0; // Number of supported capture formats
+    ptr += sizeof(uint32_t);
+
+    // Supported playback formats (example, add as needed)
+    FURI_LOG_I("MTP", "Supported Playback Formats");
+    length = sizeof(supported_playback_formats) / sizeof(uint16_t);
+    *(uint32_t*)ptr = length; // Number of supported playback formats
+    ptr += sizeof(uint32_t);
+
+    for(int i = 0; i < length; i++) {
+        *(uint16_t*)ptr = supported_playback_formats[i];
+        ptr += sizeof(uint16_t);
+    }
+
+    // Manufacturer
+    WriteMTPString(ptr, USB_MANUFACTURER_STRING, &length);
+    ptr += length;
+
+    // Model
+    WriteMTPString(ptr, USB_DEVICE_MODEL, &length);
+    ptr += length;
+
+    // Device version
+    const Version* ver = furi_hal_version_get_firmware_version();
+    WriteMTPString(ptr, version_get_version(ver), &length);
+    ptr += length;
+
+    // Serial number
+    char* serial = malloc(sizeof(char) * furi_hal_version_uid_size() * 2 + 1);
+    const uint8_t* uid = furi_hal_version_uid();
+    for(size_t i = 0; i < furi_hal_version_uid_size(); i++) {
+        serial[i * 2] = byte_to_hex(uid[i] >> 4);
+        serial[i * 2 + 1] = byte_to_hex(uid[i] & 0x0F);
+    }
+    serial[furi_hal_version_uid_size() * 2] = '\0';
+
+    WriteMTPString(ptr, serial, &length);
+    ptr += length;
+
+    free(serial);
+
+    return ptr - buffer;
+}

+ 12 - 0
mtp/src/scenes/mtp/device_props.h

@@ -0,0 +1,12 @@
+#pragma once
+
+#include <furi.h>
+#include "main.h"
+#include "mtp.h"
+
+// Device property operations
+void GetDevicePropValue(AppMTP* mtp, uint32_t transaction_id, uint32_t prop_code);
+void GetDevicePropDesc(AppMTP* mtp, uint32_t transaction_id, uint32_t prop_code);
+int GetDevicePropValueInternal(uint32_t prop_code, uint8_t* buffer);
+int GetDevicePropDescInternal(uint32_t prop_code, uint8_t* buffer);
+int BuildDeviceInfo(uint8_t* buffer);

+ 34 - 917
mtp/src/scenes/mtp/mtp.c

@@ -3,61 +3,13 @@
 #include <storage/storage.h>
 #include "main.h"
 #include "mtp.h"
+#include "mtp_ops.h"
+#include "mtp_support.h"
+#include "device_props.h"
+#include "storage_ops.h"
 #include "usb_desc.h"
 #include "utils.h"
 
-#define BLOCK_SIZE 65536
-
-// Supported operations (example, add as needed)
-uint16_t supported_operations[] = {
-    MTP_OP_GET_DEVICE_INFO,
-    MTP_OP_OPEN_SESSION,
-    MTP_OP_CLOSE_SESSION,
-    MTP_OP_GET_STORAGE_IDS,
-    MTP_OP_GET_STORAGE_INFO,
-    MTP_OP_GET_NUM_OBJECTS,
-    MTP_OP_GET_OBJECT_HANDLES,
-    MTP_OP_GET_OBJECT_INFO,
-    MTP_OP_GET_OBJECT,
-    MTP_OP_SEND_OBJECT_INFO,
-    MTP_OP_SEND_OBJECT,
-    MTP_OP_DELETE_OBJECT,
-    MTP_OP_GET_DEVICE_PROP_DESC,
-    MTP_OP_GET_DEVICE_PROP_VALUE,
-    MTP_OP_GET_OBJECT_PROPS_SUPPORTED,
-    MTP_OP_SET_OBJECT_PROP_VALUE,
-    MTP_OP_MOVE_OBJECT,
-    MTP_OP_POWER_DOWN,
-};
-
-uint16_t supported_object_props[] = {
-    MTP_PROP_STORAGE_ID,
-    MTP_PROP_OBJECT_FORMAT,
-    MTP_PROP_OBJECT_FILE_NAME,
-};
-
-// Supported device properties (example, add as needed)
-uint16_t supported_device_properties[] = {
-    0xd402, // Device friendly name
-    0x5001, // Battery level
-};
-
-uint16_t supported_playback_formats[] = {
-    MTP_FORMAT_UNDEFINED,
-    MTP_FORMAT_ASSOCIATION,
-};
-
-void merge_path(char* target, char* base, char* name) {
-    // implement this way since strcat is unavailable
-
-    char* ptr = target;
-    strcpy(target, base);
-    ptr += strlen(base);
-    strcpy(ptr, "/");
-    ptr++;
-    strcpy(ptr, name);
-}
-
 // temporary storage for buffer
 MTPDataPersistence persistence;
 
@@ -124,7 +76,7 @@ void handle_mtp_data_packet(AppMTP* mtp, uint8_t* buffer, int32_t length, bool c
         switch(header->op) {
         case MTP_OP_SEND_OBJECT_INFO:
         case MTP_OP_SET_OBJECT_PROP_VALUE: {
-            persistence.global_buffer = malloc(sizeof(uint8_t) * 256);
+            persistence.global_buffer = malloc(sizeof(uint8_t) * MTP_BUFFER_SIZE);
             persistence.buffer_offset = 0;
             break;
         }
@@ -163,20 +115,34 @@ void handle_mtp_data_packet(AppMTP* mtp, uint8_t* buffer, int32_t length, bool c
         }
 
         File* file = persistence.current_file;
-        // no need since the file is already opened
-        // storage_file_seek(file, offset, SEEK_SET);
 
-        storage_file_write(file, ptr, bytes_length);
+        // Write with error handling
+        uint16_t bytes_written = storage_file_write(file, ptr, bytes_length);
+        if(bytes_written != bytes_length) {
+            FURI_LOG_E("MTP", "Write failed: %d/%d bytes written", bytes_written, bytes_length);
+            send_mtp_response(mtp, 3, MTP_RESP_STORE_FULL, persistence.transaction_id, NULL);
+            storage_file_close(file);
+            storage_file_free(file);
+            persistence.current_file = NULL;
+            return;
+        }
+
+        persistence.buffer_offset += bytes_written;
+        persistence.left_bytes -= bytes_written;
 
-        persistence.buffer_offset += bytes_length;
-        persistence.left_bytes -= bytes_length;
+        // Sync periodically based on configured interval
+        if(persistence.buffer_offset % MTP_FILE_SYNC_INTERVAL == 0) {
+            storage_file_sync(file);
+            FURI_LOG_I("MTP", "Progress: %ld bytes remaining", persistence.left_bytes);
+        }
 
         if(persistence.left_bytes <= 0) {
+            storage_file_sync(file);
             send_mtp_response(mtp, 3, MTP_RESP_OK, persistence.transaction_id, NULL);
             storage_file_close(file);
             storage_file_free(file);
-
             persistence.current_file = NULL;
+            FURI_LOG_I("MTP", "File transfer completed successfully");
         }
     } else if(persistence.op == MTP_OP_SET_OBJECT_PROP_VALUE) {
         uint32_t handle = persistence.params[0];
@@ -195,7 +161,7 @@ void handle_mtp_data_packet(AppMTP* mtp, uint8_t* buffer, int32_t length, bool c
             print_bytes("MTP FileName", ptr, length - sizeof(struct MTPHeader));
 
             char* name = ReadMTPString(ptr);
-            char* full_path = malloc(sizeof(char) * 256);
+            char* full_path = malloc(sizeof(char) * MTP_PATH_SIZE);
 
             merge_path(full_path, get_base_path_from_storage_id(persistence.params[2]), name);
 
@@ -297,7 +263,7 @@ void handle_mtp_data_complete(AppMTP* mtp) {
             }
         }
 
-        char* full_path = malloc(sizeof(char) * 256);
+        char* full_path = malloc(sizeof(char) * MTP_PATH_SIZE);
         merge_path(full_path, base_path, name);
 
         FURI_LOG_I("MTP", "Format: %04x", info->format);
@@ -380,7 +346,7 @@ void handle_mtp_command(AppMTP* mtp, struct MTPContainer* container) {
         break;
     case MTP_OP_GET_STORAGE_INFO: {
         FURI_LOG_I("MTP", "GetStorageInfo operation");
-        uint8_t* info = malloc(sizeof(uint8_t) * 256);
+        uint8_t* info = malloc(sizeof(uint8_t) * MTP_BUFFER_SIZE);
         int length = GetStorageInfo(mtp, container->params[0], info);
         send_mtp_response_buffer(
             mtp,
@@ -406,7 +372,7 @@ void handle_mtp_command(AppMTP* mtp, struct MTPContainer* container) {
                 0);
             break;
         } else {
-            uint8_t* buffer = malloc(sizeof(uint8_t) * 256);
+            uint8_t* buffer = malloc(sizeof(uint8_t) * MTP_BUFFER_SIZE);
             int length = GetObjectHandles(mtp, container->params[0], container->params[2], buffer);
             send_mtp_response_buffer(
                 mtp,
@@ -423,7 +389,7 @@ void handle_mtp_command(AppMTP* mtp, struct MTPContainer* container) {
         break;
     case MTP_OP_GET_OBJECT_INFO: {
         FURI_LOG_I("MTP", "GetObjectInfo operation");
-        uint8_t* buffer = malloc(sizeof(uint8_t) * 512);
+        uint8_t* buffer = malloc(sizeof(uint8_t) * MTP_BUFFER_SIZE);
         int length = GetObjectInfo(mtp, container->params[0], buffer);
 
         if(length < 0) {
@@ -451,7 +417,7 @@ void handle_mtp_command(AppMTP* mtp, struct MTPContainer* container) {
     }
     case MTP_OP_GET_OBJECT_PROPS_SUPPORTED: {
         FURI_LOG_I("MTP", "GetObjectPropsSupported operation");
-        uint8_t* buffer = malloc(sizeof(uint8_t) * 256);
+        uint8_t* buffer = malloc(sizeof(uint8_t) * MTP_BUFFER_SIZE);
         uint32_t count = sizeof(supported_object_props) / sizeof(uint16_t);
         memcpy(buffer, &count, sizeof(uint32_t));
         memcpy(buffer + sizeof(uint32_t), supported_object_props, sizeof(uint16_t) * count);
@@ -481,12 +447,12 @@ void handle_mtp_command(AppMTP* mtp, struct MTPContainer* container) {
 
     case MTP_OP_GET_DEVICE_PROP_VALUE:
         FURI_LOG_I("MTP", "GetDevicePropValue operation");
-        send_device_prop_value(mtp, container->header.transaction_id, container->params[0]);
+        GetDevicePropValue(mtp, container->header.transaction_id, container->params[0]);
         // Process the GetDevicePropValue operation
         break;
     case MTP_OP_GET_DEVICE_PROP_DESC:
         FURI_LOG_I("MTP", "GetDevicePropDesc operation");
-        send_device_prop_desc(mtp, container->header.transaction_id, container->params[0]);
+        GetDevicePropDesc(mtp, container->header.transaction_id, container->params[0]);
         // Process the GetDevicePropDesc operation
         break;
     // Handle bulk transfer specific operations
@@ -562,7 +528,7 @@ void send_storage_ids(AppMTP* mtp, uint32_t transaction_id) {
 }
 
 void send_device_info(AppMTP* mtp, uint32_t transaction_id) {
-    uint8_t* response = malloc(sizeof(uint8_t) * 256);
+    uint8_t* response = malloc(sizeof(uint8_t) * MTP_BUFFER_SIZE);
     int length = BuildDeviceInfo(response);
     send_mtp_response_buffer(
         mtp, MTP_TYPE_DATA, MTP_OP_GET_DEVICE_INFO, transaction_id, response, length);
@@ -570,446 +536,9 @@ void send_device_info(AppMTP* mtp, uint32_t transaction_id) {
     free(response);
 }
 
-void send_device_prop_value(AppMTP* mtp, uint32_t transaction_id, uint32_t prop_code) {
-    uint8_t* response = malloc(sizeof(uint8_t) * 256);
-    int length = GetDevicePropValue(prop_code, response);
-    send_mtp_response_buffer(
-        mtp, MTP_TYPE_DATA, MTP_OP_GET_DEVICE_PROP_VALUE, transaction_id, response, length);
-    send_mtp_response_buffer(mtp, MTP_TYPE_RESPONSE, MTP_RESP_OK, transaction_id, NULL, 0);
-    free(response);
-}
-
-void send_device_prop_desc(AppMTP* mtp, uint32_t transaction_id, uint32_t prop_code) {
-    uint8_t* response = malloc(sizeof(uint8_t) * 256);
-    int length = GetDevicePropDesc(prop_code, response);
-    send_mtp_response_buffer(
-        mtp, MTP_TYPE_DATA, MTP_OP_GET_DEVICE_PROP_DESC, transaction_id, response, length);
-    send_mtp_response_buffer(mtp, MTP_TYPE_RESPONSE, MTP_RESP_OK, transaction_id, NULL, 0);
-    free(response);
-}
-
-char* get_path_from_handle(AppMTP* mtp, uint32_t handle) {
-    FileHandle* current = mtp->handles;
-    while(current != NULL) {
-        if(current->handle == handle) {
-            return current->path;
-        }
-        current = current->next;
-    }
-    return NULL;
-}
-
-uint32_t issue_object_handle(AppMTP* mtp, char* path) {
-    int handle = 1;
-    int length = strlen(path);
-    char* path_store = malloc(sizeof(char) * (length + 1));
-    strcpy(path_store, path);
-
-    if(mtp->handles == NULL) {
-        mtp->handles = malloc(sizeof(FileHandle));
-        mtp->handles->handle = handle;
-        mtp->handles->path = path_store;
-        mtp->handles->next = NULL;
-        return handle;
-    }
-
-    FileHandle* current = mtp->handles;
-    if(strcmp(current->path, path) == 0) {
-        return current->handle;
-    }
-
-    while(current->next != NULL) {
-        if(strcmp(current->path, path) == 0) {
-            return current->handle;
-        }
-        current = current->next;
-        handle++;
-    }
-
-    current->next = malloc(sizeof(FileHandle));
-    current = current->next;
-    handle++;
-
-    current->handle = handle;
-    current->path = path_store;
-    current->next = NULL;
-    return handle;
-}
-
-uint32_t update_object_handle_path(AppMTP* mtp, uint32_t handle, char* path) {
-    FileHandle* current = mtp->handles;
-    while(current != NULL) {
-        if(current->handle == handle) {
-            free(current->path);
-            current->path = path;
-            return handle;
-        }
-        current = current->next;
-    }
-    return 0;
-}
-
-char* get_base_path_from_storage_id(uint32_t storage_id) {
-    if(storage_id == INTERNAL_STORAGE_ID) {
-        return STORAGE_INT_PATH_PREFIX;
-    } else if(storage_id == EXTERNAL_STORAGE_ID) {
-        return STORAGE_EXT_PATH_PREFIX;
-    }
-    return NULL;
-}
-
-int list_and_issue_handles(
-    AppMTP* mtp,
-    uint32_t storage_id,
-    uint32_t association,
-    uint32_t* handles) {
-    Storage* storage = mtp->storage;
-    char* base_path = get_base_path_from_storage_id(storage_id);
-    if(base_path == NULL) {
-        base_path = "";
-    }
-
-    File* dir = storage_file_alloc(storage);
-    if(association == 0xffffffff) {
-        // count the objects in the root directory
-        storage_dir_open(dir, base_path);
-    } else {
-        char* path = get_path_from_handle(mtp, association);
-        FURI_LOG_I("MTP", "Association path: %s", path);
-        if(path == NULL) {
-            return 0;
-        }
-        storage_dir_open(dir, path);
-        base_path = path;
-    }
-
-    int count = 0;
-    FileInfo fileinfo;
-    char* file_name = malloc(sizeof(char) * 256);
-    char* full_path = malloc(sizeof(char) * 256);
-    while(storage_dir_read(dir, &fileinfo, file_name, 256)) {
-        if(file_info_is_dir(&fileinfo)) {
-            FURI_LOG_I("MTP", "Found directory: %s", file_name);
-        } else {
-            FURI_LOG_I("MTP", "Found file: %s", file_name);
-        }
-
-        merge_path(full_path, base_path, file_name);
-        FURI_LOG_I("MTP", "Full path: %s", full_path);
-
-        uint32_t handle = issue_object_handle(mtp, full_path);
-        if(handles != NULL) {
-            handles[count] = handle;
-        }
-        count++;
-    }
-
-    FURI_LOG_I("MTP", "Getting number of objects in storage %ld", storage_id);
-    FURI_LOG_I("MTP", "Base path: %s", base_path);
-    FURI_LOG_I("MTP", "Association: %ld", association);
-    FURI_LOG_I("MTP", "Number of objects: %d", count);
-
-    storage_dir_close(dir);
-    storage_file_free(dir);
-    free(file_name);
-    free(full_path);
-    return count;
-}
-
-struct GetObjectContext {
-    File* file;
-};
-
-int GetObject_callback(void* ctx, uint8_t* buffer, int length) {
-    struct GetObjectContext* obj_ctx = (struct GetObjectContext*)ctx;
-    return storage_file_read(obj_ctx->file, buffer, length);
-}
-
-void GetObject(AppMTP* mtp, uint32_t transaction_id, uint32_t handle) {
-    char* path = get_path_from_handle(mtp, handle);
-    if(path == NULL) {
-        send_mtp_response(
-            mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
-        return;
-    }
-
-    FURI_LOG_I("MTP", "Getting object: %s", path);
-
-    Storage* storage = mtp->storage;
-    File* file = storage_file_alloc(storage);
-    if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
-        FURI_LOG_E("MTP", "Failed to open file: %s", path);
-        send_mtp_response(
-            mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
-        return;
-    }
-
-    uint32_t size = storage_file_size(file);
-
-    struct GetObjectContext ctx = {
-        .file = file,
-    };
-
-    send_mtp_response_stream(
-        mtp, MTP_TYPE_DATA, MTP_OP_GET_OBJECT, transaction_id, &ctx, GetObject_callback, size);
-
-    send_mtp_response(mtp, MTP_TYPE_RESPONSE, MTP_RESP_OK, transaction_id, NULL);
-
-    storage_file_close(file);
-    storage_file_free(file);
-}
-
-int GetObjectInfo(AppMTP* mtp, uint32_t handle, uint8_t* buffer) {
-    ObjectInfoHeader* header = (ObjectInfoHeader*)buffer;
-    uint8_t* ptr = buffer + sizeof(ObjectInfoHeader);
-
-    char* path = get_path_from_handle(mtp, handle);
-    if(path == NULL) {
-        return -1;
-    }
-
-    FURI_LOG_I("MTP", "Getting object info for handle %ld", handle);
-    FURI_LOG_I("MTP", "Path: %s", path);
-
-    header->protection_status = 0;
-
-    Storage* storage = mtp->storage;
-    File* file = storage_file_alloc(storage);
-    uint16_t length;
-
-    FileInfo fileinfo;
-    FS_Error err = storage_common_stat(storage, path, &fileinfo);
-    if(err != FSE_OK) {
-        FURI_LOG_E("MTP", "Failed to get file info: %s", filesystem_api_error_get_desc(err));
-        return -1;
-    }
-
-    if(memcmp(path, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) {
-        FURI_LOG_I("MTP", "Object in Internal storage");
-        header->storage_id = INTERNAL_STORAGE_ID;
-    } else if(memcmp(path, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) {
-        FURI_LOG_I("MTP", "Object in External storage");
-        header->storage_id = EXTERNAL_STORAGE_ID;
-    } else {
-        return -1;
-    }
-
-    if(file_info_is_dir(&fileinfo)) {
-        FURI_LOG_I("MTP", "Directory");
-
-        header->format = MTP_FORMAT_ASSOCIATION;
-        header->association_type = 0x0001; // Generic folder
-    } else {
-        FURI_LOG_I("MTP", "Undefined type");
-
-        header->format = MTP_FORMAT_UNDEFINED;
-    }
-
-    header->compressed_size = fileinfo.size;
-
-    header->thumb_format = 0;
-    header->thumb_compressed_size = 0;
-    header->thumb_pix_width = 0;
-    header->thumb_pix_height = 0;
-    header->image_pix_width = 0;
-    header->image_pix_height = 0;
-    header->image_bit_depth = 0;
-
-    /*
-    // is on root directory (/int or /ext)
-    if(strchr(path + 1, '/') == NULL) {
-        header->parent_object = 0;
-    } else {
-        char* parent_path = malloc(sizeof(char) * 256);
-        strcpy(parent_path, path);
-        char* last_slash = strrchr(parent_path, '/');
-        *last_slash = '\0';
-        header->parent_object = issue_object_handle(mtp, parent_path);
-
-        free(parent_path);
-    }
-    */
-
-    char* file_name = strrchr(path, '/');
-    FURI_LOG_I("MTP", "File name: %s", file_name + 1);
-
-    WriteMTPString(ptr, file_name + 1, &length);
-    ptr += length;
-
-    // get created
-    WriteMTPString(ptr, "20240608T010702", &length);
-    ptr += length;
-
-    // get last modified
-    WriteMTPString(ptr, "20240608T010702", &length);
-    ptr += length;
-
-    // get keywords
-    WriteMTPString(ptr, "", &length);
-    ptr += length;
-
-    storage_file_free(file);
-
-    return ptr - buffer;
-}
-
-bool CheckMTPStringHasUnicode(uint8_t* buffer) {
-    uint8_t* base = buffer;
-    int len = *base;
-
-    uint16_t* ptr = (uint16_t*)(base + 1);
-
-    for(int i = 0; i < len; i++) {
-        if(ptr[i] > 0x7F) {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-char* ReadMTPString(uint8_t* buffer) {
-    int len16 = *(uint8_t*)buffer;
-    if(len16 == 0) {
-        return "";
-    }
-
-    char* str = malloc(sizeof(char) * len16);
-
-    uint8_t* base = buffer + 1;
-    uint16_t* ptr = (uint16_t*)base;
-
-    for(int i = 0; i < len16; i++) {
-        str[i] = *ptr++;
-    }
-
-    return str;
-}
-
 int GetNumObjects(AppMTP* mtp, uint32_t storage_id, uint32_t association) {
     return list_and_issue_handles(mtp, storage_id, association, NULL);
 }
-
-int GetObjectHandles(AppMTP* mtp, uint32_t storage_id, uint32_t association, uint8_t* buffer) {
-    uint8_t* ptr = buffer;
-    uint16_t length;
-
-    UNUSED(length);
-
-    // For now, just return a single object handle
-    int count = GetNumObjects(mtp, storage_id, association);
-
-    *(uint32_t*)ptr = count;
-    ptr += sizeof(uint32_t);
-
-    uint32_t* handles = (uint32_t*)ptr;
-    length = list_and_issue_handles(mtp, storage_id, association, handles);
-    ptr += length * sizeof(uint32_t);
-
-    return ptr - buffer;
-}
-
-int GetDevicePropValue(uint32_t prop_code, uint8_t* buffer) {
-    uint8_t* ptr = buffer;
-    uint16_t length;
-
-    switch(prop_code) {
-    case 0xd402: {
-        const char* deviceName = furi_hal_version_get_name_ptr();
-        if(deviceName == NULL) {
-            deviceName = "Flipper Zero";
-        }
-
-        WriteMTPString(ptr, deviceName, &length);
-        ptr += length;
-        break;
-    }
-    case 0x5001: {
-        // Battery level
-        Power* power = furi_record_open(RECORD_POWER);
-        FURI_LOG_I("MTP", "Getting battery level");
-        if(power == NULL) {
-            *(uint8_t*)ptr = 0x00;
-        } else {
-            PowerInfo info;
-            power_get_info(power, &info);
-
-            FURI_LOG_I("MTP", "Battery level: %d", info.charge);
-
-            *(uint8_t*)ptr = info.charge;
-            furi_record_close(RECORD_POWER);
-        }
-        ptr += sizeof(uint8_t);
-        break;
-    }
-    default:
-        // Unsupported property
-        break;
-    }
-
-    return ptr - buffer;
-}
-
-int GetDevicePropDesc(uint32_t prop_code, uint8_t* buffer) {
-    uint8_t* ptr = buffer;
-    uint16_t length;
-
-    switch(prop_code) {
-    case 0xd402:
-        // Device friendly name
-        *(uint16_t*)ptr = prop_code;
-        ptr += 2;
-
-        // type is string
-        *(uint16_t*)ptr = 0xffff;
-        ptr += 2;
-
-        // read-only
-        *(uint8_t*)ptr = 0x00;
-        ptr += 1;
-
-        length = GetDevicePropValue(prop_code, ptr);
-        ptr += length;
-
-        length = GetDevicePropValue(prop_code, ptr);
-        ptr += length;
-
-        // no-form
-        *(uint8_t*)ptr = 0x00;
-        ptr += 1;
-        break;
-    case 0x5001:
-        // Device friendly name
-        *(uint16_t*)ptr = prop_code;
-        ptr += 2;
-
-        // type is uint8
-        *(uint16_t*)ptr = 0x0002;
-        ptr += 2;
-
-        // read-only
-        *(uint8_t*)ptr = 0x00;
-        ptr += 1;
-
-        length = GetDevicePropValue(prop_code, ptr);
-        ptr += length;
-
-        length = GetDevicePropValue(prop_code, ptr);
-        ptr += length;
-
-        // no-form
-        *(uint8_t*)ptr = 0x00;
-        ptr += 1;
-        break;
-
-    default:
-        // Unsupported property
-        break;
-    }
-
-    return ptr - buffer;
-}
-
 void send_mtp_response_stream(
     AppMTP* mtp,
     uint16_t resp_type,
@@ -1155,415 +684,3 @@ int mtp_handle_class_control(AppMTP* mtp, usbd_device* dev, usbd_ctlreq* req) {
 
     return value;
 }
-int BuildDeviceInfo(uint8_t* buffer) {
-    uint8_t* ptr = buffer;
-    uint16_t length;
-
-    // Standard version
-    *(uint16_t*)ptr = 100;
-    ptr += sizeof(uint16_t);
-
-    // Vendor extension ID
-    *(uint32_t*)ptr = MTP_VENDOR_EXTENSION_ID;
-    ptr += sizeof(uint32_t);
-
-    // Vendor extension version
-    *(uint16_t*)ptr = MTP_VENDOR_EXTENSION_VERSION;
-    ptr += sizeof(uint16_t);
-
-    // Vendor extension description
-    WriteMTPString(ptr, "microsoft.com: 1.0;", &length);
-    ptr += length;
-
-    // Functional mode
-    FURI_LOG_I("MTP", "MTP Functional Mode");
-    *(uint16_t*)ptr = MTP_FUNCTIONAL_MODE;
-    ptr += sizeof(uint16_t);
-
-    // Operations supported
-    FURI_LOG_I("MTP", "Writing Operation Supported");
-    length = sizeof(supported_operations) / sizeof(uint16_t);
-    *(uint32_t*)ptr = length; // Number of supported operations
-    FURI_LOG_I("MTP", "Supported Operations: %d", length);
-    ptr += sizeof(uint32_t);
-
-    for(int i = 0; i < length; i++) {
-        FURI_LOG_I("MTP", "Operation: %04x", supported_operations[i]);
-        *(uint16_t*)ptr = supported_operations[i];
-        ptr += sizeof(uint16_t);
-    }
-
-    // Supported events (example, add as needed)
-    FURI_LOG_I("MTP", "Supported Events");
-    *(uint32_t*)ptr = 0; // Number of supported events
-    ptr += sizeof(uint32_t);
-
-    FURI_LOG_I("MTP", "Supported Device Properties");
-    length = sizeof(supported_device_properties) / sizeof(uint16_t);
-    *(uint32_t*)ptr = length; // Number of supported device properties
-    ptr += sizeof(uint32_t);
-
-    for(int i = 0; i < length; i++) {
-        FURI_LOG_I("MTP", "Supported Device Props");
-        *(uint16_t*)ptr = supported_device_properties[i];
-        ptr += sizeof(uint16_t);
-    }
-
-    // Supported capture formats (example, add as needed)
-    *(uint32_t*)ptr = 0; // Number of supported capture formats
-    ptr += sizeof(uint32_t);
-
-    // Supported playback formats (example, add as needed)
-    FURI_LOG_I("MTP", "Supported Playback Formats");
-    length = sizeof(supported_playback_formats) / sizeof(uint16_t);
-    *(uint32_t*)ptr = length; // Number of supported playback formats
-    ptr += sizeof(uint32_t);
-
-    for(int i = 0; i < length; i++) {
-        *(uint16_t*)ptr = supported_playback_formats[i];
-        ptr += sizeof(uint16_t);
-    }
-
-    // Manufacturer
-    WriteMTPString(ptr, USB_MANUFACTURER_STRING, &length);
-    ptr += length;
-
-    // Model
-    WriteMTPString(ptr, USB_DEVICE_MODEL, &length);
-    ptr += length;
-
-    // Device version
-    const Version* ver = furi_hal_version_get_firmware_version();
-    WriteMTPString(ptr, version_get_version(ver), &length);
-    ptr += length;
-
-    // Serial number
-    char* serial = malloc(sizeof(char) * furi_hal_version_uid_size() * 2 + 1);
-    const uint8_t* uid = furi_hal_version_uid();
-    for(size_t i = 0; i < furi_hal_version_uid_size(); i++) {
-        serial[i * 2] = byte_to_hex(uid[i] >> 4);
-        serial[i * 2 + 1] = byte_to_hex(uid[i] & 0x0F);
-    }
-    serial[furi_hal_version_uid_size() * 2] = '\0';
-
-    WriteMTPString(ptr, serial, &length);
-    ptr += length;
-
-    free(serial);
-
-    return ptr - buffer;
-}
-
-void GetStorageIDs(AppMTP* mtp, uint32_t* storage_ids, uint32_t* count) {
-    SDInfo sd_info;
-    FS_Error err = storage_sd_info(mtp->storage, &sd_info);
-
-    // Due to filesystem change, we only have external storage.
-    // storage_ids[0] = INTERNAL_STORAGE_ID;
-    // // Check if SD card is present
-    // if(err != FSE_OK) {
-    //     FURI_LOG_E("MTP", "SD Card not found");
-    //     *count = 1; // We have only one storage
-    //     return;
-    // }
-
-    // Check if SD card is present
-    if(err != FSE_OK) {
-        FURI_LOG_E("MTP", "SD Card not found");
-        *count = 0; // No storage.
-        return;
-    }
-
-    storage_ids[0] = EXTERNAL_STORAGE_ID;
-    *count = 1;
-}
-
-int GetStorageInfo(AppMTP* mtp, uint32_t storage_id, uint8_t* buf) {
-    MTPStorageInfoHeader* info = (MTPStorageInfoHeader*)buf;
-    uint8_t* ptr = buf + sizeof(MTPStorageInfoHeader);
-    uint16_t length;
-
-    info->free_space_in_objects = 20ul;
-    info->filesystem_type = 0x0002; // Generic hierarchical
-    info->access_capability = 0x0000; // Read-write
-    FURI_LOG_I("MTP", "Getting storage info for storage ID %04lx", storage_id);
-
-    if(storage_id == INTERNAL_STORAGE_ID) {
-        // Fill in details for internal storage
-        info->storage_type = 0x0003; // Fixed RAM
-
-        // Fill in details for internal storage
-        uint64_t total_space;
-        uint64_t free_space;
-        FS_Error err = storage_common_fs_info(
-            mtp->storage, STORAGE_INT_PATH_PREFIX, &total_space, &free_space);
-        if(err != FSE_OK) {
-            info->max_capacity = 0;
-            info->free_space_in_bytes = 0;
-        } else {
-            info->max_capacity = total_space / BLOCK_SIZE;
-            info->free_space_in_bytes = free_space / BLOCK_SIZE;
-        }
-
-        WriteMTPString(ptr, "Internal Storage", &length);
-        ptr += length;
-
-        WriteMTPString(ptr, "INT_STORAGE", &length);
-        ptr += length;
-    } else if(storage_id == EXTERNAL_STORAGE_ID) {
-        SDInfo sd_info;
-        FS_Error err = storage_sd_info(mtp->storage, &sd_info);
-
-        // Fill in details for internal storage
-        info->storage_type = 0x0004; // Removable RAM
-
-        if(err != FSE_OK) {
-            info->max_capacity = 0;
-            info->free_space_in_bytes = 0;
-
-            FURI_LOG_E("MTP", "SD Card not found");
-        } else {
-            // Fill in details for external storage
-            info->max_capacity = (uint64_t)sd_info.kb_total * 1024 / BLOCK_SIZE;
-            info->free_space_in_bytes = (uint64_t)sd_info.kb_free * 1024 / BLOCK_SIZE;
-        }
-
-        WriteMTPString(ptr, "SD Card", &length);
-        ptr += length;
-
-        WriteMTPString(ptr, "SD_CARD", &length);
-        ptr += length;
-    }
-
-    // try to convert into big endian???
-    //info->max_capacity = __builtin_bswap64(info->max_capacity);
-    //info->free_space_in_bytes = __builtin_bswap64(info->free_space_in_bytes);
-
-    return ptr - buf;
-}
-
-// Microsoft-style UTF-16LE string:
-void WriteMTPString(uint8_t* buffer, const char* str, uint16_t* length) {
-    uint8_t* ptr = buffer;
-    uint8_t str_len = strlen(str);
-
-    FURI_LOG_I("MTP", "Writing MTP string: %s", str);
-    FURI_LOG_I("MTP", "String length: %d", str_len);
-
-    // extra handling for empty string
-    if(str_len == 0) {
-        *ptr = 0x00;
-
-        // that's it!
-        *length = 1;
-        return;
-    }
-
-    *ptr = str_len + 1; // Length byte (number of characters including the null terminator)
-    ptr++;
-    while(*str) {
-        *ptr++ = *str++;
-        *ptr++ = 0x00; // UTF-16LE encoding (add null byte for each character)
-    }
-    *ptr++ = 0x00; // Null terminator (UTF-16LE)
-    *ptr++ = 0x00;
-
-    FURI_LOG_I("MTP", "String byte length: %d", ptr - buffer);
-    *length = ptr - buffer;
-}
-
-void WriteMTPBEString(uint8_t* buffer, const char* str, uint16_t* length) {
-    uint8_t* ptr = buffer;
-    uint8_t str_len = strlen(str);
-    *ptr++ = str_len + 1; // Length byte (number of characters including the null terminator)
-    while(*str) {
-        *ptr++ = 0x00; // UTF-16BE encoding (add null byte for each character)
-        *ptr++ = *str++;
-    }
-    *ptr++ = 0x00; // Null terminator (UTF-16LE)
-    *ptr++ = 0x00;
-    *length = ptr - buffer;
-}
-
-bool DeleteObject(AppMTP* mtp, uint32_t handle) {
-    UNUSED(mtp);
-    FURI_LOG_I("MTP", "Deleting object %ld", handle);
-
-    char* path = get_path_from_handle(mtp, handle);
-    if(path == NULL) {
-        return false;
-    }
-
-    FileInfo fileinfo;
-    FS_Error err = storage_common_stat(mtp->storage, path, &fileinfo);
-
-    if(err == FSE_OK) {
-        if(file_info_is_dir(&fileinfo)) {
-            if(!storage_simply_remove_recursive(mtp->storage, path)) {
-                return false;
-            }
-        } else {
-            if(storage_common_remove(mtp->storage, path) != FSE_OK) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    return false;
-}
-
-void MoveObject(
-    AppMTP* mtp,
-    uint32_t transaction_id,
-    uint32_t handle,
-    uint32_t storage_id,
-    uint32_t parent) {
-    UNUSED(storage_id);
-
-    FURI_LOG_I("MTP", "Moving object %ld to storage %ld, parent %ld", handle, storage_id, parent);
-    char* path = get_path_from_handle(mtp, handle);
-    if(path == NULL) {
-        send_mtp_response(
-            mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
-        return;
-    }
-
-    char* parentPath;
-
-    if(parent != 0) {
-        parentPath = get_path_from_handle(mtp, parent);
-        if(parentPath == NULL) {
-            send_mtp_response(
-                mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
-            return;
-        }
-    } else {
-        parentPath = get_base_path_from_storage_id(storage_id);
-    }
-
-    Storage* storage = mtp->storage;
-
-    char* filename = strrchr(path, '/');
-    // remove the beginning slash
-    char* realFilename = filename + 1;
-
-    char* newPath = malloc(sizeof(char) * 256);
-    merge_path(newPath, parentPath, realFilename);
-
-    FURI_LOG_I("MTP", "Moving object: %s to %s", path, newPath);
-
-    if(storage_common_rename(storage, path, newPath) != FSE_OK) {
-        FURI_LOG_E("MTP", "Failed to move object: %s", path);
-        send_mtp_response(
-            mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
-        return;
-    }
-
-    FURI_LOG_I("MTP", "Object moved successfully");
-    send_mtp_response(mtp, MTP_TYPE_RESPONSE, MTP_RESP_OK, transaction_id, NULL);
-
-    update_object_handle_path(mtp, handle, newPath);
-    free(path);
-
-    return;
-}
-
-int GetObjectPropValueInternal(AppMTP* mtp, const char* path, uint32_t prop_code, uint8_t* buffer) {
-    UNUSED(mtp);
-    uint8_t* ptr = buffer;
-    uint16_t length;
-
-    switch(prop_code) {
-    case MTP_PROP_STORAGE_ID: {
-        FURI_LOG_I("MTP", "Getting storage ID for object: %s", path);
-
-        *(uint32_t*)ptr = prop_code;
-        ptr += sizeof(uint32_t);
-
-        *(uint32_t*)ptr = 0x0006;
-        ptr += sizeof(uint32_t);
-
-        *(uint8_t*)ptr = 0x00;
-        ptr += sizeof(uint8_t);
-
-        if(memcmp(path, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) {
-            *(uint32_t*)ptr = INTERNAL_STORAGE_ID;
-        } else if(memcmp(path, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) {
-            *(uint32_t*)ptr = EXTERNAL_STORAGE_ID;
-        } else {
-            *(uint32_t*)ptr = 0;
-        }
-        ptr += sizeof(uint32_t);
-
-        *(uint8_t*)ptr = 0x00;
-        break;
-    }
-    case MTP_PROP_OBJECT_FORMAT: {
-        FURI_LOG_I("MTP", "Getting object format for object: %s", path);
-
-        *(uint32_t*)ptr = prop_code;
-        ptr += sizeof(uint32_t);
-
-        *(uint32_t*)ptr = 0x0006;
-        ptr += sizeof(uint32_t);
-
-        *(uint8_t*)ptr = 0x00;
-        ptr += sizeof(uint8_t);
-
-        *(uint16_t*)ptr = MTP_FORMAT_UNDEFINED;
-        ptr += sizeof(uint16_t);
-
-        *(uint8_t*)ptr = 0x00;
-        break;
-    }
-    case MTP_PROP_OBJECT_FILE_NAME: {
-        FURI_LOG_I("MTP", "Getting object file name for object: %s", path);
-
-        *(uint32_t*)ptr = prop_code;
-        ptr += sizeof(uint32_t);
-
-        *(uint32_t*)ptr = 0xffff;
-        ptr += sizeof(uint32_t);
-
-        *(uint8_t*)ptr = 0x01;
-        ptr += sizeof(uint8_t);
-
-        char* file_name = strrchr(path, '/');
-        WriteMTPString(ptr, file_name + 1, &length);
-        ptr += length;
-
-        *(uint8_t*)ptr = 0x00;
-        break;
-    }
-    }
-
-    return ptr - buffer;
-}
-
-void GetObjectPropValue(AppMTP* mtp, uint32_t transaction_id, uint32_t handle, uint32_t prop_code) {
-    FURI_LOG_I(
-        "MTP", "Getting object property value for handle %ld, prop code %04lx", handle, prop_code);
-
-    char* path = get_path_from_handle(mtp, handle);
-    if(path == NULL) {
-        send_mtp_response(
-            mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
-        return;
-    }
-
-    uint8_t* buffer = malloc(sizeof(uint8_t) * 256);
-    int length = GetObjectPropValueInternal(mtp, path, prop_code, buffer);
-    if(length < 0) {
-        send_mtp_response(
-            mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
-        free(buffer);
-        return;
-    }
-
-    send_mtp_response_buffer(
-        mtp, MTP_TYPE_DATA, MTP_OP_GET_OBJECT_PROP_VALUE, transaction_id, buffer, length);
-    send_mtp_response(mtp, MTP_TYPE_RESPONSE, MTP_RESP_OK, transaction_id, NULL);
-    free(buffer);
-}

+ 18 - 31
mtp/src/scenes/mtp/mtp.h

@@ -1,5 +1,10 @@
 #pragma once
 
+#include "main.h"
+
+// Block size for storage operations
+#define BLOCK_SIZE 65536
+
 // MTP Device Serial
 #define MTP_DEVICE_FALLBACK_SERIAL \
     "HakureiReimu" // If you found this, Thank you for checking my (shitty) code!
@@ -84,6 +89,14 @@
 #define MTP_FORMAT_UNDEFINED   0x3000
 #define MTP_FORMAT_ASSOCIATION 0x3001
 
+// Buffer size configuration
+#define MTP_BUFFER_SIZE 1024 // General purpose buffer size
+#define MTP_PATH_SIZE   256 // Maximum path length
+#define MTP_NAME_SIZE   256 // Maximum filename length
+
+// File transfer configuration
+#define MTP_FILE_SYNC_INTERVAL (1024 * 1024) // Sync interval in bytes (1MB)
+
 typedef struct {
     uint32_t handle;
     char name[256];
@@ -149,15 +162,8 @@ struct MTPContainer {
     uint32_t params[5]; // 12
 };
 
-extern uint16_t supported_operations[];
-extern uint16_t supported_device_properties[];
-extern uint16_t supported_playback_formats[];
-
 void OpenSession(AppMTP* mtp, uint32_t session_id);
 void CloseSession(AppMTP* mtp);
-void GetStorageIDs(AppMTP* mtp, uint32_t* storage_ids, uint32_t* count);
-int GetStorageInfo(AppMTP* mtp, uint32_t storage_id, uint8_t* buf);
-bool DeleteObject(AppMTP* mtp, uint32_t handle);
 
 void mtp_handle_bulk(AppMTP* mtp, uint8_t* buffer, uint32_t length);
 void handle_mtp_command(AppMTP* mtp, struct MTPContainer* container);
@@ -185,38 +191,19 @@ void send_mtp_response_stream(
     void* callback_context,
     int (*callback)(void* ctx, uint8_t* buffer, int length),
     uint32_t length);
-int BuildDeviceInfo(uint8_t* buffer);
 
-bool CheckMTPStringHasUnicode(uint8_t* buffer);
-char* ReadMTPString(uint8_t* buffer);
-void WriteMTPString(uint8_t* buffer, const char* str, uint16_t* length);
 void send_device_info(AppMTP* mtp, uint32_t transaction_id);
 void send_storage_ids(AppMTP* mtp, uint32_t transaction_id);
 
-void send_device_prop_value(AppMTP* mtp, uint32_t transaction_id, uint32_t prop_code);
-void send_device_prop_desc(AppMTP* mtp, uint32_t transaction_id, uint32_t prop_code);
-int GetDevicePropValue(uint32_t prop_code, uint8_t* buffer);
-int GetDevicePropDesc(uint32_t prop_code, uint8_t* buffer);
-int GetObjectHandles(AppMTP* mtp, uint32_t storage_id, uint32_t association, uint8_t* buffer);
-int GetObjectInfo(AppMTP* mtp, uint32_t handle, uint8_t* buffer);
-void GetObject(AppMTP* mtp, uint32_t transaction_id, uint32_t handle);
-char* get_base_path_from_storage_id(uint32_t storage_id);
-char* get_path_from_handle(AppMTP* mtp, uint32_t handle);
-uint32_t issue_object_handle(AppMTP* mtp, char* path);
 void handle_mtp_data_complete(AppMTP* mtp);
-uint32_t update_object_handle_path(AppMTP* mtp, uint32_t handle, char* path);
-
-void MoveObject(
-    AppMTP* mtp,
-    uint32_t transaction_id,
-    uint32_t handle,
-    uint32_t storage_id,
-    uint32_t parent);
-
-void GetObjectPropValue(AppMTP* mtp, uint32_t transaction_id, uint32_t handle, uint32_t prop_code);
 
 struct MTPResponseBufferContext {
     uint8_t* buffer;
     uint32_t size;
     uint32_t sent;
 };
+
+// Object context for file operations
+struct GetObjectContext {
+    File* file;
+};

+ 397 - 0
mtp/src/scenes/mtp/mtp_ops.c

@@ -0,0 +1,397 @@
+#include "mtp_ops.h"
+#include "utils.h"
+
+void GetStorageIDs(AppMTP* mtp, uint32_t* storage_ids, uint32_t* count) {
+    SDInfo sd_info;
+    FS_Error err = storage_sd_info(mtp->storage, &sd_info);
+
+    // Check if SD card is present
+    if(err != FSE_OK) {
+        FURI_LOG_E("MTP", "SD Card not found");
+        *count = 0; // No storage.
+        return;
+    }
+
+    storage_ids[0] = EXTERNAL_STORAGE_ID;
+    *count = 1;
+}
+
+int GetStorageInfo(AppMTP* mtp, uint32_t storage_id, uint8_t* buf) {
+    MTPStorageInfoHeader* info = (MTPStorageInfoHeader*)buf;
+    uint8_t* ptr = buf + sizeof(MTPStorageInfoHeader);
+    uint16_t length;
+
+    info->free_space_in_objects = 20ul;
+    info->filesystem_type = 0x0002; // Generic hierarchical
+    info->access_capability = 0x0000; // Read-write
+    FURI_LOG_I("MTP", "Getting storage info for storage ID %04lx", storage_id);
+
+    if(storage_id == INTERNAL_STORAGE_ID) {
+        // Fill in details for internal storage
+        info->storage_type = 0x0003; // Fixed RAM
+
+        // Fill in details for internal storage
+        uint64_t total_space;
+        uint64_t free_space;
+        FS_Error err = storage_common_fs_info(
+            mtp->storage, STORAGE_INT_PATH_PREFIX, &total_space, &free_space);
+        if(err != FSE_OK) {
+            info->max_capacity = 0;
+            info->free_space_in_bytes = 0;
+        } else {
+            info->max_capacity = total_space / BLOCK_SIZE;
+            info->free_space_in_bytes = free_space / BLOCK_SIZE;
+        }
+
+        WriteMTPString(ptr, "Internal Storage", &length);
+        ptr += length;
+
+        WriteMTPString(ptr, "INT_STORAGE", &length);
+        ptr += length;
+    } else if(storage_id == EXTERNAL_STORAGE_ID) {
+        SDInfo sd_info;
+        FS_Error err = storage_sd_info(mtp->storage, &sd_info);
+
+        // Fill in details for internal storage
+        info->storage_type = 0x0004; // Removable RAM
+
+        if(err != FSE_OK) {
+            info->max_capacity = 0;
+            info->free_space_in_bytes = 0;
+
+            FURI_LOG_E("MTP", "SD Card not found");
+        } else {
+            // Fill in details for external storage
+            info->max_capacity = (uint64_t)sd_info.kb_total * 1024 / BLOCK_SIZE;
+            info->free_space_in_bytes = (uint64_t)sd_info.kb_free * 1024 / BLOCK_SIZE;
+        }
+
+        WriteMTPString(ptr, "SD Card", &length);
+        ptr += length;
+
+        WriteMTPString(ptr, "SD_CARD", &length);
+        ptr += length;
+    }
+
+    return ptr - buf;
+}
+
+int GetObjectInfo(AppMTP* mtp, uint32_t handle, uint8_t* buffer) {
+    ObjectInfoHeader* header = (ObjectInfoHeader*)buffer;
+    uint8_t* ptr = buffer + sizeof(ObjectInfoHeader);
+
+    char* path = get_path_from_handle(mtp, handle);
+    if(path == NULL) {
+        return -1;
+    }
+
+    FURI_LOG_I("MTP", "Getting object info for handle %ld", handle);
+    FURI_LOG_I("MTP", "Path: %s", path);
+
+    header->protection_status = 0;
+
+    Storage* storage = mtp->storage;
+    File* file = storage_file_alloc(storage);
+    uint16_t length;
+
+    FileInfo fileinfo;
+    FS_Error err = storage_common_stat(storage, path, &fileinfo);
+    if(err != FSE_OK) {
+        FURI_LOG_E("MTP", "Failed to get file info: %s", filesystem_api_error_get_desc(err));
+        return -1;
+    }
+
+    if(memcmp(path, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) {
+        FURI_LOG_I("MTP", "Object in Internal storage");
+        header->storage_id = INTERNAL_STORAGE_ID;
+    } else if(memcmp(path, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) {
+        FURI_LOG_I("MTP", "Object in External storage");
+        header->storage_id = EXTERNAL_STORAGE_ID;
+    } else {
+        return -1;
+    }
+
+    if(file_info_is_dir(&fileinfo)) {
+        FURI_LOG_I("MTP", "Directory");
+
+        header->format = MTP_FORMAT_ASSOCIATION;
+        header->association_type = 0x0001; // Generic folder
+    } else {
+        FURI_LOG_I("MTP", "Undefined type");
+
+        header->format = MTP_FORMAT_UNDEFINED;
+    }
+
+    header->compressed_size = fileinfo.size;
+
+    header->thumb_format = 0;
+    header->thumb_compressed_size = 0;
+    header->thumb_pix_width = 0;
+    header->thumb_pix_height = 0;
+    header->image_pix_width = 0;
+    header->image_pix_height = 0;
+    header->image_bit_depth = 0;
+
+    char* file_name = strrchr(path, '/');
+    FURI_LOG_I("MTP", "File name: %s", file_name + 1);
+
+    WriteMTPString(ptr, file_name + 1, &length);
+    ptr += length;
+
+    // get created
+    WriteMTPString(ptr, "20240608T010702", &length);
+    ptr += length;
+
+    // get last modified
+    WriteMTPString(ptr, "20240608T010702", &length);
+    ptr += length;
+
+    // get keywords
+    WriteMTPString(ptr, "", &length);
+    ptr += length;
+
+    storage_file_free(file);
+
+    return ptr - buffer;
+}
+
+int GetObject_callback(void* ctx, uint8_t* buffer, int length) {
+    struct GetObjectContext* obj_ctx = (struct GetObjectContext*)ctx;
+    return storage_file_read(obj_ctx->file, buffer, length);
+}
+
+void GetObject(AppMTP* mtp, uint32_t transaction_id, uint32_t handle) {
+    char* path = get_path_from_handle(mtp, handle);
+    if(path == NULL) {
+        send_mtp_response(
+            mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
+        return;
+    }
+
+    FURI_LOG_I("MTP", "Getting object: %s", path);
+
+    Storage* storage = mtp->storage;
+    File* file = storage_file_alloc(storage);
+    if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+        FURI_LOG_E("MTP", "Failed to open file: %s", path);
+        send_mtp_response(
+            mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
+        return;
+    }
+
+    uint32_t size = storage_file_size(file);
+
+    struct GetObjectContext ctx = {
+        .file = file,
+    };
+
+    send_mtp_response_stream(
+        mtp, MTP_TYPE_DATA, MTP_OP_GET_OBJECT, transaction_id, &ctx, GetObject_callback, size);
+
+    send_mtp_response(mtp, MTP_TYPE_RESPONSE, MTP_RESP_OK, transaction_id, NULL);
+
+    storage_file_close(file);
+    storage_file_free(file);
+}
+
+int GetObjectHandles(AppMTP* mtp, uint32_t storage_id, uint32_t association, uint8_t* buffer) {
+    uint8_t* ptr = buffer;
+    uint16_t length;
+
+    UNUSED(length);
+
+    // For now, just return a single object handle
+    int count = list_and_issue_handles(mtp, storage_id, association, NULL);
+
+    *(uint32_t*)ptr = count;
+    ptr += sizeof(uint32_t);
+
+    uint32_t* handles = (uint32_t*)ptr;
+    length = list_and_issue_handles(mtp, storage_id, association, handles);
+    ptr += length * sizeof(uint32_t);
+
+    return ptr - buffer;
+}
+
+bool DeleteObject(AppMTP* mtp, uint32_t handle) {
+    UNUSED(mtp);
+    FURI_LOG_I("MTP", "Deleting object %ld", handle);
+
+    char* path = get_path_from_handle(mtp, handle);
+    if(path == NULL) {
+        return false;
+    }
+
+    FileInfo fileinfo;
+    FS_Error err = storage_common_stat(mtp->storage, path, &fileinfo);
+
+    if(err == FSE_OK) {
+        if(file_info_is_dir(&fileinfo)) {
+            if(!storage_simply_remove_recursive(mtp->storage, path)) {
+                return false;
+            }
+        } else {
+            if(storage_common_remove(mtp->storage, path) != FSE_OK) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+void MoveObject(
+    AppMTP* mtp,
+    uint32_t transaction_id,
+    uint32_t handle,
+    uint32_t storage_id,
+    uint32_t parent) {
+    UNUSED(storage_id);
+
+    FURI_LOG_I("MTP", "Moving object %ld to storage %ld, parent %ld", handle, storage_id, parent);
+    char* path = get_path_from_handle(mtp, handle);
+    if(path == NULL) {
+        send_mtp_response(
+            mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
+        return;
+    }
+
+    char* parentPath;
+
+    if(parent != 0) {
+        parentPath = get_path_from_handle(mtp, parent);
+        if(parentPath == NULL) {
+            send_mtp_response(
+                mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
+            return;
+        }
+    } else {
+        parentPath = get_base_path_from_storage_id(storage_id);
+    }
+
+    Storage* storage = mtp->storage;
+
+    char* filename = strrchr(path, '/');
+    // remove the beginning slash
+    char* realFilename = filename + 1;
+
+    char* newPath = malloc(sizeof(char) * 256);
+    merge_path(newPath, parentPath, realFilename);
+
+    FURI_LOG_I("MTP", "Moving object: %s to %s", path, newPath);
+
+    if(storage_common_rename(storage, path, newPath) != FSE_OK) {
+        FURI_LOG_E("MTP", "Failed to move object: %s", path);
+        send_mtp_response(
+            mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
+        return;
+    }
+
+    FURI_LOG_I("MTP", "Object moved successfully");
+    send_mtp_response(mtp, MTP_TYPE_RESPONSE, MTP_RESP_OK, transaction_id, NULL);
+
+    update_object_handle_path(mtp, handle, newPath);
+    free(path);
+
+    return;
+}
+
+int GetObjectPropValueInternal(AppMTP* mtp, const char* path, uint32_t prop_code, uint8_t* buffer) {
+    UNUSED(mtp);
+    uint8_t* ptr = buffer;
+    uint16_t length;
+
+    switch(prop_code) {
+    case MTP_PROP_STORAGE_ID: {
+        FURI_LOG_I("MTP", "Getting storage ID for object: %s", path);
+
+        *(uint32_t*)ptr = prop_code;
+        ptr += sizeof(uint32_t);
+
+        *(uint32_t*)ptr = 0x0006;
+        ptr += sizeof(uint32_t);
+
+        *(uint8_t*)ptr = 0x00;
+        ptr += sizeof(uint8_t);
+
+        if(memcmp(path, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) {
+            *(uint32_t*)ptr = INTERNAL_STORAGE_ID;
+        } else if(memcmp(path, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) {
+            *(uint32_t*)ptr = EXTERNAL_STORAGE_ID;
+        } else {
+            *(uint32_t*)ptr = 0;
+        }
+        ptr += sizeof(uint32_t);
+
+        *(uint8_t*)ptr = 0x00;
+        break;
+    }
+    case MTP_PROP_OBJECT_FORMAT: {
+        FURI_LOG_I("MTP", "Getting object format for object: %s", path);
+
+        *(uint32_t*)ptr = prop_code;
+        ptr += sizeof(uint32_t);
+
+        *(uint32_t*)ptr = 0x0006;
+        ptr += sizeof(uint32_t);
+
+        *(uint8_t*)ptr = 0x00;
+        ptr += sizeof(uint8_t);
+
+        *(uint16_t*)ptr = MTP_FORMAT_UNDEFINED;
+        ptr += sizeof(uint16_t);
+
+        *(uint8_t*)ptr = 0x00;
+        break;
+    }
+    case MTP_PROP_OBJECT_FILE_NAME: {
+        FURI_LOG_I("MTP", "Getting object file name for object: %s", path);
+
+        *(uint32_t*)ptr = prop_code;
+        ptr += sizeof(uint32_t);
+
+        *(uint32_t*)ptr = 0xffff;
+        ptr += sizeof(uint32_t);
+
+        *(uint8_t*)ptr = 0x01;
+        ptr += sizeof(uint8_t);
+
+        char* file_name = strrchr(path, '/');
+        WriteMTPString(ptr, file_name + 1, &length);
+        ptr += length;
+
+        *(uint8_t*)ptr = 0x00;
+        break;
+    }
+    }
+
+    return ptr - buffer;
+}
+
+void GetObjectPropValue(AppMTP* mtp, uint32_t transaction_id, uint32_t handle, uint32_t prop_code) {
+    FURI_LOG_I(
+        "MTP", "Getting object property value for handle %ld, prop code %04lx", handle, prop_code);
+
+    char* path = get_path_from_handle(mtp, handle);
+    if(path == NULL) {
+        send_mtp_response(
+            mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
+        return;
+    }
+
+    uint8_t* buffer = malloc(sizeof(uint8_t) * 256);
+    int length = GetObjectPropValueInternal(mtp, path, prop_code, buffer);
+    if(length < 0) {
+        send_mtp_response(
+            mtp, MTP_TYPE_RESPONSE, MTP_RESP_INVALID_OBJECT_HANDLE, transaction_id, NULL);
+        free(buffer);
+        return;
+    }
+
+    send_mtp_response_buffer(
+        mtp, MTP_TYPE_DATA, MTP_OP_GET_OBJECT_PROP_VALUE, transaction_id, buffer, length);
+    send_mtp_response(mtp, MTP_TYPE_RESPONSE, MTP_RESP_OK, transaction_id, NULL);
+    free(buffer);
+}

+ 22 - 0
mtp/src/scenes/mtp/mtp_ops.h

@@ -0,0 +1,22 @@
+#pragma once
+
+#include <furi.h>
+#include <storage/storage.h>
+#include "main.h"
+#include "mtp.h"
+#include "storage_ops.h"
+
+// MTP Operations (Actions)
+void GetStorageIDs(AppMTP* mtp, uint32_t* storage_ids, uint32_t* count);
+int GetStorageInfo(AppMTP* mtp, uint32_t storage_id, uint8_t* buf);
+int GetObjectInfo(AppMTP* mtp, uint32_t handle, uint8_t* buffer);
+void GetObject(AppMTP* mtp, uint32_t transaction_id, uint32_t handle);
+int GetObjectHandles(AppMTP* mtp, uint32_t storage_id, uint32_t association, uint8_t* buffer);
+void GetObjectPropValue(AppMTP* mtp, uint32_t transaction_id, uint32_t handle, uint32_t prop_code);
+bool DeleteObject(AppMTP* mtp, uint32_t handle);
+void MoveObject(
+    AppMTP* mtp,
+    uint32_t transaction_id,
+    uint32_t handle,
+    uint32_t storage_id,
+    uint32_t parent);

+ 31 - 0
mtp/src/scenes/mtp/mtp_support.h

@@ -0,0 +1,31 @@
+#pragma once
+
+static const uint16_t supported_operations[] = {
+    MTP_OP_GET_DEVICE_INFO,
+    MTP_OP_OPEN_SESSION,
+    MTP_OP_CLOSE_SESSION,
+    MTP_OP_GET_STORAGE_IDS,
+    MTP_OP_GET_STORAGE_INFO,
+    MTP_OP_GET_NUM_OBJECTS,
+    MTP_OP_GET_OBJECT_HANDLES,
+    MTP_OP_GET_OBJECT_INFO,
+    MTP_OP_GET_OBJECT,
+    MTP_OP_DELETE_OBJECT,
+    MTP_OP_SEND_OBJECT_INFO,
+    MTP_OP_SEND_OBJECT,
+    MTP_OP_GET_DEVICE_PROP_DESC,
+    MTP_OP_GET_DEVICE_PROP_VALUE,
+    MTP_OP_MOVE_OBJECT};
+
+static const uint16_t supported_object_props[] = {
+    MTP_PROP_STORAGE_ID,
+    MTP_PROP_OBJECT_FORMAT,
+    MTP_PROP_OBJECT_FILE_NAME,
+};
+
+static const uint16_t supported_device_properties[] = {
+    0xd402, // Device Friendly Name
+    0x5001 // Battery Level
+};
+
+static const uint16_t supported_playback_formats[] = {MTP_FORMAT_UNDEFINED, MTP_FORMAT_ASSOCIATION};

+ 130 - 0
mtp/src/scenes/mtp/storage_ops.c

@@ -0,0 +1,130 @@
+#include "storage_ops.h"
+#include "utils.h"
+
+char* get_base_path_from_storage_id(uint32_t storage_id) {
+    if(storage_id == INTERNAL_STORAGE_ID) {
+        return STORAGE_INT_PATH_PREFIX;
+    } else if(storage_id == EXTERNAL_STORAGE_ID) {
+        return STORAGE_EXT_PATH_PREFIX;
+    }
+    return NULL;
+}
+
+char* get_path_from_handle(AppMTP* mtp, uint32_t handle) {
+    FileHandle* current = mtp->handles;
+    while(current != NULL) {
+        if(current->handle == handle) {
+            return current->path;
+        }
+        current = current->next;
+    }
+    return NULL;
+}
+
+uint32_t issue_object_handle(AppMTP* mtp, char* path) {
+    int handle = 1;
+    int length = strlen(path);
+    char* path_store = malloc(sizeof(char) * (length + 1));
+    strcpy(path_store, path);
+
+    if(mtp->handles == NULL) {
+        mtp->handles = malloc(sizeof(FileHandle));
+        mtp->handles->handle = handle;
+        mtp->handles->path = path_store;
+        mtp->handles->next = NULL;
+        return handle;
+    }
+
+    FileHandle* current = mtp->handles;
+    if(strcmp(current->path, path) == 0) {
+        return current->handle;
+    }
+
+    while(current->next != NULL) {
+        if(strcmp(current->path, path) == 0) {
+            return current->handle;
+        }
+        current = current->next;
+        handle++;
+    }
+
+    current->next = malloc(sizeof(FileHandle));
+    current = current->next;
+    handle++;
+
+    current->handle = handle;
+    current->path = path_store;
+    current->next = NULL;
+    return handle;
+}
+
+uint32_t update_object_handle_path(AppMTP* mtp, uint32_t handle, char* path) {
+    FileHandle* current = mtp->handles;
+    while(current != NULL) {
+        if(current->handle == handle) {
+            free(current->path);
+            current->path = path;
+            return handle;
+        }
+        current = current->next;
+    }
+    return 0;
+}
+
+int list_and_issue_handles(
+    AppMTP* mtp,
+    uint32_t storage_id,
+    uint32_t association,
+    uint32_t* handles) {
+    Storage* storage = mtp->storage;
+    char* base_path = get_base_path_from_storage_id(storage_id);
+    if(base_path == NULL) {
+        base_path = "";
+    }
+
+    File* dir = storage_file_alloc(storage);
+    if(association == 0xffffffff) {
+        // count the objects in the root directory
+        storage_dir_open(dir, base_path);
+    } else {
+        char* path = get_path_from_handle(mtp, association);
+        FURI_LOG_I("MTP", "Association path: %s", path);
+        if(path == NULL) {
+            return 0;
+        }
+        storage_dir_open(dir, path);
+        base_path = path;
+    }
+
+    int count = 0;
+    FileInfo fileinfo;
+    char* file_name = malloc(sizeof(char) * 256);
+    char* full_path = malloc(sizeof(char) * 256);
+    while(storage_dir_read(dir, &fileinfo, file_name, 256)) {
+        if(file_info_is_dir(&fileinfo)) {
+            FURI_LOG_I("MTP", "Found directory: %s", file_name);
+        } else {
+            FURI_LOG_I("MTP", "Found file: %s", file_name);
+        }
+
+        merge_path(full_path, base_path, file_name);
+        FURI_LOG_I("MTP", "Full path: %s", full_path);
+
+        uint32_t handle = issue_object_handle(mtp, full_path);
+        if(handles != NULL) {
+            handles[count] = handle;
+        }
+        count++;
+    }
+
+    FURI_LOG_I("MTP", "Getting number of objects in storage %ld", storage_id);
+    FURI_LOG_I("MTP", "Base path: %s", base_path);
+    FURI_LOG_I("MTP", "Association: %ld", association);
+    FURI_LOG_I("MTP", "Number of objects: %d", count);
+
+    storage_dir_close(dir);
+    storage_file_free(dir);
+    free(file_name);
+    free(full_path);
+    return count;
+}

+ 16 - 0
mtp/src/scenes/mtp/storage_ops.h

@@ -0,0 +1,16 @@
+#pragma once
+
+#include <storage/storage.h>
+#include "main.h"
+#include "mtp.h"
+
+// Storage utility functions
+char* get_base_path_from_storage_id(uint32_t storage_id);
+char* get_path_from_handle(AppMTP* mtp, uint32_t handle);
+uint32_t issue_object_handle(AppMTP* mtp, char* path);
+uint32_t update_object_handle_path(AppMTP* mtp, uint32_t handle, char* path);
+int list_and_issue_handles(
+    AppMTP* mtp,
+    uint32_t storage_id,
+    uint32_t association,
+    uint32_t* handles);

+ 71 - 0
mtp/src/scenes/mtp/utils.c

@@ -40,3 +40,74 @@ void print_bytes(char* tag, uint8_t* bytes, size_t len) {
     free(line);
     FURI_LOG_I("MTP", "End of dump - TAG: %s", tag);
 }
+
+void merge_path(char* target, char* base, char* name) {
+    char* ptr = target;
+    strcpy(target, base);
+    ptr += strlen(base);
+    strcpy(ptr, "/");
+    ptr++;
+    strcpy(ptr, name);
+}
+
+bool CheckMTPStringHasUnicode(uint8_t* buffer) {
+    uint8_t* base = buffer;
+    int len = *base;
+
+    uint16_t* ptr = (uint16_t*)(base + 1);
+
+    for(int i = 0; i < len; i++) {
+        if(ptr[i] > 0x7F) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+char* ReadMTPString(uint8_t* buffer) {
+    int len16 = *(uint8_t*)buffer;
+    if(len16 == 0) {
+        return "";
+    }
+
+    char* str = malloc(sizeof(char) * len16);
+
+    uint8_t* base = buffer + 1;
+    uint16_t* ptr = (uint16_t*)base;
+
+    for(int i = 0; i < len16; i++) {
+        str[i] = *ptr++;
+    }
+
+    return str;
+}
+
+void WriteMTPString(uint8_t* buffer, const char* str, uint16_t* length) {
+    uint8_t* ptr = buffer;
+    uint8_t str_len = strlen(str);
+
+    FURI_LOG_I("MTP", "Writing MTP string: %s", str);
+    FURI_LOG_I("MTP", "String length: %d", str_len);
+
+    // extra handling for empty string
+    if(str_len == 0) {
+        *ptr = 0x00;
+
+        // that's it!
+        *length = 1;
+        return;
+    }
+
+    *ptr = str_len + 1; // Length byte (number of characters including the null terminator)
+    ptr++;
+    while(*str) {
+        *ptr++ = *str++;
+        *ptr++ = 0x00; // UTF-16LE encoding (add null byte for each character)
+    }
+    *ptr++ = 0x00; // Null terminator (UTF-16LE)
+    *ptr++ = 0x00;
+
+    FURI_LOG_I("MTP", "String byte length: %d", ptr - buffer);
+    *length = ptr - buffer;
+}

+ 5 - 0
mtp/src/scenes/mtp/utils.h

@@ -1,5 +1,10 @@
 #pragma once
+
 #include <furi.h>
 
 char byte_to_hex(uint8_t byte);
 void print_bytes(char* tag, uint8_t* bytes, size_t len);
+void merge_path(char* target, char* base, char* name);
+bool CheckMTPStringHasUnicode(uint8_t* buffer);
+char* ReadMTPString(uint8_t* buffer);
+void WriteMTPString(uint8_t* buffer, const char* str, uint16_t* length);