Merge branch 'dev' into feature/orgasmotron2
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 43 KiB |
@@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
|
||||
# Video in embedded:
|
||||
https://user-images.githubusercontent.com/55334727/213319502-a4886716-bc00-4f42-84ab-31ccd48d10ea.mp4
|
||||
@@ -0,0 +1,19 @@
|
||||
Hello there. I will happily provide some more information. Two quick notes first tho: 1. These files have recently moved, to the same place just behind an additional "NSFW" labeled folder. 2. I'm not sure if this email reply handles attachments and I can't seem to find this ticket on my github account, and don't want to risk anything, so all mentioned attachments will be [here](https://github.com/ClaraCrazy/Flipper-Xtreme/tree/dev/.github/dont%20touch) safely within a github folder of the development branch.
|
||||
|
||||
Now, let's talk about the theme. Have a look at the "Social Preview" banner that is set for this repository, and have a read through the "About" text that is to be found on the top right of the repo. (`The Dom amongst the Flipper Zero Firmware. Give your Flipper the power and freedom it is really craving. Let it show you its true form. Don't delay, switch to the one and only true Master today!`) This is where the theme starts. It clearly has a dominant touch, while also still making sure that everyone understands that this is Firmware, not some porn ad.
|
||||
|
||||
Implementation is simple. These animations can be seen on the main screen. See [Attachment_01.mp4](https://github.com/ClaraCrazy/Flipper-Xtreme/blob/dev/.github/dont%20touch/readme.md#video-in-embedded) This short video also shows how it can be turned off. Note that this feature is in the dev version. In the current release, there is no "Xtreme settings" application yet, but rather just the one toggle for "SFW mode", in case you try to find it in the code, its current implementation can be found [here](https://github.com/ClaraCrazy/Flipper-Xtreme/blob/main/applications/services/desktop/views/desktop_view_lock_menu.c#L75) Now, additionally to this purely dominant aspect, we have also noticed that people would use their device a lot more, as our implementation allows the User to unlock a new animation with each level, hence the folder names "lvl_01", "lvl_02"... "lvl_30". As seen in the previously mentioned attachment, these animations ofc go away and will reveal the assets used in the official firmware, for those that purely want our other features but no NSFW content, or simply can't have it enabled all the time due to whatever reason they may have.
|
||||
|
||||
At this point, it should be noted that the end-user, in almost all cases, is of legal age. The tool isn't that cheap and as it's a pentesting device you would not find millions of kids using it otherwise, we would not be doing this either. While you probably don't care about off-platform content, we make sure of that on our community spaces like the Discord too, not allowing entry for underage people and recommending cleaner firmware for them.
|
||||
|
||||
Back to assets, we also simply have my personal artistic style. I am a rather open person when it comes to topics like that, and being allowed to work freely on a project like that, able to just throw in my own touch feels great. I don't do any adult content, not that kind of person, but simply having this little touch of "hey, here's me with this whip, for all you crazy pentesters with a boring job out there" really makes me smile.
|
||||
|
||||
And it makes the community smile too. I have asked for some feedback on my discord, and even tho its midnight right now for most people in this community space, I already gathered a few responses for you to read, located [here]() I got these in a span of only a few minutes after simply asking for feedback. As you can see, it's certainly welcome. Mind you, I did not screenshot the comments of people simply saying "looks nice", because that's not the feedback we are after in this case. A further indicator is the incredible 650+ github stars by now, which is more than I ever expected. The community server is at 2k people as well, this is not a niche thing 3 people like, it's welcomed in the industry.
|
||||
|
||||
I get that this is github, not pornhub, but it's not like we are displaying full-on sex tapes on the readme, or exposing 15-year-olds to this stuff, that will just download it to their phone. It's hidden in the repository, it's on a device made for pentesting, and it's especially not sexual violence &/ including minors as the original report said. Now, I hope you guys have what you are looking for. Please come back to me if not. I'm not trying to mess with anyone, or create a shocking response, or anything like that. This is me writing firmware with a simple twist on a boring system that is welcomed by its community and I hope you can see that.
|
||||
|
||||
Have a wonderful rest of your day, sorry for the wall of text
|
||||
|
||||
|
||||
Kind Regards
|
||||
Clara K. aka. ClaraCrazy
|
||||
@@ -0,0 +1,55 @@
|
||||
name: SonarCloud
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
jobs:
|
||||
build:
|
||||
name: Build and analyze
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SONAR_SCANNER_VERSION: 4.7.0.2747
|
||||
SONAR_SERVER_URL: "https://sonarcloud.io"
|
||||
BUILD_WRAPPER_OUT_DIR: "$HOME/.sonar/build_wrapper_output" # Directory where build-wrapper output will be placed
|
||||
FBT_NO_SYNC: "true"
|
||||
TARGETS: f7
|
||||
DEFAULT_TARGET: f7
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
if [ -d .git ]; then
|
||||
git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)"
|
||||
fi
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
submodules: 'recursive'
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- name: Download and set up sonar-scanner
|
||||
env:
|
||||
SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip
|
||||
run: |
|
||||
mkdir -p $HOME/.sonar
|
||||
curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }}
|
||||
unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
|
||||
echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH
|
||||
- name: Download and set up build-wrapper
|
||||
env:
|
||||
BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip
|
||||
run: |
|
||||
curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }}
|
||||
unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/
|
||||
echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH
|
||||
- name: Run build-wrapper
|
||||
run: |
|
||||
mkdir $HOME/.sonar/build_wrapper_output
|
||||
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} ./sonar-build
|
||||
- name: Run sonar-scanner
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
run: |
|
||||
sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}"
|
||||
@@ -0,0 +1,94 @@
|
||||
name: 'Static C/C++ analysis with PVS-Studio'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- "release*"
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TARGETS: f7
|
||||
DEFAULT_TARGET: f7
|
||||
FBT_TOOLCHAIN_PATH: /runner/_work
|
||||
|
||||
jobs:
|
||||
analyse_c_cpp:
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
runs-on: [self-hosted, FlipperZeroShell]
|
||||
steps:
|
||||
- name: 'Decontaminate previous build leftovers'
|
||||
run: |
|
||||
if [ -d .git ]; then
|
||||
git submodule status || git checkout "$(git rev-list --max-parents=0 HEAD | tail -n 1)"
|
||||
fi
|
||||
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: 'Get commit details'
|
||||
id: names
|
||||
run: |
|
||||
if [[ ${{ github.event_name }} == 'pull_request' ]]; then
|
||||
TYPE="pull"
|
||||
elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
|
||||
TYPE="tag"
|
||||
else
|
||||
TYPE="other"
|
||||
fi
|
||||
python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE"
|
||||
|
||||
- name: 'Supply PVS credentials'
|
||||
run: |
|
||||
pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }}
|
||||
|
||||
- name: 'Convert PVS-Studio output to html and detect warnings'
|
||||
id: pvs-warn
|
||||
run: |
|
||||
WARNINGS=0
|
||||
./fbt COMPACT=1 PVSNOBROWSER=1 firmware_pvs || WARNINGS=1
|
||||
echo "warnings=${WARNINGS}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 'Upload artifacts to update server'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork && (steps.pvs-warn.outputs.warnings != 0) }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -p ${{ secrets.RSYNC_DEPLOY_PORT }} -H ${{ secrets.RSYNC_DEPLOY_HOST }} > ~/.ssh/known_hosts
|
||||
echo "${{ secrets.RSYNC_DEPLOY_KEY }}" > deploy_key;
|
||||
chmod 600 ./deploy_key;
|
||||
rsync -avrzP --mkpath \
|
||||
-e 'ssh -p ${{ secrets.RSYNC_DEPLOY_PORT }} -i ./deploy_key' \
|
||||
build/f7-firmware-DC/pvsreport/ ${{ secrets.RSYNC_DEPLOY_USER }}@${{ secrets.RSYNC_DEPLOY_HOST }}:/home/data/firmware-pvs-studio-report/"${BRANCH_NAME}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/";
|
||||
rm ./deploy_key;
|
||||
|
||||
- name: 'Find Previous Comment'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }}
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: 'PVS-Studio report for commit'
|
||||
|
||||
- name: 'Create or update comment'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork && github.event.pull_request && (steps.pvs-warn.outputs.warnings != 0) }}
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
**PVS-Studio report for commit `${{steps.names.outputs.commit_sha}}`:**
|
||||
- [Report](https://update.flipperzero.one/builds/firmware-pvs-studio-report/${{steps.names.outputs.branch_name}}/${{steps.names.outputs.default_target}}-${{steps.names.outputs.suffix}}/index.html)
|
||||
edit-mode: replace
|
||||
|
||||
- name: 'Raise exception'
|
||||
if: ${{ steps.pvs-warn.outputs.warnings != 0 }}
|
||||
run: |
|
||||
echo "Please fix all PVS varnings before merge"
|
||||
exit 1
|
||||
|
||||
@@ -1 +1 @@
|
||||
--rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/plugins/dap_link/lib/free-dap
|
||||
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/STM32CubeWB -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* -e applications/plugins/dap_link/lib/free-dap
|
||||
|
||||
@@ -105,6 +105,12 @@
|
||||
"type": "shell",
|
||||
"command": "./fbt COMPACT=1 DEBUG=0 FORCE=1 flash_usb_full"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Create PVS-Studio report",
|
||||
"group": "build",
|
||||
"type": "shell",
|
||||
"command": "./fbt firmware_pvs"
|
||||
},
|
||||
{
|
||||
"label": "[Debug] Build FAPs",
|
||||
"group": "build",
|
||||
|
||||
@@ -148,9 +148,12 @@ fap_dist = [
|
||||
for app_artifact in firmware_env["FW_EXTAPPS"].applications.values()
|
||||
),
|
||||
),
|
||||
distenv.Install(
|
||||
f"#/dist/{dist_dir}/apps",
|
||||
"#/assets/resources/apps",
|
||||
*(
|
||||
distenv.Install(
|
||||
f"#/dist/{dist_dir}/apps/{app_artifact.app.fap_category}",
|
||||
app_artifact.compact[0],
|
||||
)
|
||||
for app_artifact in firmware_env["FW_EXTAPPS"].applications.values()
|
||||
),
|
||||
]
|
||||
Depends(
|
||||
|
||||
@@ -31,7 +31,8 @@ void AccessorApp::run(void) {
|
||||
onewire_host_stop(onewire_host);
|
||||
}
|
||||
|
||||
AccessorApp::AccessorApp() {
|
||||
AccessorApp::AccessorApp()
|
||||
: text_store{0} {
|
||||
notification = static_cast<NotificationApp*>(furi_record_open(RECORD_NOTIFICATION));
|
||||
onewire_host = onewire_host_alloc();
|
||||
furi_hal_power_enable_otg();
|
||||
|
||||
@@ -171,9 +171,6 @@ bool WIEGAND::DoWiegandConversion() {
|
||||
return true;
|
||||
} else {
|
||||
_lastWiegand = sysTick;
|
||||
_bitCount = 0;
|
||||
_cardTemp = 0;
|
||||
_cardTempHigh = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
#include <gui/canvas.h>
|
||||
#include <gui/elements.h>
|
||||
|
||||
#include <lib/toolbox/float_tools.h>
|
||||
#include <m-array.h>
|
||||
#include <furi.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct BtTestParam {
|
||||
@@ -98,16 +101,16 @@ static void bt_test_draw_callback(Canvas* canvas, void* _model) {
|
||||
elements_scrollbar(canvas, model->position, BtTestParamArray_size(model->params));
|
||||
canvas_draw_str(canvas, 6, 60, model->message);
|
||||
if(model->state == BtTestStateStarted) {
|
||||
if(model->rssi != 0.0f) {
|
||||
if(!float_is_equal(model->rssi, 0.0f)) {
|
||||
snprintf(info_str, sizeof(info_str), "RSSI:%3.1f dB", (double)model->rssi);
|
||||
canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
|
||||
}
|
||||
} else if(model->state == BtTestStateStopped) {
|
||||
if(model->packets_num_rx) {
|
||||
snprintf(info_str, sizeof(info_str), "%ld pack rcv", model->packets_num_rx);
|
||||
snprintf(info_str, sizeof(info_str), "%" PRIu32 " pack rcv", model->packets_num_rx);
|
||||
canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
|
||||
} else if(model->packets_num_tx) {
|
||||
snprintf(info_str, sizeof(info_str), "%ld pack sent", model->packets_num_tx);
|
||||
snprintf(info_str, sizeof(info_str), "%" PRIu32 " pack sent", model->packets_num_tx);
|
||||
canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
|
||||
}
|
||||
}
|
||||
@@ -153,7 +156,7 @@ static bool bt_test_input_callback(InputEvent* event, void* context) {
|
||||
}
|
||||
|
||||
void bt_test_process_up(BtTest* bt_test) {
|
||||
with_view_model(
|
||||
with_view_model( // -V658
|
||||
bt_test->view,
|
||||
BtTestModel * model,
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@ FileBrowserApp* file_browser_app_alloc(char* arg) {
|
||||
|
||||
app->file_path = furi_string_alloc();
|
||||
app->file_browser = file_browser_alloc(app->file_path);
|
||||
file_browser_configure(app->file_browser, "*", NULL, true, &I_badusb_10px, true);
|
||||
file_browser_configure(app->file_browser, "*", NULL, true, false, &I_badusb_10px, true);
|
||||
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, FileBrowserAppViewStart, widget_get_view(app->widget));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
App(
|
||||
appid="sample_apps",
|
||||
name="Sample apps bundle",
|
||||
appid="example_apps",
|
||||
name="Example apps bundle",
|
||||
apptype=FlipperAppType.METAPACKAGE,
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <storage/storage.h>
|
||||
#include <input/input.h>
|
||||
#include <gui/gui.h>
|
||||
#include <stdlib.h>
|
||||
@@ -10,24 +11,23 @@
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifndef PI
|
||||
#define PI 3.14159265358979f
|
||||
#endif
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#define TAG "Asteroids" // Used for logging
|
||||
#define DEBUG_MSG 1
|
||||
#define SCREEN_XRES 128
|
||||
#define SCREEN_YRES 64
|
||||
#define GAME_START_LIVES 3
|
||||
|
||||
/* The game uses the OK button both to fire and to accelerate the ship.
|
||||
* This makes it a lot more playable since the finger does not have to
|
||||
* move between two keys. However it is important that the extra time the
|
||||
* player needs to press the button to accelerate instead of just firing
|
||||
* is precisely selected to provide a smooth experience. After a few
|
||||
* attempts, it looks like 70 milliseconds is the right spot. */
|
||||
#define SHIP_ACCELERATION_KEYPRESS_TIME 70
|
||||
#define TTLBUL 30 /* Bullet time to live, in ticks. */
|
||||
#define MAXBUL 5 /* Max bullets on the screen. */
|
||||
#define MAXAST 32 /* Max asteroids on the screen. */
|
||||
#define SHIP_HIT_ANIMATION_LEN 15
|
||||
#define SAVING_DIRECTORY "/ext/apps_data/asteroids"
|
||||
#define SAVING_FILENAME SAVING_DIRECTORY "/game_asteroids.save"
|
||||
#ifndef PI
|
||||
#define PI 3.14159265358979f
|
||||
#endif
|
||||
|
||||
/* ============================ Data structures ============================= */
|
||||
|
||||
@@ -36,7 +36,7 @@ typedef struct Ship {
|
||||
y, /* Ship y position. */
|
||||
vx, /* x velocity. */
|
||||
vy, /* y velocity. */
|
||||
rot; /* Current rotation. 2*PI full rotation. */
|
||||
rot; /* Current rotation. 2*PI full ortation. */
|
||||
} Ship;
|
||||
|
||||
typedef struct Bullet {
|
||||
@@ -51,21 +51,20 @@ typedef struct Asteroid {
|
||||
uint8_t shape_seed; /* Seed to give random shape. */
|
||||
} Asteroid;
|
||||
|
||||
#define MAXBUL 10 /* Max bullets on the screen. */
|
||||
#define MAXAST 32 /* Max asteroids on the screen. */
|
||||
#define SHIP_HIT_ANIMATION_LEN 15
|
||||
typedef struct AsteroidsApp {
|
||||
/* GUI */
|
||||
Gui* gui;
|
||||
ViewPort* view_port; /* We just use a raw viewport and we render
|
||||
everything into the low level canvas. */
|
||||
FuriMessageQueue* event_queue; /* Key press events go here. */
|
||||
FuriMessageQueue* event_queue; /* Keypress events go here. */
|
||||
|
||||
/* Game state. */
|
||||
int running; /* Once false exists the app. */
|
||||
bool gameover; /* Game over status. */
|
||||
bool gameover; /* Gameover status. */
|
||||
uint32_t ticks; /* Game ticks. Increments at each refresh. */
|
||||
uint32_t score; /* Game score. */
|
||||
uint32_t highscore; /* Highscore. Shown on Game Over Screen */
|
||||
bool is_new_highscore; /* Is the last score a new highscore? */
|
||||
uint32_t lives; /* Number of lives in the current game. */
|
||||
uint32_t ship_hit; /* When non zero, the ship was hit by an asteroid
|
||||
and we need to show an animation as long as
|
||||
@@ -90,10 +89,55 @@ typedef struct AsteroidsApp {
|
||||
bool fire; /* Short press detected: fire a bullet. */
|
||||
} AsteroidsApp;
|
||||
|
||||
/* ============================== Prototypes ================================ */
|
||||
const NotificationSequence sequence_thrusters = {
|
||||
&message_vibro_on,
|
||||
&message_delay_10,
|
||||
&message_vibro_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
const NotificationSequence sequence_brake = {
|
||||
&message_vibro_on,
|
||||
&message_delay_10,
|
||||
&message_delay_1,
|
||||
&message_delay_1,
|
||||
&message_vibro_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
const NotificationSequence sequence_crash = {
|
||||
&message_red_255,
|
||||
|
||||
&message_vibro_on,
|
||||
// &message_note_g5, // Play sound but currently disabled
|
||||
&message_delay_25,
|
||||
// &message_note_e5,
|
||||
&message_vibro_off,
|
||||
&message_sound_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
const NotificationSequence sequence_bullet_fired = {
|
||||
&message_vibro_on,
|
||||
// &message_note_g5, // Play sound but currently disabled. Need On/Off menu setting
|
||||
&message_delay_10,
|
||||
&message_delay_1,
|
||||
&message_delay_1,
|
||||
&message_delay_1,
|
||||
&message_delay_1,
|
||||
&message_delay_1,
|
||||
|
||||
// &message_note_e5,
|
||||
&message_vibro_off,
|
||||
&message_sound_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* ============================== Prototyeps ================================ */
|
||||
|
||||
// Only functions called before their definition are here.
|
||||
|
||||
bool load_game(AsteroidsApp* app);
|
||||
void save_game(AsteroidsApp* app);
|
||||
void restart_game_after_gameover(AsteroidsApp* app);
|
||||
uint32_t key_pressed_time(AsteroidsApp* app, InputKey key);
|
||||
|
||||
@@ -114,7 +158,7 @@ Poly ShipPoly = {{-3, 0, 3}, {-3, 6, -3}, 3};
|
||||
|
||||
Poly ShipFirePoly = {{-1.5, 0, 1.5}, {-3, -6, -3}, 3};
|
||||
|
||||
/* Rotate the point of the polygon 'poly' and store the new rotated
|
||||
/* Rotate the point of the poligon 'poly' and store the new rotated
|
||||
* polygon in 'rot'. The polygon is rotated by an angle 'a', with
|
||||
* center at 0,0. */
|
||||
void rotate_poly(Poly* rot, Poly* poly, float a) {
|
||||
@@ -165,7 +209,7 @@ void draw_bullet(Canvas* const canvas, Bullet* b) {
|
||||
/* Draw an asteroid. The asteroid shapes is computed on the fly and
|
||||
* is not stored in a permanent shape structure. In order to generate
|
||||
* the shape, we use an initial fixed shape that we resize according
|
||||
* to the asteroid size, perturbed according to the asteroid shape
|
||||
* to the asteroid size, perturbate according to the asteroid shape
|
||||
* seed, and finally draw it rotated of the right amount. */
|
||||
void draw_asteroid(Canvas* const canvas, Asteroid* ast) {
|
||||
Poly ap;
|
||||
@@ -241,8 +285,11 @@ void render_callback(Canvas* const canvas, void* ctx) {
|
||||
|
||||
/* Draw ship, asteroids, bullets. */
|
||||
draw_poly(canvas, &ShipPoly, app->ship.x, app->ship.y, app->ship.rot);
|
||||
if(key_pressed_time(app, InputKeyOk) > SHIP_ACCELERATION_KEYPRESS_TIME)
|
||||
|
||||
if(key_pressed_time(app, InputKeyUp) > 0) {
|
||||
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_thrusters);
|
||||
draw_poly(canvas, &ShipFirePoly, app->ship.x, app->ship.y, app->ship.rot);
|
||||
}
|
||||
|
||||
for(int j = 0; j < app->bullets_num; j++) draw_bullet(canvas, &app->bullets[j]);
|
||||
|
||||
@@ -252,6 +299,30 @@ void render_callback(Canvas* const canvas, void* ctx) {
|
||||
if(app->gameover) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
// TODO: if new highscore, display blinking "New High Score"
|
||||
// Display High Score
|
||||
if(app->is_new_highscore) {
|
||||
canvas_draw_str(canvas, 22, 9, "New High Score!");
|
||||
} else {
|
||||
canvas_draw_str(canvas, 36, 9, "High Score");
|
||||
}
|
||||
|
||||
// Convert highscore to string
|
||||
int length = snprintf(NULL, 0, "%lu", app->highscore);
|
||||
char* str_high_score = malloc(length + 1);
|
||||
snprintf(str_high_score, length + 1, "%lu", app->highscore);
|
||||
|
||||
// Get length to center on screen
|
||||
int nDigits = 0;
|
||||
if(app->highscore > 0) {
|
||||
nDigits = floor(log10(app->highscore)) + 1;
|
||||
}
|
||||
|
||||
// Draw highscore centered
|
||||
canvas_draw_str(canvas, (SCREEN_XRES / 2) - (nDigits * 2), 20, str_high_score);
|
||||
free(str_high_score);
|
||||
|
||||
canvas_draw_str(canvas, 28, 35, "GAME OVER");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 25, 50, "Press OK to restart");
|
||||
@@ -295,6 +366,7 @@ bool objects_are_colliding(float x1, float y1, float r1, float x2, float y2, flo
|
||||
/* Create a new bullet headed in the same direction of the ship. */
|
||||
void ship_fire_bullet(AsteroidsApp* app) {
|
||||
if(app->bullets_num == MAXBUL) return;
|
||||
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_bullet_fired);
|
||||
Bullet* b = &app->bullets[app->bullets_num];
|
||||
b->x = app->ship.x;
|
||||
b->y = app->ship.y;
|
||||
@@ -316,7 +388,7 @@ void ship_fire_bullet(AsteroidsApp* app) {
|
||||
b->vx += app->ship.vx;
|
||||
b->vy += app->ship.vy;
|
||||
|
||||
b->ttl = 50; /* The bullet will disappear after N ticks. */
|
||||
b->ttl = TTLBUL; /* The bullet will disappear after N ticks. */
|
||||
app->bullets_num++;
|
||||
}
|
||||
|
||||
@@ -366,7 +438,7 @@ void remove_asteroid(AsteroidsApp* app, int id) {
|
||||
/* Called when an asteroid was reached by a bullet. The asteroid
|
||||
* hit is the one with the specified 'id'. */
|
||||
void asteroid_was_hit(AsteroidsApp* app, int id) {
|
||||
float sizelimit = 6; // Smaller than that, they disappear in one shot.
|
||||
float sizelimit = 6; // Smaller than that polverize in one shot.
|
||||
Asteroid* a = &app->asteroids[id];
|
||||
|
||||
/* Asteroid is large enough to break into fragments. */
|
||||
@@ -387,17 +459,19 @@ void asteroid_was_hit(AsteroidsApp* app, int id) {
|
||||
}
|
||||
} else {
|
||||
app->score++;
|
||||
if(app->score > app->highscore) {
|
||||
app->is_new_highscore = true;
|
||||
app->highscore = app->score; // Show on Game Over Screen and future main menu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set game over state. When in game-over mode, the game displays a
|
||||
* game over text with a background of many asteroids floating around. */
|
||||
/* Set gameover state. When in game-over mode, the game displays a gameover
|
||||
* text with a background of many asteroids floating around. */
|
||||
void game_over(AsteroidsApp* app) {
|
||||
restart_game_after_gameover(app);
|
||||
if(app->is_new_highscore) save_game(app); // Save highscore but only on change
|
||||
app->gameover = true;
|
||||
int asteroids = 8;
|
||||
while(asteroids-- && add_asteroid(app) != NULL)
|
||||
;
|
||||
app->lives = GAME_START_LIVES; // Show 3 lives in game over screen to match new game start
|
||||
}
|
||||
|
||||
/* Function called when a collision between the asteroid and the
|
||||
@@ -422,16 +496,17 @@ void restart_game(AsteroidsApp* app) {
|
||||
app->bullets_num = 0;
|
||||
app->last_bullet_tick = 0;
|
||||
app->asteroids_num = 0;
|
||||
app->ship_hit = 0;
|
||||
}
|
||||
|
||||
/* Called after game over to restart the game. This function
|
||||
/* Called after gameover to restart the game. This function
|
||||
* also calls restart_game(). */
|
||||
void restart_game_after_gameover(AsteroidsApp* app) {
|
||||
app->gameover = false;
|
||||
app->ticks = 0;
|
||||
app->score = 0;
|
||||
app->ship_hit = 0;
|
||||
app->lives = GAME_START_LIVES - 1; /* -1 to account for current one. */
|
||||
app->is_new_highscore = false;
|
||||
app->lives = GAME_START_LIVES - 1;
|
||||
restart_game(app);
|
||||
}
|
||||
|
||||
@@ -505,6 +580,7 @@ void game_tick(void* ctx) {
|
||||
* 1. Ship was hit, we frozen the game as long as ship_hit isn't zero
|
||||
* again, and show an animation of a rotating ship. */
|
||||
if(app->ship_hit) {
|
||||
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_crash);
|
||||
app->ship.rot += 0.5;
|
||||
app->ship_hit--;
|
||||
view_port_update(app->view_port);
|
||||
@@ -515,7 +591,8 @@ void game_tick(void* ctx) {
|
||||
} else if(app->gameover) {
|
||||
/* 2. Game over. We need to update only background asteroids. In this
|
||||
* state the game just displays a GAME OVER text with the floating
|
||||
* asteroids in background. */
|
||||
* asteroids in backgroud. */
|
||||
|
||||
if(key_pressed_time(app, InputKeyOk) > 100) {
|
||||
restart_game_after_gameover(app);
|
||||
}
|
||||
@@ -524,13 +601,14 @@ void game_tick(void* ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Handle key presses. */
|
||||
/* Handle keypresses. */
|
||||
if(app->pressed[InputKeyLeft]) app->ship.rot -= .35;
|
||||
if(app->pressed[InputKeyRight]) app->ship.rot += .35;
|
||||
if(key_pressed_time(app, InputKeyOk) > SHIP_ACCELERATION_KEYPRESS_TIME) {
|
||||
if(app->pressed[InputKeyUp]) {
|
||||
app->ship.vx -= 0.5 * (float)sin(app->ship.rot);
|
||||
app->ship.vy += 0.5 * (float)cos(app->ship.rot);
|
||||
} else if(app->pressed[InputKeyDown]) {
|
||||
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_brake);
|
||||
app->ship.vx *= 0.75;
|
||||
app->ship.vy *= 0.75;
|
||||
}
|
||||
@@ -567,6 +645,41 @@ void game_tick(void* ctx) {
|
||||
|
||||
/* ======================== Flipper specific code =========================== */
|
||||
|
||||
bool load_game(AsteroidsApp* app) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
File* file = storage_file_alloc(storage);
|
||||
uint16_t bytes_readed = 0;
|
||||
if(storage_file_open(file, SAVING_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
bytes_readed = storage_file_read(file, app, sizeof(AsteroidsApp));
|
||||
}
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return bytes_readed == sizeof(AsteroidsApp);
|
||||
}
|
||||
|
||||
void save_game(AsteroidsApp* app) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
if(storage_common_stat(storage, SAVING_DIRECTORY, NULL) == FSE_NOT_EXIST) {
|
||||
if(!storage_simply_mkdir(storage, SAVING_DIRECTORY)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
File* file = storage_file_alloc(storage);
|
||||
if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
||||
storage_file_write(file, app, sizeof(AsteroidsApp));
|
||||
}
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
/* Here all we do is putting the events into the queue that will be handled
|
||||
* in the while() loop of the app entry point function. */
|
||||
void input_callback(InputEvent* input_event, void* ctx) {
|
||||
@@ -579,6 +692,8 @@ void input_callback(InputEvent* input_event, void* ctx) {
|
||||
AsteroidsApp* asteroids_app_alloc() {
|
||||
AsteroidsApp* app = malloc(sizeof(AsteroidsApp));
|
||||
|
||||
load_game(app);
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(app->view_port, render_callback, app);
|
||||
@@ -587,6 +702,7 @@ AsteroidsApp* asteroids_app_alloc() {
|
||||
app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
app->running = 1; /* Turns 0 when back is pressed. */
|
||||
|
||||
restart_game_after_gameover(app);
|
||||
memset(app->pressed, 0, sizeof(app->pressed));
|
||||
return app;
|
||||
@@ -617,12 +733,15 @@ uint32_t key_pressed_time(AsteroidsApp* app, InputKey key) {
|
||||
|
||||
/* Handle keys interaction. */
|
||||
void asteroids_update_keypress_state(AsteroidsApp* app, InputEvent input) {
|
||||
// Allow Rapid fire
|
||||
if(input.key == InputKeyOk) {
|
||||
app->fire = true;
|
||||
}
|
||||
|
||||
if(input.type == InputTypePress) {
|
||||
app->pressed[input.key] = furi_get_tick();
|
||||
} else if(input.type == InputTypeRelease) {
|
||||
uint32_t dur = key_pressed_time(app, input.key);
|
||||
app->pressed[input.key] = 0;
|
||||
if(dur < 200 && input.key == InputKeyOk) app->fire = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -646,7 +765,9 @@ int32_t asteroids_app_entry(void* p) {
|
||||
|
||||
/* Handle navigation here. Then handle view-specific inputs
|
||||
* in the view specific handling function. */
|
||||
if(input.type == InputTypeShort && input.key == InputKeyBack) {
|
||||
if(input.type == InputTypeLong && input.key == InputKeyBack) {
|
||||
// Save High Score even if player didn't finish game
|
||||
if(app->is_new_highscore) save_game(app); // Save highscore but only on change
|
||||
app->running = 0;
|
||||
} else {
|
||||
asteroids_update_keypress_state(app, input);
|
||||
|
||||
@@ -39,11 +39,12 @@ static void clock_render_callback(Canvas* const canvas, void* ctx) {
|
||||
} else {
|
||||
bool pm = curr_dt.hour > 12;
|
||||
bool pm12 = curr_dt.hour >= 12;
|
||||
bool am12 = curr_dt.hour == 0;
|
||||
snprintf(
|
||||
time_string,
|
||||
TIME_LEN,
|
||||
CLOCK_TIME_FORMAT,
|
||||
pm ? curr_dt.hour - 12 : curr_dt.hour,
|
||||
pm ? curr_dt.hour - 12 : (am12 ? 12 : curr_dt.hour),
|
||||
curr_dt.minute,
|
||||
curr_dt.second);
|
||||
|
||||
@@ -237,4 +238,4 @@ int32_t clock_app(void* p) {
|
||||
free(plugin_state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,26 @@ Tama P1 Emulator for Flipper Zero
|
||||
|
||||
This is a tama P1 Emulator app for Flipper Zero, based on [TamaLIB](https://github.com/jcrona/tamalib/).
|
||||
|
||||

|
||||
|
||||
How to play
|
||||
-----------
|
||||
Create a `tama_p1` folder in your microSD card, and put the ROM as `rom.bin`.
|
||||
Use a search engine to find the Tamagotchi ROM. There is a file named `a`.
|
||||
Rename this to `rom.bin`.
|
||||
|
||||
Left button is A, OK is B, and right button is C. Hold the back button to exit.
|
||||
There is currently no saving, so your progress will be reset when you exit the
|
||||
app.
|
||||
|
||||
Building
|
||||
--------
|
||||
Move this folder into flippers applications/plugins/tama_p1.
|
||||
|
||||
|
||||
Launching the app, directly from console to flipper:
|
||||
`./fbt launch_app APPSRC=applications\plugins\tama_p1`
|
||||
|
||||
Run the following to compile icons:
|
||||
```
|
||||
scripts/assets.py icons applications/tama_p1/icons applications/tama_p1/compiled
|
||||
@@ -20,16 +31,25 @@ scripts/assets.py icons applications/tama_p1/icons applications/tama_p1/compiled
|
||||
Note: you may also need to add `-Wno-unused-parameter` to `CCFLAGS` in
|
||||
`site_cons/cc.scons` to suppress unused parameter errors in TamaLIB.
|
||||
|
||||
Debugging
|
||||
---------
|
||||
Using the serial script from [FlipperScripts](https://github.com/DroomOne/FlipperScripts/blob/main/serial_logger.py)
|
||||
it is easy to add direct logging after running the appliation:
|
||||
`python .\serial_logger.py`
|
||||
|
||||
`./fbt launch_app APPSRC=applications\plugins\tama_p1; python .\serial_logger.py`
|
||||
|
||||
|
||||
Implemented
|
||||
-----------
|
||||
- Basic emulation
|
||||
- Input
|
||||
- Sound
|
||||
- Saving/Loading emaulator state (stored in `/ext/tama_p1/save.bin`)
|
||||
|
||||
To-do
|
||||
-----
|
||||
- Saving/loading
|
||||
- Multiple slots?
|
||||
- Slots
|
||||
- In-game reset
|
||||
- Test mode?
|
||||
- Volume adjustment
|
||||
|
||||
@@ -36,11 +36,11 @@ static bool_t tama_p1_hal_is_log_enabled(log_level_t level) {
|
||||
static void tama_p1_hal_log(log_level_t level, char* buff, ...) {
|
||||
if(!tama_p1_hal_is_log_enabled(level)) return;
|
||||
|
||||
FuriString* string = NULL;
|
||||
FuriString* string = furi_string_alloc();
|
||||
va_list args;
|
||||
va_start(args, buff);
|
||||
furi_string_cat_vprintf(string, buff, args);
|
||||
va_end(args);
|
||||
va_end(args);
|
||||
|
||||
switch(level) {
|
||||
case LOG_ERROR:
|
||||
@@ -50,7 +50,10 @@ static void tama_p1_hal_log(log_level_t level, char* buff, ...) {
|
||||
FURI_LOG_I(TAG_HAL, "%s", furi_string_get_cstr(string));
|
||||
break;
|
||||
case LOG_MEMORY:
|
||||
break;
|
||||
case LOG_CPU:
|
||||
FURI_LOG_D(TAG_HAL, "%s", furi_string_get_cstr(string));
|
||||
break;
|
||||
default:
|
||||
FURI_LOG_D(TAG_HAL, "%s", furi_string_get_cstr(string));
|
||||
break;
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
#define TAMA_LCD_ICON_SIZE 14
|
||||
#define TAMA_LCD_ICON_MARGIN 1
|
||||
|
||||
#define STATE_FILE_MAGIC "TLST"
|
||||
#define STATE_FILE_VERSION 2
|
||||
#define TAMA_SAVE_PATH EXT_PATH("tama_p1/save.bin")
|
||||
|
||||
|
||||
typedef struct {
|
||||
FuriThread* thread;
|
||||
hal_t hal;
|
||||
|
||||
@@ -38,26 +38,10 @@ static void tama_p1_draw_callback(Canvas* const canvas, void* cb_ctx) {
|
||||
// FURI_LOG_D(TAG, "Drawing frame");
|
||||
// Calculate positioning
|
||||
uint16_t canv_width = canvas_width(canvas);
|
||||
uint16_t canv_height = canvas_height(canvas);
|
||||
uint16_t lcd_matrix_scaled_width = 32 * TAMA_SCREEN_SCALE_FACTOR;
|
||||
uint16_t lcd_matrix_scaled_height = 16 * TAMA_SCREEN_SCALE_FACTOR;
|
||||
uint16_t lcd_matrix_top = (canv_height - lcd_matrix_scaled_height) / 2;
|
||||
uint16_t lcd_matrix_top = 0;
|
||||
uint16_t lcd_matrix_left = (canv_width - lcd_matrix_scaled_width) / 2;
|
||||
uint16_t lcd_icon_upper_top = lcd_matrix_top - TAMA_LCD_ICON_SIZE - TAMA_LCD_ICON_MARGIN;
|
||||
uint16_t lcd_icon_upper_left = lcd_matrix_left;
|
||||
uint16_t lcd_icon_lower_top =
|
||||
lcd_matrix_top + lcd_matrix_scaled_height + TAMA_LCD_ICON_MARGIN;
|
||||
uint16_t lcd_icon_lower_left = lcd_matrix_left;
|
||||
uint16_t lcd_icon_spacing_horiz =
|
||||
(lcd_matrix_scaled_width - (4 * TAMA_LCD_ICON_SIZE)) / 3 + TAMA_LCD_ICON_SIZE;
|
||||
|
||||
// Draw pixels
|
||||
// canvas_draw_frame(
|
||||
// canvas,
|
||||
// lcd_matrix_left,
|
||||
// lcd_matrix_top,
|
||||
// lcd_matrix_scaled_width,
|
||||
// lcd_matrix_scaled_height);
|
||||
|
||||
|
||||
uint16_t y = lcd_matrix_top;
|
||||
for(uint8_t row = 0; row < 16; ++row) {
|
||||
@@ -74,30 +58,20 @@ static void tama_p1_draw_callback(Canvas* const canvas, void* cb_ctx) {
|
||||
y += TAMA_SCREEN_SCALE_FACTOR;
|
||||
}
|
||||
|
||||
// Draw icons
|
||||
// Draw Icons on bottom
|
||||
uint8_t lcd_icons = g_ctx->icons;
|
||||
// Top
|
||||
y = lcd_icon_upper_top;
|
||||
uint16_t x_ic = lcd_icon_upper_left;
|
||||
for(uint8_t i = 0; i < 4; ++i) {
|
||||
// canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE);
|
||||
uint16_t x_ic = 0;
|
||||
y = 64 - TAMA_LCD_ICON_SIZE;
|
||||
for(uint8_t i = 0; i < 7; ++i) {
|
||||
if(lcd_icons & 1) {
|
||||
canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
|
||||
}
|
||||
x_ic += lcd_icon_spacing_horiz;
|
||||
x_ic += TAMA_LCD_ICON_SIZE + 4;
|
||||
lcd_icons >>= 1;
|
||||
}
|
||||
|
||||
// Bottom
|
||||
y = lcd_icon_lower_top;
|
||||
x_ic = lcd_icon_lower_left;
|
||||
for(uint8_t i = 4; i < 8; ++i) {
|
||||
// canvas_draw_frame(canvas, x_ic, y, TAMA_LCD_ICON_SIZE, TAMA_LCD_ICON_SIZE);
|
||||
if(lcd_icons & 1) {
|
||||
canvas_draw_icon(canvas, x_ic, y, icons_list[i]);
|
||||
}
|
||||
x_ic += lcd_icon_spacing_horiz;
|
||||
lcd_icons >>= 1;
|
||||
if (lcd_icons & 7) {
|
||||
canvas_draw_icon(canvas, 128 - TAMA_LCD_ICON_SIZE, 0, icons_list[7]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +92,228 @@ static void tama_p1_update_timer_callback(FuriMessageQueue* event_queue) {
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void tama_p1_load_state() {
|
||||
state_t *state;
|
||||
uint8_t buf[4];
|
||||
bool error = false;
|
||||
state = tamalib_get_state();
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
if(storage_file_open(file, TAMA_SAVE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
|
||||
storage_file_read(file, &buf, 4);
|
||||
if (buf[0] != (uint8_t) STATE_FILE_MAGIC[0] || buf[1] != (uint8_t) STATE_FILE_MAGIC[1] ||
|
||||
buf[2] != (uint8_t) STATE_FILE_MAGIC[2] || buf[3] != (uint8_t) STATE_FILE_MAGIC[3]) {
|
||||
FURI_LOG_E(TAG, "FATAL: Wrong state file magic in \"%s\" !\n", TAMA_SAVE_PATH);
|
||||
error = true;
|
||||
}
|
||||
|
||||
storage_file_read(file, &buf, 1);
|
||||
if (buf[0] != STATE_FILE_VERSION) {
|
||||
FURI_LOG_E(TAG, "FATAL: Unsupported version");
|
||||
error = true;
|
||||
}
|
||||
if (!error) {
|
||||
FURI_LOG_D(TAG, "Reading save.bin");
|
||||
|
||||
storage_file_read(file, &buf, 2);
|
||||
*(state->pc) = buf[0] | ((buf[1] & 0x1F) << 8);
|
||||
|
||||
storage_file_read(file, &buf, 2);
|
||||
*(state->x) = buf[0] | ((buf[1] & 0xF) << 8);
|
||||
|
||||
storage_file_read(file, &buf, 2);
|
||||
*(state->y) = buf[0] | ((buf[1] & 0xF) << 8);
|
||||
|
||||
storage_file_read(file, &buf, 1);
|
||||
*(state->a) = buf[0] & 0xF;
|
||||
|
||||
storage_file_read(file, &buf, 1);
|
||||
*(state->b) = buf[0] & 0xF;
|
||||
|
||||
storage_file_read(file, &buf, 1);
|
||||
*(state->np) = buf[0] & 0x1F;
|
||||
|
||||
storage_file_read(file, &buf, 1);
|
||||
*(state->sp) = buf[0];
|
||||
|
||||
storage_file_read(file, &buf, 1);
|
||||
*(state->flags) = buf[0] & 0xF;
|
||||
|
||||
storage_file_read(file, &buf, 4);
|
||||
*(state->tick_counter) = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
|
||||
|
||||
storage_file_read(file, &buf, 4);
|
||||
*(state->clk_timer_timestamp) = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
|
||||
|
||||
storage_file_read(file, &buf, 4);
|
||||
*(state->prog_timer_timestamp) = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
|
||||
|
||||
storage_file_read(file, &buf, 1);
|
||||
*(state->prog_timer_enabled) = buf[0] & 0x1;
|
||||
|
||||
storage_file_read(file, &buf, 1);
|
||||
*(state->prog_timer_data) = buf[0];
|
||||
|
||||
storage_file_read(file, &buf, 1);
|
||||
*(state->prog_timer_rld) = buf[0];
|
||||
|
||||
storage_file_read(file, &buf, 4);
|
||||
*(state->call_depth) = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
|
||||
|
||||
FURI_LOG_D(TAG, "Restoring Interupts");
|
||||
for (uint32_t i = 0; i < INT_SLOT_NUM; i++) {
|
||||
storage_file_read(file, &buf, 1);
|
||||
state->interrupts[i].factor_flag_reg = buf[0] & 0xF;
|
||||
|
||||
storage_file_read(file, &buf, 1);
|
||||
state->interrupts[i].mask_reg = buf[0] & 0xF;
|
||||
|
||||
storage_file_read(file, &buf, 1);
|
||||
state->interrupts[i].triggered = buf[0] & 0x1;
|
||||
}
|
||||
|
||||
/* First 640 half bytes correspond to the RAM */
|
||||
FURI_LOG_D(TAG, "Restoring RAM");
|
||||
for (uint32_t i = 0; i < MEM_RAM_SIZE; i++) {
|
||||
storage_file_read(file, &buf, 1);
|
||||
SET_RAM_MEMORY(state->memory, i + MEM_RAM_ADDR, buf[0] & 0xF);
|
||||
}
|
||||
|
||||
/* I/Os are from 0xF00 to 0xF7F */
|
||||
FURI_LOG_D(TAG, "Restoring I/O");
|
||||
for (uint32_t i = 0; i < MEM_IO_SIZE; i++) {
|
||||
storage_file_read(file, &buf, 1);
|
||||
SET_IO_MEMORY(state->memory, i + MEM_IO_ADDR, buf[0] & 0xF);
|
||||
}
|
||||
FURI_LOG_D(TAG, "Refreshing Hardware");
|
||||
tamalib_refresh_hw();
|
||||
}
|
||||
}
|
||||
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
|
||||
static void tama_p1_save_state() {
|
||||
|
||||
// Saving state
|
||||
FURI_LOG_D(TAG, "Saving Gamestate");
|
||||
|
||||
uint8_t buf[4];
|
||||
state_t *state;
|
||||
uint32_t offset = 0;
|
||||
state = tamalib_get_state();
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
if(storage_file_open(file, TAMA_SAVE_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
||||
buf[0] = (uint8_t) STATE_FILE_MAGIC[0];
|
||||
buf[1] = (uint8_t) STATE_FILE_MAGIC[1];
|
||||
buf[2] = (uint8_t) STATE_FILE_MAGIC[2];
|
||||
buf[3] = (uint8_t) STATE_FILE_MAGIC[3];
|
||||
offset += storage_file_write(file, &buf, sizeof(buf));
|
||||
|
||||
buf[0] = STATE_FILE_VERSION & 0xFF;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
|
||||
buf[0] = *(state->pc) & 0xFF;
|
||||
buf[1] = (*(state->pc) >> 8) & 0x1F;
|
||||
offset += storage_file_write(file, &buf, 2);
|
||||
|
||||
buf[0] = *(state->x) & 0xFF;
|
||||
buf[1] = (*(state->x) >> 8) & 0xF;
|
||||
offset += storage_file_write(file, &buf, 2);
|
||||
|
||||
buf[0] = *(state->y) & 0xFF;
|
||||
buf[1] = (*(state->y) >> 8) & 0xF;
|
||||
offset += storage_file_write(file, &buf, 2);
|
||||
|
||||
buf[0] = *(state->a) & 0xF;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
|
||||
buf[0] = *(state->b) & 0xF;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
|
||||
buf[0] = *(state->np) & 0x1F;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
|
||||
buf[0] = *(state->sp) & 0xFF;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
|
||||
buf[0] = *(state->flags) & 0xF;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
|
||||
buf[0] = *(state->tick_counter) & 0xFF;
|
||||
buf[1] = (*(state->tick_counter) >> 8) & 0xFF;
|
||||
buf[2] = (*(state->tick_counter) >> 16) & 0xFF;
|
||||
buf[3] = (*(state->tick_counter) >> 24) & 0xFF;
|
||||
offset += storage_file_write(file, &buf, sizeof(buf));
|
||||
|
||||
buf[0] = *(state->clk_timer_timestamp) & 0xFF;
|
||||
buf[1] = (*(state->clk_timer_timestamp) >> 8) & 0xFF;
|
||||
buf[2] = (*(state->clk_timer_timestamp) >> 16) & 0xFF;
|
||||
buf[3] = (*(state->clk_timer_timestamp) >> 24) & 0xFF;
|
||||
offset += storage_file_write(file, &buf, sizeof(buf));
|
||||
|
||||
buf[0] = *(state->prog_timer_timestamp) & 0xFF;
|
||||
buf[1] = (*(state->prog_timer_timestamp) >> 8) & 0xFF;
|
||||
buf[2] = (*(state->prog_timer_timestamp) >> 16) & 0xFF;
|
||||
buf[3] = (*(state->prog_timer_timestamp) >> 24) & 0xFF;
|
||||
offset += storage_file_write(file, &buf, sizeof(buf));
|
||||
|
||||
buf[0] = *(state->prog_timer_enabled) & 0x1;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
|
||||
buf[0] = *(state->prog_timer_data) & 0xFF;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
|
||||
buf[0] = *(state->prog_timer_rld) & 0xFF;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
|
||||
buf[0] = *(state->call_depth) & 0xFF;
|
||||
buf[1] = (*(state->call_depth) >> 8) & 0xFF;
|
||||
buf[2] = (*(state->call_depth) >> 16) & 0xFF;
|
||||
buf[3] = (*(state->call_depth) >> 24) & 0xFF;
|
||||
offset += storage_file_write(file, &buf, sizeof(buf));
|
||||
|
||||
for (uint32_t i = 0; i < INT_SLOT_NUM; i++) {
|
||||
buf[0] = state->interrupts[i].factor_flag_reg & 0xF;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
|
||||
buf[0] = state->interrupts[i].mask_reg & 0xF;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
|
||||
buf[0] = state->interrupts[i].triggered & 0x1;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
}
|
||||
|
||||
/* First 640 half bytes correspond to the RAM */
|
||||
for (uint32_t i = 0; i < MEM_RAM_SIZE; i++) {
|
||||
buf[0] = GET_RAM_MEMORY(state->memory, i + MEM_RAM_ADDR) & 0xF;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
}
|
||||
|
||||
/* I/Os are from 0xF00 to 0xF7F */
|
||||
for (uint32_t i = 0; i < MEM_IO_SIZE; i++) {
|
||||
buf[0] = GET_IO_MEMORY(state->memory, i + MEM_IO_ADDR) & 0xF;
|
||||
offset += storage_file_write(file, &buf, 1);
|
||||
}
|
||||
}
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
FURI_LOG_D(TAG, "Finished Writing %lu", offset);
|
||||
}
|
||||
|
||||
|
||||
static int32_t tama_p1_worker(void* context) {
|
||||
bool running = true;
|
||||
FuriMutex* mutex = context;
|
||||
@@ -125,6 +321,9 @@ static int32_t tama_p1_worker(void* context) {
|
||||
|
||||
cpu_sync_ref_timestamp();
|
||||
LL_TIM_EnableCounter(TIM2);
|
||||
|
||||
tama_p1_load_state();
|
||||
|
||||
while(running) {
|
||||
if(furi_thread_flags_get()) {
|
||||
running = false;
|
||||
@@ -138,6 +337,8 @@ static int32_t tama_p1_worker(void* context) {
|
||||
furi_mutex_release(mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void tama_p1_init(TamaApp* const ctx) {
|
||||
g_ctx = ctx;
|
||||
@@ -270,6 +471,8 @@ int32_t tama_p1_app(void* p) {
|
||||
if(event.input.key == InputKeyBack && event.input.type == InputTypeLong) {
|
||||
furi_timer_stop(timer);
|
||||
running = false;
|
||||
|
||||
tama_p1_save_state();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Alexander Kopachov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -14,6 +14,8 @@
|
||||
#define TOTP_CLI_COMMAND_ADD_ARG_DIGITS "digits"
|
||||
#define TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX "-d"
|
||||
#define TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX "-u"
|
||||
#define TOTP_CLI_COMMAND_ADD_ARG_DURATION "duration"
|
||||
#define TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX "-l"
|
||||
|
||||
static bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) {
|
||||
if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME) == 0) {
|
||||
@@ -34,6 +36,16 @@ static bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool args_read_uint8_and_trim(FuriString* args, uint8_t* value) {
|
||||
int int_value;
|
||||
if(!args_read_int_and_trim(args, &int_value) || int_value < 0 || int_value > UINT8_MAX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*value = (uint8_t)int_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
void totp_cli_command_add_docopt_commands() {
|
||||
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ADD ", " TOTP_CLI_COMMAND_ADD_ALT
|
||||
", " TOTP_CLI_COMMAND_ADD_ALT2 " Add new token\r\n");
|
||||
@@ -42,11 +54,11 @@ void totp_cli_command_add_docopt_commands() {
|
||||
void totp_cli_command_add_docopt_usage() {
|
||||
TOTP_CLI_PRINTF(
|
||||
" " TOTP_CLI_COMMAND_NAME
|
||||
" " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_NAME) " " DOCOPT_OPTIONAL(
|
||||
" " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_NAME) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_ALGO))) " " DOCOPT_OPTIONAL(
|
||||
DOCOPT_OPTION(
|
||||
TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX,
|
||||
TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX,
|
||||
DOCOPT_ARGUMENT(
|
||||
TOTP_CLI_COMMAND_ADD_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX)) "\r\n");
|
||||
TOTP_CLI_COMMAND_ADD_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX)) "\r\n");
|
||||
}
|
||||
|
||||
void totp_cli_command_add_docopt_arguments() {
|
||||
@@ -64,6 +76,10 @@ void totp_cli_command_add_docopt_options() {
|
||||
TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX,
|
||||
DOCOPT_ARGUMENT(
|
||||
TOTP_CLI_COMMAND_ADD_ARG_DIGITS)) " Number of digits to generate, one of: 6, 8 " DOCOPT_DEFAULT("6") "\r\n");
|
||||
TOTP_CLI_PRINTF(" " DOCOPT_OPTION(
|
||||
TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX,
|
||||
DOCOPT_ARGUMENT(
|
||||
TOTP_CLI_COMMAND_ADD_ARG_DURATION)) " Token lifetime duration in seconds, between: 15 and 255 " DOCOPT_DEFAULT("30") "\r\n");
|
||||
TOTP_CLI_PRINTF(" " DOCOPT_SWITCH(
|
||||
TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX) " Show console user input as-is without masking\r\n");
|
||||
}
|
||||
@@ -110,16 +126,32 @@ void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cl
|
||||
parsed = true;
|
||||
}
|
||||
} else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX) == 0) {
|
||||
if(!args_read_string_and_trim(args, temp_str)) {
|
||||
uint8_t digit_value;
|
||||
if(!args_read_uint8_and_trim(args, &digit_value)) {
|
||||
TOTP_CLI_PRINTF(
|
||||
"Missed value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX
|
||||
"Missed or incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX
|
||||
"\"\r\n");
|
||||
} else if(!token_info_set_digits_from_int(
|
||||
token_info, CONVERT_CHAR_TO_DIGIT(furi_string_get_char(temp_str, 0)))) {
|
||||
} else if(!token_info_set_digits_from_int(token_info, digit_value)) {
|
||||
TOTP_CLI_PRINTF(
|
||||
"\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX
|
||||
"\"%" PRIu8
|
||||
"\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX
|
||||
"\"\r\n",
|
||||
furi_string_get_cstr(temp_str));
|
||||
digit_value);
|
||||
} else {
|
||||
parsed = true;
|
||||
}
|
||||
} else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX) == 0) {
|
||||
uint8_t duration_value;
|
||||
if(!args_read_uint8_and_trim(args, &duration_value)) {
|
||||
TOTP_CLI_PRINTF(
|
||||
"Missed or incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX
|
||||
"\"\r\n");
|
||||
} else if(!token_info_set_duration_from_int(token_info, duration_value)) {
|
||||
TOTP_CLI_PRINTF(
|
||||
"\"%" PRIu8
|
||||
"\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX
|
||||
"\"\r\n",
|
||||
duration_value);
|
||||
} else {
|
||||
parsed = true;
|
||||
}
|
||||
|
||||
@@ -40,19 +40,21 @@ void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli) {
|
||||
return;
|
||||
}
|
||||
|
||||
TOTP_CLI_PRINTF("+-----+-----------------------------+--------+--------+\r\n");
|
||||
TOTP_CLI_PRINTF("| %-*s | %-*s | %-*s | %-s |\r\n", 3, "#", 27, "Name", 6, "Algo", "Digits");
|
||||
TOTP_CLI_PRINTF("+-----+-----------------------------+--------+--------+\r\n");
|
||||
TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
|
||||
TOTP_CLI_PRINTF(
|
||||
"| %-*s | %-*s | %-*s | %-s | %-s |\r\n", 3, "#", 25, "Name", 6, "Algo", "Ln", "Dur");
|
||||
TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
|
||||
uint16_t index = 1;
|
||||
TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
|
||||
TokenInfo* token_info = (TokenInfo*)node->data;
|
||||
TOTP_CLI_PRINTF(
|
||||
"| %-3" PRIu16 " | %-27.27s | %-6s | %-6" PRIu8 " |\r\n",
|
||||
"| %-3" PRIu16 " | %-25.25s | %-6s | %-2" PRIu8 " | %-3" PRIu8 " |\r\n",
|
||||
index,
|
||||
token_info->name,
|
||||
get_algo_as_cstr(token_info->algo),
|
||||
token_info->digits);
|
||||
token_info->digits,
|
||||
token_info->duration);
|
||||
index++;
|
||||
});
|
||||
TOTP_CLI_PRINTF("+-----+-----------------------------+--------+--------+\r\n");
|
||||
TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "../../types/common.h"
|
||||
#include "../../types/token_info.h"
|
||||
#include "migrations/config_migration_v1_to_v2.h"
|
||||
#include "migrations/config_migration_v2_to_v3.h"
|
||||
|
||||
#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/authenticator")
|
||||
#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf"
|
||||
@@ -173,6 +174,13 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF
|
||||
flipper_format_write_comment(fff_data_file, temp_str);
|
||||
flipper_format_write_comment_cstr(fff_data_file, " ");
|
||||
|
||||
flipper_format_write_comment_cstr(
|
||||
fff_data_file,
|
||||
"# Token lifetime duration in seconds. Should be between 15 and 255. Majority websites requires 30, however some rare websites may require custom lifetime. If you are not sure which one to use - use 30");
|
||||
furi_string_printf(temp_str, "%s: 30", TOTP_CONFIG_KEY_TOKEN_DURATION);
|
||||
flipper_format_write_comment(fff_data_file, temp_str);
|
||||
flipper_format_write_comment_cstr(fff_data_file, " ");
|
||||
|
||||
flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE END ===");
|
||||
flipper_format_write_comment_cstr(fff_data_file, " ");
|
||||
|
||||
@@ -232,6 +240,12 @@ TotpConfigFileUpdateResult
|
||||
break;
|
||||
}
|
||||
|
||||
tmp_uint32 = token_info->duration;
|
||||
if(!flipper_format_write_uint32(file, TOTP_CONFIG_KEY_TOKEN_DURATION, &tmp_uint32, 1)) {
|
||||
update_result = TotpConfigFileUpdateError;
|
||||
break;
|
||||
}
|
||||
|
||||
update_result = TotpConfigFileUpdateSuccess;
|
||||
} while(false);
|
||||
|
||||
@@ -483,6 +497,7 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
|
||||
if(file_version == 1) {
|
||||
if(totp_config_migrate_v1_to_v2(fff_data_file, fff_backup_data_file)) {
|
||||
FURI_LOG_I(LOGGING_TAG, "Applied migration from v1 to v2");
|
||||
file_version = 2;
|
||||
} else {
|
||||
FURI_LOG_W(
|
||||
LOGGING_TAG, "An error occurred during migration from v1 to v2");
|
||||
@@ -491,6 +506,18 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
|
||||
}
|
||||
}
|
||||
|
||||
if(file_version == 2) {
|
||||
if(totp_config_migrate_v2_to_v3(fff_data_file, fff_backup_data_file)) {
|
||||
FURI_LOG_I(LOGGING_TAG, "Applied migration from v2 to v3");
|
||||
file_version = 3;
|
||||
} else {
|
||||
FURI_LOG_W(
|
||||
LOGGING_TAG, "An error occurred during migration from v2 to v3");
|
||||
result = TotpConfigFileOpenError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
flipper_format_file_close(fff_backup_data_file);
|
||||
flipper_format_free(fff_backup_data_file);
|
||||
flipper_format_rewind(fff_data_file);
|
||||
@@ -669,6 +696,12 @@ TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state)
|
||||
tokenInfo->digits = TOTP_6_DIGITS;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_uint32(
|
||||
fff_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &temp_data32, 1) ||
|
||||
!token_info_set_duration_from_int(tokenInfo, temp_data32)) {
|
||||
tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
|
||||
}
|
||||
|
||||
FURI_LOG_D(LOGGING_TAG, "Found token \"%s\"", tokenInfo->name);
|
||||
|
||||
TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check);
|
||||
@@ -736,4 +769,4 @@ void totp_config_file_reset() {
|
||||
Storage* storage = totp_open_storage();
|
||||
storage_simply_remove(storage, CONFIG_FILE_PATH);
|
||||
totp_close_storage();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#define CONFIG_FILE_HEADER "Flipper TOTP plugin config file"
|
||||
#define CONFIG_FILE_ACTUAL_VERSION 2
|
||||
#define CONFIG_FILE_ACTUAL_VERSION 3
|
||||
|
||||
#define TOTP_CONFIG_KEY_TIMEZONE "Timezone"
|
||||
#define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName"
|
||||
#define TOTP_CONFIG_KEY_TOKEN_SECRET "TokenSecret"
|
||||
#define TOTP_CONFIG_KEY_TOKEN_ALGO "TokenAlgo"
|
||||
#define TOTP_CONFIG_KEY_TOKEN_DIGITS "TokenDigits"
|
||||
#define TOTP_CONFIG_KEY_TOKEN_DURATION "TokenDuration"
|
||||
#define TOTP_CONFIG_KEY_CRYPTO_VERIFY "Crypto"
|
||||
#define TOTP_CONFIG_KEY_BASE_IV "BaseIV"
|
||||
#define TOTP_CONFIG_KEY_PINSET "PinIsSet"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "config_migration_v1_to_v2.h"
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include "../constants.h"
|
||||
#include "../../../types/token_info.h"
|
||||
|
||||
#define NEW_VERSION 2
|
||||
|
||||
@@ -36,7 +37,7 @@ bool totp_config_migrate_v1_to_v2(
|
||||
|
||||
flipper_format_write_string_cstr(
|
||||
fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME);
|
||||
uint32_t default_digits = 6;
|
||||
const uint32_t default_digits = TOTP_6_DIGITS;
|
||||
flipper_format_write_uint32(
|
||||
fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &default_digits, 1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#include "config_migration_v2_to_v3.h"
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include "../constants.h"
|
||||
#include "../../../types/token_info.h"
|
||||
|
||||
#define NEW_VERSION 3
|
||||
|
||||
bool totp_config_migrate_v2_to_v3(
|
||||
FlipperFormat* fff_data_file,
|
||||
FlipperFormat* fff_backup_data_file) {
|
||||
flipper_format_write_header_cstr(fff_data_file, CONFIG_FILE_HEADER, NEW_VERSION);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
|
||||
if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str)) {
|
||||
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str);
|
||||
}
|
||||
|
||||
flipper_format_rewind(fff_backup_data_file);
|
||||
|
||||
if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str)) {
|
||||
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str);
|
||||
}
|
||||
|
||||
flipper_format_rewind(fff_backup_data_file);
|
||||
|
||||
if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str)) {
|
||||
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str);
|
||||
}
|
||||
|
||||
flipper_format_rewind(fff_backup_data_file);
|
||||
|
||||
if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_PINSET, temp_str)) {
|
||||
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_PINSET, temp_str);
|
||||
}
|
||||
|
||||
flipper_format_rewind(fff_backup_data_file);
|
||||
|
||||
if(flipper_format_read_string(
|
||||
fff_backup_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, temp_str)) {
|
||||
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, temp_str);
|
||||
}
|
||||
|
||||
flipper_format_rewind(fff_backup_data_file);
|
||||
|
||||
while(true) {
|
||||
if(!flipper_format_read_string(
|
||||
fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) {
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str);
|
||||
|
||||
flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
|
||||
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
|
||||
|
||||
flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
|
||||
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
|
||||
|
||||
flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str);
|
||||
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str);
|
||||
|
||||
const uint32_t default_duration = TOTP_TOKEN_DURATION_DEFAULT;
|
||||
flipper_format_write_uint32(
|
||||
fff_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &default_duration, 1);
|
||||
}
|
||||
|
||||
furi_string_free(temp_str);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
bool totp_config_migrate_v2_to_v3(
|
||||
FlipperFormat* fff_data_file,
|
||||
FlipperFormat* fff_backup_data_file);
|
||||
@@ -61,3 +61,12 @@ bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool token_info_set_duration_from_int(TokenInfo* token_info, uint8_t duration) {
|
||||
if(duration >= 15) {
|
||||
token_info->duration = duration;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#define TOTP_TOKEN_DURATION_DEFAULT 30
|
||||
|
||||
typedef uint8_t TokenHashAlgo;
|
||||
typedef uint8_t TokenDigitsCount;
|
||||
|
||||
@@ -70,6 +72,11 @@ typedef struct {
|
||||
* @brief Desired TOTP token length
|
||||
*/
|
||||
TokenDigitsCount digits;
|
||||
|
||||
/**
|
||||
* @brief Desired TOTP token duration in seconds
|
||||
*/
|
||||
uint8_t duration;
|
||||
} TokenInfo;
|
||||
|
||||
/**
|
||||
@@ -102,6 +109,14 @@ bool token_info_set_secret(
|
||||
* @brief Sets token digits count from \c uint8_t value
|
||||
* @param token_info instance whichs token digits count length should be updated
|
||||
* @param digits desired token digits count length
|
||||
* @return \c true if token digits count length has been updated; \c false p
|
||||
* @return \c true if token digits count length has been updated; \c false otherwise
|
||||
*/
|
||||
bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits);
|
||||
|
||||
/**
|
||||
* @brief Sets token duration from \c uint8_t value
|
||||
* @param token_info instance whichs token digits count length should be updated
|
||||
* @param duration desired token duration in seconds
|
||||
* @return \c true if token duration has been updated; \c false otherwise
|
||||
*/
|
||||
bool token_info_set_duration_from_int(TokenInfo* token_info, uint8_t duration);
|
||||
|
||||
@@ -21,6 +21,7 @@ typedef enum {
|
||||
TokenSecretTextBox,
|
||||
TokenAlgoSelect,
|
||||
TokenLengthSelect,
|
||||
TokenDurationSelect,
|
||||
ConfirmButton,
|
||||
} Control;
|
||||
|
||||
@@ -39,6 +40,8 @@ typedef struct {
|
||||
int16_t screen_y_offset;
|
||||
TokenHashAlgo algo;
|
||||
uint8_t digits_count_index;
|
||||
uint8_t duration;
|
||||
FuriString* duration_text;
|
||||
} SceneState;
|
||||
|
||||
void totp_scene_add_new_token_init(const PluginState* plugin_state) {
|
||||
@@ -63,6 +66,10 @@ static void on_token_secret_user_comitted(InputTextSceneCallbackResult* result)
|
||||
free(result);
|
||||
}
|
||||
|
||||
static void update_duration_text(SceneState* scene_state) {
|
||||
furi_string_printf(scene_state->duration_text, "%d sec.", scene_state->duration);
|
||||
}
|
||||
|
||||
void totp_scene_add_new_token_activate(
|
||||
PluginState* plugin_state,
|
||||
const TokenAddEditSceneContext* context) {
|
||||
@@ -89,6 +96,9 @@ void totp_scene_add_new_token_activate(
|
||||
scene_state->screen_y_offset = 0;
|
||||
|
||||
scene_state->input_state = NULL;
|
||||
scene_state->duration = TOTP_TOKEN_DURATION_DEFAULT;
|
||||
scene_state->duration_text = furi_string_alloc();
|
||||
update_duration_text(scene_state);
|
||||
|
||||
if(context == NULL) {
|
||||
TOTP_NULLABLE_NULL(scene_state->current_token_index);
|
||||
@@ -124,14 +134,23 @@ void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_s
|
||||
ui_control_select_render(
|
||||
canvas,
|
||||
0,
|
||||
63 - scene_state->screen_y_offset,
|
||||
61 - scene_state->screen_y_offset,
|
||||
SCREEN_WIDTH,
|
||||
TOKEN_DIGITS_TEXT_LIST[scene_state->digits_count_index],
|
||||
scene_state->selected_control == TokenLengthSelect);
|
||||
|
||||
ui_control_select_render(
|
||||
canvas,
|
||||
0,
|
||||
78 - scene_state->screen_y_offset,
|
||||
SCREEN_WIDTH,
|
||||
furi_string_get_cstr(scene_state->duration_text),
|
||||
scene_state->selected_control == TokenDurationSelect);
|
||||
|
||||
ui_control_button_render(
|
||||
canvas,
|
||||
SCREEN_WIDTH_CENTER - 24,
|
||||
85 - scene_state->screen_y_offset,
|
||||
101 - scene_state->screen_y_offset,
|
||||
48,
|
||||
13,
|
||||
"Confirm",
|
||||
@@ -146,8 +165,12 @@ void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_s
|
||||
}
|
||||
|
||||
void update_screen_y_offset(SceneState* scene_state) {
|
||||
if(scene_state->selected_control > TokenAlgoSelect) {
|
||||
scene_state->screen_y_offset = 35;
|
||||
if(scene_state->selected_control > TokenLengthSelect) {
|
||||
scene_state->screen_y_offset = 51;
|
||||
} else if(scene_state->selected_control > TokenAlgoSelect) {
|
||||
scene_state->screen_y_offset = 34;
|
||||
} else if(scene_state->selected_control > TokenSecretTextBox) {
|
||||
scene_state->screen_y_offset = 17;
|
||||
} else {
|
||||
scene_state->screen_y_offset = 0;
|
||||
}
|
||||
@@ -197,6 +220,9 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
|
||||
} else if(scene_state->selected_control == TokenLengthSelect) {
|
||||
totp_roll_value_uint8_t(
|
||||
&scene_state->digits_count_index, 1, 0, 1, RollOverflowBehaviorRoll);
|
||||
} else if(scene_state->selected_control == TokenDurationSelect) {
|
||||
totp_roll_value_uint8_t(&scene_state->duration, 15, 15, 255, RollOverflowBehaviorStop);
|
||||
update_duration_text(scene_state);
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
@@ -206,6 +232,10 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
|
||||
} else if(scene_state->selected_control == TokenLengthSelect) {
|
||||
totp_roll_value_uint8_t(
|
||||
&scene_state->digits_count_index, -1, 0, 1, RollOverflowBehaviorRoll);
|
||||
} else if(scene_state->selected_control == TokenDurationSelect) {
|
||||
totp_roll_value_uint8_t(
|
||||
&scene_state->duration, -15, 15, 255, RollOverflowBehaviorStop);
|
||||
update_duration_text(scene_state);
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
@@ -230,6 +260,8 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
|
||||
break;
|
||||
case TokenLengthSelect:
|
||||
break;
|
||||
case TokenDurationSelect:
|
||||
break;
|
||||
case ConfirmButton: {
|
||||
TokenInfo* tokenInfo = token_info_alloc();
|
||||
bool token_secret_set = token_info_set_secret(
|
||||
@@ -245,6 +277,7 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
|
||||
tokenInfo->name, scene_state->token_name, scene_state->token_name_length + 1);
|
||||
tokenInfo->algo = scene_state->algo;
|
||||
tokenInfo->digits = TOKEN_DIGITS_VALUE_LIST[scene_state->digits_count_index];
|
||||
tokenInfo->duration = scene_state->duration;
|
||||
|
||||
TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check);
|
||||
plugin_state->tokens_count++;
|
||||
@@ -310,6 +343,8 @@ void totp_scene_add_new_token_deactivate(PluginState* plugin_state) {
|
||||
free(scene_state->token_secret_input_context->header_text);
|
||||
free(scene_state->token_secret_input_context);
|
||||
|
||||
furi_string_free(scene_state->duration_text);
|
||||
|
||||
if(scene_state->input_state != NULL) {
|
||||
totp_input_text_free(scene_state->input_state);
|
||||
}
|
||||
|
||||
@@ -16,13 +16,11 @@
|
||||
#include "../token_menu/totp_scene_token_menu.h"
|
||||
#include "../../../workers/type_code/type_code.h"
|
||||
|
||||
#define TOKEN_LIFETIME 30
|
||||
|
||||
typedef struct {
|
||||
uint16_t current_token_index;
|
||||
char last_code[TOTP_TOKEN_DIGITS_MAX_COUNT + 1];
|
||||
char* last_code_name;
|
||||
bool need_token_update;
|
||||
TokenInfo* current_token;
|
||||
uint32_t last_token_gen_time;
|
||||
TotpTypeCodeWorkerContext* type_code_worker_context;
|
||||
NotificationMessage const** notification_sequence_new_token;
|
||||
@@ -151,7 +149,7 @@ static void update_totp_params(PluginState* const plugin_state) {
|
||||
list_element_at(plugin_state->tokens_list, scene_state->current_token_index)->data;
|
||||
|
||||
scene_state->need_token_update = true;
|
||||
scene_state->last_code_name = tokenInfo->name;
|
||||
scene_state->current_token = tokenInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +227,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
|
||||
furi_hal_rtc_get_datetime(&curr_dt);
|
||||
uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
|
||||
|
||||
bool is_new_token_time = curr_ts % TOKEN_LIFETIME == 0;
|
||||
bool is_new_token_time = curr_ts % scene_state->current_token->duration == 0;
|
||||
if(is_new_token_time && scene_state->last_token_gen_time != curr_ts) {
|
||||
scene_state->need_token_update = true;
|
||||
}
|
||||
@@ -238,10 +236,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
|
||||
scene_state->need_token_update = false;
|
||||
scene_state->last_token_gen_time = curr_ts;
|
||||
|
||||
const TokenInfo* tokenInfo =
|
||||
(TokenInfo*)(list_element_at(
|
||||
plugin_state->tokens_list, scene_state->current_token_index)
|
||||
->data);
|
||||
const TokenInfo* tokenInfo = scene_state->current_token;
|
||||
|
||||
if(tokenInfo->token != NULL && tokenInfo->token_length > 0) {
|
||||
furi_mutex_acquire(
|
||||
@@ -258,7 +253,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
|
||||
key_length,
|
||||
curr_ts,
|
||||
plugin_state->timezone_offset,
|
||||
TOKEN_LIFETIME),
|
||||
tokenInfo->duration),
|
||||
scene_state->last_code,
|
||||
tokenInfo->digits);
|
||||
memset_s(key, key_length, 0, key_length);
|
||||
@@ -279,7 +274,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
uint16_t token_name_width = canvas_string_width(canvas, scene_state->last_code_name);
|
||||
uint16_t token_name_width = canvas_string_width(canvas, scene_state->current_token->name);
|
||||
if(SCREEN_WIDTH - token_name_width > 18) {
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
@@ -287,7 +282,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
|
||||
SCREEN_HEIGHT_CENTER - 20,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
scene_state->last_code_name);
|
||||
scene_state->current_token->name);
|
||||
} else {
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
@@ -295,7 +290,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
|
||||
SCREEN_HEIGHT_CENTER - 20,
|
||||
AlignLeft,
|
||||
AlignCenter,
|
||||
scene_state->last_code_name);
|
||||
scene_state->current_token->name);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, 0, SCREEN_HEIGHT_CENTER - 24, 9, 9);
|
||||
canvas_draw_box(canvas, SCREEN_WIDTH - 10, SCREEN_HEIGHT_CENTER - 24, 9, 9);
|
||||
@@ -313,6 +308,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
|
||||
|
||||
const uint8_t BAR_MARGIN = 3;
|
||||
const uint8_t BAR_HEIGHT = 4;
|
||||
const uint8_t TOKEN_LIFETIME = scene_state->current_token->duration;
|
||||
float percentDone = (float)(TOKEN_LIFETIME - curr_ts % TOKEN_LIFETIME) / (float)TOKEN_LIFETIME;
|
||||
uint8_t barWidth = (uint8_t)((float)(SCREEN_WIDTH - (BAR_MARGIN << 1)) * percentDone);
|
||||
uint8_t barX = ((SCREEN_WIDTH - (BAR_MARGIN << 1) - barWidth) >> 1) + BAR_MARGIN;
|
||||
|
||||
@@ -36,12 +36,13 @@
|
||||
#define MIN_PIN_SIZE 4
|
||||
#define MAX_APP_LENGTH 128
|
||||
|
||||
#define DISPLAY_BATTERY_BAR 0
|
||||
#define DISPLAY_BATTERY_PERCENT 1
|
||||
#define DISPLAY_BATTERY_INVERTED_PERCENT 2
|
||||
#define DISPLAY_BATTERY_RETRO_3 3
|
||||
#define DISPLAY_BATTERY_RETRO_5 4
|
||||
#define DISPLAY_BATTERY_BAR_PERCENT 5
|
||||
#define DISPLAY_BATTERY_NO 0
|
||||
#define DISPLAY_BATTERY_BAR 1
|
||||
#define DISPLAY_BATTERY_PERCENT 2
|
||||
#define DISPLAY_BATTERY_INVERTED_PERCENT 3
|
||||
#define DISPLAY_BATTERY_RETRO_3 4
|
||||
#define DISPLAY_BATTERY_RETRO_5 5
|
||||
#define DISPLAY_BATTERY_BAR_PERCENT 6
|
||||
|
||||
#define FAP_LOADER_APP_NAME "Applications"
|
||||
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
#define DOLPHIN_STATE_PATH INT_PATH(DOLPHIN_STATE_FILE_NAME)
|
||||
#define DOLPHIN_STATE_HEADER_MAGIC 0xD0
|
||||
#define DOLPHIN_STATE_HEADER_VERSION 0x01
|
||||
int level_array[30] = {100, 200, 300, 450, 600, 750, 950, 1150, 1350, 1600,
|
||||
1850, 2100, 2400, 2700, 3000, 3350, 3700, 4050, 4450, 4850,
|
||||
5250, 5700, 6150, 6600, 7100, 7600, 8100, 8650, 9200};
|
||||
|
||||
const int DOLPHIN_LEVELS[DOLPHIN_LEVEL_COUNT] = {100, 200, 300, 450, 600, 750, 950, 1150, 1350, 1600,
|
||||
1850, 2100, 2400, 2700, 3000, 3350, 3700, 4050, 4450, 4850,
|
||||
5250, 5700, 6150, 6600, 7100, 7600, 8100, 8650, 9200};
|
||||
|
||||
#define BUTTHURT_MAX 14
|
||||
#define BUTTHURT_MIN 0
|
||||
@@ -82,39 +83,43 @@ uint64_t dolphin_state_timestamp() {
|
||||
}
|
||||
|
||||
bool dolphin_state_is_levelup(int icounter) {
|
||||
for(int i = 0; i < 30; ++i) {
|
||||
if((icounter == level_array[i])) {
|
||||
for(int i = 0; i < DOLPHIN_LEVEL_COUNT; ++i) {
|
||||
if((icounter == DOLPHIN_LEVELS[i])) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
const int* dolphin_get_levels() {
|
||||
return DOLPHIN_LEVELS;
|
||||
}
|
||||
|
||||
uint8_t dolphin_get_level(int icounter) {
|
||||
for(int i = 0; i < 29; ++i) {
|
||||
if(icounter <= level_array[i]) {
|
||||
for(int i = 0; i < DOLPHIN_LEVEL_COUNT; ++i) {
|
||||
if(icounter <= DOLPHIN_LEVELS[i]) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return 30;
|
||||
return DOLPHIN_LEVEL_COUNT + 1;
|
||||
}
|
||||
|
||||
uint32_t dolphin_state_xp_above_last_levelup(int icounter) {
|
||||
for(int i = 1; i < 29; ++i) {
|
||||
if(icounter <= level_array[i]) {
|
||||
return icounter - level_array[i - 1];
|
||||
for(int i = DOLPHIN_LEVEL_COUNT; i >= 0; --i) {
|
||||
if(icounter >= DOLPHIN_LEVELS[i]) {
|
||||
return icounter - DOLPHIN_LEVELS[i];
|
||||
}
|
||||
}
|
||||
return icounter;
|
||||
}
|
||||
|
||||
uint32_t dolphin_state_xp_to_levelup(int icounter) {
|
||||
for(int i = 0; i < 29; ++i) {
|
||||
if(icounter <= level_array[i]) {
|
||||
return level_array[i] - icounter;
|
||||
for(int i = 0; i < DOLPHIN_LEVEL_COUNT; ++i) {
|
||||
if(icounter <= DOLPHIN_LEVELS[i]) {
|
||||
return DOLPHIN_LEVELS[i] - icounter;
|
||||
}
|
||||
}
|
||||
return (uint32_t)-1 - icounter;
|
||||
return (uint32_t)-1;
|
||||
}
|
||||
|
||||
void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) {
|
||||
@@ -198,4 +203,4 @@ void dolphin_state_clear_limits(DolphinState* dolphin_state) {
|
||||
}
|
||||
dolphin_state->data.butthurt_daily_limit = 0;
|
||||
dolphin_state->dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#define DOLPHIN_LEVEL_COUNT 29
|
||||
|
||||
typedef struct DolphinState DolphinState;
|
||||
typedef struct {
|
||||
uint8_t icounter_daily_limit[DolphinAppMAX];
|
||||
@@ -41,6 +43,8 @@ uint32_t dolphin_state_xp_to_levelup(int icounter);
|
||||
|
||||
uint32_t dolphin_state_xp_above_last_levelup(int icounter);
|
||||
|
||||
const int* dolphin_get_levels();
|
||||
|
||||
bool dolphin_state_is_levelup(int icounter);
|
||||
|
||||
void dolphin_state_increase_level(DolphinState* dolphin_state);
|
||||
|
||||
@@ -9,175 +9,185 @@
|
||||
void power_draw_battery_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
Power* power = context;
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Battery_25x8);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, -1, 0, 1, 8);
|
||||
canvas_draw_box(canvas, 0, -1, 24, 1);
|
||||
canvas_draw_box(canvas, 0, 8, 24, 1);
|
||||
canvas_draw_box(canvas, 25, 1, 2, 6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, 25, 2, 1, 4);
|
||||
|
||||
if(power->info.gauge_is_ok) {
|
||||
char batteryPercentile[4];
|
||||
snprintf(batteryPercentile, sizeof(batteryPercentile), "%d", power->info.charge);
|
||||
if((power->displayBatteryPercentage == DISPLAY_BATTERY_PERCENT) &&
|
||||
(power->state !=
|
||||
PowerStateCharging)) { //if display battery percentage, black background white text
|
||||
canvas_set_font(canvas, FontBatteryPercent);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, 1, 1, 22, 6);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_str_aligned(canvas, 11, 4, AlignCenter, AlignCenter, batteryPercentile);
|
||||
} else if(
|
||||
(power->displayBatteryPercentage == DISPLAY_BATTERY_INVERTED_PERCENT) &&
|
||||
(power->state !=
|
||||
PowerStateCharging)) { //if display inverted percentage, white background black text
|
||||
canvas_set_font(canvas, FontBatteryPercent);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str_aligned(canvas, 11, 4, AlignCenter, AlignCenter, batteryPercentile);
|
||||
} else if(
|
||||
(power->displayBatteryPercentage == DISPLAY_BATTERY_RETRO_3) &&
|
||||
(power->state != PowerStateCharging)) { //Retro style segmented display, 3 parts
|
||||
if(power->info.charge > 25) {
|
||||
canvas_draw_box(canvas, 2, 2, 6, 4);
|
||||
}
|
||||
if(power->info.charge > 50) {
|
||||
canvas_draw_box(canvas, 9, 2, 6, 4);
|
||||
}
|
||||
if(power->info.charge > 75) {
|
||||
canvas_draw_box(canvas, 16, 2, 6, 4);
|
||||
}
|
||||
} else if(
|
||||
(power->displayBatteryPercentage == DISPLAY_BATTERY_RETRO_5) &&
|
||||
(power->state != PowerStateCharging)) { //Retro style segmented display, 5 parts
|
||||
if(power->info.charge > 10) {
|
||||
canvas_draw_box(canvas, 2, 2, 3, 4);
|
||||
}
|
||||
if(power->info.charge > 30) {
|
||||
canvas_draw_box(canvas, 6, 2, 3, 4);
|
||||
}
|
||||
if(power->info.charge > 50) {
|
||||
canvas_draw_box(canvas, 10, 2, 3, 4);
|
||||
}
|
||||
if(power->info.charge > 70) {
|
||||
canvas_draw_box(canvas, 14, 2, 3, 4);
|
||||
}
|
||||
if(power->info.charge > 90) {
|
||||
canvas_draw_box(canvas, 18, 2, 3, 4);
|
||||
}
|
||||
} else if(
|
||||
(power->displayBatteryPercentage == DISPLAY_BATTERY_BAR_PERCENT) &&
|
||||
(power->state != PowerStateCharging) && // Default bar display with percentage
|
||||
(power->info.voltage_battery_charging >=
|
||||
4.2)) { // not looking nice with low voltage indicator
|
||||
canvas_set_font(canvas, FontBatteryPercent);
|
||||
|
||||
// align charge dispaly value with digits to draw
|
||||
uint8_t bar_charge = power->info.charge;
|
||||
if(bar_charge > 23 && bar_charge < 38) {
|
||||
bar_charge = 23;
|
||||
} else if(bar_charge >= 38 && bar_charge < 62) {
|
||||
bar_charge = 50;
|
||||
} else if(bar_charge >= 62 && bar_charge < 74) {
|
||||
bar_charge = 74;
|
||||
}
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, 1, 1, (bar_charge * 22) / 100, 6);
|
||||
|
||||
// drawing digits
|
||||
if(bar_charge < 38) { // both digits are black
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 11, 4, AlignCenter, AlignCenter, batteryPercentile);
|
||||
} else if(bar_charge >= 38 && bar_charge < 74) { // first digit is white
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
|
||||
// first
|
||||
char batteryPercentileFirstDigit[2];
|
||||
snprintf(
|
||||
batteryPercentileFirstDigit,
|
||||
sizeof(batteryPercentileFirstDigit),
|
||||
"%c",
|
||||
batteryPercentile[0]);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 9, 4, AlignCenter, AlignCenter, batteryPercentileFirstDigit);
|
||||
|
||||
// second
|
||||
char batteryPercentileSecondDigit[2];
|
||||
snprintf(
|
||||
batteryPercentileSecondDigit,
|
||||
sizeof(batteryPercentileSecondDigit),
|
||||
"%c",
|
||||
batteryPercentile[1]);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 15, 4, AlignCenter, AlignCenter, batteryPercentileSecondDigit);
|
||||
} else { // charge >= 62, both digits are white
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 11, 4, AlignCenter, AlignCenter, batteryPercentile);
|
||||
}
|
||||
} else { //default bar display, added here to serve as fallback/default behaviour.
|
||||
canvas_draw_box(canvas, 2, 2, (power->info.charge + 4) / 5, 4);
|
||||
}
|
||||
|
||||
// TODO: Verify if it displays correctly with custom battery skins !!!
|
||||
if(power->info.voltage_battery_charging < 4.2) {
|
||||
// Battery charging voltage is modified, indicate with cross pattern
|
||||
canvas_invert_color(canvas);
|
||||
uint8_t battery_bar_width = (power->info.charge + 4) / 5;
|
||||
bool cross_odd = false;
|
||||
// Start 1 further in from the battery bar's x position
|
||||
for(uint8_t x = 3; x <= battery_bar_width; x++) {
|
||||
// Cross pattern is from the center of the battery bar
|
||||
// y = 2 + 1 (inset) + 1 (for every other)
|
||||
canvas_draw_dot(canvas, x, 3 + (uint8_t)cross_odd);
|
||||
cross_odd = !cross_odd;
|
||||
}
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
if(power->state == PowerStateCharging) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
// TODO: replace -1 magic for uint8_t with re-framing
|
||||
if(power->displayBatteryPercentage == DISPLAY_BATTERY_PERCENT ||
|
||||
power->displayBatteryPercentage == DISPLAY_BATTERY_BAR_PERCENT) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, 1, 1, 22, 6);
|
||||
canvas_draw_icon(canvas, 2, -1, &I_Charging_lightning_9x10);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_icon(canvas, 2, -1, &I_Charging_lightning_mask_9x10);
|
||||
canvas_set_font(canvas, FontBatteryPercent);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 16, 4, AlignCenter, AlignCenter, batteryPercentile);
|
||||
} else if(power->displayBatteryPercentage == DISPLAY_BATTERY_INVERTED_PERCENT) {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, 1, 1, 22, 6);
|
||||
canvas_draw_icon(canvas, 2, -1, &I_Charging_lightning_9x10);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_icon(canvas, 2, -1, &I_Charging_lightning_mask_9x10);
|
||||
canvas_set_font(canvas, FontBatteryPercent);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 16, 4, AlignCenter, AlignCenter, batteryPercentile);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_mask_9x10);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_9x10);
|
||||
}
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
}
|
||||
if(power->displayBatteryPercentage == DISPLAY_BATTERY_NO) {
|
||||
// no draw
|
||||
} else {
|
||||
canvas_draw_box(canvas, 8, 3, 8, 2);
|
||||
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Battery_25x8);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, -1, 0, 1, 8);
|
||||
canvas_draw_box(canvas, 0, -1, 24, 1);
|
||||
canvas_draw_box(canvas, 0, 8, 24, 1);
|
||||
canvas_draw_box(canvas, 25, 1, 2, 6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, 25, 2, 1, 4);
|
||||
|
||||
if(power->info.gauge_is_ok) {
|
||||
char batteryPercentile[4];
|
||||
snprintf(batteryPercentile, sizeof(batteryPercentile), "%d", power->info.charge);
|
||||
|
||||
if((power->displayBatteryPercentage == DISPLAY_BATTERY_PERCENT) &&
|
||||
(power->state !=
|
||||
PowerStateCharging)) { //if display battery percentage, black background white text
|
||||
canvas_set_font(canvas, FontBatteryPercent);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, 1, 1, 22, 6);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_str_aligned(canvas, 11, 4, AlignCenter, AlignCenter, batteryPercentile);
|
||||
} else if(
|
||||
(power->displayBatteryPercentage == DISPLAY_BATTERY_INVERTED_PERCENT) &&
|
||||
(power->state !=
|
||||
PowerStateCharging)) { //if display inverted percentage, white background black text
|
||||
canvas_set_font(canvas, FontBatteryPercent);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str_aligned(canvas, 11, 4, AlignCenter, AlignCenter, batteryPercentile);
|
||||
} else if(
|
||||
(power->displayBatteryPercentage == DISPLAY_BATTERY_RETRO_3) &&
|
||||
(power->state != PowerStateCharging)) { //Retro style segmented display, 3 parts
|
||||
if(power->info.charge > 25) {
|
||||
canvas_draw_box(canvas, 2, 2, 6, 4);
|
||||
}
|
||||
if(power->info.charge > 50) {
|
||||
canvas_draw_box(canvas, 9, 2, 6, 4);
|
||||
}
|
||||
if(power->info.charge > 75) {
|
||||
canvas_draw_box(canvas, 16, 2, 6, 4);
|
||||
}
|
||||
} else if(
|
||||
(power->displayBatteryPercentage == DISPLAY_BATTERY_RETRO_5) &&
|
||||
(power->state != PowerStateCharging)) { //Retro style segmented display, 5 parts
|
||||
if(power->info.charge > 10) {
|
||||
canvas_draw_box(canvas, 2, 2, 3, 4);
|
||||
}
|
||||
if(power->info.charge > 30) {
|
||||
canvas_draw_box(canvas, 6, 2, 3, 4);
|
||||
}
|
||||
if(power->info.charge > 50) {
|
||||
canvas_draw_box(canvas, 10, 2, 3, 4);
|
||||
}
|
||||
if(power->info.charge > 70) {
|
||||
canvas_draw_box(canvas, 14, 2, 3, 4);
|
||||
}
|
||||
if(power->info.charge > 90) {
|
||||
canvas_draw_box(canvas, 18, 2, 3, 4);
|
||||
}
|
||||
} else if(
|
||||
(power->displayBatteryPercentage == DISPLAY_BATTERY_BAR_PERCENT) &&
|
||||
(power->state != PowerStateCharging) && // Default bar display with percentage
|
||||
(power->info.voltage_battery_charging >=
|
||||
4.2)) { // not looking nice with low voltage indicator
|
||||
canvas_set_font(canvas, FontBatteryPercent);
|
||||
|
||||
// align charge dispaly value with digits to draw
|
||||
uint8_t bar_charge = power->info.charge;
|
||||
if(bar_charge > 23 && bar_charge < 38) {
|
||||
bar_charge = 23;
|
||||
} else if(bar_charge >= 38 && bar_charge < 62) {
|
||||
bar_charge = 50;
|
||||
} else if(bar_charge >= 62 && bar_charge < 74) {
|
||||
bar_charge = 74;
|
||||
}
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, 1, 1, (bar_charge * 22) / 100, 6);
|
||||
|
||||
// drawing digits
|
||||
if(bar_charge < 38) { // both digits are black
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 11, 4, AlignCenter, AlignCenter, batteryPercentile);
|
||||
} else if(bar_charge >= 38 && bar_charge < 74) { // first digit is white
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
|
||||
// first
|
||||
char batteryPercentileFirstDigit[2];
|
||||
snprintf(
|
||||
batteryPercentileFirstDigit,
|
||||
sizeof(batteryPercentileFirstDigit),
|
||||
"%c",
|
||||
batteryPercentile[0]);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 9, 4, AlignCenter, AlignCenter, batteryPercentileFirstDigit);
|
||||
|
||||
// second
|
||||
char batteryPercentileSecondDigit[2];
|
||||
snprintf(
|
||||
batteryPercentileSecondDigit,
|
||||
sizeof(batteryPercentileSecondDigit),
|
||||
"%c",
|
||||
batteryPercentile[1]);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 15, 4, AlignCenter, AlignCenter, batteryPercentileSecondDigit);
|
||||
} else { // charge >= 62, both digits are white
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 11, 4, AlignCenter, AlignCenter, batteryPercentile);
|
||||
}
|
||||
} else { //default bar display, added here to serve as fallback/default behaviour.
|
||||
canvas_draw_box(canvas, 2, 2, (power->info.charge + 4) / 5, 4);
|
||||
}
|
||||
|
||||
// TODO: Verify if it displays correctly with custom battery skins !!!
|
||||
if(power->info.voltage_battery_charging < 4.2) {
|
||||
// Battery charging voltage is modified, indicate with cross pattern
|
||||
canvas_invert_color(canvas);
|
||||
uint8_t battery_bar_width = (power->info.charge + 4) / 5;
|
||||
bool cross_odd = false;
|
||||
// Start 1 further in from the battery bar's x position
|
||||
for(uint8_t x = 3; x <= battery_bar_width; x++) {
|
||||
// Cross pattern is from the center of the battery bar
|
||||
// y = 2 + 1 (inset) + 1 (for every other)
|
||||
canvas_draw_dot(canvas, x, 3 + (uint8_t)cross_odd);
|
||||
cross_odd = !cross_odd;
|
||||
}
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
if(power->state == PowerStateCharging) {
|
||||
canvas_set_bitmap_mode(canvas, 1);
|
||||
// TODO: replace -1 magic for uint8_t with re-framing
|
||||
if(power->displayBatteryPercentage == DISPLAY_BATTERY_PERCENT ||
|
||||
power->displayBatteryPercentage == DISPLAY_BATTERY_BAR_PERCENT) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, 1, 1, 22, 6);
|
||||
canvas_draw_icon(canvas, 2, -1, &I_Charging_lightning_9x10);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_icon(canvas, 2, -1, &I_Charging_lightning_mask_9x10);
|
||||
canvas_set_font(canvas, FontBatteryPercent);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 16, 4, AlignCenter, AlignCenter, batteryPercentile);
|
||||
} else if(power->displayBatteryPercentage == DISPLAY_BATTERY_INVERTED_PERCENT) {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, 1, 1, 22, 6);
|
||||
canvas_draw_icon(canvas, 2, -1, &I_Charging_lightning_9x10);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_icon(canvas, 2, -1, &I_Charging_lightning_mask_9x10);
|
||||
canvas_set_font(canvas, FontBatteryPercent);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 16, 4, AlignCenter, AlignCenter, batteryPercentile);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_mask_9x10);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_icon(canvas, 8, -1, &I_Charging_lightning_9x10);
|
||||
}
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
}
|
||||
} else {
|
||||
canvas_draw_box(canvas, 8, 3, 8, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ViewPort* power_battery_view_port_alloc(Power* power) {
|
||||
ViewPort* battery_view_port = view_port_alloc();
|
||||
view_port_set_width(battery_view_port, icon_get_width(&I_Battery_25x8));
|
||||
view_port_draw_callback_set(battery_view_port, power_draw_battery_callback, power);
|
||||
gui_add_view_port(power->gui, battery_view_port, GuiLayerStatusBarRight);
|
||||
if(power->displayBatteryPercentage == DISPLAY_BATTERY_NO) {
|
||||
// no draw
|
||||
} else {
|
||||
view_port_set_width(battery_view_port, icon_get_width(&I_Battery_25x8));
|
||||
view_port_draw_callback_set(battery_view_port, power_draw_battery_callback, power);
|
||||
gui_add_view_port(power->gui, battery_view_port, GuiLayerStatusBarRight);
|
||||
}
|
||||
return battery_view_port;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,11 @@ const char* const auto_lock_delay_text[AUTO_LOCK_DELAY_COUNT] = {
|
||||
const uint32_t auto_lock_delay_value[AUTO_LOCK_DELAY_COUNT] =
|
||||
{0, 10000, 15000, 30000, 60000, 90000, 120000, 300000, 600000};
|
||||
|
||||
#define BATTERY_VIEW_COUNT 6
|
||||
#define BATTERY_VIEW_COUNT 7
|
||||
const char* const battery_view_count_text[BATTERY_VIEW_COUNT] =
|
||||
{"Bar", "%", "Inv. %", "Retro 3", "Retro 5", "Bar %"};
|
||||
{"Off", "Bar", "%", "Inv. %", "Retro 3", "Retro 5", "Bar %"};
|
||||
const uint32_t displayBatteryPercentage_value[BATTERY_VIEW_COUNT] = {
|
||||
DISPLAY_BATTERY_NO,
|
||||
DISPLAY_BATTERY_BAR,
|
||||
DISPLAY_BATTERY_PERCENT,
|
||||
DISPLAY_BATTERY_INVERTED_PERCENT,
|
||||
|
||||
@@ -102,7 +102,7 @@ static void render_callback(Canvas* canvas, void* _ctx) {
|
||||
}
|
||||
uint32_t xp_have = xp_levelup - xp_need;
|
||||
|
||||
if(stats->level == 30) {
|
||||
if(stats->level == DOLPHIN_LEVEL_COUNT + 1) {
|
||||
xp_progress = 0;
|
||||
} else {
|
||||
xp_progress = xp_need * 64 / xp_levelup;
|
||||
@@ -116,7 +116,7 @@ static void render_callback(Canvas* canvas, void* _ctx) {
|
||||
}
|
||||
|
||||
// portrait
|
||||
furi_assert((stats->level > 0) && (stats->level <= 30));
|
||||
furi_assert((stats->level > 0) && (stats->level <= DOLPHIN_LEVEL_COUNT + 1));
|
||||
uint16_t tmpLvl = 0;
|
||||
if(settings->sfw_mode) {
|
||||
canvas_draw_icon(canvas, 11, 2, portraits_sfw[mood][tmpLvl]);
|
||||
@@ -126,7 +126,11 @@ static void render_callback(Canvas* canvas, void* _ctx) {
|
||||
|
||||
const char* my_name = furi_hal_version_get_name_ptr();
|
||||
snprintf(level_str, 12, "Level: %hu", stats->level);
|
||||
snprintf(xp_str, 12, "%lu/%lu", xp_have, xp_levelup);
|
||||
if(stats->level == DOLPHIN_LEVEL_COUNT + 1) {
|
||||
snprintf(xp_str, 12, "Max Level!");
|
||||
} else {
|
||||
snprintf(xp_str, 12, "%lu/%lu", xp_have, xp_levelup);
|
||||
}
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 58, 10, my_name ? my_name : "Unknown");
|
||||
canvas_draw_str(canvas, 58, 22, mood_str);
|
||||
@@ -138,7 +142,7 @@ static void render_callback(Canvas* canvas, void* _ctx) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, 123 - xp_progress, 45, xp_progress + 1, 5);
|
||||
canvas_draw_box(canvas, 123 - xp_progress, 45, xp_progress + (xp_progress > 0), 5);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
canvas_draw_icon(canvas, 52, 51, &I_Ok_btn_9x9);
|
||||
|
||||
@@ -54,13 +54,14 @@ assetsenv.Alias("proto_ver", proto_ver)
|
||||
|
||||
# Gather everything into a static lib
|
||||
assets_parts = (icons, proto, dolphin_blocking, dolphin_internal, proto_ver)
|
||||
env.Replace(FW_ASSETS_HEADERS=assets_parts)
|
||||
|
||||
assetslib = assetsenv.Library("${FW_LIB_NAME}", assets_parts)
|
||||
assetsenv.Install("${LIB_DIST_DIR}", assetslib)
|
||||
|
||||
|
||||
# Resources for SD card
|
||||
|
||||
env.SetDefault(FW_RESOURCES=None)
|
||||
if assetsenv["IS_BASE_FIRMWARE"]:
|
||||
# External dolphin animations
|
||||
dolphin_external = assetsenv.DolphinExtBuilder(
|
||||
@@ -92,8 +93,7 @@ if assetsenv["IS_BASE_FIRMWARE"]:
|
||||
)
|
||||
|
||||
# Exporting resources node to external environment
|
||||
env["FW_ASSETS_HEADERS"] = assets_parts
|
||||
env["FW_RESOURCES"] = resources
|
||||
env.Replace(FW_RESOURCES=resources)
|
||||
assetsenv.Alias("resources", resources)
|
||||
|
||||
Return("assetslib")
|
||||
@@ -15,6 +15,7 @@ env = ENV.Clone(
|
||||
("compilation_db", {"COMPILATIONDB_COMSTR": "\tCDB\t${TARGET}"}),
|
||||
"fwbin",
|
||||
"fbt_apps",
|
||||
"pvsstudio",
|
||||
],
|
||||
COMPILATIONDB_USE_ABSPATH=False,
|
||||
BUILD_DIR=fw_build_meta["build_dir"],
|
||||
@@ -69,6 +70,8 @@ env = ENV.Clone(
|
||||
],
|
||||
},
|
||||
},
|
||||
SDK_APISYMS=None,
|
||||
_APP_ICONS=None,
|
||||
)
|
||||
|
||||
|
||||
@@ -128,9 +131,6 @@ if extra_int_apps := GetOption("extra_int_apps"):
|
||||
fwenv.Append(APPS=extra_int_apps.split(","))
|
||||
|
||||
|
||||
if fwenv["FAP_EXAMPLES"]:
|
||||
fwenv.Append(APPDIRS=[("applications/examples", False)])
|
||||
|
||||
for app_dir, _ in env["APPDIRS"]:
|
||||
app_dir_node = env.Dir("#").Dir(app_dir)
|
||||
|
||||
@@ -273,6 +273,24 @@ Precious(fwcdb)
|
||||
NoClean(fwcdb)
|
||||
Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_cdb", fwcdb)
|
||||
|
||||
pvscheck = fwenv.PVSCheck("pvsreport.log", fwcdb)
|
||||
Depends(
|
||||
pvscheck,
|
||||
[
|
||||
fwenv["FW_VERSION_JSON"],
|
||||
fwenv["FW_ASSETS_HEADERS"],
|
||||
fwenv["SDK_APISYMS"],
|
||||
fwenv["_APP_ICONS"],
|
||||
],
|
||||
)
|
||||
Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_pvscheck", pvscheck)
|
||||
AlwaysBuild(pvscheck)
|
||||
Precious(pvscheck)
|
||||
|
||||
pvsreport = fwenv.PVSReport(None, pvscheck, REPORT_DIR=Dir("pvsreport"))
|
||||
Alias(fwenv["FIRMWARE_BUILD_CFG"] + "_pvs", pvsreport)
|
||||
AlwaysBuild(pvsreport)
|
||||
|
||||
# If current configuration was explicitly requested, generate compilation database
|
||||
# and link its directory as build/latest
|
||||
if should_gen_cdb_and_link_dir(fwenv, BUILD_TARGETS):
|
||||
|
||||
@@ -16,8 +16,8 @@ extern "C" {
|
||||
|
||||
#define FURI_HAL_VERSION_NAME_LENGTH 8
|
||||
#define FURI_HAL_VERSION_ARRAY_NAME_LENGTH (FURI_HAL_VERSION_NAME_LENGTH + 1)
|
||||
/** BLE symbol + "Flipper " + name */
|
||||
#define FURI_HAL_VERSION_DEVICE_NAME_LENGTH (1 + 8 + FURI_HAL_VERSION_ARRAY_NAME_LENGTH)
|
||||
/** BLE symbol + name */
|
||||
#define FURI_HAL_VERSION_DEVICE_NAME_LENGTH (1 + FURI_HAL_VERSION_ARRAY_NAME_LENGTH)
|
||||
|
||||
/** OTP Versions enum */
|
||||
typedef enum {
|
||||
|
||||
@@ -93,7 +93,11 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef FURI_BIT_CLEAR
|
||||
#define FURI_BIT_CLEAR(x, n) ((x) &= ~(1UL << (n)))
|
||||
#define FURI_BIT_CLEAR(x, n) \
|
||||
({ \
|
||||
__typeof__(x) _x = (1); \
|
||||
(x) &= ~(_x << (n)); \
|
||||
})
|
||||
#endif
|
||||
|
||||
#define FURI_SW_MEMBARRIER() asm volatile("" : : : "memory")
|
||||
|
||||
@@ -244,7 +244,7 @@ LevelDuration protocol_fdx_b_encoder_yield(ProtocolFDXB* protocol) {
|
||||
static uint64_t protocol_fdx_b_get_national_code(const uint8_t* data) {
|
||||
uint64_t national_code = bit_lib_get_bits_32(data, 0, 32);
|
||||
national_code = national_code << 32;
|
||||
national_code |= bit_lib_get_bits_32(data, 32, 6) << (32 - 6);
|
||||
national_code |= (uint64_t)bit_lib_get_bits_32(data, 32, 6) << (32 - 6);
|
||||
bit_lib_reverse_bits((uint8_t*)&national_code, 0, 64);
|
||||
return national_code;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,16 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#define bit_read(value, bit) (((value) >> (bit)) & 0x01)
|
||||
#define bit_set(value, bit) ((value) |= (1UL << (bit)))
|
||||
#define bit_clear(value, bit) ((value) &= ~(1UL << (bit)))
|
||||
#define bit_set(value, bit) \
|
||||
({ \
|
||||
__typeof__(value) _one = (1); \
|
||||
(value) |= (_one << (bit)); \
|
||||
})
|
||||
#define bit_clear(value, bit) \
|
||||
({ \
|
||||
__typeof__(value) _one = (1); \
|
||||
(value) &= ~(_one << (bit)); \
|
||||
})
|
||||
#define bit_write(value, bit, bitvalue) (bitvalue ? bit_set(value, bit) : bit_clear(value, bit))
|
||||
#define DURATION_DIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y)))
|
||||
|
||||
|
||||
@@ -280,9 +280,9 @@ static bool subghz_protocol_chamb_code_to_bit(uint64_t* data, uint8_t size) {
|
||||
uint64_t data_tmp = data[0];
|
||||
uint64_t data_res = 0;
|
||||
for(uint8_t i = 0; i < size; i++) {
|
||||
if((data_tmp & 0xF) == CHAMBERLAIN_CODE_BIT_0) {
|
||||
if((data_tmp & 0xFll) == CHAMBERLAIN_CODE_BIT_0) {
|
||||
bit_write(data_res, i, 0);
|
||||
} else if((data_tmp & 0xF) == CHAMBERLAIN_CODE_BIT_1) {
|
||||
} else if((data_tmp & 0xFll) == CHAMBERLAIN_CODE_BIT_1) {
|
||||
bit_write(data_res, i, 1);
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -224,7 +224,7 @@ static bool subghz_protocol_secplus_v1_encode(SubGhzProtocolEncoderSecPlus_v1* i
|
||||
instance->generic.data &= 0xFFFFFFFF00000000;
|
||||
instance->generic.data |= rolling;
|
||||
|
||||
if(rolling > 0xFFFFFFFF) {
|
||||
if(rolling == 0xFFFFFFFF) {
|
||||
rolling = 0xE6000000;
|
||||
}
|
||||
if(fixed > 0xCFD41B90) {
|
||||
|
||||
@@ -7,7 +7,7 @@ from SCons.Node import NodeList
|
||||
import SCons.Warnings
|
||||
|
||||
from fbt.elfmanifest import assemble_manifest_data
|
||||
from fbt.appmanifest import FlipperApplication, FlipperManifestException
|
||||
from fbt.appmanifest import FlipperApplication, FlipperManifestException, FlipperAppType
|
||||
from fbt.sdk.cache import SdkCache
|
||||
from fbt.util import extract_abs_dir_path
|
||||
|
||||
@@ -32,6 +32,7 @@ def BuildAppElf(env, app):
|
||||
ext_apps_work_dir = env.subst("$EXT_APPS_WORK_DIR")
|
||||
app_work_dir = os.path.join(ext_apps_work_dir, app.appid)
|
||||
|
||||
env.SetDefault(_APP_ICONS=[])
|
||||
env.VariantDir(app_work_dir, app._appdir, duplicate=False)
|
||||
|
||||
app_env = env.Clone(FAP_SRC_DIR=app._appdir, FAP_WORK_DIR=app_work_dir)
|
||||
@@ -63,6 +64,7 @@ def BuildAppElf(env, app):
|
||||
icon_bundle_name=f"{app.fap_icon_assets_symbol if app.fap_icon_assets_symbol else app.appid }_icons",
|
||||
)
|
||||
app_env.Alias("_fap_icons", fap_icons)
|
||||
env.Append(_APP_ICONS=[fap_icons])
|
||||
|
||||
private_libs = []
|
||||
|
||||
@@ -233,11 +235,18 @@ def GetExtAppFromPath(env, app_dir):
|
||||
return app_artifacts
|
||||
|
||||
|
||||
def fap_dist_emitter(target, source, env):
|
||||
def resources_fap_dist_emitter(target, source, env):
|
||||
target_dir = target[0]
|
||||
|
||||
target = []
|
||||
for _, app_artifacts in env["EXT_APPS"].items():
|
||||
# We don't deploy example apps & debug tools with SD card resources
|
||||
if (
|
||||
app_artifacts.app.apptype == FlipperAppType.DEBUG
|
||||
or app_artifacts.app.fap_category == "Examples"
|
||||
):
|
||||
continue
|
||||
|
||||
source.extend(app_artifacts.compact)
|
||||
target.append(
|
||||
target_dir.Dir(app_artifacts.app.fap_category).File(
|
||||
@@ -248,7 +257,7 @@ def fap_dist_emitter(target, source, env):
|
||||
return (target, source)
|
||||
|
||||
|
||||
def fap_dist_action(target, source, env):
|
||||
def resources_fap_dist_action(target, source, env):
|
||||
# FIXME
|
||||
target_dir = env.Dir("#/assets/resources/apps")
|
||||
|
||||
@@ -281,10 +290,10 @@ def generate(env, **kw):
|
||||
BUILDERS={
|
||||
"FapDist": Builder(
|
||||
action=Action(
|
||||
fap_dist_action,
|
||||
resources_fap_dist_action,
|
||||
"$FAPDISTCOMSTR",
|
||||
),
|
||||
emitter=fap_dist_emitter,
|
||||
emitter=resources_fap_dist_emitter,
|
||||
),
|
||||
"EmbedAppMetadata": Builder(
|
||||
action=[
|
||||
|
||||
@@ -31,6 +31,8 @@ Other:
|
||||
run linters
|
||||
format, format_py:
|
||||
run code formatters
|
||||
firmware_pvs:
|
||||
generate a PVS-Studio report
|
||||
|
||||
For more targets & info, see documentation/fbt.md
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
from SCons.Builder import Builder
|
||||
from SCons.Action import Action
|
||||
from SCons.Script import Delete, Mkdir, GetBuildFailures
|
||||
import multiprocessing
|
||||
import webbrowser
|
||||
import atexit
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
__no_browser = False
|
||||
|
||||
|
||||
def _set_browser_action(target, source, env):
|
||||
if env["PVSNOBROWSER"]:
|
||||
global __no_browser
|
||||
__no_browser = True
|
||||
|
||||
|
||||
def emit_pvsreport(target, source, env):
|
||||
target_dir = env["REPORT_DIR"]
|
||||
if env["PLATFORM"] == "win32":
|
||||
# Report generator on Windows emits to a subfolder of given output folder
|
||||
target_dir = target_dir.Dir("fullhtml")
|
||||
return [target_dir.File("index.html")], source
|
||||
|
||||
|
||||
def atexist_handler():
|
||||
global __no_browser
|
||||
if __no_browser:
|
||||
return
|
||||
|
||||
for bf in GetBuildFailures():
|
||||
if bf.node.exists and bf.node.name.endswith(".html"):
|
||||
# macOS
|
||||
if sys.platform == "darwin":
|
||||
subprocess.run(["open", bf.node.abspath])
|
||||
else:
|
||||
webbrowser.open(bf.node.abspath)
|
||||
break
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.SetDefault(
|
||||
PVSNCORES=multiprocessing.cpu_count(),
|
||||
PVSOPTIONS=[
|
||||
"@.pvsoptions",
|
||||
"-j${PVSNCORES}",
|
||||
# "--incremental", # kinda broken on PVS side
|
||||
],
|
||||
PVSCONVOPTIONS=[
|
||||
"-a",
|
||||
"GA:1,2,3",
|
||||
"-t",
|
||||
"fullhtml",
|
||||
"--indicate-warnings",
|
||||
],
|
||||
)
|
||||
|
||||
if env["PLATFORM"] == "win32":
|
||||
env.SetDefault(
|
||||
PVSCHECKBIN="CompilerCommandsAnalyzer.exe",
|
||||
PVSCONVBIN="PlogConverter.exe",
|
||||
)
|
||||
else:
|
||||
env.SetDefault(
|
||||
PVSCHECKBIN="pvs-studio-analyzer",
|
||||
PVSCONVBIN="plog-converter",
|
||||
)
|
||||
|
||||
if not env["VERBOSE"]:
|
||||
env.SetDefault(
|
||||
PVSCHECKCOMSTR="\tPVS\t${TARGET}",
|
||||
PVSCONVCOMSTR="\tPVSREP\t${TARGET}",
|
||||
)
|
||||
|
||||
env.Append(
|
||||
BUILDERS={
|
||||
"PVSCheck": Builder(
|
||||
action=Action(
|
||||
'${PVSCHECKBIN} analyze ${PVSOPTIONS} -f "${SOURCE}" -o "${TARGET}"',
|
||||
"${PVSCHECKCOMSTR}",
|
||||
),
|
||||
suffix=".log",
|
||||
src_suffix=".json",
|
||||
),
|
||||
"PVSReport": Builder(
|
||||
action=Action(
|
||||
[
|
||||
Delete("${TARGET.dir}"),
|
||||
# PlogConverter.exe and plog-converter have different behavior
|
||||
Mkdir("${TARGET.dir}") if env["PLATFORM"] == "win32" else None,
|
||||
Action(_set_browser_action, None),
|
||||
'${PVSCONVBIN} ${PVSCONVOPTIONS} "${SOURCE}" -o "${REPORT_DIR}"',
|
||||
],
|
||||
"${PVSCONVCOMSTR}",
|
||||
),
|
||||
emitter=emit_pvsreport,
|
||||
src_suffix=".log",
|
||||
),
|
||||
}
|
||||
)
|
||||
atexit.register(atexist_handler)
|
||||
|
||||
|
||||
def exists(env):
|
||||
return True
|
||||
@@ -43,9 +43,11 @@ fbtenv_restore_env()
|
||||
|
||||
PYTHONNOUSERSITE="$SAVED_PYTHONNOUSERSITE";
|
||||
PYTHONPATH="$SAVED_PYTHONPATH";
|
||||
PYTHONHOME="$SAVED_PYTHONHOME";
|
||||
|
||||
unset SAVED_PYTHONNOUSERSITE;
|
||||
unset SAVED_PYTHONPATH;
|
||||
unset SAVED_PYTHONHOME;
|
||||
|
||||
unset SCRIPT_PATH;
|
||||
unset FBT_TOOLCHAIN_VERSION;
|
||||
@@ -69,7 +71,7 @@ fbtenv_check_sourced()
|
||||
return 1;
|
||||
}
|
||||
|
||||
fbtenv_chck_many_source()
|
||||
fbtenv_check_if_sourced_multiple_times()
|
||||
{
|
||||
if ! echo "${PS1:-""}" | grep -qF "[fbt]"; then
|
||||
if ! echo "${PROMPT:-""}" | grep -qF "[fbt]"; then
|
||||
@@ -285,7 +287,7 @@ fbtenv_main()
|
||||
fbtenv_restore_env;
|
||||
return 0;
|
||||
fi
|
||||
fbtenv_chck_many_source; # many source it's just a warning
|
||||
fbtenv_check_if_sourced_multiple_times; # many source it's just a warning
|
||||
fbtenv_check_script_path || return 1;
|
||||
fbtenv_check_download_toolchain || return 1;
|
||||
fbtenv_set_shell_prompt;
|
||||
@@ -293,12 +295,14 @@ fbtenv_main()
|
||||
PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH";
|
||||
PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH";
|
||||
PATH="$TOOLCHAIN_ARCH_DIR/openocd/bin:$PATH";
|
||||
|
||||
|
||||
SAVED_PYTHONNOUSERSITE="${PYTHONNOUSERSITE:-""}";
|
||||
SAVED_PYTHONPATH="${PYTHONPATH:-""}";
|
||||
SAVED_PYTHONHOME="${PYTHONHOME:-""}";
|
||||
|
||||
PYTHONNOUSERSITE=1;
|
||||
PYTHONPATH=;
|
||||
PYTHONHOME=;
|
||||
}
|
||||
|
||||
fbtenv_main "${1:-""}";
|
||||
|
||||
@@ -81,16 +81,6 @@ vars.AddVariables(
|
||||
"7",
|
||||
],
|
||||
),
|
||||
BoolVariable(
|
||||
"DEBUG_TOOLS",
|
||||
help="Enable debug tools to be built",
|
||||
default=False,
|
||||
),
|
||||
BoolVariable(
|
||||
"FAP_EXAMPLES",
|
||||
help="Enable example applications to be built",
|
||||
default=False,
|
||||
),
|
||||
(
|
||||
"DIST_SUFFIX",
|
||||
"Suffix for binaries in build output for dist targets",
|
||||
@@ -242,9 +232,15 @@ vars.AddVariables(
|
||||
("applications/system", False),
|
||||
("applications/debug", False),
|
||||
("applications/plugins", False),
|
||||
("applications/examples", False),
|
||||
("applications_user", False),
|
||||
],
|
||||
),
|
||||
BoolVariable(
|
||||
"PVSNOBROWSER",
|
||||
help="Don't open browser after generating error repots",
|
||||
default=False,
|
||||
),
|
||||
)
|
||||
|
||||
Return("vars")
|
||||
@@ -65,9 +65,8 @@ class FlipperExtAppBuildArtifacts:
|
||||
apps_to_build_as_faps = [
|
||||
FlipperAppType.PLUGIN,
|
||||
FlipperAppType.EXTERNAL,
|
||||
FlipperAppType.DEBUG,
|
||||
]
|
||||
if appenv["DEBUG_TOOLS"]:
|
||||
apps_to_build_as_faps.append(FlipperAppType.DEBUG)
|
||||
|
||||
known_extapps = [
|
||||
app
|
||||
@@ -143,6 +142,11 @@ sdk_apisyms = appenv.SDKSymGenerator(
|
||||
"${BUILD_DIR}/assets/compiled/symbols.h", appenv["SDK_DEFINITION"]
|
||||
)
|
||||
Alias("api_syms", sdk_apisyms)
|
||||
ENV.Replace(
|
||||
SDK_APISYMS=sdk_apisyms,
|
||||
_APP_ICONS=appenv["_APP_ICONS"],
|
||||
)
|
||||
|
||||
|
||||
if appenv["FORCE"]:
|
||||
appenv.AlwaysBuild(sdk_source, sdk_tree, sdk_apicheck, sdk_apisyms)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
pushd .
|
||||
./fbt updater_package
|
||||
popd
|
||||
@@ -0,0 +1,12 @@
|
||||
sonar.projectKey=ClaraCrazy_Flipper-Xtreme
|
||||
sonar.organization=claracrazy
|
||||
|
||||
# This is the name and version displayed in the SonarCloud UI.
|
||||
#sonar.projectName=Flipper-Xtreme
|
||||
#sonar.projectVersion=1.0
|
||||
|
||||
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
|
||||
#sonar.sources=.
|
||||
|
||||
# Encoding of the source code. Default is default system encoding
|
||||
#sonar.sourceEncoding=UTF-8
|
||||