MX %!s(int64=2) %!d(string=hai) anos
pai
achega
2ffce83350
Modificáronse 100 ficheiros con 7642 adicións e 0 borrados
  1. 191 0
      non_catalog_apps/meal_pager/.clang-format
  2. 65 0
      non_catalog_apps/meal_pager/README.md
  3. 14 0
      non_catalog_apps/meal_pager/application.fam
  4. 9 0
      non_catalog_apps/meal_pager/docs/README.md
  5. 29 0
      non_catalog_apps/meal_pager/docs/changelog.md
  6. 80 0
      non_catalog_apps/meal_pager/helpers/meal_pager_calc.c
  7. 12 0
      non_catalog_apps/meal_pager/helpers/meal_pager_calc.h
  8. 65 0
      non_catalog_apps/meal_pager/helpers/meal_pager_custom_event.h
  9. 34 0
      non_catalog_apps/meal_pager/helpers/meal_pager_haptic.c
  10. 10 0
      non_catalog_apps/meal_pager/helpers/meal_pager_haptic.h
  11. 55 0
      non_catalog_apps/meal_pager/helpers/meal_pager_led.c
  12. 13 0
      non_catalog_apps/meal_pager/helpers/meal_pager_led.h
  13. 25 0
      non_catalog_apps/meal_pager/helpers/meal_pager_speaker.c
  14. 8 0
      non_catalog_apps/meal_pager/helpers/meal_pager_speaker.h
  15. 201 0
      non_catalog_apps/meal_pager/helpers/meal_pager_storage.c
  16. 36 0
      non_catalog_apps/meal_pager/helpers/meal_pager_storage.h
  17. 130 0
      non_catalog_apps/meal_pager/helpers/retekess/meal_pager_retekess_t119.c
  18. 7 0
      non_catalog_apps/meal_pager/helpers/retekess/meal_pager_retekess_t119.h
  19. 115 0
      non_catalog_apps/meal_pager/helpers/retekess/meal_pager_retekess_td157.c
  20. 7 0
      non_catalog_apps/meal_pager/helpers/retekess/meal_pager_retekess_td157.h
  21. 77 0
      non_catalog_apps/meal_pager/helpers/subghz/subghz.c
  22. 9 0
      non_catalog_apps/meal_pager/helpers/subghz/subghz.h
  23. 14 0
      non_catalog_apps/meal_pager/helpers/subghz/subghz_error_type.h
  24. 260 0
      non_catalog_apps/meal_pager/helpers/subghz/subghz_i.c
  25. 83 0
      non_catalog_apps/meal_pager/helpers/subghz/subghz_i.h
  26. 630 0
      non_catalog_apps/meal_pager/helpers/subghz/subghz_txrx.c
  27. 336 0
      non_catalog_apps/meal_pager/helpers/subghz/subghz_txrx.h
  28. 29 0
      non_catalog_apps/meal_pager/helpers/subghz/subghz_txrx_i.h
  29. 90 0
      non_catalog_apps/meal_pager/helpers/subghz/subghz_types.h
  30. BIN=BIN
      non_catalog_apps/meal_pager/icons/meal_pager_10px.png
  31. 145 0
      non_catalog_apps/meal_pager/meal_pager.c
  32. 2 0
      non_catalog_apps/meal_pager/meal_pager.h
  33. 96 0
      non_catalog_apps/meal_pager/meal_pager_i.h
  34. 30 0
      non_catalog_apps/meal_pager/scenes/meal_pager_scene.c
  35. 29 0
      non_catalog_apps/meal_pager/scenes/meal_pager_scene.h
  36. 4 0
      non_catalog_apps/meal_pager/scenes/meal_pager_scene_config.h
  37. 81 0
      non_catalog_apps/meal_pager/scenes/meal_pager_scene_menu.c
  38. 255 0
      non_catalog_apps/meal_pager/scenes/meal_pager_scene_settings.c
  39. 55 0
      non_catalog_apps/meal_pager/scenes/meal_pager_scene_startscreen.c
  40. 100 0
      non_catalog_apps/meal_pager/scenes/meal_pager_scene_transmit.c
  41. 125 0
      non_catalog_apps/meal_pager/views/meal_pager_startscreen.c
  42. 19 0
      non_catalog_apps/meal_pager/views/meal_pager_startscreen.h
  43. 182 0
      non_catalog_apps/meal_pager/views/meal_pager_transmit.c
  44. 24 0
      non_catalog_apps/meal_pager/views/meal_pager_transmit.h
  45. 34 0
      non_catalog_apps/vexed/AUTHORS.md
  46. 25 0
      non_catalog_apps/vexed/CHANGELOG.md
  47. 336 0
      non_catalog_apps/vexed/LICENSE.md
  48. 53 0
      non_catalog_apps/vexed/README.md
  49. 5 0
      non_catalog_apps/vexed/SHORTDESC.md
  50. 15 0
      non_catalog_apps/vexed/application.fam
  51. 62 0
      non_catalog_apps/vexed/assets/levels/01 Classic Levels.vxl
  52. 62 0
      non_catalog_apps/vexed/assets/levels/02 Classic Levels 2.vxl
  53. 63 0
      non_catalog_apps/vexed/assets/levels/03 Childrens Pack.vxl
  54. 63 0
      non_catalog_apps/vexed/assets/levels/04 Confusion Pack.vxl
  55. 63 0
      non_catalog_apps/vexed/assets/levels/05 Impossible Pack.vxl
  56. 63 0
      non_catalog_apps/vexed/assets/levels/06 Panic Pack.vxl
  57. 63 0
      non_catalog_apps/vexed/assets/levels/07 Twister Levels.vxl
  58. 63 0
      non_catalog_apps/vexed/assets/levels/08 Variety Pack.vxl
  59. 63 0
      non_catalog_apps/vexed/assets/levels/09 Variety II Pack.vxl
  60. 40 0
      non_catalog_apps/vexed/common.h
  61. BIN=BIN
      non_catalog_apps/vexed/docs/img/custom_levels_dir.png
  62. BIN=BIN
      non_catalog_apps/vexed/docs/img/custom_second_btn.png
  63. BIN=BIN
      non_catalog_apps/vexed/docs/img/direction.gif
  64. BIN=BIN
      non_catalog_apps/vexed/docs/img/explosion.gif
  65. BIN=BIN
      non_catalog_apps/vexed/docs/img/levelset_info.png
  66. BIN=BIN
      non_catalog_apps/vexed/docs/img/playtru.gif
  67. BIN=BIN
      non_catalog_apps/vexed/docs/img/selection.gif
  68. BIN=BIN
      non_catalog_apps/vexed/docs/img/space_docs.png
  69. 143 0
      non_catalog_apps/vexed/docs/level_format.md
  70. 1014 0
      non_catalog_apps/vexed/draw.c
  71. 26 0
      non_catalog_apps/vexed/draw.h
  72. 487 0
      non_catalog_apps/vexed/events.c
  73. 5 0
      non_catalog_apps/vexed/events.h
  74. 160 0
      non_catalog_apps/vexed/fonts.c
  75. 9 0
      non_catalog_apps/vexed/fonts.h
  76. 656 0
      non_catalog_apps/vexed/game.c
  77. 174 0
      non_catalog_apps/vexed/game.h
  78. 114 0
      non_catalog_apps/vexed/game_vexed.c
  79. BIN=BIN
      non_catalog_apps/vexed/game_vexed.png
  80. BIN=BIN
      non_catalog_apps/vexed/images/ButtonLeft_4x7.png
  81. BIN=BIN
      non_catalog_apps/vexed/images/ButtonRight_4x7.png
  82. BIN=BIN
      non_catalog_apps/vexed/images/a.png
  83. BIN=BIN
      non_catalog_apps/vexed/images/alt_d.png
  84. BIN=BIN
      non_catalog_apps/vexed/images/arr_l.png
  85. BIN=BIN
      non_catalog_apps/vexed/images/arr_r.png
  86. BIN=BIN
      non_catalog_apps/vexed/images/b.png
  87. BIN=BIN
      non_catalog_apps/vexed/images/back_btn_10x8.png
  88. BIN=BIN
      non_catalog_apps/vexed/images/c.png
  89. BIN=BIN
      non_catalog_apps/vexed/images/d.png
  90. BIN=BIN
      non_catalog_apps/vexed/images/e.png
  91. BIN=BIN
      non_catalog_apps/vexed/images/f.png
  92. BIN=BIN
      non_catalog_apps/vexed/images/g.png
  93. BIN=BIN
      non_catalog_apps/vexed/images/h.png
  94. BIN=BIN
      non_catalog_apps/vexed/images/hint_1.png
  95. BIN=BIN
      non_catalog_apps/vexed/images/hint_2.png
  96. BIN=BIN
      non_catalog_apps/vexed/images/hint_3.png
  97. BIN=BIN
      non_catalog_apps/vexed/images/hint_4.png
  98. BIN=BIN
      non_catalog_apps/vexed/images/ico_check.png
  99. BIN=BIN
      non_catalog_apps/vexed/images/ico_hist.png
  100. BIN=BIN
      non_catalog_apps/vexed/images/ico_home.png

+ 191 - 0
non_catalog_apps/meal_pager/.clang-format

@@ -0,0 +1,191 @@
+---
+Language:        Cpp
+AccessModifierOffset: -4
+AlignAfterOpenBracket: AlwaysBreak
+AlignArrayOfStructures: None
+AlignConsecutiveMacros: None
+AlignConsecutiveAssignments: None
+AlignConsecutiveBitFields: None
+AlignConsecutiveDeclarations: None
+AlignEscapedNewlines: Left
+AlignOperands:   Align
+AlignTrailingComments: false
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortEnumsOnASingleLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: WithoutElse
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+AttributeMacros:
+  - __capability
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+  AfterCaseLabel:  false
+  AfterClass:      false
+  AfterControlStatement: Never
+  AfterEnum:       false
+  AfterFunction:   false
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     false
+  AfterUnion:      false
+  AfterExternBlock: false
+  BeforeCatch:     false
+  BeforeElse:      false
+  BeforeLambdaBody: false
+  BeforeWhile:     false
+  IndentBraces:    false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: true
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: false
+ColumnLimit:     99
+CommentPragmas:  '^ IWYU pragma:'
+QualifierAlignment: Leave
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+DisableFormat:   false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+PackConstructorInitializers: BinPack
+BasedOnStyle:    ''
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+AllowAllConstructorInitializersOnNextLine: true
+FixNamespaceComments: false
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IfMacros:
+  - KJ_IF_MAYBE
+IncludeBlocks:   Preserve
+IncludeCategories:
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
+    Priority:        3
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+IncludeIsMainRegex: '(Test)?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseLabels: false
+IndentCaseBlocks: false
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentExternBlock: AfterExternBlock
+IndentRequires:  false
+IndentWidth:     4
+IndentWrappedFunctionNames: true
+InsertTrailingCommas: None
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 4
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 10
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakString: 10
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 100
+PenaltyReturnTypeOnItsOwnLine: 60
+PenaltyIndentedWhitespace: 0
+PointerAlignment: Left
+PPIndentWidth:   -1
+ReferenceAlignment: Pointer
+ReflowComments:  false
+RemoveBracesLLVM: false
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SortIncludes:    Never
+SortJavaStaticImport: Before
+SortUsingDeclarations: false
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: Never
+SpaceBeforeParensOptions:
+  AfterControlStatements: false
+  AfterForeachMacros: false
+  AfterFunctionDefinitionName: false
+  AfterFunctionDeclarationName: false
+  AfterIfMacros:   false
+  AfterOverloadedOperator: false
+  BeforeNonEmptyParentheses: false
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  Never
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+  Minimum:         1
+  Maximum:         -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+BitFieldColonSpacing: Both
+Standard:        c++03
+StatementAttributeLikeMacros:
+  - Q_EMIT
+StatementMacros:
+  - Q_UNUSED
+  - QT_REQUIRE_VERSION
+TabWidth:        4
+UseCRLF:         false
+UseTab:          Never
+WhitespaceSensitiveMacros:
+  - STRINGIZE
+  - PP_STRINGIZE
+  - BOOST_PP_STRINGIZE
+  - NS_SWIFT_NAME
+  - CF_SWIFT_NAME
+...
+

+ 65 - 0
non_catalog_apps/meal_pager/README.md

@@ -0,0 +1,65 @@
+# Flipper Zero Meal Pager Tool
+
+## What this is?
+This app triggers restaurant pagers in a brute force manner, useful to test if devices are still functional. 
+<br><br>
+
+## Supported Pagers
+- Retekess T119
+- Retekess TD157
+- Retekess TD165 (coming soon)
+- Retekess TD174 (coming soon)
+
+### Features
+- Select range of stations (needs improving for full range)
+- Select range of pagers (needs improving for full range)
+
+## How to install on Flipper Zero
+- If you do not have one, download a firmware onto your PC via git<br>
+- Plug your Flipper Zero in via USB. <br>
+- Copy the contents of this folder into the applications_user folder of your firmware. <br> 
+
+## What does the blinking mean
+
+### Yellow Blinking LED
+Means that the SubGhz Code is being generated for the configured range
+
+### Purple Blinking LED
+Means that SubGhz Signals are being sent
+
+## Settings Explanation
+
+### Pager Type
+Sets the current Pager type your targeting. Each model uses a different encoding, you cannot trigger multiple models at once
+
+### First Station / Last Station
+Each Station has a unique id. This is important so many stations can run in one location without conflicting each other. Use these settings to define a range of stations to trigger
+
+I do not recommend a lager range than 10
+
+### Fist Pager / Last Pager
+The range of numbers on the pagers to be triggered. Most stations don't have many pagers, so a range of 0 - 31 probably is enough for all.
+
+### Signal Repeat
+How many times a single pager trigger is sent. Usually a signal is sent multiple times to combat radio noise that can cause a signal not to be recognised. 
+This is the total number of signals, so a setting of 0 will not send anything. More signals take longer, less have a higher risk of no effect. Set a number between 1 and 10. 
+
+
+## Can this Brute-Force Attacks
+This is a Proof-of-Concept. In Theory it could, but I wouldn't recommend trying it. Its annoying for people targeted and it could get you into trouble. Seriously, don't be that person, nobody will like your for it. 
+Appart from that, most pagers support 8191 Stations. Triggering ~30 Pagers per station would easily take a long time. That when only sending each trigger once. It is recommended is to repeat the signal approx 10x, so that would already take all day. 
+Also your Flipper Zero will crash in that time, as the generated signals use RAM which is limited in the device. There may be ways to work around this, but I think its better to have such a limitation.
+
+## Does this even work
+I don't know. It's based on intel collected from other people. The Flipper sends data, I checked that with a second flipper. But if the data actually triggers something is not sure. 
+
+Then run the command: 
+ ```
+.\fbt launch APPSRC=applications_user/meal_pager
+ ```
+The application will be compiled and copied onto your device. 
+
+## Thank you notes
+- [Moeker](https://github.com/moeker) and his awesome [pagger tool](https://github.com/meoker/pagger), couldn't have done this without
+- [xb8](https://github.com/xb8/t119bruteforcer) for the useful data collected
+- [Xenobyte, ShotokanZH](https://twitter.com/xenobyte_/status/1558123251276070912) for their super interesting research

+ 14 - 0
non_catalog_apps/meal_pager/application.fam

@@ -0,0 +1,14 @@
+App(
+    appid="meal_pager",
+    name="Restaurant Pager Trigger",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="meal_pager_app",
+    stack_size=2 * 1024,
+    fap_icon="icons/meal_pager_10px.png",
+    fap_icon_assets="icons",
+    fap_category="Sub-GHz",
+    fap_version="0.8",
+    fap_author="leedave",
+    fap_weburl="https://github.com/leedave/flipper-zero-meal-pager",
+    fap_description="This app triggers restaurant pagers in a brute force manner, useful to test if devices are still functional.",
+)

+ 9 - 0
non_catalog_apps/meal_pager/docs/README.md

@@ -0,0 +1,9 @@
+## Meal Pager Trigger App
+
+Can make a range of Restaurant Pagers ring. 
+
+## Features
+- Support for Retekess T119, TD157, TD165, TD174
+- Select Range of Station Ids
+- Select Range of Pager Ids
+

+ 29 - 0
non_catalog_apps/meal_pager/docs/changelog.md

@@ -0,0 +1,29 @@
+## v0.8
+- Added Repeats feature 
+- Repeats configuration in settings
+- Usage of repeats in T119
+- Usage of repeats in TD157
+
+## v0.7
+
+- Added support for TD157
+- Some log & comment cleaning
+- Moved common code to new location
+- Removed testing assets
+- Fixed bad menu label (from "scene1" to "send data")
+
+## v0.6
+
+- Display when data is being generated and when it is being sent
+- Fixed issue where callbacks were sent infitiv when leaving the transmission page
+- Fixed blinking remaining when leaving transmission page prematurely
+- Set default last station Number lower to prevent crashes on first try
+
+
+## v0.5
+
+Compiled .sub data is read out and sent via SubGhz. Currently only support for T119. 
+
+## v0.1
+
+Can now generate a temporary .sub file for Retekess T119 Pager triggers. Must still be run via SubGhz App

+ 80 - 0
non_catalog_apps/meal_pager/helpers/meal_pager_calc.c

@@ -0,0 +1,80 @@
+#include "meal_pager_calc.h"
+
+void customConcat(char* dest, const char* src) {
+    // Find the end of the destination string
+    while(*dest != '\0') {
+        dest++;
+    }
+
+    // Copy characters from src to dest
+    while(*src != '\0') {
+        *dest = *src;
+        dest++;
+        src++;
+    }
+
+    // Null-terminate the concatenated string
+    *dest = '\0';
+}
+
+char* encManchester(const char* bits, int mode) {
+    // Allocate memory for the result string
+    char* res = (char*)malloc((strlen(bits) * 2 + 1) * sizeof(char));
+
+    int index = 0;
+    for(int i = 0; bits[i] != '\0'; i++) {
+        char c = bits[i];
+        if(c == '0') {
+            if(mode) {
+                res[index++] = '1';
+                res[index++] = '0';
+            } else {
+                res[index++] = '0';
+                res[index++] = '1';
+            }
+        } else if(c == '1') {
+            if(mode) {
+                res[index++] = '0';
+                res[index++] = '1';
+            } else {
+                res[index++] = '1';
+                res[index++] = '0';
+            }
+        } else {
+            // Handle 'EE' case (error)
+            res[index++] = 'E';
+            res[index++] = 'E';
+        }
+    }
+
+    // Null-terminate the result string
+    res[index] = '\0';
+
+    return res;
+}
+
+void uint32ToBinaray(uint32_t number, char* str, int8_t length) {
+    int i = 0;
+    length--; // count length without 0
+    for(i = length; i >= 0; i--) {
+        // Bitwise AND extration of the i-th bit
+        int bit = (number >> i) & 1;
+        // convert the bit to a character of 1 or 0
+        str[length - i] = bit + '0';
+    }
+    // Terminate the string
+    str[length + 1] = '\0';
+}
+
+void reverse(char* str) {
+    int length = strlen(str);
+    int start = 0;
+    int end = length - 1;
+    while(start < end) {
+        char temp = str[start];
+        str[start] = str[end];
+        str[end] = temp;
+        start++;
+        end--;
+    }
+}

+ 12 - 0
non_catalog_apps/meal_pager/helpers/meal_pager_calc.h

@@ -0,0 +1,12 @@
+
+#pragma once
+
+#include "../meal_pager_i.h"
+
+char* encManchester(const char* bits, int mode);
+
+void uint32ToBinaray(uint32_t number, char* str, int8_t length);
+
+void reverse(char* str);
+
+void customConcat(char* dest, const char* src);

+ 65 - 0
non_catalog_apps/meal_pager/helpers/meal_pager_custom_event.h

@@ -0,0 +1,65 @@
+#pragma once
+
+typedef enum {
+    Meal_PagerCustomEventStartscreenUp,
+    Meal_PagerCustomEventStartscreenDown,
+    Meal_PagerCustomEventStartscreenLeft,
+    Meal_PagerCustomEventStartscreenRight,
+    Meal_PagerCustomEventStartscreenOk,
+    Meal_PagerCustomEventStartscreenBack,
+    Meal_PagerCustomEventTransmitUp,
+    Meal_PagerCustomEventTransmitDown,
+    Meal_PagerCustomEventTransmitLeft,
+    Meal_PagerCustomEventTransmitRight,
+    Meal_PagerCustomEventTransmitOk,
+    Meal_PagerCustomEventTransmitBack,
+    Meal_PagerCustomEventScene2Up,
+    Meal_PagerCustomEventScene2Down,
+    Meal_PagerCustomEventScene2Left,
+    Meal_PagerCustomEventScene2Right,
+    Meal_PagerCustomEventScene2Ok,
+    Meal_PagerCustomEventScene2Back,
+    Meal_PagerCustomEventViewTransmitterBack,
+    Meal_PagerCustomEventViewTransmitterSendStart,
+    Meal_PagerCustomEventViewTransmitterSendStop,
+    Meal_PagerCustomEventViewTransmitterError,
+} Meal_PagerCustomEvent;
+
+enum Meal_PagerCustomEventType {
+    // Reserve first 100 events for button types and indexes, starting from 0
+    Meal_PagerCustomEventMenuVoid,
+    Meal_PagerCustomEventMenuSelected,
+};
+
+#pragma pack(push, 1)
+typedef union {
+    uint32_t packed_value;
+    struct {
+        uint16_t type;
+        int16_t value;
+    } content;
+} Meal_PagerCustomEventMenu;
+#pragma pack(pop)
+
+static inline uint32_t meal_pager_custom_menu_event_pack(uint16_t type, int16_t value) {
+    Meal_PagerCustomEventMenu event = {.content = {.type = type, .value = value}};
+    return event.packed_value;
+}
+static inline void
+    meal_pager_custom_menu_event_unpack(uint32_t packed_value, uint16_t* type, int16_t* value) {
+    Meal_PagerCustomEventMenu event = {.packed_value = packed_value};
+    if(type) *type = event.content.type;
+    if(value) *value = event.content.value;
+}
+
+static inline uint16_t meal_pager_custom_menu_event_get_type(uint32_t packed_value) {
+    uint16_t type;
+    meal_pager_custom_menu_event_unpack(packed_value, &type, NULL);
+    return type;
+}
+
+static inline int16_t meal_pager_custom_menu_event_get_value(uint32_t packed_value) {
+    int16_t value;
+    meal_pager_custom_menu_event_unpack(packed_value, NULL, &value);
+    return value;
+}

+ 34 - 0
non_catalog_apps/meal_pager/helpers/meal_pager_haptic.c

@@ -0,0 +1,34 @@
+#include "meal_pager_haptic.h"
+
+void meal_pager_play_happy_bump(void* context) {
+    Meal_Pager* app = context;
+    if(app->haptic != 1) {
+        return;
+    }
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+void meal_pager_play_bad_bump(void* context) {
+    Meal_Pager* app = context;
+    if(app->haptic != 1) {
+        return;
+    }
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+void meal_pager_play_long_bump(void* context) {
+    Meal_Pager* app = context;
+    if(app->haptic != 1) {
+        return;
+    }
+    for(int i = 0; i < 4; i++) {
+        notification_message(app->notification, &sequence_set_vibro_on);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 50);
+        notification_message(app->notification, &sequence_reset_vibro);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    }
+}

+ 10 - 0
non_catalog_apps/meal_pager/helpers/meal_pager_haptic.h

@@ -0,0 +1,10 @@
+#pragma once
+
+#include <notification/notification_messages.h>
+#include "../meal_pager_i.h"
+
+void meal_pager_play_happy_bump(void* context);
+
+void meal_pager_play_bad_bump(void* context);
+
+void meal_pager_play_long_bump(void* context);

+ 55 - 0
non_catalog_apps/meal_pager/helpers/meal_pager_led.c

@@ -0,0 +1,55 @@
+#include "meal_pager_led.h"
+
+void meal_pager_blink_start_subghz(Meal_Pager* app) {
+    furi_assert(app);
+    notification_message(app->notification, &sequence_blink_stop);
+    notification_message(app->notification, &sequence_blink_start_magenta);
+}
+
+void meal_pager_blink_start_compile(Meal_Pager* app) {
+    furi_assert(app);
+    notification_message(app->notification, &sequence_blink_stop);
+    notification_message(app->notification, &sequence_blink_start_yellow);
+}
+
+void meal_pager_blink_stop(Meal_Pager* app) {
+    furi_assert(app);
+    notification_message(app->notification, &sequence_blink_stop);
+}
+
+void meal_pager_led_set_rgb(void* context, int red, int green, int blue) {
+    Meal_Pager* app = context;
+    if(app->led != 1) {
+        return;
+    }
+    NotificationMessage notification_led_message_1;
+    notification_led_message_1.type = NotificationMessageTypeLedRed;
+    NotificationMessage notification_led_message_2;
+    notification_led_message_2.type = NotificationMessageTypeLedGreen;
+    NotificationMessage notification_led_message_3;
+    notification_led_message_3.type = NotificationMessageTypeLedBlue;
+
+    notification_led_message_1.data.led.value = red;
+    notification_led_message_2.data.led.value = green;
+    notification_led_message_3.data.led.value = blue;
+    const NotificationSequence notification_sequence = {
+        &notification_led_message_1,
+        &notification_led_message_2,
+        &notification_led_message_3,
+        &message_do_not_reset,
+        NULL,
+    };
+    notification_message(app->notification, &notification_sequence);
+    furi_thread_flags_wait(
+        0, FuriFlagWaitAny, 10); //Delay, prevent removal from RAM before LED value set
+}
+
+void meal_pager_led_reset(void* context) {
+    Meal_Pager* app = context;
+    notification_message(app->notification, &sequence_reset_red);
+    notification_message(app->notification, &sequence_reset_green);
+    notification_message(app->notification, &sequence_reset_blue);
+
+    furi_thread_flags_wait(
+        0, FuriFlagWaitAny, 300); //Delay, prevent removal from RAM before LED value set
+}

+ 13 - 0
non_catalog_apps/meal_pager/helpers/meal_pager_led.h

@@ -0,0 +1,13 @@
+#pragma once
+
+#include "../meal_pager_i.h"
+
+void meal_pager_blink_start_subghz(Meal_Pager* app);
+
+void meal_pager_blink_start_compile(Meal_Pager* app);
+
+void meal_pager_blink_stop(Meal_Pager* app);
+
+void meal_pager_led_set_rgb(void* context, int red, int green, int blue);
+
+void meal_pager_led_reset(void* context);

+ 25 - 0
non_catalog_apps/meal_pager/helpers/meal_pager_speaker.c

@@ -0,0 +1,25 @@
+#include "meal_pager_speaker.h"
+
+#define NOTE_INPUT 587.33f
+
+void meal_pager_play_input_sound(void* context) {
+    Meal_Pager* app = context;
+    if(app->speaker != 1) {
+        return;
+    }
+    float volume = 1.0f;
+    if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
+        furi_hal_speaker_start(NOTE_INPUT, volume);
+    }
+}
+
+void meal_pager_stop_all_sound(void* context) {
+    Meal_Pager* app = context;
+    if(app->speaker != 1) {
+        return;
+    }
+    if(furi_hal_speaker_is_mine()) {
+        furi_hal_speaker_stop();
+        furi_hal_speaker_release();
+    }
+}

+ 8 - 0
non_catalog_apps/meal_pager/helpers/meal_pager_speaker.h

@@ -0,0 +1,8 @@
+#pragma once
+
+#include "../meal_pager_i.h"
+
+#define NOTE_INPUT 587.33f
+
+void meal_pager_play_input_sound(void* context);
+void meal_pager_stop_all_sound(void* context);

+ 201 - 0
non_catalog_apps/meal_pager/helpers/meal_pager_storage.c

@@ -0,0 +1,201 @@
+#include "meal_pager_storage.h"
+
+static Storage* meal_pager_open_storage() {
+    return furi_record_open(RECORD_STORAGE);
+}
+
+static void meal_pager_close_storage() {
+    furi_record_close(RECORD_STORAGE);
+}
+
+static void meal_pager_close_config_file(FlipperFormat* file) {
+    if(file == NULL) return;
+    flipper_format_rewind(file);
+    flipper_format_file_close(file);
+    flipper_format_free(file);
+}
+
+bool meal_pager_save_subghz_buffer_file_start(void* context, FlipperFormat* ff, Storage* storage) {
+    // SubGhz TXRX can only be loaded with files, makes sense as to save RAM
+    Meal_Pager* app = context;
+    UNUSED(app);
+    bool success = false;
+    FURI_LOG_D(TAG, "Creating Temp File");
+    //Storage* storage = furi_record_open(RECORD_STORAGE);
+    //FlipperFormat* ff = flipper_format_file_alloc(storage);
+
+    // Overwrite wont work, so delete first
+    if(storage_file_exists(storage, MEAL_PAGER_TMP_FILE)) {
+        bool stored = storage_simply_remove(storage, MEAL_PAGER_TMP_FILE);
+        if(!stored) {
+            FURI_LOG_D(TAG, "Cannot remove file, seems to be open");
+            return success;
+        }
+    }
+
+    // Open File, create if not exists
+    if(!storage_common_stat(storage, MEAL_PAGER_TMP_FILE, NULL) == FSE_OK) {
+        FURI_LOG_D(
+            TAG, "Config file %s is not found. Will create new.", MEAL_PAGER_SETTINGS_SAVE_PATH);
+        if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) {
+            FURI_LOG_D(
+                TAG, "Directory %s doesn't exist. Will create new.", CONFIG_FILE_DIRECTORY_PATH);
+            if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
+                FURI_LOG_D(TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH);
+            }
+        }
+    }
+
+    if(!flipper_format_file_open_new(ff, MEAL_PAGER_TMP_FILE)) {
+        //totp_close_config_file(fff_file);
+        FURI_LOG_D(TAG, "Error creating new file %s", MEAL_PAGER_TMP_FILE);
+        meal_pager_close_storage();
+        return success;
+    }
+
+    success =
+        flipper_format_write_header_cstr(
+            ff, MEAL_PAGER_SUBGHZ_FILE_TYPE, MEAL_PAGER_SUBGHZ_FILE_VERSION) &&
+        flipper_format_write_string_cstr(ff, "Frequency", MEAL_PAGER_SUBGHZ_FILE_FREQUENCY) &&
+        flipper_format_write_string_cstr(ff, "Preset", MEAL_PAGER_SUBGHZ_FILE_PRESET) &&
+        flipper_format_write_string_cstr(ff, "Protocol", MEAL_PAGER_SUBGHZ_FILE_Protocol);
+    //UNUSED(success);
+    return success;
+    //return ff;
+}
+
+void meal_pager_save_subghz_buffer_stop(void* context, FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+    FURI_LOG_D(TAG, "Closing Temp File");
+    if(!flipper_format_rewind(ff)) {
+        meal_pager_close_config_file(ff);
+        FURI_LOG_E(TAG, "Rewind error");
+        meal_pager_close_storage();
+        return;
+    }
+
+    meal_pager_close_config_file(ff);
+    meal_pager_close_storage();
+}
+
+void meal_pager_save_settings(void* context) {
+    Meal_Pager* app = context;
+    if(app->save_settings == 0) {
+        FURI_LOG_D(TAG, "Skipping Save because Disabled");
+        return;
+    }
+
+    FURI_LOG_D(TAG, "Saving Settings to File");
+    Storage* storage = meal_pager_open_storage();
+    FlipperFormat* fff_file = flipper_format_file_alloc(storage);
+
+    // Overwrite wont work, so delete first
+    if(storage_file_exists(storage, MEAL_PAGER_SETTINGS_SAVE_PATH)) {
+        storage_simply_remove(storage, MEAL_PAGER_SETTINGS_SAVE_PATH);
+    }
+
+    // Open File, create if not exists
+    if(!storage_common_stat(storage, MEAL_PAGER_SETTINGS_SAVE_PATH, NULL) == FSE_OK) {
+        FURI_LOG_D(
+            TAG, "Config file %s is not found. Will create new.", MEAL_PAGER_SETTINGS_SAVE_PATH);
+        if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) {
+            FURI_LOG_D(
+                TAG, "Directory %s doesn't exist. Will create new.", CONFIG_FILE_DIRECTORY_PATH);
+            if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
+                FURI_LOG_E(TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH);
+            }
+        }
+    }
+
+    if(!flipper_format_file_open_new(fff_file, MEAL_PAGER_SETTINGS_SAVE_PATH)) {
+        //totp_close_config_file(fff_file);
+        FURI_LOG_E(TAG, "Error creating new file %s", MEAL_PAGER_SETTINGS_SAVE_PATH);
+        meal_pager_close_storage();
+        return;
+    }
+
+    // Store Settings
+    flipper_format_write_header_cstr(
+        fff_file, MEAL_PAGER_SETTINGS_HEADER, MEAL_PAGER_SETTINGS_FILE_VERSION);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_PAGER_TYPE, &app->pager_type, 1);
+    flipper_format_write_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_FIRST_STATION, &app->first_station, 1);
+    flipper_format_write_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_LAST_STATION, &app->last_station, 1);
+    flipper_format_write_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_FIRST_PAGER, &app->first_pager, 1);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_LAST_PAGER, &app->last_pager, 1);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_LAST_PAGER, &app->last_pager, 1);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_REPEATS, &app->repeats, 1);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_LED, &app->led, 1);
+    flipper_format_write_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
+
+    if(!flipper_format_rewind(fff_file)) {
+        meal_pager_close_config_file(fff_file);
+        FURI_LOG_E(TAG, "Rewind error");
+        meal_pager_close_storage();
+        return;
+    }
+
+    meal_pager_close_config_file(fff_file);
+    meal_pager_close_storage();
+}
+
+void meal_pager_read_settings(void* context) {
+    Meal_Pager* app = context;
+    Storage* storage = meal_pager_open_storage();
+    FlipperFormat* fff_file = flipper_format_file_alloc(storage);
+
+    if(storage_common_stat(storage, MEAL_PAGER_SETTINGS_SAVE_PATH, NULL) != FSE_OK) {
+        meal_pager_close_config_file(fff_file);
+        meal_pager_close_storage();
+        return;
+    }
+    uint32_t file_version;
+    FuriString* temp_str = furi_string_alloc();
+
+    if(!flipper_format_file_open_existing(fff_file, MEAL_PAGER_SETTINGS_SAVE_PATH)) {
+        FURI_LOG_E(TAG, "Cannot open file %s", MEAL_PAGER_SETTINGS_SAVE_PATH);
+        meal_pager_close_config_file(fff_file);
+        meal_pager_close_storage();
+        return;
+    }
+
+    if(!flipper_format_read_header(fff_file, temp_str, &file_version)) {
+        FURI_LOG_E(TAG, "Missing Header Data");
+        meal_pager_close_config_file(fff_file);
+        meal_pager_close_storage();
+        return;
+    }
+
+    if(file_version < MEAL_PAGER_SETTINGS_FILE_VERSION) {
+        FURI_LOG_I(TAG, "old config version, will be removed.");
+        meal_pager_close_config_file(fff_file);
+        meal_pager_close_storage();
+        return;
+    }
+
+    flipper_format_read_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_PAGER_TYPE, &app->pager_type, 1);
+    flipper_format_read_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_FIRST_STATION, &app->first_station, 1);
+    flipper_format_read_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_LAST_STATION, &app->last_station, 1);
+    flipper_format_read_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_FIRST_PAGER, &app->first_pager, 1);
+    flipper_format_read_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_LAST_PAGER, &app->last_pager, 1);
+    flipper_format_read_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_REPEATS, &app->repeats, 1);
+    flipper_format_read_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
+    flipper_format_read_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
+    flipper_format_read_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_LED, &app->led, 1);
+    flipper_format_read_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
+
+    flipper_format_rewind(fff_file);
+
+    meal_pager_close_config_file(fff_file);
+    meal_pager_close_storage();
+}

+ 36 - 0
non_catalog_apps/meal_pager/helpers/meal_pager_storage.h

@@ -0,0 +1,36 @@
+#pragma once
+
+#include <stdlib.h>
+#include <string.h>
+#include <storage/storage.h>
+#include <flipper_format/flipper_format_i.h>
+#include "../meal_pager_i.h"
+
+#define MEAL_PAGER_SETTINGS_FILE_VERSION 2
+#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/meal_pager")
+#define MEAL_PAGER_SETTINGS_SAVE_PATH CONFIG_FILE_DIRECTORY_PATH "/meal_pager.conf"
+#define MEAL_PAGER_SETTINGS_SAVE_PATH_TMP MEAL_PAGER_SETTINGS_SAVE_PATH ".tmp"
+#define MEAL_PAGER_SETTINGS_HEADER "Meal_Pager Config File"
+#define MEAL_PAGER_SETTINGS_KEY_PAGER_TYPE "Pager Type"
+#define MEAL_PAGER_SETTINGS_KEY_FIRST_STATION "First Station"
+#define MEAL_PAGER_SETTINGS_KEY_LAST_STATION "Last Station"
+#define MEAL_PAGER_SETTINGS_KEY_FIRST_PAGER "First Pager"
+#define MEAL_PAGER_SETTINGS_KEY_LAST_PAGER "Last Pager"
+#define MEAL_PAGER_SETTINGS_KEY_REPEATS "Repeats"
+#define MEAL_PAGER_SETTINGS_KEY_HAPTIC "Haptic"
+#define MEAL_PAGER_SETTINGS_KEY_LED "Led"
+#define MEAL_PAGER_SETTINGS_KEY_SPEAKER "Speaker"
+#define MEAL_PAGER_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings"
+#define MEAL_PAGER_TMP_FILE CONFIG_FILE_DIRECTORY_PATH "/tmp.sub"
+#define MEAL_PAGER_SUBGHZ_FILE_TYPE "Flipper SubGhz RAW File"
+#define MEAL_PAGER_SUBGHZ_FILE_VERSION 1
+#define MEAL_PAGER_SUBGHZ_FILE_FREQUENCY "433920000"
+#define MEAL_PAGER_SUBGHZ_FILE_PRESET "FuriHalSubGhzPresetOok650Async"
+#define MEAL_PAGER_SUBGHZ_FILE_Protocol "RAW"
+
+bool meal_pager_save_subghz_buffer_file_start(void* context, FlipperFormat* ff, Storage* storage);
+
+void meal_pager_save_subghz_buffer_stop(void* context, FlipperFormat* ff);
+
+void meal_pager_save_settings(void* context);
+void meal_pager_read_settings(void* context);

+ 130 - 0
non_catalog_apps/meal_pager/helpers/retekess/meal_pager_retekess_t119.c

@@ -0,0 +1,130 @@
+
+#include "meal_pager_retekess_t119.h"
+
+static char* genRawDataT119(int zero, int one, const char* bits) {
+    int bitsLen = strlen(bits);
+    int lineLen = 256; // Adjust the line length as needed
+    char* line = (char*)malloc(lineLen * sizeof(char));
+
+    // Initialize the line with the first part
+    char* res = (char*)malloc(lineLen * sizeof(char));
+    res[0] = '\0'; // Null-terminate the result string
+
+    customConcat(res, "-6000");
+
+    // Append bits and create the line
+    for(int i = 0; i < bitsLen; i++) {
+        char c = bits[i];
+        int t = (c == '0') ? zero : one;
+
+        if(i % 2 == 0) {
+            snprintf(line, lineLen, " %d", t);
+        } else {
+            snprintf(line, lineLen, " -%d", t);
+        }
+
+        // Concatenate the line to the result string
+        customConcat(res, line);
+    }
+
+    // Append the closing part to the line
+    customConcat(res, " 200 -6000");
+
+    free(line); // Free memory allocated for the line
+
+    return res;
+}
+
+static void meal_pager_retekess_t119_generate_pager(
+    void* context,
+    char* stationId,
+    uint32_t pager,
+    FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    char pagerId[11];
+    char* fullId = (char*)malloc(25 * sizeof(char));
+    uint32_t action = 0; // 0 = ring, 1 = mute
+    char actionId[2];
+    //FURI_LOG_D(TAG, "Generating T119 Data for Pager %lu", pager);
+    app->current_pager = pager;
+    meal_pager_transmit_model_set_pager(app->meal_pager_transmit, app->current_pager);
+    uint32ToBinaray(pager, pagerId, 10);
+    uint32ToBinaray(action, actionId, 1);
+    reverse(pagerId);
+    reverse(actionId);
+    //FURI_LOG_D(TAG, "Station Bin: %s", stationId);
+    //FURI_LOG_D(TAG, "Pager Bin: %s", pagerId);
+    //FURI_LOG_D(TAG, "Action Bin: %s", actionId);
+    customConcat(fullId, stationId);
+    customConcat(fullId, pagerId);
+    //FURI_LOG_D(TAG, "Result %s", fullId);
+    //FURI_LOG_D(TAG, "Station & Pager: %s", stationPagerId);
+    //FURI_LOG_D(TAG, "Station & Pager: %s", stationPagerId);
+    customConcat(fullId, actionId);
+    //FURI_LOG_D(TAG, "CustomConcat: %s", fullId);
+    //FURI_LOG_D(TAG, "Station & Pager & Action: %s", fullId);
+    char* manchester = encManchester(fullId, 0);
+    //FURI_LOG_D(TAG, "Manchester: %s", manchester);
+    char* rawSignal = genRawDataT119(200, 600, manchester);
+    //FURI_LOG_D(TAG, "RAW_Data: %s", rawSignal);
+    for(u_int32_t i = 1; app->repeats >= i; i++) {
+        flipper_format_write_string_cstr(ff, "RAW_Data", rawSignal);
+    }
+    //flipper_format_write_string_cstr(ff, "RAW_Data", rawSignal);
+    free(manchester);
+    free(rawSignal);
+}
+
+static void
+    meal_pager_retekess_t119_generate_station(void* context, uint32_t station, FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    FURI_LOG_D(
+        TAG,
+        "Generating T119 Data for Station %lu. Pagers From %lu to %lu",
+        station,
+        app->first_pager,
+        app->last_pager);
+    app->current_station = station;
+    app->current_pager = app->first_pager;
+    char stationId[14];
+    uint32ToBinaray(station, stationId, 13);
+    reverse(stationId);
+    meal_pager_transmit_model_set_station(app->meal_pager_transmit, app->current_station);
+    for(u_int32_t i = app->current_pager; i <= app->last_pager; i++) {
+        meal_pager_retekess_t119_generate_pager(app, stationId, i, ff);
+        //furi_thread_flags_wait(0, FuriFlagWaitAny, 1);
+        if(app->stop_transmit) {
+            break;
+        }
+    }
+}
+
+bool meal_pager_retekess_t119_generate_all(void* context) {
+    Meal_Pager* app = context;
+
+    app->current_pager = 1;
+    app->current_station = app->first_station;
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+    bool success = meal_pager_save_subghz_buffer_file_start(app, ff, storage);
+
+    if(!success) {
+        FURI_LOG_D(TAG, "failed to save to buffer");
+        meal_pager_save_subghz_buffer_stop(app, ff);
+        furi_record_close(RECORD_STORAGE);
+        return success;
+    }
+
+    for(u_int32_t i = app->current_station; i <= app->last_station; i++) {
+        meal_pager_retekess_t119_generate_station(app, i, ff);
+        //furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+        if(app->stop_transmit) {
+            break;
+        }
+    }
+
+    meal_pager_save_subghz_buffer_stop(app, ff);
+    furi_record_close(RECORD_STORAGE);
+    return success;
+}

+ 7 - 0
non_catalog_apps/meal_pager/helpers/retekess/meal_pager_retekess_t119.h

@@ -0,0 +1,7 @@
+
+#pragma once
+
+#include "../../meal_pager_i.h"
+#include "../meal_pager_calc.h"
+
+bool meal_pager_retekess_t119_generate_all(void* context);

+ 115 - 0
non_catalog_apps/meal_pager/helpers/retekess/meal_pager_retekess_td157.c

@@ -0,0 +1,115 @@
+
+#include "meal_pager_retekess_td157.h"
+
+static char* genRawDataTD157(int zero, int one, const char* bits) {
+    int bitsLen = strlen(bits);
+    int lineLen = 256; // Adjust the line length as needed
+    char* line = (char*)malloc(lineLen * sizeof(char));
+
+    // Initialize the line with the first part
+    char* res = (char*)malloc(lineLen * sizeof(char));
+    res[0] = '\0'; // Null-terminate the result string
+
+    customConcat(res, "-6000");
+
+    // Append bits and create the line
+    for(int i = 0; i < bitsLen; i++) {
+        char c = bits[i];
+        int t = (c == '0') ? zero : one;
+
+        if(i % 2 == 0) {
+            snprintf(line, lineLen, " %d", t);
+        } else {
+            snprintf(line, lineLen, " -%d", t);
+        }
+
+        // Concatenate the line to the result string
+        customConcat(res, line);
+    }
+
+    // Append the closing part to the line
+    customConcat(res, " 200 -6000");
+
+    free(line); // Free memory allocated for the line
+
+    return res;
+}
+
+static void meal_pager_retekess_td157_generate_pager(
+    void* context,
+    char* stationId,
+    uint32_t pager,
+    FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    char pagerId[11];
+    char* fullId = (char*)malloc(25 * sizeof(char));
+    uint32_t action = 2;
+    char actionId[4];
+    app->current_pager = pager;
+    meal_pager_transmit_model_set_pager(app->meal_pager_transmit, app->current_pager);
+    FURI_LOG_D(TAG, "Generating TD157 Data for Pager %lu", pager);
+    uint32ToBinaray(pager, pagerId, 10);
+    customConcat(fullId, stationId);
+    customConcat(fullId, pagerId);
+    uint32ToBinaray(action, actionId, 4);
+    customConcat(fullId, actionId);
+    char* manchester = encManchester(fullId, 0);
+    char* rawSignal = genRawDataTD157(200, 600, manchester);
+    for(u_int32_t i = 1; app->repeats >= i; i++) {
+        flipper_format_write_string_cstr(ff, "RAW_Data", rawSignal);
+    }
+    free(manchester);
+    free(rawSignal);
+}
+
+static void
+    meal_pager_retekess_td157_generate_station(void* context, uint32_t station, FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    FURI_LOG_D(
+        TAG,
+        "Generating TD157 Data for Station %lu. Pagers From %lu to %lu",
+        station,
+        app->first_pager,
+        app->last_pager);
+    app->current_station = station;
+    app->current_pager = app->first_pager;
+    char stationId[14];
+    uint32ToBinaray(station, stationId, 10);
+    //reverse(stationId);
+    meal_pager_transmit_model_set_station(app->meal_pager_transmit, app->current_station);
+    for(u_int32_t i = app->current_pager; i <= app->last_pager; i++) {
+        meal_pager_retekess_td157_generate_pager(app, stationId, i, ff);
+        if(app->stop_transmit) {
+            break;
+        }
+    }
+}
+
+bool meal_pager_retekess_td157_generate_all(void* context) {
+    Meal_Pager* app = context;
+
+    app->current_pager = 1;
+    app->current_station = app->first_station;
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+    bool success = meal_pager_save_subghz_buffer_file_start(app, ff, storage);
+
+    if(!success) {
+        FURI_LOG_D(TAG, "failed to save to buffer");
+        meal_pager_save_subghz_buffer_stop(app, ff);
+        furi_record_close(RECORD_STORAGE);
+        return success;
+    }
+
+    for(u_int32_t i = app->current_station; i <= app->last_station; i++) {
+        meal_pager_retekess_td157_generate_station(app, i, ff);
+        if(app->stop_transmit) {
+            break;
+        }
+    }
+
+    meal_pager_save_subghz_buffer_stop(app, ff);
+    furi_record_close(RECORD_STORAGE);
+    return success;
+}

+ 7 - 0
non_catalog_apps/meal_pager/helpers/retekess/meal_pager_retekess_td157.h

@@ -0,0 +1,7 @@
+
+#pragma once
+
+#include "../../meal_pager_i.h"
+#include "../meal_pager_calc.h"
+
+bool meal_pager_retekess_td157_generate_all(void* context);

+ 77 - 0
non_catalog_apps/meal_pager/helpers/subghz/subghz.c

@@ -0,0 +1,77 @@
+/* Reduced variant of the Flipper Zero SubGhz Class */
+
+#include "subghz_i.h"
+#include "../../helpers/meal_pager_custom_event.h"
+#include "../../helpers/meal_pager_led.h"
+//#include "../meal_pager_storage.h"
+
+SubGhz* subghz_alloc() {
+    SubGhz* subghz = malloc(sizeof(SubGhz));
+
+    subghz->file_path = furi_string_alloc();
+
+    subghz->txrx = subghz_txrx_alloc();
+
+    return subghz;
+}
+
+void subghz_free(SubGhz* subghz) {
+    //TxRx
+    subghz_txrx_free(subghz->txrx);
+
+    // Furi strings
+    furi_string_free(subghz->file_path);
+
+    // The rest
+    free(subghz);
+}
+
+void subghz_scene_transmit_callback_end_tx(void* context) {
+    furi_assert(context);
+    //UNUSED(context);
+    FURI_LOG_D(TAG, "callback end");
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(
+        app->view_dispatcher, Meal_PagerCustomEventViewTransmitterSendStop);
+}
+
+void subghz_send(void* context) {
+    //UNUSED(context);
+    Meal_Pager* app = context;
+    //SubGhz* subghz = subghz_alloc();
+
+    FURI_LOG_D(TAG, "loading protocol from file");
+    subghz_load_protocol_from_file(app->subghz);
+
+    /*Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+
+    if(!flipper_format_file_open_existing(ff, MEAL_PAGER_TMP_FILE)) {
+        //totp_close_config_file(fff_file);
+        FURI_LOG_E(TAG, "Error reading Temp File %s", MEAL_PAGER_TMP_FILE);
+        furi_record_close(RECORD_STORAGE);
+        return;
+    }*/
+
+    //subghz_txrx_tx_start(subghz->txrx, ff);
+
+    FURI_LOG_D(TAG, "Starting Transmission");
+    subghz_txrx_tx_start(
+        app->subghz->txrx,
+        subghz_txrx_get_fff_data(app->subghz->txrx)); //Seems like it must be done this way
+
+    FURI_LOG_D(TAG, "setting sugbhz raw file encoder worker callback");
+    subghz_txrx_set_raw_file_encoder_worker_callback_end(
+        app->subghz->txrx, subghz_scene_transmit_callback_end_tx, app);
+    app->state_notifications = SubGhzNotificationStateTx;
+
+    /*flipper_format_rewind(ff);
+    flipper_format_file_close(ff);
+    flipper_format_free(ff);
+
+    furi_record_close(RECORD_STORAGE);*/
+
+    //subghz_free(subghz);
+    FURI_LOG_D(TAG, "Finished Transmitting");
+    //meal_pager_blink_stop(app);
+}

+ 9 - 0
non_catalog_apps/meal_pager/helpers/subghz/subghz.h

@@ -0,0 +1,9 @@
+#pragma once
+
+#include "subghz_i.h"
+
+typedef struct SubGhz SubGhz;
+
+SubGhz* subghz_alloc();
+void subghz_free(SubGhz* subghz);
+void subghz_send(void* context);

+ 14 - 0
non_catalog_apps/meal_pager/helpers/subghz/subghz_error_type.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+
+/** SubGhzErrorType */
+typedef enum {
+    SubGhzErrorTypeNoError = 0, /** There are no errors */
+    SubGhzErrorTypeParseFile =
+        1, /** File parsing error, or wrong file structure, or missing required parameters. more accurate data can be obtained through the debug port */
+    SubGhzErrorTypeOnlyRX =
+        2, /** Transmission on this frequency is blocked by regional settings */
+    SubGhzErrorTypeParserOthers = 3, /** Error in protocol parameters description */
+} SubGhzErrorType;

+ 260 - 0
non_catalog_apps/meal_pager/helpers/subghz/subghz_i.c

@@ -0,0 +1,260 @@
+#include "subghz_i.h"
+
+#include "subghz/types.h"
+#include <math.h>
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include <flipper_format/flipper_format.h>
+
+#include <flipper_format/flipper_format_i.h>
+#include <lib/toolbox/stream/stream.h>
+#include <lib/subghz/protocols/raw.h>
+
+//#define TAG "SubGhz"
+/*
+void subghz_set_default_preset(SubGhz* subghz) {
+    furi_assert(subghz);
+    subghz_txrx_set_preset(
+        subghz->txrx,
+        "AM650",
+        subghz_setting_get_default_frequency(subghz_txrx_get_setting(subghz->txrx)),
+        NULL,
+        0);
+}*/
+
+/*bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) {
+    switch(subghz_txrx_tx_start(subghz->txrx, flipper_format)) {
+    case SubGhzTxRxStartTxStateErrorParserOthers:
+        dialog_message_show_storage_error(
+            subghz->dialogs, "Error in protocol\nparameters\ndescription");
+        break;
+    case SubGhzTxRxStartTxStateErrorOnlyRx:
+        FURI_LOG_D(TAG, 'Cannot send, only RX possible');
+        break;
+
+    default:
+        return true;
+        break;
+    }
+    return false;
+}*/
+
+bool subghz_key_load(SubGhz* subghz, const char* file_path) { //, bool show_dialog) {
+    furi_assert(subghz);
+    furi_assert(file_path);
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
+    Stream* fff_data_stream =
+        flipper_format_get_raw_stream(subghz_txrx_get_fff_data(subghz->txrx));
+
+    SubGhzLoadKeyState load_key_state = SubGhzLoadKeyStateParseErr;
+    FuriString* temp_str = furi_string_alloc();
+    uint32_t temp_data32;
+
+    do {
+        stream_clean(fff_data_stream);
+        if(!flipper_format_file_open_existing(fff_data_file, file_path)) {
+            FURI_LOG_E(TAG, "Error open file %s", file_path);
+            break;
+        }
+
+        if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
+            FURI_LOG_E(TAG, "Missing or incorrect header");
+            break;
+        }
+
+        if(((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) ||
+            (!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) &&
+           temp_data32 == SUBGHZ_KEY_FILE_VERSION) {
+        } else {
+            FURI_LOG_E(TAG, "Type or version mismatch");
+            break;
+        }
+
+        //Load frequency
+        if(!flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) {
+            FURI_LOG_E(TAG, "Missing Frequency");
+            break;
+        }
+
+        if(!subghz_txrx_radio_device_is_frequecy_valid(subghz->txrx, temp_data32)) {
+            FURI_LOG_E(TAG, "Frequency not supported");
+            break;
+        }
+
+        //Load preset
+        if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) {
+            FURI_LOG_E(TAG, "Missing Preset");
+            break;
+        }
+
+        furi_string_set_str(
+            temp_str, subghz_txrx_get_preset_name(subghz->txrx, furi_string_get_cstr(temp_str)));
+        if(!strcmp(furi_string_get_cstr(temp_str), "")) {
+            break;
+        }
+        SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
+
+        if(!strcmp(furi_string_get_cstr(temp_str), "CUSTOM")) {
+            //TODO FL-3551: add Custom_preset_module
+            //delete preset if it already exists
+            subghz_setting_delete_custom_preset(setting, furi_string_get_cstr(temp_str));
+            //load custom preset from file
+            if(!subghz_setting_load_custom_preset(
+                   setting, furi_string_get_cstr(temp_str), fff_data_file)) {
+                FURI_LOG_E(TAG, "Missing Custom preset");
+                break;
+            }
+        }
+        size_t preset_index =
+            subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(temp_str));
+        subghz_txrx_set_preset(
+            subghz->txrx,
+            furi_string_get_cstr(temp_str),
+            temp_data32,
+            subghz_setting_get_preset_data(setting, preset_index),
+            subghz_setting_get_preset_data_size(setting, preset_index));
+
+        //Load protocol
+        if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) {
+            FURI_LOG_E(TAG, "Missing Protocol");
+            break;
+        }
+
+        FlipperFormat* fff_data = subghz_txrx_get_fff_data(subghz->txrx);
+        if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) {
+            //if RAW
+            subghz->load_type_file = SubGhzLoadTypeFileRaw;
+            subghz_protocol_raw_gen_fff_data(
+                fff_data, file_path, subghz_txrx_radio_device_get_name(subghz->txrx));
+        } else {
+            subghz->load_type_file = SubGhzLoadTypeFileKey;
+            stream_copy_full(
+                flipper_format_get_raw_stream(fff_data_file),
+                flipper_format_get_raw_stream(fff_data));
+        }
+
+        if(subghz_txrx_load_decoder_by_name_protocol(
+               subghz->txrx, furi_string_get_cstr(temp_str))) {
+            SubGhzProtocolStatus status = subghz_protocol_decoder_base_deserialize(
+                subghz_txrx_get_decoder(subghz->txrx), fff_data);
+            if(status != SubGhzProtocolStatusOk) {
+                load_key_state = SubGhzLoadKeyStateProtocolDescriptionErr;
+                break;
+            }
+        } else {
+            FURI_LOG_E(TAG, "Protocol not found");
+            break;
+        }
+
+        load_key_state = SubGhzLoadKeyStateOK;
+    } while(0);
+
+    furi_string_free(temp_str);
+    flipper_format_free(fff_data_file);
+    furi_record_close(RECORD_STORAGE);
+
+    switch(load_key_state) {
+    case SubGhzLoadKeyStateParseErr:
+        //if(show_dialog) {
+        //    dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile");
+        //}
+        return false;
+    case SubGhzLoadKeyStateProtocolDescriptionErr:
+        //if(show_dialog) {
+        //    dialog_message_show_storage_error(
+        //        subghz->dialogs, "Error in protocol\nparameters\ndescription");
+        //}
+        return false;
+
+    case SubGhzLoadKeyStateOK:
+        return true;
+
+    default:
+        furi_crash("SubGhz: Unknown load_key_state.");
+        return false;
+    }
+    return false;
+}
+
+/*SubGhzLoadTypeFile subghz_get_load_type_file(SubGhz* subghz) {
+    furi_assert(subghz);
+    return subghz->load_type_file;
+}*/
+
+bool subghz_load_protocol_from_file(SubGhz* subghz) {
+    furi_assert(subghz);
+
+    //FuriString* file_path = furi_string_alloc();
+
+    bool res = false;
+
+    /*DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(
+        &browser_options, SUBGHZ_APP_FILENAME_EXTENSION, &I_sub1_10px);
+    browser_options.base_path = SUBGHZ_APP_FOLDER;
+
+    // Input events and views are managed by file_select
+    bool res = dialog_file_browser_show(
+        subghz->dialogs, subghz->file_path, subghz->file_path, &browser_options);
+
+    if(res) {*/
+    //res = subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), true);
+    res = subghz_key_load(subghz, MEAL_PAGER_TMP_FILE); //, true);
+    //}
+
+    //furi_string_free(file_path);
+
+    return res;
+}
+
+/*bool subghz_file_available(SubGhz* subghz) {
+    furi_assert(subghz);
+    bool ret = true;
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    FS_Error fs_result =
+        storage_common_stat(storage, furi_string_get_cstr(subghz->file_path), NULL);
+
+    if(fs_result != FSE_OK) {
+        dialog_message_show_storage_error(subghz->dialogs, "File not available\n file/directory");
+        ret = false;
+    }
+
+    furi_record_close(RECORD_STORAGE);
+    return ret;
+}*/
+
+/*bool subghz_path_is_file(FuriString* path) {
+    return furi_string_end_with(path, SUBGHZ_APP_FILENAME_EXTENSION);
+}*/
+
+/*void subghz_lock(SubGhz* subghz) {
+    furi_assert(subghz);
+    subghz->lock = SubGhzLockOn;
+}*/
+
+/*void subghz_unlock(SubGhz* subghz) {
+    furi_assert(subghz);
+    subghz->lock = SubGhzLockOff;
+}*/
+
+/*bool subghz_is_locked(SubGhz* subghz) {
+    furi_assert(subghz);
+    return (subghz->lock == SubGhzLockOn);
+}*/
+
+/*void subghz_rx_key_state_set(SubGhz* subghz, SubGhzRxKeyState state) {
+    furi_assert(subghz);
+    subghz->rx_key_state = state;
+}*/
+
+/*SubGhzRxKeyState subghz_rx_key_state_get(SubGhz* subghz) {
+    furi_assert(subghz);
+    return subghz->rx_key_state;
+}*/

+ 83 - 0
non_catalog_apps/meal_pager/helpers/subghz/subghz_i.h

@@ -0,0 +1,83 @@
+#pragma once
+
+#include "subghz_types.h"
+#include "subghz_error_type.h"
+#include <lib/subghz/types.h>
+#include "subghz.h"
+#include "../meal_pager_storage.h"
+
+/*#include "views/receiver.h"
+#include "views/transmitter.h"
+#include "views/subghz_frequency_analyzer.h"
+#include "views/subghz_read_raw.h"
+
+#include <gui/gui.h>
+#include <dialogs/dialogs.h>
+#include <gui/scene_manager.h>
+#include <notification/notification_messages.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/widget.h>
+
+#include <subghz/scenes/subghz_scene.h>
+
+#include "subghz_history.h"
+
+#include <gui/modules/variable_item_list.h>
+#include <lib/toolbox/path.h>
+
+#include "rpc/rpc_app.h"
+
+#include "helpers/subghz_threshold_rssi.h"
+
+
+*/
+#include "subghz_txrx.h"
+
+#define SUBGHZ_MAX_LEN_NAME 64
+
+typedef struct SubGhz SubGhz;
+
+struct SubGhz {
+    SubGhzTxRx* txrx;
+    FuriString* file_path;
+    //FuriString* file_path_tmp;
+    //char file_name_tmp[SUBGHZ_MAX_LEN_NAME]; // just left it in to make the object not empty
+    //SubGhzNotificationState state_notifications;
+
+    /*SubGhzViewReceiver* subghz_receiver;
+    SubGhzViewTransmitter* subghz_transmitter;
+
+    SubGhzFrequencyAnalyzer* subghz_frequency_analyzer;
+    SubGhzReadRAW* subghz_read_raw;*/
+
+    //SubGhzProtocolFlag filter;
+    //FuriString* error_str;
+    //SubGhzLock lock;
+    //SubGhzThresholdRssi* threshold_rssi;
+    //SubGhzRxKeyState rx_key_state;
+    //SubGhzHistory* history;
+    SubGhzLoadTypeFile load_type_file;
+    //void* rpc_ctx;
+};
+
+//void subghz_set_default_preset(SubGhz* subghz);
+//void subghz_blink_start(SubGhz* subghz);
+//void subghz_blink_stop(SubGhz* subghz);
+
+//bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format);
+//void subghz_dialog_message_show_only_rx(SubGhz* subghz);
+
+bool subghz_key_load(SubGhz* subghz, const char* file_path); //, bool show_dialog);
+bool subghz_load_protocol_from_file(SubGhz* subghz);
+//bool subghz_file_available(SubGhz* subghz);
+//SubGhzLoadTypeFile subghz_get_load_type_file(SubGhz* subghz);
+
+//void subghz_lock(SubGhz* subghz);
+//void subghz_unlock(SubGhz* subghz);
+//bool subghz_is_locked(SubGhz* subghz);
+
+//void subghz_rx_key_state_set(SubGhz* subghz, SubGhzRxKeyState state);
+//SubGhzRxKeyState subghz_rx_key_state_get(SubGhz* subghz);

+ 630 - 0
non_catalog_apps/meal_pager/helpers/subghz/subghz_txrx.c

@@ -0,0 +1,630 @@
+#include "subghz_txrx_i.h"
+
+#include <lib/subghz/protocols/protocol_items.h>
+#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
+#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
+
+#define TAG "SubGhz"
+
+static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) {
+    UNUSED(instance);
+    uint8_t attempts = 5;
+    while(--attempts > 0) {
+        if(furi_hal_power_enable_otg()) break;
+    }
+    if(attempts == 0) {
+        if(furi_hal_power_get_usb_voltage() < 4.5f) {
+            FURI_LOG_E(
+                TAG,
+                "Error power otg enable. BQ2589 check otg fault = %d",
+                furi_hal_power_check_otg_fault() ? 1 : 0);
+        }
+    }
+}
+
+static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) {
+    UNUSED(instance);
+    if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
+}
+
+SubGhzTxRx* subghz_txrx_alloc() {
+    SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx));
+    instance->setting = subghz_setting_alloc();
+    subghz_setting_load(instance->setting, EXT_PATH("subghz/assets/setting_user"));
+
+    instance->preset = malloc(sizeof(SubGhzRadioPreset));
+    instance->preset->name = furi_string_alloc();
+    subghz_txrx_set_preset(
+        instance, "AM650", subghz_setting_get_default_frequency(instance->setting), NULL, 0);
+
+    instance->txrx_state = SubGhzTxRxStateSleep;
+
+    subghz_txrx_hopper_set_state(instance, SubGhzHopperStateOFF);
+    subghz_txrx_speaker_set_state(instance, SubGhzSpeakerStateDisable);
+
+    instance->worker = subghz_worker_alloc();
+    instance->fff_data = flipper_format_string_alloc();
+
+    instance->environment = subghz_environment_alloc();
+    instance->is_database_loaded =
+        subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_NAME);
+    subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_USER_NAME);
+    subghz_environment_set_came_atomo_rainbow_table_file_name(
+        instance->environment, SUBGHZ_CAME_ATOMO_DIR_NAME);
+    subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
+        instance->environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME);
+    subghz_environment_set_nice_flor_s_rainbow_table_file_name(
+        instance->environment, SUBGHZ_NICE_FLOR_S_DIR_NAME);
+    subghz_environment_set_protocol_registry(
+        instance->environment, (void*)&subghz_protocol_registry);
+    instance->receiver = subghz_receiver_alloc_init(instance->environment);
+
+    subghz_worker_set_overrun_callback(
+        instance->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
+    subghz_worker_set_pair_callback(
+        instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
+    subghz_worker_set_context(instance->worker, instance->receiver);
+
+    //set default device External
+    subghz_devices_init();
+    instance->radio_device_type = SubGhzRadioDeviceTypeInternal;
+    instance->radio_device_type =
+        subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101);
+
+    FURI_LOG_D(TAG, "completed TXRX alloc");
+
+    return instance;
+}
+
+void subghz_txrx_free(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    FURI_LOG_D(TAG, "freeing TXRX");
+
+    if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) {
+        subghz_txrx_radio_device_power_off(instance);
+        subghz_devices_end(instance->radio_device);
+    }
+
+    subghz_devices_deinit();
+
+    subghz_worker_free(instance->worker);
+    subghz_receiver_free(instance->receiver);
+    subghz_environment_free(instance->environment);
+    flipper_format_free(instance->fff_data);
+    furi_string_free(instance->preset->name);
+    subghz_setting_free(instance->setting);
+
+    free(instance->preset);
+    free(instance);
+}
+
+/*bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->is_database_loaded;
+}*/
+
+void subghz_txrx_set_preset(
+    SubGhzTxRx* instance,
+    const char* preset_name,
+    uint32_t frequency,
+    uint8_t* preset_data,
+    size_t preset_data_size) {
+    furi_assert(instance);
+    furi_string_set(instance->preset->name, preset_name);
+    SubGhzRadioPreset* preset = instance->preset;
+    preset->frequency = frequency;
+    preset->data = preset_data;
+    preset->data_size = preset_data_size;
+}
+
+const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset) {
+    UNUSED(instance);
+    const char* preset_name = "";
+    if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) {
+        preset_name = "AM270";
+    } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) {
+        preset_name = "AM650";
+    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) {
+        preset_name = "FM238";
+    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) {
+        preset_name = "FM476";
+    } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) {
+        preset_name = "CUSTOM";
+    } else {
+        FURI_LOG_E(TAG, "Unknown preset");
+    }
+    return preset_name;
+}
+
+/*SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return *instance->preset;
+}*/
+
+/*void subghz_txrx_get_frequency_and_modulation(
+    SubGhzTxRx* instance,
+    FuriString* frequency,
+    FuriString* modulation) {
+    furi_assert(instance);
+    SubGhzRadioPreset* preset = instance->preset;
+    if(frequency != NULL) {
+        furi_string_printf(
+            frequency,
+            "%03ld.%02ld",
+            preset->frequency / 1000000 % 1000,
+            preset->frequency / 10000 % 100);
+    }
+    if(modulation != NULL) {
+        furi_string_printf(modulation, "%.2s", furi_string_get_cstr(preset->name));
+    }
+}*/
+
+static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) {
+    furi_assert(instance);
+    subghz_devices_reset(instance->radio_device);
+    subghz_devices_idle(instance->radio_device);
+    subghz_devices_load_preset(instance->radio_device, FuriHalSubGhzPresetCustom, preset_data);
+    instance->txrx_state = SubGhzTxRxStateIDLE;
+    FURI_LOG_D(TAG, "completed subghz_txrx_begin");
+}
+
+/*static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) {
+    furi_assert(instance);
+
+    furi_assert(
+        instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep);
+
+    subghz_devices_idle(instance->radio_device);
+
+    uint32_t value = subghz_devices_set_frequency(instance->radio_device, frequency);
+    subghz_devices_flush_rx(instance->radio_device);
+    subghz_txrx_speaker_on(instance);
+
+    subghz_devices_start_async_rx(
+        instance->radio_device, subghz_worker_rx_callback, instance->worker);
+    subghz_worker_start(instance->worker);
+    instance->txrx_state = SubGhzTxRxStateRx;
+    return value;
+}*/
+
+static void subghz_txrx_idle(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->txrx_state != SubGhzTxRxStateSleep) {
+        subghz_devices_idle(instance->radio_device);
+        subghz_txrx_speaker_off(instance);
+        instance->txrx_state = SubGhzTxRxStateIDLE;
+    }
+    FURI_LOG_D(TAG, "completed subghz_txrx_idle");
+}
+
+/*static void subghz_txrx_rx_end(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    furi_assert(instance->txrx_state == SubGhzTxRxStateRx);
+
+    if(subghz_worker_is_running(instance->worker)) {
+        subghz_worker_stop(instance->worker);
+        subghz_devices_stop_async_rx(instance->radio_device);
+    }
+    subghz_devices_idle(instance->radio_device);
+    subghz_txrx_speaker_off(instance);
+    instance->txrx_state = SubGhzTxRxStateIDLE;
+}*/
+
+/*void subghz_txrx_sleep(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    subghz_devices_sleep(instance->radio_device);
+    instance->txrx_state = SubGhzTxRxStateSleep;
+}*/
+
+static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) {
+    furi_assert(instance);
+    furi_assert(instance->txrx_state != SubGhzTxRxStateSleep);
+    subghz_devices_idle(instance->radio_device);
+    subghz_devices_set_frequency(instance->radio_device, frequency);
+
+    bool ret = subghz_devices_set_tx(instance->radio_device);
+    if(ret) {
+        subghz_txrx_speaker_on(instance);
+        instance->txrx_state = SubGhzTxRxStateTx;
+    }
+
+    FURI_LOG_D(TAG, "completed subghz_txrx_tx");
+    return ret;
+}
+
+SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format) {
+    furi_assert(instance);
+    furi_assert(flipper_format);
+
+    subghz_txrx_stop(instance);
+
+    SubGhzTxRxStartTxState ret = SubGhzTxRxStartTxStateErrorParserOthers;
+    FuriString* temp_str = furi_string_alloc();
+    uint32_t repeat = 200;
+
+    FURI_LOG_D(TAG, "starting loop in subghz_txrx_tx_start");
+    do {
+        FURI_LOG_D(TAG, "looping");
+        if(!flipper_format_rewind(flipper_format)) {
+            FURI_LOG_E(TAG, "Rewind error");
+            break;
+        }
+        if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
+            FURI_LOG_E(TAG, "Missing Protocol");
+            break;
+        }
+        if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) {
+            FURI_LOG_E(TAG, "Unable Repeat");
+            break;
+        }
+        //FURI_LOG_D(TAG, "File loaded");
+        ret = SubGhzTxRxStartTxStateOk;
+
+        SubGhzRadioPreset* preset = instance->preset;
+        instance->transmitter =
+            subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str));
+
+        if(instance->transmitter) {
+            FURI_LOG_D(TAG, "transmitter found");
+            if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) ==
+               SubGhzProtocolStatusOk) {
+                //if (false) {
+                FURI_LOG_D(TAG, "deserialization");
+                if(strcmp(furi_string_get_cstr(preset->name), "") != 0) {
+                    FURI_LOG_D(TAG, "got preset name");
+                    subghz_txrx_begin(
+                        instance,
+                        subghz_setting_get_preset_data_by_name(
+                            instance->setting, furi_string_get_cstr(preset->name)));
+                    FURI_LOG_D(TAG, "loaded preset by name");
+                    if(preset->frequency) {
+                        if(!subghz_txrx_tx(instance, preset->frequency)) {
+                            FURI_LOG_E(TAG, "Only Rx");
+                            ret = SubGhzTxRxStartTxStateErrorOnlyRx;
+                        }
+                        FURI_LOG_D(TAG, "got frequency");
+                    } else {
+                        ret = SubGhzTxRxStartTxStateErrorParserOthers;
+                    }
+
+                } else {
+                    FURI_LOG_E(
+                        TAG, "Unknown name preset \" %s \"", furi_string_get_cstr(preset->name));
+                    ret = SubGhzTxRxStartTxStateErrorParserOthers;
+                }
+
+                if(ret == SubGhzTxRxStartTxStateOk) {
+                    //Start TX
+                    FURI_LOG_D(TAG, "starting Async TX");
+                    subghz_devices_start_async_tx(
+                        instance->radio_device, subghz_transmitter_yield, instance->transmitter);
+                }
+            } else {
+                FURI_LOG_D(TAG, "no deserialization");
+                ret = SubGhzTxRxStartTxStateErrorParserOthers;
+            }
+        } else {
+            ret = SubGhzTxRxStartTxStateErrorParserOthers;
+        }
+        if(ret != SubGhzTxRxStartTxStateOk) {
+            FURI_LOG_D(TAG, "state not ok");
+            subghz_transmitter_free(instance->transmitter); // Crashes here
+            if(instance->txrx_state != SubGhzTxRxStateIDLE) {
+                subghz_txrx_idle(instance);
+            }
+        }
+    } while(false);
+    furi_string_free(temp_str);
+    return ret;
+}
+
+/*void subghz_txrx_rx_start(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    subghz_txrx_stop(instance);
+    subghz_txrx_begin(
+        instance,
+        subghz_setting_get_preset_data_by_name(
+            subghz_txrx_get_setting(instance), furi_string_get_cstr(instance->preset->name)));
+    subghz_txrx_rx(instance, instance->preset->frequency);
+}*/
+
+/*void subghz_txrx_set_need_save_callback(
+    SubGhzTxRx* instance,
+    SubGhzTxRxNeedSaveCallback callback,
+    void* context) {
+    furi_assert(instance);
+    instance->need_save_callback = callback;
+    instance->need_save_context = context;
+}*/
+
+static void subghz_txrx_tx_stop(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    furi_assert(instance->txrx_state == SubGhzTxRxStateTx);
+    //Stop TX
+    subghz_devices_stop_async_tx(instance->radio_device);
+    subghz_transmitter_stop(instance->transmitter);
+    subghz_transmitter_free(instance->transmitter);
+
+    //if protocol dynamic then we save the last upload
+    /*if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) {
+        if(instance->need_save_callback) {
+            instance->need_save_callback(instance->need_save_context);
+        }
+    }*/
+    subghz_txrx_idle(instance);
+    subghz_txrx_speaker_off(instance);
+}
+
+FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->fff_data;
+}
+
+SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->setting;
+}
+
+void subghz_txrx_stop(SubGhzTxRx* instance) {
+    furi_assert(instance);
+
+    switch(instance->txrx_state) {
+    case SubGhzTxRxStateTx:
+        subghz_txrx_tx_stop(instance);
+        subghz_txrx_speaker_unmute(instance);
+        break;
+    case SubGhzTxRxStateRx:
+        //subghz_txrx_rx_end(instance);
+        //subghz_txrx_speaker_mute(instance);
+        break;
+
+    default:
+        break;
+    }
+}
+
+/*void subghz_txrx_hopper_update(SubGhzTxRx* instance) {
+    furi_assert(instance);
+
+    switch(instance->hopper_state) {
+    case SubGhzHopperStateOFF:
+    case SubGhzHopperStatePause:
+        return;
+    case SubGhzHopperStateRSSITimeOut:
+        if(instance->hopper_timeout != 0) {
+            instance->hopper_timeout--;
+            return;
+        }
+        break;
+    default:
+        break;
+    }
+    float rssi = -127.0f;
+    if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) {
+        // See RSSI Calculation timings in CC1101 17.3 RSSI
+        rssi = subghz_devices_get_rssi(instance->radio_device);
+
+        // Stay if RSSI is high enough
+        if(rssi > -90.0f) {
+            instance->hopper_timeout = 10;
+            instance->hopper_state = SubGhzHopperStateRSSITimeOut;
+            return;
+        }
+    } else {
+        instance->hopper_state = SubGhzHopperStateRunnig;
+    }
+    // Select next frequency
+    if(instance->hopper_idx_frequency <
+       subghz_setting_get_hopper_frequency_count(instance->setting) - 1) {
+        instance->hopper_idx_frequency++;
+    } else {
+        instance->hopper_idx_frequency = 0;
+    }
+
+    if(instance->txrx_state == SubGhzTxRxStateRx) {
+        subghz_txrx_rx_end(instance);
+    };
+    if(instance->txrx_state == SubGhzTxRxStateIDLE) {
+        subghz_receiver_reset(instance->receiver);
+        instance->preset->frequency =
+            subghz_setting_get_hopper_frequency(instance->setting, instance->hopper_idx_frequency);
+        subghz_txrx_rx(instance, instance->preset->frequency);
+    }
+}*/
+
+/*SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->hopper_state;
+}*/
+
+void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state) {
+    furi_assert(instance);
+    instance->hopper_state = state;
+}
+
+/*void subghz_txrx_hopper_unpause(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->hopper_state == SubGhzHopperStatePause) {
+        instance->hopper_state = SubGhzHopperStateRunnig;
+    }
+}*/
+
+/*void subghz_txrx_hopper_pause(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->hopper_state == SubGhzHopperStateRunnig) {
+        instance->hopper_state = SubGhzHopperStatePause;
+    }
+}*/
+
+void subghz_txrx_speaker_on(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_acquire(30)) {
+            subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker);
+        } else {
+            instance->speaker_state = SubGhzSpeakerStateDisable;
+        }
+    }
+}
+
+void subghz_txrx_speaker_off(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state != SubGhzSpeakerStateDisable) {
+        if(furi_hal_speaker_is_mine()) {
+            subghz_devices_set_async_mirror_pin(instance->radio_device, NULL);
+            furi_hal_speaker_release();
+            if(instance->speaker_state == SubGhzSpeakerStateShutdown)
+                instance->speaker_state = SubGhzSpeakerStateDisable;
+        }
+    }
+}
+
+/*void subghz_txrx_speaker_mute(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_is_mine()) {
+            subghz_devices_set_async_mirror_pin(instance->radio_device, NULL);
+        }
+    }
+}*/
+
+void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_is_mine()) {
+            subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker);
+        }
+    }
+}
+
+void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state) {
+    furi_assert(instance);
+    instance->speaker_state = state;
+}
+
+/*SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->speaker_state;
+}*/
+
+bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol) {
+    furi_assert(instance);
+    furi_assert(name_protocol);
+    bool res = false;
+    instance->decoder_result =
+        subghz_receiver_search_decoder_base_by_name(instance->receiver, name_protocol);
+    if(instance->decoder_result) {
+        res = true;
+    }
+    return res;
+}
+
+SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->decoder_result;
+}
+
+/*bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return (
+        (instance->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) ==
+        SubGhzProtocolFlag_Save);
+}*/
+
+/*bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type) {
+    furi_assert(instance);
+    const SubGhzProtocol* protocol = instance->decoder_result->protocol;
+    if(check_type) {
+        return (
+            ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) &&
+            protocol->encoder->deserialize && protocol->type == SubGhzProtocolTypeStatic);
+    }
+    return (
+        ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) &&
+        protocol->encoder->deserialize);
+}*/
+
+/*void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter) {
+    furi_assert(instance);
+    subghz_receiver_set_filter(instance->receiver, filter);
+}*/
+
+/*void subghz_txrx_set_rx_calback(
+    SubGhzTxRx* instance,
+    SubGhzReceiverCallback callback,
+    void* context) {
+    subghz_receiver_set_rx_callback(instance->receiver, callback, context);
+}*/
+
+void subghz_txrx_set_raw_file_encoder_worker_callback_end(
+    SubGhzTxRx* instance,
+    SubGhzProtocolEncoderRAWCallbackEnd callback,
+    void* context) {
+    subghz_protocol_raw_file_encoder_worker_set_callback_end(
+        (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance(instance->transmitter),
+        callback,
+        context);
+}
+
+bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name) {
+    furi_assert(instance);
+
+    bool is_connect = false;
+    bool is_otg_enabled = furi_hal_power_is_otg_enabled();
+
+    if(!is_otg_enabled) {
+        subghz_txrx_radio_device_power_on(instance);
+    }
+
+    const SubGhzDevice* device = subghz_devices_get_by_name(name);
+    if(device) {
+        is_connect = subghz_devices_is_connect(device);
+    }
+
+    if(!is_otg_enabled) {
+        subghz_txrx_radio_device_power_off(instance);
+    }
+    return is_connect;
+}
+
+SubGhzRadioDeviceType
+    subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type) {
+    furi_assert(instance);
+
+    if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 &&
+       subghz_txrx_radio_device_is_external_connected(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
+        subghz_txrx_radio_device_power_on(instance);
+        instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME);
+        subghz_devices_begin(instance->radio_device);
+        instance->radio_device_type = SubGhzRadioDeviceTypeExternalCC1101;
+    } else {
+        subghz_txrx_radio_device_power_off(instance);
+        if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) {
+            subghz_devices_end(instance->radio_device);
+        }
+        instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
+        instance->radio_device_type = SubGhzRadioDeviceTypeInternal;
+    }
+
+    return instance->radio_device_type;
+}
+
+/*SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->radio_device_type;
+}*/
+
+/*float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return subghz_devices_get_rssi(instance->radio_device);
+}*/
+
+const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return subghz_devices_get_name(instance->radio_device);
+}
+
+bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency) {
+    furi_assert(instance);
+    return subghz_devices_is_frequency_valid(instance->radio_device, frequency);
+}

+ 336 - 0
non_catalog_apps/meal_pager/helpers/subghz/subghz_txrx.h

@@ -0,0 +1,336 @@
+#pragma once
+
+#include "subghz_types.h"
+
+#include <lib/subghz/subghz_worker.h>
+#include <lib/subghz/subghz_setting.h>
+#include <lib/subghz/receiver.h>
+#include <lib/subghz/transmitter.h>
+#include <lib/subghz/protocols/raw.h>
+#include <lib/subghz/devices/devices.h>
+
+typedef struct SubGhzTxRx SubGhzTxRx;
+
+typedef void (*SubGhzTxRxNeedSaveCallback)(void* context);
+
+typedef enum {
+    SubGhzTxRxStartTxStateOk,
+    SubGhzTxRxStartTxStateErrorOnlyRx,
+    SubGhzTxRxStartTxStateErrorParserOthers,
+} SubGhzTxRxStartTxState;
+
+/**
+ * Allocate SubGhzTxRx
+ * 
+ * @return SubGhzTxRx* pointer to SubGhzTxRx
+ */
+SubGhzTxRx* subghz_txrx_alloc();
+
+/**
+ * Free SubGhzTxRx
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+void subghz_txrx_free(SubGhzTxRx* instance);
+
+/**
+ * Check if the database is loaded
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return bool True if the database is loaded
+ */
+//bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance);
+
+/**
+ * Set preset 
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param preset_name Name of preset
+ * @param frequency Frequency in Hz
+ * @param preset_data Data of preset
+ * @param preset_data_size Size of preset data
+ */
+void subghz_txrx_set_preset(
+    SubGhzTxRx* instance,
+    const char* preset_name,
+    uint32_t frequency,
+    uint8_t* preset_data,
+    size_t preset_data_size);
+
+/**
+ * Get name of preset
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param preset String of preset 
+ * @return const char*  Name of preset
+ */
+const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset);
+
+/**
+ * Get of preset
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzRadioPreset Preset
+ */
+//SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance);
+
+/**
+ * Get string frequency and modulation
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param frequency Pointer to a string frequency
+ * @param modulation Pointer to a string modulation
+ */
+/*void subghz_txrx_get_frequency_and_modulation(
+    SubGhzTxRx* instance,
+    FuriString* frequency,
+    FuriString* modulation);*/
+
+/**
+ * Start TX CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param flipper_format Pointer to a FlipperFormat
+ * @return SubGhzTxRxStartTxState 
+ */
+SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format);
+
+/**
+ * Start RX CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_rx_start(SubGhzTxRx* instance);
+
+/**
+ * Stop TX/RX CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+void subghz_txrx_stop(SubGhzTxRx* instance);
+
+/**
+ * Set sleep mode CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_sleep(SubGhzTxRx* instance);
+
+/**
+ * Update frequency CC1101 in automatic mode (hopper)
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_hopper_update(SubGhzTxRx* instance);
+
+/**
+ * Get state hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzHopperState 
+ */
+//SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance);
+
+/**
+ * Set state hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param state State hopper
+ */
+void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state);
+
+/**
+ * Unpause hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_hopper_unpause(SubGhzTxRx* instance);
+
+/**
+ * Set pause hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_hopper_pause(SubGhzTxRx* instance);
+
+/**
+ * Speaker on
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+void subghz_txrx_speaker_on(SubGhzTxRx* instance);
+
+/**
+ * Speaker off
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+void subghz_txrx_speaker_off(SubGhzTxRx* instance);
+
+/**
+ * Speaker mute
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+//void subghz_txrx_speaker_mute(SubGhzTxRx* instance);
+
+/**
+ * Speaker unmute
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+void subghz_txrx_speaker_unmute(SubGhzTxRx* instance);
+
+/**
+ * Set state speaker
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @param state State speaker
+ */
+void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state);
+
+/**
+ * Get state speaker
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @return SubGhzSpeakerState 
+ */
+//SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance);
+
+/**
+ * load decoder by name protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param name_protocol Name protocol
+ * @return bool True if the decoder is loaded 
+ */
+bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol);
+
+/**
+ * Get decoder
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzProtocolDecoderBase* Pointer to a SubGhzProtocolDecoderBase
+ */
+SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance);
+
+/**
+ * Set callback for save data
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param callback Callback for save data
+ * @param context Context for callback
+ */
+/*void subghz_txrx_set_need_save_callback(
+    SubGhzTxRx* instance,
+    SubGhzTxRxNeedSaveCallback callback,
+    void* context);*/
+
+/**
+ * Get pointer to a load data key
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return FlipperFormat* 
+ */
+FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance);
+
+/**
+ * Get pointer to a SugGhzSetting
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzSetting* 
+ */
+SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance);
+
+/**
+ * Is it possible to save this protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @return bool True if it is possible to save this protocol
+ */
+//bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance);
+
+/**
+ * Is it possible to send this protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @return bool True if it is possible to send this protocol
+ */
+//bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type);
+
+/**
+ * Set filter, what types of decoder to use 
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param filter Filter
+ */
+//void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter);
+
+/**
+ * Set callback for receive data
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param callback Callback for receive data
+ * @param context Context for callback
+ */
+/*void subghz_txrx_set_rx_calback(
+    SubGhzTxRx* instance,
+    SubGhzReceiverCallback callback,
+    void* context);*/
+
+/**
+ * Set callback for Raw decoder, end of data transfer  
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param callback Callback for Raw decoder, end of data transfer 
+ * @param context Context for callback
+ */
+void subghz_txrx_set_raw_file_encoder_worker_callback_end(
+    SubGhzTxRx* instance,
+    SubGhzProtocolEncoderRAWCallbackEnd callback,
+    void* context);
+
+/* Checking if an external radio device is connected
+* 
+* @param instance Pointer to a SubGhzTxRx
+* @param name Name of external radio device
+* @return bool True if is connected to the external radio device
+*/
+bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name);
+
+/* Set the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @param radio_device_type Radio device type
+* @return SubGhzRadioDeviceType Type of installed radio device
+*/
+SubGhzRadioDeviceType
+    subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type);
+
+/* Get the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @return SubGhzRadioDeviceType Type of installed radio device
+*/
+//SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance);
+
+/* Get RSSI the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @return float RSSI
+*/
+//float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance);
+
+/* Get name the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @return const char* Name of installed radio device
+*/
+const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance);
+
+/* Get get intelligence whether frequency the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @return bool True if the frequency is valid
+*/
+bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency);

+ 29 - 0
non_catalog_apps/meal_pager/helpers/subghz/subghz_txrx_i.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include "subghz_txrx.h"
+
+struct SubGhzTxRx {
+    SubGhzWorker* worker;
+
+    SubGhzEnvironment* environment;
+    SubGhzReceiver* receiver;
+    SubGhzTransmitter* transmitter;
+    SubGhzProtocolDecoderBase* decoder_result;
+    FlipperFormat* fff_data;
+
+    SubGhzRadioPreset* preset;
+    SubGhzSetting* setting;
+
+    uint8_t hopper_timeout;
+    uint8_t hopper_idx_frequency;
+    bool is_database_loaded;
+    SubGhzHopperState hopper_state;
+
+    SubGhzTxRxState txrx_state;
+    SubGhzSpeakerState speaker_state;
+    const SubGhzDevice* radio_device;
+    SubGhzRadioDeviceType radio_device_type;
+
+    SubGhzTxRxNeedSaveCallback need_save_callback;
+    void* need_save_context;
+};

+ 90 - 0
non_catalog_apps/meal_pager/helpers/subghz/subghz_types.h

@@ -0,0 +1,90 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+
+/** SubGhzNotification state */
+typedef enum {
+    SubGhzNotificationStateStarting,
+    SubGhzNotificationStateIDLE,
+    SubGhzNotificationStateTx,
+    SubGhzNotificationStateRx,
+    SubGhzNotificationStateRxDone,
+} SubGhzNotificationState;
+
+/** SubGhzTxRx state */
+typedef enum {
+    SubGhzTxRxStateIDLE,
+    SubGhzTxRxStateRx,
+    SubGhzTxRxStateTx,
+    SubGhzTxRxStateSleep,
+} SubGhzTxRxState;
+
+/** SubGhzHopperState state */
+typedef enum {
+    SubGhzHopperStateOFF,
+    SubGhzHopperStateRunnig,
+    SubGhzHopperStatePause,
+    SubGhzHopperStateRSSITimeOut,
+} SubGhzHopperState;
+
+/** SubGhzSpeakerState state */
+typedef enum {
+    SubGhzSpeakerStateDisable,
+    SubGhzSpeakerStateShutdown,
+    SubGhzSpeakerStateEnable,
+} SubGhzSpeakerState;
+
+/** SubGhzRadioDeviceType */
+typedef enum {
+    SubGhzRadioDeviceTypeAuto,
+    SubGhzRadioDeviceTypeInternal,
+    SubGhzRadioDeviceTypeExternalCC1101,
+} SubGhzRadioDeviceType;
+
+/** SubGhzRxKeyState state */
+typedef enum {
+    SubGhzRxKeyStateIDLE,
+    SubGhzRxKeyStateNoSave,
+    SubGhzRxKeyStateNeedSave,
+    SubGhzRxKeyStateBack,
+    SubGhzRxKeyStateStart,
+    SubGhzRxKeyStateAddKey,
+    SubGhzRxKeyStateExit,
+    SubGhzRxKeyStateRAWLoad,
+    SubGhzRxKeyStateRAWSave,
+} SubGhzRxKeyState;
+
+/** SubGhzLoadKeyState state */
+typedef enum {
+    SubGhzLoadKeyStateUnknown,
+    SubGhzLoadKeyStateOK,
+    SubGhzLoadKeyStateParseErr,
+    SubGhzLoadKeyStateProtocolDescriptionErr,
+} SubGhzLoadKeyState;
+
+/** SubGhzLock */
+typedef enum {
+    SubGhzLockOff,
+    SubGhzLockOn,
+} SubGhzLock;
+
+typedef enum {
+    SubGhzViewIdMenu,
+    SubGhzViewIdReceiver,
+    SubGhzViewIdPopup,
+    SubGhzViewIdTextInput,
+    SubGhzViewIdWidget,
+    SubGhzViewIdTransmitter,
+    SubGhzViewIdVariableItemList,
+    SubGhzViewIdFrequencyAnalyzer,
+    SubGhzViewIdReadRAW,
+
+} SubGhzViewId;
+
+/** SubGhz load type file */
+typedef enum {
+    SubGhzLoadTypeFileNoLoad,
+    SubGhzLoadTypeFileKey,
+    SubGhzLoadTypeFileRaw,
+} SubGhzLoadTypeFile;

BIN=BIN
non_catalog_apps/meal_pager/icons/meal_pager_10px.png


+ 145 - 0
non_catalog_apps/meal_pager/meal_pager.c

@@ -0,0 +1,145 @@
+#include "meal_pager.h"
+
+bool meal_pager_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+void meal_pager_tick_event_callback(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    scene_manager_handle_tick_event(app->scene_manager);
+}
+
+//leave app if back button pressed
+bool meal_pager_navigation_event_callback(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+Meal_Pager* meal_pager_app_alloc() {
+    Meal_Pager* app = malloc(sizeof(Meal_Pager));
+    app->gui = furi_record_open(RECORD_GUI);
+    app->notification = furi_record_open(RECORD_NOTIFICATION);
+
+    //Turn backlight on, believe me this makes testing your app easier
+    notification_message(app->notification, &sequence_display_backlight_on);
+
+    //Scene additions
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue(app->view_dispatcher);
+
+    app->scene_manager = scene_manager_alloc(&meal_pager_scene_handlers, app);
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    view_dispatcher_set_navigation_event_callback(
+        app->view_dispatcher, meal_pager_navigation_event_callback);
+    view_dispatcher_set_tick_event_callback(
+        app->view_dispatcher, meal_pager_tick_event_callback, 100);
+    view_dispatcher_set_custom_event_callback(
+        app->view_dispatcher, meal_pager_custom_event_callback);
+    app->submenu = submenu_alloc();
+
+    // Set defaults, in case no config loaded
+    app->haptic = 1;
+    app->speaker = 1;
+    app->led = 1;
+    app->save_settings = 1;
+    app->pager_type = 0;
+    app->first_station = 0;
+    app->first_station_char = "0";
+    app->last_station = 10;
+    app->last_station_char = "10";
+    app->first_pager = 0;
+    app->first_pager_char = "0";
+    app->last_pager = 31;
+    app->last_pager_char = "31";
+    app->stop_transmit = false;
+    app->repeats = 1;
+    app->repeats_char = "1";
+
+    // Used for File Browser
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+    app->file_path = furi_string_alloc();
+
+    app->subghz = subghz_alloc();
+
+    // Load configs
+    meal_pager_read_settings(app);
+
+    view_dispatcher_add_view(
+        app->view_dispatcher, Meal_PagerViewIdMenu, submenu_get_view(app->submenu));
+    app->meal_pager_startscreen = meal_pager_startscreen_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        Meal_PagerViewIdStartscreen,
+        meal_pager_startscreen_get_view(app->meal_pager_startscreen));
+    app->meal_pager_transmit = meal_pager_transmit_alloc(app);
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        Meal_PagerViewIdTransmit,
+        meal_pager_transmit_get_view(app->meal_pager_transmit));
+
+    app->variable_item_list = variable_item_list_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        Meal_PagerViewIdSettings,
+        variable_item_list_get_view(app->variable_item_list));
+
+    //End Scene Additions
+
+    return app;
+}
+
+void meal_pager_app_free(Meal_Pager* app) {
+    furi_assert(app);
+
+    // Scene manager
+    scene_manager_free(app->scene_manager);
+
+    // View Dispatcher
+    view_dispatcher_remove_view(app->view_dispatcher, Meal_PagerViewIdMenu);
+    view_dispatcher_remove_view(app->view_dispatcher, Meal_PagerViewIdTransmit);
+    view_dispatcher_remove_view(app->view_dispatcher, Meal_PagerViewIdSettings);
+    submenu_free(app->submenu);
+
+    view_dispatcher_free(app->view_dispatcher);
+    furi_record_close(RECORD_GUI);
+
+    app->gui = NULL;
+    app->notification = NULL;
+
+    // Close File Browser
+    furi_record_close(RECORD_DIALOGS);
+    furi_string_free(app->file_path);
+
+    subghz_free(app->subghz);
+
+    //Remove whatever is left
+    free(app);
+}
+
+int32_t meal_pager_app(void* p) {
+    UNUSED(p);
+    FURI_LOG_D(TAG, "Started Meal Pager");
+
+    Meal_Pager* app = meal_pager_app_alloc();
+
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+    scene_manager_next_scene(
+        app->scene_manager, Meal_PagerSceneStartscreen); //Start with start screen
+    //scene_manager_next_scene(app->scene_manager, Meal_PagerSceneMenu); //if you want to directly start with Menu
+
+    furi_hal_power_suppress_charge_enter();
+
+    view_dispatcher_run(app->view_dispatcher);
+
+    meal_pager_save_settings(app);
+
+    furi_hal_power_suppress_charge_exit();
+    meal_pager_app_free(app);
+
+    return 0;
+}

+ 2 - 0
non_catalog_apps/meal_pager/meal_pager.h

@@ -0,0 +1,2 @@
+#pragma once
+#include "meal_pager_i.h"

+ 96 - 0
non_catalog_apps/meal_pager/meal_pager_i.h

@@ -0,0 +1,96 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <stdlib.h>
+#include <dialogs/dialogs.h>
+#include <notification/notification_messages.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/variable_item_list.h>
+#include <gui/modules/button_menu.h>
+#include <gui/modules/dialog_ex.h>
+#include "scenes/meal_pager_scene.h"
+#include "views/meal_pager_startscreen.h"
+#include "views/meal_pager_transmit.h"
+#include "helpers/meal_pager_storage.h"
+#include "helpers/subghz/subghz_types.h"
+#include "helpers/subghz/subghz.h"
+
+#define TAG "Meal_Pager"
+
+#define SUBGHZ_APP_EXTENSION ".sub"
+#define SUBGHZ_APP_FOLDER ANY_PATH("subghz")
+
+typedef struct Meal_PagerTransmit Meal_PagerTransmit;
+typedef struct SubGhz SubGhz;
+
+typedef struct {
+    Gui* gui;
+    NotificationApp* notification;
+    SubGhzNotificationState state_notifications;
+    ViewDispatcher* view_dispatcher;
+    Submenu* submenu;
+    SubGhz* subghz;
+    SceneManager* scene_manager;
+    VariableItemList* variable_item_list;
+    Meal_PagerStartscreen* meal_pager_startscreen;
+    Meal_PagerTransmit* meal_pager_transmit;
+    DialogsApp* dialogs; // File Browser
+    FuriString* file_path; // File Browser
+    uint32_t haptic;
+    uint32_t speaker;
+    uint32_t led;
+    uint32_t save_settings;
+    uint32_t pager_type;
+    uint32_t first_station;
+    char* first_station_char;
+    uint32_t last_station;
+    char* last_station_char;
+    uint32_t first_pager;
+    char* first_pager_char;
+    uint32_t last_pager;
+    char* last_pager_char;
+    uint32_t current_station;
+    uint32_t current_pager;
+    bool stop_transmit;
+    uint32_t repeats;
+    char* repeats_char;
+} Meal_Pager;
+
+typedef enum {
+    Meal_PagerViewIdStartscreen,
+    Meal_PagerViewIdMenu,
+    Meal_PagerViewIdTransmit,
+    Meal_PagerViewIdSettings,
+} Meal_PagerViewId;
+
+typedef enum {
+    Meal_PagerPagerTypeT119,
+    Meal_PagerPagerTypeTD157,
+    Meal_PagerPagerTypeTD165,
+    Meal_PagerPagerTypeTD174,
+} Meal_PagerPagerType;
+
+typedef enum {
+    Meal_PagerHapticOff,
+    Meal_PagerHapticOn,
+} Meal_PagerHapticState;
+
+typedef enum {
+    Meal_PagerSpeakerOff,
+    Meal_PagerSpeakerOn,
+} Meal_PagerSpeakerState;
+
+typedef enum {
+    Meal_PagerLedOff,
+    Meal_PagerLedOn,
+} Meal_PagerLedState;
+
+typedef enum {
+    Meal_PagerSettingsOff,
+    Meal_PagerSettingsOn,
+} Meal_PagerSettingsStoreState;

+ 30 - 0
non_catalog_apps/meal_pager/scenes/meal_pager_scene.c

@@ -0,0 +1,30 @@
+#include "meal_pager_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const meal_pager_on_enter_handlers[])(void*) = {
+#include "meal_pager_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const meal_pager_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "meal_pager_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const meal_pager_on_exit_handlers[])(void* context) = {
+#include "meal_pager_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers meal_pager_scene_handlers = {
+    .on_enter_handlers = meal_pager_on_enter_handlers,
+    .on_event_handlers = meal_pager_on_event_handlers,
+    .on_exit_handlers = meal_pager_on_exit_handlers,
+    .scene_num = Meal_PagerSceneNum,
+};

+ 29 - 0
non_catalog_apps/meal_pager/scenes/meal_pager_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) Meal_PagerScene##id,
+typedef enum {
+#include "meal_pager_scene_config.h"
+    Meal_PagerSceneNum,
+} Meal_PagerScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers meal_pager_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "meal_pager_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "meal_pager_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "meal_pager_scene_config.h"
+#undef ADD_SCENE

+ 4 - 0
non_catalog_apps/meal_pager/scenes/meal_pager_scene_config.h

@@ -0,0 +1,4 @@
+ADD_SCENE(meal_pager, startscreen, Startscreen)
+ADD_SCENE(meal_pager, menu, Menu)
+ADD_SCENE(meal_pager, transmit, Transmit)
+ADD_SCENE(meal_pager, settings, Settings)

+ 81 - 0
non_catalog_apps/meal_pager/scenes/meal_pager_scene_menu.c

@@ -0,0 +1,81 @@
+#include "../meal_pager_i.h"
+#include "../helpers/meal_pager_led.h"
+#include "../views/meal_pager_transmit.h"
+
+enum SubmenuIndex {
+    SubmenuIndexTransmit = 10,
+    SubmenuIndexScene2,
+    SubmenuIndexScene3,
+    SubmenuIndexScene4,
+    SubmenuIndexScene5,
+    SubmenuIndexSettings,
+};
+
+void meal_pager_scene_menu_submenu_callback(void* context, uint32_t index) {
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void meal_pager_scene_menu_on_enter(void* context) {
+    Meal_Pager* app = context;
+
+    submenu_add_item(
+        app->submenu,
+        "Send Data",
+        SubmenuIndexTransmit,
+        meal_pager_scene_menu_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "Settings",
+        SubmenuIndexSettings,
+        meal_pager_scene_menu_submenu_callback,
+        app);
+
+    submenu_set_selected_item(
+        app->submenu, scene_manager_get_scene_state(app->scene_manager, Meal_PagerSceneMenu));
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, Meal_PagerViewIdMenu);
+}
+
+bool meal_pager_scene_menu_on_event(void* context, SceneManagerEvent event) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+    if(event.type == SceneManagerEventTypeBack) {
+        //exit app
+        scene_manager_stop(app->scene_manager);
+        view_dispatcher_stop(app->view_dispatcher);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexTransmit) {
+            scene_manager_set_scene_state(
+                app->scene_manager, Meal_PagerSceneMenu, SubmenuIndexTransmit);
+            scene_manager_next_scene(app->scene_manager, Meal_PagerSceneTransmit);
+            return true;
+        } else if(event.event == SubmenuIndexSettings) {
+            scene_manager_set_scene_state(
+                app->scene_manager, Meal_PagerSceneMenu, SubmenuIndexSettings);
+            scene_manager_next_scene(app->scene_manager, Meal_PagerSceneSettings);
+            return true;
+        } else if(event.event == Meal_PagerCustomEventViewTransmitterSendStop) {
+            app->state_notifications = SubGhzNotificationStateIDLE;
+            subghz_txrx_stop(app->subghz->txrx);
+            FURI_LOG_D(TAG, "Stop Event from Menu");
+            return true;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+        if(app->state_notifications == SubGhzNotificationStateTx) {
+            app->state_notifications = SubGhzNotificationStateIDLE;
+            subghz_txrx_stop(app->subghz->txrx);
+            meal_pager_blink_stop(app);
+            meal_pager_transmit_model_set_sending(app->meal_pager_transmit, 0);
+        }
+        return true;
+    }
+    return false;
+}
+
+void meal_pager_scene_menu_on_exit(void* context) {
+    Meal_Pager* app = context;
+    submenu_reset(app->submenu);
+}

+ 255 - 0
non_catalog_apps/meal_pager/scenes/meal_pager_scene_settings.c

@@ -0,0 +1,255 @@
+#include "../meal_pager_i.h"
+#include <lib/toolbox/value_index.h>
+
+enum SettingsIndex {
+    SettingsIndexHaptic = 10,
+    SettingsIndexValue1,
+    SettingsIndexValue2,
+};
+
+const char* const haptic_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t haptic_value[2] = {
+    Meal_PagerHapticOff,
+    Meal_PagerHapticOn,
+};
+
+const char* const pager_type_text[4] = {
+    "T119",
+    "TD157",
+    "TD165",
+    "TD174",
+};
+
+const uint32_t pager_type_value[4] = {
+    Meal_PagerPagerTypeT119,
+    Meal_PagerPagerTypeTD157,
+    Meal_PagerPagerTypeTD165,
+    Meal_PagerPagerTypeTD174,
+};
+
+const char* const speaker_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t speaker_value[2] = {
+    Meal_PagerSpeakerOff,
+    Meal_PagerSpeakerOn,
+};
+
+const char* const led_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t led_value[2] = {
+    Meal_PagerLedOff,
+    Meal_PagerLedOn,
+};
+
+const char* const settings_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t settings_value[2] = {
+    Meal_PagerSettingsOff,
+    Meal_PagerSettingsOn,
+};
+
+static void meal_pager_scene_settings_set_pager_type(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    variable_item_set_current_value_text(item, pager_type_text[index]);
+    app->pager_type = pager_type_value[index];
+}
+
+static void meal_pager_scene_settings_set_first_station(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint32_t index = variable_item_get_current_value_index(item);
+
+    snprintf(app->first_station_char, 20, "%lu", index);
+    variable_item_set_current_value_text(item, app->first_station_char);
+    app->first_station = index;
+}
+
+static void meal_pager_scene_settings_set_last_station(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint32_t index = variable_item_get_current_value_index(item);
+
+    snprintf(app->last_station_char, 20, "%lu", index);
+    variable_item_set_current_value_text(item, app->last_station_char);
+    app->last_station = index;
+}
+
+static void meal_pager_scene_settings_set_first_pager(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint32_t index = variable_item_get_current_value_index(item);
+
+    snprintf(app->first_pager_char, 20, "%lu", index);
+    variable_item_set_current_value_text(item, app->first_pager_char);
+    app->first_pager = index;
+}
+
+static void meal_pager_scene_settings_set_last_pager(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint32_t index = variable_item_get_current_value_index(item);
+
+    snprintf(app->last_pager_char, 20, "%lu", index);
+    variable_item_set_current_value_text(item, app->last_pager_char);
+    app->last_pager = index;
+}
+
+static void meal_pager_scene_settings_set_repeats(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint32_t index = variable_item_get_current_value_index(item);
+
+    snprintf(app->repeats_char, 20, "%lu", index);
+    variable_item_set_current_value_text(item, app->repeats_char);
+    app->repeats = index;
+}
+
+static void meal_pager_scene_settings_set_haptic(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    variable_item_set_current_value_text(item, haptic_text[index]);
+    app->haptic = haptic_value[index];
+}
+
+static void meal_pager_scene_settings_set_speaker(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, speaker_text[index]);
+    app->speaker = speaker_value[index];
+}
+
+static void meal_pager_scene_settings_set_led(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, led_text[index]);
+    app->led = led_value[index];
+}
+
+static void meal_pager_scene_settings_set_save_settings(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, settings_text[index]);
+    app->save_settings = settings_value[index];
+}
+
+void meal_pager_scene_settings_submenu_callback(void* context, uint32_t index) {
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void meal_pager_scene_settings_on_enter(void* context) {
+    Meal_Pager* app = context;
+    VariableItem* item;
+    uint8_t value_index;
+
+    // Pager Type
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "Pager Type:",
+        //4,
+        2,
+        meal_pager_scene_settings_set_pager_type,
+        app);
+    value_index = value_index_uint32(app->pager_type, pager_type_value, 4);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, pager_type_text[value_index]);
+
+    // First Station
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "First Station",
+        255,
+        meal_pager_scene_settings_set_first_station,
+        app);
+    variable_item_set_current_value_index(item, app->first_station);
+    snprintf(app->first_pager_char, 20, "%lu", app->first_station);
+    variable_item_set_current_value_text(item, app->first_station_char);
+
+    // Last Station
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "Last Station",
+        255,
+        meal_pager_scene_settings_set_last_station,
+        app);
+    variable_item_set_current_value_index(item, app->last_station);
+    snprintf(app->last_station_char, 20, "%lu", app->last_station);
+    variable_item_set_current_value_text(item, app->last_station_char);
+
+    // First Pager
+    item = variable_item_list_add(
+        app->variable_item_list, "First Pager", 99, meal_pager_scene_settings_set_first_pager, app);
+    variable_item_set_current_value_index(item, app->first_pager);
+    snprintf(app->first_pager_char, 20, "%lu", app->first_pager);
+    variable_item_set_current_value_text(item, app->first_pager_char);
+
+    // Last Pager
+    item = variable_item_list_add(
+        app->variable_item_list, "Last Pager", 99, meal_pager_scene_settings_set_last_pager, app);
+    variable_item_set_current_value_index(item, app->last_pager);
+    snprintf(app->last_pager_char, 20, "%lu", app->last_pager);
+    variable_item_set_current_value_text(item, app->last_pager_char);
+
+    // Repeat Attacks
+    item = variable_item_list_add(
+        app->variable_item_list, "Signal Repeat", 11, meal_pager_scene_settings_set_repeats, app);
+    variable_item_set_current_value_index(item, app->repeats);
+    snprintf(app->repeats_char, 20, "%lu", app->repeats);
+    variable_item_set_current_value_text(item, app->repeats_char);
+
+    // Vibro on/off
+    item = variable_item_list_add(
+        app->variable_item_list, "Vibro/Haptic:", 2, meal_pager_scene_settings_set_haptic, app);
+    value_index = value_index_uint32(app->haptic, haptic_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, haptic_text[value_index]);
+
+    // Sound on/off
+    item = variable_item_list_add(
+        app->variable_item_list, "Sound:", 2, meal_pager_scene_settings_set_speaker, app);
+    value_index = value_index_uint32(app->speaker, speaker_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, speaker_text[value_index]);
+
+    // LED Effects on/off
+    item = variable_item_list_add(
+        app->variable_item_list, "LED FX:", 2, meal_pager_scene_settings_set_led, app);
+    value_index = value_index_uint32(app->led, led_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, led_text[value_index]);
+
+    // Save Settings to File
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "Save Settings",
+        2,
+        meal_pager_scene_settings_set_save_settings,
+        app);
+    value_index = value_index_uint32(app->save_settings, settings_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, settings_text[value_index]);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, Meal_PagerViewIdSettings);
+}
+
+bool meal_pager_scene_settings_on_event(void* context, SceneManagerEvent event) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+    }
+    return consumed;
+}
+
+void meal_pager_scene_settings_on_exit(void* context) {
+    Meal_Pager* app = context;
+    variable_item_list_set_selected_item(app->variable_item_list, 0);
+    variable_item_list_reset(app->variable_item_list);
+}

+ 55 - 0
non_catalog_apps/meal_pager/scenes/meal_pager_scene_startscreen.c

@@ -0,0 +1,55 @@
+#include "../meal_pager_i.h"
+#include "../helpers/meal_pager_custom_event.h"
+#include "../views/meal_pager_startscreen.h"
+
+void meal_pager_scene_startscreen_callback(Meal_PagerCustomEvent event, void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void meal_pager_scene_startscreen_on_enter(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    meal_pager_startscreen_set_callback(
+        app->meal_pager_startscreen, meal_pager_scene_startscreen_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, Meal_PagerViewIdStartscreen);
+}
+
+bool meal_pager_scene_startscreen_on_event(void* context, SceneManagerEvent event) {
+    Meal_Pager* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case Meal_PagerCustomEventStartscreenLeft:
+        case Meal_PagerCustomEventStartscreenRight:
+            break;
+        case Meal_PagerCustomEventStartscreenUp:
+        case Meal_PagerCustomEventStartscreenDown:
+            break;
+        case Meal_PagerCustomEventStartscreenOk:
+            scene_manager_next_scene(app->scene_manager, Meal_PagerSceneMenu);
+            consumed = true;
+            break;
+        case Meal_PagerCustomEventStartscreenBack:
+            notification_message(app->notification, &sequence_reset_red);
+            notification_message(app->notification, &sequence_reset_green);
+            notification_message(app->notification, &sequence_reset_blue);
+            if(!scene_manager_search_and_switch_to_previous_scene(
+                   app->scene_manager, Meal_PagerSceneStartscreen)) {
+                scene_manager_stop(app->scene_manager);
+                view_dispatcher_stop(app->view_dispatcher);
+            }
+            consumed = true;
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+void meal_pager_scene_startscreen_on_exit(void* context) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+}

+ 100 - 0
non_catalog_apps/meal_pager/scenes/meal_pager_scene_transmit.c

@@ -0,0 +1,100 @@
+#include "../meal_pager_i.h"
+#include "../helpers/meal_pager_custom_event.h"
+#include "../helpers/retekess/meal_pager_retekess_t119.h"
+#include "../helpers/retekess/meal_pager_retekess_td157.h"
+#include "../views/meal_pager_transmit.h"
+#include "../helpers/meal_pager_led.h"
+#include "../helpers/subghz/subghz.h"
+#include "../views/meal_pager_transmit.h"
+#include <dolphin/dolphin.h>
+
+void meal_pager_transmit_callback(Meal_PagerCustomEvent event, void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void meal_pager_scene_transmit_on_enter(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    FURI_LOG_D(TAG, "Type is %lu", app->pager_type);
+
+    app->stop_transmit = false;
+    meal_pager_blink_start_compile(app);
+    meal_pager_transmit_model_set_type(app->meal_pager_transmit, app->pager_type);
+    meal_pager_transmit_model_set_station(app->meal_pager_transmit, app->current_station);
+    meal_pager_transmit_model_set_pager(app->meal_pager_transmit, app->current_pager);
+    meal_pager_transmit_set_callback(app->meal_pager_transmit, meal_pager_transmit_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, Meal_PagerViewIdTransmit);
+    bool generated = false;
+    switch(app->pager_type) {
+    case Meal_PagerPagerTypeT119:
+        generated = meal_pager_retekess_t119_generate_all(app);
+        break;
+    case Meal_PagerPagerTypeTD157:
+        generated = meal_pager_retekess_td157_generate_all(app);
+        break;
+    default:
+        generated = false;
+        break;
+    }
+    if(!generated) {
+        FURI_LOG_D(TAG, "Could not generate temp file");
+        meal_pager_blink_stop(app);
+        return;
+    }
+    FURI_LOG_D(TAG, "Generated tmp.sub");
+    FURI_LOG_D(TAG, "Start Transmitting");
+    if(!app->stop_transmit) {
+        meal_pager_transmit_model_set_sending(app->meal_pager_transmit, 1);
+        subghz_send(app);
+    }
+    dolphin_deed(DolphinDeedSubGhzSend);
+}
+
+bool meal_pager_scene_transmit_on_event(void* context, SceneManagerEvent event) {
+    Meal_Pager* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case Meal_PagerCustomEventTransmitLeft:
+        case Meal_PagerCustomEventTransmitRight:
+            break;
+        case Meal_PagerCustomEventTransmitUp:
+        case Meal_PagerCustomEventTransmitDown:
+            break;
+        case Meal_PagerCustomEventTransmitBack:
+            app->stop_transmit = true;
+            app->state_notifications = SubGhzNotificationStateIDLE;
+            meal_pager_blink_stop(app);
+            if(!scene_manager_search_and_switch_to_previous_scene(
+                   app->scene_manager, Meal_PagerSceneMenu)) {
+                scene_manager_stop(app->scene_manager);
+                view_dispatcher_stop(app->view_dispatcher);
+            }
+            consumed = true;
+            break;
+        case Meal_PagerCustomEventViewTransmitterSendStop:
+            app->state_notifications = SubGhzNotificationStateIDLE;
+            subghz_txrx_stop(app->subghz->txrx);
+            meal_pager_blink_stop(app);
+            meal_pager_transmit_model_set_sending(app->meal_pager_transmit, 0);
+            scene_manager_next_scene(app->scene_manager, Meal_PagerSceneMenu);
+            FURI_LOG_D(TAG, "Stop Event");
+            break;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+        if(app->state_notifications == SubGhzNotificationStateTx) {
+            notification_message(app->notification, &sequence_blink_magenta_10);
+        }
+        return true;
+    }
+
+    return consumed;
+}
+
+void meal_pager_scene_transmit_on_exit(void* context) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+}

+ 125 - 0
non_catalog_apps/meal_pager/views/meal_pager_startscreen.c

@@ -0,0 +1,125 @@
+#include "meal_pager_startscreen.h"
+
+#include "../meal_pager_i.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+
+struct Meal_PagerStartscreen {
+    View* view;
+    Meal_PagerStartscreenCallback callback;
+    void* context;
+};
+
+typedef struct {
+    int some_value;
+} Meal_PagerStartscreenModel;
+
+void meal_pager_startscreen_set_callback(
+    Meal_PagerStartscreen* instance,
+    Meal_PagerStartscreenCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void meal_pager_startscreen_draw(Canvas* canvas, Meal_PagerStartscreenModel* model) {
+    UNUSED(model);
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "Restaurant Pager");
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignTop, "Trigger Tool");
+    elements_button_center(canvas, "Start");
+}
+
+static void meal_pager_startscreen_model_init(Meal_PagerStartscreenModel* const model) {
+    model->some_value = 1;
+}
+
+bool meal_pager_startscreen_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    Meal_PagerStartscreen* instance = context;
+    if(event->type == InputTypeRelease) {
+        switch(event->key) {
+        case InputKeyBack:
+            with_view_model(
+                instance->view,
+                Meal_PagerStartscreenModel * model,
+                {
+                    UNUSED(model);
+                    instance->callback(Meal_PagerCustomEventStartscreenBack, instance->context);
+                },
+                true);
+            break;
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyOk:
+            with_view_model(
+                instance->view,
+                Meal_PagerStartscreenModel * model,
+                {
+                    UNUSED(model);
+                    instance->callback(Meal_PagerCustomEventStartscreenOk, instance->context);
+                },
+                true);
+            break;
+        case InputKeyMAX:
+            break;
+        }
+    }
+    return true;
+}
+
+void meal_pager_startscreen_exit(void* context) {
+    furi_assert(context);
+}
+
+void meal_pager_startscreen_enter(void* context) {
+    furi_assert(context);
+    Meal_PagerStartscreen* instance = (Meal_PagerStartscreen*)context;
+    with_view_model(
+        instance->view,
+        Meal_PagerStartscreenModel * model,
+        { meal_pager_startscreen_model_init(model); },
+        true);
+}
+
+Meal_PagerStartscreen* meal_pager_startscreen_alloc() {
+    Meal_PagerStartscreen* instance = malloc(sizeof(Meal_PagerStartscreen));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(Meal_PagerStartscreenModel));
+    view_set_context(instance->view, instance); // furi_assert crashes in events without this
+    view_set_draw_callback(instance->view, (ViewDrawCallback)meal_pager_startscreen_draw);
+    view_set_input_callback(instance->view, meal_pager_startscreen_input);
+    //view_set_enter_callback(instance->view, meal_pager_startscreen_enter);
+    //view_set_exit_callback(instance->view, meal_pager_startscreen_exit);
+
+    with_view_model(
+        instance->view,
+        Meal_PagerStartscreenModel * model,
+        { meal_pager_startscreen_model_init(model); },
+        true);
+
+    return instance;
+}
+
+void meal_pager_startscreen_free(Meal_PagerStartscreen* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, Meal_PagerStartscreenModel * model, { UNUSED(model); }, true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View* meal_pager_startscreen_get_view(Meal_PagerStartscreen* instance) {
+    furi_assert(instance);
+    return instance->view;
+}

+ 19 - 0
non_catalog_apps/meal_pager/views/meal_pager_startscreen.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/meal_pager_custom_event.h"
+
+typedef struct Meal_PagerStartscreen Meal_PagerStartscreen;
+
+typedef void (*Meal_PagerStartscreenCallback)(Meal_PagerCustomEvent event, void* context);
+
+void meal_pager_startscreen_set_callback(
+    Meal_PagerStartscreen* meal_pager_startscreen,
+    Meal_PagerStartscreenCallback callback,
+    void* context);
+
+View* meal_pager_startscreen_get_view(Meal_PagerStartscreen* meal_pager_static);
+
+Meal_PagerStartscreen* meal_pager_startscreen_alloc();
+
+void meal_pager_startscreen_free(Meal_PagerStartscreen* meal_pager_static);

+ 182 - 0
non_catalog_apps/meal_pager/views/meal_pager_transmit.c

@@ -0,0 +1,182 @@
+#include "../meal_pager_i.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#include <dolphin/dolphin.h>
+
+const char* const pager_type_text_long[4] = {
+    "Retekess T119",
+    "Retekess TD157",
+    "Retekess TD165",
+    "Retekess TD174",
+};
+
+struct Meal_PagerTransmit {
+    View* view;
+    Meal_PagerTransmitCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint32_t pager_type;
+    uint32_t station;
+    uint32_t pager;
+    uint32_t sending;
+} Meal_PagerTransmitModel;
+
+void meal_pager_transmit_set_callback(
+    Meal_PagerTransmit* instance,
+    Meal_PagerTransmitCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void meal_pager_transmit_draw(Canvas* canvas, Meal_PagerTransmitModel* model) {
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    //char* test = "";
+    //snprintf(test, 20, "%lu", model->pager_type);
+    char stationText[20] = "";
+    char pagerText[20] = "";
+    snprintf(stationText, 20, "Station: %lu", model->station);
+    snprintf(pagerText, 20, "Pager: %lu", model->pager);
+    canvas_draw_str_aligned(
+        canvas, 0, 10, AlignLeft, AlignTop, pager_type_text_long[model->pager_type]);
+    canvas_set_font(canvas, FontSecondary);
+    if(model->sending == 0) {
+        canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, "Generating Data");
+        canvas_draw_str_aligned(canvas, 0, 32, AlignLeft, AlignTop, stationText);
+        canvas_draw_str_aligned(canvas, 0, 42, AlignLeft, AlignTop, pagerText);
+    } else {
+        canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, "Sending Data");
+    }
+}
+
+static void meal_pager_transmit_model_init(Meal_PagerTransmitModel* const model) {
+    FURI_LOG_D(TAG, "Scene 1 Model Init");
+    model->pager_type = 0;
+    model->station = 0;
+    model->pager = 0;
+    model->sending = 0;
+}
+
+void meal_pager_transmit_model_set_type(Meal_PagerTransmit* instance, uint32_t type) {
+    furi_assert(instance);
+    Meal_PagerTransmitModel* model = view_get_model(instance->view);
+    model->pager_type = type;
+    view_commit_model(instance->view, false);
+}
+
+void meal_pager_transmit_model_set_station(Meal_PagerTransmit* instance, uint32_t station) {
+    furi_assert(instance);
+    Meal_PagerTransmitModel* model = view_get_model(instance->view);
+    model->station = station;
+    view_commit_model(instance->view, false);
+    with_view_model(
+        instance->view, Meal_PagerTransmitModel * model, { UNUSED(model); }, true);
+}
+
+void meal_pager_transmit_model_set_sending(Meal_PagerTransmit* instance, uint32_t value) {
+    furi_assert(instance);
+    Meal_PagerTransmitModel* model = view_get_model(instance->view);
+    model->sending = value;
+    view_commit_model(instance->view, false);
+    with_view_model(
+        instance->view, Meal_PagerTransmitModel * model, { UNUSED(model); }, true);
+}
+
+void meal_pager_transmit_model_set_pager(Meal_PagerTransmit* instance, uint32_t pager) {
+    furi_assert(instance);
+    Meal_PagerTransmitModel* model = view_get_model(instance->view);
+    model->pager = pager;
+    view_commit_model(instance->view, false);
+    with_view_model(
+        instance->view, Meal_PagerTransmitModel * model, { UNUSED(model); }, true);
+}
+
+bool meal_pager_transmit_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    Meal_PagerTransmit* instance = context;
+    if(event->type == InputTypeRelease) {
+        switch(event->key) {
+        case InputKeyBack:
+            with_view_model(
+                instance->view,
+                Meal_PagerTransmitModel * model,
+                {
+                    UNUSED(model);
+                    instance->callback(Meal_PagerCustomEventTransmitBack, instance->context);
+                },
+                true);
+            break;
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyOk:
+            with_view_model(
+                instance->view, Meal_PagerTransmitModel * model, { UNUSED(model); }, true);
+            break;
+        case InputKeyMAX:
+            break;
+        }
+    }
+    return true;
+}
+
+void meal_pager_transmit_exit(void* context) {
+    furi_assert(context);
+    FURI_LOG_D(TAG, "Scene 1 Exit");
+}
+
+void meal_pager_transmit_enter(void* context) {
+    FURI_LOG_D(TAG, "Scene 1 Enter");
+    furi_assert(context);
+    Meal_PagerTransmit* instance = (Meal_PagerTransmit*)context;
+    with_view_model(
+        instance->view, Meal_PagerTransmitModel * model, { UNUSED(model); }, true);
+}
+
+Meal_PagerTransmit* meal_pager_transmit_alloc(void* context) {
+    FURI_LOG_D(TAG, "Scene 1 Alloc");
+    furi_assert(context);
+    Meal_PagerTransmit* instance = malloc(sizeof(Meal_PagerTransmit));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(Meal_PagerTransmitModel));
+    view_set_context(instance->view, instance); // furi_assert crashes in events without this
+    view_set_draw_callback(instance->view, (ViewDrawCallback)meal_pager_transmit_draw);
+    view_set_input_callback(instance->view, meal_pager_transmit_input);
+    view_set_enter_callback(instance->view, meal_pager_transmit_enter);
+    view_set_exit_callback(instance->view, meal_pager_transmit_exit);
+
+    with_view_model(
+        instance->view,
+        Meal_PagerTransmitModel * model,
+        {
+            meal_pager_transmit_model_init(model);
+            //meal_pager_transmit_model_set_type(instance, 0);
+        },
+        true);
+
+    return instance;
+}
+
+void meal_pager_transmit_free(Meal_PagerTransmit* instance) {
+    FURI_LOG_D(TAG, "Transmit Free");
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, Meal_PagerTransmitModel * model, { UNUSED(model); }, true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View* meal_pager_transmit_get_view(Meal_PagerTransmit* instance) {
+    furi_assert(instance);
+    return instance->view;
+}

+ 24 - 0
non_catalog_apps/meal_pager/views/meal_pager_transmit.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/meal_pager_custom_event.h"
+
+typedef struct Meal_PagerTransmit Meal_PagerTransmit;
+
+typedef void (*Meal_PagerTransmitCallback)(Meal_PagerCustomEvent event, void* context);
+
+void meal_pager_transmit_set_callback(
+    Meal_PagerTransmit* meal_pager_transmit,
+    Meal_PagerTransmitCallback callback,
+    void* context);
+
+void meal_pager_transmit_model_set_sending(Meal_PagerTransmit* instance, uint32_t value);
+void meal_pager_transmit_model_set_type(Meal_PagerTransmit* instance, uint32_t type);
+void meal_pager_transmit_model_set_station(Meal_PagerTransmit* instance, uint32_t station);
+void meal_pager_transmit_model_set_pager(Meal_PagerTransmit* instance, uint32_t pager);
+
+View* meal_pager_transmit_get_view(Meal_PagerTransmit* meal_pager_static);
+
+Meal_PagerTransmit* meal_pager_transmit_alloc();
+
+void meal_pager_transmit_free(Meal_PagerTransmit* meal_pager_static);

+ 34 - 0
non_catalog_apps/vexed/AUTHORS.md

@@ -0,0 +1,34 @@
+# Authors
+
+## Flipper Zero authors and contributors
+
+- (c) 2024 Dominik Dzienia  ([dominik.dzienia@gmail.com](https://github.com/dlvoy))
+
+## Orginal Palm.OS Vexed Authors
+
+This software uses code (parts level parsing routines) and game logic from original Palm.OS Vexed game:
+
+- (c) 1999 James McCombe (Vexed 1.3)
+- (c) 2001 Mark Ingebretson (Vexed 1.4a)
+- (c) 2001-2006 The Vexed Project ([Vexed 2.2](https://vexed.sourceforge.net/))
+
+This software includes level packs created by:
+
+- (c) 1999 James McCombe ([http://spacetube.tsx.org](https://www.jamesmccombe.com/tube/Projects/Vexed/index.shtml)) 
+- (c) 2001 Steve Haynal ([softerhardware.com/vexed.html](https://web.archive.org/web/20081010144136/softerhardware.com/vexed.html), [screens](https://web.archive.org/web/20080922012137/http://www.114pda.com/game/puzzle/vexed-14a.htm))
+- (c) 2001-2006 The Vexed Project (https://vexed.sourceforge.net/)
+
+## Acknowledgments
+
+This project uses uses following u8g2 fonts:
+
+- Squeezed: Public Domain, created by the [author of u8glib and u8g2](https://github.com/olikraus)
+- Wedge: Public Domain by [Arvin](https://www.pentacom.jp/pentacom/bitfontmaker2/gallery/?id=3950)
+- Tom-Thumb: [CC0 or CC-BY 3.0](https://github.com/olikraus/u8g2/wiki/fntgrptomthumb#copyright), created by [Brian (robey)](https://robey.lag.net/2010/01/23/tiny-monospace-font.html)
+- micro: [Public Domain](https://github.com/olikraus/u8g2/wiki/fntgrpx11#copyright), from [X11 distribution](http://cgit.freedesktop.org/xorg/font/)
+
+Other assets:
+- Into / main menu logo was created using [Autosquare font](https://www.dafont.com/autosquare.font) by [casualised](https://www.dafont.com/profile.php?user=1383431) - [myfaridv@gmail.com](myfaridv@gmail.com)
+- UI assets (back, left and right [icons](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/assets)) taken from [Flipper Zero firmware](https://github.com/flipperdevices/flipperzero-firmware)
+ 
+

+ 25 - 0
non_catalog_apps/vexed/CHANGELOG.md

@@ -0,0 +1,25 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+# 1.0.1 - 2024-01-04
+
+## Fixed
+
+- Crash on game continue when no extra levels were added to SD Card
+
+# 1.0.0 - 2024-01-04
+
+## Added
+
+- Initial release of the game
+- Base gameplay extended for Flipper Zero: smart bricks selection and navigation
+- 9 level packs from original Palm-OS Vexed 2.2 game by James A. McCombe, Steve Haynal and Vexed Development Team
+- Support for built-in and user-provided levels
+- Golf-style par based scoring
+- One step back undo
+- Showing pre-recorded solution
+- Bricks counter (histogram)

+ 336 - 0
non_catalog_apps/vexed/LICENSE.md

@@ -0,0 +1,336 @@
+GNU General Public License
+==========================
+
+_Version 2, June 1991_  
+_Copyright © 1989, 1991 Free Software Foundation, Inc.,_  
+_51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA_
+
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+### Preamble
+
+The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+We protect your rights with two steps: **(1)** copyright the software, and
+**(2)** offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+**0.** This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The “Program”, below,
+refers to any such program or work, and a “work based on the Program”
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term “modification”.)  Each licensee is addressed as “you”.
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+**1.** You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+**2.** You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+* **a)** You must cause the modified files to carry prominent notices
+stating that you changed the files and the date of any change.
+* **b)** You must cause any work that you distribute or publish, that in
+whole or in part contains or is derived from the Program or any
+part thereof, to be licensed as a whole at no charge to all third
+parties under the terms of this License.
+* **c)** If the modified program normally reads commands interactively
+when run, you must cause it, when started running for such
+interactive use in the most ordinary way, to print or display an
+announcement including an appropriate copyright notice and a
+notice that there is no warranty (or else, saying that you provide
+a warranty) and that users may redistribute the program under
+these conditions, and telling the user how to view a copy of this
+License.  (Exception: if the Program itself is interactive but
+does not normally print such an announcement, your work based on
+the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+**3.** You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+* **a)** Accompany it with the complete corresponding machine-readable
+source code, which must be distributed under the terms of Sections
+1 and 2 above on a medium customarily used for software interchange; or,
+* **b)** Accompany it with a written offer, valid for at least three
+years, to give any third party, for a charge no more than your
+cost of physically performing source distribution, a complete
+machine-readable copy of the corresponding source code, to be
+distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+* **c)** Accompany it with the information you received as to the offer
+to distribute corresponding source code.  (This alternative is
+allowed only for noncommercial distribution and only if you
+received the program in object code or executable form with such
+an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+**4.** You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+**5.** You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+**6.** Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+**7.** If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+**8.** If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+**9.** The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and “any
+later version”, you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+**10.** If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+### NO WARRANTY
+
+**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+### How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the “copyright” line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+    
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+    
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+    
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w` and `show c` should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w` and `show c`; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a “copyright disclaimer” for the program, if
+necessary.  Here is a sample; alter the names:
+
+    Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+    `Gnomovision' (which makes passes at compilers) written by James Hacker.
+    
+    <signature of Ty Coon>, 1 April 1989
+    Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.

+ 53 - 0
non_catalog_apps/vexed/README.md

@@ -0,0 +1,53 @@
+![Vexed splash screen logo](img/2.png)
+
+# Vexed for Flipper Zero
+
+Vexed is puzzle game, where your goal is to clear level from bricks in smallest possible count of moves. Easy to learn, **hard to master**
+
+Originally developed for Palm.OS by James McCombe, and later extended by The Vexed Project, was abandonware since 2006. After 18 years, this full remake shares same level sets, but is written from scratch to fit on Flipper Zero 128x64 B&W screen and be controlled without touch screen.
+
+## How to play 
+
+Goal of the game is to clear levels from bricks. Level is seen as cross section, top-down, with walls - and bricks are falling due to gravity. You can only move one brick at a time, and only to the left or right, one step a time. If two or more bricks of he same type are touching - they will explode and be cleared from level. You need to explode all of bricks, but be careful! If there is any single brick of a kind left alone - game is lost! Same if brick are blocked by walls and at end cannot touch - you wont be able to clear them!
+
+![Simple level playthrough](docs/img/playtru.gif)
+
+### Selecting bricks
+
+To select brick you want to move, use arrow buttons. &#9664; Left and Right &#9654; arrows move sideways and cycle through all bricks, &#9660; Down and &#9650; Up pointing buttons moves selection vertically to the nearest block downwards or upwards.
+
+### Moving bricks
+
+If selected brick can be moved **only in one direction** you can move it immediately when it is selected, clicking &#9673; Center button. 
+
+While selected such block will be bordered by pulsating &#9633; square frame with &#x2190; arrow &#x2192; showing possible movement direction:
+
+![One way selection](docs/img/selection.gif)
+
+If selected brick can be moved **both directions** you need to **choose it first** by clicking &#9673; Center button. Pulsating border will disappear and pulsating heavy &#x21e6; arrows &#x21e8; will indicate you need to choose which way to move this block using &#9664; Left and Right &#9654; buttons.
+
+![Two way selection](docs/img/direction.gif)
+
+### Gravity and Explosions
+
+When block is moved to hang in the air - it will fall down, stopped if there is wall or another block. If after movement two or more blocks of the same kind are touching - they will explode and leave empty space - that may trigger further to fall and explode.
+
+![Explosion](docs/img/explosion.gif)
+
+## More levels
+
+This game supports loading custom levels provided by user.
+
+See more about level format and extra levels in [custom VXL format documentation](docs/level_format.md)
+
+## Acknowledgments and License
+
+This project was possible thanks to many authors, creators and contributors - see them all in dedicated [AUTHORS page](AUTHORS.md).
+
+To respect original Vexed licensing, this game is released under [GPL 2 license]
+(LICENSE.md)
+
+All changes are tracked in [Changelog](CHANGELOG.md).
+
+
+

+ 5 - 0
non_catalog_apps/vexed/SHORTDESC.md

@@ -0,0 +1,5 @@
+Vexed is puzzle game, where your goal is to clear level from bricks in in smallest possible count of moves. Easy to learn, **hard to master**
+
+Originally developed for Palm.OS by James McCombe, and later extended by The Vexed Project, was abandonware since 2006. After 18 years, this full remake shares same level sets, but is written from scratch to fit on Flipper Zero 128x64 B&W screen and be controlled without touch screen.
+
+More instructions: [Flipper Zero Vexed github pages](https://github.com/dlvoy/flipper-zero-vexed)

+ 15 - 0
non_catalog_apps/vexed/application.fam

@@ -0,0 +1,15 @@
+App(
+    appid="game_vexed",
+    name="Vexed",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="game_vexed_app",
+    requires=["gui"],
+    stack_size=5 * 1024,
+    fap_description="Vexed - classic Palm.OS puzzle game",
+    fap_category="Games",
+    fap_icon="game_vexed.png",
+    fap_icon_assets="images",
+    fap_file_assets="assets",
+    fap_author="Dominik Dzienia",
+    fap_version="1.0.1",
+)

+ 62 - 0
non_catalog_apps/vexed/assets/levels/01 Classic Levels.vxl

@@ -0,0 +1,62 @@
+# Author: James A. McCombe
+# URL: http://spacetube.tsx.org
+# Description: The classic levels released with the original Vexed!
+0;Good Night;10/10/3ba~~3/5~~3/3~~~~3/3a~~b3/4ab4/10;eCdFdCeC
+1;I Am The Walrus;10/10/3~~a~~2/3~~b~~2/3~ac~~2/3~bd~~2/3bdc~c2/10;FdEdfE
+2;I Call Your Name;10/4~~4/3~~~~3/2e~gf~e2/2f~2~g2/3f~~g3/4~~4/10;EddFdFGfHeeFcEHefDGfGfdFcEdF
+3;Slow Down;10/10/4b5/3ac~4/4a~a~2/4c~c3/6b3/10;eDdDeDeFfF
+4;Two Of Us;10/4a~c3/4d~d3/5~e3/4~~d3/4~~e3/4c1a3/10;GbFfeCGceCfF
+5;I'll Cry Instead;10/3~~~~~2/3e~~~g2/4f~~3/3fg~bg2/3ef~gb2/4egbg2/10;GfeEdCHc
+6;Can't Buy Me Love;10/10/3~~ac3/3~~cd3/3~~da3/3~~ac3/10/10;FdFeGdeF
+7;Drive My Car;10/10/2~~~~~d2/2~~~~~e2/2b~~a~b2/2ae~e~3/2ebd1b3/10;dFcEcFFfEfHcGfFfHdHeGfFfcG
+8;You Won't See Me;10/10/5~b3/3a~~cb2/4a~bc2/5~c3/10/10;eEGdfEdDeE
+9;Old Brown Shoe;10/2~~~~~~2/1c~~~~c~~1/1ded~~dg~1/1ebc~~gcb1/1ged~1bef1/1bge2efd1/10;gChDIdGeIeHeHfIffEgGbCcCdCdDeEdEdFcFEfFebEcEcFbFbG
+10;Rocky Raccoon;10/2~~~~cd2/2~~e~4/2~~1~~~2/2d~1~~~2/4~~gc2/2c~~ecg2/10;GfHfGbHbeCGbFcEcfDfGcGdG
+11;Octopus's Garden;10/2bac~cab1/5~4/3a~~~a2/4~~~3/4~~~3/4bac3/10;dDHdeFeGeBdBeBGbfDcBdBeBHbGbFgfFIbHbGb
+12;Get Back;10/1acdc~dca1/5~4/5~~3/5~~3/4d~~3/5~4/10;GbeBdBeBfFcBdBeBHbGbbBcBdBeBIbHbGb
+13;Doctor Robert;10/10/4e~~3/4g~~3/3~f~fe2/4g1e3/10/10;eDeDfDEeFeGedEFe
+14;Ask Me Why;10/10/5a~3/3a~c~a2/4~a~c2/3c~1ca2/4c5/10;FefEdDdF
+15;Hey Bulldog;10/10/2bab~cbd1/3bd~da2/4c~a3/5d4/10/10;GceEHcGcIcHccCdCeEdDGeHdeE
+16;Eleanor Rigby;10/3acd~a2/3e1ead2/3g~gec2/4~5/3eg5/3ga5/10;FdHbHceBfDdBeBFddDdD
+17;I'm Only Sleeping;10/5e~3/5g~~2/3f~f~3/3g~e~3/4~g~~2/5e4/10;dDFdfCfDFfeFdEeF
+18;Carry That Weight;10/4b~4/5~b3/2ac~~d3/2da~~bc2/2cd~dc3/4~5/10;dDcDcEGeeBGeHecFdF
+19;Bungalow Bill;10/5~4/5~a3/4~~c~2/3d~~e~2/4ac1e2/7d2/10;dEeEGcFdGdgEfEgE
+20;Eight Days A Week;10/10/4~b4/5ac3/3c~ba3/3d~cd~2/10/10;FcFedEeEeCdFeF
+21;The Word;10/2f~~~~3/5~~3/2h~~~fh2/4~~4/4h~~3/4f~~3/10;cDeFdDGdHdGdfFcBdBeB
+22;She Loves You;10/10/8b1/2~~~~a~c1/2d~e~g~a1/2c~b~f~f1/2d~g~e~e1/10;cEgDIeGgGfIfeEIgfEIfeGfGcGdGeGfGgG
+23;I'm A Loser;10/10/3d~d~~2/4~e~e2/3e~g~g2/3d~5/10/10;fCFdHdGdFddFFedCGeEeHeGeFe
+24;Rain;10/10/2~bacd~2/2~3e~2/2e~1~b~2/2d~~~1e2/2ca1ebd2/10;gDcEdFeFDccEdFGgGeEcDccFdFcFFcEcDcHgGgGe
+25;I Me Mine;10/3ba~~3/3cb~~3/3ac~d3/5~c3/3ca~b3/3d1~a3/10;eBeDGfdCeFeCdFGfFfEfeDdDeDFfdDGgeD
+26;Dig It;10/10/5~c3/4~~dc2/3d~eg3/4cgc~2/5ed3/10;GdfDgDgFdEeFeFHdHf
+27;The Inner Light;10/10/5~d3/5e4/3g~g~~2/3dg1fd2/2de3f2/10;dEdFFeEeEfGcfEgEgF
+28;Yes It Is;10/4b~4/5~~a2/3a~~~c2/3c~~ab2/3d~dc3/3c~5/10;dEHdGeeBfDHeFefFHeGedFGfFf
+29;For You Blue;10/10/2a1~~c~2/2c1~~d~2/2e~g~ed2/4c~1c2/5a1g2/10;gDgDGeeEfEgEcEdEeEcEdEcEdEeF
+30;Golden Slumbers;10/3~b~a~2/3~abc~2/3dcab~2/3becae2/4deba2/5d4/10;eBGcgDHeeEdDeDGfeFfFdEHfeF
+31;Sexy Sadie;10/3~~~4/1b~~ac~~2/2~~c1~~~1/1d~~d~~~b1/2a~1~~3/2d3c3/10;EeeEfCbEbCEecDCeDedEbEeEIeHefF
+32;Don't Let Me Down;10/4ba4/3~cde3/2~~gafe2/2~~cdhg2/3~hfg3/4eb4/10;EdFbGcGdEeFeDeEeGeHdGedEGfFfHeGfdFcEdF
+33;Love Me Do;10/2c~~~~d2/4~~4/3cec4/4d5/4e~4/3~d5/10;cBdBEgeFHbGbdGFd
+34;Words Of Love;10/4ba~3/5b~3/6~3/3dac~3/5d~3/3bacbd2/10;fFfCeGGfeEfGfCdEFgHgeEeBfCdGeG
+35;Paperback Writer;10/3ac5/4d5/3~e~4/2~~c~~e2/2c~1~~d2/2acade3/10;cFeDHeEeEddBEdFgHfeE
+36;I'll Be Back;10/2~b~~~~2/2~a~~c~2/2a1a~1c2/3~de~3/2e~cdeb2/3aecb3/10;EfDcgCdCeEfFfGeGcFdFdGeG
+37;All My Loving;10/5~~ba1/5~~ab1/3~~~bca1/2da6/2cb6/2dc6/10;HcGcFdEdGdFdEdHdGdFdEd
+38;Glass Onion;10/3ba~~3/3cd~4/4e~4/5e4/4~ad3/3~~cb3/10;eDeCeDdCeDdCeD
+39;Blue Jay Way;10/5~~ba1/5~~cb1/5~bda1/2ca~~4/2db6/2ad6/10;HbIbHbGddEHcGdcEdEIcHcGdFeEeHdGdFeEeIdHdGdFeEe
+40;Come Together;10/3~~ba3/3c~a4/2da~c~a2/2ab~da3/2da~ad3/4a5/10;FcdEFeGbHdFf
+41;Act Naturally;10/3~~~4/3cd~~3/4ed~~2/5c~e2/6~c2/10/10;HffDeCdCeCfCgEeDfDfE
+42;Long, Long, Long;10/2a~~5/2c~1c4/3d~de3/3e~ade2/4~da3/5a4/10;dEFdFdGdFdEdcCdDcCdDGeGfHeeFdEeF
+43;Twist And Shout;10/2ba~~~c2/2dcd~~d2/3bca~c2/6~3/6~3/10/10;dBeBeCdCcBHbcCfDdDeDHcHd
+44;Please Please Me;10/3b1~~a2/3a1~bc2/3d~ceb2/4~dbe2/4~be3/4~a4/10;dDFeGdfDHdfEFfdDeFdD
+45;How Do You Do It;10/3b~5/3a~~~~2/4~~~b2/3~~a~a2/3~~b1b2/3~~aba2/10;FfHedCdCeEHgGe
+46;When I Get Home;10/10/2~ba~cad1/2~ce~ed2/2~2~4/2ceb~c3/5~b3/10;DdeCGcHcGcIcGgeDdDeDfFdFGgGd
+47;And I Love Her;10/3~~b~~2/3a~c~~2/4~1~~2/3~~c~~2/3a~a~c2/3b~b4/10;FbfCdC
+48;Taxman;10/2ba~5/2cbd~~3/1c4~~2/1e~~~~e~~1/1c~dbdbab1/2d7/10;DfeCfCGeFeGedCeCfCdCeCfCcBdCEebFbFeCeFIffCcCdCeCfCGfFfEfbF
+49;All Together Now;10/3b~a4/1~~a~1~~c1/1b~d~~cd2/4~~e3/3d~e4/3e~a4/10;dDDcGdHdGdFdFgFfFbeEdDEfIcHdGdGeFedG
+50;Lady Madonna;10/2b~~~~a2/1cd~ba~ec1/1ge~2~cd1/3~~~~3/3~~~g3/4ge4/10;cBEcHbfCHccDdFGecDbDcDHdGeGfdEIcbDcDHddFdFeFIdHdGf
+51;Ticket To Ride;10/4~b4/4~a4/1~~~~b~cd1/1e~~~a~de1/4~b4/4~c4/10;fDHdIdGebEcEdEIeHeGe
+52;Don't Pass Me By;10/4bac3/4dba3/3~c1d~2/2~~3~a1/2~~dbd~c1/2dc3cb1/10;gDEdcGEdgDgDHffCgDgFfFgF
+53;Oh! Darling;10/2~~bac~2/2~4~a1/2~~~d~~e1/2e~~e~~b1/2c~bc~~g1/2bce1agd1/10;cFcFIfcGfDgFgBfEgFfFgFfBgGgBeBfBgB
+54;Revolution;10/10/1ba~~~~cd1/1dec~~gae1/1ebd~~edg1/1gdb~~1gd1/10/10;dFHceFGcFebDFfcDGdHdGdIdHdFecFHeIebFcFdFGebFcFdF
+55;Mr. Moonlight;10/2~b1a~~2/2cd3~2/2de~~1~2/2cg1~1~2/2geb~~a2/2fbc~cf2/10;dDDeDfeGfBgBEdcFcGdGeGfG
+56;In My Life;10/10/1~~~~~b~~1/1a~~c~d~~1/1e~~dcab~1/1g~~cdbf~1/1e~~ghfbh1/10;EebDbFcFdFGcGddGFdhFGfIfFfcGfGgG
+57;I Feel Fine;10/3b~2~~1/4~~g~a1/3~g~a~2/2~~eab~2/3~b1g~2/3eg1aba1/10;gEIcEeEedB
+58;Piggies;10/b~4a~a1/1~1b~~c~d1/1~2~~e~e1/1b~ed~c~cg/4e~1~ge/4c~1~2/10;GcIbgCdEeGGdIdgEIdHeIeJeJfIfbEcEaBdEbEcEdCGe

+ 62 - 0
non_catalog_apps/vexed/assets/levels/02 Classic Levels 2.vxl

@@ -0,0 +1,62 @@
+# Author: Steve Haynal
+# URL: softerhardware.com/vexed.html
+# Description: This is the challenging follow-on to the Vexed Classic levels.
+0;Albuquerque;10/1~~~~~~~~1/1~hf~~~e~1/1~eab~~fh1/1~3~~3/1~1c~~~~2/1~~b~a~c~1/10;eDFfdDeDdDeDHddGeGfGcDdDeDHdcDdDeDIdHd
+1;Fort Wayne;10/1~~2~2~1/1~~d~~~~c1/1~~1~~~~2/1~d1~~~~~1/1ha~~~c~f1/1bf~b~1ha1/10;IcdCCeEfcFIfHfeGGfFfdFeFfFgFhFbFcFbFdFcFeFcGfFgFbGcGdG
+2;Salt Lake City;10/1~~a~1~~2/1~~f~~~~~1/1g~1~~c~~1/1eb~~hd~d1/1cae~ebdh1/1efafdg1f1/10;dFdBcFcGbFGeFeIeIfgEGfFfHfGfdCHfGfeFcGfFgFhFcGdGbGcGdGeG
+3;Metairie;10/1h~2ad1~1/2~~2fd~1/1c~~~~2~1/2~~~~~~~1/1~c~ab~~b1/2a~hf~~2/10;CfEgFfEfDffGgGeGfGdGeGcGdGhCIeHfGfFfEfgChCIfHfGfFfIfgGfGeGgChCbFcFdFeFfFgFbBcFdFeFfFfBgChCIeHebDcFdFeFfGeGfGgGdGeGfGIfHfHgGgFgEg
+4;Las Vegas;10/2~1~~~~~1/1~~2~e~~1/1~e~~2~~1/1~a~~~~cf1/1~1bhf~h2/1~a2h1cb1/10;CeHfIggFIeHeHfeFgCdFHfGfeFfFgFcEdFeF
+5;Hampton;10/1h2~2d~1/1e~~~1~g~1/2~~~~~1e1/1~~a~~fgb1/1~~1~~dfa1/2~h~f1bf1/10;GeHcHcHfIfIeHeGeGfHfIfdGFfdEeEeFfFgFEgDgbCbCCfbFcGdG
+6;Ontario;10/1~~~~~~~~1/1~h~~~b~a1/1~a~1fa~2/1~1~~ch~2/1a~~~2e~1/1ef~~gbcg1/10;CdIcgDFgFeFeEeGdFeEgDgEgeGhFdGeGfGcDGeFecGdGeGfGgGbGcGdGeGfGgG
+7;Worcester;10/2~1~g~~~1/1~d~a1~e~1/1~c~2~f~1/1~a~1~~1~1/1~d~e~~~~1/3fcg~~~1/10;fBCcEcHceFcFdFHddGeG
+8;Amarillo;10/1~~~~d~~~1/1h~~~1~~~1/1b~~g~1~~1/2~~1~1~~1/1~~1~~be~1/2ghe~2d1/10;EdDebCfBgChEGfHfGfFfbDcGdG
+9;Irvine;10/1~~~~~d~~1/2~~~~1~~1/1d~~~~~~2/2~~~~~~~1/1h~~f~~~~1/1f~~d~dh~1/10;eGbGcGGbbDcGdGbGcGdGeGfG
+10;Hollywood;10/1~~~gh~~~1/1g~~hg~~g1/1fe~2~ef1/1ba~2~ba1/1cd~~~~dc1/1gh1ge1hg1/10;cDdFHdGfcEdFeFcFdFeFfFbDIdHecFdFeFbEcFbFIeHeGfHfIfdFeFGfHffBgEEbfCGfGfdFcFdFEcDf
+11;Indianapolis;10/1~~2~~~2/1~c~1~~~b1/1~1~~~~~2/1~~1~1h~2/1~~~h~d1~1/1~~c1~bd~1/10;GgCcbGeFfFIcHe
+12;Fort Worth;10/1dc1~~~~~1/1agde~~h~1/2e2~f1~1/2g~~~2~1/1~1~~~2~1/1~caf~ha~1/10;GdHcGdcEbBcEeCdEcEcEdGbCEgDgHgGgFgcE
+13;El Monte;10/1~~~1cd~e1/1f~~~1g~2/2~~~g1~~1/1~~~~1~~~1/1~~c~~~~~1/1~fd~e~~~1/10;bCIbfGgBdGeGfGgCFdeGfGfBgCdGeGfG
+14;Philadelphia;10/1~d~d~~1~1/1h1~f1~~~1/1a~~1~~~~1/2~~~g~~~1/1eh~~a~~~1/2aga1f~e1/10;FfFfbDcFcBEcdGdFeFfFbDbFcFEcdFeFcGfFgG
+15;Boston;10/1gf~~~~~~1/3b~~~3/1~2~~~a~1/1~1~~~g1~1/1~~~~ab~d1/1~~~dbf~2/10;HdGddCeFcBdCeFbBcBdCeFIfeGfG
+16;Orange;10/1~g~~2~f1/1~1~~~d~e1/1~1~~~1~2/1~~~~~~ha1/1~~~~~fg2/1e~chfdac1/10;HeGeFfGcFgIeHfIgIcIcHeGecBdFeGdGeGfGbGcGdGeGfG
+17;Charlotte;10/3~f1~h2/2~hg~~1~1/1~~3~~2/1~~2~~~2/1~~b~~~~2/1b~2f~g~1/10;DfeCfCeCfCHbdCeCfC
+18;Chesapeake;10/2~bhf2~1/1bc1a1~~~1/1cg~e~~~~1/3~1~c~~1/1ca~cge~d1/2bhbfcdb1/10;cDIfgEeDfEEdfFeFdFEdcGFbeDFfeGbFcGdBbDcDdGIgHgeD
+19;Waco;10/1b~~a~h1~1/2~e1cb~~1/1~~1~h1~~1/1~~~~1~~c1/1a~d~~bef1/1f~h~~had1/10;bFDfCfDccFdFGfbBcFdFgCHeGfgCFdFdEeHfGfHfIeHfdGeGcGHgGgeBFdIfbGcGdGeGfGIgHgGgFgEgDg
+20;Greensboro;10/1~~gf~f~d1/1g~1e~1~f1/1aedf~~~g1/1gbhb~fa2/1ahf1~dec1/1efeb~cdb1/10;IbHdGdeBeDGfeGHfGfIcHeIdHeGeeDfDdDeDfDgDdBeDcDdDeDbDcDdDCeDeGbHfGfIffGIgHgFgDfDgEebFbGEe
+21;Citrus Heights;10/1~~b1~~ed1/1~f1~~~h2/1~1~~~~1~1/1bf~~~gac1/1ch~hfcge1/1d1f1ca3/10;cEDbCbEfFfGfCcbEcEHbHcGfGfFfcFIbHcIeHeGfFfGfHfbFcFEfDfCfFfEfIfHf
+22;Albany;10/1h~1h~c~a1/1g~1a~1~b1/2~~h~~~d1/1~~~1e~~2/1ec~hgbd2/1ab~ehabg1/10;EdbBcEIdgFfFcFbFcFdFgGeCGbFgIdHfGfFgEgIdEdDgGgFgEgDgCgIgHgGgFgEgbC
+23;Minneapolis;10/2~eb1~~d1/1~~fa~~~f1/1~a2~~~a1/1~e1~~hfe1/1~f~~~eb2/1ah1afad~1/10;IcCdDbeCFfeCGeIeHeIeHeIefGHfcFdFFfGfEfDfdCeC
+24;Rochester;10/2~caf2e1/1~~4~f1/1cb~~~~~c1/3f~g~~h1/1h~a~a~~2/2bdgefdh1/10;DfFfcDdEeEfEgFIdbDcDdEeEfEDbcDdEeEfEEbDbCdFbEbDbcDbDcDFfIcHeGfIdHedGeGfGcGdGeGfGbFcGdGeGfGIe
+25;New Haven;10/1a~~g1~~~1/2~ge~~a~1/1~~h1~d1~1/2~e~~2b1/1~~1~1~ed1/1hd~gbfdf1/10;HfIfgGDdCfCggGfGeGEceCdCDdcGFeGdFeEgDgGgFgEgDgCgbBDeCfHcGdFeEgHgGgFgEgDgEcDebGIgcGHgdGGg
+26;San Jose;10/1~~b~~~~2/1~~1~~~~~1/1a~1~~~~~1/2e~~b~~~1/1~gh~1~~g1/1a1c~hc~e1/10;dFdBFedGeGbDcEcFCfdFIfeGfGdGeGfGgG
+27;Newport News;10/1e~~~f~fb1/1dh~~1~cg1/1ag~~c~ed1/2e~gacf2/2fdcgfgf1/1hde1fbdg1/10;cDFdHcfBfFHfIdIdcEbDcDdDdFbCeFgFfFcEcFdFeFIdcFdFeFGfHfFffGEfbDbG
+28;Flint;10/1~e~3~~1/1~1~~~~b~1/1~1~~~~1e1/1~f~~~~cf1/1ag~~c~g2/3b~gea~1/10;HecBdFcFdFHfeFHcIeIeHeGedGeGcFHfdGeGbFcFdGeGfG
+29;Winston-Salem;10/1~~bde~c2/1~~c1ch3/1hd2ba1~1/1acb~1b~~1/1e1f~gh~f1/1ahecbagb1/10;HbgEgEfDHffFfGgGDbEbDbCcbCdFcEdEcEDccEeFfGgGbDcEdGbEcEbGbGeGcGfGdGgGeGfD
+30;Scottsdale;10/1~~~c~f1~1/1~~~1~1~f1/1~~~~~~~a1/1h~~~c~~2/1g~~~d~g~1/2~dh1~a~1/10;eBFfEfbEcGGbIdfFHfGfIdbFFfcG
+31;Santa Clarita;10/1~~1~c~e~1/1~~~~d~c~1/1~h~1ac3/1~d~~b1~e1/1~cf~2~a1/1dbhfce~2/10;fBdFcDcFdGFccFdGFeEfbGEcIfGgFgFeEgeGfGHcGdIfHgGgFe
+32;Pittsburgh;10/1f~~1h~~~1/1c~~~1~~~1/2~~~~~~e1/2~bg~~~a1/1~efd~~~2/1hc1g~abd1/10;eEbBCfbFbCdFcFGgeFfFfGfBeGGfIedFFfEfDffGgGIeeGfG
+33;Paradise;10/1~cd~f1hc1/1~1c~1~ch1/1~~h~d~gc1/1b~c~f~ch1/1dabga~ea1/1f1eachfd1/10;HdDdFdcEbEdEcEfEeEEfFfFfdFeFcBIdHedFGeIeHeGedGeGfGcFbFcFdFFbEfDfCfeGfGgGdGeGfGgG
+34;Shreveport;10/1~~~f1c~~1/1~~~c~1g~1/1~~~h~~d~1/1~~~1~~1~1/1~~~~gbh~1/1~~fhd1bh1/10;EbEdhFgFhCfFgFHdGfgBeDHdGf
+35;Salinas;10/1~a~~g~~~1/1~e~e1~~~1/1~h~2~~b1/1c1ea~~~f1/1bcfcd~cg1/1cghdb~hc1/10;fGCdEccDdEGgHgbDdFcFbFeFdFcFFfdFgGIfHfIfHfeFeGCgDgHgGgFgcDbGcGdGeGIgfB
+36;Elizabeth;10/3~~1~~~1/1~~~~~~~~1/1~~~~~~e2/1~~1a~~1~1/1~~~f~~~~1/1defdhah2/10;EfeFfFfGcGeGdGfGHdGfbGcGdGeG
+37;Toledo;10/1~~g~1~3/1~~d~~~~2/1~~1~~~~2/1~~~~~e1~1/1~chd~1h~1/1hgebhbc~1/10;dCDcCfdFeFeGGedGbFcGdGeGfGbGcGdGeGfG
+38;Omaha;10/1c~b~c~c~1/2~1~1g1~1/2b~~~b~~1/1gh~~~ac~1/1bd~da1de1/2bdehcbc1/10;DbfBGeeFcFEgcFbFdFhEFgGgHgIfGeDgEgFgbBcFdFdGeGfGGebFcFdGcGdGeGfG
+39;Irving;10/1h~b~1~c~1/1a~1fg~1~1/1b~~gc~1~1/1d~ad1d~~1/1gehfbe~~1/1cadbac~c1/10;hBbCDbbDbDcEEeEeFfcFdFGfFfbFFgFccFcGbGcGGgFdbGcGdGeG
+40;Yonkers;10/1~~~a~a~~1/1~a~1~1~~1/1~1~~~~~~1/1~~~d~~~~1/1~~gc~~~~1/1~~fgcf~d1/10;eFeBeFdFfFgFcCFgGgGbdFdG
+41;Fullerton;10/1~~~~~a~b1/1c~~g~1~d1/1g~~1~ea2/2~~h~f1~1/1hd~1~1~~1/1ec~~~~fb1/10;gBbCcEGefGEcbDcFEebFcFIcHdGdcGdGGebGcGdGIcHdGefGgG
+42;Tulsa;10/1~~b~~~~~1/1~~1~~c~2/1d~~~~h~~1/2~h~d1~~1/1~~a~1~~~1/1~abec~~e1/10;DfDbgCfGGdFdeGfGgGbDFecG
+43;Milwaukee;10/1~1~~~1c~1/1~~d~~~1~1/1~~ah~~~~1/2~1f~~~~1/1cd~a~~~b1/3ghgfb2/10;DcIfDdcFEfdGdGeGhBbFcFIfdGeGfG
+44;Little Rock;10/1~~g1~~3/1~~1~~~~c1/1~~~~~~1g1/1~~~~~haf1/1~~b~~4/1~ac~fhgb1/10;GeFgIcHceGGeFfHeGedGFfEfIeHeGeIeHeEgDgDbcFdGHgGecGdGeGfGgG
+45;Aurora;10/1c~1~~1~~1/2~1~~~~~1/1~~~~~~~~1/1~dbgc~~~1/1~cdfgh~h1/1gfbgac~a1/10;CfbBEecGfEgEIffGgGbGcGdG
+46;Sioux Falls;10/2~~f~1~~1/1c~~b~~e~1/1g~~1~~a~1/1df~~d~cb1/2d~~c~3/1ac~ge~cd1/10;HcHeEbcEbEbDbEcGfGgGHebGcGdGeGeCIeHe
+47;Durham;10/1~~1~1~a2/1~~~~f~df1/1~~~~b~gd1/1~h~bahb2/1h1dagcgc1/1bhcecefd1/10;FdCeHcHcGcFcFeGeFeEeDeGeFeEeEfFfGfeGeGfGHdIcdFeGHecEdGIfHfeGIdHgbGcGdGeG
+48;Mobile;10/1~~~~~~~2/1~d~~~~hb1/1~1~~~~3/2~~~~~~~1/1~~~a~eah1/1~c~c~dbe1/10;cGHcgEhEIcHcgEhFgFEgcCdFeGdGeGfGgG
+49;San Antonio;10/1e~~f~1~~1/1acfe~h~b1/1dgdg~g~d1/1hafc~h~a1/1d1ba~a~h1/2cgf~1ha1/10;gCeBGfdDbBcBGeeFcDbCcDbEDdGfeFdFfFgFeFcDfFgFcEdG
+50;Madison;10/1~g~~1~~2/1~1~~a~~~1/1~1~~1~~h1/1~~~d~1~a1/1~fhce~~e1/1adfdcga2/10;IeIefFgFFcEeDeEeeFCbCeDedFcFGgeFfFbEcEbGcGdG
+51;Sunnyvale;10/1~h~c~2~1/1~f~d~~c~1/1~he1~~g~1/1a1da~~f~1/1b~bfb~h~1/1c~fbgdef1/10;hEbFDffFHeGeCbcCDfDfGfcGbFcFdFdGeCcGdGeGfGbFHgGgFgcDbGcGdGHgeC
+52;Memphis;10/1~~1~~~~e1/1gb~~~~~2/1ef~~d~~~1/3~1e~~f1/1~~~~1~cd1/1b1~~ecdg1/10;HffEIbHffEgFcDcDDfCfbDcDdFeGIfdGeGfGbDcDdGeGfGgG
+53;Salem;10/2~~~~~~~1/1~a~1~~~~1/1~f~~g~~~1/1~1~~1~~~1/1f~~~b~~~1/1ag~~e~eb1/10;CdCdfGFdcGfGgG
+54;Phoenix;10/1~~2~~b2/1~~~fc~h~1/1~~g2~d2/2~hge~1~1/1gfbhd~c~1/1dg1ef~d~1/10;DefFfCfGHccEdFeFGfdFcFbFHcHdGedFFfGfEfDfeCgGeGfC
+55;Oxnard;10/1~~e~ba~h1/1h~d~2~2/1b~b~~1~b1/1hea1~~~2/1bf1dh~~d1/1chfhbcdh1/10;IfFbDcDdbCcDdDGbFbEccEbECfbFfFIgIbgGIdHfeDDecGbGcGdGeGfG
+56;Jackson;10/1f~~~~g~2/1g~~~~a~~1/2~1ch1~~1/1~~fa1~bc1/1fhdbd~ed1/2egaf~af1/10;fFeFfFHeIeHeGfFfHfGfFfgCgGfGFdeFdFeFfFgFIgdFeFfFcFdFeFbCGcFdEfEgGgFgEgDgbC
+57;Baltimore;10/2~~~~1~h1/1~~~~~f~d1/1~~~~~b~2/1c~~~~1~~1/1h~h~cedf1/2fg~bge2/10;IcgFGdFedFbFcFIfIcHfGfdFbFcFdFdGeGcGdGHgGd
+58;Grand Rapids;10/1~1~~bh~~1/1~~~b2~~1/2~~1g~~~1/1~~~~ac~~1/1a~~~2~~1/1gch~1h~h1/10;FbgGGbFbEcFebFcFFeGeFeEfbGcG

+ 63 - 0
non_catalog_apps/vexed/assets/levels/03 Childrens Pack.vxl

@@ -0,0 +1,63 @@
+# Author: Vexed Development Team
+# URL: http://vexed.sourceforge.net
+# Description: Very easy levels meant for children.
+0;Coffee Truffle;10/10/10/6a~2/7~2/5fb~2/3e~efab1/10;dGgDgG
+1;Kahlua;10/10/10/5~d~a1/1h~1~~1~2/2~1~2~2/1h~d~d1a2/10;IdbEGdFe
+2;Butter Pecan ;10/10/10/10/5~h3/2~c~e4/2cedhd3/10;DfGeFfeG
+3;Peppermint Shower;10/3~h5/3~6/3~6/3~b5/3~h~f3/3bdfd3/10;EbEfGfeG
+4;Macadamia Crunch ;10/10/10/10/10/2~c1~h3/1c~gh~g3/10;DfGfdGeG
+5;Tin Roof Sundae ;10/6~g2/1d~2a~1h1/2~3~~a1/2~1~e~~h1/2~1e1~~2/1d~3~g2/10;HbbCIdfCFe
+6;Rum Raisin;10/3~c5/3~6/3~1e4/3e~g1f~1/2~h~d2~1/1h~c~gd1f1/10;FedEhEDfEb
+7;Blackberry;10/10/3h~~f3/4~h4/4af4/4gc~3/5agc2/10;dCGceEfFeF
+8;Cookies & Cream;10/10/6~f2/6~b2/6~3/1d~3~3/1ad~af~b2/10;HcHdbFbGcG
+9;Mocha Chip;10/2~c1a~3/2~1~e~3/2~1~1~3/2~1~e~3/2~1~c~3/1c~1c1~a2/10;DbfBFfFcFf
+10;Cherry Almond;10/1~d7/1~1f6/1~1c~f4/1~2~e4/1~2~c4/1d2~ea~a1/10;CbFedDdDgG
+11;Blueberry ;10/3ad~~c2/5~~3/5~~3/5d~~c1/5a~~2/6~~2/10;eBdBeBHbIe
+12;Chocolate Chip;10/2e7/1~c~6/1~1~~g4/1~1~~5/1~1~~ge3/1c1~g5/10;FdCccCGfFf
+13;Green Tea ;10/4h~4/5~4/5~4/5e~3/1f~2h~~d1/2f3d~e1/10;bFIfeBfEgG
+14;Praline Cashew;10/6~h2/6~3/6~3/4b~~~b1/5~~~2/2c~c1~h2/10;HbcGeEIefF
+15;Carrot Cake;10/10/10/3b~5/4~1~c2/3ad~eh~1/2adbec1h1/10;GfeGdDHehF
+16;Mocha Almond ;10/10/4b~4/1d~2~4/2~a1~4/2~1c~4/2ad1~bc2/10;eCDebDeFfG
+17;Nega Chip ;10/4c5/3~h~4/3~1~4/1e~~h~4/2~~g~4/1e~g1~c3/10;bEEfEceCEf
+18;Aurora Borealis;10/4e5/3~bd4/3~1g~3/3~2~3/3~b1~3/2de2g3/10;EcEcFcEcfD
+19;Cookie Crunch ;10/6e~2/7~2/1~c2g~~2/1~4~~e1/1~1~h1g~2/1c1h3~2/10;CdfDEfgBIe
+20;Malted Vanilla;10/5~ca~1/3~d~2~1/3~1~2~1/3~~g~1~1/3~~1~1a1/3~dcg3/10;hBEcfEGbFe
+21;Rum Custard ;10/10/10/5~d3/2b2d4/2a~fg4/1af~gb4/10;EfGdFfcGdG
+22;Rainbow ;10/5~~h2/4d~4/5~4/3h1~4/3gaf4/2gafd~3/10;fGeCHbGbdG
+23;Coconut Truffle;10/10/10/10/10/2~d2~d2/1d~g~~dg2/10;DfHfdGeGfG
+24;Pineapple ;10/10/10/10/10/3f1e~1g1/1b~bfg~~e1/10;bGfFgGfGgG
+25;Boysenberry ;10/4g2a~1/3~h3~1/3~4~1/3~1~c1~1/3~1~1a~1/2ghcd~d~1/10;EcEcfGhBGe
+26;Ginger Shower;10/2~~a5/2~7/2~a6/10/4d~~3/2f~f1~d2/10;EbDbeFfFcG
+27;Peppermint Stick ;10/10/6~f2/6~3/5a~1b1/5f~~a1/5b~~f1/10;fEIfIffGIgHc
+28;Marble Fudge ;10/7f~1/8~1/4d2c~1/4e~2~1/5~2~1/1h~~hedcf1/10;hBhDeEeEbGcG
+29;Toasted Almond ;10/7~f1/7~2/4~a1~2/4~2~2/4a2~f1/2c~cb~~b1/10;FdcGIbIffGgG
+30;Brandied Peach ;10/10/5c~3/6~d2/6~3/6~3/2c~~d~dc1/10;HdcGdGeGIgfC
+31;Chocolate Mint;10/4c~4/5~4/5~1e~1/3d~g1g~1/4~a~a~1/2h~hdc1e1/10;hDfFfFcGdEeB
+32;Coconut Almond ;10/4c~4/5~4/5~1h2/4de~a2/2~~ch~3/2d1ea~c2/10;HeHeEfDfHgeB
+33;Chocolate Peanut ;10/4d~4/5~4/5~4/5~a3/1b~2~4/2bead~e2/10;eBGebFdGeGfG
+34;Chocolate;10/3~f5/3~6/3~h1~b2/3~g1~3/3~f1~3/3gb~~~h1/10;EbeGHdeGfGgG
+35;Cherry;10/10/5~a3/4~e1~f1/4~h~~2/4~1~h2/4ea~f2/10;FdGcFdIdHefE
+36;Cookie Dough;10/10/6~h2/6~1f1/6~~e1/6e~2/1h~h2f~h1/10;bGHcgEIegFIe
+37;Maui;10/10/3d6/3a~5/3c~~h3/1hgd~c4/1gh1~ah3/10;dDdEGecFbFDf
+38;Peppermint Oreo;10/10/5f~3/6~3/5b~3/2g~g1f3/3~aba~2/10;fCfEgGcFeGfG
+39;Candy Cane;10/5~b3/5~4/5~4/5f~3/3~ge~3/1dg~dbfe2/10;fEEffFGbbGcG
+40;Carob;10/10/10/5h~3/6~3/2df~a~3/2ad~f~~h1/10;dFcFfDgGcGdG
+41;Strawberry ;10/1h~~b5/2~~6/2~~6/3~6/3~e1g~2/1behb2~g1/10;gFbBcDEfbGEb
+42;Banana Nut ;10/1~c7/1~5~g1/1~5~2/1~5~2/1~1e~2~2/1~cg~e~~2/10;CbdFIcdGeGfG
+43;Peanut Butter ;10/10/4a5/1~g1d~4/1~3~4/1c~2~4/1g~~cd~a2/10;eDCdbFcGeDfG
+44;Butter Almond;10/10/3d6/3e~5/1~a1~2~d1/1~d~d~1~2/1a1~e~1d2/10;CeIedDeFcFdD
+45;Cappuccino Crunch;10/4e5/4a~4/4b~4/5~4/4a~1h2/4he~b2/10;eCeCeDfGeGfG
+46;Coconut Chip;10/10/10/10/4~e1~d1/4~1~ga1/3g~ead2/10;FeIeHfIfdGeG
+47;Espresso ;10/10/2~b6/1b~7/2~7/2~ag2h2/2age~he~1/10;eGhGDcbDfGgG
+48;Banana Walnut;10/3b~5/4~5/4~5/1e~1ag~3/2~2a~3/2~~ebg3/10;fEeEdBeEbEcG
+49;Mango ;10/3e~5/4~d4/4~fh~2/4~2~2/4~h1~2/3e~f~~d1/10;dBFdgDfGfDgD
+50;Chocolate ;10/6~f2/6~3/6~3/6~3/2bfg~~3/3bdgd3/10;eFeGdFcFHbeG
+51;Mocha Mint;10/10/10/1e8/1bf~6/1e1~b5/2f~c~~~c1/10;cEbEcEeGfGgG
+52;Bubble Gum ;10/6~g2/1~~f2~d2/1~c3~3/1~4~3/1~c2agd2/1~f1abdb2/10;HbHcfGDcCcCd
+53;Prudhoe ;10/3b6/2~e6/2~4~c1/2~1g~1~2/2~b1~~~2/2~e2c~g1/10;DcDcIdeEfFgG
+54;Coffee Cinnamon;10/3c~5/1g~1~5/2~1~5/2~g~2f2/2~1~2e~1/2~h~chfe1/10;hFdBbCDedGeG
+55;Kona Chip ;10/4~a4/4~5/4~5/4~3f1/1b~1a1~~e1/2b1c~ecf1/10;FbbFIfHfeGfG
+56;Hot Licks Hash ;10/5d~3/6~3/2ab~1~3/4b1~3/4a1~h2/4edhe2/10;dDHffBcDdDeGfG
+57;Vanilla Bean  ;10/4~cg3/4~f4/4~5/3f~5/3d~b4/2gbdc4/10;dFFbdGFcGbFccG
+58;Alaska Blueberry;10/3~g1d~2/3~2e~2/3~3~2/3~3~2/1~~~~b1~2/1g1cbce~d1/10;gBgCFfEbDfCfdG
+59;Malted Grapenut ;10/10/10/1d~~6/2e~1~g3/3b~~4/2edbe~g2/10;dFbDcDFgcEGefG

+ 63 - 0
non_catalog_apps/vexed/assets/levels/04 Confusion Pack.vxl

@@ -0,0 +1,63 @@
+# Author: Vexed Development Team
+# URL: http://vexed.sourceforge.net
+# Description: Hard! These levels are tricky and require careful planning to solve.
+0;Seclusion;10/7d~1/1~g5~1/1~5d~1/1~3b2~1/1~~~~ghc~1/1~~h~c1b~1/10;hBhDFfFfEfGfFfHfGfeFdFeFfFgFDgCgCcbFcGbGcGdG
+1;Illusion;10/10/1e~4~b1/1b~4~2/1f~2~a~2/2c~~~c~2/1fa~f2e2/10;GeFfEfGfFfEfIcHfGfFfEfbGbCbDcFdFcFeFfFgFCgEgbE
+2;Din;10/10/1b~7/2~7/1h~~2d3/2bc~~e~~1/1edghg1c2/10;bEcEdFeFdFgFhFgFfFdGcGdGcGeGGfHfdGFfGfbCIfHfGfeFcGfFgFbGcGdG
+3;Chaos;10/3~e2b2/3~2~g2/3~2~f2/2~~~~~3/1b~1~e~~2/1g~b~1~f2/10;EbfFHcHdHdGfdGFfdEeEGegFFefFEeEgDgDebGcG
+4;Reperfusion;10/3a6/2~d2~e2/2~2~~3/1h~~~f~3/1b2d1h3/1af~eb4/10;DccEdEfEHcGdFebEcEGeFgFedEeEfEcGeGDccEdEbGcGbGcGdG
+5;Misorder;10/4g~1c~1/5~2~1/5~2~1/1c~1~f~d~1/2~~~g~f~1/2ad~a~1~1/10;fEHfbEcFFfdFfGHfGfeFeBFefFdGhBgFhFcGdGeG
+6;Topsy-turviness;10/10/4c~~3/2c1a~g~2/2g4~2/2f~~b~~2/3~g1baf1/10;fFGdeCfCEgcFHgIggDfDeDHfGfFfdFeFfFhGgGgDfDgDHfGfgGFfcFcF
+7;Abashment;10/2c~6/3~6/3~2d~2/3~1c1g~1/1~~~~bhe~1/2b~g1ehd1/10;gDFfhFhEEgFfGfHfIfEfFfGfHfDfCfEfDfFfEfcBcFbFdFcFeFfFcG
+8;Disturbance;10/4a~g~~1/1~c2~1e~1/1~3~2~1/1~3~~~e1/1~~~~~~dc1/1~~ag1~1d1/10;gBhBCchCIeHeIeHfIeHeGeFfEfGfFfHfGfbGDfEfFfIfHfGfFfdFeFfFgFhFcFdFeFfFeBFfEfcG
+9;Mux;10/10/5c4/2h~~g~3/3~~1~3/2g~~1~~a1/1ba~bh~~c1/10;FdcFEfEgcDdFdGcGeGdGfGeGgGIfHfHgfDgFbGcGdGeG
+10;Diffusion;10/2e~6/3~~5/4~~g3/1e2~~1hg1/1ba~~~~dc1/1dh1~b~ca1/10;cFbFdFcFdFeFGdfGHfeGGfHfIfbFcFdFFfGfIfEfDfCfFfEfDfcBdCfGgG
+11;Muddle;10/3e~5/2d1~2~f1/2c1~2~2/2e~~2~2/3~fg~~2/1g~~1cd~2/10;cEEfcEdEfFeFdFdBEeeFGfFfgGIcHfEfcEdFbGeFfG
+12;Disarray;10/1~c7/1~3~cbh1/1~3~e3/1~3~1~b1/1a~1~a~~f1/1f~~~e1~h1/10;GdFfbFcGHcIeIfHfGfGcfFgFIcHcGcfFgFbGcGdGCbGdbGcGdG
+13;Transfusion;10/8e1/7~g1/5~c~2/1~~a~~1~2/1~1h~~~~~1/1e1gcbhab1/10;gDIcIcHddFHfGfGdFeEeDeCefFeFhFgFfFfGHfGfGgFgdFeFfGeGfGgGdGeGfGgG
+14;Disconcertment;10/7~b1/2~b3~2/2~a3~2/1~~1~a1g2/1~c~~1~e2/1~ge~ac3/10;DcDdCeCfcFFgFebFcFdFeGHfdGeGHfcGdGeGIbHfbGcGdGeG
+15;Profusion;10/1g3~bc2/1b~2~c3/2~~1~4/2~~1~4/1a~~~~~~f1/2f~~2ga1/10;GbHbbFbCbCFfIfHfGfFfEgcDdFeFfFgFcFdFcFeFcGfFgF
+16;Discombobulation;10/2~gf5/2~7/1~~7/1h~4~e1/1a~f~a1e2/1f2~g~h2/10;IebFFfdFcFeFdFbFcFdFFfEfDfDbEbDbCdcFdFeFeGfG
+17;Preclusion;10/2h~6/2g~~a4/4~5/2g~~5/2fa~~~~h1/3h1~~~f1/10;cBcEdEdCEeDeeFdFcFeFdFfFeFfFgGIfHfGfFfcFcCdCeFFcGgeFfFfGgG
+18;Inclusion;10/1f~1~d4/2~1~f4/2~1~3h1/2~~~~~~b1/1~~d~~~~a1/1ha1~fb~2/10;FgdFGgIeIeeFfFHfGfFfFbIfHfGfeEfEEfFffGDfbBEfcECfDfFc
+19;Mess;10/4b~4/4g~~f2/1e~2~~3/2~~~~~3/2~~1gf~~1/3baceca1/10;eBfEHcGdFeEeGeFeEeDebDcEdEeEfEgFhFfGfGeGdGgGIffGHfeGGfeCfEgFgGdGfGcFdGeG
+20;Tumult;10/1b~4e~1/1h~5~1/2~~2~e~1/2~~d~~3/2f~c~c~2/2d~1bhf2/10;bBbCHdGeeEEfcDcEdEFeEegFfFeFDedFcFeFdFeFHfGfFfEfhBIdHdfG
+21;Infusion;10/5~d3/5~4/5~4/1be~1~~~f1/1gd~~~~~d1/1f1g~e~~b1/10;cEdGcFbFdFcFbFfGeGdFcFgGfGeGdFeFfFgFIeHeGfFfEfDfCfHfGfGbIfHfFfEfeGdGeGfGdGeGfGgG
+22;Discomposure;10/1dg~3~f1/1beh~2~2/4~2~2/2b~~2~2/4~~~~~1/1d~hf~g1e1/10;dCeGcBdCcCdCeFfFgFhFIbHfGfeGbBcCdCbGcGbCcCdCcEdE
+23;Disillusion;10/10/2~e6/1~~d6/1h~h~~4/1d2~e~~c1/3f~hc~f1/10;IfDcDdCddGdEcEbEdEcEfFgGfGEgdEeEeGfGdGeGfGgG
+24;Occlusion;10/6~d2/6~3/1f~3~~2/2~2~c~e1/1b~~~~b~d1/1c1~~ef~2/10;HbgDIfbDGecFgFIfHfdGFfGfbFcFEfeGDfCfdGeGfG
+25;Uneasiness;10/4~h1c2/4~1~e2/4~~f3/1a~1~~1~e1/2~~~~~~2/2c~f~1ha1/10;FbIeHfGfeFfFgFGdbEEgcFFgdFeFfFgFHcGdFfdGHcGdcGdG
+26;Exclusion;10/6~b2/3h~~~3/5~4/4~~d3/1fgf~~4/1dh1~~gb2/10;HbGgGcFgdFcFeFdFdCeCbFcFdFFeEfDfGeFeEffGDf
+27;Unease;10/6a~2/7~f1/3c2~~g1/3h~~~~2/1~hf~~~da1/1gd1~~~1c1/10;gBIcHdHeCfdFGfHfGfFfdEbFIdHfGfeEEfDfIfHffEgEhFFfGfEfDfeG
+28;Stew;10/8b1/1c~4~c1/2~2~~~2/2h2~1~d1/2d~~~~~e1/2fbef1~h1/10;cFdFeFfFgFIfIcIcHdHfGfFfHfIfcFGfFfdFeFfFgFbCcFdFGdFfcGdG
+29;Flap;10/4~f1~d1/4~1e~2/1h~1~2~2/2~~~2~2/1f1~dbh~~1/1e~~3~b1/10;gFfFgFhFIbeFfFgCHfGfFfEfbGFbbDEfcEdFbGeFfFgF
+30;Muck;10/6~f2/6~3/2d~~1~3/2ag~~b3/2f1~~1gb1/3d~~eae1/10;cDdDHbGgFgdEcEdEeEGeGeFefGEeDeeFEgFgfGIfeGfGdGeGfGgG
+31;Fusion;10/1b~7/2~1d~4/2~2~4/2~eh~~3/1~ea1~bh2/2a1~cdc2/10;bBeEdEcECfFgGfeEdEDfHffEeEeCGeFegFfFHfeGfGEebFDf
+32;Embarrassment;10/2f~6/3~~be3/2~e~5/2~1~~4/2~c~b~3/1~~b1c~f2/10;dFdDFcEdHgfFeFGceFGfFfFceEDgCgEfDdcBdDeF
+33;Ataxia;10/10/1h~7/1b~7/2~7/1c~~~~~af1/1fb1~~ahc1/10;bFcFdFGgHgIfHfGfFfEfDfCfbCbDcFdFeFfFfGeGfGgG
+34;Collusion;10/10/1f~7/2~7/1e~~~5/2~cg~~b~1/1b~gc~f1e1/10;bGbCbEcEdECgDfEfGgeFcFHffFgFhFcGdGbGcGdGeG
+35;Intrusion;10/1c~7/2~4~e1/2~3~e2/2~d~~~f2/1ahad~~c2/1f1h1~~3/10;dEbBcEdEeFdFcFHdIceFfFHeGeHeFfGfbFcFEfDfCfFfEfHf
+36;Disconcertion;10/1~~c6/1~5~a1/1~5~2/1~5~2/1~~~~g~~h1/1g~dhc~ad1/10;IcFfEfDffGeGgGIfHfDbDgCgCbbFcGdGeGfGbGcGdGeGfGgG
+37;Hullabaloo;10/10/10/4~h~da1/4~a~3/2c~~bhg2/1dg~~2bc1/10;FdFeEfFfEfGfFfHfGfFfdFeFfFgFcFdFeFfFgFHdIdHdGeFfEfcGGfFfbGcG
+38;Contusion;10/4~dc~2/4~2~2/4~1e~2/2~~~1a~2/2~beac~f1/2df1b1~2/10;DfEfFfgFIfgEeFdFFbcFEeDeeFdFfFeFgBGfHfGfHfFfEfGf
+39;Confounding;10/4~c4/4~5/4~3h1/2a~~2~e1/2g~~~~~2/1ea~~h1gc1/10;IeHfGfFgEgcEdFcFdFeFfFgFIgIeHfGfFfEfFbeFcGfFgFbGcGdG
+40;Allusion;10/2a~3~d1/3h~2~h1/4~2~2/4c~1~2/1d~~h~~a2/1h1~1c~3/10;eEbFcFEfDfCfIbdCeFHfHfGfFfEfIcHfGfcBdCeF
+41;Conclusion;10/10/7~h1/1f~4~2/2~4g2/2~h~~~fe1/1e~1cf~cg1/10;dFeFfFHfbGbDcFdFeFHfIfIceGGfFfHfGfHfFffGEfDfeGfGgG
+42;Disorganization;10/10/3~gd~3/3~a1~3/3~1f~~2/2~~dh~~e1/1heg2~fa1/10;fEEfEcDfFfEfEddFcFeFdFfFeFfFgFIfHffCgEGfgGFfEfDfbG
+43;Delusion;10/2a~~5/4~5/2fc~~~3/1~h3~~2/1~1~eb~~h1/1~ce1f~ab1/10;EfdDeDfFfDGfFfEfcBdBeDIfHffDgEGfgGcDdDeDFffDCeEfbG
+44;Pother;10/4f5/3ga~4/5~4/2h~1~4/2g~~e~~c1/2fac1~eh1/10;fFcEdFcFeFdFfFeFeCfFFfEfeCFfEfgFIfHfGfFfdCeCfFgFgG
+45;Derangement;10/10/10/4ad~3/5e~~2/2~~~fc~2/2ace1df~1/10;fDgEfEgEFfEfDfGfFfEfeDFfEgcFdFeFfFHehGGfgGFfEfcG
+46;Discomfiture;10/10/10/2eb~~h3/2c1~~4/1~b~~~g3/1g1heac~a1/10;GdFfEfEgcFFgdFcFeFdFcFdDGfeEEgFfEfeGDfCfdGeGfGgG
+47;Rediffusion;10/4h~4/5~4/4~f~~f1/1d~~~1~~2/2~~~g~bg1/1ebe~h1fd1/10;HfdGFfGfcGEfFfbGDfEfIfHffDGfIdbEFfEfcEdFcFeFcGfFgFeBFd
+48;Agitation;10/3~efc3/2~d6/2~7/2~~~g1c2/2g~~1~a2/1afe~d~3/10;DccEdFEbDccEFecFFbEbDcHfbGcGdGeGGbFbEbDcHfcGdGeG
+49;Mullock;10/3h~5/4~5/4~5/4~~~3/3d~e~~c1/1aeacd~1h1/10;dFdBeEIfHffEgFhFfFeFgFeGdGcGeGdGfGHfGfbGcGdGeG
+50;Pell-mell;10/6c~~1/7h~1/5~de~1/2h~~~2~1/2eb~f~1~1/1fd1~b~1c1/10;gBhBGdcEdEdFFecFEeDeHdGdbGDfEfFffFeFdFfFeFHdcFdFeGGfGdFfEfDfeG
+51;Lather;10/4~~f3/4~1a~2/4~2~2/4~1~a2/1g~~c~bc~1/1fb1e~ge~1/10;bFcFeFdFeFhGhFgFfFGbgCHeHfGfIfHfFffGEfDfFbEfDfeGfGgG
+52;Dither;10/2~d6/1~~f1fb3/1~4h~2/1~~~~~d~2/1~b~~2~2/1~1~d1h~2/10;gDDbfCGeEgcFdFFeGeFeEeGeFeEeDeCfDfCfCcbEcFbFcFDcCc
+53;Pandemonium;10/10/4b~4/4g~4/1f~eb~4/2~ca~~3/1ega1~~fc1/10;HgGgeCfFeEeEdEDfEfEfcFdFFefFbEcFEeDeeFdFbGfFeFgG
+54;Heat Of Fusion;10/1~e7/1~f~d~4/1~1~1~4/1~1~1~1c2/1~f~h~ha~1/1cae1~2d1/10;eFGfCbcFbFdFcFeFHfGfFfEfHfGfFfEfcCdFbGeCfFeFgFhF
+55;Huddle;10/10/1f5~g1/1h5~h1/1f~~~g~~2/3~~d~3/2g~~1d3/10;bEcGcEbEcEdGdFFefFIcEeeFfFHeGfdFIdHebEcE
+56;Optical Fusion;10/1g~7/1a~7/2~2h4/2~1~g1~b1/1c~b~1~~c1/2h1~~~a2/10;bBdFFecFIeHfeGbCcFdFeGfGbFcFdFFeEfDfIfeGfG
+57;Turmoil;10/10/10/10/4~~h~h1/1~ce~~g~2/1eaga~1~c1/10;dFcFeFdFeFGeFeEfDfCfFfEfDfGfFfEfIgIeHfGfFfEfcFbFdFcFeFcGfFgFbGcGdG
+58;Turbulence;10/5~g3/3b~~4/4~~4/4~~fe2/1~~b~~hg~1/2ge~~f1h1/10;DfdGeGGfCfcGdGFfHedCeEeFHffFgFhFEgDgbFcFdGcGdGGb
+59;Mix-up;10/4b5/2dfe~4/5~4/2c~~~4/1dhf~~2h1/1b1eg~~gc1/10;eGfGeCdGcEdFeGeCdCeCFecFdGFffGgGbFcFEfDfCfeGfGgGcCdCeCdG

+ 63 - 0
non_catalog_apps/vexed/assets/levels/05 Impossible Pack.vxl

@@ -0,0 +1,63 @@
+# Author: Vexed Development Team
+# URL: http://vexed.sourceforge.net
+# Description: Impossible. You need to be Einstein to make it through this game pack!
+0;Kvass;10/1~g1c~g3/1~2d~d3/1h1hg~4/1deah~c3/1gh1fd1ca1/1bebdhcfd1/10;eBeCeEGbeEGgeFGcFeFgfGeGfGdEIfeGfGCbdGdEcEbEcGbEdEeGfGeGcECfcGdEbGcGdGeGbGcGdGeGfGgG
+1;Spaek;10/4a5/2~~e5/1a~db~fh2/1d~a1cea2/2~gh1cd~1/1cfcbgdac1/10;bDDdEdEcDcGdHdhFEdFdGdIffEgEDfEdDeCeDegGfGeGdFcFbGDgEgfEdGeGhGgGcGdGeGfGbEcGdGeGfGgG
+2;Clay;10/1hg~~ecge1/1eh~~fbdg1/2a~~b1bd1/1hf~e1ah2/2d~b~hgc1/1ceg1ec1d1/10;cBbBFbFdFdcEEfGbHbGfHegCFfEfcEdFeFbEGfFfcEbCFdEfcFdFbGHfGfIfHffFeFfFgFhFGcFdIdeFfFIdHf
+3;Sugar Pie;10/3e2~hf1/1g~b1~a3/1dhe1ghc2/1edbcag3/1a1agc1h~1/1ehdbhefe1/10;DcHbGcfDHdeEdEcEbCcCeEdEeEdEcDdEbDcDdGdFcEdGbEcEbGbGcGIbHbhFFgEgGgGeFgfGeGfGgGdGeGfGgG
+4;Chitoum;10/3c3~a1/3d~g1~2/1e~eca~~2/1gfbgf~~h1/1heahbad2/2gh1cfac1/10;fDHffEdCdCeDgEfEdEeEIeHeeFdEcEdEeEfFeFdFHgfFeFIggFIbHecFdFbDbEeFcFfFdFbFeFfFfG
+5;Gammel Dansk;10/1gc1g5/2g1e5/2fhf~1~c1/1h1agfhed1/1d~1begdh1/1bdfefda2/10;eDeDbFdDeEGeHeeFGeFecDdDbGcGdGGfFgdEGgeFfFIdIeHecDcDdDeFfGbBEgDgdEcDeGcGdEIfbGcGdGeGfG
+6;Schweinsmagen;10/3c2h~2/3ea2~2/1~fdh~1h~1/1~d2cea~1/1gc1ad1dh1/1dh~dgahf1/10;hDCdDdgBEgFfGeHehFgEfEfGgGfGeGeDeDFfcGdDeDGedDcGdGeDbFcGdGeGfGbFcGdGeGfGgGbGcGdGeGfGgG
+7;Powsowdie;10/6f3/4h~bc~1/1e~ef~gfh1/1afdbhcgc1/1fgbhd1ag1/1hdcbad1a1/10;eCGcGcbDdEeEeFdFhCGdHdIdhEcEdFFecFGdbFcFFeGeHehFdGFfcGEfFfFgdFDgbFGgeFcFcGdGbGcGfGdGGe
+8;Ochsenmaul-Salat;10/1dba~2b2/1he1~a1h2/1agc~c~f~1/1c1afg~1~1/1ha1bfef~1/1dcbdghad1/10;dDdBhDHdgFHdfEeEcBdBcDdDeFdEGgeFfFbBcDcDdDdGdEeGfGbDcGdGcGbGbFcDdEeGCgcGdGbGcGdGeGfGgG
+9;Fugu;10/2b~~gb3/1f1~~ch3/1eca~ef~2/1hd1~2~2/1bagfca~c1/1fhf1hfhd1/10;FbgFfFeFFfEfFceFgDHfGfHfFffGEfdDcDcFbDFdEfcFdFcFbFcGbFeFfGgGGdFdcFdDeFfGgGcBGdFdCgEfDd
+10;Tequila Worms;10/1ad~3fe1/2e~3eh1/2hc1b~gb1/3ged~eh1/1fgegahfc1/1cbdfceh2/10;HdIcIcfDfEeEdEcCdFdEbBcBdEdFeFGgGfFfcCcFbFcFdFcDDfdGgGIeIecGdGeGHecGdGeGfGIfbGcGdGeGfG
+11;Blachan;10/2d1d1a~2/1~edh~1b2/1h1af~fd~1/1c3~ga~1/1fagfe1gd1/2ebgcda2/10;CchDeCeDHdhFGdgEIffEeDfDgDfGgGfGeFdFdGbCcGbFcFdFcCdCdGeDbFcFdGeGfGgBdDcGdGHgeDGebFcGdG
+12;Pemmican;10/1d~d4a1/1g~1~bheb1/1ch~~dfdc1/2bg~aeaf1/1egcecd1c1/3agac3/10;bBDbbCcDdDcDbDcDFcGcFcEdHcFdGdFdfEeEfEfFeFfGeFdFcFfFeFdFIcHecFFfGedFIcHeIebFcFdFdGGeHe
+13;Geoduck Clams;10/4g~4/3eh~1a2/1~efb~~hg1/1daed~ech1/1e3~be2/1fdbfcfhd1/10;GfCdDdeBfEHeGeIeHeeCHffDgEgGcEEdeEHgfEgFDddEeEcEdEcEbEeEgGfGdEdGeEcEdEeEcGdGbGcGdGeGfG
+14;Limburger;10/1a~g~3f1/1b~d~d~~c1/1dcf~h~eb1/1cgc~1~ha1/2adb~dge1/1afgfhgfh1/10;fCHfHfIcfDgGdBdCeFdDcDbBcEeDDfcFbDcFfGeGcGFdGgIfFgEgDgbEbEHgGgFgcEIfcGdGeGfGbGcGdGeGfGgG
+15;Crickets;10/1ha4d2/1dh~1~dch1/3h~hefb1/3acgba2/1e~hgd2g1/2bg1efba1/10;cCcCbBcCEfGcFdEeFeHcbFcFEeGcdFGdHdGdGefGHeGefGgGIcHddFeFGeFfEfIdHdGeFfEfHeGeFffGgGbCcCEf
+16;Takosu;10/1d8/1ec~2g~h1/1dg~~edga1/2ef~agcb1/1b1abfa1f1/1hfghgdeg1/10;gCcCbCCdFedDDeEebDcDcEgDIcHcGdFdbGEdEgFfGeFeHeIdHdGeHefFeEIeIgfEeGfGgGdGbGeGcGfGdGgGeGHe
+17;Roquefort;10/1~ed~hg1c1/1~gh~1e~b1/1~1c~1d~2/1aehf1f~b1/1eagdhehd1/1be1efhbg1/10;CbDbdDcCcFcCDfbFFbEeDfEfGbGfFfFbdFeFGgGfHfIcbFcFdFgFIcHfGfFfEfDfIeHfGfIfHfFffGEfDfeGfGgG
+18;Warm Beer;10/1a~4h2/1ca~~b~ad1/3~~fa1a1/2hbf1eae1/1cfdchb1c1/2eagfhag1/10;bBHcFdbCcCdDHcIcHcGcFccEdEGfFdcFdFeFeGGfHeIedGFffGgGbFIgHgGgeFEgcFIgHgfFdFdGeGcGdGgGeGHe
+19;Rat;10/5~ea2/3~~ea3/3~fgb1g1/3~e3b1/1ha~dbhfd1/1bg~bhdha1/10;FcGcHbGbcFEcGcFcEcEfDfEfFfGfdFcFdFHfIfGgHfeFeGbFcFdFeGfGdGeGfGgGIfcGHgGgFdGdbGcGIgHgGgFd
+20;Snake;10/4b~1~a1/5d1he1/1df~~b~ac1/3e~chfb1/1gab~dce2/1ebh~gfac1/10;FfdFIbHeHdIccDbDcDdDdGdFcFeFGddFFegFfGHfeGeFIdGgHgbFcFdFeFeGdGcGeGdGfGgGIeHfeBbGcGdGeGfG
+21;Seal Flipper Pie;10/2b2a~a2/1~gb1c~e~1/1g1h~g~cg1/1fc1he~fe1/1da~fb~db1/1ghbgadbc1/10;CcfBHbfChCHdFdfEeEEffEFfcFdFbEdGcGcFGgHeGedFGfHfIfFgGfgGIdbFcFdFdDcGdGeGfGgGbGcGdGeGfGgG
+22;Souse;10/6egf1/1a1h~fh3/1d~f~c4/1cdb~e1ab1/1fce~hdhg1/3fg1hfd1/10;bDDedFFdcEEebEGcFcGcFcEeFfDfFfGfHfcFbFcFHbGcbFcFdFFeEefFeFfFIfHfGfdFIfHfGfFfIbHbGcfFgFgG
+23;Chitterlings;10/1a~4~e1/2~4e2/1gbfae~g2/1chchd~e2/1fcdaheag1/1h1cbedce1/10;fDIbHefEeEdFEfcEdEFgdFGgeFbDcEdFHfGfeFIffFcFbFdFcFbFeFEgcFdFFfEfDfCfbBcFdFeFeGfGdGeGfGgG
+24;Blubber;10/2a2~g3/1~ha1~4/1cah~edb2/1aegfc1ge1/1fdhahbhd1/3afdgea1/10;CcbCFdEdDdGdFdEdDdGbFdDfEeDeEeEfHdGdFfEfFfHfGfHfIeEfFfIffGbDcEdEcEIgHgGgeEcFbEcFbFcFdFdG
+25;Sea Cucumber;10/1c1gcfh~2/1h~bfe1ga1/1ea3~d2/1b1g2~hb1/1f~hca~bh1/1hdcdfdga1/10;HdHdIfIcDcEbFbfFgGfGeGgBDfHfGfFfcGdGbFcFdGeGbFcGbFCccFbFDcCcbFDcCcFcEcDcCcbGCdbGcGdGeGfG
+26;Chichirrones;10/10/4a2~h1/4gd~~2/1~~geb~~2/1a~bh1hdc1/2chafgef1/10;IcHefDeDbFDeCeIfHffDeDEeDeFeEeEfCeEgFgFecFGebFgFEgcFGgFgFebFcFdFhFeGfGdGeGfGgGcGdGeGfGgG
+27;Lutefisk;10/1dfagd4/3gcfb~b1/3ce1d~2/1~ha1eh~e1/1fdchg1~g1/1ehfdfghf1/10;gEIffCgCfCgDIcfEgEeBfCeCdCCefGeFdFDecBbBEgcBbEFfcGcFgGfGeGcGEdDfFcDgFgEcEddGGebGcGdGeGfG
+28;Mountain Potato;10/1ha2~b1f1/2g~bge~a1/1gd~fhb~2/1dfbed1~2/1acf1b~~2/1efcghefa1/10;EccDGbgCHfIcHgIcCdEdDdgDfBhGfFgDfGgGfGhGgGfGeEdEdGcGbGcGdGGgFgEgDgbFcFdGHgeECgIgHgGgFgEgbB
+29;Oxtail;10/2e~e~1~c1/1a1~c~1~2/1c~h1~~c2/2bdhe~g2/1gcfcdcec1/1cahbeafa1/10;eBHdeCfFDddEeEcEdEeFbDcEdGdFHfdGcGcBdFeFDgbFbDEgcEdFeGdGeGfGcFdGeGfGcGdGeGfGgGbGcGdGeGfGIb
+30;Fufu;10/1g~h~1bch1/1e~1~~hgf1/2~1dhc3/1cbgha1a2/1dgc1gfgb1/1hchebaf2/10;GcdBeDHbGcbBdEcEbEfCHcGcFcIbHcGcFcEeDeFdEeDfDgbCcFFecGdGGfFgEgFgGgHgHfeEIcHcGcGdIfHfdGeGfG
+31;Hete Bliksem;10/1a~1dg~gh1/1gf~1f~3/2befe~~a1/1beac1a~2/1hgbgfdec1/1g1afcef2/10;fDgDHbIdgEeBfDfFgFdDeDeFfFgFIfHfeFcCdEdEeFbBcCdEdGeGcDcEbEcEdEGgFdcFbFbCCedGeGfGIbHbcFdGeG
+32;Natto;10/1a~h~f1c~1/1gcd~1ehc1/1hef~~fbh1/2d1a~hdb1/2hbf~ghc1/1~ecgac3/10;FbhBhCGddCeEGebBcBdDdDeDcCdDeEbCcCdDeEHdHeIedFGfHeGecDeGdGcGcFdFeFHfIfGgHfGfFfdDCgGgFgEgbD
+33;Bats;10/5~c1d1/4~~bce1/1chd~g1e2/1adfhedcf1/1bcbebcba1/1d1gbef1d1/10;GbFcGcHcIcgCIcfCgCgFfFGfeEfEdDcDdDeEbDdEcEIfHffEdEbFIfgEhEbFcFEeDeCfDfEfFfGfFfdFeFfFcFdFdG
+34;Shiokara;10/3a~e1h~1/4~3~1/1c~f~~~1h1/1ghgab~be1/1bd1bc~cf1/1efefad3/10;fEHebDdDcDeDdDeDfDfFgFFbdBeFeDfFeGFgdEcEGgeFcGcGdGbEFgdEGgeFcGIehBHfIefFbFcGdGIfHfbGcGdGeG
+35;Sild;10/2h7/1df~g3b1/2h~hagfd1/1fg~2eac1/2d~edhgf1/1bfh1efca1/10;cDcEbCCeEfEcDfbEFfEfGfFfEfEdFdHdGdcFdFeFIdHfGfHfGfFfEfIdHfGfIfHfFfcGEfDfHgfDGfIfFfEfeDIgfD
+36;Cho Do Fu;10/1b2~hf~d1/1gh~~1c~e1/3~~~g~b1/1g1e~e1~h1/1bhghbdcg1/2faehgac1/10;FbdEcCdEbCcCbCcCdFGdFgIbHfeGGfHfIcdGFfHfeGfGeGfGgDGdcGdGIfcGdGeGfGbFIfcGdGeGfGbFcGdGeGfGgG
+37;Bubble And Squeak;10/2g~fh~gc1/3~ce~3/2c~1b~3/1hg~a1~1g1/2dcehagd1/1ecbgfhe2/10;cBEcEfEcFcEcDeHbEfFfGffCfGeFdFfFeFcEdFeFfDeGGfFfcFIfHfGfFfIfHfGfbEcFdGeGIbHbcGdGeGbGcGdGeGfG
+38;Potted Hough;10/10/4ac4/1~h~bfe~c1/1hgea1gae1/1cd1febfc1/1eaeadfag1/10;CdEdFddEdEFdGdeFGfHfIdIeHgfFeFgFfFFdfGeGcEcGdGcGdGeGbFcGbGdGcGeGdGfGHfIfeGGfHffGFgEgIgHgGgdE
+39;Diniguan;10/1~da3a2/1~aehc~ch1/1~1bde~1a1/1gdc2~he1/1ahaebhd2/1cecdcg3/10;HeCbDbfCHcfDIeHeDcEceDfDdDeDfDeFdFfFeFgFIeHeIeHeGfFfcFHfGfFfcGcCbFbEcEHcGfFfcGbGcGdGbGcGdGeG
+40;Spam Musubi;10/1f2hb~dh1/1b~cgh~3/1g~bec~fa1/2~gb1~be1/1dedhga1c1/2cbfbede1/10;bDfCIdHdDeEeFdEdDdDebFfFHeHbGdFdEfEeeFDebCcEGfdEbDcEdEcFGgFgdFeFfGIeeGIgHeIbHbdGeGcGdGeGfGgG
+41;Poutine;10/1gde3a2/1be1d~1c2/2fbc~~e2/3hfadad1/1cbc1dedh1/1adegfbfe1/10;eCfDHdHdIeeDfDdDeDfDfGcDdDdEeEfEIfcDdEeEfEgGbBcDcDdDeEfFgGfGbCcDdEdGbFcFDbcDcGdGeEbGcGdGeGfG
+42;Durian;10/1fa~1d~3/3~~h~~a1/1c~~g1h~e1/1ebabcdgc1/1fdcf1ecd1/1ecbahcf2/10;cBfBgDbDcDbBcBcEFeEebFFcIcHcEeGeFeEfHdGeFeIdHdcFCgGgFgdFDgEgeGfGIedGeGHecGdGeGfGIfbGcGdGeGfG
+43;Beetroot;10/1cfb1c~a~1/1e2gf~g~1/1d2ab~1~1/1e~hb2f~1/1dgehg~hd1/1hcgafbda1/10;bEhEHfIfhBIehCgGfFgFfBfCgChCeCeFfFeFFfdFFdeFcEdEdFFdEebFEfcFdFGdFgFdEfbFbGCbbGcGDbCbbGcGdGeGfG
+44;Witchety Grub;10/2d2~b3/1ba~e~1~f1/1a1ca~~hd1/1g~b1~cae1/1defeb1ba1/1hgbdgagd1/10;GbeCdFcCEdbEcFcCDfeFdFGeFgFfEfIcHdHdGdGgHgbEEgcFdFdGeGbEcFdGeGfGbFcFdGeGfGIecGdGeGfGgGGebGcGdG
+45;Fiddlehead Ferns;10/10/1gb~6/2c~6/3~h~2h1/1~fcb~a~e1/1adegfgdc1/10;cCGfeEdEdFFecFEefFeFdFgFfFeFdFeFGfFfEfDfHfGfFfEfIfHfGfFfIfHfGfbCcCdECfDfeEcGdGeGfGbGIgHgGgFgcD
+46;Vegemite;10/2af~a4/2dg~5/1~hce1e~c1/1c1dca1~g1/1gbegbada1/1hgfedhch1/10;FbdBcBeBdBdCcCCdDdEdFbEccGEdbFcGbFcDFeEfDfFfEfGfFfHfGfIeHfGfIfHfIfEfGfgGFfcFfGeGgGgDHfcGdGeGfG
+47;Blutwurst;10/1bfc2~bd1/1eah~f~f2/4~dhea1/1fd1~abcd1/1hef~cgeh1/2dbgbed2/10;FccFHbFdGdHeIbdCFdFedCeDcCeEdCIeHeeEDfEfGfFfbBcBDfdCcCbEHfGfcGcFFgdCbCcCGgdCcGdGeGfGbFIfcGdGeGfG
+48;Sylta;10/10/10/1h~7/1db~~~~~2/1ae~~b~fa1/1e1f1d~h2/10;bDcFcEbEfGHfGfFfdEcEIfHfGfeEdEfEeEfEgEhFgFfFeFdFcFgFfFeFdFgFfFeFbFcFdFeFGfFfEfDfCfHfGfFfEfIfHffG
+49;Chicken Feet;10/5~ecg1/5~g3/5~e3/1ce~h~hdf1/1bcgbcfcb1/2faedhaf1/10;GbHbGbEeFeeFIbGcHbFeGcFeGddFFfGeFeHeGeIeHefFeEcFbEdFeFcEdFHfeFcFbFdFcFIfHfeGdFeFeGdGeGfGcGdGeGfGgG
+50;Gravlax;10/6~hb1/4g~~c2/1d1ca~~bg1/1edh1g~1h1/1fedgefdg1/1dbhd1dba1/10;eCeDdDeDfDHbGdFdEdcEHdGdHdFdcFEdDefEDgEfDfbEFfcEGfFfGfHfIdfFIfEgcFdFeFHdIbhDIfHfIfGfgGFfEfDfbEbGcG
+51;Kangaroo;10/1h~1~a4/1eh~he4/1a1da2g~1/1h1ghecab1/1bab1hbhc1/1hgedceca1/10;cCFbbBcCdCFchDGeFeHeGeIeHefFIfeEfFdDeEbCdEdGcFbFEecCdDIfgFeEdEdGeGdGfGHfGfcFdGeGcGdGHgeEbGcGdGeGfGgG
+52;Uni;10/2b~1f~3/1~dh2~a2/1dfgcg~3/2gcedbh~1/1f1gfah1b1/1aghcegda1/10;cBCcCdDdHcDeDdEdFdGdhEgEfEeEeEGgdDeEdEeEfFcEdEeEfBeGdGbDbGcGcDbGeGdGfGHeIeHeeGGffGgGFgEgDgIgHgGgFgcE
+53;Pork Loaf;10/1~e1e~hdg1/1~a1f~4/1~b1c~fb2/1~ahge1ga1/1abghfgc2/1cgf1dcad1/10;eCCbCdHdGdeCDeEeGbFbEfcEEfFffDFeEebDDfEfFfGfHfHbGdGbfDEffFHfbEFfcEcFbFcFdFeFHgIgIefFIbHbGbFfEfDfbGhGfGgD
+54;Jellied Eels;10/2dfd~ed2/1~cba~g1e1/1age1~c~h1/1gfhc~1gf1/5~fba1/2gahbged1/10;eBgDHbGbdBeBGdfFeCdCeCfGfFgDHfHfcCdCGfeCgGIfHfdEIeHfeECcdEcEeEIeHfdEeEIfHfbEcEdEeEdGcGdGeGfGbEcEdEeEfGgG
+55;Owl Soup;10/2~gc~h3/2~becb~2/1~hdhb1h~1/1~feah1gh1/1dagbcaea1/1abf2gbe1/10;hDDbEbGbFbgCfCeCdCFddCcCeCdCCdDdEdEdcEcFFdbFcFEdDeEeDeFeEehEEfFfGfHfIecEdECfDffFgFeEfFgFgGEfGcDfFfEfbFbG
+56;Chewing Gum;10/1~f3cd~1/1dg1~d2~1/1bh~de1db1/1dcehcagd1/1c1db1gag1/1bdahfad2/10;FcFdEdEeFeGeHeHfhBIdCbfGeGdEcEcDbDfEeEdEcDdDdGDgEgcEFgFebEcEdFeGdGeGIfcGdGeGfGIfbGcGdGeGfGgBhBIfbGcGdGeGfG
+57;Cod Tongues;10/3~ae1ef1/1~e~dbc1c1/1~ahg2cf1/1gcb1cda2/1a1defefa1/1fcafdhe2/10;EbCcDcFbEbDcEcDcFcEcCdDdGcFcEcbDcDEfFfGeFfGgIdhFcEEdDddFeFdFfFeFeGgGIfHfIddGcGeGdGfGHfGfhBIdIdHfGfbGcGdGbGcGdGeG
+58;Scrapple;10/4bg~f2/1~dgc1ae~1/1dec1bfb~1/1ebfaed1f1/1dca3he1/1fefehda2/10;CcCdHbhCgCfDDcCdDdCdEcEcDcFbEcDcDdDfEecFFeEeGeFeFgEgDgbFGgFgEgEeHdGeFeEedGeGfGcGdGeGfGIdHdGeFeEeIfdGeGfGIfbGcGdGeGfG
+59;Blazing Curry;10/1h2g2d2/1g~ca~ce2/1h~2dagc1/1ebgdfh3/1achece1b1/1e1dfeacg1/10;DcbCcCeCFcEcDcGcFcEcHcGcFcHcGcGdeCdCcCHdfCeCdCIdgCfCeCGcHcGcFcEcDcCcCebFDeEeFehDgDCeDeeEGeFeEeEfFfGfcEbEGgFgdEcEdEHgGgeEIgHgHdeFdFcFfFeFdFIdHddGFfGfbFcFEfeGDfCfdGeGfGgG

+ 63 - 0
non_catalog_apps/vexed/assets/levels/06 Panic Pack.vxl

@@ -0,0 +1,63 @@
+# Author: Vexed Development Team
+# URL: http://vexed.sourceforge.net
+# Description: Very Hard! So hard you'll panic after the first one.
+0;Man Made Monster;10/7~h1/7~2/7~2/1~ad~~~~c1/1hbaeb~~2/1c1d2~e~1/10;fFeFfFgFCeDedFcFbEeFdFfFeFgGfFhGgGIbIeHeGfFfEfcEHfGfFfdEeEfFgFgGeFdFcFbFcFEfDfCfFfEf
+1;Near Dark;10/10/10/10/1f~~e~~~d1/1ac~a~~~e1/1d3c~~f1/10;bEeEEfcEdEfFeFdFcFfFeFdFgGfFeFbFcFdFeFgFIeHeGfHfIfFfgGEfDfCf
+2;Caged Fury;10/10/10/2h~~5/1gb1~~4/1fdf~a~g~1/3ba1~dh1/10;cDdFcFhFHgfFeFdFcFIggFfFeFhFgFfFbFcFbFcFdFeFfFHfGfFfdDIfHfGfeEEfFffFgFgG
+3;Night Gallery;10/10/1c8/1h~7/2~~~~~hf1/1e~~bd~1c1/2~de1fb2/10;fFHeIeeFGeHeDgbDEgcFbFbDdFFfGeFeEfcEdEeEfEgEhECfDfEfFfGfFfdFeFfFcFbFdFcFeFfFcG
+4;House Of Wax;10/1cd~6/3~1~d3/3~1~4/2~~1~4/1g~ha~~~g1/1a~2c~~h1/10;eFdFIgHgfFeFIggFfFGcFfEfDfbFcFcBDedFeFfGHfGfFfEfDfbBcBdFeFfFfG
+5;Dorian Gray;10/10/5h~3/6~3/3f2~3/1g~egcf~2/1c~2he~2/10;bFDfCfDfEfFfGfgGfGeFdFcFfFeFdFgFfFeFbFcFdFeFGfFfEfDfHffCGfFffGEfDf
+6;You'll Find Out;10/7~a1/7~2/7h2/5~~b2/3~~dhg~1/1~a~gb2d1/10;HeGeFfcGEfeGGfFfdFeFfFHeGeDgCgEgDgFfGfFfHfGfIbHfGfeFcGfFgFhFbGcGdG
+7;Leprechaun;10/10/6d~2/7~2/3~~~~~e1/1d~h~g~~a1/1e2g1ah2/10;gCIebFdFcFfFHeeFGedFFeEeDfCfEfDfFfEfGfFfHfGfIfHfFfdFeFfFcFdF
+8;Evil Dead II;10/4f~4/5~4/1~b2~4/1~2~~~~g1/1~d~fhdc2/1hg~1c1b2/10;cFEfDfCfFfEfDfGfFfEfCdIecFbFcFbFdFcFeFdFfFHeeFGeFeEfDfFfEfGfFfHfGfeFdFbGeFeBfFgF
+9;Grizzly;10/10/10/1c~~6/1bh~~~~3/1cf~e~h~a1/1f1a1~eb2/10;bDcDcEdEGfIfHfGfeEfEgFfFeFhFgFfFdFcFbFeFdFcFfGeFdFfFeFbFcFdFeFGfFfEfDfCfHfGfFfEfIfHffG
+10;Diva;10/10/1h~~6/3~6/2a~~~~~e1/4~~a~a1/1bge~gb1h1/10;bCcCIeGfHfIfdGFfdEGfeEHffEgEhFcEgFfFeFEgFgGfFfdEHfeEeGcGdGbGcGdGeG
+11;Raven Dance;10/10/6~~b1/5~d3/3~~~b3/2c~~heg2/1~d~eg1hc1/10;GdFeGeEgcFdFFeEeCgDgEfDfFfEfGfFfHfGfeGdFeFfFgFcFdFeFfFgFIcHcGfFfEfcGbGcGdG
+12;Nosferatu;10/7e~1/8~1/8~1/1c~~a3~1/1d~fc~~~~1/2fa1~d~e1/10;EebFDeCeGgeFdFcFIgHgfFeFdFgFfFeFbFcFbFdFcFeFGfFfEfhGhBIffGgG
+13;Eyewitness;10/2c~6/3~6/3~d~e~2/3~3~2/1cg~~~~~2/1g1~hedhc1/10;EgcFEdcBdDFgdFGgeFdFHgfFeFeGGfdGFfgGgDHfbFcFEfDfCfeGfGeDfDdGeGfGgD
+14;Jaws III;10/10/10/7~g1/1d~~~~1~d1/1e~fgfh~c1/1h~1e1c~2/10;DfEfDfbEFfEfcEGfFfdEeEfFeFdFcFbFgFfFeFdFcFgFfFeFdFFfGfFfEfDfIdHfIeGfHfFfEfDfGfFfIf
+15;The Terror;10/3d~5/4f~~3/6~d2/4c~~h~1/2h~1~~1~1/1~fd~~~1c1/10;cFCgDgdGcGeGdGfGHdHeeGGeeEfEgEhEFgdBeCfCeCfCGebGcGdGeG
+16;Salem's Lot;10/2b~6/3~6/3~6/3~~h1~b1/1~~~g2~2/1dh~de~ge1/10;FecGcBbGDfCfEfDfEfCfcGFgEgDgbFcFdGeGfGcGdGeGfGgGIebGcGdGeGfG
+17;Evilspeak;10/10/10/2d~6/2b~~b~3/1dc~~a~~c1/1a1~2g~g1/10;cDfEFfEfcEgFIfHfGfFfdEeEfFgFeFdFcFbFfFeFdFcFEfFfGfgGEfDfCfFfEf
+18;Nightmare Castle;10/6~f2/6~3/6~~d1/5~a~2/1cb~~~1~2/1a1cb~f~d1/10;cFdFbFcFHbGgFgEgIggDHgGgFgHgGgGeFfEfDfCffGeGgGIdfGhGdGeGfGgG
+19;Patrick;10/10/10/10/2g~~5/1fca~~g~~1/1a2~bfbc1/10;cEgFhFFgdEGgeFdFcFbFfFeFdFcFfFeFdFgFfFeFeGGfFfHfGfIfHfFffGEfDfCfeGfGgG
+20;One Dark Night;10/2b~6/3~f5/3~6/1~~~~~~~b1/1hg~~eg~2/1eb~f2~h1/10;EgcFcBbFDeCedFcFdFIggFIefFHeeFGeFeEfbFFfEfGfFfHfGfbGDfEfFfdFcFEcdFeFcGfFgF
+21;Cat O' Nine Tails;10/10/2~a6/1h~7/2~~1~f~g1/1~~~~~dah1/1~f~gh1d2/10;GeIeHeGeCgEgDgFgFfGfHfIfEgFfEfDfbDGfFfEfcECfHfGfFfDfcFbFDccFdFcFeFcGfFgFbGcGdG
+22;Teen Wolf;10/2~a~e4/2~7/2~~6/1gh~~5/3~f~~~h1/2gah~e1f1/10;DbEfcDdEcEIfGgeFfFHfGfdEbEcEFgdEGgeFdFfFeFfFgFhFFbEbDbcEdFdGcGdGeG
+23;Old Dracula;10/10/10/10/1cg~~~~3/1ede~~~~a1/2g1ahdhc1/10;cEbEdFcEdEIfHfGfeEfEgFhFfFeFdFcFgFfFeFdFhFgFfFeFhFgFfFbFcFdFeFfFHfGfIfHfFffGEfDfeGfGgG
+24;Duel;10/10/10/1e8/1a~~~~~c2/2h~a~~g~1/2e2gc1h1/10;cFeFdFfFHeeFGeFeEfDfFfEfGfFfHfGfeFdFeFfFgFhFbEcEdFeFbEcFdFeF
+25;Hell Knight;10/8d1/7~a1/7~2/1h~~~~~da1/2~~fc~fh1/2~~h1~1c1/10;HeIeEffFHeGeFfEfGfFfHfGfIfHfDgEfDfbEFfEfcEGfdEdFcFdFeFfFgFhFIcIcHfGfHfFfEfcG
+26;Road Games;10/3c~1~h2/1c~e~1~3/2~1~1~3/2~1~~~3/2~d~h~~2/2~2ced2/10;dFfFeFgFfFHbGeFfdBGfeEEfDfbCcFdFfFGfHfGfFfEfDfdCeFfFfG
+27;The Children;10/7~b1/2h~~f1~2/3~~2~2/1b~f~~~~2/2~gha~~~1/1f~1a1~g~1/10;dEbGDfbEEfcEcCdDFcEdeEdEDefEeEfEgFfFeFdFhGgFfFeFhFgFfFCecFdFeFfFHfGfFfEfbEIfIbHfGfFfgG
+28;Christine;10/10/5~h3/1f~2~4/2~~~h4/1g~g~b~~e1/2e1~1f~b1/10;dFFebFbDEfcEfFgGIfHfGfFfdEeEfFgFeFdFcFfFeFdFFfGfgGFfEfDfGcFf
+29;Rage In Heaven;10/3g~5/4~5/4~1~~b1/1f~~~~1hg1/1b~chacef1/1a~3e3/10;IdHdDfEfFfGfHfdBeEIeDfEfFfHffFbEcEdEeEfFgFeFdFcFfFeFdFgFfFeFhFgFfFbFcFdFeFfFHfGfFfEfDfCfIfgDHfGfHfFfEfDf
+30;See No Evil;10/10/10/7~b1/3fh~~~2/1egab~~~g1/1dhdef1a2/10;eEdEeEfFgFIdeGdFcFeFdFfFHeeFGeFedGFfGfHfcGEfFfGfbGDfCfEfDfFfEfIfHfGfFfEfcFbFdFcFeFcGfFgFbGcGdG
+31;Night Must Fall;10/10/10/6g3/1b~~~~f~2/1dah~~b3/1hf1d~ga2/10;bEcEdEeFdFcFbFeFGedFFecFEeDegEbGDfEfFfGfGgeFdFcFHgfFeFdFHeFfGfFfGfEfDfeG
+32;The Psychopath;10/1g~b6/1c~7/2~7/1f~~6/2g~~~~~e1/1~feb~1~c1/10;bEcEdFcFeFdFeFfFgFIfHfGfFfCgDgbBEgcFdFeFfFgFDbbCcEdFcFeFcGfFgFbGcGdG
+33;The Raven;10/10/3h~5/4~5/4~~~b2/1f~he~dh~1/2~ed~b1f1/10;DfbFcFGgeFdFfFHeeFGeFeEfDfCfFfEfDfGfFfEfEgcFbFFgdFcFGgeFdFHffFdGgFhFcGGgdC
+34;The Unearthly;10/10/1f~7/2~7/1a~~6/2~~~b~hb1/1fh~~3a1/10;HfcGbGdGFfGfcGEfFfDfbCbEcEIfHfGfFfdFcFeFcGfFgFhF
+35;House Of Usher;10/10/1h~3~b2/2~~2~g2/2~~e~~3/2~hd~2g1/4e~bhd1/10;HcHdDfGgeEGeFeEeGeFeEeDebCcDdEeEfEfGdFeFcFdFHgGgGeeGfGgGcFdFeGfGgG
+36;Paranoic;10/10/1d~1g~~ef1/1f~2~2g1/2~~1~4/3~~~4/1de~~~~3/10;bCHcIcbDcEdGcGbGeGdGcGcEdFeFGcHcGceCfCgChCFffGdGeG
+37;Hellraiser;10/10/10/6~h2/6~ec1/2ac~~~ge1/2h1a~~1g1/10;dFcFeGdFfGHdeGGfFfEfDfHeIeGfFfEfHfGfFfHfGfIfHfGfeFdFfFeFgFhFeG
+38;Theater Of Blood;10/10/10/5a4/3~~c~~f1/1b~c~e~ge1/2~af1b1g1/10;IedFDgbFEgcFdFFeEeFeDfCffFHeGeeEfEEfDfFfEfgEhEGfFfHfGfIfHfEgFfEfGfFfIfHfGfFfdFeFfFgFhFcFbFdFcFeFfFcG
+39;Cujo;10/10/10/4a~1~b1/3~d~~bd1/2~~cf~3/1f~~2~ca1/10;eDbGcGEeDfEfDfHgHefEFfEfgEGfFfEfcFdFeFfFIdHeHeGfFfEfIeHeGfgGFfEf
+40;Deadly Blessing;10/1~f1~~e~f1/1~2~2~2/1~2~2~2/1~d~~2~2/1~1~~~h~d1/1e~~f~1h2/10;IbCeCbbEbGcGbGeGGfHfdGFfcGEfcEdEeFGbdFFbIfHfGfeFfFgFdG
+41;The Boogeyman II;10/10/7~c1/5~~~2/3~~bh~g1/1f~~~db~2/1gc1d1hf~1/10;bFcFdFFeGegFIeIcfFeEfEHdGdFeEeGeFeEeHeGeFehGgFfFeFdEDffFHfGfeEfEgFgGEfDfFf
+42;Bloody Birthday;10/10/10/10/3~ad1a~1/1~~~ebdcg1/1c~~2gbe1/10;EeFeEeDgEfDfCfFfEfDfGfFfEfhEHfGfFfEfcFdFeFfFgFbFcFdFeFfFgGIeHfIfGfgGFfEfDfbG
+43;Day Of Wrath;10/5~b3/5~4/3d~~4/4~~~~2/1~~~~abcg1/1e~age1dc1/10;FfdDGfeEHffEIfgEDgEgFfGfHfEfDfCfFfEfDfGfFfEfGbFfEfcFbFdFcFeFcGfFgFbGcGdG
+44;Horror Of Dracula;10/2h~c2~g1/3~3~2/3~3~2/1h~~~2~2/1fc~~e~af1/1g2a1~1e1/10;bEFfEfcEdEeFfFHfGfFfIfHfGfFfdFeFfFgFhFcFbFdFcFeFdFIbFfHfGfFfEfDfCfcBdFeFfFEbdF
+45;Day Of The Dead;10/10/6~~h1/5b~ea1/8g1/1ed~~~b~a1/1hg~~~1d2/10;IcGfIfHfGfIfHfIfFgGfFfHfGffDHchDgDhDIfHfIfeGGfFfHfGfEfcFbFdFcFgDhDIfHfGfeFcGfFgFbGcGdG
+46;Creepers;10/10/7~d1/7~c1/1f~3b~2/2~~f~h~g1/1bh~gd1~c1/10;GfcGEfFfbGDfEfGfEgcFCgFgdFeFfFgFIfHfGfFfDgbEEgcFdFeFfFgFIgIcIdHfGfFfEfcGbGcGdG
+47;Bordello Of Blood;10/1f~7/1c~7/2~~~5/3~h~~~b1/1f~~e1c1h1/1a~~b~ae2/10;bBbCeEbFcGEfcDdEFeEecDdDeEfEdEeEbGDfEfEgcFCgGgFgdFDgEgeGfGIedGeGfGHefEgEhEcGdGeGgGbGcGdGeGfG
+48;2,000 Maniacs;10/6~b2/1d~~b1~3/2~~2~~g1/1g~~~e~~2/2~1~1~~2/1~~~~1~de1/10;EcbEDebCcEcGdGHgfEHbGeFedEeEfEIggEIdHeGehGgGFeEeDeCgcGbGcG
+49;Twisted;10/10/5e~c2/1h~3~3/1g~~2~~2/1c~had~~~1/1d~1g1ea~1/10;fFfCgEeFhFgFfFhFgFdFeFfFHcgFGfFfEfDfIfHfGfFfEfDfbDcEdFeFfFgFgGbEcEdFcFbFDf
+50;House IV;10/1eb~6/3~6/3~~5/3~~~~~2/1~efc~~gd1/1gdc1b~f2/10;cBbBcBdDeEdECfDffGHfGfeEEfIfHffEgEFfEfcFbFdFcFeFdFhFgFfFeFGfHfGfIfHfFffGEfDfbG
+51;The Black Room;10/1c~7/1e~7/2~7/2~2hg3/1~~~~gdae1/1~ad2c1h1/10;FfGeEfFfCgDgEfDfFfEfCfGfFfEfHfGfFfEfIfHfGfFfEfDfbFbBbCcFdFcFeFdFbGfFgFhFeFfF
+52;Bluebeard;10/7e~1/3~f3~1/2f~4~1/3~~3~1/1bahe~~~~1/1ah1b~~~~1/10;EcdEeGfGeGdFcFcDeFdFfFeFdFbFcFdFFfEfDfCfGfFfEfDfgGfGhGhBIfeGfGgG
+53;The Severed Arm;10/7~a1/7~e1/6~~2/6~~e1/1gfa~~~e2/1f1h~~hg~1/10;dFcFbFdFcFGgeFdFHgfFeFgFfFIbIcfGHdeGGfHfdGFfEfGfFfHfGfEfeGDfCfdGeGhGfGIe
+54;Homebodies;10/5f4/2~e~c~3/2~d2~3/2~2~~3/2~b~f~~~1/1~~c2edb1/10;FcdFfFeFgFfFfCGeFfEfDfDccFdFeFhFgFfFEcHfIfHfGfFfEfCgDgDdDdcFdFbGeFfFgFgG
+55;The Wolf Man;10/1e~3~a2/2~3~3/2~3~3/1b~~1~~~g1/2~a~h~~c1/1hegc1~b2/10;dFHgHbgFfFeFgFIefFHeGeFfEfGfFfHfGfIfHfcGEfFfGfbGDfEfFfbEcEdFdFeFfFbBcFdFcFeFfFcG
+56;Conqueror Worm;10/2f~6/3e~5/4~5/2b~~~1g2/1~a~~~~fc1/1c1~1aebg1/10;HfGfFfEfHfGfFfEfcEIfHfGfFfdEeEfFgFeFdFcFfFeFdFIggFfFeFGfHfGfFfdCeEfFfGEfDfCfcBdCEf
+57;The Fifth Floor;10/1e~4~a1/2~4~b1/2~3~~2/2d~~1~3/3c~d~h2/1b~ha1ec~1/10;cEdFfFeFdFDgEgFfEfGfFfbBcEHfGfFfdEeFfFIbdFeFhGfFHdGfFfEfcGIcHdGfgGFfbGcG
+58;The Uninvited;10/4~b4/4~5/1a~~~5/1g~1~~~gh1/1d~~hcec2/4a1deb1/10;HeGeFeFbEdeEEfDfDdbDcDdDfEgEcFFfGfHfIeHeGeFeFfEfGfFfHfGfFfdFeFfFgFcFdFeFfFgFbFcFdFeFfFbFcFdF
+59;Blackenstein;10/10/10/4~h4/1~~~~e4/1cba~de~~1/1bfdcf1ah1/10;dFFdcFEeFebFDeCeEeDeCegFfFeFdFcFhFgFfFeFdFFgGfFfEfDfHfGfFfEfIfHfGfFfEfcFdFeFfFgFbFcFbFdFcFeFcGfFgFbGcGdG

+ 63 - 0
non_catalog_apps/vexed/assets/levels/07 Twister Levels.vxl

@@ -0,0 +1,63 @@
+# Author: Vexed Development Team
+# URL: http://vexed.sourceforge.net
+# Description: Difficult. Many of these levels have twists that add challenge.
+0;Tornado;10/4~~f3/4~2g2/1~e1~1~e2/1~2~1~3/1~2~~~3/1~~~e~~gf1/10;GbFbHgGgeFfFgGCdbGfGeGcGgGfGdGhGgGeGHdHdgG
+1;Helm Wind;10/1~b2b~1g1/1~3g~~a1/1~4~~2/1~g~2~~2/1~1~~~~~2/1~~~~a1~2/10;CbfBGfbGcGcEFgdFeFfFgFIcHfGfFfdGfCIc
+2;Solar Wind;10/10/10/6~b2/1b~c~~~3/2c2~~3/1~h~c~~h2/10;dECgeGcGfGdGeEHdbEcGdGeGbGcGdGeGfG
+3;Puelche;10/10/10/1e~c~5/1f~1~5/2~1~c4/1~~~~e~ef1/10;dDFfFgEgDgCgHgGgFgEgbDbEcGdGeGfGgG
+4;Thermal;10/5~a3/1g~~~~4/4~~4/4~~4/1e2~~1e2/1a~~g~~g2/10;bCcCEgHgGgdCGbbGcGdGbGcGdGeGfG
+5;Chinook;10/1c~2h4/1h~1~b~3/2~1~1~3/2~1~1~3/2~~~~h3/2~1b~c3/10;fCFcEfDfbBcFdFeGGfFfGfEfbCeG
+6;Storm;10/10/10/5~g3/4h~4/1a~eg~e~~1/2~3h~a1/10;eFIggFfFeFhFgFfFdFeFfFHfGfFfEfDfbFcFdFeFfFgFgGGdFfEfDf
+7;Breath;10/10/10/10/4ca~3/1e~3~~2/1baec~~b~1/10;eGfGfEeEfEgFdGcGeGdGfGHfeGhGgGbFcGdGeGfGbGcGdGeGfGgG
+8;Eurus;10/8e1/7~a1/2h~3~2/3~2~~2/2~~g~~h2/1ge1a~~3/10;IcHeHfGfeFfFgFIcHeGfFfEfDfHfGfFfEfDfcDdFeFeG
+9;Samiel;10/1b~7/2~7/1d~7/2~7/2~~2~~a1/1ea~~de~b1/10;bDcFdGcGdGeGgGIfHfbGcGdGeGfGbBcGdGeGfGgG
+10;Gale;10/10/1a~3~g2/2~3~3/2~3~3/2~d1~~3/1h~g~h~ad1/10;bGDfdGfGHcGfcGdGeGbCcFdGeGfGcGdGeGfGgG
+11;Gust;10/10/10/10/1be~~2~g1/1g2~2~2/1a~~~e~ba1/10;cEdEbEcEdEeGfGbGcGdGeGfGgGIebGcGdGeGfG
+12;Calm;10/10/10/2e7/2f~6/3~1~f~a1/1ha~~~h~e1/10;cEGfdGcGdGeGgGIfHfbGcGdGeGfGcEdGeGfGgG
+13;Candelia;10/10/10/7f2/6~e~1/2h~~~~1e1/1gf~g~~~h1/10;hEEgcFdFeGfGgGdGcGeGdGfGHeGfbGcGdGeG
+14;Updraft;10/1b8/1ga~~g~3/5bg~2/7~2/7~a1/7~g1/10;fCcCdCeCfCgDIfbCcCdCeCfCgDbCcCdCeC
+15;Auster;10/3c6/3e~~e3/5~4/5~4/5~~~g1/1bg~~~~bc1/10;dCeCGcIfHfcGdGeGbGcGdGeGfGdCeCfGgG
+16;Haboob;10/3~c5/3~3f~1/2f~4~1/1dc~2b~~1/3~~2~~1/2b~~~d~~1/10;EbcEcEbEcEdFeGhCdGeGfGgGgEcGdGeGfG
+17;Tropical Cyclone;10/3g~3f1/4~2~g1/4~2~2/4~2~2/4~2~2/1bfe~e~~b1/10;dGdBIceGfGIccGdGeGfGbGcGdGeGfGgG
+18;Downdraft;10/10/10/10/1a~3g3/1g~3a~e1/1e~h~~h~2/10;dGeGbEcGdGeGbFcGdGeGIfbGcGdGeGfG
+19;Cyclone;10/10/7~g1/7~2/2e~~f1~2/2g~~2~2/1fb~~~e~b1/10;cEdGeGcFdGFecGEfDfIceGfGdGeGfGgG
+20;Wuther;10/10/4g~4/1a~a1~4/2~b~~~~c1/1~~2~1~a1/1c2~~gb2/10;dEGgeEeCfEgEIeHeFgfGdEeGeEGeFefGEeDebDcECfdEIfFgHgeE
+21;Tebbad;10/10/10/10/1ba~3h2/3~~~~bg1/1g~~~~hab1/10;HfGfHfFgEgcEdFeGfGbEDgIgHgGgFgcEbGcGdGeGfGgG
+22;Lake Breeze;10/10/1c~7/1d~7/2~7/2~~~1~cg1/1dgdb~~b2/10;eGfGbCcFdFeGfGdGcGeGdGfGeGgGIfHfHgGgFgEgbD
+23;Capful Of Wind;10/2f7/1~a~6/1~1~1~a~e1/1b~~~~1~2/1f~~3~2/2~~be~~b1/10;EgDgFgEgcCCcDfbEIddGeGfGIgHgGgFgcEGdFeEe
+24;Notus;10/4~a4/4~5/4~5/4~2a2/2g~~~~gh1/2e~~~hea1/10;HfGfHfGfIfHfcFHgfFdGcGdGeGGgFgEgIgHgGgFb
+25;Blustery;10/10/10/1g~4~d1/1a~c3~c1/2~a~2~a1/2~d~g~~2/10;DfDfbDcEdFIddGeGfGbEIfHgGgFgcEIfcGdGeGfG
+26;Zephyr;10/10/7~e1/7~g1/2g3~~2/2f~2~3/2ge~d~df1/10;fGIcHedGeGIdcGdGeGcFfGdGgGeGHecGdGeGfGgG
+27;Breeze;10/4e~4/1~b1f~4/1~3~4/1~2f~4/1~~h1~4/1~~f~e~bh1/10;eBeCfGdGeEDgCgCcbFcGdGeGfGbGcGdGeGfGgG
+28;Hurricane;10/4~g1~e1/1a~e~2~2/1c~4~2/2~4~2/1a~4~2/1e~~c~~~g1/10;bCbFbDcGIbHgGgFgEgDcFbEcDccGdGeGfGgG
+29;Wind Chill;10/2c~6/3~6/2c~6/3h~~4/3fe~~~c1/4hf~~e1/10;dEeFcBdEcDFfdEfGgGIfHfeEdFeFfGeGfGgG
+30;Levanter;10/7~d1/2c4~2/2d4~2/2a~~~1~2/3~b~c~2/1b~~ad1~~1/10;gFIbHfbGcGcEcEdEeEcEdEeFeFdFfFgFdG
+31;Boreas;10/1e~1~be3/2~~~2h~1/2~~4~1/2~~4~1/2~~3~~1/1~b~geh~g1/10;FbEchCIfGbFbEgDgbBCgFgEcbGcGdGeGfGgG
+32;Roaring Forties;10/4h~4/5~4/5~1e2/1a2~~~a2/1h~~~1~f2/1ef~~h~3/10;eBbFcFFeHfcGdGeGHebGcGdGeGHfbGcGdGeG
+33;Squall;10/7~h1/6~~f1/4~~~3/3~e~4/3~2b~f1/1~c~bhec2/10;IbHceEGdFdCgEgEeDgdGFeEedGeGcGdGeGbGcGdGeGfGIcHcGdFeEeIfdGeGfG
+34;Twister;10/4b~4/5~4/5~4/1c~a~~4/2~gch~hg1/1~~a2~1b1/10;dEeEDfEfDfFfEfFfCgDfEfbEHfIfHfGfFfEfcFdFbGeBfFgFhFeFfF
+35;Typhoon;10/10/7a2/6~b2/1bf~~~~f2/3~~ad3/1d~~2fb2/10;FfGfbGcGEfcEFfdEeEHeeFdFHgfFHdGebEGfFfDgCgEfcEdFbGeFHe
+36;Friagem;10/4~a4/2~gc5/2~7/1~~~b~4/1~~~1~~bd1/1cd~~g~1a1/10;DcEcDcCeFbEcDcFgEgEecEdECgHfIfHfGgeEfFdGgFhFbGcGdGeG
+37;Simoom;10/8a1/1h~2e~~e1/2~3~~2/2~3~3/1b~~~~~~f1/1f~~ah1~b1/10;fCIcgFIfHfHdGfgFbCbFEgcFFgdFeFfFgFIcHdGfFfEfcGbGcGdG
+38;Dust Devil;10/10/10/2~g6/1c~3h~e1/1g~~~~f~c1/1e~~~~1hf1/10;DdbEcFIfgEhEdGcGeGGfHfdGFfGfIfHfIfbFGfeFcGfFgFbGcGdG
+39;Half A Gale;10/10/10/1c~g~~4/2~2~f~2/2d~~fa~~1/2a~d1c~g1/10;gEgFfFhFgFDdbDcDcGcFdFeFfFgGIfHfGfFfEfcGdDeDfFgFgG
+40;Sea Breeze;10/1~f7/1~2~df~2/1h~1~2~h1/2~1~1~~2/2~1~~~~d1/2h~cd~c2/10;IfcGfGeGdGFceFfFfGbDeGcGfGdGgGgCIdeGHeCbbDcGdGeGfG
+41;Williwaw;10/10/3~e5/3e1af~2/3c~2~2/4~~1b~1/2fb~~~ac1/10;EchFHgGgFgdEeFIggDhFfGfDgDeGfGdGeGfGgGcGdGeGfGgG
+42;Trade Winds;10/1d4d~2/1b~3g~2/2~4~2/2~4~2/1f~~3f2/1g~eb~~e2/10;DgbCcFcGdGeGfGbFcGdGeGfGgBbCcGdGeGfGgCbGcGdGeGfG
+43;Aquilo;10/10/7~b1/6f~2/4~b1~2/2e~~1~a~1/1~acfe~c~1/10;cFdFCgDgEgIcFehFHfeGgDHfdGeGcGdGeGfGbGcGdGeGfGgG
+44;Keen Blast;10/10/2d~6/1g1~6/1cf~6/1h1~~~~~~1/1cfhg~~1d1/10;cEeGdGbEbGcGbGfGeGdGcGcCcEdFeFdFfFdGgFhFbGcGdGeG
+45;Mariah;10/2a~6/3~6/1~e~6/1~1~h~4/1~~f1~~~2/1~~ecfcha1/10;eEfFgFcDcBDdCdbGDfEgcFCgDgdGcGdGeGbGcGdGeGfGgG
+46;Bise;10/3~d2~h1/3~3~2/3~~2~2/3e~2~2/2~b~~~d2/1eah~~~ab1/10;EbdDHfeGHgGgFgdFeFfGgGDfeGdGcGfGeGgGIbHfdGeGfG
+47;Stiff Breeze;10/3b~5/1b~1~5/1d~~~5/1e~g~~4/2g2~1~d1/1~eg~~e~2/10;bCdEdBEddGCgeGcGgGfGdGeEHgGgFgEgbEbEIfcGdGeGfG
+48;CAT;10/2~b6/2~4~h1/2~2~~~c1/2~2~1~2/1e~f~f~~2/1c~b~1~eh1/10;HgfFIggFIcIdHdHfDfbFDbcFGfFfdFcFeFfFcGGdFfbGcG
+49;Polar Night Jets;10/1b~7/2~7/2~7/2~2~h1g1/1g~~~~1~a1/2ch~ab~c1/10;GeFfbBFgEgcFdFeGIfdGeGfGcGdGeGfGgGbFcGdGeGfGgG
+50;Fresh Breeze;10/10/6~~f1/1a~3~h2/2~h~~~eg1/2~a~~~1e1/4~fg3/10;IcHcFgdEGgeFdFfFHeeFGeHeIeHefEgEhEFfGfeGbDcFdF
+51;Horse Latitudes;10/3~~d4/1h~~2d~2/2~~3~2/3a~2~2/1~f1f~1~2/1chca~~~2/10;dEeFbCcDCfdGdEcGfGeGbGcGdGbGcGdGeGFbEbgCdEeGfG
+52;Second Wind;10/1c~7/2~7/2~4~c1/2~1g~h~2/1a~~a~g~b1/1b~h1~1~2/10;gFIfIdHeGeGfeEHffEgEEfDfFfEfbBGfFfEfcFbFdFeFbG
+53;Foehn;10/4h~~b2/5~~3/5~~3/5~~d2/1~a~~~~eb1/1e1~h~~ad1/10;HbHeeGGfFfHfGfIfHfeBEgcFdFFfEfDfCfeGfGdGeGfGgG
+54;Anticyclone;10/10/10/10/1b3~f3/1f~1~~c~d1/1h~~d~bhc1/10;IfeGGeFfGfFfHfGfbFGgeFcGHgfFgFbFcGdGbGcGdGeG
+55;Simoon;10/10/10/6~e2/2~~e1~3/2fhb~~3/1dhd1~fb~1/10;EeDeGgeFdFcFhGfFeFdFfFeFcFdFeFGfgGFfHdEfDfbG
+56;Elephanta;10/6~a2/6~3/6~3/2c~2~3/1ha~2~d2/1fd~~~chf1/10;cEdGeGHbcFdGeGHfcGdGeGbFcGdGeGfGbGcGdGeGfGgG
+57;Hadley Cell;10/10/1c1g6/1g~b~5/2~1~5/1d~1~~~a2/1a~~~d1bc1/10;HfGfFfdDeFfFgFbDbFDdbDcDCfdDeFfFgFbGcGbGcGdG
+58;Favonius;10/1f~~3c2/3~2~a2/3~2~hf1/3~1~~3/3~~~~~e1/1e~~ha1~c1/10;bBbGcGcBdFeFfFHcGeHdGegFIfHfGfdGHdgFhFIdHdgF
+59;Willy-willy;10/10/5~~h2/5~1g~1/4~~2~1/2b~~ghf~1/2fa~ba1~1/10;HcGcFeFfEfGfFfFgdFcFGgeFdFHffFdGhDgFhFcGdGeG

+ 63 - 0
non_catalog_apps/vexed/assets/levels/08 Variety Pack.vxl

@@ -0,0 +1,63 @@
+# Author: Vexed Development Team
+# URL: http://vexed.sourceforge.net
+# Description: These levels provide a full spectrum of difficulties from easy to hard.
+0;Soda Cremosa;10/7~g1/7~2/5hb~2/4~d1~2/2~~~1~~2/1h~b~agad1/10;IbHffGFeFeEfDfGdFeeGfGgG
+1;Pineapple;10/4a~~1b1/2~b1ga~e1/2~2eb~2/2~1~b1~2/2~1~2~2/2~~~~~~g1/10;eBfBfCgCDcFecGIcIcgDFeeGfG
+2;Cereza;10/10/7~b1/6f~2/4~b1~2/2e~~1~a~1/1~acfe~c~1/10;cFdFCgDgEgIcFehFHfeGgDHfdGeGcGdGeGfGbGcGdGeGfGgG
+3;Orange;10/7~h1/1h~3~~2/2e~2~e2/3~~af3/3~~fa3/3~~h4/10;FgGfIbbCHcEgcDcDGfdEHdGfdG
+4;TABASCO Cinnamon;10/10/10/5a~~e1/4b1~~2/3~gad~2/2dg1b1~e1/10;EfIggFfFeFIdGfHfGfFfEffDGf
+5;Chicle;10/4b~4/5~4/5~4/1d1h1~4/1bhg~~4/2bd1~g~2/10;dFgGeFeBFffGEfbFbF
+6;Tutti Fruitti;10/3b~5/1b~1~5/1d~~~5/1e~g~~4/2g2~1~d1/1~eg~~e~2/10;bCdEdBEddGCgeGcGgGfGdGeEHgGgFgEgbEbEIfcGdGeGfG
+7;Peppermint;10/7~e1/2~b3~2/2~3e~2/2~4~2/2a~~c1~2/1cb~ha~h2/10;IbgDDcFfEfcFdFeFbGeGfG
+8;Chocolate Pudding;10/10/10/1h3d~3/1a~3h~2/2~~b~de2/1b~ea5/10;fDgEEfDfDgbEcFHfGfFfcGbEHfGfFfcG
+9;Manzana Verde;10/10/6~e2/6~3/6~1f1/1ca~~~~~e1/3fgc~ag1/10;HcIfHfcFdFeFfFbFcFdFeFeGfGgGdGeGfGgG
+10;Tangerine;10/2~d6/2~4bg1/1~~5e1/1~5~b1/1~4~~2/1~d3~eg1/10;DbCdIeIeHfIehCIe
+11;Lemon Drop;10/2b7/1~c7/1~a~3~h1/1~b~3~2/1~1~~1~a2/1c1g~~~hg1/10;CccDIdHfdFeGdGeGfGgG
+12;Blueberry;10/10/1~c7/1~8/1~~e6/1~hb~~~3/1ec1e~heb1/10;DeCedFcFCceGdFeFfFfGeGfGgG
+13;Cantaloupe;10/3~fb4/1~~~dg4/1~1~fh~~2/1~1~g2~2/1~2b2~2/1~~d2h~2/10;EbFbEbEcDcCcbGFcfDeDdDgDFddE
+14;Buttered Popcorn;10/4f~4/5~4/1~d2~4/1a3~4/1b~~~~~~2/1d~c~abfc1/10;eBfFgFCdDgbFcFbFcFdFdGeGcGdGeGfGgG
+15;Peach;10/1~f4~f1/1~a4~2/1~5~2/1h~~~2~h1/2~1~1~ec1/2~~~aec2/10;HfIfCbCcbEbEbEcEdEIfcGdGeGfGIbcGdGeGfG
+16;Passion Fruit;10/10/10/10/1b3~f3/1f~1~~c~d1/1h~~d~bhc1/10;IfeGGeFfGfFfHfGfbFGgeFcGHgfFgFbFcGdGbGcGdGeG
+17;Canela Picante;10/3~~d4/1h~~2d~2/2~~3~2/3a~2~2/1~f1f~1~2/1chca~~~2/10;dEeFbCcDCfdGdEcGfGeGbGcGdGbGcGdGeGFbEbgCdEeGfG
+18;Peanut Butter;10/3~a5/3~1~d1e1/3~1~f~f1/3~1d1~2/3~3~2/3~~e~a~1/10;GcgDIdIdfGhGEbdGeGfGgG
+19;Lemon;10/7~a1/2h~2f~2/3~~a1~c1/3~~2~2/3~~~h~2/3~c~1~f1/10;gCeGGfFfFdeFcCfFgFIdIbHfGf
+20;Orange Sherbet;10/10/10/6~~e1/3~~~~d2/1a~~ag~3/2a~de~g2/10;IdHgHdHeGeFegGfGcGEeDfbFcFcG
+21;Sandia;10/7a~1/6~h~1/3~e1~1~1/3~2b1~1/2eh~~a~~1/2bd~d1~~1/10;dGEdhBgFHcGfGfFfcGdG
+22;Pina Colada;10/10/5h1~g1/2~b~e1~2/1~~1~1a~2/1~2~~ba2/1~h1ge4/10;gEdDFdFdEdeFeFDdCeIcHfGf
+23;Ponche Tropical;10/10/6~g2/1~h3~3/1~4~~g1/1e~f~~d~2/1h~d~e1~f1/10;HcgEIeCdFgdFDgbFGfeFcFfFgFcGdG
+24;Anis;10/10/5~d3/5~4/2~g1~4/1c~d~a~~2/1dag~d1c2/10;bFDedFcFFgdFcFGceFcGfFgFbGcGdG
+25;Platano;10/4h~~~g1/5~~3/4~~~3/4~1~3/1~edh1~3/1~dbebg3/10;IbHbeBfGeGdFFdcFCgDgEfDfCfdGcGdGeG
+26;Pink Grapefruit;10/3h~5/4~5/4~1~b2/2~d~~~e2/1d~f~~~3/1fe1~~~hb1/10;DeDfCfHdGgFgHgGgIgHgHeGfFfdBeEEfDfeG
+27;Caramel Apple;10/2c~6/3~6/2c~6/3h~~4/3fe~~~c1/4hf~~e1/10;dEeFcBdEcDFfdEfGgGIfHfeEdFeFfGeGfGgG
+28;Spearmint;10/1g~7/2~5f1/2~5d1/2~~g2~b1/2c~c~~~2/3~db1~f1/10;EecFbBcFIgIeHfGfIeIeHfGf
+29;Watermelon;10/10/2~~a5/2~2c4/2~~1b~c2/2h~~h~d2/2a~d1~b2/10;EcDccEHeHffEgEfEFfEfGfFf
+30;Cinnamon;10/10/3a~b4/4~2f~1/4~3~1/1d~~~1~~h1/1b1~ah~df1/10;hDIfHfFcdCbFcFEfDfCfdGeGfG
+31;Cafe Latte;10/1f~1~a4/2~1~1e3/2~1~~h3/1h~a~b4/2~1~f~3/4~eb3/10;FbdEGdFfGdFdFefFEeeFDebBcEbEdE
+32;Top Banana;10/6g3/5~d3/5~4/5~~~2/2~~~d~eb1/1eb~2aga1/10;GcHfGfGcIfHffEgEFfGfgGcGEfDfFfEf
+33;Hawaiian Punch;10/10/10/1f~~~5/3e~~4/2ac~~~fg1/1~ca~g2e1/10;bDcDFgdDdECgbGHfGfIfHfGfeEEgfFdGgFhF
+34;A&W Root Beer;10/7~g1/1a~3~~2/2~e~1~3/2~c~d~a2/2~1~1~e2/2~1~~dgc1/10;fEIbdDeGfGHcHeGeGgbCFgdEFeEeDeeGfGeGfGgG
+35;Mandarina;10/10/2~g1b~~c1/2~3~~2/2~3~~h1/1b~~~~~~f1/1c1gh~f~2/10;IeIcHfIfeGGfHfdGFffGbFcFEfDfCfDccFdFfCdGeG
+36;Espresso;10/1b~~g5/2~~6/2~f~5/2~b~h4/2~1h5/3~g~f~h1/10;bBDeEbdDEgeGFgdEEgeGIgHgGgFe
+37;Red Apple;10/2~f6/2~3~a2/2~3~b2/2~g~g~f2/2~1~b~3/2~1~a~b2/10;dEFfHgHcHeGeFeEegGDbDeeGHe
+38;Juicy Pear;10/1e~5f1/2~4~d1/2~3~~2/2~2c~~2/2~1~afe2/2~~a1dc2/10;fEFfgEIcHdGffFHfGfIcbBFfcG
+39;Margarita;10/4d~4/5~1~h1/3b~~1~c1/4~~~~2/1~e1~~~d2/1ec~~h1b2/10;CfIcHeGfIdHeGfHfGfFgdDeFcGeBfFgF
+40;Green Apple;10/1hc~6/3~6/3~6/3~~~cb2/2~~~~bf2/1af~a~1h2/10;GeHeHfGfFfcGcBbBcBdEeEfFgFbGDfEfcGcGdG
+41;Raspberry;10/10/7a2/6~b2/1bf~~~~f2/3~~ad3/1d~~2fb2/10;FfGfbGcGEfcEFfdEeEHeeFdFHgfFHdGebEGfFfDgCgEfcEdFbGeFHe
+42;Island Punch;10/7~g1/3~d2~2/2g~2~h2/2d~2~3/3~~b~e2/3e1h~b2/10;HdEccEIbcEdFeFfFHfHdGfFfEffG
+43;Pera Jugosa;10/2ca~5/3g~2c2/4~1~d2/4~1~3/4~gh~2/1d~~~1ah2/10;gFFfdBeFfFcBdBdCeFfFHdHdGfFfbGcG
+44;Cappuccino;10/10/2~e6/2~c~5/2~1~2d2/1d~e~~ef2/1f~1~~ce2/10;DfbFDccFdFGfHfGfFfEfDfdDeFfFfGeGfG
+45;Champagne Punch;10/10/2e7/2c~6/3~3b2/1~~c~~~f2/1f1ba~ae2/10;dFeFHfGfFfEfDfCfcDdFeFeGHfdGeGcDdGeGfG
+46;Dr Pepper;10/6~f2/6~3/6~3/1c~3~3/1b~~~~~~d1/1dhfb~h1c1/10;bEcFbFdFcFGgeFdFIfHffFgFhFHbGfFfcGdGbGcGdGeG
+47;A&W Cream Soda;10/7~d1/2c4~2/2d4~2/2a~~~1~2/3~b~c~2/1b~~ad1~2/10;gFIbHfbGcGcEcEdEeEcEdEeFeFdFfFgFdG
+48;Coctel Honolulu;10/4c~4/2b~1~4/3~1~4/1f1~c~1~f1/1e~~b~1~2/1h~h1e~~2/10;eBEfDfbFcFdFeFbFcFcCdFDfbGIeeFfG
+49;French Vanilla;10/7~h1/7~f1/7~a1/6~~b1/3~e~~a2/1bhe1~~f2/10;EfIbIcHfHeGfHfGfFfEfIdIeHeGfHfFffGEfbG
+50;Grape Jelly;10/1c~7/2~7/2~4~c1/2~1g~h~2/1a~~a~g~b1/1b~h1~1~2/10;gFIfIdHeGeGfeEHffEgEEfDfFfEfbBGfFfEfcFbFdFeFbG
+51;Cotton Candy;10/10/10/2~g6/1c~3h~e1/1g~~~~f~c1/1e~~~~1hf1/10;DdbEcFIfgEhEdGcGeGGfHfdGFfGfIfHfIfbFGfeFcGfFgFbGcGdG
+52;Bubble Gum;10/2e~6/3~e~a3/5~h3/1~~~~~4/1~1c~~b~2/1~a1~chb2/10;gFeCcBdCGdeCFgGdFeFefFEeeFDeCedF
+53;Caramel Corn;10/3c6/2~g1~f3/2~2~4/2~2~4/2~~b~~c2/1~~b1fhgh1/10;HfGfGcDgCgEfFfEfDfDcDccFdFbGGgeFfFfGgG
+54;Sours;10/2~b6/2~4~h1/2~2~~~c1/2~2~1~2/1e~f~f~~2/1c~b~1~eh1/10;HgfFIggFIcIdHdHfDfbFDbcFGfFfdFcFeFfFcGGdFfbGcG
+55;Sizzling Cinnamon;10/4~a4/2~gc5/2~7/1~~~b~4/1~~~1~~bd1/1cd~~g~1a1/10;DcEcDcCeFbEcDcFgEgEecEdECgHfIfHfGgeEfFdGgFhFbGcGdGeG
+56;Frambuesa;10/7~h1/7~f1/2~d3~2/2~2~~f2/2~b~~ch2/1b~1c~d3/10;IbdFGgeFfFHebGDdcFdFeFGeGfFfEfDfIcHfGfeG
+57;Jalapeno;10/5~a3/3e~~4/1~d1g~4/1~1d1~4/1~gh~~~a2/1he1~~~3/10;GbeDCfDfFfdFcFbFeFdFcFfFeFdFCdbFcFdFFfEfDfCfdCeDFfEfDfHfGfeG
+58;Licorice;10/10/1c1g6/1g~b~5/2~1~5/1d~1~~~a2/1a~~~d1bc1/10;HfGfFfdDeFfFgFbDbFDdbDcDCfdDeFfFgFbGcGbGcGdG
+59;Menta;10/10/10/1c~g~~4/2~2~f~2/2d~~fa~~1/2a~d1c~g1/10;gEgFfFhFgFDdbDcDcGcFdFeFfFgGIfHfGfFfEfcGdDeDfFgFgG

+ 63 - 0
non_catalog_apps/vexed/assets/levels/09 Variety II Pack.vxl

@@ -0,0 +1,63 @@
+# Author: Vexed Development Team
+# URL: http://vexed.sourceforge.net
+# Description: Continuation of the Variety Pack. From easy to hard.
+0;Interchange;10/10/4e~e3/3bh~4/5~1~h1/4f~~~2/5cbcf1/10;eCGceDIefFdDeDfFfGeFfGgG
+1;Eighteens;10/3~d2~h1/3~3~2/3~~2~2/3e~2~2/2~b~~~d2/1eah~~~ab1/10;EbdDHfeGHgGgFgdFeFfGgGDfeGdGcGfGeGgGIbHfdGeGfG
+2;New York;10/7~h1/6~~f1/4~~~3/3~e~4/3~2b~f1/1~c~bhec2/10;IbHceEGdFdCgEgEeDgdGFeEedGeGcGdGeGbGcGdGeGfGIcHcGdFeEeIfdGeGfG
+3;Fifteen Rush;10/3f~~4/4ea~3/3~f1~3/3~1h~3/1h~~2~3/1e~~a~f~2/10;fCeGgGdBeBEdEdDfdGeGfCfEbGcGdGeG
+4;Ladies' Battle;10/10/6~g2/6~eh1/6~3/1f~~~e~~a1/1g2~1afh1/10;IfHfbFcFFfdFeFfFIgHgHcGfFfEfDfCfHdGfFfIdHd
+5;Nestor;10/3~~f4/3~ce4/2~~6/2~c2~f2/1~~a~1~3/1acef~~~2/10;DfCfFbEbEcDdeGfGdFgGeGHeFcEc
+6;TetSol;10/1~f7/1~2~df~2/1h~1~2~h1/2~1~1~~2/2~1~~~~d1/2h~cd~c2/10;IfcGfGeGdGFceFfFfGbDeGcGfGdGgGgCIdeGHeCbbDcGdGeGfG
+7;Bisley;10/5~e3/5~4/4g~4/5~4/3e~~~hc1/1b~c~~bgh1/10;GbdFHfIfGgHfGfFfeDfFgFgGbGcGdG
+8;Fourteen;10/1a~1c~~3/2~1fd~3/2~h2~3/2~3~3/2~~~~f3/2~d~ach2/10;eBfBGffCeCfCDdbBcFdFdGeGcGdGeGfG
+9;Acquaintance;10/5g~3/1d~3~~~1/2f~4~1/3~3~~1/3~~~ga~1/3af~bdb1/10;cDdFfBgChFhCIebCcDGgFgdFeFfGeGfGgGdGeGfGgG
+10;The Wish;10/6~~a1/6~3/1d4~3/1g~2~a3/2~h~~h3/2g1~~d3/10;bEGeDfbEcFdFGfFfEfDfIbHbGfeG
+11;Colorado;10/4h~4/5~4/5c4/5e4/1d~1~a~a~1/2~~ced1h1/10;FfHfEgGfeBfFdGgFhFbFcGdGeG
+12;Precedence;10/10/10/5e4/2~c~b4/1f~1~e~~2/2ef~1ceb1/10;fFgFdGdEFeEeDefFgFeFfFbFcG
+13;Red And Black;10/6df~1/7d~1/6~f~1/6~1~1/5gb~~1/1b~e~e1g2/10;hBhDgFfFdGhFgFHdgBhDgFIfHfIfGfbGcGdG
+14;Pharaohs;10/4dh~3/5a~~2/1c~3g~2/2~4~2/2~3~~~1/2~~hgdca1/10;fCgChFeBfBgCHfgDHffCgDHfeGbDcGdGeGfG
+15;Spidike;10/1e8/1f~7/2b7/1~c~6/1f1~3e2/1ba~~~~ca1/10;cEdGeGfGbCcGcECebGdGbCEgcEDgdGeGfGcGdGeGfGgG
+16;Royal Rendezvous;10/2a~6/3~6/1~e~6/1~1~h~4/1~~f1~~~2/1~~ecfcha1/10;eEfFgFcDcBDdCdbGDfEgcFCgDgdGcGdGeGbGcGdGeGfGgG
+17;Square;10/1g~7/1a~~6/2d~6/3~6/3d~c~g2/3a~1~c2/10;fFbBcCcDdGdFeFfFbCcD
+18;Fourteen Out;10/4~c4/4~5/4~1h~2/4g~1~2/3~h~~f2/3f1g~c2/10;eEFbgDeEeFfFHfGfHfFfEffG
+19;Chameleon;10/10/10/2~~ba~3/2h3~3/2b~~~d~2/3a~~1dh1/10;gFfDcFdFcFdFGfeFfFgFEdDdcFdFdG
+20;Cicely;10/10/2f7/2e~2~~a1/1g1~~1~3/1f~~~e~3/2~gafa~2/10;DgbFEgcFbFcDcDDgdEdGIdcGdGgGeGHd
+21;Wave Motion;10/1g8/1a~7/1b~7/2~1c5/1b~~e~~3/1c~a1ge3/10;bCeFfFbDbDEfcFbFdFeFbG
+22;Interregnum;10/5d4/1c~2b~g2/2~3~c~1/2~3~1~1/2~2~~1d1/1e~e~g~b2/10;fCbGfGHdHdfCgDhDGfbCcGdG
+23;Fifteen Puzzle;10/10/3~a5/3~3d2/3~3a~1/3~~e~eg1/1b~~gb1d2/10;fFEchEHfGfFfEfIfHfIfGfFfbGcGdG
+24;Czarina;10/1g~~6/3~6/3~6/3~~c1~e1/2~~ga~~b1/1ce~1b~a2/10;bBcBEfFecGEfDfIefGHfGfFfEfIfHffG
+25;Harp;10/10/3e~5/4~1~h2/1h2~a~3/1g~~~1e~2/1a~fgf1~2/10;DgbFcFFgFedCeEcGbGcGgFfEHdGegFFebGcG
+26;Nines;10/2~f1~bh2/2~2~4/2~2~4/2~1~h~~g1/1~e~~g~3/1f1~~1e~b1/10;DbFfEgcFCfFfdFeFfFGbHbGbFeIefFHeGfFfgG
+27;Push-Pin;10/10/10/5~g3/4h~4/1a~eg~e~~1/2~3h~a1/10;eFIggFfFeFhFgFfFdFeFfFHfGfFfEfDfbFcFdFeFfFgFgGGdFfEfDf
+28;Frog Gaps;10/10/4h~g3/3~gbd~2/2a~b2~2/3~3~2/2~ha2d2/10;gDcEDgeCEdFdFdEedGGdFdEe
+29;Thieves Of Egypt;10/1g~3~h2/2~2~~3/2~1~~a3/1~g~~e4/1~1h~f~3/1~e1~af3/10;bBHbGcFdGdFdFefFEeeFDeCedF
+30;Heads And Tails;10/10/10/6~a2/5~~h2/2ea~c~3/1gcge1~h2/10;dFHgfFHdGeHeGfcFdFdGFfEfbGcG
+31;Senate;10/4~f4/2f~~5/3~~e~~b1/3~~1~~2/3b~~g~d1/2dg1~e~2/10;FbcCdEdFeFGfFfEfIdIfHffDGfFfEffG
+32;Double Fourteens;10/3f~~a3/4~~c3/4~~4/4~~4/2b~~~~b~1/1gag1~c1f1/10;dBHfGfeFfFgFhFcFdFeFGgGbGcFfEfDfbG
+33;King's Way;10/10/4~f4/4~5/3~e1~e2/3~adh3/2~hd1f~a1/10;EeDgEfFfGfFfFceFfFdFeFfFHeGfgGFfEfcG
+34;Gargantua;10/1e~7/2~c~5/4~5/3g~5/3c~~~~h1/2ebhb~1g1/10;IffGHfGfbBdCcCdCdEGgeFfFeFdFfFdGgFhFcGdGeG
+35;Thirty-Six;10/8f1/8a1/6g~g1/7~2/3~c2a~1/2c~2~gf1/10;EfIdIdHghFIdgD
+36;Letter H;10/1b~1~h4/2~1~5/2~~~5/3~~~~~e1/2~ab1~~h1/2a1eb~~2/10;fGeGgGIeHfbBFbcDdEDfeEeGfEIfeGfG
+37;Solid Square;10/4~c4/4~2~g1/4~2~a1/4~h~a2/2h~~e~3/2gce1~3/10;FfFbcFFfIcHeIdHeHeGfFfcG
+38;Lucas;10/7~f1/7~2/4h2~2/3~e2~2/2~~cd~~d1/1fc1h1~~e1/10;EfEefFeFfFgGIbHfIfGfgGFfdEDfEfDf
+39;Strategy Plus;10/1f~7/2~7/2~4~e1/2~d~h1~2/2~gef~~d1/2~1hg~~2/10;FeIfHgfFeFdFbBdFGfFfEfDfgGIdHfeGfG
+40;Twenty;10/10/5~~h2/5~1g~1/4~~2~1/2b~~ghf~1/2fa~ba1~1/10;HcGcFeFfEfGfFfFgdFcFGgeFdFHffFdGhDgFhFcGdGeG
+41;Betsy Ross;10/6~d2/6~a2/6~3/2~~~~~3/2~he~4/2a1he~d2/10;HbHcGeGeFeEeDefGeFdFFgeGfG
+42;Unlimited;10/2~h6/2~d6/2~h6/2d7/1~a~~~c~2/1cg~g2~a1/10;CfcGDbcFbFDccFGfFfdFDdeFfFgFbGcG
+43;House In The Wood;10/5cd~2/6b~2/2e4~2/1~g3~~2/1~d~~~~~2/1gce1~1~b1/10;CecFdFcFeFIggBfBgBgCHeGfHfFfEfGf
+44;Toni;10/3b~5/4~5/2h1~5/1~g~~1~g2/1feh~~~e2/3b~~1f2/10;dBCecEHecFbFcFGfHfGfFgdFeFfFgFbFcFdFdG
+45;Somerset;10/1f~~3c2/3~2~a2/3~2~hf1/3~1~~3/3~~~~~e1/1e~~ha1~c1/10;bBbGcGcBdFeFfFHcGeHdGegFIfHfGfdGHdgFhFIdHdgF
+46;Kings And Queens;10/10/10/10/3b3c2/2~h~~~hd1/1c~1dhb3/10;dFHfeFdFHfeGGfFfEfDfIfHfGfeG
+47;Triple Line;10/10/4g~4/1a~a1~4/2~b~~~~c1/1~~2~1~a1/1c2~~gb2/10;dEGgeEeCfEgEIeHeFgfGdEeGeEGeFefGEeDebDcECfdEIfFgHgeE
+48;Chessboard;10/10/1b8/1a~1d1~f2/2~~c~~b2/2~1ac~3/1~~df1~3/10;eEEeDeEgDgbDbDcEdEeFCgcGfFHdHeGfFfbGcG
+49;American Toad;10/10/10/6~e2/2~~e1~3/2fhb~~3/1dhd1~fb~1/10;EeDeGgeFdFcFhGfFeFdFfFeFcFdFeFGfgGFfHdEfDfbG
+50;Busy Aces;10/4h~~b2/5~~3/5~~3/5~~d2/1~a~~~~eb1/1e1~h~~ad1/10;HbHeeGGfFfHfGfIfHfeBEgcFdFFfEfDfCfeGfGdGeGfGgG
+51;Fourteen Up;10/3~a~g3/2g~1~4/3~1~~~h1/3c~~a3/4~~gh2/3~~~1c2/10;IdHdGeGfEbFgdEdEeFfFgFcCFgEgGbdE
+52;Baker's Game;10/5~d3/5~g~2/5~1~2/5~1~a1/2~~f~~~e1/1efa1g~d2/10;EfDfGbIefGHfGfFfEfIfgCHfGfHfFffGEfbG
+53;Lucie;10/5g4/4~c1~f1/1d~~~2~2/2~~~e~~2/1~~~g1~~2/1c~~e1fd2/10;IcHfFcFcEdEeFeEebGDebDcEdEeEfECfDfEfbGcG
+54;Right And Left;10/10/2d~6/1g1~6/1cf~6/1h1~~~~~~1/1cfhg~~1d1/10;cEeGdGbEbGcGbGfGeGdGcGcCcEdFeFdFfFdGgFhFbGcGdGeG
+55;Royal Family;10/8e1/7~a1/2h~3~2/3~2~~2/2~~g~~h2/1ge1a~~3/10;IcHeHfGfeFfFgFIcHeGfFfEfDfHfGfFfEfDfcDdFeFeG
+56;Redheads;10/8a1/1h~2e~~e1/2~3~~2/2~3~3/1b~~~~~~f1/1f~~ah1~b1/10;fCIcgFIfHfHdGfgFbCbFEgcFFgdFeFfFgFIcHdGfFfEfcGbGcGdG
+57;Spanish Patience;10/1h~1~d4/1b~1~5/2~~~~f3/2~~~~d3/2c~~~f~2/2h~~~1bc1/10;bBbCGdGeFgEgcDFgdFcFGfeFfFgFdFFbeFfFgFdG
+58;Deuces And Queens;10/7~e1/2~d3~2/2~4~2/1h~3~~2/1g~e~~~a2/2~d1agh2/10;DgbFcFdFeFfFbFDccFIbdFeFfGHeGfHfFfEffG
+59;Four Leaf Clovers;10/10/6~~f1/1a~3~h2/2~h~~~eg1/2~a~~~1e1/4~fg3/10;IcHcFgdEGgeFdFfFHeeFGeHeIeHefEgEhEFfGfeGbDcFdF

+ 40 - 0
non_catalog_apps/vexed/common.h

@@ -0,0 +1,40 @@
+#pragma once
+
+#include <furi.h>
+
+#define TAG "GameVexed"
+
+#define SIZE_X 10
+#define SIZE_Y 8
+#define TILE_SIZE 8
+
+#define SIZE_X_BG 17 // 128/8 +1
+#define SIZE_Y_BG 9 // 64/8  +1
+
+#define WALL_TILE 9
+#define EMPTY_TILE 0
+
+#define MENU_PAUSED_COUNT 6
+#define MAIN_MENU_COUNT 3
+
+#define PAR_LABEL_SIZE 10
+
+// -- move -----------------
+
+#define MOVABLE_NOT 0
+#define MOVABLE_LEFT 1
+#define MOVABLE_RIGHT 2
+#define MOVABLE_BOTH 3
+
+#define MOVABLE_NOT_FOUND 128
+
+// -------------------------
+
+typedef uint8_t PlayGround[SIZE_Y][SIZE_X];
+typedef uint8_t BackGround[SIZE_Y_BG][SIZE_X_BG];
+
+typedef struct {
+    uint8_t ofBrick[WALL_TILE];
+    FuriString* bricksNonZero;
+    uint8_t statsNonZero[WALL_TILE + 1];
+} Stats;

BIN=BIN
non_catalog_apps/vexed/docs/img/custom_levels_dir.png


BIN=BIN
non_catalog_apps/vexed/docs/img/custom_second_btn.png


BIN=BIN
non_catalog_apps/vexed/docs/img/direction.gif


BIN=BIN
non_catalog_apps/vexed/docs/img/explosion.gif


BIN=BIN
non_catalog_apps/vexed/docs/img/levelset_info.png


BIN=BIN
non_catalog_apps/vexed/docs/img/playtru.gif


BIN=BIN
non_catalog_apps/vexed/docs/img/selection.gif


BIN=BIN
non_catalog_apps/vexed/docs/img/space_docs.png


+ 143 - 0
non_catalog_apps/vexed/docs/level_format.md

@@ -0,0 +1,143 @@
+# Custom Levels
+
+This game supports loading custom levels. 
+Put them into: `[SDCARD]/apps_data/game_vexed/extra_levels` - you may upload them with [qFlipper](https://flipperzero.one/update)
+
+![Where to upload files](img/custom_levels_dir.png)
+
+Levels should be in Custom VXL format (CSV alike, derived from Vexed *.ini)
+
+## Check before uploading
+
+Be warned that app have very **simple** parser, some bugs in format will be noticed and app will refuse to load level and create file with error info - but other wont, and app will not work properly, crash itself or Flipper - test and check folowing:
+
+* althou it seems like plaintext - treat it as binary! - DO NOT add extra spaces, newlines or comments, stick to the format
+* do not use special characters in file name
+* start file name from two digits (to order file on list)
+* file name is level set name displayed in UI - keep it short!
+* keep max. 100 levels per set
+* make sure levels are numbered from 0 and there are no "holes" in level numbering
+
+## VXL file format
+
+Each file have following structure:
+
+```
+# metadata
+# metadata
+# metadata
+leveldata
+leveldata
+leveldata
+```
+
+Each line ends with newline character (at least `\n`, but `\r\n` will work too).
+
+**Remember to add new line after last level!**
+
+## Metadata
+
+```
+# Id: Value
+```
+
+Supported IDs: `Author`, `URL`, `Description`
+
+_For example:_
+
+```
+# Author: author name (short!)
+# URL: author URL (short!)
+# Description: (short!)
+```
+
+* Keep single space between `#`, `Id:` and `Value`
+* Keep case the same, do not add spce before `#`
+* Keep values short and check in app if they fit by center-click on levelset button of custom menu:
+
+![Selecting level set](img/custom_second_btn.png)
+![Level set info](img/levelset_info.png)
+
+## Levels
+
+Level have fields separated by semicolon `;`
+
+```
+number;title;board;solution
+```
+
+* `number` should start from `0`, each level shoud have number, they should be continous in set (eg.: 0, 1, 2, 3...)
+* `title` as short as possible, no special characters
+* `board` and `solution` have special format, inherided from Palm.OS Vexed 2.2
+
+_For example:_
+
+```CSV
+0;Coffee Truffle;10/10/10/6a~2/7~2/5fb~2/3e~efab1/10;dGgDgG
+1;Kahlua;10/10/10/5~d~a1/1h~1~~1~2/2~1~2~2/1h~d~d1a2/10;IdbEGdFe
+```
+
+### Boad data
+
+Board have **10 x 8** logical size.
+
+Board data is string defining each line of board, separated by `/`
+Each line contains following characters:
+* `number` - if it is wall, value tels how many blocks of wall to draw, `1` to `10`
+* `character` - `a` to `h` representing each type of block
+* `~` tilde character - representing empty space
+
+| `1`..`10` | `~` | a | b | c | d | e | f | g | h |
+|:---------:|:---:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
+| ![wall](../images/w.png) | ![space](img/space_docs.png) | ![block a](../images/a.png) |  ![block b](../images/b.png) | ![block c](../images/c.png) | ![block d](../images/d.png) | ![block e](../images/e.png) | ![block fa](../images/f.png) | ![block g](../images/g.png) | ![block a](../images/h.png) |
+
+
+_In our example, for first level:_
+```
+10/10/10/6a~2/7~2/5fb~2/3e~efab1/10
+```
+
+means we have:
+* first three lines are walls, `10`` characters each
+* fourth line starts by `6` walls, followed by block type `a`, one empty space and remaining `2` walls
+* line #5 starts with `7` walls, followed by empty space and remaining `2` walls
+* line #6 starts with `5` walls, then bricks type `f` and `b` and remaining `2` walls
+* line #7 starts with `3` walls, bricks  `e`, space,  bricks `e`,`f`,`a`,`b` and single wall
+* last line is all walls
+
+### Solution data
+
+Solution string contains `XY` logical coordinates of block to move at each step of solution. Solution records only position and direction, as falling, gravity and explosions are deterministic and can be calculated for each step.
+
+Solution string length also determines what is **par** (reference solution length) - wy dividing this string length by `2` we have moves count in solution == par.
+
+Coordinates are calculated from **top-left** corner, starting from `0`.
+
+String encodes coordinates as two letters:
+* first letter for `X` coordinate, where `a` or `A` is 0, `b` or `B` is 1 ... and `j` or `J` is 9
+* first letter for `Y` coordinate, where `a` or `A` is 0, `b` or `B` is 1 ... and `h` or `H` is 7
+* if `X` coordinate letter is UPPERCASE `A..J` - it means in this step block is moved to the **left**
+* if `Y` coordinate letter is UPPERCASE `A..H` - it means in this step block is moved to the **right**
+
+_(it also means, that each pair have exactly and only one character uppercased)_
+
+|       | a | b | c | d | e | f | g | h | i | j |
+|-------|---|---|---|---|---|---|---|---|---|---|
+| **x** | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+| **y** | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+
+| case   | direction |
+|:------:|------|
+| **Xy** | left |
+| **xY** | right |
+
+_In our example, for **second** level:_
+```
+IdbEGdFe
+```
+
+means we have:
+* step 1: `Id` = block at x=8, y=3 moved to left
+* step 2: `bE` = block at x=1, y=4 moved to right
+* step 3: `Gd` = block at x=6, y=3 moved to left
+* step 4: `Fe` = block at x=5, y=4 moved to left

+ 1014 - 0
non_catalog_apps/vexed/draw.c

@@ -0,0 +1,1014 @@
+#include "draw.h"
+
+#include <gui/gui.h>
+#include <gui/icon.h>
+#include <gui/elements.h>
+#include <gui/icon_i.h>
+#include "fonts.h"
+#include "game_vexed_icons.h"
+#include "ui.h"
+#include "move.h"
+#include "utils.h"
+
+//-----------------------------------------------------------------------------
+
+u_int32_t frameNo = 0;
+int r1 = 0, r2 = 0;
+
+//-----------------------------------------------------------------------------
+
+void draw_app(Canvas* canvas, Game* game) {
+    canvas_clear(canvas);
+
+    if((game->state == MAIN_MENU) || (game->state == RESET_PROMPT)) {
+        draw_main_menu(canvas, game);
+    }
+
+    if(game->state == ABOUT) {
+        draw_about(canvas, game, frameNo);
+    }
+
+    if(game->state == INTRO) {
+        draw_intro(canvas, game, frameNo);
+    }
+
+    if(game->state == RESET_PROMPT) {
+        draw_reset_prompt(canvas, game);
+    }
+
+    if(game->state == INVALID_PROMPT) {
+        draw_invalid_prompt(canvas, game);
+    }
+
+    if(game->state >= SELECT_BRICK) {
+        draw_playground(canvas, game);
+
+        switch(game->state) {
+        case SELECT_BRICK:
+            draw_movable(canvas, game, frameNo);
+            break;
+        case SOLUTION_SELECT:
+            draw_direction_solution(canvas, game, frameNo);
+            break;
+        case SELECT_DIRECTION:
+            draw_direction(canvas, game, frameNo);
+            break;
+        case MOVE_SIDES:
+            draw_ani_sides(canvas, game);
+            break;
+        case MOVE_GRAVITY:
+            draw_ani_gravity(canvas, game);
+            break;
+        case EXPLODE:
+            draw_ani_explode(canvas, game);
+            break;
+        default:
+            break;
+        }
+
+        draw_scores(canvas, game, frameNo);
+        draw_playfield_hint(canvas, game);
+
+        switch(game->state) {
+        case PAUSED:
+            draw_paused(canvas, game);
+            break;
+        case HISTOGRAM:
+            draw_histogram(canvas, game->stats);
+            break;
+        case SOLUTION_PROMPT:
+            draw_solution_prompt(canvas, game);
+            break;
+        case GAME_OVER:
+            draw_game_over(canvas, game->gameOverReason);
+            break;
+        case LEVEL_FINISHED:
+            draw_level_finished(canvas, game);
+            break;
+        default:
+            break;
+        }
+    }
+
+    frameNo++;
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_intro(Canvas* canvas, Game* game, uint32_t frameNo) {
+    if(frameNo % 2 == 1) {
+        if(game->move.frameNo < 100) game->move.frameNo++;
+    }
+
+    canvas_set_color(canvas, ColorBlack);
+    if((game->move.frameNo < 12)) {
+        uint8_t x, y;
+        for(y = 0; y < SIZE_Y_BG; y++) {
+            for(x = 0; x < SIZE_X_BG; x++) {
+                canvas_draw_icon(
+                    canvas, x * TILE_SIZE, y * TILE_SIZE, tile_to_icon(game->bg[y][x], false));
+            }
+        }
+    }
+
+    if((game->move.frameNo < 4)) {
+        gray_canvas(canvas);
+    }
+
+    if(game->move.frameNo > 7) {
+        canvas_set_color(canvas, ColorXOR);
+        canvas_draw_box(canvas, 0, 0, GUI_DISPLAY_WIDTH, GUI_DISPLAY_HEIGHT);
+        canvas_draw_icon(canvas, 0, 0, &I_logo_vexed_big);
+    }
+
+    if(game->move.frameNo > 11) {
+        canvas_set_color(canvas, ColorBlack);
+        canvas_draw_icon(canvas, 0, 0, &I_logo_vexed_big);
+    }
+
+    if(game->move.frameNo == 24) {
+        game->state = MAIN_MENU;
+    }
+}
+
+void draw_about(Canvas* canvas, Game* game, uint32_t frameNo) {
+    if(frameNo % 10 == 9) {
+        randomize_bg(&game->bg);
+    }
+
+    if(frameNo % 50 == 49) {
+        r1 = rand();
+        r2 = rand();
+        randomize_bg(&game->bg);
+    }
+
+    uint8_t sx, sy;
+    for(sy = 0; sy < SIZE_Y_BG; sy++) {
+        for(sx = 0; sx < SIZE_X_BG; sx++) {
+            canvas_draw_icon(
+                canvas,
+                (sx * TILE_SIZE) - (r1 % 7),
+                sy * TILE_SIZE - (r2 % 7),
+                tile_to_icon(game->bg[sy][sx], false));
+        }
+    }
+
+    //gray_canvas(canvas);
+
+    // back for os buttons
+    canvas_set_color(canvas, ColorWhite);
+    canvas_draw_rbox(canvas, 44, 50, GUI_DISPLAY_WIDTH, GUI_DISPLAY_HEIGHT, 3);
+
+    const uint8_t y = dialog_frame(canvas, 92, 43, false, true, "Exit game?");
+
+    canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r6_tr);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_str_aligned(
+        canvas, GUI_DISPLAY_WIDTH / 2, y + 2, AlignCenter, AlignTop, "(c) 2024 Dominik Dzienia");
+
+    canvas_draw_str_aligned(
+        canvas, GUI_DISPLAY_WIDTH / 2, y + 13, AlignCenter, AlignTop, "based on Vexed 2.2");
+
+    canvas_draw_str_aligned(
+        canvas, GUI_DISPLAY_WIDTH / 2, y + 21, AlignCenter, AlignTop, "(c) 2006 Vexed Dev Team");
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontSecondary);
+    elements_button_center(canvas, "Exit");
+    elements_button_right_back(canvas, "Back");
+}
+
+void draw_set_info(Canvas* canvas, Game* game) {
+    BoundingBox box;
+    const uint8_t w = 118;
+    const uint8_t h = 46;
+    const uint8_t x = (GUI_DISPLAY_WIDTH - w) / 2;
+
+    const uint8_t y =
+        dialog_frame(canvas, w, h, false, false, furi_string_get_cstr(game->levelSet->title));
+
+    canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r6_tr);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_str_aligned(
+        canvas,
+        GUI_DISPLAY_WIDTH / 2,
+        y,
+        AlignCenter,
+        AlignTop,
+        furi_string_get_cstr(game->levelSet->author));
+
+    canvas_draw_str_aligned(
+        canvas,
+        GUI_DISPLAY_WIDTH / 2,
+        y + 8,
+        AlignCenter,
+        AlignTop,
+        furi_string_get_cstr(game->levelSet->url));
+
+    canvas_draw_hline_dotted(canvas, x, y + 16, w);
+
+    set_bounding_box(&box, x + 3, y + 16, w - 6, 16);
+    elements_multiline_text_aligned_limited(
+        canvas,
+        &box,
+        box.width / 2,
+        box.height / 2,
+        2,
+        AlignCenter,
+        AlignCenter,
+        furi_string_get_cstr(game->levelSet->description));
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_level_info(Canvas* canvas, Game* game) {
+    int bufSize = 80;
+    char buf[bufSize];
+
+    memset(buf, 0, bufSize);
+    snprintf(
+        buf,
+        sizeof(buf),
+        "%s #%u",
+        furi_string_get_cstr(game->levelSet->title),
+        game->selectedLevel + 1);
+
+    const uint8_t x = (GUI_DISPLAY_WIDTH - 100) / 2;
+    const uint8_t y = dialog_frame(canvas, 100, 40, false, false, buf);
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_vline_dotted(canvas, GUI_DISPLAY_CENTER_X, y, 30);
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
+    canvas_draw_str_aligned(canvas, x + 25, y + 4, AlignCenter, AlignTop, "Moves/Par");
+    memset(buf, 0, bufSize);
+    if(game->levelSet->scores[game->selectedLevel].moves == 0) {
+        snprintf(
+            buf,
+            sizeof(buf),
+            "??? / %u",
+
+            game->levelSet->pars[game->selectedLevel]);
+    } else {
+        snprintf(
+            buf,
+            sizeof(buf),
+            "%u / %u",
+            game->levelSet->scores[game->selectedLevel].moves,
+            game->levelSet->pars[game->selectedLevel]);
+    }
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, x + 25, y + 22, AlignCenter, AlignBottom, buf);
+
+    canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
+    canvas_draw_str_aligned(canvas, x + 75, y + 4, AlignCenter, AlignTop, "Score");
+    memset(buf, 0, bufSize);
+    if(game->score == 0) {
+        snprintf(buf, sizeof(buf), "on par");
+    } else {
+        snprintf(buf, sizeof(buf), "%+d", game->score);
+    }
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, x + 75, y + 22, AlignCenter, AlignBottom, buf);
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_main_menu_new_game(Canvas* canvas, Game* game) {
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontSecondary);
+    elements_button_center(canvas, "Start");
+    if(game->hasContinue) {
+        canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
+        elements_button_right(canvas, "Continue");
+        canvas_draw_str_aligned(
+            canvas,
+            GUI_DISPLAY_CENTER_X,
+            37,
+            AlignCenter,
+            AlignTop,
+            "!!! Forgets all progress and scores !!!");
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_main_menu_continue(Canvas* canvas, Game* game) {
+    int bufSize = 80;
+    char buf[bufSize];
+    int scorebufSize = 10;
+    char scorebufSet[scorebufSize];
+    bool hasNext = (game->continueLevel + 1) < game->levelSet->maxLevel;
+    memset(scorebufSet, 0, scorebufSize);
+
+    if(game->score == 0) {
+        snprintf(scorebufSet, sizeof(scorebufSet), "par");
+    } else {
+        snprintf(scorebufSet, sizeof(scorebufSet), "%+d", game->score);
+    }
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontSecondary);
+    elements_button_left(canvas, "New");
+    elements_button_center(canvas, "Start");
+    canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
+    elements_button_right(canvas, "Custom");
+
+    memset(buf, 0, bufSize);
+    if(hasNext) {
+        snprintf(
+            buf,
+            sizeof(buf),
+            "%s (%s), #%d",
+            furi_string_get_cstr(game->continueSet),
+            scorebufSet,
+            game->continueLevel + 2);
+    } else {
+        snprintf(buf, sizeof(buf), "%s finished!", furi_string_get_cstr(game->continueSet));
+    }
+
+    canvas_draw_str_aligned(canvas, GUI_DISPLAY_CENTER_X, 37, AlignCenter, AlignTop, buf);
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_main_menu_custom(Canvas* canvas, Game* game) {
+    int bufSize = 80;
+    char buf[bufSize];
+
+    int scorebufSize = 10;
+    char scorebuf[scorebufSize];
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
+    main_menu_pill(
+        canvas,
+        35,
+        90,
+        game->mainMenuBtn == LEVELSET_BTN,
+        game->setPos > 0,
+        game->setPos < game->setCount - 1,
+        furi_string_get_cstr(game->selectedSet));
+
+    memset(scorebuf, 0, scorebufSize);
+    score_for_level(game, game->selectedLevel, scorebuf, scorebufSize);
+
+    canvas_set_font(canvas, FontSecondary);
+    memset(buf, 0, bufSize);
+    snprintf(
+        buf,
+        sizeof(buf),
+        "%u of %u (%s)",
+        game->selectedLevel + 1,
+        game->levelSet->maxLevel,
+        scorebuf);
+    main_menu_pill(
+        canvas,
+        50,
+        90,
+        game->mainMenuBtn == LEVELNO_BTN,
+        game->selectedLevel > 0,
+        game->selectedLevel < game->levelSet->maxLevel - 1,
+        buf);
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_main_menu(Canvas* canvas, Game* game) {
+    canvas_set_color(canvas, ColorBlack);
+
+    canvas_draw_line(canvas, 0, 6, GUI_DISPLAY_WIDTH, 6);
+    canvas_draw_line(canvas, 0, 9, GUI_DISPLAY_WIDTH, 9);
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_rbox(canvas, GUI_DISPLAY_CENTER_X - 16 - 6, 1, 32 + 12, 14, 3);
+    canvas_set_color(canvas, ColorWhite);
+    canvas_draw_icon(canvas, GUI_DISPLAY_CENTER_X - 16, 2, &I_logo_vexed_mini);
+
+    canvas_set_font(canvas, FontSecondary);
+    main_menu_pill(
+        canvas,
+        20,
+        90,
+        game->mainMenuBtn == MODE_BTN,
+        game->mainMenuMode != NEW_GAME,
+        game->mainMenuMode != CUSTOM,
+        game_mode_label(game->mainMenuMode));
+
+    switch(game->mainMenuMode) {
+    case CONTINUE:
+        draw_main_menu_continue(canvas, game);
+        break;
+    case CUSTOM:
+        draw_main_menu_custom(canvas, game);
+        break;
+    case NEW_GAME:
+    default:
+        draw_main_menu_new_game(canvas, game);
+        break;
+    }
+
+    if(game->mainMenuInfo) {
+        gray_canvas(canvas);
+
+        if(game->mainMenuBtn == LEVELSET_BTN) {
+            draw_set_info(canvas, game);
+        } else if(game->mainMenuBtn == LEVELNO_BTN) {
+            draw_level_info(canvas, game);
+        }
+
+        canvas_set_color(canvas, ColorBlack);
+        canvas_set_font(canvas, FontSecondary);
+        elements_button_center(canvas, "Start");
+        elements_button_right_back(canvas, "Back");
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_playground(Canvas* canvas, Game* game) {
+    Neighbors tiles;
+    uint8_t tile, x, y, sx, sy, ex, ey;
+
+    bool whiteB = (game->state == LEVEL_FINISHED) || (game->solutionMode);
+
+    for(y = 0; y < SIZE_Y; y++) {
+        for(x = 0; x < SIZE_X; x++) {
+            tile = game->board[y][x];
+
+            sx = x * TILE_SIZE;
+            sy = y * TILE_SIZE;
+            ex = ((x + 1) * TILE_SIZE) - 1;
+            ey = ((y + 1) * TILE_SIZE) - 1;
+
+            if(tile > 0) {
+                if((game->state == MOVE_SIDES) && (x == game->move.x) && (y == game->move.y))
+                    continue;
+                if(((game->state == MOVE_GRAVITY) || (game->state == EXPLODE)) &&
+                   (game->toAnimate[y][x] == 1))
+                    continue;
+
+                canvas_set_color(canvas, ColorBlack);
+                canvas_draw_icon(canvas, sx, sy, tile_to_icon(tile, game->state == GAME_OVER));
+            }
+            if(tile == WALL_TILE) {
+                tiles = find_neighbors(&game->board, x, y);
+
+                // UP
+                if(tiles.u != WALL_TILE) {
+                    canvas_set_color(canvas, ColorBlack);
+                    canvas_draw_line(canvas, sx, sy + 1, ex, sy + 1);
+
+                    canvas_set_color(canvas, ColorWhite);
+                    canvas_draw_line(canvas, sx, sy, ex, sy);
+                    if(whiteB) canvas_draw_line(canvas, sx, sy + 2, ex, sy + 2);
+                }
+
+                // DOWN
+                if(tiles.d != WALL_TILE) {
+                    canvas_set_color(canvas, ColorBlack);
+                    canvas_draw_line(canvas, sx, ey, ex, ey);
+                    canvas_set_color(canvas, ColorWhite);
+                    if(whiteB) canvas_draw_line(canvas, sx, ey - 1, ex, ey - 1);
+                }
+
+                // LEFT
+                if(tiles.l != WALL_TILE) {
+                    canvas_set_color(canvas, ColorBlack);
+                    canvas_draw_line(
+                        canvas, sx + 1, sy + ((tiles.u != WALL_TILE) ? 1 : 0), sx + 1, ey);
+
+                    canvas_set_color(canvas, ColorWhite);
+                    canvas_draw_line(canvas, sx, sy, sx, ey);
+                    if(whiteB)
+                        canvas_draw_line(
+                            canvas,
+                            sx + 2,
+                            sy + ((tiles.u != WALL_TILE) ? 2 : 0),
+                            sx + 2,
+                            ey - ((tiles.d != WALL_TILE) ? 2 : 0));
+                }
+
+                // RIGHT
+                if(tiles.r != WALL_TILE) {
+                    canvas_set_color(canvas, ColorBlack);
+                    canvas_draw_line(canvas, ex, (sy) + ((tiles.u != WALL_TILE) ? 1 : 0), ex, ey);
+                    canvas_set_color(canvas, ColorWhite);
+                    if(whiteB)
+                        canvas_draw_line(
+                            canvas,
+                            ex - 1,
+                            sy + ((tiles.u != WALL_TILE) ? 2 : 0),
+                            ex - 1,
+                            ey - ((tiles.d != WALL_TILE) ? 2 : 0));
+                }
+
+                if((tiles.dl != WALL_TILE) && (tiles.l == WALL_TILE)) {
+                    canvas_set_color(canvas, ColorBlack);
+                    canvas_draw_line(canvas, sx, ey, sx + 1, ey);
+                    canvas_set_color(canvas, ColorWhite);
+                    if(whiteB) canvas_draw_line(canvas, sx, ey - 1, sx + 2, ey - 1);
+                }
+
+                if((tiles.ur != WALL_TILE) && (tiles.u == WALL_TILE)) {
+                    canvas_set_color(canvas, ColorBlack);
+                    canvas_draw_line(canvas, ex, sy, ex, sy + 1);
+                    canvas_set_color(canvas, ColorWhite);
+                    if(whiteB) canvas_draw_line(canvas, ex - 1, sy, ex - 1, sy + 2);
+                }
+
+                if(tiles.ul != WALL_TILE) {
+                    canvas_set_color(canvas, ColorWhite);
+                    canvas_draw_dot(canvas, sx, sy);
+                    if(whiteB) canvas_draw_dot(canvas, sx + 2, sy + 2);
+                    if(tiles.l == WALL_TILE) {
+                        canvas_set_color(canvas, ColorBlack);
+                        canvas_draw_line(canvas, sx, sy + 1, sx + 1, sy + 1);
+                        canvas_set_color(canvas, ColorWhite);
+                        if(whiteB) canvas_draw_line(canvas, sx, sy + 2, sx + 1, sy + 2);
+                    }
+                    if(tiles.u == WALL_TILE) {
+                        canvas_set_color(canvas, ColorBlack);
+                        canvas_draw_line(canvas, sx + 1, sy, sx + 1, sy + 1);
+                        canvas_set_color(canvas, ColorWhite);
+                        if(whiteB) canvas_draw_line(canvas, sx + 2, sy, sx + 2, sy + 1);
+                    }
+                }
+
+                if((tiles.dr != WALL_TILE) && (tiles.r == WALL_TILE) && (tiles.d == WALL_TILE)) {
+                    canvas_set_color(canvas, ColorWhite);
+                    if(whiteB) canvas_draw_line(canvas, ex - 1, ey - 1, ex - 1, ey);
+                }
+            }
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_movable(Canvas* canvas, Game* game, uint32_t frameNo) {
+    bool oddFrame = (frameNo % 20 < 10);
+    if(game->currentMovable != MOVABLE_NOT_FOUND) {
+        canvas_set_color(canvas, ColorBlack);
+        uint8_t x = coord_x(game->currentMovable);
+        uint8_t y = coord_y(game->currentMovable);
+        uint8_t how_movable = game->movables[y][x];
+
+        if((how_movable & MOVABLE_LEFT) != 0) {
+            canvas_draw_icon(
+                canvas, (x - 1) * TILE_SIZE + (oddFrame ? 0 : 1), y * TILE_SIZE, &I_arr_l);
+        }
+
+        if((how_movable & MOVABLE_RIGHT) != 0) {
+            canvas_draw_icon(
+                canvas, (x + 1) * TILE_SIZE + (oddFrame ? 1 : 0), y * TILE_SIZE, &I_arr_r);
+        }
+
+        canvas_draw_frame(
+            canvas, x * TILE_SIZE - 1, y * TILE_SIZE - 1, TILE_SIZE + 3, TILE_SIZE + 3);
+
+        if(oddFrame) {
+            canvas_draw_frame(
+                canvas, x * TILE_SIZE - 2, y * TILE_SIZE - 2, TILE_SIZE + 5, TILE_SIZE + 5);
+        } else {
+            canvas_draw_frame(canvas, x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE + 1, TILE_SIZE + 1);
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_direction(Canvas* canvas, Game* game, uint32_t frameNo) {
+    bool oddFrame = (frameNo % 20 < 10);
+    if(game->currentMovable != MOVABLE_NOT_FOUND) {
+        canvas_set_color(canvas, ColorBlack);
+        uint8_t x = coord_x(game->currentMovable);
+        uint8_t y = coord_y(game->currentMovable);
+
+        if(oddFrame) {
+            canvas_draw_icon(canvas, (x - 1) * TILE_SIZE, y * TILE_SIZE, &I_mov_l);
+            canvas_draw_icon(canvas, ((x + 1) * TILE_SIZE) + 1, y * TILE_SIZE, &I_mov_r);
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_direction_solution(Canvas* canvas, Game* game, uint32_t frameNo) {
+    bool oddFrame = (frameNo % 20 < 10);
+    if(game->currentMovable != MOVABLE_NOT_FOUND) {
+        canvas_set_color(canvas, ColorBlack);
+        uint8_t x = coord_x(game->currentMovable);
+        uint8_t y = coord_y(game->currentMovable);
+        uint8_t how_movable = game->movables[y][x];
+
+        if((how_movable & MOVABLE_LEFT) != 0) {
+            canvas_draw_icon(
+                canvas, (x - 1) * TILE_SIZE + (oddFrame ? 0 : 1), y * TILE_SIZE, &I_arr_l);
+        }
+
+        if((how_movable & MOVABLE_RIGHT) != 0) {
+            canvas_draw_icon(
+                canvas, (x + 1) * TILE_SIZE + (oddFrame ? 1 : 0), y * TILE_SIZE, &I_mov_r);
+        }
+
+        canvas_draw_frame(
+            canvas, x * TILE_SIZE - 1, y * TILE_SIZE - 1, TILE_SIZE + 3, TILE_SIZE + 3);
+
+        if(oddFrame) {
+            canvas_draw_frame(
+                canvas, x * TILE_SIZE - 2, y * TILE_SIZE - 2, TILE_SIZE + 5, TILE_SIZE + 5);
+        } else {
+            canvas_draw_frame(canvas, x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE + 1, TILE_SIZE + 1);
+        }
+    }
+
+    game->move.frameNo--;
+    if(game->move.frameNo == 0) {
+        solution_move(game);
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_ani_sides(Canvas* canvas, Game* game) {
+    uint8_t tile, sx, sy, deltaX;
+
+    if(game->state == MOVE_SIDES) {
+        tile = game->board[game->move.y][game->move.x];
+        deltaX = ((game->move.dir & MOVABLE_LEFT) != 0) ? -1 : 1;
+
+        sx = (game->move.x * TILE_SIZE) + (deltaX * game->move.frameNo);
+        sy = game->move.y * TILE_SIZE;
+
+        canvas_set_color(canvas, ColorBlack);
+        canvas_draw_icon(canvas, sx, sy, tile_to_icon(tile, game->state == GAME_OVER));
+
+        game->move.frameNo++;
+
+        if(game->move.frameNo > TILE_SIZE) {
+            stop_move(game);
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_ani_gravity(Canvas* canvas, Game* game) {
+    uint8_t tile, x, y, sx, sy;
+
+    if(game->state == MOVE_GRAVITY) {
+        for(y = 0; y < SIZE_Y; y++) {
+            for(x = 0; x < SIZE_X; x++) {
+                tile = game->board[y][x];
+
+                sx = x * TILE_SIZE;
+                sy = y * TILE_SIZE;
+
+                if((tile > 0) && (game->toAnimate[y][x] == 1)) {
+                    canvas_set_color(canvas, ColorBlack);
+                    canvas_draw_icon(
+                        canvas,
+                        sx,
+                        sy + game->move.frameNo,
+                        tile_to_icon(tile, game->state == GAME_OVER));
+                }
+            }
+        }
+
+        if(game->move.delay > 0) {
+            game->move.delay--;
+            return;
+        }
+
+        game->move.frameNo++;
+        if(game->move.frameNo > TILE_SIZE) {
+            stop_gravity(game);
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_ani_explode(Canvas* canvas, Game* game) {
+    uint8_t tile, x, y, sx, sy, cx, cy, s, o;
+
+    if(game->state == EXPLODE) {
+        for(y = 0; y < SIZE_Y; y++) {
+            for(x = 0; x < SIZE_X; x++) {
+                tile = game->board[y][x];
+
+                if((tile > 0) && (game->toAnimate[y][x] == 1)) {
+                    sx = x * TILE_SIZE;
+                    sy = y * TILE_SIZE;
+                    cx = sx + 4;
+                    cy = sy + 4;
+
+                    if((game->move.delay % 4 < 2) || (game->move.delay > 8)) {
+                        canvas_set_color(canvas, ColorBlack);
+                        canvas_draw_icon(
+                            canvas, sx, sy, tile_to_icon(tile, game->state == GAME_OVER));
+                    }
+
+                    if(game->move.frameNo > 0) {
+                        canvas_set_color(canvas, ColorXOR);
+                        o = MIN(((game->move.frameNo - 1) / 2), (uint8_t)4);
+                        s = (o * 2) + 1;
+                        canvas_draw_box(canvas, cx - o, cy - o, s, s);
+                    }
+                }
+            }
+        }
+
+        if(game->move.delay > 0) {
+            game->move.delay--;
+            return;
+        }
+
+        game->move.frameNo++;
+        if(game->move.frameNo > 10) {
+            stop_explosion(game);
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_scores(Canvas* canvas, Game* game, uint32_t frameNo) {
+    BoundingBox box;
+    int bufSize = 80;
+    char buf[bufSize];
+
+    bool showScore = (frameNo % 200) < 100;
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_rbox(canvas, 82, 1, 46, 17, 2);
+
+    canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r6_tr);
+    canvas_set_color(canvas, ColorWhite);
+    set_bounding_box(&box, 82, 1, 46, 17);
+    elements_multiline_text_aligned_limited(
+        canvas,
+        &box,
+        box.width / 2,
+        box.height / 2,
+        2,
+        AlignCenter,
+        AlignCenter,
+        furi_string_get_cstr(game->levelData->title));
+
+    if(game->solutionMode) {
+        canvas_set_color(canvas, ColorBlack);
+        canvas_set_custom_u8g2_font(canvas, app_u8g2_font_wedge_tr);
+        canvas_draw_str_aligned(canvas, 104, 27, AlignCenter, AlignTop, "Solution");
+        canvas_set_custom_u8g2_font(canvas, app_u8g2_font_tom_thumb_4x6_mr);
+        memset(buf, 0, bufSize);
+        snprintf(buf, sizeof(buf), "%d of %d", game->solutionStep + 1, game->solutionTotal);
+        canvas_draw_str_aligned(canvas, 104, 34, AlignCenter, AlignTop, buf);
+    } else {
+        canvas_set_color(canvas, ColorBlack);
+        canvas_set_custom_u8g2_font(canvas, app_u8g2_font_wedge_tr);
+        canvas_draw_str_aligned(
+            canvas, 104, 20, AlignCenter, AlignTop, showScore ? "Score" : "Level");
+        canvas_set_custom_u8g2_font(canvas, app_u8g2_font_tom_thumb_4x6_mr);
+        memset(buf, 0, bufSize);
+        if(showScore) {
+            if(game->score == 0) {
+                snprintf(buf, sizeof(buf), "on par");
+            } else {
+                snprintf(buf, sizeof(buf), "%+d", game->score);
+            }
+        } else {
+            snprintf(buf, sizeof(buf), "%u/%u", game->currentLevel + 1, game->levelSet->maxLevel);
+        }
+
+        canvas_draw_str_aligned(canvas, 104, 27, AlignCenter, AlignTop, buf);
+
+        canvas_set_custom_u8g2_font(canvas, app_u8g2_font_wedge_tr);
+        canvas_draw_str_aligned(
+            canvas, 104, 34, AlignCenter, AlignTop, showScore ? "Best" : "Moves");
+        canvas_set_custom_u8g2_font(canvas, app_u8g2_font_tom_thumb_4x6_mr);
+        memset(buf, 0, bufSize);
+
+        if(showScore) {
+            snprintf(buf, sizeof(buf), "%s", game->parLabel);
+        } else {
+            snprintf(buf, sizeof(buf), "%u/%u", game->gameMoves, game->levelData->gamePar);
+        }
+
+        canvas_draw_str_aligned(canvas, 104, 41, AlignCenter, AlignTop, buf);
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_paused(Canvas* canvas, Game* game) {
+    gray_canvas(canvas);
+
+    menu_pill(
+        canvas,
+        0,
+        MENU_PAUSED_COUNT,
+        ((game->menuPausedPos == 0) && (game->undoMovable != MOVABLE_NOT_FOUND)),
+        game->undoMovable == MOVABLE_NOT_FOUND,
+        "Undo",
+        &I_ico_undo);
+    menu_pill(
+        canvas, 1, MENU_PAUSED_COUNT, game->menuPausedPos == 1, false, "Restart", &I_ico_restart);
+    menu_pill(canvas, 2, MENU_PAUSED_COUNT, game->menuPausedPos == 2, false, "Menu", &I_ico_home);
+    menu_pill(canvas, 3, MENU_PAUSED_COUNT, game->menuPausedPos == 3, false, "Skip", &I_ico_skip);
+    menu_pill(canvas, 4, MENU_PAUSED_COUNT, game->menuPausedPos == 4, false, "Count", &I_ico_hist);
+    menu_pill(
+        canvas, 5, MENU_PAUSED_COUNT, game->menuPausedPos == 5, false, "Solve", &I_ico_check);
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontSecondary);
+    elements_button_right_back(canvas, "Back to game");
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_histogram(Canvas* canvas, Stats* stats) {
+    gray_canvas(canvas);
+    panel_histogram(canvas, furi_string_get_cstr(stats->bricksNonZero), stats->statsNonZero);
+}
+
+void draw_playfield_hint(Canvas* canvas, Game* game) {
+    if(game->state == SELECT_BRICK) {
+        if((game->currentMovable != MOVABLE_NOT_FOUND) &&
+           (movable_dir(&game->movables, game->currentMovable) == MOVABLE_BOTH)) {
+            hint_pill_double(canvas, "Select", "Choose", &I_hint_2);
+        } else {
+            hint_pill_double(canvas, "Select", "Move", &I_hint_1);
+        }
+    }
+
+    if(game->state == SELECT_DIRECTION) {
+        hint_pill_double(canvas, "Move", "Cancel", &I_hint_3);
+    }
+
+    if(game->state == SOLUTION_SELECT || game->solutionMode) {
+        hint_pill_double(canvas, "ANY", "CANCEL", &I_hint_4);
+    } else {
+        if(game->state == MOVE_SIDES) {
+            hint_pill_single(canvas, "moving..");
+        }
+
+        if(game->state == MOVE_GRAVITY) {
+            hint_pill_single(canvas, "falling..");
+        }
+
+        if(game->state == EXPLODE) {
+            hint_pill_single(canvas, "BOOM!");
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_game_over(Canvas* canvas, GameOver gameOverReason) {
+    gray_canvas(canvas);
+
+    const uint8_t y = dialog_frame(canvas, 100, 38, true, false, "Game Over!");
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontSecondary);
+    if(gameOverReason == CANNOT_MOVE) {
+        canvas_draw_str_aligned(
+            canvas, GUI_DISPLAY_CENTER_X, y + 8, AlignCenter, AlignTop, "Cannot move");
+    } else if(gameOverReason == BRICKS_LEFT) {
+        canvas_draw_str_aligned(
+            canvas, GUI_DISPLAY_CENTER_X, y + 8, AlignCenter, AlignTop, "Unpaired bricks left");
+    }
+
+    elements_button_left(canvas, "Retry");
+    elements_button_center(canvas, "Menu");
+    elements_button_right_back(canvas, "Undo");
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_level_finished(Canvas* canvas, Game* game) {
+    int bufSize = 80;
+    char buf[bufSize];
+
+    bool hasNext = game->currentLevel < game->levelSet->maxLevel - 1;
+
+    gray_canvas(canvas);
+
+    const uint8_t x = (GUI_DISPLAY_WIDTH - 100) / 2;
+    const uint8_t y =
+        dialog_frame(canvas, 100, 40, true, false, hasNext ? "Level finished!" : "Pack finished!");
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_vline_dotted(canvas, GUI_DISPLAY_CENTER_X, y, 30);
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
+    canvas_draw_str_aligned(canvas, x + 25, y + 4, AlignCenter, AlignTop, "Moves/Par");
+    memset(buf, 0, bufSize);
+    snprintf(buf, sizeof(buf), "%u / %u", game->gameMoves, game->levelData->gamePar);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, x + 25, y + 22, AlignCenter, AlignBottom, buf);
+
+    canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
+    canvas_draw_str_aligned(canvas, x + 75, y + 4, AlignCenter, AlignTop, "Score");
+    memset(buf, 0, bufSize);
+    if(game->score == 0) {
+        snprintf(buf, sizeof(buf), "on par");
+    } else {
+        snprintf(buf, sizeof(buf), "%+d", game->score);
+    }
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, x + 75, y + 22, AlignCenter, AlignBottom, buf);
+
+    canvas_set_font(canvas, FontSecondary);
+
+    if(hasNext) {
+        elements_button_center(canvas, "Next");
+        elements_button_right_back(canvas, "Menu");
+    } else {
+        elements_button_center(canvas, "Menu");
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_solution_prompt(Canvas* canvas, Game* game) {
+    gray_canvas(canvas);
+
+    const uint8_t y = dialog_frame(canvas, 100, 40, true, false, "Show solution?");
+    const bool penalty = solution_will_have_penalty(game);
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
+
+    if(penalty) {
+        canvas_draw_str_aligned(
+            canvas, GUI_DISPLAY_CENTER_X, y + 4, AlignCenter, AlignTop, "It has one-time penalty");
+        canvas_draw_str_aligned(
+            canvas, GUI_DISPLAY_CENTER_X, y + 15, AlignCenter, AlignTop, "of additional 5 point");
+    } else {
+        canvas_draw_str_aligned(
+            canvas, GUI_DISPLAY_CENTER_X, y + 4, AlignCenter, AlignTop, "Show solution?");
+    }
+
+    elements_button_center(canvas, "Show");
+    elements_button_right_back(canvas, "Resign");
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_reset_prompt(Canvas* canvas, Game* game) {
+    UNUSED(game);
+
+    gray_canvas(canvas);
+
+    const uint8_t y = dialog_frame(canvas, 110, 45, true, false, "Reset game?");
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
+
+    canvas_draw_str_aligned(
+        canvas, GUI_DISPLAY_CENTER_X, y + 2, AlignCenter, AlignTop, "Starting new game will reset");
+    canvas_draw_str_aligned(
+        canvas, GUI_DISPLAY_CENTER_X, y + 10, AlignCenter, AlignTop, "all progress and scores!");
+
+    canvas_draw_str_aligned(
+        canvas, GUI_DISPLAY_CENTER_X, y + 21, AlignCenter, AlignTop, "Are you sure?");
+
+    canvas_set_font(canvas, FontSecondary);
+    elements_button_center(canvas, "Confirm");
+    canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
+    elements_button_right_back(canvas, "Back");
+}
+
+//-----------------------------------------------------------------------------
+
+void draw_invalid_prompt(Canvas* canvas, Game* game) {
+    UNUSED(game);
+
+    gray_canvas(canvas);
+
+    const uint8_t y = dialog_frame(canvas, 110, 45, true, false, "Invalid level");
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_custom_u8g2_font(canvas, app_u8g2_font_squeezed_r7_tr);
+
+    canvas_draw_str_aligned(
+        canvas, GUI_DISPLAY_CENTER_X, y + 2, AlignCenter, AlignTop, "Cannot load/parse level!");
+    canvas_draw_str_aligned(
+        canvas,
+        GUI_DISPLAY_CENTER_X,
+        y + 10,
+        AlignCenter,
+        AlignTop,
+        furi_string_get_cstr(game->errorMsg));
+
+    canvas_draw_str_aligned(
+        canvas, GUI_DISPLAY_CENTER_X, y + 21, AlignCenter, AlignTop, "Repair or remove file!");
+
+    canvas_set_font(canvas, FontSecondary);
+    elements_button_center(canvas, "Understood");
+}

+ 26 - 0
non_catalog_apps/vexed/draw.h

@@ -0,0 +1,26 @@
+#pragma once
+
+#include "game.h"
+
+void draw_app(Canvas* canvas, Game* game);
+void draw_intro(Canvas* canvas, Game* game, uint32_t frameNo);
+void draw_reset_prompt(Canvas* canvas, Game* game);
+void draw_about(Canvas* canvas, Game* game, uint32_t frameNo);
+void draw_set_info(Canvas* canvas, Game* game);
+void draw_level_info(Canvas* canvas, Game* game);
+void draw_main_menu(Canvas* canvas, Game* game);
+void draw_playground(Canvas* canvas, Game* game);
+void draw_movable(Canvas* canvas, Game* game, uint32_t frameNo);
+void draw_direction(Canvas* canvas, Game* game, uint32_t frameNo);
+void draw_direction_solution(Canvas* canvas, Game* game, uint32_t frameNo);
+void draw_ani_sides(Canvas* canvas, Game* game);
+void draw_ani_gravity(Canvas* canvas, Game* game);
+void draw_ani_explode(Canvas* canvas, Game* game);
+void draw_scores(Canvas* canvas, Game* game, uint32_t frameNo);
+void draw_paused(Canvas* canvas, Game* game);
+void draw_histogram(Canvas* canvas, Stats* stats);
+void draw_playfield_hint(Canvas* canvas, Game* game);
+void draw_game_over(Canvas* canvas, GameOver gameOverReason);
+void draw_level_finished(Canvas* canvas, Game* game);
+void draw_solution_prompt(Canvas* canvas, Game* game);
+void draw_invalid_prompt(Canvas* canvas, Game* game);

+ 487 - 0
non_catalog_apps/vexed/events.c

@@ -0,0 +1,487 @@
+#include "events.h"
+
+#include "move.h"
+#include "game.h"
+
+//-----------------------------------------------------------------------------
+
+void events_for_selection(InputEvent* event, Game* game) {
+    if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
+        if(game->solutionMode) {
+            end_solution(game);
+            return;
+        }
+        switch(event->key) {
+        case InputKeyLeft:
+            find_movable_left(&game->movables, &game->currentMovable);
+            break;
+        case InputKeyRight:
+            find_movable_right(&game->movables, &game->currentMovable);
+            break;
+        case InputKeyUp:
+            find_movable_up(&game->movables, &game->currentMovable);
+            break;
+        case InputKeyDown:
+            find_movable_down(&game->movables, &game->currentMovable);
+            break;
+        case InputKeyOk:
+            click_selected(game);
+            break;
+        case InputKeyBack:
+            game->menuPausedPos = (game->undoMovable == MOVABLE_NOT_FOUND) ? 4 : 0;
+            game->state = PAUSED;
+            break;
+        default:
+            break;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void events_for_direction(InputEvent* event, Game* game) {
+    if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
+        switch(event->key) {
+        case InputKeyLeft:
+            start_move(game, MOVABLE_LEFT);
+            break;
+        case InputKeyRight:
+            start_move(game, MOVABLE_RIGHT);
+            break;
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyBack:
+        case InputKeyOk:
+            game->state = SELECT_BRICK;
+            break;
+        default:
+            break;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void events_for_paused(InputEvent* event, Game* game) {
+    if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
+        switch(event->key) {
+        case InputKeyLeft:
+            game->menuPausedPos =
+                (game->menuPausedPos + MENU_PAUSED_COUNT - 1) % MENU_PAUSED_COUNT;
+            if((game->menuPausedPos == 0) && (game->undoMovable == MOVABLE_NOT_FOUND)) {
+                game->menuPausedPos =
+                    (game->menuPausedPos + MENU_PAUSED_COUNT - 1) % MENU_PAUSED_COUNT;
+            }
+            break;
+        case InputKeyRight:
+            game->menuPausedPos = (game->menuPausedPos + 1) % MENU_PAUSED_COUNT;
+            if((game->menuPausedPos == 0) && (game->undoMovable == MOVABLE_NOT_FOUND)) {
+                game->menuPausedPos = (game->menuPausedPos + 1) % MENU_PAUSED_COUNT;
+            }
+            break;
+
+        case InputKeyUp:
+            game->menuPausedPos =
+                (game->menuPausedPos + MENU_PAUSED_COUNT - 2) % MENU_PAUSED_COUNT;
+            if((game->menuPausedPos == 0) && (game->undoMovable == MOVABLE_NOT_FOUND)) {
+                game->menuPausedPos =
+                    (game->menuPausedPos + MENU_PAUSED_COUNT - 2) % MENU_PAUSED_COUNT;
+            }
+            break;
+
+        case InputKeyDown:
+            game->menuPausedPos = (game->menuPausedPos + 2) % MENU_PAUSED_COUNT;
+            if((game->menuPausedPos == 0) && (game->undoMovable == MOVABLE_NOT_FOUND)) {
+                game->menuPausedPos = (game->menuPausedPos + 2) % MENU_PAUSED_COUNT;
+            }
+            break;
+        case InputKeyOk:
+            switch(game->menuPausedPos) {
+            case 0: // undo
+                undo(game);
+                break;
+            case 1: // restart
+                refresh_level(game);
+                break;
+            case 2: // menu
+                game->mainMenuMode = CUSTOM;
+                game->mainMenuBtn = MODE_BTN;
+                game->state = MAIN_MENU;
+                break;
+            case 3: // skip
+                start_game_at_level(game, game->currentLevel + 1);
+                break;
+            case 4: // count
+                game->state = HISTOGRAM;
+                break;
+            case 5: // solve
+                if(solution_will_have_penalty(game)) {
+                    game->state = SOLUTION_PROMPT;
+                } else {
+                    start_solution(game);
+                }
+                break;
+            default:
+                break;
+            }
+            break;
+        case InputKeyBack:
+            game->state = SELECT_BRICK;
+            break;
+        default:
+            break;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void events_for_game_over(InputEvent* event, Game* game) {
+    if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
+        switch(event->key) {
+        case InputKeyBack:
+            undo(game);
+            break;
+        case InputKeyOk:
+            game->mainMenuMode = (game->hasContinue) ? CONTINUE : NEW_GAME;
+            game->mainMenuBtn = MODE_BTN;
+            game->state = MAIN_MENU;
+            break;
+        case InputKeyLeft:
+            refresh_level(game);
+            break;
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        default:
+            break;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void events_for_level_finished(InputEvent* event, Game* game) {
+    if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
+        switch(event->key) {
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyBack:
+            game->mainMenuMode = CONTINUE;
+            game->mainMenuBtn = MODE_BTN;
+            game->state = MAIN_MENU;
+            break;
+        case InputKeyOk:
+            start_game_at_level(game, game->currentLevel + 1);
+            break;
+        default:
+            break;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void events_for_histogram(InputEvent* event, Game* game) {
+    if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
+        switch(event->key) {
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyBack:
+        case InputKeyOk:
+            game->state = SELECT_BRICK;
+            break;
+        default:
+            break;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void events_for_main_menu(InputEvent* event, Game* game) {
+    if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
+        switch(event->key) {
+        case InputKeyOk:
+            switch(game->mainMenuMode) {
+            case NEW_GAME:
+                if(game->hasContinue) {
+                    game->state = RESET_PROMPT;
+                } else {
+                    new_game(game);
+                }
+                break;
+            case CUSTOM:
+                switch(game->mainMenuBtn) {
+                case LEVELSET_BTN:
+                case LEVELNO_BTN:
+                    if(game->mainMenuInfo) {
+                        game->mainMenuInfo = false;
+                        load_gameset_if_needed(game, game->selectedSet);
+                        start_game_at_level(game, game->selectedLevel);
+                    } else {
+                        game->mainMenuInfo = true;
+                    }
+                    break;
+                default:
+                case MODE_BTN:
+                    load_gameset_if_needed(game, game->selectedSet);
+                    start_game_at_level(game, game->selectedLevel);
+                    break;
+                }
+                break;
+            case CONTINUE:
+            default:
+                load_gameset_if_needed(game, game->continueSet);
+                start_game_at_level(game, game->continueLevel + 1);
+                break;
+            }
+            break;
+        case InputKeyLeft:
+            if(game->mainMenuInfo) return;
+            switch(game->mainMenuBtn) {
+            case LEVELSET_BTN:
+                game->setPos = (game->setPos > 0) ? game->setPos - 1 : game->setCount - 1;
+                furi_string_set(game->selectedSet, level_on_pos(game, game->setPos));
+                load_gameset_if_needed(game, game->selectedSet);
+                game->selectedLevel = 0;
+                break;
+            case LEVELNO_BTN:
+                game->selectedLevel = (game->selectedLevel > 0) ? game->selectedLevel - 1 :
+                                                                  game->levelSet->maxLevel - 1;
+                break;
+            case MODE_BTN:
+            default:
+                if(game->mainMenuMode == CUSTOM) {
+                    game->mainMenuMode = game->hasContinue ? CONTINUE : NEW_GAME;
+                } else if(game->mainMenuMode == CONTINUE) {
+                    game->mainMenuMode = NEW_GAME;
+                } else {
+                    game->mainMenuMode = CUSTOM;
+                }
+                break;
+            }
+            break;
+        case InputKeyRight:
+            if(game->mainMenuInfo) return;
+            switch(game->mainMenuBtn) {
+            case LEVELSET_BTN:
+                game->setPos = (game->setPos < game->setCount - 1) ? game->setPos + 1 : 0;
+                furi_string_set(game->selectedSet, level_on_pos(game, game->setPos));
+                load_gameset_if_needed(game, game->selectedSet);
+                game->selectedLevel = 0;
+                break;
+            case LEVELNO_BTN:
+                game->selectedLevel = (game->selectedLevel < (game->levelSet->maxLevel - 1)) ?
+                                          game->selectedLevel + 1 :
+                                          0;
+                break;
+            case MODE_BTN:
+            default:
+
+                if(game->mainMenuMode == NEW_GAME) {
+                    game->mainMenuMode = game->hasContinue ? CONTINUE : CUSTOM;
+                } else if(game->mainMenuMode == CONTINUE) {
+                    game->mainMenuMode = CUSTOM;
+                } else {
+                    game->mainMenuMode = NEW_GAME;
+                }
+            }
+            break;
+        case InputKeyUp:
+            if(game->mainMenuInfo) return;
+            if(game->mainMenuMode == CUSTOM) {
+                game->mainMenuBtn = (game->mainMenuBtn - 1 + MAIN_MENU_COUNT) % MAIN_MENU_COUNT;
+            }
+            break;
+        case InputKeyDown:
+            if(game->mainMenuInfo) return;
+            if(game->mainMenuMode == CUSTOM) {
+                game->mainMenuBtn = (game->mainMenuBtn + 1 + MAIN_MENU_COUNT) % MAIN_MENU_COUNT;
+            }
+            break;
+        case InputKeyBack:
+            if(game->mainMenuInfo) {
+                game->mainMenuInfo = false;
+            } else {
+                game->state = ABOUT;
+            }
+        default:
+            break;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void events_for_intro(InputEvent* event, Game* game) {
+    if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
+        switch(event->key) {
+        case InputKeyOk:
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyBack:
+            game->state = MAIN_MENU;
+        default:
+            break;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void events_for_about(InputEvent* event, Game* game) {
+    if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
+        switch(event->key) {
+        case InputKeyOk:
+            // handled on root level - exit
+            // see: game_vexed.c
+            break;
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyBack:
+            game->state = MAIN_MENU;
+        default:
+            break;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void events_for_solution_prompt(InputEvent* event, Game* game) {
+    if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
+        switch(event->key) {
+        case InputKeyOk:
+            start_solution(game);
+            break;
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyBack:
+            game->state = SELECT_BRICK;
+        default:
+            break;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void events_for_solution_select(InputEvent* event, Game* game) {
+    if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
+        switch(event->key) {
+        case InputKeyUp:
+        case InputKeyDown:
+            break;
+        case InputKeyOk:
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyBack:
+            end_solution(game);
+        default:
+            break;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void events_for_reset(InputEvent* event, Game* game) {
+    if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
+        switch(event->key) {
+        case InputKeyOk:
+            new_game(game);
+            break;
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyBack:
+            game->state = MAIN_MENU;
+        default:
+            break;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void events_for_invalid(InputEvent* event, Game* game) {
+    if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
+        switch(event->key) {
+        case InputKeyOk:
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyBack:
+            game->state = MAIN_MENU;
+        default:
+            break;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void events_for_game(InputEvent* event, Game* game) {
+    switch(game->state) {
+    case MAIN_MENU:
+        events_for_main_menu(event, game);
+        break;
+    case ABOUT:
+        events_for_about(event, game);
+        break;
+    case RESET_PROMPT:
+        events_for_reset(event, game);
+        break;
+    case INVALID_PROMPT:
+        events_for_invalid(event, game);
+        break;
+    case INTRO:
+        events_for_intro(event, game);
+        break;
+    case SELECT_BRICK:
+        events_for_selection(event, game);
+        break;
+    case SELECT_DIRECTION:
+        events_for_direction(event, game);
+        break;
+    case PAUSED:
+        events_for_paused(event, game);
+        break;
+    case HISTOGRAM:
+        events_for_histogram(event, game);
+        break;
+    case SOLUTION_PROMPT:
+        events_for_solution_prompt(event, game);
+        break;
+    case SOLUTION_SELECT:
+        events_for_solution_select(event, game);
+        break;
+    case GAME_OVER:
+        events_for_game_over(event, game);
+        break;
+    case LEVEL_FINISHED:
+        events_for_level_finished(event, game);
+        break;
+    case MOVE_SIDES:
+    case MOVE_GRAVITY:
+    case EXPLODE:
+        if(game->solutionMode) {
+            events_for_solution_select(event, game);
+        }
+    default:
+        break;
+    }
+}

+ 5 - 0
non_catalog_apps/vexed/events.h

@@ -0,0 +1,5 @@
+#pragma once
+
+#include "game.h"
+
+void events_for_game(InputEvent* event, Game* game);

+ 160 - 0
non_catalog_apps/vexed/fonts.c

@@ -0,0 +1,160 @@
+#include "fonts.h"
+
+/*
+  Fontname: u8g2_squeezed_regular_6
+  Copyright: public domain
+  Glyphs: 95/95
+  BBX Build Mode: 0
+*/
+const uint8_t app_u8g2_font_squeezed_r6_tr[708] =
+    "_\0\2\2\3\3\1\4\4\5\7\0\377\6\377\6\0\0\345\1\315\2\247 \4@T!\6qTF"
+    "\12\42\6Sf\222\12#\12m\364\252\241\252\241*\0$\13\375s\363Py\247\241#\0%\11sd"
+    "RL\61\305\24&\11t\354b\212U\63\5'\5QV\4(\6r\334T\63)\10r\134b\252\24"
+    "\0*\7\353dR\265\32+\7[\345\322J\0,\5\321S\4-\5\312]\4.\5IT\2/\10"
+    "sd\253\230b\4\60\7s\344*W\5\61\6r\334V\7\62\7r\134bRI\63\7r\134\224L"
+    "\12\64\10sd\222\32\261\1\65\7r\134VL\12\66\10s\344J\225T\5\67\10sdf\212-\0"
+    "\70\7s\344z\251\12\71\10s\344Z\262J\0:\6\331TR\0;\6\351S\242\0<\6Z\335d"
+    "\0=\6Z]\244\0>\6Z]\242\2\77\7r\134\242\312\11@\10t\354TT\67\32A\10s\344"
+    "\252\241\254\0B\11sdTZI-\0C\6r\334T\63D\10sdTr-\0E\7r\134\326"
+    "J\2F\7r\134\326\252\0G\10s\344f\262\222\0H\10sd\222\32\312\12I\5qT\16J\6"
+    "r\334\272\6K\10sd\222Z\311\12L\7r\134R\227\0M\11ut\362ZI\333\1N\10tl"
+    "\342R\323\14O\7s\344*W\5P\10sdTZ\261\4Q\7s\344*W\22R\10sdTZ"
+    "\311\12S\7r\334TL\12T\7sdV\354\2U\7sd\222\327\10V\10sd\222\253L\0W"
+    "\11ut\262+\251.\0X\11sd\222*SR\1Y\10sd\222UV\0Z\7r\134TRI"
+    "[\6r\134VK\134\10sdb\216\71\6]\6r\134Tk^\5S\346\32_\5J\134\4`\6"
+    "R^b\0a\7c\344\226J\2b\11sdbTI-\0c\6b\334T\14d\10sd\253\245"
+    "\222\0e\6c\344\252)f\7r\334\324\252\0g\7\353\343V\222\13h\10sdbT\311\12i\6"
+    "qT\322\10j\10\372\333r\252\24\0k\10sdb\265\222\12l\5qT\16m\11etF\252T"
+    "R\5n\7cdT\262\2o\7c\344*U\1p\10\353cTR+\2q\7\353\343\226J\62r"
+    "\7cd\322\212\21s\6b\334\244\2t\7r\134RZ\61u\7cd\222\65\2v\7cd\222\265"
+    "\0w\10et\262J\252\13x\7cdR\231\12y\11\353c\222J\62%\0z\6b\134\224\1{"
+    "\10sdS\222u\0|\5qT\16}\11sdrT\61E\0~\7T\356\222J\0\0\0\0\4"
+    "\377\377\0";
+
+/*
+  Fontname: u8g2_squeezed_regular_7
+  Copyright: public domain
+  Glyphs: 95/95
+  BBX Build Mode: 0
+*/
+const uint8_t app_u8g2_font_squeezed_r7_tr[744] =
+    "_\0\2\2\3\4\1\4\4\5\10\0\377\7\377\7\0\0\362\1\345\2\313 \5\200\250\0!\6\271\250"
+    "\14\25\42\6\223\315$\25#\12\255\351UCUCU\0$\13\275\350\346\241\362NCG\0%\11\263"
+    "\310\244\230b\212)&\11\274\330\305\324\254*U'\5\221\255\10(\7\272\270\251\316\0)\10\272\270\304"
+    "\324R\0*\7\253\311\244j\65+\7\233\312\245\225\0,\5\221\247\10-\5\212\273\10.\5\211\250\4"
+    "/\10\273\310V\261\212\21\60\10\273\310U^\25\0\61\6\272\270\255\36\62\10\272\270\304\224T\22\63\10"
+    "\272\270\250$\223\2\64\10\273\310$k\304\6\65\10\272\270\254\230\222\2\66\11\273\310\225Q%U\1\67"
+    "\10\273\310\314*\266\0\70\11\273\310U\252\245*\0\71\11\273\310U*\311*\1:\6\241\251\304\0;"
+    "\6\251\247D\1<\7\253\311\246\272\0=\6\232\273H\1>\7\253\311\344\252\4\77\10\272\270\304\244r"
+    "\2@\10\274\330\251h\335hA\10\273\310Uj(+B\11\273\310\250\244VR\13C\7\272\270\251\316"
+    "\0D\10\273\310\250\344\265\0E\10\272\270\254\264\222\0F\10\272\270\254\264*\0G\10\273\310\315\312J"
+    "\2H\10\273\310$k(+I\6\271\250\34\1J\6\272\270\365\32K\10\273\310$k%+L\7\272"
+    "\270\244^\2M\11\275\350\344\265\222v\7N\10\274\330\304\245\246\63O\10\273\310U^\25\0P\11\273"
+    "\310\250\244V,\1Q\7\273\310U^IR\11\273\310\250\244V\262\2S\10\272\270\251\230\222\2T\7"
+    "\273\310\254\330\13U\7\273\310$_#V\10\273\310$\257\62\1W\11\275\350d\257\244\272\0X\11\273"
+    "\310$U\246d\5Y\10\273\310$\253l\1Z\10\272\270\250\244*\1[\7\272\270\254.\1\134\10\273"
+    "\310\304\34\353\30]\7\272\270\250\256\1^\5\223\315\65_\5\212\270\10`\6\222\275\304\0a\7\253\310"
+    "-+\11b\11\273\310\304\250\222\265\0c\6\252\270\251\62d\10\273\310V\313J\2e\7\253\310UC"
+    "\13f\7\272\270\251\325\2g\10\263\307-\225\344\2h\10\273\310\304\250\222+i\6\271\250\244!j\10"
+    "\302\267\345\324R\0k\10\273\310\304j%+l\6\271\250\34\1m\12\255\350\214T\251\244\222*n\7"
+    "\253\310\250\344\12o\7\253\310UV\5p\10\263\307\250d\255\10q\10\263\307-+\311\0r\10\253\310"
+    "\244\25K\0s\7\252\270\311\244\0t\10\272\270\244\264\312\0u\7\253\310$\327\10v\7\253\310$\327"
+    "\2w\11\255\350d+\251.\0x\10\253\310\244\62%\25y\11\263\307$+\311\224\0z\7\252\270("
+    "\225\4{\10\273\310\246\230d\35|\6\271\250\34\1}\11\273\310\344\30UL\21~\7\224\335%\225\0"
+    "\0\0\0\4\377\377\0";
+
+/*
+  Fontname: -FreeType-Wedge-Medium-R-Normal--16-160-72-72-P-47-ISO10646-1
+  Copyright: Arvin
+  Glyphs: 95/106
+  BBX Build Mode: 0
+*/
+const uint8_t app_u8g2_font_wedge_tr[792] =
+    "_\0\2\3\3\3\2\3\5\7\6\0\377\5\377\5\0\1\2\2\11\2\373 \5\200\254\0!\6\252\234"
+    "\230\2\42\10\235\266H\224\224\2#\11\257\304\251.\325\245\2$\11\264\253\206\65T\206\0%\11\256\274"
+    "\210\224\254\42\5&\12\256\274\216\62(M\242$'\6\232\236T\0(\7\253\244QJ\1)\10\253\244"
+    "\210\222D\1*\11\255\264\206\325$Q\2+\11\254\254\211\22%J\0,\6\232\233P\1-\5\314\266"
+    "\20.\5\222\234\20/\11\264\253\252DI\24\1\60\12\255\264M\322!I\23\0\61\10\255\264M\221\254"
+    "*\62\10\255\264\320\220\250,\63\10\255\264\324\361l\1\64\10\255\264N\321x\13\65\7\255\264\334\265\5"
+    "\66\11\255\264M\62\245\232\0\67\10\255\264\324\242Z\4\70\11\255\264M\252I\65\1\71\11\255\264M*"
+    "Z\232\0:\6\252\234\220\4;\7\262\233\220\25\0<\7\254\254Jj\5=\7\234\255\320\20\1>\10"
+    "\254\254\310*%\0\77\12\265\263MjQFJ\0@\11\255\264M\22\33\232\0A\11\255\264\306\223t"
+    "H\2B\11\255\264P\222%Y\0C\7\254\254\225\352\1D\10\255\264P\22\313\2E\7\254\254\230\223"
+    "HF\10\254\254\230\223(\2G\11\255\264M\262DI\4H\11\255\264H\324!Q\2I\10\254\254P"
+    "\242\212\0J\10\254\254\215\312\12\0K\12\255\264HT\64J\221\2L\7\254\254\210\232\4M\11\255\264"
+    "\304\323!Q\2N\11\355\274\204\324!\221\1O\11\255\264M\22K\232\0P\11\255\264P\22e\311\0"
+    "Q\12\265\263M\22K\242\241\0R\10\255\264P\22e)S\7\254\254\225d\5T\10\254\254P\242\26"
+    "\0U\10\255\264H\134\322\4V\11\255\264H,i\16\1W\11\255\264H\324!\315\1X\12\255\264H"
+    "\224\64I\224\0Y\12\255\264H\224\64\312\42\0Z\10\254\254\220\222\222\0[\7\253\244T\252\1\134\11"
+    "\264\253\210\262(\213\2]\7\253\244L\252\2^\7\235\266\306\223\2_\5\214\253\20`\6\232\236P\1"
+    "a\10\245\264M\22\245\12b\11\255\264\310\224DY\0c\7\244\254\225\362\0d\10\255\264K\26%\21"
+    "e\10\245\264MR=\1f\10\254\254\225\223(\2g\11\255\263Y\22-M\0h\10\255\264\310\224\304"
+    "\22i\6\252\234\210\6j\10\263\243\311\220\212\2k\10\255\264\310*K\1l\7\253\244H\232\2m\12"
+    "\247\304XRDE\264\0n\10\245\264P\22K\0o\11\245\264M\22%M\0p\11\255\263P\22e"
+    "\311\0q\10\255\263Y\224D\13r\7\244\254\230J\0s\7\244\254\325P\5t\7\253\244H\225(u"
+    "\10\245\264H,\211\0v\11\245\264H\224\64\207\0w\12\247\304\310TDER\5x\10\245\264H\322"
+    "\70)y\12\255\263H\224DK\23\0z\6\244\254\220\14{\10\254\254M\322(\16|\6\262\233\34\5"
+    "}\11\254\254\214\342$M\0~\7\226\275M\322\4\0\0\0\4\377\377\0";
+
+/*
+  Fontname: -Raccoon-Fixed4x6-Medium-R-Normal--6-60-75-75-P-40-ISO10646-1
+  Copyright: 
+  Glyphs: 95/203
+  BBX Build Mode: 2
+*/
+const uint8_t app_u8g2_font_tom_thumb_4x6_mr[782] =
+    "_\2\3\2\3\3\1\1\4\4\6\0\377\5\377\5\0\0\377\2\0\2\361 \6t|\66\0!\7t"
+    "\234\254\36\3\42\7t\214\244\235\6#\11t\214$Y*K\25$\10t\34\233\242\305\0%\10t\214"
+    "\64\252\246\0&\11t\14IZJ*\0'\7t\234,'\2(\7t\254Z\61\5)\10t\214\60"
+    "+\345\0*\11t\214$\252\344\60\0+\10t\334h\312\61\0,\7t|\70\312\1-\7t|d"
+    "\207\1.\6t|Z\14/\10t\254,\252\345\0\60\10t\34\245/\62\0\61\7t\234Hk\6\62"
+    "\10t\14\61\252\255\0\63\10t\14\61\12\23\31\64\10t\214\244e\253\2\65\10t\214%\23\23\31\66"
+    "\11t\34%[*+\0\67\10t\214-\252\345\0\70\11t\214\245\262TV\0\71\11t\214\245\262%"
+    "\62\0:\7t\334<\307\0;\7t\334<\312\1<\7t\254\306*\0=\7t\314u\207\0>\7"
+    "t\214\260T\7\77\10t\214-\312c\0@\11t\234(I\226P\5A\11t\234(I\226\256\0B"
+    "\12t\14)I\244$\221\1C\10t\34%+\252\0D\10t\14)\351E\6E\10t\214%[\262"
+    "\25F\10t\214%[\262\34G\11t\34%[J*\0H\10t\214\244e\351\12I\10t\214)+"
+    "\255\0J\10t\254\254R\212\1K\11t\214\244EJZ\1L\7t\214\254m\5M\10t\214$\271"
+    "t\5N\10t\214$y\251\2O\10t\234(\351)\6P\11t\14)I\244,\7Q\11t\234("
+    "i\231T\0R\12t\14)I\26)I\1S\10t\34%\254\310\0T\7t\214)k\6U\10t"
+    "\214\244\237T\0V\10t\214\244\247,\6W\10t\214\244\345R\5X\10t\214\244\251\322\12Y\10t"
+    "\214\244)+\3Z\10t\214-\252\255\0[\10t\214%\253\255\0\134\6t\314v\10]\7t\214\255"
+    "e\5^\10t\234(\311i\0_\6t|\322\12`\7t\214\60'\2a\10tLM\251\254\0b"
+    "\11t\214LJZd\0c\10t\134%\13U\0d\10t\254H\351\244\2e\10t\134\245\242\251\0"
+    "f\10t\254\322\224\305\0g\11t\134\245\262E\21\0h\10t\214LJz\5i\7t\234<+\3"
+    "j\11t\254<KJ\21\0k\12t\214,I$)I\1l\7t\14\255i\5m\7t\314\345R"
+    "\5n\7tL)\351\25o\10t\334(i\212\1p\11tL)i\221\62\0q\10t\134\245\223\226"
+    "\0r\7t\134%\253\3s\10t\134ESd\0t\10t\234h\312\62\25u\7t\314\244'\25v"
+    "\10t\314\244e\212\1w\7t\314$y\5x\11t\314$\312\242$\5y\11t\314\244I\213\42\0"
+    "z\10t\314I\221V\0{\10t\34\251\230\251\0|\10t\234,\317b\0}\10t\14-\214\42\31"
+    "~\6t\34;\21\0\0\0\4\377\377\0";
+
+/*
+  Fontname: micro
+  Copyright: Public domain font.  Share and enjoy.
+  Glyphs: 96/128
+  BBX Build Mode: 0
+*/
+const uint8_t app_u8g2_font_micro_tr[702] =
+    "`\0\2\2\2\3\2\4\4\3\5\0\0\5\0\5\0\0\336\1\306\2\241 \4\300f!\6VdN"
+    "\1\42\6\313e\222\12#\10Wd\322PC\5$\7W\344\322\220C%\10\323dR\32\251\0&\10"
+    "W\344b\32\62\1'\5\352\345\6(\6v\344T\31)\7VdbJ\12*\7WdR\265\32+"
+    "\7\317\344\322J\0,\5J\344\6-\5Ge\6.\6JdF\0/\10Wd\253\230\42\0\60\7"
+    "W\344*\253\2\61\6V\344V\3\62\7Wdt*\7\63\7Wdt\222#\64\10Wd\222\65b"
+    "\0\65\7WdF\324\13\66\7W\344\346H#\67\10Wdf\25\23\0\70\10WdF\32j\4\71"
+    "\10WdF\32q\1:\7VdF\34\1;\6VdF\36<\6WdS]=\6\317d\366\0"
+    ">\7WdrU\2\77\10Wdf\322(\1@\6W\344\256,A\10WdF\32J\5B\10W"
+    "dFZi\4C\7WdF,\7D\10WdT\262\26\0E\7WdF\34qF\10WdF"
+    "\34\61\2G\10WdFLj\4H\10Wd\222\32J\5I\7WdV\254\6J\7WdK\65"
+    "\2K\10Wd\222ZI\5L\6WdbsM\10Wd\322PV\0N\7WdFr\5O\10"
+    "WdF\262F\0P\10WdF\32\62\2Q\7WdFRSR\10WdFZI\5S\7W"
+    "dF<\2T\7WdVl\1U\7Wd\222k\4V\7Wd\222\253\2W\10Wd\222\65T"
+    "\0X\11Wd\322Hi\244\0Y\11Wd\222\32)&\0Z\7Wdf*\7[\6vdV%"
+    "\134\7Wdb\216\71]\6VdT\65^\5\313\345\32_\5Gd\6`\6\352e\222\0a\6S"
+    "d\326\21b\10Wd\342Hj\4c\7SdF\214\3d\7Wd\207R#e\6SdF:f"
+    "\7W\344\246\212\21g\7SdFJCh\10Wd\342HV\0i\6qdF\0j\7Sd\243"
+    "\32\1k\10WdbZ\252\0l\6VdT\7m\7Sd\322P*n\7SdF\262\2o\7"
+    "SdFR#p\7SdF\32\22q\7SdF\32\61r\7SdF\222\21s\7Sd\206\34"
+    "\2t\7W\344\322\212Qu\7Sd\222\65\2v\7Sd\222U\1w\7Sd\222\32*x\7S"
+    "dR\231\12y\10Sd\222J#\1z\7Sd\326H\3{\7W\344T\222Q|\6ud\206\0"
+    "}\10WddTI\1~\6Ke\244\0\177\10W\344T\254\24\0\0\0\0\4\377\377\0";

+ 9 - 0
non_catalog_apps/vexed/fonts.h

@@ -0,0 +1,9 @@
+#pragma once
+
+#include <furi.h>
+
+extern const uint8_t app_u8g2_font_squeezed_r6_tr[708];
+extern const uint8_t app_u8g2_font_squeezed_r7_tr[744];
+extern const uint8_t app_u8g2_font_wedge_tr[792];
+extern const uint8_t app_u8g2_font_tom_thumb_4x6_mr[782];
+extern const uint8_t app_u8g2_font_micro_tr[702];

+ 656 - 0
non_catalog_apps/vexed/game.c

@@ -0,0 +1,656 @@
+#include "game.h"
+#include "utils.h"
+#include "move.h"
+
+Game* alloc_game_state(int* error) {
+    *error = 0;
+    Game* game = malloc(sizeof(Game));
+
+    game->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    if(!game->mutex) {
+        FURI_LOG_E(TAG, "cannot create mutex\r\n");
+        free(game);
+        *error = 255;
+        return NULL;
+    }
+
+    game->levelData = alloc_level_data();
+    game->levelSet = alloc_level_set();
+    game->stats = alloc_stats();
+
+    game->currentLevel = 0;
+    game->gameMoves = 0;
+    game->score = 0;
+
+    game->currentMovableBackup = MOVABLE_NOT_FOUND;
+    game->solutionMode = false;
+    game->solutionStep = 0;
+    game->solutionTotal = 0;
+
+    game->undoMovable = MOVABLE_NOT_FOUND;
+    game->currentMovable = MOVABLE_NOT_FOUND;
+    game->nextMovable = MOVABLE_NOT_FOUND;
+    game->menuPausedPos = 0;
+
+    game->mainMenuBtn = MODE_BTN;
+    game->mainMenuMode = NEW_GAME;
+    game->mainMenuInfo = false;
+    game->hasContinue = false;
+    game->selectedSet = furi_string_alloc_set(assetLevels[0]);
+    game->selectedLevel = 0;
+    game->continueSet = furi_string_alloc_set(assetLevels[0]);
+    game->continueLevel = 0;
+    game->setPos = 0;
+    game->setCount = 1;
+
+    game->state = INTRO;
+    game->gameOverReason = NOT_GAME_OVER;
+
+    game->move.frameNo = 0;
+
+    memset(game->parLabel, 0, PAR_LABEL_SIZE);
+    game->errorMsg = furi_string_alloc();
+
+    return game;
+}
+
+//-----------------------------------------------------------------------------
+
+void load_game_board(Game* g) {
+    bool levelLoadable = false;
+    // Open storage
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    if(load_level(storage, g->levelSet->id, g->currentLevel, g->levelData, g->errorMsg)) {
+        levelLoadable = parse_level_notation(furi_string_get_cstr(g->levelData->board), &g->board);
+    }
+    // Close storage
+
+    if(!levelLoadable) {
+        handle_ivalid_set(g, storage, g->levelSet->id, g->errorMsg);
+    }
+
+    furi_record_close(RECORD_STORAGE);
+}
+
+//-----------------------------------------------------------------------------
+
+void free_game_state(Game* game) {
+    view_port_free(game->viewPort);
+    furi_mutex_free(game->mutex);
+    free_level_data(game->levelData);
+    free_level_set(game->levelSet);
+    free_stats(game->stats);
+    furi_string_free(game->selectedSet);
+    furi_string_free(game->continueSet);
+    furi_string_free(game->errorMsg);
+    free_level_list(&game->levelList);
+    free(game);
+}
+
+//-----------------------------------------------------------------------------
+
+GameOver is_game_over(PlayGround* mv, Stats* stats) {
+    uint8_t sumMov = 0;
+    uint8_t sum = 0;
+    uint8_t x, y;
+    for(uint8_t i = 0; i < WALL_TILE; i++) {
+        sum += stats->ofBrick[i];
+    }
+    for(y = 0; y < SIZE_Y; y++) {
+        for(x = 0; x < SIZE_X; x++) {
+            sumMov += (*mv)[y][x];
+        }
+    }
+    if((sum > 0) && (sumMov == 0)) {
+        return CANNOT_MOVE;
+    }
+    for(uint8_t i = 0; i < WALL_TILE; i++) {
+        if(stats->ofBrick[i] == 1) return BRICKS_LEFT;
+    }
+    return NOT_GAME_OVER;
+}
+
+//-----------------------------------------------------------------------------
+
+bool is_level_finished(Stats* stats) {
+    uint8_t sum = 0;
+    for(uint8_t i = 0; i < WALL_TILE; i++) {
+        sum += stats->ofBrick[i];
+    }
+    return (sum == 0);
+}
+
+//-----------------------------------------------------------------------------
+
+Neighbors find_neighbors(PlayGround* pg, uint8_t x, uint8_t y) {
+    Neighbors ne;
+
+    ne.u = (y > 0) ? (*pg)[y - 1][x] : EMPTY_TILE;
+    ne.l = (x > 0) ? (*pg)[y][x - 1] : EMPTY_TILE;
+
+    ne.d = (y < SIZE_Y - 1) ? (*pg)[y + 1][x] : EMPTY_TILE;
+    ne.r = (x < SIZE_X - 1) ? (*pg)[y][x + 1] : EMPTY_TILE;
+
+    ne.dl = ((y < SIZE_Y - 1) && (x > 0)) ? (*pg)[y + 1][x - 1] : EMPTY_TILE;
+    ne.ur = ((y > 0) && (x < SIZE_X - 1)) ? (*pg)[y - 1][x + 1] : EMPTY_TILE;
+
+    ne.ul = ((y > 0) && (x > 0)) ? (*pg)[y - 1][x - 1] : EMPTY_TILE;
+    ne.dr = ((x < SIZE_X - 1) && (y < SIZE_Y - 1)) ? (*pg)[y + 1][x + 1] : EMPTY_TILE;
+
+    return ne;
+}
+
+//-----------------------------------------------------------------------------
+
+void index_set(Game* game) {
+    const char* findSetId = furi_string_get_cstr(game->levelSet->id);
+    game->setCount = level_count(game);
+    game->setPos = 0;
+    for(uint8_t i = 0; i < ASSETS_LEVELS_COUNT; i++) {
+        if(strcmp(findSetId, assetLevels[i]) == 0) {
+            game->setPos = i;
+            return;
+        }
+    }
+
+    if(game->levelList.ids != NULL) {
+        for(uint8_t j = 0; j < game->levelList.count; j++) {
+            if(strcmp(findSetId, furi_string_get_cstr(game->levelList.ids[j])) == 0) {
+                game->setPos = ASSETS_LEVELS_COUNT + j;
+                return;
+            }
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void recalc_score(Game* g) {
+    g->score = 0;
+    for(uint8_t i = 0; i < g->levelSet->maxLevel; i++) {
+        if(g->levelSet->scores[i].moves > 0) {
+            g->score += g->levelSet->scores[i].moves - g->levelSet->pars[i];
+        }
+        if(g->levelSet->scores[i].spoiled) {
+            g->score += 5;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void handle_ivalid_set(Game* game, Storage* storage, FuriString* setId, FuriString* errorMsg) {
+    mark_set_invalid(storage, setId, errorMsg);
+    list_extra_levels(storage, &game->levelList);
+    furi_string_set(game->errorMsg, "Invalid level: ");
+    furi_string_cat(game->errorMsg, setId);
+    furi_string_set(game->selectedSet, assetLevels[0]);
+    game->mainMenuMode = CUSTOM;
+    game->selectedLevel = 0;
+    game->mainMenuBtn = LEVELSET_BTN;
+    load_level_set(storage, game->selectedSet, game->levelSet, game->errorMsg);
+    game->state = INVALID_PROMPT;
+}
+
+//-----------------------------------------------------------------------------
+
+void initial_load_game(Game* game) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    list_extra_levels(storage, &game->levelList);
+
+    game->hasContinue = load_last_level(game->continueSet, &game->continueLevel);
+
+    if(game->hasContinue) {
+        furi_string_set(game->selectedSet, game->continueSet);
+        game->selectedLevel = game->continueLevel + 1;
+        game->mainMenuMode = CONTINUE;
+    } else {
+        furi_string_set(game->selectedSet, assetLevels[0]);
+        game->selectedLevel = 0;
+        game->mainMenuMode = NEW_GAME;
+    }
+
+    if(!load_level_set(storage, game->selectedSet, game->levelSet, game->errorMsg)) {
+        handle_ivalid_set(game, storage, game->selectedSet, game->errorMsg);
+    }
+    furi_record_close(RECORD_STORAGE);
+    index_set(game);
+    recalc_score(game);
+
+    if(game->selectedLevel > game->levelSet->maxLevel - 1) {
+        game->selectedLevel = game->levelSet->maxLevel - 1;
+    }
+
+    randomize_bg(&game->bg);
+}
+
+//-----------------------------------------------------------------------------
+
+void new_game(Game* game) {
+    forget_continue(game);
+    FuriString* setName = furi_string_alloc_set(assetLevels[0]);
+    load_gameset_if_needed(game, setName);
+    furi_string_free(setName);
+    start_game_at_level(game, 0);
+}
+
+//-----------------------------------------------------------------------------
+
+void load_gameset_if_needed(Game* game, FuriString* expectedSet) {
+    if(furi_string_cmp(expectedSet, game->levelSet->id) != 0) {
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+        if(!load_level_set(storage, expectedSet, game->levelSet, game->errorMsg)) {
+            handle_ivalid_set(game, storage, game->selectedSet, game->errorMsg);
+        }
+        furi_record_close(RECORD_STORAGE);
+    }
+    index_set(game);
+    recalc_score(game);
+}
+
+//-----------------------------------------------------------------------------
+
+const char* level_on_pos(Game* game, int pos) {
+    if(pos < ASSETS_LEVELS_COUNT) {
+        return assetLevels[pos];
+    } else {
+        int adjPos = pos - ASSETS_LEVELS_COUNT;
+        FURI_LOG_D(TAG, "Level for exra %d, %d", pos, adjPos);
+        if((game->levelList.ids != NULL) && (adjPos < game->levelList.count)) {
+            if(game->levelList.ids[adjPos] != NULL) {
+                return furi_string_get_cstr(game->levelList.ids[adjPos]);
+            } else {
+                return assetLevels[ASSETS_LEVELS_COUNT - 1];
+            }
+        } else {
+            return assetLevels[ASSETS_LEVELS_COUNT - 1];
+        }
+    }
+
+    return assetLevels[0];
+}
+
+//-----------------------------------------------------------------------------
+
+int level_count(Game* game) {
+    return ASSETS_LEVELS_COUNT + ((game->levelList.ids != NULL) ? game->levelList.count : 0);
+}
+
+//-----------------------------------------------------------------------------
+
+void start_game_at_level(Game* game, uint8_t levelNo) {
+    if(levelNo < game->levelSet->maxLevel) {
+        game->currentLevel = levelNo;
+        refresh_level(game);
+    } else {
+        game->mainMenuBtn = LEVELSET_BTN;
+        game->mainMenuMode = CUSTOM;
+
+        game->setPos = (game->setPos < game->setCount - 1) ? game->setPos + 1 : 0;
+        furi_string_set(game->selectedSet, level_on_pos(game, game->setPos));
+        load_gameset_if_needed(game, game->selectedSet);
+        game->selectedLevel = 0;
+
+        game->state = MAIN_MENU;
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void score_for_level(Game* g, uint8_t levelNo, char* buf, size_t max) {
+    if(g->levelSet->scores[levelNo].moves == 0) {
+        snprintf(buf, max, "???");
+    } else {
+        if(g->levelSet->scores[levelNo].moves == g->levelSet->pars[levelNo]) {
+            snprintf(buf, max, "par");
+        } else {
+            snprintf(
+                buf, max, "%+d", g->levelSet->scores[levelNo].moves - g->levelSet->pars[levelNo]);
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void refresh_level(Game* g) {
+    clear_board(&g->board);
+    clear_board(&g->boardUndo);
+    clear_board(&g->toAnimate);
+
+    furi_string_set(g->selectedSet, g->levelSet->id);
+    furi_string_set(g->continueSet, g->levelSet->id);
+
+    g->selectedLevel = g->currentLevel;
+    load_game_board(g);
+
+    map_movability(&g->board, &g->movables);
+    update_board_stats(&g->board, g->stats);
+    g->currentMovable = find_movable(&g->movables);
+    g->undoMovable = MOVABLE_NOT_FOUND;
+    g->gameMoves = 0;
+    g->state = SELECT_BRICK;
+
+    memset(g->parLabel, 0, PAR_LABEL_SIZE);
+    score_for_level(g, g->selectedLevel, g->parLabel, PAR_LABEL_SIZE);
+}
+
+//-----------------------------------------------------------------------------
+
+void level_finished(Game* g) {
+    g->hasContinue = true;
+    furi_string_set(g->selectedSet, g->levelSet->id);
+    furi_string_set(g->continueSet, g->levelSet->id);
+    g->continueLevel = g->currentLevel;
+
+    uint16_t moves = (uint16_t)g->gameMoves;
+    if((moves < g->levelSet->scores[g->currentLevel].moves) ||
+       (g->levelSet->scores[g->currentLevel].moves == 0)) {
+        g->levelSet->scores[g->currentLevel].moves = moves;
+    }
+
+    save_last_level(g->levelSet->id, g->currentLevel);
+    save_set_scores(g->levelSet->id, g->levelSet->scores);
+    recalc_score(g);
+}
+
+//-----------------------------------------------------------------------------
+
+void forget_continue(Game* game) {
+    game->hasContinue = false;
+    furi_string_set(game->selectedSet, assetLevels[0]);
+    furi_string_set(game->continueSet, assetLevels[0]);
+    game->selectedLevel = 0;
+    game->continueLevel = 0;
+    delete_progress(game->levelSet->scores);
+    recalc_score(game);
+}
+
+//-----------------------------------------------------------------------------
+
+void click_selected(Game* game) {
+    const uint8_t dir = movable_dir(&game->movables, game->currentMovable);
+    switch(dir) {
+    case MOVABLE_LEFT:
+    case MOVABLE_RIGHT:
+        start_move(game, dir);
+        break;
+    case MOVABLE_BOTH:
+        game->state = SELECT_DIRECTION;
+        break;
+    default:
+        break;
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void start_gravity(Game* g) {
+    uint8_t x, y;
+    bool change = false;
+
+    clear_board(&g->toAnimate);
+
+    // go through it bottom to top so as all the blocks tumble down on top of each other
+    for(y = (SIZE_Y - 2); y > 0; y--) {
+        for(x = (SIZE_X - 1); x > 0; x--) {
+            if((is_block(g->board[y][x])) && (g->board[y + 1][x] == EMPTY_TILE)) {
+                change = true;
+                g->toAnimate[y][x] = 1;
+            }
+        }
+    }
+
+    if(change) {
+        g->move.frameNo = 0;
+        g->move.delay = 5;
+        g->state = MOVE_GRAVITY;
+    } else {
+        g->state = SELECT_BRICK;
+        start_explosion(g);
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void stop_gravity(Game* g) {
+    uint8_t x, y;
+    for(y = 0; y < SIZE_Y - 1; y++) {
+        for(x = 0; x < SIZE_X; x++) {
+            if(g->toAnimate[y][x] == 1) {
+                g->board[y + 1][x] = g->board[y][x];
+                g->board[y][x] = EMPTY_TILE;
+            }
+        }
+    }
+
+    start_gravity(g);
+}
+
+//-----------------------------------------------------------------------------
+
+void start_explosion(Game* g) {
+    uint8_t x, y;
+    bool change = false;
+
+    clear_board(&g->toAnimate);
+
+    // go through it bottom to top so as all the blocks tumble down on top of each other
+    for(y = 0; y < SIZE_Y; y++) {
+        for(x = 0; x < SIZE_X; x++) {
+            if(is_block(g->board[y][x])) {
+                if(((y > 0) && (g->board[y][x] == g->board[y - 1][x])) ||
+                   ((x > 0) && (g->board[y][x] == g->board[y][x - 1])) ||
+                   ((y < SIZE_Y - 1) && (g->board[y][x] == g->board[y + 1][x])) ||
+                   ((x < SIZE_X - 1) && (g->board[y][x] == g->board[y][x + 1]))) {
+                    change = true;
+                    g->toAnimate[y][x] = 1;
+                }
+            }
+        }
+    }
+
+    if(change) {
+        g->move.frameNo = 0;
+        g->move.delay = 12;
+        g->state = EXPLODE;
+    } else {
+        g->state = SELECT_BRICK;
+        movement_stoped(g);
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+void stop_explosion(Game* g) {
+    uint8_t x, y;
+    for(y = 0; y < SIZE_Y - 1; y++) {
+        for(x = 0; x < SIZE_X; x++) {
+            if(g->toAnimate[y][x] == 1) {
+                g->board[y][x] = EMPTY_TILE;
+            }
+        }
+    }
+
+    start_gravity(g);
+}
+
+//-----------------------------------------------------------------------------
+
+void start_move(Game* g, uint8_t direction) {
+    if(!g->solutionMode) {
+        g->undoMovable = g->currentMovable;
+        copy_level(g->boardUndo, g->board);
+        g->gameMoves++;
+    }
+    g->move.dir = direction;
+    g->move.x = coord_x(g->currentMovable);
+    g->move.y = coord_y(g->currentMovable);
+    g->move.frameNo = 0;
+    if(!g->solutionMode) {
+        g->nextMovable =
+            coord_from((g->move.x + ((direction == MOVABLE_LEFT) ? -1 : 1)), g->move.y);
+    }
+    g->state = MOVE_SIDES;
+}
+
+//-----------------------------------------------------------------------------
+
+void stop_move(Game* g) {
+    uint8_t deltaX = ((g->move.dir & MOVABLE_LEFT) != 0) ? -1 : 1;
+    uint8_t tile = g->board[g->move.y][g->move.x];
+
+    g->board[g->move.y][g->move.x] = EMPTY_TILE;
+    g->board[g->move.y][cap_x(g->move.x + deltaX)] = tile;
+
+    start_gravity(g);
+}
+
+//-----------------------------------------------------------------------------
+
+void movement_stoped(Game* g) {
+    if(g->solutionMode) {
+        solution_next(g);
+    } else {
+        map_movability(&g->board, &g->movables);
+        update_board_stats(&g->board, g->stats);
+        g->currentMovable = g->nextMovable;
+        g->nextMovable = MOVABLE_NOT_FOUND;
+        if(!is_block(g->board[coord_y(g->currentMovable)][coord_x(g->currentMovable)])) {
+            find_movable_down(&g->movables, &g->currentMovable);
+        }
+        if(!is_block(g->board[coord_y(g->currentMovable)][coord_x(g->currentMovable)])) {
+            find_movable_right(&g->movables, &g->currentMovable);
+        }
+        if(!is_block(g->board[coord_y(g->currentMovable)][coord_x(g->currentMovable)])) {
+            g->currentMovable = MOVABLE_NOT_FOUND;
+        }
+
+        g->gameOverReason = is_game_over(&g->movables, g->stats);
+
+        if(g->gameOverReason > NOT_GAME_OVER) {
+            g->state = GAME_OVER;
+        } else if(is_level_finished(g->stats)) {
+            g->state = LEVEL_FINISHED;
+            level_finished(g);
+        } else {
+            g->state = SELECT_BRICK;
+        }
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+bool undo(Game* g) {
+    if(g->undoMovable != MOVABLE_NOT_FOUND) {
+        g->currentMovable = g->undoMovable;
+        g->undoMovable = MOVABLE_NOT_FOUND;
+        copy_level(g->board, g->boardUndo);
+        map_movability(&g->board, &g->movables);
+        update_board_stats(&g->board, g->stats);
+        g->gameMoves--;
+        g->state = SELECT_BRICK;
+        return true;
+    } else {
+        g->state = SELECT_BRICK;
+        return false;
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+uint8_t
+    movable_from_solution(Game* g, const char* solutionStr, uint8_t step, PlayGround* movables) {
+    const char solX = solutionStr[step * 2];
+    const char solY = solutionStr[step * 2 + 1];
+
+    int x, y;
+    uint8_t dir;
+
+    x = solX - 'a';
+    if(solX <= 'Z') {
+        dir = MOVABLE_LEFT;
+        x = solX - 'A';
+    }
+    y = solY - 'a';
+    if(solY <= 'Z') {
+        dir = MOVABLE_RIGHT;
+        y = solY - 'A';
+    }
+
+    if(x < 0 || x >= SIZE_X || y < 0 || y >= SIZE_Y) {
+        end_solution(g);
+        return 0;
+    }
+
+    clear_board(movables);
+    (*movables)[y][x] = dir;
+
+    return coord_from(x, y);
+}
+
+//-----------------------------------------------------------------------------
+
+void start_solution(Game* g) {
+    copy_level(g->boardBackup, g->board);
+
+    clear_board(&g->board);
+    load_game_board(g);
+
+    g->currentMovableBackup = g->currentMovable;
+    g->solutionStep = 0;
+    g->solutionTotal = furi_string_size(g->levelData->solution) / 2;
+    g->solutionMode = true;
+    if(solution_will_have_penalty(g)) {
+        g->levelSet->scores[g->currentLevel].spoiled = true;
+        save_set_scores(g->levelSet->id, g->levelSet->scores);
+        recalc_score(g);
+    }
+    solution_select(g);
+}
+
+//-----------------------------------------------------------------------------
+
+void end_solution(Game* g) {
+    g->state = SELECT_BRICK;
+    g->currentMovable = g->currentMovableBackup;
+    copy_level(g->board, g->boardBackup);
+    clear_board(&g->toAnimate);
+    map_movability(&g->board, &g->movables);
+    update_board_stats(&g->board, g->stats);
+    g->solutionMode = false;
+}
+
+//-----------------------------------------------------------------------------
+
+void solution_select(Game* g) {
+    g->currentMovable = movable_from_solution(
+        g, furi_string_get_cstr(g->levelData->solution), g->solutionStep, &g->movables);
+    g->move.frameNo = 35;
+    g->state = SOLUTION_SELECT;
+}
+
+//-----------------------------------------------------------------------------
+
+void solution_move(Game* g) {
+    const uint8_t dir = movable_dir(&g->movables, g->currentMovable);
+    start_move(g, dir);
+}
+
+//-----------------------------------------------------------------------------
+
+void solution_next(Game* g) {
+    if(g->solutionStep < g->solutionTotal - 1) {
+        g->solutionStep++;
+        solution_select(g);
+    } else {
+        end_solution(g);
+    }
+}
+
+//-----------------------------------------------------------------------------
+
+bool solution_will_have_penalty(Game* g) {
+    return (g->levelSet->scores[g->currentLevel].moves == 0) &&
+           (!g->levelSet->scores[g->currentLevel].spoiled);
+}

+ 174 - 0
non_catalog_apps/vexed/game.h

@@ -0,0 +1,174 @@
+#pragma once
+
+#include <furi.h>
+#include <gui/gui.h>
+
+#include "common.h"
+#include "load.h"
+#include "stats.h"
+
+//-----------------------------------------------------------------------------
+
+typedef struct {
+    uint8_t u;
+    uint8_t l;
+    uint8_t d;
+    uint8_t r;
+    uint8_t dl;
+    uint8_t ur;
+    uint8_t ul;
+    uint8_t dr;
+} Neighbors;
+
+typedef enum {
+    MODE_BTN = 0,
+    LEVELSET_BTN = 1,
+    LEVELNO_BTN = 2,
+} MenuButtons;
+
+typedef enum {
+    MAIN_MENU,
+    INTRO,
+    RESET_PROMPT,
+    INVALID_PROMPT,
+    ABOUT,
+    SELECT_BRICK,
+    SELECT_DIRECTION,
+    SOLUTION_SELECT,
+    MOVE_SIDES,
+    MOVE_GRAVITY,
+    EXPLODE,
+    PAUSED,
+    HISTOGRAM,
+    SOLUTION_PROMPT,
+    GAME_OVER,
+    LEVEL_FINISHED,
+} State;
+
+typedef enum {
+    NEW_GAME,
+    CONTINUE,
+    CUSTOM,
+} GameMode;
+
+typedef enum {
+    NOT_GAME_OVER = 0,
+    CANNOT_MOVE = 1,
+    BRICKS_LEFT = 2,
+} GameOver;
+
+typedef struct {
+    u_int32_t frameNo;
+    u_int32_t dir;
+    u_int32_t x;
+    u_int32_t y;
+    u_int32_t delay;
+} MoveInfo;
+
+typedef struct {
+    ViewPort* viewPort;
+    FuriMutex* mutex;
+    State state;
+
+    LevelSet* levelSet;
+    LevelData* levelData;
+
+    // score
+    uint8_t currentLevel;
+    unsigned int gameMoves;
+    int16_t score;
+    char parLabel[PAR_LABEL_SIZE];
+
+    // board
+    PlayGround board;
+    PlayGround boardUndo;
+    PlayGround toAnimate;
+    PlayGround movables;
+
+    // solution
+    PlayGround boardBackup;
+    uint8_t currentMovableBackup;
+    bool solutionMode;
+    uint8_t solutionStep;
+    uint8_t solutionTotal;
+
+    // board stats
+    Stats* stats;
+
+    // selections
+    uint8_t undoMovable;
+    uint8_t currentMovable;
+    uint8_t nextMovable;
+
+    // menus
+    uint8_t menuPausedPos;
+    MenuButtons mainMenuBtn;
+    GameMode mainMenuMode;
+    bool mainMenuInfo;
+    bool hasContinue;
+    FuriString* selectedSet;
+    uint8_t selectedLevel;
+    FuriString* continueSet;
+    uint8_t continueLevel;
+    uint8_t setPos;
+    uint8_t setCount;
+
+    // game state
+    GameOver gameOverReason;
+    MoveInfo move;
+
+    // extra levels
+    LevelList levelList;
+
+    FuriString* errorMsg;
+    BackGround bg;
+
+} Game;
+
+//-----------------------------------------------------------------------------
+
+Game* alloc_game_state(int* error);
+void free_game_state(Game* game);
+
+//-----------------------------------------------------------------------------
+
+void new_game(Game* game);
+GameOver is_game_over(PlayGround* mv, Stats* stats);
+bool is_level_finished(Stats* stats);
+Neighbors find_neighbors(PlayGround* pg, uint8_t x, uint8_t y);
+
+//-----------------------------------------------------------------------------
+
+const char* level_on_pos(Game* game, int pos);
+int level_count(Game* game);
+void handle_ivalid_set(Game* game, Storage* storage, FuriString* setId, FuriString* errorMsg);
+void initial_load_game(Game* game);
+void load_gameset_if_needed(Game* game, FuriString* expectedSet);
+void start_game_at_level(Game* game, uint8_t levelNo);
+void refresh_level(Game* g);
+void level_finished(Game* g);
+void forget_continue(Game* g);
+void score_for_level(Game* g, uint8_t levelNo, char* buf, size_t max);
+
+//-----------------------------------------------------------------------------
+
+void click_selected(Game* game);
+
+void start_gravity(Game* g);
+void stop_gravity(Game* g);
+void start_explosion(Game* g);
+void stop_explosion(Game* g);
+void start_move(Game* g, uint8_t direction);
+void stop_move(Game* g);
+
+void movement_stoped(Game* g);
+bool undo(Game* g);
+
+//-----------------------------------------------------------------------------
+
+void start_solution(Game* g);
+void end_solution(Game* g);
+void solution_select(Game* g);
+void solution_move(Game* g);
+void solution_next(Game* g);
+bool solution_will_have_penalty(Game* g);

+ 114 - 0
non_catalog_apps/vexed/game_vexed.c

@@ -0,0 +1,114 @@
+#include <furi.h>
+#include <furi_hal.h>
+#include <furi/core/string.h>
+
+#include <gui/gui.h>
+
+#include <input/input.h>
+#include <stdlib.h>
+
+#include "common.h"
+#include "game.h"
+#include "utils.h"
+#include "load.h"
+#include "move.h"
+#include "fonts.h"
+#include "ui.h"
+#include "draw.h"
+#include "events.h"
+
+//-----------------------------------------------------------------------------
+
+void game_tick(void* ctx) {
+    furi_assert(ctx);
+    const Game* game = ctx;
+    view_port_update(game->viewPort);
+}
+
+static void app_input_callback(InputEvent* input_event, void* ctx) {
+    furi_assert(ctx);
+    FuriMessageQueue* event_queue = ctx;
+    furi_message_queue_put(event_queue, input_event, FuriWaitForever);
+}
+
+static void app_draw_callback(Canvas* canvas, void* ctx) {
+    furi_assert(ctx);
+    Game* game = ctx;
+    furi_mutex_acquire(game->mutex, FuriWaitForever);
+    draw_app(canvas, game);
+    furi_mutex_release(game->mutex);
+}
+
+//-----------------------------------------------------------------------------
+
+int32_t game_vexed_app(void* p) {
+    UNUSED(p);
+    int error;
+    bool running = true;
+    bool paused = false;
+    InputEvent event;
+
+    Game* game = alloc_game_state(&error);
+    if(error > 0) {
+        return error;
+    }
+
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
+
+    // Configure view port
+    game->viewPort = view_port_alloc();
+    view_port_draw_callback_set(game->viewPort, app_draw_callback, game);
+    view_port_input_callback_set(game->viewPort, app_input_callback, event_queue);
+
+    // Register view port in GUI
+    Gui* gui = furi_record_open(RECORD_GUI);
+    gui_add_view_port(gui, game->viewPort, GuiLayerFullscreen);
+
+    // Create a timer. When non-paused, it trigers UI refresh
+    FuriTimer* timer = furi_timer_alloc(game_tick, FuriTimerTypePeriodic, game);
+    furi_timer_start(timer, furi_kernel_get_tick_frequency() / 20);
+
+    initial_load_game(game);
+
+    while(running) {
+        FuriStatus event_status = furi_message_queue_get(event_queue, &event, FuriWaitForever);
+
+        if(event_status == FuriStatusOk) {
+            furi_mutex_acquire(game->mutex, FuriWaitForever);
+
+            if(((event.type == InputTypeLong) && (event.key == InputKeyBack)) ||
+               ((game->state == ABOUT) && (event.key == InputKeyOk))) {
+                running = false;
+            } else {
+                events_for_game(&event, game);
+            }
+
+            bool shouldBePaused = is_state_pause(game->state);
+
+            if(paused != shouldBePaused) {
+                paused = shouldBePaused;
+                if(paused) {
+                    furi_timer_stop(timer);
+                    FURI_LOG_D(TAG, "PAUSE - timer stoped");
+                } else {
+                    furi_timer_start(timer, furi_kernel_get_tick_frequency() / 20);
+                    FURI_LOG_D(TAG, "UNPAUSE - timer started");
+                }
+            }
+
+            if(shouldBePaused) {
+                view_port_update(game->viewPort);
+            }
+            furi_mutex_release(game->mutex);
+        }
+    }
+
+    furi_timer_free(timer);
+    view_port_enabled_set(game->viewPort, false);
+    gui_remove_view_port(gui, game->viewPort);
+    furi_message_queue_free(event_queue);
+    free_game_state(game);
+    furi_record_close(RECORD_GUI);
+
+    return 0;
+}

BIN=BIN
non_catalog_apps/vexed/game_vexed.png


BIN=BIN
non_catalog_apps/vexed/images/ButtonLeft_4x7.png


BIN=BIN
non_catalog_apps/vexed/images/ButtonRight_4x7.png


BIN=BIN
non_catalog_apps/vexed/images/a.png


BIN=BIN
non_catalog_apps/vexed/images/alt_d.png


BIN=BIN
non_catalog_apps/vexed/images/arr_l.png


BIN=BIN
non_catalog_apps/vexed/images/arr_r.png


BIN=BIN
non_catalog_apps/vexed/images/b.png


BIN=BIN
non_catalog_apps/vexed/images/back_btn_10x8.png


BIN=BIN
non_catalog_apps/vexed/images/c.png


BIN=BIN
non_catalog_apps/vexed/images/d.png


BIN=BIN
non_catalog_apps/vexed/images/e.png


BIN=BIN
non_catalog_apps/vexed/images/f.png


BIN=BIN
non_catalog_apps/vexed/images/g.png


BIN=BIN
non_catalog_apps/vexed/images/h.png


BIN=BIN
non_catalog_apps/vexed/images/hint_1.png


BIN=BIN
non_catalog_apps/vexed/images/hint_2.png


BIN=BIN
non_catalog_apps/vexed/images/hint_3.png


BIN=BIN
non_catalog_apps/vexed/images/hint_4.png


BIN=BIN
non_catalog_apps/vexed/images/ico_check.png


BIN=BIN
non_catalog_apps/vexed/images/ico_hist.png


BIN=BIN
non_catalog_apps/vexed/images/ico_home.png


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio