[FL-2627] Flipper applications: SDK, build and debug system (#1387)

* Added support for running applications from SD card (FAPs - Flipper Application Packages)
* Added plugin_dist target for fbt to build FAPs
* All apps of type FlipperAppType.EXTERNAL and FlipperAppType.PLUGIN are built as FAPs by default
* Updated VSCode configuration for new fbt features - re-deploy stock configuration to use them
* Added debugging support for FAPs with fbt debug & VSCode
* Added public firmware API with automated versioning

Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: SG <who.just.the.doctor@gmail.com>
Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
SG
2022-09-15 02:11:38 +10:00
committed by Aleksandr Kutuzov
parent 0f6f9ad52e
commit b9a766d909
895 changed files with 8862 additions and 1465 deletions
+10
View File
@@ -0,0 +1,10 @@
App(
appid="basic_plugins",
name="Basic applications for plug-in menu",
apptype=FlipperAppType.METAPACKAGE,
provides=[
"music_player",
"snake_game",
"bt_hid",
],
)
@@ -0,0 +1,15 @@
App(
appid="bt_hid",
name="Bluetooth Remote",
apptype=FlipperAppType.PLUGIN,
entry_point="bt_hid_app",
stack_size=1 * 1024,
cdefines=["APP_BLE_HID"],
requires=[
"bt",
"gui",
],
order=10,
fap_icon="bt_remote_10px.png",
fap_category="Tools",
)
+197
View File
@@ -0,0 +1,197 @@
#include "bt_hid.h"
#include <furi_hal_bt.h>
#include <notification/notification_messages.h>
#define TAG "BtHidApp"
enum BtDebugSubmenuIndex {
BtHidSubmenuIndexKeynote,
BtHidSubmenuIndexKeyboard,
BtHidSubmenuIndexMedia,
BtHidSubmenuIndexMouse,
};
void bt_hid_submenu_callback(void* context, uint32_t index) {
furi_assert(context);
BtHid* app = context;
if(index == BtHidSubmenuIndexKeynote) {
app->view_id = BtHidViewKeynote;
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeynote);
} else if(index == BtHidSubmenuIndexKeyboard) {
app->view_id = BtHidViewKeyboard;
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewKeyboard);
} else if(index == BtHidSubmenuIndexMedia) {
app->view_id = BtHidViewMedia;
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMedia);
} else if(index == BtHidSubmenuIndexMouse) {
app->view_id = BtHidViewMouse;
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewMouse);
}
}
void bt_hid_dialog_callback(DialogExResult result, void* context) {
furi_assert(context);
BtHid* app = context;
if(result == DialogExResultLeft) {
view_dispatcher_stop(app->view_dispatcher);
} else if(result == DialogExResultRight) {
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view
} else if(result == DialogExResultCenter) {
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewSubmenu);
}
}
uint32_t bt_hid_exit_confirm_view(void* context) {
UNUSED(context);
return BtHidViewExitConfirm;
}
uint32_t bt_hid_exit(void* context) {
UNUSED(context);
return VIEW_NONE;
}
void bt_hid_connection_status_changed_callback(BtStatus status, void* context) {
furi_assert(context);
BtHid* bt_hid = context;
bool connected = (status == BtStatusConnected);
if(connected) {
notification_internal_message(bt_hid->notifications, &sequence_set_blue_255);
} else {
notification_internal_message(bt_hid->notifications, &sequence_reset_blue);
}
bt_hid_keynote_set_connected_status(bt_hid->bt_hid_keynote, connected);
bt_hid_keyboard_set_connected_status(bt_hid->bt_hid_keyboard, connected);
bt_hid_media_set_connected_status(bt_hid->bt_hid_media, connected);
bt_hid_mouse_set_connected_status(bt_hid->bt_hid_mouse, connected);
}
BtHid* bt_hid_app_alloc() {
BtHid* app = malloc(sizeof(BtHid));
// Gui
app->gui = furi_record_open(RECORD_GUI);
// Bt
app->bt = furi_record_open(RECORD_BT);
// Notifications
app->notifications = furi_record_open(RECORD_NOTIFICATION);
// View dispatcher
app->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
// Submenu view
app->submenu = submenu_alloc();
submenu_add_item(
app->submenu, "Keynote", BtHidSubmenuIndexKeynote, bt_hid_submenu_callback, app);
submenu_add_item(
app->submenu, "Keyboard", BtHidSubmenuIndexKeyboard, bt_hid_submenu_callback, app);
submenu_add_item(app->submenu, "Media", BtHidSubmenuIndexMedia, bt_hid_submenu_callback, app);
submenu_add_item(app->submenu, "Mouse", BtHidSubmenuIndexMouse, bt_hid_submenu_callback, app);
view_set_previous_callback(submenu_get_view(app->submenu), bt_hid_exit);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewSubmenu, submenu_get_view(app->submenu));
// Dialog view
app->dialog = dialog_ex_alloc();
dialog_ex_set_result_callback(app->dialog, bt_hid_dialog_callback);
dialog_ex_set_context(app->dialog, app);
dialog_ex_set_left_button_text(app->dialog, "Exit");
dialog_ex_set_right_button_text(app->dialog, "Stay");
dialog_ex_set_center_button_text(app->dialog, "Menu");
dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewExitConfirm, dialog_ex_get_view(app->dialog));
// Keynote view
app->bt_hid_keynote = bt_hid_keynote_alloc();
view_set_previous_callback(
bt_hid_keynote_get_view(app->bt_hid_keynote), bt_hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewKeynote, bt_hid_keynote_get_view(app->bt_hid_keynote));
// Keyboard view
app->bt_hid_keyboard = bt_hid_keyboard_alloc();
view_set_previous_callback(
bt_hid_keyboard_get_view(app->bt_hid_keyboard), bt_hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewKeyboard, bt_hid_keyboard_get_view(app->bt_hid_keyboard));
// Media view
app->bt_hid_media = bt_hid_media_alloc();
view_set_previous_callback(bt_hid_media_get_view(app->bt_hid_media), bt_hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewMedia, bt_hid_media_get_view(app->bt_hid_media));
// Mouse view
app->bt_hid_mouse = bt_hid_mouse_alloc();
view_set_previous_callback(bt_hid_mouse_get_view(app->bt_hid_mouse), bt_hid_exit_confirm_view);
view_dispatcher_add_view(
app->view_dispatcher, BtHidViewMouse, bt_hid_mouse_get_view(app->bt_hid_mouse));
// TODO switch to menu after Media is done
app->view_id = BtHidViewSubmenu;
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id);
return app;
}
void bt_hid_app_free(BtHid* app) {
furi_assert(app);
// Reset notification
notification_internal_message(app->notifications, &sequence_reset_blue);
// Free views
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewSubmenu);
submenu_free(app->submenu);
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewExitConfirm);
dialog_ex_free(app->dialog);
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeynote);
bt_hid_keynote_free(app->bt_hid_keynote);
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewKeyboard);
bt_hid_keyboard_free(app->bt_hid_keyboard);
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMedia);
bt_hid_media_free(app->bt_hid_media);
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewMouse);
bt_hid_mouse_free(app->bt_hid_mouse);
view_dispatcher_free(app->view_dispatcher);
// Close records
furi_record_close(RECORD_GUI);
app->gui = NULL;
furi_record_close(RECORD_NOTIFICATION);
app->notifications = NULL;
furi_record_close(RECORD_BT);
app->bt = NULL;
// Free rest
free(app);
}
int32_t bt_hid_app(void* p) {
UNUSED(p);
// Switch profile to Hid
BtHid* app = bt_hid_app_alloc();
bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app);
// Change profile
if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) {
FURI_LOG_E(TAG, "Failed to switch profile");
bt_hid_app_free(app);
return -1;
}
furi_hal_bt_start_advertising();
view_dispatcher_run(app->view_dispatcher);
bt_set_status_changed_callback(app->bt, NULL, NULL);
// Change back profile to Serial
bt_set_profile(app->bt, BtProfileSerial);
bt_hid_app_free(app);
return 0;
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <furi.h>
#include <bt/bt_service/bt.h>
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <notification/notification.h>
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
#include "views/bt_hid_keynote.h"
#include "views/bt_hid_keyboard.h"
#include "views/bt_hid_media.h"
#include "views/bt_hid_mouse.h"
typedef struct {
Bt* bt;
Gui* gui;
NotificationApp* notifications;
ViewDispatcher* view_dispatcher;
Submenu* submenu;
DialogEx* dialog;
BtHidKeynote* bt_hid_keynote;
BtHidKeyboard* bt_hid_keyboard;
BtHidMedia* bt_hid_media;
BtHidMouse* bt_hid_mouse;
uint32_t view_id;
} BtHid;
typedef enum {
BtHidViewSubmenu,
BtHidViewKeynote,
BtHidViewKeyboard,
BtHidViewMedia,
BtHidViewMouse,
BtHidViewExitConfirm,
} BtHidView;
Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

@@ -0,0 +1,384 @@
#include "bt_hid_keyboard.h"
#include <furi.h>
#include <furi_hal_bt_hid.h>
#include <furi_hal_usb_hid.h>
#include <gui/elements.h>
#include <gui/icon_i.h>
struct BtHidKeyboard {
View* view;
};
typedef struct {
bool shift;
bool alt;
bool ctrl;
bool gui;
uint8_t x;
uint8_t y;
uint8_t last_key_code;
uint16_t modifier_code;
bool ok_pressed;
bool back_pressed;
bool connected;
char key_string[5];
} BtHidKeyboardModel;
typedef struct {
uint8_t width;
char* key;
const Icon* icon;
char* shift_key;
uint8_t value;
} BtHidKeyboardKey;
typedef struct {
int8_t x;
int8_t y;
} BtHidKeyboardPoint;
// 4 BY 12
#define MARGIN_TOP 0
#define MARGIN_LEFT 4
#define KEY_WIDTH 9
#define KEY_HEIGHT 12
#define KEY_PADDING 1
#define ROW_COUNT 6
#define COLUMN_COUNT 12
// 0 width items are not drawn, but there value is used
const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = {
{
{.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1},
{.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2},
{.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3},
{.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4},
{.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5},
{.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6},
{.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7},
{.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8},
{.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9},
{.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0},
{.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE},
{.width = 0, .value = HID_KEYBOARD_DELETE},
},
{
{.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q},
{.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W},
{.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E},
{.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R},
{.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T},
{.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y},
{.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U},
{.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I},
{.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O},
{.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P},
{.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET},
{.width = 1,
.icon = NULL,
.key = "]",
.shift_key = "}",
.value = HID_KEYBOARD_CLOSE_BRACKET},
},
{
{.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A},
{.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S},
{.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D},
{.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F},
{.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G},
{.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H},
{.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J},
{.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K},
{.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L},
{.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON},
{.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN},
{.width = 0, .value = HID_KEYBOARD_RETURN},
},
{
{.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z},
{.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X},
{.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C},
{.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V},
{.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B},
{.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N},
{.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M},
{.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH},
{.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH},
{.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT},
{.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW},
{.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS},
},
{
{.width = 1, .icon = &I_Pin_arrow_up7x9, .value = HID_KEYBOARD_L_SHIFT},
{.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYPAD_COMMA},
{.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT},
{.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR},
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
{.width = 0, .value = HID_KEYBOARD_SPACEBAR},
{.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE},
{.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN},
{.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW},
{.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW},
{.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW},
},
{
{.width = 3, .icon = NULL, .key = "Ctrl", .value = HID_KEYBOARD_L_CTRL},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_CTRL},
{.width = 3, .icon = NULL, .key = "Alt", .value = HID_KEYBOARD_L_ALT},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_ALT},
{.width = 3, .icon = NULL, .key = "Cmd", .value = HID_KEYBOARD_L_GUI},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_L_GUI},
{.width = 3, .icon = NULL, .key = "Tab", .value = HID_KEYBOARD_TAB},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
{.width = 0, .icon = NULL, .value = HID_KEYBOARD_TAB},
},
};
static void bt_hid_keyboard_to_upper(char* str) {
while(*str) {
*str = toupper((unsigned char)*str);
str++;
}
}
static void bt_hid_keyboard_draw_key(
Canvas* canvas,
BtHidKeyboardModel* model,
uint8_t x,
uint8_t y,
BtHidKeyboardKey key,
bool selected) {
if(!key.width) return;
canvas_set_color(canvas, ColorBlack);
uint8_t keyWidth = KEY_WIDTH * key.width + KEY_PADDING * (key.width - 1);
if(selected) {
// Draw a filled box
elements_slightly_rounded_box(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
keyWidth,
KEY_HEIGHT);
canvas_set_color(canvas, ColorWhite);
} else {
// Draw a framed box
elements_slightly_rounded_frame(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING),
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING),
keyWidth,
KEY_HEIGHT);
}
if(key.icon != NULL) {
// Draw the icon centered on the button
canvas_draw_icon(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2,
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2,
key.icon);
} else {
// If shift is toggled use the shift key when available
strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key);
// Upper case if ctrl or alt was toggled true
if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) ||
(model->alt && key.value == HID_KEYBOARD_L_ALT) ||
(model->gui && key.value == HID_KEYBOARD_L_GUI)) {
bt_hid_keyboard_to_upper(model->key_string);
}
canvas_draw_str_aligned(
canvas,
MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1,
MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2,
AlignCenter,
AlignCenter,
model->key_string);
}
}
static void bt_hid_keyboard_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
BtHidKeyboardModel* model = context;
// Header
if(!model->connected) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keyboard");
elements_multiline_text_aligned(
canvas, 4, 60, AlignLeft, AlignBottom, "Waiting for Connection...");
return; // Dont render the keyboard if we are not yet connected
}
canvas_set_font(canvas, FontKeyboard);
// Start shifting the all keys up if on the next row (Scrolling)
uint8_t initY = model->y - 4 > 0 ? model->y - 4 : 0;
for(uint8_t y = initY; y < ROW_COUNT; y++) {
const BtHidKeyboardKey* keyboardKeyRow = bt_hid_keyboard_keyset[y];
uint8_t x = 0;
for(uint8_t i = 0; i < COLUMN_COUNT; i++) {
BtHidKeyboardKey key = keyboardKeyRow[i];
// Select when the button is hovered
// Select if the button is hovered within its width
// Select if back is clicked and its the backspace key
// Deselect when the button clicked or not hovered
bool keySelected = (x <= model->x && model->x < (x + key.width)) && y == model->y;
bool backSelected = model->back_pressed && key.value == HID_KEYBOARD_DELETE;
bt_hid_keyboard_draw_key(
canvas,
model,
x,
y - initY,
key,
(!model->ok_pressed && keySelected) || backSelected);
x += key.width;
}
}
}
static uint8_t bt_hid_keyboard_get_selected_key(BtHidKeyboardModel* model) {
BtHidKeyboardKey key = bt_hid_keyboard_keyset[model->y][model->x];
// Use upper case if shift is toggled
bool useUppercase = model->shift;
// Check if the key has an upper case version
bool hasUppercase = key.shift_key != 0;
if(useUppercase && hasUppercase)
return key.value;
else
return key.value;
}
static void bt_hid_keyboard_get_select_key(BtHidKeyboardModel* model, BtHidKeyboardPoint delta) {
// Keep going until a valid spot is found, this allows for nulls and zero width keys in the map
do {
if(((int8_t)model->y) + delta.y < 0)
model->y = ROW_COUNT - 1;
else
model->y = (model->y + delta.y) % ROW_COUNT;
} while(delta.y != 0 && bt_hid_keyboard_keyset[model->y][model->x].value == 0);
do {
if(((int8_t)model->x) + delta.x < 0)
model->x = COLUMN_COUNT - 1;
else
model->x = (model->x + delta.x) % COLUMN_COUNT;
} while(delta.x != 0 && bt_hid_keyboard_keyset[model->y][model->x].width ==
0); // Skip zero width keys, pretend they are one key
}
static void bt_hid_keyboard_process(BtHidKeyboard* bt_hid_keyboard, InputEvent* event) {
with_view_model(
bt_hid_keyboard->view, (BtHidKeyboardModel * model) {
if(event->key == InputKeyOk) {
if(event->type == InputTypePress) {
model->ok_pressed = true;
} else if(event->type == InputTypeLong || event->type == InputTypeShort) {
model->last_key_code = bt_hid_keyboard_get_selected_key(model);
// Toggle the modifier key when clicked, and click the key
if(model->last_key_code == HID_KEYBOARD_L_SHIFT) {
model->shift = !model->shift;
if(model->shift)
model->modifier_code |= KEY_MOD_LEFT_SHIFT;
else
model->modifier_code &= ~KEY_MOD_LEFT_SHIFT;
} else if(model->last_key_code == HID_KEYBOARD_L_ALT) {
model->alt = !model->alt;
if(model->alt)
model->modifier_code |= KEY_MOD_LEFT_ALT;
else
model->modifier_code &= ~KEY_MOD_LEFT_ALT;
} else if(model->last_key_code == HID_KEYBOARD_L_CTRL) {
model->ctrl = !model->ctrl;
if(model->ctrl)
model->modifier_code |= KEY_MOD_LEFT_CTRL;
else
model->modifier_code &= ~KEY_MOD_LEFT_CTRL;
} else if(model->last_key_code == HID_KEYBOARD_L_GUI) {
model->gui = !model->gui;
if(model->gui)
model->modifier_code |= KEY_MOD_LEFT_GUI;
else
model->modifier_code &= ~KEY_MOD_LEFT_GUI;
}
furi_hal_bt_hid_kb_press(model->modifier_code | model->last_key_code);
} else if(event->type == InputTypeRelease) {
// Release happens after short and long presses
furi_hal_bt_hid_kb_release(model->modifier_code | model->last_key_code);
model->ok_pressed = false;
}
} else if(event->key == InputKeyBack) {
// If back is pressed for a short time, backspace
if(event->type == InputTypePress) {
model->back_pressed = true;
} else if(event->type == InputTypeShort) {
furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE);
furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE);
} else if(event->type == InputTypeRelease) {
model->back_pressed = false;
}
} else if(event->type == InputTypePress || event->type == InputTypeRepeat) {
// Cycle the selected keys
if(event->key == InputKeyUp) {
bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = -1});
} else if(event->key == InputKeyDown) {
bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 0, .y = 1});
} else if(event->key == InputKeyLeft) {
bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = -1, .y = 0});
} else if(event->key == InputKeyRight) {
bt_hid_keyboard_get_select_key(model, (BtHidKeyboardPoint){.x = 1, .y = 0});
}
}
return true;
});
}
static bool bt_hid_keyboard_input_callback(InputEvent* event, void* context) {
furi_assert(context);
BtHidKeyboard* bt_hid_keyboard = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
furi_hal_bt_hid_kb_release_all();
} else {
bt_hid_keyboard_process(bt_hid_keyboard, event);
consumed = true;
}
return consumed;
}
BtHidKeyboard* bt_hid_keyboard_alloc() {
BtHidKeyboard* bt_hid_keyboard = malloc(sizeof(BtHidKeyboard));
bt_hid_keyboard->view = view_alloc();
view_set_context(bt_hid_keyboard->view, bt_hid_keyboard);
view_allocate_model(bt_hid_keyboard->view, ViewModelTypeLocking, sizeof(BtHidKeyboardModel));
view_set_draw_callback(bt_hid_keyboard->view, bt_hid_keyboard_draw_callback);
view_set_input_callback(bt_hid_keyboard->view, bt_hid_keyboard_input_callback);
return bt_hid_keyboard;
}
void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard) {
furi_assert(bt_hid_keyboard);
view_free(bt_hid_keyboard->view);
free(bt_hid_keyboard);
}
View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard) {
furi_assert(bt_hid_keyboard);
return bt_hid_keyboard->view;
}
void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected) {
furi_assert(bt_hid_keyboard);
with_view_model(
bt_hid_keyboard->view, (BtHidKeyboardModel * model) {
model->connected = connected;
return true;
});
}
@@ -0,0 +1,13 @@
#pragma once
#include <gui/view.h>
typedef struct BtHidKeyboard BtHidKeyboard;
BtHidKeyboard* bt_hid_keyboard_alloc();
void bt_hid_keyboard_free(BtHidKeyboard* bt_hid_keyboard);
View* bt_hid_keyboard_get_view(BtHidKeyboard* bt_hid_keyboard);
void bt_hid_keyboard_set_connected_status(BtHidKeyboard* bt_hid_keyboard, bool connected);
@@ -0,0 +1,204 @@
#include "bt_hid_keynote.h"
#include <furi.h>
#include <furi_hal_bt_hid.h>
#include <furi_hal_usb_hid.h>
#include <gui/elements.h>
struct BtHidKeynote {
View* view;
};
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool ok_pressed;
bool back_pressed;
bool connected;
} BtHidKeynoteModel;
static void bt_hid_keynote_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
if(dir == CanvasDirectionBottomToTop) {
canvas_draw_line(canvas, x, y + 6, x, y - 1);
} else if(dir == CanvasDirectionTopToBottom) {
canvas_draw_line(canvas, x, y - 6, x, y + 1);
} else if(dir == CanvasDirectionRightToLeft) {
canvas_draw_line(canvas, x + 6, y, x - 1, y);
} else if(dir == CanvasDirectionLeftToRight) {
canvas_draw_line(canvas, x - 6, y, x + 1, y);
}
}
static void bt_hid_keynote_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
BtHidKeynoteModel* model = context;
// Header
if(model->connected) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
} else {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
}
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Keynote");
canvas_draw_icon(canvas, 68, 2, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 127, 3, AlignRight, AlignTop, "Hold to exit");
// Up
canvas_draw_icon(canvas, 21, 24, &I_Button_18x18);
if(model->up_pressed) {
elements_slightly_rounded_box(canvas, 24, 26, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
bt_hid_keynote_draw_arrow(canvas, 30, 30, CanvasDirectionBottomToTop);
canvas_set_color(canvas, ColorBlack);
// Down
canvas_draw_icon(canvas, 21, 45, &I_Button_18x18);
if(model->down_pressed) {
elements_slightly_rounded_box(canvas, 24, 47, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
bt_hid_keynote_draw_arrow(canvas, 30, 55, CanvasDirectionTopToBottom);
canvas_set_color(canvas, ColorBlack);
// Left
canvas_draw_icon(canvas, 0, 45, &I_Button_18x18);
if(model->left_pressed) {
elements_slightly_rounded_box(canvas, 3, 47, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
bt_hid_keynote_draw_arrow(canvas, 7, 53, CanvasDirectionRightToLeft);
canvas_set_color(canvas, ColorBlack);
// Right
canvas_draw_icon(canvas, 42, 45, &I_Button_18x18);
if(model->right_pressed) {
elements_slightly_rounded_box(canvas, 45, 47, 13, 13);
canvas_set_color(canvas, ColorWhite);
}
bt_hid_keynote_draw_arrow(canvas, 53, 53, CanvasDirectionLeftToRight);
canvas_set_color(canvas, ColorBlack);
// Ok
canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
if(model->ok_pressed) {
elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Space");
canvas_set_color(canvas, ColorBlack);
// Back
canvas_draw_icon(canvas, 63, 45, &I_Space_65x18);
if(model->back_pressed) {
elements_slightly_rounded_box(canvas, 66, 47, 60, 13);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Back");
}
static void bt_hid_keynote_process(BtHidKeynote* bt_hid_keynote, InputEvent* event) {
with_view_model(
bt_hid_keynote->view, (BtHidKeynoteModel * model) {
if(event->type == InputTypePress) {
if(event->key == InputKeyUp) {
model->up_pressed = true;
furi_hal_bt_hid_kb_press(HID_KEYBOARD_UP_ARROW);
} else if(event->key == InputKeyDown) {
model->down_pressed = true;
furi_hal_bt_hid_kb_press(HID_KEYBOARD_DOWN_ARROW);
} else if(event->key == InputKeyLeft) {
model->left_pressed = true;
furi_hal_bt_hid_kb_press(HID_KEYBOARD_LEFT_ARROW);
} else if(event->key == InputKeyRight) {
model->right_pressed = true;
furi_hal_bt_hid_kb_press(HID_KEYBOARD_RIGHT_ARROW);
} else if(event->key == InputKeyOk) {
model->ok_pressed = true;
furi_hal_bt_hid_kb_press(HID_KEYBOARD_SPACEBAR);
} else if(event->key == InputKeyBack) {
model->back_pressed = true;
}
} else if(event->type == InputTypeRelease) {
if(event->key == InputKeyUp) {
model->up_pressed = false;
furi_hal_bt_hid_kb_release(HID_KEYBOARD_UP_ARROW);
} else if(event->key == InputKeyDown) {
model->down_pressed = false;
furi_hal_bt_hid_kb_release(HID_KEYBOARD_DOWN_ARROW);
} else if(event->key == InputKeyLeft) {
model->left_pressed = false;
furi_hal_bt_hid_kb_release(HID_KEYBOARD_LEFT_ARROW);
} else if(event->key == InputKeyRight) {
model->right_pressed = false;
furi_hal_bt_hid_kb_release(HID_KEYBOARD_RIGHT_ARROW);
} else if(event->key == InputKeyOk) {
model->ok_pressed = false;
furi_hal_bt_hid_kb_release(HID_KEYBOARD_SPACEBAR);
} else if(event->key == InputKeyBack) {
model->back_pressed = false;
}
} else if(event->type == InputTypeShort) {
if(event->key == InputKeyBack) {
furi_hal_bt_hid_kb_press(HID_KEYBOARD_DELETE);
furi_hal_bt_hid_kb_release(HID_KEYBOARD_DELETE);
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_AC_BACK);
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_AC_BACK);
}
}
return true;
});
}
static bool bt_hid_keynote_input_callback(InputEvent* event, void* context) {
furi_assert(context);
BtHidKeynote* bt_hid_keynote = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
furi_hal_bt_hid_kb_release_all();
} else {
bt_hid_keynote_process(bt_hid_keynote, event);
consumed = true;
}
return consumed;
}
BtHidKeynote* bt_hid_keynote_alloc() {
BtHidKeynote* bt_hid_keynote = malloc(sizeof(BtHidKeynote));
bt_hid_keynote->view = view_alloc();
view_set_context(bt_hid_keynote->view, bt_hid_keynote);
view_allocate_model(bt_hid_keynote->view, ViewModelTypeLocking, sizeof(BtHidKeynoteModel));
view_set_draw_callback(bt_hid_keynote->view, bt_hid_keynote_draw_callback);
view_set_input_callback(bt_hid_keynote->view, bt_hid_keynote_input_callback);
return bt_hid_keynote;
}
void bt_hid_keynote_free(BtHidKeynote* bt_hid_keynote) {
furi_assert(bt_hid_keynote);
view_free(bt_hid_keynote->view);
free(bt_hid_keynote);
}
View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote) {
furi_assert(bt_hid_keynote);
return bt_hid_keynote->view;
}
void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected) {
furi_assert(bt_hid_keynote);
with_view_model(
bt_hid_keynote->view, (BtHidKeynoteModel * model) {
model->connected = connected;
return true;
});
}
@@ -0,0 +1,13 @@
#pragma once
#include <gui/view.h>
typedef struct BtHidKeynote BtHidKeynote;
BtHidKeynote* bt_hid_keynote_alloc();
void bt_hid_keynote_free(BtHidKeynote* bt_hid_keynote);
View* bt_hid_keynote_get_view(BtHidKeynote* bt_hid_keynote);
void bt_hid_keynote_set_connected_status(BtHidKeynote* bt_hid_keynote, bool connected);
@@ -0,0 +1,203 @@
#include "bt_hid_media.h"
#include <furi.h>
#include <furi_hal_bt_hid.h>
#include <furi_hal_usb_hid.h>
#include <gui/elements.h>
struct BtHidMedia {
View* view;
};
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool ok_pressed;
bool connected;
} BtHidMediaModel;
static void bt_hid_media_draw_arrow(Canvas* canvas, uint8_t x, uint8_t y, CanvasDirection dir) {
canvas_draw_triangle(canvas, x, y, 5, 3, dir);
if(dir == CanvasDirectionBottomToTop) {
canvas_draw_dot(canvas, x, y - 1);
} else if(dir == CanvasDirectionTopToBottom) {
canvas_draw_dot(canvas, x, y + 1);
} else if(dir == CanvasDirectionRightToLeft) {
canvas_draw_dot(canvas, x - 1, y);
} else if(dir == CanvasDirectionLeftToRight) {
canvas_draw_dot(canvas, x + 1, y);
}
}
static void bt_hid_media_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
BtHidMediaModel* model = context;
// Header
if(model->connected) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
} else {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
}
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Media");
canvas_set_font(canvas, FontSecondary);
// Keypad circles
canvas_draw_icon(canvas, 76, 8, &I_Circles_47x47);
// Up
if(model->up_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 93, 9, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 96, 12, &I_Volup_8x6);
canvas_set_color(canvas, ColorBlack);
// Down
if(model->down_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 93, 41, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 96, 45, &I_Voldwn_6x6);
canvas_set_color(canvas, ColorBlack);
// Left
if(model->left_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 77, 25, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
bt_hid_media_draw_arrow(canvas, 82, 31, CanvasDirectionRightToLeft);
bt_hid_media_draw_arrow(canvas, 86, 31, CanvasDirectionRightToLeft);
canvas_set_color(canvas, ColorBlack);
// Right
if(model->right_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 109, 25, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
bt_hid_media_draw_arrow(canvas, 112, 31, CanvasDirectionLeftToRight);
bt_hid_media_draw_arrow(canvas, 116, 31, CanvasDirectionLeftToRight);
canvas_set_color(canvas, ColorBlack);
// Ok
if(model->ok_pressed) {
canvas_draw_icon(canvas, 93, 25, &I_Pressed_Button_13x13);
canvas_set_color(canvas, ColorWhite);
}
bt_hid_media_draw_arrow(canvas, 96, 31, CanvasDirectionLeftToRight);
canvas_draw_line(canvas, 100, 29, 100, 33);
canvas_draw_line(canvas, 102, 29, 102, 33);
canvas_set_color(canvas, ColorBlack);
// Exit
canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
}
static void bt_hid_media_process_press(BtHidMedia* bt_hid_media, InputEvent* event) {
with_view_model(
bt_hid_media->view, (BtHidMediaModel * model) {
if(event->key == InputKeyUp) {
model->up_pressed = true;
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyDown) {
model->down_pressed = true;
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyLeft) {
model->left_pressed = true;
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_PREVIOUS_TRACK);
} else if(event->key == InputKeyRight) {
model->right_pressed = true;
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_SCAN_NEXT_TRACK);
} else if(event->key == InputKeyOk) {
model->ok_pressed = true;
furi_hal_bt_hid_consumer_key_press(HID_CONSUMER_PLAY_PAUSE);
}
return true;
});
}
static void bt_hid_media_process_release(BtHidMedia* bt_hid_media, InputEvent* event) {
with_view_model(
bt_hid_media->view, (BtHidMediaModel * model) {
if(event->key == InputKeyUp) {
model->up_pressed = false;
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_INCREMENT);
} else if(event->key == InputKeyDown) {
model->down_pressed = false;
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_VOLUME_DECREMENT);
} else if(event->key == InputKeyLeft) {
model->left_pressed = false;
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_PREVIOUS_TRACK);
} else if(event->key == InputKeyRight) {
model->right_pressed = false;
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_SCAN_NEXT_TRACK);
} else if(event->key == InputKeyOk) {
model->ok_pressed = false;
furi_hal_bt_hid_consumer_key_release(HID_CONSUMER_PLAY_PAUSE);
}
return true;
});
}
static bool bt_hid_media_input_callback(InputEvent* event, void* context) {
furi_assert(context);
BtHidMedia* bt_hid_media = context;
bool consumed = false;
if(event->type == InputTypePress) {
bt_hid_media_process_press(bt_hid_media, event);
consumed = true;
} else if(event->type == InputTypeRelease) {
bt_hid_media_process_release(bt_hid_media, event);
consumed = true;
} else if(event->type == InputTypeShort) {
if(event->key == InputKeyBack) {
furi_hal_bt_hid_consumer_key_release_all();
}
}
return consumed;
}
BtHidMedia* bt_hid_media_alloc() {
BtHidMedia* bt_hid_media = malloc(sizeof(BtHidMedia));
bt_hid_media->view = view_alloc();
view_set_context(bt_hid_media->view, bt_hid_media);
view_allocate_model(bt_hid_media->view, ViewModelTypeLocking, sizeof(BtHidMediaModel));
view_set_draw_callback(bt_hid_media->view, bt_hid_media_draw_callback);
view_set_input_callback(bt_hid_media->view, bt_hid_media_input_callback);
return bt_hid_media;
}
void bt_hid_media_free(BtHidMedia* bt_hid_media) {
furi_assert(bt_hid_media);
view_free(bt_hid_media->view);
free(bt_hid_media);
}
View* bt_hid_media_get_view(BtHidMedia* bt_hid_media) {
furi_assert(bt_hid_media);
return bt_hid_media->view;
}
void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected) {
furi_assert(bt_hid_media);
with_view_model(
bt_hid_media->view, (BtHidMediaModel * model) {
model->connected = connected;
return true;
});
}
@@ -0,0 +1,13 @@
#pragma once
#include <gui/view.h>
typedef struct BtHidMedia BtHidMedia;
BtHidMedia* bt_hid_media_alloc();
void bt_hid_media_free(BtHidMedia* bt_hid_media);
View* bt_hid_media_get_view(BtHidMedia* bt_hid_media);
void bt_hid_media_set_connected_status(BtHidMedia* bt_hid_media, bool connected);
@@ -0,0 +1,218 @@
#include "bt_hid_mouse.h"
#include <furi.h>
#include <furi_hal_bt_hid.h>
#include <furi_hal_usb_hid.h>
#include <gui/elements.h>
struct BtHidMouse {
View* view;
};
#define MOUSE_MOVE_SHORT 5
#define MOUSE_MOVE_LONG 20
typedef struct {
bool left_pressed;
bool up_pressed;
bool right_pressed;
bool down_pressed;
bool left_mouse_pressed;
bool left_mouse_held;
bool right_mouse_pressed;
bool connected;
} BtHidMouseModel;
static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) {
furi_assert(context);
BtHidMouseModel* model = context;
// Header
if(model->connected) {
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
} else {
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
}
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse");
canvas_set_font(canvas, FontSecondary);
if(model->left_mouse_held == true) {
elements_multiline_text_aligned(canvas, 0, 62, AlignLeft, AlignBottom, "Selecting...");
} else {
canvas_draw_icon(canvas, 0, 54, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
}
// Keypad circles
canvas_draw_icon(canvas, 64, 8, &I_Circles_47x47);
// Up
if(model->up_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 81, 9, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up7x9);
canvas_set_color(canvas, ColorBlack);
// Down
if(model->down_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 81, 41, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 84, 43, &I_Pin_arrow_down_7x9);
canvas_set_color(canvas, ColorBlack);
// Left
if(model->left_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 65, 25, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 67, 28, &I_Pin_arrow_left_9x7);
canvas_set_color(canvas, ColorBlack);
// Right
if(model->right_pressed) {
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 97, 25, &I_Pressed_Button_13x13);
canvas_set_bitmap_mode(canvas, 0);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_icon(canvas, 99, 28, &I_Pin_arrow_right_9x7);
canvas_set_color(canvas, ColorBlack);
// Ok
if(model->left_mouse_pressed) {
canvas_draw_icon(canvas, 81, 25, &I_Ok_btn_pressed_13x13);
} else {
canvas_draw_icon(canvas, 83, 27, &I_Left_mouse_icon_9x9);
}
// Back
if(model->right_mouse_pressed) {
canvas_draw_icon(canvas, 108, 48, &I_Ok_btn_pressed_13x13);
} else {
canvas_draw_icon(canvas, 110, 50, &I_Right_mouse_icon_9x9);
}
}
static void bt_hid_mouse_process(BtHidMouse* bt_hid_mouse, InputEvent* event) {
with_view_model(
bt_hid_mouse->view, (BtHidMouseModel * model) {
if(event->key == InputKeyBack) {
if(event->type == InputTypeShort) {
furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_RIGHT);
furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_RIGHT);
} else if(event->type == InputTypePress) {
model->right_mouse_pressed = true;
} else if(event->type == InputTypeRelease) {
model->right_mouse_pressed = false;
}
} else if(event->key == InputKeyOk) {
if(event->type == InputTypeShort) {
// Just release if it was being held before
if(!model->left_mouse_held) furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
furi_hal_bt_hid_mouse_release(HID_MOUSE_BTN_LEFT);
model->left_mouse_held = false;
} else if(event->type == InputTypeLong) {
furi_hal_bt_hid_mouse_press(HID_MOUSE_BTN_LEFT);
model->left_mouse_held = true;
model->left_mouse_pressed = true;
} else if(event->type == InputTypePress) {
model->left_mouse_pressed = true;
} else if(event->type == InputTypeRelease) {
// Only release if it wasn't a long press
if(!model->left_mouse_held) model->left_mouse_pressed = false;
}
} else if(event->key == InputKeyRight) {
if(event->type == InputTypePress) {
model->right_pressed = true;
furi_hal_bt_hid_mouse_move(MOUSE_MOVE_SHORT, 0);
} else if(event->type == InputTypeRepeat) {
furi_hal_bt_hid_mouse_move(MOUSE_MOVE_LONG, 0);
} else if(event->type == InputTypeRelease) {
model->right_pressed = false;
}
} else if(event->key == InputKeyLeft) {
if(event->type == InputTypePress) {
model->left_pressed = true;
furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_SHORT, 0);
} else if(event->type == InputTypeRepeat) {
furi_hal_bt_hid_mouse_move(-MOUSE_MOVE_LONG, 0);
} else if(event->type == InputTypeRelease) {
model->left_pressed = false;
}
} else if(event->key == InputKeyDown) {
if(event->type == InputTypePress) {
model->down_pressed = true;
furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_SHORT);
} else if(event->type == InputTypeRepeat) {
furi_hal_bt_hid_mouse_move(0, MOUSE_MOVE_LONG);
} else if(event->type == InputTypeRelease) {
model->down_pressed = false;
}
} else if(event->key == InputKeyUp) {
if(event->type == InputTypePress) {
model->up_pressed = true;
furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_SHORT);
} else if(event->type == InputTypeRepeat) {
furi_hal_bt_hid_mouse_move(0, -MOUSE_MOVE_LONG);
} else if(event->type == InputTypeRelease) {
model->up_pressed = false;
}
}
return true;
});
}
static bool bt_hid_mouse_input_callback(InputEvent* event, void* context) {
furi_assert(context);
BtHidMouse* bt_hid_mouse = context;
bool consumed = false;
if(event->type == InputTypeLong && event->key == InputKeyBack) {
furi_hal_bt_hid_mouse_release_all();
} else {
bt_hid_mouse_process(bt_hid_mouse, event);
consumed = true;
}
return consumed;
}
BtHidMouse* bt_hid_mouse_alloc() {
BtHidMouse* bt_hid_mouse = malloc(sizeof(BtHidMouse));
bt_hid_mouse->view = view_alloc();
view_set_context(bt_hid_mouse->view, bt_hid_mouse);
view_allocate_model(bt_hid_mouse->view, ViewModelTypeLocking, sizeof(BtHidMouseModel));
view_set_draw_callback(bt_hid_mouse->view, bt_hid_mouse_draw_callback);
view_set_input_callback(bt_hid_mouse->view, bt_hid_mouse_input_callback);
return bt_hid_mouse;
}
void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse) {
furi_assert(bt_hid_mouse);
view_free(bt_hid_mouse->view);
free(bt_hid_mouse);
}
View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse) {
furi_assert(bt_hid_mouse);
return bt_hid_mouse->view;
}
void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected) {
furi_assert(bt_hid_mouse);
with_view_model(
bt_hid_mouse->view, (BtHidMouseModel * model) {
model->connected = connected;
return true;
});
}
@@ -0,0 +1,13 @@
#pragma once
#include <gui/view.h>
typedef struct BtHidMouse BtHidMouse;
BtHidMouse* bt_hid_mouse_alloc();
void bt_hid_mouse_free(BtHidMouse* bt_hid_mouse);
View* bt_hid_mouse_get_view(BtHidMouse* bt_hid_mouse);
void bt_hid_mouse_set_connected_status(BtHidMouse* bt_hid_mouse, bool connected);
@@ -0,0 +1,24 @@
App(
appid="music_player",
name="Music Player",
apptype=FlipperAppType.PLUGIN,
entry_point="music_player_app",
cdefines=["APP_MUSIC_PLAYER"],
requires=[
"gui",
"dialogs",
],
provides=["music_player_start"],
stack_size=2 * 1024,
order=20,
fap_icon="../../../assets/icons/Archive/music_10px.png",
fap_category="Misc",
)
App(
appid="music_player_start",
apptype=FlipperAppType.STARTUP,
entry_point="music_player_on_system_start",
requires=["music_player"],
order=30,
)
@@ -0,0 +1,361 @@
#include "music_player_worker.h"
#include <furi.h>
#include <furi_hal.h>
#include <assets_icons.h>
#include <gui/gui.h>
#include <dialogs/dialogs.h>
#include <storage/storage.h>
#include <m-string.h>
#define TAG "MusicPlayer"
#define MUSIC_PLAYER_APP_PATH_FOLDER ANY_PATH("music_player")
#define MUSIC_PLAYER_APP_EXTENSION "*"
#define MUSIC_PLAYER_SEMITONE_HISTORY_SIZE 4
typedef struct {
uint8_t semitone_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE];
uint8_t duration_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE];
uint8_t volume;
uint8_t semitone;
uint8_t dots;
uint8_t duration;
float position;
} MusicPlayerModel;
typedef struct {
MusicPlayerModel* model;
FuriMutex** model_mutex;
FuriMessageQueue* input_queue;
ViewPort* view_port;
Gui* gui;
MusicPlayerWorker* worker;
} MusicPlayer;
static const float MUSIC_PLAYER_VOLUMES[] = {0, .25, .5, .75, 1};
static const char* semitone_to_note(int8_t semitone) {
switch(semitone) {
case 0:
return "C";
case 1:
return "C#";
case 2:
return "D";
case 3:
return "D#";
case 4:
return "E";
case 5:
return "F";
case 6:
return "F#";
case 7:
return "G";
case 8:
return "G#";
case 9:
return "A";
case 10:
return "A#";
case 11:
return "B";
default:
return "--";
}
}
static bool is_white_note(uint8_t semitone, uint8_t id) {
switch(semitone) {
case 0:
if(id == 0) return true;
break;
case 2:
if(id == 1) return true;
break;
case 4:
if(id == 2) return true;
break;
case 5:
if(id == 3) return true;
break;
case 7:
if(id == 4) return true;
break;
case 9:
if(id == 5) return true;
break;
case 11:
if(id == 6) return true;
break;
default:
break;
}
return false;
}
static bool is_black_note(uint8_t semitone, uint8_t id) {
switch(semitone) {
case 1:
if(id == 0) return true;
break;
case 3:
if(id == 1) return true;
break;
case 6:
if(id == 3) return true;
break;
case 8:
if(id == 4) return true;
break;
case 10:
if(id == 5) return true;
break;
default:
break;
}
return false;
}
static void render_callback(Canvas* canvas, void* ctx) {
MusicPlayer* music_player = ctx;
furi_check(furi_mutex_acquire(music_player->model_mutex, FuriWaitForever) == FuriStatusOk);
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 0, 12, "MusicPlayer");
uint8_t x_pos = 0;
uint8_t y_pos = 24;
const uint8_t white_w = 10;
const uint8_t white_h = 40;
const int8_t black_x = 6;
const int8_t black_y = -5;
const uint8_t black_w = 8;
const uint8_t black_h = 32;
// white keys
for(size_t i = 0; i < 7; i++) {
if(is_white_note(music_player->model->semitone, i)) {
canvas_draw_box(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h);
} else {
canvas_draw_frame(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h);
}
}
// black keys
for(size_t i = 0; i < 7; i++) {
if(i != 2 && i != 6) {
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(
canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h);
canvas_set_color(canvas, ColorBlack);
if(is_black_note(music_player->model->semitone, i)) {
canvas_draw_box(
canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h);
} else {
canvas_draw_frame(
canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h);
}
}
}
// volume view_port
x_pos = 124;
y_pos = 0;
const uint8_t volume_h =
(64 / (COUNT_OF(MUSIC_PLAYER_VOLUMES) - 1)) * music_player->model->volume;
canvas_draw_frame(canvas, x_pos, y_pos, 4, 64);
canvas_draw_box(canvas, x_pos, y_pos + (64 - volume_h), 4, volume_h);
// note stack view_port
x_pos = 73;
y_pos = 0;
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
canvas_draw_frame(canvas, x_pos, y_pos, 49, 64);
canvas_draw_line(canvas, x_pos + 28, 0, x_pos + 28, 64);
char duration_text[16];
for(uint8_t i = 0; i < MUSIC_PLAYER_SEMITONE_HISTORY_SIZE; i++) {
if(music_player->model->duration_history[i] == 0xFF) {
snprintf(duration_text, 15, "--");
} else {
snprintf(duration_text, 15, "%d", music_player->model->duration_history[i]);
}
if(i == 0) {
canvas_draw_box(canvas, x_pos, y_pos + 48, 49, 16);
canvas_set_color(canvas, ColorWhite);
} else {
canvas_set_color(canvas, ColorBlack);
}
canvas_draw_str(
canvas,
x_pos + 4,
64 - 16 * i - 3,
semitone_to_note(music_player->model->semitone_history[i]));
canvas_draw_str(canvas, x_pos + 31, 64 - 16 * i - 3, duration_text);
canvas_draw_line(canvas, x_pos, 64 - 16 * i, x_pos + 48, 64 - 16 * i);
}
furi_mutex_release(music_player->model_mutex);
}
static void input_callback(InputEvent* input_event, void* ctx) {
MusicPlayer* music_player = ctx;
if(input_event->type == InputTypeShort) {
furi_message_queue_put(music_player->input_queue, input_event, 0);
}
}
static void music_player_worker_callback(
uint8_t semitone,
uint8_t dots,
uint8_t duration,
float position,
void* context) {
MusicPlayer* music_player = context;
furi_check(furi_mutex_acquire(music_player->model_mutex, FuriWaitForever) == FuriStatusOk);
for(size_t i = 0; i < MUSIC_PLAYER_SEMITONE_HISTORY_SIZE - 1; i++) {
size_t r = MUSIC_PLAYER_SEMITONE_HISTORY_SIZE - 1 - i;
music_player->model->duration_history[r] = music_player->model->duration_history[r - 1];
music_player->model->semitone_history[r] = music_player->model->semitone_history[r - 1];
}
semitone = (semitone == 0xFF) ? 0xFF : semitone % 12;
music_player->model->semitone = semitone;
music_player->model->dots = dots;
music_player->model->duration = duration;
music_player->model->position = position;
music_player->model->semitone_history[0] = semitone;
music_player->model->duration_history[0] = duration;
furi_mutex_release(music_player->model_mutex);
view_port_update(music_player->view_port);
}
MusicPlayer* music_player_alloc() {
MusicPlayer* instance = malloc(sizeof(MusicPlayer));
instance->model = malloc(sizeof(MusicPlayerModel));
memset(instance->model->duration_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE);
memset(instance->model->semitone_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE);
instance->model->volume = 3;
instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
instance->worker = music_player_worker_alloc();
music_player_worker_set_volume(
instance->worker, MUSIC_PLAYER_VOLUMES[instance->model->volume]);
music_player_worker_set_callback(instance->worker, music_player_worker_callback, instance);
instance->view_port = view_port_alloc();
view_port_draw_callback_set(instance->view_port, render_callback, instance);
view_port_input_callback_set(instance->view_port, input_callback, instance);
// Open GUI and register view_port
instance->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
return instance;
}
void music_player_free(MusicPlayer* instance) {
gui_remove_view_port(instance->gui, instance->view_port);
furi_record_close(RECORD_GUI);
view_port_free(instance->view_port);
music_player_worker_free(instance->worker);
furi_message_queue_free(instance->input_queue);
furi_mutex_free(instance->model_mutex);
free(instance->model);
free(instance);
}
int32_t music_player_app(void* p) {
MusicPlayer* music_player = music_player_alloc();
string_t file_path;
string_init(file_path);
do {
if(p && strlen(p)) {
string_cat_str(file_path, p);
} else {
string_set_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(
&browser_options, MUSIC_PLAYER_APP_EXTENSION, &I_music_10px);
browser_options.hide_ext = false;
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
furi_record_close(RECORD_DIALOGS);
if(!res) {
FURI_LOG_E(TAG, "No file selected");
break;
}
}
if(!music_player_worker_load(music_player->worker, string_get_cstr(file_path))) {
FURI_LOG_E(TAG, "Unable to load file");
break;
}
music_player_worker_start(music_player->worker);
InputEvent input;
while(furi_message_queue_get(music_player->input_queue, &input, FuriWaitForever) ==
FuriStatusOk) {
furi_check(
furi_mutex_acquire(music_player->model_mutex, FuriWaitForever) == FuriStatusOk);
if(input.key == InputKeyBack) {
furi_mutex_release(music_player->model_mutex);
break;
} else if(input.key == InputKeyUp) {
if(music_player->model->volume < COUNT_OF(MUSIC_PLAYER_VOLUMES) - 1)
music_player->model->volume++;
music_player_worker_set_volume(
music_player->worker, MUSIC_PLAYER_VOLUMES[music_player->model->volume]);
} else if(input.key == InputKeyDown) {
if(music_player->model->volume > 0) music_player->model->volume--;
music_player_worker_set_volume(
music_player->worker, MUSIC_PLAYER_VOLUMES[music_player->model->volume]);
}
furi_mutex_release(music_player->model_mutex);
view_port_update(music_player->view_port);
}
music_player_worker_stop(music_player->worker);
} while(0);
string_clear(file_path);
music_player_free(music_player);
return 0;
}
@@ -0,0 +1,48 @@
#include <furi.h>
#include <cli/cli.h>
#include <storage/storage.h>
#include "music_player_worker.h"
static void music_player_cli(Cli* cli, string_t args, void* context) {
UNUSED(context);
MusicPlayerWorker* music_player_worker = music_player_worker_alloc();
Storage* storage = furi_record_open(RECORD_STORAGE);
do {
if(storage_common_stat(storage, string_get_cstr(args), NULL) == FSE_OK) {
if(!music_player_worker_load(music_player_worker, string_get_cstr(args))) {
printf("Failed to open file %s\r\n", string_get_cstr(args));
break;
}
} else {
if(!music_player_worker_load_rtttl_from_string(
music_player_worker, string_get_cstr(args))) {
printf("Argument is not a file or RTTTL\r\n");
break;
}
}
printf("Press CTRL+C to stop\r\n");
music_player_worker_set_volume(music_player_worker, 1.0f);
music_player_worker_start(music_player_worker);
while(!cli_cmd_interrupt_received(cli)) {
furi_delay_ms(50);
}
music_player_worker_stop(music_player_worker);
} while(0);
furi_record_close(RECORD_STORAGE);
music_player_worker_free(music_player_worker);
}
void music_player_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open(RECORD_CLI);
cli_add_command(cli, "music_player", CliCommandFlagDefault, music_player_cli, NULL);
furi_record_close(RECORD_CLI);
#else
UNUSED(music_player_cli);
#endif
}
@@ -0,0 +1,499 @@
#include "music_player_worker.h"
#include <furi_hal.h>
#include <furi.h>
#include <storage/storage.h>
#include <lib/flipper_format/flipper_format.h>
#include <m-array.h>
#define TAG "MusicPlayerWorker"
#define MUSIC_PLAYER_FILETYPE "Flipper Music Format"
#define MUSIC_PLAYER_VERSION 0
#define SEMITONE_PAUSE 0xFF
#define NOTE_C4 261.63f
#define NOTE_C4_SEMITONE (4.0f * 12.0f)
#define TWO_POW_TWELTH_ROOT 1.059463094359f
typedef struct {
uint8_t semitone;
uint8_t duration;
uint8_t dots;
} NoteBlock;
ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST);
struct MusicPlayerWorker {
FuriThread* thread;
bool should_work;
MusicPlayerWorkerCallback callback;
void* callback_context;
float volume;
uint32_t bpm;
uint32_t duration;
uint32_t octave;
NoteBlockArray_t notes;
};
static int32_t music_player_worker_thread_callback(void* context) {
furi_assert(context);
MusicPlayerWorker* instance = context;
NoteBlockArray_it_t it;
NoteBlockArray_it(it, instance->notes);
while(instance->should_work) {
if(NoteBlockArray_end_p(it)) {
NoteBlockArray_it(it, instance->notes);
furi_delay_ms(10);
} else {
NoteBlock* note_block = NoteBlockArray_ref(it);
float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE;
float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4);
float duration =
60.0 * furi_kernel_get_tick_frequency() * 4 / instance->bpm / note_block->duration;
uint32_t dots = note_block->dots;
while(dots > 0) {
duration += duration / 2;
dots--;
}
uint32_t next_tick = furi_get_tick() + duration;
float volume = instance->volume;
if(instance->callback) {
instance->callback(
note_block->semitone,
note_block->dots,
note_block->duration,
0.0,
instance->callback_context);
}
furi_hal_speaker_stop();
furi_hal_speaker_start(frequency, volume);
while(instance->should_work && furi_get_tick() < next_tick) {
volume *= 0.9945679;
furi_hal_speaker_set_volume(volume);
furi_delay_ms(2);
}
NoteBlockArray_next(it);
}
}
furi_hal_speaker_stop();
return 0;
}
MusicPlayerWorker* music_player_worker_alloc() {
MusicPlayerWorker* instance = malloc(sizeof(MusicPlayerWorker));
NoteBlockArray_init(instance->notes);
instance->thread = furi_thread_alloc();
furi_thread_set_name(instance->thread, "MusicPlayerWorker");
furi_thread_set_stack_size(instance->thread, 1024);
furi_thread_set_context(instance->thread, instance);
furi_thread_set_callback(instance->thread, music_player_worker_thread_callback);
instance->volume = 1.0f;
return instance;
}
void music_player_worker_free(MusicPlayerWorker* instance) {
furi_assert(instance);
furi_thread_free(instance->thread);
NoteBlockArray_clear(instance->notes);
free(instance);
}
static bool is_digit(const char c) {
return isdigit(c) != 0;
}
static bool is_letter(const char c) {
return islower(c) != 0 || isupper(c) != 0;
}
static bool is_space(const char c) {
return c == ' ' || c == '\t';
}
static size_t extract_number(const char* string, uint32_t* number) {
size_t ret = 0;
while(is_digit(*string)) {
*number *= 10;
*number += (*string - '0');
string++;
ret++;
}
return ret;
}
static size_t extract_dots(const char* string, uint32_t* number) {
size_t ret = 0;
while(*string == '.') {
*number += 1;
string++;
ret++;
}
return ret;
}
static size_t extract_char(const char* string, char* symbol) {
if(is_letter(*string)) {
*symbol = *string;
return 1;
} else {
return 0;
}
}
static size_t extract_sharp(const char* string, char* symbol) {
if(*string == '#' || *string == '_') {
*symbol = '#';
return 1;
} else {
return 0;
}
}
static size_t skip_till(const char* string, const char symbol) {
size_t ret = 0;
while(*string != '\0' && *string != symbol) {
string++;
ret++;
}
if(*string != symbol) {
ret = 0;
}
return ret;
}
static bool music_player_worker_add_note(
MusicPlayerWorker* instance,
uint8_t semitone,
uint8_t duration,
uint8_t dots) {
NoteBlock note_block;
note_block.semitone = semitone;
note_block.duration = duration;
note_block.dots = dots;
NoteBlockArray_push_back(instance->notes, note_block);
return true;
}
static int8_t note_to_semitone(const char note) {
switch(note) {
case 'C':
return 0;
// C#
case 'D':
return 2;
// D#
case 'E':
return 4;
case 'F':
return 5;
// F#
case 'G':
return 7;
// G#
case 'A':
return 9;
// A#
case 'B':
return 11;
default:
return 0;
}
}
static bool music_player_worker_parse_notes(MusicPlayerWorker* instance, const char* string) {
const char* cursor = string;
bool result = true;
while(*cursor != '\0') {
if(!is_space(*cursor)) {
uint32_t duration = 0;
char note_char = '\0';
char sharp_char = '\0';
uint32_t octave = 0;
uint32_t dots = 0;
// Parsing
cursor += extract_number(cursor, &duration);
cursor += extract_char(cursor, &note_char);
cursor += extract_sharp(cursor, &sharp_char);
cursor += extract_number(cursor, &octave);
cursor += extract_dots(cursor, &dots);
// Post processing
note_char = toupper(note_char);
if(!duration) {
duration = instance->duration;
}
if(!octave) {
octave = instance->octave;
}
// Validation
bool is_valid = true;
is_valid &= (duration >= 1 && duration <= 128);
is_valid &= ((note_char >= 'A' && note_char <= 'G') || note_char == 'P');
is_valid &= (sharp_char == '#' || sharp_char == '\0');
is_valid &= (octave <= 16);
is_valid &= (dots <= 16);
if(!is_valid) {
FURI_LOG_E(
TAG,
"Invalid note: %u%c%c%u.%u",
duration,
note_char == '\0' ? '_' : note_char,
sharp_char == '\0' ? '_' : sharp_char,
octave,
dots);
result = false;
break;
}
// Note to semitones
uint8_t semitone = 0;
if(note_char == 'P') {
semitone = SEMITONE_PAUSE;
} else {
semitone += octave * 12;
semitone += note_to_semitone(note_char);
semitone += sharp_char == '#' ? 1 : 0;
}
if(music_player_worker_add_note(instance, semitone, duration, dots)) {
FURI_LOG_D(
TAG,
"Added note: %c%c%u.%u = %u %u",
note_char == '\0' ? '_' : note_char,
sharp_char == '\0' ? '_' : sharp_char,
octave,
dots,
semitone,
duration);
} else {
FURI_LOG_E(
TAG,
"Invalid note: %c%c%u.%u = %u %u",
note_char == '\0' ? '_' : note_char,
sharp_char == '\0' ? '_' : sharp_char,
octave,
dots,
semitone,
duration);
}
cursor += skip_till(cursor, ',');
}
if(*cursor != '\0') cursor++;
}
return result;
}
bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path) {
furi_assert(instance);
furi_assert(file_path);
bool ret = false;
if(strcasestr(file_path, ".fmf")) {
ret = music_player_worker_load_fmf_from_file(instance, file_path);
} else {
ret = music_player_worker_load_rtttl_from_file(instance, file_path);
}
return ret;
}
bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path) {
furi_assert(instance);
furi_assert(file_path);
bool result = false;
string_t temp_str;
string_init(temp_str);
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* file = flipper_format_file_alloc(storage);
do {
if(!flipper_format_file_open_existing(file, file_path)) break;
uint32_t version = 0;
if(!flipper_format_read_header(file, temp_str, &version)) break;
if(string_cmp_str(temp_str, MUSIC_PLAYER_FILETYPE) || (version != MUSIC_PLAYER_VERSION)) {
FURI_LOG_E(TAG, "Incorrect file format or version");
break;
}
if(!flipper_format_read_uint32(file, "BPM", &instance->bpm, 1)) {
FURI_LOG_E(TAG, "BPM is missing");
break;
}
if(!flipper_format_read_uint32(file, "Duration", &instance->duration, 1)) {
FURI_LOG_E(TAG, "Duration is missing");
break;
}
if(!flipper_format_read_uint32(file, "Octave", &instance->octave, 1)) {
FURI_LOG_E(TAG, "Octave is missing");
break;
}
if(!flipper_format_read_string(file, "Notes", temp_str)) {
FURI_LOG_E(TAG, "Notes is missing");
break;
}
if(!music_player_worker_parse_notes(instance, string_get_cstr(temp_str))) {
break;
}
result = true;
} while(false);
furi_record_close(RECORD_STORAGE);
flipper_format_free(file);
string_clear(temp_str);
return result;
}
bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path) {
furi_assert(instance);
furi_assert(file_path);
bool result = false;
string_t content;
string_init(content);
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
do {
if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
FURI_LOG_E(TAG, "Unable to open file");
break;
};
uint16_t ret = 0;
do {
uint8_t buffer[65] = {0};
ret = storage_file_read(file, buffer, sizeof(buffer) - 1);
for(size_t i = 0; i < ret; i++) {
string_push_back(content, buffer[i]);
}
} while(ret > 0);
string_strim(content);
if(!string_size(content)) {
FURI_LOG_E(TAG, "Empty file");
break;
}
if(!music_player_worker_load_rtttl_from_string(instance, string_get_cstr(content))) {
FURI_LOG_E(TAG, "Invalid file content");
break;
}
result = true;
} while(0);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
string_clear(content);
return result;
}
bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string) {
furi_assert(instance);
const char* cursor = string;
// Skip name
cursor += skip_till(cursor, ':');
if(*cursor != ':') {
return false;
}
// Duration
cursor += skip_till(cursor, '=');
if(*cursor != '=') {
return false;
}
cursor++;
cursor += extract_number(cursor, &instance->duration);
// Octave
cursor += skip_till(cursor, '=');
if(*cursor != '=') {
return false;
}
cursor++;
cursor += extract_number(cursor, &instance->octave);
// BPM
cursor += skip_till(cursor, '=');
if(*cursor != '=') {
return false;
}
cursor++;
cursor += extract_number(cursor, &instance->bpm);
// Notes
cursor += skip_till(cursor, ':');
if(*cursor != ':') {
return false;
}
cursor++;
if(!music_player_worker_parse_notes(instance, cursor)) {
return false;
}
return true;
}
void music_player_worker_set_callback(
MusicPlayerWorker* instance,
MusicPlayerWorkerCallback callback,
void* context) {
furi_assert(instance);
instance->callback = callback;
instance->callback_context = context;
}
void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume) {
furi_assert(instance);
instance->volume = volume;
}
void music_player_worker_start(MusicPlayerWorker* instance) {
furi_assert(instance);
furi_assert(instance->should_work == false);
instance->should_work = true;
furi_thread_start(instance->thread);
}
void music_player_worker_stop(MusicPlayerWorker* instance) {
furi_assert(instance);
furi_assert(instance->should_work == true);
instance->should_work = false;
furi_thread_join(instance->thread);
}
@@ -0,0 +1,36 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef void (*MusicPlayerWorkerCallback)(
uint8_t semitone,
uint8_t dots,
uint8_t duration,
float position,
void* context);
typedef struct MusicPlayerWorker MusicPlayerWorker;
MusicPlayerWorker* music_player_worker_alloc();
void music_player_worker_free(MusicPlayerWorker* instance);
bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path);
bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path);
bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path);
bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string);
void music_player_worker_set_callback(
MusicPlayerWorker* instance,
MusicPlayerWorkerCallback callback,
void* context);
void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume);
void music_player_worker_start(MusicPlayerWorker* instance);
void music_player_worker_stop(MusicPlayerWorker* instance);
@@ -0,0 +1,17 @@
App(
appid="picopass",
name="PicoPass Reader",
apptype=FlipperAppType.PLUGIN,
entry_point="picopass_app",
requires=[
"storage",
"gui",
],
stack_size=4 * 1024,
order=30,
fap_icon="../../../assets/icons/Archive/125_10px.png",
fap_libs=[
"mbedtls",
],
fap_category="Tools",
)
@@ -0,0 +1,318 @@
//-----------------------------------------------------------------------------
// Borrowed initially from https://github.com/holiman/loclass
// Copyright (C) 2014 Martin Holst Swende
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// WARNING
//
// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY.
//
// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL
// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL,
// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES.
//
// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS.
//-----------------------------------------------------------------------------
// It is a reconstruction of the cipher engine used in iClass, and RFID techology.
//
// The implementation is based on the work performed by
// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and
// Milosch Meriac in the paper "Dismantling IClass".
//-----------------------------------------------------------------------------
/*
This file contains an optimized version of the MAC-calculation algorithm. Some measurements on
a std laptop showed it runs in about 1/3 of the time:
Std: 0.428962
Opt: 0.151609
Additionally, it is self-reliant, not requiring e.g. bitstreams from the cipherutils, thus can
be easily dropped into a code base.
The optimizations have been performed in the following steps:
* Parameters passed by reference instead of by value.
* Iteration instead of recursion, un-nesting recursive loops into for-loops.
* Handling of bytes instead of individual bits, for less shuffling and masking
* Less creation of "objects", structs, and instead reuse of alloc:ed memory
* Inlining some functions via #define:s
As a consequence, this implementation is less generic. Also, I haven't bothered documenting this.
For a thorough documentation, check out the MAC-calculation within cipher.c instead.
-- MHS 2015
**/
/**
The runtime of opt_doTagMAC_2() with the MHS optimized version was 403 microseconds on Proxmark3.
This was still to slow for some newer readers which didn't want to wait that long.
Further optimizations to speedup the MAC calculations:
* Optimized opt_Tt logic
* Look up table for opt_select
* Removing many unnecessary bit maskings (& 0x1)
* updating state in place instead of alternating use of a second state structure
* remove the necessity to reverse bits of input and output bytes
opt_doTagMAC_2() now completes in 270 microseconds.
-- piwi 2019
**/
/**
add the possibility to do iCLASS on device only
-- iceman 2020
**/
#include "optimized_cipher.h"
#include "optimized_elite.h"
#include "optimized_ikeys.h"
#include "optimized_cipherutils.h"
static const uint8_t loclass_opt_select_LUT[256] = {
00, 03, 02, 01, 02, 03, 00, 01, 04, 07, 07, 04, 06, 07, 05, 04, 01, 02, 03, 00, 02, 03, 00, 01,
05, 06, 06, 05, 06, 07, 05, 04, 06, 05, 04, 07, 04, 05, 06, 07, 06, 05, 05, 06, 04, 05, 07, 06,
07, 04, 05, 06, 04, 05, 06, 07, 07, 04, 04, 07, 04, 05, 07, 06, 06, 05, 04, 07, 04, 05, 06, 07,
02, 01, 01, 02, 00, 01, 03, 02, 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06,
00, 03, 02, 01, 02, 03, 00, 01, 00, 03, 03, 00, 02, 03, 01, 00, 05, 06, 07, 04, 06, 07, 04, 05,
05, 06, 06, 05, 06, 07, 05, 04, 02, 01, 00, 03, 00, 01, 02, 03, 06, 05, 05, 06, 04, 05, 07, 06,
03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, 02, 01, 00, 03, 00, 01, 02, 03,
02, 01, 01, 02, 00, 01, 03, 02, 03, 00, 01, 02, 00, 01, 02, 03, 03, 00, 00, 03, 00, 01, 03, 02,
04, 07, 06, 05, 06, 07, 04, 05, 00, 03, 03, 00, 02, 03, 01, 00, 01, 02, 03, 00, 02, 03, 00, 01,
05, 06, 06, 05, 06, 07, 05, 04, 04, 07, 06, 05, 06, 07, 04, 05, 04, 07, 07, 04, 06, 07, 05, 04,
01, 02, 03, 00, 02, 03, 00, 01, 01, 02, 02, 01, 02, 03, 01, 00};
/********************** the table above has been generated with this code: ********
#include "util.h"
static void init_opt_select_LUT(void) {
for (int r = 0; r < 256; r++) {
uint8_t r_ls2 = r << 2;
uint8_t r_and_ls2 = r & r_ls2;
uint8_t r_or_ls2 = r | r_ls2;
uint8_t z0 = (r_and_ls2 >> 5) ^ ((r & ~r_ls2) >> 4) ^ ( r_or_ls2 >> 3);
uint8_t z1 = (r_or_ls2 >> 6) ^ ( r_or_ls2 >> 1) ^ (r >> 5) ^ r;
uint8_t z2 = ((r & ~r_ls2) >> 4) ^ (r_and_ls2 >> 3) ^ r;
loclass_opt_select_LUT[r] = (z0 & 4) | (z1 & 2) | (z2 & 1);
}
print_result("", loclass_opt_select_LUT, 256);
}
***********************************************************************************/
#define loclass_opt__select(x, y, r) \
(4 & (((r & (r << 2)) >> 5) ^ ((r & ~(r << 2)) >> 4) ^ ((r | r << 2) >> 3))) | \
(2 & (((r | r << 2) >> 6) ^ ((r | r << 2) >> 1) ^ (r >> 5) ^ r ^ ((x ^ y) << 1))) | \
(1 & (((r & ~(r << 2)) >> 4) ^ ((r & (r << 2)) >> 3) ^ r ^ x))
static void loclass_opt_successor(const uint8_t* k, LoclassState_t* s, uint8_t y) {
uint16_t Tt = s->t & 0xc533;
Tt = Tt ^ (Tt >> 1);
Tt = Tt ^ (Tt >> 4);
Tt = Tt ^ (Tt >> 10);
Tt = Tt ^ (Tt >> 8);
s->t = (s->t >> 1);
s->t |= (Tt ^ (s->r >> 7) ^ (s->r >> 3)) << 15;
uint8_t opt_B = s->b;
opt_B ^= s->b >> 6;
opt_B ^= s->b >> 5;
opt_B ^= s->b >> 4;
s->b = s->b >> 1;
s->b |= (opt_B ^ s->r) << 7;
uint8_t opt_select = loclass_opt_select_LUT[s->r] & 0x04;
opt_select |= (loclass_opt_select_LUT[s->r] ^ ((Tt ^ y) << 1)) & 0x02;
opt_select |= (loclass_opt_select_LUT[s->r] ^ Tt) & 0x01;
uint8_t r = s->r;
s->r = (k[opt_select] ^ s->b) + s->l;
s->l = s->r + r;
}
static void loclass_opt_suc(
const uint8_t* k,
LoclassState_t* s,
const uint8_t* in,
uint8_t length,
bool add32Zeroes) {
for(int i = 0; i < length; i++) {
uint8_t head;
head = in[i];
loclass_opt_successor(k, s, head);
head >>= 1;
loclass_opt_successor(k, s, head);
head >>= 1;
loclass_opt_successor(k, s, head);
head >>= 1;
loclass_opt_successor(k, s, head);
head >>= 1;
loclass_opt_successor(k, s, head);
head >>= 1;
loclass_opt_successor(k, s, head);
head >>= 1;
loclass_opt_successor(k, s, head);
head >>= 1;
loclass_opt_successor(k, s, head);
}
//For tag MAC, an additional 32 zeroes
if(add32Zeroes) {
for(int i = 0; i < 16; i++) {
loclass_opt_successor(k, s, 0);
loclass_opt_successor(k, s, 0);
}
}
}
static void loclass_opt_output(const uint8_t* k, LoclassState_t* s, uint8_t* buffer) {
for(uint8_t times = 0; times < 4; times++) {
uint8_t bout = 0;
bout |= (s->r & 0x4) >> 2;
loclass_opt_successor(k, s, 0);
bout |= (s->r & 0x4) >> 1;
loclass_opt_successor(k, s, 0);
bout |= (s->r & 0x4);
loclass_opt_successor(k, s, 0);
bout |= (s->r & 0x4) << 1;
loclass_opt_successor(k, s, 0);
bout |= (s->r & 0x4) << 2;
loclass_opt_successor(k, s, 0);
bout |= (s->r & 0x4) << 3;
loclass_opt_successor(k, s, 0);
bout |= (s->r & 0x4) << 4;
loclass_opt_successor(k, s, 0);
bout |= (s->r & 0x4) << 5;
loclass_opt_successor(k, s, 0);
buffer[times] = bout;
}
}
static void loclass_opt_MAC(uint8_t* k, uint8_t* input, uint8_t* out) {
LoclassState_t _init = {
((k[0] ^ 0x4c) + 0xEC) & 0xFF, // l
((k[0] ^ 0x4c) + 0x21) & 0xFF, // r
0x4c, // b
0xE012 // t
};
loclass_opt_suc(k, &_init, input, 12, false);
loclass_opt_output(k, &_init, out);
}
static void loclass_opt_MAC_N(uint8_t* k, uint8_t* input, uint8_t in_size, uint8_t* out) {
LoclassState_t _init = {
((k[0] ^ 0x4c) + 0xEC) & 0xFF, // l
((k[0] ^ 0x4c) + 0x21) & 0xFF, // r
0x4c, // b
0xE012 // t
};
loclass_opt_suc(k, &_init, input, in_size, false);
loclass_opt_output(k, &_init, out);
}
void loclass_opt_doReaderMAC(uint8_t* cc_nr_p, uint8_t* div_key_p, uint8_t mac[4]) {
uint8_t dest[] = {0, 0, 0, 0, 0, 0, 0, 0};
loclass_opt_MAC(div_key_p, cc_nr_p, dest);
memcpy(mac, dest, 4);
}
void loclass_opt_doReaderMAC_2(
LoclassState_t _init,
uint8_t* nr,
uint8_t mac[4],
const uint8_t* div_key_p) {
loclass_opt_suc(div_key_p, &_init, nr, 4, false);
loclass_opt_output(div_key_p, &_init, mac);
}
void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]) {
uint8_t dest[] = {0, 0, 0, 0, 0, 0, 0, 0};
loclass_opt_MAC_N(div_key_p, in_p, in_size, dest);
memcpy(mac, dest, 4);
}
void loclass_opt_doTagMAC(uint8_t* cc_p, const uint8_t* div_key_p, uint8_t mac[4]) {
LoclassState_t _init = {
((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF, // l
((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF, // r
0x4c, // b
0xE012 // t
};
loclass_opt_suc(div_key_p, &_init, cc_p, 12, true);
loclass_opt_output(div_key_p, &_init, mac);
}
/**
* The tag MAC can be divided (both can, but no point in dividing the reader mac) into
* two functions, since the first 8 bytes are known, we can pre-calculate the state
* reached after feeding CC to the cipher.
* @param cc_p
* @param div_key_p
* @return the cipher state
*/
LoclassState_t loclass_opt_doTagMAC_1(uint8_t* cc_p, const uint8_t* div_key_p) {
LoclassState_t _init = {
((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF, // l
((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF, // r
0x4c, // b
0xE012 // t
};
loclass_opt_suc(div_key_p, &_init, cc_p, 8, false);
return _init;
}
/**
* The second part of the tag MAC calculation, since the CC is already calculated into the state,
* this function is fed only the NR, and internally feeds the remaining 32 0-bits to generate the tag
* MAC response.
* @param _init - precalculated cipher state
* @param nr - the reader challenge
* @param mac - where to store the MAC
* @param div_key_p - the key to use
*/
void loclass_opt_doTagMAC_2(
LoclassState_t _init,
uint8_t* nr,
uint8_t mac[4],
const uint8_t* div_key_p) {
loclass_opt_suc(div_key_p, &_init, nr, 4, true);
loclass_opt_output(div_key_p, &_init, mac);
}
void loclass_iclass_calc_div_key(uint8_t* csn, uint8_t* key, uint8_t* div_key, bool elite) {
if(elite) {
uint8_t keytable[128] = {0};
uint8_t key_index[8] = {0};
uint8_t key_sel[8] = {0};
uint8_t key_sel_p[8] = {0};
loclass_hash2(key, keytable);
loclass_hash1(csn, key_index);
for(uint8_t i = 0; i < 8; i++) key_sel[i] = keytable[key_index[i]];
//Permute from iclass format to standard format
loclass_permutekey_rev(key_sel, key_sel_p);
loclass_diversifyKey(csn, key_sel_p, div_key);
} else {
loclass_diversifyKey(csn, key, div_key);
}
}
@@ -0,0 +1,98 @@
//-----------------------------------------------------------------------------
// Borrowed initially from https://github.com/holiman/loclass
// More recently from https://github.com/RfidResearchGroup/proxmark3
// Copyright (C) 2014 Martin Holst Swende
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// WARNING
//
// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY.
//
// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL
// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL,
// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES.
//
// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS.
//-----------------------------------------------------------------------------
// It is a reconstruction of the cipher engine used in iClass, and RFID techology.
//
// The implementation is based on the work performed by
// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and
// Milosch Meriac in the paper "Dismantling IClass".
//-----------------------------------------------------------------------------
#ifndef OPTIMIZED_CIPHER_H
#define OPTIMIZED_CIPHER_H
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
/**
* Definition 1 (Cipher state). A cipher state of iClass s is an element of F 40/2
* consisting of the following four components:
* 1. the left register l = (l 0 . . . l 7 ) ∈ F 8/2 ;
* 2. the right register r = (r 0 . . . r 7 ) ∈ F 8/2 ;
* 3. the top register t = (t 0 . . . t 15 ) ∈ F 16/2 .
* 4. the bottom register b = (b 0 . . . b 7 ) ∈ F 8/2 .
**/
typedef struct {
uint8_t l;
uint8_t r;
uint8_t b;
uint16_t t;
} LoclassState_t;
/** The reader MAC is MAC(key, CC * NR )
**/
void loclass_opt_doReaderMAC(uint8_t* cc_nr_p, uint8_t* div_key_p, uint8_t mac[4]);
void loclass_opt_doReaderMAC_2(
LoclassState_t _init,
uint8_t* nr,
uint8_t mac[4],
const uint8_t* div_key_p);
/**
* The tag MAC is MAC(key, CC * NR * 32x0))
*/
void loclass_opt_doTagMAC(uint8_t* cc_p, const uint8_t* div_key_p, uint8_t mac[4]);
/**
* The tag MAC can be divided (both can, but no point in dividing the reader mac) into
* two functions, since the first 8 bytes are known, we can pre-calculate the state
* reached after feeding CC to the cipher.
* @param cc_p
* @param div_key_p
* @return the cipher state
*/
LoclassState_t loclass_opt_doTagMAC_1(uint8_t* cc_p, const uint8_t* div_key_p);
/**
* The second part of the tag MAC calculation, since the CC is already calculated into the state,
* this function is fed only the NR, and internally feeds the remaining 32 0-bits to generate the tag
* MAC response.
* @param _init - precalculated cipher state
* @param nr - the reader challenge
* @param mac - where to store the MAC
* @param div_key_p - the key to use
*/
void loclass_opt_doTagMAC_2(
LoclassState_t _init,
uint8_t* nr,
uint8_t mac[4],
const uint8_t* div_key_p);
void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]);
void loclass_iclass_calc_div_key(uint8_t* csn, uint8_t* key, uint8_t* div_key, bool elite);
#endif // OPTIMIZED_CIPHER_H
@@ -0,0 +1,136 @@
//-----------------------------------------------------------------------------
// Borrowed initially from https://github.com/holiman/loclass
// Copyright (C) 2014 Martin Holst Swende
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// WARNING
//
// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY.
//
// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL
// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL,
// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES.
//
// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS.
//-----------------------------------------------------------------------------
// It is a reconstruction of the cipher engine used in iClass, and RFID techology.
//
// The implementation is based on the work performed by
// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and
// Milosch Meriac in the paper "Dismantling IClass".
//-----------------------------------------------------------------------------
#include "optimized_cipherutils.h"
#include <stdint.h>
/**
*
* @brief Return and remove the first bit (x0) in the stream : <x0 x1 x2 x3 ... xn >
* @param stream
* @return
*/
bool loclass_headBit(LoclassBitstreamIn_t* stream) {
int bytepos = stream->position >> 3; // divide by 8
int bitpos = (stream->position++) & 7; // mask out 00000111
return (*(stream->buffer + bytepos) >> (7 - bitpos)) & 1;
}
/**
* @brief Return and remove the last bit (xn) in the stream: <x0 x1 x2 ... xn>
* @param stream
* @return
*/
bool loclass_tailBit(LoclassBitstreamIn_t* stream) {
int bitpos = stream->numbits - 1 - (stream->position++);
int bytepos = bitpos >> 3;
bitpos &= 7;
return (*(stream->buffer + bytepos) >> (7 - bitpos)) & 1;
}
/**
* @brief Pushes bit onto the stream
* @param stream
* @param bit
*/
void loclass_pushBit(LoclassBitstreamOut_t* stream, bool bit) {
int bytepos = stream->position >> 3; // divide by 8
int bitpos = stream->position & 7;
*(stream->buffer + bytepos) |= (bit) << (7 - bitpos);
stream->position++;
stream->numbits++;
}
/**
* @brief Pushes the lower six bits onto the stream
* as b0 b1 b2 b3 b4 b5 b6
* @param stream
* @param bits
*/
void loclass_push6bits(LoclassBitstreamOut_t* stream, uint8_t bits) {
loclass_pushBit(stream, bits & 0x20);
loclass_pushBit(stream, bits & 0x10);
loclass_pushBit(stream, bits & 0x08);
loclass_pushBit(stream, bits & 0x04);
loclass_pushBit(stream, bits & 0x02);
loclass_pushBit(stream, bits & 0x01);
}
/**
* @brief loclass_bitsLeft
* @param stream
* @return number of bits left in stream
*/
int loclass_bitsLeft(LoclassBitstreamIn_t* stream) {
return stream->numbits - stream->position;
}
/**
* @brief numBits
* @param stream
* @return Number of bits stored in stream
*/
void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t* dest) {
while(len--) {
dest[len] = (uint8_t)n;
n >>= 8;
}
}
uint64_t loclass_x_bytes_to_num(uint8_t* src, size_t len) {
uint64_t num = 0;
while(len--) {
num = (num << 8) | (*src);
src++;
}
return num;
}
uint8_t loclass_reversebytes(uint8_t b) {
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}
void loclass_reverse_arraybytes(uint8_t* arr, size_t len) {
uint8_t i;
for(i = 0; i < len; i++) {
arr[i] = loclass_reversebytes(arr[i]);
}
}
void loclass_reverse_arraycopy(uint8_t* arr, uint8_t* dest, size_t len) {
uint8_t i;
for(i = 0; i < len; i++) {
dest[i] = loclass_reversebytes(arr[i]);
}
}
@@ -0,0 +1,64 @@
//-----------------------------------------------------------------------------
// Borrowed initially from https://github.com/holiman/loclass
// More recently from https://github.com/RfidResearchGroup/proxmark3
// Copyright (C) 2014 Martin Holst Swende
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// WARNING
//
// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY.
//
// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL
// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL,
// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES.
//
// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS.
//-----------------------------------------------------------------------------
// It is a reconstruction of the cipher engine used in iClass, and RFID techology.
//
// The implementation is based on the work performed by
// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and
// Milosch Meriac in the paper "Dismantling IClass".
//-----------------------------------------------------------------------------
#ifndef CIPHERUTILS_H
#define CIPHERUTILS_H
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
typedef struct {
uint8_t* buffer;
uint8_t numbits;
uint8_t position;
} LoclassBitstreamIn_t;
typedef struct {
uint8_t* buffer;
uint8_t numbits;
uint8_t position;
} LoclassBitstreamOut_t;
bool loclass_headBit(LoclassBitstreamIn_t* stream);
bool loclass_tailBit(LoclassBitstreamIn_t* stream);
void loclass_pushBit(LoclassBitstreamOut_t* stream, bool bit);
int loclass_bitsLeft(LoclassBitstreamIn_t* stream);
void loclass_push6bits(LoclassBitstreamOut_t* stream, uint8_t bits);
void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t* dest);
uint64_t loclass_x_bytes_to_num(uint8_t* src, size_t len);
uint8_t loclass_reversebytes(uint8_t b);
void loclass_reverse_arraybytes(uint8_t* arr, size_t len);
void loclass_reverse_arraycopy(uint8_t* arr, uint8_t* dest, size_t len);
#endif // CIPHERUTILS_H
@@ -0,0 +1,232 @@
//-----------------------------------------------------------------------------
// Borrowed initially from https://github.com/holiman/loclass
// Copyright (C) 2014 Martin Holst Swende
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// WARNING
//
// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY.
//
// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL
// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL,
// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES.
//
// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS.
//-----------------------------------------------------------------------------
// It is a reconstruction of the cipher engine used in iClass, and RFID techology.
//
// The implementation is based on the work performed by
// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and
// Milosch Meriac in the paper "Dismantling IClass".
//-----------------------------------------------------------------------------
#include "optimized_elite.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <mbedtls/des.h>
#include "optimized_ikeys.h"
/**
* @brief Permutes a key from standard NIST format to Iclass specific format
* from http://www.proxmark.org/forum/viewtopic.php?pid=11220#p11220
*
* If you loclass_permute [6c 8d 44 f9 2a 2d 01 bf] you get [8a 0d b9 88 bb a7 90 ea] as shown below.
*
* 1 0 1 1 1 1 1 1 bf
* 0 0 0 0 0 0 0 1 01
* 0 0 1 0 1 1 0 1 2d
* 0 0 1 0 1 0 1 0 2a
* 1 1 1 1 1 0 0 1 f9
* 0 1 0 0 0 1 0 0 44
* 1 0 0 0 1 1 0 1 8d
* 0 1 1 0 1 1 0 0 6c
*
* 8 0 b 8 b a 9 e
* a d 9 8 b 7 0 a
*
* @param key
* @param dest
*/
void loclass_permutekey(const uint8_t key[8], uint8_t dest[8]) {
int i;
for(i = 0; i < 8; i++) {
dest[i] = (((key[7] & (0x80 >> i)) >> (7 - i)) << 7) |
(((key[6] & (0x80 >> i)) >> (7 - i)) << 6) |
(((key[5] & (0x80 >> i)) >> (7 - i)) << 5) |
(((key[4] & (0x80 >> i)) >> (7 - i)) << 4) |
(((key[3] & (0x80 >> i)) >> (7 - i)) << 3) |
(((key[2] & (0x80 >> i)) >> (7 - i)) << 2) |
(((key[1] & (0x80 >> i)) >> (7 - i)) << 1) |
(((key[0] & (0x80 >> i)) >> (7 - i)) << 0);
}
}
/**
* Permutes a key from iclass specific format to NIST format
* @brief loclass_permutekey_rev
* @param key
* @param dest
*/
void loclass_permutekey_rev(const uint8_t key[8], uint8_t dest[8]) {
int i;
for(i = 0; i < 8; i++) {
dest[7 - i] = (((key[0] & (0x80 >> i)) >> (7 - i)) << 7) |
(((key[1] & (0x80 >> i)) >> (7 - i)) << 6) |
(((key[2] & (0x80 >> i)) >> (7 - i)) << 5) |
(((key[3] & (0x80 >> i)) >> (7 - i)) << 4) |
(((key[4] & (0x80 >> i)) >> (7 - i)) << 3) |
(((key[5] & (0x80 >> i)) >> (7 - i)) << 2) |
(((key[6] & (0x80 >> i)) >> (7 - i)) << 1) |
(((key[7] & (0x80 >> i)) >> (7 - i)) << 0);
}
}
/**
* Helper function for loclass_hash1
* @brief loclass_rr
* @param val
* @return
*/
static uint8_t loclass_rr(uint8_t val) {
return val >> 1 | ((val & 1) << 7);
}
/**
* Helper function for loclass_hash1
* @brief rl
* @param val
* @return
*/
static uint8_t loclass_rl(uint8_t val) {
return val << 1 | ((val & 0x80) >> 7);
}
/**
* Helper function for loclass_hash1
* @brief loclass_swap
* @param val
* @return
*/
static uint8_t loclass_swap(uint8_t val) {
return ((val >> 4) & 0xFF) | ((val & 0xFF) << 4);
}
/**
* Hash1 takes CSN as input, and determines what bytes in the keytable will be used
* when constructing the K_sel.
* @param csn the CSN used
* @param k output
*/
void loclass_hash1(const uint8_t csn[], uint8_t k[]) {
k[0] = csn[0] ^ csn[1] ^ csn[2] ^ csn[3] ^ csn[4] ^ csn[5] ^ csn[6] ^ csn[7];
k[1] = csn[0] + csn[1] + csn[2] + csn[3] + csn[4] + csn[5] + csn[6] + csn[7];
k[2] = loclass_rr(loclass_swap(csn[2] + k[1]));
k[3] = loclass_rl(loclass_swap(csn[3] + k[0]));
k[4] = ~loclass_rr(csn[4] + k[2]) + 1;
k[5] = ~loclass_rl(csn[5] + k[3]) + 1;
k[6] = loclass_rr(csn[6] + (k[4] ^ 0x3c));
k[7] = loclass_rl(csn[7] + (k[5] ^ 0xc3));
k[7] &= 0x7F;
k[6] &= 0x7F;
k[5] &= 0x7F;
k[4] &= 0x7F;
k[3] &= 0x7F;
k[2] &= 0x7F;
k[1] &= 0x7F;
k[0] &= 0x7F;
}
/**
Definition 14. Define the rotate key function loclass_rk : (F 82 ) 8 × N → (F 82 ) 8 as
loclass_rk(x [0] . . . x [7] , 0) = x [0] . . . x [7]
loclass_rk(x [0] . . . x [7] , n + 1) = loclass_rk(loclass_rl(x [0] ) . . . loclass_rl(x [7] ), n)
**/
static void loclass_rk(uint8_t* key, uint8_t n, uint8_t* outp_key) {
memcpy(outp_key, key, 8);
uint8_t j;
while(n-- > 0) {
for(j = 0; j < 8; j++) outp_key[j] = loclass_rl(outp_key[j]);
}
return;
}
static mbedtls_des_context loclass_ctx_enc;
static mbedtls_des_context loclass_ctx_dec;
static void loclass_desdecrypt_iclass(uint8_t* iclass_key, uint8_t* input, uint8_t* output) {
uint8_t key_std_format[8] = {0};
loclass_permutekey_rev(iclass_key, key_std_format);
mbedtls_des_setkey_dec(&loclass_ctx_dec, key_std_format);
mbedtls_des_crypt_ecb(&loclass_ctx_dec, input, output);
}
static void loclass_desencrypt_iclass(uint8_t* iclass_key, uint8_t* input, uint8_t* output) {
uint8_t key_std_format[8] = {0};
loclass_permutekey_rev(iclass_key, key_std_format);
mbedtls_des_setkey_enc(&loclass_ctx_enc, key_std_format);
mbedtls_des_crypt_ecb(&loclass_ctx_enc, input, output);
}
/**
* @brief Insert uint8_t[8] custom master key to calculate hash2 and return key_select.
* @param key unpermuted custom key
* @param loclass_hash1 loclass_hash1
* @param key_sel output key_sel=h[loclass_hash1[i]]
*/
void hash2(uint8_t* key64, uint8_t* outp_keytable) {
/**
*Expected:
* High Security Key Table
00 F1 35 59 A1 0D 5A 26 7F 18 60 0B 96 8A C0 25 C1
10 BF A1 3B B0 FF 85 28 75 F2 1F C6 8F 0E 74 8F 21
20 14 7A 55 16 C8 A9 7D B3 13 0C 5D C9 31 8D A9 B2
30 A3 56 83 0F 55 7E DE 45 71 21 D2 6D C1 57 1C 9C
40 78 2F 64 51 42 7B 64 30 FA 26 51 76 D3 E0 FB B6
50 31 9F BF 2F 7E 4F 94 B4 BD 4F 75 91 E3 1B EB 42
60 3F 88 6F B8 6C 2C 93 0D 69 2C D5 20 3C C1 61 95
70 43 08 A0 2F FE B3 26 D7 98 0B 34 7B 47 70 A0 AB
**** The 64-bit HS Custom Key Value = 5B7C62C491C11B39 ******/
uint8_t key64_negated[8] = {0};
uint8_t z[8][8] = {{0}, {0}};
uint8_t temp_output[8] = {0};
//calculate complement of key
int i;
for(i = 0; i < 8; i++) key64_negated[i] = ~key64[i];
// Once again, key is on iclass-format
loclass_desencrypt_iclass(key64, key64_negated, z[0]);
uint8_t y[8][8] = {{0}, {0}};
// y[0]=DES_dec(z[0],~key)
// Once again, key is on iclass-format
loclass_desdecrypt_iclass(z[0], key64_negated, y[0]);
for(i = 1; i < 8; i++) {
loclass_rk(key64, i, temp_output);
loclass_desdecrypt_iclass(temp_output, z[i - 1], z[i]);
loclass_desencrypt_iclass(temp_output, y[i - 1], y[i]);
}
if(outp_keytable != NULL) {
for(i = 0; i < 8; i++) {
memcpy(outp_keytable + i * 16, y[i], 8);
memcpy(outp_keytable + 8 + i * 16, z[i], 8);
}
}
}
@@ -0,0 +1,58 @@
//-----------------------------------------------------------------------------
// Borrowed initially from https://github.com/holiman/loclass
// More recently from https://github.com/RfidResearchGroup/proxmark3
// Copyright (C) 2014 Martin Holst Swende
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// WARNING
//
// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY.
//
// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL
// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL,
// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES.
//
// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS.
//-----------------------------------------------------------------------------
// It is a reconstruction of the cipher engine used in iClass, and RFID techology.
//
// The implementation is based on the work performed by
// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and
// Milosch Meriac in the paper "Dismantling IClass".
//-----------------------------------------------------------------------------
#ifndef ELITE_CRACK_H
#define ELITE_CRACK_H
#include <stdint.h>
#include <stdlib.h>
void loclass_permutekey(const uint8_t key[8], uint8_t dest[8]);
/**
* Permutes a key from iclass specific format to NIST format
* @brief loclass_permutekey_rev
* @param key
* @param dest
*/
void loclass_permutekey_rev(const uint8_t key[8], uint8_t dest[8]);
/**
* Hash1 takes CSN as input, and determines what bytes in the keytable will be used
* when constructing the K_sel.
* @param csn the CSN used
* @param k output
*/
void loclass_hash1(const uint8_t* csn, uint8_t* k);
void loclass_hash2(uint8_t* key64, uint8_t* outp_keytable);
#endif
@@ -0,0 +1,320 @@
//-----------------------------------------------------------------------------
// Borrowed initially from https://github.com/holiman/loclass
// Copyright (C) 2014 Martin Holst Swende
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// WARNING
//
// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY.
//
// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL
// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL,
// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES.
//
// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS.
//-----------------------------------------------------------------------------
// It is a reconstruction of the cipher engine used in iClass, and RFID techology.
//
// The implementation is based on the work performed by
// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and
// Milosch Meriac in the paper "Dismantling IClass".
//-----------------------------------------------------------------------------
/**
From "Dismantling iclass":
This section describes in detail the built-in key diversification algorithm of iClass.
Besides the obvious purpose of deriving a card key from a master key, this
algorithm intends to circumvent weaknesses in the cipher by preventing the
usage of certain weak keys. In order to compute a diversified key, the iClass
reader first encrypts the card identity id with the master key K, using single
DES. The resulting ciphertext is then input to a function called loclass_hash0 which
outputs the diversified key k.
k = loclass_hash0(DES enc (id, K))
Here the DES encryption of id with master key K outputs a cryptogram c
of 64 bits. These 64 bits are divided as c = x, y, z [0] , . . . , z [7] ∈ F 82 × F 82 × (F 62 ) 8
which is used as input to the loclass_hash0 function. This function introduces some
obfuscation by performing a number of permutations, complement and modulo
operations, see Figure 2.5. Besides that, it checks for and removes patterns like
similar key bytes, which could produce a strong bias in the cipher. Finally, the
output of loclass_hash0 is the diversified card key k = k [0] , . . . , k [7] ∈ (F 82 ) 8 .
**/
#include "optimized_ikeys.h"
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>
#include <mbedtls/des.h>
#include "optimized_cipherutils.h"
static const uint8_t loclass_pi[35] = {0x0F, 0x17, 0x1B, 0x1D, 0x1E, 0x27, 0x2B, 0x2D, 0x2E,
0x33, 0x35, 0x39, 0x36, 0x3A, 0x3C, 0x47, 0x4B, 0x4D,
0x4E, 0x53, 0x55, 0x56, 0x59, 0x5A, 0x5C, 0x63, 0x65,
0x66, 0x69, 0x6A, 0x6C, 0x71, 0x72, 0x74, 0x78};
/**
* @brief The key diversification algorithm uses 6-bit bytes.
* This implementation uses 64 bit uint to pack seven of them into one
* variable. When they are there, they are placed as follows:
* XXXX XXXX N0 .... N7, occupying the last 48 bits.
*
* This function picks out one from such a collection
* @param all
* @param n bitnumber
* @return
*/
static uint8_t loclass_getSixBitByte(uint64_t c, int n) {
return (c >> (42 - 6 * n)) & 0x3F;
}
/**
* @brief Puts back a six-bit 'byte' into a uint64_t.
* @param c buffer
* @param z the value to place there
* @param n bitnumber.
*/
static void loclass_pushbackSixBitByte(uint64_t* c, uint8_t z, int n) {
//0x XXXX YYYY ZZZZ ZZZZ ZZZZ
// ^z0 ^z7
//z0: 1111 1100 0000 0000
uint64_t masked = z & 0x3F;
uint64_t eraser = 0x3F;
masked <<= 42 - 6 * n;
eraser <<= 42 - 6 * n;
//masked <<= 6*n;
//eraser <<= 6*n;
eraser = ~eraser;
(*c) &= eraser;
(*c) |= masked;
}
/**
* @brief Swaps the z-values.
* If the input value has format XYZ0Z1...Z7, the output will have the format
* XYZ7Z6...Z0 instead
* @param c
* @return
*/
static uint64_t loclass_swapZvalues(uint64_t c) {
uint64_t newz = 0;
loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 0), 7);
loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 1), 6);
loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 2), 5);
loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 3), 4);
loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 4), 3);
loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 5), 2);
loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 6), 1);
loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 7), 0);
newz |= (c & 0xFFFF000000000000);
return newz;
}
/**
* @return 4 six-bit bytes chunked into a uint64_t,as 00..00a0a1a2a3
*/
static uint64_t loclass_ck(int i, int j, uint64_t z) {
if(i == 1 && j == -1) {
// loclass_ck(1, 1, z [0] . . . z [3] ) = z [0] . . . z [3]
return z;
} else if(j == -1) {
// loclass_ck(i, 1, z [0] . . . z [3] ) = loclass_ck(i 1, i 2, z [0] . . . z [3] )
return loclass_ck(i - 1, i - 2, z);
}
if(loclass_getSixBitByte(z, i) == loclass_getSixBitByte(z, j)) {
//loclass_ck(i, j 1, z [0] . . . z [i] ← j . . . z [3] )
uint64_t newz = 0;
int c;
for(c = 0; c < 4; c++) {
uint8_t val = loclass_getSixBitByte(z, c);
if(c == i)
loclass_pushbackSixBitByte(&newz, j, c);
else
loclass_pushbackSixBitByte(&newz, val, c);
}
return loclass_ck(i, j - 1, newz);
} else {
return loclass_ck(i, j - 1, z);
}
}
/**
Definition 8.
Let the function check : (F 62 ) 8 → (F 62 ) 8 be defined as
check(z [0] . . . z [7] ) = loclass_ck(3, 2, z [0] . . . z [3] ) · loclass_ck(3, 2, z [4] . . . z [7] )
where loclass_ck : N × N × (F 62 ) 4 → (F 62 ) 4 is defined as
loclass_ck(1, 1, z [0] . . . z [3] ) = z [0] . . . z [3]
loclass_ck(i, 1, z [0] . . . z [3] ) = loclass_ck(i 1, i 2, z [0] . . . z [3] )
loclass_ck(i, j, z [0] . . . z [3] ) =
loclass_ck(i, j 1, z [0] . . . z [i] ← j . . . z [3] ), if z [i] = z [j] ;
loclass_ck(i, j 1, z [0] . . . z [3] ), otherwise
otherwise.
**/
static uint64_t loclass_check(uint64_t z) {
//These 64 bits are divided as c = x, y, z [0] , . . . , z [7]
// loclass_ck(3, 2, z [0] . . . z [3] )
uint64_t ck1 = loclass_ck(3, 2, z);
// loclass_ck(3, 2, z [4] . . . z [7] )
uint64_t ck2 = loclass_ck(3, 2, z << 24);
//The loclass_ck function will place the values
// in the middle of z.
ck1 &= 0x00000000FFFFFF000000;
ck2 &= 0x00000000FFFFFF000000;
return ck1 | ck2 >> 24;
}
static void loclass_permute(
LoclassBitstreamIn_t* p_in,
uint64_t z,
int l,
int r,
LoclassBitstreamOut_t* out) {
if(loclass_bitsLeft(p_in) == 0) return;
bool pn = loclass_tailBit(p_in);
if(pn) { // pn = 1
uint8_t zl = loclass_getSixBitByte(z, l);
loclass_push6bits(out, zl + 1);
loclass_permute(p_in, z, l + 1, r, out);
} else { // otherwise
uint8_t zr = loclass_getSixBitByte(z, r);
loclass_push6bits(out, zr);
loclass_permute(p_in, z, l, r + 1, out);
}
}
/**
* @brief
*Definition 11. Let the function loclass_hash0 : F 82 × F 82 × (F 62 ) 8 → (F 82 ) 8 be defined as
* loclass_hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where
* z'[i] = (z[i] mod (63-i)) + i i = 0...3
* z'[i+4] = (z[i+4] mod (64-i)) + i i = 0...3
* ẑ = check(z');
* @param c
* @param k this is where the diversified key is put (should be 8 bytes)
* @return
*/
void loclass_hash0(uint64_t c, uint8_t k[8]) {
c = loclass_swapZvalues(c);
//These 64 bits are divided as c = x, y, z [0] , . . . , z [7]
// x = 8 bits
// y = 8 bits
// z0-z7 6 bits each : 48 bits
uint8_t x = (c & 0xFF00000000000000) >> 56;
uint8_t y = (c & 0x00FF000000000000) >> 48;
uint64_t zP = 0;
for(int n = 0; n < 4; n++) {
uint8_t zn = loclass_getSixBitByte(c, n);
uint8_t zn4 = loclass_getSixBitByte(c, n + 4);
uint8_t _zn = (zn % (63 - n)) + n;
uint8_t _zn4 = (zn4 % (64 - n)) + n;
loclass_pushbackSixBitByte(&zP, _zn, n);
loclass_pushbackSixBitByte(&zP, _zn4, n + 4);
}
uint64_t zCaret = loclass_check(zP);
uint8_t p = loclass_pi[x % 35];
if(x & 1) //Check if x7 is 1
p = ~p;
LoclassBitstreamIn_t p_in = {&p, 8, 0};
uint8_t outbuffer[] = {0, 0, 0, 0, 0, 0, 0, 0};
LoclassBitstreamOut_t out = {outbuffer, 0, 0};
loclass_permute(&p_in, zCaret, 0, 4, &out); //returns 48 bits? or 6 8-bytes
//Out is now a buffer containing six-bit bytes, should be 48 bits
// if all went well
//Shift z-values down onto the lower segment
uint64_t zTilde = loclass_x_bytes_to_num(outbuffer, sizeof(outbuffer));
zTilde >>= 16;
for(int i = 0; i < 8; i++) {
// the key on index i is first a bit from y
// then six bits from z,
// then a bit from p
// Init with zeroes
k[i] = 0;
// First, place yi leftmost in k
//k[i] |= (y << i) & 0x80 ;
// First, place y(7-i) leftmost in k
k[i] |= (y << (7 - i)) & 0x80;
uint8_t zTilde_i = loclass_getSixBitByte(zTilde, i);
// zTildeI is now on the form 00XXXXXX
// with one leftshift, it'll be
// 0XXXXXX0
// So after leftshift, we can OR it into k
// However, when doing complement, we need to
// again MASK 0XXXXXX0 (0x7E)
zTilde_i <<= 1;
//Finally, add bit from p or p-mod
//Shift bit i into rightmost location (mask only after complement)
uint8_t p_i = p >> i & 0x1;
if(k[i]) { // yi = 1
k[i] |= ~zTilde_i & 0x7E;
k[i] |= p_i & 1;
k[i] += 1;
} else { // otherwise
k[i] |= zTilde_i & 0x7E;
k[i] |= (~p_i) & 1;
}
}
}
/**
* @brief Performs Elite-class key diversification
* @param csn
* @param key
* @param div_key
*/
void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key) {
mbedtls_des_context loclass_ctx_enc;
// Prepare the DES key
mbedtls_des_setkey_enc(&loclass_ctx_enc, key);
uint8_t crypted_csn[8] = {0};
// Calculate DES(CSN, KEY)
mbedtls_des_crypt_ecb(&loclass_ctx_enc, csn, crypted_csn);
//Calculate HASH0(DES))
uint64_t c_csn = loclass_x_bytes_to_num(crypted_csn, sizeof(crypted_csn));
loclass_hash0(c_csn, div_key);
}
@@ -0,0 +1,66 @@
//-----------------------------------------------------------------------------
// Borrowed initially from https://github.com/holiman/loclass
// More recently from https://github.com/RfidResearchGroup/proxmark3
// Copyright (C) 2014 Martin Holst Swende
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// WARNING
//
// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY.
//
// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL
// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL,
// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES.
//
// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS.
//-----------------------------------------------------------------------------
// It is a reconstruction of the cipher engine used in iClass, and RFID techology.
//
// The implementation is based on the work performed by
// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and
// Milosch Meriac in the paper "Dismantling IClass".
//-----------------------------------------------------------------------------
#ifndef IKEYS_H
#define IKEYS_H
#include <inttypes.h>
/**
* @brief
*Definition 11. Let the function loclass_hash0 : F 82 × F 82 × (F 62 ) 8 → (F 82 ) 8 be defined as
* loclass_hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where
* z'[i] = (z[i] mod (63-i)) + i i = 0...3
* z'[i+4] = (z[i+4] mod (64-i)) + i i = 0...3
* ẑ = check(z');
* @param c
* @param k this is where the diversified key is put (should be 8 bytes)
* @return
*/
void loclass_hash0(uint64_t c, uint8_t k[8]);
/**
* @brief Performs Elite-class key diversification
* @param csn
* @param key
* @param div_key
*/
void loclass_diversifyKey(uint8_t* csn, const uint8_t* key, uint8_t* div_key);
/**
* @brief Permutes a key from standard NIST format to Iclass specific format
* @param key
* @param dest
*/
#endif // IKEYS_H
+185
View File
@@ -0,0 +1,185 @@
#include "picopass_i.h"
#define TAG "PicoPass"
bool picopass_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
Picopass* picopass = context;
return scene_manager_handle_custom_event(picopass->scene_manager, event);
}
bool picopass_back_event_callback(void* context) {
furi_assert(context);
Picopass* picopass = context;
return scene_manager_handle_back_event(picopass->scene_manager);
}
void picopass_tick_event_callback(void* context) {
furi_assert(context);
Picopass* picopass = context;
scene_manager_handle_tick_event(picopass->scene_manager);
}
Picopass* picopass_alloc() {
Picopass* picopass = malloc(sizeof(Picopass));
picopass->worker = picopass_worker_alloc();
picopass->view_dispatcher = view_dispatcher_alloc();
picopass->scene_manager = scene_manager_alloc(&picopass_scene_handlers, picopass);
view_dispatcher_enable_queue(picopass->view_dispatcher);
view_dispatcher_set_event_callback_context(picopass->view_dispatcher, picopass);
view_dispatcher_set_custom_event_callback(
picopass->view_dispatcher, picopass_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
picopass->view_dispatcher, picopass_back_event_callback);
view_dispatcher_set_tick_event_callback(
picopass->view_dispatcher, picopass_tick_event_callback, 100);
// Picopass device
picopass->dev = picopass_device_alloc();
// Open GUI record
picopass->gui = furi_record_open(RECORD_GUI);
view_dispatcher_attach_to_gui(
picopass->view_dispatcher, picopass->gui, ViewDispatcherTypeFullscreen);
// Open Notification record
picopass->notifications = furi_record_open(RECORD_NOTIFICATION);
// Submenu
picopass->submenu = submenu_alloc();
view_dispatcher_add_view(
picopass->view_dispatcher, PicopassViewMenu, submenu_get_view(picopass->submenu));
// Popup
picopass->popup = popup_alloc();
view_dispatcher_add_view(
picopass->view_dispatcher, PicopassViewPopup, popup_get_view(picopass->popup));
// Loading
picopass->loading = loading_alloc();
view_dispatcher_add_view(
picopass->view_dispatcher, PicopassViewLoading, loading_get_view(picopass->loading));
// Text Input
picopass->text_input = text_input_alloc();
view_dispatcher_add_view(
picopass->view_dispatcher,
PicopassViewTextInput,
text_input_get_view(picopass->text_input));
// Custom Widget
picopass->widget = widget_alloc();
view_dispatcher_add_view(
picopass->view_dispatcher, PicopassViewWidget, widget_get_view(picopass->widget));
return picopass;
}
void picopass_free(Picopass* picopass) {
furi_assert(picopass);
// Picopass device
picopass_device_free(picopass->dev);
picopass->dev = NULL;
// Submenu
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewMenu);
submenu_free(picopass->submenu);
// Popup
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewPopup);
popup_free(picopass->popup);
// Loading
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewLoading);
loading_free(picopass->loading);
// TextInput
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewTextInput);
text_input_free(picopass->text_input);
// Custom Widget
view_dispatcher_remove_view(picopass->view_dispatcher, PicopassViewWidget);
widget_free(picopass->widget);
// Worker
picopass_worker_stop(picopass->worker);
picopass_worker_free(picopass->worker);
// View Dispatcher
view_dispatcher_free(picopass->view_dispatcher);
// Scene Manager
scene_manager_free(picopass->scene_manager);
// GUI
furi_record_close(RECORD_GUI);
picopass->gui = NULL;
// Notifications
furi_record_close(RECORD_NOTIFICATION);
picopass->notifications = NULL;
free(picopass);
}
void picopass_text_store_set(Picopass* picopass, const char* text, ...) {
va_list args;
va_start(args, text);
vsnprintf(picopass->text_store, sizeof(picopass->text_store), text, args);
va_end(args);
}
void picopass_text_store_clear(Picopass* picopass) {
memset(picopass->text_store, 0, sizeof(picopass->text_store));
}
static const NotificationSequence picopass_sequence_blink_start_blue = {
&message_blink_start_10,
&message_blink_set_color_blue,
&message_do_not_reset,
NULL,
};
static const NotificationSequence picopass_sequence_blink_stop = {
&message_blink_stop,
NULL,
};
void picopass_blink_start(Picopass* picopass) {
notification_message(picopass->notifications, &picopass_sequence_blink_start_blue);
}
void picopass_blink_stop(Picopass* picopass) {
notification_message(picopass->notifications, &picopass_sequence_blink_stop);
}
void picopass_show_loading_popup(void* context, bool show) {
Picopass* picopass = context;
TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
if(show) {
// Raise timer priority so that animations can play
vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewLoading);
} else {
// Restore default timer priority
vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
}
}
int32_t picopass_app(void* p) {
UNUSED(p);
Picopass* picopass = picopass_alloc();
scene_manager_next_scene(picopass->scene_manager, PicopassSceneStart);
view_dispatcher_run(picopass->view_dispatcher);
picopass_free(picopass);
return 0;
}
+3
View File
@@ -0,0 +1,3 @@
#pragma once
typedef struct Picopass Picopass;
@@ -0,0 +1,378 @@
#include "picopass_device.h"
#include <toolbox/path.h>
#include <flipper_format/flipper_format.h>
#define TAG "PicopassDevice"
static const char* picopass_file_header = "Flipper Picopass device";
static const uint32_t picopass_file_version = 1;
const uint8_t picopass_iclass_decryptionkey[] =
{0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36};
PicopassDevice* picopass_device_alloc() {
PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice));
picopass_dev->dev_data.pacs.legacy = false;
picopass_dev->dev_data.pacs.se_enabled = false;
picopass_dev->dev_data.pacs.pin_length = 0;
picopass_dev->storage = furi_record_open(RECORD_STORAGE);
picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS);
string_init(picopass_dev->load_path);
return picopass_dev;
}
void picopass_device_set_name(PicopassDevice* dev, const char* name) {
furi_assert(dev);
strlcpy(dev->dev_name, name, PICOPASS_DEV_NAME_MAX_LEN);
}
static bool picopass_device_save_file(
PicopassDevice* dev,
const char* dev_name,
const char* folder,
const char* extension,
bool use_load_path) {
furi_assert(dev);
bool saved = false;
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
PicopassPacs* pacs = &dev->dev_data.pacs;
PicopassBlock* AA1 = dev->dev_data.AA1;
string_t temp_str;
string_init(temp_str);
do {
if(use_load_path && !string_empty_p(dev->load_path)) {
// Get directory name
path_extract_dirname(string_get_cstr(dev->load_path), temp_str);
// Create picopass directory if necessary
if(!storage_simply_mkdir(dev->storage, string_get_cstr(temp_str))) break;
// Make path to file to save
string_cat_printf(temp_str, "/%s%s", dev_name, extension);
} else {
// Create picopass directory if necessary
if(!storage_simply_mkdir(dev->storage, PICOPASS_APP_FOLDER)) break;
// First remove picopass device file if it was saved
string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
}
// Open file
if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break;
if(dev->format == PicopassDeviceSaveFormatHF) {
uint32_t fc = pacs->record.FacilityCode;
uint32_t cn = pacs->record.CardNumber;
// Write header
if(!flipper_format_write_header_cstr(file, picopass_file_header, picopass_file_version))
break;
if(pacs->record.valid) {
if(!flipper_format_write_uint32(file, "Facility Code", &fc, 1)) break;
if(!flipper_format_write_uint32(file, "Card Number", &cn, 1)) break;
if(!flipper_format_write_hex(
file, "Credential", pacs->credential, PICOPASS_BLOCK_LEN))
break;
if(pacs->pin_length > 0) {
if(!flipper_format_write_hex(file, "PIN\t\t", pacs->pin0, PICOPASS_BLOCK_LEN))
break;
if(!flipper_format_write_hex(
file, "PIN(cont.)\t", pacs->pin1, PICOPASS_BLOCK_LEN))
break;
}
}
if(!flipper_format_write_comment_cstr(file, "Picopass blocks")) break;
bool block_saved = true;
size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] < PICOPASS_MAX_APP_LIMIT ?
AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0] :
PICOPASS_MAX_APP_LIMIT;
for(size_t i = 0; i < app_limit; i++) {
string_printf(temp_str, "Block %d", i);
if(!flipper_format_write_hex(
file, string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) {
block_saved = false;
break;
}
}
if(!block_saved) break;
} else if(dev->format == PicopassDeviceSaveFormatLF) {
const char* lf_header = "Flipper RFID key";
// Write header
if(!flipper_format_write_header_cstr(file, lf_header, 1)) break;
if(!flipper_format_write_comment_cstr(
file,
"This was generated from the Picopass plugin and may not match current lfrfid"))
break;
// When lfrfid supports more formats, update this
if(!flipper_format_write_string_cstr(file, "Key type", "H10301")) break;
uint8_t H10301[3] = {0};
H10301[0] = pacs->record.FacilityCode;
H10301[1] = pacs->record.CardNumber >> 8;
H10301[2] = pacs->record.CardNumber & 0x00FF;
if(!flipper_format_write_hex(file, "Data", H10301, 3)) break;
}
saved = true;
} while(0);
if(!saved) {
dialog_message_show_storage_error(dev->dialogs, "Can not save\nfile");
}
string_clear(temp_str);
flipper_format_free(file);
return saved;
}
bool picopass_device_save(PicopassDevice* dev, const char* dev_name) {
if(dev->format == PicopassDeviceSaveFormatHF) {
return picopass_device_save_file(
dev, dev_name, PICOPASS_APP_FOLDER, PICOPASS_APP_EXTENSION, true);
} else if(dev->format == PicopassDeviceSaveFormatLF) {
return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", true);
}
return false;
}
static bool picopass_device_load_data(PicopassDevice* dev, string_t path, bool show_dialog) {
bool parsed = false;
FlipperFormat* file = flipper_format_file_alloc(dev->storage);
PicopassBlock* AA1 = dev->dev_data.AA1;
PicopassPacs* pacs = &dev->dev_data.pacs;
string_t temp_str;
string_init(temp_str);
bool deprecated_version = false;
if(dev->loading_cb) {
dev->loading_cb(dev->loading_cb_ctx, true);
}
do {
if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break;
// Read and verify file header
uint32_t version = 0;
if(!flipper_format_read_header(file, temp_str, &version)) break;
if(string_cmp_str(temp_str, picopass_file_header) || (version != picopass_file_version)) {
deprecated_version = true;
break;
}
// Parse header blocks
bool block_read = true;
for(size_t i = 0; i < 6; i++) {
string_printf(temp_str, "Block %d", i);
if(!flipper_format_read_hex(
file, string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) {
block_read = false;
break;
}
}
size_t app_limit = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data[0];
for(size_t i = 6; i < app_limit; i++) {
string_printf(temp_str, "Block %d", i);
if(!flipper_format_read_hex(
file, string_get_cstr(temp_str), AA1[i].data, PICOPASS_BLOCK_LEN)) {
block_read = false;
break;
}
}
if(!block_read) break;
if(picopass_device_parse_credential(AA1, pacs) != ERR_NONE) break;
if(picopass_device_parse_wiegand(pacs->credential, &pacs->record) != ERR_NONE) break;
parsed = true;
} while(false);
if(dev->loading_cb) {
dev->loading_cb(dev->loading_cb_ctx, false);
}
if((!parsed) && (show_dialog)) {
if(deprecated_version) {
dialog_message_show_storage_error(dev->dialogs, "File format deprecated");
} else {
dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile");
}
}
string_clear(temp_str);
flipper_format_free(file);
return parsed;
}
void picopass_device_clear(PicopassDevice* dev) {
furi_assert(dev);
picopass_device_data_clear(&dev->dev_data);
memset(&dev->dev_data, 0, sizeof(dev->dev_data));
dev->format = PicopassDeviceSaveFormatHF;
string_reset(dev->load_path);
}
void picopass_device_free(PicopassDevice* picopass_dev) {
furi_assert(picopass_dev);
picopass_device_clear(picopass_dev);
furi_record_close(RECORD_STORAGE);
furi_record_close(RECORD_DIALOGS);
string_clear(picopass_dev->load_path);
free(picopass_dev);
}
bool picopass_file_select(PicopassDevice* dev) {
furi_assert(dev);
// Input events and views are managed by file_browser
string_t picopass_app_folder;
string_init_set_str(picopass_app_folder, PICOPASS_APP_FOLDER);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, PICOPASS_APP_EXTENSION, &I_Nfc_10px);
bool res = dialog_file_browser_show(
dev->dialogs, dev->load_path, picopass_app_folder, &browser_options);
string_clear(picopass_app_folder);
if(res) {
string_t filename;
string_init(filename);
path_extract_filename(dev->load_path, filename, true);
strncpy(dev->dev_name, string_get_cstr(filename), PICOPASS_DEV_NAME_MAX_LEN);
res = picopass_device_load_data(dev, dev->load_path, true);
if(res) {
picopass_device_set_name(dev, dev->dev_name);
}
string_clear(filename);
}
return res;
}
void picopass_device_data_clear(PicopassDeviceData* dev_data) {
for(size_t i = 0; i < PICOPASS_MAX_APP_LIMIT; i++) {
memset(dev_data->AA1[i].data, 0, sizeof(dev_data->AA1[i].data));
}
dev_data->pacs.legacy = false;
dev_data->pacs.se_enabled = false;
dev_data->pacs.pin_length = 0;
}
bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) {
furi_assert(dev);
bool deleted = false;
string_t file_path;
string_init(file_path);
do {
// Delete original file
if(use_load_path && !string_empty_p(dev->load_path)) {
string_set(file_path, dev->load_path);
} else {
string_printf(
file_path, "%s/%s%s", PICOPASS_APP_FOLDER, dev->dev_name, PICOPASS_APP_EXTENSION);
}
if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break;
deleted = true;
} while(0);
if(!deleted) {
dialog_message_show_storage_error(dev->dialogs, "Can not remove file");
}
string_clear(file_path);
return deleted;
}
void picopass_device_set_loading_callback(
PicopassDevice* dev,
PicopassLoadingCallback callback,
void* context) {
furi_assert(dev);
dev->loading_cb = callback;
dev->loading_cb_ctx = context;
}
ReturnCode picopass_device_decrypt(uint8_t* enc_data, uint8_t* dec_data) {
uint8_t key[32] = {0};
memcpy(key, picopass_iclass_decryptionkey, sizeof(picopass_iclass_decryptionkey));
mbedtls_des3_context ctx;
mbedtls_des3_init(&ctx);
mbedtls_des3_set2key_dec(&ctx, key);
mbedtls_des3_crypt_ecb(&ctx, enc_data, dec_data);
mbedtls_des3_free(&ctx);
return ERR_NONE;
}
ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs) {
ReturnCode err;
// Thank you proxmark!
pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0);
pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
pacs->biometrics = AA1[6].data[4];
pacs->pin_length = AA1[6].data[6] & 0x0F;
pacs->encryption = AA1[6].data[7];
if(pacs->encryption == PicopassDeviceEncryption3DES) {
FURI_LOG_D(TAG, "3DES Encrypted");
err = picopass_device_decrypt(AA1[7].data, pacs->credential);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "decrypt error %d", err);
return err;
}
err = picopass_device_decrypt(AA1[8].data, pacs->pin0);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "decrypt error %d", err);
return err;
}
err = picopass_device_decrypt(AA1[9].data, pacs->pin1);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "decrypt error %d", err);
return err;
}
} else if(pacs->encryption == PicopassDeviceEncryptionNone) {
FURI_LOG_D(TAG, "No Encryption");
memcpy(pacs->credential, AA1[7].data, PICOPASS_BLOCK_LEN);
memcpy(pacs->pin0, AA1[8].data, PICOPASS_BLOCK_LEN);
memcpy(pacs->pin1, AA1[9].data, PICOPASS_BLOCK_LEN);
} else if(pacs->encryption == PicopassDeviceEncryptionDES) {
FURI_LOG_D(TAG, "DES Encrypted");
} else {
FURI_LOG_D(TAG, "Unknown encryption");
}
return ERR_NONE;
}
ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record) {
uint32_t* halves = (uint32_t*)data;
if(halves[0] == 0) {
uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1]));
record->bitLength = 31 - leading0s;
} else {
uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[0]));
record->bitLength = 63 - leading0s;
}
FURI_LOG_D(TAG, "bitLength: %d", record->bitLength);
if(record->bitLength == 26) {
uint8_t* v4 = data + 4;
uint32_t bot = v4[3] | (v4[2] << 8) | (v4[1] << 16) | (v4[0] << 24);
record->CardNumber = (bot >> 1) & 0xFFFF;
record->FacilityCode = (bot >> 17) & 0xFF;
FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber);
record->valid = true;
} else {
record->CardNumber = 0;
record->FacilityCode = 0;
record->valid = false;
}
return ERR_NONE;
}
@@ -0,0 +1,102 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include <mbedtls/des.h>
#include "rfal_picopass.h"
#include "loclass/optimized_ikeys.h"
#include "loclass/optimized_cipher.h"
#define PICOPASS_DEV_NAME_MAX_LEN 22
#define PICOPASS_READER_DATA_MAX_SIZE 64
#define PICOPASS_BLOCK_LEN 8
#define PICOPASS_MAX_APP_LIMIT 32
#define PICOPASS_CSN_BLOCK_INDEX 0
#define PICOPASS_CONFIG_BLOCK_INDEX 1
#define PICOPASS_AIA_BLOCK_INDEX 5
#define PICOPASS_APP_FOLDER ANY_PATH("picopass")
#define PICOPASS_APP_EXTENSION ".picopass"
#define PICOPASS_APP_SHADOW_EXTENSION ".pas"
typedef void (*PicopassLoadingCallback)(void* context, bool state);
typedef enum {
PicopassDeviceEncryptionUnknown = 0,
PicopassDeviceEncryptionNone = 0x14,
PicopassDeviceEncryptionDES = 0x15,
PicopassDeviceEncryption3DES = 0x17,
} PicopassEncryption;
typedef enum {
PicopassDeviceSaveFormatHF,
PicopassDeviceSaveFormatLF,
} PicopassDeviceSaveFormat;
typedef struct {
bool valid;
uint8_t bitLength;
uint8_t FacilityCode;
uint16_t CardNumber;
} PicopassWiegandRecord;
typedef struct {
bool legacy;
bool se_enabled;
bool biometrics;
uint8_t pin_length;
PicopassEncryption encryption;
uint8_t credential[8];
uint8_t pin0[8];
uint8_t pin1[8];
PicopassWiegandRecord record;
} PicopassPacs;
typedef struct {
uint8_t data[PICOPASS_BLOCK_LEN];
} PicopassBlock;
typedef struct {
PicopassBlock AA1[PICOPASS_MAX_APP_LIMIT];
PicopassPacs pacs;
} PicopassDeviceData;
typedef struct {
Storage* storage;
DialogsApp* dialogs;
PicopassDeviceData dev_data;
char dev_name[PICOPASS_DEV_NAME_MAX_LEN + 1];
string_t load_path;
PicopassDeviceSaveFormat format;
PicopassLoadingCallback loading_cb;
void* loading_cb_ctx;
} PicopassDevice;
PicopassDevice* picopass_device_alloc();
void picopass_device_free(PicopassDevice* picopass_dev);
void picopass_device_set_name(PicopassDevice* dev, const char* name);
bool picopass_device_save(PicopassDevice* dev, const char* dev_name);
bool picopass_file_select(PicopassDevice* dev);
void picopass_device_data_clear(PicopassDeviceData* dev_data);
void picopass_device_clear(PicopassDevice* dev);
bool picopass_device_delete(PicopassDevice* dev, bool use_load_path);
void picopass_device_set_loading_callback(
PicopassDevice* dev,
PicopassLoadingCallback callback,
void* context);
ReturnCode picopass_device_parse_credential(PicopassBlock* AA1, PicopassPacs* pacs);
ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* record);
@@ -0,0 +1,82 @@
#pragma once
#include "picopass.h"
#include "picopass_worker.h"
#include "picopass_device.h"
#include "rfal_picopass.h"
#include <furi.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <notification/notification_messages.h>
#include <gui/modules/submenu.h>
#include <gui/modules/popup.h>
#include <gui/modules/loading.h>
#include <gui/modules/text_input.h>
#include <gui/modules/widget.h>
#include <input/input.h>
#include "scenes/picopass_scene.h"
#include <storage/storage.h>
#include <lib/toolbox/path.h>
#define PICOPASS_TEXT_STORE_SIZE 128
enum PicopassCustomEvent {
// Reserve first 100 events for button types and indexes, starting from 0
PicopassCustomEventReserved = 100,
PicopassCustomEventViewExit,
PicopassCustomEventWorkerExit,
PicopassCustomEventByteInputDone,
PicopassCustomEventTextInputDone,
};
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
struct Picopass {
PicopassWorker* worker;
ViewDispatcher* view_dispatcher;
Gui* gui;
NotificationApp* notifications;
SceneManager* scene_manager;
PicopassDevice* dev;
char text_store[PICOPASS_TEXT_STORE_SIZE + 1];
string_t text_box_store;
// Common Views
Submenu* submenu;
Popup* popup;
Loading* loading;
TextInput* text_input;
Widget* widget;
};
typedef enum {
PicopassViewMenu,
PicopassViewPopup,
PicopassViewLoading,
PicopassViewTextInput,
PicopassViewWidget,
} PicopassView;
Picopass* picopass_alloc();
void picopass_text_store_set(Picopass* picopass, const char* text, ...);
void picopass_text_store_clear(Picopass* picopass);
void picopass_blink_start(Picopass* picopass);
void picopass_blink_stop(Picopass* picopass);
void picopass_show_loading_popup(void* context, bool show);
@@ -0,0 +1,349 @@
#include "picopass_worker_i.h"
#define TAG "PicopassWorker"
const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78};
const uint8_t picopass_factory_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00};
static void picopass_worker_enable_field() {
furi_hal_nfc_ll_txrx_on();
furi_hal_nfc_exit_sleep();
furi_hal_nfc_ll_poll();
}
static ReturnCode picopass_worker_disable_field(ReturnCode rc) {
furi_hal_nfc_ll_txrx_off();
furi_hal_nfc_start_sleep();
return rc;
}
/***************************** Picopass Worker API *******************************/
PicopassWorker* picopass_worker_alloc() {
PicopassWorker* picopass_worker = malloc(sizeof(PicopassWorker));
// Worker thread attributes
picopass_worker->thread = furi_thread_alloc();
furi_thread_set_name(picopass_worker->thread, "PicopassWorker");
furi_thread_set_stack_size(picopass_worker->thread, 8192);
furi_thread_set_callback(picopass_worker->thread, picopass_worker_task);
furi_thread_set_context(picopass_worker->thread, picopass_worker);
picopass_worker->callback = NULL;
picopass_worker->context = NULL;
picopass_worker->storage = furi_record_open(RECORD_STORAGE);
picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady);
return picopass_worker;
}
void picopass_worker_free(PicopassWorker* picopass_worker) {
furi_assert(picopass_worker);
furi_thread_free(picopass_worker->thread);
furi_record_close(RECORD_STORAGE);
free(picopass_worker);
}
PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker) {
return picopass_worker->state;
}
void picopass_worker_start(
PicopassWorker* picopass_worker,
PicopassWorkerState state,
PicopassDeviceData* dev_data,
PicopassWorkerCallback callback,
void* context) {
furi_assert(picopass_worker);
furi_assert(dev_data);
picopass_worker->callback = callback;
picopass_worker->context = context;
picopass_worker->dev_data = dev_data;
picopass_worker_change_state(picopass_worker, state);
furi_thread_start(picopass_worker->thread);
}
void picopass_worker_stop(PicopassWorker* picopass_worker) {
furi_assert(picopass_worker);
if(picopass_worker->state == PicopassWorkerStateBroken ||
picopass_worker->state == PicopassWorkerStateReady) {
return;
}
picopass_worker_disable_field(ERR_NONE);
picopass_worker_change_state(picopass_worker, PicopassWorkerStateStop);
furi_thread_join(picopass_worker->thread);
}
void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state) {
picopass_worker->state = state;
}
/***************************** Picopass Worker Thread *******************************/
ReturnCode picopass_detect_card(int timeout) {
UNUSED(timeout);
ReturnCode err;
err = rfalPicoPassPollerInitialize();
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerInitialize error %d", err);
return err;
}
err = rfalFieldOnAndStartGT();
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalFieldOnAndStartGT error %d", err);
return err;
}
err = rfalPicoPassPollerCheckPresence();
if(err != ERR_RF_COLLISION) {
FURI_LOG_E(TAG, "rfalPicoPassPollerCheckPresence error %d", err);
return err;
}
return ERR_NONE;
}
ReturnCode picopass_read_card(PicopassBlock* AA1) {
rfalPicoPassIdentifyRes idRes;
rfalPicoPassSelectRes selRes;
rfalPicoPassReadCheckRes rcRes;
rfalPicoPassCheckRes chkRes;
ReturnCode err;
uint8_t div_key[8] = {0};
uint8_t mac[4] = {0};
uint8_t ccnr[12] = {0};
err = rfalPicoPassPollerIdentify(&idRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err);
return err;
}
err = rfalPicoPassPollerSelect(idRes.CSN, &selRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err);
return err;
}
err = rfalPicoPassPollerReadCheck(&rcRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
return err;
}
memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
loclass_diversifyKey(selRes.CSN, picopass_iclass_key, div_key);
loclass_opt_doReaderMAC(ccnr, div_key, mac);
err = rfalPicoPassPollerCheck(mac, &chkRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
return err;
}
rfalPicoPassReadBlockRes csn;
err = rfalPicoPassPollerReadBlock(PICOPASS_CSN_BLOCK_INDEX, &csn);
memcpy(AA1[PICOPASS_CSN_BLOCK_INDEX].data, csn.data, sizeof(csn.data));
rfalPicoPassReadBlockRes cfg;
err = rfalPicoPassPollerReadBlock(PICOPASS_CONFIG_BLOCK_INDEX, &cfg);
memcpy(AA1[PICOPASS_CONFIG_BLOCK_INDEX].data, cfg.data, sizeof(cfg.data));
size_t app_limit = cfg.data[0] < PICOPASS_MAX_APP_LIMIT ? cfg.data[0] : PICOPASS_MAX_APP_LIMIT;
for(size_t i = 2; i < app_limit; i++) {
FURI_LOG_D(TAG, "rfalPicoPassPollerReadBlock block %d", i);
rfalPicoPassReadBlockRes block;
err = rfalPicoPassPollerReadBlock(i, &block);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerReadBlock error %d", err);
return err;
}
FURI_LOG_D(
TAG,
"rfalPicoPassPollerReadBlock %d %02x%02x%02x%02x%02x%02x%02x%02x",
i,
block.data[0],
block.data[1],
block.data[2],
block.data[3],
block.data[4],
block.data[5],
block.data[6],
block.data[7]);
memcpy(AA1[i].data, block.data, sizeof(block.data));
}
return ERR_NONE;
}
ReturnCode picopass_write_card(PicopassBlock* AA1) {
rfalPicoPassIdentifyRes idRes;
rfalPicoPassSelectRes selRes;
rfalPicoPassReadCheckRes rcRes;
rfalPicoPassCheckRes chkRes;
ReturnCode err;
uint8_t div_key[8] = {0};
uint8_t mac[4] = {0};
uint8_t ccnr[12] = {0};
err = rfalPicoPassPollerIdentify(&idRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err);
return err;
}
err = rfalPicoPassPollerSelect(idRes.CSN, &selRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err);
return err;
}
err = rfalPicoPassPollerReadCheck(&rcRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err);
return err;
}
memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
loclass_diversifyKey(selRes.CSN, picopass_iclass_key, div_key);
loclass_opt_doReaderMAC(ccnr, div_key, mac);
err = rfalPicoPassPollerCheck(mac, &chkRes);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err);
return err;
}
for(size_t i = 6; i < 10; i++) {
FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", i);
uint8_t data[9] = {0};
data[0] = i;
memcpy(data + 1, AA1[i].data, RFAL_PICOPASS_MAX_BLOCK_LEN);
loclass_doMAC_N(data, sizeof(data), div_key, mac);
FURI_LOG_D(
TAG,
"loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x",
i,
data[1],
data[2],
data[3],
data[4],
data[5],
data[6],
data[7],
data[8],
mac[0],
mac[1],
mac[2],
mac[3]);
err = rfalPicoPassPollerWriteBlock(i, AA1[i].data, mac);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err);
return err;
}
}
return ERR_NONE;
}
int32_t picopass_worker_task(void* context) {
PicopassWorker* picopass_worker = context;
picopass_worker_enable_field();
if(picopass_worker->state == PicopassWorkerStateDetect) {
picopass_worker_detect(picopass_worker);
} else if(picopass_worker->state == PicopassWorkerStateWrite) {
picopass_worker_write(picopass_worker);
}
picopass_worker_disable_field(ERR_NONE);
picopass_worker_change_state(picopass_worker, PicopassWorkerStateReady);
return 0;
}
void picopass_worker_detect(PicopassWorker* picopass_worker) {
picopass_device_data_clear(picopass_worker->dev_data);
PicopassDeviceData* dev_data = picopass_worker->dev_data;
PicopassBlock* AA1 = dev_data->AA1;
PicopassPacs* pacs = &dev_data->pacs;
ReturnCode err;
PicopassWorkerEvent nextState = PicopassWorkerEventSuccess;
while(picopass_worker->state == PicopassWorkerStateDetect) {
if(picopass_detect_card(1000) == ERR_NONE) {
// Process first found device
err = picopass_read_card(AA1);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "picopass_read_card error %d", err);
nextState = PicopassWorkerEventFail;
}
if(nextState == PicopassWorkerEventSuccess) {
err = picopass_device_parse_credential(AA1, pacs);
}
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "picopass_device_parse_credential error %d", err);
nextState = PicopassWorkerEventFail;
}
if(nextState == PicopassWorkerEventSuccess) {
err = picopass_device_parse_wiegand(pacs->credential, &pacs->record);
}
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "picopass_device_parse_wiegand error %d", err);
nextState = PicopassWorkerEventFail;
}
// Notify caller and exit
if(picopass_worker->callback) {
picopass_worker->callback(nextState, picopass_worker->context);
}
break;
}
furi_delay_ms(100);
}
}
void picopass_worker_write(PicopassWorker* picopass_worker) {
PicopassDeviceData* dev_data = picopass_worker->dev_data;
PicopassBlock* AA1 = dev_data->AA1;
ReturnCode err;
PicopassWorkerEvent nextState = PicopassWorkerEventSuccess;
while(picopass_worker->state == PicopassWorkerStateWrite) {
if(picopass_detect_card(1000) == ERR_NONE) {
err = picopass_write_card(AA1);
if(err != ERR_NONE) {
FURI_LOG_E(TAG, "picopass_write_card error %d", err);
nextState = PicopassWorkerEventFail;
}
// Notify caller and exit
if(picopass_worker->callback) {
picopass_worker->callback(nextState, picopass_worker->context);
}
break;
}
furi_delay_ms(100);
}
}
@@ -0,0 +1,46 @@
#pragma once
#include "picopass_device.h"
typedef struct PicopassWorker PicopassWorker;
typedef enum {
// Init states
PicopassWorkerStateNone,
PicopassWorkerStateBroken,
PicopassWorkerStateReady,
// Main worker states
PicopassWorkerStateDetect,
PicopassWorkerStateWrite,
// Transition
PicopassWorkerStateStop,
} PicopassWorkerState;
typedef enum {
// Reserve first 50 events for application events
PicopassWorkerEventReserved = 50,
// Picopass worker common events
PicopassWorkerEventSuccess,
PicopassWorkerEventFail,
PicopassWorkerEventNoCardDetected,
PicopassWorkerEventStartReading,
} PicopassWorkerEvent;
typedef void (*PicopassWorkerCallback)(PicopassWorkerEvent event, void* context);
PicopassWorker* picopass_worker_alloc();
PicopassWorkerState picopass_worker_get_state(PicopassWorker* picopass_worker);
void picopass_worker_free(PicopassWorker* picopass_worker);
void picopass_worker_start(
PicopassWorker* picopass_worker,
PicopassWorkerState state,
PicopassDeviceData* dev_data,
PicopassWorkerCallback callback,
void* context);
void picopass_worker_stop(PicopassWorker* picopass_worker);
@@ -0,0 +1,34 @@
#pragma once
#include "picopass_worker.h"
#include "picopass_i.h"
#include <furi.h>
#include <lib/toolbox/stream/file_stream.h>
#include <furi_hal.h>
#include <stdlib.h>
#include <st25r3916.h>
#include <rfal_analogConfig.h>
#include <rfal_rf.h>
#include <platform.h>
struct PicopassWorker {
FuriThread* thread;
Storage* storage;
PicopassDeviceData* dev_data;
PicopassWorkerCallback callback;
void* context;
PicopassWorkerState state;
};
void picopass_worker_change_state(PicopassWorker* picopass_worker, PicopassWorkerState state);
int32_t picopass_worker_task(void* context);
void picopass_worker_detect(PicopassWorker* picopass_worker);
void picopass_worker_write(PicopassWorker* picopass_worker);
@@ -0,0 +1,215 @@
#include "rfal_picopass.h"
#include "utils.h"
#define RFAL_PICOPASS_TXRX_FLAGS \
(FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | \
FURI_HAL_NFC_LL_TXRX_FLAGS_PAR_RX_REMV | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP)
#define TAG "RFAL_PICOPASS"
typedef struct {
uint8_t CMD;
uint8_t CSN[RFAL_PICOPASS_UID_LEN];
} rfalPicoPassSelectReq;
typedef struct {
uint8_t CMD;
uint8_t null[4];
uint8_t mac[4];
} rfalPicoPassCheckReq;
static uint16_t rfalPicoPassUpdateCcitt(uint16_t crcSeed, uint8_t dataByte) {
uint16_t crc = crcSeed;
uint8_t dat = dataByte;
dat ^= (uint8_t)(crc & 0xFFU);
dat ^= (dat << 4);
crc = (crc >> 8) ^ (((uint16_t)dat) << 8) ^ (((uint16_t)dat) << 3) ^ (((uint16_t)dat) >> 4);
return crc;
}
static uint16_t
rfalPicoPassCalculateCcitt(uint16_t preloadValue, const uint8_t* buf, uint16_t length) {
uint16_t crc = preloadValue;
uint16_t index;
for(index = 0; index < length; index++) {
crc = rfalPicoPassUpdateCcitt(crc, buf[index]);
}
return crc;
}
FuriHalNfcReturn rfalPicoPassPollerInitialize(void) {
FuriHalNfcReturn ret;
ret = furi_hal_nfc_ll_set_mode(
FuriHalNfcModePollPicopass, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48);
if(ret != FuriHalNfcReturnOk) {
return ret;
};
furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc);
furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_PICOPASS);
furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_PICOPASS_POLLER);
furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_PICOPASS_POLLER);
return FuriHalNfcReturnOk;
}
FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void) {
FuriHalNfcReturn ret;
uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_ACTALL};
uint8_t rxBuf[32] = {0};
uint16_t recvLen = 0;
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
ret = furi_hal_nfc_ll_txrx(txBuf, 1, rxBuf, 32, &recvLen, flags, fwt);
return ret;
}
FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes) {
FuriHalNfcReturn ret;
uint8_t txBuf[1] = {RFAL_PICOPASS_CMD_IDENTIFY};
uint16_t recvLen = 0;
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
ret = furi_hal_nfc_ll_txrx(
txBuf,
sizeof(txBuf),
(uint8_t*)idRes,
sizeof(rfalPicoPassIdentifyRes),
&recvLen,
flags,
fwt);
// printf("identify rx: %d %s\n", recvLen, hex2Str(idRes->CSN, RFAL_PICOPASS_UID_LEN));
return ret;
}
FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes) {
FuriHalNfcReturn ret;
rfalPicoPassSelectReq selReq;
selReq.CMD = RFAL_PICOPASS_CMD_SELECT;
ST_MEMCPY(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN);
uint16_t recvLen = 0;
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
ret = furi_hal_nfc_ll_txrx(
(uint8_t*)&selReq,
sizeof(rfalPicoPassSelectReq),
(uint8_t*)selRes,
sizeof(rfalPicoPassSelectRes),
&recvLen,
flags,
fwt);
// printf("select rx: %d %s\n", recvLen, hex2Str(selRes->CSN, RFAL_PICOPASS_UID_LEN));
if(ret == FuriHalNfcReturnTimeout) {
return FuriHalNfcReturnOk;
}
return ret;
}
FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes) {
FuriHalNfcReturn ret;
uint8_t txBuf[2] = {RFAL_PICOPASS_CMD_READCHECK, 0x02};
uint16_t recvLen = 0;
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
ret = furi_hal_nfc_ll_txrx(
txBuf,
sizeof(txBuf),
(uint8_t*)rcRes,
sizeof(rfalPicoPassReadCheckRes),
&recvLen,
flags,
fwt);
// printf("readcheck rx: %d %s\n", recvLen, hex2Str(rcRes->CCNR, 8));
if(ret == FuriHalNfcReturnCrc) {
return FuriHalNfcReturnOk;
}
return ret;
}
FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes) {
FuriHalNfcReturn ret;
rfalPicoPassCheckReq chkReq;
chkReq.CMD = RFAL_PICOPASS_CMD_CHECK;
ST_MEMCPY(chkReq.mac, mac, 4);
ST_MEMSET(chkReq.null, 0, 4);
uint16_t recvLen = 0;
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
// printf("check tx: %s\n", hex2Str((uint8_t *)&chkReq, sizeof(rfalPicoPassCheckReq)));
ret = furi_hal_nfc_ll_txrx(
(uint8_t*)&chkReq,
sizeof(rfalPicoPassCheckReq),
(uint8_t*)chkRes,
sizeof(rfalPicoPassCheckRes),
&recvLen,
flags,
fwt);
// printf("check rx: %d %s\n", recvLen, hex2Str(chkRes->mac, 4));
if(ret == FuriHalNfcReturnCrc) {
return FuriHalNfcReturnOk;
}
return ret;
}
FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes) {
FuriHalNfcReturn ret;
uint8_t txBuf[4] = {RFAL_PICOPASS_CMD_READ, 0, 0, 0};
txBuf[1] = blockNum;
uint16_t crc = rfalPicoPassCalculateCcitt(0xE012, txBuf + 1, 1);
memcpy(txBuf + 2, &crc, sizeof(uint16_t));
uint16_t recvLen = 0;
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
ret = furi_hal_nfc_ll_txrx(
txBuf,
sizeof(txBuf),
(uint8_t*)readRes,
sizeof(rfalPicoPassReadBlockRes),
&recvLen,
flags,
fwt);
return ret;
}
FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]) {
FuriHalNfcReturn ret;
uint8_t txBuf[14] = {RFAL_PICOPASS_CMD_WRITE, blockNum, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
memcpy(txBuf + 2, data, RFAL_PICOPASS_MAX_BLOCK_LEN);
memcpy(txBuf + 10, mac, 4);
uint16_t recvLen = 0;
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
rfalPicoPassReadBlockRes block;
ret = furi_hal_nfc_ll_txrx(
txBuf, sizeof(txBuf), (uint8_t*)&block, sizeof(block), &recvLen, flags, fwt);
if(ret == FuriHalNfcReturnOk) {
// TODO: compare response
}
return ret;
}
@@ -0,0 +1,48 @@
#pragma once
#include <furi_hal_nfc.h>
#define RFAL_PICOPASS_UID_LEN 8
#define RFAL_PICOPASS_MAX_BLOCK_LEN 8
enum {
RFAL_PICOPASS_CMD_ACTALL = 0x0A,
RFAL_PICOPASS_CMD_IDENTIFY = 0x0C,
RFAL_PICOPASS_CMD_SELECT = 0x81,
RFAL_PICOPASS_CMD_READCHECK = 0x88,
RFAL_PICOPASS_CMD_CHECK = 0x05,
RFAL_PICOPASS_CMD_READ = 0x0C,
RFAL_PICOPASS_CMD_WRITE = 0x87,
};
typedef struct {
uint8_t CSN[RFAL_PICOPASS_UID_LEN]; // Anti-collision CSN
uint8_t crc[2];
} rfalPicoPassIdentifyRes;
typedef struct {
uint8_t CSN[RFAL_PICOPASS_UID_LEN]; // Real CSN
uint8_t crc[2];
} rfalPicoPassSelectRes;
typedef struct {
uint8_t CCNR[8];
} rfalPicoPassReadCheckRes;
typedef struct {
uint8_t mac[4];
} rfalPicoPassCheckRes;
typedef struct {
uint8_t data[RFAL_PICOPASS_MAX_BLOCK_LEN];
uint8_t crc[2];
} rfalPicoPassReadBlockRes;
FuriHalNfcReturn rfalPicoPassPollerInitialize(void);
FuriHalNfcReturn rfalPicoPassPollerCheckPresence(void);
FuriHalNfcReturn rfalPicoPassPollerIdentify(rfalPicoPassIdentifyRes* idRes);
FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* selRes);
FuriHalNfcReturn rfalPicoPassPollerReadCheck(rfalPicoPassReadCheckRes* rcRes);
FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chkRes);
FuriHalNfcReturn rfalPicoPassPollerReadBlock(uint8_t blockNum, rfalPicoPassReadBlockRes* readRes);
FuriHalNfcReturn rfalPicoPassPollerWriteBlock(uint8_t blockNum, uint8_t data[8], uint8_t mac[4]);
@@ -0,0 +1,30 @@
#include "picopass_scene.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const picopass_on_enter_handlers[])(void*) = {
#include "picopass_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_event handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
bool (*const picopass_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "picopass_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_exit handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
void (*const picopass_on_exit_handlers[])(void* context) = {
#include "picopass_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers picopass_scene_handlers = {
.on_enter_handlers = picopass_on_enter_handlers,
.on_event_handlers = picopass_on_event_handlers,
.on_exit_handlers = picopass_on_exit_handlers,
.scene_num = PicopassSceneNum,
};
@@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) PicopassScene##id,
typedef enum {
#include "picopass_scene_config.h"
PicopassSceneNum,
} PicopassScene;
#undef ADD_SCENE
extern const SceneManagerHandlers picopass_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "picopass_scene_config.h"
#undef ADD_SCENE
// Generate scene on_event handlers declaration
#define ADD_SCENE(prefix, name, id) \
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
#include "picopass_scene_config.h"
#undef ADD_SCENE
// Generate scene on_exit handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
#include "picopass_scene_config.h"
#undef ADD_SCENE
@@ -0,0 +1,65 @@
#include "../picopass_i.h"
enum SubmenuIndex {
SubmenuIndexSave,
SubmenuIndexSaveAsLF,
};
void picopass_scene_card_menu_submenu_callback(void* context, uint32_t index) {
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, index);
}
void picopass_scene_card_menu_on_enter(void* context) {
Picopass* picopass = context;
Submenu* submenu = picopass->submenu;
submenu_add_item(
submenu, "Save", SubmenuIndexSave, picopass_scene_card_menu_submenu_callback, picopass);
if(picopass->dev->dev_data.pacs.record.valid) {
submenu_add_item(
submenu,
"Save as LF",
SubmenuIndexSaveAsLF,
picopass_scene_card_menu_submenu_callback,
picopass);
}
submenu_set_selected_item(
picopass->submenu,
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneCardMenu));
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu);
}
bool picopass_scene_card_menu_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexSave) {
scene_manager_set_scene_state(
picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSave);
scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName);
picopass->dev->format = PicopassDeviceSaveFormatHF;
consumed = true;
} else if(event.event == SubmenuIndexSaveAsLF) {
scene_manager_set_scene_state(
picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSaveAsLF);
picopass->dev->format = PicopassDeviceSaveFormatLF;
scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
consumed = scene_manager_search_and_switch_to_previous_scene(
picopass->scene_manager, PicopassSceneStart);
}
return consumed;
}
void picopass_scene_card_menu_on_exit(void* context) {
Picopass* picopass = context;
submenu_reset(picopass->submenu);
}
@@ -0,0 +1,13 @@
ADD_SCENE(picopass, start, Start)
ADD_SCENE(picopass, read_card, ReadCard)
ADD_SCENE(picopass, read_card_success, ReadCardSuccess)
ADD_SCENE(picopass, card_menu, CardMenu)
ADD_SCENE(picopass, save_name, SaveName)
ADD_SCENE(picopass, save_success, SaveSuccess)
ADD_SCENE(picopass, saved_menu, SavedMenu)
ADD_SCENE(picopass, file_select, FileSelect)
ADD_SCENE(picopass, device_info, DeviceInfo)
ADD_SCENE(picopass, delete, Delete)
ADD_SCENE(picopass, delete_success, DeleteSuccess)
ADD_SCENE(picopass, write_card, WriteCard)
ADD_SCENE(picopass, write_card_success, WriteCardSuccess)
@@ -0,0 +1,58 @@
#include "../picopass_i.h"
void picopass_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) {
Picopass* picopass = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(picopass->view_dispatcher, result);
}
}
void picopass_scene_delete_on_enter(void* context) {
Picopass* picopass = context;
// Setup Custom Widget view
char temp_str[64];
snprintf(temp_str, sizeof(temp_str), "\e#Delete %s?\e#", picopass->dev->dev_name);
widget_add_text_box_element(
picopass->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, temp_str, false);
widget_add_button_element(
picopass->widget,
GuiButtonTypeLeft,
"Back",
picopass_scene_delete_widget_callback,
picopass);
widget_add_button_element(
picopass->widget,
GuiButtonTypeRight,
"Delete",
picopass_scene_delete_widget_callback,
picopass);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
}
bool picopass_scene_delete_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeLeft) {
return scene_manager_previous_scene(picopass->scene_manager);
} else if(event.event == GuiButtonTypeRight) {
if(picopass_device_delete(picopass->dev, true)) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneDeleteSuccess);
} else {
scene_manager_search_and_switch_to_previous_scene(
picopass->scene_manager, PicopassSceneStart);
}
consumed = true;
}
}
return consumed;
}
void picopass_scene_delete_on_exit(void* context) {
Picopass* picopass = context;
widget_reset(picopass->widget);
}
@@ -0,0 +1,40 @@
#include "../picopass_i.h"
void picopass_scene_delete_success_popup_callback(void* context) {
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit);
}
void picopass_scene_delete_success_on_enter(void* context) {
Picopass* picopass = context;
// Setup view
Popup* popup = picopass->popup;
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500);
popup_set_context(popup, picopass);
popup_set_callback(popup, picopass_scene_delete_success_popup_callback);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
}
bool picopass_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PicopassCustomEventViewExit) {
consumed = scene_manager_search_and_switch_to_previous_scene(
picopass->scene_manager, PicopassSceneStart);
}
}
return consumed;
}
void picopass_scene_delete_success_on_exit(void* context) {
Picopass* picopass = context;
// Clear view
popup_reset(picopass->popup);
}
@@ -0,0 +1,82 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
void picopass_scene_device_info_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
Picopass* picopass = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(picopass->view_dispatcher, result);
}
}
void picopass_scene_device_info_on_enter(void* context) {
Picopass* picopass = context;
string_t credential_str;
string_t wiegand_str;
string_init(credential_str);
string_init(wiegand_str);
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
// Setup view
PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
Widget* widget = picopass->widget;
size_t bytesLength = 1 + pacs->record.bitLength / 8;
string_set_str(credential_str, "");
for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) {
string_cat_printf(credential_str, " %02X", pacs->credential[i]);
}
if(pacs->record.valid) {
string_cat_printf(
wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber);
} else {
string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength);
}
widget_add_string_element(
widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str));
widget_add_string_element(
widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(credential_str));
string_clear(credential_str);
string_clear(wiegand_str);
widget_add_button_element(
picopass->widget,
GuiButtonTypeLeft,
"Back",
picopass_scene_device_info_widget_callback,
picopass);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
}
bool picopass_scene_device_info_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeLeft) {
consumed = scene_manager_previous_scene(picopass->scene_manager);
} else if(event.event == PicopassCustomEventViewExit) {
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeBack) {
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
consumed = true;
}
return consumed;
}
void picopass_scene_device_info_on_exit(void* context) {
Picopass* picopass = context;
// Clear views
widget_reset(picopass->widget);
}
@@ -0,0 +1,25 @@
#include "../picopass_i.h"
#include "../picopass_device.h"
void picopass_scene_file_select_on_enter(void* context) {
Picopass* picopass = context;
// Process file_select return
picopass_device_set_loading_callback(picopass->dev, picopass_show_loading_popup, picopass);
if(picopass_file_select(picopass->dev)) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneSavedMenu);
} else {
scene_manager_search_and_switch_to_previous_scene(
picopass->scene_manager, PicopassSceneStart);
}
picopass_device_set_loading_callback(picopass->dev, NULL, picopass);
}
bool picopass_scene_file_select_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void picopass_scene_file_select_on_exit(void* context) {
UNUSED(context);
}
@@ -0,0 +1,53 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) {
UNUSED(event);
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit);
}
void picopass_scene_read_card_on_enter(void* context) {
Picopass* picopass = context;
DOLPHIN_DEED(DolphinDeedNfcRead);
// Setup view
Popup* popup = picopass->popup;
popup_set_header(popup, "Detecting\npicopass\ncard", 68, 30, AlignLeft, AlignTop);
popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
// Start worker
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
picopass_worker_start(
picopass->worker,
PicopassWorkerStateDetect,
&picopass->dev->dev_data,
picopass_read_card_worker_callback,
picopass);
picopass_blink_start(picopass);
}
bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PicopassCustomEventWorkerExit) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess);
consumed = true;
}
}
return consumed;
}
void picopass_scene_read_card_on_exit(void* context) {
Picopass* picopass = context;
// Stop worker
picopass_worker_stop(picopass->worker);
// Clear view
popup_reset(picopass->popup);
picopass_blink_stop(picopass);
}
@@ -0,0 +1,111 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
void picopass_scene_read_card_success_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
furi_assert(context);
Picopass* picopass = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(picopass->view_dispatcher, result);
}
}
void picopass_scene_read_card_success_on_enter(void* context) {
Picopass* picopass = context;
string_t credential_str;
string_t wiegand_str;
string_init(credential_str);
string_init(wiegand_str);
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
// Send notification
notification_message(picopass->notifications, &sequence_success);
// Setup view
PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
Widget* widget = picopass->widget;
if(pacs->record.bitLength == 0) {
string_cat_printf(wiegand_str, "Read Failed");
widget_add_button_element(
widget,
GuiButtonTypeLeft,
"Retry",
picopass_scene_read_card_success_widget_callback,
picopass);
widget_add_string_element(
widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str));
} else {
size_t bytesLength = 1 + pacs->record.bitLength / 8;
string_set_str(credential_str, "");
for(uint8_t i = PICOPASS_BLOCK_LEN - bytesLength; i < PICOPASS_BLOCK_LEN; i++) {
string_cat_printf(credential_str, " %02X", pacs->credential[i]);
}
if(pacs->record.valid) {
string_cat_printf(
wiegand_str, "FC: %u CN: %u", pacs->record.FacilityCode, pacs->record.CardNumber);
} else {
string_cat_printf(wiegand_str, "%d bits", pacs->record.bitLength);
}
widget_add_button_element(
widget,
GuiButtonTypeLeft,
"Retry",
picopass_scene_read_card_success_widget_callback,
picopass);
widget_add_button_element(
widget,
GuiButtonTypeRight,
"More",
picopass_scene_read_card_success_widget_callback,
picopass);
widget_add_string_element(
widget, 64, 12, AlignCenter, AlignCenter, FontPrimary, string_get_cstr(wiegand_str));
widget_add_string_element(
widget,
64,
32,
AlignCenter,
AlignCenter,
FontSecondary,
string_get_cstr(credential_str));
}
string_clear(credential_str);
string_clear(wiegand_str);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
}
bool picopass_scene_read_card_success_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeLeft) {
consumed = scene_manager_previous_scene(picopass->scene_manager);
} else if(event.event == GuiButtonTypeRight) {
// Clear device name
picopass_device_set_name(picopass->dev, "");
scene_manager_next_scene(picopass->scene_manager, PicopassSceneCardMenu);
consumed = true;
}
}
return consumed;
}
void picopass_scene_read_card_success_on_exit(void* context) {
Picopass* picopass = context;
// Clear view
widget_reset(picopass->widget);
}
@@ -0,0 +1,84 @@
#include "../picopass_i.h"
#include "m-string.h"
#include <lib/toolbox/random_name.h>
#include <gui/modules/validators.h>
#include <toolbox/path.h>
void picopass_scene_save_name_text_input_callback(void* context) {
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventTextInputDone);
}
void picopass_scene_save_name_on_enter(void* context) {
Picopass* picopass = context;
// Setup view
TextInput* text_input = picopass->text_input;
bool dev_name_empty = false;
if(!strcmp(picopass->dev->dev_name, "")) {
set_random_name(picopass->text_store, sizeof(picopass->text_store));
dev_name_empty = true;
} else {
picopass_text_store_set(picopass, picopass->dev->dev_name);
}
text_input_set_header_text(text_input, "Name the card");
text_input_set_result_callback(
text_input,
picopass_scene_save_name_text_input_callback,
picopass,
picopass->text_store,
PICOPASS_DEV_NAME_MAX_LEN,
dev_name_empty);
string_t folder_path;
string_init(folder_path);
if(string_end_with_str_p(picopass->dev->load_path, PICOPASS_APP_EXTENSION)) {
path_extract_dirname(string_get_cstr(picopass->dev->load_path), folder_path);
} else {
string_set_str(folder_path, PICOPASS_APP_FOLDER);
}
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
string_get_cstr(folder_path), PICOPASS_APP_EXTENSION, picopass->dev->dev_name);
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewTextInput);
string_clear(folder_path);
}
bool picopass_scene_save_name_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PicopassCustomEventTextInputDone) {
if(strcmp(picopass->dev->dev_name, "")) {
// picopass_device_delete(picopass->dev, true);
}
strlcpy(
picopass->dev->dev_name, picopass->text_store, strlen(picopass->text_store) + 1);
if(picopass_device_save(picopass->dev, picopass->text_store)) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveSuccess);
consumed = true;
} else {
consumed = scene_manager_search_and_switch_to_previous_scene(
picopass->scene_manager, PicopassSceneStart);
}
}
}
return consumed;
}
void picopass_scene_save_name_on_exit(void* context) {
Picopass* picopass = context;
// Clear view
void* validator_context = text_input_get_validator_callback_context(picopass->text_input);
text_input_set_validator(picopass->text_input, NULL, NULL);
validator_is_file_free(validator_context);
text_input_reset(picopass->text_input);
}
@@ -0,0 +1,47 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
void picopass_scene_save_success_popup_callback(void* context) {
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventViewExit);
}
void picopass_scene_save_success_on_enter(void* context) {
Picopass* picopass = context;
DOLPHIN_DEED(DolphinDeedNfcSave);
// Setup view
Popup* popup = picopass->popup;
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500);
popup_set_context(popup, picopass);
popup_set_callback(popup, picopass_scene_save_success_popup_callback);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
}
bool picopass_scene_save_success_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PicopassCustomEventViewExit) {
if(scene_manager_has_previous_scene(picopass->scene_manager, PicopassSceneCardMenu)) {
consumed = scene_manager_search_and_switch_to_previous_scene(
picopass->scene_manager, PicopassSceneCardMenu);
} else {
consumed = scene_manager_search_and_switch_to_previous_scene(
picopass->scene_manager, PicopassSceneStart);
}
}
}
return consumed;
}
void picopass_scene_save_success_on_exit(void* context) {
Picopass* picopass = context;
// Clear view
popup_reset(picopass->popup);
}
@@ -0,0 +1,64 @@
#include "../picopass_i.h"
enum SubmenuIndex {
SubmenuIndexDelete,
SubmenuIndexInfo,
SubmenuIndexWrite,
};
void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, index);
}
void picopass_scene_saved_menu_on_enter(void* context) {
Picopass* picopass = context;
Submenu* submenu = picopass->submenu;
submenu_add_item(
submenu,
"Delete",
SubmenuIndexDelete,
picopass_scene_saved_menu_submenu_callback,
picopass);
submenu_add_item(
submenu, "Info", SubmenuIndexInfo, picopass_scene_saved_menu_submenu_callback, picopass);
submenu_add_item(
submenu, "Write", SubmenuIndexWrite, picopass_scene_saved_menu_submenu_callback, picopass);
submenu_set_selected_item(
picopass->submenu,
scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneSavedMenu));
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu);
}
bool picopass_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_set_scene_state(
picopass->scene_manager, PicopassSceneSavedMenu, event.event);
if(event.event == SubmenuIndexDelete) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneDelete);
consumed = true;
} else if(event.event == SubmenuIndexInfo) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneDeviceInfo);
consumed = true;
} else if(event.event == SubmenuIndexWrite) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCard);
consumed = true;
}
}
return consumed;
}
void picopass_scene_saved_menu_on_exit(void* context) {
Picopass* picopass = context;
submenu_reset(picopass->submenu);
}
@@ -0,0 +1,50 @@
#include "../picopass_i.h"
enum SubmenuIndex {
SubmenuIndexRead,
SubmenuIndexRunScript,
SubmenuIndexSaved,
SubmenuIndexAddManualy,
SubmenuIndexDebug,
};
void picopass_scene_start_submenu_callback(void* context, uint32_t index) {
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, index);
}
void picopass_scene_start_on_enter(void* context) {
Picopass* picopass = context;
Submenu* submenu = picopass->submenu;
submenu_add_item(
submenu, "Read Card", SubmenuIndexRead, picopass_scene_start_submenu_callback, picopass);
submenu_add_item(
submenu, "Saved", SubmenuIndexSaved, picopass_scene_start_submenu_callback, picopass);
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(picopass->scene_manager, PicopassSceneStart));
picopass_device_clear(picopass->dev);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewMenu);
}
bool picopass_scene_start_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexRead) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCard);
consumed = true;
} else if(event.event == SubmenuIndexSaved) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneFileSelect);
consumed = true;
}
scene_manager_set_scene_state(picopass->scene_manager, PicopassSceneStart, event.event);
}
return consumed;
}
void picopass_scene_start_on_exit(void* context) {
Picopass* picopass = context;
submenu_reset(picopass->submenu);
}
@@ -0,0 +1,53 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
void picopass_write_card_worker_callback(PicopassWorkerEvent event, void* context) {
UNUSED(event);
Picopass* picopass = context;
view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit);
}
void picopass_scene_write_card_on_enter(void* context) {
Picopass* picopass = context;
DOLPHIN_DEED(DolphinDeedNfcSave);
// Setup view
Popup* popup = picopass->popup;
popup_set_header(popup, "Writing\npicopass\ncard", 68, 30, AlignLeft, AlignTop);
popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61);
// Start worker
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup);
picopass_worker_start(
picopass->worker,
PicopassWorkerStateWrite,
&picopass->dev->dev_data,
picopass_write_card_worker_callback,
picopass);
picopass_blink_start(picopass);
}
bool picopass_scene_write_card_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == PicopassCustomEventWorkerExit) {
scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess);
consumed = true;
}
}
return consumed;
}
void picopass_scene_write_card_on_exit(void* context) {
Picopass* picopass = context;
// Stop worker
picopass_worker_stop(picopass->worker);
// Clear view
popup_reset(picopass->popup);
picopass_blink_stop(picopass);
}
@@ -0,0 +1,57 @@
#include "../picopass_i.h"
#include <dolphin/dolphin.h>
void picopass_scene_write_card_success_widget_callback(
GuiButtonType result,
InputType type,
void* context) {
furi_assert(context);
Picopass* picopass = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(picopass->view_dispatcher, result);
}
}
void picopass_scene_write_card_success_on_enter(void* context) {
Picopass* picopass = context;
Widget* widget = picopass->widget;
DOLPHIN_DEED(DolphinDeedNfcReadSuccess);
// Send notification
notification_message(picopass->notifications, &sequence_success);
widget_add_button_element(
widget,
GuiButtonTypeLeft,
"Retry",
picopass_scene_write_card_success_widget_callback,
picopass);
view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget);
}
bool picopass_scene_write_card_success_on_event(void* context, SceneManagerEvent event) {
Picopass* picopass = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeLeft) {
consumed = scene_manager_previous_scene(picopass->scene_manager);
} else if(event.event == GuiButtonTypeRight) {
// Clear device name
picopass_device_set_name(picopass->dev, "");
scene_manager_next_scene(picopass->scene_manager, PicopassSceneCardMenu);
consumed = true;
}
}
return consumed;
}
void picopass_scene_write_card_success_on_exit(void* context) {
Picopass* picopass = context;
// Clear view
widget_reset(picopass->widget);
}
@@ -0,0 +1,12 @@
App(
appid="snake_game",
name="Snake Game",
apptype=FlipperAppType.PLUGIN,
entry_point="snake_game_app",
cdefines=["APP_SNAKE_GAME"],
requires=["gui"],
stack_size=1 * 1024,
order=30,
fap_icon="snake_10px.png",
fap_category="Games",
)
Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

@@ -0,0 +1,387 @@
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
typedef struct {
// +-----x
// |
// |
// y
uint8_t x;
uint8_t y;
} Point;
typedef enum {
GameStateLife,
// https://melmagazine.com/en-us/story/snake-nokia-6110-oral-history-taneli-armanto
// Armanto: While testing the early versions of the game, I noticed it was hard
// to control the snake upon getting close to and edge but not crashing — especially
// in the highest speed levels. I wanted the highest level to be as fast as I could
// possibly make the device "run," but on the other hand, I wanted to be friendly
// and help the player manage that level. Otherwise it might not be fun to play. So
// I implemented a little delay. A few milliseconds of extra time right before
// the player crashes, during which she can still change the directions. And if
// she does, the game continues.
GameStateLastChance,
GameStateGameOver,
} GameState;
// Note: do not change without purpose. Current values are used in smart
// orthogonality calculation in `snake_game_get_turn_snake`.
typedef enum {
DirectionUp,
DirectionRight,
DirectionDown,
DirectionLeft,
} Direction;
#define MAX_SNAKE_LEN 253
typedef struct {
Point points[MAX_SNAKE_LEN];
uint16_t len;
Direction currentMovement;
Direction nextMovement; // if backward of currentMovement, ignore
Point fruit;
GameState state;
} SnakeState;
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} SnakeEvent;
static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
const SnakeState* snake_state = acquire_mutex((ValueMutex*)ctx, 25);
if(snake_state == NULL) {
return;
}
// Before the function is called, the state is set with the canvas_reset(canvas)
// Frame
canvas_draw_frame(canvas, 0, 0, 128, 64);
// Fruit
Point f = snake_state->fruit;
f.x = f.x * 4 + 1;
f.y = f.y * 4 + 1;
canvas_draw_rframe(canvas, f.x, f.y, 6, 6, 2);
// Snake
for(uint16_t i = 0; i < snake_state->len; i++) {
Point p = snake_state->points[i];
p.x = p.x * 4 + 2;
p.y = p.y * 4 + 2;
canvas_draw_box(canvas, p.x, p.y, 4, 4);
}
// Game Over banner
if(snake_state->state == GameStateGameOver) {
// Screen is 128x64 px
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 34, 20, 62, 24);
canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, 34, 20, 62, 24);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 37, 31, "Game Over");
canvas_set_font(canvas, FontSecondary);
char buffer[12];
snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7);
canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer);
}
release_mutex((ValueMutex*)ctx, snake_state);
}
static void snake_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
SnakeEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void snake_game_update_timer_callback(FuriMessageQueue* event_queue) {
furi_assert(event_queue);
SnakeEvent event = {.type = EventTypeTick};
furi_message_queue_put(event_queue, &event, 0);
}
static void snake_game_init_game(SnakeState* const snake_state) {
Point p[] = {{8, 6}, {7, 6}, {6, 6}, {5, 6}, {4, 6}, {3, 6}, {2, 6}};
memcpy(snake_state->points, p, sizeof(p));
snake_state->len = 7;
snake_state->currentMovement = DirectionRight;
snake_state->nextMovement = DirectionRight;
Point f = {18, 6};
snake_state->fruit = f;
snake_state->state = GameStateLife;
}
static Point snake_game_get_new_fruit(SnakeState const* const snake_state) {
// 1 bit for each point on the playing field where the snake can turn
// and where the fruit can appear
uint16_t buffer[8];
memset(buffer, 0, sizeof(buffer));
uint8_t empty = 8 * 16;
for(uint16_t i = 0; i < snake_state->len; i++) {
Point p = snake_state->points[i];
if(p.x % 2 != 0 || p.y % 2 != 0) {
continue;
}
p.x /= 2;
p.y /= 2;
buffer[p.y] |= 1 << p.x;
empty--;
}
// Bit set if snake use that playing field
uint16_t newFruit = rand() % empty;
// Skip random number of _empty_ fields
for(uint8_t y = 0; y < 8; y++) {
for(uint16_t x = 0, mask = 1; x < 16; x += 1, mask <<= 1) {
if((buffer[y] & mask) == 0) {
if(newFruit == 0) {
Point p = {
.x = x * 2,
.y = y * 2,
};
return p;
}
newFruit--;
}
}
}
// We will never be here
Point p = {0, 0};
return p;
}
static bool snake_game_collision_with_frame(Point const next_step) {
// if x == 0 && currentMovement == left then x - 1 == 255 ,
// so check only x > right border
return next_step.x > 30 || next_step.y > 14;
}
static bool
snake_game_collision_with_tail(SnakeState const* const snake_state, Point const next_step) {
for(uint16_t i = 0; i < snake_state->len; i++) {
Point p = snake_state->points[i];
if(p.x == next_step.x && p.y == next_step.y) {
return true;
}
}
return false;
}
static Direction snake_game_get_turn_snake(SnakeState const* const snake_state) {
// Sum of two `Direction` lies between 0 and 6, odd values indicate orthogonality.
bool is_orthogonal = (snake_state->currentMovement + snake_state->nextMovement) % 2 == 1;
return is_orthogonal ? snake_state->nextMovement : snake_state->currentMovement;
}
static Point snake_game_get_next_step(SnakeState const* const snake_state) {
Point next_step = snake_state->points[0];
switch(snake_state->currentMovement) {
// +-----x
// |
// |
// y
case DirectionUp:
next_step.y--;
break;
case DirectionRight:
next_step.x++;
break;
case DirectionDown:
next_step.y++;
break;
case DirectionLeft:
next_step.x--;
break;
}
return next_step;
}
static void snake_game_move_snake(SnakeState* const snake_state, Point const next_step) {
memmove(snake_state->points + 1, snake_state->points, snake_state->len * sizeof(Point));
snake_state->points[0] = next_step;
}
static void snake_game_process_game_step(SnakeState* const snake_state) {
if(snake_state->state == GameStateGameOver) {
return;
}
bool can_turn = (snake_state->points[0].x % 2 == 0) && (snake_state->points[0].y % 2 == 0);
if(can_turn) {
snake_state->currentMovement = snake_game_get_turn_snake(snake_state);
}
Point next_step = snake_game_get_next_step(snake_state);
bool crush = snake_game_collision_with_frame(next_step);
if(crush) {
if(snake_state->state == GameStateLife) {
snake_state->state = GameStateLastChance;
return;
} else if(snake_state->state == GameStateLastChance) {
snake_state->state = GameStateGameOver;
return;
}
} else {
if(snake_state->state == GameStateLastChance) {
snake_state->state = GameStateLife;
}
}
crush = snake_game_collision_with_tail(snake_state, next_step);
if(crush) {
snake_state->state = GameStateGameOver;
return;
}
bool eatFruit = (next_step.x == snake_state->fruit.x) && (next_step.y == snake_state->fruit.y);
if(eatFruit) {
snake_state->len++;
if(snake_state->len >= MAX_SNAKE_LEN) {
snake_state->state = GameStateGameOver;
return;
}
}
snake_game_move_snake(snake_state, next_step);
if(eatFruit) {
snake_state->fruit = snake_game_get_new_fruit(snake_state);
}
}
int32_t snake_game_app(void* p) {
UNUSED(p);
srand(DWT->CYCCNT);
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent));
SnakeState* snake_state = malloc(sizeof(SnakeState));
snake_game_init_game(snake_state);
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, snake_state, sizeof(SnakeState))) {
FURI_LOG_E("SnakeGame", "cannot create mutex\r\n");
free(snake_state);
return 255;
}
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, snake_game_render_callback, &state_mutex);
view_port_input_callback_set(view_port, snake_game_input_callback, event_queue);
FuriTimer* timer =
furi_timer_alloc(snake_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4);
// Open GUI and register view_port
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
SnakeEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
SnakeState* snake_state = (SnakeState*)acquire_mutex_block(&state_mutex);
if(event_status == FuriStatusOk) {
// press events
if(event.type == EventTypeKey) {
if(event.input.type == InputTypePress) {
switch(event.input.key) {
case InputKeyUp:
snake_state->nextMovement = DirectionUp;
break;
case InputKeyDown:
snake_state->nextMovement = DirectionDown;
break;
case InputKeyRight:
snake_state->nextMovement = DirectionRight;
break;
case InputKeyLeft:
snake_state->nextMovement = DirectionLeft;
break;
case InputKeyOk:
if(snake_state->state == GameStateGameOver) {
snake_game_init_game(snake_state);
}
break;
case InputKeyBack:
processing = false;
break;
}
}
} else if(event.type == EventTypeTick) {
snake_game_process_game_step(snake_state);
}
} else {
// event timeout
}
view_port_update(view_port);
release_mutex(&state_mutex, snake_state);
}
furi_timer_free(timer);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close(RECORD_GUI);
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
free(snake_state);
return 0;
}
// Screen is 128x64 px
// (4 + 4) * 16 - 4 + 2 + 2border == 128
// (4 + 4) * 8 - 4 + 2 + 2border == 64
// Game field from point{x: 0, y: 0} to point{x: 30, y: 14}.
// The snake turns only in even cells - intersections.
// ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// ╎ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ▪ ╎
// └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘