nfc_apdu_runner.c 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  1. /*
  2. * @Author: SpenserCai
  3. * @Date: 2025-02-28 17:52:49
  4. * @version:
  5. * @LastEditors: SpenserCai
  6. * @LastEditTime: 2025-03-16 19:59:42
  7. * @Description: file content
  8. */
  9. #include "nfc_apdu_runner.h"
  10. #include "scenes/nfc_apdu_runner_scene.h"
  11. // 菜单项枚举
  12. typedef enum {
  13. NfcApduRunnerSubmenuIndexLoadFile,
  14. NfcApduRunnerSubmenuIndexViewLogs,
  15. NfcApduRunnerSubmenuIndexAbout,
  16. } NfcApduRunnerSubmenuIndex;
  17. // 前向声明
  18. static void nfc_apdu_runner_free(NfcApduRunner* app);
  19. static NfcApduRunner* nfc_apdu_runner_alloc();
  20. static void nfc_apdu_runner_init(NfcApduRunner* app);
  21. static bool nfc_apdu_runner_custom_event_callback(void* context, uint32_t event);
  22. static bool nfc_apdu_runner_back_event_callback(void* context);
  23. static void nfc_apdu_runner_tick_event_callback(void* context);
  24. // 释放APDU脚本资源
  25. void nfc_apdu_script_free(NfcApduScript* script) {
  26. if(script == NULL) return;
  27. for(uint32_t i = 0; i < script->command_count; i++) {
  28. free(script->commands[i]);
  29. }
  30. free(script);
  31. }
  32. // 释放APDU响应资源
  33. void nfc_apdu_responses_free(NfcApduResponse* responses, uint32_t count) {
  34. if(responses == NULL) return;
  35. for(uint32_t i = 0; i < count; i++) {
  36. free(responses[i].command);
  37. free(responses[i].response);
  38. }
  39. free(responses);
  40. }
  41. // 解析卡类型字符串
  42. static CardType parse_card_type(const char* type_str) {
  43. if(strcmp(type_str, "iso14443_3a") == 0) {
  44. return CardTypeIso14443_3a;
  45. } else if(strcmp(type_str, "iso14443_3b") == 0) {
  46. return CardTypeIso14443_3b;
  47. } else if(strcmp(type_str, "iso14443_4a") == 0) {
  48. return CardTypeIso14443_4a;
  49. } else if(strcmp(type_str, "iso14443_4b") == 0) {
  50. return CardTypeIso14443_4b;
  51. } else {
  52. return CardTypeUnknown;
  53. }
  54. }
  55. // 解析APDU脚本文件
  56. NfcApduScript* nfc_apdu_script_parse(Storage* storage, const char* file_path) {
  57. FURI_LOG_I("APDU_DEBUG", "开始解析脚本文件: %s", file_path);
  58. if(!storage || !file_path) {
  59. FURI_LOG_E("APDU_DEBUG", "无效的存储或文件路径");
  60. return NULL;
  61. }
  62. NfcApduScript* script = malloc(sizeof(NfcApduScript));
  63. if(!script) {
  64. FURI_LOG_E("APDU_DEBUG", "分配脚本内存失败");
  65. return NULL;
  66. }
  67. memset(script, 0, sizeof(NfcApduScript));
  68. FuriString* temp_str = furi_string_alloc();
  69. if(!temp_str) {
  70. FURI_LOG_E("APDU_DEBUG", "分配临时字符串内存失败");
  71. free(script);
  72. return NULL;
  73. }
  74. File* file = storage_file_alloc(storage);
  75. if(!file) {
  76. FURI_LOG_E("APDU_DEBUG", "分配文件失败");
  77. furi_string_free(temp_str);
  78. free(script);
  79. return NULL;
  80. }
  81. bool success = false;
  82. bool in_data_section = false;
  83. do {
  84. if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
  85. FURI_LOG_E("APDU_DEBUG", "打开文件失败: %s", file_path);
  86. break;
  87. }
  88. // 读取整个文件内容到缓冲区
  89. char file_buf[1024];
  90. size_t bytes_read = storage_file_read(file, file_buf, sizeof(file_buf) - 1);
  91. if(bytes_read <= 0) {
  92. FURI_LOG_E("APDU_DEBUG", "读取文件内容失败");
  93. break;
  94. }
  95. file_buf[bytes_read] = '\0';
  96. FURI_LOG_I("APDU_DEBUG", "读取文件内容成功, 大小: %zu 字节", bytes_read);
  97. // 解析文件内容
  98. char* line = file_buf;
  99. char* next_line;
  100. // 读取文件类型行
  101. next_line = strchr(line, '\n');
  102. if(!next_line) {
  103. FURI_LOG_E("APDU_DEBUG", "文件格式错误,找不到换行符");
  104. break;
  105. }
  106. *next_line = '\0';
  107. if(strncmp(line, "Filetype: APDU Script", 21) != 0) {
  108. FURI_LOG_E("APDU_DEBUG", "无效的文件类型: %s", line);
  109. break;
  110. }
  111. // 读取版本行
  112. line = next_line + 1;
  113. next_line = strchr(line, '\n');
  114. if(!next_line) {
  115. FURI_LOG_E("APDU_DEBUG", "文件格式错误,找不到版本行结束");
  116. break;
  117. }
  118. *next_line = '\0';
  119. if(strncmp(line, "Version: 1", 10) != 0) {
  120. FURI_LOG_E("APDU_DEBUG", "无效的版本: %s", line);
  121. break;
  122. }
  123. // 读取卡类型行
  124. line = next_line + 1;
  125. next_line = strchr(line, '\n');
  126. if(!next_line) {
  127. FURI_LOG_E("APDU_DEBUG", "文件格式错误,找不到卡类型行结束");
  128. break;
  129. }
  130. *next_line = '\0';
  131. if(strncmp(line, "CardType: ", 10) != 0) {
  132. FURI_LOG_E("APDU_DEBUG", "无效的卡类型行: %s", line);
  133. break;
  134. }
  135. // 提取卡类型
  136. const char* card_type_str = line + 10; // 跳过 "CardType: "
  137. script->card_type = parse_card_type(card_type_str);
  138. if(script->card_type == CardTypeUnknown) {
  139. FURI_LOG_E("APDU_DEBUG", "不支持的卡类型: %s", card_type_str);
  140. break;
  141. }
  142. FURI_LOG_I("APDU_DEBUG", "卡类型解析成功: %s", card_type_str);
  143. // 读取数据行
  144. line = next_line + 1;
  145. next_line = strchr(line, '\n');
  146. // 检查当前行是否为 Data 行
  147. if(strncmp(line, "Data: [", 7) != 0) {
  148. FURI_LOG_E("APDU_DEBUG", "无效的数据行: %s", line);
  149. break;
  150. }
  151. in_data_section = true;
  152. // 解析命令数组
  153. char* data_str = line + 7; // 跳过 "Data: ["
  154. // 处理多行格式的命令
  155. // 将整个文件内容中的所有换行符和空格替换为空格,以便正确解析命令
  156. char* p = data_str;
  157. while(*p) {
  158. if(*p == '\n' || *p == '\r') {
  159. *p = ' '; // 将换行符替换为空格
  160. }
  161. p++;
  162. }
  163. // 清理多余的空格,使解析更加稳健
  164. p = data_str;
  165. char* q = data_str;
  166. bool in_quotes = false;
  167. while(*p) {
  168. // 在引号内保留所有字符
  169. if(*p == '"') {
  170. in_quotes = !in_quotes;
  171. *q++ = *p++;
  172. continue;
  173. }
  174. // 在引号外,跳过空格
  175. if(!in_quotes && (*p == ' ' || *p == '\t')) {
  176. p++;
  177. continue;
  178. }
  179. // 复制其他字符
  180. *q++ = *p++;
  181. }
  182. *q = '\0'; // 确保字符串正确终止
  183. // 查找第一个引号
  184. char* command_start = strchr(data_str, '"');
  185. if(!command_start) {
  186. FURI_LOG_E("APDU_DEBUG", "未找到命令开始引号");
  187. break;
  188. }
  189. while(command_start != NULL && script->command_count < MAX_APDU_COMMANDS) {
  190. command_start++; // 跳过开始的引号
  191. // 查找结束引号
  192. char* command_end = strchr(command_start, '"');
  193. if(!command_end) {
  194. FURI_LOG_E("APDU_DEBUG", "未找到命令结束引号");
  195. break;
  196. }
  197. size_t command_len = command_end - command_start;
  198. if(command_len == 0) {
  199. // 空命令,跳过
  200. FURI_LOG_W("APDU_DEBUG", "空命令,跳过");
  201. command_start = strchr(command_end + 1, '"');
  202. continue;
  203. }
  204. // 验证命令是否只包含十六进制字符
  205. bool valid_hex = true;
  206. for(size_t i = 0; i < command_len; i++) {
  207. char c = command_start[i];
  208. if(!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) {
  209. valid_hex = false;
  210. break;
  211. }
  212. }
  213. if(!valid_hex) {
  214. FURI_LOG_E(
  215. "APDU_DEBUG", "无效的十六进制命令: %.*s", (int)command_len, command_start);
  216. command_start = strchr(command_end + 1, '"');
  217. continue;
  218. }
  219. // 命令长度必须是偶数
  220. if(command_len % 2 != 0) {
  221. FURI_LOG_E(
  222. "APDU_DEBUG", "命令长度必须是偶数: %.*s", (int)command_len, command_start);
  223. command_start = strchr(command_end + 1, '"');
  224. continue;
  225. }
  226. // 分配内存并复制命令
  227. script->commands[script->command_count] = malloc(command_len + 1);
  228. if(!script->commands[script->command_count]) {
  229. FURI_LOG_E("APDU_DEBUG", "分配命令内存失败");
  230. break;
  231. }
  232. strncpy(script->commands[script->command_count], command_start, command_len);
  233. script->commands[script->command_count][command_len] = '\0';
  234. script->command_count++;
  235. // 查找下一个命令
  236. command_start = strchr(command_end + 1, '"');
  237. }
  238. success = in_data_section && (script->command_count > 0);
  239. FURI_LOG_I("APDU_DEBUG", "命令解析完成, 命令数: %lu", script->command_count);
  240. } while(false);
  241. storage_file_close(file);
  242. storage_file_free(file);
  243. furi_string_free(temp_str);
  244. if(!success) {
  245. FURI_LOG_E("APDU_DEBUG", "解析失败,释放脚本资源");
  246. nfc_apdu_script_free(script);
  247. return NULL;
  248. }
  249. FURI_LOG_I("APDU_DEBUG", "脚本解析成功");
  250. return script;
  251. }
  252. // 保存APDU响应结果
  253. bool nfc_apdu_save_responses(
  254. Storage* storage,
  255. const char* file_path,
  256. NfcApduResponse* responses,
  257. uint32_t response_count,
  258. const char* custom_save_path) {
  259. FuriString* temp_str = furi_string_alloc();
  260. FuriString* file_path_str = furi_string_alloc_set(file_path);
  261. FuriString* response_path = furi_string_alloc();
  262. // 构建响应文件路径
  263. if(custom_save_path != NULL) {
  264. // 使用自定义保存路径
  265. furi_string_set(response_path, custom_save_path);
  266. } else {
  267. // 使用默认路径
  268. FuriString* filename = furi_string_alloc();
  269. path_extract_filename(file_path_str, filename, true);
  270. furi_string_cat_printf(
  271. response_path,
  272. "%s/%s%s",
  273. APP_DIRECTORY_PATH,
  274. furi_string_get_cstr(filename),
  275. RESPONSE_EXTENSION);
  276. furi_string_free(filename);
  277. }
  278. File* file = storage_file_alloc(storage);
  279. bool success = false;
  280. do {
  281. if(!storage_file_open(
  282. file, furi_string_get_cstr(response_path), FSAM_WRITE, FSOM_CREATE_ALWAYS))
  283. break;
  284. // 写入文件头
  285. furi_string_printf(temp_str, "Filetype: APDU Script Response\n");
  286. if(!storage_file_write(file, furi_string_get_cstr(temp_str), furi_string_size(temp_str)))
  287. break;
  288. furi_string_printf(temp_str, "Response:\n");
  289. if(!storage_file_write(file, furi_string_get_cstr(temp_str), furi_string_size(temp_str)))
  290. break;
  291. // 写入每个命令和响应
  292. for(uint32_t i = 0; i < response_count; i++) {
  293. furi_string_printf(temp_str, "In: %s\n", responses[i].command);
  294. if(!storage_file_write(
  295. file, furi_string_get_cstr(temp_str), furi_string_size(temp_str)))
  296. break;
  297. furi_string_printf(temp_str, "Out: ");
  298. if(!storage_file_write(
  299. file, furi_string_get_cstr(temp_str), furi_string_size(temp_str)))
  300. break;
  301. // 将响应数据转换为十六进制字符串
  302. for(uint16_t j = 0; j < responses[i].response_length; j++) {
  303. furi_string_printf(temp_str, "%02X", responses[i].response[j]);
  304. if(!storage_file_write(
  305. file, furi_string_get_cstr(temp_str), furi_string_size(temp_str)))
  306. break;
  307. }
  308. furi_string_printf(temp_str, "\n");
  309. if(!storage_file_write(
  310. file, furi_string_get_cstr(temp_str), furi_string_size(temp_str)))
  311. break;
  312. }
  313. success = true;
  314. } while(false);
  315. storage_file_close(file);
  316. storage_file_free(file);
  317. furi_string_free(temp_str);
  318. furi_string_free(file_path_str);
  319. furi_string_free(response_path);
  320. return success;
  321. }
  322. // 添加日志
  323. void add_log_entry(NfcApduRunner* app, const char* message, bool is_error) {
  324. if(app->log_count >= MAX_LOG_ENTRIES) {
  325. // 移除最旧的日志
  326. free(app->log_entries[0].message);
  327. for(uint32_t i = 0; i < app->log_count - 1; i++) {
  328. app->log_entries[i] = app->log_entries[i + 1];
  329. }
  330. app->log_count--;
  331. }
  332. app->log_entries[app->log_count].message = strdup(message);
  333. app->log_entries[app->log_count].is_error = is_error;
  334. app->log_count++;
  335. }
  336. // 释放日志资源
  337. void free_logs(NfcApduRunner* app) {
  338. if(!app) {
  339. return;
  340. }
  341. if(!app->log_entries) {
  342. return;
  343. }
  344. for(uint32_t i = 0; i < app->log_count; i++) {
  345. if(app->log_entries[i].message) {
  346. free(app->log_entries[i].message);
  347. app->log_entries[i].message = NULL;
  348. }
  349. }
  350. app->log_count = 0;
  351. }
  352. // 视图分发器回调
  353. static bool nfc_apdu_runner_custom_event_callback(void* context, uint32_t event) {
  354. furi_assert(context);
  355. NfcApduRunner* app = context;
  356. bool handled = scene_manager_handle_custom_event(app->scene_manager, event);
  357. return handled;
  358. }
  359. static bool nfc_apdu_runner_back_event_callback(void* context) {
  360. furi_assert(context);
  361. NfcApduRunner* app = context;
  362. // 直接交给场景管理器处理返回事件
  363. bool handled = scene_manager_handle_back_event(app->scene_manager);
  364. return handled;
  365. }
  366. static void nfc_apdu_runner_tick_event_callback(void* context) {
  367. furi_assert(context);
  368. NfcApduRunner* app = context;
  369. scene_manager_handle_tick_event(app->scene_manager);
  370. }
  371. // 分配应用程序资源
  372. static NfcApduRunner* nfc_apdu_runner_alloc() {
  373. NfcApduRunner* app = malloc(sizeof(NfcApduRunner));
  374. if(!app) {
  375. return NULL;
  376. }
  377. memset(app, 0, sizeof(NfcApduRunner));
  378. // 分配GUI资源
  379. app->gui = furi_record_open(RECORD_GUI);
  380. if(!app->gui) {
  381. nfc_apdu_runner_free(app);
  382. return NULL;
  383. }
  384. app->view_dispatcher = view_dispatcher_alloc();
  385. if(!app->view_dispatcher) {
  386. nfc_apdu_runner_free(app);
  387. return NULL;
  388. }
  389. app->scene_manager = scene_manager_alloc(&nfc_apdu_runner_scene_handlers, app);
  390. if(!app->scene_manager) {
  391. nfc_apdu_runner_free(app);
  392. return NULL;
  393. }
  394. app->submenu = submenu_alloc();
  395. if(!app->submenu) {
  396. nfc_apdu_runner_free(app);
  397. return NULL;
  398. }
  399. app->dialogs = furi_record_open(RECORD_DIALOGS);
  400. if(!app->dialogs) {
  401. nfc_apdu_runner_free(app);
  402. return NULL;
  403. }
  404. app->widget = widget_alloc();
  405. if(!app->widget) {
  406. nfc_apdu_runner_free(app);
  407. return NULL;
  408. }
  409. app->text_box = text_box_alloc();
  410. if(!app->text_box) {
  411. nfc_apdu_runner_free(app);
  412. return NULL;
  413. }
  414. app->text_box_store = furi_string_alloc();
  415. if(!app->text_box_store) {
  416. nfc_apdu_runner_free(app);
  417. return NULL;
  418. }
  419. app->popup = popup_alloc();
  420. if(!app->popup) {
  421. nfc_apdu_runner_free(app);
  422. return NULL;
  423. }
  424. app->button_menu = button_menu_alloc();
  425. if(!app->button_menu) {
  426. nfc_apdu_runner_free(app);
  427. return NULL;
  428. }
  429. app->text_input = text_input_alloc();
  430. if(!app->text_input) {
  431. nfc_apdu_runner_free(app);
  432. return NULL;
  433. }
  434. // 分配NFC资源
  435. app->nfc = nfc_alloc();
  436. if(!app->nfc) {
  437. nfc_apdu_runner_free(app);
  438. return NULL;
  439. }
  440. // 分配NFC Worker
  441. app->worker = nfc_worker_alloc(app->nfc);
  442. if(!app->worker) {
  443. nfc_apdu_runner_free(app);
  444. return NULL;
  445. }
  446. // 分配存储资源
  447. app->storage = furi_record_open(RECORD_STORAGE);
  448. if(!app->storage) {
  449. nfc_apdu_runner_free(app);
  450. return NULL;
  451. }
  452. app->file_path = furi_string_alloc();
  453. if(!app->file_path) {
  454. nfc_apdu_runner_free(app);
  455. return NULL;
  456. }
  457. // 设置默认目录为APP_DIRECTORY_PATH
  458. furi_string_set(app->file_path, APP_DIRECTORY_PATH);
  459. // 分配日志资源
  460. app->log_entries = malloc(sizeof(LogEntry) * MAX_LOG_ENTRIES);
  461. if(!app->log_entries) {
  462. nfc_apdu_runner_free(app);
  463. return NULL;
  464. }
  465. app->log_count = 0;
  466. return app;
  467. }
  468. // 释放应用程序资源
  469. static void nfc_apdu_runner_free(NfcApduRunner* app) {
  470. if(!app) {
  471. return;
  472. }
  473. // 释放脚本和响应资源
  474. if(app->script) {
  475. nfc_apdu_script_free(app->script);
  476. app->script = NULL;
  477. }
  478. if(app->responses) {
  479. nfc_apdu_responses_free(app->responses, app->response_count);
  480. app->responses = NULL;
  481. app->response_count = 0;
  482. }
  483. // 先释放所有视图
  484. if(app->submenu) {
  485. view_dispatcher_remove_view(app->view_dispatcher, NfcApduRunnerViewSubmenu);
  486. submenu_free(app->submenu);
  487. app->submenu = NULL;
  488. }
  489. if(app->widget) {
  490. view_dispatcher_remove_view(app->view_dispatcher, NfcApduRunnerViewWidget);
  491. widget_free(app->widget);
  492. app->widget = NULL;
  493. }
  494. if(app->text_box) {
  495. view_dispatcher_remove_view(app->view_dispatcher, NfcApduRunnerViewTextBox);
  496. text_box_free(app->text_box);
  497. app->text_box = NULL;
  498. }
  499. if(app->text_box_store) {
  500. furi_string_free(app->text_box_store);
  501. app->text_box_store = NULL;
  502. }
  503. if(app->popup) {
  504. view_dispatcher_remove_view(app->view_dispatcher, NfcApduRunnerViewPopup);
  505. popup_free(app->popup);
  506. app->popup = NULL;
  507. }
  508. if(app->button_menu) {
  509. button_menu_free(app->button_menu);
  510. app->button_menu = NULL;
  511. }
  512. if(app->text_input) {
  513. view_dispatcher_remove_view(app->view_dispatcher, NfcApduRunnerViewTextInput);
  514. text_input_free(app->text_input);
  515. app->text_input = NULL;
  516. }
  517. // 关闭记录
  518. if(app->dialogs) {
  519. furi_record_close(RECORD_DIALOGS);
  520. app->dialogs = NULL;
  521. }
  522. if(app->gui) {
  523. furi_record_close(RECORD_GUI);
  524. app->gui = NULL;
  525. }
  526. // 释放NFC资源
  527. if(app->nfc) {
  528. nfc_free(app->nfc);
  529. app->nfc = NULL;
  530. }
  531. // 释放NFC Worker
  532. if(app->worker) {
  533. nfc_worker_free(app->worker);
  534. app->worker = NULL;
  535. }
  536. // 释放存储资源
  537. if(app->file_path) {
  538. furi_string_free(app->file_path);
  539. app->file_path = NULL;
  540. }
  541. if(app->storage) {
  542. furi_record_close(RECORD_STORAGE);
  543. app->storage = NULL;
  544. }
  545. // 释放日志资源
  546. free_logs(app);
  547. if(app->log_entries) {
  548. free(app->log_entries);
  549. app->log_entries = NULL;
  550. }
  551. // 先释放场景管理器
  552. if(app->scene_manager) {
  553. scene_manager_free(app->scene_manager);
  554. app->scene_manager = NULL;
  555. }
  556. // 最后处理视图分发器
  557. if(app->view_dispatcher) {
  558. view_dispatcher_free(app->view_dispatcher);
  559. app->view_dispatcher = NULL;
  560. FURI_LOG_I("APDU_DEBUG", "View dispatcher freed");
  561. }
  562. // 释放应用程序资源
  563. free(app);
  564. }
  565. // 初始化应用程序
  566. static void nfc_apdu_runner_init(NfcApduRunner* app) {
  567. if(!app) {
  568. return;
  569. }
  570. // 检查视图分发器是否已分配
  571. if(!app->view_dispatcher) {
  572. return;
  573. }
  574. // 配置视图分发器
  575. view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
  576. view_dispatcher_set_custom_event_callback(
  577. app->view_dispatcher, nfc_apdu_runner_custom_event_callback);
  578. view_dispatcher_set_navigation_event_callback(
  579. app->view_dispatcher, nfc_apdu_runner_back_event_callback);
  580. view_dispatcher_set_tick_event_callback(
  581. app->view_dispatcher, nfc_apdu_runner_tick_event_callback, 100);
  582. // 检查GUI是否已分配
  583. if(!app->gui) {
  584. return;
  585. }
  586. view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
  587. // 检查视图组件是否已分配
  588. if(!app->submenu || !app->widget || !app->text_box || !app->popup || !app->text_input) {
  589. return;
  590. }
  591. // 添加视图
  592. view_dispatcher_add_view(
  593. app->view_dispatcher, NfcApduRunnerViewSubmenu, submenu_get_view(app->submenu));
  594. view_dispatcher_add_view(
  595. app->view_dispatcher, NfcApduRunnerViewWidget, widget_get_view(app->widget));
  596. view_dispatcher_add_view(
  597. app->view_dispatcher, NfcApduRunnerViewTextBox, text_box_get_view(app->text_box));
  598. view_dispatcher_add_view(
  599. app->view_dispatcher, NfcApduRunnerViewPopup, popup_get_view(app->popup));
  600. view_dispatcher_add_view(
  601. app->view_dispatcher, NfcApduRunnerViewTextInput, text_input_get_view(app->text_input));
  602. // 配置文本框
  603. text_box_set_font(app->text_box, TextBoxFontText);
  604. // 检查存储是否已分配
  605. if(!app->storage) {
  606. return;
  607. }
  608. // 确保应用目录存在
  609. if(!storage_dir_exists(app->storage, APP_DIRECTORY_PATH)) {
  610. if(!storage_simply_mkdir(app->storage, APP_DIRECTORY_PATH)) {
  611. FURI_LOG_E("APDU_DEBUG", "无法创建应用目录");
  612. return;
  613. }
  614. }
  615. // 启动场景管理器
  616. scene_manager_next_scene(app->scene_manager, NfcApduRunnerSceneStart);
  617. }
  618. // 应用程序入口点
  619. int32_t nfc_apdu_runner_app(void* p) {
  620. UNUSED(p);
  621. furi_hal_power_suppress_charge_enter();
  622. // 分配应用程序资源
  623. NfcApduRunner* app = nfc_apdu_runner_alloc();
  624. if(!app) {
  625. return -1;
  626. }
  627. // 初始化应用程序
  628. nfc_apdu_runner_init(app);
  629. // 运行事件循环
  630. view_dispatcher_run(app->view_dispatcher);
  631. // 释放应用程序资源
  632. nfc_apdu_runner_free(app);
  633. furi_hal_power_suppress_charge_exit();
  634. return 0;
  635. }