Compare commits

...

3 Commits

Author SHA1 Message Date
d4rks1d33 492cee4373 Fix frezze on exit FlipperDoom
Build Dev Firmware / build (push) Has been cancelled
2026-07-02 22:17:59 -03:00
d4rks1d33 a0c53e381c Added better Doom for fun xD
Build Dev Firmware / build (push) Has been cancelled
2026-07-02 20:50:10 -03:00
d4rks1d33 46d7e1263c Updated ProtoPirate, new Zero-Mega version (amazing updates)
Build Dev Firmware / build (push) Successful in 17m44s
2026-06-30 20:47:24 -03:00
147 changed files with 16046 additions and 3033 deletions
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2019 James Howard (original)
Copyright (c) 2025 Apfxtech (Flipper Zero port)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+177
View File
@@ -0,0 +1,177 @@
# Flipper DOOM
A DOOM-style first-person shooter demake for the Flipper Zero
(128×64 monochrome LCD). Built on the raycaster engine from
[FlipperCatacombs](https://github.com/apfxtech/FlipperCatacombs) (a port of
*Catacombs of the Damned* / arduboy3d by jhhoward), re-skinned with graphics
converted **at build time from your own DOOM shareware WAD**.
---
## Quick Start
1. Copy `dist/flipdoom.fap` to your SD card under `SD/apps/Games/`
(or run `ufbt launch` with the Flipper connected via USB).
2. On the Flipper: **Apps → Games → Flipper DOOM**.
3. Title screen → main menu → select **Play** with OK.
4. Fight through procedurally generated levels, find the exit gate on each
floor, survive as deep as you can. Your score and high score are saved
automatically.
---
## Controls
### Title screen
| Button | Action |
|---|---|
| Any button | Skip the title screen |
### Main menu
| Button | Action |
|---|---|
| **Up / Down** | Move cursor (Play / Sound on-off / Score / High score) |
| **OK** | Select item |
| **Back (hold ~300 ms)** | Exit the app |
### In game
| Button | Action |
|---|---|
| **Up / Down** | Move forward / backward |
| **Left / Right** | Turn left / right |
| **OK** | Fire the shotgun (hold for continuous fire) |
| **OK held + Left / Right** | **Strafe** (circle-strafe while firing) — essential for dodging fireballs |
| **Back (hold ~300 ms)** | Leave the game and return to the menu |
Notes:
- Firing costs ammo (bottom HUD bar); ammo regenerates when not shooting.
- Strafing only works while OK is held. Tap OK to fire while turning
normally; hold OK to switch the side arrows into dodge mode.
- Walking into a backpack opens it; walking over pickups collects them.
### HUD
- **Bottom bar with cross icon** — health.
- **Bar above it with bullets icon** — ammo.
- Screen border flashes when you take damage.
- Pickup/event messages appear at the top of the screen.
---
## Gameplay
### Enemies
| Enemy | Behavior |
|---|---|
| **Zombieman** | Weak, shoots from range, appears from level 1 |
| **Sergeant** | Fast, shoots hard, keeps distance |
| **Imp** | Throws fireballs, backs away if you get close |
| **Demon** | Melee tank — charges you and bites |
Difficulty scales with depth: early levels spawn mostly zombies; imps join
from level 3, and demons dominate from level 6.
### Exploding barrels
Shooting a barrel triggers an area explosion that deals heavy damage to every
enemy nearby (one-shots zombies and sergeants) and hurts you if you stand too
close — though it will never kill you outright, it can leave you at 1 HP.
Barrels sometimes leave a pickup behind.
### Pickups
| Item | Effect |
|---|---|
| Stimpack | Restores health |
| Backpack (walk into it) | Bonus points |
| Armor / helmet / bonus items | Points |
### Scoring
Points are awarded for floors cleared, kills per enemy type, and items
collected. Escaping the base (final exit) grants a large bonus. Score and
high score are stored on the SD card (`apps_data/flipdoom/`) and persist
between sessions.
---
## Sound
Tone-based sound effects through the Flipper buzzer (shotgun blast, hits,
kills, pickups, player damage), all tuned within the buzzer's physical
1002500 Hz range. Sound can be toggled in the main menu; the system
Stealth Mode is respected.
---
## Building from source
Requires Python 3 and [ufbt](https://pypi.org/project/ufbt/):
```sh
pip install ufbt
cd FlipperDoom
# 1) Generate the sprite header from YOUR shareware WAD (not included here):
python3 tools/extract_doom_assets.py /path/to/Doom1.WAD
# 2) Build / install:
ufbt # produces dist/flipdoom.fap
ufbt launch # builds, installs and runs on a connected Flipper
```
### About the assets
No id Software assets are stored in this repository. The generated header
`game/Generated/DoomSprites.inc.h` is produced locally by
`tools/extract_doom_assets.py`, which decodes sprites from the user's own
shareware WAD (freely distributable as a whole), rescales them and quantizes
to 1-bit (black / 50% checker / white) in the engine's sprite formats.
Do not redistribute the generated header — always regenerate it from a WAD
you own. The "DOOM" title lettering and the HUD icons are original pixel art
made for this project. Preview images of the converted assets are written to
`tools/preview/`.
Entity mapping (game mechanics are inherited from the Catacombs engine):
| Engine entity | DOOM sprite | Role |
|---|---|---|
| Skeleton | Demon (SARG) | melee tank |
| Mage | Imp (TROO) | fireball thrower |
| Bat | Sergeant (SPOS) | fast shooter |
| Spider | Zombieman (POSS) | weak shooter |
| Weapon | Shotgun (SHTG + SHTF flash) | first person, centered |
| Urn | Barrel (BAR1) | explodes when shot |
| Potion | Stimpack (STIM) | health |
| Chest / opened | Backpack (BPAK) / Clip (CLIP) | treasure |
| Crown / scroll / coins | Armor / helmet / potion bottle (ARM1, BON2, BON1) | points |
| Sign | Skull pile (POL5) | decoration |
| Projectiles | Fireball (BAL1) | player & enemies |
## Why not a real doomgeneric port?
The hardware makes it impossible — not a software choice:
| | Flipper Zero | Real DOOM (doomgeneric) |
|---|---|---|
| Total RAM | 256 KB | — |
| Free heap for apps | ~140 KB | ~7 MB (zone memory + framebuffer) |
| Engine binary | FAPs load fully into RAM | ~524 KB compiled for Cortex-M4 |
The Flipper cannot execute code from the SD card (no XIP), so it can't even
load the DOOM engine binary. DOOM ports to 256 KB microcontrollers (GBA,
nRF52840) rely on memory-mapped flash, which the Flipper does not have. This
demake keeps the aesthetic with an engine that actually fits: ~30 KB loaded,
30 FPS on the 64 MHz Cortex-M4.
## License
Same license as the base project (see `LICENSE`). WAD contents are property
of id Software; the extraction tool only transforms them locally for
personal use.
@@ -0,0 +1,15 @@
App(
appid="flipdoom",
name="Flipper DOOM",
apptype=FlipperAppType.EXTERNAL,
entry_point="flipdoom_app",
cdefines=["APP_FLIPDOOM"],
requires=["gui"],
stack_size=8 * 1024,
fap_category="Games",
fap_icon="flipdoom_icon.png",
order=36,
fap_author="user",
fap_version="1.0",
fap_description="Doom-style raycaster demake for Flipper Zero. Graphics converted at build time from your own Doom shareware WAD.",
)
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

@@ -0,0 +1,87 @@
#pragma once
#include <cstdint>
#include <cstring>
static inline const void* pgm_read_ptr_safe(const void* p) {
const void* out;
std::memcpy(&out, p, sizeof(out));
return out;
}
// Platform detection
#if defined(_WIN32)
#include <stdint.h>
#include <string.h>
#define PROGMEM
#define PSTR(s) (s)
#define pgm_read_byte(x) (*((uint8_t*)(x)))
#define pgm_read_word(x) (*((uint16_t*)(x)))
#define pgm_read_ptr(x) (*((uintptr_t*)(x)))
#define strlen_P(x) strlen(x)
#define strcpy_P(dst, src) strcpy(dst, src)
#define memcpy_P(dst, src, n) memcpy(dst, src, n)
#elif defined(__AVR__)
// Arduino/Arduboy platform
#include <avr/pgmspace.h>
#else
// Flipper Zero and other ARM platforms
#include <stdint.h>
#include <string.h>
#define PROGMEM
#define PSTR(s) (s)
#define pgm_read_byte(x) (*((const uint8_t*)(x)))
#define pgm_read_word(x) (*((const uint16_t*)(x)))
#define pgm_read_dword(x) (*((const uint32_t*)(x)))
#define strlen_P(x) strlen(x)
#define strcpy_P(dst, src) strcpy(dst, src)
#define strcmp_P(s1, s2) strcmp(s1, s2)
#define strncmp_P(s1, s2, n) strncmp(s1, s2, n)
#define memcpy_P(dst, src, n) memcpy(dst, src, n)
#define sprintf_P sprintf
#define snprintf_P snprintf
#endif
// Display configuration
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
// Game settings
#define DEV_MODE 0
// Input definitions
#define INPUT_LEFT 1
#define INPUT_RIGHT 2
#define INPUT_UP 4
#define INPUT_DOWN 8
#define INPUT_A 16
#define INPUT_B 32
#ifndef COLOUR_WHITE
#define COLOUR_WHITE 1
#endif
#ifndef COLOUR_BLACK
#define COLOUR_BLACK 0
#endif
// Angle system (256 = 360 degrees)
#define FIXED_ANGLE_MAX 256
// 3D rendering settings
#define CAMERA_SCALE 1
#define CLIP_PLANE 32
#define CLIP_ANGLE 32
#define NEAR_PLANE_MULTIPLIER 130
#define NEAR_PLANE (DISPLAY_WIDTH * NEAR_PLANE_MULTIPLIER / 256)
#define HORIZON (DISPLAY_HEIGHT / 2)
// World settings
#define CELL_SIZE 256
#define PARTICLES_PER_SYSTEM 8
#define BASE_SPRITE_SIZE 16
#define MAX_SPRITE_SIZE (DISPLAY_HEIGHT / 2)
#define MIN_TEXTURE_DISTANCE 4
#define MAX_QUEUED_DRAWABLES 12
// Player settings
#define TURN_SPEED 3
File diff suppressed because it is too large Load Diff
+146
View File
@@ -0,0 +1,146 @@
#pragma once
#include "game/Defines.h"
#define WITH_IMAGE_TEXTURES 0
#define WITH_VECTOR_TEXTURES 1
#define WITH_TEXTURES (WITH_IMAGE_TEXTURES || WITH_VECTOR_TEXTURES)
#define WITH_SPRITE_OUTLINES 1
struct Camera {
int16_t x, y;
uint8_t angle;
int16_t rotCos, rotSin;
int16_t clipCos, clipSin;
uint8_t cellX, cellY;
int8_t tilt;
int8_t bob;
uint8_t shakeTime;
};
enum class DrawableType : uint8_t {
Sprite = 0,
ParticleSystem = 1
};
enum class AnchorType : uint8_t {
Floor,
Center,
BelowCenter,
Ceiling
};
struct QueuedDrawable {
union {
const uint16_t* spriteData;
struct ParticleSystem* particleSystem;
};
DrawableType type : 1;
bool invert : 1;
int8_t x;
int8_t y;
uint8_t halfSize;
uint8_t inverseCameraDistance;
};
class Renderer {
public:
static Camera camera;
static uint8_t wBuffer[DISPLAY_WIDTH];
static uint8_t globalRenderFrame;
static void Render();
static void DrawObject(
const uint16_t* spriteData,
int16_t x,
int16_t y,
uint8_t scale = 128,
AnchorType anchor = AnchorType::Floor,
bool invert = false);
static QueuedDrawable* CreateQueuedDrawable(uint8_t inverseCameraDistance);
static int8_t GetHorizon(int16_t x);
static bool
TransformAndCull(int16_t worldX, int16_t worldY, int16_t& outScreenX, int16_t& outScreenW);
static void DrawScaled(
const uint16_t* data,
int8_t x,
int8_t y,
uint8_t halfSize,
uint8_t inverseCameraDistance,
bool invert = false,
uint8_t color = COLOUR_BLACK);
static int8_t horizonBuffer[DISPLAY_WIDTH];
static uint8_t numBufferSlicesFilled;
static void DrawWallLine(
int16_t x1,
int16_t y1,
int16_t x2,
int16_t y2,
uint8_t clipLeft,
uint8_t clipRight,
uint8_t col);
static void DrawWallSegment(
const uint8_t* texture,
int16_t x1,
int16_t w1,
int16_t x2,
int16_t w2,
uint8_t u1clip,
uint8_t u2clip,
bool edgeLeft,
bool edgeRight,
bool shadeEdge);
static void DrawWall(
const uint8_t* texture,
int16_t x1,
int16_t y1,
int16_t x2,
int16_t y2,
bool edgeLeft,
bool edgeRight,
bool shadeEdge);
static void DrawBackground();
static uint8_t numQueuedDrawables;
static bool isFrustrumClipped(int16_t x, int16_t y);
private:
static QueuedDrawable queuedDrawables[MAX_QUEUED_DRAWABLES];
// #if WITH_IMAGE_TEXTURES
// static void DrawWallSegment(const uint16_t* texture, int16_t x1, int16_t w1, int16_t x2, int16_t w2, uint8_t u1clip, uint8_t u2clip, bool edgeLeft, bool edgeRight, bool shadeEdge);
// static void DrawWall(const uint16_t* texture, int16_t x1, int16_t y1, int16_t x2, int16_t y2, bool edgeLeft, bool edgeRight, bool shadeEdge);
// #elif WITH_VECTOR_TEXTURES
// static void DrawWallLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint8_t clipLeft, uint8_t clipRight, uint8_t col);
// static void DrawWallSegment(const uint8_t* texture, int16_t x1, int16_t w1, int16_t x2, int16_t w2, uint8_t u1clip, uint8_t u2clip, bool edgeLeft, bool edgeRight, bool shadeEdge);
// static void DrawWall(const uint8_t* texture, int16_t x1, int16_t y1, int16_t x2, int16_t y2, bool edgeLeft, bool edgeRight, bool shadeEdge);
// #else
// static void DrawWallSegment(int16_t x1, int16_t w1, int16_t x2, int16_t w2, bool edgeLeft, bool edgeRight, bool shadeEdge);
// static void DrawWall(int16_t x1, int16_t y1, int16_t x2, int16_t y2, bool edgeLeft, bool edgeRight, bool shadeEdge);
// #endif
static void DrawFloorLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2);
static void DrawFloorLineInner(int16_t x1, int16_t y1, int16_t x2, int16_t y2);
static void DrawFloorLines();
static void TransformToViewSpace(int16_t x, int16_t y, int16_t& outX, int16_t& outY);
static void TransformToScreenSpace(int16_t viewX, int16_t viewZ, int16_t& outX, int16_t& outW);
static void DrawCell(uint8_t x, uint8_t y);
static void DrawCells();
static void DrawWeapon();
static void DrawHUD();
static void DrawBar(uint8_t* screenPtr, const uint8_t* iconData, uint8_t amount, uint8_t max);
static void DrawDamageIndicator();
static void QueueSprite(
const uint16_t* data,
int8_t x,
int8_t y,
uint8_t halfSize,
uint8_t inverseCameraDistance,
bool invert = false);
static void RenderQueuedDrawables();
};
@@ -0,0 +1,484 @@
#include "game/Enemy.h"
#include "game/Defines.h"
#include "game/Draw.h"
#include "game/Map.h"
#include "game/FixedMath.h"
#include "game/Game.h"
#include "game/Projectile.h"
#include "game/Generated/SpriteTypes.h"
#include "game/Sounds.h"
#include "game/Platform.h"
#include "game/Particle.h"
Enemy EnemyManager::enemies[maxEnemies];
const EnemyArchetype Enemy::archetypes[(int)EnemyType::NumEnemyTypes] PROGMEM =
{
{
// Skeleton
skeletonSpriteData,
50, // hp
4, // speed
20, // attackStrength
3, // attackDuration
2, // stunDuration
false, // isRanged
96, // sprite scale
AnchorType::Floor // sprite anchor
},
{
// Mage
mageSpriteData,
30, // hp
5, // speed
20, // attackStrength
3, // attackDuration
2, // stunDuration
true, // isRanged
96, // sprite scale
AnchorType::Floor // sprite anchor
},
{
// Bat -> shotgun sergeant (ranged, hits hard)
batSpriteData,
20, // hp
6, // speed
10, // attackStrength
2, // attackDuration
0, // stunDuration
true, // isRanged
90, // sprite scale
AnchorType::Floor // sprite anchor
},
{
// Spider -> zombieman (ranged, weak)
spiderSpriteData,
10, // hp
5, // speed
4, // attackStrength
1, // attackDuration
0, // stunDuration
true, // isRanged
80, // sprite scale
AnchorType::Floor // sprite anchor
}
};
void Enemy::Init(EnemyType initType, int16_t initX, int16_t initY)
{
state = EnemyState::Idle;
type = initType;
x = initX;
y = initY;
frameDelay = 0;
targetCellX = x / CELL_SIZE;
targetCellY = y / CELL_SIZE;
hp = GetArchetype()->GetHP();
}
void Enemy::Damage(uint8_t amount)
{
if (amount >= hp)
{
Game::stats.enemyKills[(int)type]++;
type = EnemyType::None;
Platform::PlaySound(Sounds::Kill);
ParticleSystemManager::CreateExplosion(x, y, true);
}
else
{
hp -= amount;
Platform::PlaySound(Sounds::Hit);
state = EnemyState::Stunned;
frameDelay = GetArchetype()->GetStunDuration();
}
}
const EnemyArchetype* Enemy::GetArchetype() const
{
if (type == EnemyType::None)
return nullptr;
return &archetypes[(int)type];
}
int16_t Clamp(int16_t x, int16_t min, int16_t max)
{
if(x < min)
return min;
if(x > max)
return max;
return x;
}
bool Enemy::TryPickCell(int8_t newX, int8_t newY)
{
if(Map::IsBlocked(newX, newY))// && !engine.map.isDoor(newX, newZ))
return false;
if(Map::IsBlocked(targetCellX, newY)) // && !engine.map.isDoor(targetCellX, newZ))
return false;
if(Map::IsBlocked(newX, targetCellY)) // && !engine.map.isDoor(newX, targetCellZ))
return false;
for (Enemy& other : EnemyManager::enemies)
{
if(this != &other && other.IsValid())
{
if(other.targetCellX == newX && other.targetCellY == newY)
return false;
}
}
targetCellX = newX;
targetCellY = newY;
return true;
}
bool Enemy::TryPickCells(int8_t deltaX, int8_t deltaY)
{
return TryPickCell(targetCellX + deltaX, targetCellY + deltaY)
|| TryPickCell(targetCellX + deltaX, targetCellY)
|| TryPickCell(targetCellX, targetCellY + deltaY)
|| TryPickCell(targetCellX - deltaX, targetCellY + deltaY)
|| TryPickCell(targetCellX + deltaX, targetCellY - deltaY);
}
uint8_t Enemy::GetPlayerCellDistance() const
{
uint8_t dx = ABS(Game::player.x - x) / CELL_SIZE;
uint8_t dy = ABS(Game::player.y - y) / CELL_SIZE;
return dx > dy ? dx : dy;
}
void Enemy::PickNewTargetCell()
{
int8_t deltaX = (int8_t) Clamp((Game::player.x / CELL_SIZE) - targetCellX, -1, 1);
int8_t deltaY = (int8_t) Clamp((Game::player.y / CELL_SIZE) - targetCellY, -1, 1);
uint8_t dodgeChance = (uint8_t) Random();
if (GetArchetype()->GetIsRanged() && GetPlayerCellDistance() < 3)
{
deltaX = -deltaX;
deltaY = -deltaY;
}
if(deltaX == 0)
{
if(dodgeChance < 64)
{
deltaX = -1;
}
else if(dodgeChance < 128)
{
deltaX = 1;
}
}
else if(deltaY == 0)
{
if(dodgeChance < 64)
{
deltaY = -1;
}
else if(dodgeChance < 128)
{
deltaY = 1;
}
}
TryPickCells(deltaX, deltaY);
}
void Enemy::StunMove()
{
//int16_t targetX = Game::player.x;
//int16_t targetY = Game::player.y;
//
//int16_t maxDelta = 3;
//
//int16_t deltaX = Clamp(targetX - x, -maxDelta, maxDelta);
//int16_t deltaY = Clamp(targetY - y, -maxDelta, maxDelta);
//
//x -= deltaX;
//y -= deltaY;
// int16_t deltaX = (Random() % 16) - 8;
// int16_t deltaY = (Random() % 16) - 8;
// x += deltaX;
// y += deltaY;
}
bool Enemy::TryMove()
{
if(Map::IsSolid(targetCellX, targetCellY))
{
//engine.map.openDoorsAt(targetCellX, targetCellZ, Direction_None);
return false;
}
int16_t targetX = (targetCellX * CELL_SIZE) + CELL_SIZE / 2;
int16_t targetY = (targetCellY * CELL_SIZE) + CELL_SIZE / 2;
int16_t maxDelta = GetArchetype()->GetMovementSpeed();
int16_t deltaX = Clamp(targetX - x, -maxDelta, maxDelta);
int16_t deltaY = Clamp(targetY - y, -maxDelta, maxDelta);
x += deltaX;
y += deltaY;
if(IsOverlappingEntity(Game::player))
{
if (!GetArchetype()->GetIsRanged())
{
Game::player.Damage(GetArchetype()->GetAttackStrength());
if (Game::player.hp == 0)
{
Game::stats.killedBy = type;
}
state = EnemyState::Attacking;
frameDelay = GetArchetype()->GetAttackDuration();
}
x -= deltaX;
y -= deltaY;
return false;
}
if(x == targetX && y == targetY)
{
PickNewTargetCell();
}
return true;
}
bool Enemy::FireProjectile(uint8_t angle)
{
return ProjectileManager::FireProjectile(this, x, y, angle) != nullptr;
}
bool Enemy::TryFireProjectile()
{
int8_t deltaX = (Game::player.x - x) / CELL_SIZE;
int8_t deltaY = (Game::player.y - y) / CELL_SIZE;
if (deltaX == 0)
{
if (deltaY < 0)
{
return FireProjectile(FIXED_ANGLE_270);
}
else if (deltaY > 0)
{
return FireProjectile(FIXED_ANGLE_90);
}
}
else if (deltaY == 0)
{
if (deltaX < 0)
{
return FireProjectile(FIXED_ANGLE_180);
}
else if (deltaX > 0)
{
return FireProjectile(0);
}
}
else if (deltaX == deltaY)
{
if (deltaX > 0)
{
return FireProjectile(FIXED_ANGLE_45);
}
else
{
return FireProjectile(FIXED_ANGLE_180 + FIXED_ANGLE_45);
}
}
else if (deltaX == -deltaY)
{
if (deltaX > 0)
{
return FireProjectile(FIXED_ANGLE_270 + FIXED_ANGLE_45);
}
else
{
return FireProjectile(FIXED_ANGLE_90 + FIXED_ANGLE_45);
}
}
return false;
}
bool Enemy::ShouldFireProjectile() const
{
uint8_t distance = GetPlayerCellDistance();
uint8_t chance = 16 / (distance > 0 ? distance : 1);
return GetArchetype()->GetIsRanged() && (Random() & 0xff) < chance && Map::IsClearLine(x, y, Game::player.x, Game::player.y);
}
void Enemy::Tick()
{
if (state == EnemyState::Stunned)
{
StunMove();
}
if (frameDelay > 0)
{
if ((Game::globalTickFrame & 0xf) == 0)
{
frameDelay--;
}
return;
}
switch (state)
{
case EnemyState::Idle:
if (Map::IsClearLine(x, y, Game::player.x, Game::player.y))
{
Platform::PlaySound(Sounds::SpotPlayer);
state = EnemyState::Moving;
}
break;
case EnemyState::Moving:
TryMove();
if (ShouldFireProjectile())
{
if (TryFireProjectile())
{
Platform::PlaySound(Sounds::Shoot);
state = EnemyState::Attacking;
frameDelay = GetArchetype()->GetAttackDuration();
}
}
break;
case EnemyState::Attacking:
state = EnemyState::Moving;
break;
case EnemyState::Stunned:
state = EnemyState::Moving;
break;
default:
break;
}
}
void EnemyManager::Init()
{
for (Enemy& enemy : enemies)
{
enemy.Clear();
}
}
void EnemyManager::Update()
{
for (Enemy& enemy : enemies)
{
if(enemy.IsValid())
{
enemy.Tick();
}
}
}
void EnemyManager::Draw()
{
for(Enemy& enemy : enemies)
{
if(enemy.IsValid())
{
bool invert = enemy.GetState() == EnemyState::Stunned && (Renderer::globalRenderFrame & 1);
int frameOffset = (enemy.GetType() == EnemyType::Bat || enemy.GetState() == EnemyState::Moving) && (Game::globalTickFrame & 8) == 0 ? 32 : 0;
const EnemyArchetype* archetype = enemy.GetArchetype();
Renderer::DrawObject(archetype->GetSpriteData() + frameOffset, enemy.x, enemy.y, archetype->GetSpriteScale(), archetype->GetSpriteAnchor(), invert);
}
}
}
void EnemyManager::Spawn(EnemyType enemyType, int16_t x, int16_t y)
{
for (Enemy& enemy : enemies)
{
if(!enemy.IsValid())
{
enemy.Init(enemyType, x, y);
return;
}
}
}
// Doom-like difficulty curve: early levels are mostly zombiemen,
// deeper levels bring sergeants, imps and finally demons
static EnemyType PickEnemyType()
{
uint8_t roll = (uint8_t)(Random() % 100);
uint8_t f = Game::floor;
if (f < 3)
{
if (roll < 55) return EnemyType::Spider; // zombieman
if (roll < 85) return EnemyType::Bat; // sergeant
return EnemyType::Mage; // imp
}
if (f < 6)
{
if (roll < 30) return EnemyType::Spider;
if (roll < 55) return EnemyType::Bat;
if (roll < 85) return EnemyType::Mage;
return EnemyType::Skeleton; // demon
}
if (roll < 15) return EnemyType::Spider;
if (roll < 40) return EnemyType::Bat;
if (roll < 70) return EnemyType::Mage;
return EnemyType::Skeleton;
}
void EnemyManager::SpawnEnemies()
{
for (uint8_t y = 0; y < Map::height; y++)
{
for (uint8_t x = 0; x < Map::width; x++)
{
if (Map::GetCellSafe(x, y) == CellType::Monster)
{
EnemyManager::Spawn(PickEnemyType(), x * CELL_SIZE + CELL_SIZE / 2, y * CELL_SIZE + CELL_SIZE / 2);
Map::SetCell(x, y, CellType::Empty);
break;
}
}
}
}
Enemy* EnemyManager::GetOverlappingEnemy(Entity& entity)
{
for (Enemy& enemy : enemies)
{
if (enemy.IsValid() && enemy.IsOverlappingEntity(entity))
{
return &enemy;
}
}
return nullptr;
}
Enemy* EnemyManager::GetOverlappingEnemy(int16_t x, int16_t y)
{
for (Enemy& enemy : enemies)
{
if (enemy.IsValid() && enemy.IsOverlappingPoint(x, y))
{
return &enemy;
}
}
return nullptr;
}
@@ -0,0 +1,100 @@
#pragma once
#include <stdint.h>
#include "game/Entity.h"
#include "game/Defines.h"
#include "game/Draw.h"
enum class EnemyType : uint8_t
{
Skeleton,
Mage,
Bat,
Spider,
NumEnemyTypes,
None = NumEnemyTypes,
Exit,
};
enum class EnemyState : uint8_t
{
Idle,
Moving,
Attacking,
Stunned,
Dying,
Dead
};
struct EnemyArchetype
{
const uint16_t* spriteData;
uint8_t hp;
uint8_t movementSpeed;
uint8_t attackStrength;
uint8_t attackDuration;
uint8_t stunDuration;
uint8_t isRanged;
uint8_t spriteScale;
AnchorType spriteAnchor;
const uint16_t* GetSpriteData() const {return static_cast<const uint16_t*>(pgm_read_ptr_safe(&spriteData));}
uint8_t GetHP() const { return pgm_read_byte(&hp); }
uint8_t GetMovementSpeed() const { return pgm_read_byte(&movementSpeed); }
uint8_t GetAttackStrength() const { return pgm_read_byte(&attackStrength); }
uint8_t GetAttackDuration() const { return pgm_read_byte(&attackDuration); }
uint8_t GetStunDuration() const { return pgm_read_byte(&stunDuration); }
bool GetIsRanged() const { return pgm_read_byte(&isRanged) != 0; }
uint8_t GetSpriteScale() const { return pgm_read_byte(&spriteScale); }
AnchorType GetSpriteAnchor() const { return (AnchorType) pgm_read_byte(&spriteAnchor); }
};
class Enemy : public Entity
{
public:
void Init(EnemyType type, int16_t x, int16_t y);
void Tick();
bool IsValid() const { return type != EnemyType::None; }
void Damage(uint8_t amount);
void Clear() { type = EnemyType::None; }
const EnemyArchetype* GetArchetype() const;
EnemyState GetState() const { return state; }
EnemyType GetType() const { return type; }
private:
static const EnemyArchetype archetypes[(int)EnemyType::NumEnemyTypes];
bool ShouldFireProjectile() const;
bool FireProjectile(uint8_t angle);
bool TryMove();
void StunMove();
bool TryFireProjectile();
void PickNewTargetCell();
bool TryPickCells(int8_t deltaX, int8_t deltaY);
bool TryPickCell(int8_t newX, int8_t newY);
uint8_t GetPlayerCellDistance() const;
EnemyType type : 3;
EnemyState state : 3;
uint8_t frameDelay : 2;
uint8_t hp;
uint8_t targetCellX, targetCellY;
};
class EnemyManager
{
public:
static constexpr int maxEnemies = 24; //24;
static Enemy enemies[maxEnemies];
static void Spawn(EnemyType enemyType, int16_t x, int16_t y);
static void SpawnEnemies();
static Enemy* GetOverlappingEnemy(Entity& entity);
static Enemy* GetOverlappingEnemy(int16_t x, int16_t y);
static void Init();
static void Draw();
static void Update();
};
@@ -0,0 +1,17 @@
#include "game/Entity.h"
#include "game/Game.h"
#include "game/Map.h"
#define ENTITY_SIZE 192
bool Entity::IsOverlappingEntity(const Entity& other) const
{
return (x >= other.x - ENTITY_SIZE && x <= other.x + ENTITY_SIZE
&& y >= other.y - ENTITY_SIZE && y <= other.y + ENTITY_SIZE);
}
bool Entity::IsOverlappingPoint(int16_t pointX, int16_t pointY) const
{
return (pointX >= x - ENTITY_SIZE / 2 && pointX <= x + ENTITY_SIZE / 2
&& pointY >= y - ENTITY_SIZE / 2 && pointY <= y + ENTITY_SIZE / 2);
}
@@ -0,0 +1,12 @@
#pragma once
#include <stdint.h>
class Entity
{
public:
bool IsOverlappingPoint(int16_t pointX, int16_t pointY) const;
bool IsOverlappingEntity(const Entity& other) const;
int16_t x, y;
};
@@ -0,0 +1,19 @@
// #include "FixedMath.h"
// static uint16_t xs = 1;
// uint16_t Random() {
// xs ^= xs << 7;
// xs ^= xs >> 9;
// xs ^= xs << 8;
// return xs;
// }
// void SeedRandom() {
// uint32_t r = furi_hal_random_get();
// xs = (uint16_t)(r ^ (r >> 16));
// }
// void SeedRandom(uint16_t seed) {
// xs = seed | 1;
// }
@@ -0,0 +1,46 @@
#pragma once
#include <stdint.h>
#include <furi.h>
#include <furi_hal.h>
#if defined(_WIN32)
#include <math.h>
#endif
#include "game/Defines.h"
#define FIXED_SHIFT 8
#define FIXED_ONE (1 << FIXED_SHIFT)
#define INT_TO_FIXED(x) ((x) * FIXED_ONE)
#define FIXED_TO_INT(x) ((x) >> 8)
#define FLOAT_TO_FIXED(x) ((int16_t)((x) * FIXED_ONE))
#ifndef ABS
#define ABS(x) (((x) < 0) ? -(x) : (x))
#endif
#define FIXED_ANGLE_MAX 256
#define FIXED_ANGLE_WRAP(x) ((x) & 255)
#define FIXED_ANGLE_45 (FIXED_ANGLE_90 / 2)
#define FIXED_ANGLE_90 (FIXED_ANGLE_MAX / 4)
#define FIXED_ANGLE_180 (FIXED_ANGLE_90 * 2)
#define FIXED_ANGLE_270 (FIXED_ANGLE_90 * 3)
#define FIXED_ANGLE_TO_RADIANS(x) ((x) * (2.0f * 3.141592654f / FIXED_ANGLE_MAX))
extern const int16_t sinTable[FIXED_ANGLE_MAX] PROGMEM;
inline int16_t FixedSin(uint8_t angle) {
return pgm_read_word(&sinTable[angle]);
}
inline int16_t FixedCos(uint8_t angle) {
return pgm_read_word(&sinTable[FIXED_ANGLE_WRAP(FIXED_ANGLE_90 - angle)]);
}
// uint16_t Random();
// void SeedRandom();
// void SeedRandom(uint16_t seed);
inline uint16_t Random() {
uint32_t r = furi_hal_random_get();
return (uint16_t)(r ^ (r >> 16));
}
@@ -0,0 +1,115 @@
#include <stdint.h>
#include "game/Defines.h"
#include "game/Font.h"
#include "game/Platform.h"
#include "game/Generated/SpriteTypes.h"
static inline uint8_t v3(uint8_t m)
{
return m | (m << 1) | (m >> 1);
}
static inline void apply4(uint8_t* dst, uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t xorMask)
{
if (xorMask)
{
dst[0] |= a;
dst[1] |= b;
dst[2] |= c;
dst[3] |= d;
}
else
{
dst[0] &= ~a;
dst[1] &= ~b;
dst[2] &= ~c;
dst[3] &= ~d;
}
}
static inline void apply1(uint8_t* dst, uint8_t m, uint8_t xorMask)
{
if (xorMask) *dst |= m;
else *dst &= ~m;
}
void Font::PrintString(const char* str, uint8_t line, uint8_t x, uint8_t colour)
{
uint8_t* p = Platform::GetScreenBuffer() + DISPLAY_WIDTH * line + x;
uint8_t xorMask = (colour == COLOUR_BLACK) ? 0 : 0xff;
for (;;)
{
char c = *str++;
if (!c) break;
DrawChar(p, c, xorMask);
p += glyphWidth;
}
}
void Font::PrintInt(uint16_t val, uint8_t line, uint8_t x, uint8_t colour)
{
uint8_t* p = Platform::GetScreenBuffer() + DISPLAY_WIDTH * line + x;
uint8_t xorMask = (colour == COLOUR_BLACK) ? 0 : 0xff;
if (val == 0)
{
DrawChar(p, '0', xorMask);
return;
}
char buf[5];
int n = 0;
while (val && n < 5)
{
buf[n++] = (char)('0' + (val % 10));
val /= 10;
}
while (n--)
{
DrawChar(p, buf[n], xorMask);
p += glyphWidth;
}
}
void Font::DrawChar(uint8_t* p, char c, uint8_t xorMask)
{
uint8_t uc = (uint8_t)c;
if (uc < firstGlyphIndex) return;
const uint8_t* f = fontPageData + glyphWidth * (uc - firstGlyphIndex);
uint8_t i0 = ~f[0];
uint8_t i1 = ~f[1];
uint8_t i2 = ~f[2];
uint8_t i3 = ~f[3];
uint8_t t0 = v3(i0 | i1);
uint8_t t1 = v3(i0 | i1 | i2);
uint8_t t2 = v3(i1 | i2 | i3);
uint8_t t3 = v3(i2 | i3);
uint8_t r0 = t0 & ~i0;
uint8_t r1 = t1 & ~i1;
uint8_t r2 = t2 & ~i2;
uint8_t r3 = t3 & ~i3;
uint8_t outlineMask = xorMask ^ 0xff;
// The outline spills one byte to each side of the glyph. Clamp it to the
// screen buffer: with x == 0 on the first line `p - 1` lands on the heap
// block header of FlipperState (back_buffer is its first member) and the
// `|=`/`&=` write corrupts the allocator metadata -- the game keeps
// running fine but the firmware crashes/hangs later, when the app frees
// its state on exit.
uint8_t* bufStart = Platform::GetScreenBuffer();
uint8_t* bufEnd = bufStart + (DISPLAY_WIDTH * DISPLAY_HEIGHT / 8);
if (p - 1 >= bufStart) apply1(p - 1, v3(i0), outlineMask);
apply4(p, r0, r1, r2, r3, outlineMask);
if (p + 4 < bufEnd) apply1(p + 4, v3(i3), outlineMask);
apply4(p, i0, i1, i2, i3, xorMask);
}
@@ -0,0 +1,21 @@
#pragma once
#include <stdint.h>
#include "game/Defines.h"
#define FONT_WIDTH 4
#define FONT_HEIGHT 6
class Font
{
public:
static constexpr int glyphWidth = 4;
static constexpr int glyphHeight = 8;
static constexpr int firstGlyphIndex = 32;
static void PrintString(const char* str, uint8_t line, uint8_t x, uint8_t colour = COLOUR_BLACK);
static void PrintInt(uint16_t value, uint8_t line, uint8_t x, uint8_t xorMask = COLOUR_BLACK);
private:
static void DrawChar(uint8_t* screenPtr, char c, uint8_t xorMask);
};
@@ -0,0 +1,166 @@
#include "game/Defines.h"
#include "game/Game.h"
#include "game/FixedMath.h"
#include "game/Draw.h"
#include "game/Map.h"
#include "game/Projectile.h"
#include "game/Particle.h"
#include "game/MapGenerator.h"
#include "game/Platform.h"
#include "game/Entity.h"
#include "game/Enemy.h"
#include "game/Menu.h"
Player Game::player;
const char* Game::displayMessage = nullptr;
uint8_t Game::displayMessageTime = 0;
Game::State Game::state = Game::State::Menu;
uint8_t Game::floor = 1;
uint8_t Game::globalTickFrame = 0;
Stats Game::stats;
Menu Game::menu;
void Game::Init() {
menu.Init();
ParticleSystemManager::Init();
ProjectileManager::Init();
EnemyManager::Init();
}
bool Game::InMenu() {
return (state != State::InGame);
}
void Game::GoToMenu() {
Game::stats.killedBy = EnemyType::Exit;
SwitchState(State::FadeOut);
}
void Game::StartGame() {
floor = 1;
stats.Reset();
player.Init();
SwitchState(State::EnteringLevel);
}
void Game::SwitchState(State newState) {
if(state != newState) {
state = newState;
menu.ResetTimer();
}
}
void Game::ShowMessage(const char* message) {
constexpr uint8_t messageDisplayTime = 90;
displayMessage = message;
displayMessageTime = messageDisplayTime;
}
void Game::NextLevel() {
if(floor == 10) {
GameOver();
} else {
floor++;
SwitchState(State::EnteringLevel);
}
}
void Game::StartLevel() {
ParticleSystemManager::Init();
ProjectileManager::Init();
EnemyManager::Init();
MapGenerator::Generate();
EnemyManager::SpawnEnemies();
player.NextLevel();
SwitchState(State::InGame);
}
void Game::Draw() {
switch(state) {
case State::Menu:
menu.Draw();
break;
case State::EnteringLevel:
menu.DrawEnteringLevel();
break;
// case State::TransitionToLevel:
// menu.TransitionToLevel();
// break;
case State::InGame: {
Renderer::camera.x = player.x;
Renderer::camera.y = player.y;
Renderer::camera.angle = player.angle;
Renderer::Render();
} break;
case State::GameOver:
menu.DrawGameOver();
break;
case State::FadeOut:
menu.FadeOut();
break;
default:
break;
}
}
void Game::TickInGame() {
if(displayMessageTime > 0) {
displayMessageTime--;
if(displayMessageTime == 0) displayMessage = nullptr;
}
player.Tick();
ProjectileManager::Update();
ParticleSystemManager::Update();
EnemyManager::Update();
if(Map::GetCellSafe(player.x / CELL_SIZE, player.y / CELL_SIZE) == CellType::Exit) {
NextLevel();
}
if(player.hp == 0) {
GameOver();
}
}
void Game::Tick() {
globalTickFrame++;
switch(state) {
case State::InGame:
TickInGame();
return;
case State::EnteringLevel:
menu.TickEnteringLevel();
return;
case State::Menu:
menu.Tick();
return;
case State::GameOver:
menu.TickGameOver();
return;
// case State::TransitionToLevel:
// return;
default:
return;
}
}
void Game::GameOver() {
SwitchState(State::FadeOut);
}
void Stats::Reset() {
killedBy = EnemyType::None;
chestsOpened = 0;
coinsCollected = 0;
crownsCollected = 0;
scrollsCollected = 0;
for(uint8_t& killCounter : enemyKills) {
killCounter = 0;
}
}
@@ -0,0 +1,63 @@
#pragma once
#include <stdint.h>
#include "game/Defines.h"
#include "game/Player.h"
#include "game/Enemy.h"
#include "game/Menu.h"
class Entity;
struct Stats {
EnemyType killedBy;
uint8_t enemyKills[(int)EnemyType::NumEnemyTypes];
uint8_t chestsOpened;
uint8_t crownsCollected;
uint8_t scrollsCollected;
uint8_t coinsCollected;
void Reset();
};
class Game {
public:
static Menu menu;
static uint8_t globalTickFrame;
enum class State : uint8_t {
Menu,
EnteringLevel,
InGame,
GameOver,
FadeOut,
// TransitionToLevel
};
static void Init();
static void Tick();
static void Draw();
static bool InMenu();
static void GoToMenu();
static void StartGame();
static void StartLevel();
static void NextLevel();
static void GameOver();
static void SwitchState(State newState);
static void ShowMessage(const char* message);
static Player player;
static const char* displayMessage;
static uint8_t displayMessageTime;
static uint8_t floor;
static Stats stats;
private:
static void TickInGame();
static State state;
};
@@ -0,0 +1,204 @@
// Auto-generated by tools/extract_doom_assets.py
// Derived at build time from the user's local Doom shareware WAD.
// Do not commit WAD-derived data to public repositories.
constexpr uint8_t skeletonSpriteData_numFrames = 2;
extern const uint16_t skeletonSpriteData[] PROGMEM =
{
0x0,0x0,0x0,0x0,0x1fc,0x144,0xffc,0xfac,0x7ffe,0x416,0x3ffe,0x2e,0x7ff,0x455,0x7fe,0x3e,
0x4ffe,0x4476,0xfffe,0x2022,0xfe3e,0x7016,0xc9fe,0x802,0x1e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x3f0,0x150,0x3fc,0x200,0x103e,0x1016,0x7ffe,0x82e,0x3ffe,0x1476,0x17ff,0x23a,
0x7ff,0x41f,0x4ffe,0x23e,0xfffe,0x7056,0xfe1e,0x2802,0x3fe,0x114,0x3f0,0x2a0,0x0,0x0,0x0,0x0
};
constexpr uint8_t mageSpriteData_numFrames = 2;
extern const uint16_t mageSpriteData[] PROGMEM =
{
0x0,0x0,0x0,0x0,0x0,0x0,0x3e0,0x0,0x78,0x8,0x38,0x8,0xbffe,0x414,0xffff,0x800a,
0x83ff,0x15,0x7ffe,0xa,0x7ff8,0x1010,0x238,0x8,0x3f0,0x10,0x1c0,0x80,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x1e0,0x0,0x1f8,0x8,0x38,0x8,0x1ffc,0x8c,0x7fff,0x1107,0x17ff,0x8f,
0xffff,0x517,0xfff8,0xa618,0x1b8,0x8,0xf8,0xa8,0x60,0x40,0x0,0x0,0x0,0x0,0x0,0x0
};
constexpr uint8_t batSpriteData_numFrames = 2;
extern const uint16_t batSpriteData[] PROGMEM =
{
0x0,0x0,0x0,0x0,0x0,0x0,0xf8,0x20,0x1f8,0x70,0xffc,0x88,0x1fff,0x47,0xffff,0x8023,
0xfffe,0x4406,0x3f8,0x0,0x38,0x10,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x60,0x20,0x1f8,0x50,0xff8,0x8,0x5fff,0x47,0x7fff,0x23,
0x7fff,0x7,0xfff8,0x0,0x378,0x10,0x78,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0
};
constexpr uint8_t spiderSpriteData_numFrames = 2;
extern const uint16_t spiderSpriteData[] PROGMEM =
{
0x0,0x0,0x0,0x0,0x78,0x50,0xf8,0x78,0x7fc,0x4c,0xfff,0xa,0xffff,0x17,0xffff,0x2a2a,
0x3ffc,0x1004,0x38,0x8,0x30,0x10,0x30,0x20,0x8,0x0,0x8,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x60,0x40,0x1f8,0x78,0x7f8,0x58,0x1fff,0x20a,0x7fff,0x17,0x3fff,0x2a,
0xfff8,0x10,0x778,0x8,0x78,0x10,0x38,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0
};
constexpr uint8_t projectileSpriteData_numFrames = 1;
extern const uint16_t projectileSpriteData[] PROGMEM =
{
0x7e0,0x100,0x1ff8,0x2a0,0x3ffc,0x550,0x7ffe,0xaa8,0x7ffe,0x554,0xffff,0x2bea,0xffff,0x57f4,0xffff,0x2ffa,
0xffff,0x57f4,0xffff,0x2bea,0xffff,0x57d4,0x7ffe,0x2baa,0x7ffe,0x1554,0x3ffc,0xaa0,0x1ff8,0x540,0x7e0,0x280
};
constexpr uint8_t enemyProjectileSpriteData_numFrames = 1;
extern const uint16_t enemyProjectileSpriteData[] PROGMEM =
{
0x7e0,0x0,0x1ff8,0x220,0x3ffc,0x554,0x7ffe,0xaaa,0x7ffe,0x1554,0xffff,0x2fea,0xffff,0x17f4,0xffff,0x2fea,
0xffff,0x57f5,0xffff,0x2bea,0xffff,0x57f4,0x7ffe,0x2fea,0x7ffe,0x15d4,0x3ffc,0x2aa8,0x1ff8,0x550,0x7e0,0x280
};
constexpr uint8_t torchSpriteData1_numFrames = 1;
extern const uint16_t torchSpriteData1[] PROGMEM =
{
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8000,0x0,0xc0f0,0xc020,
0xc0f8,0x4070,0x8000,0x8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0
};
constexpr uint8_t torchSpriteData2_numFrames = 1;
extern const uint16_t torchSpriteData2[] PROGMEM =
{
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8000,0x8000,0xc070,0xc050,0xfff8,0xea38,
0xc070,0xc050,0x8000,0x8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0
};
constexpr uint8_t urnSpriteData_numFrames = 1;
extern const uint16_t urnSpriteData[] PROGMEM =
{
0x0,0x0,0x0,0x0,0x7ffe,0x7ffe,0xffff,0xffee,0xffff,0x5dfd,0xffff,0xaaaa,0xffff,0x5557,0xffff,0x8aa,
0xffff,0x5547,0xffff,0xb,0xffff,0x1101,0xffff,0x3,0xffff,0x1,0xffff,0x0,0x0,0x0,0x0,0x0
};
constexpr uint8_t potionSpriteData_numFrames = 1;
extern const uint16_t potionSpriteData[] PROGMEM =
{
0xfffc,0x5554,0xfffe,0xafbe,0xffff,0x575f,0xffff,0xaebf,0xffff,0x555f,0xffff,0xa2bf,0xffff,0x57f,0xffff,0x82ff,
0xffff,0x55f,0xffff,0xa2bf,0xffff,0x555f,0xffff,0xaabf,0xffff,0x575f,0xfffe,0xaebe,0xfffc,0x1554,0x0,0x0
};
constexpr uint8_t chestSpriteData_numFrames = 1;
extern const uint16_t chestSpriteData[] PROGMEM =
{
0x0,0x0,0x0,0x0,0xfffc,0x1554,0xffff,0xaebe,0xffff,0x477f,0xfffe,0xaaae,0xfffc,0x4574,0xfffc,0xa0bc,
0xfffc,0x457c,0xfffc,0xaaac,0xfffe,0x5776,0xffff,0xaabe,0xffff,0x457e,0xfffc,0x22a0,0x0,0x0,0x0,0x0
};
constexpr uint8_t chestOpenSpriteData_numFrames = 1;
extern const uint16_t chestOpenSpriteData[] PROGMEM =
{
0x0,0x0,0xfffe,0xaaaa,0xffff,0x5557,0xffff,0x2aab,0xffff,0x5,0xffff,0x28ab,0xffff,0x1557,0xffff,0x2b,
0xffff,0x1555,0xffff,0x28ab,0xffff,0x5,0xffff,0x2aab,0xffff,0x5557,0xfffe,0xaaaa,0x0,0x0,0x0,0x0
};
constexpr uint8_t scrollSpriteData_numFrames = 1;
extern const uint16_t scrollSpriteData[] PROGMEM =
{
0x3fc0,0x1540,0x7ff0,0x2aa0,0xfff8,0x5550,0xfffc,0xe2a8,0x3ffc,0x1d4,0x3ffe,0x3fa,0xfffe,0x45fc,0xfffe,0xabfe,
0xfffe,0x55fc,0xfffe,0x2bfe,0x3ffe,0x35c,0x3ffc,0x2a8,0xfffc,0xf154,0xfff8,0xaaa8,0x7ff0,0x5550,0x3fc0,0x2a80
};
constexpr uint8_t coinsSpriteData_numFrames = 1;
extern const uint16_t coinsSpriteData[] PROGMEM =
{
0x0,0x0,0x0,0x0,0x1e00,0x1000,0x3f80,0x2000,0x7fc0,0x4500,0xffc0,0x8000,0xfffe,0x4000,0xffff,0x2,
0xffff,0x15,0xfffe,0x82,0xffc0,0x540,0x7fc0,0xa80,0x3f80,0x1500,0x1e00,0x800,0x0,0x0,0x0,0x0
};
constexpr uint8_t crownSpriteData_numFrames = 1;
extern const uint16_t crownSpriteData[] PROGMEM =
{
0x300,0x100,0x380,0x380,0x780,0x180,0x1f80,0x380,0x7f80,0x4780,0xff80,0xae80,0xff80,0x5600,0xff00,0xaa00,
0xff00,0x5400,0xff80,0xaa00,0xff80,0x4700,0x7f80,0x380,0x1f80,0x580,0x780,0x380,0x380,0x180,0x300,0x200
};
constexpr uint8_t signSpriteData_numFrames = 1;
extern const uint16_t signSpriteData[] PROGMEM =
{
0x0,0x0,0xc000,0x0,0xc000,0x4000,0xc000,0x8000,0xc000,0xc000,0xc000,0x8000,0xc000,0x4000,0xc000,0x8000,
0xc000,0x4000,0xe000,0xe000,0xe000,0x4000,0xc000,0x8000,0xc000,0x4000,0xc000,0xc000,0x0,0x0,0x0,0x0
};
extern const uint8_t handSpriteData1[] PROGMEM =
{
0x2e,0x1c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x0,0xe0,0x8,0xf8,0x4,0xfc,
0x0,0xfc,0x0,0xff,0x8,0xfc,0x4,0xfc,0x8,0xf8,0x0,0xe0,0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x80,0x80,0x40,0xc0,0x80,0xc0,0x40,0xc0,0xa0,0xe0,0x40,0xe0,
0xa0,0xe0,0x10,0xf0,0x0,0xf0,0x0,0xf0,0x0,0xf8,0x10,0xfe,0x0,0xff,0x0,0xff,0x0,0xff,0x10,0xff,0x0,0xff,0x0,0xff,
0x0,0xff,0x10,0xff,0x0,0xff,0x0,0xff,0x0,0xff,0x10,0xfe,0x0,0xf8,0x10,0xf0,0x0,0xe0,0x0,0xe0,0x80,0xc0,0x0,0x80,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0xe0,0x80,0xfe,0x40,0xff,0xaa,0xff,0x55,0xff,0xaa,0xff,0x57,0xff,0x2f,0xff,0x5,0xff,0x0,0xff,0x0,0xff,
0x20,0xff,0x60,0xff,0x62,0xff,0x60,0xff,0x60,0xff,0x60,0xff,0x20,0xff,0x20,0xff,0x20,0xff,0x0,0xff,0x20,0xff,0x20,0xff,
0x20,0xff,0x60,0xff,0x60,0xff,0x60,0xff,0x22,0xff,0x60,0xff,0x20,0xff,0x50,0xff,0x0,0xff,0x1,0xff,0x0,0xfe,0x40,0xe0,
0x80,0xc0,0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xa,0xe,0x5,0xf,
0xa,0xf,0x5,0xf,0x2,0xf,0x1,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,
0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,
0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,0x0,0xf,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0
};
extern const uint8_t handSpriteData2[] PROGMEM =
{
0x2e,0x26,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x6,0x45,0xe7,
0xa8,0xff,0x4,0xff,0xa,0xff,0x5,0xff,0xa,0xff,0x45,0xff,0xae,0xff,0x3f,0x3f,0xf,0xf,0x3,0x3,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0x3,0xf,0xf,0x3f,0x3f,0xae,0xff,0x45,0xff,0xa,0xff,0x4,0xff,
0xa,0xff,0x4,0xff,0xaa,0xff,0x15,0x37,0x2,0x6,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,
0x0,0x1,0x0,0x1,0x0,0x1,0x0,0x1,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x80,0xa0,0xe0,0x50,0xf0,0xa0,0xf0,0x44,0xfc,
0xa0,0xf0,0x50,0xf0,0xa0,0xe0,0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x1,0x0,0x3,0x0,0x3,0x2,0x3,0x1,0x1,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x80,0x80,0x80,0x80,0x80,0x40,0xc0,
0x80,0xc0,0x40,0xc0,0xa0,0xe0,0x50,0xf8,0xaa,0xfe,0x55,0xff,0xaa,0xff,0x55,0xff,0xaa,0xff,0x55,0xff,0xaa,0xff,0x55,0xff,
0xaa,0xff,0x55,0xff,0xaa,0xfe,0x50,0xf8,0xa0,0xe0,0x40,0xc0,0x80,0x80,0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,
0xa8,0xf8,0x54,0xfe,0xaa,0xfe,0x55,0xff,0xff,0xff,0xfd,0xff,0xff,0xff,0x1d,0xff,0x8a,0xff,0x55,0xff,0xaa,0xff,0xd5,0xff,
0x88,0xff,0x88,0xff,0x88,0xff,0x80,0xff,0x80,0xff,0x80,0xff,0x80,0xff,0x0,0xff,0x80,0xff,0x80,0xff,0x80,0xff,0x80,0xff,
0x88,0xff,0x88,0xff,0x88,0xff,0xd5,0xff,0xaa,0xff,0x51,0xff,0x82,0xff,0x14,0xfe,0xa8,0xf8,0x0,0x80,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x28,0x38,0x15,0x3f,0x2a,0x3f,0x1d,0x3f,
0x2a,0x3f,0x17,0x3f,0xb,0x3f,0x1,0x3f,0x8,0x3f,0x5,0x3f,0x0,0x3f,0x1,0x3f,0x0,0x3f,0x1,0x3f,0x1,0x3f,0x1,0x3f,
0x3,0x3f,0x1,0x3f,0x0,0x3f,0x1,0x3f,0x0,0x3f,0x1,0x3f,0x0,0x3f,0x1,0x3f,0x0,0x3f,0x1,0x3f,0x3,0x3f,0x1,0x3f,
0x1,0x3f,0x1,0x3f,0x1,0x3f,0x1,0x3f,0x0,0x3f,0x5,0x3f,0x0,0x3f,0x5,0x3f,0xa,0x3f,0x14,0x3e,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0
};
extern const uint8_t titleBitmapData[] PROGMEM =
{
0x80,0x40,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xf,0xf,0xf,0xf,0x0,0x0,0x0,0x0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,
0x0,0x0,0x0,0x0,0xf,0xf,0xf,0xf,0xff,0xff,0xff,0xff,0xf,0xf,0xf,0xf,0x0,0x0,0x0,0x0,0xf0,0xf0,0xf0,0xf0,
0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0x0,0x0,0x0,0x0,0xf,0xf,0xf,0xf,0xff,0xff,0xff,0xff,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0xf,0xf,0xf,0xf,0xff,0xff,0xff,0xff,0xf,0xf,0xf,0xf,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0xff,0xff,0xff,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0xf0,0xf0,0xf0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xff,0xff,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xff,0xff,0xff,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0xff,0xff,0xff,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0xf0,0xf0,0xf0,0x0,0x0,0x0,0x0,0xf0,0xf0,0xf0,0xf0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xff,0xff,0xff,0xa0,0x50,0xa0,0x50,0xa0,0x50,0xa0,0x50,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xaf,0x5f,0xaf,0x5f,0xa0,0x50,0xa0,0x50,0xa0,0x50,0xa0,0x50,0xff,0xff,0xff,0xff,0xa0,0x50,0xa0,0x50,
0xa0,0x50,0xa0,0x50,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xa0,0x50,0xa0,0x50,0xa0,0x50,0xa0,0x50,
0xff,0xff,0xff,0xff,0xa0,0x50,0xa0,0x50,0xa0,0x50,0xa0,0x50,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xa0,0x50,0xa0,0x50,0xa0,0x50,0xa0,0x50,0xff,0xff,0xff,0xff,0xa0,0x50,0xa0,0x50,0xa0,0x50,0xa0,0x50,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xa0,0x50,0xa0,0x50,0xa0,0x50,0xa0,0x50,0xff,0xff,0xff,0xff,0xaa,0x55,0xaa,0x55,
0xaa,0x55,0xaa,0x55,0xaf,0x5f,0xaf,0x5f,0xaf,0x5f,0xaf,0x5f,0xaa,0x55,0xaa,0x55,0xaa,0x55,0xaa,0x55,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xfa,0xf5,0xfa,0xf5,0xaa,0x55,0xaa,0x55,0xaf,0x5f,0xaf,0x5f,0xaf,0x5f,0xaf,0x5f,0xaf,0x5f,0xaf,0x5f,
0xaa,0x55,0xaa,0x55,0xfa,0xf5,0xfa,0xf5,0xff,0xff,0xff,0xff,0xfa,0xf5,0xfa,0xf5,0xaa,0x55,0xaa,0x55,0xaf,0x5f,0xaf,0x5f,
0xaf,0x5f,0xaf,0x5f,0xaf,0x5f,0xaf,0x5f,0xaa,0x55,0xaa,0x55,0xfa,0xf5,0xfa,0xf5,0xff,0xff,0xff,0xff,0xaa,0x55,0xaa,0x55,
0xaa,0x55,0xaa,0x55,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xaa,0x55,0xaa,0x55,0xaa,0x55,0xaa,0x55,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
};
extern const uint8_t heartSpriteData[] PROGMEM =
{
0x1c,0x14,0x77,0x41,0x77,0x14,0x1c,0x0
};
extern const uint8_t manaSpriteData[] PROGMEM =
{
0x7e,0x7f,0x7e,0x0,0x7f,0x7e,0x7e,0x0
};
@@ -0,0 +1,24 @@
// Engine assets kept from FlipperCatacombs (non-Doom): gates, wall texture, font
// Generated from Images/entrance.png
constexpr uint8_t entranceSpriteData_numFrames = 1;
extern const uint16_t entranceSpriteData[] PROGMEM =
{
0x4,0x0,0xe,0x0,0xe,0x0,0x1f,0x0,0x3ff,0x3fc,0x9f,0x90,0x9f,0x90,0x9f,0x90,0x9f,0x90,0x9f,0x90,0x9f,0x90,0x3ff,0x3fc,0x1f,0x0,0xe,0x0,0xe,0x0,0x4,0x0
};
// Generated from Images/exit.png
constexpr uint8_t exitSpriteData_numFrames = 1;
extern const uint16_t exitSpriteData[] PROGMEM =
{
0x2000,0x0,0x7000,0x0,0x7000,0x0,0xf800,0x0,0xffc0,0x3fc0,0xf900,0x900,0xf900,0x900,0xf900,0x900,0xf900,0x900,0xf900,0x900,0xf900,0x900,0xffc0,0x3fc0,0xf800,0x0,0x7000,0x0,0x7000,0x0,0x2000,0x0
};
// Generated from Images/textures.png
constexpr uint8_t wallTextureData_numTextures = 1;
extern const uint16_t wallTextureData[] PROGMEM =
{
0xf3cf,0xf3cf,0xf3cf,0xf3c0,0xf3c0,0xf3cf,0x3cf,0x3cf,0xf3cf,0xf3cf,0xf00f,0xf00f,0xf3cf,0xf3cf,0xf3cf,0xf3cf
};
// Generated from Images/font.png
extern const uint8_t fontPageData[] PROGMEM =
{
0xff,0xff,0xff,0xff,0xff,0xd1,0xff,0xff,0xf9,0xff,0xf9,0xff,0xc1,0xeb,0xc1,0xff,0xd3,0xc1,0xe5,0xff,0xcd,0xf7,0xd9,0xff,0xcb,0xd5,0xeb,0xff,0xff,0xf9,0xff,0xff,0xff,0xe3,0xdd,0xff,0xff,0xdd,0xe3,0xff,0xeb,0xf7,0xeb,0xff,0xf7,0xe3,0xf7,0xff,0xdf,0xef,0xff,0xff,0xf7,0xf7,0xf7,0xff,0xff,0xdf,0xff,0xff,0xcf,0xf7,0xf9,0xff,0xc3,0xdd,0xe1,0xff,0xdb,0xc1,0xdf,0xff,0xcd,0xd5,0xd3,0xff,0xdd,0xd5,0xeb,0xff,0xe1,0xef,0xc7,0xff,0xd1,0xd5,0xe5,0xff,0xc3,0xd5,0xe5,0xff,0xcd,0xf5,0xf9,0xff,0xc3,0xd5,0xe1,0xff,0xd3,0xd5,0xe1,0xff,0xff,0xeb,0xff,0xff,0xdf,0xeb,0xff,0xff,0xf7,0xeb,0xdd,0xff,0xeb,0xeb,0xeb,0xff,0xdd,0xeb,0xf7,0xff,0xfd,0xd5,0xf9,0xff,0xe3,0xdd,0xd3,0xff,0xc3,0xf5,0xc1,0xff,0xc3,0xd5,0xe9,0xff,0xe3,0xdd,0xdd,0xff,0xc1,0xdd,0xe3,0xff,0xc3,0xd5,0xd5,0xff,0xc3,0xf5,0xf5,0xff,0xe3,0xdd,0xc5,0xff,0xc1,0xf7,0xc1,0xff,0xdd,0xc1,0xdd,0xff,0xef,0xdd,0xe1,0xff,0xc1,0xf7,0xc9,0xff,0xc1,0xdf,0xdf,0xff,0xc1,0xfb,0xc1,0xff,0xc1,0xfd,0xc3,0xff,0xe3,0xdd,0xe3,0xff,0xc1,0xf5,0xf3,0xff,0xe3,0xcd,0xc1,0xff,0xc3,0xf5,0xc9,0xff,0xdb,0xd5,0xed,0xff,0xfd,0xc1,0xfd,0xff,0xe1,0xdf,0xc1,0xff,0xe1,0xdf,0xe1,0xff,0xc1,0xef,0xc1,0xff,0xc9,0xf7,0xc9,0xff,0xf9,0xc7,0xf9,0xff,0xcd,0xd5,0xd9,0xff,0xff,0xc1,0xdd,0xff,0xf9,0xf7,0xcf,0xff,0xff,0xdd,0xc1,0xff,0xfb,0xfd,0xfb,0xff,0xdf,0xdf,0xdf,0xff,0xff,0xfd,0xfb,0xff,0xe7,0xdb,0xc3,0xff,0xc1,0xdb,0xe7,0xff,0xe7,0xdb,0xdb,0xff,0xe7,0xdb,0xc1,0xff,0xe7,0xcb,0xd3,0xff,0xc3,0xed,0xfb,0xff,0xb7,0xab,0xc7,0xff,0xc1,0xf7,0xcf,0xff,0xff,0xe5,0xdf,0xff,0xbf,0xcb,0xff,0xff,0xc1,0xf7,0xcb,0xff,0xff,0xe1,0xdf,0xff,0xc3,0xf7,0xc3,0xff,0xc3,0xfb,0xc7,0xff,0xe7,0xdb,0xe7,0xff,0x83,0xdb,0xe7,0xff,0xe7,0xdb,0x83,0xff,0xc3,0xf7,0xfb,0xff,0xd7,0xd3,0xeb,0xff,0xe1,0xdb,0xdf,0xff,0xe3,0xdf,0xc3,0xff,0xc3,0xdf,0xe3,0xff,0xc3,0xef,0xc3,0xff,0xcb,0xf7,0xcb,0xff,0xf3,0xaf,0xc3,0xff,0xdb,0xcb,0xd3,0xff,0xf7,0xc1,0xdd,0xff,0xff,0xc1,0xff,0xff,0xdd,0xc1,0xf7,0xff,0xfb,0xfd,0xfb,0xff,0xe3,0xed,0xe3,0xff
};
@@ -0,0 +1,48 @@
// Declarations for assets generated by tools/extract_doom_assets.py
// (converted at build time from the user's local Doom shareware WAD)
constexpr uint8_t skeletonSpriteData_numFrames = 2;
extern const uint16_t skeletonSpriteData[]; // demon
constexpr uint8_t mageSpriteData_numFrames = 2;
extern const uint16_t mageSpriteData[]; // imp
constexpr uint8_t torchSpriteData1_numFrames = 1;
extern const uint16_t torchSpriteData1[];
constexpr uint8_t torchSpriteData2_numFrames = 1;
extern const uint16_t torchSpriteData2[];
constexpr uint8_t projectileSpriteData_numFrames = 1;
extern const uint16_t projectileSpriteData[];
constexpr uint8_t enemyProjectileSpriteData_numFrames = 1;
extern const uint16_t enemyProjectileSpriteData[];
constexpr uint8_t entranceSpriteData_numFrames = 1;
extern const uint16_t entranceSpriteData[];
constexpr uint8_t exitSpriteData_numFrames = 1;
extern const uint16_t exitSpriteData[];
constexpr uint8_t urnSpriteData_numFrames = 1;
extern const uint16_t urnSpriteData[]; // barrel
constexpr uint8_t signSpriteData_numFrames = 1;
extern const uint16_t signSpriteData[]; // skull pile
constexpr uint8_t crownSpriteData_numFrames = 1;
extern const uint16_t crownSpriteData[]; // armor
constexpr uint8_t coinsSpriteData_numFrames = 1;
extern const uint16_t coinsSpriteData[]; // health bonus
constexpr uint8_t scrollSpriteData_numFrames = 1;
extern const uint16_t scrollSpriteData[]; // armor bonus
constexpr uint8_t chestSpriteData_numFrames = 1;
extern const uint16_t chestSpriteData[]; // backpack
constexpr uint8_t chestOpenSpriteData_numFrames = 1;
extern const uint16_t chestOpenSpriteData[]; // clip
constexpr uint8_t potionSpriteData_numFrames = 1;
extern const uint16_t potionSpriteData[]; // stimpack
constexpr uint8_t batSpriteData_numFrames = 2;
extern const uint16_t batSpriteData[]; // sergeant
constexpr uint8_t spiderSpriteData_numFrames = 2;
extern const uint16_t spiderSpriteData[]; // zombieman
// First-person weapon (shotgun idle / firing)
extern const uint8_t handSpriteData1[];
extern const uint8_t handSpriteData2[];
// Title screen (128x64, Platform::DrawSolidBitmap format)
extern const uint8_t titleBitmapData[];
constexpr uint8_t wallTextureData_numTextures = 1;
extern const uint16_t wallTextureData[];
extern const uint8_t fontPageData[];
extern const uint8_t heartSpriteData[];
extern const uint8_t manaSpriteData[];
@@ -0,0 +1,8 @@
const uint8_t scaleLUT[] PROGMEM = {
15,0,8,15,0,4,8,12,15,0,2,5,8,10,13,15,0,2,4,6,8,10,12,14,15,0,1,3,4,6,8,9,11,12,14,15,0,1,2,4,5,6,8,9,10,12,13,14,15,0,1,2,3,4,5,6,8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,15,0,0,1,2,3,4,5,6,7,8,8,9,10,11,12,13,14,15,15,0,0,1,2,3,4,4,5,6,7,8,8,9,10,11,12,12,13,14,15,15,0,0,1,2,2,3,4,5,5,6,7,8,8,9,10,10,11,12,13,13,14,15,15,0,0,1,2,2,3,4,4,5,6,6,7,8,8,9,10,10,11,12,12,13,14,14,15,15,0,0,1,1,2,3,3,4,4,5,6,6,7,8,8,9,9,10,11,11,12,12,13,14,14,15,15,0,0,1,1,2,2,3,4,4,5,5,6,6,7,8,8,9,9,10,10,11,12,12,13,13,14,14,15,15,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,8,8,9,9,10,10,11,11,12,12,13,13,14,14,15,15,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,14,14,15,15,15
};
const int16_t sinTable[] PROGMEM = {
0,6,12,18,25,31,37,43,49,56,62,68,74,80,86,92,97,103,109,115,120,126,131,136,142,147,152,157,162,167,171,176,181,185,189,193,197,201,205,209,212,216,219,222,225,228,231,234,236,238,241,243,244,246,248,249,251,252,253,254,254,255,255,255,255,255,255,255,254,254,253,252,251,249,248,246,244,243,241,238,236,234,231,228,225,222,219,216,212,209,205,201,197,193,189,185,181,176,171,167,162,157,152,147,142,136,131,126,120,115,109,103,97,92,86,80,74,68,62,56,49,43,37,31,25,18,12,6,0,-6,-12,-18,-25,-31,-37,-43,-49,-56,-62,-68,-74,-80,-86,-92,-97,-103,-109,-115,-120,-126,-131,-136,-142,-147,-152,-157,-162,-167,-171,-176,-181,-185,-189,-193,-197,-201,-205,-209,-212,-216,-219,-222,-225,-228,-231,-234,-236,-238,-241,-243,-244,-246,-248,-249,-251,-252,-253,-254,-254,-255,-255,-255,-255,-255,-255,-255,-254,-254,-253,-252,-251,-249,-248,-246,-244,-243,-241,-238,-236,-234,-231,-228,-225,-222,-219,-216,-212,-209,-205,-201,-197,-193,-189,-185,-181,-176,-171,-167,-162,-157,-152,-147,-142,-136,-131,-126,-120,-115,-109,-103,-97,-92,-86,-80,-74,-68,-62,-56,-49,-43,-37,-31,-25,-18,-12,-6
};
@@ -0,0 +1,254 @@
#include "game/Defines.h"
#include "game/Map.h"
#include "game/Game.h"
#include "game/FixedMath.h"
#include "game/Draw.h"
#include "game/Platform.h"
#include "game/Enemy.h"
uint8_t Map::level[Map::width * Map::height / 2];
bool Map::IsBlocked(uint8_t x, uint8_t y)
{
return GetCellSafe(x, y) >= CellType::FirstCollidableCell;
}
bool Map::IsSolid(uint8_t x, uint8_t y)
{
return GetCellSafe(x, y) >= CellType::FirstSolidCell;
}
CellType Map::GetCell(uint8_t x, uint8_t y)
{
int index = y * Map::width + x;
uint8_t cellData = level[index / 2];
if(index & 1)
{
return (CellType)(cellData >> 4);
}
else
{
return (CellType)(cellData & 0xf);
}
}
CellType Map::GetCellSafe(uint8_t x, uint8_t y)
{
if(x >= Map::width || y >= Map::height)
return CellType::BrickWall;
int index = y * Map::width + x;
uint8_t cellData = level[index / 2];
if(index & 1)
{
return (CellType)(cellData >> 4);
}
else
{
return (CellType)(cellData & 0xf);
}
}
void Map::SetCell(uint8_t x, uint8_t y, CellType type)
{
if (x >= Map::width || y >= Map::height)
{
return;
}
int index = (y * Map::width + x) / 2;
uint8_t cellType = (uint8_t)type;
if(x & 1)
{
level[index] = (level[index] & 0xf) | (cellType << 4);
}
else
{
level[index] = (level[index] & 0xf0) | (cellType & 0xf);
}
}
void Map::DebugDraw()
{
for(int y = 0; y < Map::height; y++)
{
for(int x = 0; x < Map::width; x++)
{
Platform::PutPixel(x, y, GetCell(x, y) == CellType::BrickWall ? 1 : 0);
if (x == Renderer::camera.cellX && y == Renderer::camera.cellY && (Game::globalTickFrame & 8) != 0)
{
Platform::PutPixel(x, y, 1);
}
}
}
if ((Game::globalTickFrame & 2) != 0)
{
for (uint8_t n = 0; n < EnemyManager::maxEnemies; n++)
{
Enemy& enemy = EnemyManager::enemies[n];
if (enemy.IsValid())
{
Platform::PutPixel(enemy.x / CELL_SIZE, enemy.y / CELL_SIZE, 1);
}
}
}
}
bool Map::IsClearLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2)
{
int cellX1 = x1 / CELL_SIZE;
int cellX2 = x2 / CELL_SIZE;
int cellY1 = y1 / CELL_SIZE;
int cellY2 = y2 / CELL_SIZE;
int xdist = ABS(cellX2 - cellX1);
int partial, delta;
int deltafrac;
int xfrac, yfrac;
int xstep, ystep;
int32_t ltemp;
int x, y;
if (xdist > 0)
{
if (cellX2 > cellX1)
{
partial = (CELL_SIZE * (cellX1 + 1) - x1);
xstep = 1;
}
else
{
partial = (x1 - CELL_SIZE * (cellX1));
xstep = -1;
}
deltafrac = ABS(x2 - x1);
delta = y2 - y1;
ltemp = ((int32_t)delta * CELL_SIZE) / deltafrac;
if (ltemp > 0x7fffl)
ystep = 0x7fff;
else if (ltemp < -0x7fffl)
ystep = -0x7fff;
else
ystep = ltemp;
yfrac = y1 + (((int32_t)ystep*partial) / CELL_SIZE);
x = cellX1 + xstep;
cellX2 += xstep;
do
{
y = (yfrac) / CELL_SIZE;
yfrac += ystep;
if (IsSolid(x, y))
return false;
x += xstep;
//
// see if the door is open enough
//
/*value &= ~0x80;
intercept = yfrac-ystep/2;
if (intercept>doorposition[value])
return false;*/
} while (x != cellX2);
}
int ydist = ABS(cellY2 - cellY1);
if (ydist > 0)
{
if (cellY2 > cellY1)
{
partial = (CELL_SIZE * (cellY1 + 1) - y1);
ystep = 1;
}
else
{
partial = (y1 - CELL_SIZE * (cellY1));
ystep = -1;
}
deltafrac = ABS(y2 - y1);
delta = x2 - x1;
ltemp = ((int32_t)delta * CELL_SIZE)/deltafrac;
if (ltemp > 0x7fffl)
xstep = 0x7fff;
else if (ltemp < -0x7fffl)
xstep = -0x7fff;
else
xstep = ltemp;
xfrac = x1 + (((int32_t)xstep*partial) / CELL_SIZE);
y = cellY1 + ystep;
cellY2 += ystep;
do
{
x = (xfrac) / CELL_SIZE;
xfrac += xstep;
if (IsSolid(x, y))
return false;
y += ystep;
//
// see if the door is open enough
//
/*value &= ~0x80;
intercept = xfrac-xstep/2;
if (intercept>doorposition[value])
return false;*/
} while (y != cellY2);
}
return true;
}
void Map::DrawMinimap()
{
constexpr uint8_t minimapWidth = 24;
constexpr uint8_t minimapHeight = 18;
constexpr uint8_t minimapX = 0; //DISPLAY_WIDTH / 2 - minimapWidth / 2;
constexpr uint8_t minimapY = 0; //DISPLAY_HEIGHT - minimapHeight;
uint8_t playerCellX = Game::player.x / CELL_SIZE;
uint8_t playerCellY = Game::player.y / CELL_SIZE;
uint8_t startCellX = playerCellX - minimapWidth / 2;
uint8_t startCellY = playerCellY - minimapHeight / 2;
uint8_t outX = minimapX;
uint8_t cellX = startCellX;
for (uint8_t x = 0; x < minimapWidth; x++)
{
uint8_t outY = minimapY;
uint8_t cellY = startCellY;
for (uint8_t y = 0; y < minimapHeight; y++)
{
if (cellX == playerCellX && cellY == playerCellY)
{
Platform::PutPixel(outX, outY, (Game::globalTickFrame & 3) ? COLOUR_BLACK : COLOUR_WHITE);
}
else
{
Platform::PutPixel(outX, outY, cellX < width && cellY < height && IsSolid(cellX, cellY) ? COLOUR_BLACK : COLOUR_WHITE);
}
outY++;
cellY++;
}
outX++;
cellX++;
}
}
@@ -0,0 +1,60 @@
#pragma once
#include <stdint.h>
enum class CellType : uint8_t
{
Empty = 0,
// Monster types
Monster,
// Non collidable decorations
Torch,
Entrance,
Exit,
// Items
Potion,
Coins,
Crown,
Scroll,
// Collidable decorations
Urn,
Chest,
ChestOpened,
Sign,
// Solid cells
BrickWall,
FirstCollidableCell = Urn,
FirstSolidCell = BrickWall
};
class Map
{
public:
static constexpr int width = 32;
static constexpr int height = 24;
static bool IsSolid(uint8_t x, uint8_t y);
static bool IsBlocked(uint8_t x, uint8_t y);
static inline bool IsBlockedAtWorldPosition(int16_t x, int16_t y)
{
return IsBlocked((uint8_t)(x >> 8), (uint8_t)(y >> 8));
}
static CellType GetCell(uint8_t x, uint8_t y);
static CellType GetCellSafe(uint8_t x, uint8_t y);
static void SetCell(uint8_t x, uint8_t y, CellType cellType);
static void DebugDraw();
static void DrawMinimap();
static bool IsClearLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2);
private:
static uint8_t level[width * height / 2];
};
@@ -0,0 +1,610 @@
#include "game/MapGenerator.h"
#include "game/Map.h"
#include "game/FixedMath.h"
#include "game/Enemy.h"
#include "game/Game.h"
uint8_t MapGenerator::GetDistanceToCellType(uint8_t x, uint8_t y, CellType cellType)
{
uint8_t ringWidth = 3;
for (uint8_t offset = 1; offset < Map::width; offset++)
{
for (uint8_t i = 0; i < ringWidth; i++)
{
if (Map::GetCellSafe(x - offset + i, y - offset) == cellType)
{
return offset;
}
if (Map::GetCellSafe(x - offset + i, y + offset) == cellType)
{
return offset;
}
if (Map::GetCellSafe(x - offset, y - offset + i) == cellType)
{
return offset;
}
if (Map::GetCellSafe(x + offset, y - offset + i) == cellType)
{
return offset;
}
}
ringWidth += 2;
}
return 0xff;
}
uint8_t MapGenerator::CountNeighbours(uint8_t x, uint8_t y)
{
uint8_t result = 0;
if (Map::GetCellSafe(x + 1, y) == CellType::Empty)
result++;
if (Map::GetCellSafe(x, y + 1) == CellType::Empty)
result++;
if (Map::GetCellSafe(x - 1, y) == CellType::Empty)
result++;
if (Map::GetCellSafe(x, y - 1) == CellType::Empty)
result++;
if (Map::GetCellSafe(x + 1, y + 1) == CellType::Empty)
result++;
if (Map::GetCellSafe(x - 1, y + 1) == CellType::Empty)
result++;
if (Map::GetCellSafe(x - 1, y - 1) == CellType::Empty)
result++;
if (Map::GetCellSafe(x + 1, y - 1) == CellType::Empty)
result++;
return result;
}
MapGenerator::NeighbourInfo MapGenerator::GetCellNeighbourInfo(uint8_t x, uint8_t y)
{
NeighbourInfo result;
result.count = 0;
result.mask = 0;
if (Map::IsSolid(x, y - 1))
{
result.hasNorth = true;
result.count++;
}
if (Map::IsSolid(x + 1, y))
{
result.hasEast = true;
result.count++;
}
if (Map::IsSolid(x, y + 1))
{
result.hasSouth = true;
result.count++;
}
if (Map::IsSolid(x - 1, y))
{
result.hasWest = true;
result.count++;
}
return result;
}
uint8_t MapGenerator::CountImmediateNeighbours(uint8_t x, uint8_t y)
{
uint8_t result = 0;
if (Map::GetCellSafe(x + 1, y) == CellType::Empty)
result++;
if (Map::GetCellSafe(x, y + 1) == CellType::Empty)
result++;
if (Map::GetCellSafe(x - 1, y) == CellType::Empty)
result++;
if (Map::GetCellSafe(x, y - 1) == CellType::Empty)
result++;
return result;
}
MapGenerator::NeighbourInfo MapGenerator::GetRoomNeighbourMask(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
{
NeighbourInfo result;
result.mask = 0;
result.count = 0;
result.canDemolishNorth = y > 1;
result.canDemolishWest = x > 1;
result.canDemolishEast = x + w + 1 < Map::width - 1;
result.canDemolishSouth = y + h + 1 < Map::height - 1;
// Don't demolish walls if the neighbouring room has the same wall length
if (Map::GetCell(x - 1, y - 2) != CellType::Empty && Map::GetCell(x + w, y - 2) != CellType::Empty)
{
result.canDemolishNorth = false;
}
if (Map::GetCell(x - 2, y - 1) != CellType::Empty && Map::GetCell(x - 2, y + h) != CellType::Empty)
{
result.canDemolishWest = false;
}
if (Map::GetCell(x + w + 1, y - 1) != CellType::Empty && Map::GetCell(x + w, y + h + 1) != CellType::Empty)
{
result.canDemolishEast = false;
}
if (Map::GetCell(x - 1, y + h + 1) != CellType::Empty && Map::GetCell(x + w, y + h + 1) != CellType::Empty)
{
result.canDemolishSouth = false;
}
// Don't demolish wall if this will leave an unattached wall
if (Map::GetCell(x - 1, y - 2) == CellType::Empty && Map::GetCell(x - 2, y - 1) == CellType::Empty)
{
result.canDemolishNorth = false;
result.canDemolishWest = false;
}
if (Map::GetCell(x + w, y - 2) == CellType::Empty && Map::GetCell(x + w + 1, y - 1) == CellType::Empty)
{
result.canDemolishNorth = false;
result.canDemolishEast = false;
}
if (Map::GetCell(x + w, y + h + 1) == CellType::Empty && Map::GetCell(x + w + 1, y + h) == CellType::Empty)
{
result.canDemolishSouth = false;
result.canDemolishEast = false;
}
if (Map::GetCell(x - 1, y + h + 1) == CellType::Empty && Map::GetCell(x - 2, y + h) == CellType::Empty)
{
result.canDemolishSouth = false;
result.canDemolishWest = false;
}
bool hasNorthWall = Map::GetCell(x, y - 1) != CellType::Empty && Map::GetCell(x + w - 1, y - 1) != CellType::Empty;
bool hasEastWall = Map::GetCell(x + w, y) != CellType::Empty && Map::GetCell(x + w, y + h - 1) != CellType::Empty;
bool hasSouthWall = Map::GetCell(x, y + h) != CellType::Empty && Map::GetCell(x + w - 1, y + h) != CellType::Empty;
bool hasWestWall = Map::GetCell(x - 1, y) != CellType::Empty && Map::GetCell(x - 1, y + h - 1) != CellType::Empty;
if (!hasNorthWall)
{
result.canDemolishNorth = false;
result.canDemolishEast = false;
result.canDemolishWest = false;
}
if (!hasEastWall)
{
result.canDemolishNorth = false;
result.canDemolishEast = false;
result.canDemolishSouth = false;
}
if (!hasSouthWall)
{
result.canDemolishEast = false;
result.canDemolishSouth = false;
result.canDemolishWest = false;
}
if (!hasWestWall)
{
result.canDemolishNorth = false;
result.canDemolishSouth = false;
result.canDemolishWest = false;
}
for (int i = x; i < x + w; i++)
{
if (Map::GetCell(i, y - 1) == CellType::Empty)
{
result.hasNorth = true;
result.count++;
}
if (Map::GetCell(i, y + h) == CellType::Empty)
{
result.hasSouth = true;
result.count++;
}
// Don't demolish wall if there is an intersecting wall attached
if (y > 1 && Map::GetCell(i, y - 2) != CellType::Empty)
{
result.canDemolishNorth = false;
}
if (y + h + 1 < Map::height - 1 && Map::GetCell(i, y + h + 1) != CellType::Empty)
{
result.canDemolishSouth = false;
}
}
for (int j = y; j < y + h; j++)
{
if (Map::GetCell(x - 1, j) == CellType::Empty)
{
result.hasWest = true;
result.count++;
}
if (Map::GetCell(x + w, j) == CellType::Empty)
{
result.hasEast = true;
result.count++;
}
// Don't demolish wall if there is an intersecting wall attached
if (x > 1 && Map::GetCell(x - 2, j) != CellType::Empty)
{
result.canDemolishWest = false;
}
if (x + w + 1 < Map::width - 1 && Map::GetCell(x + w + 1, j) != CellType::Empty)
{
result.canDemolishEast = false;
}
}
return result;
}
void MapGenerator::SplitMap(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t doorX, uint8_t doorY)
{
constexpr int minRoomSize = 3;
constexpr int maxRoomSize = 8;
//constexpr int maxFloorSpace = 80;
constexpr int demolishWallChance = 20;
if (doorX != 0 && doorY != 0)
{
Map::SetCell(doorX, doorY, CellType::Empty);
}
bool splitVertical = false;
bool splitHorizontal = false;
if (w > maxRoomSize || h > maxRoomSize)//w * h > maxFloorSpace)
{
if (w < h)
{
splitVertical = true;
}
else
{
splitHorizontal = true;
}
}
if (splitVertical)
{
uint8_t splitSize;
uint8_t splitAttempts = 255;
do
{
splitSize = (Random() % (h - 2 * minRoomSize)) + minRoomSize;
splitAttempts--;
} while (splitAttempts > 0 && (Map::GetCell(x - 1, y + splitSize) == CellType::Empty || Map::GetCell(x + w, y + splitSize) == CellType::Empty
|| Map::GetCell(x - 1, y + splitSize - 1) == CellType::Empty || Map::GetCell(x + w, y + splitSize - 1) == CellType::Empty
|| Map::GetCell(x - 1, y + splitSize + 1) == CellType::Empty || Map::GetCell(x + w, y + splitSize + 1) == CellType::Empty));
if (splitAttempts > 0)
{
uint8_t splitDoorX = x + (Random() % (w - 2)) + 1;
uint8_t splitDoorY = y + splitSize;
for (uint8_t i = x; i < x + w; i++)
{
Map::SetCell(i, y + splitSize, CellType::BrickWall);
}
SplitMap(x, y + splitSize + 1, w, h - splitSize - 1, splitDoorX, splitDoorY);
SplitMap(x, y, w, splitSize, splitDoorX, splitDoorY);
return;
}
}
else if (splitHorizontal)
{
uint8_t splitSize;
uint8_t splitAttempts = 255;
do
{
splitSize = (Random() % (w - 2 * minRoomSize)) + minRoomSize;
splitAttempts--;
} while (splitAttempts > 0 && (Map::GetCell(x + splitSize, y - 1) == CellType::Empty || Map::GetCell(x + splitSize, y + h) == CellType::Empty
|| Map::GetCell(x + splitSize - 1, y - 1) == CellType::Empty || Map::GetCell(x + splitSize - 1, y + h) == CellType::Empty
|| Map::GetCell(x + splitSize + 1, y - 1) == CellType::Empty || Map::GetCell(x + splitSize + 1, y + h) == CellType::Empty));
if (splitAttempts > 0)
{
uint8_t splitDoorX = x + splitSize;
uint8_t splitDoorY = y + (Random() % (h - 2)) + 1;
for (uint8_t j = y; j < y + h; j++)
{
Map::SetCell(x + splitSize, j, CellType::BrickWall);
}
SplitMap(x + splitSize + 1, y, w - splitSize - 1, h, splitDoorX, splitDoorY);
SplitMap(x, y, splitSize, h, splitDoorX, splitDoorY);
return;
}
}
{
NeighbourInfo neighbours = GetRoomNeighbourMask(x, y, w, h);
if (neighbours.canDemolishNorth && (Random() % 100) < demolishWallChance)
{
for (int i = 0; i < w; i++)
{
Map::SetCell(x + i, y - 1, CellType::Empty);
}
}
else if (neighbours.canDemolishWest && (Random() % 100) < demolishWallChance)
{
for (int j = 0; j < h; j++)
{
Map::SetCell(x - 1, y + j, CellType::Empty);
}
}
else if (neighbours.canDemolishSouth && (Random() % 100) < demolishWallChance)
{
for (int i = 0; i < w; i++)
{
Map::SetCell(x + i, y + h, CellType::Empty);
}
}
else if (neighbours.canDemolishEast && (Random() % 100) < demolishWallChance)
{
for (int j = 0; j < h; j++)
{
Map::SetCell(x + w, y + j, CellType::Empty);
}
}
// Add decorations
{
// Add four cornering columns
if (w == h && w >= 7 && h >= 7)
{
Map::SetCell(x + 1, y + 1, CellType::BrickWall);
Map::SetCell(x + w - 2, y + 1, CellType::BrickWall);
Map::SetCell(x + w - 2, y + h - 2, CellType::BrickWall);
Map::SetCell(x + 1, y + h - 2, CellType::BrickWall);
}
}
}
}
void MapGenerator::Generate()
{
uint8_t playerStartX = 1;
uint8_t playerStartY = 1;
for (int y = 0; y < Map::height; y++)
{
for (int x = 0; x < Map::width; x++)
{
bool isEdge = x == 0 || y == 0 || x == Map::width - 1 || y == Map::height - 1;
Map::SetCell(x, y, isEdge ? CellType::BrickWall : CellType::Empty);
}
}
SplitMap(1, 1, Map::width - 2, Map::height - 2, 0, 0);
// Find any big open spaces
{
bool hasOpenSpaces = true;
while (hasOpenSpaces)
{
hasOpenSpaces = false;
uint8_t x = 0, y = 0, space = 0;
for (uint8_t i = 1; i < Map::width - 1; i++)
{
for (uint8_t j = 0; j < Map::height - 1; j++)
{
bool foundWall = false;
for (uint8_t k = 0; k < Map::height && !foundWall; k++)
{
for (uint8_t u = 0; u < k && !foundWall; u++)
{
for (uint8_t v = 0; v < k && !foundWall; v++)
{
if (Map::GetCellSafe(i + u, j + v) != CellType::Empty)
{
foundWall = true;
}
}
}
if (!foundWall && k > space)
{
space = k;
x = i;
y = j;
}
}
}
}
if (space > 6)
{
hasOpenSpaces = true;
// Stick a donut in the middle
for (uint8_t n = 2; n < space - 2; n++)
{
Map::SetCell(x + n, y + 2, CellType::BrickWall);
Map::SetCell(x + 2, y + n, CellType::BrickWall);
Map::SetCell(x + n, y + space - 3, CellType::BrickWall);
Map::SetCell(x + space - 3, y + n, CellType::BrickWall);
}
}
}
}
// Add torches
{
uint8_t attempts = 255;
uint8_t toSpawn = 64;
uint8_t minSpacing = 3;
while (attempts > 0 && toSpawn > 0)
{
uint8_t x = Random() % Map::width;
uint8_t y = Random() % Map::height;
if (Map::GetCellSafe(x, y) == CellType::Empty)
{
NeighbourInfo info = GetCellNeighbourInfo(x, y);
if(info.count == 1 && GetDistanceToCellType(x, y, CellType::Torch) > minSpacing)
{
Map::SetCell(x, y, CellType::Torch);
toSpawn--;
attempts = 255;
}
}
attempts--;
}
}
// Add monsters
{
uint8_t attempts = 255;
uint8_t monstersToSpawn = EnemyManager::maxEnemies;
CellType monsterType = CellType::Monster;
uint8_t minSpacing = 3;
while (attempts > 0 && monstersToSpawn > 0)
{
uint8_t x = Random() % Map::width;
uint8_t y = Random() % Map::height;
if (Map::GetCellSafe(x, y) == CellType::Empty && Map::IsClearLine(x * CELL_SIZE + CELL_SIZE / 2, y * CELL_SIZE + CELL_SIZE / 2, playerStartX * CELL_SIZE + CELL_SIZE / 2, playerStartY * CELL_SIZE + CELL_SIZE / 2) == false)
{
NeighbourInfo info = GetCellNeighbourInfo(x, y);
if (info.count == 0 && GetDistanceToCellType(x, y, monsterType) > minSpacing)
{
Map::SetCell(x, y, monsterType);
monstersToSpawn--;
attempts = 255;
}
}
attempts--;
}
}
// Add blocking decorations
{
uint8_t attempts = 255;
uint8_t toSpawn = 255;
CellType cellType = CellType::Urn;
uint8_t minSpacing = 3;
while (attempts > 0 && toSpawn > 0)
{
uint8_t x = Random() % Map::width;
uint8_t y = Random() % Map::height;
if (Map::GetCellSafe(x, y) == CellType::Empty)
{
NeighbourInfo info = GetCellNeighbourInfo(x, y);
if(info.IsCorner() && GetDistanceToCellType(x, y, cellType) > minSpacing)
{
Map::SetCell(x, y, cellType);
toSpawn--;
attempts = 255;
}
}
attempts--;
}
}
// Add entrance and exit
Map::SetCell(1, 1, CellType::Entrance);
Map::SetCell(Map::width - 3, Map::height - 3, CellType::Exit);
// Add sign
if(false)
{
uint16_t attempts = 65535;
constexpr uint8_t closeness = 5;
while (attempts > 0)
{
uint8_t x = Random() % closeness;
uint8_t y = Random() % closeness;
if (Map::GetCellSafe(x, y) == CellType::Empty
&& Map::GetCellSafe(x - 1, y) == CellType::Empty
&& Map::GetCellSafe(x, y - 1) == CellType::Empty
&& Map::GetCellSafe(x + 1, y) == CellType::Empty
&& Map::GetCellSafe(x, y + 1) == CellType::Empty
&& Map::GetCellSafe(x - 1, y - 1) == CellType::Empty
&& Map::GetCellSafe(x + 1, y - 1) == CellType::Empty
&& Map::GetCellSafe(x - 1, y + 1) == CellType::Empty
&& Map::GetCellSafe(x + 1, y + 1) == CellType::Empty
&& Map::IsClearLine(x * CELL_SIZE + CELL_SIZE / 2, y * CELL_SIZE + CELL_SIZE / 2, playerStartX * CELL_SIZE + CELL_SIZE / 2, playerStartY * CELL_SIZE + CELL_SIZE / 2))
{
Map::SetCell(x, y, CellType::Sign);
break;
}
attempts--;
}
}
else if(Game::floor == 1)
{
Map::SetCell(2, 2, CellType::Sign);
}
// Add treasure / items
{
uint16_t attempts = 65535;
uint8_t toSpawn = 8;
CellType cellType = CellType::Chest;
uint8_t minSpacing = 3;
uint8_t minExitSpacing = 6;
while (attempts > 0 && toSpawn > 0)
{
uint8_t x = Random() % Map::width;
uint8_t y = Random() % Map::height;
switch (Random() % 5)
{
case 0:
cellType = CellType::Potion;
break;
case 1:
cellType = CellType::Coins;
break;
case 2:
cellType = CellType::Chest;
break;
case 3:
cellType = CellType::Crown;
break;
case 4:
cellType = CellType::Scroll;
break;
}
if (Map::GetCellSafe(x, y) == CellType::Empty)
{
NeighbourInfo info = GetCellNeighbourInfo(x, y);
if(info.count == 1
&& GetDistanceToCellType(x, y, cellType) > minSpacing
&& GetDistanceToCellType(x, y, CellType::Entrance) > minExitSpacing
&& GetDistanceToCellType(x, y, CellType::Exit) > minExitSpacing)
{
Map::SetCell(x, y, cellType);
toSpawn--;
attempts = 255;
}
}
attempts--;
}
}
}
@@ -0,0 +1,60 @@
#pragma once
#include <stdint.h>
#include "game/Map.h"
class MapGenerator
{
public:
static void Generate();
private:
struct NeighbourInfo
{
union
{
uint8_t mask;
struct
{
bool hasNorth : 1;
bool hasEast : 1;
bool hasSouth : 1;
bool hasWest : 1;
bool canDemolishNorth : 1;
bool canDemolishEast : 1;
bool canDemolishSouth : 1;
bool canDemolishWest : 1;
};
};
uint8_t count;
bool IsCorner() const
{
if (count != 2)
return false;
if (hasNorth && hasEast)
return true;
if (hasEast && hasSouth)
return true;
if (hasSouth && hasWest)
return true;
if (hasWest && hasNorth)
return true;
return false;
}
};
static uint8_t CountNeighbours(uint8_t x, uint8_t y);
static uint8_t CountImmediateNeighbours(uint8_t x, uint8_t y);
static NeighbourInfo GetRoomNeighbourMask(uint8_t x, uint8_t y, uint8_t w, uint8_t h);
static void SplitMap(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t doorX, uint8_t doorY);
static NeighbourInfo GetCellNeighbourInfo(uint8_t x, uint8_t y);
static uint8_t GetDistanceToCellType(uint8_t x, uint8_t y, CellType cellType);
};
@@ -0,0 +1,705 @@
#include "game/Defines.h"
#include "game/Platform.h"
#include "game/Menu.h"
#include "game/Font.h"
#include "game/Game.h"
#include "game/Draw.h"
#include "game/Textures.h"
#include "game/Generated/SpriteTypes.h"
#include "game/Map.h"
#include "game/FixedMath.h"
#include "lib/EEPROM.h"
#include <stdio.h>
#include <string.h>
constexpr uint8_t EEPROM_BASE_ADDR = 0;
struct ObjDesc {
const uint16_t* sprite;
bool animated;
bool invert;
uint8_t varsIndex;
};
static const ObjDesc kObjects[] = {
{ chestSpriteData, false, false, 0 },
{ crownSpriteData, false, false, 1 },
{ scrollSpriteData, false, false, 2 },
{ coinsSpriteData, false, false, 3 },
{ skeletonSpriteData, true, false, 4 },
{ mageSpriteData, true, false, 5 },
{ batSpriteData, true, false, 6 },
{ spiderSpriteData, true, false, 7 },
{ exitSpriteData, false, false, 8 }
};
static constexpr uint8_t kObjectsCount = (uint8_t)(sizeof(kObjects) / sizeof(kObjects[0]));
static constexpr uint8_t SHIFT_MASK = 63;
namespace {
constexpr uint8_t MENU_ITEMS_COUNT = 4;
constexpr uint8_t VISIBLE_ROWS = 2;
constexpr uint8_t MENU_FIRST_ROW = 4;
constexpr uint8_t TEXT_X = 18;
constexpr uint8_t CURSOR_X = 10;
constexpr uint8_t SPLASH_TIME_TICKS = 90;
static uint8_t splashTimer = 0;
static bool splashActive = true;
static uint8_t Wrap(int v, int n) {
v %= n;
if(v < 0) v += n;
return (uint8_t)v;
}
static uint8_t MaxTop() {
return (MENU_ITEMS_COUNT > VISIBLE_ROWS) ? (uint8_t)(MENU_ITEMS_COUNT - VISIBLE_ROWS) : 0;
}
void DrawMenuRoom() {
const int16_t leftWall = 1 * CELL_SIZE;
const int16_t rightWall = 4 * CELL_SIZE;
const int16_t topWall = 1 * CELL_SIZE;
const int16_t bottomWall = 4 * CELL_SIZE;
Renderer::camera.x = (int16_t)((leftWall + rightWall) / 2);
Renderer::camera.y = (int16_t)((topWall + bottomWall) / 2);
static uint16_t angleFP = 0;
constexpr uint16_t SPEED_FP = 64;
angleFP = (uint16_t)(angleFP + SPEED_FP);
Renderer::camera.angle = (uint8_t)(angleFP >> 8);
Renderer::camera.tilt = 0;
Renderer::camera.bob = 0;
Renderer::globalRenderFrame++;
Renderer::DrawBackground();
Renderer::numBufferSlicesFilled = 0;
Renderer::numQueuedDrawables = 0;
for(uint8_t n = 0; n < DISPLAY_WIDTH; n++) {
Renderer::wBuffer[n] = 0;
Renderer::horizonBuffer[n] = HORIZON +
(((DISPLAY_WIDTH / 2 - n) * Renderer::camera.tilt) >> 8) +
Renderer::camera.bob;
}
Renderer::camera.cellX = Renderer::camera.x / CELL_SIZE;
Renderer::camera.cellY = Renderer::camera.y / CELL_SIZE;
{
uint16_t rotPhase = (uint16_t)(0 - angleFP);
uint8_t a0 = (uint8_t)(rotPhase >> 8);
uint8_t f = (uint8_t)(rotPhase & 0xff);
uint8_t a1 = (uint8_t)(a0 + 1);
int16_t c0 = FixedCos(a0);
int16_t c1 = FixedCos(a1);
int16_t s0 = FixedSin(a0);
int16_t s1 = FixedSin(a1);
Renderer::camera.rotCos = (int16_t)(c0 + (((int32_t)(c1 - c0) * f) >> 8));
Renderer::camera.rotSin = (int16_t)(s0 + (((int32_t)(s1 - s0) * f) >> 8));
}
{
uint16_t clipPhase = (uint16_t)(((uint16_t)CLIP_ANGLE << 8) - angleFP);
uint8_t a0 = (uint8_t)(clipPhase >> 8);
uint8_t f = (uint8_t)(clipPhase & 0xff);
uint8_t a1 = (uint8_t)(a0 + 1);
int16_t c0 = FixedCos(a0);
int16_t c1 = FixedCos(a1);
int16_t s0 = FixedSin(a0);
int16_t s1 = FixedSin(a1);
Renderer::camera.clipCos = (int16_t)(c0 + (((int32_t)(c1 - c0) * f) >> 8));
Renderer::camera.clipSin = (int16_t)(s0 + (((int32_t)(s1 - s0) * f) >> 8));
}
#if WITH_IMAGE_TEXTURES
const uint16_t* texture = wallTextureData;
#elif WITH_VECTOR_TEXTURES
const uint8_t* texture = vectorTexture0;
#endif
constexpr int8_t MIN_CELL = 0;
constexpr int8_t MAX_CELL = 4;
#define MENU_SOLID(cx, cy) (((cx) == 0) || ((cx) == MAX_CELL) || ((cy) == 0) || ((cy) == MAX_CELL))
#define MENU_SOLID_SAFE(cx, cy) \
(((cx) < MIN_CELL || (cx) > MAX_CELL || (cy) < MIN_CELL || (cy) > MAX_CELL) ? \
true : \
MENU_SOLID((cx), (cy)))
int8_t xd, yd;
int8_t x1, y1, x2, y2;
if(Renderer::camera.rotCos > 0) {
x1 = MIN_CELL;
x2 = MAX_CELL + 1;
xd = 1;
} else {
x2 = MIN_CELL - 1;
x1 = MAX_CELL;
xd = -1;
}
if(Renderer::camera.rotSin < 0) {
y1 = MIN_CELL;
y2 = MAX_CELL + 1;
yd = 1;
} else {
y2 = MIN_CELL - 1;
y1 = MAX_CELL;
yd = -1;
}
auto drawMenuCell = [&](int8_t x, int8_t y) {
if(!MENU_SOLID(x, y)) return;
if(Renderer::isFrustrumClipped(x, y)) return;
if(Renderer::numBufferSlicesFilled >= DISPLAY_WIDTH) return;
const bool blockedLeft = MENU_SOLID_SAFE(x - 1, y);
const bool blockedRight = MENU_SOLID_SAFE(x + 1, y);
const bool blockedUp = MENU_SOLID_SAFE(x, y - 1);
const bool blockedDown = MENU_SOLID_SAFE(x, y + 1);
int16_t wx1 = (int16_t)(x * CELL_SIZE);
int16_t wy1 = (int16_t)(y * CELL_SIZE);
int16_t wx2 = (int16_t)(wx1 + CELL_SIZE);
int16_t wy2 = (int16_t)(wy1 + CELL_SIZE);
if(!blockedLeft && Renderer::camera.x < wx1) {
#if WITH_TEXTURES
Renderer::DrawWall(
texture,
wx1,
wy1,
wx1,
wy2,
!blockedUp && Renderer::camera.y > wy1,
!blockedDown && Renderer::camera.y < wy2,
false);
#else
Renderer::DrawWall(
wx1,
wy1,
wx1,
wy2,
!blockedUp && Renderer::camera.y > wy1,
!blockedDown && Renderer::camera.y < wy2,
false);
#endif
}
if(!blockedDown && Renderer::camera.y > wy2) {
#if WITH_TEXTURES
Renderer::DrawWall(
texture,
wx1,
wy2,
wx2,
wy2,
!blockedLeft && Renderer::camera.x > wx1,
!blockedRight && Renderer::camera.x < wx2,
false);
#else
Renderer::DrawWall(
wx1,
wy2,
wx2,
wy2,
!blockedLeft && Renderer::camera.x > wx1,
!blockedRight && Renderer::camera.x < wx2,
false);
#endif
}
if(!blockedRight && Renderer::camera.x > wx2) {
#if WITH_TEXTURES
Renderer::DrawWall(
texture,
wx2,
wy2,
wx2,
wy1,
!blockedDown && Renderer::camera.y < wy2,
!blockedUp && Renderer::camera.y > wy1,
false);
#else
Renderer::DrawWall(
wx2,
wy2,
wx2,
wy1,
!blockedDown && Renderer::camera.y < wy2,
!blockedUp && Renderer::camera.y > wy1,
false);
#endif
}
if(!blockedUp && Renderer::camera.y < wy1) {
#if WITH_TEXTURES
Renderer::DrawWall(
texture,
wx2,
wy1,
wx1,
wy1,
!blockedRight && Renderer::camera.x < wx2,
!blockedLeft && Renderer::camera.x > wx1,
false);
#else
Renderer::DrawWall(
wx2,
wy1,
wx1,
wy1,
!blockedRight && Renderer::camera.x < wx2,
!blockedLeft && Renderer::camera.x > wx1,
false);
#endif
}
};
if(ABS(Renderer::camera.rotCos) < ABS(Renderer::camera.rotSin)) {
for(int8_t y = y1; y != y2; y += yd) {
for(int8_t x = x1; x != x2; x += xd) {
drawMenuCell(x, y);
}
}
} else {
for(int8_t x = x1; x != x2; x += xd) {
for(int8_t y = y1; y != y2; y += yd) {
drawMenuCell(x, y);
}
}
}
#undef MENU_SOLID_SAFE
#undef MENU_SOLID
}
}
void Menu::Draw() {
if (splashActive) {
// Title screen converted from the user's own shareware WAD
Platform::DrawSolidBitmap(0, 0, titleBitmapData);
return;
}
DrawMenuRoom();
Font::PrintString(PSTR("FLIPPER DOOM"), 2, 40, COLOUR_WHITE);
for (uint8_t row = 0; row < VISIBLE_ROWS; ++row) {
uint8_t idx = (uint8_t)(m_topIndex + row);
if (idx >= MENU_ITEMS_COUNT) break;
PrintItem(idx, (uint8_t)(MENU_FIRST_ROW + row));
}
static uint8_t bubble = 0;
static uint16_t lastFrameSeen = 0xFFFF;
const uint16_t frame = (uint16_t)Game::globalTickFrame;
if (frame != lastFrameSeen) {
if ((frame & SHIFT_MASK) == 0) {
bubble = (uint8_t)(bubble + 2);
if (bubble >= kObjectsCount) bubble = (uint8_t)(bubble - kObjectsCount);
if (bubble >= kObjectsCount) bubble = (uint8_t)(bubble - kObjectsCount);
}
lastFrameSeen = frame;
}
const uint8_t num1 = bubble;
uint8_t num2 = (uint8_t)(bubble + 1);
if (num2 >= kObjectsCount) num2 = 0;
const ObjDesc& sprite1 = kObjects[num1];
const ObjDesc& sprite2 = kObjects[num2];
const int animOffset = ((Game::globalTickFrame & 8) == 0) ? 32 : 0;
const int off1 = sprite1.animated ? animOffset : 0;
const int off2 = sprite2.animated ? animOffset : 0;
const uint16_t* torchSprite =
(Game::globalTickFrame & 4) ? torchSpriteData1 : torchSpriteData2;
if (sprite1.invert) {
Renderer::DrawScaled(sprite1.sprite + off1, 66, 29, 9, 255, true, COLOUR_BLACK);
} else {
Renderer::DrawScaled(sprite1.sprite + off1, 66, 29, 9, 255);
}
if (sprite2.invert) {
Renderer::DrawScaled(sprite2.sprite + off2, 96, 30, 9, 255, true, COLOUR_BLACK);
} else {
Renderer::DrawScaled(sprite2.sprite + off2, 96, 30, 9, 255);
}
Renderer::DrawScaled(torchSprite, 0, 10, 9, 255);
Renderer::DrawScaled(torchSprite, DISPLAY_WIDTH - 18, 10, 9, 255);
Font::PrintInt(m_save[sprite1.varsIndex], MENU_FIRST_ROW + 1, 86, COLOUR_WHITE);
Font::PrintInt(m_save[sprite2.varsIndex], MENU_FIRST_ROW + 1, 116, COLOUR_WHITE);
Font::PrintString(PSTR(">"), (uint8_t)(MENU_FIRST_ROW + m_cursorPos), CURSOR_X, COLOUR_WHITE);
}
void Menu::PrintItem(uint8_t idx, uint8_t row) {
switch(idx) {
case 0:
Font::PrintString(PSTR("Play"), row, TEXT_X, COLOUR_WHITE);
break;
case 1:
Font::PrintString(PSTR("Sound:"), row, TEXT_X, COLOUR_WHITE);
Font::PrintString(
Platform::IsAudioEnabled() ? PSTR("on") : PSTR("off"), row, TEXT_X + 28, COLOUR_WHITE);
break;
case 2:
Font::PrintString(PSTR("Score:"), row, TEXT_X, COLOUR_WHITE);
Font::PrintInt(m_score, row, TEXT_X + 28, COLOUR_WHITE);
break;
case 3:
Font::PrintString(PSTR("High:"), row, TEXT_X, COLOUR_WHITE);
Font::PrintInt(m_high, row, TEXT_X + 28, COLOUR_WHITE);
break;
}
}
void Menu::Init() {
m_selection = 0;
m_topIndex = 0;
m_cursorPos = 0;
splashTimer = 0;
splashActive = true;
}
void Menu::DrawEnteringLevel() {
DrawMenuRoom();
Font::PrintString(PSTR("Entering E1M"), 3, 34, COLOUR_BLACK);
Font::PrintInt(Game::floor, 3, 82, COLOUR_BLACK);
}
static int CountCharsInt(int v) {
int n = 1;
if(v < 0) {
n++;
v = -v;
} // минус тоже символ
while(v >= 10) {
v /= 10;
n++;
}
return n;
}
void PrintScoreCentered(int finalScore) {
const int screenW = 128;
int n = CountCharsInt(finalScore);
int textW = 4 * n - 1;
int x = (screenW - textW) / 2;
Font::PrintInt(finalScore, 2, x, COLOUR_BLACK);
}
void Menu::DrawGameOver() {
uint16_t finalScore = 0;
constexpr int finishBonus = 500;
constexpr int levelBonus = 20;
constexpr int chestBonus = 15;
constexpr int crownBonus = 10;
constexpr int scrollBonus = 8;
constexpr int coinsBonus = 4;
constexpr int skeletonKillBonus = 10;
constexpr int mageKillBonus = 10;
constexpr int batKillBonus = 5;
constexpr int spiderKillBonus = 4;
finalScore += (Game::floor - 1) * levelBonus;
if(Game::stats.killedBy == EnemyType::None) finalScore += finishBonus;
finalScore += Game::stats.chestsOpened * chestBonus;
finalScore += Game::stats.crownsCollected * crownBonus;
finalScore += Game::stats.scrollsCollected * scrollBonus;
finalScore += Game::stats.coinsCollected * coinsBonus;
finalScore += Game::stats.enemyKills[(int)EnemyType::Skeleton] * skeletonKillBonus;
finalScore += Game::stats.enemyKills[(int)EnemyType::Mage] * mageKillBonus;
finalScore += Game::stats.enemyKills[(int)EnemyType::Bat] * batKillBonus;
finalScore += Game::stats.enemyKills[(int)EnemyType::Spider] * spiderKillBonus;
DrawMenuRoom();
PrintScoreCentered(finalScore);
switch(Game::stats.killedBy) {
case EnemyType::Exit:
Font::PrintString(PSTR("You have left the game."), 1, 18, COLOUR_BLACK);
break;
case EnemyType::None:
Font::PrintString(PSTR("You escaped the base!"), 1, 22, COLOUR_BLACK);
break;
case EnemyType::Mage:
Font::PrintString(PSTR("Killed by an imp on level"), 1, 8, COLOUR_BLACK);
Font::PrintInt(Game::floor, 1, 112, COLOUR_BLACK);
break;
case EnemyType::Skeleton:
Font::PrintString(PSTR("Killed by a demon on level"), 1, 6, COLOUR_BLACK);
Font::PrintInt(Game::floor, 1, 114, COLOUR_BLACK);
break;
case EnemyType::Bat:
Font::PrintString(PSTR("Killed by a sergeant on level"), 1, 2, COLOUR_BLACK);
Font::PrintInt(Game::floor, 1, 120, COLOUR_BLACK);
break;
case EnemyType::Spider:
Font::PrintString(PSTR("Killed by a zombie on level"), 1, 4, COLOUR_BLACK);
Font::PrintInt(Game::floor, 1, 116, COLOUR_BLACK);
break;
}
constexpr uint8_t firstRow = 21;
constexpr uint8_t secondRow = 38;
int offset = (Game::globalTickFrame & 8) == 0 ? 32 : 0;
Renderer::DrawScaled(chestSpriteData, 6, firstRow, 9, 255, false, COLOUR_WHITE);
Font::PrintInt(Game::stats.chestsOpened, 4, 24, COLOUR_BLACK);
Renderer::DrawScaled(crownSpriteData, 6, secondRow, 9, 255, false, COLOUR_WHITE);
Font::PrintInt(Game::stats.crownsCollected, 6, 24, COLOUR_BLACK);
Renderer::DrawScaled(scrollSpriteData, 36, firstRow, 9, 255, false, COLOUR_WHITE);
Font::PrintInt(Game::stats.scrollsCollected, 4, 54, COLOUR_BLACK);
Renderer::DrawScaled(coinsSpriteData, 36, secondRow, 9, 255, false, COLOUR_WHITE);
Font::PrintInt(Game::stats.coinsCollected, 6, 54, COLOUR_BLACK);
Renderer::DrawScaled(skeletonSpriteData + offset, 72, firstRow, 9, 255, false, COLOUR_WHITE);
Font::PrintInt(Game::stats.enemyKills[(int)EnemyType::Skeleton], 4, 90, COLOUR_BLACK);
Renderer::DrawScaled(mageSpriteData + offset, 72, secondRow, 9, 255, false, COLOUR_WHITE);
Font::PrintInt(Game::stats.enemyKills[(int)EnemyType::Mage], 6, 90, COLOUR_BLACK);
Renderer::DrawScaled(batSpriteData + offset, 102, firstRow, 9, 255, false, COLOUR_WHITE);
Font::PrintInt(Game::stats.enemyKills[(int)EnemyType::Bat], 4, 120, COLOUR_BLACK);
Renderer::DrawScaled(spiderSpriteData + offset, 102, secondRow, 9, 255, false, COLOUR_WHITE);
Font::PrintInt(Game::stats.enemyKills[(int)EnemyType::Spider], 6, 120, COLOUR_BLACK);
m_save[0] = Game::stats.chestsOpened;
m_save[1] = Game::stats.crownsCollected;
m_save[2] = Game::stats.scrollsCollected;
m_save[3] = Game::stats.coinsCollected;
m_save[4] = Game::stats.enemyKills[(int)EnemyType::Skeleton];
m_save[5] = Game::stats.enemyKills[(int)EnemyType::Mage];
m_save[6] = Game::stats.enemyKills[(int)EnemyType::Bat];
m_save[7] = Game::stats.enemyKills[(int)EnemyType::Spider];
m_save[8] = 0;
if(Game::floor > 0) {
m_save[8] = Game::floor - 1;
}
SetScore(finalScore);
}
void Menu::Tick() {
static uint8_t lastInput = 0;
uint8_t input = Platform::GetInput();
if(splashActive) {
if(splashTimer < SPLASH_TIME_TICKS) splashTimer++;
if(splashTimer >= SPLASH_TIME_TICKS) splashActive = false;
// any key skips the title screen
if(input && !lastInput && splashTimer > 8) splashActive = false;
lastInput = input;
return;
}
auto syncWindow = [&]() {
uint8_t maxTop = MaxTop();
if(m_selection < m_topIndex) m_topIndex = m_selection;
uint8_t end = (uint8_t)(m_topIndex + (VISIBLE_ROWS - 1));
if(m_selection > end) {
int t = (int)m_selection - (VISIBLE_ROWS - 1);
if(t < 0) t = 0;
if(t > maxTop) t = maxTop;
m_topIndex = (uint8_t)t;
}
m_cursorPos = (uint8_t)(m_selection - m_topIndex);
if(m_cursorPos >= VISIBLE_ROWS) m_cursorPos = (VISIBLE_ROWS - 1);
};
if((input & INPUT_DOWN) && !(lastInput & INPUT_DOWN)) {
uint8_t next = Wrap((int)m_selection + 1, MENU_ITEMS_COUNT);
if(m_cursorPos < (VISIBLE_ROWS - 1)) {
m_selection = next;
m_cursorPos++;
} else {
m_selection = next;
if(m_topIndex < MaxTop()) {
m_topIndex++;
} else {
m_topIndex = 0;
m_cursorPos = 0;
}
}
syncWindow();
}
if((input & INPUT_UP) && !(lastInput & INPUT_UP)) {
uint8_t prev = Wrap((int)m_selection - 1, MENU_ITEMS_COUNT);
if(m_cursorPos > 0) {
m_selection = prev;
m_cursorPos--;
} else {
m_selection = prev;
if(m_topIndex > 0) {
m_topIndex--;
} else {
m_topIndex = MaxTop();
m_cursorPos = (VISIBLE_ROWS - 1);
}
}
syncWindow();
}
if((input & (INPUT_A | INPUT_B)) && !(lastInput & (INPUT_A | INPUT_B))) {
switch(m_selection) {
case 0:
Game::StartGame();
break;
case 1:
Platform::SetAudioEnabled(!Platform::IsAudioEnabled());
break;
default:
break;
}
}
lastInput = input;
}
void Menu::TickEnteringLevel() {
constexpr uint8_t showTime = 45;
if(timer < showTime) timer++;
if(timer == showTime && Platform::GetInput() == 0) {
Game::StartLevel();
}
}
void Menu::TickGameOver() {
constexpr uint8_t minShowTime = 30;
if(timer < minShowTime) timer++;
if(timer == minShowTime && (Platform::GetInput() & (INPUT_A | INPUT_B))) {
timer++;
} else if(timer == minShowTime + 1 && Platform::GetInput() == 0) {
Game::SwitchState(Game::State::Menu);
}
}
static inline void DrawEraseTile8x8(int16_t x, int16_t y, const uint8_t* frame8bytes) {
for(uint8_t row = 0; row < 8; row++) {
uint8_t rowMask = pgm_read_byte(frame8bytes + row);
while(rowMask) {
uint8_t b = (uint8_t)__builtin_ctz((unsigned)rowMask);
uint8_t col = 7 - b;
Platform::PutPixel(x + col, y + row, COLOUR_WHITE);
rowMask &= (uint8_t)(rowMask - 1);
}
}
}
void Menu::DrawTransitionFrame(uint8_t frameIndex) {
const uint8_t w = pgm_read_byte(transitionSet + 0);
const uint8_t h = pgm_read_byte(transitionSet + 1);
(void)w;
(void)h;
const uint8_t* framePtr = transitionSet + 2 + (uint16_t)frameIndex * 8;
int16_t tileX = 120;
int16_t tileY = 56;
while(true) {
DrawEraseTile8x8(tileX, tileY, framePtr);
tileX -= 8;
if(tileX < 0) {
tileX = 120;
tileY -= 8;
if(tileY < 0) break;
}
}
}
void Menu::ResetTimer() {
timer = 0;
}
static constexpr uint8_t TOTAL_TIME = 40;
static constexpr uint8_t TOTAL_FRAMES = 8;
void Menu::RunTransition(Menu* menu, uint8_t& t, TransitionNextFn next) {
uint8_t frameIndex = (uint16_t)t * TOTAL_FRAMES / TOTAL_TIME;
if(frameIndex >= TOTAL_FRAMES) frameIndex = TOTAL_FRAMES - 1;
menu->DrawTransitionFrame(frameIndex);
if(frameIndex >= TOTAL_FRAMES - 2) {
t = 0;
next();
return;
}
++t;
}
void Menu::FadeOut() {
static uint8_t t = 0;
RunTransition(this, t, +[]() { Game::SwitchState(Game::State::GameOver); });
}
void Menu::ReadSave() {
uint8_t addr = EEPROM_BASE_ADDR;
m_score = (uint16_t)EEPROM.read(addr) | ((uint16_t)EEPROM.read(addr + 1) << 8); addr += 2;
m_high = (uint16_t)EEPROM.read(addr) | ((uint16_t)EEPROM.read(addr + 1) << 8); addr += 2;
m_storedHigh = m_high;
for(int i = 0; i < 9; i++) {
m_save[i] = EEPROM.read(addr++);
}
}
void Menu::SetScore(uint16_t score) {
if(score == 0) return;
m_high = (score > m_storedHigh) ? score : m_storedHigh;
m_score = score;
}
void Menu::WriteSave() {
uint8_t addr = EEPROM_BASE_ADDR;
EEPROM.update(addr++, (uint8_t)(m_score & 0xFF));
EEPROM.update(addr++, (uint8_t)(m_score >> 8));
EEPROM.update(addr++, (uint8_t)(m_high & 0xFF));
EEPROM.update(addr++, (uint8_t)(m_high >> 8));
for(int i = 0; i < 9; i++) {
EEPROM.update(addr++, m_save[i]);
}
EEPROM.commit();
}
+122
View File
@@ -0,0 +1,122 @@
#pragma once
#include <stdint.h>
// 8x8, 8 кадров (0..7) — как в первом коде
static const uint8_t PROGMEM transitionSet[] = {
8,
8,
// FRAME 00
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
// FRAME 01
0x00,
0x55,
0x00,
0x55,
0x00,
0x55,
0x00,
0x55,
// FRAME 02
0x00,
0xFF,
0x00,
0xFF,
0x00,
0xFF,
0x00,
0xFF,
// FRAME 03
0xAA,
0xFF,
0xAA,
0xFF,
0xAA,
0xFF,
0xAA,
0xFF,
// FRAME 04
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
// FRAME 05
0xFF,
0xAA,
0xFF,
0xAA,
0xFF,
0xAA,
0xFF,
0xAA,
// FRAME 06
0xFF,
0x00,
0xFF,
0x00,
0xFF,
0x00,
0xFF,
0x00,
// FRAME 07
0x55,
0x00,
0x55,
0x00,
0x55,
0x00,
0x55,
0x00,
};
class Menu {
public:
void Init();
void Draw();
void Tick();
void ReadSave();
void WriteSave();
void TickEnteringLevel();
void DrawEnteringLevel();
void TransitionToLevel();
void TickGameOver();
void DrawGameOver();
void ResetTimer();
void FadeOut();
private:
using TransitionNextFn = void (*)();
static void RunTransition(Menu* menu, uint8_t& t, TransitionNextFn next);
void DrawTransitionFrame(uint8_t frameIndex);
void SetScore(uint16_t score);
void PrintItem(uint8_t idx, uint8_t row);
uint8_t m_selection = 0;
uint8_t m_topIndex = 0;
uint8_t m_cursorPos = 0;
uint16_t m_score = 0;
uint16_t m_high = 0;
uint16_t m_storedHigh = 0;
uint8_t m_save[9] = {0};
union {
uint16_t timer;
uint16_t fizzleFade;
};
};
@@ -0,0 +1,149 @@
#include "game/Particle.h"
#include "game/FixedMath.h"
#include "game/Platform.h"
ParticleSystem ParticleSystemManager::systems[MAX_SYSTEMS];
void ParticleSystem::Init()
{
life = 0;
}
void ParticleSystem::Step()
{
for (Particle& p : particles)
{
if (p.IsActive())
{
p.velY += gravity;
if (p.x + p.velX < -127 || p.x + p.velX > 127 || p.y + p.velY < -127)
{
p.x = -128;
continue;
}
if (p.y + p.velY >= 128)
{
p.velY = p.velX = 0;
p.y = 127;
}
p.x += p.velX;
p.y += p.velY;
}
}
life--;
}
void ParticleSystem::Draw(int x, int halfScale)
{
int scale = 2 * halfScale;
int8_t horizon = Renderer::GetHorizon(x);
uint8_t colour = isWhite ? COLOUR_WHITE : COLOUR_BLACK;
for (Particle& p : particles)
{
if (p.IsActive())
{
int outX = x + ((p.x * scale) >> 8);
int outY = horizon + ((p.y * scale) >> 8);
if (outX >= 0 && outY >= 0 && outX < DISPLAY_WIDTH - 1 && outY < DISPLAY_HEIGHT - 1 && halfScale >= Renderer::wBuffer[outX])
{
Platform::PutPixel(outX, outY, colour);
Platform::PutPixel(outX + 1, outY, colour);
Platform::PutPixel(outX + 1, outY + 1, colour);
Platform::PutPixel(outX, outY + 1, colour);
}
}
}
}
void ParticleSystem::Explode()
{
for (Particle& p : particles)
{
p.x = (Random() & 31) - 16;
p.y = (Random() & 31) - 16;
p.velX = (Random() & 31) - 16;
p.velY = (Random() & 31) - 25;
}
life = 22;
}
void ParticleSystemManager::Draw()
{
for (ParticleSystem& system : systems)
{
if(system.IsActive())
{
int16_t screenX, screenW;
if(Renderer::TransformAndCull(system.worldX, system.worldY, screenX, screenW))
{
QueuedDrawable* drawable = Renderer::CreateQueuedDrawable((uint8_t)screenW);
if(drawable)
{
drawable->type = DrawableType::ParticleSystem;
drawable->x = (int8_t)screenX;
drawable->inverseCameraDistance = (uint8_t)screenW;
drawable->particleSystem = &system;
}
}
}
}
}
void ParticleSystemManager::Init()
{
for (ParticleSystem& system : systems)
{
system.Init();
}
}
void ParticleSystemManager::Update()
{
for (ParticleSystem& system : systems)
{
if(system.IsActive())
{
system.Step();
}
}
}
void ParticleSystemManager::CreateExplosion(int16_t worldX, int16_t worldY, bool isWhite)
{
ParticleSystem* newSystem = nullptr;
for(ParticleSystem& system : systems)
{
if(!system.IsActive())
{
newSystem = &system;
break;
}
}
if (!newSystem)
{
newSystem = &systems[0];
for (uint8_t n = 1; n < MAX_SYSTEMS; n++)
{
if (systems[n].life < newSystem->life)
{
newSystem = &systems[n];
}
}
}
newSystem->worldX = worldX;
newSystem->worldY = worldY;
newSystem->isWhite = isWhite;
newSystem->Explode();
}
@@ -0,0 +1,42 @@
#pragma once
#include <stdint.h>
#include "game/Defines.h"
#include "game/Draw.h"
#include "game/Game.h"
struct Particle
{
int8_t x, y;
int8_t velX, velY;
inline bool IsActive() { return x != -128; }
};
struct ParticleSystem
{
static constexpr int8_t gravity = 3;
int16_t worldX, worldY;
bool isWhite : 1;
uint8_t life : 7;
Particle particles[PARTICLES_PER_SYSTEM];
bool IsActive() { return life > 0; }
void Init();
void Step();
void Draw(int x, int scale);
void Explode();
};
class ParticleSystemManager
{
public:
static constexpr int MAX_SYSTEMS = 3;
static ParticleSystem systems[MAX_SYSTEMS];
static void Init();
static void Draw();
static void Update();
static void CreateExplosion(int16_t x, int16_t y, bool isWhite = false);
};
@@ -0,0 +1,365 @@
#include <furi.h>
#include <furi_hal.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <stdlib.h>
#include <string.h>
#include "lib/flipper.h"
#define COLOUR_WHITE 0
#define COLOUR_BLACK 1
#include "game/Game.h"
#include "game/Draw.h"
#include "game/FixedMath.h"
#include "game/Platform.h"
#include "game/Defines.h"
#include "game/Sounds.h"
#include "lib/EEPROM.h"
// ---------------- SOUND ----------------
typedef struct {
const uint16_t* pattern;
} SoundRequest;
static FuriMessageQueue* g_sound_queue = NULL;
static FuriThread* g_sound_thread = NULL;
static volatile bool g_sound_thread_running = false;
static const float kSoundVolume = 1.0f;
static const uint32_t kToneTickHz = 780;
static inline uint32_t arduboy_ticks_to_ms(uint16_t ticks) {
return (uint32_t)((ticks * 1000u + (kToneTickHz / 2)) / kToneTickHz);
}
static int32_t sound_thread_fn(void* ctx) {
UNUSED(ctx);
SoundRequest req;
while(g_sound_thread_running) {
if(furi_message_queue_get(g_sound_queue, &req, 50) != FuriStatusOk) continue;
if(!g_state || !g_state->audio_enabled || !req.pattern) continue;
if(!furi_hal_speaker_acquire(50)) continue;
const uint16_t* p = req.pattern;
while(g_sound_thread_running && g_state && g_state->audio_enabled) {
SoundRequest new_req;
if(furi_message_queue_get(g_sound_queue, &new_req, 0) == FuriStatusOk) {
if(new_req.pattern) p = new_req.pattern;
}
uint16_t freq = *p++;
if(freq == TONES_END) break;
uint16_t dur_ticks = *p++;
uint32_t dur_ms = arduboy_ticks_to_ms(dur_ticks);
if(dur_ms == 0) dur_ms = 1;
if(freq == 0) {
furi_hal_speaker_stop();
furi_delay_ms(dur_ms);
} else {
furi_hal_speaker_start((float)freq, kSoundVolume);
furi_delay_ms(dur_ms);
furi_hal_speaker_stop();
}
}
furi_hal_speaker_stop();
furi_hal_speaker_release();
}
if(furi_hal_speaker_is_mine()) {
furi_hal_speaker_stop();
furi_hal_speaker_release();
}
return 0;
}
static void sound_system_init() {
if(g_sound_queue || g_sound_thread) return;
g_sound_queue = furi_message_queue_alloc(4, sizeof(SoundRequest));
g_sound_thread = furi_thread_alloc();
furi_thread_set_name(g_sound_thread, "GameSound");
furi_thread_set_stack_size(g_sound_thread, 1024);
furi_thread_set_priority(g_sound_thread, FuriThreadPriorityNormal);
furi_thread_set_callback(g_sound_thread, sound_thread_fn);
g_sound_thread_running = true;
furi_thread_start(g_sound_thread);
}
static void sound_system_deinit() {
if(!g_sound_thread) return;
g_sound_thread_running = false;
furi_thread_join(g_sound_thread);
furi_thread_free(g_sound_thread);
g_sound_thread = NULL;
if(g_sound_queue) {
furi_message_queue_free(g_sound_queue);
g_sound_queue = NULL;
}
}
void Platform::PlaySound(const uint16_t* audioPattern) {
if(!g_state || !g_state->audio_enabled || !audioPattern || !g_sound_queue) return;
SoundRequest req = {.pattern = audioPattern};
if(furi_message_queue_put(g_sound_queue, &req, 0) != FuriStatusOk) {
SoundRequest dummy;
(void)furi_message_queue_get(g_sound_queue, &dummy, 0);
(void)furi_message_queue_put(g_sound_queue, &req, 0);
}
}
bool Platform::IsAudioEnabled() {
return g_state && g_state->audio_enabled;
}
void Platform::SetAudioEnabled(bool enabled) {
if(!g_state) return;
bool was_enabled = g_state->audio_enabled;
g_state->audio_enabled = enabled;
if(enabled && !was_enabled)
sound_system_init();
else if(!enabled && was_enabled)
sound_system_deinit();
}
// ---------------- INPUT ----------------
uint8_t Platform::GetInput() {
return g_state ? g_state->input_state : 0;
}
// ---------------- DRAW ----------------
static constexpr uint8_t kDisplayPages = DISPLAY_HEIGHT / 8;
static inline int16_t floor_div8(int16_t value) {
return (value >= 0) ? (value >> 3) : (int16_t)(-(((-value) + 7) >> 3));
}
static inline void set_pixel(int16_t x, int16_t y, bool color) {
if(!g_state) return;
if((uint16_t)x >= DISPLAY_WIDTH || (uint16_t)y >= DISPLAY_HEIGHT) return;
uint8_t* buf = g_state->back_buffer;
uint16_t idx = (uint16_t)(x + (y >> 3) * DISPLAY_WIDTH);
uint8_t mask = (uint8_t)(1u << (y & 7));
if(color)
buf[idx] |= mask;
else
buf[idx] &= (uint8_t)~mask;
}
void Platform::PutPixel(uint8_t x, uint8_t y, uint8_t color) {
set_pixel(x, y, color);
}
void Platform::FillScreen(uint8_t color) {
if(!g_state) return;
memset(g_state->back_buffer, color ? 0xFF : 0x00, BUFFER_SIZE);
}
uint8_t* Platform::GetScreenBuffer() {
return g_state ? g_state->back_buffer : NULL;
}
void Platform::DrawVLine(uint8_t x, int8_t y0, int8_t y1, uint8_t pattern) {
if(!g_state || pattern == 0 || x >= DISPLAY_WIDTH) return;
int16_t top = y0;
int16_t bottom = y1;
if(top > bottom) {
int16_t t = top;
top = bottom;
bottom = t;
}
if(bottom < 0 || top >= DISPLAY_HEIGHT) return;
if(top < 0) top = 0;
if(bottom >= DISPLAY_HEIGHT) bottom = DISPLAY_HEIGHT - 1;
uint8_t start_page = (uint8_t)(top >> 3);
uint8_t end_page = (uint8_t)(bottom >> 3);
uint8_t start_bit = (uint8_t)(top & 7);
uint8_t end_bit = (uint8_t)(bottom & 7);
uint8_t* buf = g_state->back_buffer;
if(start_page == end_page) {
uint8_t clip_mask =
(uint8_t)((uint8_t)(0xFFu << start_bit) & (uint8_t)(0xFFu >> (7u - end_bit)));
buf[(uint16_t)x + (uint16_t)start_page * DISPLAY_WIDTH] |= (uint8_t)(pattern & clip_mask);
return;
}
buf[(uint16_t)x + (uint16_t)start_page * DISPLAY_WIDTH] |=
(uint8_t)(pattern & (uint8_t)(0xFFu << start_bit));
for(uint8_t page = (uint8_t)(start_page + 1); page < end_page; page++) {
buf[(uint16_t)x + (uint16_t)page * DISPLAY_WIDTH] |= pattern;
}
buf[(uint16_t)x + (uint16_t)end_page * DISPLAY_WIDTH] |=
(uint8_t)(pattern & (uint8_t)(0xFFu >> (7u - end_bit)));
}
static inline uint8_t get_page_mask(uint8_t page, uint8_t total_pages, uint8_t height) {
if((page + 1u) != total_pages) return 0xFFu;
uint8_t tail_bits = (uint8_t)(height & 7u);
return tail_bits ? (uint8_t)((1u << tail_bits) - 1u) : 0xFFu;
}
void Platform::DrawBitmap(int16_t x, int16_t y, const uint8_t* bmp) {
if(!g_state || !bmp) return;
uint8_t w = bmp[0];
uint8_t h = bmp[1];
if(!w || !h) return;
int16_t x0 = x < 0 ? 0 : x;
int16_t x1 = x + w;
if(x1 > DISPLAY_WIDTH) x1 = DISPLAY_WIDTH;
if(x0 >= x1) return;
const uint8_t* data = bmp + 2;
uint8_t pages = (uint8_t)((h + 7u) >> 3);
uint8_t* dst = g_state->back_buffer;
for(int16_t dx = x0; dx < x1; dx++) {
uint8_t sx = (uint8_t)(dx - x);
const uint8_t* src_col = data + sx;
for(uint8_t page = 0; page < pages; page++) {
int16_t base_y = y + ((int16_t)page << 3);
if(base_y <= -8 || base_y >= DISPLAY_HEIGHT) continue;
uint8_t src = src_col[(uint16_t)page * w] & get_page_mask(page, pages, h);
if(src == 0) continue;
int16_t dst_page = floor_div8(base_y);
uint8_t y_shift = (uint8_t)(base_y - (dst_page << 3));
uint8_t low = (uint8_t)(src << y_shift);
if((uint16_t)dst_page < kDisplayPages) {
uint16_t idx = (uint16_t)dx + (uint16_t)dst_page * DISPLAY_WIDTH;
dst[idx] &= (uint8_t)~low;
}
if(y_shift && (uint16_t)(dst_page + 1) < kDisplayPages) {
uint8_t high = (uint8_t)(src >> (8u - y_shift));
uint16_t idx = (uint16_t)dx + (uint16_t)(dst_page + 1) * DISPLAY_WIDTH;
dst[idx] &= (uint8_t)~high;
}
}
}
}
void Platform::DrawSolidBitmap(int16_t x, int16_t y, const uint8_t* bmp) {
if(!g_state || !bmp) return;
uint8_t w = bmp[0];
uint8_t h = bmp[1];
if(!w || !h) return;
int16_t x0 = x < 0 ? 0 : x;
int16_t x1 = x + w;
if(x1 > DISPLAY_WIDTH) x1 = DISPLAY_WIDTH;
if(x0 >= x1) return;
const uint8_t* data = bmp + 2;
uint8_t pages = (uint8_t)((h + 7u) >> 3);
uint8_t* dst = g_state->back_buffer;
for(int16_t dx = x0; dx < x1; dx++) {
uint8_t sx = (uint8_t)(dx - x);
const uint8_t* src_col = data + sx;
for(uint8_t page = 0; page < pages; page++) {
int16_t base_y = y + ((int16_t)page << 3);
if(base_y <= -8 || base_y >= DISPLAY_HEIGHT) continue;
uint8_t page_mask = get_page_mask(page, pages, h);
uint8_t src = src_col[(uint16_t)page * w] & page_mask;
uint8_t fill = (uint8_t)(~src) & page_mask;
int16_t dst_page = floor_div8(base_y);
uint8_t y_shift = (uint8_t)(base_y - (dst_page << 3));
uint8_t region_low = (uint8_t)(page_mask << y_shift);
uint8_t fill_low = (uint8_t)(fill << y_shift);
if((uint16_t)dst_page < kDisplayPages) {
uint16_t idx = (uint16_t)dx + (uint16_t)dst_page * DISPLAY_WIDTH;
dst[idx] = (uint8_t)((dst[idx] & (uint8_t)~region_low) | fill_low);
}
if(y_shift && (uint16_t)(dst_page + 1) < kDisplayPages) {
uint8_t region_high = (uint8_t)(page_mask >> (8u - y_shift));
uint8_t fill_high = (uint8_t)(fill >> (8u - y_shift));
uint16_t idx = (uint16_t)dx + (uint16_t)(dst_page + 1) * DISPLAY_WIDTH;
dst[idx] = (uint8_t)((dst[idx] & (uint8_t)~region_high) | fill_high);
}
}
}
}
void Platform::DrawSprite(int16_t x, int16_t y, const uint8_t* bmp, uint8_t frame) {
if(!g_state || !bmp) return;
uint8_t w = bmp[0];
uint8_t h = bmp[1];
if(!w || !h) return;
int16_t x0 = x < 0 ? 0 : x;
int16_t x1 = x + w;
if(x1 > DISPLAY_WIDTH) x1 = DISPLAY_WIDTH;
if(x0 >= x1) return;
uint8_t pages = (uint8_t)((h + 7u) >> 3);
uint16_t frame_size = (uint16_t)(w * pages);
const uint8_t* data = bmp + 2 + (uint32_t)frame * frame_size * 2u;
uint8_t* dst = g_state->back_buffer;
for(int16_t dx = x0; dx < x1; dx++) {
uint8_t sx = (uint8_t)(dx - x);
for(uint8_t page = 0; page < pages; page++) {
int16_t base_y = y + ((int16_t)page << 3);
if(base_y <= -8 || base_y >= DISPLAY_HEIGHT) continue;
uint16_t src_index = (uint16_t)((page * w + sx) * 2u);
uint8_t src = data[src_index];
uint8_t mask = data[src_index + 1] & get_page_mask(page, pages, h);
if(mask == 0) continue;
uint8_t fill = (uint8_t)(src & mask);
int16_t dst_page = floor_div8(base_y);
uint8_t y_shift = (uint8_t)(base_y - (dst_page << 3));
uint8_t region_low = (uint8_t)(mask << y_shift);
uint8_t fill_low = (uint8_t)(fill << y_shift);
if((uint16_t)dst_page < kDisplayPages) {
uint16_t idx = (uint16_t)dx + (uint16_t)dst_page * DISPLAY_WIDTH;
dst[idx] = (uint8_t)((dst[idx] & (uint8_t)~region_low) | fill_low);
}
if(y_shift && (uint16_t)(dst_page + 1) < kDisplayPages) {
uint8_t region_high = (uint8_t)(mask >> (8u - y_shift));
uint8_t fill_high = (uint8_t)(fill >> (8u - y_shift));
uint16_t idx = (uint16_t)dx + (uint16_t)(dst_page + 1) * DISPLAY_WIDTH;
dst[idx] = (uint8_t)((dst[idx] & (uint8_t)~region_high) | fill_high);
}
}
}
}
@@ -0,0 +1,24 @@
#pragma once
#include <stdint.h>
class Platform
{
public:
static uint8_t GetInput(void);
static uint8_t* GetScreenBuffer();
static void PlaySound(const uint16_t* audioPattern);
static bool IsAudioEnabled();
static void SetAudioEnabled(bool isEnabled);
static void FillScreen(uint8_t col);
static void PutPixel(uint8_t x, uint8_t y, uint8_t colour);
static void DrawBitmap(int16_t x, int16_t y, const uint8_t *bitmap);
static void DrawSolidBitmap(int16_t x, int16_t y, const uint8_t *bitmap);
static void DrawSprite(int16_t x, int16_t y, const uint8_t *bitmap, const uint8_t *mask, uint8_t frame, uint8_t mask_frame);
static void DrawSprite(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame);
static void DrawVLine(uint8_t x, int8_t y1, int8_t y2, uint8_t pattern);
static void DrawBackground();
};
@@ -0,0 +1,328 @@
#include "game/Player.h"
#include "game/Game.h"
#include "game/FixedMath.h"
#include "game/Projectile.h"
#include "game/Platform.h"
#include "game/Draw.h"
#include "game/Enemy.h"
#include "game/Map.h"
#include "game/Sounds.h"
#include "game/Particle.h"
#define USE_ROTATE_BOB 0
#define STRAFE_TILT 14
#define ROTATE_TILT 3
const char SignMessage1[] PROGMEM = "Abandon all hope ye who enter!";
void Player::Init()
{
NextLevel();
hp = maxHP;
}
void Player::NextLevel()
{
x = CELL_SIZE * 1 + CELL_SIZE / 2;
y = CELL_SIZE * 1 + CELL_SIZE / 2;
angle = FIXED_ANGLE_45;
mana = maxMana;
damageTime = 0;
shakeTime = 0;
reloadTime = 0;
velocityX = 0;
velocityY = 0;
angularVelocity = 0;
}
void Player::Fire()
{
if (mana >= manaFireCost)
{
reloadTime = 8;
shakeTime = 6;
int16_t projectileX = x + FixedCos(angle + FIXED_ANGLE_90 / 2) / 4;
int16_t projectileY = y + FixedSin(angle + FIXED_ANGLE_90 / 2) / 4;
ProjectileManager::FireProjectile(this, projectileX, projectileY, angle);
mana -= manaFireCost;
Platform::PlaySound(Sounds::Attack);
}
}
void Player::Tick()
{
uint8_t input = Platform::GetInput();
int8_t turnDelta = 0;
int8_t targetTilt = 0;
int8_t moveDelta = 0;
int8_t strafeDelta = 0;
// Doom-style circle strafe: holding OK (fire) makes left/right strafe
if (input & (INPUT_A | INPUT_B))
{
if (input & INPUT_LEFT)
{
strafeDelta--;
}
if (input & INPUT_RIGHT)
{
strafeDelta++;
}
}
else
{
if (input & INPUT_LEFT)
{
turnDelta -= TURN_SPEED * 2;
}
if (input & INPUT_RIGHT)
{
turnDelta += TURN_SPEED * 2;
}
}
// Testing shooting / recoil mechanic
if (reloadTime > 0)
{
reloadTime--;
}
else if (input & INPUT_B)
{
Fire();
}
if (angularVelocity < turnDelta)
{
angularVelocity++;
}
else if (angularVelocity > turnDelta)
{
angularVelocity--;
}
angle += angularVelocity >> 1;
if (input & INPUT_UP)
{
moveDelta++;
}
if (input & INPUT_DOWN)
{
moveDelta--;
}
static int tiltTimer = 0;
tiltTimer++;
if (moveDelta && USE_ROTATE_BOB)
{
targetTilt = (int8_t)(FixedSin(tiltTimer * 10) / 32);
}
else
{
targetTilt = 0;
}
targetTilt += angularVelocity * ROTATE_TILT;
targetTilt += strafeDelta * STRAFE_TILT;
int8_t targetBob = moveDelta || strafeDelta ? FixedSin(tiltTimer * 10) / 128 : 0;
if (shakeTime > 0)
{
shakeTime--;
targetBob += (Random() & 3) - 1;
targetTilt += (Random() & 31) - 16;
}
constexpr int tiltRate = 6;
if (Renderer::camera.tilt < targetTilt)
{
Renderer::camera.tilt += tiltRate;
if (Renderer::camera.tilt > targetTilt)
{
Renderer::camera.tilt = targetTilt;
}
}
else if (Renderer::camera.tilt > targetTilt)
{
Renderer::camera.tilt -= tiltRate;
if (Renderer::camera.tilt < targetTilt)
{
Renderer::camera.tilt = targetTilt;
}
}
constexpr int bobRate = 3;
if (Renderer::camera.bob < targetBob)
{
Renderer::camera.bob += bobRate;
if (Renderer::camera.bob > targetBob)
{
Renderer::camera.bob = targetBob;
}
}
else if (Renderer::camera.bob > targetBob)
{
Renderer::camera.bob -= bobRate;
if (Renderer::camera.bob < targetBob)
{
Renderer::camera.bob = targetBob;
}
}
int16_t cosAngle = FixedCos(angle);
int16_t sinAngle = FixedSin(angle);
int16_t cos90Angle = FixedCos(angle + FIXED_ANGLE_90);
int16_t sin90Angle = FixedSin(angle + FIXED_ANGLE_90);
//camera.x += (moveDelta * cosAngle) >> 4;
//camera.y += (moveDelta * sinAngle) >> 4;
velocityX += (moveDelta * cosAngle) / 24;
velocityY += (moveDelta * sinAngle) / 24;
velocityX += (strafeDelta * cos90Angle) / 24;
velocityY += (strafeDelta * sin90Angle) / 24;
Move(velocityX / 4, velocityY / 4);
velocityX = (velocityX * 7) / 8;
velocityY = (velocityY * 7) / 8;
if (mana < maxMana && reloadTime == 0)
{
mana += manaRechargeRate;
}
if (damageTime > 0)
damageTime--;
uint8_t cellX = x / CELL_SIZE;
uint8_t cellY = y / CELL_SIZE;
switch (Map::GetCellSafe(cellX, cellY))
{
case CellType::Potion:
if (hp < maxHP)
{
if (hp + potionStrength > maxHP)
hp = maxHP;
else
hp += potionStrength;
Map::SetCell(cellX, cellY, CellType::Empty);
Platform::PlaySound(Sounds::Pickup);
Game::ShowMessage(PSTR("Picked up a stimpack"));
}
break;
case CellType::Coins:
Map::SetCell(cellX, cellY, CellType::Empty);
Platform::PlaySound(Sounds::Pickup);
Game::ShowMessage(PSTR("Picked up a health bonus"));
Game::stats.coinsCollected++;
break;
case CellType::Crown:
Map::SetCell(cellX, cellY, CellType::Empty);
Platform::PlaySound(Sounds::Pickup);
Game::ShowMessage(PSTR("Picked up some armor"));
Game::stats.crownsCollected++;
break;
case CellType::Scroll:
Map::SetCell(cellX, cellY, CellType::Empty);
Platform::PlaySound(Sounds::Pickup);
Game::ShowMessage(PSTR("Picked up an armor bonus"));
Game::stats.scrollsCollected++;
break;
default:
break;
}
}
bool Player::IsWorldColliding() const
{
return Map::IsBlockedAtWorldPosition(x - collisionSize, y - collisionSize)
|| Map::IsBlockedAtWorldPosition(x + collisionSize, y - collisionSize)
|| Map::IsBlockedAtWorldPosition(x + collisionSize, y + collisionSize)
|| Map::IsBlockedAtWorldPosition(x - collisionSize, y + collisionSize);
}
bool Player::CheckCollisions()
{
int16_t lookAheadX = (x + (FixedCos(angle) * lookAheadDistance) / FIXED_ONE);
int16_t lookAheadY = (y + (FixedSin(angle) * lookAheadDistance) / FIXED_ONE);
uint8_t lookAheadCellX = (uint8_t)(lookAheadX / CELL_SIZE);
uint8_t lookAheadCellY = (uint8_t)(lookAheadY / CELL_SIZE);
CellType lookAheadCell = Map::GetCellSafe(lookAheadCellX, lookAheadCellY);
switch (lookAheadCell)
{
case CellType::Chest:
Map::SetCell(lookAheadCellX, lookAheadCellY, CellType::ChestOpened);
ParticleSystemManager::CreateExplosion(lookAheadX, lookAheadY, true);
Platform::PlaySound(Sounds::Pickup);
Game::ShowMessage(PSTR("Found a backpack of ammo!"));
Game::stats.chestsOpened++;
break;
case CellType::Sign:
Game::ShowMessage(SignMessage1);
break;
default:
break;
}
if (IsWorldColliding())
{
return true;
}
if (EnemyManager::GetOverlappingEnemy(*this))
{
return true;
}
return false;
}
void Player::Move(int16_t deltaX, int16_t deltaY)
{
x += deltaX;
y += deltaY;
if (CheckCollisions())
{
y -= deltaY;
if (CheckCollisions())
{
x -= deltaX;
y += deltaY;
if (CheckCollisions())
{
y -= deltaY;
}
}
}
}
void Player::Damage(uint8_t damageAmount)
{
if(shakeTime < 6)
shakeTime = 6;
damageTime = 8;
if (hp <= damageAmount)
{
Platform::PlaySound(Sounds::PlayerDeath);
hp = 0;
}
else
{
Platform::PlaySound(Sounds::Ouch);
hp -= damageAmount;
}
}
@@ -0,0 +1,37 @@
#pragma once
#include <stdint.h>
#include "game/Entity.h"
class Player : public Entity
{
public:
uint8_t angle;
int16_t velocityX, velocityY;
int8_t angularVelocity;
uint8_t shakeTime;
uint8_t damageTime;
uint8_t reloadTime;
static constexpr uint8_t maxHP = 100;
static constexpr uint8_t maxMana = 100;
static constexpr uint8_t manaFireCost = 20;
static constexpr uint8_t manaRechargeRate = 1;
static constexpr uint8_t attackStrength = 10;
static constexpr uint8_t collisionSize = 48;
static constexpr uint8_t lookAheadDistance = 60;
static constexpr uint8_t potionStrength = 25;
uint8_t hp;
uint8_t mana;
void Init();
void NextLevel();
void Tick();
void Fire();
void Move(int16_t deltaX, int16_t deltaY);
bool CheckCollisions();
void Damage(uint8_t amount);
bool IsWorldColliding() const;
};
@@ -0,0 +1,185 @@
#include "game/Defines.h"
#include "game/Projectile.h"
#include "game/Map.h"
#include "game/FixedMath.h"
#include "game/Particle.h"
#include "game/Enemy.h"
#include "game/Generated/SpriteTypes.h"
#include "game/Platform.h"
#include "game/Sounds.h"
Projectile ProjectileManager::projectiles[ProjectileManager::MAX_PROJECTILES];
Projectile* ProjectileManager::FireProjectile(Entity* owner, int16_t x, int16_t y, uint8_t angle)
{
for (Projectile& p : projectiles)
{
if(p.life == 0)
{
if (owner == &Game::player)
p.ownerId = Projectile::playerOwnerId;
else
{
for (uint8_t n = 0; n < EnemyManager::maxEnemies; n++)
{
if (&EnemyManager::enemies[n] == owner)
{
p.ownerId = n;
break;
}
}
}
p.life = 255;
p.x = x;
p.y = y;
p.angle = angle;
return &p;
}
}
return nullptr;
}
Entity* Projectile::GetOwner() const
{
if (ownerId == playerOwnerId)
return &Game::player;
return &EnemyManager::enemies[ownerId];
}
void ProjectileManager::Update()
{
for (Projectile& p : projectiles)
{
if(p.life > 0)
{
p.life--;
int16_t deltaX = FixedCos(p.angle) / 4;
int16_t deltaY = FixedSin(p.angle) / 4;
p.x += deltaX;
p.y += deltaY;
bool hitAnything = false;
Entity* owner = p.GetOwner();
if (Map::IsBlockedAtWorldPosition(p.x, p.y))
{
uint8_t cellX = p.x / CELL_SIZE;
uint8_t cellY = p.y / CELL_SIZE;
if (Map::GetCellSafe(cellX, cellY) == CellType::Urn)
{
// Exploding barrel: splash damage to everything nearby
int16_t barrelX = cellX * CELL_SIZE + CELL_SIZE / 2;
int16_t barrelY = cellY * CELL_SIZE + CELL_SIZE / 2;
constexpr int16_t blastRadius = CELL_SIZE + CELL_SIZE / 2;
constexpr uint8_t enemyBlastDamage = 40;
constexpr uint8_t playerBlastDamage = 15;
Map::SetCell(cellX, cellY, CellType::Empty);
ParticleSystemManager::CreateExplosion(barrelX, barrelY, true);
for (uint8_t n = 0; n < EnemyManager::maxEnemies; n++)
{
Enemy& enemy = EnemyManager::enemies[n];
if (!enemy.IsValid())
continue;
int16_t dx = enemy.x - barrelX;
int16_t dy = enemy.y - barrelY;
if (ABS(dx) < blastRadius && ABS(dy) < blastRadius)
{
enemy.Damage(enemyBlastDamage);
}
}
{
int16_t dx = Game::player.x - barrelX;
int16_t dy = Game::player.y - barrelY;
if (ABS(dx) < blastRadius && ABS(dy) < blastRadius)
{
// barrels hurt but never kill the player outright
uint8_t dmg = playerBlastDamage;
if (Game::player.hp <= dmg)
dmg = Game::player.hp > 1 ? (uint8_t)(Game::player.hp - 1) : 0;
if (dmg)
Game::player.Damage(dmg);
}
}
// occasionally the barrel leaves a pickup behind
switch ((Random() % 6))
{
case 0:
Map::SetCell(cellX, cellY, CellType::Potion);
break;
case 1:
Map::SetCell(cellX, cellY, CellType::Coins);
break;
}
Platform::PlaySound(Sounds::Kill);
}
else
{
Platform::PlaySound(Sounds::Hit);
}
hitAnything = true;
}
else
{
if (owner == &Game::player)
{
Enemy* overlappingEnemy = EnemyManager::GetOverlappingEnemy(p.x, p.y);
if (overlappingEnemy)
{
overlappingEnemy->Damage(Player::attackStrength);
hitAnything = true;
}
}
else if(Game::player.IsOverlappingPoint(p.x, p.y))
{
const EnemyArchetype* enemyArchetype = ((Enemy*)owner)->GetArchetype();
if (enemyArchetype)
{
Game::player.Damage(enemyArchetype->GetAttackStrength());
if (Game::player.hp == 0)
{
Game::stats.killedBy = ((Enemy*)owner)->GetType();
}
}
hitAnything = true;
}
}
if (hitAnything)
{
ParticleSystemManager::CreateExplosion(p.x - deltaX, p.y - deltaY);
p.life = 0;
}
}
}
}
void ProjectileManager::Init()
{
for (Projectile& p : projectiles)
{
p.life = 0;
}
}
void ProjectileManager::Draw()
{
for(Projectile& p : projectiles)
{
if (p.life > 0)
{
Renderer::DrawObject(p.ownerId == Projectile::playerOwnerId ? projectileSpriteData : enemyProjectileSpriteData, p.x, p.y, 32, AnchorType::BelowCenter);
}
}
}
@@ -0,0 +1,28 @@
#pragma once
#include <stdint.h>
#include "game/Entity.h"
class Projectile : public Entity
{
public:
uint8_t angle;
uint8_t life;
uint8_t ownerId;
static constexpr uint8_t playerOwnerId = 0xff;
Entity* GetOwner() const;
};
class ProjectileManager
{
public:
static constexpr int MAX_PROJECTILES = 8;
static Projectile projectiles[MAX_PROJECTILES];
static Projectile* FireProjectile(Entity* owner, int16_t x, int16_t y, uint8_t angle);
static void Init();
static void Draw();
static void Update();
};
@@ -0,0 +1,60 @@
#include "game/Sounds.h"
// Shotgun blast: sharp crack followed by a fast descending boom with a
// short pump echo. All frequencies within the buzzer's 100-2500 Hz range.
const uint16_t Sounds::Attack[] PROGMEM = {
900, 2, 500, 3, 320, 4, 230, 5,
180, 6, 150, 7, 128, 9, 112, 11, 104, 13, 100, 15,
0, 8,
170, 4, 135, 6, 112, 9, 100, 14,
TONES_END
};
const uint16_t Sounds::Kill[] PROGMEM = {
0x0151,0x0007,0x0000,0x0015,0x018d,0x0007,0x0000,0x0007,0x014b,0x0007,0x0136,0x0007,0x0146,0x0007,0x0169,0x0007,0x019e,0x0007,0x007f,0x0007,0x0185,
0x0007,0x0228,0x0007,0x026d,0x0007,0x02fc,0x0007,0x02e0,0x0007,0x01fd,0x0007,0x0219,0x0007,0x033c,0x0007,0x00e7,0x0007,0x0281,0x0007,0x026d,
0x000e,0x052d,0x000e,0x04da,0x000e,0x011c,0x0007,0x0387,0x0007,0x0360,0x0007,0x033c,0x0007,0x0387,0x0007,0x02ad,0x000e,0x02c6,0x0007,0x010c,
0x000e,0x02e0,0x0007,0x02c6,0x0007,0x0296,0x000e,0x0281,0x0007,0x02ad,0x0007,0x02c6,0x0007,0x02e0,0x0007,0x00e1,0x0007,0x031b,0x0007,0x0248,
0x0007,0x0219,0x0007,0x01f1,0x0007,0x01d9,0x000e,0x01f1,0x0007,0x020b,0x0007,0x02ad,0x0007,0x00d1,0x0007,0x0248,0x0007,0x0238,0x0007,0x0219,
0x0007,0x0228,0x000e,0x020b,0x0007,0x01e5,0x0007,0x01d9,0x0007, TONES_END
};
const uint16_t Sounds::Hit[] PROGMEM = {
0x0195,0x0007,0x0000,0x0015,0x018d,0x0007,0x0000,0x0007,0x0416,0x0007,0x007f,0x0007,0x0000,0x000e,0x0177,0x0007,0x0000,0x001d,0x0146,0x0007,0x0140,
0x0007,0x013b,0x0007,0x0000,0x0047,0x00e7,0x0007,0x00e4,0x0007,0x0000,0x0015,0x009f,0x0007,0x009d,0x0007,0x0000,0x0088,0x0088,0x0007, TONES_END
};
const uint16_t Sounds::PlayerDeath[] PROGMEM = {
0x01b0,0x0007,0x0000,0x0015,0x00c8,0x0007,0x0000,0x0007,0x03b2,0x0007,0x0360,0x0007,0x02e0,0x0007,0x0296,0x0007,0x0248,0x0007,0x0219,0x0007,0x01fd,
0x0007,0x0450,0x0007,0x01ce,0x0007,0x01b9,0x0007,0x01a7,0x0007,0x0450,0x0007,0x017e,0x0007,0x0163,0x0007,0x0140,0x0007,0x052d,0x0007,0x0110,
0x0007,0x0109,0x0007,0x0102,0x0007,0x00f8,0x0007,0x04da,0x0007,0x00ca,0x0007,0x00bb,0x0007,0x00b3,0x0007,0x0491,0x0007,0x0096,0x0007,0x00d8,
0x0007,0x0000,0x001d,0x0296,0x0007,0x0000,0x002b,0x0228,0x0007,0x0000,0x0024,0x00fb,0x000e, TONES_END
};
const uint16_t Sounds::SpotPlayer[] PROGMEM = {
0x0110,0x0007,0x0000,0x0015,0x018d,0x0007,0x0000,0x0007,0x01d9,0x0007,0x01fd,0x0007,0x020b,0x000e,0x0228,0x000e,0x0238,0x0007,0x0248,0x0007,0x026d,
0x0007,0x0281,0x0032,0x026d,0x0015,0x0248,0x0007,0x0238,0x0007,0x01b0,0x0007,0x017e,0x0007,0x0169,0x0007,0x014b,0x0007,0x0136,0x0007,0x0131,
0x0007,0x0102,0x0007,0x00f8,0x0007,0x00f2,0x0007,0x00e9,0x0007,0x00e4,0x0007,0x00d8,0x0007,0x00bd,0x0007,0x00ab,0x0007,0x009d,0x0007,0x0096,
0x0007,0x0094,0x0007,0x0092,0x0007,0x008e,0x0007,0x008b,0x0007,0x008a,0x0007,0x0089,0x0007,0x0088,0x0007,0x0087,0x0007,0x0086,0x0007,0x0085,
0x0007,0x0084,0x0007,0x0083,0x0007,0x0082,0x0007,0x0081,0x000e,0x0081,0x0007,0x0080,0x0007,0x007e,0x0007,0x007d,0x0007,0x007d,0x0007,0x0000,
0x0040,0x0090,0x0032, TONES_END
};
const uint16_t Sounds::Shoot[] PROGMEM = {
0x02e0,0x0007,0x0000,0x0015,0x03e2,0x0007,0x0000,0x0007,0x04da,0x0015,0x00b4,0x0007,0x01d9,0x0007,0x0000,0x0007,0x01f1,0x000e,0x0080,0x0007,0x01f1,
0x0007,0x0000,0x000e,0x025a,0x0007,0x00e4,0x0007,0x0000,0x0015,0x00c1,0x0007,0x0000,0x000e,0x01e5,0x0007,0x0000,0x0007,0x00ac,0x0007,0x0000,
0x0015,0x0091,0x0007, TONES_END
};
const uint16_t Sounds::Pickup[] PROGMEM = {
0x0120,0x0007,0x0000,0x0015,0x00e9,0x0007,0x0000,0x0007,0x0156,0x0015,0x0000,0x0032,0x0185,0x001d,0x0000,0x0040,0x020b,0x0015,0x0000,0x0032,0x0387,
0x0024,0x0000,0x0040,0x0387,0x002b,0x0000,0x0040,0x03b2,0x0032, TONES_END
};
const uint16_t Sounds::Ouch[] PROGMEM = {
0x01ce,0x0007,0x0000,0x0015,0x018d,0x0007,0x0000,0x0007,0x0416,0x0007,0x0491,0x0007,0x03b2,0x0007,0x04da,0x0007,0x052d,0x0015,0x04da,0x0007,0x0491,
0x0007,0x02e0,0x0007,0x0296,0x0007,0x025a,0x0007,0x01f1,0x0007,0x0219,0x0007,0x0000,0x0007,0x01ce,0x0007,0x007c,0x000e,0x015c,0x0007,0x007d,
0x000e,0x0120,0x0007,0x007d,0x000e,0x00ef,0x0007,0x00d5,0x0007,0x00ca,0x0007,0x0000,0x0007,0x00c1,0x0007,0x00b9,0x0007,0x00b3,0x0007,0x00a9,
0x0007,0x007d,0x0007,0x007e,0x0007,0x0093,0x0007,0x007e,0x0007,0x0000,0x0007,0x0089,0x0007,0x0085,0x0007,0x0082,0x0007,0x0080,0x0007,0x007e,
0x0007,0x007d,0x0007, TONES_END
};
@@ -0,0 +1,18 @@
#pragma once
#include <stdint.h>
#include "game/Defines.h"
#define TONES_END 0x8000
class Sounds
{
public:
static const uint16_t Attack[];
static const uint16_t Kill[];
static const uint16_t Hit[];
static const uint16_t PlayerDeath[];
static const uint16_t SpotPlayer[];
static const uint16_t Shoot[];
static const uint16_t Pickup[];
static const uint16_t Ouch[];
};
@@ -0,0 +1,56 @@
#pragma once
#include "game/Defines.h"
// Tech-base wall panel: top/bottom seams with vertical panel gaps
const uint8_t vectorTexture0[] PROGMEM =
{
6,
0, 18, 128, 18,
0, 110, 128, 110,
32, 18, 32, 110,
64, 18, 64, 110,
96, 18, 96, 110,
48, 64, 80, 64,
};
const uint8_t vectorTexture1[] PROGMEM =
{
6,
0, 16, 128, 16 ,
0, 112, 128, 112 ,
0, 16, 0, 112,
0, 16, 128, 112,
0, 112, 128, 16,
128, 16, 128, 112,
/* 16, 16, 112, 16 ,
16, 16, 16, 128,
48, 16, 48, 128,
80, 16, 80, 128,
112, 16, 112, 128,*/
};
const uint8_t vectorTexture2[] PROGMEM =
{
12,
38,13,90,13,
38,13,64,38,
64,38,90,13,
13,38,38,64,
13,38,13,90,
13,90,38,64,
38,115,90,115,
38,115,64,90,
64,90,90,115,
90,64,115,38,
90,64,115,90,
115,38,115,90,
};
const uint8_t* const textures[] PROGMEM =
{
vectorTexture0,
vectorTexture1,
vectorTexture2,
};
@@ -0,0 +1,235 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <furi.h>
#include <storage/storage.h>
#define EEPROM_LIB_PATH APP_DATA_PATH("eeprom.bin")
class EEPROMClass {
public:
static constexpr int kSize = 16;
static constexpr size_t kPathSize = 128;
EEPROMClass()
: loaded_(false)
, dirty_(false)
, path_resolved_(false) {
memset(mem_, 0x00, kSize);
memset(file_path_, 0x00, kPathSize);
strncpy(file_path_, EEPROM_LIB_PATH, kPathSize - 1);
}
void begin() {
ensureLoaded_();
}
int length() const {
return kSize;
}
uint8_t read(int addr) const {
ensureLoaded_();
if(addr < 0 || addr >= kSize) return 0;
return mem_[addr];
}
void write(int addr, uint8_t value) {
ensureLoaded_();
if(addr < 0 || addr >= kSize) return;
mem_[addr] = value;
dirty_ = true;
}
void update(int addr, uint8_t value) {
ensureLoaded_();
if(addr < 0 || addr >= kSize) return;
if(mem_[addr] != value) {
mem_[addr] = value;
dirty_ = true;
}
}
template <typename T>
T& get(int addr, T& out) const {
ensureLoaded_();
if(addr < 0 || addr + (int)sizeof(T) > kSize) return out;
memcpy(&out, mem_ + addr, sizeof(T));
return out;
}
template <typename T>
const T& put(int addr, const T& in) {
ensureLoaded_();
if(addr < 0 || addr + (int)sizeof(T) > kSize) return in;
bool changed = false;
const uint8_t* src = reinterpret_cast<const uint8_t*>(&in);
for(size_t i = 0; i < sizeof(T); i++) {
int a = addr + (int)i;
if(mem_[a] != src[i]) {
mem_[a] = src[i];
changed = true;
}
}
if(changed) dirty_ = true;
return in;
}
void clear(uint8_t value = 0) {
ensureLoaded_();
memset(mem_, value, kSize);
dirty_ = true;
}
bool commit() {
ensureLoaded_();
if(!dirty_) return true;
const bool ok = writeFile_();
if(ok) dirty_ = false;
return ok;
}
bool isDirty() const {
return dirty_;
}
private:
bool resolvePathIfNeeded_(Storage* storage) const {
if(path_resolved_) return true;
if(!storage) return false;
FuriString* path = furi_string_alloc_set_str(file_path_);
if(!path) return false;
storage_common_resolve_path_and_ensure_app_directory(storage, path);
const char* resolved = furi_string_get_cstr(path);
bool ok = false;
if(resolved && resolved[0]) {
const size_t len = strlen(resolved);
if(len < kPathSize) {
memcpy(file_path_, resolved, len + 1);
path_resolved_ = true;
ok = true;
}
}
furi_string_free(path);
return ok;
}
static void ensureDefaultDir_(Storage* storage) {
if(!storage) return;
(void)storage_common_mkdir(storage, STORAGE_APP_DATA_PATH_PREFIX);
}
void ensureLoaded_() const {
if(loaded_) return;
Storage* storage = (Storage*)furi_record_open(RECORD_STORAGE);
if(!storage) {
return;
}
(void)resolvePathIfNeeded_(storage);
ensureDefaultDir_(storage);
File* file = storage_file_alloc(storage);
if(!file) {
furi_record_close(RECORD_STORAGE);
return;
}
memset(mem_, 0x00, kSize);
bool ok = storage_file_open(file, file_path_, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS);
if(ok) {
const uint64_t file_size = storage_file_size(file);
bool need_rewrite = false;
(void)storage_file_seek(file, 0, true);
const size_t rd = storage_file_read(file, mem_, kSize);
if(rd < (size_t)kSize) {
need_rewrite = true;
}
if(file_size != (uint64_t)kSize) {
need_rewrite = true;
}
if(need_rewrite) {
(void)storage_file_seek(file, 0, true);
const size_t wr = storage_file_write(file, mem_, kSize);
if(wr == (size_t)kSize) {
(void)storage_file_truncate(file);
(void)storage_file_sync(file);
}
}
(void)storage_file_close(file);
} else {
(void)storage_file_close(file);
}
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
loaded_ = true;
dirty_ = false;
}
bool writeFile_() const {
Storage* storage = (Storage*)furi_record_open(RECORD_STORAGE);
if(!storage) return false;
if(!path_resolved_ && !resolvePathIfNeeded_(storage)) {
furi_record_close(RECORD_STORAGE);
return false;
}
ensureDefaultDir_(storage);
File* file = storage_file_alloc(storage);
if(!file) {
furi_record_close(RECORD_STORAGE);
return false;
}
bool ok = storage_file_open(file, file_path_, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS);
bool success = false;
if(ok) {
(void)storage_file_seek(file, 0, true);
size_t wr = storage_file_write(file, mem_, kSize);
(void)storage_file_truncate(file);
(void)storage_file_sync(file);
(void)storage_file_close(file);
success = (wr == (size_t)kSize);
} else {
(void)storage_file_close(file);
}
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
return success;
}
private:
mutable uint8_t mem_[kSize];
mutable bool loaded_;
mutable bool dirty_;
mutable bool path_resolved_;
mutable char file_path_[kPathSize];
};
#if (__cplusplus >= 201703L)
inline EEPROMClass EEPROM;
#else
extern EEPROMClass EEPROM;
#ifdef EEPROM_DEFINE_INSTANCE
EEPROMClass EEPROM;
#endif
#endif
@@ -0,0 +1,36 @@
//lib/flipper.h
#pragma once
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdbool.h>
#include <stdint.h>
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 64
#define BUFFER_SIZE (DISPLAY_WIDTH * DISPLAY_HEIGHT / 8)
typedef struct {
uint8_t back_buffer[BUFFER_SIZE];
uint8_t front_buffer[BUFFER_SIZE];
Gui* gui;
Canvas* canvas;
FuriMutex* fb_mutex;
volatile uint8_t input_state;
volatile bool exit_requested;
volatile bool audio_enabled;
// back-hold логика
bool back_hold_active;
uint16_t back_hold_start;
bool back_hold_handled;
// input pubsub
FuriPubSub* input_events;
FuriPubSubSubscription* input_sub;
} FlipperState;
extern FlipperState* g_state;
+261
View File
@@ -0,0 +1,261 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <string.h>
#include "lib/flipper.h"
#include "lib/EEPROM.h"
#include "game/Game.h"
#include "game/Platform.h"
#define TARGET_FRAMERATE 30
#define HOLD_TIME_MS 300
FlipperState* g_state = NULL;
static volatile uint32_t s_input_cb_inflight = 0;
static volatile uint32_t s_fb_cb_inflight = 0;
static volatile uint8_t s_back_pressed = 0;
static inline void wait_inflight_zero(volatile uint32_t* counter) {
while(__atomic_load_n(counter, __ATOMIC_ACQUIRE) != 0) {
furi_delay_ms(1);
}
}
inline bool audio_enable(){
return !furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode);
}
static void framebuffer_commit_callback(
uint8_t* data,
size_t size,
CanvasOrientation orientation,
void* context) {
__atomic_fetch_add(&s_fb_cb_inflight, 1, __ATOMIC_RELAXED);
FlipperState* state = (FlipperState*)context;
if(!state || !data || size < BUFFER_SIZE) {
__atomic_fetch_sub(&s_fb_cb_inflight, 1, __ATOMIC_RELAXED);
return;
}
(void)orientation;
if(furi_mutex_acquire(state->fb_mutex, 0) != FuriStatusOk) {
__atomic_fetch_sub(&s_fb_cb_inflight, 1, __ATOMIC_RELAXED);
return;
}
const uint8_t* src = state->front_buffer;
for(size_t i = 0; i < BUFFER_SIZE; i++) {
data[i] = (uint8_t)(src[i] ^ 0xFF);
}
furi_mutex_release(state->fb_mutex);
__atomic_fetch_sub(&s_fb_cb_inflight, 1, __ATOMIC_RELAXED);
}
static void input_events_callback(const void* value, void* ctx) {
if(!value || !ctx) return;
__atomic_fetch_add(&s_input_cb_inflight, 1, __ATOMIC_RELAXED);
FlipperState* state = (FlipperState*)ctx;
const InputEvent* event = (const InputEvent*)value;
uint8_t bit = 0;
switch(event->key) {
case InputKeyUp:
bit = INPUT_UP;
break;
case InputKeyDown:
bit = INPUT_DOWN;
break;
case InputKeyLeft:
bit = INPUT_LEFT;
break;
case InputKeyRight:
bit = INPUT_RIGHT;
break;
case InputKeyOk:
bit = INPUT_B;
break;
case InputKeyBack:
if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
(void)__atomic_store_n(&s_back_pressed, 1, __ATOMIC_RELAXED);
} else if(event->type == InputTypeRelease) {
(void)__atomic_store_n(&s_back_pressed, 0, __ATOMIC_RELAXED);
}
break;
default:
break;
}
if(state && bit) {
if((event->type == InputTypePress) || (event->type == InputTypeRepeat)) {
(void)__atomic_fetch_or((uint8_t*)&state->input_state, bit, __ATOMIC_RELAXED);
} else if(event->type == InputTypeRelease) {
(void)__atomic_fetch_and(
(uint8_t*)&state->input_state, (uint8_t)~bit, __ATOMIC_RELAXED);
}
}
__atomic_fetch_sub(&s_input_cb_inflight, 1, __ATOMIC_RELAXED);
}
extern "C" int32_t flipdoom_app(void* p) {
UNUSED(p);
Gui* gui = NULL;
Canvas* canvas = NULL;
FuriPubSub* input_events = NULL;
FuriPubSubSubscription* input_sub = NULL;
FlipperState* st = (FlipperState*)malloc(sizeof(FlipperState));
if(!st) return -1;
memset(st, 0, sizeof(FlipperState));
g_state = st;
do {
st->fb_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!st->fb_mutex) break;
memset(st->back_buffer, 0x00, BUFFER_SIZE);
memset(st->front_buffer, 0x00, BUFFER_SIZE);
EEPROM.begin();
furi_delay_ms(50);
Platform::SetAudioEnabled(audio_enable());
Game::menu.ReadSave();
gui = (Gui*)furi_record_open(RECORD_GUI);
if(!gui) break;
st->gui = gui;
gui_add_framebuffer_callback(gui, framebuffer_commit_callback, st);
canvas = gui_direct_draw_acquire(gui);
if(!canvas) break;
st->canvas = canvas;
input_events = (FuriPubSub*)furi_record_open(RECORD_INPUT_EVENTS);
if(!input_events) break;
st->input_events = input_events;
input_sub = furi_pubsub_subscribe(input_events, input_events_callback, st);
if(!input_sub) break;
st->input_sub = input_sub;
const uint32_t tick_hz = furi_kernel_get_tick_frequency();
uint32_t period_ticks = (tick_hz + (TARGET_FRAMERATE / 2)) / TARGET_FRAMERATE;
if(period_ticks == 0) period_ticks = 1;
const uint32_t hold_ticks = (uint32_t)((HOLD_TIME_MS * tick_hz + 999u) / 1000u);
uint32_t next_tick = furi_get_tick();
bool back_was_pressed = false;
bool back_hold_fired = false;
uint32_t back_press_tick = 0;
while(!st->exit_requested) {
uint32_t now = furi_get_tick();
// frame pacing
if((int32_t)(now - next_tick) < 0) {
uint32_t dt_ticks = next_tick - now;
uint32_t dt_ms = (dt_ticks * 1000u) / tick_hz;
furi_delay_ms(dt_ms ? dt_ms : 1);
continue;
}
if((int32_t)(now - next_tick) > (int32_t)(period_ticks * 2)) {
next_tick = now;
}
next_tick += period_ticks;
const bool back_pressed = (__atomic_load_n(&s_back_pressed, __ATOMIC_RELAXED) != 0);
// BACK hold logic
if(!back_pressed) {
back_was_pressed = false;
back_hold_fired = false;
} else {
if(!back_was_pressed) {
back_was_pressed = true;
back_press_tick = now;
back_hold_fired = false;
}
if(!back_hold_fired && ((uint32_t)(now - back_press_tick) >= hold_ticks)) {
back_hold_fired = true;
if(Game::InMenu())
st->exit_requested = true;
else
Game::GoToMenu();
}
}
if(st->exit_requested) break;
Game::Tick();
Game::Draw();
// swap for framebuffer callback
furi_mutex_acquire(st->fb_mutex, FuriWaitForever);
memcpy(st->front_buffer, st->back_buffer, BUFFER_SIZE);
furi_mutex_release(st->fb_mutex);
canvas_commit(canvas);
}
} while(false);
Game::menu.WriteSave();
if(input_sub && input_events) {
furi_pubsub_unsubscribe(input_events, input_sub);
input_sub = NULL;
}
st->input_sub = NULL;
wait_inflight_zero(&s_input_cb_inflight);
(void)__atomic_store_n(&s_back_pressed, 0, __ATOMIC_RELAXED);
if(input_events) {
furi_record_close(RECORD_INPUT_EVENTS);
input_events = NULL;
}
st->input_events = NULL;
if(gui) {
gui_remove_framebuffer_callback(gui, framebuffer_commit_callback, st);
}
wait_inflight_zero(&s_fb_cb_inflight);
if(gui) {
if(canvas) {
gui_direct_draw_release(gui);
canvas = NULL;
}
furi_record_close(RECORD_GUI);
gui = NULL;
}
st->gui = NULL;
st->canvas = NULL;
if(st->fb_mutex) {
furi_mutex_free(st->fb_mutex);
st->fb_mutex = NULL;
}
Platform::SetAudioEnabled(false);
free(st);
g_state = NULL;
return 0;
}
@@ -0,0 +1,521 @@
#!/usr/bin/env python3
"""
extract_doom_assets.py
Reads the user's own Doom shareware IWAD (Doom1.WAAD is freely distributable
as a whole) and converts a selection of its sprites into 1-bit assets in the
FlipperCatacombs engine formats. Nothing from the WAD is stored in this
repository: the header is generated locally at build time from the WAD the
user already has.
Usage:
python3 tools/extract_doom_assets.py <path/to/Doom1.WAD> [output_header]
Output (default): game/Generated/DoomSprites.inc.h
Engine formats
--------------
1) Scaled sprite (16x16), uint16_t array, per frame:
16 columns x 2 words: [transparency mask, colour], bit v = row v (bit0=top)
2) Page sprite (Platform::DrawSprite): uint8_t array:
w, h, then per page (8 rows), per column: [colour byte, mask byte]
(bit0 = top row of the page)
3) Solid bitmap (Platform::DrawSolidBitmap): uint8_t array:
w, h, then per page, per column: colour byte where bit=1 means BLACK
(DrawSolidBitmap writes fill = ~src)
4) HUD icon: 8 raw page bytes (bit=1 -> white pixel)
"""
import struct
import sys
import os
# ---------------------------------------------------------------- WAD parsing
class Wad:
def __init__(self, path):
self.data = open(path, "rb").read()
ident, numlumps, diroff = struct.unpack_from("<4sII", self.data, 0)
if ident not in (b"IWAD", b"PWAD"):
raise ValueError("Not a WAD file")
self.lumps = {}
for i in range(numlumps):
off, size, name = struct.unpack_from("<II8s", self.data, diroff + 16 * i)
name = name.rstrip(b"\0").decode("ascii", "replace")
# keep first occurrence (IWAD order)
if name not in self.lumps:
self.lumps[name] = (off, size)
def lump(self, name):
off, size = self.lumps[name]
return self.data[off : off + size]
def has(self, name):
return name in self.lumps
def load_palette(wad):
pal = wad.lump("PLAYPAL")[:768]
grays = []
for i in range(256):
r, g, b = pal[i * 3], pal[i * 3 + 1], pal[i * 3 + 2]
grays.append(0.299 * r + 0.587 * g + 0.114 * b)
return grays
def decode_picture(wad, name, grays):
"""Decode Doom picture format -> (w, h, pixels) where pixels is a list of
rows; each entry is None (transparent) or gray 0..255."""
raw = wad.lump(name)
w, h, _lo, _to = struct.unpack_from("<hhhh", raw, 0)
colofs = struct.unpack_from("<%di" % w, raw, 8)
pix = [[None] * w for _ in range(h)]
for x in range(w):
p = colofs[x]
while raw[p] != 0xFF:
topdelta = raw[p]
length = raw[p + 1]
p += 3 # topdelta, length, pad
for i in range(length):
y = topdelta + i
if 0 <= y < h:
pix[y][x] = grays[raw[p]]
p += 1
p += 1 # trailing pad
return w, h, pix
# ------------------------------------------------------------- image helpers
BAYER4 = [
[0, 8, 2, 10],
[12, 4, 14, 6],
[3, 11, 1, 9],
[15, 7, 13, 5],
]
def bbox(pix):
xs, ys = [], []
for y, row in enumerate(pix):
for x, v in enumerate(row):
if v is not None:
xs.append(x)
ys.append(y)
return min(xs), min(ys), max(xs) + 1, max(ys) + 1
def crop(pix, x0, y0, x1, y1):
return [row[x0:x1] for row in pix[y0:y1]]
def box_resize(pix, nw, nh):
"""Box-filter resize of (gray|None) grid; alpha = coverage."""
h = len(pix)
w = len(pix[0])
out_gray = [[0.0] * nw for _ in range(nh)]
out_alpha = [[0.0] * nw for _ in range(nh)]
for ny in range(nh):
sy0 = ny * h / nh
sy1 = (ny + 1) * h / nh
for nx in range(nw):
sx0 = nx * w / nw
sx1 = (nx + 1) * w / nw
acc_g = acc_a = acc_w = 0.0
y = int(sy0)
while y < sy1 and y < h:
wy = min(sy1, y + 1) - max(sy0, y)
x = int(sx0)
while x < sx1 and x < w:
wx = min(sx1, x + 1) - max(sx0, x)
weight = wx * wy
acc_w += weight
v = pix[y][x]
if v is not None:
acc_a += weight
acc_g += weight * v
x += 1
y += 1
if acc_w > 0 and acc_a > 0:
out_gray[ny][nx] = acc_g / acc_a
out_alpha[ny][nx] = acc_a / acc_w
return out_gray, out_alpha
def normalize(gray, alpha, thresh=0.5):
"""Per-sprite contrast stretch over opaque pixels."""
vals = [
gray[y][x]
for y in range(len(gray))
for x in range(len(gray[0]))
if alpha[y][x] >= thresh
]
if not vals:
return gray
lo, hi = min(vals), max(vals)
if hi - lo < 1e-6:
hi = lo + 1.0
return [
[(v - lo) * 255.0 / (hi - lo) for v in row]
for row in gray
]
def to_1bit(gray, alpha, bias=0.0):
"""3-tone quantization (black / 50% checker / white) for clean tiny
sprites. Returns (colour, mask) row-major bools."""
h, w = len(gray), len(gray[0])
colour = [[False] * w for _ in range(h)]
mask = [[False] * w for _ in range(h)]
for y in range(h):
for x in range(w):
if alpha[y][x] >= 0.5:
mask[y][x] = True
v = gray[y][x] + bias
if v < 80:
colour[y][x] = False
elif v < 175:
colour[y][x] = ((x + y) & 1) == 0
else:
colour[y][x] = True
return colour, mask
def fit_grid(pix, size, valign, hpad=0):
"""Crop to bbox, keep aspect, fit into size x size grid.
valign: 'bottom' or 'center'."""
x0, y0, x1, y1 = bbox(pix)
pix = crop(pix, x0, y0, x1, y1)
w = x1 - x0
h = y1 - y0
avail = size - hpad * 2
if w >= h:
nw = avail
nh = max(1, round(h * avail / w))
else:
nh = avail
nw = max(1, round(w * avail / h))
gray, alpha = box_resize(pix, nw, nh)
# paste into size x size
g = [[0.0] * size for _ in range(size)]
a = [[0.0] * size for _ in range(size)]
ox = (size - nw) // 2
oy = (size - nh) if valign == "bottom" else (size - nh) // 2
for y in range(nh):
for x in range(nw):
g[oy + y][ox + x] = gray[y][x]
a[oy + y][ox + x] = alpha[y][x]
return g, a
# ------------------------------------------------------------ format emitters
def emit_scaled16(frames):
"""frames: list of (colour, mask) 16x16 row-major -> list of uint16 words."""
words = []
for colour, mask in frames:
for x in range(16):
t = c = 0
for y in range(16):
if mask[y][x]:
t |= 1 << y
if colour[y][x]:
c |= 1 << y
words.append(t)
words.append(c)
return words
def emit_page_sprite(colour, mask):
"""-> list of bytes: w, h, then per page per column [colour, mask]."""
h, w = len(colour), len(colour[0])
pages = (h + 7) // 8
out = [w, h]
for page in range(pages):
for x in range(w):
cb = mb = 0
for bit in range(8):
y = page * 8 + bit
if y < h and mask[y][x]:
mb |= 1 << bit
if colour[y][x]:
cb |= 1 << bit
out.append(cb)
out.append(mb)
return out
def emit_solid_bitmap(colour):
"""DrawSolidBitmap: bit=1 -> black. colour True = white pixel."""
h, w = len(colour), len(colour[0])
pages = (h + 7) // 8
out = [w, h]
for page in range(pages):
for x in range(w):
b = 0
for bit in range(8):
y = page * 8 + bit
if y < h and not colour[y][x]:
b |= 1 << bit
out.append(b)
return out
def fmt_words(words, per_line=16):
lines = []
for i in range(0, len(words), per_line):
lines.append(",".join("0x%x" % v for v in words[i : i + per_line]))
return ",\n\t".join(lines)
# ------------------------------------------------------------------ pipeline
def sprite16(wad, grays, names, valign, bias=0.0):
frames = []
for n in names:
w, h, pix = decode_picture(wad, n, grays)
g, a = fit_grid(pix, 16, valign)
g = normalize(g, a)
frames.append(to_1bit(g, a, bias))
return emit_scaled16(frames)
def first_present(wad, *names):
for n in names:
if wad.has(n):
return n
raise KeyError("none of %s in WAD" % (names,))
def build_weapon(wad, grays, target_w=46):
"""Idle shotgun + firing frame (shotgun with muzzle flash composited)."""
w, h, gun = decode_picture(wad, "SHTGA0", grays)
x0, y0, x1, y1 = bbox(gun)
gun = crop(gun, x0, y0, x1, y1)
gw, gh = x1 - x0, y1 - y0
nw = target_w
nh = max(1, round(gh * nw / gw))
g, a = box_resize(gun, nw, nh)
g = normalize(g, a)
# limit height so it doesn't cover too much of the 64px screen: keep the
# top rows (barrel); the grip sticks out of the screen bottom like in Doom
max_h = 28
if nh > max_h:
g = g[:max_h]
a = a[:max_h]
nh = max_h
idle = to_1bit(g, a)
# firing frame: muzzle flash above the barrel
fname = first_present(wad, "SHTFB0", "SHTFA0")
fw, fh, fl = decode_picture(wad, fname, grays)
fx0, fy0, fx1, fy1 = bbox(fl)
fl = crop(fl, fx0, fy0, fx1, fy1)
fsw = max(1, round((fx1 - fx0) * nw / gw))
fsh = max(1, round((fy1 - fy0) * nw / gw))
fg, fa = box_resize(fl, fsw, fsh)
fg = normalize(fg, fa)
# keep total height reasonable: crop the top of the flash if needed
max_total = 38
if nh + fsh > max_total:
cut = nh + fsh - max_total
fg = fg[cut:]
fa = fa[cut:]
fsh -= cut
# find barrel top-center of scaled gun: centroid of top opaque row
top_row = 0
for y in range(nh):
if any(a[y][x] >= 0.5 for x in range(nw)):
top_row = y
break
cols = [x for x in range(nw) if a[top_row][x] >= 0.5]
cx = sum(cols) // len(cols) if cols else nw // 2
fire_h = nh + fsh
FG = [[0.0] * nw for _ in range(fire_h)]
FA = [[0.0] * nw for _ in range(fire_h)]
for y in range(nh):
for x in range(nw):
FG[fsh + y][x] = g[y][x]
FA[fsh + y][x] = a[y][x]
ox = cx - fsw // 2
for y in range(fsh):
for x in range(fsw):
dx = ox + x
if 0 <= dx < nw and fa[y][x] >= 0.5:
FG[y][dx] = fg[y][x]
FA[y][dx] = fa[y][x]
fire = to_1bit(FG, FA, bias=40.0) # flash reads brighter
return emit_page_sprite(*idle), emit_page_sprite(*fire)
TITLE_LETTERS = {
"D": [
"######.",
"##..##.",
"##..###",
"##...##",
"##...##",
"##..###",
"##..##.",
"######.",
],
"O": [
".#####.",
"##...##",
"##...##",
"##...##",
"##...##",
"##...##",
"##...##",
".#####.",
],
"M": [
"##...##",
"###.###",
"#######",
"##.#.##",
"##...##",
"##...##",
"##...##",
"##...##",
],
}
def build_title(wad, grays):
"""Original blocky 'DOOM' pixel title with dithered gradient, 128x64."""
W, H = 128, 64
colour = [[False] * W for _ in range(H)]
scale = 4 # each letter 7x8 -> 28x32
lw, lh = 7 * scale, 8 * scale
gap = 4
total = 4 * lw + 3 * gap
x0 = (W - total) // 2
y0 = (H - lh) // 2
for i, ch in enumerate("DOOM"):
gl = TITLE_LETTERS[ch]
ox = x0 + i * (lw + gap)
for gy in range(8):
for gx in range(7):
if gl[gy][gx] != "#":
continue
for sy in range(scale):
for sx in range(scale):
x = ox + gx * scale + sx
y = y0 + gy * scale + sy
# metallic gradient: solid on top, dithered below
if y - y0 < lh * 5 // 8:
colour[y][x] = True
else:
colour[y][x] = ((x + y) & 1) == 0
return emit_solid_bitmap(colour)
def icon_from_pixmap(rows):
"""8x8 pixmap ('#'=white) -> 8 page bytes, bit0 = top row."""
out = []
for x in range(8):
b = 0
for y in range(8):
if rows[y][x] == "#":
b |= 1 << y
out.append(b)
return out
HEALTH_ICON = [
"..###...",
"..#.#...",
"###.###.",
"#.....#.",
"###.###.",
"..#.#...",
"..###...",
"........",
]
AMMO_ICON = [
".#..#...",
"###.###.",
"###.###.",
"###.###.",
"###.###.",
"###.###.",
"###.###.",
"........",
]
def main():
wad_path = sys.argv[1] if len(sys.argv) > 1 else "../doomgeneric/Doom1.WAD"
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
out_path = (
sys.argv[2]
if len(sys.argv) > 2
else os.path.join(root, "game", "Generated", "DoomSprites.inc.h")
)
wad = Wad(wad_path)
grays = load_palette(wad)
scaled = [] # (symbol, numFrames, words)
def add16(symbol, names, valign, bias=0.0):
scaled.append((symbol, len(names), sprite16(wad, grays, names, valign, bias)))
# enemies (2 walk frames each)
add16("skeletonSpriteData", ["SARGA1", "SARGB1"], "bottom") # pinky demon
add16("mageSpriteData", ["TROOA1", "TROOB1"], "bottom") # imp
add16("batSpriteData", ["SPOSA1", "SPOSB1"], "bottom") # shotgun sergeant
add16("spiderSpriteData", ["POSSA1", "POSSB1"], "bottom") # zombieman
# projectiles
ball = first_present(wad, "BAL1A0")
ball2 = first_present(wad, "BAL1B0", "BAL1A0")
add16("projectileSpriteData", [ball], "center", bias=60.0)
add16("enemyProjectileSpriteData", [ball2], "center", bias=60.0)
# decorations / pickups
torch1 = first_present(wad, "TREDA0", "CANDA0")
torch2 = first_present(wad, "TREDC0", "TREDB0", "CANDA0")
add16("torchSpriteData1", [torch1], "center", bias=40.0)
add16("torchSpriteData2", [torch2], "center", bias=40.0)
add16("urnSpriteData", ["BAR1A0"], "bottom") # barrel
add16("potionSpriteData", ["STIMA0"], "bottom", bias=30.0) # stimpack
add16("chestSpriteData", ["BPAKA0"], "bottom", bias=30.0) # backpack
add16("chestOpenSpriteData", ["CLIPA0"], "bottom", bias=30.0)
add16("scrollSpriteData", ["BON2A0"], "bottom", bias=30.0) # armor helmet
add16("coinsSpriteData", ["BON1A0"], "bottom", bias=30.0) # potion bottle
add16("crownSpriteData", ["ARM1A0"], "bottom", bias=30.0) # green armor
add16("signSpriteData", ["POL5A0"], "bottom", bias=20.0) # skull pile
weapon_idle, weapon_fire = build_weapon(wad, grays)
title = build_title(wad, grays)
health = icon_from_pixmap(HEALTH_ICON)
ammo = icon_from_pixmap(AMMO_ICON)
with open(out_path, "w") as f:
f.write("// Auto-generated by tools/extract_doom_assets.py\n")
f.write("// Derived at build time from the user's local Doom shareware WAD.\n")
f.write("// Do not commit WAD-derived data to public repositories.\n\n")
for symbol, nframes, words in scaled:
f.write("constexpr uint8_t %s_numFrames = %d;\n" % (symbol, nframes))
f.write("extern const uint16_t %s[] PROGMEM =\n{\n\t%s\n};\n" % (symbol, fmt_words(words)))
f.write("extern const uint8_t handSpriteData1[] PROGMEM =\n{\n\t%s\n};\n" % fmt_words(weapon_idle, 24))
f.write("extern const uint8_t handSpriteData2[] PROGMEM =\n{\n\t%s\n};\n" % fmt_words(weapon_fire, 24))
f.write("extern const uint8_t titleBitmapData[] PROGMEM =\n{\n\t%s\n};\n" % fmt_words(title, 24))
f.write("extern const uint8_t heartSpriteData[] PROGMEM =\n{\n%s\n};\n" % fmt_words(health))
f.write("extern const uint8_t manaSpriteData[] PROGMEM =\n{\n%s\n};\n" % fmt_words(ammo))
total = sum(len(w) * 2 for _, _, w in scaled) + len(weapon_idle) + len(weapon_fire) + len(title) + 16
print("Wrote %s (%d bytes of asset data)" % (out_path, total))
if __name__ == "__main__":
main()
Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

+6 -2
View File
@@ -29,12 +29,16 @@ Protocols are split into **AM** and **FM** registries. The active registry is ch
| ------------------------ | ------- | ------- | --------------- | ---------- | ------------------------------ | ------------ | --------------- |
| Chrysler V0 | ✅ | ✅ | PWM | AM650 | Rolling Code | Checksum | 315.00 / 433.92 |
| Fiat V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code (static emu only) | ❌ | 315.00 / 433.92 |
| Fiat V1 | ✅ | | Manchester | AM650 | Rolling Code | CRC8 | 315.00 / 433.92 |
| Fiat V1 | ✅ | | Manchester | AM650 | HITAG2 | XOR8 | 315.00 / 433.92 |
| Fiat V2 | ✅ | ❌ | Manchester | AM650 | Rolling Code | ❌ | 315.00 / 433.92 |
| Ford V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code | ✅ + Checksum | 315.00 / 433.92 |
| Ford V3 | ✅ | ❌ | Manchester | AM650 | Rolling Code | ❌ | 434.25 |
| Honda V1 | ✅ | ✅ | Manchester | AM650 | Rolling Code | CRC4 | 315.00 / 433.92 |
| Kia V1 | ✅ | ✅ | Manchester | AM650 | Rolling Code | CRC4 | 315.00 / 433.92 |
| Mazda V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code | Checksum | 315.00 / 433.92 |
| Porsche Touareg | ✅ | ❌ | PWM | AM650 | Rolling Code | ❌ | 315.00 / 433.92 |
| PSA (Peugeot/Citroen) | ✅ | ✅ | Manchester | AM650 | XTEA/XOR | CRC8 | 315.00 / 433.92 |
| Renault V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code / Replay | Type/IC | 315.00 / 433.92 |
| StarLine | ✅ | ✅ | PWM | AM650 | KeeLoq | ❌ | 315.00 / 433.92 |
| Subaru | ✅ | ✅ | PPM | AM650 | Rolling Code | ❌ | 315.00 / 433.92 |
| VAG (VW/Audi/Seat/Skoda) | ✅ | ✅ | Manchester | AM650 | AUT64/XTEA | ❌ | 434.42 |
@@ -55,7 +59,7 @@ Protocols are split into **AM** and **FM** registries. The active registry is ch
| Kia V5 | ✅ | ✅ | PWM | FM476 | Rolling Code | ✅ | 315.00 / 433.92 |
| Kia V6 | ✅ | ✅ | Manchester | FM476 | AES128 | CRC8 | 315.00 / 433.92 |
| Kia V7 | ✅ | ✅ | Manchester | FM476 | Rolling Code | CRC8 | 315.00 / 433.92 |
| Land Rover V0 | ✅ | ✅ | PWM | F4 | Rolling Code | Check+Tail | 315.00 / 433.92 |
| Honda V2 | ✅ | ✅ | PWM | F4 | Rolling Code | Check+Tail | 315.00 / 433.92 |
| Mazda V0 | ✅ | ✅ | Manchester | FM (F2?) | Rolling Code | Checksum | 315.00 / 433.92 |
| Mitsubishi V0 | ✅ | ❌ | PWM | FM476 | Rolling Code | ❌ | 315.00 / 433.92 |
| PSA (Peugeot/Citroen) | ✅ | ✅ | Manchester | FM (F3?) | XTEA/XOR | CRC8 | 315.00 / 433.92 |
+310 -7
View File
@@ -1,3 +1,35 @@
# --- Main app ---
import os
def ProtoPirateDefineEnabled(name, app_manifest_path=app_manifest_path):
defines_path = os.path.join(os.path.dirname(app_manifest_path), "defines.h")
with open(defines_path) as defines_file:
for line in defines_file:
parts = line.strip().split()
if len(parts) >= 2 and parts[0] == "#define" and parts[1] == name:
return True
return False
_ENABLE_TIMING_TUNER = ProtoPirateDefineEnabled("ENABLE_TIMING_TUNER_SCENE")
_MAIN_APP_SOURCES = [
"*.c*",
"!protocols/plugins",
"!scenes/plugins",
"!protocols",
"protocols/protocol_items.c",
"protocols/protocols_common.c",
"!raw_file_reader.c",
]
if not _ENABLE_TIMING_TUNER:
_MAIN_APP_SOURCES += [
"!protopirate_scene_timing_tuner.c",
"!protocol_timings.c",
]
App(
appid="proto_pirate",
name="ProtoPirate",
@@ -12,39 +44,62 @@ App(
fap_category="Sub-GHz",
fap_icon_assets="images",
fap_file_assets="keystore",
cdefines=["PROTOPIRATE_PROTOCOL_RX_ONLY=1"],
sources=_MAIN_APP_SOURCES,
)
# --- RX protocol plugins ---
App(
appid="protopirate_am_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_am_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_PROTOCOL_RX_ONLY=1"],
sources=[
"protocols/plugins/protopirate_am_plugin.c",
"protocols/protocols_common.c",
"protocols/aut64.c",
"protocols/chrysler_v0.c",
"protocols/fiat_v0.c",
"protocols/fiat_v1.c",
"protocols/fiat_v2.c",
"protocols/ford_v0.c",
"protocols/ford_v3.c",
"protocols/honda_v1.c",
"protocols/kia_v1.c",
"protocols/kia_v2.c",
"protocols/mazda_v0.c",
"protocols/porsche_touareg.c",
"protocols/psa.c",
"protocols/psa_crypto.c",
"protocols/renault_v0.c",
"protocols/subaru.c",
"protocols/vag.c",
"protocols/star_line.c",
],
fal_embedded=True,
)
App(
appid="protopirate_am_vag_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_am_vag_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_PROTOCOL_RX_ONLY=1"],
sources=[
"protocols/plugins/protopirate_am_vag_plugin.c",
"protocols/protocols_common.c",
"protocols/aut64.c",
"protocols/vag.c",
],
fal_embedded=True,
)
App(
appid="protopirate_fm_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_fm_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_PROTOCOL_RX_ONLY=1"],
sources=[
"protocols/plugins/protopirate_fm_plugin.c",
"protocols/protocols_common.c",
@@ -55,11 +110,6 @@ App(
"protocols/kia_v5.c",
"protocols/kia_v6.c",
"protocols/kia_v7.c",
"protocols/ford_v1.c",
"protocols/ford_v2.c",
"protocols/ford_v3.c",
"protocols/honda_static.c",
"protocols/land_rover_v0.c",
"protocols/mazda_v0.c",
"protocols/mitsubishi_v0.c",
"protocols/psa.c",
@@ -68,6 +118,258 @@ App(
fal_embedded=True,
)
App(
appid="protopirate_fm_f4_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_fm_f4_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_PROTOCOL_RX_ONLY=1"],
sources=[
"protocols/plugins/protopirate_fm_f4_plugin.c",
"protocols/protocols_common.c",
"protocols/ford_v1.c",
"protocols/ford_v2.c",
"protocols/ford_v3.c",
"protocols/honda_v2.c",
],
fal_embedded=True,
)
App(
appid="protopirate_fm_honda1_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_fm_honda1_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_PROTOCOL_RX_ONLY=1"],
sources=[
"protocols/plugins/protopirate_fm_honda1_plugin.c",
"protocols/protocols_common.c",
"protocols/honda_static.c",
],
fal_embedded=True,
)
# --- TX protocol plugins ---
_TX_PLUGIN_SOURCES = [
"protocols/plugins/protopirate_tx_protocol_plugin.c",
"protocols/protocols_common.c",
]
def ProtoPirateTxProtocolPlugin(
key,
protocol_header,
protocol_name,
protocol_item,
protocol_sources,
App=App,
FlipperAppType=FlipperAppType,
tx_plugin_sources=_TX_PLUGIN_SOURCES,
):
App(
appid=f"protopirate_tx_{key}_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_tx_protocol_plugin_ep",
requires=["proto_pirate"],
cdefines=[
"PROTOPIRATE_PROTOCOL_TX_ONLY=1",
f"PP_TX_PROTOCOL_HEADER=\\\"../{protocol_header}\\\"",
f"PP_TX_PROTOCOL_NAME={protocol_name}",
f"PP_TX_PROTOCOL_ITEM={protocol_item}",
],
sources=tx_plugin_sources + [f"protocols/{source}" for source in protocol_sources],
fal_embedded=True,
)
ProtoPirateTxProtocolPlugin(
"chrysler_v0",
"chrysler_v0.h",
"CHRYSLER_PROTOCOL_V0_NAME",
"chrysler_protocol_v0",
["chrysler_v0.c"],
)
ProtoPirateTxProtocolPlugin(
"fiat_v0",
"fiat_v0.h",
"FIAT_PROTOCOL_V0_NAME",
"fiat_protocol_v0",
["fiat_v0.c"],
)
ProtoPirateTxProtocolPlugin(
"fiat_v1",
"fiat_v1.h",
"FIAT_V1_PROTOCOL_NAME",
"fiat_v1_protocol",
["fiat_v1.c"],
)
ProtoPirateTxProtocolPlugin(
"ford_v0",
"ford_v0.h",
"FORD_PROTOCOL_V0_NAME",
"ford_protocol_v0",
["ford_v0.c"],
)
ProtoPirateTxProtocolPlugin(
"ford_v1",
"ford_v1.h",
"FORD_PROTOCOL_V1_NAME",
"ford_protocol_v1",
["ford_v1.c"],
)
ProtoPirateTxProtocolPlugin(
"ford_v2",
"ford_v2.h",
"FORD_PROTOCOL_V2_NAME",
"ford_protocol_v2",
["ford_v2.c"],
)
ProtoPirateTxProtocolPlugin(
"honda_static",
"honda_static.h",
"HONDA_STATIC_PROTOCOL_NAME",
"honda_static_protocol",
["honda_static.c"],
)
ProtoPirateTxProtocolPlugin(
"honda_v1",
"honda_v1.h",
"HONDA_V1_PROTOCOL_NAME",
"honda_v1_protocol",
["honda_v1.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v0",
"kia_v0.h",
"KIA_PROTOCOL_V0_NAME",
"kia_protocol_v0",
["kia_v0.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v1",
"kia_v1.h",
"KIA_PROTOCOL_V1_NAME",
"kia_protocol_v1",
["kia_v1.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v2",
"kia_v2.h",
"KIA_PROTOCOL_V2_NAME",
"kia_protocol_v2",
["kia_v2.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v3_v4",
"kia_v3_v4.h",
"KIA_PROTOCOL_V3_V4_NAME",
"kia_protocol_v3_v4",
["keys.c", "kia_v3_v4.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v5",
"kia_v5.h",
"KIA_PROTOCOL_V5_NAME",
"kia_protocol_v5",
["keys.c", "kia_v5.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v6",
"kia_v6.h",
"KIA_PROTOCOL_V6_NAME",
"kia_protocol_v6",
["keys.c", "kia_v6.c"],
)
ProtoPirateTxProtocolPlugin(
"kia_v7",
"kia_v7.h",
"KIA_PROTOCOL_V7_NAME",
"kia_protocol_v7",
["kia_v7.c"],
)
ProtoPirateTxProtocolPlugin(
"honda_v2",
"honda_v2.h",
"HONDA_V2_PROTOCOL_NAME",
"honda_v2_protocol",
["honda_v2.c"],
)
ProtoPirateTxProtocolPlugin(
"mazda_v0",
"mazda_v0.h",
"MAZDA_PROTOCOL_V0_NAME",
"mazda_v0_protocol",
["mazda_v0.c"],
)
ProtoPirateTxProtocolPlugin(
"psa",
"psa.h",
"PSA_PROTOCOL_NAME",
"psa_protocol",
["psa.c", "psa_crypto.c"],
)
ProtoPirateTxProtocolPlugin(
"renault_v0",
"renault_v0.h",
"RENAULT_PROTOCOL_V0_NAME",
"renault_v0_protocol",
["renault_v0.c"],
)
ProtoPirateTxProtocolPlugin(
"star_line",
"star_line.h",
"SUBGHZ_PROTOCOL_STAR_LINE_NAME",
"subghz_protocol_star_line",
["star_line.c"],
)
ProtoPirateTxProtocolPlugin(
"subaru",
"subaru.h",
"SUBARU_PROTOCOL_NAME",
"subaru_protocol",
["subaru.c"],
)
ProtoPirateTxProtocolPlugin(
"vag",
"vag.h",
"VAG_PROTOCOL_NAME",
"vag_protocol",
["aut64.c", "vag.c"],
)
# --- Tool / scene plugins ---
App(
appid="protopirate_sub_decode_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_sub_decode_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_SUB_DECODE_PLUGIN_BUILD=1"],
sources=[
"scenes/protopirate_scene_sub_decode.c",
"helpers/raw_file_reader.c",
"protopirate_history.c",
"helpers/protopirate_storage.c",
"protocols/protocol_items.c",
"protocols/protocols_common.c",
],
fap_icon_assets="images",
fal_embedded=True,
)
if _ENABLE_TIMING_TUNER:
App(
appid="protopirate_timing_tuner_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_timing_tuner_plugin_ep",
requires=["proto_pirate"],
cdefines=["PROTOPIRATE_TIMING_TUNER_PLUGIN_BUILD=1"],
sources=[
"scenes/protopirate_scene_timing_tuner.c",
"protocols/protocol_timings.c",
],
fal_embedded=True,
)
App(
appid="protopirate_emulate_plugin",
apptype=FlipperAppType.PLUGIN,
@@ -75,6 +377,7 @@ App(
requires=["proto_pirate"],
sources=[
"scenes/plugins/protopirate_emulate_plugin.c",
"protocols/protocol_items.c",
],
fal_embedded=True,
)
+14 -3
View File
@@ -1,10 +1,21 @@
#pragma once
//#define ENABLE_TIMING_TUNER_SCENE
//#define ENABLE_SUB_DECODE_SCENE
// #define ENABLE_TIMING_TUNER_SCENE
#define ENABLE_SUB_DECODE_SCENE
#define ENABLE_EMULATE_FEATURE
#if defined(ENABLE_EMULATE_FEATURE) && !defined(PROTOPIRATE_PROTOCOL_RX_ONLY)
#define PROTOPIRATE_WITH_ENCODER 1
#else
#define PROTOPIRATE_WITH_ENCODER 0
#endif
#ifndef PROTOPIRATE_PROTOCOL_TX_ONLY
#define PROTOPIRATE_WITH_DECODER 1
#else
#define PROTOPIRATE_WITH_DECODER 0
#endif
#define REMOVE_LOGS
#ifdef REMOVE_LOGS
@@ -0,0 +1,447 @@
#include "../protopirate_app_i.h"
#include "protopirate_txrx.h"
#include "../protocols/protocol_items.h"
#include <loader/firmware_api/firmware_api.h>
#include <stdio.h>
#include <string.h>
#define TAG "ProtoPirateProtocolPlugin"
#define PROTOPIRATE_TX_PLUGIN_PATH_MAX 160U
static const char* protopirate_get_registry_plugin_path(ProtoPirateProtocolRegistryRoute route) {
switch(route) {
case ProtoPirateProtocolRegistryRouteAMVag:
return APP_ASSETS_PATH("plugins/protopirate_am_vag_plugin.fal");
case ProtoPirateProtocolRegistryRouteFMDefault:
return APP_ASSETS_PATH("plugins/protopirate_fm_plugin.fal");
case ProtoPirateProtocolRegistryRouteFMF4:
return APP_ASSETS_PATH("plugins/protopirate_fm_f4_plugin.fal");
case ProtoPirateProtocolRegistryRouteFMHonda1:
return APP_ASSETS_PATH("plugins/protopirate_fm_honda1_plugin.fal");
case ProtoPirateProtocolRegistryRouteAMDefault:
default:
return APP_ASSETS_PATH("plugins/protopirate_am_plugin.fal");
}
}
static bool protopirate_build_tx_protocol_plugin_path(
const char* tx_key,
char* plugin_path,
size_t plugin_path_size) {
if(!tx_key || !plugin_path || plugin_path_size == 0U) {
return false;
}
int written = snprintf(
plugin_path,
plugin_path_size,
APP_ASSETS_PATH("plugins/protopirate_tx_%s_plugin.fal"),
tx_key);
return (written > 0) && ((size_t)written < plugin_path_size);
}
static const SubGhzProtocolRegistry protopirate_empty_protocol_registry = {
.items = NULL,
.size = 0,
};
void protopirate_unload_protocol_plugin(ProtoPirateTxRx* txrx) {
furi_check(txrx);
if(txrx->environment) {
subghz_environment_set_protocol_registry(
txrx->environment, &protopirate_empty_protocol_registry);
}
txrx->protocol_registry = NULL;
if(txrx->protocol_plugin && txrx->protocol_plugin->release) {
txrx->protocol_plugin->release();
}
txrx->protocol_plugin = NULL;
if(txrx->protocol_plugin_manager) {
plugin_manager_free(txrx->protocol_plugin_manager);
txrx->protocol_plugin_manager = NULL;
}
if(txrx->plugin_resolver) {
composite_api_resolver_free(txrx->plugin_resolver);
txrx->plugin_resolver = NULL;
}
}
static bool protopirate_ensure_protocol_registry_plugin(
ProtoPirateApp* app,
ProtoPirateProtocolRegistryRoute route,
const SubGhzProtocolRegistry** registry) {
furi_check(app);
furi_check(app->txrx);
furi_check(registry);
*registry = NULL;
if(!app->txrx->environment) {
FURI_LOG_E(TAG, "Cannot load protocol plugin without radio environment");
return false;
}
if(app->txrx->protocol_plugin &&
app->txrx->protocol_plugin->kind == ProtoPirateProtocolPluginKindRx &&
app->txrx->protocol_plugin->registry && app->txrx->protocol_registry_route == route) {
*registry = app->txrx->protocol_plugin->registry;
return true;
}
if(app->txrx->protocol_plugin || app->txrx->protocol_plugin_manager ||
app->txrx->plugin_resolver) {
protopirate_unload_protocol_plugin(app->txrx);
}
CompositeApiResolver* resolver = composite_api_resolver_alloc();
if(!resolver) {
FURI_LOG_E(TAG, "Failed to allocate protocol plugin resolver");
return false;
}
composite_api_resolver_add(resolver, firmware_api_interface);
PluginManager* manager = plugin_manager_alloc(
PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
composite_api_resolver_get(resolver));
if(!manager) {
FURI_LOG_E(TAG, "Failed to allocate protocol plugin manager");
composite_api_resolver_free(resolver);
return false;
}
const char* plugin_path = protopirate_get_registry_plugin_path(route);
PluginManagerError error = plugin_manager_load_single(manager, plugin_path);
if(error != PluginManagerErrorNone) {
FURI_LOG_E(TAG, "Failed to load protocol plugin %s: %d", plugin_path, (int)error);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
const ProtoPirateProtocolPlugin* plugin = plugin_manager_get_ep(manager, 0U);
if(!plugin || !plugin->registry) {
FURI_LOG_E(TAG, "Protocol plugin entry point is invalid");
if(plugin && plugin->release) {
plugin->release();
}
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
if(plugin->kind != ProtoPirateProtocolPluginKindRx) {
FURI_LOG_E(TAG, "Protocol plugin kind mismatch for RX route");
if(plugin->release) {
plugin->release();
}
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
if(plugin->route != route) {
FURI_LOG_E(
TAG, "Protocol plugin route mismatch (expected %d got %d)", route, plugin->route);
if(plugin->release) {
plugin->release();
}
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
app->txrx->plugin_resolver = resolver;
app->txrx->protocol_plugin_manager = manager;
app->txrx->protocol_plugin = plugin;
app->txrx->protocol_registry_route = route;
*registry = plugin->registry;
return true;
}
static bool protopirate_ensure_tx_protocol_plugin(
ProtoPirateApp* app,
const char* protocol_name,
const SubGhzProtocolRegistry** registry) {
furi_check(app);
furi_check(app->txrx);
furi_check(registry);
*registry = NULL;
if(!app->txrx->environment) {
FURI_LOG_E(TAG, "Cannot load TX protocol plugin without radio environment");
return false;
}
const ProtoPirateProtocolCatalogEntry* catalog_entry =
protopirate_protocol_catalog_find(protocol_name);
const char* registry_name = protopirate_protocol_catalog_canonical_name(protocol_name);
const char* tx_key = protopirate_protocol_catalog_tx_key(protocol_name);
char plugin_path[PROTOPIRATE_TX_PLUGIN_PATH_MAX];
if(catalog_entry && !tx_key) {
FURI_LOG_W(TAG, "TX disabled for %s: protocol catalog has no tx_key", registry_name);
return false;
}
if(!registry_name || !tx_key ||
!protopirate_build_tx_protocol_plugin_path(tx_key, plugin_path, sizeof(plugin_path))) {
FURI_LOG_E(TAG, "No TX protocol plugin for %s", protocol_name ? protocol_name : "?");
return false;
}
if(app->txrx->protocol_plugin &&
app->txrx->protocol_plugin->kind == ProtoPirateProtocolPluginKindTx &&
app->txrx->protocol_plugin->registry && app->txrx->protocol_plugin->protocol_name &&
strcmp(app->txrx->protocol_plugin->protocol_name, registry_name) == 0) {
*registry = app->txrx->protocol_plugin->registry;
return true;
}
if(app->txrx->protocol_plugin || app->txrx->protocol_plugin_manager ||
app->txrx->plugin_resolver) {
protopirate_unload_protocol_plugin(app->txrx);
}
CompositeApiResolver* resolver = composite_api_resolver_alloc();
if(!resolver) {
FURI_LOG_E(TAG, "Failed to allocate TX protocol plugin resolver");
return false;
}
composite_api_resolver_add(resolver, firmware_api_interface);
PluginManager* manager = plugin_manager_alloc(
PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
composite_api_resolver_get(resolver));
if(!manager) {
FURI_LOG_E(TAG, "Failed to allocate TX protocol plugin manager");
composite_api_resolver_free(resolver);
return false;
}
PluginManagerError error = plugin_manager_load_single(manager, plugin_path);
if(error != PluginManagerErrorNone) {
FURI_LOG_E(TAG, "Failed to load TX protocol plugin %s: %d", plugin_path, (int)error);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
const ProtoPirateProtocolPlugin* plugin = plugin_manager_get_ep(manager, 0U);
if(!plugin || plugin->kind != ProtoPirateProtocolPluginKindTx || !plugin->registry ||
plugin->registry->size == 0U || !plugin->protocol_name ||
strcmp(plugin->protocol_name, registry_name) != 0) {
FURI_LOG_E(TAG, "TX protocol plugin entry point is invalid for %s", registry_name);
if(plugin && plugin->release) {
plugin->release();
}
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
const SubGhzProtocol* tx_protocol = plugin->registry->items[0];
if(!tx_protocol || !tx_protocol->encoder || !tx_protocol->encoder->alloc ||
!tx_protocol->encoder->deserialize || !tx_protocol->encoder->yield) {
FURI_LOG_E(TAG, "TX protocol plugin for %s has no encoder", registry_name);
if(plugin->release) {
plugin->release();
}
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
app->txrx->plugin_resolver = resolver;
app->txrx->protocol_plugin_manager = manager;
app->txrx->protocol_plugin = plugin;
*registry = plugin->registry;
return true;
}
bool protopirate_refresh_protocol_registry(ProtoPirateApp* app, bool ensure_receiver_ready) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->environment || !app->txrx->preset) {
return true;
}
const char* preset_name = furi_string_get_cstr(app->txrx->preset->name);
ProtoPirateProtocolRegistryRoute route = protopirate_get_protocol_registry_route(
preset_name,
app->txrx->preset->frequency,
app->txrx->preset->data,
app->txrx->preset->data_size,
NULL);
bool route_changed = !app->txrx->protocol_plugin ||
(app->txrx->protocol_plugin->kind != ProtoPirateProtocolPluginKindRx) ||
(app->txrx->protocol_registry_route != route);
if(route_changed) {
protopirate_rx_stack_teardown_for_registry_switch(app);
} else if(ensure_receiver_ready && !app->txrx->receiver) {
protopirate_rx_stack_teardown_for_registry_switch(app);
}
const SubGhzProtocolRegistry* registry = NULL;
if(!protopirate_ensure_protocol_registry_plugin(app, route, &registry) || !registry) {
FURI_LOG_E(
TAG,
"Failed to resolve %s protocol registry plugin",
protopirate_get_protocol_registry_route_name(route));
return false;
}
const bool registry_already_bound = (app->txrx->protocol_registry == registry);
if(!registry_already_bound) {
FURI_LOG_I(
TAG,
"Using %s protocol registry (%zu protocols)",
protopirate_get_protocol_registry_route_name(route),
registry->size);
subghz_environment_set_protocol_registry(app->txrx->environment, registry);
app->txrx->protocol_registry = registry;
}
if(!ensure_receiver_ready) {
return true;
}
if(app->txrx->receiver) {
return true;
}
app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment);
if(!app->txrx->receiver) {
FURI_LOG_E(
TAG,
"Failed to allocate receiver for %s registry",
protopirate_get_protocol_registry_route_name(route));
return false;
}
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
return true;
}
static bool protopirate_ensure_receiver_allocated(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
if(app->txrx->receiver) {
return true;
}
if(!app->txrx->environment) {
return false;
}
app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment);
if(!app->txrx->receiver) {
FURI_LOG_E(TAG, "Failed to allocate receiver after registry restore");
return false;
}
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
return true;
}
bool protopirate_apply_protocol_registry_for_context(
ProtoPirateApp* app,
const char* preset_name,
uint32_t frequency,
const uint8_t* preset_data,
size_t preset_data_size,
const char* protocol_name) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->environment) {
return false;
}
if(!preset_name && app->txrx->preset && app->txrx->preset->name) {
preset_name = furi_string_get_cstr(app->txrx->preset->name);
}
if(frequency == 0U && app->txrx->preset) {
frequency = app->txrx->preset->frequency;
}
if((!preset_data || preset_data_size == 0U) && app->txrx->preset) {
preset_data = app->txrx->preset->data;
preset_data_size = app->txrx->preset->data_size;
}
if(protocol_name && protocol_name[0] != '\0') {
const char* registry_name = protopirate_protocol_catalog_canonical_name(protocol_name);
if(!registry_name || !protopirate_protocol_catalog_can_tx(protocol_name)) {
FURI_LOG_E(TAG, "No TX protocol plugin for %s", protocol_name);
return false;
}
bool tx_changed = !app->txrx->protocol_plugin ||
(app->txrx->protocol_plugin->kind != ProtoPirateProtocolPluginKindTx) ||
!app->txrx->protocol_plugin->protocol_name ||
strcmp(app->txrx->protocol_plugin->protocol_name, registry_name) != 0;
if(tx_changed) {
protopirate_rx_stack_teardown_for_registry_switch(app);
}
const SubGhzProtocolRegistry* tx_registry = NULL;
if(!protopirate_ensure_tx_protocol_plugin(app, registry_name, &tx_registry) ||
!tx_registry) {
FURI_LOG_E(TAG, "Failed to resolve TX protocol plugin for %s", protocol_name);
return false;
}
if(app->txrx->protocol_registry == tx_registry) {
return true;
}
FURI_LOG_I(
TAG,
"Switching active protocol registry to TX %s",
registry_name ? registry_name : "?");
subghz_environment_set_protocol_registry(app->txrx->environment, tx_registry);
app->txrx->protocol_registry = tx_registry;
return true;
}
ProtoPirateProtocolRegistryRoute route = protopirate_get_protocol_registry_route(
preset_name, frequency, preset_data, preset_data_size, NULL);
bool route_changed = !app->txrx->protocol_plugin ||
(app->txrx->protocol_plugin->kind != ProtoPirateProtocolPluginKindRx) ||
(app->txrx->protocol_registry_route != route);
if(route_changed) {
protopirate_rx_stack_teardown_for_registry_switch(app);
}
const SubGhzProtocolRegistry* registry = NULL;
if(!protopirate_ensure_protocol_registry_plugin(app, route, &registry) || !registry) {
FURI_LOG_E(
TAG,
"Failed to resolve %s registry plugin for preset apply",
protopirate_get_protocol_registry_route_name(route));
return false;
}
if(app->txrx->protocol_registry == registry) {
return protopirate_ensure_receiver_allocated(app);
}
FURI_LOG_I(
TAG,
"Switching active protocol registry to %s (%zu protocols)",
protopirate_get_protocol_registry_route_name(route),
registry->size);
subghz_environment_set_protocol_registry(app->txrx->environment, registry);
app->txrx->protocol_registry = registry;
return protopirate_ensure_receiver_allocated(app);
}
@@ -0,0 +1,18 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct ProtoPirateApp ProtoPirateApp;
typedef struct ProtoPirateTxRx ProtoPirateTxRx;
void protopirate_unload_protocol_plugin(ProtoPirateTxRx* txrx);
bool protopirate_refresh_protocol_registry(ProtoPirateApp* app, bool ensure_receiver_ready);
bool protopirate_apply_protocol_registry_for_context(
ProtoPirateApp* app,
const char* preset_name,
uint32_t frequency,
const uint8_t* preset_data,
size_t preset_data_size,
const char* protocol_name);
@@ -80,15 +80,9 @@ static void host_receiver_info_rebuild_widget(void* app) {
protopirate_receiver_info_rebuild_normal_widget(app);
}
#ifdef ENABLE_SUB_DECODE_SCENE
static void host_subdecode_signal_info_refresh(void* app) {
protopirate_subdecode_psa_bf_complete_refresh(app);
host_send_custom_event(app, ProtoPirateCustomEventSubDecodeUpdate);
}
#else
static void host_subdecode_signal_info_refresh(void* app) {
UNUSED(app);
}
#endif
static void host_scene_previous(void* app) {
ProtoPirateApp* a = (ProtoPirateApp*)app;
@@ -0,0 +1,269 @@
#include "protopirate_radio.h"
#include "../protopirate_app_i.h"
#include <furi.h>
#include <string.h>
#define TAG "ProtoPirateRadio"
static void protopirate_radio_free_receiver(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->receiver) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Receiver was NULL, skipping free");
#endif
return;
}
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Freeing receiver %p", app->txrx->receiver);
#endif
subghz_receiver_free(app->txrx->receiver);
app->txrx->receiver = NULL;
}
static void protopirate_radio_free_environment(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->environment) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Environment was NULL, skipping free");
#endif
return;
}
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Freeing environment %p", app->txrx->environment);
#endif
subghz_environment_free(app->txrx->environment);
app->txrx->environment = NULL;
app->txrx->protocol_registry = NULL;
}
static void protopirate_radio_end_device(ProtoPirateApp* app, bool sleep_before_end) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->radio_device) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Radio device was NULL, skipping sleep/end");
#endif
return;
}
#ifndef REMOVE_LOGS
FURI_LOG_D(
TAG,
"Putting radio device to %s and ending: %p",
sleep_before_end ? "sleep" : "idle",
app->txrx->radio_device);
#endif
if(sleep_before_end) {
subghz_devices_sleep(app->txrx->radio_device);
} else {
subghz_devices_idle(app->txrx->radio_device);
}
radio_device_loader_end(app->txrx->radio_device);
app->txrx->radio_device = NULL;
}
static void protopirate_radio_reset_state(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
app->txrx->protocol_registry = NULL;
app->txrx->protocol_plugin = NULL;
app->txrx->protocol_registry_route = ProtoPirateProtocolRegistryRouteAMDefault;
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->radio_initialized = false;
}
static void protopirate_radio_init_cleanup(ProtoPirateApp* app, bool devices_initialized) {
furi_check(app);
furi_check(app->txrx);
protopirate_radio_free_receiver(app);
protopirate_radio_end_device(app, false);
protopirate_radio_free_environment(app);
protopirate_unload_protocol_plugin(app->txrx);
if(devices_initialized) {
subghz_devices_deinit();
}
protopirate_radio_reset_state(app);
}
bool protopirate_radio_init(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
FURI_LOG_I(TAG, "=== protopirate_radio_init called ===");
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "State: radio_initialized=%d", app->radio_initialized);
#endif
if(app->radio_initialized) {
const bool radio_ready = (app->txrx->environment != NULL) &&
(app->txrx->radio_device != NULL);
if(radio_ready) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Radio already initialized, returning true");
#endif
return true;
}
FURI_LOG_W(
TAG,
"Radio marked initialized but resources missing (env=%p device=%p), repairing",
app->txrx->environment,
app->txrx->radio_device);
protopirate_radio_deinit(app);
}
FURI_LOG_I(TAG, "Fresh radio init - allocating all components");
app->txrx->environment = subghz_environment_alloc();
if(!app->txrx->environment) {
FURI_LOG_E(TAG, "Failed to allocate environment!");
protopirate_radio_init_cleanup(app, false);
return false;
}
app->txrx->protocol_registry = NULL;
if(!protopirate_refresh_protocol_registry(app, false)) {
FURI_LOG_E(TAG, "Failed to configure protocol registry");
protopirate_radio_init_cleanup(app, false);
return false;
}
subghz_environment_load_keystore(app->txrx->environment, PROTOPIRATE_KEYSTORE_DIR_NAME);
subghz_devices_init();
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "SubGhz devices initialized");
#endif
app->txrx->radio_device = radio_device_loader_set(NULL, SubGhzRadioDeviceTypeExternalCC1101);
if(!app->txrx->radio_device) {
FURI_LOG_W(TAG, "External CC1101 not found, trying internal radio");
app->txrx->radio_device = radio_device_loader_set(NULL, SubGhzRadioDeviceTypeInternal);
}
if(!app->txrx->radio_device) {
FURI_LOG_E(TAG, "Failed to initialize any radio device!");
protopirate_radio_init_cleanup(app, true);
return false;
}
#ifndef REMOVE_LOGS
const char* device_name = subghz_devices_get_name(app->txrx->radio_device);
bool is_external = device_name && strstr(device_name, "ext");
FURI_LOG_I(
TAG,
"Radio device initialized: %s (%s)",
device_name ? device_name : "unknown",
is_external ? "external" : "internal");
#endif
subghz_devices_reset(app->txrx->radio_device);
subghz_devices_idle(app->txrx->radio_device);
app->radio_initialized = true;
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Final state: radio_initialized=%d", app->radio_initialized);
#endif
return true;
}
void protopirate_radio_deinit(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
FURI_LOG_I(TAG, "=== protopirate_radio_deinit called ===");
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "State: radio_initialized=%d", app->radio_initialized);
FURI_LOG_D(
TAG,
"Pointers: worker=%p, environment=%p, receiver=%p, history=%p, radio_device=%p",
app->txrx->worker,
app->txrx->environment,
app->txrx->receiver,
app->txrx->history,
app->txrx->radio_device);
#endif
bool has_radio_resources = app->radio_initialized || app->txrx->worker ||
app->txrx->environment || app->txrx->receiver ||
app->txrx->history || app->txrx->radio_device ||
app->txrx->protocol_plugin_manager ||
app->txrx->plugin_resolver || app->txrx->protocol_plugin;
if(!has_radio_resources) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Radio resources were not initialized, returning");
#endif
return;
}
bool devices_initialized = app->radio_initialized || (app->txrx->radio_device != NULL);
if(app->txrx->worker && app->txrx->txrx_state == ProtoPirateTxRxStateRx) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Stopping active RX, state=%d", app->txrx->txrx_state);
#endif
subghz_worker_stop(app->txrx->worker);
if(app->txrx->radio_device) {
subghz_devices_stop_async_rx(app->txrx->radio_device);
}
}
protopirate_radio_end_device(app, true);
if(devices_initialized) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Calling subghz_devices_deinit");
#endif
subghz_devices_deinit();
}
protopirate_radio_free_receiver(app);
protopirate_radio_free_environment(app);
protopirate_unload_protocol_plugin(app->txrx);
if(app->txrx->history) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Freeing history %p", app->txrx->history);
#endif
protopirate_history_free(app->txrx->history);
app->txrx->history = NULL;
} else {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "History was NULL, skipping free");
#endif
}
if(app->txrx->worker) {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Freeing worker %p", app->txrx->worker);
#endif
subghz_worker_free(app->txrx->worker);
app->txrx->worker = NULL;
} else {
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Worker was NULL, skipping free");
#endif
}
protopirate_radio_reset_state(app);
#ifndef REMOVE_LOGS
FURI_LOG_D(TAG, "Final state: radio_initialized=%d", app->radio_initialized);
#endif
}
@@ -0,0 +1,8 @@
#pragma once
#include <stdbool.h>
typedef struct ProtoPirateApp ProtoPirateApp;
bool protopirate_radio_init(ProtoPirateApp* app);
void protopirate_radio_deinit(ProtoPirateApp* app);
@@ -0,0 +1,166 @@
#include "protopirate_saved_match.h"
#include "protopirate_storage.h"
#include "../protocols/protocols_common.h"
#include <storage/storage.h>
#include <string.h>
#define TAG "ProtoPirateMatch"
#define CNT_MATCH_MARGIN 50
bool protopirate_saved_match_signal(
FlipperFormat* received_ff,
FuriString* out_matched_name,
FuriString* out_matched_path) {
furi_check(received_ff);
furi_check(out_matched_name);
furi_check(out_matched_path);
FuriString* rx_protocol = furi_string_alloc();
FuriString* rx_key = furi_string_alloc();
uint32_t rx_serial = 0;
uint32_t rx_cnt = 0;
bool rx_has_serial = false;
bool rx_has_key = false;
bool rx_has_cnt = false;
flipper_format_rewind(received_ff);
if(!flipper_format_read_string(received_ff, FF_PROTOCOL, rx_protocol)) {
furi_string_free(rx_protocol);
furi_string_free(rx_key);
return false;
}
flipper_format_rewind(received_ff);
if(flipper_format_read_uint32(received_ff, FF_SERIAL, &rx_serial, 1)) {
rx_has_serial = true;
}
if(!rx_has_serial) {
flipper_format_rewind(received_ff);
if(flipper_format_read_string(received_ff, FF_KEY, rx_key)) {
rx_has_key = true;
}
}
flipper_format_rewind(received_ff);
if(flipper_format_read_uint32(received_ff, FF_CNT, &rx_cnt, 1)) {
rx_has_cnt = true;
}
if(!rx_has_serial && !rx_has_key) {
FURI_LOG_D(TAG, "No Serial or Key in received signal, skip match");
furi_string_free(rx_protocol);
furi_string_free(rx_key);
return false;
}
Storage* storage = furi_record_open(RECORD_STORAGE);
File* dir = storage_file_alloc(storage);
bool found = false;
if(!storage_dir_open(dir, PROTOPIRATE_APP_FOLDER)) {
FURI_LOG_D(TAG, "Cannot open saved/ folder");
storage_file_free(dir);
furi_record_close(RECORD_STORAGE);
furi_string_free(rx_protocol);
furi_string_free(rx_key);
return false;
}
FileInfo file_info;
char file_name[128];
while(storage_dir_read(dir, &file_info, file_name, sizeof(file_name))) {
if(file_info.flags & FSF_DIRECTORY) continue;
size_t name_len = strlen(file_name);
if(name_len < 5) continue;
if(strcmp(file_name + name_len - 4, PROTOPIRATE_APP_EXTENSION) != 0) continue;
if(strcmp(file_name, ".temp.psf") == 0) continue;
FuriString* saved_path =
furi_string_alloc_printf("%s/%s", PROTOPIRATE_APP_FOLDER, file_name);
FlipperFormat* saved_ff = flipper_format_file_alloc(storage);
if(!flipper_format_file_open_existing(saved_ff, furi_string_get_cstr(saved_path))) {
flipper_format_free(saved_ff);
furi_string_free(saved_path);
continue;
}
FuriString* saved_protocol = furi_string_alloc();
flipper_format_rewind(saved_ff);
bool protocol_match = flipper_format_read_string(saved_ff, FF_PROTOCOL, saved_protocol) &&
furi_string_cmp(rx_protocol, saved_protocol) == 0;
furi_string_free(saved_protocol);
if(!protocol_match) {
flipper_format_free(saved_ff);
furi_string_free(saved_path);
continue;
}
bool identity_match = false;
if(rx_has_serial) {
uint32_t saved_serial = 0;
flipper_format_rewind(saved_ff);
if(flipper_format_read_uint32(saved_ff, FF_SERIAL, &saved_serial, 1)) {
identity_match = (saved_serial == rx_serial);
}
} else {
FuriString* saved_key = furi_string_alloc();
flipper_format_rewind(saved_ff);
if(flipper_format_read_string(saved_ff, FF_KEY, saved_key)) {
identity_match = (furi_string_cmp(rx_key, saved_key) == 0);
}
furi_string_free(saved_key);
}
if(!identity_match) {
flipper_format_free(saved_ff);
furi_string_free(saved_path);
continue;
}
if(rx_has_cnt) {
uint32_t saved_cnt = 0;
flipper_format_rewind(saved_ff);
if(flipper_format_read_uint32(saved_ff, FF_CNT, &saved_cnt, 1)) {
int64_t diff = (int64_t)rx_cnt - (int64_t)saved_cnt;
if(diff < 0) diff = -diff;
if(diff > CNT_MATCH_MARGIN) {
FURI_LOG_D(
TAG,
"Cnt diff %lld > %d, skip %s",
(long long)diff,
CNT_MATCH_MARGIN,
file_name);
flipper_format_free(saved_ff);
furi_string_free(saved_path);
continue;
}
}
}
size_t ext_pos = name_len - 4;
furi_string_set_strn(out_matched_name, file_name, ext_pos);
furi_string_set(out_matched_path, saved_path);
found = true;
flipper_format_free(saved_ff);
furi_string_free(saved_path);
break;
}
storage_dir_close(dir);
storage_file_free(dir);
furi_record_close(RECORD_STORAGE);
furi_string_free(rx_protocol);
furi_string_free(rx_key);
return found;
}
@@ -0,0 +1,9 @@
#pragma once
#include <furi.h>
#include <flipper_format/flipper_format.h>
bool protopirate_saved_match_signal(
FlipperFormat* received_ff,
FuriString* out_matched_name,
FuriString* out_matched_path);
@@ -1,5 +1,6 @@
// helpers/protopirate_settings.c
#include "protopirate_settings.h"
#include "protopirate_storage.h"
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#include <furi.h>
@@ -18,6 +19,7 @@ void protopirate_settings_set_defaults(ProtoPirateSettings* settings) {
settings->auto_save = false;
settings->hopping_enabled = false;
settings->emulate_feature_enabled = false;
settings->check_saved = false;
}
void protopirate_settings_load(ProtoPirateSettings* settings) {
@@ -42,12 +44,6 @@ void protopirate_settings_load(ProtoPirateSettings* settings) {
break;
}
if(version != SETTINGS_FILE_VERSION) {
FURI_LOG_W(TAG, "Unsupported settings version %lu", (unsigned long)version);
furi_string_free(header);
break;
}
if(furi_string_cmp_str(header, SETTINGS_FILE_HEADER) != 0) {
FURI_LOG_W(TAG, "Invalid settings file header");
furi_string_free(header);
@@ -56,6 +52,14 @@ void protopirate_settings_load(ProtoPirateSettings* settings) {
furi_string_free(header);
if(version != SETTINGS_FILE_VERSION) {
FURI_LOG_I(
TAG,
"Migrating settings from version %lu to %u",
(unsigned long)version,
SETTINGS_FILE_VERSION);
}
// Read frequency
if(!flipper_format_read_uint32(ff, FF_FREQUENCY, &settings->frequency, 1)) {
FURI_LOG_W(TAG, "Failed to read frequency, using default");
@@ -84,6 +88,11 @@ void protopirate_settings_load(ProtoPirateSettings* settings) {
FURI_LOG_W(TAG, "Failed to read TXPower, using default");
tx_power_temp = 0;
}
if(tx_power_temp > PROTOPIRATE_TX_POWER_MAX_INDEX) {
FURI_LOG_W(TAG, "TXPower %lu out of range, clamping", (unsigned long)tx_power_temp);
tx_power_temp = PROTOPIRATE_TX_POWER_MAX_INDEX;
}
settings->tx_power = (uint8_t)tx_power_temp;
// Read hopping
@@ -101,14 +110,21 @@ void protopirate_settings_load(ProtoPirateSettings* settings) {
}
settings->emulate_feature_enabled = (emulate_temp == 1);
uint32_t check_saved_temp = 0;
if(!flipper_format_read_uint32(ff, "CheckSaved", &check_saved_temp, 1)) {
check_saved_temp = 0;
}
settings->check_saved = (check_saved_temp == 1);
FURI_LOG_I(
TAG,
"Settings loaded: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d",
"Settings loaded: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d, check_saved=%d",
settings->frequency,
settings->preset_index,
settings->auto_save,
settings->hopping_enabled,
settings->emulate_feature_enabled);
settings->emulate_feature_enabled,
settings->check_saved);
} while(false);
@@ -120,12 +136,17 @@ void protopirate_settings_save(ProtoPirateSettings* settings) {
Storage* storage = furi_record_open(RECORD_STORAGE);
// Ensure directory exists
storage_simply_mkdir(storage, PROTOPIRATE_SETTINGS_DIR);
if(!storage_simply_mkdir(storage, PROTOPIRATE_SETTINGS_DIR)) {
FURI_LOG_W(TAG, "Settings directory could not be created");
}
FlipperFormat* ff = flipper_format_file_alloc(storage);
bool write_ok = false;
const char* tmp_path = PROTOPIRATE_SETTINGS_FILE ".tmp";
do {
if(!flipper_format_file_open_always(ff, PROTOPIRATE_SETTINGS_FILE)) {
if(!flipper_format_file_open_always(ff, tmp_path)) {
FURI_LOG_E(TAG, "Failed to open settings file for writing");
break;
}
@@ -170,17 +191,35 @@ void protopirate_settings_save(ProtoPirateSettings* settings) {
break;
}
uint32_t check_saved_temp = settings->check_saved ? 1 : 0;
if(!flipper_format_write_uint32(ff, "CheckSaved", &check_saved_temp, 1)) {
FURI_LOG_E(TAG, "Failed to write check saved");
break;
}
write_ok = true;
FURI_LOG_I(
TAG,
"Settings saved: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d",
"Settings saved: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d, check_saved=%d",
settings->frequency,
settings->preset_index,
settings->auto_save,
settings->hopping_enabled,
settings->emulate_feature_enabled);
settings->emulate_feature_enabled,
settings->check_saved);
} while(false);
flipper_format_free(ff);
if(write_ok) {
if(!protopirate_storage_commit_temp_file(storage, tmp_path, PROTOPIRATE_SETTINGS_FILE)) {
FURI_LOG_E(TAG, "Failed to commit settings file");
}
} else if(storage_file_exists(storage, tmp_path)) {
storage_simply_remove(storage, tmp_path);
}
furi_record_close(RECORD_STORAGE);
}
@@ -7,6 +7,8 @@
#define PROTOPIRATE_SETTINGS_FILE APP_DATA_PATH("settings.txt")
#define PROTOPIRATE_SETTINGS_DIR APP_DATA_PATH()
#define PROTOPIRATE_TX_POWER_MAX_INDEX 8U
typedef struct {
uint32_t frequency;
uint8_t preset_index;
@@ -14,6 +16,7 @@ typedef struct {
bool auto_save;
bool hopping_enabled;
bool emulate_feature_enabled;
bool check_saved;
} ProtoPirateSettings;
void protopirate_settings_load(ProtoPirateSettings* settings);
@@ -1,7 +1,11 @@
// helpers/protopirate_storage.c
#include "protopirate_storage.h"
#include "../defines.h"
#include "../protocols/protocol_items.h"
#include "../protocols/protocols_common.h"
#include <lib/flipper_format/flipper_format_i.h>
#include <toolbox/stream/stream.h>
#include <string.h>
#define TAG "ProtoPirateStorage"
@@ -12,21 +16,67 @@ bool protopirate_storage_init(void) {
return result;
}
void protopirate_storage_wipe_history_cache(void) {
bool protopirate_storage_commit_temp_file(
Storage* storage,
const char* tmp_path,
const char* final_path) {
furi_check(storage);
furi_check(tmp_path);
furi_check(final_path);
FuriString* backup_path = furi_string_alloc();
furi_string_printf(backup_path, "%s.bak", final_path);
const char* backup_cstr = furi_string_get_cstr(backup_path);
if(storage_file_exists(storage, backup_cstr)) {
storage_simply_remove(storage, backup_cstr);
}
if(storage_file_exists(storage, final_path)) {
if(storage_common_rename(storage, final_path, backup_cstr) != FSE_OK) {
FURI_LOG_E(TAG, "Failed to stage backup for %s", final_path);
furi_string_free(backup_path);
return false;
}
}
if(storage_common_rename(storage, tmp_path, final_path) != FSE_OK) {
FURI_LOG_E(TAG, "Failed to commit %s", final_path);
if(storage_file_exists(storage, backup_cstr)) {
if(storage_common_rename(storage, backup_cstr, final_path) != FSE_OK) {
FURI_LOG_E(TAG, "Failed to restore backup for %s", final_path);
}
}
if(storage_file_exists(storage, tmp_path)) {
storage_simply_remove(storage, tmp_path);
}
furi_string_free(backup_path);
return false;
}
if(storage_file_exists(storage, backup_cstr)) {
storage_simply_remove(storage, backup_cstr);
}
furi_string_free(backup_path);
return true;
}
static void protopirate_storage_remove_history_folder(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_dir_exists(storage, PROTOPIRATE_HISTORY_FOLDER)) {
storage_simply_remove_recursive(storage, PROTOPIRATE_HISTORY_FOLDER);
FURI_LOG_I(TAG, "Wiped history cache");
}
furi_record_close(RECORD_STORAGE);
}
void protopirate_storage_wipe_history_cache(void) {
protopirate_storage_remove_history_folder();
FURI_LOG_I(TAG, "Wiped history cache");
}
void protopirate_storage_purge_temp_history_at_startup(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_dir_exists(storage, PROTOPIRATE_HISTORY_FOLDER)) {
storage_simply_remove_recursive(storage, PROTOPIRATE_HISTORY_FOLDER);
}
furi_record_close(RECORD_STORAGE);
protopirate_storage_remove_history_folder();
}
bool protopirate_storage_ensure_history_folder(void) {
@@ -34,8 +84,8 @@ bool protopirate_storage_ensure_history_folder(void) {
return false;
}
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_simply_mkdir(storage, PROTOPIRATE_CACHE_FOLDER);
bool ok = storage_simply_mkdir(storage, PROTOPIRATE_HISTORY_FOLDER);
bool ok = storage_simply_mkdir(storage, PROTOPIRATE_CACHE_FOLDER) &&
storage_simply_mkdir(storage, PROTOPIRATE_HISTORY_FOLDER);
furi_record_close(RECORD_STORAGE);
return ok;
}
@@ -122,6 +172,33 @@ bool protopirate_storage_get_next_filename(const char* protocol_name, FuriString
return found;
}
bool protopirate_storage_get_capture_display_protocol(
FlipperFormat* flipper_format,
FuriString* protocol_name) {
furi_check(flipper_format);
furi_check(protocol_name);
FuriString* raw_protocol = furi_string_alloc();
bool have_protocol = false;
uint32_t protocol_type = 0U;
flipper_format_rewind(flipper_format);
have_protocol = flipper_format_read_string(flipper_format, FF_PROTOCOL, raw_protocol);
if(!have_protocol) {
furi_string_set(raw_protocol, "Unknown");
}
flipper_format_rewind(flipper_format);
flipper_format_read_uint32(flipper_format, FF_TYPE, &protocol_type, 1);
const char* display_name = protopirate_protocol_catalog_display_name(
furi_string_get_cstr(raw_protocol), protocol_type);
furi_string_set(protocol_name, display_name ? display_name : "Unknown");
furi_string_free(raw_protocol);
return have_protocol;
}
static const char* const protopirate_storage_base_u32_fields[] = {
"TE",
FF_SERIAL,
@@ -141,6 +218,7 @@ static const char* const protopirate_storage_tail_u32_fields[] = {
"DataHi",
"DataLo",
"RawCnt",
"Rolling",
"Encrypted",
"Decrypted",
"KIAVersion",
@@ -161,6 +239,89 @@ static bool
return flipper_format_get_value_count(flipper_format, key, count) && (*count > 0);
}
static bool protopirate_storage_stream_read_char(Stream* stream, char* out) {
uint8_t value = 0;
if(stream_read(stream, &value, 1U) != 1U) return false;
*out = (char)value;
return true;
}
static bool protopirate_storage_stream_write_char(Stream* stream, char value) {
return stream_write_char(stream, value) == 1U;
}
static bool protopirate_storage_copy_raw_value_line(
Stream* out_stream,
Stream* in_stream,
const char* key) {
const size_t key_len = strlen(key);
if(!key_len || !stream_rewind(in_stream) || !stream_seek(out_stream, 0, StreamOffsetFromEnd)) {
return protopirate_storage_fail("Stream", key);
}
bool copied = false;
while(!stream_eof(in_stream)) {
bool line_match = true;
bool line_ended = false;
for(size_t i = 0; i < key_len; i++) {
char c = '\0';
if(!protopirate_storage_stream_read_char(in_stream, &c)) {
return protopirate_storage_fail("Read", key);
}
if(c == '\n') {
line_match = false;
line_ended = true;
break;
}
if(c != key[i]) {
line_match = false;
}
}
if(line_ended) continue;
char c = '\0';
if(!protopirate_storage_stream_read_char(in_stream, &c)) {
return protopirate_storage_fail("Read", key);
}
if(c != ':') {
line_match = false;
}
if(line_match) {
if(stream_write(out_stream, (const uint8_t*)key, key_len) != key_len ||
!protopirate_storage_stream_write_char(out_stream, ':')) {
return protopirate_storage_fail("Write", key);
}
bool wrote_newline = false;
while(protopirate_storage_stream_read_char(in_stream, &c)) {
if(!protopirate_storage_stream_write_char(out_stream, c)) {
return protopirate_storage_fail("Write", key);
}
if(c == '\n') {
wrote_newline = true;
break;
}
}
if(!wrote_newline && !protopirate_storage_stream_write_char(out_stream, '\n')) {
return protopirate_storage_fail("Write", key);
}
copied = true;
continue;
}
while(c != '\n' && protopirate_storage_stream_read_char(in_stream, &c)) {
}
}
return copied ? true : protopirate_storage_fail("Read", key);
}
static bool protopirate_storage_copy_string_optional(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
@@ -253,29 +414,19 @@ static bool protopirate_storage_copy_u32_array(
const char* key,
uint32_t count,
uint32_t max_count) {
if(count >= max_count) {
if(count > max_count) {
FURI_LOG_E(TAG, "%s too large: %lu", key, (unsigned long)count);
return false;
}
uint32_t* data = malloc(sizeof(uint32_t) * count);
if(!data) {
FURI_LOG_E(TAG, "Malloc failed: %s (%lu u32)", key, (unsigned long)count);
Stream* in_stream = flipper_format_get_raw_stream(flipper_format);
Stream* out_stream = flipper_format_get_raw_stream(save_file);
if(!in_stream || !out_stream) {
FURI_LOG_E(TAG, "Raw stream missing: %s", key);
return false;
}
bool status = false;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, key, data, count)) {
protopirate_storage_fail("Read", key);
} else if(!flipper_format_write_uint32(save_file, key, data, count)) {
protopirate_storage_fail("Write", key);
} else {
status = true;
}
free(data);
return status;
return protopirate_storage_copy_raw_value_line(out_stream, in_stream, key);
}
static bool protopirate_storage_copy_u32_array_if_present(
@@ -299,29 +450,19 @@ static bool protopirate_storage_copy_hex_array_if_present(
if(!protopirate_storage_get_count(flipper_format, key, &count)) {
return true;
}
if(count >= max_count) {
if(count > max_count) {
FURI_LOG_E(TAG, "%s too large: %lu", key, (unsigned long)count);
return false;
}
uint8_t* data = malloc(count);
if(!data) {
FURI_LOG_E(TAG, "Malloc failed: %s (%lu bytes)", key, (unsigned long)count);
Stream* in_stream = flipper_format_get_raw_stream(flipper_format);
Stream* out_stream = flipper_format_get_raw_stream(save_file);
if(!in_stream || !out_stream) {
FURI_LOG_E(TAG, "Raw stream missing: %s", key);
return false;
}
bool status = false;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_hex(flipper_format, key, data, count)) {
protopirate_storage_fail("Read", key);
} else if(!flipper_format_write_hex(save_file, key, data, count)) {
protopirate_storage_fail("Write", key);
} else {
status = true;
}
free(data);
return status;
return protopirate_storage_copy_raw_value_line(out_stream, in_stream, key);
}
static bool protopirate_storage_copy_key(
@@ -407,7 +548,7 @@ static bool protopirate_storage_write_capture_data(
protopirate_storage_base_u32_fields,
COUNT_OF(protopirate_storage_base_u32_fields)))
break;
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Key2", 8, NULL)) break;
if(!protopirate_storage_copy_hex_or_u32(save_file, flipper_format, "Key2", 4)) break;
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "KeyIdx")) break;
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Seed")) break;
if(!protopirate_storage_copy_hex_or_u32(save_file, flipper_format, "ValidationField", 2))
@@ -418,6 +559,9 @@ static bool protopirate_storage_write_capture_data(
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Fx")) break;
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Key1", 8, NULL)) break;
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Check")) break;
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Hitag2 Key", 6, NULL))
break;
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Hitag2 Epoch")) break;
if(!protopirate_storage_copy_u32_array_if_present(
save_file, flipper_format, "RAW_Data", 4096))
break;
@@ -438,6 +582,73 @@ static bool protopirate_storage_write_capture_data(
return status;
}
static bool protopirate_storage_write_capture_file(
Storage* storage,
FlipperFormat* flipper_format,
const char* path) {
FlipperFormat* save_file = flipper_format_file_alloc(storage);
bool ok = false;
do {
if(!flipper_format_file_open_new(save_file, path)) {
FURI_LOG_E(TAG, "Failed to create file: %s", path);
break;
}
if(!flipper_format_write_header_cstr(
save_file, "Flipper SubGhz Key File", PROTOPIRATE_APP_FILE_VERSION)) {
FURI_LOG_E(TAG, "Failed to write header");
break;
}
if(!protopirate_storage_write_capture_data(save_file, flipper_format)) {
FURI_LOG_E(TAG, "Failed to write capture data");
break;
}
ok = true;
} while(false);
flipper_format_free(save_file);
return ok;
}
static bool protopirate_storage_save_capture_atomic(
Storage* storage,
FlipperFormat* flipper_format,
const char* full_path) {
FuriString* tmp_path = furi_string_alloc();
furi_check(tmp_path);
furi_string_printf(tmp_path, "%s.tmp", full_path);
const char* tmp_cstr = furi_string_get_cstr(tmp_path);
bool ok = false;
bool write_ok = false;
do {
if(storage_file_exists(storage, tmp_cstr)) {
storage_simply_remove(storage, tmp_cstr);
}
if(!protopirate_storage_write_capture_file(storage, flipper_format, tmp_cstr)) {
break;
}
write_ok = true;
if(!protopirate_storage_commit_temp_file(storage, tmp_cstr, full_path)) {
break;
}
ok = true;
} while(false);
if(!write_ok && storage_file_exists(storage, tmp_cstr)) {
storage_simply_remove(storage, tmp_cstr);
}
furi_string_free(tmp_path);
return ok;
}
bool protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path) {
furi_check(flipper_format);
furi_check(full_path);
@@ -448,37 +659,12 @@ bool protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, con
}
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* save_file = flipper_format_file_alloc(storage);
bool result = false;
do {
// Remove if it already exists (overwrite)
if(storage_file_exists(storage, full_path)) {
storage_simply_remove(storage, full_path);
}
if(!flipper_format_file_open_new(save_file, full_path)) {
FURI_LOG_E(TAG, "Failed to create file: %s", full_path);
break;
}
if(!flipper_format_write_header_cstr(save_file, "Flipper SubGhz Key File", 1)) {
FURI_LOG_E(TAG, "Failed to write header");
break;
}
if(!protopirate_storage_write_capture_data(save_file, flipper_format)) {
FURI_LOG_E(TAG, "Failed to write capture data");
break;
}
result = true;
FURI_LOG_I(TAG, "Saved capture to %s", full_path);
} while(false);
flipper_format_free(save_file);
bool result = protopirate_storage_save_capture_atomic(storage, flipper_format, full_path);
furi_record_close(RECORD_STORAGE);
if(result) {
FURI_LOG_I(TAG, "Saved capture to %s", full_path);
}
return result;
}
@@ -513,35 +699,16 @@ bool protopirate_storage_save_capture(
}
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* save_file = flipper_format_file_alloc(storage);
bool result = false;
do {
if(!flipper_format_file_open_new(save_file, furi_string_get_cstr(file_path))) {
FURI_LOG_E(TAG, "Failed to create file");
break;
}
if(!flipper_format_write_header_cstr(save_file, "Flipper SubGhz Key File", 1)) {
FURI_LOG_E(TAG, "Failed to write header");
break;
}
if(!protopirate_storage_write_capture_data(save_file, flipper_format)) {
FURI_LOG_E(TAG, "Failed to write capture data");
break;
}
if(out_path) furi_string_set(out_path, file_path);
result = true;
FURI_LOG_I(TAG, "Saved capture to %s", furi_string_get_cstr(file_path));
} while(false);
flipper_format_free(save_file);
furi_string_free(file_path);
bool result = protopirate_storage_save_capture_atomic(
storage, flipper_format, furi_string_get_cstr(file_path));
furi_record_close(RECORD_STORAGE);
if(result) {
if(out_path) furi_string_set(out_path, file_path);
FURI_LOG_I(TAG, "Saved capture to %s", furi_string_get_cstr(file_path));
}
furi_string_free(file_path);
return result;
}
@@ -15,6 +15,11 @@
// Initialize storage (create folder if needed)
bool protopirate_storage_init(void);
bool protopirate_storage_commit_temp_file(
Storage* storage,
const char* tmp_path,
const char* final_path);
// Save a capture to a new file (auto-generated name)
bool protopirate_storage_save_capture(
FlipperFormat* flipper_format,
@@ -24,27 +29,19 @@ bool protopirate_storage_save_capture(
// Save a capture to a specific file path (user-chosen name)
bool protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path);
// Save to temp file for emulation
bool protopirate_storage_save_temp(FlipperFormat* flipper_format);
// Delete temp file
void protopirate_storage_delete_temp(void);
// Get next available filename for a protocol
bool protopirate_storage_get_next_filename(const char* protocol_name, FuriString* out_filename);
bool protopirate_storage_get_capture_display_protocol(
FlipperFormat* flipper_format,
FuriString* protocol_name);
// Delete a file
bool protopirate_storage_delete_file(const char* file_path);
// Load a file (caller must close with protopirate_storage_close_file)
FlipperFormat* protopirate_storage_load_file(const char* file_path);
// Close a loaded file (by protopirate_storage_load_file only)
void protopirate_storage_close_file(FlipperFormat* flipper_format);
// Check if file exists
bool protopirate_storage_file_exists(const char* file_path);
bool protopirate_storage_ensure_history_folder(void);
void protopirate_storage_purge_temp_history_at_startup(void);
@@ -0,0 +1,294 @@
#include "../protopirate_app_i.h"
#include "protopirate_psa_bf_host.h"
#include "radio_device_loader.h"
#include <loader/firmware_api/firmware_api.h>
#include <notification/notification_messages.h>
#define TAG "ProtoPirateToolScene"
#define SUB_DECODE_PLUGIN_PATH APP_ASSETS_PATH("plugins/protopirate_sub_decode_plugin.fal")
#ifdef ENABLE_TIMING_TUNER_SCENE
#define TIMING_TUNER_PLUGIN_PATH APP_ASSETS_PATH("plugins/protopirate_timing_tuner_plugin.fal")
#endif
static const char*
protopirate_tool_scene_plugin_path(ProtoPirateToolScenePluginKind kind) {
switch(kind) {
case ProtoPirateToolScenePluginKindSubDecode:
return SUB_DECODE_PLUGIN_PATH;
#ifdef ENABLE_TIMING_TUNER_SCENE
case ProtoPirateToolScenePluginKindTimingTuner:
return TIMING_TUNER_PLUGIN_PATH;
#endif
default:
return NULL;
}
}
static bool host_ensure_receiver_view(void* app) {
return protopirate_ensure_receiver_view((ProtoPirateApp*)app);
}
static bool host_ensure_widget(void* app) {
return protopirate_ensure_widget((ProtoPirateApp*)app);
}
static bool host_ensure_view_about(void* app) {
return protopirate_ensure_view_about((ProtoPirateApp*)app);
}
static bool host_radio_init(void* app) {
return protopirate_radio_init((ProtoPirateApp*)app);
}
static void host_rx_stack_resume_after_tx(void* app) {
protopirate_rx_stack_resume_after_tx((ProtoPirateApp*)app);
}
static void host_preset_init(
void* app,
const char* preset_name,
uint32_t frequency,
uint8_t* preset_data,
size_t preset_data_size) {
protopirate_preset_init(app, preset_name, frequency, preset_data, preset_data_size);
}
static bool host_refresh_protocol_registry(void* app, bool ensure_receiver_ready) {
return protopirate_refresh_protocol_registry((ProtoPirateApp*)app, ensure_receiver_ready);
}
static bool host_apply_protocol_registry_for_context(
void* app,
const char* preset_name,
uint32_t frequency,
const uint8_t* preset_data,
size_t preset_data_size,
const char* protocol_name) {
return protopirate_apply_protocol_registry_for_context(
(ProtoPirateApp*)app,
preset_name,
frequency,
preset_data,
preset_data_size,
protocol_name);
}
static void host_begin(void* app, uint8_t* preset_data) {
protopirate_begin((ProtoPirateApp*)app, preset_data);
}
static uint32_t host_rx(void* app, uint32_t frequency) {
return protopirate_rx((ProtoPirateApp*)app, frequency);
}
static void host_rx_end(void* app) {
protopirate_rx_end((ProtoPirateApp*)app);
}
static void host_get_frequency_modulation_str(
void* app,
char* frequency,
size_t frequency_size,
char* modulation,
size_t modulation_size) {
protopirate_get_frequency_modulation_str(
(ProtoPirateApp*)app, frequency, frequency_size, modulation, modulation_size);
}
static bool host_psa_bf_plugin_ensure_loaded(void* app) {
return protopirate_psa_bf_plugin_ensure_loaded((ProtoPirateApp*)app);
}
static void host_psa_bf_context_release(void* app) {
protopirate_psa_bf_context_release((ProtoPirateApp*)app);
}
static const ProtoPirateToolSceneHostApi protopirate_tool_scene_host_api = {
.ensure_receiver_view = host_ensure_receiver_view,
.ensure_widget = host_ensure_widget,
.ensure_view_about = host_ensure_view_about,
.radio_init = host_radio_init,
.rx_stack_resume_after_tx = host_rx_stack_resume_after_tx,
.preset_init = host_preset_init,
.refresh_protocol_registry = host_refresh_protocol_registry,
.apply_protocol_registry_for_context = host_apply_protocol_registry_for_context,
.begin = host_begin,
.rx = host_rx,
.rx_end = host_rx_end,
.get_frequency_modulation_str = host_get_frequency_modulation_str,
.history_release_scratch = protopirate_history_release_scratch,
.radio_device_is_external = radio_device_loader_is_external,
.receiver_add_data_statusbar = protopirate_view_receiver_add_data_statusbar,
.receiver_get_idx_menu = protopirate_view_receiver_get_idx_menu,
.receiver_set_idx_menu = protopirate_view_receiver_set_idx_menu,
.receiver_set_callback = protopirate_view_receiver_set_callback,
.receiver_set_sub_decode_mode = protopirate_view_receiver_set_sub_decode_mode,
.receiver_set_sub_decode_progress = protopirate_view_receiver_set_sub_decode_progress,
.receiver_reset_menu = protopirate_view_receiver_reset_menu,
.receiver_sync_menu_from_history = protopirate_view_receiver_sync_menu_from_history,
.psa_bf_plugin_ensure_loaded = host_psa_bf_plugin_ensure_loaded,
.psa_bf_context_release = host_psa_bf_context_release,
};
static void protopirate_tool_scene_plugin_unload(ProtoPirateApp* app) {
furi_check(app);
app->tool_scene_plugin = NULL;
if(app->tool_scene_plugin_manager) {
plugin_manager_free(app->tool_scene_plugin_manager);
app->tool_scene_plugin_manager = NULL;
}
if(app->tool_scene_plugin_resolver) {
composite_api_resolver_free(app->tool_scene_plugin_resolver);
app->tool_scene_plugin_resolver = NULL;
}
}
static bool protopirate_tool_scene_plugin_ensure_loaded(
ProtoPirateApp* app,
ProtoPirateToolScenePluginKind kind) {
furi_check(app);
if(app->tool_scene_plugin && app->tool_scene_plugin->kind == kind) {
return true;
}
if(app->tool_scene_plugin) {
if(app->tool_scene_plugin->release) {
app->tool_scene_plugin->release(app);
}
protopirate_tool_scene_plugin_unload(app);
}
const char* plugin_path = protopirate_tool_scene_plugin_path(kind);
if(!plugin_path) {
FURI_LOG_E(TAG, "No tool scene plugin path for kind %d", (int)kind);
return false;
}
CompositeApiResolver* resolver = composite_api_resolver_alloc();
if(!resolver) {
FURI_LOG_E(TAG, "Failed to allocate tool scene resolver");
return false;
}
composite_api_resolver_add(resolver, firmware_api_interface);
PluginManager* manager = plugin_manager_alloc(
PROTOPIRATE_TOOL_SCENE_PLUGIN_APP_ID,
PROTOPIRATE_TOOL_SCENE_PLUGIN_API_VERSION,
composite_api_resolver_get(resolver));
if(!manager) {
FURI_LOG_E(TAG, "Failed to allocate tool scene plugin manager");
composite_api_resolver_free(resolver);
return false;
}
PluginManagerError error = plugin_manager_load_single(manager, plugin_path);
if(error != PluginManagerErrorNone) {
FURI_LOG_E(TAG, "Failed to load tool scene plugin %s: %d", plugin_path, (int)error);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
const ProtoPirateToolScenePlugin* plugin = plugin_manager_get_ep(manager, 0U);
if(!plugin || plugin->kind != kind || !plugin->set_host_api || !plugin->on_enter ||
!plugin->on_event || !plugin->on_exit) {
FURI_LOG_E(TAG, "Tool scene plugin entry point is invalid for kind %d", (int)kind);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
app->tool_scene_plugin_resolver = resolver;
app->tool_scene_plugin_manager = manager;
app->tool_scene_plugin = plugin;
app->tool_scene_plugin_kind = kind;
plugin->set_host_api(&protopirate_tool_scene_host_api);
return true;
}
static void protopirate_tool_scene_apply_pending_nav(ProtoPirateApp* app) {
furi_check(app);
const uint8_t nav = app->tool_scene_nav_pending;
if(nav == TOOL_SCENE_NAV_NONE) {
return;
}
const uint32_t target = app->tool_scene_nav_target;
app->tool_scene_nav_pending = TOOL_SCENE_NAV_NONE;
app->tool_scene_nav_target = 0;
switch(nav) {
case TOOL_SCENE_NAV_POP:
scene_manager_previous_scene(app->scene_manager);
break;
case TOOL_SCENE_NAV_NEXT:
scene_manager_next_scene(app->scene_manager, target);
break;
case TOOL_SCENE_NAV_SEARCH_PREVIOUS:
scene_manager_search_and_switch_to_previous_scene(app->scene_manager, target);
break;
default:
break;
}
}
bool protopirate_tool_scene_on_enter(void* context, ProtoPirateToolScenePluginKind kind) {
ProtoPirateApp* app = context;
furi_check(app);
app->tool_scene_nav_pending = TOOL_SCENE_NAV_NONE;
app->tool_scene_nav_target = 0;
if(!protopirate_tool_scene_plugin_ensure_loaded(app, kind) || !app->tool_scene_plugin) {
notification_message(app->notifications, &sequence_error);
scene_manager_previous_scene(app->scene_manager);
return false;
}
app->tool_scene_plugin->on_enter(app);
protopirate_tool_scene_apply_pending_nav(app);
return true;
}
bool protopirate_tool_scene_on_event(void* context, SceneManagerEvent event) {
ProtoPirateApp* app = context;
if(!app || !app->tool_scene_plugin || !app->tool_scene_plugin->on_event) {
return false;
}
const bool consumed = app->tool_scene_plugin->on_event(app, event);
protopirate_tool_scene_apply_pending_nav(app);
return consumed;
}
void protopirate_tool_scene_on_exit(void* context) {
ProtoPirateApp* app = context;
if(!app) return;
if(app->tool_scene_plugin) {
if(app->tool_scene_plugin->on_exit) {
app->tool_scene_plugin->on_exit(app);
}
if(app->tool_scene_plugin->release) {
app->tool_scene_plugin->release(app);
}
}
protopirate_tool_scene_plugin_unload(app);
}
void protopirate_tool_scene_plugin_release(ProtoPirateApp* app) {
if(!app) return;
if(app->tool_scene_plugin && app->tool_scene_plugin->release) {
app->tool_scene_plugin->release(app);
}
protopirate_tool_scene_plugin_unload(app);
}
@@ -1,35 +1,14 @@
// protopirate_app_i.c
#include "protopirate_app_i.h"
#include "protocols/protocol_items.h"
#include <loader/firmware_api/firmware_api.h>
#include "protopirate_txrx.h"
#include "../protopirate_app_i.h"
#include "protopirate_protocol_plugin_host.h"
#include "protopirate_radio.h"
#include "protopirate_views.h"
#include <stdio.h>
#define TAG "ProtoPirateTxRx"
static const char* protopirate_get_registry_plugin_path(ProtoPirateProtocolRegistryFilter filter) {
return (filter == ProtoPirateProtocolRegistryFilterFM) ?
APP_ASSETS_PATH("plugins/protopirate_fm_plugin.fal") :
APP_ASSETS_PATH("plugins/protopirate_am_plugin.fal");
}
static void protopirate_unload_protocol_plugin(ProtoPirateTxRx* txrx) {
furi_check(txrx);
txrx->protocol_plugin = NULL;
txrx->protocol_registry = NULL;
if(txrx->protocol_plugin_manager) {
plugin_manager_free(txrx->protocol_plugin_manager);
txrx->protocol_plugin_manager = NULL;
}
if(txrx->plugin_resolver) {
composite_api_resolver_free(txrx->plugin_resolver);
txrx->plugin_resolver = NULL;
}
}
static void protopirate_teardown_receiver_stack_for_registry_switch(ProtoPirateApp* app) {
void protopirate_rx_stack_teardown_for_registry_switch(ProtoPirateApp* app) {
furi_check(app);
furi_check(app->txrx);
@@ -57,186 +36,6 @@ static void protopirate_teardown_receiver_stack_for_registry_switch(ProtoPirateA
}
}
static bool protopirate_ensure_protocol_registry_plugin(
ProtoPirateApp* app,
ProtoPirateProtocolRegistryFilter filter,
const SubGhzProtocolRegistry** registry) {
furi_check(app);
furi_check(app->txrx);
furi_check(registry);
*registry = NULL;
if(!app->txrx->environment) {
FURI_LOG_E(TAG, "Cannot load protocol plugin without radio environment");
return false;
}
if(app->txrx->protocol_plugin && app->txrx->protocol_plugin->registry &&
app->txrx->protocol_registry_filter == filter) {
*registry = app->txrx->protocol_plugin->registry;
return true;
}
if(app->txrx->protocol_plugin || app->txrx->protocol_plugin_manager ||
app->txrx->plugin_resolver) {
protopirate_unload_protocol_plugin(app->txrx);
}
CompositeApiResolver* resolver = composite_api_resolver_alloc();
if(!resolver) {
FURI_LOG_E(TAG, "Failed to allocate protocol plugin resolver");
return false;
}
composite_api_resolver_add(resolver, firmware_api_interface);
PluginManager* manager = plugin_manager_alloc(
PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
composite_api_resolver_get(resolver));
if(!manager) {
FURI_LOG_E(TAG, "Failed to allocate protocol plugin manager");
composite_api_resolver_free(resolver);
return false;
}
const char* plugin_path = protopirate_get_registry_plugin_path(filter);
PluginManagerError error = plugin_manager_load_single(manager, plugin_path);
if(error != PluginManagerErrorNone) {
FURI_LOG_E(TAG, "Failed to load protocol plugin %s: %d", plugin_path, (int)error);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
const ProtoPirateProtocolPlugin* plugin = plugin_manager_get_ep(manager, 0U);
if(!plugin || !plugin->registry) {
FURI_LOG_E(TAG, "Protocol plugin entry point is invalid");
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
if(plugin->filter != filter) {
FURI_LOG_E(
TAG, "Protocol plugin filter mismatch (expected %d got %d)", filter, plugin->filter);
plugin_manager_free(manager);
composite_api_resolver_free(resolver);
return false;
}
app->txrx->plugin_resolver = resolver;
app->txrx->protocol_plugin_manager = manager;
app->txrx->protocol_plugin = plugin;
app->txrx->protocol_registry_filter = filter;
*registry = plugin->registry;
return true;
}
bool protopirate_refresh_protocol_registry(ProtoPirateApp* app, bool ensure_receiver_ready) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->environment || !app->txrx->preset) {
return true;
}
ProtoPirateProtocolRegistryFilter filter = protopirate_get_protocol_registry_filter_for_preset(
app->txrx->preset->data, app->txrx->preset->data_size);
bool filter_changed = !app->txrx->protocol_plugin ||
(app->txrx->protocol_registry_filter != filter);
if(filter_changed) {
protopirate_teardown_receiver_stack_for_registry_switch(app);
} else if(ensure_receiver_ready && !app->txrx->receiver) {
protopirate_teardown_receiver_stack_for_registry_switch(app);
}
const SubGhzProtocolRegistry* registry = NULL;
if(!protopirate_ensure_protocol_registry_plugin(app, filter, &registry) || !registry) {
FURI_LOG_E(
TAG,
"Failed to resolve %s protocol registry plugin",
protopirate_get_protocol_registry_filter_name(filter));
return false;
}
const bool registry_already_bound = (app->txrx->protocol_registry == registry);
if(!registry_already_bound) {
FURI_LOG_I(
TAG,
"Using %s protocol registry (%zu protocols)",
protopirate_get_protocol_registry_filter_name(filter),
registry->size);
subghz_environment_set_protocol_registry(app->txrx->environment, registry);
app->txrx->protocol_registry = registry;
}
if(!ensure_receiver_ready) {
return true;
}
if(app->txrx->receiver) {
return true;
}
app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment);
if(!app->txrx->receiver) {
FURI_LOG_E(
TAG,
"Failed to allocate receiver for %s registry",
protopirate_get_protocol_registry_filter_name(filter));
return false;
}
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
return true;
}
bool protopirate_apply_protocol_registry_for_preset_data(
ProtoPirateApp* app,
const uint8_t* preset_data,
size_t preset_data_size) {
furi_check(app);
furi_check(app->txrx);
if(!app->txrx->environment) {
return false;
}
ProtoPirateProtocolRegistryFilter filter =
protopirate_get_protocol_registry_filter_for_preset(preset_data, preset_data_size);
bool filter_changed = !app->txrx->protocol_plugin ||
(app->txrx->protocol_registry_filter != filter);
if(filter_changed) {
protopirate_teardown_receiver_stack_for_registry_switch(app);
}
const SubGhzProtocolRegistry* registry = NULL;
if(!protopirate_ensure_protocol_registry_plugin(app, filter, &registry) || !registry) {
FURI_LOG_E(
TAG,
"Failed to resolve %s registry plugin for preset apply",
protopirate_get_protocol_registry_filter_name(filter));
return false;
}
if(app->txrx->protocol_registry == registry) {
return true;
}
FURI_LOG_I(
TAG,
"Switching active protocol registry to %s (%zu protocols)",
protopirate_get_protocol_registry_filter_name(filter),
registry->size);
subghz_environment_set_protocol_registry(app->txrx->environment, registry);
app->txrx->protocol_registry = registry;
return true;
}
void protopirate_preset_init(
void* context,
const char* preset_name,
@@ -318,7 +117,12 @@ uint32_t protopirate_rx(ProtoPirateApp* app, uint32_t frequency) {
}
if(!subghz_devices_is_frequency_valid(app->txrx->radio_device, frequency)) {
furi_crash("ProtoPirate: Incorrect RX frequency.");
FURI_LOG_E(TAG, "RX start rejected: invalid frequency %lu", frequency);
if(app->notifications) {
notification_message(app->notifications, &sequence_error);
}
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
return 0;
}
if(app->txrx->txrx_state == ProtoPirateTxRxStateRx ||
app->txrx->txrx_state == ProtoPirateTxRxStateSleep) {
@@ -418,17 +222,17 @@ void protopirate_rx_stack_resume_after_tx(ProtoPirateApp* app) {
}
}
void protopirate_hopper_update(ProtoPirateApp* app) {
bool protopirate_hopper_update(ProtoPirateApp* app) {
furi_check(app);
switch(app->txrx->hopper_state) {
case ProtoPirateHopperStateOFF:
case ProtoPirateHopperStatePause:
return;
return false;
case ProtoPirateHopperStateRSSITimeOut:
if(app->txrx->hopper_timeout != 0) {
app->txrx->hopper_timeout--;
return;
return false;
}
break;
default:
@@ -441,7 +245,7 @@ void protopirate_hopper_update(ProtoPirateApp* app) {
if(rssi > -90.0f) {
app->txrx->hopper_timeout = 10;
app->txrx->hopper_state = ProtoPirateHopperStateRSSITimeOut;
return;
return false;
}
} else {
app->txrx->hopper_state = ProtoPirateHopperStateRunning;
@@ -451,7 +255,7 @@ void protopirate_hopper_update(ProtoPirateApp* app) {
if(hopper_count == 0) {
app->txrx->hopper_state = ProtoPirateHopperStateOFF;
app->txrx->hopper_idx_frequency = 0;
return;
return false;
}
if(app->txrx->hopper_idx_frequency < hopper_count - 1) {
app->txrx->hopper_idx_frequency++;
@@ -462,12 +266,17 @@ void protopirate_hopper_update(ProtoPirateApp* app) {
if(app->txrx->txrx_state == ProtoPirateTxRxStateRx) {
protopirate_rx_end(app);
}
if(app->txrx->txrx_state == ProtoPirateTxRxStateIDLE && app->txrx->receiver) {
subghz_receiver_reset(app->txrx->receiver);
if(app->txrx->txrx_state == ProtoPirateTxRxStateIDLE) {
app->txrx->preset->frequency =
subghz_setting_get_hopper_frequency(app->setting, app->txrx->hopper_idx_frequency);
protopirate_rx(app, app->txrx->preset->frequency);
if(!protopirate_refresh_protocol_registry(app, true) || !app->txrx->receiver) {
FURI_LOG_E(TAG, "Failed to refresh registry while hopping");
return false;
}
return true;
}
return false;
}
void protopirate_tx(ProtoPirateApp* app, uint32_t frequency) {
@@ -0,0 +1,41 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <core/string.h>
typedef struct ProtoPirateApp ProtoPirateApp;
void protopirate_rx_stack_teardown_for_registry_switch(ProtoPirateApp* app);
void protopirate_preset_init(
void* context,
const char* preset_name,
uint32_t frequency,
uint8_t* preset_data,
size_t preset_data_size);
void protopirate_get_frequency_modulation(
ProtoPirateApp* app,
FuriString* frequency,
FuriString* modulation);
void protopirate_get_frequency_modulation_str(
ProtoPirateApp* app,
char* frequency,
size_t frequency_size,
char* modulation,
size_t modulation_size);
void protopirate_begin(ProtoPirateApp* app, uint8_t* preset_data);
uint32_t protopirate_rx(ProtoPirateApp* app, uint32_t frequency);
void protopirate_idle(ProtoPirateApp* app);
void protopirate_rx_end(ProtoPirateApp* app);
void protopirate_sleep(ProtoPirateApp* app);
bool protopirate_hopper_update(ProtoPirateApp* app);
void protopirate_tx(ProtoPirateApp* app, uint32_t frequency);
void protopirate_tx_stop(ProtoPirateApp* app);
void protopirate_release_shared_radio_state(ProtoPirateApp* app);
void protopirate_rx_stack_suspend_for_tx(ProtoPirateApp* app);
void protopirate_rx_stack_resume_after_tx(ProtoPirateApp* app);
@@ -28,6 +28,7 @@ typedef enum {
// File management
ProtoPirateCustomEventReceiverInfoSave,
ProtoPirateCustomEventReceiverInfoSaveConfirm,
ProtoPirateCustomEventReceiverInfoUpdate,
ProtoPirateCustomEventReceiverInfoEmulate,
ProtoPirateCustomEventReceiverInfoBruteforceStart,
ProtoPirateCustomEventReceiverInfoBruteforceCancel,
@@ -40,6 +41,7 @@ typedef enum {
// Sub decode
ProtoPirateCustomEventSubDecodeUpdate,
ProtoPirateCustomEventSubDecodeSave,
ProtoPirateCustomEventSubDecodeEmulate,
ProtoPirateCustomEventSubDecodeBruteforceStart,
ProtoPirateCustomEventPsaBruteforceComplete,
// File Browser
@@ -0,0 +1,135 @@
#include "protopirate_views.h"
#include "../protopirate_app_i.h"
#include <furi.h>
#define TAG "ProtoPirateViews"
bool protopirate_ensure_variable_item_list(ProtoPirateApp* app) {
furi_check(app);
if(app->variable_item_list) {
return true;
}
app->variable_item_list = variable_item_list_alloc();
if(!app->variable_item_list) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher,
ProtoPirateViewVariableItemList,
variable_item_list_get_view(app->variable_item_list));
return true;
}
bool protopirate_ensure_widget(ProtoPirateApp* app) {
furi_check(app);
if(app->widget) {
return true;
}
app->widget = widget_alloc();
if(!app->widget) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher, ProtoPirateViewWidget, widget_get_view(app->widget));
return true;
}
bool protopirate_ensure_text_input(ProtoPirateApp* app) {
furi_check(app);
if(app->text_input) {
return true;
}
app->text_input = text_input_alloc();
if(!app->text_input) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher, ProtoPirateViewTextInput, text_input_get_view(app->text_input));
return true;
}
bool protopirate_ensure_view_about(ProtoPirateApp* app) {
furi_check(app);
if(app->view_about) {
return true;
}
app->view_about = view_alloc();
if(!app->view_about) {
return false;
}
view_dispatcher_add_view(app->view_dispatcher, ProtoPirateViewAbout, app->view_about);
return true;
}
bool protopirate_ensure_receiver_view(ProtoPirateApp* app) {
furi_check(app);
if(app->protopirate_receiver) {
return true;
}
app->protopirate_receiver = protopirate_view_receiver_alloc(app->auto_save);
if(!app->protopirate_receiver) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher,
ProtoPirateViewReceiver,
protopirate_view_receiver_get_view(app->protopirate_receiver));
return true;
}
void protopirate_views_free(ProtoPirateApp* app) {
furi_check(app);
if(app->submenu) {
FURI_LOG_D(TAG, "Removing submenu view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewSubmenu);
submenu_free(app->submenu);
app->submenu = NULL;
}
if(app->variable_item_list) {
FURI_LOG_D(TAG, "Removing variable_item_list view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewVariableItemList);
variable_item_list_free(app->variable_item_list);
app->variable_item_list = NULL;
}
if(app->view_about) {
FURI_LOG_D(TAG, "Removing about view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewAbout);
view_free(app->view_about);
app->view_about = NULL;
}
if(app->widget) {
FURI_LOG_D(TAG, "Removing widget view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewWidget);
widget_free(app->widget);
app->widget = NULL;
}
if(app->text_input) {
FURI_LOG_D(TAG, "Removing text_input view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewTextInput);
text_input_free(app->text_input);
app->text_input = NULL;
}
if(app->protopirate_receiver) {
FURI_LOG_D(TAG, "Removing receiver view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewReceiver);
protopirate_view_receiver_free(app->protopirate_receiver);
app->protopirate_receiver = NULL;
}
}
@@ -0,0 +1,12 @@
#pragma once
#include <stdbool.h>
typedef struct ProtoPirateApp ProtoPirateApp;
bool protopirate_ensure_variable_item_list(ProtoPirateApp* app);
bool protopirate_ensure_widget(ProtoPirateApp* app);
bool protopirate_ensure_text_input(ProtoPirateApp* app);
bool protopirate_ensure_view_about(ProtoPirateApp* app);
bool protopirate_ensure_receiver_view(ProtoPirateApp* app);
void protopirate_views_free(ProtoPirateApp* app);
@@ -2,25 +2,26 @@
#ifdef ENABLE_SUB_DECODE_SCENE
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <toolbox/stream/stream.h>
#include <lib/flipper_format/flipper_format.h>
#include <lib/flipper_format/flipper_format_i.h>
#include "../protocols/protocols_common.h"
#define TAG "RawFileReader"
#define RAW_READER_MAX_TOKEN_LEN 64U
#define RAW_READER_KEY "RAW_Data:"
static const char local_flipper_format_delimiter = ':';
static const char local_flipper_format_comment = '#';
static const char local_flipper_format_eoln = '\n';
static const char local_flipper_format_eolr = '\r';
struct FlipperFormat {
Stream* stream;
bool strict_mode;
};
RawFileReader* raw_file_reader_alloc(void) {
RawFileReader* reader = malloc(sizeof(RawFileReader));
furi_check(reader);
if(!reader) return NULL;
memset(reader, 0, sizeof(RawFileReader));
return reader;
}
@@ -31,84 +32,6 @@ void raw_file_reader_free(RawFileReader* reader) {
free(reader);
}
static inline bool local_flipper_format_stream_is_space(char c) {
return c == ' ' || c == '\t' || c == local_flipper_format_eolr;
}
static bool local_flipper_format_stream_read_value(Stream* stream, FuriString* value, bool* last) {
enum {
LeadingSpace,
ReadValue,
TrailingSpace
} state = LeadingSpace;
const size_t buffer_size = 32;
uint8_t buffer[buffer_size];
bool result = false;
bool error = false;
furi_string_reset(value);
while(true) {
size_t was_read = stream_read(stream, buffer, buffer_size);
if(was_read == 0) {
if(state != LeadingSpace && stream_eof(stream)) {
result = true;
*last = true;
} else {
error = true;
}
}
for(size_t i = 0; i < was_read; i++) {
const uint8_t data = buffer[i];
if(state == LeadingSpace) {
if(local_flipper_format_stream_is_space(data)) {
continue;
} else if(data == local_flipper_format_eoln) {
stream_seek(stream, (int32_t)i - (int32_t)was_read, StreamOffsetFromCurrent);
error = true;
break;
} else {
state = ReadValue;
furi_string_push_back(value, data);
}
} else if(state == ReadValue) {
if(local_flipper_format_stream_is_space(data)) {
state = TrailingSpace;
} else if(data == local_flipper_format_eoln) {
if(!stream_seek(
stream, (int32_t)i - (int32_t)was_read, StreamOffsetFromCurrent)) {
error = true;
} else {
result = true;
*last = true;
}
break;
} else {
furi_string_push_back(value, data);
}
} else if(state == TrailingSpace) {
if(local_flipper_format_stream_is_space(data)) {
continue;
} else if(!stream_seek(
stream, (int32_t)i - (int32_t)was_read, StreamOffsetFromCurrent)) {
error = true;
} else {
*last = (data == local_flipper_format_eoln);
result = true;
}
break;
}
}
if(error || result) break;
}
return result;
}
static bool local_flipper_format_stream_read_valid_key(Stream* stream, FuriString* key) {
furi_string_reset(key);
const size_t buffer_size = 32;
@@ -165,8 +88,12 @@ static bool local_flipper_format_stream_read_valid_key(Stream* stream, FuriStrin
// just new symbol, reset the new_line flag
new_line = false;
if(accumulate) {
// and accumulate data if we want
furi_string_push_back(key, data);
if(furi_string_size(key) >= RAW_READER_MAX_TOKEN_LEN) {
accumulate = false;
} else {
furi_string_push_back(key, data);
}
}
}
}
@@ -202,36 +129,125 @@ static bool
return found;
}
static bool local_flipper_format_stream_get_value_count(
Stream* stream,
const char* key,
uint32_t* count,
bool strict_mode) {
bool result = false;
bool last = false;
static bool raw_file_reader_stream_read_char(RawFileReader* reader, char* out) {
if(!reader || !reader->stream || !out) {
return false;
}
FuriString* value;
value = furi_string_alloc();
if(reader->buffer_index >= reader->buffer_count) {
const size_t was_read =
stream_read(reader->stream, reader->buffer, RAW_READER_BUFFER_SIZE);
if(was_read == 0U) {
return false;
}
do {
if(!local_flipper_format_stream_seek_to_key(stream, key, strict_mode)) break;
*count = 0;
reader->buffer_count = (uint16_t)was_read;
reader->buffer_index = 0U;
}
result = true;
while(true) {
if(!local_flipper_format_stream_read_value(stream, value, &last)) {
result = false;
const uint8_t value = reader->buffer[reader->buffer_index++];
if(reader->stream_pos < UINT64_MAX) {
reader->stream_pos++;
}
*out = (char)value;
return true;
}
static bool raw_file_reader_at_logical_eof(const RawFileReader* reader) {
return reader && reader->stream && reader->buffer_index >= reader->buffer_count &&
stream_eof(reader->stream);
}
static void raw_file_reader_mark_finished(RawFileReader* reader) {
reader->file_finished = true;
reader->stream_pos = reader->file_size > 0U ? reader->file_size : reader->stream_pos;
}
static bool raw_file_reader_seek_next_values(RawFileReader* reader) {
const char* key = RAW_READER_KEY;
size_t match = 0U;
char c = '\0';
while(raw_file_reader_stream_read_char(reader, &c)) {
if(c == key[match]) {
match++;
if(key[match] == '\0') {
reader->in_line = true;
return true;
}
} else {
match = (c == key[0]) ? 1U : 0U;
}
}
raw_file_reader_mark_finished(reader);
return false;
}
static bool raw_file_reader_stream_next_int(RawFileReader* reader, int32_t* out) {
if(!reader || !out || !reader->stream) return false;
while(true) {
if(!reader->in_line && !raw_file_reader_seek_next_values(reader)) {
return false;
}
bool negative = false;
bool sign_seen = false;
bool have_digits = false;
int32_t value = 0;
char c = '\0';
while(raw_file_reader_stream_read_char(reader, &c)) {
if(c == '\r') {
continue;
}
if(c == '\n') {
reader->in_line = false;
if(have_digits) {
*out = negative ? -value : value;
return true;
}
break;
}
*count = *count + 1;
if(last) break;
if(!sign_seen && !have_digits && (c == ' ' || c == '\t' || c == ',')) {
continue;
}
if(!sign_seen && !have_digits && (c == '-' || c == '+')) {
negative = (c == '-');
sign_seen = true;
continue;
}
if(c >= '0' && c <= '9') {
have_digits = true;
value = (value * 10) + (c - '0');
continue;
}
if(have_digits) {
*out = negative ? -value : value;
return true;
}
negative = false;
sign_seen = false;
}
} while(false);
if(have_digits) {
raw_file_reader_mark_finished(reader);
*out = negative ? -value : value;
return true;
}
furi_string_free(value);
return result;
if(raw_file_reader_at_logical_eof(reader)) {
raw_file_reader_mark_finished(reader);
return false;
}
}
}
bool raw_file_reader_open(RawFileReader* reader, const char* file_path) {
@@ -287,26 +303,43 @@ bool raw_file_reader_open(RawFileReader* reader, const char* file_path) {
reader->buffer_count = 0;
reader->buffer_index = 0;
reader->file_finished = false;
reader->current_level = true;
reader->in_line = false;
FURI_LOG_I(TAG, "Opened RAW file: %s", file_path);
reader->count = 0;
uint32_t temp_count = 0;
while(local_flipper_format_stream_get_value_count(
reader->ff->stream, "RAW_Data", &temp_count, reader->ff->strict_mode)) {
//reader->file_finished = true;
reader->count += temp_count;
reader->stream = flipper_format_get_raw_stream(reader->ff);
if(!reader->stream) {
FURI_LOG_E(TAG, "Missing raw stream");
raw_file_reader_close(reader);
return false;
}
flipper_format_rewind(reader->ff);
if(!local_flipper_format_stream_seek_to_key(reader->stream, "RAW_Data", false)) {
FURI_LOG_E(TAG, "RAW file has no samples");
raw_file_reader_close(reader);
return false;
}
reader->in_line = true;
reader->data_start_offset = stream_tell(reader->stream);
reader->stream_pos = reader->data_start_offset;
reader->file_size = stream_size(reader->stream);
if(reader->file_size == 0) {
FileInfo file_info = {0};
if(storage_common_stat(reader->storage, file_path, &file_info) == FSE_OK) {
reader->file_size = file_info.size;
}
}
FURI_LOG_I(
TAG,
"Opened RAW file for streaming decode: %s",
file_path);
return true;
}
void raw_file_reader_close(RawFileReader* reader) {
if(!reader) return;
reader->stream = NULL;
if(reader->ff) {
flipper_format_free(reader->ff);
reader->ff = NULL;
@@ -320,44 +353,26 @@ void raw_file_reader_close(RawFileReader* reader) {
reader->storage = NULL;
reader->buffer_count = 0;
reader->buffer_index = 0;
reader->count = 0;
reader->in_line = false;
reader->file_finished = false;
}
static bool raw_file_reader_load_chunk(RawFileReader* reader) {
if(reader->file_finished) return false;
size_t to_read = (reader->count < RAW_READER_BUFFER_SIZE) ? reader->count :
RAW_READER_BUFFER_SIZE;
if(!flipper_format_read_int32(reader->ff, "RAW_Data", reader->buffer, to_read)) {
reader->file_finished = true;
return false;
}
reader->buffer_count = to_read;
reader->buffer_index = 0;
reader->count -= to_read;
return true;
reader->file_size = 0;
reader->data_start_offset = 0;
reader->stream_pos = 0;
}
bool raw_file_reader_get_next(RawFileReader* reader, bool* level, uint32_t* duration) {
if(!reader || !level || !duration) return false;
if(memmgr_get_free_heap() < 1024) {
if(reader->buffer_index >= reader->buffer_count && memmgr_get_free_heap() < 1024) {
FURI_LOG_E(TAG, "Not enough memory to continue reading");
return false;
}
if(reader->buffer_index >= reader->buffer_count) {
if(!raw_file_reader_load_chunk(reader)) {
return false;
}
int32_t value = 0;
if(!raw_file_reader_stream_next_int(reader, &value)) {
return false;
}
int32_t value = reader->buffer[reader->buffer_index++];
if(value >= 0) {
*level = true;
*duration = (uint32_t)value;
@@ -371,6 +386,22 @@ bool raw_file_reader_get_next(RawFileReader* reader, bool* level, uint32_t* dura
bool raw_file_reader_is_finished(RawFileReader* reader) {
if(!reader) return true;
return reader->file_finished && (reader->buffer_index >= reader->buffer_count);
return reader->file_finished && reader->buffer_index >= reader->buffer_count;
}
uint8_t raw_file_reader_get_progress(const RawFileReader* reader) {
if(!reader) return 0;
if(reader->file_finished && reader->buffer_index >= reader->buffer_count) return 100;
if(reader->file_size <= reader->data_start_offset) return 0;
const uint64_t total = reader->file_size - reader->data_start_offset;
const uint64_t current_pos = reader->stream_pos;
uint64_t done = 0;
if(current_pos > reader->data_start_offset) {
done = current_pos - reader->data_start_offset;
}
if(done >= total) return 100;
return (uint8_t)((done * 100ULL) / total);
}
#endif // ENABLE_SUB_DECODE_SCENE
@@ -5,19 +5,23 @@
#include <furi.h>
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#include <toolbox/stream/stream.h>
#define RAW_READER_BUFFER_SIZE 512
#define RAW_READER_BUFFER_SIZE 2048U
typedef struct {
Storage* storage;
FlipperFormat* ff;
int32_t buffer[RAW_READER_BUFFER_SIZE];
size_t buffer_count;
size_t buffer_index;
uint32_t count;
Stream* stream;
uint8_t buffer[RAW_READER_BUFFER_SIZE];
uint16_t buffer_count;
uint16_t buffer_index;
bool in_line;
bool file_finished;
bool current_level;
bool storage_opened;
uint64_t file_size;
uint64_t data_start_offset;
uint64_t stream_pos;
} RawFileReader;
RawFileReader* raw_file_reader_alloc(void);
@@ -26,4 +30,5 @@ bool raw_file_reader_open(RawFileReader* reader, const char* file_path);
void raw_file_reader_close(RawFileReader* reader);
bool raw_file_reader_get_next(RawFileReader* reader, bool* level, uint32_t* duration);
bool raw_file_reader_is_finished(RawFileReader* reader);
uint8_t raw_file_reader_get_progress(const RawFileReader* reader);
#endif // ENABLE_SUB_DECODE_SCENE
@@ -215,7 +215,7 @@ static void chrysler_v0_decoder_commit(SubGhzProtocolDecoderChrysler_V0* instanc
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint8_t chrysler_v0_payload_get_bit(const uint8_t payload[10], uint8_t index) {
const uint8_t byte = payload[index >> 3U];
@@ -295,7 +295,7 @@ const SubGhzProtocolDecoder subghz_protocol_chrysler_v0_decoder = {
.get_string = subghz_protocol_decoder_chrysler_v0_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_chrysler_v0_encoder = {
.alloc = subghz_protocol_encoder_chrysler_v0_alloc,
.free = pp_encoder_free,
@@ -318,15 +318,23 @@ const SubGhzProtocol chrysler_protocol_v0 = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
| SubGhzProtocolFlag_Send
#endif
,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_chrysler_v0_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_chrysler_v0_encoder,
#else
.encoder = NULL,
#endif
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_chrysler_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
@@ -31,7 +31,7 @@ SubGhzProtocolStatus
subghz_protocol_decoder_chrysler_v0_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_chrysler_v0_get_string(void* context, FuriString* output);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_chrysler_v0_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
subghz_protocol_encoder_chrysler_v0_deserialize(void* context, FlipperFormat* flipper_format);
@@ -1,11 +1,8 @@
#include "fiat_v0.h"
#include "protocols_common.h"
#include "../protopirate_app_i.h"
#include <lib/toolbox/manchester_decoder.h>
#include <inttypes.h>
#define TAG "FiatProtocolV0"
#define FIAT_PROTOCOL_V0_NAME "Fiat V0"
#define FIAT_V0_PREAMBLE_PAIRS 150
#define FIAT_V0_GAP_US 800
#define FIAT_V0_TOTAL_BURSTS 3
@@ -42,6 +39,17 @@ typedef enum {
FiatV0DecoderStepData = 2,
} FiatV0DecoderStep;
static const char* fiat_v0_display_suffix(uint8_t endbyte) {
const uint8_t low_nibble = endbyte & 0x0FU;
if((low_nibble >= 0x04U) && (low_nibble <= 0x07U)) {
return "Lock";
}
if((low_nibble >= 0x08U) && (low_nibble <= 0x0BU)) {
return "Unlock";
}
return "??";
}
static void fiat_v0_finish_packet(struct SubGhzProtocolDecoderFiatV0* instance) {
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
instance->generic.data_count_bit = 71;
@@ -78,7 +86,7 @@ const SubGhzProtocolDecoder subghz_protocol_fiat_v0_decoder = {
.get_string = subghz_protocol_decoder_fiat_v0_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_fiat_v0_encoder = {
.alloc = subghz_protocol_encoder_fiat_v0_alloc,
.free = pp_encoder_free,
@@ -102,14 +110,22 @@ const SubGhzProtocol fiat_protocol_v0 = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_fiat_v0_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_fiat_v0_encoder,
#else
.encoder = NULL,
#endif
};
// ============================================================================
// ENCODER IMPLEMENTATION
// ============================================================================
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
@@ -128,7 +144,7 @@ void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiatV0* instance) {
furi_check(instance);
@@ -220,7 +236,7 @@ static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiat
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
SubGhzProtocolStatus
subghz_protocol_encoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
@@ -239,10 +255,9 @@ SubGhzProtocolStatus
uint32_t bit_count = 0;
if(pp_encoder_read_bit(flipper_format, allowed_bits, 2, &bit_count) !=
SubGhzProtocolStatusOk) {
instance->generic.data_count_bit = 71; // legacy default for garbage Bit values
} else {
instance->generic.data_count_bit = bit_count;
return SubGhzProtocolStatusErrorValueBitCount;
}
instance->generic.data_count_bit = bit_count;
uint64_t key = 0;
if(!pp_flipper_read_hex_u64(flipper_format, FF_KEY, &key)) {
@@ -489,51 +504,64 @@ SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize(
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret != SubGhzProtocolStatusOk) {
return ret;
}
do {
if(!flipper_format_write_uint32(flipper_format, FF_FREQUENCY, &preset->frequency, 1))
break;
ret = pp_serialize_fields(
flipper_format,
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
instance->fix,
instance->endbyte,
instance->hop,
0);
if(ret != SubGhzProtocolStatusOk) {
return ret;
}
if(!flipper_format_write_string_cstr(
flipper_format, FF_PRESET, furi_string_get_cstr(preset->name)))
break;
uint32_t endbyte_ff = instance->endbyte;
if(!flipper_format_write_uint32(flipper_format, "EndByte", &endbyte_ff, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
if(!flipper_format_write_string_cstr(
flipper_format, FF_PROTOCOL, instance->generic.protocol_name))
break;
uint32_t bits = instance->generic.data_count_bit;
if(!flipper_format_write_uint32(flipper_format, FF_BIT, &bits, 1)) break;
char key_str[20];
snprintf(key_str, sizeof(key_str), "%08lX%08lX", instance->hop, instance->fix);
if(!flipper_format_write_string_cstr(flipper_format, FF_KEY, key_str)) break;
if(pp_serialize_fields(
flipper_format,
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
instance->fix,
instance->endbyte,
instance->hop,
0) != SubGhzProtocolStatusOk)
break;
uint32_t endbyte_ff = instance->endbyte;
if(!flipper_format_write_uint32(flipper_format, "EndByte", &endbyte_ff, 1)) break;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
return pp_write_display(
flipper_format,
instance->generic.protocol_name,
fiat_v0_display_suffix(instance->endbyte));
}
SubGhzProtocolStatus
subghz_protocol_decoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_fiat_v0_const.min_count_bit_for_found);
if(status != SubGhzProtocolStatusOk) {
return status;
}
instance->hop = (uint32_t)(instance->generic.data >> 32U);
instance->fix = (uint32_t)(instance->generic.data & 0xFFFFFFFFU);
instance->decoder.decode_data = instance->generic.data;
instance->decoder.decode_count_bit = instance->generic.data_count_bit;
uint32_t endbyte_u32 = 0U;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "EndByte", &endbyte_u32, 1)) {
instance->endbyte = (uint8_t)(endbyte_u32 & 0x7FU);
} else {
pp_encoder_read_fields(flipper_format, NULL, &endbyte_u32, NULL, NULL);
instance->endbyte = (uint8_t)(endbyte_u32 & 0x7FU);
}
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
return status;
}
void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* output) {
@@ -12,6 +12,8 @@
#include "../defines.h"
#define FIAT_PROTOCOL_V0_NAME "Fiat V0"
typedef struct SubGhzProtocolDecoderFiatV0 SubGhzProtocolDecoderFiatV0;
typedef struct SubGhzProtocolEncoderFiatV0 SubGhzProtocolEncoderFiatV0;
@@ -19,7 +21,6 @@ extern const SubGhzProtocol fiat_protocol_v0;
// Decoder functions
void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_v0_free(void* context);
void subghz_protocol_decoder_fiat_v0_reset(void* context);
void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context);
File diff suppressed because it is too large Load Diff
@@ -5,28 +5,32 @@
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <lib/toolbox/manchester_decoder.h>
#include <flipper_format/flipper_format.h>
#include "../defines.h"
#define FIAT_MARELLI_PROTOCOL_NAME "Fiat V1"
#define FIAT_V1_PROTOCOL_NAME "Fiat V1"
typedef struct SubGhzProtocolDecoderFiatMarelli SubGhzProtocolDecoderFiatMarelli;
typedef struct SubGhzProtocolDecoderFiatV1 SubGhzProtocolDecoderFiatV1;
typedef struct SubGhzProtocolEncoderFiatV1 SubGhzProtocolEncoderFiatV1;
extern const SubGhzProtocol fiat_v1_protocol;
void* subghz_protocol_decoder_fiat_marelli_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_marelli_free(void* context);
void subghz_protocol_decoder_fiat_marelli_reset(void* context);
void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_marelli_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_marelli_serialize(
void* subghz_protocol_decoder_fiat_v1_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_v1_reset(void* context);
void subghz_protocol_decoder_fiat_v1_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_v1_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v1_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString* output);
subghz_protocol_decoder_fiat_v1_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_fiat_v1_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_fiat_v1_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
subghz_protocol_encoder_fiat_v1_deserialize(void* context, FlipperFormat* flipper_format);
@@ -0,0 +1,417 @@
#include "fiat_v2.h"
#include "protocols_common.h"
#include <string.h>
#define TAG "FiatProtocolV2"
#define FIAT_V2_TE_SHORT 210U
#define FIAT_V2_TE_LONG 420U
#define FIAT_V2_TE_DELTA 100U
#define FIAT_V2_BOUNDARY_MIN_US 900U
#define FIAT_V2_WIRE_BITS 112U
#define FIAT_V2_WIRE_BYTES 14U
#define FIAT_V2_WIRE_CELLS (FIAT_V2_WIRE_BITS * 2U)
#define FIAT_V2_LOGICAL_BITS 112U
#define FIAT_V2_MARKER0 0x00U
#define FIAT_V2_MARKER1 0x01U
#define FIAT_V2_BTN_SHIFT 6U
#define FIAT_V2_BUTTON_LOCK 0x2U
#define FIAT_V2_BUTTON_UNLOCK 0x3U
#define FIAT_V2_BUTTON_TRUNK 0x1U
#define FIAT_V2_CNT_SHIFT 3U
#define FIAT_V2_FCA_TYPE_NIBBLE 0xD0U
#define FIAT_V2_RAW_FIELD "Raw"
#define FIAT_V2_HOP_FIELD "Hop"
#define FIAT_V2_BTN_FIELD "Btn"
static const SubGhzBlockConst subghz_protocol_fiat_v2_const = {
.te_short = FIAT_V2_TE_SHORT,
.te_long = FIAT_V2_TE_LONG,
.te_delta = FIAT_V2_TE_DELTA,
.min_count_bit_for_found = FIAT_V2_LOGICAL_BITS,
};
typedef enum {
FiatV2DecoderStepReset = 0,
FiatV2DecoderStepData = 1,
} FiatV2DecoderStep;
struct SubGhzProtocolDecoderFiatV2 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint8_t cells[FIAT_V2_WIRE_CELLS];
uint16_t cell_count;
uint8_t raw_data[FIAT_V2_WIRE_BYTES];
uint8_t last_raw_data[FIAT_V2_WIRE_BYTES];
bool last_raw_valid;
uint32_t uid;
uint32_t hop;
uint8_t button;
};
static bool fiat_v2_feed_data_pulse(
SubGhzProtocolDecoderFiatV2* instance,
bool level,
uint32_t duration);
static bool fiat_v2_frame_valid(const uint8_t raw[FIAT_V2_WIRE_BYTES]);
const SubGhzProtocolDecoder subghz_protocol_fiat_v2_decoder = {
.alloc = subghz_protocol_decoder_fiat_v2_alloc,
.free = pp_decoder_free_default,
.feed = subghz_protocol_decoder_fiat_v2_feed,
.reset = subghz_protocol_decoder_fiat_v2_reset,
.get_hash_data = subghz_protocol_decoder_fiat_v2_get_hash_data,
.serialize = subghz_protocol_decoder_fiat_v2_serialize,
.deserialize = subghz_protocol_decoder_fiat_v2_deserialize,
.get_string = subghz_protocol_decoder_fiat_v2_get_string,
};
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_fiat_v2_encoder = {
.alloc = NULL,
.free = NULL,
.deserialize = NULL,
.stop = NULL,
.yield = NULL,
};
#endif
const SubGhzProtocol fiat_v2_protocol = {
.name = FIAT_V2_PROTOCOL_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_fiat_v2_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_fiat_v2_encoder,
#else
.encoder = NULL,
#endif
};
static bool fiat_v2_duration_is_short(uint32_t duration) {
return pp_is_short(duration, &subghz_protocol_fiat_v2_const);
}
static bool fiat_v2_duration_is_long(uint32_t duration) {
return pp_is_long(duration, &subghz_protocol_fiat_v2_const);
}
static bool fiat_v2_button_valid(uint8_t button) {
const uint8_t sel = button >> FIAT_V2_BTN_SHIFT;
return sel == FIAT_V2_BUTTON_LOCK || sel == FIAT_V2_BUTTON_UNLOCK ||
sel == FIAT_V2_BUTTON_TRUNK;
}
static const char* fiat_v2_button_name(uint8_t button) {
switch(button >> FIAT_V2_BTN_SHIFT) {
case FIAT_V2_BUTTON_LOCK:
return "Lock";
case FIAT_V2_BUTTON_UNLOCK:
return "Unlock";
case FIAT_V2_BUTTON_TRUNK:
return "Trunk";
default:
return "Unknown";
}
}
static uint32_t fiat_v2_uid(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
return ((uint32_t)raw[2] << 24U) | ((uint32_t)raw[3] << 16U) |
((uint32_t)raw[4] << 8U) | raw[5];
}
static bool fiat_v2_is_fca(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
return (raw[6] & 0xF0U) == FIAT_V2_FCA_TYPE_NIBBLE;
}
static uint32_t fiat_v2_hop(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
if(fiat_v2_is_fca(raw)) {
return ((uint32_t)raw[10] << 24U) | ((uint32_t)raw[11] << 16U) |
((uint32_t)raw[12] << 8U) | raw[13];
}
return ((uint32_t)raw[9] << 24U) | ((uint32_t)raw[10] << 16U) |
((uint32_t)raw[11] << 8U) | raw[12];
}
static uint32_t fiat_v2_counter(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
if(fiat_v2_is_fca(raw)) {
const uint32_t raw_cnt = ((uint32_t)raw[8] << 6U) | (uint32_t)(raw[9] >> 2U);
return (~raw_cnt) & 0x3FFFU;
}
const uint32_t raw_cnt =
((uint32_t)(raw[7] & 0x3FU) << 5U) | (uint32_t)(raw[8] >> FIAT_V2_CNT_SHIFT);
return (~raw_cnt) & 0x7FFU;
}
static bool fiat_v2_frame_valid(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
if(raw[0] != FIAT_V2_MARKER0 || raw[1] != FIAT_V2_MARKER1) {
return false;
}
if(!fiat_v2_button_valid(raw[7])) {
return false;
}
const uint32_t uid = fiat_v2_uid(raw);
return uid != 0U && uid != UINT32_MAX;
}
static void fiat_v2_clear_cells(SubGhzProtocolDecoderFiatV2* instance) {
instance->cell_count = 0U;
memset(instance->cells, 0, sizeof(instance->cells));
}
static void fiat_v2_decode_fields(SubGhzProtocolDecoderFiatV2* instance) {
instance->uid = fiat_v2_uid(instance->raw_data);
instance->button = instance->raw_data[7];
instance->hop = fiat_v2_hop(instance->raw_data);
instance->generic.serial = instance->uid;
instance->generic.btn = instance->button;
instance->generic.cnt = fiat_v2_counter(instance->raw_data);
instance->generic.data = ((uint64_t)instance->generic.serial << 32U) | instance->hop;
instance->generic.data_count_bit = FIAT_V2_LOGICAL_BITS;
instance->decoder.decode_data = instance->generic.data;
instance->decoder.decode_count_bit = instance->generic.data_count_bit;
}
static bool fiat_v2_commit(
SubGhzProtocolDecoderFiatV2* instance,
const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
if(!fiat_v2_frame_valid(raw)) {
return false;
}
if(instance->last_raw_valid && memcmp(instance->last_raw_data, raw, FIAT_V2_WIRE_BYTES) == 0) {
return true;
}
memcpy(instance->raw_data, raw, FIAT_V2_WIRE_BYTES);
memcpy(instance->last_raw_data, raw, FIAT_V2_WIRE_BYTES);
instance->last_raw_valid = true;
fiat_v2_decode_fields(instance);
FURI_LOG_D(
TAG,
"Accepted UID:%08lX Btn:%02X Cnt:%02lX Hop:%08lX",
(unsigned long)instance->uid,
instance->button,
(unsigned long)instance->generic.cnt,
(unsigned long)instance->hop);
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
return true;
}
static bool fiat_v2_try_decode_window(SubGhzProtocolDecoderFiatV2* instance, bool invert) {
if(instance->cell_count != FIAT_V2_WIRE_CELLS) {
return false;
}
uint8_t raw[FIAT_V2_WIRE_BYTES] = {0};
for(uint8_t bit_index = 0U; bit_index < FIAT_V2_WIRE_BITS; bit_index++) {
const uint8_t first = instance->cells[bit_index * 2U];
const uint8_t second = instance->cells[bit_index * 2U + 1U];
if(first == second) {
return false;
}
bool bit = first != 0U;
if(invert) {
bit = !bit;
}
if(bit) {
raw[bit_index >> 3U] |= (uint8_t)(1U << (7U - (bit_index & 7U)));
}
}
return fiat_v2_commit(instance, raw);
}
static void fiat_v2_try_decode(SubGhzProtocolDecoderFiatV2* instance) {
if(fiat_v2_try_decode_window(instance, false)) {
return;
}
(void)fiat_v2_try_decode_window(instance, true);
}
static void fiat_v2_push_cell(SubGhzProtocolDecoderFiatV2* instance, bool level) {
if(instance->cell_count < FIAT_V2_WIRE_CELLS) {
instance->cells[instance->cell_count++] = level ? 1U : 0U;
} else {
memmove(instance->cells, &instance->cells[1], FIAT_V2_WIRE_CELLS - 1U);
instance->cells[FIAT_V2_WIRE_CELLS - 1U] = level ? 1U : 0U;
}
fiat_v2_try_decode(instance);
}
static bool fiat_v2_feed_data_pulse(
SubGhzProtocolDecoderFiatV2* instance,
bool level,
uint32_t duration) {
if(fiat_v2_duration_is_short(duration)) {
fiat_v2_push_cell(instance, level);
return true;
}
if(fiat_v2_duration_is_long(duration)) {
fiat_v2_push_cell(instance, level);
fiat_v2_push_cell(instance, level);
return true;
}
if(!level && duration >= FIAT_V2_BOUNDARY_MIN_US) {
fiat_v2_push_cell(instance, false);
}
fiat_v2_clear_cells(instance);
return false;
}
void* subghz_protocol_decoder_fiat_v2_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderFiatV2* instance = calloc(1, sizeof(SubGhzProtocolDecoderFiatV2));
furi_check(instance);
instance->base.protocol = &fiat_v2_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
subghz_protocol_decoder_fiat_v2_reset(instance);
return instance;
}
void subghz_protocol_decoder_fiat_v2_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV2* instance = context;
memset(instance->raw_data, 0, sizeof(instance->raw_data));
memset(instance->last_raw_data, 0, sizeof(instance->last_raw_data));
instance->decoder.parser_step = FiatV2DecoderStepReset;
instance->decoder.decode_data = 0U;
instance->decoder.decode_count_bit = 0U;
instance->last_raw_valid = false;
instance->generic.data = 0U;
instance->generic.data_count_bit = 0U;
instance->generic.serial = 0U;
instance->generic.btn = 0U;
instance->generic.cnt = 0U;
instance->uid = 0U;
instance->hop = 0U;
instance->button = 0U;
fiat_v2_clear_cells(instance);
}
void subghz_protocol_decoder_fiat_v2_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderFiatV2* instance = context;
switch(instance->decoder.parser_step) {
case FiatV2DecoderStepReset:
if(fiat_v2_duration_is_short(duration) || fiat_v2_duration_is_long(duration)) {
fiat_v2_clear_cells(instance);
instance->decoder.parser_step = FiatV2DecoderStepData;
(void)fiat_v2_feed_data_pulse(instance, level, duration);
}
break;
case FiatV2DecoderStepData:
if(!fiat_v2_feed_data_pulse(instance, level, duration)) {
instance->decoder.parser_step = FiatV2DecoderStepReset;
}
break;
}
}
uint8_t subghz_protocol_decoder_fiat_v2_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV2* instance = context;
SubGhzBlockDecoder decoder = {
.decode_data = instance->generic.data,
.decode_count_bit = 64U,
};
return subghz_protocol_blocks_get_hash_data(&decoder, 8U) ^ instance->generic.cnt ^
instance->button;
}
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v2_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderFiatV2* instance = context;
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret != SubGhzProtocolStatusOk) {
return ret;
}
flipper_format_rewind(flipper_format);
flipper_format_insert_or_update_hex(
flipper_format, FIAT_V2_RAW_FIELD, instance->raw_data, FIAT_V2_WIRE_BYTES);
uint32_t hop = instance->hop;
uint32_t button = instance->button;
if(!flipper_format_write_uint32(flipper_format, FIAT_V2_HOP_FIELD, &hop, 1) ||
!flipper_format_write_uint32(flipper_format, FIAT_V2_BTN_FIELD, &button, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->generic.serial);
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->generic.btn);
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->generic.cnt);
return SubGhzProtocolStatusOk;
}
SubGhzProtocolStatus
subghz_protocol_decoder_fiat_v2_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderFiatV2* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_fiat_v2_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
return ret;
}
if(instance->generic.data_count_bit != FIAT_V2_LOGICAL_BITS) {
return SubGhzProtocolStatusErrorValueBitCount;
}
flipper_format_rewind(flipper_format);
if(flipper_format_read_hex(
flipper_format, FIAT_V2_RAW_FIELD, instance->raw_data, FIAT_V2_WIRE_BYTES)) {
if(!fiat_v2_frame_valid(instance->raw_data)) {
return SubGhzProtocolStatusErrorParserOthers;
}
fiat_v2_decode_fields(instance);
return SubGhzProtocolStatusOk;
}
return SubGhzProtocolStatusErrorParserOthers;
}
void subghz_protocol_decoder_fiat_v2_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderFiatV2* instance = context;
furi_string_cat_printf(
output,
"%s %ubit\r\n"
"UID:%08lX\r\n"
"Hop:%08lX Type:%01X\r\n"
"Btn:%02X [%s] Cnt:%02lX\r\n",
instance->generic.protocol_name,
FIAT_V2_LOGICAL_BITS,
(unsigned long)instance->uid,
(unsigned long)instance->hop,
(unsigned)(instance->raw_data[6] >> 4),
instance->button,
fiat_v2_button_name(instance->button),
(unsigned long)instance->generic.cnt);
}
@@ -0,0 +1,31 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <flipper_format/flipper_format.h>
#include "../defines.h"
#define FIAT_V2_PROTOCOL_NAME "Fiat V2"
typedef struct SubGhzProtocolDecoderFiatV2 SubGhzProtocolDecoderFiatV2;
extern const SubGhzProtocol fiat_v2_protocol;
void* subghz_protocol_decoder_fiat_v2_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_v2_reset(void* context);
void subghz_protocol_decoder_fiat_v2_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_v2_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v2_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_fiat_v2_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_fiat_v2_get_string(void* context, FuriString* output);
@@ -61,7 +61,7 @@ typedef struct SubGhzProtocolDecoderFordV0 {
uint8_t button;
uint32_t count;
} SubGhzProtocolDecoderFordV0;
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
typedef struct SubGhzProtocolEncoderFordV0 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
@@ -94,7 +94,7 @@ static void decode_ford_v0(
uint32_t* serial,
uint8_t* button,
uint32_t* count);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void encode_ford_v0(
uint8_t header_byte,
uint32_t serial,
@@ -120,7 +120,7 @@ const SubGhzProtocolDecoder subghz_protocol_ford_v0_decoder = {
.get_string = subghz_protocol_decoder_ford_v0_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_ford_v0_encoder = {
.alloc = subghz_protocol_encoder_ford_v0_alloc,
.free = pp_encoder_free,
@@ -144,14 +144,22 @@ const SubGhzProtocol ford_protocol_v0 = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_ford_v0_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_ford_v0_encoder,
#else
.encoder = NULL,
#endif
};
// =============================================================================
// CHECKSUM CALCULATION
// =============================================================================
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint8_t ford_v0_calculate_checksum(uint32_t serial, uint32_t count, uint8_t button) {
return (uint8_t)((((count >> 24) & 0xFF) + ((count >> 16) & 0xFF) + ((count >> 8) & 0xFF) +
(count & 0xFF) + ((serial >> 24) & 0xFF) + ((serial >> 16) & 0xFF) +
@@ -179,7 +187,7 @@ static uint8_t ford_v0_calculate_crc(uint8_t* buf) {
return crc;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint8_t ford_v0_calculate_crc_for_tx(uint64_t key1, uint8_t checksum) {
uint8_t buf[16] = {0};
@@ -274,7 +282,7 @@ static void decode_ford_v0(
// =============================================================================
// ENCODE FUNCTION
// =============================================================================
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void encode_ford_v0(
uint8_t header_byte,
uint32_t serial,
@@ -357,7 +365,10 @@ static void encode_ford_v0(
void* subghz_protocol_encoder_ford_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderFordV0* instance = malloc(sizeof(SubGhzProtocolEncoderFordV0));
SubGhzProtocolEncoderFordV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderFordV0));
if(!instance) {
return NULL;
}
instance->base.protocol = &ford_protocol_v0;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -607,7 +618,10 @@ static bool ford_v0_process_data(SubGhzProtocolDecoderFordV0* instance) {
void* subghz_protocol_decoder_ford_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderFordV0* instance = malloc(sizeof(SubGhzProtocolDecoderFordV0));
SubGhzProtocolDecoderFordV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderFordV0));
if(!instance) {
return NULL;
}
instance->base.protocol = &ford_protocol_v0;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
@@ -66,7 +66,7 @@ static bool ford_v1_extract_plain_from_raw(
const uint8_t* raw17_in,
uint8_t* plain9_out,
uint8_t* raw17_canonical_out_opt);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void
ford_v1_plain_apply_fields(uint8_t* plain9, uint32_t serial, uint8_t btn, uint32_t cnt);
static void ford_v1_encoder_rebuild_raw_from_plain(uint8_t* raw17, const uint8_t* plain9);
@@ -83,7 +83,7 @@ const SubGhzProtocolDecoder subghz_protocol_ford_v1_decoder = {
.get_string = subghz_protocol_decoder_ford_v1_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_ford_v1_encoder = {
.alloc = subghz_protocol_encoder_ford_v1_alloc,
.free = pp_encoder_free,
@@ -106,12 +106,20 @@ const SubGhzProtocol ford_protocol_v1 = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
| SubGhzProtocolFlag_Send
#endif
,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_ford_v1_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_ford_v1_encoder,
#else
.encoder = NULL,
#endif
};
#define ford_v1_crc16(data, len) subghz_protocol_blocks_crc16((data), (len), 0x1021, 0x0000)
@@ -796,11 +804,15 @@ SubGhzProtocolStatus
if(ret == SubGhzProtocolStatusOk) {
flipper_format_rewind(flipper_format);
uint8_t key1_bytes[8] = {0};
flipper_format_read_hex(flipper_format, FF_KEY, key1_bytes, 8);
if(!flipper_format_read_hex(flipper_format, FF_KEY, key1_bytes, 8)) {
return SubGhzProtocolStatusErrorParserKey;
}
flipper_format_rewind(flipper_format);
uint8_t key2_bytes[8] = {0};
flipper_format_read_hex(flipper_format, "Key_2", key2_bytes, 8);
if(!flipper_format_read_hex(flipper_format, "Key_2", key2_bytes, 8)) {
return SubGhzProtocolStatusErrorParserOthers;
}
flipper_format_rewind(flipper_format);
uint8_t key3_bytes[4] = {0};
@@ -912,7 +924,7 @@ void subghz_protocol_decoder_ford_v1_get_string(void* context, FuriString* outpu
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
#define FORD_V1_ENC_BURST_COUNT 6U
#define FORD_V1_ENC_PREAMBLE_PAIRS 400U
@@ -1200,4 +1212,4 @@ SubGhzProtocolStatus
return ret;
}
#endif // ENABLE_EMULATE_FEATURE
#endif // PROTOPIRATE_WITH_ENCODER
@@ -32,7 +32,7 @@ SubGhzProtocolStatus
subghz_protocol_decoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_ford_v1_get_string(void* context, FuriString* output);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_ford_v1_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
subghz_protocol_encoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
@@ -16,7 +16,6 @@
#define FORD_V2_ENC_PREAMBLE_PAIRS 70U
#define FORD_V2_ENC_BURST_COUNT 6U
#define FORD_V2_ENC_INTER_BURST_GAP_US 16000U
#define FORD_V2_ENC_ALLOC_ELEMS 2600U
#define FORD_V2_ENC_SEPARATOR_ELEMS 2U
#define FORD_V2_ENC_PREAMBLE_ELEMS (FORD_V2_ENC_PREAMBLE_PAIRS * 2U)
#define FORD_V2_ENC_DATA_ELEMS ((FORD_V2_DATA_BITS - 1U) * 2U)
@@ -70,7 +69,7 @@ typedef struct SubGhzProtocolDecoderFordV2 {
bool structure_ok;
} SubGhzProtocolDecoderFordV2;
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
typedef struct SubGhzProtocolEncoderFordV2 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
@@ -125,7 +124,7 @@ static bool ford_v2_button_is_valid(uint8_t btn) {
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint8_t ford_v2_uint8_parity(uint8_t value) {
uint8_t parity = 0U;
while(value) {
@@ -350,7 +349,7 @@ static void ford_v2_decoder_rebuild_raw_buffer(SubGhzProtocolDecoderFordV2* inst
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static inline void ford_v2_encoder_add_level(
SubGhzProtocolEncoderFordV2* instance,
bool level,
@@ -360,7 +359,7 @@ static inline void ford_v2_encoder_add_level(
uint32_t prev = level_duration_get_duration(instance->encoder.upload[idx - 1]);
instance->encoder.upload[idx - 1] = level_duration_make(level, prev + duration);
} else {
furi_check(idx < FORD_V2_ENC_ALLOC_ELEMS);
furi_check(idx < FORD_V2_ENC_UPLOAD_ELEMS);
instance->encoder.upload[idx] = level_duration_make(level, duration);
instance->encoder.size_upload++;
}
@@ -486,11 +485,8 @@ static SubGhzProtocolStatus
static void ford_v2_encoder_deserialize_apply_repeat(
SubGhzProtocolEncoderFordV2* instance,
FlipperFormat* flipper_format) {
flipper_format_rewind(flipper_format);
uint32_t repeat = FORD_V2_ENCODER_DEFAULT_REPEAT;
if(flipper_format_read_uint32(flipper_format, "Repeat", &repeat, 1)) {
instance->encoder.repeat = repeat;
}
instance->encoder.repeat =
(int32_t)pp_encoder_read_repeat(flipper_format, FORD_V2_ENCODER_DEFAULT_REPEAT);
}
void* subghz_protocol_encoder_ford_v2_alloc(SubGhzEnvironment* environment) {
@@ -501,7 +497,7 @@ void* subghz_protocol_encoder_ford_v2_alloc(SubGhzEnvironment* environment) {
instance->base.protocol = &ford_protocol_v2;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = FORD_V2_ENCODER_DEFAULT_REPEAT;
instance->encoder.upload = calloc(FORD_V2_ENC_ALLOC_ELEMS, sizeof(LevelDuration));
instance->encoder.upload = calloc(FORD_V2_ENC_UPLOAD_ELEMS, sizeof(LevelDuration));
furi_check(instance->encoder.upload);
return instance;
@@ -797,7 +793,7 @@ const SubGhzProtocolDecoder subghz_protocol_ford_v2_decoder = {
.get_string = subghz_protocol_decoder_ford_v2_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_ford_v2_encoder = {
.alloc = subghz_protocol_encoder_ford_v2_alloc,
.free = subghz_protocol_encoder_ford_v2_free,
@@ -820,10 +816,18 @@ const SubGhzProtocol ford_protocol_v2 = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
| SubGhzProtocolFlag_Send
#endif
,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_ford_v2_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_ford_v2_encoder,
#else
.encoder = NULL,
#endif
};
@@ -30,7 +30,7 @@ SubGhzProtocolStatus
subghz_protocol_decoder_ford_v2_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_ford_v2_get_string(void* context, FuriString* output);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_ford_v2_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
subghz_protocol_encoder_ford_v2_deserialize(void* context, FlipperFormat* flipper_format);
@@ -3,16 +3,24 @@
#include "protocols_common.h"
#include <string.h>
#define FORD_V3_TE_SHORT 240U
#define FORD_V3_TE_LONG 480U
#define FORD_V3_TE_DELTA 60U
#define FORD_V3_DATA_BITS 104U
#define FORD_V3_DATA_BYTES 13U
#define FORD_V3_PREAMBLE_MIN 30U
#define FORD_V3_TE_SHORT 240U
#define FORD_V3_TE_LONG 480U
#define FORD_V3_TE_DELTA 60U
#define FORD_V3_CELL_TE_DELTA 120U
#define FORD_V3_DATA_BITS 104U
#define FORD_V3_DATA_BYTES 13U
#define FORD_V3_PREAMBLE_MIN 30U
#define FORD_V3_CELL_CAP 320U
#define FORD_V3_CELL_MIN 200U
#define FORD_V3_CELL_MIN_BITS 100U
#define FORD_V3_FF_VARIANT "Variant"
#define FORD_V3_BTN_LOCK 0x01U
#define FORD_V3_BTN_UNLOCK 0x02U
#define FORD_V3_VARIANT_EU 0U
#define FORD_V3_VARIANT_US 1U
static const SubGhzBlockConst subghz_protocol_ford_v3_const = {
.te_short = FORD_V3_TE_SHORT,
.te_long = FORD_V3_TE_LONG,
@@ -20,6 +28,13 @@ static const SubGhzBlockConst subghz_protocol_ford_v3_const = {
.min_count_bit_for_found = FORD_V3_DATA_BITS,
};
static const SubGhzBlockConst subghz_protocol_ford_v3_cell_const = {
.te_short = FORD_V3_TE_SHORT,
.te_long = FORD_V3_TE_LONG,
.te_delta = FORD_V3_CELL_TE_DELTA,
.min_count_bit_for_found = FORD_V3_DATA_BITS,
};
typedef enum {
FordV3DecoderStepReset = 0,
FordV3DecoderStepPreamble = 1,
@@ -32,21 +47,52 @@ typedef struct SubGhzProtocolDecoderFordV3 {
SubGhzBlockGeneric generic;
ManchesterState manchester_state;
uint8_t raw_bytes[FORD_V3_DATA_BYTES];
uint8_t bit_count;
uint8_t manchester_raw[FORD_V3_DATA_BYTES];
uint8_t manchester_bit_count;
uint16_t preamble_count;
uint8_t cells[FORD_V3_CELL_CAP];
uint16_t cell_count;
uint8_t raw_bytes[FORD_V3_DATA_BYTES];
uint8_t last_raw_bytes[FORD_V3_DATA_BYTES];
bool last_raw_valid;
uint8_t variant;
uint8_t flag;
uint32_t serial;
uint16_t counter;
} SubGhzProtocolDecoderFordV3;
static void ford_v3_reset_data(SubGhzProtocolDecoderFordV3* instance);
static void ford_v3_add_bit(SubGhzProtocolDecoderFordV3* instance, bool bit);
static void ford_v3_reset_manchester(SubGhzProtocolDecoderFordV3* instance);
static void ford_v3_reset_cells(SubGhzProtocolDecoderFordV3* instance);
static void ford_v3_add_manchester_bit(SubGhzProtocolDecoderFordV3* instance, bool bit);
static bool ford_v3_cell_frame_valid(const uint8_t* raw);
static uint8_t ford_v3_variant_from_saved_or_raw(const uint8_t* raw, uint32_t saved_variant);
static void ford_v3_parse_fields(SubGhzProtocolDecoderFordV3* instance);
static void ford_v3_emit_if_ready(SubGhzProtocolDecoderFordV3* instance);
static const char* ford_v3_button_name(uint8_t btn);
static bool ford_v3_commit_frame(
SubGhzProtocolDecoderFordV3* instance,
const uint8_t* raw,
uint8_t variant);
static void ford_v3_manchester_emit_if_ready(SubGhzProtocolDecoderFordV3* instance);
static void ford_v3_cell_process(SubGhzProtocolDecoderFordV3* instance);
static void ford_v3_cell_feed(SubGhzProtocolDecoderFordV3* instance, bool level, uint32_t duration);
static void
ford_v3_manchester_feed(SubGhzProtocolDecoderFordV3* instance, bool level, uint32_t duration);
static const char* ford_v3_button_name(uint8_t btn, uint8_t variant);
static const char* ford_v3_button_name(uint8_t btn, uint8_t variant) {
if(variant == FORD_V3_VARIANT_US) {
switch(btn) {
case FORD_V3_BTN_LOCK:
return "Lock";
case FORD_V3_BTN_UNLOCK:
return "Unlock";
default:
return "?";
}
}
static const char* ford_v3_button_name(uint8_t btn) {
switch(btn) {
case FORD_V3_BTN_LOCK:
return "Lock";
@@ -57,25 +103,62 @@ static const char* ford_v3_button_name(uint8_t btn) {
}
}
static void ford_v3_reset_data(SubGhzProtocolDecoderFordV3* instance) {
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
instance->bit_count = 0;
static bool ford_v3_cell_frame_valid(const uint8_t* raw) {
if(raw[0] != 0xFFU) {
return false;
}
const uint32_t serial = ((uint32_t)raw[1] << 24) | ((uint32_t)raw[2] << 16) |
((uint32_t)raw[3] << 8) | (uint32_t)raw[4];
if(serial == 0U || serial == 0xFFFFFFFFU) {
return false;
}
if(raw[6] != FORD_V3_BTN_LOCK && raw[6] != FORD_V3_BTN_UNLOCK) {
return false;
}
if((raw[5] & 0x80U) == 0U) {
return false;
}
return true;
}
static uint8_t ford_v3_variant_from_saved_or_raw(const uint8_t* raw, uint32_t saved_variant) {
if(saved_variant == FORD_V3_VARIANT_US) {
return FORD_V3_VARIANT_US;
}
if(saved_variant == FORD_V3_VARIANT_EU) {
return FORD_V3_VARIANT_EU;
}
return ford_v3_cell_frame_valid(raw) ? FORD_V3_VARIANT_US : FORD_V3_VARIANT_EU;
}
static void ford_v3_reset_manchester(SubGhzProtocolDecoderFordV3* instance) {
memset(instance->manchester_raw, 0, sizeof(instance->manchester_raw));
instance->manchester_bit_count = 0;
instance->preamble_count = 0;
manchester_advance(
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
}
static void ford_v3_add_bit(SubGhzProtocolDecoderFordV3* instance, bool bit) {
if(instance->bit_count >= FORD_V3_DATA_BITS) {
static void ford_v3_reset_cells(SubGhzProtocolDecoderFordV3* instance) {
instance->cell_count = 0;
}
static void ford_v3_add_manchester_bit(SubGhzProtocolDecoderFordV3* instance, bool bit) {
if(instance->manchester_bit_count >= FORD_V3_DATA_BITS) {
return;
}
const uint8_t byte_index = instance->bit_count / 8U;
const uint8_t bit_in_byte = 7U - (instance->bit_count % 8U);
const uint8_t byte_index = instance->manchester_bit_count / 8U;
const uint8_t bit_in_byte = 7U - (instance->manchester_bit_count % 8U);
if(bit) {
instance->raw_bytes[byte_index] |= (uint8_t)(1U << bit_in_byte);
instance->manchester_raw[byte_index] |= (uint8_t)(1U << bit_in_byte);
}
instance->bit_count++;
instance->manchester_bit_count++;
}
static void ford_v3_parse_fields(SubGhzProtocolDecoderFordV3* instance) {
@@ -83,55 +166,121 @@ static void ford_v3_parse_fields(SubGhzProtocolDecoderFordV3* instance) {
instance->serial = ((uint32_t)b[1] << 24) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 8) |
(uint32_t)b[4];
instance->counter = (uint16_t)((((uint16_t)(uint8_t)~b[7]) << 8) | (uint8_t)~b[8]);
instance->generic.serial = instance->serial;
instance->generic.btn = (b[6] & 0x01U) ? FORD_V3_BTN_UNLOCK : FORD_V3_BTN_LOCK;
if(instance->variant == FORD_V3_VARIANT_US) {
instance->flag = b[5];
instance->counter = (uint16_t)(((uint16_t)b[7] << 8) | (uint16_t)b[8]);
instance->generic.btn = b[6];
} else {
instance->flag = 0;
instance->counter = (uint16_t)((((uint16_t)(uint8_t)~b[7]) << 8) | (uint8_t)~b[8]);
instance->generic.btn = (b[6] & 0x01U) ? FORD_V3_BTN_UNLOCK : FORD_V3_BTN_LOCK;
}
instance->generic.cnt = instance->counter;
}
static void ford_v3_emit_if_ready(SubGhzProtocolDecoderFordV3* instance) {
if(instance->bit_count < FORD_V3_DATA_BITS) {
return;
static bool ford_v3_commit_frame(
SubGhzProtocolDecoderFordV3* instance,
const uint8_t* raw,
uint8_t variant) {
if(instance->last_raw_valid && memcmp(instance->last_raw_bytes, raw, FORD_V3_DATA_BYTES) == 0) {
return true;
}
memcpy(instance->raw_bytes, raw, FORD_V3_DATA_BYTES);
memcpy(instance->last_raw_bytes, raw, FORD_V3_DATA_BYTES);
instance->last_raw_valid = true;
instance->variant = variant;
instance->generic.data_count_bit = FORD_V3_DATA_BITS;
ford_v3_parse_fields(instance);
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
return true;
}
void* subghz_protocol_decoder_ford_v3_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
static void ford_v3_manchester_emit_if_ready(SubGhzProtocolDecoderFordV3* instance) {
if(instance->manchester_bit_count < FORD_V3_DATA_BITS) {
return;
}
SubGhzProtocolDecoderFordV3* instance = calloc(1, sizeof(SubGhzProtocolDecoderFordV3));
furi_check(instance);
instance->base.protocol = &ford_protocol_v3;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
(void)ford_v3_commit_frame(instance, instance->manchester_raw, FORD_V3_VARIANT_EU);
}
void subghz_protocol_decoder_ford_v3_reset(void* context) {
furi_check(context);
static bool ford_v3_cell_decode(const uint8_t* cells, uint16_t cell_count, uint8_t* raw_out) {
for(int phase = 0; phase < 2; phase++) {
uint8_t frame[FORD_V3_DATA_BYTES];
memset(frame, 0, sizeof(frame));
SubGhzProtocolDecoderFordV3* instance = context;
instance->decoder.parser_step = FordV3DecoderStepReset;
ford_v3_reset_data(instance);
int bit_count = 0;
bool ok = true;
for(int i = phase; (i + 1) < (int)cell_count && bit_count < (int)FORD_V3_DATA_BITS; i += 2) {
const uint8_t first = cells[i];
const uint8_t second = cells[i + 1];
if(first == second) {
ok = false;
break;
}
if(first) {
frame[bit_count >> 3] |= (uint8_t)(1U << (7 - (bit_count & 7)));
}
bit_count++;
}
if(!ok || bit_count < (int)FORD_V3_CELL_MIN_BITS) {
continue;
}
if(!ford_v3_cell_frame_valid(frame)) {
continue;
}
memcpy(raw_out, frame, FORD_V3_DATA_BYTES);
return true;
}
return false;
}
void subghz_protocol_decoder_ford_v3_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
static void ford_v3_cell_process(SubGhzProtocolDecoderFordV3* instance) {
if(instance->cell_count < FORD_V3_CELL_MIN) {
return;
}
SubGhzProtocolDecoderFordV3* instance = context;
uint8_t raw[FORD_V3_DATA_BYTES];
if(!ford_v3_cell_decode(instance->cells, instance->cell_count, raw)) {
return;
}
(void)ford_v3_commit_frame(instance, raw, FORD_V3_VARIANT_US);
}
static void ford_v3_cell_feed(SubGhzProtocolDecoderFordV3* instance, bool level, uint32_t duration) {
if(pp_is_short(duration, &subghz_protocol_ford_v3_cell_const)) {
if(instance->cell_count < FORD_V3_CELL_CAP) {
instance->cells[instance->cell_count++] = level ? 1U : 0U;
}
} else if(pp_is_long(duration, &subghz_protocol_ford_v3_cell_const)) {
if(instance->cell_count + 2U <= FORD_V3_CELL_CAP) {
instance->cells[instance->cell_count++] = level ? 1U : 0U;
instance->cells[instance->cell_count++] = level ? 1U : 0U;
}
} else {
ford_v3_cell_process(instance);
instance->cell_count = 0;
}
}
static void
ford_v3_manchester_feed(SubGhzProtocolDecoderFordV3* instance, bool level, uint32_t duration) {
switch(instance->decoder.parser_step) {
case FordV3DecoderStepReset:
if(pp_is_short(duration, &subghz_protocol_ford_v3_const)) {
ford_v3_reset_data(instance);
ford_v3_reset_manchester(instance);
instance->preamble_count = 1U;
instance->decoder.parser_step = FordV3DecoderStepPreamble;
}
@@ -151,7 +300,7 @@ void subghz_protocol_decoder_ford_v3_feed(void* context, bool level, uint32_t du
const bool valid = manchester_advance(
instance->manchester_state, event, &instance->manchester_state, &data_bit);
if(valid) {
ford_v3_add_bit(instance, data_bit);
ford_v3_add_manchester_bit(instance, data_bit);
}
instance->decoder.parser_step = FordV3DecoderStepData;
} else {
@@ -159,14 +308,14 @@ void subghz_protocol_decoder_ford_v3_feed(void* context, bool level, uint32_t du
}
break;
case FordV3DecoderStepData: {
case FordV3DecoderStepData:
if(!pp_is_short(duration, &subghz_protocol_ford_v3_const) &&
!pp_is_long(duration, &subghz_protocol_ford_v3_const)) {
ford_v3_emit_if_ready(instance);
ford_v3_manchester_emit_if_ready(instance);
instance->decoder.parser_step = FordV3DecoderStepReset;
if(pp_is_short(duration, &subghz_protocol_ford_v3_const)) {
ford_v3_reset_data(instance);
ford_v3_reset_manchester(instance);
instance->preamble_count = 1U;
instance->decoder.parser_step = FordV3DecoderStepPreamble;
}
@@ -189,15 +338,44 @@ void subghz_protocol_decoder_ford_v3_feed(void* context, bool level, uint32_t du
instance->manchester_state, event, &instance->manchester_state, &data_bit);
if(valid) {
ford_v3_add_bit(instance, data_bit);
if(instance->bit_count >= FORD_V3_DATA_BITS) {
ford_v3_emit_if_ready(instance);
ford_v3_add_manchester_bit(instance, data_bit);
if(instance->manchester_bit_count >= FORD_V3_DATA_BITS) {
ford_v3_manchester_emit_if_ready(instance);
instance->decoder.parser_step = FordV3DecoderStepReset;
}
}
break;
}
}
}
void* subghz_protocol_decoder_ford_v3_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderFordV3* instance = calloc(1, sizeof(SubGhzProtocolDecoderFordV3));
furi_check(instance);
instance->base.protocol = &ford_protocol_v3;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_ford_v3_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderFordV3* instance = context;
instance->decoder.parser_step = FordV3DecoderStepReset;
ford_v3_reset_manchester(instance);
ford_v3_reset_cells(instance);
instance->last_raw_valid = false;
}
void subghz_protocol_decoder_ford_v3_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderFordV3* instance = context;
ford_v3_cell_feed(instance, level, duration);
ford_v3_manchester_feed(instance, level, duration);
}
uint8_t subghz_protocol_decoder_ford_v3_get_hash_data(void* context) {
@@ -239,6 +417,7 @@ SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_serialize(
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->generic.serial);
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->generic.btn);
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->counter);
pp_flipper_update_or_insert_u32(flipper_format, FORD_V3_FF_VARIANT, instance->variant);
}
return ret;
@@ -266,7 +445,13 @@ SubGhzProtocolStatus
flipper_format_rewind(flipper_format);
flipper_format_read_hex(flipper_format, "Raw", instance->raw_bytes, FORD_V3_DATA_BYTES);
instance->bit_count = FORD_V3_DATA_BITS;
uint32_t variant = UINT32_MAX;
if(!flipper_format_read_uint32(flipper_format, FORD_V3_FF_VARIANT, &variant, 1)) {
variant = UINT32_MAX;
}
instance->variant = ford_v3_variant_from_saved_or_raw(instance->raw_bytes, variant);
instance->manchester_bit_count = FORD_V3_DATA_BITS;
ford_v3_parse_fields(instance);
return ret;
@@ -278,6 +463,39 @@ void subghz_protocol_decoder_ford_v3_get_string(void* context, FuriString* outpu
SubGhzProtocolDecoderFordV3* instance = context;
const uint8_t* k = instance->raw_bytes;
if(instance->variant == FORD_V3_VARIANT_US) {
furi_string_cat_printf(
output,
"%s US %dbit\r\n"
"Key:%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\r\n"
"Sn:%08lX Btn:%02X %s\r\n"
"Cnt:%04X Hop:%02X%02X%02X%02X\r\n",
instance->generic.protocol_name,
(int)instance->generic.data_count_bit,
k[0],
k[1],
k[2],
k[3],
k[4],
k[5],
k[6],
k[7],
k[8],
k[9],
k[10],
k[11],
k[12],
(unsigned long)instance->generic.serial,
instance->generic.btn,
ford_v3_button_name(instance->generic.btn, FORD_V3_VARIANT_US),
(unsigned)instance->counter,
k[9],
k[10],
k[11],
k[12]);
return;
}
furi_string_cat_printf(
output,
"%s %dbit\r\n"
@@ -301,7 +519,7 @@ void subghz_protocol_decoder_ford_v3_get_string(void* context, FuriString* outpu
k[12],
(unsigned long)instance->generic.serial,
instance->generic.btn,
ford_v3_button_name(instance->generic.btn),
ford_v3_button_name(instance->generic.btn, FORD_V3_VARIANT_EU),
(unsigned)instance->counter,
k[9],
k[10],
@@ -331,8 +549,17 @@ const SubGhzProtocolEncoder subghz_protocol_ford_v3_encoder = {
const SubGhzProtocol ford_protocol_v3 = {
.name = FORD_PROTOCOL_V3_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_ford_v3_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_ford_v3_encoder,
#else
.encoder = NULL,
#endif
};
@@ -20,7 +20,7 @@ _Static_assert(
HONDA_STATIC_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
"HONDA_STATIC_UPLOAD_CAPACITY exceeds shared upload slab");
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static const uint8_t honda_static_encoder_button_map[4] = {0x02, 0x04, 0x08, 0x05};
#endif
static const char* const honda_static_button_names[9] = {
@@ -50,7 +50,7 @@ struct SubGhzProtocolDecoderHondaStatic {
uint16_t symbols_count;
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
struct SubGhzProtocolEncoderHondaStatic {
SubGhzProtocolEncoderBase base;
@@ -92,7 +92,7 @@ static uint32_t honda_static_get_bits_u32(const uint8_t* data, uint8_t start, ui
return value;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void honda_static_set_bits(uint8_t* data, uint8_t start, uint8_t count, uint32_t value) {
for(uint8_t i = 0; i < count; i++) {
const uint8_t bit_index = start + i;
@@ -143,7 +143,7 @@ static bool honda_static_is_valid_serial(uint32_t serial) {
return (serial != 0U) && (serial != 0x0FFFFFFFU);
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint8_t honda_static_encoder_remap_button(uint8_t button) {
if(button < 2U) {
return 1U;
@@ -212,7 +212,7 @@ static uint64_t honda_static_pack_compact(const HondaStaticFields* fields) {
return pp_bytes_to_u64_be(compact);
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void honda_static_build_packet_bytes(const HondaStaticFields* fields, uint8_t packet[8]) {
memset(packet, 0, 8);
@@ -408,7 +408,7 @@ static void honda_static_decoder_commit(
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void honda_static_build_upload(SubGhzProtocolEncoderHondaStatic* instance) {
uint8_t packet[8];
honda_static_build_packet_bytes(&instance->decoded, packet);
@@ -449,7 +449,7 @@ const SubGhzProtocolDecoder subghz_protocol_honda_static_decoder = {
.get_string = subghz_protocol_decoder_honda_static_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_honda_static_encoder = {
.alloc = subghz_protocol_encoder_honda_static_alloc,
.free = pp_encoder_free,
@@ -473,11 +473,19 @@ const SubGhzProtocol honda_static_protocol = {
.flag = SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_honda_static_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_honda_static_encoder,
#else
.encoder = NULL,
#endif
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
@@ -30,7 +30,7 @@ SubGhzProtocolStatus
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
@@ -62,7 +62,7 @@ static const char* const honda_v1_button_names[HONDA_V1_BUTTON_MAX + 1U] = {
[HondaV1ButtonPanic] = "Panic",
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static const uint32_t honda_v1_button_codes[HONDA_V1_BUTTON_MAX + 1U] = {
[HondaV1ButtonUnlock] = 0x00080808,
[HondaV1ButtonLock] = 0x00088888,
@@ -87,7 +87,7 @@ struct SubGhzProtocolDecoderHondaV1 {
uint8_t k2;
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
struct SubGhzProtocolEncoderHondaV1 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
@@ -109,7 +109,7 @@ static const char* honda_v1_button_name(uint8_t b) {
return "Unknown";
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint32_t honda_v1_button_code(uint8_t button) {
if(!honda_v1_button_valid(button)) {
return HONDA_V1_BUTTON_FALLBACK_CODE;
@@ -177,7 +177,7 @@ static void honda_v1_decode_fields(SubGhzBlockGeneric* generic) {
generic->data_count_bit = HONDA_V1_BIT_COUNT;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint64_t honda_v1_build_key(uint32_t serial, uint8_t button, uint16_t counter) {
const uint32_t table = honda_v1_button_code(button);
const uint32_t low = ((table & HONDA_V1_COUNTER_MASK) << 16U) | counter;
@@ -306,7 +306,7 @@ static void
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static bool honda_v1_append_frame(
SubGhzProtocolEncoderHondaV1* instance,
size_t* index,
@@ -407,7 +407,7 @@ const SubGhzProtocolDecoder subghz_protocol_honda_v1_decoder = {
.get_string = subghz_protocol_decoder_honda_v1_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_honda_v1_encoder = {
.alloc = subghz_protocol_encoder_honda_v1_alloc,
.free = pp_encoder_free,
@@ -430,15 +430,23 @@ const SubGhzProtocol honda_v1_protocol = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
| SubGhzProtocolFlag_Send
#endif
,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_honda_v1_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_honda_v1_encoder,
#else
.encoder = NULL,
#endif
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_honda_v1_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
@@ -29,7 +29,7 @@ SubGhzProtocolStatus
subghz_protocol_decoder_honda_v1_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_honda_v1_get_string(void* context, FuriString* output);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_honda_v1_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
subghz_protocol_encoder_honda_v1_deserialize(void* context, FlipperFormat* flipper_format);
@@ -1,40 +1,40 @@
#include "land_rover_v0.h"
#include "honda_v2.h"
#include "protocols_common.h"
#include <string.h>
#define TAG "LandRoverV0"
#define TAG "HondaV2"
static const SubGhzBlockConst subghz_protocol_land_rover_v0_const = {
static const SubGhzBlockConst subghz_protocol_honda_v2_const = {
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit_for_found = 81,
};
#define LAND_ROVER_V0_PREAMBLE_PAIRS 319U
#define LAND_ROVER_V0_MIN_PREAMBLE_PAIRS 64U
#define LAND_ROVER_V0_SYNC_US 750U
#define LAND_ROVER_V0_SYNC_DELTA_US 120U
#define LAND_ROVER_V0_UPLOAD_CAPACITY 1024U
#define LAND_ROVER_V0_GAP_US 50000U
#define HONDA_V2_PREAMBLE_PAIRS 319U
#define HONDA_V2_MIN_PREAMBLE_PAIRS 64U
#define HONDA_V2_SYNC_US 750U
#define HONDA_V2_SYNC_DELTA_US 120U
#define HONDA_V2_UPLOAD_CAPACITY 1024U
#define HONDA_V2_GAP_US 50000U
_Static_assert(
LAND_ROVER_V0_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
"LAND_ROVER_V0_UPLOAD_CAPACITY exceeds shared upload slab");
HONDA_V2_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
"HONDA_V2_UPLOAD_CAPACITY exceeds shared upload slab");
#define LAND_ROVER_V0_BTN_UNKNOWN 0x00U
#define LAND_ROVER_V0_BTN_LOCK 0x02U
#define LAND_ROVER_V0_BTN_UNLOCK 0x04U
#define HONDA_V2_BTN_UNKNOWN 0x00U
#define HONDA_V2_BTN_LOCK 0x02U
#define HONDA_V2_BTN_UNLOCK 0x04U
#define LAND_ROVER_V0_SIG_UNLOCK 0xA285E3UL
#define LAND_ROVER_V0_SIG_LOCK 0xC20363UL
#define HONDA_V2_SIG_UNLOCK 0xA285E3UL
#define HONDA_V2_SIG_LOCK 0xC20363UL
#define LAND_ROVER_V0_FF_BTNSIG "BtnSig"
#define LAND_ROVER_V0_FF_CHECK "Check"
#define LAND_ROVER_V0_FF_TAIL "Tail"
#define LAND_ROVER_V0_FF_EXTRA_BIT "ExtraBit"
#define HONDA_V2_FF_BTNSIG "BtnSig"
#define HONDA_V2_FF_CHECK "Check"
#define HONDA_V2_FF_TAIL "Tail"
#define HONDA_V2_FF_EXTRA_BIT "ExtraBit"
typedef struct SubGhzProtocolDecoderLandRoverV0 {
typedef struct SubGhzProtocolDecoderHondaV2 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
@@ -56,10 +56,10 @@ typedef struct SubGhzProtocolDecoderLandRoverV0 {
uint8_t check;
bool check_ok;
bool tail_ok;
} SubGhzProtocolDecoderLandRoverV0;
} SubGhzProtocolDecoderHondaV2;
#ifdef ENABLE_EMULATE_FEATURE
typedef struct SubGhzProtocolEncoderLandRoverV0 {
#if PROTOPIRATE_WITH_ENCODER
typedef struct SubGhzProtocolEncoderHondaV2 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
@@ -71,77 +71,77 @@ typedef struct SubGhzProtocolEncoderLandRoverV0 {
uint32_t count;
uint8_t button;
uint8_t check;
} SubGhzProtocolEncoderLandRoverV0;
} SubGhzProtocolEncoderHondaV2;
#endif
typedef enum {
LandRoverV0DecoderStepReset = 0,
LandRoverV0DecoderStepPreambleLow,
LandRoverV0DecoderStepPreambleHigh,
LandRoverV0DecoderStepSyncLow,
LandRoverV0DecoderStepData,
} LandRoverV0DecoderStep;
HondaV2DecoderStepReset = 0,
HondaV2DecoderStepPreambleLow,
HondaV2DecoderStepPreambleHigh,
HondaV2DecoderStepSyncLow,
HondaV2DecoderStepData,
} HondaV2DecoderStep;
static uint8_t land_rover_v0_button_from_signature(uint32_t signature);
static const char* land_rover_v0_button_name(uint8_t button);
static uint8_t land_rover_v0_calculate_check(uint32_t count);
static bool land_rover_v0_calculate_tail_msb(uint32_t count);
static uint16_t land_rover_v0_calculate_tail(uint32_t count);
static void land_rover_v0_parse_key_fields(
static uint8_t honda_v2_button_from_signature(uint32_t signature);
static const char* honda_v2_button_name(uint8_t button);
static uint8_t honda_v2_calculate_check(uint32_t count);
static bool honda_v2_calculate_tail_msb(uint32_t count);
static uint16_t honda_v2_calculate_tail(uint32_t count);
static void honda_v2_parse_key_fields(
uint64_t key,
uint32_t* signature,
uint32_t* serial,
uint32_t* count,
uint8_t* button,
uint8_t* check);
static bool land_rover_v0_validate_frame(
static bool honda_v2_validate_frame(
uint64_t key,
uint16_t tail,
bool extra_bit,
bool* check_ok,
bool* tail_ok);
static bool land_rover_v0_add_decoded_bit(SubGhzProtocolDecoderLandRoverV0* instance, bool bit);
static bool land_rover_v0_process_transition(
SubGhzProtocolDecoderLandRoverV0* instance,
static bool honda_v2_add_decoded_bit(SubGhzProtocolDecoderHondaV2* instance, bool bit);
static bool honda_v2_process_transition(
SubGhzProtocolDecoderHondaV2* instance,
bool level,
uint32_t duration);
static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instance);
static bool honda_v2_finish_frame(SubGhzProtocolDecoderHondaV2* instance);
#ifdef ENABLE_EMULATE_FEATURE
static bool land_rover_v0_encoder_add_level(
SubGhzProtocolEncoderLandRoverV0* instance,
#if PROTOPIRATE_WITH_ENCODER
static bool honda_v2_encoder_add_level(
SubGhzProtocolEncoderHondaV2* instance,
size_t* index,
bool level,
uint32_t duration);
static bool land_rover_v0_encoder_add_bit(
SubGhzProtocolEncoderLandRoverV0* instance,
static bool honda_v2_encoder_add_bit(
SubGhzProtocolEncoderHondaV2* instance,
size_t* index,
bool* previous_bit,
bool bit);
static bool land_rover_v0_build_upload(SubGhzProtocolEncoderLandRoverV0* instance);
static bool honda_v2_build_upload(SubGhzProtocolEncoderHondaV2* instance);
#endif
const SubGhzProtocolDecoder subghz_protocol_land_rover_v0_decoder = {
.alloc = subghz_protocol_decoder_land_rover_v0_alloc,
.free = subghz_protocol_decoder_land_rover_v0_free,
.feed = subghz_protocol_decoder_land_rover_v0_feed,
.reset = subghz_protocol_decoder_land_rover_v0_reset,
.get_hash_data = subghz_protocol_decoder_land_rover_v0_get_hash_data,
.serialize = subghz_protocol_decoder_land_rover_v0_serialize,
.deserialize = subghz_protocol_decoder_land_rover_v0_deserialize,
.get_string = subghz_protocol_decoder_land_rover_v0_get_string,
const SubGhzProtocolDecoder subghz_protocol_honda_v2_decoder = {
.alloc = subghz_protocol_decoder_honda_v2_alloc,
.free = subghz_protocol_decoder_honda_v2_free,
.feed = subghz_protocol_decoder_honda_v2_feed,
.reset = subghz_protocol_decoder_honda_v2_reset,
.get_hash_data = subghz_protocol_decoder_honda_v2_get_hash_data,
.serialize = subghz_protocol_decoder_honda_v2_serialize,
.deserialize = subghz_protocol_decoder_honda_v2_deserialize,
.get_string = subghz_protocol_decoder_honda_v2_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
const SubGhzProtocolEncoder subghz_protocol_land_rover_v0_encoder = {
.alloc = subghz_protocol_encoder_land_rover_v0_alloc,
.free = subghz_protocol_encoder_land_rover_v0_free,
.deserialize = subghz_protocol_encoder_land_rover_v0_deserialize,
.stop = subghz_protocol_encoder_land_rover_v0_stop,
.yield = subghz_protocol_encoder_land_rover_v0_yield,
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_honda_v2_encoder = {
.alloc = subghz_protocol_encoder_honda_v2_alloc,
.free = subghz_protocol_encoder_honda_v2_free,
.deserialize = subghz_protocol_encoder_honda_v2_deserialize,
.stop = subghz_protocol_encoder_honda_v2_stop,
.yield = subghz_protocol_encoder_honda_v2_yield,
};
#else
const SubGhzProtocolEncoder subghz_protocol_land_rover_v0_encoder = {
const SubGhzProtocolEncoder subghz_protocol_honda_v2_encoder = {
.alloc = NULL,
.free = NULL,
.deserialize = NULL,
@@ -150,49 +150,57 @@ const SubGhzProtocolEncoder subghz_protocol_land_rover_v0_encoder = {
};
#endif
const SubGhzProtocol land_rover_v0_protocol = {
.name = LAND_ROVER_PROTOCOL_V0_NAME,
const SubGhzProtocol honda_v2_protocol = {
.name = HONDA_V2_PROTOCOL_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_land_rover_v0_decoder,
.encoder = &subghz_protocol_land_rover_v0_encoder,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_honda_v2_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_honda_v2_encoder,
#else
.encoder = NULL,
#endif
};
static bool land_rover_v0_is_short(uint32_t duration) {
return pp_is_short(duration, &subghz_protocol_land_rover_v0_const);
static bool honda_v2_is_short(uint32_t duration) {
return pp_is_short(duration, &subghz_protocol_honda_v2_const);
}
static bool land_rover_v0_is_long(uint32_t duration) {
return pp_is_long(duration, &subghz_protocol_land_rover_v0_const);
static bool honda_v2_is_long(uint32_t duration) {
return pp_is_long(duration, &subghz_protocol_honda_v2_const);
}
static bool land_rover_v0_is_sync(uint32_t duration) {
return DURATION_DIFF(duration, LAND_ROVER_V0_SYNC_US) < LAND_ROVER_V0_SYNC_DELTA_US;
static bool honda_v2_is_sync(uint32_t duration) {
return DURATION_DIFF(duration, HONDA_V2_SYNC_US) < HONDA_V2_SYNC_DELTA_US;
}
static uint8_t land_rover_v0_button_from_signature(uint32_t signature) {
if(signature == LAND_ROVER_V0_SIG_UNLOCK) {
return LAND_ROVER_V0_BTN_UNLOCK;
} else if(signature == LAND_ROVER_V0_SIG_LOCK) {
return LAND_ROVER_V0_BTN_LOCK;
static uint8_t honda_v2_button_from_signature(uint32_t signature) {
if(signature == HONDA_V2_SIG_UNLOCK) {
return HONDA_V2_BTN_UNLOCK;
} else if(signature == HONDA_V2_SIG_LOCK) {
return HONDA_V2_BTN_LOCK;
}
return LAND_ROVER_V0_BTN_UNKNOWN;
return HONDA_V2_BTN_UNKNOWN;
}
static const char* land_rover_v0_button_name(uint8_t button) {
static const char* honda_v2_button_name(uint8_t button) {
switch(button) {
case LAND_ROVER_V0_BTN_LOCK:
case HONDA_V2_BTN_LOCK:
return "Lock";
case LAND_ROVER_V0_BTN_UNLOCK:
case HONDA_V2_BTN_UNLOCK:
return "Unlock";
default:
return "Unknown";
}
}
static uint8_t land_rover_v0_calculate_check(uint32_t count) {
static uint8_t honda_v2_calculate_check(uint32_t count) {
const uint8_t c0 = ((count >> 1) ^ (count >> 2) ^ (count >> 3) ^ (count >> 4) ^ (count >> 6)) &
1U;
const uint8_t c1 = ((count >> 0) ^ (count >> 2) ^ (count >> 3) ^ (count >> 4) ^ (count >> 5) ^
@@ -204,16 +212,16 @@ static uint8_t land_rover_v0_calculate_check(uint32_t count) {
return (uint8_t)(c0 | (c1 << 1) | (c2 << 2));
}
static bool land_rover_v0_calculate_tail_msb(uint32_t count) {
static bool honda_v2_calculate_tail_msb(uint32_t count) {
const uint8_t tail = ((count >> 0) ^ (count >> 2) ^ (count >> 4) ^ (count >> 5)) & 1U;
return tail != 0U;
}
static uint16_t land_rover_v0_calculate_tail(uint32_t count) {
return land_rover_v0_calculate_tail_msb(count) ? 0xFFFFU : 0x7FFFU;
static uint16_t honda_v2_calculate_tail(uint32_t count) {
return honda_v2_calculate_tail_msb(count) ? 0xFFFFU : 0x7FFFU;
}
static void land_rover_v0_parse_key_fields(
static void honda_v2_parse_key_fields(
uint64_t key,
uint32_t* signature,
uint32_t* serial,
@@ -232,11 +240,11 @@ static void land_rover_v0_parse_key_fields(
if(signature) *signature = sig;
if(serial) *serial = sn;
if(count) *count = cnt;
if(button) *button = land_rover_v0_button_from_signature(sig);
if(button) *button = honda_v2_button_from_signature(sig);
if(check) *check = key_bytes[7] & 0x07U;
}
static bool land_rover_v0_validate_frame(
static bool honda_v2_validate_frame(
uint64_t key,
uint16_t tail,
bool extra_bit,
@@ -246,8 +254,8 @@ static bool land_rover_v0_validate_frame(
pp_u64_to_bytes_be(key, key_bytes);
const uint32_t count = ((uint32_t)key_bytes[6] << 1) | ((key_bytes[7] >> 7) & 1U);
const uint8_t expected_check = land_rover_v0_calculate_check(count);
const uint16_t expected_tail = land_rover_v0_calculate_tail(count);
const uint8_t expected_check = honda_v2_calculate_check(count);
const uint16_t expected_tail = honda_v2_calculate_tail(count);
const bool local_check_ok = ((key_bytes[7] & 0x78U) == 0U) &&
((key_bytes[7] & 0x07U) == expected_check);
@@ -259,7 +267,7 @@ static bool land_rover_v0_validate_frame(
return local_check_ok && local_tail_ok;
}
static bool land_rover_v0_add_decoded_bit(SubGhzProtocolDecoderLandRoverV0* instance, bool bit) {
static bool honda_v2_add_decoded_bit(SubGhzProtocolDecoderHondaV2* instance, bool bit) {
if(instance->bit_count < 80U) {
const uint8_t byte_index = instance->bit_count / 8U;
const uint8_t bit_index = 7U - (instance->bit_count % 8U);
@@ -276,11 +284,11 @@ static bool land_rover_v0_add_decoded_bit(SubGhzProtocolDecoderLandRoverV0* inst
return true;
}
static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instance) {
static bool honda_v2_finish_frame(SubGhzProtocolDecoderHondaV2* instance) {
const uint64_t key = pp_bytes_to_u64_be(instance->raw);
const uint16_t tail = ((uint16_t)instance->raw[8] << 8) | instance->raw[9];
if(!land_rover_v0_validate_frame(
if(!honda_v2_validate_frame(
key, tail, instance->extra_bit, &instance->check_ok, &instance->tail_ok)) {
return false;
}
@@ -288,7 +296,7 @@ static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instanc
instance->key = key;
instance->tail = tail;
land_rover_v0_parse_key_fields(
honda_v2_parse_key_fields(
key,
&instance->command_signature,
&instance->serial,
@@ -297,7 +305,7 @@ static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instanc
&instance->check);
instance->generic.data = instance->key;
instance->generic.data_count_bit = subghz_protocol_land_rover_v0_const.min_count_bit_for_found;
instance->generic.data_count_bit = subghz_protocol_honda_v2_const.min_count_bit_for_found;
instance->generic.serial = instance->serial;
instance->generic.btn = instance->button;
instance->generic.cnt = instance->count;
@@ -305,12 +313,12 @@ static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instanc
return true;
}
static bool land_rover_v0_process_transition(
SubGhzProtocolDecoderLandRoverV0* instance,
static bool honda_v2_process_transition(
SubGhzProtocolDecoderHondaV2* instance,
bool level,
uint32_t duration) {
if(!instance->boundary_pad_skipped) {
if(level && land_rover_v0_is_short(duration)) {
if(level && honda_v2_is_short(duration)) {
instance->boundary_pad_skipped = true;
return true;
}
@@ -318,31 +326,31 @@ static bool land_rover_v0_process_transition(
}
if(instance->pending_short) {
if(!instance->previous_bit && !level && land_rover_v0_is_short(duration)) {
if(!instance->previous_bit && !level && honda_v2_is_short(duration)) {
instance->pending_short = false;
return land_rover_v0_add_decoded_bit(instance, false);
} else if(instance->previous_bit && level && land_rover_v0_is_short(duration)) {
return honda_v2_add_decoded_bit(instance, false);
} else if(instance->previous_bit && level && honda_v2_is_short(duration)) {
instance->pending_short = false;
return land_rover_v0_add_decoded_bit(instance, true);
return honda_v2_add_decoded_bit(instance, true);
}
return false;
}
if(!instance->previous_bit) {
if(level && land_rover_v0_is_long(duration)) {
if(level && honda_v2_is_long(duration)) {
instance->previous_bit = true;
return land_rover_v0_add_decoded_bit(instance, true);
} else if(level && land_rover_v0_is_short(duration)) {
return honda_v2_add_decoded_bit(instance, true);
} else if(level && honda_v2_is_short(duration)) {
instance->pending_short = true;
return true;
}
return false;
}
if(!level && land_rover_v0_is_long(duration)) {
if(!level && honda_v2_is_long(duration)) {
instance->previous_bit = false;
return land_rover_v0_add_decoded_bit(instance, false);
} else if(!level && land_rover_v0_is_short(duration)) {
return honda_v2_add_decoded_bit(instance, false);
} else if(!level && honda_v2_is_short(duration)) {
instance->pending_short = true;
return true;
}
@@ -350,43 +358,43 @@ static bool land_rover_v0_process_transition(
return false;
}
#ifdef ENABLE_EMULATE_FEATURE
static bool land_rover_v0_encoder_add_level(
SubGhzProtocolEncoderLandRoverV0* instance,
#if PROTOPIRATE_WITH_ENCODER
static bool honda_v2_encoder_add_level(
SubGhzProtocolEncoderHondaV2* instance,
size_t* index,
bool level,
uint32_t duration) {
if(*index >= LAND_ROVER_V0_UPLOAD_CAPACITY) {
if(*index >= HONDA_V2_UPLOAD_CAPACITY) {
return false;
}
instance->encoder.upload[(*index)++] = level_duration_make(level, duration);
return true;
}
static bool land_rover_v0_encoder_add_bit(
SubGhzProtocolEncoderLandRoverV0* instance,
static bool honda_v2_encoder_add_bit(
SubGhzProtocolEncoderHondaV2* instance,
size_t* index,
bool* previous_bit,
bool bit) {
const uint32_t te_short = subghz_protocol_land_rover_v0_const.te_short;
const uint32_t te_long = subghz_protocol_land_rover_v0_const.te_long;
const uint32_t te_short = subghz_protocol_honda_v2_const.te_short;
const uint32_t te_long = subghz_protocol_honda_v2_const.te_long;
if(!*previous_bit && !bit) {
if(!land_rover_v0_encoder_add_level(instance, index, true, te_short) ||
!land_rover_v0_encoder_add_level(instance, index, false, te_short)) {
if(!honda_v2_encoder_add_level(instance, index, true, te_short) ||
!honda_v2_encoder_add_level(instance, index, false, te_short)) {
return false;
}
} else if(!*previous_bit && bit) {
if(!land_rover_v0_encoder_add_level(instance, index, true, te_long)) {
if(!honda_v2_encoder_add_level(instance, index, true, te_long)) {
return false;
}
} else if(*previous_bit && !bit) {
if(!land_rover_v0_encoder_add_level(instance, index, false, te_long)) {
if(!honda_v2_encoder_add_level(instance, index, false, te_long)) {
return false;
}
} else {
if(!land_rover_v0_encoder_add_level(instance, index, false, te_short) ||
!land_rover_v0_encoder_add_level(instance, index, true, te_short)) {
if(!honda_v2_encoder_add_level(instance, index, false, te_short) ||
!honda_v2_encoder_add_level(instance, index, true, te_short)) {
return false;
}
}
@@ -395,30 +403,30 @@ static bool land_rover_v0_encoder_add_bit(
return true;
}
static bool land_rover_v0_build_upload(SubGhzProtocolEncoderLandRoverV0* instance) {
static bool honda_v2_build_upload(SubGhzProtocolEncoderHondaV2* instance) {
furi_check(instance);
size_t index = 0;
const uint32_t te_short = subghz_protocol_land_rover_v0_const.te_short;
const uint32_t te_short = subghz_protocol_honda_v2_const.te_short;
uint8_t key_bytes[8];
pp_u64_to_bytes_be(instance->key, key_bytes);
for(uint16_t i = 0; i < LAND_ROVER_V0_PREAMBLE_PAIRS; i++) {
if(!land_rover_v0_encoder_add_level(instance, &index, true, te_short) ||
!land_rover_v0_encoder_add_level(instance, &index, false, te_short)) {
for(uint16_t i = 0; i < HONDA_V2_PREAMBLE_PAIRS; i++) {
if(!honda_v2_encoder_add_level(instance, &index, true, te_short) ||
!honda_v2_encoder_add_level(instance, &index, false, te_short)) {
return false;
}
}
if(!land_rover_v0_encoder_add_level(instance, &index, true, LAND_ROVER_V0_SYNC_US) ||
!land_rover_v0_encoder_add_level(instance, &index, false, LAND_ROVER_V0_SYNC_US) ||
!land_rover_v0_encoder_add_level(instance, &index, true, te_short)) {
if(!honda_v2_encoder_add_level(instance, &index, true, HONDA_V2_SYNC_US) ||
!honda_v2_encoder_add_level(instance, &index, false, HONDA_V2_SYNC_US) ||
!honda_v2_encoder_add_level(instance, &index, true, te_short)) {
return false;
}
bool previous_bit = true;
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, false)) {
if(!honda_v2_encoder_add_bit(instance, &index, &previous_bit, false)) {
return false;
}
@@ -426,24 +434,24 @@ static bool land_rover_v0_build_upload(SubGhzProtocolEncoderLandRoverV0* instanc
const uint8_t byte_index = bit_index / 8U;
const uint8_t bit_in_byte = 7U - (bit_index % 8U);
const bool bit = (key_bytes[byte_index] >> bit_in_byte) & 1U;
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, bit)) {
if(!honda_v2_encoder_add_bit(instance, &index, &previous_bit, bit)) {
return false;
}
}
instance->tail = land_rover_v0_calculate_tail(instance->count);
instance->tail = honda_v2_calculate_tail(instance->count);
for(uint8_t bit_index = 0; bit_index < 16; bit_index++) {
const bool bit = (instance->tail >> (15U - bit_index)) & 1U;
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, bit)) {
if(!honda_v2_encoder_add_bit(instance, &index, &previous_bit, bit)) {
return false;
}
}
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, true)) {
if(!honda_v2_encoder_add_bit(instance, &index, &previous_bit, true)) {
return false;
}
if(!land_rover_v0_encoder_add_level(instance, &index, false, LAND_ROVER_V0_GAP_US)) {
if(!honda_v2_encoder_add_level(instance, &index, false, HONDA_V2_GAP_US)) {
return false;
}
@@ -453,29 +461,29 @@ static bool land_rover_v0_build_upload(SubGhzProtocolEncoderLandRoverV0* instanc
}
#endif
void* subghz_protocol_decoder_land_rover_v0_alloc(SubGhzEnvironment* environment) {
void* subghz_protocol_decoder_honda_v2_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderLandRoverV0* instance =
calloc(1, sizeof(SubGhzProtocolDecoderLandRoverV0));
SubGhzProtocolDecoderHondaV2* instance =
calloc(1, sizeof(SubGhzProtocolDecoderHondaV2));
furi_check(instance);
instance->base.protocol = &land_rover_v0_protocol;
instance->base.protocol = &honda_v2_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_land_rover_v0_free(void* context) {
void subghz_protocol_decoder_honda_v2_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* instance = context;
free(instance);
}
void subghz_protocol_decoder_land_rover_v0_reset(void* context) {
void subghz_protocol_decoder_honda_v2_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* instance = context;
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
instance->decoder.parser_step = HondaV2DecoderStepReset;
instance->decoder.te_last = 0;
instance->preamble_count = 0;
memset(instance->raw, 0, sizeof(instance->raw));
@@ -486,65 +494,65 @@ void subghz_protocol_decoder_land_rover_v0_reset(void* context) {
instance->pending_short = false;
}
void subghz_protocol_decoder_land_rover_v0_feed(void* context, bool level, uint32_t duration) {
void subghz_protocol_decoder_honda_v2_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* instance = context;
switch(instance->decoder.parser_step) {
case LandRoverV0DecoderStepReset:
if(level && land_rover_v0_is_short(duration)) {
case HondaV2DecoderStepReset:
if(level && honda_v2_is_short(duration)) {
instance->preamble_count = 0;
instance->decoder.parser_step = LandRoverV0DecoderStepPreambleLow;
instance->decoder.parser_step = HondaV2DecoderStepPreambleLow;
}
break;
case LandRoverV0DecoderStepPreambleLow:
if(!level && land_rover_v0_is_short(duration)) {
case HondaV2DecoderStepPreambleLow:
if(!level && honda_v2_is_short(duration)) {
instance->preamble_count++;
instance->decoder.parser_step = LandRoverV0DecoderStepPreambleHigh;
instance->decoder.parser_step = HondaV2DecoderStepPreambleHigh;
} else {
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
instance->decoder.parser_step = HondaV2DecoderStepReset;
}
break;
case LandRoverV0DecoderStepPreambleHigh:
if(level && land_rover_v0_is_short(duration)) {
instance->decoder.parser_step = LandRoverV0DecoderStepPreambleLow;
case HondaV2DecoderStepPreambleHigh:
if(level && honda_v2_is_short(duration)) {
instance->decoder.parser_step = HondaV2DecoderStepPreambleLow;
} else if(
level && land_rover_v0_is_sync(duration) &&
instance->preamble_count >= LAND_ROVER_V0_MIN_PREAMBLE_PAIRS) {
instance->decoder.parser_step = LandRoverV0DecoderStepSyncLow;
level && honda_v2_is_sync(duration) &&
instance->preamble_count >= HONDA_V2_MIN_PREAMBLE_PAIRS) {
instance->decoder.parser_step = HondaV2DecoderStepSyncLow;
} else {
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
instance->decoder.parser_step = HondaV2DecoderStepReset;
}
break;
case LandRoverV0DecoderStepSyncLow:
if(!level && land_rover_v0_is_sync(duration)) {
case HondaV2DecoderStepSyncLow:
if(!level && honda_v2_is_sync(duration)) {
memset(instance->raw, 0, sizeof(instance->raw));
instance->bit_count = 0;
instance->extra_bit = false;
instance->previous_bit = true;
instance->boundary_pad_skipped = false;
instance->pending_short = false;
land_rover_v0_add_decoded_bit(instance, true);
instance->decoder.parser_step = LandRoverV0DecoderStepData;
honda_v2_add_decoded_bit(instance, true);
instance->decoder.parser_step = HondaV2DecoderStepData;
} else {
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
instance->decoder.parser_step = HondaV2DecoderStepReset;
}
break;
case LandRoverV0DecoderStepData:
if(!land_rover_v0_process_transition(instance, level, duration)) {
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
case HondaV2DecoderStepData:
if(!honda_v2_process_transition(instance, level, duration)) {
instance->decoder.parser_step = HondaV2DecoderStepReset;
break;
}
if(instance->bit_count == subghz_protocol_land_rover_v0_const.min_count_bit_for_found) {
if(land_rover_v0_finish_frame(instance) && instance->base.callback) {
if(instance->bit_count == subghz_protocol_honda_v2_const.min_count_bit_for_found) {
if(honda_v2_finish_frame(instance) && instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
instance->decoder.parser_step = HondaV2DecoderStepReset;
}
break;
}
@@ -552,9 +560,9 @@ void subghz_protocol_decoder_land_rover_v0_feed(void* context, bool level, uint3
instance->decoder.te_last = duration;
}
uint8_t subghz_protocol_decoder_land_rover_v0_get_hash_data(void* context) {
uint8_t subghz_protocol_decoder_honda_v2_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* instance = context;
SubGhzBlockDecoder decoder = {
.decode_data = instance->key,
@@ -567,12 +575,12 @@ uint8_t subghz_protocol_decoder_land_rover_v0_get_hash_data(void* context) {
return hash;
}
SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_serialize(
SubGhzProtocolStatus subghz_protocol_decoder_honda_v2_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* instance = context;
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
@@ -585,27 +593,27 @@ SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_serialize(
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->serial);
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->button);
pp_flipper_update_or_insert_u32(
flipper_format, LAND_ROVER_V0_FF_BTNSIG, instance->command_signature);
flipper_format, HONDA_V2_FF_BTNSIG, instance->command_signature);
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->count);
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_CHECK, instance->check);
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_TAIL, instance->tail);
pp_flipper_update_or_insert_u32(flipper_format, HONDA_V2_FF_CHECK, instance->check);
pp_flipper_update_or_insert_u32(flipper_format, HONDA_V2_FF_TAIL, instance->tail);
pp_flipper_update_or_insert_u32(
flipper_format, LAND_ROVER_V0_FF_EXTRA_BIT, instance->extra_bit ? 1U : 0U);
flipper_format, HONDA_V2_FF_EXTRA_BIT, instance->extra_bit ? 1U : 0U);
}
return ret;
}
SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_deserialize(
SubGhzProtocolStatus subghz_protocol_decoder_honda_v2_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_land_rover_v0_const.min_count_bit_for_found);
subghz_protocol_honda_v2_const.min_count_bit_for_found);
if(ret == SubGhzProtocolStatusOk) {
uint8_t key_bytes[8] = {0};
@@ -624,27 +632,27 @@ SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_deserialize(
uint32_t temp = 0;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, LAND_ROVER_V0_FF_TAIL, &temp, 1)) {
if(flipper_format_read_uint32(flipper_format, HONDA_V2_FF_TAIL, &temp, 1)) {
instance->tail = temp & 0xFFFFU;
} else {
const uint32_t count = ((uint32_t)key_bytes[6] << 1) | ((key_bytes[7] >> 7) & 1U);
instance->tail = land_rover_v0_calculate_tail(count);
instance->tail = honda_v2_calculate_tail(count);
}
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, LAND_ROVER_V0_FF_EXTRA_BIT, &temp, 1)) {
if(flipper_format_read_uint32(flipper_format, HONDA_V2_FF_EXTRA_BIT, &temp, 1)) {
instance->extra_bit = (temp & 1U) != 0;
} else {
instance->extra_bit = true;
}
land_rover_v0_validate_frame(
honda_v2_validate_frame(
instance->key,
instance->tail,
instance->extra_bit,
&instance->check_ok,
&instance->tail_ok);
land_rover_v0_parse_key_fields(
honda_v2_parse_key_fields(
instance->key,
&instance->command_signature,
&instance->serial,
@@ -654,7 +662,7 @@ SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_deserialize(
instance->generic.data = instance->key;
instance->generic.data_count_bit =
subghz_protocol_land_rover_v0_const.min_count_bit_for_found;
subghz_protocol_honda_v2_const.min_count_bit_for_found;
instance->generic.serial = instance->serial;
instance->generic.btn = instance->button;
instance->generic.cnt = instance->count;
@@ -663,9 +671,9 @@ SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_deserialize(
return ret;
}
void subghz_protocol_decoder_land_rover_v0_get_string(void* context, FuriString* output) {
void subghz_protocol_decoder_honda_v2_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderLandRoverV0* instance = context;
SubGhzProtocolDecoderHondaV2* instance = context;
furi_string_cat_printf(
output,
@@ -679,7 +687,7 @@ void subghz_protocol_decoder_land_rover_v0_get_string(void* context, FuriString*
(unsigned long long)instance->key,
(unsigned long)instance->serial,
instance->button,
land_rover_v0_button_name(instance->button),
honda_v2_button_name(instance->button),
(unsigned long)instance->command_signature,
(unsigned long)instance->count,
instance->check,
@@ -688,20 +696,20 @@ void subghz_protocol_decoder_land_rover_v0_get_string(void* context, FuriString*
instance->tail_ok ? "OK" : "BAD");
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint32_t land_rover_v0_signature_from_button(uint8_t button) {
static uint32_t honda_v2_signature_from_button(uint8_t button) {
switch(button) {
case LAND_ROVER_V0_BTN_LOCK:
return LAND_ROVER_V0_SIG_LOCK;
case LAND_ROVER_V0_BTN_UNLOCK:
return LAND_ROVER_V0_SIG_UNLOCK;
case HONDA_V2_BTN_LOCK:
return HONDA_V2_SIG_LOCK;
case HONDA_V2_BTN_UNLOCK:
return HONDA_V2_SIG_UNLOCK;
default:
return 0;
}
}
static uint64_t land_rover_v0_build_key(uint32_t signature, uint32_t serial, uint32_t count) {
static uint64_t honda_v2_build_key(uint32_t signature, uint32_t serial, uint32_t count) {
uint8_t key_bytes[8] = {0};
key_bytes[0] = (uint8_t)((signature >> 16) & 0xFFU);
key_bytes[1] = (uint8_t)((signature >> 8) & 0xFFU);
@@ -712,39 +720,39 @@ static uint64_t land_rover_v0_build_key(uint32_t signature, uint32_t serial, uin
key_bytes[6] = (uint8_t)((count >> 1) & 0xFFU);
const bool counter_lsb = (count & 1U) != 0;
const uint8_t check = land_rover_v0_calculate_check(count);
const uint8_t check = honda_v2_calculate_check(count);
key_bytes[7] = (counter_lsb ? 0x80U : 0x00U) | check;
return pp_bytes_to_u64_be(key_bytes);
}
void* subghz_protocol_encoder_land_rover_v0_alloc(SubGhzEnvironment* environment) {
void* subghz_protocol_encoder_honda_v2_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderLandRoverV0* instance =
calloc(1, sizeof(SubGhzProtocolEncoderLandRoverV0));
SubGhzProtocolEncoderHondaV2* instance =
calloc(1, sizeof(SubGhzProtocolEncoderHondaV2));
furi_check(instance);
instance->base.protocol = &land_rover_v0_protocol;
instance->base.protocol = &honda_v2_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 10;
instance->encoder.size_upload = 0;
instance->encoder.front = 0;
instance->encoder.is_running = false;
pp_encoder_buffer_ensure(instance, LAND_ROVER_V0_UPLOAD_CAPACITY);
pp_encoder_buffer_ensure(instance, HONDA_V2_UPLOAD_CAPACITY);
return instance;
}
void subghz_protocol_encoder_land_rover_v0_free(void* context) {
void subghz_protocol_encoder_honda_v2_free(void* context) {
furi_check(context);
pp_encoder_free(context);
}
SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
SubGhzProtocolStatus subghz_protocol_encoder_honda_v2_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderLandRoverV0* instance = context;
SubGhzProtocolEncoderHondaV2* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
@@ -760,7 +768,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
SubGhzProtocolStatus load_status = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_land_rover_v0_const.min_count_bit_for_found);
subghz_protocol_honda_v2_const.min_count_bit_for_found);
if(load_status != SubGhzProtocolStatusOk) {
break;
}
@@ -778,7 +786,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
}
if(have_key) {
land_rover_v0_parse_key_fields(
honda_v2_parse_key_fields(
instance->key,
&instance->command_signature,
&instance->serial,
@@ -807,12 +815,12 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
}
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, LAND_ROVER_V0_FF_BTNSIG, &u32, 1)) {
if(flipper_format_read_uint32(flipper_format, HONDA_V2_FF_BTNSIG, &u32, 1)) {
instance->command_signature = u32 & 0xFFFFFFU;
}
if(have_button) {
const uint32_t signature = land_rover_v0_signature_from_button(instance->button);
const uint32_t signature = honda_v2_signature_from_button(instance->button);
if(signature != 0U) {
instance->command_signature = signature;
}
@@ -822,13 +830,13 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
break;
}
instance->key = land_rover_v0_build_key(
instance->key = honda_v2_build_key(
instance->command_signature, instance->serial, instance->count);
pp_u64_to_bytes_be(instance->key, key_bytes);
instance->tail = land_rover_v0_calculate_tail(instance->count);
instance->tail = honda_v2_calculate_tail(instance->count);
land_rover_v0_parse_key_fields(
honda_v2_parse_key_fields(
instance->key,
&instance->command_signature,
&instance->serial,
@@ -838,7 +846,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
instance->generic.data = instance->key;
instance->generic.data_count_bit =
subghz_protocol_land_rover_v0_const.min_count_bit_for_found;
subghz_protocol_honda_v2_const.min_count_bit_for_found;
instance->generic.serial = instance->serial;
instance->generic.btn = instance->button;
instance->generic.cnt = instance->count;
@@ -846,7 +854,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
flipper_format_rewind(flipper_format);
instance->encoder.repeat = pp_encoder_read_repeat(flipper_format, 10);
if(!land_rover_v0_build_upload(instance) || instance->encoder.size_upload == 0U) {
if(!honda_v2_build_upload(instance) || instance->encoder.size_upload == 0U) {
break;
}
@@ -855,11 +863,11 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->serial);
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->button);
pp_flipper_update_or_insert_u32(
flipper_format, LAND_ROVER_V0_FF_BTNSIG, instance->command_signature);
flipper_format, HONDA_V2_FF_BTNSIG, instance->command_signature);
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->count);
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_CHECK, instance->check);
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_TAIL, instance->tail);
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_EXTRA_BIT, 1U);
pp_flipper_update_or_insert_u32(flipper_format, HONDA_V2_FF_CHECK, instance->check);
pp_flipper_update_or_insert_u32(flipper_format, HONDA_V2_FF_TAIL, instance->tail);
pp_flipper_update_or_insert_u32(flipper_format, HONDA_V2_FF_EXTRA_BIT, 1U);
instance->encoder.is_running = true;
ret = SubGhzProtocolStatusOk;
@@ -868,11 +876,11 @@ SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
return ret;
}
void subghz_protocol_encoder_land_rover_v0_stop(void* context) {
void subghz_protocol_encoder_honda_v2_stop(void* context) {
pp_encoder_stop(context);
}
LevelDuration subghz_protocol_encoder_land_rover_v0_yield(void* context) {
LevelDuration subghz_protocol_encoder_honda_v2_yield(void* context) {
return pp_encoder_yield(context);
}
#endif
@@ -0,0 +1,38 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <flipper_format/flipper_format.h>
#include <lib/toolbox/level_duration.h>
#include "../defines.h"
#define HONDA_V2_PROTOCOL_NAME "Honda V2"
extern const SubGhzProtocol honda_v2_protocol;
void* subghz_protocol_decoder_honda_v2_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_honda_v2_free(void* context);
void subghz_protocol_decoder_honda_v2_reset(void* context);
void subghz_protocol_decoder_honda_v2_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_honda_v2_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_honda_v2_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_honda_v2_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_honda_v2_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_honda_v2_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_honda_v2_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_honda_v2_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_honda_v2_stop(void* context);
LevelDuration subghz_protocol_encoder_honda_v2_yield(void* context);
@@ -10,26 +10,63 @@ uint64_t kia_v6_a_key = 0;
uint64_t kia_v6_b_key = 0;
uint64_t kia_v5_key = 0;
void protopirate_keys_load(SubGhzEnvironment* environment) {
static bool kia_mf_key_loaded = false;
static bool kia_v6_a_key_loaded = false;
static bool kia_v6_b_key_loaded = false;
static bool kia_v5_key_loaded = false;
static void protopirate_keys_reset(void) {
kia_mf_key = 0;
kia_v6_a_key = 0;
kia_v6_b_key = 0;
kia_v5_key = 0;
kia_mf_key_loaded = false;
kia_v6_a_key_loaded = false;
kia_v6_b_key_loaded = false;
kia_v5_key_loaded = false;
}
bool protopirate_keys_load(SubGhzEnvironment* environment) {
protopirate_keys_reset();
if(!environment) {
return false;
}
SubGhzKeystore* keystore = subghz_environment_get_keystore(environment);
// Load keys from secure keystore
for
M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) {
switch(manufacture_code->type) {
case KIA_KEY1:
kia_mf_key = manufacture_code->key;
break;
case KIA_KEY2:
kia_v6_a_key = manufacture_code->key;
break;
case KIA_KEY3:
kia_v6_b_key = manufacture_code->key;
break;
case KIA_KEY4:
kia_v5_key = manufacture_code->key;
break;
}
}
if(!keystore) {
return false;
}
SubGhzKeyArray_t* key_data = subghz_keystore_get_data(keystore);
if(!key_data) {
return false;
}
for
M_EACH(manufacture_code, *key_data, SubGhzKeyArray_t) {
switch(manufacture_code->type) {
case KIA_KEY1:
kia_mf_key = manufacture_code->key;
kia_mf_key_loaded = true;
break;
case KIA_KEY2:
kia_v6_a_key = manufacture_code->key;
kia_v6_a_key_loaded = true;
break;
case KIA_KEY3:
kia_v6_b_key = manufacture_code->key;
kia_v6_b_key_loaded = true;
break;
case KIA_KEY4:
kia_v5_key = manufacture_code->key;
kia_v5_key_loaded = true;
break;
default:
break;
}
}
return kia_mf_key_loaded || kia_v6_a_key_loaded || kia_v6_b_key_loaded || kia_v5_key_loaded;
}
uint64_t get_kia_mf_key() {
@@ -47,3 +84,19 @@ uint64_t get_kia_v6_keystore_b() {
uint64_t get_kia_v5_key() {
return kia_v5_key;
}
bool protopirate_keys_has_kia_mf_key(void) {
return kia_mf_key_loaded;
}
bool protopirate_keys_has_kia_v6_keystore_a(void) {
return kia_v6_a_key_loaded;
}
bool protopirate_keys_has_kia_v6_keystore_b(void) {
return kia_v6_b_key_loaded;
}
bool protopirate_keys_has_kia_v5_key(void) {
return kia_v5_key_loaded;
}
@@ -1,5 +1,6 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <lib/subghz/environment.h>
@@ -18,4 +19,9 @@ uint64_t get_kia_v6_keystore_b();
uint64_t get_kia_v5_key();
void protopirate_keys_load(SubGhzEnvironment* environment);
bool protopirate_keys_has_kia_mf_key(void);
bool protopirate_keys_has_kia_v6_keystore_a(void);
bool protopirate_keys_has_kia_v6_keystore_b(void);
bool protopirate_keys_has_kia_v5_key(void);
bool protopirate_keys_load(SubGhzEnvironment* environment);
@@ -458,7 +458,7 @@ static bool kia_v0_decoder_try_honda(SubGhzProtocolDecoderKIA* instance) {
kia_v0_decoder_commit(instance, key, KIA_V0_TYPE_HONDA, KIA_V0_BIT_COUNT_HONDA);
return true;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static size_t kia_v0_append_short_pairs(LevelDuration* upload, size_t index, size_t count) {
return pp_emit_short_pairs(
@@ -466,7 +466,7 @@ static size_t kia_v0_append_short_pairs(LevelDuration* upload, size_t index, siz
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static size_t kia_v0_append_data_pairs(
LevelDuration* upload,
@@ -484,7 +484,7 @@ static size_t kia_v0_append_data_pairs(
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v0_build_honda_upload(SubGhzProtocolEncoderKIA* instance, uint64_t raw) {
size_t index = 0;
@@ -507,7 +507,7 @@ static void kia_v0_build_honda_upload(SubGhzProtocolEncoderKIA* instance, uint64
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v0_build_kia_upload(SubGhzProtocolEncoderKIA* instance, uint64_t raw) {
size_t index = 0;
@@ -528,7 +528,7 @@ static void kia_v0_build_kia_upload(SubGhzProtocolEncoderKIA* instance, uint64_t
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v0_build_suzuki_upload(SubGhzProtocolEncoderKIA* instance, uint64_t shifted) {
size_t index = 0;
@@ -557,7 +557,7 @@ static uint8_t kia_v0_infer_type_from_bits(uint32_t bits) {
if(bits == KIA_V0_BIT_COUNT_HONDA) return KIA_V0_TYPE_HONDA;
return KIA_V0_TYPE_KIA;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v0_encoder_apply_fields(SubGhzProtocolEncoderKIA* instance) {
instance->generic.serial = instance->fields.serial;
@@ -611,7 +611,7 @@ static void kia_v0_encoder_apply_fields(SubGhzProtocolEncoderKIA* instance) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v0_encoder_sync_from_generic(SubGhzProtocolEncoderKIA* instance) {
instance->fields.serial = instance->generic.serial;
@@ -625,7 +625,7 @@ static void kia_v0_encoder_sync_from_generic(SubGhzProtocolEncoderKIA* instance)
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v0_encoder_apply_flipper_fields(
SubGhzProtocolEncoderKIA* instance,
@@ -668,7 +668,7 @@ static void kia_v0_encoder_apply_flipper_fields(
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* subghz_protocol_encoder_kia_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
@@ -686,7 +686,7 @@ void* subghz_protocol_encoder_kia_alloc(SubGhzEnvironment* environment) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
SubGhzProtocolStatus
subghz_protocol_encoder_kia_deserialize(void* context, FlipperFormat* flipper_format) {
@@ -764,7 +764,7 @@ SubGhzProtocolStatus
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void subghz_protocol_encoder_kia_set_button(void* context, uint8_t button) {
furi_check(context);
@@ -778,7 +778,7 @@ void subghz_protocol_encoder_kia_set_button(void* context, uint8_t button) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void subghz_protocol_encoder_kia_set_counter(void* context, uint16_t counter) {
furi_check(context);
@@ -788,7 +788,7 @@ void subghz_protocol_encoder_kia_set_counter(void* context, uint16_t counter) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void subghz_protocol_encoder_kia_increment_counter(void* context) {
furi_check(context);
@@ -798,7 +798,7 @@ void subghz_protocol_encoder_kia_increment_counter(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
uint16_t subghz_protocol_encoder_kia_get_counter(void* context) {
furi_check(context);
@@ -807,7 +807,7 @@ uint16_t subghz_protocol_encoder_kia_get_counter(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
uint8_t subghz_protocol_encoder_kia_get_button(void* context) {
furi_check(context);
@@ -1106,7 +1106,7 @@ const SubGhzProtocolDecoder subghz_protocol_kia_decoder = {
.get_string = subghz_protocol_decoder_kia_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder subghz_protocol_kia_encoder = {
.alloc = subghz_protocol_encoder_kia_alloc,
.free = pp_encoder_free,
@@ -1130,6 +1130,14 @@ const SubGhzProtocol kia_protocol_v0 = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &subghz_protocol_kia_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &subghz_protocol_kia_encoder,
#else
.encoder = NULL,
#endif
};
@@ -60,7 +60,7 @@ const SubGhzProtocolDecoder kia_protocol_v1_decoder = {
.get_string = kia_protocol_decoder_v1_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder kia_protocol_v1_encoder = {
.alloc = kia_protocol_encoder_v1_alloc,
.free = pp_encoder_free,
@@ -86,8 +86,20 @@ const SubGhzProtocol kia_protocol_v1 = {
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &kia_protocol_v1_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &kia_protocol_v1_encoder,
#else
.encoder = NULL,
#endif
};
static void kia_v1_check_remote_controller(SubGhzProtocolDecoderKiaV1* instance);
@@ -144,10 +156,13 @@ static const char* kia_v1_get_button_name(uint8_t btn) {
}
return name;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* kia_protocol_encoder_v1_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderKiaV1* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV1));
SubGhzProtocolEncoderKiaV1* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV1));
if(!instance) {
return NULL;
}
instance->base.protocol = &kia_protocol_v1;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -161,12 +176,14 @@ void* kia_protocol_encoder_v1_alloc(SubGhzEnvironment* environment) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_protocol_encoder_v1_get_upload(SubGhzProtocolEncoderKiaV1* instance) {
furi_check(instance);
if(instance->encoder.upload == NULL) return; // lazy buffer not yet allocated
size_t index = 0;
LevelDuration* up = instance->encoder.upload;
const size_t cap = KIA_V1_UPLOAD_CAPACITY;
uint8_t cnt_high = (instance->generic.cnt >> 8) & 0xF;
uint8_t char_data[7];
@@ -184,34 +201,25 @@ static void kia_protocol_encoder_v1_get_upload(SubGhzProtocolEncoderKiaV1* insta
instance->generic.btn << 16 | (instance->generic.cnt & 0xFF) << 8 |
((instance->generic.cnt >> 8) & 0xF) << 4 | crc;
const uint32_t te_short = (uint32_t)kia_protocol_v1_const.te_short;
const uint32_t te_long = (uint32_t)kia_protocol_v1_const.te_long;
for(uint8_t burst = 0; burst < KIA_V1_TOTAL_BURSTS; burst++) {
if(burst > 0) {
instance->encoder.upload[index++] =
level_duration_make(false, KIA_V1_INTER_BURST_GAP_US);
index = pp_emit(up, index, cap, false, KIA_V1_INTER_BURST_GAP_US);
}
for(int i = 0; i < KIA_V1_HEADER_PULSES; i++) {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v1_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)kia_protocol_v1_const.te_long);
index = pp_emit(up, index, cap, false, te_long);
index = pp_emit(up, index, cap, true, te_long);
}
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v1_const.te_short);
index = pp_emit(up, index, cap, false, te_short);
for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) {
if(bit_read(instance->generic.data, i - 2)) {
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)kia_protocol_v1_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v1_const.te_short);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v1_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)kia_protocol_v1_const.te_short);
}
bool bit = bit_read(instance->generic.data, i - 2);
index = pp_emit(up, index, cap, bit, te_short);
index = pp_emit(up, index, cap, !bit, te_short);
}
}
@@ -228,7 +236,7 @@ static void kia_protocol_encoder_v1_get_upload(SubGhzProtocolEncoderKiaV1* insta
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
SubGhzProtocolStatus
kia_protocol_encoder_v1_deserialize(void* context, FlipperFormat* flipper_format) {
@@ -287,7 +295,7 @@ SubGhzProtocolStatus
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v1_set_button(void* context, uint8_t button) {
furi_check(context);
@@ -298,7 +306,7 @@ void kia_protocol_encoder_v1_set_button(void* context, uint8_t button) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v1_set_counter(void* context, uint16_t counter) {
furi_check(context);
@@ -312,7 +320,7 @@ void kia_protocol_encoder_v1_set_counter(void* context, uint16_t counter) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v1_increment_counter(void* context) {
furi_check(context);
@@ -326,7 +334,7 @@ void kia_protocol_encoder_v1_increment_counter(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
uint16_t kia_protocol_encoder_v1_get_counter(void* context) {
furi_check(context);
@@ -335,7 +343,7 @@ uint16_t kia_protocol_encoder_v1_get_counter(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
uint8_t kia_protocol_encoder_v1_get_button(void* context) {
furi_check(context);
@@ -347,7 +355,10 @@ uint8_t kia_protocol_encoder_v1_get_button(void* context) {
void* kia_protocol_decoder_v1_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderKiaV1* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV1));
SubGhzProtocolDecoderKiaV1* instance = calloc(1, sizeof(SubGhzProtocolDecoderKiaV1));
if(!instance) {
return NULL;
}
instance->base.protocol = &kia_protocol_v1;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
@@ -52,7 +52,7 @@ const SubGhzProtocolDecoder kia_protocol_v2_decoder = {
.get_string = kia_protocol_decoder_v2_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder kia_protocol_v2_encoder = {
.alloc = kia_protocol_encoder_v2_alloc,
.free = pp_encoder_free,
@@ -76,8 +76,16 @@ const SubGhzProtocol kia_protocol_v2 = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &kia_protocol_v2_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &kia_protocol_v2_encoder,
#else
.encoder = NULL,
#endif
};
static uint8_t kia_v2_calculate_crc(uint64_t data) {
@@ -100,41 +108,33 @@ static uint8_t kia_v2_calculate_crc(uint64_t data) {
return (crc + 1) & 0x0F;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_protocol_encoder_v2_get_upload(SubGhzProtocolEncoderKiaV2* instance) {
furi_check(instance);
if(instance->encoder.upload == NULL) return;
size_t index = 0;
LevelDuration* up = instance->encoder.upload;
const size_t cap = KIA_V2_UPLOAD_CAPACITY;
const uint32_t te_short = (uint32_t)kia_protocol_v2_const.te_short;
const uint32_t te_long = (uint32_t)kia_protocol_v2_const.te_long;
uint8_t crc = kia_v2_calculate_crc(instance->generic.data);
instance->generic.data = (instance->generic.data & ~0x0FULL) | crc;
for(uint8_t burst = 0; burst < KIA_V2_TOTAL_BURSTS; burst++) {
for(int i = 0; i < KIA_V2_HEADER_PAIRS; i++) {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v2_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)kia_protocol_v2_const.te_long);
index = pp_emit(up, index, cap, false, te_long);
index = pp_emit(up, index, cap, true, te_long);
}
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v2_const.te_short);
index = pp_emit(up, index, cap, false, te_short);
for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) {
bool bit = bit_read(instance->generic.data, i - 2);
if(bit) {
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)kia_protocol_v2_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v2_const.te_short);
} else {
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)kia_protocol_v2_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)kia_protocol_v2_const.te_short);
}
index = pp_emit(up, index, cap, bit, te_short);
index = pp_emit(up, index, cap, !bit, te_short);
}
}
@@ -151,11 +151,14 @@ static void kia_protocol_encoder_v2_get_upload(SubGhzProtocolEncoderKiaV2* insta
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* kia_protocol_encoder_v2_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderKiaV2* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV2));
SubGhzProtocolEncoderKiaV2* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV2));
if(!instance) {
return NULL;
}
instance->base.protocol = &kia_protocol_v2;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -170,7 +173,7 @@ void* kia_protocol_encoder_v2_alloc(SubGhzEnvironment* environment) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
SubGhzProtocolStatus
kia_protocol_encoder_v2_deserialize(void* context, FlipperFormat* flipper_format) {
@@ -256,7 +259,10 @@ SubGhzProtocolStatus
void* kia_protocol_decoder_v2_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderKiaV2* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV2));
SubGhzProtocolDecoderKiaV2* instance = calloc(1, sizeof(SubGhzProtocolDecoderKiaV2));
if(!instance) {
return NULL;
}
instance->base.protocol = &kia_protocol_v2;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
@@ -87,7 +87,7 @@ static void kia_v3_v4_add_raw_bit(SubGhzProtocolDecoderKiaV3V4* instance, bool b
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static inline void kia_v3_v4_emit_bit_pwm(LevelDuration* upload, size_t* idx, bool bit, bool v4) {
const uint32_t te_short = kia_protocol_v3_v4_const.te_short;
const uint32_t te_long = kia_protocol_v3_v4_const.te_long;
@@ -173,7 +173,7 @@ const SubGhzProtocolDecoder kia_protocol_v3_v4_decoder = {
.get_string = kia_protocol_decoder_v3_v4_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder kia_protocol_v3_v4_encoder = {
.alloc = kia_protocol_encoder_v3_v4_alloc,
.free = pp_encoder_free,
@@ -197,22 +197,34 @@ const SubGhzProtocol kia_protocol_v3_v4 = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &kia_protocol_v3_v4_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &kia_protocol_v3_v4_encoder,
#else
.encoder = NULL,
#endif
};
// ============================================================================
// ENCODER IMPLEMENTATION
// ============================================================================
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* kia_protocol_encoder_v3_v4_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolEncoderKiaV3V4* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV3V4));
furi_check(instance);
if(environment) {
protopirate_keys_load(environment);
}
if(!protopirate_keys_has_kia_mf_key()) {
FURI_LOG_E(TAG, "Kia V3/V4 encoder missing KIA_KEY1 keystore entry");
return NULL;
}
SubGhzProtocolEncoderKiaV3V4* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV3V4));
furi_check(instance);
instance->base.protocol = &kia_protocol_v3_v4;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -234,7 +246,7 @@ void* kia_protocol_encoder_v3_v4_alloc(SubGhzEnvironment* environment) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_protocol_encoder_v3_v4_build_packet(
SubGhzProtocolEncoderKiaV3V4* instance,
@@ -303,7 +315,7 @@ static void kia_protocol_encoder_v3_v4_patch_crc(SubGhzProtocolEncoderKiaV3V4* i
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_protocol_encoder_v3_v4_get_upload(SubGhzProtocolEncoderKiaV3V4* instance) {
furi_check(instance);
@@ -370,7 +382,7 @@ static void kia_protocol_encoder_v3_v4_get_upload(SubGhzProtocolEncoderKiaV3V4*
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
SubGhzProtocolStatus
kia_protocol_encoder_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
@@ -474,7 +486,7 @@ SubGhzProtocolStatus
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v3_v4_stop(void* context) {
if(!context) return;
@@ -484,7 +496,7 @@ void kia_protocol_encoder_v3_v4_stop(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
LevelDuration kia_protocol_encoder_v3_v4_yield(void* context) {
SubGhzProtocolEncoderKiaV3V4* instance = context;
@@ -537,7 +549,7 @@ LevelDuration kia_protocol_encoder_v3_v4_yield(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v3_v4_set_button(void* context, uint8_t button) {
furi_check(context);
@@ -549,7 +561,7 @@ void kia_protocol_encoder_v3_v4_set_button(void* context, uint8_t button) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v3_v4_set_counter(void* context, uint16_t counter) {
furi_check(context);
@@ -561,7 +573,7 @@ void kia_protocol_encoder_v3_v4_set_counter(void* context, uint16_t counter) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void kia_protocol_encoder_v3_v4_increment_counter(void* context) {
furi_check(context);
@@ -573,7 +585,7 @@ void kia_protocol_encoder_v3_v4_increment_counter(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
uint16_t kia_protocol_encoder_v3_v4_get_counter(void* context) {
furi_check(context);
@@ -582,7 +594,7 @@ uint16_t kia_protocol_encoder_v3_v4_get_counter(void* context) {
}
#endif
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
uint8_t kia_protocol_encoder_v3_v4_get_button(void* context) {
furi_check(context);
@@ -79,7 +79,7 @@ static uint16_t mixer_decode(uint32_t encrypted) {
return (s0 + (s1 << 8)) & 0xFFFF;
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint32_t mixer_encode(uint32_t serial, uint16_t counter, uint8_t button) {
build_keystore_from_mfkey(keystore_bytes);
@@ -196,7 +196,7 @@ const SubGhzProtocolDecoder kia_protocol_v5_decoder = {
.get_string = kia_protocol_decoder_v5_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder kia_protocol_v5_encoder = {
.alloc = kia_protocol_encoder_v5_alloc,
.free = pp_encoder_free,
@@ -219,15 +219,23 @@ const SubGhzProtocol kia_protocol_v5 = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
| SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Send
#endif
,
#if PROTOPIRATE_WITH_DECODER
.decoder = &kia_protocol_v5_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &kia_protocol_v5_encoder,
#else
.encoder = NULL,
#endif
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static uint8_t kia_v5_calculate_crc(uint64_t data) {
uint8_t crc = 0;
@@ -243,12 +251,16 @@ static uint8_t kia_v5_calculate_crc(uint64_t data) {
}
void* kia_protocol_encoder_v5_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolEncoderKiaV5* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV5));
furi_check(instance);
if(environment) {
protopirate_keys_load(environment);
}
if(!protopirate_keys_has_kia_v5_key()) {
FURI_LOG_E(TAG, "Kia V5 encoder missing KIA_KEY4 keystore entry");
return NULL;
}
SubGhzProtocolEncoderKiaV5* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV5));
furi_check(instance);
instance->base.protocol = &kia_protocol_v5;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -114,7 +114,7 @@ const SubGhzProtocolDecoder kia_protocol_v6_decoder = {
.get_string = kia_protocol_decoder_v6_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder kia_protocol_v6_encoder = {
.alloc = kia_protocol_encoder_v6_alloc,
.free = pp_encoder_free,
@@ -137,8 +137,16 @@ const SubGhzProtocol kia_protocol_v6 = {
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load,
#if PROTOPIRATE_WITH_DECODER
.decoder = &kia_protocol_v6_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &kia_protocol_v6_encoder,
#else
.encoder = NULL,
#endif
};
#define kia_v6_crc8(data, len) subghz_protocol_blocks_crc8((data), (len), 0x07, 0xFF)
@@ -215,7 +223,7 @@ static void aes_addroundkey(uint8_t* state, const uint8_t* round_key) {
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void aes_subbytes(uint8_t* state) {
for(int row = 0; row < 4; row++) {
for(int col = 0; col < 4; col++) {
@@ -358,7 +366,7 @@ static void get_kia_v6_aes_key(uint8_t* aes_key) {
}
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v6_encrypt_payload(
uint8_t fx_field,
uint32_t serial,
@@ -812,7 +820,7 @@ void kia_protocol_decoder_v6_get_string(void* context, FuriString* output) {
instance->crc2_field);
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
#define KIA_V6_PREAMBLE_PAIRS_1 640
#define KIA_V6_PREAMBLE_PAIRS_2 38
@@ -928,13 +936,17 @@ static void kia_protocol_encoder_v6_build_upload(SubGhzProtocolEncoderKiaV6* ins
}
void* kia_protocol_encoder_v6_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolEncoderKiaV6* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV6));
if(!instance) return NULL;
memset(instance, 0, sizeof(SubGhzProtocolEncoderKiaV6));
if(environment) {
protopirate_keys_load(environment);
}
if(!protopirate_keys_has_kia_v6_keystore_a() || !protopirate_keys_has_kia_v6_keystore_b()) {
FURI_LOG_E(TAG, "Kia V6 encoder missing KIA_KEY2/KIA_KEY3 keystore entries");
return NULL;
}
SubGhzProtocolEncoderKiaV6* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV6));
if(!instance) return NULL;
memset(instance, 0, sizeof(SubGhzProtocolEncoderKiaV6));
instance->base.protocol = &kia_protocol_v6;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -985,4 +997,4 @@ SubGhzProtocolStatus
return SubGhzProtocolStatusOk;
}
#endif // ENABLE_EMULATE_FEATURE
#endif // PROTOPIRATE_WITH_ENCODER
@@ -37,7 +37,7 @@ SubGhzProtocolStatus
kia_protocol_decoder_v6_deserialize(void* context, FlipperFormat* flipper_format);
void kia_protocol_decoder_v6_get_string(void* context, FuriString* output);
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* kia_protocol_encoder_v6_alloc(SubGhzEnvironment* environment);
SubGhzProtocolStatus
kia_protocol_encoder_v6_deserialize(void* context, FlipperFormat* flipper_format);
@@ -43,7 +43,7 @@ struct SubGhzProtocolDecoderKiaV7 {
bool crc_valid;
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
struct SubGhzProtocolEncoderKiaV7 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
@@ -155,7 +155,7 @@ static uint64_t kia_v7_encode_key(
return pp_bytes_to_u64_be(bytes);
}
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
static void kia_v7_decode_key_encoder(SubGhzProtocolEncoderKiaV7* instance) {
kia_v7_decode_key_common(
&instance->generic,
@@ -233,7 +233,7 @@ const SubGhzProtocolDecoder kia_protocol_v7_decoder = {
.get_string = kia_protocol_decoder_v7_get_string,
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
const SubGhzProtocolEncoder kia_protocol_v7_encoder = {
.alloc = kia_protocol_encoder_v7_alloc,
.free = pp_encoder_free,
@@ -257,11 +257,19 @@ const SubGhzProtocol kia_protocol_v7 = {
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
#if PROTOPIRATE_WITH_DECODER
.decoder = &kia_protocol_v7_decoder,
#else
.decoder = NULL,
#endif
#if PROTOPIRATE_WITH_ENCODER
.encoder = &kia_protocol_v7_encoder,
#else
.encoder = NULL,
#endif
};
#ifdef ENABLE_EMULATE_FEATURE
#if PROTOPIRATE_WITH_ENCODER
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);

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