mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-10 05:59:08 -07:00
Merge branch 'dev' of https://github.com/Next-Flip/Momentum-Firmware into dev
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @file furi_hal_subghz.h
|
||||
* SubGhz HAL API
|
||||
* @file cc1101_ext.h
|
||||
* @brief External CC1101 transceiver access API.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
# Apps Assets folder Example
|
||||
# Apps Assets folder Example {#example_app_assets}
|
||||
|
||||
This example shows how to use the Apps Assets folder to store data that is not part of the application itself, but is required for its operation, and that data is provided with the application.
|
||||
|
||||
## Source code
|
||||
|
||||
Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_apps_assets).
|
||||
|
||||
## What is the Apps Assets Folder?
|
||||
|
||||
The **Apps Assets** folder is a folder where external applications unpack their assets.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file example_apps_assets.c
|
||||
* @brief Application assets example.
|
||||
*/
|
||||
#include <furi.h>
|
||||
#include <storage/storage.h>
|
||||
#include <toolbox/stream/stream.h>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
# Apps Data folder Example
|
||||
# Apps Data folder Example {#example_app_data}
|
||||
|
||||
This example demonstrates how to utilize the Apps Data folder to store data that is not part of the app itself, such as user data, configuration files, and so forth.
|
||||
|
||||
## Source code
|
||||
|
||||
Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_apps_data).
|
||||
|
||||
## What is the Apps Data Folder?
|
||||
|
||||
The **Apps Data** folder is a folder used to store data for external apps that are not part of the main firmware.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file example_apps_data.c
|
||||
* @brief Application data example.
|
||||
*/
|
||||
#include <furi.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file ble_beacon_app.h
|
||||
* @brief BLE beacon example.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "extra_beacon.h"
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file example_custom_font.c
|
||||
* @brief Custom font example.
|
||||
*/
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
# Application icons
|
||||
# Application icons {#example_app_images}
|
||||
|
||||
## Source code
|
||||
|
||||
Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_images).
|
||||
|
||||
## General principle
|
||||
|
||||
To use icons, do the following:
|
||||
* add a line to the application manifest: `fap_icon_assets="folder"`, where `folder` points to the folder where your icons are located
|
||||
* add `#include "application_id_icons.h"` to the application code, where `application_id` is the appid from the manifest
|
||||
* every icon in the folder will be available as a `I_icon_name` variable, where `icon_name` is the name of the icon file without the extension
|
||||
|
||||
* Add a line to the application manifest: `fap_icon_assets="folder"`, where `folder` points to the folder where your icons are located
|
||||
* Add `#include "application_id_icons.h"` to the application code, where `application_id` is the appid from the manifest
|
||||
* Every icon in the folder will be available as a `I_icon_name` variable, where `icon_name` is the name of the icon file without the extension
|
||||
|
||||
## Example
|
||||
|
||||
We have an application with the following manifest:
|
||||
|
||||
```
|
||||
App(
|
||||
appid="example_images",
|
||||
@@ -17,6 +27,7 @@ App(
|
||||
So the icons are in the `images` folder and will be available in the generated `example_images_icons.h` file.
|
||||
|
||||
The example code is located in `example_images_main.c` and contains the following line:
|
||||
|
||||
```
|
||||
#include "example_images_icons.h"
|
||||
```
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @file example_images.c
|
||||
* @brief Custom images example.
|
||||
*/
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* An example of a plugin host application.
|
||||
/**
|
||||
* @file example_plugins.c
|
||||
* @brief Plugin host application example.
|
||||
*
|
||||
* Loads a single plugin and calls its methods.
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
* An example of an advanced plugin host application.
|
||||
/**
|
||||
* @file example_plugins_multi.c
|
||||
* @brief Advanced plugin host application example.
|
||||
*
|
||||
* It uses PluginManager to load all plugins from a directory
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
/* A simple plugin implementing example_plugins application's plugin interface */
|
||||
/**
|
||||
* @file plugin1.c
|
||||
* @brief Plugin example 1.
|
||||
*
|
||||
* A simple plugin implementing example_plugins application's plugin interface
|
||||
*/
|
||||
|
||||
#include "plugin_interface.h"
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
/* Second plugin implementing example_plugins application's plugin interface */
|
||||
/**
|
||||
* @file plugin2.c
|
||||
* @brief Plugin example 2.
|
||||
*
|
||||
* Second plugin implementing example_plugins application's plugin interface
|
||||
*/
|
||||
|
||||
#include "plugin_interface.h"
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
/**
|
||||
* @file plugin_interface.h
|
||||
* @brief Example plugin interface.
|
||||
*
|
||||
* Common interface between a plugin and host application
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/* Common interface between a plugin and host application */
|
||||
|
||||
#define PLUGIN_APP_ID "example_plugins"
|
||||
#define PLUGIN_API_VERSION 1
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
/**
|
||||
* @file app_api.h
|
||||
* @brief Application API example.
|
||||
*
|
||||
* This file contains an API that is internally implemented by the application
|
||||
* It is also exposed to plugins to allow them to use the application's API.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/*
|
||||
/**
|
||||
* @file plugin1.c
|
||||
* @brief Plugin example 1.
|
||||
*
|
||||
* This plugin uses both firmware's API interface and private application headers.
|
||||
* It can be loaded by a plugin manager that uses CompoundApiInterface,
|
||||
* which combines both interfaces.
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/*
|
||||
/**
|
||||
* @file plugin2.c
|
||||
* @brief Plugin example 2.
|
||||
*
|
||||
* This plugin uses both firmware's API interface and private application headers.
|
||||
* It can be loaded by a plugin manager that uses CompoundApiInterface,
|
||||
* which combines both interfaces.
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
/**
|
||||
* @file plugin_interface.h
|
||||
* @brief Example plugin interface.
|
||||
*
|
||||
* Common interface between a plugin and host application
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/* Common interface between a plugin and host application */
|
||||
|
||||
#define PLUGIN_APP_ID "example_plugins_advanced"
|
||||
#define PLUGIN_API_VERSION 1
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
# 1-Wire Thermometer
|
||||
# 1-Wire Thermometer {#example_thermo}
|
||||
|
||||
This example application demonstrates the use of the 1-Wire library with a DS18B20 thermometer.
|
||||
It also covers basic GUI, input handling, threads and localisation.
|
||||
|
||||
## Source code
|
||||
|
||||
Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_thermo).
|
||||
|
||||
## Electrical connections
|
||||
|
||||
Before launching the application, connect the sensor to Flipper's external GPIO according to the table below:
|
||||
| DS18B20 | Flipper |
|
||||
| :-----: | :-----: |
|
||||
@@ -15,12 +21,14 @@ Before launching the application, connect the sensor to Flipper's external GPIO
|
||||
*NOTE 2*: For any other pin than 17, connect an external 4.7k pull-up resistor to pin 9.
|
||||
|
||||
## Launching the application
|
||||
|
||||
In order to launch this demo, follow the steps below:
|
||||
1. Make sure your Flipper has an SD card installed.
|
||||
2. Connect your Flipper to the computer via a USB cable.
|
||||
3. Run `./fbt launch APPSRC=example_thermo` in your terminal emulator of choice.
|
||||
|
||||
## Changing the data pin
|
||||
|
||||
It is possible to use other GPIO pin as a 1-Wire data pin. In order to change it, set the `THERMO_GPIO_PIN` macro to any of the options listed below:
|
||||
|
||||
```c
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/*
|
||||
/**
|
||||
* @file example_thermo.c
|
||||
* @brief 1-Wire thermometer example.
|
||||
*
|
||||
* This file contains an example application that reads and displays
|
||||
* the temperature from a DS18B20 1-wire thermometer.
|
||||
*
|
||||
|
||||
Submodule applications/external updated: caf103e898...ea5d532d8c
@@ -25,6 +25,12 @@ void archive_scene_delete_on_enter(void* context) {
|
||||
filename = furi_string_alloc();
|
||||
|
||||
ArchiveFile_t* current = archive_get_current_file(app->browser);
|
||||
|
||||
FuriString* filename_no_ext = furi_string_alloc();
|
||||
path_extract_filename(current->path, filename_no_ext, true);
|
||||
strlcpy(app->text_store, furi_string_get_cstr(filename_no_ext), MAX_NAME_LEN);
|
||||
furi_string_free(filename_no_ext);
|
||||
|
||||
path_extract_filename(current->path, filename, false);
|
||||
|
||||
char delete_str[64];
|
||||
|
||||
@@ -58,18 +58,6 @@ static void momentum_app_scene_protocols_gpio_nmea_channel_changed(VariableItem*
|
||||
app->save_settings = true;
|
||||
}
|
||||
|
||||
static void momentum_app_scene_protocols_gpio_general_channel_changed(VariableItem* item) {
|
||||
MomentumApp* app = variable_item_get_context(item);
|
||||
momentum_settings.uart_general_channel = variable_item_get_current_value_index(item) == 0 ?
|
||||
FuriHalSerialIdUsart :
|
||||
FuriHalSerialIdLpuart;
|
||||
variable_item_set_current_value_text(
|
||||
item,
|
||||
momentum_settings.uart_general_channel == FuriHalSerialIdUsart ? UART_DEFAULT :
|
||||
UART_EXTRA);
|
||||
app->save_settings = true;
|
||||
}
|
||||
|
||||
void momentum_app_scene_protocols_gpio_on_enter(void* context) {
|
||||
MomentumApp* app = context;
|
||||
VariableItemList* var_item_list = app->var_item_list;
|
||||
@@ -113,18 +101,6 @@ void momentum_app_scene_protocols_gpio_on_enter(void* context) {
|
||||
item,
|
||||
momentum_settings.uart_nmea_channel == FuriHalSerialIdUsart ? UART_DEFAULT : UART_EXTRA);
|
||||
|
||||
item = variable_item_list_add(
|
||||
var_item_list,
|
||||
"General UART",
|
||||
2,
|
||||
momentum_app_scene_protocols_gpio_general_channel_changed,
|
||||
app);
|
||||
variable_item_set_current_value_index(item, momentum_settings.uart_general_channel);
|
||||
variable_item_set_current_value_text(
|
||||
item,
|
||||
momentum_settings.uart_general_channel == FuriHalSerialIdUsart ? UART_DEFAULT :
|
||||
UART_EXTRA);
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
var_item_list, momentum_app_scene_protocols_gpio_var_item_list_callback, app);
|
||||
|
||||
|
||||
1308
applications/main/nfc/api/mosgortrans/mosgortrans_util.c
Normal file
1308
applications/main/nfc/api/mosgortrans/mosgortrans_util.c
Normal file
File diff suppressed because it is too large
Load Diff
17
applications/main/nfc/api/mosgortrans/mosgortrans_util.h
Normal file
17
applications/main/nfc/api/mosgortrans/mosgortrans_util.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <bit_lib.h>
|
||||
#include <datetime.h>
|
||||
#include <furi/core/string.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "gallagher/gallagher_util.h"
|
||||
#include "mosgortrans/mosgortrans_util.h"
|
||||
|
||||
/*
|
||||
* A list of app's private functions and objects to expose for plugins.
|
||||
@@ -10,4 +11,8 @@ static constexpr auto nfc_app_api_table = sort(create_array_t<sym_entry>(
|
||||
gallagher_deobfuscate_and_parse_credential,
|
||||
void,
|
||||
(GallagherCredential * credential, const uint8_t* cardholder_data_obfuscated)),
|
||||
API_VARIABLE(GALLAGHER_CARDAX_ASCII, const uint8_t*)));
|
||||
API_VARIABLE(GALLAGHER_CARDAX_ASCII, const uint8_t*),
|
||||
API_METHOD(
|
||||
mosgortrans_parse_transport_block,
|
||||
bool,
|
||||
(const MfClassicBlock* block, FuriString* result))));
|
||||
|
||||
@@ -146,6 +146,15 @@ App(
|
||||
sources=["plugins/supported_cards/aime.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="bip_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="bip_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/bip.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="saflok_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
|
||||
368
applications/main/nfc/plugins/supported_cards/bip.c
Normal file
368
applications/main/nfc/plugins/supported_cards/bip.c
Normal file
@@ -0,0 +1,368 @@
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
|
||||
#define TAG "Bip"
|
||||
|
||||
#define SECTOR_BLOCK_OFFSET(sector, block) (((sector) * 4) + (block))
|
||||
|
||||
static const uint64_t bip_keys_a[] = {
|
||||
0x3a42f33af429,
|
||||
0x6338a371c0ed,
|
||||
0xf124c2578ad0,
|
||||
0x32ac3b90ac13,
|
||||
0x4ad1e273eaf1,
|
||||
0xe2c42591368a,
|
||||
0x2a3c347a1200,
|
||||
0x16f3d5ab1139,
|
||||
0x937a4fff3011,
|
||||
0x35c3d2caee88,
|
||||
0x693143f10368,
|
||||
0xa3f97428dd01,
|
||||
0x63f17a449af0,
|
||||
0xc4652c54261c,
|
||||
0xd49e2826664f,
|
||||
0x3df14c8000a1,
|
||||
};
|
||||
|
||||
static const uint64_t bip_keys_b[] = {
|
||||
0x1fc235ac1309,
|
||||
0x243f160918d1,
|
||||
0x9afc42372af1,
|
||||
0x682d401abb09,
|
||||
0x067db45454a9,
|
||||
0x15fc4c7613fe,
|
||||
0x68d30288910a,
|
||||
0xf59a36a2546d,
|
||||
0x64e3c10394c2,
|
||||
0xb736412614af,
|
||||
0x324f5df65310,
|
||||
0x643fb6de2217,
|
||||
0x82f435dedf01,
|
||||
0x0263de1278f3,
|
||||
0x51284c3686a6,
|
||||
0x6a470d54127c,
|
||||
};
|
||||
|
||||
bool bip_verify(Nfc* nfc) {
|
||||
bool verified = true;
|
||||
|
||||
const uint8_t verify_sector = 0;
|
||||
uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
|
||||
FURI_LOG_D(TAG, "Verifying sector %u", verify_sector);
|
||||
|
||||
MfClassicKey key_a_0 = {};
|
||||
bit_lib_num_to_bytes_be(bip_keys_a[0], COUNT_OF(key_a_0.data), key_a_0.data);
|
||||
|
||||
MfClassicAuthContext auth_ctx = {};
|
||||
MfClassicError error =
|
||||
mf_classic_poller_sync_auth(nfc, block_num, &key_a_0, MfClassicKeyTypeA, &auth_ctx);
|
||||
|
||||
if(error == MfClassicErrorNotPresent) {
|
||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
||||
verified = false;
|
||||
}
|
||||
|
||||
return verified;
|
||||
}
|
||||
|
||||
static bool bip_read(Nfc* nfc, NfcDevice* device) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(device);
|
||||
|
||||
bool is_read = false;
|
||||
|
||||
MfClassicData* data = mf_classic_alloc();
|
||||
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
do {
|
||||
MfClassicType type = MfClassicType1k;
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
if(error == MfClassicErrorNotPresent) {
|
||||
FURI_LOG_W(TAG, "Card not MIFARE Classic 1k");
|
||||
break;
|
||||
}
|
||||
|
||||
data->type = type;
|
||||
MfClassicDeviceKeys keys = {};
|
||||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||
bit_lib_num_to_bytes_be(bip_keys_a[i], sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
bit_lib_num_to_bytes_be(bip_keys_b[i], sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error == MfClassicErrorNotPresent) {
|
||||
FURI_LOG_W(TAG, "Failed to read data. Bad keys?");
|
||||
break;
|
||||
}
|
||||
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
is_read = true;
|
||||
} while(false);
|
||||
|
||||
mf_classic_free(data);
|
||||
|
||||
return is_read;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t day;
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
} BipTimestamp;
|
||||
|
||||
static void parse_bip_timestamp(const MfClassicBlock* block, BipTimestamp* timestamp) {
|
||||
furi_assert(block);
|
||||
furi_assert(timestamp);
|
||||
|
||||
timestamp->day = (((block->data[1] << 8) + block->data[0]) >> 6) & 0x1f;
|
||||
timestamp->month = (((block->data[1] << 8) + block->data[0]) >> 11) & 0xf;
|
||||
timestamp->year = 2000 + ((((block->data[2] << 8) + block->data[1]) >> 7) & 0x1f);
|
||||
timestamp->hour = (((block->data[3] << 8) + block->data[2]) >> 4) & 0x1f;
|
||||
timestamp->minute = (((block->data[3] << 8) + block->data[2]) >> 9) & 0x3f;
|
||||
timestamp->second = (((block->data[4] << 8) + block->data[3]) >> 7) & 0x3f;
|
||||
}
|
||||
|
||||
static int compare_bip_timestamp(const BipTimestamp* t1, const BipTimestamp* t2) {
|
||||
furi_assert(t1);
|
||||
furi_assert(t2);
|
||||
if(t1->year != t2->year) {
|
||||
return t1->year - t2->year;
|
||||
}
|
||||
if(t1->month != t2->month) {
|
||||
return t1->month - t2->month;
|
||||
}
|
||||
if(t1->day != t2->day) {
|
||||
return t1->day - t2->day;
|
||||
}
|
||||
if(t1->hour != t2->hour) {
|
||||
return t1->hour - t2->hour;
|
||||
}
|
||||
if(t1->minute != t2->minute) {
|
||||
return t1->minute - t2->minute;
|
||||
}
|
||||
if(t1->second != t2->second) {
|
||||
return t1->second - t2->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void print_bip_timestamp(const BipTimestamp* timestamp, FuriString* str) {
|
||||
furi_assert(timestamp);
|
||||
furi_assert(str);
|
||||
furi_string_cat_printf(
|
||||
str,
|
||||
"%04u-%02u-%02u %02u:%02u:%02u",
|
||||
timestamp->year,
|
||||
timestamp->month,
|
||||
timestamp->day,
|
||||
timestamp->hour,
|
||||
timestamp->minute,
|
||||
timestamp->second);
|
||||
}
|
||||
|
||||
static bool is_bip_block_empty(const MfClassicBlock* block) {
|
||||
furi_assert(block);
|
||||
// check if all but last byte are zero (last is checksum)
|
||||
for(size_t i = 0; i < sizeof(block->data) - 1; i++) {
|
||||
if(block->data[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void parse_uint16_le(const uint8_t* data, uint16_t* value) {
|
||||
furi_assert(data);
|
||||
furi_assert(value);
|
||||
|
||||
*value = (data[0]) | (data[1] << 8);
|
||||
}
|
||||
|
||||
static void parse_uint32_le(const uint8_t* data, uint32_t* value) {
|
||||
furi_assert(data);
|
||||
furi_assert(value);
|
||||
|
||||
*value = (data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
|
||||
}
|
||||
|
||||
static void parse_uint16_txn_amount(const uint8_t* data, uint16_t* value) {
|
||||
furi_assert(data);
|
||||
furi_assert(value);
|
||||
|
||||
parse_uint16_le(data, value);
|
||||
*value = *value >> 2;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
BipTimestamp timestamp;
|
||||
uint16_t amount;
|
||||
} BipTransaction;
|
||||
|
||||
static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
furi_assert(parsed_data);
|
||||
|
||||
bool parsed = true;
|
||||
|
||||
struct {
|
||||
uint32_t card_id;
|
||||
uint16_t balance;
|
||||
uint16_t flags;
|
||||
BipTimestamp trip_time_window;
|
||||
BipTransaction top_ups[3];
|
||||
BipTransaction charges[3];
|
||||
} bip_data = {
|
||||
.card_id = 0,
|
||||
.balance = 0,
|
||||
.flags = 0,
|
||||
.trip_time_window = {0, 0, 0, 0, 0, 0},
|
||||
.top_ups =
|
||||
{
|
||||
{{0, 0, 0, 0, 0, 0}, 0},
|
||||
{{0, 0, 0, 0, 0, 0}, 0},
|
||||
{{0, 0, 0, 0, 0, 0}, 0},
|
||||
},
|
||||
.charges =
|
||||
{
|
||||
{{0, 0, 0, 0, 0, 0}, 0},
|
||||
{{0, 0, 0, 0, 0, 0}, 0},
|
||||
{{0, 0, 0, 0, 0, 0}, 0},
|
||||
},
|
||||
};
|
||||
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
|
||||
do {
|
||||
// verify first sector keys
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0);
|
||||
uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
|
||||
if(key != bip_keys_a[0]) {
|
||||
parsed = false;
|
||||
break;
|
||||
}
|
||||
key = bit_lib_bytes_to_num_be(sec_tr->key_b.data, 6);
|
||||
if(key != bip_keys_b[0]) {
|
||||
parsed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Get Card ID, little-endian 4 bytes at sector 0 block 1, bytes 4-7
|
||||
parse_uint32_le(&data->block[SECTOR_BLOCK_OFFSET(0, 1)].data[4], &bip_data.card_id);
|
||||
|
||||
// Get balance, little-endian 2 bytes at sector 8 block 1, bytes 0-1
|
||||
parse_uint16_le(&data->block[SECTOR_BLOCK_OFFSET(8, 1)].data[0], &bip_data.balance);
|
||||
|
||||
// Get balance flags (negative balance, etc.), little-endian 2 bytes at sector 8 block 1, bytes 2-3
|
||||
parse_uint16_le(&data->block[SECTOR_BLOCK_OFFSET(8, 1)].data[2], &bip_data.flags);
|
||||
|
||||
// Get trip time window, proprietary format, at sector 5 block 1, bytes 0-7
|
||||
parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(5, 1)], &bip_data.trip_time_window);
|
||||
|
||||
// Last 3 top-ups: sector 10, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 9-10
|
||||
for(size_t i = 0; i < 3; i++) {
|
||||
if(is_bip_block_empty(&data->block[SECTOR_BLOCK_OFFSET(10, i)])) {
|
||||
continue;
|
||||
}
|
||||
BipTransaction* top_up = &bip_data.top_ups[i];
|
||||
parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(10, i)], &top_up->timestamp);
|
||||
parse_uint16_txn_amount(
|
||||
&data->block[SECTOR_BLOCK_OFFSET(10, i)].data[9], &top_up->amount);
|
||||
}
|
||||
|
||||
// Last 3 charges (i.e. trips), sector 11, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 10-11
|
||||
for(size_t i = 0; i < 3; i++) {
|
||||
if(is_bip_block_empty(&data->block[SECTOR_BLOCK_OFFSET(11, i)])) {
|
||||
continue;
|
||||
}
|
||||
BipTransaction* charge = &bip_data.charges[i];
|
||||
parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(11, i)], &charge->timestamp);
|
||||
parse_uint16_txn_amount(
|
||||
&data->block[SECTOR_BLOCK_OFFSET(11, i)].data[10], &charge->amount);
|
||||
}
|
||||
|
||||
// All data is now parsed and stored in bip_data, now print it
|
||||
|
||||
// Print basic info
|
||||
furi_string_printf(
|
||||
parsed_data,
|
||||
"\e#Tarjeta Bip!\n"
|
||||
"Card Number: %lu\n"
|
||||
"Balance: $%hu (flags %hu)\n"
|
||||
"Current Trip Window Ends:\n @",
|
||||
bip_data.card_id,
|
||||
bip_data.balance,
|
||||
bip_data.flags);
|
||||
|
||||
print_bip_timestamp(&bip_data.trip_time_window, parsed_data);
|
||||
|
||||
// Find newest top-up
|
||||
size_t newest_top_up = 0;
|
||||
for(size_t i = 1; i < 3; i++) {
|
||||
const BipTimestamp* newest = &bip_data.top_ups[newest_top_up].timestamp;
|
||||
const BipTimestamp* current = &bip_data.top_ups[i].timestamp;
|
||||
if(compare_bip_timestamp(current, newest) > 0) {
|
||||
newest_top_up = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Print top-ups, newest first
|
||||
furi_string_cat_printf(parsed_data, "\n\e#Last Top-ups");
|
||||
for(size_t i = 0; i < 3; i++) {
|
||||
const BipTransaction* top_up = &bip_data.top_ups[(3u + newest_top_up - i) % 3];
|
||||
furi_string_cat_printf(parsed_data, "\n+$%d\n @", top_up->amount);
|
||||
print_bip_timestamp(&top_up->timestamp, parsed_data);
|
||||
}
|
||||
|
||||
// Find newest charge
|
||||
size_t newest_charge = 0;
|
||||
for(size_t i = 1; i < 3; i++) {
|
||||
const BipTimestamp* newest = &bip_data.charges[newest_charge].timestamp;
|
||||
const BipTimestamp* current = &bip_data.charges[i].timestamp;
|
||||
if(compare_bip_timestamp(current, newest) > 0) {
|
||||
newest_charge = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Print charges
|
||||
furi_string_cat_printf(parsed_data, "\n\e#Last Charges (Trips)");
|
||||
for(size_t i = 0; i < 3; i++) {
|
||||
const BipTransaction* charge = &bip_data.charges[(3u + newest_charge - i) % 3];
|
||||
furi_string_cat_printf(parsed_data, "\n-$%d\n @", charge->amount);
|
||||
print_bip_timestamp(&charge->timestamp, parsed_data);
|
||||
}
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin bip_plugin = {
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = bip_verify,
|
||||
.read = bip_read,
|
||||
.parse = bip_parse,
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
static const FlipperAppPluginDescriptor bip_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &bip_plugin,
|
||||
};
|
||||
|
||||
/* Plugin entry point - must return a pointer to const descriptor */
|
||||
const FlipperAppPluginDescriptor* bip_plugin_ep() {
|
||||
return &bip_plugin_descriptor;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -55,20 +55,20 @@ static bool washcity_verify(Nfc* nfc) {
|
||||
bool verified = false;
|
||||
|
||||
do {
|
||||
const uint8_t ticket_sector_number = 0;
|
||||
const uint8_t ticket_block_number =
|
||||
mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1;
|
||||
FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number);
|
||||
const uint8_t verify_sector_number = 1;
|
||||
const uint8_t verify_block_number =
|
||||
mf_classic_get_first_block_num_of_sector(verify_sector_number);
|
||||
FURI_LOG_D(TAG, "Verifying sector %u", verify_sector_number);
|
||||
|
||||
MfClassicKey key = {0};
|
||||
bit_lib_num_to_bytes_be(
|
||||
washcity_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data);
|
||||
washcity_1k_keys[verify_sector_number].a, COUNT_OF(key.data), key.data);
|
||||
|
||||
MfClassicAuthContext auth_context;
|
||||
MfClassicError error = mf_classic_poller_sync_auth(
|
||||
nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context);
|
||||
nfc, verify_block_number, &key, MfClassicKeyTypeA, &auth_context);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error);
|
||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", verify_block_number, error);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ typedef enum {
|
||||
ExpansionStateDisabled,
|
||||
ExpansionStateEnabled,
|
||||
ExpansionStateRunning,
|
||||
ExpansionStateConnectionEstablished,
|
||||
} ExpansionState;
|
||||
|
||||
typedef enum {
|
||||
@@ -26,10 +27,15 @@ typedef enum {
|
||||
ExpansionMessageTypeSetListenSerial,
|
||||
ExpansionMessageTypeModuleConnected,
|
||||
ExpansionMessageTypeModuleDisconnected,
|
||||
ExpansionMessageTypeConnectionEstablished,
|
||||
ExpansionMessageTypeIsConnected,
|
||||
} ExpansionMessageType;
|
||||
|
||||
typedef union {
|
||||
FuriHalSerialId serial_id;
|
||||
union {
|
||||
FuriHalSerialId serial_id;
|
||||
bool* is_connected;
|
||||
};
|
||||
} ExpansionMessageData;
|
||||
|
||||
typedef struct {
|
||||
@@ -68,13 +74,21 @@ static void expansion_detect_callback(void* context) {
|
||||
UNUSED(status);
|
||||
}
|
||||
|
||||
static void expansion_worker_callback(void* context) {
|
||||
static void expansion_worker_callback(void* context, ExpansionWorkerCallbackReason reason) {
|
||||
furi_assert(context);
|
||||
Expansion* instance = context;
|
||||
|
||||
ExpansionMessage message = {
|
||||
.type = ExpansionMessageTypeModuleDisconnected,
|
||||
.api_lock = NULL, // Not locking the API here to avoid a deadlock
|
||||
ExpansionMessage message;
|
||||
switch(reason) {
|
||||
case ExpansionWorkerCallbackReasonExit:
|
||||
message.type = ExpansionMessageTypeModuleDisconnected;
|
||||
message.api_lock = NULL; // Not locking the API here to avoid a deadlock
|
||||
break;
|
||||
|
||||
case ExpansionWorkerCallbackReasonConnected:
|
||||
message.type = ExpansionMessageTypeConnectionEstablished;
|
||||
message.api_lock = api_lock_alloc_locked();
|
||||
break;
|
||||
};
|
||||
|
||||
const FuriStatus status = furi_message_queue_put(instance->queue, &message, FuriWaitForever);
|
||||
@@ -105,7 +119,9 @@ static void
|
||||
|
||||
if(instance->state == ExpansionStateDisabled) {
|
||||
return;
|
||||
} else if(instance->state == ExpansionStateRunning) {
|
||||
} else if(
|
||||
instance->state == ExpansionStateRunning ||
|
||||
instance->state == ExpansionStateConnectionEstablished) {
|
||||
expansion_worker_stop(instance->worker);
|
||||
expansion_worker_free(instance->worker);
|
||||
} else {
|
||||
@@ -122,7 +138,8 @@ static void expansion_control_handler_set_listen_serial(
|
||||
const ExpansionMessageData* data) {
|
||||
furi_check(data->serial_id < FuriHalSerialIdMax);
|
||||
|
||||
if(instance->state == ExpansionStateRunning) {
|
||||
if(instance->state == ExpansionStateRunning ||
|
||||
instance->state == ExpansionStateConnectionEstablished) {
|
||||
expansion_worker_stop(instance->worker);
|
||||
expansion_worker_free(instance->worker);
|
||||
|
||||
@@ -160,7 +177,8 @@ static void expansion_control_handler_module_disconnected(
|
||||
Expansion* instance,
|
||||
const ExpansionMessageData* data) {
|
||||
UNUSED(data);
|
||||
if(instance->state != ExpansionStateRunning) {
|
||||
if(instance->state != ExpansionStateRunning &&
|
||||
instance->state != ExpansionStateConnectionEstablished) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -170,6 +188,23 @@ static void expansion_control_handler_module_disconnected(
|
||||
instance->serial_id, expansion_detect_callback, instance);
|
||||
}
|
||||
|
||||
static void expansion_control_handler_connection_established(
|
||||
Expansion* instance,
|
||||
const ExpansionMessageData* data) {
|
||||
UNUSED(data);
|
||||
if(instance->state != ExpansionStateRunning &&
|
||||
instance->state != ExpansionStateConnectionEstablished) {
|
||||
return;
|
||||
}
|
||||
|
||||
instance->state = ExpansionStateConnectionEstablished;
|
||||
}
|
||||
|
||||
static void
|
||||
expansion_control_handler_is_connected(Expansion* instance, const ExpansionMessageData* data) {
|
||||
*data->is_connected = instance->state == ExpansionStateConnectionEstablished;
|
||||
}
|
||||
|
||||
typedef void (*ExpansionControlHandler)(Expansion*, const ExpansionMessageData*);
|
||||
|
||||
static const ExpansionControlHandler expansion_control_handlers[] = {
|
||||
@@ -178,6 +213,8 @@ static const ExpansionControlHandler expansion_control_handlers[] = {
|
||||
[ExpansionMessageTypeSetListenSerial] = expansion_control_handler_set_listen_serial,
|
||||
[ExpansionMessageTypeModuleConnected] = expansion_control_handler_module_connected,
|
||||
[ExpansionMessageTypeModuleDisconnected] = expansion_control_handler_module_disconnected,
|
||||
[ExpansionMessageTypeConnectionEstablished] = expansion_control_handler_connection_established,
|
||||
[ExpansionMessageTypeIsConnected] = expansion_control_handler_is_connected,
|
||||
};
|
||||
|
||||
static int32_t expansion_control(void* context) {
|
||||
@@ -249,6 +286,22 @@ void expansion_disable(Expansion* instance) {
|
||||
api_lock_wait_unlock_and_free(message.api_lock);
|
||||
}
|
||||
|
||||
bool expansion_is_connected(Expansion* instance) {
|
||||
furi_check(instance);
|
||||
bool is_connected;
|
||||
|
||||
ExpansionMessage message = {
|
||||
.type = ExpansionMessageTypeIsConnected,
|
||||
.data.is_connected = &is_connected,
|
||||
.api_lock = api_lock_alloc_locked(),
|
||||
};
|
||||
|
||||
furi_message_queue_put(instance->queue, &message, FuriWaitForever);
|
||||
api_lock_wait_unlock_and_free(message.api_lock);
|
||||
|
||||
return is_connected;
|
||||
}
|
||||
|
||||
void expansion_set_listen_serial(Expansion* instance, FuriHalSerialId serial_id) {
|
||||
furi_check(instance);
|
||||
furi_check(serial_id < FuriHalSerialIdMax);
|
||||
|
||||
@@ -50,6 +50,15 @@ void expansion_enable(Expansion* instance);
|
||||
*/
|
||||
void expansion_disable(Expansion* instance);
|
||||
|
||||
/**
|
||||
* @brief Check if an expansion module is connected.
|
||||
*
|
||||
* @param[in,out] instance pointer to the Expansion instance.
|
||||
*
|
||||
* @returns true if the module is connected and initialized, false otherwise.
|
||||
*/
|
||||
bool expansion_is_connected(Expansion* instance);
|
||||
|
||||
/**
|
||||
* @brief Enable support for expansion modules on designated serial port.
|
||||
*
|
||||
|
||||
@@ -223,6 +223,7 @@ static bool expansion_worker_handle_state_handshake(
|
||||
|
||||
if(furi_hal_serial_is_baud_rate_supported(instance->serial_handle, baud_rate)) {
|
||||
instance->state = ExpansionWorkerStateConnected;
|
||||
instance->callback(instance->cb_context, ExpansionWorkerCallbackReasonConnected);
|
||||
// Send response at previous baud rate
|
||||
if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break;
|
||||
furi_hal_serial_set_br(instance->serial_handle, baud_rate);
|
||||
@@ -351,7 +352,7 @@ static int32_t expansion_worker(void* context) {
|
||||
|
||||
// Do not invoke worker callback on user-requested exit
|
||||
if((instance->exit_reason != ExpansionWorkerExitReasonUser) && (instance->callback != NULL)) {
|
||||
instance->callback(instance->cb_context);
|
||||
instance->callback(instance->cb_context, ExpansionWorkerCallbackReasonExit);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -17,14 +17,20 @@
|
||||
*/
|
||||
typedef struct ExpansionWorker ExpansionWorker;
|
||||
|
||||
typedef enum {
|
||||
ExpansionWorkerCallbackReasonExit,
|
||||
ExpansionWorkerCallbackReasonConnected,
|
||||
} ExpansionWorkerCallbackReason;
|
||||
|
||||
/**
|
||||
* @brief Worker callback type.
|
||||
*
|
||||
* @see expansion_worker_set_callback()
|
||||
*
|
||||
* @param[in,out] context pointer to a user-defined object.
|
||||
* @param[in] reason reason for the callback.
|
||||
*/
|
||||
typedef void (*ExpansionWorkerCallback)(void* context);
|
||||
typedef void (*ExpansionWorkerCallback)(void* context, ExpansionWorkerCallbackReason reason);
|
||||
|
||||
/**
|
||||
* @brief Create an expansion worker instance.
|
||||
|
||||
@@ -248,7 +248,6 @@ void canvas_draw_bitmap(
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param icon Icon instance
|
||||
* @param flip IconFlip
|
||||
* @param rotation IconRotation
|
||||
*/
|
||||
void canvas_draw_icon_ex(
|
||||
|
||||
@@ -107,7 +107,7 @@ CanvasOrientation canvas_get_orientation(const Canvas* canvas);
|
||||
|
||||
/** Draw a u8g2 bitmap
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
* @param u8g2 u8g2 instance
|
||||
* @param x x coordinate
|
||||
* @param y y coordinate
|
||||
* @param width width
|
||||
|
||||
@@ -205,8 +205,7 @@ void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_
|
||||
* @param canvas Canvas instance
|
||||
* @param x left x coordinates
|
||||
* @param y top y coordinate
|
||||
* @param width bubble width
|
||||
* @param height bubble height
|
||||
* @param text text to display
|
||||
* @param horizontal horizontal aligning
|
||||
* @param vertical aligning
|
||||
*/
|
||||
|
||||
@@ -114,7 +114,6 @@ void dialog_ex_set_text(
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
* @param icon The icon
|
||||
* @param name icon to be shown
|
||||
*/
|
||||
void dialog_ex_set_icon(DialogEx* dialog_ex, uint8_t x, uint8_t y, const Icon* icon);
|
||||
|
||||
|
||||
@@ -108,7 +108,6 @@ bool scene_manager_handle_back_event(SceneManager* scene_manager);
|
||||
* Calls Scene event handler with Tick event parameter
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
* @return true if event was consumed, false otherwise
|
||||
*/
|
||||
void scene_manager_handle_tick_event(SceneManager* scene_manager);
|
||||
|
||||
|
||||
@@ -34,15 +34,15 @@ typedef enum {
|
||||
typedef struct View View;
|
||||
|
||||
/** View Draw callback
|
||||
* @param canvas, pointer to canvas
|
||||
* @param view_model, pointer to context
|
||||
* @param canvas pointer to canvas
|
||||
* @param model pointer to model
|
||||
* @warning called from GUI thread
|
||||
*/
|
||||
typedef void (*ViewDrawCallback)(Canvas* canvas, void* model);
|
||||
|
||||
/** View Input callback
|
||||
* @param event, pointer to input event data
|
||||
* @param context, pointer to context
|
||||
* @param event pointer to input event data
|
||||
* @param context pointer to context
|
||||
* @return true if event handled, false if event ignored
|
||||
* @warning called from GUI thread
|
||||
*/
|
||||
@@ -57,29 +57,29 @@ typedef bool (*ViewInputCallback)(InputEvent* event, void* context);
|
||||
typedef bool (*ViewAsciiCallback)(AsciiEvent* event, void* context);
|
||||
|
||||
/** View Custom callback
|
||||
* @param event, number of custom event
|
||||
* @param context, pointer to context
|
||||
* @param event number of custom event
|
||||
* @param context pointer to context
|
||||
* @return true if event handled, false if event ignored
|
||||
*/
|
||||
typedef bool (*ViewCustomCallback)(uint32_t event, void* context);
|
||||
|
||||
/** View navigation callback
|
||||
* @param context, pointer to context
|
||||
* @param context pointer to context
|
||||
* @return next view id
|
||||
* @warning called from GUI thread
|
||||
*/
|
||||
typedef uint32_t (*ViewNavigationCallback)(void* context);
|
||||
|
||||
/** View callback
|
||||
* @param context, pointer to context
|
||||
* @param context pointer to context
|
||||
* @warning called from GUI thread
|
||||
*/
|
||||
typedef void (*ViewCallback)(void* context);
|
||||
|
||||
/** View Update Callback Called upon model change, need to be propagated to GUI
|
||||
* throw ViewPort update
|
||||
* @param view, pointer to view
|
||||
* @param context, pointer to context
|
||||
* @param view pointer to view
|
||||
* @param context pointer to context
|
||||
* @warning called from GUI thread
|
||||
*/
|
||||
typedef void (*ViewUpdateCallback)(View* view, void* context);
|
||||
|
||||
@@ -44,7 +44,7 @@ View* view_stack_get_view(ViewStack* view_stack);
|
||||
* Adds View on top of ViewStack.
|
||||
*
|
||||
* @param view_stack instance
|
||||
* @view view view to add
|
||||
* @param view view to add
|
||||
*/
|
||||
void view_stack_add_view(ViewStack* view_stack, View* view);
|
||||
|
||||
@@ -52,7 +52,7 @@ void view_stack_add_view(ViewStack* view_stack, View* view);
|
||||
* If no View to remove found - ignore.
|
||||
*
|
||||
* @param view_stack instance
|
||||
* @view view view to remove
|
||||
* @param view view to remove
|
||||
*/
|
||||
void view_stack_remove_view(ViewStack* view_stack, View* view);
|
||||
|
||||
|
||||
@@ -368,7 +368,7 @@ FS_Error storage_common_merge(Storage* storage, const char* old_path, const char
|
||||
* @brief Create a directory.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param fs_path pointer to a zero-terminated string containing the directory path.
|
||||
* @param path pointer to a zero-terminated string containing the directory path.
|
||||
* @return FSE_OK if the directory has been successfully created, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_common_mkdir(Storage* storage, const char* path);
|
||||
@@ -395,7 +395,6 @@ FS_Error storage_common_fs_info(
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param path pointer to a zero-terminated string containing the path in question.
|
||||
* @return true if the path was successfully resolved, false otherwise.
|
||||
*/
|
||||
void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, FuriString* path);
|
||||
|
||||
@@ -555,7 +554,8 @@ FS_Error storage_int_backup(Storage* storage, const char* dstname);
|
||||
* @param converter pointer to a filename conversion function (may be NULL).
|
||||
* @return FSE_OK if the storage was successfully restored, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_int_restore(Storage* api, const char* dstname, Storage_name_converter converter);
|
||||
FS_Error
|
||||
storage_int_restore(Storage* storage, const char* dstname, Storage_name_converter converter);
|
||||
|
||||
/******************* FatFs Virtual Mount Functions *******************/
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#include "storage.h"
|
||||
#include <toolbox/tar/tar_archive.h>
|
||||
|
||||
FS_Error storage_int_backup(Storage* api, const char* dstname) {
|
||||
TarArchive* archive = tar_archive_alloc(api);
|
||||
FS_Error storage_int_backup(Storage* storage, const char* dstname) {
|
||||
TarArchive* archive = tar_archive_alloc(storage);
|
||||
bool success = tar_archive_open(archive, dstname, TAR_OPEN_MODE_WRITE) &&
|
||||
tar_archive_add_dir(archive, STORAGE_INT_PATH_PREFIX, "") &&
|
||||
tar_archive_finalize(archive);
|
||||
@@ -11,8 +11,9 @@ FS_Error storage_int_backup(Storage* api, const char* dstname) {
|
||||
return success ? FSE_OK : FSE_INTERNAL;
|
||||
}
|
||||
|
||||
FS_Error storage_int_restore(Storage* api, const char* srcname, Storage_name_converter converter) {
|
||||
TarArchive* archive = tar_archive_alloc(api);
|
||||
FS_Error
|
||||
storage_int_restore(Storage* storage, const char* srcname, Storage_name_converter converter) {
|
||||
TarArchive* archive = tar_archive_alloc(storage);
|
||||
bool success = tar_archive_open(archive, srcname, TAR_OPEN_MODE_READ) &&
|
||||
tar_archive_unpack_to(archive, STORAGE_INT_PATH_PREFIX, converter);
|
||||
tar_archive_free(archive);
|
||||
|
||||
@@ -66,7 +66,7 @@ static void render_callback(Canvas* canvas, void* _ctx) {
|
||||
if(stats->level == DOLPHIN_LEVEL_COUNT + 1) {
|
||||
xp_progress = 0;
|
||||
} else {
|
||||
xp_progress = xp_to_levelup * 64 / xp_target;
|
||||
xp_progress = (xp_target - xp_have) * 64 / xp_target;
|
||||
}
|
||||
|
||||
// multipass
|
||||
@@ -78,9 +78,9 @@ static void render_callback(Canvas* canvas, void* _ctx) {
|
||||
|
||||
const char* my_name = furi_hal_version_get_name_ptr();
|
||||
snprintf(level_str, sizeof(level_str), "Level: %hu", stats->level);
|
||||
canvas_draw_str(canvas, 58, 10, my_name ? my_name : "Unknown");
|
||||
canvas_draw_str(canvas, 58, 22, mood_str);
|
||||
canvas_draw_str(canvas, 58, 34, level_str);
|
||||
canvas_draw_str(canvas, 59, 10, my_name ? my_name : "Unknown");
|
||||
canvas_draw_str(canvas, 59, 22, mood_str);
|
||||
canvas_draw_str(canvas, 59, 34, level_str);
|
||||
|
||||
if(stats->level == DOLPHIN_LEVEL_COUNT + 1) {
|
||||
snprintf(xp_str, sizeof(xp_str), "Max Level!");
|
||||
@@ -88,7 +88,7 @@ static void render_callback(Canvas* canvas, void* _ctx) {
|
||||
snprintf(xp_str, sizeof(xp_str), "%lu/%lu", xp_have, xp_target);
|
||||
}
|
||||
canvas_set_font(canvas, FontBatteryPercent);
|
||||
canvas_draw_str(canvas, 58, 42, xp_str);
|
||||
canvas_draw_str(canvas, 59, 42, xp_str);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
|
||||
@@ -147,6 +147,7 @@ bool mass_storage_scene_create_image_on_event(void* context, SceneManagerEvent e
|
||||
}
|
||||
if(storage_virtual_format(app->fs_api) == FSE_OK) {
|
||||
success = true;
|
||||
error = NULL;
|
||||
}
|
||||
storage_virtual_quit(app->fs_api);
|
||||
} while(false);
|
||||
|
||||
Reference in New Issue
Block a user