animation_storage.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. #include "animation_manager.h"
  2. #include "file_worker.h"
  3. #include "flipper_file.h"
  4. #include "furi/common_defines.h"
  5. #include "furi/memmgr.h"
  6. #include "furi/record.h"
  7. #include "animation_storage.h"
  8. #include "gui/canvas.h"
  9. #include "m-string.h"
  10. #include "pb.h"
  11. #include "pb_decode.h"
  12. #include "storage/filesystem_api_defines.h"
  13. #include "storage/storage.h"
  14. #include "animation_storage_i.h"
  15. #include <stdint.h>
  16. #include <gui/icon_i.h>
  17. // Read documentation before using it
  18. #include <furi/dangerous_defines.h>
  19. #define ANIMATION_META_FILE "meta.txt"
  20. #define ANIMATION_DIR "/ext/dolphin/animations"
  21. #define ANIMATION_MANIFEST_FILE ANIMATION_DIR "/manifest.txt"
  22. #define TAG "AnimationStorage"
  23. #define DEBUG_PB 0
  24. static void animation_storage_free_bubbles(BubbleAnimation* animation);
  25. static void animation_storage_free_frames(BubbleAnimation* animation);
  26. static void animation_storage_free_animation(BubbleAnimation** storage_animation);
  27. static BubbleAnimation* animation_storage_load_animation(const char* name);
  28. void animation_storage_fill_animation_list(StorageAnimationList_t* animation_list) {
  29. furi_assert(sizeof(StorageAnimationList_t) == sizeof(void*));
  30. furi_assert(!StorageAnimationList_size(*animation_list));
  31. Storage* storage = furi_record_open("storage");
  32. FlipperFile* file = flipper_file_alloc(storage);
  33. /* Forbid skipping fields */
  34. flipper_file_set_strict_mode(file, true);
  35. string_t header;
  36. string_init(header);
  37. do {
  38. uint32_t u32value;
  39. StorageAnimation* storage_animation = NULL;
  40. if(FSE_OK != storage_sd_status(storage)) break;
  41. if(!flipper_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break;
  42. if(!flipper_file_read_header(file, header, &u32value)) break;
  43. if(string_cmp_str(header, "Flipper Animation Manifest")) break;
  44. do {
  45. storage_animation = furi_alloc(sizeof(StorageAnimation));
  46. storage_animation->external = true;
  47. storage_animation->animation = NULL;
  48. if(!flipper_file_read_string(file, "Name", storage_animation->meta.name)) break;
  49. if(!flipper_file_read_uint32(file, "Min butthurt", &u32value, 1)) break;
  50. storage_animation->meta.min_butthurt = u32value;
  51. if(!flipper_file_read_uint32(file, "Max butthurt", &u32value, 1)) break;
  52. storage_animation->meta.max_butthurt = u32value;
  53. if(!flipper_file_read_uint32(file, "Min level", &u32value, 1)) break;
  54. storage_animation->meta.min_level = u32value;
  55. if(!flipper_file_read_uint32(file, "Max level", &u32value, 1)) break;
  56. storage_animation->meta.max_level = u32value;
  57. if(!flipper_file_read_uint32(file, "Weight", &u32value, 1)) break;
  58. storage_animation->meta.weight = u32value;
  59. StorageAnimationList_push_back(*animation_list, storage_animation);
  60. } while(1);
  61. animation_storage_free_storage_animation(&storage_animation);
  62. } while(0);
  63. string_clear(header);
  64. flipper_file_close(file);
  65. flipper_file_free(file);
  66. // add hard-coded animations
  67. for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) {
  68. StorageAnimationList_push_back(*animation_list, &StorageAnimationInternal[i]);
  69. }
  70. furi_record_close("storage");
  71. }
  72. StorageAnimation* animation_storage_find_animation(const char* name) {
  73. furi_assert(name);
  74. furi_assert(strlen(name));
  75. StorageAnimation* storage_animation = NULL;
  76. /* look through internal animations */
  77. for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) {
  78. if(!string_cmp_str(StorageAnimationInternal[i].meta.name, name)) {
  79. storage_animation = &StorageAnimationInternal[i];
  80. break;
  81. }
  82. }
  83. /* look through external animations */
  84. if(!storage_animation) {
  85. BubbleAnimation* animation = animation_storage_load_animation(name);
  86. if(animation != NULL) {
  87. storage_animation = furi_alloc(sizeof(StorageAnimation));
  88. storage_animation->animation = animation;
  89. storage_animation->external = true;
  90. /* meta data takes part in random animation selection, so it
  91. * doesn't need here as we exactly know which animation we need,
  92. * that's why we can ignore reading manifest.txt file
  93. * filling meta data by zeroes */
  94. storage_animation->meta.min_butthurt = 0;
  95. storage_animation->meta.max_butthurt = 0;
  96. storage_animation->meta.min_level = 0;
  97. storage_animation->meta.max_level = 0;
  98. storage_animation->meta.weight = 0;
  99. string_init_set_str(storage_animation->meta.name, name);
  100. }
  101. }
  102. return storage_animation;
  103. }
  104. StorageAnimationMeta* animation_storage_get_meta(StorageAnimation* storage_animation) {
  105. furi_assert(storage_animation);
  106. return &storage_animation->meta;
  107. }
  108. const BubbleAnimation*
  109. animation_storage_get_bubble_animation(StorageAnimation* storage_animation) {
  110. furi_assert(storage_animation);
  111. animation_storage_cache_animation(storage_animation);
  112. return storage_animation->animation;
  113. }
  114. void animation_storage_cache_animation(StorageAnimation* storage_animation) {
  115. furi_assert(storage_animation);
  116. if(storage_animation->external) {
  117. if(!storage_animation->animation) {
  118. storage_animation->animation =
  119. animation_storage_load_animation(string_get_cstr(storage_animation->meta.name));
  120. }
  121. }
  122. }
  123. static void animation_storage_free_animation(BubbleAnimation** animation) {
  124. furi_assert(animation);
  125. if(*animation) {
  126. animation_storage_free_bubbles(*animation);
  127. animation_storage_free_frames(*animation);
  128. free(*animation);
  129. *animation = NULL;
  130. }
  131. }
  132. void animation_storage_free_storage_animation(StorageAnimation** storage_animation) {
  133. furi_assert(storage_animation);
  134. furi_assert(*storage_animation);
  135. if((*storage_animation)->external) {
  136. animation_storage_free_animation((BubbleAnimation**)&(*storage_animation)->animation);
  137. string_clear((*storage_animation)->meta.name);
  138. free(*storage_animation);
  139. }
  140. *storage_animation = NULL;
  141. }
  142. static bool animation_storage_cast_align(string_t align_str, Align* align) {
  143. if(!string_cmp_str(align_str, "Bottom")) {
  144. *align = AlignBottom;
  145. } else if(!string_cmp_str(align_str, "Top")) {
  146. *align = AlignTop;
  147. } else if(!string_cmp_str(align_str, "Left")) {
  148. *align = AlignLeft;
  149. } else if(!string_cmp_str(align_str, "Right")) {
  150. *align = AlignRight;
  151. } else if(!string_cmp_str(align_str, "Center")) {
  152. *align = AlignCenter;
  153. } else {
  154. return false;
  155. }
  156. return true;
  157. }
  158. static void animation_storage_free_frames(BubbleAnimation* animation) {
  159. furi_assert(animation);
  160. furi_assert(animation->icons);
  161. const Icon** icons = animation->icons;
  162. uint16_t frames = animation->active_frames + animation->passive_frames;
  163. furi_assert(frames > 0);
  164. for(int i = 0; i < frames; ++i) {
  165. if(!icons[i]) continue;
  166. const Icon* icon = icons[i];
  167. free((void*)icon->frames[0]);
  168. free(icon->frames);
  169. free((void*)icon);
  170. for(int j = i; j < frames; ++j) {
  171. if(icons[j] == icon) {
  172. icons[j] = NULL;
  173. }
  174. }
  175. }
  176. free(animation->icons);
  177. animation->icons = NULL;
  178. }
  179. static Icon* animation_storage_alloc_icon(size_t frame_size) {
  180. Icon* icon = furi_alloc(sizeof(Icon));
  181. icon->frames = furi_alloc(sizeof(const uint8_t*));
  182. icon->frames[0] = furi_alloc(frame_size);
  183. return icon;
  184. }
  185. static void animation_storage_free_icon(Icon* icon) {
  186. free((void*)icon->frames[0]);
  187. free(icon->frames);
  188. free(icon);
  189. }
  190. static bool animation_storage_load_frames(
  191. Storage* storage,
  192. const char* name,
  193. BubbleAnimation* animation,
  194. uint32_t* frame_order,
  195. uint32_t width,
  196. uint32_t height) {
  197. furi_assert(!animation->icons);
  198. uint16_t frame_order_size = animation->passive_frames + animation->active_frames;
  199. bool frames_ok = false;
  200. animation->icons = furi_alloc(sizeof(const Icon*) * frame_order_size);
  201. File* file = storage_file_alloc(storage);
  202. FileInfo file_info;
  203. string_t filename;
  204. string_init(filename);
  205. size_t max_filesize = ROUND_UP_TO(width, 8) * height + 1;
  206. for(int i = 0; i < frame_order_size; ++i) {
  207. if(animation->icons[i]) continue;
  208. frames_ok = false;
  209. string_printf(filename, ANIMATION_DIR "/%s/frame_%d.bm", name, frame_order[i]);
  210. if(storage_common_stat(storage, string_get_cstr(filename), &file_info) != FSE_OK) break;
  211. if(file_info.size > max_filesize) {
  212. FURI_LOG_E(
  213. TAG,
  214. "Filesize %d, max: %d (width %d, height %d)",
  215. file_info.size,
  216. max_filesize,
  217. width,
  218. height);
  219. break;
  220. }
  221. if(!storage_file_open(file, string_get_cstr(filename), FSAM_READ, FSOM_OPEN_EXISTING)) {
  222. FURI_LOG_E(TAG, "Can't open file \'%s\'", string_get_cstr(filename));
  223. break;
  224. }
  225. Icon* icon = animation_storage_alloc_icon(file_info.size);
  226. if(storage_file_read(file, (void*)icon->frames[0], file_info.size) != file_info.size) {
  227. FURI_LOG_E(TAG, "Read failed: \'%s\'", string_get_cstr(filename));
  228. animation_storage_free_icon(icon);
  229. break;
  230. }
  231. storage_file_close(file);
  232. FURI_CONST_ASSIGN(icon->frame_count, 1);
  233. FURI_CONST_ASSIGN(icon->frame_rate, 0);
  234. FURI_CONST_ASSIGN(icon->height, height);
  235. FURI_CONST_ASSIGN(icon->width, width);
  236. /* Claim 1 allocation for 1 files blob and several links to it */
  237. for(int j = i; j < frame_order_size; ++j) {
  238. if(frame_order[i] == frame_order[j]) {
  239. animation->icons[j] = icon;
  240. }
  241. }
  242. frames_ok = true;
  243. }
  244. if(!frames_ok) {
  245. FURI_LOG_E(
  246. TAG,
  247. "Load \'%s\' failed, %dx%d, size: %d",
  248. string_get_cstr(filename),
  249. width,
  250. height,
  251. file_info.size);
  252. animation_storage_free_frames(animation);
  253. animation->icons = NULL;
  254. } else {
  255. for(int i = 0; i < frame_order_size; ++i) {
  256. furi_check(animation->icons[i]);
  257. furi_check(animation->icons[i]->frames[0]);
  258. }
  259. }
  260. storage_file_free(file);
  261. string_clear(filename);
  262. return frames_ok;
  263. }
  264. static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFile* ff) {
  265. uint32_t u32value;
  266. string_t str;
  267. string_init(str);
  268. bool success = false;
  269. furi_assert(!animation->frame_bubbles);
  270. do {
  271. if(!flipper_file_read_uint32(ff, "Bubble slots", &u32value, 1)) break;
  272. if(u32value > 20) break;
  273. animation->frame_bubbles_count = u32value;
  274. if(animation->frame_bubbles_count == 0) {
  275. animation->frame_bubbles = NULL;
  276. success = true;
  277. break;
  278. }
  279. animation->frame_bubbles =
  280. furi_alloc(sizeof(FrameBubble*) * animation->frame_bubbles_count);
  281. uint32_t current_slot = 0;
  282. for(int i = 0; i < animation->frame_bubbles_count; ++i) {
  283. animation->frame_bubbles[i] = furi_alloc(sizeof(FrameBubble));
  284. }
  285. FrameBubble* bubble = animation->frame_bubbles[0];
  286. int8_t index = -1;
  287. for(;;) {
  288. if(!flipper_file_read_uint32(ff, "Slot", &current_slot, 1)) break;
  289. if((current_slot != 0) && (index == -1)) break;
  290. if(current_slot == index) {
  291. bubble->next_bubble = furi_alloc(sizeof(FrameBubble));
  292. bubble = bubble->next_bubble;
  293. } else if(current_slot == index + 1) {
  294. ++index;
  295. bubble = animation->frame_bubbles[index];
  296. } else {
  297. /* slots have to start from 0, be ascending sorted, and
  298. * have exact number of slots as specified in "Bubble slots" */
  299. break;
  300. }
  301. if(index >= animation->frame_bubbles_count) break;
  302. if(!flipper_file_read_uint32(ff, "X", &u32value, 1)) break;
  303. bubble->bubble.x = u32value;
  304. if(!flipper_file_read_uint32(ff, "Y", &u32value, 1)) break;
  305. bubble->bubble.y = u32value;
  306. if(!flipper_file_read_string(ff, "Text", str)) break;
  307. if(string_size(str) > 100) break;
  308. string_replace_all_str(str, "\\n", "\n");
  309. bubble->bubble.str = furi_alloc(string_size(str) + 1);
  310. strcpy((char*)bubble->bubble.str, string_get_cstr(str));
  311. if(!flipper_file_read_string(ff, "AlignH", str)) break;
  312. if(!animation_storage_cast_align(str, &bubble->bubble.horizontal)) break;
  313. if(!flipper_file_read_string(ff, "AlignV", str)) break;
  314. if(!animation_storage_cast_align(str, &bubble->bubble.vertical)) break;
  315. if(!flipper_file_read_uint32(ff, "StartFrame", &u32value, 1)) break;
  316. bubble->starts_at_frame = u32value;
  317. if(!flipper_file_read_uint32(ff, "EndFrame", &u32value, 1)) break;
  318. bubble->ends_at_frame = u32value;
  319. }
  320. success = (index + 1) == animation->frame_bubbles_count;
  321. } while(0);
  322. if(!success) {
  323. if(animation->frame_bubbles) {
  324. FURI_LOG_E(TAG, "Failed to load animation bubbles");
  325. animation_storage_free_bubbles(animation);
  326. }
  327. }
  328. string_clear(str);
  329. return success;
  330. }
  331. static BubbleAnimation* animation_storage_load_animation(const char* name) {
  332. furi_assert(name);
  333. BubbleAnimation* animation = furi_alloc(sizeof(BubbleAnimation));
  334. uint32_t height = 0;
  335. uint32_t width = 0;
  336. uint32_t* u32array = NULL;
  337. Storage* storage = furi_record_open("storage");
  338. FlipperFile* ff = flipper_file_alloc(storage);
  339. /* Forbid skipping fields */
  340. flipper_file_set_strict_mode(ff, true);
  341. string_t str;
  342. string_init(str);
  343. animation->frame_bubbles = NULL;
  344. bool success = false;
  345. do {
  346. uint32_t u32value;
  347. if(FSE_OK != storage_sd_status(storage)) break;
  348. string_printf(str, ANIMATION_DIR "/%s/" ANIMATION_META_FILE, name);
  349. if(!flipper_file_open_existing(ff, string_get_cstr(str))) break;
  350. if(!flipper_file_read_header(ff, str, &u32value)) break;
  351. if(string_cmp_str(str, "Flipper Animation")) break;
  352. if(!flipper_file_read_uint32(ff, "Width", &width, 1)) break;
  353. if(!flipper_file_read_uint32(ff, "Height", &height, 1)) break;
  354. if(!flipper_file_read_uint32(ff, "Passive frames", &u32value, 1)) break;
  355. animation->passive_frames = u32value;
  356. if(!flipper_file_read_uint32(ff, "Active frames", &u32value, 1)) break;
  357. animation->active_frames = u32value;
  358. uint8_t frames = animation->passive_frames + animation->active_frames;
  359. u32array = furi_alloc(sizeof(uint32_t) * frames);
  360. if(!flipper_file_read_uint32(ff, "Frames order", u32array, frames)) break;
  361. /* passive and active frames must be loaded up to this point */
  362. if(!animation_storage_load_frames(storage, name, animation, u32array, width, height))
  363. break;
  364. if(!flipper_file_read_uint32(ff, "Active cycles", &u32value, 1)) break;
  365. animation->active_cycles = u32value;
  366. if(!flipper_file_read_uint32(ff, "Frame rate", &u32value, 1)) break;
  367. animation->frame_rate = u32value;
  368. if(!flipper_file_read_uint32(ff, "Duration", &u32value, 1)) break;
  369. animation->duration = u32value;
  370. if(!flipper_file_read_uint32(ff, "Active cooldown", &u32value, 1)) break;
  371. animation->active_cooldown = u32value;
  372. if(!animation_storage_load_bubbles(animation, ff)) break;
  373. success = true;
  374. } while(0);
  375. string_clear(str);
  376. flipper_file_close(ff);
  377. flipper_file_free(ff);
  378. if(u32array) {
  379. free(u32array);
  380. }
  381. if(!success) {
  382. free(animation);
  383. animation = NULL;
  384. }
  385. return animation;
  386. }
  387. static void animation_storage_free_bubbles(BubbleAnimation* animation) {
  388. if(!animation->frame_bubbles) return;
  389. for(int i = 0; i < animation->frame_bubbles_count;) {
  390. FrameBubble** bubble = &animation->frame_bubbles[i];
  391. if((*bubble) == NULL) break;
  392. while((*bubble)->next_bubble != NULL) {
  393. bubble = &(*bubble)->next_bubble;
  394. }
  395. if((*bubble)->bubble.str) {
  396. free((void*)(*bubble)->bubble.str);
  397. }
  398. if((*bubble) == animation->frame_bubbles[i]) {
  399. ++i;
  400. }
  401. free(*bubble);
  402. *bubble = NULL;
  403. }
  404. free(animation->frame_bubbles);
  405. animation->frame_bubbles = NULL;
  406. }