Skip to content

Commit

Permalink
docs: enjoy
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex4386 committed Jun 15, 2024
1 parent 2b9504a commit e8e0ea0
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 15 deletions.
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ Due to limitation of the Flipper Zero's hardware, The MTP connection utilizes `F
> So, the speed of the MTP connection probably will not the bottleneck here.
> <b>So, If you need quick copy, Use a proper SD Card Reader.</b>
### Before using...
Here are some things you should know before using this application:

> [!WARNING]
> **DO NOT transfer files over 64K** in one go.
> This will:
> - **Crash the Flipper** if the file is too big.
> - **Corrupt the SD Card filesystem** due to flipper zero's filesystem handler's limitation.
>
> This is applicable to both uploading and downloading files.
> [!WARNING]
> **DO NOT** use `UNICODE` characters in the file/directory names.
> Flipper Zero's filesystem isn't designed to handle `UNICODE` characters. such as:
> - `한글.txt`
> - `日本語.txt`
> - `中文.txt`
### Features
* Access Internal and SD card storages
* List files and directories
Expand All @@ -35,12 +53,15 @@ Due to limitation of the Flipper Zero's hardware, The MTP connection utilizes `F
- Large file transfer now **WORKS**!
- It didn't work since the header did not have proper `size` defined, ignoring the further packets.
- Now utilizing even less memory via `streaming` support!
* Move Files into Flipper
- **NEW!** Now you can upload files to Flipper Zero!
- **Note:** Flipper Zero has limited memory, please refrain from moving files bigger than 64K on one go. (for me 80K was a hard limit)
* Deleting Files/Directories

### Known Issues
* Creating directories, files, uploading files are not supported yet.
* Due to memory leak happening somewhere, sometimes the Flipper crashes during the file transfer.
- I'm currently busy working on "Creating" part to at least work, so this won't be fixed soon.
* Renaming directories, files are not supported yet.
* Fix "memory leaks"
- I'm currently busy working on code cleanup. So, I can't afford to do this right now.

## How to build
See [HOW_TO_BUILD.md](HOW_TO_BUILD.md) for more information.
Expand Down
139 changes: 127 additions & 12 deletions src/scenes/mtp/mtp.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "main.h"
#include "mtp.h"
#include "usb_desc.h"
#include "utils.h"

#define BLOCK_SIZE 65536

Expand All @@ -21,7 +22,15 @@ uint16_t supported_operations[] = {
MTP_OP_SEND_OBJECT,
MTP_OP_DELETE_OBJECT,
MTP_OP_GET_DEVICE_PROP_DESC,
MTP_OP_GET_DEVICE_PROP_VALUE};
MTP_OP_GET_DEVICE_PROP_VALUE,
MTP_OP_GET_OBJECT_PROPS_SUPPORTED,
MTP_OP_SET_OBJECT_PROP_VALUE};

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[] = {
Expand Down Expand Up @@ -108,11 +117,61 @@ void handle_mtp_data_packet(AppMTP* mtp, uint8_t* buffer, int32_t length, bool c
ptr = (uint8_t*)buffer + sizeof(struct MTPHeader);

switch(header->op) {
case MTP_OP_SEND_OBJECT_INFO: {
case MTP_OP_SEND_OBJECT_INFO:
case MTP_OP_SET_OBJECT_PROP_VALUE: {
persistence.global_buffer = malloc(sizeof(uint8_t) * 256);
persistence.buffer_offset = 0;
break;
}
case MTP_OP_SEND_OBJECT: {
persistence.buffer_offset = 0;
persistence.left_bytes = header->len - sizeof(struct MTPHeader);
}
}
}

// for speicific operations.
if(persistence.op == MTP_OP_SEND_OBJECT) {
uint32_t handle = persistence.prev_handle;
if(handle == 0) {
FURI_LOG_E("MTP", "Invalid handle for MTP_OP_SEND_OBJECT");
send_mtp_response(
mtp, 3, MTP_RESP_INVALID_OBJECT_HANDLE, persistence.transaction_id, NULL);
return;
}

char* path = get_path_from_handle(mtp, handle);
int bytes_length = length - (ptr - buffer);
int offset = persistence.buffer_offset;

FURI_LOG_I("MTP", "Writing to file: %s with offset %02x", path, offset);
if(persistence.current_file == NULL) {
persistence.current_file = storage_file_alloc(mtp->storage);
if(!storage_file_open(persistence.current_file, path, FSAM_WRITE, FSOM_OPEN_EXISTING)) {
FURI_LOG_E("MTP", "Failed to open file: %s", path);
send_mtp_response(
mtp, 3, MTP_RESP_INVALID_OBJECT_HANDLE, persistence.transaction_id, NULL);
storage_file_free(persistence.current_file);
persistence.current_file = NULL;
return;
}
}

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);

persistence.buffer_offset += bytes_length;
persistence.left_bytes -= bytes_length;

if(persistence.left_bytes <= 0) {
send_mtp_response(mtp, 3, MTP_RESP_OK, persistence.transaction_id, NULL);
storage_file_close(file);
storage_file_free(file);

persistence.current_file = NULL;
}
}

Expand All @@ -134,16 +193,29 @@ void handle_mtp_data_packet(AppMTP* mtp, uint8_t* buffer, int32_t length, bool c
}

void handle_mtp_data_complete(AppMTP* mtp) {
UNUSED(mtp);
// dump the buffer
print_bytes("MTP Data", persistence.global_buffer, persistence.buffer_offset);

switch(persistence.op) {
case MTP_OP_SEND_OBJECT_INFO: {
struct ObjectInfoHeader* info =
(struct ObjectInfoHeader*)(persistence.global_buffer + sizeof(struct MTPHeader));
// parse the object info - persistence.global_buffer doesn't contain MTPHeader
struct ObjectInfoHeader* info = (struct ObjectInfoHeader*)(persistence.global_buffer);

uint8_t* ptr = persistence.global_buffer + sizeof(struct ObjectInfoHeader);
ptr += 12;

uint8_t* ptr =
persistence.global_buffer + sizeof(struct MTPHeader) + sizeof(struct ObjectInfoHeader);
ptr += 4;
char* name = ReadMTPString(ptr);
// dump the ptr
print_bytes(
"MTP FileName", ptr, persistence.buffer_offset - sizeof(struct ObjectInfoHeader));

bool is_dir = info->format == MTP_FORMAT_ASSOCIATION;
char* name = NULL;
if(CheckMTPStringHasUnicode(ptr)) {
// unicode isn't supported
name = is_dir ? "New Folder" : "New File";
} else {
name = ReadMTPString(ptr);
}

// if the name is blank, generate random name
if(*name == 0) {
Expand Down Expand Up @@ -184,7 +256,6 @@ void handle_mtp_data_complete(AppMTP* mtp) {

FURI_LOG_I("MTP", "Format: %04x", info->format);

bool is_dir = info->format == MTP_FORMAT_ASSOCIATION;
if(is_dir) {
if(!storage_dir_exists(mtp->storage, full_path)) {
if(!storage_simply_mkdir(mtp->storage, full_path)) {
Expand All @@ -208,7 +279,10 @@ void handle_mtp_data_complete(AppMTP* mtp) {
break;
}

storage_file_close(file);
storage_file_free(file);

persistence.prev_handle = issue_object_handle(mtp, full_path);
}
}

Expand Down Expand Up @@ -329,6 +403,26 @@ void handle_mtp_command(AppMTP* mtp, struct MTPContainer* container) {
mtp, MTP_TYPE_RESPONSE, MTP_RESP_OK, container->header.transaction_id, NULL, 0);
break;
}
case MTP_OP_GET_OBJECT_PROPS_SUPPORTED: {
FURI_LOG_I("MTP", "GetObjectPropsSupported operation");
uint8_t* buffer = malloc(sizeof(uint8_t) * 256);
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);

int length = sizeof(uint32_t) + sizeof(uint16_t) * count;
send_mtp_response_buffer(
mtp,
MTP_TYPE_DATA,
MTP_OP_GET_OBJECT_PROPS_SUPPORTED,
container->header.transaction_id,
buffer,
length);
free(buffer);
send_mtp_response_buffer(
mtp, MTP_TYPE_RESPONSE, MTP_RESP_OK, container->header.transaction_id, NULL, 0);
break;
}
case MTP_OP_DELETE_OBJECT:
FURI_LOG_I("MTP", "DeleteObject operation");
if(DeleteObject(mtp, container->params[0])) {
Expand All @@ -354,6 +448,10 @@ void handle_mtp_command(AppMTP* mtp, struct MTPContainer* container) {
FURI_LOG_I("MTP", "SendObjectInfo operation");
setup_persistence(container);
break;
case MTP_OP_SEND_OBJECT:
FURI_LOG_I("MTP", "SendObject operation");
setup_persistence(container);
break;
case MTP_OP_GET_OBJECT: {
FURI_LOG_I("MTP", "GetObject operation");
GetObject(mtp, container->header.transaction_id, container->params[0]);
Expand Down Expand Up @@ -664,6 +762,21 @@ int GetObjectInfo(AppMTP* mtp, uint32_t handle, uint8_t* buffer) {
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) {
Expand Down Expand Up @@ -1116,7 +1229,7 @@ bool DeleteObject(AppMTP* mtp, uint32_t handle) {
FileInfo fileinfo;
FS_Error err = storage_common_stat(mtp->storage, path, &fileinfo);

if(err != FSE_OK) {
if(err == FSE_OK) {
if(file_info_is_dir(&fileinfo)) {
if(!storage_simply_remove_recursive(mtp->storage, path)) {
return false;
Expand All @@ -1126,7 +1239,9 @@ bool DeleteObject(AppMTP* mtp, uint32_t handle) {
return false;
}
}

return true;
}

return true;
return false;
}
14 changes: 14 additions & 0 deletions src/scenes/mtp/mtp.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@
#define MTP_OP_GET_DEVICE_PROP_DESC 0x1014
#define MTP_OP_GET_DEVICE_PROP_VALUE 0x1015

#define MTP_OP_GET_OBJECT_PROPS_SUPPORTED 0x9801
#define MTP_OP_GET_OBJECT_PROP_DESC 0x9802
#define MTP_OP_GET_OBJECT_PROP_VALUE 0x9803
#define MTP_OP_SET_OBJECT_PROP_VALUE 0x9804

// MTP Object Props
#define MTP_PROP_STORAGE_ID 0xDC01
#define MTP_PROP_OBJECT_FORMAT 0xDC02
#define MTP_PROP_OBJECT_FILE_NAME 0xDC07

// MTP Response Codes
#define MTP_RESP_UNKNOWN 0x2000
#define MTP_RESP_OK 0x2001
Expand Down Expand Up @@ -121,6 +131,9 @@ typedef struct MTPDataPersistence {
uint16_t op;
uint32_t transaction_id;
uint32_t params[5];

uint32_t prev_handle;
File* current_file;
} MTPDataPersistence;

struct MTPContainer {
Expand Down Expand Up @@ -166,6 +179,7 @@ void send_mtp_response_stream(
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);
Expand Down
23 changes: 23 additions & 0 deletions src/scenes/mtp/utils.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include "utils.h"

const char hex[] = "0123456789ABCDEF";

void print_bytes(char* tag, uint8_t* bytes, size_t len) {
FURI_LOG_I("MTP", "Dumping bytes - TAG: %s", tag);
int lines = len / 16;
size_t last_line_len = len % 16;
char* line = (char*)malloc(16 * 3 + 1);
for(int i = 0; i < lines; i++) {
int line_len = i == lines - 1 ? last_line_len : 16;
for(int j = 0; j < line_len; j++) {
// write hex without sprintf
line[j * 3] = hex[bytes[i * 16 + j] >> 4];
line[j * 3 + 1] = hex[bytes[i * 16 + j] & 0x0F];
line[j * 3 + 2] = ' ';
}
line[16 * 3] = '\0';
FURI_LOG_I("MTP", line);
}
free(line);
FURI_LOG_I("MTP", "End of dump - TAG: %s", tag);
}
3 changes: 3 additions & 0 deletions src/scenes/mtp/utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once
#include <furi.h>
void print_bytes(char* tag, uint8_t* bytes, size_t len);

0 comments on commit e8e0ea0

Please sign in to comment.