Merge branch 'dev' of https://github.com/Next-Flip/Momentum-Firmware into expansion_connected_api
2
.github/FUNDING.yml
vendored
@@ -1,2 +1,2 @@
|
|||||||
ko_fi: willyjl
|
ko_fi: willyjl
|
||||||
custom: ["https://bunq.me/WillyJL", "https://paypal.me/willyjl1"]
|
custom: ["https://paypal.me/willyjl1"]
|
||||||
|
|||||||
BIN
.github/assets/icon.png
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
12
.github/workflow_data/release.md
vendored
@@ -8,24 +8,14 @@
|
|||||||
**Check the [install guide](https://github.com/Next-Flip/Momentum-Firmware#install) if you're not sure, or [join our Discord](https://discord.gg/momentum) if you have questions or encounter issues!**
|
**Check the [install guide](https://github.com/Next-Flip/Momentum-Firmware#install) if you're not sure, or [join our Discord](https://discord.gg/momentum) if you have questions or encounter issues!**
|
||||||
|
|
||||||
## ❤️ Support
|
## ❤️ Support
|
||||||
If you enjoy the firmware, please consider donating to the team, or sharing it with others! :D
|
If you enjoy the firmware please __**spread the word!**__ And if you really love it, maybe consider donating to the team? :D
|
||||||
|
|
||||||
> **[Ko-fi](https://ko-fi.com/willyjl)**: One-off or Recurring, No signup required
|
> **[Ko-fi](https://ko-fi.com/willyjl)**: One-off or Recurring, No signup required
|
||||||
|
|
||||||
> **[Bank transfer](https://bunq.me/WillyJL)**: One-off, No signup required
|
|
||||||
|
|
||||||
> **[PayPal](https://paypal.me/willyjl1)**: One-off, Signup required
|
> **[PayPal](https://paypal.me/willyjl1)**: One-off, Signup required
|
||||||
|
|
||||||
> **BCH**: `1EnCi1HF8Jw6m2dWSUwHLbCRbVBCQSyDKm`
|
|
||||||
|
|
||||||
> **ETH**: `0x90b8284c3eba44108427e3148ff8efa0ae7a61a8`
|
|
||||||
|
|
||||||
> **BTC**: `1EnCi1HF8Jw6m2dWSUwHLbCRbVBCQSyDKm`
|
> **BTC**: `1EnCi1HF8Jw6m2dWSUwHLbCRbVBCQSyDKm`
|
||||||
|
|
||||||
> **SHIB**: `0x90b8284c3eba44108427e3148ff8efa0ae7a61a8`
|
|
||||||
|
|
||||||
> **DOGE**: `DNUdUqtmWaAiJ6yoV6hRNEh6Nn1Tg4Aorr`
|
|
||||||
|
|
||||||
**Thank you <3**
|
**Thank you <3**
|
||||||
|
|
||||||
## 🚀 Changelog
|
## 🚀 Changelog
|
||||||
|
|||||||
22
.github/workflows/build.yml
vendored
@@ -54,14 +54,14 @@ jobs:
|
|||||||
- name: "Make tgz, zip and sdk"
|
- name: "Make tgz, zip and sdk"
|
||||||
run: bash .github/workflow_data/package.sh
|
run: bash .github/workflow_data/package.sh
|
||||||
|
|
||||||
- name: Send devbuild webhook
|
# - name: Send devbuild webhook
|
||||||
if: "github.event_name == 'push' && github.ref_name == 'dev' && !contains(github.event.head_commit.message, '--nobuild')"
|
# if: "github.event_name == 'push' && github.ref_name == 'dev' && !contains(github.event.head_commit.message, '--nobuild')"
|
||||||
env:
|
# env:
|
||||||
NC_HOST: "https://cloud.cynthialabs.net/"
|
# NC_HOST: "https://cloud.cynthialabs.net/"
|
||||||
NC_USERAGENT: "${{ secrets.NC_USERAGENT }}"
|
# NC_USERAGENT: "${{ secrets.NC_USERAGENT }}"
|
||||||
NC_USER: "${{ secrets.NC_USER }}"
|
# NC_USER: "${{ secrets.NC_USER }}"
|
||||||
NC_PASS: "${{ secrets.NC_PASS }}"
|
# NC_PASS: "${{ secrets.NC_PASS }}"
|
||||||
BUILD_WEBHOOK: ${{ secrets.BUILD_WEBHOOK }}
|
# BUILD_WEBHOOK: ${{ secrets.BUILD_WEBHOOK }}
|
||||||
run: |
|
# run: |
|
||||||
python -m pip install pyncclient
|
# python -m pip install pyncclient
|
||||||
python .github/workflow_data/devbuild.py
|
# python .github/workflow_data/devbuild.py
|
||||||
|
|||||||
16
ReadMe.md
@@ -7,7 +7,7 @@
|
|||||||
<a href="#Install">Install</a> | <a href="#list-of-changes">Features</a> | <a href="https://discord.gg/momentum">Discord</a> | <a href="#%EF%B8%8F-support">Donate</a>
|
<a href="#Install">Install</a> | <a href="#list-of-changes">Features</a> | <a href="https://discord.gg/momentum">Discord</a> | <a href="#%EF%B8%8F-support">Donate</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
This custom firmware is based on the [Official Firmware](https://github.com/flipperdevices/flipperzero-firmware), and features most of the awesome features from [Unleashed](https://github.com/DarkFlippers/unleashed-firmware). It is a direct continuation of the Xtreme firmware, built by the same (and only) developers who made that project special, until being unfairly thrown out.
|
This custom firmware is based on the [Official Firmware](https://github.com/flipperdevices/flipperzero-firmware), and includes most of the awesome features from [Unleashed](https://github.com/DarkFlippers/unleashed-firmware). It is a direct continuation of the Xtreme firmware, built by the same (and only) developers who made that project special.
|
||||||
|
|
||||||
-----
|
-----
|
||||||
<br>
|
<br>
|
||||||
@@ -83,7 +83,7 @@ In USB mode it also enables additional functionality to spoof the manufacturer a
|
|||||||
|
|
||||||
There are too many to name them all, this is a **non-comprehensive** list of the **most notable from an end-user perspective**. For a more detailed list, you can read through the [**changelogs**](https://github.com/Next-Flip/Momentum-Firmware/releases) and commits/code. Also, you can find a **feature comparison with other firmwares** on [our website](https://momentum-fw.dev/#features).
|
There are too many to name them all, this is a **non-comprehensive** list of the **most notable from an end-user perspective**. For a more detailed list, you can read through the [**changelogs**](https://github.com/Next-Flip/Momentum-Firmware/releases) and commits/code. Also, you can find a **feature comparison with other firmwares** on [our website](https://momentum-fw.dev/#features).
|
||||||
|
|
||||||
Note that this repo is always updated with the great work from our friends at [Unleashed](https://github.com/DarkFlippers/unleashed-firmware) and the latest changes from [OFW](https://github.com/flipperdevices/flipperzero-firmware). Below are mentioned only **our** changes that we can actually be credited for, so make sure to check their fantastic additions aswell. And a huge thank you to both teams!
|
Note that this repo is always updated with the great work from our friends at [Unleashed](https://github.com/DarkFlippers/unleashed-firmware) and the latest changes from [OFW](https://github.com/flipperdevices/flipperzero-firmware). Below are mentioned only **our** changes that we can actually be credited for, so make sure to check their fantastic additions aswell. And a huge thank you to both teams!
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
[Added]
|
[Added]
|
||||||
@@ -192,22 +192,12 @@ $ ./fbt launch APPSRC=your_appid
|
|||||||
|
|
||||||
|
|
||||||
## ❤️ Support
|
## ❤️ Support
|
||||||
If you enjoy the firmware, please consider donating to the team, or sharing it with others! :D
|
If you enjoy the firmware please __**spread the word!**__ And if you really love it, maybe consider donating to the team? :D
|
||||||
|
|
||||||
> **[Ko-fi](https://ko-fi.com/willyjl)**: One-off or Recurring, No signup required
|
> **[Ko-fi](https://ko-fi.com/willyjl)**: One-off or Recurring, No signup required
|
||||||
|
|
||||||
> **[Bank transfer](https://bunq.me/WillyJL)**: One-off, No signup required
|
|
||||||
|
|
||||||
> **[PayPal](https://paypal.me/willyjl1)**: One-off, Signup required
|
> **[PayPal](https://paypal.me/willyjl1)**: One-off, Signup required
|
||||||
|
|
||||||
> **BCH**: `1EnCi1HF8Jw6m2dWSUwHLbCRbVBCQSyDKm`
|
|
||||||
|
|
||||||
> **ETH**: `0x90b8284c3eba44108427e3148ff8efa0ae7a61a8`
|
|
||||||
|
|
||||||
> **BTC**: `1EnCi1HF8Jw6m2dWSUwHLbCRbVBCQSyDKm`
|
> **BTC**: `1EnCi1HF8Jw6m2dWSUwHLbCRbVBCQSyDKm`
|
||||||
|
|
||||||
> **SHIB**: `0x90b8284c3eba44108427e3148ff8efa0ae7a61a8`
|
|
||||||
|
|
||||||
> **DOGE**: `DNUdUqtmWaAiJ6yoV6hRNEh6Nn1Tg4Aorr`
|
|
||||||
|
|
||||||
**Thank you <3**
|
**Thank you <3**
|
||||||
|
|||||||
61
SConstruct
@@ -66,6 +66,7 @@ if GetOption("fullenv") or any(
|
|||||||
|
|
||||||
# Target for self-update package
|
# Target for self-update package
|
||||||
dist_basic_arguments = [
|
dist_basic_arguments = [
|
||||||
|
"${ARGS}",
|
||||||
"--bundlever",
|
"--bundlever",
|
||||||
"${UPDATE_VERSION_STRING}",
|
"${UPDATE_VERSION_STRING}",
|
||||||
]
|
]
|
||||||
@@ -182,6 +183,7 @@ fap_deploy = distenv.PhonyTarget(
|
|||||||
"send",
|
"send",
|
||||||
"${SOURCE}",
|
"${SOURCE}",
|
||||||
"/ext/apps",
|
"/ext/apps",
|
||||||
|
"${ARGS}",
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
@@ -208,7 +210,7 @@ distenv.Alias("jflash", firmware_jflash)
|
|||||||
|
|
||||||
distenv.PhonyTarget(
|
distenv.PhonyTarget(
|
||||||
"gdb_trace_all",
|
"gdb_trace_all",
|
||||||
"$GDB $GDBOPTS $SOURCES $GDBFLASH",
|
[["${GDB}", "${GDBOPTS}", "${SOURCES}", "${GDBFLASH}"]],
|
||||||
source=firmware_env["FW_ELF"],
|
source=firmware_env["FW_ELF"],
|
||||||
GDBOPTS="${GDBOPTS_BASE}",
|
GDBOPTS="${GDBOPTS_BASE}",
|
||||||
GDBREMOTE="${OPENOCD_GDB_PIPE}",
|
GDBREMOTE="${OPENOCD_GDB_PIPE}",
|
||||||
@@ -272,19 +274,35 @@ distenv.PhonyTarget(
|
|||||||
# Just start OpenOCD
|
# Just start OpenOCD
|
||||||
distenv.PhonyTarget(
|
distenv.PhonyTarget(
|
||||||
"openocd",
|
"openocd",
|
||||||
"${OPENOCDCOM}",
|
[["${OPENOCDCOM}", "${ARGS}"]],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Linter
|
# Linter
|
||||||
distenv.PhonyTarget(
|
distenv.PhonyTarget(
|
||||||
"lint",
|
"lint",
|
||||||
[["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "check", "${LINT_SOURCES}"]],
|
[
|
||||||
|
[
|
||||||
|
"${PYTHON3}",
|
||||||
|
"${FBT_SCRIPT_DIR}/lint.py",
|
||||||
|
"check",
|
||||||
|
"${LINT_SOURCES}",
|
||||||
|
"${ARGS}",
|
||||||
|
]
|
||||||
|
],
|
||||||
LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]],
|
LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]],
|
||||||
)
|
)
|
||||||
|
|
||||||
distenv.PhonyTarget(
|
distenv.PhonyTarget(
|
||||||
"format",
|
"format",
|
||||||
[["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "format", "${LINT_SOURCES}"]],
|
[
|
||||||
|
[
|
||||||
|
"${PYTHON3}",
|
||||||
|
"${FBT_SCRIPT_DIR}/lint.py",
|
||||||
|
"format",
|
||||||
|
"${LINT_SOURCES}",
|
||||||
|
"${ARGS}",
|
||||||
|
]
|
||||||
|
],
|
||||||
LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]],
|
LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -307,7 +325,16 @@ firmware_env.Append(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
black_commandline = "@${PYTHON3} -m black ${PY_BLACK_ARGS} ${PY_LINT_SOURCES}"
|
black_commandline = [
|
||||||
|
[
|
||||||
|
"@${PYTHON3}",
|
||||||
|
"-m",
|
||||||
|
"black",
|
||||||
|
"${PY_BLACK_ARGS}",
|
||||||
|
"${PY_LINT_SOURCES}",
|
||||||
|
"${ARGS}",
|
||||||
|
]
|
||||||
|
]
|
||||||
black_base_args = [
|
black_base_args = [
|
||||||
"--include",
|
"--include",
|
||||||
'"(\\.scons|\\.py|SConscript|SConstruct|\\.fam)$"',
|
'"(\\.scons|\\.py|SConscript|SConstruct|\\.fam)$"',
|
||||||
@@ -333,12 +360,28 @@ distenv.PhonyTarget(
|
|||||||
|
|
||||||
# Start Flipper CLI via PySerial's miniterm
|
# Start Flipper CLI via PySerial's miniterm
|
||||||
distenv.PhonyTarget(
|
distenv.PhonyTarget(
|
||||||
"cli", [["${PYTHON3}", "${FBT_SCRIPT_DIR}/serial_cli.py", "-p", "${FLIP_PORT}"]]
|
"cli",
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"${PYTHON3}",
|
||||||
|
"${FBT_SCRIPT_DIR}/serial_cli.py",
|
||||||
|
"-p",
|
||||||
|
"${FLIP_PORT}",
|
||||||
|
"${ARGS}",
|
||||||
|
]
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update WiFi devboard firmware
|
# Update WiFi devboard firmware with release channel
|
||||||
distenv.PhonyTarget(
|
distenv.PhonyTarget(
|
||||||
"devboard_flash", [["${PYTHON3}", "${FBT_SCRIPT_DIR}/wifi_board.py"]]
|
"devboard_flash",
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"${PYTHON3}",
|
||||||
|
"${FBT_SCRIPT_DIR}/wifi_board.py",
|
||||||
|
"${ARGS}",
|
||||||
|
]
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -353,7 +396,7 @@ distenv.PhonyTarget(
|
|||||||
distenv.PhonyTarget(
|
distenv.PhonyTarget(
|
||||||
"get_stlink",
|
"get_stlink",
|
||||||
distenv.Action(
|
distenv.Action(
|
||||||
lambda **kw: distenv.GetDevices(),
|
lambda **_: distenv.GetDevices(),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ void archive_scene_delete_on_enter(void* context) {
|
|||||||
filename = furi_string_alloc();
|
filename = furi_string_alloc();
|
||||||
|
|
||||||
ArchiveFile_t* current = archive_get_current_file(app->browser);
|
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);
|
path_extract_filename(current->path, filename, false);
|
||||||
|
|
||||||
char delete_str[64];
|
char delete_str[64];
|
||||||
|
|||||||
@@ -382,6 +382,7 @@ static void ducky_script_preload(BadKbScript* bad_kb, File* script_file) {
|
|||||||
app->has_usb_id = strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0;
|
app->has_usb_id = strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0;
|
||||||
app->has_bt_id = strncmp(line_tmp, ducky_cmd_bt_id, strlen(ducky_cmd_bt_id)) == 0;
|
app->has_bt_id = strncmp(line_tmp, ducky_cmd_bt_id, strlen(ducky_cmd_bt_id)) == 0;
|
||||||
|
|
||||||
|
// Auto-switch to mode chosen with ID/BT_ID, can override manually in config screen
|
||||||
if(app->has_usb_id) {
|
if(app->has_usb_id) {
|
||||||
app->is_bt = false;
|
app->is_bt = false;
|
||||||
app->set_usb_id = ducky_set_usb_id(bad_kb, &line_tmp[strlen(ducky_cmd_id) + 1]);
|
app->set_usb_id = ducky_set_usb_id(bad_kb, &line_tmp[strlen(ducky_cmd_id) + 1]);
|
||||||
|
|||||||
@@ -52,11 +52,6 @@ void bad_kb_scene_config_on_enter(void* context) {
|
|||||||
var_item_list, "Connection", 2, bad_kb_scene_config_connection_callback, bad_kb);
|
var_item_list, "Connection", 2, bad_kb_scene_config_connection_callback, bad_kb);
|
||||||
variable_item_set_current_value_index(item, bad_kb->is_bt);
|
variable_item_set_current_value_index(item, bad_kb->is_bt);
|
||||||
variable_item_set_current_value_text(item, bad_kb->is_bt ? "BT" : "USB");
|
variable_item_set_current_value_text(item, bad_kb->is_bt ? "BT" : "USB");
|
||||||
if(bad_kb->has_usb_id) {
|
|
||||||
variable_item_set_locked(item, true, "Script has\nID cmd!\nLocked to\nUSB Mode!");
|
|
||||||
} else if(bad_kb->has_bt_id) {
|
|
||||||
variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nBT Mode!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(bad_kb->is_bt) {
|
if(bad_kb->is_bt) {
|
||||||
item = variable_item_list_add(
|
item = variable_item_list_add(
|
||||||
@@ -65,43 +60,24 @@ void bad_kb_scene_config_on_enter(void* context) {
|
|||||||
variable_item_set_current_value_text(item, bad_kb->bt_remember ? "ON" : "OFF");
|
variable_item_set_current_value_text(item, bad_kb->bt_remember ? "ON" : "OFF");
|
||||||
|
|
||||||
item = variable_item_list_add(var_item_list, "BT Device Name", 0, NULL, bad_kb);
|
item = variable_item_list_add(var_item_list, "BT Device Name", 0, NULL, bad_kb);
|
||||||
if(bad_kb->set_bt_id) {
|
|
||||||
variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nset Name!");
|
|
||||||
}
|
|
||||||
|
|
||||||
item = variable_item_list_add(var_item_list, "BT MAC Address", 0, NULL, bad_kb);
|
item = variable_item_list_add(var_item_list, "BT MAC Address", 0, NULL, bad_kb);
|
||||||
if(bad_kb->bt_remember) {
|
if(bad_kb->bt_remember) {
|
||||||
variable_item_set_locked(item, true, "Remember\nmust be Off!");
|
variable_item_set_locked(item, true, "Remember\nmust be Off!");
|
||||||
} else if(bad_kb->set_bt_id) {
|
|
||||||
variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nset MAC!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item = variable_item_list_add(var_item_list, "Randomize BT MAC", 0, NULL, bad_kb);
|
item = variable_item_list_add(var_item_list, "Randomize BT MAC", 0, NULL, bad_kb);
|
||||||
if(bad_kb->bt_remember) {
|
if(bad_kb->bt_remember) {
|
||||||
variable_item_set_locked(item, true, "Remember\nmust be Off!");
|
variable_item_set_locked(item, true, "Remember\nmust be Off!");
|
||||||
} else if(bad_kb->set_bt_id) {
|
|
||||||
variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nset MAC!");
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
item = variable_item_list_add(var_item_list, "USB Manufacturer", 0, NULL, bad_kb);
|
item = variable_item_list_add(var_item_list, "USB Manufacturer", 0, NULL, bad_kb);
|
||||||
if(bad_kb->set_usb_id) {
|
|
||||||
variable_item_set_locked(item, true, "Script has\nID cmd!\nLocked to\nset Mname!");
|
|
||||||
}
|
|
||||||
|
|
||||||
item = variable_item_list_add(var_item_list, "USB Product Name", 0, NULL, bad_kb);
|
item = variable_item_list_add(var_item_list, "USB Product Name", 0, NULL, bad_kb);
|
||||||
if(bad_kb->set_usb_id) {
|
|
||||||
variable_item_set_locked(item, true, "Script has\nID cmd!\nLocked to\nset Pname!");
|
|
||||||
}
|
|
||||||
|
|
||||||
item = variable_item_list_add(var_item_list, "USB VID and PID", 0, NULL, bad_kb);
|
item = variable_item_list_add(var_item_list, "USB VID and PID", 0, NULL, bad_kb);
|
||||||
if(bad_kb->set_usb_id) {
|
|
||||||
variable_item_set_locked(item, true, "Script has\nID cmd!\nLocked to\nset IDs!");
|
|
||||||
}
|
|
||||||
|
|
||||||
item = variable_item_list_add(var_item_list, "Randomize USB VID:PID", 0, NULL, bad_kb);
|
item = variable_item_list_add(var_item_list, "Randomize USB VID:PID", 0, NULL, bad_kb);
|
||||||
if(bad_kb->set_usb_id) {
|
|
||||||
variable_item_set_locked(item, true, "Script has\nID cmd!\nLocked to\nset IDs!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
variable_item_list_set_enter_callback(
|
variable_item_list_set_enter_callback(
|
||||||
@@ -142,7 +118,15 @@ bool bad_kb_scene_config_on_event(void* context, SceneManagerEvent event) {
|
|||||||
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigBtMac);
|
scene_manager_next_scene(bad_kb->scene_manager, BadKbSceneConfigBtMac);
|
||||||
break;
|
break;
|
||||||
case VarItemListIndexBtRandomizeMac:
|
case VarItemListIndexBtRandomizeMac:
|
||||||
|
// Set user config and remember
|
||||||
furi_hal_random_fill_buf(bad_kb->config.ble.mac, sizeof(bad_kb->config.ble.mac));
|
furi_hal_random_fill_buf(bad_kb->config.ble.mac, sizeof(bad_kb->config.ble.mac));
|
||||||
|
// Apply to ID config so its temporarily overridden
|
||||||
|
if(bad_kb->set_bt_id) {
|
||||||
|
memcpy(
|
||||||
|
bad_kb->id_config.ble.mac,
|
||||||
|
bad_kb->config.ble.mac,
|
||||||
|
sizeof(bad_kb->id_config.ble.mac));
|
||||||
|
}
|
||||||
bad_kb_config_refresh(bad_kb);
|
bad_kb_config_refresh(bad_kb);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -166,8 +150,14 @@ bool bad_kb_scene_config_on_event(void* context, SceneManagerEvent event) {
|
|||||||
case VarItemListIndexUsbRandomizeVidPid:
|
case VarItemListIndexUsbRandomizeVidPid:
|
||||||
furi_hal_random_fill_buf(
|
furi_hal_random_fill_buf(
|
||||||
(void*)bad_kb->usb_vidpid_buf, sizeof(bad_kb->usb_vidpid_buf));
|
(void*)bad_kb->usb_vidpid_buf, sizeof(bad_kb->usb_vidpid_buf));
|
||||||
|
// Set user config and remember
|
||||||
bad_kb->config.usb.vid = bad_kb->usb_vidpid_buf[0];
|
bad_kb->config.usb.vid = bad_kb->usb_vidpid_buf[0];
|
||||||
bad_kb->config.usb.pid = bad_kb->usb_vidpid_buf[1];
|
bad_kb->config.usb.pid = bad_kb->usb_vidpid_buf[1];
|
||||||
|
// Apply to ID config so its temporarily overridden
|
||||||
|
if(bad_kb->set_usb_id) {
|
||||||
|
bad_kb->id_config.usb.vid = bad_kb->config.usb.vid;
|
||||||
|
bad_kb->id_config.usb.pid = bad_kb->config.usb.pid;
|
||||||
|
}
|
||||||
bad_kb_config_refresh(bad_kb);
|
bad_kb_config_refresh(bad_kb);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ void bad_kb_scene_config_bt_mac_on_enter(void* context) {
|
|||||||
BadKbApp* bad_kb = context;
|
BadKbApp* bad_kb = context;
|
||||||
ByteInput* byte_input = bad_kb->byte_input;
|
ByteInput* byte_input = bad_kb->byte_input;
|
||||||
|
|
||||||
memcpy(bad_kb->bt_mac_buf, bad_kb->config.ble.mac, sizeof(bad_kb->bt_mac_buf));
|
memcpy(
|
||||||
|
bad_kb->bt_mac_buf,
|
||||||
|
bad_kb->set_bt_id ? bad_kb->id_config.ble.mac : bad_kb->config.ble.mac,
|
||||||
|
sizeof(bad_kb->bt_mac_buf));
|
||||||
furi_hal_bt_reverse_mac_addr(bad_kb->bt_mac_buf);
|
furi_hal_bt_reverse_mac_addr(bad_kb->bt_mac_buf);
|
||||||
byte_input_set_header_text(byte_input, "Set BT MAC address");
|
byte_input_set_header_text(byte_input, "Set BT MAC address");
|
||||||
|
|
||||||
@@ -33,7 +36,15 @@ bool bad_kb_scene_config_bt_mac_on_event(void* context, SceneManagerEvent event)
|
|||||||
consumed = true;
|
consumed = true;
|
||||||
if(event.event == BadKbAppCustomEventByteInputDone) {
|
if(event.event == BadKbAppCustomEventByteInputDone) {
|
||||||
furi_hal_bt_reverse_mac_addr(bad_kb->bt_mac_buf);
|
furi_hal_bt_reverse_mac_addr(bad_kb->bt_mac_buf);
|
||||||
|
// Set user config and remember
|
||||||
memcpy(bad_kb->config.ble.mac, bad_kb->bt_mac_buf, sizeof(bad_kb->config.ble.mac));
|
memcpy(bad_kb->config.ble.mac, bad_kb->bt_mac_buf, sizeof(bad_kb->config.ble.mac));
|
||||||
|
// Apply to ID config so its temporarily overridden
|
||||||
|
if(bad_kb->set_bt_id) {
|
||||||
|
memcpy(
|
||||||
|
bad_kb->id_config.ble.mac,
|
||||||
|
bad_kb->bt_mac_buf,
|
||||||
|
sizeof(bad_kb->id_config.ble.mac));
|
||||||
|
}
|
||||||
bad_kb_config_refresh(bad_kb);
|
bad_kb_config_refresh(bad_kb);
|
||||||
}
|
}
|
||||||
scene_manager_previous_scene(bad_kb->scene_manager);
|
scene_manager_previous_scene(bad_kb->scene_manager);
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ void bad_kb_scene_config_bt_name_on_enter(void* context) {
|
|||||||
BadKbApp* bad_kb = context;
|
BadKbApp* bad_kb = context;
|
||||||
TextInput* text_input = bad_kb->text_input;
|
TextInput* text_input = bad_kb->text_input;
|
||||||
|
|
||||||
strlcpy(bad_kb->bt_name_buf, bad_kb->config.ble.name, sizeof(bad_kb->bt_name_buf));
|
strlcpy(
|
||||||
|
bad_kb->bt_name_buf,
|
||||||
|
bad_kb->set_bt_id ? bad_kb->id_config.ble.name : bad_kb->config.ble.name,
|
||||||
|
sizeof(bad_kb->bt_name_buf));
|
||||||
text_input_set_header_text(text_input, "Set BT device name");
|
text_input_set_header_text(text_input, "Set BT device name");
|
||||||
|
|
||||||
text_input_set_result_callback(
|
text_input_set_result_callback(
|
||||||
@@ -31,7 +34,15 @@ bool bad_kb_scene_config_bt_name_on_event(void* context, SceneManagerEvent event
|
|||||||
if(event.type == SceneManagerEventTypeCustom) {
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
consumed = true;
|
consumed = true;
|
||||||
if(event.event == BadKbAppCustomEventTextInputDone) {
|
if(event.event == BadKbAppCustomEventTextInputDone) {
|
||||||
|
// Set user config and remember
|
||||||
strlcpy(bad_kb->config.ble.name, bad_kb->bt_name_buf, sizeof(bad_kb->config.ble.name));
|
strlcpy(bad_kb->config.ble.name, bad_kb->bt_name_buf, sizeof(bad_kb->config.ble.name));
|
||||||
|
// Apply to ID config so its temporarily overridden
|
||||||
|
if(bad_kb->set_bt_id) {
|
||||||
|
strlcpy(
|
||||||
|
bad_kb->id_config.ble.name,
|
||||||
|
bad_kb->bt_name_buf,
|
||||||
|
sizeof(bad_kb->id_config.ble.name));
|
||||||
|
}
|
||||||
bad_kb_config_refresh(bad_kb);
|
bad_kb_config_refresh(bad_kb);
|
||||||
}
|
}
|
||||||
scene_manager_previous_scene(bad_kb->scene_manager);
|
scene_manager_previous_scene(bad_kb->scene_manager);
|
||||||
|
|||||||
@@ -11,10 +11,16 @@ void bad_kb_scene_config_usb_name_on_enter(void* context) {
|
|||||||
TextInput* text_input = bad_kb->text_input;
|
TextInput* text_input = bad_kb->text_input;
|
||||||
|
|
||||||
if(scene_manager_get_scene_state(bad_kb->scene_manager, BadKbSceneConfigUsbName)) {
|
if(scene_manager_get_scene_state(bad_kb->scene_manager, BadKbSceneConfigUsbName)) {
|
||||||
strlcpy(bad_kb->usb_name_buf, bad_kb->config.usb.manuf, sizeof(bad_kb->usb_name_buf));
|
strlcpy(
|
||||||
|
bad_kb->usb_name_buf,
|
||||||
|
bad_kb->set_usb_id ? bad_kb->id_config.usb.manuf : bad_kb->config.usb.manuf,
|
||||||
|
sizeof(bad_kb->usb_name_buf));
|
||||||
text_input_set_header_text(text_input, "Set USB manufacturer name");
|
text_input_set_header_text(text_input, "Set USB manufacturer name");
|
||||||
} else {
|
} else {
|
||||||
strlcpy(bad_kb->usb_name_buf, bad_kb->config.usb.product, sizeof(bad_kb->usb_name_buf));
|
strlcpy(
|
||||||
|
bad_kb->usb_name_buf,
|
||||||
|
bad_kb->set_usb_id ? bad_kb->id_config.usb.product : bad_kb->config.usb.product,
|
||||||
|
sizeof(bad_kb->usb_name_buf));
|
||||||
text_input_set_header_text(text_input, "Set USB product name");
|
text_input_set_header_text(text_input, "Set USB product name");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,15 +43,31 @@ bool bad_kb_scene_config_usb_name_on_event(void* context, SceneManagerEvent even
|
|||||||
consumed = true;
|
consumed = true;
|
||||||
if(event.event == BadKbAppCustomEventTextInputDone) {
|
if(event.event == BadKbAppCustomEventTextInputDone) {
|
||||||
if(scene_manager_get_scene_state(bad_kb->scene_manager, BadKbSceneConfigUsbName)) {
|
if(scene_manager_get_scene_state(bad_kb->scene_manager, BadKbSceneConfigUsbName)) {
|
||||||
|
// Set user config and remember
|
||||||
strlcpy(
|
strlcpy(
|
||||||
bad_kb->config.usb.manuf,
|
bad_kb->config.usb.manuf,
|
||||||
bad_kb->usb_name_buf,
|
bad_kb->usb_name_buf,
|
||||||
sizeof(bad_kb->config.usb.product));
|
sizeof(bad_kb->config.usb.manuf));
|
||||||
|
// Apply to ID config so its temporarily overridden
|
||||||
|
if(bad_kb->set_usb_id) {
|
||||||
|
strlcpy(
|
||||||
|
bad_kb->id_config.usb.manuf,
|
||||||
|
bad_kb->usb_name_buf,
|
||||||
|
sizeof(bad_kb->id_config.usb.manuf));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Set user config and remember
|
||||||
strlcpy(
|
strlcpy(
|
||||||
bad_kb->config.usb.product,
|
bad_kb->config.usb.product,
|
||||||
bad_kb->usb_name_buf,
|
bad_kb->usb_name_buf,
|
||||||
sizeof(bad_kb->config.usb.product));
|
sizeof(bad_kb->config.usb.product));
|
||||||
|
// Apply to ID config so its temporarily overridden
|
||||||
|
if(bad_kb->set_usb_id) {
|
||||||
|
strlcpy(
|
||||||
|
bad_kb->id_config.usb.product,
|
||||||
|
bad_kb->usb_name_buf,
|
||||||
|
sizeof(bad_kb->id_config.usb.product));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bad_kb_config_refresh(bad_kb);
|
bad_kb_config_refresh(bad_kb);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,13 @@ void bad_kb_scene_config_usb_vidpid_on_enter(void* context) {
|
|||||||
BadKbApp* bad_kb = context;
|
BadKbApp* bad_kb = context;
|
||||||
ByteInput* byte_input = bad_kb->byte_input;
|
ByteInput* byte_input = bad_kb->byte_input;
|
||||||
|
|
||||||
bad_kb->usb_vidpid_buf[0] = __REVSH(bad_kb->config.usb.vid);
|
if(bad_kb->set_usb_id) {
|
||||||
bad_kb->usb_vidpid_buf[1] = __REVSH(bad_kb->config.usb.pid);
|
bad_kb->usb_vidpid_buf[0] = __REVSH(bad_kb->id_config.usb.vid);
|
||||||
|
bad_kb->usb_vidpid_buf[1] = __REVSH(bad_kb->id_config.usb.pid);
|
||||||
|
} else {
|
||||||
|
bad_kb->usb_vidpid_buf[0] = __REVSH(bad_kb->config.usb.vid);
|
||||||
|
bad_kb->usb_vidpid_buf[1] = __REVSH(bad_kb->config.usb.pid);
|
||||||
|
}
|
||||||
byte_input_set_header_text(byte_input, "Set USB VID:PID");
|
byte_input_set_header_text(byte_input, "Set USB VID:PID");
|
||||||
|
|
||||||
byte_input_set_result_callback(
|
byte_input_set_result_callback(
|
||||||
@@ -32,8 +37,14 @@ bool bad_kb_scene_config_usb_vidpid_on_event(void* context, SceneManagerEvent ev
|
|||||||
if(event.type == SceneManagerEventTypeCustom) {
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
consumed = true;
|
consumed = true;
|
||||||
if(event.event == BadKbAppCustomEventByteInputDone) {
|
if(event.event == BadKbAppCustomEventByteInputDone) {
|
||||||
|
// Set user config and remember
|
||||||
bad_kb->config.usb.vid = __REVSH(bad_kb->usb_vidpid_buf[0]);
|
bad_kb->config.usb.vid = __REVSH(bad_kb->usb_vidpid_buf[0]);
|
||||||
bad_kb->config.usb.pid = __REVSH(bad_kb->usb_vidpid_buf[1]);
|
bad_kb->config.usb.pid = __REVSH(bad_kb->usb_vidpid_buf[1]);
|
||||||
|
// Apply to ID config so its temporarily overridden
|
||||||
|
if(bad_kb->set_usb_id) {
|
||||||
|
bad_kb->id_config.usb.vid = bad_kb->config.usb.vid;
|
||||||
|
bad_kb->id_config.usb.pid = bad_kb->config.usb.pid;
|
||||||
|
}
|
||||||
bad_kb_config_refresh(bad_kb);
|
bad_kb_config_refresh(bad_kb);
|
||||||
}
|
}
|
||||||
scene_manager_previous_scene(bad_kb->scene_manager);
|
scene_manager_previous_scene(bad_kb->scene_manager);
|
||||||
|
|||||||
@@ -58,18 +58,6 @@ static void momentum_app_scene_protocols_gpio_nmea_channel_changed(VariableItem*
|
|||||||
app->save_settings = true;
|
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) {
|
void momentum_app_scene_protocols_gpio_on_enter(void* context) {
|
||||||
MomentumApp* app = context;
|
MomentumApp* app = context;
|
||||||
VariableItemList* var_item_list = app->var_item_list;
|
VariableItemList* var_item_list = app->var_item_list;
|
||||||
@@ -113,18 +101,6 @@ void momentum_app_scene_protocols_gpio_on_enter(void* context) {
|
|||||||
item,
|
item,
|
||||||
momentum_settings.uart_nmea_channel == FuriHalSerialIdUsart ? UART_DEFAULT : UART_EXTRA);
|
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(
|
variable_item_list_set_enter_callback(
|
||||||
var_item_list, momentum_app_scene_protocols_gpio_var_item_list_callback, app);
|
var_item_list, momentum_app_scene_protocols_gpio_var_item_list_callback, app);
|
||||||
|
|
||||||
|
|||||||
1308
applications/main/nfc/api/mosgortrans/mosgortrans_util.c
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 "gallagher/gallagher_util.h"
|
||||||
|
#include "mosgortrans/mosgortrans_util.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A list of app's private functions and objects to expose for plugins.
|
* 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,
|
gallagher_deobfuscate_and_parse_credential,
|
||||||
void,
|
void,
|
||||||
(GallagherCredential * credential, const uint8_t* cardholder_data_obfuscated)),
|
(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"],
|
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(
|
App(
|
||||||
appid="saflok_parser",
|
appid="saflok_parser",
|
||||||
apptype=FlipperAppType.PLUGIN,
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
@@ -55,20 +55,20 @@ static bool washcity_verify(Nfc* nfc) {
|
|||||||
bool verified = false;
|
bool verified = false;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
const uint8_t ticket_sector_number = 0;
|
const uint8_t verify_sector_number = 1;
|
||||||
const uint8_t ticket_block_number =
|
const uint8_t verify_block_number =
|
||||||
mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1;
|
mf_classic_get_first_block_num_of_sector(verify_sector_number);
|
||||||
FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number);
|
FURI_LOG_D(TAG, "Verifying sector %u", verify_sector_number);
|
||||||
|
|
||||||
MfClassicKey key = {0};
|
MfClassicKey key = {0};
|
||||||
bit_lib_num_to_bytes_be(
|
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;
|
MfClassicAuthContext auth_context;
|
||||||
MfClassicError error = mf_classic_poller_sync_auth(
|
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) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4389,4 +4389,4 @@ EB9D9C1B03F6
|
|||||||
5A4920FD6F87
|
5A4920FD6F87
|
||||||
544954CBB2C4
|
544954CBB2C4
|
||||||
4752533E1965
|
4752533E1965
|
||||||
17C06D19E92F
|
17C06D19E92F
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
const uint32_t DOLPHIN_LEVELS[] = {100, 200, 300, 450, 600, 750, 950, 1150, 1350, 1600,
|
const uint32_t DOLPHIN_LEVELS[] = {100, 200, 300, 450, 600, 750, 950, 1150, 1350, 1600,
|
||||||
1850, 2100, 2400, 2700, 3000, 3350, 3700, 4050, 4450, 4850,
|
1850, 2100, 2400, 2700, 3000, 3350, 3700, 4050, 4450, 4850,
|
||||||
5250, 5700, 6150, 6600, 7100, 7600, 8100, 8650, 9200};
|
5250, 5700, 6150, 6600, 7100, 7600, 8100, 8650, 9999};
|
||||||
const size_t DOLPHIN_LEVEL_COUNT = COUNT_OF(DOLPHIN_LEVELS);
|
const size_t DOLPHIN_LEVEL_COUNT = COUNT_OF(DOLPHIN_LEVELS);
|
||||||
|
|
||||||
DolphinState* dolphin_state_alloc() {
|
DolphinState* dolphin_state_alloc() {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ static void render_callback(Canvas* canvas, void* _ctx) {
|
|||||||
PassportContext* ctx = _ctx;
|
PassportContext* ctx = _ctx;
|
||||||
DolphinStats* stats = ctx->stats;
|
DolphinStats* stats = ctx->stats;
|
||||||
|
|
||||||
char level_str[20];
|
char level_str[12];
|
||||||
char xp_str[12];
|
char xp_str[12];
|
||||||
const char* mood_str = NULL;
|
const char* mood_str = NULL;
|
||||||
const Icon* portrait = NULL;
|
const Icon* portrait = NULL;
|
||||||
@@ -48,54 +48,52 @@ static void render_callback(Canvas* canvas, void* _ctx) {
|
|||||||
portrait = &I_passport_bad_46x49;
|
portrait = &I_passport_bad_46x49;
|
||||||
mood_str = "Mood: Angry";
|
mood_str = "Mood: Angry";
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t xp_progress = 0;
|
uint32_t xp_progress = 0;
|
||||||
uint32_t xp_need = dolphin_state_xp_to_levelup(stats->icounter);
|
uint32_t xp_to_levelup = dolphin_state_xp_to_levelup(stats->icounter);
|
||||||
uint32_t xp_above_last_levelup = dolphin_state_xp_above_last_levelup(stats->icounter);
|
uint32_t xp_above_last_levelup = dolphin_state_xp_above_last_levelup(stats->icounter);
|
||||||
uint32_t xp_levelup = 0;
|
|
||||||
|
uint32_t xp_have = 0;
|
||||||
|
uint32_t xp_target = 0;
|
||||||
if(ctx->progress_total) {
|
if(ctx->progress_total) {
|
||||||
xp_levelup = xp_need + stats->icounter;
|
xp_have = stats->icounter;
|
||||||
|
xp_target = DOLPHIN_LEVELS[DOLPHIN_LEVEL_COUNT - 1];
|
||||||
} else {
|
} else {
|
||||||
xp_levelup = xp_need + xp_above_last_levelup;
|
xp_have = xp_above_last_levelup;
|
||||||
|
xp_target = xp_to_levelup + xp_above_last_levelup;
|
||||||
}
|
}
|
||||||
uint32_t xp_have = xp_levelup - xp_need;
|
|
||||||
|
|
||||||
if(stats->level == DOLPHIN_LEVEL_COUNT + 1) {
|
if(stats->level == DOLPHIN_LEVEL_COUNT + 1) {
|
||||||
xp_progress = 0;
|
xp_progress = 0;
|
||||||
} else {
|
} else {
|
||||||
xp_progress = xp_need * 64 / xp_levelup;
|
xp_progress = (xp_target - xp_have) * 64 / xp_target;
|
||||||
}
|
}
|
||||||
|
|
||||||
// multipass
|
// multipass
|
||||||
canvas_draw_icon(canvas, 0, 0, &I_passport_DB);
|
canvas_draw_icon(canvas, 0, 0, &I_passport_128x64);
|
||||||
|
|
||||||
// portrait
|
// portrait
|
||||||
furi_assert((stats->level > 0) && (stats->level <= DOLPHIN_LEVEL_COUNT + 1));
|
furi_assert((stats->level > 0) && (stats->level <= DOLPHIN_LEVEL_COUNT + 1));
|
||||||
canvas_draw_icon(canvas, 11, 2, portrait);
|
canvas_draw_icon(canvas, 11, 2, portrait);
|
||||||
|
|
||||||
const char* my_name = furi_hal_version_get_name_ptr();
|
const char* my_name = furi_hal_version_get_name_ptr();
|
||||||
snprintf(level_str, 12, "Level: %hu", stats->level);
|
snprintf(level_str, sizeof(level_str), "Level: %hu", stats->level);
|
||||||
if(stats->level == DOLPHIN_LEVEL_COUNT + 1) {
|
canvas_draw_str(canvas, 59, 10, my_name ? my_name : "Unknown");
|
||||||
snprintf(xp_str, 12, "Max Level!");
|
canvas_draw_str(canvas, 59, 22, mood_str);
|
||||||
} else {
|
canvas_draw_str(canvas, 59, 34, level_str);
|
||||||
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);
|
|
||||||
canvas_set_color(canvas, ColorBlack);
|
|
||||||
|
|
||||||
canvas_draw_str(canvas, 58, 34, level_str);
|
if(stats->level == DOLPHIN_LEVEL_COUNT + 1) {
|
||||||
|
snprintf(xp_str, sizeof(xp_str), "Max Level!");
|
||||||
|
} else {
|
||||||
|
snprintf(xp_str, sizeof(xp_str), "%lu/%lu", xp_have, xp_target);
|
||||||
|
}
|
||||||
canvas_set_font(canvas, FontBatteryPercent);
|
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_font(canvas, FontSecondary);
|
||||||
|
|
||||||
canvas_set_color(canvas, ColorWhite);
|
canvas_set_color(canvas, ColorWhite);
|
||||||
canvas_draw_box(canvas, 123 - xp_progress, 45, xp_progress + (xp_progress > 0), 5);
|
canvas_draw_box(canvas, 123 - xp_progress, 45, xp_progress + (xp_progress > 0), 5);
|
||||||
canvas_set_color(canvas, ColorBlack);
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
canvas_draw_icon(canvas, 52, 51, &I_Ok_btn_9x9);
|
|
||||||
canvas_draw_str(
|
|
||||||
canvas, ctx->progress_total ? 37 : 36, 59, ctx->progress_total ? "Lvl" : "Tot");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t passport_app(void* p) {
|
int32_t passport_app(void* p) {
|
||||||
|
|||||||
@@ -83,16 +83,16 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(data->alt) {
|
if(data->alt) {
|
||||||
if(!strcmp(value, "")) {
|
if(!value[0]) {
|
||||||
canvas_draw_str_aligned(canvas, x + 92, y + 14, AlignCenter, AlignCenter, header);
|
canvas_draw_str_aligned(canvas, x + 92, y + 14, AlignCenter, AlignCenter, header);
|
||||||
} else if(!strcmp(header, "")) {
|
} else if(!header[0]) {
|
||||||
canvas_draw_str_aligned(canvas, x + 92, y + 14, AlignCenter, AlignCenter, value);
|
canvas_draw_str_aligned(canvas, x + 92, y + 14, AlignCenter, AlignCenter, value);
|
||||||
} else {
|
} else {
|
||||||
canvas_draw_str_aligned(canvas, x + 92, y + 9, AlignCenter, AlignCenter, header);
|
canvas_draw_str_aligned(canvas, x + 92, y + 9, AlignCenter, AlignCenter, header);
|
||||||
canvas_draw_str_aligned(canvas, x + 92, y + 19, AlignCenter, AlignCenter, value);
|
canvas_draw_str_aligned(canvas, x + 92, y + 19, AlignCenter, AlignCenter, value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(!strcmp(emote, "")) {
|
if(!emote[0] && header[0] && value[0]) {
|
||||||
canvas_draw_str_aligned(canvas, x + 92, y + 9, AlignCenter, AlignCenter, header);
|
canvas_draw_str_aligned(canvas, x + 92, y + 9, AlignCenter, AlignCenter, header);
|
||||||
canvas_draw_str_aligned(canvas, x + 92, y + 21, AlignCenter, AlignCenter, value);
|
canvas_draw_str_aligned(canvas, x + 92, y + 21, AlignCenter, AlignCenter, value);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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) {
|
if(storage_virtual_format(app->fs_api) == FSE_OK) {
|
||||||
success = true;
|
success = true;
|
||||||
|
error = NULL;
|
||||||
}
|
}
|
||||||
storage_virtual_quit(app->fs_api);
|
storage_virtual_quit(app->fs_api);
|
||||||
} while(false);
|
} while(false);
|
||||||
|
|||||||
BIN
assets/icons/Passport/passport_128x64.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 750 B |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,14 @@
|
|||||||
|
Filetype: Flipper Animation
|
||||||
|
Version: 1
|
||||||
|
|
||||||
|
Width: 128
|
||||||
|
Height: 64
|
||||||
|
Passive frames: 16
|
||||||
|
Active frames: 83
|
||||||
|
Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 8 8 8 8 8 8 16 17 18 19 20 21 22 23 24 25 25 26 27 28 29 20 30 31 32 33 8 8 16 17 18 19 20 21 22 23 24 25 25 26 27 28 29 20 30 31 32 33 8 8 16 17 18 19 20 21 22 23 24 25 25 25 25 25 25 34 35 36 37 38 39 40 41 42 42
|
||||||
|
Active cycles: 1
|
||||||
|
Frame rate: 6
|
||||||
|
Duration: 3600
|
||||||
|
Active cooldown: 3
|
||||||
|
|
||||||
|
Bubble slots: 0
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.2 KiB |