animation_storage.c 16 KB

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