storage.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  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. #include <stdint.h>
  183. #include <stdbool.h>
  184. #include <stdio.h>
  185. #include <stdlib.h>
  186. #include <string.h>
  187. #include <limits.h>
  188. // Helper function to load a string safely
  189. bool load_string(const char *path_name, char *buffer, size_t buffer_size)
  190. {
  191. if (!path_name || !buffer || buffer_size == 0)
  192. {
  193. FURI_LOG_E(TAG, "Invalid arguments to load_string");
  194. return false;
  195. }
  196. // Initialize buffer to zero
  197. memset(buffer, 0, buffer_size);
  198. if (!load_char(path_name, buffer, buffer_size))
  199. {
  200. FURI_LOG_E(TAG, "Failed to load string from path: %s", path_name);
  201. return false;
  202. }
  203. // Ensure null-termination
  204. buffer[buffer_size - 1] = '\0';
  205. return true;
  206. }
  207. // Helper function to load an integer
  208. bool load_number(const char *path_name, int *value)
  209. {
  210. if (!path_name || !value)
  211. {
  212. FURI_LOG_E(TAG, "Invalid arguments to load_number");
  213. return false;
  214. }
  215. char buffer[32];
  216. memset(buffer, 0, sizeof(buffer)); // Initialize buffer
  217. if (!load_char(path_name, buffer, sizeof(buffer)))
  218. {
  219. FURI_LOG_E(TAG, "Failed to load number from path: %s", path_name);
  220. return false;
  221. }
  222. buffer[sizeof(buffer) - 1] = '\0'; // Ensure null-termination
  223. *value = atoi(buffer);
  224. return true;
  225. }
  226. // Helper function to load a float
  227. bool load_float(const char *path_name, float *value)
  228. {
  229. if (!path_name || !value)
  230. {
  231. FURI_LOG_E(TAG, "Invalid arguments to load_float");
  232. return false;
  233. }
  234. char buffer[32];
  235. memset(buffer, 0, sizeof(buffer)); // Initialize buffer
  236. if (!load_char(path_name, buffer, sizeof(buffer)))
  237. {
  238. FURI_LOG_E(TAG, "Failed to load float from path: %s", path_name);
  239. return false;
  240. }
  241. buffer[sizeof(buffer) - 1] = '\0'; // Ensure null-termination
  242. *value = strtof(buffer, NULL);
  243. return true;
  244. }
  245. // Helper function to load an int8_t
  246. bool load_int8(const char *path_name, int8_t *value)
  247. {
  248. if (!path_name || !value)
  249. {
  250. FURI_LOG_E(TAG, "Invalid arguments to load_int8");
  251. return false;
  252. }
  253. char buffer[32];
  254. memset(buffer, 0, sizeof(buffer)); // Initialize buffer
  255. if (!load_char(path_name, buffer, sizeof(buffer)))
  256. {
  257. FURI_LOG_E(TAG, "Failed to load int8 from path: %s", path_name);
  258. return false;
  259. }
  260. buffer[sizeof(buffer) - 1] = '\0'; // Ensure null-termination
  261. long temp = strtol(buffer, NULL, 10);
  262. if (temp < INT8_MIN || temp > INT8_MAX)
  263. {
  264. FURI_LOG_E(TAG, "Value out of range for int8: %ld", temp);
  265. return false;
  266. }
  267. *value = (int8_t)temp;
  268. return true;
  269. }
  270. // Helper function to load a uint32_t
  271. bool load_uint32(const char *path_name, uint32_t *value)
  272. {
  273. if (!path_name || !value)
  274. {
  275. FURI_LOG_E(TAG, "Invalid arguments to load_uint32");
  276. return false;
  277. }
  278. char buffer[32];
  279. memset(buffer, 0, sizeof(buffer)); // Initialize buffer
  280. if (!load_char(path_name, buffer, sizeof(buffer)))
  281. {
  282. FURI_LOG_E(TAG, "Failed to load uint32 from path: %s", path_name);
  283. return false;
  284. }
  285. buffer[sizeof(buffer) - 1] = '\0'; // Ensure null-termination
  286. *value = (uint32_t)strtoul(buffer, NULL, 10);
  287. return true;
  288. }
  289. bool load_player_context(PlayerContext *player_context)
  290. {
  291. if (!player_context)
  292. {
  293. FURI_LOG_E(TAG, "Player context is NULL");
  294. return false;
  295. }
  296. // Load each field and check for success
  297. // 1. Username (String)
  298. if (!load_string("player/username", player_context->username, sizeof(player_context->username)))
  299. {
  300. FURI_LOG_E(TAG, "Failed to load player username");
  301. return false;
  302. }
  303. // 2. Level (uint32_t)
  304. if (!load_uint32("player/level", &player_context->level))
  305. {
  306. FURI_LOG_E(TAG, "Failed to load player level");
  307. return false;
  308. }
  309. // 3. XP (uint32_t)
  310. if (!load_uint32("player/xp", &player_context->xp))
  311. {
  312. FURI_LOG_E(TAG, "Failed to load player xp");
  313. return false;
  314. }
  315. // 4. Health (uint32_t)
  316. if (!load_uint32("player/health", &player_context->health))
  317. {
  318. FURI_LOG_E(TAG, "Failed to load player health");
  319. return false;
  320. }
  321. // 5. Strength (uint32_t)
  322. if (!load_uint32("player/strength", &player_context->strength))
  323. {
  324. FURI_LOG_E(TAG, "Failed to load player strength");
  325. return false;
  326. }
  327. // 6. Max Health (uint32_t)
  328. if (!load_uint32("player/max_health", &player_context->max_health))
  329. {
  330. FURI_LOG_E(TAG, "Failed to load player max health");
  331. return false;
  332. }
  333. // 7. Health Regen (uint32_t)
  334. if (!load_uint32("player/health_regen", &player_context->health_regen))
  335. {
  336. FURI_LOG_E(TAG, "Failed to load player health regen");
  337. return false;
  338. }
  339. // 8. Elapsed Health Regen (float)
  340. if (!load_float("player/elapsed_health_regen", &player_context->elapsed_health_regen))
  341. {
  342. FURI_LOG_E(TAG, "Failed to load player elapsed health regen");
  343. return false;
  344. }
  345. // 9. Attack Timer (float)
  346. if (!load_float("player/attack_timer", &player_context->attack_timer))
  347. {
  348. FURI_LOG_E(TAG, "Failed to load player attack timer");
  349. return false;
  350. }
  351. // 10. Elapsed Attack Timer (float)
  352. if (!load_float("player/elapsed_attack_timer", &player_context->elapsed_attack_timer))
  353. {
  354. FURI_LOG_E(TAG, "Failed to load player elapsed attack timer");
  355. return false;
  356. }
  357. // 11. Direction (enum PlayerDirection)
  358. {
  359. int direction_int = 0;
  360. if (!load_number("player/direction", &direction_int))
  361. {
  362. FURI_LOG_E(TAG, "Failed to load player direction");
  363. return false;
  364. }
  365. switch (direction_int)
  366. {
  367. case 0:
  368. player_context->direction = PLAYER_UP;
  369. break;
  370. case 1:
  371. player_context->direction = PLAYER_DOWN;
  372. break;
  373. case 2:
  374. player_context->direction = PLAYER_LEFT;
  375. break;
  376. case 3:
  377. player_context->direction = PLAYER_RIGHT;
  378. break;
  379. default:
  380. FURI_LOG_E(TAG, "Invalid direction value: %d", direction_int);
  381. player_context->direction = PLAYER_RIGHT; // Default value
  382. break;
  383. }
  384. }
  385. // 12. State (enum PlayerState)
  386. {
  387. int state_int = 0;
  388. if (!load_number("player/state", &state_int))
  389. {
  390. FURI_LOG_E(TAG, "Failed to load player state");
  391. return false;
  392. }
  393. switch (state_int)
  394. {
  395. case 0:
  396. player_context->state = PLAYER_IDLE;
  397. break;
  398. case 1:
  399. player_context->state = PLAYER_MOVING;
  400. break;
  401. case 2:
  402. player_context->state = PLAYER_ATTACKING;
  403. break;
  404. case 3:
  405. player_context->state = PLAYER_ATTACKED;
  406. break;
  407. case 4:
  408. player_context->state = PLAYER_DEAD; // Assuming '4' represents 'dead' or similar
  409. break;
  410. default:
  411. FURI_LOG_E(TAG, "Invalid state value: %d", state_int);
  412. player_context->state = PLAYER_IDLE; // Default value
  413. break;
  414. }
  415. }
  416. // 13. Start Position X (float)
  417. if (!load_float("player/start_position_x", &player_context->start_position.x))
  418. {
  419. FURI_LOG_E(TAG, "Failed to load player start position x");
  420. return false;
  421. }
  422. // 14. Start Position Y (float)
  423. if (!load_float("player/start_position_y", &player_context->start_position.y))
  424. {
  425. FURI_LOG_E(TAG, "Failed to load player start position y");
  426. return false;
  427. }
  428. // 15. dx (int8_t)
  429. if (!load_int8("player/dx", &player_context->dx))
  430. {
  431. FURI_LOG_E(TAG, "Failed to load player dx");
  432. return false;
  433. }
  434. // 16. dy (int8_t)
  435. if (!load_int8("player/dy", &player_context->dy))
  436. {
  437. FURI_LOG_E(TAG, "Failed to load player dy");
  438. return false;
  439. }
  440. return true;
  441. }
  442. static inline void furi_string_remove_str(FuriString *string, const char *needle)
  443. {
  444. furi_string_replace_str(string, needle, "", 0);
  445. }
  446. static FuriString *enemy_data(const FuriString *world_data)
  447. {
  448. size_t enemy_data_pos = furi_string_search_str(world_data, "enemy_data", 0);
  449. if (enemy_data_pos == FURI_STRING_FAILURE)
  450. {
  451. FURI_LOG_E("Game", "Failed to find enemy_data in world data");
  452. return NULL;
  453. }
  454. size_t bracket_start = furi_string_search_char(world_data, '[', enemy_data_pos);
  455. if (bracket_start == FURI_STRING_FAILURE)
  456. {
  457. FURI_LOG_E("Game", "Failed to find start of enemy_data array");
  458. return NULL;
  459. }
  460. size_t bracket_end = furi_string_search_char(world_data, ']', bracket_start);
  461. if (bracket_end == FURI_STRING_FAILURE)
  462. {
  463. FURI_LOG_E("Game", "Failed to find end of enemy_data array");
  464. return NULL;
  465. }
  466. FuriString *enemy_data_str = furi_string_alloc();
  467. if (!enemy_data_str)
  468. {
  469. FURI_LOG_E("Game", "Failed to allocate enemy_data string");
  470. return NULL;
  471. }
  472. furi_string_cat_str(enemy_data_str, "{\"enemy_data\":");
  473. {
  474. FuriString *temp_sub = furi_string_alloc();
  475. furi_string_set_strn(
  476. temp_sub,
  477. furi_string_get_cstr(world_data) + bracket_start,
  478. (bracket_end + 1) - bracket_start);
  479. furi_string_cat(enemy_data_str, temp_sub);
  480. furi_string_free(temp_sub);
  481. }
  482. furi_string_cat_str(enemy_data_str, "}");
  483. return enemy_data_str;
  484. }
  485. static FuriString *json_data(const FuriString *world_data)
  486. {
  487. size_t json_data_pos = furi_string_search_str(world_data, "json_data", 0);
  488. if (json_data_pos == FURI_STRING_FAILURE)
  489. {
  490. FURI_LOG_E("Game", "Failed to find json_data in world data");
  491. return NULL;
  492. }
  493. size_t bracket_start = furi_string_search_char(world_data, '[', json_data_pos);
  494. if (bracket_start == FURI_STRING_FAILURE)
  495. {
  496. FURI_LOG_E("Game", "Failed to find start of json_data array");
  497. return NULL;
  498. }
  499. size_t bracket_end = furi_string_search_char(world_data, ']', bracket_start);
  500. if (bracket_end == FURI_STRING_FAILURE)
  501. {
  502. FURI_LOG_E("Game", "Failed to find end of json_data array");
  503. return NULL;
  504. }
  505. FuriString *json_data_str = furi_string_alloc();
  506. if (!json_data_str)
  507. {
  508. FURI_LOG_E("Game", "Failed to allocate json_data string");
  509. return NULL;
  510. }
  511. furi_string_cat_str(json_data_str, "{\"json_data\":");
  512. {
  513. FuriString *temp_sub = furi_string_alloc();
  514. furi_string_set_strn(
  515. temp_sub,
  516. furi_string_get_cstr(world_data) + bracket_start,
  517. (bracket_end + 1) - bracket_start);
  518. furi_string_cat(json_data_str, temp_sub);
  519. furi_string_free(temp_sub);
  520. }
  521. furi_string_cat_str(json_data_str, "}");
  522. return json_data_str;
  523. }
  524. bool separate_world_data(char *id, FuriString *world_data)
  525. {
  526. if (!id || !world_data)
  527. {
  528. FURI_LOG_E("Game", "Invalid parameters");
  529. return false;
  530. }
  531. FuriString *file_json_data = json_data(world_data);
  532. if (!file_json_data || furi_string_size(file_json_data) == 0)
  533. {
  534. FURI_LOG_E("Game", "Failed to get json data in separate_world_data");
  535. return false;
  536. }
  537. // Save file_json_data to disk
  538. char directory_path[256];
  539. snprintf(directory_path, sizeof(directory_path),
  540. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s", id);
  541. Storage *storage = furi_record_open(RECORD_STORAGE);
  542. storage_common_mkdir(storage, directory_path);
  543. File *file = storage_file_alloc(storage);
  544. char file_path[256];
  545. snprintf(file_path, sizeof(file_path),
  546. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s/%s_json_data.json",
  547. id, id);
  548. if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
  549. {
  550. FURI_LOG_E("Game", "Failed to open file for writing: %s", file_path);
  551. storage_file_free(file);
  552. furi_record_close(RECORD_STORAGE);
  553. furi_string_free(file_json_data);
  554. return false;
  555. }
  556. size_t data_size = furi_string_size(file_json_data);
  557. if (storage_file_write(file, furi_string_get_cstr(file_json_data), data_size) != data_size)
  558. {
  559. FURI_LOG_E("Game", "Failed to write json_data");
  560. }
  561. storage_file_close(file);
  562. furi_string_replace_at(file_json_data, 0, 1, "");
  563. furi_string_replace_at(file_json_data, furi_string_size(file_json_data) - 1, 1, "");
  564. // include the comma at the end of the json_data array
  565. furi_string_cat_str(file_json_data, ",");
  566. furi_string_remove_str(world_data, furi_string_get_cstr(file_json_data));
  567. furi_string_free(file_json_data);
  568. FuriString *file_enemy_data = enemy_data(world_data);
  569. if (!file_enemy_data)
  570. {
  571. FURI_LOG_E("Game", "Failed to get enemy data");
  572. return false;
  573. }
  574. snprintf(file_path, sizeof(file_path),
  575. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_world/worlds/%s/%s_enemy_data.json",
  576. id, id);
  577. if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
  578. {
  579. FURI_LOG_E("Game", "Failed to open file for writing: %s", file_path);
  580. storage_file_free(file);
  581. furi_record_close(RECORD_STORAGE);
  582. furi_string_free(file_enemy_data);
  583. return false;
  584. }
  585. data_size = furi_string_size(file_enemy_data);
  586. if (storage_file_write(file, furi_string_get_cstr(file_enemy_data), data_size) != data_size)
  587. {
  588. FURI_LOG_E("Game", "Failed to write enemy_data");
  589. }
  590. // Clean up
  591. furi_string_free(file_enemy_data);
  592. storage_file_close(file);
  593. storage_file_free(file);
  594. furi_record_close(RECORD_STORAGE);
  595. return true;
  596. }