load.c 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. #include "load.h"
  2. #include <furi.h>
  3. #include <furi_hal.h>
  4. #include <furi/core/string.h>
  5. #include <storage/storage.h>
  6. #include <toolbox/stream/stream.h>
  7. #include <toolbox/stream/file_stream.h>
  8. // i overwrite it because this: https://github.com/flipperdevices/flipperzero-firmware/blob/a7b60bf2a610e1a364d26a925f3713c08d16d49c/applications/services/storage/storage_processing.c#L530
  9. // gets me gui thread instead of app thread
  10. #define MY_APP_DATA_PATH(path) \
  11. "/ext/apps_data/game_vexed" \
  12. "/" path
  13. char* assetLevels[] = {
  14. "01 Classic Levels",
  15. "02 Classic Levels 2",
  16. "03 Childrens Pack",
  17. "04 Confusion Pack",
  18. "05 Impossible Pack",
  19. "06 Panic Pack",
  20. "07 Twister Levels",
  21. "08 Variety Pack",
  22. "09 Variety II Pack"};
  23. //-----------------------------------------------------------------------------
  24. LevelData* alloc_level_data() {
  25. LevelData* ld = malloc(sizeof(LevelData));
  26. ld->solution = furi_string_alloc();
  27. ld->board = furi_string_alloc();
  28. ld->title = furi_string_alloc();
  29. ld->gamePar = 0;
  30. return ld;
  31. }
  32. void free_level_data(LevelData* ld) {
  33. furi_string_free(ld->solution);
  34. furi_string_free(ld->board);
  35. furi_string_free(ld->title);
  36. free(ld);
  37. }
  38. LevelSet* alloc_level_set() {
  39. LevelSet* ls = malloc(sizeof(LevelSet));
  40. ls->id = furi_string_alloc();
  41. ls->title = furi_string_alloc();
  42. ls->author = furi_string_alloc();
  43. ls->description = furi_string_alloc();
  44. ls->url = furi_string_alloc();
  45. ls->maxLevel = 0;
  46. return ls;
  47. }
  48. void free_level_set(LevelSet* ls) {
  49. furi_string_free(ls->id);
  50. furi_string_free(ls->title);
  51. furi_string_free(ls->author);
  52. furi_string_free(ls->description);
  53. furi_string_free(ls->url);
  54. free(ls);
  55. }
  56. //-----------------------------------------------------------------------------
  57. bool level_set_id_to_path(Storage* storage, FuriString* levelSetId, size_t maxSize, char* path) {
  58. memset(path, 0, maxSize);
  59. snprintf(path, maxSize - 1, "/assets/levels/%s.vxl", furi_string_get_cstr(levelSetId));
  60. if(storage_common_exists(storage, path)) {
  61. return true;
  62. }
  63. snprintf(
  64. path,
  65. maxSize - 1,
  66. "/ext/apps_data/game_vexed/extra_levels/%s.vxl",
  67. furi_string_get_cstr(levelSetId));
  68. if(storage_common_exists(storage, path)) {
  69. FURI_LOG_D(TAG, "Found extra level \"%s\"", path);
  70. return true;
  71. } else {
  72. FURI_LOG_E(TAG, "Extra level not found \"%s\"", path);
  73. }
  74. return false;
  75. }
  76. //-----------------------------------------------------------------------------
  77. void level_set_id_to_error_path(FuriString* levelSetId, size_t maxSize, char* path) {
  78. memset(path, 0, maxSize);
  79. snprintf(
  80. path,
  81. maxSize - 1,
  82. "/ext/apps_data/game_vexed/extra_levels/%s.error.txt",
  83. furi_string_get_cstr(levelSetId));
  84. }
  85. //-----------------------------------------------------------------------------
  86. void mark_set_invalid(Storage* storage, FuriString* levelSetId, FuriString* errorMsg) {
  87. FURI_LOG_D(
  88. TAG,
  89. "MARKING LEVEL AS BAD \"%s\" - REASON: %s",
  90. furi_string_get_cstr(levelSetId),
  91. furi_string_get_cstr(errorMsg));
  92. const int bufSize = 1024;
  93. char filePath[bufSize];
  94. level_set_id_to_error_path(levelSetId, bufSize, filePath);
  95. if(ensure_paths(storage)) {
  96. Stream* stream = file_stream_alloc(storage);
  97. if(file_stream_open(stream, filePath, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
  98. const char* data = furi_string_get_cstr(errorMsg);
  99. stream_write(stream, (uint8_t*)data, strlen(data));
  100. file_stream_close(stream);
  101. stream_free(stream);
  102. }
  103. }
  104. }
  105. //-----------------------------------------------------------------------------
  106. bool level_set_id_to_score_path(
  107. Storage* storage,
  108. FuriString* levelSetId,
  109. size_t maxSize,
  110. char* path) {
  111. memset(path, 0, maxSize);
  112. snprintf(
  113. path,
  114. maxSize - 1,
  115. "/ext/apps_data/game_vexed/scores/%s.sco",
  116. furi_string_get_cstr(levelSetId));
  117. if(storage_common_exists(storage, path)) {
  118. return true;
  119. }
  120. return false;
  121. }
  122. //-----------------------------------------------------------------------------
  123. int load_level_row(uint8_t* pb, const char* psz, const char* pszMax) {
  124. int cBlocks = 0;
  125. for(; psz < pszMax; psz++) {
  126. char ch = *psz;
  127. // Is this a number (non-moveable blocks?)
  128. int c = 0;
  129. while(ch >= '0' && ch <= '9') {
  130. c = c * 10 + ch - '0';
  131. psz++;
  132. if(psz >= pszMax) break;
  133. ch = *psz;
  134. }
  135. if(c != 0) {
  136. cBlocks += c;
  137. if(pb != NULL) {
  138. while(c != 0) {
  139. *pb++ = 9;
  140. c--;
  141. }
  142. }
  143. psz--;
  144. continue;
  145. }
  146. // Is this empty space?
  147. if(ch == '~') {
  148. cBlocks++;
  149. if(pb != NULL) *pb++ = 0;
  150. continue;
  151. }
  152. // This is a block type. Remember it verbatim
  153. if(ch < 'a' || ch > 'h') return -1;
  154. cBlocks++;
  155. if(pb != NULL) *pb++ = ch - 'a' + 1;
  156. }
  157. return cBlocks;
  158. }
  159. //-----------------------------------------------------------------------------
  160. bool parse_level_notation(const char* pszLevel, PlayGround* level) {
  161. uint8_t* pbLoad;
  162. const char* pszLast = pszLevel;
  163. bool fLoop = true;
  164. int cRows = 0;
  165. int cCols = -1;
  166. while(fLoop) {
  167. int cColsT;
  168. const char* pszNext = strchr(pszLast, '/');
  169. if(pszNext == NULL) {
  170. pszNext = pszLast + strlen(pszLast);
  171. fLoop = false;
  172. }
  173. cColsT = load_level_row(NULL, pszLast, pszNext);
  174. if(cCols == -1) {
  175. cCols = cColsT;
  176. } else if(cCols != cColsT) {
  177. return false;
  178. }
  179. cRows++;
  180. pszLast = pszNext + 1;
  181. }
  182. // Vexed wants these sizes
  183. if(cCols != SIZE_X || cRows != SIZE_Y) return false;
  184. // Load it this time
  185. memset(level, '\0', sizeof(uint8_t) * SIZE_X * SIZE_Y);
  186. pbLoad = level[0][0];
  187. pszLast = pszLevel;
  188. fLoop = true;
  189. while(fLoop) {
  190. // Find the end of this row
  191. const char* pszNext = strchr(pszLast, '/');
  192. if(pszNext == NULL) {
  193. pszNext = pszLast + strlen(pszLast);
  194. fLoop = false;
  195. }
  196. // Load the row
  197. load_level_row(pbLoad, pszLast, pszNext);
  198. // Next row...
  199. pbLoad += SIZE_X;
  200. pszLast = pszNext + 1;
  201. }
  202. return true;
  203. }
  204. //-----------------------------------------------------------------------------
  205. bool load_level(
  206. Storage* storage,
  207. FuriString* levelSetId,
  208. int level,
  209. LevelData* levelData,
  210. FuriString* errorMsg) {
  211. Stream* stream = file_stream_alloc(storage);
  212. FuriString* line = furi_string_alloc();
  213. FuriString* value;
  214. bool loaded = false;
  215. size_t errBufSize = 128;
  216. char errMsg[errBufSize];
  217. size_t bufSize = 512;
  218. char filePath[bufSize];
  219. if(!level_set_id_to_path(storage, levelSetId, bufSize, filePath)) {
  220. FURI_LOG_E(TAG, "LEVEL NOT FOUND! \"%s\"", filePath);
  221. furi_string_set(errorMsg, "Missing level file: ");
  222. furi_string_cat(errorMsg, filePath);
  223. }
  224. if(file_stream_open(stream, filePath, FSAM_READ, FSOM_OPEN_EXISTING)) {
  225. while(stream_read_line(stream, line)) {
  226. if(furi_string_start_with(line, "#")) continue;
  227. size_t level_no_sep = furi_string_search_char(line, ';', 0);
  228. if(level_no_sep == FURI_STRING_FAILURE) continue;
  229. size_t level_name_sep = furi_string_search_char(line, ';', level_no_sep + 1);
  230. if(level_name_sep == FURI_STRING_FAILURE) continue;
  231. size_t level_board_sep = furi_string_search_char(line, ';', level_name_sep + 1);
  232. if(level_board_sep == FURI_STRING_FAILURE) continue;
  233. value = furi_string_alloc_set(line);
  234. furi_string_left(value, level_no_sep);
  235. const char* value_raw = furi_string_get_cstr(value);
  236. int levelNo = atoi(value_raw);
  237. furi_string_free(value);
  238. if(levelNo == level) {
  239. loaded = true;
  240. furi_string_free(levelData->title);
  241. levelData->title = furi_string_alloc_set(line);
  242. furi_string_left(levelData->title, level_name_sep);
  243. furi_string_right(levelData->title, level_no_sep + 1);
  244. furi_string_trim(levelData->title, "\n\r\t");
  245. furi_string_free(levelData->board);
  246. levelData->board = furi_string_alloc_set(line);
  247. furi_string_left(levelData->board, level_board_sep);
  248. furi_string_right(levelData->board, level_name_sep + 1);
  249. furi_string_trim(levelData->board, "\n\r\t");
  250. furi_string_free(levelData->solution);
  251. levelData->solution = furi_string_alloc_set(line);
  252. furi_string_right(levelData->solution, level_board_sep + 1);
  253. furi_string_trim(levelData->solution, "\n\r\t");
  254. levelData->gamePar = strlen(furi_string_get_cstr(levelData->solution)) / 2;
  255. FURI_LOG_I(TAG, "LEVEL TITLE \"%s\"", furi_string_get_cstr(levelData->title));
  256. FURI_LOG_D(TAG, "LEVEL BOARD \"%s\"", furi_string_get_cstr(levelData->board));
  257. FURI_LOG_D(
  258. TAG, "LEVEL SOLUTION \"%s\"", furi_string_get_cstr(levelData->solution));
  259. break;
  260. } else {
  261. continue;
  262. }
  263. }
  264. furi_string_free(line);
  265. file_stream_close(stream);
  266. stream_free(stream);
  267. if(!loaded) {
  268. memset(errMsg, 0, errBufSize);
  269. snprintf(
  270. errMsg, errBufSize, "Cannot load level #%u from levelset %s", level, filePath);
  271. furi_string_set(errorMsg, errMsg);
  272. }
  273. return loaded;
  274. } else {
  275. return false;
  276. }
  277. }
  278. //-----------------------------------------------------------------------------
  279. // https://stackoverflow.com/a/53966346
  280. void btox(char* xp, const char* bb, int n) {
  281. const char xx[] = "0123456789ABCDEF";
  282. while(--n >= 0) xp[n] = xx[(bb[n >> 1] >> ((1 - (n & 1)) << 2)) & 0xF];
  283. }
  284. //-----------------------------------------------------------------------------
  285. void debug_dump_hex(const char* label, const char* data, int n) {
  286. char hexstr[n + 1];
  287. btox(hexstr, data, n);
  288. hexstr[n] = 0; /* Terminate! */
  289. FURI_LOG_D(TAG, "%s = %s", label, hexstr);
  290. }
  291. //-----------------------------------------------------------------------------
  292. bool load_set_scores(Storage* storage, FuriString* levelSetId, LevelScore* scores) {
  293. bool loaded = false;
  294. const size_t scoreSize = sizeof(LevelScore) * MAX_LEVELS_PER_SET;
  295. const size_t bufSize = 512;
  296. char filePath[bufSize];
  297. memset(scores, 0, scoreSize);
  298. if(!level_set_id_to_score_path(storage, levelSetId, bufSize, filePath)) {
  299. FURI_LOG_E(TAG, "SCORES FOR LEVEL NOT FOUND! \"%s\"", filePath);
  300. return false;
  301. }
  302. Stream* stream = file_stream_alloc(storage);
  303. if(file_stream_open(stream, filePath, FSAM_READ, FSOM_OPEN_EXISTING)) {
  304. size_t actualyRead = stream_read(stream, (uint8_t*)scores, scoreSize);
  305. if(scoreSize != actualyRead) {
  306. FURI_LOG_E(TAG, "Error while reading scores!");
  307. } else {
  308. debug_dump_hex("Scores", (char*)scores, scoreSize << 1);
  309. loaded = true;
  310. }
  311. file_stream_close(stream);
  312. stream_free(stream);
  313. }
  314. return loaded;
  315. }
  316. //-----------------------------------------------------------------------------
  317. bool save_set_scores(FuriString* levelSetId, LevelScore* scores) {
  318. bool saved = false;
  319. const size_t scoreSize = sizeof(LevelScore) * MAX_LEVELS_PER_SET;
  320. const size_t bufSize = 512;
  321. char filePath[bufSize];
  322. Storage* storage = furi_record_open(RECORD_STORAGE);
  323. if(ensure_paths(storage)) {
  324. //debug_dump_hex("Scores to write", (char*)scores, scoreSize << 1);
  325. if(!level_set_id_to_score_path(storage, levelSetId, bufSize, filePath)) {
  326. FURI_LOG_D(TAG, "Writing new scores \"%s\"", filePath);
  327. } else {
  328. FURI_LOG_D(TAG, "Overwriting scores \"%s\"", filePath);
  329. }
  330. Stream* stream = file_stream_alloc(storage);
  331. if(file_stream_open(stream, filePath, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
  332. size_t actualyWrote = stream_write(stream, (uint8_t*)scores, scoreSize);
  333. if(scoreSize != actualyWrote) {
  334. FURI_LOG_E(TAG, "Error while writing scores!");
  335. } else {
  336. saved = true;
  337. }
  338. file_stream_close(stream);
  339. stream_free(stream);
  340. }
  341. }
  342. furi_record_close(RECORD_STORAGE);
  343. return saved;
  344. }
  345. //-----------------------------------------------------------------------------
  346. bool load_level_set(
  347. Storage* storage,
  348. FuriString* levelSetId,
  349. LevelSet* levelSet,
  350. FuriString* errorMsg) {
  351. Stream* stream = file_stream_alloc(storage);
  352. FuriString* line = furi_string_alloc();
  353. FuriString* value;
  354. bool loaded = true;
  355. uint8_t levelCount = 0;
  356. size_t sep, level_no_sep, level_name_sep, level_board_sep;
  357. memset(levelSet->pars, 0, sizeof(uint8_t) * MAX_LEVELS_PER_SET);
  358. size_t errBufSize = 128;
  359. char errMsg[errBufSize];
  360. const size_t bufSize = 512;
  361. char filePath[bufSize];
  362. if(!level_set_id_to_path(storage, levelSetId, bufSize, filePath)) {
  363. FURI_LOG_E(TAG, "LEVEL NOT FOUND! \"%s\"", filePath);
  364. furi_string_set(errorMsg, "Missing level file: ");
  365. furi_string_cat(errorMsg, filePath);
  366. loaded = false;
  367. }
  368. load_set_scores(storage, levelSetId, levelSet->scores);
  369. furi_string_set(levelSet->id, levelSetId);
  370. furi_string_set(levelSet->title, levelSetId);
  371. int lineNo = 0;
  372. if(file_stream_open(stream, filePath, FSAM_READ, FSOM_OPEN_EXISTING)) {
  373. while(stream_read_line(stream, line)) {
  374. lineNo++;
  375. if(furi_string_start_with(line, "#")) {
  376. //size_t url_sep = furi_string_search(line, "URL:", 1);
  377. //size_t descr_sep = furi_string_search(line, "Description:", 1);
  378. sep = furi_string_search(line, "Author:", 1);
  379. if(sep != FURI_STRING_FAILURE) {
  380. value = furi_string_alloc_set(line);
  381. furi_string_right(value, sep + 7);
  382. furi_string_trim(value, " \t\n\r");
  383. FURI_LOG_D(TAG, "AUTHOR \"%s\"", furi_string_get_cstr(value));
  384. furi_string_set(levelSet->author, value);
  385. furi_string_free(value);
  386. }
  387. sep = furi_string_search(line, "URL:", 1);
  388. if(sep != FURI_STRING_FAILURE) {
  389. value = furi_string_alloc_set(line);
  390. furi_string_right(value, sep + 4);
  391. furi_string_trim(value, " \t\n\r");
  392. FURI_LOG_D(TAG, "URL \"%s\"", furi_string_get_cstr(value));
  393. furi_string_set(levelSet->url, value);
  394. furi_string_free(value);
  395. }
  396. sep = furi_string_search(line, "Description:", 1);
  397. if(sep != FURI_STRING_FAILURE) {
  398. value = furi_string_alloc_set(line);
  399. furi_string_right(value, sep + 12);
  400. furi_string_trim(value, " \t\n\r");
  401. FURI_LOG_D(TAG, "DESCR \"%s\"", furi_string_get_cstr(value));
  402. furi_string_set(levelSet->description, value);
  403. furi_string_free(value);
  404. }
  405. continue;
  406. }
  407. level_no_sep = furi_string_search_char(line, ';', 0);
  408. if(level_no_sep == FURI_STRING_FAILURE) {
  409. loaded = false;
  410. memset(errMsg, 0, errBufSize);
  411. snprintf(
  412. errMsg,
  413. errBufSize,
  414. "Invalid levelset format %s - missing level nr. at line no: %d",
  415. filePath,
  416. lineNo);
  417. furi_string_set(errorMsg, errMsg);
  418. continue;
  419. }
  420. level_name_sep = furi_string_search_char(line, ';', level_no_sep + 1);
  421. if(level_name_sep == FURI_STRING_FAILURE) {
  422. loaded = false;
  423. memset(errMsg, 0, errBufSize);
  424. snprintf(
  425. errMsg,
  426. errBufSize,
  427. "Invalid levelset format %s - missing board data at line no: %d",
  428. filePath,
  429. lineNo);
  430. furi_string_set(errorMsg, errMsg);
  431. continue;
  432. };
  433. level_board_sep = furi_string_search_char(line, ';', level_name_sep + 1);
  434. if(level_board_sep == FURI_STRING_FAILURE) {
  435. loaded = false;
  436. memset(errMsg, 0, errBufSize);
  437. snprintf(
  438. errMsg,
  439. errBufSize,
  440. "Invalid levelset format %s - missing solution at line no: %d",
  441. filePath,
  442. lineNo);
  443. furi_string_set(errorMsg, errMsg);
  444. continue;
  445. };
  446. value = furi_string_alloc_set(line);
  447. furi_string_left(value, level_no_sep);
  448. const char* value_raw = furi_string_get_cstr(value);
  449. int levelNo = atoi(value_raw);
  450. furi_string_free(value);
  451. if(levelNo < MAX_LEVELS_PER_SET) {
  452. value = furi_string_alloc_set(line);
  453. furi_string_right(value, level_board_sep + 1);
  454. furi_string_trim(value, "\n\r\t");
  455. levelSet->pars[levelCount] = (furi_string_size(value) / 2) % 256;
  456. furi_string_free(value);
  457. levelCount++;
  458. }
  459. }
  460. debug_dump_hex("Pars", (char*)levelSet->pars, sizeof(levelSet->pars) << 1);
  461. furi_string_free(line);
  462. file_stream_close(stream);
  463. stream_free(stream);
  464. levelSet->maxLevel = levelCount;
  465. return loaded;
  466. } else {
  467. memset(errMsg, 0, errBufSize);
  468. snprintf(errMsg, errBufSize, "Cannot read file %s", filePath);
  469. furi_string_set(errorMsg, errMsg);
  470. return false;
  471. }
  472. }
  473. //-----------------------------------------------------------------------------
  474. void load_all(File* f, FuriString* target) {
  475. size_t bufSize = 256;
  476. size_t bytesRead;
  477. char buf[bufSize];
  478. do {
  479. memset(buf, 0, sizeof(buf));
  480. bytesRead = storage_file_read(f, buf, bufSize - 1);
  481. FURI_LOG_D(TAG, "Bufer read: %s", buf);
  482. if(bytesRead > 0) {
  483. furi_string_cat_str(target, buf);
  484. FURI_LOG_D(TAG, "Furi: %s", furi_string_get_cstr(target));
  485. }
  486. } while(bytesRead > 0);
  487. }
  488. //-----------------------------------------------------------------------------
  489. bool load_last_level(FuriString* lastLevelSetId, uint8_t* levelNo) {
  490. FuriString* fbuf = furi_string_alloc();
  491. size_t pos;
  492. Storage* storage = furi_record_open(RECORD_STORAGE);
  493. File* file = storage_file_alloc(storage);
  494. bool loaded = false;
  495. if(!storage_common_exists(storage, MY_APP_DATA_PATH("scores/game.txt"))) {
  496. FURI_LOG_I(TAG, "No scores file (yet)");
  497. return false;
  498. }
  499. if(!storage_file_open(
  500. file, MY_APP_DATA_PATH("scores/game.txt"), FSAM_READ, FSOM_OPEN_EXISTING)) {
  501. FURI_LOG_E(TAG, "Failed to open file");
  502. return false;
  503. }
  504. load_all(file, fbuf);
  505. FURI_LOG_T(TAG, "Loaded scores: %s", furi_string_get_cstr(fbuf));
  506. if(furi_string_size(fbuf) > 0) {
  507. pos = furi_string_search_char(fbuf, '\n', 0);
  508. if(pos == FURI_STRING_FAILURE) {
  509. FURI_LOG_E(TAG, "ERROR - no file ID in scores file");
  510. } else {
  511. furi_string_set_n(lastLevelSetId, fbuf, 0, pos);
  512. furi_string_trim(lastLevelSetId);
  513. furi_string_right(fbuf, pos + 1);
  514. FURI_LOG_T(
  515. TAG,
  516. "AFTER LEVEL LOAD: [%s] [%s]",
  517. furi_string_get_cstr(lastLevelSetId),
  518. furi_string_get_cstr(fbuf));
  519. pos = furi_string_search_char(fbuf, '\n', 0);
  520. if(pos == FURI_STRING_FAILURE) {
  521. FURI_LOG_E(TAG, "ERROR - no level NO in scores file");
  522. } else {
  523. furi_string_left(fbuf, pos);
  524. int levelRead = atoi(furi_string_get_cstr(fbuf));
  525. *levelNo = (uint8_t)(levelRead % 256);
  526. loaded = true;
  527. }
  528. }
  529. }
  530. storage_file_close(file);
  531. storage_file_free(file);
  532. furi_record_close(RECORD_STORAGE);
  533. furi_string_free(fbuf);
  534. return loaded;
  535. }
  536. //-----------------------------------------------------------------------------
  537. bool ensure_paths(Storage* storage) {
  538. if(!storage_common_exists(storage, "/ext/apps_data")) {
  539. if(storage_common_mkdir(storage, "/ext/apps_data/") != FSE_OK) {
  540. FURI_LOG_E(TAG, "Cannot created /ext/apps_data/ dir");
  541. return false;
  542. }
  543. }
  544. if(!storage_common_exists(storage, "/ext/apps_data/game_vexed")) {
  545. if(storage_common_mkdir(storage, "/ext/apps_data/game_vexed") != FSE_OK) {
  546. FURI_LOG_E(TAG, "Cannot created /ext/apps_data/game_vexed dir");
  547. return false;
  548. }
  549. }
  550. if(!storage_common_exists(storage, MY_APP_DATA_PATH("scores"))) {
  551. if(storage_common_mkdir(storage, MY_APP_DATA_PATH("scores")) != FSE_OK) {
  552. FURI_LOG_E(TAG, "Cannot created scored data dir");
  553. return false;
  554. }
  555. }
  556. if(!storage_common_exists(storage, MY_APP_DATA_PATH("extra_levels"))) {
  557. if(storage_common_mkdir(storage, MY_APP_DATA_PATH("extra_levels")) != FSE_OK) {
  558. FURI_LOG_E(TAG, "Cannot create dir for extra_levels");
  559. return false;
  560. }
  561. }
  562. return true;
  563. }
  564. //-----------------------------------------------------------------------------
  565. bool save_last_level(FuriString* lastLevelSetId, uint8_t levelNo) {
  566. const int bufSize = 256;
  567. char buf[bufSize];
  568. memset(buf, 0, bufSize);
  569. snprintf(buf, sizeof(buf), "%s\n%u\n", furi_string_get_cstr(lastLevelSetId), levelNo);
  570. Storage* storage = furi_record_open(RECORD_STORAGE);
  571. if(ensure_paths(storage)) {
  572. File* file = storage_file_alloc(storage);
  573. if(!storage_file_open(
  574. file, MY_APP_DATA_PATH("scores/game.txt"), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
  575. FURI_LOG_E(TAG, "Failed to open file");
  576. return false;
  577. }
  578. if(!storage_file_write(file, buf, strlen(buf))) {
  579. FURI_LOG_E(TAG, "Failed to write to file");
  580. return false;
  581. }
  582. storage_file_close(file);
  583. storage_file_free(file);
  584. }
  585. furi_record_close(RECORD_STORAGE);
  586. return true;
  587. }
  588. //-----------------------------------------------------------------------------
  589. void delete_progress(LevelScore* scores) {
  590. Storage* storage = furi_record_open(RECORD_STORAGE);
  591. if(storage_common_exists(storage, MY_APP_DATA_PATH("scores/game.txt"))) {
  592. storage_simply_remove_recursive(storage, MY_APP_DATA_PATH("scores"));
  593. }
  594. furi_record_close(RECORD_STORAGE);
  595. memset(scores, 0, sizeof(LevelScore) * MAX_LEVELS_PER_SET);
  596. }
  597. //-----------------------------------------------------------------------------
  598. void init_level_list(LevelList* ls, int capacity) {
  599. ls->count = capacity;
  600. if(capacity > 0) {
  601. ls->ids = malloc(sizeof(FuriString*) * capacity);
  602. } else {
  603. ls->ids = NULL;
  604. }
  605. }
  606. //-----------------------------------------------------------------------------
  607. void free_level_list(LevelList* ls) {
  608. if(ls->count > 0) {
  609. for(int i = 0; i < ls->count; i++) {
  610. furi_string_free(ls->ids[i]);
  611. }
  612. free(ls->ids);
  613. ls->ids = NULL;
  614. }
  615. }
  616. //-----------------------------------------------------------------------------
  617. void list_extra_levels(Storage* storage, LevelList* levelList) {
  618. const int bufSize = 1024;
  619. char buf[bufSize];
  620. char buf2[bufSize];
  621. bool fileFound = false;
  622. FileInfo fileinfo;
  623. File* file = storage_file_alloc(storage);
  624. FuriString* levelId;
  625. size_t levelCount = 0, levelNo = 0;
  626. if(storage_dir_open(file, MY_APP_DATA_PATH("extra_levels"))) {
  627. do {
  628. memset(buf, 0, bufSize);
  629. fileFound = storage_dir_read(file, &fileinfo, buf, bufSize);
  630. if(fileFound) {
  631. levelId = furi_string_alloc_set(buf);
  632. if(furi_string_end_with_str(levelId, ".vxl")) {
  633. furi_string_left(levelId, furi_string_size(levelId) - 4);
  634. memset(buf2, 0, bufSize);
  635. level_set_id_to_error_path(levelId, bufSize, buf2);
  636. if(!storage_common_exists(storage, buf2)) levelCount++;
  637. }
  638. furi_string_free(levelId);
  639. }
  640. } while(fileFound);
  641. if(levelCount > 0) {
  642. init_level_list(levelList, levelCount);
  643. storage_dir_close(file);
  644. storage_file_free(file);
  645. file = storage_file_alloc(storage);
  646. storage_dir_open(file, MY_APP_DATA_PATH("extra_levels"));
  647. do {
  648. memset(buf, 0, bufSize);
  649. fileFound = storage_dir_read(file, &fileinfo, buf, bufSize);
  650. if(fileFound) {
  651. levelId = furi_string_alloc_set(buf);
  652. if(furi_string_end_with_str(levelId, ".vxl")) {
  653. furi_string_left(levelId, furi_string_size(levelId) - 4);
  654. memset(buf2, 0, bufSize);
  655. level_set_id_to_error_path(levelId, bufSize, buf2);
  656. if(!storage_common_exists(storage, buf2)) {
  657. FURI_LOG_D(
  658. TAG, "EXTRA LEVEL FILE \"%s\"", furi_string_get_cstr(levelId));
  659. levelList->ids[levelNo] = furi_string_alloc_set(levelId);
  660. levelNo++;
  661. } else {
  662. FURI_LOG_W(
  663. TAG,
  664. "CANNOT LOAD LEVEL \"%s\" - error file exists at %s",
  665. furi_string_get_cstr(levelId),
  666. buf2);
  667. }
  668. }
  669. furi_string_free(levelId);
  670. }
  671. } while(fileFound);
  672. }
  673. }
  674. storage_dir_close(file);
  675. storage_file_free(file);
  676. }