storage.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. #include <game/storage.h>
  2. #include <stdio.h>
  3. #include <furi.h>
  4. #include <furi_hal.h>
  5. bool save_uint32(const char *path_name, uint32_t value)
  6. {
  7. char buffer[32];
  8. snprintf(buffer, sizeof(buffer), "%lu", value);
  9. return save_char(path_name, buffer);
  10. }
  11. // Helper function to save an int8_t
  12. bool save_int8(const char *path_name, int8_t value)
  13. {
  14. char buffer[32];
  15. snprintf(buffer, sizeof(buffer), "%d", value);
  16. return save_char(path_name, buffer);
  17. }
  18. // Helper function to save a float
  19. bool save_float(const char *path_name, float value)
  20. {
  21. char buffer[32];
  22. snprintf(buffer, sizeof(buffer), "%.6f", (double)value); // Limit to 6 decimal places
  23. return save_char(path_name, buffer);
  24. }
  25. bool save_player_context(PlayerContext *player_context)
  26. {
  27. if (!player_context)
  28. {
  29. FURI_LOG_E(TAG, "Invalid player context");
  30. return false;
  31. }
  32. // Create the directory for saving settings
  33. char directory_path[256];
  34. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/data/player");
  35. // Create the directory
  36. Storage *storage = furi_record_open(RECORD_STORAGE);
  37. storage_common_mkdir(storage, directory_path);
  38. furi_record_close(RECORD_STORAGE);
  39. // 1. Username (String)
  40. if (!save_char("player/username", player_context->username))
  41. {
  42. FURI_LOG_E(TAG, "Failed to save player username");
  43. return false;
  44. }
  45. // 2. Level (uint32_t)
  46. if (!save_uint32("player/level", player_context->level))
  47. {
  48. FURI_LOG_E(TAG, "Failed to save player level");
  49. return false;
  50. }
  51. // 3. XP (uint32_t)
  52. if (!save_uint32("player/xp", player_context->xp))
  53. {
  54. FURI_LOG_E(TAG, "Failed to save player xp");
  55. return false;
  56. }
  57. // 4. Health (uint32_t)
  58. if (!save_uint32("player/health", player_context->health))
  59. {
  60. FURI_LOG_E(TAG, "Failed to save player health");
  61. return false;
  62. }
  63. // 5. Strength (uint32_t)
  64. if (!save_uint32("player/strength", player_context->strength))
  65. {
  66. FURI_LOG_E(TAG, "Failed to save player strength");
  67. return false;
  68. }
  69. // 6. Max Health (uint32_t)
  70. if (!save_uint32("player/max_health", player_context->max_health))
  71. {
  72. FURI_LOG_E(TAG, "Failed to save player max health");
  73. return false;
  74. }
  75. // 7. Health Regen (uint32_t)
  76. if (!save_uint32("player/health_regen", player_context->health_regen))
  77. {
  78. FURI_LOG_E(TAG, "Failed to save player health regen");
  79. return false;
  80. }
  81. // 8. Elapsed Health Regen (float)
  82. if (!save_float("player/elapsed_health_regen", player_context->elapsed_health_regen))
  83. {
  84. FURI_LOG_E(TAG, "Failed to save player elapsed health regen");
  85. return false;
  86. }
  87. // 9. Attack Timer (float)
  88. if (!save_float("player/attack_timer", player_context->attack_timer))
  89. {
  90. FURI_LOG_E(TAG, "Failed to save player attack timer");
  91. return false;
  92. }
  93. // 10. Elapsed Attack Timer (float)
  94. if (!save_float("player/elapsed_attack_timer", player_context->elapsed_attack_timer))
  95. {
  96. FURI_LOG_E(TAG, "Failed to save player elapsed attack timer");
  97. return false;
  98. }
  99. // 11. Direction (enum PlayerDirection)
  100. {
  101. char direction_str[2];
  102. switch (player_context->direction)
  103. {
  104. case PLAYER_UP:
  105. strncpy(direction_str, "0", sizeof(direction_str));
  106. break;
  107. case PLAYER_DOWN:
  108. strncpy(direction_str, "1", sizeof(direction_str));
  109. break;
  110. case PLAYER_LEFT:
  111. strncpy(direction_str, "2", sizeof(direction_str));
  112. break;
  113. case PLAYER_RIGHT:
  114. default:
  115. strncpy(direction_str, "3", sizeof(direction_str));
  116. break;
  117. }
  118. direction_str[1] = '\0'; // Ensure null termination
  119. if (!save_char("player/direction", direction_str))
  120. {
  121. FURI_LOG_E(TAG, "Failed to save player direction");
  122. return false;
  123. }
  124. }
  125. // 12. State (enum PlayerState)
  126. {
  127. char state_str[2];
  128. switch (player_context->state)
  129. {
  130. case PLAYER_IDLE:
  131. strncpy(state_str, "0", sizeof(state_str));
  132. break;
  133. case PLAYER_MOVING:
  134. strncpy(state_str, "1", sizeof(state_str));
  135. break;
  136. case PLAYER_ATTACKING:
  137. strncpy(state_str, "2", sizeof(state_str));
  138. break;
  139. case PLAYER_ATTACKED:
  140. strncpy(state_str, "3", sizeof(state_str));
  141. break;
  142. case PLAYER_DEAD:
  143. strncpy(state_str, "4", sizeof(state_str));
  144. break;
  145. default:
  146. strncpy(state_str, "5", sizeof(state_str)); // Assuming '5' for unknown states
  147. break;
  148. }
  149. state_str[1] = '\0'; // Ensure null termination
  150. if (!save_char("player/state", state_str))
  151. {
  152. FURI_LOG_E(TAG, "Failed to save player state");
  153. return false;
  154. }
  155. }
  156. // 13. Start Position X (float)
  157. if (!save_float("player/start_position_x", player_context->start_position.x))
  158. {
  159. FURI_LOG_E(TAG, "Failed to save player start position x");
  160. return false;
  161. }
  162. // 14. Start Position Y (float)
  163. if (!save_float("player/start_position_y", player_context->start_position.y))
  164. {
  165. FURI_LOG_E(TAG, "Failed to save player start position y");
  166. return false;
  167. }
  168. // 15. dx (int8_t)
  169. if (!save_int8("player/dx", player_context->dx))
  170. {
  171. FURI_LOG_E(TAG, "Failed to save player dx");
  172. return false;
  173. }
  174. // 16. dy (int8_t)
  175. if (!save_int8("player/dy", player_context->dy))
  176. {
  177. FURI_LOG_E(TAG, "Failed to save player dy");
  178. return false;
  179. }
  180. return true;
  181. }
  182. // Helper function to load an integer
  183. bool load_number(const char *path_name, int *value)
  184. {
  185. if (!path_name || !value)
  186. {
  187. FURI_LOG_E(TAG, "Invalid arguments to load_number");
  188. return false;
  189. }
  190. char buffer[32];
  191. if (!load_char(path_name, buffer, sizeof(buffer)))
  192. {
  193. FURI_LOG_E(TAG, "Failed to load number from path: %s", path_name);
  194. return false;
  195. }
  196. *value = atoi(buffer);
  197. return true;
  198. }
  199. // Helper function to load a float
  200. bool load_float(const char *path_name, float *value)
  201. {
  202. if (!path_name || !value)
  203. {
  204. FURI_LOG_E(TAG, "Invalid arguments to load_float");
  205. return false;
  206. }
  207. char buffer[32];
  208. if (!load_char(path_name, buffer, sizeof(buffer)))
  209. {
  210. FURI_LOG_E(TAG, "Failed to load float from path: %s", path_name);
  211. return false;
  212. }
  213. *value = strtof(buffer, NULL);
  214. return true;
  215. }
  216. bool load_int8(const char *path_name, int8_t *value)
  217. {
  218. if (!path_name || !value)
  219. {
  220. FURI_LOG_E(TAG, "Invalid arguments to load_int8");
  221. return false;
  222. }
  223. char buffer[32];
  224. if (!load_char(path_name, buffer, sizeof(buffer)))
  225. {
  226. FURI_LOG_E(TAG, "Failed to load int8 from path: %s", path_name);
  227. return false;
  228. }
  229. long temp = strtol(buffer, NULL, 10);
  230. if (temp < INT8_MIN || temp > INT8_MAX)
  231. {
  232. FURI_LOG_E(TAG, "Value out of range for int8: %ld", temp);
  233. return false;
  234. }
  235. *value = (int8_t)temp;
  236. return true;
  237. }
  238. bool load_uint32(const char *path_name, uint32_t *value)
  239. {
  240. if (!path_name || !value)
  241. {
  242. FURI_LOG_E(TAG, "Invalid arguments to load_uint32");
  243. return false;
  244. }
  245. char buffer[32];
  246. if (!load_char(path_name, buffer, sizeof(buffer)))
  247. {
  248. FURI_LOG_E(TAG, "Failed to load uint32 from path: %s", path_name);
  249. return false;
  250. }
  251. *value = (uint32_t)strtoul(buffer, NULL, 10);
  252. return true;
  253. }
  254. bool load_player_context(PlayerContext *player_context)
  255. {
  256. if (!player_context)
  257. {
  258. FURI_LOG_E(TAG, "Player context is NULL");
  259. return false;
  260. }
  261. // Load each field and check for success
  262. // 1. Username (String)
  263. if (!load_char("player/username", player_context->username, sizeof(player_context->username)))
  264. {
  265. FURI_LOG_E(TAG, "Failed to load player username");
  266. return false;
  267. }
  268. // 2. Level (uint32_t)
  269. if (!load_uint32("player/level", &player_context->level))
  270. {
  271. FURI_LOG_E(TAG, "Failed to load player level");
  272. return false;
  273. }
  274. // 3. XP (uint32_t)
  275. if (!load_uint32("player/xp", &player_context->xp))
  276. {
  277. FURI_LOG_E(TAG, "Failed to load player xp");
  278. return false;
  279. }
  280. // 4. Health (uint32_t)
  281. if (!load_uint32("player/health", &player_context->health))
  282. {
  283. FURI_LOG_E(TAG, "Failed to load player health");
  284. return false;
  285. }
  286. // 5. Strength (uint32_t)
  287. if (!load_uint32("player/strength", &player_context->strength))
  288. {
  289. FURI_LOG_E(TAG, "Failed to load player strength");
  290. return false;
  291. }
  292. // 6. Max Health (uint32_t)
  293. if (!load_uint32("player/max_health", &player_context->max_health))
  294. {
  295. FURI_LOG_E(TAG, "Failed to load player max health");
  296. return false;
  297. }
  298. // 7. Health Regen (uint32_t)
  299. if (!load_uint32("player/health_regen", &player_context->health_regen))
  300. {
  301. FURI_LOG_E(TAG, "Failed to load player health regen");
  302. return false;
  303. }
  304. // 8. Elapsed Health Regen (float)
  305. if (!load_float("player/elapsed_health_regen", &player_context->elapsed_health_regen))
  306. {
  307. FURI_LOG_E(TAG, "Failed to load player elapsed health regen");
  308. return false;
  309. }
  310. // 9. Attack Timer (float)
  311. if (!load_float("player/attack_timer", &player_context->attack_timer))
  312. {
  313. FURI_LOG_E(TAG, "Failed to load player attack timer");
  314. return false;
  315. }
  316. // 10. Elapsed Attack Timer (float)
  317. if (!load_float("player/elapsed_attack_timer", &player_context->elapsed_attack_timer))
  318. {
  319. FURI_LOG_E(TAG, "Failed to load player elapsed attack timer");
  320. return false;
  321. }
  322. // 11. Direction (enum PlayerDirection)
  323. {
  324. char direction_str[2];
  325. if (!load_char("player/direction", direction_str, sizeof(direction_str)))
  326. {
  327. FURI_LOG_E(TAG, "Failed to load player direction");
  328. return false;
  329. }
  330. int direction_int = atoi(direction_str);
  331. switch (direction_int)
  332. {
  333. case 0:
  334. player_context->direction = PLAYER_UP;
  335. break;
  336. case 1:
  337. player_context->direction = PLAYER_DOWN;
  338. break;
  339. case 2:
  340. player_context->direction = PLAYER_LEFT;
  341. break;
  342. case 3:
  343. player_context->direction = PLAYER_RIGHT;
  344. break;
  345. default:
  346. FURI_LOG_E(TAG, "Invalid direction value: %d", direction_int);
  347. player_context->direction = PLAYER_RIGHT; // Default value
  348. break;
  349. }
  350. }
  351. // 12. State (enum PlayerState)
  352. {
  353. char state_str[2];
  354. if (!load_char("player/state", state_str, sizeof(state_str)))
  355. {
  356. FURI_LOG_E(TAG, "Failed to load player state");
  357. return false;
  358. }
  359. int state_int = atoi(state_str);
  360. switch (state_int)
  361. {
  362. case 0:
  363. player_context->state = PLAYER_IDLE;
  364. break;
  365. case 1:
  366. player_context->state = PLAYER_MOVING;
  367. break;
  368. case 2:
  369. player_context->state = PLAYER_ATTACKING;
  370. break;
  371. case 3:
  372. player_context->state = PLAYER_ATTACKED;
  373. break;
  374. case 4:
  375. player_context->state = PLAYER_DEAD; // Assuming '4' represents 'dead' or similar
  376. break;
  377. default:
  378. FURI_LOG_E(TAG, "Invalid state value: %d", state_int);
  379. player_context->state = PLAYER_IDLE; // Default value
  380. break;
  381. }
  382. }
  383. // 13. Start Position X (float)
  384. if (!load_float("player/start_position_x", &player_context->start_position.x))
  385. {
  386. FURI_LOG_E(TAG, "Failed to load player start position x");
  387. return false;
  388. }
  389. // 14. Start Position Y (float)
  390. if (!load_float("player/start_position_y", &player_context->start_position.y))
  391. {
  392. FURI_LOG_E(TAG, "Failed to load player start position y");
  393. return false;
  394. }
  395. // 15. dx (int8_t)
  396. if (!load_int8("player/dx", &player_context->dx))
  397. {
  398. FURI_LOG_E(TAG, "Failed to load player dx");
  399. return false;
  400. }
  401. // 16. dy (int8_t)
  402. if (!load_int8("player/dy", &player_context->dy))
  403. {
  404. FURI_LOG_E(TAG, "Failed to load player dy");
  405. return false;
  406. }
  407. return true;
  408. }
  409. static inline void furi_string_remove_str(FuriString *string, const char *needle)
  410. {
  411. furi_string_replace_str(string, needle, "", 0);
  412. }
  413. static FuriString *enemy_data(const FuriString *world_data)
  414. {
  415. size_t enemy_data_pos = furi_string_search_str(world_data, "enemy_data", 0);
  416. if (enemy_data_pos == FURI_STRING_FAILURE)
  417. {
  418. FURI_LOG_E("Game", "Failed to find enemy_data in world data");
  419. return NULL;
  420. }
  421. size_t bracket_start = furi_string_search_char(world_data, '[', enemy_data_pos);
  422. if (bracket_start == FURI_STRING_FAILURE)
  423. {
  424. FURI_LOG_E("Game", "Failed to find start of enemy_data array");
  425. return NULL;
  426. }
  427. size_t bracket_end = furi_string_search_char(world_data, ']', bracket_start);
  428. if (bracket_end == FURI_STRING_FAILURE)
  429. {
  430. FURI_LOG_E("Game", "Failed to find end of enemy_data array");
  431. return NULL;
  432. }
  433. FuriString *enemy_data_str = furi_string_alloc();
  434. if (!enemy_data_str)
  435. {
  436. FURI_LOG_E("Game", "Failed to allocate enemy_data string");
  437. return NULL;
  438. }
  439. furi_string_cat_str(enemy_data_str, "{\"enemy_data\":");
  440. {
  441. FuriString *temp_sub = furi_string_alloc();
  442. furi_string_set_strn(
  443. temp_sub,
  444. furi_string_get_cstr(world_data) + bracket_start,
  445. (bracket_end + 1) - bracket_start);
  446. furi_string_cat(enemy_data_str, temp_sub);
  447. furi_string_free(temp_sub);
  448. }
  449. furi_string_cat_str(enemy_data_str, "}");
  450. return enemy_data_str;
  451. }
  452. static FuriString *json_data(const FuriString *world_data)
  453. {
  454. size_t json_data_pos = furi_string_search_str(world_data, "json_data", 0);
  455. if (json_data_pos == FURI_STRING_FAILURE)
  456. {
  457. FURI_LOG_E("Game", "Failed to find json_data in world data");
  458. return NULL;
  459. }
  460. size_t bracket_start = furi_string_search_char(world_data, '[', json_data_pos);
  461. if (bracket_start == FURI_STRING_FAILURE)
  462. {
  463. FURI_LOG_E("Game", "Failed to find start of json_data array");
  464. return NULL;
  465. }
  466. size_t bracket_end = furi_string_search_char(world_data, ']', bracket_start);
  467. if (bracket_end == FURI_STRING_FAILURE)
  468. {
  469. FURI_LOG_E("Game", "Failed to find end of json_data array");
  470. return NULL;
  471. }
  472. FuriString *json_data_str = furi_string_alloc();
  473. if (!json_data_str)
  474. {
  475. FURI_LOG_E("Game", "Failed to allocate json_data string");
  476. return NULL;
  477. }
  478. furi_string_cat_str(json_data_str, "{\"json_data\":");
  479. {
  480. FuriString *temp_sub = furi_string_alloc();
  481. furi_string_set_strn(
  482. temp_sub,
  483. furi_string_get_cstr(world_data) + bracket_start,
  484. (bracket_end + 1) - bracket_start);
  485. furi_string_cat(json_data_str, temp_sub);
  486. furi_string_free(temp_sub);
  487. }
  488. furi_string_cat_str(json_data_str, "}");
  489. return json_data_str;
  490. }
  491. bool separate_world_data(char *id, FuriString *world_data)
  492. {
  493. if (!id || !world_data)
  494. {
  495. FURI_LOG_E("Game", "Invalid parameters");
  496. return false;
  497. }
  498. FuriString *file_json_data = json_data(world_data);
  499. if (!file_json_data || furi_string_size(file_json_data) == 0)
  500. {
  501. FURI_LOG_E("Game", "Failed to get json data in separate_world_data");
  502. return false;
  503. }
  504. // Save file_json_data to disk
  505. char directory_path[256];
  506. snprintf(directory_path, sizeof(directory_path),
  507. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s", id);
  508. Storage *storage = furi_record_open(RECORD_STORAGE);
  509. storage_common_mkdir(storage, directory_path);
  510. File *file = storage_file_alloc(storage);
  511. char file_path[256];
  512. snprintf(file_path, sizeof(file_path),
  513. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s/%s_json_data.json",
  514. id, id);
  515. if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
  516. {
  517. FURI_LOG_E("Game", "Failed to open file for writing: %s", file_path);
  518. storage_file_free(file);
  519. furi_record_close(RECORD_STORAGE);
  520. furi_string_free(file_json_data);
  521. return false;
  522. }
  523. size_t data_size = furi_string_size(file_json_data);
  524. if (storage_file_write(file, furi_string_get_cstr(file_json_data), data_size) != data_size)
  525. {
  526. FURI_LOG_E("Game", "Failed to write json_data");
  527. }
  528. storage_file_close(file);
  529. furi_string_replace_at(file_json_data, 0, 1, "");
  530. furi_string_replace_at(file_json_data, furi_string_size(file_json_data) - 1, 1, "");
  531. // include the comma at the end of the json_data array
  532. furi_string_cat_str(file_json_data, ",");
  533. furi_string_remove_str(world_data, furi_string_get_cstr(file_json_data));
  534. furi_string_free(file_json_data);
  535. FuriString *file_enemy_data = enemy_data(world_data);
  536. if (!file_enemy_data)
  537. {
  538. FURI_LOG_E("Game", "Failed to get enemy data");
  539. return false;
  540. }
  541. snprintf(file_path, sizeof(file_path),
  542. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s/%s_enemy_data.json",
  543. id, id);
  544. if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
  545. {
  546. FURI_LOG_E("Game", "Failed to open file for writing: %s", file_path);
  547. storage_file_free(file);
  548. furi_record_close(RECORD_STORAGE);
  549. furi_string_free(file_enemy_data);
  550. return false;
  551. }
  552. data_size = furi_string_size(file_enemy_data);
  553. if (storage_file_write(file, furi_string_get_cstr(file_enemy_data), data_size) != data_size)
  554. {
  555. FURI_LOG_E("Game", "Failed to write enemy_data");
  556. }
  557. // Clean up
  558. furi_string_free(file_enemy_data);
  559. storage_file_close(file);
  560. storage_file_free(file);
  561. furi_record_close(RECORD_STORAGE);
  562. return true;
  563. }