Просмотр исходного кода

Merge branch 'flipperdevices:dev' into feature_wifi_marauder_app

0xchocolate 3 лет назад
Родитель
Сommit
7f00c3e66c
100 измененных файлов с 1694 добавлено и 3390 удалено
  1. 1 9
      .github/CODEOWNERS
  2. 0 11
      .github/actions/docker/action.yml
  3. 124 0
      .github/workflows/amap_analyse.yml
  4. 5 1
      .github/workflows/build.yml
  5. 1 0
      .github/workflows/check_submodules.yml
  6. 1 0
      .github/workflows/lint_c.yml
  7. 1 0
      .github/workflows/lint_python.yml
  8. 108 0
      .github/workflows/pvs_studio.yml
  9. 4 0
      .gitignore
  10. 22 0
      .pvsconfig
  11. 1 0
      .pvsoptions
  12. 1 37
      ReadMe.md
  13. 4 14
      applications/archive/helpers/archive_apps.c
  14. 19 9
      applications/archive/helpers/archive_browser.c
  15. 0 1
      applications/archive/helpers/archive_browser.h
  16. 10 23
      applications/archive/helpers/archive_favorites.c
  17. 3 6
      applications/archive/views/archive_browser_view.c
  18. 1 0
      applications/archive/views/archive_browser_view.h
  19. 1 1
      applications/bad_usb/views/bad_usb_view.h
  20. 3 3
      applications/bt/bt_debug_app/views/bt_carrier_test.c
  21. 5 2
      applications/bt/bt_hid_app/views/bt_hid_keynote.c
  22. 14 0
      applications/bt/bt_hid_app/views/bt_hid_media.c
  23. 19 8
      applications/bt/bt_hid_app/views/bt_hid_mouse.c
  24. 3 0
      applications/bt/bt_settings_app/bt_settings_app.c
  25. 1 2
      applications/cli/cli_i.h
  26. 4 0
      applications/desktop/helpers/slideshow.c
  27. 1 0
      applications/desktop/helpers/slideshow.h
  28. 8 7
      applications/desktop/views/desktop_view_debug.c
  29. 9 5
      applications/desktop/views/desktop_view_slideshow.c
  30. 2 2
      applications/dolphin/helpers/dolphin_state.c
  31. 5 0
      applications/gpio/views/gpio_test.c
  32. 13 0
      applications/gui/modules/menu.h
  33. 21 0
      applications/gui/modules/widget.h
  34. 8 0
      applications/gui/modules/widget_elements/widget_element_frame.c
  35. 245 0
      applications/gui/modules/widget_elements/widget_element_string.c
  36. 0 50
      applications/gui/scene_manager.c
  37. 0 21
      applications/lfrfid/helpers/decoder_analyzer.h
  38. 0 72
      applications/lfrfid/helpers/decoder_emmarin.cpp
  39. 0 21
      applications/lfrfid/helpers/decoder_emmarin.h
  40. 0 15
      applications/lfrfid/helpers/decoder_gpio_out.cpp
  41. 0 14
      applications/lfrfid/helpers/decoder_gpio_out.h
  42. 0 98
      applications/lfrfid/helpers/decoder_hid26.cpp
  43. 0 24
      applications/lfrfid/helpers/decoder_hid26.h
  44. 0 76
      applications/lfrfid/helpers/decoder_indala.cpp
  45. 0 25
      applications/lfrfid/helpers/decoder_indala.h
  46. 0 107
      applications/lfrfid/helpers/decoder_ioprox.cpp
  47. 0 26
      applications/lfrfid/helpers/decoder_ioprox.h
  48. 0 15
      applications/lfrfid/helpers/emmarin.h
  49. 0 24
      applications/lfrfid/helpers/encoder_emmarin.cpp
  50. 0 22
      applications/lfrfid/helpers/encoder_emmarin.h
  51. 0 27
      applications/lfrfid/helpers/encoder_generic.h
  52. 0 46
      applications/lfrfid/helpers/encoder_hid_h10301.cpp
  53. 0 26
      applications/lfrfid/helpers/encoder_hid_h10301.h
  54. 0 36
      applications/lfrfid/helpers/encoder_indala_40134.cpp
  55. 0 23
      applications/lfrfid/helpers/encoder_indala_40134.h
  56. 0 32
      applications/lfrfid/helpers/encoder_ioprox.cpp
  57. 0 25
      applications/lfrfid/helpers/encoder_ioprox.h
  58. 0 76
      applications/lfrfid/helpers/key_info.cpp
  59. 0 17
      applications/lfrfid/helpers/key_info.h
  60. 0 20
      applications/lfrfid/helpers/osc_fsk.cpp
  61. 0 30
      applications/lfrfid/helpers/osc_fsk.h
  62. 0 150
      applications/lfrfid/helpers/protocols/protocol_emmarin.cpp
  63. 0 22
      applications/lfrfid/helpers/protocols/protocol_emmarin.h
  64. 0 60
      applications/lfrfid/helpers/protocols/protocol_generic.h
  65. 0 238
      applications/lfrfid/helpers/protocols/protocol_hid_h10301.cpp
  66. 0 22
      applications/lfrfid/helpers/protocols/protocol_hid_h10301.h
  67. 0 237
      applications/lfrfid/helpers/protocols/protocol_indala_40134.cpp
  68. 0 22
      applications/lfrfid/helpers/protocols/protocol_indala_40134.h
  69. 0 193
      applications/lfrfid/helpers/protocols/protocol_ioprox.cpp
  70. 0 26
      applications/lfrfid/helpers/protocols/protocol_ioprox.h
  71. 0 95
      applications/lfrfid/helpers/pulse_joiner.cpp
  72. 0 36
      applications/lfrfid/helpers/pulse_joiner.h
  73. 0 65
      applications/lfrfid/helpers/rfid_key.cpp
  74. 0 27
      applications/lfrfid/helpers/rfid_key.h
  75. 0 175
      applications/lfrfid/helpers/rfid_reader.cpp
  76. 0 59
      applications/lfrfid/helpers/rfid_reader.h
  77. 0 56
      applications/lfrfid/helpers/rfid_timer_emulator.cpp
  78. 0 31
      applications/lfrfid/helpers/rfid_timer_emulator.h
  79. 0 136
      applications/lfrfid/helpers/rfid_worker.cpp
  80. 0 48
      applications/lfrfid/helpers/rfid_worker.h
  81. 0 183
      applications/lfrfid/helpers/rfid_writer.cpp
  82. 0 21
      applications/lfrfid/helpers/rfid_writer.h
  83. 0 50
      applications/lfrfid/helpers/state_sequencer.cpp
  84. 0 25
      applications/lfrfid/helpers/state_sequencer.h
  85. 45 63
      applications/lfrfid/lfrfid_app.cpp
  86. 42 9
      applications/lfrfid/lfrfid_app.h
  87. 575 0
      applications/lfrfid/lfrfid_cli.c
  88. 0 177
      applications/lfrfid/lfrfid_cli.cpp
  89. 11 36
      applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp
  90. 0 1
      applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.h
  91. 8 15
      applications/lfrfid/scene/lfrfid_app_scene_emulate.cpp
  92. 0 3
      applications/lfrfid/scene/lfrfid_app_scene_emulate.h
  93. 63 0
      applications/lfrfid/scene/lfrfid_app_scene_extra_actions.cpp
  94. 13 0
      applications/lfrfid/scene/lfrfid_app_scene_extra_actions.h
  95. 77 0
      applications/lfrfid/scene/lfrfid_app_scene_raw_info.cpp
  96. 12 0
      applications/lfrfid/scene/lfrfid_app_scene_raw_info.h
  97. 46 0
      applications/lfrfid/scene/lfrfid_app_scene_raw_name.cpp
  98. 12 0
      applications/lfrfid/scene/lfrfid_app_scene_raw_name.h
  99. 107 0
      applications/lfrfid/scene/lfrfid_app_scene_raw_read.cpp
  100. 15 0
      applications/lfrfid/scene/lfrfid_app_scene_raw_read.h

+ 1 - 9
.github/CODEOWNERS

@@ -17,7 +17,6 @@
 /applications/gui/ @skotopes @DrZlo13 @hedger
 /applications/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov
 /applications/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
-/applications/infrared_monitor/ @skotopes @DrZlo13 @hedger @gsurkov
 /applications/input/ @skotopes @DrZlo13 @hedger
 /applications/lfrfid/ @skotopes @DrZlo13 @hedger
 /applications/lfrfid_debug/ @skotopes @DrZlo13 @hedger
@@ -45,12 +44,8 @@
 # Debug tools and plugins
 /debug/ @skotopes @DrZlo13 @hedger
 
-# Docker
-/docker/ @skotopes @DrZlo13 @hedger @aprosvetova
-/docker-compose.yml @skotopes @DrZlo13 @hedger @aprosvetova
-
 # Documentation
-/documentation/ @skotopes @DrZlo13 @hedger @aprosvetova
+/documentation/ @skotopes @DrZlo13 @hedger @drunkbatya
 
 # Firmware targets
 /firmware/ @skotopes @DrZlo13 @hedger
@@ -84,8 +79,5 @@
 /lib/u8g2/ @skotopes @DrZlo13 @hedger
 /lib/update_util/ @skotopes @DrZlo13 @hedger
 
-# Make tools
-/make/ @skotopes @DrZlo13 @hedger @aprosvetova
-
 # Helper scripts
 /scripts/ @skotopes @DrZlo13 @hedger

+ 0 - 11
.github/actions/docker/action.yml

@@ -1,11 +0,0 @@
-name: 'Run in docker'
-inputs:
-  run:  # id of input
-    description: 'A command to run'
-    required: true
-    default: ''
-runs:
-  using: 'docker'
-  image: '../../../docker/Dockerfile'
-  args:
-    - ${{ inputs.run }}

+ 124 - 0
.github/workflows/amap_analyse.yml

@@ -0,0 +1,124 @@
+name: 'Analyze .map file with Amap'
+
+on:
+  push:
+    branches:
+      - dev
+      - "release*"
+    tags:
+      - '*'
+  pull_request:
+
+env:
+  TARGETS: f7
+
+jobs:
+  amap_analyse:
+    if: ${{ !github.event.pull_request.head.repo.fork }}
+    runs-on: [self-hosted,FlipperZeroMacShell]
+    timeout-minutes: 15
+    steps:
+      - name: 'Wait Build workflow'
+        uses: fountainhead/action-wait-for-check@v1.0.0
+        id: wait-for-build
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          checkName: 'main'
+          ref: ${{ github.event.pull_request.head.sha || github.sha }}
+          intervalSeconds: 20
+
+      - name: 'Check Build workflow status'
+        if: steps.wait-for-build.outputs.conclusion == 'failure'
+        run: |
+          exit 1
+
+      - name: 'Decontaminate previous build leftovers'
+        run: |
+          if [ -d .git ]; then
+            git submodule status \
+              || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1`
+          fi
+
+      - name: 'Checkout code'
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+          ref: ${{ github.event.pull_request.head.sha }}
+
+      - name: 'Generate prefixes by commit'
+        id: names
+        run: |
+          REF="${{github.ref}}"
+          COMMIT_HASH="$(git rev-parse HEAD)"
+          SHA="$(git rev-parse --short HEAD)"
+          COMMIT_MSG="${{github.event.head_commit.message}}"
+          if [[ ${{ github.event_name }} == 'pull_request' ]]; then
+            REF="${{github.head_ref}}"
+            COMMIT_HASH="$(git log -1 --pretty=oneline | awk '{print $1}')"
+            SHA="$(cut -c -8 <<< "$COMMIT_HASH")"
+            COMMIT_MSG="$(git log -1 --pretty=format:"%s")"
+            PULL_ID="${{github.event.pull_request.number}}"
+            PULL_NAME="${{github.event.pull_request.title}}"
+          fi
+          BRANCH_NAME=${REF#refs/*/}
+          SUFFIX=${BRANCH_NAME//\//_}-$(date +'%d%m%Y')-${SHA}
+          if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
+            SUFFIX=${BRANCH_NAME//\//_}
+          fi
+          echo "::set-output name=commit-hash::${COMMIT_HASH}"
+          echo "::set-output name=commit-msg::${COMMIT_MSG}"
+          echo "::set-output name=pull-id::${PULL_ID}"
+          echo "::set-output name=pull-name::${PULL_NAME}"
+          echo "::set-output name=branch-name::${BRANCH_NAME}"
+          echo "::set-output name=suffix::${SUFFIX}"
+
+      - name: 'Make artifacts directory'
+        run: |
+          rm -rf artifacts
+          mkdir artifacts
+
+      - name: 'Download build artifacts'
+        if: ${{ !github.event.pull_request.head.repo.fork }}
+        run: |
+          echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key;
+          chmod 600 ./deploy_key;
+          rsync -avzP \
+              -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \
+              ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${{steps.names.outputs.branch-name}}/" artifacts/;
+          rm ./deploy_key;
+
+      - name: 'Make .map file analyze'
+        run: |
+          cd artifacts/
+          /Applications/amap/Contents/MacOS/amap -f flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map
+
+      - name: 'Upload report to DB'
+        run: |
+          FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh
+          get_size()
+          {
+            SECTION="$1";
+            arm-none-eabi-size \
+              -A artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf \
+              | grep "^$SECTION" | awk '{print $2}'
+          }
+          export COMMIT_HASH="${{steps.names.outputs.commit-hash}}"
+          export COMMIT_MSG="${{steps.names.outputs.commit-msg}}"
+          export BRANCH_NAME="${{steps.names.outputs.branch-name}}"
+          export BSS_SIZE="$(get_size ".bss")"
+          export TEXT_SIZE="$(get_size ".text")"
+          export RODATA_SIZE="$(get_size ".rodata")"
+          export DATA_SIZE="$(get_size ".data")"
+          export FREE_FLASH_SIZE="$(get_size ".free_flash")"
+          if [[ ${{ github.event_name }} == 'pull_request' ]]; then
+            export PULL_ID="${{steps.names.outputs.pull-id}}"
+            export PULL_NAME="${{steps.names.outputs.pull-name}}"
+          fi
+          python3 -m pip install mariadb
+          python3 scripts/amap_mariadb_insert.py \
+            ${{ secrets.AMAP_MARIADB_USER }} \
+            ${{ secrets.AMAP_MARIADB_PASSWORD }} \
+            ${{ secrets.AMAP_MARIADB_HOST }} \
+            ${{ secrets.AMAP_MARIADB_PORT }} \
+            ${{ secrets.AMAP_MARIADB_DATABASE }} \
+            artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map.all

+ 5 - 1
.github/workflows/build.yml

@@ -93,7 +93,7 @@ jobs:
             rm -rf artifacts/${BUNDLE_NAME}
           done
 
-      - name: "Check for uncommited changes"
+      - name: "Check for uncommitted changes"
         run: |
           git diff --exit-code
 
@@ -108,6 +108,10 @@ jobs:
           FBT_TOOLCHAIN_PATH=/opt ./fbt copro_dist
           tar czpf artifacts/flipper-z-any-core2_firmware-${{steps.names.outputs.suffix}}.tgz -C assets core2_firmware
 
+      - name: 'Copy .map file'
+        run: |
+          cp build/f7-firmware-*/firmware.elf.map artifacts/flipper-z-f7-firmware-${{steps.names.outputs.suffix}}.elf.map
+
       - name: 'Upload artifacts to update server'
         if: ${{ !github.event.pull_request.head.repo.fork }}
         run: |

+ 1 - 0
.github/workflows/check_submodules.yml

@@ -25,6 +25,7 @@ jobs:
         uses: actions/checkout@v2
         with:
           fetch-depth: 0
+          ref: ${{ github.event.pull_request.head.sha }}
 
       - name: 'Check protobuf branch'
         run: |

+ 1 - 0
.github/workflows/lint_c.yml

@@ -28,6 +28,7 @@ jobs:
         uses: actions/checkout@v2
         with:
           fetch-depth: 0
+          ref: ${{ github.event.pull_request.head.sha }}
 
       - name: 'Check code formatting'
         id: syntax_check

+ 1 - 0
.github/workflows/lint_python.yml

@@ -25,6 +25,7 @@ jobs:
         uses: actions/checkout@v2
         with:
           fetch-depth: 0
+          ref: ${{ github.event.pull_request.head.sha }}
 
       - name: 'Check code formatting'
         run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint_py

+ 108 - 0
.github/workflows/pvs_studio.yml

@@ -0,0 +1,108 @@
+name: 'Static C/C++ analysis with PVS-Studio'
+
+on:
+  push:
+    branches:
+      - dev
+      - "release*"
+    tags:
+      - '*'
+  pull_request:
+
+env:
+  TARGETS: f7
+  DEFAULT_TARGET: f7
+
+jobs:
+  analyse_c_cpp:
+    if: ${{ !github.event.pull_request.head.repo.fork }}
+    runs-on: [self-hosted, FlipperZeroShell]
+    steps:
+      - name: 'Decontaminate previous build leftovers'
+        run: |
+          if [ -d .git ]
+          then
+            git submodule status \
+              || git checkout `git rev-list --max-parents=0 HEAD | tail -n 1`
+          fi
+
+      - name: 'Checkout code'
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+          ref: ${{ github.event.pull_request.head.sha }}
+
+      - name: 'Generate suffix and folder name'
+        id: names
+        run: |
+          REF=${{ github.ref }}
+          if [[ ${{ github.event_name }} == 'pull_request' ]]; then
+            REF=${{ github.head_ref }}
+          fi
+          BRANCH_OR_TAG=${REF#refs/*/}
+          SHA=$(git rev-parse --short HEAD)
+
+          if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
+            SUFFIX=${BRANCH_OR_TAG//\//_}
+          else
+            SUFFIX=${BRANCH_OR_TAG//\//_}-$(date +'%d%m%Y')-${SHA}
+          fi
+
+          echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV
+          echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV
+          echo "::set-output name=artifacts-path::${BRANCH_OR_TAG}"
+          echo "::set-output name=suffix::${SUFFIX}"
+          echo "::set-output name=short-hash::${SHA}"
+          echo "::set-output name=default-target::${DEFAULT_TARGET}"
+
+      - name: 'Make reports directory'
+        run: |
+          rm -rf reports/
+          mkdir reports
+
+      - name: 'Generate compile_comands.json'
+        run: |
+          FBT_TOOLCHAIN_PATH=/opt ./fbt COMPACT=1 version_json proto_ver icons firmware_cdb dolphin_internal dolphin_blocking
+
+      - name: 'Static code analysis'
+        run: |
+          FBT_TOOLCHAIN_PATH=/opt source scripts/toolchain/fbtenv.sh
+          pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }}
+          pvs-studio-analyzer analyze \
+              @.pvsoptions \
+              -j$(grep -c processor /proc/cpuinfo) \
+              -f build/f7-firmware-DC/compile_commands.json \
+              -o PVS-Studio.log
+
+      - name: 'Convert PVS-Studio output to html page'
+        run: plog-converter -a GA:1,2,3 -t fullhtml PVS-Studio.log -o reports/${{steps.names.outputs.default-target}}-${{steps.names.outputs.suffix}}
+
+      - name: 'Upload artifacts to update server'
+        if: ${{ !github.event.pull_request.head.repo.fork }}
+        run: |
+          echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key;
+          chmod 600 ./deploy_key;
+          rsync -avrzP --mkpath \
+              -e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \
+              reports/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:/home/data/firmware-pvs-studio-report/"${{steps.names.outputs.artifacts-path}}/";
+          rm ./deploy_key;
+
+      - name: 'Find Previous Comment'
+        if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request }}
+        uses: peter-evans/find-comment@v1
+        id: fc
+        with:
+          issue-number: ${{ github.event.pull_request.number }}
+          comment-author: 'github-actions[bot]'
+          body-includes: 'PVS-Studio report for commit'
+
+      - name: 'Create or update comment'
+        if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request}}
+        uses: peter-evans/create-or-update-comment@v1
+        with:
+          comment-id: ${{ steps.fc.outputs.comment-id }}
+          issue-number: ${{ github.event.pull_request.number }}
+          body: |
+            **PVS-Studio report for commit `${{steps.names.outputs.short-hash}}`:**
+            - [Report](https://update.flipperzero.one/builds/firmware-pvs-studio-report/${{steps.names.outputs.artifacts-path}}/${{steps.names.outputs.default-target}}-${{steps.names.outputs.suffix}}/index.html)
+          edit-mode: replace

+ 4 - 0
.gitignore

@@ -50,3 +50,7 @@ build/
 
 # openocd output file
 openocd.log
+
+# PVS Studio temporary files
+.PVS-Studio/
+PVS-Studio.log

+ 22 - 0
.pvsconfig

@@ -0,0 +1,22 @@
+# MLib macros we can't do much about.
+//-V:M_EACH:1048,1044
+//-V:ARRAY_DEF:760,747,568,776,729,712,654
+//-V:LIST_DEF:760,747,568,712,729,654,776
+//-V:BPTREE_DEF2:779,1086,557,773,512
+//-V:DICT_DEF2:779,524,776,760,1044,1001,729,590,568,747,685
+//-V:ALGO_DEF:1048,747,1044
+
+# Non-severe malloc/null pointer deref warnings
+//-V::522:2,3
+
+# Warning about headers with copyleft license
+//-V::1042
+
+# Potentially null argument warnings
+//-V:memset:575
+//-V:memcpy:575
+//-V:strcpy:575
+//-V:strchr:575
+
+# For loop warning on M_FOREACH
+//-V:for:1044

+ 1 - 0
.pvsoptions

@@ -0,0 +1 @@
+--rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e */arm-none-eabi/*

+ 1 - 37
ReadMe.md

@@ -61,29 +61,6 @@ One liner: `./fbt firmware_flash`
 
 3. Run `dfu-util -D full.dfu -a 0`
 
-# Build with Docker
-
-## Prerequisites
-
-1. Install [Docker Engine and Docker Compose](https://www.docker.com/get-started)
-2. Prepare the container:
-
- ```sh
- docker-compose up -d
- ```
-
-## Compile everything
-
-```sh
-docker-compose exec dev ./fbt
-```
-
-Check `dist/` for build outputs.
-
-Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device.
-
-If compilation fails, make sure all submodules are all initialized. Either clone with `--recursive` or use `git submodule update --init --recursive`.
-
 # Build on Linux/macOS
 
 Check out `documentation/fbt.md` for details on building and flashing firmware. 
@@ -97,19 +74,7 @@ brew bundle --verbose
 
 ## Linux Prerequisites
 
-### gcc-arm-none-eabi
-
-```sh
-toolchain="gcc-arm-none-eabi-10.3-2021.10"
-toolchain_package="$toolchain-$(uname -m)-linux"
-
-wget -P /opt "https://developer.arm.com/-/media/Files/downloads/gnu-rm/10.3-2021.10/$toolchain_package.tar.bz2"
-
-tar xjf /opt/$toolchain_package.tar.bz2 -C /opt
-rm /opt/$toolchain_package.tar.bz2
-
-for file in /opt/$toolchain/bin/* ; do ln -s "${file}" "/usr/bin/$(basename ${file})" ; done
-```
+The FBT tool handles everything, only `git` is required.
 
 ### Optional dependencies
 
@@ -157,7 +122,6 @@ Connect your device via ST-Link and run:
 - `assets`          - Assets used by applications and services
 - `furi`            - Furi Core: os level primitives and helpers
 - `debug`           - Debug tool: GDB-plugins, SVD-file and etc
-- `docker`          - Docker image sources (used for firmware build automation)
 - `documentation`   - Documentation generation system configs and input files
 - `firmware`        - Firmware source code
 - `lib`             - Our and 3rd party libraries, drivers and etc...

+ 4 - 14
applications/archive/helpers/archive_apps.c

@@ -29,23 +29,13 @@ bool archive_app_is_available(void* context, const char* path) {
 
     if(app == ArchiveAppTypeU2f) {
         bool file_exists = false;
-        Storage* fs_api = furi_record_open(RECORD_STORAGE);
-        File* file = storage_file_alloc(fs_api);
-
-        file_exists =
-            storage_file_open(file, ANY_PATH("u2f/key.u2f"), FSAM_READ, FSOM_OPEN_EXISTING);
-        if(file_exists) {
-            storage_file_close(file);
-            file_exists =
-                storage_file_open(file, ANY_PATH("u2f/cnt.u2f"), FSAM_READ, FSOM_OPEN_EXISTING);
-            if(file_exists) {
-                storage_file_close(file);
-            }
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+
+        if(storage_file_exists(storage, ANY_PATH("u2f/key.u2f"))) {
+            file_exists = storage_file_exists(storage, ANY_PATH("u2f/cnt.u2f"));
         }
 
-        storage_file_free(file);
         furi_record_close(RECORD_STORAGE);
-
         return file_exists;
     } else {
         return false;

+ 19 - 9
applications/archive/helpers/archive_browser.c

@@ -77,14 +77,24 @@ static void archive_long_load_cb(void* context) {
         });
 }
 
-void archive_file_browser_set_callbacks(ArchiveBrowserView* browser) {
+static void archive_file_browser_set_path(
+    ArchiveBrowserView* browser,
+    string_t path,
+    const char* filter_ext,
+    bool skip_assets) {
     furi_assert(browser);
-
-    file_browser_worker_set_callback_context(browser->worker, browser);
-    file_browser_worker_set_folder_callback(browser->worker, archive_folder_open_cb);
-    file_browser_worker_set_list_callback(browser->worker, archive_list_load_cb);
-    file_browser_worker_set_item_callback(browser->worker, archive_list_item_cb);
-    file_browser_worker_set_long_load_callback(browser->worker, archive_long_load_cb);
+    if(!browser->worker_running) {
+        browser->worker = file_browser_worker_alloc(path, filter_ext, skip_assets);
+        file_browser_worker_set_callback_context(browser->worker, browser);
+        file_browser_worker_set_folder_callback(browser->worker, archive_folder_open_cb);
+        file_browser_worker_set_list_callback(browser->worker, archive_list_load_cb);
+        file_browser_worker_set_item_callback(browser->worker, archive_list_item_cb);
+        file_browser_worker_set_long_load_callback(browser->worker, archive_long_load_cb);
+        browser->worker_running = true;
+    } else {
+        furi_assert(browser->worker);
+        file_browser_worker_set_config(browser->worker, path, filter_ext, skip_assets);
+    }
 }
 
 bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) {
@@ -438,8 +448,8 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) {
         tab = archive_get_tab(browser);
         if(archive_is_dir_exists(browser->path)) {
             bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true;
-            file_browser_worker_set_config(
-                browser->worker, browser->path, archive_get_tab_ext(tab), skip_assets);
+            archive_file_browser_set_path(
+                browser, browser->path, archive_get_tab_ext(tab), skip_assets);
             tab_empty = false; // Empty check will be performed later
         } else {
             tab_empty = true;

+ 0 - 1
applications/archive/helpers/archive_browser.h

@@ -87,4 +87,3 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key);
 void archive_enter_dir(ArchiveBrowserView* browser, string_t name);
 void archive_leave_dir(ArchiveBrowserView* browser);
 void archive_refresh_dir(ArchiveBrowserView* browser);
-void archive_file_browser_set_callbacks(ArchiveBrowserView* browser);

+ 10 - 23
applications/archive/helpers/archive_favorites.c

@@ -82,9 +82,8 @@ uint16_t archive_favorites_count(void* context) {
 static bool archive_favourites_rescan() {
     string_t buffer;
     string_init(buffer);
-    Storage* fs_api = furi_record_open(RECORD_STORAGE);
-    File* file = storage_file_alloc(fs_api);
-    File* fav_item_file = storage_file_alloc(fs_api);
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    File* file = storage_file_alloc(storage);
 
     bool result = storage_file_open(file, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
     if(result) {
@@ -101,13 +100,8 @@ static bool archive_favourites_rescan() {
                     archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer));
                 }
             } else {
-                bool file_exists = storage_file_open(
-                    fav_item_file, string_get_cstr(buffer), FSAM_READ, FSOM_OPEN_EXISTING);
-                if(file_exists) {
-                    storage_file_close(fav_item_file);
+                if(storage_file_exists(storage, string_get_cstr(buffer))) {
                     archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(buffer));
-                } else {
-                    storage_file_close(fav_item_file);
                 }
             }
         }
@@ -116,12 +110,11 @@ static bool archive_favourites_rescan() {
     string_clear(buffer);
 
     storage_file_close(file);
-    storage_common_remove(fs_api, ARCHIVE_FAV_PATH);
-    storage_common_rename(fs_api, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH);
-    storage_common_remove(fs_api, ARCHIVE_FAV_TEMP_PATH);
+    storage_common_remove(storage, ARCHIVE_FAV_PATH);
+    storage_common_rename(storage, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH);
+    storage_common_remove(storage, ARCHIVE_FAV_TEMP_PATH);
 
     storage_file_free(file);
-    storage_file_free(fav_item_file);
     furi_record_close(RECORD_STORAGE);
 
     return result;
@@ -131,9 +124,8 @@ bool archive_favorites_read(void* context) {
     furi_assert(context);
 
     ArchiveBrowserView* browser = context;
-    Storage* fs_api = furi_record_open(RECORD_STORAGE);
-    File* file = storage_file_alloc(fs_api);
-    File* fav_item_file = storage_file_alloc(fs_api);
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    File* file = storage_file_alloc(storage);
 
     string_t buffer;
     FileInfo file_info;
@@ -163,16 +155,12 @@ bool archive_favorites_read(void* context) {
                     need_refresh = true;
                 }
             } else {
-                bool file_exists = storage_file_open(
-                    fav_item_file, string_get_cstr(buffer), FSAM_READ, FSOM_OPEN_EXISTING);
-                if(file_exists) {
-                    storage_common_stat(fs_api, string_get_cstr(buffer), &file_info);
-                    storage_file_close(fav_item_file);
+                if(storage_file_exists(storage, string_get_cstr(buffer))) {
+                    storage_common_stat(storage, string_get_cstr(buffer), &file_info);
                     archive_add_file_item(
                         browser, (file_info.flags & FSF_DIRECTORY), string_get_cstr(buffer));
                     file_count++;
                 } else {
-                    storage_file_close(fav_item_file);
                     need_refresh = true;
                 }
             }
@@ -183,7 +171,6 @@ bool archive_favorites_read(void* context) {
     storage_file_close(file);
     string_clear(buffer);
     storage_file_free(file);
-    storage_file_free(fav_item_file);
     furi_record_close(RECORD_STORAGE);
 
     archive_set_item_count(browser, file_count);

+ 3 - 6
applications/archive/views/archive_browser_view.c

@@ -370,18 +370,15 @@ ArchiveBrowserView* browser_alloc() {
             return true;
         });
 
-    browser->worker = file_browser_worker_alloc(browser->path, "*", false);
-    archive_file_browser_set_callbacks(browser);
-
-    file_browser_worker_set_callback_context(browser->worker, browser);
-
     return browser;
 }
 
 void browser_free(ArchiveBrowserView* browser) {
     furi_assert(browser);
 
-    file_browser_worker_free(browser->worker);
+    if(browser->worker_running) {
+        file_browser_worker_free(browser->worker);
+    }
 
     with_view_model(
         browser->view, (ArchiveBrowserViewModel * model) {

+ 1 - 0
applications/archive/views/archive_browser_view.h

@@ -74,6 +74,7 @@ typedef enum {
 struct ArchiveBrowserView {
     View* view;
     BrowserWorker* worker;
+    bool worker_running;
     ArchiveBrowserViewCallback callback;
     void* context;
     string_t path;

+ 1 - 1
applications/bad_usb/views/bad_usb_view.h

@@ -3,7 +3,7 @@
 #include <applications/cli/cli.h>
 #include <lib/toolbox/args.h>
 
-#include "ble.h"
+#include <ble/ble.h>
 #include "bt_settings.h"
 #include "bt_service/bt.h"
 

+ 3 - 3
applications/bt/bt_debug_app/views/bt_carrier_test.c

@@ -89,8 +89,7 @@ BtHid* bt_hid_app_alloc() {
         app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app);
     submenu_add_item(
         app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app);
-    submenu_add_item(
-        app->submenu, "Media Player", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app);
+    submenu_add_item(app->submenu, "Media", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app);
     submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, bt_hid_submenu_callback, app);
     view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit);
     view_dispatcher_add_view(
@@ -134,7 +133,8 @@ BtHid* bt_hid_app_alloc() {
         app->view_dispatcher, BtHidViewMouse, bt_hid_mouse_get_view(app->bt_hid_mouse));
 
     // TODO switch to menu after Media is done
-    view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote);
+    app->view_id = BtHidViewSubmenu;
+    view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id);
 
     return app;
 }

+ 5 - 2
applications/bt/bt_hid_app/views/bt_hid_keynote.c

@@ -43,7 +43,10 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) {
     }
     canvas_set_font(canvas, FontPrimary);
     elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote");
+
+    canvas_draw_icon(canvas, 68, 2, &I_Pin_back_arrow_10x8);
     canvas_set_font(canvas, FontSecondary);
+    elements_multiline_text_aligned(canvas, 127, 3, AlignRight, AlignTop, "Hold to exit");
 
     // Up
     canvas_draw_icon(canvas, 21, 24, &I_Button_18x18);
@@ -97,8 +100,8 @@ static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) {
         elements_slightly_rounded_box(canvas, 66, 47, 60, 13);
         canvas_set_color(canvas, ColorWhite);
     }
-    canvas_draw_icon(canvas, 110, 49, &I_Ok_btn_9x9);
-    elements_multiline_text_aligned(canvas, 76, 56, AlignLeft, AlignBottom, "Back");
+    canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
+    elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back");
 }
 
 static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* event) {

+ 14 - 0
applications/bt/bt_hid_app/views/bt_hid_media.c

@@ -49,7 +49,9 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) {
 
     // Up
     if(model->up_pressed) {
+        canvas_set_bitmap_mode(canvas, 1);
         canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13);
+        canvas_set_bitmap_mode(canvas, 0);
         canvas_set_color(canvas, ColorWhite);
     }
     canvas_draw_icon(canvas, 96, 12, &I_Volup_8x6);
@@ -57,7 +59,9 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) {
 
     // Down
     if(model->down_pressed) {
+        canvas_set_bitmap_mode(canvas, 1);
         canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13);
+        canvas_set_bitmap_mode(canvas, 0);
         canvas_set_color(canvas, ColorWhite);
     }
     canvas_draw_icon(canvas, 96, 45, &I_Voldwn_6x6);
@@ -65,7 +69,9 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) {
 
     // Left
     if(model->left_pressed) {
+        canvas_set_bitmap_mode(canvas, 1);
         canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13);
+        canvas_set_bitmap_mode(canvas, 0);
         canvas_set_color(canvas, ColorWhite);
     }
     bt_hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft);
@@ -74,7 +80,9 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) {
 
     // Right
     if(model->right_pressed) {
+        canvas_set_bitmap_mode(canvas, 1);
         canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13);
+        canvas_set_bitmap_mode(canvas, 0);
         canvas_set_color(canvas, ColorWhite);
     }
     bt_hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight);
@@ -89,6 +97,12 @@ static void bt_hid_media_draw_callback(Canvas* canvas, void* context) {
     bt_hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight);
     canvas_draw_line(canvas, 100, 29, 100, 33);
     canvas_draw_line(canvas, 102, 29, 102, 33);
+    canvas_set_color(canvas, ColorBlack);
+
+    // Exit
+    canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
+    canvas_set_font(canvas, FontSecondary);
+    elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
 }
 
 static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* event) {

+ 19 - 8
applications/bt/bt_hid_app/views/bt_hid_mouse.c

@@ -36,7 +36,11 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) {
     canvas_set_font(canvas, FontSecondary);
 
     if(model->left_mouse_held == true) {
-        elements_multiline_text_aligned(canvas, 0, 60, AlignLeft, AlignBottom, "Selecting...");
+        elements_multiline_text_aligned(canvas, 0, 62, AlignLeft, AlignBottom, "Selecting...");
+    } else {
+        canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
+        canvas_set_font(canvas, FontSecondary);
+        elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
     }
 
     // Keypad circles
@@ -44,7 +48,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) {
 
     // Up
     if(model->up_pressed) {
+        canvas_set_bitmap_mode(canvas, 1);
         canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13);
+        canvas_set_bitmap_mode(canvas, 0);
         canvas_set_color(canvas, ColorWhite);
     }
     canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up7x9);
@@ -52,7 +58,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) {
 
     // Down
     if(model->down_pressed) {
+        canvas_set_bitmap_mode(canvas, 1);
         canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13);
+        canvas_set_bitmap_mode(canvas, 0);
         canvas_set_color(canvas, ColorWhite);
     }
     canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9);
@@ -60,7 +68,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) {
 
     // Left
     if(model->left_pressed) {
+        canvas_set_bitmap_mode(canvas, 1);
         canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13);
+        canvas_set_bitmap_mode(canvas, 0);
         canvas_set_color(canvas, ColorWhite);
     }
     canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7);
@@ -68,7 +78,9 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) {
 
     // Right
     if(model->right_pressed) {
+        canvas_set_bitmap_mode(canvas, 1);
         canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13);
+        canvas_set_bitmap_mode(canvas, 0);
         canvas_set_color(canvas, ColorWhite);
     }
     canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7);
@@ -76,18 +88,17 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) {
 
     // Ok
     if(model->left_mouse_pressed) {
-        canvas_draw_icon(canvas, 81, 25, &I_Pressed_Button_13x13);
-        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_icon(canvas, 81, 25, &I_Ok_btn_pressed_13x13);
+    } else {
+        canvas_draw_icon(canvas, 83, 27, &I_Left_mouse_icon_9x9);
     }
-    canvas_draw_icon(canvas, 83, 27, &I_Ok_btn_9x9);
-    canvas_set_color(canvas, ColorBlack);
 
     // Back
     if(model->right_mouse_pressed) {
-        canvas_draw_icon(canvas, 108, 48, &I_Pressed_Button_13x13);
-        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_icon(canvas, 108, 48, &I_Ok_btn_pressed_13x13);
+    } else {
+        canvas_draw_icon(canvas, 110, 50, &I_Right_mouse_icon_9x9);
     }
-    canvas_draw_icon(canvas, 110, 50, &I_Ok_btn_9x9);
 }
 
 static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) {

+ 3 - 0
applications/bt/bt_settings_app/bt_settings_app.c

@@ -281,6 +281,9 @@ void cli_command_free(Cli* cli, string_t args, void* context) {
     printf("Total heap size: %d\r\n", memmgr_get_total_heap());
     printf("Minimum heap size: %d\r\n", memmgr_get_minimum_free_heap());
     printf("Maximum heap block: %d\r\n", memmgr_heap_get_max_free_block());
+
+    printf("Pool free: %d\r\n", memmgr_pool_get_free());
+    printf("Maximum pool block: %d\r\n", memmgr_pool_get_max_block());
 }
 
 void cli_command_free_blocks(Cli* cli, string_t args, void* context) {

+ 1 - 2
applications/cli/cli_i.h

@@ -220,8 +220,7 @@ static bool animation_manager_check_blocking(AnimationManager* animation_manager
             furi_assert(blocking_animation);
             animation_manager->sd_shown_sd_ok = true;
         } else if(!animation_manager->sd_shown_no_db) {
-            bool db_exists = storage_common_stat(storage, EXT_PATH("Manifest"), NULL) == FSE_OK;
-            if(!db_exists) {
+            if(!storage_file_exists(storage, EXT_PATH("Manifest"))) {
                 blocking_animation = animation_storage_find_animation(NO_DB_ANIMATION_NAME);
                 furi_assert(blocking_animation);
                 animation_manager->sd_shown_no_db = true;

+ 4 - 0
applications/desktop/helpers/slideshow.c

@@ -94,6 +94,10 @@ bool slideshow_is_loaded(Slideshow* slideshow) {
     return slideshow->loaded;
 }
 
+bool slideshow_is_one_page(Slideshow* slideshow) {
+    return slideshow->loaded && (slideshow->icon.frame_count == 1);
+}
+
 bool slideshow_advance(Slideshow* slideshow) {
     uint8_t next_frame = slideshow->current_frame + 1;
     if(next_frame < slideshow->icon.frame_count) {

+ 1 - 0
applications/desktop/helpers/slideshow.h

@@ -9,6 +9,7 @@ Slideshow* slideshow_alloc();
 void slideshow_free(Slideshow* slideshow);
 bool slideshow_load(Slideshow* slideshow, const char* fspath);
 bool slideshow_is_loaded(Slideshow* slideshow);
+bool slideshow_is_one_page(Slideshow* slideshow);
 void slideshow_goback(Slideshow* slideshow);
 bool slideshow_advance(Slideshow* slideshow);
 void slideshow_draw(Slideshow* slideshow, Canvas* canvas, uint8_t x, uint8_t y);

+ 8 - 7
applications/desktop/views/desktop_view_debug.c

@@ -23,11 +23,12 @@ void desktop_debug_render(Canvas* canvas, void* model) {
     const Version* ver;
     char buffer[64];
 
-    static const char* headers[] = {"FW Version Info:", "Dolphin Info:"};
+    static const char* headers[] = {"Device Info:", "Dolphin Info:"};
 
     canvas_set_color(canvas, ColorBlack);
     canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str(canvas, 2, 9 + STATUS_BAR_Y_SHIFT, headers[m->screen]);
+    canvas_draw_str_aligned(
+        canvas, 64, 1 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignTop, headers[m->screen]);
     canvas_set_font(canvas, FontSecondary);
 
     if(m->screen != DesktopViewStatsMeta) {
@@ -44,7 +45,7 @@ void desktop_debug_render(Canvas* canvas, void* model) {
             furi_hal_version_get_hw_region_name(),
             furi_hal_region_get_name(),
             my_name ? my_name : "Unknown");
-        canvas_draw_str(canvas, 5, 19 + STATUS_BAR_Y_SHIFT, buffer);
+        canvas_draw_str(canvas, 0, 19 + STATUS_BAR_Y_SHIFT, buffer);
 
         ver = furi_hal_version_get_firmware_version();
         const BleGlueC2Info* c2_ver = NULL;
@@ -52,7 +53,7 @@ void desktop_debug_render(Canvas* canvas, void* model) {
         c2_ver = ble_glue_get_c2_info();
 #endif
         if(!ver) {
-            canvas_draw_str(canvas, 5, 29 + STATUS_BAR_Y_SHIFT, "No info");
+            canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, "No info");
             return;
         }
 
@@ -62,7 +63,7 @@ void desktop_debug_render(Canvas* canvas, void* model) {
             "%s [%s]",
             version_get_version(ver),
             version_get_builddate(ver));
-        canvas_draw_str(canvas, 5, 28 + STATUS_BAR_Y_SHIFT, buffer);
+        canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, buffer);
 
         snprintf(
             buffer,
@@ -72,11 +73,11 @@ void desktop_debug_render(Canvas* canvas, void* model) {
             version_get_githash(ver),
             version_get_gitbranchnum(ver),
             c2_ver ? c2_ver->StackTypeString : "<none>");
-        canvas_draw_str(canvas, 5, 39 + STATUS_BAR_Y_SHIFT, buffer);
+        canvas_draw_str(canvas, 0, 40 + STATUS_BAR_Y_SHIFT, buffer);
 
         snprintf(
             buffer, sizeof(buffer), "[%d] %s", version_get_target(ver), version_get_gitbranch(ver));
-        canvas_draw_str(canvas, 5, 50 + STATUS_BAR_Y_SHIFT, buffer);
+        canvas_draw_str(canvas, 0, 50 + STATUS_BAR_Y_SHIFT, buffer);
 
     } else {
         Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);

+ 9 - 5
applications/desktop/views/desktop_view_slideshow.c

@@ -35,8 +35,9 @@ static bool desktop_view_slideshow_input(InputEvent* event, void* context) {
     furi_assert(event);
     DesktopSlideshowView* instance = context;
 
+    DesktopSlideshowViewModel* model = view_get_model(instance->view);
+    bool update_view = false;
     if(event->type == InputTypeShort) {
-        DesktopSlideshowViewModel* model = view_get_model(instance->view);
         bool end_slideshow = false;
         switch(event->key) {
         case InputKeyLeft:
@@ -54,15 +55,18 @@ static bool desktop_view_slideshow_input(InputEvent* event, void* context) {
         if(end_slideshow) {
             instance->callback(DesktopSlideshowCompleted, instance->context);
         }
-        view_commit_model(instance->view, true);
+        update_view = true;
     } else if(event->key == InputKeyOk) {
         if(event->type == InputTypePress) {
             furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_SHORT);
         } else if(event->type == InputTypeRelease) {
             furi_timer_stop(instance->timer);
-            furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_LONG);
+            if(!slideshow_is_one_page(model->slideshow)) {
+                furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_LONG);
+            }
         }
     }
+    view_commit_model(instance->view, update_view);
 
     return true;
 }
@@ -79,12 +83,12 @@ static void desktop_view_slideshow_enter(void* context) {
     instance->timer =
         furi_timer_alloc(desktop_first_start_timer_callback, FuriTimerTypeOnce, instance);
 
-    furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_LONG);
-
     DesktopSlideshowViewModel* model = view_get_model(instance->view);
     model->slideshow = slideshow_alloc();
     if(!slideshow_load(model->slideshow, SLIDESHOW_FS_PATH)) {
         instance->callback(DesktopSlideshowCompleted, instance->context);
+    } else if(!slideshow_is_one_page(model->slideshow)) {
+        furi_timer_start(instance->timer, DESKTOP_SLIDESHOW_POWEROFF_LONG);
     }
     view_commit_model(instance->view, false);
 }

+ 2 - 2
applications/dolphin/helpers/dolphin_state.c

@@ -14,8 +14,8 @@
 #define DOLPHIN_STATE_PATH INT_PATH(DOLPHIN_STATE_FILE_NAME)
 #define DOLPHIN_STATE_HEADER_MAGIC 0xD0
 #define DOLPHIN_STATE_HEADER_VERSION 0x01
-#define LEVEL2_THRESHOLD 735
-#define LEVEL3_THRESHOLD 2940
+#define LEVEL2_THRESHOLD 300
+#define LEVEL3_THRESHOLD 1800
 #define BUTTHURT_MAX 14
 #define BUTTHURT_MIN 0
 

+ 5 - 0
applications/gpio/views/gpio_test.c

@@ -99,6 +99,11 @@ static bool browser_folder_check_and_switch(string_t path) {
     FileInfo file_info;
     Storage* storage = furi_record_open(RECORD_STORAGE);
     bool is_root = false;
+
+    if(string_search_rchar(path, '/') == 0) {
+        is_root = true;
+    }
+
     while(1) {
         // Check if folder is existing and navigate back if not
         if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) {

+ 13 - 0
applications/gui/modules/menu.h

@@ -162,6 +162,19 @@ void widget_add_text_box_element(
     widget_add_element(widget, text_box_element);
 }
 
+void widget_add_text_scroll_element(
+    Widget* widget,
+    uint8_t x,
+    uint8_t y,
+    uint8_t width,
+    uint8_t height,
+    const char* text) {
+    furi_assert(widget);
+    WidgetElement* text_scroll_element =
+        widget_element_text_scroll_create(x, y, width, height, text);
+    widget_add_element(widget, text_scroll_element);
+}
+
 void widget_add_button_element(
     Widget* widget,
     GuiButtonType button_type,

+ 21 - 0
applications/gui/modules/widget.h

@@ -105,6 +105,27 @@ void widget_add_text_box_element(
     const char* text,
     bool strip_to_dots);
 
+/** Add Text Scroll Element
+ *
+ * @param      widget           Widget instance
+ * @param      x                x coordinate
+ * @param      y                y coordinate
+ * @param      width            width to fit text
+ * @param      height           height to fit text
+ * @param[in]  text             Formatted text. Default format: align left, Secondary font.
+ *                              The following formats are available:
+ *                               "\e#Bold text" - sets bold font before until next '\n' symbol
+ *                               "\ecCenter-aligned text" - sets center horizontal align until the next '\n' symbol
+ *                               "\erRight-aligned text" - sets right horizontal align until the next '\n' symbol
+ */
+void widget_add_text_scroll_element(
+    Widget* widget,
+    uint8_t x,
+    uint8_t y,
+    uint8_t width,
+    uint8_t height,
+    const char* text);
+
 /** Add Button Element
  *
  * @param      widget       Widget instance

+ 8 - 0
applications/gui/modules/widget_elements/widget_element_frame.c

@@ -29,6 +29,7 @@ struct WidgetElement {
 
     // generic model holder
     void* model;
+    FuriMutex* model_mutex;
 
     // pointer to widget that hold our element
     Widget* parent;
@@ -80,3 +81,10 @@ WidgetElement* widget_element_frame_create(
     uint8_t width,
     uint8_t height,
     uint8_t radius);
+
+WidgetElement* widget_element_text_scroll_create(
+    uint8_t x,
+    uint8_t y,
+    uint8_t width,
+    uint8_t height,
+    const char* text);

+ 245 - 0
applications/gui/modules/widget_elements/widget_element_string.c

@@ -0,0 +1,245 @@
+#include "widget_element_i.h"
+#include <m-string.h>
+#include <gui/elements.h>
+#include <m-array.h>
+
+#define WIDGET_ELEMENT_TEXT_SCROLL_BAR_OFFSET (4)
+
+typedef struct {
+    Font font;
+    Align horizontal;
+    string_t text;
+} TextScrollLineArray;
+
+ARRAY_DEF(TextScrollLineArray, TextScrollLineArray, M_POD_OPLIST)
+
+typedef struct {
+    TextScrollLineArray_t line_array;
+    uint8_t x;
+    uint8_t y;
+    uint8_t width;
+    uint8_t height;
+    string_t text;
+    uint8_t scroll_pos_total;
+    uint8_t scroll_pos_current;
+    bool text_formatted;
+} WidgetElementTextScrollModel;
+
+static bool
+    widget_element_text_scroll_process_ctrl_symbols(TextScrollLineArray* line, string_t text) {
+    bool processed = false;
+
+    do {
+        if(string_get_char(text, 0) != '\e') break;
+        char ctrl_symbol = string_get_char(text, 1);
+        if(ctrl_symbol == 'c') {
+            line->horizontal = AlignCenter;
+        } else if(ctrl_symbol == 'r') {
+            line->horizontal = AlignRight;
+        } else if(ctrl_symbol == '#') {
+            line->font = FontPrimary;
+        }
+        string_right(text, 2);
+        processed = true;
+    } while(false);
+
+    return processed;
+}
+
+void widget_element_text_scroll_add_line(WidgetElement* element, TextScrollLineArray* line) {
+    WidgetElementTextScrollModel* model = element->model;
+    TextScrollLineArray new_line;
+    new_line.font = line->font;
+    new_line.horizontal = line->horizontal;
+    string_init_set(new_line.text, line->text);
+    TextScrollLineArray_push_back(model->line_array, new_line);
+}
+
+static void widget_element_text_scroll_fill_lines(Canvas* canvas, WidgetElement* element) {
+    WidgetElementTextScrollModel* model = element->model;
+    TextScrollLineArray line_tmp;
+    bool all_text_processed = false;
+    string_init(line_tmp.text);
+    bool reached_new_line = true;
+    uint16_t total_height = 0;
+
+    while(!all_text_processed) {
+        if(reached_new_line) {
+            // Set default line properties
+            line_tmp.font = FontSecondary;
+            line_tmp.horizontal = AlignLeft;
+            string_reset(line_tmp.text);
+            // Process control symbols
+            while(widget_element_text_scroll_process_ctrl_symbols(&line_tmp, model->text))
+                ;
+        }
+        // Set canvas font
+        canvas_set_font(canvas, line_tmp.font);
+        CanvasFontParameters* params = canvas_get_font_params(canvas, line_tmp.font);
+        total_height += params->height;
+        if(total_height > model->height) {
+            model->scroll_pos_total++;
+        }
+
+        uint8_t line_width = 0;
+        uint16_t char_i = 0;
+        while(true) {
+            char next_char = string_get_char(model->text, char_i++);
+            if(next_char == '\0') {
+                string_push_back(line_tmp.text, '\0');
+                widget_element_text_scroll_add_line(element, &line_tmp);
+                total_height += params->leading_default - params->height;
+                all_text_processed = true;
+                break;
+            } else if(next_char == '\n') {
+                string_push_back(line_tmp.text, '\0');
+                widget_element_text_scroll_add_line(element, &line_tmp);
+                string_right(model->text, char_i);
+                total_height += params->leading_default - params->height;
+                reached_new_line = true;
+                break;
+            } else {
+                line_width += canvas_glyph_width(canvas, next_char);
+                if(line_width > model->width) {
+                    string_push_back(line_tmp.text, '\0');
+                    widget_element_text_scroll_add_line(element, &line_tmp);
+                    string_right(model->text, char_i - 1);
+                    string_reset(line_tmp.text);
+                    total_height += params->leading_default - params->height;
+                    reached_new_line = false;
+                    break;
+                } else {
+                    string_push_back(line_tmp.text, next_char);
+                }
+            }
+        }
+    }
+
+    string_clear(line_tmp.text);
+}
+
+static void widget_element_text_scroll_draw(Canvas* canvas, WidgetElement* element) {
+    furi_assert(canvas);
+    furi_assert(element);
+
+    furi_mutex_acquire(element->model_mutex, FuriWaitForever);
+
+    WidgetElementTextScrollModel* model = element->model;
+    if(!model->text_formatted) {
+        widget_element_text_scroll_fill_lines(canvas, element);
+        model->text_formatted = true;
+    }
+
+    uint8_t y = model->y;
+    uint8_t x = model->x;
+    uint16_t curr_line = 0;
+    if(TextScrollLineArray_size(model->line_array)) {
+        TextScrollLineArray_it_t it;
+        for(TextScrollLineArray_it(it, model->line_array); !TextScrollLineArray_end_p(it);
+            TextScrollLineArray_next(it), curr_line++) {
+            if(curr_line < model->scroll_pos_current) continue;
+            TextScrollLineArray* line = TextScrollLineArray_ref(it);
+            CanvasFontParameters* params = canvas_get_font_params(canvas, line->font);
+            if(y + params->descender > model->y + model->height) break;
+            canvas_set_font(canvas, line->font);
+            if(line->horizontal == AlignLeft) {
+                x = model->x;
+            } else if(line->horizontal == AlignCenter) {
+                x = (model->x + model->width) / 2;
+            } else if(line->horizontal == AlignRight) {
+                x = model->x + model->width;
+            }
+            canvas_draw_str_aligned(
+                canvas, x, y, line->horizontal, AlignTop, string_get_cstr(line->text));
+            y += params->leading_default;
+        }
+        // Draw scroll bar
+        if(model->scroll_pos_total > 1) {
+            elements_scrollbar_pos(
+                canvas,
+                model->x + model->width + WIDGET_ELEMENT_TEXT_SCROLL_BAR_OFFSET,
+                model->y,
+                model->height,
+                model->scroll_pos_current,
+                model->scroll_pos_total);
+        }
+    }
+
+    furi_mutex_release(element->model_mutex);
+}
+
+static bool widget_element_text_scroll_input(InputEvent* event, WidgetElement* element) {
+    furi_assert(event);
+    furi_assert(element);
+
+    furi_mutex_acquire(element->model_mutex, FuriWaitForever);
+
+    WidgetElementTextScrollModel* model = element->model;
+    bool consumed = false;
+
+    if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
+        if(event->key == InputKeyUp) {
+            if(model->scroll_pos_current > 0) {
+                model->scroll_pos_current--;
+            }
+            consumed = true;
+        } else if(event->key == InputKeyDown) {
+            if((model->scroll_pos_total > 1) &&
+               (model->scroll_pos_current < model->scroll_pos_total - 1)) {
+                model->scroll_pos_current++;
+            }
+            consumed = true;
+        }
+    }
+
+    furi_mutex_release(element->model_mutex);
+
+    return consumed;
+}
+
+static void widget_element_text_scroll_free(WidgetElement* text_scroll) {
+    furi_assert(text_scroll);
+
+    WidgetElementTextScrollModel* model = text_scroll->model;
+    TextScrollLineArray_it_t it;
+    for(TextScrollLineArray_it(it, model->line_array); !TextScrollLineArray_end_p(it);
+        TextScrollLineArray_next(it)) {
+        TextScrollLineArray* line = TextScrollLineArray_ref(it);
+        string_clear(line->text);
+    }
+    TextScrollLineArray_clear(model->line_array);
+    string_clear(model->text);
+    free(text_scroll->model);
+    furi_mutex_free(text_scroll->model_mutex);
+    free(text_scroll);
+}
+
+WidgetElement* widget_element_text_scroll_create(
+    uint8_t x,
+    uint8_t y,
+    uint8_t width,
+    uint8_t height,
+    const char* text) {
+    furi_assert(text);
+
+    // Allocate and init model
+    WidgetElementTextScrollModel* model = malloc(sizeof(WidgetElementTextScrollModel));
+    model->x = x;
+    model->y = y;
+    model->width = width - WIDGET_ELEMENT_TEXT_SCROLL_BAR_OFFSET;
+    model->height = height;
+    model->scroll_pos_current = 0;
+    model->scroll_pos_total = 1;
+    TextScrollLineArray_init(model->line_array);
+    string_init_set_str(model->text, text);
+
+    WidgetElement* text_scroll = malloc(sizeof(WidgetElement));
+    text_scroll->parent = NULL;
+    text_scroll->draw = widget_element_text_scroll_draw;
+    text_scroll->input = widget_element_text_scroll_input;
+    text_scroll->free = widget_element_text_scroll_free;
+    text_scroll->model = model;
+    text_scroll->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+
+    return text_scroll;
+}

+ 0 - 50
applications/gui/scene_manager.c

@@ -1,50 +0,0 @@
-#include "decoder_analyzer.h"
-#include <furi.h>
-#include <furi_hal.h>
-
-// FIXME: unused args?
-bool DecoderAnalyzer::read(uint8_t* /* _data */, uint8_t /* _data_size */) {
-    bool result = false;
-
-    if(ready) {
-        result = true;
-
-        for(size_t i = 0; i < data_size; i++) {
-            printf("%lu ", data[i]);
-            if((i + 1) % 8 == 0) printf("\r\n");
-        }
-        printf("\r\n--------\r\n");
-
-        ready = false;
-    }
-
-    return result;
-}
-
-void DecoderAnalyzer::process_front(bool polarity, uint32_t time) {
-    UNUSED(polarity);
-    if(ready) return;
-
-    data[data_index] = time;
-
-    if(data_index < data_size) {
-        data_index++;
-    } else {
-        data_index = 0;
-        ready = true;
-    }
-}
-
-DecoderAnalyzer::DecoderAnalyzer() {
-    data = reinterpret_cast<uint32_t*>(calloc(data_size, sizeof(uint32_t)));
-    furi_check(data);
-    data_index = 0;
-    ready = false;
-}
-
-DecoderAnalyzer::~DecoderAnalyzer() {
-    free(data);
-}
-
-void DecoderAnalyzer::reset_state() {
-}

+ 0 - 21
applications/lfrfid/helpers/decoder_analyzer.h

@@ -1,21 +0,0 @@
-#pragma once
-#include <stdint.h>
-#include <atomic>
-
-class DecoderAnalyzer {
-public:
-    bool read(uint8_t* data, uint8_t data_size);
-    void process_front(bool polarity, uint32_t time);
-
-    DecoderAnalyzer();
-    ~DecoderAnalyzer();
-
-private:
-    void reset_state();
-
-    std::atomic<bool> ready;
-
-    static const uint32_t data_size = 2048;
-    uint32_t data_index = 0;
-    uint32_t* data;
-};

+ 0 - 72
applications/lfrfid/helpers/decoder_emmarin.cpp

@@ -1,72 +0,0 @@
-#include "emmarin.h"
-#include "decoder_emmarin.h"
-#include <furi.h>
-#include <furi_hal.h>
-
-constexpr uint32_t clocks_in_us = 64;
-constexpr uint32_t short_time = 255 * clocks_in_us;
-constexpr uint32_t long_time = 510 * clocks_in_us;
-constexpr uint32_t jitter_time = 100 * clocks_in_us;
-
-constexpr uint32_t short_time_low = short_time - jitter_time;
-constexpr uint32_t short_time_high = short_time + jitter_time;
-constexpr uint32_t long_time_low = long_time - jitter_time;
-constexpr uint32_t long_time_high = long_time + jitter_time;
-
-void DecoderEMMarin::reset_state() {
-    ready = false;
-    read_data = 0;
-    manchester_advance(
-        manchester_saved_state, ManchesterEventReset, &manchester_saved_state, nullptr);
-}
-
-bool DecoderEMMarin::read(uint8_t* data, uint8_t data_size) {
-    bool result = false;
-
-    if(ready) {
-        result = true;
-        em_marin.decode(
-            reinterpret_cast<const uint8_t*>(&read_data), sizeof(uint64_t), data, data_size);
-        ready = false;
-    }
-
-    return result;
-}
-
-void DecoderEMMarin::process_front(bool polarity, uint32_t time) {
-    if(ready) return;
-    if(time < short_time_low) return;
-
-    ManchesterEvent event = ManchesterEventReset;
-
-    if(time > short_time_low && time < short_time_high) {
-        if(polarity) {
-            event = ManchesterEventShortHigh;
-        } else {
-            event = ManchesterEventShortLow;
-        }
-    } else if(time > long_time_low && time < long_time_high) {
-        if(polarity) {
-            event = ManchesterEventLongHigh;
-        } else {
-            event = ManchesterEventLongLow;
-        }
-    }
-
-    if(event != ManchesterEventReset) {
-        bool data;
-        bool data_ok =
-            manchester_advance(manchester_saved_state, event, &manchester_saved_state, &data);
-
-        if(data_ok) {
-            read_data = (read_data << 1) | data;
-
-            ready = em_marin.can_be_decoded(
-                reinterpret_cast<const uint8_t*>(&read_data), sizeof(uint64_t));
-        }
-    }
-}
-
-DecoderEMMarin::DecoderEMMarin() {
-    reset_state();
-}

+ 0 - 21
applications/lfrfid/helpers/decoder_emmarin.h

@@ -1,21 +0,0 @@
-#pragma once
-#include <stdint.h>
-#include <atomic>
-#include <lib/toolbox/manchester_decoder.h>
-#include "protocols/protocol_emmarin.h"
-class DecoderEMMarin {
-public:
-    bool read(uint8_t* data, uint8_t data_size);
-    void process_front(bool polarity, uint32_t time);
-
-    DecoderEMMarin();
-
-private:
-    void reset_state();
-
-    uint64_t read_data = 0;
-    std::atomic<bool> ready;
-
-    ManchesterState manchester_saved_state;
-    ProtocolEMMarin em_marin;
-};

+ 0 - 15
applications/lfrfid/helpers/decoder_gpio_out.cpp

@@ -1,15 +0,0 @@
-#include "decoder_gpio_out.h"
-#include <furi.h>
-#include <furi_hal.h>
-
-void DecoderGpioOut::process_front(bool polarity, uint32_t /* time */) {
-    furi_hal_gpio_write(&gpio_ext_pa7, polarity);
-}
-
-DecoderGpioOut::DecoderGpioOut() {
-    furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeOutputPushPull);
-}
-
-DecoderGpioOut::~DecoderGpioOut() {
-    furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeAnalog);
-}

+ 0 - 14
applications/lfrfid/helpers/decoder_gpio_out.h

@@ -1,14 +0,0 @@
-#pragma once
-#include <stdint.h>
-#include <atomic>
-
-class DecoderGpioOut {
-public:
-    void process_front(bool polarity, uint32_t time);
-
-    DecoderGpioOut();
-    ~DecoderGpioOut();
-
-private:
-    void reset_state();
-};

+ 0 - 98
applications/lfrfid/helpers/decoder_hid26.cpp

@@ -1,98 +0,0 @@
-#include "decoder_hid26.h"
-#include <furi_hal.h>
-
-constexpr uint32_t clocks_in_us = 64;
-
-constexpr uint32_t jitter_time_us = 20;
-constexpr uint32_t min_time_us = 64;
-constexpr uint32_t max_time_us = 80;
-
-constexpr uint32_t min_time = (min_time_us - jitter_time_us) * clocks_in_us;
-constexpr uint32_t mid_time = ((max_time_us - min_time_us) / 2 + min_time_us) * clocks_in_us;
-constexpr uint32_t max_time = (max_time_us + jitter_time_us) * clocks_in_us;
-
-bool DecoderHID26::read(uint8_t* data, uint8_t data_size) {
-    bool result = false;
-    furi_assert(data_size >= 3);
-
-    if(ready) {
-        result = true;
-        hid.decode(
-            reinterpret_cast<const uint8_t*>(&stored_data), sizeof(uint32_t) * 3, data, data_size);
-        ready = false;
-    }
-
-    return result;
-}
-
-void DecoderHID26::process_front(bool polarity, uint32_t time) {
-    if(ready) return;
-
-    if(polarity == true) {
-        last_pulse_time = time;
-    } else {
-        last_pulse_time += time;
-
-        if(last_pulse_time > min_time && last_pulse_time < max_time) {
-            bool pulse;
-
-            if(last_pulse_time < mid_time) {
-                // 6 pulses
-                pulse = false;
-            } else {
-                // 5 pulses
-                pulse = true;
-            }
-
-            if(last_pulse == pulse) {
-                pulse_count++;
-
-                if(pulse) {
-                    if(pulse_count > 4) {
-                        pulse_count = 0;
-                        store_data(1);
-                    }
-                } else {
-                    if(pulse_count > 5) {
-                        pulse_count = 0;
-                        store_data(0);
-                    }
-                }
-            } else {
-                if(last_pulse) {
-                    if(pulse_count > 2) {
-                        store_data(1);
-                    }
-                } else {
-                    if(pulse_count > 3) {
-                        store_data(0);
-                    }
-                }
-
-                pulse_count = 0;
-                last_pulse = pulse;
-            }
-        }
-    }
-}
-
-DecoderHID26::DecoderHID26() {
-    reset_state();
-}
-
-void DecoderHID26::store_data(bool data) {
-    stored_data[0] = (stored_data[0] << 1) | ((stored_data[1] >> 31) & 1);
-    stored_data[1] = (stored_data[1] << 1) | ((stored_data[2] >> 31) & 1);
-    stored_data[2] = (stored_data[2] << 1) | data;
-
-    if(hid.can_be_decoded(reinterpret_cast<const uint8_t*>(&stored_data), sizeof(uint32_t) * 3)) {
-        ready = true;
-    }
-}
-
-void DecoderHID26::reset_state() {
-    last_pulse = false;
-    pulse_count = 0;
-    ready = false;
-    last_pulse_time = 0;
-}

+ 0 - 24
applications/lfrfid/helpers/decoder_hid26.h

@@ -1,24 +0,0 @@
-#pragma once
-#include <stdint.h>
-#include <atomic>
-#include "protocols/protocol_hid_h10301.h"
-
-class DecoderHID26 {
-public:
-    bool read(uint8_t* data, uint8_t data_size);
-    void process_front(bool polarity, uint32_t time);
-    DecoderHID26();
-
-private:
-    uint32_t last_pulse_time = 0;
-    bool last_pulse;
-    uint8_t pulse_count;
-
-    uint32_t stored_data[3] = {0, 0, 0};
-    void store_data(bool data);
-
-    std::atomic<bool> ready;
-
-    void reset_state();
-    ProtocolHID10301 hid;
-};

+ 0 - 76
applications/lfrfid/helpers/decoder_indala.cpp

@@ -1,76 +0,0 @@
-#include "decoder_indala.h"
-#include <furi_hal.h>
-
-constexpr uint32_t clocks_in_us = 64;
-constexpr uint32_t us_per_bit = 255;
-
-bool DecoderIndala::read(uint8_t* data, uint8_t data_size) {
-    bool result = false;
-
-    if(ready) {
-        result = true;
-        if(cursed_data_valid) {
-            indala.decode(
-                reinterpret_cast<const uint8_t*>(&cursed_raw_data),
-                sizeof(uint64_t),
-                data,
-                data_size);
-        } else {
-            indala.decode(
-                reinterpret_cast<const uint8_t*>(&raw_data), sizeof(uint64_t), data, data_size);
-        }
-        reset_state();
-    }
-
-    return result;
-}
-
-void DecoderIndala::process_front(bool polarity, uint32_t time) {
-    if(ready) return;
-
-    process_internal(polarity, time, &raw_data);
-    if(ready) return;
-
-    if(polarity) {
-        time = time + 110;
-    } else {
-        time = time - 110;
-    }
-
-    process_internal(!polarity, time, &cursed_raw_data);
-    if(ready) {
-        cursed_data_valid = true;
-    }
-}
-
-void DecoderIndala::process_internal(bool polarity, uint32_t time, uint64_t* data) {
-    time /= clocks_in_us;
-    time += (us_per_bit / 2);
-
-    uint32_t bit_count = (time / us_per_bit);
-
-    if(bit_count < 64) {
-        for(uint32_t i = 0; i < bit_count; i++) {
-            *data = (*data << 1) | polarity;
-
-            if((*data >> 32) == 0xa0000000ULL) {
-                if(indala.can_be_decoded(
-                       reinterpret_cast<const uint8_t*>(data), sizeof(uint64_t))) {
-                    ready = true;
-                    break;
-                }
-            }
-        }
-    }
-}
-
-DecoderIndala::DecoderIndala() {
-    reset_state();
-}
-
-void DecoderIndala::reset_state() {
-    raw_data = 0;
-    cursed_raw_data = 0;
-    ready = false;
-    cursed_data_valid = false;
-}

+ 0 - 25
applications/lfrfid/helpers/decoder_indala.h

@@ -1,25 +0,0 @@
-#pragma once
-#include <stdint.h>
-#include <limits.h>
-#include <atomic>
-#include "protocols/protocol_indala_40134.h"
-
-class DecoderIndala {
-public:
-    bool read(uint8_t* data, uint8_t data_size);
-    void process_front(bool polarity, uint32_t time);
-
-    void process_internal(bool polarity, uint32_t time, uint64_t* data);
-
-    DecoderIndala();
-
-private:
-    void reset_state();
-
-    uint64_t raw_data;
-    uint64_t cursed_raw_data;
-
-    std::atomic<bool> ready;
-    std::atomic<bool> cursed_data_valid;
-    ProtocolIndala40134 indala;
-};

+ 0 - 107
applications/lfrfid/helpers/decoder_ioprox.cpp

@@ -1,107 +0,0 @@
-#include "decoder_ioprox.h"
-#include <furi_hal.h>
-#include <cli/cli.h>
-#include <utility>
-
-constexpr uint32_t clocks_in_us = 64;
-
-constexpr uint32_t jitter_time_us = 20;
-constexpr uint32_t min_time_us = 64;
-constexpr uint32_t max_time_us = 80;
-constexpr uint32_t baud_time_us = 500;
-
-constexpr uint32_t min_time = (min_time_us - jitter_time_us) * clocks_in_us;
-constexpr uint32_t mid_time = ((max_time_us - min_time_us) / 2 + min_time_us) * clocks_in_us;
-constexpr uint32_t max_time = (max_time_us + jitter_time_us) * clocks_in_us;
-constexpr uint32_t baud_time = baud_time_us * clocks_in_us;
-
-bool DecoderIoProx::read(uint8_t* data, uint8_t data_size) {
-    bool result = false;
-    furi_assert(data_size >= 4);
-
-    if(ready) {
-        result = true;
-        ioprox.decode(raw_data, sizeof(raw_data), data, data_size);
-        ready = false;
-    }
-
-    return result;
-}
-
-void DecoderIoProx::process_front(bool is_rising_edge, uint32_t time) {
-    if(ready) {
-        return;
-    }
-
-    // Always track the time that's gone by.
-    current_period_duration += time;
-    demodulation_sample_duration += time;
-
-    // If a baud time has elapsed, we're at a sample point.
-    if(demodulation_sample_duration >= baud_time) {
-        // Start a new baud period...
-        demodulation_sample_duration = 0;
-        demodulated_value_invalid = false;
-
-        // ... and if we didn't have any baud errors, capture a sample.
-        if(!demodulated_value_invalid) {
-            store_data(current_demodulated_value);
-        }
-    }
-
-    //
-    // FSK demodulator.
-    //
-
-    // If this isn't a rising edge, this isn't a pulse of interest.
-    // We're done.
-    if(!is_rising_edge) {
-        return;
-    }
-
-    bool is_valid_low = (current_period_duration > min_time) &&
-                        (current_period_duration <= mid_time);
-    bool is_valid_high = (current_period_duration > mid_time) &&
-                         (current_period_duration < max_time);
-
-    // If this is between the minimum and our threshold, this is a logical 0.
-    if(is_valid_low) {
-        current_demodulated_value = false;
-    }
-    // Otherwise, if between our threshold and the max time, it's a logical 1.
-    else if(is_valid_high) {
-        current_demodulated_value = true;
-    }
-    // Otherwise, invalidate this sample.
-    else {
-        demodulated_value_invalid = true;
-    }
-
-    // We're starting a new period; track that.
-    current_period_duration = 0;
-}
-
-DecoderIoProx::DecoderIoProx() {
-    reset_state();
-}
-
-void DecoderIoProx::store_data(bool data) {
-    for(int i = 0; i < 7; ++i) {
-        raw_data[i] = (raw_data[i] << 1) | ((raw_data[i + 1] >> 7) & 1);
-    }
-    raw_data[7] = (raw_data[7] << 1) | data;
-
-    if(ioprox.can_be_decoded(raw_data, sizeof(raw_data))) {
-        ready = true;
-    }
-}
-
-void DecoderIoProx::reset_state() {
-    current_demodulated_value = false;
-    demodulated_value_invalid = false;
-
-    current_period_duration = 0;
-    demodulation_sample_duration = 0;
-
-    ready = false;
-}

+ 0 - 26
applications/lfrfid/helpers/decoder_ioprox.h

@@ -1,26 +0,0 @@
-#pragma once
-#include <stdint.h>
-#include <atomic>
-#include "protocols/protocol_ioprox.h"
-
-class DecoderIoProx {
-public:
-    bool read(uint8_t* data, uint8_t data_size);
-    void process_front(bool polarity, uint32_t time);
-    DecoderIoProx();
-
-private:
-    uint32_t current_period_duration = 0;
-    uint32_t demodulation_sample_duration = 0;
-
-    bool current_demodulated_value = false;
-    bool demodulated_value_invalid = false;
-
-    uint8_t raw_data[8] = {0};
-    void store_data(bool data);
-
-    std::atomic<bool> ready;
-
-    void reset_state();
-    ProtocolIoProx ioprox;
-};

+ 0 - 15
applications/lfrfid/helpers/emmarin.h

@@ -1,15 +0,0 @@
-#pragma once
-#include <stdint.h>
-
-#define EM_HEADER_POS 55
-#define EM_HEADER_MASK (0x1FFLLU << EM_HEADER_POS)
-
-#define EM_FIRST_ROW_POS 50
-#define EM_ROW_COUNT 10
-
-#define EM_COLUMN_POS 4
-#define EM_STOP_POS 0
-#define EM_STOP_MASK (0x1LLU << EM_STOP_POS)
-
-#define EM_HEADER_AND_STOP_MASK (EM_HEADER_MASK | EM_STOP_MASK)
-#define EM_HEADER_AND_STOP_DATA (EM_HEADER_MASK)

+ 0 - 24
applications/lfrfid/helpers/encoder_emmarin.cpp

@@ -1,24 +0,0 @@
-#include "encoder_emmarin.h"
-#include "protocols/protocol_emmarin.h"
-#include <furi.h>
-
-void EncoderEM::init(const uint8_t* data, const uint8_t data_size) {
-    ProtocolEMMarin em_marin;
-    em_marin.encode(data, data_size, reinterpret_cast<uint8_t*>(&card_data), sizeof(uint64_t));
-
-    card_data_index = 0;
-}
-
-// data transmitted as manchester encoding
-// 0 - high2low
-// 1 - low2high
-void EncoderEM::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) {
-    *period = clocks_per_bit;
-    *pulse = clocks_per_bit / 2;
-    *polarity = (card_data >> (63 - card_data_index)) & 1;
-
-    card_data_index++;
-    if(card_data_index >= 64) {
-        card_data_index = 0;
-    }
-}

+ 0 - 22
applications/lfrfid/helpers/encoder_emmarin.h

@@ -1,22 +0,0 @@
-#pragma once
-#include "encoder_generic.h"
-
-class EncoderEM : public EncoderGeneric {
-public:
-    /**
-     * @brief init data to emulate
-     * 
-     * @param data 1 byte FC, next 4 byte SN
-     * @param data_size must be 5
-     */
-    void init(const uint8_t* data, const uint8_t data_size) final;
-
-    void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final;
-
-private:
-    // clock pulses per bit
-    static const uint8_t clocks_per_bit = 64;
-
-    uint64_t card_data;
-    uint8_t card_data_index;
-};

+ 0 - 27
applications/lfrfid/helpers/encoder_generic.h

@@ -1,27 +0,0 @@
-#pragma once
-#include <stdbool.h>
-#include <stdint.h>
-
-class EncoderGeneric {
-public:
-    /**
-     * @brief init encoder
-     * 
-     * @param data data array
-     * @param data_size data array size
-     */
-    virtual void init(const uint8_t* data, const uint8_t data_size) = 0;
-
-    /**
-     * @brief Get the next timer pulse
-     * 
-     * @param polarity pulse polarity true = high2low, false = low2high
-     * @param period overall period time in timer clicks
-     * @param pulse pulse time in timer clicks
-     */
-    virtual void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) = 0;
-
-    virtual ~EncoderGeneric(){};
-
-private:
-};

+ 0 - 46
applications/lfrfid/helpers/encoder_hid_h10301.cpp

@@ -1,46 +0,0 @@
-#include "encoder_hid_h10301.h"
-#include "protocols/protocol_hid_h10301.h"
-#include <furi.h>
-
-void EncoderHID_H10301::init(const uint8_t* data, const uint8_t data_size) {
-    ProtocolHID10301 hid;
-    hid.encode(data, data_size, reinterpret_cast<uint8_t*>(&card_data), sizeof(card_data) * 3);
-
-    card_data_index = 0;
-}
-
-void EncoderHID_H10301::write_bit(bool bit, uint8_t position) {
-    write_raw_bit(bit, position + 0);
-    write_raw_bit(!bit, position + 1);
-}
-
-void EncoderHID_H10301::write_raw_bit(bool bit, uint8_t position) {
-    if(bit) {
-        card_data[position / 32] |= 1UL << (31 - (position % 32));
-    } else {
-        card_data[position / 32] &= ~(1UL << (31 - (position % 32)));
-    }
-}
-
-void EncoderHID_H10301::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) {
-    uint8_t bit = (card_data[card_data_index / 32] >> (31 - (card_data_index % 32))) & 1;
-
-    bool advance = fsk->next(bit, period);
-    if(advance) {
-        card_data_index++;
-        if(card_data_index >= (32 * card_data_max)) {
-            card_data_index = 0;
-        }
-    }
-
-    *polarity = true;
-    *pulse = *period / 2;
-}
-
-EncoderHID_H10301::EncoderHID_H10301() {
-    fsk = new OscFSK(8, 10, 50);
-}
-
-EncoderHID_H10301::~EncoderHID_H10301() {
-    delete fsk;
-}

+ 0 - 26
applications/lfrfid/helpers/encoder_hid_h10301.h

@@ -1,26 +0,0 @@
-#pragma once
-#include "encoder_generic.h"
-#include "osc_fsk.h"
-
-class EncoderHID_H10301 : public EncoderGeneric {
-public:
-    /**
-     * @brief init data to emulate
-     * 
-     * @param data 1 byte FC, next 2 byte SN
-     * @param data_size must be 3
-     */
-    void init(const uint8_t* data, const uint8_t data_size) final;
-    void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final;
-    EncoderHID_H10301();
-    ~EncoderHID_H10301();
-
-private:
-    static const uint8_t card_data_max = 3;
-    uint32_t card_data[card_data_max];
-    uint8_t card_data_index;
-    void write_bit(bool bit, uint8_t position);
-    void write_raw_bit(bool bit, uint8_t position);
-
-    OscFSK* fsk;
-};

+ 0 - 36
applications/lfrfid/helpers/encoder_indala_40134.cpp

@@ -1,36 +0,0 @@
-#include "encoder_indala_40134.h"
-#include "protocols/protocol_indala_40134.h"
-#include <furi.h>
-
-void EncoderIndala_40134::init(const uint8_t* data, const uint8_t data_size) {
-    ProtocolIndala40134 indala;
-    indala.encode(data, data_size, reinterpret_cast<uint8_t*>(&card_data), sizeof(card_data));
-
-    last_bit = card_data & 1;
-    card_data_index = 0;
-    current_polarity = true;
-}
-
-void EncoderIndala_40134::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) {
-    *period = 2;
-    *pulse = 1;
-    *polarity = current_polarity;
-
-    bit_clock_index++;
-    if(bit_clock_index >= clock_per_bit) {
-        bit_clock_index = 0;
-
-        bool current_bit = (card_data >> (63 - card_data_index)) & 1;
-
-        if(current_bit != last_bit) {
-            current_polarity = !current_polarity;
-        }
-
-        last_bit = current_bit;
-
-        card_data_index++;
-        if(card_data_index >= 64) {
-            card_data_index = 0;
-        }
-    }
-}

+ 0 - 23
applications/lfrfid/helpers/encoder_indala_40134.h

@@ -1,23 +0,0 @@
-#pragma once
-#include "encoder_generic.h"
-
-class EncoderIndala_40134 : public EncoderGeneric {
-public:
-    /**
-     * @brief init data to emulate
-     * 
-     * @param data indala raw data
-     * @param data_size must be 5
-     */
-    void init(const uint8_t* data, const uint8_t data_size) final;
-
-    void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final;
-
-private:
-    uint64_t card_data;
-    uint8_t card_data_index;
-    uint8_t bit_clock_index;
-    bool last_bit;
-    bool current_polarity;
-    static const uint8_t clock_per_bit = 16;
-};

+ 0 - 32
applications/lfrfid/helpers/encoder_ioprox.cpp

@@ -1,32 +0,0 @@
-#include "encoder_ioprox.h"
-#include "protocols/protocol_ioprox.h"
-#include <furi.h>
-
-void EncoderIoProx::init(const uint8_t* data, const uint8_t data_size) {
-    ProtocolIoProx ioprox;
-    ioprox.encode(data, data_size, card_data, sizeof(card_data));
-    card_data_index = 0;
-}
-
-void EncoderIoProx::get_next(bool* polarity, uint16_t* period, uint16_t* pulse) {
-    uint8_t bit = (card_data[card_data_index / 8] >> (7 - (card_data_index % 8))) & 1;
-
-    bool advance = fsk->next(bit, period);
-    if(advance) {
-        card_data_index++;
-        if(card_data_index >= (8 * card_data_max)) {
-            card_data_index = 0;
-        }
-    }
-
-    *polarity = true;
-    *pulse = *period / 2;
-}
-
-EncoderIoProx::EncoderIoProx() {
-    fsk = new OscFSK(8, 10, 64);
-}
-
-EncoderIoProx::~EncoderIoProx() {
-    delete fsk;
-}

+ 0 - 25
applications/lfrfid/helpers/encoder_ioprox.h

@@ -1,25 +0,0 @@
-#pragma once
-#include "encoder_generic.h"
-#include "osc_fsk.h"
-
-class EncoderIoProx : public EncoderGeneric {
-public:
-    /**
-     * @brief init data to emulate
-     * 
-     * @param data 1 byte FC, 1 byte Version, 2 bytes code
-     * @param data_size must be 4
-     */
-    void init(const uint8_t* data, const uint8_t data_size) final;
-    void get_next(bool* polarity, uint16_t* period, uint16_t* pulse) final;
-    EncoderIoProx();
-    ~EncoderIoProx();
-
-private:
-    static const uint8_t card_data_max = 8;
-
-    uint8_t card_data[card_data_max];
-    uint8_t card_data_index;
-
-    OscFSK* fsk;
-};

+ 0 - 76
applications/lfrfid/helpers/key_info.cpp

@@ -1,76 +0,0 @@
-#include "key_info.h"
-#include <string.h>
-
-const char* lfrfid_key_get_type_string(LfrfidKeyType type) {
-    switch(type) {
-    case LfrfidKeyType::KeyEM4100:
-        return "EM4100";
-        break;
-    case LfrfidKeyType::KeyH10301:
-        return "H10301";
-        break;
-    case LfrfidKeyType::KeyI40134:
-        return "I40134";
-        break;
-    case LfrfidKeyType::KeyIoProxXSF:
-        return "IoProxXSF";
-        break;
-    }
-
-    return "Unknown";
-}
-
-const char* lfrfid_key_get_manufacturer_string(LfrfidKeyType type) {
-    switch(type) {
-    case LfrfidKeyType::KeyEM4100:
-        return "EM-Marin";
-        break;
-    case LfrfidKeyType::KeyH10301:
-        return "HID";
-        break;
-    case LfrfidKeyType::KeyI40134:
-        return "Indala";
-        break;
-    case LfrfidKeyType::KeyIoProxXSF:
-        return "Kantech";
-    }
-
-    return "Unknown";
-}
-
-bool lfrfid_key_get_string_type(const char* string, LfrfidKeyType* type) {
-    bool result = true;
-
-    if(strcmp("EM4100", string) == 0) {
-        *type = LfrfidKeyType::KeyEM4100;
-    } else if(strcmp("H10301", string) == 0) {
-        *type = LfrfidKeyType::KeyH10301;
-    } else if(strcmp("I40134", string) == 0) {
-        *type = LfrfidKeyType::KeyI40134;
-    } else if(strcmp("IoProxXSF", string) == 0) {
-        *type = LfrfidKeyType::KeyIoProxXSF;
-    } else {
-        result = false;
-    }
-
-    return result;
-}
-
-uint8_t lfrfid_key_get_type_data_count(LfrfidKeyType type) {
-    switch(type) {
-    case LfrfidKeyType::KeyEM4100:
-        return 5;
-        break;
-    case LfrfidKeyType::KeyH10301:
-        return 3;
-        break;
-    case LfrfidKeyType::KeyI40134:
-        return 3;
-        break;
-    case LfrfidKeyType::KeyIoProxXSF:
-        return 4;
-        break;
-    }
-
-    return 0;
-}

+ 0 - 17
applications/lfrfid/helpers/key_info.h

@@ -1,17 +0,0 @@
-#pragma once
-#include <stdint.h>
-
-static const uint8_t LFRFID_KEY_SIZE = 8;
-static const uint8_t LFRFID_KEY_NAME_SIZE = 22;
-
-enum class LfrfidKeyType : uint8_t {
-    KeyEM4100,
-    KeyH10301,
-    KeyI40134,
-    KeyIoProxXSF,
-};
-
-const char* lfrfid_key_get_type_string(LfrfidKeyType type);
-const char* lfrfid_key_get_manufacturer_string(LfrfidKeyType type);
-bool lfrfid_key_get_string_type(const char* string, LfrfidKeyType* type);
-uint8_t lfrfid_key_get_type_data_count(LfrfidKeyType type);

+ 0 - 20
applications/lfrfid/helpers/osc_fsk.cpp

@@ -1,20 +0,0 @@
-#include "osc_fsk.h"
-
-OscFSK::OscFSK(uint16_t _freq_low, uint16_t _freq_hi, uint16_t _osc_phase_max)
-    : freq{_freq_low, _freq_hi}
-    , osc_phase_max(_osc_phase_max) {
-    osc_phase_current = 0;
-}
-
-bool OscFSK::next(bool bit, uint16_t* period) {
-    bool advance = false;
-    *period = freq[bit];
-    osc_phase_current += *period;
-
-    if(osc_phase_current > osc_phase_max) {
-        advance = true;
-        osc_phase_current -= osc_phase_max;
-    }
-
-    return advance;
-}

+ 0 - 30
applications/lfrfid/helpers/osc_fsk.h

@@ -1,30 +0,0 @@
-#pragma once
-#include <stdint.h>
-
-/**
- * This code tries to fit the periods into a given number of cycles (phases) by taking cycles from the next cycle of periods.
- */
-class OscFSK {
-public:
-    /**
-     * Get next period
-     * @param bit bit value
-     * @param period return period
-     * @return bool whether to advance to the next bit
-     */
-    bool next(bool bit, uint16_t* period);
-
-    /**
-     * FSK ocillator constructor
-     * 
-     * @param freq_low bit 0 freq
-     * @param freq_hi bit 1 freq
-     * @param osc_phase_max max oscillator phase
-     */
-    OscFSK(uint16_t freq_low, uint16_t freq_hi, uint16_t osc_phase_max);
-
-private:
-    const uint16_t freq[2];
-    const uint16_t osc_phase_max;
-    int32_t osc_phase_current;
-};

+ 0 - 150
applications/lfrfid/helpers/protocols/protocol_emmarin.cpp

@@ -1,150 +0,0 @@
-#include "protocol_emmarin.h"
-#include <furi.h>
-
-#define EM_HEADER_POS 55
-#define EM_HEADER_MASK (0x1FFLLU << EM_HEADER_POS)
-
-#define EM_FIRST_ROW_POS 50
-
-#define EM_ROW_COUNT 10
-#define EM_COLUMN_COUNT 4
-#define EM_BITS_PER_ROW_COUNT (EM_COLUMN_COUNT + 1)
-
-#define EM_COLUMN_POS 4
-#define EM_STOP_POS 0
-#define EM_STOP_MASK (0x1LLU << EM_STOP_POS)
-
-#define EM_HEADER_AND_STOP_MASK (EM_HEADER_MASK | EM_STOP_MASK)
-#define EM_HEADER_AND_STOP_DATA (EM_HEADER_MASK)
-
-typedef uint64_t EMMarinCardData;
-
-void write_nibble(bool low_nibble, uint8_t data, EMMarinCardData* card_data) {
-    uint8_t parity_sum = 0;
-    uint8_t start = 0;
-    if(!low_nibble) start = 4;
-
-    for(int8_t i = (start + 3); i >= start; i--) {
-        parity_sum += (data >> i) & 1;
-        *card_data = (*card_data << 1) | ((data >> i) & 1);
-    }
-
-    *card_data = (*card_data << 1) | ((parity_sum % 2) & 1);
-}
-
-uint8_t ProtocolEMMarin::get_encoded_data_size() {
-    return sizeof(EMMarinCardData);
-}
-
-uint8_t ProtocolEMMarin::get_decoded_data_size() {
-    return 5;
-}
-
-void ProtocolEMMarin::encode(
-    const uint8_t* decoded_data,
-    const uint8_t decoded_data_size,
-    uint8_t* encoded_data,
-    const uint8_t encoded_data_size) {
-    furi_check(decoded_data_size >= get_decoded_data_size());
-    furi_check(encoded_data_size >= get_encoded_data_size());
-
-    EMMarinCardData card_data;
-
-    // header
-    card_data = 0b111111111;
-
-    // data
-    for(uint8_t i = 0; i < get_decoded_data_size(); i++) {
-        write_nibble(false, decoded_data[i], &card_data);
-        write_nibble(true, decoded_data[i], &card_data);
-    }
-
-    // column parity and stop bit
-    uint8_t parity_sum;
-
-    for(uint8_t c = 0; c < EM_COLUMN_COUNT; c++) {
-        parity_sum = 0;
-        for(uint8_t i = 1; i <= EM_ROW_COUNT; i++) {
-            uint8_t parity_bit = (card_data >> (i * EM_BITS_PER_ROW_COUNT - 1)) & 1;
-            parity_sum += parity_bit;
-        }
-        card_data = (card_data << 1) | ((parity_sum % 2) & 1);
-    }
-
-    // stop bit
-    card_data = (card_data << 1) | 0;
-
-    memcpy(encoded_data, &card_data, get_encoded_data_size());
-}
-
-void ProtocolEMMarin::decode(
-    const uint8_t* encoded_data,
-    const uint8_t encoded_data_size,
-    uint8_t* decoded_data,
-    const uint8_t decoded_data_size) {
-    furi_check(decoded_data_size >= get_decoded_data_size());
-    furi_check(encoded_data_size >= get_encoded_data_size());
-
-    uint8_t decoded_data_index = 0;
-    EMMarinCardData card_data = *(reinterpret_cast<const EMMarinCardData*>(encoded_data));
-
-    // clean result
-    memset(decoded_data, 0, decoded_data_size);
-
-    // header
-    for(uint8_t i = 0; i < 9; i++) {
-        card_data = card_data << 1;
-    }
-
-    // nibbles
-    uint8_t value = 0;
-    for(uint8_t r = 0; r < EM_ROW_COUNT; r++) {
-        uint8_t nibble = 0;
-        for(uint8_t i = 0; i < 5; i++) {
-            if(i < 4) nibble = (nibble << 1) | (card_data & (1LLU << 63) ? 1 : 0);
-            card_data = card_data << 1;
-        }
-        value = (value << 4) | nibble;
-        if(r % 2) {
-            decoded_data[decoded_data_index] |= value;
-            decoded_data_index++;
-            value = 0;
-        }
-    }
-}
-
-bool ProtocolEMMarin::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) {
-    furi_check(encoded_data_size >= get_encoded_data_size());
-    const EMMarinCardData* card_data = reinterpret_cast<const EMMarinCardData*>(encoded_data);
-
-    // check header and stop bit
-    if((*card_data & EM_HEADER_AND_STOP_MASK) != EM_HEADER_AND_STOP_DATA) return false;
-
-    // check row parity
-    for(uint8_t i = 0; i < EM_ROW_COUNT; i++) {
-        uint8_t parity_sum = 0;
-
-        for(uint8_t j = 0; j < EM_BITS_PER_ROW_COUNT; j++) {
-            parity_sum += (*card_data >> (EM_FIRST_ROW_POS - i * EM_BITS_PER_ROW_COUNT + j)) & 1;
-        }
-
-        if((parity_sum % 2)) {
-            return false;
-        }
-    }
-
-    // check columns parity
-    for(uint8_t i = 0; i < EM_COLUMN_COUNT; i++) {
-        uint8_t parity_sum = 0;
-
-        for(uint8_t j = 0; j < EM_ROW_COUNT + 1; j++) {
-            parity_sum += (*card_data >> (EM_COLUMN_POS - i + j * EM_BITS_PER_ROW_COUNT)) & 1;
-        }
-
-        if((parity_sum % 2)) {
-            return false;
-        }
-    }
-
-    return true;
-}

+ 0 - 22
applications/lfrfid/helpers/protocols/protocol_emmarin.h

@@ -1,22 +0,0 @@
-#pragma once
-#include "protocol_generic.h"
-
-class ProtocolEMMarin : public ProtocolGeneric {
-public:
-    uint8_t get_encoded_data_size() final;
-    uint8_t get_decoded_data_size() final;
-
-    void encode(
-        const uint8_t* decoded_data,
-        const uint8_t decoded_data_size,
-        uint8_t* encoded_data,
-        const uint8_t encoded_data_size) final;
-
-    void decode(
-        const uint8_t* encoded_data,
-        const uint8_t encoded_data_size,
-        uint8_t* decoded_data,
-        const uint8_t decoded_data_size) final;
-
-    bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) final;
-};

+ 0 - 60
applications/lfrfid/helpers/protocols/protocol_generic.h

@@ -1,60 +0,0 @@
-#pragma once
-#include "stdint.h"
-#include "stdbool.h"
-
-class ProtocolGeneric {
-public:
-    /**
-     * @brief Get the encoded data size
-     * 
-     * @return uint8_t size of encoded data in bytes
-     */
-    virtual uint8_t get_encoded_data_size() = 0;
-
-    /**
-     * @brief Get the decoded data size
-     * 
-     * @return uint8_t size of decoded data in bytes
-     */
-    virtual uint8_t get_decoded_data_size() = 0;
-
-    /**
-     * @brief encode decoded data
-     * 
-     * @param decoded_data 
-     * @param decoded_data_size 
-     * @param encoded_data 
-     * @param encoded_data_size 
-     */
-    virtual void encode(
-        const uint8_t* decoded_data,
-        const uint8_t decoded_data_size,
-        uint8_t* encoded_data,
-        const uint8_t encoded_data_size) = 0;
-
-    /**
-     * @brief decode encoded data
-     * 
-     * @param encoded_data 
-     * @param encoded_data_size 
-     * @param decoded_data 
-     * @param decoded_data_size 
-     */
-    virtual void decode(
-        const uint8_t* encoded_data,
-        const uint8_t encoded_data_size,
-        uint8_t* decoded_data,
-        const uint8_t decoded_data_size) = 0;
-
-    /**
-     * @brief fast check that data can be correctly decoded
-     * 
-     * @param encoded_data 
-     * @param encoded_data_size 
-     * @return true - can be correctly decoded
-     * @return false - cannot be correctly decoded
-     */
-    virtual bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) = 0;
-
-    virtual ~ProtocolGeneric(){};
-};

+ 0 - 238
applications/lfrfid/helpers/protocols/protocol_hid_h10301.cpp

@@ -1,238 +0,0 @@
-#include "protocol_hid_h10301.h"
-#include <furi.h>
-
-typedef uint32_t HID10301CardData;
-constexpr uint8_t HID10301Count = 3;
-constexpr uint8_t HID10301BitSize = sizeof(HID10301CardData) * 8;
-
-static void write_raw_bit(bool bit, uint8_t position, HID10301CardData* card_data) {
-    if(bit) {
-        card_data[position / HID10301BitSize] |=
-            1UL << (HID10301BitSize - (position % HID10301BitSize) - 1);
-    } else {
-        card_data[position / (sizeof(HID10301CardData) * 8)] &=
-            ~(1UL << (HID10301BitSize - (position % HID10301BitSize) - 1));
-    }
-}
-
-static void write_bit(bool bit, uint8_t position, HID10301CardData* card_data) {
-    write_raw_bit(bit, position + 0, card_data);
-    write_raw_bit(!bit, position + 1, card_data);
-}
-
-uint8_t ProtocolHID10301::get_encoded_data_size() {
-    return sizeof(HID10301CardData) * HID10301Count;
-}
-
-uint8_t ProtocolHID10301::get_decoded_data_size() {
-    return 3;
-}
-
-void ProtocolHID10301::encode(
-    const uint8_t* decoded_data,
-    const uint8_t decoded_data_size,
-    uint8_t* encoded_data,
-    const uint8_t encoded_data_size) {
-    furi_check(decoded_data_size >= get_decoded_data_size());
-    furi_check(encoded_data_size >= get_encoded_data_size());
-
-    HID10301CardData card_data[HID10301Count] = {0, 0, 0};
-
-    uint32_t fc_cn = (decoded_data[0] << 16) | (decoded_data[1] << 8) | decoded_data[2];
-
-    // even parity sum calculation (high 12 bits of data)
-    uint8_t even_parity_sum = 0;
-    for(int8_t i = 12; i < 24; i++) {
-        if(((fc_cn >> i) & 1) == 1) {
-            even_parity_sum++;
-        }
-    }
-
-    // odd parity sum calculation (low 12 bits of data)
-    uint8_t odd_parity_sum = 1;
-    for(int8_t i = 0; i < 12; i++) {
-        if(((fc_cn >> i) & 1) == 1) {
-            odd_parity_sum++;
-        }
-    }
-
-    // 0x1D preamble
-    write_raw_bit(0, 0, card_data);
-    write_raw_bit(0, 1, card_data);
-    write_raw_bit(0, 2, card_data);
-    write_raw_bit(1, 3, card_data);
-    write_raw_bit(1, 4, card_data);
-    write_raw_bit(1, 5, card_data);
-    write_raw_bit(0, 6, card_data);
-    write_raw_bit(1, 7, card_data);
-
-    // company / OEM code 1
-    write_bit(0, 8, card_data);
-    write_bit(0, 10, card_data);
-    write_bit(0, 12, card_data);
-    write_bit(0, 14, card_data);
-    write_bit(0, 16, card_data);
-    write_bit(0, 18, card_data);
-    write_bit(1, 20, card_data);
-
-    // card format / length 1
-    write_bit(0, 22, card_data);
-    write_bit(0, 24, card_data);
-    write_bit(0, 26, card_data);
-    write_bit(0, 28, card_data);
-    write_bit(0, 30, card_data);
-    write_bit(0, 32, card_data);
-    write_bit(0, 34, card_data);
-    write_bit(0, 36, card_data);
-    write_bit(0, 38, card_data);
-    write_bit(0, 40, card_data);
-    write_bit(1, 42, card_data);
-
-    // even parity bit
-    write_bit((even_parity_sum % 2), 44, card_data);
-
-    // data
-    for(uint8_t i = 0; i < 24; i++) {
-        write_bit((fc_cn >> (23 - i)) & 1, 46 + (i * 2), card_data);
-    }
-
-    // odd parity bit
-    write_bit((odd_parity_sum % 2), 94, card_data);
-
-    memcpy(encoded_data, &card_data, get_encoded_data_size());
-}
-
-void ProtocolHID10301::decode(
-    const uint8_t* encoded_data,
-    const uint8_t encoded_data_size,
-    uint8_t* decoded_data,
-    const uint8_t decoded_data_size) {
-    furi_check(decoded_data_size >= get_decoded_data_size());
-    furi_check(encoded_data_size >= get_encoded_data_size());
-
-    const HID10301CardData* card_data = reinterpret_cast<const HID10301CardData*>(encoded_data);
-
-    // data decoding
-    uint32_t result = 0;
-
-    // decode from word 1
-    // coded with 01 = 0, 10 = 1 transitions
-    for(int8_t i = 9; i >= 0; i--) {
-        switch((*(card_data + 1) >> (2 * i)) & 0b11) {
-        case 0b01:
-            result = (result << 1) | 0;
-            break;
-        case 0b10:
-            result = (result << 1) | 1;
-            break;
-        default:
-            break;
-        }
-    }
-
-    // decode from word 2
-    // coded with 01 = 0, 10 = 1 transitions
-    for(int8_t i = 15; i >= 0; i--) {
-        switch((*(card_data + 2) >> (2 * i)) & 0b11) {
-        case 0b01:
-            result = (result << 1) | 0;
-            break;
-        case 0b10:
-            result = (result << 1) | 1;
-            break;
-        default:
-            break;
-        }
-    }
-
-    uint8_t data[3] = {(uint8_t)(result >> 17), (uint8_t)(result >> 9), (uint8_t)(result >> 1)};
-
-    memcpy(decoded_data, &data, get_decoded_data_size());
-}
-
-bool ProtocolHID10301::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) {
-    furi_check(encoded_data_size >= get_encoded_data_size());
-
-    const HID10301CardData* card_data = reinterpret_cast<const HID10301CardData*>(encoded_data);
-
-    // packet preamble
-    // raw data
-    if(*(encoded_data + 3) != 0x1D) {
-        return false;
-    }
-
-    // encoded company/oem
-    // coded with 01 = 0, 10 = 1 transitions
-    // stored in word 0
-    if((*card_data >> 10 & 0x3FFF) != 0x1556) {
-        return false;
-    }
-
-    // encoded format/length
-    // coded with 01 = 0, 10 = 1 transitions
-    // stored in word 0 and word 1
-    if((((*card_data & 0x3FF) << 12) | ((*(card_data + 1) >> 20) & 0xFFF)) != 0x155556) {
-        return false;
-    }
-
-    // data decoding
-    uint32_t result = 0;
-
-    // decode from word 1
-    // coded with 01 = 0, 10 = 1 transitions
-    for(int8_t i = 9; i >= 0; i--) {
-        switch((*(card_data + 1) >> (2 * i)) & 0b11) {
-        case 0b01:
-            result = (result << 1) | 0;
-            break;
-        case 0b10:
-            result = (result << 1) | 1;
-            break;
-        default:
-            return false;
-            break;
-        }
-    }
-
-    // decode from word 2
-    // coded with 01 = 0, 10 = 1 transitions
-    for(int8_t i = 15; i >= 0; i--) {
-        switch((*(card_data + 2) >> (2 * i)) & 0b11) {
-        case 0b01:
-            result = (result << 1) | 0;
-            break;
-        case 0b10:
-            result = (result << 1) | 1;
-            break;
-        default:
-            return false;
-            break;
-        }
-    }
-
-    // trailing parity (odd) test
-    uint8_t parity_sum = 0;
-    for(int8_t i = 0; i < 13; i++) {
-        if(((result >> i) & 1) == 1) {
-            parity_sum++;
-        }
-    }
-
-    if((parity_sum % 2) != 1) {
-        return false;
-    }
-
-    // leading parity (even) test
-    parity_sum = 0;
-    for(int8_t i = 13; i < 26; i++) {
-        if(((result >> i) & 1) == 1) {
-            parity_sum++;
-        }
-    }
-
-    if((parity_sum % 2) == 1) {
-        return false;
-    }
-
-    return true;
-}

+ 0 - 22
applications/lfrfid/helpers/protocols/protocol_hid_h10301.h

@@ -1,22 +0,0 @@
-#pragma once
-#include "protocol_generic.h"
-
-class ProtocolHID10301 : public ProtocolGeneric {
-public:
-    uint8_t get_encoded_data_size() final;
-    uint8_t get_decoded_data_size() final;
-
-    void encode(
-        const uint8_t* decoded_data,
-        const uint8_t decoded_data_size,
-        uint8_t* encoded_data,
-        const uint8_t encoded_data_size) final;
-
-    void decode(
-        const uint8_t* encoded_data,
-        const uint8_t encoded_data_size,
-        uint8_t* decoded_data,
-        const uint8_t decoded_data_size) final;
-
-    bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) final;
-};

+ 0 - 237
applications/lfrfid/helpers/protocols/protocol_indala_40134.cpp

@@ -1,237 +0,0 @@
-#include "protocol_indala_40134.h"
-#include <furi.h>
-
-typedef uint64_t Indala40134CardData;
-
-static void set_bit(bool bit, uint8_t position, Indala40134CardData* card_data) {
-    position = (sizeof(Indala40134CardData) * 8) - 1 - position;
-    if(bit) {
-        *card_data |= 1ull << position;
-    } else {
-        *card_data &= ~(1ull << position);
-    }
-}
-
-static bool get_bit(uint8_t position, const Indala40134CardData* card_data) {
-    position = (sizeof(Indala40134CardData) * 8) - 1 - position;
-    return (*card_data >> position) & 1;
-}
-
-uint8_t ProtocolIndala40134::get_encoded_data_size() {
-    return sizeof(Indala40134CardData);
-}
-
-uint8_t ProtocolIndala40134::get_decoded_data_size() {
-    return 3;
-}
-
-void ProtocolIndala40134::encode(
-    const uint8_t* decoded_data,
-    const uint8_t decoded_data_size,
-    uint8_t* encoded_data,
-    const uint8_t encoded_data_size) {
-    furi_check(decoded_data_size >= get_decoded_data_size());
-    furi_check(encoded_data_size >= get_encoded_data_size());
-
-    uint32_t fc_and_card = (decoded_data[0] << 16) | (decoded_data[1] << 8) | decoded_data[2];
-    Indala40134CardData card_data = 0;
-
-    // preamble
-    set_bit(1, 0, &card_data);
-    set_bit(1, 2, &card_data);
-    set_bit(1, 32, &card_data);
-
-    // factory code
-    set_bit(((fc_and_card >> 23) & 1), 57, &card_data);
-    set_bit(((fc_and_card >> 22) & 1), 49, &card_data);
-    set_bit(((fc_and_card >> 21) & 1), 44, &card_data);
-    set_bit(((fc_and_card >> 20) & 1), 47, &card_data);
-    set_bit(((fc_and_card >> 19) & 1), 48, &card_data);
-    set_bit(((fc_and_card >> 18) & 1), 53, &card_data);
-    set_bit(((fc_and_card >> 17) & 1), 39, &card_data);
-    set_bit(((fc_and_card >> 16) & 1), 58, &card_data);
-
-    // card number
-    set_bit(((fc_and_card >> 15) & 1), 42, &card_data);
-    set_bit(((fc_and_card >> 14) & 1), 45, &card_data);
-    set_bit(((fc_and_card >> 13) & 1), 43, &card_data);
-    set_bit(((fc_and_card >> 12) & 1), 40, &card_data);
-    set_bit(((fc_and_card >> 11) & 1), 52, &card_data);
-    set_bit(((fc_and_card >> 10) & 1), 36, &card_data);
-    set_bit(((fc_and_card >> 9) & 1), 35, &card_data);
-    set_bit(((fc_and_card >> 8) & 1), 51, &card_data);
-    set_bit(((fc_and_card >> 7) & 1), 46, &card_data);
-    set_bit(((fc_and_card >> 6) & 1), 33, &card_data);
-    set_bit(((fc_and_card >> 5) & 1), 37, &card_data);
-    set_bit(((fc_and_card >> 4) & 1), 54, &card_data);
-    set_bit(((fc_and_card >> 3) & 1), 56, &card_data);
-    set_bit(((fc_and_card >> 2) & 1), 59, &card_data);
-    set_bit(((fc_and_card >> 1) & 1), 50, &card_data);
-    set_bit(((fc_and_card >> 0) & 1), 41, &card_data);
-
-    // checksum
-    uint8_t checksum = 0;
-    checksum += ((fc_and_card >> 14) & 1);
-    checksum += ((fc_and_card >> 12) & 1);
-    checksum += ((fc_and_card >> 9) & 1);
-    checksum += ((fc_and_card >> 8) & 1);
-    checksum += ((fc_and_card >> 6) & 1);
-    checksum += ((fc_and_card >> 5) & 1);
-    checksum += ((fc_and_card >> 2) & 1);
-    checksum += ((fc_and_card >> 0) & 1);
-
-    // wiegand parity bits
-    // even parity sum calculation (high 12 bits of data)
-    uint8_t even_parity_sum = 0;
-    for(int8_t i = 12; i < 24; i++) {
-        if(((fc_and_card >> i) & 1) == 1) {
-            even_parity_sum++;
-        }
-    }
-
-    // odd parity sum calculation (low 12 bits of data)
-    uint8_t odd_parity_sum = 1;
-    for(int8_t i = 0; i < 12; i++) {
-        if(((fc_and_card >> i) & 1) == 1) {
-            odd_parity_sum++;
-        }
-    }
-
-    // even parity bit
-    set_bit((even_parity_sum % 2), 34, &card_data);
-
-    // odd parity bit
-    set_bit((odd_parity_sum % 2), 38, &card_data);
-
-    // checksum
-    if((checksum & 1) == 1) {
-        set_bit(0, 62, &card_data);
-        set_bit(1, 63, &card_data);
-    } else {
-        set_bit(1, 62, &card_data);
-        set_bit(0, 63, &card_data);
-    }
-
-    memcpy(encoded_data, &card_data, get_encoded_data_size());
-}
-
-// factory code
-static uint8_t get_fc(const Indala40134CardData* card_data) {
-    uint8_t fc = 0;
-
-    fc = fc << 1 | get_bit(57, card_data);
-    fc = fc << 1 | get_bit(49, card_data);
-    fc = fc << 1 | get_bit(44, card_data);
-    fc = fc << 1 | get_bit(47, card_data);
-    fc = fc << 1 | get_bit(48, card_data);
-    fc = fc << 1 | get_bit(53, card_data);
-    fc = fc << 1 | get_bit(39, card_data);
-    fc = fc << 1 | get_bit(58, card_data);
-
-    return fc;
-}
-
-// card number
-static uint16_t get_cn(const Indala40134CardData* card_data) {
-    uint16_t cn = 0;
-
-    cn = cn << 1 | get_bit(42, card_data);
-    cn = cn << 1 | get_bit(45, card_data);
-    cn = cn << 1 | get_bit(43, card_data);
-    cn = cn << 1 | get_bit(40, card_data);
-    cn = cn << 1 | get_bit(52, card_data);
-    cn = cn << 1 | get_bit(36, card_data);
-    cn = cn << 1 | get_bit(35, card_data);
-    cn = cn << 1 | get_bit(51, card_data);
-    cn = cn << 1 | get_bit(46, card_data);
-    cn = cn << 1 | get_bit(33, card_data);
-    cn = cn << 1 | get_bit(37, card_data);
-    cn = cn << 1 | get_bit(54, card_data);
-    cn = cn << 1 | get_bit(56, card_data);
-    cn = cn << 1 | get_bit(59, card_data);
-    cn = cn << 1 | get_bit(50, card_data);
-    cn = cn << 1 | get_bit(41, card_data);
-
-    return cn;
-}
-
-void ProtocolIndala40134::decode(
-    const uint8_t* encoded_data,
-    const uint8_t encoded_data_size,
-    uint8_t* decoded_data,
-    const uint8_t decoded_data_size) {
-    furi_check(decoded_data_size >= get_decoded_data_size());
-    furi_check(encoded_data_size >= get_encoded_data_size());
-
-    const Indala40134CardData* card_data =
-        reinterpret_cast<const Indala40134CardData*>(encoded_data);
-
-    uint8_t fc = get_fc(card_data);
-    uint16_t card = get_cn(card_data);
-
-    decoded_data[0] = fc;
-    decoded_data[1] = card >> 8;
-    decoded_data[2] = card;
-}
-
-bool ProtocolIndala40134::can_be_decoded(
-    const uint8_t* encoded_data,
-    const uint8_t encoded_data_size) {
-    furi_check(encoded_data_size >= get_encoded_data_size());
-    bool can_be_decoded = false;
-
-    const Indala40134CardData* card_data =
-        reinterpret_cast<const Indala40134CardData*>(encoded_data);
-
-    do {
-        // preambula
-        if((*card_data >> 32) != 0xa0000000UL) break;
-
-        // data
-        const uint32_t fc_and_card = get_fc(card_data) << 16 | get_cn(card_data);
-
-        // checksum
-        const uint8_t checksum = get_bit(62, card_data) << 1 | get_bit(63, card_data);
-        uint8_t checksum_sum = 0;
-        checksum_sum += ((fc_and_card >> 14) & 1);
-        checksum_sum += ((fc_and_card >> 12) & 1);
-        checksum_sum += ((fc_and_card >> 9) & 1);
-        checksum_sum += ((fc_and_card >> 8) & 1);
-        checksum_sum += ((fc_and_card >> 6) & 1);
-        checksum_sum += ((fc_and_card >> 5) & 1);
-        checksum_sum += ((fc_and_card >> 2) & 1);
-        checksum_sum += ((fc_and_card >> 0) & 1);
-        checksum_sum = checksum_sum & 0b1;
-
-        if(checksum_sum == 1 && checksum == 0b01) {
-        } else if(checksum_sum == 0 && checksum == 0b10) {
-        } else {
-            break;
-        }
-
-        // wiegand parity bits
-        // even parity sum calculation (high 12 bits of data)
-        const bool even_parity = get_bit(34, card_data);
-        uint8_t even_parity_sum = 0;
-        for(int8_t i = 12; i < 24; i++) {
-            if(((fc_and_card >> i) & 1) == 1) {
-                even_parity_sum++;
-            }
-        }
-        if(even_parity_sum % 2 != even_parity) break;
-
-        // odd parity sum calculation (low 12 bits of data)
-        const bool odd_parity = get_bit(38, card_data);
-        uint8_t odd_parity_sum = 1;
-        for(int8_t i = 0; i < 12; i++) {
-            if(((fc_and_card >> i) & 1) == 1) {
-                odd_parity_sum++;
-            }
-        }
-        if(odd_parity_sum % 2 != odd_parity) break;
-
-        can_be_decoded = true;
-    } while(false);
-
-    return can_be_decoded;
-}

+ 0 - 22
applications/lfrfid/helpers/protocols/protocol_indala_40134.h

@@ -1,22 +0,0 @@
-#pragma once
-#include "protocol_generic.h"
-
-class ProtocolIndala40134 : public ProtocolGeneric {
-public:
-    uint8_t get_encoded_data_size() final;
-    uint8_t get_decoded_data_size() final;
-
-    void encode(
-        const uint8_t* decoded_data,
-        const uint8_t decoded_data_size,
-        uint8_t* encoded_data,
-        const uint8_t encoded_data_size) final;
-
-    void decode(
-        const uint8_t* encoded_data,
-        const uint8_t encoded_data_size,
-        uint8_t* decoded_data,
-        const uint8_t decoded_data_size) final;
-
-    bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) final;
-};

+ 0 - 193
applications/lfrfid/helpers/protocols/protocol_ioprox.cpp

@@ -1,193 +0,0 @@
-#include "protocol_ioprox.h"
-#include <furi.h>
-#include <cli/cli.h>
-
-/**
- * Writes a bit into the output buffer.
- */
-static void write_bit(bool bit, uint8_t position, uint8_t* data) {
-    if(bit) {
-        data[position / 8] |= 1UL << (7 - (position % 8));
-    } else {
-        data[position / 8] &= ~(1UL << (7 - (position % 8)));
-    }
-}
-
-/**
- * Writes up to eight contiguous bits into the output buffer.
- */
-static void write_bits(uint8_t byte, uint8_t position, uint8_t* data, uint8_t length) {
-    furi_check(length <= 8);
-    furi_check(length > 0);
-
-    for(uint8_t i = 0; i < length; ++i) {
-        uint8_t shift = 7 - i;
-        write_bit((byte >> shift) & 1, position + i, data);
-    }
-}
-
-uint8_t ProtocolIoProx::get_encoded_data_size() {
-    return 8;
-}
-
-uint8_t ProtocolIoProx::get_decoded_data_size() {
-    return 4;
-}
-
-void ProtocolIoProx::encode(
-    const uint8_t* decoded_data,
-    const uint8_t decoded_data_size,
-    uint8_t* encoded_data,
-    const uint8_t encoded_data_size) {
-    furi_check(decoded_data_size >= get_decoded_data_size());
-    furi_check(encoded_data_size >= get_encoded_data_size());
-
-    // Packet to transmit:
-    //
-    // 0           10          20          30          40          50          60
-    // v           v           v           v           v           v           v
-    // 01234567 8 90123456 7 89012345 6 78901234 5 67890123 4 56789012 3 45678901 23
-    // -----------------------------------------------------------------------------
-    // 00000000 0 11110000 1 facility 1 version_ 1 code-one 1 code-two 1 checksum 11
-
-    // Preamble.
-    write_bits(0b00000000, 0, encoded_data, 8);
-    write_bit(0, 8, encoded_data);
-
-    write_bits(0b11110000, 9, encoded_data, 8);
-    write_bit(1, 17, encoded_data);
-
-    // Facility code.
-    write_bits(decoded_data[0], 18, encoded_data, 8);
-    write_bit(1, 26, encoded_data);
-
-    // Version
-    write_bits(decoded_data[1], 27, encoded_data, 8);
-    write_bit(1, 35, encoded_data);
-
-    // Code one
-    write_bits(decoded_data[2], 36, encoded_data, 8);
-    write_bit(1, 44, encoded_data);
-
-    // Code two
-    write_bits(decoded_data[3], 45, encoded_data, 8);
-    write_bit(1, 53, encoded_data);
-
-    // Checksum
-    write_bits(compute_checksum(encoded_data, 8), 54, encoded_data, 8);
-    write_bit(1, 62, encoded_data);
-    write_bit(1, 63, encoded_data);
-}
-
-void ProtocolIoProx::decode(
-    const uint8_t* encoded_data,
-    const uint8_t encoded_data_size,
-    uint8_t* decoded_data,
-    const uint8_t decoded_data_size) {
-    furi_check(decoded_data_size >= get_decoded_data_size());
-    furi_check(encoded_data_size >= get_encoded_data_size());
-
-    // Packet structure:
-    // (Note: the second word seems fixed; but this may not be a guarantee;
-    //  it currently has no meaning.)
-    //
-    //0        1        2        3        4        5        6        7
-    //v        v        v        v        v        v        v        v
-    //01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF
-    //-----------------------------------------------------------------------
-    //00000000 01111000 01FFFFFF FF1VVVVV VVV1CCCC CCCC1CCC CCCCC1XX XXXXXX11
-    //
-    // F = facility code
-    // V = version
-    // C = code
-    // X = checksum
-
-    // Facility code
-    decoded_data[0] = (encoded_data[2] << 2) | (encoded_data[3] >> 6);
-
-    // Version code.
-    decoded_data[1] = (encoded_data[3] << 3) | (encoded_data[4] >> 5);
-
-    // Code bytes.
-    decoded_data[2] = (encoded_data[4] << 4) | (encoded_data[5] >> 4);
-    decoded_data[3] = (encoded_data[5] << 5) | (encoded_data[6] >> 3);
-}
-
-bool ProtocolIoProx::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) {
-    furi_check(encoded_data_size >= get_encoded_data_size());
-
-    // Packet framing
-    //
-    //0        1        2        3        4        5        6        7
-    //v        v        v        v        v        v        v        v
-    //01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF 01234567 89ABCDEF
-    //-----------------------------------------------------------------------
-    //00000000 01______ _1______ __1_____ ___1____ ____1___ _____1XX XXXXXX11
-    //
-    // _ = variable data
-    // 0 = preamble 0
-    // 1 = framing 1
-    // X = checksum
-
-    // Validate the packet preamble is there...
-    if(encoded_data[0] != 0b00000000) {
-        return false;
-    }
-    if((encoded_data[1] >> 6) != 0b01) {
-        return false;
-    }
-
-    // ... check for known ones...
-    if((encoded_data[2] & 0b01000000) == 0) {
-        return false;
-    }
-    if((encoded_data[3] & 0b00100000) == 0) {
-        return false;
-    }
-    if((encoded_data[4] & 0b00010000) == 0) {
-        return false;
-    }
-    if((encoded_data[5] & 0b00001000) == 0) {
-        return false;
-    }
-    if((encoded_data[6] & 0b00000100) == 0) {
-        return false;
-    }
-    if((encoded_data[7] & 0b00000011) == 0) {
-        return false;
-    }
-
-    // ... and validate our checksums.
-    uint8_t checksum = compute_checksum(encoded_data, 8);
-    uint8_t checkval = (encoded_data[6] << 6) | (encoded_data[7] >> 2);
-
-    if(checksum != checkval) {
-        return false;
-    }
-
-    return true;
-}
-
-uint8_t ProtocolIoProx::compute_checksum(const uint8_t* data, const uint8_t data_size) {
-    furi_check(data_size == get_encoded_data_size());
-
-    // Packet structure:
-    //
-    //0        1        2         3         4         5         6         7
-    //v        v        v         v         v         v         v         v
-    //01234567 8 9ABCDEF0 1 23456789 A BCDEF012 3 456789AB C DEF01234 5 6789ABCD EF
-    //00000000 0 VVVVVVVV 1 WWWWWWWW 1 XXXXXXXX 1 YYYYYYYY 1 ZZZZZZZZ 1 CHECKSUM 11
-    //
-    // algorithm as observed by the proxmark3 folks
-    // CHECKSUM == 0xFF - (V + W + X + Y + Z)
-
-    uint8_t checksum = 0;
-
-    checksum += (data[1] << 1) | (data[2] >> 7); // VVVVVVVVV
-    checksum += (data[2] << 2) | (data[3] >> 6); // WWWWWWWWW
-    checksum += (data[3] << 3) | (data[4] >> 5); // XXXXXXXXX
-    checksum += (data[4] << 4) | (data[5] >> 4); // YYYYYYYYY
-    checksum += (data[5] << 5) | (data[6] >> 3); // ZZZZZZZZZ
-
-    return 0xFF - checksum;
-}

+ 0 - 26
applications/lfrfid/helpers/protocols/protocol_ioprox.h

@@ -1,26 +0,0 @@
-#pragma once
-#include "protocol_generic.h"
-
-class ProtocolIoProx : public ProtocolGeneric {
-public:
-    uint8_t get_encoded_data_size() final;
-    uint8_t get_decoded_data_size() final;
-
-    void encode(
-        const uint8_t* decoded_data,
-        const uint8_t decoded_data_size,
-        uint8_t* encoded_data,
-        const uint8_t encoded_data_size) final;
-
-    void decode(
-        const uint8_t* encoded_data,
-        const uint8_t encoded_data_size,
-        uint8_t* decoded_data,
-        const uint8_t decoded_data_size) final;
-
-    bool can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) final;
-
-private:
-    /**  Computes the IoProx checksum of the provided (decoded) data. */
-    uint8_t compute_checksum(const uint8_t* data, const uint8_t data_size);
-};

+ 0 - 95
applications/lfrfid/helpers/pulse_joiner.cpp

@@ -1,95 +0,0 @@
-#include "pulse_joiner.h"
-#include <furi.h>
-
-bool PulseJoiner::push_pulse(bool polarity, uint16_t period, uint16_t pulse) {
-    bool result = false;
-    furi_check((pulse_index + 1) < pulse_max);
-
-    if(polarity == false && pulse_index == 0) {
-        // first negative pulse is ommited
-
-    } else {
-        pulses[pulse_index].polarity = polarity;
-        pulses[pulse_index].time = pulse;
-        pulse_index++;
-    }
-
-    if(period > pulse) {
-        pulses[pulse_index].polarity = !polarity;
-        pulses[pulse_index].time = period - pulse;
-        pulse_index++;
-    }
-
-    if(pulse_index >= 4) {
-        // we know that first pulse is always high
-        // so we wait 2 edges, hi2low and next low2hi
-
-        uint8_t edges_count = 0;
-        bool last_polarity = pulses[0].polarity;
-
-        for(uint8_t i = 1; i < pulse_index; i++) {
-            if(pulses[i].polarity != last_polarity) {
-                edges_count++;
-                last_polarity = pulses[i].polarity;
-            }
-        }
-
-        if(edges_count >= 2) {
-            result = true;
-        }
-    }
-
-    return result;
-}
-
-void PulseJoiner::pop_pulse(uint16_t* period, uint16_t* pulse) {
-    furi_check(pulse_index <= (pulse_max + 1));
-
-    uint16_t tmp_period = 0;
-    uint16_t tmp_pulse = 0;
-    uint8_t edges_count = 0;
-    bool last_polarity = pulses[0].polarity;
-    uint8_t next_fist_pulse = 0;
-
-    for(uint8_t i = 0; i < pulse_max; i++) {
-        // count edges
-        if(pulses[i].polarity != last_polarity) {
-            edges_count++;
-            last_polarity = pulses[i].polarity;
-        }
-
-        // wait for 2 edges
-        if(edges_count == 2) {
-            next_fist_pulse = i;
-            break;
-        }
-
-        // sum pulse time
-        if(pulses[i].polarity) {
-            tmp_period += pulses[i].time;
-            tmp_pulse += pulses[i].time;
-        } else {
-            tmp_period += pulses[i].time;
-        }
-        pulse_index--;
-    }
-
-    *period = tmp_period;
-    *pulse = tmp_pulse;
-
-    // remove counted periods and shift data
-    for(uint8_t i = 0; i < pulse_max; i++) {
-        if((next_fist_pulse + i) < pulse_max) {
-            pulses[i].polarity = pulses[next_fist_pulse + i].polarity;
-            pulses[i].time = pulses[next_fist_pulse + i].time;
-        } else {
-            break;
-        }
-    }
-}
-
-PulseJoiner::PulseJoiner() {
-    for(uint8_t i = 0; i < pulse_max; i++) {
-        pulses[i] = {false, 0};
-    }
-}

+ 0 - 36
applications/lfrfid/helpers/pulse_joiner.h

@@ -1,36 +0,0 @@
-#pragma once
-#include "stdint.h"
-
-class PulseJoiner {
-public:
-    /**
-     * @brief Push timer pulse. First negative pulse is ommited.
-     * 
-     * @param polarity pulse polarity: true = high2low, false = low2high
-     * @param period overall period time in timer clicks
-     * @param pulse pulse time in timer clicks
-     * 
-     * @return true - next pulse can and must be popped immediatly
-     */
-    bool push_pulse(bool polarity, uint16_t period, uint16_t pulse);
-
-    /**
-     * @brief Get the next timer pulse. Call only if push_pulse returns true.
-     * 
-     * @param period overall period time in timer clicks
-     * @param pulse pulse time in timer clicks
-     */
-    void pop_pulse(uint16_t* period, uint16_t* pulse);
-
-    PulseJoiner();
-
-private:
-    struct Pulse {
-        bool polarity;
-        uint16_t time;
-    };
-
-    uint8_t pulse_index = 0;
-    static const uint8_t pulse_max = 6;
-    Pulse pulses[pulse_max];
-};

+ 0 - 65
applications/lfrfid/helpers/rfid_key.cpp

@@ -1,65 +0,0 @@
-#include "rfid_key.h"
-#include <core/check.h>
-#include <string.h>
-
-RfidKey::RfidKey() {
-    clear();
-}
-
-RfidKey::~RfidKey() {
-}
-
-void RfidKey::set_type(LfrfidKeyType _type) {
-    type = _type;
-}
-
-void RfidKey::set_data(const uint8_t* _data, const uint8_t _data_size) {
-    furi_assert(_data_size <= data.size());
-    for(uint8_t i = 0; i < _data_size; i++) {
-        data[i] = _data[i];
-    }
-}
-
-void RfidKey::set_name(const char* _name) {
-    strlcpy(name, _name, get_name_length());
-}
-
-LfrfidKeyType RfidKey::get_type() {
-    return type;
-}
-
-const uint8_t* RfidKey::get_data() {
-    return &data[0];
-}
-
-const char* RfidKey::get_type_text() {
-    return lfrfid_key_get_type_string(type);
-}
-
-uint8_t RfidKey::get_type_data_count() const {
-    return lfrfid_key_get_type_data_count(type);
-}
-
-char* RfidKey::get_name() {
-    return name;
-}
-
-uint8_t RfidKey::get_name_length() {
-    return LFRFID_KEY_NAME_SIZE;
-}
-
-void RfidKey::clear() {
-    set_name("");
-    set_type(LfrfidKeyType::KeyEM4100);
-    data.fill(0);
-}
-
-RfidKey& RfidKey::operator=(const RfidKey& rhs) {
-    if(this == &rhs) return *this;
-
-    set_type(rhs.type);
-    set_name(rhs.name);
-    set_data(&rhs.data[0], get_type_data_count());
-
-    return *this;
-}

+ 0 - 27
applications/lfrfid/helpers/rfid_key.h

@@ -1,27 +0,0 @@
-#pragma once
-#include "key_info.h"
-#include <array>
-
-class RfidKey {
-public:
-    RfidKey();
-    ~RfidKey();
-
-    void set_type(LfrfidKeyType type);
-    void set_data(const uint8_t* data, const uint8_t data_size);
-    void set_name(const char* name);
-
-    LfrfidKeyType get_type();
-    const uint8_t* get_data();
-    const char* get_type_text();
-    uint8_t get_type_data_count() const;
-    char* get_name();
-    uint8_t get_name_length();
-    void clear();
-    RfidKey& operator=(const RfidKey& rhs);
-
-private:
-    std::array<uint8_t, LFRFID_KEY_SIZE> data;
-    LfrfidKeyType type;
-    char name[LFRFID_KEY_NAME_SIZE + 1];
-};

+ 0 - 175
applications/lfrfid/helpers/rfid_reader.cpp

@@ -1,175 +0,0 @@
-#include "rfid_reader.h"
-#include <furi.h>
-#include <furi_hal.h>
-#include <stm32wbxx_ll_cortex.h>
-
-/**
- * @brief private violation assistant for RfidReader
- */
-struct RfidReaderAccessor {
-    static void decode(RfidReader& rfid_reader, bool polarity) {
-        rfid_reader.decode(polarity);
-    }
-};
-
-void RfidReader::decode(bool polarity) {
-    uint32_t current_dwt_value = DWT->CYCCNT;
-    uint32_t period = current_dwt_value - last_dwt_value;
-    last_dwt_value = current_dwt_value;
-
-#ifdef RFID_GPIO_DEBUG
-    decoder_gpio_out.process_front(polarity, period);
-#endif
-
-    switch(type) {
-    case Type::Normal:
-        decoder_em.process_front(polarity, period);
-        decoder_hid26.process_front(polarity, period);
-        decoder_ioprox.process_front(polarity, period);
-        break;
-    case Type::Indala:
-        decoder_em.process_front(polarity, period);
-        decoder_hid26.process_front(polarity, period);
-        decoder_ioprox.process_front(polarity, period);
-        decoder_indala.process_front(polarity, period);
-        break;
-    }
-
-    detect_ticks++;
-}
-
-bool RfidReader::switch_timer_elapsed() {
-    const uint32_t seconds_to_switch = furi_kernel_get_tick_frequency() * 2.0f;
-    return (furi_get_tick() - switch_os_tick_last) > seconds_to_switch;
-}
-
-void RfidReader::switch_timer_reset() {
-    switch_os_tick_last = furi_get_tick();
-}
-
-void RfidReader::switch_mode() {
-    switch(type) {
-    case Type::Normal:
-        type = Type::Indala;
-        furi_hal_rfid_change_read_config(62500.0f, 0.25f);
-        break;
-    case Type::Indala:
-        type = Type::Normal;
-        furi_hal_rfid_change_read_config(125000.0f, 0.5f);
-        break;
-    }
-
-    switch_timer_reset();
-}
-
-static void comparator_trigger_callback(bool level, void* comp_ctx) {
-    RfidReader* _this = static_cast<RfidReader*>(comp_ctx);
-
-    RfidReaderAccessor::decode(*_this, !level);
-}
-
-RfidReader::RfidReader() {
-}
-
-void RfidReader::start() {
-    type = Type::Normal;
-
-    furi_hal_rfid_pins_read();
-    furi_hal_rfid_tim_read(125000, 0.5);
-    furi_hal_rfid_tim_read_start();
-    start_comparator();
-
-    switch_timer_reset();
-    last_read_count = 0;
-}
-
-void RfidReader::start_forced(RfidReader::Type _type) {
-    start();
-    if(_type == Type::Indala) {
-        switch_mode();
-    }
-}
-
-void RfidReader::stop() {
-    furi_hal_rfid_pins_reset();
-    furi_hal_rfid_tim_read_stop();
-    furi_hal_rfid_tim_reset();
-    stop_comparator();
-}
-
-bool RfidReader::read(LfrfidKeyType* _type, uint8_t* data, uint8_t data_size, bool switch_enable) {
-    bool result = false;
-    bool something_read = false;
-
-    // reading
-    if(decoder_em.read(data, data_size)) {
-        *_type = LfrfidKeyType::KeyEM4100;
-        something_read = true;
-    }
-
-    if(decoder_hid26.read(data, data_size)) {
-        *_type = LfrfidKeyType::KeyH10301;
-        something_read = true;
-    }
-
-    if(decoder_ioprox.read(data, data_size)) {
-        *_type = LfrfidKeyType::KeyIoProxXSF;
-        something_read = true;
-    }
-
-    if(decoder_indala.read(data, data_size)) {
-        *_type = LfrfidKeyType::KeyI40134;
-        something_read = true;
-    }
-
-    // validation
-    if(something_read) {
-        switch_timer_reset();
-
-        if(last_read_type == *_type && memcmp(last_read_data, data, data_size) == 0) {
-            last_read_count = last_read_count + 1;
-
-            if(last_read_count > 2) {
-                result = true;
-            }
-        } else {
-            last_read_type = *_type;
-            memcpy(last_read_data, data, data_size);
-            last_read_count = 0;
-        }
-    }
-
-    // mode switching
-    if(switch_enable && switch_timer_elapsed()) {
-        switch_mode();
-        last_read_count = 0;
-    }
-
-    return result;
-}
-
-bool RfidReader::detect() {
-    bool detected = false;
-    if(detect_ticks > 10) {
-        detected = true;
-    }
-    detect_ticks = 0;
-
-    return detected;
-}
-
-bool RfidReader::any_read() {
-    return last_read_count > 0;
-}
-
-void RfidReader::start_comparator(void) {
-    furi_hal_rfid_comp_set_callback(comparator_trigger_callback, this);
-    last_dwt_value = DWT->CYCCNT;
-
-    furi_hal_rfid_comp_start();
-}
-
-void RfidReader::stop_comparator(void) {
-    furi_hal_rfid_comp_stop();
-    furi_hal_rfid_comp_set_callback(NULL, NULL);
-}

+ 0 - 59
applications/lfrfid/helpers/rfid_reader.h

@@ -1,59 +0,0 @@
-#pragma once
-//#include "decoder_analyzer.h"
-#include "decoder_gpio_out.h"
-#include "decoder_emmarin.h"
-#include "decoder_hid26.h"
-#include "decoder_indala.h"
-#include "decoder_ioprox.h"
-#include "key_info.h"
-
-//#define RFID_GPIO_DEBUG 1
-
-class RfidReader {
-public:
-    enum class Type : uint8_t {
-        Normal,
-        Indala,
-    };
-
-    RfidReader();
-    void start();
-    void start_forced(RfidReader::Type type);
-    void stop();
-    bool read(LfrfidKeyType* type, uint8_t* data, uint8_t data_size, bool switch_enable = true);
-
-    bool detect();
-    bool any_read();
-
-private:
-    friend struct RfidReaderAccessor;
-
-    //DecoderAnalyzer decoder_analyzer;
-#ifdef RFID_GPIO_DEBUG
-    DecoderGpioOut decoder_gpio_out;
-#endif
-    DecoderEMMarin decoder_em;
-    DecoderHID26 decoder_hid26;
-    DecoderIndala decoder_indala;
-    DecoderIoProx decoder_ioprox;
-
-    uint32_t last_dwt_value;
-
-    void start_comparator(void);
-    void stop_comparator(void);
-
-    void decode(bool polarity);
-
-    uint32_t detect_ticks;
-
-    uint32_t switch_os_tick_last;
-    bool switch_timer_elapsed();
-    void switch_timer_reset();
-    void switch_mode();
-
-    LfrfidKeyType last_read_type;
-    uint8_t last_read_data[LFRFID_KEY_SIZE];
-    uint8_t last_read_count;
-
-    Type type = Type::Normal;
-};

+ 0 - 56
applications/lfrfid/helpers/rfid_timer_emulator.cpp

@@ -1,56 +0,0 @@
-#include "rfid_timer_emulator.h"
-
-RfidTimerEmulator::RfidTimerEmulator() {
-}
-
-RfidTimerEmulator::~RfidTimerEmulator() {
-    std::map<LfrfidKeyType, EncoderGeneric*>::iterator it;
-
-    for(it = encoders.begin(); it != encoders.end(); ++it) {
-        delete it->second;
-    }
-
-    encoders.clear();
-}
-
-void RfidTimerEmulator::start(LfrfidKeyType type, const uint8_t* data, uint8_t data_size) {
-    if(encoders.count(type)) {
-        current_encoder = encoders.find(type)->second;
-
-        if(data_size >= lfrfid_key_get_type_data_count(type)) {
-            current_encoder->init(data, data_size);
-
-            furi_hal_rfid_tim_emulate(125000);
-            furi_hal_rfid_pins_emulate();
-
-            furi_hal_rfid_tim_emulate_start(RfidTimerEmulator::timer_update_callback, this);
-        }
-    } else {
-        // not found
-    }
-}
-
-void RfidTimerEmulator::stop() {
-    furi_hal_rfid_tim_emulate_stop();
-    furi_hal_rfid_tim_reset();
-    furi_hal_rfid_pins_reset();
-}
-
-void RfidTimerEmulator::timer_update_callback(void* ctx) {
-    RfidTimerEmulator* _this = static_cast<RfidTimerEmulator*>(ctx);
-
-    bool result;
-    bool polarity;
-    uint16_t period;
-    uint16_t pulse;
-
-    do {
-        _this->current_encoder->get_next(&polarity, &period, &pulse);
-        result = _this->pulse_joiner.push_pulse(polarity, period, pulse);
-    } while(result == false);
-
-    _this->pulse_joiner.pop_pulse(&period, &pulse);
-
-    furi_hal_rfid_set_emulate_period(period - 1);
-    furi_hal_rfid_set_emulate_pulse(pulse);
-}

+ 0 - 31
applications/lfrfid/helpers/rfid_timer_emulator.h

@@ -1,31 +0,0 @@
-#pragma once
-#include <furi_hal.h>
-#include "key_info.h"
-#include "encoder_generic.h"
-#include "encoder_emmarin.h"
-#include "encoder_hid_h10301.h"
-#include "encoder_indala_40134.h"
-#include "encoder_ioprox.h"
-#include "pulse_joiner.h"
-#include <map>
-
-class RfidTimerEmulator {
-public:
-    RfidTimerEmulator();
-    ~RfidTimerEmulator();
-    void start(LfrfidKeyType type, const uint8_t* data, uint8_t data_size);
-    void stop();
-
-private:
-    EncoderGeneric* current_encoder = nullptr;
-
-    std::map<LfrfidKeyType, EncoderGeneric*> encoders = {
-        {LfrfidKeyType::KeyEM4100, new EncoderEM()},
-        {LfrfidKeyType::KeyH10301, new EncoderHID_H10301()},
-        {LfrfidKeyType::KeyI40134, new EncoderIndala_40134()},
-        {LfrfidKeyType::KeyIoProxXSF, new EncoderIoProx()},
-    };
-
-    PulseJoiner pulse_joiner;
-    static void timer_update_callback(void* ctx);
-};

+ 0 - 136
applications/lfrfid/helpers/rfid_worker.cpp

@@ -1,136 +0,0 @@
-#include "rfid_worker.h"
-
-RfidWorker::RfidWorker() {
-}
-
-RfidWorker::~RfidWorker() {
-}
-
-void RfidWorker::start_read() {
-    reader.start();
-}
-
-bool RfidWorker::read() {
-    static const uint8_t data_size = LFRFID_KEY_SIZE;
-    uint8_t data[data_size] = {0};
-    LfrfidKeyType type;
-
-    bool result = reader.read(&type, data, data_size);
-
-    if(result) {
-        key.set_type(type);
-        key.set_data(data, data_size);
-    };
-
-    return result;
-}
-
-bool RfidWorker::detect() {
-    return reader.detect();
-}
-
-bool RfidWorker::any_read() {
-    return reader.any_read();
-}
-
-void RfidWorker::stop_read() {
-    reader.stop();
-}
-
-void RfidWorker::start_write() {
-    write_result = WriteResult::Nothing;
-    write_sequence = new TickSequencer();
-    validate_counts = 0;
-
-    write_sequence->do_every_tick(1, std::bind(&RfidWorker::sq_write, this));
-    write_sequence->do_after_tick(2, std::bind(&RfidWorker::sq_write_start_validate, this));
-    write_sequence->do_every_tick(30, std::bind(&RfidWorker::sq_write_validate, this));
-    write_sequence->do_every_tick(1, std::bind(&RfidWorker::sq_write_stop_validate, this));
-}
-
-RfidWorker::WriteResult RfidWorker::write() {
-    write_sequence->tick();
-    return write_result;
-}
-
-void RfidWorker::stop_write() {
-    delete write_sequence;
-    reader.stop();
-}
-
-void RfidWorker::start_emulate() {
-    emulator.start(key.get_type(), key.get_data(), key.get_type_data_count());
-}
-
-void RfidWorker::stop_emulate() {
-    emulator.stop();
-}
-
-void RfidWorker::sq_write() {
-    for(size_t i = 0; i < 5; i++) {
-        switch(key.get_type()) {
-        case LfrfidKeyType::KeyEM4100:
-            writer.start();
-            writer.write_em(key.get_data());
-            writer.stop();
-            break;
-        case LfrfidKeyType::KeyH10301:
-            writer.start();
-            writer.write_hid(key.get_data());
-            writer.stop();
-            break;
-        case LfrfidKeyType::KeyI40134:
-            writer.start();
-            writer.write_indala(key.get_data());
-            writer.stop();
-            break;
-        case LfrfidKeyType::KeyIoProxXSF:
-            writer.start();
-            writer.write_ioprox(key.get_data());
-            writer.stop();
-            break;
-        }
-    }
-}
-
-void RfidWorker::sq_write_start_validate() {
-    switch(key.get_type()) {
-    case LfrfidKeyType::KeyEM4100:
-    case LfrfidKeyType::KeyH10301:
-    case LfrfidKeyType::KeyIoProxXSF:
-        reader.start_forced(RfidReader::Type::Normal);
-        break;
-    case LfrfidKeyType::KeyI40134:
-        reader.start_forced(RfidReader::Type::Indala);
-        break;
-    }
-}
-
-void RfidWorker::sq_write_validate() {
-    static const uint8_t data_size = LFRFID_KEY_SIZE;
-    uint8_t data[data_size] = {0};
-    LfrfidKeyType type;
-
-    bool result = reader.read(&type, data, data_size);
-
-    if(result && (write_result != WriteResult::Ok)) {
-        if(validate_counts > (5 * 60)) {
-            write_result = WriteResult::NotWritable;
-        }
-
-        if(type == key.get_type()) {
-            if(memcmp(data, key.get_data(), key.get_type_data_count()) == 0) {
-                write_result = WriteResult::Ok;
-                validate_counts = 0;
-            } else {
-                validate_counts++;
-            }
-        } else {
-            validate_counts++;
-        }
-    };
-}
-
-void RfidWorker::sq_write_stop_validate() {
-    reader.stop();
-}

+ 0 - 48
applications/lfrfid/helpers/rfid_worker.h

@@ -1,48 +0,0 @@
-#pragma once
-#include "key_info.h"
-#include "rfid_reader.h"
-#include "rfid_writer.h"
-#include "rfid_timer_emulator.h"
-#include "rfid_key.h"
-#include "state_sequencer.h"
-
-class RfidWorker {
-public:
-    RfidWorker();
-    ~RfidWorker();
-
-    void start_read();
-    bool read();
-    bool detect();
-    bool any_read();
-    void stop_read();
-
-    enum class WriteResult : uint8_t {
-        Ok,
-        NotWritable,
-        Nothing,
-    };
-
-    void start_write();
-    WriteResult write();
-    void stop_write();
-
-    void start_emulate();
-    void stop_emulate();
-
-    RfidKey key;
-
-private:
-    RfidWriter writer;
-    RfidReader reader;
-    RfidTimerEmulator emulator;
-
-    WriteResult write_result;
-    TickSequencer* write_sequence;
-
-    void sq_write();
-    void sq_write_start_validate();
-    void sq_write_validate();
-    uint16_t validate_counts;
-    void sq_write_stop_validate();
-};

+ 0 - 183
applications/lfrfid/helpers/rfid_writer.cpp

@@ -1,183 +0,0 @@
-#include "rfid_writer.h"
-#include "protocols/protocol_ioprox.h"
-#include <furi_hal.h>
-#include "protocols/protocol_emmarin.h"
-#include "protocols/protocol_hid_h10301.h"
-#include "protocols/protocol_indala_40134.h"
-
-/**
- * @brief all timings are specified in field clocks (field clock = 125 kHz, 8 us)
- * 
- */
-class T55xxTiming {
-public:
-    constexpr static const uint16_t wait_time = 400;
-    constexpr static const uint8_t start_gap = 30;
-    constexpr static const uint8_t write_gap = 18;
-    constexpr static const uint8_t data_0 = 24;
-    constexpr static const uint8_t data_1 = 56;
-    constexpr static const uint16_t program = 700;
-};
-
-class T55xxCmd {
-public:
-    constexpr static const uint8_t opcode_page_0 = 0b10;
-    constexpr static const uint8_t opcode_page_1 = 0b11;
-    constexpr static const uint8_t opcode_reset = 0b00;
-};
-
-RfidWriter::RfidWriter() {
-}
-
-RfidWriter::~RfidWriter() {
-}
-
-void RfidWriter::start() {
-    furi_hal_rfid_tim_read(125000, 0.5);
-    furi_hal_rfid_pins_read();
-    furi_hal_rfid_tim_read_start();
-
-    // do not ground the antenna
-    furi_hal_rfid_pin_pull_release();
-}
-
-void RfidWriter::stop() {
-    furi_hal_rfid_tim_read_stop();
-    furi_hal_rfid_tim_reset();
-    furi_hal_rfid_pins_reset();
-}
-
-void RfidWriter::write_gap(uint32_t gap_time) {
-    furi_hal_rfid_tim_read_stop();
-    furi_delay_us(gap_time * 8);
-    furi_hal_rfid_tim_read_start();
-}
-
-void RfidWriter::write_bit(bool value) {
-    if(value) {
-        furi_delay_us(T55xxTiming::data_1 * 8);
-    } else {
-        furi_delay_us(T55xxTiming::data_0 * 8);
-    }
-    write_gap(T55xxTiming::write_gap);
-}
-
-void RfidWriter::write_byte(uint8_t value) {
-    for(uint8_t i = 0; i < 8; i++) {
-        write_bit((value >> i) & 1);
-    }
-}
-
-void RfidWriter::write_block(uint8_t page, uint8_t block, bool lock_bit, uint32_t data) {
-    furi_delay_us(T55xxTiming::wait_time * 8);
-
-    // start gap
-    write_gap(T55xxTiming::start_gap);
-
-    // opcode
-    switch(page) {
-    case 0:
-        write_bit(1);
-        write_bit(0);
-        break;
-    case 1:
-        write_bit(1);
-        write_bit(1);
-        break;
-    default:
-        furi_check(false);
-        break;
-    }
-
-    // lock bit
-    write_bit(lock_bit);
-
-    // data
-    for(uint8_t i = 0; i < 32; i++) {
-        write_bit((data >> (31 - i)) & 1);
-    }
-
-    // block address
-    write_bit((block >> 2) & 1);
-    write_bit((block >> 1) & 1);
-    write_bit((block >> 0) & 1);
-
-    furi_delay_us(T55xxTiming::program * 8);
-
-    furi_delay_us(T55xxTiming::wait_time * 8);
-    write_reset();
-}
-
-void RfidWriter::write_reset() {
-    write_gap(T55xxTiming::start_gap);
-    write_bit(1);
-    write_bit(0);
-}
-
-void RfidWriter::write_em(const uint8_t em_data[5]) {
-    ProtocolEMMarin em_card;
-    uint64_t em_encoded_data;
-    em_card.encode(em_data, 5, reinterpret_cast<uint8_t*>(&em_encoded_data), sizeof(uint64_t));
-    const uint32_t em_config_block_data = 0b00000000000101001000000001000000;
-
-    FURI_CRITICAL_ENTER();
-    write_block(0, 0, false, em_config_block_data);
-    write_block(0, 1, false, em_encoded_data);
-    write_block(0, 2, false, em_encoded_data >> 32);
-    write_reset();
-    FURI_CRITICAL_EXIT();
-}
-
-void RfidWriter::write_hid(const uint8_t hid_data[3]) {
-    ProtocolHID10301 hid_card;
-    uint32_t card_data[3];
-    hid_card.encode(hid_data, 3, reinterpret_cast<uint8_t*>(&card_data), sizeof(card_data) * 3);
-
-    const uint32_t hid_config_block_data = 0b00000000000100000111000001100000;
-
-    FURI_CRITICAL_ENTER();
-    write_block(0, 0, false, hid_config_block_data);
-    write_block(0, 1, false, card_data[0]);
-    write_block(0, 2, false, card_data[1]);
-    write_block(0, 3, false, card_data[2]);
-    write_reset();
-    FURI_CRITICAL_EXIT();
-}
-
-/** Endian fixup. Translates an ioprox block into a t5577 block */
-static uint32_t ioprox_encode_block(const uint8_t block_data[4]) {
-    uint8_t raw_card_data[] = {block_data[3], block_data[2], block_data[1], block_data[0]};
-    return *reinterpret_cast<uint32_t*>(&raw_card_data);
-}
-
-void RfidWriter::write_ioprox(const uint8_t ioprox_data[4]) {
-    ProtocolIoProx ioprox_card;
-
-    uint8_t encoded_data[8];
-    ioprox_card.encode(ioprox_data, 4, encoded_data, sizeof(encoded_data));
-
-    const uint32_t ioprox_config_block_data = 0b00000000000101000111000001000000;
-
-    FURI_CRITICAL_ENTER();
-    write_block(0, 0, false, ioprox_config_block_data);
-    write_block(0, 1, false, ioprox_encode_block(&encoded_data[0]));
-    write_block(0, 2, false, ioprox_encode_block(&encoded_data[4]));
-    write_reset();
-    FURI_CRITICAL_EXIT();
-}
-
-void RfidWriter::write_indala(const uint8_t indala_data[3]) {
-    ProtocolIndala40134 indala_card;
-    uint32_t card_data[2];
-    indala_card.encode(
-        indala_data, 3, reinterpret_cast<uint8_t*>(&card_data), sizeof(card_data) * 2);
-
-    const uint32_t indala_config_block_data = 0b00000000000010000001000001000000;
-
-    FURI_CRITICAL_ENTER();
-    write_block(0, 0, false, indala_config_block_data);
-    write_block(0, 1, false, card_data[0]);
-    write_block(0, 2, false, card_data[1]);
-    write_reset();
-    FURI_CRITICAL_EXIT();
-}

+ 0 - 21
applications/lfrfid/helpers/rfid_writer.h

@@ -1,21 +0,0 @@
-#pragma once
-#include "stdint.h"
-
-class RfidWriter {
-public:
-    RfidWriter();
-    ~RfidWriter();
-    void start();
-    void stop();
-    void write_em(const uint8_t em_data[5]);
-    void write_hid(const uint8_t hid_data[3]);
-    void write_ioprox(const uint8_t ioprox_data[4]);
-    void write_indala(const uint8_t indala_data[3]);
-
-private:
-    void write_gap(uint32_t gap_time);
-    void write_bit(bool value);
-    void write_byte(uint8_t value);
-    void write_block(uint8_t page, uint8_t block, bool lock_bit, uint32_t data);
-    void write_reset();
-};

+ 0 - 50
applications/lfrfid/helpers/state_sequencer.cpp

@@ -1,50 +0,0 @@
-#include "state_sequencer.h"
-#include "stdio.h"
-
-TickSequencer::TickSequencer() {
-}
-
-TickSequencer::~TickSequencer() {
-}
-
-void TickSequencer::tick() {
-    if(tick_count == list_it->first) {
-        tick_count = 0;
-
-        list_it++;
-        if(list_it == list.end()) {
-            list_it = list.begin();
-        }
-    }
-
-    list_it->second();
-    tick_count++;
-}
-
-void TickSequencer::reset() {
-    list_it = list.begin();
-    tick_count = 0;
-}
-
-void TickSequencer::clear() {
-    list.clear();
-    reset();
-}
-
-void TickSequencer::do_every_tick(uint32_t tick_count, std::function<void(void)> fn) {
-    list.push_back(std::make_pair(tick_count, fn));
-    reset();
-}
-
-void TickSequencer::do_after_tick(uint32_t tick_count, std::function<void(void)> fn) {
-    if(tick_count > 1) {
-        list.push_back(
-            std::make_pair(tick_count - 1, std::bind(&TickSequencer::do_nothing, this)));
-    }
-    list.push_back(std::make_pair(1, fn));
-
-    reset();
-}
-
-void TickSequencer::do_nothing() {
-}

+ 0 - 25
applications/lfrfid/helpers/state_sequencer.h

@@ -1,25 +0,0 @@
-#pragma once
-#include "stdint.h"
-#include <list>
-#include <functional>
-
-class TickSequencer {
-public:
-    TickSequencer();
-    ~TickSequencer();
-
-    void tick();
-    void reset();
-    void clear();
-
-    void do_every_tick(uint32_t tick_count, std::function<void(void)> fn);
-    void do_after_tick(uint32_t tick_count, std::function<void(void)> fn);
-
-private:
-    std::list<std::pair<uint32_t, std::function<void(void)> > > list;
-    std::list<std::pair<uint32_t, std::function<void(void)> > >::iterator list_it;
-
-    uint32_t tick_count;
-
-    void do_nothing();
-};

+ 45 - 63
applications/lfrfid/lfrfid_app.cpp

@@ -21,6 +21,11 @@
 #include "scene/lfrfid_app_scene_delete_confirm.h"
 #include "scene/lfrfid_app_scene_delete_success.h"
 #include "scene/lfrfid_app_scene_rpc.h"
+#include "scene/lfrfid_app_scene_extra_actions.h"
+#include "scene/lfrfid_app_scene_raw_info.h"
+#include "scene/lfrfid_app_scene_raw_name.h"
+#include "scene/lfrfid_app_scene_raw_read.h"
+#include "scene/lfrfid_app_scene_raw_success.h"
 
 #include <toolbox/path.h>
 #include <flipper_format/flipper_format.h>
@@ -28,24 +33,44 @@
 #include <rpc/rpc_app.h>
 
 const char* LfRfidApp::app_folder = ANY_PATH("lfrfid");
+const char* LfRfidApp::app_sd_folder = EXT_PATH("lfrfid");
 const char* LfRfidApp::app_extension = ".rfid";
 const char* LfRfidApp::app_filetype = "Flipper RFID key";
 
 LfRfidApp::LfRfidApp()
     : scene_controller{this}
-    , notification{"notification"}
-    , storage{"storage"}
-    , dialogs{"dialogs"}
+    , notification{RECORD_NOTIFICATION}
+    , storage{RECORD_STORAGE}
+    , dialogs{RECORD_DIALOGS}
     , text_store(40) {
+    string_init(file_name);
+    string_init(raw_file_name);
     string_init_set_str(file_path, app_folder);
+
+    dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
+
+    size_t size = protocol_dict_get_max_data_size(dict);
+    new_key_data = (uint8_t*)malloc(size);
+    old_key_data = (uint8_t*)malloc(size);
+
+    lfworker = lfrfid_worker_alloc(dict);
 }
 
 LfRfidApp::~LfRfidApp() {
+    string_clear(raw_file_name);
+    string_clear(file_name);
     string_clear(file_path);
+    protocol_dict_free(dict);
+
+    lfrfid_worker_free(lfworker);
+
     if(rpc_ctx) {
         rpc_system_app_set_callback(rpc_ctx, NULL, NULL);
         rpc_system_app_send_exited(rpc_ctx);
     }
+
+    free(new_key_data);
+    free(old_key_data);
 }
 
 static void rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) {
@@ -88,7 +113,7 @@ void LfRfidApp::run(void* _args) {
             scene_controller.process(100, SceneType::Rpc);
         } else {
             string_set_str(file_path, args);
-            load_key_data(file_path, &worker.key, true);
+            load_key_data(file_path, true);
             view_controller.attach_to_gui(ViewDispatcherTypeFullscreen);
             scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate());
             scene_controller.process(100, SceneType::Emulate);
@@ -114,11 +139,16 @@ void LfRfidApp::run(void* _args) {
         scene_controller.add_scene(SceneType::SavedInfo, new LfRfidAppSceneSavedInfo());
         scene_controller.add_scene(SceneType::DeleteConfirm, new LfRfidAppSceneDeleteConfirm());
         scene_controller.add_scene(SceneType::DeleteSuccess, new LfRfidAppSceneDeleteSuccess());
+        scene_controller.add_scene(SceneType::ExtraActions, new LfRfidAppSceneExtraActions());
+        scene_controller.add_scene(SceneType::RawInfo, new LfRfidAppSceneRawInfo());
+        scene_controller.add_scene(SceneType::RawName, new LfRfidAppSceneRawName());
+        scene_controller.add_scene(SceneType::RawRead, new LfRfidAppSceneRawRead());
+        scene_controller.add_scene(SceneType::RawSuccess, new LfRfidAppSceneRawSuccess());
         scene_controller.process(100);
     }
 }
 
-bool LfRfidApp::save_key(RfidKey* key) {
+bool LfRfidApp::save_key() {
     bool result = false;
 
     make_app_folder();
@@ -128,9 +158,9 @@ bool LfRfidApp::save_key(RfidKey* key) {
         string_left(file_path, filename_start);
     }
 
-    string_cat_printf(file_path, "/%s%s", key->get_name(), app_extension);
+    string_cat_printf(file_path, "/%s%s", string_get_cstr(file_name), app_extension);
 
-    result = save_key_data(file_path, key);
+    result = save_key_data(file_path);
     return result;
 }
 
@@ -143,56 +173,27 @@ bool LfRfidApp::load_key_from_file_select(bool need_restore) {
         dialogs, file_path, file_path, app_extension, true, &I_125_10px, true);
 
     if(result) {
-        result = load_key_data(file_path, &worker.key, true);
+        result = load_key_data(file_path, true);
     }
 
     return result;
 }
 
-bool LfRfidApp::delete_key(RfidKey* key) {
-    UNUSED(key);
+bool LfRfidApp::delete_key() {
     return storage_simply_remove(storage, string_get_cstr(file_path));
 }
 
-bool LfRfidApp::load_key_data(string_t path, RfidKey* key, bool show_dialog) {
-    FlipperFormat* file = flipper_format_file_alloc(storage);
+bool LfRfidApp::load_key_data(string_t path, bool show_dialog) {
     bool result = false;
-    string_t str_result;
-    string_init(str_result);
 
     do {
-        if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break;
-
-        // header
-        uint32_t version;
-        if(!flipper_format_read_header(file, str_result, &version)) break;
-        if(string_cmp_str(str_result, app_filetype) != 0) break;
-        if(version != 1) break;
-
-        // key type
-        LfrfidKeyType type;
-        RfidKey loaded_key;
-
-        if(!flipper_format_read_string(file, "Key type", str_result)) break;
-        if(!lfrfid_key_get_string_type(string_get_cstr(str_result), &type)) break;
-        loaded_key.set_type(type);
+        protocol_id = lfrfid_dict_file_load(dict, string_get_cstr(path));
+        if(protocol_id == PROTOCOL_NO) break;
 
-        // key data
-        uint8_t key_data[loaded_key.get_type_data_count()] = {};
-        if(!flipper_format_read_hex(file, "Data", key_data, loaded_key.get_type_data_count()))
-            break;
-        loaded_key.set_data(key_data, loaded_key.get_type_data_count());
-
-        path_extract_filename(path, str_result, true);
-        loaded_key.set_name(string_get_cstr(str_result));
-
-        *key = loaded_key;
+        path_extract_filename(path, file_name, true);
         result = true;
     } while(0);
 
-    flipper_format_free(file);
-    string_clear(str_result);
-
     if((!result) && (show_dialog)) {
         dialog_message_show_storage_error(dialogs, "Cannot load\nkey file");
     }
@@ -200,27 +201,8 @@ bool LfRfidApp::load_key_data(string_t path, RfidKey* key, bool show_dialog) {
     return result;
 }
 
-bool LfRfidApp::save_key_data(string_t path, RfidKey* key) {
-    FlipperFormat* file = flipper_format_file_alloc(storage);
-    bool result = false;
-
-    do {
-        if(!flipper_format_file_open_always(file, string_get_cstr(path))) break;
-        if(!flipper_format_write_header_cstr(file, app_filetype, 1)) break;
-        if(!flipper_format_write_comment_cstr(file, "Key type can be EM4100, H10301 or I40134"))
-            break;
-        if(!flipper_format_write_string_cstr(
-               file, "Key type", lfrfid_key_get_type_string(key->get_type())))
-            break;
-        if(!flipper_format_write_comment_cstr(
-               file, "Data size for EM4100 is 5, for H10301 is 3, for I40134 is 3"))
-            break;
-        if(!flipper_format_write_hex(file, "Data", key->get_data(), key->get_type_data_count()))
-            break;
-        result = true;
-    } while(0);
-
-    flipper_format_free(file);
+bool LfRfidApp::save_key_data(string_t path) {
+    bool result = lfrfid_dict_file_save(dict, protocol_id, string_get_cstr(path));
 
     if(!result) {
         dialog_message_show_storage_error(dialogs, "Cannot save\nkey file");

+ 42 - 9
applications/lfrfid/lfrfid_app.h

@@ -20,9 +20,15 @@
 #include <storage/storage.h>
 #include <dialogs/dialogs.h>
 
-#include "helpers/rfid_worker.h"
 #include "rpc/rpc_app.h"
 
+#include <toolbox/protocols/protocol_dict.h>
+#include <lfrfid/lfrfid_dict_file.h>
+#include <lfrfid/protocols/lfrfid_protocols.h>
+#include <lfrfid/lfrfid_worker.h>
+
+#define LFRFID_KEY_NAME_SIZE 22
+
 class LfRfidApp {
 public:
     enum class EventType : uint8_t {
@@ -32,7 +38,19 @@ public:
         Stay,
         Retry,
         Exit,
-        EmulateStart,
+        ReadEventSenseStart,
+        ReadEventSenseEnd,
+        ReadEventSenseCardStart,
+        ReadEventSenseCardEnd,
+        ReadEventStartASK,
+        ReadEventStartPSK,
+        ReadEventDone,
+        ReadEventOverrun,
+        ReadEventError,
+        WriteEventOK,
+        WriteEventProtocolCannotBeWritten,
+        WriteEventFobCannotBeWritten,
+        WriteEventTooLongToWrite,
         RpcLoadFile,
         RpcSessionClose,
     };
@@ -57,12 +75,17 @@ public:
         DeleteConfirm,
         DeleteSuccess,
         Rpc,
+        ExtraActions,
+        RawInfo,
+        RawName,
+        RawRead,
+        RawSuccess,
     };
 
     class Event {
     public:
         union {
-            int32_t menu_index;
+            int32_t signed_int;
         } payload;
 
         EventType type;
@@ -79,8 +102,6 @@ public:
     RecordController<Storage> storage;
     RecordController<DialogsApp> dialogs;
 
-    RfidWorker worker;
-
     TextStore text_store;
 
     string_t file_path;
@@ -90,15 +111,27 @@ public:
     void run(void* args);
 
     static const char* app_folder;
+    static const char* app_sd_folder;
     static const char* app_extension;
     static const char* app_filetype;
 
-    bool save_key(RfidKey* key);
+    bool save_key();
     bool load_key_from_file_select(bool need_restore);
-    bool delete_key(RfidKey* key);
+    bool delete_key();
 
-    bool load_key_data(string_t path, RfidKey* key, bool show_dialog);
-    bool save_key_data(string_t path, RfidKey* key);
+    bool load_key_data(string_t path, bool show_dialog);
+    bool save_key_data(string_t path);
 
     void make_app_folder();
+
+    ProtocolDict* dict;
+    LFRFIDWorker* lfworker;
+    string_t file_name;
+    ProtocolId protocol_id;
+    LFRFIDWorkerReadType read_type;
+
+    uint8_t* old_key_data;
+    uint8_t* new_key_data;
+
+    string_t raw_file_name;
 };

+ 575 - 0
applications/lfrfid/lfrfid_cli.c

@@ -0,0 +1,575 @@
+#include <furi.h>
+#include <furi_hal.h>
+#include <stdarg.h>
+#include <cli/cli.h>
+#include <lib/toolbox/args.h>
+#include <lib/lfrfid/lfrfid_worker.h>
+#include <storage/storage.h>
+#include <toolbox/stream/file_stream.h>
+
+#include <toolbox/varint.h>
+
+#include <toolbox/protocols/protocol_dict.h>
+#include <lfrfid/protocols/lfrfid_protocols.h>
+#include <lfrfid/lfrfid_raw_file.h>
+#include <toolbox/pulse_protocols/pulse_glue.h>
+
+static void lfrfid_cli(Cli* cli, string_t args, void* context);
+
+// app cli function
+void lfrfid_on_system_start() {
+    Cli* cli = furi_record_open(RECORD_CLI);
+    cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli, NULL);
+    furi_record_close(RECORD_CLI);
+}
+
+static void lfrfid_cli_print_usage() {
+    printf("Usage:\r\n");
+    printf("rfid read <optional: normal | indala>\r\n");
+    printf("rfid <write | emulate> <key_type> <key_data>\r\n");
+    printf("rfid raw_read <ask | psk> <filename>\r\n");
+    printf("rfid raw_emulate <filename>\r\n");
+};
+
+typedef struct {
+    ProtocolId protocol;
+    FuriEventFlag* event;
+} LFRFIDCliReadContext;
+
+static void lfrfid_cli_read_callback(LFRFIDWorkerReadResult result, ProtocolId proto, void* ctx) {
+    furi_assert(ctx);
+    LFRFIDCliReadContext* context = ctx;
+    if(result == LFRFIDWorkerReadDone) {
+        context->protocol = proto;
+        FURI_SW_MEMBARRIER();
+    }
+    furi_event_flag_set(context->event, 1 << result);
+}
+
+static void lfrfid_cli_read(Cli* cli, string_t args) {
+    string_t type_string;
+    string_init(type_string);
+    LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto;
+
+    if(args_read_string_and_trim(args, type_string)) {
+        if(string_cmp_str(type_string, "normal") == 0 || string_cmp_str(type_string, "ask") == 0) {
+            // ask
+            type = LFRFIDWorkerReadTypeASKOnly;
+        } else if(
+            string_cmp_str(type_string, "indala") == 0 ||
+            string_cmp_str(type_string, "psk") == 0) {
+            // psk
+            type = LFRFIDWorkerReadTypePSKOnly;
+        } else {
+            lfrfid_cli_print_usage();
+            string_clear(type_string);
+            return;
+        }
+    }
+    string_clear(type_string);
+
+    ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
+    LFRFIDWorker* worker = lfrfid_worker_alloc(dict);
+    LFRFIDCliReadContext context;
+    context.protocol = PROTOCOL_NO;
+    context.event = furi_event_flag_alloc();
+
+    lfrfid_worker_start_thread(worker);
+
+    printf("Reading RFID...\r\nPress Ctrl+C to abort\r\n");
+
+    const uint32_t available_flags = (1 << LFRFIDWorkerReadDone);
+
+    lfrfid_worker_read_start(worker, type, lfrfid_cli_read_callback, &context);
+
+    while(true) {
+        uint32_t flags =
+            furi_event_flag_wait(context.event, available_flags, FuriFlagWaitAny, 100);
+
+        if(flags != FuriFlagErrorTimeout) {
+            if(FURI_BIT(flags, LFRFIDWorkerReadDone)) {
+                break;
+            }
+        }
+
+        if(cli_cmd_interrupt_received(cli)) break;
+    }
+
+    lfrfid_worker_stop(worker);
+    lfrfid_worker_stop_thread(worker);
+    lfrfid_worker_free(worker);
+
+    if(context.protocol != PROTOCOL_NO) {
+        printf("%s ", protocol_dict_get_name(dict, context.protocol));
+
+        size_t size = protocol_dict_get_data_size(dict, context.protocol);
+        uint8_t* data = malloc(size);
+        protocol_dict_get_data(dict, context.protocol, data, size);
+        for(size_t i = 0; i < size; i++) {
+            printf("%02X", data[i]);
+        }
+        printf("\r\n");
+        free(data);
+
+        string_t info;
+        string_init(info);
+        protocol_dict_render_data(dict, info, context.protocol);
+        if(string_size(info) > 0) {
+            printf("%s\r\n", string_get_cstr(info));
+        }
+        string_clear(info);
+    }
+
+    printf("Reading stopped\r\n");
+    protocol_dict_free(dict);
+
+    furi_event_flag_free(context.event);
+}
+
+static bool lfrfid_cli_parse_args(string_t args, ProtocolDict* dict, ProtocolId* protocol) {
+    bool result = false;
+    string_t protocol_name, data_text;
+    string_init(protocol_name);
+    string_init(data_text);
+    size_t data_size = protocol_dict_get_max_data_size(dict);
+    uint8_t* data = malloc(data_size);
+
+    do {
+        // load args
+        if(!args_read_string_and_trim(args, protocol_name) ||
+           !args_read_string_and_trim(args, data_text)) {
+            lfrfid_cli_print_usage();
+            break;
+        }
+
+        // check protocol arg
+        *protocol = protocol_dict_get_protocol_by_name(dict, string_get_cstr(protocol_name));
+        if(*protocol == PROTOCOL_NO) {
+            printf(
+                "Unknown protocol: %s\r\n"
+                "Available protocols:\r\n",
+                string_get_cstr(protocol_name));
+
+            for(ProtocolId i = 0; i < LFRFIDProtocolMax; i++) {
+                printf(
+                    "\t%s, %d bytes long\r\n",
+                    protocol_dict_get_name(dict, i),
+                    protocol_dict_get_data_size(dict, i));
+            }
+            break;
+        }
+
+        data_size = protocol_dict_get_data_size(dict, *protocol);
+
+        // check data arg
+        if(!args_read_hex_bytes(data_text, data, data_size)) {
+            printf(
+                "%s data needs to be %d bytes long\r\n",
+                protocol_dict_get_name(dict, *protocol),
+                data_size);
+            break;
+        }
+
+        // load data to protocol
+        protocol_dict_set_data(dict, *protocol, data, data_size);
+
+        result = true;
+    } while(false);
+
+    free(data);
+    string_clear(protocol_name);
+    string_clear(data_text);
+    return result;
+}
+
+static void lfrfid_cli_write_callback(LFRFIDWorkerWriteResult result, void* ctx) {
+    furi_assert(ctx);
+    FuriEventFlag* events = ctx;
+    furi_event_flag_set(events, 1 << result);
+}
+
+static void lfrfid_cli_write(Cli* cli, string_t args) {
+    ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
+    ProtocolId protocol;
+
+    if(!lfrfid_cli_parse_args(args, dict, &protocol)) {
+        protocol_dict_free(dict);
+        return;
+    }
+
+    LFRFIDWorker* worker = lfrfid_worker_alloc(dict);
+    FuriEventFlag* event = furi_event_flag_alloc();
+
+    lfrfid_worker_start_thread(worker);
+    lfrfid_worker_write_start(worker, protocol, lfrfid_cli_write_callback, event);
+
+    printf("Writing RFID...\r\nPress Ctrl+C to abort\r\n");
+    const uint32_t available_flags = (1 << LFRFIDWorkerWriteOK) |
+                                     (1 << LFRFIDWorkerWriteProtocolCannotBeWritten) |
+                                     (1 << LFRFIDWorkerWriteFobCannotBeWritten);
+
+    while(!cli_cmd_interrupt_received(cli)) {
+        uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100);
+        if(flags != FuriFlagErrorTimeout) {
+            if(FURI_BIT(flags, LFRFIDWorkerWriteOK)) {
+                printf("Written!\r\n");
+                break;
+            }
+
+            if(FURI_BIT(flags, LFRFIDWorkerWriteProtocolCannotBeWritten)) {
+                printf("This protocol cannot be written.\r\n");
+                break;
+            }
+
+            if(FURI_BIT(flags, LFRFIDWorkerWriteFobCannotBeWritten)) {
+                printf("Seems this fob cannot be written.\r\n");
+            }
+        }
+    }
+    printf("Writing stopped\r\n");
+
+    lfrfid_worker_stop(worker);
+    lfrfid_worker_stop_thread(worker);
+    lfrfid_worker_free(worker);
+    protocol_dict_free(dict);
+    furi_event_flag_free(event);
+}
+
+static void lfrfid_cli_emulate(Cli* cli, string_t args) {
+    ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
+    ProtocolId protocol;
+
+    if(!lfrfid_cli_parse_args(args, dict, &protocol)) {
+        protocol_dict_free(dict);
+        return;
+    }
+
+    LFRFIDWorker* worker = lfrfid_worker_alloc(dict);
+
+    lfrfid_worker_start_thread(worker);
+    lfrfid_worker_emulate_start(worker, protocol);
+
+    printf("Emulating RFID...\r\nPress Ctrl+C to abort\r\n");
+    while(!cli_cmd_interrupt_received(cli)) {
+        furi_delay_ms(100);
+    }
+    printf("Emulation stopped\r\n");
+
+    lfrfid_worker_stop(worker);
+    lfrfid_worker_stop_thread(worker);
+    lfrfid_worker_free(worker);
+    protocol_dict_free(dict);
+}
+
+static void lfrfid_cli_raw_analyze(Cli* cli, string_t args) {
+    UNUSED(cli);
+    string_t filepath, info_string;
+    string_init(filepath);
+    string_init(info_string);
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    LFRFIDRawFile* file = lfrfid_raw_file_alloc(storage);
+
+    do {
+        float frequency = 0;
+        float duty_cycle = 0;
+
+        if(!args_read_probably_quoted_string_and_trim(args, filepath)) {
+            lfrfid_cli_print_usage();
+            break;
+        }
+
+        if(!lfrfid_raw_file_open_read(file, string_get_cstr(filepath))) {
+            printf("Failed to open file\r\n");
+            break;
+        }
+
+        if(!lfrfid_raw_file_read_header(file, &frequency, &duty_cycle)) {
+            printf("Invalid header\r\n");
+            break;
+        }
+
+        bool file_end = false;
+        uint32_t total_warns = 0;
+        uint32_t total_duration = 0;
+        uint32_t total_pulse = 0;
+        ProtocolId total_protocol = PROTOCOL_NO;
+
+        ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
+        protocol_dict_decoders_start(dict);
+
+        while(!file_end) {
+            uint32_t pulse = 0;
+            uint32_t duration = 0;
+            if(lfrfid_raw_file_read_pair(file, &duration, &pulse, &file_end)) {
+                bool warn = false;
+
+                if(pulse > duration || pulse <= 0 || duration <= 0) {
+                    total_warns += 1;
+                    warn = true;
+                }
+
+                string_printf(info_string, "[%ld %ld]", pulse, duration);
+                printf("%-16s", string_get_cstr(info_string));
+                string_printf(info_string, "[%ld %ld]", pulse, duration - pulse);
+                printf("%-16s", string_get_cstr(info_string));
+
+                if(warn) {
+                    printf(" <<----");
+                }
+
+                if(total_protocol == PROTOCOL_NO) {
+                    total_protocol = protocol_dict_decoders_feed(dict, true, pulse);
+                    if(total_protocol == PROTOCOL_NO) {
+                        total_protocol =
+                            protocol_dict_decoders_feed(dict, false, duration - pulse);
+                    }
+
+                    if(total_protocol != PROTOCOL_NO) {
+                        printf(" <FOUND %s>", protocol_dict_get_name(dict, total_protocol));
+                    }
+                }
+
+                printf("\r\n");
+
+                total_pulse += pulse;
+                total_duration += duration;
+
+                if(total_protocol != PROTOCOL_NO) {
+                    break;
+                }
+            } else {
+                printf("Failed to read pair\r\n");
+                break;
+            }
+        }
+
+        printf("   Frequency: %f\r\n", (double)frequency);
+        printf("  Duty Cycle: %f\r\n", (double)duty_cycle);
+        printf("       Warns: %ld\r\n", total_warns);
+        printf("   Pulse sum: %ld\r\n", total_pulse);
+        printf("Duration sum: %ld\r\n", total_duration);
+        printf("     Average: %f\r\n", (double)((float)total_pulse / (float)total_duration));
+        printf("    Protocol: ");
+
+        if(total_protocol != PROTOCOL_NO) {
+            size_t data_size = protocol_dict_get_data_size(dict, total_protocol);
+            uint8_t* data = malloc(data_size);
+            protocol_dict_get_data(dict, total_protocol, data, data_size);
+
+            printf("%s [", protocol_dict_get_name(dict, total_protocol));
+            for(size_t i = 0; i < data_size; i++) {
+                printf("%02X", data[i]);
+                if(i < data_size - 1) {
+                    printf(" ");
+                }
+            }
+            printf("]\r\n");
+
+            protocol_dict_render_data(dict, info_string, total_protocol);
+            printf("%s\r\n", string_get_cstr(info_string));
+
+            free(data);
+        } else {
+            printf("not found\r\n");
+        }
+
+        protocol_dict_free(dict);
+    } while(false);
+
+    string_clear(filepath);
+    string_clear(info_string);
+    lfrfid_raw_file_free(file);
+    furi_record_close(RECORD_STORAGE);
+}
+
+static void lfrfid_cli_raw_read_callback(LFRFIDWorkerReadRawResult result, void* context) {
+    furi_assert(context);
+    FuriEventFlag* event = context;
+    furi_event_flag_set(event, 1 << result);
+}
+
+static void lfrfid_cli_raw_read(Cli* cli, string_t args) {
+    UNUSED(cli);
+
+    string_t filepath, type_string;
+    string_init(filepath);
+    string_init(type_string);
+    LFRFIDWorkerReadType type = LFRFIDWorkerReadTypeAuto;
+
+    do {
+        if(args_read_string_and_trim(args, type_string)) {
+            if(string_cmp_str(type_string, "normal") == 0 ||
+               string_cmp_str(type_string, "ask") == 0) {
+                // ask
+                type = LFRFIDWorkerReadTypeASKOnly;
+            } else if(
+                string_cmp_str(type_string, "indala") == 0 ||
+                string_cmp_str(type_string, "psk") == 0) {
+                // psk
+                type = LFRFIDWorkerReadTypePSKOnly;
+            } else {
+                lfrfid_cli_print_usage();
+                break;
+            }
+        }
+
+        if(!args_read_probably_quoted_string_and_trim(args, filepath)) {
+            lfrfid_cli_print_usage();
+            break;
+        }
+
+        ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
+        LFRFIDWorker* worker = lfrfid_worker_alloc(dict);
+        FuriEventFlag* event = furi_event_flag_alloc();
+
+        lfrfid_worker_start_thread(worker);
+
+        bool overrun = false;
+
+        const uint32_t available_flags = (1 << LFRFIDWorkerReadRawFileError) |
+                                         (1 << LFRFIDWorkerReadRawOverrun);
+
+        lfrfid_worker_read_raw_start(
+            worker, string_get_cstr(filepath), type, lfrfid_cli_raw_read_callback, event);
+        while(true) {
+            uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100);
+
+            if(flags != FuriFlagErrorTimeout) {
+                if(FURI_BIT(flags, LFRFIDWorkerReadRawFileError)) {
+                    printf("File is not RFID raw file\r\n");
+                    break;
+                }
+
+                if(FURI_BIT(flags, LFRFIDWorkerReadRawOverrun)) {
+                    if(!overrun) {
+                        printf("Overrun\r\n");
+                        overrun = true;
+                    }
+                }
+            }
+
+            if(cli_cmd_interrupt_received(cli)) break;
+        }
+
+        if(overrun) {
+            printf("An overrun occurred during read\r\n");
+        }
+
+        lfrfid_worker_stop(worker);
+
+        lfrfid_worker_stop_thread(worker);
+        lfrfid_worker_free(worker);
+        protocol_dict_free(dict);
+
+        furi_event_flag_free(event);
+
+    } while(false);
+
+    string_clear(filepath);
+    string_clear(type_string);
+}
+
+static void lfrfid_cli_raw_emulate_callback(LFRFIDWorkerEmulateRawResult result, void* context) {
+    furi_assert(context);
+    FuriEventFlag* event = context;
+    furi_event_flag_set(event, 1 << result);
+}
+
+static void lfrfid_cli_raw_emulate(Cli* cli, string_t args) {
+    UNUSED(cli);
+
+    string_t filepath;
+    string_init(filepath);
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    do {
+        if(!args_read_probably_quoted_string_and_trim(args, filepath)) {
+            lfrfid_cli_print_usage();
+            break;
+        }
+
+        if(!storage_file_exists(storage, string_get_cstr(filepath))) {
+            printf("File not found: \"%s\"\r\n", string_get_cstr(filepath));
+            break;
+        }
+
+        ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
+        LFRFIDWorker* worker = lfrfid_worker_alloc(dict);
+        FuriEventFlag* event = furi_event_flag_alloc();
+
+        lfrfid_worker_start_thread(worker);
+
+        bool overrun = false;
+
+        const uint32_t available_flags = (1 << LFRFIDWorkerEmulateRawFileError) |
+                                         (1 << LFRFIDWorkerEmulateRawOverrun);
+
+        lfrfid_worker_emulate_raw_start(
+            worker, string_get_cstr(filepath), lfrfid_cli_raw_emulate_callback, event);
+        while(true) {
+            uint32_t flags = furi_event_flag_wait(event, available_flags, FuriFlagWaitAny, 100);
+
+            if(flags != FuriFlagErrorTimeout) {
+                if(FURI_BIT(flags, LFRFIDWorkerEmulateRawFileError)) {
+                    printf("File is not RFID raw file\r\n");
+                    break;
+                }
+
+                if(FURI_BIT(flags, LFRFIDWorkerEmulateRawOverrun)) {
+                    if(!overrun) {
+                        printf("Overrun\r\n");
+                        overrun = true;
+                    }
+                }
+            }
+
+            if(cli_cmd_interrupt_received(cli)) break;
+        }
+
+        if(overrun) {
+            printf("An overrun occurred during emulation\r\n");
+        }
+
+        lfrfid_worker_stop(worker);
+
+        lfrfid_worker_stop_thread(worker);
+        lfrfid_worker_free(worker);
+        protocol_dict_free(dict);
+
+        furi_event_flag_free(event);
+
+    } while(false);
+
+    furi_record_close(RECORD_STORAGE);
+    string_clear(filepath);
+}
+
+static void lfrfid_cli(Cli* cli, string_t args, void* context) {
+    UNUSED(context);
+    string_t cmd;
+    string_init(cmd);
+
+    if(!args_read_string_and_trim(args, cmd)) {
+        string_clear(cmd);
+        lfrfid_cli_print_usage();
+        return;
+    }
+
+    if(string_cmp_str(cmd, "read") == 0) {
+        lfrfid_cli_read(cli, args);
+    } else if(string_cmp_str(cmd, "write") == 0) {
+        lfrfid_cli_write(cli, args);
+    } else if(string_cmp_str(cmd, "emulate") == 0) {
+        lfrfid_cli_emulate(cli, args);
+    } else if(string_cmp_str(cmd, "raw_read") == 0) {
+        lfrfid_cli_raw_read(cli, args);
+    } else if(string_cmp_str(cmd, "raw_emulate") == 0) {
+        lfrfid_cli_raw_emulate(cli, args);
+    } else if(string_cmp_str(cmd, "raw_analyze") == 0) {
+        lfrfid_cli_raw_analyze(cli, args);
+    } else {
+        lfrfid_cli_print_usage();
+    }
+
+    string_clear(cmd);
+}

+ 0 - 177
applications/lfrfid/lfrfid_cli.cpp

@@ -1,177 +0,0 @@
-#include <furi.h>
-#include <furi_hal.h>
-#include <stdarg.h>
-#include <cli/cli.h>
-#include <lib/toolbox/args.h>
-
-#include "helpers/rfid_reader.h"
-#include "helpers/rfid_timer_emulator.h"
-
-static void lfrfid_cli(Cli* cli, string_t args, void* context);
-
-// app cli function
-extern "C" void lfrfid_on_system_start() {
-#ifdef SRV_CLI
-    Cli* cli = static_cast<Cli*>(furi_record_open("cli"));
-    cli_add_command(cli, "rfid", CliCommandFlagDefault, lfrfid_cli, NULL);
-    furi_record_close("cli");
-#else
-    UNUSED(lfrfid_cli);
-#endif
-}
-
-void lfrfid_cli_print_usage() {
-    printf("Usage:\r\n");
-    printf("rfid read <optional: normal | indala>\r\n");
-    printf("rfid <write | emulate> <key_type> <key_data>\r\n");
-    printf("\t<key_type> choose from:\r\n");
-    printf("\tEM4100, EM-Marin (5 bytes key_data)\r\n");
-    printf("\tH10301, HID26 (3 bytes key_data)\r\n");
-    printf("\tI40134, Indala (3 bytes key_data)\r\n");
-    printf("\tIoProxXSF, IoProx (4 bytes key_data)\r\n");
-    printf("\t<key_data> are hex-formatted\r\n");
-};
-
-static bool lfrfid_cli_get_key_type(string_t data, LfrfidKeyType* type) {
-    bool result = false;
-
-    if(string_cmp_str(data, "EM4100") == 0 || string_cmp_str(data, "EM-Marin") == 0) {
-        result = true;
-        *type = LfrfidKeyType::KeyEM4100;
-    } else if(string_cmp_str(data, "H10301") == 0 || string_cmp_str(data, "HID26") == 0) {
-        result = true;
-        *type = LfrfidKeyType::KeyH10301;
-    } else if(string_cmp_str(data, "I40134") == 0 || string_cmp_str(data, "Indala") == 0) {
-        result = true;
-        *type = LfrfidKeyType::KeyI40134;
-    } else if(string_cmp_str(data, "IoProxXSF") == 0 || string_cmp_str(data, "IoProx") == 0) {
-        result = true;
-        *type = LfrfidKeyType::KeyIoProxXSF;
-    }
-
-    return result;
-}
-
-static void lfrfid_cli_read(Cli* cli, string_t args) {
-    RfidReader reader;
-    string_t type_string;
-    string_init(type_string);
-    bool simple_mode = true;
-    LfrfidKeyType type;
-    RfidReader::Type reader_type = RfidReader::Type::Normal;
-    static const uint8_t data_size = LFRFID_KEY_SIZE;
-    uint8_t data[data_size] = {0};
-
-    if(args_read_string_and_trim(args, type_string)) {
-        simple_mode = false;
-
-        if(string_cmp_str(type_string, "normal") == 0) {
-            reader_type = RfidReader::Type::Normal;
-        } else if(string_cmp_str(type_string, "indala") == 0) {
-            reader_type = RfidReader::Type::Indala;
-        } else {
-            lfrfid_cli_print_usage();
-            string_clear(type_string);
-            return;
-        }
-    }
-
-    if(simple_mode) {
-        reader.start();
-    } else {
-        reader.start_forced(reader_type);
-    }
-
-    printf("Reading RFID...\r\nPress Ctrl+C to abort\r\n");
-    while(!cli_cmd_interrupt_received(cli)) {
-        if(reader.read(&type, data, data_size, simple_mode)) {
-            printf("%s", lfrfid_key_get_type_string(type));
-            printf(" ");
-
-            for(uint8_t i = 0; i < lfrfid_key_get_type_data_count(type); i++) {
-                printf("%02X", data[i]);
-            }
-            printf("\r\n");
-            break;
-        }
-        furi_delay_ms(100);
-    }
-
-    printf("Reading stopped\r\n");
-    reader.stop();
-
-    string_clear(type_string);
-}
-
-static void lfrfid_cli_write(Cli* cli, string_t args) {
-    UNUSED(cli);
-    UNUSED(args);
-    // TODO implement rfid write
-    printf("Not Implemented :(\r\n");
-}
-
-static void lfrfid_cli_emulate(Cli* cli, string_t args) {
-    string_t data;
-    string_init(data);
-    RfidTimerEmulator emulator;
-
-    static const uint8_t data_size = LFRFID_KEY_SIZE;
-    uint8_t key_data[data_size] = {0};
-    uint8_t key_data_size = 0;
-    LfrfidKeyType type;
-
-    if(!args_read_string_and_trim(args, data)) {
-        lfrfid_cli_print_usage();
-        string_clear(data);
-        return;
-    }
-
-    if(!lfrfid_cli_get_key_type(data, &type)) {
-        lfrfid_cli_print_usage();
-        string_clear(data);
-        return;
-    }
-
-    key_data_size = lfrfid_key_get_type_data_count(type);
-
-    if(!args_read_hex_bytes(args, key_data, key_data_size)) {
-        lfrfid_cli_print_usage();
-        string_clear(data);
-        return;
-    }
-
-    emulator.start(type, key_data, key_data_size);
-
-    printf("Emulating RFID...\r\nPress Ctrl+C to abort\r\n");
-    while(!cli_cmd_interrupt_received(cli)) {
-        furi_delay_ms(100);
-    }
-    printf("Emulation stopped\r\n");
-    emulator.stop();
-
-    string_clear(data);
-}
-
-static void lfrfid_cli(Cli* cli, string_t args, void* context) {
-    UNUSED(context);
-    string_t cmd;
-    string_init(cmd);
-
-    if(!args_read_string_and_trim(args, cmd)) {
-        string_clear(cmd);
-        lfrfid_cli_print_usage();
-        return;
-    }
-
-    if(string_cmp_str(cmd, "read") == 0) {
-        lfrfid_cli_read(cli, args);
-    } else if(string_cmp_str(cmd, "write") == 0) {
-        lfrfid_cli_write(cli, args);
-    } else if(string_cmp_str(cmd, "emulate") == 0) {
-        lfrfid_cli_emulate(cli, args);
-    } else {
-        lfrfid_cli_print_usage();
-    }
-
-    string_clear(cmd);
-}

+ 11 - 36
applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.cpp

@@ -5,7 +5,6 @@
 
 void LfRfidAppSceneDeleteConfirm::on_enter(LfRfidApp* app, bool /* need_restore */) {
     string_init(string_data);
-    string_init(string_decrypted);
     string_init(string_header);
 
     auto container = app->view_controller.get<ContainerVM>();
@@ -21,49 +20,26 @@ void LfRfidAppSceneDeleteConfirm::on_enter(LfRfidApp* app, bool /* need_restore
     auto line_1 = container->add<StringElement>();
     auto line_2 = container->add<StringElement>();
     auto line_3 = container->add<StringElement>();
-    auto line_4 = container->add<StringElement>();
 
-    RfidKey& key = app->worker.key;
-    const uint8_t* data = key.get_data();
-
-    for(uint8_t i = 0; i < key.get_type_data_count(); i++) {
+    size_t size = protocol_dict_get_data_size(app->dict, app->protocol_id);
+    uint8_t* data = (uint8_t*)malloc(size);
+    protocol_dict_get_data(app->dict, app->protocol_id, data, size);
+    for(uint8_t i = 0; i < MIN(size, (size_t)8); i++) {
         if(i != 0) {
             string_cat_printf(string_data, " ");
         }
+
         string_cat_printf(string_data, "%02X", data[i]);
     }
+    free(data);
 
-    string_printf(string_header, "Delete %s?", key.get_name());
+    string_printf(string_header, "Delete %s?", string_get_cstr(app->file_name));
     line_1->set_text(
-        string_get_cstr(string_header), 64, 19, 128 - 2, AlignCenter, AlignBottom, FontPrimary);
+        string_get_cstr(string_header), 64, 0, 128 - 2, AlignCenter, AlignTop, FontPrimary);
     line_2->set_text(
-        string_get_cstr(string_data), 64, 29, 0, AlignCenter, AlignBottom, FontSecondary);
-
-    switch(key.get_type()) {
-    case LfrfidKeyType::KeyEM4100:
-        string_printf(
-            string_decrypted, "%03u,%05u", data[2], (uint16_t)((data[3] << 8) | (data[4])));
-
-        break;
-    case LfrfidKeyType::KeyH10301:
-    case LfrfidKeyType::KeyI40134:
-        string_printf(
-            string_decrypted, "FC: %u    ID: %u", data[0], (uint16_t)((data[1] << 8) | (data[2])));
-        break;
-    case LfrfidKeyType::KeyIoProxXSF:
-        string_printf(
-            string_decrypted,
-            "FC: %u   VC: %u   ID: %u",
-            data[0],
-            data[1],
-            (uint16_t)((data[2] << 8) | (data[3])));
-        break;
-    }
+        string_get_cstr(string_data), 64, 19, 0, AlignCenter, AlignTop, FontSecondary);
     line_3->set_text(
-        string_get_cstr(string_decrypted), 64, 39, 0, AlignCenter, AlignBottom, FontSecondary);
-
-    line_4->set_text(
-        lfrfid_key_get_type_string(key.get_type()),
+        protocol_dict_get_name(app->dict, app->protocol_id),
         64,
         49,
         0,
@@ -78,7 +54,7 @@ bool LfRfidAppSceneDeleteConfirm::on_event(LfRfidApp* app, LfRfidApp::Event* eve
     bool consumed = false;
 
     if(event->type == LfRfidApp::EventType::Next) {
-        app->delete_key(&app->worker.key);
+        app->delete_key();
         app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::DeleteSuccess);
         consumed = true;
     } else if(event->type == LfRfidApp::EventType::Stay) {
@@ -94,7 +70,6 @@ bool LfRfidAppSceneDeleteConfirm::on_event(LfRfidApp* app, LfRfidApp::Event* eve
 void LfRfidAppSceneDeleteConfirm::on_exit(LfRfidApp* app) {
     app->view_controller.get<ContainerVM>()->clean();
     string_clear(string_data);
-    string_clear(string_decrypted);
     string_clear(string_header);
 }
 

+ 0 - 1
applications/lfrfid/scene/lfrfid_app_scene_delete_confirm.h

@@ -13,5 +13,4 @@ private:
 
     string_t string_header;
     string_t string_data;
-    string_t string_decrypted;
 };

+ 8 - 15
applications/lfrfid/scene/lfrfid_app_scene_emulate.cpp

@@ -3,28 +3,21 @@
 #include <dolphin/dolphin.h>
 
 void LfRfidAppSceneEmulate::on_enter(LfRfidApp* app, bool /* need_restore */) {
-    string_init(data_string);
-
     DOLPHIN_DEED(DolphinDeedRfidEmulate);
-    const uint8_t* data = app->worker.key.get_data();
-
-    for(uint8_t i = 0; i < app->worker.key.get_type_data_count(); i++) {
-        string_cat_printf(data_string, "%02X", data[i]);
-    }
-
     auto popup = app->view_controller.get<PopupVM>();
 
     popup->set_header("Emulating", 89, 30, AlignCenter, AlignTop);
-    if(strlen(app->worker.key.get_name())) {
-        popup->set_text(app->worker.key.get_name(), 89, 43, AlignCenter, AlignTop);
+    if(string_size(app->file_name)) {
+        popup->set_text(string_get_cstr(app->file_name), 89, 43, AlignCenter, AlignTop);
     } else {
-        popup->set_text(string_get_cstr(data_string), 89, 43, AlignCenter, AlignTop);
+        popup->set_text(
+            protocol_dict_get_name(app->dict, app->protocol_id), 89, 43, AlignCenter, AlignTop);
     }
     popup->set_icon(0, 3, &I_RFIDDolphinSend_97x61);
 
     app->view_controller.switch_to<PopupVM>();
-    app->worker.start_emulate();
-
+    lfrfid_worker_start_thread(app->lfworker);
+    lfrfid_worker_emulate_start(app->lfworker, (LFRFIDProtocol)app->protocol_id);
     notification_message(app->notification, &sequence_blink_start_magenta);
 }
 
@@ -37,7 +30,7 @@ bool LfRfidAppSceneEmulate::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
 
 void LfRfidAppSceneEmulate::on_exit(LfRfidApp* app) {
     app->view_controller.get<PopupVM>()->clean();
-    app->worker.stop_emulate();
-    string_clear(data_string);
+    lfrfid_worker_stop(app->lfworker);
+    lfrfid_worker_stop_thread(app->lfworker);
     notification_message(app->notification, &sequence_blink_stop);
 }

+ 0 - 3
applications/lfrfid/scene/lfrfid_app_scene_emulate.h

@@ -6,7 +6,4 @@ public:
     void on_enter(LfRfidApp* app, bool need_restore) final;
     bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
     void on_exit(LfRfidApp* app) final;
-
-private:
-    string_t data_string;
 };

+ 63 - 0
applications/lfrfid/scene/lfrfid_app_scene_extra_actions.cpp

@@ -0,0 +1,63 @@
+#include "lfrfid_app_scene_extra_actions.h"
+
+typedef enum {
+    SubmenuASK,
+    SubmenuPSK,
+    SubmenuRAW,
+} SubmenuIndex;
+
+void LfRfidAppSceneExtraActions::on_enter(LfRfidApp* app, bool need_restore) {
+    auto submenu = app->view_controller.get<SubmenuVM>();
+
+    submenu->add_item("Read ASK (Animal, Ordinary Card)", SubmenuASK, submenu_callback, app);
+    submenu->add_item("Read PSK (Indala)", SubmenuPSK, submenu_callback, app);
+
+    if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
+        submenu->add_item("Read RAW RFID data", SubmenuRAW, submenu_callback, app);
+    }
+
+    if(need_restore) {
+        submenu->set_selected_item(submenu_item_selected);
+    }
+
+    app->view_controller.switch_to<SubmenuVM>();
+}
+
+bool LfRfidAppSceneExtraActions::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
+    bool consumed = false;
+
+    if(event->type == LfRfidApp::EventType::MenuSelected) {
+        submenu_item_selected = event->payload.signed_int;
+        switch(event->payload.signed_int) {
+        case SubmenuASK:
+            app->read_type = LFRFIDWorkerReadTypeASKOnly;
+            app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Read);
+            break;
+        case SubmenuPSK:
+            app->read_type = LFRFIDWorkerReadTypePSKOnly;
+            app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Read);
+            break;
+        case SubmenuRAW:
+            app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::RawName);
+            break;
+        }
+
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void LfRfidAppSceneExtraActions::on_exit(LfRfidApp* app) {
+    app->view_controller.get<SubmenuVM>()->clean();
+}
+
+void LfRfidAppSceneExtraActions::submenu_callback(void* context, uint32_t index) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(context);
+    LfRfidApp::Event event;
+
+    event.type = LfRfidApp::EventType::MenuSelected;
+    event.payload.signed_int = index;
+
+    app->view_controller.send_event(&event);
+}

+ 13 - 0
applications/lfrfid/scene/lfrfid_app_scene_extra_actions.h

@@ -0,0 +1,13 @@
+#pragma once
+#include "../lfrfid_app.h"
+
+class LfRfidAppSceneExtraActions : public GenericScene<LfRfidApp> {
+public:
+    void on_enter(LfRfidApp* app, bool need_restore) final;
+    bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
+    void on_exit(LfRfidApp* app) final;
+
+private:
+    static void submenu_callback(void* context, uint32_t index);
+    uint32_t submenu_item_selected = 0;
+};

+ 77 - 0
applications/lfrfid/scene/lfrfid_app_scene_raw_info.cpp

@@ -0,0 +1,77 @@
+#include "lfrfid_app_scene_raw_info.h"
+#include "../view/elements/button_element.h"
+#include "../view/elements/icon_element.h"
+#include "../view/elements/string_element.h"
+
+static void ok_callback(void* context) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(context);
+    LfRfidApp::Event event;
+    event.type = LfRfidApp::EventType::Next;
+    app->view_controller.send_event(&event);
+}
+
+static void back_callback(void* context) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(context);
+    LfRfidApp::Event event;
+    event.type = LfRfidApp::EventType::Back;
+    app->view_controller.send_event(&event);
+}
+
+void LfRfidAppSceneRawInfo::on_enter(LfRfidApp* app, bool /* need_restore */) {
+    string_init(string_info);
+
+    auto container = app->view_controller.get<ContainerVM>();
+
+    bool sd_exist = storage_sd_status(app->storage) == FSE_OK;
+    if(!sd_exist) {
+        auto icon = container->add<IconElement>();
+        icon->set_icon(0, 0, &I_SDQuestion_35x43);
+        auto line = container->add<StringElement>();
+        line->set_text(
+            "No SD card found.\nThis function will not\nwork without\nSD card.",
+            81,
+            4,
+            0,
+            AlignCenter,
+            AlignTop,
+            FontSecondary);
+
+        auto button = container->add<ButtonElement>();
+        button->set_type(ButtonElement::Type::Left, "Back");
+        button->set_callback(app, back_callback);
+    } else {
+        string_printf(
+            string_info,
+            "RAW RFID data reader\r\n"
+            "1) Put the Flipper on your card\r\n"
+            "2) Press OK\r\n"
+            "3) Wait until data is read");
+
+        auto line = container->add<StringElement>();
+        line->set_text(string_get_cstr(string_info), 0, 1, 0, AlignLeft, AlignTop, FontSecondary);
+
+        auto button = container->add<ButtonElement>();
+        button->set_type(ButtonElement::Type::Center, "OK");
+        button->set_callback(app, ok_callback);
+    }
+
+    app->view_controller.switch_to<ContainerVM>();
+}
+
+bool LfRfidAppSceneRawInfo::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
+    bool consumed = false;
+    if(event->type == LfRfidApp::EventType::Next) {
+        app->scene_controller.switch_to_scene({LfRfidApp::SceneType::RawRead});
+        consumed = true;
+    } else if(event->type == LfRfidApp::EventType::Back) {
+        app->scene_controller.search_and_switch_to_previous_scene(
+            {LfRfidApp::SceneType::ExtraActions});
+        consumed = true;
+    }
+    return consumed;
+}
+
+void LfRfidAppSceneRawInfo::on_exit(LfRfidApp* app) {
+    app->view_controller.get<ContainerVM>()->clean();
+    string_clear(string_info);
+}

+ 12 - 0
applications/lfrfid/scene/lfrfid_app_scene_raw_info.h

@@ -0,0 +1,12 @@
+#pragma once
+#include "../lfrfid_app.h"
+
+class LfRfidAppSceneRawInfo : public GenericScene<LfRfidApp> {
+public:
+    void on_enter(LfRfidApp* app, bool need_restore) final;
+    bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
+    void on_exit(LfRfidApp* app) final;
+
+private:
+    string_t string_info;
+};

+ 46 - 0
applications/lfrfid/scene/lfrfid_app_scene_raw_name.cpp

@@ -0,0 +1,46 @@
+
+#include "lfrfid_app_scene_raw_name.h"
+#include "m-string.h"
+#include <lib/toolbox/random_name.h>
+#include <lib/toolbox/path.h>
+
+void LfRfidAppSceneRawName::on_enter(LfRfidApp* app, bool /* need_restore */) {
+    const char* key_name = string_get_cstr(app->raw_file_name);
+
+    bool key_name_empty = (string_size(app->raw_file_name) == 0);
+    if(key_name_empty) {
+        app->text_store.set("RfidRecord");
+    } else {
+        app->text_store.set("%s", key_name);
+    }
+
+    auto text_input = app->view_controller.get<TextInputVM>();
+    text_input->set_header_text("Name the raw file");
+
+    text_input->set_result_callback(
+        save_callback, app, app->text_store.text, LFRFID_KEY_NAME_SIZE, key_name_empty);
+
+    app->view_controller.switch_to<TextInputVM>();
+}
+
+bool LfRfidAppSceneRawName::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
+    bool consumed = false;
+
+    if(event->type == LfRfidApp::EventType::Next) {
+        string_set_str(app->raw_file_name, app->text_store.text);
+        app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::RawInfo);
+    }
+
+    return consumed;
+}
+
+void LfRfidAppSceneRawName::on_exit(LfRfidApp* app) {
+    app->view_controller.get<TextInputVM>()->clean();
+}
+
+void LfRfidAppSceneRawName::save_callback(void* context) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(context);
+    LfRfidApp::Event event;
+    event.type = LfRfidApp::EventType::Next;
+    app->view_controller.send_event(&event);
+}

+ 12 - 0
applications/lfrfid/scene/lfrfid_app_scene_raw_name.h

@@ -0,0 +1,12 @@
+#pragma once
+#include "../lfrfid_app.h"
+
+class LfRfidAppSceneRawName : public GenericScene<LfRfidApp> {
+public:
+    void on_enter(LfRfidApp* app, bool need_restore) final;
+    bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
+    void on_exit(LfRfidApp* app) final;
+
+private:
+    static void save_callback(void* context);
+};

+ 107 - 0
applications/lfrfid/scene/lfrfid_app_scene_raw_read.cpp

@@ -0,0 +1,107 @@
+#include "lfrfid_app_scene_raw_read.h"
+#include <dolphin/dolphin.h>
+
+#define RAW_READ_TIME 5000
+
+static void lfrfid_read_callback(LFRFIDWorkerReadRawResult result, void* ctx) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(ctx);
+    LfRfidApp::Event event;
+
+    switch(result) {
+    case LFRFIDWorkerReadRawFileError:
+        event.type = LfRfidApp::EventType::ReadEventError;
+        break;
+    case LFRFIDWorkerReadRawOverrun:
+        event.type = LfRfidApp::EventType::ReadEventOverrun;
+        break;
+    }
+
+    app->view_controller.send_event(&event);
+}
+
+static void timer_callback(void* ctx) {
+    LfRfidApp* app = static_cast<LfRfidApp*>(ctx);
+    LfRfidApp::Event event;
+    event.type = LfRfidApp::EventType::ReadEventDone;
+    app->view_controller.send_event(&event);
+}
+
+void LfRfidAppSceneRawRead::on_enter(LfRfidApp* app, bool /* need_restore */) {
+    string_init(string_file_name);
+    auto popup = app->view_controller.get<PopupVM>();
+    popup->set_icon(0, 3, &I_RFIDDolphinReceive_97x61);
+    app->view_controller.switch_to<PopupVM>();
+    lfrfid_worker_start_thread(app->lfworker);
+    app->make_app_folder();
+
+    timer = furi_timer_alloc(timer_callback, FuriTimerTypeOnce, app);
+    furi_timer_start(timer, RAW_READ_TIME);
+    string_printf(
+        string_file_name, "%s/%s.ask.raw", app->app_sd_folder, string_get_cstr(app->raw_file_name));
+    popup->set_header("Reading\nRAW RFID\nASK", 89, 30, AlignCenter, AlignTop);
+    lfrfid_worker_read_raw_start(
+        app->lfworker,
+        string_get_cstr(string_file_name),
+        LFRFIDWorkerReadTypeASKOnly,
+        lfrfid_read_callback,
+        app);
+
+    notification_message(app->notification, &sequence_blink_start_cyan);
+
+    is_psk = false;
+    error = false;
+}
+
+bool LfRfidAppSceneRawRead::on_event(LfRfidApp* app, LfRfidApp::Event* event) {
+    UNUSED(app);
+    bool consumed = true;
+    auto popup = app->view_controller.get<PopupVM>();
+
+    switch(event->type) {
+    case LfRfidApp::EventType::ReadEventError:
+        error = true;
+        popup->set_header("Reading\nRAW RFID\nFile error", 89, 30, AlignCenter, AlignTop);
+        notification_message(app->notification, &sequence_blink_start_red);
+        furi_timer_stop(timer);
+        break;
+    case LfRfidApp::EventType::ReadEventDone:
+        if(!error) {
+            if(is_psk) {
+                notification_message(app->notification, &sequence_success);
+                app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::RawSuccess);
+            } else {
+                popup->set_header("Reading\nRAW RFID\nPSK", 89, 30, AlignCenter, AlignTop);
+                notification_message(app->notification, &sequence_blink_start_yellow);
+                lfrfid_worker_stop(app->lfworker);
+                string_printf(
+                    string_file_name,
+                    "%s/%s.psk.raw",
+                    app->app_sd_folder,
+                    string_get_cstr(app->raw_file_name));
+                lfrfid_worker_read_raw_start(
+                    app->lfworker,
+                    string_get_cstr(string_file_name),
+                    LFRFIDWorkerReadTypePSKOnly,
+                    lfrfid_read_callback,
+                    app);
+                furi_timer_start(timer, RAW_READ_TIME);
+                is_psk = true;
+            }
+        }
+        break;
+    default:
+        consumed = false;
+        break;
+    }
+
+    return consumed;
+}
+
+void LfRfidAppSceneRawRead::on_exit(LfRfidApp* app) {
+    notification_message(app->notification, &sequence_blink_stop);
+    app->view_controller.get<PopupVM>()->clean();
+    lfrfid_worker_stop(app->lfworker);
+    lfrfid_worker_stop_thread(app->lfworker);
+    furi_timer_free(timer);
+    string_clear(string_file_name);
+}

+ 15 - 0
applications/lfrfid/scene/lfrfid_app_scene_raw_read.h

@@ -0,0 +1,15 @@
+#pragma once
+#include "../lfrfid_app.h"
+
+class LfRfidAppSceneRawRead : public GenericScene<LfRfidApp> {
+public:
+    void on_enter(LfRfidApp* app, bool need_restore) final;
+    bool on_event(LfRfidApp* app, LfRfidApp::Event* event) final;
+    void on_exit(LfRfidApp* app) final;
+
+private:
+    string_t string_file_name;
+    FuriTimer* timer;
+    bool is_psk;
+    bool error;
+};

Некоторые файлы не были показаны из-за большого количества измененных файлов