mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-18 00:38:10 -07:00
my changes for some plugins and 2048
This commit is contained in:
@@ -1 +1,69 @@
|
||||
## Doing a direct unleashed fork to implement FAPS and all the old stuff will be re-implemented.
|
||||
|
||||
To Be Added/To Verify Present:
|
||||
|
||||
- Games & Plugins?
|
||||
- Assets: Running DolphinRestorer.fap on new install will auto-level to the end of Level 2.
|
||||
- GPIO: Feature to read EEPROM of SFP Modules using I2C [(By marcusju)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/198)
|
||||
- Development free space thanks to removal of unused debug tools and [thanks to ESurge](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/46/files) for removal of first start assets.
|
||||
- Plugins: Menu, Icons, and Raycast exit [Thanks to Redlink](https://github.com/redlink2/flipperzero-firmware/tree/menuChanges)
|
||||
- Settings: Favorite Game by holding UP on Desktop [Thanks to gotnull](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/57)
|
||||
- Settings: Hold Down for Games Menu [(Thanks to ESurge)](https://github.com/ESurge/flipperzero-firmware-wPlugins)
|
||||
- Settings: Rename from SD `dolphin/name.txt` [(Thanks to E_Surge)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/259)
|
||||
- Settings: Scan names will have timestamp instead of random name assigned for [NFC](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/lib/toolbox/random_name.c) and [SubGHz](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/applications/subghz/scenes/subghz_scene_read_raw.c) (By RogueMaster)
|
||||
- SubGHz: Auto Detect Raw in READ action (Needs To Be Enabled In Read Settings) [from perspecdev](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/152)
|
||||
- SubGHz: [Add settings to subghz read functionality to allow setting RSSI threshold (raw only) (By PolymerPrints)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/184)
|
||||
|
||||
Implemented:
|
||||
|
||||
- Animations: Hold Center to change Flipper idle animation. [Thanks to Zycenios](https://github.com/flipperdevices/flipperzero-firmware/commit/111786ef40e50a40d2e510595672b569d9b97bba) With changes by RogueMaster.
|
||||
- Animations: iButton and RFID P0kem0n images [Thanks to Panzer00Z](https://github.com/Panzer00Z/flipperzero-firmware/)
|
||||
- Animations: Idle animations will show all animations regardless of level and butthurt [Thanks to qqMajiKpp]
|
||||
- Animations: RM FW Update image [(Thanks to E_Surge)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/257)
|
||||
- Animations: SubGHZ Scanning image with Pikachu [Thanks to Panzer00Z](https://github.com/Panzer00Z/flipperzero-firmware/blob/3a548ea9bb181c9348d8afb427890c411456134e/assets/icons/SubGhz/Scanning_123x52.png)
|
||||
- Assets: Includes a NFC Level 50 Gan0n Amiibo
|
||||
- Assets: Includes a NFC Rick Roll link
|
||||
- Assets: Includes New Dolphin Animations: [Rick Roll, Matrix & Swim animations (By qqMajiKpp)], [Rukamon 2x and Agumon animations (By Syrius)], [Mew (By Arkaivos)], [Eye of the Flipper (By Kuronons)], [Shodan (By qqMajiKpp)], [Sirene & The Witch (By Haseo)], [Dino & A New Hope (By Haseo)], [Earth Arcadia (By Kuronons)], [Kuronons Black Flags Collection (21 Animations)(By Kuronons)], [D.Va (By Haseo)], [GITS (By Haseo)], [Slayers (By qqMajiKpp)], [P0liwhirl (By Panzer00Z)], [RogueMaster CFW Animation (By Kuronons)], [Nyan Cat (By Haseo)], [L (By Kuronons)], [Laughing Man (By Kuronons)], [B0ws3r (By Haseo)], [Kuronons Black Flags Collection VOL 2 (9 Animations)(By Kuronons)], [Thanks for all the fish (By qqMajiKpp)], [OCP, Skynet and Weyland (By Kuronons)], [Mario (By Haseo)], [Umbrella (By Haseo)], [Trioptimum (By qqMajiKpp)], [Tyrell & Cyberdyne (By Kuronons)], [Allen & Maha (By Haseo)], [Starfield (By qqMajiKpp)] & [DJ (By ut1s)]
|
||||
- Assets: Includes sample Music Player tunes
|
||||
- Assets: Includes sample SubGHz assets for Crosswalk, Handicap Doors, Sextoys, Tesla Charge Port, and Unitree Go1 Robot Dog
|
||||
- Assets: Includes Sonic Screw Driver sound for the Wav Player
|
||||
- BadUSB: Added ignore DUCKY_LANG cmd to retain compatibility with existing scripts [(Thanks to v1nc)](https://github.com/v1nc/flipperzero-firmware)
|
||||
- BadUSB: Assets for Kiosk Evasion and Wifi Stealer
|
||||
- BadUSB: sk-SK maping keybord for BadUsb [(By jaroslavmraz)](https://github.com/flipperdevices/flipperzero-firmware/pull/1619)
|
||||
- BadUSB: pt-BR maping keybord for BadUsb [(By web-mancha)](https://github.com/Eng1n33r/flipperzero-firmware/pull/65)
|
||||
- BadUSB: show script errors on screen [(By CromFr)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/200)
|
||||
- Dolphin: Assigned profile pic for levels 1-10, 11-20 and 21-30 respectively from default lvl 1, 2 & 3
|
||||
- Dolphin: Expanded max level from 3 to 30 using [Roll20](https://roll20.net/compendium/dnd5e/Monsters#h-Experience%20Points), Increased max deed XP per action type from 15 to 45 exp daily & updated animation manifest for max level 30 for all animations (By RogueMaster)
|
||||
- Dolphin: Internal manifest updated to have animations always available with max level 30
|
||||
- Dolphin: Level animation sequence for level 3+ to be lvl1->2 for levels 1-20 and lvl2->3 for levels 21-30.
|
||||
- Dolphin: Mood Stays Happy [(Thanks to biocage)](https://github.com/biocage/flipperzero-firmware), changed to Mood Stays Less Than 6 / Mood Stays High (but not in game mode) (By RogueMaster)
|
||||
- Dolphin: Passport: DB Themed [(Thanks to Kuronons)]
|
||||
- Dolphin: Passport: Show EXP [(By Dabolus)](https://github.com/Dabolus/flipperzero-firmware-rpg/)
|
||||
- Dolphin: Changed daily MAX to 198 on all 7 Deed Types. Random Deed Selection used for MAX +3 EXP daily gain.
|
||||
- Dolphin: Plugin Achivement +3 EXP for a total of up to 700 EXP daily. Plugin Achivements are:
|
||||
- - Dice First Roll for d20+ = sides on dice (i.e. Nat 20 on d20)
|
||||
- - Dice First Roll for d20+ = sides on dice - 1 (+1 EXP) (i.e. 19 on d20)
|
||||
- - Games Only Mode from Lock Menu
|
||||
- - Getting 2048 in 2048
|
||||
- - Mouse Jacker Ducky Run
|
||||
- - NRFSniffer Found Address
|
||||
- - Stopwatch @ Alert
|
||||
- - Tetris, Snake, or Flappy Bird Score For EXP
|
||||
- - Zombiez for every 20 Zombie kills
|
||||
- Dolphin: SD dolphin manifest updated to weight animations differently
|
||||
- Icon Decode/Encode [(Thanks to PixlEmly)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/55/files)
|
||||
- IR: Universal AC, Audio, Fans & Projectors from [Unleashed/Eng1n33r](https://github.com/Eng1n33r/flipperzero-firmware)
|
||||
- Plugins: 2048, Arkanoid, Snake, and Tetris show score. Thanks to [whoamins](https://github.com/flipperdevices/flipperzero-firmware/commit/7feda832ede1ba8468eff2ca055fef3ddbdc16ac) and [DevMilanIan](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/188) With position changes by RogueMaster. Also all + Tic Tac Toe updated by [Unleashed/Eng1n33r](https://github.com/Eng1n33r/flipperzero-firmware) for stability.
|
||||
- Settings: Actual PIN Lock [(By RogueMaster)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/applications/desktop/desktop.c)
|
||||
- Settings: Auto-Lock Options Added: 10s+15s+90s [(By RogueMaster)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/applications/desktop/desktop_settings/scenes/desktop_settings_scene_start.c)
|
||||
- Settings: Battery Meter on Desktop [Thanks to McAzzaMan](https://github.com/McAzzaMan/flipperzero-firmware/tree/BatteryPercentageView)
|
||||
- Settings: Custom name with this compile: CUSTOM_FLIPPER_NAME=name ./fbt updater_package [By Unleashed/xMasterX](https://github.com/Eng1n33r/flipperzero-firmware)
|
||||
- Settings: Desktop => [Games Only Mode (By RogueMaster)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/GAMES_ONLY.md) [(Thanks to Astrrra for Inverted Mode to Mimic)](https://github.com/wetox-team/flipperzero-firmware/commit/ce91582b7417c5d7a9d8416c17a102d3a5868238)
|
||||
- - [UP UP DOWN DOWN LEFT RIGHT LEFT RIGHT FROM CLOCK](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/GAMES_ONLY.md)<== FULL LIST OF GAMES ONLY CONTROLS
|
||||
- Settings: "DUMB Mode" is now "Lock W PIN + Off" [(By RogueMaster)]
|
||||
- Settings: LCD Timeout Options Added: 10s+90s+2min+5min+10min [(By RogueMaster)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/applications/notification/notification_settings_app.c)
|
||||
- SubGHz: Extended ranges enabled through flag in /ext/subghz/assets/extend_range.txt [from tkerrby](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/116)
|
||||
- SubGHz: Moved setting_user file to setting_user.txt! This makes it changable from IOS app. (By RogueMaster)
|
||||
- SubGHz: New frequency analyzer [(By ClusterM)](https://github.com/flipperdevices/flipperzero-firmware/pull/1501) [feedback mode (by darmiel)](https://github.com/darmiel/flipper-playlist/tree/feat/stealth-frequency-analyzer)
|
||||
- SubGHz: Protocols An-Motors, BFT Mitto, Came Atomo, FAAC SLH (Spa), HCS101, Keeloq, Keeloq Common, Nice Flor S, SecPlus v1+v2 and Star Line updates from [Eng1n33r](https://github.com/Eng1n33r/flipperzero-firmware)
|
||||
- SubGHz: Unlock from SD flag from [(cloudbreakdaniel)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/commit/97db0dc91ee3dff812b4dec0618e3f198de14405). Update `subghz/assets/extend_range.txt` with [this file](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/assets/resources/subghz/assets/extend_range.txt) on SD. **UPDATE IGNORE FLAG TO TRUE TO UNLEASH YOUR FLIPPER!!**
|
||||
@@ -113,6 +113,7 @@ static const char ducky_cmd_string[] = {"STRING "};
|
||||
static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "};
|
||||
static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "};
|
||||
static const char ducky_cmd_repeat[] = {"REPEAT "};
|
||||
static const char ducky_cmd_sysrq[] = {"SYSRQ "};
|
||||
|
||||
static const char ducky_cmd_altchar[] = {"ALTCHAR "};
|
||||
static const char ducky_cmd_altstr_1[] = {"ALTSTRING "};
|
||||
@@ -236,7 +237,8 @@ static uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
|
||||
static int32_t
|
||||
ducky_parse_line(BadUsbScript* bad_usb, string_t line, char* error, size_t error_len) {
|
||||
uint32_t line_len = string_size(line);
|
||||
const char* line_tmp = string_get_cstr(line);
|
||||
bool state = false;
|
||||
@@ -269,6 +271,9 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
|
||||
if((state) && (delay_val > 0)) {
|
||||
return (int32_t)delay_val;
|
||||
}
|
||||
if(error != NULL) {
|
||||
snprintf(error, error_len, "Invalid number %s", line_tmp);
|
||||
}
|
||||
return SCRIPT_STATE_ERROR;
|
||||
} else if(
|
||||
(strncmp(line_tmp, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) ||
|
||||
@@ -276,17 +281,26 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
|
||||
// DEFAULT_DELAY
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
state = ducky_get_number(line_tmp, &bad_usb->defdelay);
|
||||
if(!state && error != NULL) {
|
||||
snprintf(error, error_len, "Invalid number %s", line_tmp);
|
||||
}
|
||||
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
|
||||
// STRING
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
state = ducky_string(bad_usb, line_tmp);
|
||||
if(!state && error != NULL) {
|
||||
snprintf(error, error_len, "Invalid string %s", line_tmp);
|
||||
}
|
||||
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) {
|
||||
// ALTCHAR
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
ducky_numlock_on();
|
||||
state = ducky_altchar(line_tmp);
|
||||
if(!state && error != NULL) {
|
||||
snprintf(error, error_len, "Invalid altchar %s", line_tmp);
|
||||
}
|
||||
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||
} else if(
|
||||
(strncmp(line_tmp, ducky_cmd_altstr_1, strlen(ducky_cmd_altstr_1)) == 0) ||
|
||||
@@ -295,16 +309,35 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
ducky_numlock_on();
|
||||
state = ducky_altstring(line_tmp);
|
||||
if(!state && error != NULL) {
|
||||
snprintf(error, error_len, "Invalid altstring %s", line_tmp);
|
||||
}
|
||||
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) {
|
||||
// REPEAT
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt);
|
||||
if(!state && error != NULL) {
|
||||
snprintf(error, error_len, "Invalid number %s", line_tmp);
|
||||
}
|
||||
return (state) ? (0) : SCRIPT_STATE_ERROR;
|
||||
} else if(strncmp(line_tmp, ducky_cmd_sysrq, strlen(ducky_cmd_sysrq)) == 0) {
|
||||
// SYSRQ
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, true);
|
||||
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
|
||||
furi_hal_hid_kb_press(key);
|
||||
furi_hal_hid_kb_release_all();
|
||||
return (0);
|
||||
} else {
|
||||
// Special keys + modifiers
|
||||
uint16_t key = ducky_get_keycode(bad_usb, line_tmp, false);
|
||||
if(key == HID_KEYBOARD_NONE) return SCRIPT_STATE_ERROR;
|
||||
if(key == HID_KEYBOARD_NONE) {
|
||||
if(error != NULL) {
|
||||
snprintf(error, error_len, "No keycode defined for %s", line_tmp);
|
||||
}
|
||||
return SCRIPT_STATE_ERROR;
|
||||
}
|
||||
if((key & 0xFF00) != 0) {
|
||||
// It's a modifier key
|
||||
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
|
||||
@@ -314,6 +347,9 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
|
||||
furi_hal_hid_kb_release(key);
|
||||
return (0);
|
||||
}
|
||||
if(error != NULL) {
|
||||
strncpy(error, "Unknown error", error_len);
|
||||
}
|
||||
return SCRIPT_STATE_ERROR;
|
||||
}
|
||||
|
||||
@@ -392,7 +428,8 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil
|
||||
|
||||
if(bad_usb->repeat_cnt > 0) {
|
||||
bad_usb->repeat_cnt--;
|
||||
delay_val = ducky_parse_line(bad_usb, bad_usb->line_prev);
|
||||
delay_val = ducky_parse_line(
|
||||
bad_usb, bad_usb->line_prev, bad_usb->st.error, sizeof(bad_usb->st.error));
|
||||
if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
|
||||
return 0;
|
||||
} else if(delay_val < 0) { // Script error
|
||||
@@ -426,10 +463,20 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil
|
||||
bad_usb->st.line_cur++;
|
||||
bad_usb->buf_len = bad_usb->buf_len + bad_usb->buf_start - (i + 1);
|
||||
bad_usb->buf_start = i + 1;
|
||||
delay_val = ducky_parse_line(bad_usb, bad_usb->line);
|
||||
delay_val = ducky_parse_line(
|
||||
bad_usb, bad_usb->line, bad_usb->st.error, sizeof(bad_usb->st.error));
|
||||
|
||||
if(delay_val < 0) {
|
||||
bad_usb->st.error_line = bad_usb->st.line_cur;
|
||||
FURI_LOG_E(WORKER_TAG, "Unknown command at line %lu", bad_usb->st.line_cur);
|
||||
if(delay_val == SCRIPT_STATE_NEXT_LINE) {
|
||||
snprintf(
|
||||
bad_usb->st.error, sizeof(bad_usb->st.error), "Forbidden empty line");
|
||||
FURI_LOG_E(
|
||||
WORKER_TAG, "Forbidden empty line at line %lu", bad_usb->st.line_cur);
|
||||
} else {
|
||||
FURI_LOG_E(
|
||||
WORKER_TAG, "Unknown command at line %lu", bad_usb->st.line_cur);
|
||||
}
|
||||
return SCRIPT_STATE_ERROR;
|
||||
} else {
|
||||
return (delay_val + bad_usb->defdelay);
|
||||
@@ -609,6 +656,7 @@ BadUsbScript* bad_usb_script_open(string_t file_path) {
|
||||
bad_usb_script_set_default_keyboard_layout(bad_usb);
|
||||
|
||||
bad_usb->st.state = BadUsbStateInit;
|
||||
bad_usb->st.error[0] = '\0';
|
||||
|
||||
bad_usb->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(bad_usb->thread, "BadUsbWorker");
|
||||
|
||||
@@ -26,6 +26,7 @@ typedef struct {
|
||||
uint16_t line_nb;
|
||||
uint32_t delay_remain;
|
||||
uint16_t error_line;
|
||||
char error[64];
|
||||
} BadUsbState;
|
||||
|
||||
BadUsbScript* bad_usb_script_open(string_t file_path);
|
||||
|
||||
@@ -14,13 +14,15 @@ static bool bad_usb_layout_select(BadUsbApp* bad_usb) {
|
||||
string_set_str(predefined_path, BAD_USB_APP_PATH_LAYOUT_FOLDER);
|
||||
}
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, BAD_USB_APP_LAYOUT_EXTENSION, &I_keyboard_10px);
|
||||
|
||||
// Input events and views are managed by file_browser
|
||||
bool res = dialog_file_browser_show(
|
||||
bad_usb->dialogs, bad_usb->keyboard_layout, predefined_path, &browser_options);
|
||||
bad_usb->dialogs,
|
||||
bad_usb->keyboard_layout,
|
||||
predefined_path,
|
||||
BAD_USB_APP_LAYOUT_EXTENSION,
|
||||
true,
|
||||
&I_keyboard_10px,
|
||||
true);
|
||||
|
||||
string_clear(predefined_path);
|
||||
return res;
|
||||
|
||||
@@ -5,13 +5,15 @@
|
||||
static bool bad_usb_file_select(BadUsbApp* bad_usb) {
|
||||
furi_assert(bad_usb);
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, BAD_USB_APP_SCRIPT_EXTENSION, &I_badusb_10px);
|
||||
|
||||
// Input events and views are managed by file_browser
|
||||
bool res = dialog_file_browser_show(
|
||||
bad_usb->dialogs, bad_usb->file_path, bad_usb->file_path, &browser_options);
|
||||
bad_usb->dialogs,
|
||||
bad_usb->file_path,
|
||||
bad_usb->file_path,
|
||||
BAD_USB_APP_SCRIPT_EXTENSION,
|
||||
true,
|
||||
&I_badusb_10px,
|
||||
true);
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -46,4 +48,4 @@ bool bad_usb_scene_file_select_on_event(void* context, SceneManagerEvent event)
|
||||
void bad_usb_scene_file_select_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
// BadUsbApp* bad_usb = context;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
elements_string_fit_width(canvas, disp_str, 128 - 2);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 2, 8, string_get_cstr(disp_str));
|
||||
string_reset(disp_str);
|
||||
|
||||
if(strlen(model->layout) == 0) {
|
||||
string_set(disp_str, "(default)");
|
||||
@@ -39,8 +40,6 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
elements_string_fit_width(canvas, disp_str, 128 - 2);
|
||||
canvas_draw_str(canvas, 2, 8 + canvas_current_font_height(canvas), string_get_cstr(disp_str));
|
||||
|
||||
string_reset(disp_str);
|
||||
|
||||
canvas_draw_icon(canvas, 22, 24, &I_UsbTree_48x22);
|
||||
|
||||
if((model->state.state == BadUsbStateIdle) || (model->state.state == BadUsbStateDone)) {
|
||||
@@ -73,6 +72,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 127, 46, AlignRight, AlignBottom, string_get_cstr(disp_str));
|
||||
string_reset(disp_str);
|
||||
canvas_draw_str_aligned(canvas, 127, 56, AlignRight, AlignBottom, model->state.error);
|
||||
} else if(model->state.state == BadUsbStateIdle) {
|
||||
canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18);
|
||||
canvas_set_font(canvas, FontBigNumbers);
|
||||
@@ -188,6 +188,7 @@ void bad_usb_set_layout(BadUsb* bad_usb, const char* layout) {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) {
|
||||
furi_assert(st);
|
||||
with_view_model(
|
||||
@@ -196,4 +197,4 @@ void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st) {
|
||||
model->anim_frame ^= 1;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#include "bird.h"
|
||||
|
||||
@@ -237,9 +238,9 @@ static void flappy_game_render_callback(Canvas* const canvas, void* ctx) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 37, 31, "Game Over");
|
||||
|
||||
/*if(game_state->points != 0 && game_state->points % 5 == 0) {
|
||||
if(game_state->points != 0 && game_state->points % 5 == 0) {
|
||||
DOLPHIN_DEED(getRandomDeed());
|
||||
}*/
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
char buffer[12];
|
||||
@@ -334,7 +335,7 @@ int32_t flappy_game_app(void* p) {
|
||||
flappy_game_tick(game_state);
|
||||
}
|
||||
} else {
|
||||
//FURI_LOG_D(TAG, "osMessageQueue: event timeout");
|
||||
// FURI_LOG_D(TAG, "osMessageQueue: event timeout");
|
||||
// event timeout
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 522 B |
@@ -0,0 +1,12 @@
|
||||
App(
|
||||
appid="GAME_2048",
|
||||
name="2048",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="game_2048_app",
|
||||
cdefines=["APP_2048_GAME"],
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=10,
|
||||
fap_icon="2048.png",
|
||||
fap_category="Games",
|
||||
)
|
||||
@@ -0,0 +1,155 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <gui/canvas.h>
|
||||
|
||||
/* 7px 3 width digit font by Sefjor
|
||||
* digit encoding example
|
||||
*7 ¦¦¦ 111
|
||||
*6 ¦ ¦ 101
|
||||
*5 ¦ ¦ 101
|
||||
*4 ¦ ¦ 101
|
||||
*3 ¦ ¦ 101
|
||||
*2 ¦ ¦ 101
|
||||
*1 ¦¦¦ 111
|
||||
*0 000 this string is empty, used to align
|
||||
* ? ? ?
|
||||
* FE 82 FE //0
|
||||
*/
|
||||
|
||||
static uint8_t font[10][3] = {
|
||||
{0xFE, 0x82, 0xFE}, // 0;
|
||||
{0x00, 0xFE, 0x00}, // 1;
|
||||
{0xF2, 0x92, 0x9E}, // 2;
|
||||
{0x92, 0x92, 0xFE}, // 3;
|
||||
{0x1E, 0x10, 0xFE}, // 4;
|
||||
{0x9E, 0x92, 0xF2}, // 5;
|
||||
{0xFE, 0x92, 0xF2}, // 6;
|
||||
{0x02, 0x02, 0xFE}, // 7;
|
||||
{0xFE, 0x92, 0xFE}, // 8;
|
||||
{0x9E, 0x92, 0xFE}, // 9;
|
||||
};
|
||||
|
||||
#define FONT_HEIGHT 8
|
||||
#define FONT_WIDTH 3
|
||||
|
||||
static void game_2048_draw_black_point(Canvas* const canvas, uint8_t x, uint8_t y) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_dot(canvas, x, y);
|
||||
}
|
||||
|
||||
static void game_2048_draw_white_square(Canvas* const canvas, uint8_t x, uint8_t y) {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x, y, 15 - 1, 15 - 3);
|
||||
}
|
||||
|
||||
static void _game_2048_draw_column(
|
||||
Canvas* const canvas,
|
||||
int digit,
|
||||
int coord_x,
|
||||
int coord_y,
|
||||
uint8_t column) {
|
||||
for(int x = 0; x < FONT_HEIGHT; ++x) {
|
||||
bool is_filled = (font[digit][column] >> x) & 0x1;
|
||||
if(is_filled) {
|
||||
game_2048_draw_black_point(canvas, coord_x, coord_y + x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
_game_2048_draw_digit(Canvas* const canvas, uint8_t digit, uint8_t coord_x, uint8_t coord_y) {
|
||||
uint8_t x_shift = 0;
|
||||
|
||||
if(digit != 1) {
|
||||
for(int column = 0; column < FONT_WIDTH; column++) {
|
||||
_game_2048_draw_column(canvas, digit, coord_x + column, coord_y, column);
|
||||
}
|
||||
x_shift = 3;
|
||||
} else {
|
||||
_game_2048_draw_column(canvas, digit, coord_x, coord_y, true);
|
||||
x_shift = 1;
|
||||
}
|
||||
|
||||
return x_shift;
|
||||
}
|
||||
|
||||
/* We drawing text field with 1px white border
|
||||
* at given coords. Total size is:
|
||||
* x = 9 = 1 + 7 + 1
|
||||
* y = 1 + total text width + 1
|
||||
*/
|
||||
|
||||
/*
|
||||
* Returns array of digits and it's size,
|
||||
* digits should be at least 4 size
|
||||
* works from 1 to 9999
|
||||
*/
|
||||
static void _game_2048_parse_number(uint16_t number, uint8_t* digits, uint8_t* size) {
|
||||
*size = 0;
|
||||
uint16_t divider = 1000;
|
||||
//find first digit, result is highest divider
|
||||
while(number / divider == 0) {
|
||||
divider /= 10;
|
||||
if(divider == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; divider != 0; i++) {
|
||||
digits[i] = number / divider;
|
||||
number %= divider;
|
||||
*size += 1;
|
||||
divider /= 10;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t _game_2048_calculate_shift(uint16_t num) {
|
||||
uint8_t shift = 0;
|
||||
switch(num) {
|
||||
case 1:
|
||||
shift = 7;
|
||||
break;
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
shift = 6;
|
||||
break;
|
||||
case 16:
|
||||
shift = 5;
|
||||
break;
|
||||
case 32:
|
||||
case 64:
|
||||
shift = 4;
|
||||
break;
|
||||
case 128:
|
||||
shift = 3;
|
||||
break;
|
||||
case 256:
|
||||
shift = 2;
|
||||
break;
|
||||
case 512:
|
||||
shift = 3;
|
||||
break;
|
||||
case 1024:
|
||||
shift = 2;
|
||||
break;
|
||||
}
|
||||
return shift;
|
||||
}
|
||||
|
||||
void game_2048_draw_number(Canvas* const canvas, uint8_t x, uint8_t y, int number) {
|
||||
uint8_t digits[4];
|
||||
uint8_t size;
|
||||
|
||||
_game_2048_parse_number(number, digits, &size);
|
||||
if(number > 512) {
|
||||
game_2048_draw_white_square(canvas, x, y);
|
||||
}
|
||||
|
||||
x += _game_2048_calculate_shift(number);
|
||||
y += 4;
|
||||
for(int i = 0; i < size; ++i) {
|
||||
x += _game_2048_draw_digit(canvas, digits[i], x, y);
|
||||
x++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#include <gui/canvas.h>
|
||||
|
||||
void game_2048_draw_number(Canvas* const canvas, uint8_t x, uint8_t y, int number);
|
||||
@@ -0,0 +1,493 @@
|
||||
#include <stdint.h>
|
||||
#include <gui/gui.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#include "font.h"
|
||||
|
||||
#define DEBUG false
|
||||
/*
|
||||
0 empty
|
||||
1 2
|
||||
2 4
|
||||
3 8
|
||||
4 16
|
||||
5 32
|
||||
6 64
|
||||
7 128
|
||||
8 256
|
||||
9 512
|
||||
10 1024
|
||||
11 2048
|
||||
12 4096
|
||||
...
|
||||
*/
|
||||
typedef uint8_t cell_state;
|
||||
|
||||
/* DirectionLeft <--
|
||||
┌╌╌╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐
|
||||
╎ ╎╎ ╎╎ ╎╎ ╎
|
||||
└╌╌╌╌┘└╌╌╌╌┘└╌╌╌╌┘└╌╌╌╌┘
|
||||
┌╌╌╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐
|
||||
╎ ╎╎ ╎╎ ╎╎ ╎
|
||||
└╌╌╌╌┘└╌╌╌╌┘└╌╌╌╌┘└╌╌╌╌┘
|
||||
┌╌╌┌╌╌╌╌┐╌╌┐┌╌╌╌╌┐┌╌╌╌╌┐
|
||||
╎ 2╎ 2 ╎ ╎╎ ╎╎ ╎
|
||||
└╌╌└╌╌╌╌┘╌╌┘└╌╌╌╌┘└╌╌╌╌┘
|
||||
┌╌╌┌╌╌╌╌┐┌╌╌┌╌╌╌╌┐┌╌╌╌╌┐
|
||||
╎ 4╎ 4 ╎╎ 2╎ 2 ╎╎ ╎
|
||||
└╌╌└╌╌╌╌┘└╌╌└╌╌╌╌┘└╌╌╌╌┘
|
||||
*/
|
||||
typedef enum {
|
||||
DirectionIdle,
|
||||
DirectionUp,
|
||||
DirectionRight,
|
||||
DirectionDown,
|
||||
DirectionLeft,
|
||||
} Direction;
|
||||
|
||||
typedef struct {
|
||||
uint8_t y; // 0 <= y <= 3
|
||||
uint8_t x; // 0 <= x <= 3
|
||||
} Point;
|
||||
|
||||
typedef struct {
|
||||
uint32_t gameScore;
|
||||
uint32_t highScore;
|
||||
} Score;
|
||||
|
||||
typedef struct {
|
||||
/*
|
||||
+----X
|
||||
|
|
||||
| field[x][y]
|
||||
Y
|
||||
*/
|
||||
uint8_t field[4][4];
|
||||
|
||||
uint8_t next_field[4][4];
|
||||
|
||||
Score score; // original scoring
|
||||
|
||||
Direction direction;
|
||||
/*
|
||||
field {
|
||||
animation-timing-function: linear;
|
||||
animation-duration: 300ms;
|
||||
}
|
||||
*/
|
||||
uint32_t animation_start_ticks;
|
||||
|
||||
Point keyframe_from[4][4];
|
||||
|
||||
Point keyframe_to[4][4];
|
||||
|
||||
bool debug;
|
||||
|
||||
} GameState;
|
||||
|
||||
#define XtoPx(x) (33 + x * 15)
|
||||
|
||||
#define YtoPx(x) (1 + y * 15)
|
||||
|
||||
static void game_2048_render_callback(Canvas* const canvas, ValueMutex* const vm) {
|
||||
const GameState* game_state = acquire_mutex(vm, 25);
|
||||
if(game_state == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Before the function is called, the state is set with the canvas_reset(canvas)
|
||||
|
||||
if(game_state->direction == DirectionIdle) {
|
||||
for(uint8_t y = 0; y < 4; y++) {
|
||||
for(uint8_t x = 0; x < 4; x++) {
|
||||
uint8_t field = game_state->field[y][x];
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_frame(canvas, XtoPx(x), YtoPx(y), 16, 16);
|
||||
if(field != 0) {
|
||||
game_2048_draw_number(canvas, XtoPx(x), YtoPx(y), 1 << field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// display score
|
||||
char buffer[12];
|
||||
snprintf(buffer, sizeof(buffer), "%lu", game_state->score.gameScore);
|
||||
canvas_draw_str_aligned(canvas, 127, 8, AlignRight, AlignBottom, buffer);
|
||||
|
||||
if(game_state->score.highScore > 0) {
|
||||
char buffer2[12];
|
||||
snprintf(buffer2, sizeof(buffer2), "%lu", game_state->score.highScore);
|
||||
canvas_draw_str_aligned(canvas, 127, 62, AlignRight, AlignBottom, buffer2);
|
||||
}
|
||||
} else { // if animation
|
||||
// for animation
|
||||
// (osKernelGetSysTimerCount() - game_state->animation_start_ticks) / osKernelGetSysTimerFreq();
|
||||
|
||||
// TODO: end animation event/callback/set AnimationIdle
|
||||
}
|
||||
|
||||
release_mutex(vm, game_state);
|
||||
}
|
||||
|
||||
static void
|
||||
game_2048_input_callback(const InputEvent* const input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
|
||||
}
|
||||
|
||||
// if return false then Game Over
|
||||
static bool game_2048_set_new_number(GameState* const game_state) {
|
||||
uint8_t empty = 0;
|
||||
for(uint8_t y = 0; y < 4; y++) {
|
||||
for(uint8_t x = 0; x < 4; x++) {
|
||||
if(game_state->field[y][x] == 0) {
|
||||
empty++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(empty == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(empty == 1) {
|
||||
// If it is 1 move before losing, we help the player and get rid of randomness.
|
||||
for(uint8_t y = 0; y < 4; y++) {
|
||||
for(uint8_t x = 0; x < 4; x++) {
|
||||
if(game_state->field[y][x] == 0) {
|
||||
bool haveFour =
|
||||
// +----X
|
||||
// |
|
||||
// | field[x][y], 0 <= x, y <= 3
|
||||
// Y
|
||||
|
||||
// up == 4 or
|
||||
(y > 0 && game_state->field[y - 1][x] == 2) ||
|
||||
// right == 4 or
|
||||
(x < 3 && game_state->field[y][x + 1] == 2) ||
|
||||
// down == 4
|
||||
(y < 3 && game_state->field[y + 1][x] == 2) ||
|
||||
// left == 4
|
||||
(x > 0 && game_state->field[y][x - 1] == 2);
|
||||
|
||||
if(haveFour) {
|
||||
game_state->field[y][x] = 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
game_state->field[y][x] = 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t target = rand() % empty;
|
||||
uint8_t twoOrFore = rand() % 4 < 3;
|
||||
for(uint8_t y = 0; y < 4; y++) {
|
||||
for(uint8_t x = 0; x < 4; x++) {
|
||||
if(game_state->field[y][x] == 0) {
|
||||
if(target == 0) {
|
||||
if(twoOrFore) {
|
||||
game_state->field[y][x] = 1; // 2^1 == 2 75%
|
||||
} else {
|
||||
game_state->field[y][x] = 2; // 2^2 == 4 25%
|
||||
}
|
||||
goto exit;
|
||||
}
|
||||
target--;
|
||||
}
|
||||
}
|
||||
}
|
||||
exit:
|
||||
return true;
|
||||
}
|
||||
|
||||
// static void game_2048_process_row(uint8_t before[4], uint8_t *(after[4])) {
|
||||
// // move 1 row left.
|
||||
// for(uint8_t i = 0; i <= 2; i++) {
|
||||
// if(before[i] != 0 && before[i] == before[i + 1]) {
|
||||
// before[i]++;
|
||||
// before[i + 1] = 0;
|
||||
// i++;
|
||||
// }
|
||||
// }
|
||||
// for(uint8_t i = 0, j = 0; i <= 3; i++) {
|
||||
// if (before[i] != 0) {
|
||||
// before[j] = before[i];
|
||||
// i++;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
static void game_2048_process_move(GameState* const game_state) {
|
||||
memset(game_state->next_field, 0, sizeof(game_state->next_field));
|
||||
// +----X
|
||||
// |
|
||||
// | field[x][y], 0 <= x, y <= 3
|
||||
// Y
|
||||
|
||||
// up
|
||||
if(game_state->direction == DirectionUp) {
|
||||
for(uint8_t x = 0; x < 4; x++) {
|
||||
uint8_t next_y = 0;
|
||||
for(int8_t y = 0; y < 4; y++) {
|
||||
uint8_t field = game_state->field[y][x];
|
||||
if(field == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(game_state->next_field[next_y][x] == 0) {
|
||||
game_state->next_field[next_y][x] = field;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(field == game_state->next_field[next_y][x]) {
|
||||
game_state->next_field[next_y][x]++;
|
||||
game_state->score.gameScore += pow(2, game_state->next_field[next_y][x]);
|
||||
if(game_state->next_field[next_y][x] == 11 && !game_state->debug) {
|
||||
DOLPHIN_DEED(getRandomDeed());
|
||||
} // get some xp for making a 2048 tile
|
||||
next_y++;
|
||||
continue;
|
||||
}
|
||||
|
||||
next_y++;
|
||||
game_state->next_field[next_y][x] = field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// right
|
||||
if(game_state->direction == DirectionRight) {
|
||||
for(uint8_t y = 0; y < 4; y++) {
|
||||
uint8_t next_x = 3;
|
||||
for(int8_t x = 3; x >= 0; x--) {
|
||||
uint8_t field = game_state->field[y][x];
|
||||
if(field == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(game_state->next_field[y][next_x] == 0) {
|
||||
game_state->next_field[y][next_x] = field;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(field == game_state->next_field[y][next_x]) {
|
||||
game_state->next_field[y][next_x]++;
|
||||
game_state->score.gameScore += pow(2, game_state->next_field[y][next_x]);
|
||||
if(game_state->next_field[y][next_x] == 11 && !game_state->debug) {
|
||||
DOLPHIN_DEED(getRandomDeed());
|
||||
} // get some xp for making a 2048 tile
|
||||
next_x--;
|
||||
continue;
|
||||
}
|
||||
|
||||
next_x--;
|
||||
game_state->next_field[y][next_x] = field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// down
|
||||
if(game_state->direction == DirectionDown) {
|
||||
for(uint8_t x = 0; x < 4; x++) {
|
||||
uint8_t next_y = 3;
|
||||
for(int8_t y = 3; y >= 0; y--) {
|
||||
uint8_t field = game_state->field[y][x];
|
||||
if(field == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(game_state->next_field[next_y][x] == 0) {
|
||||
game_state->next_field[next_y][x] = field;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(field == game_state->next_field[next_y][x]) {
|
||||
game_state->next_field[next_y][x]++;
|
||||
game_state->score.gameScore += pow(2, game_state->next_field[next_y][x]);
|
||||
if(game_state->next_field[next_y][x] == 11 && !game_state->debug) {
|
||||
DOLPHIN_DEED(getRandomDeed());
|
||||
} // get some xp for making a 2048 tile
|
||||
next_y--;
|
||||
continue;
|
||||
}
|
||||
|
||||
next_y--;
|
||||
game_state->next_field[next_y][x] = field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0, 0, 1, 1
|
||||
// 1, 0, 0, 0
|
||||
|
||||
// left
|
||||
if(game_state->direction == DirectionLeft) {
|
||||
for(uint8_t y = 0; y < 4; y++) {
|
||||
uint8_t next_x = 0;
|
||||
for(uint8_t x = 0; x < 4; x++) {
|
||||
uint8_t field = game_state->field[y][x];
|
||||
if(field == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(game_state->next_field[y][next_x] == 0) {
|
||||
game_state->next_field[y][next_x] = field;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(field == game_state->next_field[y][next_x]) {
|
||||
game_state->next_field[y][next_x]++;
|
||||
game_state->score.gameScore += pow(2, game_state->next_field[y][next_x]);
|
||||
if(game_state->next_field[y][next_x] == 11 && !game_state->debug) {
|
||||
DOLPHIN_DEED(getRandomDeed());
|
||||
} // get some xp for making a 2048 tile
|
||||
next_x++;
|
||||
continue;
|
||||
}
|
||||
|
||||
next_x++;
|
||||
game_state->next_field[y][next_x] = field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// <debug>
|
||||
game_state->direction = DirectionIdle;
|
||||
memcpy(game_state->field, game_state->next_field, sizeof(game_state->field));
|
||||
// </debug>
|
||||
}
|
||||
|
||||
static void game_2048_restart(GameState* const game_state) {
|
||||
game_state->debug = DEBUG;
|
||||
|
||||
// check score
|
||||
if(game_state->score.gameScore > game_state->score.highScore) {
|
||||
game_state->score.highScore = game_state->score.gameScore;
|
||||
}
|
||||
|
||||
// clear all cells
|
||||
for(uint8_t y = 0; y < 4; y++) {
|
||||
for(uint8_t x = 0; x < 4; x++) {
|
||||
game_state->field[y][x] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// start next game
|
||||
game_state->score.gameScore = 0;
|
||||
game_2048_set_new_number(game_state);
|
||||
game_2048_set_new_number(game_state);
|
||||
}
|
||||
|
||||
int32_t game_2048_app(void* p) {
|
||||
UNUSED(p);
|
||||
int32_t return_code = 0;
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
GameState* game_state = malloc(sizeof(GameState));
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, game_state, sizeof(GameState))) {
|
||||
return_code = 255;
|
||||
goto free_and_exit;
|
||||
}
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(
|
||||
view_port, (ViewPortDrawCallback)game_2048_render_callback, &state_mutex);
|
||||
view_port_input_callback_set(
|
||||
view_port, (ViewPortInputCallback)game_2048_input_callback, event_queue);
|
||||
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
game_state->direction = DirectionIdle;
|
||||
game_2048_restart(game_state);
|
||||
|
||||
if(game_state->debug) {
|
||||
game_state->field[0][0] = 0;
|
||||
game_state->field[0][1] = 0;
|
||||
game_state->field[0][2] = 0;
|
||||
game_state->field[0][3] = 0;
|
||||
|
||||
game_state->field[1][0] = 1;
|
||||
game_state->field[1][1] = 2;
|
||||
game_state->field[1][2] = 3;
|
||||
game_state->field[1][3] = 4;
|
||||
|
||||
game_state->field[2][0] = 5;
|
||||
game_state->field[2][1] = 6;
|
||||
game_state->field[2][2] = 7;
|
||||
game_state->field[2][3] = 8;
|
||||
|
||||
game_state->field[3][0] = 9;
|
||||
game_state->field[3][1] = 10;
|
||||
game_state->field[3][2] = 11;
|
||||
game_state->field[3][3] = 12;
|
||||
}
|
||||
|
||||
InputEvent event;
|
||||
for(bool loop = true; loop;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
GameState* game_state = (GameState*)acquire_mutex_block(&state_mutex);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
if(event.type == InputTypeShort) {
|
||||
switch(event.key) {
|
||||
case InputKeyUp:
|
||||
game_state->direction = DirectionUp;
|
||||
game_2048_process_move(game_state);
|
||||
game_2048_set_new_number(game_state);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
game_state->direction = DirectionDown;
|
||||
game_2048_process_move(game_state);
|
||||
game_2048_set_new_number(game_state);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
game_state->direction = DirectionRight;
|
||||
game_2048_process_move(game_state);
|
||||
game_2048_set_new_number(game_state);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
game_state->direction = DirectionLeft;
|
||||
game_2048_process_move(game_state);
|
||||
game_2048_set_new_number(game_state);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
game_state->direction = DirectionIdle;
|
||||
break;
|
||||
case InputKeyBack:
|
||||
loop = false;
|
||||
break;
|
||||
}
|
||||
} else if(event.type == InputTypeLong) {
|
||||
if(event.key == InputKeyOk) {
|
||||
game_state->direction = DirectionIdle;
|
||||
game_2048_restart(game_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, game_state);
|
||||
}
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(view_port);
|
||||
delete_mutex(&state_mutex);
|
||||
|
||||
free_and_exit:
|
||||
free(game_state);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
return return_code;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
# flipperzero-nrf24
|
||||
|
||||
An [NRF24](https://www.sparkfun.com/datasheets/Components/SMD/nRF24L01Pluss_Preliminary_Product_Specification_v1_0.pdf) driver for the [Flipper Zero](https://flipperzero.one/) device. The NRF24 is a popular line of 2.4GHz radio transceivers from Nordic Semiconductors. This library is not currently complete, but functional.
|
||||
|
||||
## Warning
|
||||
This repo contains two Flipper Zero apps that utilize the NRF24 driver to sniff for NRF24 addresses and perform mousejack attacks. These apps are for **educational purposes** only. Please use this code responsibly and only use these apps on your own equipment.
|
||||
|
||||
## Acknowledgments
|
||||
The NRF24 sniffing technique was discovered and shared by Travis Goodspeed in [his blog](http://travisgoodspeed.blogspot.com/2011/02/promiscuity-is-nrf24l01s-duty.html).
|
||||
|
||||
The mousejack vulnerabilities were discovered and reported by Marc Newlin, see [the blog](https://www.bastille.net/research/vulnerabilities/mousejack/technical-details) for technical details.
|
||||
|
||||
Much of the driver code was inspired by [RadioHead's Arduino library](https://www.airspayce.com/mikem/arduino/RadioHead/classRH__NRF24.html).
|
||||
Much of the mousejack code was inspired by the [Jackit project](https://github.com/insecurityofthings/jackit).
|
||||
|
||||
|
||||
## PinOut from from NoComp/Frog
|
||||
<img src="https://media.discordapp.net/attachments/937479784726949900/994495234618687509/unknown.png?width=567&height=634">
|
||||
|
||||
# Mousejack / NRF24 pinout by UberGuidoZ
|
||||
2/A7 on FZ goes to MOSI/6 on nrf24l01<br>
|
||||
3/A6 on FZ goes to MISO/7 on nrf24l01<br>
|
||||
4/A4 on FZ goes to CSN/4 on nrf24l01<br>
|
||||
5/B3 on FZ goes to SCK/5 on nrf24l01<br>
|
||||
6/B2 on FZ goes to CE/3 on nrf24l01<br>
|
||||
8/GND on FZ goes to GND/1 on nrf24l01<br>
|
||||
9/3V3 on FZ goes to VCC/2 on nrf24l01<br>
|
||||
IRQ/8 is left disconnected on nrf24l01
|
||||
|
||||

|
||||
|
||||
If the nRF module is acting a bit flakey, try adding a capacitor to the vcc/gnd lines! I've not tried the Plus model so it may have a bigger need for a cap. Otherwise, I haven't had any major issues. Anything from a 3.3 uF to 10 uF should do. (Watch your positive/negative placement! Negative to ground.) I learned if you wanna get fancy, include a 0.1 uF cap in parallel. The 3.3 uF to 10 uF will respond to slow freq changes while the 0.1 uF will respond to the high freq switching spikes that the larger one cannot. That said, a single 10 uF will likely suffice for the Mousejack attack. ¯\\\_(ツ)_/¯
|
||||
|
||||

|
||||
|
||||
Selfmade NRF24 breakoutboard:
|
||||

|
||||

|
||||
|
||||
## Practical hints from einstein2150
|
||||
If you are not successfull with the NRF Sniff Plugin you can try to get the MAC of the device with the crazyradio pa USB dongle. Have a look here: https://github.com/SySS-Research/nrf24-playset
|
||||
|
||||
|
||||
A sample output of the NRF-Research script could be like
|
||||
```
|
||||
[2022-08-05 13:28:56.366] Found nRF24 device with address 38:24:93:C0:07 on channel 75\
|
||||
[2022-08-05 13:28:56.371] Checking communication\
|
||||
[2022-08-05 13:28:59.088] Scan for nRF24 device\
|
||||
[2022-08-05 13:28:59.097] Actively searching for address 07:C0:93:24:38\
|
||||
```
|
||||
|
||||
Be carefull with the byte-order using in mousejacker! The correct byte order is the reverse-byteorder. In my example the reversed one is ```07:C0:93:24:38``` for my Logitech R400 presenter.
|
||||
The next thing which could make trouble is the datarate. In my case the Logitech-device is only responding at 2Mbit. Commands at 1Mbit are not detected.
|
||||
|
||||
Now its time to write all the relevant information in the config file. In my case you are creating a file ```addresses.txt``` in the SD directory of ```/nrfsniff```. The file content corresponding to the results of the research is ```07C0932438,2``` representing the reversed byte order of the MAC and the comma-separated datarate.
|
||||
|
||||
Start the Mouse Jacker plugin and select the prepared ```addresses.txt```. If everything is alright you are starting with your MAC ready for attack:
|
||||
|
||||

|
||||
|
||||
If you have troube you can check if a datarate of 1 Mbit will help. Change ```07C0932438,2``` to ```07C0932438,1``` in this case. Another problem in the practical use is electromagnetical noise. In my case the external USB-3.1-Hub creating massive noise in the 2,4 Ghz-Frequency around a distance of 5 cm. Try using a long USB connection-cable for the receiver. In my case the response of the attack raises significant because the signals of the NRF-breakoutboard is no more covered with noise.
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <furi_hal_resources.h>
|
||||
#include <nrf24.h>
|
||||
#include "mousejacker_ducky.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#define TAG "mousejacker"
|
||||
#define LOGITECH_MAX_CHANNEL 85
|
||||
@@ -174,6 +175,7 @@ static bool process_ducky_file(
|
||||
mj_process_ducky_script(
|
||||
nrf24_HANDLE, addr, addr_size, rate, (char*)file_buf, plugin_state);
|
||||
FURI_LOG_D(TAG, "finished execution");
|
||||
DOLPHIN_DEED(getRandomDeed());
|
||||
loaded = true;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "load failed. file size: %d", file_size);
|
||||
|
||||
@@ -256,7 +256,7 @@ MusicPlayer* music_player_alloc() {
|
||||
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->volume = 4;
|
||||
|
||||
instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# flipperzero-nrf24
|
||||
|
||||
An [NRF24](https://www.sparkfun.com/datasheets/Components/SMD/nRF24L01Pluss_Preliminary_Product_Specification_v1_0.pdf) driver for the [Flipper Zero](https://flipperzero.one/) device. The NRF24 is a popular line of 2.4GHz radio transceivers from Nordic Semiconductors. This library is not currently complete, but functional.
|
||||
|
||||
## Warning
|
||||
This repo contains two Flipper Zero apps that utilize the NRF24 driver to sniff for NRF24 addresses and perform mousejack attacks. These apps are for **educational purposes** only. Please use this code responsibly and only use these apps on your own equipment.
|
||||
|
||||
## Acknowledgments
|
||||
The NRF24 sniffing technique was discovered and shared by Travis Goodspeed in [his blog](http://travisgoodspeed.blogspot.com/2011/02/promiscuity-is-nrf24l01s-duty.html).
|
||||
|
||||
The mousejack vulnerabilities were discovered and reported by Marc Newlin, see [the blog](https://www.bastille.net/research/vulnerabilities/mousejack/technical-details) for technical details.
|
||||
|
||||
Much of the driver code was inspired by [RadioHead's Arduino library](https://www.airspayce.com/mikem/arduino/RadioHead/classRH__NRF24.html).
|
||||
Much of the mousejack code was inspired by the [Jackit project](https://github.com/insecurityofthings/jackit).
|
||||
#
|
||||
## PinOut from from NoComp/Frog
|
||||
<img src="https://media.discordapp.net/attachments/937479784726949900/994495234618687509/unknown.png?width=567&height=634">
|
||||
|
||||
# Mousejack / NRF24 pinout by UberGuidoZ
|
||||
2/A7 on FZ goes to MOSI/6 on nrf24l01<br>
|
||||
3/A6 on FZ goes to MISO/7 on nrf24l01<br>
|
||||
4/A4 on FZ goes to CSN/4 on nrf24l01<br>
|
||||
5/B3 on FZ goes to SCK/5 on nrf24l01<br>
|
||||
6/B2 on FZ goes to CE/3 on nrf24l01<br>
|
||||
8/GND on FZ goes to GND/1 on nrf24l01<br>
|
||||
9/3V3 on FZ goes to VCC/2 on nrf24l01<br>
|
||||
IRQ/8 is left disconnected on nrf24l01
|
||||

|
||||
If the nRF module is acting a bit flakey, try adding a capacitor to the vcc/gnd lines! I've not tried the Plus model so it may have a bigger need for a cap. Otherwise, I haven't had any major issues. Anything from a 3.3 uF to 10 uF should do. (Watch your positive/negative placement! Negative to ground.) I learned if you wanna get fancy, include a 0.1 uF cap in parallel. The 3.3 uF to 10 uF will respond to slow freq changes while the 0.1 uF will respond to the high freq switching spikes that the larger one cannot. That said, a single 10 uF will likely suffice for the Mousejack attack. ¯\\\_(ツ)_/¯
|
||||

|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <input/input.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <nrf24.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
|
||||
@@ -291,6 +291,7 @@ static void wrap_up(Storage* storage, NotificationApp* notification) {
|
||||
hexlify(addr, 5, top_address);
|
||||
found_count++;
|
||||
save_addr_to_file(storage, addr, 5, notification);
|
||||
DOLPHIN_DEED(getRandomDeed());
|
||||
if(confirmed_idx < MAX_CONFIRMED) memcpy(confirmed[confirmed_idx++], addr, 5);
|
||||
break;
|
||||
}
|
||||
@@ -402,6 +403,9 @@ int32_t nrfsniff_app(void* p) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// FURI_LOG_D(TAG, "osMessageQueue: event timeout");
|
||||
// event timeout
|
||||
}
|
||||
|
||||
if(sniffing_state) {
|
||||
@@ -448,7 +452,6 @@ int32_t nrfsniff_app(void* p) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
free(plugin_state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
typedef struct {
|
||||
// +-----x
|
||||
@@ -61,12 +62,17 @@ typedef struct {
|
||||
InputEvent input;
|
||||
} SnakeEvent;
|
||||
|
||||
static const NotificationSequence sequence_short_vibro_and_sound = {
|
||||
static const NotificationSequence sequence_short_vibro_and_rgb = {
|
||||
&message_red_255,
|
||||
&message_blue_255,
|
||||
&message_green_255,
|
||||
&message_vibro_on,
|
||||
&message_note_c5,
|
||||
&message_delay_50,
|
||||
&message_delay_100,
|
||||
&message_sound_off,
|
||||
&message_vibro_off,
|
||||
&message_red_0,
|
||||
&message_blue_0,
|
||||
&message_green_0,
|
||||
NULL,
|
||||
};
|
||||
|
||||
@@ -113,6 +119,10 @@ static void snake_game_render_callback(Canvas* const canvas, void* ctx) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 37, 31, "Game Over");
|
||||
|
||||
if((snake_state->len - 7) % 20 == 0 && (snake_state->len - 7) != 0) {
|
||||
DOLPHIN_DEED(getRandomDeed());
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
char buffer[12];
|
||||
snprintf(buffer, sizeof(buffer), "Score: %u", snake_state->len - 7);
|
||||
@@ -266,6 +276,7 @@ static void snake_game_process_game_step(SnakeState* const snake_state, Notifica
|
||||
return;
|
||||
} else if(snake_state->state == GameStateLastChance) {
|
||||
snake_state->state = GameStateGameOver;
|
||||
notification_message(notify, &sequence_single_vibro);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -277,17 +288,18 @@ static void snake_game_process_game_step(SnakeState* const snake_state, Notifica
|
||||
crush = snake_game_collision_with_tail(snake_state, next_step);
|
||||
if(crush) {
|
||||
snake_state->state = GameStateGameOver;
|
||||
notification_message(notify, &sequence_single_vibro);
|
||||
return;
|
||||
}
|
||||
|
||||
bool eatFruit = (next_step.x == snake_state->fruit.x) && (next_step.y == snake_state->fruit.y);
|
||||
if(eatFruit) {
|
||||
notification_message(notify, &sequence_short_vibro_and_sound);
|
||||
//notification_message(notify, &sequence_blink_white_100);
|
||||
|
||||
snake_state->len++;
|
||||
if(snake_state->len >= MAX_SNAKE_LEN) {
|
||||
snake_state->state = GameStateGameOver;
|
||||
notification_message(notify, &sequence_single_vibro);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <string.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#define BORDER_OFFSET 1
|
||||
#define MARGIN_OFFSET 3
|
||||
@@ -150,6 +151,10 @@ static void tetris_game_render_callback(Canvas* const canvas, void* ctx) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 4, 63, "Game Over");
|
||||
|
||||
if(tetris_state->numLines % 8 == 0 && tetris_state->numLines != 0) {
|
||||
DOLPHIN_DEED(getRandomDeed());
|
||||
}
|
||||
|
||||
char buffer[13];
|
||||
snprintf(buffer, sizeof(buffer), "Lines: %u", tetris_state->numLines);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
@@ -17,19 +17,14 @@
|
||||
#define WAVPLAYER_FOLDER "/ext/wav_player"
|
||||
|
||||
static bool open_wav_stream(Stream* stream) {
|
||||
DialogsApp* dialogs = furi_record_open("dialogs");
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
bool result = false;
|
||||
string_t path;
|
||||
string_init(path);
|
||||
string_set_str(path, WAVPLAYER_FOLDER);
|
||||
bool ret = dialog_file_browser_show(dialogs, path, path, ".wav", true, &I_music_10px, false);
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(&browser_options, ".wav", &I_music_10px);
|
||||
browser_options.hide_ext = false;
|
||||
|
||||
bool ret = dialog_file_browser_show(dialogs, path, path, &browser_options);
|
||||
|
||||
furi_record_close("dialogs");
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
if(ret) {
|
||||
if(!file_stream_open(stream, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_E(TAG, "Cannot open file \"%s\"", string_get_cstr(path));
|
||||
@@ -119,7 +114,7 @@ static WavPlayerApp* app_alloc() {
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, 0);
|
||||
|
||||
app->notification = furi_record_open("notification");
|
||||
app->notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message(app->notification, &sequence_display_backlight_enforce_on);
|
||||
|
||||
return app;
|
||||
@@ -139,7 +134,7 @@ static void app_free(WavPlayerApp* app) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
notification_message(app->notification, &sequence_display_backlight_enforce_auto);
|
||||
furi_record_close("notification");
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
free(app);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#####################################
|
||||
encode.py
|
||||
decode.py
|
||||
iconencode.py
|
||||
icondecode.py
|
||||
|
||||
A set of python3 scripts for processing the Flipper image files.
|
||||
These work as-is but I am rolling in improvements.
|
||||
#####################################
|
||||
PREREQUISITES
|
||||
|
||||
|
||||
You'll need heatshrink installed - a small embedded/RTOS compression and decompression library
|
||||
You can get that here https://github.com/atomicobject/heatshrink
|
||||
|
||||
#####################################
|
||||
HOW TO USE
|
||||
|
||||
##
|
||||
# decode.
|
||||
|
||||
Decode a .mb into .xbm:
|
||||
decode.py input_image output_image [width] [height]
|
||||
Dimensions are not stored in .bm so you need to specify
|
||||
If you have the meta.txt available for the animation set the dimensions will be in here.
|
||||
It may also be part of the directory name for the animation files as well.
|
||||
|
||||
If you do not enter anything here it will assume 128x64. THIS WILL NOT ALWAYS BE CORRECT.
|
||||
|
||||
##
|
||||
# encode
|
||||
Encode an .xbm file into .xb
|
||||
encode.py input_image output_image
|
||||
You will also get the image dimensions for use in meta.txt
|
||||
That's it.
|
||||
|
||||
##
|
||||
# iconencode
|
||||
Compress an icon asset from an XBM to a compressed char array ready to paste
|
||||
Will assume dimensions of 128x64
|
||||
Header works like this, you'll have to do this manually for now!
|
||||
|
||||
Image Header Format Example
|
||||
0x01 = Compressed
|
||||
0x00 = Reserved Section
|
||||
0xa4,0x01 = 0x1a4, or, 420 - the size of the compressed array, minus this header. Just count the commas ;)
|
||||
Rest of the data is char array output from heatshrink of the original XBM char array.
|
||||
Calculated Header: 0x01,0x00,0xa4,0x01
|
||||
from furi_hal_compress.c:
|
||||
typedef struct {
|
||||
uint8_t is_compressed;
|
||||
uint8_t reserved;
|
||||
uint16_t compressed_buff_size;
|
||||
} FuriHalCompressHeader;
|
||||
|
||||
|
||||
##
|
||||
# icondecode
|
||||
Decompress an icon asset (as found in assets_icons.c and elsewhere)
|
||||
icondecodepy input_image output_image [trim] [width] [height]
|
||||
The icons in this file have a different header format. This will need to be trimmed.
|
||||
A value of 8 for trim appears to be correct.
|
||||
As with regular decoding, the width and height are not listed - but can be found in code/const/variable/etc names.
|
||||
copy just the char array. The script does not care if the curly braces or semicolon are in place.
|
||||
i.e. the following are all acceptable and equivalent.
|
||||
{0x00,0x08,0x1C,0x3E,0x7F,};
|
||||
{0x00,0x08,0x1C,0x3E,0x7F,}
|
||||
0x00,0x08,0x1C,0x3E,0x7F
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import logging
|
||||
import argparse
|
||||
import subprocess
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
def padded_hex(i, l):
|
||||
given_int = i
|
||||
given_len = l
|
||||
|
||||
hex_result = hex(given_int)[2:] # remove '0x' from beginning of str
|
||||
num_hex_chars = len(hex_result)
|
||||
extra_zeros = '0' * (given_len - num_hex_chars) # may not get used..
|
||||
|
||||
return ('0x' + hex_result if num_hex_chars == given_len else
|
||||
'?' * given_len if num_hex_chars > given_len else
|
||||
'0x' + extra_zeros + hex_result if num_hex_chars < given_len else
|
||||
None)
|
||||
|
||||
parser = argparse.ArgumentParser(description='Turn cooked Flipper .bm files back into .xbm')
|
||||
|
||||
parser.add_argument('infile', metavar='i',
|
||||
help='Input file')
|
||||
parser.add_argument('outfile', metavar='o',
|
||||
help='File to write to')
|
||||
parser.add_argument('Width', metavar='W', type=int, nargs="?", default="128",
|
||||
help='Width of the image. Find from meta.txt or directory name')
|
||||
parser.add_argument('Height', metavar='H', type=int, nargs="?", default="64",
|
||||
help='Height of the image. Find from meta.txt or directory name')
|
||||
|
||||
args = vars(parser.parse_args())
|
||||
|
||||
r = open(args["infile"],"rb")
|
||||
w = open(args["outfile"],"w")
|
||||
|
||||
fileStream=r.read()
|
||||
filename=os.path.splitext(os.path.basename(args["outfile"]))[0]
|
||||
|
||||
|
||||
imageWidth=args["Width"]
|
||||
imageHeight=args["Height"]
|
||||
|
||||
|
||||
#remove headers and padding
|
||||
if(fileStream[0:2] == bytes([0x01,0x00])):
|
||||
unpad=fileStream[4:]
|
||||
else:
|
||||
if(fileStream[0:1] == bytes([0x00])):
|
||||
unpad=fileStream[2:]
|
||||
|
||||
|
||||
|
||||
#lzss decompress
|
||||
data_decoded_str = subprocess.check_output(
|
||||
["heatshrink", "-d","-w8","-l4"], input=unpad
|
||||
)
|
||||
|
||||
#turn it back into xbm
|
||||
|
||||
b=list(data_decoded_str)
|
||||
c=', '.join(padded_hex(my_int,2) for my_int in b)
|
||||
|
||||
width_out = "#define "+ filename+ "_width "+ str(imageWidth) + "\n"
|
||||
height_out = "#define "+ filename+ "_height "+ str(imageHeight) + "\n"
|
||||
bytes_out = "static unsigned char "+ filename+ "_bits[] = {"+ str(c) + "};"
|
||||
|
||||
data=width_out+height_out+bytes_out
|
||||
|
||||
w.write(data)
|
||||
r.close()
|
||||
w.close()
|
||||
@@ -0,0 +1,49 @@
|
||||
import logging
|
||||
import argparse
|
||||
import subprocess
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Turn .xbm files into cooked .bm files for flipper FS')
|
||||
|
||||
parser.add_argument('infile', metavar='i',
|
||||
help='Input file')
|
||||
parser.add_argument('outfile', metavar='o',
|
||||
help='File to write to')
|
||||
|
||||
args = vars(parser.parse_args())
|
||||
|
||||
r = open(args["infile"],"r")
|
||||
w = open(args["outfile"],"wb")
|
||||
|
||||
|
||||
output = subprocess.check_output(["cat", args["infile"]])
|
||||
f = io.StringIO(output.decode().strip())
|
||||
print("Image Dimensions:")
|
||||
width = int(f.readline().strip().split(" ")[2])
|
||||
print("W: ", width)
|
||||
height = int(f.readline().strip().split(" ")[2])
|
||||
print("H: ", height)
|
||||
|
||||
|
||||
data = f.read().strip().replace("\n", "").replace(" ", "").split("=")[1][:-1]
|
||||
data_str = data[1:-1].replace(",", " ").replace("0x", "")
|
||||
|
||||
data_bin = bytearray.fromhex(data_str)
|
||||
data_encoded_str = subprocess.check_output(
|
||||
["heatshrink", "-e", "-w8", "-l4"], input=data_bin
|
||||
)
|
||||
|
||||
assert data_encoded_str
|
||||
|
||||
data_enc = bytearray(data_encoded_str)
|
||||
data_enc = bytearray([len(data_enc) & 0xFF, len(data_enc) >> 8]) + data_enc
|
||||
if len(data_enc) < len(data_bin) + 1:
|
||||
data = b"\x01\x00" + data_enc
|
||||
else:
|
||||
data = b"\x00" + data_bin
|
||||
w.write(data)
|
||||
r.close()
|
||||
w.close()
|
||||
@@ -0,0 +1,65 @@
|
||||
import logging
|
||||
import argparse
|
||||
import subprocess
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
def padded_hex(i, l):
|
||||
given_int = i
|
||||
given_len = l
|
||||
|
||||
hex_result = hex(given_int)[2:] # remove '0x' from beginning of str
|
||||
num_hex_chars = len(hex_result)
|
||||
extra_zeros = '0' * (given_len - num_hex_chars) # may not get used..
|
||||
|
||||
return ('0x' + hex_result if num_hex_chars == given_len else
|
||||
'?' * given_len if num_hex_chars > given_len else
|
||||
'0x' + extra_zeros + hex_result if num_hex_chars < given_len else
|
||||
None)
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Turn icon char arrays back into .xbm')
|
||||
|
||||
parser.add_argument('infile', metavar='i',
|
||||
help='Input file')
|
||||
parser.add_argument('outfile', metavar='o',
|
||||
help='File to write to')
|
||||
parser.add_argument('Width', metavar='W', type=int, nargs="?", default="128",
|
||||
help='Width of the image. Find from meta.txt or directory name')
|
||||
parser.add_argument('Height', metavar='H', type=int, nargs="?", default="64",
|
||||
help='Height of the image. Find from meta.txt or directory name')
|
||||
parser.add_argument('Trim', metavar='T', type=int, nargs="?", default="8",
|
||||
help='Number of bytes off the start/header to trim. Multiples of 2 required.')
|
||||
args = vars(parser.parse_args())
|
||||
|
||||
r = open(args["infile"],"r")
|
||||
w = open(args["outfile"],"w")
|
||||
imageWidth=args["Width"]
|
||||
imageHeight=args["Height"]
|
||||
trimStart=args["Trim"]
|
||||
|
||||
output = subprocess.check_output(["cat", args["infile"]]) #yes this is terrible.
|
||||
f = io.StringIO(output.decode().strip())
|
||||
|
||||
data = f.read().strip().replace(";","").replace("{","").replace("}","")
|
||||
data_str = data.replace(",", "").replace("0x", "")
|
||||
data_bin = bytearray.fromhex(data_str[trimStart:])
|
||||
|
||||
data_decoded_str = subprocess.check_output(
|
||||
["heatshrink", "-d","-w8","-l4"], input=data_bin
|
||||
)
|
||||
|
||||
b=list(data_decoded_str)
|
||||
|
||||
c=', '.join(padded_hex(my_int,2) for my_int in b)
|
||||
|
||||
width_out = "#define icon_width "+ str(imageWidth) + "\n"
|
||||
height_out = "#define icon_height "+ str(imageHeight) + "\n"
|
||||
bytes_out = "static unsigned char icon_bits[] = {"+ str(c) + "};"
|
||||
|
||||
data=width_out+height_out+bytes_out
|
||||
|
||||
w.write(data)
|
||||
r.close()
|
||||
w.close()
|
||||
@@ -0,0 +1,68 @@
|
||||
import logging
|
||||
import argparse
|
||||
import subprocess
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
def padded_hex(i, l):
|
||||
given_int = i
|
||||
given_len = l
|
||||
|
||||
hex_result = hex(given_int)[2:] # remove '0x' from beginning of str
|
||||
num_hex_chars = len(hex_result)
|
||||
extra_zeros = '0' * (given_len - num_hex_chars) # may not get used..
|
||||
|
||||
return ('0x' + hex_result if num_hex_chars == given_len else
|
||||
'?' * given_len if num_hex_chars > given_len else
|
||||
'0x' + extra_zeros + hex_result if num_hex_chars < given_len else
|
||||
None)
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='Turn icon char arrays back into .xbm')
|
||||
|
||||
parser.add_argument('infile', metavar='i',
|
||||
help='Input file')
|
||||
parser.add_argument('Width', metavar='W', type=int, nargs="?", default="128",
|
||||
help='Width of the image. Find from meta.txt or directory name')
|
||||
parser.add_argument('Height', metavar='H', type=int, nargs="?", default="64",
|
||||
help='Height of the image. Find from meta.txt or directory name')
|
||||
args = vars(parser.parse_args())
|
||||
|
||||
r = open(args["infile"],"r")
|
||||
infile=args["infile"].split(".")[0]
|
||||
|
||||
imageWidth=args["Width"]
|
||||
imageHeight=args["Height"]
|
||||
dims=str(imageWidth)+"x"+str(imageHeight)
|
||||
|
||||
output = subprocess.check_output(["cat", args["infile"]]) #yes this is terrible.
|
||||
f = io.StringIO(output.decode().strip())
|
||||
|
||||
data = f.read().strip().replace(";","").replace("{","").replace("}","")
|
||||
data_str = data.replace(",", "").replace("0x", "")
|
||||
data_bin = bytearray.fromhex(data_str)
|
||||
|
||||
data_encoded_str = subprocess.check_output(
|
||||
["heatshrink", "-e","-w8","-l4"], input=data_bin
|
||||
)
|
||||
|
||||
b=list(data_encoded_str)
|
||||
|
||||
c=','.join(padded_hex(my_int,2) for my_int in b)
|
||||
|
||||
# a bit ugly.
|
||||
|
||||
framename="_I_"+infile+"_"+dims
|
||||
print(len(b))
|
||||
#d=len(b)
|
||||
# if b > 255 split 0x1234 into 0x34,0x12
|
||||
#d=hex(len(b))
|
||||
|
||||
char_out = "const uint8_t "+framename+"_0[] = {"+ str(c) + ",};"
|
||||
char_out2 = "const uint8_t "+framename+"[] = {"+framename+"_0};"
|
||||
#data=bytes_out
|
||||
print(char_out)
|
||||
print(char_out2)
|
||||
#w.write(data)
|
||||
#w.close()
|
||||
Reference in New Issue
Block a user