gb_cartridge_scene_5.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. #include "../gb_cartridge_app.h"
  2. #include <furi.h>
  3. #include <furi_hal.h>
  4. #include <input/input.h>
  5. #include <gui/elements.h>
  6. #include <dolphin/dolphin.h>
  7. #include <gui/elements.h>
  8. #include <notification/notification_messages.h>
  9. #include <dialogs/dialogs.h>
  10. #include <gui/modules/dialog_ex.h>
  11. #include <toolbox/stream/file_stream.h>
  12. #include "../helpers/gb_cartridge_speaker.h"
  13. #include "../helpers/sequential_file.h"
  14. #include <stdio.h> // Para sprintf
  15. #include <string.h> // Para strlen
  16. static uint64_t last_toggle_time = 0;
  17. struct GBCartridgeScene5 {
  18. View* view;
  19. GBCartridgeScene5Callback callback;
  20. void* context;
  21. GBCartridge* app;
  22. };
  23. typedef struct {
  24. char* event_type;
  25. int progress;
  26. int total_ram;
  27. int transfered;
  28. int ramBanks;
  29. int elapsed_time;
  30. int start_time;
  31. char* cart_dump_ram_filename_sequential;
  32. bool rx_active;
  33. char* event_title;
  34. uint32_t offset;
  35. uint32_t value;
  36. File* selectedfile;
  37. } GameBoyCartridgeRAMWriteModel;
  38. static bool select_ram_file(GBCartridge* app, File* file) {
  39. bool result = false;
  40. FuriString* file_path = furi_string_alloc();
  41. furi_string_set(file_path, MALVEKE_APP_FOLDER_RAMS);
  42. DialogsFileBrowserOptions browser_options;
  43. dialog_file_browser_set_basic_options(&browser_options, "sav", NULL);
  44. browser_options.base_path = MALVEKE_APP_FOLDER_RAMS;
  45. browser_options.skip_assets = true;
  46. // Input events and views are managed by file_browser
  47. bool res = dialog_file_browser_show(app->dialogs, file_path, file_path, &browser_options);
  48. // UNUSED(res);
  49. // FURI_LOG_I(TAG, "File selected: %s", furi_string_get_cstr(file_path));
  50. if(res) {
  51. if(!storage_file_open(file, furi_string_get_cstr(file_path), FSAM_READ, FSOM_OPEN_EXISTING))
  52. // if (!file_stream_open(stream, furi_string_get_cstr(file_path), FSAM_READ, FSOM_OPEN_EXISTING))
  53. {
  54. // FURI_LOG_D(TAG, "Cannot open file \"%s\"", furi_string_get_cstr(file_path));
  55. // file_stream_close(stream);
  56. } else {
  57. // FURI_LOG_D(TAG, "Open file \"%s\"", furi_string_get_cstr(file_path));
  58. result = true;
  59. }
  60. }
  61. furi_string_free(file_path);
  62. return result;
  63. }
  64. static int32_t cartridge_writting_worker_thread(void* thread_context) {
  65. GBCartridge* app = thread_context;
  66. UNUSED(app);
  67. File* file = storage_file_alloc(app->storage);
  68. if(select_ram_file(app, file)) {
  69. uint16_t fileSize = storage_file_size(file);
  70. FURI_LOG_I(TAG, "fileSize: %d ", fileSize);
  71. with_view_model(
  72. app->gb_cartridge_scene_5->view,
  73. GameBoyCartridgeRAMWriteModel * model,
  74. {
  75. model->total_ram = fileSize;
  76. model->selectedfile = file;
  77. },
  78. true);
  79. char gbcartridge_start_command[80]; // A reasonably sized buffer.
  80. snprintf(
  81. gbcartridge_start_command,
  82. sizeof(gbcartridge_start_command),
  83. "gbcartridge -w -a %d\n",
  84. fileSize);
  85. uart_tx(app->uart, (uint8_t*)gbcartridge_start_command, strlen(gbcartridge_start_command));
  86. furi_delay_ms(500); // wait
  87. uint8_t* the_savefile = NULL;
  88. size_t savefile_size = 0;
  89. with_view_model(
  90. app->gb_cartridge_scene_5->view,
  91. GameBoyCartridgeRAMWriteModel * model,
  92. {
  93. model->event_title = "Transferring...";
  94. model->transfered = 0;
  95. model->start_time = furi_hal_rtc_get_timestamp(); // Registra el tiempo de inicio
  96. },
  97. true);
  98. the_savefile = malloc(fileSize); // to be freed by caller
  99. uint8_t* buf_ptr = the_savefile;
  100. size_t read = 0;
  101. while(read < fileSize) {
  102. size_t to_read = fileSize - read;
  103. if(to_read > UINT16_MAX) to_read = UINT16_MAX;
  104. uint16_t now_read = storage_file_read(file, buf_ptr, (uint16_t)to_read);
  105. read += now_read;
  106. buf_ptr += now_read;
  107. }
  108. savefile_size = read;
  109. uart_tx(app->uart, (uint8_t*)the_savefile, savefile_size);
  110. uart_tx(app->uart, (uint8_t*)("\n"), 1);
  111. with_view_model(
  112. app->gb_cartridge_scene_5->view,
  113. GameBoyCartridgeRAMWriteModel * model,
  114. { model->event_title = "Writing Cartridge..."; },
  115. true);
  116. free(the_savefile);
  117. if(file && storage_file_is_open(file)) {
  118. storage_file_close(file);
  119. }
  120. }
  121. return 0;
  122. }
  123. void gb_cartridge_scene_5_set_callback(
  124. GBCartridgeScene5* instance,
  125. GBCartridgeScene5Callback callback,
  126. void* context) {
  127. furi_assert(instance);
  128. furi_assert(callback);
  129. instance->callback = callback;
  130. instance->context = context;
  131. }
  132. static void drawProgressBar(Canvas* canvas, int progress) {
  133. for(int x = 0; x < 64 - 14 - UI_PADDING - UI_PADDING - UI_PADDING - UI_PADDING; x += 5) {
  134. for(int row = 0; row < 20; row += 5) {
  135. if(progress > 0) {
  136. canvas_draw_box(
  137. canvas, 14 /*ARROW*/ + UI_PADDING + 2 + x + 4, /*45*/ 26 + row, 4, 4);
  138. progress--;
  139. } else {
  140. canvas_draw_frame(
  141. canvas, 14 /*ARROW*/ + UI_PADDING + 2 + x + 4, /*45*/ 26 + row, 4, 4);
  142. }
  143. }
  144. }
  145. }
  146. void gb_cartridge_scene_5_draw(Canvas* canvas, GameBoyCartridgeRAMWriteModel* model) {
  147. // Clear the screen.
  148. canvas_set_color(canvas, ColorBlack);
  149. canvas_clear(canvas);
  150. canvas_set_color(canvas, ColorBlack);
  151. canvas_set_font(canvas, FontKeyboard);
  152. canvas_draw_frame(canvas, 0, 24, (128 / 2), 25);
  153. canvas_set_bitmap_mode(canvas, 1);
  154. canvas_set_font(canvas, FontPrimary);
  155. char progressText[42];
  156. int progress = 0;
  157. if(model->total_ram > 0 && model->transfered > 0) {
  158. progress = model->transfered * 100 / model->total_ram;
  159. }
  160. snprintf(progressText, sizeof(progressText), "%d%% Write RAM...", progress);
  161. canvas_draw_str_aligned(canvas, 128 / 2, 0, AlignCenter, AlignTop, progressText);
  162. canvas_set_font(canvas, FontSecondary);
  163. canvas_draw_str_aligned(canvas, 128 / 2, 12, AlignCenter, AlignTop, model->event_title);
  164. char total_ram_str[20];
  165. snprintf(
  166. total_ram_str,
  167. sizeof(total_ram_str),
  168. "of %.2lf MiB",
  169. (double)(model->total_ram / 1024.0 / 1024.0));
  170. char transfered_ram_str[20];
  171. snprintf(
  172. transfered_ram_str,
  173. sizeof(transfered_ram_str),
  174. "%.2lf MiB",
  175. (double)(model->transfered / 1024.0 / 1024.0));
  176. // Calcula la Tasa de Transferencia en KiB/s
  177. char transfer_rate_str[20];
  178. if(model->transfered > 0 && model->elapsed_time > 0) {
  179. double transfer_rate_kibps =
  180. (double)model->transfered / ((double)model->elapsed_time) / (double)1024.0;
  181. snprintf(transfer_rate_str, sizeof(transfer_rate_str), "%.2lf KiB/s", transfer_rate_kibps);
  182. } else {
  183. snprintf(transfer_rate_str, sizeof(transfer_rate_str), "0 KiB/s");
  184. }
  185. canvas_draw_str_aligned(
  186. canvas, (128 / 2) + UI_PADDING, 22 + 2, AlignLeft, AlignTop, transfered_ram_str);
  187. canvas_draw_str_aligned(
  188. canvas, (128 / 2) + UI_PADDING, 30 + 2, AlignLeft, AlignTop, total_ram_str);
  189. canvas_draw_str_aligned(
  190. canvas, (128 / 2) + UI_PADDING, 38 + 2, AlignLeft, AlignTop, transfer_rate_str);
  191. if(model->rx_active) {
  192. canvas_draw_icon_ex(canvas, UI_PADDING, 28, &I_ArrowUpFilled_14x15, IconRotation180);
  193. } else {
  194. canvas_draw_icon_ex(canvas, UI_PADDING, 28, &I_ArrowUpEmpty_14x15, IconRotation180);
  195. }
  196. char totalText[42];
  197. snprintf(totalText, sizeof(totalText), "%d", model->total_ram);
  198. drawProgressBar(
  199. canvas,
  200. (progress * UI_PROGRESS_ROWS * UI_PROGRESS_COLS) /
  201. 100); // Pinta las primeras 10 cajas de negro
  202. elements_button_center(canvas, "Write");
  203. }
  204. static void gb_cartridge_scene_5_model_init(GameBoyCartridgeRAMWriteModel* const model) {
  205. model->progress = 0;
  206. model->total_ram = 0;
  207. model->transfered = 0;
  208. model->ramBanks = 0;
  209. model->elapsed_time = 0;
  210. model->start_time = 0;
  211. model->event_title = "...";
  212. }
  213. void gameboy_handle_rx_data_cb(uint8_t* buf, size_t len, void* context) {
  214. furi_assert(context);
  215. UNUSED(len);
  216. UNUSED(buf);
  217. GBCartridge* instance = context;
  218. with_view_model(
  219. instance->gb_cartridge_scene_5->view,
  220. GameBoyCartridgeRAMWriteModel * model,
  221. {
  222. UNUSED(model);
  223. uint64_t current_time = furi_hal_rtc_get_timestamp();
  224. model->elapsed_time = current_time - model->start_time;
  225. if(current_time - last_toggle_time >= 0.2) {
  226. model->rx_active = !model->rx_active;
  227. last_toggle_time = current_time;
  228. }
  229. cJSON* json = cJSON_Parse((char*)buf);
  230. if(json == NULL) {
  231. } else {
  232. cJSON* type = cJSON_GetObjectItemCaseSensitive(json, "type");
  233. if(cJSON_IsString(type) && (type->valuestring != NULL)) {
  234. model->event_type = strdup(type->valuestring);
  235. } else {
  236. model->event_type = "None";
  237. }
  238. // offset
  239. cJSON* offset = cJSON_GetObjectItemCaseSensitive(json, "offset");
  240. if(cJSON_IsNumber(offset)) {
  241. model->offset = offset->valueint;
  242. } else {
  243. model->offset = 0;
  244. }
  245. // value
  246. cJSON* value = cJSON_GetObjectItemCaseSensitive(json, "value");
  247. if(cJSON_IsNumber(value)) {
  248. model->value = value->valueint;
  249. } else {
  250. model->value = 0;
  251. }
  252. }
  253. if(strcmp(model->event_type, "progress") == 0) {
  254. // progress
  255. cJSON* progress = cJSON_GetObjectItemCaseSensitive(json, "progress");
  256. if(cJSON_IsNumber(progress)) {
  257. model->transfered += progress->valueint;
  258. }
  259. }
  260. if(strcmp(model->event_type, "success") == 0) {
  261. notification_success(instance->notification);
  262. model->transfered = model->total_ram;
  263. model->event_title = "Done!";
  264. }
  265. },
  266. true);
  267. }
  268. bool gb_cartridge_scene_5_input(InputEvent* event, void* context) {
  269. furi_assert(context);
  270. GBCartridgeScene5* instance = context;
  271. if(event->type == InputTypeRelease) {
  272. switch(event->key) {
  273. case InputKeyBack:
  274. with_view_model(
  275. instance->view,
  276. GameBoyCartridgeRAMWriteModel * model,
  277. {
  278. UNUSED(model);
  279. GBCartridge* app = (GBCartridge*)instance->context;
  280. // Unregister rx callback
  281. uart_set_handle_rx_data_cb(app->uart, NULL);
  282. uart_set_handle_rx_data_cb(app->lp_uart, NULL);
  283. instance->callback(GBCartridgeCustomEventScene5Back, instance->context);
  284. },
  285. true);
  286. break;
  287. case InputKeyUp:
  288. case InputKeyDown:
  289. case InputKeyLeft:
  290. case InputKeyRight:
  291. break;
  292. case InputKeyOk: {
  293. GBCartridge* app = ((GBCartridge*)instance->context);
  294. uart_set_handle_rx_data_cb(app->uart, gameboy_handle_rx_data_cb);
  295. cartridge_writting_worker_thread(app);
  296. } break;
  297. case InputKeyMAX:
  298. break;
  299. }
  300. }
  301. return true;
  302. }
  303. void gb_cartridge_scene_5_exit(void* context) {
  304. furi_assert(context);
  305. GBCartridge* app = context;
  306. gb_cartridge_stop_all_sound(app);
  307. }
  308. void gb_cartridge_scene_5_enter(void* context) {
  309. furi_assert(context);
  310. GBCartridgeScene5* instance = context;
  311. GBCartridge* app = (GBCartridge*)instance->context;
  312. UNUSED(app);
  313. with_view_model(
  314. app->gb_cartridge_scene_5->view,
  315. GameBoyCartridgeRAMWriteModel * model,
  316. {
  317. UNUSED(model);
  318. gb_cartridge_scene_5_model_init(model);
  319. },
  320. false);
  321. }
  322. GBCartridgeScene5* gb_cartridge_scene_5_alloc() {
  323. GBCartridgeScene5* instance = malloc(sizeof(GBCartridgeScene5));
  324. instance->view = view_alloc();
  325. view_allocate_model(
  326. instance->view, ViewModelTypeLocking, sizeof(GameBoyCartridgeRAMWriteModel));
  327. view_set_context(instance->view, instance);
  328. view_set_draw_callback(instance->view, (ViewDrawCallback)gb_cartridge_scene_5_draw);
  329. view_set_input_callback(instance->view, gb_cartridge_scene_5_input);
  330. view_set_enter_callback(instance->view, gb_cartridge_scene_5_enter);
  331. view_set_exit_callback(instance->view, gb_cartridge_scene_5_exit);
  332. with_view_model(
  333. instance->view,
  334. GameBoyCartridgeRAMWriteModel * model,
  335. { gb_cartridge_scene_5_model_init(model); },
  336. true);
  337. return instance;
  338. }
  339. void gb_cartridge_scene_5_free(GBCartridgeScene5* instance) {
  340. GBCartridge* app = instance->context;
  341. UNUSED(app);
  342. furi_assert(instance);
  343. view_free(instance->view);
  344. free(instance);
  345. }
  346. View* gb_cartridge_scene_5_get_view(GBCartridgeScene5* instance) {
  347. furi_assert(instance);
  348. return instance->view;
  349. }