diff --git a/.gitignore b/.gitignore
index 4985256c1..f1290f11f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
# ESP-IDF auto generated by build
sdkconfig
sdkconfig.old
+dependencies.lock
+**/dependencies.lock
# compiled output
/build/
@@ -1044,8 +1046,8 @@ size_info_*.txt
# pytest-embedded log folder
pytest_embedded_log/
-# idf-component-manager output
-dependencies.lock
+# idf-component-manager lock: TRACKED for reproducible builds (pins the exact
+# managed-component versions so every clone resolves the same deps).
!assets/
!assets/**
diff --git a/AGENTS.md b/AGENTS.md
index d2f416f06..41a5214aa 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -61,7 +61,7 @@ firmware_p4/ # ESP32-P4 master firmware (embeds C5 binary)
Drivers/ # Hardware drivers (SPI, buttons, display, radio, USB)
Service/ # Support services (OTA, WiFi, console, storage, SPI bridge)
Core/ # System core and main managers
- Applications/ # User-facing apps (UI, bad_usb, SubGhz)
+ Applications/ # User-facing apps (UI, bad_usb, SubGhz, gameboy, nfc)
Drivers/spi_bridge_phy/ # SPI bridge physical layer (P4 side)
main/main.c # Entry point
common/metadata/ # Shared metadata
diff --git a/README.md b/README.md
index 36f6cb9b8..af0765420 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
@@ -45,7 +45,7 @@ See the general project architecture:
## How to use this project
-We recommend that this project serves as a basis for custom projects with ESP32-S3.
+We recommend that this project serves as a basis for custom projects with the ESP32-P4 and ESP32-C5.
To start a new project with ESP-IDF, follow the official guide:
[ESP-IDF Documentation - Create a new project](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#start-a-new-project)
diff --git a/README.pt.md b/README.pt.md
index a98a519b0..f798feda7 100644
--- a/README.pt.md
+++ b/README.pt.md
@@ -1,5 +1,5 @@
-
+
@@ -47,7 +47,7 @@ Veja a arquitetura geral do projeto:
## Como utilizar este projeto
-Recomendamos que este projeto sirva como base para projetos personalizados com ESP32-S3.
+Recomendamos que este projeto sirva como base para projetos personalizados com o ESP32-P4 e o ESP32-C5.
Para começar um novo projeto com ESP-IDF, siga o guia oficial:
[Documentação ESP-IDF - Criar novo projeto](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#start-a-new-project)
diff --git a/docs/LoRa/README.md b/docs/LoRa/README.md
new file mode 100644
index 000000000..a88038d35
--- /dev/null
+++ b/docs/LoRa/README.md
@@ -0,0 +1,244 @@
+# LoRa Application
+
+This component implements the LoRa mesh application layer on top of the `sx1262` driver. It hosts two full mesh protocol stacks - **Meshtastic** and **MeshCore** - behind a unified session manager, plus an **RNode**-compatible KISS modem. It also bridges a companion phone app over BLE and drives the on-device chat UI.
+
+## Overview
+
+- **Location:** `components/Applications/LoRa/`
+- **Sub-modules:** `session/`, `meshtastic/`, `meshcore/`, `rnode/`
+- **Radio:** single SX1262 (see `docs/sx1262/README.md`)
+- **UI screens:** `components/Applications/ui/screens/lora/`
+- **BLE / Wi-Fi:** terminated on the **C5 co-processor**, reached over the SPI bridge
+
+## Single-Owner Radio
+
+The SX1262 is a single-owner resource. Meshtastic, MeshCore, and RNode each register their own radio callbacks and cannot run at the same time; whichever stack starts first owns the radio. **Switching protocols requires a reboot** - there is no clean whole-stack teardown. The session manager enforces this: once a protocol is running, starting a different one is refused.
+
+## Split-Chip Architecture (P4 + C5)
+
+The design is consistent across both mesh stacks:
+
+- **The P4** owns the protocol logic, the radio, and the PhoneAPI/command state.
+- **The C5** owns NimBLE (GATT service, pairing, CCCD subscribe) and the Wi-Fi/TCP server.
+
+The two communicate over the **SPI bridge**. Each mesh stack has a "phone bridge" on the P4 that:
+
+- Reassembles chunked StreamAPI/companion frames (`spi_mesh_chunk_hdr_t`: seq / chunk_idx / total_chunks / flags) arriving from the C5 and forwards them to the P4 PhoneAPI.
+- Fragments outbound frames and pushes them to the C5.
+- Polls a cached C5 status struct (`spi_mesh_status_t` / `spi_mcore_status_t`: `ble_connected`, `ble_subscribed`, `tcp_clients`, `logradio_subscribed`).
+- Runs a **want-vs-actual reconcile loop** so BLE re-arms automatically after a C5 reboot.
+
+**"Connected" vs "linked":** `ble_connected` is the raw BLE link (true before the pairing PIN is even shown). The UI/session truth is **"linked" = subscribed after encryption** - the phone has enabled notifications on the notify characteristic (a CCCD subscribe on the C5). The session and UI gate on the *subscribed* predicate, not the *connected* one.
+
+## Session Manager (`session/`)
+
+`lora_session` is a thin, protocol-agnostic facade the chat hub reads. It never calls the SX1262 directly - each stack's `app_start` brings the radio up. The session layer:
+
+- Tracks which protocol owns the radio.
+- Holds a **local chat ring** of 24 messages (in PSRAM), guarded by a mutex, with a monotonic sequence used by a cursor-based read API.
+- Maps a unified node/contact list onto either Meshtastic's nodedb or MeshCore's contacts.
+- Fronts the phone-bridge connect/subscribe state.
+
+### Types
+
+```c
+typedef enum { LORA_PROTO_NONE = 0, LORA_PROTO_MESHTASTIC, LORA_PROTO_MESHCORE } lora_proto_t;
+typedef struct { bool outgoing; char who[24]; char text[160]; } lora_msg_t;
+typedef struct { char name[32]; int16_t rssi; float snr; } lora_node_t;
+```
+
+### API
+
+```c
+esp_err_t lora_session_start(lora_proto_t proto);
+lora_proto_t lora_session_active(void);
+esp_err_t lora_session_send_text(const char *text);
+uint16_t lora_session_msg_count(void);
+uint16_t lora_session_msg_since(uint32_t *io_seq, lora_msg_t *out, uint16_t max);
+uint16_t lora_session_node_count(void);
+bool lora_session_node_get(uint16_t idx, lora_node_t *out);
+void lora_session_on_rx_text(const char *who, const char *text);
+bool lora_session_app_connected(void);
+esp_err_t lora_session_app_connect(void);
+```
+
+- `lora_session_start` is idempotent for the same protocol; a request for a different protocol while one is running returns `ESP_ERR_INVALID_STATE`.
+- `lora_session_send_text` sends a broadcast/public-channel text on the active stack (Meshtastic `0xFFFFFFFF`, MeshCore public channel `0`) and, on success, appends it to the ring as outgoing ("me").
+- `lora_session_msg_since` copies messages newer than a caller-held cursor under a single lock; passing `*io_seq == 0` snaps to the oldest retained message.
+- `lora_session_on_rx_text` is called by the backend RX taps from the mesh poll task (not the LVGL thread).
+- `lora_session_app_connected` maps to the active stack's **`_is_subscribed()`** predicate (linked, not merely connected). `lora_session_app_connect` maps to `_phone_bridge_ble_start()`.
+
+## Meshtastic Stack (`meshtastic/`)
+
+Entry point: `esp_err_t meshtastic_app_start(void)` (call **after** `bridge_manager_init()`).
+
+Bring-up derives `node_num` from the MAC, loads persisted preset/region (default LONG_FAST + US), brings up the SX1262 (region center frequency, preset SF/BW/CR), starts the IRQ task, initializes PKI (X25519 / Curve25519 via mbedTLS), the nodedb, the module hub, the mesh core, the PhoneAPI, and the phone bridge, then starts a poll task (mesh poll at 50 ms, module tick at 1 Hz) and requests C5 BLE advertising.
+
+### Mesh core (`meshtastic_mesh`)
+
+```c
+esp_err_t meshtastic_mesh_init(uint32_t node_num);
+esp_err_t meshtastic_mesh_start(void);
+void meshtastic_mesh_stop(void);
+esp_err_t meshtastic_mesh_send(const uint8_t *pb_data, uint16_t pb_len);
+void meshtastic_mesh_poll(void);
+esp_err_t meshtastic_mesh_send_nodeinfo(void);
+esp_err_t meshtastic_mesh_send_text(const char *text, uint32_t to);
+esp_err_t meshtastic_mesh_send_data(uint32_t to, uint8_t channel, uint8_t hop_limit,
+ uint8_t portnum, const uint8_t *payload, uint16_t plen,
+ uint32_t request_id, bool want_ack, bool want_response);
+bool meshtastic_mesh_retry_ack(uint32_t pkt_id, bool is_implicit);
+```
+
+### Modules (`mt_modules` + `mt_mod_*`)
+
+The module hub dispatches decoded packets by portnum. Modules present: Admin, NodeInfo, Position, Text, Routing, TraceRoute, Telemetry, KeyVerify, NeighborInfo. Each exposes `_init(node_num)` and `_on_received(meta, payload, len)`; timer-driven ones add a tick.
+
+```c
+esp_err_t mt_modules_init(uint32_t node_num);
+void mt_modules_dispatch(const mt_packet_meta_t *meta, const uint8_t *data, uint16_t len);
+void mt_modules_tick(void); // 1 Hz
+bool mt_parse_data(const uint8_t *data, uint16_t len, uint32_t *out_portnum,
+ const uint8_t **out_payload, uint16_t *out_payload_len,
+ uint32_t *out_request_id);
+```
+
+Per-packet metadata (`mt_packet_meta_t`) carries `from`, `to`, `id`, `channel`, `hop_limit`, `hop_start`, `rssi_dbm`, `snr_db`, `want_ack`, `want_response`, `request_id`.
+
+Supporting modules: `meshtastic_nodedb` (node database, favorites/ignore/mute, next-hop learning), `meshtastic_channels` (channel PSKs and hashes), `meshtastic_pki` / `meshtastic_crypto_pki` (Curve25519 encrypt/decrypt), `meshtastic_pkt_history` (dedup / relayer tracking), `meshtastic_mqtt`, `meshtastic_wifi`, `meshtastic_regions`, `meshtastic_presets`, `meshtastic_roles`, plus vendored `unishox2` text compression.
+
+### Traceroute (`mt_mod_traceroute`)
+
+A traceroute request is a zero-payload Data packet on the TraceRoute port with `want_response` set.
+
+```c
+#define MT_TRACE_MAX_HOPS 8
+void mt_mod_traceroute_init(uint32_t node_num);
+void mt_mod_traceroute_on_received(const mt_packet_meta_t *meta, const uint8_t *payload, uint16_t len);
+void mt_mod_traceroute_start(uint32_t to);
+bool mt_mod_traceroute_is_pending(void);
+bool mt_mod_traceroute_get_result(uint32_t *out_hops, int *out_count, uint32_t *out_target);
+```
+
+- `mt_mod_traceroute_start(to)` marks a pending request, clears any prior result, and sends the request via `meshtastic_mesh_send_data(...)`.
+- `mt_mod_traceroute_on_received` decodes the `RouteDiscovery` protobuf hop list. If it matches the pending request (reply addressed to us, from the target), the hops are captured into module-static state and `_get_result` reports them. If this node is itself the request destination, it appends its own node number to the route and sends the reply. Non-terminal hop forwarding relies on the mesh core's flood rebroadcast.
+- There is no dedicated result struct; results are returned through the out-parameters of `mt_mod_traceroute_get_result` and held internally until the next request. The traceroute screen is `lora_traceroute_ui`.
+
+### PhoneAPI (`meshtastic_phoneapi`)
+
+Implements the official PhoneAPI handshake as a state machine (`phoneapi_state_t`: MyInfo -> Metadata -> DeviceUIConfig -> Channels -> Config -> ModuleConfig -> FileManifest -> own NodeInfo -> other NodeInfos -> complete-id -> packets).
+
+```c
+esp_err_t phoneapi_init(uint32_t node_num);
+esp_err_t phoneapi_on_toradio(const uint8_t *pb_data, uint16_t pb_len);
+uint16_t phoneapi_poll_fromradio(uint8_t *out_buf, uint16_t max_len);
+bool phoneapi_has_data(void);
+void phoneapi_push_packet(const uint8_t *mp_bytes, uint16_t mp_len);
+void phoneapi_disconnect(void);
+```
+
+### BLE phone-app connectivity
+
+On the P4, `meshtastic_ble` is a thin shim forwarding to `meshtastic_phone_bridge`; the real NimBLE host and GATT service run on the C5.
+
+```c
+esp_err_t meshtastic_phone_bridge_init(void);
+esp_err_t meshtastic_phone_bridge_ble_start(void);
+esp_err_t meshtastic_phone_bridge_ble_stop(void);
+bool meshtastic_phone_bridge_is_connected(void); // ble_connected || tcp_clients
+bool meshtastic_phone_bridge_is_subscribed(void); // ble_subscribed || tcp_clients
+```
+
+- **NimBLE arbitration:** the P4 never owns NimBLE. `_ble_start()` sends a BLE-init request to the C5 over the SPI bridge; a notify task reconciles desired vs actual C5 state (re-arming BLE after a C5 reboot) and polls status. Because only one LoRa stack runs at a time, only one bridge ever issues BLE-init; cross-app BLE arbitration (mesh vs host-link vs HID) lives on the C5, which avoids tearing NimBLE out from under an active app.
+- **Pairing PIN:** for Meshtastic the passkey is generated randomly on the C5 per pairing (display-only IO capability, MITM + bonding) and logged there. It is **not** shown on the P4 chat screen.
+- **Linked-on-subscribe gating:** `_is_connected()` is true at the raw BLE link; `_is_subscribed()` is true only after the phone subscribes (CCCD notify enabled on the FromNum characteristic, after encryption). Fromradio data is only pushed to the phone while a BLE client is connected or a TCP client is present.
+
+Other module surfaces (used by the UI/config screens): `meshtastic_mqtt` (`_init/_publish/_is_connected`), `meshtastic_wifi`, region/preset/role selection (`mt_region_*`, `mt_preset_*`, `mt_role_*` with `MT_ROLE_ROUTER` / `MT_ROLE_CLIENT`).
+
+## MeshCore Stack (`meshcore/`)
+
+Entry point: `esp_err_t meshcore_app_start(void)` (call **after** `bridge_manager_init()`).
+
+Bring-up initializes libsodium, brings up the SX1262 with MeshCore defaults (915 MHz, SF10, BW 250 kHz, CR 4/5, +20 dBm, preamble 8), loads or creates an Ed25519 identity (32-byte seed persisted in NVS, default node name "Highboy"), initializes the core with five router callbacks, the companion PhoneAPI, and the phone bridge, then starts a poll task (50 ms) and requests C5 BLE advertising.
+
+The five router callbacks bridge radio events to both the phone and the local ring: new advert, group text (`lora_session_on_rx_text("ch%u", ...)`), direct message (`lora_session_on_rx_text(contact-name or "dm", ...)`), ACK confirmation, and path update.
+
+### Core (`meshcore`)
+
+Constants include `MESHCORE_MAX_CONTACTS 32`, `MESHCORE_MAX_CHANNELS 8`, `MESHCORE_PUBLIC_CHANNEL 0`, `MESHCORE_TEXT_MAX 160`. Key surface used by the app/session:
+
+- `meshcore_init`, `meshcore_start` / `_stop` / `_poll`
+- `meshcore_set_node_name`, `meshcore_set_advert_latlon`, `meshcore_send_advert` / `_throttled` (10 s cooldown)
+- `meshcore_channel_get` / `_set`, `meshcore_send_grp_txt(channel_idx, text)`
+- Contacts: `meshcore_contacts_array`, `meshcore_contacts_count`, `meshcore_contact_find` / `_find_by_hash` / `_upsert` / `_remove` / `_reset_path`
+- `meshcore_send_direct_msg(peer_pub_key, text, timestamp, attempt, out_expected_ack)` - X25519 ECDH + AES-128 + HMAC-SHA256
+- `meshcore_set_radio_params` / `_get_radio_prefs`, `meshcore_set_unix_time` / `_get_unix_time`, `meshcore_set_relay` / `_get_relay`
+
+Identity/contact/channel structs (`meshcore_identity_t`, `meshcore_contact_t`, `meshcore_channel_t`) are defined in `meshcore.h`.
+
+### Companion PhoneAPI (`meshcore_phoneapi`)
+
+Unlike Meshtastic's FSM, the outbound path is a **registered callback** invoked synchronously from command handlers and push helpers.
+
+```c
+esp_err_t meshcore_phoneapi_init(void); // loads/generates the 6-digit PIN from NVS
+void meshcore_phoneapi_set_outbound(meshcore_phoneapi_outbound_cb_t cb, void *ctx);
+uint32_t meshcore_phoneapi_get_pin(void); // 6-digit BLE pairing PIN
+void meshcore_phoneapi_on_disconnect(void);
+void meshcore_phoneapi_on_inbound(const uint8_t *buf, uint16_t len); // dispatches CMD_* opcodes
+void meshcore_phoneapi_push_new_advert(const meshcore_contact_t *contact);
+void meshcore_phoneapi_push_channel_msg(uint8_t ch, uint8_t path_len, uint32_t ts, int8_t snr, const char *text);
+void meshcore_phoneapi_push_contact_msg(const uint8_t peer_pub_key[32], uint8_t path_len,
+ uint8_t txt_type, uint32_t ts, int8_t snr, const char *text);
+void meshcore_phoneapi_push_send_confirmed(uint32_t ack_crc, uint8_t snr);
+void meshcore_phoneapi_push_path_updated(const meshcore_contact_t *contact);
+```
+
+### Phone bridge (`meshcore_phone_bridge`)
+
+```c
+esp_err_t meshcore_phone_bridge_init(const char *name_prefix); // NULL -> "Highboy-MC"
+void meshcore_phone_bridge_stop(void);
+esp_err_t meshcore_phone_bridge_ble_start(void);
+esp_err_t meshcore_phone_bridge_ble_stop(void);
+bool meshcore_phone_bridge_is_connected(void); // ble_connected
+bool meshcore_phone_bridge_is_subscribed(void); // ble_subscribed
+```
+
+Same C5-terminated model as Meshtastic (chunked reassembly, status polling task, reconcile loop that survives C5 reboots). Two differences worth noting:
+
+- **Pairing PIN:** MeshCore uses a **fixed, device-persisted 6-digit PIN** (from `meshcore_phoneapi_get_pin()`), passed to the C5 at BLE-init and injected on the passkey-display event. This PIN **is** rendered on the P4 chat connect screen.
+- **Linked-on-subscribe gating:** `ble_connected` flips at the raw link; `ble_subscribed` flips only after encryption and the app subscribing to the notify characteristic (CCCD subscribe on the TX characteristic, on the C5).
+
+## Chat UI (`ui/screens/lora/`)
+
+`lora_chat_ui` is a single-screen state machine.
+
+```c
+void ui_lora_chat_open(void); // opens on the protocol picker
+void ui_lora_chat_open_chat(void); // opens directly on the chat conversation
+```
+
+Views: **Protocol picker** (MeshCore / Meshtastic) -> **Home** -> { Connect App, Nodes, Chat, Configs }. Home also links out to the sibling screens: Channels, Position, Telemetry, Secure DM, Traceroute (each its own screen under `ui/screens/lora/`).
+
+- **Home** shows region + preset (Meshtastic only) and a status banner driven by the linked state.
+- **Nodes** shows either a radial "map" of node pins with RSSI-colored links or a scrolling list, both fed by `lora_session_node_count()` / `lora_session_node_get()`.
+- **Chat** renders message bubbles ("MESH BROADCAST" for Meshtastic, "PUBLIC CHANNEL" for MeshCore). An LVGL timer drains new messages every 500 ms via `lora_session_msg_since(...)`; the on-screen keyboard submits through `lora_session_send_text(...)`.
+- **Configs** (Meshtastic only) offers Region / Preset selectors and a Router-mode toggle.
+- **Connect** calls `lora_session_app_connect()`, polls `lora_session_app_connected()` every second, and shows the pairing PIN (MeshCore only).
+
+Starting a protocol runs `lora_session_start(proto)` on a background task pinned to the radio core; if a different protocol already owns the radio the UI prompts to reboot to switch.
+
+## RNode Modem (`rnode/`)
+
+`rnode/` shares the SX1262 driver but is **not** a mesh protocol - it is an **RNode-compatible KISS/serial modem** (Reticulum RNode firmware emulation, reporting v1.52). A host running Reticulum drives the radio over KISS-framed serial. It is mutually exclusive with the mesh stacks (same single-owner radio), but it is started independently of `lora_session` (it is not one of the `lora_proto_t` values).
+
+```c
+esp_err_t rnode_init(const sx1262_hal_t *hal); // KISS engine + serial + SX1262 hookup
+esp_err_t rnode_start(void); // IRQ task + continuous RX
+void rnode_poll(void); // drains serial, runs KISS decoder, dispatches
+const rnode_radio_cfg_t *rnode_get_radio_cfg(void);
+const rnode_stats_t *rnode_get_stats(void);
+```
+
+Defaults: 915 MHz, BW 125 kHz, SF8, CR 4/5, +17 dBm. The on-device screen is `lora_rnode_ui`.
diff --git a/docs/README.md b/docs/README.md
index 4a7f1569d..28434744a 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -14,21 +14,33 @@ README.md, separated by `---`.
| Component | Docs |
|-----------|------|
+| `audio_i2s` | [README.md](audio_i2s/README.md) - I2S audio output driver |
| `bad_usb` | [README.md](bad_usb/README.md) |
| `bluetooth` | [README.md](bluetooth/README.md) |
| `boot_report` | [README.md](boot_report/README.md) |
+| `bq25896` | [README.md](bq25896/README.md) - TI BQ25896 battery charger / PMIC driver |
+| `bridge_manager` | [README.md](bridge_manager/README.md) - P4<->C5 bridge lifecycle and coordination service |
| `buttons_gpio` | [README.md](buttons_gpio/README.md) |
| `c5_flasher` | [README.md](c5_flasher/README.md) |
| `cc1101` | [README.md](cc1101/README.md) |
| `console` | [README.md](console/README.md) |
| `dns_server` | [README.md](dns_server/README.md) |
+| `drv2605l` | [README.md](drv2605l/README.md) - TI DRV2605L haptic motor driver |
| `esp_now` | [README.md](esp_now/README.md) |
| `espnow_chat` | [README.md](espnow_chat/README.md) |
+| `gameboy` | [README.md](gameboy/README.md) - Game Boy emulator application |
| `host_link` | [app-guide.md](host_link/app-guide.md) [protocol.md](host_link/protocol.md) [README.md](host_link/README.md) |
| `http_server` | [README.md](http_server/README.md) |
+| `i2c_init` | [README.md](i2c_init/README.md) - shared I2C bus initialization driver |
| `input_manager` | [README.md](input_manager/README.md) |
-| `lvgl_port` | [README.md](lvgl_port/README.md) |
+| `ir` | [README.md](ir/README.md) - infrared transmit/receive service |
+| `led` | [README.md](led/README.md) - status/RGB LED driver |
+| `LoRa` | [README.md](LoRa/README.md) - LoRa messaging application |
+| `lvgl` | [README.md](lvgl/README.md) |
+| `nfc` | [README.md](nfc/README.md) - NFC read/write application |
| `ota` | [README.md](ota/README.md) |
+| `pins` | [README.md](pins/README.md) - board pin assignment definitions |
+| `power_manager` | [README.md](power_manager/README.md) - battery and power-state management service |
| `recovery` | [README.md](recovery/README.md) |
| `sd_card` | [README.md](sd_card/README.md) |
| `spi` | [README.md](spi/README.md) |
@@ -38,8 +50,11 @@ README.md, separated by `---`.
| `storage_assets` | [README.md](storage_assets/README.md) |
| `storage_vfs` | [README.md](storage_vfs/README.md) |
| `SubGhz` | [README.md](SubGhz/README.md) |
+| `sx1262` | [README.md](sx1262/README.md) - Semtech SX1262 LoRa radio transceiver driver |
| `sys_monitor` | [README.md](sys_monitor/README.md) |
| `sys_prio` | [README.md](sys_prio/README.md) |
+| `sys_time` | [README.md](sys_time/README.md) - system clock / RTC time service |
| `tusb_desc` | [README.md](tusb_desc/README.md) |
| `ui` | [README.md](ui/README.md) [input-migration.md](ui/input-migration.md) |
| `wifi` | [README.md](wifi/README.md) |
+| `ys_rfid2` | [README.md](ys_rfid2/README.md) - YS-RFID2 125 kHz RFID reader driver |
diff --git a/docs/SubGhz/README.md b/docs/SubGhz/README.md
index e8b2c7376..87535b93b 100644
--- a/docs/SubGhz/README.md
+++ b/docs/SubGhz/README.md
@@ -60,6 +60,8 @@ Captures RF signals via the CC1101 GDO0 pin routed to the ESP32 RMT RX periphera
4. **SCAN mode:** Protocol registry tries all decoders -> Analyzer for unknowns
5. **RAW mode:** Direct save to storage
+**LED feedback:** The RX pipeline drives the status LED as it processes signals: `led_signal_info()` fires when a known protocol is decoded, and `led_signal_warning()` fires when RF is captured but no registered protocol matches.
+
#### API
```c
@@ -120,7 +122,7 @@ void subghz_spectrum_start(uint32_t center_freq, uint32_t span_hz);
void subghz_spectrum_stop(void);
bool subghz_spectrum_get_line(subghz_spectrum_line_t *out_line);
```
-- Task stack: 4096 bytes, priority 1, Core 1.
+- Task stack: 4096 bytes, Core 1. Priority comes from `sys_prio.h`: the `SYS_PRIO_BACKGROUND_LO` band (lowest non-idle background work), so the sweep never contends with the renderer or latency-sensitive services.
- Thread-safe reads via `subghz_spectrum_get_line`.
### Signal Analyzer (`subghz_analyzer`)
diff --git a/docs/audio_i2s/README.md b/docs/audio_i2s/README.md
new file mode 100644
index 000000000..fdc309311
--- /dev/null
+++ b/docs/audio_i2s/README.md
@@ -0,0 +1,151 @@
+# Audio I2S Driver (P4)
+
+This component drives the on-board audio path of the HighBoy V2 (ESP32-P4 firmware only): a class-D speaker amplifier for playback (TX) and a PDM microphone for capture (RX), both on a single I2S port. It owns one persistent TX channel used for UI chimes, tones, melodies, raw PCM and continuous streaming, and opens independent PDM-RX channels for mic recording and live streaming.
+
+## Overview
+
+- **Location:** `firmware_p4/components/Drivers/audio_i2s/`
+- **Header:** `include/audio_i2s.h`
+- **Firmware:** `firmware_p4` only (no C5 counterpart)
+- **Dependencies:** `driver/i2s_std`, `driver/i2s_pdm`, `driver/gpio`, `freertos`, `pin_def`, `sys_prio`
+- **I2S port:** `I2S_NUM_0` (master role). The single TX channel and the PDM-RX channels share this one port.
+
+> **Part naming note:** the public header docstrings refer to a **MAX98357A** amplifier, while the driver source (`audio_i2s.c`) and the pin map (`pin_def.h`) name an **NS4168** amp and an **MSM261** PDM mic. The code and pin comments are the authoritative wiring reference; the amplifier is enabled by taking its shutdown pin high, and its hardware gain is fixed, so volume is applied digitally.
+
+## Signal Path
+
+### TX (speaker / amplifier)
+
+- **Mode:** I2S standard (Philips) slot format, **16-bit**, **mono**, master.
+- **Default sample rate:** `44100` Hz (`SAMPLE_RATE_HZ`). PCM and stream calls may pass any rate; the driver reconfigures the TX clock only when the requested rate actually changes.
+- **One persistent channel:** `I2S_NUM_0` has exactly one TX channel. It is created once at init and never deleted; playback only enables/disables it and reconfigures its clock. Creating/deleting a channel per sound churned internal DMA RAM and stuttered the UI, so that is avoided.
+- **DMA depth:** `AUDIO_DMA_DESC_NUM` = 8 descriptors x `AUDIO_DMA_FRAME_NUM` = 256 frames (~46 ms, ~8 KB internal RAM), deeper than the IDF default for underrun slack.
+- **Underrun behaviour:** `auto_clear = true`, so a starved channel emits zeros instead of looping the stale buffer.
+- **Anti-click:** each note gets a raised-cosine attack (`ENV_ATTACK_MS` = 4 ms) / release (`ENV_RELEASE_MS` = 8 ms) envelope, and playback brackets sounds with short silence; melodies use a continuous phase accumulator for gapless rendering.
+- **Amp enable:** `GPIO_AUDIO_EN_PIN` is driven high (out of shutdown) at init; idempotent.
+
+### RX (PDM microphone)
+
+- **Mode:** PDM RX, **16-bit**, **mono**, master.
+- **Filtering:** high-pass filter enabled with a 50 Hz cut-off (`hp_cut_off_freq_hz`), digital gain `amplify_num = 3`. A ~120 ms settle delay is applied after enable to let the HP filter converge.
+- **Channels:** the mic uses its own RX channel on `I2S_NUM_0`, independent of the TX side. `audio_i2s_mic_record()` opens/closes a channel per call; the streaming API keeps a persistent RX channel (`s_rx_stream`).
+
+## Concurrency
+
+- Playback runs on a pinned task `"audio_i2s"`: stack `AUDIO_TASK_STACK_SIZE` = 4096, priority `SYS_PRIO_SERVICE_LO`, core `SYS_CORE_RADIO`. It is deliberately **not** on the UI core, because the LVGL renderer at `SYS_PRIO_RENDER` would starve the I2S DMA during a full-frame redraw.
+- A single mutex (`s_tx_mux`) guards the shared TX channel. Callers from the LVGL thread use finite timeouts and drop the sound rather than stall the UI:
+ - `TX_LOCK_WAIT_MS` = 250 ms for blocking playback / stream claims.
+ - `FX_TX_WAIT_MS` = 0 for queued UI effects (never wait behind a melody).
+- Queued UI effects (`audio_play_chime`, `audio_click`) post `fx_t` entries to a depth-4 queue (`QUEUE_DEPTH`); overflow is dropped so button mashing never piles up.
+- While a TX stream is active (`s_streaming`), one-shot playback calls become no-ops so they do not fight the stream.
+
+## Pins (from `pin_def.h`)
+
+| Signal | Define | GPIO |
+| :--- | :--- | :--- |
+| Amp enable / shutdown | `GPIO_AUDIO_EN_PIN` | 49 |
+| I2S bit clock (BCLK) | `GPIO_AUDIO_BCLK_PIN` | 28 |
+| I2S word select (LRCLK / WS) | `GPIO_AUDIO_LRCLK_PIN` | 29 |
+| I2S data out (DOUT) | `GPIO_AUDIO_DIN_PIN` | 27 |
+| Mic PDM clock | `GPIO_MIC_PDM_CLK_PIN` | 54 |
+| Mic PDM data | `GPIO_MIC_PDM_DATA_PIN` | 53 |
+
+MCLK and the TX-side DIN are `I2S_GPIO_UNUSED`.
+
+## API Reference
+
+### Initialization
+
+#### `audio_i2s_init`
+```c
+esp_err_t audio_i2s_init(void);
+```
+Brings up `I2S_NUM_0` on the amp pins, allocates the DMA chunk buffer, mutex and effect queue, and starts the playback task. Idempotent; safe to call multiple times.
+
+#### `audio_i2s_set_volume`
+```c
+void audio_i2s_set_volume(uint8_t pct);
+```
+Sets global output volume (0..100), clamped. Applied as a perceptual (square-law) digital scale on top of each sound's own amplitude, because the amplifier's hardware gain is fixed.
+
+### Tones and Melodies
+
+#### `audio_i2s_play_tone`
+```c
+esp_err_t audio_i2s_play_tone(float freq_hz, int dur_ms, float amp);
+```
+Plays a single blocking sine tone at 44.1 kHz mono. `amp` is in [0..1]. Returns `ESP_ERR_TIMEOUT` if the channel is busy (sound dropped), `ESP_OK` otherwise (also when not ready or a stream is active).
+
+#### `audio_i2s_play_song` / `audio_i2s_play_song_cb`
+```c
+esp_err_t audio_i2s_play_song(const audio_note_t *notes, int count, float amp);
+esp_err_t audio_i2s_play_song_cb(const audio_note_t *notes, int count, float amp,
+ audio_song_progress_cb_t cb, void *ctx);
+```
+Plays a melody blocking and gapless: opens the channel once, renders all notes back-to-back with per-note anti-click envelopes and a continuous phase accumulator, then ends. The `_cb` variant invokes `cb` before each note (in the worker thread) with the note index, total count and frequency; returning `false` cancels playback cooperatively after the current note. Pass `cb = NULL` for plain playback.
+
+`audio_note_t` is `{ uint16_t freq_hz; uint16_t dur_ms; }`; `freq_hz == 0` is a rest (silence).
+
+`audio_song_progress_cb_t`:
+```c
+typedef bool (*audio_song_progress_cb_t)(int note_index, int note_count,
+ uint16_t freq_hz, void *ctx);
+```
+Must be trivial (touch only volatile scalars); never call `lv_*` from it.
+
+### Raw PCM Playback
+
+#### `audio_i2s_play_pcm`
+```c
+esp_err_t audio_i2s_play_pcm(const int16_t *pcm, size_t n_samples, uint32_t sample_rate);
+```
+Plays a raw 16-bit mono PCM buffer (blocking) at `sample_rate`. Reconfigures the TX clock to the given rate and appends a short tail-silence.
+
+### Continuous TX Streaming
+
+#### `audio_i2s_stream_start` / `audio_i2s_stream_write` / `audio_i2s_stream_stop`
+```c
+esp_err_t audio_i2s_stream_start(uint32_t sample_rate);
+int audio_i2s_stream_write(const int16_t *pcm, int n_samples);
+void audio_i2s_stream_stop(void);
+```
+Begins a continuous 16-bit mono TX stream (e.g. WAV playback), reusing the shared TX channel and suppressing UI chimes/clicks for its duration. `stream_write` applies global volume, blocks and paces to real time via the I2S DMA, and returns samples written (or -1 if the stream is not active). `stream_stop` drains the DMA, restores the default clock and re-enables UI sounds; safe when not started.
+
+### PDM Microphone Capture
+
+#### `audio_i2s_mic_record`
+```c
+esp_err_t audio_i2s_mic_record(int16_t *out, size_t max_samples, uint32_t sample_rate,
+ size_t *out_captured, audio_mic_level_cb_t cb, void *ctx);
+```
+Captures up to `max_samples` of 16-bit mono PCM from the PDM mic (blocking): opens a PDM-RX channel, lets the HP filter settle (~120 ms), fills `out`, then closes. `*out_captured` receives the real sample count. If `cb` is non-NULL it is invoked after each chunk with that chunk's peak and RMS level for a live VU meter.
+
+`audio_mic_level_cb_t`:
+```c
+typedef void (*audio_mic_level_cb_t)(int peak, int rms, void *ctx);
+```
+`peak` is max |sample| (0..32767) of the last chunk. Runs in the recording task's context; keep it trivial.
+
+#### `audio_i2s_mic_stream_start` / `audio_i2s_mic_stream_read` / `audio_i2s_mic_stream_stop`
+```c
+esp_err_t audio_i2s_mic_stream_start(uint32_t sample_rate);
+int audio_i2s_mic_stream_read(int16_t *buf, int max_samples);
+void audio_i2s_mic_stream_stop(void);
+```
+Opens a persistent PDM-RX stream at `sample_rate` (idempotent). `mic_stream_read` reads up to `max_samples` 16-bit mono samples, returning the count read (0 on error/timeout/not-started). `mic_stream_stop` stops and frees the RX channel; safe when not started.
+
+### UI Sound Effects
+
+#### `audio_play_chime`
+```c
+void audio_play_chime(void);
+```
+Queues a 3-note boot chime (C5 -> E5 -> G5, ~130-180 ms each). Non-blocking; dropped if the queue is full.
+
+#### `audio_click`
+```c
+void audio_click(void);
+```
+Queues a short click (~30 ms, ~2 kHz). Non-blocking; dropped if the queue already holds pending entries so rapid button presses never pile up.
+
+
diff --git a/docs/bad_usb/README.md b/docs/bad_usb/README.md
index b89fc7086..c4a58fc9a 100644
--- a/docs/bad_usb/README.md
+++ b/docs/bad_usb/README.md
@@ -51,26 +51,29 @@ This component implements a modular HID injection tool capable of emulating keyb
esp_err_t bad_usb_init(void);
esp_err_t bad_usb_deinit(void);
void bad_usb_wait_for_connection(void);
+bool bad_usb_wait_for_connection_ex(bad_usb_abort_cb_t should_abort);
```
-- `bad_usb_init` initializes TinyUSB and registers USB HID callbacks into the HAL.
+- `bad_usb_init` initializes TinyUSB (via `busb_init`) and registers USB HID callbacks (keyboard, mouse, wait, report-ready) into the HAL.
- `bad_usb_deinit` unregisters callbacks and uninstalls the TinyUSB driver.
-- `bad_usb_wait_for_connection` blocks until the USB host mounts the device, then waits 2 seconds for enumeration.
+- `bad_usb_wait_for_connection` blocks until the USB host mounts the device, then waits 2 seconds (`USB_SETTLE_DELAY_MS`) for the host OS to enumerate.
+- `bad_usb_wait_for_connection_ex` is the abortable variant. It polls `tud_mounted()` with an **8 second mount timeout** (`USB_MOUNT_TIMEOUT_MS`), calling `should_abort` (a `bad_usb_abort_cb_t`) between polls and during the post-mount settle. It returns `true` if the host mounted and settled, `false` if the timeout elapsed or the abort predicate fired. `bad_usb_wait_for_connection` is just `_ex(NULL)`, which never times out on the abort path but still stops after the mount timeout. Use `_ex` so a run waiting for a host that never arrives can be cancelled.
### HID HAL (`hid_hal.h`)
```c
void hid_hal_register_callback(hid_send_cb_t send_cb,
hid_mouse_cb_t mouse_cb,
- hid_wait_cb_t wait_cb);
+ hid_wait_cb_t wait_cb,
+ hid_ready_cb_t ready_cb);
void hid_hal_press_key(uint8_t keycode, uint8_t modifiers);
void hid_hal_mouse_move(int8_t x, int8_t y);
void hid_hal_mouse_click(uint8_t buttons);
void hid_hal_mouse_scroll(int8_t wheel);
void hid_hal_wait_for_connection(void);
```
-- `hid_hal_press_key` sends a key-down + key-up report with ~5 ms per phase.
-- Mouse functions use ~2 ms delay for moves and ~5 ms for clicks.
-- All functions yield to the scheduler (`vTaskDelay(0)`) to prevent WDT starvation.
+- Reports are paced by a report-readiness callback (`ready_cb`), not fixed delays. Before and after each report the HAL calls `wait_report_ready`, which spins on `tud_hid_ready()` (yielding with `vTaskDelay(1)` between checks) until the previous report has been delivered to the host, so an unpolled report is never overwritten (which would drop keys). This paces output to the host's ~1 ms poll rate.
+- The wait is capped by `REPORT_READY_TIMEOUT_MS` (1000 ms): it must exceed how long a busy host/editor can stall HID polling, so only a real disconnect hits it. Raising it to 1 s is what keeps keys (e.g. a leading `HOME`) from being dropped when the host stalls.
+- If no `ready_cb` is registered, the HAL falls back to fixed `ets_delay_us` delays (`FALLBACK_KEY_DELAY_US` = 5000 us for keys, `FALLBACK_MOUSE_DELAY_US` = 2000 us for mouse).
### Keyboard Layouts (`hid_layouts.h`)
@@ -79,7 +82,12 @@ void hid_layouts_type_string_us(const char *str);
void hid_layouts_type_string_abnt2(const char *str);
```
- `hid_layouts_type_string_us` maps ASCII characters to US keyboard HID keycodes.
-- `hid_layouts_type_string_abnt2` handles Brazilian Portuguese layout including UTF-8 dead-key sequences for accented characters (e.g. a, e, c, a, o).
+- `hid_layouts_type_string_abnt2` types the Brazilian Portuguese (ABNT2) layout with full accented-character coverage, not just a few dead-key accents. It decodes UTF-8 two-byte sequences via a lookup table (`ABNT2_UTF8_MAP`) that covers:
+ - Accented vowels **a e i o u**, lowercase and uppercase, in all five diacritics: acute, grave, circumflex, tilde, and diaeresis (each emitted as the ABNT2 dead key + base letter, with Shift for uppercase).
+ - **c-cedilha** (c) and **n-tilde** (n), both lowercase and uppercase.
+ - AltGr (RightAlt) symbols: cent, pound, section, not-sign, and superscripts 1 / 2 / 3.
+ - Bracket/brace/backslash/pipe (`[ ] { } \ |`) via RightAlt (with Shift for the brace/pipe forms).
+ - The symbols that a naive US mapping gets wrong on ABNT2 hardware are fixed: `^` and `~` are typed as dead key + space to emit the literal, and `<` / `>` use the correct ABNT2 keys. Apostrophe / double-quote / backtick are also remapped to their ABNT2 positions.
### DuckyScript Parser (`ducky_parser.h`)
@@ -106,18 +114,20 @@ void ducky_abort(void);
| `DELAY` | [ms] | Pause execution for N milliseconds |
| `STRING` | [text] | Type text using the active keyboard layout |
| `ENTER` / `RETURN` | - | Press Enter |
-| `GUI` / `WINDOWS` / `COMMAND` | [key] | Windows/Command key (optionally with a key) |
+| `GUI` / `WINDOWS` / `SUPER` / `COMMAND` | [key] | GUI (Windows/Command/Super) modifier, optionally with a key |
| `CTRL` / `CONTROL` | [key] | Control + key |
| `SHIFT` | [key] | Shift + key |
| `ALT` | [key] | Alt + key |
| `TAB` | - | Tab key |
| `ESC` / `ESCAPE` | - | Escape key |
+| `SPACE` | - | Space bar |
+| `BACKSPACE` | - | Backspace |
| `F1` - `F12` | - | Function keys |
-| `UP` / `DOWN` / `LEFT` / `RIGHT` | - | Arrow keys |
+| `UP` / `DOWN` / `LEFT` / `RIGHT` (and `UPARROW` / `DOWNARROW` / `LEFTARROW` / `RIGHTARROW`) | - | Arrow keys |
| `HOME` / `END` / `INSERT` / `DELETE` | - | Navigation keys |
| `PAGEUP` / `PAGEDOWN` | - | Page navigation |
| `CAPSLOCK` / `NUMLOCK` / `SCROLLLOCK` | - | Lock keys |
-| `PRINTSCREEN` / `PAUSE` / `APP` / `MENU` | - | Special system keys |
+| `PRINTSCREEN` / `PAUSE` / `APP` / `MENU` | - | Special system keys (`APP` and `MENU` both map to the Application key) |
| `MOUSE_MOVE` | [x] [y] | Move mouse relative (-127 to 127) |
| `MOUSE_CLICK` / `LCLICK` | - | Left mouse click |
| `MOUSE_RIGHT_CLICK` / `RCLICK` | - | Right mouse click |
@@ -130,5 +140,14 @@ Modifier keys can be combined: `CTRL SHIFT ESC`, `GUI r`, `ALT F4`.
| Layout | Enum | Notes |
|--------|------|-------|
| US (QWERTY) | `DUCKY_LAYOUT_US` | Default. Standard ASCII mapping. |
-| ABNT2 (Brazil) | `DUCKY_LAYOUT_ABNT2` | Dead-key accent support, remapped punctuation. |
+| ABNT2 (Brazil) | `DUCKY_LAYOUT_ABNT2` | Full accented-character coverage (see the layouts API above), remapped punctuation, AltGr symbols. |
+
+## Payload Sources
+
+Scripts run either from the internal flash asset partition (`ducky_run_from_assets`) or from the SD card (`ducky_run_from_sdcard`, max 8 KB per script). In the UI menu, SD payloads are scanned **recursively across subfolders** and listed together in a single view, so scripts organised into folders on the card are all discoverable.
+
+Example payloads ship in the asset partition under `firmware_p4/assets/storage/bad_usb_scripts/`:
+
+- `rickroll.txt`
+- `amiga.txt`
diff --git a/docs/bluetooth/README.md b/docs/bluetooth/README.md
index 249e7f91c..3ae42d66f 100644
--- a/docs/bluetooth/README.md
+++ b/docs/bluetooth/README.md
@@ -59,6 +59,37 @@ Performs a blocking discovery procedure for the specified duration. Results are
- `bluetooth_service_get_scan_count()`: Returns the number of unique devices found.
- `bluetooth_service_get_scan_result(uint16_t index)`: Returns a pointer to a `bluetooth_service_scan_result_t` structure containing name, RSSI, and MAC address.
+### Sniffer & Tracker
+
+#### `bluetooth_service_start_sniffer`
+```c
+typedef void (*bluetooth_service_sniffer_cb_t)(
+ const uint8_t *addr, uint8_t addr_type, int rssi, const uint8_t *data, uint16_t len);
+
+esp_err_t bluetooth_service_start_sniffer(bluetooth_service_sniffer_cb_t cb);
+```
+Starts a passive BLE sniffer (raw advertisement capture). `cb` is invoked for each received advertisement with the advertiser address, address type, RSSI, and the raw advertisement bytes.
+
+#### `bluetooth_service_stop_sniffer`
+```c
+void bluetooth_service_stop_sniffer(void);
+```
+Stops the BLE sniffer.
+
+#### `bluetooth_service_start_tracker`
+```c
+typedef void (*bluetooth_service_tracker_cb_t)(int rssi);
+
+esp_err_t bluetooth_service_start_tracker(const uint8_t *addr, bluetooth_service_tracker_cb_t cb);
+```
+Starts RSSI tracking for a specific BLE device (6-byte `addr`). `cb` is invoked with the RSSI of the tracked device on each update.
+
+#### `bluetooth_service_stop_tracker`
+```c
+void bluetooth_service_stop_tracker(void);
+```
+Stops the RSSI tracker.
+
### Advertising Management
#### `bluetooth_service_start_advertising` / `stop_advertising`
@@ -66,6 +97,14 @@ Standard connectable advertising using the configured device name. Advertising a
### Connection Management
+#### `bluetooth_service_connect`
+```c
+esp_err_t bluetooth_service_connect(const uint8_t *addr,
+ uint8_t addr_type,
+ int (*cb)(struct ble_gap_event *event, void *arg));
+```
+Initiates a BLE connection to a remote device. `addr` is the 6-byte target address, `addr_type` its address type, and `cb` a GAP event callback that receives connection events for this link.
+
#### `bluetooth_service_disconnect_all`
```c
void bluetooth_service_disconnect_all(void);
diff --git a/docs/boot_report/README.md b/docs/boot_report/README.md
index a7ee0c0d4..1ff3a35cb 100644
--- a/docs/boot_report/README.md
+++ b/docs/boot_report/README.md
@@ -26,9 +26,12 @@ const boot_stage_t *boot_report_stages(int *out_count);
bool boot_report_all_required_ok(void);
```
-Recorded stages with their real return codes: `sd-storage` (optional, absent SD
-is fine), `assets` (required), `battery` (optional), `display` (required),
-`nvs` (required). A **required** failure makes `kernel_init` drop into
+Recorded stages with their real return codes, in the order `kernel_init` runs
+them: `nvs` (required), `i2c` (optional, degrades charger/haptic/LED only),
+`sd-storage` (optional, absent SD is fine), `sd-health` (optional, recorded only
+when the SD is mounted), `assets` (required), `battery` (optional), `audio`
+(optional), `display` (required), `lvgl` (required, recorded only when the panel
+came up). A **required** failure makes `kernel_init` drop into
[safe mode](../recovery/README.md) instead of booting blind, and the function
returns `ESP_FAIL`.
@@ -95,6 +98,7 @@ void boot_report_track_bootloop(void); // FIRST thing in app_main
bool boot_report_in_bootloop(void);
uint32_t boot_report_abnormal_boots(void);
uint32_t boot_report_panic_total(void);
+uint32_t boot_report_boot_count(void);
void boot_report_mark_stable(void);
```
@@ -113,9 +117,13 @@ void boot_report_mark_stable(void);
[safe mode](../recovery/README.md) - the degraded target (no radios, no SD
theme, minimal UI). The safe-mode footer shows the reset reason, and the crash
viewer shows `Panics total` and `Abnormal boots N/3`.
-- Only a **summary** is persisted to NVS (namespace `boot_report`: last reason +
- running panic total), written on abnormal boots only, so flash wear is
- negligible. The RTC counter carries the loop state itself.
+- A **summary** is persisted to NVS (namespace `boot_report`). A real
+ **total boot count** (`boot_total`, exposed via `boot_report_boot_count()`) is
+ bumped once every boot from `boot_report_capture_crash` - one small write per
+ boot. The **last reason** + running **panic total** (`boot_report_panic_total()`)
+ are written only on abnormal boots, so panic-path flash wear stays negligible.
+ The RTC counter carries the consecutive-loop state itself; NVS holds the
+ lifetime totals.
## Follow-ups
diff --git a/docs/bq25896/README.md b/docs/bq25896/README.md
new file mode 100644
index 000000000..8e8711de9
--- /dev/null
+++ b/docs/bq25896/README.md
@@ -0,0 +1,172 @@
+# BQ25896 Charger / PMIC Driver
+
+This component drives the Texas Instruments BQ25896 single-cell battery charger and power-path IC over I2C. It exposes battery voltage, charge and VBUS status, charge enable control, and a real ship-mode power-off. A thin `battery_service` layer sits on top and publishes a single smoothed snapshot that the UI reads instead of polling the charger directly.
+
+## Overview
+
+- **Location:** `components/Drivers/bq25896/` (firmware_p4 only)
+- **Headers:** `include/bq25896.h`, `include/battery_service.h`
+- **Sources:** `bq25896.c` (register I/O + controls), `bq25896_ext.c` (telemetry aggregator), `battery_service.c` (poll task + smoothing)
+- **Dependencies:** `i2c_init`, `pins` (`pin_def.h`), `sys_prio`, `driver/gpio`, `freertos`
+- **Interface:** I2C master bus `I2C_NUM_0` (via `i2c_init`, shared), 100 kHz standard-mode
+- **I2C address:** `0x6B` (`BQ25896_I2C_ADDR`, 7-bit)
+- **CE pin:** `GPIO_CHARGER_CE_PIN` (GPIO33), active-low charge enable, driven at init
+
+## Charge Status (`bq25896_charge_status_t`)
+
+| Value | Meaning |
+|-------|---------|
+| `CHARGE_STATUS_NOT_CHARGING` | Not charging |
+| `CHARGE_STATUS_PRECHARGE` | Pre-charge phase |
+| `CHARGE_STATUS_FAST_CHARGE` | Fast-charge phase |
+| `CHARGE_STATUS_CHARGE_DONE` | Charge complete |
+
+## VBUS Status (`bq25896_vbus_status_t`)
+
+| Value | Meaning |
+|-------|---------|
+| `VBUS_STATUS_UNKNOWN` | No/unknown VBUS source |
+| `VBUS_STATUS_USB_HOST` | USB host (SDP) input |
+| `VBUS_STATUS_ADAPTER_PORT` | Dedicated adapter input |
+| `VBUS_STATUS_OTG` | OTG (boost) output |
+
+## BQ25896 API Reference
+
+### Initialization
+
+#### `bq25896_init`
+```c
+esp_err_t bq25896_init(void);
+```
+Adds the charger to the shared I2C bus, probes it (REG14), and marks the chip present. Drives CE (GPIO33) low to enable charging, disables the charge watchdog and sets `JEITA_ISET=0` (REG07), lowers `SYS_MIN` to 3.0 V (REG03) so the battery ADC keeps converting down to the 3.0 V knee, and enables the continuous ADC (REG02). Returns `ESP_OK` on success, or the failing `esp_err_t`.
+
+#### `bq25896_is_present`
+```c
+bool bq25896_is_present(void);
+```
+True once the charger has answered on I2C at init.
+
+### Battery State
+
+#### `bq25896_get_battery_voltage`
+```c
+uint16_t bq25896_get_battery_voltage(void);
+```
+Battery voltage in mV from REG0E (base 2304 mV, 20 mV/step), or 0 on read failure.
+
+#### `bq25896_get_battery_percentage`
+```c
+int bq25896_get_battery_percentage(uint16_t voltage_mv);
+```
+Linear voltage-to-percent estimate (0-100) between 3200 mV and 4200 mV.
+
+#### `bq25896_get_charge_status` / `bq25896_get_vbus_status`
+```c
+bq25896_charge_status_t bq25896_get_charge_status(void);
+bq25896_vbus_status_t bq25896_get_vbus_status(void);
+```
+Decode the CHG_STAT and VBUS_STAT fields of the status register (REG0B).
+
+#### `bq25896_is_charging`
+```c
+bool bq25896_is_charging(void);
+```
+True while pre-charging or fast charging.
+
+#### `bq25896_get_fault`
+```c
+uint8_t bq25896_get_fault(void);
+```
+Raw fault register (REG0C): `CHRG_FAULT[5:4]`, `BAT_FAULT[3]`, `NTC_FAULT[2:0]`.
+
+### Charge Control
+
+#### `bq25896_get_charge_enable`
+```c
+bool bq25896_get_charge_enable(void);
+```
+Reads REG03 `CHG_CONFIG` (bit 4).
+
+#### `bq25896_set_charge_enable`
+```c
+esp_err_t bq25896_set_charge_enable(bool enable);
+```
+Drives both gates: the active-low CE pin (GPIO33) and REG03 `CHG_CONFIG`, so the state is unambiguous.
+
+### Ship Mode / Power Off
+
+#### `bq25896_power_off`
+```c
+esp_err_t bq25896_power_off(void);
+```
+Real power-off: sets `BATFET_DIS` (REG09 bit 5) to disconnect the battery. Also sets `BATFET_DLY` (bit 3) so the MCU finishes the I2C write before the rail collapses, and clears `BATFET_RST_EN` (bit 2) to disarm the /QON auto-reset. Without disarming, a /QON pulse (the BACK button on this board) would cycle BATFET back on and cold-boot the device a few seconds later. Has no effect while VBUS (USB) is present: the part keeps the system powered from USB.
+
+### Telemetry
+
+#### `bq25896_read_telemetry`
+```c
+esp_err_t bq25896_read_telemetry(bq25896_telem_t *out);
+```
+Fills a `bq25896_telem_t` snapshot. Battery voltage/percent/charge/VBUS/fault fields are real; the `vsys_mv`, `vbus_mv`, `ichg_ma`, and `iinlim_ma` diagnostic fields are currently 0 (approximate/unpopulated). Returns `ESP_ERR_INVALID_ARG` if `out` is NULL.
+
+`bq25896_telem_t` fields: `vbat_mv`, `vsys_mv`, `vbus_mv`, `ichg_ma`, `iinlim_ma`, `fault`, `soc`, `chg`, `vbus`, `charging`, `power_good`.
+
+#### `bq25896_reg_raw`
+```c
+uint8_t bq25896_reg_raw(uint8_t reg);
+```
+Raw single-register read, or 0 on failure.
+
+## Battery Service API Reference
+
+A background poll task (`battery_svc`, core `SYS_CORE_RADIO`, priority `SYS_PRIO_BACKGROUND`) keeps a mutex-guarded smoothed snapshot so screens do not each poll the charger.
+
+#### `battery_service_init`
+```c
+void battery_service_init(void);
+```
+Idempotent. Requires `bq25896_init()` first. Takes one synchronous reading so the first UI paint has real data, then polls in the background.
+
+#### `battery_service_get`
+```c
+bool battery_service_get(battery_snapshot_t *out);
+```
+Copies the latest snapshot into `out`. Returns true if a valid reading is available.
+
+#### `battery_service_soc` / `battery_service_is_low`
+```c
+int battery_service_soc(void);
+bool battery_service_is_low(void);
+```
+Convenience accessors: smoothed SoC (0-100, or -1 if no valid reading yet) and the latched low-battery flag.
+
+`battery_snapshot_t` fields: `soc`, `vbat_mv`, `present`, `charging`, `vbus_present`, `low`, `valid`, `chg`.
+
+### Filtered SoC and Smoothing
+
+The raw voltage-derived SoC is filtered before it reaches the UI:
+
+- **EMA:** `ema = (ema*3 + raw) / 4` (`SOC_EMA_DEN = 4`) rejects the transient voltage sag when a radio transmits.
+- **Slew limit:** at most `SOC_STEP_MAX = 3` % change applied per poll.
+- **Monotonic on battery:** off external power, SoC never climbs (a recovering voltage after a load sag would otherwise bounce it up); it only rises while charging or on external power.
+- **Low-battery hysteresis:** latches `low` at/below `LOW_ENTER_PCT = 15`, releases at/above `LOW_EXIT_PCT = 20`; never low while charging or on VBUS.
+- **Charging debounce:** turns the charging indicator on immediately, off immediately when unplugged, but requires `CHG_OFF_DEBOUNCE = 3` consecutive not-charging polls to drop it while still on external power.
+
+## Key Config / Tunables
+
+| Macro | Location | Value | Meaning |
+|-------|----------|-------|---------|
+| `BQ25896_I2C_ADDR` | `bq25896.h` | `0x6B` | I2C 7-bit address |
+| `I2C_TIMEOUT_MS` | `bq25896.c` | 100 | Per-transfer timeout |
+| `I2C_FAIL_RECOVER` | `bq25896.c` | 3 | Consecutive failures before `i2c_bus_recover()` |
+| `BATV_BASE_MV` / `BATV_STEP_MV` | `bq25896.c` | 2304 / 20 | REG0E voltage decode |
+| `BATTERY_MIN_MV` / `BATTERY_MAX_MV` | `bq25896.c` | 3200 / 4200 | Percent-estimate endpoints |
+| `POLL_INTERVAL_MS` | `battery_service.c` | 1000 | Steady poll cadence (first sample at ~1200 ms) |
+| `SOC_STEP_MAX` | `battery_service.c` | 3 | Max SoC delta per poll |
+| `SOC_EMA_DEN` | `battery_service.c` | 4 | EMA denominator |
+| `LOW_ENTER_PCT` / `LOW_EXIT_PCT` | `battery_service.c` | 15 / 20 | Low-battery hysteresis |
+| `CHG_OFF_DEBOUNCE` | `battery_service.c` | 3 | Not-charging polls before clearing the indicator |
+
+### Bus Recovery
+
+Reads and writes track a failure streak; after `I2C_FAIL_RECOVER` consecutive failures the driver calls `i2c_bus_recover()` (from `i2c_init`) to clock out a slave that has wedged the bus, so the charger does not stay unreadable for the rest of the session.
diff --git a/docs/bridge_manager/README.md b/docs/bridge_manager/README.md
new file mode 100644
index 000000000..2b866512d
--- /dev/null
+++ b/docs/bridge_manager/README.md
@@ -0,0 +1,71 @@
+# Bridge Manager - P4
+
+The **P4-side lifecycle / link manager for the P4<->C5 SPI bridge**. It brings the
+bridge up in the right handshake mode, checks that the two chips agree on the wire
+contract, keeps the link status honest while the C5 comes and goes, and is the
+entry point that triggers a C5 firmware update.
+
+- Location: `firmware_p4/components/Service/bridge_manager/`
+- Transport it drives: [`../spi_bridge/README.md`](../spi_bridge/README.md)
+- Update path it triggers: [`../c5_flasher/README.md`](../c5_flasher/README.md)
+
+## API
+
+```c
+esp_err_t bridge_manager_init(void); // bring up the bridge + run the boot checks
+esp_err_t bridge_manager_force_update(void); // push a C5 app OTA (SD image, SPI transport)
+```
+
+## What `bridge_manager_init()` does
+
+1. **Bridge init (POLL mode).** The HighBoy V2 PCB has no bridge IRQ trace
+ (`GPIO_BRIDGE_IRQ_PIN == -1`), so the master polls the bus instead of waiting
+ on an IRQ: `spi_bridge_master_init_mode(SPI_BRIDGE_MODE_POLL)`. The C5 slave
+ must be initialized in the matching mode.
+2. **App-version check.** Reads the C5 firmware version (`SPI_ID_SYSTEM_VERSION`)
+ and compares it against the P4's expected `FIRMWARE_VERSION` (generated from
+ `common/metadata/version_info.txt`, currently **1.4.0**). This is
+ **detection-only**: a mismatch just logs "update available" and a silent C5
+ marks the bridge down - the P4 never auto-flashes. The user updates explicitly
+ with the `c5` console command.
+3. **Proto-version match check (SPI-2).** `check_c5_protocol()` reads the C5's
+ `SPI_ID_SYSTEM_PROTO_VERSION` and compares it against `SPI_PROTOCOL_VERSION`
+ (**2**). This is the second integrity layer, distinct from the per-frame CRC
+ (SPI-1): a drifted `spi_protocol.h` leaves the bytes intact but makes the two
+ ends assign them different meaning, which the CRC cannot catch. On mismatch
+ (or an older C5 that answers `UNSUPPORTED`) it logs loudly and calls
+ `led_signal_error()`, but keeps the bridge up - detection, not enforcement.
+4. **Starts the C5 link monitor task** (below).
+
+## C5 link monitor
+
+A background task (`c5_link_monitor`), pinned to `SYS_CORE_RADIO` at
+`SYS_PRIO_SERVICE_LO`, that re-detects a C5 which booted late or rebooted after an
+OTA:
+
+- Idles while `spi_bridge_is_alive()` - it never pokes the bus once the link is up.
+- While the bridge is dead, every ~1.5 s it optimistically probes
+ `SPI_ID_SYSTEM_VERSION` (500 ms timeout). On success it marks the bridge alive,
+ calls `led_signal_info()` to clear the degraded indicator, and re-runs
+ `check_c5_protocol()`. On failure it drops back to dead and retries later.
+
+## C5-link LED signalling
+
+The manager reflects bridge health on the status LED via `led_control`:
+
+- `led_signal_error()` on a proto-version mismatch / unavailable (headers out of
+ sync - reflash both).
+- `led_signal_info()` when the link is (re)established, clearing the warning state.
+
+## OTA triggering
+
+`bridge_manager_force_update()` runs `c5_flasher_init()` then
+`c5_flasher_update(NULL, 0, SPI_OTA_TRANSPORT_SPI)` - i.e. the C5 app OTA streamed
+from the SD image over the SPI transport (control always on the bridge). See the
+c5_flasher doc for the OTA flow.
+
+## Safe reads (out_capacity clamp)
+
+Every `spi_bridge_send_command()` call passes an explicit `out_capacity` equal to
+the receiving buffer's `sizeof`, so a slave that announces (or a corrupted length
+that inflates) more bytes than the buffer holds is clamped and cannot overflow it.
diff --git a/docs/c5_flasher/README.md b/docs/c5_flasher/README.md
index dfa9fc968..436d673b6 100644
--- a/docs/c5_flasher/README.md
+++ b/docs/c5_flasher/README.md
@@ -1,21 +1,103 @@
# C5 Flasher Service - P4 Master
-This service allows the ESP32-P4 to update the firmware of the ESP32-C5 using an embedded binary image.
+Lets the ESP32-P4 update or recover the ESP32-C5 firmware. There are two paths:
-## Features
-- **Embedded Binary**: The C5 firmware is embedded directly into the P4 executable during the build process.
-- **Bootloader Control**: Automatically puts the C5 into serial bootloader mode using the BOOT and RESET pins.
-- **Serial Protocol**: Implements the Espressif Serial Protocol (SLIP framing) to write blocks to the C5 flash.
+- **App OTA (primary):** the C5 keeps running its app; the P4 hands it a new
+ image, which it writes to its inactive OTA slot and boots. The control plane
+ (begin / status / per-chunk acks) always rides the SPI bridge; the image bytes
+ travel over the transport the caller selects. The image is normally **streamed
+ from the SD card** (`/sdcard/c5/TentacleOS_C5.bin`), so it is no longer embedded
+ in the P4 binary for the everyday update.
+- **ROM serial-flash (fallback / bricked C5):** the P4 speaks the ROM
+ serial-bootloader protocol over UART itself (via `esp-serial-flasher`) to a C5
+ that is in ROM download mode. Only this path uses the **embedded** C5 images
+ (`c5_rom_flasher.c`, guarded by `C5_ROM_IMAGES_EMBEDDED`).
-## Usage
-1. **Initial Sync**: On boot, the `bridge_manager` checks the C5 version.
-2. **Auto-Update**: If the C5 is unresponsive or outdated, `c5_flasher_update(NULL, 0)` is called.
-3. **Execution**: The P4 stops the SPI bridge, initializes the Flasher UART, pulses the Reset pin with Boot LOW, and starts streaming the binary.
+## App OTA
+
+```c
+esp_err_t c5_flasher_update(const uint8_t *bin_data, uint32_t bin_size, uint8_t transport);
+```
+
+- `bin_data == NULL` -> the image is streamed from `/sdcard/c5/TentacleOS_C5.bin`
+ (`bin_size` is taken from the file). Otherwise the caller's buffer is used.
+- `transport` is a `spi_ota_transport_t` selector:
+ - `SPI_OTA_TRANSPORT_SPI` - image bytes arrive as `SPI_ID_SYSTEM_OTA_DATA`
+ chunks (240 firmware bytes per chunk). No UART wiring needed.
+ - `SPI_OTA_TRANSPORT_UART` - image bytes arrive raw over UART1 -> C5 UART0
+ (needs `c5_flasher_init()`). **Non-functional on the HighBoy V2** (no direct
+ P4<->C5 UART on that board); use SPI there.
+
+Flow (`c5_flasher_update`):
+1. Send `SPI_ID_SYSTEM_OTA_BEGIN` with `spi_ota_begin_t { size, transport }`. The
+ BEGIN ack often coincides with the C5's erase and is lost, so the P4 confirms
+ the C5 actually started by polling `SPI_ID_SYSTEM_OTA_STATUS`
+ (`spi_ota_status_t { state, bytes_written }`) instead of trusting the ack.
+2. Poll `OTA_STATUS` until the C5 reaches `SPI_OTA_STATE_READY` (erase done).
+3. Stream the image in blocks (SPI: 240 B `OTA_DATA` chunks; UART: 4 KB blocks),
+ advancing the live progress counters read by `c5_flasher_progress()`.
+4. The C5 validates, sets the new slot to boot and reboots. A failed transfer is
+ harmless: the old slot still boots.
+
+`spi_ota_state_t` progression: `IDLE -> ERASING -> READY -> RECEIVING -> DONE`
+(or `ERROR`).
+
+## ROM download entry points
+
+For a blank or bricked C5 (no working app / bridge), reflash over the ROM
+bootloader:
+
+```c
+esp_err_t c5_flasher_enter_download(void); // ask the running C5 to reboot into ROM download mode
+esp_err_t c5_flasher_rom_flash(void); // reflash from embedded images
+void c5_passthrough_run(void); // forward host esptool <-> C5 ROM (never returns)
+```
+
+- `c5_flasher_enter_download()` sends `SPI_ID_SYSTEM_ENTER_DOWNLOAD` over the SPI
+ bridge; the C5 acks and reboots into ROM serial-download mode. After this the
+ C5 is no longer running its app (the OTA receiver is gone), so recovery must
+ continue via `c5_flasher_rom_flash()` or passthrough.
+- `c5_flasher_rom_flash()` writes bootloader + partition table + otadata + app -
+ all embedded in the P4 - and MD5-verifies each region. **Precondition:** the C5
+ must already be in ROM download mode (strap it manually, or call
+ `c5_flasher_enter_download()` first). Returns `ESP_ERR_NOT_FOUND` unless
+ `C5_ROM_IMAGES_EMBEDDED` is set.
+- `c5_passthrough_run()` bridges the P4's console UART (host PC USB-serial) to the
+ C5 UART0 so the host can run `esptool` directly against the C5 ROM bootloader.
+ Kills the REPL, disables UART logs and never returns; reboot the P4 (or press
+ BACK) to exit.
+
+## UART helpers
+
+```c
+esp_err_t c5_flasher_init(void); // bring up UART1 (only needed for the UART OTA transport)
+void c5_flasher_release_uart(void); // delete UART1 + tri-state the C5-UART pins for an external programmer
+```
+
+## Bridge / status helpers
+
+```c
+esp_err_t c5_flasher_ping(void); // SPI_ID_SYSTEM_PING
+esp_err_t c5_flasher_info(void); // SPI_ID_SYSTEM_INFO -> chip model / rev / MAC / free heap
+esp_err_t c5_flasher_sync(void); // re-probe the C5 and mark the bridge alive iff it answers a ping
+void c5_flasher_progress(uint32_t *sent, uint32_t *total); // live OTA progress for the UI bar
+```
+
+## Callers
+
+`bridge_manager` triggers the app OTA (`bridge_manager_force_update()` calls
+`c5_flasher_update(NULL, 0, SPI_OTA_TRANSPORT_SPI)`). Version mismatch is
+**detection-only** - the user starts the update explicitly (the `c5` console
+command); the P4 never auto-flashes. See
+[`../bridge_manager/README.md`](../bridge_manager/README.md).
## Symbols
-The embedded binary is accessed via:
-- `_binary_firmware_c5_bin_start`
-- `_binary_firmware_c5_bin_end`
+
+The ROM-flash fallback references the embedded C5 app image via:
+- `_binary_TentacleOS_C5_bin_start`
+- `_binary_TentacleOS_C5_bin_end`
## Build Automation
-Use the `./tools/build_and_flash.sh` script to ensure the C5 binary is updated and embedded correctly before flashing the P4.
+
+Use `./tools/build_and_flash.sh` to keep the C5 binary current (SD image and, for
+the ROM fallback, the embedded copy) before flashing the P4.
diff --git a/docs/console/README.md b/docs/console/README.md
index 25203d31b..4afffafa6 100644
--- a/docs/console/README.md
+++ b/docs/console/README.md
@@ -21,10 +21,14 @@ The prompt `highboy>` indicates the system is ready.
| Command | Description | Usage |
| :--- | :--- | :--- |
| `help` | Lists all available commands. | `help [command]` |
-| `free` | Displays available internal and PSRAM memory. | `free` |
-| `tasks` | Lists running FreeRTOS tasks and stack usage. | `tasks` |
+| `free` | Displays free internal and PSRAM (SPIRAM) memory, current and minimum. | `free` |
+| `tasks` | Lists running FreeRTOS tasks (state, priority, stack high-water, num). | `tasks` |
+| `stack` | Stack budget report: allocated vs high-water free per task, with reclaim/low flags. | `stack` |
+| `date` | Shows the UTC wall-clock (with sync state and source), or sets it. | `date [ ]` |
+| `ip` | Shows current network interfaces (IP, Mask, GW, MAC) for STA and AP. | `ip` |
| `restart` | Reboots the system. | `restart` |
-| `ip` | Shows current network interfaces (IP, Mask, GW, MAC). | `ip` |
+| `firstboot` | Clears the first-boot onboarding + screen-tips flags so they run on the next reset. | `firstboot` |
+| `capprep` | Skips onboarding (wizard + tips) and restarts clean, for screen capture. | `capprep` |
### File System Commands
@@ -55,6 +59,71 @@ The `wifi` command is a wrapper for all wireless functions.
| `portscan`| Scans TCP/UDP ports on target. | `-i `: Target IP
`-min`: Start Port
`-max`: End Port | `wifi portscan -i 192.168.1.1` |
| `status` | Shows active attacks and state. | None | `wifi status` |
+### BadUSB Commands (`badusb`)
+
+HID injection via DuckyScript. Hint: `badusb ...`.
+
+| Subcommand | Description | Arguments | Example |
+| :--- | :--- | :--- | :--- |
+| `run` | Runs a DuckyScript. | `-a `: internal asset (under `bad_usb_scripts/`)
`-f `: script from SD card | `badusb run -a rickroll.txt` |
+| `type` | Types a literal string over HID. | `` | `badusb type hello world` |
+| `layout` | Sets the keyboard layout. | `` | `badusb layout abnt2` |
+| `stop` | Aborts the running script (stops at the next line). | None | `badusb stop` |
+| `status` | Shows whether the USB host has mounted the device. | None | `badusb status` |
+
+### Host Link Commands (`hostlink`)
+
+Companion (BLE) host-link pairing and status.
+
+| Subcommand | Description | Arguments | Example |
+| :--- | :--- | :--- | :--- |
+| `psk` | Shows the pairing PSK to provision the companion app. | None | `hostlink psk` |
+| `regen` | Generates a new PSK (invalidates existing pairings), then prints it. | None | `hostlink regen` |
+| `ble` | Turns companion BLE advertising on/off. | `` | `hostlink ble on` |
+| `status` | Shows the companion session (authenticated) and BLE connection state. | None | `hostlink status` |
+
+### C5 Co-processor Commands (`c5`)
+
+Manages the C5 radio co-processor over the SPI bridge (and UART for recovery).
+
+| Subcommand | Description | Example |
+| :--- | :--- | :--- |
+| `ota ` | Pushes the C5 image; control on SPI, image bytes over SPI (default) or UART. | `c5 ota spi` |
+| `ping` | Pings the running C5 over the SPI bridge. | `c5 ping` |
+| `info` | Reads the C5 chip info over the SPI bridge. | `c5 info` |
+| `sync` | Re-probes and reconnects the bridge (e.g. after a C5 reboot). | `c5 sync` |
+| `download` | Asks the running C5 to enter ROM download mode (over SPI). | `c5 download` |
+| `rom` | Serial-flashes a C5 already in download mode (recovery). | `c5 rom` |
+| `passthrough` | Bridges host `esptool` to the C5 (never returns; reboot/BACK exits). | `c5 passthrough` |
+| `release` | Tri-states the C5 UART lines for an external programmer. | `c5 release` |
+
+### Infrared Commands (`ir`)
+
+RMT-backed IR receive/transmit. The `rx`/`send` channels are brought up on demand and released after each operation; `flash` stays on until turned off.
+
+| Subcommand | Description | Arguments | Example |
+| :--- | :--- | :--- | :--- |
+| `rx` | Waits for one IR frame and decodes it. | `[timeout_ms]` (default 5000) | `ir rx 10000` |
+| `send` | Transmits a code (address/command accept `0x` hex). | ` [repeat]` | `ir send NEC 0x00 0x45` |
+| `flash` | Drives the IR emitter at full power (torch) / off. | `` | `ir flash on` |
+
+### Hardware / Diagnostics
+
+| Command | Description | Usage |
+| :--- | :--- | :--- |
+| `battery` | Shows battery/charger (BQ25896) status: SoC (raw + smoothed), voltage, charge state, VBUS source and limits, faults. | `battery` |
+| `i2cscan` | Scans the I2C bus (`0x08`-`0x77`) and lists responding addresses. | `i2cscan` |
+
+### UI / Screen Commands
+
+Debug/capture helpers registered as top-level commands (from the screen command group). Used for automated screenshotting and UI navigation.
+
+| Command | Description | Usage |
+| :--- | :--- | :--- |
+| `goto` | Switches the UI to a screen by enum id. | `goto ` (1..`SCREEN_COUNT`-1) |
+| `key` | Injects a simulated button press. | `key [-t ]`
idx: 0=UP 1=DOWN 2=LEFT 3=RIGHT 4=OK 5=BACK; hold default 120 ms |
+| `screenshot` | Captures the active screen as base64 RGB565 strips. | `screenshot [id]` (optionally switch to `id` first) |
+
## Developing New Commands
To add a new command to the console, follow these steps:
diff --git a/docs/drv2605l/README.md b/docs/drv2605l/README.md
new file mode 100644
index 000000000..f476de210
--- /dev/null
+++ b/docs/drv2605l/README.md
@@ -0,0 +1,84 @@
+# DRV2605L Haptic Driver
+
+This component is a minimal driver for the Texas Instruments DRV2605L haptic motor driver over I2C. It uses the chip's internal ROM waveform library for named effects and exposes RTP (real-time playback) for variable-intensity feedback. The driver is tuned for an ERM (eccentric rotating mass) actuator.
+
+## Overview
+
+- **Location:** `components/Drivers/drv2605l/` (firmware_p4 only)
+- **Header:** `include/drv2605l.h`
+- **Source:** `drv2605l.c`
+- **Dependencies:** `i2c_init`, `pins` (`pin_def.h`), `driver/gpio`, `freertos`
+- **Interface:** I2C master bus `I2C_NUM_0` (via `i2c_init`, shared), 100 kHz standard-mode
+- **I2C address:** `0x5A` (`DRV2605L_I2C_ADDR`, 7-bit)
+- **Enable line:** per the header, EN is on GPIO37 (the driver itself does not toggle it)
+- **Effect library:** internal ROM library `TS2200 Library A` (`LIB_SEL = 0x01`)
+
+## Configuration Applied at Init
+
+`drv2605l_init()` adds the device to the shared bus, reads the status register (deriving `DEVICE_ID` from bits [7:5]), and programs:
+
+- Mode: internal-trigger (`0x00`)
+- Library select: TS2200 Library A (`0x01`)
+- Rated voltage: `0x90` (ERM), overdrive clamp: `0xCC` (ERM)
+- Feedback control `0x35`, Control1 `0x93`, Control2 `0xF5`, Control3 `0xA0`
+
+Auto-calibration is not run at boot (it is opt-in via `drv2605l_autocal()`) so it never regresses the working manual tune.
+
+## API Reference
+
+### Initialization
+
+#### `drv2605l_init`
+```c
+esp_err_t drv2605l_init(void);
+```
+Initializes the driver as described above. Returns `ESP_OK` on success, otherwise the failing `esp_err_t`. Marks the driver ready; all other calls return `ESP_ERR_INVALID_STATE` until it succeeds.
+
+#### `drv2605l_device_id`
+```c
+uint8_t drv2605l_device_id(void);
+```
+Last-read `DEVICE_ID` register value (0 if not yet probed).
+
+### Playback
+
+#### `drv2605l_play_effect`
+```c
+esp_err_t drv2605l_play_effect(uint8_t effect);
+```
+Plays a single ROM library waveform effect (1..123) once. Leaves RTP mode first if active, writes the effect into waveform-sequence slot 0 with a terminator in slot 1 (skipping the write when the effect is unchanged from the last call), then strobes GO.
+
+#### `drv2605l_stop`
+```c
+esp_err_t drv2605l_stop(void);
+```
+Stops any currently-playing waveform (clears GO).
+
+#### `drv2605l_set_rtp`
+```c
+esp_err_t drv2605l_set_rtp(uint8_t intensity);
+```
+Enters RTP mode and applies an intensity value (useful range 0..127 for forward drive).
+
+### Calibration
+
+#### `drv2605l_autocal`
+```c
+esp_err_t drv2605l_autocal(void);
+```
+Runs ERM auto-calibration (MODE `0x07`) against the actuator, using the rated/overdrive/feedback registers already programmed at init, then returns to internal-trigger mode. Blocks up to ~1.5 s while polling GO. Returns `ESP_OK` if `DIAG_RESULT` passed (status bit 3 clear), else `ESP_FAIL`. Opt-in; not run at boot.
+
+## Key Config / Tunables
+
+| Macro | Location | Value | Meaning |
+|-------|----------|-------|---------|
+| `DRV2605L_I2C_ADDR` | `drv2605l.h` | `0x5A` | I2C 7-bit address |
+| `MODE_INTERNAL_TRIG` | `drv2605l.c` | `0x00` | Internal-trigger playback mode |
+| `MODE_AUTO_CAL` | `drv2605l.c` | `0x07` | Auto-calibration mode |
+| `MODE_RTP` | `drv2605l.c` | `0x05` | Real-time playback mode |
+| `LIB_TS2200_A` | `drv2605l.c` | `0x01` | TS2200 Library A (ERM) |
+| `RATED_V_ERM` | `drv2605l.c` | `0x90` | Rated voltage (ERM) |
+| `OD_CLAMP_ERM` | `drv2605l.c` | `0xCC` | Overdrive clamp (ERM) |
+| `I2C_TIMEOUT_MS` | `drv2605l.c` | 50 | Per-transfer timeout |
+| `AUTOCAL_POLL_INTERVAL_MS` | `drv2605l.c` | 10 | Auto-cal GO poll interval |
+| `AUTOCAL_POLL_MAX` | `drv2605l.c` | 150 | Max auto-cal poll iterations (~1.5 s) |
diff --git a/docs/gameboy/README.md b/docs/gameboy/README.md
new file mode 100644
index 000000000..33337bb9e
--- /dev/null
+++ b/docs/gameboy/README.md
@@ -0,0 +1,166 @@
+# Game Boy Emulator Application
+
+This component is a Game Boy (DMG) emulator built on the single-header
+[Peanut-GB](https://github.com/deltabeard/Peanut-GB) core plus a HighBoy
+platform layer. It runs `.gb` / `.gbc` ROMs from the SD card, takes over the
+ST7789 panel in landscape, plays audio through the I2S codec, and persists
+battery-backed cartridge RAM back to the SD card. It is reached from the games
+menu through a ROM picker screen.
+
+## Overview
+
+- **Emulator location:** `components/Applications/gameboy/`
+- **ROM picker location:** `components/Applications/ui/screens/games/gb_ui.c`
+- **Core:** Peanut-GB (`peanut_gb.h`, single-header) + `minigb_apu` (APU)
+- **Dependencies:** `Drivers`, `Service`, `lvgl`, `esp_lcd`, `esp_timer`, `esp_system`, `driver`
+- **Build flags:** `ENABLE_SOUND=1`, `MINIGB_APU_AUDIO_FORMAT_S16SYS`, compiled `-O2 -w`
+- **Native GB resolution:** 160 x 144 (`LCD_WIDTH` x `LCD_HEIGHT`)
+- **Output resolution:** 320 x 240 (full screen, landscape)
+
+## Architecture
+
+```
+┌──────────────────────────────────────────────────────────┐
+│ ROM Picker (gb_ui.c) │
+│ Scans SD recursively, lists ROMs, launches emulator, │
+│ polls highboy_gb_finished(), restores panel + returns │
+└───────────────────────────┬──────────────────────────────┘
+ │ highboy_gb_start(path)
+ ▼
+┌──────────────────────────────────────────────────────────┐
+│ HighBoy Platform (gb_highboy.c) │
+│ │
+│ ┌────────────┐ ┌──────────────┐ ┌────────────────┐ │
+│ │ gb_main │ │ Peanut-GB │ │ gb_audio │ │
+│ │ task │──▶│ core │ │ task │ │
+│ │ (SYS_CORE_ │ │ (peanut_gb.h)│ │ (SYS_CORE_ │ │
+│ │ UI) │ └──────┬───────┘ │ RADIO) │ │
+│ └─────┬──────┘ │ └───────┬────────┘ │
+│ │ rom/cram callbacks │ │
+│ ▼ ▼ ▼ │
+│ ┌──────────┐ ┌────────────────┐ ┌────────────────┐ │
+│ │ scale + │ │ CRAM save/load │ │ minigb_apu │ │
+│ │ blit │ │ .sav (SD) │ │ 32768 Hz stereo│ │
+│ │ (strips) │ └────────────────┘ │ -> mono I2S │ │
+│ └────┬─────┘ └────────────────┘ │
+└───────┼──────────────────────────────────────────────────┘
+ ▼
+ ST7789 panel (esp_lcd_panel_draw_bitmap, under LVGL lock)
+```
+
+## ROM Picker (`gb_ui.c`)
+
+`ui_gb_open()` builds an LVGL list of the ROMs found on the SD card.
+
+- **Where ROMs come from:** `scan_dir()` walks `/sdcard` recursively to a depth
+ of `MAX_DEPTH` (3), collecting up to `MAX_ROMS` (64) files whose name ends in
+ `.gb` or `.gbc` (case-insensitive). Dot-files are skipped. Path and name
+ tables live in PSRAM (`EXT_RAM_BSS_ATTR`).
+- **Navigation:** a repeating LVGL timer (`NAV_MS` = 80 ms) reads buttons. UP /
+ DOWN move the selection, OK launches the highlighted ROM, BACK / LEFT return
+ to `SCREEN_GAMES_MENU`. With no ROMs found, the screen shows a "copy games
+ anywhere on the SD card" message.
+- **Launch / return handshake:** OK calls `highboy_gb_start(path)` and sets a
+ `launched` flag. The nav timer then polls `highboy_gb_finished()`; once the
+ emulator has torn down it restores the panel orientation
+ (`lcd_set_rotation(lcd_get_rotation())`) and switches back to the games menu -
+ no firmware reboot.
+
+## Emulator Core (`gb_highboy.c`)
+
+### Public API (`gb_highboy.h`)
+
+```c
+void highboy_gb_start(const char *rompath);
+bool highboy_gb_finished(void);
+```
+
+- `highboy_gb_start` resets state and spawns the emulator task. `rompath` is the
+ full SD path of the ROM, or `NULL`/`""` to auto-discover the first ROM.
+- `highboy_gb_finished` returns `true` only once the task has fully torn down and
+ released the panel; it is polled by the ROM picker.
+
+### Tasks
+
+| Task | Stack | Priority | Core |
+|------|-------|----------|------|
+| `gameboy` (main loop) | 32768 | `SYS_PRIO_SERVICE_HI` | `SYS_CORE_UI` |
+| `gb_audio` | 4096 | `SYS_PRIO_SERVICE_HI` | `SYS_CORE_RADIO` |
+
+Both tasks and their buffers are created with `MALLOC_CAP_SPIRAM` caps.
+
+### ROM loading and save RAM
+
+- If no path was supplied, `find_gb_rom()` scans `/sdcard`, `/sdcard/gb`, and
+ `/sdcard/roms` for the first `.gb`/`.gbc` file.
+- The full ROM is read into PSRAM. `gb_init()` wires the `rom_read` / `cram_read`
+ / `cram_write` callbacks.
+- Cartridge RAM size comes from `gb_get_save_size_s()`. The save file path is the
+ ROM path with its extension replaced by `.sav`, next to the ROM on the SD card.
+ CRAM is loaded on start (size must match or it is ignored) and written back
+ whenever it has been dirty for `GB_AUTOSAVE_MS` (3 s), plus once more on exit.
+
+### Display integration
+
+- The GB frame is written by the core into an 8-bit shade buffer
+ (160 x 144, PSRAM). The DMG palette is four classic greens
+ (`0xE0F8D0`, `0x88C070`, `0x346856`, `0x081820`) converted to big-endian
+ RGB565 (`to565be`).
+- Scaling to 320 x 240 is nearest-neighbor via precomputed `s_sx` / `s_sy`
+ lookup tables (no interpolation).
+- Output is pushed in 24-row strips (`GB_STRIP_ROWS`) through
+ `esp_lcd_panel_draw_bitmap()`, each strip followed by `lvgl_glue_wait_flush()`.
+- The emulator takes exclusive control of the panel: it holds the LVGL lock
+ (`lvgl_glue_lock(-1)`), enters direct-draw mode (`lvgl_glue_direct_begin()`),
+ and rotates to landscape with `esp_lcd_panel_swap_xy(true)` +
+ `esp_lcd_panel_mirror(true, false)`. The strip DMA buffer is the only buffer in
+ internal DMA RAM (`MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL`).
+- `ui_render_beat_kick()` is called every loop iteration so `sys_monitor` sees
+ the render path as alive while the standard LVGL render loop is suspended.
+
+### Audio integration
+
+- `minigb_apu` renders at `AUDIO_SAMPLE_RATE` (32768 Hz). The `gb_audio` task
+ pulls stereo interleaved samples, downmixes to mono, and writes them to the
+ I2S stream (`audio_i2s_stream_*`). Sample buffers are allocated in PSRAM.
+- If the APU or its buffers cannot be allocated, the emulator runs silently.
+
+### Frame pacing
+
+The loop targets 60 fps (`GB_FRAME_US`). When it falls behind it sets the core's
+`frame_skip` and skips the blit; a blit happens at most once every two frames
+(so the panel refresh is capped near 30 fps). If it drifts more than
+`GB_RESYNC_US` (250 ms) behind, the frame clock is resynced.
+
+### Controls / input mapping
+
+Input is polled from the GPIO buttons each frame. Because the panel is rotated
+to landscape, the D-pad is remapped accordingly:
+
+| Physical button | Game Boy input |
+|-----------------|----------------|
+| RIGHT | D-pad UP |
+| LEFT | D-pad DOWN |
+| UP | D-pad LEFT |
+| DOWN | D-pad RIGHT |
+| OK | A |
+| BACK | B |
+| OK + BACK | START |
+| hold BACK ~1.2 s | exit emulator |
+
+### Teardown
+
+On exit the task saves CRAM, stops the audio task and I2S stream, drains DMA,
+frees all buffers, leaves direct-draw mode, sets the finished flag, releases the
+LVGL lock, and deletes itself. If the core buffers cannot be allocated at start,
+the task calls `esp_restart()` rather than continuing.
+
+## Performance / PSRAM notes
+
+- ROM image, the `gb_s` state struct, the shade framebuffer, the APU context, and
+ cartridge RAM all live in PSRAM (`MALLOC_CAP_SPIRAM`); only the 320 x 24 strip
+ buffer is in internal DMA RAM.
+- The core is performance-critical, so the whole component is built at `-O2` with
+ warnings silenced (`-w`).
+- The main task uses a large 32 KB stack; the emulator pins to the UI core and
+ the audio task to the radio core (see `sys_prio.h`).
diff --git a/docs/i2c_init/README.md b/docs/i2c_init/README.md
new file mode 100644
index 000000000..04957ed50
--- /dev/null
+++ b/docs/i2c_init/README.md
@@ -0,0 +1,107 @@
+# I2C Master Bus Init
+
+This component brings up the shared I2C master bus used by every on-board I2C
+device (charger, haptic, LED, etc.). The two firmwares are at different stages of
+the ESP-IDF I2C driver migration: P4 uses the new `driver/i2c_master.h` bus-object
+API and adds stuck-bus recovery (at init and reactively at runtime), while C5 still
+uses the legacy `driver/i2c.h` driver with a simpler init and no recovery.
+
+# P4
+
+Shared I2C master bus on the new `i2c_master.h` driver, with stuck-bus recovery.
+
+## Overview
+
+- **Location:** `components/Drivers/i2c_init/`
+- **Header:** `include/i2c_init.h`
+- **Driver:** `driver/i2c_master.h` (new bus-object API)
+- **Port:** `I2C_NUM_0`
+- **Pins:** SDA `GPIO_I2C_SDA_PIN` (GPIO 31), SCL `GPIO_I2C_SCL_PIN` (GPIO 30), from `pin_def.h`
+- **Speed:** `I2C_MASTER_FREQ_HZ` = 100 kHz (standard mode)
+- **Dependencies:** `pins`, `driver/gpio`, `esp_rom_sys`
+
+## Bus / Hardware Notes
+
+The bus runs at 100 kHz standard mode because the V2 board pulls SDA/SCL up with
+10k (R12/R13/R14 to 3.3VF) plus 22R series (R9/R10): too weak for 400 kHz
+fast-mode (rise time can't reach VIH in a bit period), which is why the BQ25896
+NACKs at 400 kHz. Bus config uses `I2C_CLK_SRC_DEFAULT`, `glitch_ignore_cnt = 7`,
+and internal pull-ups enabled.
+
+## Recovery Mechanisms
+
+**Stuck-bus recovery at init.** Before the I2C driver claims the pins, `init_i2c()`
+runs `bus_recover()`: it drives SCL as open-drain and reads SDA. If a slave is
+holding SDA low (stuck mid-transfer after a partial reset or power glitch), it
+bit-bangs up to `I2C_RECOVER_CLOCKS` (9) SCL pulses at ~100 kHz
+(`I2C_RECOVER_HALF_US` = 5 us half period) to walk the slave past its ACK, then
+issues a STOP (SDA rising while SCL is high). It is a no-op when SDA is already
+released. This keeps a stuck bus from leaving the charger/haptic/LED unreachable
+for the whole session.
+
+**Reactive recovery at runtime.** Consumers that see repeated transfer failures
+call `i2c_bus_recover()`, which calls `i2c_master_bus_reset()` on the shared bus
+and increments the recovery counter. `i2c_recover_count()` exposes that counter
+for `sys_monitor` health reporting.
+
+## API Reference
+
+#### `init_i2c`
+```c
+esp_err_t init_i2c(void);
+```
+Runs the init-time stuck-bus recovery sequence, then creates the I2C master bus on
+`I2C_NUM_0`. Returns `ESP_OK`, or the failing `esp_err_t`.
+
+#### `i2c_get_bus`
+```c
+i2c_master_bus_handle_t i2c_get_bus(void);
+```
+Returns the shared master bus handle; each device driver adds itself to it via
+`i2c_master_bus_add_device()`.
+
+#### `i2c_bus_recover`
+```c
+esp_err_t i2c_bus_recover(void);
+```
+Resets a wedged bus at runtime (reactive recovery) and increments the recovery
+counter. Returns `ESP_ERR_INVALID_STATE` if the bus is not initialized, otherwise
+the result of `i2c_master_bus_reset()`.
+
+#### `i2c_recover_count`
+```c
+uint32_t i2c_recover_count(void);
+```
+Number of runtime bus recoveries performed since boot, for `sys_monitor` health.
+
+---
+
+# C5
+
+Shared I2C master bus on the legacy `driver/i2c.h` driver. No bus recovery and no
+recovery counter; the bus handle is not exposed.
+
+## Overview
+
+- **Location:** `components/Drivers/i2c_init/`
+- **Header:** `include/i2c_init.h`
+- **Driver:** `driver/i2c.h` (legacy driver)
+- **Port:** `I2C_NUM_0`
+- **Pins:** SDA `GPIO_I2C_SDA_PIN` (GPIO 8), SCL `GPIO_I2C_SCL_PIN` (GPIO 9), from `pin_def.h`
+- **Speed:** `I2C_MASTER_FREQ_HZ` = 400 kHz (fast mode)
+- **Dependencies:** `pins`, `driver/i2c`
+
+## Configuration
+
+Configured as `I2C_MODE_MASTER` with SDA/SCL internal pull-ups enabled and a
+400 kHz clock. Init calls `i2c_param_config()` then `i2c_driver_install()` with no
+RX/TX buffers.
+
+## API Reference
+
+#### `init_i2c`
+```c
+void init_i2c(void);
+```
+Configures and installs the legacy I2C master driver on `I2C_NUM_0`. Logs and
+returns early on failure; no value is returned.
diff --git a/docs/ir/README.md b/docs/ir/README.md
new file mode 100644
index 000000000..fcf3dfea7
--- /dev/null
+++ b/docs/ir/README.md
@@ -0,0 +1,223 @@
+# IR TX/RX Service
+
+This component provides the infrared transmit/receive service for TentacleOS on the ESP32-P4. It drives the IR emitter and detector through the RMT peripheral, ships a full protocol decode/encode library (consumer remotes and air-conditioner state frames), a Flipper-Zero-compatible file format, a console command, and a full-power torch (flashlight) mode.
+
+## Overview
+
+- **Location:** `firmware_p4/components/Service/ir/`
+- **Target:** `firmware_p4` only (ESP32-P4)
+- **Main header:** `include/ir.h`
+- **Companion headers:** `include/ir_protocol.h` (remote protocols), `include/ir_ac.h` (air-conditioner protocols), `include/ir_file.h` (Flipper IR file format)
+- **Dependencies:** `pin_def`, `led_control`, `driver/rmt_tx`, `driver/rmt_rx`, `driver/rmt_encoder`, `driver/gpio`, `freertos`
+- **Peripheral:** RMT (one RX channel + one TX channel), non-DMA
+- **Console command:** `ir` (registered in `Service/console/commands/cmd_ir.c`)
+
+The service is brought up lazily - there is no boot-time init. Callers init the RX or TX channel on demand and release it when done, so RMT channels are not held for the whole session.
+
+## Hardware / Bus
+
+| Item | Value | Source |
+|------|-------|--------|
+| TX pin | `GPIO_IR_TX_PIN` = GPIO 0 | `pin_def.h` |
+| RX pin | `GPIO_IR_RX_PIN` = GPIO 1 | `pin_def.h` |
+| RMT resolution | 1 MHz (1 tick = 1 us) | `IR_RMT_RESOLUTION_HZ` |
+| TX carrier duty | 0.33 | `IR_CARRIER_DUTY_CYCLE` |
+| Default carrier | 38 kHz (protocol-dependent) | `ir_carrier_freq()` |
+
+The emitter is infrared and invisible to the eye: verify output with a phone camera. The TX pin rests low via an external pull-down (R90).
+
+### RMT tuning constants (`ir.h`)
+
+| Constant | Value | Meaning |
+|----------|-------|---------|
+| `IR_RMT_MEM_SYMBOLS` | 128 | Encode buffer size for a single frame |
+| `IR_MAX_SYMBOLS` | 512 | User RX/last-frame buffer depth |
+| `IR_RX_MEM_BLOCK_SYMBOLS` | 96 | Non-DMA RX FIFO depth (2x the P4 48-word block) |
+| `IR_TX_MEM_BLOCK_SYMBOLS` | 64 | TX channel FIFO (2 P4 channels) |
+| `IR_RX_MIN_NS` / `IR_RX_MAX_NS` | 1250 / 12000000 | RX glitch/timeout window |
+| `IR_TX_QUEUE_DEPTH` | 4 | TX transaction queue depth |
+| `IR_TX_WAIT_MS` | 1000 | Blocking wait for a transmit to finish |
+| `IR_PRINT_MAX_SYMBOLS` | 40 | Cap on symbols logged by `ir_print_raw` |
+
+RX and TX are non-DMA on this board: DMA-backed `rmt_receive()` failed here, so the RX channel uses a ping-ponged FIFO into the `IR_MAX_SYMBOLS` user buffer.
+
+## API Reference
+
+### Channel lifecycle
+
+#### `ir_rx_init` / `ir_rx_deinit`
+```c
+esp_err_t ir_rx_init(void);
+void ir_rx_deinit(void);
+```
+`ir_rx_init` creates the RMT RX channel on `GPIO_IR_RX_PIN`, its event queue and callback, enables it, and arms a one-shot receive. Returns `ESP_OK` (idempotent if already inited), `ESP_ERR_NO_MEM` on mutex/queue failure, or an RMT error. `ir_rx_deinit` disables and deletes the channel and queue (no-op if RX was never inited).
+
+#### `ir_rx_prime`
+```c
+void ir_rx_prime(void);
+```
+Resets the RX queue (drops a stale/ambient frame) and re-arms the one-shot receive if a prior frame left it idle. Call at the start of every capture: a one-shot `rmt_receive()` is consumed by every frame, including NEC repeat frames, so without priming the next `ir_receive()` could wait forever.
+
+#### `ir_tx_init` / `ir_tx_deinit`
+```c
+esp_err_t ir_tx_init(void);
+void ir_tx_deinit(void);
+```
+`ir_tx_init` creates the RMT TX channel on `GPIO_IR_TX_PIN`, a copy encoder, and enables the channel. Returns `ESP_OK` (idempotent) or an RMT error. `ir_tx_deinit` disables and deletes the channel and encoder and resets the cached carrier (no-op if TX was never inited).
+
+### Receive / decode
+
+#### `ir_receive`
+```c
+esp_err_t ir_receive(ir_data_t *out_data, uint32_t timeout_ms);
+```
+Waits up to `timeout_ms` for a frame, snapshots the raw symbols (for `ir_get_last_raw`), decodes with `ir_decode`, and re-arms the channel. Signals the RGB LED (info on decode, warning on timeout/undecoded). Returns `ESP_OK` on decode, `ESP_ERR_TIMEOUT` if nothing arrived, `ESP_ERR_NOT_FOUND` if a frame arrived but did not decode, or an RMT error.
+
+#### `ir_get_last_raw`
+```c
+esp_err_t ir_get_last_raw(rmt_symbol_word_t *out_buf, size_t buf_max, size_t *out_count);
+```
+Copies the raw RMT symbols from the most recent received frame (mutex-guarded). `out_count` may be NULL. Returns `ESP_ERR_INVALID_ARG` on a NULL/zero buffer, `ESP_ERR_TIMEOUT` if the mutex could not be taken.
+
+### Transmit / encode
+
+#### `ir_send`
+```c
+esp_err_t ir_send(const ir_data_t *data);
+```
+Encodes `data` with `ir_encode` and transmits it at the protocol's carrier frequency. Requires `ir_tx_init`. Returns `ESP_ERR_INVALID_ARG` if encoding produced no symbols, else the transmit result.
+
+#### `ir_send_raw`
+```c
+esp_err_t ir_send_raw(const rmt_symbol_word_t *symbols, size_t count, uint32_t carrier_hz);
+```
+Transmits a raw symbol array. Pass `carrier_hz = 0` to disable the carrier. Returns `ESP_ERR_INVALID_ARG` on a NULL/empty array.
+
+### Torch / flashlight
+
+#### `ir_flash_on` / `ir_flash_off`
+```c
+esp_err_t ir_flash_on(void);
+esp_err_t ir_flash_off(void);
+```
+`ir_flash_on` drives the emitter fully on as DC with no carrier: it releases the RMT TX channel (`ir_tx_deinit`) so it does not fight for the pin, reconfigures `GPIO_IR_TX_PIN` as a plain output, and holds it high at full power. This is continuous full-power drive - use it for tests or short bursts, do not leave it on long. `ir_flash_off` drives the pin low again (a later `ir_tx_init` re-routes it back to RMT). Both return `ESP_OK` or a GPIO driver error.
+
+### Logging helpers
+
+```c
+void ir_print_raw(const rmt_symbol_word_t *symbols, size_t count); // DEBUG level, capped at IR_PRINT_MAX_SYMBOLS
+void ir_print_data(const ir_data_t *data); // INFO level: protocol, address, command, repeat
+```
+
+## Decoded Frame (`ir_data_t`)
+
+```c
+typedef struct {
+ ir_protocol_t protocol;
+ uint32_t address;
+ uint32_t command;
+ bool repeat;
+} ir_data_t;
+```
+
+## Protocol Library (`ir_protocol.h`)
+
+The library decodes and encodes consumer-remote protocols. `ir_decode` tries all known protocols against a symbol buffer; `ir_encode` produces symbols for a given `ir_data_t`. Pulse matching uses `IR_TOLERANCE` (25%) with a strict `IR_TOLERANCE_STRICT` (6%) for protocols whose preambles are close (Pioneer vs NEC).
+
+| Protocol (`ir_protocol_t`) | Source | Carrier |
+|----------------------------|--------|---------|
+| `IR_PROTO_NEC` | `ir_protocol_nec.c` | 38 kHz |
+| `IR_PROTO_NEC42` | `ir_protocol_nec42.c` | 38 kHz |
+| `IR_PROTO_SAMSUNG` | `ir_protocol_samsung.c` | 38 kHz |
+| `IR_PROTO_SONY` | `ir_protocol_sony.c` | 40 kHz |
+| `IR_PROTO_RC5` | `ir_protocol_rc5.c` | 36 kHz |
+| `IR_PROTO_RC6` | `ir_protocol_rc6.c` | 36 kHz |
+| `IR_PROTO_RCA` | `ir_protocol_rca.c` | 38 kHz |
+| `IR_PROTO_JVC` | `ir_protocol_jvc.c` | 38 kHz |
+| `IR_PROTO_LG` | `ir_protocol_lg.c` | 38 kHz |
+| `IR_PROTO_DENON` | `ir_protocol_denon.c` | 38 kHz |
+| `IR_PROTO_PANASONIC` | `ir_protocol_panasonic.c` | 37 kHz |
+| `IR_PROTO_PIONEER` | `ir_protocol_pioneer.c` | 40 kHz |
+
+Carrier constants: `IR_CARRIER_HZ_DEFAULT` (38 kHz - NEC, Samsung, LG, JVC, Denon and others), `IR_CARRIER_HZ_RC5_RC6` (36 kHz), `IR_CARRIER_HZ_SONY` (40 kHz), `IR_CARRIER_HZ_PANASONIC` (37 kHz), `IR_CARRIER_HZ_PIONEER` (40 kHz). Use `ir_carrier_freq(proto)` to resolve a protocol's carrier and `ir_protocol_name(proto)` for its display name.
+
+### Low-level codec building blocks
+
+The pulse-distance and pulse-width primitives that the per-protocol files build on are also public:
+
+```c
+uint64_t ir_decode_pulse_distance(const rmt_symbol_word_t *symbols, size_t offset,
+ size_t num_bits, const ir_pulse_distance_cfg_t *cfg);
+uint64_t ir_decode_pulse_width(const rmt_symbol_word_t *symbols, size_t offset,
+ size_t num_bits, const ir_pulse_width_cfg_t *cfg);
+size_t ir_encode_pulse_distance(rmt_symbol_word_t *symbols, uint64_t data,
+ size_t num_bits, const ir_encode_distance_cfg_t *cfg);
+size_t ir_encode_pulse_width(rmt_symbol_word_t *symbols, uint64_t data,
+ size_t num_bits, const ir_encode_width_cfg_t *cfg);
+bool ir_match(uint32_t measured_us, uint32_t expected_us);
+bool ir_match_tol(uint32_t measured_us, uint32_t expected_us, uint32_t tol_percent);
+```
+
+## Air-Conditioner Library (`ir_ac.h`)
+
+Unlike remote protocols, each AC frame carries the full appliance state (power, mode, temperature, fan) rather than a single command. The full state is captured in `ir_ac_state_t`:
+
+```c
+typedef struct {
+ ir_ac_protocol_t protocol;
+ bool power;
+ ir_ac_mode_t mode; // AUTO, COOL, DRY, HEAT, FAN
+ uint8_t temp_c; // clamped to the target protocol's range at encode time
+ ir_ac_fan_t fan; // AUTO, LOW, MED, HIGH
+} ir_ac_state_t;
+```
+
+| AC protocol (`ir_ac_protocol_t`) | Source |
+|----------------------------------|--------|
+| `IR_AC_PROTO_COOLIX` | `ir_ac_coolix.c` |
+| `IR_AC_PROTO_GREE` | `ir_ac_gree.c` |
+| `IR_AC_PROTO_LG` | `ir_ac_lg.c` |
+| `IR_AC_PROTO_MIDEA` | `ir_ac_midea.c` |
+| `IR_AC_PROTO_TOSHIBA` | `ir_ac_toshiba.c` |
+| `IR_AC_PROTO_HAIER` | `ir_ac_haier.c` |
+
+```c
+size_t ir_ac_encode(const ir_ac_state_t *state, rmt_symbol_word_t *symbols, size_t max);
+esp_err_t ir_ac_send(const ir_ac_state_t *state); // requires ir_tx_init()
+bool ir_ac_decode(const rmt_symbol_word_t *symbols, size_t count, ir_ac_state_t *out_state);
+const char *ir_ac_protocol_name(ir_ac_protocol_t proto);
+const char *ir_ac_mode_name(ir_ac_mode_t mode);
+const char *ir_ac_fan_name(ir_ac_fan_t fan);
+uint32_t ir_ac_carrier_freq(ir_ac_protocol_t proto);
+```
+
+## Flipper IR File Format (`ir_file.h`)
+
+Parses and serializes Flipper Zero `.ir` files. A file (`ir_file_t`) holds a grown-on-demand array of `ir_signal_t`, each either a decoded `ir_data_t` or a raw symbol/timing sequence.
+
+```c
+void ir_file_init(ir_file_t *file);
+void ir_file_free(ir_file_t *file);
+esp_err_t ir_file_parse(const char *content, ir_file_t *file); // append parsed signals
+size_t ir_file_to_string(const ir_file_t *file, char *buf, size_t buf_size);
+ir_signal_t *ir_file_find(const ir_file_t *file, const char *name);
+esp_err_t ir_file_send(const ir_signal_t *signal); // dispatches ir_send_raw / ir_send
+esp_err_t ir_file_add_parsed(ir_file_t *file, const char *name, const ir_data_t *data);
+esp_err_t ir_file_add_raw(ir_file_t *file, const ir_file_add_raw_cfg_t *cfg);
+```
+
+Buffer ownership: `ir_file_parse` and `ir_file_add_raw` allocate the raw buffers, and `ir_file_free` releases everything. Limits: `IR_FILE_NAME_MAX` (32), `IR_FILE_PROTO_NAME_MAX` (32), `IR_FILE_LINE_BUF_SIZE` (1024), `IR_FILE_INITIAL_CAP` (8).
+
+## Console Command
+
+Registered by `register_ir_commands()` in `Service/console/commands/cmd_ir.c`. The `rx` and `send` subcommands are one-shot: they init the RMT channel on demand and release it when done. `flash` is the exception - it stays on until `flash off`.
+
+```
+ir rx [timeout_ms] wait for one frame and decode it (default 5000 ms)
+ir send [repeat] transmit a code (addr/cmd accept 0x hex)
+ir flash drive the emitter at full power (torch) / off
+```
+
+- `ir rx` calls `ir_rx_init` + `ir_rx_prime`, waits for a frame, prints protocol/address/command (and `[repeat]`), then `ir_rx_deinit`. Reports timeout and undecoded-frame cases distinctly.
+- `ir send` resolves the protocol name (case-insensitive), parses `addr`/`cmd` (0x hex accepted), inits TX, sends, then deinits TX.
+- `ir flash on` drives the emitter to full power (`ir_flash_on`); `ir flash off` turns it off. The output is infrared - check with a phone camera.
diff --git a/docs/led/README.md b/docs/led/README.md
new file mode 100644
index 000000000..a7c061e8a
--- /dev/null
+++ b/docs/led/README.md
@@ -0,0 +1,158 @@
+# LED Status Driver
+
+This component drives the single RGB status LED. The two firmwares use different
+hardware, so the driver differs between them: on P4 the LED is a Texas Instruments
+**LP5816** I2C current-sink driver, while on C5 it is an addressable RMT LED strip
+(single pixel). The P4 build additionally exposes a semantic signal API
+(`led_signal_info/warning/error`) with a dedicated blink-off task; the C5 build
+only exposes the fixed-color blink helpers.
+
+# P4
+
+RGB status LED backed by the LP5816 4-channel I2C current-sink driver.
+
+## Overview
+
+- **Location:** `components/Drivers/led/`
+- **Header:** `include/led_control.h`
+- **Chip:** Texas Instruments LP5816 (U22), 4-channel I2C current-sink LED driver
+- **Interface:** I2C (shared bus via the `i2c_init` component, `i2c_get_bus()`)
+- **I2C address:** `0x2C` (7-bit)
+- **I2C speed:** `I2C_MASTER_FREQ_HZ` (100 kHz, from `i2c_init.h`)
+- **I2C timeout:** 50 ms per transfer
+- **Dependencies:** `i2c_init`, `sys_prio`, `esp_timer`, `freertos`
+
+## Hardware
+
+The LEDs are common-anode (anode to 3.3V); the LP5816 sinks each cathode. Channel
+map (schematic sheet 6):
+
+| Channel | Color | LED |
+| :--- | :--- | :--- |
+| OUT0 | Red | D10.1 |
+| OUT1 | Green | D10.2 |
+| OUT2 | Blue | D10.3 |
+| OUT3 | (unused) | D11 |
+
+Only OUT0..OUT2 (RGB) are enabled. Channels run in manual 8-bit PWM mode with the
+dot-current ceiling set to full scale (25.5 mA MAX_CURRENT) for saturated color;
+fade and exponential dimming are disabled so PWM updates take effect immediately.
+
+## Configuration / Tunables
+
+- `LP5816_ADDR` = `0x2C`: I2C target address.
+- `I2C_TIMEOUT_MS` = `50`: per-transfer timeout.
+- `OUT_ENABLE_RGB` = `0x07`: enables OUT0..OUT2.
+- `LED_DC_LEVEL` = `0xFF`: per-channel dot-current ceiling (full scale).
+- `SIGNAL_BLINK_US` = `250000`: semantic-signal flash duration (250 ms).
+- Signal defaults: info `0xFF00FF`, warning `0xFFFF00`, error `0xFF0000`,
+ brightness `10`. These are overridden via `led_set_signal_config()`.
+
+## Blink-off Task
+
+Turning the LED off is a blocking I2C write, so it does not run in an
+`esp_timer` callback (that task has a ~3.5 KB stack the I2C path overflows, and
+blocking there stalls other timers). Instead, the first semantic signal lazily
+creates a dedicated task `led_sig` (3072-byte stack, `SYS_PRIO_BACKGROUND`,
+pinned to `SYS_CORE_RADIO`). Each signal sets the color and an off-deadline, then
+notifies the task; the task sleeps until the (possibly re-armed) deadline and then
+clears the LED once. The flash itself is therefore non-blocking to the caller.
+
+## API Reference
+
+### Initialization
+
+#### `led_rgb_init`
+```c
+esp_err_t led_rgb_init(void);
+```
+Assumes the shared I2C bus is already up. Adds the LP5816 to the bus, resets it,
+enables the RGB channels in manual PWM mode, and starts with the LED off. Returns
+`ESP_OK`, or an `esp_err_t` if the LP5816 does not respond on I2C.
+
+### Direct Color Control
+
+```c
+void led_set_color(uint8_t r, uint8_t g, uint8_t b);
+void led_clear(void);
+void led_blink(uint8_t r, uint8_t g, uint8_t b, int duration_ms);
+```
+- `led_set_color`: sets the RGB PWM duty (0-255 per channel).
+- `led_clear`: turns the LED off (all channels to 0).
+- `led_blink`: sets the color, blocks for `duration_ms`, then clears.
+
+### Fixed-color Blink Helpers
+
+```c
+void led_blink_red(void); // 255,0,0 for 500 ms (error)
+void led_blink_green(void); // 0,150,0 for 220 ms (success)
+void led_blink_blue(void); // 0,0,255 for 500 ms (info)
+void led_blink_purple(void); // 200,0,220 for 500 ms (info)
+```
+Each blocks for its duration.
+
+### Semantic Signal API
+
+```c
+void led_set_signal_config(uint32_t info, uint32_t warning, uint32_t error, int brightness);
+void led_signal_info(void);
+void led_signal_warning(void);
+void led_signal_error(void);
+```
+- `led_set_signal_config`: pushes the signal colors (0xRRGGBB) and global
+ brightness (0-100) in from the Service layer, so the driver never depends on the
+ config module. The brightness scales each channel before it is written.
+- `led_signal_info` / `led_signal_warning` / `led_signal_error`: flash the status
+ LED once in the configured color, then turn it off. Non-blocking; the off runs on
+ the dedicated `led_sig` task described above.
+
+---
+
+# C5
+
+RGB status LED backed by an addressable RMT LED strip (single pixel), driven
+through the `led_strip` component. This is not the LP5816 used on P4, and there is
+no I2C, signal API, or dedicated off task.
+
+## Overview
+
+- **Location:** `components/Drivers/led/`
+- **Header:** `include/led_control.h`
+- **Interface:** RMT (single-pixel addressable LED via the `led_strip` component)
+- **Data pin:** `GPIO_LED_RGB_PIN` (GPIO 27, from `pin_def.h`)
+- **Dependencies:** `led_strip`, `pin_def`, `freertos`
+
+## Configuration / Tunables
+
+- `RMT_RESOLUTION_HZ` = `10 MHz`: RMT channel resolution.
+- Strip: `max_leds = 1`, `RMT_CLK_SRC_DEFAULT`, DMA disabled.
+- Blink colors/durations:
+
+| Helper | RGB | Duration |
+| :--- | :--- | :--- |
+| `led_blink_red` | 255,0,0 | 500 ms |
+| `led_blink_green` | 0,150,0 | 220 ms |
+| `led_blink_blue` | 0,0,255 | 500 ms |
+| `led_blink_purple` | 200,0,220 | 500 ms |
+
+## API Reference
+
+### Initialization
+
+#### `led_rgb_init`
+```c
+void led_rgb_init(void);
+```
+Creates the single-pixel RMT LED strip device on `GPIO_LED_RGB_PIN` and clears it.
+Uses `ESP_ERROR_CHECK`, so a failure aborts.
+
+### Fixed-color Blink Helpers
+
+```c
+void led_blink_red(void);
+void led_blink_green(void);
+void led_blink_blue(void);
+void led_blink_purple(void);
+```
+Each sets the pixel to its color, refreshes, blocks for the color's duration, then
+clears and refreshes. No-op if the strip is not initialized.
diff --git a/docs/lvgl/README.md b/docs/lvgl/README.md
index b5c7a68a3..004213a65 100644
--- a/docs/lvgl/README.md
+++ b/docs/lvgl/README.md
@@ -53,8 +53,15 @@ esp_err_t lvgl_glue_init(void);
are not ready.
2. **`lvgl_port_init`:** starts the managed LVGL task (calls `lv_init`
internally), the periodic tick, and a recursive mutex (the lock).
-3. **`lvgl_port_add_disp`:** registers the display - DMA double buffering (20
- lines), RGB565 with byte swap, internal RAM (no PSRAM).
+3. **`lvgl_port_add_disp`:** registers the display - a partial DMA double buffer
+ of `LVGL_BUF_LINES` (`LCD_PANEL_H / 4` = 80) lines in internal DMA-capable
+ RAM, RGB565 with byte swap.
+4. **PSRAM draw buffers:** overrides LVGL's draw and image buffer handlers with
+ `draw_buf_psram_malloc`, which routes allocations larger than
+ `DRAWBUF_PSRAM_THRESHOLD` (48 KB) to PSRAM (`MALLOC_CAP_SPIRAM`) and falls
+ back to the default heap otherwise. This keeps big off-screen / image buffers
+ off the scarce internal heap. The DMA render double buffer above stays in
+ internal DMA RAM (PSRAM is not DMA-reachable by the LCD).
### Thread safety
```c
@@ -81,6 +88,35 @@ wrap it.
> run inside the LVGL task already under the lock. See
> [ui: long-running work](../ui/README.md#long-running-work-and-the-watchdog).
+### Direct draw (panel takeover)
+```c
+void lvgl_glue_direct_begin(void);
+void lvgl_glue_direct_end(void);
+void lvgl_glue_wait_flush(uint32_t timeout_ms);
+```
+For a full-screen app (e.g. the Game Boy emulator) that holds the LVGL lock and
+drives the ST7789 itself with raw `esp_lcd_panel_draw_bitmap()` calls. The panel
+SPI is async - `draw_bitmap` queues the transfer and returns - so reusing one
+blit buffer for the next strip while the previous DMA is still reading it
+corrupts the image. Between `_begin()` and `_end()` the shared
+color-transfer-done ISR raises a semaphore instead of signalling LVGL flush-ready;
+the app calls `lvgl_glue_wait_flush()` after each draw to block until that
+strip's DMA finished, making single-buffer reuse safe. Call `_begin()` right
+after taking the lock and `_end()` before releasing it; outside this window the
+ISR drives LVGL exactly as before.
+
+### Screenshot capture
+```c
+typedef void (*lvgl_glue_strip_cb_t)(
+ int32_t x1, int32_t y1, int32_t x2, int32_t y2, const uint8_t *data, int32_t stride);
+void lvgl_glue_capture_begin(lvgl_glue_strip_cb_t cb);
+void lvgl_glue_capture_end(void);
+```
+Tees each rendered strip to `cb` from the LVGL flush path, handing over the
+strip's area plus the active buffer in native RGB565 (pre byte-swap).
+`_capture_begin(cb)` starts the tee (pass `NULL` to disable); `_capture_end()`
+stops it.
+
### Rotation
```c
bool lvgl_glue_toggle_rotation(void); // returns true if now landscape
diff --git a/docs/nfc/README.md b/docs/nfc/README.md
new file mode 100644
index 000000000..2f620115d
--- /dev/null
+++ b/docs/nfc/README.md
@@ -0,0 +1,209 @@
+# NFC Application
+
+The NFC feature has two halves in the tree:
+
+1. A complete ST25R3916-based NFC stack under
+ `components/Applications/nfc/` (manager, scanner, reader, listener, device /
+ store persistence, and a full protocol tree), talking to the
+ `st25r3916` driver (`components/Drivers/st25r3916/`, "HighBoy NFC").
+2. An on-device UI suite under `components/Applications/ui/screens/nfc/`.
+
+**Important:** the shipping UI currently runs as a **simulation**. As documented
+in `nfc_sim.h`, the ST25R3916 shares the SPI3 bus whose MISO line is tied to
+LCD-RST by a board jumper and cannot be read reliably, so the NFC screens use a
+faithful simulation model (`nfc_sim`) instead of driving the radio. The protocol
+stack and driver are compiled and complete, but the current screens do not call
+them (the only wiring is a defensive `nfc_manager_stop` close-hook registered in
+`ui_manager.c`).
+
+## Overview
+
+- **App location:** `components/Applications/nfc/`
+- **UI location:** `components/Applications/ui/screens/nfc/`
+- **Driver:** `components/Drivers/st25r3916/` (`highboy_nfc_*`)
+- **Target IC:** ST25R3916 / ST25R3916B (SPI mode 1, clock <= 6 MHz)
+- **Simulation model:** `ui/screens/nfc/nfc_sim.c` (saved library persisted in NVS)
+
+## Architecture
+
+```
+┌──────────────────────────────────────────────────────────┐
+│ UI suite (ui/screens/nfc/*) │
+│ Menu + Read / Identify / Emulate / Write / Saved / │
+│ Config / per-family detail screens │
+│ event-driven input, rotation-aware layout │
+│ │ │
+│ ▼ (currently) nfc_sim model + NVS │
+│ ┌───────────────────────────┐ │
+│ │ nfc_sim: card model + │ │
+│ │ saved library (NVS) │ │
+│ └───────────────────────────┘ │
+└──────────────────────────────────────────────────────────┘
+ · · · · · · (not wired in current UI) · · · · · · · · ·
+┌──────────────────────────────────────────────────────────┐
+│ NFC stack (Applications/nfc/) │
+│ manager (state machine + scan task) │
+│ scanner · reader · listener (emulation) │
+│ device / store (NVS card profiles + entries) │
+│ protocols/ (see table below) │
+└───────────────────────────┬──────────────────────────────┘
+ ▼
+┌──────────────────────────────────────────────────────────┐
+│ st25r3916 driver (highboy_nfc, SPI2/SPI3) │
+│ core · fifo · irq · aat + HAL (gpio / spi / timer) │
+└──────────────────────────────────────────────────────────┘
+```
+
+## UI Suite (`ui/screens/nfc/`)
+
+`ui_nfc_menu_open()` (`nfc_menu_ui.c`) builds the NFC menu. Selecting an item
+switches to the matching screen via the `screen_id_t` enum in `ui_manager.h`.
+
+| Menu item | Screen | File |
+|-----------|--------|------|
+| READ TAGS | `SCREEN_NFC_READ` | `nfc_read_ui.c` |
+| SCAN / IDENTIFY | `SCREEN_NFC_SCAN` | `nfc_scan_ui.c` |
+| EMULATE | `SCREEN_CARD_EMU` | `card_emu_ui.c` |
+| WRITE | `SCREEN_NFC_WRITE` | `nfc_write_ui.c` |
+| CONFIGURATIONS | `SCREEN_NFC_CONFIG` | `nfc_config_ui.c` |
+| SAVED | `SCREEN_NFC_SAVED` | `nfc_saved_ui.c` |
+| BANK CARD | `SCREEN_NFC_BANKCARD` | `nfc_bankcard_ui.c` |
+| DESFIRE | `SCREEN_NFC_DESFIRE` | `nfc_desfire_ui.c` |
+| NFC-V / 15693 | `SCREEN_NFC_ISO15693` | `nfc_iso15693_ui.c` |
+| ULTRALIGHT/NTAG | `SCREEN_NFC_ULTRALIGHT` | `nfc_ultralight_ui.c` |
+| NDEF | `SCREEN_NFC_NDEF` | `nfc_ndef_ui.c` |
+| FELICA | `SCREEN_NFC_FELICA` | `nfc_felica_ui.c` |
+| SHARE (P2P) | `SCREEN_NFC_P2P` | `nfc_p2p_ui.c` |
+| KEY DICTIONARY | `SCREEN_NFC_KEYDICT` | `nfc_keydict_ui.c` |
+
+There is also a dedicated emulate screen (`nfc_emulate_ui.c`,
+`SCREEN_NFC_EMULATE`) reached from the read flow. All of these screens are driven
+by the `nfc_sim` model and LVGL animations; none currently drive the ST25R3916.
+
+### Event-driven input
+
+Each screen registers a handler with `ui_input_set_screen_handler()` and reacts
+to `input_event_t` events (`INPUT_ACTION_PRESS` / `INPUT_ACTION_REPEAT` for
+`INPUT_BTN_UP/DOWN/LEFT/RIGHT/OK/BACK`). The central input pump only calls a
+handler while input is unlocked and no modal overlay is up, so screens carry no
+per-frame edge-detection of their own. Held UP/DOWN auto-scroll via the REPEAT
+action.
+
+### Rotation-aware layout
+
+Screens size and place elements with the live logical dimensions from
+`ui_metrics.h` (`ui_screen_w()` / `ui_screen_h()`), which follow
+`lv_display_set_rotation` rather than the fixed 240 x 320 panel constants, so the
+layout survives rotation (e.g. the read screen clamps its dump-list Y against
+`ui_screen_h()` and the footer height).
+
+### Shared visual kit (`nfc_ui_common`)
+
+`nfc_ui_common.h` provides the common look used by Read / Saved / Write /
+Emulate: an accent header + underline (`nfc_ui_header`), a credit-card-style
+panel that renders a card (`nfc_ui_card_panel`), an expanding concentric-ring
+"broadcasting field" animation (`nfc_ui_field_create` / `nfc_ui_field_tick`), and
+fire-and-forget speaker cues (`nfc_ui_play_sound`: a rising blip on tag found, a
+tick on save). The Identify screen animates a per-technology checklist
+(NFC-A / NFC-B / NFC-F / NFC-V).
+
+### Simulation model (`nfc_sim`)
+
+`nfc_sim.c` is the shared card record (`nfc_sim_card_t`: name, type, UID, ATQA,
+SAK) plus a saved "library" (up to `NFC_SIM_MAX_SAVED` = 16 cards) persisted in
+NVS and seeded with presets on first run. It can synthesize a random discovered
+tag (`nfc_sim_random_card`), build a card from a specific template
+(`nfc_sim_make_card`), and format UIDs as `DE:AD:BE:EF`.
+
+## NFC Stack (`Applications/nfc/`)
+
+The stack is a full reader/emulator implementation that targets the ST25R3916.
+
+### Manager (`nfc_manager`)
+
+A scan-task state machine over the driver:
+
+```c
+typedef enum {
+ NFC_MANAGER_STATE_IDLE, NFC_MANAGER_STATE_SCANNING,
+ NFC_MANAGER_STATE_READING, NFC_MANAGER_STATE_EMULATING,
+ NFC_MANAGER_STATE_ERROR, NFC_MANAGER_STATE_COUNT
+} nfc_manager_state_t;
+
+hb_nfc_err_t nfc_manager_start(nfc_manager_card_found_cb_t cb, void *ctx);
+void nfc_manager_stop(void);
+nfc_manager_state_t nfc_manager_get_state(void);
+```
+
+Hardware must be pre-initialized with `highboy_nfc_init()`. The card-found
+callback delivers an `hb_nfc_card_data_t`.
+
+### Scanner / Reader / Listener
+
+- `nfc_scanner` (`nfc_scanner_alloc/start/stop`) polls the field and reports the
+ detected protocol list (`nfc_scanner_event_t`, up to
+ `NFC_SCANNER_MAX_PROTOCOLS` = 4).
+- `nfc_reader` implements the concrete read/write flows:
+ `mf_classic_read_full()` (dumps all sectors into the global emulation card
+ `s_emu_card`), `mf_classic_write_all()` (writes data blocks back, trailers
+ guarded), `mfp_probe_and_dump()` (MIFARE Plus), `mful_dump_card()`
+ (Ultralight), and `t4t_dump_ndef()` (Type 4 NDEF).
+- `nfc_listener` starts card emulation from generic card data
+ (`nfc_listener_start`) or from a pre-loaded MIFARE Classic dump
+ (`nfc_listener_start_emu`).
+
+### Persistence (`nfc_device`, `nfc_store`)
+
+- `nfc_device` stores MIFARE Classic card profiles (with keys) in NVS namespace
+ `nfc_cards`, up to `NFC_DEVICE_MAX_PROFILES` (8), with an active-profile
+ selector for emulation and legacy generic-card wrappers.
+- `nfc_store` stores generic card entries (name, protocol, UID, ATQA, SAK, and a
+ protocol-specific payload up to `NFC_STORE_PAYLOAD_MAX` = 2048 B) in NVS, up to
+ `NFC_STORE_MAX_ENTRIES` (16), plus NTAG/Ultralight pack/unpack helpers.
+
+### Card families / protocols (`protocols/`)
+
+| Family | Directory | Notes |
+|--------|-----------|-------|
+| Common | `common/` | APDU, crypto, RF, tag, ISO-DEP TCL layer |
+| ISO 14443-A | `iso14443a/` | anti-collision, ISO-DEP, poller, NDEF, T4T + T4T emulation |
+| ISO 14443-B | `iso14443b/` | reader + emulation |
+| ISO 15693 (NFC-V) | `iso15693/` | reader + emulation |
+| FeliCa (NFC-F) | `felica/` | reader + emulation |
+| EMV | `emv/` | bank card / payment applications |
+| LLCP / SNEP | `llcp/` | NFC P2P link + SNEP exchange |
+| MIFARE | `mifare/` | Classic (+ emu, + writer), Ultralight, Plus, DESFire (+ emu), crypto1, nested attack (`mf_nested`), `mfkey`, key cache, key dictionary loader, known cards |
+| Topaz / Type 1 | `t1t/` | Type 1 tag |
+| Type 2 | `t2t/` | Type 2 tag emulation |
+
+The supported protocol identifiers are enumerated in
+`highboy_nfc_protocol_t` (`highboy_nfc_types.h`): ISO14443-3A/-3B/-4A/-4B,
+FeliCa, ISO15693, MIFARE Classic / Ultralight / Plus / DESFire, ST25TB, and SLIX.
+
+### Key dictionaries (`assets/nfc/dict/`)
+
+MIFARE key dictionaries shipped as assets and loaded by `nfc_dict_loader`:
+`mf_classic_default.dic`, `mf_classic_user.dic` (Classic key A/B lists) and
+`mf_ulc_default.dic` (Ultralight-C keys).
+
+## Driver: HighBoy NFC (`st25r3916`)
+
+`highboy_nfc.h` is the driver front end for the ST25R3916 / ST25R3916B:
+
+```c
+esp_err_t highboy_nfc_init(const highboy_nfc_config_t *config);
+void highboy_nfc_deinit(void);
+esp_err_t highboy_nfc_ping(uint8_t *out_chip_id);
+esp_err_t highboy_nfc_field_on(void);
+esp_err_t highboy_nfc_field_off(void);
+uint8_t highboy_nfc_measure_amplitude(void);
+bool highboy_nfc_field_detected(uint8_t *out_aux_display);
+```
+
+- Configuration is a `highboy_nfc_config_t` (SPI pins, host, mode 1, clock).
+ `HIGHBOY_NFC_CONFIG_DEFAULT()` provides the ESP32-P4 reference wiring.
+- The driver is split into `st25r3916_core.c`, `st25r3916_fifo.c`,
+ `st25r3916_irq.c`, `st25r3916_aat.c` (antenna auto-tuning) and a HAL layer
+ (`hal/hb_nfc_gpio.c`, `hb_nfc_spi.c`, `hb_nfc_timer.c`).
+- Capacity constants live in `highboy_nfc_types.h` (UID up to 10 bytes for
+ triple cascade, ATS up to 64 bytes, 512-byte hardware FIFO).
diff --git a/docs/ota/README.md b/docs/ota/README.md
index 8c7438411..539bda9b6 100644
--- a/docs/ota/README.md
+++ b/docs/ota/README.md
@@ -4,9 +4,20 @@ Handles firmware updates for TentacleOS via MicroSD card. Uses A/B OTA partition
## How It Works
-The C5 firmware is embedded inside the P4 binary at build time. A single `.bin` file updates both chips.
+The P4 and C5 update through **two separate flows**:
-### Update Flow
+- **P4 self-OTA** - the P4 flashes its own inactive OTA slot from an image on the
+ SD card, then reboots and self-validates (this document's main subject).
+- **C5 app OTA** - a separate flow (`c5_flasher`) reads
+ `/sdcard/c5/TentacleOS_C5.bin` and pushes it to the C5 over the SPI bridge; the
+ C5 writes its own inactive slot and reboots. See [C5 App OTA](#c5-app-ota-over-the-spi-bridge).
+
+> **Note:** embedding the C5 firmware inside the P4 binary now applies **only** to
+> the ROM-download recovery path (used to reflash a bricked/blank C5 over UART/ROM);
+> the normal C5 app update is the separate SPI-bridge flow above, not a single
+> combined `.bin`.
+
+### Update Flow (P4 self-OTA)
1. Place firmware at `/sdcard/update/tentacleos.bin`
2. Trigger `ota_start_update()` from UI or console
@@ -45,6 +56,37 @@ Scenarios:
fails, P4 does not confirm, bootloader rolls back
- **Healthy new image** - confirmed within a few seconds of boot
+### C5 App OTA (over the SPI bridge)
+
+The normal C5 firmware update is independent of the P4 self-OTA above. It is
+driven from the P4 by `c5_flasher` (`components/Service/c5_flasher/c5_flasher.c`):
+
+1. The image is read from `/sdcard/c5/TentacleOS_C5.bin` (not embedded in the P4
+ binary).
+2. The P4 sends `SPI_ID_SYSTEM_OTA_BEGIN` (size + transport) over the SPI bridge.
+ The C5 erases its inactive OTA slot asynchronously and reports `READY` via
+ `SPI_ID_SYSTEM_OTA_STATUS`.
+3. The P4 streams the image as `SPI_ID_SYSTEM_OTA_DATA` chunks; the C5 writes each
+ sequentially and acks. `bytes_written` (from the STATUS poll) is the resync
+ point if a chunk ack is lost.
+4. When the last chunk lands, the C5 finalizes, sets the boot slot to `DONE`, and
+ reboots into the new firmware. The P4 marks the bridge link down so the link
+ monitor re-probes and reconnects the new C5.
+
+> On the HighBoy V2 the transport is **SPI** (`SPI_OTA_TRANSPORT_SPI`); the UART
+> transport path in `c5_flasher` is kept for a future board that routes a real
+> P4->C5 UART and does not work on V2.
+
+**C5-side rollback (validated by P4 bridge health).** On the C5, after a fresh OTA
+image boots, `ota_post_boot_check()`
+(`firmware_c5/components/Service/ota/include/ota_service.h`) sees the running
+partition is pending verification and **waits for the P4 to reach it over the
+bridge** before marking the app valid; if the P4 does not establish the link
+within the validation window, the C5 does not confirm and the bootloader rolls
+back to the previous C5 image. It must be called after the C5's `kernel_init` so
+the bridge slave is already listening. This is the inverse of the P4 side, whose
+confirmation depends only on **local** health (see below) and never on the C5.
+
### Partition Table
See [boot_report](../boot_report/README.md) for the full current layout (the OTA
@@ -59,7 +101,21 @@ slots were resized to `0x270000` to make room for a `coredump` partition).
### Versioning
-Version is read from `assets/config/OTA/firmware.json`. Both P4 and C5 share the same version string. The C5 responds its version via `SPI_ID_SYSTEM_VERSION` (0x04).
+The build-time version string is `common/metadata/version_info.txt` (a single
+line, e.g. `1.4.0`). `firmware_p4/components/Service/CMakeLists.txt` reads it and
+configures `common/metadata/ota_version.h.in` into a generated `ota_version.h`
+that defines `FIRMWARE_VERSION`, which `ota_service.c` compiles in and returns
+from `ota_get_current_version()`. (The header is generated at build time from
+`version_info.txt`; there is no checked-in `ota_version.h`.)
+
+`assets/config/OTA/firmware.json` is **only the runtime/synced copy**, not the
+source of truth: `firmware_p4/CMakeLists.txt` stamps the same `version_info.txt`
+value into the assets image at build time, and at boot `ota_sync_version_to_assets()`
+(called from `ota_post_boot_check()`) rewrites `firmware.json` to the running
+`FIRMWARE_VERSION` if they differ.
+
+The C5 reports its own version over the SPI bridge via `SPI_ID_SYSTEM_VERSION`
+(SYSTEM subcommand `0x04`).
## API
diff --git a/docs/pins/README.md b/docs/pins/README.md
new file mode 100644
index 000000000..206e0defd
--- /dev/null
+++ b/docs/pins/README.md
@@ -0,0 +1,128 @@
+# Pins (Board GPIO Map)
+
+`pin_def.h` is the central, header-only map of every GPIO assignment for a board. No driver should hardcode a GPIO number: all of them include this header and use the defines. The P4 and C5 firmwares each ship their own `pin_def.h` with different pin numbers and different peripheral sets, so the two are documented as separate sections below.
+
+## Overview
+
+- **Location:** `components/Drivers/pins/include/pin_def.h` (per firmware)
+- **Header only:** no `.c` file; pure `#define`s inside an `extern "C"` guard.
+- **Rule:** hardware pin numbers live here and only here.
+
+A `-1` value marks a signal that is not wired on the board (or is driven by something other than that MCU), and `LED_COUNT` gives the addressable-LED count.
+
+---
+
+# P4 (ESP32-P4, HighBoy V2)
+
+Header: `firmware_p4/components/Drivers/pins/include/pin_def.h`
+
+## Pin Groups
+
+### Shared SPI bus
+One SPI bus is shared by the display, CC1101, NFC, IMU and LoRa.
+
+| Signal | Define | GPIO |
+| :--- | :--- | :--- |
+| MOSI | `GPIO_SPI_MOSI_PIN` | 22 |
+| SCLK | `GPIO_SPI_SCLK_PIN` | 21 |
+| MISO | `GPIO_SPI_MISO_PIN` | 23 |
+
+The SX1262 LoRa reuses these same lines: `GPIO_LORA_SCLK_PIN` (21), `GPIO_LORA_MOSI_PIN` (22) and `GPIO_LORA_MISO_PIN` (23) alias the shared bus, with its own `GPIO_LORA_CS_PIN` (26), `GPIO_LORA_BUSY_PIN` (4) and `GPIO_LORA_DIO1_PIN` (5). `GPIO_LORA_RESET_PIN`, `GPIO_LORA_TXEN_PIN` and `GPIO_LORA_RXEN_PIN` are `-1` (not wired).
+
+### I2C bus
+Charger, fuel gauge, LED driver and haptic share one I2C bus: `GPIO_I2C_SDA_PIN` (31), `GPIO_I2C_SCL_PIN` (30).
+
+### Chip selects and IRQs on the shared SPI bus
+
+| Peripheral | Signal | Define | GPIO |
+| :--- | :--- | :--- | :--- |
+| CC1101 | CS | `GPIO_CC1101_CS_PIN` | 20 |
+| CC1101 | GDO0 | `GPIO_CC1101_GDO0_PIN` | 8 |
+| CC1101 | GDO2 | `GPIO_CC1101_GDO2_PIN` | 9 |
+| ST7789 | CS / DC / RST / BL | `GPIO_ST7789_CS_PIN` / `_DC_PIN` / `_RST_PIN` / `_BL_PIN` | 51 / 50 / 52 / 14 |
+| IMU QMI8658 | CS | `GPIO_QMI8658A_CS_PIN` | 36 |
+| NFC ST25R3916 | CS / IRQ | `GPIO_NFC_CS_PIN` / `GPIO_NFC_IRQ_PIN` | 24 / 10 |
+
+### Sub-GHz antenna switch (PE613050)
+`GPIO_RF_SW_V1_PIN` (17), `GPIO_RF_SW_V2_PIN` (18).
+
+### Buttons (active-low)
+
+| Button | Define | GPIO |
+| :--- | :--- | :--- |
+| LEFT | `GPIO_BTN_LEFT_PIN` | 35 |
+| RIGHT | `GPIO_BTN_RIGHT_PIN` | 13 |
+| UP | `GPIO_BTN_UP_PIN` | 3 |
+| DOWN | `GPIO_BTN_DOWN_PIN` | 7 |
+| OK | `GPIO_BTN_OK_PIN` | 25 |
+| BACK | `GPIO_BTN_BACK_PIN` | 6 |
+
+**BACK / LEFT are on the hardware power path.** As documented in `power_policy.c`, BACK and LEFT are wired to the charger `/QON` and the P4 `CHIP_PU` respectively, so holding **BACK + LEFT** together resets the P4 in hardware before firmware can react. Because of this the graceful power-off combo is deliberately **OK + LEFT** (OK is not on that path), letting firmware stay alive to issue the ship-mode command. Keep this in mind when picking or documenting button combos.
+
+Note also that `GPIO_BTN_LEFT_PIN` (35) is the same pin as the P4 boot strap `GPIO_P4_BOOT_PIN` (35, "button B5"), so LEFT doubles as a boot-strapping pin.
+
+### 125 kHz LF RFID
+`GPIO_RFID_LF_CARRIER_PIN` (15), `GPIO_RFID_LF_DATA_PIN` (16), `GPIO_RFID_LF_MOD_PIN` (11), `GPIO_RFID_LF_COIL_PIN` (2).
+
+### IR
+`GPIO_IR_TX_PIN` (0), `GPIO_IR_RX_PIN` (1) (TSOP receiver + LED).
+
+### SDMMC (4-bit SDIO)
+`GPIO_SDMMC_CLK_PIN` (43), `GPIO_SDMMC_CMD_PIN` (44), `GPIO_SDMMC_D0_PIN` (39), `GPIO_SDMMC_D1_PIN` (40), `GPIO_SDMMC_D2_PIN` (41), `GPIO_SDMMC_D3_PIN` (42), card-detect `GPIO_SD_CD_PIN` (34).
+
+### P4 to C5 bridge (SPI master)
+`GPIO_BRIDGE_SCLK_PIN` (45), `GPIO_BRIDGE_MOSI_PIN` (46), `GPIO_BRIDGE_MISO_PIN` (47), `GPIO_BRIDGE_CS_PIN` (48). `GPIO_BRIDGE_IRQ_PIN` is `-1`. C5 reset/boot (`GPIO_C5_RESET_PIN`, `GPIO_C5_BOOT_PIN`) are `-1` because the CP2105, not the P4, drives them.
+
+### Audio (I2S) and mic (PDM)
+`GPIO_AUDIO_EN_PIN` (49), `GPIO_AUDIO_LRCLK_PIN` (29), `GPIO_AUDIO_BCLK_PIN` (28), `GPIO_AUDIO_DIN_PIN` (27) for the amp (commented NS4168); `GPIO_MIC_PDM_CLK_PIN` (54), `GPIO_MIC_PDM_DATA_PIN` (53) for the PDM mic (commented MSM261). See [docs/audio_i2s/README.md](../audio_i2s/README.md).
+
+### Miscellaneous
+`GPIO_HAPTIC_TRIG_PIN` (32, AW8623 trigger), `GPIO_CHARGER_CE_PIN` (33, BQ25896 OTG/CE), `GPIO_USB_MUX_SEL_PIN` (19, TS3USB221 D+/D- mux), console UART0 `GPIO_P4_UART0_RX_PIN` (37) / `GPIO_P4_UART0_TX_PIN` (38) to the CP2105 SCI, and `GPIO_LED_RGB_PIN` `-1` with `LED_COUNT` 1 (the LP5816 RGB LED is I2C-driven in V2, no data pin).
+
+### No direct P4 to C5 UART
+On the HighBoy V2 there is **no** direct P4 to C5 UART. The schematic routes the P4 UART0 (GPIO37/38) and the C5 UART0 (GPIO11/12) to two **separate** channels of the CP2105 USB bridge; they never meet. `GPIO38` is the P4's own console TX to the CP2105 (SCI), not a wire to the C5. The legacy defines `GPIO_C5_UART_TX_PIN` (38), `GPIO_C5_UART_RX_PIN` (39), `GPIO_RFID_UART_TX_PIN` (24) and `GPIO_RFID_UART_RX_PIN` (25) are kept only so unported V1 drivers still build. Consequently a "C5 OTA over UART" path cannot reach the C5 (it just writes to the P4's USB serial); the only P4 to C5 data path is the SPI bridge.
+
+---
+
+# C5 (ESP32-C5)
+
+Header: `firmware_c5/components/Drivers/pins/include/pin_def.h`
+
+The C5 carries a smaller peripheral set than the P4 and uses entirely different GPIO numbers.
+
+## Pin Groups
+
+### SPI bus
+`GPIO_SPI_MOSI_PIN` (11), `GPIO_SPI_SCLK_PIN` (12), `GPIO_SPI_MISO_PIN` (13).
+
+### CC1101 Sub-GHz radio
+`GPIO_CC1101_CS_PIN` (3), `GPIO_CC1101_GDO0_PIN` (8), `GPIO_CC1101_GDO2_PIN` (9).
+
+### SD card (SPI mode)
+`GPIO_SD_CARD_CS_PIN` (14). Unlike the P4 (4-bit SDIO), the C5 uses SPI-mode SD.
+
+### ST7789 display
+`GPIO_ST7789_CS_PIN` (48), `GPIO_ST7789_DC_PIN` (47), `GPIO_ST7789_RST_PIN` (21), `GPIO_ST7789_BL_PIN` (38).
+
+### Buttons
+
+| Button | Define | GPIO |
+| :--- | :--- | :--- |
+| LEFT | `GPIO_BTN_LEFT_PIN` | 5 |
+| BACK | `GPIO_BTN_BACK_PIN` | 7 |
+| UP | `GPIO_BTN_UP_PIN` | 15 |
+| DOWN | `GPIO_BTN_DOWN_PIN` | 6 |
+| OK | `GPIO_BTN_OK_PIN` | 4 |
+| RIGHT | `GPIO_BTN_RIGHT_PIN` | 16 |
+
+The C5 button pins are independent GPIOs and are not tied to the charger `/QON` or `CHIP_PU` power path that the P4's BACK/LEFT are on.
+
+### I2C bus
+`GPIO_I2C_SDA_PIN` (8), `GPIO_I2C_SCL_PIN` (9). These share the same GPIO numbers as the CC1101 GDO0/GDO2 defines above.
+
+### RGB LED
+`GPIO_LED_RGB_PIN` (27), `LED_COUNT` 1 (WS2812 / SK6812). Unlike the P4, the C5 has a real addressable-LED data pin.
+
+### P4 to C5 bridge (SPI slave)
+The C5 side of the bridge is the SPI **slave**: `GPIO_BRIDGE_SCLK_PIN` (26), `GPIO_BRIDGE_MOSI_PIN` (25), `GPIO_BRIDGE_MISO_PIN` (24), `GPIO_BRIDGE_CS_PIN` (23), `GPIO_BRIDGE_IRQ_PIN` (3). As noted on the P4 side, this SPI bridge is the only data path between the two chips; there is no direct P4 to C5 UART on the V2 board.
+
diff --git a/docs/power_manager/README.md b/docs/power_manager/README.md
new file mode 100644
index 000000000..eafc618a9
--- /dev/null
+++ b/docs/power_manager/README.md
@@ -0,0 +1,79 @@
+# Power Manager - P4 (esp_pm DFS / light-sleep)
+
+System power manager for `firmware_p4`. It owns `esp_pm` and drives automatic light sleep gated by a shared `NO_LIGHT_SLEEP` lock, so the CPU only powers off when the device is genuinely idle (screen off, no active resource, not plugged in).
+
+## Overview
+
+- **Location:** `firmware_p4/components/Service/power_manager/`
+- **Header:** `include/power_manager.h`
+- **Sources:** `power_manager.c` (esp_pm setup + lock), `power_manager_usb.c` (TinyUSB bus callbacks)
+- **Dependencies:** `esp_pm`, `esp_log`, `esp_err`, `tusb` (for the USB callbacks)
+- **Gated by:** `CONFIG_PM_ENABLE`. When PM is off, all entry points are inert no-ops (USB state is still tracked); the CPU stays at a fixed frequency and never sleeps.
+- **Why Service (not Core):** `power_policy` lives in Applications, which cannot depend on Core (Core already REQUIRES Applications, so that edge would be a cycle). Both Core (transitively) and Applications reach Service.
+
+## Power model
+
+- **Frequency is pinned:** `esp_pm_configure` is called with `min_freq_mhz == max_freq_mhz == 360`, so there is **no DFS**. DFS would scale the APB clock and break the console UART baud (the IDF UART driver excludes the console UART from PM). The power win here is light sleep (CPU fully off when idle), not frequency scaling.
+- **Light sleep** (`light_sleep_enable = true`) engages only when nobody holds the shared `ESP_PM_NO_LIGHT_SLEEP` lock **and** all tasks are blocked.
+- **The `NO_LIGHT_SLEEP` lock** is created once at init and is not held by the manager itself. Resource owners that need the CPU awake acquire it and release it themselves (screen on, active radio, host-link/OTA session, ...). It is ref-counted: while the count is `> 0` the system never light-sleeps.
+
+## USB / external-power gating (item 41)
+
+Two independent inputs feed a single internal wake hold:
+
+- **VBUS / external power** from the charger (battery service) via `power_manager_set_external_power`.
+- **USB bus lifecycle** via `power_manager_set_usb_suspended`.
+
+The internal rule is `want = external_power && !usb_suspended`. When that transitions true the manager takes one `NO_LIGHT_SLEEP` hold; when it transitions false it releases it. So the device never light-sleeps while plugged in and the host has not suspended the bus (mid charge, host-link, or OTA session).
+
+`power_manager_usb.c` provides the strong TinyUSB bus-lifecycle callbacks (TinyUSB declares them weak). They only fire when native P4 USB is up (mux switched to native); on the default UART-bridge path TinyUSB is not installed and plugged-in state comes solely from VBUS. Mapping:
+
+| TinyUSB callback | USB suspended input | Effect |
+|------------------|---------------------|--------|
+| `tud_mount_cb` / `tud_resume_cb` | not suspended | hold wake lock (if external power) |
+| `tud_umount_cb` / `tud_suspend_cb` | suspended | release wake lock |
+
+## API Reference
+
+### `power_manager_init`
+```c
+void power_manager_init(void);
+```
+Call once early in boot. Configures `esp_pm` (pinned frequency + light sleep) and creates the shared `NO_LIGHT_SLEEP` lock. Does not acquire the lock. No-op when `CONFIG_PM_ENABLE` is off.
+
+### `power_manager_no_sleep_acquire`
+```c
+esp_err_t power_manager_no_sleep_acquire(void);
+```
+Hold the CPU out of light sleep. Ref-counted: every acquire needs a matching release. Returns `ESP_ERR_INVALID_STATE` if the lock was never created; `ESP_OK` otherwise (also `ESP_OK` as a no-op when PM is disabled).
+
+### `power_manager_no_sleep_release`
+```c
+esp_err_t power_manager_no_sleep_release(void);
+```
+Release one hold taken with `power_manager_no_sleep_acquire`. Same return contract as acquire.
+
+### `power_manager_set_external_power`
+```c
+void power_manager_set_external_power(bool present);
+```
+Report external power (USB/charger VBUS) presence. Idempotent; recomputes the internal USB wake hold.
+
+### `power_manager_set_usb_suspended`
+```c
+void power_manager_set_usb_suspended(bool suspended);
+```
+Report USB bus suspend state (from `tud_suspend`/`tud_resume`, and treated as suspended on umount). A suspended host drops the plugged-in hold. Idempotent.
+
+### `power_manager_external_power`
+```c
+bool power_manager_external_power(void);
+```
+True while external power (VBUS) is present. Always valid regardless of `CONFIG_PM_ENABLE`, for battery-vs-plugged policy decisions.
+
+## Tunables
+
+| Symbol | Location | Value | Meaning |
+|--------|----------|-------|---------|
+| `PM_FREQ_MHZ` | `power_manager.c` | `360` | Pinned CPU frequency (min == max, no DFS). |
+| `CONFIG_PM_ENABLE` | sdkconfig | - | Master gate. Off: all calls are inert no-ops. |
diff --git a/docs/spi/README.md b/docs/spi/README.md
index d234b6341..f2f5ac577 100644
--- a/docs/spi/README.md
+++ b/docs/spi/README.md
@@ -12,8 +12,11 @@ This component acts as a central manager for the SPI bus, allowing multiple devi
## Supported Devices (`spi_device_id_t`)
1. **SPI_DEVICE_ST7789:** Display Driver
-2. **SPI_DEVICE_CC1101:** Sub-GHz Radio
+2. **SPI_DEVICE_CC1101:** Sub-GHz radio. The enum name is **legacy**: the SPI3
+ radio is now the **SX1262 LoRa** part, which shares the SPI3 bus with the
+ ST7789 display behind a bus lock (see `spi_bus_lock_take` / `spi_bus_lock_give`).
3. **SPI_DEVICE_SD_CARD:** Storage
+4. **SPI_DEVICE_BRIDGE:** The P4->C5 SPI bridge device (P4 only).
## API Reference
@@ -69,7 +72,7 @@ This component acts as a central manager for the SPI bus, allowing multiple devi
## Supported Devices (`spi_device_id_t`)
1. **SPI_DEVICE_ST7789:** Display Driver
-2. **SPI_DEVICE_CC1101:** Sub-GHz Radio
+2. **SPI_DEVICE_CC1101:** Sub-GHz radio (legacy enum name for the LoRa part).
3. **SPI_DEVICE_SD_CARD:** Storage
## API Reference
diff --git a/docs/spi_bridge/README.md b/docs/spi_bridge/README.md
index f2daa92b6..de3fa5740 100644
--- a/docs/spi_bridge/README.md
+++ b/docs/spi_bridge/README.md
@@ -74,20 +74,47 @@ The P4 flashes the C5's firmware over a separate UART link using the official
## 3. Frame format
-Every packet on the SPI bus starts with a fixed **5-byte header**:
+Every packet on the SPI bus starts with a fixed **7-byte header** (5 bytes of
+framing + a 2-byte CRC-16):
```c
-typedef struct {
+typedef struct __attribute__((packed)) {
uint8_t sync; // 0xAA
uint8_t type; // 0x01 CMD, 0x02 RESP, 0x03 STREAM
uint8_t category; // spi_cat_t - subsystem
uint8_t op; // operation within the category
uint8_t length; // payload bytes that follow (0-255)
+ uint16_t crc; // CRC-16 over [type,category,op,length] + data
} spi_header_t;
```
-`SPI_FRAME_SIZE` = header + 256 B payload, **rounded up to a multiple of 4** for
-DMA = **264 B**. The command/response path always transfers `SPI_FRAME_SIZE`.
+`SPI_MAX_PAYLOAD` is **255** (not 256): the header's `length` is a `uint8_t`, so
+256 was never representable. For a RESP, `length` also counts the status byte, so
+its data maxes at 254.
+
+`SPI_FRAME_SIZE` = header + `SPI_MAX_PAYLOAD`, **rounded up to a multiple of 4**
+for DMA = **264 B** (unchanged: the header grew by 2 and the payload cap shrank by
+1). The command/response path always transfers `SPI_FRAME_SIZE`.
+
+### Frame-integrity CRC (SPI-1)
+
+Every frame carries a **CRC-16/CCITT** (the ESP-ROM `esp_rom_crc16_le`
+implementation) over the header's `[type,category,op,length]` plus the data bytes.
+The `sync` byte (framing marker) and the `crc` field itself are excluded. Inline
+helpers in `spi_protocol.h`:
+
+```c
+uint16_t spi_frame_crc(const spi_header_t *h, uint16_t data_len); // compute
+void spi_frame_seal(const spi_header_t *h, uint16_t data_len); // stamp into a fresh frame
+bool spi_frame_valid(const spi_header_t *h, uint16_t data_len);// verify on receive
+```
+
+`data_len` is `header.length` for CMD/RESP frames, or `2 + batch_len` for STREAM
+frames (whose `header.length` is 0 and whose real size lives in the leading `u16`
+of the payload). The receiver drops any frame that fails `spi_frame_valid` - a
+mismatch means the bus corrupted it. The CRC catches bus corruption; it does
+**not** catch a drifted `spi_protocol.h` (intact bytes, different meaning) - that
+is the job of the proto-version check (SPI-2, §8).
### Command identifier = `category` + `op`
@@ -104,6 +131,7 @@ alone; `op` selects the operation within it.
| `SPI_CAT_MESH` | `0x04` | meshtastic (split BLE/WiFi transport) |
| `SPI_CAT_MCORE` | `0x05` | meshcore → `bt_dispatcher` |
| `SPI_CAT_HOST` | `0x06` | companion host-link BLE relay → `bt_dispatcher` |
+| `SPI_CAT_SCREEN` | `0x07` | P4-native screen sharing over the USB host link (handled locally, never relayed) |
| `SPI_CAT_SESSION` | `0xFF` | inline session handlers |
In C, the `SPI_ID_*` constants stay single named values (e.g.
@@ -142,6 +170,24 @@ P4 (master) C5 (slave)
- A per-command **mutex** on the P4 serialises commands; long radio ops get
longer timeouts (`SPI_TIMEOUT_WIFI_MS = 20 s`, default `1 s`).
+### Handshake modes: IRQ vs POLL
+
+The handshake is a physical-layer choice (`spi_bridge_mode_t`); the wire protocol
+is identical either way. Both ends must be initialized in the **same** mode.
+
+- **`SPI_BRIDGE_MODE_IRQ`** (default) - the C5 pulses the IRQ GPIO when a
+ response/stream frame is armed; the P4 catches the rising edge. Needs the IRQ
+ trace wired.
+- **`SPI_BRIDGE_MODE_POLL`** - no IRQ line, so the P4 re-clocks the bus until the
+ slave answers with a valid frame. Used on boards without an IRQ trace (the
+ HighBoy V2 PCB has `GPIO_BRIDGE_IRQ_PIN == -1`, so `bridge_manager` initializes
+ the master in POLL mode).
+
+```c
+esp_err_t spi_bridge_master_init(void); // == IRQ mode
+esp_err_t spi_bridge_master_init_mode(spi_bridge_mode_t mode); // pick IRQ or POLL
+```
+
---
## 5. Generic data pipe (pulling lists)
@@ -174,7 +220,7 @@ transfer** (`SPI_STREAM_FRAME_SIZE = 2048 B`) instead of one record per
round-trip:
```
-STREAM frame payload (after the 5-byte header, type = STREAM):
+STREAM frame payload (after the 7-byte header, type = STREAM):
[u16 batch_len][record][record]... record = [u16 op][u8 len][len bytes]
```
@@ -218,14 +264,76 @@ running into the void if the P4 crashes or stops listening:
---
+## Async scans (non-blocking)
+
+A radio scan can take seconds. Rather than hold the per-command bridge mutex for
+its whole duration, a scan is run **asynchronously**: fire the scan command (the
+C5 starts it and returns immediately), then poll a lightweight status id until it
+reports the scan finished. Results are fetched afterwards through the generic data
+pipe as usual.
+
+```c
+esp_err_t spi_bridge_run_scan(spi_id_t scan_id, spi_id_t status_id,
+ const uint8_t *payload, uint8_t len);
+```
+
+Returns `ESP_OK` when the status id reports done, or `ESP_ERR_TIMEOUT` if it never
+cleared. The status ids are `SPI_ID_WIFI_SCAN_STATUS` (`0x0150`) and
+`SPI_ID_BT_SCAN_STATUS` (`0x027F`) - each responds `1` while its scan is running.
+
+---
+
+## Power management
+
+The P4 tells the C5 its power state so the co-processor can drop its radio when
+the P4 is idle or asleep instead of running full RX all the time. It sends
+`SPI_ID_SYSTEM_POWER_STATE` (op `0x4A`) with a one-byte `spi_power_state_t`:
+
+| State | Value | Meaning |
+|-------|-------|---------|
+| `SPI_POWER_ACTIVE` | 0 | Normal operation: full radio. |
+| `SPI_POWER_IDLE` | 1 | Screen dimmed: deeper modem sleep (MAX_MODEM). |
+| `SPI_POWER_SLEEP` | 2 | Device asleep: drop the radio (unless a capture is running). |
+
+---
+
## 8. Firmware versioning & flashing
-The C5 firmware is **embedded in the P4 firmware** at build time (bootloader +
-partition table + app). On boot, `bridge_manager` queries the C5's version
-(`SPI_ID_SYSTEM_VERSION`) and compares it against the P4's expected version
-(`FIRMWARE_VERSION`, currently **1.3.0**). On mismatch (or no response) the P4
-re-flashes the C5 over the UART link using `esp-serial-flasher`, writing the
-full image:
+Two independent checks run at boot in `bridge_manager`, both **detection-only** -
+the P4 never auto-flashes (the user updates explicitly with the `c5` console
+command):
+
+- **App version.** Query the C5's version (`SPI_ID_SYSTEM_VERSION`) and compare it
+ against the P4's expected `FIRMWARE_VERSION` (generated from
+ `common/metadata/version_info.txt`, currently **1.4.0**). A mismatch logs
+ "update available"; a silent C5 marks the bridge down.
+- **Wire-protocol version (SPI-2).** Read the C5's `SPI_ID_SYSTEM_PROTO_VERSION`
+ (op `0x0D`) and compare it against `SPI_PROTOCOL_VERSION` (**2**). This catches a
+ drifted `spi_protocol.h` that the per-frame CRC cannot (intact bytes, different
+ meaning). A mismatch logs loudly and lights the error LED but keeps the bridge
+ up. Bump `SPI_PROTOCOL_VERSION` in **both** copies of `spi_protocol.h` on any
+ change to the contract (an id, a struct, or the header layout).
+
+### Update path
+
+The everyday update is an **app OTA**: the C5 keeps running its app and receives a
+new image into its inactive OTA slot. The image is **streamed from the SD card**
+(`/sdcard/c5/TentacleOS_C5.bin`) - it is no longer embedded in the P4 binary for
+this path. The OTA control plane (begin / status / per-chunk acks) always rides
+this SPI bridge:
+
+| Op | `spi_id_t` | Purpose |
+|----|------------|---------|
+| `SPI_ID_SYSTEM_OTA_BEGIN` | `0x0009` | begin OTA; payload `spi_ota_begin_t { size, transport }` |
+| `SPI_ID_SYSTEM_OTA_STATUS` | `0x000A` | poll `spi_ota_status_t { state, bytes_written }` |
+| `SPI_ID_SYSTEM_OTA_DATA` | `0x000B` | one firmware chunk (SPI transport) |
+
+The image bytes travel over the transport chosen in `spi_ota_begin_t.transport`
+(`spi_ota_transport_t`): `SPI_OTA_TRANSPORT_SPI` (as `OTA_DATA` chunks) or
+`SPI_OTA_TRANSPORT_UART` (raw over UART). For a blank/bricked C5 that has no
+running app, the P4 first sends `SPI_ID_SYSTEM_ENTER_DOWNLOAD` (op `0x08`) to boot
+the C5 into ROM download mode, then reflashes over UART with `esp-serial-flasher`
+using the **embedded** images (the fallback path):
| Image | C5 flash offset |
|-------|-----------------|
@@ -233,9 +341,8 @@ full image:
| partition table | `0x8000` |
| app | `0x10000` |
-Any breaking change to the wire format must bump **both** versions
-(`FIRMWARE_VERSION` on the P4 and `SPI_FW_VERSION_STRING` on the C5) to the same
-new value, forcing a re-sync.
+Full detail: [`../c5_flasher/README.md`](../c5_flasher/README.md) and
+[`../bridge_manager/README.md`](../bridge_manager/README.md).
---
@@ -245,8 +352,10 @@ new value, forcing a re-sync.
- `components/Service/spi_bridge/` - `spi_bridge.c` (send command, stream task),
`spi_session.c` (session/heartbeat), `spi_protocol.h` (shared contract)
- `components/Drivers/spi_bridge_phy/` - SPI master PHY + IRQ edge ISR
-- `components/Service/bridge_manager/` - version check + C5 recovery
-- `components/Service/c5_flasher/` - `esp-serial-flasher` wrapper
+- `components/Service/bridge_manager/` - bridge lifecycle, app + proto-version
+ checks, C5 link monitor, OTA trigger
+- `components/Service/c5_flasher/` - app OTA (SD image over SPI/UART) +
+ `esp-serial-flasher` ROM fallback
**C5 (slave)**
- `components/Service/spi_bridge/` - `spi_bridge.c` (`bridge_task` routing +
@@ -280,17 +389,18 @@ This component manages the high-speed communication link between the **ESP32-P4
The P4 acts as the **SPI Master**. It is responsible for:
1. Generating the SCLK and managing the CS line.
2. Initiating all command transfers.
-3. Handling the **IRQ (Handshake)** signal from the C5 to know when response data is ready.
-4. Managing the C5 lifecycle (Reset, Boot mode, and Firmware Updates via UART).
+3. Handling the **IRQ (Handshake)** signal from the C5 to know when response data is ready (or re-clocking the bus in POLL mode on boards without an IRQ trace).
+4. Managing the C5 lifecycle: reset/boot control, the app OTA (streamed from the SD image over the SPI bridge), and ROM serial-flash recovery over UART.
## Protocol Specification
-Every packet follows a 5-byte fixed header:
+Every packet follows a 7-byte fixed header (5 framing bytes + a 2-byte CRC-16):
- `Sync (0xAA)`: Packet synchronization.
- `Type`: `0x01` (Command), `0x02` (Response), `0x03` (Stream).
- `Category`: Subsystem selector (`spi_cat_t`: WiFi `0x01`, BT `0x02`, …). The C5
routes a command to a dispatcher by this byte alone.
- `Op`: Operation within the category.
-- `Length`: Size of the following payload (0-255 bytes).
+- `Length`: Size of the following payload (0-255 bytes; `SPI_MAX_PAYLOAD = 255`).
+- `CRC`: CRC-16 over `[type,category,op,length]` + data (see §3, Frame-integrity CRC).
`Category` + `Op` together form the packed command identifier (`spi_id_t`),
built via `SPI_CMD(cat, op)`. Use `spi_header_cmd()` / `spi_header_set_cmd()` to
@@ -311,10 +421,29 @@ Every command's `spi_id_t` packs `Category` (high byte) and `Op` (low byte) via
| `SPI_ID_SYSTEM_DATA` | `0x05` | `0x0005` |
| `SPI_ID_SYSTEM_STREAM` | `0x06` | `0x0006` |
| `SPI_ID_SYSTEM_LOG` | `0x07` | `0x0007` |
+| `SPI_ID_SYSTEM_ENTER_DOWNLOAD` | `0x08` | `0x0008` |
+| `SPI_ID_SYSTEM_OTA_BEGIN` | `0x09` | `0x0009` |
+| `SPI_ID_SYSTEM_OTA_STATUS` | `0x0A` | `0x000A` |
+| `SPI_ID_SYSTEM_OTA_DATA` | `0x0B` | `0x000B` |
+| `SPI_ID_SYSTEM_INFO` | `0x0C` | `0x000C` |
+| `SPI_ID_SYSTEM_PROTO_VERSION` | `0x0D` | `0x000D` |
+| `SPI_ID_SYSTEM_POWER_STATE` | `0x4A` | `0x004A` |
`SPI_ID_SYSTEM_LOG` is a C5→P4 stream carrying log lines (`[level u8][utf-8]`) for
the companion's C5 console (see the host-link docs).
+- `ENTER_DOWNLOAD` (`0x08`): P4→C5 - reboot into ROM serial-download mode for
+ serial-flash recovery.
+- `OTA_BEGIN` / `OTA_STATUS` / `OTA_DATA` (`0x09`-`0x0B`): P4→C5 app-OTA control
+ plane (§8). BEGIN payload `spi_ota_begin_t { u32 size, u8 transport }`; STATUS
+ response `spi_ota_status_t { u8 state, u32 bytes_written }`; DATA carries one
+ firmware chunk on the SPI transport.
+- `SYSTEM_INFO` (`0x0C`): P4→C5 - read chip identity (`spi_sys_info_t`:
+ model / revision / MAC / free heap).
+- `PROTO_VERSION` (`0x0D`): P4→C5 - read the C5's `SPI_PROTOCOL_VERSION` (u16).
+ Checked at bridge init (§8).
+- `POWER_STATE` (`0x4A`): P4→C5 - device power state (see Power management below).
+
System ops `0x40`-`0x49` (`FILE_*`, `SYSTEM_DEVICE_STATE`, `SYSTEM_CONSOLE_EXEC`,
`SYSTEM_GET_SETTINGS`, `SYSTEM_SET_SETTINGS`) are **P4-local host-link commands**:
they share the `spi_id_t` space so the companion app and P4 agree, but they are
@@ -326,6 +455,7 @@ in [`../host_link/protocol.md`](../host_link/protocol.md).
| Command | Op | `spi_id_t` |
|---------|----|------------|
| `SPI_ID_WIFI_SCAN` | `0x10` | `0x0110` |
+| `SPI_ID_WIFI_SCAN_STATUS` | `0x50` | `0x0150` |
| `SPI_ID_WIFI_CONNECT` | `0x11` | `0x0111` |
| `SPI_ID_WIFI_DISCONNECT` | `0x12` | `0x0112` |
| `SPI_ID_WIFI_GET_STA_INFO` | `0x13` | `0x0113` |
@@ -396,6 +526,7 @@ in [`../host_link/protocol.md`](../host_link/protocol.md).
| Command | Op | `spi_id_t` |
|---------|----|------------|
| `SPI_ID_BT_SCAN` | `0x50` | `0x0250` |
+| `SPI_ID_BT_SCAN_STATUS` | `0x7F` | `0x027F` |
| `SPI_ID_BT_CONNECT` | `0x51` | `0x0251` |
| `SPI_ID_BT_DISCONNECT` | `0x52` | `0x0252` |
| `SPI_ID_BT_GET_INFO` | `0x53` | `0x0253` |
@@ -475,6 +606,27 @@ this category to `bt_dispatcher`. See [`../host_link/`](../host_link/README.md).
| `SPI_ID_HOST_RX` | `0xA3` | `0x06A3` | C5→P4 stream: app→device (BLE write) |
| `SPI_ID_HOST_STATUS` | `0xA4` | `0x06A4` | P4→C5 cmd: poll BLE connection state |
+### Screen (`0x07`)
+
+P4-native screen sharing over the USB host link. These are **handled locally on
+the P4 and never relayed to the C5**; they share the `spi_id_t` space so the
+companion app and P4 agree on the ids. `START`/`STOP`/`KEY` are app→device
+commands; `FRAME` is a device→app STREAM.
+
+| Command | Op | `spi_id_t` | Direction |
+|---------|----|------------|-----------|
+| `SPI_ID_SCREEN_START` | `0x01` | `0x0701` | app→device: start streaming the live screen |
+| `SPI_ID_SCREEN_STOP` | `0x02` | `0x0702` | app→device: stop streaming |
+| `SPI_ID_SCREEN_KEY` | `0x03` | `0x0703` | app→device: inject a key (`spi_screen_key_t`) |
+| `SPI_ID_SCREEN_FRAME` | `0x04` | `0x0704` | device→app STREAM: RGB565 row-strip |
+
+- `spi_screen_key_t`: `UP` (0), `DOWN` (1), `LEFT` (2), `RIGHT` (3), `OK` (4),
+ `BACK` (5) - mapped to the LVGL keypad.
+- `SPI_ID_SCREEN_FRAME` payload starts with `spi_screen_strip_t { u16 y, u16 rows,
+ u16 width }`, followed by `rows * width` little-endian RGB565 pixels. The screen
+ is streamed as horizontal row-strips; `y == 0` marks the first strip of a new
+ frame.
+
### Session (`0xFF`)
| Command | Op | `spi_id_t` |
@@ -485,15 +637,17 @@ this category to `bt_dispatcher`. See [`../host_link/`](../host_link/README.md).
## Frame Example
-The 5-byte header maps directly to `spi_header_t`:
+The 7-byte header maps directly to `spi_header_t` (`cc cc` below is the little-
+endian CRC-16, computed over `[type,category,op,length]` + data - see §3):
```c
-typedef struct {
+typedef struct __attribute__((packed)) {
uint8_t sync; // 0xAA
uint8_t type; // spi_type_t: CMD 0x01 / RESP 0x02 / STREAM 0x03
uint8_t category; // spi_cat_t
uint8_t op; // operation within the category
uint8_t length; // payload bytes that follow (0-255)
+ uint16_t crc; // CRC-16 over [type,category,op,length] + data
} spi_header_t;
```
@@ -501,23 +655,25 @@ typedef struct {
```
P4 -> C5 (command)
- AA 01 01 10 00
- ^ ^ ^ ^ ^
- | | | | +-- length = 0
- | | | +----- op = 0x10
- | | +-------- category = 0x01 (WiFi)
- | +----------- type = 0x01 (CMD)
- +-------------- sync = 0xAA
-
-C5 -> P4 (response, after raising IRQ) - payload byte 0 is the status
- AA 02 01 10 01 00
- ^ ^ ^ ^ ^ ^
- | | | | | +-- status = 0x00 (SPI_STATUS_OK)
- | | | | +----- length = 1
- | | | +-------- op = 0x10
- | | +----------- category = 0x01
- | +-------------- type = 0x02 (RESP)
- +----------------- sync = 0xAA
+ AA 01 01 10 00 cc cc
+ ^ ^ ^ ^ ^ ^--^
+ | | | | | +-- crc = CRC-16 (little-endian)
+ | | | | +------- length = 0
+ | | | +---------- op = 0x10
+ | | +------------- category = 0x01 (WiFi)
+ | +---------------- type = 0x01 (CMD)
+ +------------------- sync = 0xAA
+
+C5 -> P4 (response, after the handshake) - payload byte 0 is the status
+ AA 02 01 10 01 cc cc 00
+ ^ ^ ^ ^ ^ ^--^ ^
+ | | | | | | +-- status = 0x00 (SPI_STATUS_OK) [payload byte 0]
+ | | | | | +------- crc = CRC-16 (little-endian)
+ | | | | +---------- length = 1
+ | | | +------------- op = 0x10
+ | | +---------------- category = 0x01
+ | +------------------- type = 0x02 (RESP)
+ +---------------------- sync = 0xAA
```
Scan results are then pulled item-by-item through the **Generic Data Pipe** (`SPI_ID_SYSTEM_DATA`) described below.
@@ -538,7 +694,7 @@ per round-trip:
- The C5 buffers records in a ring (depth `SPI_STREAM_QUEUE_LEN = 64`). On a
`SPI_ID_SYSTEM_STREAM` poll it packs as many as fit into a single large frame
of `SPI_STREAM_FRAME_SIZE` (2048 B) and the P4 always clocks that fixed size.
-- Stream frame layout (after the 5-byte header, `type = STREAM`):
+- Stream frame layout (after the 7-byte header, `type = STREAM`):
`[u16 batch_len]` then `batch_len` bytes of records, each
`[u16 op][u8 len][len bytes]`. `batch_len = 0` means "no data" → the P4 backs
off and polls again later.
@@ -805,7 +961,8 @@ This component transforms the **ESP32-C5** into a high-performance radio co-proc
## How it Works
The C5 runs a background task (`spi_bridge_task`) that stays in a blocked state waiting for the P4 to send SPI bytes.
-1. **Reception**: When bytes arrive, the task validates the `0xAA` sync byte.
+1. **Reception**: When bytes arrive, the task validates the `0xAA` sync byte and
+ the frame CRC-16 (`spi_frame_valid`), dropping any corrupted frame.
2. **Routing**: It switches on the `Category` byte and routes the payload to the appropriate **Dispatcher** (WiFi or Bluetooth); the `Op` byte selects the operation within that dispatcher.
3. **Execution**: The Dispatcher executes the radio command (e.g., starts a scan).
4. **Notification**: Once the command is done (or results are ready), the C5 raises the **IRQ (Handshake)** pin.
diff --git a/docs/st7789/README.md b/docs/st7789/README.md
index 3e3af18c3..877477baa 100644
--- a/docs/st7789/README.md
+++ b/docs/st7789/README.md
@@ -9,28 +9,38 @@ This component initializes and manages the ST7789 LCD controller using the ESP-I
- **Dependencies:** `esp_lcd`, `driver/gpio`, `driver/ledc`, `spi`
## Hardware Configuration
-- **Resolution:** 240x240
+- **Resolution:** 240x320 (`LCD_H_RES` 240 / `LCD_V_RES` 320; `LCD_PANEL_W`/`LCD_PANEL_H` match).
- **Color Depth:** 16-bit (RGB565)
-- **Interface:** SPI (via `spi` component driver)
+- **Interface:** SPI on `SPI3_HOST`, 20 MHz pixel clock (`LCD_PIXEL_CLOCK_HZ`).
+
+## SPI drive-strength hardening
+`st7789_init` bumps the SPI3 `SCLK`/`MOSI` pins to `LCD_SPI_DRIVE_CAP`
+(`GPIO_DRIVE_CAP_3`, the strongest) via `gpio_set_drive_capability`. The display
+FFC is long, capacitive and unterminated: at the IDF default the 20 MHz edges
+barely settle, so a radio's EMI corrupts the still-settling edge and garbles long
+transfers. Do not lower this - a weaker cap starves the FFC so hard the panel
+will not even init.
## Internal Backlight Control
-Although a separate `backlight` component exists, this driver currently includes its own internal PWM initialization (`init_backlight_pwm`) and control logic using `LEDC_TIMER_0` / `LEDC_CHANNEL_0`.
-*Note: This overlaps with the standalone `backlight` component. Verify project integration to avoid timer conflicts.*
+This driver owns its own PWM backlight init (`init_backlight_pwm`) and control
+logic using `LEDC_TIMER_0` / `LEDC_CHANNEL_0`, 13-bit resolution at 5 kHz on
+`GPIO_ST7789_BL_PIN`.
## API Reference
### `st7789_init`
```c
-void st7789_init(void);
+esp_err_t st7789_init(void);
```
-Initializes the display.
-1. Creates the SPI device interface on `SPI3_HOST`.
-2. Configures the ST7789 panel (Reset pin, RGB order, etc.).
-3. Resets and initializes the panel.
-4. Inverts colors (standard for many ST7789 IPS panels).
-5. Turns the display ON.
-6. Initializes the backlight PWM and **applies** (does not re-save) the saved
- brightness/rotation.
+Initializes the display. Returns `ESP_OK`, or the failing `esp_err_t` from the
+panel IO / panel create / reset / init step (cleaning up any handles it created).
+1. Creates the SPI panel IO on `SPI3_HOST`.
+2. Hardens the SPI3 `SCLK`/`MOSI` drive strength (see above).
+3. Configures the ST7789 panel (Reset pin, RGB order, etc.).
+4. Resets and initializes the panel.
+5. Inverts colors (standard for many ST7789 IPS panels).
+6. Turns the display ON.
+7. Initializes the backlight PWM and applies the saved brightness/rotation.
### `lcd_apply_brightness`
```c
@@ -54,6 +64,20 @@ uint8_t lcd_get_brightness(void);
```
Reads the persisted brightness back from the config file.
+### `lcd_set_rotation`
+```c
+void lcd_set_rotation(uint8_t rotation);
+```
+Sets the panel rotation (index `1`-`4`, clamped). Applies the matching
+mirror / swap-xy / gap for the 240x320 panel and persists the value. Rotations
+3 and 4 apply a `ROTATION_GAP_OFFSET` (80) to line the visible window up.
+
+### `lcd_get_rotation`
+```c
+uint8_t lcd_get_rotation(void);
+```
+Reads the persisted rotation index (1-4) back from the config file.
+
### `lcd_display_sleep`
```c
void lcd_display_sleep(bool sleep);
diff --git a/docs/storage_api/README.md b/docs/storage_api/README.md
index 4bfe5897d..d7a77c689 100644
--- a/docs/storage_api/README.md
+++ b/docs/storage_api/README.md
@@ -291,6 +291,38 @@ storage_append_csv_row("/data/sensors.csv", row, 3);
---
+## Atomic Writes
+
+Header: `storage_atomic.h`
+
+For config files where a crash mid-write must never leave a half-written file,
+use the atomic replace. It writes to `.tmp`, flushes and fsyncs it, then
+renames it over the destination. On FAT (where rename cannot replace an existing
+target) it unlinks the target first and retries. The original file is untouched
+on any failure before the rename, and if power is lost between the unlink and the
+rename the complete `.tmp` survives for the loader to recover. Works on both
+FAT (SD) and LittleFS (flash) mounts.
+
+```c
+#include "storage_atomic.h"
+
+esp_err_t storage_write_atomic(const char *path, const void *data, size_t len);
+```
+
+| Return | Meaning |
+|--------|---------|
+| `ESP_OK` | File replaced atomically |
+| `ESP_ERR_INVALID_ARG` | Bad arguments (`path` NULL, or `data` NULL with non-zero `len`) |
+| `ESP_ERR_INVALID_SIZE` | The temp path does not fit |
+| `ESP_FAIL` | I/O failure (destination preserved unless recovery applies) |
+
+```c
+// Replace a config file so a crash never leaves it half-written
+storage_write_atomic(TOS_PATH_CONFIG_SYSTEM, json, strlen(json));
+```
+
+---
+
## Stream I/O
Header: `storage_stream.h`
diff --git a/docs/storage_assets/README.md b/docs/storage_assets/README.md
index 0d84294fc..66d3b9bef 100644
--- a/docs/storage_assets/README.md
+++ b/docs/storage_assets/README.md
@@ -32,20 +32,52 @@ This component provides read-only access to a dedicated LittleFS partition for s
### Partition Table
-The assets partition must be defined in your partition table (`partitions.csv`):
+The assets partition is defined in the firmware partition table
+(`firmware_p4/partitions.csv` and `firmware_c5/partitions.csv`). The layout is
+OTA-based: there is no `factory` app partition and no separate `storage` data
+partition. Two app slots (`ota_0` / `ota_1`) hold the A/B firmware images, a
+`coredump` partition captures crash dumps, and `assets` is a read-only LittleFS
+image. The P4 and C5 differ in slot sizes and ordering.
+
+**P4 (`firmware_p4/partitions.csv`):**
```csv
-# Name, Type, SubType, Offset, Size, Flags
-nvs, data, nvs, 0x9000, 0x6000,
-phy_init, data, phy, 0xf000, 0x1000,
-factory, app, factory, 0x10000, 1M,
-assets, data, spiffs, 0x110000, 512K,
-storage, data, spiffs, 0x190000, 1M,
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 24K,
+otadata, data, ota, 0xf000, 8K,
+phy_init, data, phy, 0x11000, 4K,
+ota_0, app, ota_0, 0x20000, 0x270000,
+ota_1, app, ota_1, 0x290000, 0x270000,
+coredump, data, coredump, 0x500000, 64K,
+assets, data, littlefs, 0x510000, 0x2E0000,
```
+`assets` is `0x2E0000` bytes (~2.9 MB) at offset `0x510000`.
+
+**C5 (`firmware_c5/partitions.csv`):**
+
+```csv
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 24K,
+otadata, data, ota, 0xf000, 8K,
+phy_init, data, phy, 0x11000, 4K,
+ota_0, app, ota_0, 0x20000, 2M,
+ota_1, app, ota_1, 0x220000, 2M,
+assets, data, littlefs, 0x420000, 0x1E0000,
+coredump, data, coredump, 0x600000, 64K,
+```
+
+`assets` is `0x1E0000` bytes (~1.9 MB) at offset `0x420000`. Note the ordering
+differs from the P4: on the C5, `assets` comes before `coredump`.
+
**Important Notes:**
-- The SubType must be `spiffs` (even though we use LittleFS - this is an ESP-IDF quirk).
-- Size should be sufficient for all your assets (adjust as needed).
+- The SubType is `littlefs` (the image is mounted with esp_littlefs). This
+ project uses the native `littlefs` subtype, not the older `spiffs`-subtype
+ workaround.
+- A `coredump` partition (64K) is reserved for crash dumps; it is separate from
+ the assets and app storage.
+- There is no `factory` app partition and no separate `storage` data partition;
+ firmware lives in the `ota_0` / `ota_1` slots.
- The partition must be flashed before use.
### Constants
@@ -102,7 +134,7 @@ void app_main(void) {
```
I (1234) storage_assets: Initializing LittleFS for assets partition
I (1245) storage_assets: Assets ready at /assets
-I (1246) storage_assets: Partition size: 524288 bytes, used: 12345 bytes
+I (1246) storage_assets: Partition size: 3014656 bytes, used: 12345 bytes
I (1247) storage_assets: === Files in assets partition ===
I (1248) storage_assets: [1] logo.bin (1200 bytes)
I (1249) storage_assets: [DIR] fonts/
@@ -304,10 +336,10 @@ Prints detailed information about the assets partition to the console.
I (1234) storage_assets: === Assets Partition Info ===
I (1235) storage_assets: Mount point: /assets
I (1236) storage_assets: Partition: assets
-I (1237) storage_assets: Total size: 524288 bytes (512.00 KB)
+I (1237) storage_assets: Total size: 3014656 bytes (2944.00 KB)
I (1238) storage_assets: Used: 98765 bytes (96.45 KB)
-I (1239) storage_assets: Free: 425523 bytes (415.55 KB)
-I (1240) storage_assets: Usage: 18.8%
+I (1239) storage_assets: Free: 2915891 bytes (2847.55 KB)
+I (1240) storage_assets: Usage: 3.3%
```
**Usage:**
@@ -548,7 +580,7 @@ project/
**Solution:**
1. Add partition to `partitions.csv`:
```csv
- assets, data, spiffs, 0x110000, 512K,
+ assets, data, littlefs, 0x510000, 0x2E0000,
```
2. Set partition table in `sdkconfig`:
```
@@ -655,20 +687,52 @@ This component provides read-only access to a dedicated LittleFS partition for s
### Partition Table
-The assets partition must be defined in your partition table (`partitions.csv`):
+The assets partition is defined in the firmware partition table
+(`firmware_p4/partitions.csv` and `firmware_c5/partitions.csv`). The layout is
+OTA-based: there is no `factory` app partition and no separate `storage` data
+partition. Two app slots (`ota_0` / `ota_1`) hold the A/B firmware images, a
+`coredump` partition captures crash dumps, and `assets` is a read-only LittleFS
+image. The P4 and C5 differ in slot sizes and ordering.
+
+**P4 (`firmware_p4/partitions.csv`):**
```csv
-# Name, Type, SubType, Offset, Size, Flags
-nvs, data, nvs, 0x9000, 0x6000,
-phy_init, data, phy, 0xf000, 0x1000,
-factory, app, factory, 0x10000, 1M,
-assets, data, spiffs, 0x110000, 512K,
-storage, data, spiffs, 0x190000, 1M,
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 24K,
+otadata, data, ota, 0xf000, 8K,
+phy_init, data, phy, 0x11000, 4K,
+ota_0, app, ota_0, 0x20000, 0x270000,
+ota_1, app, ota_1, 0x290000, 0x270000,
+coredump, data, coredump, 0x500000, 64K,
+assets, data, littlefs, 0x510000, 0x2E0000,
```
+`assets` is `0x2E0000` bytes (~2.9 MB) at offset `0x510000`.
+
+**C5 (`firmware_c5/partitions.csv`):**
+
+```csv
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 24K,
+otadata, data, ota, 0xf000, 8K,
+phy_init, data, phy, 0x11000, 4K,
+ota_0, app, ota_0, 0x20000, 2M,
+ota_1, app, ota_1, 0x220000, 2M,
+assets, data, littlefs, 0x420000, 0x1E0000,
+coredump, data, coredump, 0x600000, 64K,
+```
+
+`assets` is `0x1E0000` bytes (~1.9 MB) at offset `0x420000`. Note the ordering
+differs from the P4: on the C5, `assets` comes before `coredump`.
+
**Important Notes:**
-- The SubType must be `spiffs` (even though we use LittleFS - this is an ESP-IDF quirk).
-- Size should be sufficient for all your assets (adjust as needed).
+- The SubType is `littlefs` (the image is mounted with esp_littlefs). This
+ project uses the native `littlefs` subtype, not the older `spiffs`-subtype
+ workaround.
+- A `coredump` partition (64K) is reserved for crash dumps; it is separate from
+ the assets and app storage.
+- There is no `factory` app partition and no separate `storage` data partition;
+ firmware lives in the `ota_0` / `ota_1` slots.
- The partition must be flashed before use.
### Constants
@@ -725,7 +789,7 @@ void app_main(void) {
```
I (1234) storage_assets: Initializing LittleFS for assets partition
I (1245) storage_assets: Assets ready at /assets
-I (1246) storage_assets: Partition size: 524288 bytes, used: 12345 bytes
+I (1246) storage_assets: Partition size: 3014656 bytes, used: 12345 bytes
I (1247) storage_assets: === Files in assets partition ===
I (1248) storage_assets: [1] logo.bin (1200 bytes)
I (1249) storage_assets: [DIR] fonts/
@@ -927,10 +991,10 @@ Prints detailed information about the assets partition to the console.
I (1234) storage_assets: === Assets Partition Info ===
I (1235) storage_assets: Mount point: /assets
I (1236) storage_assets: Partition: assets
-I (1237) storage_assets: Total size: 524288 bytes (512.00 KB)
+I (1237) storage_assets: Total size: 3014656 bytes (2944.00 KB)
I (1238) storage_assets: Used: 98765 bytes (96.45 KB)
-I (1239) storage_assets: Free: 425523 bytes (415.55 KB)
-I (1240) storage_assets: Usage: 18.8%
+I (1239) storage_assets: Free: 2915891 bytes (2847.55 KB)
+I (1240) storage_assets: Usage: 3.3%
```
**Usage:**
@@ -1171,7 +1235,7 @@ project/
**Solution:**
1. Add partition to `partitions.csv`:
```csv
- assets, data, spiffs, 0x110000, 512K,
+ assets, data, littlefs, 0x510000, 0x2E0000,
```
2. Set partition table in `sdkconfig`:
```
diff --git a/docs/storage_vfs/README.md b/docs/storage_vfs/README.md
index 68faedef5..401b17ab3 100644
--- a/docs/storage_vfs/README.md
+++ b/docs/storage_vfs/README.md
@@ -504,7 +504,14 @@ esp_err_t vfs_sdcard_init(void);
esp_err_t vfs_sdcard_deinit(void);
bool vfs_sdcard_is_mounted(void);
void vfs_sdcard_print_info(void);
+bool vfs_sdcard_get_name(char *out, size_t n);
esp_err_t vfs_sdcard_format(void);
+esp_err_t vfs_register_sd_backend(void);
+esp_err_t vfs_unregister_sd_backend(void);
+
+// Raw handoff for USB Mass Storage (see "USB Mass Storage (MSC)" below)
+esp_err_t vfs_sdcard_detach_for_msc(void **out_card);
+esp_err_t vfs_sdcard_reattach_after_msc(void *card);
```
### LittleFS Backend
@@ -521,6 +528,70 @@ esp_err_t vfs_littlefs_format(void);
---
+## USB Mass Storage (MSC)
+
+> P4 only. The MSC path lives in `storage_vfs`
+> (`usb_msc.c` / `vfs_sdcard.c`, headers `include/usb_msc.h` and
+> `include/vfs_sdcard.h`). The C5 has no USB connector or SD card and does not
+> build these.
+
+USB Mass Storage mode exposes the microSD directly to a host PC as a USB drive.
+Because a host filesystem and the firmware's FAT mount cannot own the card at
+the same time, entering MSC hands the raw block device over to the USB stack and
+tears down the app's `/sdcard` mount; leaving MSC does the reverse and resumes
+normal operation **without a reboot** (it only reboots if the remount fails).
+
+### Control API (`usb_msc.h`)
+
+```c
+#include "usb_msc.h"
+
+typedef enum {
+ USB_MSC_IDLE = 0, // Not in USB-storage mode
+ USB_MSC_ENTERING, // Detaching SD / bringing USB up
+ USB_MSC_ACTIVE, // SD exposed to the host as a USB drive
+ USB_MSC_ERROR, // Could not enter (SD restored, safe to leave)
+ USB_MSC_EXITING, // Tearing down / remounting SD
+} usb_msc_state_t;
+
+usb_msc_state_t usb_msc_get_state(void); // poll from the UI
+bool usb_msc_host_connected(void); // true while the host has it mounted
+void usb_msc_enter(void); // blocking - run on a worker task
+void usb_msc_exit(void); // blocking - run on a worker task
+```
+
+- `usb_msc_enter()` detaches the app's `/sdcard` FAT, exposes the raw card to the
+ host, and switches the USB mux to native. On failure the SD is restored and the
+ state goes to `USB_MSC_ERROR`.
+- `usb_msc_exit()` stops exposing the card, remounts `/sdcard`, and routes the USB
+ connector back to the UART bridge. It resumes without a reboot; only a failed
+ remount forces a reboot to recover.
+- Both `usb_msc_enter()` and `usb_msc_exit()` are **blocking** - never call them
+ from the LVGL thread; drive them from a worker task and poll `usb_msc_get_state()`.
+
+### Raw SD handoff (`vfs_sdcard.h`)
+
+MSC is implemented on top of two raw-handoff helpers in the SD backend that give
+up and reclaim the physical card around the USB stack:
+
+```c
+// Give up the app's FAT mount and hand back the SD as a RAW, still-powered block
+// device for USB MSC. The card is re-initialized (never formatted).
+// out_card receives an sdmmc_card_t* (as void*) on success.
+esp_err_t vfs_sdcard_detach_for_msc(void **out_card);
+
+// Undo the detach: release the raw SDMMC card+host handed to MSC and remount the
+// app FAT at /sdcard. Call AFTER the MSC storage layer is torn down. Lets the
+// firmware resume without a reboot. `card` is the handle from the detach call.
+esp_err_t vfs_sdcard_reattach_after_msc(void *card);
+```
+
+`vfs_sdcard_detach_for_msc()` leaves the app FAT mount detached on failure;
+`vfs_sdcard_reattach_after_msc()` performs the remount that returns the card to
+the firmware.
+
+---
+
## Switching Backends
To switch between storage backends, edit `vfs_config.h`:
diff --git a/docs/sx1262/README.md b/docs/sx1262/README.md
new file mode 100644
index 000000000..cabf359df
--- /dev/null
+++ b/docs/sx1262/README.md
@@ -0,0 +1,164 @@
+# SX1262 LoRa Transceiver Driver
+
+This component provides a driver for the Semtech SX1262 sub-GHz LoRa transceiver. It handles the full SX1262 command set over SPI, LoRa modulation/packet configuration, TX/RX (single, continuous, and duty-cycle), Channel Activity Detection (CAD), sleep/wake power management, and interrupt-driven packet reception with a ring buffer.
+
+The driver core is platform-agnostic: all hardware access is delegated through a HAL callback struct (`sx1262_hal_t`), and the ESP32 port lives in a single file. This keeps the register/command logic portable and testable.
+
+## Overview
+
+- **Location:** `components/Drivers/sx1262/`
+- **Public header:** `include/sx1262.h` (types in `include/sx1262_types.h`)
+- **Dependencies:** `spi`, `pins` (`pin_def.h`), `sys_prio`, `driver/spi_master`, `driver/gpio`, `freertos`, `esp_log`
+- **Interface:** SPI3_HOST at 4 MHz, SPI mode 0 (shared bus - see below)
+- **Reference:** register/opcode comments cite the SX1262 datasheet (DS) sections
+
+## File Layout
+
+| File | Responsibility |
+|------|----------------|
+| `sx1262.c` | Public API, init/bring-up sequence, config, IRQ task, recovery |
+| `sx1262_cmd.c` | Low-level SPI opcode/register/buffer access and BUSY-pin waiting |
+| `sx1262_fsm.c` | Radio state machine (tracks `sx1262_state_t`) |
+| `sx1262_hal.c` | ESP32 HAL port: SPI device, GPIO, mutex, bus lock, antenna switch |
+| `sx1262_irq.c` | IRQ dispatch, RX packet read, RX ring buffer |
+| `sx1262_radio.c` | TX/RX/CAD/sleep/wake/duty-cycle operations |
+| `sx1262_regs.h` | Opcodes, register addresses, LoRa parameter constants |
+
+## Shared SPI3 Bus Contract (important)
+
+The SX1262 sits on **SPI3_HOST**, the same bus as the **ST7789 display**. Because two independent drivers share one bus, every SX1262 transaction is serialized through a layered lock in the HAL (`hal_lock` / `hal_unlock` in `sx1262_hal.c`):
+
+1. Take the SX1262 device mutex (`spi_mutex`, `portMAX_DELAY`).
+2. Take the shared SPI3 bus lock via `spi_bus_lock_take(SPI3_BUS_LOCK_TIMEOUT_MS)` (1000 ms) - this is the cross-component handshake with the display driver.
+3. `spi_device_acquire_bus()` for the duration of the transaction.
+
+Unlock reverses the order (`release_bus` -> `spi_bus_give` -> give mutex). The bus lock is taken/released per transaction so the display is never starved.
+
+Bus lifecycle:
+
+- `sx1262_hal_create()` calls `spi_bus_initialize(SPI3_HOST, ...)`. If the bus is **already initialized** by another driver (the ST7789 display, via the kernel `spi_init`), the returned `ESP_ERR_INVALID_STATE` is treated as success - the SX1262 simply adds itself as a second device.
+- `sx1262_hal_destroy()` removes only the SX1262 device and deletes the device mutex. **It never frees the SPI3 bus**, because the display still needs it. `sx1262_deinit()` -> `sx1262_hal_destroy()` therefore leaves the display fully functional.
+
+## RX Robustness / Hardening
+
+The RX path is hardened against errored packets and a flaky/contended SPI3 bus:
+
+- **Retry bring-up until STDBY_RC.** `sx1262_init()` runs `sx1262_hw_bringup()` up to `SX1262_INIT_MAX_ATTEMPTS` (3) times. Bring-up performs reset -> standby -> DCDC regulator -> full calibration -> image calibration -> workarounds -> LoRa config, then reads the chip status and **only succeeds if the chip actually landed in STDBY_RC** (`chip_mode == STDBY_RC`). Any device error reported after calibration also fails the attempt, triggering a retry.
+- **IRQ-task self-recovery.** The IRQ task (`irq_task`) counts consecutive `sx1262_irq_process()` failures; after `SX1262_IRQ_FAIL_RECOVER` (5) in a row it calls `sx1262_recover()`, which does a hardware reset + full reconfigure (`sx1262_hw_bringup`) and resumes continuous RX. A single success resets the streak.
+- **Errored packets do not read the FIFO.** In `read_rx_packet()`, if the RxDone IRQ arrives with CRC-error or header-error flags set, the payload buffer is **not** read (`len = 0`), avoiding acting on corrupt FIFO contents. RSSI/SNR and the `has_crc_error` / `has_header_error` flags are still populated so the caller can observe the failure. Standalone CRC/header errors (no RxDone) are surfaced through the `on_error` callback.
+- **Demoted RX-error hot-path logs.** RxDone, CRC error, header error, timeout, CAD, and ring-buffer-full messages log at `ESP_LOGD` (debug), not error/warning. This keeps the hot path quiet under noisy conditions instead of flooding the console on every errored packet.
+- **Bounded RX ring buffer.** Received packets are pushed into a fixed ring of `SX1262_RX_RING_SIZE` (8) entries; a full ring drops the newest packet (debug log). Ring access is guarded by the HAL critical section, so `sx1262_get_packet()` is safe against the IRQ task.
+
+## Radio Parameters (`sx1262_config_t`)
+
+```c
+typedef struct {
+ sx1262_hal_t hal; // Platform HAL - all callbacks
+ uint32_t frequency_hz; // 150_000_000 .. 960_000_000 Hz
+ uint8_t sf; // SF5 .. SF12
+ uint8_t bw; // BW_7 .. BW_500
+ uint8_t cr; // CR_4_5 .. CR_4_8
+ int8_t tx_power_dbm; // -9 .. +22 dBm
+ uint16_t preamble_len; // preamble symbols (min 2, 12+ recommended)
+ bool is_crc_on;
+ bool is_inverted_iq; // true = LoRaWAN downlink (activates workaround W4)
+ bool is_implicit_hdr; // true = implicit header (activates W3); SF6 requires this
+ bool is_public_network; // sync word 0x3444 (public) vs 0x1424 (private/Meshtastic)
+} sx1262_config_t;
+```
+
+Validation (`validate_config`) rejects out-of-range values before any SPI transaction. Notable rules: `BW_500` requires `SF >= 6`, and `SF6` requires implicit-header mode. LDRO (low data rate optimize) is derived automatically for `SF11/SF12` at `BW <= 125 kHz`.
+
+## Pin / Hardware Configuration
+
+Pins come from `pin_def.h` (`components/Drivers/pins`):
+
+| Signal | Macro | GPIO |
+|--------|-------|------|
+| SCLK | `GPIO_LORA_SCLK_PIN` | 21 |
+| MOSI | `GPIO_LORA_MOSI_PIN` | 22 |
+| MISO | `GPIO_LORA_MISO_PIN` | 23 |
+| NSS/CS | `GPIO_LORA_CS_PIN` | 26 |
+| BUSY | `GPIO_LORA_BUSY_PIN` | 4 |
+| DIO1 | `GPIO_LORA_DIO1_PIN` | 5 |
+| NRESET | `GPIO_LORA_RESET_PIN` | -1 (not wired) |
+| TXEN | `GPIO_LORA_TXEN_PIN` | -1 (not wired) |
+| RXEN | `GPIO_LORA_RXEN_PIN` | -1 (not wired) |
+
+`-1` pins are skipped by the HAL. With no discrete TX/RX antenna-switch pins, the RF switch is driven by the chip itself via **DIO2 as RF switch** (`SetDIO2AsRfSwitch`, enabled during bring-up). NSS is toggled in software (the SPI device is configured with `spics_io_num = -1`). BUSY and DIO1 are inputs; DIO1 signals IRQs. Reset/wait timings: `SX1262_RESET_HOLD_MS` (2), `SX1262_RESET_WAIT_MS` (20); BUSY polling caps at `SX1262_WAIT_BUSY_TIMEOUT_MS` (100).
+
+## HAL Contract (`sx1262_hal_t`)
+
+The driver core never includes platform headers. Porting means implementing the callback struct: `spi_transfer`, `cs_low` / `cs_high`, `reset_write`, `busy_read`, `delay_ms`, `get_tick_ms`, `lock` / `unlock`, `enter_critical` / `exit_critical`, `set_antenna`, and an opaque `ctx`. `sx1262_hal_create()` populates it for the ESP32; a different platform supplies its own file.
+
+## API Reference
+
+### Lifecycle
+
+```c
+esp_err_t sx1262_init(const sx1262_config_t *config);
+esp_err_t sx1262_deinit(void);
+esp_err_t sx1262_start(void);
+void sx1262_stop(void);
+esp_err_t sx1262_config_lora(const sx1262_config_t *config);
+esp_err_t sx1262_set_callbacks(const sx1262_callbacks_t *cbs);
+bool sx1262_is_running(void);
+```
+
+- `sx1262_init` validates HAL + config, then runs the retrying bring-up (see hardening). Returns `ESP_ERR_INVALID_ARG` on bad config/HAL, or the last bring-up error after all attempts fail.
+- `sx1262_start` creates the DIO1 IRQ processing task (stack 4096, `SYS_PRIO_REALTIME`, `SYS_CORE_RADIO` = core 0). `ESP_ERR_INVALID_STATE` if already running or not initialized; `ESP_ERR_NO_MEM` on task-create failure.
+- `sx1262_stop` signals the IRQ task and waits (up to 500 ms) for it to exit via task notification, then turns the antenna switch off.
+- `sx1262_config_lora` reconfigures LoRa parameters at runtime; re-applies workaround W4 (IQ polarity) every call. The stored HAL is preserved across the config copy.
+
+### Callbacks (`sx1262_callbacks_t`)
+
+```c
+esp_err_t sx1262_set_callbacks(const sx1262_callbacks_t *cbs);
+```
+
+Registers `on_tx_done`, `on_rx_done`, `on_cad_done`, `on_timeout`, `on_error` (each may be NULL) plus a shared `cb_ctx`. Callbacks fire from the IRQ task, not an ISR.
+
+### TX / RX
+
+```c
+esp_err_t sx1262_transmit(const uint8_t *data, uint8_t len, uint32_t timeout_ms);
+esp_err_t sx1262_receive_single(uint32_t timeout_ms);
+esp_err_t sx1262_receive_continuous(void);
+void sx1262_stop_rx(void);
+esp_err_t sx1262_get_packet(sx1262_packet_t *out_packet);
+```
+
+- `sx1262_transmit` is non-blocking (payload 1..255 bytes); completion arrives via `on_tx_done`. Applies workaround W1 (BW500 sensitivity) before each TX.
+- `sx1262_receive_single` returns to STDBY after one packet or timeout (`timeout_ms = 0` = wait forever); `sx1262_receive_continuous` stays in RX until `sx1262_stop_rx()`.
+- `sx1262_get_packet` dequeues from the RX ring; `ESP_ERR_NOT_FOUND` when empty.
+
+Received packet (`sx1262_packet_t`) carries `buf[256]`, `len`, `rssi_pkt_dbm`, `snr_pkt_db`, `signal_rssi_dbm`, `has_crc_error`, and `has_header_error`.
+
+### CAD & Power
+
+```c
+esp_err_t sx1262_cad_start(void);
+esp_err_t sx1262_sleep(bool is_warm);
+esp_err_t sx1262_wakeup(void);
+esp_err_t sx1262_set_rx_duty_cycle(uint32_t rx_ms, uint32_t sleep_ms);
+```
+
+- `sx1262_sleep(false)` (cold start) marks the driver as needing re-init before the next TX/RX; `true` is a warm start that retains config.
+- `sx1262_set_rx_duty_cycle` runs wake-on-radio (RX window then sleep window, repeating in hardware).
+
+### Status / Diagnostics
+
+```c
+sx1262_state_t sx1262_get_state(void);
+esp_err_t sx1262_get_status(uint8_t *out_status);
+esp_err_t sx1262_get_device_errors(uint16_t *out_errors);
+esp_err_t sx1262_get_rssi_inst(int16_t *out_rssi_dbm);
+esp_err_t sx1262_get_stats(sx1262_stats_t *out_stats);
+esp_err_t sx1262_process_irq(void);
+```
+
+`sx1262_get_stats` returns cumulative counters (`nb_pkt_received`, `nb_crc_error`, `nb_header_error`). `sx1262_process_irq` is called by the IRQ task when DIO1 rises; it is not for direct ISR use.
+
+## Chip Workarounds
+
+The driver applies the datasheet errata workarounds automatically: **W1** (BW500 sensitivity, before each TX), **W2** (TX clamp config, during bring-up), **W3** (implicit-header RX, when `is_implicit_hdr`), and **W4** (IQ polarity, on every `config_lora`).
diff --git a/docs/sys_monitor/README.md b/docs/sys_monitor/README.md
index bd813037e..1c23bd983 100644
--- a/docs/sys_monitor/README.md
+++ b/docs/sys_monitor/README.md
@@ -22,12 +22,29 @@ kills tasks.
(`esp_restart()`), preceded by a log, a UI alert and a short grace delay.
5. Tasks that recover or exit drop out of the table, so a fresh dip starts a new
streak instead of inheriting a stale one.
-6. It is also the **Task Watchdog heartbeat** (`esp_task_wdt_add`/`reset` each
+6. **Heap watch** (`check_heap()`): reads internal free and the largest
+ contiguous internal block. When either drops low
+ (`HEAP_WARN_FREE_B` / `HEAP_WARN_LARGEST_B`) it warns once, evicts the image
+ cache via `assets_manager_evict_cache()` to reclaim RAM, then shows one UI
+ alert. If total internal free stays under `HEAP_CRIT_FREE_B` for
+ `HEAP_CRIT_CYCLES` consecutive cycles it does a controlled restart. Both the
+ warn latch and the critical streak reset once free RAM recovers.
+7. **SD health watch** (`check_storage_health()`, every `STORAGE_CHECK_CYCLES`
+ cycles ~= 60 s): when the SD is mounted and `storage_check_health()` fails, it
+ requests a remount via `header_ui_request_sd_remount()`.
+8. **I2C recovery watch** (`check_i2c_health()`): polls `i2c_recover_count()` and
+ logs when the bus recovery count advances (the I2C driver self-recovered a
+ stuck bus). Observe-and-report only; no escalation.
+9. It is also the **Task Watchdog heartbeat** (`esp_task_wdt_add`/`reset` each
cycle) and the **UI render supervisor**: it polls `ui_render_beat()` (a beat
- bumped by an `lv_timer` inside the LVGL task) and, if the beat stalls for a
- couple of cycles, does the same controlled restart. This replaces the old
- "watch a task called `UI Task` by name" logic - it watches the renderer's
- actual progress, not a task name.
+ bumped by an `lv_timer` inside the LVGL task) and, if the beat does not move
+ for `UI_STALL_ESCALATE_CYCLES` cycles (8, ~16 s), does the same controlled
+ restart. The tolerance is intentionally generous: a legitimately slow but
+ blocking op on the UI thread (e.g. a multi-second `wifi scan` run under the
+ LVGL lock) freezes the renderer for a few seconds without being deadlocked, so
+ only a genuinely stuck UI should reboot. This replaces the old "watch a task
+ called `UI Task` by name" logic - it watches the renderer's actual progress,
+ not a task name.
`usStackHighWaterMark` is monotonic (it records the lowest free stack ever seen,
and never rises again), so a sustained streak means the task is alive and running
@@ -56,12 +73,14 @@ escalation is the controlled restart above.
| `CRITICAL_STACK_THRESHOLD` | 256 | Free-stack floor (bytes) that flags a task |
| `STACK_ESCALATE_CYCLES` | 5 | Consecutive critical cycles before a controlled restart |
| `STACK_WATCH_MAX` | 8 | Distinct critical tasks tracked for persistence |
+| `UI_STALL_ESCALATE_CYCLES` | 8 | Cycles with no render-beat progress (~16 s) before a controlled restart |
+| `HEAP_WARN_FREE_B` | 24576 | Internal free below this warns once |
+| `HEAP_WARN_LARGEST_B` | 12288 | Largest contiguous internal block below this warns once |
+| `HEAP_CRIT_FREE_B` | 8192 | Internal free below this (sustained) escalates |
+| `HEAP_CRIT_CYCLES` | 3 | Consecutive critical-heap cycles before a controlled restart |
+| `STORAGE_CHECK_CYCLES` | 30 | SD-health probe cadence (cycles, ~60 s) |
| `REBOOT_GRACE_MS` | 1500 | Delay after the alert so it renders / logs flush |
-> The controlled restart has a `TODO(item 31)` hook: once the graceful shutdown
-> path exists it should run there so the filesystem and radios flush their state
-> before `esp_restart()`.
-
---
## ESP32-C5 (`firmware_c5`)
diff --git a/docs/sys_time/README.md b/docs/sys_time/README.md
new file mode 100644
index 000000000..520447564
--- /dev/null
+++ b/docs/sys_time/README.md
@@ -0,0 +1,92 @@
+# System Time - P4
+
+Central wall-clock owner for `firmware_p4`. The HighBoy V2 has no battery-backed RTC, so wall-clock time is volatile across power cycles: the P4's internal RTC timer runs while powered but resets on power-off. Time must be injected from an external source every power cycle (console, companion app over `host_link`, or SNTP via the C5) and is maintained by the internal timer until the next power loss. The whole system runs in UTC.
+
+## Overview
+
+- **Location:** `firmware_p4/components/Service/sys_time/`
+- **Header:** `include/sys_time.h`
+- **Source:** `sys_time.c`
+- **Dependencies:** `esp_log`, `nvs`, `esp_err`, libc `` / ``
+- **Persistence:** NVS namespace `systime` (keys `last_epoch`, `last_src`)
+
+## Build-date baseline
+
+So nothing is ever dated to 1970, `sys_time_init` seeds the clock with a baseline instead of leaving it at epoch 0:
+
+1. It derives a build-date epoch from the compiler `__DATE__` / `__TIME__` macros (`sys_time_build_epoch`). The device cannot predate its own image, so the build date is a safe floor. If parsing the macros fails, it falls back to `SYS_TIME_EPOCH_MIN`.
+2. It reads the last-known time persisted in NVS (`sys_time_saved_epoch`).
+3. The baseline is raised to the NVS value **only if** the saved epoch is `>= SYS_TIME_EPOCH_MIN`, otherwise the build-date floor stands.
+4. The timezone is fixed to `UTC0`, `settimeofday` applies the baseline, and the state is left at `SYS_TIME_STATE_ESTIMATED` with source `SYS_TIME_SOURCE_NONE`.
+
+The clock stays `ESTIMATED` (a floor, not a real time) until a real source calls `sys_time_set`, which promotes it to `SYS_TIME_STATE_SYNCED`.
+
+## The `SYS_TIME_EPOCH_MIN` guard
+
+`SYS_TIME_EPOCH_MIN` is `1735689600` (2025-01-01 00:00:00 UTC). It is used in two places:
+
+- **Baseline selection:** a persisted NVS epoch below the guard is treated as garbage and ignored, keeping the build-date floor.
+- **Rejecting bad sets:** `sys_time_set` returns `ESP_ERR_INVALID_ARG` for any `epoch < SYS_TIME_EPOCH_MIN`, so an implausibly early value can never sync the clock.
+
+## API Reference
+
+### States and sources
+
+```c
+typedef enum {
+ SYS_TIME_STATE_ESTIMATED = 0, // baseline (build date or last-known NVS); a floor
+ SYS_TIME_STATE_SYNCED, // set from a real external source; time() trustworthy
+} sys_time_state_t;
+
+typedef enum {
+ SYS_TIME_SOURCE_NONE = 0,
+ SYS_TIME_SOURCE_MANUAL, // console `date` command
+ SYS_TIME_SOURCE_HOST, // companion app over host_link
+ SYS_TIME_SOURCE_SNTP, // NTP over WiFi (via the C5)
+} sys_time_source_t;
+```
+
+### `sys_time_init`
+```c
+void sys_time_init(void);
+```
+Fixes the timezone to UTC and seeds the clock to the later of the firmware build date and the last-known NVS time, leaving state at `SYS_TIME_STATE_ESTIMATED`. Call once at boot.
+
+### `sys_time_set`
+```c
+esp_err_t sys_time_set(time_t epoch, sys_time_source_t source);
+```
+Apply a wall-clock time from a real source. Sets the clock, marks it `SYNCED`, records `source`, and persists the value to NVS. Returns `ESP_ERR_INVALID_ARG` if `epoch` is below `SYS_TIME_EPOCH_MIN`, `ESP_FAIL` if `settimeofday` fails, `ESP_OK` on success.
+
+### `sys_time_state`
+```c
+sys_time_state_t sys_time_state(void);
+```
+Current trust level of the clock.
+
+### `sys_time_source`
+```c
+sys_time_source_t sys_time_source(void);
+```
+Source of the last successful sync.
+
+### `sys_time_now`
+```c
+time_t sys_time_now(void);
+```
+Current wall-clock (UTC); at least the boot baseline.
+
+### `sys_time_format`
+```c
+bool sys_time_format(char *out, size_t out_size, const char *fmt);
+```
+Format the current time with `strftime` (UTC fields) into `out`. Sets `out` to `""` on error. Returns `false` on a bad argument or if nothing was written.
+
+## Tunables
+
+| Symbol | Value | Meaning |
+|--------|-------|---------|
+| `SYS_TIME_EPOCH_MIN` | `1735689600` | Plausibility floor (2025-01-01 UTC): rejects early sets and stale NVS baselines. |
+| `SYS_TIME_NVS_NS` | `"systime"` | NVS namespace for last-known time. |
+| `SYS_TIME_NVS_KEY_EPOCH` | `"last_epoch"` | NVS key: last synced epoch (`u64`). |
+| `SYS_TIME_NVS_KEY_SRC` | `"last_src"` | NVS key: last sync source (`u8`). |
diff --git a/docs/tusb_desc/README.md b/docs/tusb_desc/README.md
index 580cd0735..cc8918e46 100644
--- a/docs/tusb_desc/README.md
+++ b/docs/tusb_desc/README.md
@@ -1,6 +1,6 @@
-# TinyUSB Descriptors (HID Composite)
+# TinyUSB Descriptors (HID + CDC + optional MSC Composite)
-This component defines the USB descriptors required to enumerate the ESP32-P4 as a USB HID Composite Device (Keyboard + Mouse) and provides the initialization routine for the TinyUSB driver.
+This component defines the USB descriptors required to enumerate the ESP32-P4 as a USB composite device and provides the initialization routine for the TinyUSB driver. The composite is HID (Keyboard + Mouse, for BadUSB) plus a CDC-ACM interface (the companion host link), with an optional mass-storage (MSC) interface exposed only while the SD card is handed to the host.
## Overview
@@ -15,20 +15,39 @@ This component defines the USB descriptors required to enumerate the ESP32-P4 as
| Field | Value |
|-------|-------|
-| USB Version | 2.0 |
+| USB Version | 2.0 (`bcdUSB` `0x0200`) |
| Vendor ID | `0xCAFE` |
| Product ID | `0x4001` |
-| Device Class | Defined at interface level |
+| Device Class | Miscellaneous / Common / IAD (`TUSB_CLASS_MISC`) - required so the host groups the CDC interfaces |
| Configurations | 1 |
### Configuration Descriptor
+The config is runtime-selected. `tud_descriptor_configuration_cb` picks a variant
+by both link speed and whether MSC is currently exposed, all built from a single
+`HID_CDC_BLOCK` macro (optionally followed by an `MSC_INTERFACE`).
+
| Field | Value |
|-------|-------|
-| Interfaces | 1 (HID) |
+| Interfaces | 3 (HID + CDC comm + CDC data), or 4 with MSC (`TUSB_DESC_ITF_NUM_TOTAL`) |
| Max Power | 100 mA |
| Attributes | Remote Wakeup |
+The CDC bulk (data) endpoint size is speed-dependent: USB requires exactly 512
+bytes at High Speed and 64 at Full Speed. The P4 USB is High Speed, but two
+descriptor variants are built per mode (HS/FS) and the matching one is served so
+`tu_edpt_validate` accepts the config. A wrong size fails `SET_CONFIGURATION`,
+which would also take the HID keyboard down.
+
+### Interfaces and Endpoints
+
+| Interface | Number | Endpoint(s) |
+|-----------|--------|-------------|
+| HID (keyboard + mouse) | 0 | IN `0x81` |
+| CDC-ACM comm | 1 | notification IN `0x82` |
+| CDC-ACM data | 2 | OUT `0x03`, IN `0x83` |
+| MSC (optional) | 3 | OUT `0x04`, IN `0x84` |
+
### HID Report Descriptor
Single HID interface with two reports using Report IDs:
@@ -46,6 +65,8 @@ Single HID interface with two reports using Report IDs:
| 1 | Manufacturer: "HighCode" |
| 2 | Product: "BadUSB Device" |
| 3 | Serial: "123456" |
+| 4 | CDC interface: "TentacleOS Companion" |
+| 5 | MSC interface: "TentacleOS SD" (only present when MSC is compiled in) |
## API Reference
@@ -60,7 +81,30 @@ Initializes the TinyUSB driver with the defined descriptors.
Must be called before any HID report transmission. It does **not** touch the
USB-C data mux (see below) - installing the driver and routing the connector are
-separate steps.
+separate steps. The HID (BadUSB) and CDC (companion) sides share one TinyUSB
+install: whichever calls first brings the composite up, later calls are no-ops.
+
+### `busb_set_msc_exposed`
+```c
+void busb_set_msc_exposed(bool exposed);
+```
+Advertises (or hides) the mass-storage interface on the composite at runtime.
+
+MSC is off by default, so plain native-USB bring-ups enumerate as HID + CDC only.
+`tud_descriptor_configuration_cb` reads `s_msc_exposed` at request time and serves
+the 4-interface (MSC) config variant only while the flag is set. Behavior:
+
+- If TinyUSB is not up yet, the call just latches the flag so the first
+ enumeration advertises the right layout.
+- If it is already up, the device detaches (`tud_disconnect`), waits
+ `BUSB_REENUM_DELAY_MS` (100 ms) so the host notices, swaps the advertised
+ config, and re-attaches (`tud_connect`) so the host re-reads the descriptor.
+
+**Rationale:** an MSC LUN that is advertised but not backed by an initialized
+storage handle crashes the TinyUSB task on the host's first SCSI command. MSC is
+therefore exposed only while the SD is actually handed to the host (mass-storage
+mode) and hidden again on exit. Only compiled in when `CFG_TUD_MSC` is set; a
+no-op otherwise.
### `usb_mux_init`
```c
@@ -115,9 +159,14 @@ The component implements the required TinyUSB callbacks to serve descriptors to
| Callback | Purpose |
|----------|---------|
-| `tud_descriptor_device_cb` | Returns the device descriptor |
-| `tud_descriptor_configuration_cb` | Returns the configuration descriptor |
-| `tud_descriptor_string_cb` | Returns string descriptors (manufacturer, product, serial) |
+| `tud_descriptor_device_cb` | Returns the composite device descriptor |
+| `tud_descriptor_configuration_cb` | Returns the configuration descriptor, selecting the variant by link speed (HS/FS) and whether MSC is currently exposed |
+| `tud_descriptor_string_cb` | Returns string descriptors (manufacturer, product, serial, CDC, MSC) |
| `tud_hid_descriptor_report_cb` | Returns the HID report descriptor |
| `tud_hid_get_report_cb` | Handles GET_REPORT requests (stub) |
| `tud_hid_set_report_cb` | Handles SET_REPORT requests (stub) |
+
+The CDC-ACM and MSC class callbacks are not implemented here: they live in the
+components that own those interfaces (the companion host link for CDC, and the
+SD/mass-storage feature for MSC). This component only defines the shared
+descriptors and the descriptor-serving callbacks above.
diff --git a/docs/ui/README.md b/docs/ui/README.md
index eb3b0d408..663dcd686 100644
--- a/docs/ui/README.md
+++ b/docs/ui/README.md
@@ -1,13 +1,17 @@
# ui_manager
-step-by-step process for adding a new screen (feature) to the HighBoy system using the ui_manager architecture.
-**Example** used: We'll create a fictional **Bluetooth (BLE)** screen.
+Step-by-step process for adding a new screen (feature) to TentacleOS using the
+`ui_manager` architecture. Source of truth: `ui/ui_manager.c`,
+`ui/include/ui_manager.h`, `ui/include/ui_metrics.h`, `ui/include/ui_theme.h`,
+and the screens under `ui/screens/`.
-### 1. Register the screen in the UI ui_manager
-The `ui_manager` needs to know about the new screen to handle navigation.
+**Example** used: a fictional **Bluetooth (BLE)** menu screen.
-**File:** `ui/ui_manager.h`
-1. Add a new identifier to the `enum`:
+### 1. Add a screen id
+The `ui_manager` identifies every screen by an enum value.
+
+**File:** `ui/include/ui_manager.h`
+Add a new identifier to `screen_id_t`:
```c
typedef enum {
SCREEN_NONE,
@@ -15,163 +19,202 @@ typedef enum {
SCREEN_MENU,
SCREEN_WIFI_MENU,
// ...
- SCREEN_BLE_MENU, // <--- NEW ID ADDED
+ SCREEN_BLE_MENU, // <--- NEW ID
} screen_id_t;
```
-### 2. Configure routing and Power Management
-Define how the ui_manager should open the screen and handle any required hardware power states.
+### 2. Register the open function in the dispatch table
+Routing is a dispatch table, not a `switch` inside `ui_switch_screen`.
+`screen_open_fn(screen_id_t)` maps an id to the function that builds and loads
+the screen; `ui_switch_screen` looks the id up there. If it returns `NULL` the
+screen is treated as unavailable and the manager stays put.
**File:** `ui/ui_manager.c`
-1. Include de header for the new screen (created in Step 3):
+1. Include the screen header (created in Step 4):
```c
-#include "screens/bluetooth/ui_ble_menu.h"
+#include "ui_ble_menu.h"
```
-2. (Optional) Power Management: If the screen uses a radio (Wi-Fi, BLE, RF), add logic to automatically enable/disable the hardware.
+2. Add a `case` to `screen_open_fn()`:
```c
-static bool is_ble_screen(screen_id_t screen) {
- switch (screen) {
- case SCREEN_BLE_MENU:
- case SCREEN_BLE_SCAN: // Future sub-screens
- return true;
- default:
- return false;
- }
+static ui_open_fn_t screen_open_fn(screen_id_t s) {
+ switch (s) {
+ // ... other cases ...
+ case SCREEN_BLE_MENU:
+ return ui_ble_menu_open; // <--- NEW ROUTE
+ default:
+ return NULL;
+ }
}
```
-Update `ui_switch_screen` to call `ble_init()` / `ble_deinit()` based on this flag (similar to how Wi-Fi is handled).
+### 3. If the screen owns hardware or a task, register a stop hook
+Screens that start a radio, a worker task, or a media player must register a
+stop function in `screen_close_fn()`. `ui_switch_screen` calls it on the
+outgoing screen before tearing it down, so the resource is always released on
+navigation. Current examples: `SCREEN_SUBGHZ_READ` -> `subghz_receiver_stop`,
+`SCREEN_NFC_READ` / `SCREEN_NFC_EMULATE` -> `nfc_manager_stop`,
+`SCREEN_WAV_PLAYER` -> `ui_wav_player_stop`, `SCREEN_MP3_PLAYER` ->
+`ui_mp3_player_stop`, `SCREEN_IMAGE_VIEWER` -> `ui_image_viewer_stop`,
+`SCREEN_USB_STORAGE` -> `ui_usb_storage_stop`.
-3. Add the case to the main switch statement:
```c
-void ui_switch_screen(screen_id_t new_screen) {
- if (ui_acquire()) {
- // ... init/deinit logic ...
- clear_current_screen();
-
- switch (new_screen) {
- // ... other cases ...
-
- case SCREEN_BLE_MENU: // <--- NEW ROUTE
- ui_ble_menu_open();
- break;
- }
- // ...
- }
+static ui_close_fn_t screen_close_fn(screen_id_t s) {
+ switch (s) {
+ // ... other cases ...
+ case SCREEN_BLE_READ:
+ return ble_scanner_stop; // <--- release the radio/task on leave
+ default:
+ return NULL;
+ }
}
```
-### 3. Create the New Screen UI
-Create the folder and files for the new feature: `ui/screens/bluetooth/`
+This supersedes the old advice of calling `ble_init()` / `ble_deinit()` inside
+`ui_switch_screen`. A plain menu that owns no hardware needs no close hook.
+
+Enabling a radio for a whole area is done at the menu, not here: `menu_ui.c`'s
+`ensure_radio_on()` powers Wi-Fi / BLE on when the user opens that area's menu.
+
+### 4. Create the screen source
+Create the files under `ui/screens/bluetooth/`.
-**Header File:** `ui_ble_menu.h`
+**Header:** `ui/screens/bluetooth/include/ui_ble_menu.h`
```c
#ifndef UI_BLE_MENU_H
#define UI_BLE_MENU_H
-#include "lvgl.h"
-void ui_ble_menu_open(void); // Public function
+void ui_ble_menu_open(void);
#endif
```
-**Source File:** `ui_ble_menu.c`
-Standard template from any Highboy screen:
+**Source:** `ui/screens/bluetooth/ui_ble_menu.c`. The current template:
```c
#include "ui_ble_menu.h"
-#include "ui_manager.h"
-#include "lv_port_indev.h" // Access to main_group
+
#include "esp_log.h"
-static const char *TAG = "UI_BLE";
-static lv_obj_t * screen_ble = NULL;
-
-// 1. Event Callback (Navigation)
-static void ble_event_cb(lv_event_t * e) {
- lv_event_code_t code = lv_event_get_code(e);
-
- if (code == LV_EVENT_KEY) {
- uint32_t key = lv_event_get_key(e);
- // BACK BUTTON (ESC/LEFT)
- if (key == LV_KEY_ESC || key == LV_KEY_LEFT) {
- ESP_LOGI(TAG, "Returning to Main Menu");
- // Destroy current screen and open Menu
- ui_switch_screen(SCREEN_MENU);
- }
- }
+#include "menu_component_ui.h"
+#include "ui_manager.h"
+#include "ui_metrics.h"
+#include "ui_theme.h"
+
+static const char *TAG = "UI_BLE_MENU";
+
+static lv_obj_t *s_screen = NULL;
+static menu_component_t s_menu;
+
+// Event-driven input: one debounced event at a time. The central pump only
+// calls this while input is unlocked and no modal overlay is up. UP/DOWN also
+// act on REPEAT for held auto-scroll; actions use PRESS only.
+static void ble_menu_input(const input_event_t *ev, void *ctx) {
+ (void)ctx;
+ const bool press = (ev->action == INPUT_ACTION_PRESS);
+ const bool nav = press || (ev->action == INPUT_ACTION_REPEAT);
+ switch (ev->button) {
+ case INPUT_BTN_DOWN:
+ if (nav) menu_component_next(&s_menu);
+ break;
+ case INPUT_BTN_UP:
+ if (nav) menu_component_prev(&s_menu);
+ break;
+ case INPUT_BTN_BACK:
+ case INPUT_BTN_LEFT:
+ if (press) ui_switch_screen(SCREEN_MENU);
+ break;
+ case INPUT_BTN_OK:
+ case INPUT_BTN_RIGHT:
+ if (press) { /* enter the selected item */ }
+ break;
+ default:
+ break;
+ }
}
-// 2. Screen Build Function
void ui_ble_menu_open(void) {
- // Safety cleanup
- if (screen_ble) {
- lv_obj_del(screen_ble);
- screen_ble = NULL;
- }
-
- // A. Create Base Screen
- screen_ble = lv_obj_create(NULL);
- lv_obj_set_style_bg_color(screen_ble, lv_color_black(), 0);
-
- // B. Add Content (e.g., Title)
- lv_obj_t * label = lv_label_create(screen_ble);
- lv_label_set_text(label, "Bluetooth Menu");
- lv_obj_set_style_text_color(label, lv_color_white(), 0);
- lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
-
- // C. Setup Navigation
- lv_obj_add_event_cb(screen_ble, ble_event_cb, LV_EVENT_KEY, NULL);
-
- // Add to Input Group (Essential!)
- if (main_group) {
- lv_group_add_obj(main_group, screen_ble);
- lv_group_focus_obj(screen_ble);
- }
-
- // D. Load Screen
- lv_screen_load(screen_ble);
+ if (s_screen != NULL) {
+ lv_obj_del(s_screen);
+ s_screen = NULL;
+ }
+
+ s_screen = lv_obj_create(NULL);
+ lv_obj_set_style_bg_color(s_screen, current_theme.screen_base, 0);
+ lv_obj_set_style_bg_opa(s_screen, LV_OPA_COVER, 0);
+ lv_obj_remove_flag(s_screen, LV_OBJ_FLAG_SCROLLABLE);
+
+ s_menu = menu_component_create(s_screen, "BLUETOOTH", "/assets/icons/bluetooth.bin");
+ // ... add items ...
+
+ ui_input_set_screen_handler(ble_menu_input, NULL);
+
+ ui_screen_load_owned(&s_screen, s_screen);
}
```
-### 4. Link from the main Menu
-Add a button/entru in the main menu to access the new screen
-
-**File:** `ui/screens/menu/ui_menu.c`
-1. In the `menu_event_cb` callback, locate the corresponding item ID case and add/uncomment the call:
+Points that differ from older screens - all required for new screens:
+
+- **Input is event-driven.** Register a handler with
+ `ui_input_set_screen_handler(handler, ctx)`; it receives an `input_event_t`
+ (`ev->button` in `INPUT_BTN_UP..BACK`, `ev->action` in
+ `INPUT_ACTION_PRESS` / `RELEASE` / `LONG_PRESS` / `REPEAT`, both from
+ `input_manager.h`). No `LV_EVENT_KEY` callback, no `lv_event_get_key`, no
+ manual `main_group` focus, no `s_*_last` edge bookkeeping. The handler is
+ cleared for you on the next screen switch. See
+ [input-migration.md](input-migration.md) for the full pattern and gotchas.
+- **Load the screen owned.** Use `ui_screen_load_owned(&s_screen, scr)` instead
+ of raw `lv_screen_load`. When the object is freed on navigation, the slot is
+ set back to `NULL`, so the screen never double-frees or dereferences a stale
+ pointer. Do not manually `lv_obj_del` the previous screen in the open
+ function beyond the guarded self-cleanup shown above.
+- **Be rotation-aware.** Never hardcode `LCD_H_RES` / `LCD_V_RES` for layout;
+ those are fixed panel constants. Use `ui_screen_w()` / `ui_screen_h()` from
+ `ui_metrics.h` (they follow the live rotation). If the screen must reflow
+ after a rotation change, call `ui_relayout_current_screen()`. When a component
+ polls button levels directly, use `ui_nav_pressed(logical)` so it navigates
+ correctly in landscape.
+- **Schedule onto the UI thread from workers.** Any worker/radio task that
+ needs to touch LVGL must marshal through `ui_async_call()` (the required
+ wrapper around `lv_async_call`; it takes the UI lock first). Never call LVGL
+ directly from another task.
+- **Styling uses the theme.** Pull colors from `current_theme` in
+ `ui_theme.h` (`screen_base`, `bg_primary`, `text_main`, `border_accent`,
+ ...). For a protocol screen, set the active protocol with
+ `ui_theme_set_protocol(PROTOCOL_BLE)` and read the accent with
+ `ui_theme_get_accent()`, or use the per-protocol fields directly
+ (`current_theme.protocol_ble`, `protocol_nfc`, `protocol_wifi`,
+ `protocol_subghz`, `protocol_rfid`, `protocol_ir`, `protocol_lora`). Do not
+ hardcode `lv_color_black()` / `lv_color_white()`.
+
+### 5. Link from the main menu
+The main menu (`ui/screens/menu/menu_ui.c`) is a data table: each
+`menu_ui_item_t` carries a `target` of type `screen_id_t`. Add (or point) an
+entry at the new id:
```c
-case MENU_ID_BLUETOOTH:
- ui_switch_screen(SCREEN_BLE_MENU); // <--- Routes to the new screen
- break;
+{"BLUETOOTH",
+ { /* icon frames */ },
+ BASE_FRAMES, {NULL}, {NULL},
+ SCREEN_BLE_MENU}, // <--- target
```
-(Note: If the MENU_ID_BLUETOOTH entry doesn't exist yet in menu_item_id_t, create it.)
+Selecting the item calls `ensure_radio_on(target)` and then
+`ui_switch_screen(target)`; you do not write a per-item `case`.
+
+### 6. Build system (CMake)
+**File:** `components/Applications/CMakeLists.txt` (this is the `Applications`
+component; there is no separate CMakeLists under `ui/`).
-### 5. Update Build System (CMake)
-Commom error: forgettint to register the new source files.
+Sources are picked up automatically: `file(GLOB_RECURSE UI_SRCS "ui/*.c")`
+globs every `.c` under `ui/`, so a new screen source compiles without editing
+the SRCS list. Because it is a glob, **adding a new `.c` needs a reconfigure**
+(`idf.py reconfigure`, or a clean build) to re-run it.
-**File:** `CMakeLists.txt` (UI component)
-1. Add the new sources files and include directory:
+The only hand-maintained part is `INCLUDE_DIRS`: when you introduce a brand-new
+screen *area* (a new folder with its own `include/`), add its include dir:
```cmake
-file(GLOB_RECURSE HOME_UI_SRCS "ui/screens/home/*.c")
-file(GLOB_RECURSE MENU_UI_SRCS "ui/screens/menu/*.c")
-file(GLOB_RECURSE WIFI_UI_SRCS "ui/screens/wifi/*.c")
-file(GLOB_RECURSE BLE_UI_SRCS "ui/screens/ble/*.c") # <---- Add srcs here
-
-idf_component_register(SRCS
- "ui/ui_manager.c"
- ${HOME_UI_SRCS}
- ${MENU_UI_SRCS}
- ${WIFI_UI_SRCS}
- ${BLE_UI_SRCS} # <----- and call it here
-
- INCLUDE_DIRS
- "ui/include"
- "ui/screens/home/include"
- "ui/screens/menu/include"
- "ui/screens/wifi/include"
- "ui/screens/ble/include" # <----- dont forget include files
-)
+ # --- ui screens ---
+ "ui/screens/bluetooth/include" # note: screens/bluetooth/, there is no screens/ble/
```
-2. Recommended: Run `idf.py reconfigure` in the terminal after saving
+An existing area (like `bluetooth`) already has its include dir listed, so a
+new screen inside it needs nothing here.
---
@@ -224,9 +267,10 @@ That is all: no timer to create or delete, no `ui_input_is_locked()` /
you on the next screen switch. Use `REPEAT` for held auto-scroll and
`input_is_down(button)` when you need a continuous held state (games).
-See `nfc_menu_ui.c` for the reference migration, and
-[input-migration.md](input-migration.md) for the step-by-step guide to convert
-the remaining ~91 screens off their polling timers (with the gotchas).
+See `nfc_menu_ui.c` for the reference handler, and
+[input-migration.md](input-migration.md) for the full pattern and gotchas. Every
+screen already uses this model; the lone exception is `games/octopet_ui.c`,
+which keeps a poll timer on purpose for continuous held-direction movement.
## Screen power policy (auto-dim / sleep)
@@ -249,16 +293,19 @@ lock. It reads `input_last_activity_ms()` (from `input_manager`) and the
The display settings screen writes `auto_lock_seconds` / `auto_dim` / brightness
through `tos_config`, which is the sole writer of the `screen` config file.
-## Execution Flow Sumamary
-1. User selects **Bluetooth** from the Main Menu.
-2. Menu callback calls `ui_switch_screen(SCREEN_BLE_MENU)`.
-3. `ui_manager`:
- - Handles hardware power (enables BLE if needed).
- - Clears previous screen.
- - Calls `ui_ble_menu_open()`.
+## Execution flow summary
+1. User selects **Bluetooth** in the main menu.
+2. The menu resolves the item's `target` (`SCREEN_BLE_MENU`), calls
+ `ensure_radio_on(target)` (powers BLE on for the area), then
+ `ui_switch_screen(SCREEN_BLE_MENU)`.
+3. `ui_switch_screen`:
+ - Looks the id up in `screen_open_fn()`; stays put if it is `NULL`.
+ - Calls the outgoing screen's `screen_close_fn()` stop hook, if any.
+ - Clears the previous screen (and the old input handler).
+ - Calls the resolved open function, `ui_ble_menu_open()`.
4. `ui_ble_menu_open`:
- - Creates visual objects.
- - Adds objects to `main_group`.
- - Loads the screen.
+ - Builds the screen objects (theme colors, rotation-aware layout).
+ - Registers its input handler with `ui_input_set_screen_handler()`.
+ - Loads the screen with `ui_screen_load_owned(&s_screen, scr)`.
-**Done! The new screen is fully integrated, safe and navigable.**
+**Done: the new screen is integrated, safe and navigable.**
diff --git a/docs/ui/input-migration.md b/docs/ui/input-migration.md
index cdd29a624..8e4d5ecb1 100644
--- a/docs/ui/input-migration.md
+++ b/docs/ui/input-migration.md
@@ -1,18 +1,25 @@
# Screen input migration guide
-How to convert one screen from the old per-screen polling `lv_timer` to the
-central event-driven input. Background: [input_manager](../input_manager/README.md),
+How a screen wires up input: the central event-driven model, and how it was
+converted from the old per-screen polling `lv_timer`. Background:
+[input_manager](../input_manager/README.md),
[ui: event-driven handlers](README.md#input-event-driven-handlers).
-**Status:** 7 screens migrated (see [Reference examples](#reference-examples));
-~91 still use `nav_timer_cb`. Find the remaining ones with:
+**Status: migration complete (historical reference).** Every screen now
+registers an input handler with `ui_input_set_screen_handler` (~110 screen
+files). The single remaining `nav_timer_cb` is in `games/octopet_ui.c`, a
+deliberate continuous-held-state case (see the games gotcha below), not a
+pending TODO. Confirm with:
```sh
-grep -rln "nav_timer_cb\|nav_cb\b" firmware_p4/components/Applications/ui/screens --include='*.c'
+grep -rln "ui_input_set_screen_handler" firmware_p4/components/Applications/ui/screens --include='*.c' | wc -l
+grep -rln "nav_timer_cb" firmware_p4/components/Applications/ui/screens --include='*.c'
```
-Nothing is broken in the meantime: unmigrated screens keep working through the
-`buttons_gpio` shim. Migrate opportunistically, build after each batch.
+This document is kept as a reference: the recipe and gotchas below describe the
+current handler pattern, so use it when adding a new screen or if you ever find
+a stray polling timer. The reference-examples table still points at real,
+representative screens.
## What you are replacing
@@ -110,14 +117,12 @@ Copy the closest match:
| `bluetooth/ui_ble_menu.c` | Forward decl + `MENU_ITEMS` target dispatch |
| `games/games_menu_ui.c` | Carousel: LEFT/UP and RIGHT/DOWN cycle an index with REPEAT |
-## Remaining work (~91 screens)
+## The one intentional exception
-By area (run the grep above for the live list): bluetooth 15, wifi 14, nfc 13,
-settings 11, lora 8, infrared 7, subghz 5, audio 4, dev 3, games 2, badusb 2,
-and one each in theme, rfid, haptic, gpio, files, connect_wifi,
-connection_settings. The simple area menus (settings, submenus) are quickest;
-the games and multi-view NFC/RFID screens need the continuous-state / multi-view
-handling above.
+`games/octopet_ui.c` keeps a `nav_timer_cb` on purpose: the pet moves while a
+direction is held, which is a continuous-held-state case (see the games gotcha
+above), not something a pure press/edge handler models well. It reads button
+levels from its own tick timer. This is by design; do not "migrate" it.
## Verify
diff --git a/docs/wifi/README.md b/docs/wifi/README.md
index 26fa45a1e..7ef61361f 100644
--- a/docs/wifi/README.md
+++ b/docs/wifi/README.md
@@ -1,19 +1,41 @@
+# Wi-Fi service
+
+Split across **both firmwares**. The **C5 owns the real radio** (the full esp-idf
+Wi-Fi stack, AP/STA, scanning, promiscuous capture, config persistence). The **P4
+is a pure proxy**: every public call forwards to the C5 over the SPI bridge. Keep
+this split in mind - the two `wifi_service` APIs look almost identical, but the
+P4 one has no local Wi-Fi stack behind it.
+
+Both headers share the same file-path defines:
+
+```c
+#define WIFI_AP_CONFIG_FILE "config/wifi/wifi_ap.conf"
+#define WIFI_KNOWN_NETWORKS_FILE "storage/wifi/know_networks.json"
+```
+
+Both are relative paths handed to the `storage_assets` API; the actual files
+live on the C5 (it owns storage for Wi-Fi state).
+
+---
+
# P4
-This component manages Wi-Fi functionalities including Access Point (AP) mode, Station (STA) mode, scanning, and configuration persistence using JSON files.
+`firmware_p4/components/Service/wifi/` - a **thin SPI-bridge proxy** to the C5.
+There is **no local esp-idf Wi-Fi stack on the P4**: no APSTA mode, no NVS init,
+no default event loop, no netif / static IP / DHCP server, no `cJSON`, no local
+channel-hop task, and no LED signalling in this module. Every operation is a
+`spi_bridge_send_command` (or `spi_bridge_run_scan`) to the C5.
-## Functionality Overview
+## How state is read without blocking the UI
-The service handles:
-- **Initialization/Deinitialization:** Setup of NVS, Netif, Event Loops, and Wi-Fi drivers.
-- **Access Point (AP):** Configurable SSID, password, max connections, and custom IP address.
-- **Scanning:** Active scanning for nearby networks.
-- **Station (STA):** Connecting to external Wi-Fi networks.
-- **Hotspot Management:** Dynamic switching of AP configuration.
-- **Promiscuous Mode:** Low-level packet sniffing and environment monitoring.
-- **Channel Hopping:** Automated cycling through Wi-Fi channels for environment monitoring.
-- **Configuration Persistence:** AP/client settings loaded via `tos_config_load_all()` from SD (`config/wifi.conf`) with flash fallback (`/assets/config/wifi/wifi_ap.conf`).
-- **Known Networks:** Automatically saves connected network credentials to `wifi/` on SD card.
+The header getters (`wifi_service_is_active` / `wifi_service_is_connected`) are
+polled from an `lv_timer` on the LVGL thread. Doing a blocking SPI RPC there
+froze the renderer whenever the bridge was busy (e.g. during a scan, which holds
+the bridge mutex for seconds). So a background `status_poll_task` (pinned to
+`SYS_CORE_RADIO`, priority `SYS_PRIO_BACKGROUND`) polls `SPI_ID_SYSTEM_STATUS`
+once per second and caches `wifi_active` / `wifi_connected` into volatile flags;
+the getters just read those flags. The same poll also feeds
+`bluetooth_service_set_running_cached`.
## API Functions
@@ -23,51 +45,51 @@ The service handles:
```c
void wifi_service_init(void);
```
-Initializes the Wi-Fi stack in `APSTA` mode.
-- Initializes NVS (performing erase if necessary).
-- Sets up the default event loop and registers handlers.
-- Loads AP configuration from storage (or uses defaults "Darth Maul"/"MyPassword123").
-- Configures the static IP (default: 192.168.4.1) and starts the DHCP server.
+Spawns the background `status_poll_task` (once). It does **not** bring up any
+radio - the C5 owns that.
#### `wifi_service_deinit`
```c
void wifi_service_deinit(void);
```
-Completely shuts down the Wi-Fi service.
-- Stops the Wi-Fi driver.
-- Unregisters event handlers.
-- Deinitializes the driver.
-- Frees synchronization primitives (mutexes) and clears static data.
+Sends `SPI_ID_WIFI_STOP` to the C5 (functionally identical to
+`wifi_service_stop`; there is no local stack to tear down).
#### `wifi_service_start` / `wifi_service_stop`
```c
void wifi_service_start(void);
void wifi_service_stop(void);
```
-Simple wrappers to start or stop the Wi-Fi driver without full deinitialization. `wifi_service_stop` also clears stored scan results.
+Forward `SPI_ID_WIFI_START` / `SPI_ID_WIFI_STOP` to the C5.
### Scanning
#### `wifi_service_scan`
```c
-void wifi_service_scan(void);
+esp_err_t wifi_service_scan(void);
```
-Performs an active Wi-Fi scan.
-- Uses a mutex to ensure thread safety.
-- Stores up to `WIFI_SCAN_LIST_SIZE` results internally.
-- Provides visual feedback via LEDs (Green for AP connection, Red for failures, Blue for scan success).
+**Async, proxied.** The C5 runs the scan. This calls
+`spi_bridge_run_scan(SPI_ID_WIFI_SCAN, SPI_ID_WIFI_SCAN_STATUS, NULL, 0)`, which
+kicks the scan and polls the scan-status id **without holding the bridge mutex**
+(so the UI and other peripherals stay free), returning once the C5 reports
+completion. Returns `esp_err_t`.
#### `wifi_service_get_ap_count`
```c
uint16_t wifi_service_get_ap_count(void);
```
-Returns the number of networks found in the last scan.
+Fetches the last-scan count from the C5 via `SPI_ID_SYSTEM_DATA` (with the
+`SPI_DATA_INDEX_COUNT` magic index). Returns 0 on bridge failure.
#### `wifi_service_get_ap_record`
```c
-wifi_ap_record_t* wifi_service_get_ap_record(uint16_t index);
+wifi_ap_record_t *wifi_service_get_ap_record(uint16_t index);
```
-Retrieves a pointer to a specific scan result record. Returns `NULL` if the index is invalid.
+Fetches one scan record from the C5 (`SPI_ID_SYSTEM_DATA`, indexed) into a static
+cache and returns a pointer to it, or `NULL` on failure. Sanitizes the SSID at
+this single point for every consumer (console + UI): non-printable / non-ASCII
+bytes become `?` (they hang LVGL's text renderer), and an empty SSID (hidden
+network) is replaced with the placeholder `[rede oculta]`.
### Connection & Management
@@ -75,132 +97,105 @@ Retrieves a pointer to a specific scan result record. Returns `NULL` if the inde
```c
esp_err_t wifi_service_connect_to_ap(const char *ssid, const char *password);
```
-Connects the device (as a station) to an external Access Point.
-- Configures authentication mode based on the presence of a password (WPA2_PSK or OPEN).
-- Disconnects any existing connection before attempting a new one.
-- **Persistence:** Automatically saves the SSID and password to `assets/storage/wifi/know_networks.json`. If the network already exists, the password is updated.
+Packs the SSID/password into `spi_wifi_connect_t` and forwards
+`SPI_ID_WIFI_CONNECT` to the C5. The C5 performs the actual join and known-network
+persistence.
#### `wifi_service_is_connected`
```c
bool wifi_service_is_connected(void);
```
-Returns `true` if the device is currently connected to an external Wi-Fi network and has an IP address.
+Returns the cached `wifi_connected` flag (refreshed by `status_poll_task`). Does
+**not** hit the bridge.
#### `wifi_service_is_active`
```c
bool wifi_service_is_active(void);
```
-Returns `true` if the Wi-Fi service is started (driver initialized and interface up).
+Returns the cached `wifi_active` flag (refreshed by `status_poll_task`). Does
+**not** hit the bridge.
#### `wifi_service_get_connected_ssid`
```c
-const char* wifi_service_get_connected_ssid(void);
+const char *wifi_service_get_connected_ssid(void);
```
-Returns the SSID of the currently connected network. Returns `NULL` if not connected.
+Fetches the STA SSID from the C5 via `SPI_ID_WIFI_GET_STA_INFO` into a static
+buffer; returns it (NUL-terminated) or `NULL` on failure.
#### `wifi_service_change_to_hotspot`
```c
void wifi_service_change_to_hotspot(const char *new_ssid);
```
-Dynamically reconfigures the device's Access Point to an **Open** network with the specified SSID.
-- Stops the Wi-Fi driver briefly to apply changes.
-- Sets `authmode` to `WIFI_AUTH_OPEN`.
-- Restarts Wi-Fi with the new configuration.
+On the P4 this is just a wrapper around `wifi_service_set_ap_ssid(new_ssid)`.
### Promiscuous Mode
-#### `wifi_service_promiscuous_start`
+#### `wifi_service_promiscuous_start` / `wifi_service_promiscuous_stop`
```c
void wifi_service_promiscuous_start(wifi_promiscuous_cb_t cb, wifi_promiscuous_filter_t *filter);
-```
-Enables promiscuous mode (sniffer) with a custom callback and filter.
-- `cb`: Function to handle captured packets.
-- `filter`: Filter mask (e.g., `WIFI_PROMIS_FILTER_MASK_MGMT`).
-
-#### `wifi_service_promiscuous_stop`
-```c
void wifi_service_promiscuous_stop(void);
```
-Disables promiscuous mode and clears the callback.
+Forward `SPI_ID_WIFI_PROMISC_START` / `SPI_ID_WIFI_PROMISC_STOP`. The `cb` and
+`filter` arguments are **ignored** on the P4 (the capture and its callback run on
+the C5); the signature is kept for source compatibility. If the C5 replies
+`ESP_ERR_NOT_SUPPORTED`, a warning is logged.
### Channel Hopping
-#### `wifi_service_start_channel_hopping`
+#### `wifi_service_start_channel_hopping` / `wifi_service_stop_channel_hopping`
```c
void wifi_service_start_channel_hopping(void);
-```
-Starts a background task that cycles the Wi-Fi interface through channels 1 to 13.
-- Useful for promiscuous mode applications (e.g., deauth detection).
-- Task memory is allocated in PSRAM if available.
-
-#### `wifi_service_stop_channel_hopping`
-```c
void wifi_service_stop_channel_hopping(void);
```
-Stops the channel hopping task and frees associated memory resources.
+Forward `SPI_ID_WIFI_CH_HOP_START` / `SPI_ID_WIFI_CH_HOP_STOP`. The hopping task
+itself lives on the C5.
### Configuration Storage
+All setters forward to the C5, which persists to `WIFI_AP_CONFIG_FILE` and
+applies any radio state change.
+
#### `wifi_service_save_ap_config`
```c
-esp_err_t wifi_service_save_ap_config(const char *ssid, const char *password, uint8_t max_conn, const char *ip_addr, bool enabled);
+esp_err_t wifi_service_save_ap_config(
+ const char *ssid, const char *password, uint8_t max_conn, const char *ip_addr, bool enabled);
```
-Saves the AP configuration to a JSON file (`/assets/config/wifi/wifi_ap.conf`).
-- Uses `cJSON` to serialize settings.
-- Persists data using the storage API.
-- **State Management:** If `enabled` is `true` and Wi-Fi is inactive, it calls `wifi_service_start()`. If `enabled` is `false` and Wi-Fi is active, it calls `wifi_service_stop()`.
+Packs the fields into `spi_wifi_ap_config_t` and forwards
+`SPI_ID_WIFI_SAVE_AP_CONFIG`. Returns `ESP_ERR_INVALID_ARG` if `ssid` is NULL.
#### Individual Setters
-Helper functions to update a single configuration parameter while preserving others. They automatically save the config and trigger state changes if `enabled` is toggled.
-
```c
-esp_err_t wifi_service_set_enabled(bool enabled);
-esp_err_t wifi_service_set_ap_ssid(const char *ssid);
-esp_err_t wifi_service_set_ap_password(const char *password);
-esp_err_t wifi_service_set_ap_max_conn(uint8_t max_conn);
-esp_err_t wifi_service_set_ap_ip(const char *ip_addr);
+esp_err_t wifi_service_set_enabled(bool enabled); // SPI_ID_WIFI_SET_ENABLED
+esp_err_t wifi_service_set_ap_ssid(const char *ssid); // SPI_ID_WIFI_SET_AP
+esp_err_t wifi_service_set_ap_password(const char *p); // SPI_ID_WIFI_SET_AP_PASSWORD
+esp_err_t wifi_service_set_ap_max_conn(uint8_t n); // SPI_ID_WIFI_SET_AP_MAX_CONN
+esp_err_t wifi_service_set_ap_ip(const char *ip_addr); // SPI_ID_WIFI_SET_AP_IP
```
-
-**Internal Loader:** `wifi_service_load_ap_config` is called during initialization to read these settings. If `enabled` is found to be `false` in the config, `wifi_service_init` will initialize the driver but **not** start the radio.
-
-## Internal Implementation Details
-
-### Event Handling
-A static `wifi_event_handler` manages Wi-Fi and IP events:
-- **WIFI_EVENT_AP_STACONNECTED:** Logs the MAC of the connected station and blinks Green.
-- **WIFI_EVENT_AP_STADISCONNECTED:** Blinks Red.
-- **IP_EVENT_AP_STAIPASSIGNED:** Logs IP assignment and blinks Green.
-
-### Thread Safety
-A `wifi_mutex` (Semaphore) is used to protect the scanning process (`wifi_service_scan`), preventing concurrent scan requests which could lead to resource conflicts.
-
-### Channel Hopping Task
-The channel hopping feature runs as a static FreeRTOS task. It uses `esp_wifi_set_channel` to switch channels every 250ms. To optimize internal RAM usage, both the task stack and the Task Control Block (TCB) are allocated in **PSRAM** using the `SPIRAM` capability.
-
-### Castings & Memory Management
-- **cJSON:** Used extensively for parsing and generating configuration files.
-- **PSRAM Allocation:** Critical tasks and large buffers are allocated in PSRAM to preserve internal memory.
-- **Type Casting:** `event_data` is cast to specific event structures (e.g., `wifi_event_ap_staconnected_t*`) within handlers.
-- **String Handling:** `strncpy` is used safely with explicit null-termination to prevent buffer overflows when handling SSIDs and passwords.
+Each forwards one SPI command. `wifi_service_set_ap_password` /
+`wifi_service_set_ap_ip` return `ESP_ERR_INVALID_ARG` on a NULL argument.
---
# C5
-This component manages Wi-Fi functionalities including Access Point (AP) mode, Station (STA) mode, scanning, and configuration persistence using JSON files.
+`firmware_c5/components/Service/wifi/` - this is where the **real Wi-Fi stack
+lives**. It manages AP mode, STA mode, scanning, promiscuous capture, channel
+hopping, and JSON config persistence directly on the esp-idf Wi-Fi driver.
## Functionality Overview
-The service handles:
-- **Initialization/Deinitialization:** Setup of NVS, Netif, Event Loops, and Wi-Fi drivers.
-- **Access Point (AP):** Configurable SSID, password, max connections, and custom IP address.
-- **Scanning:** Active scanning for nearby networks.
-- **Station (STA):** Connecting to external Wi-Fi networks.
-- **Hotspot Management:** Dynamic switching of AP configuration.
-- **Promiscuous Mode:** Low-level packet sniffing and environment monitoring.
-- **Channel Hopping:** Automated cycling through Wi-Fi channels for environment monitoring.
-- **Configuration Persistence:** Loading and saving AP settings to/from `assets/config/wifi/wifi_ap.conf`.
-- **Known Networks:** Automatically saves connected network credentials to `assets/storage/wifi/know_networks.json`.
+- **Initialization/Deinitialization:** NVS, Netif, default event loop + handlers,
+ and the Wi-Fi driver, brought up in `WIFI_MODE_APSTA`.
+- **Access Point (AP):** Configurable SSID, password, max connections, custom
+ static IP (default `192.168.4.1`), DHCP server.
+- **Scanning:** Active scan storing up to `WIFI_SCAN_LIST_SIZE` results.
+- **Station (STA):** Joins external networks.
+- **Promiscuous Mode / Channel Hopping:** Low-level capture plus a background task
+ cycling channels for environment monitoring.
+- **Configuration Persistence:** AP settings to/from `WIFI_AP_CONFIG_FILE`
+ (`config/wifi/wifi_ap.conf`) via `storage_assets`.
+- **Known Networks:** Connected credentials saved to `WIFI_KNOWN_NETWORKS_FILE`
+ (`storage/wifi/know_networks.json`).
## API Functions
@@ -210,28 +205,38 @@ The service handles:
```c
void wifi_service_init(void);
```
-Initializes the Wi-Fi stack in `APSTA` mode.
-- Initializes NVS (performing erase if necessary).
-- Sets up the default event loop and registers handlers.
-- Loads AP configuration from storage (or uses defaults "Darth Maul"/"MyPassword123").
-- Configures the static IP (default: 192.168.4.1) and starts the DHCP server.
+Initializes the Wi-Fi stack in `APSTA` mode: NVS (erase if needed), default event
+loop + handlers, AP config load (defaults `Darth Maul` / `MyPassword123`), static
+IP and DHCP server. If the loaded config has `enabled == false`, the driver is
+initialized but the radio is **not** started.
#### `wifi_service_deinit`
```c
void wifi_service_deinit(void);
```
-Completely shuts down the Wi-Fi service.
-- Stops the Wi-Fi driver.
-- Unregisters event handlers.
-- Deinitializes the driver.
-- Frees synchronization primitives (mutexes) and clears static data.
+Fully shuts down the service: stops the driver, unregisters handlers, deinits the
+driver, frees mutexes, and clears static state.
#### `wifi_service_start` / `wifi_service_stop`
```c
void wifi_service_start(void);
void wifi_service_stop(void);
```
-Simple wrappers to start or stop the Wi-Fi driver without full deinitialization. `wifi_service_stop` also clears stored scan results.
+Start or stop the driver without full deinit. `wifi_service_stop` also clears
+stored scan results.
+
+#### `wifi_service_is_busy`
+```c
+bool wifi_service_is_busy(void);
+```
+Returns `true` while a capture (promiscuous sniffer) is running.
+
+#### `wifi_service_set_power_save`
+```c
+void wifi_service_set_power_save(bool deep);
+```
+Sets the Wi-Fi modem-sleep depth via `esp_wifi_set_ps`: `deep == true` selects
+`WIFI_PS_MAX_MODEM`, otherwise `WIFI_PS_MIN_MODEM`.
### Scanning
@@ -239,10 +244,9 @@ Simple wrappers to start or stop the Wi-Fi driver without full deinitialization.
```c
void wifi_service_scan(void);
```
-Performs an active Wi-Fi scan.
-- Uses a mutex to ensure thread safety.
-- Stores up to `WIFI_SCAN_LIST_SIZE` results internally.
-- Provides visual feedback via LEDs (Green for AP connection, Red for failures, Blue for scan success).
+Performs an active Wi-Fi scan (mutex-guarded, stops channel hopping first). Stores
+up to `WIFI_SCAN_LIST_SIZE` results and gives LED feedback (red on failure, blue
+on success).
#### `wifi_service_get_ap_count`
```c
@@ -252,9 +256,9 @@ Returns the number of networks found in the last scan.
#### `wifi_service_get_ap_record`
```c
-wifi_ap_record_t* wifi_service_get_ap_record(uint16_t index);
+wifi_ap_record_t *wifi_service_get_ap_record(uint16_t index);
```
-Retrieves a pointer to a specific scan result record. Returns `NULL` if the index is invalid.
+Returns a pointer to a specific scan result, or `NULL` if the index is invalid.
### Connection & Management
@@ -262,37 +266,36 @@ Retrieves a pointer to a specific scan result record. Returns `NULL` if the inde
```c
esp_err_t wifi_service_connect_to_ap(const char *ssid, const char *password);
```
-Connects the device (as a station) to an external Access Point.
-- Configures authentication mode based on the presence of a password (WPA2_PSK or OPEN).
-- Disconnects any existing connection before attempting a new one.
-- **Persistence:** Automatically saves the SSID and password to `assets/storage/wifi/know_networks.json`. If the network already exists, the password is updated.
+Connects (as STA) to an external AP.
+- Auth mode chosen by password presence (WPA2_PSK or OPEN).
+- Disconnects any existing connection first.
+- **Persistence:** saves SSID/password to `WIFI_KNOWN_NETWORKS_FILE`, updating the
+ password if the network already exists.
#### `wifi_service_is_connected`
```c
bool wifi_service_is_connected(void);
```
-Returns `true` if the device is currently connected to an external Wi-Fi network and has an IP address.
+Returns `true` if connected to an external network and holding an IP.
#### `wifi_service_is_active`
```c
bool wifi_service_is_active(void);
```
-Returns `true` if the Wi-Fi service is started (driver initialized and interface up).
+Returns `true` if the service is started (driver up, interface up).
#### `wifi_service_get_connected_ssid`
```c
-const char* wifi_service_get_connected_ssid(void);
+const char *wifi_service_get_connected_ssid(void);
```
-Returns the SSID of the currently connected network. Returns `NULL` if not connected.
+Returns the connected SSID, or `NULL` if not connected.
#### `wifi_service_change_to_hotspot`
```c
void wifi_service_change_to_hotspot(const char *new_ssid);
```
-Dynamically reconfigures the device's Access Point to an **Open** network with the specified SSID.
-- Stops the Wi-Fi driver briefly to apply changes.
-- Sets `authmode` to `WIFI_AUTH_OPEN`.
-- Restarts Wi-Fi with the new configuration.
+Reconfigures the AP to an **Open** network with the given SSID: briefly stops the
+driver, sets `authmode` to `WIFI_AUTH_OPEN`, restarts with the new config.
### Promiscuous Mode
@@ -300,9 +303,8 @@ Dynamically reconfigures the device's Access Point to an **Open** network with t
```c
void wifi_service_promiscuous_start(wifi_promiscuous_cb_t cb, wifi_promiscuous_filter_t *filter);
```
-Enables promiscuous mode (sniffer) with a custom callback and filter.
-- `cb`: Function to handle captured packets.
-- `filter`: Filter mask (e.g., `WIFI_PROMIS_FILTER_MASK_MGMT`).
+Enables promiscuous (sniffer) mode with the given packet callback and filter mask
+(e.g. `WIFI_PROMIS_FILTER_MASK_MGMT`; `NULL` for no filter).
#### `wifi_service_promiscuous_stop`
```c
@@ -316,30 +318,32 @@ Disables promiscuous mode and clears the callback.
```c
void wifi_service_start_channel_hopping(void);
```
-Starts a background task that cycles the Wi-Fi interface through channels 1 to 13.
-- Useful for promiscuous mode applications (e.g., deauth detection).
-- Task memory is allocated in PSRAM if available.
+Starts a background task cycling channels 1..13 (250 ms cadence via
+`esp_wifi_set_channel`), useful for promiscuous applications (e.g. deauth
+detection). Both the task stack and TCB are allocated in **PSRAM** (`SPIRAM`) to
+spare internal RAM.
#### `wifi_service_stop_channel_hopping`
```c
void wifi_service_stop_channel_hopping(void);
```
-Stops the channel hopping task and frees associated memory resources.
+Stops the channel-hopping task and frees its resources.
### Configuration Storage
#### `wifi_service_save_ap_config`
```c
-esp_err_t wifi_service_save_ap_config(const char *ssid, const char *password, uint8_t max_conn, const char *ip_addr, bool enabled);
+esp_err_t wifi_service_save_ap_config(
+ const char *ssid, const char *password, uint8_t max_conn, const char *ip_addr, bool enabled);
```
-Saves the AP configuration to a JSON file (`/assets/config/wifi/wifi_ap.conf`).
-- Uses `cJSON` to serialize settings.
-- Persists data using the storage API.
-- **State Management:** If `enabled` is `true` and Wi-Fi is inactive, it calls `wifi_service_start()`. If `enabled` is `false` and Wi-Fi is active, it calls `wifi_service_stop()`.
+Serializes the settings with `cJSON` and writes them to `WIFI_AP_CONFIG_FILE`.
+**State management:** if `enabled` is `true` and Wi-Fi is inactive it calls
+`wifi_service_start()`; if `enabled` is `false` and Wi-Fi is active it calls
+`wifi_service_stop()`.
#### Individual Setters
-Helper functions to update a single configuration parameter while preserving others. They automatically save the config and trigger state changes if `enabled` is toggled.
-
+Helpers that update a single parameter while preserving the rest, auto-saving and
+triggering state changes when `enabled` toggles.
```c
esp_err_t wifi_service_set_enabled(bool enabled);
esp_err_t wifi_service_set_ap_ssid(const char *ssid);
@@ -348,24 +352,27 @@ esp_err_t wifi_service_set_ap_max_conn(uint8_t max_conn);
esp_err_t wifi_service_set_ap_ip(const char *ip_addr);
```
-**Internal Loader:** `wifi_service_load_ap_config` is called during initialization to read these settings. If `enabled` is found to be `false` in the config, `wifi_service_init` will initialize the driver but **not** start the radio.
+**Internal loader:** an internal `load_ap_config` runs during init. If `enabled`
+is `false` in the config, `wifi_service_init` brings the driver up but does **not**
+start the radio.
## Internal Implementation Details
### Event Handling
-A static `wifi_event_handler` manages Wi-Fi and IP events:
-- **WIFI_EVENT_AP_STACONNECTED:** Logs the MAC of the connected station and blinks Green.
-- **WIFI_EVENT_AP_STADISCONNECTED:** Blinks Red.
-- **IP_EVENT_AP_STAIPASSIGNED:** Logs IP assignment and blinks Green.
+A static `event_handler` (registered for `WIFI_EVENT` and `IP_EVENT`) manages
+station connect/disconnect and IP-assignment events, logging and driving LED
+feedback (green on connect / IP, red on disconnect).
### Thread Safety
-A `wifi_mutex` (Semaphore) is used to protect the scanning process (`wifi_service_scan`), preventing concurrent scan requests which could lead to resource conflicts.
+A mutex protects the scan path (`wifi_service_scan`) against concurrent scan
+requests.
### Channel Hopping Task
-The channel hopping feature runs as a static FreeRTOS task. It uses `esp_wifi_set_channel` to switch channels every 250ms. To optimize internal RAM usage, both the task stack and the Task Control Block (TCB) are allocated in **PSRAM** using the `SPIRAM` capability.
-
-### Castings & Memory Management
-- **cJSON:** Used extensively for parsing and generating configuration files.
-- **PSRAM Allocation:** Critical tasks and large buffers are allocated in PSRAM to preserve internal memory.
-- **Type Casting:** `event_data` is cast to specific event structures (e.g., `wifi_event_ap_staconnected_t*`) within handlers.
-- **String Handling:** `strncpy` is used safely with explicit null-termination to prevent buffer overflows when handling SSIDs and passwords.
+Runs as a static FreeRTOS task; stack and TCB are placed in **PSRAM** (`SPIRAM`)
+to preserve internal RAM.
+
+### Memory & String Handling
+`cJSON` is used for config parse/serialize; SSIDs/passwords are copied with
+`strncpy` plus explicit NUL-termination.
+
+
diff --git a/docs/ys_rfid2/README.md b/docs/ys_rfid2/README.md
new file mode 100644
index 000000000..f78a39017
--- /dev/null
+++ b/docs/ys_rfid2/README.md
@@ -0,0 +1,227 @@
+# YS-RFID2 UART RFID Reader Driver
+
+This component drives the YS-RFID2 serial RFID reader module. It reads the module's ASCII output over UART, parses the "card number" frames into card IDs, and delivers card-detected / card-removed events to the application through a callback. It is `firmware_p4` only.
+
+## Overview
+
+- **Location:** `firmware_p4/components/Drivers/ys_rfid2/`
+- **Header:** `include/ys_rfid2.h`
+- **Dependencies:** `pin_def`, `sys_prio`, `driver/uart`, `esp_timer`, `freertos`
+- **Interface:** UART (via the ESP-IDF `driver/uart` layer, wrapped by the local HAL)
+- **UART port:** `UART_NUM_2` (default)
+- **Baud rate:** 9600 8N1, no flow control (default)
+- **Pins:** TX = GPIO 24 (`GPIO_RFID_UART_TX_PIN`), RX = GPIO 25 (`GPIO_RFID_UART_RX_PIN`)
+
+## Module Structure
+
+The driver is split into three layers:
+
+| Layer | Files | Responsibility |
+|--------|-------|----------------|
+| Core | `ys_rfid2_core.c`, `include/ys_rfid2.h`, `include/ys_rfid2_types.h` | Public API, state machine, background reader task, debounce and removal logic, event dispatch. |
+| Parser | `ys_rfid2_parser.c`, `include/ys_rfid2_parser.h` | Byte-by-byte frame accumulation, card ID extraction and validation, decimal-to-bytes conversion. |
+| HAL | `hal/ys_rfid2_hal_uart.c`, `hal/include/ys_rfid2_hal_uart.h` | Thin UART wrapper: install/config/pins, read, write, flush. |
+
+Data flow: HAL UART reads raw bytes -> core reader task feeds each byte to the parser -> a completed valid frame becomes a `YS_RFID2_EVENT_CARD_DETECTED` event delivered to the user callback.
+
+## Frame Format and Parsing
+
+The module emits ASCII lines of the form:
+
+```
+card number: xxxxxxxxxx@
+```
+
+The parser (`ys_rfid2_parser_feed`) accumulates bytes into a 64-byte line buffer until it sees the `@` delimiter. On the delimiter it searches the buffer for the `"card number: "` prefix (13 chars) followed by exactly 10 decimal digits (`YS_RFID2_CARD_ID_LEN`). If found and all 10 characters are digits `0-9`, it:
+
+- Copies the 10-digit ASCII string into `id_str` (NUL-terminated).
+- Converts the decimal string to 5 big-endian raw bytes (40 bits) in `data`.
+- Sets `bit_count` to 40.
+
+Any byte that overflows the line buffer resets the accumulator. The parser holds a single static line buffer, so it is not reentrant.
+
+## Data Types (`ys_rfid2_types.h`)
+
+Constants:
+
+| Constant | Value | Meaning |
+|----------|-------|---------|
+| `YS_RFID2_CARD_ID_LEN` | 10 | Card ID string length (decimal digits) |
+| `YS_RFID2_RAW_DATA_LEN` | 5 | Raw byte count (40 bits) |
+
+### `ys_rfid2_state_t`
+
+`YS_RFID2_STATE_UNINITIALIZED`, `YS_RFID2_STATE_IDLE`, `YS_RFID2_STATE_SCANNING`, `YS_RFID2_STATE_ERROR`, `YS_RFID2_STATE_COUNT`.
+
+### `ys_rfid2_event_type_t`
+
+`YS_RFID2_EVENT_CARD_DETECTED`, `YS_RFID2_EVENT_CARD_REMOVED`, `YS_RFID2_EVENT_COUNT`.
+
+### `ys_rfid2_raw_data_t`
+```c
+typedef struct {
+ char id_str[YS_RFID2_CARD_ID_LEN + 1]; // 10-digit ID + NUL
+ uint8_t data[YS_RFID2_RAW_DATA_LEN]; // 5 raw bytes (40 bits, big-endian)
+ uint8_t bit_count; // 40 for a parsed card
+} ys_rfid2_raw_data_t;
+```
+
+### `ys_rfid2_event_t`
+```c
+typedef struct {
+ ys_rfid2_event_type_t type;
+ ys_rfid2_raw_data_t raw;
+ int64_t timestamp_ms; // esp_timer time in ms at detection
+} ys_rfid2_event_t;
+```
+
+### `ys_rfid2_event_cb_t`
+```c
+typedef void (*ys_rfid2_event_cb_t)(const ys_rfid2_event_t *event, void *ctx);
+```
+Invoked from the reader task context. Must not block for extended periods. The `event` pointer is valid only during the callback.
+
+### `ys_rfid2_config_t`
+```c
+typedef struct {
+ int uart_port;
+ int baud_rate;
+ int tx_pin;
+ int rx_pin;
+ uint32_t debounce_ms;
+ uint32_t removal_timeout_ms;
+} ys_rfid2_config_t;
+```
+
+## Configuration and Tunables
+
+`ys_rfid2_default_config()` returns:
+
+| Field | Default | Source |
+|-------|---------|--------|
+| `uart_port` | `UART_NUM_2` | fixed default |
+| `baud_rate` | 9600 | `RFID_DEFAULT_BAUD` |
+| `tx_pin` | GPIO 24 | `GPIO_RFID_UART_TX_PIN` (pin_def.h) |
+| `rx_pin` | GPIO 25 | `GPIO_RFID_UART_RX_PIN` (pin_def.h) |
+| `debounce_ms` | 1000 | `RFID_DEFAULT_DEBOUNCE_MS` |
+| `removal_timeout_ms` | 2000 | `RFID_DEFAULT_REMOVAL_MS` |
+
+- **debounce_ms:** while the same card ID keeps being read, repeat detections within this window are suppressed (the timer is refreshed on each read). A different card ID fires immediately.
+- **removal_timeout_ms:** if no byte is read for longer than this after the last detection, a `YS_RFID2_EVENT_CARD_REMOVED` event fires for the last card.
+
+Other internal constants (`ys_rfid2_core.c`): reader task stack 4096 bytes, priority `SYS_PRIO_SERVICE_HI`, UART read timeout 100 ms. The task is pinned to `SYS_CORE_RADIO` (core 0) via `xTaskCreatePinnedToCore`.
+
+## API Reference
+
+### Configuration
+
+#### `ys_rfid2_default_config`
+```c
+ys_rfid2_config_t ys_rfid2_default_config(void);
+```
+Returns the default configuration described above.
+
+### Lifecycle
+
+#### `ys_rfid2_init`
+```c
+esp_err_t ys_rfid2_init(const ys_rfid2_config_t *config);
+```
+Creates the driver mutex and initializes the UART via the HAL. Does NOT start scanning. Pass `NULL` to use the default config. Returns `ESP_OK`, `ESP_ERR_INVALID_STATE` if already initialized, `ESP_ERR_NO_MEM` if mutex creation fails, or a propagated HAL error.
+
+#### `ys_rfid2_deinit`
+```c
+esp_err_t ys_rfid2_deinit(void);
+```
+Stops scanning if active, tears down the UART, and deletes the mutex. Returns `ESP_OK`, or `ESP_ERR_INVALID_STATE` if not initialized.
+
+### Scanning
+
+#### `ys_rfid2_start`
+```c
+esp_err_t ys_rfid2_start(ys_rfid2_event_cb_t cb, void *ctx);
+```
+Resets the parser, flushes the UART input, and creates the background reader task that delivers events via `cb`. `cb` must not be NULL. Returns `ESP_OK`, `ESP_ERR_INVALID_ARG` if `cb` is NULL, `ESP_ERR_INVALID_STATE` if not initialized (state must be IDLE), or `ESP_ERR_NO_MEM` if task creation fails.
+
+#### `ys_rfid2_stop`
+```c
+void ys_rfid2_stop(void);
+```
+Signals the reader task to stop and blocks until it has exited, then clears the callback.
+
+### State and Query
+
+#### `ys_rfid2_get_state`
+```c
+ys_rfid2_state_t ys_rfid2_get_state(void);
+```
+Returns the current driver state.
+
+#### `ys_rfid2_get_last_card`
+```c
+esp_err_t ys_rfid2_get_last_card(ys_rfid2_event_t *out_event);
+```
+Copies the last detected card event into `out_event` (mutex-guarded, 100 ms take timeout). Returns `ESP_OK` if a card was previously detected, `ESP_ERR_NOT_FOUND` if none yet, `ESP_ERR_INVALID_ARG` if `out_event` is NULL, or `ESP_ERR_TIMEOUT` if the mutex could not be taken.
+
+## HAL UART Layer (`ys_rfid2_hal_uart.h`)
+
+A thin wrapper over ESP-IDF `driver/uart`. It installs the driver with a 256-byte RX buffer, configures 8N1 with no flow control and `UART_SCLK_DEFAULT`, and sets TX/RX pins (no RTS/CTS). It holds a single static port, so only one instance is supported at a time.
+
+#### `ys_rfid2_hal_uart_config_t`
+```c
+typedef struct {
+ int port;
+ int baud_rate;
+ int tx_pin;
+ int rx_pin;
+} ys_rfid2_hal_uart_config_t;
+```
+
+#### API
+```c
+esp_err_t ys_rfid2_hal_uart_init(const ys_rfid2_hal_uart_config_t *config);
+void ys_rfid2_hal_uart_deinit(void);
+int ys_rfid2_hal_uart_read(uint8_t *out_data, size_t len, uint32_t timeout_ms);
+esp_err_t ys_rfid2_hal_uart_write(const uint8_t *data, size_t len);
+void ys_rfid2_hal_uart_flush(void);
+```
+
+- `ys_rfid2_hal_uart_init`: returns `ESP_OK`, `ESP_ERR_INVALID_ARG` if `config` is NULL, `ESP_ERR_INVALID_STATE` if already initialized, or a propagated `uart_*` error.
+- `ys_rfid2_hal_uart_read`: wraps `uart_read_bytes`. Returns the number of bytes read, or -1 on error / not initialized.
+- `ys_rfid2_hal_uart_write`: wraps `uart_write_bytes`. Returns `ESP_OK`, `ESP_ERR_INVALID_STATE` if not initialized or `data` is NULL, or `ESP_FAIL` on write error.
+- `ys_rfid2_hal_uart_flush`: flushes the UART input buffer.
+
+## Parser Layer (`ys_rfid2_parser.h`)
+
+#### `ys_rfid2_parser_reset`
+```c
+void ys_rfid2_parser_reset(void);
+```
+Discards accumulated bytes and resets the line position.
+
+#### `ys_rfid2_parser_feed`
+```c
+bool ys_rfid2_parser_feed(uint8_t byte, ys_rfid2_raw_data_t *out_raw);
+```
+Feeds one byte. Returns `true` and fills `out_raw` when a complete, valid `"card number: xxxxxxxxxx@"` frame is parsed; otherwise `false`.
+
+## Usage Example
+
+```c
+static void on_card(const ys_rfid2_event_t *ev, void *ctx) {
+ if (ev->type == YS_RFID2_EVENT_CARD_DETECTED) {
+ ESP_LOGI("APP", "Card: %s", ev->raw.id_str);
+ } else {
+ ESP_LOGI("APP", "Card removed");
+ }
+}
+
+void app(void) {
+ ys_rfid2_config_t cfg = ys_rfid2_default_config();
+ ESP_ERROR_CHECK(ys_rfid2_init(&cfg));
+ ESP_ERROR_CHECK(ys_rfid2_start(on_card, NULL));
+ // ...
+ ys_rfid2_stop();
+ ys_rfid2_deinit();
+}
+```
diff --git a/firmware_c5/components/Applications/wifi/wifi_deauther.c b/firmware_c5/components/Applications/wifi/wifi_deauther.c
index dd1906df4..a1dcd463b 100644
--- a/firmware_c5/components/Applications/wifi/wifi_deauther.c
+++ b/firmware_c5/components/Applications/wifi/wifi_deauther.c
@@ -64,12 +64,8 @@ static void deauth_task(void *pvParameters);
void wifi_deauther_send_raw_frame(const uint8_t *frame_buffer, int size) {
esp_err_t ret = esp_wifi_80211_tx(WIFI_IF_AP, frame_buffer, size, false);
- if (ret != ESP_OK) {
+ if (ret != ESP_OK)
ESP_LOGD(TAG, "TX Fail: 0x%x", ret);
- led_blink_red();
- } else {
- led_blink_green();
- }
}
void wifi_deauther_send_deauth_frame(const wifi_ap_record_t *ap_record,
@@ -159,7 +155,6 @@ bool wifi_deauther_start(const wifi_ap_record_t *ap_record,
if (ap_record == NULL)
return false;
- // Clean up previous task memory if it wasn't freed
if (s_deauth_task_stack != NULL) {
free(s_deauth_task_stack);
s_deauth_task_stack = NULL;
@@ -262,7 +257,6 @@ bool wifi_deauther_start_targeted(const wifi_ap_record_t *ap_record,
void wifi_deauther_stop(void) {
if (s_is_running) {
s_is_running = false;
- // Give task time to finish loop and delete itself
vTaskDelay(pdMS_TO_TICKS(DEAUTHER_DELAY_MS + DEAUTHER_STOP_MARGIN_MS));
}
}
@@ -290,6 +284,7 @@ static const uint8_t *get_deauth_frame_template(wifi_deauther_frame_type_t type)
static void deauth_task(void *pvParameters) {
ESP_LOGI(TAG, "Deauther Task Started");
+ led_blink_green();
const uint8_t *frame_template = get_deauth_frame_template(s_type);
uint8_t frame[DEAUTH_FRAME_LEN];
diff --git a/firmware_c5/components/Applications/wifi/wifi_sniffer.c b/firmware_c5/components/Applications/wifi/wifi_sniffer.c
index ad3b4b9b4..3d2b6216b 100644
--- a/firmware_c5/components/Applications/wifi/wifi_sniffer.c
+++ b/firmware_c5/components/Applications/wifi/wifi_sniffer.c
@@ -793,19 +793,36 @@ static void sniffer_callback(void *buf, wifi_promiscuous_pkt_type_t type) {
}
if (is_save && spi_bridge_stream_is_enabled(SPI_ID_WIFI_APP_SNIFFER)) {
+ uint16_t total_len = ppkt->rx_ctrl.sig_len;
+ if (total_len > SPI_WIFI_SNIFFER_FRAME_MAX)
+ total_len = SPI_WIFI_SNIFFER_FRAME_MAX; // bounded by the P4 reassembly buffer
+
uint8_t stream_buf[SPI_MAX_PAYLOAD];
- spi_wifi_sniffer_frame_t *stream = (spi_wifi_sniffer_frame_t *)stream_buf;
- uint16_t raw_len = ppkt->rx_ctrl.sig_len;
- if (raw_len > SPI_WIFI_SNIFFER_MAX_DATA)
- raw_len = SPI_WIFI_SNIFFER_MAX_DATA;
- stream->rssi = ppkt->rx_ctrl.rssi;
- stream->channel = ppkt->rx_ctrl.channel;
- stream->len = (uint8_t)raw_len;
- memcpy(stream->data, ppkt->payload, raw_len);
- if (s_session_id != SPI_SESSION_INVALID_ID) {
- session_manager_try_emit(s_session_id, stream_buf, (uint8_t)(raw_len + 3));
- } else {
- spi_bridge_stream_push(SPI_ID_WIFI_APP_SNIFFER, stream_buf, (uint8_t)(raw_len + 3));
+ spi_wifi_sniffer_frame_t *frag = (spi_wifi_sniffer_frame_t *)stream_buf;
+
+ // Split the frame into ordered fragments; the P4 reassembles them. A frame
+ // that fits in one transfer is just a single fragment (offset 0, no MORE).
+ for (uint16_t off = 0; off < total_len;) {
+ uint16_t chunk = total_len - off;
+ if (chunk > SPI_WIFI_SNIFFER_FRAG_DATA_MAX)
+ chunk = SPI_WIFI_SNIFFER_FRAG_DATA_MAX;
+
+ frag->rssi = ppkt->rx_ctrl.rssi;
+ frag->channel = ppkt->rx_ctrl.channel;
+ frag->total_len = total_len;
+ frag->frag_off = off;
+ frag->frag_len = (uint8_t)chunk;
+ frag->flags = ((off + chunk) < total_len) ? SPI_WIFI_SNIFFER_FRAG_MORE : 0;
+ memcpy(frag->data, ppkt->payload + off, chunk);
+
+ uint8_t buf_len = (uint8_t)(sizeof(spi_wifi_sniffer_frame_t) + chunk);
+ bool ok = (s_session_id != SPI_SESSION_INVALID_ID)
+ ? (session_manager_try_emit(s_session_id, stream_buf, buf_len) == ESP_OK)
+ : spi_bridge_stream_push(SPI_ID_WIFI_APP_SNIFFER, stream_buf, buf_len);
+ if (!ok)
+ break; // backpressure: drop the rest; the P4 discards the partial frame
+
+ off += chunk;
}
}
diff --git a/firmware_c5/components/Drivers/i2c_init/README.md b/firmware_c5/components/Drivers/i2c_init/README.md
new file mode 100644
index 000000000..f1b430fe9
--- /dev/null
+++ b/firmware_c5/components/Drivers/i2c_init/README.md
@@ -0,0 +1,7 @@
+# I2C Init - C5
+
+Documentation for this component lives in the project docs hub (single source of truth):
+
+- [docs/i2c_init/README.md](../../../../docs/i2c_init/README.md)
+
+This file is only a pointer: edit the docs there to keep things from drifting.
diff --git a/firmware_c5/components/Drivers/led/README.md b/firmware_c5/components/Drivers/led/README.md
new file mode 100644
index 000000000..11dd3207b
--- /dev/null
+++ b/firmware_c5/components/Drivers/led/README.md
@@ -0,0 +1,7 @@
+# LED - C5 (LP5816 status LED)
+
+Documentation for this component lives in the project docs hub (single source of truth):
+
+- [docs/led/README.md](../../../../docs/led/README.md)
+
+This file is only a pointer: edit the docs there to keep things from drifting.
diff --git a/firmware_c5/components/Drivers/pins/README.md b/firmware_c5/components/Drivers/pins/README.md
new file mode 100644
index 000000000..6a3242d82
--- /dev/null
+++ b/firmware_c5/components/Drivers/pins/README.md
@@ -0,0 +1,7 @@
+# Pins - C5 (GPIO map)
+
+Documentation for this component lives in the project docs hub (single source of truth):
+
+- [docs/pins/README.md](../../../../docs/pins/README.md)
+
+This file is only a pointer: edit the docs there to keep things from drifting.
diff --git a/firmware_c5/components/Service/spi_bridge/include/spi_protocol.h b/firmware_c5/components/Service/spi_bridge/include/spi_protocol.h
index 707abeab0..02c4c350e 100644
--- a/firmware_c5/components/Service/spi_bridge/include/spi_protocol.h
+++ b/firmware_c5/components/Service/spi_bridge/include/spi_protocol.h
@@ -575,15 +575,37 @@ typedef struct {
} __attribute__((packed)) spi_wifi_scan_record_t;
/**
- * @brief WiFi sniffer stream frame.
+ * @brief WiFi sniffer stream fragment.
+ *
+ * A single 802.11 frame can exceed one SPI transfer (payload capped at
+ * SPI_MAX_PAYLOAD), so the C5 splits large frames into ordered fragments and
+ * the P4 reassembles them. rssi/channel/total_len are repeated on every
+ * fragment so the P4 can validate cheaply. frag_off is this fragment's byte
+ * offset within the full frame; SPI_WIFI_SNIFFER_FRAG_MORE is set on every
+ * fragment except the last. A frame that fits in one transfer is a single
+ * fragment with frag_off == 0 and the MORE bit clear.
+ *
+ * Max data bytes per fragment = SPI_MAX_PAYLOAD - sizeof(spi_stream_meta_t)
+ * - sizeof(spi_wifi_sniffer_frame_t).
*/
typedef struct {
- int8_t rssi;
- uint8_t channel;
- uint8_t len;
+ int8_t rssi; // dBm signal of the frame
+ uint8_t channel; // primary channel
+ uint16_t total_len; // full 802.11 frame length across all fragments
+ uint16_t frag_off; // byte offset of this fragment within the frame
+ uint8_t frag_len; // data bytes carried by this fragment
+ uint8_t flags; // SPI_WIFI_SNIFFER_FRAG_* bits
uint8_t data[0];
} __attribute__((packed)) spi_wifi_sniffer_frame_t;
+#define SPI_WIFI_SNIFFER_FRAG_MORE 0x01u // more fragments follow this one
+#define SPI_WIFI_SNIFFER_FRAME_MAX 2346 // max reassembled 802.11 frame (bytes)
+
+// Max frame bytes carried by a single fragment (transfer cap minus the stream
+// meta prepended by the session layer minus this fragment header).
+#define SPI_WIFI_SNIFFER_FRAG_DATA_MAX \
+ ((int)(SPI_MAX_PAYLOAD - sizeof(spi_stream_meta_t) - sizeof(spi_wifi_sniffer_frame_t)))
+
/**
* @brief BLE sniffer stream frame.
*/
diff --git a/firmware_c5/components/Service/wifi/wifi_service.c b/firmware_c5/components/Service/wifi/wifi_service.c
index 1d865b5fd..2d709c898 100644
--- a/firmware_c5/components/Service/wifi/wifi_service.c
+++ b/firmware_c5/components/Service/wifi/wifi_service.c
@@ -76,8 +76,6 @@ static void get_config_defaults(char *out_ssid,
char *out_ip_addr,
bool *out_enabled);
-// Public function implementations
-
void wifi_service_init(void) {
esp_err_t err;
@@ -114,8 +112,6 @@ void wifi_service_init(void) {
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
- // Dual-band (2.4 + 5 GHz) BEFORE configuring the AP: a stale 5G-only band mode
- // persisted in NVS otherwise rejects the 2.4 GHz AP channel and aborts set_config.
esp_err_t band_err = esp_wifi_set_band_mode(WIFI_BAND_MODE_AUTO);
if (band_err != ESP_OK) {
ESP_LOGW(TAG, "Could not enable dual-band: %s", esp_err_to_name(band_err));
@@ -166,10 +162,6 @@ void wifi_service_init(void) {
if (is_enabled) {
ESP_ERROR_CHECK(esp_wifi_start());
s_is_active = true;
- // Enable modem sleep explicitly. It only takes effect for an idle, connected
- // STA (radio wakes per DTIM); AP mode and promiscuous sniffing keep the radio
- // in continuous RX regardless. The real idle savings need the P4 to signal
- // low-power over the bridge so the C5 can drop the radio (see power state API).
esp_wifi_set_ps(WIFI_PS_MIN_MODEM);
ESP_LOGI(TAG, "Wi-Fi AP started with SSID: %s", target_ssid);
} else {
@@ -565,14 +557,10 @@ void wifi_service_promiscuous_stop(void) {
}
bool wifi_service_is_busy(void) {
- // A capture is in flight (sniffer / deauth detector / handshake). Do not drop
- // the radio for power saving while this is true.
return s_promiscuous_active;
}
void wifi_service_set_power_save(bool deep) {
- // deep = screen dimmed (MAX_MODEM, longer beacon skips); otherwise MIN_MODEM.
- // Only meaningful for an idle connected STA; harmless otherwise.
esp_wifi_set_ps(deep ? WIFI_PS_MAX_MODEM : WIFI_PS_MIN_MODEM);
}
@@ -637,31 +625,24 @@ void wifi_service_stop_channel_hopping(void) {
ESP_LOGI(TAG, "Channel hopping stopped");
}
-// Static function implementations
-
static void
event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data;
ESP_LOGI(TAG, "Station connected to AP, MAC: " MACSTR, MAC2STR(event->mac));
- led_blink_green();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) {
- led_blink_red();
+ ESP_LOGI(TAG, "Station disconnected from AP");
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Disconnected from AP");
s_is_connected = false;
} else if (event_base == IP_EVENT && event_id == IP_EVENT_AP_STAIPASSIGNED) {
ESP_LOGI(TAG, "IP assigned to station connected to AP");
- led_blink_green();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ESP_LOGI(TAG, "Got IP address, Wi-Fi connected");
s_is_connected = true;
}
}
-// 2.4 GHz (1-13) plus the common non-DFS 5 GHz channels (UNII-1 + UNII-3). DFS
-// channels (52-144) need radar detection and aren't usable for passive hopping,
-// so they're left out. esp_wifi_set_channel picks the band from the number.
static const uint8_t HOP_CHANNELS[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 36, 40, 44, 48, 149, 153, 157, 161, 165};
#define HOP_CHANNEL_COUNT (sizeof(HOP_CHANNELS) / sizeof(HOP_CHANNELS[0]))
diff --git a/firmware_p4/CMakeLists.txt b/firmware_p4/CMakeLists.txt
index 54191944d..68ad194c1 100644
--- a/firmware_p4/CMakeLists.txt
+++ b/firmware_p4/CMakeLists.txt
@@ -8,8 +8,26 @@ cmake_minimum_required(VERSION 3.16)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(IDF_TARGET "esp32p4")
+set(EXTRA_COMPONENT_DIRS
+ "${CMAKE_CURRENT_LIST_DIR}/components/Applications/gameboy"
+ "${CMAKE_CURRENT_LIST_DIR}/components/Applications/doom")
+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+# The -O2 perf build (CONFIG_COMPILER_OPTIMIZATION_PERF) makes GCC run the flow
+# analysis behind -Wstringop-truncation / -Wformat-truncation / -Wstringop-overflow
+# / -Warray-bounds / -Wmaybe-uninitialized. With the IDF default -Werror=all these
+# turn a cascade of pre-existing warnings across unrelated code into hard errors.
+# Keep -O2 and -Werror everywhere else; demote just these noisy -O2 analysis
+# classes to warnings (appended after IDF's flags so they win). NOTE: some of the
+# stringop-overflow / array-bounds hits (LoRa mesh varint/resp buffers) are REAL
+# undersized-buffer bugs, not false positives — track them for a dedicated fix.
+idf_build_set_property(COMPILE_OPTIONS "-Wno-error=stringop-truncation" APPEND)
+idf_build_set_property(COMPILE_OPTIONS "-Wno-error=format-truncation" APPEND)
+idf_build_set_property(COMPILE_OPTIONS "-Wno-error=stringop-overflow" APPEND)
+idf_build_set_property(COMPILE_OPTIONS "-Wno-error=array-bounds" APPEND)
+idf_build_set_property(COMPILE_OPTIONS "-Wno-error=maybe-uninitialized" APPEND)
+
project(TentacleOS_P4)
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
diff --git a/firmware_p4/assets/icons/game_brk.png b/firmware_p4/assets/icons/game_brk.png
new file mode 100644
index 000000000..5e4a14ddc
Binary files /dev/null and b/firmware_p4/assets/icons/game_brk.png differ
diff --git a/firmware_p4/assets/icons/game_flap.png b/firmware_p4/assets/icons/game_flap.png
new file mode 100644
index 000000000..677683541
Binary files /dev/null and b/firmware_p4/assets/icons/game_flap.png differ
diff --git a/firmware_p4/assets/icons/game_gb.png b/firmware_p4/assets/icons/game_gb.png
new file mode 100644
index 000000000..106c3c75d
Binary files /dev/null and b/firmware_p4/assets/icons/game_gb.png differ
diff --git a/firmware_p4/assets/icons/game_pet.png b/firmware_p4/assets/icons/game_pet.png
new file mode 100644
index 000000000..18f05611b
Binary files /dev/null and b/firmware_p4/assets/icons/game_pet.png differ
diff --git a/firmware_p4/assets/icons/game_snake.png b/firmware_p4/assets/icons/game_snake.png
new file mode 100644
index 000000000..4f1fc510b
Binary files /dev/null and b/firmware_p4/assets/icons/game_snake.png differ
diff --git a/firmware_p4/assets/storage/bad_usb_scripts/amiga.txt b/firmware_p4/assets/storage/bad_usb_scripts/amiga.txt
new file mode 100644
index 000000000..6f400b1c3
--- /dev/null
+++ b/firmware_p4/assets/storage/bad_usb_scripts/amiga.txt
@@ -0,0 +1,137 @@
+REM Amiga ASCII art (LGB) - types the art into the focused window
+REM Focus a text editor first. END before ENTER stops the editor auto-indent
+REM from accumulating; HOME returns to column 0 for the next line.
+DELAY 1000
+HOME
+STRING ___..-.---.---.--..___
+END
+ENTER
+HOME
+STRING _..-- `.`. `. `. `. --.._
+END
+ENTER
+HOME
+STRING / ___________\ \ \______ \
+END
+ENTER
+HOME
+STRING | |.-----------`. `. `.---.| |
+END
+ENTER
+HOME
+STRING |`. |' \`. \ \ \ '| |
+END
+ENTER
+HOME
+STRING |`. |' \ `-._ `. `. `.'| |
+END
+ENTER
+HOME
+STRING /| |' `-._o)\ /(o\ \ \| |\
+END
+ENTER
+HOME
+STRING .' | |' `. .' '. `. `. `. | `.
+END
+ENTER
+HOME
+STRING / .| |' `. (_.==._) \ \ \ |. \ _.--.
+END
+ENTER
+HOME
+STRING .' .' | |' _.-======-._ `. `. `. `. `. _.-_.-'\\
+END
+ENTER
+HOME
+STRING / / | |' .' |_||_| `. \ \ \ \ \ .'_.' ||
+END
+ENTER
+HOME
+STRING / .' |`. |' /_.-'========`-._\ `. `-._`._`. \(.__ :|
+END
+ENTER
+HOME
+STRING ( ' |`. |'.______________________.'\ _.) ` )`-._`-._/ /
+END
+ENTER
+HOME
+STRING \\ | '.------------------------.'`-._-' // `-._.'
+END
+ENTER
+HOME
+STRING _\\_ \ | AMIGA O O O O * * `.`.| ' //
+END
+ENTER
+HOME
+STRING (_ _) '-._|________________________|_.-'| _//_
+END
+ENTER
+HOME
+STRING / / /`-._ |`-._ / / / | (_ _)
+END
+ENTER
+HOME
+STRING .' \ |`-._ `-._ `-._`-._/ / / | \ \
+END
+ENTER
+HOME
+STRING / `. | `-._ `-._ `-._|/ / | / `.
+END
+ENTER
+HOME
+STRING / / / /. ) | `-._ `-._ `-._ / / .' \
+END
+ENTER
+HOME
+STRING | | | \ \|/ | `-._`-._ `-._ `-._ / /. ( .\ \ \ \
+END
+ENTER
+HOME
+STRING \ \ \ \/ | `-._`-._`-._ `-._ `-._/ / \ \|/ / | | |
+END
+ENTER
+HOME
+STRING `.\_\/ `-._ `-._`-._`-._ `-._/| /| \ \/ / / /
+END
+ENTER
+HOME
+STRING / `-._ `-._`-._`-._ || / | \ \/_/.'
+END
+ENTER
+HOME
+STRING .' `-._ `-._`-._ || / | \
+END
+ENTER
+HOME
+STRING LGB / / . `-._ `-._ || / | \
+END
+ENTER
+HOME
+STRING '\ / / `-._ ||/'._.' \
+END
+ENTER
+HOME
+STRING \`. .' / `-._|/ \
+END
+ENTER
+HOME
+STRING `.`-._.' .' \ .'
+END
+ENTER
+HOME
+STRING `-.__\/ `\ .' '
+END
+ENTER
+HOME
+STRING \`. _.' .'
+END
+ENTER
+HOME
+STRING `.`-._.-' _.'
+END
+ENTER
+HOME
+STRING `-.__.-'
+END
+ENTER
+HOME
diff --git a/firmware_p4/components/Applications/CMakeLists.txt b/firmware_p4/components/Applications/CMakeLists.txt
index c9f7e1884..e5a7d22c7 100644
--- a/firmware_p4/components/Applications/CMakeLists.txt
+++ b/firmware_p4/components/Applications/CMakeLists.txt
@@ -18,16 +18,14 @@
# ui_manager.c and assets_manager.c at the ui/ root) — new screens compile
# automatically. INCLUDE_DIRS is the only hand-maintained part; add a screen's
# include/ dir there when a new screen area is introduced.
+# Globs every ui/**/*.c (e.g. screens/files/usb_storage_ui.c). NOTE: adding a new
+# .c under ui/ needs a CMake reconfigure to re-run this glob.
file(GLOB_RECURSE UI_SRCS "ui/*.c")
list(REMOVE_ITEM UI_SRCS
- "${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/games_menu_ui.c"
"${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/flappy_ui.c"
- "${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/snake_ui.c"
- "${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/breakout_ui.c"
"${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/octopet_ui.c"
"${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/motion_ui.c"
- "${CMAKE_CURRENT_SOURCE_DIR}/ui/screens/games/game_fx.c"
)
# Application (non-UI) sources — the real drivers/services stay in the build.
@@ -83,6 +81,8 @@ idf_component_register(SRCS
"ui/include"
# --- ui screens (mirrors firmware_p4_prototype) ---
"ui/screens/audio/include"
+ "ui/screens/video/include"
+ "ui/screens/images/include"
"ui/screens/badusb/include"
"ui/screens/bluetooth/include"
"ui/screens/boot/include"
@@ -139,8 +139,11 @@ idf_component_register(SRCS
REQUIRES
driver
esp_driver_tsens
+ esp_driver_jpeg
Drivers
Service
+ gameboy
+ doom
esp_common
esp_wifi
esp_tinyusb
@@ -151,6 +154,7 @@ idf_component_register(SRCS
joltwallet__littlefs
nvs_flash
espressif__esp-dsp
+ chmorgan__esp-libhelix-mp3
)
target_link_libraries(${COMPONENT_LIB} -Wl,-zmuldefs)
target_compile_definitions(${COMPONENT_LIB} PRIVATE MESH_HEADLESS=0)
diff --git a/firmware_p4/components/Applications/LoRa/README.md b/firmware_p4/components/Applications/LoRa/README.md
new file mode 100644
index 000000000..67f10556a
--- /dev/null
+++ b/firmware_p4/components/Applications/LoRa/README.md
@@ -0,0 +1,7 @@
+# LoRa Application
+
+Documentation for this component lives in the project docs hub (single source of truth):
+
+- [docs/LoRa/README.md](../../../../docs/LoRa/README.md)
+
+This file is only a pointer: edit the docs there to keep things from drifting.
diff --git a/firmware_p4/components/Applications/LoRa/meshcore/include/meshcore_phone_bridge.h b/firmware_p4/components/Applications/LoRa/meshcore/include/meshcore_phone_bridge.h
index 592874304..6906a41bf 100644
--- a/firmware_p4/components/Applications/LoRa/meshcore/include/meshcore_phone_bridge.h
+++ b/firmware_p4/components/Applications/LoRa/meshcore/include/meshcore_phone_bridge.h
@@ -81,6 +81,16 @@ esp_err_t meshcore_phone_bridge_ble_stop(void);
*/
bool meshcore_phone_bridge_is_connected(void);
+/**
+ * @brief Whether the connected phone has subscribed to notifications, i.e. the
+ * companion app is actually paired and ready (not just BLE-linked).
+ *
+ * Use this for the "linked" UI state: ble_connected goes true at the raw BLE
+ * link (before the pairing PIN even shows), while ble_subscribed only goes true
+ * after encryption + the app subscribing.
+ */
+bool meshcore_phone_bridge_is_subscribed(void);
+
#ifdef __cplusplus
}
#endif
diff --git a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_db.c b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_db.c
index 1818c6306..20264b754 100644
--- a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_db.c
+++ b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_db.c
@@ -15,6 +15,8 @@
#include "meshcore_internal.h"
+#include "esp_attr.h"
+
#include
#include
@@ -60,7 +62,7 @@ typedef struct {
} mc_pending_t;
static meshcore_channel_t s_channels[MESHCORE_MAX_CHANNELS];
-static meshcore_contact_t s_contacts[MESHCORE_MAX_CONTACTS];
+EXT_RAM_BSS_ATTR static meshcore_contact_t s_contacts[MESHCORE_MAX_CONTACTS];
static mc_dedup_entry_t s_dedup[MC_DEDUP_SIZE];
static uint16_t s_dedup_idx = 0;
diff --git a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phone_bridge.c b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phone_bridge.c
index bcc906a74..02d74e1b1 100644
--- a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phone_bridge.c
+++ b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phone_bridge.c
@@ -163,6 +163,18 @@ bool meshcore_phone_bridge_is_connected(void) {
return is_connected;
}
+bool meshcore_phone_bridge_is_subscribed(void) {
+ bool is_subscribed = false;
+ if (s_status_mutex == NULL) {
+ return false;
+ }
+ if (xSemaphoreTake(s_status_mutex, pdMS_TO_TICKS(BRIDGE_STATUS_TICK_MS)) == pdTRUE) {
+ is_subscribed = (s_cached_status.ble_subscribed != 0);
+ xSemaphoreGive(s_status_mutex);
+ }
+ return is_subscribed;
+}
+
static void status_task(void *pvParameters) {
(void)pvParameters;
diff --git a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phoneapi.c b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phoneapi.c
index 33bad58f8..ad6ab661a 100644
--- a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phoneapi.c
+++ b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_phoneapi.c
@@ -15,6 +15,8 @@
#include "meshcore_phoneapi.h"
+#include "esp_attr.h"
+
#include
#include
#include
@@ -22,6 +24,8 @@
#include "esp_err.h"
#include "esp_log.h"
#include "esp_random.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
#include "meshcore.h"
#include "meshcore_internal.h"
@@ -103,8 +107,9 @@ static const char *TAG = "MC_PHONEAPI";
#define CONTACT_FRAME_SIZE 143
-#define MC_OFFLINE_QUEUE_SIZE 16
-#define MC_OFFLINE_FRAME_MAX 240
+#define MC_OFFLINE_QUEUE_SIZE 16
+#define MC_OFFLINE_FRAME_MAX 240
+#define MC_QUEUE_MUTEX_TIMEOUT_MS 100
#define MC_NVS_KEY_BLE_PIN "ble_pin"
#define MC_NVS_KEY_AUTOADD "autoadd"
@@ -136,8 +141,9 @@ static uint8_t s_autoadd_max_hops = MC_AUTOADD_DEFAULT_MAX_HOPS;
static meshcore_phoneapi_outbound_cb_t s_outbound_cb = NULL;
static void *s_outbound_ctx = NULL;
-static mc_queued_frame_t s_offline_queue[MC_OFFLINE_QUEUE_SIZE];
+EXT_RAM_BSS_ATTR static mc_queued_frame_t s_offline_queue[MC_OFFLINE_QUEUE_SIZE];
static uint8_t s_offline_queue_len = 0;
+static SemaphoreHandle_t s_queue_mutex = NULL;
static void send_resp(const uint8_t *buf, uint16_t len);
static void send_err(uint8_t sub);
@@ -193,6 +199,12 @@ esp_err_t meshcore_phoneapi_init(void) {
s_offline_queue_len = 0;
s_outbound_cb = NULL;
s_outbound_ctx = NULL;
+ if (s_queue_mutex == NULL) {
+ s_queue_mutex = xSemaphoreCreateMutex();
+ if (s_queue_mutex == NULL) {
+ return ESP_ERR_NO_MEM;
+ }
+ }
s_is_initialized = true;
ESP_LOGI(TAG, "Initialized — PIN=%lu", (unsigned long)s_ble_pin);
return ESP_OK;
@@ -209,7 +221,11 @@ uint32_t meshcore_phoneapi_get_pin(void) {
void meshcore_phoneapi_on_disconnect(void) {
s_app_target_ver = 0;
+ if (s_queue_mutex != NULL)
+ xSemaphoreTake(s_queue_mutex, portMAX_DELAY);
s_offline_queue_len = 0;
+ if (s_queue_mutex != NULL)
+ xSemaphoreGive(s_queue_mutex);
}
void meshcore_phoneapi_on_inbound(const uint8_t *buf, uint16_t len) {
@@ -460,6 +476,10 @@ static uint32_t load_or_generate_ble_pin(void) {
static void offline_queue_push(const uint8_t *frame, uint16_t len) {
if (len == 0 || len > MC_OFFLINE_FRAME_MAX)
return;
+ if (s_queue_mutex != NULL &&
+ xSemaphoreTake(s_queue_mutex, pdMS_TO_TICKS(MC_QUEUE_MUTEX_TIMEOUT_MS)) != pdTRUE) {
+ return;
+ }
if (s_offline_queue_len >= MC_OFFLINE_QUEUE_SIZE) {
for (int i = 0; i < MC_OFFLINE_QUEUE_SIZE - 1; i++) {
s_offline_queue[i] = s_offline_queue[i + 1];
@@ -469,17 +489,28 @@ static void offline_queue_push(const uint8_t *frame, uint16_t len) {
s_offline_queue[s_offline_queue_len].len = len;
memcpy(s_offline_queue[s_offline_queue_len].buf, frame, len);
s_offline_queue_len++;
+ if (s_queue_mutex != NULL)
+ xSemaphoreGive(s_queue_mutex);
}
static bool offline_queue_pop(uint8_t *out, uint16_t *out_len) {
- if (s_offline_queue_len == 0)
+ if (s_queue_mutex != NULL &&
+ xSemaphoreTake(s_queue_mutex, pdMS_TO_TICKS(MC_QUEUE_MUTEX_TIMEOUT_MS)) != pdTRUE) {
+ return false;
+ }
+ if (s_offline_queue_len == 0) {
+ if (s_queue_mutex != NULL)
+ xSemaphoreGive(s_queue_mutex);
return false;
+ }
*out_len = s_offline_queue[0].len;
memcpy(out, s_offline_queue[0].buf, *out_len);
for (int i = 0; i < s_offline_queue_len - 1; i++) {
s_offline_queue[i] = s_offline_queue[i + 1];
}
s_offline_queue_len--;
+ if (s_queue_mutex != NULL)
+ xSemaphoreGive(s_queue_mutex);
return true;
}
@@ -514,6 +545,8 @@ static void deserialize_contact(meshcore_contact_t *c, const uint8_t in[], size_
c->type = in[o++];
c->flags = in[o++];
c->out_path_len = in[o++];
+ if (c->out_path_len != MESHCORE_OUT_PATH_UNKNOWN && c->out_path_len > MESHCORE_MAX_PATH)
+ c->out_path_len = MESHCORE_MAX_PATH;
memcpy(c->out_path, &in[o], MESHCORE_MAX_PATH);
o += MESHCORE_MAX_PATH;
char tmp[MESHCORE_NAME_MAX + 1] = {0};
@@ -545,7 +578,7 @@ static void handle_device_query(const uint8_t *p, uint16_t len) {
s_app_target_ver = p[1];
ESP_LOGI(TAG, "app_target_ver = %u", s_app_target_ver);
}
- uint8_t resp[80];
+ uint8_t resp[82];
uint16_t o = 0;
resp[o++] = RESP_CODE_DEVICE_INFO;
resp[o++] = FIRMWARE_VER_CODE;
@@ -925,9 +958,12 @@ static void handle_get_advert_path(const uint8_t *p, uint16_t len) {
if (c == NULL || c->out_path_len == MESHCORE_OUT_PATH_UNKNOWN) {
resp[o++] = 0;
} else {
- resp[o++] = c->out_path_len;
- memcpy(&resp[o], c->out_path, c->out_path_len);
- o += c->out_path_len;
+ uint8_t plen = c->out_path_len;
+ if (plen > MESHCORE_MAX_PATH)
+ plen = MESHCORE_MAX_PATH;
+ resp[o++] = plen;
+ memcpy(&resp[o], c->out_path, plen);
+ o += plen;
}
send_resp(resp, o);
}
diff --git a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_router.c b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_router.c
index bbb874a40..07d15300f 100644
--- a/firmware_p4/components/Applications/LoRa/meshcore/meshcore_router.c
+++ b/firmware_p4/components/Applications/LoRa/meshcore/meshcore_router.c
@@ -658,14 +658,21 @@ static void on_rx_done(const sx1262_packet_t *pkt, void *ctx) {
(void)ctx;
if (pkt == NULL)
return;
+ ESP_LOGD(TAG,
+ "RX diag: %u bytes rssi=%d snr=%d crc_err=%d hdr_err=%d",
+ pkt->len,
+ pkt->rssi_pkt_dbm,
+ pkt->snr_pkt_db,
+ (int)pkt->has_crc_error,
+ (int)pkt->has_header_error);
if (pkt->has_crc_error || pkt->has_header_error) {
- ESP_LOGW(TAG, "RX HW error -- skip");
+ ESP_LOGD(TAG, "RX HW error -- skip");
return;
}
meshcore_packet_view_t view;
if (!meshcore_packet_parse(pkt->buf, pkt->len, &view)) {
- ESP_LOGW(TAG, "RX parse failed (%d bytes)", pkt->len);
+ ESP_LOGD(TAG, "RX parse failed (%d bytes)", pkt->len);
return;
}
view.rssi_dbm = pkt->rssi_pkt_dbm;
diff --git a/firmware_p4/components/Applications/LoRa/meshtastic/include/meshtastic_phone_bridge.h b/firmware_p4/components/Applications/LoRa/meshtastic/include/meshtastic_phone_bridge.h
index 0dc955158..867c9789d 100644
--- a/firmware_p4/components/Applications/LoRa/meshtastic/include/meshtastic_phone_bridge.h
+++ b/firmware_p4/components/Applications/LoRa/meshtastic/include/meshtastic_phone_bridge.h
@@ -94,6 +94,13 @@ esp_err_t meshtastic_phone_bridge_wifi_stop(void);
*/
bool meshtastic_phone_bridge_is_connected(void);
+/**
+ * @brief Whether the phone app is actually paired and ready: BLE subscribed
+ * (after encryption) or a TCP client is attached — not just BLE-linked.
+ * Use this for the "linked" UI state.
+ */
+bool meshtastic_phone_bridge_is_subscribed(void);
+
#ifdef __cplusplus
}
#endif
diff --git a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_mesh.c b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_mesh.c
index 18d386b20..75318b933 100644
--- a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_mesh.c
+++ b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_mesh.c
@@ -1181,7 +1181,7 @@ static void on_rx_done(const sx1262_packet_t *pkt, void *ctx) {
if (pkt == NULL)
return;
if (pkt->has_crc_error || pkt->has_header_error) {
- ESP_LOGW(TAG, "RX: erro HW, ignoring");
+ ESP_LOGD(TAG, "RX: erro HW, ignoring");
if (s_is_running)
sx1262_receive_continuous();
diff --git a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_nodedb.c b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_nodedb.c
index c1cd02761..8adac8efc 100644
--- a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_nodedb.c
+++ b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_nodedb.c
@@ -28,7 +28,7 @@ static const char *TAG = "MT_NODEDB";
#define MT_NODEDB_NVS_KEY "nodedb"
-static mt_node_entry_t s_nodes[MT_NODEDB_MAX_NODES];
+EXT_RAM_BSS_ATTR static mt_node_entry_t s_nodes[MT_NODEDB_MAX_NODES];
static uint16_t s_count = 0;
static uint64_t dec_varint(const uint8_t *buf, uint16_t max_len, uint16_t *out_used) {
diff --git a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phone_bridge.c b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phone_bridge.c
index a1f1726d4..8776d7671 100644
--- a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phone_bridge.c
+++ b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phone_bridge.c
@@ -31,7 +31,7 @@
static const char *TAG = "MESH_BRIDGE";
-#define BRIDGE_NOTIFY_TASK_STACK 4096
+#define BRIDGE_NOTIFY_TASK_STACK 8192
#define BRIDGE_NOTIFY_TASK_PRIO SYS_PRIO_SERVICE_HI
#define BRIDGE_NOTIFY_TICK_MS 100
#define BRIDGE_SPI_TIMEOUT_MS 1000
@@ -184,6 +184,18 @@ bool meshtastic_phone_bridge_is_connected(void) {
return is_connected;
}
+bool meshtastic_phone_bridge_is_subscribed(void) {
+ bool is_subscribed = false;
+ if (s_status_mutex == NULL) {
+ return false;
+ }
+ if (xSemaphoreTake(s_status_mutex, pdMS_TO_TICKS(BRIDGE_NOTIFY_TICK_MS)) == pdTRUE) {
+ is_subscribed = (s_cached_status.ble_subscribed != 0) || (s_cached_status.tcp_clients != 0);
+ xSemaphoreGive(s_status_mutex);
+ }
+ return is_subscribed;
+}
+
static void notify_task(void *pvParameters) {
(void)pvParameters;
uint8_t frame_buf[BRIDGE_FROMRADIO_BUF_SIZE];
diff --git a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phoneapi.c b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phoneapi.c
index 38da59a6b..e6aefdf11 100644
--- a/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phoneapi.c
+++ b/firmware_p4/components/Applications/LoRa/meshtastic/meshtastic_phoneapi.c
@@ -50,10 +50,11 @@ typedef struct {
uint8_t buf[PA_MAX_FRAME_SIZE];
} pa_frame_t;
-static pa_frame_t s_queue[PA_FROMRADIO_QUEUE_SZ];
+EXT_RAM_BSS_ATTR static pa_frame_t s_queue[PA_FROMRADIO_QUEUE_SZ];
static uint8_t s_queue_head = 0;
static uint8_t s_queue_tail = 0;
static SemaphoreHandle_t s_queue_mutex = NULL;
+static SemaphoreHandle_t s_fsm_mutex = NULL;
static uint32_t s_node_num = 0;
static phoneapi_state_t s_state = PA_STATE_IDLE;
@@ -74,25 +75,25 @@ static uint16_t enc_varint(uint8_t *buf, uint64_t value) {
}
static uint16_t enc_field_varint(uint8_t *buf, uint8_t field_num, uint64_t value) {
- buf[0] = (field_num << 3) | 0;
- return 1 + enc_varint(&buf[1], value);
+ uint16_t pos = enc_varint(buf, ((uint64_t)field_num << 3) | 0);
+ return pos + enc_varint(&buf[pos], value);
}
static uint16_t
enc_field_bytes(uint8_t *buf, uint8_t field_num, const uint8_t *data, uint16_t len) {
- buf[0] = (field_num << 3) | 2;
- uint16_t pos = 1 + enc_varint(&buf[1], len);
+ uint16_t pos = enc_varint(buf, ((uint64_t)field_num << 3) | 2);
+ pos += enc_varint(&buf[pos], len);
memcpy(&buf[pos], data, len);
return pos + len;
}
static uint16_t enc_field_fixed32(uint8_t *buf, uint8_t field_num, uint32_t value) {
- buf[0] = (field_num << 3) | 5;
- buf[1] = (uint8_t)(value & 0xFF);
- buf[2] = (uint8_t)((value >> 8) & 0xFF);
- buf[3] = (uint8_t)((value >> 16) & 0xFF);
- buf[4] = (uint8_t)((value >> 24) & 0xFF);
- return 5;
+ uint16_t pos = enc_varint(buf, ((uint64_t)field_num << 3) | 5);
+ buf[pos++] = (uint8_t)(value & 0xFF);
+ buf[pos++] = (uint8_t)((value >> 8) & 0xFF);
+ buf[pos++] = (uint8_t)((value >> 16) & 0xFF);
+ buf[pos++] = (uint8_t)((value >> 24) & 0xFF);
+ return pos;
}
static uint64_t dec_varint(const uint8_t *buf, uint16_t max_len, uint16_t *out_used) {
@@ -272,7 +273,7 @@ static uint16_t build_config_empty(uint8_t *out, uint32_t rid, uint8_t cfg_type)
static uint16_t build_moduleconfig_empty(uint8_t *out, uint32_t rid, uint8_t mc_type) {
uint8_t mc[8];
uint16_t mc_len = 0;
- mc[mc_len++] = (mc_type << 3) | 2;
+ mc_len += enc_varint(&mc[mc_len], ((uint64_t)mc_type << 3) | 2);
mc[mc_len++] = 0;
uint16_t pos = 0;
@@ -576,6 +577,11 @@ esp_err_t phoneapi_init(uint32_t node_num) {
if (s_queue_mutex == NULL)
return ESP_ERR_NO_MEM;
}
+ if (s_fsm_mutex == NULL) {
+ s_fsm_mutex = xSemaphoreCreateMutex();
+ if (s_fsm_mutex == NULL)
+ return ESP_ERR_NO_MEM;
+ }
ESP_LOGI(
TAG, "Initialized - node=0x%08lX, queue=%d", (unsigned long)node_num, PA_FROMRADIO_QUEUE_SZ);
@@ -620,6 +626,8 @@ esp_err_t phoneapi_on_toradio(const uint8_t *pb_data, uint16_t pb_len) {
uint64_t value = dec_varint(&pb_data[i], pb_len - i, &vused);
i += vused;
if (field == 3) {
+ if (s_fsm_mutex != NULL)
+ xSemaphoreTake(s_fsm_mutex, portMAX_DELAY);
s_want_config_nonce = (uint32_t)value;
ESP_LOGI(TAG, "ToRadio.want_config_id = %lu", (unsigned long)value);
if (s_want_config_nonce == PA_NONCE_ONLY_NODES) {
@@ -633,6 +641,8 @@ esp_err_t phoneapi_on_toradio(const uint8_t *pb_data, uint16_t pb_len) {
k++) {
advance_fsm();
}
+ if (s_fsm_mutex != NULL)
+ xSemaphoreGive(s_fsm_mutex);
}
} else if (wire_type == 5) {
i += 4;
@@ -644,9 +654,13 @@ esp_err_t phoneapi_on_toradio(const uint8_t *pb_data, uint16_t pb_len) {
}
uint16_t phoneapi_poll_fromradio(uint8_t *out_buf, uint16_t max_len) {
+ if (s_fsm_mutex != NULL)
+ xSemaphoreTake(s_fsm_mutex, portMAX_DELAY);
while (!queue_has_data() && s_state != PA_STATE_SEND_PACKETS && s_state != PA_STATE_IDLE) {
advance_fsm();
}
+ if (s_fsm_mutex != NULL)
+ xSemaphoreGive(s_fsm_mutex);
return queue_pop(out_buf, max_len);
}
@@ -669,7 +683,15 @@ void phoneapi_push_packet(const uint8_t *mp_bytes, uint16_t mp_len) {
void phoneapi_disconnect(void) {
ESP_LOGI(TAG, "Transporte desconectado - resetando FSM");
+ if (s_fsm_mutex != NULL)
+ xSemaphoreTake(s_fsm_mutex, portMAX_DELAY);
s_state = PA_STATE_IDLE;
- s_queue_head = s_queue_tail;
s_config_iter = 0;
+ if (s_fsm_mutex != NULL)
+ xSemaphoreGive(s_fsm_mutex);
+ if (s_queue_mutex != NULL)
+ xSemaphoreTake(s_queue_mutex, portMAX_DELAY);
+ s_queue_head = s_queue_tail;
+ if (s_queue_mutex != NULL)
+ xSemaphoreGive(s_queue_mutex);
}
diff --git a/firmware_p4/components/Applications/LoRa/session/lora_session.c b/firmware_p4/components/Applications/LoRa/session/lora_session.c
index a36488d95..ec31af3f1 100644
--- a/firmware_p4/components/Applications/LoRa/session/lora_session.c
+++ b/firmware_p4/components/Applications/LoRa/session/lora_session.c
@@ -39,7 +39,7 @@
static const char *TAG = "LORA_SESSION";
static lora_proto_t s_proto = LORA_PROTO_NONE;
-static lora_msg_t s_msgs[MSG_RING];
+EXT_RAM_BSS_ATTR static lora_msg_t s_msgs[MSG_RING];
static uint16_t s_head = 0;
static uint16_t s_count = 0;
static uint32_t s_total = 0;
@@ -219,9 +219,9 @@ void lora_session_on_rx_text(const char *who, const char *text) {
bool lora_session_app_connected(void) {
switch (s_proto) {
case LORA_PROTO_MESHTASTIC:
- return meshtastic_phone_bridge_is_connected();
+ return meshtastic_phone_bridge_is_subscribed();
case LORA_PROTO_MESHCORE:
- return meshcore_phone_bridge_is_connected();
+ return meshcore_phone_bridge_is_subscribed();
default:
return false;
}
diff --git a/firmware_p4/components/Applications/SubGhz/include/subghz_brute.h b/firmware_p4/components/Applications/SubGhz/include/subghz_brute.h
new file mode 100644
index 000000000..6a0343de0
--- /dev/null
+++ b/firmware_p4/components/Applications/SubGhz/include/subghz_brute.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2025 HIGH CODE LLC
+//
+// TentacleOS is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// TentacleOS is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with TentacleOS. If not, see .
+
+#ifndef SUBGHZ_BRUTE_H
+#define SUBGHZ_BRUTE_H
+
+#include
+#include
+
+#include "esp_err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Progress of a running brute-force sweep.
+ */
+typedef struct {
+ uint32_t sent; /**< @brief Codes transmitted so far. */
+ uint32_t total; /**< @brief Total codes in the sweep. */
+ bool running; /**< @brief true while the sweep is active. */
+ bool done; /**< @brief true once the sweep finished (or was stopped). */
+} subghz_brute_status_t;
+
+/**
+ * @brief Start a real brute-force sweep: transmit every code for a protocol.
+ *
+ * Stops the receiver, initializes the transmitter, tunes to @p freq, then
+ * transmits each candidate code. There is no "hit" detection on fixed-code
+ * remotes — the sweep simply completes.
+ *
+ * @param protocol Protocol name (must have an encoder: e.g. "CAME", "RCSwitch").
+ * @param bit_count Code width in bits (sweep size is capped internally).
+ * @param freq Transmit frequency in Hz.
+ * @return ESP_OK if the sweep started, or an error code on failure.
+ */
+esp_err_t subghz_brute_start(const char *protocol, uint8_t bit_count, uint32_t freq);
+
+/**
+ * @brief Stop a running brute-force sweep.
+ */
+void subghz_brute_stop(void);
+
+/**
+ * @brief Copy the current sweep progress.
+ *
+ * @param out Destination.
+ * @return true if a sweep has been started (out is filled), false otherwise.
+ */
+bool subghz_brute_get_status(subghz_brute_status_t *out);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // SUBGHZ_BRUTE_H
diff --git a/firmware_p4/components/Applications/SubGhz/include/subghz_receiver.h b/firmware_p4/components/Applications/SubGhz/include/subghz_receiver.h
index 8f0da732a..fbe45c6b3 100644
--- a/firmware_p4/components/Applications/SubGhz/include/subghz_receiver.h
+++ b/firmware_p4/components/Applications/SubGhz/include/subghz_receiver.h
@@ -23,6 +23,8 @@
#include "esp_err.h"
#include "cc1101.h"
+#include "subghz_analyzer.h"
+#include "subghz_types.h"
#ifdef __cplusplus
extern "C" {
@@ -37,6 +39,27 @@ typedef enum {
SUBGHZ_MODE_COUNT /**< @brief Number of receiver modes (sentinel). */
} subghz_mode_t;
+/**
+ * @brief Latest capture published by the receiver task for UI consumers.
+ */
+typedef struct {
+ uint32_t seq; /**< @brief Capture counter; 0 means nothing captured yet. */
+ bool decoded; /**< @brief true if a known protocol was decoded. */
+ subghz_data_t data; /**< @brief Decoded fields; protocol_name points to name_buf. */
+ char name_buf[32]; /**< @brief Backing storage for data.protocol_name. */
+ char save_name[40]; /**< @brief File name the capture was auto-saved under. */
+ uint32_t freq; /**< @brief Frequency in Hz at the moment of capture. */
+ subghz_analyzer_result_t analysis; /**< @brief Timing/modulation analysis of the capture. */
+} subghz_rx_result_t;
+
+/**
+ * @brief Copy the most recent capture published by the receiver task.
+ *
+ * @param out Destination buffer.
+ * @return true if a capture is available (out is filled), false otherwise.
+ */
+bool subghz_receiver_get_result(subghz_rx_result_t *out);
+
/**
* @brief Start the Sub-GHz receiver.
*
diff --git a/firmware_p4/components/Service/storage_api/include/tos_log.h b/firmware_p4/components/Applications/SubGhz/include/subghz_replay.h
similarity index 51%
rename from firmware_p4/components/Service/storage_api/include/tos_log.h
rename to firmware_p4/components/Applications/SubGhz/include/subghz_replay.h
index c6c9acaf8..4b0a8da46 100644
--- a/firmware_p4/components/Service/storage_api/include/tos_log.h
+++ b/firmware_p4/components/Applications/SubGhz/include/subghz_replay.h
@@ -13,43 +13,31 @@
// You should have received a copy of the GNU General Public License
// along with TentacleOS. If not, see .
-#ifndef TOS_LOG_H
-#define TOS_LOG_H
+#ifndef SUBGHZ_REPLAY_H
+#define SUBGHZ_REPLAY_H
+
+#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
-#include "esp_err.h"
-
/**
- * @brief Initialize the persistent log system.
- *
- * Redirects all ESP_LOGx output to a file on the SD card via
- * esp_log_set_vprintf. Logs are written to both serial and file
- * simultaneously.
+ * @brief Load a saved capture and transmit it over the CC1101.
*
- * Log files are rotated automatically:
- * - sys.1.log (current) -> sys.2.log -> ... -> sys.5.log
- * - Maximum 5 files, total limited to 10 MB
- * - When sys.1.log exceeds 2 MB, rotation occurs
- *
- * @return
- * - ESP_OK on success
- * - ESP_FAIL if the log file cannot be opened
- */
-esp_err_t tos_log_init(void);
-
-/**
- * @brief Flush and close the log file.
+ * Stops the receiver if running, initializes the transmitter, tunes to the
+ * saved frequency, and replays the signal: RAW captures are sent pulse-for-pulse;
+ * decoded captures are re-encoded via the protocol registry (only protocols with
+ * an encoder are supported).
*
- * Restores the default ESP_LOG vprintf handler. Call this before
- * unmounting the SD card.
+ * @param name Saved capture name (without .sub).
+ * @return ESP_OK if the transmit was queued, ESP_ERR_NOT_SUPPORTED if the
+ * protocol cannot be re-encoded, or another error code on failure.
*/
-void tos_log_deinit(void);
+esp_err_t subghz_replay_file(const char *name);
#ifdef __cplusplus
}
#endif
-#endif // TOS_LOG_H
+#endif // SUBGHZ_REPLAY_H
diff --git a/firmware_p4/components/Applications/SubGhz/include/subghz_settings.h b/firmware_p4/components/Applications/SubGhz/include/subghz_settings.h
new file mode 100644
index 000000000..1e79c5fb6
--- /dev/null
+++ b/firmware_p4/components/Applications/SubGhz/include/subghz_settings.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2025 HIGH CODE LLC
+//
+// TentacleOS is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// TentacleOS is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with TentacleOS. If not, see .
+
+#ifndef SUBGHZ_SETTINGS_H
+#define SUBGHZ_SETTINGS_H
+
+#include
+
+#include "cc1101.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Shared Sub-GHz radio settings chosen in the Radio Config screen and
+ * consumed by the Read/scan screen.
+ */
+typedef struct {
+ cc1101_preset_t preset; /**< @brief CC1101 preset used when starting RX. */
+ uint32_t freq; /**< @brief RX frequency in Hz; 0 means frequency hopping. */
+} subghz_settings_t;
+
+/**
+ * @brief Get the current shared Sub-GHz settings.
+ */
+subghz_settings_t subghz_settings_get(void);
+
+/**
+ * @brief Set the preset used when starting RX.
+ */
+void subghz_settings_set_preset(cc1101_preset_t preset);
+
+/**
+ * @brief Set the RX frequency in Hz (0 = frequency hopping).
+ */
+void subghz_settings_set_freq(uint32_t freq);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // SUBGHZ_SETTINGS_H
diff --git a/firmware_p4/components/Applications/SubGhz/include/subghz_storage.h b/firmware_p4/components/Applications/SubGhz/include/subghz_storage.h
index 21460af30..a5b653890 100644
--- a/firmware_p4/components/Applications/SubGhz/include/subghz_storage.h
+++ b/firmware_p4/components/Applications/SubGhz/include/subghz_storage.h
@@ -27,6 +27,18 @@
extern "C" {
#endif
+#define SUBGHZ_STORAGE_NAME_MAX 40
+#define SUBGHZ_STORAGE_PROTO_MAX 24
+
+/**
+ * @brief Metadata for one saved Sub-GHz capture (parsed from its .sub header).
+ */
+typedef struct {
+ char name[SUBGHZ_STORAGE_NAME_MAX]; /**< @brief File name without the .sub suffix. */
+ char protocol[SUBGHZ_STORAGE_PROTO_MAX]; /**< @brief Protocol label ("RAW" for raw captures). */
+ uint32_t frequency; /**< @brief Center frequency in Hz. */
+} subghz_storage_entry_t;
+
/**
* @brief Initialize the Sub-GHz storage subsystem.
*
@@ -34,6 +46,33 @@ extern "C" {
*/
esp_err_t subghz_storage_init(void);
+/**
+ * @brief List saved captures on the SD card.
+ *
+ * @param out Destination array.
+ * @param max_entries Capacity of the destination array.
+ * @return Number of entries written, or -1 on error.
+ */
+int subghz_storage_list(subghz_storage_entry_t *out, int max_entries);
+
+/**
+ * @brief Read a saved capture's raw .sub text.
+ *
+ * @param name Capture name (without .sub).
+ * @param out_buf Destination buffer (null-terminated on success).
+ * @param out_size Size of the destination buffer.
+ * @return Number of bytes read, or -1 on error.
+ */
+int subghz_storage_read(const char *name, char *out_buf, size_t out_size);
+
+/**
+ * @brief Delete a saved capture.
+ *
+ * @param name Capture name (without .sub).
+ * @return esp_err_t ESP_OK on success, or an error code on failure.
+ */
+esp_err_t subghz_storage_delete(const char *name);
+
/**
* @brief Save a decoded signal to persistent storage.
*
diff --git a/firmware_p4/components/Applications/SubGhz/subghz_brute.c b/firmware_p4/components/Applications/SubGhz/subghz_brute.c
new file mode 100644
index 000000000..d61cc19de
--- /dev/null
+++ b/firmware_p4/components/Applications/SubGhz/subghz_brute.c
@@ -0,0 +1,164 @@
+// Copyright (c) 2025 HIGH CODE LLC
+//
+// TentacleOS is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// TentacleOS is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with TentacleOS. If not, see .
+
+#include "subghz_brute.h"
+
+#include
+#include
+
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
+#include "freertos/task.h"
+#include "sys_prio.h"
+
+#include "cc1101.h"
+#include "subghz_protocol_decoder.h"
+#include "subghz_protocol_registry.h"
+#include "subghz_receiver.h"
+#include "subghz_transmitter.h"
+#include "subghz_types.h"
+
+static const char *TAG = "SUBGHZ_BRUTE";
+
+#define BRUTE_MAX_CODES 65536
+#define BRUTE_MAX_PULSES 2048
+#define BRUTE_PACING_MS 100
+#define BRUTE_RX_DRAIN_MS 150
+#define BRUTE_TASK_STACK 4096
+#define BRUTE_TASK_PRIORITY SYS_PRIO_SERVICE_HI
+#define BRUTE_TASK_CORE SYS_CORE_RADIO
+#define BRUTE_LOCK_MS 20
+
+static const subghz_protocol_t *s_proto = NULL;
+static uint8_t s_bit_count = 0;
+static volatile bool s_run = false;
+static TaskHandle_t s_task = NULL;
+static SemaphoreHandle_t s_mutex = NULL;
+static subghz_brute_status_t s_status;
+
+static void set_status(uint32_t sent, uint32_t total, bool running, bool done) {
+ if (s_mutex == NULL)
+ return;
+ if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(BRUTE_LOCK_MS)) != pdTRUE)
+ return;
+ s_status.sent = sent;
+ s_status.total = total;
+ s_status.running = running;
+ s_status.done = done;
+ xSemaphoreGive(s_mutex);
+}
+
+static void brute_task(void *arg) {
+ (void)arg;
+ uint32_t total = s_status.total;
+ int32_t *pulses = malloc(BRUTE_MAX_PULSES * sizeof(int32_t));
+
+ if (pulses == NULL || s_proto == NULL || s_proto->encode == NULL) {
+ if (pulses != NULL)
+ free(pulses);
+ set_status(0, total, false, true);
+ s_run = false;
+ s_task = NULL;
+ vTaskDelete(NULL);
+ return;
+ }
+
+ for (uint32_t code = 0; code < total && s_run; code++) {
+ subghz_data_t data = {0};
+ data.protocol_name = s_proto->name;
+ data.bit_count = s_bit_count;
+ data.raw_value = code;
+ data.serial = code;
+
+ size_t n = s_proto->encode(&data, pulses, BRUTE_MAX_PULSES);
+ if (n > 0)
+ subghz_tx_send_raw(pulses, n);
+
+ set_status(code + 1, total, true, false);
+ vTaskDelay(pdMS_TO_TICKS(BRUTE_PACING_MS));
+ }
+
+ free(pulses);
+ ESP_LOGI(TAG, "sweep complete (%lu codes)", (unsigned long)total);
+ set_status(s_status.sent, total, false, true);
+ s_run = false;
+ s_task = NULL;
+ vTaskDelete(NULL);
+}
+
+esp_err_t subghz_brute_start(const char *protocol, uint8_t bit_count, uint32_t freq) {
+ if (protocol == NULL || bit_count == 0 || s_run)
+ return ESP_ERR_INVALID_STATE;
+
+ const subghz_protocol_t *p = subghz_protocol_registry_get_by_name(protocol);
+ if (p == NULL || p->encode == NULL) {
+ ESP_LOGW(TAG, "protocol '%s' has no encoder", protocol);
+ return ESP_ERR_NOT_SUPPORTED;
+ }
+
+ uint32_t total = (bit_count >= 32) ? 0xFFFFFFFFu : (1u << bit_count);
+ if (total == 0 || total > BRUTE_MAX_CODES)
+ total = BRUTE_MAX_CODES;
+
+ if (s_mutex == NULL)
+ s_mutex = xSemaphoreCreateMutex();
+
+ s_proto = p;
+ s_bit_count = bit_count;
+ set_status(0, total, true, false);
+
+ if (subghz_receiver_is_running()) {
+ subghz_receiver_stop();
+ vTaskDelay(pdMS_TO_TICKS(BRUTE_RX_DRAIN_MS));
+ }
+
+ esp_err_t err = subghz_tx_init();
+ if (err != ESP_OK) {
+ set_status(0, total, false, true);
+ return err;
+ }
+ if (freq != 0)
+ cc1101_set_frequency(freq);
+
+ s_run = true;
+ BaseType_t ret = xTaskCreatePinnedToCore(brute_task,
+ "subghz_brute",
+ BRUTE_TASK_STACK,
+ NULL,
+ BRUTE_TASK_PRIORITY,
+ &s_task,
+ BRUTE_TASK_CORE);
+ if (ret != pdPASS) {
+ s_run = false;
+ set_status(0, total, false, true);
+ return ESP_ERR_NO_MEM;
+ }
+ return ESP_OK;
+}
+
+void subghz_brute_stop(void) {
+ s_run = false;
+}
+
+bool subghz_brute_get_status(subghz_brute_status_t *out) {
+ if (out == NULL || s_mutex == NULL)
+ return false;
+ if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(BRUTE_LOCK_MS)) != pdTRUE)
+ return false;
+ *out = s_status;
+ xSemaphoreGive(s_mutex);
+ return true;
+}
diff --git a/firmware_p4/components/Applications/SubGhz/subghz_receiver.c b/firmware_p4/components/Applications/SubGhz/subghz_receiver.c
index d34bd4ab6..46f587fd1 100644
--- a/firmware_p4/components/Applications/SubGhz/subghz_receiver.c
+++ b/firmware_p4/components/Applications/SubGhz/subghz_receiver.c
@@ -15,6 +15,8 @@
#include "subghz_receiver.h"
+#include
+
#include "driver/gpio.h"
#include "driver/rmt_rx.h"
#include "driver/rmt_encoder.h"
@@ -23,6 +25,7 @@
#include "freertos/task.h"
#include "sys_prio.h"
#include "freertos/queue.h"
+#include "freertos/semphr.h"
#include "cc1101.h"
#include "pin_def.h"
@@ -30,6 +33,7 @@
#include "subghz_protocol_registry.h"
#include "subghz_analyzer.h"
#include "subghz_storage.h"
+#include "subghz_transmitter.h"
static const char *TAG = "SUBGHZ_RX";
@@ -79,6 +83,49 @@ static uint32_t s_capture_count = 0;
static rmt_channel_handle_t s_rx_channel = NULL;
static QueueHandle_t s_rx_queue = NULL;
+#define RESULT_LOCK_TIMEOUT_MS 20
+static SemaphoreHandle_t s_result_mutex = NULL;
+static subghz_rx_result_t s_latest;
+static uint32_t s_result_seq = 0;
+
+static void publish_rx_result(bool decoded,
+ const subghz_data_t *data,
+ const subghz_analyzer_result_t *analysis,
+ const char *save_name) {
+ if (s_result_mutex == NULL)
+ return;
+ if (xSemaphoreTake(s_result_mutex, pdMS_TO_TICKS(RESULT_LOCK_TIMEOUT_MS)) != pdTRUE)
+ return;
+
+ s_result_seq++;
+ s_latest.seq = s_result_seq;
+ s_latest.decoded = decoded;
+ s_latest.freq = s_rx_freq;
+ if (save_name != NULL)
+ strlcpy(s_latest.save_name, save_name, sizeof(s_latest.save_name));
+ else
+ s_latest.save_name[0] = '\0';
+
+ if (data != NULL) {
+ s_latest.data = *data;
+ if (data->protocol_name != NULL)
+ strlcpy(s_latest.name_buf, data->protocol_name, sizeof(s_latest.name_buf));
+ else
+ s_latest.name_buf[0] = '\0';
+ } else {
+ memset(&s_latest.data, 0, sizeof(s_latest.data));
+ s_latest.name_buf[0] = '\0';
+ }
+ s_latest.data.protocol_name = s_latest.name_buf;
+
+ if (analysis != NULL)
+ s_latest.analysis = *analysis;
+ else
+ memset(&s_latest.analysis, 0, sizeof(s_latest.analysis));
+
+ xSemaphoreGive(s_result_mutex);
+}
+
static void get_dynamic_filename(char *out_name, size_t out_size, const char *prefix) {
s_capture_count++;
snprintf(out_name, out_size, "%s_%03lu", prefix, (unsigned long)s_capture_count);
@@ -121,6 +168,7 @@ static void handle_raw_mode(const int32_t *decode_buffer, size_t decode_idx) {
ESP_LOGD(TAG, "RAW: received %d pulses", (int)decode_idx);
get_dynamic_filename(filename, sizeof(filename), "RAW");
subghz_storage_save_raw(filename, decode_buffer, decode_idx, s_rx_freq);
+ publish_rx_result(false, NULL, NULL, filename);
}
static void log_recovered_bitstream(const subghz_analyzer_result_t *analysis) {
@@ -165,6 +213,7 @@ static void handle_scan_mode(const int32_t *decode_buffer, size_t decode_idx) {
subghz_analyzer_process(decode_buffer, decode_idx, &analysis);
get_dynamic_filename(filename, sizeof(filename), "DEC");
subghz_storage_save_decoded(filename, &decoded, s_rx_freq, analysis.estimated_te);
+ publish_rx_result(true, &decoded, &analysis, filename);
led_signal_info(); // decoded a known protocol
return;
}
@@ -180,6 +229,7 @@ static void handle_scan_mode(const int32_t *decode_buffer, size_t decode_idx) {
get_dynamic_filename(filename, sizeof(filename), "UNK");
subghz_storage_save_raw(filename, decode_buffer, decode_idx, s_rx_freq);
+ publish_rx_result(false, NULL, &analysis, filename);
log_recovered_bitstream(&analysis);
led_signal_warning(); // captured RF but no known protocol matched
}
@@ -350,9 +400,21 @@ esp_err_t subghz_receiver_start(subghz_mode_t mode, cc1101_preset_t preset, uint
if (s_is_running) {
return ESP_ERR_INVALID_STATE;
}
+
+ subghz_tx_stop();
+
s_rx_mode = mode;
s_rx_preset = preset;
+ if (s_result_mutex == NULL)
+ s_result_mutex = xSemaphoreCreateMutex();
+ if (s_result_mutex != NULL &&
+ xSemaphoreTake(s_result_mutex, pdMS_TO_TICKS(RESULT_LOCK_TIMEOUT_MS)) == pdTRUE) {
+ s_result_seq = 0;
+ memset(&s_latest, 0, sizeof(s_latest));
+ xSemaphoreGive(s_result_mutex);
+ }
+
if (freq == 0) {
s_is_hopping_active = true;
s_hop_idx = 0;
@@ -384,3 +446,18 @@ void subghz_receiver_stop(void) {
bool subghz_receiver_is_running(void) {
return s_is_running;
}
+
+bool subghz_receiver_get_result(subghz_rx_result_t *out) {
+ if (out == NULL || s_result_mutex == NULL)
+ return false;
+ if (xSemaphoreTake(s_result_mutex, pdMS_TO_TICKS(RESULT_LOCK_TIMEOUT_MS)) != pdTRUE)
+ return false;
+
+ bool has = (s_latest.seq != 0);
+ if (has) {
+ *out = s_latest;
+ out->data.protocol_name = out->name_buf;
+ }
+ xSemaphoreGive(s_result_mutex);
+ return has;
+}
diff --git a/firmware_p4/components/Applications/SubGhz/subghz_replay.c b/firmware_p4/components/Applications/SubGhz/subghz_replay.c
new file mode 100644
index 000000000..ae8d06d0a
--- /dev/null
+++ b/firmware_p4/components/Applications/SubGhz/subghz_replay.c
@@ -0,0 +1,184 @@
+// Copyright (c) 2025 HIGH CODE LLC
+//
+// TentacleOS is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// TentacleOS is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with TentacleOS. If not, see .
+
+#include "subghz_replay.h"
+
+#include
+#include
+#include
+
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#include "cc1101.h"
+#include "subghz_protocol_decoder.h"
+#include "subghz_protocol_registry.h"
+#include "subghz_protocol_serializer.h"
+#include "subghz_receiver.h"
+#include "subghz_storage.h"
+#include "subghz_transmitter.h"
+#include "subghz_types.h"
+
+static const char *TAG = "SUBGHZ_REPLAY";
+
+#define REPLAY_FILE_BUF 8192
+#define REPLAY_MAX_PULSES 2048
+#define REPLAY_RX_DRAIN_MS 150
+#define REPLAY_PROTO_MAX 24
+
+static uint32_t parse_u32_after(const char *content, const char *key) {
+ const char *p = strstr(content, key);
+ if (p == NULL)
+ return 0;
+ return (uint32_t)strtoul(p + strlen(key), NULL, 10);
+}
+
+static void parse_protocol(const char *content, char *out, size_t out_size) {
+ out[0] = '\0';
+ const char *p = strstr(content, "Protocol: ");
+ if (p == NULL)
+ return;
+ p += strlen("Protocol: ");
+ size_t i = 0;
+ while (p[i] != '\0' && p[i] != '\n' && p[i] != '\r' && i < out_size - 1) {
+ out[i] = p[i];
+ i++;
+ }
+ out[i] = '\0';
+}
+
+static uint32_t parse_key_value(const char *content) {
+ const char *p = strstr(content, "Key: ");
+ if (p == NULL)
+ return 0;
+ unsigned b[8] = {0};
+ int n = sscanf(p + strlen("Key: "),
+ "%x %x %x %x %x %x %x %x",
+ &b[0],
+ &b[1],
+ &b[2],
+ &b[3],
+ &b[4],
+ &b[5],
+ &b[6],
+ &b[7]);
+ if (n < 8)
+ return 0;
+ return ((uint32_t)b[4] << 24) | ((uint32_t)b[5] << 16) | ((uint32_t)b[6] << 8) | (uint32_t)b[7];
+}
+
+static const subghz_protocol_t *lookup_protocol(const char *proto) {
+ const subghz_protocol_t *p = subghz_protocol_registry_get_by_name(proto);
+ if (p != NULL)
+ return p;
+
+ char first[REPLAY_PROTO_MAX];
+ size_t i = 0;
+ while (proto[i] != '\0' && proto[i] != ' ' && i < sizeof(first) - 1) {
+ first[i] = proto[i];
+ i++;
+ }
+ first[i] = '\0';
+ if (i == 0)
+ return NULL;
+ return subghz_protocol_registry_get_by_name(first);
+}
+
+static esp_err_t replay_raw(const char *content) {
+ int32_t *pulses = malloc(REPLAY_MAX_PULSES * sizeof(int32_t));
+ if (pulses == NULL)
+ return ESP_ERR_NO_MEM;
+
+ uint32_t freq = 0;
+ uint8_t preset = 0;
+ size_t n = subghz_protocol_parse_raw(content, pulses, REPLAY_MAX_PULSES, &freq, &preset);
+ if (n == 0) {
+ free(pulses);
+ return ESP_FAIL;
+ }
+
+ if (freq != 0)
+ cc1101_set_frequency(freq);
+ esp_err_t err = subghz_tx_send_raw(pulses, n);
+ free(pulses);
+ return err;
+}
+
+static esp_err_t replay_decoded(const char *content) {
+ uint32_t freq = parse_u32_after(content, "Frequency: ");
+ char proto[REPLAY_PROTO_MAX];
+ parse_protocol(content, proto, sizeof(proto));
+
+ const subghz_protocol_t *p = lookup_protocol(proto);
+ if (p == NULL || p->encode == NULL) {
+ ESP_LOGW(TAG, "replay unsupported for protocol '%s'", proto);
+ return ESP_ERR_NOT_SUPPORTED;
+ }
+
+ subghz_data_t data = {0};
+ data.protocol_name = proto;
+ data.bit_count = (uint8_t)parse_u32_after(content, "Bit: ");
+ data.raw_value = parse_key_value(content);
+ data.serial = data.raw_value;
+
+ int32_t *pulses = malloc(REPLAY_MAX_PULSES * sizeof(int32_t));
+ if (pulses == NULL)
+ return ESP_ERR_NO_MEM;
+
+ size_t n = p->encode(&data, pulses, REPLAY_MAX_PULSES);
+ if (n == 0) {
+ free(pulses);
+ return ESP_FAIL;
+ }
+
+ if (freq != 0)
+ cc1101_set_frequency(freq);
+ esp_err_t err = subghz_tx_send_raw(pulses, n);
+ free(pulses);
+ return err;
+}
+
+esp_err_t subghz_replay_file(const char *name) {
+ if (name == NULL)
+ return ESP_ERR_INVALID_ARG;
+
+ char *content = malloc(REPLAY_FILE_BUF);
+ if (content == NULL)
+ return ESP_ERR_NO_MEM;
+
+ int rd = subghz_storage_read(name, content, REPLAY_FILE_BUF);
+ if (rd <= 0) {
+ free(content);
+ return ESP_ERR_NOT_FOUND;
+ }
+
+ if (subghz_receiver_is_running()) {
+ subghz_receiver_stop();
+ vTaskDelay(pdMS_TO_TICKS(REPLAY_RX_DRAIN_MS));
+ }
+
+ esp_err_t err = subghz_tx_init();
+ if (err != ESP_OK) {
+ free(content);
+ return err;
+ }
+
+ bool is_raw = (strstr(content, "RAW_Data:") != NULL);
+ err = is_raw ? replay_raw(content) : replay_decoded(content);
+
+ free(content);
+ return err;
+}
diff --git a/firmware_p4/components/Applications/SubGhz/subghz_settings.c b/firmware_p4/components/Applications/SubGhz/subghz_settings.c
new file mode 100644
index 000000000..22d3ed905
--- /dev/null
+++ b/firmware_p4/components/Applications/SubGhz/subghz_settings.c
@@ -0,0 +1,33 @@
+// Copyright (c) 2025 HIGH CODE LLC
+//
+// TentacleOS is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// TentacleOS is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with TentacleOS. If not, see .
+
+#include "subghz_settings.h"
+
+static subghz_settings_t s_settings = {
+ .preset = CC1101_PRESET_OOK_800KHZ,
+ .freq = 0,
+};
+
+subghz_settings_t subghz_settings_get(void) {
+ return s_settings;
+}
+
+void subghz_settings_set_preset(cc1101_preset_t preset) {
+ s_settings.preset = preset;
+}
+
+void subghz_settings_set_freq(uint32_t freq) {
+ s_settings.freq = freq;
+}
diff --git a/firmware_p4/components/Applications/SubGhz/subghz_spectrum.c b/firmware_p4/components/Applications/SubGhz/subghz_spectrum.c
index 7e6f8cfd5..dd70fea10 100644
--- a/firmware_p4/components/Applications/SubGhz/subghz_spectrum.c
+++ b/firmware_p4/components/Applications/SubGhz/subghz_spectrum.c
@@ -33,8 +33,8 @@ static const char *TAG = "SUBGHZ_SPECTRUM";
#define RSSI_SAMPLE_DELAY_US 20
#define RSSI_SAMPLE_COUNT 3
#define RSSI_MIN_DBM (-130.0f)
-#define YIELD_INTERVAL 16
-#define SWEEP_DELAY_MS 5
+#define YIELD_INTERVAL 1
+#define SWEEP_DELAY_MS 15
#define MUTEX_TIMEOUT_MS 10
#define GET_LINE_TIMEOUT_MS 5
#define SPECTRUM_TASK_STACK 4096
diff --git a/firmware_p4/components/Applications/SubGhz/subghz_storage.c b/firmware_p4/components/Applications/SubGhz/subghz_storage.c
index ac6652d89..7ca83b08f 100644
--- a/firmware_p4/components/Applications/SubGhz/subghz_storage.c
+++ b/firmware_p4/components/Applications/SubGhz/subghz_storage.c
@@ -15,23 +15,63 @@
#include "subghz_storage.h"
+#include
#include
#include
+#include
#include "esp_log.h"
+#include "storage_mkdir.h"
#include "subghz_protocol_serializer.h"
+#include "tos_storage_paths.h"
static const char *TAG = "SUBGHZ_STORAGE";
#define DECODED_BUF_SIZE 1024
-#define RAW_BUF_SIZE 4096
+#define RAW_BUF_SIZE 8192
+#define STORAGE_DIR TOS_PATH_SUBGHZ
+#define PATH_BUF_SIZE 128
+#define HEADER_PEEK_SIZE 256
+#define SUB_EXT ".sub"
+
+static void build_path(char *out, size_t out_size, const char *name) {
+ snprintf(out, out_size, "%s/%s%s", STORAGE_DIR, name, SUB_EXT);
+}
-esp_err_t subghz_storage_init(void) {
- ESP_LOGI(TAG, "Storage API Initialized (Placeholder Mode)");
+static esp_err_t write_file(const char *name, const char *content, size_t len) {
+ if (name == NULL || content == NULL || len == 0)
+ return ESP_ERR_INVALID_ARG;
+
+ storage_mkdir_recursive(STORAGE_DIR);
+
+ char path[PATH_BUF_SIZE];
+ build_path(path, sizeof(path), name);
+
+ FILE *f = fopen(path, "wb");
+ if (f == NULL) {
+ ESP_LOGE(TAG, "open for write failed: %s", path);
+ return ESP_FAIL;
+ }
+
+ size_t written = fwrite(content, 1, len, f);
+ fclose(f);
+
+ if (written != len) {
+ ESP_LOGE(TAG, "short write: %s (%u/%u)", path, (unsigned)written, (unsigned)len);
+ return ESP_FAIL;
+ }
+
+ ESP_LOGI(TAG, "saved %s (%u bytes)", path, (unsigned)len);
return ESP_OK;
}
+esp_err_t subghz_storage_init(void) {
+ esp_err_t err = storage_mkdir_recursive(STORAGE_DIR);
+ ESP_LOGI(TAG, "storage ready at %s", STORAGE_DIR);
+ return err;
+}
+
esp_err_t subghz_storage_save_decoded(const char *name,
const subghz_data_t *data,
uint32_t frequency,
@@ -42,18 +82,14 @@ esp_err_t subghz_storage_save_decoded(const char *name,
}
char *buf = malloc(DECODED_BUF_SIZE);
- if (buf == NULL) {
- ESP_LOGE(TAG, "Failed to allocate decoded buffer");
+ if (buf == NULL)
return ESP_ERR_NO_MEM;
- }
-
- subghz_protocol_serialize_decoded(data, frequency, te, buf, DECODED_BUF_SIZE);
- ESP_LOGI(TAG, "Saving Decoded Signal to %s...", name);
- ESP_LOGI(TAG, "\n--- FILE CONTENT START (%s) ---\n%s--- FILE CONTENT END ---\n", name, buf);
+ size_t len = subghz_protocol_serialize_decoded(data, frequency, te, buf, DECODED_BUF_SIZE);
+ esp_err_t err = write_file(name, buf, len);
free(buf);
- return ESP_OK;
+ return err;
}
esp_err_t
@@ -64,16 +100,107 @@ subghz_storage_save_raw(const char *name, const int32_t *pulses, size_t count, u
}
char *buf = malloc(RAW_BUF_SIZE);
- if (buf == NULL) {
- ESP_LOGE(TAG, "Failed to allocate raw buffer");
+ if (buf == NULL)
return ESP_ERR_NO_MEM;
+
+ size_t len = subghz_protocol_serialize_raw(pulses, count, frequency, buf, RAW_BUF_SIZE);
+ esp_err_t err = write_file(name, buf, len);
+
+ free(buf);
+ return err;
+}
+
+int subghz_storage_read(const char *name, char *out_buf, size_t out_size) {
+ if (name == NULL || out_buf == NULL || out_size == 0)
+ return -1;
+
+ char path[PATH_BUF_SIZE];
+ build_path(path, sizeof(path), name);
+
+ FILE *f = fopen(path, "rb");
+ if (f == NULL)
+ return -1;
+
+ size_t rd = fread(out_buf, 1, out_size - 1, f);
+ fclose(f);
+ out_buf[rd] = '\0';
+ return (int)rd;
+}
+
+static void
+parse_header(const char *content, char *proto_out, size_t proto_size, uint32_t *freq_out) {
+ proto_out[0] = '\0';
+ *freq_out = 0;
+
+ const char *fp = strstr(content, "Frequency: ");
+ if (fp != NULL)
+ *freq_out = (uint32_t)strtoul(fp + strlen("Frequency: "), NULL, 10);
+
+ const char *pp = strstr(content, "Protocol: ");
+ if (pp != NULL) {
+ pp += strlen("Protocol: ");
+ size_t i = 0;
+ while (pp[i] != '\0' && pp[i] != '\n' && pp[i] != '\r' && i < proto_size - 1) {
+ proto_out[i] = pp[i];
+ i++;
+ }
+ proto_out[i] = '\0';
}
+}
- subghz_protocol_serialize_raw(pulses, count, frequency, buf, RAW_BUF_SIZE);
+int subghz_storage_list(subghz_storage_entry_t *out, int max_entries) {
+ if (out == NULL || max_entries <= 0)
+ return -1;
+
+ DIR *d = opendir(STORAGE_DIR);
+ if (d == NULL)
+ return 0;
+
+ int count = 0;
+ struct dirent *ent;
+ char content[HEADER_PEEK_SIZE];
+
+ while ((ent = readdir(d)) != NULL && count < max_entries) {
+ if (ent->d_name[0] == '.')
+ continue;
+ const char *dot = strrchr(ent->d_name, '.');
+ if (dot == NULL || strcmp(dot, SUB_EXT) != 0)
+ continue;
+
+ size_t base_len = (size_t)(dot - ent->d_name);
+ if (base_len == 0 || base_len >= SUBGHZ_STORAGE_NAME_MAX)
+ continue;
+
+ subghz_storage_entry_t *e = &out[count];
+ memcpy(e->name, ent->d_name, base_len);
+ e->name[base_len] = '\0';
+ e->protocol[0] = '\0';
+ e->frequency = 0;
+
+ int rd = subghz_storage_read(e->name, content, sizeof(content));
+ if (rd > 0)
+ parse_header(content, e->protocol, sizeof(e->protocol), &e->frequency);
+ if (e->protocol[0] == '\0')
+ strlcpy(e->protocol, "RAW", sizeof(e->protocol));
+
+ count++;
+ }
- ESP_LOGI(TAG, "Saving RAW Signal to %s...", name);
- ESP_LOGI(TAG, "\n--- FILE CONTENT START (%s) ---\n%s--- FILE CONTENT END ---\n", name, buf);
+ closedir(d);
+ return count;
+}
- free(buf);
+esp_err_t subghz_storage_delete(const char *name) {
+ if (name == NULL)
+ return ESP_ERR_INVALID_ARG;
+
+ char path[PATH_BUF_SIZE];
+ build_path(path, sizeof(path), name);
+
+ if (remove(path) != 0) {
+ ESP_LOGE(TAG, "delete failed: %s", path);
+ return ESP_FAIL;
+ }
+ ESP_LOGI(TAG, "deleted %s", path);
return ESP_OK;
}
diff --git a/firmware_p4/components/Applications/SubGhz/subghz_transmitter.c b/firmware_p4/components/Applications/SubGhz/subghz_transmitter.c
index ce4765028..bb72836ce 100644
--- a/firmware_p4/components/Applications/SubGhz/subghz_transmitter.c
+++ b/firmware_p4/components/Applications/SubGhz/subghz_transmitter.c
@@ -156,7 +156,7 @@ esp_err_t subghz_tx_init(void) {
.resolution_hz = RMT_RESOLUTION_HZ,
.mem_block_symbols = RMT_MEM_BLOCK_SYMBOLS,
.trans_queue_depth = RMT_TRANS_QUEUE_DEPTH,
- .gpio_num = GPIO_CC1101_GDO2_PIN,
+ .gpio_num = GPIO_CC1101_GDO0_PIN,
.flags.invert_out = false,
};
esp_err_t err = rmt_new_tx_channel(&tx_channel_cfg, &s_tx_channel);
diff --git a/firmware_p4/components/Applications/bad_usb/bad_usb.c b/firmware_p4/components/Applications/bad_usb/bad_usb.c
index afea66022..b97386f73 100644
--- a/firmware_p4/components/Applications/bad_usb/bad_usb.c
+++ b/firmware_p4/components/Applications/bad_usb/bad_usb.c
@@ -38,6 +38,7 @@ static bool s_is_initialized = false;
static void send_keyboard_report(uint8_t keycode, uint8_t modifier);
static void send_mouse_report(int8_t x, int8_t y, uint8_t buttons, int8_t wheel);
+static bool hid_report_ready(void);
esp_err_t bad_usb_init(void) {
if (s_is_initialized) {
@@ -51,7 +52,8 @@ esp_err_t bad_usb_init(void) {
return err;
}
- hid_hal_register_callback(send_keyboard_report, send_mouse_report, bad_usb_wait_for_connection);
+ hid_hal_register_callback(
+ send_keyboard_report, send_mouse_report, bad_usb_wait_for_connection, hid_report_ready);
s_is_initialized = true;
ESP_LOGI(TAG, "Initialized");
@@ -65,7 +67,7 @@ esp_err_t bad_usb_deinit(void) {
}
ESP_LOGI(TAG, "Deinitializing...");
- hid_hal_register_callback(NULL, NULL, NULL);
+ hid_hal_register_callback(NULL, NULL, NULL, NULL);
esp_err_t err = tinyusb_driver_uninstall();
if (err != ESP_OK) {
@@ -117,3 +119,7 @@ static void send_keyboard_report(uint8_t keycode, uint8_t modifier) {
static void send_mouse_report(int8_t x, int8_t y, uint8_t buttons, int8_t wheel) {
tud_hid_mouse_report(HID_REPORT_ID_MOUSE, buttons, x, y, wheel, 0);
}
+
+static bool hid_report_ready(void) {
+ return tud_hid_ready();
+}
diff --git a/firmware_p4/components/Applications/bad_usb/ducky_parser.c b/firmware_p4/components/Applications/bad_usb/ducky_parser.c
index 8fbbc84d5..48baaf8dd 100644
--- a/firmware_p4/components/Applications/bad_usb/ducky_parser.c
+++ b/firmware_p4/components/Applications/bad_usb/ducky_parser.c
@@ -259,7 +259,7 @@ static bool is_modifier(const char *word, uint8_t *out_mod) {
return true;
}
if (strcasecmp(word, "GUI") == 0 || strcasecmp(word, "WINDOWS") == 0 ||
- strcasecmp(word, "COMMAND") == 0) {
+ strcasecmp(word, "SUPER") == 0 || strcasecmp(word, "COMMAND") == 0) {
*out_mod |= KEYBOARD_MODIFIER_LEFTGUI;
return true;
}
diff --git a/firmware_p4/components/Applications/bad_usb/hid_hal.c b/firmware_p4/components/Applications/bad_usb/hid_hal.c
index d7170c050..1e23ed169 100644
--- a/firmware_p4/components/Applications/bad_usb/hid_hal.c
+++ b/firmware_p4/components/Applications/bad_usb/hid_hal.c
@@ -24,21 +24,41 @@
static const char *TAG = "HID_HAL";
-#define KEY_PRESS_DELAY_US 5000
-#define KEY_RELEASE_DELAY_US 5000
-#define MOUSE_MOVE_DELAY_US 2000
-#define MOUSE_CLICK_DELAY_US 5000
+// Fallback pacing used only when no readiness source is registered.
+#define FALLBACK_KEY_DELAY_US 5000
+#define FALLBACK_MOUSE_DELAY_US 2000
+// Must exceed how long a busy host/editor can stall HID polling, else a report
+// is sent into a busy endpoint and dropped. Only a real disconnect should hit it.
+#define REPORT_READY_TIMEOUT_MS 1000
static hid_send_cb_t s_send_cb = NULL;
static hid_mouse_cb_t s_mouse_cb = NULL;
static hid_wait_cb_t s_wait_cb = NULL;
+static hid_ready_cb_t s_ready_cb = NULL;
void hid_hal_register_callback(hid_send_cb_t send_cb,
hid_mouse_cb_t mouse_cb,
- hid_wait_cb_t wait_cb) {
+ hid_wait_cb_t wait_cb,
+ hid_ready_cb_t ready_cb) {
s_send_cb = send_cb;
s_mouse_cb = mouse_cb;
s_wait_cb = wait_cb;
+ s_ready_cb = ready_cb;
+}
+
+// Wait until the previous report was delivered before sending the next, so we
+// never overwrite an unpolled report (dropped keys). Paces to the host's ~1ms poll.
+static void wait_report_ready(uint32_t fallback_us) {
+ if (s_ready_cb == NULL) {
+ ets_delay_us(fallback_us);
+ return;
+ }
+ for (uint32_t waited_ms = 0; !s_ready_cb(); waited_ms++) {
+ if (waited_ms >= REPORT_READY_TIMEOUT_MS) {
+ return;
+ }
+ vTaskDelay(pdMS_TO_TICKS(1));
+ }
}
void hid_hal_press_key(uint8_t keycode, uint8_t modifiers) {
@@ -46,13 +66,11 @@ void hid_hal_press_key(uint8_t keycode, uint8_t modifiers) {
return;
}
+ wait_report_ready(FALLBACK_KEY_DELAY_US);
s_send_cb(keycode, modifiers);
- ets_delay_us(KEY_PRESS_DELAY_US);
-
+ wait_report_ready(FALLBACK_KEY_DELAY_US);
s_send_cb(0, 0);
- ets_delay_us(KEY_RELEASE_DELAY_US);
-
- vTaskDelay(0); // Yield to prevent WDT starvation
+ wait_report_ready(FALLBACK_KEY_DELAY_US);
}
void hid_hal_mouse_move(int8_t x, int8_t y) {
@@ -60,9 +78,9 @@ void hid_hal_mouse_move(int8_t x, int8_t y) {
return;
}
+ wait_report_ready(FALLBACK_MOUSE_DELAY_US);
s_mouse_cb(x, y, 0, 0);
- ets_delay_us(MOUSE_MOVE_DELAY_US);
- vTaskDelay(0);
+ wait_report_ready(FALLBACK_MOUSE_DELAY_US);
}
void hid_hal_mouse_click(uint8_t buttons) {
@@ -70,11 +88,11 @@ void hid_hal_mouse_click(uint8_t buttons) {
return;
}
+ wait_report_ready(FALLBACK_MOUSE_DELAY_US);
s_mouse_cb(0, 0, buttons, 0); // Press
- ets_delay_us(MOUSE_CLICK_DELAY_US);
+ wait_report_ready(FALLBACK_MOUSE_DELAY_US);
s_mouse_cb(0, 0, 0, 0); // Release
- ets_delay_us(MOUSE_CLICK_DELAY_US);
- vTaskDelay(0);
+ wait_report_ready(FALLBACK_MOUSE_DELAY_US);
}
void hid_hal_mouse_scroll(int8_t wheel) {
@@ -82,9 +100,9 @@ void hid_hal_mouse_scroll(int8_t wheel) {
return;
}
+ wait_report_ready(FALLBACK_MOUSE_DELAY_US);
s_mouse_cb(0, 0, 0, wheel);
- ets_delay_us(MOUSE_MOVE_DELAY_US);
- vTaskDelay(0);
+ wait_report_ready(FALLBACK_MOUSE_DELAY_US);
}
void hid_hal_wait_for_connection(void) {
diff --git a/firmware_p4/components/Applications/bad_usb/hid_layouts.c b/firmware_p4/components/Applications/bad_usb/hid_layouts.c
index 01c5ff61d..37b2e3344 100644
--- a/firmware_p4/components/Applications/bad_usb/hid_layouts.c
+++ b/firmware_p4/components/Applications/bad_usb/hid_layouts.c
@@ -32,22 +32,6 @@ static const char *TAG = "HID_LAYOUTS";
#define HID_KEY_NON_US_BACKSLASH 0x64
#endif
-// ABNT2 UTF-8 byte pairs for accented characters
-#define UTF8_LOWER_C_CEDILLA_B2 0xA7 // c = 0xC3 0xA7
-#define UTF8_UPPER_C_CEDILLA_B2 0x87 // C = 0xC3 0x87
-#define UTF8_LOWER_A_ACUTE_B2 0xA1 // a = 0xC3 0xA1
-#define UTF8_LOWER_E_ACUTE_B2 0xA9 // e = 0xC3 0xA9
-#define UTF8_LOWER_I_ACUTE_B2 0xAD // i = 0xC3 0xAD
-#define UTF8_LOWER_O_ACUTE_B2 0xB3 // o = 0xC3 0xB3
-#define UTF8_LOWER_U_ACUTE_B2 0xBA // u = 0xC3 0xBA
-#define UTF8_LOWER_A_CIRCUM_B2 0xA2 // a = 0xC3 0xA2
-#define UTF8_LOWER_E_CIRCUM_B2 0xAA // e = 0xC3 0xAA
-#define UTF8_LOWER_O_CIRCUM_B2 0xB4 // o = 0xC3 0xB4
-#define UTF8_LOWER_A_TILDE_B2 0xA3 // a = 0xC3 0xA3
-#define UTF8_LOWER_O_TILDE_B2 0xB5 // o = 0xC3 0xB5
-#define UTF8_LOWER_A_GRAVE_B2 0xA0 // a = 0xC3 0xA0
-#define UTF8_2BYTE_LEAD 0xC3
-
static bool try_decode_abnt2_utf8(uint8_t c1, uint8_t c2);
void hid_layouts_type_string_us(const char *str) {
@@ -144,6 +128,19 @@ void hid_layouts_type_string_us(const char *str) {
case ';':
keycode = HID_KEY_SEMICOLON;
break;
+ case '\'':
+ keycode = HID_KEY_APOSTROPHE;
+ break;
+ case '`':
+ keycode = HID_KEY_GRAVE;
+ break;
+ case '\\':
+ keycode = HID_KEY_BACKSLASH;
+ break;
+ case '|':
+ modifier = KEYBOARD_MODIFIER_LEFTSHIFT;
+ keycode = HID_KEY_BACKSLASH;
+ break;
default:
break;
}
@@ -160,16 +157,32 @@ void hid_layouts_type_string_abnt2(const char *str) {
uint8_t c1 = (uint8_t)str[i];
uint8_t c2 = (uint8_t)str[i + 1];
- // Single quote -> dead key acute + space
+ // Apostrophe and double quote sit on the key left of '1' (US grave position).
if (c1 == '\'') {
- hid_hal_press_key(HID_KEY_BRACKET_LEFT, 0);
- hid_hal_press_key(HID_KEY_SPACE, 0);
+ hid_hal_press_key(HID_KEY_GRAVE, 0);
continue;
}
-
- // Double quote -> dead key acute + shift
if (c1 == '"') {
+ hid_hal_press_key(HID_KEY_GRAVE, KEYBOARD_MODIFIER_LEFTSHIFT);
+ continue;
+ }
+
+ // Backtick is the grave dead key (Shift + acute key); space emits the literal.
+ if (c1 == '`') {
hid_hal_press_key(HID_KEY_BRACKET_LEFT, KEYBOARD_MODIFIER_LEFTSHIFT);
+ hid_hal_press_key(HID_KEY_SPACE, 0);
+ continue;
+ }
+
+ // Circumflex/tilde are dead keys; a trailing space emits the literal char.
+ if (c1 == '^') {
+ hid_hal_press_key(HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT);
+ hid_hal_press_key(HID_KEY_SPACE, 0);
+ continue;
+ }
+ if (c1 == '~') {
+ hid_hal_press_key(HID_KEY_APOSTROPHE, 0);
+ hid_hal_press_key(HID_KEY_SPACE, 0);
continue;
}
@@ -260,6 +273,14 @@ void hid_layouts_type_string_abnt2(const char *str) {
case ',':
keycode = HID_KEY_COMMA;
break;
+ case '<':
+ modifier = KEYBOARD_MODIFIER_LEFTSHIFT;
+ keycode = HID_KEY_COMMA;
+ break;
+ case '>':
+ modifier = KEYBOARD_MODIFIER_LEFTSHIFT;
+ keycode = HID_KEY_PERIOD;
+ break;
case ';':
keycode = HID_KEY_SLASH;
break;
@@ -308,83 +329,96 @@ void hid_layouts_type_string_abnt2(const char *str) {
}
}
-static bool try_decode_abnt2_utf8(uint8_t c1, uint8_t c2) {
- if (c1 != UTF8_2BYTE_LEAD) {
- return false;
- }
+typedef struct {
+ uint8_t c1; // UTF-8 lead byte (0xC2 or 0xC3)
+ uint8_t c2; // UTF-8 continuation byte
+ uint8_t k1; // dead key or direct key
+ uint8_t m1; // modifier for k1
+ uint8_t k2; // base letter, 0 for a single keypress
+ uint8_t m2; // modifier for k2 (shift = uppercase)
+} abnt2_utf8_entry_t;
- // Cedilla
- if (c2 == UTF8_LOWER_C_CEDILLA_B2) {
- hid_hal_press_key(HID_KEY_SEMICOLON, 0);
- return true;
- }
- if (c2 == UTF8_UPPER_C_CEDILLA_B2) {
- hid_hal_press_key(HID_KEY_SEMICOLON, KEYBOARD_MODIFIER_LEFTSHIFT);
- return true;
- }
+// ABNT2 dead-key prefix (key, modifier) pressed before the base letter.
+#define DK_ACUTE HID_KEY_BRACKET_LEFT, 0
+#define DK_GRAVE HID_KEY_BRACKET_LEFT, KEYBOARD_MODIFIER_LEFTSHIFT
+#define DK_CIRCUM HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT
+#define DK_TILDE HID_KEY_APOSTROPHE, 0
+#define DK_DIAER HID_KEY_6, KEYBOARD_MODIFIER_LEFTSHIFT
- // Acute accent (dead key = BRACKET_LEFT)
- if (c2 == UTF8_LOWER_A_ACUTE_B2) {
- hid_hal_press_key(HID_KEY_BRACKET_LEFT, 0);
- hid_hal_press_key(HID_KEY_A, 0);
- return true;
- }
- if (c2 == UTF8_LOWER_E_ACUTE_B2) {
- hid_hal_press_key(HID_KEY_BRACKET_LEFT, 0);
- hid_hal_press_key(HID_KEY_E, 0);
- return true;
- }
- if (c2 == UTF8_LOWER_I_ACUTE_B2) {
- hid_hal_press_key(HID_KEY_BRACKET_LEFT, 0);
- hid_hal_press_key(HID_KEY_I, 0);
- return true;
- }
- if (c2 == UTF8_LOWER_O_ACUTE_B2) {
- hid_hal_press_key(HID_KEY_BRACKET_LEFT, 0);
- hid_hal_press_key(HID_KEY_O, 0);
- return true;
- }
- if (c2 == UTF8_LOWER_U_ACUTE_B2) {
- hid_hal_press_key(HID_KEY_BRACKET_LEFT, 0);
- hid_hal_press_key(HID_KEY_U, 0);
- return true;
- }
+static const abnt2_utf8_entry_t ABNT2_UTF8_MAP[] = {
+ {0xC3, 0xA7, HID_KEY_SEMICOLON, 0, 0, 0}, // ç
+ {0xC3, 0x87, HID_KEY_SEMICOLON, KEYBOARD_MODIFIER_LEFTSHIFT, 0, 0}, // Ç
+ {0xC3, 0xA0, DK_GRAVE, HID_KEY_A, 0}, // à
+ {0xC3, 0xA1, DK_ACUTE, HID_KEY_A, 0}, // á
+ {0xC3, 0xA2, DK_CIRCUM, HID_KEY_A, 0}, // â
+ {0xC3, 0xA3, DK_TILDE, HID_KEY_A, 0}, // ã
+ {0xC3, 0xA4, DK_DIAER, HID_KEY_A, 0}, // ä
+ {0xC3, 0xA8, DK_GRAVE, HID_KEY_E, 0}, // è
+ {0xC3, 0xA9, DK_ACUTE, HID_KEY_E, 0}, // é
+ {0xC3, 0xAA, DK_CIRCUM, HID_KEY_E, 0}, // ê
+ {0xC3, 0xAB, DK_DIAER, HID_KEY_E, 0}, // ë
+ {0xC3, 0xAC, DK_GRAVE, HID_KEY_I, 0}, // ì
+ {0xC3, 0xAD, DK_ACUTE, HID_KEY_I, 0}, // í
+ {0xC3, 0xAE, DK_CIRCUM, HID_KEY_I, 0}, // î
+ {0xC3, 0xAF, DK_DIAER, HID_KEY_I, 0}, // ï
+ {0xC3, 0xB1, DK_TILDE, HID_KEY_N, 0}, // ñ
+ {0xC3, 0xB2, DK_GRAVE, HID_KEY_O, 0}, // ò
+ {0xC3, 0xB3, DK_ACUTE, HID_KEY_O, 0}, // ó
+ {0xC3, 0xB4, DK_CIRCUM, HID_KEY_O, 0}, // ô
+ {0xC3, 0xB5, DK_TILDE, HID_KEY_O, 0}, // õ
+ {0xC3, 0xB6, DK_DIAER, HID_KEY_O, 0}, // ö
+ {0xC3, 0xB9, DK_GRAVE, HID_KEY_U, 0}, // ù
+ {0xC3, 0xBA, DK_ACUTE, HID_KEY_U, 0}, // ú
+ {0xC3, 0xBB, DK_CIRCUM, HID_KEY_U, 0}, // û
+ {0xC3, 0xBC, DK_DIAER, HID_KEY_U, 0}, // ü
+ {0xC3, 0x80, DK_GRAVE, HID_KEY_A, KEYBOARD_MODIFIER_LEFTSHIFT}, // À
+ {0xC3, 0x81, DK_ACUTE, HID_KEY_A, KEYBOARD_MODIFIER_LEFTSHIFT}, // Á
+ {0xC3, 0x82, DK_CIRCUM, HID_KEY_A, KEYBOARD_MODIFIER_LEFTSHIFT}, // Â
+ {0xC3, 0x83, DK_TILDE, HID_KEY_A, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ã
+ {0xC3, 0x84, DK_DIAER, HID_KEY_A, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ä
+ {0xC3, 0x88, DK_GRAVE, HID_KEY_E, KEYBOARD_MODIFIER_LEFTSHIFT}, // È
+ {0xC3, 0x89, DK_ACUTE, HID_KEY_E, KEYBOARD_MODIFIER_LEFTSHIFT}, // É
+ {0xC3, 0x8A, DK_CIRCUM, HID_KEY_E, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ê
+ {0xC3, 0x8B, DK_DIAER, HID_KEY_E, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ë
+ {0xC3, 0x8C, DK_GRAVE, HID_KEY_I, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ì
+ {0xC3, 0x8D, DK_ACUTE, HID_KEY_I, KEYBOARD_MODIFIER_LEFTSHIFT}, // Í
+ {0xC3, 0x8E, DK_CIRCUM, HID_KEY_I, KEYBOARD_MODIFIER_LEFTSHIFT}, // Î
+ {0xC3, 0x8F, DK_DIAER, HID_KEY_I, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ï
+ {0xC3, 0x91, DK_TILDE, HID_KEY_N, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ñ
+ {0xC3, 0x92, DK_GRAVE, HID_KEY_O, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ò
+ {0xC3, 0x93, DK_ACUTE, HID_KEY_O, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ó
+ {0xC3, 0x94, DK_CIRCUM, HID_KEY_O, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ô
+ {0xC3, 0x95, DK_TILDE, HID_KEY_O, KEYBOARD_MODIFIER_LEFTSHIFT}, // Õ
+ {0xC3, 0x96, DK_DIAER, HID_KEY_O, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ö
+ {0xC3, 0x99, DK_GRAVE, HID_KEY_U, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ù
+ {0xC3, 0x9A, DK_ACUTE, HID_KEY_U, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ú
+ {0xC3, 0x9B, DK_CIRCUM, HID_KEY_U, KEYBOARD_MODIFIER_LEFTSHIFT}, // Û
+ {0xC3, 0x9C, DK_DIAER, HID_KEY_U, KEYBOARD_MODIFIER_LEFTSHIFT}, // Ü
+ {0xC2, 0xA2, HID_KEY_5, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // ¢
+ {0xC2, 0xA3, HID_KEY_4, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // £
+ {0xC2, 0xA7, HID_KEY_EQUAL, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // §
+ {0xC2, 0xAC, HID_KEY_6, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // ¬
+ {0xC2, 0xB2, HID_KEY_2, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // ²
+ {0xC2, 0xB3, HID_KEY_3, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // ³
+ {0xC2, 0xB9, HID_KEY_1, KEYBOARD_MODIFIER_RIGHTALT, 0, 0}, // ¹
+};
- // Circumflex accent (dead key = APOSTROPHE + SHIFT)
- if (c2 == UTF8_LOWER_A_CIRCUM_B2) {
- hid_hal_press_key(HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT);
- hid_hal_press_key(HID_KEY_A, 0);
- return true;
- }
- if (c2 == UTF8_LOWER_E_CIRCUM_B2) {
- hid_hal_press_key(HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT);
- hid_hal_press_key(HID_KEY_E, 0);
- return true;
- }
- if (c2 == UTF8_LOWER_O_CIRCUM_B2) {
- hid_hal_press_key(HID_KEY_APOSTROPHE, KEYBOARD_MODIFIER_LEFTSHIFT);
- hid_hal_press_key(HID_KEY_O, 0);
- return true;
- }
+#undef DK_ACUTE
+#undef DK_GRAVE
+#undef DK_CIRCUM
+#undef DK_TILDE
+#undef DK_DIAER
- // Tilde (dead key = APOSTROPHE)
- if (c2 == UTF8_LOWER_A_TILDE_B2) {
- hid_hal_press_key(HID_KEY_APOSTROPHE, 0);
- hid_hal_press_key(HID_KEY_A, 0);
- return true;
- }
- if (c2 == UTF8_LOWER_O_TILDE_B2) {
- hid_hal_press_key(HID_KEY_APOSTROPHE, 0);
- hid_hal_press_key(HID_KEY_O, 0);
- return true;
- }
-
- // Grave accent (dead key = BRACKET_LEFT + SHIFT)
- if (c2 == UTF8_LOWER_A_GRAVE_B2) {
- hid_hal_press_key(HID_KEY_BRACKET_LEFT, KEYBOARD_MODIFIER_LEFTSHIFT);
- hid_hal_press_key(HID_KEY_A, 0);
- return true;
+static bool try_decode_abnt2_utf8(uint8_t c1, uint8_t c2) {
+ for (size_t i = 0; i < sizeof(ABNT2_UTF8_MAP) / sizeof(ABNT2_UTF8_MAP[0]); ++i) {
+ const abnt2_utf8_entry_t *e = &ABNT2_UTF8_MAP[i];
+ if (e->c1 == c1 && e->c2 == c2) {
+ hid_hal_press_key(e->k1, e->m1);
+ if (e->k2 != 0) {
+ hid_hal_press_key(e->k2, e->m2);
+ }
+ return true;
+ }
}
-
return false;
}
diff --git a/firmware_p4/components/Applications/bad_usb/include/hid_hal.h b/firmware_p4/components/Applications/bad_usb/include/hid_hal.h
index 2598af055..2ad15ef8f 100644
--- a/firmware_p4/components/Applications/bad_usb/include/hid_hal.h
+++ b/firmware_p4/components/Applications/bad_usb/include/hid_hal.h
@@ -20,6 +20,7 @@
extern "C" {
#endif
+#include
#include
/**
@@ -45,6 +46,15 @@ typedef void (*hid_mouse_cb_t)(int8_t x, int8_t y, uint8_t buttons, int8_t wheel
*/
typedef void (*hid_wait_cb_t)(void);
+/**
+ * @brief Callback type reporting whether the transport can accept a new report.
+ *
+ * Returns true once the previous report has been delivered to the host. The HAL
+ * gates on this to send at the host's poll rate without overwriting a report the
+ * host has not read yet (which would drop keys).
+ */
+typedef bool (*hid_ready_cb_t)(void);
+
/**
* @brief Register the transport driver callbacks.
*
@@ -54,10 +64,12 @@ typedef void (*hid_wait_cb_t)(void);
* @param send_cb Keyboard report callback.
* @param mouse_cb Mouse report callback.
* @param wait_cb Connection wait callback.
+ * @param ready_cb Report-readiness callback (NULL falls back to fixed delays).
*/
void hid_hal_register_callback(hid_send_cb_t send_cb,
hid_mouse_cb_t mouse_cb,
- hid_wait_cb_t wait_cb);
+ hid_wait_cb_t wait_cb,
+ hid_ready_cb_t ready_cb);
/**
* @brief Press and release a key.
diff --git a/firmware_p4/components/Applications/doom/CMakeLists.txt b/firmware_p4/components/Applications/doom/CMakeLists.txt
new file mode 100644
index 000000000..39b6b4e47
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/CMakeLists.txt
@@ -0,0 +1,23 @@
+# Official DOOM (doomgeneric) as an ESP-IDF component for the HighBoy P4.
+# All engine sources + doomgeneric.c + the HighBoy platform layer compile here.
+file(GLOB DOOM_SRCS "*.c")
+
+idf_component_register(
+ SRCS ${DOOM_SRCS}
+ INCLUDE_DIRS "include" # public: only doom_highboy.h (launcher entry)
+ PRIV_INCLUDE_DIRS "." # private: doomgeneric internal headers (config.h, etc.)
+ REQUIRES Drivers Service esp_lcd esp_timer esp_system driver
+ LDFRAGMENTS "linker.lf" # park DOOM's ~300 KB static .bss in PSRAM
+)
+
+# Render at DOOM's native internal resolution (no upscale). ALL sources must
+# agree, so define it component-wide.
+target_compile_definitions(${COMPONENT_LIB} PRIVATE
+ DOOMGENERIC_RESX=320
+ DOOMGENERIC_RESY=200
+ FEATURE_SOUND=1 # enable SFX (DG_sound_module in doom_sound_highboy.c)
+)
+
+# Legacy id-Software C: silence its many warnings and optimize for framerate
+# (the project default is -Og; DOOM needs -O2 to be playable).
+target_compile_options(${COMPONENT_LIB} PRIVATE -O2 -w)
diff --git a/firmware_p4/components/Applications/doom/am_map.c b/firmware_p4/components/Applications/doom/am_map.c
new file mode 100644
index 000000000..d3d504e32
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/am_map.c
@@ -0,0 +1,1355 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+//
+// DESCRIPTION: the automap code
+//
+
+
+#include
+
+#include "deh_main.h"
+
+#include "z_zone.h"
+#include "doomkeys.h"
+#include "doomdef.h"
+#include "st_stuff.h"
+#include "p_local.h"
+#include "w_wad.h"
+
+#include "m_cheat.h"
+#include "m_controls.h"
+#include "m_misc.h"
+#include "i_system.h"
+
+// Needs access to LFB.
+#include "v_video.h"
+
+// State.
+#include "doomstat.h"
+#include "r_state.h"
+
+// Data.
+#include "dstrings.h"
+
+#include "am_map.h"
+
+
+// For use if I do walls with outsides/insides
+#define REDS (256-5*16)
+#define REDRANGE 16
+#define BLUES (256-4*16+8)
+#define BLUERANGE 8
+#define GREENS (7*16)
+#define GREENRANGE 16
+#define GRAYS (6*16)
+#define GRAYSRANGE 16
+#define BROWNS (4*16)
+#define BROWNRANGE 16
+#define YELLOWS (256-32+7)
+#define YELLOWRANGE 1
+#define BLACK 0
+#define WHITE (256-47)
+
+// Automap colors
+#define BACKGROUND BLACK
+#define YOURCOLORS WHITE
+#define YOURRANGE 0
+#define WALLCOLORS REDS
+#define WALLRANGE REDRANGE
+#define TSWALLCOLORS GRAYS
+#define TSWALLRANGE GRAYSRANGE
+#define FDWALLCOLORS BROWNS
+#define FDWALLRANGE BROWNRANGE
+#define CDWALLCOLORS YELLOWS
+#define CDWALLRANGE YELLOWRANGE
+#define THINGCOLORS GREENS
+#define THINGRANGE GREENRANGE
+#define SECRETWALLCOLORS WALLCOLORS
+#define SECRETWALLRANGE WALLRANGE
+#define GRIDCOLORS (GRAYS + GRAYSRANGE/2)
+#define GRIDRANGE 0
+#define XHAIRCOLORS GRAYS
+
+// drawing stuff
+
+#define AM_NUMMARKPOINTS 10
+
+// scale on entry
+#define INITSCALEMTOF (.2*FRACUNIT)
+// how much the automap moves window per tic in frame-buffer coordinates
+// moves 140 pixels in 1 second
+#define F_PANINC 4
+// how much zoom-in per tic
+// goes to 2x in 1 second
+#define M_ZOOMIN ((int) (1.02*FRACUNIT))
+// how much zoom-out per tic
+// pulls out to 0.5x in 1 second
+#define M_ZOOMOUT ((int) (FRACUNIT/1.02))
+
+// translates between frame-buffer and map distances
+#define FTOM(x) FixedMul(((x)<<16),scale_ftom)
+#define MTOF(x) (FixedMul((x),scale_mtof)>>16)
+// translates between frame-buffer and map coordinates
+#define CXMTOF(x) (f_x + MTOF((x)-m_x))
+#define CYMTOF(y) (f_y + (f_h - MTOF((y)-m_y)))
+
+// the following is crap
+#define LINE_NEVERSEE ML_DONTDRAW
+
+typedef struct
+{
+ int x, y;
+} fpoint_t;
+
+typedef struct
+{
+ fpoint_t a, b;
+} fline_t;
+
+typedef struct
+{
+ fixed_t x,y;
+} mpoint_t;
+
+typedef struct
+{
+ mpoint_t a, b;
+} mline_t;
+
+typedef struct
+{
+ fixed_t slp, islp;
+} islope_t;
+
+
+
+//
+// The vector graphics for the automap.
+// A line drawing of the player pointing right,
+// starting from the middle.
+//
+#define R ((8*PLAYERRADIUS)/7)
+mline_t player_arrow[] = {
+ { { -R+R/8, 0 }, { R, 0 } }, // -----
+ { { R, 0 }, { R-R/2, R/4 } }, // ----->
+ { { R, 0 }, { R-R/2, -R/4 } },
+ { { -R+R/8, 0 }, { -R-R/8, R/4 } }, // >---->
+ { { -R+R/8, 0 }, { -R-R/8, -R/4 } },
+ { { -R+3*R/8, 0 }, { -R+R/8, R/4 } }, // >>--->
+ { { -R+3*R/8, 0 }, { -R+R/8, -R/4 } }
+};
+#undef R
+
+#define R ((8*PLAYERRADIUS)/7)
+mline_t cheat_player_arrow[] = {
+ { { -R+R/8, 0 }, { R, 0 } }, // -----
+ { { R, 0 }, { R-R/2, R/6 } }, // ----->
+ { { R, 0 }, { R-R/2, -R/6 } },
+ { { -R+R/8, 0 }, { -R-R/8, R/6 } }, // >----->
+ { { -R+R/8, 0 }, { -R-R/8, -R/6 } },
+ { { -R+3*R/8, 0 }, { -R+R/8, R/6 } }, // >>----->
+ { { -R+3*R/8, 0 }, { -R+R/8, -R/6 } },
+ { { -R/2, 0 }, { -R/2, -R/6 } }, // >>-d--->
+ { { -R/2, -R/6 }, { -R/2+R/6, -R/6 } },
+ { { -R/2+R/6, -R/6 }, { -R/2+R/6, R/4 } },
+ { { -R/6, 0 }, { -R/6, -R/6 } }, // >>-dd-->
+ { { -R/6, -R/6 }, { 0, -R/6 } },
+ { { 0, -R/6 }, { 0, R/4 } },
+ { { R/6, R/4 }, { R/6, -R/7 } }, // >>-ddt->
+ { { R/6, -R/7 }, { R/6+R/32, -R/7-R/32 } },
+ { { R/6+R/32, -R/7-R/32 }, { R/6+R/10, -R/7 } }
+};
+#undef R
+
+#define R (FRACUNIT)
+mline_t triangle_guy[] = {
+ { { (fixed_t)(-.867*R), (fixed_t)(-.5*R) }, { (fixed_t)(.867*R ), (fixed_t)(-.5*R) } },
+ { { (fixed_t)(.867*R ), (fixed_t)(-.5*R) }, { (fixed_t)(0 ), (fixed_t)(R ) } },
+ { { (fixed_t)(0 ), (fixed_t)(R ) }, { (fixed_t)(-.867*R), (fixed_t)(-.5*R) } }
+};
+#undef R
+
+#define R (FRACUNIT)
+mline_t thintriangle_guy[] = {
+ { { (fixed_t)(-.5*R), (fixed_t)(-.7*R) }, { (fixed_t)(R ), (fixed_t)(0 ) } },
+ { { (fixed_t)(R ), (fixed_t)(0 ) }, { (fixed_t)(-.5*R), (fixed_t)(.7*R ) } },
+ { { (fixed_t)(-.5*R), (fixed_t)(.7*R ) }, { (fixed_t)(-.5*R), (fixed_t)(-.7*R) } }
+};
+#undef R
+
+
+
+
+static int cheating = 0;
+static int grid = 0;
+
+static int leveljuststarted = 1; // kluge until AM_LevelInit() is called
+
+boolean automapactive = false;
+static int finit_width = SCREENWIDTH;
+static int finit_height = SCREENHEIGHT - 32;
+
+// location of window on screen
+static int f_x;
+static int f_y;
+
+// size of window on screen
+static int f_w;
+static int f_h;
+
+static int lightlev; // used for funky strobing effect
+static byte* fb; // pseudo-frame buffer
+static int amclock;
+
+static mpoint_t m_paninc; // how far the window pans each tic (map coords)
+static fixed_t mtof_zoommul; // how far the window zooms in each tic (map coords)
+static fixed_t ftom_zoommul; // how far the window zooms in each tic (fb coords)
+
+static fixed_t m_x, m_y; // LL x,y where the window is on the map (map coords)
+static fixed_t m_x2, m_y2; // UR x,y where the window is on the map (map coords)
+
+//
+// width/height of window on map (map coords)
+//
+static fixed_t m_w;
+static fixed_t m_h;
+
+// based on level size
+static fixed_t min_x;
+static fixed_t min_y;
+static fixed_t max_x;
+static fixed_t max_y;
+
+static fixed_t max_w; // max_x-min_x,
+static fixed_t max_h; // max_y-min_y
+
+// based on player size
+static fixed_t min_w;
+static fixed_t min_h;
+
+
+static fixed_t min_scale_mtof; // used to tell when to stop zooming out
+static fixed_t max_scale_mtof; // used to tell when to stop zooming in
+
+// old stuff for recovery later
+static fixed_t old_m_w, old_m_h;
+static fixed_t old_m_x, old_m_y;
+
+// old location used by the Follower routine
+static mpoint_t f_oldloc;
+
+// used by MTOF to scale from map-to-frame-buffer coords
+static fixed_t scale_mtof = (fixed_t)INITSCALEMTOF;
+// used by FTOM to scale from frame-buffer-to-map coords (=1/scale_mtof)
+static fixed_t scale_ftom;
+
+static player_t *plr; // the player represented by an arrow
+
+static patch_t *marknums[10]; // numbers used for marking by the automap
+static mpoint_t markpoints[AM_NUMMARKPOINTS]; // where the points are
+static int markpointnum = 0; // next point to be assigned
+
+static int followplayer = 1; // specifies whether to follow the player around
+
+cheatseq_t cheat_amap = CHEAT("iddt", 0);
+
+static boolean stopped = true;
+
+// Calculates the slope and slope according to the x-axis of a line
+// segment in map coordinates (with the upright y-axis n' all) so
+// that it can be used with the brain-dead drawing stuff.
+
+void
+AM_getIslope
+( mline_t* ml,
+ islope_t* is )
+{
+ int dx, dy;
+
+ dy = ml->a.y - ml->b.y;
+ dx = ml->b.x - ml->a.x;
+ if (!dy) is->islp = (dx<0?-INT_MAX:INT_MAX);
+ else is->islp = FixedDiv(dx, dy);
+ if (!dx) is->slp = (dy<0?-INT_MAX:INT_MAX);
+ else is->slp = FixedDiv(dy, dx);
+
+}
+
+//
+//
+//
+void AM_activateNewScale(void)
+{
+ m_x += m_w/2;
+ m_y += m_h/2;
+ m_w = FTOM(f_w);
+ m_h = FTOM(f_h);
+ m_x -= m_w/2;
+ m_y -= m_h/2;
+ m_x2 = m_x + m_w;
+ m_y2 = m_y + m_h;
+}
+
+//
+//
+//
+void AM_saveScaleAndLoc(void)
+{
+ old_m_x = m_x;
+ old_m_y = m_y;
+ old_m_w = m_w;
+ old_m_h = m_h;
+}
+
+//
+//
+//
+void AM_restoreScaleAndLoc(void)
+{
+
+ m_w = old_m_w;
+ m_h = old_m_h;
+ if (!followplayer)
+ {
+ m_x = old_m_x;
+ m_y = old_m_y;
+ } else {
+ m_x = plr->mo->x - m_w/2;
+ m_y = plr->mo->y - m_h/2;
+ }
+ m_x2 = m_x + m_w;
+ m_y2 = m_y + m_h;
+
+ // Change the scaling multipliers
+ scale_mtof = FixedDiv(f_w< max_x)
+ max_x = vertexes[i].x;
+
+ if (vertexes[i].y < min_y)
+ min_y = vertexes[i].y;
+ else if (vertexes[i].y > max_y)
+ max_y = vertexes[i].y;
+ }
+
+ max_w = max_x - min_x;
+ max_h = max_y - min_y;
+
+ min_w = 2*PLAYERRADIUS; // const? never changed?
+ min_h = 2*PLAYERRADIUS;
+
+ a = FixedDiv(f_w< max_x)
+ m_x = max_x - m_w/2;
+ else if (m_x + m_w/2 < min_x)
+ m_x = min_x - m_w/2;
+
+ if (m_y + m_h/2 > max_y)
+ m_y = max_y - m_h/2;
+ else if (m_y + m_h/2 < min_y)
+ m_y = min_y - m_h/2;
+
+ m_x2 = m_x + m_w;
+ m_y2 = m_y + m_h;
+}
+
+
+//
+//
+//
+void AM_initVariables(void)
+{
+ int pnum;
+ static event_t st_notify = { ev_keyup, AM_MSGENTERED, 0, 0 };
+
+ automapactive = true;
+ fb = I_VideoBuffer;
+
+ f_oldloc.x = INT_MAX;
+ amclock = 0;
+ lightlev = 0;
+
+ m_paninc.x = m_paninc.y = 0;
+ ftom_zoommul = FRACUNIT;
+ mtof_zoommul = FRACUNIT;
+
+ m_w = FTOM(f_w);
+ m_h = FTOM(f_h);
+
+ // find player to center on initially
+ if (playeringame[consoleplayer])
+ {
+ plr = &players[consoleplayer];
+ }
+ else
+ {
+ plr = &players[0];
+
+ for (pnum=0;pnummo->x - m_w/2;
+ m_y = plr->mo->y - m_h/2;
+ AM_changeWindowLoc();
+
+ // for saving & restoring
+ old_m_x = m_x;
+ old_m_y = m_y;
+ old_m_w = m_w;
+ old_m_h = m_h;
+
+ // inform the status bar of the change
+ ST_Responder(&st_notify);
+
+}
+
+//
+//
+//
+void AM_loadPics(void)
+{
+ int i;
+ char namebuf[9];
+
+ for (i=0;i<10;i++)
+ {
+ DEH_snprintf(namebuf, 9, "AMMNUM%d", i);
+ marknums[i] = W_CacheLumpName(namebuf, PU_STATIC);
+ }
+
+}
+
+void AM_unloadPics(void)
+{
+ int i;
+ char namebuf[9];
+
+ for (i=0;i<10;i++)
+ {
+ DEH_snprintf(namebuf, 9, "AMMNUM%d", i);
+ W_ReleaseLumpName(namebuf);
+ }
+}
+
+void AM_clearMarks(void)
+{
+ int i;
+
+ for (i=0;i max_scale_mtof)
+ scale_mtof = min_scale_mtof;
+ scale_ftom = FixedDiv(FRACUNIT, scale_mtof);
+}
+
+
+
+
+//
+//
+//
+void AM_Stop (void)
+{
+ static event_t st_notify = { 0, ev_keyup, AM_MSGEXITED, 0 };
+
+ AM_unloadPics();
+ automapactive = false;
+ ST_Responder(&st_notify);
+ stopped = true;
+}
+
+//
+//
+//
+void AM_Start (void)
+{
+ static int lastlevel = -1, lastepisode = -1;
+
+ if (!stopped) AM_Stop();
+ stopped = false;
+ if (lastlevel != gamemap || lastepisode != gameepisode)
+ {
+ AM_LevelInit();
+ lastlevel = gamemap;
+ lastepisode = gameepisode;
+ }
+ AM_initVariables();
+ AM_loadPics();
+}
+
+//
+// set the window scale to the maximum size
+//
+void AM_minOutWindowScale(void)
+{
+ scale_mtof = min_scale_mtof;
+ scale_ftom = FixedDiv(FRACUNIT, scale_mtof);
+ AM_activateNewScale();
+}
+
+//
+// set the window scale to the minimum size
+//
+void AM_maxOutWindowScale(void)
+{
+ scale_mtof = max_scale_mtof;
+ scale_ftom = FixedDiv(FRACUNIT, scale_mtof);
+ AM_activateNewScale();
+}
+
+
+//
+// Handle events (user inputs) in automap mode
+//
+boolean
+AM_Responder
+( event_t* ev )
+{
+
+ int rc;
+ static int bigstate=0;
+ static char buffer[20];
+ int key;
+
+ rc = false;
+
+ if (!automapactive)
+ {
+ if (ev->type == ev_keydown && ev->data1 == key_map_toggle)
+ {
+ AM_Start ();
+ viewactive = false;
+ rc = true;
+ }
+ }
+ else if (ev->type == ev_keydown)
+ {
+ rc = true;
+ key = ev->data1;
+
+ if (key == key_map_east) // pan right
+ {
+ if (!followplayer) m_paninc.x = FTOM(F_PANINC);
+ else rc = false;
+ }
+ else if (key == key_map_west) // pan left
+ {
+ if (!followplayer) m_paninc.x = -FTOM(F_PANINC);
+ else rc = false;
+ }
+ else if (key == key_map_north) // pan up
+ {
+ if (!followplayer) m_paninc.y = FTOM(F_PANINC);
+ else rc = false;
+ }
+ else if (key == key_map_south) // pan down
+ {
+ if (!followplayer) m_paninc.y = -FTOM(F_PANINC);
+ else rc = false;
+ }
+ else if (key == key_map_zoomout) // zoom out
+ {
+ mtof_zoommul = M_ZOOMOUT;
+ ftom_zoommul = M_ZOOMIN;
+ }
+ else if (key == key_map_zoomin) // zoom in
+ {
+ mtof_zoommul = M_ZOOMIN;
+ ftom_zoommul = M_ZOOMOUT;
+ }
+ else if (key == key_map_toggle)
+ {
+ bigstate = 0;
+ viewactive = true;
+ AM_Stop ();
+ }
+ else if (key == key_map_maxzoom)
+ {
+ bigstate = !bigstate;
+ if (bigstate)
+ {
+ AM_saveScaleAndLoc();
+ AM_minOutWindowScale();
+ }
+ else AM_restoreScaleAndLoc();
+ }
+ else if (key == key_map_follow)
+ {
+ followplayer = !followplayer;
+ f_oldloc.x = INT_MAX;
+ if (followplayer)
+ plr->message = DEH_String(AMSTR_FOLLOWON);
+ else
+ plr->message = DEH_String(AMSTR_FOLLOWOFF);
+ }
+ else if (key == key_map_grid)
+ {
+ grid = !grid;
+ if (grid)
+ plr->message = DEH_String(AMSTR_GRIDON);
+ else
+ plr->message = DEH_String(AMSTR_GRIDOFF);
+ }
+ else if (key == key_map_mark)
+ {
+ M_snprintf(buffer, sizeof(buffer), "%s %d",
+ DEH_String(AMSTR_MARKEDSPOT), markpointnum);
+ plr->message = buffer;
+ AM_addMark();
+ }
+ else if (key == key_map_clearmark)
+ {
+ AM_clearMarks();
+ plr->message = DEH_String(AMSTR_MARKSCLEARED);
+ }
+ else
+ {
+ rc = false;
+ }
+
+ if (!deathmatch && cht_CheckCheat(&cheat_amap, ev->data2))
+ {
+ rc = false;
+ cheating = (cheating+1) % 3;
+ }
+ }
+ else if (ev->type == ev_keyup)
+ {
+ rc = false;
+ key = ev->data1;
+
+ if (key == key_map_east)
+ {
+ if (!followplayer) m_paninc.x = 0;
+ }
+ else if (key == key_map_west)
+ {
+ if (!followplayer) m_paninc.x = 0;
+ }
+ else if (key == key_map_north)
+ {
+ if (!followplayer) m_paninc.y = 0;
+ }
+ else if (key == key_map_south)
+ {
+ if (!followplayer) m_paninc.y = 0;
+ }
+ else if (key == key_map_zoomout || key == key_map_zoomin)
+ {
+ mtof_zoommul = FRACUNIT;
+ ftom_zoommul = FRACUNIT;
+ }
+ }
+
+ return rc;
+
+}
+
+
+//
+// Zooming
+//
+void AM_changeWindowScale(void)
+{
+
+ // Change the scaling multipliers
+ scale_mtof = FixedMul(scale_mtof, mtof_zoommul);
+ scale_ftom = FixedDiv(FRACUNIT, scale_mtof);
+
+ if (scale_mtof < min_scale_mtof)
+ AM_minOutWindowScale();
+ else if (scale_mtof > max_scale_mtof)
+ AM_maxOutWindowScale();
+ else
+ AM_activateNewScale();
+}
+
+
+//
+//
+//
+void AM_doFollowPlayer(void)
+{
+
+ if (f_oldloc.x != plr->mo->x || f_oldloc.y != plr->mo->y)
+ {
+ m_x = FTOM(MTOF(plr->mo->x)) - m_w/2;
+ m_y = FTOM(MTOF(plr->mo->y)) - m_h/2;
+ m_x2 = m_x + m_w;
+ m_y2 = m_y + m_h;
+ f_oldloc.x = plr->mo->x;
+ f_oldloc.y = plr->mo->y;
+
+ // m_x = FTOM(MTOF(plr->mo->x - m_w/2));
+ // m_y = FTOM(MTOF(plr->mo->y - m_h/2));
+ // m_x = plr->mo->x - m_w/2;
+ // m_y = plr->mo->y - m_h/2;
+
+ }
+
+}
+
+//
+//
+//
+void AM_updateLightLev(void)
+{
+ static int nexttic = 0;
+ //static int litelevels[] = { 0, 3, 5, 6, 6, 7, 7, 7 };
+ static int litelevels[] = { 0, 4, 7, 10, 12, 14, 15, 15 };
+ static int litelevelscnt = 0;
+
+ // Change light level
+ if (amclock>nexttic)
+ {
+ lightlev = litelevels[litelevelscnt++];
+ if (litelevelscnt == arrlen(litelevels)) litelevelscnt = 0;
+ nexttic = amclock + 6 - (amclock % 6);
+ }
+
+}
+
+
+//
+// Updates on Game Tick
+//
+void AM_Ticker (void)
+{
+
+ if (!automapactive)
+ return;
+
+ amclock++;
+
+ if (followplayer)
+ AM_doFollowPlayer();
+
+ // Change the zoom if necessary
+ if (ftom_zoommul != FRACUNIT)
+ AM_changeWindowScale();
+
+ // Change x,y location
+ if (m_paninc.x || m_paninc.y)
+ AM_changeWindowLoc();
+
+ // Update light level
+ // AM_updateLightLev();
+
+}
+
+
+//
+// Clear automap frame buffer.
+//
+void AM_clearFB(int color)
+{
+ memset(fb, color, f_w*f_h);
+}
+
+
+//
+// Automap clipping of lines.
+//
+// Based on Cohen-Sutherland clipping algorithm but with a slightly
+// faster reject and precalculated slopes. If the speed is needed,
+// use a hash algorithm to handle the common cases.
+//
+boolean
+AM_clipMline
+( mline_t* ml,
+ fline_t* fl )
+{
+ enum
+ {
+ LEFT =1,
+ RIGHT =2,
+ BOTTOM =4,
+ TOP =8
+ };
+
+ register int outcode1 = 0;
+ register int outcode2 = 0;
+ register int outside;
+
+ fpoint_t tmp;
+ int dx;
+ int dy;
+
+
+#define DOOUTCODE(oc, mx, my) \
+ (oc) = 0; \
+ if ((my) < 0) (oc) |= TOP; \
+ else if ((my) >= f_h) (oc) |= BOTTOM; \
+ if ((mx) < 0) (oc) |= LEFT; \
+ else if ((mx) >= f_w) (oc) |= RIGHT;
+
+
+ // do trivial rejects and outcodes
+ if (ml->a.y > m_y2)
+ outcode1 = TOP;
+ else if (ml->a.y < m_y)
+ outcode1 = BOTTOM;
+
+ if (ml->b.y > m_y2)
+ outcode2 = TOP;
+ else if (ml->b.y < m_y)
+ outcode2 = BOTTOM;
+
+ if (outcode1 & outcode2)
+ return false; // trivially outside
+
+ if (ml->a.x < m_x)
+ outcode1 |= LEFT;
+ else if (ml->a.x > m_x2)
+ outcode1 |= RIGHT;
+
+ if (ml->b.x < m_x)
+ outcode2 |= LEFT;
+ else if (ml->b.x > m_x2)
+ outcode2 |= RIGHT;
+
+ if (outcode1 & outcode2)
+ return false; // trivially outside
+
+ // transform to frame-buffer coordinates.
+ fl->a.x = CXMTOF(ml->a.x);
+ fl->a.y = CYMTOF(ml->a.y);
+ fl->b.x = CXMTOF(ml->b.x);
+ fl->b.y = CYMTOF(ml->b.y);
+
+ DOOUTCODE(outcode1, fl->a.x, fl->a.y);
+ DOOUTCODE(outcode2, fl->b.x, fl->b.y);
+
+ if (outcode1 & outcode2)
+ return false;
+
+ while (outcode1 | outcode2)
+ {
+ // may be partially inside box
+ // find an outside point
+ if (outcode1)
+ outside = outcode1;
+ else
+ outside = outcode2;
+
+ // clip to each side
+ if (outside & TOP)
+ {
+ dy = fl->a.y - fl->b.y;
+ dx = fl->b.x - fl->a.x;
+ tmp.x = fl->a.x + (dx*(fl->a.y))/dy;
+ tmp.y = 0;
+ }
+ else if (outside & BOTTOM)
+ {
+ dy = fl->a.y - fl->b.y;
+ dx = fl->b.x - fl->a.x;
+ tmp.x = fl->a.x + (dx*(fl->a.y-f_h))/dy;
+ tmp.y = f_h-1;
+ }
+ else if (outside & RIGHT)
+ {
+ dy = fl->b.y - fl->a.y;
+ dx = fl->b.x - fl->a.x;
+ tmp.y = fl->a.y + (dy*(f_w-1 - fl->a.x))/dx;
+ tmp.x = f_w-1;
+ }
+ else if (outside & LEFT)
+ {
+ dy = fl->b.y - fl->a.y;
+ dx = fl->b.x - fl->a.x;
+ tmp.y = fl->a.y + (dy*(-fl->a.x))/dx;
+ tmp.x = 0;
+ }
+ else
+ {
+ tmp.x = 0;
+ tmp.y = 0;
+ }
+
+ if (outside == outcode1)
+ {
+ fl->a = tmp;
+ DOOUTCODE(outcode1, fl->a.x, fl->a.y);
+ }
+ else
+ {
+ fl->b = tmp;
+ DOOUTCODE(outcode2, fl->b.x, fl->b.y);
+ }
+
+ if (outcode1 & outcode2)
+ return false; // trivially outside
+ }
+
+ return true;
+}
+#undef DOOUTCODE
+
+
+//
+// Classic Bresenham w/ whatever optimizations needed for speed
+//
+void
+AM_drawFline
+( fline_t* fl,
+ int color )
+{
+ register int x;
+ register int y;
+ register int dx;
+ register int dy;
+ register int sx;
+ register int sy;
+ register int ax;
+ register int ay;
+ register int d;
+
+ static int fuck = 0;
+
+ // For debugging only
+ if ( fl->a.x < 0 || fl->a.x >= f_w
+ || fl->a.y < 0 || fl->a.y >= f_h
+ || fl->b.x < 0 || fl->b.x >= f_w
+ || fl->b.y < 0 || fl->b.y >= f_h)
+ {
+ DEH_fprintf(stderr, "fuck %d \r", fuck++);
+ return;
+ }
+
+#define PUTDOT(xx,yy,cc) fb[(yy)*f_w+(xx)]=(cc)
+
+ dx = fl->b.x - fl->a.x;
+ ax = 2 * (dx<0 ? -dx : dx);
+ sx = dx<0 ? -1 : 1;
+
+ dy = fl->b.y - fl->a.y;
+ ay = 2 * (dy<0 ? -dy : dy);
+ sy = dy<0 ? -1 : 1;
+
+ x = fl->a.x;
+ y = fl->a.y;
+
+ if (ax > ay)
+ {
+ d = ay - ax/2;
+ while (1)
+ {
+ PUTDOT(x,y,color);
+ if (x == fl->b.x) return;
+ if (d>=0)
+ {
+ y += sy;
+ d -= ax;
+ }
+ x += sx;
+ d += ay;
+ }
+ }
+ else
+ {
+ d = ax - ay/2;
+ while (1)
+ {
+ PUTDOT(x, y, color);
+ if (y == fl->b.y) return;
+ if (d >= 0)
+ {
+ x += sx;
+ d -= ay;
+ }
+ y += sy;
+ d += ax;
+ }
+ }
+}
+
+
+//
+// Clip lines, draw visible part sof lines.
+//
+void
+AM_drawMline
+( mline_t* ml,
+ int color )
+{
+ static fline_t fl;
+
+ if (AM_clipMline(ml, &fl))
+ AM_drawFline(&fl, color); // draws it on frame buffer using fb coords
+}
+
+
+
+//
+// Draws flat (floor/ceiling tile) aligned grid lines.
+//
+void AM_drawGrid(int color)
+{
+ fixed_t x, y;
+ fixed_t start, end;
+ mline_t ml;
+
+ // Figure out start of vertical gridlines
+ start = m_x;
+ if ((start-bmaporgx)%(MAPBLOCKUNITS<x;
+ l.a.y = lines[i].v1->y;
+ l.b.x = lines[i].v2->x;
+ l.b.y = lines[i].v2->y;
+ if (cheating || (lines[i].flags & ML_MAPPED))
+ {
+ if ((lines[i].flags & LINE_NEVERSEE) && !cheating)
+ continue;
+ if (!lines[i].backsector)
+ {
+ AM_drawMline(&l, WALLCOLORS+lightlev);
+ }
+ else
+ {
+ if (lines[i].special == 39)
+ { // teleporters
+ AM_drawMline(&l, WALLCOLORS+WALLRANGE/2);
+ }
+ else if (lines[i].flags & ML_SECRET) // secret door
+ {
+ if (cheating) AM_drawMline(&l, SECRETWALLCOLORS + lightlev);
+ else AM_drawMline(&l, WALLCOLORS+lightlev);
+ }
+ else if (lines[i].backsector->floorheight
+ != lines[i].frontsector->floorheight) {
+ AM_drawMline(&l, FDWALLCOLORS + lightlev); // floor level change
+ }
+ else if (lines[i].backsector->ceilingheight
+ != lines[i].frontsector->ceilingheight) {
+ AM_drawMline(&l, CDWALLCOLORS+lightlev); // ceiling level change
+ }
+ else if (cheating) {
+ AM_drawMline(&l, TSWALLCOLORS+lightlev);
+ }
+ }
+ }
+ else if (plr->powers[pw_allmap])
+ {
+ if (!(lines[i].flags & LINE_NEVERSEE)) AM_drawMline(&l, GRAYS+3);
+ }
+ }
+}
+
+
+//
+// Rotation in 2D.
+// Used to rotate player arrow line character.
+//
+void
+AM_rotate
+( fixed_t* x,
+ fixed_t* y,
+ angle_t a )
+{
+ fixed_t tmpx;
+
+ tmpx =
+ FixedMul(*x,finecosine[a>>ANGLETOFINESHIFT])
+ - FixedMul(*y,finesine[a>>ANGLETOFINESHIFT]);
+
+ *y =
+ FixedMul(*x,finesine[a>>ANGLETOFINESHIFT])
+ + FixedMul(*y,finecosine[a>>ANGLETOFINESHIFT]);
+
+ *x = tmpx;
+}
+
+void
+AM_drawLineCharacter
+( mline_t* lineguy,
+ int lineguylines,
+ fixed_t scale,
+ angle_t angle,
+ int color,
+ fixed_t x,
+ fixed_t y )
+{
+ int i;
+ mline_t l;
+
+ for (i=0;imo->angle, WHITE, plr->mo->x, plr->mo->y);
+ else
+ AM_drawLineCharacter
+ (player_arrow, arrlen(player_arrow), 0, plr->mo->angle,
+ WHITE, plr->mo->x, plr->mo->y);
+ return;
+ }
+
+ for (i=0;ipowers[pw_invisibility])
+ color = 246; // *close* to black
+ else
+ color = their_colors[their_color];
+
+ AM_drawLineCharacter
+ (player_arrow, arrlen(player_arrow), 0, p->mo->angle,
+ color, p->mo->x, p->mo->y);
+ }
+
+}
+
+void
+AM_drawThings
+( int colors,
+ int colorrange)
+{
+ int i;
+ mobj_t* t;
+
+ for (i=0;iangle, colors+lightlev, t->x, t->y);
+ t = t->snext;
+ }
+ }
+}
+
+void AM_drawMarks(void)
+{
+ int i, fx, fy, w, h;
+
+ for (i=0;iwidth);
+ // h = SHORT(marknums[i]->height);
+ w = 5; // because something's wrong with the wad, i guess
+ h = 6; // because something's wrong with the wad, i guess
+ fx = CXMTOF(markpoints[i].x);
+ fy = CYMTOF(markpoints[i].y);
+ if (fx >= f_x && fx <= f_w - w && fy >= f_y && fy <= f_h - h)
+ V_DrawPatch(fx, fy, marknums[i]);
+ }
+ }
+
+}
+
+void AM_drawCrosshair(int color)
+{
+ fb[(f_w*(f_h+1))/2] = color; // single point for now
+
+}
+
+void AM_Drawer (void)
+{
+ if (!automapactive) return;
+
+ AM_clearFB(BACKGROUND);
+ if (grid)
+ AM_drawGrid(GRIDCOLORS);
+ AM_drawWalls();
+ AM_drawPlayers();
+ if (cheating==2)
+ AM_drawThings(THINGCOLORS, THINGRANGE);
+ AM_drawCrosshair(XHAIRCOLORS);
+
+ AM_drawMarks();
+
+ V_MarkRect(f_x, f_y, f_w, f_h);
+
+}
diff --git a/firmware_p4/components/Applications/doom/am_map.h b/firmware_p4/components/Applications/doom/am_map.h
new file mode 100644
index 000000000..572d2389b
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/am_map.h
@@ -0,0 +1,49 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// AutoMap module.
+//
+
+#ifndef __AMMAP_H__
+#define __AMMAP_H__
+
+#include "d_event.h"
+#include "m_cheat.h"
+
+// Used by ST StatusBar stuff.
+#define AM_MSGHEADER (('a'<<24)+('m'<<16))
+#define AM_MSGENTERED (AM_MSGHEADER | ('e'<<8))
+#define AM_MSGEXITED (AM_MSGHEADER | ('x'<<8))
+
+
+// Called by main loop.
+boolean AM_Responder (event_t* ev);
+
+// Called by main loop.
+void AM_Ticker (void);
+
+// Called by main loop,
+// called instead of view drawer if automap active.
+void AM_Drawer (void);
+
+// Called to force the automap to quit
+// if the level is completed while it is up.
+void AM_Stop (void);
+
+
+extern cheatseq_t cheat_amap;
+
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/config.h b/firmware_p4/components/Applications/doom/config.h
new file mode 100644
index 000000000..7e05102e0
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/config.h
@@ -0,0 +1,100 @@
+/* config.hin. Generated from configure.ac by autoheader. */
+
+/* Define to 1 if you have the header file. */
+#undef HAVE_DEV_ISA_SPKRIO_H
+
+/* Define to 1 if you have the header file. */
+#undef HAVE_DEV_SPEAKER_SPEAKER_H
+
+/* Define to 1 if you have the header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `ioperm' function. */
+#undef HAVE_IOPERM
+
+/* Define to 1 if you have the `amd64' library (-lamd64). */
+#undef HAVE_LIBAMD64
+
+/* Define to 1 if you have the `i386' library (-li386). */
+#undef HAVE_LIBI386
+
+/* Define to 1 if you have the `m' library (-lm). */
+#undef HAVE_LIBM
+
+/* Define to 1 if you have the `png' library (-lpng). */
+#undef HAVE_LIBPNG
+
+/* Define to 1 if you have the `samplerate' library (-lsamplerate). */
+#undef HAVE_LIBSAMPLERATE
+
+/* Define to 1 if you have the `z' library (-lz). */
+#undef HAVE_LIBZ
+
+/* Define to 1 if you have the header file. */
+#undef HAVE_LINUX_KD_H
+
+/* Define to 1 if you have the header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the `mmap' function. */
+#undef HAVE_MMAP
+
+/* Define to 1 if you have the `sched_setaffinity' function. */
+#undef HAVE_SCHED_SETAFFINITY
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the header file. */
+#undef HAVE_UNISTD_H
+
+/* Name of package */
+#define PACKAGE "Doom"
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "Doom Generic"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "Doom Generic 0.1"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "doomgeneric.tar"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION 0.1
+
+/* Change this when you create your awesome forked version */
+#define PROGRAM_PREFIX "doomgeneric"
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Version number of package */
+#define VERSION 0.1
+
+/* Define to 1 if you want to compile the unmodified code */
+#undef ORIGCODE
+
+/* Define to the directory where all game files are located */
+#define FILES_DIR "."
diff --git a/firmware_p4/components/Applications/doom/d_englsh.h b/firmware_p4/components/Applications/doom/d_englsh.h
new file mode 100644
index 000000000..cfb05722c
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_englsh.h
@@ -0,0 +1,693 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Printed strings for translation.
+// English language support (default).
+//
+
+#ifndef __D_ENGLSH__
+#define __D_ENGLSH__
+
+//
+// Printed strings for translation
+//
+
+//
+// D_Main.C
+//
+#define D_DEVSTR "Development mode ON.\n"
+#define D_CDROM "CD-ROM Version: default.cfg from c:\\doomdata\n"
+
+//
+// M_Menu.C
+//
+#define PRESSKEY "press a key."
+#define PRESSYN "press y or n."
+#define QUITMSG "are you sure you want to\nquit this great game?"
+#define LOADNET "you can't do load while in a net game!\n\n"PRESSKEY
+#define QLOADNET "you can't quickload during a netgame!\n\n"PRESSKEY
+#define QSAVESPOT "you haven't picked a quicksave slot yet!\n\n"PRESSKEY
+#define SAVEDEAD "you can't save if you aren't playing!\n\n"PRESSKEY
+#define QSPROMPT "quicksave over your game named\n\n'%s'?\n\n"PRESSYN
+#define QLPROMPT "do you want to quickload the game named\n\n'%s'?\n\n"PRESSYN
+
+#define NEWGAME \
+"you can't start a new game\n"\
+"while in a network game.\n\n"PRESSKEY
+
+#define NIGHTMARE \
+"are you sure? this skill level\n"\
+"isn't even remotely fair.\n\n"PRESSYN
+
+#define SWSTRING \
+"this is the shareware version of doom.\n\n"\
+"you need to order the entire trilogy.\n\n"PRESSKEY
+
+#define MSGOFF "Messages OFF"
+#define MSGON "Messages ON"
+#define NETEND "you can't end a netgame!\n\n"PRESSKEY
+#define ENDGAME "are you sure you want to end the game?\n\n"PRESSYN
+
+#define DOSY "(press y to quit to dos.)"
+
+#define DETAILHI "High detail"
+#define DETAILLO "Low detail"
+#define GAMMALVL0 "Gamma correction OFF"
+#define GAMMALVL1 "Gamma correction level 1"
+#define GAMMALVL2 "Gamma correction level 2"
+#define GAMMALVL3 "Gamma correction level 3"
+#define GAMMALVL4 "Gamma correction level 4"
+#define EMPTYSTRING "empty slot"
+
+//
+// P_inter.C
+//
+#define GOTARMOR "Picked up the armor."
+#define GOTMEGA "Picked up the MegaArmor!"
+#define GOTHTHBONUS "Picked up a health bonus."
+#define GOTARMBONUS "Picked up an armor bonus."
+#define GOTSTIM "Picked up a stimpack."
+#define GOTMEDINEED "Picked up a medikit that you REALLY need!"
+#define GOTMEDIKIT "Picked up a medikit."
+#define GOTSUPER "Supercharge!"
+
+#define GOTBLUECARD "Picked up a blue keycard."
+#define GOTYELWCARD "Picked up a yellow keycard."
+#define GOTREDCARD "Picked up a red keycard."
+#define GOTBLUESKUL "Picked up a blue skull key."
+#define GOTYELWSKUL "Picked up a yellow skull key."
+#define GOTREDSKULL "Picked up a red skull key."
+
+#define GOTINVUL "Invulnerability!"
+#define GOTBERSERK "Berserk!"
+#define GOTINVIS "Partial Invisibility"
+#define GOTSUIT "Radiation Shielding Suit"
+#define GOTMAP "Computer Area Map"
+#define GOTVISOR "Light Amplification Visor"
+#define GOTMSPHERE "MegaSphere!"
+
+#define GOTCLIP "Picked up a clip."
+#define GOTCLIPBOX "Picked up a box of bullets."
+#define GOTROCKET "Picked up a rocket."
+#define GOTROCKBOX "Picked up a box of rockets."
+#define GOTCELL "Picked up an energy cell."
+#define GOTCELLBOX "Picked up an energy cell pack."
+#define GOTSHELLS "Picked up 4 shotgun shells."
+#define GOTSHELLBOX "Picked up a box of shotgun shells."
+#define GOTBACKPACK "Picked up a backpack full of ammo!"
+
+#define GOTBFG9000 "You got the BFG9000! Oh, yes."
+#define GOTCHAINGUN "You got the chaingun!"
+#define GOTCHAINSAW "A chainsaw! Find some meat!"
+#define GOTLAUNCHER "You got the rocket launcher!"
+#define GOTPLASMA "You got the plasma gun!"
+#define GOTSHOTGUN "You got the shotgun!"
+#define GOTSHOTGUN2 "You got the super shotgun!"
+
+//
+// P_Doors.C
+//
+#define PD_BLUEO "You need a blue key to activate this object"
+#define PD_REDO "You need a red key to activate this object"
+#define PD_YELLOWO "You need a yellow key to activate this object"
+#define PD_BLUEK "You need a blue key to open this door"
+#define PD_REDK "You need a red key to open this door"
+#define PD_YELLOWK "You need a yellow key to open this door"
+
+//
+// G_game.C
+//
+#define GGSAVED "game saved."
+
+//
+// HU_stuff.C
+//
+#define HUSTR_MSGU "[Message unsent]"
+
+#define HUSTR_E1M1 "E1M1: Hangar"
+#define HUSTR_E1M2 "E1M2: Nuclear Plant"
+#define HUSTR_E1M3 "E1M3: Toxin Refinery"
+#define HUSTR_E1M4 "E1M4: Command Control"
+#define HUSTR_E1M5 "E1M5: Phobos Lab"
+#define HUSTR_E1M6 "E1M6: Central Processing"
+#define HUSTR_E1M7 "E1M7: Computer Station"
+#define HUSTR_E1M8 "E1M8: Phobos Anomaly"
+#define HUSTR_E1M9 "E1M9: Military Base"
+
+#define HUSTR_E2M1 "E2M1: Deimos Anomaly"
+#define HUSTR_E2M2 "E2M2: Containment Area"
+#define HUSTR_E2M3 "E2M3: Refinery"
+#define HUSTR_E2M4 "E2M4: Deimos Lab"
+#define HUSTR_E2M5 "E2M5: Command Center"
+#define HUSTR_E2M6 "E2M6: Halls of the Damned"
+#define HUSTR_E2M7 "E2M7: Spawning Vats"
+#define HUSTR_E2M8 "E2M8: Tower of Babel"
+#define HUSTR_E2M9 "E2M9: Fortress of Mystery"
+
+#define HUSTR_E3M1 "E3M1: Hell Keep"
+#define HUSTR_E3M2 "E3M2: Slough of Despair"
+#define HUSTR_E3M3 "E3M3: Pandemonium"
+#define HUSTR_E3M4 "E3M4: House of Pain"
+#define HUSTR_E3M5 "E3M5: Unholy Cathedral"
+#define HUSTR_E3M6 "E3M6: Mt. Erebus"
+#define HUSTR_E3M7 "E3M7: Limbo"
+#define HUSTR_E3M8 "E3M8: Dis"
+#define HUSTR_E3M9 "E3M9: Warrens"
+
+#define HUSTR_E4M1 "E4M1: Hell Beneath"
+#define HUSTR_E4M2 "E4M2: Perfect Hatred"
+#define HUSTR_E4M3 "E4M3: Sever The Wicked"
+#define HUSTR_E4M4 "E4M4: Unruly Evil"
+#define HUSTR_E4M5 "E4M5: They Will Repent"
+#define HUSTR_E4M6 "E4M6: Against Thee Wickedly"
+#define HUSTR_E4M7 "E4M7: And Hell Followed"
+#define HUSTR_E4M8 "E4M8: Unto The Cruel"
+#define HUSTR_E4M9 "E4M9: Fear"
+
+#define HUSTR_1 "level 1: entryway"
+#define HUSTR_2 "level 2: underhalls"
+#define HUSTR_3 "level 3: the gantlet"
+#define HUSTR_4 "level 4: the focus"
+#define HUSTR_5 "level 5: the waste tunnels"
+#define HUSTR_6 "level 6: the crusher"
+#define HUSTR_7 "level 7: dead simple"
+#define HUSTR_8 "level 8: tricks and traps"
+#define HUSTR_9 "level 9: the pit"
+#define HUSTR_10 "level 10: refueling base"
+#define HUSTR_11 "level 11: 'o' of destruction!"
+
+#define HUSTR_12 "level 12: the factory"
+#define HUSTR_13 "level 13: downtown"
+#define HUSTR_14 "level 14: the inmost dens"
+#define HUSTR_15 "level 15: industrial zone"
+#define HUSTR_16 "level 16: suburbs"
+#define HUSTR_17 "level 17: tenements"
+#define HUSTR_18 "level 18: the courtyard"
+#define HUSTR_19 "level 19: the citadel"
+#define HUSTR_20 "level 20: gotcha!"
+
+#define HUSTR_21 "level 21: nirvana"
+#define HUSTR_22 "level 22: the catacombs"
+#define HUSTR_23 "level 23: barrels o' fun"
+#define HUSTR_24 "level 24: the chasm"
+#define HUSTR_25 "level 25: bloodfalls"
+#define HUSTR_26 "level 26: the abandoned mines"
+#define HUSTR_27 "level 27: monster condo"
+#define HUSTR_28 "level 28: the spirit world"
+#define HUSTR_29 "level 29: the living end"
+#define HUSTR_30 "level 30: icon of sin"
+
+#define HUSTR_31 "level 31: wolfenstein"
+#define HUSTR_32 "level 32: grosse"
+
+#define PHUSTR_1 "level 1: congo"
+#define PHUSTR_2 "level 2: well of souls"
+#define PHUSTR_3 "level 3: aztec"
+#define PHUSTR_4 "level 4: caged"
+#define PHUSTR_5 "level 5: ghost town"
+#define PHUSTR_6 "level 6: baron's lair"
+#define PHUSTR_7 "level 7: caughtyard"
+#define PHUSTR_8 "level 8: realm"
+#define PHUSTR_9 "level 9: abattoire"
+#define PHUSTR_10 "level 10: onslaught"
+#define PHUSTR_11 "level 11: hunted"
+
+#define PHUSTR_12 "level 12: speed"
+#define PHUSTR_13 "level 13: the crypt"
+#define PHUSTR_14 "level 14: genesis"
+#define PHUSTR_15 "level 15: the twilight"
+#define PHUSTR_16 "level 16: the omen"
+#define PHUSTR_17 "level 17: compound"
+#define PHUSTR_18 "level 18: neurosphere"
+#define PHUSTR_19 "level 19: nme"
+#define PHUSTR_20 "level 20: the death domain"
+
+#define PHUSTR_21 "level 21: slayer"
+#define PHUSTR_22 "level 22: impossible mission"
+#define PHUSTR_23 "level 23: tombstone"
+#define PHUSTR_24 "level 24: the final frontier"
+#define PHUSTR_25 "level 25: the temple of darkness"
+#define PHUSTR_26 "level 26: bunker"
+#define PHUSTR_27 "level 27: anti-christ"
+#define PHUSTR_28 "level 28: the sewers"
+#define PHUSTR_29 "level 29: odyssey of noises"
+#define PHUSTR_30 "level 30: the gateway of hell"
+
+#define PHUSTR_31 "level 31: cyberden"
+#define PHUSTR_32 "level 32: go 2 it"
+
+#define THUSTR_1 "level 1: system control"
+#define THUSTR_2 "level 2: human bbq"
+#define THUSTR_3 "level 3: power control"
+#define THUSTR_4 "level 4: wormhole"
+#define THUSTR_5 "level 5: hanger"
+#define THUSTR_6 "level 6: open season"
+#define THUSTR_7 "level 7: prison"
+#define THUSTR_8 "level 8: metal"
+#define THUSTR_9 "level 9: stronghold"
+#define THUSTR_10 "level 10: redemption"
+#define THUSTR_11 "level 11: storage facility"
+
+#define THUSTR_12 "level 12: crater"
+#define THUSTR_13 "level 13: nukage processing"
+#define THUSTR_14 "level 14: steel works"
+#define THUSTR_15 "level 15: dead zone"
+#define THUSTR_16 "level 16: deepest reaches"
+#define THUSTR_17 "level 17: processing area"
+#define THUSTR_18 "level 18: mill"
+#define THUSTR_19 "level 19: shipping/respawning"
+#define THUSTR_20 "level 20: central processing"
+
+#define THUSTR_21 "level 21: administration center"
+#define THUSTR_22 "level 22: habitat"
+#define THUSTR_23 "level 23: lunar mining project"
+#define THUSTR_24 "level 24: quarry"
+#define THUSTR_25 "level 25: baron's den"
+#define THUSTR_26 "level 26: ballistyx"
+#define THUSTR_27 "level 27: mount pain"
+#define THUSTR_28 "level 28: heck"
+#define THUSTR_29 "level 29: river styx"
+#define THUSTR_30 "level 30: last call"
+
+#define THUSTR_31 "level 31: pharaoh"
+#define THUSTR_32 "level 32: caribbean"
+
+#define HUSTR_CHATMACRO1 "I'm ready to kick butt!"
+#define HUSTR_CHATMACRO2 "I'm OK."
+#define HUSTR_CHATMACRO3 "I'm not looking too good!"
+#define HUSTR_CHATMACRO4 "Help!"
+#define HUSTR_CHATMACRO5 "You suck!"
+#define HUSTR_CHATMACRO6 "Next time, scumbag..."
+#define HUSTR_CHATMACRO7 "Come here!"
+#define HUSTR_CHATMACRO8 "I'll take care of it."
+#define HUSTR_CHATMACRO9 "Yes"
+#define HUSTR_CHATMACRO0 "No"
+
+#define HUSTR_TALKTOSELF1 "You mumble to yourself"
+#define HUSTR_TALKTOSELF2 "Who's there?"
+#define HUSTR_TALKTOSELF3 "You scare yourself"
+#define HUSTR_TALKTOSELF4 "You start to rave"
+#define HUSTR_TALKTOSELF5 "You've lost it..."
+
+#define HUSTR_MESSAGESENT "[Message Sent]"
+
+// The following should NOT be changed unless it seems
+// just AWFULLY necessary
+
+#define HUSTR_PLRGREEN "Green: "
+#define HUSTR_PLRINDIGO "Indigo: "
+#define HUSTR_PLRBROWN "Brown: "
+#define HUSTR_PLRRED "Red: "
+
+#define HUSTR_KEYGREEN 'g'
+#define HUSTR_KEYINDIGO 'i'
+#define HUSTR_KEYBROWN 'b'
+#define HUSTR_KEYRED 'r'
+
+//
+// AM_map.C
+//
+
+#define AMSTR_FOLLOWON "Follow Mode ON"
+#define AMSTR_FOLLOWOFF "Follow Mode OFF"
+
+#define AMSTR_GRIDON "Grid ON"
+#define AMSTR_GRIDOFF "Grid OFF"
+
+#define AMSTR_MARKEDSPOT "Marked Spot"
+#define AMSTR_MARKSCLEARED "All Marks Cleared"
+
+//
+// ST_stuff.C
+//
+
+#define STSTR_MUS "Music Change"
+#define STSTR_NOMUS "IMPOSSIBLE SELECTION"
+#define STSTR_DQDON "Degreelessness Mode On"
+#define STSTR_DQDOFF "Degreelessness Mode Off"
+
+#define STSTR_KFAADDED "Very Happy Ammo Added"
+#define STSTR_FAADDED "Ammo (no keys) Added"
+
+#define STSTR_NCON "No Clipping Mode ON"
+#define STSTR_NCOFF "No Clipping Mode OFF"
+
+#define STSTR_BEHOLD "inVuln, Str, Inviso, Rad, Allmap, or Lite-amp"
+#define STSTR_BEHOLDX "Power-up Toggled"
+
+#define STSTR_CHOPPERS "... doesn't suck - GM"
+#define STSTR_CLEV "Changing Level..."
+
+//
+// F_Finale.C
+//
+#define E1TEXT \
+"Once you beat the big badasses and\n"\
+"clean out the moon base you're supposed\n"\
+"to win, aren't you? Aren't you? Where's\n"\
+"your fat reward and ticket home? What\n"\
+"the hell is this? It's not supposed to\n"\
+"end this way!\n"\
+"\n" \
+"It stinks like rotten meat, but looks\n"\
+"like the lost Deimos base. Looks like\n"\
+"you're stuck on The Shores of Hell.\n"\
+"The only way out is through.\n"\
+"\n"\
+"To continue the DOOM experience, play\n"\
+"The Shores of Hell and its amazing\n"\
+"sequel, Inferno!\n"
+
+
+#define E2TEXT \
+"You've done it! The hideous cyber-\n"\
+"demon lord that ruled the lost Deimos\n"\
+"moon base has been slain and you\n"\
+"are triumphant! But ... where are\n"\
+"you? You clamber to the edge of the\n"\
+"moon and look down to see the awful\n"\
+"truth.\n" \
+"\n"\
+"Deimos floats above Hell itself!\n"\
+"You've never heard of anyone escaping\n"\
+"from Hell, but you'll make the bastards\n"\
+"sorry they ever heard of you! Quickly,\n"\
+"you rappel down to the surface of\n"\
+"Hell.\n"\
+"\n" \
+"Now, it's on to the final chapter of\n"\
+"DOOM! -- Inferno."
+
+
+#define E3TEXT \
+"The loathsome spiderdemon that\n"\
+"masterminded the invasion of the moon\n"\
+"bases and caused so much death has had\n"\
+"its ass kicked for all time.\n"\
+"\n"\
+"A hidden doorway opens and you enter.\n"\
+"You've proven too tough for Hell to\n"\
+"contain, and now Hell at last plays\n"\
+"fair -- for you emerge from the door\n"\
+"to see the green fields of Earth!\n"\
+"Home at last.\n" \
+"\n"\
+"You wonder what's been happening on\n"\
+"Earth while you were battling evil\n"\
+"unleashed. It's good that no Hell-\n"\
+"spawn could have come through that\n"\
+"door with you ..."
+
+
+#define E4TEXT \
+"the spider mastermind must have sent forth\n"\
+"its legions of hellspawn before your\n"\
+"final confrontation with that terrible\n"\
+"beast from hell. but you stepped forward\n"\
+"and brought forth eternal damnation and\n"\
+"suffering upon the horde as a true hero\n"\
+"would in the face of something so evil.\n"\
+"\n"\
+"besides, someone was gonna pay for what\n"\
+"happened to daisy, your pet rabbit.\n"\
+"\n"\
+"but now, you see spread before you more\n"\
+"potential pain and gibbitude as a nation\n"\
+"of demons run amok among our cities.\n"\
+"\n"\
+"next stop, hell on earth!"
+
+
+// after level 6, put this:
+
+#define C1TEXT \
+"YOU HAVE ENTERED DEEPLY INTO THE INFESTED\n" \
+"STARPORT. BUT SOMETHING IS WRONG. THE\n" \
+"MONSTERS HAVE BROUGHT THEIR OWN REALITY\n" \
+"WITH THEM, AND THE STARPORT'S TECHNOLOGY\n" \
+"IS BEING SUBVERTED BY THEIR PRESENCE.\n" \
+"\n"\
+"AHEAD, YOU SEE AN OUTPOST OF HELL, A\n" \
+"FORTIFIED ZONE. IF YOU CAN GET PAST IT,\n" \
+"YOU CAN PENETRATE INTO THE HAUNTED HEART\n" \
+"OF THE STARBASE AND FIND THE CONTROLLING\n" \
+"SWITCH WHICH HOLDS EARTH'S POPULATION\n" \
+"HOSTAGE."
+
+// After level 11, put this:
+
+#define C2TEXT \
+"YOU HAVE WON! YOUR VICTORY HAS ENABLED\n" \
+"HUMANKIND TO EVACUATE EARTH AND ESCAPE\n"\
+"THE NIGHTMARE. NOW YOU ARE THE ONLY\n"\
+"HUMAN LEFT ON THE FACE OF THE PLANET.\n"\
+"CANNIBAL MUTATIONS, CARNIVOROUS ALIENS,\n"\
+"AND EVIL SPIRITS ARE YOUR ONLY NEIGHBORS.\n"\
+"YOU SIT BACK AND WAIT FOR DEATH, CONTENT\n"\
+"THAT YOU HAVE SAVED YOUR SPECIES.\n"\
+"\n"\
+"BUT THEN, EARTH CONTROL BEAMS DOWN A\n"\
+"MESSAGE FROM SPACE: \"SENSORS HAVE LOCATED\n"\
+"THE SOURCE OF THE ALIEN INVASION. IF YOU\n"\
+"GO THERE, YOU MAY BE ABLE TO BLOCK THEIR\n"\
+"ENTRY. THE ALIEN BASE IS IN THE HEART OF\n"\
+"YOUR OWN HOME CITY, NOT FAR FROM THE\n"\
+"STARPORT.\" SLOWLY AND PAINFULLY YOU GET\n"\
+"UP AND RETURN TO THE FRAY."
+
+
+// After level 20, put this:
+
+#define C3TEXT \
+"YOU ARE AT THE CORRUPT HEART OF THE CITY,\n"\
+"SURROUNDED BY THE CORPSES OF YOUR ENEMIES.\n"\
+"YOU SEE NO WAY TO DESTROY THE CREATURES'\n"\
+"ENTRYWAY ON THIS SIDE, SO YOU CLENCH YOUR\n"\
+"TEETH AND PLUNGE THROUGH IT.\n"\
+"\n"\
+"THERE MUST BE A WAY TO CLOSE IT ON THE\n"\
+"OTHER SIDE. WHAT DO YOU CARE IF YOU'VE\n"\
+"GOT TO GO THROUGH HELL TO GET TO IT?"
+
+
+// After level 29, put this:
+
+#define C4TEXT \
+"THE HORRENDOUS VISAGE OF THE BIGGEST\n"\
+"DEMON YOU'VE EVER SEEN CRUMBLES BEFORE\n"\
+"YOU, AFTER YOU PUMP YOUR ROCKETS INTO\n"\
+"HIS EXPOSED BRAIN. THE MONSTER SHRIVELS\n"\
+"UP AND DIES, ITS THRASHING LIMBS\n"\
+"DEVASTATING UNTOLD MILES OF HELL'S\n"\
+"SURFACE.\n"\
+"\n"\
+"YOU'VE DONE IT. THE INVASION IS OVER.\n"\
+"EARTH IS SAVED. HELL IS A WRECK. YOU\n"\
+"WONDER WHERE BAD FOLKS WILL GO WHEN THEY\n"\
+"DIE, NOW. WIPING THE SWEAT FROM YOUR\n"\
+"FOREHEAD YOU BEGIN THE LONG TREK BACK\n"\
+"HOME. REBUILDING EARTH OUGHT TO BE A\n"\
+"LOT MORE FUN THAN RUINING IT WAS.\n"
+
+
+
+// Before level 31, put this:
+
+#define C5TEXT \
+"CONGRATULATIONS, YOU'VE FOUND THE SECRET\n"\
+"LEVEL! LOOKS LIKE IT'S BEEN BUILT BY\n"\
+"HUMANS, RATHER THAN DEMONS. YOU WONDER\n"\
+"WHO THE INMATES OF THIS CORNER OF HELL\n"\
+"WILL BE."
+
+
+// Before level 32, put this:
+
+#define C6TEXT \
+"CONGRATULATIONS, YOU'VE FOUND THE\n"\
+"SUPER SECRET LEVEL! YOU'D BETTER\n"\
+"BLAZE THROUGH THIS ONE!\n"
+
+
+// after map 06
+
+#define P1TEXT \
+"You gloat over the steaming carcass of the\n"\
+"Guardian. With its death, you've wrested\n"\
+"the Accelerator from the stinking claws\n"\
+"of Hell. You relax and glance around the\n"\
+"room. Damn! There was supposed to be at\n"\
+"least one working prototype, but you can't\n"\
+"see it. The demons must have taken it.\n"\
+"\n"\
+"You must find the prototype, or all your\n"\
+"struggles will have been wasted. Keep\n"\
+"moving, keep fighting, keep killing.\n"\
+"Oh yes, keep living, too."
+
+
+// after map 11
+
+#define P2TEXT \
+"Even the deadly Arch-Vile labyrinth could\n"\
+"not stop you, and you've gotten to the\n"\
+"prototype Accelerator which is soon\n"\
+"efficiently and permanently deactivated.\n"\
+"\n"\
+"You're good at that kind of thing."
+
+
+// after map 20
+
+#define P3TEXT \
+"You've bashed and battered your way into\n"\
+"the heart of the devil-hive. Time for a\n"\
+"Search-and-Destroy mission, aimed at the\n"\
+"Gatekeeper, whose foul offspring is\n"\
+"cascading to Earth. Yeah, he's bad. But\n"\
+"you know who's worse!\n"\
+"\n"\
+"Grinning evilly, you check your gear, and\n"\
+"get ready to give the bastard a little Hell\n"\
+"of your own making!"
+
+// after map 30
+
+#define P4TEXT \
+"The Gatekeeper's evil face is splattered\n"\
+"all over the place. As its tattered corpse\n"\
+"collapses, an inverted Gate forms and\n"\
+"sucks down the shards of the last\n"\
+"prototype Accelerator, not to mention the\n"\
+"few remaining demons. You're done. Hell\n"\
+"has gone back to pounding bad dead folks \n"\
+"instead of good live ones. Remember to\n"\
+"tell your grandkids to put a rocket\n"\
+"launcher in your coffin. If you go to Hell\n"\
+"when you die, you'll need it for some\n"\
+"final cleaning-up ..."
+
+// before map 31
+
+#define P5TEXT \
+"You've found the second-hardest level we\n"\
+"got. Hope you have a saved game a level or\n"\
+"two previous. If not, be prepared to die\n"\
+"aplenty. For master marines only."
+
+// before map 32
+
+#define P6TEXT \
+"Betcha wondered just what WAS the hardest\n"\
+"level we had ready for ya? Now you know.\n"\
+"No one gets out alive."
+
+
+#define T1TEXT \
+"You've fought your way out of the infested\n"\
+"experimental labs. It seems that UAC has\n"\
+"once again gulped it down. With their\n"\
+"high turnover, it must be hard for poor\n"\
+"old UAC to buy corporate health insurance\n"\
+"nowadays..\n"\
+"\n"\
+"Ahead lies the military complex, now\n"\
+"swarming with diseased horrors hot to get\n"\
+"their teeth into you. With luck, the\n"\
+"complex still has some warlike ordnance\n"\
+"laying around."
+
+
+#define T2TEXT \
+"You hear the grinding of heavy machinery\n"\
+"ahead. You sure hope they're not stamping\n"\
+"out new hellspawn, but you're ready to\n"\
+"ream out a whole herd if you have to.\n"\
+"They might be planning a blood feast, but\n"\
+"you feel about as mean as two thousand\n"\
+"maniacs packed into one mad killer.\n"\
+"\n"\
+"You don't plan to go down easy."
+
+
+#define T3TEXT \
+"The vista opening ahead looks real damn\n"\
+"familiar. Smells familiar, too -- like\n"\
+"fried excrement. You didn't like this\n"\
+"place before, and you sure as hell ain't\n"\
+"planning to like it now. The more you\n"\
+"brood on it, the madder you get.\n"\
+"Hefting your gun, an evil grin trickles\n"\
+"onto your face. Time to take some names."
+
+#define T4TEXT \
+"Suddenly, all is silent, from one horizon\n"\
+"to the other. The agonizing echo of Hell\n"\
+"fades away, the nightmare sky turns to\n"\
+"blue, the heaps of monster corpses start \n"\
+"to evaporate along with the evil stench \n"\
+"that filled the air. Jeeze, maybe you've\n"\
+"done it. Have you really won?\n"\
+"\n"\
+"Something rumbles in the distance.\n"\
+"A blue light begins to glow inside the\n"\
+"ruined skull of the demon-spitter."
+
+
+#define T5TEXT \
+"What now? Looks totally different. Kind\n"\
+"of like King Tut's condo. Well,\n"\
+"whatever's here can't be any worse\n"\
+"than usual. Can it? Or maybe it's best\n"\
+"to let sleeping gods lie.."
+
+
+#define T6TEXT \
+"Time for a vacation. You've burst the\n"\
+"bowels of hell and by golly you're ready\n"\
+"for a break. You mutter to yourself,\n"\
+"Maybe someone else can kick Hell's ass\n"\
+"next time around. Ahead lies a quiet town,\n"\
+"with peaceful flowing water, quaint\n"\
+"buildings, and presumably no Hellspawn.\n"\
+"\n"\
+"As you step off the transport, you hear\n"\
+"the stomp of a cyberdemon's iron shoe."
+
+
+
+//
+// Character cast strings F_FINALE.C
+//
+#define CC_ZOMBIE "ZOMBIEMAN"
+#define CC_SHOTGUN "SHOTGUN GUY"
+#define CC_HEAVY "HEAVY WEAPON DUDE"
+#define CC_IMP "IMP"
+#define CC_DEMON "DEMON"
+#define CC_LOST "LOST SOUL"
+#define CC_CACO "CACODEMON"
+#define CC_HELL "HELL KNIGHT"
+#define CC_BARON "BARON OF HELL"
+#define CC_ARACH "ARACHNOTRON"
+#define CC_PAIN "PAIN ELEMENTAL"
+#define CC_REVEN "REVENANT"
+#define CC_MANCU "MANCUBUS"
+#define CC_ARCH "ARCH-VILE"
+#define CC_SPIDER "THE SPIDER MASTERMIND"
+#define CC_CYBER "THE CYBERDEMON"
+#define CC_HERO "OUR HERO"
+
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/d_event.c b/firmware_p4/components/Applications/doom/d_event.c
new file mode 100644
index 000000000..3eef57282
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_event.c
@@ -0,0 +1,63 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+//
+// DESCRIPTION: Event handling.
+//
+// Events are asynchronous inputs generally generated by the game user.
+// Events can be discarded if no responder claims them
+//
+
+#include
+#include "d_event.h"
+
+#define MAXEVENTS 64
+
+static event_t events[MAXEVENTS];
+static int eventhead;
+static int eventtail;
+
+//
+// D_PostEvent
+// Called by the I/O functions when input is detected
+//
+void D_PostEvent (event_t* ev)
+{
+ events[eventhead] = *ev;
+ eventhead = (eventhead + 1) % MAXEVENTS;
+}
+
+// Read an event from the queue.
+
+event_t *D_PopEvent(void)
+{
+ event_t *result;
+
+ // No more events waiting.
+
+ if (eventtail == eventhead)
+ {
+ return NULL;
+ }
+
+ result = &events[eventtail];
+
+ // Advance to the next event in the queue.
+
+ eventtail = (eventtail + 1) % MAXEVENTS;
+
+ return result;
+}
+
+
diff --git a/firmware_p4/components/Applications/doom/d_event.h b/firmware_p4/components/Applications/doom/d_event.h
new file mode 100644
index 000000000..c9374b296
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_event.h
@@ -0,0 +1,137 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+//
+//
+
+
+#ifndef __D_EVENT__
+#define __D_EVENT__
+
+
+#include "doomtype.h"
+
+
+//
+// Event handling.
+//
+
+// Input event types.
+typedef enum
+{
+ ev_keydown,
+ ev_keyup,
+ ev_mouse,
+ ev_joystick,
+ ev_quit
+} evtype_t;
+
+// Event structure.
+typedef struct
+{
+ evtype_t type;
+
+ // Event-related data that depends on the type of event:
+ //
+ // ev_keydown/ev_keyup:
+ // data1: Key code (from doomkeys.h) of the key that was
+ // pressed or released.
+ // data2: Ascii text of the character that was pressed,
+ // shifted appropriately (eg. '$' if 4 was pressed
+ // while shift was held).
+ //
+ // ev_mouse:
+ // data1: Bitfield of buttons currently held down.
+ // (bit 0 = left; bit 1 = right; bit 2 = middle).
+ // data2: X axis mouse movement (turn).
+ // data3: Y axis mouse movement (forward/backward).
+ //
+ // ev_joystick:
+ // data1: Bitfield of buttons currently pressed.
+ // data2: X axis mouse movement (turn).
+ // data3: Y axis mouse movement (forward/backward).
+ // data4: Third axis mouse movement (strafe).
+
+ int data1, data2, data3, data4;
+} event_t;
+
+
+//
+// Button/action code definitions.
+//
+typedef enum
+{
+ // Press "Fire".
+ BT_ATTACK = 1,
+ // Use button, to open doors, activate switches.
+ BT_USE = 2,
+
+ // Flag: game events, not really buttons.
+ BT_SPECIAL = 128,
+ BT_SPECIALMASK = 3,
+
+ // Flag, weapon change pending.
+ // If true, the next 3 bits hold weapon num.
+ BT_CHANGE = 4,
+ // The 3bit weapon mask and shift, convenience.
+ BT_WEAPONMASK = (8+16+32),
+ BT_WEAPONSHIFT = 3,
+
+ // Pause the game.
+ BTS_PAUSE = 1,
+ // Save the game at each console.
+ BTS_SAVEGAME = 2,
+
+ // Savegame slot numbers
+ // occupy the second byte of buttons.
+ BTS_SAVEMASK = (4+8+16),
+ BTS_SAVESHIFT = 2,
+
+} buttoncode_t;
+
+// villsa [STRIFE] Strife specific buttons
+// TODO - not finished
+typedef enum
+{
+ // Player view look up
+ BT2_LOOKUP = 1,
+ // Player view look down
+ BT2_LOOKDOWN = 2,
+ // Center player's view
+ BT2_CENTERVIEW = 4,
+ // Use inventory item
+ BT2_INVUSE = 8,
+ // Drop inventory item
+ BT2_INVDROP = 16,
+ // Jump up and down
+ BT2_JUMP = 32,
+ // Use medkit
+ BT2_HEALTH = 128,
+
+} buttoncode2_t;
+
+
+
+
+// Called by IO functions when input is detected.
+void D_PostEvent (event_t *ev);
+
+// Read an event from the event queue
+
+event_t *D_PopEvent(void);
+
+
+#endif
+
diff --git a/firmware_p4/components/Applications/doom/d_items.c b/firmware_p4/components/Applications/doom/d_items.c
new file mode 100644
index 000000000..33f310c63
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_items.c
@@ -0,0 +1,128 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+//
+
+
+// We are referring to sprite numbers.
+#include "info.h"
+
+#include "d_items.h"
+
+
+//
+// PSPRITE ACTIONS for waepons.
+// This struct controls the weapon animations.
+//
+// Each entry is:
+// ammo/amunition type
+// upstate
+// downstate
+// readystate
+// atkstate, i.e. attack/fire/hit frame
+// flashstate, muzzle flash
+//
+weaponinfo_t weaponinfo[NUMWEAPONS] =
+{
+ {
+ // fist
+ am_noammo,
+ S_PUNCHUP,
+ S_PUNCHDOWN,
+ S_PUNCH,
+ S_PUNCH1,
+ S_NULL
+ },
+ {
+ // pistol
+ am_clip,
+ S_PISTOLUP,
+ S_PISTOLDOWN,
+ S_PISTOL,
+ S_PISTOL1,
+ S_PISTOLFLASH
+ },
+ {
+ // shotgun
+ am_shell,
+ S_SGUNUP,
+ S_SGUNDOWN,
+ S_SGUN,
+ S_SGUN1,
+ S_SGUNFLASH1
+ },
+ {
+ // chaingun
+ am_clip,
+ S_CHAINUP,
+ S_CHAINDOWN,
+ S_CHAIN,
+ S_CHAIN1,
+ S_CHAINFLASH1
+ },
+ {
+ // missile launcher
+ am_misl,
+ S_MISSILEUP,
+ S_MISSILEDOWN,
+ S_MISSILE,
+ S_MISSILE1,
+ S_MISSILEFLASH1
+ },
+ {
+ // plasma rifle
+ am_cell,
+ S_PLASMAUP,
+ S_PLASMADOWN,
+ S_PLASMA,
+ S_PLASMA1,
+ S_PLASMAFLASH1
+ },
+ {
+ // bfg 9000
+ am_cell,
+ S_BFGUP,
+ S_BFGDOWN,
+ S_BFG,
+ S_BFG1,
+ S_BFGFLASH1
+ },
+ {
+ // chainsaw
+ am_noammo,
+ S_SAWUP,
+ S_SAWDOWN,
+ S_SAW,
+ S_SAW1,
+ S_NULL
+ },
+ {
+ // super shotgun
+ am_shell,
+ S_DSGUNUP,
+ S_DSGUNDOWN,
+ S_DSGUN,
+ S_DSGUN1,
+ S_DSGUNFLASH1
+ },
+};
+
+
+
+
+
+
+
+
diff --git a/firmware_p4/components/Applications/doom/d_items.h b/firmware_p4/components/Applications/doom/d_items.h
new file mode 100644
index 000000000..3d22a0630
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_items.h
@@ -0,0 +1,41 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Items: key cards, artifacts, weapon, ammunition.
+//
+
+
+#ifndef __D_ITEMS__
+#define __D_ITEMS__
+
+#include "doomdef.h"
+
+
+
+// Weapon info: sprite frames, ammunition use.
+typedef struct
+{
+ ammotype_t ammo;
+ int upstate;
+ int downstate;
+ int readystate;
+ int atkstate;
+ int flashstate;
+
+} weaponinfo_t;
+
+extern weaponinfo_t weaponinfo[NUMWEAPONS];
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/d_iwad.c b/firmware_p4/components/Applications/doom/d_iwad.c
new file mode 100644
index 000000000..508cc6188
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_iwad.c
@@ -0,0 +1,848 @@
+//
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Search for and locate an IWAD file, and initialize according
+// to the IWAD type.
+//
+
+#include
+#include
+#include
+#include
+
+#include "config.h"
+#include "deh_str.h"
+#include "doomkeys.h"
+#include "d_iwad.h"
+#include "i_system.h"
+#include "m_argv.h"
+#include "m_config.h"
+#include "m_misc.h"
+#include "w_wad.h"
+#include "z_zone.h"
+
+static const iwad_t iwads[] =
+{
+ { "doom2.wad", doom2, commercial, "Doom II" },
+ { "plutonia.wad", pack_plut, commercial, "Final Doom: Plutonia Experiment" },
+ { "tnt.wad", pack_tnt, commercial, "Final Doom: TNT: Evilution" },
+ { "doom.wad", doom, retail, "Doom" },
+ { "doom1.wad", doom, shareware, "Doom Shareware" },
+ { "chex.wad", pack_chex, shareware, "Chex Quest" },
+ { "hacx.wad", pack_hacx, commercial, "Hacx" },
+ { "freedm.wad", doom2, commercial, "FreeDM" },
+ { "freedoom2.wad", doom2, commercial, "Freedoom: Phase 2" },
+ { "freedoom1.wad", doom, retail, "Freedoom: Phase 1" },
+ { "heretic.wad", heretic, retail, "Heretic" },
+ { "heretic1.wad", heretic, shareware, "Heretic Shareware" },
+ { "hexen.wad", hexen, commercial, "Hexen" },
+ //{ "strife0.wad", strife, commercial, "Strife" }, // haleyjd: STRIFE-FIXME
+ { "strife1.wad", strife, commercial, "Strife" },
+};
+
+// Array of locations to search for IWAD files
+//
+// "128 IWAD search directories should be enough for anybody".
+
+#define MAX_IWAD_DIRS 128
+
+static boolean iwad_dirs_built = false;
+static char *iwad_dirs[MAX_IWAD_DIRS];
+static int num_iwad_dirs = 0;
+
+static void AddIWADDir(char *dir)
+{
+ if (num_iwad_dirs < MAX_IWAD_DIRS)
+ {
+ iwad_dirs[num_iwad_dirs] = dir;
+ ++num_iwad_dirs;
+ }
+}
+
+// This is Windows-specific code that automatically finds the location
+// of installed IWAD files. The registry is inspected to find special
+// keys installed by the Windows installers for various CD versions
+// of Doom. From these keys we can deduce where to find an IWAD.
+
+#if defined(_WIN32) && !defined(_WIN32_WCE)
+
+#define WIN32_LEAN_AND_MEAN
+#include
+
+typedef struct
+{
+ HKEY root;
+ char *path;
+ char *value;
+} registry_value_t;
+
+#define UNINSTALLER_STRING "\\uninstl.exe /S "
+
+// Keys installed by the various CD editions. These are actually the
+// commands to invoke the uninstaller and look like this:
+//
+// C:\Program Files\Path\uninstl.exe /S C:\Program Files\Path
+//
+// With some munging we can find where Doom was installed.
+
+// [AlexMax] From the persepctive of a 64-bit executable, 32-bit registry
+// keys are located in a different spot.
+#if _WIN64
+#define SOFTWARE_KEY "Software\\Wow6432Node"
+#else
+#define SOFTWARE_KEY "Software"
+#endif
+
+static registry_value_t uninstall_values[] =
+{
+ // Ultimate Doom, CD version (Depths of Doom trilogy)
+
+ {
+ HKEY_LOCAL_MACHINE,
+ SOFTWARE_KEY "\\Microsoft\\Windows\\CurrentVersion\\"
+ "Uninstall\\Ultimate Doom for Windows 95",
+ "UninstallString",
+ },
+
+ // Doom II, CD version (Depths of Doom trilogy)
+
+ {
+ HKEY_LOCAL_MACHINE,
+ SOFTWARE_KEY "\\Microsoft\\Windows\\CurrentVersion\\"
+ "Uninstall\\Doom II for Windows 95",
+ "UninstallString",
+ },
+
+ // Final Doom
+
+ {
+ HKEY_LOCAL_MACHINE,
+ SOFTWARE_KEY "\\Microsoft\\Windows\\CurrentVersion\\"
+ "Uninstall\\Final Doom for Windows 95",
+ "UninstallString",
+ },
+
+ // Shareware version
+
+ {
+ HKEY_LOCAL_MACHINE,
+ SOFTWARE_KEY "\\Microsoft\\Windows\\CurrentVersion\\"
+ "Uninstall\\Doom Shareware for Windows 95",
+ "UninstallString",
+ },
+};
+
+// Value installed by the Collector's Edition when it is installed
+
+static registry_value_t collectors_edition_value =
+{
+ HKEY_LOCAL_MACHINE,
+ SOFTWARE_KEY "\\Activision\\DOOM Collector's Edition\\v1.0",
+ "INSTALLPATH",
+};
+
+// Subdirectories of the above install path, where IWADs are installed.
+
+static char *collectors_edition_subdirs[] =
+{
+ "Doom2",
+ "Final Doom",
+ "Ultimate Doom",
+};
+
+// Location where Steam is installed
+
+static registry_value_t steam_install_location =
+{
+ HKEY_LOCAL_MACHINE,
+ SOFTWARE_KEY "\\Valve\\Steam",
+ "InstallPath",
+};
+
+// Subdirs of the steam install directory where IWADs are found
+
+static char *steam_install_subdirs[] =
+{
+ "steamapps\\common\\doom 2\\base",
+ "steamapps\\common\\final doom\\base",
+ "steamapps\\common\\ultimate doom\\base",
+ "steamapps\\common\\heretic shadow of the serpent riders\\base",
+ "steamapps\\common\\hexen\\base",
+ "steamapps\\common\\hexen deathkings of the dark citadel\\base",
+
+ // From Doom 3: BFG Edition:
+
+ "steamapps\\common\\DOOM 3 BFG Edition\\base\\wads",
+};
+
+#define STEAM_BFG_GUS_PATCHES \
+ "steamapps\\common\\DOOM 3 BFG Edition\\base\\classicmusic\\instruments"
+
+static char *GetRegistryString(registry_value_t *reg_val)
+{
+ HKEY key;
+ DWORD len;
+ DWORD valtype;
+ char *result;
+
+ // Open the key (directory where the value is stored)
+
+ if (RegOpenKeyEx(reg_val->root, reg_val->path,
+ 0, KEY_READ, &key) != ERROR_SUCCESS)
+ {
+ return NULL;
+ }
+
+ result = NULL;
+
+ // Find the type and length of the string, and only accept strings.
+
+ if (RegQueryValueEx(key, reg_val->value,
+ NULL, &valtype, NULL, &len) == ERROR_SUCCESS
+ && valtype == REG_SZ)
+ {
+ // Allocate a buffer for the value and read the value
+
+ result = malloc(len);
+
+ if (RegQueryValueEx(key, reg_val->value, NULL, &valtype,
+ (unsigned char *) result, &len) != ERROR_SUCCESS)
+ {
+ free(result);
+ result = NULL;
+ }
+ }
+
+ // Close the key
+
+ RegCloseKey(key);
+
+ return result;
+}
+
+// Check for the uninstall strings from the CD versions
+
+static void CheckUninstallStrings(void)
+{
+ unsigned int i;
+
+ for (i=0; i 0)
+ {
+ return;
+ }
+
+ install_path = GetRegistryString(&steam_install_location);
+
+ if (install_path == NULL)
+ {
+ return;
+ }
+
+ len = strlen(install_path) + strlen(STEAM_BFG_GUS_PATCHES) + 20;
+ patch_path = malloc(len);
+ M_snprintf(patch_path, len, "%s\\%s\\ACBASS.PAT",
+ install_path, STEAM_BFG_GUS_PATCHES);
+
+ // Does acbass.pat exist? If so, then set gus_patch_path.
+ if (M_FileExists(patch_path))
+ {
+ M_snprintf(patch_path, len, "%s\\%s",
+ install_path, STEAM_BFG_GUS_PATCHES);
+ M_SetVariable("gus_patch_path", patch_path);
+ }
+
+ free(patch_path);
+ free(install_path);
+}
+
+// Default install directories for DOS Doom
+
+static void CheckDOSDefaults(void)
+{
+ // These are the default install directories used by the deice
+ // installer program:
+
+ AddIWADDir("\\doom2"); // Doom II
+ AddIWADDir("\\plutonia"); // Final Doom
+ AddIWADDir("\\tnt");
+ AddIWADDir("\\doom_se"); // Ultimate Doom
+ AddIWADDir("\\doom"); // Shareware / Registered Doom
+ AddIWADDir("\\dooms"); // Shareware versions
+ AddIWADDir("\\doomsw");
+
+ AddIWADDir("\\heretic"); // Heretic
+ AddIWADDir("\\hrtic_se"); // Heretic Shareware from Quake disc
+
+ AddIWADDir("\\hexen"); // Hexen
+ AddIWADDir("\\hexendk"); // Hexen Deathkings of the Dark Citadel
+
+ AddIWADDir("\\strife"); // Strife
+}
+
+#endif
+
+// Returns true if the specified path is a path to a file
+// of the specified name.
+
+static boolean DirIsFile(char *path, char *filename)
+{
+ size_t path_len;
+ size_t filename_len;
+
+ path_len = strlen(path);
+ filename_len = strlen(filename);
+
+ return path_len >= filename_len + 1
+ && path[path_len - filename_len - 1] == DIR_SEPARATOR
+ && !strcasecmp(&path[path_len - filename_len], filename);
+}
+
+// Check if the specified directory contains the specified IWAD
+// file, returning the full path to the IWAD if found, or NULL
+// if not found.
+
+static char *CheckDirectoryHasIWAD(char *dir, char *iwadname)
+{
+ char *filename;
+
+ // As a special case, the "directory" may refer directly to an
+ // IWAD file if the path comes from DOOMWADDIR or DOOMWADPATH.
+
+ if (DirIsFile(dir, iwadname) && M_FileExists(dir))
+ {
+ return strdup(dir);
+ }
+
+ // Construct the full path to the IWAD if it is located in
+ // this directory, and check if it exists.
+
+ if (!strcmp(dir, "."))
+ {
+ filename = strdup(iwadname);
+ }
+ else
+ {
+ filename = M_StringJoin(dir, DIR_SEPARATOR_S, iwadname, NULL);
+ }
+
+ printf("Trying IWAD file:%s\n", filename);
+
+ if (M_FileExists(filename))
+ {
+ return filename;
+ }
+
+ free(filename);
+
+ return NULL;
+}
+
+// Search a directory to try to find an IWAD
+// Returns the location of the IWAD if found, otherwise NULL.
+
+static char *SearchDirectoryForIWAD(char *dir, int mask, GameMission_t *mission)
+{
+ char *filename;
+ size_t i;
+
+ for (i=0; i
+ //
+
+ iwadparm = M_CheckParmWithArgs("-iwad", 1);
+
+ if (iwadparm)
+ {
+ // Search through IWAD dirs for an IWAD with the given name.
+
+ iwadfile = myargv[iwadparm + 1];
+
+ result = D_FindWADByName(iwadfile);
+
+ if (result == NULL)
+ {
+ I_Error("IWAD file '%s' not found!", iwadfile);
+ }
+
+ *mission = IdentifyIWADByName(result, mask);
+ }
+ else
+ {
+ // Search through the list and look for an IWAD
+
+ printf("-iwad not specified, trying a few iwad names\n");
+
+ result = NULL;
+
+ BuildIWADDirList();
+
+ for (i=0; result == NULL && i
+#include
+
+#include "doomfeatures.h"
+
+#include "d_event.h"
+#include "d_loop.h"
+#include "d_ticcmd.h"
+
+#include "i_system.h"
+#include "i_timer.h"
+#include "i_video.h"
+
+#include "m_argv.h"
+#include "m_fixed.h"
+
+#include "net_client.h"
+#include "net_gui.h"
+#include "net_io.h"
+#include "net_query.h"
+#include "net_server.h"
+#include "net_sdl.h"
+#include "net_loop.h"
+
+// The complete set of data for a particular tic.
+
+typedef struct
+{
+ ticcmd_t cmds[NET_MAXPLAYERS];
+ boolean ingame[NET_MAXPLAYERS];
+} ticcmd_set_t;
+
+//
+// gametic is the tic about to (or currently being) run
+// maketic is the tic that hasn't had control made for it yet
+// recvtic is the latest tic received from the server.
+//
+// a gametic cannot be run until ticcmds are received for it
+// from all players.
+//
+
+static ticcmd_set_t ticdata[BACKUPTICS];
+
+// The index of the next tic to be made (with a call to BuildTiccmd).
+
+static int maketic;
+
+// The number of complete tics received from the server so far.
+
+static int recvtic;
+
+// The number of tics that have been run (using RunTic) so far.
+
+int gametic;
+
+// When set to true, a single tic is run each time TryRunTics() is called.
+// This is used for -timedemo mode.
+
+boolean singletics = false;
+
+// Index of the local player.
+
+static int localplayer;
+
+// Used for original sync code.
+
+static int skiptics = 0;
+
+// Reduce the bandwidth needed by sampling game input less and transmitting
+// less. If ticdup is 2, sample half normal, 3 = one third normal, etc.
+
+int ticdup;
+
+// Amount to offset the timer for game sync.
+
+fixed_t offsetms;
+
+// Use new client syncronisation code
+
+static boolean new_sync = true;
+
+// Callback functions for loop code.
+
+static loop_interface_t *loop_interface = NULL;
+
+// Current players in the multiplayer game.
+// This is distinct from playeringame[] used by the game code, which may
+// modify playeringame[] when playing back multiplayer demos.
+
+static boolean local_playeringame[NET_MAXPLAYERS];
+
+// Requested player class "sent" to the server on connect.
+// If we are only doing a single player game then this needs to be remembered
+// and saved in the game settings.
+
+static int player_class;
+
+
+// 35 fps clock adjusted by offsetms milliseconds
+
+static int GetAdjustedTime(void)
+{
+ int time_ms;
+
+ time_ms = I_GetTimeMS();
+
+ if (new_sync)
+ {
+ // Use the adjustments from net_client.c only if we are
+ // using the new sync mode.
+
+ time_ms += (offsetms / FRACUNIT);
+ }
+
+ return (time_ms * TICRATE) / 1000;
+}
+
+static boolean BuildNewTic(void)
+{
+ int gameticdiv;
+ ticcmd_t cmd;
+
+ gameticdiv = gametic/ticdup;
+
+ I_StartTic ();
+ loop_interface->ProcessEvents();
+
+ // Always run the menu
+
+ loop_interface->RunMenu();
+
+ if (drone)
+ {
+ // In drone mode, do not generate any ticcmds.
+
+ return false;
+ }
+
+ if (new_sync)
+ {
+ // If playing single player, do not allow tics to buffer
+ // up very far
+
+ if (!net_client_connected && maketic - gameticdiv > 2)
+ return false;
+
+ // Never go more than ~200ms ahead
+
+ if (maketic - gameticdiv > 8)
+ return false;
+ }
+ else
+ {
+ if (maketic - gameticdiv >= 5)
+ return false;
+ }
+
+ //printf ("mk:%i ",maketic);
+ memset(&cmd, 0, sizeof(ticcmd_t));
+ loop_interface->BuildTiccmd(&cmd, maketic);
+
+#ifdef FEATURE_MULTIPLAYER
+
+ if (net_client_connected)
+ {
+ NET_CL_SendTiccmd(&cmd, maketic);
+ }
+
+#endif
+ ticdata[maketic % BACKUPTICS].cmds[localplayer] = cmd;
+ ticdata[maketic % BACKUPTICS].ingame[localplayer] = true;
+
+ ++maketic;
+
+ return true;
+}
+
+//
+// NetUpdate
+// Builds ticcmds for console player,
+// sends out a packet
+//
+int lasttime;
+
+void NetUpdate (void)
+{
+ int nowtime;
+ int newtics;
+ int i;
+
+ // If we are running with singletics (timing a demo), this
+ // is all done separately.
+
+ if (singletics)
+ return;
+
+#ifdef FEATURE_MULTIPLAYER
+
+ // Run network subsystems
+
+ NET_CL_Run();
+ NET_SV_Run();
+
+#endif
+
+ // check time
+ nowtime = GetAdjustedTime() / ticdup;
+ newtics = nowtime - lasttime;
+
+ lasttime = nowtime;
+
+ if (skiptics <= newtics)
+ {
+ newtics -= skiptics;
+ skiptics = 0;
+ }
+ else
+ {
+ skiptics -= newtics;
+ newtics = 0;
+ }
+
+ // build new ticcmds for console player
+
+ for (i=0 ; iconsoleplayer = 0;
+ settings->num_players = 1;
+ settings->player_classes[0] = player_class;
+
+ //!
+ // @category net
+ //
+ // Use new network client sync code rather than the classic
+ // sync code. This is currently disabled by default because it
+ // has some bugs.
+ //
+ if (M_CheckParm("-newsync") > 0)
+ settings->new_sync = 1;
+ else
+ settings->new_sync = 0;
+
+ // TODO: New sync code is not enabled by default because it's
+ // currently broken.
+ //if (M_CheckParm("-oldsync") > 0)
+ // settings->new_sync = 0;
+ //else
+ // settings->new_sync = 1;
+
+ //!
+ // @category net
+ // @arg
+ //
+ // Send n extra tics in every packet as insurance against dropped
+ // packets.
+ //
+
+ i = M_CheckParmWithArgs("-extratics", 1);
+
+ if (i > 0)
+ settings->extratics = atoi(myargv[i+1]);
+ else
+ settings->extratics = 1;
+
+ //!
+ // @category net
+ // @arg
+ //
+ // Reduce the resolution of the game by a factor of n, reducing
+ // the amount of network bandwidth needed.
+ //
+
+ i = M_CheckParmWithArgs("-dup", 1);
+
+ if (i > 0)
+ settings->ticdup = atoi(myargv[i+1]);
+ else
+ settings->ticdup = 1;
+
+ if (net_client_connected)
+ {
+ // Send our game settings and block until game start is received
+ // from the server.
+
+ NET_CL_StartGame(settings);
+ BlockUntilStart(settings, callback);
+
+ // Read the game settings that were received.
+
+ NET_CL_GetSettings(settings);
+ }
+
+ if (drone)
+ {
+ settings->consoleplayer = 0;
+ }
+
+ // Set the local player and playeringame[] values.
+
+ localplayer = settings->consoleplayer;
+
+ for (i = 0; i < NET_MAXPLAYERS; ++i)
+ {
+ local_playeringame[i] = i < settings->num_players;
+ }
+
+ // Copy settings to global variables.
+
+ ticdup = settings->ticdup;
+ new_sync = settings->new_sync;
+
+ // TODO: Message disabled until we fix new_sync.
+ //if (!new_sync)
+ //{
+ // printf("Syncing netgames like Vanilla Doom.\n");
+ //}
+#else
+ settings->consoleplayer = 0;
+ settings->num_players = 1;
+ settings->player_classes[0] = player_class;
+ settings->new_sync = 0;
+ settings->extratics = 1;
+ settings->ticdup = 1;
+
+ ticdup = settings->ticdup;
+ new_sync = settings->new_sync;
+#endif
+}
+
+boolean D_InitNetGame(net_connect_data_t *connect_data)
+{
+ boolean result = false;
+#ifdef FEATURE_MULTIPLAYER
+ net_addr_t *addr = NULL;
+ int i;
+#endif
+
+ // Call D_QuitNetGame on exit:
+
+ I_AtExit(D_QuitNetGame, true);
+
+ player_class = connect_data->player_class;
+
+#ifdef FEATURE_MULTIPLAYER
+
+ //!
+ // @category net
+ //
+ // Start a multiplayer server, listening for connections.
+ //
+
+ if (M_CheckParm("-server") > 0
+ || M_CheckParm("-privateserver") > 0)
+ {
+ NET_SV_Init();
+ NET_SV_AddModule(&net_loop_server_module);
+ NET_SV_AddModule(&net_sdl_module);
+ NET_SV_RegisterWithMaster();
+
+ net_loop_client_module.InitClient();
+ addr = net_loop_client_module.ResolveAddress(NULL);
+ }
+ else
+ {
+ //!
+ // @category net
+ //
+ // Automatically search the local LAN for a multiplayer
+ // server and join it.
+ //
+
+ i = M_CheckParm("-autojoin");
+
+ if (i > 0)
+ {
+ addr = NET_FindLANServer();
+
+ if (addr == NULL)
+ {
+ I_Error("No server found on local LAN");
+ }
+ }
+
+ //!
+ // @arg
+ // @category net
+ //
+ // Connect to a multiplayer server running on the given
+ // address.
+ //
+
+ i = M_CheckParmWithArgs("-connect", 1);
+
+ if (i > 0)
+ {
+ net_sdl_module.InitClient();
+ addr = net_sdl_module.ResolveAddress(myargv[i+1]);
+
+ if (addr == NULL)
+ {
+ I_Error("Unable to resolve '%s'\n", myargv[i+1]);
+ }
+ }
+ }
+
+ if (addr != NULL)
+ {
+ if (M_CheckParm("-drone") > 0)
+ {
+ connect_data->drone = true;
+ }
+
+ if (!NET_CL_Connect(addr, connect_data))
+ {
+ I_Error("D_InitNetGame: Failed to connect to %s\n",
+ NET_AddrToString(addr));
+ }
+
+ printf("D_InitNetGame: Connected to %s\n", NET_AddrToString(addr));
+
+ // Wait for launch message received from server.
+
+ NET_WaitForLaunch();
+
+ result = true;
+ }
+#endif
+
+ return result;
+}
+
+
+//
+// D_QuitNetGame
+// Called before quitting to leave a net game
+// without hanging the other players
+//
+void D_QuitNetGame (void)
+{
+#ifdef FEATURE_MULTIPLAYER
+ NET_SV_Shutdown();
+ NET_CL_Disconnect();
+#endif
+}
+
+static int GetLowTic(void)
+{
+ int lowtic;
+
+ lowtic = maketic;
+
+#ifdef FEATURE_MULTIPLAYER
+ if (net_client_connected)
+ {
+ if (drone || recvtic < lowtic)
+ {
+ lowtic = recvtic;
+ }
+ }
+#endif
+
+ return lowtic;
+}
+
+static int frameon;
+static int frameskip[4];
+static int oldnettics;
+
+static void OldNetSync(void)
+{
+ unsigned int i;
+ int keyplayer = -1;
+
+ frameon++;
+
+ // ideally maketic should be 1 - 3 tics above lowtic
+ // if we are consistantly slower, speed up time
+
+ for (i=0 ; i recvtic;
+ oldnettics = maketic;
+
+ if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3])
+ {
+ skiptics = 1;
+ // printf ("+");
+ }
+ }
+}
+
+// Returns true if there are players in the game:
+
+static boolean PlayersInGame(void)
+{
+ boolean result = false;
+ unsigned int i;
+
+ // If we are connected to a server, check if there are any players
+ // in the game.
+
+ if (net_client_connected)
+ {
+ for (i = 0; i < NET_MAXPLAYERS; ++i)
+ {
+ result = result || local_playeringame[i];
+ }
+ }
+
+ // Whether single or multi-player, unless we are running as a drone,
+ // we are in the game.
+
+ if (!drone)
+ {
+ result = true;
+ }
+
+ return result;
+}
+
+// When using ticdup, certain values must be cleared out when running
+// the duplicate ticcmds.
+
+static void TicdupSquash(ticcmd_set_t *set)
+{
+ ticcmd_t *cmd;
+ unsigned int i;
+
+ for (i = 0; i < NET_MAXPLAYERS ; ++i)
+ {
+ cmd = &set->cmds[i];
+ cmd->chatchar = 0;
+ if (cmd->buttons & BT_SPECIAL)
+ cmd->buttons = 0;
+ }
+}
+
+// When running in single player mode, clear all the ingame[] array
+// except the local player.
+
+static void SinglePlayerClear(ticcmd_set_t *set)
+{
+ unsigned int i;
+
+ for (i = 0; i < NET_MAXPLAYERS; ++i)
+ {
+ if (i != localplayer)
+ {
+ set->ingame[i] = false;
+ }
+ }
+}
+
+//
+// TryRunTics
+//
+
+void TryRunTics (void)
+{
+ int i;
+ int lowtic;
+ int entertic;
+ static int oldentertics;
+ int realtics;
+ int availabletics;
+ int counts;
+
+ // get real tics
+ entertic = I_GetTime() / ticdup;
+ realtics = entertic - oldentertics;
+ oldentertics = entertic;
+
+ // in singletics mode, run a single tic every time this function
+ // is called.
+
+ if (singletics)
+ {
+ BuildNewTic();
+ }
+ else
+ {
+ NetUpdate ();
+ }
+
+ lowtic = GetLowTic();
+
+ availabletics = lowtic - gametic/ticdup;
+
+ // decide how many tics to run
+
+ if (new_sync)
+ {
+ counts = availabletics;
+ }
+ else
+ {
+ // decide how many tics to run
+ if (realtics < availabletics-1)
+ counts = realtics+1;
+ else if (realtics < availabletics)
+ counts = realtics;
+ else
+ counts = availabletics;
+
+ if (counts < 1)
+ counts = 1;
+
+ if (net_client_connected)
+ {
+ OldNetSync();
+ }
+ }
+
+ if (counts < 1)
+ counts = 1;
+
+ // wait for new tics if needed
+
+ while (!PlayersInGame() || lowtic < gametic/ticdup + counts)
+ {
+ NetUpdate ();
+
+ lowtic = GetLowTic();
+
+ if (lowtic < gametic/ticdup)
+ I_Error ("TryRunTics: lowtic < gametic");
+
+ // Don't stay in this loop forever. The menu is still running,
+ // so return to update the screen
+
+ if (I_GetTime() / ticdup - entertic > 0)
+ {
+ return;
+ }
+
+ I_Sleep(1);
+ }
+
+ // run the count * ticdup dics
+ while (counts--)
+ {
+ ticcmd_set_t *set;
+
+ if (!PlayersInGame())
+ {
+ return;
+ }
+
+ set = &ticdata[(gametic / ticdup) % BACKUPTICS];
+
+ if (!net_client_connected)
+ {
+ SinglePlayerClear(set);
+ }
+
+ for (i=0 ; i lowtic)
+ I_Error ("gametic>lowtic");
+
+ memcpy(local_playeringame, set->ingame, sizeof(local_playeringame));
+
+ loop_interface->RunTic(set->cmds, set->ingame);
+ gametic++;
+
+ // modify command for duplicated tics
+
+ TicdupSquash(set);
+ }
+
+ NetUpdate (); // check for new console commands
+ }
+}
+
+void D_RegisterLoopCallbacks(loop_interface_t *i)
+{
+ loop_interface = i;
+}
diff --git a/firmware_p4/components/Applications/doom/d_loop.h b/firmware_p4/components/Applications/doom/d_loop.h
new file mode 100644
index 000000000..eb87d8488
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_loop.h
@@ -0,0 +1,81 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Main loop stuff.
+//
+
+#ifndef __D_LOOP__
+#define __D_LOOP__
+
+#include "net_defs.h"
+
+// Callback function invoked while waiting for the netgame to start.
+// The callback is invoked when new players are ready. The callback
+// should return true, or return false to abort startup.
+
+typedef boolean (*netgame_startup_callback_t)(int ready_players,
+ int num_players);
+
+typedef struct
+{
+ // Read events from the event queue, and process them.
+
+ void (*ProcessEvents)();
+
+ // Given the current input state, fill in the fields of the specified
+ // ticcmd_t structure with data for a new tic.
+
+ void (*BuildTiccmd)(ticcmd_t *cmd, int maketic);
+
+ // Advance the game forward one tic, using the specified player input.
+
+ void (*RunTic)(ticcmd_t *cmds, boolean *ingame);
+
+ // Run the menu (runs independently of the game).
+
+ void (*RunMenu)();
+} loop_interface_t;
+
+// Register callback functions for the main loop code to use.
+void D_RegisterLoopCallbacks(loop_interface_t *i);
+
+// Create any new ticcmds and broadcast to other players.
+void NetUpdate (void);
+
+// Broadcasts special packets to other players
+// to notify of game exit
+void D_QuitNetGame (void);
+
+//? how many ticks to run?
+void TryRunTics (void);
+
+// Called at start of game loop to initialize timers
+void D_StartGameLoop(void);
+
+// Initialize networking code and connect to server.
+
+boolean D_InitNetGame(net_connect_data_t *connect_data);
+
+// Start game with specified settings. The structure will be updated
+// with the actual settings for the game.
+
+void D_StartNetGame(net_gamesettings_t *settings,
+ netgame_startup_callback_t callback);
+
+extern boolean singletics;
+extern int gametic, ticdup;
+
+#endif
+
diff --git a/firmware_p4/components/Applications/doom/d_main.c b/firmware_p4/components/Applications/doom/d_main.c
new file mode 100644
index 000000000..9012e5f54
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_main.c
@@ -0,0 +1,1845 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// DOOM main program (D_DoomMain) and game loop (D_DoomLoop),
+// plus functions to determine game mode (shareware, registered),
+// parse command line parameters, configure game parameters (turbo),
+// and call the startup functions.
+//
+
+
+#include
+#include
+#include
+#include
+
+#include "config.h"
+#include "deh_main.h"
+#include "doomdef.h"
+#include "doomstat.h"
+
+#include "dstrings.h"
+#include "doomfeatures.h"
+#include "sounds.h"
+
+#include "d_iwad.h"
+
+#include "z_zone.h"
+#include "w_main.h"
+#include "w_wad.h"
+#include "s_sound.h"
+#include "v_video.h"
+
+#include "f_finale.h"
+#include "f_wipe.h"
+
+#include "m_argv.h"
+#include "m_config.h"
+#include "m_controls.h"
+#include "m_misc.h"
+#include "m_menu.h"
+#include "p_saveg.h"
+
+#include "i_endoom.h"
+#include "i_joystick.h"
+#include "i_system.h"
+#include "i_timer.h"
+#include "i_video.h"
+
+#include "g_game.h"
+
+#include "hu_stuff.h"
+#include "wi_stuff.h"
+#include "st_stuff.h"
+#include "am_map.h"
+#include "net_client.h"
+#include "net_dedicated.h"
+#include "net_query.h"
+
+#include "p_setup.h"
+#include "r_local.h"
+#include "statdump.h"
+
+#include "d_main.h"
+
+//
+// D-DoomLoop()
+// Not a globally visible function,
+// just included for source reference,
+// called by D_DoomMain, never exits.
+// Manages timing and IO,
+// calls all ?_Responder, ?_Ticker, and ?_Drawer,
+// calls I_GetTime, I_StartFrame, and I_StartTic
+//
+void D_DoomLoop (void);
+
+// Location where savegames are stored
+
+char * savegamedir;
+
+// location of IWAD and WAD files
+
+char * iwadfile;
+
+
+boolean devparm; // started game with -devparm
+boolean nomonsters; // checkparm of -nomonsters
+boolean respawnparm; // checkparm of -respawn
+boolean fastparm; // checkparm of -fast
+
+//extern int soundVolume;
+//extern int sfxVolume;
+//extern int musicVolume;
+
+extern boolean inhelpscreens;
+
+skill_t startskill;
+int startepisode;
+int startmap;
+boolean autostart;
+int startloadgame;
+
+boolean advancedemo;
+
+// Store demo, do not accept any inputs
+boolean storedemo;
+
+// "BFG Edition" version of doom2.wad does not include TITLEPIC.
+boolean bfgedition;
+
+// If true, the main game loop has started.
+boolean main_loop_started = false;
+
+char wadfile[1024]; // primary wad file
+char mapdir[1024]; // directory of development maps
+
+int show_endoom = 1;
+
+
+void D_ConnectNetGame(void);
+void D_CheckNetGame(void);
+
+
+//
+// D_ProcessEvents
+// Send all the events of the given timestamp down the responder chain
+//
+void D_ProcessEvents (void)
+{
+ event_t* ev;
+
+ // IF STORE DEMO, DO NOT ACCEPT INPUT
+ if (storedemo)
+ return;
+
+ while ((ev = D_PopEvent()) != NULL)
+ {
+ if (M_Responder (ev))
+ continue; // menu ate the event
+ G_Responder (ev);
+ }
+}
+
+
+
+
+//
+// D_Display
+// draw current display, possibly wiping it from the previous
+//
+
+// wipegamestate can be set to -1 to force a wipe on the next draw
+gamestate_t wipegamestate = GS_DEMOSCREEN;
+extern boolean setsizeneeded;
+extern int showMessages;
+void R_ExecuteSetViewSize (void);
+
+void D_Display (void)
+{
+ static boolean viewactivestate = false;
+ static boolean menuactivestate = false;
+ static boolean inhelpscreensstate = false;
+ static boolean fullscreen = false;
+ static gamestate_t oldgamestate = -1;
+ static int borderdrawcount;
+ int nowtime;
+ int tics;
+ int wipestart;
+ int y;
+ boolean done;
+ boolean wipe;
+ boolean redrawsbar;
+
+ if (nodrawers)
+ return; // for comparative timing / profiling
+
+ redrawsbar = false;
+
+ // change the view size if needed
+ if (setsizeneeded)
+ {
+ R_ExecuteSetViewSize ();
+ oldgamestate = -1; // force background redraw
+ borderdrawcount = 3;
+ }
+
+ // save the current screen if about to wipe
+ if (gamestate != wipegamestate)
+ {
+ wipe = true;
+ wipe_StartScreen(0, 0, SCREENWIDTH, SCREENHEIGHT);
+ }
+ else
+ wipe = false;
+
+ if (gamestate == GS_LEVEL && gametic)
+ HU_Erase();
+
+ // do buffered drawing
+ switch (gamestate)
+ {
+ case GS_LEVEL:
+ if (!gametic)
+ break;
+ if (automapactive)
+ AM_Drawer ();
+ if (wipe || (viewheight != 200 && fullscreen) )
+ redrawsbar = true;
+ if (inhelpscreensstate && !inhelpscreens)
+ redrawsbar = true; // just put away the help screen
+ ST_Drawer (viewheight == 200, redrawsbar );
+ fullscreen = viewheight == 200;
+ break;
+
+ case GS_INTERMISSION:
+ WI_Drawer ();
+ break;
+
+ case GS_FINALE:
+ F_Drawer ();
+ break;
+
+ case GS_DEMOSCREEN:
+ D_PageDrawer ();
+ break;
+ }
+
+ // draw buffered stuff to screen
+ I_UpdateNoBlit ();
+
+ // draw the view directly
+ if (gamestate == GS_LEVEL && !automapactive && gametic)
+ R_RenderPlayerView (&players[displayplayer]);
+
+ if (gamestate == GS_LEVEL && gametic)
+ HU_Drawer ();
+
+ // clean up border stuff
+ if (gamestate != oldgamestate && gamestate != GS_LEVEL)
+ I_SetPalette (W_CacheLumpName (DEH_String("PLAYPAL"),PU_CACHE));
+
+ // see if the border needs to be initially drawn
+ if (gamestate == GS_LEVEL && oldgamestate != GS_LEVEL)
+ {
+ viewactivestate = false; // view was not active
+ R_FillBackScreen (); // draw the pattern into the back screen
+ }
+
+ // see if the border needs to be updated to the screen
+ if (gamestate == GS_LEVEL && !automapactive && scaledviewwidth != 320)
+ {
+ if (menuactive || menuactivestate || !viewactivestate)
+ borderdrawcount = 3;
+ if (borderdrawcount)
+ {
+ R_DrawViewBorder (); // erase old menu stuff
+ borderdrawcount--;
+ }
+ }
+
+ if (testcontrols)
+ {
+ // Box showing current mouse speed
+
+ V_DrawMouseSpeedBox(testcontrols_mousespeed);
+ }
+
+ menuactivestate = menuactive;
+ viewactivestate = viewactive;
+ inhelpscreensstate = inhelpscreens;
+ oldgamestate = wipegamestate = gamestate;
+
+ // draw pause pic
+ if (paused)
+ {
+ if (automapactive)
+ y = 4;
+ else
+ y = viewwindowy+4;
+ V_DrawPatchDirect(viewwindowx + (scaledviewwidth - 68) / 2, y,
+ W_CacheLumpName (DEH_String("M_PAUSE"), PU_CACHE));
+ }
+
+
+ // menus go directly to the screen
+ M_Drawer (); // menu is drawn even on top of everything
+ NetUpdate (); // send out any new accumulation
+
+
+ // normal update
+ if (!wipe)
+ {
+ I_FinishUpdate (); // page flip or blit buffer
+ return;
+ }
+
+ // wipe update
+ wipe_EndScreen(0, 0, SCREENWIDTH, SCREENHEIGHT);
+
+ wipestart = I_GetTime () - 1;
+
+ do
+ {
+ do
+ {
+ nowtime = I_GetTime ();
+ tics = nowtime - wipestart;
+ I_Sleep(1);
+ } while (tics <= 0);
+
+ wipestart = nowtime;
+ done = wipe_ScreenWipe(wipe_Melt
+ , 0, 0, SCREENWIDTH, SCREENHEIGHT, tics);
+ I_UpdateNoBlit ();
+ M_Drawer (); // menu is drawn even on top of wipes
+ I_FinishUpdate (); // page flip or blit buffer
+ } while (!done);
+}
+
+//
+// Add configuration file variable bindings.
+//
+
+void D_BindVariables(void)
+{
+ int i;
+
+ M_ApplyPlatformDefaults();
+
+ I_BindVideoVariables();
+ I_BindJoystickVariables();
+ I_BindSoundVariables();
+
+ M_BindBaseControls();
+ M_BindWeaponControls();
+ M_BindMapControls();
+ M_BindMenuControls();
+ M_BindChatControls(MAXPLAYERS);
+
+ key_multi_msgplayer[0] = HUSTR_KEYGREEN;
+ key_multi_msgplayer[1] = HUSTR_KEYINDIGO;
+ key_multi_msgplayer[2] = HUSTR_KEYBROWN;
+ key_multi_msgplayer[3] = HUSTR_KEYRED;
+
+#ifdef FEATURE_MULTIPLAYER
+ NET_BindVariables();
+#endif
+
+ M_BindVariable("mouse_sensitivity", &mouseSensitivity);
+ M_BindVariable("sfx_volume", &sfxVolume);
+ M_BindVariable("music_volume", &musicVolume);
+ M_BindVariable("show_messages", &showMessages);
+ M_BindVariable("screenblocks", &screenblocks);
+ M_BindVariable("detaillevel", &detailLevel);
+ M_BindVariable("snd_channels", &snd_channels);
+ M_BindVariable("vanilla_savegame_limit", &vanilla_savegame_limit);
+ M_BindVariable("vanilla_demo_limit", &vanilla_demo_limit);
+ M_BindVariable("show_endoom", &show_endoom);
+
+ // Multiplayer chat macros
+
+ for (i=0; i<10; ++i)
+ {
+ char buf[12];
+
+ M_snprintf(buf, sizeof(buf), "chatmacro%i", i);
+ M_BindVariable(buf, &chat_macros[i]);
+ }
+}
+
+//
+// D_GrabMouseCallback
+//
+// Called to determine whether to grab the mouse pointer
+//
+
+boolean D_GrabMouseCallback(void)
+{
+ // Drone players don't need mouse focus
+
+ if (drone)
+ return false;
+
+ // when menu is active or game is paused, release the mouse
+
+ if (menuactive || paused)
+ return false;
+
+ // only grab mouse when playing levels (but not demos)
+
+ return (gamestate == GS_LEVEL) && !demoplayback && !advancedemo;
+}
+
+void doomgeneric_Tick()
+{
+ // frame syncronous IO operations
+ I_StartFrame ();
+
+ TryRunTics (); // will run at least one tic
+
+ S_UpdateSounds (players[consoleplayer].mo);// move positional sounds
+
+ // Update display, next frame, with current state.
+ if (screenvisible)
+ {
+ D_Display ();
+ }
+}
+
+//
+// D_DoomLoop
+//
+void D_DoomLoop (void)
+{
+ if (bfgedition &&
+ (demorecording || (gameaction == ga_playdemo) || netgame))
+ {
+ printf(" WARNING: You are playing using one of the Doom Classic\n"
+ " IWAD files shipped with the Doom 3: BFG Edition. These are\n"
+ " known to be incompatible with the regular IWAD files and\n"
+ " may cause demos and network games to get out of sync.\n");
+ }
+
+ if (demorecording)
+ G_BeginRecording ();
+
+ main_loop_started = true;
+
+ TryRunTics();
+
+ I_SetWindowTitle(gamedescription);
+ I_GraphicsCheckCommandLine();
+ I_SetGrabMouseCallback(D_GrabMouseCallback);
+ I_InitGraphics();
+ I_EnableLoadingDisk();
+
+ V_RestoreBuffer();
+ R_ExecuteSetViewSize();
+
+ D_StartGameLoop();
+
+ if (testcontrols)
+ {
+ wipegamestate = gamestate;
+ }
+
+ doomgeneric_Tick();
+}
+
+
+
+//
+// DEMO LOOP
+//
+int demosequence;
+int pagetic;
+char *pagename;
+
+
+//
+// D_PageTicker
+// Handles timing for warped projection
+//
+void D_PageTicker (void)
+{
+ if (--pagetic < 0)
+ D_AdvanceDemo ();
+}
+
+
+
+//
+// D_PageDrawer
+//
+void D_PageDrawer (void)
+{
+ V_DrawPatch (0, 0, W_CacheLumpName(pagename, PU_CACHE));
+}
+
+
+//
+// D_AdvanceDemo
+// Called after each demo or intro demosequence finishes
+//
+void D_AdvanceDemo (void)
+{
+ advancedemo = true;
+}
+
+
+//
+// This cycles through the demo sequences.
+// FIXME - version dependend demo numbers?
+//
+void D_DoAdvanceDemo (void)
+{
+ players[consoleplayer].playerstate = PST_LIVE; // not reborn
+ advancedemo = false;
+ usergame = false; // no save / end game here
+ paused = false;
+ gameaction = ga_nothing;
+
+ // The Ultimate Doom executable changed the demo sequence to add
+ // a DEMO4 demo. Final Doom was based on Ultimate, so also
+ // includes this change; however, the Final Doom IWADs do not
+ // include a DEMO4 lump, so the game bombs out with an error
+ // when it reaches this point in the demo sequence.
+
+ // However! There is an alternate version of Final Doom that
+ // includes a fixed executable.
+
+ if (gameversion == exe_ultimate || gameversion == exe_final)
+ demosequence = (demosequence+1)%7;
+ else
+ demosequence = (demosequence+1)%6;
+
+ switch (demosequence)
+ {
+ case 0:
+ if ( gamemode == commercial )
+ pagetic = TICRATE * 11;
+ else
+ pagetic = 170;
+ gamestate = GS_DEMOSCREEN;
+ pagename = DEH_String("TITLEPIC");
+ if ( gamemode == commercial )
+ S_StartMusic(mus_dm2ttl);
+ else
+ S_StartMusic (mus_intro);
+ break;
+ case 1:
+ G_DeferedPlayDemo(DEH_String("demo1"));
+ break;
+ case 2:
+ pagetic = 200;
+ gamestate = GS_DEMOSCREEN;
+ pagename = DEH_String("CREDIT");
+ break;
+ case 3:
+ G_DeferedPlayDemo(DEH_String("demo2"));
+ break;
+ case 4:
+ gamestate = GS_DEMOSCREEN;
+ if ( gamemode == commercial)
+ {
+ pagetic = TICRATE * 11;
+ pagename = DEH_String("TITLEPIC");
+ S_StartMusic(mus_dm2ttl);
+ }
+ else
+ {
+ pagetic = 200;
+
+ if ( gamemode == retail )
+ pagename = DEH_String("CREDIT");
+ else
+ pagename = DEH_String("HELP2");
+ }
+ break;
+ case 5:
+ G_DeferedPlayDemo(DEH_String("demo3"));
+ break;
+ // THE DEFINITIVE DOOM Special Edition demo
+ case 6:
+ G_DeferedPlayDemo(DEH_String("demo4"));
+ break;
+ }
+
+ // The Doom 3: BFG Edition version of doom2.wad does not have a
+ // TITLETPIC lump. Use INTERPIC instead as a workaround.
+ if (bfgedition && !strcasecmp(pagename, "TITLEPIC")
+ && W_CheckNumForName("titlepic") < 0)
+ {
+ pagename = DEH_String("INTERPIC");
+ }
+}
+
+
+
+//
+// D_StartTitle
+//
+void D_StartTitle (void)
+{
+ gameaction = ga_nothing;
+ demosequence = -1;
+ D_AdvanceDemo ();
+}
+
+// Strings for dehacked replacements of the startup banner
+//
+// These are from the original source: some of them are perhaps
+// not used in any dehacked patches
+
+static char *banners[] =
+{
+ // doom2.wad
+ " "
+ "DOOM 2: Hell on Earth v%i.%i"
+ " ",
+ // doom1.wad
+ " "
+ "DOOM Shareware Startup v%i.%i"
+ " ",
+ // doom.wad
+ " "
+ "DOOM Registered Startup v%i.%i"
+ " ",
+ // Registered DOOM uses this
+ " "
+ "DOOM System Startup v%i.%i"
+ " ",
+ // doom.wad (Ultimate DOOM)
+ " "
+ "The Ultimate DOOM Startup v%i.%i"
+ " ",
+ // tnt.wad
+ " "
+ "DOOM 2: TNT - Evilution v%i.%i"
+ " ",
+ // plutonia.wad
+ " "
+ "DOOM 2: Plutonia Experiment v%i.%i"
+ " ",
+};
+
+//
+// Get game name: if the startup banner has been replaced, use that.
+// Otherwise, use the name given
+//
+
+static char *GetGameName(char *gamename)
+{
+ size_t i;
+ char *deh_sub;
+
+ for (i=0; i 0)
+ {
+ // Ultimate Doom
+
+ gamemode = retail;
+ }
+ else if (W_CheckNumForName("E3M1") > 0)
+ {
+ gamemode = registered;
+ }
+ else
+ {
+ gamemode = shareware;
+ }
+ }
+ else
+ {
+ int p;
+
+ // Doom 2 of some kind.
+ gamemode = commercial;
+
+ // We can manually override the gamemission that we got from the
+ // IWAD detection code. This allows us to eg. play Plutonia 2
+ // with Freedoom and get the right level names.
+
+ //!
+ // @arg
+ //
+ // Explicitly specify a Doom II "mission pack" to run as, instead of
+ // detecting it based on the filename. Valid values are: "doom2",
+ // "tnt" and "plutonia".
+ //
+ p = M_CheckParmWithArgs("-pack", 1);
+ if (p > 0)
+ {
+ SetMissionForPackName(myargv[p + 1]);
+ }
+ }
+}
+
+// Set the gamedescription string
+
+void D_SetGameDescription(void)
+{
+ boolean is_freedoom = W_CheckNumForName("FREEDOOM") >= 0,
+ is_freedm = W_CheckNumForName("FREEDM") >= 0;
+
+ gamedescription = "Unknown";
+
+ if (logical_gamemission == doom)
+ {
+ // Doom 1. But which version?
+
+ if (is_freedoom)
+ {
+ gamedescription = GetGameName("Freedoom: Phase 1");
+ }
+ else if (gamemode == retail)
+ {
+ // Ultimate Doom
+
+ gamedescription = GetGameName("The Ultimate DOOM");
+ }
+ else if (gamemode == registered)
+ {
+ gamedescription = GetGameName("DOOM Registered");
+ }
+ else if (gamemode == shareware)
+ {
+ gamedescription = GetGameName("DOOM Shareware");
+ }
+ }
+ else
+ {
+ // Doom 2 of some kind. But which mission?
+
+ if (is_freedoom)
+ {
+ if (is_freedm)
+ {
+ gamedescription = GetGameName("FreeDM");
+ }
+ else
+ {
+ gamedescription = GetGameName("Freedoom: Phase 2");
+ }
+ }
+ else if (logical_gamemission == doom2)
+ {
+ gamedescription = GetGameName("DOOM 2: Hell on Earth");
+ }
+ else if (logical_gamemission == pack_plut)
+ {
+ gamedescription = GetGameName("DOOM 2: Plutonia Experiment");
+ }
+ else if (logical_gamemission == pack_tnt)
+ {
+ gamedescription = GetGameName("DOOM 2: TNT - Evilution");
+ }
+ }
+}
+
+// print title for every printed line
+char title[128];
+
+static boolean D_AddFile(char *filename)
+{
+ wad_file_t *handle;
+
+ printf(" adding %s\n", filename);
+ handle = W_AddFile(filename);
+
+ return handle != NULL;
+}
+
+// Copyright message banners
+// Some dehacked mods replace these. These are only displayed if they are
+// replaced by dehacked.
+
+static char *copyright_banners[] =
+{
+ "===========================================================================\n"
+ "ATTENTION: This version of DOOM has been modified. If you would like to\n"
+ "get a copy of the original game, call 1-800-IDGAMES or see the readme file.\n"
+ " You will not receive technical support for modified games.\n"
+ " press enter to continue\n"
+ "===========================================================================\n",
+
+ "===========================================================================\n"
+ " Commercial product - do not distribute!\n"
+ " Please report software piracy to the SPA: 1-800-388-PIR8\n"
+ "===========================================================================\n",
+
+ "===========================================================================\n"
+ " Shareware!\n"
+ "===========================================================================\n"
+};
+
+// Prints a message only if it has been modified by dehacked.
+
+void PrintDehackedBanners(void)
+{
+ size_t i;
+
+ for (i=0; i
+ // @category compat
+ //
+ // Emulate a specific version of Doom. Valid values are "1.9",
+ // "ultimate", "final", "final2", "hacx" and "chex".
+ //
+
+ p = M_CheckParmWithArgs("-gameversion", 1);
+
+ if (p)
+ {
+ for (i=0; gameversions[i].description != NULL; ++i)
+ {
+ if (!strcmp(myargv[p+1], gameversions[i].cmdline))
+ {
+ gameversion = gameversions[i].version;
+ break;
+ }
+ }
+
+ if (gameversions[i].description == NULL)
+ {
+ printf("Supported game versions:\n");
+
+ for (i=0; gameversions[i].description != NULL; ++i)
+ {
+ printf("\t%s (%s)\n", gameversions[i].cmdline,
+ gameversions[i].description);
+ }
+
+ I_Error("Unknown game version '%s'", myargv[p+1]);
+ }
+ }
+ else
+ {
+ // Determine automatically
+
+ if (gamemission == pack_chex)
+ {
+ // chex.exe - identified by iwad filename
+
+ gameversion = exe_chex;
+ }
+ else if (gamemission == pack_hacx)
+ {
+ // hacx.exe: identified by iwad filename
+
+ gameversion = exe_hacx;
+ }
+ else if (gamemode == shareware || gamemode == registered)
+ {
+ // original
+
+ gameversion = exe_doom_1_9;
+
+ // TODO: Detect IWADs earlier than Doom v1.9.
+ }
+ else if (gamemode == retail)
+ {
+ gameversion = exe_ultimate;
+ }
+ else if (gamemode == commercial)
+ {
+ if (gamemission == doom2)
+ {
+ gameversion = exe_doom_1_9;
+ }
+ else
+ {
+ // Final Doom: tnt or plutonia
+ // Defaults to emulating the first Final Doom executable,
+ // which has the crash in the demo loop; however, having
+ // this as the default should mean that it plays back
+ // most demos correctly.
+
+ gameversion = exe_final;
+ }
+ }
+ }
+
+ // The original exe does not support retail - 4th episode not supported
+
+ if (gameversion < exe_ultimate && gamemode == retail)
+ {
+ gamemode = registered;
+ }
+
+ // EXEs prior to the Final Doom exes do not support Final Doom.
+
+ if (gameversion < exe_final && gamemode == commercial
+ && (gamemission == pack_tnt || gamemission == pack_plut))
+ {
+ gamemission = doom2;
+ }
+}
+
+void PrintGameVersion(void)
+{
+ int i;
+
+ for (i=0; gameversions[i].description != NULL; ++i)
+ {
+ if (gameversions[i].version == gameversion)
+ {
+ printf("Emulating the behavior of the "
+ "'%s' executable.\n", gameversions[i].description);
+ break;
+ }
+ }
+}
+
+// Function called at exit to display the ENDOOM screen
+
+static void D_Endoom(void)
+{
+ byte *endoom;
+
+ // Don't show ENDOOM if we have it disabled, or we're running
+ // in screensaver or control test mode. Only show it once the
+ // game has actually started.
+
+ if (!show_endoom || !main_loop_started
+ || screensaver_mode || M_CheckParm("-testcontrols") > 0)
+ {
+ return;
+ }
+
+ endoom = W_CacheLumpName(DEH_String("ENDOOM"), PU_STATIC);
+
+ I_Endoom(endoom);
+
+ exit(0);
+}
+
+#if ORIGCODE
+// Load dehacked patches needed for certain IWADs.
+static void LoadIwadDeh(void)
+{
+ // The Freedoom IWADs have DEHACKED lumps that must be loaded.
+ if (W_CheckNumForName("FREEDOOM") >= 0)
+ {
+ // Old versions of Freedoom (before 2014-09) did not have technically
+ // valid DEHACKED lumps, so ignore errors and just continue if this
+ // is an old IWAD.
+ DEH_LoadLumpByName("DEHACKED", false, true);
+ }
+
+ // If this is the HACX IWAD, we need to load the DEHACKED lump.
+ if (gameversion == exe_hacx)
+ {
+ if (!DEH_LoadLumpByName("DEHACKED", true, false))
+ {
+ I_Error("DEHACKED lump not found. Please check that this is the "
+ "Hacx v1.2 IWAD.");
+ }
+ }
+
+ // Chex Quest needs a separate Dehacked patch which must be downloaded
+ // and installed next to the IWAD.
+ if (gameversion == exe_chex)
+ {
+ char *chex_deh = NULL;
+ char *sep;
+
+ // Look for chex.deh in the same directory as the IWAD file.
+ sep = strrchr(iwadfile, DIR_SEPARATOR);
+
+ if (sep != NULL)
+ {
+ size_t chex_deh_len = strlen(iwadfile) + 9;
+ chex_deh = malloc(chex_deh_len);
+ M_StringCopy(chex_deh, iwadfile, chex_deh_len);
+ chex_deh[sep - iwadfile + 1] = '\0';
+ M_StringConcat(chex_deh, "chex.deh", chex_deh_len);
+ }
+ else
+ {
+ chex_deh = strdup("chex.deh");
+ }
+
+ // If the dehacked patch isn't found, try searching the WAD
+ // search path instead. We might find it...
+ if (!M_FileExists(chex_deh))
+ {
+ free(chex_deh);
+ chex_deh = D_FindWADByName("chex.deh");
+ }
+
+ // Still not found?
+ if (chex_deh == NULL)
+ {
+ I_Error("Unable to find Chex Quest dehacked file (chex.deh).\n"
+ "The dehacked file is required in order to emulate\n"
+ "chex.exe correctly. It can be found in your nearest\n"
+ "/idgames repository mirror at:\n\n"
+ " utils/exe_edit/patches/chexdeh.zip");
+ }
+
+ if (!DEH_LoadFile(chex_deh))
+ {
+ I_Error("Failed to load chex.deh needed for emulating chex.exe.");
+ }
+ }
+}
+#endif
+
+//
+// D_DoomMain
+//
+void D_DoomMain (void)
+{
+ int p;
+ char file[256];
+ char demolumpname[9];
+#if ORIGCODE
+ int numiwadlumps;
+#endif
+
+ I_AtExit(D_Endoom, false);
+
+ // print banner
+
+ I_PrintBanner(PACKAGE_STRING);
+
+ DEH_printf("Z_Init: Init zone memory allocation daemon. \n");
+ Z_Init ();
+
+#ifdef FEATURE_MULTIPLAYER
+ //!
+ // @category net
+ //
+ // Start a dedicated server, routing packets but not participating
+ // in the game itself.
+ //
+
+ if (M_CheckParm("-dedicated") > 0)
+ {
+ printf("Dedicated server mode.\n");
+ NET_DedicatedServer();
+
+ // Never returns
+ }
+
+ //!
+ // @category net
+ //
+ // Query the Internet master server for a global list of active
+ // servers.
+ //
+
+ if (M_CheckParm("-search"))
+ {
+ NET_MasterQuery();
+ exit(0);
+ }
+
+ //!
+ // @arg
+ // @category net
+ //
+ // Query the status of the server running on the given IP
+ // address.
+ //
+
+ p = M_CheckParmWithArgs("-query", 1);
+
+ if (p)
+ {
+ NET_QueryAddress(myargv[p+1]);
+ exit(0);
+ }
+
+ //!
+ // @category net
+ //
+ // Search the local LAN for running servers.
+ //
+
+ if (M_CheckParm("-localsearch"))
+ {
+ NET_LANQuery();
+ exit(0);
+ }
+
+#endif
+
+ //!
+ // @vanilla
+ //
+ // Disable monsters.
+ //
+
+ nomonsters = M_CheckParm ("-nomonsters");
+
+ //!
+ // @vanilla
+ //
+ // Monsters respawn after being killed.
+ //
+
+ respawnparm = M_CheckParm ("-respawn");
+
+ //!
+ // @vanilla
+ //
+ // Monsters move faster.
+ //
+
+ fastparm = M_CheckParm ("-fast");
+
+ //!
+ // @vanilla
+ //
+ // Developer mode. F1 saves a screenshot in the current working
+ // directory.
+ //
+
+ devparm = M_CheckParm ("-devparm");
+
+ I_DisplayFPSDots(devparm);
+
+ //!
+ // @category net
+ // @vanilla
+ //
+ // Start a deathmatch game.
+ //
+
+ if (M_CheckParm ("-deathmatch"))
+ deathmatch = 1;
+
+ //!
+ // @category net
+ // @vanilla
+ //
+ // Start a deathmatch 2.0 game. Weapons do not stay in place and
+ // all items respawn after 30 seconds.
+ //
+
+ if (M_CheckParm ("-altdeath"))
+ deathmatch = 2;
+
+ if (devparm)
+ DEH_printf(D_DEVSTR);
+
+ // find which dir to use for config files
+
+#ifdef _WIN32
+
+ //!
+ // @platform windows
+ // @vanilla
+ //
+ // Save configuration data and savegames in c:\doomdata,
+ // allowing play from CD.
+ //
+
+ if (M_ParmExists("-cdrom"))
+ {
+ printf(D_CDROM);
+
+ M_SetConfigDir("c:\\doomdata\\");
+ }
+ else
+#endif
+ {
+ // Auto-detect the configuration dir.
+
+ M_SetConfigDir(NULL);
+ }
+
+ //!
+ // @arg
+ // @vanilla
+ //
+ // Turbo mode. The player's speed is multiplied by x%. If unspecified,
+ // x defaults to 200. Values are rounded up to 10 and down to 400.
+ //
+
+ if ( (p=M_CheckParm ("-turbo")) )
+ {
+ int scale = 200;
+ extern int forwardmove[2];
+ extern int sidemove[2];
+
+ if (p 400)
+ scale = 400;
+ DEH_printf("turbo scale: %i%%\n", scale);
+ forwardmove[0] = forwardmove[0]*scale/100;
+ forwardmove[1] = forwardmove[1]*scale/100;
+ sidemove[0] = sidemove[0]*scale/100;
+ sidemove[1] = sidemove[1]*scale/100;
+ }
+
+ // init subsystems
+ DEH_printf("V_Init: allocate screens.\n");
+ V_Init ();
+
+ // Load configuration files before initialising other subsystems.
+ DEH_printf("M_LoadDefaults: Load system defaults.\n");
+ M_SetConfigFilenames("default.cfg", PROGRAM_PREFIX "doom.cfg");
+ D_BindVariables();
+ M_LoadDefaults();
+
+ // Save configuration at exit.
+ I_AtExit(M_SaveDefaults, false);
+
+ // Find main IWAD file and load it.
+ iwadfile = D_FindIWAD(IWAD_MASK_DOOM, &gamemission);
+
+ // None found?
+
+ if (iwadfile == NULL)
+ {
+ I_Error("Game mode indeterminate. No IWAD file was found. Try\n"
+ "specifying one with the '-iwad' command line parameter.\n");
+ }
+
+ modifiedgame = false;
+
+ DEH_printf("W_Init: Init WADfiles.\n");
+ D_AddFile(iwadfile);
+#if ORIGCODE
+ numiwadlumps = numlumps;
+#endif
+
+ W_CheckCorrectIWAD(doom);
+
+ // Now that we've loaded the IWAD, we can figure out what gamemission
+ // we're playing and which version of Vanilla Doom we need to emulate.
+ D_IdentifyVersion();
+ InitGameVersion();
+
+#if ORIGCODE
+ //!
+ // @category mod
+ //
+ // Disable automatic loading of Dehacked patches for certain
+ // IWAD files.
+ //
+ if (!M_ParmExists("-nodeh"))
+ {
+ // Some IWADs have dehacked patches that need to be loaded for
+ // them to be played properly.
+ LoadIwadDeh();
+ }
+#endif
+
+ // Doom 3: BFG Edition includes modified versions of the classic
+ // IWADs which can be identified by an additional DMENUPIC lump.
+ // Furthermore, the M_GDHIGH lumps have been modified in a way that
+ // makes them incompatible to Vanilla Doom and the modified version
+ // of doom2.wad is missing the TITLEPIC lump.
+ // We specifically check for DMENUPIC here, before PWADs have been
+ // loaded which could probably include a lump of that name.
+
+ if (W_CheckNumForName("dmenupic") >= 0)
+ {
+ printf("BFG Edition: Using workarounds as needed.\n");
+ bfgedition = true;
+
+ // BFG Edition changes the names of the secret levels to
+ // censor the Wolfenstein references. It also has an extra
+ // secret level (MAP33). In Vanilla Doom (meaning the DOS
+ // version), MAP33 overflows into the Plutonia level names
+ // array, so HUSTR_33 is actually PHUSTR_1.
+
+ DEH_AddStringReplacement(HUSTR_31, "level 31: idkfa");
+ DEH_AddStringReplacement(HUSTR_32, "level 32: keen");
+ DEH_AddStringReplacement(PHUSTR_1, "level 33: betray");
+
+ // The BFG edition doesn't have the "low detail" menu option (fair
+ // enough). But bizarrely, it reuses the M_GDHIGH patch as a label
+ // for the options menu (says "Fullscreen:"). Why the perpetrators
+ // couldn't just add a new graphic lump and had to reuse this one,
+ // I don't know.
+ //
+ // The end result is that M_GDHIGH is too wide and causes the game
+ // to crash. As a workaround to get a minimum level of support for
+ // the BFG edition IWADs, use the "ON"/"OFF" graphics instead.
+
+ DEH_AddStringReplacement("M_GDHIGH", "M_MSGON");
+ DEH_AddStringReplacement("M_GDLOW", "M_MSGOFF");
+ }
+
+#ifdef FEATURE_DEHACKED
+ // Load Dehacked patches specified on the command line with -deh.
+ // Note that there's a very careful and deliberate ordering to how
+ // Dehacked patches are loaded. The order we use is:
+ // 1. IWAD dehacked patches.
+ // 2. Command line dehacked patches specified with -deh.
+ // 3. PWAD dehacked patches in DEHACKED lumps.
+ DEH_ParseCommandLine();
+#endif
+
+ // Load PWAD files.
+ modifiedgame = W_ParseCommandLine();
+
+ // Debug:
+// W_PrintDirectory();
+
+ //!
+ // @arg
+ // @category demo
+ // @vanilla
+ //
+ // Play back the demo named demo.lmp.
+ //
+
+ p = M_CheckParmWithArgs ("-playdemo", 1);
+
+ if (!p)
+ {
+ //!
+ // @arg
+ // @category demo
+ // @vanilla
+ //
+ // Play back the demo named demo.lmp, determining the framerate
+ // of the screen.
+ //
+ p = M_CheckParmWithArgs("-timedemo", 1);
+
+ }
+
+ if (p)
+ {
+ // With Vanilla you have to specify the file without extension,
+ // but make that optional.
+ if (M_StringEndsWith(myargv[p + 1], ".lmp"))
+ {
+ M_StringCopy(file, myargv[p + 1], sizeof(file));
+ }
+ else
+ {
+ DEH_snprintf(file, sizeof(file), "%s.lmp", myargv[p+1]);
+ }
+
+ if (D_AddFile(file))
+ {
+ M_StringCopy(demolumpname, lumpinfo[numlumps - 1].name,
+ sizeof(demolumpname));
+ }
+ else
+ {
+ // If file failed to load, still continue trying to play
+ // the demo in the same way as Vanilla Doom. This makes
+ // tricks like "-playdemo demo1" possible.
+
+ M_StringCopy(demolumpname, myargv[p + 1], sizeof(demolumpname));
+ }
+
+ printf("Playing demo %s.\n", file);
+ }
+
+ I_AtExit((atexit_func_t) G_CheckDemoStatus, true);
+
+ // Generate the WAD hash table. Speed things up a bit.
+ W_GenerateHashTable();
+
+ // Load DEHACKED lumps from WAD files - but only if we give the right
+ // command line parameter.
+
+#if ORIGCODE
+ //!
+ // @category mod
+ //
+ // Load Dehacked patches from DEHACKED lumps contained in one of the
+ // loaded PWAD files.
+ //
+ if (M_ParmExists("-dehlump"))
+ {
+ int i, loaded = 0;
+
+ for (i = numiwadlumps; i < numlumps; ++i)
+ {
+ if (!strncmp(lumpinfo[i].name, "DEHACKED", 8))
+ {
+ DEH_LoadLump(i, false, false);
+ loaded++;
+ }
+ }
+
+ printf(" loaded %i DEHACKED lumps from PWAD files.\n", loaded);
+ }
+#endif
+
+ // Set the gamedescription string. This is only possible now that
+ // we've finished loading Dehacked patches.
+ D_SetGameDescription();
+
+#ifdef _WIN32
+ // In -cdrom mode, we write savegames to c:\doomdata as well as configs.
+ if (M_ParmExists("-cdrom"))
+ {
+ savegamedir = configdir;
+ }
+ else
+#endif
+ {
+ savegamedir = M_GetSaveGameDir(D_SaveGameIWADName(gamemission));
+ }
+
+ // Check for -file in shareware
+ if (modifiedgame)
+ {
+ // These are the lumps that will be checked in IWAD,
+ // if any one is not present, execution will be aborted.
+ char name[23][8]=
+ {
+ "e2m1","e2m2","e2m3","e2m4","e2m5","e2m6","e2m7","e2m8","e2m9",
+ "e3m1","e3m3","e3m3","e3m4","e3m5","e3m6","e3m7","e3m8","e3m9",
+ "dphoof","bfgga0","heada1","cybra1","spida1d1"
+ };
+ int i;
+
+ if ( gamemode == shareware)
+ I_Error(DEH_String("\nYou cannot -file with the shareware "
+ "version. Register!"));
+
+ // Check for fake IWAD with right name,
+ // but w/o all the lumps of the registered version.
+ if (gamemode == registered)
+ for (i = 0;i < 23; i++)
+ if (W_CheckNumForName(name[i])<0)
+ I_Error(DEH_String("\nThis is not the registered version."));
+ }
+
+ if (W_CheckNumForName("SS_START") >= 0
+ || W_CheckNumForName("FF_END") >= 0)
+ {
+ I_PrintDivider();
+ printf(" WARNING: The loaded WAD file contains modified sprites or\n"
+ " floor textures. You may want to use the '-merge' command\n"
+ " line option instead of '-file'.\n");
+ }
+
+ I_PrintStartupBanner(gamedescription);
+ PrintDehackedBanners();
+
+ // Freedoom's IWADs are Boom-compatible, which means they usually
+ // don't work in Vanilla (though FreeDM is okay). Show a warning
+ // message and give a link to the website.
+ if (W_CheckNumForName("FREEDOOM") >= 0 && W_CheckNumForName("FREEDM") < 0)
+ {
+ printf(" WARNING: You are playing using one of the Freedoom IWAD\n"
+ " files, which might not work in this port. See this page\n"
+ " for more information on how to play using Freedoom:\n"
+ " http://www.chocolate-doom.org/wiki/index.php/Freedoom\n");
+ I_PrintDivider();
+ }
+
+ DEH_printf("I_Init: Setting up machine state.\n");
+ I_CheckIsScreensaver();
+ I_InitTimer();
+ I_InitJoystick();
+ I_InitSound(true);
+ I_InitMusic();
+
+#ifdef FEATURE_MULTIPLAYER
+ printf ("NET_Init: Init network subsystem.\n");
+ NET_Init ();
+#endif
+
+ // Initial netgame startup. Connect to server etc.
+ D_ConnectNetGame();
+
+ // get skill / episode / map from parms
+ startskill = sk_medium;
+ startepisode = 1;
+ startmap = 1;
+ autostart = false;
+
+ //!
+ // @arg
+ // @vanilla
+ //
+ // Set the game skill, 1-5 (1: easiest, 5: hardest). A skill of
+ // 0 disables all monsters.
+ //
+
+ p = M_CheckParmWithArgs("-skill", 1);
+
+ if (p)
+ {
+ startskill = myargv[p+1][0]-'1';
+ autostart = true;
+ }
+
+ //!
+ // @arg
+ // @vanilla
+ //
+ // Start playing on episode n (1-4)
+ //
+
+ p = M_CheckParmWithArgs("-episode", 1);
+
+ if (p)
+ {
+ startepisode = myargv[p+1][0]-'0';
+ startmap = 1;
+ autostart = true;
+ }
+
+ timelimit = 0;
+
+ //!
+ // @arg
+ // @category net
+ // @vanilla
+ //
+ // For multiplayer games: exit each level after n minutes.
+ //
+
+ p = M_CheckParmWithArgs("-timer", 1);
+
+ if (p)
+ {
+ timelimit = atoi(myargv[p+1]);
+ }
+
+ //!
+ // @category net
+ // @vanilla
+ //
+ // Austin Virtual Gaming: end levels after 20 minutes.
+ //
+
+ p = M_CheckParm ("-avg");
+
+ if (p)
+ {
+ timelimit = 20;
+ }
+
+ //!
+ // @arg [ | ]
+ // @vanilla
+ //
+ // Start a game immediately, warping to ExMy (Doom 1) or MAPxy
+ // (Doom 2)
+ //
+
+ p = M_CheckParmWithArgs("-warp", 1);
+
+ if (p)
+ {
+ if (gamemode == commercial)
+ startmap = atoi (myargv[p+1]);
+ else
+ {
+ startepisode = myargv[p+1][0]-'0';
+
+ if (p + 2 < myargc)
+ {
+ startmap = myargv[p+2][0]-'0';
+ }
+ else
+ {
+ startmap = 1;
+ }
+ }
+ autostart = true;
+ }
+
+ // Undocumented:
+ // Invoked by setup to test the controls.
+
+ p = M_CheckParm("-testcontrols");
+
+ if (p > 0)
+ {
+ startepisode = 1;
+ startmap = 1;
+ autostart = true;
+ testcontrols = true;
+ }
+
+ // Check for load game parameter
+ // We do this here and save the slot number, so that the network code
+ // can override it or send the load slot to other players.
+
+ //!
+ // @arg
+ // @vanilla
+ //
+ // Load the game in slot s.
+ //
+
+ p = M_CheckParmWithArgs("-loadgame", 1);
+
+ if (p)
+ {
+ startloadgame = atoi(myargv[p+1]);
+ }
+ else
+ {
+ // Not loading a game
+ startloadgame = -1;
+ }
+
+ DEH_printf("M_Init: Init miscellaneous info.\n");
+ M_Init ();
+
+ DEH_printf("R_Init: Init DOOM refresh daemon - ");
+ R_Init ();
+
+ DEH_printf("\nP_Init: Init Playloop state.\n");
+ P_Init ();
+
+ DEH_printf("S_Init: Setting up sound.\n");
+ S_Init (sfxVolume * 8, musicVolume * 8);
+
+ DEH_printf("D_CheckNetGame: Checking network game status.\n");
+ D_CheckNetGame ();
+
+ PrintGameVersion();
+
+ DEH_printf("HU_Init: Setting up heads up display.\n");
+ HU_Init ();
+
+ DEH_printf("ST_Init: Init status bar.\n");
+ ST_Init ();
+
+ // If Doom II without a MAP01 lump, this is a store demo.
+ // Moved this here so that MAP01 isn't constantly looked up
+ // in the main loop.
+
+ if (gamemode == commercial && W_CheckNumForName("map01") < 0)
+ storedemo = true;
+
+ if (M_CheckParmWithArgs("-statdump", 1))
+ {
+ I_AtExit(StatDump, true);
+ DEH_printf("External statistics registered.\n");
+ }
+
+ //!
+ // @arg
+ // @category demo
+ // @vanilla
+ //
+ // Record a demo named x.lmp.
+ //
+
+ p = M_CheckParmWithArgs("-record", 1);
+
+ if (p)
+ {
+ G_RecordDemo (myargv[p+1]);
+ autostart = true;
+ }
+
+ p = M_CheckParmWithArgs("-playdemo", 1);
+ if (p)
+ {
+ singledemo = true; // quit after one demo
+ G_DeferedPlayDemo (demolumpname);
+ D_DoomLoop ();
+ return;
+ }
+
+ p = M_CheckParmWithArgs("-timedemo", 1);
+ if (p)
+ {
+ G_TimeDemo (demolumpname);
+ D_DoomLoop ();
+ return;
+ }
+
+ if (startloadgame >= 0)
+ {
+ M_StringCopy(file, P_SaveGameFile(startloadgame), sizeof(file));
+ G_LoadGame(file);
+ }
+
+ if (gameaction != ga_loadgame )
+ {
+ if (autostart || netgame)
+ G_InitNew (startskill, startepisode, startmap);
+ else
+ D_StartTitle (); // start up intro loop
+ }
+
+ D_DoomLoop ();
+}
+
diff --git a/firmware_p4/components/Applications/doom/d_main.h b/firmware_p4/components/Applications/doom/d_main.h
new file mode 100644
index 000000000..0fe9547b3
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_main.h
@@ -0,0 +1,50 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// System specific interface stuff.
+//
+
+
+#ifndef __D_MAIN__
+#define __D_MAIN__
+
+#include "doomdef.h"
+
+
+
+
+// Read events from all input devices
+
+void D_ProcessEvents (void);
+
+
+//
+// BASE LEVEL
+//
+void D_PageTicker (void);
+void D_PageDrawer (void);
+void D_AdvanceDemo (void);
+void D_DoAdvanceDemo (void);
+void D_StartTitle (void);
+
+//
+// GLOBAL VARIABLES
+//
+
+extern gameaction_t gameaction;
+
+
+#endif
+
diff --git a/firmware_p4/components/Applications/doom/d_mode.c b/firmware_p4/components/Applications/doom/d_mode.c
new file mode 100644
index 000000000..afd84acb3
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_mode.c
@@ -0,0 +1,209 @@
+//
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+//
+// DESCRIPTION:
+// Functions and definitions relating to the game type and operational
+// mode.
+//
+
+#include "doomtype.h"
+#include "d_mode.h"
+
+// Valid game mode/mission combinations, with the number of
+// episodes/maps for each.
+
+static struct
+{
+ GameMission_t mission;
+ GameMode_t mode;
+ int episode;
+ int map;
+} valid_modes[] = {
+ { pack_chex, shareware, 1, 5 },
+ { doom, shareware, 1, 9 },
+ { doom, registered, 3, 9 },
+ { doom, retail, 4, 9 },
+ { doom2, commercial, 1, 32 },
+ { pack_tnt, commercial, 1, 32 },
+ { pack_plut, commercial, 1, 32 },
+ { pack_hacx, commercial, 1, 32 },
+ { heretic, shareware, 1, 9 },
+ { heretic, registered, 3, 9 },
+ { heretic, retail, 5, 9 },
+ { hexen, commercial, 1, 60 },
+ { strife, commercial, 1, 34 },
+};
+
+// Check that a gamemode+gamemission received over the network is valid.
+
+boolean D_ValidGameMode(GameMission_t mission, GameMode_t mode)
+{
+ int i;
+
+ for (i=0; i= 1 && map <= 3;
+ }
+ else if (mode == registered && episode == 4)
+ {
+ return map == 1;
+ }
+ }
+
+ // Find the table entry for this mission/mode combination.
+
+ for (i=0; i= 1 && episode <= valid_modes[i].episode
+ && map >= 1 && map <= valid_modes[i].map;
+ }
+ }
+
+ // Unknown mode/mission combination
+
+ return false;
+}
+
+// Get the number of valid episodes for the specified mission/mode.
+
+int D_GetNumEpisodes(GameMission_t mission, GameMode_t mode)
+{
+ int episode;
+
+ episode = 1;
+
+ while (D_ValidEpisodeMap(mission, mode, episode, 1))
+ {
+ ++episode;
+ }
+
+ return episode - 1;
+}
+
+// Table of valid versions
+
+static struct {
+ GameMission_t mission;
+ GameVersion_t version;
+} valid_versions[] = {
+ { doom, exe_doom_1_9 },
+ { doom, exe_hacx },
+ { doom, exe_ultimate },
+ { doom, exe_final },
+ { doom, exe_final2 },
+ { doom, exe_chex },
+ { heretic, exe_heretic_1_3 },
+ { hexen, exe_hexen_1_1 },
+ { strife, exe_strife_1_2 },
+ { strife, exe_strife_1_31 },
+};
+
+boolean D_ValidGameVersion(GameMission_t mission, GameVersion_t version)
+{
+ int i;
+
+ // All Doom variants can use the Doom versions.
+
+ if (mission == doom2 || mission == pack_plut || mission == pack_tnt
+ || mission == pack_hacx || mission == pack_chex)
+ {
+ mission = doom;
+ }
+
+ for (i=0; i
+
+#include "doomfeatures.h"
+
+#include "d_main.h"
+#include "m_argv.h"
+#include "m_menu.h"
+#include "m_misc.h"
+#include "i_system.h"
+#include "i_timer.h"
+#include "i_video.h"
+#include "g_game.h"
+#include "doomdef.h"
+#include "doomstat.h"
+#include "w_checksum.h"
+#include "w_wad.h"
+
+#include "deh_main.h"
+
+#include "d_loop.h"
+
+ticcmd_t *netcmds;
+
+// Called when a player leaves the game
+
+static void PlayerQuitGame(player_t *player)
+{
+ static char exitmsg[80];
+ unsigned int player_num;
+
+ player_num = player - players;
+
+ // Do this the same way as Vanilla Doom does, to allow dehacked
+ // replacements of this message
+
+ M_StringCopy(exitmsg, DEH_String("Player 1 left the game"),
+ sizeof(exitmsg));
+
+ exitmsg[7] += player_num;
+
+ playeringame[player_num] = false;
+ players[consoleplayer].message = exitmsg;
+
+ // TODO: check if it is sensible to do this:
+
+ if (demorecording)
+ {
+ G_CheckDemoStatus ();
+ }
+}
+
+static void RunTic(ticcmd_t *cmds, boolean *ingame)
+{
+ extern boolean advancedemo;
+ unsigned int i;
+
+ // Check for player quits.
+
+ for (i = 0; i < MAXPLAYERS; ++i)
+ {
+ if (!demoplayback && playeringame[i] && !ingame[i])
+ {
+ PlayerQuitGame(&players[i]);
+ }
+ }
+
+ netcmds = cmds;
+
+ // check that there are players in the game. if not, we cannot
+ // run a tic.
+
+ if (advancedemo)
+ D_DoAdvanceDemo ();
+
+ G_Ticker ();
+}
+
+static loop_interface_t doom_loop_interface = {
+ D_ProcessEvents,
+ G_BuildTiccmd,
+ RunTic,
+ M_Ticker
+};
+
+
+// Load game settings from the specified structure and
+// set global variables.
+
+static void LoadGameSettings(net_gamesettings_t *settings)
+{
+ unsigned int i;
+
+ deathmatch = settings->deathmatch;
+ startepisode = settings->episode;
+ startmap = settings->map;
+ startskill = settings->skill;
+ startloadgame = settings->loadgame;
+ lowres_turn = settings->lowres_turn;
+ nomonsters = settings->nomonsters;
+ fastparm = settings->fast_monsters;
+ respawnparm = settings->respawn_monsters;
+ timelimit = settings->timelimit;
+ consoleplayer = settings->consoleplayer;
+
+ if (lowres_turn)
+ {
+ printf("NOTE: Turning resolution is reduced; this is probably "
+ "because there is a client recording a Vanilla demo.\n");
+ }
+
+ for (i = 0; i < MAXPLAYERS; ++i)
+ {
+ playeringame[i] = i < settings->num_players;
+ }
+}
+
+// Save the game settings from global variables to the specified
+// game settings structure.
+
+static void SaveGameSettings(net_gamesettings_t *settings)
+{
+ // Fill in game settings structure with appropriate parameters
+ // for the new game
+
+ settings->deathmatch = deathmatch;
+ settings->episode = startepisode;
+ settings->map = startmap;
+ settings->skill = startskill;
+ settings->loadgame = startloadgame;
+ settings->gameversion = gameversion;
+ settings->nomonsters = nomonsters;
+ settings->fast_monsters = fastparm;
+ settings->respawn_monsters = respawnparm;
+ settings->timelimit = timelimit;
+
+ settings->lowres_turn = M_CheckParm("-record") > 0
+ && M_CheckParm("-longtics") == 0;
+}
+
+static void InitConnectData(net_connect_data_t *connect_data)
+{
+ connect_data->max_players = MAXPLAYERS;
+ connect_data->drone = false;
+
+ //!
+ // @category net
+ //
+ // Run as the left screen in three screen mode.
+ //
+
+ if (M_CheckParm("-left") > 0)
+ {
+ viewangleoffset = ANG90;
+ connect_data->drone = true;
+ }
+
+ //!
+ // @category net
+ //
+ // Run as the right screen in three screen mode.
+ //
+
+ if (M_CheckParm("-right") > 0)
+ {
+ viewangleoffset = ANG270;
+ connect_data->drone = true;
+ }
+
+ //
+ // Connect data
+ //
+
+ // Game type fields:
+
+ connect_data->gamemode = gamemode;
+ connect_data->gamemission = gamemission;
+
+ // Are we recording a demo? Possibly set lowres turn mode
+
+ connect_data->lowres_turn = M_CheckParm("-record") > 0
+ && M_CheckParm("-longtics") == 0;
+
+ // Read checksums of our WAD directory and dehacked information
+
+ W_Checksum(connect_data->wad_sha1sum);
+
+#if ORIGCODE
+ DEH_Checksum(connect_data->deh_sha1sum);
+#endif
+
+ // Are we playing with the Freedoom IWAD?
+
+ connect_data->is_freedoom = W_CheckNumForName("FREEDOOM") >= 0;
+}
+
+void D_ConnectNetGame(void)
+{
+ net_connect_data_t connect_data;
+
+ InitConnectData(&connect_data);
+ netgame = D_InitNetGame(&connect_data);
+
+ //!
+ // @category net
+ //
+ // Start the game playing as though in a netgame with a single
+ // player. This can also be used to play back single player netgame
+ // demos.
+ //
+
+ if (M_CheckParm("-solo-net") > 0)
+ {
+ netgame = true;
+ }
+}
+
+//
+// D_CheckNetGame
+// Works out player numbers among the net participants
+//
+void D_CheckNetGame (void)
+{
+ net_gamesettings_t settings;
+
+ if (netgame)
+ {
+ autostart = true;
+ }
+
+ D_RegisterLoopCallbacks(&doom_loop_interface);
+
+ SaveGameSettings(&settings);
+ D_StartNetGame(&settings, NULL);
+ LoadGameSettings(&settings);
+
+ DEH_printf("startskill %i deathmatch: %i startmap: %i startepisode: %i\n",
+ startskill, deathmatch, startmap, startepisode);
+
+ DEH_printf("player %i of %i (%i nodes)\n",
+ consoleplayer+1, settings.num_players, settings.num_players);
+
+ // Show players here; the server might have specified a time limit
+
+ if (timelimit > 0 && deathmatch)
+ {
+ // Gross hack to work like Vanilla:
+
+ if (timelimit == 20 && M_CheckParm("-avg"))
+ {
+ DEH_printf("Austin Virtual Gaming: Levels will end "
+ "after 20 minutes\n");
+ }
+ else
+ {
+ DEH_printf("Levels will end after %d minute", timelimit);
+ if (timelimit > 1)
+ printf("s");
+ printf(".\n");
+ }
+ }
+}
+
diff --git a/firmware_p4/components/Applications/doom/d_player.h b/firmware_p4/components/Applications/doom/d_player.h
new file mode 100644
index 000000000..a72c2f5f1
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_player.h
@@ -0,0 +1,209 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+//
+//
+
+
+#ifndef __D_PLAYER__
+#define __D_PLAYER__
+
+
+// The player data structure depends on a number
+// of other structs: items (internal inventory),
+// animation states (closely tied to the sprites
+// used to represent them, unfortunately).
+#include "d_items.h"
+#include "p_pspr.h"
+
+// In addition, the player is just a special
+// case of the generic moving object/actor.
+#include "p_mobj.h"
+
+// Finally, for odd reasons, the player input
+// is buffered within the player data struct,
+// as commands per game tick.
+#include "d_ticcmd.h"
+
+#include "net_defs.h"
+
+
+
+
+//
+// Player states.
+//
+typedef enum
+{
+ // Playing or camping.
+ PST_LIVE,
+ // Dead on the ground, view follows killer.
+ PST_DEAD,
+ // Ready to restart/respawn???
+ PST_REBORN
+
+} playerstate_t;
+
+
+//
+// Player internal flags, for cheats and debug.
+//
+typedef enum
+{
+ // No clipping, walk through barriers.
+ CF_NOCLIP = 1,
+ // No damage, no health loss.
+ CF_GODMODE = 2,
+ // Not really a cheat, just a debug aid.
+ CF_NOMOMENTUM = 4
+
+} cheat_t;
+
+
+//
+// Extended player object info: player_t
+//
+typedef struct player_s
+{
+ mobj_t* mo;
+ playerstate_t playerstate;
+ ticcmd_t cmd;
+
+ // Determine POV,
+ // including viewpoint bobbing during movement.
+ // Focal origin above r.z
+ fixed_t viewz;
+ // Base height above floor for viewz.
+ fixed_t viewheight;
+ // Bob/squat speed.
+ fixed_t deltaviewheight;
+ // bounded/scaled total momentum.
+ fixed_t bob;
+
+ // This is only used between levels,
+ // mo->health is used during levels.
+ int health;
+ int armorpoints;
+ // Armor type is 0-2.
+ int armortype;
+
+ // Power ups. invinc and invis are tic counters.
+ int powers[NUMPOWERS];
+ boolean cards[NUMCARDS];
+ boolean backpack;
+
+ // Frags, kills of other players.
+ int frags[MAXPLAYERS];
+ weapontype_t readyweapon;
+
+ // Is wp_nochange if not changing.
+ weapontype_t pendingweapon;
+
+ boolean weaponowned[NUMWEAPONS];
+ int ammo[NUMAMMO];
+ int maxammo[NUMAMMO];
+
+ // True if button down last tic.
+ int attackdown;
+ int usedown;
+
+ // Bit flags, for cheats and debug.
+ // See cheat_t, above.
+ int cheats;
+
+ // Refired shots are less accurate.
+ int refire;
+
+ // For intermission stats.
+ int killcount;
+ int itemcount;
+ int secretcount;
+
+ // Hint messages.
+ char* message;
+
+ // For screen flashing (red or bright).
+ int damagecount;
+ int bonuscount;
+
+ // Who did damage (NULL for floors/ceilings).
+ mobj_t* attacker;
+
+ // So gun flashes light up areas.
+ int extralight;
+
+ // Current PLAYPAL, ???
+ // can be set to REDCOLORMAP for pain, etc.
+ int fixedcolormap;
+
+ // Player skin colorshift,
+ // 0-3 for which color to draw player.
+ int colormap;
+
+ // Overlay view sprites (gun, etc).
+ pspdef_t psprites[NUMPSPRITES];
+
+ // True if secret level has been done.
+ boolean didsecret;
+
+} player_t;
+
+
+//
+// INTERMISSION
+// Structure passed e.g. to WI_Start(wb)
+//
+typedef struct
+{
+ boolean in; // whether the player is in game
+
+ // Player stats, kills, collected items etc.
+ int skills;
+ int sitems;
+ int ssecret;
+ int stime;
+ int frags[4];
+ int score; // current score on entry, modified on return
+
+} wbplayerstruct_t;
+
+typedef struct
+{
+ int epsd; // episode # (0-2)
+
+ // if true, splash the secret level
+ boolean didsecret;
+
+ // previous and next levels, origin 0
+ int last;
+ int next;
+
+ int maxkills;
+ int maxitems;
+ int maxsecret;
+ int maxfrags;
+
+ // the par time
+ int partime;
+
+ // index of this player in game
+ int pnum;
+
+ wbplayerstruct_t plyr[MAXPLAYERS];
+
+} wbstartstruct_t;
+
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/d_textur.h b/firmware_p4/components/Applications/doom/d_textur.h
new file mode 100644
index 000000000..1afe040eb
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_textur.h
@@ -0,0 +1,43 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Typedefs related to to textures etc.,
+// isolated here to make it easier separating modules.
+//
+
+
+#ifndef __D_TEXTUR__
+#define __D_TEXTUR__
+
+#include "doomtype.h"
+
+
+
+
+//
+// Flats?
+//
+// a pic is an unmasked block of pixels
+typedef struct
+{
+ byte width;
+ byte height;
+ byte data;
+} pic_t;
+
+
+
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/d_think.h b/firmware_p4/components/Applications/doom/d_think.h
new file mode 100644
index 000000000..0966ad960
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_think.h
@@ -0,0 +1,68 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// MapObj data. Map Objects or mobjs are actors, entities,
+// thinker, take-your-pick... anything that moves, acts, or
+// suffers state changes of more or less violent nature.
+//
+
+
+#ifndef __D_THINK__
+#define __D_THINK__
+
+
+
+
+
+//
+// Experimental stuff.
+// To compile this as "ANSI C with classes"
+// we will need to handle the various
+// action functions cleanly.
+//
+typedef void (*actionf_v)();
+typedef void (*actionf_p1)( void* );
+typedef void (*actionf_p2)( void*, void* );
+
+typedef union
+{
+ actionf_v acv;
+ actionf_p1 acp1;
+ actionf_p2 acp2;
+
+} actionf_t;
+
+
+
+
+
+// Historically, "think_t" is yet another
+// function pointer to a routine to handle
+// an actor.
+typedef actionf_t think_t;
+
+
+// Doubly linked list of actors.
+typedef struct thinker_s
+{
+ struct thinker_s* prev;
+ struct thinker_s* next;
+ think_t function;
+
+} thinker_t;
+
+
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/d_ticcmd.h b/firmware_p4/components/Applications/doom/d_ticcmd.h
new file mode 100644
index 000000000..daf0da318
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/d_ticcmd.h
@@ -0,0 +1,56 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// System specific interface stuff.
+//
+
+
+#ifndef __D_TICCMD__
+#define __D_TICCMD__
+
+#include "doomtype.h"
+
+
+// The data sampled per tick (single player)
+// and transmitted to other peers (multiplayer).
+// Mainly movements/button commands per game tick,
+// plus a checksum for internal state consistency.
+
+typedef struct
+{
+ signed char forwardmove; // *2048 for move
+ signed char sidemove; // *2048 for move
+ short angleturn; // <<16 for angle delta
+ byte chatchar;
+ byte buttons;
+ // villsa [STRIFE] according to the asm,
+ // consistancy is a short, not a byte
+ byte consistancy; // checks for net game
+
+ // villsa - Strife specific:
+
+ byte buttons2;
+ int inventory;
+
+ // Heretic/Hexen specific:
+
+ byte lookfly; // look/fly up/down/centering
+ byte arti; // artitype_t to use
+} ticcmd_t;
+
+
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/deh_main.h b/firmware_p4/components/Applications/doom/deh_main.h
new file mode 100644
index 000000000..10ac2360e
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/deh_main.h
@@ -0,0 +1,48 @@
+//
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+//
+// Dehacked entrypoint and common code
+//
+
+#ifndef DEH_MAIN_H
+#define DEH_MAIN_H
+
+#include "doomtype.h"
+#include "doomfeatures.h"
+#include "deh_str.h"
+#include "sha1.h"
+
+// These are the limits that dehacked uses (from dheinit.h in the dehacked
+// source). If these limits are exceeded, it does not generate an error, but
+// a warning is displayed.
+
+#define DEH_VANILLA_NUMSTATES 966
+#define DEH_VANILLA_NUMSFX 107
+
+void DEH_ParseCommandLine(void);
+int DEH_LoadFile(char *filename);
+int DEH_LoadLump(int lumpnum, boolean allow_long, boolean allow_error);
+int DEH_LoadLumpByName(char *name, boolean allow_long, boolean allow_error);
+
+boolean DEH_ParseAssignment(char *line, char **variable_name, char **value);
+
+void DEH_Checksum(sha1_digest_t digest);
+
+extern boolean deh_allow_extended_strings;
+extern boolean deh_allow_long_strings;
+extern boolean deh_allow_long_cheats;
+extern boolean deh_apply_cheats;
+
+#endif /* #ifndef DEH_MAIN_H */
+
diff --git a/firmware_p4/components/Applications/doom/deh_misc.h b/firmware_p4/components/Applications/doom/deh_misc.h
new file mode 100644
index 000000000..319a1451a
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/deh_misc.h
@@ -0,0 +1,83 @@
+//
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+//
+// Parses "Misc" sections in dehacked files
+//
+
+#ifndef DEH_MISC_H
+#define DEH_MISC_H
+
+#include "doomfeatures.h"
+
+#define DEH_DEFAULT_INITIAL_HEALTH 100
+#define DEH_DEFAULT_INITIAL_BULLETS 50
+#define DEH_DEFAULT_MAX_HEALTH 200
+#define DEH_DEFAULT_MAX_ARMOR 200
+#define DEH_DEFAULT_GREEN_ARMOR_CLASS 1
+#define DEH_DEFAULT_BLUE_ARMOR_CLASS 2
+#define DEH_DEFAULT_MAX_SOULSPHERE 200
+#define DEH_DEFAULT_SOULSPHERE_HEALTH 100
+#define DEH_DEFAULT_MEGASPHERE_HEALTH 200
+#define DEH_DEFAULT_GOD_MODE_HEALTH 100
+#define DEH_DEFAULT_IDFA_ARMOR 200
+#define DEH_DEFAULT_IDFA_ARMOR_CLASS 2
+#define DEH_DEFAULT_IDKFA_ARMOR 200
+#define DEH_DEFAULT_IDKFA_ARMOR_CLASS 2
+#define DEH_DEFAULT_BFG_CELLS_PER_SHOT 40
+#define DEH_DEFAULT_SPECIES_INFIGHTING 0
+
+#ifdef FEATURE_DEHACKED
+
+extern int deh_initial_health;
+extern int deh_initial_bullets;
+extern int deh_max_health;
+extern int deh_max_armor;
+extern int deh_green_armor_class;
+extern int deh_blue_armor_class;
+extern int deh_max_soulsphere;
+extern int deh_soulsphere_health;
+extern int deh_megasphere_health;
+extern int deh_god_mode_health;
+extern int deh_idfa_armor;
+extern int deh_idfa_armor_class;
+extern int deh_idkfa_armor;
+extern int deh_idkfa_armor_class;
+extern int deh_bfg_cells_per_shot;
+extern int deh_species_infighting;
+
+#else
+
+// If dehacked is disabled, hard coded values
+
+#define deh_initial_health DEH_DEFAULT_INITIAL_HEALTH
+#define deh_initial_bullets DEH_DEFAULT_INITIAL_BULLETS
+#define deh_max_health DEH_DEFAULT_MAX_HEALTH
+#define deh_max_armor DEH_DEFAULT_MAX_ARMOR
+#define deh_green_armor_class DEH_DEFAULT_GREEN_ARMOR_CLASS
+#define deh_blue_armor_class DEH_DEFAULT_BLUE_ARMOR_CLASS
+#define deh_max_soulsphere DEH_DEFAULT_MAX_SOULSPHERE
+#define deh_soulsphere_health DEH_DEFAULT_SOULSPHERE_HEALTH
+#define deh_megasphere_health DEH_DEFAULT_MEGASPHERE_HEALTH
+#define deh_god_mode_health DEH_DEFAULT_GOD_MODE_HEALTH
+#define deh_idfa_armor DEH_DEFAULT_IDFA_ARMOR
+#define deh_idfa_armor_class DEH_DEFAULT_IDFA_ARMOR_CLASS
+#define deh_idkfa_armor DEH_DEFAULT_IDKFA_ARMOR
+#define deh_idkfa_armor_class DEH_DEFAULT_IDKFA_ARMOR_CLASS
+#define deh_bfg_cells_per_shot DEH_DEFAULT_BFG_CELLS_PER_SHOT
+#define deh_species_infighting DEH_DEFAULT_SPECIES_INFIGHTING
+
+#endif
+
+#endif /* #ifndef DEH_MISC_H */
+
diff --git a/firmware_p4/components/Applications/doom/deh_str.h b/firmware_p4/components/Applications/doom/deh_str.h
new file mode 100644
index 000000000..cdecaf0e5
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/deh_str.h
@@ -0,0 +1,47 @@
+//
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+//
+// Dehacked string replacements
+//
+
+#ifndef DEH_STR_H
+#define DEH_STR_H
+
+#include
+
+#include "doomfeatures.h"
+
+// Used to do dehacked text substitutions throughout the program
+
+#ifdef FEATURE_DEHACKED
+
+char *DEH_String(char *s);
+void DEH_printf(char *fmt, ...);
+void DEH_fprintf(FILE *fstream, char *fmt, ...);
+void DEH_snprintf(char *buffer, size_t len, char *fmt, ...);
+void DEH_AddStringReplacement(char *from_text, char *to_text);
+
+
+#else
+
+#define DEH_String(x) (x)
+#define DEH_printf printf
+#define DEH_fprintf fprintf
+#define DEH_snprintf snprintf
+#define DEH_AddStringReplacement(x, y)
+
+#endif
+
+#endif /* #ifndef DEH_STR_H */
+
diff --git a/firmware_p4/components/Applications/doom/doom.h b/firmware_p4/components/Applications/doom/doom.h
new file mode 100644
index 000000000..3c0d0be74
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/doom.h
@@ -0,0 +1,42 @@
+/*
+ * doom.h
+ *
+ * Created on: 18.02.2015
+ * Author: Florian
+ */
+
+
+#ifndef SRC_CHOCDOOM_DOOM_H_
+#define SRC_CHOCDOOM_DOOM_H_
+
+/*---------------------------------------------------------------------*
+ * additional includes *
+ *---------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------*
+ * global definitions *
+ *---------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------*
+ * type declarations *
+ *---------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------*
+ * function prototypes *
+ *---------------------------------------------------------------------*/
+
+void D_DoomMain (void);
+
+/*---------------------------------------------------------------------*
+ * global data *
+ *---------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------*
+ * inline functions and function-like macros *
+ *---------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------*
+ * eof *
+ *---------------------------------------------------------------------*/
+
+#endif /* SRC_CHOCDOOM_DOOM_H_ */
diff --git a/firmware_p4/components/Applications/doom/doom_sound_highboy.c b/firmware_p4/components/Applications/doom/doom_sound_highboy.c
new file mode 100644
index 000000000..fd1c52572
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/doom_sound_highboy.c
@@ -0,0 +1,158 @@
+// HighBoy DOOM sound backend (replaces the SDL_mixer module). Provides the
+// DG_sound_module (SFX) + DG_music_module (no-op) that i_sound.c references when
+// FEATURE_SOUND is defined. SFX are DMX lumps (8-bit unsigned PCM); a mixer task
+// sums the active channels and streams mono 16-bit PCM to the NS4168 via I2S.
+// Music (MIDI/MUS) is not synthesised — the music module is a silent stub.
+
+#include
+#include
+#include
+
+#include "doomtype.h"
+#include "i_sound.h"
+#include "w_wad.h"
+#include "z_zone.h"
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/idf_additions.h"
+#include "esp_heap_caps.h"
+#include "esp_log.h"
+#include "audio_i2s.h"
+
+#define ARRLEN(a) ((int)(sizeof(a) / sizeof((a)[0])))
+#define NUM_CH 16 // matches DOOM's max sfx channels
+#define OUT_RATE 11025 // DOOM sfx native rate; per-channel resample handles others
+
+typedef struct {
+ const uint8_t *pcm; // 8-bit unsigned PCM (into the cached WAD lump)
+ uint32_t len; // sample count
+ volatile uint32_t pos; // 16.16 fixed-point read position
+ uint32_t step; // 16.16 increment = src_rate / OUT_RATE
+ volatile int vol; // 0..127
+ volatile bool active;
+} sfxchan_t;
+
+static sfxchan_t s_ch[NUM_CH];
+static bool s_use_prefix;
+static bool s_started;
+
+// Config vars that i_sound.c's I_BindSoundVariables() references (were defined
+// in the excluded i_sdlsound.c). We don't use libsamplerate, but must provide them.
+int use_libsamplerate = 0;
+float libsamplerate_scale = 0.65f;
+
+static snddevice_t sfx_devices[] = {
+ SNDDEVICE_SB, SNDDEVICE_PAS, SNDDEVICE_GUS,
+ SNDDEVICE_WAVEBLASTER, SNDDEVICE_SOUNDCANVAS, SNDDEVICE_AWE32,
+};
+
+static void mixer_task(void *arg) {
+ (void)arg;
+ if (audio_i2s_stream_start(OUT_RATE) != ESP_OK) {
+ ESP_LOGE("DOOM_SND", "stream start failed — no sound");
+ for (;;) vTaskDelay(pdMS_TO_TICKS(1000));
+ }
+ const int N = 512;
+ int16_t *out = heap_caps_malloc(N * sizeof(int16_t), MALLOC_CAP_SPIRAM);
+ if (!out) { for (;;) vTaskDelay(pdMS_TO_TICKS(1000)); }
+ for (;;) {
+ for (int i = 0; i < N; i++) {
+ int acc = 0;
+ for (int c = 0; c < NUM_CH; c++) {
+ sfxchan_t *ch = &s_ch[c];
+ if (!ch->active) continue;
+ uint32_t idx = ch->pos >> 16;
+ if (idx >= ch->len) { ch->active = false; continue; }
+ int s = (int)ch->pcm[idx] - 128; // 8-bit unsigned -> signed
+ acc += s * ch->vol; // vol 0..127
+ ch->pos += ch->step;
+ }
+ if (acc > 32767) acc = 32767;
+ else if (acc < -32768) acc = -32768;
+ out[i] = (int16_t)acc;
+ }
+ audio_i2s_stream_write(out, N);
+ }
+}
+
+static boolean I_HB_InitSound(boolean use_sfx_prefix) {
+ s_use_prefix = use_sfx_prefix;
+ memset(s_ch, 0, sizeof(s_ch));
+ if (!s_started) {
+ xTaskCreatePinnedToCoreWithCaps(mixer_task, "doom_snd", 4096, NULL, 6, NULL, 0,
+ MALLOC_CAP_SPIRAM);
+ s_started = true;
+ }
+ return true;
+}
+static void I_HB_ShutdownSound(void) {}
+
+static int I_HB_GetSfxLumpNum(sfxinfo_t *sfx) {
+ char nm[16];
+ if (s_use_prefix) snprintf(nm, sizeof(nm), "ds%s", sfx->name);
+ else snprintf(nm, sizeof(nm), "%s", sfx->name);
+ return W_CheckNumForName(nm);
+}
+
+static int I_HB_StartSound(sfxinfo_t *sfx, int channel, int vol, int sep) {
+ (void)sep;
+ if (channel < 0 || channel >= NUM_CH) return channel;
+ int lump = sfx->lumpnum;
+ if (lump < 0) lump = I_HB_GetSfxLumpNum(sfx);
+ if (lump < 0) return channel;
+ const uint8_t *data = W_CacheLumpNum(lump, PU_STATIC);
+ int lumplen = W_LumpLength(lump);
+ if (!data || lumplen < 8) return channel;
+ uint16_t rate = (uint16_t)(data[2] | (data[3] << 8));
+ uint32_t plen = (uint32_t)data[4] | ((uint32_t)data[5] << 8) |
+ ((uint32_t)data[6] << 16) | ((uint32_t)data[7] << 24);
+ if (rate == 0) rate = OUT_RATE;
+ if (plen + 8 > (uint32_t)lumplen) plen = (uint32_t)lumplen - 8;
+
+ s_ch[channel].active = false; // retire any old sound first
+ s_ch[channel].pcm = data + 8; // skip 8-byte DMX header
+ s_ch[channel].len = plen;
+ s_ch[channel].pos = 0;
+ s_ch[channel].step = (uint32_t)(((uint64_t)rate << 16) / OUT_RATE);
+ s_ch[channel].vol = vol;
+ s_ch[channel].active = true;
+ return channel;
+}
+static void I_HB_StopSound(int ch) { if (ch >= 0 && ch < NUM_CH) s_ch[ch].active = false; }
+static boolean I_HB_SoundIsPlaying(int ch) { return (ch >= 0 && ch < NUM_CH) && s_ch[ch].active; }
+static void I_HB_UpdateSoundParams(int ch, int vol, int sep) {
+ (void)sep;
+ if (ch >= 0 && ch < NUM_CH) s_ch[ch].vol = vol;
+}
+static void I_HB_Update(void) {}
+static void I_HB_CacheSounds(sfxinfo_t *sounds, int num) { (void)sounds; (void)num; }
+
+sound_module_t DG_sound_module = {
+ sfx_devices, ARRLEN(sfx_devices),
+ I_HB_InitSound, I_HB_ShutdownSound, I_HB_GetSfxLumpNum, I_HB_Update,
+ I_HB_UpdateSoundParams, I_HB_StartSound, I_HB_StopSound, I_HB_SoundIsPlaying,
+ I_HB_CacheSounds,
+};
+
+// --- music: silent stub -----------------------------------------------------
+static snddevice_t mus_devices[] = {
+ SNDDEVICE_GENMIDI, SNDDEVICE_ADLIB, SNDDEVICE_SB, SNDDEVICE_PAS, SNDDEVICE_GUS,
+};
+static boolean M_Init(void) { return true; }
+static void M_Shutdown(void) {}
+static void M_SetVol(int v) { (void)v; }
+static void M_Pause(void) {}
+static void M_Resume(void) {}
+static void *M_Register(void *d, int l) { (void)d; (void)l; return NULL; }
+static void M_Unregister(void *h) { (void)h; }
+static void M_Play(void *h, boolean loop) { (void)h; (void)loop; }
+static void M_Stop(void) {}
+static boolean M_IsPlaying(void) { return false; }
+static void M_Poll(void) {}
+
+music_module_t DG_music_module = {
+ mus_devices, ARRLEN(mus_devices),
+ M_Init, M_Shutdown, M_SetVol, M_Pause, M_Resume,
+ M_Register, M_Unregister, M_Play, M_Stop, M_IsPlaying, M_Poll,
+};
diff --git a/firmware_p4/components/Applications/doom/doomdata.h b/firmware_p4/components/Applications/doom/doomdata.h
new file mode 100644
index 000000000..46b3e11b6
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/doomdata.h
@@ -0,0 +1,213 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// all external data is defined here
+// most of the data is loaded into different structures at run time
+// some internal structures shared by many modules are here
+//
+
+#ifndef __DOOMDATA__
+#define __DOOMDATA__
+
+// The most basic types we use, portability.
+#include "doomtype.h"
+
+// Some global defines, that configure the game.
+#include "doomdef.h"
+
+
+
+//
+// Map level types.
+// The following data structures define the persistent format
+// used in the lumps of the WAD files.
+//
+
+// Lump order in a map WAD: each map needs a couple of lumps
+// to provide a complete scene geometry description.
+enum
+{
+ ML_LABEL, // A separator, name, ExMx or MAPxx
+ ML_THINGS, // Monsters, items..
+ ML_LINEDEFS, // LineDefs, from editing
+ ML_SIDEDEFS, // SideDefs, from editing
+ ML_VERTEXES, // Vertices, edited and BSP splits generated
+ ML_SEGS, // LineSegs, from LineDefs split by BSP
+ ML_SSECTORS, // SubSectors, list of LineSegs
+ ML_NODES, // BSP nodes
+ ML_SECTORS, // Sectors, from editing
+ ML_REJECT, // LUT, sector-sector visibility
+ ML_BLOCKMAP // LUT, motion clipping, walls/grid element
+};
+
+
+// A single Vertex.
+typedef struct
+{
+ short x;
+ short y;
+} PACKEDATTR mapvertex_t;
+
+
+// A SideDef, defining the visual appearance of a wall,
+// by setting textures and offsets.
+typedef struct
+{
+ short textureoffset;
+ short rowoffset;
+ char toptexture[8];
+ char bottomtexture[8];
+ char midtexture[8];
+ // Front sector, towards viewer.
+ short sector;
+} PACKEDATTR mapsidedef_t;
+
+
+
+// A LineDef, as used for editing, and as input
+// to the BSP builder.
+typedef struct
+{
+ short v1;
+ short v2;
+ short flags;
+ short special;
+ short tag;
+ // sidenum[1] will be -1 if one sided
+ short sidenum[2];
+} PACKEDATTR maplinedef_t;
+
+
+//
+// LineDef attributes.
+//
+
+// Solid, is an obstacle.
+#define ML_BLOCKING 1
+
+// Blocks monsters only.
+#define ML_BLOCKMONSTERS 2
+
+// Backside will not be present at all
+// if not two sided.
+#define ML_TWOSIDED 4
+
+// If a texture is pegged, the texture will have
+// the end exposed to air held constant at the
+// top or bottom of the texture (stairs or pulled
+// down things) and will move with a height change
+// of one of the neighbor sectors.
+// Unpegged textures allways have the first row of
+// the texture at the top pixel of the line for both
+// top and bottom textures (use next to windows).
+
+// upper texture unpegged
+#define ML_DONTPEGTOP 8
+
+// lower texture unpegged
+#define ML_DONTPEGBOTTOM 16
+
+// In AutoMap: don't map as two sided: IT'S A SECRET!
+#define ML_SECRET 32
+
+// Sound rendering: don't let sound cross two of these.
+#define ML_SOUNDBLOCK 64
+
+// Don't draw on the automap at all.
+#define ML_DONTDRAW 128
+
+// Set if already seen, thus drawn in automap.
+#define ML_MAPPED 256
+
+
+
+
+// Sector definition, from editing.
+typedef struct
+{
+ short floorheight;
+ short ceilingheight;
+ char floorpic[8];
+ char ceilingpic[8];
+ short lightlevel;
+ short special;
+ short tag;
+} PACKEDATTR mapsector_t;
+
+// SubSector, as generated by BSP.
+typedef struct
+{
+ short numsegs;
+ // Index of first one, segs are stored sequentially.
+ short firstseg;
+} PACKEDATTR mapsubsector_t;
+
+
+// LineSeg, generated by splitting LineDefs
+// using partition lines selected by BSP builder.
+typedef struct
+{
+ short v1;
+ short v2;
+ short angle;
+ short linedef;
+ short side;
+ short offset;
+} PACKEDATTR mapseg_t;
+
+
+
+// BSP node structure.
+
+// Indicate a leaf.
+#define NF_SUBSECTOR 0x8000
+
+typedef struct
+{
+ // Partition line from (x,y) to x+dx,y+dy)
+ short x;
+ short y;
+ short dx;
+ short dy;
+
+ // Bounding box for each child,
+ // clip against view frustum.
+ short bbox[2][4];
+
+ // If NF_SUBSECTOR its a subsector,
+ // else it's a node of another subtree.
+ unsigned short children[2];
+
+} PACKEDATTR mapnode_t;
+
+
+
+
+// Thing definition, position, orientation and type,
+// plus skill/visibility flags and attributes.
+typedef struct
+{
+ short x;
+ short y;
+ short angle;
+ short type;
+ short options;
+} PACKEDATTR mapthing_t;
+
+
+
+
+
+#endif // __DOOMDATA__
diff --git a/firmware_p4/components/Applications/doom/doomdef.c b/firmware_p4/components/Applications/doom/doomdef.c
new file mode 100644
index 000000000..d31f3efa2
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/doomdef.c
@@ -0,0 +1,28 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// DoomDef - basic defines for DOOM, e.g. Version, game mode
+// and skill level, and display parameters.
+//
+
+
+
+#include "doomdef.h"
+
+// Location for any defines turned variables.
+
+// None.
+
+
diff --git a/firmware_p4/components/Applications/doom/doomdef.h b/firmware_p4/components/Applications/doom/doomdef.h
new file mode 100644
index 000000000..62d729ddb
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/doomdef.h
@@ -0,0 +1,168 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Internally used data structures for virtually everything,
+// lots of other stuff.
+//
+
+#ifndef __DOOMDEF__
+#define __DOOMDEF__
+
+#include
+#include
+
+#include "doomtype.h"
+#include "i_timer.h"
+#include "d_mode.h"
+
+//
+// Global parameters/defines.
+//
+// DOOM version
+#define DOOM_VERSION 109
+
+// Version code for cph's longtics hack ("v1.91")
+#define DOOM_191_VERSION 111
+
+
+// If rangecheck is undefined,
+// most parameter validation debugging code will not be compiled
+#define RANGECHECK
+
+// The maximum number of players, multiplayer/networking.
+#define MAXPLAYERS 4
+
+// The current state of the game: whether we are
+// playing, gazing at the intermission screen,
+// the game final animation, or a demo.
+typedef enum
+{
+ GS_LEVEL,
+ GS_INTERMISSION,
+ GS_FINALE,
+ GS_DEMOSCREEN,
+} gamestate_t;
+
+typedef enum
+{
+ ga_nothing,
+ ga_loadlevel,
+ ga_newgame,
+ ga_loadgame,
+ ga_savegame,
+ ga_playdemo,
+ ga_completed,
+ ga_victory,
+ ga_worlddone,
+ ga_screenshot
+} gameaction_t;
+
+//
+// Difficulty/skill settings/filters.
+//
+
+// Skill flags.
+#define MTF_EASY 1
+#define MTF_NORMAL 2
+#define MTF_HARD 4
+
+// Deaf monsters/do not react to sound.
+#define MTF_AMBUSH 8
+
+
+//
+// Key cards.
+//
+typedef enum
+{
+ it_bluecard,
+ it_yellowcard,
+ it_redcard,
+ it_blueskull,
+ it_yellowskull,
+ it_redskull,
+
+ NUMCARDS
+
+} card_t;
+
+
+
+// The defined weapons,
+// including a marker indicating
+// user has not changed weapon.
+typedef enum
+{
+ wp_fist,
+ wp_pistol,
+ wp_shotgun,
+ wp_chaingun,
+ wp_missile,
+ wp_plasma,
+ wp_bfg,
+ wp_chainsaw,
+ wp_supershotgun,
+
+ NUMWEAPONS,
+
+ // No pending weapon change.
+ wp_nochange
+
+} weapontype_t;
+
+
+// Ammunition types defined.
+typedef enum
+{
+ am_clip, // Pistol / chaingun ammo.
+ am_shell, // Shotgun / double barreled shotgun.
+ am_cell, // Plasma rifle, BFG.
+ am_misl, // Missile launcher.
+ NUMAMMO,
+ am_noammo // Unlimited for chainsaw / fist.
+
+} ammotype_t;
+
+
+// Power up artifacts.
+typedef enum
+{
+ pw_invulnerability,
+ pw_strength,
+ pw_invisibility,
+ pw_ironfeet,
+ pw_allmap,
+ pw_infrared,
+ NUMPOWERS
+
+} powertype_t;
+
+
+
+//
+// Power up durations,
+// how many seconds till expiration,
+// assuming TICRATE is 35 ticks/second.
+//
+typedef enum
+{
+ INVULNTICS = (30*TICRATE),
+ INVISTICS = (60*TICRATE),
+ INFRATICS = (120*TICRATE),
+ IRONTICS = (60*TICRATE)
+
+} powerduration_t;
+
+#endif // __DOOMDEF__
diff --git a/firmware_p4/components/Applications/doom/doomfeatures.h b/firmware_p4/components/Applications/doom/doomfeatures.h
new file mode 100644
index 000000000..dff693611
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/doomfeatures.h
@@ -0,0 +1,40 @@
+//
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// List of features which can be enabled/disabled to slim down the
+// program.
+//
+
+#ifndef DOOM_FEATURES_H
+#define DOOM_FEATURES_H
+
+// Enables wad merging (the '-merge' command line parameter)
+
+#undef FEATURE_WAD_MERGE
+
+// Enables dehacked support ('-deh')
+
+#undef FEATURE_DEHACKED
+
+// Enables multiplayer support (network games)
+
+#undef FEATURE_MULTIPLAYER
+
+// Enables sound output
+
+//#undef FEATURE_SOUND
+
+#endif /* #ifndef DOOM_FEATURES_H */
+
+
diff --git a/firmware_p4/components/Applications/doom/doomgeneric.c b/firmware_p4/components/Applications/doom/doomgeneric.c
new file mode 100644
index 000000000..881d70077
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/doomgeneric.c
@@ -0,0 +1,27 @@
+#include
+
+#include "m_argv.h"
+
+#include "doomgeneric.h"
+
+pixel_t* DG_ScreenBuffer = NULL;
+
+void M_FindResponseFile(void);
+void D_DoomMain (void);
+
+
+void doomgeneric_Create(int argc, char **argv)
+{
+ // save arguments
+ myargc = argc;
+ myargv = argv;
+
+ M_FindResponseFile();
+
+ DG_ScreenBuffer = malloc(DOOMGENERIC_RESX * DOOMGENERIC_RESY * 4);
+
+ DG_Init();
+
+ D_DoomMain ();
+}
+
diff --git a/firmware_p4/components/Applications/doom/doomgeneric.h b/firmware_p4/components/Applications/doom/doomgeneric.h
new file mode 100644
index 000000000..fd8708f42
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/doomgeneric.h
@@ -0,0 +1,49 @@
+#ifndef DOOM_GENERIC
+#define DOOM_GENERIC
+
+#include
+#include
+
+#ifndef DOOMGENERIC_RESX
+#define DOOMGENERIC_RESX 640
+#endif // DOOMGENERIC_RESX
+
+#ifndef DOOMGENERIC_RESY
+#define DOOMGENERIC_RESY 400
+#endif // DOOMGENERIC_RESY
+
+
+#ifdef CMAP256
+
+typedef uint8_t pixel_t;
+
+#else // CMAP256
+
+typedef uint32_t pixel_t;
+
+#endif // CMAP256
+
+
+extern pixel_t* DG_ScreenBuffer;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void doomgeneric_Create(int argc, char **argv);
+void doomgeneric_Tick();
+
+
+//Implement below functions for your platform
+void DG_Init();
+void DG_DrawFrame();
+void DG_SleepMs(uint32_t ms);
+uint32_t DG_GetTicksMs();
+int DG_GetKey(int* pressed, unsigned char* key);
+void DG_SetWindowTitle(const char * title);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //DOOM_GENERIC
diff --git a/firmware_p4/components/Applications/doom/doomgeneric_highboy.c b/firmware_p4/components/Applications/doom/doomgeneric_highboy.c
new file mode 100644
index 000000000..79286d75b
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/doomgeneric_highboy.c
@@ -0,0 +1,312 @@
+// HighBoy (ESP32-P4) platform layer for doomgeneric.
+//
+// Implements the six DG_* hooks + a launcher that runs DOOM in its own task:
+// - DG_DrawFrame : DG_ScreenBuffer (ARGB8888, DOOMGENERIC_RESX x RESY) -> RGB565
+// big-endian, blitted directly to the ST7789 (bypassing LVGL)
+// in landscape (320x240), letterboxed to DOOM's 320x200.
+// - DG_GetKey : drained from a ring buffer filled by a polling input task that
+// maps the 6 physical buttons (D-pad + OK + BACK) to DOOM keys,
+// with an OK+BACK chord = ESC (menu) and hold-both ~2s = quit.
+// - timing : esp_timer / FreeRTOS ticks.
+// The WAD is streamed from /sdcard/doom1.wad. Zone memory comes from PSRAM
+// automatically (CONFIG_SPIRAM_USE_MALLOC: large mallocs land in PSRAM).
+//
+// Exit model: doomgeneric_Create() runs DOOM's own infinite loop and never
+// returns, so "quit to launcher" = esp_restart() (clean reboot into the UI).
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "doomgeneric.h"
+#include "doomkeys.h"
+#include "doom_highboy.h"
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/idf_additions.h" // xTaskCreatePinnedToCoreWithCaps
+#include "esp_timer.h"
+#include "esp_log.h"
+#include "esp_system.h"
+#include "esp_heap_caps.h"
+#include "esp_lcd_panel_ops.h"
+
+#include "st7789.h" // extern panel_handle
+#include "buttons_gpio.h" // *_button_is_down()
+#include "lvgl_glue.h" // lvgl_glue_direct_begin()
+#include "storage_init.h" // storage_is_mounted(), storage_init()
+
+static const char *TAG = "DOOM";
+
+// ---- geometry -------------------------------------------------------------
+#define DG_W DOOMGENERIC_RESX // 320
+#define DG_H DOOMGENERIC_RESY // 200
+#define PANEL_W 320 // ST7789 in landscape (swap_xy)
+#define PANEL_H 240
+#define Y_OFF ((PANEL_H - DG_H) / 2) // 20px letterbox top/bottom
+#define STRIP 20 // blit height per SPI burst (avoid corruption)
+
+static uint16_t *s_fb[2]; // double-buffered RGB565 (big-endian) frames
+static int s_fb_sel;
+
+// Pulsed once per frame to keep the sys_monitor render-liveness beat alive while
+// DOOM owns the panel and the LVGL task is parked (see doom_main_task).
+static void (*s_beat_kick)(void);
+
+// ---- timing ---------------------------------------------------------------
+uint32_t DG_GetTicksMs(void) { return (uint32_t)(esp_timer_get_time() / 1000); }
+void DG_SleepMs(uint32_t ms) { vTaskDelay(pdMS_TO_TICKS(ms ? ms : 1)); }
+void DG_SetWindowTitle(const char *title) { (void)title; }
+
+// ---- input ring buffer (filled by doom_input_task, drained by DG_GetKey) --
+#define KQ 64
+static volatile struct { int pressed; unsigned char key; } s_kq[KQ];
+static volatile int s_kq_head, s_kq_tail;
+
+static void kq_push(int pressed, unsigned char key) {
+ int n = (s_kq_head + 1) % KQ;
+ if (n == s_kq_tail) return; // full: drop
+ s_kq[s_kq_head].pressed = pressed;
+ s_kq[s_kq_head].key = key;
+ s_kq_head = n;
+}
+
+int DG_GetKey(int *pressed, unsigned char *key) {
+ if (s_kq_tail == s_kq_head) return 0;
+ *pressed = s_kq[s_kq_tail].pressed;
+ *key = s_kq[s_kq_tail].key;
+ s_kq_tail = (s_kq_tail + 1) % KQ;
+ return 1;
+}
+
+// Simple edge helper for the D-pad (one-shot press/release into the ring).
+static void edge(bool now, bool *prev, unsigned char key) {
+ if (now && !*prev) { kq_push(1, key); *prev = true; }
+ else if (!now && *prev) { kq_push(0, key); *prev = false; }
+}
+
+// Poll the 6 buttons and translate to DOOM keys. Mapping:
+// D-pad -> arrows, ROTATED 90° for landscape (UP->LEFT DOWN->RIGHT RIGHT->UP LEFT->DOWN)
+// OK -> FIRE (+ENTER so it also selects in menus)
+// BACK -> USE (open doors/switches)
+// OK+BACK pressed together (within ~250ms) -> ESCAPE (open/close menu)
+// OK+BACK held together ~2s -> quit to launcher (esp_restart)
+static void doom_input_task(void *arg) {
+ (void)arg;
+ bool p_up = 0, p_dn = 0, p_l = 0, p_r = 0;
+ bool ok_c = 0, bk_c = 0; // OK / BACK committed as their own key
+ bool ok_sw = 0, bk_sw = 0; // swallow until physical release (post-chord)
+ bool chord = 0; // ESC chord active
+ int64_t t_ok = 0, t_bk = 0, t_chord = 0;
+
+ for (;;) {
+ bool up = up_button_is_down(), dn = down_button_is_down();
+ bool l = left_button_is_down(), r = right_button_is_down();
+ bool ok = ok_button_is_down(), bk = back_button_is_down();
+ int64_t now = esp_timer_get_time() / 1000;
+
+ // D-pad rotated 90° for landscape play (device held sideways):
+ edge(up, &p_up, KEY_LEFTARROW); // physical UP -> game LEFT
+ edge(dn, &p_dn, KEY_RIGHTARROW); // physical DOWN -> game RIGHT
+ edge(r, &p_r, KEY_UPARROW); // physical RIGHT -> game UP
+ edge(l, &p_l, KEY_DOWNARROW); // physical LEFT -> game DOWN
+
+ // Track when OK / BACK physically went down (for the chord window).
+ static bool pok = 0, pbk = 0;
+ if (ok && !pok) t_ok = now;
+ if (bk && !pbk) t_bk = now;
+
+ if (chord) {
+ if (ok && bk) {
+ if (now - t_chord >= 2000) { // hold both ~2s -> quit to launcher
+ ESP_LOGW(TAG, "quit gesture -> esp_restart()");
+ vTaskDelay(pdMS_TO_TICKS(50));
+ esp_restart();
+ }
+ } else { // one released -> close chord
+ kq_push(0, KEY_ESCAPE);
+ chord = 0;
+ ok_sw = ok; // swallow whatever is still held
+ bk_sw = bk;
+ }
+ } else if (ok && bk && !ok_sw && !bk_sw &&
+ (t_ok > t_bk ? t_ok - t_bk : t_bk - t_ok) <= 250) {
+ // enter chord: retract any single-key commits, send ESC down
+ if (ok_c) { kq_push(0, KEY_FIRE); kq_push(0, KEY_ENTER); ok_c = 0; }
+ if (bk_c) { kq_push(0, KEY_USE); bk_c = 0; }
+ kq_push(1, KEY_ESCAPE);
+ chord = 1;
+ t_chord = now;
+ } else {
+ // normal handling; honor swallow-until-release
+ if (ok_sw && !ok) ok_sw = 0;
+ if (bk_sw && !bk) bk_sw = 0;
+ bool ok_eff = ok && !ok_sw;
+ bool bk_eff = bk && !bk_sw;
+ if (ok_eff && !ok_c) { kq_push(1, KEY_FIRE); kq_push(1, KEY_ENTER); ok_c = 1; }
+ else if (!ok_eff && ok_c) { kq_push(0, KEY_FIRE); kq_push(0, KEY_ENTER); ok_c = 0; }
+ if (bk_eff && !bk_c) { kq_push(1, KEY_USE); bk_c = 1; }
+ else if (!bk_eff && bk_c) { kq_push(0, KEY_USE); bk_c = 0; }
+ }
+
+ pok = ok; pbk = bk;
+ vTaskDelay(pdMS_TO_TICKS(15));
+ }
+}
+
+// ---- framebuffer ----------------------------------------------------------
+static inline uint16_t argb_to_565be(uint32_t p) {
+ uint8_t r = (p >> 16) & 0xFF, g = (p >> 8) & 0xFF, b = p & 0xFF;
+ uint16_t c = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
+ return (uint16_t)((c >> 8) | (c << 8)); // ST7789 wants big-endian RGB565
+}
+
+static void panel_clear_black(void) {
+ // Fill the whole 320x240 landscape panel black once (letterbox stays black).
+ uint16_t *row = heap_caps_malloc(PANEL_W * STRIP * sizeof(uint16_t),
+ MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
+ if (!row) return;
+ memset(row, 0, PANEL_W * STRIP * sizeof(uint16_t));
+ for (int y = 0; y < PANEL_H; y += STRIP) {
+ int rows = (y + STRIP <= PANEL_H) ? STRIP : (PANEL_H - y);
+ esp_lcd_panel_draw_bitmap(panel_handle, 0, y, PANEL_W, y + rows, row);
+ }
+ vTaskDelay(pdMS_TO_TICKS(30)); // let the blits drain before freeing
+ heap_caps_free(row);
+}
+
+void DG_Init(void) {
+ for (int i = 0; i < 2; i++)
+ s_fb[i] = heap_caps_malloc(DG_W * DG_H * sizeof(uint16_t), MALLOC_CAP_SPIRAM);
+ ESP_LOGW(TAG, "DG_Init: fb0=%p fb1=%p (%d bytes each)", s_fb[0], s_fb[1],
+ DG_W * DG_H * (int)sizeof(uint16_t));
+ // Input runs in its own task (DOOM's loop blocks this task via Create()).
+ // Stack in PSRAM — internal RAM is scarce (~30 KB free after full boot).
+ xTaskCreatePinnedToCoreWithCaps(doom_input_task, "doom_in", 4096, NULL, 6, NULL, 0,
+ MALLOC_CAP_SPIRAM);
+}
+
+void DG_DrawFrame(void) {
+ uint16_t *fb = s_fb[s_fb_sel];
+ if (!fb) return;
+ s_fb_sel ^= 1; // ping-pong so the previous frame's DMA can still be in flight
+
+ const uint32_t *src = (const uint32_t *)DG_ScreenBuffer;
+ const int n = DG_W * DG_H;
+ for (int i = 0; i < n; i++) fb[i] = argb_to_565be(src[i]);
+
+ for (int y = 0; y < DG_H; y += STRIP) {
+ int rows = (y + STRIP <= DG_H) ? STRIP : (DG_H - y);
+ esp_lcd_panel_draw_bitmap(panel_handle, 0, Y_OFF + y, DG_W, Y_OFF + y + rows,
+ &fb[y * DG_W]);
+ }
+}
+
+// ---- WAD discovery --------------------------------------------------------
+static bool ends_with_wad(const char *name) {
+ size_t n = strlen(name);
+ return n >= 4 && strcasecmp(name + n - 4, ".wad") == 0;
+}
+
+// List a directory to the log and return the first *.wad found (full path).
+static bool scan_dir_for_wad(const char *dir, char *out, size_t outsz) {
+ DIR *d = opendir(dir);
+ if (!d) return false;
+ ESP_LOGW(TAG, "listing %s:", dir);
+ bool found = false;
+ struct dirent *e;
+ while ((e = readdir(d)) != NULL) {
+ ESP_LOGI(TAG, " %s", e->d_name);
+ if (!found && ends_with_wad(e->d_name)) {
+ snprintf(out, outsz, "%s/%s", dir, e->d_name);
+ found = true;
+ }
+ }
+ closedir(d);
+ return found;
+}
+
+// Find a WAD: try common exact paths, then scan the SD root and /sdcard/doom
+// for any *.wad. Robust to subfolders and name/case differences.
+static bool find_wad(char *out, size_t outsz) {
+ static const char *cands[] = {
+ "/sdcard/doom1.wad", "/sdcard/doom.wad",
+ "/sdcard/DOOM1.WAD", "/sdcard/DOOM.WAD",
+ "/sdcard/doom/doom1.wad", "/sdcard/doom/doom.wad", NULL};
+ for (int i = 0; cands[i]; i++) {
+ FILE *f = fopen(cands[i], "rb");
+ if (f) { fclose(f); snprintf(out, outsz, "%s", cands[i]); return true; }
+ }
+ if (scan_dir_for_wad("/sdcard", out, outsz)) return true;
+ if (scan_dir_for_wad("/sdcard/doom", out, outsz)) return true;
+ return false;
+}
+
+// ---- launcher -------------------------------------------------------------
+static void doom_main_task(void *arg) {
+ (void)arg;
+ ESP_LOGW(TAG, "doom_main_task: enter (free int=%u psram=%u)",
+ (unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
+ (unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
+ vTaskDelay(pdMS_TO_TICKS(150)); // let the UI screen switch settle
+
+ // Own the display exclusively: direct-draw routes the panel's DMA-done callback
+ // to DOOM (not LVGL), and holding the LVGL lock forever parks the LVGL task so
+ // it never flushes or fights DOOM for the SPI3 bus. The sys_monitor render beat
+ // is fed by s_beat_kick() in the loop instead of by LVGL. Quitting reboots, so
+ // the lock is never released.
+ lvgl_glue_direct_begin();
+ lvgl_glue_lock(-1);
+ ESP_LOGW(TAG, "doom_main_task: panel owned (LVGL parked)");
+
+ // Landscape 320x240 so DOOM's 320-wide frame fits (portrait is only 240 wide).
+ esp_lcd_panel_swap_xy(panel_handle, true);
+ esp_lcd_panel_mirror(panel_handle, true, false);
+ panel_clear_black();
+ ESP_LOGW(TAG, "doom_main_task: panel landscape + cleared");
+
+ if (!storage_is_mounted()) {
+ ESP_LOGW(TAG, "SD not mounted, mounting...");
+ storage_init();
+ }
+ // Locate a WAD (lists the SD so you can see what's there). Fail loudly rather
+ // than hang inside DOOM's I_Error if none is present.
+ static char wadpath[128];
+ if (!find_wad(wadpath, sizeof(wadpath))) {
+ ESP_LOGE(TAG, "No .wad found on SD (checked /sdcard and /sdcard/doom).");
+ ESP_LOGE(TAG, "Copy doom1.wad to the SD root, then reboot.");
+ for (;;) vTaskDelay(pdMS_TO_TICKS(2000));
+ }
+ chdir("/sdcard"); // DOOM writes config/saves relative to CWD (SD mount point)
+
+ ESP_LOGW(TAG, "starting DOOM (wad=%s). Quit = hold OK+BACK ~2s.", wadpath);
+ static char *argv[] = {"doom", "-iwad", wadpath, NULL};
+ doomgeneric_Create(3, argv); // init only (D_DoomMain/D_DoomLoop return after 1 tick)
+
+ // doomgeneric's design: the platform drives the game loop by calling
+ // doomgeneric_Tick() repeatedly (D_DoomLoop runs ONE tick then returns).
+ for (;;) {
+ doomgeneric_Tick();
+ // LVGL is parked, so feed the render-liveness beat ourselves.
+ if (s_beat_kick != NULL)
+ s_beat_kick();
+ // Yield 1 tick (1ms @1kHz) so core 1's IDLE task runs and resets the task
+ // watchdog; doomgeneric's tic loop never blocks on its own.
+ vTaskDelay(1);
+ }
+}
+
+void highboy_doom_start(void (*render_beat_kick)(void)) {
+ s_beat_kick = render_beat_kick;
+ // 48 KB stack in PSRAM: DOOM recurses deeply (R_RenderBSPNode) and internal
+ // RAM is nearly exhausted after boot (~30 KB), so an internal stack can't be
+ // allocated. WithCaps puts the stack in the ample 32 MB PSRAM.
+ BaseType_t ok = xTaskCreatePinnedToCoreWithCaps(
+ doom_main_task, "doom", 49152, NULL, 5, NULL, 1, MALLOC_CAP_SPIRAM);
+ ESP_LOGW(TAG, "highboy_doom_start: task create -> %s",
+ ok == pdPASS ? "OK" : "FAILED");
+}
diff --git a/firmware_p4/components/Applications/doom/doomkeys.h b/firmware_p4/components/Applications/doom/doomkeys.h
new file mode 100644
index 000000000..2dfc431b9
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/doomkeys.h
@@ -0,0 +1,97 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Key definitions
+//
+
+#ifndef __DOOMKEYS__
+#define __DOOMKEYS__
+
+//
+// DOOM keyboard definition.
+// This is the stuff configured by Setup.Exe.
+// Most key data are simple ascii (uppercased).
+//
+#define KEY_RIGHTARROW 0xae
+#define KEY_LEFTARROW 0xac
+#define KEY_UPARROW 0xad
+#define KEY_DOWNARROW 0xaf
+#define KEY_STRAFE_L 0xa0
+#define KEY_STRAFE_R 0xa1
+#define KEY_USE 0xa2
+#define KEY_FIRE 0xa3
+#define KEY_ESCAPE 27
+#define KEY_ENTER 13
+#define KEY_TAB 9
+#define KEY_F1 (0x80+0x3b)
+#define KEY_F2 (0x80+0x3c)
+#define KEY_F3 (0x80+0x3d)
+#define KEY_F4 (0x80+0x3e)
+#define KEY_F5 (0x80+0x3f)
+#define KEY_F6 (0x80+0x40)
+#define KEY_F7 (0x80+0x41)
+#define KEY_F8 (0x80+0x42)
+#define KEY_F9 (0x80+0x43)
+#define KEY_F10 (0x80+0x44)
+#define KEY_F11 (0x80+0x57)
+#define KEY_F12 (0x80+0x58)
+
+#define KEY_BACKSPACE 0x7f
+#define KEY_PAUSE 0xff
+
+#define KEY_EQUALS 0x3d
+#define KEY_MINUS 0x2d
+
+#define KEY_RSHIFT (0x80+0x36)
+#define KEY_RCTRL (0x80+0x1d)
+#define KEY_RALT (0x80+0x38)
+
+#define KEY_LALT KEY_RALT
+
+// new keys:
+
+#define KEY_CAPSLOCK (0x80+0x3a)
+#define KEY_NUMLOCK (0x80+0x45)
+#define KEY_SCRLCK (0x80+0x46)
+#define KEY_PRTSCR (0x80+0x59)
+
+#define KEY_HOME (0x80+0x47)
+#define KEY_END (0x80+0x4f)
+#define KEY_PGUP (0x80+0x49)
+#define KEY_PGDN (0x80+0x51)
+#define KEY_INS (0x80+0x52)
+#define KEY_DEL (0x80+0x53)
+
+#define KEYP_0 0
+#define KEYP_1 KEY_END
+#define KEYP_2 KEY_DOWNARROW
+#define KEYP_3 KEY_PGDN
+#define KEYP_4 KEY_LEFTARROW
+#define KEYP_5 '5'
+#define KEYP_6 KEY_RIGHTARROW
+#define KEYP_7 KEY_HOME
+#define KEYP_8 KEY_UPARROW
+#define KEYP_9 KEY_PGUP
+
+#define KEYP_DIVIDE '/'
+#define KEYP_PLUS '+'
+#define KEYP_MINUS '-'
+#define KEYP_MULTIPLY '*'
+#define KEYP_PERIOD 0
+#define KEYP_EQUALS KEY_EQUALS
+#define KEYP_ENTER KEY_ENTER
+
+#endif // __DOOMKEYS__
+
diff --git a/firmware_p4/components/Applications/doom/doomstat.c b/firmware_p4/components/Applications/doom/doomstat.c
new file mode 100644
index 000000000..ed4c6ccc3
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/doomstat.c
@@ -0,0 +1,35 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Put all global tate variables here.
+//
+
+#include
+
+#include "doomstat.h"
+
+
+// Game Mode - identify IWAD as shareware, retail etc.
+GameMode_t gamemode = indetermined;
+GameMission_t gamemission = doom;
+GameVersion_t gameversion = exe_final2;
+char *gamedescription;
+
+// Set if homebrew PWAD stuff has been added.
+boolean modifiedgame;
+
+
+
+
diff --git a/firmware_p4/components/Applications/doom/doomstat.h b/firmware_p4/components/Applications/doom/doomstat.h
new file mode 100644
index 000000000..acd65dcca
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/doomstat.h
@@ -0,0 +1,281 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// All the global variables that store the internal state.
+// Theoretically speaking, the internal state of the engine
+// should be found by looking at the variables collected
+// here, and every relevant module will have to include
+// this header file.
+// In practice, things are a bit messy.
+//
+
+
+#ifndef __D_STATE__
+#define __D_STATE__
+
+// We need globally shared data structures,
+// for defining the global state variables.
+#include "doomdata.h"
+#include "d_loop.h"
+
+// We need the playr data structure as well.
+#include "d_player.h"
+
+// Game mode/mission
+#include "d_mode.h"
+
+#include "net_defs.h"
+
+
+
+// ------------------------
+// Command line parameters.
+//
+extern boolean nomonsters; // checkparm of -nomonsters
+extern boolean respawnparm; // checkparm of -respawn
+extern boolean fastparm; // checkparm of -fast
+
+extern boolean devparm; // DEBUG: launched with -devparm
+
+
+// -----------------------------------------------------
+// Game Mode - identify IWAD as shareware, retail etc.
+//
+extern GameMode_t gamemode;
+extern GameMission_t gamemission;
+extern GameVersion_t gameversion;
+extern char *gamedescription;
+
+// If true, we're using one of the mangled BFG edition IWADs.
+extern boolean bfgedition;
+
+// Convenience macro.
+// 'gamemission' can be equal to pack_chex or pack_hacx, but these are
+// just modified versions of doom and doom2, and should be interpreted
+// as the same most of the time.
+
+#define logical_gamemission \
+ (gamemission == pack_chex ? doom : \
+ gamemission == pack_hacx ? doom2 : gamemission)
+
+// Set if homebrew PWAD stuff has been added.
+extern boolean modifiedgame;
+
+
+// -------------------------------------------
+// Selected skill type, map etc.
+//
+
+// Defaults for menu, methinks.
+extern skill_t startskill;
+extern int startepisode;
+extern int startmap;
+
+// Savegame slot to load on startup. This is the value provided to
+// the -loadgame option. If this has not been provided, this is -1.
+
+extern int startloadgame;
+
+extern boolean autostart;
+
+// Selected by user.
+extern skill_t gameskill;
+extern int gameepisode;
+extern int gamemap;
+
+// If non-zero, exit the level after this number of minutes
+extern int timelimit;
+
+// Nightmare mode flag, single player.
+extern boolean respawnmonsters;
+
+// Netgame? Only true if >1 player.
+extern boolean netgame;
+
+// 0=Cooperative; 1=Deathmatch; 2=Altdeath
+extern int deathmatch;
+
+// -------------------------
+// Internal parameters for sound rendering.
+// These have been taken from the DOS version,
+// but are not (yet) supported with Linux
+// (e.g. no sound volume adjustment with menu.
+
+// From m_menu.c:
+// Sound FX volume has default, 0 - 15
+// Music volume has default, 0 - 15
+// These are multiplied by 8.
+extern int sfxVolume;
+extern int musicVolume;
+
+// Current music/sfx card - index useless
+// w/o a reference LUT in a sound module.
+// Ideally, this would use indices found
+// in: /usr/include/linux/soundcard.h
+extern int snd_MusicDevice;
+extern int snd_SfxDevice;
+// Config file? Same disclaimer as above.
+extern int snd_DesiredMusicDevice;
+extern int snd_DesiredSfxDevice;
+
+
+// -------------------------
+// Status flags for refresh.
+//
+
+// Depending on view size - no status bar?
+// Note that there is no way to disable the
+// status bar explicitely.
+extern boolean statusbaractive;
+
+extern boolean automapactive; // In AutoMap mode?
+extern boolean menuactive; // Menu overlayed?
+extern boolean paused; // Game Pause?
+
+
+extern boolean viewactive;
+
+extern boolean nodrawers;
+
+
+extern boolean testcontrols;
+extern int testcontrols_mousespeed;
+
+
+
+
+// This one is related to the 3-screen display mode.
+// ANG90 = left side, ANG270 = right
+extern int viewangleoffset;
+
+// Player taking events, and displaying.
+extern int consoleplayer;
+extern int displayplayer;
+
+
+// -------------------------------------
+// Scores, rating.
+// Statistics on a given map, for intermission.
+//
+extern int totalkills;
+extern int totalitems;
+extern int totalsecret;
+
+// Timer, for scores.
+extern int levelstarttic; // gametic at level start
+extern int leveltime; // tics in game play for par
+
+
+
+// --------------------------------------
+// DEMO playback/recording related stuff.
+// No demo, there is a human player in charge?
+// Disable save/end game?
+extern boolean usergame;
+
+//?
+extern boolean demoplayback;
+extern boolean demorecording;
+
+// Round angleturn in ticcmds to the nearest 256. This is used when
+// recording Vanilla demos in netgames.
+
+extern boolean lowres_turn;
+
+// Quit after playing a demo from cmdline.
+extern boolean singledemo;
+
+
+
+
+//?
+extern gamestate_t gamestate;
+
+
+
+
+
+
+//-----------------------------
+// Internal parameters, fixed.
+// These are set by the engine, and not changed
+// according to user inputs. Partly load from
+// WAD, partly set at startup time.
+
+
+
+// Bookkeeping on players - state.
+extern player_t players[MAXPLAYERS];
+
+// Alive? Disconnected?
+extern boolean playeringame[MAXPLAYERS];
+
+
+// Player spawn spots for deathmatch.
+#define MAX_DM_STARTS 10
+extern mapthing_t deathmatchstarts[MAX_DM_STARTS];
+extern mapthing_t* deathmatch_p;
+
+// Player spawn spots.
+extern mapthing_t playerstarts[MAXPLAYERS];
+
+// Intermission stats.
+// Parameters for world map / intermission.
+extern wbstartstruct_t wminfo;
+
+
+
+
+
+
+
+//-----------------------------------------
+// Internal parameters, used for engine.
+//
+
+// File handling stuff.
+extern char * savegamedir;
+extern char basedefault[1024];
+
+// if true, load all graphics at level load
+extern boolean precache;
+
+
+// wipegamestate can be set to -1
+// to force a wipe on the next draw
+extern gamestate_t wipegamestate;
+
+extern int mouseSensitivity;
+
+extern int bodyqueslot;
+
+
+
+// Needed to store the number of the dummy sky flat.
+// Used for rendering,
+// as well as tracking projectiles etc.
+extern int skyflatnum;
+
+
+
+// Netgame stuff (buffers and pointers, i.e. indices).
+
+
+extern int rndindex;
+
+extern ticcmd_t *netcmds;
+
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/doomtype.h b/firmware_p4/components/Applications/doom/doomtype.h
new file mode 100644
index 000000000..dc14bc5da
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/doomtype.h
@@ -0,0 +1,104 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Simple basic typedefs, isolated here to make it easier
+// separating modules.
+//
+
+
+#ifndef __DOOMTYPE__
+#define __DOOMTYPE__
+
+// #define macros to provide functions missing in Windows.
+// Outside Windows, we use strings.h for str[n]casecmp.
+
+
+#ifdef _WIN32
+
+#define strcasecmp _stricmp
+#define strncasecmp _strnicmp
+
+#else
+
+#include
+#include
+#endif
+
+
+//
+// The packed attribute forces structures to be packed into the minimum
+// space necessary. If this is not done, the compiler may align structure
+// fields differently to optimize memory access, inflating the overall
+// structure size. It is important to use the packed attribute on certain
+// structures where alignment is important, particularly data read/written
+// to disk.
+//
+
+#ifdef __GNUC__
+#define PACKEDATTR __attribute__((packed))
+#else
+#define PACKEDATTR
+#endif
+
+// C99 integer types; with gcc we just use this. Other compilers
+// should add conditional statements that define the C99 types.
+
+// What is really wanted here is stdint.h; however, some old versions
+// of Solaris don't have stdint.h and only have inttypes.h (the
+// pre-standardisation version). inttypes.h is also in the C99
+// standard and defined to include stdint.h, so include this.
+
+#include
+
+#if defined(__cplusplus) || defined(__bool_true_false_are_defined)
+
+//boolean is cast to int* in doom. so to keep size the same, make boolean an int.
+typedef unsigned int boolean;
+
+#else
+
+#ifndef __bool_true_false_are_defined
+typedef enum
+{
+ false = 0,
+ true = 1,
+ undef = 0xFFFFFFFF
+} boolean;
+#endif
+
+#endif
+
+typedef uint8_t byte;
+
+#include
+
+#if defined(_WIN32) || defined(__DJGPP__)
+
+#define DIR_SEPARATOR '\\'
+#define DIR_SEPARATOR_S "\\"
+#define PATH_SEPARATOR ';'
+
+#else
+
+#define DIR_SEPARATOR '/'
+#define DIR_SEPARATOR_S "/"
+#define PATH_SEPARATOR ':'
+
+#endif
+
+#define arrlen(array) (sizeof(array) / sizeof(*array))
+
+#endif
+
diff --git a/firmware_p4/components/Applications/doom/dstrings.c b/firmware_p4/components/Applications/doom/dstrings.c
new file mode 100644
index 000000000..b87f198b3
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/dstrings.c
@@ -0,0 +1,73 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Globally defined strings.
+//
+
+
+
+#include "dstrings.h"
+
+char *doom1_endmsg[] =
+{
+ "are you sure you want to\nquit this great game?",
+ "please don't leave, there's more\ndemons to toast!",
+ "let's beat it -- this is turning\ninto a bloodbath!",
+ "i wouldn't leave if i were you.\ndos is much worse.",
+ "you're trying to say you like dos\nbetter than me, right?",
+ "don't leave yet -- there's a\ndemon around that corner!",
+ "ya know, next time you come in here\ni'm gonna toast ya.",
+ "go ahead and leave. see if i care.",
+};
+
+char *doom2_endmsg[] =
+{
+ // QuitDOOM II messages
+ "are you sure you want to\nquit this great game?",
+ "you want to quit?\nthen, thou hast lost an eighth!",
+ "don't go now, there's a \ndimensional shambler waiting\nat the dos prompt!",
+ "get outta here and go back\nto your boring programs.",
+ "if i were your boss, i'd \n deathmatch ya in a minute!",
+ "look, bud. you leave now\nand you forfeit your body count!",
+ "just leave. when you come\nback, i'll be waiting with a bat.",
+ "you're lucky i don't smack\nyou for thinking about leaving.",
+};
+
+#if 0
+
+// UNUSED messages included in the source release
+
+char* endmsg[] =
+{
+ // DOOM1
+ QUITMSG,
+ // FinalDOOM?
+ "fuck you, pussy!\nget the fuck out!",
+ "you quit and i'll jizz\nin your cystholes!",
+ "if you leave, i'll make\nthe lord drink my jizz.",
+ "hey, ron! can we say\n'fuck' in the game?",
+ "i'd leave: this is just\nmore monsters and levels.\nwhat a load.",
+ "suck it down, asshole!\nyou're a fucking wimp!",
+ "don't quit now! we're \nstill spending your money!",
+
+ // Internal debug. Different style, too.
+ "THIS IS NO MESSAGE!\nPage intentionally left blank."
+};
+
+#endif
+
+
+
+
diff --git a/firmware_p4/components/Applications/doom/dstrings.h b/firmware_p4/components/Applications/doom/dstrings.h
new file mode 100644
index 000000000..d3240b499
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/dstrings.h
@@ -0,0 +1,41 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+//
+// DESCRIPTION:
+// DOOM strings, by language.
+//
+
+
+#ifndef __DSTRINGS__
+#define __DSTRINGS__
+
+
+// All important printed strings.
+
+#include "d_englsh.h"
+
+// Misc. other strings.
+#define SAVEGAMENAME "doomsav"
+
+
+// QuitDOOM messages
+// 8 per each game type
+#define NUM_QUITMESSAGES 8
+
+extern char *doom1_endmsg[];
+extern char *doom2_endmsg[];
+
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/dummy.c b/firmware_p4/components/Applications/doom/dummy.c
new file mode 100644
index 000000000..d78816b8c
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/dummy.c
@@ -0,0 +1,53 @@
+/*
+ * dummy.c
+ *
+ * Created on: 16.02.2015
+ * Author: Florian
+ */
+
+
+/*---------------------------------------------------------------------*
+ * include files *
+ *---------------------------------------------------------------------*/
+
+#include "doomtype.h"
+
+/*---------------------------------------------------------------------*
+ * local definitions *
+ *---------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------*
+ * external declarations *
+ *---------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------*
+ * public data *
+ *---------------------------------------------------------------------*/
+
+boolean net_client_connected = false;
+
+boolean drone = false;
+
+/*---------------------------------------------------------------------*
+ * private data *
+ *---------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------*
+ * private functions *
+ *---------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------*
+ * public functions *
+ *---------------------------------------------------------------------*/
+
+#ifndef FEATURE_SOUND
+
+void I_InitTimidityConfig(void)
+{
+}
+
+#endif
+
+/*---------------------------------------------------------------------*
+ * eof *
+ *---------------------------------------------------------------------*/
diff --git a/firmware_p4/components/Applications/doom/f_finale.c b/firmware_p4/components/Applications/doom/f_finale.c
new file mode 100644
index 000000000..ca6775ef8
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/f_finale.c
@@ -0,0 +1,718 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Game completion, final screen animation.
+//
+
+
+#include
+#include
+
+// Functions.
+#include "deh_main.h"
+#include "i_system.h"
+#include "i_swap.h"
+#include "z_zone.h"
+#include "v_video.h"
+#include "w_wad.h"
+#include "s_sound.h"
+
+// Data.
+#include "d_main.h"
+#include "dstrings.h"
+#include "sounds.h"
+
+#include "doomstat.h"
+#include "r_state.h"
+
+typedef enum
+{
+ F_STAGE_TEXT,
+ F_STAGE_ARTSCREEN,
+ F_STAGE_CAST,
+} finalestage_t;
+
+// ?
+//#include "doomstat.h"
+//#include "r_local.h"
+//#include "f_finale.h"
+
+// Stage of animation:
+finalestage_t finalestage;
+
+unsigned int finalecount;
+
+#define TEXTSPEED 3
+#define TEXTWAIT 250
+
+typedef struct
+{
+ GameMission_t mission;
+ int episode, level;
+ char *background;
+ char *text;
+} textscreen_t;
+
+static textscreen_t textscreens[] =
+{
+ { doom, 1, 8, "FLOOR4_8", E1TEXT},
+ { doom, 2, 8, "SFLR6_1", E2TEXT},
+ { doom, 3, 8, "MFLR8_4", E3TEXT},
+ { doom, 4, 8, "MFLR8_3", E4TEXT},
+
+ { doom2, 1, 6, "SLIME16", C1TEXT},
+ { doom2, 1, 11, "RROCK14", C2TEXT},
+ { doom2, 1, 20, "RROCK07", C3TEXT},
+ { doom2, 1, 30, "RROCK17", C4TEXT},
+ { doom2, 1, 15, "RROCK13", C5TEXT},
+ { doom2, 1, 31, "RROCK19", C6TEXT},
+
+ { pack_tnt, 1, 6, "SLIME16", T1TEXT},
+ { pack_tnt, 1, 11, "RROCK14", T2TEXT},
+ { pack_tnt, 1, 20, "RROCK07", T3TEXT},
+ { pack_tnt, 1, 30, "RROCK17", T4TEXT},
+ { pack_tnt, 1, 15, "RROCK13", T5TEXT},
+ { pack_tnt, 1, 31, "RROCK19", T6TEXT},
+
+ { pack_plut, 1, 6, "SLIME16", P1TEXT},
+ { pack_plut, 1, 11, "RROCK14", P2TEXT},
+ { pack_plut, 1, 20, "RROCK07", P3TEXT},
+ { pack_plut, 1, 30, "RROCK17", P4TEXT},
+ { pack_plut, 1, 15, "RROCK13", P5TEXT},
+ { pack_plut, 1, 31, "RROCK19", P6TEXT},
+};
+
+char* finaletext;
+char* finaleflat;
+
+void F_StartCast (void);
+void F_CastTicker (void);
+boolean F_CastResponder (event_t *ev);
+void F_CastDrawer (void);
+
+//
+// F_StartFinale
+//
+void F_StartFinale (void)
+{
+ size_t i;
+
+ gameaction = ga_nothing;
+ gamestate = GS_FINALE;
+ viewactive = false;
+ automapactive = false;
+
+ if (logical_gamemission == doom)
+ {
+ S_ChangeMusic(mus_victor, true);
+ }
+ else
+ {
+ S_ChangeMusic(mus_read_m, true);
+ }
+
+ // Find the right screen and set the text and background
+
+ for (i=0; imission == doom)
+ {
+ screen->level = 5;
+ }
+
+ if (logical_gamemission == screen->mission
+ && (logical_gamemission != doom || gameepisode == screen->episode)
+ && gamemap == screen->level)
+ {
+ finaletext = screen->text;
+ finaleflat = screen->background;
+ }
+ }
+
+ // Do dehacked substitutions of strings
+
+ finaletext = DEH_String(finaletext);
+ finaleflat = DEH_String(finaleflat);
+
+ finalestage = F_STAGE_TEXT;
+ finalecount = 0;
+
+}
+
+
+
+boolean F_Responder (event_t *event)
+{
+ if (finalestage == F_STAGE_CAST)
+ return F_CastResponder (event);
+
+ return false;
+}
+
+
+//
+// F_Ticker
+//
+void F_Ticker (void)
+{
+ size_t i;
+
+ // check for skipping
+ if ( (gamemode == commercial)
+ && ( finalecount > 50) )
+ {
+ // go on to the next level
+ for (i=0 ; istrlen (finaletext)*TEXTSPEED + TEXTWAIT)
+ {
+ finalecount = 0;
+ finalestage = F_STAGE_ARTSCREEN;
+ wipegamestate = -1; // force a wipe
+ if (gameepisode == 3)
+ S_StartMusic (mus_bunny);
+ }
+}
+
+
+
+//
+// F_TextWrite
+//
+
+#include "hu_stuff.h"
+extern patch_t *hu_font[HU_FONTSIZE];
+
+
+void F_TextWrite (void)
+{
+ byte* src;
+ byte* dest;
+
+ int x,y,w;
+ signed int count;
+ char* ch;
+ int c;
+ int cx;
+ int cy;
+
+ // erase the entire screen to a tiled background
+ src = W_CacheLumpName ( finaleflat , PU_CACHE);
+ dest = I_VideoBuffer;
+
+ for (y=0 ; y HU_FONTSIZE)
+ {
+ cx += 4;
+ continue;
+ }
+
+ w = SHORT (hu_font[c]->width);
+ if (cx+w > SCREENWIDTH)
+ break;
+ V_DrawPatch(cx, cy, hu_font[c]);
+ cx+=w;
+ }
+
+}
+
+//
+// Final DOOM 2 animation
+// Casting by id Software.
+// in order of appearance
+//
+typedef struct
+{
+ char *name;
+ mobjtype_t type;
+} castinfo_t;
+
+castinfo_t castorder[] = {
+ {CC_ZOMBIE, MT_POSSESSED},
+ {CC_SHOTGUN, MT_SHOTGUY},
+ {CC_HEAVY, MT_CHAINGUY},
+ {CC_IMP, MT_TROOP},
+ {CC_DEMON, MT_SERGEANT},
+ {CC_LOST, MT_SKULL},
+ {CC_CACO, MT_HEAD},
+ {CC_HELL, MT_KNIGHT},
+ {CC_BARON, MT_BRUISER},
+ {CC_ARACH, MT_BABY},
+ {CC_PAIN, MT_PAIN},
+ {CC_REVEN, MT_UNDEAD},
+ {CC_MANCU, MT_FATSO},
+ {CC_ARCH, MT_VILE},
+ {CC_SPIDER, MT_SPIDER},
+ {CC_CYBER, MT_CYBORG},
+ {CC_HERO, MT_PLAYER},
+
+ {NULL,0}
+};
+
+int castnum;
+int casttics;
+state_t* caststate;
+boolean castdeath;
+int castframes;
+int castonmelee;
+boolean castattacking;
+
+
+//
+// F_StartCast
+//
+void F_StartCast (void)
+{
+ wipegamestate = -1; // force a screen wipe
+ castnum = 0;
+ caststate = &states[mobjinfo[castorder[castnum].type].seestate];
+ casttics = caststate->tics;
+ castdeath = false;
+ finalestage = F_STAGE_CAST;
+ castframes = 0;
+ castonmelee = 0;
+ castattacking = false;
+ S_ChangeMusic(mus_evil, true);
+}
+
+
+//
+// F_CastTicker
+//
+void F_CastTicker (void)
+{
+ int st;
+ int sfx;
+
+ if (--casttics > 0)
+ return; // not time to change state yet
+
+ if (caststate->tics == -1 || caststate->nextstate == S_NULL)
+ {
+ // switch from deathstate to next monster
+ castnum++;
+ castdeath = false;
+ if (castorder[castnum].name == NULL)
+ castnum = 0;
+ if (mobjinfo[castorder[castnum].type].seesound)
+ S_StartSound (NULL, mobjinfo[castorder[castnum].type].seesound);
+ caststate = &states[mobjinfo[castorder[castnum].type].seestate];
+ castframes = 0;
+ }
+ else
+ {
+ // just advance to next state in animation
+ if (caststate == &states[S_PLAY_ATK1])
+ goto stopattack; // Oh, gross hack!
+ st = caststate->nextstate;
+ caststate = &states[st];
+ castframes++;
+
+ // sound hacks....
+ switch (st)
+ {
+ case S_PLAY_ATK1: sfx = sfx_dshtgn; break;
+ case S_POSS_ATK2: sfx = sfx_pistol; break;
+ case S_SPOS_ATK2: sfx = sfx_shotgn; break;
+ case S_VILE_ATK2: sfx = sfx_vilatk; break;
+ case S_SKEL_FIST2: sfx = sfx_skeswg; break;
+ case S_SKEL_FIST4: sfx = sfx_skepch; break;
+ case S_SKEL_MISS2: sfx = sfx_skeatk; break;
+ case S_FATT_ATK8:
+ case S_FATT_ATK5:
+ case S_FATT_ATK2: sfx = sfx_firsht; break;
+ case S_CPOS_ATK2:
+ case S_CPOS_ATK3:
+ case S_CPOS_ATK4: sfx = sfx_shotgn; break;
+ case S_TROO_ATK3: sfx = sfx_claw; break;
+ case S_SARG_ATK2: sfx = sfx_sgtatk; break;
+ case S_BOSS_ATK2:
+ case S_BOS2_ATK2:
+ case S_HEAD_ATK2: sfx = sfx_firsht; break;
+ case S_SKULL_ATK2: sfx = sfx_sklatk; break;
+ case S_SPID_ATK2:
+ case S_SPID_ATK3: sfx = sfx_shotgn; break;
+ case S_BSPI_ATK2: sfx = sfx_plasma; break;
+ case S_CYBER_ATK2:
+ case S_CYBER_ATK4:
+ case S_CYBER_ATK6: sfx = sfx_rlaunc; break;
+ case S_PAIN_ATK3: sfx = sfx_sklatk; break;
+ default: sfx = 0; break;
+ }
+
+ if (sfx)
+ S_StartSound (NULL, sfx);
+ }
+
+ if (castframes == 12)
+ {
+ // go into attack frame
+ castattacking = true;
+ if (castonmelee)
+ caststate=&states[mobjinfo[castorder[castnum].type].meleestate];
+ else
+ caststate=&states[mobjinfo[castorder[castnum].type].missilestate];
+ castonmelee ^= 1;
+ if (caststate == &states[S_NULL])
+ {
+ if (castonmelee)
+ caststate=
+ &states[mobjinfo[castorder[castnum].type].meleestate];
+ else
+ caststate=
+ &states[mobjinfo[castorder[castnum].type].missilestate];
+ }
+ }
+
+ if (castattacking)
+ {
+ if (castframes == 24
+ || caststate == &states[mobjinfo[castorder[castnum].type].seestate] )
+ {
+ stopattack:
+ castattacking = false;
+ castframes = 0;
+ caststate = &states[mobjinfo[castorder[castnum].type].seestate];
+ }
+ }
+
+ casttics = caststate->tics;
+ if (casttics == -1)
+ casttics = 15;
+}
+
+
+//
+// F_CastResponder
+//
+
+boolean F_CastResponder (event_t* ev)
+{
+ if (ev->type != ev_keydown)
+ return false;
+
+ if (castdeath)
+ return true; // already in dying frames
+
+ // go into death frame
+ castdeath = true;
+ caststate = &states[mobjinfo[castorder[castnum].type].deathstate];
+ casttics = caststate->tics;
+ castframes = 0;
+ castattacking = false;
+ if (mobjinfo[castorder[castnum].type].deathsound)
+ S_StartSound (NULL, mobjinfo[castorder[castnum].type].deathsound);
+
+ return true;
+}
+
+
+void F_CastPrint (char* text)
+{
+ char* ch;
+ int c;
+ int cx;
+ int w;
+ int width;
+
+ // find width
+ ch = text;
+ width = 0;
+
+ while (ch)
+ {
+ c = *ch++;
+ if (!c)
+ break;
+ c = toupper(c) - HU_FONTSTART;
+ if (c < 0 || c> HU_FONTSIZE)
+ {
+ width += 4;
+ continue;
+ }
+
+ w = SHORT (hu_font[c]->width);
+ width += w;
+ }
+
+ // draw it
+ cx = 160-width/2;
+ ch = text;
+ while (ch)
+ {
+ c = *ch++;
+ if (!c)
+ break;
+ c = toupper(c) - HU_FONTSTART;
+ if (c < 0 || c> HU_FONTSIZE)
+ {
+ cx += 4;
+ continue;
+ }
+
+ w = SHORT (hu_font[c]->width);
+ V_DrawPatch(cx, 180, hu_font[c]);
+ cx+=w;
+ }
+
+}
+
+
+//
+// F_CastDrawer
+//
+
+void F_CastDrawer (void)
+{
+ spritedef_t* sprdef;
+ spriteframe_t* sprframe;
+ int lump;
+ boolean flip;
+ patch_t* patch;
+
+ // erase the entire screen to a background
+ V_DrawPatch (0, 0, W_CacheLumpName (DEH_String("BOSSBACK"), PU_CACHE));
+
+ F_CastPrint (DEH_String(castorder[castnum].name));
+
+ // draw the current frame in the middle of the screen
+ sprdef = &sprites[caststate->sprite];
+ sprframe = &sprdef->spriteframes[ caststate->frame & FF_FRAMEMASK];
+ lump = sprframe->lump[0];
+ flip = (boolean)sprframe->flip[0];
+
+ patch = W_CacheLumpNum (lump+firstspritelump, PU_CACHE);
+ if (flip)
+ V_DrawPatchFlipped(160, 170, patch);
+ else
+ V_DrawPatch(160, 170, patch);
+}
+
+
+//
+// F_DrawPatchCol
+//
+void
+F_DrawPatchCol
+( int x,
+ patch_t* patch,
+ int col )
+{
+ column_t* column;
+ byte* source;
+ byte* dest;
+ byte* desttop;
+ int count;
+
+ column = (column_t *)((byte *)patch + LONG(patch->columnofs[col]));
+ desttop = I_VideoBuffer + x;
+
+ // step through the posts in a column
+ while (column->topdelta != 0xff )
+ {
+ source = (byte *)column + 3;
+ dest = desttop + column->topdelta*SCREENWIDTH;
+ count = column->length;
+
+ while (count--)
+ {
+ *dest = *source++;
+ dest += SCREENWIDTH;
+ }
+ column = (column_t *)( (byte *)column + column->length + 4 );
+ }
+}
+
+
+//
+// F_BunnyScroll
+//
+void F_BunnyScroll (void)
+{
+ signed int scrolled;
+ int x;
+ patch_t* p1;
+ patch_t* p2;
+ char name[10];
+ int stage;
+ static int laststage;
+
+ p1 = W_CacheLumpName (DEH_String("PFUB2"), PU_LEVEL);
+ p2 = W_CacheLumpName (DEH_String("PFUB1"), PU_LEVEL);
+
+ V_MarkRect (0, 0, SCREENWIDTH, SCREENHEIGHT);
+
+ scrolled = (320 - ((signed int) finalecount-230)/2);
+ if (scrolled > 320)
+ scrolled = 320;
+ if (scrolled < 0)
+ scrolled = 0;
+
+ for ( x=0 ; x 6)
+ stage = 6;
+ if (stage > laststage)
+ {
+ S_StartSound (NULL, sfx_pistol);
+ laststage = stage;
+ }
+
+ DEH_snprintf(name, 10, "END%i", stage);
+ V_DrawPatch((SCREENWIDTH - 13 * 8) / 2,
+ (SCREENHEIGHT - 8 * 8) / 2,
+ W_CacheLumpName (name,PU_CACHE));
+}
+
+static void F_ArtScreenDrawer(void)
+{
+ char *lumpname;
+
+ if (gameepisode == 3)
+ {
+ F_BunnyScroll();
+ }
+ else
+ {
+ switch (gameepisode)
+ {
+ case 1:
+ if (gamemode == retail)
+ {
+ lumpname = "CREDIT";
+ }
+ else
+ {
+ lumpname = "HELP2";
+ }
+ break;
+ case 2:
+ lumpname = "VICTORY2";
+ break;
+ case 4:
+ lumpname = "ENDPIC";
+ break;
+ default:
+ return;
+ }
+
+ lumpname = DEH_String(lumpname);
+
+ V_DrawPatch (0, 0, W_CacheLumpName(lumpname, PU_CACHE));
+ }
+}
+
+//
+// F_Drawer
+//
+void F_Drawer (void)
+{
+ switch (finalestage)
+ {
+ case F_STAGE_CAST:
+ F_CastDrawer();
+ break;
+ case F_STAGE_TEXT:
+ F_TextWrite();
+ break;
+ case F_STAGE_ARTSCREEN:
+ F_ArtScreenDrawer();
+ break;
+ }
+}
+
+
diff --git a/firmware_p4/components/Applications/doom/f_finale.h b/firmware_p4/components/Applications/doom/f_finale.h
new file mode 100644
index 000000000..daa71c32a
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/f_finale.h
@@ -0,0 +1,45 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+//
+//
+
+
+#ifndef __F_FINALE__
+#define __F_FINALE__
+
+
+#include "doomtype.h"
+#include "d_event.h"
+//
+// FINALE
+//
+
+// Called by main loop.
+boolean F_Responder (event_t* ev);
+
+// Called by main loop.
+void F_Ticker (void);
+
+// Called by main loop.
+void F_Drawer (void);
+
+
+void F_StartFinale (void);
+
+
+
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/f_wipe.c b/firmware_p4/components/Applications/doom/f_wipe.c
new file mode 100644
index 000000000..05852fd20
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/f_wipe.c
@@ -0,0 +1,294 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Mission begin melt/wipe screen special effect.
+//
+
+#include
+
+#include "z_zone.h"
+#include "i_video.h"
+#include "v_video.h"
+#include "m_random.h"
+
+#include "doomtype.h"
+
+#include "f_wipe.h"
+
+//
+// SCREEN WIPE PACKAGE
+//
+
+// when zero, stop the wipe
+static boolean go = 0;
+
+static byte* wipe_scr_start;
+static byte* wipe_scr_end;
+static byte* wipe_scr;
+
+
+void
+wipe_shittyColMajorXform
+( short* array,
+ int width,
+ int height )
+{
+ int x;
+ int y;
+ short* dest;
+
+ dest = (short*) Z_Malloc(width*height*2, PU_STATIC, 0);
+
+ for(y=0;y *e)
+ {
+ newval = *w - ticks;
+ if (newval < *e)
+ *w = *e;
+ else
+ *w = newval;
+ changed = true;
+ }
+ else if (*w < *e)
+ {
+ newval = *w + ticks;
+ if (newval > *e)
+ *w = *e;
+ else
+ *w = newval;
+ changed = true;
+ }
+ }
+ w++;
+ e++;
+ }
+
+ return !changed;
+
+}
+
+int
+wipe_exitColorXForm
+( int width,
+ int height,
+ int ticks )
+{
+ return 0;
+}
+
+
+static int* y;
+
+int
+wipe_initMelt
+( int width,
+ int height,
+ int ticks )
+{
+ int i, r;
+
+ // copy start screen to main screen
+ memcpy(wipe_scr, wipe_scr_start, width*height);
+
+ // makes this wipe faster (in theory)
+ // to have stuff in column-major format
+ wipe_shittyColMajorXform((short*)wipe_scr_start, width/2, height);
+ wipe_shittyColMajorXform((short*)wipe_scr_end, width/2, height);
+
+ // setup initial column positions
+ // (y<0 => not ready to scroll yet)
+ y = (int *) Z_Malloc(width*sizeof(int), PU_STATIC, 0);
+ y[0] = -(M_Random()%16);
+ for (i=1;i 0) y[i] = 0;
+ else if (y[i] == -16) y[i] = -15;
+ }
+
+ return 0;
+}
+
+int
+wipe_doMelt
+( int width,
+ int height,
+ int ticks )
+{
+ int i;
+ int j;
+ int dy;
+ int idx;
+
+ short* s;
+ short* d;
+ boolean done = true;
+
+ width/=2;
+
+ while (ticks--)
+ {
+ for (i=0;i= height) dy = height - y[i];
+ s = &((short *)wipe_scr_end)[i*height+y[i]];
+ d = &((short *)wipe_scr)[y[i]*width+i];
+ idx = 0;
+ for (j=dy;j;j--)
+ {
+ d[idx] = *(s++);
+ idx += width;
+ }
+ y[i] += dy;
+ s = &((short *)wipe_scr_start)[i*height];
+ d = &((short *)wipe_scr)[y[i]*width+i];
+ idx = 0;
+ for (j=height-y[i];j;j--)
+ {
+ d[idx] = *(s++);
+ idx += width;
+ }
+ done = false;
+ }
+ }
+ }
+
+ return done;
+
+}
+
+int
+wipe_exitMelt
+( int width,
+ int height,
+ int ticks )
+{
+ Z_Free(y);
+ Z_Free(wipe_scr_start);
+ Z_Free(wipe_scr_end);
+ return 0;
+}
+
+int
+wipe_StartScreen
+( int x,
+ int y,
+ int width,
+ int height )
+{
+ wipe_scr_start = Z_Malloc(SCREENWIDTH * SCREENHEIGHT, PU_STATIC, NULL);
+ I_ReadScreen(wipe_scr_start);
+ return 0;
+}
+
+int
+wipe_EndScreen
+( int x,
+ int y,
+ int width,
+ int height )
+{
+ wipe_scr_end = Z_Malloc(SCREENWIDTH * SCREENHEIGHT, PU_STATIC, NULL);
+ I_ReadScreen(wipe_scr_end);
+ V_DrawBlock(x, y, width, height, wipe_scr_start); // restore start scr.
+ return 0;
+}
+
+int
+wipe_ScreenWipe
+( int wipeno,
+ int x,
+ int y,
+ int width,
+ int height,
+ int ticks )
+{
+ int rc;
+ static int (*wipes[])(int, int, int) =
+ {
+ wipe_initColorXForm, wipe_doColorXForm, wipe_exitColorXForm,
+ wipe_initMelt, wipe_doMelt, wipe_exitMelt
+ };
+
+ // initial stuff
+ if (!go)
+ {
+ go = 1;
+ // wipe_scr = (byte *) Z_Malloc(width*height, PU_STATIC, 0); // DEBUG
+ wipe_scr = I_VideoBuffer;
+ (*wipes[wipeno*3])(width, height, ticks);
+ }
+
+ // do a piece of wipe-in
+ V_MarkRect(0, 0, width, height);
+ rc = (*wipes[wipeno*3+1])(width, height, ticks);
+ // V_DrawBlock(x, y, 0, width, height, wipe_scr); // DEBUG
+
+ // final stuff
+ if (rc)
+ {
+ go = 0;
+ (*wipes[wipeno*3+2])(width, height, ticks);
+ }
+
+ return !go;
+}
+
diff --git a/firmware_p4/components/Applications/doom/f_wipe.h b/firmware_p4/components/Applications/doom/f_wipe.h
new file mode 100644
index 000000000..f48a9ca65
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/f_wipe.h
@@ -0,0 +1,63 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Mission start screen wipe/melt, special effects.
+//
+
+
+#ifndef __F_WIPE_H__
+#define __F_WIPE_H__
+
+//
+// SCREEN WIPE PACKAGE
+//
+
+enum
+{
+ // simple gradual pixel change for 8-bit only
+ wipe_ColorXForm,
+
+ // weird screen melt
+ wipe_Melt,
+
+ wipe_NUMWIPES
+};
+
+int
+wipe_StartScreen
+( int x,
+ int y,
+ int width,
+ int height );
+
+
+int
+wipe_EndScreen
+( int x,
+ int y,
+ int width,
+ int height );
+
+
+int
+wipe_ScreenWipe
+( int wipeno,
+ int x,
+ int y,
+ int width,
+ int height,
+ int ticks );
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/g_game.c b/firmware_p4/components/Applications/doom/g_game.c
new file mode 100644
index 000000000..9954d7890
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/g_game.c
@@ -0,0 +1,2303 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION: none
+//
+
+
+
+#include
+#include
+#include
+
+#include "doomdef.h"
+#include "doomkeys.h"
+#include "doomstat.h"
+
+#include "deh_main.h"
+#include "deh_misc.h"
+
+#include "z_zone.h"
+#include "f_finale.h"
+#include "m_argv.h"
+#include "m_controls.h"
+#include "m_misc.h"
+#include "m_menu.h"
+#include "m_random.h"
+#include "i_system.h"
+#include "i_timer.h"
+#include "i_video.h"
+
+#include "p_setup.h"
+#include "p_saveg.h"
+#include "p_tick.h"
+
+#include "d_main.h"
+
+#include "wi_stuff.h"
+#include "hu_stuff.h"
+#include "st_stuff.h"
+#include "am_map.h"
+#include "statdump.h"
+
+// Needs access to LFB.
+#include "v_video.h"
+
+#include "w_wad.h"
+
+#include "p_local.h"
+
+#include "s_sound.h"
+
+// Data.
+#include "dstrings.h"
+#include "sounds.h"
+
+// SKY handling - still the wrong place.
+#include "r_data.h"
+#include "r_sky.h"
+
+
+
+#include "g_game.h"
+
+
+#define SAVEGAMESIZE 0x2c000
+
+void G_ReadDemoTiccmd (ticcmd_t* cmd);
+void G_WriteDemoTiccmd (ticcmd_t* cmd);
+void G_PlayerReborn (int player);
+
+void G_DoReborn (int playernum);
+
+void G_DoLoadLevel (void);
+void G_DoNewGame (void);
+void G_DoPlayDemo (void);
+void G_DoCompleted (void);
+void G_DoVictory (void);
+void G_DoWorldDone (void);
+void G_DoSaveGame (void);
+
+// Gamestate the last time G_Ticker was called.
+
+gamestate_t oldgamestate;
+
+gameaction_t gameaction;
+gamestate_t gamestate;
+skill_t gameskill;
+boolean respawnmonsters;
+int gameepisode;
+int gamemap;
+
+// If non-zero, exit the level after this number of minutes.
+
+int timelimit;
+
+boolean paused;
+boolean sendpause; // send a pause event next tic
+boolean sendsave; // send a save event next tic
+boolean usergame; // ok to save / end game
+
+boolean timingdemo; // if true, exit with report on completion
+boolean nodrawers; // for comparative timing purposes
+int starttime; // for comparative timing purposes
+
+boolean viewactive;
+
+int deathmatch; // only if started as net death
+boolean netgame; // only true if packets are broadcast
+boolean playeringame[MAXPLAYERS];
+player_t players[MAXPLAYERS];
+
+boolean turbodetected[MAXPLAYERS];
+
+int consoleplayer; // player taking events and displaying
+int displayplayer; // view being displayed
+int levelstarttic; // gametic at level start
+int totalkills, totalitems, totalsecret; // for intermission
+
+char *demoname;
+boolean demorecording;
+boolean longtics; // cph's doom 1.91 longtics hack
+boolean lowres_turn; // low resolution turning for longtics
+boolean demoplayback;
+boolean netdemo;
+byte* demobuffer;
+byte* demo_p;
+byte* demoend;
+boolean singledemo; // quit after playing a demo from cmdline
+
+boolean precache = true; // if true, load all graphics at start
+
+boolean testcontrols = false; // Invoked by setup to test controls
+int testcontrols_mousespeed;
+
+
+
+wbstartstruct_t wminfo; // parms for world map / intermission
+
+byte consistancy[MAXPLAYERS][BACKUPTICS];
+
+#define MAXPLMOVE (forwardmove[1])
+
+#define TURBOTHRESHOLD 0x32
+
+fixed_t forwardmove[2] = {0x19, 0x32};
+fixed_t sidemove[2] = {0x18, 0x28};
+fixed_t angleturn[3] = {640, 1280, 320}; // + slow turn
+
+static int *weapon_keys[] = {
+ &key_weapon1,
+ &key_weapon2,
+ &key_weapon3,
+ &key_weapon4,
+ &key_weapon5,
+ &key_weapon6,
+ &key_weapon7,
+ &key_weapon8
+};
+
+// Set to -1 or +1 to switch to the previous or next weapon.
+
+static int next_weapon = 0;
+
+// Used for prev/next weapon keys.
+
+static const struct
+{
+ weapontype_t weapon;
+ weapontype_t weapon_num;
+} weapon_order_table[] = {
+ { wp_fist, wp_fist },
+ { wp_chainsaw, wp_fist },
+ { wp_pistol, wp_pistol },
+ { wp_shotgun, wp_shotgun },
+ { wp_supershotgun, wp_shotgun },
+ { wp_chaingun, wp_chaingun },
+ { wp_missile, wp_missile },
+ { wp_plasma, wp_plasma },
+ { wp_bfg, wp_bfg }
+};
+
+#define SLOWTURNTICS 6
+
+#define NUMKEYS 256
+#define MAX_JOY_BUTTONS 20
+
+static boolean gamekeydown[NUMKEYS];
+static int turnheld; // for accelerative turning
+
+static boolean mousearray[MAX_MOUSE_BUTTONS + 1];
+static boolean *mousebuttons = &mousearray[1]; // allow [-1]
+
+// mouse values are used once
+int mousex;
+int mousey;
+
+static int dclicktime;
+static boolean dclickstate;
+static int dclicks;
+static int dclicktime2;
+static boolean dclickstate2;
+static int dclicks2;
+
+// joystick values are repeated
+static int joyxmove;
+static int joyymove;
+static int joystrafemove;
+static boolean joyarray[MAX_JOY_BUTTONS + 1];
+static boolean *joybuttons = &joyarray[1]; // allow [-1]
+
+static int savegameslot;
+static char savedescription[32];
+
+#define BODYQUESIZE 32
+
+mobj_t* bodyque[BODYQUESIZE];
+int bodyqueslot;
+
+int vanilla_savegame_limit = 1;
+int vanilla_demo_limit = 1;
+
+int G_CmdChecksum (ticcmd_t* cmd)
+{
+ size_t i;
+ int sum = 0;
+
+ for (i=0 ; i< sizeof(*cmd)/4 - 1 ; i++)
+ sum += ((int *)cmd)[i];
+
+ return sum;
+}
+
+static boolean WeaponSelectable(weapontype_t weapon)
+{
+ // Can't select the super shotgun in Doom 1.
+
+ if (weapon == wp_supershotgun && logical_gamemission == doom)
+ {
+ return false;
+ }
+
+ // These weapons aren't available in shareware.
+
+ if ((weapon == wp_plasma || weapon == wp_bfg)
+ && gamemission == doom && gamemode == shareware)
+ {
+ return false;
+ }
+
+ // Can't select a weapon if we don't own it.
+
+ if (!players[consoleplayer].weaponowned[weapon])
+ {
+ return false;
+ }
+
+ // Can't select the fist if we have the chainsaw, unless
+ // we also have the berserk pack.
+
+ if (weapon == wp_fist
+ && players[consoleplayer].weaponowned[wp_chainsaw]
+ && !players[consoleplayer].powers[pw_strength])
+ {
+ return false;
+ }
+
+ return true;
+}
+
+static int G_NextWeapon(int direction)
+{
+ weapontype_t weapon;
+ int start_i, i;
+
+ // Find index in the table.
+
+ if (players[consoleplayer].pendingweapon == wp_nochange)
+ {
+ weapon = players[consoleplayer].readyweapon;
+ }
+ else
+ {
+ weapon = players[consoleplayer].pendingweapon;
+ }
+
+ for (i=0; iconsistancy =
+ consistancy[consoleplayer][maketic%BACKUPTICS];
+
+ strafe = gamekeydown[key_strafe] || mousebuttons[mousebstrafe]
+ || joybuttons[joybstrafe];
+
+ // fraggle: support the old "joyb_speed = 31" hack which
+ // allowed an autorun effect
+
+ speed = key_speed >= NUMKEYS
+ || joybspeed >= MAX_JOY_BUTTONS
+ || gamekeydown[key_speed]
+ || joybuttons[joybspeed];
+
+ forward = side = 0;
+
+ // use two stage accelerative turning
+ // on the keyboard and joystick
+ if (joyxmove < 0
+ || joyxmove > 0
+ || gamekeydown[key_right]
+ || gamekeydown[key_left])
+ turnheld += ticdup;
+ else
+ turnheld = 0;
+
+ if (turnheld < SLOWTURNTICS)
+ tspeed = 2; // slow turn
+ else
+ tspeed = speed;
+
+ // let movement keys cancel each other out
+ if (strafe)
+ {
+ if (gamekeydown[key_right])
+ {
+ // fprintf(stderr, "strafe right\n");
+ side += sidemove[speed];
+ }
+ if (gamekeydown[key_left])
+ {
+ // fprintf(stderr, "strafe left\n");
+ side -= sidemove[speed];
+ }
+ if (joyxmove > 0)
+ side += sidemove[speed];
+ if (joyxmove < 0)
+ side -= sidemove[speed];
+
+ }
+ else
+ {
+ if (gamekeydown[key_right])
+ cmd->angleturn -= angleturn[tspeed];
+ if (gamekeydown[key_left])
+ cmd->angleturn += angleturn[tspeed];
+ if (joyxmove > 0)
+ cmd->angleturn -= angleturn[tspeed];
+ if (joyxmove < 0)
+ cmd->angleturn += angleturn[tspeed];
+ }
+
+ if (gamekeydown[key_up])
+ {
+ // fprintf(stderr, "up\n");
+ forward += forwardmove[speed];
+ }
+ if (gamekeydown[key_down])
+ {
+ // fprintf(stderr, "down\n");
+ forward -= forwardmove[speed];
+ }
+
+ if (joyymove < 0)
+ forward += forwardmove[speed];
+ if (joyymove > 0)
+ forward -= forwardmove[speed];
+
+ if (gamekeydown[key_strafeleft]
+ || joybuttons[joybstrafeleft]
+ || mousebuttons[mousebstrafeleft]
+ || joystrafemove < 0)
+ {
+ side -= sidemove[speed];
+ }
+
+ if (gamekeydown[key_straferight]
+ || joybuttons[joybstraferight]
+ || mousebuttons[mousebstraferight]
+ || joystrafemove > 0)
+ {
+ side += sidemove[speed];
+ }
+
+ // buttons
+ cmd->chatchar = HU_dequeueChatChar();
+
+ if (gamekeydown[key_fire] || mousebuttons[mousebfire]
+ || joybuttons[joybfire])
+ cmd->buttons |= BT_ATTACK;
+
+ if (gamekeydown[key_use]
+ || joybuttons[joybuse]
+ || mousebuttons[mousebuse])
+ {
+ cmd->buttons |= BT_USE;
+ // clear double clicks if hit use button
+ dclicks = 0;
+ }
+
+ // If the previous or next weapon button is pressed, the
+ // next_weapon variable is set to change weapons when
+ // we generate a ticcmd. Choose a new weapon.
+
+ if (gamestate == GS_LEVEL && next_weapon != 0)
+ {
+ i = G_NextWeapon(next_weapon);
+ cmd->buttons |= BT_CHANGE;
+ cmd->buttons |= i << BT_WEAPONSHIFT;
+ }
+ else
+ {
+ // Check weapon keys.
+
+ for (i=0; ibuttons |= BT_CHANGE;
+ cmd->buttons |= i< 1 )
+ {
+ dclickstate = mousebuttons[mousebforward];
+ if (dclickstate)
+ dclicks++;
+ if (dclicks == 2)
+ {
+ cmd->buttons |= BT_USE;
+ dclicks = 0;
+ }
+ else
+ dclicktime = 0;
+ }
+ else
+ {
+ dclicktime += ticdup;
+ if (dclicktime > 20)
+ {
+ dclicks = 0;
+ dclickstate = 0;
+ }
+ }
+
+ // strafe double click
+ bstrafe =
+ mousebuttons[mousebstrafe]
+ || joybuttons[joybstrafe];
+ if (bstrafe != dclickstate2 && dclicktime2 > 1 )
+ {
+ dclickstate2 = bstrafe;
+ if (dclickstate2)
+ dclicks2++;
+ if (dclicks2 == 2)
+ {
+ cmd->buttons |= BT_USE;
+ dclicks2 = 0;
+ }
+ else
+ dclicktime2 = 0;
+ }
+ else
+ {
+ dclicktime2 += ticdup;
+ if (dclicktime2 > 20)
+ {
+ dclicks2 = 0;
+ dclickstate2 = 0;
+ }
+ }
+ }
+
+ forward += mousey;
+
+ if (strafe)
+ side += mousex*2;
+ else
+ cmd->angleturn -= mousex*0x8;
+
+ if (mousex == 0)
+ {
+ // No movement in the previous frame
+
+ testcontrols_mousespeed = 0;
+ }
+
+ mousex = mousey = 0;
+
+ if (forward > MAXPLMOVE)
+ forward = MAXPLMOVE;
+ else if (forward < -MAXPLMOVE)
+ forward = -MAXPLMOVE;
+ if (side > MAXPLMOVE)
+ side = MAXPLMOVE;
+ else if (side < -MAXPLMOVE)
+ side = -MAXPLMOVE;
+
+ cmd->forwardmove += forward;
+ cmd->sidemove += side;
+
+ // special buttons
+ if (sendpause)
+ {
+ sendpause = false;
+ cmd->buttons = BT_SPECIAL | BTS_PAUSE;
+ }
+
+ if (sendsave)
+ {
+ sendsave = false;
+ cmd->buttons = BT_SPECIAL | BTS_SAVEGAME | (savegameslot<angleturn + carry;
+
+ // round angleturn to the nearest 256 unit boundary
+ // for recording demos with single byte values for turn
+
+ cmd->angleturn = (desired_angleturn + 128) & 0xff00;
+
+ // Carry forward the error from the reduced resolution to the
+ // next tic, so that successive small movements can accumulate.
+
+ carry = desired_angleturn - cmd->angleturn;
+ }
+}
+
+
+//
+// G_DoLoadLevel
+//
+void G_DoLoadLevel (void)
+{
+ int i;
+
+ // Set the sky map.
+ // First thing, we have a dummy sky texture name,
+ // a flat. The data is in the WAD only because
+ // we look for an actual index, instead of simply
+ // setting one.
+
+ skyflatnum = R_FlatNumForName(DEH_String(SKYFLATNAME));
+
+ // The "Sky never changes in Doom II" bug was fixed in
+ // the id Anthology version of doom2.exe for Final Doom.
+ if ((gamemode == commercial)
+ && (gameversion == exe_final2 || gameversion == exe_chex))
+ {
+ char *skytexturename;
+
+ if (gamemap < 12)
+ {
+ skytexturename = "SKY1";
+ }
+ else if (gamemap < 21)
+ {
+ skytexturename = "SKY2";
+ }
+ else
+ {
+ skytexturename = "SKY3";
+ }
+
+ skytexturename = DEH_String(skytexturename);
+
+ skytexture = R_TextureNumForName(skytexturename);
+ }
+
+ levelstarttic = gametic; // for time calculation
+
+ if (wipegamestate == GS_LEVEL)
+ wipegamestate = -1; // force a wipe
+
+ gamestate = GS_LEVEL;
+
+ for (i=0 ; itype == ev_keydown
+ && ev->data1 == key_spy && (singledemo || !deathmatch) )
+ {
+ // spy mode
+ do
+ {
+ displayplayer++;
+ if (displayplayer == MAXPLAYERS)
+ displayplayer = 0;
+ } while (!playeringame[displayplayer] && displayplayer != consoleplayer);
+ return true;
+ }
+
+ // any other key pops up menu if in demos
+ if (gameaction == ga_nothing && !singledemo &&
+ (demoplayback || gamestate == GS_DEMOSCREEN)
+ )
+ {
+ if (ev->type == ev_keydown ||
+ (ev->type == ev_mouse && ev->data1) ||
+ (ev->type == ev_joystick && ev->data1) )
+ {
+ M_StartControlPanel ();
+ return true;
+ }
+ return false;
+ }
+
+ if (gamestate == GS_LEVEL)
+ {
+#if 0
+ if (devparm && ev->type == ev_keydown && ev->data1 == ';')
+ {
+ G_DeathMatchSpawnPlayer (0);
+ return true;
+ }
+#endif
+ if (HU_Responder (ev))
+ return true; // chat ate the event
+ if (ST_Responder (ev))
+ return true; // status window ate it
+ if (AM_Responder (ev))
+ return true; // automap ate it
+ }
+
+ if (gamestate == GS_FINALE)
+ {
+ if (F_Responder (ev))
+ return true; // finale ate the event
+ }
+
+ if (testcontrols && ev->type == ev_mouse)
+ {
+ // If we are invoked by setup to test the controls, save the
+ // mouse speed so that we can display it on-screen.
+ // Perform a low pass filter on this so that the thermometer
+ // appears to move smoothly.
+
+ testcontrols_mousespeed = abs(ev->data2);
+ }
+
+ // If the next/previous weapon keys are pressed, set the next_weapon
+ // variable to change weapons when the next ticcmd is generated.
+
+ if (ev->type == ev_keydown && ev->data1 == key_prevweapon)
+ {
+ next_weapon = -1;
+ }
+ else if (ev->type == ev_keydown && ev->data1 == key_nextweapon)
+ {
+ next_weapon = 1;
+ }
+
+ switch (ev->type)
+ {
+ case ev_keydown:
+ if (ev->data1 == key_pause)
+ {
+ sendpause = true;
+ }
+ else if (ev->data1 data1] = true;
+ }
+
+ return true; // eat key down events
+
+ case ev_keyup:
+ if (ev->data1 data1] = false;
+ return false; // always let key up events filter down
+
+ case ev_mouse:
+ SetMouseButtons(ev->data1);
+ mousex = ev->data2*(mouseSensitivity+5)/10;
+ mousey = ev->data3*(mouseSensitivity+5)/10;
+ return true; // eat events
+
+ case ev_joystick:
+ SetJoyButtons(ev->data1);
+ joyxmove = ev->data2;
+ joyymove = ev->data3;
+ joystrafemove = ev->data4;
+ return true; // eat events
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+
+
+//
+// G_Ticker
+// Make ticcmd_ts for the players.
+//
+void G_Ticker (void)
+{
+ int i;
+ int buf;
+ ticcmd_t* cmd;
+
+ // do player reborns if needed
+ for (i=0 ; iforwardmove > TURBOTHRESHOLD)
+ {
+ turbodetected[i] = true;
+ }
+
+ if ((gametic & 31) == 0
+ && ((gametic >> 5) % MAXPLAYERS) == i
+ && turbodetected[i])
+ {
+ static char turbomessage[80];
+ extern char *player_names[4];
+ M_snprintf(turbomessage, sizeof(turbomessage),
+ "%s is turbo!", player_names[i]);
+ players[consoleplayer].message = turbomessage;
+ turbodetected[i] = false;
+ }
+
+ if (netgame && !netdemo && !(gametic%ticdup) )
+ {
+ if (gametic > BACKUPTICS
+ && consistancy[i][buf] != cmd->consistancy)
+ {
+ I_Error ("consistency failure (%i should be %i)",
+ cmd->consistancy, consistancy[i][buf]);
+ }
+ if (players[i].mo)
+ consistancy[i][buf] = players[i].mo->x;
+ else
+ consistancy[i][buf] = rndindex;
+ }
+ }
+ }
+
+ // check for special buttons
+ for (i=0 ; i>BTS_SAVESHIFT;
+ gameaction = ga_savegame;
+ break;
+ }
+ }
+ }
+ }
+
+ // Have we just finished displaying an intermission screen?
+
+ if (oldgamestate == GS_INTERMISSION && gamestate != GS_INTERMISSION)
+ {
+ WI_End();
+ }
+
+ oldgamestate = gamestate;
+
+ // do main actions
+ switch (gamestate)
+ {
+ case GS_LEVEL:
+ P_Ticker ();
+ ST_Ticker ();
+ AM_Ticker ();
+ HU_Ticker ();
+ break;
+
+ case GS_INTERMISSION:
+ WI_Ticker ();
+ break;
+
+ case GS_FINALE:
+ F_Ticker ();
+ break;
+
+ case GS_DEMOSCREEN:
+ D_PageTicker ();
+ break;
+ }
+}
+
+
+//
+// PLAYER STRUCTURE FUNCTIONS
+// also see P_SpawnPlayer in P_Things
+//
+
+//
+// G_InitPlayer
+// Called at the start.
+// Called by the game initialization functions.
+//
+void G_InitPlayer (int player)
+{
+ // clear everything else to defaults
+ G_PlayerReborn (player);
+}
+
+
+
+//
+// G_PlayerFinishLevel
+// Can when a player completes a level.
+//
+void G_PlayerFinishLevel (int player)
+{
+ player_t* p;
+
+ p = &players[player];
+
+ memset (p->powers, 0, sizeof (p->powers));
+ memset (p->cards, 0, sizeof (p->cards));
+ p->mo->flags &= ~MF_SHADOW; // cancel invisibility
+ p->extralight = 0; // cancel gun flashes
+ p->fixedcolormap = 0; // cancel ir gogles
+ p->damagecount = 0; // no palette changes
+ p->bonuscount = 0;
+}
+
+
+//
+// G_PlayerReborn
+// Called after a player dies
+// almost everything is cleared and initialized
+//
+void G_PlayerReborn (int player)
+{
+ player_t* p;
+ int i;
+ int frags[MAXPLAYERS];
+ int killcount;
+ int itemcount;
+ int secretcount;
+
+ memcpy (frags,players[player].frags,sizeof(frags));
+ killcount = players[player].killcount;
+ itemcount = players[player].itemcount;
+ secretcount = players[player].secretcount;
+
+ p = &players[player];
+ memset (p, 0, sizeof(*p));
+
+ memcpy (players[player].frags, frags, sizeof(players[player].frags));
+ players[player].killcount = killcount;
+ players[player].itemcount = itemcount;
+ players[player].secretcount = secretcount;
+
+ p->usedown = p->attackdown = true; // don't do anything immediately
+ p->playerstate = PST_LIVE;
+ p->health = deh_initial_health; // Use dehacked value
+ p->readyweapon = p->pendingweapon = wp_pistol;
+ p->weaponowned[wp_fist] = true;
+ p->weaponowned[wp_pistol] = true;
+ p->ammo[am_clip] = deh_initial_bullets;
+
+ for (i=0 ; imaxammo[i] = maxammo[i];
+
+}
+
+//
+// G_CheckSpot
+// Returns false if the player cannot be respawned
+// at the given mapthing_t spot
+// because something is occupying it
+//
+void P_SpawnPlayer (mapthing_t* mthing);
+
+boolean
+G_CheckSpot
+( int playernum,
+ mapthing_t* mthing )
+{
+ fixed_t x;
+ fixed_t y;
+ subsector_t* ss;
+ mobj_t* mo;
+ int i;
+
+ if (!players[playernum].mo)
+ {
+ // first spawn of level, before corpses
+ for (i=0 ; ix == mthing->x << FRACBITS
+ && players[i].mo->y == mthing->y << FRACBITS)
+ return false;
+ return true;
+ }
+
+ x = mthing->x << FRACBITS;
+ y = mthing->y << FRACBITS;
+
+ if (!P_CheckPosition (players[playernum].mo, x, y) )
+ return false;
+
+ // flush an old corpse if needed
+ if (bodyqueslot >= BODYQUESIZE)
+ P_RemoveMobj (bodyque[bodyqueslot%BODYQUESIZE]);
+ bodyque[bodyqueslot%BODYQUESIZE] = players[playernum].mo;
+ bodyqueslot++;
+
+ // spawn a teleport fog
+ ss = R_PointInSubsector (x,y);
+
+
+ // The code in the released source looks like this:
+ //
+ // an = ( ANG45 * (((unsigned int) mthing->angle)/45) )
+ // >> ANGLETOFINESHIFT;
+ // mo = P_SpawnMobj (x+20*finecosine[an], y+20*finesine[an]
+ // , ss->sector->floorheight
+ // , MT_TFOG);
+ //
+ // But 'an' can be a signed value in the DOS version. This means that
+ // we get a negative index and the lookups into finecosine/finesine
+ // end up dereferencing values in finetangent[].
+ // A player spawning on a deathmatch start facing directly west spawns
+ // "silently" with no spawn fog. Emulate this.
+ //
+ // This code is imported from PrBoom+.
+
+ {
+ fixed_t xa, ya;
+ signed int an;
+
+ // This calculation overflows in Vanilla Doom, but here we deliberately
+ // avoid integer overflow as it is undefined behavior, so the value of
+ // 'an' will always be positive.
+ an = (ANG45 >> ANGLETOFINESHIFT) * ((signed int) mthing->angle / 45);
+
+ switch (an)
+ {
+ case 4096: // -4096:
+ xa = finetangent[2048]; // finecosine[-4096]
+ ya = finetangent[0]; // finesine[-4096]
+ break;
+ case 5120: // -3072:
+ xa = finetangent[3072]; // finecosine[-3072]
+ ya = finetangent[1024]; // finesine[-3072]
+ break;
+ case 6144: // -2048:
+ xa = finesine[0]; // finecosine[-2048]
+ ya = finetangent[2048]; // finesine[-2048]
+ break;
+ case 7168: // -1024:
+ xa = finesine[1024]; // finecosine[-1024]
+ ya = finetangent[3072]; // finesine[-1024]
+ break;
+ case 0:
+ case 1024:
+ case 2048:
+ case 3072:
+ xa = finecosine[an];
+ ya = finesine[an];
+ break;
+ default:
+ I_Error("G_CheckSpot: unexpected angle %d\n", an);
+ xa = ya = 0;
+ break;
+ }
+ mo = P_SpawnMobj(x + 20 * xa, y + 20 * ya,
+ ss->sector->floorheight, MT_TFOG);
+ }
+
+ if (players[consoleplayer].viewz != 1)
+ S_StartSound (mo, sfx_telept); // don't start sound on first frame
+
+ return true;
+}
+
+
+//
+// G_DeathMatchSpawnPlayer
+// Spawns a player at one of the random death match spots
+// called at level load and each death
+//
+void G_DeathMatchSpawnPlayer (int playernum)
+{
+ int i,j;
+ int selections;
+
+ selections = deathmatch_p - deathmatchstarts;
+ if (selections < 4)
+ I_Error ("Only %i deathmatch spots, 4 required", selections);
+
+ for (j=0 ; j<20 ; j++)
+ {
+ i = P_Random() % selections;
+ if (G_CheckSpot (playernum, &deathmatchstarts[i]) )
+ {
+ deathmatchstarts[i].type = playernum+1;
+ P_SpawnPlayer (&deathmatchstarts[i]);
+ return;
+ }
+ }
+
+ // no good spot, so the player will probably get stuck
+ P_SpawnPlayer (&playerstarts[playernum]);
+}
+
+//
+// G_DoReborn
+//
+void G_DoReborn (int playernum)
+{
+ int i;
+
+ if (!netgame)
+ {
+ // reload the level from scratch
+ gameaction = ga_loadlevel;
+ }
+ else
+ {
+ // respawn at the start
+
+ // first dissasociate the corpse
+ players[playernum].mo->player = NULL;
+
+ // spawn at random spot if in death match
+ if (deathmatch)
+ {
+ G_DeathMatchSpawnPlayer (playernum);
+ return;
+ }
+
+ if (G_CheckSpot (playernum, &playerstarts[playernum]) )
+ {
+ P_SpawnPlayer (&playerstarts[playernum]);
+ return;
+ }
+
+ // try to spawn at one of the other players spots
+ for (i=0 ; i SAVEGAMESIZE)
+ {
+ I_Error ("Savegame buffer overrun");
+ }
+
+ // Finish up, close the savegame file.
+
+ fclose(save_stream);
+
+ if (recovery_savegame_file != NULL)
+ {
+ // We failed to save to the normal location, but we wrote a
+ // recovery file to the temp directory. Now we can bomb out
+ // with an error.
+ I_Error("Failed to open savegame file '%s' for writing.\n"
+ "But your game has been saved to '%s' for recovery.",
+ temp_savegame_file, recovery_savegame_file);
+ }
+
+ // Now rename the temporary savegame file to the actual savegame
+ // file, overwriting the old savegame if there was one there.
+
+ remove(savegame_file);
+ rename(temp_savegame_file, savegame_file);
+
+ gameaction = ga_nothing;
+ M_StringCopy(savedescription, "", sizeof(savedescription));
+
+ players[consoleplayer].message = DEH_String(GGSAVED);
+
+ // draw the pattern into the back screen
+ R_FillBackScreen ();
+}
+
+
+//
+// G_InitNew
+// Can be called by the startup code or the menu task,
+// consoleplayer, displayplayer, playeringame[] should be set.
+//
+skill_t d_skill;
+int d_episode;
+int d_map;
+
+void
+G_DeferedInitNew
+( skill_t skill,
+ int episode,
+ int map)
+{
+ d_skill = skill;
+ d_episode = episode;
+ d_map = map;
+ gameaction = ga_newgame;
+}
+
+
+void G_DoNewGame (void)
+{
+ demoplayback = false;
+ netdemo = false;
+ netgame = false;
+ deathmatch = false;
+ playeringame[1] = playeringame[2] = playeringame[3] = 0;
+ respawnparm = false;
+ fastparm = false;
+ nomonsters = false;
+ consoleplayer = 0;
+ G_InitNew (d_skill, d_episode, d_map);
+ gameaction = ga_nothing;
+}
+
+
+void
+G_InitNew
+( skill_t skill,
+ int episode,
+ int map )
+{
+ char *skytexturename;
+ int i;
+
+ if (paused)
+ {
+ paused = false;
+ S_ResumeSound ();
+ }
+
+ /*
+ // Note: This commented-out block of code was added at some point
+ // between the DOS version(s) and the Doom source release. It isn't
+ // found in disassemblies of the DOS version and causes IDCLEV and
+ // the -warp command line parameter to behave differently.
+ // This is left here for posterity.
+
+ // This was quite messy with SPECIAL and commented parts.
+ // Supposedly hacks to make the latest edition work.
+ // It might not work properly.
+ if (episode < 1)
+ episode = 1;
+
+ if ( gamemode == retail )
+ {
+ if (episode > 4)
+ episode = 4;
+ }
+ else if ( gamemode == shareware )
+ {
+ if (episode > 1)
+ episode = 1; // only start episode 1 on shareware
+ }
+ else
+ {
+ if (episode > 3)
+ episode = 3;
+ }
+ */
+
+ if (skill > sk_nightmare)
+ skill = sk_nightmare;
+
+ if (gameversion >= exe_ultimate)
+ {
+ if (episode == 0)
+ {
+ episode = 4;
+ }
+ }
+ else
+ {
+ if (episode < 1)
+ {
+ episode = 1;
+ }
+ if (episode > 3)
+ {
+ episode = 3;
+ }
+ }
+
+ if (episode > 1 && gamemode == shareware)
+ {
+ episode = 1;
+ }
+
+ if (map < 1)
+ map = 1;
+
+ if ( (map > 9)
+ && ( gamemode != commercial) )
+ map = 9;
+
+ M_ClearRandom ();
+
+ if (skill == sk_nightmare || respawnparm )
+ respawnmonsters = true;
+ else
+ respawnmonsters = false;
+
+ if (fastparm || (skill == sk_nightmare && gameskill != sk_nightmare) )
+ {
+ for (i=S_SARG_RUN1 ; i<=S_SARG_PAIN2 ; i++)
+ states[i].tics >>= 1;
+ mobjinfo[MT_BRUISERSHOT].speed = 20*FRACUNIT;
+ mobjinfo[MT_HEADSHOT].speed = 20*FRACUNIT;
+ mobjinfo[MT_TROOPSHOT].speed = 20*FRACUNIT;
+ }
+ else if (skill != sk_nightmare && gameskill == sk_nightmare)
+ {
+ for (i=S_SARG_RUN1 ; i<=S_SARG_PAIN2 ; i++)
+ states[i].tics <<= 1;
+ mobjinfo[MT_BRUISERSHOT].speed = 15*FRACUNIT;
+ mobjinfo[MT_HEADSHOT].speed = 10*FRACUNIT;
+ mobjinfo[MT_TROOPSHOT].speed = 10*FRACUNIT;
+ }
+
+ // force players to be initialized upon first level load
+ for (i=0 ; iforwardmove = ((signed char)*demo_p++);
+ cmd->sidemove = ((signed char)*demo_p++);
+
+ // If this is a longtics demo, read back in higher resolution
+
+ if (longtics)
+ {
+ cmd->angleturn = *demo_p++;
+ cmd->angleturn |= (*demo_p++) << 8;
+ }
+ else
+ {
+ cmd->angleturn = ((unsigned char) *demo_p++)<<8;
+ }
+
+ cmd->buttons = (unsigned char)*demo_p++;
+}
+
+// Increase the size of the demo buffer to allow unlimited demos
+
+static void IncreaseDemoBuffer(void)
+{
+ int current_length;
+ byte *new_demobuffer;
+ byte *new_demop;
+ int new_length;
+
+ // Find the current size
+
+ current_length = demoend - demobuffer;
+
+ // Generate a new buffer twice the size
+ new_length = current_length * 2;
+
+ new_demobuffer = Z_Malloc(new_length, PU_STATIC, 0);
+ new_demop = new_demobuffer + (demo_p - demobuffer);
+
+ // Copy over the old data
+
+ memcpy(new_demobuffer, demobuffer, current_length);
+
+ // Free the old buffer and point the demo pointers at the new buffer.
+
+ Z_Free(demobuffer);
+
+ demobuffer = new_demobuffer;
+ demo_p = new_demop;
+ demoend = demobuffer + new_length;
+}
+
+void G_WriteDemoTiccmd (ticcmd_t* cmd)
+{
+ byte *demo_start;
+
+ if (gamekeydown[key_demo_quit]) // press q to end demo recording
+ G_CheckDemoStatus ();
+
+ demo_start = demo_p;
+
+ *demo_p++ = cmd->forwardmove;
+ *demo_p++ = cmd->sidemove;
+
+ // If this is a longtics demo, record in higher resolution
+
+ if (longtics)
+ {
+ *demo_p++ = (cmd->angleturn & 0xff);
+ *demo_p++ = (cmd->angleturn >> 8) & 0xff;
+ }
+ else
+ {
+ *demo_p++ = cmd->angleturn >> 8;
+ }
+
+ *demo_p++ = cmd->buttons;
+
+ // reset demo pointer back
+ demo_p = demo_start;
+
+ if (demo_p > demoend - 16)
+ {
+ if (vanilla_demo_limit)
+ {
+ // no more space
+ G_CheckDemoStatus ();
+ return;
+ }
+ else
+ {
+ // Vanilla demo limit disabled: unlimited
+ // demo lengths!
+
+ IncreaseDemoBuffer();
+ }
+ }
+
+ G_ReadDemoTiccmd (cmd); // make SURE it is exactly the same
+}
+
+
+
+//
+// G_RecordDemo
+//
+void G_RecordDemo (char *name)
+{
+ size_t demoname_size;
+ int i;
+ int maxsize;
+
+ usergame = false;
+ demoname_size = strlen(name) + 5;
+ demoname = Z_Malloc(demoname_size, PU_STATIC, NULL);
+ M_snprintf(demoname, demoname_size, "%s.lmp", name);
+ maxsize = 0x20000;
+
+ //!
+ // @arg
+ // @category demo
+ // @vanilla
+ //
+ // Specify the demo buffer size (KiB)
+ //
+
+ i = M_CheckParmWithArgs("-maxdemo", 1);
+ if (i)
+ maxsize = atoi(myargv[i+1])*1024;
+ demobuffer = Z_Malloc (maxsize,PU_STATIC,NULL);
+ demoend = demobuffer + maxsize;
+
+ demorecording = true;
+}
+
+// Get the demo version code appropriate for the version set in gameversion.
+int G_VanillaVersionCode(void)
+{
+ switch (gameversion)
+ {
+ case exe_doom_1_2:
+ I_Error("Doom 1.2 does not have a version code!");
+ case exe_doom_1_666:
+ return 106;
+ case exe_doom_1_7:
+ return 107;
+ case exe_doom_1_8:
+ return 108;
+ case exe_doom_1_9:
+ default: // All other versions are variants on v1.9:
+ return 109;
+ }
+}
+
+void G_BeginRecording (void)
+{
+ int i;
+
+ //!
+ // @category demo
+ //
+ // Record a high resolution "Doom 1.91" demo.
+ //
+
+ longtics = M_CheckParm("-longtics") != 0;
+
+ // If not recording a longtics demo, record in low res
+
+ lowres_turn = !longtics;
+
+ demo_p = demobuffer;
+
+ // Save the right version code for this demo
+
+ if (longtics)
+ {
+ *demo_p++ = DOOM_191_VERSION;
+ }
+ else
+ {
+ *demo_p++ = G_VanillaVersionCode();
+ }
+
+ *demo_p++ = gameskill;
+ *demo_p++ = gameepisode;
+ *demo_p++ = gamemap;
+ *demo_p++ = deathmatch;
+ *demo_p++ = respawnparm;
+ *demo_p++ = fastparm;
+ *demo_p++ = nomonsters;
+ *demo_p++ = consoleplayer;
+
+ for (i=0 ; i= 0 && version <= 4)
+ {
+ return "v1.0/v1.1/v1.2";
+ }
+ else
+ {
+ M_snprintf(resultbuf, sizeof(resultbuf),
+ "%i.%i (unknown)", version / 100, version % 100);
+ return resultbuf;
+ }
+}
+
+void G_DoPlayDemo (void)
+{
+ skill_t skill;
+ int i, episode, map;
+ int demoversion;
+
+ gameaction = ga_nothing;
+ demobuffer = demo_p = W_CacheLumpName (defdemoname, PU_STATIC);
+
+ demoversion = *demo_p++;
+
+ if (demoversion == G_VanillaVersionCode())
+ {
+ longtics = false;
+ }
+ else if (demoversion == DOOM_191_VERSION)
+ {
+ // demo recorded with cph's modified "v1.91" doom exe
+ longtics = true;
+ }
+ else
+ {
+ char *message = "Demo is from a different game version!\n"
+ "(read %i, should be %i)\n"
+ "\n"
+ "*** You may need to upgrade your version "
+ "of Doom to v1.9. ***\n"
+ " See: https://www.doomworld.com/classicdoom"
+ "/info/patches.php\n"
+ " This appears to be %s.";
+
+ //I_Error(message, demoversion, G_VanillaVersionCode(),
+ printf(message, demoversion, G_VanillaVersionCode(),
+ DemoVersionDescription(demoversion));
+ }
+
+ skill = *demo_p++;
+ episode = *demo_p++;
+ map = *demo_p++;
+ deathmatch = *demo_p++;
+ respawnparm = *demo_p++;
+ fastparm = *demo_p++;
+ nomonsters = *demo_p++;
+ consoleplayer = *demo_p++;
+
+ for (i=0 ; i 0
+ || M_CheckParm("-netdemo") > 0)
+ {
+ netgame = true;
+ netdemo = true;
+ }
+
+ // don't spend a lot of time in loadlevel
+ precache = false;
+ G_InitNew (skill, episode, map);
+ precache = true;
+ starttime = I_GetTime ();
+
+ usergame = false;
+ demoplayback = true;
+}
+
+//
+// G_TimeDemo
+//
+void G_TimeDemo (char* name)
+{
+ //!
+ // @vanilla
+ //
+ // Disable rendering the screen entirely.
+ //
+
+ nodrawers = M_CheckParm ("-nodraw");
+
+ timingdemo = true;
+ singletics = true;
+
+ defdemoname = name;
+ gameaction = ga_playdemo;
+}
+
+
+/*
+===================
+=
+= G_CheckDemoStatus
+=
+= Called after a death or level completion to allow demos to be cleaned up
+= Returns true if a new demo loop action will take place
+===================
+*/
+
+boolean G_CheckDemoStatus (void)
+{
+ int endtime;
+
+ if (timingdemo)
+ {
+ float fps;
+ int realtics;
+
+ endtime = I_GetTime ();
+ realtics = endtime - starttime;
+ fps = ((float) gametic * TICRATE) / realtics;
+
+ // Prevent recursive calls
+ timingdemo = false;
+ demoplayback = false;
+
+ I_Error ("timed %i gametics in %i realtics (%f fps)",
+ gametic, realtics, fps);
+ }
+
+ if (demoplayback)
+ {
+ W_ReleaseLumpName(defdemoname);
+ demoplayback = false;
+ netdemo = false;
+ netgame = false;
+ deathmatch = false;
+ playeringame[1] = playeringame[2] = playeringame[3] = 0;
+ respawnparm = false;
+ fastparm = false;
+ nomonsters = false;
+ consoleplayer = 0;
+
+ if (singledemo)
+ I_Quit ();
+ else
+ D_AdvanceDemo ();
+
+ return true;
+ }
+
+ if (demorecording)
+ {
+ *demo_p++ = DEMOMARKER;
+ M_WriteFile (demoname, demobuffer, demo_p - demobuffer);
+ Z_Free (demobuffer);
+ demorecording = false;
+ I_Error ("Demo %s recorded",demoname);
+ }
+
+ return false;
+}
+
+
+
diff --git a/firmware_p4/components/Applications/doom/g_game.h b/firmware_p4/components/Applications/doom/g_game.h
new file mode 100644
index 000000000..da0df39b1
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/g_game.h
@@ -0,0 +1,80 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Duh.
+//
+
+
+#ifndef __G_GAME__
+#define __G_GAME__
+
+#include "doomdef.h"
+#include "d_event.h"
+#include "d_ticcmd.h"
+
+
+//
+// GAME
+//
+void G_DeathMatchSpawnPlayer (int playernum);
+
+void G_InitNew (skill_t skill, int episode, int map);
+
+// Can be called by the startup code or M_Responder.
+// A normal game starts at map 1,
+// but a warp test can start elsewhere
+void G_DeferedInitNew (skill_t skill, int episode, int map);
+
+void G_DeferedPlayDemo (char* demo);
+
+// Can be called by the startup code or M_Responder,
+// calls P_SetupLevel or W_EnterWorld.
+void G_LoadGame (char* name);
+
+void G_DoLoadGame (void);
+
+// Called by M_Responder.
+void G_SaveGame (int slot, char* description);
+
+// Only called by startup code.
+void G_RecordDemo (char* name);
+
+void G_BeginRecording (void);
+
+void G_PlayDemo (char* name);
+void G_TimeDemo (char* name);
+boolean G_CheckDemoStatus (void);
+
+void G_ExitLevel (void);
+void G_SecretExitLevel (void);
+
+void G_WorldDone (void);
+
+// Read current data from inputs and build a player movement command.
+
+void G_BuildTiccmd (ticcmd_t *cmd, int maketic);
+
+void G_Ticker (void);
+boolean G_Responder (event_t* ev);
+
+void G_ScreenShot (void);
+
+void G_DrawMouseSpeedBox(void);
+int G_VanillaVersionCode(void);
+
+extern int vanilla_savegame_limit;
+extern int vanilla_demo_limit;
+#endif
+
diff --git a/firmware_p4/components/Applications/doom/gusconf.c b/firmware_p4/components/Applications/doom/gusconf.c
new file mode 100644
index 000000000..70cdd87c7
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/gusconf.c
@@ -0,0 +1,271 @@
+//
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// GUS emulation code.
+//
+// Actually emulating a GUS is far too much work; fortunately
+// GUS "emulation" already exists in the form of Timidity, which
+// supports GUS patch files. This code therefore converts Doom's
+// DMXGUS lump into an equivalent Timidity configuration file.
+//
+
+
+#include
+#include
+#include
+#include
+
+#include "w_wad.h"
+#include "z_zone.h"
+
+#define MAX_INSTRUMENTS 256
+
+typedef struct
+{
+ char *patch_names[MAX_INSTRUMENTS];
+ int mapping[MAX_INSTRUMENTS];
+} gus_config_t;
+
+char *gus_patch_path = "";
+unsigned int gus_ram_kb = 1024;
+
+static unsigned int MappingIndex(void)
+{
+ unsigned int result = gus_ram_kb / 256;
+
+ if (result < 1)
+ {
+ return 1;
+ }
+ else if (result > 4)
+ {
+ return 4;
+ }
+ else
+ {
+ return result;
+ }
+}
+
+static int SplitLine(char *line, char **fields, unsigned int max_fields)
+{
+ unsigned int num_fields;
+ char *p;
+
+ fields[0] = line;
+ num_fields = 1;
+
+ for (p = line; *p != '\0'; ++p)
+ {
+ if (*p == ',')
+ {
+ *p = '\0';
+
+ // Skip spaces following the comma.
+ do
+ {
+ ++p;
+ } while (*p != '\0' && isspace(*p));
+
+ fields[num_fields] = p;
+ ++num_fields;
+ --p;
+
+ if (num_fields >= max_fields)
+ {
+ break;
+ }
+ }
+ else if (*p == '#')
+ {
+ *p = '\0';
+ break;
+ }
+ }
+
+ // Strip off trailing whitespace from the end of the line.
+ p = fields[num_fields - 1] + strlen(fields[num_fields - 1]);
+ while (p > fields[num_fields - 1] && isspace(*(p - 1)))
+ {
+ --p;
+ *p = '\0';
+ }
+
+ return num_fields;
+}
+
+static void ParseLine(gus_config_t *config, char *line)
+{
+ char *fields[6];
+ unsigned int num_fields;
+ unsigned int instr_id, mapped_id;
+
+ num_fields = SplitLine(line, fields, 6);
+
+ if (num_fields < 6)
+ {
+ return;
+ }
+
+ instr_id = atoi(fields[0]);
+ mapped_id = atoi(fields[MappingIndex()]);
+
+ free(config->patch_names[instr_id]);
+ config->patch_names[instr_id] = strdup(fields[5]);
+ config->mapping[instr_id] = mapped_id;
+}
+
+static void ParseDMXConfig(char *dmxconf, gus_config_t *config)
+{
+ char *p, *newline;
+ unsigned int i;
+
+ memset(config, 0, sizeof(gus_config_t));
+
+ for (i = 0; i < MAX_INSTRUMENTS; ++i)
+ {
+ config->mapping[i] = -1;
+ }
+
+ p = dmxconf;
+
+ for (;;)
+ {
+ newline = strchr(p, '\n');
+
+ if (newline != NULL)
+ {
+ *newline = '\0';
+ }
+
+ ParseLine(config, p);
+
+ if (newline == NULL)
+ {
+ break;
+ }
+ else
+ {
+ p = newline + 1;
+ }
+ }
+}
+
+static void FreeDMXConfig(gus_config_t *config)
+{
+ unsigned int i;
+
+ for (i = 0; i < MAX_INSTRUMENTS; ++i)
+ {
+ free(config->patch_names[i]);
+ }
+}
+
+static char *ReadDMXConfig(void)
+{
+ int lumpnum;
+ unsigned int len;
+ char *data;
+
+ // TODO: This should be chosen based on gamemode == commercial:
+
+ lumpnum = W_CheckNumForName("DMXGUS");
+
+ if (lumpnum < 0)
+ {
+ lumpnum = W_GetNumForName("DMXGUSC");
+ }
+
+ len = W_LumpLength(lumpnum);
+ data = Z_Malloc(len + 1, PU_STATIC, NULL);
+ W_ReadLump(lumpnum, data);
+
+ return data;
+}
+
+static boolean WriteTimidityConfig(char *path, gus_config_t *config)
+{
+ FILE *fstream;
+ unsigned int i;
+
+ fstream = fopen(path, "w");
+
+ if (fstream == NULL)
+ {
+ return false;
+ }
+
+ fprintf(fstream, "# Autogenerated Timidity config.\n\n");
+
+ fprintf(fstream, "dir %s\n", gus_patch_path);
+
+ fprintf(fstream, "\nbank 0\n\n");
+
+ for (i = 0; i < 128; ++i)
+ {
+ if (config->mapping[i] >= 0 && config->mapping[i] < MAX_INSTRUMENTS
+ && config->patch_names[config->mapping[i]] != NULL)
+ {
+ fprintf(fstream, "%i %s\n",
+ i, config->patch_names[config->mapping[i]]);
+ }
+ }
+
+ fprintf(fstream, "\ndrumset 0\n\n");
+
+ for (i = 128 + 25; i < MAX_INSTRUMENTS; ++i)
+ {
+ if (config->mapping[i] >= 0 && config->mapping[i] < MAX_INSTRUMENTS
+ && config->patch_names[config->mapping[i]] != NULL)
+ {
+ fprintf(fstream, "%i %s\n",
+ i - 128, config->patch_names[config->mapping[i]]);
+ }
+ }
+
+ fprintf(fstream, "\n");
+
+ fclose(fstream);
+
+ return true;
+}
+
+boolean GUS_WriteConfig(char *path)
+{
+ boolean result;
+ char *dmxconf;
+ gus_config_t config;
+
+ if (!strcmp(gus_patch_path, ""))
+ {
+ printf("You haven't configured gus_patch_path.\n");
+ printf("gus_patch_path needs to point to the location of "
+ "your GUS patch set.\n"
+ "To get a copy of the \"standard\" GUS patches, "
+ "download a copy of dgguspat.zip.\n");
+
+ return false;
+ }
+
+ dmxconf = ReadDMXConfig();
+ ParseDMXConfig(dmxconf, &config);
+
+ result = WriteTimidityConfig(path, &config);
+
+ FreeDMXConfig(&config);
+ Z_Free(dmxconf);
+
+ return result;
+}
+
diff --git a/firmware_p4/components/Applications/doom/gusconf.h b/firmware_p4/components/Applications/doom/gusconf.h
new file mode 100644
index 000000000..e0124266e
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/gusconf.h
@@ -0,0 +1,29 @@
+//
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// GUS emulation code.
+//
+
+#ifndef __GUSCONF_H__
+#define __GUSCONF_H__
+
+#include "doomtype.h"
+
+extern char *gus_patch_path;
+extern unsigned int gus_ram_kb;
+
+boolean GUS_WriteConfig(char *path);
+
+#endif /* #ifndef __GUSCONF_H__ */
+
diff --git a/firmware_p4/components/Applications/doom/hu_lib.c b/firmware_p4/components/Applications/doom/hu_lib.c
new file mode 100644
index 000000000..47038a043
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/hu_lib.c
@@ -0,0 +1,347 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION: heads-up text and input code
+//
+
+
+#include
+
+#include "doomdef.h"
+#include "doomkeys.h"
+
+#include "v_video.h"
+#include "i_swap.h"
+
+#include "hu_lib.h"
+#include "r_local.h"
+#include "r_draw.h"
+
+// boolean : whether the screen is always erased
+#define noterased viewwindowx
+
+extern boolean automapactive; // in AM_map.c
+
+void HUlib_init(void)
+{
+}
+
+void HUlib_clearTextLine(hu_textline_t* t)
+{
+ t->len = 0;
+ t->l[0] = 0;
+ t->needsupdate = true;
+}
+
+void
+HUlib_initTextLine
+( hu_textline_t* t,
+ int x,
+ int y,
+ patch_t** f,
+ int sc )
+{
+ t->x = x;
+ t->y = y;
+ t->f = f;
+ t->sc = sc;
+ HUlib_clearTextLine(t);
+}
+
+boolean
+HUlib_addCharToTextLine
+( hu_textline_t* t,
+ char ch )
+{
+
+ if (t->len == HU_MAXLINELENGTH)
+ return false;
+ else
+ {
+ t->l[t->len++] = ch;
+ t->l[t->len] = 0;
+ t->needsupdate = 4;
+ return true;
+ }
+
+}
+
+boolean HUlib_delCharFromTextLine(hu_textline_t* t)
+{
+
+ if (!t->len) return false;
+ else
+ {
+ t->l[--t->len] = 0;
+ t->needsupdate = 4;
+ return true;
+ }
+
+}
+
+void
+HUlib_drawTextLine
+( hu_textline_t* l,
+ boolean drawcursor )
+{
+
+ int i;
+ int w;
+ int x;
+ unsigned char c;
+
+ // draw the new stuff
+ x = l->x;
+ for (i=0;ilen;i++)
+ {
+ c = toupper((int)l->l[i]);
+ if (c != ' '
+ && c >= l->sc
+ && c <= '_')
+ {
+ w = SHORT(l->f[c - l->sc]->width);
+ if (x+w > SCREENWIDTH)
+ break;
+ V_DrawPatchDirect(x, l->y, l->f[c - l->sc]);
+ x += w;
+ }
+ else
+ {
+ x += 4;
+ if (x >= SCREENWIDTH)
+ break;
+ }
+ }
+
+ // draw the cursor if requested
+ if (drawcursor
+ && x + SHORT(l->f['_' - l->sc]->width) <= SCREENWIDTH)
+ {
+ V_DrawPatchDirect(x, l->y, l->f['_' - l->sc]);
+ }
+}
+
+
+// sorta called by HU_Erase and just better darn get things straight
+void HUlib_eraseTextLine(hu_textline_t* l)
+{
+ int lh;
+ int y;
+ int yoffset;
+
+ // Only erases when NOT in automap and the screen is reduced,
+ // and the text must either need updating or refreshing
+ // (because of a recent change back from the automap)
+
+ if (!automapactive &&
+ viewwindowx && l->needsupdate)
+ {
+ lh = SHORT(l->f[0]->height) + 1;
+ for (y=l->y,yoffset=y*SCREENWIDTH ; yy+lh ; y++,yoffset+=SCREENWIDTH)
+ {
+ if (y < viewwindowy || y >= viewwindowy + viewheight)
+ R_VideoErase(yoffset, SCREENWIDTH); // erase entire line
+ else
+ {
+ R_VideoErase(yoffset, viewwindowx); // erase left border
+ R_VideoErase(yoffset + viewwindowx + viewwidth, viewwindowx);
+ // erase right border
+ }
+ }
+ }
+
+ if (l->needsupdate) l->needsupdate--;
+
+}
+
+void
+HUlib_initSText
+( hu_stext_t* s,
+ int x,
+ int y,
+ int h,
+ patch_t** font,
+ int startchar,
+ boolean* on )
+{
+
+ int i;
+
+ s->h = h;
+ s->on = on;
+ s->laston = true;
+ s->cl = 0;
+ for (i=0;il[i],
+ x, y - i*(SHORT(font[0]->height)+1),
+ font, startchar);
+
+}
+
+void HUlib_addLineToSText(hu_stext_t* s)
+{
+
+ int i;
+
+ // add a clear line
+ if (++s->cl == s->h)
+ s->cl = 0;
+ HUlib_clearTextLine(&s->l[s->cl]);
+
+ // everything needs updating
+ for (i=0 ; ih ; i++)
+ s->l[i].needsupdate = 4;
+
+}
+
+void
+HUlib_addMessageToSText
+( hu_stext_t* s,
+ char* prefix,
+ char* msg )
+{
+ HUlib_addLineToSText(s);
+ if (prefix)
+ while (*prefix)
+ HUlib_addCharToTextLine(&s->l[s->cl], *(prefix++));
+
+ while (*msg)
+ HUlib_addCharToTextLine(&s->l[s->cl], *(msg++));
+}
+
+void HUlib_drawSText(hu_stext_t* s)
+{
+ int i, idx;
+ hu_textline_t *l;
+
+ if (!*s->on)
+ return; // if not on, don't draw
+
+ // draw everything
+ for (i=0 ; ih ; i++)
+ {
+ idx = s->cl - i;
+ if (idx < 0)
+ idx += s->h; // handle queue of lines
+
+ l = &s->l[idx];
+
+ // need a decision made here on whether to skip the draw
+ HUlib_drawTextLine(l, false); // no cursor, please
+ }
+
+}
+
+void HUlib_eraseSText(hu_stext_t* s)
+{
+
+ int i;
+
+ for (i=0 ; ih ; i++)
+ {
+ if (s->laston && !*s->on)
+ s->l[i].needsupdate = 4;
+ HUlib_eraseTextLine(&s->l[i]);
+ }
+ s->laston = *s->on;
+
+}
+
+void
+HUlib_initIText
+( hu_itext_t* it,
+ int x,
+ int y,
+ patch_t** font,
+ int startchar,
+ boolean* on )
+{
+ it->lm = 0; // default left margin is start of text
+ it->on = on;
+ it->laston = true;
+ HUlib_initTextLine(&it->l, x, y, font, startchar);
+}
+
+
+// The following deletion routines adhere to the left margin restriction
+void HUlib_delCharFromIText(hu_itext_t* it)
+{
+ if (it->l.len != it->lm)
+ HUlib_delCharFromTextLine(&it->l);
+}
+
+void HUlib_eraseLineFromIText(hu_itext_t* it)
+{
+ while (it->lm != it->l.len)
+ HUlib_delCharFromTextLine(&it->l);
+}
+
+// Resets left margin as well
+void HUlib_resetIText(hu_itext_t* it)
+{
+ it->lm = 0;
+ HUlib_clearTextLine(&it->l);
+}
+
+void
+HUlib_addPrefixToIText
+( hu_itext_t* it,
+ char* str )
+{
+ while (*str)
+ HUlib_addCharToTextLine(&it->l, *(str++));
+ it->lm = it->l.len;
+}
+
+// wrapper function for handling general keyed input.
+// returns true if it ate the key
+boolean
+HUlib_keyInIText
+( hu_itext_t* it,
+ unsigned char ch )
+{
+ ch = toupper(ch);
+
+ if (ch >= ' ' && ch <= '_')
+ HUlib_addCharToTextLine(&it->l, (char) ch);
+ else
+ if (ch == KEY_BACKSPACE)
+ HUlib_delCharFromIText(it);
+ else
+ if (ch != KEY_ENTER)
+ return false; // did not eat key
+
+ return true; // ate the key
+
+}
+
+void HUlib_drawIText(hu_itext_t* it)
+{
+
+ hu_textline_t *l = &it->l;
+
+ if (!*it->on)
+ return;
+ HUlib_drawTextLine(l, true); // draw the line w/ cursor
+
+}
+
+void HUlib_eraseIText(hu_itext_t* it)
+{
+ if (it->laston && !*it->on)
+ it->l.needsupdate = 4;
+ HUlib_eraseTextLine(&it->l);
+ it->laston = *it->on;
+}
+
diff --git a/firmware_p4/components/Applications/doom/hu_lib.h b/firmware_p4/components/Applications/doom/hu_lib.h
new file mode 100644
index 000000000..8f0994e60
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/hu_lib.h
@@ -0,0 +1,182 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION: none
+//
+
+#ifndef __HULIB__
+#define __HULIB__
+
+// We are referring to patches.
+#include "r_defs.h"
+
+// font stuff
+#define HU_CHARERASE KEY_BACKSPACE
+
+#define HU_MAXLINES 4
+#define HU_MAXLINELENGTH 80
+
+//
+// Typedefs of widgets
+//
+
+// Text Line widget
+// (parent of Scrolling Text and Input Text widgets)
+typedef struct
+{
+ // left-justified position of scrolling text window
+ int x;
+ int y;
+
+ patch_t** f; // font
+ int sc; // start character
+ char l[HU_MAXLINELENGTH+1]; // line of text
+ int len; // current line length
+
+ // whether this line needs to be udpated
+ int needsupdate;
+
+} hu_textline_t;
+
+
+
+// Scrolling Text window widget
+// (child of Text Line widget)
+typedef struct
+{
+ hu_textline_t l[HU_MAXLINES]; // text lines to draw
+ int h; // height in lines
+ int cl; // current line number
+
+ // pointer to boolean stating whether to update window
+ boolean* on;
+ boolean laston; // last value of *->on.
+
+} hu_stext_t;
+
+
+
+// Input Text Line widget
+// (child of Text Line widget)
+typedef struct
+{
+ hu_textline_t l; // text line to input on
+
+ // left margin past which I am not to delete characters
+ int lm;
+
+ // pointer to boolean stating whether to update window
+ boolean* on;
+ boolean laston; // last value of *->on;
+
+} hu_itext_t;
+
+
+//
+// Widget creation, access, and update routines
+//
+
+// initializes heads-up widget library
+void HUlib_init(void);
+
+//
+// textline code
+//
+
+// clear a line of text
+void HUlib_clearTextLine(hu_textline_t *t);
+
+void HUlib_initTextLine(hu_textline_t *t, int x, int y, patch_t **f, int sc);
+
+// returns success
+boolean HUlib_addCharToTextLine(hu_textline_t *t, char ch);
+
+// returns success
+boolean HUlib_delCharFromTextLine(hu_textline_t *t);
+
+// draws tline
+void HUlib_drawTextLine(hu_textline_t *l, boolean drawcursor);
+
+// erases text line
+void HUlib_eraseTextLine(hu_textline_t *l);
+
+
+//
+// Scrolling Text window widget routines
+//
+
+// ?
+void
+HUlib_initSText
+( hu_stext_t* s,
+ int x,
+ int y,
+ int h,
+ patch_t** font,
+ int startchar,
+ boolean* on );
+
+// add a new line
+void HUlib_addLineToSText(hu_stext_t* s);
+
+// ?
+void
+HUlib_addMessageToSText
+( hu_stext_t* s,
+ char* prefix,
+ char* msg );
+
+// draws stext
+void HUlib_drawSText(hu_stext_t* s);
+
+// erases all stext lines
+void HUlib_eraseSText(hu_stext_t* s);
+
+// Input Text Line widget routines
+void
+HUlib_initIText
+( hu_itext_t* it,
+ int x,
+ int y,
+ patch_t** font,
+ int startchar,
+ boolean* on );
+
+// enforces left margin
+void HUlib_delCharFromIText(hu_itext_t* it);
+
+// enforces left margin
+void HUlib_eraseLineFromIText(hu_itext_t* it);
+
+// resets line and left margin
+void HUlib_resetIText(hu_itext_t* it);
+
+// left of left-margin
+void
+HUlib_addPrefixToIText
+( hu_itext_t* it,
+ char* str );
+
+// whether eaten
+boolean
+HUlib_keyInIText
+( hu_itext_t* it,
+ unsigned char ch );
+
+void HUlib_drawIText(hu_itext_t* it);
+
+// erases all itext lines
+void HUlib_eraseIText(hu_itext_t* it);
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/hu_stuff.c b/firmware_p4/components/Applications/doom/hu_stuff.c
new file mode 100644
index 000000000..b63cac765
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/hu_stuff.c
@@ -0,0 +1,641 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION: Heads-up displays
+//
+
+
+#include
+
+#include "doomdef.h"
+#include "doomkeys.h"
+
+#include "z_zone.h"
+
+#include "deh_main.h"
+#include "i_swap.h"
+#include "i_video.h"
+
+#include "hu_stuff.h"
+#include "hu_lib.h"
+#include "m_controls.h"
+#include "m_misc.h"
+#include "w_wad.h"
+
+#include "s_sound.h"
+
+#include "doomstat.h"
+
+// Data.
+#include "dstrings.h"
+#include "sounds.h"
+
+//
+// Locally used constants, shortcuts.
+//
+#define HU_TITLE (mapnames[(gameepisode-1)*9+gamemap-1])
+#define HU_TITLE2 (mapnames_commercial[gamemap-1])
+#define HU_TITLEP (mapnames_commercial[gamemap-1 + 32])
+#define HU_TITLET (mapnames_commercial[gamemap-1 + 64])
+#define HU_TITLE_CHEX (mapnames[gamemap - 1])
+#define HU_TITLEHEIGHT 1
+#define HU_TITLEX 0
+#define HU_TITLEY (167 - SHORT(hu_font[0]->height))
+
+#define HU_INPUTTOGGLE 't'
+#define HU_INPUTX HU_MSGX
+#define HU_INPUTY (HU_MSGY + HU_MSGHEIGHT*(SHORT(hu_font[0]->height) +1))
+#define HU_INPUTWIDTH 64
+#define HU_INPUTHEIGHT 1
+
+
+
+char *chat_macros[10] =
+{
+ HUSTR_CHATMACRO0,
+ HUSTR_CHATMACRO1,
+ HUSTR_CHATMACRO2,
+ HUSTR_CHATMACRO3,
+ HUSTR_CHATMACRO4,
+ HUSTR_CHATMACRO5,
+ HUSTR_CHATMACRO6,
+ HUSTR_CHATMACRO7,
+ HUSTR_CHATMACRO8,
+ HUSTR_CHATMACRO9
+};
+
+char* player_names[] =
+{
+ HUSTR_PLRGREEN,
+ HUSTR_PLRINDIGO,
+ HUSTR_PLRBROWN,
+ HUSTR_PLRRED
+};
+
+char chat_char; // remove later.
+static player_t* plr;
+patch_t* hu_font[HU_FONTSIZE];
+static hu_textline_t w_title;
+boolean chat_on;
+static hu_itext_t w_chat;
+static boolean always_off = false;
+static char chat_dest[MAXPLAYERS];
+static hu_itext_t w_inputbuffer[MAXPLAYERS];
+
+static boolean message_on;
+boolean message_dontfuckwithme;
+static boolean message_nottobefuckedwith;
+
+static hu_stext_t w_message;
+static int message_counter;
+
+extern int showMessages;
+
+static boolean headsupactive = false;
+
+//
+// Builtin map names.
+// The actual names can be found in DStrings.h.
+//
+
+char* mapnames[] = // DOOM shareware/registered/retail (Ultimate) names.
+{
+
+ HUSTR_E1M1,
+ HUSTR_E1M2,
+ HUSTR_E1M3,
+ HUSTR_E1M4,
+ HUSTR_E1M5,
+ HUSTR_E1M6,
+ HUSTR_E1M7,
+ HUSTR_E1M8,
+ HUSTR_E1M9,
+
+ HUSTR_E2M1,
+ HUSTR_E2M2,
+ HUSTR_E2M3,
+ HUSTR_E2M4,
+ HUSTR_E2M5,
+ HUSTR_E2M6,
+ HUSTR_E2M7,
+ HUSTR_E2M8,
+ HUSTR_E2M9,
+
+ HUSTR_E3M1,
+ HUSTR_E3M2,
+ HUSTR_E3M3,
+ HUSTR_E3M4,
+ HUSTR_E3M5,
+ HUSTR_E3M6,
+ HUSTR_E3M7,
+ HUSTR_E3M8,
+ HUSTR_E3M9,
+
+ HUSTR_E4M1,
+ HUSTR_E4M2,
+ HUSTR_E4M3,
+ HUSTR_E4M4,
+ HUSTR_E4M5,
+ HUSTR_E4M6,
+ HUSTR_E4M7,
+ HUSTR_E4M8,
+ HUSTR_E4M9,
+
+ "NEWLEVEL",
+ "NEWLEVEL",
+ "NEWLEVEL",
+ "NEWLEVEL",
+ "NEWLEVEL",
+ "NEWLEVEL",
+ "NEWLEVEL",
+ "NEWLEVEL",
+ "NEWLEVEL"
+};
+
+// List of names for levels in commercial IWADs
+// (doom2.wad, plutonia.wad, tnt.wad). These are stored in a
+// single large array; WADs like pl2.wad have a MAP33, and rely on
+// the layout in the Vanilla executable, where it is possible to
+// overflow the end of one array into the next.
+
+char *mapnames_commercial[] =
+{
+ // DOOM 2 map names.
+
+ HUSTR_1,
+ HUSTR_2,
+ HUSTR_3,
+ HUSTR_4,
+ HUSTR_5,
+ HUSTR_6,
+ HUSTR_7,
+ HUSTR_8,
+ HUSTR_9,
+ HUSTR_10,
+ HUSTR_11,
+
+ HUSTR_12,
+ HUSTR_13,
+ HUSTR_14,
+ HUSTR_15,
+ HUSTR_16,
+ HUSTR_17,
+ HUSTR_18,
+ HUSTR_19,
+ HUSTR_20,
+
+ HUSTR_21,
+ HUSTR_22,
+ HUSTR_23,
+ HUSTR_24,
+ HUSTR_25,
+ HUSTR_26,
+ HUSTR_27,
+ HUSTR_28,
+ HUSTR_29,
+ HUSTR_30,
+ HUSTR_31,
+ HUSTR_32,
+
+ // Plutonia WAD map names.
+
+ PHUSTR_1,
+ PHUSTR_2,
+ PHUSTR_3,
+ PHUSTR_4,
+ PHUSTR_5,
+ PHUSTR_6,
+ PHUSTR_7,
+ PHUSTR_8,
+ PHUSTR_9,
+ PHUSTR_10,
+ PHUSTR_11,
+
+ PHUSTR_12,
+ PHUSTR_13,
+ PHUSTR_14,
+ PHUSTR_15,
+ PHUSTR_16,
+ PHUSTR_17,
+ PHUSTR_18,
+ PHUSTR_19,
+ PHUSTR_20,
+
+ PHUSTR_21,
+ PHUSTR_22,
+ PHUSTR_23,
+ PHUSTR_24,
+ PHUSTR_25,
+ PHUSTR_26,
+ PHUSTR_27,
+ PHUSTR_28,
+ PHUSTR_29,
+ PHUSTR_30,
+ PHUSTR_31,
+ PHUSTR_32,
+
+ // TNT WAD map names.
+
+ THUSTR_1,
+ THUSTR_2,
+ THUSTR_3,
+ THUSTR_4,
+ THUSTR_5,
+ THUSTR_6,
+ THUSTR_7,
+ THUSTR_8,
+ THUSTR_9,
+ THUSTR_10,
+ THUSTR_11,
+
+ THUSTR_12,
+ THUSTR_13,
+ THUSTR_14,
+ THUSTR_15,
+ THUSTR_16,
+ THUSTR_17,
+ THUSTR_18,
+ THUSTR_19,
+ THUSTR_20,
+
+ THUSTR_21,
+ THUSTR_22,
+ THUSTR_23,
+ THUSTR_24,
+ THUSTR_25,
+ THUSTR_26,
+ THUSTR_27,
+ THUSTR_28,
+ THUSTR_29,
+ THUSTR_30,
+ THUSTR_31,
+ THUSTR_32
+};
+
+void HU_Init(void)
+{
+
+ int i;
+ int j;
+ char buffer[9];
+
+ // load the heads-up font
+ j = HU_FONTSTART;
+ for (i=0;imessage && !message_nottobefuckedwith)
+ || (plr->message && message_dontfuckwithme))
+ {
+ HUlib_addMessageToSText(&w_message, 0, plr->message);
+ plr->message = 0;
+ message_on = true;
+ message_counter = HU_MSGTIMEOUT;
+ message_nottobefuckedwith = message_dontfuckwithme;
+ message_dontfuckwithme = 0;
+ }
+
+ } // else message_on = false;
+
+ // check for incoming chat characters
+ if (netgame)
+ {
+ for (i=0 ; imessage = DEH_String(HUSTR_MSGU);
+ }
+ else
+ {
+ chatchars[head] = c;
+ head = (head + 1) & (QUEUESIZE-1);
+ }
+}
+
+char HU_dequeueChatChar(void)
+{
+ char c;
+
+ if (head != tail)
+ {
+ c = chatchars[tail];
+ tail = (tail + 1) & (QUEUESIZE-1);
+ }
+ else
+ {
+ c = 0;
+ }
+
+ return c;
+}
+
+boolean HU_Responder(event_t *ev)
+{
+
+ static char lastmessage[HU_MAXLINELENGTH+1];
+ char* macromessage;
+ boolean eatkey = false;
+ static boolean altdown = false;
+ unsigned char c;
+ int i;
+ int numplayers;
+
+ static int num_nobrainers = 0;
+
+ numplayers = 0;
+ for (i=0 ; idata1 == KEY_RSHIFT)
+ {
+ return false;
+ }
+ else if (ev->data1 == KEY_RALT || ev->data1 == KEY_LALT)
+ {
+ altdown = ev->type == ev_keydown;
+ return false;
+ }
+
+ if (ev->type != ev_keydown)
+ return false;
+
+ if (!chat_on)
+ {
+ if (ev->data1 == key_message_refresh)
+ {
+ message_on = true;
+ message_counter = HU_MSGTIMEOUT;
+ eatkey = true;
+ }
+ else if (netgame && ev->data2 == key_multi_msg)
+ {
+ eatkey = chat_on = true;
+ HUlib_resetIText(&w_chat);
+ HU_queueChatChar(HU_BROADCAST);
+ }
+ else if (netgame && numplayers > 2)
+ {
+ for (i=0; idata2 == key_multi_msgplayer[i])
+ {
+ if (playeringame[i] && i!=consoleplayer)
+ {
+ eatkey = chat_on = true;
+ HUlib_resetIText(&w_chat);
+ HU_queueChatChar(i+1);
+ break;
+ }
+ else if (i == consoleplayer)
+ {
+ num_nobrainers++;
+ if (num_nobrainers < 3)
+ plr->message = DEH_String(HUSTR_TALKTOSELF1);
+ else if (num_nobrainers < 6)
+ plr->message = DEH_String(HUSTR_TALKTOSELF2);
+ else if (num_nobrainers < 9)
+ plr->message = DEH_String(HUSTR_TALKTOSELF3);
+ else if (num_nobrainers < 32)
+ plr->message = DEH_String(HUSTR_TALKTOSELF4);
+ else
+ plr->message = DEH_String(HUSTR_TALKTOSELF5);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // send a macro
+ if (altdown)
+ {
+ c = ev->data1 - '0';
+ if (c > 9)
+ return false;
+ // fprintf(stderr, "got here\n");
+ macromessage = chat_macros[c];
+
+ // kill last message with a '\n'
+ HU_queueChatChar(KEY_ENTER); // DEBUG!!!
+
+ // send the macro message
+ while (*macromessage)
+ HU_queueChatChar(*macromessage++);
+ HU_queueChatChar(KEY_ENTER);
+
+ // leave chat mode and notify that it was sent
+ chat_on = false;
+ M_StringCopy(lastmessage, chat_macros[c], sizeof(lastmessage));
+ plr->message = lastmessage;
+ eatkey = true;
+ }
+ else
+ {
+ c = ev->data2;
+
+ eatkey = HUlib_keyInIText(&w_chat, c);
+ if (eatkey)
+ {
+ // static unsigned char buf[20]; // DEBUG
+ HU_queueChatChar(c);
+
+ // M_snprintf(buf, sizeof(buf), "KEY: %d => %d", ev->data1, c);
+ // plr->message = buf;
+ }
+ if (c == KEY_ENTER)
+ {
+ chat_on = false;
+ if (w_chat.l.len)
+ {
+ M_StringCopy(lastmessage, w_chat.l.l, sizeof(lastmessage));
+ plr->message = lastmessage;
+ }
+ }
+ else if (c == KEY_ESCAPE)
+ chat_on = false;
+ }
+ }
+
+ return eatkey;
+
+}
diff --git a/firmware_p4/components/Applications/doom/hu_stuff.h b/firmware_p4/components/Applications/doom/hu_stuff.h
new file mode 100644
index 000000000..a3affc5b9
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/hu_stuff.h
@@ -0,0 +1,59 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION: Head up display
+//
+
+#ifndef __HU_STUFF_H__
+#define __HU_STUFF_H__
+
+#include "d_event.h"
+
+
+//
+// Globally visible constants.
+//
+#define HU_FONTSTART '!' // the first font characters
+#define HU_FONTEND '_' // the last font characters
+
+// Calculate # of glyphs in font.
+#define HU_FONTSIZE (HU_FONTEND - HU_FONTSTART + 1)
+
+#define HU_BROADCAST 5
+
+#define HU_MSGX 0
+#define HU_MSGY 0
+#define HU_MSGWIDTH 64 // in characters
+#define HU_MSGHEIGHT 1 // in lines
+
+#define HU_MSGTIMEOUT (4*TICRATE)
+
+//
+// HEADS UP TEXT
+//
+
+void HU_Init(void);
+void HU_Start(void);
+
+boolean HU_Responder(event_t* ev);
+
+void HU_Ticker(void);
+void HU_Drawer(void);
+char HU_dequeueChatChar(void);
+void HU_Erase(void);
+
+extern char *chat_macros[10];
+
+#endif
+
diff --git a/firmware_p4/components/Applications/doom/i_cdmus.c b/firmware_p4/components/Applications/doom/i_cdmus.c
new file mode 100644
index 000000000..12a815cfa
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_cdmus.c
@@ -0,0 +1,243 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+//
+// SDL implementation of the Hexen CD interface.
+//
+
+#include
+
+#ifdef ORIGCODE
+#include "SDL2/SDL.h"
+#include "SDL2/SDL_cdrom.h"
+#endif
+
+#include "doomtype.h"
+
+#include "i_cdmus.h"
+
+#ifdef ORIGCODE
+static SDL_CD *cd_handle = NULL;
+static char *startup_error = NULL;
+static const char *cd_name = NULL;
+#endif
+
+int cd_Error;
+
+int I_CDMusInit(void)
+{
+#ifdef ORIGCODE
+ int drive_num = 0;
+
+ // The initialize function is re-invoked when the CD track play cheat
+ // is used, so use the opportunity to call SDL_CDStatus() to update
+ // the status of the drive.
+
+ if (cd_handle == NULL)
+ {
+ if (SDL_Init(SDL_INIT_CDROM) < 0)
+ {
+ startup_error = "Failed to init CD subsystem.";
+ cd_Error = 1;
+ return -1;
+ }
+
+ // TODO: config variable to control CDROM to use.
+
+ cd_handle = SDL_CDOpen(drive_num);
+
+ if (cd_handle == NULL)
+ {
+ startup_error = "Failed to open CD-ROM drive.";
+ cd_Error = 1;
+ return -1;
+ }
+
+ cd_name = SDL_CDName(drive_num);
+ }
+
+ if (SDL_CDStatus(cd_handle) == CD_ERROR)
+ {
+ startup_error = "Failed to read CD status.";
+ cd_Error = 1;
+ return -1;
+ }
+
+ if (!CD_INDRIVE(cd_handle->status))
+ {
+ startup_error = "No CD in drive.";
+ cd_Error = 1;
+ return -1;
+ }
+
+ cd_Error = 0;
+#endif
+ return 0;
+}
+
+// We cannot print status messages inline during startup, they must
+// be deferred until after I_CDMusInit has returned.
+
+void I_CDMusPrintStartup(void)
+{
+#ifdef ORIGCODE
+ if (cd_name != NULL)
+ {
+ printf("I_CDMusInit: Using CD-ROM drive: %s\n", cd_name);
+ }
+
+ if (startup_error != NULL)
+ {
+ fprintf(stderr, "I_CDMusInit: %s\n", startup_error);
+ }
+#endif
+}
+
+int I_CDMusPlay(int track)
+{
+#ifdef ORIGCODE
+ int result;
+
+ if (cd_handle == NULL)
+ {
+ cd_Error = 1;
+ return -1;
+ }
+
+ // Play one track
+ // Track is indexed from 1.
+
+ result = SDL_CDPlayTracks(cd_handle, track - 1, 0, 1, 0);
+
+ cd_Error = 0;
+ return result;
+#else
+ return 0;
+#endif
+}
+
+int I_CDMusStop(void)
+{
+#ifdef ORIGCODE
+ int result;
+
+ result = SDL_CDStop(cd_handle);
+
+ cd_Error = 0;
+
+ return result;
+#else
+ return 0;
+#endif
+}
+
+int I_CDMusResume(void)
+{
+#ifdef ORIGCODE
+ int result;
+
+ result = SDL_CDResume(cd_handle);
+
+ cd_Error = 0;
+
+ return result;
+#else
+ return 0;
+#endif
+}
+
+int I_CDMusSetVolume(int volume)
+{
+ /* Not supported yet */
+
+ cd_Error = 0;
+
+ return 0;
+}
+
+int I_CDMusFirstTrack(void)
+{
+#ifdef ORIGCODE
+ int i;
+
+ if (cd_handle == NULL)
+ {
+ cd_Error = 1;
+ return -1;
+ }
+
+ // Find the first audio track.
+
+ for (i=0; inumtracks; ++i)
+ {
+ if (cd_handle->track[i].type == SDL_AUDIO_TRACK)
+ {
+ cd_Error = 0;
+
+ // Tracks are indexed from 1.
+ return i + 1;
+ }
+ }
+
+ // Don't know?
+ cd_Error = 1;
+
+ return -1;
+#else
+ return 0;
+#endif
+}
+
+int I_CDMusLastTrack(void)
+{
+#ifdef ORIGCODE
+ if (cd_handle == NULL)
+ {
+ cd_Error = 1;
+ return -1;
+ }
+
+ cd_Error = 0;
+
+ return cd_handle->numtracks;
+#else
+ return 0;
+#endif
+}
+
+int I_CDMusTrackLength(int track_num)
+{
+#ifdef ORIGCODE
+ SDL_CDtrack *track;
+
+ if (cd_handle == NULL || track_num < 1 || track_num > cd_handle->numtracks)
+ {
+ cd_Error = 1;
+ return -1;
+ }
+
+ // Track number is indexed from 1.
+
+ track = &cd_handle->track[track_num - 1];
+
+ // Round up to the next second
+
+ cd_Error = 0;
+
+ return (track->length + CD_FPS - 1) / CD_FPS;
+#else
+ return 0;
+#endif
+}
+
diff --git a/firmware_p4/components/Applications/doom/i_cdmus.h b/firmware_p4/components/Applications/doom/i_cdmus.h
new file mode 100644
index 000000000..31db2a659
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_cdmus.h
@@ -0,0 +1,41 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+
+// i_cdmus.h
+
+#ifndef __ICDMUS__
+#define __ICDMUS__
+
+#define CDERR_NOTINSTALLED 10 // MSCDEX not installed
+#define CDERR_NOAUDIOSUPPORT 11 // CD-ROM Doesn't support audio
+#define CDERR_NOAUDIOTRACKS 12 // Current CD has no audio tracks
+#define CDERR_BADDRIVE 20 // Bad drive number
+#define CDERR_BADTRACK 21 // Bad track number
+#define CDERR_IOCTLBUFFMEM 22 // Not enough low memory for IOCTL
+#define CDERR_DEVREQBASE 100 // DevReq errors
+
+extern int cd_Error;
+
+int I_CDMusInit(void);
+void I_CDMusPrintStartup(void);
+int I_CDMusPlay(int track);
+int I_CDMusStop(void);
+int I_CDMusResume(void);
+int I_CDMusSetVolume(int volume);
+int I_CDMusFirstTrack(void);
+int I_CDMusLastTrack(void);
+int I_CDMusTrackLength(int track);
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/i_endoom.c b/firmware_p4/components/Applications/doom/i_endoom.c
new file mode 100644
index 000000000..6de261bb1
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_endoom.c
@@ -0,0 +1,101 @@
+//
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Exit text-mode ENDOOM screen.
+//
+
+#include
+#include
+
+#include "config.h"
+#include "doomtype.h"
+#include "i_video.h"
+
+#ifdef ORIGCODE
+#include "txt_main.h"
+#endif
+
+
+#ifdef __DJGPP__
+#include
+#endif // __DJGPP__
+
+
+#define ENDOOM_W 80
+#define ENDOOM_H 25
+
+//
+// Displays the text mode ending screen after the game quits
+//
+
+void I_Endoom(byte *endoom_data)
+{
+#ifdef ORIGCODE
+ unsigned char *screendata;
+ int y;
+ int indent;
+
+ // Set up text mode screen
+
+ TXT_Init();
+ I_InitWindowTitle();
+ I_InitWindowIcon();
+
+ // Write the data to the screen memory
+
+ screendata = TXT_GetScreenData();
+
+ indent = (ENDOOM_W - TXT_SCREEN_W) / 2;
+
+ for (y=0; y 0)
+ {
+ break;
+ }
+
+ TXT_Sleep(0);
+ }
+
+ // Shut down text mode screen
+
+ TXT_Shutdown();
+
+#elif defined(__DJGPP__)
+
+ int y;
+
+ // move cursor to bottom
+ // there's a direct call for moving cursor somewhere but this is simpler to write
+ for (y = 0; y < ENDOOM_H; y++) {
+ puts("\n");
+ }
+
+ // allegro exit should have been run already and so we should be in text mode again
+ movedata(_my_ds(), (unsigned) endoom_data, _dos_ds, 0xB8000UL, ENDOOM_W * ENDOOM_H * 2);
+
+#endif
+}
+
diff --git a/firmware_p4/components/Applications/doom/i_endoom.h b/firmware_p4/components/Applications/doom/i_endoom.h
new file mode 100644
index 000000000..8c8ff457f
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_endoom.h
@@ -0,0 +1,29 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Exit text-mode ENDOOM screen.
+//
+
+
+#ifndef __I_ENDOOM__
+#define __I_ENDOOM__
+
+// Display the Endoom screen on shutdown. Pass a pointer to the
+// ENDOOM lump.
+
+void I_Endoom(byte *data);
+
+#endif
+
diff --git a/firmware_p4/components/Applications/doom/i_input.c b/firmware_p4/components/Applications/doom/i_input.c
new file mode 100644
index 000000000..7ec4b63dd
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_input.c
@@ -0,0 +1,341 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "config.h"
+#include "deh_str.h"
+#include "doomtype.h"
+#include "doomkeys.h"
+#include "i_joystick.h"
+#include "i_system.h"
+#include "i_swap.h"
+#include "i_timer.h"
+#include "i_video.h"
+#include "i_scale.h"
+#include "m_argv.h"
+#include "m_config.h"
+#include "m_misc.h"
+#include "tables.h"
+#include "v_video.h"
+#include "w_wad.h"
+#include "z_zone.h"
+
+#include "doomgeneric.h"
+
+int vanilla_keyboard_mapping = 1;
+
+// Is the shift key currently down?
+
+static int shiftdown = 0;
+
+// Lookup table for mapping AT keycodes to their doom keycode
+static const char at_to_doom[] =
+{
+ /* 0x00 */ 0x00,
+ /* 0x01 */ KEY_ESCAPE,
+ /* 0x02 */ '1',
+ /* 0x03 */ '2',
+ /* 0x04 */ '3',
+ /* 0x05 */ '4',
+ /* 0x06 */ '5',
+ /* 0x07 */ '6',
+ /* 0x08 */ '7',
+ /* 0x09 */ '8',
+ /* 0x0a */ '9',
+ /* 0x0b */ '0',
+ /* 0x0c */ '-',
+ /* 0x0d */ '=',
+ /* 0x0e */ KEY_BACKSPACE,
+ /* 0x0f */ KEY_TAB,
+ /* 0x10 */ 'q',
+ /* 0x11 */ 'w',
+ /* 0x12 */ 'e',
+ /* 0x13 */ 'r',
+ /* 0x14 */ 't',
+ /* 0x15 */ 'y',
+ /* 0x16 */ 'u',
+ /* 0x17 */ 'i',
+ /* 0x18 */ 'o',
+ /* 0x19 */ 'p',
+ /* 0x1a */ '[',
+ /* 0x1b */ ']',
+ /* 0x1c */ KEY_ENTER,
+ /* 0x1d */ KEY_FIRE, /* KEY_RCTRL, */
+ /* 0x1e */ 'a',
+ /* 0x1f */ 's',
+ /* 0x20 */ 'd',
+ /* 0x21 */ 'f',
+ /* 0x22 */ 'g',
+ /* 0x23 */ 'h',
+ /* 0x24 */ 'j',
+ /* 0x25 */ 'k',
+ /* 0x26 */ 'l',
+ /* 0x27 */ ';',
+ /* 0x28 */ '\'',
+ /* 0x29 */ '`',
+ /* 0x2a */ KEY_RSHIFT,
+ /* 0x2b */ '\\',
+ /* 0x2c */ 'z',
+ /* 0x2d */ 'x',
+ /* 0x2e */ 'c',
+ /* 0x2f */ 'v',
+ /* 0x30 */ 'b',
+ /* 0x31 */ 'n',
+ /* 0x32 */ 'm',
+ /* 0x33 */ ',',
+ /* 0x34 */ '.',
+ /* 0x35 */ '/',
+ /* 0x36 */ KEY_RSHIFT,
+ /* 0x37 */ KEYP_MULTIPLY,
+ /* 0x38 */ KEY_LALT,
+ /* 0x39 */ KEY_USE,
+ /* 0x3a */ KEY_CAPSLOCK,
+ /* 0x3b */ KEY_F1,
+ /* 0x3c */ KEY_F2,
+ /* 0x3d */ KEY_F3,
+ /* 0x3e */ KEY_F4,
+ /* 0x3f */ KEY_F5,
+ /* 0x40 */ KEY_F6,
+ /* 0x41 */ KEY_F7,
+ /* 0x42 */ KEY_F8,
+ /* 0x43 */ KEY_F9,
+ /* 0x44 */ KEY_F10,
+ /* 0x45 */ KEY_NUMLOCK,
+ /* 0x46 */ 0x0,
+ /* 0x47 */ 0x0, /* 47 (Keypad-7/Home) */
+ /* 0x48 */ 0x0, /* 48 (Keypad-8/Up) */
+ /* 0x49 */ 0x0, /* 49 (Keypad-9/PgUp) */
+ /* 0x4a */ 0x0, /* 4a (Keypad--) */
+ /* 0x4b */ 0x0, /* 4b (Keypad-4/Left) */
+ /* 0x4c */ 0x0, /* 4c (Keypad-5) */
+ /* 0x4d */ 0x0, /* 4d (Keypad-6/Right) */
+ /* 0x4e */ 0x0, /* 4e (Keypad-+) */
+ /* 0x4f */ 0x0, /* 4f (Keypad-1/End) */
+ /* 0x50 */ 0x0, /* 50 (Keypad-2/Down) */
+ /* 0x51 */ 0x0, /* 51 (Keypad-3/PgDn) */
+ /* 0x52 */ 0x0, /* 52 (Keypad-0/Ins) */
+ /* 0x53 */ 0x0, /* 53 (Keypad-./Del) */
+ /* 0x54 */ 0x0, /* 54 (Alt-SysRq) on a 84+ key keyboard */
+ /* 0x55 */ 0x0,
+ /* 0x56 */ 0x0,
+ /* 0x57 */ 0x0,
+ /* 0x58 */ 0x0,
+ /* 0x59 */ 0x0,
+ /* 0x5a */ 0x0,
+ /* 0x5b */ 0x0,
+ /* 0x5c */ 0x0,
+ /* 0x5d */ 0x0,
+ /* 0x5e */ 0x0,
+ /* 0x5f */ 0x0,
+ /* 0x60 */ 0x0,
+ /* 0x61 */ 0x0,
+ /* 0x62 */ 0x0,
+ /* 0x63 */ 0x0,
+ /* 0x64 */ 0x0,
+ /* 0x65 */ 0x0,
+ /* 0x66 */ 0x0,
+ /* 0x67 */ KEY_UPARROW,
+ /* 0x68 */ 0x0,
+ /* 0x69 */ KEY_LEFTARROW,
+ /* 0x6a */ KEY_RIGHTARROW,
+ /* 0x6b */ 0x0,
+ /* 0x6c */ KEY_DOWNARROW,
+ /* 0x6d */ 0x0,
+ /* 0x6e */ 0x0,
+ /* 0x6f */ 0x0,
+ /* 0x70 */ 0x0,
+ /* 0x71 */ 0x0,
+ /* 0x72 */ 0x0,
+ /* 0x73 */ 0x0,
+ /* 0x74 */ 0x0,
+ /* 0x75 */ 0x0,
+ /* 0x76 */ 0x0,
+ /* 0x77 */ 0x0,
+ /* 0x78 */ 0x0,
+ /* 0x79 */ 0x0,
+ /* 0x7a */ 0x0,
+ /* 0x7b */ 0x0,
+ /* 0x7c */ 0x0,
+ /* 0x7d */ 0x0,
+ /* 0x7e */ 0x0,
+ /* 0x7f */ KEY_FIRE, //KEY_RCTRL,
+};
+
+// Lookup table for mapping ASCII characters to their equivalent when
+// shift is pressed on an American layout keyboard:
+static const char shiftxform[] =
+{
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+ 31, ' ', '!', '"', '#', '$', '%', '&',
+ '"', // shift-'
+ '(', ')', '*', '+',
+ '<', // shift-,
+ '_', // shift--
+ '>', // shift-.
+ '?', // shift-/
+ ')', // shift-0
+ '!', // shift-1
+ '@', // shift-2
+ '#', // shift-3
+ '$', // shift-4
+ '%', // shift-5
+ '^', // shift-6
+ '&', // shift-7
+ '*', // shift-8
+ '(', // shift-9
+ ':',
+ ':', // shift-;
+ '<',
+ '+', // shift-=
+ '>', '?', '@',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ '[', // shift-[
+ '!', // shift-backslash - OH MY GOD DOES WATCOM SUCK
+ ']', // shift-]
+ '"', '_',
+ '\'', // shift-`
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ '{', '|', '}', '~', 127
+};
+
+
+static unsigned char TranslateKey(unsigned char key)
+{
+ return key;
+
+ /*
+ if (key < sizeof(at_to_doom))
+ return at_to_doom[key];
+ else
+ return 0x0;
+ */
+
+ //default:
+ // return tolower(key);
+}
+
+// Get the equivalent ASCII (Unicode?) character for a keypress.
+
+static unsigned char GetTypedChar(unsigned char key)
+{
+ key = TranslateKey(key);
+
+ // Is shift held down? If so, perform a translation.
+
+ if (shiftdown > 0)
+ {
+ if (key >= 0 && key < arrlen(shiftxform))
+ {
+ key = shiftxform[key];
+ }
+ else
+ {
+ key = 0;
+ }
+ }
+
+ return key;
+}
+
+static void UpdateShiftStatus(int pressed, unsigned char key)
+{
+ int change;
+
+ if (pressed) {
+ change = 1;
+ } else {
+ change = -1;
+ }
+
+ if (key == KEY_RSHIFT) {
+ shiftdown += change;
+ }
+}
+
+
+void I_GetEvent(void)
+{
+ event_t event;
+ int pressed;
+ unsigned char key;
+
+
+ while (DG_GetKey(&pressed, &key))
+ {
+ UpdateShiftStatus(pressed, key);
+
+ // process event
+
+ if (pressed)
+ {
+ // data1 has the key pressed, data2 has the character
+ // (shift-translated, etc)
+ event.type = ev_keydown;
+ event.data1 = TranslateKey(key);
+ event.data2 = GetTypedChar(key);
+
+ if (event.data1 != 0)
+ {
+ D_PostEvent(&event);
+ }
+ }
+ else
+ {
+ event.type = ev_keyup;
+ event.data1 = TranslateKey(key);
+
+ // data2 is just initialized to zero for ev_keyup.
+ // For ev_keydown it's the shifted Unicode character
+ // that was typed, but if something wants to detect
+ // key releases it should do so based on data1
+ // (key ID), not the printable char.
+
+ event.data2 = 0;
+
+ if (event.data1 != 0)
+ {
+ D_PostEvent(&event);
+ }
+ break;
+ }
+ }
+
+
+ /*
+ case SDL_MOUSEMOTION:
+ event.type = ev_mouse;
+ event.data1 = mouse_button_state;
+ event.data2 = AccelerateMouse(sdlevent.motion.xrel);
+ event.data3 = -AccelerateMouse(sdlevent.motion.yrel);
+ D_PostEvent(&event);
+ break;
+ */
+}
+
+void I_InitInput(void)
+{
+}
+
diff --git a/firmware_p4/components/Applications/doom/i_joystick.c b/firmware_p4/components/Applications/doom/i_joystick.c
new file mode 100644
index 000000000..755aec3d3
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_joystick.c
@@ -0,0 +1,359 @@
+//
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// SDL Joystick code.
+//
+
+#ifdef ORIGCODE
+#include "SDL.h"
+#include "SDL_joystick.h"
+#endif
+
+#include
+#include
+#include
+
+#include "doomtype.h"
+#include "d_event.h"
+#include "i_joystick.h"
+#include "i_system.h"
+
+#include "m_config.h"
+#include "m_misc.h"
+
+// When an axis is within the dead zone, it is set to zero.
+// This is 5% of the full range:
+
+#define DEAD_ZONE (32768 / 3)
+
+#ifdef ORIGCODE
+static SDL_Joystick *joystick = NULL;
+#endif
+
+// Configuration variables:
+
+// Standard default.cfg Joystick enable/disable
+
+static int usejoystick = 0;
+
+// Joystick to use, as an SDL joystick index:
+
+static int joystick_index = -1;
+
+// Which joystick axis to use for horizontal movement, and whether to
+// invert the direction:
+
+static int joystick_x_axis = 0;
+static int joystick_x_invert = 0;
+
+// Which joystick axis to use for vertical movement, and whether to
+// invert the direction:
+
+static int joystick_y_axis = 1;
+static int joystick_y_invert = 0;
+
+// Which joystick axis to use for strafing?
+
+static int joystick_strafe_axis = -1;
+static int joystick_strafe_invert = 0;
+
+// Virtual to physical button joystick button mapping. By default this
+// is a straight mapping.
+static int joystick_physical_buttons[NUM_VIRTUAL_BUTTONS] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
+};
+
+void I_ShutdownJoystick(void)
+{
+#ifdef ORIGCODE
+ if (joystick != NULL)
+ {
+ SDL_JoystickClose(joystick);
+ joystick = NULL;
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+ }
+#endif
+}
+
+#ifdef ORIGCODE
+static boolean IsValidAxis(int axis)
+{
+ int num_axes;
+
+ if (axis < 0)
+ {
+ return true;
+ }
+
+ if (IS_BUTTON_AXIS(axis))
+ {
+ return true;
+ }
+
+ if (IS_HAT_AXIS(axis))
+ {
+ return HAT_AXIS_HAT(axis) < SDL_JoystickNumHats(joystick);
+ }
+
+ num_axes = SDL_JoystickNumAxes(joystick);
+
+ return axis < num_axes;
+}
+#endif
+
+void I_InitJoystick(void)
+{
+#ifdef ORIGCODE
+ if (!usejoystick)
+ {
+ return;
+ }
+
+ if (SDL_Init(SDL_INIT_JOYSTICK) < 0)
+ {
+ return;
+ }
+
+ if (joystick_index < 0 || joystick_index >= SDL_NumJoysticks())
+ {
+ printf("I_InitJoystick: Invalid joystick ID: %i\n", joystick_index);
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+ return;
+ }
+
+ // Open the joystick
+
+ joystick = SDL_JoystickOpen(joystick_index);
+
+ if (joystick == NULL)
+ {
+ printf("I_InitJoystick: Failed to open joystick #%i\n",
+ joystick_index);
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+ return;
+ }
+
+ if (!IsValidAxis(joystick_x_axis)
+ || !IsValidAxis(joystick_y_axis)
+ || !IsValidAxis(joystick_strafe_axis))
+ {
+ printf("I_InitJoystick: Invalid joystick axis for joystick #%i "
+ "(run joystick setup again)\n",
+ joystick_index);
+
+ SDL_JoystickClose(joystick);
+ joystick = NULL;
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+ }
+
+ SDL_JoystickEventState(SDL_ENABLE);
+
+ // Initialized okay!
+
+ printf("I_InitJoystick: %s\n", SDL_JoystickName(joystick_index));
+
+ I_AtExit(I_ShutdownJoystick, true);
+#endif
+}
+
+#ifdef ORIGCODE
+static boolean IsAxisButton(int physbutton)
+{
+ if (IS_BUTTON_AXIS(joystick_x_axis))
+ {
+ if (physbutton == BUTTON_AXIS_NEG(joystick_x_axis)
+ || physbutton == BUTTON_AXIS_POS(joystick_x_axis))
+ {
+ return true;
+ }
+ }
+ if (IS_BUTTON_AXIS(joystick_y_axis))
+ {
+ if (physbutton == BUTTON_AXIS_NEG(joystick_y_axis)
+ || physbutton == BUTTON_AXIS_POS(joystick_y_axis))
+ {
+ return true;
+ }
+ }
+ if (IS_BUTTON_AXIS(joystick_strafe_axis))
+ {
+ if (physbutton == BUTTON_AXIS_NEG(joystick_strafe_axis)
+ || physbutton == BUTTON_AXIS_POS(joystick_strafe_axis))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Get the state of the given virtual button.
+
+static int ReadButtonState(int vbutton)
+{
+ int physbutton;
+
+ // Map from virtual button to physical (SDL) button.
+ if (vbutton < NUM_VIRTUAL_BUTTONS)
+ {
+ physbutton = joystick_physical_buttons[vbutton];
+ }
+ else
+ {
+ physbutton = vbutton;
+ }
+
+ // Never read axis buttons as buttons.
+ if (IsAxisButton(physbutton))
+ {
+ return 0;
+ }
+
+ return SDL_JoystickGetButton(joystick, physbutton);
+}
+
+// Get a bitmask of all currently-pressed buttons
+
+static int GetButtonsState(void)
+{
+ int i;
+ int result;
+
+ result = 0;
+
+ for (i = 0; i < 20; ++i)
+ {
+ if (ReadButtonState(i))
+ {
+ result |= 1 << i;
+ }
+ }
+
+ return result;
+}
+
+// Read the state of an axis, inverting if necessary.
+
+static int GetAxisState(int axis, int invert)
+{
+ int result;
+
+ // Axis -1 means disabled.
+
+ if (axis < 0)
+ {
+ return 0;
+ }
+
+ // Is this a button axis, or a hat axis?
+ // If so, we need to handle it specially.
+
+ result = 0;
+
+ if (IS_BUTTON_AXIS(axis))
+ {
+ if (SDL_JoystickGetButton(joystick, BUTTON_AXIS_NEG(axis)))
+ {
+ result -= 32767;
+ }
+ if (SDL_JoystickGetButton(joystick, BUTTON_AXIS_POS(axis)))
+ {
+ result += 32767;
+ }
+ }
+ else if (IS_HAT_AXIS(axis))
+ {
+ int direction = HAT_AXIS_DIRECTION(axis);
+ int hatval = SDL_JoystickGetHat(joystick, HAT_AXIS_HAT(axis));
+
+ if (direction == HAT_AXIS_HORIZONTAL)
+ {
+ if ((hatval & SDL_HAT_LEFT) != 0)
+ {
+ result -= 32767;
+ }
+ else if ((hatval & SDL_HAT_RIGHT) != 0)
+ {
+ result += 32767;
+ }
+ }
+ else if (direction == HAT_AXIS_VERTICAL)
+ {
+ if ((hatval & SDL_HAT_UP) != 0)
+ {
+ result -= 32767;
+ }
+ else if ((hatval & SDL_HAT_DOWN) != 0)
+ {
+ result += 32767;
+ }
+ }
+ }
+ else
+ {
+ result = SDL_JoystickGetAxis(joystick, axis);
+
+ if (result < DEAD_ZONE && result > -DEAD_ZONE)
+ {
+ result = 0;
+ }
+ }
+
+ if (invert)
+ {
+ result = -result;
+ }
+
+ return result;
+}
+#endif
+void I_UpdateJoystick(void)
+{
+#ifdef ORIGCODE
+ if (joystick != NULL)
+ {
+ event_t ev;
+
+ ev.type = ev_joystick;
+ ev.data1 = GetButtonsState();
+ ev.data2 = GetAxisState(joystick_x_axis, joystick_x_invert);
+ ev.data3 = GetAxisState(joystick_y_axis, joystick_y_invert);
+ ev.data4 = GetAxisState(joystick_strafe_axis, joystick_strafe_invert);
+
+ D_PostEvent(&ev);
+ }
+#endif
+}
+
+void I_BindJoystickVariables(void)
+{
+ int i;
+
+ M_BindVariable("use_joystick", &usejoystick);
+ M_BindVariable("joystick_index", &joystick_index);
+ M_BindVariable("joystick_x_axis", &joystick_x_axis);
+ M_BindVariable("joystick_y_axis", &joystick_y_axis);
+ M_BindVariable("joystick_strafe_axis", &joystick_strafe_axis);
+ M_BindVariable("joystick_x_invert", &joystick_x_invert);
+ M_BindVariable("joystick_y_invert", &joystick_y_invert);
+ M_BindVariable("joystick_strafe_invert",&joystick_strafe_invert);
+
+ for (i = 0; i < NUM_VIRTUAL_BUTTONS; ++i)
+ {
+ char name[32];
+ M_snprintf(name, sizeof(name), "joystick_physical_button%i", i);
+ M_BindVariable(name, &joystick_physical_buttons[i]);
+ }
+}
+
diff --git a/firmware_p4/components/Applications/doom/i_joystick.h b/firmware_p4/components/Applications/doom/i_joystick.h
new file mode 100644
index 000000000..b8815e215
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_joystick.h
@@ -0,0 +1,70 @@
+//
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// System-specific joystick interface.
+//
+
+
+#ifndef __I_JOYSTICK__
+#define __I_JOYSTICK__
+
+// Number of "virtual" joystick buttons defined in configuration files.
+// This needs to be at least as large as the number of different key
+// bindings supported by the higher-level game code (joyb* variables).
+#define NUM_VIRTUAL_BUTTONS 10
+
+// If this bit is set in a configuration file axis value, the axis is
+// not actually a joystick axis, but instead is a "button axis". This
+// means that instead of reading an SDL joystick axis, we read the
+// state of two buttons to get the axis value. This is needed for eg.
+// the PS3 SIXAXIS controller, where the D-pad buttons register as
+// buttons, not as two axes.
+#define BUTTON_AXIS 0x10000
+
+// Query whether a given axis value describes a button axis.
+#define IS_BUTTON_AXIS(axis) ((axis) >= 0 && ((axis) & BUTTON_AXIS) != 0)
+
+// Get the individual buttons from a button axis value.
+#define BUTTON_AXIS_NEG(axis) ((axis) & 0xff)
+#define BUTTON_AXIS_POS(axis) (((axis) >> 8) & 0xff)
+
+// Create a button axis value from two button values.
+#define CREATE_BUTTON_AXIS(neg, pos) (BUTTON_AXIS | (neg) | ((pos) << 8))
+
+// If this bit is set in an axis value, the axis is not actually a
+// joystick axis, but is a "hat" axis. This means that we read (one of)
+// the hats on the joystick.
+#define HAT_AXIS 0x20000
+
+#define IS_HAT_AXIS(axis) ((axis) >= 0 && ((axis) & HAT_AXIS) != 0)
+
+// Get the hat number from a hat axis value.
+#define HAT_AXIS_HAT(axis) ((axis) & 0xff)
+// Which axis of the hat? (horizonal or vertical)
+#define HAT_AXIS_DIRECTION(axis) (((axis) >> 8) & 0xff)
+
+#define CREATE_HAT_AXIS(hat, direction) \
+ (HAT_AXIS | (hat) | ((direction) << 8))
+
+#define HAT_AXIS_HORIZONTAL 1
+#define HAT_AXIS_VERTICAL 2
+
+void I_InitJoystick(void);
+void I_ShutdownJoystick(void);
+void I_UpdateJoystick(void);
+
+void I_BindJoystickVariables(void);
+
+#endif /* #ifndef __I_JOYSTICK__ */
+
diff --git a/firmware_p4/components/Applications/doom/i_scale.c b/firmware_p4/components/Applications/doom/i_scale.c
new file mode 100644
index 000000000..f88c69449
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_scale.c
@@ -0,0 +1,1452 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Screen scale-up code:
+// 1x,2x,3x,4x pixel doubling
+// Aspect ratio-correcting stretch functions
+//
+
+#include
+#include
+#include
+
+#include "doomtype.h"
+
+#include "i_video.h"
+#include "m_argv.h"
+#include "z_zone.h"
+
+#if defined(_MSC_VER) && !defined(__cplusplus)
+#define inline __inline
+#endif
+
+// Should be I_VideoBuffer
+
+static byte *src_buffer;
+
+// Destination buffer, ie. screen->pixels.
+
+static byte *dest_buffer;
+
+// Pitch of destination buffer, ie. screen->pitch.
+
+static int dest_pitch;
+
+// Lookup tables used for aspect ratio correction stretching code.
+// stretch_tables[0] : 20% / 80%
+// stretch_tables[1] : 40% / 60%
+// All other combinations can be reached from these two tables.
+
+static byte *stretch_tables[2] = { NULL, NULL };
+
+// 50%/50% stretch table, for 800x600 squash mode
+
+static byte *half_stretch_table = NULL;
+
+// Called to set the source and destination buffers before doing the
+// scale.
+
+void I_InitScale(byte *_src_buffer, byte *_dest_buffer, int _dest_pitch)
+{
+ src_buffer = _src_buffer;
+ dest_buffer = _dest_buffer;
+ dest_pitch = _dest_pitch;
+}
+
+//
+// Pixel doubling scale-up functions.
+//
+
+// 1x scale doesn't really do any scaling: it just copies the buffer
+// a line at a time for when pitch != SCREENWIDTH (!native_surface)
+
+static boolean I_Scale1x(int x1, int y1, int x2, int y2)
+{
+ byte *bufp, *screenp;
+ int y;
+ int w = x2 - x1;
+
+ // Need to byte-copy from buffer into the screen buffer
+
+ bufp = src_buffer + y1 * SCREENWIDTH + x1;
+ screenp = (byte *) dest_buffer + y1 * dest_pitch + x1;
+
+ for (y=y1; y 240)
+
+ for (y=0; y 480)
+
+ for (y=0; y 720)
+
+ for (y=0; y 960)
+
+ for (y=0; y 1200)
+
+ for (y=0; y 0)
+ {
+ screenp = (byte *) dest_buffer + 2 * dest_pitch;
+
+ for (y=0; y<1198; y += 3)
+ {
+ memset(screenp, 0, 1600);
+
+ screenp += dest_pitch * 3;
+ }
+ }
+
+ return true;
+}
+
+screen_mode_t mode_stretch_5x = {
+ SCREENWIDTH * 5, SCREENHEIGHT_4_3 * 5,
+ I_InitStretchTables,
+ I_Stretch5x,
+ false,
+};
+
+//
+// Aspect ratio correcting "squash" functions.
+//
+// These do the opposite of the "stretch" functions above: while the
+// stretch functions increase the vertical dimensions, the squash
+// functions decrease the horizontal dimensions for the same result.
+//
+// The same blend tables from the stretch functions are reused; as
+// a result, the dimensions are *slightly* wrong (eg. 320x200 should
+// squash to 266x200, but actually squashes to 256x200).
+//
+
+//
+// 1x squashed scale (256x200)
+//
+
+static inline void WriteSquashedLine1x(byte *dest, byte *src)
+{
+ int x;
+
+ for (x=0; x multiples of 320x240)
+
+extern screen_mode_t mode_stretch_1x;
+extern screen_mode_t mode_stretch_2x;
+extern screen_mode_t mode_stretch_3x;
+extern screen_mode_t mode_stretch_4x;
+extern screen_mode_t mode_stretch_5x;
+
+// Horizontally squashed modes (320x200 -> multiples of 256x200)
+
+extern screen_mode_t mode_squash_1x;
+extern screen_mode_t mode_squash_2x;
+extern screen_mode_t mode_squash_3x;
+extern screen_mode_t mode_squash_4x;
+extern screen_mode_t mode_squash_5x;
+
+#endif /* #ifndef __I_SCALE__ */
+
diff --git a/firmware_p4/components/Applications/doom/i_sound.c b/firmware_p4/components/Applications/doom/i_sound.c
new file mode 100644
index 000000000..c93e9ace4
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_sound.c
@@ -0,0 +1,419 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION: none
+//
+
+#include
+#include
+
+// HighBoy: SDL_mixer is not used — the DG_sound_module/DG_music_module backend
+// lives in doom_sound_highboy.c. (i_sound.c itself uses no SDL symbols.)
+
+#include "config.h"
+#include "doomfeatures.h"
+#include "doomtype.h"
+
+#ifdef ORIGCODE
+#include "gusconf.h"
+#endif
+#include "i_sound.h"
+#include "i_video.h"
+#include "m_argv.h"
+#include "m_config.h"
+
+// Sound sample rate to use for digital output (Hz)
+
+int snd_samplerate = 44100;
+
+// Maximum number of bytes to dedicate to allocated sound effects.
+// (Default: 64MB)
+
+int snd_cachesize = 64 * 1024 * 1024;
+
+// Config variable that controls the sound buffer size.
+// We default to 28ms (1000 / 35fps = 1 buffer per tic).
+
+int snd_maxslicetime_ms = 28;
+
+// External command to invoke to play back music.
+
+char *snd_musiccmd = "";
+
+// Low-level sound and music modules we are using
+
+static sound_module_t *sound_module = NULL;
+static music_module_t *music_module = NULL;
+
+int snd_musicdevice = SNDDEVICE_SB;
+int snd_sfxdevice = SNDDEVICE_SB;
+
+// DOS-specific options: These are unused but should be maintained
+// so that the config file can be shared between chocolate
+// doom and doom.exe
+
+static int snd_sbport = 0;
+static int snd_sbirq = 0;
+static int snd_sbdma = 0;
+static int snd_mport = 0;
+
+// Compiled-in sound modules:
+
+static sound_module_t *sound_modules[] =
+{
+ #ifdef FEATURE_SOUND
+ &DG_sound_module,
+ #endif
+ NULL,
+};
+
+// Check if a sound device is in the given list of devices
+
+static boolean SndDeviceInList(snddevice_t device, snddevice_t *list,
+ int len)
+{
+ int i;
+
+ for (i=0; isound_devices,
+ sound_modules[i]->num_sound_devices))
+ {
+ // Initialize the module
+
+ if (sound_modules[i]->Init(use_sfx_prefix))
+ {
+ sound_module = sound_modules[i];
+ return;
+ }
+ }
+ }
+}
+
+// Initialize music according to snd_musicdevice.
+
+static void InitMusicModule(void)
+{
+#ifdef FEATURE_SOUND
+ music_module = &DG_music_module;
+#endif /* FEATURE_SOUND */
+}
+
+//
+// Initializes sound stuff, including volume
+// Sets channels, SFX and music volume,
+// allocates channel buffer, sets S_sfx lookup.
+//
+
+void I_InitSound(boolean use_sfx_prefix)
+{
+ boolean nosound, nosfx, nomusic;
+
+ //!
+ // @vanilla
+ //
+ // Disable all sound output.
+ //
+
+ nosound = M_CheckParm("-nosound") > 0;
+
+ //!
+ // @vanilla
+ //
+ // Disable sound effects.
+ //
+
+ nosfx = M_CheckParm("-nosfx") > 0;
+
+ //!
+ // @vanilla
+ //
+ // Disable music.
+ //
+
+ nomusic = M_CheckParm("-nomusic") > 0;
+
+ // Initialize the sound and music subsystems.
+
+ if (!nosound && !screensaver_mode)
+ {
+ // This is kind of a hack. If native MIDI is enabled, set up
+ // the TIMIDITY_CFG environment variable here before SDL_mixer
+ // is opened.
+
+ if (!nomusic
+ && (snd_musicdevice == SNDDEVICE_GENMIDI
+ || snd_musicdevice == SNDDEVICE_GUS))
+ {
+ //I_InitTimidityConfig();
+ }
+
+ if (!nosfx)
+ {
+ InitSfxModule(use_sfx_prefix);
+ }
+
+ if (!nomusic)
+ {
+ InitMusicModule();
+ }
+ }
+
+}
+
+void I_ShutdownSound(void)
+{
+ if (sound_module != NULL)
+ {
+ sound_module->Shutdown();
+ }
+
+ if (music_module != NULL)
+ {
+ music_module->Shutdown();
+ }
+}
+
+int I_GetSfxLumpNum(sfxinfo_t *sfxinfo)
+{
+ if (sound_module != NULL)
+ {
+ return sound_module->GetSfxLumpNum(sfxinfo);
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+void I_UpdateSound(void)
+{
+ if (sound_module != NULL)
+ {
+ sound_module->Update();
+ }
+
+ if (music_module != NULL && music_module->Poll != NULL)
+ {
+ music_module->Poll();
+ }
+}
+
+static void CheckVolumeSeparation(int *vol, int *sep)
+{
+ if (*sep < 0)
+ {
+ *sep = 0;
+ }
+ else if (*sep > 254)
+ {
+ *sep = 254;
+ }
+
+ if (*vol < 0)
+ {
+ *vol = 0;
+ }
+ else if (*vol > 127)
+ {
+ *vol = 127;
+ }
+}
+
+void I_UpdateSoundParams(int channel, int vol, int sep)
+{
+ if (sound_module != NULL)
+ {
+ CheckVolumeSeparation(&vol, &sep);
+ sound_module->UpdateSoundParams(channel, vol, sep);
+ }
+}
+
+int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep)
+{
+ if (sound_module != NULL)
+ {
+ CheckVolumeSeparation(&vol, &sep);
+ return sound_module->StartSound(sfxinfo, channel, vol, sep);
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+void I_StopSound(int channel)
+{
+ if (sound_module != NULL)
+ {
+ sound_module->StopSound(channel);
+ }
+}
+
+boolean I_SoundIsPlaying(int channel)
+{
+ if (sound_module != NULL)
+ {
+ return sound_module->SoundIsPlaying(channel);
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds)
+{
+ if (sound_module != NULL && sound_module->CacheSounds != NULL)
+ {
+ sound_module->CacheSounds(sounds, num_sounds);
+ }
+}
+
+void I_InitMusic(void)
+{
+ if(music_module != NULL)
+ {
+ music_module->Init();
+ }
+}
+
+void I_ShutdownMusic(void)
+{
+
+}
+
+void I_SetMusicVolume(int volume)
+{
+ if (music_module != NULL)
+ {
+ music_module->SetMusicVolume(volume);
+ }
+}
+
+void I_PauseSong(void)
+{
+ if (music_module != NULL)
+ {
+ music_module->PauseMusic();
+ }
+}
+
+void I_ResumeSong(void)
+{
+ if (music_module != NULL)
+ {
+ music_module->ResumeMusic();
+ }
+}
+
+void *I_RegisterSong(void *data, int len)
+{
+ if (music_module != NULL)
+ {
+ return music_module->RegisterSong(data, len);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+void I_UnRegisterSong(void *handle)
+{
+ if (music_module != NULL)
+ {
+ music_module->UnRegisterSong(handle);
+ }
+}
+
+void I_PlaySong(void *handle, boolean looping)
+{
+ if (music_module != NULL)
+ {
+ music_module->PlaySong(handle, looping);
+ }
+}
+
+void I_StopSong(void)
+{
+ if (music_module != NULL)
+ {
+ music_module->StopSong();
+ }
+}
+
+boolean I_MusicIsPlaying(void)
+{
+ if (music_module != NULL)
+ {
+ return music_module->MusicIsPlaying();
+ }
+ else
+ {
+ return false;
+ }
+
+}
+
+void I_BindSoundVariables(void)
+{
+ extern int use_libsamplerate;
+ extern float libsamplerate_scale;
+
+ M_BindVariable("snd_musicdevice", &snd_musicdevice);
+ M_BindVariable("snd_sfxdevice", &snd_sfxdevice);
+ M_BindVariable("snd_sbport", &snd_sbport);
+ M_BindVariable("snd_sbirq", &snd_sbirq);
+ M_BindVariable("snd_sbdma", &snd_sbdma);
+ M_BindVariable("snd_mport", &snd_mport);
+ M_BindVariable("snd_maxslicetime_ms", &snd_maxslicetime_ms);
+ M_BindVariable("snd_musiccmd", &snd_musiccmd);
+ M_BindVariable("snd_samplerate", &snd_samplerate);
+ M_BindVariable("snd_cachesize", &snd_cachesize);
+
+#ifdef FEATURE_SOUND
+ M_BindVariable("use_libsamplerate", &use_libsamplerate);
+ M_BindVariable("libsamplerate_scale", &libsamplerate_scale);
+#endif
+
+ // Before SDL_mixer version 1.2.11, MIDI music caused the game
+ // to crash when it looped. If this is an old SDL_mixer version,
+ // disable MIDI.
+}
+
diff --git a/firmware_p4/components/Applications/doom/i_sound.h b/firmware_p4/components/Applications/doom/i_sound.h
new file mode 100644
index 000000000..e31b5e58e
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_sound.h
@@ -0,0 +1,256 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// The not so system specific sound interface.
+//
+
+
+#ifndef __I_SOUND__
+#define __I_SOUND__
+
+#include "doomtype.h"
+
+
+//
+// SoundFX struct.
+//
+typedef struct sfxinfo_struct sfxinfo_t;
+
+struct sfxinfo_struct
+{
+ // tag name, used for hexen.
+ char *tagname;
+
+ // lump name. If we are running with use_sfx_prefix=true, a
+ // 'DS' (or 'DP' for PC speaker sounds) is prepended to this.
+
+ char name[9];
+
+ // Sfx priority
+ int priority;
+
+ // referenced sound if a link
+ sfxinfo_t *link;
+
+ // pitch if a link
+ int pitch;
+
+ // volume if a link
+ int volume;
+
+ // this is checked every second to see if sound
+ // can be thrown out (if 0, then decrement, if -1,
+ // then throw out, if > 0, then it is in use)
+ int usefulness;
+
+ // lump number of sfx
+ int lumpnum;
+
+ // Maximum number of channels that the sound can be played on
+ // (Heretic)
+ int numchannels;
+
+ // data used by the low level code
+ void *driver_data;
+};
+
+//
+// MusicInfo struct.
+//
+typedef struct
+{
+ // up to 6-character name
+ char *name;
+
+ // lump number of music
+ int lumpnum;
+
+ // music data
+ void *data;
+
+ // music handle once registered
+ void *handle;
+
+} musicinfo_t;
+
+typedef enum
+{
+ SNDDEVICE_NONE = 0,
+ SNDDEVICE_PCSPEAKER = 1,
+ SNDDEVICE_ADLIB = 2,
+ SNDDEVICE_SB = 3,
+ SNDDEVICE_PAS = 4,
+ SNDDEVICE_GUS = 5,
+ SNDDEVICE_WAVEBLASTER = 6,
+ SNDDEVICE_SOUNDCANVAS = 7,
+ SNDDEVICE_GENMIDI = 8,
+ SNDDEVICE_AWE32 = 9,
+ SNDDEVICE_CD = 10,
+} snddevice_t;
+
+// Interface for sound modules
+
+typedef struct
+{
+ // List of sound devices that this sound module is used for.
+
+ snddevice_t *sound_devices;
+ int num_sound_devices;
+
+ // Initialise sound module
+ // Returns true if successfully initialised
+
+ boolean (*Init)(boolean use_sfx_prefix);
+
+ // Shutdown sound module
+
+ void (*Shutdown)(void);
+
+ // Returns the lump index of the given sound.
+
+ int (*GetSfxLumpNum)(sfxinfo_t *sfxinfo);
+
+ // Called periodically to update the subsystem.
+
+ void (*Update)(void);
+
+ // Update the sound settings on the given channel.
+
+ void (*UpdateSoundParams)(int channel, int vol, int sep);
+
+ // Start a sound on a given channel. Returns the channel id
+ // or -1 on failure.
+
+ int (*StartSound)(sfxinfo_t *sfxinfo, int channel, int vol, int sep);
+
+ // Stop the sound playing on the given channel.
+
+ void (*StopSound)(int channel);
+
+ // Query if a sound is playing on the given channel
+
+ boolean (*SoundIsPlaying)(int channel);
+
+ // Called on startup to precache sound effects (if necessary)
+
+ void (*CacheSounds)(sfxinfo_t *sounds, int num_sounds);
+
+} sound_module_t;
+
+void I_InitSound(boolean use_sfx_prefix);
+void I_ShutdownSound(void);
+int I_GetSfxLumpNum(sfxinfo_t *sfxinfo);
+void I_UpdateSound(void);
+void I_UpdateSoundParams(int channel, int vol, int sep);
+int I_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep);
+void I_StopSound(int channel);
+boolean I_SoundIsPlaying(int channel);
+void I_PrecacheSounds(sfxinfo_t *sounds, int num_sounds);
+
+// Interface for music modules
+
+typedef struct
+{
+ // List of sound devices that this music module is used for.
+
+ snddevice_t *sound_devices;
+ int num_sound_devices;
+
+ // Initialise the music subsystem
+
+ boolean (*Init)(void);
+
+ // Shutdown the music subsystem
+
+ void (*Shutdown)(void);
+
+ // Set music volume - range 0-127
+
+ void (*SetMusicVolume)(int volume);
+
+ // Pause music
+
+ void (*PauseMusic)(void);
+
+ // Un-pause music
+
+ void (*ResumeMusic)(void);
+
+ // Register a song handle from data
+ // Returns a handle that can be used to play the song
+
+ void *(*RegisterSong)(void *data, int len);
+
+ // Un-register (free) song data
+
+ void (*UnRegisterSong)(void *handle);
+
+ // Play the song
+
+ void (*PlaySong)(void *handle, boolean looping);
+
+ // Stop playing the current song.
+
+ void (*StopSong)(void);
+
+ // Query if music is playing.
+
+ boolean (*MusicIsPlaying)(void);
+
+ // Invoked periodically to poll.
+
+ void (*Poll)(void);
+} music_module_t;
+
+void I_InitMusic(void);
+void I_ShutdownMusic(void);
+void I_SetMusicVolume(int volume);
+void I_PauseSong(void);
+void I_ResumeSong(void);
+void *I_RegisterSong(void *data, int len);
+void I_UnRegisterSong(void *handle);
+void I_PlaySong(void *handle, boolean looping);
+void I_StopSong(void);
+boolean I_MusicIsPlaying(void);
+
+extern int snd_sfxdevice;
+extern int snd_musicdevice;
+extern int snd_samplerate;
+extern int snd_cachesize;
+extern int snd_maxslicetime_ms;
+extern char *snd_musiccmd;
+
+void I_BindSoundVariables(void);
+
+// Sound modules
+
+void I_InitTimidityConfig(void);
+#ifdef FEATURE_SOUND
+extern sound_module_t DG_sound_module;
+extern music_module_t DG_music_module;
+#endif
+extern sound_module_t sound_pcsound_module;
+extern music_module_t music_opl_module;
+
+// For OPL module:
+
+extern int opl_io_port;
+
+// For native music module:
+
+extern char *timidity_cfg_path;
+
+#endif
+
diff --git a/firmware_p4/components/Applications/doom/i_swap.h b/firmware_p4/components/Applications/doom/i_swap.h
new file mode 100644
index 000000000..fa45d4c91
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_swap.h
@@ -0,0 +1,73 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Endianess handling, swapping 16bit and 32bit.
+//
+
+
+#ifndef __I_SWAP__
+#define __I_SWAP__
+
+#ifdef __DJGPP__
+
+
+#define SHORT(x) ((signed short) (x))
+#define LONG(x) ((signed int) (x))
+
+#define SYS_LITTLE_ENDIAN
+
+
+#else // __DJGPP__
+
+
+#if ( __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ )
+#define SYS_LITTLE_ENDIAN
+#define SHORT(x) ((signed short) (x))
+#define LONG(x) ((signed int) (x))
+#elif ( __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ )
+#define SYS_BIG_ENDIAN
+
+static inline unsigned short swapLE16(unsigned short val) {
+ return ((val << 8) | (val >> 8));
+}
+
+static inline unsigned long swapLE32(unsigned long val) {
+ return ((val << 24) | ((val << 8) & 0x00FF0000) | ((val >> 8) & 0x0000FF00) | (val >> 24));
+}
+
+#define SHORT(x) ((signed short) swapLE16(x))
+#define LONG(x) ((signed int) swapLE32(x))
+#else
+#error "Unknown byte order"
+#endif
+
+
+// cosmito from lsdldoom
+#define doom_swap_s(x) \
+ ((short int)((((unsigned short int)(x) & 0x00ff) << 8) | \
+ (((unsigned short int)(x) & 0xff00) >> 8)))
+
+#if ( __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ )
+#define doom_wtohs(x) doom_swap_s(x)
+#else
+#define doom_wtohs(x) (short int)(x)
+#endif
+
+
+#endif // __DJGPP__
+
+
+#endif
+
diff --git a/firmware_p4/components/Applications/doom/i_system.c b/firmware_p4/components/Applications/doom/i_system.c
new file mode 100644
index 000000000..53ab2c945
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_system.c
@@ -0,0 +1,578 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+//
+
+
+
+#include
+#include
+#include
+
+#include
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include
+#else
+#include
+#endif
+
+#ifdef ORIGCODE
+#include "SDL.h"
+#endif
+
+#include "config.h"
+
+#include "deh_str.h"
+#include "doomtype.h"
+#include "m_argv.h"
+#include "m_config.h"
+#include "m_misc.h"
+#include "i_joystick.h"
+#include "i_sound.h"
+#include "i_timer.h"
+#include "i_video.h"
+
+#include "i_system.h"
+
+#include "w_wad.h"
+#include "z_zone.h"
+
+#ifdef __MACOSX__
+#include
+#endif
+
+#define DEFAULT_RAM 6 /* MiB */
+#define MIN_RAM 6 /* MiB */
+
+
+typedef struct atexit_listentry_s atexit_listentry_t;
+
+struct atexit_listentry_s
+{
+ atexit_func_t func;
+ boolean run_on_error;
+ atexit_listentry_t *next;
+};
+
+static atexit_listentry_t *exit_funcs = NULL;
+
+void I_AtExit(atexit_func_t func, boolean run_on_error)
+{
+ atexit_listentry_t *entry;
+
+ entry = malloc(sizeof(*entry));
+
+ entry->func = func;
+ entry->run_on_error = run_on_error;
+ entry->next = exit_funcs;
+ exit_funcs = entry;
+}
+
+// Tactile feedback function, probably used for the Logitech Cyberman
+
+void I_Tactile(int on, int off, int total)
+{
+}
+
+// Zone memory auto-allocation function that allocates the zone size
+// by trying progressively smaller zone sizes until one is found that
+// works.
+
+static byte *AutoAllocMemory(int *size, int default_ram, int min_ram)
+{
+ byte *zonemem;
+
+ // Allocate the zone memory. This loop tries progressively smaller
+ // zone sizes until a size is found that can be allocated.
+ // If we used the -mb command line parameter, only the parameter
+ // provided is accepted.
+
+ zonemem = NULL;
+
+ while (zonemem == NULL)
+ {
+ // We need a reasonable minimum amount of RAM to start.
+
+ if (default_ram < min_ram)
+ {
+ I_Error("Unable to allocate %i MiB of RAM for zone", default_ram);
+ }
+
+ // Try to allocate the zone memory.
+
+ *size = default_ram * 1024 * 1024;
+
+ zonemem = malloc(*size);
+
+ // Failed to allocate? Reduce zone size until we reach a size
+ // that is acceptable.
+
+ if (zonemem == NULL)
+ {
+ default_ram -= 1;
+ }
+ }
+
+ return zonemem;
+}
+
+byte *I_ZoneBase (int *size)
+{
+ byte *zonemem;
+ int min_ram, default_ram;
+ int p;
+
+ //!
+ // @arg
+ //
+ // Specify the heap size, in MiB (default 16).
+ //
+
+ p = M_CheckParmWithArgs("-mb", 1);
+
+ if (p > 0)
+ {
+ default_ram = atoi(myargv[p+1]);
+ min_ram = default_ram;
+ }
+ else
+ {
+ default_ram = DEFAULT_RAM;
+ min_ram = MIN_RAM;
+ }
+
+ zonemem = AutoAllocMemory(size, default_ram, min_ram);
+
+ printf("zone memory: %p, %x allocated for zone\n",
+ zonemem, *size);
+
+ return zonemem;
+}
+
+void I_PrintBanner(char *msg)
+{
+ int i;
+ int spaces = 35 - (strlen(msg) / 2);
+
+ for (i=0; ifunc();
+ entry = entry->next;
+ }
+
+#if ORIGCODE
+ SDL_Quit();
+
+ exit(0);
+#endif
+}
+
+#if !defined(_WIN32) && !defined(__MACOSX__) && !defined(__DJGPP__)
+#define ZENITY_BINARY "/usr/bin/zenity"
+
+// returns non-zero if zenity is available
+
+static int ZenityAvailable(void)
+{
+ return system(ZENITY_BINARY " --help >/dev/null 2>&1") == 0;
+}
+
+// Escape special characters in the given string so that they can be
+// safely enclosed in shell quotes.
+
+static char *EscapeShellString(char *string)
+{
+ char *result;
+ char *r, *s;
+
+ // In the worst case, every character might be escaped.
+ result = malloc(strlen(string) * 2 + 3);
+ r = result;
+
+ // Enclosing quotes.
+ *r = '"';
+ ++r;
+
+ for (s = string; *s != '\0'; ++s)
+ {
+ // From the bash manual:
+ //
+ // "Enclosing characters in double quotes preserves the literal
+ // value of all characters within the quotes, with the exception
+ // of $, `, \, and, when history expansion is enabled, !."
+ //
+ // Therefore, escape these characters by prefixing with a backslash.
+
+ if (strchr("$`\\!", *s) != NULL)
+ {
+ *r = '\\';
+ ++r;
+ }
+
+ *r = *s;
+ ++r;
+ }
+
+ // Enclosing quotes.
+ *r = '"';
+ ++r;
+ *r = '\0';
+
+ return result;
+}
+
+// Open a native error box with a message using zenity
+
+static int ZenityErrorBox(char *message)
+{
+ int result;
+ char *escaped_message;
+ char *errorboxpath;
+ static size_t errorboxpath_size;
+
+ if (!ZenityAvailable())
+ {
+ return 0;
+ }
+
+ escaped_message = EscapeShellString(message);
+
+ errorboxpath_size = strlen(ZENITY_BINARY) + strlen(escaped_message) + 19;
+ errorboxpath = malloc(errorboxpath_size);
+ M_snprintf(errorboxpath, errorboxpath_size, "%s --error --text=%s",
+ ZENITY_BINARY, escaped_message);
+
+ result = system(errorboxpath);
+
+ free(errorboxpath);
+ free(escaped_message);
+
+ return result;
+}
+
+#endif /* !defined(_WIN32) && !defined(__MACOSX__) && !defined(__DJGPP__) */
+
+
+//
+// I_Error
+//
+
+static boolean already_quitting = false;
+
+void I_Error (char *error, ...)
+{
+ char msgbuf[512];
+ va_list argptr;
+ atexit_listentry_t *entry;
+ boolean exit_gui_popup;
+
+ if (already_quitting)
+ {
+ fprintf(stderr, "Warning: recursive call to I_Error detected.\n");
+#if ORIGCODE
+ exit(-1);
+#endif
+ }
+ else
+ {
+ already_quitting = true;
+ }
+
+ // Message first.
+ va_start(argptr, error);
+ //fprintf(stderr, "\nError: ");
+ vfprintf(stderr, error, argptr);
+ fprintf(stderr, "\n\n");
+ va_end(argptr);
+ fflush(stderr);
+
+ // Write a copy of the message into buffer.
+ va_start(argptr, error);
+ memset(msgbuf, 0, sizeof(msgbuf));
+ M_vsnprintf(msgbuf, sizeof(msgbuf), error, argptr);
+ va_end(argptr);
+
+ // Shutdown. Here might be other errors.
+
+ entry = exit_funcs;
+
+ while (entry != NULL)
+ {
+ if (entry->run_on_error)
+ {
+ entry->func();
+ }
+
+ entry = entry->next;
+ }
+
+ exit_gui_popup = !M_ParmExists("-nogui");
+
+ // Pop up a GUI dialog box to show the error message, if the
+ // game was not run from the console (and the user will
+ // therefore be unable to otherwise see the message).
+ if (exit_gui_popup && !I_ConsoleStdout())
+#ifdef _WIN32
+ {
+ wchar_t wmsgbuf[512];
+
+ MultiByteToWideChar(CP_ACP, 0,
+ msgbuf, strlen(msgbuf) + 1,
+ wmsgbuf, sizeof(wmsgbuf));
+
+ MessageBoxW(NULL, wmsgbuf, L"", MB_OK);
+ }
+#elif defined(__MACOSX__)
+ {
+ CFStringRef message;
+ int i;
+
+ // The CoreFoundation message box wraps text lines, so replace
+ // newline characters with spaces so that multiline messages
+ // are continuous.
+
+ for (i = 0; msgbuf[i] != '\0'; ++i)
+ {
+ if (msgbuf[i] == '\n')
+ {
+ msgbuf[i] = ' ';
+ }
+ }
+
+ message = CFStringCreateWithCString(NULL, msgbuf,
+ kCFStringEncodingUTF8);
+
+ CFUserNotificationDisplayNotice(0,
+ kCFUserNotificationCautionAlertLevel,
+ NULL,
+ NULL,
+ NULL,
+ CFSTR(PACKAGE_STRING),
+ message,
+ NULL);
+ }
+#elif defined(__DJGPP__)
+ {
+ printf("%s\n", msgbuf);
+ exit(-1);
+ }
+
+#else
+ {
+ ZenityErrorBox(msgbuf);
+ }
+#endif
+
+ // abort();
+#if ORIGCODE
+ SDL_Quit();
+
+ exit(-1);
+#else
+ exit(-1);
+#endif
+}
+
+//
+// Read Access Violation emulation.
+//
+// From PrBoom+, by entryway.
+//
+
+// C:\>debug
+// -d 0:0
+//
+// DOS 6.22:
+// 0000:0000 (57 92 19 00) F4 06 70 00-(16 00)
+// DOS 7.1:
+// 0000:0000 (9E 0F C9 00) 65 04 70 00-(16 00)
+// Win98:
+// 0000:0000 (9E 0F C9 00) 65 04 70 00-(16 00)
+// DOSBox under XP:
+// 0000:0000 (00 00 00 F1) ?? ?? ?? 00-(07 00)
+
+#define DOS_MEM_DUMP_SIZE 10
+
+static const unsigned char mem_dump_dos622[DOS_MEM_DUMP_SIZE] = {
+ 0x57, 0x92, 0x19, 0x00, 0xF4, 0x06, 0x70, 0x00, 0x16, 0x00};
+static const unsigned char mem_dump_win98[DOS_MEM_DUMP_SIZE] = {
+ 0x9E, 0x0F, 0xC9, 0x00, 0x65, 0x04, 0x70, 0x00, 0x16, 0x00};
+static const unsigned char mem_dump_dosbox[DOS_MEM_DUMP_SIZE] = {
+ 0x00, 0x00, 0x00, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00};
+static unsigned char mem_dump_custom[DOS_MEM_DUMP_SIZE];
+
+static const unsigned char *dos_mem_dump = mem_dump_dos622;
+
+boolean I_GetMemoryValue(unsigned int offset, void *value, int size)
+{
+ static boolean firsttime = true;
+
+ if (firsttime)
+ {
+ int p, i, val;
+
+ firsttime = false;
+ i = 0;
+
+ //!
+ // @category compat
+ // @arg
+ //
+ // Specify DOS version to emulate for NULL pointer dereference
+ // emulation. Supported versions are: dos622, dos71, dosbox.
+ // The default is to emulate DOS 7.1 (Windows 98).
+ //
+
+ p = M_CheckParmWithArgs("-setmem", 1);
+
+ if (p > 0)
+ {
+ if (!strcasecmp(myargv[p + 1], "dos622"))
+ {
+ dos_mem_dump = mem_dump_dos622;
+ }
+ if (!strcasecmp(myargv[p + 1], "dos71"))
+ {
+ dos_mem_dump = mem_dump_win98;
+ }
+ else if (!strcasecmp(myargv[p + 1], "dosbox"))
+ {
+ dos_mem_dump = mem_dump_dosbox;
+ }
+ else
+ {
+ for (i = 0; i < DOS_MEM_DUMP_SIZE; ++i)
+ {
+ ++p;
+
+ if (p >= myargc || myargv[p][0] == '-')
+ {
+ break;
+ }
+
+ M_StrToInt(myargv[p], &val);
+ mem_dump_custom[i++] = (unsigned char) val;
+ }
+
+ dos_mem_dump = mem_dump_custom;
+ }
+ }
+ }
+
+ switch (size)
+ {
+ case 1:
+ *((unsigned char *) value) = dos_mem_dump[offset];
+ return true;
+ case 2:
+ *((unsigned short *) value) = dos_mem_dump[offset]
+ | (dos_mem_dump[offset + 1] << 8);
+ return true;
+ case 4:
+ *((unsigned int *) value) = dos_mem_dump[offset]
+ | (dos_mem_dump[offset + 1] << 8)
+ | (dos_mem_dump[offset + 2] << 16)
+ | (dos_mem_dump[offset + 3] << 24);
+ return true;
+ }
+
+ return false;
+}
+
diff --git a/firmware_p4/components/Applications/doom/i_system.h b/firmware_p4/components/Applications/doom/i_system.h
new file mode 100644
index 000000000..b65daff00
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_system.h
@@ -0,0 +1,84 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// System specific interface stuff.
+//
+
+
+#ifndef __I_SYSTEM__
+#define __I_SYSTEM__
+
+#include "d_ticcmd.h"
+#include "d_event.h"
+
+
+typedef void (*atexit_func_t)(void);
+
+// Called by DoomMain.
+void I_Init (void);
+
+// Called by startup code
+// to get the ammount of memory to malloc
+// for the zone management.
+byte* I_ZoneBase (int *size);
+
+boolean I_ConsoleStdout(void);
+
+
+// Asynchronous interrupt functions should maintain private queues
+// that are read by the synchronous functions
+// to be converted into events.
+
+// Either returns a null ticcmd,
+// or calls a loadable driver to build it.
+// This ticcmd will then be modified by the gameloop
+// for normal input.
+ticcmd_t* I_BaseTiccmd (void);
+
+
+// Called by M_Responder when quit is selected.
+// Clean exit, displays sell blurb.
+void I_Quit (void);
+
+void I_Error (char *error, ...);
+
+void I_Tactile (int on, int off, int total);
+
+boolean I_GetMemoryValue(unsigned int offset, void *value, int size);
+
+// Schedule a function to be called when the program exits.
+// If run_if_error is true, the function is called if the exit
+// is due to an error (I_Error)
+
+void I_AtExit(atexit_func_t func, boolean run_if_error);
+
+// Add all system-specific config file variable bindings.
+
+void I_BindVariables(void);
+
+// Print startup banner copyright message.
+
+void I_PrintStartupBanner(char *gamedescription);
+
+// Print a centered text banner displaying the given string.
+
+void I_PrintBanner(char *text);
+
+// Print a dividing line for startup banners.
+
+void I_PrintDivider(void);
+
+#endif
+
diff --git a/firmware_p4/components/Applications/doom/i_timer.c b/firmware_p4/components/Applications/doom/i_timer.c
new file mode 100644
index 000000000..0a85c722b
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_timer.c
@@ -0,0 +1,96 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Timer functions.
+//
+
+#include "i_timer.h"
+#include "doomtype.h"
+
+#include "doomgeneric.h"
+
+#include
+
+//#include
+//#include
+
+
+//
+// I_GetTime
+// returns time in 1/35th second tics
+//
+
+static uint32_t basetime = 0;
+
+
+int I_GetTicks(void)
+{
+ return DG_GetTicksMs();
+}
+
+int I_GetTime (void)
+{
+ uint32_t ticks;
+
+ ticks = I_GetTicks();
+
+ if (basetime == 0)
+ basetime = ticks;
+
+ ticks -= basetime;
+
+ return (ticks * TICRATE) / 1000;
+}
+
+
+//
+// Same as I_GetTime, but returns time in milliseconds
+//
+
+int I_GetTimeMS(void)
+{
+ uint32_t ticks;
+
+ ticks = I_GetTicks();
+
+ if (basetime == 0)
+ basetime = ticks;
+
+ return ticks - basetime;
+}
+
+// Sleep for a specified number of ms
+
+void I_Sleep(int ms)
+{
+ //SDL_Delay(ms);
+ //usleep (ms * 1000);
+
+ DG_SleepMs(ms);
+}
+
+void I_WaitVBL(int count)
+{
+ //I_Sleep((count * 1000) / 70);
+}
+
+
+void I_InitTimer(void)
+{
+ // initialize timer
+
+ //SDL_Init(SDL_INIT_TIMER);
+}
+
diff --git a/firmware_p4/components/Applications/doom/i_timer.h b/firmware_p4/components/Applications/doom/i_timer.h
new file mode 100644
index 000000000..9b3dbb8d1
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_timer.h
@@ -0,0 +1,42 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// System-specific timer interface
+//
+
+
+#ifndef __I_TIMER__
+#define __I_TIMER__
+
+#define TICRATE 35
+
+// Called by D_DoomLoop,
+// returns current time in tics.
+int I_GetTime (void);
+
+// returns current time in ms
+int I_GetTimeMS (void);
+
+// Pause for a specified number of ms
+void I_Sleep(int ms);
+
+// Initialize timer
+void I_InitTimer(void);
+
+// Wait for vertical retrace or pause a bit.
+void I_WaitVBL(int count);
+
+#endif
+
diff --git a/firmware_p4/components/Applications/doom/i_video.c b/firmware_p4/components/Applications/doom/i_video.c
new file mode 100644
index 000000000..84bebdeb6
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_video.c
@@ -0,0 +1,495 @@
+// Emacs style mode select -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// $Id:$
+//
+// Copyright (C) 1993-1996 by id Software, Inc.
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// $Log:$
+//
+// DESCRIPTION:
+// DOOM graphics stuff for X11, UNIX.
+//
+//-----------------------------------------------------------------------------
+
+static const char
+rcsid[] = "$Id: i_x.c,v 1.6 1997/02/03 22:45:10 b1 Exp $";
+
+#include "config.h"
+#include "v_video.h"
+#include "m_argv.h"
+#include "d_event.h"
+#include "d_main.h"
+#include "i_video.h"
+#include "i_system.h"
+#include "z_zone.h"
+
+#include "tables.h"
+#include "doomkeys.h"
+
+#include "doomgeneric.h"
+
+#include
+#include
+
+#include
+
+#include
+
+#include
+
+//#define CMAP256
+
+struct FB_BitField
+{
+ uint32_t offset; /* beginning of bitfield */
+ uint32_t length; /* length of bitfield */
+};
+
+struct FB_ScreenInfo
+{
+ uint32_t xres; /* visible resolution */
+ uint32_t yres;
+ uint32_t xres_virtual; /* virtual resolution */
+ uint32_t yres_virtual;
+
+ uint32_t bits_per_pixel; /* guess what */
+
+ /* >1 = FOURCC */
+ struct FB_BitField red; /* bitfield in s_Fb mem if true color, */
+ struct FB_BitField green; /* else only length is significant */
+ struct FB_BitField blue;
+ struct FB_BitField transp; /* transparency */
+};
+
+static struct FB_ScreenInfo s_Fb;
+int fb_scaling = 1;
+int usemouse = 0;
+
+
+#ifdef CMAP256
+
+boolean palette_changed;
+struct color colors[256];
+
+#else // CMAP256
+
+static struct color colors[256];
+
+
+#endif // CMAP256
+
+
+void I_GetEvent(void);
+
+// The screen buffer; this is modified to draw things to the screen
+
+byte *I_VideoBuffer = NULL;
+
+// If true, game is running as a screensaver
+
+boolean screensaver_mode = false;
+
+// Flag indicating whether the screen is currently visible:
+// when the screen isnt visible, don't render the screen
+
+boolean screenvisible;
+
+// Mouse acceleration
+//
+// This emulates some of the behavior of DOS mouse drivers by increasing
+// the speed when the mouse is moved fast.
+//
+// The mouse input values are input directly to the game, but when
+// the values exceed the value of mouse_threshold, they are multiplied
+// by mouse_acceleration to increase the speed.
+
+float mouse_acceleration = 2.0;
+int mouse_threshold = 10;
+
+// Gamma correction level to use
+
+int usegamma = 0;
+
+typedef struct
+{
+ byte r;
+ byte g;
+ byte b;
+} col_t;
+
+// Palette converted to RGB565
+
+static uint16_t rgb565_palette[256];
+
+void cmap_to_rgb565(uint16_t * out, uint8_t * in, int in_pixels)
+{
+ int i, j;
+ struct color c;
+ uint16_t r, g, b;
+
+ for (i = 0; i < in_pixels; i++)
+ {
+ c = colors[*in];
+ r = ((uint16_t)(c.r >> 3)) << 11;
+ g = ((uint16_t)(c.g >> 2)) << 5;
+ b = ((uint16_t)(c.b >> 3)) << 0;
+ *out = (r | g | b);
+
+ in++;
+ for (j = 0; j < fb_scaling; j++) {
+ out++;
+ }
+ }
+}
+
+void cmap_to_fb(uint8_t *out, uint8_t *in, int in_pixels)
+{
+ int i, k;
+ struct color c;
+ uint32_t pix;
+
+ for (i = 0; i < in_pixels; i++)
+ {
+ c = colors[*in]; // R:8 G:8 B:8
+
+ if (s_Fb.bits_per_pixel == 16)
+ {
+ // RGB565 packing
+ uint16_t p = ((c.r & 0xF8) << 8) |
+ ((c.g & 0xFC) << 3) |
+ (c.b >> 3);
+
+#ifdef SYS_BIG_ENDIAN
+ p = swapeLE16(p); // can't use SHORT() because this needs to stay unsigned
+#endif
+ for (k = 0; k < fb_scaling; k++) {
+ *(uint16_t *)out = p;
+ out += 2;
+ }
+ }
+ else if (s_Fb.bits_per_pixel == 32)
+ {
+ // Assuming RGBA8888
+ pix = (c.r << s_Fb.red.offset) |
+ (c.g << s_Fb.green.offset) |
+ (c.b << s_Fb.blue.offset);
+
+#ifdef SYS_BIG_ENDIAN
+ pix = swapLE32(pix);
+#endif
+ for (k = 0; k < fb_scaling; k++) {
+ *(uint32_t *)out = pix;
+ out += 4;
+ }
+ }
+ else {
+ // no clue how to convert this
+ I_Error("No idea how to convert %d bpp pixels", s_Fb.bits_per_pixel);
+ }
+
+ in++;
+ }
+}
+
+void I_InitGraphics (void)
+{
+ int i, gfxmodeparm;
+ char *mode;
+
+ memset(&s_Fb, 0, sizeof(struct FB_ScreenInfo));
+ s_Fb.xres = DOOMGENERIC_RESX;
+ s_Fb.yres = DOOMGENERIC_RESY;
+ s_Fb.xres_virtual = s_Fb.xres;
+ s_Fb.yres_virtual = s_Fb.yres;
+
+#ifdef CMAP256
+
+ s_Fb.bits_per_pixel = 8;
+
+#else // CMAP256
+
+ gfxmodeparm = M_CheckParmWithArgs("-gfxmode", 1);
+
+ if (gfxmodeparm) {
+ mode = myargv[gfxmodeparm + 1];
+ }
+ else {
+ // default to rgba8888 like the old behavior, for compatibility
+ // maybe could warn here?
+ mode = "rgba8888";
+ }
+
+ if (strcmp(mode, "rgba8888") == 0) {
+ // default mode
+ s_Fb.bits_per_pixel = 32;
+
+ s_Fb.blue.length = 8;
+ s_Fb.green.length = 8;
+ s_Fb.red.length = 8;
+ s_Fb.transp.length = 8;
+
+ s_Fb.blue.offset = 0;
+ s_Fb.green.offset = 8;
+ s_Fb.red.offset = 16;
+ s_Fb.transp.offset = 24;
+ }
+
+ else if (strcmp(mode, "rgb565") == 0) {
+ s_Fb.bits_per_pixel = 16;
+
+ s_Fb.blue.length = 5;
+ s_Fb.green.length = 6;
+ s_Fb.red.length = 5;
+ s_Fb.transp.length = 0;
+
+ s_Fb.blue.offset = 11;
+ s_Fb.green.offset = 5;
+ s_Fb.red.offset = 0;
+ s_Fb.transp.offset = 16;
+ }
+ else
+ I_Error("Unknown gfxmode value: %s\n", mode);
+
+
+#endif // CMAP256
+
+ printf("I_InitGraphics: framebuffer: x_res: %d, y_res: %d, x_virtual: %d, y_virtual: %d, bpp: %d\n",
+ s_Fb.xres, s_Fb.yres, s_Fb.xres_virtual, s_Fb.yres_virtual, s_Fb.bits_per_pixel);
+
+ printf("I_InitGraphics: framebuffer: RGBA: %d%d%d%d, red_off: %d, green_off: %d, blue_off: %d, transp_off: %d\n",
+ s_Fb.red.length, s_Fb.green.length, s_Fb.blue.length, s_Fb.transp.length, s_Fb.red.offset, s_Fb.green.offset, s_Fb.blue.offset, s_Fb.transp.offset);
+
+ printf("I_InitGraphics: DOOM screen size: w x h: %d x %d\n", SCREENWIDTH, SCREENHEIGHT);
+
+
+ i = M_CheckParmWithArgs("-scaling", 1);
+ if (i > 0) {
+ i = atoi(myargv[i + 1]);
+ fb_scaling = i;
+ printf("I_InitGraphics: Scaling factor: %d\n", fb_scaling);
+ } else {
+ fb_scaling = s_Fb.xres / SCREENWIDTH;
+ if (s_Fb.yres / SCREENHEIGHT < fb_scaling)
+ fb_scaling = s_Fb.yres / SCREENHEIGHT;
+ printf("I_InitGraphics: Auto-scaling factor: %d\n", fb_scaling);
+ }
+
+
+ /* Allocate screen to draw to */
+ I_VideoBuffer = (byte*)Z_Malloc (SCREENWIDTH * SCREENHEIGHT, PU_STATIC, NULL); // For DOOM to draw on
+
+ screenvisible = true;
+
+ extern void I_InitInput(void);
+ I_InitInput();
+}
+
+void I_ShutdownGraphics (void)
+{
+ Z_Free (I_VideoBuffer);
+}
+
+void I_StartFrame (void)
+{
+
+}
+
+void I_StartTic (void)
+{
+ I_GetEvent();
+}
+
+void I_UpdateNoBlit (void)
+{
+}
+
+//
+// I_FinishUpdate
+//
+
+void I_FinishUpdate (void)
+{
+ int y;
+ int x_offset, y_offset, x_offset_end;
+ unsigned char *line_in, *line_out;
+
+ /* Offsets in case FB is bigger than DOOM */
+ /* 600 = s_Fb heigt, 200 screenheight */
+ /* 600 = s_Fb heigt, 200 screenheight */
+ /* 2048 =s_Fb width, 320 screenwidth */
+ y_offset = (((s_Fb.yres - (SCREENHEIGHT * fb_scaling)) * s_Fb.bits_per_pixel/8)) / 2;
+ x_offset = (((s_Fb.xres - (SCREENWIDTH * fb_scaling)) * s_Fb.bits_per_pixel/8)) / 2; // XXX: siglent FB hack: /4 instead of /2, since it seems to handle the resolution in a funny way
+ //x_offset = 0;
+ x_offset_end = ((s_Fb.xres - (SCREENWIDTH * fb_scaling)) * s_Fb.bits_per_pixel/8) - x_offset;
+
+ /* DRAW SCREEN */
+ line_in = (unsigned char *) I_VideoBuffer;
+ line_out = (unsigned char *) DG_ScreenBuffer;
+
+ y = SCREENHEIGHT;
+
+ while (y--)
+ {
+ int i;
+ for (i = 0; i < fb_scaling; i++) {
+ line_out += x_offset;
+#ifdef CMAP256
+ if (fb_scaling == 1) {
+ memcpy(line_out, line_in, SCREENWIDTH); /* fb_width is bigger than Doom SCREENWIDTH... */
+ } else {
+ int j;
+
+ for (j = 0; j < SCREENWIDTH; j++) {
+ int k;
+ for (k = 0; k < fb_scaling; k++) {
+ line_out[j * fb_scaling + k] = line_in[j];
+ }
+ }
+ }
+#else
+ //cmap_to_rgb565((void*)line_out, (void*)line_in, SCREENWIDTH);
+ cmap_to_fb((void*)line_out, (void*)line_in, SCREENWIDTH);
+#endif
+ line_out += (SCREENWIDTH * fb_scaling * (s_Fb.bits_per_pixel/8)) + x_offset_end;
+ }
+ line_in += SCREENWIDTH;
+ }
+
+ DG_DrawFrame();
+}
+
+//
+// I_ReadScreen
+//
+void I_ReadScreen (byte* scr)
+{
+ memcpy (scr, I_VideoBuffer, SCREENWIDTH * SCREENHEIGHT);
+}
+
+//
+// I_SetPalette
+//
+#define GFX_RGB565(r, g, b) ((((r & 0xF8) >> 3) << 11) | (((g & 0xFC) >> 2) << 5) | ((b & 0xF8) >> 3))
+#define GFX_RGB565_R(color) ((0xF800 & color) >> 11)
+#define GFX_RGB565_G(color) ((0x07E0 & color) >> 5)
+#define GFX_RGB565_B(color) (0x001F & color)
+
+void I_SetPalette (byte* palette)
+{
+ int i;
+ //col_t* c;
+
+ //for (i = 0; i < 256; i++)
+ //{
+ // c = (col_t*)palette;
+
+ // rgb565_palette[i] = GFX_RGB565(gammatable[usegamma][c->r],
+ // gammatable[usegamma][c->g],
+ // gammatable[usegamma][c->b]);
+
+ // palette += 3;
+ //}
+
+
+ /* performance boost:
+ * map to the right pixel format over here! */
+
+ for (i=0; i<256; ++i ) {
+ colors[i].a = 0;
+ colors[i].r = gammatable[usegamma][*palette++];
+ colors[i].g = gammatable[usegamma][*palette++];
+ colors[i].b = gammatable[usegamma][*palette++];
+ }
+
+#ifdef CMAP256
+
+ palette_changed = true;
+
+#endif // CMAP256
+}
+
+// Given an RGB value, find the closest matching palette index.
+
+int I_GetPaletteIndex (int r, int g, int b)
+{
+ int best, best_diff, diff;
+ int i;
+ col_t color;
+
+ printf("I_GetPaletteIndex\n");
+
+ best = 0;
+ best_diff = INT_MAX;
+
+ for (i = 0; i < 256; ++i)
+ {
+ color.r = GFX_RGB565_R(rgb565_palette[i]);
+ color.g = GFX_RGB565_G(rgb565_palette[i]);
+ color.b = GFX_RGB565_B(rgb565_palette[i]);
+
+ diff = (r - color.r) * (r - color.r)
+ + (g - color.g) * (g - color.g)
+ + (b - color.b) * (b - color.b);
+
+ if (diff < best_diff)
+ {
+ best = i;
+ best_diff = diff;
+ }
+
+ if (diff == 0)
+ {
+ break;
+ }
+ }
+
+ return best;
+}
+
+void I_BeginRead (void)
+{
+}
+
+void I_EndRead (void)
+{
+}
+
+void I_SetWindowTitle (char *title)
+{
+ DG_SetWindowTitle(title);
+}
+
+void I_GraphicsCheckCommandLine (void)
+{
+}
+
+void I_SetGrabMouseCallback (grabmouse_callback_t func)
+{
+}
+
+void I_EnableLoadingDisk(void)
+{
+}
+
+void I_BindVideoVariables (void)
+{
+}
+
+void I_DisplayFPSDots (boolean dots_on)
+{
+}
+
+void I_CheckIsScreensaver (void)
+{
+}
diff --git a/firmware_p4/components/Applications/doom/i_video.h b/firmware_p4/components/Applications/doom/i_video.h
new file mode 100644
index 000000000..c8565f419
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/i_video.h
@@ -0,0 +1,175 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// System specific interface stuff.
+//
+
+
+#ifndef __I_VIDEO__
+#define __I_VIDEO__
+
+#include "doomtype.h"
+
+// Screen width and height.
+
+#define SCREENWIDTH 320
+#define SCREENHEIGHT 200
+
+// Screen width used for "squash" scale functions
+
+#define SCREENWIDTH_4_3 256
+
+// Screen height used for "stretch" scale functions.
+
+#define SCREENHEIGHT_4_3 240
+
+#define MAX_MOUSE_BUTTONS 8
+
+typedef struct
+{
+ // Screen width and height
+
+ int width;
+ int height;
+
+ // Initialisation function to call when using this mode.
+ // Called with a pointer to the Doom palette.
+ //
+ // If NULL, no init function is called.
+
+ void (*InitMode)(byte *palette);
+
+ // Function to call to draw the screen from the source buffer.
+ // Return true if draw was successful.
+
+ boolean (*DrawScreen)(int x1, int y1, int x2, int y2);
+
+ // If true, this is a "poor quality" mode. The autoadjust
+ // code should always attempt to use a different mode to this
+ // mode in fullscreen.
+ //
+ // Some notes about what "poor quality" means in this context:
+ //
+ // The aspect ratio correction works by scaling up to the larger
+ // screen size and then drawing pixels on the edges between the
+ // "virtual" pixels so that an authentic blocky look-and-feel is
+ // achieved.
+ //
+ // For a mode like 640x480, you can imagine the grid of the
+ // "original" pixels spaced out, with extra "blurry" pixels added
+ // in the space between them to fill it out. However, when you're
+ // running at a resolution like 320x240, this is not the case. In
+ // the small screen case, every single pixel has to be a blurry
+ // interpolation of two pixels from the original image.
+ //
+ // If you run in 320x240 and put your face up close to the screen
+ // you can see this: it's particularly visible in the small yellow
+ // status bar numbers for example. Overall it still looks "okay"
+ // but there's an obvious - albeit small - deterioration in
+ // quality.
+ //
+ // Once you get to 640x480, all the original pixels are there at
+ // least once and it's okay (the higher the resolution, the more
+ // accurate it is). When I first wrote the code I was expecting
+ // that even higher resolutions would be needed before it would
+ // look acceptable, but it turned out to be okay even at 640x480.
+
+ boolean poor_quality;
+} screen_mode_t;
+
+typedef boolean (*grabmouse_callback_t)(void);
+
+// Called by D_DoomMain,
+// determines the hardware configuration
+// and sets up the video mode
+void I_InitGraphics (void);
+
+void I_GraphicsCheckCommandLine(void);
+
+void I_ShutdownGraphics(void);
+
+// Takes full 8 bit values.
+void I_SetPalette (byte* palette);
+int I_GetPaletteIndex(int r, int g, int b);
+
+void I_UpdateNoBlit (void);
+void I_FinishUpdate (void);
+
+void I_ReadScreen (byte* scr);
+
+void I_BeginRead (void);
+
+void I_SetWindowTitle(char *title);
+
+void I_CheckIsScreensaver(void);
+void I_SetGrabMouseCallback(grabmouse_callback_t func);
+
+void I_DisplayFPSDots(boolean dots_on);
+void I_BindVideoVariables(void);
+
+void I_InitWindowTitle(void);
+void I_InitWindowIcon(void);
+
+// Called before processing any tics in a frame (just after displaying a frame).
+// Time consuming syncronous operations are performed here (joystick reading).
+
+void I_StartFrame (void);
+
+// Called before processing each tic in a frame.
+// Quick syncronous operations are performed here.
+
+void I_StartTic (void);
+
+// Enable the loading disk image displayed when reading from disk.
+
+void I_EnableLoadingDisk(void);
+
+void I_EndRead (void);
+
+struct color {
+ uint32_t b:8;
+ uint32_t g:8;
+ uint32_t r:8;
+ uint32_t a:8;
+};
+
+
+extern char *video_driver;
+extern boolean screenvisible;
+
+extern float mouse_acceleration;
+extern int mouse_threshold;
+extern int vanilla_keyboard_mapping;
+extern boolean screensaver_mode;
+extern int usegamma;
+extern byte *I_VideoBuffer;
+
+extern int screen_width;
+extern int screen_height;
+extern int screen_bpp;
+extern int fullscreen;
+extern int aspect_ratio_correct;
+
+extern int show_diskicon;
+extern int diskicon_readbytes;
+
+#ifdef CMAP256
+
+extern boolean palette_changed;
+extern struct color colors[256];
+
+#endif // CMAP256
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/icon.c b/firmware_p4/components/Applications/doom/icon.c
new file mode 100644
index 000000000..14c607c23
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/icon.c
@@ -0,0 +1,262 @@
+static int icon_w = 32;
+static int icon_h = 32;
+
+static unsigned char icon_data[] = {
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xa2,0x86,0x73,
+ 0xa9,0x8d,0x7a, 0xbd,0xa0,0x8c, 0xda,0xba,0xa0, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0xbd,0x8d,0x67, 0xd7,0xb9,0xa5, 0xeb,0xd8,0xcd, 0xd3,0xbf,0xae,
+ 0xbd,0xa0,0x8c, 0xeb,0xd8,0xcd, 0xc2,0x9d,0x86, 0x95,0x5d,0x38,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x9b,0x7e,0x66,
+ 0xc5,0x9e,0x81, 0xd3,0xb3,0x99, 0xd4,0xac,0x8e, 0xee,0xdc,0xd1,
+ 0xb9,0x93,0x76, 0xad,0x71,0x45, 0xd4,0xac,0x8e, 0xb9,0x93,0x76,
+ 0xa3,0x77,0x58, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x39,0x1d,0x2d, 0x55,0x20,0x22, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0xda,0xb4,0x9c, 0xd3,0xa3,0x83,
+ 0xaf,0x91,0x78, 0xa7,0x83,0x6d, 0xc4,0xa7,0x93, 0xee,0xe2,0xd5,
+ 0xeb,0xd8,0xcd, 0x8c,0x60,0x3d, 0x9b,0x7e,0x66, 0xce,0x9f,0x7e,
+ 0x84,0x54,0x33, 0xba,0x83,0x5b, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x24,0x1c,0x35, 0x00,0x0f,0x32, 0x29,0x18,0x2e, 0x55,0x20,0x22,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0xd3,0xb3,0x99, 0xca,0x93,0x6f, 0xc4,0x94,0x6e,
+ 0x98,0x66,0x45, 0x78,0x50,0x2d, 0xd7,0xb9,0xa5, 0xee,0xdc,0xd1,
+ 0xc4,0x9b,0x79, 0xa1,0x6d,0x45, 0x66,0x40,0x24, 0xb8,0x7a,0x4f,
+ 0xcf,0xa6,0x83, 0x98,0x6d,0x4e, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x30,0x1c,0x2f, 0x08,0x13,0x30, 0x00,0x0f,0x32, 0x00,0x0f,0x32,
+ 0x39,0x1d,0x2d, 0x52,0x1c,0x1a, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x9e,0x7b,0x65, 0xb9,0x89,0x64, 0xaa,0x7d,0x5e, 0x9e,0x72,0x53,
+ 0x88,0x5e,0x40, 0xc4,0xa7,0x93, 0xb9,0x89,0x64, 0x90,0x6c,0x51,
+ 0x7f,0x50,0x2f, 0x90,0x5e,0x37, 0x75,0x4d,0x30, 0x7f,0x50,0x2f,
+ 0xd3,0xa3,0x83, 0xd4,0xac,0x8e, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x29,0x18,0x2e, 0x08,0x13,0x30, 0x08,0x13,0x30, 0x08,0x13,0x30,
+ 0x00,0x0f,0x32, 0x08,0x13,0x30, 0x49,0x1e,0x2b, 0x49,0x1a,0x16,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xda,0xba,0xa0,
+ 0xd4,0xac,0x8e, 0xc4,0x9b,0x79, 0xaa,0x7d,0x5e, 0xaa,0x7d,0x5e,
+ 0xbd,0xa0,0x8c, 0x8c,0x60,0x3d, 0x70,0x49,0x2c, 0x89,0x60,0x42,
+ 0x57,0x38,0x20, 0x6c,0x45,0x29, 0x66,0x40,0x24, 0x51,0x35,0x21,
+ 0x7e,0x55,0x38, 0xce,0x9f,0x7e, 0xc2,0x8a,0x61, 0x00,0x00,0x00,
+ 0x30,0x1c,0x2f, 0x00,0x0f,0x32, 0x00,0x0f,0x32, 0x08,0x13,0x30,
+ 0x00,0x0f,0x32, 0x00,0x0f,0x32, 0x08,0x13,0x30, 0x08,0x13,0x30,
+ 0x59,0x25,0x2b, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xcb,0x9a,0x74,
+ 0xb7,0x81,0x58, 0x8c,0x60,0x3d, 0x79,0x4b,0x2b, 0x89,0x58,0x31,
+ 0x89,0x58,0x31, 0x7f,0x50,0x2f, 0x9e,0x64,0x39, 0x75,0x4c,0x2a,
+ 0x51,0x35,0x21, 0x84,0x54,0x33, 0x54,0x36,0x1d, 0x98,0x6d,0x4e,
+ 0xb4,0x7f,0x5c, 0xba,0x83,0x5b, 0xb8,0x7a,0x4f, 0x00,0x00,0x00,
+ 0x3e,0x28,0x36, 0x08,0x13,0x30, 0x00,0x0f,0x32, 0x08,0x13,0x30,
+ 0x00,0x0f,0x32, 0x00,0x0f,0x32, 0x00,0x0f,0x32, 0x08,0x13,0x30,
+ 0x20,0x1f,0x36, 0x35,0x19,0x12, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xc2,0x8a,0x61,
+ 0x89,0x60,0x42, 0x84,0x54,0x33, 0x7f,0x50,0x2f, 0x86,0x56,0x35,
+ 0x8d,0x5b,0x35, 0x75,0x4c,0x2a, 0x8d,0x5b,0x35, 0x5c,0x38,0x22,
+ 0x5e,0x3f,0x27, 0x75,0x4d,0x30, 0x9d,0x64,0x3f, 0x75,0x4c,0x2a,
+ 0x78,0x50,0x2d, 0x7f,0x50,0x2f, 0xb7,0x81,0x58, 0x00,0x00,0x00,
+ 0x46,0x35,0x42, 0x04,0x18,0x3a, 0x08,0x13,0x30, 0x5d,0x30,0x28,
+ 0x20,0x1f,0x36, 0x08,0x13,0x30, 0x08,0x13,0x30, 0x04,0x18,0x3a,
+ 0x19,0x1c,0x37, 0x3a,0x1d,0x16, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x91,0x5f,0x3e,
+ 0x84,0x54,0x33, 0x89,0x58,0x31, 0x7e,0x6e,0x64, 0xc4,0x94,0x6e,
+ 0x78,0x50,0x2d, 0x92,0x6f,0x59, 0xa1,0x7c,0x60, 0x9c,0x6f,0x4b,
+ 0x8d,0x5b,0x35, 0xbc,0x7f,0x53, 0xad,0x71,0x45, 0x75,0x4d,0x30,
+ 0x51,0x35,0x21, 0x4b,0x2f,0x1c, 0x70,0x49,0x2c, 0x00,0x00,0x00,
+ 0x59,0x44,0x4d, 0x1e,0x28,0x42, 0x1e,0x28,0x42, 0x48,0x19,0x10,
+ 0x42,0x19,0x12, 0x53,0x2b,0x30, 0x0c,0x26,0x48, 0x1e,0x28,0x42,
+ 0x24,0x2d,0x48, 0x5f,0x2c,0x1d, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x89,0x58,0x31,
+ 0xa1,0x84,0x6c, 0xc4,0x94,0x6e, 0x88,0x64,0x44, 0xb5,0x8f,0x73,
+ 0x9e,0x72,0x53, 0xa1,0x6d,0x45, 0x93,0x60,0x3a, 0xad,0x71,0x45,
+ 0xb4,0x7f,0x5c, 0xbd,0x8d,0x67, 0xc2,0x8a,0x61, 0xb3,0x76,0x4b,
+ 0xb8,0x7a,0x4f, 0x88,0x64,0x44, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x61,0x50,0x52, 0x1c,0x34,0x52, 0x1c,0x34,0x52, 0x54,0x27,0x16,
+ 0x29,0x17,0x09, 0x5d,0x30,0x28, 0x1c,0x34,0x52, 0x1c,0x34,0x52,
+ 0x24,0x35,0x4f, 0x69,0x34,0x24, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xa7,0x83,0x6d,
+ 0xac,0x86,0x6a, 0x74,0x47,0x2d, 0x84,0x54,0x33, 0x5c,0x38,0x22,
+ 0x54,0x36,0x1d, 0x6c,0x45,0x29, 0x96,0x63,0x3c, 0xa3,0x6e,0x41,
+ 0xb3,0x76,0x4b, 0xb3,0x76,0x4b, 0xa2,0x68,0x3d, 0x7c,0x4e,0x2d,
+ 0x63,0x3e,0x27, 0x96,0x63,0x3c, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x6b,0x5d,0x59, 0x22,0x42,0x5f, 0x22,0x42,0x5f, 0x5d,0x34,0x1a,
+ 0x38,0x23,0x0f, 0x5c,0x38,0x22, 0x22,0x42,0x5f, 0x22,0x42,0x5f,
+ 0x2c,0x45,0x5e, 0x6f,0x3e,0x2b, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xb7,0x81,0x58,
+ 0x98,0x74,0x59, 0x6c,0x45,0x29, 0x4b,0x35,0x25, 0x78,0x50,0x2d,
+ 0x78,0x50,0x2d, 0x78,0x50,0x2d, 0x7f,0x50,0x2f, 0x84,0x54,0x33,
+ 0x8d,0x5b,0x35, 0x96,0x63,0x3c, 0x74,0x47,0x2d, 0x65,0x45,0x26,
+ 0x65,0x45,0x26, 0x7c,0x4e,0x2d, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x77,0x69,0x64, 0x30,0x4e,0x6d, 0x32,0x52,0x6b, 0x69,0x42,0x26,
+ 0x49,0x31,0x11, 0x6c,0x47,0x2f, 0x27,0x4f,0x6d, 0x27,0x4f,0x6d,
+ 0x32,0x52,0x6b, 0x70,0x49,0x2c, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xb7,0x81,0x58,
+ 0x8a,0x5a,0x39, 0x8a,0x5a,0x39, 0x91,0x5f,0x3e, 0x5e,0x3f,0x27,
+ 0x5c,0x38,0x22, 0x89,0x58,0x31, 0x89,0x58,0x31, 0x95,0x5d,0x38,
+ 0x9d,0x64,0x3f, 0x65,0x45,0x26, 0x4b,0x2f,0x1c, 0x7f,0x50,0x2f,
+ 0x78,0x50,0x2d, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x77,0x69,0x64, 0x36,0x5c,0x7a, 0x3e,0x5e,0x78, 0x76,0x52,0x2e,
+ 0x5d,0x42,0x22, 0x75,0x4d,0x30, 0x36,0x5c,0x7a, 0x36,0x5c,0x7a,
+ 0x3e,0x5e,0x78, 0x74,0x47,0x2d, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x89,0x58,0x31, 0x63,0x3e,0x27, 0xa8,0x6d,0x42, 0x4b,0x2f,0x1c,
+ 0x65,0x45,0x26, 0x70,0x49,0x2c, 0x51,0x35,0x21, 0x78,0x50,0x2d,
+ 0x42,0x30,0x14, 0x49,0x31,0x11, 0x59,0x44,0x22, 0x7c,0x5c,0x2a,
+ 0x8a,0x71,0x27, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x71,0x67,0x5c, 0x37,0x52,0x66, 0x3f,0x55,0x64, 0x80,0x55,0x27,
+ 0x64,0x4c,0x1f, 0x7e,0x59,0x2e, 0x37,0x52,0x66, 0x37,0x52,0x66,
+ 0x3f,0x55,0x64, 0x6c,0x47,0x2f, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x70,0x49,0x2c, 0x65,0x45,0x26, 0x65,0x45,0x26, 0x63,0x3e,0x27,
+ 0x76,0x4d,0x25, 0x5d,0x42,0x22, 0x5e,0x3f,0x27, 0x4e,0x43,0x18,
+ 0x4e,0x43,0x18, 0x6a,0x5b,0x1c, 0x4e,0x43,0x18, 0x5f,0x51,0x19,
+ 0x8a,0x76,0x2a, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x63,0x38,0x19, 0x34,0x11,0x04, 0x32,0x0f,0x00, 0x86,0x58,0x1e,
+ 0x74,0x59,0x25, 0x86,0x58,0x1e, 0x34,0x15,0x00, 0x32,0x0f,0x00,
+ 0x34,0x15,0x00, 0x4e,0x31,0x18, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x7e,0x6c,0x27, 0x5a,0x4d,0x1c,
+ 0x4d,0x3e,0x15, 0x67,0x58,0x21, 0x5a,0x4d,0x1c, 0x57,0x4b,0x1a,
+ 0x5f,0x51,0x19, 0x64,0x55,0x1e, 0x5a,0x4d,0x1c, 0x8a,0x71,0x27,
+ 0x8e,0x79,0x26, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x63,0x38,0x19, 0x34,0x11,0x04, 0x32,0x0f,0x00, 0x8d,0x63,0x1f,
+ 0x83,0x66,0x2c, 0x8d,0x63,0x1f, 0x32,0x0f,0x00, 0x35,0x19,0x12,
+ 0x34,0x11,0x04, 0x53,0x3a,0x20, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0xac,0x93,0x39, 0x76,0x65,0x20,
+ 0x6a,0x5b,0x1c, 0x6a,0x5b,0x1c, 0x67,0x58,0x21, 0x4e,0x43,0x18,
+ 0x4e,0x43,0x18, 0x9b,0x85,0x32, 0xb8,0x9e,0x3c, 0xb1,0x8d,0x36,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x63,0x38,0x19, 0x34,0x15,0x00, 0x32,0x0f,0x00, 0x8d,0x63,0x1f,
+ 0x83,0x66,0x2c, 0x8d,0x63,0x1f, 0x32,0x0f,0x00, 0x32,0x0f,0x00,
+ 0x34,0x15,0x00, 0x53,0x3a,0x20, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0xb0,0x96,0x34, 0x76,0x65,0x20,
+ 0x7e,0x66,0x23, 0x8e,0x79,0x26, 0x8a,0x71,0x27, 0x7e,0x6c,0x27,
+ 0x8a,0x71,0x27, 0x8a,0x71,0x27, 0xb0,0x96,0x34, 0x98,0x82,0x2f,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x75,0x4c,0x2a, 0x38,0x19,0x05, 0x38,0x19,0x05, 0x99,0x6d,0x22,
+ 0x96,0x70,0x2a, 0x99,0x6d,0x22, 0x38,0x19,0x05, 0x38,0x19,0x05,
+ 0x38,0x19,0x05, 0x59,0x3f,0x25, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0xac,0x93,0x39, 0x8a,0x76,0x2a,
+ 0x7e,0x66,0x23, 0x76,0x65,0x20, 0x93,0x7d,0x2a, 0x82,0x6f,0x23,
+ 0x9f,0x88,0x35, 0xb8,0xa0,0x4c, 0xb8,0xa0,0x4c, 0xc4,0xa8,0x3f,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x7c,0x5c,0x2a, 0x52,0x2e,0x0d, 0x52,0x2e,0x0d, 0xa4,0x7b,0x27,
+ 0xa1,0x80,0x37, 0x9f,0x77,0x1a, 0x52,0x2e,0x0d, 0x52,0x2e,0x0d,
+ 0x52,0x2e,0x0d, 0x5f,0x4e,0x2a, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x86,0x72,0x26,
+ 0xac,0x93,0x39, 0x97,0x82,0x36, 0xb1,0x8d,0x36, 0xac,0x93,0x39,
+ 0x97,0x82,0x36, 0xa4,0x8c,0x32, 0xbd,0xa2,0x41, 0x8a,0x71,0x27,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x8b,0x6d,0x32, 0x66,0x44,0x14, 0x6d,0x4a,0x20, 0xab,0x86,0x29,
+ 0xb1,0x8d,0x36, 0xa4,0x7b,0x27, 0x66,0x44,0x14, 0x66,0x44,0x14,
+ 0x66,0x44,0x14, 0x69,0x56,0x2c, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xac,0x93,0x31,
+ 0x7e,0x6c,0x27, 0x9f,0x88,0x35, 0x97,0x82,0x36, 0x7e,0x66,0x23,
+ 0x7e,0x66,0x23, 0xb2,0x99,0x3f, 0xbd,0xa2,0x41, 0x8a,0x76,0x2a,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x99,0x7a,0x38, 0x86,0x58,0x1e, 0x7f,0x59,0x22, 0xb2,0x8b,0x1c,
+ 0x94,0x6e,0x21, 0x7f,0x59,0x22, 0x7f,0x59,0x22, 0x7f,0x59,0x22,
+ 0x7f,0x59,0x22, 0x69,0x56,0x2c, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xac,0x93,0x39,
+ 0x8a,0x71,0x27, 0xb4,0x9c,0x48, 0x7e,0x66,0x23, 0xac,0x93,0x39,
+ 0x9c,0x87,0x3a, 0x9c,0x87,0x3a, 0xbd,0xa2,0x41, 0x8e,0x79,0x26,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0xa8,0x86,0x3d, 0x96,0x70,0x2a, 0x96,0x70,0x2a, 0x96,0x70,0x2a,
+ 0x96,0x70,0x2a, 0x96,0x70,0x2a, 0x96,0x70,0x2a, 0x96,0x70,0x2a,
+ 0xa1,0x80,0x37, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xb4,0x9c,0x48,
+ 0xac,0x93,0x31, 0x93,0x7d,0x2a, 0xbd,0xa3,0x48, 0x93,0x7d,0x2a,
+ 0xb8,0xa0,0x4c, 0xb4,0x9c,0x48, 0xcc,0xa5,0x4e, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0xa8,0x86,0x3d, 0xaf,0x85,0x31, 0xaf,0x85,0x31, 0xaf,0x85,0x31,
+ 0xaf,0x85,0x31, 0xaf,0x85,0x31, 0xaf,0x85,0x31, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x97,0x82,0x36, 0xb4,0x9c,0x48, 0xb2,0x99,0x3f, 0xb4,0x9c,0x48,
+ 0xb0,0x96,0x34, 0xc1,0xa7,0x4c, 0x9b,0x84,0x2a, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0xbd,0x9e,0x4c, 0xc7,0x9a,0x3f, 0xc7,0x9a,0x3f, 0xc7,0x9a,0x3f,
+ 0xc7,0x9a,0x3f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x9b,0x85,0x32, 0xa7,0x8e,0x2c, 0xac,0x93,0x39, 0xb5,0x91,0x41,
+ 0x76,0x65,0x20, 0xa7,0x8e,0x2c, 0xb4,0x9a,0x38, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0xcc,0xa5,0x4e, 0xe0,0xaf,0x45, 0xe0,0xaf,0x45, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0xa4,0x8c,0x32, 0xb8,0x9e,0x44, 0x86,0x72,0x26, 0x9f,0x88,0x35,
+ 0xbd,0xa3,0x48, 0x9b,0x85,0x32, 0xa3,0x81,0x32, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0xd1,0xae,0x4e, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0xb0,0x97,0x3c, 0xb4,0x9a,0x38, 0xac,0x94,0x41, 0xb2,0x99,0x3f,
+ 0xb4,0x9a,0x38, 0xb8,0x9e,0x3c, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0xcc,0xa5,0x4e, 0xa6,0x8f,0x3c, 0xb2,0x99,0x3f, 0xb4,0x9c,0x48,
+ 0xa8,0x90,0x36, 0x9f,0x88,0x35, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+ 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00,
+
+};
diff --git a/firmware_p4/components/Applications/doom/include/doom_highboy.h b/firmware_p4/components/Applications/doom/include/doom_highboy.h
new file mode 100644
index 000000000..34ab48c2a
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/include/doom_highboy.h
@@ -0,0 +1,22 @@
+#ifndef DOOM_HIGHBOY_H
+#define DOOM_HIGHBOY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Launch official DOOM (doomgeneric) in its own FreeRTOS task. Takes over the
+// ST7789 (landscape, bypassing LVGL) and streams /sdcard/doom1.wad. Call from
+// the games-menu screen open handler. Quitting DOOM reboots the device
+// (hold OK+BACK ~2s), so this does not return control to the caller's UI.
+//
+// render_beat_kick: optional callback pulsed once per frame while DOOM owns the
+// panel (LVGL is parked). Pass ui_render_beat_kick so the render-liveness
+// watchdog (sys_monitor) sees progress; NULL disables it.
+void highboy_doom_start(void (*render_beat_kick)(void));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // DOOM_HIGHBOY_H
diff --git a/firmware_p4/components/Applications/doom/info.c b/firmware_p4/components/Applications/doom/info.c
new file mode 100644
index 000000000..9389e84f0
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/info.c
@@ -0,0 +1,4662 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Thing frame/state LUT,
+// generated by multigen utilitiy.
+// This one is the original DOOM version, preserved.
+//
+
+#include
+#include
+
+// Data.
+#include "sounds.h"
+#include "m_fixed.h"
+
+#include "info.h"
+
+#include "p_mobj.h"
+
+char *sprnames[] = {
+ "TROO","SHTG","PUNG","PISG","PISF","SHTF","SHT2","CHGG","CHGF","MISG",
+ "MISF","SAWG","PLSG","PLSF","BFGG","BFGF","BLUD","PUFF","BAL1","BAL2",
+ "PLSS","PLSE","MISL","BFS1","BFE1","BFE2","TFOG","IFOG","PLAY","POSS",
+ "SPOS","VILE","FIRE","FATB","FBXP","SKEL","MANF","FATT","CPOS","SARG",
+ "HEAD","BAL7","BOSS","BOS2","SKUL","SPID","BSPI","APLS","APBX","CYBR",
+ "PAIN","SSWV","KEEN","BBRN","BOSF","ARM1","ARM2","BAR1","BEXP","FCAN",
+ "BON1","BON2","BKEY","RKEY","YKEY","BSKU","RSKU","YSKU","STIM","MEDI",
+ "SOUL","PINV","PSTR","PINS","MEGA","SUIT","PMAP","PVIS","CLIP","AMMO",
+ "ROCK","BROK","CELL","CELP","SHEL","SBOX","BPAK","BFUG","MGUN","CSAW",
+ "LAUN","PLAS","SHOT","SGN2","COLU","SMT2","GOR1","POL2","POL5","POL4",
+ "POL3","POL1","POL6","GOR2","GOR3","GOR4","GOR5","SMIT","COL1","COL2",
+ "COL3","COL4","CAND","CBRA","COL6","TRE1","TRE2","ELEC","CEYE","FSKU",
+ "COL5","TBLU","TGRN","TRED","SMBT","SMGT","SMRT","HDB1","HDB2","HDB3",
+ "HDB4","HDB5","HDB6","POB1","POB2","BRS1","TLMP","TLP2", NULL
+};
+
+
+// Doesn't work with g++, needs actionf_p1
+void A_Light0();
+void A_WeaponReady();
+void A_Lower();
+void A_Raise();
+void A_Punch();
+void A_ReFire();
+void A_FirePistol();
+void A_Light1();
+void A_FireShotgun();
+void A_Light2();
+void A_FireShotgun2();
+void A_CheckReload();
+void A_OpenShotgun2();
+void A_LoadShotgun2();
+void A_CloseShotgun2();
+void A_FireCGun();
+void A_GunFlash();
+void A_FireMissile();
+void A_Saw();
+void A_FirePlasma();
+void A_BFGsound();
+void A_FireBFG();
+void A_BFGSpray();
+void A_Explode();
+void A_Pain();
+void A_PlayerScream();
+void A_Fall();
+void A_XScream();
+void A_Look();
+void A_Chase();
+void A_FaceTarget();
+void A_PosAttack();
+void A_Scream();
+void A_SPosAttack();
+void A_VileChase();
+void A_VileStart();
+void A_VileTarget();
+void A_VileAttack();
+void A_StartFire();
+void A_Fire();
+void A_FireCrackle();
+void A_Tracer();
+void A_SkelWhoosh();
+void A_SkelFist();
+void A_SkelMissile();
+void A_FatRaise();
+void A_FatAttack1();
+void A_FatAttack2();
+void A_FatAttack3();
+void A_BossDeath();
+void A_CPosAttack();
+void A_CPosRefire();
+void A_TroopAttack();
+void A_SargAttack();
+void A_HeadAttack();
+void A_BruisAttack();
+void A_SkullAttack();
+void A_Metal();
+void A_SpidRefire();
+void A_BabyMetal();
+void A_BspiAttack();
+void A_Hoof();
+void A_CyberAttack();
+void A_PainAttack();
+void A_PainDie();
+void A_KeenDie();
+void A_BrainPain();
+void A_BrainScream();
+void A_BrainDie();
+void A_BrainAwake();
+void A_BrainSpit();
+void A_SpawnSound();
+void A_SpawnFly();
+void A_BrainExplode();
+
+
+state_t states[NUMSTATES] = {
+ {SPR_TROO,0,-1,{NULL},S_NULL,0,0}, // S_NULL
+ {SPR_SHTG,4,0,{A_Light0},S_NULL,0,0}, // S_LIGHTDONE
+ {SPR_PUNG,0,1,{A_WeaponReady},S_PUNCH,0,0}, // S_PUNCH
+ {SPR_PUNG,0,1,{A_Lower},S_PUNCHDOWN,0,0}, // S_PUNCHDOWN
+ {SPR_PUNG,0,1,{A_Raise},S_PUNCHUP,0,0}, // S_PUNCHUP
+ {SPR_PUNG,1,4,{NULL},S_PUNCH2,0,0}, // S_PUNCH1
+ {SPR_PUNG,2,4,{A_Punch},S_PUNCH3,0,0}, // S_PUNCH2
+ {SPR_PUNG,3,5,{NULL},S_PUNCH4,0,0}, // S_PUNCH3
+ {SPR_PUNG,2,4,{NULL},S_PUNCH5,0,0}, // S_PUNCH4
+ {SPR_PUNG,1,5,{A_ReFire},S_PUNCH,0,0}, // S_PUNCH5
+ {SPR_PISG,0,1,{A_WeaponReady},S_PISTOL,0,0},// S_PISTOL
+ {SPR_PISG,0,1,{A_Lower},S_PISTOLDOWN,0,0}, // S_PISTOLDOWN
+ {SPR_PISG,0,1,{A_Raise},S_PISTOLUP,0,0}, // S_PISTOLUP
+ {SPR_PISG,0,4,{NULL},S_PISTOL2,0,0}, // S_PISTOL1
+ {SPR_PISG,1,6,{A_FirePistol},S_PISTOL3,0,0},// S_PISTOL2
+ {SPR_PISG,2,4,{NULL},S_PISTOL4,0,0}, // S_PISTOL3
+ {SPR_PISG,1,5,{A_ReFire},S_PISTOL,0,0}, // S_PISTOL4
+ {SPR_PISF,32768,7,{A_Light1},S_LIGHTDONE,0,0}, // S_PISTOLFLASH
+ {SPR_SHTG,0,1,{A_WeaponReady},S_SGUN,0,0}, // S_SGUN
+ {SPR_SHTG,0,1,{A_Lower},S_SGUNDOWN,0,0}, // S_SGUNDOWN
+ {SPR_SHTG,0,1,{A_Raise},S_SGUNUP,0,0}, // S_SGUNUP
+ {SPR_SHTG,0,3,{NULL},S_SGUN2,0,0}, // S_SGUN1
+ {SPR_SHTG,0,7,{A_FireShotgun},S_SGUN3,0,0}, // S_SGUN2
+ {SPR_SHTG,1,5,{NULL},S_SGUN4,0,0}, // S_SGUN3
+ {SPR_SHTG,2,5,{NULL},S_SGUN5,0,0}, // S_SGUN4
+ {SPR_SHTG,3,4,{NULL},S_SGUN6,0,0}, // S_SGUN5
+ {SPR_SHTG,2,5,{NULL},S_SGUN7,0,0}, // S_SGUN6
+ {SPR_SHTG,1,5,{NULL},S_SGUN8,0,0}, // S_SGUN7
+ {SPR_SHTG,0,3,{NULL},S_SGUN9,0,0}, // S_SGUN8
+ {SPR_SHTG,0,7,{A_ReFire},S_SGUN,0,0}, // S_SGUN9
+ {SPR_SHTF,32768,4,{A_Light1},S_SGUNFLASH2,0,0}, // S_SGUNFLASH1
+ {SPR_SHTF,32769,3,{A_Light2},S_LIGHTDONE,0,0}, // S_SGUNFLASH2
+ {SPR_SHT2,0,1,{A_WeaponReady},S_DSGUN,0,0}, // S_DSGUN
+ {SPR_SHT2,0,1,{A_Lower},S_DSGUNDOWN,0,0}, // S_DSGUNDOWN
+ {SPR_SHT2,0,1,{A_Raise},S_DSGUNUP,0,0}, // S_DSGUNUP
+ {SPR_SHT2,0,3,{NULL},S_DSGUN2,0,0}, // S_DSGUN1
+ {SPR_SHT2,0,7,{A_FireShotgun2},S_DSGUN3,0,0}, // S_DSGUN2
+ {SPR_SHT2,1,7,{NULL},S_DSGUN4,0,0}, // S_DSGUN3
+ {SPR_SHT2,2,7,{A_CheckReload},S_DSGUN5,0,0}, // S_DSGUN4
+ {SPR_SHT2,3,7,{A_OpenShotgun2},S_DSGUN6,0,0}, // S_DSGUN5
+ {SPR_SHT2,4,7,{NULL},S_DSGUN7,0,0}, // S_DSGUN6
+ {SPR_SHT2,5,7,{A_LoadShotgun2},S_DSGUN8,0,0}, // S_DSGUN7
+ {SPR_SHT2,6,6,{NULL},S_DSGUN9,0,0}, // S_DSGUN8
+ {SPR_SHT2,7,6,{A_CloseShotgun2},S_DSGUN10,0,0}, // S_DSGUN9
+ {SPR_SHT2,0,5,{A_ReFire},S_DSGUN,0,0}, // S_DSGUN10
+ {SPR_SHT2,1,7,{NULL},S_DSNR2,0,0}, // S_DSNR1
+ {SPR_SHT2,0,3,{NULL},S_DSGUNDOWN,0,0}, // S_DSNR2
+ {SPR_SHT2,32776,5,{A_Light1},S_DSGUNFLASH2,0,0}, // S_DSGUNFLASH1
+ {SPR_SHT2,32777,4,{A_Light2},S_LIGHTDONE,0,0}, // S_DSGUNFLASH2
+ {SPR_CHGG,0,1,{A_WeaponReady},S_CHAIN,0,0}, // S_CHAIN
+ {SPR_CHGG,0,1,{A_Lower},S_CHAINDOWN,0,0}, // S_CHAINDOWN
+ {SPR_CHGG,0,1,{A_Raise},S_CHAINUP,0,0}, // S_CHAINUP
+ {SPR_CHGG,0,4,{A_FireCGun},S_CHAIN2,0,0}, // S_CHAIN1
+ {SPR_CHGG,1,4,{A_FireCGun},S_CHAIN3,0,0}, // S_CHAIN2
+ {SPR_CHGG,1,0,{A_ReFire},S_CHAIN,0,0}, // S_CHAIN3
+ {SPR_CHGF,32768,5,{A_Light1},S_LIGHTDONE,0,0}, // S_CHAINFLASH1
+ {SPR_CHGF,32769,5,{A_Light2},S_LIGHTDONE,0,0}, // S_CHAINFLASH2
+ {SPR_MISG,0,1,{A_WeaponReady},S_MISSILE,0,0}, // S_MISSILE
+ {SPR_MISG,0,1,{A_Lower},S_MISSILEDOWN,0,0}, // S_MISSILEDOWN
+ {SPR_MISG,0,1,{A_Raise},S_MISSILEUP,0,0}, // S_MISSILEUP
+ {SPR_MISG,1,8,{A_GunFlash},S_MISSILE2,0,0}, // S_MISSILE1
+ {SPR_MISG,1,12,{A_FireMissile},S_MISSILE3,0,0}, // S_MISSILE2
+ {SPR_MISG,1,0,{A_ReFire},S_MISSILE,0,0}, // S_MISSILE3
+ {SPR_MISF,32768,3,{A_Light1},S_MISSILEFLASH2,0,0}, // S_MISSILEFLASH1
+ {SPR_MISF,32769,4,{NULL},S_MISSILEFLASH3,0,0}, // S_MISSILEFLASH2
+ {SPR_MISF,32770,4,{A_Light2},S_MISSILEFLASH4,0,0}, // S_MISSILEFLASH3
+ {SPR_MISF,32771,4,{A_Light2},S_LIGHTDONE,0,0}, // S_MISSILEFLASH4
+ {SPR_SAWG,2,4,{A_WeaponReady},S_SAWB,0,0}, // S_SAW
+ {SPR_SAWG,3,4,{A_WeaponReady},S_SAW,0,0}, // S_SAWB
+ {SPR_SAWG,2,1,{A_Lower},S_SAWDOWN,0,0}, // S_SAWDOWN
+ {SPR_SAWG,2,1,{A_Raise},S_SAWUP,0,0}, // S_SAWUP
+ {SPR_SAWG,0,4,{A_Saw},S_SAW2,0,0}, // S_SAW1
+ {SPR_SAWG,1,4,{A_Saw},S_SAW3,0,0}, // S_SAW2
+ {SPR_SAWG,1,0,{A_ReFire},S_SAW,0,0}, // S_SAW3
+ {SPR_PLSG,0,1,{A_WeaponReady},S_PLASMA,0,0}, // S_PLASMA
+ {SPR_PLSG,0,1,{A_Lower},S_PLASMADOWN,0,0}, // S_PLASMADOWN
+ {SPR_PLSG,0,1,{A_Raise},S_PLASMAUP,0,0}, // S_PLASMAUP
+ {SPR_PLSG,0,3,{A_FirePlasma},S_PLASMA2,0,0}, // S_PLASMA1
+ {SPR_PLSG,1,20,{A_ReFire},S_PLASMA,0,0}, // S_PLASMA2
+ {SPR_PLSF,32768,4,{A_Light1},S_LIGHTDONE,0,0}, // S_PLASMAFLASH1
+ {SPR_PLSF,32769,4,{A_Light1},S_LIGHTDONE,0,0}, // S_PLASMAFLASH2
+ {SPR_BFGG,0,1,{A_WeaponReady},S_BFG,0,0}, // S_BFG
+ {SPR_BFGG,0,1,{A_Lower},S_BFGDOWN,0,0}, // S_BFGDOWN
+ {SPR_BFGG,0,1,{A_Raise},S_BFGUP,0,0}, // S_BFGUP
+ {SPR_BFGG,0,20,{A_BFGsound},S_BFG2,0,0}, // S_BFG1
+ {SPR_BFGG,1,10,{A_GunFlash},S_BFG3,0,0}, // S_BFG2
+ {SPR_BFGG,1,10,{A_FireBFG},S_BFG4,0,0}, // S_BFG3
+ {SPR_BFGG,1,20,{A_ReFire},S_BFG,0,0}, // S_BFG4
+ {SPR_BFGF,32768,11,{A_Light1},S_BFGFLASH2,0,0}, // S_BFGFLASH1
+ {SPR_BFGF,32769,6,{A_Light2},S_LIGHTDONE,0,0}, // S_BFGFLASH2
+ {SPR_BLUD,2,8,{NULL},S_BLOOD2,0,0}, // S_BLOOD1
+ {SPR_BLUD,1,8,{NULL},S_BLOOD3,0,0}, // S_BLOOD2
+ {SPR_BLUD,0,8,{NULL},S_NULL,0,0}, // S_BLOOD3
+ {SPR_PUFF,32768,4,{NULL},S_PUFF2,0,0}, // S_PUFF1
+ {SPR_PUFF,1,4,{NULL},S_PUFF3,0,0}, // S_PUFF2
+ {SPR_PUFF,2,4,{NULL},S_PUFF4,0,0}, // S_PUFF3
+ {SPR_PUFF,3,4,{NULL},S_NULL,0,0}, // S_PUFF4
+ {SPR_BAL1,32768,4,{NULL},S_TBALL2,0,0}, // S_TBALL1
+ {SPR_BAL1,32769,4,{NULL},S_TBALL1,0,0}, // S_TBALL2
+ {SPR_BAL1,32770,6,{NULL},S_TBALLX2,0,0}, // S_TBALLX1
+ {SPR_BAL1,32771,6,{NULL},S_TBALLX3,0,0}, // S_TBALLX2
+ {SPR_BAL1,32772,6,{NULL},S_NULL,0,0}, // S_TBALLX3
+ {SPR_BAL2,32768,4,{NULL},S_RBALL2,0,0}, // S_RBALL1
+ {SPR_BAL2,32769,4,{NULL},S_RBALL1,0,0}, // S_RBALL2
+ {SPR_BAL2,32770,6,{NULL},S_RBALLX2,0,0}, // S_RBALLX1
+ {SPR_BAL2,32771,6,{NULL},S_RBALLX3,0,0}, // S_RBALLX2
+ {SPR_BAL2,32772,6,{NULL},S_NULL,0,0}, // S_RBALLX3
+ {SPR_PLSS,32768,6,{NULL},S_PLASBALL2,0,0}, // S_PLASBALL
+ {SPR_PLSS,32769,6,{NULL},S_PLASBALL,0,0}, // S_PLASBALL2
+ {SPR_PLSE,32768,4,{NULL},S_PLASEXP2,0,0}, // S_PLASEXP
+ {SPR_PLSE,32769,4,{NULL},S_PLASEXP3,0,0}, // S_PLASEXP2
+ {SPR_PLSE,32770,4,{NULL},S_PLASEXP4,0,0}, // S_PLASEXP3
+ {SPR_PLSE,32771,4,{NULL},S_PLASEXP5,0,0}, // S_PLASEXP4
+ {SPR_PLSE,32772,4,{NULL},S_NULL,0,0}, // S_PLASEXP5
+ {SPR_MISL,32768,1,{NULL},S_ROCKET,0,0}, // S_ROCKET
+ {SPR_BFS1,32768,4,{NULL},S_BFGSHOT2,0,0}, // S_BFGSHOT
+ {SPR_BFS1,32769,4,{NULL},S_BFGSHOT,0,0}, // S_BFGSHOT2
+ {SPR_BFE1,32768,8,{NULL},S_BFGLAND2,0,0}, // S_BFGLAND
+ {SPR_BFE1,32769,8,{NULL},S_BFGLAND3,0,0}, // S_BFGLAND2
+ {SPR_BFE1,32770,8,{A_BFGSpray},S_BFGLAND4,0,0}, // S_BFGLAND3
+ {SPR_BFE1,32771,8,{NULL},S_BFGLAND5,0,0}, // S_BFGLAND4
+ {SPR_BFE1,32772,8,{NULL},S_BFGLAND6,0,0}, // S_BFGLAND5
+ {SPR_BFE1,32773,8,{NULL},S_NULL,0,0}, // S_BFGLAND6
+ {SPR_BFE2,32768,8,{NULL},S_BFGEXP2,0,0}, // S_BFGEXP
+ {SPR_BFE2,32769,8,{NULL},S_BFGEXP3,0,0}, // S_BFGEXP2
+ {SPR_BFE2,32770,8,{NULL},S_BFGEXP4,0,0}, // S_BFGEXP3
+ {SPR_BFE2,32771,8,{NULL},S_NULL,0,0}, // S_BFGEXP4
+ {SPR_MISL,32769,8,{A_Explode},S_EXPLODE2,0,0}, // S_EXPLODE1
+ {SPR_MISL,32770,6,{NULL},S_EXPLODE3,0,0}, // S_EXPLODE2
+ {SPR_MISL,32771,4,{NULL},S_NULL,0,0}, // S_EXPLODE3
+ {SPR_TFOG,32768,6,{NULL},S_TFOG01,0,0}, // S_TFOG
+ {SPR_TFOG,32769,6,{NULL},S_TFOG02,0,0}, // S_TFOG01
+ {SPR_TFOG,32768,6,{NULL},S_TFOG2,0,0}, // S_TFOG02
+ {SPR_TFOG,32769,6,{NULL},S_TFOG3,0,0}, // S_TFOG2
+ {SPR_TFOG,32770,6,{NULL},S_TFOG4,0,0}, // S_TFOG3
+ {SPR_TFOG,32771,6,{NULL},S_TFOG5,0,0}, // S_TFOG4
+ {SPR_TFOG,32772,6,{NULL},S_TFOG6,0,0}, // S_TFOG5
+ {SPR_TFOG,32773,6,{NULL},S_TFOG7,0,0}, // S_TFOG6
+ {SPR_TFOG,32774,6,{NULL},S_TFOG8,0,0}, // S_TFOG7
+ {SPR_TFOG,32775,6,{NULL},S_TFOG9,0,0}, // S_TFOG8
+ {SPR_TFOG,32776,6,{NULL},S_TFOG10,0,0}, // S_TFOG9
+ {SPR_TFOG,32777,6,{NULL},S_NULL,0,0}, // S_TFOG10
+ {SPR_IFOG,32768,6,{NULL},S_IFOG01,0,0}, // S_IFOG
+ {SPR_IFOG,32769,6,{NULL},S_IFOG02,0,0}, // S_IFOG01
+ {SPR_IFOG,32768,6,{NULL},S_IFOG2,0,0}, // S_IFOG02
+ {SPR_IFOG,32769,6,{NULL},S_IFOG3,0,0}, // S_IFOG2
+ {SPR_IFOG,32770,6,{NULL},S_IFOG4,0,0}, // S_IFOG3
+ {SPR_IFOG,32771,6,{NULL},S_IFOG5,0,0}, // S_IFOG4
+ {SPR_IFOG,32772,6,{NULL},S_NULL,0,0}, // S_IFOG5
+ {SPR_PLAY,0,-1,{NULL},S_NULL,0,0}, // S_PLAY
+ {SPR_PLAY,0,4,{NULL},S_PLAY_RUN2,0,0}, // S_PLAY_RUN1
+ {SPR_PLAY,1,4,{NULL},S_PLAY_RUN3,0,0}, // S_PLAY_RUN2
+ {SPR_PLAY,2,4,{NULL},S_PLAY_RUN4,0,0}, // S_PLAY_RUN3
+ {SPR_PLAY,3,4,{NULL},S_PLAY_RUN1,0,0}, // S_PLAY_RUN4
+ {SPR_PLAY,4,12,{NULL},S_PLAY,0,0}, // S_PLAY_ATK1
+ {SPR_PLAY,32773,6,{NULL},S_PLAY_ATK1,0,0}, // S_PLAY_ATK2
+ {SPR_PLAY,6,4,{NULL},S_PLAY_PAIN2,0,0}, // S_PLAY_PAIN
+ {SPR_PLAY,6,4,{A_Pain},S_PLAY,0,0}, // S_PLAY_PAIN2
+ {SPR_PLAY,7,10,{NULL},S_PLAY_DIE2,0,0}, // S_PLAY_DIE1
+ {SPR_PLAY,8,10,{A_PlayerScream},S_PLAY_DIE3,0,0}, // S_PLAY_DIE2
+ {SPR_PLAY,9,10,{A_Fall},S_PLAY_DIE4,0,0}, // S_PLAY_DIE3
+ {SPR_PLAY,10,10,{NULL},S_PLAY_DIE5,0,0}, // S_PLAY_DIE4
+ {SPR_PLAY,11,10,{NULL},S_PLAY_DIE6,0,0}, // S_PLAY_DIE5
+ {SPR_PLAY,12,10,{NULL},S_PLAY_DIE7,0,0}, // S_PLAY_DIE6
+ {SPR_PLAY,13,-1,{NULL},S_NULL,0,0}, // S_PLAY_DIE7
+ {SPR_PLAY,14,5,{NULL},S_PLAY_XDIE2,0,0}, // S_PLAY_XDIE1
+ {SPR_PLAY,15,5,{A_XScream},S_PLAY_XDIE3,0,0}, // S_PLAY_XDIE2
+ {SPR_PLAY,16,5,{A_Fall},S_PLAY_XDIE4,0,0}, // S_PLAY_XDIE3
+ {SPR_PLAY,17,5,{NULL},S_PLAY_XDIE5,0,0}, // S_PLAY_XDIE4
+ {SPR_PLAY,18,5,{NULL},S_PLAY_XDIE6,0,0}, // S_PLAY_XDIE5
+ {SPR_PLAY,19,5,{NULL},S_PLAY_XDIE7,0,0}, // S_PLAY_XDIE6
+ {SPR_PLAY,20,5,{NULL},S_PLAY_XDIE8,0,0}, // S_PLAY_XDIE7
+ {SPR_PLAY,21,5,{NULL},S_PLAY_XDIE9,0,0}, // S_PLAY_XDIE8
+ {SPR_PLAY,22,-1,{NULL},S_NULL,0,0}, // S_PLAY_XDIE9
+ {SPR_POSS,0,10,{A_Look},S_POSS_STND2,0,0}, // S_POSS_STND
+ {SPR_POSS,1,10,{A_Look},S_POSS_STND,0,0}, // S_POSS_STND2
+ {SPR_POSS,0,4,{A_Chase},S_POSS_RUN2,0,0}, // S_POSS_RUN1
+ {SPR_POSS,0,4,{A_Chase},S_POSS_RUN3,0,0}, // S_POSS_RUN2
+ {SPR_POSS,1,4,{A_Chase},S_POSS_RUN4,0,0}, // S_POSS_RUN3
+ {SPR_POSS,1,4,{A_Chase},S_POSS_RUN5,0,0}, // S_POSS_RUN4
+ {SPR_POSS,2,4,{A_Chase},S_POSS_RUN6,0,0}, // S_POSS_RUN5
+ {SPR_POSS,2,4,{A_Chase},S_POSS_RUN7,0,0}, // S_POSS_RUN6
+ {SPR_POSS,3,4,{A_Chase},S_POSS_RUN8,0,0}, // S_POSS_RUN7
+ {SPR_POSS,3,4,{A_Chase},S_POSS_RUN1,0,0}, // S_POSS_RUN8
+ {SPR_POSS,4,10,{A_FaceTarget},S_POSS_ATK2,0,0}, // S_POSS_ATK1
+ {SPR_POSS,5,8,{A_PosAttack},S_POSS_ATK3,0,0}, // S_POSS_ATK2
+ {SPR_POSS,4,8,{NULL},S_POSS_RUN1,0,0}, // S_POSS_ATK3
+ {SPR_POSS,6,3,{NULL},S_POSS_PAIN2,0,0}, // S_POSS_PAIN
+ {SPR_POSS,6,3,{A_Pain},S_POSS_RUN1,0,0}, // S_POSS_PAIN2
+ {SPR_POSS,7,5,{NULL},S_POSS_DIE2,0,0}, // S_POSS_DIE1
+ {SPR_POSS,8,5,{A_Scream},S_POSS_DIE3,0,0}, // S_POSS_DIE2
+ {SPR_POSS,9,5,{A_Fall},S_POSS_DIE4,0,0}, // S_POSS_DIE3
+ {SPR_POSS,10,5,{NULL},S_POSS_DIE5,0,0}, // S_POSS_DIE4
+ {SPR_POSS,11,-1,{NULL},S_NULL,0,0}, // S_POSS_DIE5
+ {SPR_POSS,12,5,{NULL},S_POSS_XDIE2,0,0}, // S_POSS_XDIE1
+ {SPR_POSS,13,5,{A_XScream},S_POSS_XDIE3,0,0}, // S_POSS_XDIE2
+ {SPR_POSS,14,5,{A_Fall},S_POSS_XDIE4,0,0}, // S_POSS_XDIE3
+ {SPR_POSS,15,5,{NULL},S_POSS_XDIE5,0,0}, // S_POSS_XDIE4
+ {SPR_POSS,16,5,{NULL},S_POSS_XDIE6,0,0}, // S_POSS_XDIE5
+ {SPR_POSS,17,5,{NULL},S_POSS_XDIE7,0,0}, // S_POSS_XDIE6
+ {SPR_POSS,18,5,{NULL},S_POSS_XDIE8,0,0}, // S_POSS_XDIE7
+ {SPR_POSS,19,5,{NULL},S_POSS_XDIE9,0,0}, // S_POSS_XDIE8
+ {SPR_POSS,20,-1,{NULL},S_NULL,0,0}, // S_POSS_XDIE9
+ {SPR_POSS,10,5,{NULL},S_POSS_RAISE2,0,0}, // S_POSS_RAISE1
+ {SPR_POSS,9,5,{NULL},S_POSS_RAISE3,0,0}, // S_POSS_RAISE2
+ {SPR_POSS,8,5,{NULL},S_POSS_RAISE4,0,0}, // S_POSS_RAISE3
+ {SPR_POSS,7,5,{NULL},S_POSS_RUN1,0,0}, // S_POSS_RAISE4
+ {SPR_SPOS,0,10,{A_Look},S_SPOS_STND2,0,0}, // S_SPOS_STND
+ {SPR_SPOS,1,10,{A_Look},S_SPOS_STND,0,0}, // S_SPOS_STND2
+ {SPR_SPOS,0,3,{A_Chase},S_SPOS_RUN2,0,0}, // S_SPOS_RUN1
+ {SPR_SPOS,0,3,{A_Chase},S_SPOS_RUN3,0,0}, // S_SPOS_RUN2
+ {SPR_SPOS,1,3,{A_Chase},S_SPOS_RUN4,0,0}, // S_SPOS_RUN3
+ {SPR_SPOS,1,3,{A_Chase},S_SPOS_RUN5,0,0}, // S_SPOS_RUN4
+ {SPR_SPOS,2,3,{A_Chase},S_SPOS_RUN6,0,0}, // S_SPOS_RUN5
+ {SPR_SPOS,2,3,{A_Chase},S_SPOS_RUN7,0,0}, // S_SPOS_RUN6
+ {SPR_SPOS,3,3,{A_Chase},S_SPOS_RUN8,0,0}, // S_SPOS_RUN7
+ {SPR_SPOS,3,3,{A_Chase},S_SPOS_RUN1,0,0}, // S_SPOS_RUN8
+ {SPR_SPOS,4,10,{A_FaceTarget},S_SPOS_ATK2,0,0}, // S_SPOS_ATK1
+ {SPR_SPOS,32773,10,{A_SPosAttack},S_SPOS_ATK3,0,0}, // S_SPOS_ATK2
+ {SPR_SPOS,4,10,{NULL},S_SPOS_RUN1,0,0}, // S_SPOS_ATK3
+ {SPR_SPOS,6,3,{NULL},S_SPOS_PAIN2,0,0}, // S_SPOS_PAIN
+ {SPR_SPOS,6,3,{A_Pain},S_SPOS_RUN1,0,0}, // S_SPOS_PAIN2
+ {SPR_SPOS,7,5,{NULL},S_SPOS_DIE2,0,0}, // S_SPOS_DIE1
+ {SPR_SPOS,8,5,{A_Scream},S_SPOS_DIE3,0,0}, // S_SPOS_DIE2
+ {SPR_SPOS,9,5,{A_Fall},S_SPOS_DIE4,0,0}, // S_SPOS_DIE3
+ {SPR_SPOS,10,5,{NULL},S_SPOS_DIE5,0,0}, // S_SPOS_DIE4
+ {SPR_SPOS,11,-1,{NULL},S_NULL,0,0}, // S_SPOS_DIE5
+ {SPR_SPOS,12,5,{NULL},S_SPOS_XDIE2,0,0}, // S_SPOS_XDIE1
+ {SPR_SPOS,13,5,{A_XScream},S_SPOS_XDIE3,0,0}, // S_SPOS_XDIE2
+ {SPR_SPOS,14,5,{A_Fall},S_SPOS_XDIE4,0,0}, // S_SPOS_XDIE3
+ {SPR_SPOS,15,5,{NULL},S_SPOS_XDIE5,0,0}, // S_SPOS_XDIE4
+ {SPR_SPOS,16,5,{NULL},S_SPOS_XDIE6,0,0}, // S_SPOS_XDIE5
+ {SPR_SPOS,17,5,{NULL},S_SPOS_XDIE7,0,0}, // S_SPOS_XDIE6
+ {SPR_SPOS,18,5,{NULL},S_SPOS_XDIE8,0,0}, // S_SPOS_XDIE7
+ {SPR_SPOS,19,5,{NULL},S_SPOS_XDIE9,0,0}, // S_SPOS_XDIE8
+ {SPR_SPOS,20,-1,{NULL},S_NULL,0,0}, // S_SPOS_XDIE9
+ {SPR_SPOS,11,5,{NULL},S_SPOS_RAISE2,0,0}, // S_SPOS_RAISE1
+ {SPR_SPOS,10,5,{NULL},S_SPOS_RAISE3,0,0}, // S_SPOS_RAISE2
+ {SPR_SPOS,9,5,{NULL},S_SPOS_RAISE4,0,0}, // S_SPOS_RAISE3
+ {SPR_SPOS,8,5,{NULL},S_SPOS_RAISE5,0,0}, // S_SPOS_RAISE4
+ {SPR_SPOS,7,5,{NULL},S_SPOS_RUN1,0,0}, // S_SPOS_RAISE5
+ {SPR_VILE,0,10,{A_Look},S_VILE_STND2,0,0}, // S_VILE_STND
+ {SPR_VILE,1,10,{A_Look},S_VILE_STND,0,0}, // S_VILE_STND2
+ {SPR_VILE,0,2,{A_VileChase},S_VILE_RUN2,0,0}, // S_VILE_RUN1
+ {SPR_VILE,0,2,{A_VileChase},S_VILE_RUN3,0,0}, // S_VILE_RUN2
+ {SPR_VILE,1,2,{A_VileChase},S_VILE_RUN4,0,0}, // S_VILE_RUN3
+ {SPR_VILE,1,2,{A_VileChase},S_VILE_RUN5,0,0}, // S_VILE_RUN4
+ {SPR_VILE,2,2,{A_VileChase},S_VILE_RUN6,0,0}, // S_VILE_RUN5
+ {SPR_VILE,2,2,{A_VileChase},S_VILE_RUN7,0,0}, // S_VILE_RUN6
+ {SPR_VILE,3,2,{A_VileChase},S_VILE_RUN8,0,0}, // S_VILE_RUN7
+ {SPR_VILE,3,2,{A_VileChase},S_VILE_RUN9,0,0}, // S_VILE_RUN8
+ {SPR_VILE,4,2,{A_VileChase},S_VILE_RUN10,0,0}, // S_VILE_RUN9
+ {SPR_VILE,4,2,{A_VileChase},S_VILE_RUN11,0,0}, // S_VILE_RUN10
+ {SPR_VILE,5,2,{A_VileChase},S_VILE_RUN12,0,0}, // S_VILE_RUN11
+ {SPR_VILE,5,2,{A_VileChase},S_VILE_RUN1,0,0}, // S_VILE_RUN12
+ {SPR_VILE,32774,0,{A_VileStart},S_VILE_ATK2,0,0}, // S_VILE_ATK1
+ {SPR_VILE,32774,10,{A_FaceTarget},S_VILE_ATK3,0,0}, // S_VILE_ATK2
+ {SPR_VILE,32775,8,{A_VileTarget},S_VILE_ATK4,0,0}, // S_VILE_ATK3
+ {SPR_VILE,32776,8,{A_FaceTarget},S_VILE_ATK5,0,0}, // S_VILE_ATK4
+ {SPR_VILE,32777,8,{A_FaceTarget},S_VILE_ATK6,0,0}, // S_VILE_ATK5
+ {SPR_VILE,32778,8,{A_FaceTarget},S_VILE_ATK7,0,0}, // S_VILE_ATK6
+ {SPR_VILE,32779,8,{A_FaceTarget},S_VILE_ATK8,0,0}, // S_VILE_ATK7
+ {SPR_VILE,32780,8,{A_FaceTarget},S_VILE_ATK9,0,0}, // S_VILE_ATK8
+ {SPR_VILE,32781,8,{A_FaceTarget},S_VILE_ATK10,0,0}, // S_VILE_ATK9
+ {SPR_VILE,32782,8,{A_VileAttack},S_VILE_ATK11,0,0}, // S_VILE_ATK10
+ {SPR_VILE,32783,20,{NULL},S_VILE_RUN1,0,0}, // S_VILE_ATK11
+ {SPR_VILE,32794,10,{NULL},S_VILE_HEAL2,0,0}, // S_VILE_HEAL1
+ {SPR_VILE,32795,10,{NULL},S_VILE_HEAL3,0,0}, // S_VILE_HEAL2
+ {SPR_VILE,32796,10,{NULL},S_VILE_RUN1,0,0}, // S_VILE_HEAL3
+ {SPR_VILE,16,5,{NULL},S_VILE_PAIN2,0,0}, // S_VILE_PAIN
+ {SPR_VILE,16,5,{A_Pain},S_VILE_RUN1,0,0}, // S_VILE_PAIN2
+ {SPR_VILE,16,7,{NULL},S_VILE_DIE2,0,0}, // S_VILE_DIE1
+ {SPR_VILE,17,7,{A_Scream},S_VILE_DIE3,0,0}, // S_VILE_DIE2
+ {SPR_VILE,18,7,{A_Fall},S_VILE_DIE4,0,0}, // S_VILE_DIE3
+ {SPR_VILE,19,7,{NULL},S_VILE_DIE5,0,0}, // S_VILE_DIE4
+ {SPR_VILE,20,7,{NULL},S_VILE_DIE6,0,0}, // S_VILE_DIE5
+ {SPR_VILE,21,7,{NULL},S_VILE_DIE7,0,0}, // S_VILE_DIE6
+ {SPR_VILE,22,7,{NULL},S_VILE_DIE8,0,0}, // S_VILE_DIE7
+ {SPR_VILE,23,5,{NULL},S_VILE_DIE9,0,0}, // S_VILE_DIE8
+ {SPR_VILE,24,5,{NULL},S_VILE_DIE10,0,0}, // S_VILE_DIE9
+ {SPR_VILE,25,-1,{NULL},S_NULL,0,0}, // S_VILE_DIE10
+ {SPR_FIRE,32768,2,{A_StartFire},S_FIRE2,0,0}, // S_FIRE1
+ {SPR_FIRE,32769,2,{A_Fire},S_FIRE3,0,0}, // S_FIRE2
+ {SPR_FIRE,32768,2,{A_Fire},S_FIRE4,0,0}, // S_FIRE3
+ {SPR_FIRE,32769,2,{A_Fire},S_FIRE5,0,0}, // S_FIRE4
+ {SPR_FIRE,32770,2,{A_FireCrackle},S_FIRE6,0,0}, // S_FIRE5
+ {SPR_FIRE,32769,2,{A_Fire},S_FIRE7,0,0}, // S_FIRE6
+ {SPR_FIRE,32770,2,{A_Fire},S_FIRE8,0,0}, // S_FIRE7
+ {SPR_FIRE,32769,2,{A_Fire},S_FIRE9,0,0}, // S_FIRE8
+ {SPR_FIRE,32770,2,{A_Fire},S_FIRE10,0,0}, // S_FIRE9
+ {SPR_FIRE,32771,2,{A_Fire},S_FIRE11,0,0}, // S_FIRE10
+ {SPR_FIRE,32770,2,{A_Fire},S_FIRE12,0,0}, // S_FIRE11
+ {SPR_FIRE,32771,2,{A_Fire},S_FIRE13,0,0}, // S_FIRE12
+ {SPR_FIRE,32770,2,{A_Fire},S_FIRE14,0,0}, // S_FIRE13
+ {SPR_FIRE,32771,2,{A_Fire},S_FIRE15,0,0}, // S_FIRE14
+ {SPR_FIRE,32772,2,{A_Fire},S_FIRE16,0,0}, // S_FIRE15
+ {SPR_FIRE,32771,2,{A_Fire},S_FIRE17,0,0}, // S_FIRE16
+ {SPR_FIRE,32772,2,{A_Fire},S_FIRE18,0,0}, // S_FIRE17
+ {SPR_FIRE,32771,2,{A_Fire},S_FIRE19,0,0}, // S_FIRE18
+ {SPR_FIRE,32772,2,{A_FireCrackle},S_FIRE20,0,0}, // S_FIRE19
+ {SPR_FIRE,32773,2,{A_Fire},S_FIRE21,0,0}, // S_FIRE20
+ {SPR_FIRE,32772,2,{A_Fire},S_FIRE22,0,0}, // S_FIRE21
+ {SPR_FIRE,32773,2,{A_Fire},S_FIRE23,0,0}, // S_FIRE22
+ {SPR_FIRE,32772,2,{A_Fire},S_FIRE24,0,0}, // S_FIRE23
+ {SPR_FIRE,32773,2,{A_Fire},S_FIRE25,0,0}, // S_FIRE24
+ {SPR_FIRE,32774,2,{A_Fire},S_FIRE26,0,0}, // S_FIRE25
+ {SPR_FIRE,32775,2,{A_Fire},S_FIRE27,0,0}, // S_FIRE26
+ {SPR_FIRE,32774,2,{A_Fire},S_FIRE28,0,0}, // S_FIRE27
+ {SPR_FIRE,32775,2,{A_Fire},S_FIRE29,0,0}, // S_FIRE28
+ {SPR_FIRE,32774,2,{A_Fire},S_FIRE30,0,0}, // S_FIRE29
+ {SPR_FIRE,32775,2,{A_Fire},S_NULL,0,0}, // S_FIRE30
+ {SPR_PUFF,1,4,{NULL},S_SMOKE2,0,0}, // S_SMOKE1
+ {SPR_PUFF,2,4,{NULL},S_SMOKE3,0,0}, // S_SMOKE2
+ {SPR_PUFF,1,4,{NULL},S_SMOKE4,0,0}, // S_SMOKE3
+ {SPR_PUFF,2,4,{NULL},S_SMOKE5,0,0}, // S_SMOKE4
+ {SPR_PUFF,3,4,{NULL},S_NULL,0,0}, // S_SMOKE5
+ {SPR_FATB,32768,2,{A_Tracer},S_TRACER2,0,0}, // S_TRACER
+ {SPR_FATB,32769,2,{A_Tracer},S_TRACER,0,0}, // S_TRACER2
+ {SPR_FBXP,32768,8,{NULL},S_TRACEEXP2,0,0}, // S_TRACEEXP1
+ {SPR_FBXP,32769,6,{NULL},S_TRACEEXP3,0,0}, // S_TRACEEXP2
+ {SPR_FBXP,32770,4,{NULL},S_NULL,0,0}, // S_TRACEEXP3
+ {SPR_SKEL,0,10,{A_Look},S_SKEL_STND2,0,0}, // S_SKEL_STND
+ {SPR_SKEL,1,10,{A_Look},S_SKEL_STND,0,0}, // S_SKEL_STND2
+ {SPR_SKEL,0,2,{A_Chase},S_SKEL_RUN2,0,0}, // S_SKEL_RUN1
+ {SPR_SKEL,0,2,{A_Chase},S_SKEL_RUN3,0,0}, // S_SKEL_RUN2
+ {SPR_SKEL,1,2,{A_Chase},S_SKEL_RUN4,0,0}, // S_SKEL_RUN3
+ {SPR_SKEL,1,2,{A_Chase},S_SKEL_RUN5,0,0}, // S_SKEL_RUN4
+ {SPR_SKEL,2,2,{A_Chase},S_SKEL_RUN6,0,0}, // S_SKEL_RUN5
+ {SPR_SKEL,2,2,{A_Chase},S_SKEL_RUN7,0,0}, // S_SKEL_RUN6
+ {SPR_SKEL,3,2,{A_Chase},S_SKEL_RUN8,0,0}, // S_SKEL_RUN7
+ {SPR_SKEL,3,2,{A_Chase},S_SKEL_RUN9,0,0}, // S_SKEL_RUN8
+ {SPR_SKEL,4,2,{A_Chase},S_SKEL_RUN10,0,0}, // S_SKEL_RUN9
+ {SPR_SKEL,4,2,{A_Chase},S_SKEL_RUN11,0,0}, // S_SKEL_RUN10
+ {SPR_SKEL,5,2,{A_Chase},S_SKEL_RUN12,0,0}, // S_SKEL_RUN11
+ {SPR_SKEL,5,2,{A_Chase},S_SKEL_RUN1,0,0}, // S_SKEL_RUN12
+ {SPR_SKEL,6,0,{A_FaceTarget},S_SKEL_FIST2,0,0}, // S_SKEL_FIST1
+ {SPR_SKEL,6,6,{A_SkelWhoosh},S_SKEL_FIST3,0,0}, // S_SKEL_FIST2
+ {SPR_SKEL,7,6,{A_FaceTarget},S_SKEL_FIST4,0,0}, // S_SKEL_FIST3
+ {SPR_SKEL,8,6,{A_SkelFist},S_SKEL_RUN1,0,0}, // S_SKEL_FIST4
+ {SPR_SKEL,32777,0,{A_FaceTarget},S_SKEL_MISS2,0,0}, // S_SKEL_MISS1
+ {SPR_SKEL,32777,10,{A_FaceTarget},S_SKEL_MISS3,0,0}, // S_SKEL_MISS2
+ {SPR_SKEL,10,10,{A_SkelMissile},S_SKEL_MISS4,0,0}, // S_SKEL_MISS3
+ {SPR_SKEL,10,10,{A_FaceTarget},S_SKEL_RUN1,0,0}, // S_SKEL_MISS4
+ {SPR_SKEL,11,5,{NULL},S_SKEL_PAIN2,0,0}, // S_SKEL_PAIN
+ {SPR_SKEL,11,5,{A_Pain},S_SKEL_RUN1,0,0}, // S_SKEL_PAIN2
+ {SPR_SKEL,11,7,{NULL},S_SKEL_DIE2,0,0}, // S_SKEL_DIE1
+ {SPR_SKEL,12,7,{NULL},S_SKEL_DIE3,0,0}, // S_SKEL_DIE2
+ {SPR_SKEL,13,7,{A_Scream},S_SKEL_DIE4,0,0}, // S_SKEL_DIE3
+ {SPR_SKEL,14,7,{A_Fall},S_SKEL_DIE5,0,0}, // S_SKEL_DIE4
+ {SPR_SKEL,15,7,{NULL},S_SKEL_DIE6,0,0}, // S_SKEL_DIE5
+ {SPR_SKEL,16,-1,{NULL},S_NULL,0,0}, // S_SKEL_DIE6
+ {SPR_SKEL,16,5,{NULL},S_SKEL_RAISE2,0,0}, // S_SKEL_RAISE1
+ {SPR_SKEL,15,5,{NULL},S_SKEL_RAISE3,0,0}, // S_SKEL_RAISE2
+ {SPR_SKEL,14,5,{NULL},S_SKEL_RAISE4,0,0}, // S_SKEL_RAISE3
+ {SPR_SKEL,13,5,{NULL},S_SKEL_RAISE5,0,0}, // S_SKEL_RAISE4
+ {SPR_SKEL,12,5,{NULL},S_SKEL_RAISE6,0,0}, // S_SKEL_RAISE5
+ {SPR_SKEL,11,5,{NULL},S_SKEL_RUN1,0,0}, // S_SKEL_RAISE6
+ {SPR_MANF,32768,4,{NULL},S_FATSHOT2,0,0}, // S_FATSHOT1
+ {SPR_MANF,32769,4,{NULL},S_FATSHOT1,0,0}, // S_FATSHOT2
+ {SPR_MISL,32769,8,{NULL},S_FATSHOTX2,0,0}, // S_FATSHOTX1
+ {SPR_MISL,32770,6,{NULL},S_FATSHOTX3,0,0}, // S_FATSHOTX2
+ {SPR_MISL,32771,4,{NULL},S_NULL,0,0}, // S_FATSHOTX3
+ {SPR_FATT,0,15,{A_Look},S_FATT_STND2,0,0}, // S_FATT_STND
+ {SPR_FATT,1,15,{A_Look},S_FATT_STND,0,0}, // S_FATT_STND2
+ {SPR_FATT,0,4,{A_Chase},S_FATT_RUN2,0,0}, // S_FATT_RUN1
+ {SPR_FATT,0,4,{A_Chase},S_FATT_RUN3,0,0}, // S_FATT_RUN2
+ {SPR_FATT,1,4,{A_Chase},S_FATT_RUN4,0,0}, // S_FATT_RUN3
+ {SPR_FATT,1,4,{A_Chase},S_FATT_RUN5,0,0}, // S_FATT_RUN4
+ {SPR_FATT,2,4,{A_Chase},S_FATT_RUN6,0,0}, // S_FATT_RUN5
+ {SPR_FATT,2,4,{A_Chase},S_FATT_RUN7,0,0}, // S_FATT_RUN6
+ {SPR_FATT,3,4,{A_Chase},S_FATT_RUN8,0,0}, // S_FATT_RUN7
+ {SPR_FATT,3,4,{A_Chase},S_FATT_RUN9,0,0}, // S_FATT_RUN8
+ {SPR_FATT,4,4,{A_Chase},S_FATT_RUN10,0,0}, // S_FATT_RUN9
+ {SPR_FATT,4,4,{A_Chase},S_FATT_RUN11,0,0}, // S_FATT_RUN10
+ {SPR_FATT,5,4,{A_Chase},S_FATT_RUN12,0,0}, // S_FATT_RUN11
+ {SPR_FATT,5,4,{A_Chase},S_FATT_RUN1,0,0}, // S_FATT_RUN12
+ {SPR_FATT,6,20,{A_FatRaise},S_FATT_ATK2,0,0}, // S_FATT_ATK1
+ {SPR_FATT,32775,10,{A_FatAttack1},S_FATT_ATK3,0,0}, // S_FATT_ATK2
+ {SPR_FATT,8,5,{A_FaceTarget},S_FATT_ATK4,0,0}, // S_FATT_ATK3
+ {SPR_FATT,6,5,{A_FaceTarget},S_FATT_ATK5,0,0}, // S_FATT_ATK4
+ {SPR_FATT,32775,10,{A_FatAttack2},S_FATT_ATK6,0,0}, // S_FATT_ATK5
+ {SPR_FATT,8,5,{A_FaceTarget},S_FATT_ATK7,0,0}, // S_FATT_ATK6
+ {SPR_FATT,6,5,{A_FaceTarget},S_FATT_ATK8,0,0}, // S_FATT_ATK7
+ {SPR_FATT,32775,10,{A_FatAttack3},S_FATT_ATK9,0,0}, // S_FATT_ATK8
+ {SPR_FATT,8,5,{A_FaceTarget},S_FATT_ATK10,0,0}, // S_FATT_ATK9
+ {SPR_FATT,6,5,{A_FaceTarget},S_FATT_RUN1,0,0}, // S_FATT_ATK10
+ {SPR_FATT,9,3,{NULL},S_FATT_PAIN2,0,0}, // S_FATT_PAIN
+ {SPR_FATT,9,3,{A_Pain},S_FATT_RUN1,0,0}, // S_FATT_PAIN2
+ {SPR_FATT,10,6,{NULL},S_FATT_DIE2,0,0}, // S_FATT_DIE1
+ {SPR_FATT,11,6,{A_Scream},S_FATT_DIE3,0,0}, // S_FATT_DIE2
+ {SPR_FATT,12,6,{A_Fall},S_FATT_DIE4,0,0}, // S_FATT_DIE3
+ {SPR_FATT,13,6,{NULL},S_FATT_DIE5,0,0}, // S_FATT_DIE4
+ {SPR_FATT,14,6,{NULL},S_FATT_DIE6,0,0}, // S_FATT_DIE5
+ {SPR_FATT,15,6,{NULL},S_FATT_DIE7,0,0}, // S_FATT_DIE6
+ {SPR_FATT,16,6,{NULL},S_FATT_DIE8,0,0}, // S_FATT_DIE7
+ {SPR_FATT,17,6,{NULL},S_FATT_DIE9,0,0}, // S_FATT_DIE8
+ {SPR_FATT,18,6,{NULL},S_FATT_DIE10,0,0}, // S_FATT_DIE9
+ {SPR_FATT,19,-1,{A_BossDeath},S_NULL,0,0}, // S_FATT_DIE10
+ {SPR_FATT,17,5,{NULL},S_FATT_RAISE2,0,0}, // S_FATT_RAISE1
+ {SPR_FATT,16,5,{NULL},S_FATT_RAISE3,0,0}, // S_FATT_RAISE2
+ {SPR_FATT,15,5,{NULL},S_FATT_RAISE4,0,0}, // S_FATT_RAISE3
+ {SPR_FATT,14,5,{NULL},S_FATT_RAISE5,0,0}, // S_FATT_RAISE4
+ {SPR_FATT,13,5,{NULL},S_FATT_RAISE6,0,0}, // S_FATT_RAISE5
+ {SPR_FATT,12,5,{NULL},S_FATT_RAISE7,0,0}, // S_FATT_RAISE6
+ {SPR_FATT,11,5,{NULL},S_FATT_RAISE8,0,0}, // S_FATT_RAISE7
+ {SPR_FATT,10,5,{NULL},S_FATT_RUN1,0,0}, // S_FATT_RAISE8
+ {SPR_CPOS,0,10,{A_Look},S_CPOS_STND2,0,0}, // S_CPOS_STND
+ {SPR_CPOS,1,10,{A_Look},S_CPOS_STND,0,0}, // S_CPOS_STND2
+ {SPR_CPOS,0,3,{A_Chase},S_CPOS_RUN2,0,0}, // S_CPOS_RUN1
+ {SPR_CPOS,0,3,{A_Chase},S_CPOS_RUN3,0,0}, // S_CPOS_RUN2
+ {SPR_CPOS,1,3,{A_Chase},S_CPOS_RUN4,0,0}, // S_CPOS_RUN3
+ {SPR_CPOS,1,3,{A_Chase},S_CPOS_RUN5,0,0}, // S_CPOS_RUN4
+ {SPR_CPOS,2,3,{A_Chase},S_CPOS_RUN6,0,0}, // S_CPOS_RUN5
+ {SPR_CPOS,2,3,{A_Chase},S_CPOS_RUN7,0,0}, // S_CPOS_RUN6
+ {SPR_CPOS,3,3,{A_Chase},S_CPOS_RUN8,0,0}, // S_CPOS_RUN7
+ {SPR_CPOS,3,3,{A_Chase},S_CPOS_RUN1,0,0}, // S_CPOS_RUN8
+ {SPR_CPOS,4,10,{A_FaceTarget},S_CPOS_ATK2,0,0}, // S_CPOS_ATK1
+ {SPR_CPOS,32773,4,{A_CPosAttack},S_CPOS_ATK3,0,0}, // S_CPOS_ATK2
+ {SPR_CPOS,32772,4,{A_CPosAttack},S_CPOS_ATK4,0,0}, // S_CPOS_ATK3
+ {SPR_CPOS,5,1,{A_CPosRefire},S_CPOS_ATK2,0,0}, // S_CPOS_ATK4
+ {SPR_CPOS,6,3,{NULL},S_CPOS_PAIN2,0,0}, // S_CPOS_PAIN
+ {SPR_CPOS,6,3,{A_Pain},S_CPOS_RUN1,0,0}, // S_CPOS_PAIN2
+ {SPR_CPOS,7,5,{NULL},S_CPOS_DIE2,0,0}, // S_CPOS_DIE1
+ {SPR_CPOS,8,5,{A_Scream},S_CPOS_DIE3,0,0}, // S_CPOS_DIE2
+ {SPR_CPOS,9,5,{A_Fall},S_CPOS_DIE4,0,0}, // S_CPOS_DIE3
+ {SPR_CPOS,10,5,{NULL},S_CPOS_DIE5,0,0}, // S_CPOS_DIE4
+ {SPR_CPOS,11,5,{NULL},S_CPOS_DIE6,0,0}, // S_CPOS_DIE5
+ {SPR_CPOS,12,5,{NULL},S_CPOS_DIE7,0,0}, // S_CPOS_DIE6
+ {SPR_CPOS,13,-1,{NULL},S_NULL,0,0}, // S_CPOS_DIE7
+ {SPR_CPOS,14,5,{NULL},S_CPOS_XDIE2,0,0}, // S_CPOS_XDIE1
+ {SPR_CPOS,15,5,{A_XScream},S_CPOS_XDIE3,0,0}, // S_CPOS_XDIE2
+ {SPR_CPOS,16,5,{A_Fall},S_CPOS_XDIE4,0,0}, // S_CPOS_XDIE3
+ {SPR_CPOS,17,5,{NULL},S_CPOS_XDIE5,0,0}, // S_CPOS_XDIE4
+ {SPR_CPOS,18,5,{NULL},S_CPOS_XDIE6,0,0}, // S_CPOS_XDIE5
+ {SPR_CPOS,19,-1,{NULL},S_NULL,0,0}, // S_CPOS_XDIE6
+ {SPR_CPOS,13,5,{NULL},S_CPOS_RAISE2,0,0}, // S_CPOS_RAISE1
+ {SPR_CPOS,12,5,{NULL},S_CPOS_RAISE3,0,0}, // S_CPOS_RAISE2
+ {SPR_CPOS,11,5,{NULL},S_CPOS_RAISE4,0,0}, // S_CPOS_RAISE3
+ {SPR_CPOS,10,5,{NULL},S_CPOS_RAISE5,0,0}, // S_CPOS_RAISE4
+ {SPR_CPOS,9,5,{NULL},S_CPOS_RAISE6,0,0}, // S_CPOS_RAISE5
+ {SPR_CPOS,8,5,{NULL},S_CPOS_RAISE7,0,0}, // S_CPOS_RAISE6
+ {SPR_CPOS,7,5,{NULL},S_CPOS_RUN1,0,0}, // S_CPOS_RAISE7
+ {SPR_TROO,0,10,{A_Look},S_TROO_STND2,0,0}, // S_TROO_STND
+ {SPR_TROO,1,10,{A_Look},S_TROO_STND,0,0}, // S_TROO_STND2
+ {SPR_TROO,0,3,{A_Chase},S_TROO_RUN2,0,0}, // S_TROO_RUN1
+ {SPR_TROO,0,3,{A_Chase},S_TROO_RUN3,0,0}, // S_TROO_RUN2
+ {SPR_TROO,1,3,{A_Chase},S_TROO_RUN4,0,0}, // S_TROO_RUN3
+ {SPR_TROO,1,3,{A_Chase},S_TROO_RUN5,0,0}, // S_TROO_RUN4
+ {SPR_TROO,2,3,{A_Chase},S_TROO_RUN6,0,0}, // S_TROO_RUN5
+ {SPR_TROO,2,3,{A_Chase},S_TROO_RUN7,0,0}, // S_TROO_RUN6
+ {SPR_TROO,3,3,{A_Chase},S_TROO_RUN8,0,0}, // S_TROO_RUN7
+ {SPR_TROO,3,3,{A_Chase},S_TROO_RUN1,0,0}, // S_TROO_RUN8
+ {SPR_TROO,4,8,{A_FaceTarget},S_TROO_ATK2,0,0}, // S_TROO_ATK1
+ {SPR_TROO,5,8,{A_FaceTarget},S_TROO_ATK3,0,0}, // S_TROO_ATK2
+ {SPR_TROO,6,6,{A_TroopAttack},S_TROO_RUN1,0,0}, // S_TROO_ATK3
+ {SPR_TROO,7,2,{NULL},S_TROO_PAIN2,0,0}, // S_TROO_PAIN
+ {SPR_TROO,7,2,{A_Pain},S_TROO_RUN1,0,0}, // S_TROO_PAIN2
+ {SPR_TROO,8,8,{NULL},S_TROO_DIE2,0,0}, // S_TROO_DIE1
+ {SPR_TROO,9,8,{A_Scream},S_TROO_DIE3,0,0}, // S_TROO_DIE2
+ {SPR_TROO,10,6,{NULL},S_TROO_DIE4,0,0}, // S_TROO_DIE3
+ {SPR_TROO,11,6,{A_Fall},S_TROO_DIE5,0,0}, // S_TROO_DIE4
+ {SPR_TROO,12,-1,{NULL},S_NULL,0,0}, // S_TROO_DIE5
+ {SPR_TROO,13,5,{NULL},S_TROO_XDIE2,0,0}, // S_TROO_XDIE1
+ {SPR_TROO,14,5,{A_XScream},S_TROO_XDIE3,0,0}, // S_TROO_XDIE2
+ {SPR_TROO,15,5,{NULL},S_TROO_XDIE4,0,0}, // S_TROO_XDIE3
+ {SPR_TROO,16,5,{A_Fall},S_TROO_XDIE5,0,0}, // S_TROO_XDIE4
+ {SPR_TROO,17,5,{NULL},S_TROO_XDIE6,0,0}, // S_TROO_XDIE5
+ {SPR_TROO,18,5,{NULL},S_TROO_XDIE7,0,0}, // S_TROO_XDIE6
+ {SPR_TROO,19,5,{NULL},S_TROO_XDIE8,0,0}, // S_TROO_XDIE7
+ {SPR_TROO,20,-1,{NULL},S_NULL,0,0}, // S_TROO_XDIE8
+ {SPR_TROO,12,8,{NULL},S_TROO_RAISE2,0,0}, // S_TROO_RAISE1
+ {SPR_TROO,11,8,{NULL},S_TROO_RAISE3,0,0}, // S_TROO_RAISE2
+ {SPR_TROO,10,6,{NULL},S_TROO_RAISE4,0,0}, // S_TROO_RAISE3
+ {SPR_TROO,9,6,{NULL},S_TROO_RAISE5,0,0}, // S_TROO_RAISE4
+ {SPR_TROO,8,6,{NULL},S_TROO_RUN1,0,0}, // S_TROO_RAISE5
+ {SPR_SARG,0,10,{A_Look},S_SARG_STND2,0,0}, // S_SARG_STND
+ {SPR_SARG,1,10,{A_Look},S_SARG_STND,0,0}, // S_SARG_STND2
+ {SPR_SARG,0,2,{A_Chase},S_SARG_RUN2,0,0}, // S_SARG_RUN1
+ {SPR_SARG,0,2,{A_Chase},S_SARG_RUN3,0,0}, // S_SARG_RUN2
+ {SPR_SARG,1,2,{A_Chase},S_SARG_RUN4,0,0}, // S_SARG_RUN3
+ {SPR_SARG,1,2,{A_Chase},S_SARG_RUN5,0,0}, // S_SARG_RUN4
+ {SPR_SARG,2,2,{A_Chase},S_SARG_RUN6,0,0}, // S_SARG_RUN5
+ {SPR_SARG,2,2,{A_Chase},S_SARG_RUN7,0,0}, // S_SARG_RUN6
+ {SPR_SARG,3,2,{A_Chase},S_SARG_RUN8,0,0}, // S_SARG_RUN7
+ {SPR_SARG,3,2,{A_Chase},S_SARG_RUN1,0,0}, // S_SARG_RUN8
+ {SPR_SARG,4,8,{A_FaceTarget},S_SARG_ATK2,0,0}, // S_SARG_ATK1
+ {SPR_SARG,5,8,{A_FaceTarget},S_SARG_ATK3,0,0}, // S_SARG_ATK2
+ {SPR_SARG,6,8,{A_SargAttack},S_SARG_RUN1,0,0}, // S_SARG_ATK3
+ {SPR_SARG,7,2,{NULL},S_SARG_PAIN2,0,0}, // S_SARG_PAIN
+ {SPR_SARG,7,2,{A_Pain},S_SARG_RUN1,0,0}, // S_SARG_PAIN2
+ {SPR_SARG,8,8,{NULL},S_SARG_DIE2,0,0}, // S_SARG_DIE1
+ {SPR_SARG,9,8,{A_Scream},S_SARG_DIE3,0,0}, // S_SARG_DIE2
+ {SPR_SARG,10,4,{NULL},S_SARG_DIE4,0,0}, // S_SARG_DIE3
+ {SPR_SARG,11,4,{A_Fall},S_SARG_DIE5,0,0}, // S_SARG_DIE4
+ {SPR_SARG,12,4,{NULL},S_SARG_DIE6,0,0}, // S_SARG_DIE5
+ {SPR_SARG,13,-1,{NULL},S_NULL,0,0}, // S_SARG_DIE6
+ {SPR_SARG,13,5,{NULL},S_SARG_RAISE2,0,0}, // S_SARG_RAISE1
+ {SPR_SARG,12,5,{NULL},S_SARG_RAISE3,0,0}, // S_SARG_RAISE2
+ {SPR_SARG,11,5,{NULL},S_SARG_RAISE4,0,0}, // S_SARG_RAISE3
+ {SPR_SARG,10,5,{NULL},S_SARG_RAISE5,0,0}, // S_SARG_RAISE4
+ {SPR_SARG,9,5,{NULL},S_SARG_RAISE6,0,0}, // S_SARG_RAISE5
+ {SPR_SARG,8,5,{NULL},S_SARG_RUN1,0,0}, // S_SARG_RAISE6
+ {SPR_HEAD,0,10,{A_Look},S_HEAD_STND,0,0}, // S_HEAD_STND
+ {SPR_HEAD,0,3,{A_Chase},S_HEAD_RUN1,0,0}, // S_HEAD_RUN1
+ {SPR_HEAD,1,5,{A_FaceTarget},S_HEAD_ATK2,0,0}, // S_HEAD_ATK1
+ {SPR_HEAD,2,5,{A_FaceTarget},S_HEAD_ATK3,0,0}, // S_HEAD_ATK2
+ {SPR_HEAD,32771,5,{A_HeadAttack},S_HEAD_RUN1,0,0}, // S_HEAD_ATK3
+ {SPR_HEAD,4,3,{NULL},S_HEAD_PAIN2,0,0}, // S_HEAD_PAIN
+ {SPR_HEAD,4,3,{A_Pain},S_HEAD_PAIN3,0,0}, // S_HEAD_PAIN2
+ {SPR_HEAD,5,6,{NULL},S_HEAD_RUN1,0,0}, // S_HEAD_PAIN3
+ {SPR_HEAD,6,8,{NULL},S_HEAD_DIE2,0,0}, // S_HEAD_DIE1
+ {SPR_HEAD,7,8,{A_Scream},S_HEAD_DIE3,0,0}, // S_HEAD_DIE2
+ {SPR_HEAD,8,8,{NULL},S_HEAD_DIE4,0,0}, // S_HEAD_DIE3
+ {SPR_HEAD,9,8,{NULL},S_HEAD_DIE5,0,0}, // S_HEAD_DIE4
+ {SPR_HEAD,10,8,{A_Fall},S_HEAD_DIE6,0,0}, // S_HEAD_DIE5
+ {SPR_HEAD,11,-1,{NULL},S_NULL,0,0}, // S_HEAD_DIE6
+ {SPR_HEAD,11,8,{NULL},S_HEAD_RAISE2,0,0}, // S_HEAD_RAISE1
+ {SPR_HEAD,10,8,{NULL},S_HEAD_RAISE3,0,0}, // S_HEAD_RAISE2
+ {SPR_HEAD,9,8,{NULL},S_HEAD_RAISE4,0,0}, // S_HEAD_RAISE3
+ {SPR_HEAD,8,8,{NULL},S_HEAD_RAISE5,0,0}, // S_HEAD_RAISE4
+ {SPR_HEAD,7,8,{NULL},S_HEAD_RAISE6,0,0}, // S_HEAD_RAISE5
+ {SPR_HEAD,6,8,{NULL},S_HEAD_RUN1,0,0}, // S_HEAD_RAISE6
+ {SPR_BAL7,32768,4,{NULL},S_BRBALL2,0,0}, // S_BRBALL1
+ {SPR_BAL7,32769,4,{NULL},S_BRBALL1,0,0}, // S_BRBALL2
+ {SPR_BAL7,32770,6,{NULL},S_BRBALLX2,0,0}, // S_BRBALLX1
+ {SPR_BAL7,32771,6,{NULL},S_BRBALLX3,0,0}, // S_BRBALLX2
+ {SPR_BAL7,32772,6,{NULL},S_NULL,0,0}, // S_BRBALLX3
+ {SPR_BOSS,0,10,{A_Look},S_BOSS_STND2,0,0}, // S_BOSS_STND
+ {SPR_BOSS,1,10,{A_Look},S_BOSS_STND,0,0}, // S_BOSS_STND2
+ {SPR_BOSS,0,3,{A_Chase},S_BOSS_RUN2,0,0}, // S_BOSS_RUN1
+ {SPR_BOSS,0,3,{A_Chase},S_BOSS_RUN3,0,0}, // S_BOSS_RUN2
+ {SPR_BOSS,1,3,{A_Chase},S_BOSS_RUN4,0,0}, // S_BOSS_RUN3
+ {SPR_BOSS,1,3,{A_Chase},S_BOSS_RUN5,0,0}, // S_BOSS_RUN4
+ {SPR_BOSS,2,3,{A_Chase},S_BOSS_RUN6,0,0}, // S_BOSS_RUN5
+ {SPR_BOSS,2,3,{A_Chase},S_BOSS_RUN7,0,0}, // S_BOSS_RUN6
+ {SPR_BOSS,3,3,{A_Chase},S_BOSS_RUN8,0,0}, // S_BOSS_RUN7
+ {SPR_BOSS,3,3,{A_Chase},S_BOSS_RUN1,0,0}, // S_BOSS_RUN8
+ {SPR_BOSS,4,8,{A_FaceTarget},S_BOSS_ATK2,0,0}, // S_BOSS_ATK1
+ {SPR_BOSS,5,8,{A_FaceTarget},S_BOSS_ATK3,0,0}, // S_BOSS_ATK2
+ {SPR_BOSS,6,8,{A_BruisAttack},S_BOSS_RUN1,0,0}, // S_BOSS_ATK3
+ {SPR_BOSS,7,2,{NULL},S_BOSS_PAIN2,0,0}, // S_BOSS_PAIN
+ {SPR_BOSS,7,2,{A_Pain},S_BOSS_RUN1,0,0}, // S_BOSS_PAIN2
+ {SPR_BOSS,8,8,{NULL},S_BOSS_DIE2,0,0}, // S_BOSS_DIE1
+ {SPR_BOSS,9,8,{A_Scream},S_BOSS_DIE3,0,0}, // S_BOSS_DIE2
+ {SPR_BOSS,10,8,{NULL},S_BOSS_DIE4,0,0}, // S_BOSS_DIE3
+ {SPR_BOSS,11,8,{A_Fall},S_BOSS_DIE5,0,0}, // S_BOSS_DIE4
+ {SPR_BOSS,12,8,{NULL},S_BOSS_DIE6,0,0}, // S_BOSS_DIE5
+ {SPR_BOSS,13,8,{NULL},S_BOSS_DIE7,0,0}, // S_BOSS_DIE6
+ {SPR_BOSS,14,-1,{A_BossDeath},S_NULL,0,0}, // S_BOSS_DIE7
+ {SPR_BOSS,14,8,{NULL},S_BOSS_RAISE2,0,0}, // S_BOSS_RAISE1
+ {SPR_BOSS,13,8,{NULL},S_BOSS_RAISE3,0,0}, // S_BOSS_RAISE2
+ {SPR_BOSS,12,8,{NULL},S_BOSS_RAISE4,0,0}, // S_BOSS_RAISE3
+ {SPR_BOSS,11,8,{NULL},S_BOSS_RAISE5,0,0}, // S_BOSS_RAISE4
+ {SPR_BOSS,10,8,{NULL},S_BOSS_RAISE6,0,0}, // S_BOSS_RAISE5
+ {SPR_BOSS,9,8,{NULL},S_BOSS_RAISE7,0,0}, // S_BOSS_RAISE6
+ {SPR_BOSS,8,8,{NULL},S_BOSS_RUN1,0,0}, // S_BOSS_RAISE7
+ {SPR_BOS2,0,10,{A_Look},S_BOS2_STND2,0,0}, // S_BOS2_STND
+ {SPR_BOS2,1,10,{A_Look},S_BOS2_STND,0,0}, // S_BOS2_STND2
+ {SPR_BOS2,0,3,{A_Chase},S_BOS2_RUN2,0,0}, // S_BOS2_RUN1
+ {SPR_BOS2,0,3,{A_Chase},S_BOS2_RUN3,0,0}, // S_BOS2_RUN2
+ {SPR_BOS2,1,3,{A_Chase},S_BOS2_RUN4,0,0}, // S_BOS2_RUN3
+ {SPR_BOS2,1,3,{A_Chase},S_BOS2_RUN5,0,0}, // S_BOS2_RUN4
+ {SPR_BOS2,2,3,{A_Chase},S_BOS2_RUN6,0,0}, // S_BOS2_RUN5
+ {SPR_BOS2,2,3,{A_Chase},S_BOS2_RUN7,0,0}, // S_BOS2_RUN6
+ {SPR_BOS2,3,3,{A_Chase},S_BOS2_RUN8,0,0}, // S_BOS2_RUN7
+ {SPR_BOS2,3,3,{A_Chase},S_BOS2_RUN1,0,0}, // S_BOS2_RUN8
+ {SPR_BOS2,4,8,{A_FaceTarget},S_BOS2_ATK2,0,0}, // S_BOS2_ATK1
+ {SPR_BOS2,5,8,{A_FaceTarget},S_BOS2_ATK3,0,0}, // S_BOS2_ATK2
+ {SPR_BOS2,6,8,{A_BruisAttack},S_BOS2_RUN1,0,0}, // S_BOS2_ATK3
+ {SPR_BOS2,7,2,{NULL},S_BOS2_PAIN2,0,0}, // S_BOS2_PAIN
+ {SPR_BOS2,7,2,{A_Pain},S_BOS2_RUN1,0,0}, // S_BOS2_PAIN2
+ {SPR_BOS2,8,8,{NULL},S_BOS2_DIE2,0,0}, // S_BOS2_DIE1
+ {SPR_BOS2,9,8,{A_Scream},S_BOS2_DIE3,0,0}, // S_BOS2_DIE2
+ {SPR_BOS2,10,8,{NULL},S_BOS2_DIE4,0,0}, // S_BOS2_DIE3
+ {SPR_BOS2,11,8,{A_Fall},S_BOS2_DIE5,0,0}, // S_BOS2_DIE4
+ {SPR_BOS2,12,8,{NULL},S_BOS2_DIE6,0,0}, // S_BOS2_DIE5
+ {SPR_BOS2,13,8,{NULL},S_BOS2_DIE7,0,0}, // S_BOS2_DIE6
+ {SPR_BOS2,14,-1,{NULL},S_NULL,0,0}, // S_BOS2_DIE7
+ {SPR_BOS2,14,8,{NULL},S_BOS2_RAISE2,0,0}, // S_BOS2_RAISE1
+ {SPR_BOS2,13,8,{NULL},S_BOS2_RAISE3,0,0}, // S_BOS2_RAISE2
+ {SPR_BOS2,12,8,{NULL},S_BOS2_RAISE4,0,0}, // S_BOS2_RAISE3
+ {SPR_BOS2,11,8,{NULL},S_BOS2_RAISE5,0,0}, // S_BOS2_RAISE4
+ {SPR_BOS2,10,8,{NULL},S_BOS2_RAISE6,0,0}, // S_BOS2_RAISE5
+ {SPR_BOS2,9,8,{NULL},S_BOS2_RAISE7,0,0}, // S_BOS2_RAISE6
+ {SPR_BOS2,8,8,{NULL},S_BOS2_RUN1,0,0}, // S_BOS2_RAISE7
+ {SPR_SKUL,32768,10,{A_Look},S_SKULL_STND2,0,0}, // S_SKULL_STND
+ {SPR_SKUL,32769,10,{A_Look},S_SKULL_STND,0,0}, // S_SKULL_STND2
+ {SPR_SKUL,32768,6,{A_Chase},S_SKULL_RUN2,0,0}, // S_SKULL_RUN1
+ {SPR_SKUL,32769,6,{A_Chase},S_SKULL_RUN1,0,0}, // S_SKULL_RUN2
+ {SPR_SKUL,32770,10,{A_FaceTarget},S_SKULL_ATK2,0,0}, // S_SKULL_ATK1
+ {SPR_SKUL,32771,4,{A_SkullAttack},S_SKULL_ATK3,0,0}, // S_SKULL_ATK2
+ {SPR_SKUL,32770,4,{NULL},S_SKULL_ATK4,0,0}, // S_SKULL_ATK3
+ {SPR_SKUL,32771,4,{NULL},S_SKULL_ATK3,0,0}, // S_SKULL_ATK4
+ {SPR_SKUL,32772,3,{NULL},S_SKULL_PAIN2,0,0}, // S_SKULL_PAIN
+ {SPR_SKUL,32772,3,{A_Pain},S_SKULL_RUN1,0,0}, // S_SKULL_PAIN2
+ {SPR_SKUL,32773,6,{NULL},S_SKULL_DIE2,0,0}, // S_SKULL_DIE1
+ {SPR_SKUL,32774,6,{A_Scream},S_SKULL_DIE3,0,0}, // S_SKULL_DIE2
+ {SPR_SKUL,32775,6,{NULL},S_SKULL_DIE4,0,0}, // S_SKULL_DIE3
+ {SPR_SKUL,32776,6,{A_Fall},S_SKULL_DIE5,0,0}, // S_SKULL_DIE4
+ {SPR_SKUL,9,6,{NULL},S_SKULL_DIE6,0,0}, // S_SKULL_DIE5
+ {SPR_SKUL,10,6,{NULL},S_NULL,0,0}, // S_SKULL_DIE6
+ {SPR_SPID,0,10,{A_Look},S_SPID_STND2,0,0}, // S_SPID_STND
+ {SPR_SPID,1,10,{A_Look},S_SPID_STND,0,0}, // S_SPID_STND2
+ {SPR_SPID,0,3,{A_Metal},S_SPID_RUN2,0,0}, // S_SPID_RUN1
+ {SPR_SPID,0,3,{A_Chase},S_SPID_RUN3,0,0}, // S_SPID_RUN2
+ {SPR_SPID,1,3,{A_Chase},S_SPID_RUN4,0,0}, // S_SPID_RUN3
+ {SPR_SPID,1,3,{A_Chase},S_SPID_RUN5,0,0}, // S_SPID_RUN4
+ {SPR_SPID,2,3,{A_Metal},S_SPID_RUN6,0,0}, // S_SPID_RUN5
+ {SPR_SPID,2,3,{A_Chase},S_SPID_RUN7,0,0}, // S_SPID_RUN6
+ {SPR_SPID,3,3,{A_Chase},S_SPID_RUN8,0,0}, // S_SPID_RUN7
+ {SPR_SPID,3,3,{A_Chase},S_SPID_RUN9,0,0}, // S_SPID_RUN8
+ {SPR_SPID,4,3,{A_Metal},S_SPID_RUN10,0,0}, // S_SPID_RUN9
+ {SPR_SPID,4,3,{A_Chase},S_SPID_RUN11,0,0}, // S_SPID_RUN10
+ {SPR_SPID,5,3,{A_Chase},S_SPID_RUN12,0,0}, // S_SPID_RUN11
+ {SPR_SPID,5,3,{A_Chase},S_SPID_RUN1,0,0}, // S_SPID_RUN12
+ {SPR_SPID,32768,20,{A_FaceTarget},S_SPID_ATK2,0,0}, // S_SPID_ATK1
+ {SPR_SPID,32774,4,{A_SPosAttack},S_SPID_ATK3,0,0}, // S_SPID_ATK2
+ {SPR_SPID,32775,4,{A_SPosAttack},S_SPID_ATK4,0,0}, // S_SPID_ATK3
+ {SPR_SPID,32775,1,{A_SpidRefire},S_SPID_ATK2,0,0}, // S_SPID_ATK4
+ {SPR_SPID,8,3,{NULL},S_SPID_PAIN2,0,0}, // S_SPID_PAIN
+ {SPR_SPID,8,3,{A_Pain},S_SPID_RUN1,0,0}, // S_SPID_PAIN2
+ {SPR_SPID,9,20,{A_Scream},S_SPID_DIE2,0,0}, // S_SPID_DIE1
+ {SPR_SPID,10,10,{A_Fall},S_SPID_DIE3,0,0}, // S_SPID_DIE2
+ {SPR_SPID,11,10,{NULL},S_SPID_DIE4,0,0}, // S_SPID_DIE3
+ {SPR_SPID,12,10,{NULL},S_SPID_DIE5,0,0}, // S_SPID_DIE4
+ {SPR_SPID,13,10,{NULL},S_SPID_DIE6,0,0}, // S_SPID_DIE5
+ {SPR_SPID,14,10,{NULL},S_SPID_DIE7,0,0}, // S_SPID_DIE6
+ {SPR_SPID,15,10,{NULL},S_SPID_DIE8,0,0}, // S_SPID_DIE7
+ {SPR_SPID,16,10,{NULL},S_SPID_DIE9,0,0}, // S_SPID_DIE8
+ {SPR_SPID,17,10,{NULL},S_SPID_DIE10,0,0}, // S_SPID_DIE9
+ {SPR_SPID,18,30,{NULL},S_SPID_DIE11,0,0}, // S_SPID_DIE10
+ {SPR_SPID,18,-1,{A_BossDeath},S_NULL,0,0}, // S_SPID_DIE11
+ {SPR_BSPI,0,10,{A_Look},S_BSPI_STND2,0,0}, // S_BSPI_STND
+ {SPR_BSPI,1,10,{A_Look},S_BSPI_STND,0,0}, // S_BSPI_STND2
+ {SPR_BSPI,0,20,{NULL},S_BSPI_RUN1,0,0}, // S_BSPI_SIGHT
+ {SPR_BSPI,0,3,{A_BabyMetal},S_BSPI_RUN2,0,0}, // S_BSPI_RUN1
+ {SPR_BSPI,0,3,{A_Chase},S_BSPI_RUN3,0,0}, // S_BSPI_RUN2
+ {SPR_BSPI,1,3,{A_Chase},S_BSPI_RUN4,0,0}, // S_BSPI_RUN3
+ {SPR_BSPI,1,3,{A_Chase},S_BSPI_RUN5,0,0}, // S_BSPI_RUN4
+ {SPR_BSPI,2,3,{A_Chase},S_BSPI_RUN6,0,0}, // S_BSPI_RUN5
+ {SPR_BSPI,2,3,{A_Chase},S_BSPI_RUN7,0,0}, // S_BSPI_RUN6
+ {SPR_BSPI,3,3,{A_BabyMetal},S_BSPI_RUN8,0,0}, // S_BSPI_RUN7
+ {SPR_BSPI,3,3,{A_Chase},S_BSPI_RUN9,0,0}, // S_BSPI_RUN8
+ {SPR_BSPI,4,3,{A_Chase},S_BSPI_RUN10,0,0}, // S_BSPI_RUN9
+ {SPR_BSPI,4,3,{A_Chase},S_BSPI_RUN11,0,0}, // S_BSPI_RUN10
+ {SPR_BSPI,5,3,{A_Chase},S_BSPI_RUN12,0,0}, // S_BSPI_RUN11
+ {SPR_BSPI,5,3,{A_Chase},S_BSPI_RUN1,0,0}, // S_BSPI_RUN12
+ {SPR_BSPI,32768,20,{A_FaceTarget},S_BSPI_ATK2,0,0}, // S_BSPI_ATK1
+ {SPR_BSPI,32774,4,{A_BspiAttack},S_BSPI_ATK3,0,0}, // S_BSPI_ATK2
+ {SPR_BSPI,32775,4,{NULL},S_BSPI_ATK4,0,0}, // S_BSPI_ATK3
+ {SPR_BSPI,32775,1,{A_SpidRefire},S_BSPI_ATK2,0,0}, // S_BSPI_ATK4
+ {SPR_BSPI,8,3,{NULL},S_BSPI_PAIN2,0,0}, // S_BSPI_PAIN
+ {SPR_BSPI,8,3,{A_Pain},S_BSPI_RUN1,0,0}, // S_BSPI_PAIN2
+ {SPR_BSPI,9,20,{A_Scream},S_BSPI_DIE2,0,0}, // S_BSPI_DIE1
+ {SPR_BSPI,10,7,{A_Fall},S_BSPI_DIE3,0,0}, // S_BSPI_DIE2
+ {SPR_BSPI,11,7,{NULL},S_BSPI_DIE4,0,0}, // S_BSPI_DIE3
+ {SPR_BSPI,12,7,{NULL},S_BSPI_DIE5,0,0}, // S_BSPI_DIE4
+ {SPR_BSPI,13,7,{NULL},S_BSPI_DIE6,0,0}, // S_BSPI_DIE5
+ {SPR_BSPI,14,7,{NULL},S_BSPI_DIE7,0,0}, // S_BSPI_DIE6
+ {SPR_BSPI,15,-1,{A_BossDeath},S_NULL,0,0}, // S_BSPI_DIE7
+ {SPR_BSPI,15,5,{NULL},S_BSPI_RAISE2,0,0}, // S_BSPI_RAISE1
+ {SPR_BSPI,14,5,{NULL},S_BSPI_RAISE3,0,0}, // S_BSPI_RAISE2
+ {SPR_BSPI,13,5,{NULL},S_BSPI_RAISE4,0,0}, // S_BSPI_RAISE3
+ {SPR_BSPI,12,5,{NULL},S_BSPI_RAISE5,0,0}, // S_BSPI_RAISE4
+ {SPR_BSPI,11,5,{NULL},S_BSPI_RAISE6,0,0}, // S_BSPI_RAISE5
+ {SPR_BSPI,10,5,{NULL},S_BSPI_RAISE7,0,0}, // S_BSPI_RAISE6
+ {SPR_BSPI,9,5,{NULL},S_BSPI_RUN1,0,0}, // S_BSPI_RAISE7
+ {SPR_APLS,32768,5,{NULL},S_ARACH_PLAZ2,0,0}, // S_ARACH_PLAZ
+ {SPR_APLS,32769,5,{NULL},S_ARACH_PLAZ,0,0}, // S_ARACH_PLAZ2
+ {SPR_APBX,32768,5,{NULL},S_ARACH_PLEX2,0,0}, // S_ARACH_PLEX
+ {SPR_APBX,32769,5,{NULL},S_ARACH_PLEX3,0,0}, // S_ARACH_PLEX2
+ {SPR_APBX,32770,5,{NULL},S_ARACH_PLEX4,0,0}, // S_ARACH_PLEX3
+ {SPR_APBX,32771,5,{NULL},S_ARACH_PLEX5,0,0}, // S_ARACH_PLEX4
+ {SPR_APBX,32772,5,{NULL},S_NULL,0,0}, // S_ARACH_PLEX5
+ {SPR_CYBR,0,10,{A_Look},S_CYBER_STND2,0,0}, // S_CYBER_STND
+ {SPR_CYBR,1,10,{A_Look},S_CYBER_STND,0,0}, // S_CYBER_STND2
+ {SPR_CYBR,0,3,{A_Hoof},S_CYBER_RUN2,0,0}, // S_CYBER_RUN1
+ {SPR_CYBR,0,3,{A_Chase},S_CYBER_RUN3,0,0}, // S_CYBER_RUN2
+ {SPR_CYBR,1,3,{A_Chase},S_CYBER_RUN4,0,0}, // S_CYBER_RUN3
+ {SPR_CYBR,1,3,{A_Chase},S_CYBER_RUN5,0,0}, // S_CYBER_RUN4
+ {SPR_CYBR,2,3,{A_Chase},S_CYBER_RUN6,0,0}, // S_CYBER_RUN5
+ {SPR_CYBR,2,3,{A_Chase},S_CYBER_RUN7,0,0}, // S_CYBER_RUN6
+ {SPR_CYBR,3,3,{A_Metal},S_CYBER_RUN8,0,0}, // S_CYBER_RUN7
+ {SPR_CYBR,3,3,{A_Chase},S_CYBER_RUN1,0,0}, // S_CYBER_RUN8
+ {SPR_CYBR,4,6,{A_FaceTarget},S_CYBER_ATK2,0,0}, // S_CYBER_ATK1
+ {SPR_CYBR,5,12,{A_CyberAttack},S_CYBER_ATK3,0,0}, // S_CYBER_ATK2
+ {SPR_CYBR,4,12,{A_FaceTarget},S_CYBER_ATK4,0,0}, // S_CYBER_ATK3
+ {SPR_CYBR,5,12,{A_CyberAttack},S_CYBER_ATK5,0,0}, // S_CYBER_ATK4
+ {SPR_CYBR,4,12,{A_FaceTarget},S_CYBER_ATK6,0,0}, // S_CYBER_ATK5
+ {SPR_CYBR,5,12,{A_CyberAttack},S_CYBER_RUN1,0,0}, // S_CYBER_ATK6
+ {SPR_CYBR,6,10,{A_Pain},S_CYBER_RUN1,0,0}, // S_CYBER_PAIN
+ {SPR_CYBR,7,10,{NULL},S_CYBER_DIE2,0,0}, // S_CYBER_DIE1
+ {SPR_CYBR,8,10,{A_Scream},S_CYBER_DIE3,0,0}, // S_CYBER_DIE2
+ {SPR_CYBR,9,10,{NULL},S_CYBER_DIE4,0,0}, // S_CYBER_DIE3
+ {SPR_CYBR,10,10,{NULL},S_CYBER_DIE5,0,0}, // S_CYBER_DIE4
+ {SPR_CYBR,11,10,{NULL},S_CYBER_DIE6,0,0}, // S_CYBER_DIE5
+ {SPR_CYBR,12,10,{A_Fall},S_CYBER_DIE7,0,0}, // S_CYBER_DIE6
+ {SPR_CYBR,13,10,{NULL},S_CYBER_DIE8,0,0}, // S_CYBER_DIE7
+ {SPR_CYBR,14,10,{NULL},S_CYBER_DIE9,0,0}, // S_CYBER_DIE8
+ {SPR_CYBR,15,30,{NULL},S_CYBER_DIE10,0,0}, // S_CYBER_DIE9
+ {SPR_CYBR,15,-1,{A_BossDeath},S_NULL,0,0}, // S_CYBER_DIE10
+ {SPR_PAIN,0,10,{A_Look},S_PAIN_STND,0,0}, // S_PAIN_STND
+ {SPR_PAIN,0,3,{A_Chase},S_PAIN_RUN2,0,0}, // S_PAIN_RUN1
+ {SPR_PAIN,0,3,{A_Chase},S_PAIN_RUN3,0,0}, // S_PAIN_RUN2
+ {SPR_PAIN,1,3,{A_Chase},S_PAIN_RUN4,0,0}, // S_PAIN_RUN3
+ {SPR_PAIN,1,3,{A_Chase},S_PAIN_RUN5,0,0}, // S_PAIN_RUN4
+ {SPR_PAIN,2,3,{A_Chase},S_PAIN_RUN6,0,0}, // S_PAIN_RUN5
+ {SPR_PAIN,2,3,{A_Chase},S_PAIN_RUN1,0,0}, // S_PAIN_RUN6
+ {SPR_PAIN,3,5,{A_FaceTarget},S_PAIN_ATK2,0,0}, // S_PAIN_ATK1
+ {SPR_PAIN,4,5,{A_FaceTarget},S_PAIN_ATK3,0,0}, // S_PAIN_ATK2
+ {SPR_PAIN,32773,5,{A_FaceTarget},S_PAIN_ATK4,0,0}, // S_PAIN_ATK3
+ {SPR_PAIN,32773,0,{A_PainAttack},S_PAIN_RUN1,0,0}, // S_PAIN_ATK4
+ {SPR_PAIN,6,6,{NULL},S_PAIN_PAIN2,0,0}, // S_PAIN_PAIN
+ {SPR_PAIN,6,6,{A_Pain},S_PAIN_RUN1,0,0}, // S_PAIN_PAIN2
+ {SPR_PAIN,32775,8,{NULL},S_PAIN_DIE2,0,0}, // S_PAIN_DIE1
+ {SPR_PAIN,32776,8,{A_Scream},S_PAIN_DIE3,0,0}, // S_PAIN_DIE2
+ {SPR_PAIN,32777,8,{NULL},S_PAIN_DIE4,0,0}, // S_PAIN_DIE3
+ {SPR_PAIN,32778,8,{NULL},S_PAIN_DIE5,0,0}, // S_PAIN_DIE4
+ {SPR_PAIN,32779,8,{A_PainDie},S_PAIN_DIE6,0,0}, // S_PAIN_DIE5
+ {SPR_PAIN,32780,8,{NULL},S_NULL,0,0}, // S_PAIN_DIE6
+ {SPR_PAIN,12,8,{NULL},S_PAIN_RAISE2,0,0}, // S_PAIN_RAISE1
+ {SPR_PAIN,11,8,{NULL},S_PAIN_RAISE3,0,0}, // S_PAIN_RAISE2
+ {SPR_PAIN,10,8,{NULL},S_PAIN_RAISE4,0,0}, // S_PAIN_RAISE3
+ {SPR_PAIN,9,8,{NULL},S_PAIN_RAISE5,0,0}, // S_PAIN_RAISE4
+ {SPR_PAIN,8,8,{NULL},S_PAIN_RAISE6,0,0}, // S_PAIN_RAISE5
+ {SPR_PAIN,7,8,{NULL},S_PAIN_RUN1,0,0}, // S_PAIN_RAISE6
+ {SPR_SSWV,0,10,{A_Look},S_SSWV_STND2,0,0}, // S_SSWV_STND
+ {SPR_SSWV,1,10,{A_Look},S_SSWV_STND,0,0}, // S_SSWV_STND2
+ {SPR_SSWV,0,3,{A_Chase},S_SSWV_RUN2,0,0}, // S_SSWV_RUN1
+ {SPR_SSWV,0,3,{A_Chase},S_SSWV_RUN3,0,0}, // S_SSWV_RUN2
+ {SPR_SSWV,1,3,{A_Chase},S_SSWV_RUN4,0,0}, // S_SSWV_RUN3
+ {SPR_SSWV,1,3,{A_Chase},S_SSWV_RUN5,0,0}, // S_SSWV_RUN4
+ {SPR_SSWV,2,3,{A_Chase},S_SSWV_RUN6,0,0}, // S_SSWV_RUN5
+ {SPR_SSWV,2,3,{A_Chase},S_SSWV_RUN7,0,0}, // S_SSWV_RUN6
+ {SPR_SSWV,3,3,{A_Chase},S_SSWV_RUN8,0,0}, // S_SSWV_RUN7
+ {SPR_SSWV,3,3,{A_Chase},S_SSWV_RUN1,0,0}, // S_SSWV_RUN8
+ {SPR_SSWV,4,10,{A_FaceTarget},S_SSWV_ATK2,0,0}, // S_SSWV_ATK1
+ {SPR_SSWV,5,10,{A_FaceTarget},S_SSWV_ATK3,0,0}, // S_SSWV_ATK2
+ {SPR_SSWV,32774,4,{A_CPosAttack},S_SSWV_ATK4,0,0}, // S_SSWV_ATK3
+ {SPR_SSWV,5,6,{A_FaceTarget},S_SSWV_ATK5,0,0}, // S_SSWV_ATK4
+ {SPR_SSWV,32774,4,{A_CPosAttack},S_SSWV_ATK6,0,0}, // S_SSWV_ATK5
+ {SPR_SSWV,5,1,{A_CPosRefire},S_SSWV_ATK2,0,0}, // S_SSWV_ATK6
+ {SPR_SSWV,7,3,{NULL},S_SSWV_PAIN2,0,0}, // S_SSWV_PAIN
+ {SPR_SSWV,7,3,{A_Pain},S_SSWV_RUN1,0,0}, // S_SSWV_PAIN2
+ {SPR_SSWV,8,5,{NULL},S_SSWV_DIE2,0,0}, // S_SSWV_DIE1
+ {SPR_SSWV,9,5,{A_Scream},S_SSWV_DIE3,0,0}, // S_SSWV_DIE2
+ {SPR_SSWV,10,5,{A_Fall},S_SSWV_DIE4,0,0}, // S_SSWV_DIE3
+ {SPR_SSWV,11,5,{NULL},S_SSWV_DIE5,0,0}, // S_SSWV_DIE4
+ {SPR_SSWV,12,-1,{NULL},S_NULL,0,0}, // S_SSWV_DIE5
+ {SPR_SSWV,13,5,{NULL},S_SSWV_XDIE2,0,0}, // S_SSWV_XDIE1
+ {SPR_SSWV,14,5,{A_XScream},S_SSWV_XDIE3,0,0}, // S_SSWV_XDIE2
+ {SPR_SSWV,15,5,{A_Fall},S_SSWV_XDIE4,0,0}, // S_SSWV_XDIE3
+ {SPR_SSWV,16,5,{NULL},S_SSWV_XDIE5,0,0}, // S_SSWV_XDIE4
+ {SPR_SSWV,17,5,{NULL},S_SSWV_XDIE6,0,0}, // S_SSWV_XDIE5
+ {SPR_SSWV,18,5,{NULL},S_SSWV_XDIE7,0,0}, // S_SSWV_XDIE6
+ {SPR_SSWV,19,5,{NULL},S_SSWV_XDIE8,0,0}, // S_SSWV_XDIE7
+ {SPR_SSWV,20,5,{NULL},S_SSWV_XDIE9,0,0}, // S_SSWV_XDIE8
+ {SPR_SSWV,21,-1,{NULL},S_NULL,0,0}, // S_SSWV_XDIE9
+ {SPR_SSWV,12,5,{NULL},S_SSWV_RAISE2,0,0}, // S_SSWV_RAISE1
+ {SPR_SSWV,11,5,{NULL},S_SSWV_RAISE3,0,0}, // S_SSWV_RAISE2
+ {SPR_SSWV,10,5,{NULL},S_SSWV_RAISE4,0,0}, // S_SSWV_RAISE3
+ {SPR_SSWV,9,5,{NULL},S_SSWV_RAISE5,0,0}, // S_SSWV_RAISE4
+ {SPR_SSWV,8,5,{NULL},S_SSWV_RUN1,0,0}, // S_SSWV_RAISE5
+ {SPR_KEEN,0,-1,{NULL},S_KEENSTND,0,0}, // S_KEENSTND
+ {SPR_KEEN,0,6,{NULL},S_COMMKEEN2,0,0}, // S_COMMKEEN
+ {SPR_KEEN,1,6,{NULL},S_COMMKEEN3,0,0}, // S_COMMKEEN2
+ {SPR_KEEN,2,6,{A_Scream},S_COMMKEEN4,0,0}, // S_COMMKEEN3
+ {SPR_KEEN,3,6,{NULL},S_COMMKEEN5,0,0}, // S_COMMKEEN4
+ {SPR_KEEN,4,6,{NULL},S_COMMKEEN6,0,0}, // S_COMMKEEN5
+ {SPR_KEEN,5,6,{NULL},S_COMMKEEN7,0,0}, // S_COMMKEEN6
+ {SPR_KEEN,6,6,{NULL},S_COMMKEEN8,0,0}, // S_COMMKEEN7
+ {SPR_KEEN,7,6,{NULL},S_COMMKEEN9,0,0}, // S_COMMKEEN8
+ {SPR_KEEN,8,6,{NULL},S_COMMKEEN10,0,0}, // S_COMMKEEN9
+ {SPR_KEEN,9,6,{NULL},S_COMMKEEN11,0,0}, // S_COMMKEEN10
+ {SPR_KEEN,10,6,{A_KeenDie},S_COMMKEEN12,0,0},// S_COMMKEEN11
+ {SPR_KEEN,11,-1,{NULL},S_NULL,0,0}, // S_COMMKEEN12
+ {SPR_KEEN,12,4,{NULL},S_KEENPAIN2,0,0}, // S_KEENPAIN
+ {SPR_KEEN,12,8,{A_Pain},S_KEENSTND,0,0}, // S_KEENPAIN2
+ {SPR_BBRN,0,-1,{NULL},S_NULL,0,0}, // S_BRAIN
+ {SPR_BBRN,1,36,{A_BrainPain},S_BRAIN,0,0}, // S_BRAIN_PAIN
+ {SPR_BBRN,0,100,{A_BrainScream},S_BRAIN_DIE2,0,0}, // S_BRAIN_DIE1
+ {SPR_BBRN,0,10,{NULL},S_BRAIN_DIE3,0,0}, // S_BRAIN_DIE2
+ {SPR_BBRN,0,10,{NULL},S_BRAIN_DIE4,0,0}, // S_BRAIN_DIE3
+ {SPR_BBRN,0,-1,{A_BrainDie},S_NULL,0,0}, // S_BRAIN_DIE4
+ {SPR_SSWV,0,10,{A_Look},S_BRAINEYE,0,0}, // S_BRAINEYE
+ {SPR_SSWV,0,181,{A_BrainAwake},S_BRAINEYE1,0,0}, // S_BRAINEYESEE
+ {SPR_SSWV,0,150,{A_BrainSpit},S_BRAINEYE1,0,0}, // S_BRAINEYE1
+ {SPR_BOSF,32768,3,{A_SpawnSound},S_SPAWN2,0,0}, // S_SPAWN1
+ {SPR_BOSF,32769,3,{A_SpawnFly},S_SPAWN3,0,0}, // S_SPAWN2
+ {SPR_BOSF,32770,3,{A_SpawnFly},S_SPAWN4,0,0}, // S_SPAWN3
+ {SPR_BOSF,32771,3,{A_SpawnFly},S_SPAWN1,0,0}, // S_SPAWN4
+ {SPR_FIRE,32768,4,{A_Fire},S_SPAWNFIRE2,0,0}, // S_SPAWNFIRE1
+ {SPR_FIRE,32769,4,{A_Fire},S_SPAWNFIRE3,0,0}, // S_SPAWNFIRE2
+ {SPR_FIRE,32770,4,{A_Fire},S_SPAWNFIRE4,0,0}, // S_SPAWNFIRE3
+ {SPR_FIRE,32771,4,{A_Fire},S_SPAWNFIRE5,0,0}, // S_SPAWNFIRE4
+ {SPR_FIRE,32772,4,{A_Fire},S_SPAWNFIRE6,0,0}, // S_SPAWNFIRE5
+ {SPR_FIRE,32773,4,{A_Fire},S_SPAWNFIRE7,0,0}, // S_SPAWNFIRE6
+ {SPR_FIRE,32774,4,{A_Fire},S_SPAWNFIRE8,0,0}, // S_SPAWNFIRE7
+ {SPR_FIRE,32775,4,{A_Fire},S_NULL,0,0}, // S_SPAWNFIRE8
+ {SPR_MISL,32769,10,{NULL},S_BRAINEXPLODE2,0,0}, // S_BRAINEXPLODE1
+ {SPR_MISL,32770,10,{NULL},S_BRAINEXPLODE3,0,0}, // S_BRAINEXPLODE2
+ {SPR_MISL,32771,10,{A_BrainExplode},S_NULL,0,0}, // S_BRAINEXPLODE3
+ {SPR_ARM1,0,6,{NULL},S_ARM1A,0,0}, // S_ARM1
+ {SPR_ARM1,32769,7,{NULL},S_ARM1,0,0}, // S_ARM1A
+ {SPR_ARM2,0,6,{NULL},S_ARM2A,0,0}, // S_ARM2
+ {SPR_ARM2,32769,6,{NULL},S_ARM2,0,0}, // S_ARM2A
+ {SPR_BAR1,0,6,{NULL},S_BAR2,0,0}, // S_BAR1
+ {SPR_BAR1,1,6,{NULL},S_BAR1,0,0}, // S_BAR2
+ {SPR_BEXP,32768,5,{NULL},S_BEXP2,0,0}, // S_BEXP
+ {SPR_BEXP,32769,5,{A_Scream},S_BEXP3,0,0}, // S_BEXP2
+ {SPR_BEXP,32770,5,{NULL},S_BEXP4,0,0}, // S_BEXP3
+ {SPR_BEXP,32771,10,{A_Explode},S_BEXP5,0,0}, // S_BEXP4
+ {SPR_BEXP,32772,10,{NULL},S_NULL,0,0}, // S_BEXP5
+ {SPR_FCAN,32768,4,{NULL},S_BBAR2,0,0}, // S_BBAR1
+ {SPR_FCAN,32769,4,{NULL},S_BBAR3,0,0}, // S_BBAR2
+ {SPR_FCAN,32770,4,{NULL},S_BBAR1,0,0}, // S_BBAR3
+ {SPR_BON1,0,6,{NULL},S_BON1A,0,0}, // S_BON1
+ {SPR_BON1,1,6,{NULL},S_BON1B,0,0}, // S_BON1A
+ {SPR_BON1,2,6,{NULL},S_BON1C,0,0}, // S_BON1B
+ {SPR_BON1,3,6,{NULL},S_BON1D,0,0}, // S_BON1C
+ {SPR_BON1,2,6,{NULL},S_BON1E,0,0}, // S_BON1D
+ {SPR_BON1,1,6,{NULL},S_BON1,0,0}, // S_BON1E
+ {SPR_BON2,0,6,{NULL},S_BON2A,0,0}, // S_BON2
+ {SPR_BON2,1,6,{NULL},S_BON2B,0,0}, // S_BON2A
+ {SPR_BON2,2,6,{NULL},S_BON2C,0,0}, // S_BON2B
+ {SPR_BON2,3,6,{NULL},S_BON2D,0,0}, // S_BON2C
+ {SPR_BON2,2,6,{NULL},S_BON2E,0,0}, // S_BON2D
+ {SPR_BON2,1,6,{NULL},S_BON2,0,0}, // S_BON2E
+ {SPR_BKEY,0,10,{NULL},S_BKEY2,0,0}, // S_BKEY
+ {SPR_BKEY,32769,10,{NULL},S_BKEY,0,0}, // S_BKEY2
+ {SPR_RKEY,0,10,{NULL},S_RKEY2,0,0}, // S_RKEY
+ {SPR_RKEY,32769,10,{NULL},S_RKEY,0,0}, // S_RKEY2
+ {SPR_YKEY,0,10,{NULL},S_YKEY2,0,0}, // S_YKEY
+ {SPR_YKEY,32769,10,{NULL},S_YKEY,0,0}, // S_YKEY2
+ {SPR_BSKU,0,10,{NULL},S_BSKULL2,0,0}, // S_BSKULL
+ {SPR_BSKU,32769,10,{NULL},S_BSKULL,0,0}, // S_BSKULL2
+ {SPR_RSKU,0,10,{NULL},S_RSKULL2,0,0}, // S_RSKULL
+ {SPR_RSKU,32769,10,{NULL},S_RSKULL,0,0}, // S_RSKULL2
+ {SPR_YSKU,0,10,{NULL},S_YSKULL2,0,0}, // S_YSKULL
+ {SPR_YSKU,32769,10,{NULL},S_YSKULL,0,0}, // S_YSKULL2
+ {SPR_STIM,0,-1,{NULL},S_NULL,0,0}, // S_STIM
+ {SPR_MEDI,0,-1,{NULL},S_NULL,0,0}, // S_MEDI
+ {SPR_SOUL,32768,6,{NULL},S_SOUL2,0,0}, // S_SOUL
+ {SPR_SOUL,32769,6,{NULL},S_SOUL3,0,0}, // S_SOUL2
+ {SPR_SOUL,32770,6,{NULL},S_SOUL4,0,0}, // S_SOUL3
+ {SPR_SOUL,32771,6,{NULL},S_SOUL5,0,0}, // S_SOUL4
+ {SPR_SOUL,32770,6,{NULL},S_SOUL6,0,0}, // S_SOUL5
+ {SPR_SOUL,32769,6,{NULL},S_SOUL,0,0}, // S_SOUL6
+ {SPR_PINV,32768,6,{NULL},S_PINV2,0,0}, // S_PINV
+ {SPR_PINV,32769,6,{NULL},S_PINV3,0,0}, // S_PINV2
+ {SPR_PINV,32770,6,{NULL},S_PINV4,0,0}, // S_PINV3
+ {SPR_PINV,32771,6,{NULL},S_PINV,0,0}, // S_PINV4
+ {SPR_PSTR,32768,-1,{NULL},S_NULL,0,0}, // S_PSTR
+ {SPR_PINS,32768,6,{NULL},S_PINS2,0,0}, // S_PINS
+ {SPR_PINS,32769,6,{NULL},S_PINS3,0,0}, // S_PINS2
+ {SPR_PINS,32770,6,{NULL},S_PINS4,0,0}, // S_PINS3
+ {SPR_PINS,32771,6,{NULL},S_PINS,0,0}, // S_PINS4
+ {SPR_MEGA,32768,6,{NULL},S_MEGA2,0,0}, // S_MEGA
+ {SPR_MEGA,32769,6,{NULL},S_MEGA3,0,0}, // S_MEGA2
+ {SPR_MEGA,32770,6,{NULL},S_MEGA4,0,0}, // S_MEGA3
+ {SPR_MEGA,32771,6,{NULL},S_MEGA,0,0}, // S_MEGA4
+ {SPR_SUIT,32768,-1,{NULL},S_NULL,0,0}, // S_SUIT
+ {SPR_PMAP,32768,6,{NULL},S_PMAP2,0,0}, // S_PMAP
+ {SPR_PMAP,32769,6,{NULL},S_PMAP3,0,0}, // S_PMAP2
+ {SPR_PMAP,32770,6,{NULL},S_PMAP4,0,0}, // S_PMAP3
+ {SPR_PMAP,32771,6,{NULL},S_PMAP5,0,0}, // S_PMAP4
+ {SPR_PMAP,32770,6,{NULL},S_PMAP6,0,0}, // S_PMAP5
+ {SPR_PMAP,32769,6,{NULL},S_PMAP,0,0}, // S_PMAP6
+ {SPR_PVIS,32768,6,{NULL},S_PVIS2,0,0}, // S_PVIS
+ {SPR_PVIS,1,6,{NULL},S_PVIS,0,0}, // S_PVIS2
+ {SPR_CLIP,0,-1,{NULL},S_NULL,0,0}, // S_CLIP
+ {SPR_AMMO,0,-1,{NULL},S_NULL,0,0}, // S_AMMO
+ {SPR_ROCK,0,-1,{NULL},S_NULL,0,0}, // S_ROCK
+ {SPR_BROK,0,-1,{NULL},S_NULL,0,0}, // S_BROK
+ {SPR_CELL,0,-1,{NULL},S_NULL,0,0}, // S_CELL
+ {SPR_CELP,0,-1,{NULL},S_NULL,0,0}, // S_CELP
+ {SPR_SHEL,0,-1,{NULL},S_NULL,0,0}, // S_SHEL
+ {SPR_SBOX,0,-1,{NULL},S_NULL,0,0}, // S_SBOX
+ {SPR_BPAK,0,-1,{NULL},S_NULL,0,0}, // S_BPAK
+ {SPR_BFUG,0,-1,{NULL},S_NULL,0,0}, // S_BFUG
+ {SPR_MGUN,0,-1,{NULL},S_NULL,0,0}, // S_MGUN
+ {SPR_CSAW,0,-1,{NULL},S_NULL,0,0}, // S_CSAW
+ {SPR_LAUN,0,-1,{NULL},S_NULL,0,0}, // S_LAUN
+ {SPR_PLAS,0,-1,{NULL},S_NULL,0,0}, // S_PLAS
+ {SPR_SHOT,0,-1,{NULL},S_NULL,0,0}, // S_SHOT
+ {SPR_SGN2,0,-1,{NULL},S_NULL,0,0}, // S_SHOT2
+ {SPR_COLU,32768,-1,{NULL},S_NULL,0,0}, // S_COLU
+ {SPR_SMT2,0,-1,{NULL},S_NULL,0,0}, // S_STALAG
+ {SPR_GOR1,0,10,{NULL},S_BLOODYTWITCH2,0,0}, // S_BLOODYTWITCH
+ {SPR_GOR1,1,15,{NULL},S_BLOODYTWITCH3,0,0}, // S_BLOODYTWITCH2
+ {SPR_GOR1,2,8,{NULL},S_BLOODYTWITCH4,0,0}, // S_BLOODYTWITCH3
+ {SPR_GOR1,1,6,{NULL},S_BLOODYTWITCH,0,0}, // S_BLOODYTWITCH4
+ {SPR_PLAY,13,-1,{NULL},S_NULL,0,0}, // S_DEADTORSO
+ {SPR_PLAY,18,-1,{NULL},S_NULL,0,0}, // S_DEADBOTTOM
+ {SPR_POL2,0,-1,{NULL},S_NULL,0,0}, // S_HEADSONSTICK
+ {SPR_POL5,0,-1,{NULL},S_NULL,0,0}, // S_GIBS
+ {SPR_POL4,0,-1,{NULL},S_NULL,0,0}, // S_HEADONASTICK
+ {SPR_POL3,32768,6,{NULL},S_HEADCANDLES2,0,0}, // S_HEADCANDLES
+ {SPR_POL3,32769,6,{NULL},S_HEADCANDLES,0,0}, // S_HEADCANDLES2
+ {SPR_POL1,0,-1,{NULL},S_NULL,0,0}, // S_DEADSTICK
+ {SPR_POL6,0,6,{NULL},S_LIVESTICK2,0,0}, // S_LIVESTICK
+ {SPR_POL6,1,8,{NULL},S_LIVESTICK,0,0}, // S_LIVESTICK2
+ {SPR_GOR2,0,-1,{NULL},S_NULL,0,0}, // S_MEAT2
+ {SPR_GOR3,0,-1,{NULL},S_NULL,0,0}, // S_MEAT3
+ {SPR_GOR4,0,-1,{NULL},S_NULL,0,0}, // S_MEAT4
+ {SPR_GOR5,0,-1,{NULL},S_NULL,0,0}, // S_MEAT5
+ {SPR_SMIT,0,-1,{NULL},S_NULL,0,0}, // S_STALAGTITE
+ {SPR_COL1,0,-1,{NULL},S_NULL,0,0}, // S_TALLGRNCOL
+ {SPR_COL2,0,-1,{NULL},S_NULL,0,0}, // S_SHRTGRNCOL
+ {SPR_COL3,0,-1,{NULL},S_NULL,0,0}, // S_TALLREDCOL
+ {SPR_COL4,0,-1,{NULL},S_NULL,0,0}, // S_SHRTREDCOL
+ {SPR_CAND,32768,-1,{NULL},S_NULL,0,0}, // S_CANDLESTIK
+ {SPR_CBRA,32768,-1,{NULL},S_NULL,0,0}, // S_CANDELABRA
+ {SPR_COL6,0,-1,{NULL},S_NULL,0,0}, // S_SKULLCOL
+ {SPR_TRE1,0,-1,{NULL},S_NULL,0,0}, // S_TORCHTREE
+ {SPR_TRE2,0,-1,{NULL},S_NULL,0,0}, // S_BIGTREE
+ {SPR_ELEC,0,-1,{NULL},S_NULL,0,0}, // S_TECHPILLAR
+ {SPR_CEYE,32768,6,{NULL},S_EVILEYE2,0,0}, // S_EVILEYE
+ {SPR_CEYE,32769,6,{NULL},S_EVILEYE3,0,0}, // S_EVILEYE2
+ {SPR_CEYE,32770,6,{NULL},S_EVILEYE4,0,0}, // S_EVILEYE3
+ {SPR_CEYE,32769,6,{NULL},S_EVILEYE,0,0}, // S_EVILEYE4
+ {SPR_FSKU,32768,6,{NULL},S_FLOATSKULL2,0,0}, // S_FLOATSKULL
+ {SPR_FSKU,32769,6,{NULL},S_FLOATSKULL3,0,0}, // S_FLOATSKULL2
+ {SPR_FSKU,32770,6,{NULL},S_FLOATSKULL,0,0}, // S_FLOATSKULL3
+ {SPR_COL5,0,14,{NULL},S_HEARTCOL2,0,0}, // S_HEARTCOL
+ {SPR_COL5,1,14,{NULL},S_HEARTCOL,0,0}, // S_HEARTCOL2
+ {SPR_TBLU,32768,4,{NULL},S_BLUETORCH2,0,0}, // S_BLUETORCH
+ {SPR_TBLU,32769,4,{NULL},S_BLUETORCH3,0,0}, // S_BLUETORCH2
+ {SPR_TBLU,32770,4,{NULL},S_BLUETORCH4,0,0}, // S_BLUETORCH3
+ {SPR_TBLU,32771,4,{NULL},S_BLUETORCH,0,0}, // S_BLUETORCH4
+ {SPR_TGRN,32768,4,{NULL},S_GREENTORCH2,0,0}, // S_GREENTORCH
+ {SPR_TGRN,32769,4,{NULL},S_GREENTORCH3,0,0}, // S_GREENTORCH2
+ {SPR_TGRN,32770,4,{NULL},S_GREENTORCH4,0,0}, // S_GREENTORCH3
+ {SPR_TGRN,32771,4,{NULL},S_GREENTORCH,0,0}, // S_GREENTORCH4
+ {SPR_TRED,32768,4,{NULL},S_REDTORCH2,0,0}, // S_REDTORCH
+ {SPR_TRED,32769,4,{NULL},S_REDTORCH3,0,0}, // S_REDTORCH2
+ {SPR_TRED,32770,4,{NULL},S_REDTORCH4,0,0}, // S_REDTORCH3
+ {SPR_TRED,32771,4,{NULL},S_REDTORCH,0,0}, // S_REDTORCH4
+ {SPR_SMBT,32768,4,{NULL},S_BTORCHSHRT2,0,0}, // S_BTORCHSHRT
+ {SPR_SMBT,32769,4,{NULL},S_BTORCHSHRT3,0,0}, // S_BTORCHSHRT2
+ {SPR_SMBT,32770,4,{NULL},S_BTORCHSHRT4,0,0}, // S_BTORCHSHRT3
+ {SPR_SMBT,32771,4,{NULL},S_BTORCHSHRT,0,0}, // S_BTORCHSHRT4
+ {SPR_SMGT,32768,4,{NULL},S_GTORCHSHRT2,0,0}, // S_GTORCHSHRT
+ {SPR_SMGT,32769,4,{NULL},S_GTORCHSHRT3,0,0}, // S_GTORCHSHRT2
+ {SPR_SMGT,32770,4,{NULL},S_GTORCHSHRT4,0,0}, // S_GTORCHSHRT3
+ {SPR_SMGT,32771,4,{NULL},S_GTORCHSHRT,0,0}, // S_GTORCHSHRT4
+ {SPR_SMRT,32768,4,{NULL},S_RTORCHSHRT2,0,0}, // S_RTORCHSHRT
+ {SPR_SMRT,32769,4,{NULL},S_RTORCHSHRT3,0,0}, // S_RTORCHSHRT2
+ {SPR_SMRT,32770,4,{NULL},S_RTORCHSHRT4,0,0}, // S_RTORCHSHRT3
+ {SPR_SMRT,32771,4,{NULL},S_RTORCHSHRT,0,0}, // S_RTORCHSHRT4
+ {SPR_HDB1,0,-1,{NULL},S_NULL,0,0}, // S_HANGNOGUTS
+ {SPR_HDB2,0,-1,{NULL},S_NULL,0,0}, // S_HANGBNOBRAIN
+ {SPR_HDB3,0,-1,{NULL},S_NULL,0,0}, // S_HANGTLOOKDN
+ {SPR_HDB4,0,-1,{NULL},S_NULL,0,0}, // S_HANGTSKULL
+ {SPR_HDB5,0,-1,{NULL},S_NULL,0,0}, // S_HANGTLOOKUP
+ {SPR_HDB6,0,-1,{NULL},S_NULL,0,0}, // S_HANGTNOBRAIN
+ {SPR_POB1,0,-1,{NULL},S_NULL,0,0}, // S_COLONGIBS
+ {SPR_POB2,0,-1,{NULL},S_NULL,0,0}, // S_SMALLPOOL
+ {SPR_BRS1,0,-1,{NULL},S_NULL,0,0}, // S_BRAINSTEM
+ {SPR_TLMP,32768,4,{NULL},S_TECHLAMP2,0,0}, // S_TECHLAMP
+ {SPR_TLMP,32769,4,{NULL},S_TECHLAMP3,0,0}, // S_TECHLAMP2
+ {SPR_TLMP,32770,4,{NULL},S_TECHLAMP4,0,0}, // S_TECHLAMP3
+ {SPR_TLMP,32771,4,{NULL},S_TECHLAMP,0,0}, // S_TECHLAMP4
+ {SPR_TLP2,32768,4,{NULL},S_TECH2LAMP2,0,0}, // S_TECH2LAMP
+ {SPR_TLP2,32769,4,{NULL},S_TECH2LAMP3,0,0}, // S_TECH2LAMP2
+ {SPR_TLP2,32770,4,{NULL},S_TECH2LAMP4,0,0}, // S_TECH2LAMP3
+ {SPR_TLP2,32771,4,{NULL},S_TECH2LAMP,0,0} // S_TECH2LAMP4
+};
+
+
+mobjinfo_t mobjinfo[NUMMOBJTYPES] = {
+
+ { // MT_PLAYER
+ -1, // doomednum
+ S_PLAY, // spawnstate
+ 100, // spawnhealth
+ S_PLAY_RUN1, // seestate
+ sfx_None, // seesound
+ 0, // reactiontime
+ sfx_None, // attacksound
+ S_PLAY_PAIN, // painstate
+ 255, // painchance
+ sfx_plpain, // painsound
+ S_NULL, // meleestate
+ S_PLAY_ATK1, // missilestate
+ S_PLAY_DIE1, // deathstate
+ S_PLAY_XDIE1, // xdeathstate
+ sfx_pldeth, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 56*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_DROPOFF|MF_PICKUP|MF_NOTDMATCH, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_POSSESSED
+ 3004, // doomednum
+ S_POSS_STND, // spawnstate
+ 20, // spawnhealth
+ S_POSS_RUN1, // seestate
+ sfx_posit1, // seesound
+ 8, // reactiontime
+ sfx_pistol, // attacksound
+ S_POSS_PAIN, // painstate
+ 200, // painchance
+ sfx_popain, // painsound
+ 0, // meleestate
+ S_POSS_ATK1, // missilestate
+ S_POSS_DIE1, // deathstate
+ S_POSS_XDIE1, // xdeathstate
+ sfx_podth1, // deathsound
+ 8, // speed
+ 20*FRACUNIT, // radius
+ 56*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_posact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_POSS_RAISE1 // raisestate
+ },
+
+ { // MT_SHOTGUY
+ 9, // doomednum
+ S_SPOS_STND, // spawnstate
+ 30, // spawnhealth
+ S_SPOS_RUN1, // seestate
+ sfx_posit2, // seesound
+ 8, // reactiontime
+ 0, // attacksound
+ S_SPOS_PAIN, // painstate
+ 170, // painchance
+ sfx_popain, // painsound
+ 0, // meleestate
+ S_SPOS_ATK1, // missilestate
+ S_SPOS_DIE1, // deathstate
+ S_SPOS_XDIE1, // xdeathstate
+ sfx_podth2, // deathsound
+ 8, // speed
+ 20*FRACUNIT, // radius
+ 56*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_posact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_SPOS_RAISE1 // raisestate
+ },
+
+ { // MT_VILE
+ 64, // doomednum
+ S_VILE_STND, // spawnstate
+ 700, // spawnhealth
+ S_VILE_RUN1, // seestate
+ sfx_vilsit, // seesound
+ 8, // reactiontime
+ 0, // attacksound
+ S_VILE_PAIN, // painstate
+ 10, // painchance
+ sfx_vipain, // painsound
+ 0, // meleestate
+ S_VILE_ATK1, // missilestate
+ S_VILE_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_vildth, // deathsound
+ 15, // speed
+ 20*FRACUNIT, // radius
+ 56*FRACUNIT, // height
+ 500, // mass
+ 0, // damage
+ sfx_vilact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_FIRE
+ -1, // doomednum
+ S_FIRE1, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_UNDEAD
+ 66, // doomednum
+ S_SKEL_STND, // spawnstate
+ 300, // spawnhealth
+ S_SKEL_RUN1, // seestate
+ sfx_skesit, // seesound
+ 8, // reactiontime
+ 0, // attacksound
+ S_SKEL_PAIN, // painstate
+ 100, // painchance
+ sfx_popain, // painsound
+ S_SKEL_FIST1, // meleestate
+ S_SKEL_MISS1, // missilestate
+ S_SKEL_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_skedth, // deathsound
+ 10, // speed
+ 20*FRACUNIT, // radius
+ 56*FRACUNIT, // height
+ 500, // mass
+ 0, // damage
+ sfx_skeact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_SKEL_RAISE1 // raisestate
+ },
+
+ { // MT_TRACER
+ -1, // doomednum
+ S_TRACER, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_skeatk, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_TRACEEXP1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_barexp, // deathsound
+ 10*FRACUNIT, // speed
+ 11*FRACUNIT, // radius
+ 8*FRACUNIT, // height
+ 100, // mass
+ 10, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_SMOKE
+ -1, // doomednum
+ S_SMOKE1, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_FATSO
+ 67, // doomednum
+ S_FATT_STND, // spawnstate
+ 600, // spawnhealth
+ S_FATT_RUN1, // seestate
+ sfx_mansit, // seesound
+ 8, // reactiontime
+ 0, // attacksound
+ S_FATT_PAIN, // painstate
+ 80, // painchance
+ sfx_mnpain, // painsound
+ 0, // meleestate
+ S_FATT_ATK1, // missilestate
+ S_FATT_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_mandth, // deathsound
+ 8, // speed
+ 48*FRACUNIT, // radius
+ 64*FRACUNIT, // height
+ 1000, // mass
+ 0, // damage
+ sfx_posact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_FATT_RAISE1 // raisestate
+ },
+
+ { // MT_FATSHOT
+ -1, // doomednum
+ S_FATSHOT1, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_firsht, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_FATSHOTX1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_firxpl, // deathsound
+ 20*FRACUNIT, // speed
+ 6*FRACUNIT, // radius
+ 8*FRACUNIT, // height
+ 100, // mass
+ 8, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_CHAINGUY
+ 65, // doomednum
+ S_CPOS_STND, // spawnstate
+ 70, // spawnhealth
+ S_CPOS_RUN1, // seestate
+ sfx_posit2, // seesound
+ 8, // reactiontime
+ 0, // attacksound
+ S_CPOS_PAIN, // painstate
+ 170, // painchance
+ sfx_popain, // painsound
+ 0, // meleestate
+ S_CPOS_ATK1, // missilestate
+ S_CPOS_DIE1, // deathstate
+ S_CPOS_XDIE1, // xdeathstate
+ sfx_podth2, // deathsound
+ 8, // speed
+ 20*FRACUNIT, // radius
+ 56*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_posact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_CPOS_RAISE1 // raisestate
+ },
+
+ { // MT_TROOP
+ 3001, // doomednum
+ S_TROO_STND, // spawnstate
+ 60, // spawnhealth
+ S_TROO_RUN1, // seestate
+ sfx_bgsit1, // seesound
+ 8, // reactiontime
+ 0, // attacksound
+ S_TROO_PAIN, // painstate
+ 200, // painchance
+ sfx_popain, // painsound
+ S_TROO_ATK1, // meleestate
+ S_TROO_ATK1, // missilestate
+ S_TROO_DIE1, // deathstate
+ S_TROO_XDIE1, // xdeathstate
+ sfx_bgdth1, // deathsound
+ 8, // speed
+ 20*FRACUNIT, // radius
+ 56*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_bgact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_TROO_RAISE1 // raisestate
+ },
+
+ { // MT_SERGEANT
+ 3002, // doomednum
+ S_SARG_STND, // spawnstate
+ 150, // spawnhealth
+ S_SARG_RUN1, // seestate
+ sfx_sgtsit, // seesound
+ 8, // reactiontime
+ sfx_sgtatk, // attacksound
+ S_SARG_PAIN, // painstate
+ 180, // painchance
+ sfx_dmpain, // painsound
+ S_SARG_ATK1, // meleestate
+ 0, // missilestate
+ S_SARG_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_sgtdth, // deathsound
+ 10, // speed
+ 30*FRACUNIT, // radius
+ 56*FRACUNIT, // height
+ 400, // mass
+ 0, // damage
+ sfx_dmact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_SARG_RAISE1 // raisestate
+ },
+
+ { // MT_SHADOWS
+ 58, // doomednum
+ S_SARG_STND, // spawnstate
+ 150, // spawnhealth
+ S_SARG_RUN1, // seestate
+ sfx_sgtsit, // seesound
+ 8, // reactiontime
+ sfx_sgtatk, // attacksound
+ S_SARG_PAIN, // painstate
+ 180, // painchance
+ sfx_dmpain, // painsound
+ S_SARG_ATK1, // meleestate
+ 0, // missilestate
+ S_SARG_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_sgtdth, // deathsound
+ 10, // speed
+ 30*FRACUNIT, // radius
+ 56*FRACUNIT, // height
+ 400, // mass
+ 0, // damage
+ sfx_dmact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_SHADOW|MF_COUNTKILL, // flags
+ S_SARG_RAISE1 // raisestate
+ },
+
+ { // MT_HEAD
+ 3005, // doomednum
+ S_HEAD_STND, // spawnstate
+ 400, // spawnhealth
+ S_HEAD_RUN1, // seestate
+ sfx_cacsit, // seesound
+ 8, // reactiontime
+ 0, // attacksound
+ S_HEAD_PAIN, // painstate
+ 128, // painchance
+ sfx_dmpain, // painsound
+ 0, // meleestate
+ S_HEAD_ATK1, // missilestate
+ S_HEAD_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_cacdth, // deathsound
+ 8, // speed
+ 31*FRACUNIT, // radius
+ 56*FRACUNIT, // height
+ 400, // mass
+ 0, // damage
+ sfx_dmact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_FLOAT|MF_NOGRAVITY|MF_COUNTKILL, // flags
+ S_HEAD_RAISE1 // raisestate
+ },
+
+ { // MT_BRUISER
+ 3003, // doomednum
+ S_BOSS_STND, // spawnstate
+ 1000, // spawnhealth
+ S_BOSS_RUN1, // seestate
+ sfx_brssit, // seesound
+ 8, // reactiontime
+ 0, // attacksound
+ S_BOSS_PAIN, // painstate
+ 50, // painchance
+ sfx_dmpain, // painsound
+ S_BOSS_ATK1, // meleestate
+ S_BOSS_ATK1, // missilestate
+ S_BOSS_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_brsdth, // deathsound
+ 8, // speed
+ 24*FRACUNIT, // radius
+ 64*FRACUNIT, // height
+ 1000, // mass
+ 0, // damage
+ sfx_dmact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_BOSS_RAISE1 // raisestate
+ },
+
+ { // MT_BRUISERSHOT
+ -1, // doomednum
+ S_BRBALL1, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_firsht, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_BRBALLX1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_firxpl, // deathsound
+ 15*FRACUNIT, // speed
+ 6*FRACUNIT, // radius
+ 8*FRACUNIT, // height
+ 100, // mass
+ 8, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_KNIGHT
+ 69, // doomednum
+ S_BOS2_STND, // spawnstate
+ 500, // spawnhealth
+ S_BOS2_RUN1, // seestate
+ sfx_kntsit, // seesound
+ 8, // reactiontime
+ 0, // attacksound
+ S_BOS2_PAIN, // painstate
+ 50, // painchance
+ sfx_dmpain, // painsound
+ S_BOS2_ATK1, // meleestate
+ S_BOS2_ATK1, // missilestate
+ S_BOS2_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_kntdth, // deathsound
+ 8, // speed
+ 24*FRACUNIT, // radius
+ 64*FRACUNIT, // height
+ 1000, // mass
+ 0, // damage
+ sfx_dmact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_BOS2_RAISE1 // raisestate
+ },
+
+ { // MT_SKULL
+ 3006, // doomednum
+ S_SKULL_STND, // spawnstate
+ 100, // spawnhealth
+ S_SKULL_RUN1, // seestate
+ 0, // seesound
+ 8, // reactiontime
+ sfx_sklatk, // attacksound
+ S_SKULL_PAIN, // painstate
+ 256, // painchance
+ sfx_dmpain, // painsound
+ 0, // meleestate
+ S_SKULL_ATK1, // missilestate
+ S_SKULL_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_firxpl, // deathsound
+ 8, // speed
+ 16*FRACUNIT, // radius
+ 56*FRACUNIT, // height
+ 50, // mass
+ 3, // damage
+ sfx_dmact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_FLOAT|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_SPIDER
+ 7, // doomednum
+ S_SPID_STND, // spawnstate
+ 3000, // spawnhealth
+ S_SPID_RUN1, // seestate
+ sfx_spisit, // seesound
+ 8, // reactiontime
+ sfx_shotgn, // attacksound
+ S_SPID_PAIN, // painstate
+ 40, // painchance
+ sfx_dmpain, // painsound
+ 0, // meleestate
+ S_SPID_ATK1, // missilestate
+ S_SPID_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_spidth, // deathsound
+ 12, // speed
+ 128*FRACUNIT, // radius
+ 100*FRACUNIT, // height
+ 1000, // mass
+ 0, // damage
+ sfx_dmact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_BABY
+ 68, // doomednum
+ S_BSPI_STND, // spawnstate
+ 500, // spawnhealth
+ S_BSPI_SIGHT, // seestate
+ sfx_bspsit, // seesound
+ 8, // reactiontime
+ 0, // attacksound
+ S_BSPI_PAIN, // painstate
+ 128, // painchance
+ sfx_dmpain, // painsound
+ 0, // meleestate
+ S_BSPI_ATK1, // missilestate
+ S_BSPI_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_bspdth, // deathsound
+ 12, // speed
+ 64*FRACUNIT, // radius
+ 64*FRACUNIT, // height
+ 600, // mass
+ 0, // damage
+ sfx_bspact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_BSPI_RAISE1 // raisestate
+ },
+
+ { // MT_CYBORG
+ 16, // doomednum
+ S_CYBER_STND, // spawnstate
+ 4000, // spawnhealth
+ S_CYBER_RUN1, // seestate
+ sfx_cybsit, // seesound
+ 8, // reactiontime
+ 0, // attacksound
+ S_CYBER_PAIN, // painstate
+ 20, // painchance
+ sfx_dmpain, // painsound
+ 0, // meleestate
+ S_CYBER_ATK1, // missilestate
+ S_CYBER_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_cybdth, // deathsound
+ 16, // speed
+ 40*FRACUNIT, // radius
+ 110*FRACUNIT, // height
+ 1000, // mass
+ 0, // damage
+ sfx_dmact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_PAIN
+ 71, // doomednum
+ S_PAIN_STND, // spawnstate
+ 400, // spawnhealth
+ S_PAIN_RUN1, // seestate
+ sfx_pesit, // seesound
+ 8, // reactiontime
+ 0, // attacksound
+ S_PAIN_PAIN, // painstate
+ 128, // painchance
+ sfx_pepain, // painsound
+ 0, // meleestate
+ S_PAIN_ATK1, // missilestate
+ S_PAIN_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_pedth, // deathsound
+ 8, // speed
+ 31*FRACUNIT, // radius
+ 56*FRACUNIT, // height
+ 400, // mass
+ 0, // damage
+ sfx_dmact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_FLOAT|MF_NOGRAVITY|MF_COUNTKILL, // flags
+ S_PAIN_RAISE1 // raisestate
+ },
+
+ { // MT_WOLFSS
+ 84, // doomednum
+ S_SSWV_STND, // spawnstate
+ 50, // spawnhealth
+ S_SSWV_RUN1, // seestate
+ sfx_sssit, // seesound
+ 8, // reactiontime
+ 0, // attacksound
+ S_SSWV_PAIN, // painstate
+ 170, // painchance
+ sfx_popain, // painsound
+ 0, // meleestate
+ S_SSWV_ATK1, // missilestate
+ S_SSWV_DIE1, // deathstate
+ S_SSWV_XDIE1, // xdeathstate
+ sfx_ssdth, // deathsound
+ 8, // speed
+ 20*FRACUNIT, // radius
+ 56*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_posact, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_SSWV_RAISE1 // raisestate
+ },
+
+ { // MT_KEEN
+ 72, // doomednum
+ S_KEENSTND, // spawnstate
+ 100, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_KEENPAIN, // painstate
+ 256, // painchance
+ sfx_keenpn, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_COMMKEEN, // deathstate
+ S_NULL, // xdeathstate
+ sfx_keendt, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 72*FRACUNIT, // height
+ 10000000, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY|MF_SHOOTABLE|MF_COUNTKILL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_BOSSBRAIN
+ 88, // doomednum
+ S_BRAIN, // spawnstate
+ 250, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_BRAIN_PAIN, // painstate
+ 255, // painchance
+ sfx_bospn, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_BRAIN_DIE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_bosdth, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 10000000, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SHOOTABLE, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_BOSSSPIT
+ 89, // doomednum
+ S_BRAINEYE, // spawnstate
+ 1000, // spawnhealth
+ S_BRAINEYESEE, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 32*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_NOSECTOR, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_BOSSTARGET
+ 87, // doomednum
+ S_NULL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 32*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_NOSECTOR, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_SPAWNSHOT
+ -1, // doomednum
+ S_SPAWN1, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_bospit, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_firxpl, // deathsound
+ 10*FRACUNIT, // speed
+ 6*FRACUNIT, // radius
+ 32*FRACUNIT, // height
+ 100, // mass
+ 3, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY|MF_NOCLIP, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_SPAWNFIRE
+ -1, // doomednum
+ S_SPAWNFIRE1, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_BARREL
+ 2035, // doomednum
+ S_BAR1, // spawnstate
+ 20, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_BEXP, // deathstate
+ S_NULL, // xdeathstate
+ sfx_barexp, // deathsound
+ 0, // speed
+ 10*FRACUNIT, // radius
+ 42*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SHOOTABLE|MF_NOBLOOD, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_TROOPSHOT
+ -1, // doomednum
+ S_TBALL1, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_firsht, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_TBALLX1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_firxpl, // deathsound
+ 10*FRACUNIT, // speed
+ 6*FRACUNIT, // radius
+ 8*FRACUNIT, // height
+ 100, // mass
+ 3, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_HEADSHOT
+ -1, // doomednum
+ S_RBALL1, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_firsht, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_RBALLX1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_firxpl, // deathsound
+ 10*FRACUNIT, // speed
+ 6*FRACUNIT, // radius
+ 8*FRACUNIT, // height
+ 100, // mass
+ 5, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_ROCKET
+ -1, // doomednum
+ S_ROCKET, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_rlaunc, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_EXPLODE1, // deathstate
+ S_NULL, // xdeathstate
+ sfx_barexp, // deathsound
+ 20*FRACUNIT, // speed
+ 11*FRACUNIT, // radius
+ 8*FRACUNIT, // height
+ 100, // mass
+ 20, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_PLASMA
+ -1, // doomednum
+ S_PLASBALL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_plasma, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_PLASEXP, // deathstate
+ S_NULL, // xdeathstate
+ sfx_firxpl, // deathsound
+ 25*FRACUNIT, // speed
+ 13*FRACUNIT, // radius
+ 8*FRACUNIT, // height
+ 100, // mass
+ 5, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_BFG
+ -1, // doomednum
+ S_BFGSHOT, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ 0, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_BFGLAND, // deathstate
+ S_NULL, // xdeathstate
+ sfx_rxplod, // deathsound
+ 25*FRACUNIT, // speed
+ 13*FRACUNIT, // radius
+ 8*FRACUNIT, // height
+ 100, // mass
+ 100, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_ARACHPLAZ
+ -1, // doomednum
+ S_ARACH_PLAZ, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_plasma, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_ARACH_PLEX, // deathstate
+ S_NULL, // xdeathstate
+ sfx_firxpl, // deathsound
+ 25*FRACUNIT, // speed
+ 13*FRACUNIT, // radius
+ 8*FRACUNIT, // height
+ 100, // mass
+ 5, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_MISSILE|MF_DROPOFF|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_PUFF
+ -1, // doomednum
+ S_PUFF1, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_BLOOD
+ -1, // doomednum
+ S_BLOOD1, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_TFOG
+ -1, // doomednum
+ S_TFOG, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_IFOG
+ -1, // doomednum
+ S_IFOG, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_TELEPORTMAN
+ 14, // doomednum
+ S_NULL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_NOSECTOR, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_EXTRABFG
+ -1, // doomednum
+ S_BFGEXP, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC0
+ 2018, // doomednum
+ S_ARM1, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC1
+ 2019, // doomednum
+ S_ARM2, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC2
+ 2014, // doomednum
+ S_BON1, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_COUNTITEM, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC3
+ 2015, // doomednum
+ S_BON2, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_COUNTITEM, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC4
+ 5, // doomednum
+ S_BKEY, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_NOTDMATCH, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC5
+ 13, // doomednum
+ S_RKEY, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_NOTDMATCH, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC6
+ 6, // doomednum
+ S_YKEY, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_NOTDMATCH, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC7
+ 39, // doomednum
+ S_YSKULL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_NOTDMATCH, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC8
+ 38, // doomednum
+ S_RSKULL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_NOTDMATCH, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC9
+ 40, // doomednum
+ S_BSKULL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_NOTDMATCH, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC10
+ 2011, // doomednum
+ S_STIM, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC11
+ 2012, // doomednum
+ S_MEDI, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC12
+ 2013, // doomednum
+ S_SOUL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_COUNTITEM, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_INV
+ 2022, // doomednum
+ S_PINV, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_COUNTITEM, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC13
+ 2023, // doomednum
+ S_PSTR, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_COUNTITEM, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_INS
+ 2024, // doomednum
+ S_PINS, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_COUNTITEM, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC14
+ 2025, // doomednum
+ S_SUIT, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC15
+ 2026, // doomednum
+ S_PMAP, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_COUNTITEM, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC16
+ 2045, // doomednum
+ S_PVIS, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_COUNTITEM, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MEGA
+ 83, // doomednum
+ S_MEGA, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL|MF_COUNTITEM, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_CLIP
+ 2007, // doomednum
+ S_CLIP, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC17
+ 2048, // doomednum
+ S_AMMO, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC18
+ 2010, // doomednum
+ S_ROCK, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC19
+ 2046, // doomednum
+ S_BROK, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC20
+ 2047, // doomednum
+ S_CELL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC21
+ 17, // doomednum
+ S_CELP, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC22
+ 2008, // doomednum
+ S_SHEL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC23
+ 2049, // doomednum
+ S_SBOX, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC24
+ 8, // doomednum
+ S_BPAK, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC25
+ 2006, // doomednum
+ S_BFUG, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_CHAINGUN
+ 2002, // doomednum
+ S_MGUN, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC26
+ 2005, // doomednum
+ S_CSAW, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC27
+ 2003, // doomednum
+ S_LAUN, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC28
+ 2004, // doomednum
+ S_PLAS, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_SHOTGUN
+ 2001, // doomednum
+ S_SHOT, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_SUPERSHOTGUN
+ 82, // doomednum
+ S_SHOT2, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPECIAL, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC29
+ 85, // doomednum
+ S_TECHLAMP, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC30
+ 86, // doomednum
+ S_TECH2LAMP, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC31
+ 2028, // doomednum
+ S_COLU, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC32
+ 30, // doomednum
+ S_TALLGRNCOL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC33
+ 31, // doomednum
+ S_SHRTGRNCOL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC34
+ 32, // doomednum
+ S_TALLREDCOL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC35
+ 33, // doomednum
+ S_SHRTREDCOL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC36
+ 37, // doomednum
+ S_SKULLCOL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC37
+ 36, // doomednum
+ S_HEARTCOL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC38
+ 41, // doomednum
+ S_EVILEYE, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC39
+ 42, // doomednum
+ S_FLOATSKULL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC40
+ 43, // doomednum
+ S_TORCHTREE, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC41
+ 44, // doomednum
+ S_BLUETORCH, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC42
+ 45, // doomednum
+ S_GREENTORCH, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC43
+ 46, // doomednum
+ S_REDTORCH, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC44
+ 55, // doomednum
+ S_BTORCHSHRT, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC45
+ 56, // doomednum
+ S_GTORCHSHRT, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC46
+ 57, // doomednum
+ S_RTORCHSHRT, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC47
+ 47, // doomednum
+ S_STALAGTITE, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC48
+ 48, // doomednum
+ S_TECHPILLAR, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC49
+ 34, // doomednum
+ S_CANDLESTIK, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ 0, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC50
+ 35, // doomednum
+ S_CANDELABRA, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC51
+ 49, // doomednum
+ S_BLOODYTWITCH, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 68*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC52
+ 50, // doomednum
+ S_MEAT2, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 84*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC53
+ 51, // doomednum
+ S_MEAT3, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 84*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC54
+ 52, // doomednum
+ S_MEAT4, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 68*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC55
+ 53, // doomednum
+ S_MEAT5, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 52*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC56
+ 59, // doomednum
+ S_MEAT2, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 84*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC57
+ 60, // doomednum
+ S_MEAT4, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 68*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC58
+ 61, // doomednum
+ S_MEAT3, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 52*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC59
+ 62, // doomednum
+ S_MEAT5, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 52*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC60
+ 63, // doomednum
+ S_BLOODYTWITCH, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 68*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC61
+ 22, // doomednum
+ S_HEAD_DIE6, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ 0, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC62
+ 15, // doomednum
+ S_PLAY_DIE7, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ 0, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC63
+ 18, // doomednum
+ S_POSS_DIE5, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ 0, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC64
+ 21, // doomednum
+ S_SARG_DIE6, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ 0, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC65
+ 23, // doomednum
+ S_SKULL_DIE6, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ 0, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC66
+ 20, // doomednum
+ S_TROO_DIE5, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ 0, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC67
+ 19, // doomednum
+ S_SPOS_DIE5, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ 0, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC68
+ 10, // doomednum
+ S_PLAY_XDIE9, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ 0, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC69
+ 12, // doomednum
+ S_PLAY_XDIE9, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ 0, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC70
+ 28, // doomednum
+ S_HEADSONSTICK, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC71
+ 24, // doomednum
+ S_GIBS, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ 0, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC72
+ 27, // doomednum
+ S_HEADONASTICK, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC73
+ 29, // doomednum
+ S_HEADCANDLES, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC74
+ 25, // doomednum
+ S_DEADSTICK, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC75
+ 26, // doomednum
+ S_LIVESTICK, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC76
+ 54, // doomednum
+ S_BIGTREE, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 32*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC77
+ 70, // doomednum
+ S_BBAR1, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC78
+ 73, // doomednum
+ S_HANGNOGUTS, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 88*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC79
+ 74, // doomednum
+ S_HANGBNOBRAIN, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 88*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC80
+ 75, // doomednum
+ S_HANGTLOOKDN, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 64*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC81
+ 76, // doomednum
+ S_HANGTSKULL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 64*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC82
+ 77, // doomednum
+ S_HANGTLOOKUP, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 64*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC83
+ 78, // doomednum
+ S_HANGTNOBRAIN, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 16*FRACUNIT, // radius
+ 64*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC84
+ 79, // doomednum
+ S_COLONGIBS, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC85
+ 80, // doomednum
+ S_SMALLPOOL, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP, // flags
+ S_NULL // raisestate
+ },
+
+ { // MT_MISC86
+ 81, // doomednum
+ S_BRAINSTEM, // spawnstate
+ 1000, // spawnhealth
+ S_NULL, // seestate
+ sfx_None, // seesound
+ 8, // reactiontime
+ sfx_None, // attacksound
+ S_NULL, // painstate
+ 0, // painchance
+ sfx_None, // painsound
+ S_NULL, // meleestate
+ S_NULL, // missilestate
+ S_NULL, // deathstate
+ S_NULL, // xdeathstate
+ sfx_None, // deathsound
+ 0, // speed
+ 20*FRACUNIT, // radius
+ 16*FRACUNIT, // height
+ 100, // mass
+ 0, // damage
+ sfx_None, // activesound
+ MF_NOBLOCKMAP, // flags
+ S_NULL // raisestate
+ }
+};
+
diff --git a/firmware_p4/components/Applications/doom/info.h b/firmware_p4/components/Applications/doom/info.h
new file mode 100644
index 000000000..648b51888
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/info.h
@@ -0,0 +1,1331 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Thing frame/state LUT,
+// generated by multigen utilitiy.
+// This one is the original DOOM version, preserved.
+//
+
+#ifndef __INFO__
+#define __INFO__
+
+// Needed for action function pointer handling.
+#include "d_think.h"
+
+typedef enum
+{
+ SPR_TROO,
+ SPR_SHTG,
+ SPR_PUNG,
+ SPR_PISG,
+ SPR_PISF,
+ SPR_SHTF,
+ SPR_SHT2,
+ SPR_CHGG,
+ SPR_CHGF,
+ SPR_MISG,
+ SPR_MISF,
+ SPR_SAWG,
+ SPR_PLSG,
+ SPR_PLSF,
+ SPR_BFGG,
+ SPR_BFGF,
+ SPR_BLUD,
+ SPR_PUFF,
+ SPR_BAL1,
+ SPR_BAL2,
+ SPR_PLSS,
+ SPR_PLSE,
+ SPR_MISL,
+ SPR_BFS1,
+ SPR_BFE1,
+ SPR_BFE2,
+ SPR_TFOG,
+ SPR_IFOG,
+ SPR_PLAY,
+ SPR_POSS,
+ SPR_SPOS,
+ SPR_VILE,
+ SPR_FIRE,
+ SPR_FATB,
+ SPR_FBXP,
+ SPR_SKEL,
+ SPR_MANF,
+ SPR_FATT,
+ SPR_CPOS,
+ SPR_SARG,
+ SPR_HEAD,
+ SPR_BAL7,
+ SPR_BOSS,
+ SPR_BOS2,
+ SPR_SKUL,
+ SPR_SPID,
+ SPR_BSPI,
+ SPR_APLS,
+ SPR_APBX,
+ SPR_CYBR,
+ SPR_PAIN,
+ SPR_SSWV,
+ SPR_KEEN,
+ SPR_BBRN,
+ SPR_BOSF,
+ SPR_ARM1,
+ SPR_ARM2,
+ SPR_BAR1,
+ SPR_BEXP,
+ SPR_FCAN,
+ SPR_BON1,
+ SPR_BON2,
+ SPR_BKEY,
+ SPR_RKEY,
+ SPR_YKEY,
+ SPR_BSKU,
+ SPR_RSKU,
+ SPR_YSKU,
+ SPR_STIM,
+ SPR_MEDI,
+ SPR_SOUL,
+ SPR_PINV,
+ SPR_PSTR,
+ SPR_PINS,
+ SPR_MEGA,
+ SPR_SUIT,
+ SPR_PMAP,
+ SPR_PVIS,
+ SPR_CLIP,
+ SPR_AMMO,
+ SPR_ROCK,
+ SPR_BROK,
+ SPR_CELL,
+ SPR_CELP,
+ SPR_SHEL,
+ SPR_SBOX,
+ SPR_BPAK,
+ SPR_BFUG,
+ SPR_MGUN,
+ SPR_CSAW,
+ SPR_LAUN,
+ SPR_PLAS,
+ SPR_SHOT,
+ SPR_SGN2,
+ SPR_COLU,
+ SPR_SMT2,
+ SPR_GOR1,
+ SPR_POL2,
+ SPR_POL5,
+ SPR_POL4,
+ SPR_POL3,
+ SPR_POL1,
+ SPR_POL6,
+ SPR_GOR2,
+ SPR_GOR3,
+ SPR_GOR4,
+ SPR_GOR5,
+ SPR_SMIT,
+ SPR_COL1,
+ SPR_COL2,
+ SPR_COL3,
+ SPR_COL4,
+ SPR_CAND,
+ SPR_CBRA,
+ SPR_COL6,
+ SPR_TRE1,
+ SPR_TRE2,
+ SPR_ELEC,
+ SPR_CEYE,
+ SPR_FSKU,
+ SPR_COL5,
+ SPR_TBLU,
+ SPR_TGRN,
+ SPR_TRED,
+ SPR_SMBT,
+ SPR_SMGT,
+ SPR_SMRT,
+ SPR_HDB1,
+ SPR_HDB2,
+ SPR_HDB3,
+ SPR_HDB4,
+ SPR_HDB5,
+ SPR_HDB6,
+ SPR_POB1,
+ SPR_POB2,
+ SPR_BRS1,
+ SPR_TLMP,
+ SPR_TLP2,
+ NUMSPRITES
+
+} spritenum_t;
+
+typedef enum
+{
+ S_NULL,
+ S_LIGHTDONE,
+ S_PUNCH,
+ S_PUNCHDOWN,
+ S_PUNCHUP,
+ S_PUNCH1,
+ S_PUNCH2,
+ S_PUNCH3,
+ S_PUNCH4,
+ S_PUNCH5,
+ S_PISTOL,
+ S_PISTOLDOWN,
+ S_PISTOLUP,
+ S_PISTOL1,
+ S_PISTOL2,
+ S_PISTOL3,
+ S_PISTOL4,
+ S_PISTOLFLASH,
+ S_SGUN,
+ S_SGUNDOWN,
+ S_SGUNUP,
+ S_SGUN1,
+ S_SGUN2,
+ S_SGUN3,
+ S_SGUN4,
+ S_SGUN5,
+ S_SGUN6,
+ S_SGUN7,
+ S_SGUN8,
+ S_SGUN9,
+ S_SGUNFLASH1,
+ S_SGUNFLASH2,
+ S_DSGUN,
+ S_DSGUNDOWN,
+ S_DSGUNUP,
+ S_DSGUN1,
+ S_DSGUN2,
+ S_DSGUN3,
+ S_DSGUN4,
+ S_DSGUN5,
+ S_DSGUN6,
+ S_DSGUN7,
+ S_DSGUN8,
+ S_DSGUN9,
+ S_DSGUN10,
+ S_DSNR1,
+ S_DSNR2,
+ S_DSGUNFLASH1,
+ S_DSGUNFLASH2,
+ S_CHAIN,
+ S_CHAINDOWN,
+ S_CHAINUP,
+ S_CHAIN1,
+ S_CHAIN2,
+ S_CHAIN3,
+ S_CHAINFLASH1,
+ S_CHAINFLASH2,
+ S_MISSILE,
+ S_MISSILEDOWN,
+ S_MISSILEUP,
+ S_MISSILE1,
+ S_MISSILE2,
+ S_MISSILE3,
+ S_MISSILEFLASH1,
+ S_MISSILEFLASH2,
+ S_MISSILEFLASH3,
+ S_MISSILEFLASH4,
+ S_SAW,
+ S_SAWB,
+ S_SAWDOWN,
+ S_SAWUP,
+ S_SAW1,
+ S_SAW2,
+ S_SAW3,
+ S_PLASMA,
+ S_PLASMADOWN,
+ S_PLASMAUP,
+ S_PLASMA1,
+ S_PLASMA2,
+ S_PLASMAFLASH1,
+ S_PLASMAFLASH2,
+ S_BFG,
+ S_BFGDOWN,
+ S_BFGUP,
+ S_BFG1,
+ S_BFG2,
+ S_BFG3,
+ S_BFG4,
+ S_BFGFLASH1,
+ S_BFGFLASH2,
+ S_BLOOD1,
+ S_BLOOD2,
+ S_BLOOD3,
+ S_PUFF1,
+ S_PUFF2,
+ S_PUFF3,
+ S_PUFF4,
+ S_TBALL1,
+ S_TBALL2,
+ S_TBALLX1,
+ S_TBALLX2,
+ S_TBALLX3,
+ S_RBALL1,
+ S_RBALL2,
+ S_RBALLX1,
+ S_RBALLX2,
+ S_RBALLX3,
+ S_PLASBALL,
+ S_PLASBALL2,
+ S_PLASEXP,
+ S_PLASEXP2,
+ S_PLASEXP3,
+ S_PLASEXP4,
+ S_PLASEXP5,
+ S_ROCKET,
+ S_BFGSHOT,
+ S_BFGSHOT2,
+ S_BFGLAND,
+ S_BFGLAND2,
+ S_BFGLAND3,
+ S_BFGLAND4,
+ S_BFGLAND5,
+ S_BFGLAND6,
+ S_BFGEXP,
+ S_BFGEXP2,
+ S_BFGEXP3,
+ S_BFGEXP4,
+ S_EXPLODE1,
+ S_EXPLODE2,
+ S_EXPLODE3,
+ S_TFOG,
+ S_TFOG01,
+ S_TFOG02,
+ S_TFOG2,
+ S_TFOG3,
+ S_TFOG4,
+ S_TFOG5,
+ S_TFOG6,
+ S_TFOG7,
+ S_TFOG8,
+ S_TFOG9,
+ S_TFOG10,
+ S_IFOG,
+ S_IFOG01,
+ S_IFOG02,
+ S_IFOG2,
+ S_IFOG3,
+ S_IFOG4,
+ S_IFOG5,
+ S_PLAY,
+ S_PLAY_RUN1,
+ S_PLAY_RUN2,
+ S_PLAY_RUN3,
+ S_PLAY_RUN4,
+ S_PLAY_ATK1,
+ S_PLAY_ATK2,
+ S_PLAY_PAIN,
+ S_PLAY_PAIN2,
+ S_PLAY_DIE1,
+ S_PLAY_DIE2,
+ S_PLAY_DIE3,
+ S_PLAY_DIE4,
+ S_PLAY_DIE5,
+ S_PLAY_DIE6,
+ S_PLAY_DIE7,
+ S_PLAY_XDIE1,
+ S_PLAY_XDIE2,
+ S_PLAY_XDIE3,
+ S_PLAY_XDIE4,
+ S_PLAY_XDIE5,
+ S_PLAY_XDIE6,
+ S_PLAY_XDIE7,
+ S_PLAY_XDIE8,
+ S_PLAY_XDIE9,
+ S_POSS_STND,
+ S_POSS_STND2,
+ S_POSS_RUN1,
+ S_POSS_RUN2,
+ S_POSS_RUN3,
+ S_POSS_RUN4,
+ S_POSS_RUN5,
+ S_POSS_RUN6,
+ S_POSS_RUN7,
+ S_POSS_RUN8,
+ S_POSS_ATK1,
+ S_POSS_ATK2,
+ S_POSS_ATK3,
+ S_POSS_PAIN,
+ S_POSS_PAIN2,
+ S_POSS_DIE1,
+ S_POSS_DIE2,
+ S_POSS_DIE3,
+ S_POSS_DIE4,
+ S_POSS_DIE5,
+ S_POSS_XDIE1,
+ S_POSS_XDIE2,
+ S_POSS_XDIE3,
+ S_POSS_XDIE4,
+ S_POSS_XDIE5,
+ S_POSS_XDIE6,
+ S_POSS_XDIE7,
+ S_POSS_XDIE8,
+ S_POSS_XDIE9,
+ S_POSS_RAISE1,
+ S_POSS_RAISE2,
+ S_POSS_RAISE3,
+ S_POSS_RAISE4,
+ S_SPOS_STND,
+ S_SPOS_STND2,
+ S_SPOS_RUN1,
+ S_SPOS_RUN2,
+ S_SPOS_RUN3,
+ S_SPOS_RUN4,
+ S_SPOS_RUN5,
+ S_SPOS_RUN6,
+ S_SPOS_RUN7,
+ S_SPOS_RUN8,
+ S_SPOS_ATK1,
+ S_SPOS_ATK2,
+ S_SPOS_ATK3,
+ S_SPOS_PAIN,
+ S_SPOS_PAIN2,
+ S_SPOS_DIE1,
+ S_SPOS_DIE2,
+ S_SPOS_DIE3,
+ S_SPOS_DIE4,
+ S_SPOS_DIE5,
+ S_SPOS_XDIE1,
+ S_SPOS_XDIE2,
+ S_SPOS_XDIE3,
+ S_SPOS_XDIE4,
+ S_SPOS_XDIE5,
+ S_SPOS_XDIE6,
+ S_SPOS_XDIE7,
+ S_SPOS_XDIE8,
+ S_SPOS_XDIE9,
+ S_SPOS_RAISE1,
+ S_SPOS_RAISE2,
+ S_SPOS_RAISE3,
+ S_SPOS_RAISE4,
+ S_SPOS_RAISE5,
+ S_VILE_STND,
+ S_VILE_STND2,
+ S_VILE_RUN1,
+ S_VILE_RUN2,
+ S_VILE_RUN3,
+ S_VILE_RUN4,
+ S_VILE_RUN5,
+ S_VILE_RUN6,
+ S_VILE_RUN7,
+ S_VILE_RUN8,
+ S_VILE_RUN9,
+ S_VILE_RUN10,
+ S_VILE_RUN11,
+ S_VILE_RUN12,
+ S_VILE_ATK1,
+ S_VILE_ATK2,
+ S_VILE_ATK3,
+ S_VILE_ATK4,
+ S_VILE_ATK5,
+ S_VILE_ATK6,
+ S_VILE_ATK7,
+ S_VILE_ATK8,
+ S_VILE_ATK9,
+ S_VILE_ATK10,
+ S_VILE_ATK11,
+ S_VILE_HEAL1,
+ S_VILE_HEAL2,
+ S_VILE_HEAL3,
+ S_VILE_PAIN,
+ S_VILE_PAIN2,
+ S_VILE_DIE1,
+ S_VILE_DIE2,
+ S_VILE_DIE3,
+ S_VILE_DIE4,
+ S_VILE_DIE5,
+ S_VILE_DIE6,
+ S_VILE_DIE7,
+ S_VILE_DIE8,
+ S_VILE_DIE9,
+ S_VILE_DIE10,
+ S_FIRE1,
+ S_FIRE2,
+ S_FIRE3,
+ S_FIRE4,
+ S_FIRE5,
+ S_FIRE6,
+ S_FIRE7,
+ S_FIRE8,
+ S_FIRE9,
+ S_FIRE10,
+ S_FIRE11,
+ S_FIRE12,
+ S_FIRE13,
+ S_FIRE14,
+ S_FIRE15,
+ S_FIRE16,
+ S_FIRE17,
+ S_FIRE18,
+ S_FIRE19,
+ S_FIRE20,
+ S_FIRE21,
+ S_FIRE22,
+ S_FIRE23,
+ S_FIRE24,
+ S_FIRE25,
+ S_FIRE26,
+ S_FIRE27,
+ S_FIRE28,
+ S_FIRE29,
+ S_FIRE30,
+ S_SMOKE1,
+ S_SMOKE2,
+ S_SMOKE3,
+ S_SMOKE4,
+ S_SMOKE5,
+ S_TRACER,
+ S_TRACER2,
+ S_TRACEEXP1,
+ S_TRACEEXP2,
+ S_TRACEEXP3,
+ S_SKEL_STND,
+ S_SKEL_STND2,
+ S_SKEL_RUN1,
+ S_SKEL_RUN2,
+ S_SKEL_RUN3,
+ S_SKEL_RUN4,
+ S_SKEL_RUN5,
+ S_SKEL_RUN6,
+ S_SKEL_RUN7,
+ S_SKEL_RUN8,
+ S_SKEL_RUN9,
+ S_SKEL_RUN10,
+ S_SKEL_RUN11,
+ S_SKEL_RUN12,
+ S_SKEL_FIST1,
+ S_SKEL_FIST2,
+ S_SKEL_FIST3,
+ S_SKEL_FIST4,
+ S_SKEL_MISS1,
+ S_SKEL_MISS2,
+ S_SKEL_MISS3,
+ S_SKEL_MISS4,
+ S_SKEL_PAIN,
+ S_SKEL_PAIN2,
+ S_SKEL_DIE1,
+ S_SKEL_DIE2,
+ S_SKEL_DIE3,
+ S_SKEL_DIE4,
+ S_SKEL_DIE5,
+ S_SKEL_DIE6,
+ S_SKEL_RAISE1,
+ S_SKEL_RAISE2,
+ S_SKEL_RAISE3,
+ S_SKEL_RAISE4,
+ S_SKEL_RAISE5,
+ S_SKEL_RAISE6,
+ S_FATSHOT1,
+ S_FATSHOT2,
+ S_FATSHOTX1,
+ S_FATSHOTX2,
+ S_FATSHOTX3,
+ S_FATT_STND,
+ S_FATT_STND2,
+ S_FATT_RUN1,
+ S_FATT_RUN2,
+ S_FATT_RUN3,
+ S_FATT_RUN4,
+ S_FATT_RUN5,
+ S_FATT_RUN6,
+ S_FATT_RUN7,
+ S_FATT_RUN8,
+ S_FATT_RUN9,
+ S_FATT_RUN10,
+ S_FATT_RUN11,
+ S_FATT_RUN12,
+ S_FATT_ATK1,
+ S_FATT_ATK2,
+ S_FATT_ATK3,
+ S_FATT_ATK4,
+ S_FATT_ATK5,
+ S_FATT_ATK6,
+ S_FATT_ATK7,
+ S_FATT_ATK8,
+ S_FATT_ATK9,
+ S_FATT_ATK10,
+ S_FATT_PAIN,
+ S_FATT_PAIN2,
+ S_FATT_DIE1,
+ S_FATT_DIE2,
+ S_FATT_DIE3,
+ S_FATT_DIE4,
+ S_FATT_DIE5,
+ S_FATT_DIE6,
+ S_FATT_DIE7,
+ S_FATT_DIE8,
+ S_FATT_DIE9,
+ S_FATT_DIE10,
+ S_FATT_RAISE1,
+ S_FATT_RAISE2,
+ S_FATT_RAISE3,
+ S_FATT_RAISE4,
+ S_FATT_RAISE5,
+ S_FATT_RAISE6,
+ S_FATT_RAISE7,
+ S_FATT_RAISE8,
+ S_CPOS_STND,
+ S_CPOS_STND2,
+ S_CPOS_RUN1,
+ S_CPOS_RUN2,
+ S_CPOS_RUN3,
+ S_CPOS_RUN4,
+ S_CPOS_RUN5,
+ S_CPOS_RUN6,
+ S_CPOS_RUN7,
+ S_CPOS_RUN8,
+ S_CPOS_ATK1,
+ S_CPOS_ATK2,
+ S_CPOS_ATK3,
+ S_CPOS_ATK4,
+ S_CPOS_PAIN,
+ S_CPOS_PAIN2,
+ S_CPOS_DIE1,
+ S_CPOS_DIE2,
+ S_CPOS_DIE3,
+ S_CPOS_DIE4,
+ S_CPOS_DIE5,
+ S_CPOS_DIE6,
+ S_CPOS_DIE7,
+ S_CPOS_XDIE1,
+ S_CPOS_XDIE2,
+ S_CPOS_XDIE3,
+ S_CPOS_XDIE4,
+ S_CPOS_XDIE5,
+ S_CPOS_XDIE6,
+ S_CPOS_RAISE1,
+ S_CPOS_RAISE2,
+ S_CPOS_RAISE3,
+ S_CPOS_RAISE4,
+ S_CPOS_RAISE5,
+ S_CPOS_RAISE6,
+ S_CPOS_RAISE7,
+ S_TROO_STND,
+ S_TROO_STND2,
+ S_TROO_RUN1,
+ S_TROO_RUN2,
+ S_TROO_RUN3,
+ S_TROO_RUN4,
+ S_TROO_RUN5,
+ S_TROO_RUN6,
+ S_TROO_RUN7,
+ S_TROO_RUN8,
+ S_TROO_ATK1,
+ S_TROO_ATK2,
+ S_TROO_ATK3,
+ S_TROO_PAIN,
+ S_TROO_PAIN2,
+ S_TROO_DIE1,
+ S_TROO_DIE2,
+ S_TROO_DIE3,
+ S_TROO_DIE4,
+ S_TROO_DIE5,
+ S_TROO_XDIE1,
+ S_TROO_XDIE2,
+ S_TROO_XDIE3,
+ S_TROO_XDIE4,
+ S_TROO_XDIE5,
+ S_TROO_XDIE6,
+ S_TROO_XDIE7,
+ S_TROO_XDIE8,
+ S_TROO_RAISE1,
+ S_TROO_RAISE2,
+ S_TROO_RAISE3,
+ S_TROO_RAISE4,
+ S_TROO_RAISE5,
+ S_SARG_STND,
+ S_SARG_STND2,
+ S_SARG_RUN1,
+ S_SARG_RUN2,
+ S_SARG_RUN3,
+ S_SARG_RUN4,
+ S_SARG_RUN5,
+ S_SARG_RUN6,
+ S_SARG_RUN7,
+ S_SARG_RUN8,
+ S_SARG_ATK1,
+ S_SARG_ATK2,
+ S_SARG_ATK3,
+ S_SARG_PAIN,
+ S_SARG_PAIN2,
+ S_SARG_DIE1,
+ S_SARG_DIE2,
+ S_SARG_DIE3,
+ S_SARG_DIE4,
+ S_SARG_DIE5,
+ S_SARG_DIE6,
+ S_SARG_RAISE1,
+ S_SARG_RAISE2,
+ S_SARG_RAISE3,
+ S_SARG_RAISE4,
+ S_SARG_RAISE5,
+ S_SARG_RAISE6,
+ S_HEAD_STND,
+ S_HEAD_RUN1,
+ S_HEAD_ATK1,
+ S_HEAD_ATK2,
+ S_HEAD_ATK3,
+ S_HEAD_PAIN,
+ S_HEAD_PAIN2,
+ S_HEAD_PAIN3,
+ S_HEAD_DIE1,
+ S_HEAD_DIE2,
+ S_HEAD_DIE3,
+ S_HEAD_DIE4,
+ S_HEAD_DIE5,
+ S_HEAD_DIE6,
+ S_HEAD_RAISE1,
+ S_HEAD_RAISE2,
+ S_HEAD_RAISE3,
+ S_HEAD_RAISE4,
+ S_HEAD_RAISE5,
+ S_HEAD_RAISE6,
+ S_BRBALL1,
+ S_BRBALL2,
+ S_BRBALLX1,
+ S_BRBALLX2,
+ S_BRBALLX3,
+ S_BOSS_STND,
+ S_BOSS_STND2,
+ S_BOSS_RUN1,
+ S_BOSS_RUN2,
+ S_BOSS_RUN3,
+ S_BOSS_RUN4,
+ S_BOSS_RUN5,
+ S_BOSS_RUN6,
+ S_BOSS_RUN7,
+ S_BOSS_RUN8,
+ S_BOSS_ATK1,
+ S_BOSS_ATK2,
+ S_BOSS_ATK3,
+ S_BOSS_PAIN,
+ S_BOSS_PAIN2,
+ S_BOSS_DIE1,
+ S_BOSS_DIE2,
+ S_BOSS_DIE3,
+ S_BOSS_DIE4,
+ S_BOSS_DIE5,
+ S_BOSS_DIE6,
+ S_BOSS_DIE7,
+ S_BOSS_RAISE1,
+ S_BOSS_RAISE2,
+ S_BOSS_RAISE3,
+ S_BOSS_RAISE4,
+ S_BOSS_RAISE5,
+ S_BOSS_RAISE6,
+ S_BOSS_RAISE7,
+ S_BOS2_STND,
+ S_BOS2_STND2,
+ S_BOS2_RUN1,
+ S_BOS2_RUN2,
+ S_BOS2_RUN3,
+ S_BOS2_RUN4,
+ S_BOS2_RUN5,
+ S_BOS2_RUN6,
+ S_BOS2_RUN7,
+ S_BOS2_RUN8,
+ S_BOS2_ATK1,
+ S_BOS2_ATK2,
+ S_BOS2_ATK3,
+ S_BOS2_PAIN,
+ S_BOS2_PAIN2,
+ S_BOS2_DIE1,
+ S_BOS2_DIE2,
+ S_BOS2_DIE3,
+ S_BOS2_DIE4,
+ S_BOS2_DIE5,
+ S_BOS2_DIE6,
+ S_BOS2_DIE7,
+ S_BOS2_RAISE1,
+ S_BOS2_RAISE2,
+ S_BOS2_RAISE3,
+ S_BOS2_RAISE4,
+ S_BOS2_RAISE5,
+ S_BOS2_RAISE6,
+ S_BOS2_RAISE7,
+ S_SKULL_STND,
+ S_SKULL_STND2,
+ S_SKULL_RUN1,
+ S_SKULL_RUN2,
+ S_SKULL_ATK1,
+ S_SKULL_ATK2,
+ S_SKULL_ATK3,
+ S_SKULL_ATK4,
+ S_SKULL_PAIN,
+ S_SKULL_PAIN2,
+ S_SKULL_DIE1,
+ S_SKULL_DIE2,
+ S_SKULL_DIE3,
+ S_SKULL_DIE4,
+ S_SKULL_DIE5,
+ S_SKULL_DIE6,
+ S_SPID_STND,
+ S_SPID_STND2,
+ S_SPID_RUN1,
+ S_SPID_RUN2,
+ S_SPID_RUN3,
+ S_SPID_RUN4,
+ S_SPID_RUN5,
+ S_SPID_RUN6,
+ S_SPID_RUN7,
+ S_SPID_RUN8,
+ S_SPID_RUN9,
+ S_SPID_RUN10,
+ S_SPID_RUN11,
+ S_SPID_RUN12,
+ S_SPID_ATK1,
+ S_SPID_ATK2,
+ S_SPID_ATK3,
+ S_SPID_ATK4,
+ S_SPID_PAIN,
+ S_SPID_PAIN2,
+ S_SPID_DIE1,
+ S_SPID_DIE2,
+ S_SPID_DIE3,
+ S_SPID_DIE4,
+ S_SPID_DIE5,
+ S_SPID_DIE6,
+ S_SPID_DIE7,
+ S_SPID_DIE8,
+ S_SPID_DIE9,
+ S_SPID_DIE10,
+ S_SPID_DIE11,
+ S_BSPI_STND,
+ S_BSPI_STND2,
+ S_BSPI_SIGHT,
+ S_BSPI_RUN1,
+ S_BSPI_RUN2,
+ S_BSPI_RUN3,
+ S_BSPI_RUN4,
+ S_BSPI_RUN5,
+ S_BSPI_RUN6,
+ S_BSPI_RUN7,
+ S_BSPI_RUN8,
+ S_BSPI_RUN9,
+ S_BSPI_RUN10,
+ S_BSPI_RUN11,
+ S_BSPI_RUN12,
+ S_BSPI_ATK1,
+ S_BSPI_ATK2,
+ S_BSPI_ATK3,
+ S_BSPI_ATK4,
+ S_BSPI_PAIN,
+ S_BSPI_PAIN2,
+ S_BSPI_DIE1,
+ S_BSPI_DIE2,
+ S_BSPI_DIE3,
+ S_BSPI_DIE4,
+ S_BSPI_DIE5,
+ S_BSPI_DIE6,
+ S_BSPI_DIE7,
+ S_BSPI_RAISE1,
+ S_BSPI_RAISE2,
+ S_BSPI_RAISE3,
+ S_BSPI_RAISE4,
+ S_BSPI_RAISE5,
+ S_BSPI_RAISE6,
+ S_BSPI_RAISE7,
+ S_ARACH_PLAZ,
+ S_ARACH_PLAZ2,
+ S_ARACH_PLEX,
+ S_ARACH_PLEX2,
+ S_ARACH_PLEX3,
+ S_ARACH_PLEX4,
+ S_ARACH_PLEX5,
+ S_CYBER_STND,
+ S_CYBER_STND2,
+ S_CYBER_RUN1,
+ S_CYBER_RUN2,
+ S_CYBER_RUN3,
+ S_CYBER_RUN4,
+ S_CYBER_RUN5,
+ S_CYBER_RUN6,
+ S_CYBER_RUN7,
+ S_CYBER_RUN8,
+ S_CYBER_ATK1,
+ S_CYBER_ATK2,
+ S_CYBER_ATK3,
+ S_CYBER_ATK4,
+ S_CYBER_ATK5,
+ S_CYBER_ATK6,
+ S_CYBER_PAIN,
+ S_CYBER_DIE1,
+ S_CYBER_DIE2,
+ S_CYBER_DIE3,
+ S_CYBER_DIE4,
+ S_CYBER_DIE5,
+ S_CYBER_DIE6,
+ S_CYBER_DIE7,
+ S_CYBER_DIE8,
+ S_CYBER_DIE9,
+ S_CYBER_DIE10,
+ S_PAIN_STND,
+ S_PAIN_RUN1,
+ S_PAIN_RUN2,
+ S_PAIN_RUN3,
+ S_PAIN_RUN4,
+ S_PAIN_RUN5,
+ S_PAIN_RUN6,
+ S_PAIN_ATK1,
+ S_PAIN_ATK2,
+ S_PAIN_ATK3,
+ S_PAIN_ATK4,
+ S_PAIN_PAIN,
+ S_PAIN_PAIN2,
+ S_PAIN_DIE1,
+ S_PAIN_DIE2,
+ S_PAIN_DIE3,
+ S_PAIN_DIE4,
+ S_PAIN_DIE5,
+ S_PAIN_DIE6,
+ S_PAIN_RAISE1,
+ S_PAIN_RAISE2,
+ S_PAIN_RAISE3,
+ S_PAIN_RAISE4,
+ S_PAIN_RAISE5,
+ S_PAIN_RAISE6,
+ S_SSWV_STND,
+ S_SSWV_STND2,
+ S_SSWV_RUN1,
+ S_SSWV_RUN2,
+ S_SSWV_RUN3,
+ S_SSWV_RUN4,
+ S_SSWV_RUN5,
+ S_SSWV_RUN6,
+ S_SSWV_RUN7,
+ S_SSWV_RUN8,
+ S_SSWV_ATK1,
+ S_SSWV_ATK2,
+ S_SSWV_ATK3,
+ S_SSWV_ATK4,
+ S_SSWV_ATK5,
+ S_SSWV_ATK6,
+ S_SSWV_PAIN,
+ S_SSWV_PAIN2,
+ S_SSWV_DIE1,
+ S_SSWV_DIE2,
+ S_SSWV_DIE3,
+ S_SSWV_DIE4,
+ S_SSWV_DIE5,
+ S_SSWV_XDIE1,
+ S_SSWV_XDIE2,
+ S_SSWV_XDIE3,
+ S_SSWV_XDIE4,
+ S_SSWV_XDIE5,
+ S_SSWV_XDIE6,
+ S_SSWV_XDIE7,
+ S_SSWV_XDIE8,
+ S_SSWV_XDIE9,
+ S_SSWV_RAISE1,
+ S_SSWV_RAISE2,
+ S_SSWV_RAISE3,
+ S_SSWV_RAISE4,
+ S_SSWV_RAISE5,
+ S_KEENSTND,
+ S_COMMKEEN,
+ S_COMMKEEN2,
+ S_COMMKEEN3,
+ S_COMMKEEN4,
+ S_COMMKEEN5,
+ S_COMMKEEN6,
+ S_COMMKEEN7,
+ S_COMMKEEN8,
+ S_COMMKEEN9,
+ S_COMMKEEN10,
+ S_COMMKEEN11,
+ S_COMMKEEN12,
+ S_KEENPAIN,
+ S_KEENPAIN2,
+ S_BRAIN,
+ S_BRAIN_PAIN,
+ S_BRAIN_DIE1,
+ S_BRAIN_DIE2,
+ S_BRAIN_DIE3,
+ S_BRAIN_DIE4,
+ S_BRAINEYE,
+ S_BRAINEYESEE,
+ S_BRAINEYE1,
+ S_SPAWN1,
+ S_SPAWN2,
+ S_SPAWN3,
+ S_SPAWN4,
+ S_SPAWNFIRE1,
+ S_SPAWNFIRE2,
+ S_SPAWNFIRE3,
+ S_SPAWNFIRE4,
+ S_SPAWNFIRE5,
+ S_SPAWNFIRE6,
+ S_SPAWNFIRE7,
+ S_SPAWNFIRE8,
+ S_BRAINEXPLODE1,
+ S_BRAINEXPLODE2,
+ S_BRAINEXPLODE3,
+ S_ARM1,
+ S_ARM1A,
+ S_ARM2,
+ S_ARM2A,
+ S_BAR1,
+ S_BAR2,
+ S_BEXP,
+ S_BEXP2,
+ S_BEXP3,
+ S_BEXP4,
+ S_BEXP5,
+ S_BBAR1,
+ S_BBAR2,
+ S_BBAR3,
+ S_BON1,
+ S_BON1A,
+ S_BON1B,
+ S_BON1C,
+ S_BON1D,
+ S_BON1E,
+ S_BON2,
+ S_BON2A,
+ S_BON2B,
+ S_BON2C,
+ S_BON2D,
+ S_BON2E,
+ S_BKEY,
+ S_BKEY2,
+ S_RKEY,
+ S_RKEY2,
+ S_YKEY,
+ S_YKEY2,
+ S_BSKULL,
+ S_BSKULL2,
+ S_RSKULL,
+ S_RSKULL2,
+ S_YSKULL,
+ S_YSKULL2,
+ S_STIM,
+ S_MEDI,
+ S_SOUL,
+ S_SOUL2,
+ S_SOUL3,
+ S_SOUL4,
+ S_SOUL5,
+ S_SOUL6,
+ S_PINV,
+ S_PINV2,
+ S_PINV3,
+ S_PINV4,
+ S_PSTR,
+ S_PINS,
+ S_PINS2,
+ S_PINS3,
+ S_PINS4,
+ S_MEGA,
+ S_MEGA2,
+ S_MEGA3,
+ S_MEGA4,
+ S_SUIT,
+ S_PMAP,
+ S_PMAP2,
+ S_PMAP3,
+ S_PMAP4,
+ S_PMAP5,
+ S_PMAP6,
+ S_PVIS,
+ S_PVIS2,
+ S_CLIP,
+ S_AMMO,
+ S_ROCK,
+ S_BROK,
+ S_CELL,
+ S_CELP,
+ S_SHEL,
+ S_SBOX,
+ S_BPAK,
+ S_BFUG,
+ S_MGUN,
+ S_CSAW,
+ S_LAUN,
+ S_PLAS,
+ S_SHOT,
+ S_SHOT2,
+ S_COLU,
+ S_STALAG,
+ S_BLOODYTWITCH,
+ S_BLOODYTWITCH2,
+ S_BLOODYTWITCH3,
+ S_BLOODYTWITCH4,
+ S_DEADTORSO,
+ S_DEADBOTTOM,
+ S_HEADSONSTICK,
+ S_GIBS,
+ S_HEADONASTICK,
+ S_HEADCANDLES,
+ S_HEADCANDLES2,
+ S_DEADSTICK,
+ S_LIVESTICK,
+ S_LIVESTICK2,
+ S_MEAT2,
+ S_MEAT3,
+ S_MEAT4,
+ S_MEAT5,
+ S_STALAGTITE,
+ S_TALLGRNCOL,
+ S_SHRTGRNCOL,
+ S_TALLREDCOL,
+ S_SHRTREDCOL,
+ S_CANDLESTIK,
+ S_CANDELABRA,
+ S_SKULLCOL,
+ S_TORCHTREE,
+ S_BIGTREE,
+ S_TECHPILLAR,
+ S_EVILEYE,
+ S_EVILEYE2,
+ S_EVILEYE3,
+ S_EVILEYE4,
+ S_FLOATSKULL,
+ S_FLOATSKULL2,
+ S_FLOATSKULL3,
+ S_HEARTCOL,
+ S_HEARTCOL2,
+ S_BLUETORCH,
+ S_BLUETORCH2,
+ S_BLUETORCH3,
+ S_BLUETORCH4,
+ S_GREENTORCH,
+ S_GREENTORCH2,
+ S_GREENTORCH3,
+ S_GREENTORCH4,
+ S_REDTORCH,
+ S_REDTORCH2,
+ S_REDTORCH3,
+ S_REDTORCH4,
+ S_BTORCHSHRT,
+ S_BTORCHSHRT2,
+ S_BTORCHSHRT3,
+ S_BTORCHSHRT4,
+ S_GTORCHSHRT,
+ S_GTORCHSHRT2,
+ S_GTORCHSHRT3,
+ S_GTORCHSHRT4,
+ S_RTORCHSHRT,
+ S_RTORCHSHRT2,
+ S_RTORCHSHRT3,
+ S_RTORCHSHRT4,
+ S_HANGNOGUTS,
+ S_HANGBNOBRAIN,
+ S_HANGTLOOKDN,
+ S_HANGTSKULL,
+ S_HANGTLOOKUP,
+ S_HANGTNOBRAIN,
+ S_COLONGIBS,
+ S_SMALLPOOL,
+ S_BRAINSTEM,
+ S_TECHLAMP,
+ S_TECHLAMP2,
+ S_TECHLAMP3,
+ S_TECHLAMP4,
+ S_TECH2LAMP,
+ S_TECH2LAMP2,
+ S_TECH2LAMP3,
+ S_TECH2LAMP4,
+ NUMSTATES
+} statenum_t;
+
+
+typedef struct
+{
+ spritenum_t sprite;
+ int frame;
+ int tics;
+ // void (*action) ();
+ actionf_t action;
+ statenum_t nextstate;
+ int misc1;
+ int misc2;
+} state_t;
+
+extern state_t states[NUMSTATES];
+extern char *sprnames[];
+
+typedef enum {
+ MT_PLAYER,
+ MT_POSSESSED,
+ MT_SHOTGUY,
+ MT_VILE,
+ MT_FIRE,
+ MT_UNDEAD,
+ MT_TRACER,
+ MT_SMOKE,
+ MT_FATSO,
+ MT_FATSHOT,
+ MT_CHAINGUY,
+ MT_TROOP,
+ MT_SERGEANT,
+ MT_SHADOWS,
+ MT_HEAD,
+ MT_BRUISER,
+ MT_BRUISERSHOT,
+ MT_KNIGHT,
+ MT_SKULL,
+ MT_SPIDER,
+ MT_BABY,
+ MT_CYBORG,
+ MT_PAIN,
+ MT_WOLFSS,
+ MT_KEEN,
+ MT_BOSSBRAIN,
+ MT_BOSSSPIT,
+ MT_BOSSTARGET,
+ MT_SPAWNSHOT,
+ MT_SPAWNFIRE,
+ MT_BARREL,
+ MT_TROOPSHOT,
+ MT_HEADSHOT,
+ MT_ROCKET,
+ MT_PLASMA,
+ MT_BFG,
+ MT_ARACHPLAZ,
+ MT_PUFF,
+ MT_BLOOD,
+ MT_TFOG,
+ MT_IFOG,
+ MT_TELEPORTMAN,
+ MT_EXTRABFG,
+ MT_MISC0,
+ MT_MISC1,
+ MT_MISC2,
+ MT_MISC3,
+ MT_MISC4,
+ MT_MISC5,
+ MT_MISC6,
+ MT_MISC7,
+ MT_MISC8,
+ MT_MISC9,
+ MT_MISC10,
+ MT_MISC11,
+ MT_MISC12,
+ MT_INV,
+ MT_MISC13,
+ MT_INS,
+ MT_MISC14,
+ MT_MISC15,
+ MT_MISC16,
+ MT_MEGA,
+ MT_CLIP,
+ MT_MISC17,
+ MT_MISC18,
+ MT_MISC19,
+ MT_MISC20,
+ MT_MISC21,
+ MT_MISC22,
+ MT_MISC23,
+ MT_MISC24,
+ MT_MISC25,
+ MT_CHAINGUN,
+ MT_MISC26,
+ MT_MISC27,
+ MT_MISC28,
+ MT_SHOTGUN,
+ MT_SUPERSHOTGUN,
+ MT_MISC29,
+ MT_MISC30,
+ MT_MISC31,
+ MT_MISC32,
+ MT_MISC33,
+ MT_MISC34,
+ MT_MISC35,
+ MT_MISC36,
+ MT_MISC37,
+ MT_MISC38,
+ MT_MISC39,
+ MT_MISC40,
+ MT_MISC41,
+ MT_MISC42,
+ MT_MISC43,
+ MT_MISC44,
+ MT_MISC45,
+ MT_MISC46,
+ MT_MISC47,
+ MT_MISC48,
+ MT_MISC49,
+ MT_MISC50,
+ MT_MISC51,
+ MT_MISC52,
+ MT_MISC53,
+ MT_MISC54,
+ MT_MISC55,
+ MT_MISC56,
+ MT_MISC57,
+ MT_MISC58,
+ MT_MISC59,
+ MT_MISC60,
+ MT_MISC61,
+ MT_MISC62,
+ MT_MISC63,
+ MT_MISC64,
+ MT_MISC65,
+ MT_MISC66,
+ MT_MISC67,
+ MT_MISC68,
+ MT_MISC69,
+ MT_MISC70,
+ MT_MISC71,
+ MT_MISC72,
+ MT_MISC73,
+ MT_MISC74,
+ MT_MISC75,
+ MT_MISC76,
+ MT_MISC77,
+ MT_MISC78,
+ MT_MISC79,
+ MT_MISC80,
+ MT_MISC81,
+ MT_MISC82,
+ MT_MISC83,
+ MT_MISC84,
+ MT_MISC85,
+ MT_MISC86,
+ NUMMOBJTYPES
+
+} mobjtype_t;
+
+typedef struct
+{
+ int doomednum;
+ int spawnstate;
+ int spawnhealth;
+ int seestate;
+ int seesound;
+ int reactiontime;
+ int attacksound;
+ int painstate;
+ int painchance;
+ int painsound;
+ int meleestate;
+ int missilestate;
+ int deathstate;
+ int xdeathstate;
+ int deathsound;
+ int speed;
+ int radius;
+ int height;
+ int mass;
+ int damage;
+ int activesound;
+ int flags;
+ int raisestate;
+
+} mobjinfo_t;
+
+extern mobjinfo_t mobjinfo[NUMMOBJTYPES];
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/linker.lf b/firmware_p4/components/Applications/doom/linker.lf
new file mode 100644
index 000000000..1562f4de9
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/linker.lf
@@ -0,0 +1,22 @@
+# Move the DOOM engine's large static .bss/.common out of scarce internal SRAM
+# and into PSRAM. libdoom.a parks ~300 KB in internal RAM at link time (visplanes
+# ~85 KB, openings ~41 KB, ticdata, viewangletox, drawsegs, zlight, vissprites,
+# ...), which starves the whole system to ~14 KB free internal — so entering DOOM
+# fails to get the ST7789 SPI-DMA bounce buffer ("Failed to allocate priv TX
+# buffer") and every blit dies.
+#
+# These are CPU-only renderer scratch buffers (the real double framebuffer is
+# malloc'd in PSRAM already), so PSRAM placement is safe — no DMA touches them.
+# Only .bss/.common move; initialized .data (states, mobjinfo, S_sfx) stays
+# internal. Requires CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y for the
+# extern_ram region to exist.
+
+[scheme:doom_extram]
+entries:
+ bss -> extern_ram
+ common -> extern_ram
+
+[mapping:doom]
+archive: libdoom.a
+entries:
+ * (doom_extram)
diff --git a/firmware_p4/components/Applications/doom/m_argv.c b/firmware_p4/components/Applications/doom/m_argv.c
new file mode 100644
index 000000000..1582450fe
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/m_argv.c
@@ -0,0 +1,265 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+//
+
+
+#include
+#include
+#include
+#include
+
+#include "doomtype.h"
+#include "i_system.h"
+#include "m_misc.h"
+#include "m_argv.h" // haleyjd 20110212: warning fix
+
+int myargc;
+char** myargv;
+
+
+
+
+//
+// M_CheckParm
+// Checks for the given parameter
+// in the program's command line arguments.
+// Returns the argument number (1 to argc-1)
+// or 0 if not present
+//
+
+int M_CheckParmWithArgs(char *check, int num_args)
+{
+ int i;
+
+ for (i = 1; i < myargc - num_args; i++)
+ {
+ if (!strcasecmp(check, myargv[i]))
+ return i;
+ }
+
+ return 0;
+}
+
+//
+// M_ParmExists
+//
+// Returns true if the given parameter exists in the program's command
+// line arguments, false if not.
+//
+
+boolean M_ParmExists(char *check)
+{
+ return M_CheckParm(check) != 0;
+}
+
+int M_CheckParm(char *check)
+{
+ return M_CheckParmWithArgs(check, 0);
+}
+
+#define MAXARGVS 100
+
+static void LoadResponseFile(int argv_index)
+{
+#if ORIGCODE
+ FILE *handle;
+ int size;
+ char *infile;
+ char *file;
+ char *response_filename;
+ char **newargv;
+ int newargc;
+ int i, k;
+
+ response_filename = myargv[argv_index] + 1;
+
+ // Read the response file into memory
+ handle = fopen(response_filename, "rb");
+
+ if (handle == NULL)
+ {
+ printf ("\nNo such response file!");
+#if ORIGCODE
+ exit(1);
+#endif
+ }
+
+ printf("Found response file %s!\n", response_filename);
+
+ size = M_FileLength(handle);
+
+ // Read in the entire file
+ // Allocate one byte extra - this is in case there is an argument
+ // at the end of the response file, in which case a '\0' will be
+ // needed.
+
+ file = malloc(size + 1);
+
+ i = 0;
+
+ while (i < size)
+ {
+ k = fread(file + i, 1, size - i, handle);
+
+ if (k < 0)
+ {
+ I_Error("Failed to read full contents of '%s'", response_filename);
+ }
+
+ i += k;
+ }
+
+ fclose(handle);
+
+ // Create new arguments list array
+
+ newargv = malloc(sizeof(char *) * MAXARGVS);
+ newargc = 0;
+ memset(newargv, 0, sizeof(char *) * MAXARGVS);
+
+ // Copy all the arguments in the list up to the response file
+
+ for (i=0; i= size)
+ {
+ break;
+ }
+
+ // If the next argument is enclosed in quote marks, treat
+ // the contents as a single argument. This allows long filenames
+ // to be specified.
+
+ if (infile[k] == '\"')
+ {
+ // Skip the first character(")
+ ++k;
+
+ newargv[newargc++] = &infile[k];
+
+ // Read all characters between quotes
+
+ while (k < size && infile[k] != '\"' && infile[k] != '\n')
+ {
+ ++k;
+ }
+
+ if (k >= size || infile[k] == '\n')
+ {
+ I_Error("Quotes unclosed in response file '%s'",
+ response_filename);
+ }
+
+ // Cut off the string at the closing quote
+
+ infile[k] = '\0';
+ ++k;
+ }
+ else
+ {
+ // Read in the next argument until a space is reached
+
+ newargv[newargc++] = &infile[k];
+
+ while(k < size && !isspace((int)infile[k]))
+ {
+ ++k;
+ }
+
+ // Cut off the end of the argument at the first space
+
+ infile[k] = '\0';
+
+ ++k;
+ }
+ }
+
+ // Add arguments following the response file argument
+
+ for (i=argv_index + 1; ibox[BOXRIGHT])
+ box[BOXRIGHT] = x;
+ if (ybox[BOXTOP])
+ box[BOXTOP] = y;
+}
+
+
+
+
+
diff --git a/firmware_p4/components/Applications/doom/m_bbox.h b/firmware_p4/components/Applications/doom/m_bbox.h
new file mode 100644
index 000000000..5a7af57c4
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/m_bbox.h
@@ -0,0 +1,47 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Nil.
+//
+
+
+#ifndef __M_BBOX__
+#define __M_BBOX__
+
+#include
+
+#include "m_fixed.h"
+
+
+// Bounding box coordinate storage.
+enum
+{
+ BOXTOP,
+ BOXBOTTOM,
+ BOXLEFT,
+ BOXRIGHT
+}; // bbox coordinates
+
+// Bounding box functions.
+void M_ClearBox (fixed_t* box);
+
+void
+M_AddToBox
+( fixed_t* box,
+ fixed_t x,
+ fixed_t y );
+
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/m_cheat.c b/firmware_p4/components/Applications/doom/m_cheat.c
new file mode 100644
index 000000000..1565f9ef5
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/m_cheat.c
@@ -0,0 +1,89 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Cheat sequence checking.
+//
+
+
+
+#include
+
+#include "doomtype.h"
+#include "m_cheat.h"
+
+//
+// CHEAT SEQUENCE PACKAGE
+//
+
+//
+// Called in st_stuff module, which handles the input.
+// Returns a 1 if the cheat was successful, 0 if failed.
+//
+int
+cht_CheckCheat
+( cheatseq_t* cht,
+ char key )
+{
+ // if we make a short sequence on a cheat with parameters, this
+ // will not work in vanilla doom. behave the same.
+
+ if (cht->parameter_chars > 0 && strlen(cht->sequence) < cht->sequence_len)
+ return false;
+
+ if (cht->chars_read < strlen(cht->sequence))
+ {
+ // still reading characters from the cheat code
+ // and verifying. reset back to the beginning
+ // if a key is wrong
+
+ if (key == cht->sequence[cht->chars_read])
+ ++cht->chars_read;
+ else
+ cht->chars_read = 0;
+
+ cht->param_chars_read = 0;
+ }
+ else if (cht->param_chars_read < cht->parameter_chars)
+ {
+ // we have passed the end of the cheat sequence and are
+ // entering parameters now
+
+ cht->parameter_buf[cht->param_chars_read] = key;
+
+ ++cht->param_chars_read;
+ }
+
+ if (cht->chars_read >= strlen(cht->sequence)
+ && cht->param_chars_read >= cht->parameter_chars)
+ {
+ cht->chars_read = cht->param_chars_read = 0;
+
+ return true;
+ }
+
+ // cheat not matched yet
+
+ return false;
+}
+
+void
+cht_GetParam
+( cheatseq_t* cht,
+ char* buffer )
+{
+ memcpy(buffer, cht->parameter_buf, cht->parameter_chars);
+}
+
+
diff --git a/firmware_p4/components/Applications/doom/m_cheat.h b/firmware_p4/components/Applications/doom/m_cheat.h
new file mode 100644
index 000000000..6775e709e
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/m_cheat.h
@@ -0,0 +1,62 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Cheat code checking.
+//
+
+
+#ifndef __M_CHEAT__
+#define __M_CHEAT__
+
+//
+// CHEAT SEQUENCE PACKAGE
+//
+
+// declaring a cheat
+
+#define CHEAT(value, parameters) \
+ { value, sizeof(value) - 1, parameters, 0, 0, "" }
+
+#define MAX_CHEAT_LEN 25
+#define MAX_CHEAT_PARAMS 5
+
+typedef struct
+{
+ // settings for this cheat
+
+ char sequence[MAX_CHEAT_LEN];
+ size_t sequence_len;
+ int parameter_chars;
+
+ // state used during the game
+
+ size_t chars_read;
+ int param_chars_read;
+ char parameter_buf[MAX_CHEAT_PARAMS];
+} cheatseq_t;
+
+int
+cht_CheckCheat
+( cheatseq_t* cht,
+ char key );
+
+
+void
+cht_GetParam
+( cheatseq_t* cht,
+ char* buffer );
+
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/m_config.c b/firmware_p4/components/Applications/doom/m_config.c
new file mode 100644
index 000000000..865e16842
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/m_config.c
@@ -0,0 +1,2128 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Configuration file interface.
+//
+
+
+#include
+#include
+#include
+#include
+#include
+
+#include "config.h"
+
+#include "doomtype.h"
+#include "doomkeys.h"
+#include "doomfeatures.h"
+#include "i_system.h"
+#include "m_argv.h"
+#include "m_misc.h"
+
+#include "z_zone.h"
+
+//
+// DEFAULTS
+//
+
+// Location where all configuration data is stored -
+// default.cfg, savegames, etc.
+
+char *configdir;
+
+// Default filenames for configuration files.
+
+static char *default_main_config;
+static char *default_extra_config;
+
+typedef enum
+{
+ DEFAULT_INT,
+ DEFAULT_INT_HEX,
+ DEFAULT_STRING,
+ DEFAULT_FLOAT,
+ DEFAULT_KEY,
+} default_type_t;
+
+typedef struct
+{
+ // Name of the variable
+ char *name;
+
+ // Pointer to the location in memory of the variable
+ void *location;
+
+ // Type of the variable
+ default_type_t type;
+
+ // If this is a key value, the original integer scancode we read from
+ // the config file before translating it to the internal key value.
+ // If zero, we didn't read this value from a config file.
+ int untranslated;
+
+ // The value we translated the scancode into when we read the
+ // config file on startup. If the variable value is different from
+ // this, it has been changed and needs to be converted; otherwise,
+ // use the 'untranslated' value.
+ int original_translated;
+
+ // If true, this config variable has been bound to a variable
+ // and is being used.
+ boolean bound;
+} default_t;
+
+typedef struct
+{
+ default_t *defaults;
+ int numdefaults;
+ char *filename;
+} default_collection_t;
+
+#define CONFIG_VARIABLE_GENERIC(name, type) \
+ { #name, NULL, type, 0, 0, false }
+
+#define CONFIG_VARIABLE_KEY(name) \
+ CONFIG_VARIABLE_GENERIC(name, DEFAULT_KEY)
+#define CONFIG_VARIABLE_INT(name) \
+ CONFIG_VARIABLE_GENERIC(name, DEFAULT_INT)
+#define CONFIG_VARIABLE_INT_HEX(name) \
+ CONFIG_VARIABLE_GENERIC(name, DEFAULT_INT_HEX)
+#define CONFIG_VARIABLE_FLOAT(name) \
+ CONFIG_VARIABLE_GENERIC(name, DEFAULT_FLOAT)
+#define CONFIG_VARIABLE_STRING(name) \
+ CONFIG_VARIABLE_GENERIC(name, DEFAULT_STRING)
+
+//! @begin_config_file default
+
+static default_t doom_defaults_list[] =
+{
+ //!
+ // Mouse sensitivity. This value is used to multiply input mouse
+ // movement to control the effect of moving the mouse.
+ //
+ // The "normal" maximum value available for this through the
+ // in-game options menu is 9. A value of 31 or greater will cause
+ // the game to crash when entering the options menu.
+ //
+
+ CONFIG_VARIABLE_INT(mouse_sensitivity),
+
+ //!
+ // Volume of sound effects, range 0-15.
+ //
+
+ CONFIG_VARIABLE_INT(sfx_volume),
+
+ //!
+ // Volume of in-game music, range 0-15.
+ //
+
+ CONFIG_VARIABLE_INT(music_volume),
+
+ //!
+ // @game strife
+ //
+ // If non-zero, dialogue text is displayed over characters' pictures
+ // when engaging actors who have voices.
+ //
+
+ CONFIG_VARIABLE_INT(show_talk),
+
+ //!
+ // @game strife
+ //
+ // Volume of voice sound effects, range 0-15.
+ //
+
+ CONFIG_VARIABLE_INT(voice_volume),
+
+ //!
+ // @game doom
+ //
+ // If non-zero, messages are displayed on the heads-up display
+ // in the game ("picked up a clip", etc). If zero, these messages
+ // are not displayed.
+ //
+
+ CONFIG_VARIABLE_INT(show_messages),
+
+ //!
+ // Keyboard key to turn right.
+ //
+
+ CONFIG_VARIABLE_KEY(key_right),
+
+ //!
+ // Keyboard key to turn left.
+ //
+
+ CONFIG_VARIABLE_KEY(key_left),
+
+ //!
+ // Keyboard key to move forward.
+ //
+
+ CONFIG_VARIABLE_KEY(key_up),
+
+ //!
+ // Keyboard key to move backward.
+ //
+
+ CONFIG_VARIABLE_KEY(key_down),
+
+ //!
+ // Keyboard key to strafe left.
+ //
+
+ CONFIG_VARIABLE_KEY(key_strafeleft),
+
+ //!
+ // Keyboard key to strafe right.
+ //
+
+ CONFIG_VARIABLE_KEY(key_straferight),
+
+ //!
+ // @game strife
+ //
+ // Keyboard key to use health.
+ //
+
+ CONFIG_VARIABLE_KEY(key_useHealth),
+
+ //!
+ // @game hexen
+ //
+ // Keyboard key to jump.
+ //
+
+ CONFIG_VARIABLE_KEY(key_jump),
+
+ //!
+ // @game heretic hexen
+ //
+ // Keyboard key to fly upward.
+ //
+
+ CONFIG_VARIABLE_KEY(key_flyup),
+
+ //!
+ // @game heretic hexen
+ //
+ // Keyboard key to fly downwards.
+ //
+
+ CONFIG_VARIABLE_KEY(key_flydown),
+
+ //!
+ // @game heretic hexen
+ //
+ // Keyboard key to center flying.
+ //
+
+ CONFIG_VARIABLE_KEY(key_flycenter),
+
+ //!
+ // @game heretic hexen
+ //
+ // Keyboard key to look up.
+ //
+
+ CONFIG_VARIABLE_KEY(key_lookup),
+
+ //!
+ // @game heretic hexen
+ //
+ // Keyboard key to look down.
+ //
+
+ CONFIG_VARIABLE_KEY(key_lookdown),
+
+ //!
+ // @game heretic hexen
+ //
+ // Keyboard key to center the view.
+ //
+
+ CONFIG_VARIABLE_KEY(key_lookcenter),
+
+ //!
+ // @game strife
+ //
+ // Keyboard key to query inventory.
+ //
+
+ CONFIG_VARIABLE_KEY(key_invquery),
+
+ //!
+ // @game strife
+ //
+ // Keyboard key to display mission objective.
+ //
+
+ CONFIG_VARIABLE_KEY(key_mission),
+
+ //!
+ // @game strife
+ //
+ // Keyboard key to display inventory popup.
+ //
+
+ CONFIG_VARIABLE_KEY(key_invPop),
+
+ //!
+ // @game strife
+ //
+ // Keyboard key to display keys popup.
+ //
+
+ CONFIG_VARIABLE_KEY(key_invKey),
+
+ //!
+ // @game strife
+ //
+ // Keyboard key to jump to start of inventory.
+ //
+
+ CONFIG_VARIABLE_KEY(key_invHome),
+
+ //!
+ // @game strife
+ //
+ // Keyboard key to jump to end of inventory.
+ //
+
+ CONFIG_VARIABLE_KEY(key_invEnd),
+
+ //!
+ // @game heretic hexen
+ //
+ // Keyboard key to scroll left in the inventory.
+ //
+
+ CONFIG_VARIABLE_KEY(key_invleft),
+
+ //!
+ // @game heretic hexen
+ //
+ // Keyboard key to scroll right in the inventory.
+ //
+
+ CONFIG_VARIABLE_KEY(key_invright),
+
+ //!
+ // @game strife
+ //
+ // Keyboard key to scroll left in the inventory.
+ //
+
+ CONFIG_VARIABLE_KEY(key_invLeft),
+
+ //!
+ // @game strife
+ //
+ // Keyboard key to scroll right in the inventory.
+ //
+
+ CONFIG_VARIABLE_KEY(key_invRight),
+
+ //!
+ // @game heretic hexen
+ //
+ // Keyboard key to use the current item in the inventory.
+ //
+
+ CONFIG_VARIABLE_KEY(key_useartifact),
+
+ //!
+ // @game strife
+ //
+ // Keyboard key to use inventory item.
+ //
+
+ CONFIG_VARIABLE_KEY(key_invUse),
+
+ //!
+ // @game strife
+ //
+ // Keyboard key to drop an inventory item.
+ //
+
+ CONFIG_VARIABLE_KEY(key_invDrop),
+
+ //!
+ // @game strife
+ //
+ // Keyboard key to look up.
+ //
+
+ CONFIG_VARIABLE_KEY(key_lookUp),
+
+ //!
+ // @game strife
+ //
+ // Keyboard key to look down.
+ //
+
+ CONFIG_VARIABLE_KEY(key_lookDown),
+
+ //!
+ // Keyboard key to fire the currently selected weapon.
+ //
+
+ CONFIG_VARIABLE_KEY(key_fire),
+
+ //!
+ // Keyboard key to "use" an object, eg. a door or switch.
+ //
+
+ CONFIG_VARIABLE_KEY(key_use),
+
+ //!
+ // Keyboard key to turn on strafing. When held down, pressing the
+ // key to turn left or right causes the player to strafe left or
+ // right instead.
+ //
+
+ CONFIG_VARIABLE_KEY(key_strafe),
+
+ //!
+ // Keyboard key to make the player run.
+ //
+
+ CONFIG_VARIABLE_KEY(key_speed),
+
+ //!
+ // If non-zero, mouse input is enabled. If zero, mouse input is
+ // disabled.
+ //
+
+ CONFIG_VARIABLE_INT(use_mouse),
+
+ //!
+ // Mouse button to fire the currently selected weapon.
+ //
+
+ CONFIG_VARIABLE_INT(mouseb_fire),
+
+ //!
+ // Mouse button to turn on strafing. When held down, the player
+ // will strafe left and right instead of turning left and right.
+ //
+
+ CONFIG_VARIABLE_INT(mouseb_strafe),
+
+ //!
+ // Mouse button to move forward.
+ //
+
+ CONFIG_VARIABLE_INT(mouseb_forward),
+
+ //!
+ // @game hexen strife
+ //
+ // Mouse button to jump.
+ //
+
+ CONFIG_VARIABLE_INT(mouseb_jump),
+
+ //!
+ // If non-zero, joystick input is enabled.
+ //
+
+ CONFIG_VARIABLE_INT(use_joystick),
+
+ //!
+ // Joystick virtual button that fires the current weapon.
+ //
+
+ CONFIG_VARIABLE_INT(joyb_fire),
+
+ //!
+ // Joystick virtual button that makes the player strafe while
+ // held down.
+ //
+
+ CONFIG_VARIABLE_INT(joyb_strafe),
+
+ //!
+ // Joystick virtual button to "use" an object, eg. a door or switch.
+ //
+
+ CONFIG_VARIABLE_INT(joyb_use),
+
+ //!
+ // Joystick virtual button that makes the player run while held
+ // down.
+ //
+ // If this has a value of 20 or greater, the player will always run,
+ // even if use_joystick is 0.
+ //
+
+ CONFIG_VARIABLE_INT(joyb_speed),
+
+ //!
+ // @game hexen strife
+ //
+ // Joystick virtual button that makes the player jump.
+ //
+
+ CONFIG_VARIABLE_INT(joyb_jump),
+
+ //!
+ // @game doom heretic hexen
+ //
+ // Screen size, range 3-11.
+ //
+ // A value of 11 gives a full-screen view with the status bar not
+ // displayed. A value of 10 gives a full-screen view with the
+ // status bar displayed.
+ //
+
+ CONFIG_VARIABLE_INT(screenblocks),
+
+ //!
+ // @game strife
+ //
+ // Screen size, range 3-11.
+ //
+ // A value of 11 gives a full-screen view with the status bar not
+ // displayed. A value of 10 gives a full-screen view with the
+ // status bar displayed.
+ //
+
+ CONFIG_VARIABLE_INT(screensize),
+
+ //!
+ // @game doom
+ //
+ // Screen detail. Zero gives normal "high detail" mode, while
+ // a non-zero value gives "low detail" mode.
+ //
+
+ CONFIG_VARIABLE_INT(detaillevel),
+
+ //!
+ // Number of sounds that will be played simultaneously.
+ //
+
+ CONFIG_VARIABLE_INT(snd_channels),
+
+ //!
+ // Music output device. A non-zero value gives MIDI sound output,
+ // while a value of zero disables music.
+ //
+
+ CONFIG_VARIABLE_INT(snd_musicdevice),
+
+ //!
+ // Sound effects device. A value of zero disables in-game sound
+ // effects, a value of 1 enables PC speaker sound effects, while
+ // a value in the range 2-9 enables the "normal" digital sound
+ // effects.
+ //
+
+ CONFIG_VARIABLE_INT(snd_sfxdevice),
+
+ //!
+ // SoundBlaster I/O port. Unused.
+ //
+
+ CONFIG_VARIABLE_INT(snd_sbport),
+
+ //!
+ // SoundBlaster IRQ. Unused.
+ //
+
+ CONFIG_VARIABLE_INT(snd_sbirq),
+
+ //!
+ // SoundBlaster DMA channel. Unused.
+ //
+
+ CONFIG_VARIABLE_INT(snd_sbdma),
+
+ //!
+ // Output port to use for OPL MIDI playback. Unused.
+ //
+
+ CONFIG_VARIABLE_INT(snd_mport),
+
+ //!
+ // Gamma correction level. A value of zero disables gamma
+ // correction, while a value in the range 1-4 gives increasing
+ // levels of gamma correction.
+ //
+
+ CONFIG_VARIABLE_INT(usegamma),
+
+ //!
+ // @game hexen
+ //
+ // Directory in which to store savegames.
+ //
+
+ CONFIG_VARIABLE_STRING(savedir),
+
+ //!
+ // @game hexen
+ //
+ // Controls whether messages are displayed in the heads-up display.
+ // If this has a non-zero value, messages are displayed.
+ //
+
+ CONFIG_VARIABLE_INT(messageson),
+
+ //!
+ // @game strife
+ //
+ // Name of background flat used by view border.
+ //
+
+ CONFIG_VARIABLE_STRING(back_flat),
+
+ //!
+ // @game strife
+ //
+ // Multiplayer nickname (?).
+ //
+
+ CONFIG_VARIABLE_STRING(nickname),
+
+ //!
+ // Multiplayer chat macro: message to send when alt+0 is pressed.
+ //
+
+ CONFIG_VARIABLE_STRING(chatmacro0),
+
+ //!
+ // Multiplayer chat macro: message to send when alt+1 is pressed.
+ //
+
+ CONFIG_VARIABLE_STRING(chatmacro1),
+
+ //!
+ // Multiplayer chat macro: message to send when alt+2 is pressed.
+ //
+
+ CONFIG_VARIABLE_STRING(chatmacro2),
+
+ //!
+ // Multiplayer chat macro: message to send when alt+3 is pressed.
+ //
+
+ CONFIG_VARIABLE_STRING(chatmacro3),
+
+ //!
+ // Multiplayer chat macro: message to send when alt+4 is pressed.
+ //
+
+ CONFIG_VARIABLE_STRING(chatmacro4),
+
+ //!
+ // Multiplayer chat macro: message to send when alt+5 is pressed.
+ //
+
+ CONFIG_VARIABLE_STRING(chatmacro5),
+
+ //!
+ // Multiplayer chat macro: message to send when alt+6 is pressed.
+ //
+
+ CONFIG_VARIABLE_STRING(chatmacro6),
+
+ //!
+ // Multiplayer chat macro: message to send when alt+7 is pressed.
+ //
+
+ CONFIG_VARIABLE_STRING(chatmacro7),
+
+ //!
+ // Multiplayer chat macro: message to send when alt+8 is pressed.
+ //
+
+ CONFIG_VARIABLE_STRING(chatmacro8),
+
+ //!
+ // Multiplayer chat macro: message to send when alt+9 is pressed.
+ //
+
+ CONFIG_VARIABLE_STRING(chatmacro9),
+
+ //!
+ // @game strife
+ //
+ // Serial port number to use for SERSETUP.EXE (unused).
+ //
+
+ CONFIG_VARIABLE_INT(comport),
+};
+
+static default_collection_t doom_defaults =
+{
+ doom_defaults_list,
+ arrlen(doom_defaults_list),
+ NULL,
+};
+
+//! @begin_config_file extended
+
+static default_t extra_defaults_list[] =
+{
+ //!
+ // @game heretic hexen strife
+ //
+ // If non-zero, display the graphical startup screen.
+ //
+
+ CONFIG_VARIABLE_INT(graphical_startup),
+
+ //!
+ // If non-zero, video settings will be autoadjusted to a valid
+ // configuration when the screen_width and screen_height variables
+ // do not match any valid configuration.
+ //
+
+ CONFIG_VARIABLE_INT(autoadjust_video_settings),
+
+ //!
+ // If non-zero, the game will run in full screen mode. If zero,
+ // the game will run in a window.
+ //
+
+ CONFIG_VARIABLE_INT(fullscreen),
+
+ //!
+ // If non-zero, the screen will be stretched vertically to display
+ // correctly on a square pixel video mode.
+ //
+
+ CONFIG_VARIABLE_INT(aspect_ratio_correct),
+
+ //!
+ // Number of milliseconds to wait on startup after the video mode
+ // has been set, before the game will start. This allows the
+ // screen to settle on some monitors that do not display an image
+ // for a brief interval after changing video modes.
+ //
+
+ CONFIG_VARIABLE_INT(startup_delay),
+
+ //!
+ // Screen width in pixels. If running in full screen mode, this is
+ // the X dimension of the video mode to use. If running in
+ // windowed mode, this is the width of the window in which the game
+ // will run.
+ //
+
+ CONFIG_VARIABLE_INT(screen_width),
+
+ //!
+ // Screen height in pixels. If running in full screen mode, this is
+ // the Y dimension of the video mode to use. If running in
+ // windowed mode, this is the height of the window in which the game
+ // will run.
+ //
+
+ CONFIG_VARIABLE_INT(screen_height),
+
+ //!
+ // Color depth of the screen, in bits.
+ // If this is set to zero, the color depth will be automatically set
+ // on startup to the machine's default/native color depth.
+ //
+
+ CONFIG_VARIABLE_INT(screen_bpp),
+
+ //!
+ // If this is non-zero, the mouse will be "grabbed" when running
+ // in windowed mode so that it can be used as an input device.
+ // When running full screen, this has no effect.
+ //
+
+ CONFIG_VARIABLE_INT(grabmouse),
+
+ //!
+ // If non-zero, all vertical mouse movement is ignored. This
+ // emulates the behavior of the "novert" tool available under DOS
+ // that performs the same function.
+ //
+
+ CONFIG_VARIABLE_INT(novert),
+
+ //!
+ // Mouse acceleration factor. When the speed of mouse movement
+ // exceeds the threshold value (mouse_threshold), the speed is
+ // multiplied by this value.
+ //
+
+ CONFIG_VARIABLE_FLOAT(mouse_acceleration),
+
+ //!
+ // Mouse acceleration threshold. When the speed of mouse movement
+ // exceeds this threshold value, the speed is multiplied by an
+ // acceleration factor (mouse_acceleration).
+ //
+
+ CONFIG_VARIABLE_INT(mouse_threshold),
+
+ //!
+ // Sound output sample rate, in Hz. Typical values to use are
+ // 11025, 22050, 44100 and 48000.
+ //
+
+ CONFIG_VARIABLE_INT(snd_samplerate),
+
+ //!
+ // Maximum number of bytes to allocate for caching converted sound
+ // effects in memory. If set to zero, there is no limit applied.
+ //
+
+ CONFIG_VARIABLE_INT(snd_cachesize),
+
+ //!
+ // Maximum size of the output sound buffer size in milliseconds.
+ // Sound output is generated periodically in slices. Higher values
+ // might be more efficient but will introduce latency to the
+ // sound output. The default is 28ms (one slice per tic with the
+ // 35fps timer).
+
+ CONFIG_VARIABLE_INT(snd_maxslicetime_ms),
+
+ //!
+ // External command to invoke to perform MIDI playback. If set to
+ // the empty string, SDL_mixer's internal MIDI playback is used.
+ // This only has any effect when snd_musicdevice is set to General
+ // MIDI output.
+
+ CONFIG_VARIABLE_STRING(snd_musiccmd),
+
+ //!
+ // The I/O port to use to access the OPL chip. Only relevant when
+ // using native OPL music playback.
+ //
+
+ CONFIG_VARIABLE_INT_HEX(opl_io_port),
+
+ //!
+ // @game doom heretic strife
+ //
+ // If non-zero, the ENDOOM text screen is displayed when exiting the
+ // game. If zero, the ENDOOM screen is not displayed.
+ //
+
+ CONFIG_VARIABLE_INT(show_endoom),
+
+ //!
+ // If non-zero, save screenshots in PNG format.
+ //
+
+ CONFIG_VARIABLE_INT(png_screenshots),
+
+ //!
+ // @game doom strife
+ //
+ // If non-zero, the Vanilla savegame limit is enforced; if the
+ // savegame exceeds 180224 bytes in size, the game will exit with
+ // an error. If this has a value of zero, there is no limit to
+ // the size of savegames.
+ //
+
+ CONFIG_VARIABLE_INT(vanilla_savegame_limit),
+
+ //!
+ // @game doom strife
+ //
+ // If non-zero, the Vanilla demo size limit is enforced; the game
+ // exits with an error when a demo exceeds the demo size limit
+ // (128KiB by default). If this has a value of zero, there is no
+ // limit to the size of demos.
+ //
+
+ CONFIG_VARIABLE_INT(vanilla_demo_limit),
+
+ //!
+ // If non-zero, the game behaves like Vanilla Doom, always assuming
+ // an American keyboard mapping. If this has a value of zero, the
+ // native keyboard mapping of the keyboard is used.
+ //
+
+ CONFIG_VARIABLE_INT(vanilla_keyboard_mapping),
+
+ //!
+ // Name of the SDL video driver to use. If this is an empty string,
+ // the default video driver is used.
+ //
+
+ CONFIG_VARIABLE_STRING(video_driver),
+
+ //!
+ // Position of the window on the screen when running in windowed
+ // mode. Accepted values are: "" (empty string) - don't care,
+ // "center" - place window at center of screen, "x,y" - place
+ // window at the specified coordinates.
+
+ CONFIG_VARIABLE_STRING(window_position),
+
+#ifdef FEATURE_MULTIPLAYER
+
+ //!
+ // Name to use in network games for identification. This is only
+ // used on the "waiting" screen while waiting for the game to start.
+ //
+
+ CONFIG_VARIABLE_STRING(player_name),
+
+#endif
+
+ //!
+ // Joystick number to use; '0' is the first joystick. A negative
+ // value ('-1') indicates that no joystick is configured.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_index),
+
+ //!
+ // Joystick axis to use to for horizontal (X) movement.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_x_axis),
+
+ //!
+ // If non-zero, movement on the horizontal joystick axis is inverted.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_x_invert),
+
+ //!
+ // Joystick axis to use to for vertical (Y) movement.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_y_axis),
+
+ //!
+ // If non-zero, movement on the vertical joystick axis is inverted.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_y_invert),
+
+ //!
+ // Joystick axis to use to for strafing movement.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_strafe_axis),
+
+ //!
+ // If non-zero, movement on the joystick axis used for strafing
+ // is inverted.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_strafe_invert),
+
+ //!
+ // The physical joystick button that corresponds to joystick
+ // virtual button #0.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_physical_button0),
+
+ //!
+ // The physical joystick button that corresponds to joystick
+ // virtual button #1.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_physical_button1),
+
+ //!
+ // The physical joystick button that corresponds to joystick
+ // virtual button #2.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_physical_button2),
+
+ //!
+ // The physical joystick button that corresponds to joystick
+ // virtual button #3.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_physical_button3),
+
+ //!
+ // The physical joystick button that corresponds to joystick
+ // virtual button #4.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_physical_button4),
+
+ //!
+ // The physical joystick button that corresponds to joystick
+ // virtual button #5.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_physical_button5),
+
+ //!
+ // The physical joystick button that corresponds to joystick
+ // virtual button #6.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_physical_button6),
+
+ //!
+ // The physical joystick button that corresponds to joystick
+ // virtual button #7.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_physical_button7),
+
+ //!
+ // The physical joystick button that corresponds to joystick
+ // virtual button #8.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_physical_button8),
+
+ //!
+ // The physical joystick button that corresponds to joystick
+ // virtual button #9.
+ //
+
+ CONFIG_VARIABLE_INT(joystick_physical_button9),
+
+ //!
+ // Joystick virtual button to make the player strafe left.
+ //
+
+ CONFIG_VARIABLE_INT(joyb_strafeleft),
+
+ //!
+ // Joystick virtual button to make the player strafe right.
+ //
+
+ CONFIG_VARIABLE_INT(joyb_straferight),
+
+ //!
+ // Joystick virtual button to activate the menu.
+ //
+
+ CONFIG_VARIABLE_INT(joyb_menu_activate),
+
+ //!
+ // Joystick virtual button that cycles to the previous weapon.
+ //
+
+ CONFIG_VARIABLE_INT(joyb_prevweapon),
+
+ //!
+ // Joystick virtual button that cycles to the next weapon.
+ //
+
+ CONFIG_VARIABLE_INT(joyb_nextweapon),
+
+ //!
+ // Mouse button to strafe left.
+ //
+
+ CONFIG_VARIABLE_INT(mouseb_strafeleft),
+
+ //!
+ // Mouse button to strafe right.
+ //
+
+ CONFIG_VARIABLE_INT(mouseb_straferight),
+
+ //!
+ // Mouse button to "use" an object, eg. a door or switch.
+ //
+
+ CONFIG_VARIABLE_INT(mouseb_use),
+
+ //!
+ // Mouse button to move backwards.
+ //
+
+ CONFIG_VARIABLE_INT(mouseb_backward),
+
+ //!
+ // Mouse button to cycle to the previous weapon.
+ //
+
+ CONFIG_VARIABLE_INT(mouseb_prevweapon),
+
+ //!
+ // Mouse button to cycle to the next weapon.
+ //
+
+ CONFIG_VARIABLE_INT(mouseb_nextweapon),
+
+ //!
+ // If non-zero, double-clicking a mouse button acts like pressing
+ // the "use" key to use an object in-game, eg. a door or switch.
+ //
+
+ CONFIG_VARIABLE_INT(dclick_use),
+
+#ifdef FEATURE_SOUND
+
+ //!
+ // Controls whether libsamplerate support is used for performing
+ // sample rate conversions of sound effects. Support for this
+ // must be compiled into the program.
+ //
+ // If zero, libsamplerate support is disabled. If non-zero,
+ // libsamplerate is enabled. Increasing values roughly correspond
+ // to higher quality conversion; the higher the quality, the
+ // slower the conversion process. Linear conversion = 1;
+ // Zero order hold = 2; Fast Sinc filter = 3; Medium quality
+ // Sinc filter = 4; High quality Sinc filter = 5.
+ //
+
+ CONFIG_VARIABLE_INT(use_libsamplerate),
+
+ //!
+ // Scaling factor used by libsamplerate. This is used when converting
+ // sounds internally back into integer form; normally it should not
+ // be necessary to change it from the default value. The only time
+ // it might be needed is if a PWAD file is loaded that contains very
+ // loud sounds, in which case the conversion may cause sound clipping
+ // and the scale factor should be reduced. The lower the value, the
+ // quieter the sound effects become, so it should be set as high as is
+ // possible without clipping occurring.
+
+ CONFIG_VARIABLE_FLOAT(libsamplerate_scale),
+
+ //!
+ // Full path to a Timidity configuration file to use for MIDI
+ // playback. The file will be evaluated from the directory where
+ // it is evaluated, so there is no need to add "dir" commands
+ // into it.
+ //
+
+ CONFIG_VARIABLE_STRING(timidity_cfg_path),
+
+ //!
+ // Path to GUS patch files to use when operating in GUS emulation
+ // mode.
+ //
+
+ CONFIG_VARIABLE_STRING(gus_patch_path),
+
+ //!
+ // Number of kilobytes of RAM to use in GUS emulation mode. Valid
+ // values are 256, 512, 768 or 1024.
+ //
+
+ CONFIG_VARIABLE_INT(gus_ram_kb),
+
+#endif
+
+ //!
+ // Key to pause or unpause the game.
+ //
+
+ CONFIG_VARIABLE_KEY(key_pause),
+
+ //!
+ // Key that activates the menu when pressed.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_activate),
+
+ //!
+ // Key that moves the cursor up on the menu.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_up),
+
+ //!
+ // Key that moves the cursor down on the menu.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_down),
+
+ //!
+ // Key that moves the currently selected slider on the menu left.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_left),
+
+ //!
+ // Key that moves the currently selected slider on the menu right.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_right),
+
+ //!
+ // Key to go back to the previous menu.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_back),
+
+ //!
+ // Key to activate the currently selected menu item.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_forward),
+
+ //!
+ // Key to answer 'yes' to a question in the menu.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_confirm),
+
+ //!
+ // Key to answer 'no' to a question in the menu.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_abort),
+
+ //!
+ // Keyboard shortcut to bring up the help screen.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_help),
+
+ //!
+ // Keyboard shortcut to bring up the save game menu.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_save),
+
+ //!
+ // Keyboard shortcut to bring up the load game menu.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_load),
+
+ //!
+ // Keyboard shortcut to bring up the sound volume menu.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_volume),
+
+ //!
+ // Keyboard shortcut to toggle the detail level.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_detail),
+
+ //!
+ // Keyboard shortcut to quicksave the current game.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_qsave),
+
+ //!
+ // Keyboard shortcut to end the game.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_endgame),
+
+ //!
+ // Keyboard shortcut to toggle heads-up messages.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_messages),
+
+ //!
+ // Keyboard shortcut to load the last quicksave.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_qload),
+
+ //!
+ // Keyboard shortcut to quit the game.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_quit),
+
+ //!
+ // Keyboard shortcut to toggle the gamma correction level.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_gamma),
+
+ //!
+ // Keyboard shortcut to switch view in multiplayer.
+ //
+
+ CONFIG_VARIABLE_KEY(key_spy),
+
+ //!
+ // Keyboard shortcut to increase the screen size.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_incscreen),
+
+ //!
+ // Keyboard shortcut to decrease the screen size.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_decscreen),
+
+ //!
+ // Keyboard shortcut to save a screenshot.
+ //
+
+ CONFIG_VARIABLE_KEY(key_menu_screenshot),
+
+ //!
+ // Key to toggle the map view.
+ //
+
+ CONFIG_VARIABLE_KEY(key_map_toggle),
+
+ //!
+ // Key to pan north when in the map view.
+ //
+
+ CONFIG_VARIABLE_KEY(key_map_north),
+
+ //!
+ // Key to pan south when in the map view.
+ //
+
+ CONFIG_VARIABLE_KEY(key_map_south),
+
+ //!
+ // Key to pan east when in the map view.
+ //
+
+ CONFIG_VARIABLE_KEY(key_map_east),
+
+ //!
+ // Key to pan west when in the map view.
+ //
+
+ CONFIG_VARIABLE_KEY(key_map_west),
+
+ //!
+ // Key to zoom in when in the map view.
+ //
+
+ CONFIG_VARIABLE_KEY(key_map_zoomin),
+
+ //!
+ // Key to zoom out when in the map view.
+ //
+
+ CONFIG_VARIABLE_KEY(key_map_zoomout),
+
+ //!
+ // Key to zoom out the maximum amount when in the map view.
+ //
+
+ CONFIG_VARIABLE_KEY(key_map_maxzoom),
+
+ //!
+ // Key to toggle follow mode when in the map view.
+ //
+
+ CONFIG_VARIABLE_KEY(key_map_follow),
+
+ //!
+ // Key to toggle the grid display when in the map view.
+ //
+
+ CONFIG_VARIABLE_KEY(key_map_grid),
+
+ //!
+ // Key to set a mark when in the map view.
+ //
+
+ CONFIG_VARIABLE_KEY(key_map_mark),
+
+ //!
+ // Key to clear all marks when in the map view.
+ //
+
+ CONFIG_VARIABLE_KEY(key_map_clearmark),
+
+ //!
+ // Key to select weapon 1.
+ //
+
+ CONFIG_VARIABLE_KEY(key_weapon1),
+
+ //!
+ // Key to select weapon 2.
+ //
+
+ CONFIG_VARIABLE_KEY(key_weapon2),
+
+ //!
+ // Key to select weapon 3.
+ //
+
+ CONFIG_VARIABLE_KEY(key_weapon3),
+
+ //!
+ // Key to select weapon 4.
+ //
+
+ CONFIG_VARIABLE_KEY(key_weapon4),
+
+ //!
+ // Key to select weapon 5.
+ //
+
+ CONFIG_VARIABLE_KEY(key_weapon5),
+
+ //!
+ // Key to select weapon 6.
+ //
+
+ CONFIG_VARIABLE_KEY(key_weapon6),
+
+ //!
+ // Key to select weapon 7.
+ //
+
+ CONFIG_VARIABLE_KEY(key_weapon7),
+
+ //!
+ // Key to select weapon 8.
+ //
+
+ CONFIG_VARIABLE_KEY(key_weapon8),
+
+ //!
+ // Key to cycle to the previous weapon.
+ //
+
+ CONFIG_VARIABLE_KEY(key_prevweapon),
+
+ //!
+ // Key to cycle to the next weapon.
+ //
+
+ CONFIG_VARIABLE_KEY(key_nextweapon),
+
+ //!
+ // @game hexen
+ //
+ // Key to use one of each artifact.
+ //
+
+ CONFIG_VARIABLE_KEY(key_arti_all),
+
+ //!
+ // @game hexen
+ //
+ // Key to use "quartz flask" artifact.
+ //
+
+ CONFIG_VARIABLE_KEY(key_arti_health),
+
+ //!
+ // @game hexen
+ //
+ // Key to use "flechette" artifact.
+ //
+
+ CONFIG_VARIABLE_KEY(key_arti_poisonbag),
+
+ //!
+ // @game hexen
+ //
+ // Key to use "disc of repulsion" artifact.
+ //
+
+ CONFIG_VARIABLE_KEY(key_arti_blastradius),
+
+ //!
+ // @game hexen
+ //
+ // Key to use "chaos device" artifact.
+ //
+
+ CONFIG_VARIABLE_KEY(key_arti_teleport),
+
+ //!
+ // @game hexen
+ //
+ // Key to use "banishment device" artifact.
+ //
+
+ CONFIG_VARIABLE_KEY(key_arti_teleportother),
+
+ //!
+ // @game hexen
+ //
+ // Key to use "porkalator" artifact.
+ //
+
+ CONFIG_VARIABLE_KEY(key_arti_egg),
+
+ //!
+ // @game hexen
+ //
+ // Key to use "icon of the defender" artifact.
+ //
+
+ CONFIG_VARIABLE_KEY(key_arti_invulnerability),
+
+ //!
+ // Key to re-display last message.
+ //
+
+ CONFIG_VARIABLE_KEY(key_message_refresh),
+
+ //!
+ // Key to quit the game when recording a demo.
+ //
+
+ CONFIG_VARIABLE_KEY(key_demo_quit),
+
+ //!
+ // Key to send a message during multiplayer games.
+ //
+
+ CONFIG_VARIABLE_KEY(key_multi_msg),
+
+ //!
+ // Key to send a message to player 1 during multiplayer games.
+ //
+
+ CONFIG_VARIABLE_KEY(key_multi_msgplayer1),
+
+ //!
+ // Key to send a message to player 2 during multiplayer games.
+ //
+
+ CONFIG_VARIABLE_KEY(key_multi_msgplayer2),
+
+ //!
+ // Key to send a message to player 3 during multiplayer games.
+ //
+
+ CONFIG_VARIABLE_KEY(key_multi_msgplayer3),
+
+ //!
+ // Key to send a message to player 4 during multiplayer games.
+ //
+
+ CONFIG_VARIABLE_KEY(key_multi_msgplayer4),
+
+ //!
+ // @game hexen strife
+ //
+ // Key to send a message to player 5 during multiplayer games.
+ //
+
+ CONFIG_VARIABLE_KEY(key_multi_msgplayer5),
+
+ //!
+ // @game hexen strife
+ //
+ // Key to send a message to player 6 during multiplayer games.
+ //
+
+ CONFIG_VARIABLE_KEY(key_multi_msgplayer6),
+
+ //!
+ // @game hexen strife
+ //
+ // Key to send a message to player 7 during multiplayer games.
+ //
+
+ CONFIG_VARIABLE_KEY(key_multi_msgplayer7),
+
+ //!
+ // @game hexen strife
+ //
+ // Key to send a message to player 8 during multiplayer games.
+ //
+
+ CONFIG_VARIABLE_KEY(key_multi_msgplayer8),
+};
+
+static default_collection_t extra_defaults =
+{
+ extra_defaults_list,
+ arrlen(extra_defaults_list),
+ NULL,
+};
+
+// Search a collection for a variable
+
+static default_t *SearchCollection(default_collection_t *collection, char *name)
+{
+ int i;
+
+ for (i=0; inumdefaults; ++i)
+ {
+ if (!strcmp(name, collection->defaults[i].name))
+ {
+ return &collection->defaults[i];
+ }
+ }
+
+ return NULL;
+}
+
+// Mapping from DOS keyboard scan code to internal key code (as defined
+// in doomkey.h). I think I (fraggle) reused this from somewhere else
+// but I can't find where. Anyway, notes:
+// * KEY_PAUSE is wrong - it's in the KEY_NUMLOCK spot. This shouldn't
+// matter in terms of Vanilla compatibility because neither of
+// those were valid for key bindings.
+// * There is no proper scan code for PrintScreen (on DOS machines it
+// sends an interrupt). So I added a fake scan code of 126 for it.
+// The presence of this is important so we can bind PrintScreen as
+// a screenshot key.
+static const int scantokey[128] =
+{
+ 0 , 27, '1', '2', '3', '4', '5', '6',
+ '7', '8', '9', '0', '-', '=', KEY_BACKSPACE, 9,
+ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i',
+ 'o', 'p', '[', ']', 13, KEY_RCTRL, 'a', 's',
+ 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',
+ '\'', '`', KEY_RSHIFT,'\\', 'z', 'x', 'c', 'v',
+ 'b', 'n', 'm', ',', '.', '/', KEY_RSHIFT,KEYP_MULTIPLY,
+ KEY_RALT, ' ', KEY_CAPSLOCK,KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5,
+ KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, /*KEY_NUMLOCK?*/KEY_PAUSE,KEY_SCRLCK,KEY_HOME,
+ KEY_UPARROW,KEY_PGUP,KEY_MINUS,KEY_LEFTARROW,KEYP_5,KEY_RIGHTARROW,KEYP_PLUS,KEY_END,
+ KEY_DOWNARROW,KEY_PGDN,KEY_INS,KEY_DEL,0, 0, 0, KEY_F11,
+ KEY_F12, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, KEY_PRTSCR, 0
+};
+
+
+static void SaveDefaultCollection(default_collection_t *collection)
+{
+#if ORIGCODE
+ default_t *defaults;
+ int i, v;
+ FILE *f;
+
+ f = fopen (collection->filename, "w");
+ if (!f)
+ return; // can't write the file, but don't complain
+
+ defaults = collection->defaults;
+
+ for (i=0 ; inumdefaults ; i++)
+ {
+ int chars_written;
+
+ // Ignore unbound variables
+
+ if (!defaults[i].bound)
+ {
+ continue;
+ }
+
+ // Print the name and line up all values at 30 characters
+
+ chars_written = fprintf(f, "%s ", defaults[i].name);
+
+ for (; chars_written < 30; ++chars_written)
+ fprintf(f, " ");
+
+ // Print the value
+
+ switch (defaults[i].type)
+ {
+ case DEFAULT_KEY:
+
+ // use the untranslated version if we can, to reduce
+ // the possibility of screwing up the user's config
+ // file
+
+ v = * (int *) defaults[i].location;
+
+ if (v == KEY_RSHIFT)
+ {
+ // Special case: for shift, force scan code for
+ // right shift, as this is what Vanilla uses.
+ // This overrides the change check below, to fix
+ // configuration files made by old versions that
+ // mistakenly used the scan code for left shift.
+
+ v = 54;
+ }
+ else if (defaults[i].untranslated
+ && v == defaults[i].original_translated)
+ {
+ // Has not been changed since the last time we
+ // read the config file.
+
+ v = defaults[i].untranslated;
+ }
+ else
+ {
+ // search for a reverse mapping back to a scancode
+ // in the scantokey table
+
+ int s;
+
+ for (s=0; s<128; ++s)
+ {
+ if (scantokey[s] == v)
+ {
+ v = s;
+ break;
+ }
+ }
+ }
+
+ fprintf(f, "%i", v);
+ break;
+
+ case DEFAULT_INT:
+ fprintf(f, "%i", * (int *) defaults[i].location);
+ break;
+
+ case DEFAULT_INT_HEX:
+ fprintf(f, "0x%x", * (int *) defaults[i].location);
+ break;
+
+ case DEFAULT_FLOAT:
+ fprintf(f, "%f", * (float *) defaults[i].location);
+ break;
+
+ case DEFAULT_STRING:
+ fprintf(f,"\"%s\"", * (char **) (defaults[i].location));
+ break;
+ }
+
+ fprintf(f, "\n");
+ }
+
+ fclose (f);
+#endif
+}
+
+// Parses integer values in the configuration file
+
+static int ParseIntParameter(char *strparm)
+{
+ int parm;
+
+ if (strparm[0] == '0' && strparm[1] == 'x')
+ sscanf(strparm+2, "%x", &parm);
+ else
+ sscanf(strparm, "%i", &parm);
+
+ return parm;
+}
+
+static void SetVariable(default_t *def, char *value)
+{
+ int intparm;
+
+ // parameter found
+
+ switch (def->type)
+ {
+ case DEFAULT_STRING:
+ * (char **) def->location = strdup(value);
+ break;
+
+ case DEFAULT_INT:
+ case DEFAULT_INT_HEX:
+ * (int *) def->location = ParseIntParameter(value);
+ break;
+
+ case DEFAULT_KEY:
+
+ // translate scancodes read from config
+ // file (save the old value in untranslated)
+
+ intparm = ParseIntParameter(value);
+ def->untranslated = intparm;
+ if (intparm >= 0 && intparm < 128)
+ {
+ intparm = scantokey[intparm];
+ }
+ else
+ {
+ intparm = 0;
+ }
+
+ def->original_translated = intparm;
+ * (int *) def->location = intparm;
+ break;
+
+ case DEFAULT_FLOAT:
+ * (float *) def->location = (float) atof(value);
+ break;
+ }
+}
+
+static void LoadDefaultCollection(default_collection_t *collection)
+{
+#if ORIGCODE
+ FILE *f;
+ default_t *def;
+ char defname[80];
+ char strparm[100];
+
+ // read the file in, overriding any set defaults
+ f = fopen(collection->filename, "r");
+
+ if (f == NULL)
+ {
+ // File not opened, but don't complain.
+ // It's probably just the first time they ran the game.
+
+ return;
+ }
+
+ while (!feof(f))
+ {
+ if (fscanf(f, "%79s %99[^\n]\n", defname, strparm) != 2)
+ {
+ // This line doesn't match
+
+ continue;
+ }
+
+ // Find the setting in the list
+
+ def = SearchCollection(collection, defname);
+
+ if (def == NULL || !def->bound)
+ {
+ // Unknown variable? Unbound variables are also treated
+ // as unknown.
+
+ continue;
+ }
+
+ // Strip off trailing non-printable characters (\r characters
+ // from DOS text files)
+
+ while (strlen(strparm) > 0 && !isprint(strparm[strlen(strparm)-1]))
+ {
+ strparm[strlen(strparm)-1] = '\0';
+ }
+
+ // Surrounded by quotes? If so, remove them.
+ if (strlen(strparm) >= 2
+ && strparm[0] == '"' && strparm[strlen(strparm) - 1] == '"')
+ {
+ strparm[strlen(strparm) - 1] = '\0';
+ memmove(strparm, strparm + 1, sizeof(strparm) - 1);
+ }
+
+ SetVariable(def, strparm);
+ }
+
+ fclose (f);
+#endif
+}
+
+// Set the default filenames to use for configuration files.
+
+void M_SetConfigFilenames(char *main_config, char *extra_config)
+{
+ default_main_config = main_config;
+ default_extra_config = extra_config;
+}
+
+//
+// M_SaveDefaults
+//
+
+void M_SaveDefaults (void)
+{
+ SaveDefaultCollection(&doom_defaults);
+ SaveDefaultCollection(&extra_defaults);
+}
+
+//
+// Save defaults to alternate filenames
+//
+
+void M_SaveDefaultsAlternate(char *main, char *extra)
+{
+ char *orig_main;
+ char *orig_extra;
+
+ // Temporarily change the filenames
+
+ orig_main = doom_defaults.filename;
+ orig_extra = extra_defaults.filename;
+
+ doom_defaults.filename = main;
+ extra_defaults.filename = extra;
+
+ M_SaveDefaults();
+
+ // Restore normal filenames
+
+ doom_defaults.filename = orig_main;
+ extra_defaults.filename = orig_extra;
+}
+
+//
+// M_LoadDefaults
+//
+
+void M_LoadDefaults (void)
+{
+ int i;
+
+ // check for a custom default file
+
+ //!
+ // @arg
+ // @vanilla
+ //
+ // Load main configuration from the specified file, instead of the
+ // default.
+ //
+
+ i = M_CheckParmWithArgs("-config", 1);
+
+ if (i)
+ {
+ doom_defaults.filename = myargv[i+1];
+ printf (" default file: %s\n",doom_defaults.filename);
+ }
+ else
+ {
+ doom_defaults.filename
+ = M_StringJoin(configdir, default_main_config, NULL);
+ }
+
+ printf("saving config in %s\n", doom_defaults.filename);
+
+ //!
+ // @arg
+ //
+ // Load additional configuration from the specified file, instead of
+ // the default.
+ //
+
+ i = M_CheckParmWithArgs("-extraconfig", 1);
+
+ if (i)
+ {
+ extra_defaults.filename = myargv[i+1];
+ printf(" extra configuration file: %s\n",
+ extra_defaults.filename);
+ }
+ else
+ {
+ extra_defaults.filename
+ = M_StringJoin(configdir, default_extra_config, NULL);
+ }
+
+ LoadDefaultCollection(&doom_defaults);
+ LoadDefaultCollection(&extra_defaults);
+}
+
+// Get a configuration file variable by its name
+
+static default_t *GetDefaultForName(char *name)
+{
+ default_t *result;
+
+ // Try the main list and the extras
+
+ result = SearchCollection(&doom_defaults, name);
+
+ if (result == NULL)
+ {
+ result = SearchCollection(&extra_defaults, name);
+ }
+
+ // Not found? Internal error.
+
+ if (result == NULL)
+ {
+ I_Error("Unknown configuration variable: '%s'", name);
+ }
+
+ return result;
+}
+
+//
+// Bind a variable to a given configuration file variable, by name.
+//
+
+void M_BindVariable(char *name, void *location)
+{
+ default_t *variable;
+
+ variable = GetDefaultForName(name);
+
+ variable->location = location;
+ variable->bound = true;
+}
+
+// Set the value of a particular variable; an API function for other
+// parts of the program to assign values to config variables by name.
+
+boolean M_SetVariable(char *name, char *value)
+{
+ default_t *variable;
+
+ variable = GetDefaultForName(name);
+
+ if (variable == NULL || !variable->bound)
+ {
+ return false;
+ }
+
+ SetVariable(variable, value);
+
+ return true;
+}
+
+// Get the value of a variable.
+
+int M_GetIntVariable(char *name)
+{
+ default_t *variable;
+
+ variable = GetDefaultForName(name);
+
+ if (variable == NULL || !variable->bound
+ || (variable->type != DEFAULT_INT && variable->type != DEFAULT_INT_HEX))
+ {
+ return 0;
+ }
+
+ return *((int *) variable->location);
+}
+
+const char *M_GetStrVariable(char *name)
+{
+ default_t *variable;
+
+ variable = GetDefaultForName(name);
+
+ if (variable == NULL || !variable->bound
+ || variable->type != DEFAULT_STRING)
+ {
+ return NULL;
+ }
+
+ return *((const char **) variable->location);
+}
+
+float M_GetFloatVariable(char *name)
+{
+ default_t *variable;
+
+ variable = GetDefaultForName(name);
+
+ if (variable == NULL || !variable->bound
+ || variable->type != DEFAULT_FLOAT)
+ {
+ return 0;
+ }
+
+ return *((float *) variable->location);
+}
+
+// Get the path to the default configuration dir to use, if NULL
+// is passed to M_SetConfigDir.
+
+static char *GetDefaultConfigDir(void)
+{
+ char *result = (char *)malloc(2);
+ result[0] = '.';
+ result[1] = '\0';
+
+ return result;
+}
+
+//
+// SetConfigDir:
+//
+// Sets the location of the configuration directory, where configuration
+// files are stored - default.cfg, chocolate-doom.cfg, savegames, etc.
+//
+
+void M_SetConfigDir(char *dir)
+{
+ // Use the directory that was passed, or find the default.
+
+ if (dir != NULL)
+ {
+ configdir = dir;
+ }
+ else
+ {
+ configdir = GetDefaultConfigDir();
+ }
+
+ if (strcmp(configdir, "") != 0)
+ {
+ printf("Using %s for configuration and saves\n", configdir);
+ }
+
+ // Make the directory if it doesn't already exist:
+
+ M_MakeDirectory(configdir);
+}
+
+//
+// Calculate the path to the directory to use to store save games.
+// Creates the directory as necessary.
+//
+
+char *M_GetSaveGameDir(char *iwadname)
+{
+ char *savegamedir;
+#if ORIGCODE
+ char *topdir;
+#endif
+
+ // If not "doing" a configuration directory (Windows), don't "do"
+ // a savegame directory, either.
+
+ if (!strcmp(configdir, ""))
+ {
+ savegamedir = strdup("");
+ }
+ else
+ {
+#if ORIGCODE
+ // ~/.chocolate-doom/savegames
+
+ topdir = M_StringJoin(configdir, "savegame", NULL);
+ M_MakeDirectory(topdir);
+
+ // eg. ~/.chocolate-doom/savegames/doom2.wad/
+
+ savegamedir = M_StringJoin(topdir, DIR_SEPARATOR_S, iwadname,
+ DIR_SEPARATOR_S, NULL);
+
+ M_MakeDirectory(savegamedir);
+
+ free(topdir);
+#else
+ savegamedir = M_StringJoin(configdir, DIR_SEPARATOR_S, ".savegame/", NULL);
+
+ M_MakeDirectory(savegamedir);
+
+ printf ("Using %s for savegames\n", savegamedir);
+#endif
+ }
+
+ return savegamedir;
+}
+
diff --git a/firmware_p4/components/Applications/doom/m_config.h b/firmware_p4/components/Applications/doom/m_config.h
new file mode 100644
index 000000000..a17de2b45
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/m_config.h
@@ -0,0 +1,39 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Configuration file interface.
+//
+
+
+#ifndef __M_CONFIG__
+#define __M_CONFIG__
+
+#include "doomtype.h"
+
+void M_LoadDefaults(void);
+void M_SaveDefaults(void);
+void M_SaveDefaultsAlternate(char *main, char *extra);
+void M_SetConfigDir(char *dir);
+void M_BindVariable(char *name, void *variable);
+boolean M_SetVariable(char *name, char *value);
+int M_GetIntVariable(char *name);
+const char *M_GetStrVariable(char *name);
+float M_GetFloatVariable(char *name);
+void M_SetConfigFilenames(char *main_config, char *extra_config);
+char *M_GetSaveGameDir(char *iwadname);
+
+extern char *configdir;
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/m_controls.c b/firmware_p4/components/Applications/doom/m_controls.c
new file mode 100644
index 000000000..f0ffa0157
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/m_controls.c
@@ -0,0 +1,398 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+
+#include
+
+#include "doomtype.h"
+#include "doomkeys.h"
+
+#include "m_config.h"
+#include "m_misc.h"
+
+//
+// Keyboard controls
+//
+
+int key_right = KEY_RIGHTARROW;
+int key_left = KEY_LEFTARROW;
+int key_up = KEY_UPARROW;
+int key_down = KEY_DOWNARROW;
+int key_strafeleft = KEY_STRAFE_L;
+int key_straferight = KEY_STRAFE_R;
+int key_fire = KEY_FIRE;
+int key_use = KEY_USE;
+int key_strafe = KEY_RALT;
+int key_speed = KEY_RSHIFT;
+
+//
+// Heretic keyboard controls
+//
+
+int key_flyup = KEY_PGUP;
+int key_flydown = KEY_INS;
+int key_flycenter = KEY_HOME;
+
+int key_lookup = KEY_PGDN;
+int key_lookdown = KEY_DEL;
+int key_lookcenter = KEY_END;
+
+int key_invleft = '[';
+int key_invright = ']';
+int key_useartifact = KEY_ENTER;
+
+//
+// Hexen key controls
+//
+
+int key_jump = '/';
+
+int key_arti_all = KEY_BACKSPACE;
+int key_arti_health = '\\';
+int key_arti_poisonbag = '0';
+int key_arti_blastradius = '9';
+int key_arti_teleport = '8';
+int key_arti_teleportother = '7';
+int key_arti_egg = '6';
+int key_arti_invulnerability = '5';
+
+//
+// Strife key controls
+//
+// haleyjd 09/01/10
+//
+
+// Note: Strife also uses key_invleft, key_invright, key_jump, key_lookup, and
+// key_lookdown, but with different default values.
+
+int key_usehealth = 'h';
+int key_invquery = 'q';
+int key_mission = 'w';
+int key_invpop = 'z';
+int key_invkey = 'k';
+int key_invhome = KEY_HOME;
+int key_invend = KEY_END;
+int key_invuse = KEY_ENTER;
+int key_invdrop = KEY_BACKSPACE;
+
+
+//
+// Mouse controls
+//
+
+int mousebfire = 0;
+int mousebstrafe = 1;
+int mousebforward = 2;
+
+int mousebjump = -1;
+
+int mousebstrafeleft = -1;
+int mousebstraferight = -1;
+int mousebbackward = -1;
+int mousebuse = -1;
+
+int mousebprevweapon = -1;
+int mousebnextweapon = -1;
+
+
+int key_message_refresh = KEY_ENTER;
+int key_pause = KEY_PAUSE;
+int key_demo_quit = 'q';
+int key_spy = KEY_F12;
+
+// Multiplayer chat keys:
+
+int key_multi_msg = 't';
+int key_multi_msgplayer[8];
+
+// Weapon selection keys:
+
+int key_weapon1 = '1';
+int key_weapon2 = '2';
+int key_weapon3 = '3';
+int key_weapon4 = '4';
+int key_weapon5 = '5';
+int key_weapon6 = '6';
+int key_weapon7 = '7';
+int key_weapon8 = '8';
+int key_prevweapon = 0;
+int key_nextweapon = 0;
+
+// Map control keys:
+
+int key_map_north = KEY_UPARROW;
+int key_map_south = KEY_DOWNARROW;
+int key_map_east = KEY_RIGHTARROW;
+int key_map_west = KEY_LEFTARROW;
+int key_map_zoomin = '=';
+int key_map_zoomout = '-';
+int key_map_toggle = KEY_TAB;
+int key_map_maxzoom = '0';
+int key_map_follow = 'f';
+int key_map_grid = 'g';
+int key_map_mark = 'm';
+int key_map_clearmark = 'c';
+
+// menu keys:
+
+int key_menu_activate = KEY_ESCAPE;
+int key_menu_up = KEY_UPARROW;
+int key_menu_down = KEY_DOWNARROW;
+int key_menu_left = KEY_LEFTARROW;
+int key_menu_right = KEY_RIGHTARROW;
+int key_menu_back = KEY_BACKSPACE;
+int key_menu_forward = KEY_ENTER;
+int key_menu_confirm = 'y';
+int key_menu_abort = 'n';
+
+int key_menu_help = KEY_F1;
+int key_menu_save = KEY_F2;
+int key_menu_load = KEY_F3;
+int key_menu_volume = KEY_F4;
+int key_menu_detail = KEY_F5;
+int key_menu_qsave = KEY_F6;
+int key_menu_endgame = KEY_F7;
+int key_menu_messages = KEY_F8;
+int key_menu_qload = KEY_F9;
+int key_menu_quit = KEY_F10;
+int key_menu_gamma = KEY_F11;
+
+int key_menu_incscreen = KEY_EQUALS;
+int key_menu_decscreen = KEY_MINUS;
+int key_menu_screenshot = 0;
+
+//
+// Joystick controls
+//
+
+int joybfire = 0;
+int joybstrafe = 1;
+int joybuse = 3;
+int joybspeed = 2;
+
+int joybstrafeleft = -1;
+int joybstraferight = -1;
+
+int joybjump = -1;
+
+int joybprevweapon = -1;
+int joybnextweapon = -1;
+
+int joybmenu = -1;
+
+// Control whether if a mouse button is double clicked, it acts like
+// "use" has been pressed
+
+int dclick_use = 1;
+
+//
+// Bind all of the common controls used by Doom and all other games.
+//
+
+void M_BindBaseControls(void)
+{
+ M_BindVariable("key_right", &key_right);
+ M_BindVariable("key_left", &key_left);
+ M_BindVariable("key_up", &key_up);
+ M_BindVariable("key_down", &key_down);
+ M_BindVariable("key_strafeleft", &key_strafeleft);
+ M_BindVariable("key_straferight", &key_straferight);
+ M_BindVariable("key_fire", &key_fire);
+ M_BindVariable("key_use", &key_use);
+ M_BindVariable("key_strafe", &key_strafe);
+ M_BindVariable("key_speed", &key_speed);
+
+ M_BindVariable("mouseb_fire", &mousebfire);
+ M_BindVariable("mouseb_strafe", &mousebstrafe);
+ M_BindVariable("mouseb_forward", &mousebforward);
+
+ M_BindVariable("joyb_fire", &joybfire);
+ M_BindVariable("joyb_strafe", &joybstrafe);
+ M_BindVariable("joyb_use", &joybuse);
+ M_BindVariable("joyb_speed", &joybspeed);
+
+ M_BindVariable("joyb_menu_activate", &joybmenu);
+
+ // Extra controls that are not in the Vanilla versions:
+
+ M_BindVariable("joyb_strafeleft", &joybstrafeleft);
+ M_BindVariable("joyb_straferight", &joybstraferight);
+ M_BindVariable("mouseb_strafeleft", &mousebstrafeleft);
+ M_BindVariable("mouseb_straferight", &mousebstraferight);
+ M_BindVariable("mouseb_use", &mousebuse);
+ M_BindVariable("mouseb_backward", &mousebbackward);
+ M_BindVariable("dclick_use", &dclick_use);
+ M_BindVariable("key_pause", &key_pause);
+ M_BindVariable("key_message_refresh", &key_message_refresh);
+}
+
+void M_BindHereticControls(void)
+{
+ M_BindVariable("key_flyup", &key_flyup);
+ M_BindVariable("key_flydown", &key_flydown);
+ M_BindVariable("key_flycenter", &key_flycenter);
+
+ M_BindVariable("key_lookup", &key_lookup);
+ M_BindVariable("key_lookdown", &key_lookdown);
+ M_BindVariable("key_lookcenter", &key_lookcenter);
+
+ M_BindVariable("key_invleft", &key_invleft);
+ M_BindVariable("key_invright", &key_invright);
+ M_BindVariable("key_useartifact", &key_useartifact);
+}
+
+void M_BindHexenControls(void)
+{
+ M_BindVariable("key_jump", &key_jump);
+ M_BindVariable("mouseb_jump", &mousebjump);
+ M_BindVariable("joyb_jump", &joybjump);
+
+ M_BindVariable("key_arti_all", &key_arti_all);
+ M_BindVariable("key_arti_health", &key_arti_health);
+ M_BindVariable("key_arti_poisonbag", &key_arti_poisonbag);
+ M_BindVariable("key_arti_blastradius", &key_arti_blastradius);
+ M_BindVariable("key_arti_teleport", &key_arti_teleport);
+ M_BindVariable("key_arti_teleportother", &key_arti_teleportother);
+ M_BindVariable("key_arti_egg", &key_arti_egg);
+ M_BindVariable("key_arti_invulnerability", &key_arti_invulnerability);
+}
+
+void M_BindStrifeControls(void)
+{
+ // These are shared with all games, but have different defaults:
+ key_message_refresh = '/';
+
+ // These keys are shared with Heretic/Hexen but have different defaults:
+ key_jump = 'a';
+ key_lookup = KEY_PGUP;
+ key_lookdown = KEY_PGDN;
+ key_invleft = KEY_INS;
+ key_invright = KEY_DEL;
+
+ M_BindVariable("key_jump", &key_jump);
+ M_BindVariable("key_lookUp", &key_lookup);
+ M_BindVariable("key_lookDown", &key_lookdown);
+ M_BindVariable("key_invLeft", &key_invleft);
+ M_BindVariable("key_invRight", &key_invright);
+
+ // Custom Strife-only Keys:
+ M_BindVariable("key_useHealth", &key_usehealth);
+ M_BindVariable("key_invquery", &key_invquery);
+ M_BindVariable("key_mission", &key_mission);
+ M_BindVariable("key_invPop", &key_invpop);
+ M_BindVariable("key_invKey", &key_invkey);
+ M_BindVariable("key_invHome", &key_invhome);
+ M_BindVariable("key_invEnd", &key_invend);
+ M_BindVariable("key_invUse", &key_invuse);
+ M_BindVariable("key_invDrop", &key_invdrop);
+
+ // Strife also supports jump on mouse and joystick, and in the exact same
+ // manner as Hexen!
+ M_BindVariable("mouseb_jump", &mousebjump);
+ M_BindVariable("joyb_jump", &joybjump);
+}
+
+void M_BindWeaponControls(void)
+{
+ M_BindVariable("key_weapon1", &key_weapon1);
+ M_BindVariable("key_weapon2", &key_weapon2);
+ M_BindVariable("key_weapon3", &key_weapon3);
+ M_BindVariable("key_weapon4", &key_weapon4);
+ M_BindVariable("key_weapon5", &key_weapon5);
+ M_BindVariable("key_weapon6", &key_weapon6);
+ M_BindVariable("key_weapon7", &key_weapon7);
+ M_BindVariable("key_weapon8", &key_weapon8);
+
+ M_BindVariable("key_prevweapon", &key_prevweapon);
+ M_BindVariable("key_nextweapon", &key_nextweapon);
+
+ M_BindVariable("joyb_prevweapon", &joybprevweapon);
+ M_BindVariable("joyb_nextweapon", &joybnextweapon);
+
+ M_BindVariable("mouseb_prevweapon", &mousebprevweapon);
+ M_BindVariable("mouseb_nextweapon", &mousebnextweapon);
+}
+
+void M_BindMapControls(void)
+{
+ M_BindVariable("key_map_north", &key_map_north);
+ M_BindVariable("key_map_south", &key_map_south);
+ M_BindVariable("key_map_east", &key_map_east);
+ M_BindVariable("key_map_west", &key_map_west);
+ M_BindVariable("key_map_zoomin", &key_map_zoomin);
+ M_BindVariable("key_map_zoomout", &key_map_zoomout);
+ M_BindVariable("key_map_toggle", &key_map_toggle);
+ M_BindVariable("key_map_maxzoom", &key_map_maxzoom);
+ M_BindVariable("key_map_follow", &key_map_follow);
+ M_BindVariable("key_map_grid", &key_map_grid);
+ M_BindVariable("key_map_mark", &key_map_mark);
+ M_BindVariable("key_map_clearmark", &key_map_clearmark);
+}
+
+void M_BindMenuControls(void)
+{
+ M_BindVariable("key_menu_activate", &key_menu_activate);
+ M_BindVariable("key_menu_up", &key_menu_up);
+ M_BindVariable("key_menu_down", &key_menu_down);
+ M_BindVariable("key_menu_left", &key_menu_left);
+ M_BindVariable("key_menu_right", &key_menu_right);
+ M_BindVariable("key_menu_back", &key_menu_back);
+ M_BindVariable("key_menu_forward", &key_menu_forward);
+ M_BindVariable("key_menu_confirm", &key_menu_confirm);
+ M_BindVariable("key_menu_abort", &key_menu_abort);
+
+ M_BindVariable("key_menu_help", &key_menu_help);
+ M_BindVariable("key_menu_save", &key_menu_save);
+ M_BindVariable("key_menu_load", &key_menu_load);
+ M_BindVariable("key_menu_volume", &key_menu_volume);
+ M_BindVariable("key_menu_detail", &key_menu_detail);
+ M_BindVariable("key_menu_qsave", &key_menu_qsave);
+ M_BindVariable("key_menu_endgame", &key_menu_endgame);
+ M_BindVariable("key_menu_messages", &key_menu_messages);
+ M_BindVariable("key_menu_qload", &key_menu_qload);
+ M_BindVariable("key_menu_quit", &key_menu_quit);
+ M_BindVariable("key_menu_gamma", &key_menu_gamma);
+
+ M_BindVariable("key_menu_incscreen", &key_menu_incscreen);
+ M_BindVariable("key_menu_decscreen", &key_menu_decscreen);
+ M_BindVariable("key_menu_screenshot",&key_menu_screenshot);
+ M_BindVariable("key_demo_quit", &key_demo_quit);
+ M_BindVariable("key_spy", &key_spy);
+}
+
+void M_BindChatControls(unsigned int num_players)
+{
+ char name[32]; // haleyjd: 20 not large enough - Thank you, come again!
+ unsigned int i; // haleyjd: signedness conflict
+
+ M_BindVariable("key_multi_msg", &key_multi_msg);
+
+ for (i=0; i> FRACBITS;
+}
+
+
+
+//
+// FixedDiv, C version.
+//
+
+fixed_t FixedDiv(fixed_t a, fixed_t b)
+{
+ if ((abs(a) >> 14) >= abs(b))
+ {
+ return (a^b) < 0 ? INT_MIN : INT_MAX;
+ }
+ else
+ {
+ int64_t result;
+
+ result = ((int64_t) a << 16) / b;
+
+ return (fixed_t) result;
+ }
+}
+
diff --git a/firmware_p4/components/Applications/doom/m_fixed.h b/firmware_p4/components/Applications/doom/m_fixed.h
new file mode 100644
index 000000000..733b2901c
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/m_fixed.h
@@ -0,0 +1,39 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Fixed point arithemtics, implementation.
+//
+
+
+#ifndef __M_FIXED__
+#define __M_FIXED__
+
+
+
+
+//
+// Fixed point, 32bit as 16.16.
+//
+#define FRACBITS 16
+#define FRACUNIT (1<
+#include
+
+
+#include "doomdef.h"
+#include "doomkeys.h"
+#include "dstrings.h"
+
+#include "d_main.h"
+#include "deh_main.h"
+
+#include "i_swap.h"
+#include "i_system.h"
+#include "i_timer.h"
+#include "i_video.h"
+#include "m_misc.h"
+#include "v_video.h"
+#include "w_wad.h"
+#include "z_zone.h"
+
+#include "r_local.h"
+
+
+#include "hu_stuff.h"
+
+#include "g_game.h"
+
+#include "m_argv.h"
+#include "m_controls.h"
+#include "p_saveg.h"
+
+#include "s_sound.h"
+
+#include "doomstat.h"
+
+// Data.
+#include "sounds.h"
+
+#include "m_menu.h"
+
+
+extern patch_t* hu_font[HU_FONTSIZE];
+extern boolean message_dontfuckwithme;
+
+extern boolean chat_on; // in heads-up code
+
+//
+// defaulted values
+//
+int mouseSensitivity = 5;
+
+// Show messages has default, 0 = off, 1 = on
+int showMessages = 1;
+
+
+// Blocky mode, has default, 0 = high, 1 = normal
+int detailLevel = 0;
+int screenblocks = 10;
+
+// temp for screenblocks (0-9)
+int screenSize;
+
+// -1 = no quicksave slot picked!
+int quickSaveSlot;
+
+ // 1 = message to be printed
+int messageToPrint;
+// ...and here is the message string!
+char* messageString;
+
+// message x & y
+int messx;
+int messy;
+int messageLastMenuActive;
+
+// timed message = no input from user
+boolean messageNeedsInput;
+
+void (*messageRoutine)(int response);
+
+char gammamsg[5][26] =
+{
+ GAMMALVL0,
+ GAMMALVL1,
+ GAMMALVL2,
+ GAMMALVL3,
+ GAMMALVL4
+};
+
+// we are going to be entering a savegame string
+int saveStringEnter;
+int saveSlot; // which slot to save in
+int saveCharIndex; // which char we're editing
+// old save description before edit
+char saveOldString[SAVESTRINGSIZE];
+
+boolean inhelpscreens;
+boolean menuactive;
+
+#define SKULLXOFF -32
+#define LINEHEIGHT 16
+
+extern boolean sendpause;
+char savegamestrings[10][SAVESTRINGSIZE];
+
+char endstring[160];
+
+//static boolean opldev;
+
+//
+// MENU TYPEDEFS
+//
+typedef struct
+{
+ // 0 = no cursor here, 1 = ok, 2 = arrows ok
+ short status;
+
+ char name[10];
+
+ // choice = menu item #.
+ // if status = 2,
+ // choice=0:leftarrow,1:rightarrow
+ void (*routine)(int choice);
+
+ // hotkey in menu
+ char alphaKey;
+} menuitem_t;
+
+
+
+typedef struct menu_s
+{
+ short numitems; // # of menu items
+ struct menu_s* prevMenu; // previous menu
+ menuitem_t* menuitems; // menu items
+ void (*routine)(); // draw routine
+ short x;
+ short y; // x,y of menu
+ short lastOn; // last item user was on in menu
+} menu_t;
+
+short itemOn; // menu item skull is on
+short skullAnimCounter; // skull animation counter
+short whichSkull; // which skull to draw
+
+// graphic name of skulls
+// warning: initializer-string for array of chars is too long
+char *skullName[2] = {"M_SKULL1","M_SKULL2"};
+
+// current menudef
+menu_t* currentMenu;
+
+//
+// PROTOTYPES
+//
+void M_NewGame(int choice);
+void M_Episode(int choice);
+void M_ChooseSkill(int choice);
+void M_LoadGame(int choice);
+void M_SaveGame(int choice);
+void M_Options(int choice);
+void M_EndGame(int choice);
+void M_ReadThis(int choice);
+void M_ReadThis2(int choice);
+void M_QuitDOOM(int choice);
+
+void M_ChangeMessages(int choice);
+void M_ChangeSensitivity(int choice);
+void M_SfxVol(int choice);
+void M_MusicVol(int choice);
+void M_ChangeDetail(int choice);
+void M_SizeDisplay(int choice);
+void M_StartGame(int choice);
+void M_Sound(int choice);
+
+void M_FinishReadThis(int choice);
+void M_LoadSelect(int choice);
+void M_SaveSelect(int choice);
+void M_ReadSaveStrings(void);
+void M_QuickSave(void);
+void M_QuickLoad(void);
+
+void M_DrawMainMenu(void);
+void M_DrawReadThis1(void);
+void M_DrawReadThis2(void);
+void M_DrawNewGame(void);
+void M_DrawEpisode(void);
+void M_DrawOptions(void);
+void M_DrawSound(void);
+void M_DrawLoad(void);
+void M_DrawSave(void);
+
+void M_DrawSaveLoadBorder(int x,int y);
+void M_SetupNextMenu(menu_t *menudef);
+void M_DrawThermo(int x,int y,int thermWidth,int thermDot);
+void M_DrawEmptyCell(menu_t *menu,int item);
+void M_DrawSelCell(menu_t *menu,int item);
+void M_WriteText(int x, int y, char *string);
+int M_StringWidth(char *string);
+int M_StringHeight(char *string);
+void M_StartMessage(char *string,void *routine,boolean input);
+void M_StopMessage(void);
+void M_ClearMenus (void);
+
+
+
+
+//
+// DOOM MENU
+//
+enum
+{
+ newgame = 0,
+ options,
+ loadgame,
+ savegame,
+ readthis,
+ quitdoom,
+ main_end
+} main_e;
+
+menuitem_t MainMenu[]=
+{
+ {1,"M_NGAME",M_NewGame,'n'},
+ {1,"M_OPTION",M_Options,'o'},
+ {1,"M_LOADG",M_LoadGame,'l'},
+ {1,"M_SAVEG",M_SaveGame,'s'},
+ // Another hickup with Special edition.
+ {1,"M_RDTHIS",M_ReadThis,'r'},
+ {1,"M_QUITG",M_QuitDOOM,'q'}
+};
+
+menu_t MainDef =
+{
+ main_end,
+ NULL,
+ MainMenu,
+ M_DrawMainMenu,
+ 97,64,
+ 0
+};
+
+
+//
+// EPISODE SELECT
+//
+enum
+{
+ ep1,
+ ep2,
+ ep3,
+ ep4,
+ ep_end
+} episodes_e;
+
+menuitem_t EpisodeMenu[]=
+{
+ {1,"M_EPI1", M_Episode,'k'},
+ {1,"M_EPI2", M_Episode,'t'},
+ {1,"M_EPI3", M_Episode,'i'},
+ {1,"M_EPI4", M_Episode,'t'}
+};
+
+menu_t EpiDef =
+{
+ ep_end, // # of menu items
+ &MainDef, // previous menu
+ EpisodeMenu, // menuitem_t ->
+ M_DrawEpisode, // drawing routine ->
+ 48,63, // x,y
+ ep1 // lastOn
+};
+
+//
+// NEW GAME
+//
+enum
+{
+ killthings,
+ toorough,
+ hurtme,
+ violence,
+ nightmare,
+ newg_end
+} newgame_e;
+
+menuitem_t NewGameMenu[]=
+{
+ {1,"M_JKILL", M_ChooseSkill, 'i'},
+ {1,"M_ROUGH", M_ChooseSkill, 'h'},
+ {1,"M_HURT", M_ChooseSkill, 'h'},
+ {1,"M_ULTRA", M_ChooseSkill, 'u'},
+ {1,"M_NMARE", M_ChooseSkill, 'n'}
+};
+
+menu_t NewDef =
+{
+ newg_end, // # of menu items
+ &EpiDef, // previous menu
+ NewGameMenu, // menuitem_t ->
+ M_DrawNewGame, // drawing routine ->
+ 48,63, // x,y
+ hurtme // lastOn
+};
+
+
+
+//
+// OPTIONS MENU
+//
+enum
+{
+ endgame,
+ messages,
+ detail,
+ scrnsize,
+ option_empty1,
+ mousesens,
+ option_empty2,
+ soundvol,
+ opt_end
+} options_e;
+
+menuitem_t OptionsMenu[]=
+{
+ {1,"M_ENDGAM", M_EndGame,'e'},
+ {1,"M_MESSG", M_ChangeMessages,'m'},
+ {1,"M_DETAIL", M_ChangeDetail,'g'},
+ {2,"M_SCRNSZ", M_SizeDisplay,'s'},
+ {-1,"",0,'\0'},
+ {2,"M_MSENS", M_ChangeSensitivity,'m'},
+ {-1,"",0,'\0'},
+ {1,"M_SVOL", M_Sound,'s'}
+};
+
+menu_t OptionsDef =
+{
+ opt_end,
+ &MainDef,
+ OptionsMenu,
+ M_DrawOptions,
+ 60,37,
+ 0
+};
+
+//
+// Read This! MENU 1 & 2
+//
+enum
+{
+ rdthsempty1,
+ read1_end
+} read_e;
+
+menuitem_t ReadMenu1[] =
+{
+ {1,"",M_ReadThis2,0}
+};
+
+menu_t ReadDef1 =
+{
+ read1_end,
+ &MainDef,
+ ReadMenu1,
+ M_DrawReadThis1,
+ 280,185,
+ 0
+};
+
+enum
+{
+ rdthsempty2,
+ read2_end
+} read_e2;
+
+menuitem_t ReadMenu2[]=
+{
+ {1,"",M_FinishReadThis,0}
+};
+
+menu_t ReadDef2 =
+{
+ read2_end,
+ &ReadDef1,
+ ReadMenu2,
+ M_DrawReadThis2,
+ 330,175,
+ 0
+};
+
+//
+// SOUND VOLUME MENU
+//
+enum
+{
+ sfx_vol,
+ sfx_empty1,
+ music_vol,
+ sfx_empty2,
+ sound_end
+} sound_e;
+
+menuitem_t SoundMenu[]=
+{
+ {2,"M_SFXVOL",M_SfxVol,'s'},
+ {-1,"",0,'\0'},
+ {2,"M_MUSVOL",M_MusicVol,'m'},
+ {-1,"",0,'\0'}
+};
+
+menu_t SoundDef =
+{
+ sound_end,
+ &OptionsDef,
+ SoundMenu,
+ M_DrawSound,
+ 80,64,
+ 0
+};
+
+//
+// LOAD GAME MENU
+//
+enum
+{
+ load1,
+ load2,
+ load3,
+ load4,
+ load5,
+ load6,
+ load_end
+} load_e;
+
+menuitem_t LoadMenu[]=
+{
+ {1,"", M_LoadSelect,'1'},
+ {1,"", M_LoadSelect,'2'},
+ {1,"", M_LoadSelect,'3'},
+ {1,"", M_LoadSelect,'4'},
+ {1,"", M_LoadSelect,'5'},
+ {1,"", M_LoadSelect,'6'}
+};
+
+menu_t LoadDef =
+{
+ load_end,
+ &MainDef,
+ LoadMenu,
+ M_DrawLoad,
+ 80,54,
+ 0
+};
+
+//
+// SAVE GAME MENU
+//
+menuitem_t SaveMenu[]=
+{
+ {1,"", M_SaveSelect,'1'},
+ {1,"", M_SaveSelect,'2'},
+ {1,"", M_SaveSelect,'3'},
+ {1,"", M_SaveSelect,'4'},
+ {1,"", M_SaveSelect,'5'},
+ {1,"", M_SaveSelect,'6'}
+};
+
+menu_t SaveDef =
+{
+ load_end,
+ &MainDef,
+ SaveMenu,
+ M_DrawSave,
+ 80,54,
+ 0
+};
+
+
+//
+// M_ReadSaveStrings
+// read the strings from the savegame files
+//
+void M_ReadSaveStrings(void)
+{
+ FILE *handle;
+ int i;
+ char name[256];
+
+ for (i = 0;i < load_end;i++)
+ {
+ M_StringCopy(name, P_SaveGameFile(i), sizeof(name));
+
+ handle = fopen(name, "rb");
+ if (handle == NULL)
+ {
+ M_StringCopy(savegamestrings[i], EMPTYSTRING, SAVESTRINGSIZE);
+ LoadMenu[i].status = 0;
+ continue;
+ }
+ fread(&savegamestrings[i], 1, SAVESTRINGSIZE, handle);
+ fclose(handle);
+ LoadMenu[i].status = 1;
+ }
+}
+
+
+//
+// M_LoadGame & Cie.
+//
+void M_DrawLoad(void)
+{
+ int i;
+
+ V_DrawPatchDirect(72, 28,
+ W_CacheLumpName(DEH_String("M_LOADG"), PU_CACHE));
+
+ for (i = 0;i < load_end; i++)
+ {
+ M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i);
+ M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]);
+ }
+}
+
+
+
+//
+// Draw border for the savegame description
+//
+void M_DrawSaveLoadBorder(int x,int y)
+{
+ int i;
+
+ V_DrawPatchDirect(x - 8, y + 7,
+ W_CacheLumpName(DEH_String("M_LSLEFT"), PU_CACHE));
+
+ for (i = 0;i < 24;i++)
+ {
+ V_DrawPatchDirect(x, y + 7,
+ W_CacheLumpName(DEH_String("M_LSCNTR"), PU_CACHE));
+ x += 8;
+ }
+
+ V_DrawPatchDirect(x, y + 7,
+ W_CacheLumpName(DEH_String("M_LSRGHT"), PU_CACHE));
+}
+
+
+
+//
+// User wants to load this game
+//
+void M_LoadSelect(int choice)
+{
+ char name[256];
+
+ M_StringCopy(name, P_SaveGameFile(choice), sizeof(name));
+
+ G_LoadGame (name);
+ M_ClearMenus ();
+}
+
+//
+// Selected from DOOM menu
+//
+void M_LoadGame (int choice)
+{
+ if (netgame)
+ {
+ M_StartMessage(DEH_String(LOADNET),NULL,false);
+ return;
+ }
+
+ M_SetupNextMenu(&LoadDef);
+ M_ReadSaveStrings();
+}
+
+
+//
+// M_SaveGame & Cie.
+//
+void M_DrawSave(void)
+{
+ int i;
+
+ V_DrawPatchDirect(72, 28, W_CacheLumpName(DEH_String("M_SAVEG"), PU_CACHE));
+ for (i = 0;i < load_end; i++)
+ {
+ M_DrawSaveLoadBorder(LoadDef.x,LoadDef.y+LINEHEIGHT*i);
+ M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]);
+ }
+
+ if (saveStringEnter)
+ {
+ i = M_StringWidth(savegamestrings[saveSlot]);
+ M_WriteText(LoadDef.x + i,LoadDef.y+LINEHEIGHT*saveSlot,"_");
+ }
+}
+
+//
+// M_Responder calls this when user is finished
+//
+void M_DoSave(int slot)
+{
+ G_SaveGame (slot,savegamestrings[slot]);
+ M_ClearMenus ();
+
+ // PICK QUICKSAVE SLOT YET?
+ if (quickSaveSlot == -2)
+ quickSaveSlot = slot;
+}
+
+//
+// User wants to save. Start string input for M_Responder
+//
+void M_SaveSelect(int choice)
+{
+ // we are going to be intercepting all chars
+ saveStringEnter = 1;
+
+ saveSlot = choice;
+ M_StringCopy(saveOldString,savegamestrings[choice], SAVESTRINGSIZE);
+ if (!strcmp(savegamestrings[choice], EMPTYSTRING))
+ savegamestrings[choice][0] = 0;
+ saveCharIndex = strlen(savegamestrings[choice]);
+}
+
+//
+// Selected from DOOM menu
+//
+void M_SaveGame (int choice)
+{
+ if (!usergame)
+ {
+ M_StartMessage(DEH_String(SAVEDEAD),NULL,false);
+ return;
+ }
+
+ if (gamestate != GS_LEVEL)
+ return;
+
+ M_SetupNextMenu(&SaveDef);
+ M_ReadSaveStrings();
+}
+
+
+
+//
+// M_QuickSave
+//
+char tempstring[80];
+
+void M_QuickSaveResponse(int key)
+{
+ if (key == key_menu_confirm)
+ {
+ M_DoSave(quickSaveSlot);
+ S_StartSound(NULL,sfx_swtchx);
+ }
+}
+
+void M_QuickSave(void)
+{
+ if (!usergame)
+ {
+ S_StartSound(NULL,sfx_oof);
+ return;
+ }
+
+ if (gamestate != GS_LEVEL)
+ return;
+
+ if (quickSaveSlot < 0)
+ {
+ M_StartControlPanel();
+ M_ReadSaveStrings();
+ M_SetupNextMenu(&SaveDef);
+ quickSaveSlot = -2; // means to pick a slot now
+ return;
+ }
+ DEH_snprintf(tempstring, 80, QSPROMPT, savegamestrings[quickSaveSlot]);
+ M_StartMessage(tempstring,M_QuickSaveResponse,true);
+}
+
+
+
+//
+// M_QuickLoad
+//
+void M_QuickLoadResponse(int key)
+{
+ if (key == key_menu_confirm)
+ {
+ M_LoadSelect(quickSaveSlot);
+ S_StartSound(NULL,sfx_swtchx);
+ }
+}
+
+
+void M_QuickLoad(void)
+{
+ if (netgame)
+ {
+ M_StartMessage(DEH_String(QLOADNET),NULL,false);
+ return;
+ }
+
+ if (quickSaveSlot < 0)
+ {
+ M_StartMessage(DEH_String(QSAVESPOT),NULL,false);
+ return;
+ }
+ DEH_snprintf(tempstring, 80, QLPROMPT, savegamestrings[quickSaveSlot]);
+ M_StartMessage(tempstring,M_QuickLoadResponse,true);
+}
+
+
+
+
+//
+// Read This Menus
+// Had a "quick hack to fix romero bug"
+//
+void M_DrawReadThis1(void)
+{
+ char *lumpname = "CREDIT";
+ int skullx = 330, skully = 175;
+
+ inhelpscreens = true;
+
+ // Different versions of Doom 1.9 work differently
+
+ switch (gameversion)
+ {
+ case exe_doom_1_666:
+ case exe_doom_1_7:
+ case exe_doom_1_8:
+ case exe_doom_1_9:
+ case exe_hacx:
+
+ if (gamemode == commercial)
+ {
+ // Doom 2
+
+ lumpname = "HELP";
+
+ skullx = 330;
+ skully = 165;
+ }
+ else
+ {
+ // Doom 1
+ // HELP2 is the first screen shown in Doom 1
+
+ lumpname = "HELP2";
+
+ skullx = 280;
+ skully = 185;
+ }
+ break;
+
+ case exe_ultimate:
+ case exe_chex:
+
+ // Ultimate Doom always displays "HELP1".
+
+ // Chex Quest version also uses "HELP1", even though it is based
+ // on Final Doom.
+
+ lumpname = "HELP1";
+
+ break;
+
+ case exe_final:
+ case exe_final2:
+
+ // Final Doom always displays "HELP".
+
+ lumpname = "HELP";
+
+ break;
+
+ default:
+ I_Error("Unhandled game version");
+ break;
+ }
+
+ lumpname = DEH_String(lumpname);
+
+ V_DrawPatchDirect (0, 0, W_CacheLumpName(lumpname, PU_CACHE));
+
+ ReadDef1.x = skullx;
+ ReadDef1.y = skully;
+}
+
+
+
+//
+// Read This Menus - optional second page.
+//
+void M_DrawReadThis2(void)
+{
+ inhelpscreens = true;
+
+ // We only ever draw the second page if this is
+ // gameversion == exe_doom_1_9 and gamemode == registered
+
+ V_DrawPatchDirect(0, 0, W_CacheLumpName(DEH_String("HELP1"), PU_CACHE));
+}
+
+
+//
+// Change Sfx & Music volumes
+//
+void M_DrawSound(void)
+{
+ V_DrawPatchDirect (60, 38, W_CacheLumpName(DEH_String("M_SVOL"), PU_CACHE));
+
+ M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(sfx_vol+1),
+ 16,sfxVolume);
+
+ M_DrawThermo(SoundDef.x,SoundDef.y+LINEHEIGHT*(music_vol+1),
+ 16,musicVolume);
+}
+
+void M_Sound(int choice)
+{
+ M_SetupNextMenu(&SoundDef);
+}
+
+void M_SfxVol(int choice)
+{
+ switch(choice)
+ {
+ case 0:
+ if (sfxVolume)
+ sfxVolume--;
+ break;
+ case 1:
+ if (sfxVolume < 15)
+ sfxVolume++;
+ break;
+ }
+
+ S_SetSfxVolume(sfxVolume * 8);
+}
+
+void M_MusicVol(int choice)
+{
+ switch(choice)
+ {
+ case 0:
+ if (musicVolume)
+ musicVolume--;
+ break;
+ case 1:
+ if (musicVolume < 15)
+ musicVolume++;
+ break;
+ }
+
+ S_SetMusicVolume(musicVolume * 8);
+}
+
+
+
+
+//
+// M_DrawMainMenu
+//
+void M_DrawMainMenu(void)
+{
+ V_DrawPatchDirect(94, 2,
+ W_CacheLumpName(DEH_String("M_DOOM"), PU_CACHE));
+}
+
+
+
+
+//
+// M_NewGame
+//
+void M_DrawNewGame(void)
+{
+ V_DrawPatchDirect(96, 14, W_CacheLumpName(DEH_String("M_NEWG"), PU_CACHE));
+ V_DrawPatchDirect(54, 38, W_CacheLumpName(DEH_String("M_SKILL"), PU_CACHE));
+}
+
+void M_NewGame(int choice)
+{
+ if (netgame && !demoplayback)
+ {
+ M_StartMessage(DEH_String(NEWGAME),NULL,false);
+ return;
+ }
+
+ // Chex Quest disabled the episode select screen, as did Doom II.
+
+ if (gamemode == commercial || gameversion == exe_chex)
+ M_SetupNextMenu(&NewDef);
+ else
+ M_SetupNextMenu(&EpiDef);
+}
+
+
+//
+// M_Episode
+//
+int epi;
+
+void M_DrawEpisode(void)
+{
+ V_DrawPatchDirect(54, 38, W_CacheLumpName(DEH_String("M_EPISOD"), PU_CACHE));
+}
+
+void M_VerifyNightmare(int key)
+{
+ if (key != key_menu_confirm)
+ return;
+
+ G_DeferedInitNew(nightmare,epi+1,1);
+ M_ClearMenus ();
+}
+
+void M_ChooseSkill(int choice)
+{
+ if (choice == nightmare)
+ {
+ M_StartMessage(DEH_String(NIGHTMARE),M_VerifyNightmare,true);
+ return;
+ }
+
+ G_DeferedInitNew(choice,epi+1,1);
+ M_ClearMenus ();
+}
+
+void M_Episode(int choice)
+{
+ if ( (gamemode == shareware)
+ && choice)
+ {
+ M_StartMessage(DEH_String(SWSTRING),NULL,false);
+ M_SetupNextMenu(&ReadDef1);
+ return;
+ }
+
+ // Yet another hack...
+ if ( (gamemode == registered)
+ && (choice > 2))
+ {
+ fprintf( stderr,
+ "M_Episode: 4th episode requires UltimateDOOM\n");
+ choice = 0;
+ }
+
+ epi = choice;
+ M_SetupNextMenu(&NewDef);
+}
+
+
+
+//
+// M_Options
+//
+static char *detailNames[2] = {"M_GDHIGH","M_GDLOW"};
+static char *msgNames[2] = {"M_MSGOFF","M_MSGON"};
+
+void M_DrawOptions(void)
+{
+ V_DrawPatchDirect(108, 15, W_CacheLumpName(DEH_String("M_OPTTTL"),
+ PU_CACHE));
+
+ V_DrawPatchDirect(OptionsDef.x + 175, OptionsDef.y + LINEHEIGHT * detail,
+ W_CacheLumpName(DEH_String(detailNames[detailLevel]),
+ PU_CACHE));
+
+ V_DrawPatchDirect(OptionsDef.x + 120, OptionsDef.y + LINEHEIGHT * messages,
+ W_CacheLumpName(DEH_String(msgNames[showMessages]),
+ PU_CACHE));
+
+ M_DrawThermo(OptionsDef.x, OptionsDef.y + LINEHEIGHT * (mousesens + 1),
+ 10, mouseSensitivity);
+
+ M_DrawThermo(OptionsDef.x,OptionsDef.y+LINEHEIGHT*(scrnsize+1),
+ 9,screenSize);
+}
+
+void M_Options(int choice)
+{
+ M_SetupNextMenu(&OptionsDef);
+}
+
+
+
+//
+// Toggle messages on/off
+//
+void M_ChangeMessages(int choice)
+{
+ // warning: unused parameter `int choice'
+ choice = 0;
+ showMessages = 1 - showMessages;
+
+ if (!showMessages)
+ players[consoleplayer].message = DEH_String(MSGOFF);
+ else
+ players[consoleplayer].message = DEH_String(MSGON);
+
+ message_dontfuckwithme = true;
+}
+
+
+//
+// M_EndGame
+//
+void M_EndGameResponse(int key)
+{
+ if (key != key_menu_confirm)
+ return;
+
+ currentMenu->lastOn = itemOn;
+ M_ClearMenus ();
+ D_StartTitle ();
+}
+
+void M_EndGame(int choice)
+{
+ choice = 0;
+ if (!usergame)
+ {
+ S_StartSound(NULL,sfx_oof);
+ return;
+ }
+
+ if (netgame)
+ {
+ M_StartMessage(DEH_String(NETEND),NULL,false);
+ return;
+ }
+
+ M_StartMessage(DEH_String(ENDGAME),M_EndGameResponse,true);
+}
+
+
+
+
+//
+// M_ReadThis
+//
+void M_ReadThis(int choice)
+{
+ choice = 0;
+ M_SetupNextMenu(&ReadDef1);
+}
+
+void M_ReadThis2(int choice)
+{
+ // Doom 1.9 had two menus when playing Doom 1
+ // All others had only one
+
+ if (gameversion <= exe_doom_1_9 && gamemode != commercial)
+ {
+ choice = 0;
+ M_SetupNextMenu(&ReadDef2);
+ }
+ else
+ {
+ // Close the menu
+
+ M_FinishReadThis(0);
+ }
+}
+
+void M_FinishReadThis(int choice)
+{
+ choice = 0;
+ M_SetupNextMenu(&MainDef);
+}
+
+
+
+
+//
+// M_QuitDOOM
+//
+int quitsounds[8] =
+{
+ sfx_pldeth,
+ sfx_dmpain,
+ sfx_popain,
+ sfx_slop,
+ sfx_telept,
+ sfx_posit1,
+ sfx_posit3,
+ sfx_sgtatk
+};
+
+int quitsounds2[8] =
+{
+ sfx_vilact,
+ sfx_getpow,
+ sfx_boscub,
+ sfx_slop,
+ sfx_skeswg,
+ sfx_kntdth,
+ sfx_bspact,
+ sfx_sgtatk
+};
+
+
+
+void M_QuitResponse(int key)
+{
+ if (key != key_menu_confirm)
+ return;
+ if (!netgame)
+ {
+ if (gamemode == commercial)
+ S_StartSound(NULL,quitsounds2[(gametic>>2)&7]);
+ else
+ S_StartSound(NULL,quitsounds[(gametic>>2)&7]);
+ I_WaitVBL(105);
+ }
+ I_Quit ();
+}
+
+
+static char *M_SelectEndMessage(void)
+{
+ char **endmsg;
+
+ if (logical_gamemission == doom)
+ {
+ // Doom 1
+
+ endmsg = doom1_endmsg;
+ }
+ else
+ {
+ // Doom 2
+
+ endmsg = doom2_endmsg;
+ }
+
+ return endmsg[gametic % NUM_QUITMESSAGES];
+}
+
+
+void M_QuitDOOM(int choice)
+{
+ DEH_snprintf(endstring, sizeof(endstring), "%s\n\n" DOSY,
+ DEH_String(M_SelectEndMessage()));
+
+ M_StartMessage(endstring,M_QuitResponse,true);
+}
+
+
+
+
+void M_ChangeSensitivity(int choice)
+{
+ switch(choice)
+ {
+ case 0:
+ if (mouseSensitivity)
+ mouseSensitivity--;
+ break;
+ case 1:
+ if (mouseSensitivity < 9)
+ mouseSensitivity++;
+ break;
+ }
+}
+
+
+
+
+void M_ChangeDetail(int choice)
+{
+ choice = 0;
+ detailLevel = 1 - detailLevel;
+
+ R_SetViewSize (screenblocks, detailLevel);
+
+ if (!detailLevel)
+ players[consoleplayer].message = DEH_String(DETAILHI);
+ else
+ players[consoleplayer].message = DEH_String(DETAILLO);
+}
+
+
+
+
+void M_SizeDisplay(int choice)
+{
+ switch(choice)
+ {
+ case 0:
+ if (screenSize > 0)
+ {
+ screenblocks--;
+ screenSize--;
+ }
+ break;
+ case 1:
+ if (screenSize < 8)
+ {
+ screenblocks++;
+ screenSize++;
+ }
+ break;
+ }
+
+
+ R_SetViewSize (screenblocks, detailLevel);
+}
+
+
+
+
+//
+// Menu Functions
+//
+void
+M_DrawThermo
+( int x,
+ int y,
+ int thermWidth,
+ int thermDot )
+{
+ int xx;
+ int i;
+
+ xx = x;
+ V_DrawPatchDirect(xx, y, W_CacheLumpName(DEH_String("M_THERML"), PU_CACHE));
+ xx += 8;
+ for (i=0;ix - 10, menu->y + item * LINEHEIGHT - 1,
+ W_CacheLumpName(DEH_String("M_CELL1"), PU_CACHE));
+}
+
+void
+M_DrawSelCell
+( menu_t* menu,
+ int item )
+{
+ V_DrawPatchDirect(menu->x - 10, menu->y + item * LINEHEIGHT - 1,
+ W_CacheLumpName(DEH_String("M_CELL2"), PU_CACHE));
+}
+
+
+void
+M_StartMessage
+( char* string,
+ void* routine,
+ boolean input )
+{
+ messageLastMenuActive = menuactive;
+ messageToPrint = 1;
+ messageString = string;
+ messageRoutine = routine;
+ messageNeedsInput = input;
+ menuactive = true;
+ return;
+}
+
+
+void M_StopMessage(void)
+{
+ menuactive = messageLastMenuActive;
+ messageToPrint = 0;
+}
+
+
+
+//
+// Find string width from hu_font chars
+//
+int M_StringWidth(char* string)
+{
+ size_t i;
+ int w = 0;
+ int c;
+
+ for (i = 0;i < strlen(string);i++)
+ {
+ c = toupper(string[i]) - HU_FONTSTART;
+ if (c < 0 || c >= HU_FONTSIZE)
+ w += 4;
+ else
+ w += SHORT (hu_font[c]->width);
+ }
+
+ return w;
+}
+
+
+
+//
+// Find string height from hu_font chars
+//
+int M_StringHeight(char* string)
+{
+ size_t i;
+ int h;
+ int height = SHORT(hu_font[0]->height);
+
+ h = height;
+ for (i = 0;i < strlen(string);i++)
+ if (string[i] == '\n')
+ h += height;
+
+ return h;
+}
+
+
+//
+// Write a string using the hu_font
+//
+void
+M_WriteText
+( int x,
+ int y,
+ char* string)
+{
+ int w;
+ char* ch;
+ int c;
+ int cx;
+ int cy;
+
+
+ ch = string;
+ cx = x;
+ cy = y;
+
+ while(1)
+ {
+ c = *ch++;
+ if (!c)
+ break;
+ if (c == '\n')
+ {
+ cx = x;
+ cy += 12;
+ continue;
+ }
+
+ c = toupper(c) - HU_FONTSTART;
+ if (c < 0 || c>= HU_FONTSIZE)
+ {
+ cx += 4;
+ continue;
+ }
+
+ w = SHORT (hu_font[c]->width);
+ if (cx+w > SCREENWIDTH)
+ break;
+ V_DrawPatchDirect(cx, cy, hu_font[c]);
+ cx+=w;
+ }
+}
+
+// These keys evaluate to a "null" key in Vanilla Doom that allows weird
+// jumping in the menus. Preserve this behavior for accuracy.
+
+static boolean IsNullKey(int key)
+{
+ return key == KEY_PAUSE || key == KEY_CAPSLOCK
+ || key == KEY_SCRLCK || key == KEY_NUMLOCK;
+}
+
+//
+// CONTROL PANEL
+//
+
+//
+// M_Responder
+//
+boolean M_Responder (event_t* ev)
+{
+ int ch;
+ int key;
+ int i;
+ static int joywait = 0;
+ static int mousewait = 0;
+ static int mousey = 0;
+ static int lasty = 0;
+ static int mousex = 0;
+ static int lastx = 0;
+
+ // In testcontrols mode, none of the function keys should do anything
+ // - the only key is escape to quit.
+
+ if (testcontrols)
+ {
+ if (ev->type == ev_quit
+ || (ev->type == ev_keydown
+ && (ev->data1 == key_menu_activate || ev->data1 == key_menu_quit)))
+ {
+ I_Quit();
+ return true;
+ }
+
+ return false;
+ }
+
+ // "close" button pressed on window?
+ if (ev->type == ev_quit)
+ {
+ // First click on close button = bring up quit confirm message.
+ // Second click on close button = confirm quit
+
+ if (menuactive && messageToPrint && messageRoutine == M_QuitResponse)
+ {
+ M_QuitResponse(key_menu_confirm);
+ }
+ else
+ {
+ S_StartSound(NULL,sfx_swtchn);
+ M_QuitDOOM(0);
+ }
+
+ return true;
+ }
+
+ // key is the key pressed, ch is the actual character typed
+
+ ch = 0;
+ key = -1;
+
+ if (ev->type == ev_joystick && joywait < I_GetTime())
+ {
+ if (ev->data3 < 0)
+ {
+ key = key_menu_up;
+ joywait = I_GetTime() + 5;
+ }
+ else if (ev->data3 > 0)
+ {
+ key = key_menu_down;
+ joywait = I_GetTime() + 5;
+ }
+
+ if (ev->data2 < 0)
+ {
+ key = key_menu_left;
+ joywait = I_GetTime() + 2;
+ }
+ else if (ev->data2 > 0)
+ {
+ key = key_menu_right;
+ joywait = I_GetTime() + 2;
+ }
+
+ if (ev->data1&1)
+ {
+ key = key_menu_forward;
+ joywait = I_GetTime() + 5;
+ }
+ if (ev->data1&2)
+ {
+ key = key_menu_back;
+ joywait = I_GetTime() + 5;
+ }
+ if (joybmenu >= 0 && (ev->data1 & (1 << joybmenu)) != 0)
+ {
+ key = key_menu_activate;
+ joywait = I_GetTime() + 5;
+ }
+ }
+ else
+ {
+ if (ev->type == ev_mouse && mousewait < I_GetTime())
+ {
+ mousey += ev->data3;
+ if (mousey < lasty-30)
+ {
+ key = key_menu_down;
+ mousewait = I_GetTime() + 5;
+ mousey = lasty -= 30;
+ }
+ else if (mousey > lasty+30)
+ {
+ key = key_menu_up;
+ mousewait = I_GetTime() + 5;
+ mousey = lasty += 30;
+ }
+
+ mousex += ev->data2;
+ if (mousex < lastx-30)
+ {
+ key = key_menu_left;
+ mousewait = I_GetTime() + 5;
+ mousex = lastx -= 30;
+ }
+ else if (mousex > lastx+30)
+ {
+ key = key_menu_right;
+ mousewait = I_GetTime() + 5;
+ mousex = lastx += 30;
+ }
+
+ if (ev->data1&1)
+ {
+ key = key_menu_forward;
+ mousewait = I_GetTime() + 15;
+ }
+
+ if (ev->data1&2)
+ {
+ key = key_menu_back;
+ mousewait = I_GetTime() + 15;
+ }
+ }
+ else
+ {
+ if (ev->type == ev_keydown)
+ {
+ key = ev->data1;
+ ch = ev->data2;
+ }
+ }
+ }
+
+ if (key == -1)
+ return false;
+
+ // Save Game string input
+ if (saveStringEnter)
+ {
+ switch(key)
+ {
+ case KEY_BACKSPACE:
+ if (saveCharIndex > 0)
+ {
+ saveCharIndex--;
+ savegamestrings[saveSlot][saveCharIndex] = 0;
+ }
+ break;
+
+ case KEY_ESCAPE:
+ saveStringEnter = 0;
+ M_StringCopy(savegamestrings[saveSlot], saveOldString,
+ SAVESTRINGSIZE);
+ break;
+
+ case KEY_ENTER:
+ saveStringEnter = 0;
+ if (savegamestrings[saveSlot][0])
+ M_DoSave(saveSlot);
+ break;
+
+ default:
+ // This is complicated.
+ // Vanilla has a bug where the shift key is ignored when entering
+ // a savegame name. If vanilla_keyboard_mapping is on, we want
+ // to emulate this bug by using 'data1'. But if it's turned off,
+ // it implies the user doesn't care about Vanilla emulation: just
+ // use the correct 'data2'.
+
+ if (vanilla_keyboard_mapping)
+ {
+ ch = key;
+ }
+
+ ch = toupper(ch);
+
+ if (ch != ' '
+ && (ch - HU_FONTSTART < 0 || ch - HU_FONTSTART >= HU_FONTSIZE))
+ {
+ break;
+ }
+
+ if (ch >= 32 && ch <= 127 &&
+ saveCharIndex < SAVESTRINGSIZE-1 &&
+ M_StringWidth(savegamestrings[saveSlot]) <
+ (SAVESTRINGSIZE-2)*8)
+ {
+ savegamestrings[saveSlot][saveCharIndex++] = ch;
+ savegamestrings[saveSlot][saveCharIndex] = 0;
+ }
+ break;
+ }
+ return true;
+ }
+
+ // Take care of any messages that need input
+ if (messageToPrint)
+ {
+ if (messageNeedsInput)
+ {
+ if (key != ' ' && key != KEY_ESCAPE
+ && key != key_menu_confirm && key != key_menu_abort)
+ {
+ return false;
+ }
+ }
+
+ menuactive = messageLastMenuActive;
+ messageToPrint = 0;
+ if (messageRoutine)
+ messageRoutine(key);
+
+ menuactive = false;
+ S_StartSound(NULL,sfx_swtchx);
+ return true;
+ }
+
+ if ((devparm && key == key_menu_help) ||
+ (key != 0 && key == key_menu_screenshot))
+ {
+ G_ScreenShot ();
+ return true;
+ }
+
+ // F-Keys
+ if (!menuactive)
+ {
+ if (key == key_menu_decscreen) // Screen size down
+ {
+ if (automapactive || chat_on)
+ return false;
+ M_SizeDisplay(0);
+ S_StartSound(NULL,sfx_stnmov);
+ return true;
+ }
+ else if (key == key_menu_incscreen) // Screen size up
+ {
+ if (automapactive || chat_on)
+ return false;
+ M_SizeDisplay(1);
+ S_StartSound(NULL,sfx_stnmov);
+ return true;
+ }
+ else if (key == key_menu_help) // Help key
+ {
+ M_StartControlPanel ();
+
+ if ( gamemode == retail )
+ currentMenu = &ReadDef2;
+ else
+ currentMenu = &ReadDef1;
+
+ itemOn = 0;
+ S_StartSound(NULL,sfx_swtchn);
+ return true;
+ }
+ else if (key == key_menu_save) // Save
+ {
+ M_StartControlPanel();
+ S_StartSound(NULL,sfx_swtchn);
+ M_SaveGame(0);
+ return true;
+ }
+ else if (key == key_menu_load) // Load
+ {
+ M_StartControlPanel();
+ S_StartSound(NULL,sfx_swtchn);
+ M_LoadGame(0);
+ return true;
+ }
+ else if (key == key_menu_volume) // Sound Volume
+ {
+ M_StartControlPanel ();
+ currentMenu = &SoundDef;
+ itemOn = sfx_vol;
+ S_StartSound(NULL,sfx_swtchn);
+ return true;
+ }
+ else if (key == key_menu_detail) // Detail toggle
+ {
+ M_ChangeDetail(0);
+ S_StartSound(NULL,sfx_swtchn);
+ return true;
+ }
+ else if (key == key_menu_qsave) // Quicksave
+ {
+ S_StartSound(NULL,sfx_swtchn);
+ M_QuickSave();
+ return true;
+ }
+ else if (key == key_menu_endgame) // End game
+ {
+ S_StartSound(NULL,sfx_swtchn);
+ M_EndGame(0);
+ return true;
+ }
+ else if (key == key_menu_messages) // Toggle messages
+ {
+ M_ChangeMessages(0);
+ S_StartSound(NULL,sfx_swtchn);
+ return true;
+ }
+ else if (key == key_menu_qload) // Quickload
+ {
+ S_StartSound(NULL,sfx_swtchn);
+ M_QuickLoad();
+ return true;
+ }
+ else if (key == key_menu_quit) // Quit DOOM
+ {
+ S_StartSound(NULL,sfx_swtchn);
+ M_QuitDOOM(0);
+ return true;
+ }
+ else if (key == key_menu_gamma) // gamma toggle
+ {
+ usegamma++;
+ if (usegamma > 4)
+ usegamma = 0;
+ players[consoleplayer].message = DEH_String(gammamsg[usegamma]);
+ I_SetPalette (W_CacheLumpName (DEH_String("PLAYPAL"),PU_CACHE));
+ return true;
+ }
+ }
+
+ // Pop-up menu?
+ if (!menuactive)
+ {
+ if (key == key_menu_activate)
+ {
+ M_StartControlPanel ();
+ S_StartSound(NULL,sfx_swtchn);
+ return true;
+ }
+ return false;
+ }
+
+ // Keys usable within menu
+
+ if (key == key_menu_down)
+ {
+ // Move down to next item
+
+ do
+ {
+ if (itemOn+1 > currentMenu->numitems-1)
+ itemOn = 0;
+ else itemOn++;
+ S_StartSound(NULL,sfx_pstop);
+ } while(currentMenu->menuitems[itemOn].status==-1);
+
+ return true;
+ }
+ else if (key == key_menu_up)
+ {
+ // Move back up to previous item
+
+ do
+ {
+ if (!itemOn)
+ itemOn = currentMenu->numitems-1;
+ else itemOn--;
+ S_StartSound(NULL,sfx_pstop);
+ } while(currentMenu->menuitems[itemOn].status==-1);
+
+ return true;
+ }
+ else if (key == key_menu_left)
+ {
+ // Slide slider left
+
+ if (currentMenu->menuitems[itemOn].routine &&
+ currentMenu->menuitems[itemOn].status == 2)
+ {
+ S_StartSound(NULL,sfx_stnmov);
+ currentMenu->menuitems[itemOn].routine(0);
+ }
+ return true;
+ }
+ else if (key == key_menu_right)
+ {
+ // Slide slider right
+
+ if (currentMenu->menuitems[itemOn].routine &&
+ currentMenu->menuitems[itemOn].status == 2)
+ {
+ S_StartSound(NULL,sfx_stnmov);
+ currentMenu->menuitems[itemOn].routine(1);
+ }
+ return true;
+ }
+ else if (key == key_menu_forward)
+ {
+ // Activate menu item
+
+ if (currentMenu->menuitems[itemOn].routine &&
+ currentMenu->menuitems[itemOn].status)
+ {
+ currentMenu->lastOn = itemOn;
+ if (currentMenu->menuitems[itemOn].status == 2)
+ {
+ currentMenu->menuitems[itemOn].routine(1); // right arrow
+ S_StartSound(NULL,sfx_stnmov);
+ }
+ else
+ {
+ currentMenu->menuitems[itemOn].routine(itemOn);
+ S_StartSound(NULL,sfx_pistol);
+ }
+ }
+ return true;
+ }
+ else if (key == key_menu_activate)
+ {
+ // Deactivate menu
+
+ currentMenu->lastOn = itemOn;
+ M_ClearMenus ();
+ S_StartSound(NULL,sfx_swtchx);
+ return true;
+ }
+ else if (key == key_menu_back)
+ {
+ // Go back to previous menu
+
+ currentMenu->lastOn = itemOn;
+ if (currentMenu->prevMenu)
+ {
+ currentMenu = currentMenu->prevMenu;
+ itemOn = currentMenu->lastOn;
+ S_StartSound(NULL,sfx_swtchn);
+ }
+ return true;
+ }
+
+ // Keyboard shortcut?
+ // Vanilla Doom has a weird behavior where it jumps to the scroll bars
+ // when the certain keys are pressed, so emulate this.
+
+ else if (ch != 0 || IsNullKey(key))
+ {
+ for (i = itemOn+1;i < currentMenu->numitems;i++)
+ {
+ if (currentMenu->menuitems[i].alphaKey == ch)
+ {
+ itemOn = i;
+ S_StartSound(NULL,sfx_pstop);
+ return true;
+ }
+ }
+
+ for (i = 0;i <= itemOn;i++)
+ {
+ if (currentMenu->menuitems[i].alphaKey == ch)
+ {
+ itemOn = i;
+ S_StartSound(NULL,sfx_pstop);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+
+//
+// M_StartControlPanel
+//
+void M_StartControlPanel (void)
+{
+ // intro might call this repeatedly
+ if (menuactive)
+ return;
+
+ menuactive = 1;
+ currentMenu = &MainDef; // JDC
+ itemOn = currentMenu->lastOn; // JDC
+}
+
+// Display OPL debug messages - hack for GENMIDI development.
+
+#if 0
+static void M_DrawOPLDev(void)
+{
+ extern void I_OPL_DevMessages(char *, size_t);
+ char debug[1024];
+ char *curr, *p;
+ int line;
+
+ //XXX I_OPL_DevMessages(debug, sizeof(debug));
+ curr = debug;
+ line = 0;
+
+ for (;;)
+ {
+ p = strchr(curr, '\n');
+
+ if (p != NULL)
+ {
+ *p = '\0';
+ }
+
+ M_WriteText(0, line * 8, curr);
+ ++line;
+
+ if (p == NULL)
+ {
+ break;
+ }
+
+ curr = p + 1;
+ }
+}
+#endif
+
+//
+// M_Drawer
+// Called after the view has been rendered,
+// but before it has been blitted.
+//
+void M_Drawer (void)
+{
+ static short x;
+ static short y;
+ unsigned int i;
+ unsigned int max;
+ char string[80];
+ char *name;
+ int start;
+
+ inhelpscreens = false;
+
+ // Horiz. & Vertically center string and print it.
+ if (messageToPrint)
+ {
+ start = 0;
+ y = SCREENHEIGHT/2 - M_StringHeight(messageString) / 2;
+ while (messageString[start] != '\0')
+ {
+ int foundnewline = 0;
+
+ for (i = 0; i < strlen(messageString + start); i++)
+ {
+ if (messageString[start + i] == '\n')
+ {
+ M_StringCopy(string, messageString + start,
+ sizeof(string));
+ if (i < sizeof(string))
+ {
+ string[i] = '\0';
+ }
+
+ foundnewline = 1;
+ start += i + 1;
+ break;
+ }
+ }
+
+ if (!foundnewline)
+ {
+ M_StringCopy(string, messageString + start, sizeof(string));
+ start += strlen(string);
+ }
+
+ x = SCREENWIDTH/2 - M_StringWidth(string) / 2;
+ M_WriteText(x, y, string);
+ y += SHORT(hu_font[0]->height);
+ }
+
+ return;
+ }
+
+ //if (opldev)
+ //{
+ // M_DrawOPLDev();
+ //}
+
+ if (!menuactive)
+ return;
+
+ if (currentMenu->routine)
+ currentMenu->routine(); // call Draw routine
+
+ // DRAW MENU
+ x = currentMenu->x;
+ y = currentMenu->y;
+ max = currentMenu->numitems;
+
+ for (i=0;imenuitems[i].name);
+
+ if (name[0])
+ {
+ V_DrawPatchDirect (x, y, W_CacheLumpName(name, PU_CACHE));
+ }
+ y += LINEHEIGHT;
+ }
+
+
+ // DRAW SKULL
+ V_DrawPatchDirect(x + SKULLXOFF, currentMenu->y - 5 + itemOn*LINEHEIGHT,
+ W_CacheLumpName(DEH_String(skullName[whichSkull]),
+ PU_CACHE));
+}
+
+
+//
+// M_ClearMenus
+//
+void M_ClearMenus (void)
+{
+ menuactive = 0;
+ // if (!netgame && usergame && paused)
+ // sendpause = true;
+}
+
+
+
+
+//
+// M_SetupNextMenu
+//
+void M_SetupNextMenu(menu_t *menudef)
+{
+ currentMenu = menudef;
+ itemOn = currentMenu->lastOn;
+}
+
+
+//
+// M_Ticker
+//
+void M_Ticker (void)
+{
+ if (--skullAnimCounter <= 0)
+ {
+ whichSkull ^= 1;
+ skullAnimCounter = 8;
+ }
+}
+
+
+//
+// M_Init
+//
+void M_Init (void)
+{
+ currentMenu = &MainDef;
+ menuactive = 0;
+ itemOn = currentMenu->lastOn;
+ whichSkull = 0;
+ skullAnimCounter = 10;
+ screenSize = screenblocks - 3;
+ messageToPrint = 0;
+ messageString = NULL;
+ messageLastMenuActive = menuactive;
+ quickSaveSlot = -1;
+
+ // Here we could catch other version dependencies,
+ // like HELP1/2, and four episodes.
+
+
+ switch ( gamemode )
+ {
+ case commercial:
+ // Commercial has no "read this" entry.
+ MainMenu[readthis] = MainMenu[quitdoom];
+ MainDef.numitems--;
+ MainDef.y += 8;
+ NewDef.prevMenu = &MainDef;
+ break;
+ case shareware:
+ // Episode 2 and 3 are handled,
+ // branching to an ad screen.
+ case registered:
+ break;
+ case retail:
+ // We are fine.
+ default:
+ break;
+ }
+
+ // Versions of doom.exe before the Ultimate Doom release only had
+ // three episodes; if we're emulating one of those then don't try
+ // to show episode four. If we are, then do show episode four
+ // (should crash if missing).
+ if (gameversion < exe_ultimate)
+ {
+ EpiDef.numitems--;
+ }
+
+ //opldev = M_CheckParm("-opldev") > 0;
+}
+
diff --git a/firmware_p4/components/Applications/doom/m_menu.h b/firmware_p4/components/Applications/doom/m_menu.h
new file mode 100644
index 000000000..ce41db39a
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/m_menu.h
@@ -0,0 +1,61 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Menu widget stuff, episode selection and such.
+//
+
+
+#ifndef __M_MENU__
+#define __M_MENU__
+
+
+
+#include "d_event.h"
+
+//
+// MENUS
+//
+// Called by main loop,
+// saves config file and calls I_Quit when user exits.
+// Even when the menu is not displayed,
+// this can resize the view and change game parameters.
+// Does all the real work of the menu interaction.
+boolean M_Responder (event_t *ev);
+
+
+// Called by main loop,
+// only used for menu (skull cursor) animation.
+void M_Ticker (void);
+
+// Called by main loop,
+// draws the menus directly into the screen buffer.
+void M_Drawer (void);
+
+// Called by D_DoomMain,
+// loads the config file.
+void M_Init (void);
+
+// Called by intro code to force menu up upon a keypress,
+// does nothing if menu is already up.
+void M_StartControlPanel (void);
+
+
+
+extern int detailLevel;
+extern int screenblocks;
+
+
+
+#endif
diff --git a/firmware_p4/components/Applications/doom/m_misc.c b/firmware_p4/components/Applications/doom/m_misc.c
new file mode 100644
index 000000000..54b261344
--- /dev/null
+++ b/firmware_p4/components/Applications/doom/m_misc.c
@@ -0,0 +1,536 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// DESCRIPTION:
+// Miscellaneous.
+//
+
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#include
+#include
+#ifdef _MSC_VER
+#include
+#endif
+#else
+#include