Compare commits

...

4 Commits

Author SHA1 Message Date
d4rks1d33 492cee4373 Fix frezze on exit FlipperDoom
Build Dev Firmware / build (push) Waiting to run
2026-07-02 22:17:59 -03:00
d4rks1d33 a0c53e381c Added better Doom for fun xD
Build Dev Firmware / build (push) Has been cancelled
2026-07-02 20:50:10 -03:00
d4rks1d33 46d7e1263c Updated ProtoPirate, new Zero-Mega version (amazing updates)
Build Dev Firmware / build (push) Successful in 17m44s
2026-06-30 20:47:24 -03:00
d4rks1d33 426607f916 Thanks AussieMike for renaming Garage Door App, thanks to this ProtoPirate NOW is working!
Build Dev Firmware / build (push) Successful in 18m26s
2026-06-29 21:47:52 -03:00
234 changed files with 18870 additions and 6933 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

@@ -1,246 +0,0 @@
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: true
AcrossEmptyLines: true
AcrossComments: true
AlignCompound: false
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: false
AcrossComments: true
AlignCompound: true
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseColons: false
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments:
Kind: Never
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
AttributeMacros:
- __capability
BinPackArguments: false
BinPackParameters: false
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Leave
BreakAfterJavaFieldAnnotations: false
BreakArrays: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: BeforeComma
BreakInheritanceList: BeforeColon
BreakStringLiterals: false
ColumnLimit: 99
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
- M_EACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: false
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: false
IndentWidth: 4
IndentWrappedFunctionNames: true
InsertBraces: false
InsertNewlineAtEOF: true
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
KeepEmptyLinesAtEOF: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 10
PenaltyBreakBeforeFirstCallParameter: 30
PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 0
PenaltyBreakOpenParenthesis: 0
PenaltyBreakScopeResolution: 500
PenaltyBreakString: 10
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 100
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Left
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: false
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: true
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SkipMacroDefinitionBody: false
SortIncludes: Never
SortJavaStaticImport: Before
SortUsingDeclarations: Never
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: Never
SpaceBeforeParensOptions:
AfterControlStatements: false
AfterForeachMacros: false
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: false
AfterOverloadedOperator: false
AfterPlacementOperator: true
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInContainerLiterals: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: c++20
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 4
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
...
@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
@@ -1,130 +0,0 @@
# **ProtoPirate**
### _for Flipper Zero_
## **⚠️ Warning: Important Security & Project Update**
Read message by following link below:
https://protopirate.net/ProtoPirate
Main repo is located at: https://protopirate.net/ProtoPirate/ProtoPirate
All others are read only mirrors!
ProtoPirate is an experimental rolling-code analysis toolkit developed by members of **The Pirates' Plunder**.
The app currently supports decoding for multiple automotive key-fob families (Kia, Ford, Subaru, Suzuki, VW, and more), with the goal of being a drop-in Flipper app (.fap) that is free, open source, and can be used on any Flipper Zero firmware.
App is intended for educational and security purposes only, and has no signal transmission enabled by default. This prevents users from accidentally desyncing their keyfobs, making it safe for non-specialists.
## **Supported Protocols**
Protocols are split into **AM** and **FM** registries. The active registry is chosen from the receiver selected preset.
### **AM protocols**
| Protocol | Decoder | Encoder | Signal Encoding | Modulation | Encryption | CRC | Frequency |
| ------------------------ | ------- | ------- | --------------- | ---------- | ------------------------------ | ------------ | --------------- |
| 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 |
| Ford V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code | ✅ + Checksum | 315.00 / 433.92 |
| Honda V1 | ✅ | ✅ | Manchester | AM650 | Rolling Code | CRC4 | 315.00 / 433.92 |
| Kia V1 | ✅ | ✅ | Manchester | AM650 | Rolling Code | CRC4 | 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 |
| 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 |
### **FM protocols**
| Protocol | Decoder | Encoder | Signal Encoding | Modulation | Encryption | CRC | Frequency |
| ----------------------------- | ------- | ------- | --------------- | ---------- | ---------------------------- | ---------- | --------------- |
| Ford V1 | ✅ | ✅ | Manchester | F4 | Rolling Code | CRC16 | 315.00 / 433.92 |
| Ford V2 | ✅ | ✅ | Manchester | F4 | Rolling Code (simple replay) | ❌ | 434.25 |
| Ford V3 | ✅ | ❌ | Manchester | F4 | Rolling Code | ❌ | 434.25 |
| Honda Static | ✅ | ✅ | PWM | Honda1 | Static Code | Checksum | 315.00 / 433.92 |
| Kia V0 / Suzuki V0 / Honda V0 | ✅ | ✅ | PWM | FM476 | Rolling Code | CRC8 | 315.00 / 433.92 |
| Kia V2 | ✅ | ✅ | Manchester | FM476 | Rolling Code | CRC4 | 315.00 / 433.92 |
| Kia V3 / V4 | ✅ | ✅ | PWM | FM476 | KeeLoq | CRC4 (BF) | 315.00 / 433.92 |
| 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 |
| 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 |
| Scher-Khan | ✅ | ❌ | PWM | FM | Magic Code | ❌ | 315.00 / 433.92 |
*More Coming Soon*
## **Features**
### 📡 Protocol Receiver
Real-time signal capture and decoding with animated radar display. Supports frequency hopping.
### 📂 Sub Decode
Load and analyze existing `.sub` files from your SD card. Browse `/ext/subghz/` to decode previously captured signals.
### ⏱️ Timing Tuner
Tool for protocol developers to compare real fob signal timing against protocol definitions.
- **Protocol Definition**: Expected short/long pulse durations and tolerance
- **Received Signal**: Measured timing from real fob (avg, min, max, sample count)
- **Analysis**: Difference from expected, jitter measurements
- **Conclusion**: Whether timing matches or needs adjustment with specific recommendations
## **Credits**
The following contributors are recognized for helping us keep open sourced projects and the freeware community alive.
### **App Development**
- RocketGod
- MMX
- Leeroy
- gullradriel
- Skorp - Thanks, I sneaked a lot from Weather App!
- Vadim's Radio Driver
### **Protocol Magic**
- L0rdDiakon
- YougZ
- RocketGod
- MMX
- DoobTheGoober
- Skorp
- Slackware
- Trikk
- Wootini
- Li0ard
- Leeroy
- Ash
### **Reverse Engineering Support**
- DoobTheGoober
- MMX
- NeedNotApply
- RocketGod
- Slackware
- Trikk
- Li0ard
## **Community & Support**
Join **The Pirates' Plunder** on Discord for development updates, testing, protocol research, community support, and a bunch of badasses doing fun shit:
➡️ **[https://discord.gg/thepirates](https://discord.gg/thepirates)**
<img alt="rocketgod_logo_transparent" src="https://github.com/user-attachments/assets/ad15b106-152c-4a60-a9e2-4d40dfa8f3c6" />
@@ -3,42 +3,42 @@ App(
name="Garage Door Remote",
apptype=FlipperAppType.EXTERNAL,
targets=["f7"],
entry_point="protopirate_app",
entry_point="gdr_app",
requires=["gui"],
stack_size=4 * 1024,
stack_size=8 * 1024,
fap_description="Capture and emulate garage and gate remote signals from Sub-GHz",
fap_version="2.6",
fap_icon="images/protopirate_10px.png",
fap_icon="images/gdr_10x10.png",
fap_category="Sub-GHz",
fap_icon_assets="images",
fap_file_assets="keystore",
sources=[
"protopirate_app.c",
"protopirate_app_i.c",
"protopirate_history.c",
"helpers/protopirate_psa_bf_host.c",
"helpers/protopirate_settings.c",
"helpers/protopirate_storage.c",
"gdr_app.c",
"gdr_app_i.c",
"gdr_history.c",
"helpers/gdr_psa_bf_host.c",
"helpers/gdr_settings.c",
"helpers/gdr_storage.c",
"helpers/radio_device_loader.c",
"helpers/raw_file_reader.c",
"scenes/protopirate_scene.c",
"scenes/protopirate_scene_about.c",
"scenes/protopirate_scene_dual_receiver.c",
"scenes/protopirate_scene_dual_receiver_config.c",
"scenes/protopirate_scene_emulate.c",
"scenes/protopirate_scene_need_saving.c",
"scenes/protopirate_scene_receiver.c",
"scenes/protopirate_scene_receiver_config.c",
"scenes/protopirate_scene_receiver_info.c",
"scenes/protopirate_scene_saved.c",
"scenes/protopirate_scene_saved_info.c",
"scenes/protopirate_scene_shield_receiver.c",
"scenes/protopirate_scene_shield_receiver_config.c",
"scenes/protopirate_scene_start.c",
"scenes/protopirate_scene_sub_decode.c",
"scenes/protopirate_scene_timing_tuner.c",
"views/protopirate_dual_receiver.c",
"views/protopirate_receiver.c",
"scenes/gdr_scene.c",
"scenes/gdr_scene_about.c",
"scenes/gdr_scene_dual_receiver.c",
"scenes/gdr_scene_dual_receiver_config.c",
"scenes/gdr_scene_emulate.c",
"scenes/gdr_scene_need_saving.c",
"scenes/gdr_scene_receiver.c",
"scenes/gdr_scene_receiver_config.c",
"scenes/gdr_scene_receiver_info.c",
"scenes/gdr_scene_saved.c",
"scenes/gdr_scene_saved_info.c",
"scenes/gdr_scene_shield_receiver.c",
"scenes/gdr_scene_shield_receiver_config.c",
"scenes/gdr_scene_start.c",
"scenes/gdr_scene_sub_decode.c",
"scenes/gdr_scene_timing_tuner.c",
"views/gdr_dual_receiver.c",
"views/gdr_receiver.c",
"protocols/protocol_items.c",
"protocols/protocols_common.c",
"protocols/keys.c",
@@ -46,12 +46,12 @@ App(
)
App(
appid="protopirate_am_plugin",
appid="gdr_am_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_am_plugin_ep",
entry_point="gdr_am_plugin_ep",
requires=["garage_door_remote"],
sources=[
"protocols/plugins/protopirate_am_plugin.c",
"protocols/plugins/gdr_am_plugin.c",
"protocols/protocols_common.c",
"protocols/keys.c",
"protocols/alutech_at_4n.c",
@@ -79,12 +79,12 @@ App(
)
App(
appid="protopirate_fm_plugin",
appid="gdr_fm_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_fm_plugin_ep",
entry_point="gdr_fm_plugin_ep",
requires=["garage_door_remote"],
sources=[
"protocols/plugins/protopirate_fm_plugin.c",
"protocols/plugins/gdr_fm_plugin.c",
"protocols/protocols_common.c",
"protocols/keys.c",
"protocols/ansonic.c",
@@ -93,38 +93,12 @@ App(
)
App(
appid="protopirate_emulate_plugin",
appid="gdr_emulate_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_emulate_plugin_ep",
entry_point="gdr_emulate_plugin_ep",
requires=["garage_door_remote"],
sources=[
"scenes/plugins/protopirate_emulate_plugin.c",
],
fal_embedded=True,
)
App(
appid="protopirate_fm_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_fm_plugin_ep",
requires=["garage_door_remote"],
sources=[
"protocols/plugins/protopirate_fm_plugin.c",
"protocols/protocols_common.c",
"protocols/keys.c",
"protocols/ansonic.c",
],
fal_embedded=True,
)
App(
appid="protopirate_emulate_plugin",
apptype=FlipperAppType.PLUGIN,
entry_point="protopirate_emulate_plugin_ep",
requires=["garage_door_remote"],
sources=[
"scenes/plugins/protopirate_emulate_plugin.c",
"scenes/plugins/gdr_emulate_plugin.c",
],
fal_embedded=True,
)
Binary file not shown.
@@ -1,20 +1,20 @@
// protopirate_app.c
#include "protopirate_app_i.h"
// gdr_app.c
#include "gdr_app_i.h"
#include <furi.h>
#include <furi_hal.h>
#include "protocols/protocol_items.h"
#include "protocols/protocols_common.h"
#include "helpers/protopirate_settings.h"
#include "helpers/protopirate_storage.h"
#include "helpers/protopirate_psa_bf_host.h"
#include "helpers/gdr_settings.h"
#include "helpers/gdr_storage.h"
#include "helpers/gdr_psa_bf_host.h"
#include "protocols/keys.h"
#include <string.h>
#define TAG "ProtoPirateApp"
#define TAG "GDRApp"
#if defined(ENABLE_DUAL_RX_SCENE) || defined(ENABLE_SHIELD_RX_SCENE)
static bool protopirate_setting_has_frequency(SubGhzSetting* setting, uint32_t frequency) {
static bool gdr_setting_has_frequency(SubGhzSetting* setting, uint32_t frequency) {
size_t count = subghz_setting_get_frequency_count(setting);
for(size_t i = 0; i < count; i++) {
if(subghz_setting_get_frequency(setting, i) == frequency) {
@@ -26,27 +26,27 @@ static bool protopirate_setting_has_frequency(SubGhzSetting* setting, uint32_t f
#endif
#ifdef ENABLE_DUAL_RX_SCENE
static ProtoPirateProtocolRegistryFilter protopirate_setting_preset_filter(
static GDRProtocolRegistryFilter gdr_setting_preset_filter(
SubGhzSetting* setting,
uint8_t index) {
return protopirate_get_protocol_registry_filter_for_preset(
return gdr_get_protocol_registry_filter_for_preset(
subghz_setting_get_preset_data(setting, index),
subghz_setting_get_preset_data_size(setting, index));
}
static uint8_t protopirate_find_preset_by_name_or_filter(
static uint8_t gdr_find_preset_by_name_or_filter(
SubGhzSetting* setting,
const char* preferred_name,
ProtoPirateProtocolRegistryFilter filter) {
GDRProtocolRegistryFilter filter) {
size_t count = subghz_setting_get_preset_count(setting);
for(size_t i = 0; i < count; i++) {
if(strcmp(subghz_setting_get_preset_name(setting, i), preferred_name) == 0 &&
protopirate_setting_preset_filter(setting, (uint8_t)i) == filter) {
gdr_setting_preset_filter(setting, (uint8_t)i) == filter) {
return (uint8_t)i;
}
}
for(size_t i = 0; i < count; i++) {
if(protopirate_setting_preset_filter(setting, (uint8_t)i) == filter) {
if(gdr_setting_preset_filter(setting, (uint8_t)i) == filter) {
return (uint8_t)i;
}
}
@@ -54,7 +54,7 @@ static uint8_t protopirate_find_preset_by_name_or_filter(
}
static uint8_t
protopirate_find_preset_by_name(SubGhzSetting* setting, const char* preset_name) {
gdr_find_preset_by_name(SubGhzSetting* setting, const char* preset_name) {
if(!preset_name || preset_name[0] == '\0') {
return UINT8_MAX;
}
@@ -69,25 +69,25 @@ static uint8_t
}
#endif
static bool protopirate_app_custom_event_callback(void* context, uint32_t event) {
static bool gdr_app_custom_event_callback(void* context, uint32_t event) {
furi_check(context);
ProtoPirateApp* app = context;
GDRApp* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
static bool protopirate_app_back_event_callback(void* context) {
static bool gdr_app_back_event_callback(void* context) {
furi_check(context);
ProtoPirateApp* app = context;
GDRApp* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static void protopirate_app_tick_event_callback(void* context) {
static void gdr_app_tick_event_callback(void* context) {
furi_check(context);
ProtoPirateApp* app = context;
GDRApp* app = context;
scene_manager_handle_tick_event(app->scene_manager);
}
bool protopirate_ensure_variable_item_list(ProtoPirateApp* app) {
bool gdr_ensure_variable_item_list(GDRApp* app) {
furi_check(app);
if(app->variable_item_list) {
return true;
@@ -100,12 +100,12 @@ bool protopirate_ensure_variable_item_list(ProtoPirateApp* app) {
view_dispatcher_add_view(
app->view_dispatcher,
ProtoPirateViewVariableItemList,
GDRViewVariableItemList,
variable_item_list_get_view(app->variable_item_list));
return true;
}
bool protopirate_ensure_widget(ProtoPirateApp* app) {
bool gdr_ensure_widget(GDRApp* app) {
furi_check(app);
if(app->widget) {
return true;
@@ -117,11 +117,11 @@ bool protopirate_ensure_widget(ProtoPirateApp* app) {
}
view_dispatcher_add_view(
app->view_dispatcher, ProtoPirateViewWidget, widget_get_view(app->widget));
app->view_dispatcher, GDRViewWidget, widget_get_view(app->widget));
return true;
}
bool protopirate_ensure_text_input(ProtoPirateApp* app) {
bool gdr_ensure_text_input(GDRApp* app) {
furi_check(app);
if(app->text_input) {
return true;
@@ -133,11 +133,11 @@ bool protopirate_ensure_text_input(ProtoPirateApp* app) {
}
view_dispatcher_add_view(
app->view_dispatcher, ProtoPirateViewTextInput, text_input_get_view(app->text_input));
app->view_dispatcher, GDRViewTextInput, text_input_get_view(app->text_input));
return true;
}
bool protopirate_ensure_view_about(ProtoPirateApp* app) {
bool gdr_ensure_view_about(GDRApp* app) {
furi_check(app);
if(app->view_about) {
return true;
@@ -148,49 +148,49 @@ bool protopirate_ensure_view_about(ProtoPirateApp* app) {
return false;
}
view_dispatcher_add_view(app->view_dispatcher, ProtoPirateViewAbout, app->view_about);
view_dispatcher_add_view(app->view_dispatcher, GDRViewAbout, app->view_about);
return true;
}
bool protopirate_ensure_receiver_view(ProtoPirateApp* app) {
bool gdr_ensure_receiver_view(GDRApp* app) {
furi_check(app);
if(app->protopirate_receiver) {
if(app->gdr_receiver) {
return true;
}
app->protopirate_receiver = protopirate_view_receiver_alloc(app->auto_save);
if(!app->protopirate_receiver) {
app->gdr_receiver = gdr_view_receiver_alloc(app->auto_save);
if(!app->gdr_receiver) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher,
ProtoPirateViewReceiver,
protopirate_view_receiver_get_view(app->protopirate_receiver));
GDRViewReceiver,
gdr_view_receiver_get_view(app->gdr_receiver));
return true;
}
#ifdef ENABLE_DUAL_RX_SCENE
bool protopirate_ensure_dual_receiver_view(ProtoPirateApp* app) {
bool gdr_ensure_dual_receiver_view(GDRApp* app) {
furi_check(app);
if(app->dual_receiver) {
return true;
}
app->dual_receiver = protopirate_view_dual_receiver_alloc();
app->dual_receiver = gdr_view_dual_receiver_alloc();
if(!app->dual_receiver) {
return false;
}
view_dispatcher_add_view(
app->view_dispatcher,
ProtoPirateViewDualReceiver,
protopirate_view_dual_receiver_get_view(app->dual_receiver));
GDRViewDualReceiver,
gdr_view_dual_receiver_get_view(app->dual_receiver));
return true;
}
#endif
static void protopirate_radio_init_cleanup(ProtoPirateApp* app, bool devices_initialized) {
static void gdr_radio_init_cleanup(GDRApp* app, bool devices_initialized) {
furi_check(app);
furi_check(app->txrx);
@@ -228,21 +228,21 @@ static void protopirate_radio_init_cleanup(ProtoPirateApp* app, bool devices_ini
app->txrx->protocol_registry = NULL;
app->txrx->protocol_plugin = NULL;
app->txrx->protocol_registry_filter = ProtoPirateProtocolRegistryFilterAM;
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->txrx->protocol_registry_filter = GDRProtocolRegistryFilterAM;
app->txrx->txrx_state = GDRTxRxStateIDLE;
app->radio_initialized = false;
}
ProtoPirateApp* protopirate_app_alloc() {
protopirate_storage_purge_temp_history_at_startup();
ProtoPirateApp* app = malloc(sizeof(ProtoPirateApp));
GDRApp* gdr_app_alloc() {
gdr_storage_purge_temp_history_at_startup();
GDRApp* app = malloc(sizeof(GDRApp));
if(!app) {
FURI_LOG_E(TAG, "Failed to allocate ProtoPirateApp app !");
FURI_LOG_E(TAG, "Failed to allocate GDRApp app !");
return NULL;
}
memset(app, 0, sizeof(ProtoPirateApp));
memset(app, 0, sizeof(GDRApp));
FURI_LOG_I(TAG, "Allocating ProtoPirate Decoder App");
FURI_LOG_I(TAG, "Allocating GDR Decoder App");
// GUI
app->gui = furi_record_open(RECORD_GUI);
@@ -252,15 +252,15 @@ ProtoPirateApp* protopirate_app_alloc() {
#if defined(FW_ORIGIN_RM)
view_dispatcher_enable_queue(app->view_dispatcher);
#endif
app->scene_manager = scene_manager_alloc(&protopirate_scene_handlers, app);
app->scene_manager = scene_manager_alloc(&gdr_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, protopirate_app_custom_event_callback);
app->view_dispatcher, gdr_app_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, protopirate_app_back_event_callback);
app->view_dispatcher, gdr_app_back_event_callback);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, protopirate_app_tick_event_callback, 100);
app->view_dispatcher, gdr_app_tick_event_callback, 100);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
@@ -273,7 +273,7 @@ ProtoPirateApp* protopirate_app_alloc() {
// SubMenu
app->submenu = submenu_alloc();
view_dispatcher_add_view(
app->view_dispatcher, ProtoPirateViewSubmenu, submenu_get_view(app->submenu));
app->view_dispatcher, GDRViewSubmenu, submenu_get_view(app->submenu));
app->save_protocol = NULL;
app->save_from_saved_info = false;
@@ -283,11 +283,11 @@ ProtoPirateApp* protopirate_app_alloc() {
// File Browser path
app->file_path = furi_string_alloc();
furi_string_set(app->file_path, PROTOPIRATE_APP_FOLDER);
furi_string_set(app->file_path, GDR_APP_FOLDER);
// Load saved settings
ProtoPirateSettings settings;
protopirate_settings_load(&settings);
GDRSettings settings;
gdr_settings_load(&settings);
// Apply auto-save setting
app->auto_save = settings.auto_save;
@@ -327,18 +327,18 @@ ProtoPirateApp* protopirate_app_alloc() {
}
// Initialize TxRx structure with minimal setup
app->lock = ProtoPirateLockOff;
app->txrx = malloc(sizeof(ProtoPirateTxRx));
app->lock = GDRLockOff;
app->txrx = malloc(sizeof(GDRTxRx));
furi_check(app->txrx);
memset(app->txrx, 0, sizeof(ProtoPirateTxRx));
memset(app->txrx, 0, sizeof(GDRTxRx));
app->txrx->preset = malloc(sizeof(SubGhzRadioPreset));
furi_check(app->txrx->preset);
app->txrx->preset->name = furi_string_alloc();
furi_check(app->txrx->preset->name);
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->txrx->rx_key_state = ProtoPirateRxKeyStateIDLE;
app->txrx->protocol_registry_filter = ProtoPirateProtocolRegistryFilterAM;
app->txrx->txrx_state = GDRTxRxStateIDLE;
app->txrx->rx_key_state = GDRRxKeyStateIDLE;
app->txrx->protocol_registry_filter = GDRProtocolRegistryFilterAM;
// Get preset name and data
const char* preset_name = subghz_setting_get_preset_name(app->setting, preset_index);
@@ -353,22 +353,22 @@ ProtoPirateApp* protopirate_app_alloc() {
settings.auto_save,
settings.hopping_enabled);
protopirate_preset_init(app, preset_name, frequency, preset_data, preset_data_size);
gdr_preset_init(app, preset_name, frequency, preset_data, preset_data_size);
#ifdef ENABLE_DUAL_RX_SCENE
uint32_t default_frequency = subghz_setting_get_default_frequency(app->setting);
app->dual_freq_a = protopirate_setting_has_frequency(app->setting, settings.dual_freq_a) ?
app->dual_freq_a = gdr_setting_has_frequency(app->setting, settings.dual_freq_a) ?
settings.dual_freq_a :
default_frequency;
app->dual_freq_b = protopirate_setting_has_frequency(app->setting, settings.dual_freq_b) ?
app->dual_freq_b = gdr_setting_has_frequency(app->setting, settings.dual_freq_b) ?
settings.dual_freq_b :
default_frequency;
uint8_t preset_count = (uint8_t)subghz_setting_get_preset_count(app->setting);
uint8_t named_preset_a =
protopirate_find_preset_by_name(app->setting, settings.dual_preset_name_a);
gdr_find_preset_by_name(app->setting, settings.dual_preset_name_a);
uint8_t named_preset_b =
protopirate_find_preset_by_name(app->setting, settings.dual_preset_name_b);
gdr_find_preset_by_name(app->setting, settings.dual_preset_name_b);
app->dual_preset_a =
named_preset_a != UINT8_MAX ? named_preset_a : settings.dual_preset_a;
app->dual_preset_b =
@@ -377,15 +377,15 @@ ProtoPirateApp* protopirate_app_alloc() {
app->dual_preset_a = UINT8_MAX;
app->dual_preset_b = UINT8_MAX;
} else if(app->dual_preset_a >= preset_count) {
app->dual_preset_a = protopirate_find_preset_by_name_or_filter(
app->setting, "AM650", ProtoPirateProtocolRegistryFilterAM);
app->dual_preset_a = gdr_find_preset_by_name_or_filter(
app->setting, "AM650", GDRProtocolRegistryFilterAM);
if(app->dual_preset_a == UINT8_MAX) {
app->dual_preset_a = 0;
}
}
if(app->dual_preset_b >= preset_count) {
app->dual_preset_b = protopirate_find_preset_by_name_or_filter(
app->setting, "FM476", ProtoPirateProtocolRegistryFilterFM);
app->dual_preset_b = gdr_find_preset_by_name_or_filter(
app->setting, "FM476", GDRProtocolRegistryFilterFM);
if(app->dual_preset_b == UINT8_MAX) {
app->dual_preset_b = 0;
}
@@ -395,7 +395,7 @@ ProtoPirateApp* protopirate_app_alloc() {
#ifdef ENABLE_SHIELD_RX_SCENE
{
uint32_t default_frequency = subghz_setting_get_default_frequency(app->setting);
app->shield_freq = protopirate_setting_has_frequency(app->setting, settings.shield_freq) ?
app->shield_freq = gdr_setting_has_frequency(app->setting, settings.shield_freq) ?
settings.shield_freq :
default_frequency;
app->shield_preset_index = settings.shield_preset_index;
@@ -414,8 +414,8 @@ ProtoPirateApp* protopirate_app_alloc() {
#endif
// Apply hopping state from settings
app->txrx->hopper_state = settings.hopping_enabled ? ProtoPirateHopperStateRunning :
ProtoPirateHopperStateOFF;
app->txrx->hopper_state = settings.hopping_enabled ? GDRHopperStateRunning :
GDRHopperStateOFF;
app->txrx->hopper_idx_frequency = 0;
app->txrx->hopper_timeout = 0;
app->txrx->idx_menu_chosen = 0;
@@ -425,11 +425,11 @@ ProtoPirateApp* protopirate_app_alloc() {
return app;
}
bool protopirate_radio_init(ProtoPirateApp* app) {
bool gdr_radio_init(GDRApp* app) {
furi_check(app);
furi_check(app->txrx);
FURI_LOG_I(TAG, "=== protopirate_radio_init called ===");
FURI_LOG_I(TAG, "=== gdr_radio_init called ===");
FURI_LOG_D(TAG, "State: radio_initialized=%d", app->radio_initialized);
if(app->radio_initialized) {
@@ -445,7 +445,7 @@ bool protopirate_radio_init(ProtoPirateApp* app) {
"Radio marked initialized but resources missing (env=%p device=%p), repairing",
app->txrx->environment,
app->txrx->radio_device);
protopirate_radio_deinit(app);
gdr_radio_deinit(app);
}
// Fresh radio init - nothing was initialized before
@@ -455,24 +455,24 @@ bool protopirate_radio_init(ProtoPirateApp* app) {
app->txrx->environment = subghz_environment_alloc();
if(!app->txrx->environment) {
FURI_LOG_E(TAG, "Failed to allocate environment!");
protopirate_radio_init_cleanup(app, false);
gdr_radio_init_cleanup(app, false);
return false;
}
app->txrx->protocol_registry = NULL;
if(!protopirate_refresh_protocol_registry(app, false)) {
if(!gdr_refresh_protocol_registry(app, false)) {
FURI_LOG_E(TAG, "Failed to configure protocol registry");
protopirate_radio_init_cleanup(app, false);
gdr_radio_init_cleanup(app, false);
return false;
}
// Load keystores
subghz_environment_load_keystore(app->txrx->environment, PROTOPIRATE_KEYSTORE_DIR_NAME);
subghz_environment_load_keystore(app->txrx->environment, GDR_KEYSTORE_DIR_NAME);
// Load ProtoPirate specific keys
protopirate_keys_load(app->txrx->environment);
FURI_LOG_I(TAG, "Loaded ProtoPirate secure keys");
// Load GDR specific keys
gdr_keys_load(app->txrx->environment);
FURI_LOG_I(TAG, "Loaded GDR secure keys");
// Initialize SubGhz devices
subghz_devices_init();
@@ -489,7 +489,7 @@ bool protopirate_radio_init(ProtoPirateApp* app) {
if(!app->txrx->radio_device) {
FURI_LOG_E(TAG, "Failed to initialize any radio device!");
protopirate_radio_init_cleanup(app, true);
gdr_radio_init_cleanup(app, true);
return false;
}
#ifndef REMOVE_LOGS
@@ -512,8 +512,8 @@ bool protopirate_radio_init(ProtoPirateApp* app) {
}
// Deinitialize radio subsystem
void protopirate_radio_deinit(ProtoPirateApp* app) {
FURI_LOG_I(TAG, "=== protopirate_radio_deinit called ===");
void gdr_radio_deinit(GDRApp* app) {
FURI_LOG_I(TAG, "=== gdr_radio_deinit called ===");
FURI_LOG_D(TAG, "State: radio_initialized=%d", app->radio_initialized);
FURI_LOG_D(
TAG,
@@ -535,7 +535,7 @@ void protopirate_radio_deinit(ProtoPirateApp* app) {
bool devices_initialized = app->radio_initialized || (app->txrx->radio_device != NULL);
// Make sure we're not receiving
if(app->txrx->worker && app->txrx->txrx_state == ProtoPirateTxRxStateRx) {
if(app->txrx->worker && app->txrx->txrx_state == GDRTxRxStateRx) {
FURI_LOG_D(TAG, "Stopping active RX, state=%d", app->txrx->txrx_state);
subghz_worker_stop(app->txrx->worker);
if(app->txrx->radio_device) {
@@ -590,9 +590,9 @@ void protopirate_radio_deinit(ProtoPirateApp* app) {
if(app->txrx->history) {
FURI_LOG_D(TAG, "Freeing history %p", app->txrx->history);
if(app->selected_capture.history == app->txrx->history) {
protopirate_selected_capture_clear(app);
gdr_selected_capture_clear(app);
}
protopirate_history_free(app->txrx->history);
gdr_history_free(app->txrx->history);
app->txrx->history = NULL;
} else {
FURI_LOG_D(TAG, "History was NULL, skipping free");
@@ -606,25 +606,25 @@ void protopirate_radio_deinit(ProtoPirateApp* app) {
FURI_LOG_D(TAG, "Worker was NULL, skipping free");
}
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->txrx->txrx_state = GDRTxRxStateIDLE;
app->radio_initialized = false;
FURI_LOG_D(TAG, "Final state: radio_initialized=%d", app->radio_initialized);
}
void protopirate_app_free(ProtoPirateApp* app) {
void gdr_app_free(GDRApp* app) {
furi_check(app);
FURI_LOG_I(TAG, "=== protopirate_app_free called ===");
FURI_LOG_I(TAG, "=== gdr_app_free called ===");
FURI_LOG_D(TAG, "State: radio_initialized=%d", app->radio_initialized);
// Save settings before exiting
ProtoPirateSettings settings;
protopirate_settings_load(&settings);
GDRSettings settings;
gdr_settings_load(&settings);
settings.frequency = app->txrx->preset->frequency;
settings.auto_save = app->auto_save;
settings.tx_power = app->tx_power;
settings.hopping_enabled = (app->txrx->hopper_state != ProtoPirateHopperStateOFF);
settings.hopping_enabled = (app->txrx->hopper_state != GDRHopperStateOFF);
settings.emulate_feature_enabled = app->emulate_feature_enabled;
#ifdef ENABLE_DUAL_RX_SCENE
settings.dual_freq_a = app->dual_freq_a;
@@ -675,11 +675,11 @@ void protopirate_app_free(ProtoPirateApp* app) {
settings.hopping_enabled,
settings.emulate_feature_enabled);
protopirate_settings_save(&settings);
gdr_settings_save(&settings);
// Deinitialize whichever is active - NULL checks inside handle all cases
FURI_LOG_D(TAG, "Calling radio_deinit");
protopirate_radio_deinit(app);
gdr_radio_deinit(app);
if(app->loaded_file_path) {
FURI_LOG_D(TAG, "Freeing loaded_file_path");
@@ -690,21 +690,21 @@ void protopirate_app_free(ProtoPirateApp* app) {
// Submenu
if(app->submenu) {
FURI_LOG_D(TAG, "Removing submenu view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewSubmenu);
view_dispatcher_remove_view(app->view_dispatcher, GDRViewSubmenu);
submenu_free(app->submenu);
}
// Variable Item List
if(app->variable_item_list) {
FURI_LOG_D(TAG, "Removing variable_item_list view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewVariableItemList);
view_dispatcher_remove_view(app->view_dispatcher, GDRViewVariableItemList);
variable_item_list_free(app->variable_item_list);
}
// About View
if(app->view_about) {
FURI_LOG_D(TAG, "Removing about view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewAbout);
view_dispatcher_remove_view(app->view_dispatcher, GDRViewAbout);
view_free(app->view_about);
}
@@ -717,14 +717,14 @@ void protopirate_app_free(ProtoPirateApp* app) {
// Widget
if(app->widget) {
FURI_LOG_D(TAG, "Removing widget view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewWidget);
view_dispatcher_remove_view(app->view_dispatcher, GDRViewWidget);
widget_free(app->widget);
}
// Text Input
if(app->text_input) {
FURI_LOG_D(TAG, "Removing text_input view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewTextInput);
view_dispatcher_remove_view(app->view_dispatcher, GDRViewTextInput);
text_input_free(app->text_input);
}
if(app->save_protocol) {
@@ -733,21 +733,21 @@ void protopirate_app_free(ProtoPirateApp* app) {
}
// Receiver
if(app->protopirate_receiver) {
if(app->gdr_receiver) {
FURI_LOG_D(TAG, "Removing receiver view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewReceiver);
protopirate_view_receiver_free(app->protopirate_receiver);
view_dispatcher_remove_view(app->view_dispatcher, GDRViewReceiver);
gdr_view_receiver_free(app->gdr_receiver);
}
#ifdef ENABLE_DUAL_RX_SCENE
bool dual_devices_initialized = app->dual_chain_a || app->dual_chain_b;
if(app->dual_chain_a) {
protopirate_rx_chain_free(app->dual_chain_a);
gdr_rx_chain_free(app->dual_chain_a);
app->dual_chain_a = NULL;
}
if(app->dual_chain_b) {
protopirate_rx_chain_free(app->dual_chain_b);
gdr_rx_chain_free(app->dual_chain_b);
app->dual_chain_b = NULL;
}
if(dual_devices_initialized) {
@@ -755,15 +755,15 @@ void protopirate_app_free(ProtoPirateApp* app) {
}
if(app->dual_receiver) {
FURI_LOG_D(TAG, "Removing dual receiver view");
view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewDualReceiver);
protopirate_view_dual_receiver_free(app->dual_receiver);
view_dispatcher_remove_view(app->view_dispatcher, GDRViewDualReceiver);
gdr_view_dual_receiver_free(app->dual_receiver);
app->dual_receiver = NULL;
}
if(app->dual_history) {
if(app->selected_capture.history == app->dual_history) {
protopirate_selected_capture_clear(app);
gdr_selected_capture_clear(app);
}
protopirate_history_free(app->dual_history);
gdr_history_free(app->dual_history);
app->dual_history = NULL;
}
if(app->dual_history_mutex) {
@@ -775,11 +775,11 @@ void protopirate_app_free(ProtoPirateApp* app) {
#ifdef ENABLE_SHIELD_RX_SCENE
bool shield_devices_initialized = app->shield_rx_chain || app->shield_tx_chain;
if(app->shield_rx_chain) {
protopirate_rx_chain_free(app->shield_rx_chain);
gdr_rx_chain_free(app->shield_rx_chain);
app->shield_rx_chain = NULL;
}
if(app->shield_tx_chain) {
protopirate_tx_chain_free(app->shield_tx_chain);
gdr_tx_chain_free(app->shield_tx_chain);
app->shield_tx_chain = NULL;
}
if(shield_devices_initialized) {
@@ -787,9 +787,9 @@ void protopirate_app_free(ProtoPirateApp* app) {
}
if(app->shield_history) {
if(app->selected_capture.history == app->shield_history) {
protopirate_selected_capture_clear(app);
gdr_selected_capture_clear(app);
}
protopirate_history_free(app->shield_history);
gdr_history_free(app->shield_history);
app->shield_history = NULL;
}
if(app->shield_history_mutex) {
@@ -798,7 +798,7 @@ void protopirate_app_free(ProtoPirateApp* app) {
}
#endif
protopirate_psa_bf_context_release(app);
gdr_psa_bf_context_release(app);
// Setting
FURI_LOG_D(TAG, "Freeing subghz_setting");
@@ -812,7 +812,7 @@ void protopirate_app_free(ProtoPirateApp* app) {
free(app->txrx);
#ifdef ENABLE_EMULATE_FEATURE
protopirate_emulate_context_release(app);
gdr_emulate_context_release(app);
#endif
pp_shared_upload_release();
@@ -840,38 +840,38 @@ void protopirate_app_free(ProtoPirateApp* app) {
free(app);
}
int32_t protopirate_app(char* p) {
int32_t gdr_app(char* p) {
furi_hal_power_suppress_charge_enter();
ProtoPirateApp* protopirate_app = protopirate_app_alloc();
if(!protopirate_app) {
// logging is already done in protopirate_app_alloc()
GDRApp* gdr_app = gdr_app_alloc();
if(!gdr_app) {
// logging is already done in gdr_app_alloc()
furi_hal_power_suppress_charge_exit();
return -1;
}
// Handle Command line PSF that may have been passed to us
bool load_saved = (p && strlen(p));
if(load_saved) protopirate_app->loaded_file_path = furi_string_alloc_set(p);
if(load_saved) gdr_app->loaded_file_path = furi_string_alloc_set(p);
scene_manager_next_scene(
protopirate_app->scene_manager,
(load_saved) ? ProtoPirateSceneSavedInfo : ProtoPirateSceneStart);
gdr_app->scene_manager,
(load_saved) ? GDRSceneSavedInfo : GDRSceneStart);
//We now jump straight to emulate scene from Browser. If the user wanted the key to look at, just click back.
if(load_saved) {
if(protopirate_app->emulate_feature_enabled) {
if(gdr_app->emulate_feature_enabled) {
view_dispatcher_send_custom_event(
protopirate_app->view_dispatcher, ProtoPirateCustomEventSavedInfoEmulate);
notification_message(protopirate_app->notifications, &sequence_success);
gdr_app->view_dispatcher, GDRCustomEventSavedInfoEmulate);
notification_message(gdr_app->notifications, &sequence_success);
} else {
view_dispatcher_send_custom_event(
protopirate_app->view_dispatcher, ProtoPirateCustomEventReceiverInfoSave);
gdr_app->view_dispatcher, GDRCustomEventReceiverInfoSave);
}
}
view_dispatcher_run(protopirate_app->view_dispatcher);
view_dispatcher_run(gdr_app->view_dispatcher);
protopirate_app_free(protopirate_app);
gdr_app_free(gdr_app);
furi_hal_power_suppress_charge_exit();
@@ -1,17 +1,17 @@
// protopirate_app_i.c
#include "protopirate_app_i.h"
// gdr_app_i.c
#include "gdr_app_i.h"
#include "protocols/protocol_items.h"
#include <loader/firmware_api/firmware_api.h>
#include <stdio.h>
#define TAG "ProtoPirateTxRx"
#define TAG "GDRTxRx"
void protopirate_selected_capture_set(
ProtoPirateApp* app,
ProtoPirateHistory* history,
void gdr_selected_capture_set(
GDRApp* app,
GDRHistory* history,
FuriMutex* mutex,
uint16_t index,
ProtoPirateCaptureOwner owner) {
GDRCaptureOwner owner) {
furi_check(app);
app->selected_capture.history = history;
app->selected_capture.mutex = mutex;
@@ -19,75 +19,75 @@ void protopirate_selected_capture_set(
app->selected_capture.owner = owner;
}
void protopirate_selected_capture_clear(ProtoPirateApp* app) {
void gdr_selected_capture_clear(GDRApp* app) {
furi_check(app);
memset(&app->selected_capture, 0, sizeof(app->selected_capture));
}
bool protopirate_selected_capture_is_valid(ProtoPirateApp* app) {
bool gdr_selected_capture_is_valid(GDRApp* app) {
furi_check(app);
ProtoPirateSelectedCapture* selected = &app->selected_capture;
if(!selected->history || selected->owner == ProtoPirateCaptureOwnerNone) {
GDRSelectedCapture* selected = &app->selected_capture;
if(!selected->history || selected->owner == GDRCaptureOwnerNone) {
return false;
}
if(selected->mutex) {
furi_mutex_acquire(selected->mutex, FuriWaitForever);
}
bool valid = selected->index < protopirate_history_get_item(selected->history);
bool valid = selected->index < gdr_history_get_item(selected->history);
if(selected->mutex) {
furi_mutex_release(selected->mutex);
}
return valid;
}
ProtoPirateHistory* protopirate_selected_capture_get_history(ProtoPirateApp* app) {
return protopirate_selected_capture_is_valid(app) ? app->selected_capture.history : NULL;
GDRHistory* gdr_selected_capture_get_history(GDRApp* app) {
return gdr_selected_capture_is_valid(app) ? app->selected_capture.history : NULL;
}
uint16_t protopirate_selected_capture_get_index(ProtoPirateApp* app) {
uint16_t gdr_selected_capture_get_index(GDRApp* app) {
furi_check(app);
return app->selected_capture.index;
}
ProtoPirateHistorySource protopirate_selected_capture_get_source(ProtoPirateApp* app) {
ProtoPirateHistory* history = protopirate_selected_capture_get_history(app);
GDRHistorySource gdr_selected_capture_get_source(GDRApp* app) {
GDRHistory* history = gdr_selected_capture_get_history(app);
if(!history) {
return ProtoPirateHistorySourceUnknown;
return GDRHistorySourceUnknown;
}
return protopirate_history_get_source(history, app->selected_capture.index);
return gdr_history_get_source(history, app->selected_capture.index);
}
FlipperFormat* protopirate_selected_capture_get_raw_data(ProtoPirateApp* app) {
ProtoPirateHistory* history = protopirate_selected_capture_get_history(app);
FlipperFormat* gdr_selected_capture_get_raw_data(GDRApp* app) {
GDRHistory* history = gdr_selected_capture_get_history(app);
if(!history) {
return NULL;
}
return protopirate_history_get_raw_data(history, app->selected_capture.index);
return gdr_history_get_raw_data(history, app->selected_capture.index);
}
bool protopirate_selected_capture_get_path(ProtoPirateApp* app, FuriString* out_path) {
bool gdr_selected_capture_get_path(GDRApp* app, FuriString* out_path) {
furi_check(out_path);
ProtoPirateHistory* history = protopirate_selected_capture_get_history(app);
GDRHistory* history = gdr_selected_capture_get_history(app);
if(!history) {
return false;
}
return protopirate_history_get_capture_path(history, app->selected_capture.index, out_path);
return gdr_history_get_capture_path(history, app->selected_capture.index, out_path);
}
void protopirate_selected_capture_release_scratch(ProtoPirateApp* app) {
void gdr_selected_capture_release_scratch(GDRApp* app) {
furi_check(app);
if(app->selected_capture.history) {
protopirate_history_release_scratch(app->selected_capture.history);
gdr_history_release_scratch(app->selected_capture.history);
}
}
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 const char* gdr_get_registry_plugin_path(GDRProtocolRegistryFilter filter) {
return (filter == GDRProtocolRegistryFilterFM) ?
APP_ASSETS_PATH("plugins/gdr_fm_plugin.fal") :
APP_ASSETS_PATH("plugins/gdr_am_plugin.fal");
}
static void protopirate_unload_protocol_plugin(ProtoPirateTxRx* txrx) {
static void gdr_unload_protocol_plugin(GDRTxRx* txrx) {
furi_check(txrx);
txrx->protocol_plugin = NULL;
@@ -104,12 +104,12 @@ static void protopirate_unload_protocol_plugin(ProtoPirateTxRx* txrx) {
}
}
static void protopirate_teardown_receiver_stack_for_registry_switch(ProtoPirateApp* app) {
static void gdr_teardown_receiver_stack_for_registry_switch(GDRApp* app) {
furi_check(app);
furi_check(app->txrx);
if(app->txrx->txrx_state == ProtoPirateTxRxStateRx) {
protopirate_rx_end(app);
if(app->txrx->txrx_state == GDRTxRxStateRx) {
gdr_rx_end(app);
}
if(app->txrx->receiver) {
@@ -126,15 +126,15 @@ static void protopirate_teardown_receiver_stack_for_registry_switch(ProtoPirateA
app->txrx->worker = NULL;
}
if(app->txrx->radio_device && app->txrx->txrx_state != ProtoPirateTxRxStateTx) {
if(app->txrx->radio_device && app->txrx->txrx_state != GDRTxRxStateTx) {
subghz_devices_idle(app->txrx->radio_device);
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->txrx->txrx_state = GDRTxRxStateIDLE;
}
}
static bool protopirate_ensure_protocol_registry_plugin(
ProtoPirateApp* app,
ProtoPirateProtocolRegistryFilter filter,
static bool gdr_ensure_protocol_registry_plugin(
GDRApp* app,
GDRProtocolRegistryFilter filter,
const SubGhzProtocolRegistry** registry) {
furi_check(app);
furi_check(app->txrx);
@@ -155,7 +155,7 @@ static bool protopirate_ensure_protocol_registry_plugin(
if(app->txrx->protocol_plugin || app->txrx->protocol_plugin_manager ||
app->txrx->plugin_resolver) {
protopirate_unload_protocol_plugin(app->txrx);
gdr_unload_protocol_plugin(app->txrx);
}
CompositeApiResolver* resolver = composite_api_resolver_alloc();
@@ -166,8 +166,8 @@ static bool protopirate_ensure_protocol_registry_plugin(
composite_api_resolver_add(resolver, firmware_api_interface);
PluginManager* manager = plugin_manager_alloc(
PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
GDR_PROTOCOL_PLUGIN_APP_ID,
GDR_PROTOCOL_PLUGIN_API_VERSION,
composite_api_resolver_get(resolver));
if(!manager) {
FURI_LOG_E(TAG, "Failed to allocate protocol plugin manager");
@@ -175,7 +175,7 @@ static bool protopirate_ensure_protocol_registry_plugin(
return false;
}
const char* plugin_path = protopirate_get_registry_plugin_path(filter);
const char* plugin_path = gdr_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);
@@ -184,7 +184,7 @@ static bool protopirate_ensure_protocol_registry_plugin(
return false;
}
const ProtoPirateProtocolPlugin* plugin = plugin_manager_get_ep(manager, 0U);
const GDRProtocolPlugin* 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);
@@ -208,7 +208,7 @@ static bool protopirate_ensure_protocol_registry_plugin(
return true;
}
bool protopirate_refresh_protocol_registry(ProtoPirateApp* app, bool ensure_receiver_ready) {
bool gdr_refresh_protocol_registry(GDRApp* app, bool ensure_receiver_ready) {
furi_check(app);
furi_check(app->txrx);
@@ -216,23 +216,23 @@ bool protopirate_refresh_protocol_registry(ProtoPirateApp* app, bool ensure_rece
return true;
}
ProtoPirateProtocolRegistryFilter filter = protopirate_get_protocol_registry_filter_for_preset(
GDRProtocolRegistryFilter filter = gdr_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);
gdr_teardown_receiver_stack_for_registry_switch(app);
} else if(ensure_receiver_ready && !app->txrx->receiver) {
protopirate_teardown_receiver_stack_for_registry_switch(app);
gdr_teardown_receiver_stack_for_registry_switch(app);
}
const SubGhzProtocolRegistry* registry = NULL;
if(!protopirate_ensure_protocol_registry_plugin(app, filter, &registry) || !registry) {
if(!gdr_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));
gdr_get_protocol_registry_filter_name(filter));
return false;
}
@@ -241,7 +241,7 @@ bool protopirate_refresh_protocol_registry(ProtoPirateApp* app, bool ensure_rece
FURI_LOG_I(
TAG,
"Using %s protocol registry (%zu protocols)",
protopirate_get_protocol_registry_filter_name(filter),
gdr_get_protocol_registry_filter_name(filter),
registry->size);
subghz_environment_set_protocol_registry(app->txrx->environment, registry);
app->txrx->protocol_registry = registry;
@@ -260,7 +260,7 @@ bool protopirate_refresh_protocol_registry(ProtoPirateApp* app, bool ensure_rece
FURI_LOG_E(
TAG,
"Failed to allocate receiver for %s registry",
protopirate_get_protocol_registry_filter_name(filter));
gdr_get_protocol_registry_filter_name(filter));
return false;
}
@@ -268,8 +268,8 @@ bool protopirate_refresh_protocol_registry(ProtoPirateApp* app, bool ensure_rece
return true;
}
bool protopirate_apply_protocol_registry_for_preset_data(
ProtoPirateApp* app,
bool gdr_apply_protocol_registry_for_preset_data(
GDRApp* app,
const uint8_t* preset_data,
size_t preset_data_size) {
furi_check(app);
@@ -279,22 +279,22 @@ bool protopirate_apply_protocol_registry_for_preset_data(
return false;
}
ProtoPirateProtocolRegistryFilter filter =
protopirate_get_protocol_registry_filter_for_preset(preset_data, preset_data_size);
GDRProtocolRegistryFilter filter =
gdr_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);
gdr_teardown_receiver_stack_for_registry_switch(app);
}
const SubGhzProtocolRegistry* registry = NULL;
if(!protopirate_ensure_protocol_registry_plugin(app, filter, &registry) || !registry) {
if(!gdr_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));
gdr_get_protocol_registry_filter_name(filter));
return false;
}
@@ -305,29 +305,29 @@ bool protopirate_apply_protocol_registry_for_preset_data(
FURI_LOG_I(
TAG,
"Switching active protocol registry to %s (%zu protocols)",
protopirate_get_protocol_registry_filter_name(filter),
gdr_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 gdr_preset_init(
void* context,
const char* preset_name,
uint32_t frequency,
uint8_t* preset_data,
size_t preset_data_size) {
furi_check(context);
ProtoPirateApp* app = context;
GDRApp* app = context;
furi_string_set(app->txrx->preset->name, preset_name);
app->txrx->preset->frequency = frequency;
app->txrx->preset->data = preset_data;
app->txrx->preset->data_size = preset_data_size;
}
void protopirate_get_frequency_modulation_str(
ProtoPirateApp* app,
void gdr_get_frequency_modulation_str(
GDRApp* app,
char* frequency,
size_t frequency_size,
char* modulation,
@@ -346,15 +346,15 @@ void protopirate_get_frequency_modulation_str(
}
}
void protopirate_get_frequency_modulation(
ProtoPirateApp* app,
void gdr_get_frequency_modulation(
GDRApp* app,
FuriString* frequency,
FuriString* modulation) {
furi_check(app);
char frequency_buf[16] = {0};
char modulation_buf[8] = {0};
protopirate_get_frequency_modulation_str(
gdr_get_frequency_modulation_str(
app, frequency_buf, sizeof(frequency_buf), modulation_buf, sizeof(modulation_buf));
if(frequency != NULL) {
@@ -365,20 +365,20 @@ void protopirate_get_frequency_modulation(
}
}
void protopirate_begin(ProtoPirateApp* app, uint8_t* preset_data) {
void gdr_begin(GDRApp* app, uint8_t* preset_data) {
furi_check(app);
if(!app->txrx->radio_device) {
FURI_LOG_W(TAG, "begin requested without radio device");
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->txrx->txrx_state = GDRTxRxStateIDLE;
return;
}
subghz_devices_reset(app->txrx->radio_device);
subghz_devices_idle(app->txrx->radio_device);
subghz_devices_load_preset(app->txrx->radio_device, FuriHalSubGhzPresetCustom, preset_data);
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->txrx->txrx_state = GDRTxRxStateIDLE;
}
uint32_t protopirate_rx(ProtoPirateApp* app, uint32_t frequency) {
uint32_t gdr_rx(GDRApp* app, uint32_t frequency) {
furi_check(app);
furi_check(app->txrx);
if(!app->radio_initialized || !app->txrx->radio_device || !app->txrx->worker) {
@@ -388,15 +388,15 @@ uint32_t protopirate_rx(ProtoPirateApp* app, uint32_t frequency) {
app->radio_initialized,
app->txrx->radio_device,
app->txrx->worker);
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->txrx->txrx_state = GDRTxRxStateIDLE;
return 0;
}
if(!subghz_devices_is_frequency_valid(app->txrx->radio_device, frequency)) {
furi_crash("ProtoPirate: Incorrect RX frequency.");
furi_crash("GDR: Incorrect RX frequency.");
}
if(app->txrx->txrx_state == ProtoPirateTxRxStateRx ||
app->txrx->txrx_state == ProtoPirateTxRxStateSleep) {
if(app->txrx->txrx_state == GDRTxRxStateRx ||
app->txrx->txrx_state == GDRTxRxStateSleep) {
FURI_LOG_W(TAG, "RX start ignored in state %d", app->txrx->txrx_state);
return app->txrx->preset ? app->txrx->preset->frequency : 0;
}
@@ -410,24 +410,24 @@ uint32_t protopirate_rx(ProtoPirateApp* app, uint32_t frequency) {
app->txrx->radio_device, subghz_worker_rx_callback, app->txrx->worker);
subghz_worker_start(app->txrx->worker);
app->txrx->txrx_state = ProtoPirateTxRxStateRx;
app->txrx->txrx_state = GDRTxRxStateRx;
return value;
}
void protopirate_idle(ProtoPirateApp* app) {
void gdr_idle(GDRApp* app) {
furi_check(app);
furi_check(app->txrx->txrx_state != ProtoPirateTxRxStateSleep);
furi_check(app->txrx->txrx_state != GDRTxRxStateSleep);
if(app->txrx->radio_device) {
subghz_devices_idle(app->txrx->radio_device);
} else {
FURI_LOG_W(TAG, "idle requested without radio device");
}
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->txrx->txrx_state = GDRTxRxStateIDLE;
}
void protopirate_rx_end(ProtoPirateApp* app) {
void gdr_rx_end(GDRApp* app) {
furi_check(app);
if(!app->txrx || app->txrx->txrx_state != ProtoPirateTxRxStateRx) {
if(!app->txrx || app->txrx->txrx_state != GDRTxRxStateRx) {
return;
}
@@ -440,33 +440,33 @@ void protopirate_rx_end(ProtoPirateApp* app) {
subghz_devices_idle(app->txrx->radio_device);
}
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->txrx->txrx_state = GDRTxRxStateIDLE;
}
void protopirate_sleep(ProtoPirateApp* app) {
void gdr_sleep(GDRApp* app) {
furi_check(app);
subghz_devices_sleep(app->txrx->radio_device);
app->txrx->txrx_state = ProtoPirateTxRxStateSleep;
app->txrx->txrx_state = GDRTxRxStateSleep;
}
void protopirate_release_shared_radio_state(ProtoPirateApp* app) {
void gdr_release_shared_radio_state(GDRApp* app) {
furi_check(app);
furi_check(app->txrx);
if(app->protopirate_receiver) {
protopirate_view_receiver_reset_menu(app->protopirate_receiver);
if(app->gdr_receiver) {
gdr_view_receiver_reset_menu(app->gdr_receiver);
}
protopirate_radio_deinit(app);
gdr_radio_deinit(app);
}
void protopirate_rx_stack_suspend_for_tx(ProtoPirateApp* app) {
void gdr_rx_stack_suspend_for_tx(GDRApp* app) {
if(!app || !app->radio_initialized) {
return;
}
if(app->txrx->txrx_state == ProtoPirateTxRxStateRx) {
protopirate_rx_end(app);
if(app->txrx->txrx_state == GDRTxRxStateRx) {
gdr_rx_end(app);
}
if(app->txrx->worker && subghz_worker_is_running(app->txrx->worker)) {
@@ -477,30 +477,30 @@ void protopirate_rx_stack_suspend_for_tx(ProtoPirateApp* app) {
subghz_receiver_set_rx_callback(app->txrx->receiver, NULL, NULL);
}
if(app->txrx->radio_device && app->txrx->txrx_state != ProtoPirateTxRxStateTx) {
if(app->txrx->radio_device && app->txrx->txrx_state != GDRTxRxStateTx) {
subghz_devices_idle(app->txrx->radio_device);
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->txrx->txrx_state = GDRTxRxStateIDLE;
}
}
void protopirate_rx_stack_resume_after_tx(ProtoPirateApp* app) {
void gdr_rx_stack_resume_after_tx(GDRApp* app) {
if(!app || !app->radio_initialized || !app->txrx->environment) {
return;
}
if(!protopirate_refresh_protocol_registry(app, true)) {
if(!gdr_refresh_protocol_registry(app, true)) {
FURI_LOG_E(TAG, "rx_stack_resume: failed to restore RX stack");
}
}
void protopirate_hopper_update(ProtoPirateApp* app) {
void gdr_hopper_update(GDRApp* app) {
furi_check(app);
switch(app->txrx->hopper_state) {
case ProtoPirateHopperStateOFF:
case ProtoPirateHopperStatePause:
case GDRHopperStateOFF:
case GDRHopperStatePause:
return;
case ProtoPirateHopperStateRSSITimeOut:
case GDRHopperStateRSSITimeOut:
if(app->txrx->hopper_timeout != 0) {
app->txrx->hopper_timeout--;
return;
@@ -510,21 +510,21 @@ void protopirate_hopper_update(ProtoPirateApp* app) {
break;
}
float rssi = -127.0f;
if(app->txrx->hopper_state != ProtoPirateHopperStateRSSITimeOut) {
if(app->txrx->hopper_state != GDRHopperStateRSSITimeOut) {
rssi = subghz_devices_get_rssi(app->txrx->radio_device);
if(rssi > -90.0f) {
app->txrx->hopper_timeout = 10;
app->txrx->hopper_state = ProtoPirateHopperStateRSSITimeOut;
app->txrx->hopper_state = GDRHopperStateRSSITimeOut;
return;
}
} else {
app->txrx->hopper_state = ProtoPirateHopperStateRunning;
app->txrx->hopper_state = GDRHopperStateRunning;
}
const size_t hopper_count = subghz_setting_get_hopper_frequency_count(app->setting);
if(hopper_count == 0) {
app->txrx->hopper_state = ProtoPirateHopperStateOFF;
app->txrx->hopper_state = GDRHopperStateOFF;
app->txrx->hopper_idx_frequency = 0;
return;
}
@@ -534,36 +534,36 @@ void protopirate_hopper_update(ProtoPirateApp* app) {
app->txrx->hopper_idx_frequency = 0;
}
if(app->txrx->txrx_state == ProtoPirateTxRxStateRx) {
protopirate_rx_end(app);
if(app->txrx->txrx_state == GDRTxRxStateRx) {
gdr_rx_end(app);
}
if(app->txrx->txrx_state == ProtoPirateTxRxStateIDLE && app->txrx->receiver) {
if(app->txrx->txrx_state == GDRTxRxStateIDLE && app->txrx->receiver) {
subghz_receiver_reset(app->txrx->receiver);
app->txrx->preset->frequency =
subghz_setting_get_hopper_frequency(app->setting, app->txrx->hopper_idx_frequency);
protopirate_rx(app, app->txrx->preset->frequency);
gdr_rx(app, app->txrx->preset->frequency);
}
}
void protopirate_tx(ProtoPirateApp* app, uint32_t frequency) {
void gdr_tx(GDRApp* app, uint32_t frequency) {
furi_check(app);
if(!subghz_devices_is_frequency_valid(app->txrx->radio_device, frequency)) {
return;
}
furi_check(app->txrx->txrx_state == ProtoPirateTxRxStateIDLE);
furi_check(app->txrx->txrx_state == GDRTxRxStateIDLE);
subghz_devices_idle(app->txrx->radio_device);
subghz_devices_set_frequency(app->txrx->radio_device, frequency);
subghz_devices_set_tx(app->txrx->radio_device);
app->txrx->txrx_state = ProtoPirateTxRxStateTx;
app->txrx->txrx_state = GDRTxRxStateTx;
}
void protopirate_tx_stop(ProtoPirateApp* app) {
void gdr_tx_stop(GDRApp* app) {
furi_check(app);
furi_check(app->txrx->txrx_state == ProtoPirateTxRxStateTx);
furi_check(app->txrx->txrx_state == GDRTxRxStateTx);
subghz_devices_idle(app->txrx->radio_device);
app->txrx->txrx_state = ProtoPirateTxRxStateIDLE;
app->txrx->txrx_state = GDRTxRxStateIDLE;
}
@@ -0,0 +1,237 @@
// gdr_app_i.h
#pragma once
#include <stddef.h>
#include "helpers/gdr_types.h"
#include "helpers/gdr_settings.h"
#include "scenes/gdr_scene.h"
#include "views/gdr_receiver.h"
#include "gdr_history.h"
#include "helpers/radio_device_loader.h"
#ifdef ENABLE_DUAL_RX_SCENE
#include "helpers/gdr_rx_chain.h"
#include "views/gdr_dual_receiver.h"
#endif
#ifdef ENABLE_SHIELD_RX_SCENE
#include "helpers/gdr_rx_chain.h"
#include "helpers/gdr_tx_chain.h"
#endif
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h>
#include <gui/modules/text_input.h>
#include <notification/notification_messages.h>
#include <lib/subghz/subghz_setting.h>
#include <lib/subghz/subghz_worker.h>
#include <lib/subghz/receiver.h>
#include <lib/subghz/transmitter.h>
#include <lib/subghz/devices/devices.h>
#include <lib/subghz/subghz_file_encoder_worker.h>
#include <lib/flipper_application/plugins/plugin_manager.h>
#include <lib/flipper_application/plugins/composite_resolver.h>
#include <dialogs/dialogs.h>
#include "defines.h"
#include "protocols/protocols_common.h"
#include "protocols/protocol_items.h"
#include "protocols/gdr_protocol_plugins.h"
#ifdef ENABLE_EMULATE_FEATURE
#include "scenes/plugins/gdr_emulate_plugin.h"
#endif
#include "scenes/plugins/gdr_psa_bf_plugin.h"
#define GDR_KEYSTORE_DIR_NAME APP_ASSETS_PATH("encrypted")
typedef struct GDRApp GDRApp;
typedef enum {
GDRCaptureOwnerNone = 0,
GDRCaptureOwnerReceiver,
GDRCaptureOwnerDualReceiver,
#ifdef ENABLE_SHIELD_RX_SCENE
GDRCaptureOwnerShieldReceiver,
#endif
GDRCaptureOwnerSubDecode,
} GDRCaptureOwner;
typedef struct {
GDRHistory* history;
FuriMutex* mutex;
uint16_t index;
GDRCaptureOwner owner;
} GDRSelectedCapture;
typedef struct {
SubGhzWorker* worker;
SubGhzEnvironment* environment;
SubGhzReceiver* receiver;
SubGhzRadioPreset* preset;
const SubGhzProtocolRegistry* protocol_registry;
CompositeApiResolver* plugin_resolver;
PluginManager* protocol_plugin_manager;
const GDRProtocolPlugin* protocol_plugin;
GDRProtocolRegistryFilter protocol_registry_filter;
GDRHistory* history;
const SubGhzDevice* radio_device;
GDRTxRxState txrx_state;
GDRHopperState hopper_state;
GDRRxKeyState rx_key_state;
uint8_t hopper_idx_frequency;
uint8_t hopper_timeout;
uint16_t idx_menu_chosen;
} GDRTxRx;
struct GDRApp {
Gui* gui;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
NotificationApp* notifications;
DialogsApp* dialogs;
VariableItemList* variable_item_list;
Submenu* submenu;
Widget* widget;
TextInput* text_input;
View* view_about;
FuriString* file_path;
GDRReceiver* gdr_receiver;
GDRTxRx* txrx;
SubGhzSetting* setting;
GDRLock lock;
FuriString* loaded_file_path;
bool auto_save;
bool radio_initialized;
GDRSettings settings;
uint32_t start_tx_time;
uint8_t tx_power;
char save_filename[64];
FuriString* save_protocol;
uint16_t save_history_idx;
bool save_from_saved_info;
bool emulate_disabled_for_loaded;
bool emulate_feature_enabled;
GDRSelectedCapture selected_capture;
GDRCaptureOwner unsaved_history_owner;
#ifdef ENABLE_EMULATE_FEATURE
#define EMULATE_NAV_NONE 0U
#define EMULATE_NAV_POP 1U
#define EMULATE_NAV_STOP_APP 2U
CompositeApiResolver* emulate_plugin_resolver;
PluginManager* emulate_plugin_manager;
const GDREmulatePlugin* emulate_plugin;
uint8_t emulate_nav_pending;
#endif
CompositeApiResolver* psa_bf_plugin_resolver;
PluginManager* psa_bf_plugin_manager;
const GDRPsaBfPlugin* psa_bf_plugin;
#ifdef ENABLE_DUAL_RX_SCENE
GDRDualReceiver* dual_receiver;
GDRRxChain* dual_chain_a;
GDRRxChain* dual_chain_b;
GDRHistory* dual_history;
FuriMutex* dual_history_mutex;
uint32_t dual_freq_a;
uint32_t dual_freq_b;
uint8_t dual_preset_a;
uint8_t dual_preset_b;
#endif
#ifdef ENABLE_SHIELD_RX_SCENE
GDRRxChain* shield_rx_chain;
GDRTxChain* shield_tx_chain;
GDRHistory* shield_history;
FuriMutex* shield_history_mutex;
uint32_t shield_freq;
uint8_t shield_preset_index;
uint8_t shield_tx_offset_index;
uint8_t shield_tx_power;
bool shield_auto_save_failed;
#endif
};
#ifdef ENABLE_EMULATE_FEATURE
void gdr_emulate_context_release(GDRApp* app);
#endif
typedef enum {
GDRSetTypeFord_v0,
GDRSetTypeMAX,
} GDRSetType;
void gdr_preset_init(
void* context,
const char* preset_name,
uint32_t frequency,
uint8_t* preset_data,
size_t preset_data_size);
void gdr_get_frequency_modulation(
GDRApp* app,
FuriString* frequency,
FuriString* modulation);
void gdr_get_frequency_modulation_str(
GDRApp* app,
char* frequency,
size_t frequency_size,
char* modulation,
size_t modulation_size);
void gdr_begin(GDRApp* app, uint8_t* preset_data);
uint32_t gdr_rx(GDRApp* app, uint32_t frequency);
void gdr_idle(GDRApp* app);
void gdr_rx_end(GDRApp* app);
void gdr_sleep(GDRApp* app);
void gdr_hopper_update(GDRApp* app);
void gdr_tx(GDRApp* app, uint32_t frequency);
void gdr_tx_stop(GDRApp* app);
bool gdr_radio_init(GDRApp* app);
void gdr_radio_deinit(GDRApp* app);
bool gdr_refresh_protocol_registry(GDRApp* app, bool ensure_receiver_ready);
bool gdr_apply_protocol_registry_for_preset_data(
GDRApp* app,
const uint8_t* preset_data,
size_t preset_data_size);
bool gdr_ensure_variable_item_list(GDRApp* app);
bool gdr_ensure_widget(GDRApp* app);
bool gdr_ensure_text_input(GDRApp* app);
bool gdr_ensure_view_about(GDRApp* app);
bool gdr_ensure_receiver_view(GDRApp* app);
#ifdef ENABLE_DUAL_RX_SCENE
bool gdr_ensure_dual_receiver_view(GDRApp* app);
#endif
void gdr_release_shared_radio_state(GDRApp* app);
void gdr_rx_stack_suspend_for_tx(GDRApp* app);
void gdr_rx_stack_resume_after_tx(GDRApp* app);
void gdr_selected_capture_set(
GDRApp* app,
GDRHistory* history,
FuriMutex* mutex,
uint16_t index,
GDRCaptureOwner owner);
void gdr_selected_capture_clear(GDRApp* app);
bool gdr_selected_capture_is_valid(GDRApp* app);
GDRHistory* gdr_selected_capture_get_history(GDRApp* app);
uint16_t gdr_selected_capture_get_index(GDRApp* app);
GDRHistorySource gdr_selected_capture_get_source(GDRApp* app);
FlipperFormat* gdr_selected_capture_get_raw_data(GDRApp* app);
bool gdr_selected_capture_get_path(GDRApp* app, FuriString* out_path);
void gdr_selected_capture_release_scratch(GDRApp* app);
void gdr_app_free(GDRApp* app);
static const NotificationSequence sequence_tx = {
&message_note_c5,
&message_vibro_on,
&message_red_255,
&message_blue_255,
&message_blink_start_10,
&message_delay_25,
&message_vibro_off,
&message_delay_25,
&message_sound_off,
NULL,
};
@@ -1,6 +1,6 @@
// protopirate_history.c
#include "protopirate_history.h"
#include "helpers/protopirate_storage.h"
// gdr_history.c
#include "gdr_history.h"
#include "helpers/gdr_storage.h"
#include <lib/subghz/receiver.h>
#include <storage/storage.h>
#include <string.h>
@@ -8,28 +8,28 @@
#include <furi.h>
#include "defines.h"
#define TAG "ProtoPirateHistory"
#define TAG "GDRHistory"
#define HISTORY_SCRATCH_TEXT_RESERVE 256U
#define HISTORY_SCRATCH_PATH_RESERVE 128U
#define HISTORY_ARENA_RESERVE 1024U
static uint32_t protopirate_history_next_capture_seq = 0;
static uint32_t gdr_history_next_capture_seq = 0;
typedef struct {
uint32_t seq_id;
uint16_t text_offset;
uint16_t text_len;
uint8_t type;
ProtoPirateHistorySource source;
} ProtoPirateHistoryItem;
GDRHistorySource source;
} GDRHistoryItem;
ARRAY_DEF(ProtoPirateHistoryItemArray, ProtoPirateHistoryItem, M_POD_OPLIST)
ARRAY_DEF(GDRHistoryItemArray, GDRHistoryItem, M_POD_OPLIST)
struct ProtoPirateHistory {
ProtoPirateHistoryItemArray_t data;
struct GDRHistory {
GDRHistoryItemArray_t data;
uint16_t last_index;
uint32_t last_update_timestamp[ProtoPirateHistorySourceCount];
uint8_t code_last_hash_data[ProtoPirateHistorySourceCount];
uint32_t last_update_timestamp[GDRHistorySourceCount];
uint8_t code_last_hash_data[GDRHistorySourceCount];
Storage* storage;
FlipperFormat* loaded_ff;
int16_t loaded_idx;
@@ -39,26 +39,26 @@ struct ProtoPirateHistory {
FuriString* text_arena;
};
static uint32_t protopirate_history_allocate_capture_seq(void) {
static uint32_t gdr_history_allocate_capture_seq(void) {
uint32_t tick_seed = (uint32_t)(furi_get_tick() & 0x0FFFFFFF);
if(tick_seed == 0) {
tick_seed = 1;
}
FURI_CRITICAL_ENTER();
if(protopirate_history_next_capture_seq == 0 ||
protopirate_history_next_capture_seq < tick_seed) {
protopirate_history_next_capture_seq = tick_seed;
if(gdr_history_next_capture_seq == 0 ||
gdr_history_next_capture_seq < tick_seed) {
gdr_history_next_capture_seq = tick_seed;
}
uint32_t seq = protopirate_history_next_capture_seq++;
if(protopirate_history_next_capture_seq == 0) {
protopirate_history_next_capture_seq = 1;
uint32_t seq = gdr_history_next_capture_seq++;
if(gdr_history_next_capture_seq == 0) {
gdr_history_next_capture_seq = 1;
}
FURI_CRITICAL_EXIT();
return seq;
}
void protopirate_history_release_scratch(ProtoPirateHistory* instance) {
void gdr_history_release_scratch(GDRHistory* instance) {
furi_check(instance);
if(instance->loaded_ff) {
flipper_format_free(instance->loaded_ff);
@@ -68,27 +68,27 @@ void protopirate_history_release_scratch(ProtoPirateHistory* instance) {
}
static void
protopirate_history_build_path(ProtoPirateHistory* instance, uint32_t seq_id, FuriString* out) {
gdr_history_build_path(GDRHistory* instance, uint32_t seq_id, FuriString* out) {
UNUSED(instance);
protopirate_storage_build_history_path(seq_id, out);
gdr_storage_build_history_path(seq_id, out);
}
static void
protopirate_history_delete_capture_file(ProtoPirateHistory* instance, uint32_t seq_id) {
protopirate_history_build_path(instance, seq_id, instance->scratch_path);
protopirate_storage_delete_file(furi_string_get_cstr(instance->scratch_path));
gdr_history_delete_capture_file(GDRHistory* instance, uint32_t seq_id) {
gdr_history_build_path(instance, seq_id, instance->scratch_path);
gdr_storage_delete_file(furi_string_get_cstr(instance->scratch_path));
}
static void protopirate_history_delete_all_capture_files(ProtoPirateHistory* instance) {
size_t item_count = ProtoPirateHistoryItemArray_size(instance->data);
static void gdr_history_delete_all_capture_files(GDRHistory* instance) {
size_t item_count = GDRHistoryItemArray_size(instance->data);
for(size_t i = 0; i < item_count; i++) {
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, i);
protopirate_history_delete_capture_file(instance, item->seq_id);
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, i);
gdr_history_delete_capture_file(instance, item->seq_id);
}
}
static void
protopirate_history_arena_remove(ProtoPirateHistory* instance, uint16_t offset, uint16_t len) {
gdr_history_arena_remove(GDRHistory* instance, uint16_t offset, uint16_t len) {
if(len == 0) return;
size_t arena_size = furi_string_size(instance->text_arena);
@@ -103,19 +103,19 @@ static void
furi_string_cat_str(rebuilt, arena + offset + len);
furi_string_move(instance->text_arena, rebuilt);
size_t n = ProtoPirateHistoryItemArray_size(instance->data);
size_t n = GDRHistoryItemArray_size(instance->data);
for(size_t i = 0; i < n; i++) {
ProtoPirateHistoryItem* it = ProtoPirateHistoryItemArray_get(instance->data, i);
GDRHistoryItem* it = GDRHistoryItemArray_get(instance->data, i);
if(it->text_offset > offset) {
it->text_offset -= len;
}
}
}
ProtoPirateHistory* protopirate_history_alloc(void) {
ProtoPirateHistory* instance = malloc(sizeof(ProtoPirateHistory));
GDRHistory* gdr_history_alloc(void) {
GDRHistory* instance = malloc(sizeof(GDRHistory));
furi_check(instance);
ProtoPirateHistoryItemArray_init(instance->data);
GDRHistoryItemArray_init(instance->data);
instance->last_index = 0;
memset(instance->last_update_timestamp, 0, sizeof(instance->last_update_timestamp));
memset(instance->code_last_hash_data, 0, sizeof(instance->code_last_hash_data));
@@ -138,11 +138,11 @@ ProtoPirateHistory* protopirate_history_alloc(void) {
return instance;
}
void protopirate_history_free(ProtoPirateHistory* instance) {
void gdr_history_free(GDRHistory* instance) {
furi_check(instance);
protopirate_history_release_scratch(instance);
protopirate_history_delete_all_capture_files(instance);
ProtoPirateHistoryItemArray_clear(instance->data);
gdr_history_release_scratch(instance);
gdr_history_delete_all_capture_files(instance);
GDRHistoryItemArray_clear(instance->data);
if(instance->scratch_text) {
furi_string_free(instance->scratch_text);
@@ -164,29 +164,29 @@ void protopirate_history_free(ProtoPirateHistory* instance) {
free(instance);
}
void protopirate_history_reset(ProtoPirateHistory* instance) {
void gdr_history_reset(GDRHistory* instance) {
furi_check(instance);
protopirate_history_release_scratch(instance);
protopirate_history_delete_all_capture_files(instance);
ProtoPirateHistoryItemArray_reset(instance->data);
gdr_history_release_scratch(instance);
gdr_history_delete_all_capture_files(instance);
GDRHistoryItemArray_reset(instance->data);
furi_string_reset(instance->text_arena);
instance->last_index = 0;
memset(instance->last_update_timestamp, 0, sizeof(instance->last_update_timestamp));
memset(instance->code_last_hash_data, 0, sizeof(instance->code_last_hash_data));
}
uint16_t protopirate_history_get_item(ProtoPirateHistory* instance) {
uint16_t gdr_history_get_item(GDRHistory* instance) {
furi_check(instance);
return ProtoPirateHistoryItemArray_size(instance->data);
return GDRHistoryItemArray_size(instance->data);
}
uint16_t protopirate_history_get_last_index(ProtoPirateHistory* instance) {
uint16_t gdr_history_get_last_index(GDRHistory* instance) {
furi_check(instance);
return instance->last_index;
}
void protopirate_history_format_status_text(
ProtoPirateHistory* instance,
void gdr_history_format_status_text(
GDRHistory* instance,
char* output,
size_t output_size) {
furi_check(instance);
@@ -196,87 +196,87 @@ void protopirate_history_format_status_text(
return;
}
uint16_t n = protopirate_history_get_item(instance);
if(n >= PROTOPIRATE_HISTORY_MAX) {
uint16_t n = gdr_history_get_item(instance);
if(n >= GDR_HISTORY_MAX) {
snprintf(output, output_size, "FULL");
} else {
snprintf(output, output_size, "%u/%u", n, PROTOPIRATE_HISTORY_MAX);
snprintf(output, output_size, "%u/%u", n, GDR_HISTORY_MAX);
}
}
void protopirate_history_get_status_text(ProtoPirateHistory* instance, FuriString* output) {
void gdr_history_get_status_text(GDRHistory* instance, FuriString* output) {
furi_check(instance);
furi_check(output);
char status_text[16];
protopirate_history_format_status_text(instance, status_text, sizeof(status_text));
gdr_history_format_status_text(instance, status_text, sizeof(status_text));
furi_string_set_str(output, status_text);
}
bool protopirate_history_get_capture_path(
ProtoPirateHistory* instance,
bool gdr_history_get_capture_path(
GDRHistory* instance,
uint16_t idx,
FuriString* out_path) {
furi_check(instance);
furi_check(out_path);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
if(idx >= GDRHistoryItemArray_size(instance->data)) {
return false;
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
protopirate_history_build_path(instance, item->seq_id, out_path);
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
gdr_history_build_path(instance, item->seq_id, out_path);
return true;
}
bool protopirate_history_capture_path_equals(
ProtoPirateHistory* instance,
bool gdr_history_capture_path_equals(
GDRHistory* instance,
uint16_t idx,
const char* path) {
furi_check(instance);
if(!path || idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
if(!path || idx >= GDRHistoryItemArray_size(instance->data)) {
return false;
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
protopirate_history_build_path(instance, item->seq_id, instance->scratch_path);
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
gdr_history_build_path(instance, item->seq_id, instance->scratch_path);
return strcmp(furi_string_get_cstr(instance->scratch_path), path) == 0;
}
ProtoPirateHistorySource protopirate_history_get_source(
ProtoPirateHistory* instance,
GDRHistorySource gdr_history_get_source(
GDRHistory* instance,
uint16_t idx) {
furi_check(instance);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
return ProtoPirateHistorySourceUnknown;
if(idx >= GDRHistoryItemArray_size(instance->data)) {
return GDRHistorySourceUnknown;
}
return ProtoPirateHistoryItemArray_get(instance->data, idx)->source;
return GDRHistoryItemArray_get(instance->data, idx)->source;
}
const char* protopirate_history_source_name(ProtoPirateHistorySource source) {
const char* gdr_history_source_name(GDRHistorySource source) {
switch(source) {
case ProtoPirateHistorySourceExternal:
case GDRHistorySourceExternal:
return "External";
case ProtoPirateHistorySourceInternal:
case GDRHistorySourceInternal:
return "Internal";
default:
return "Unknown";
}
}
bool protopirate_history_add_to_history(
ProtoPirateHistory* instance,
bool gdr_history_add_to_history(
GDRHistory* instance,
void* context,
SubGhzRadioPreset* preset,
ProtoPirateHistorySource source) {
GDRHistorySource source) {
furi_check(instance);
furi_check(context);
if(source >= ProtoPirateHistorySourceCount) {
source = ProtoPirateHistorySourceUnknown;
if(source >= GDRHistorySourceCount) {
source = GDRHistorySourceUnknown;
}
if(ProtoPirateHistoryItemArray_size(instance->data) >= PROTOPIRATE_HISTORY_MAX) {
if(GDRHistoryItemArray_size(instance->data) >= GDR_HISTORY_MAX) {
return false;
}
@@ -289,7 +289,7 @@ bool protopirate_history_add_to_history(
return false;
}
protopirate_history_release_scratch(instance);
gdr_history_release_scratch(instance);
furi_string_reset(instance->scratch_text);
furi_string_reset(instance->scratch_path);
@@ -307,13 +307,13 @@ bool protopirate_history_add_to_history(
return false;
}
if(source != ProtoPirateHistorySourceUnknown) {
if(source != GDRHistorySourceUnknown) {
flipper_format_insert_or_update_string_cstr(
temp_ff, "RadioDevice", protopirate_history_source_name(source));
temp_ff, "RadioDevice", gdr_history_source_name(source));
}
uint32_t seq = protopirate_history_allocate_capture_seq();
bool saved = protopirate_storage_save_history_capture(temp_ff, seq, instance->scratch_path);
uint32_t seq = gdr_history_allocate_capture_seq();
bool saved = gdr_storage_save_history_capture(temp_ff, seq, instance->scratch_path);
flipper_format_free(temp_ff);
if(!saved) {
@@ -332,7 +332,7 @@ bool protopirate_history_add_to_history(
furi_check(offset <= UINT16_MAX);
furi_string_cat_str(instance->text_arena, text_cstr);
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_push_raw(instance->data);
GDRHistoryItem* item = GDRHistoryItemArray_push_raw(instance->data);
item->seq_id = seq;
item->text_offset = (uint16_t)offset;
item->text_len = (uint16_t)text_len;
@@ -345,57 +345,57 @@ bool protopirate_history_add_to_history(
TAG,
"Added item %u to history (size: %zu) seq=%lu",
instance->last_index,
ProtoPirateHistoryItemArray_size(instance->data),
GDRHistoryItemArray_size(instance->data),
(unsigned long)seq);
return true;
}
void protopirate_history_delete_item(ProtoPirateHistory* instance, uint16_t idx) {
void gdr_history_delete_item(GDRHistory* instance, uint16_t idx) {
furi_check(instance);
size_t item_count = ProtoPirateHistoryItemArray_size(instance->data);
size_t item_count = GDRHistoryItemArray_size(instance->data);
if(idx >= item_count) {
return;
}
if(instance->loaded_ff) {
if(instance->loaded_idx == (int16_t)idx) {
protopirate_history_release_scratch(instance);
gdr_history_release_scratch(instance);
} else if(instance->loaded_idx > (int16_t)idx) {
instance->loaded_idx--;
}
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
uint32_t seq_id = item->seq_id;
uint16_t text_offset = item->text_offset;
uint16_t text_len = item->text_len;
protopirate_history_delete_capture_file(instance, seq_id);
ProtoPirateHistoryItemArray_pop_at(NULL, instance->data, idx);
protopirate_history_arena_remove(instance, text_offset, text_len);
gdr_history_delete_capture_file(instance, seq_id);
GDRHistoryItemArray_pop_at(NULL, instance->data, idx);
gdr_history_arena_remove(instance, text_offset, text_len);
FURI_LOG_I(
TAG,
"Deleted history item %u (size: %zu)",
idx,
ProtoPirateHistoryItemArray_size(instance->data));
GDRHistoryItemArray_size(instance->data));
}
void protopirate_history_get_text_item_menu(
ProtoPirateHistory* instance,
void gdr_history_get_text_item_menu(
GDRHistory* instance,
FuriString* output,
uint16_t idx) {
furi_check(instance);
furi_check(output);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
if(idx >= GDRHistoryItemArray_size(instance->data)) {
furi_string_set(output, "---");
return;
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
const char* arena = furi_string_get_cstr(instance->text_arena);
const char* str = arena + item->text_offset;
size_t remaining = item->text_len;
@@ -407,16 +407,16 @@ void protopirate_history_get_text_item_menu(
uint16_t display_idx = idx + 1;
const char* source_tag = "";
if(item->source == ProtoPirateHistorySourceExternal) {
if(item->source == GDRHistorySourceExternal) {
source_tag = "[E] ";
} else if(item->source == ProtoPirateHistorySourceInternal) {
} else if(item->source == GDRHistorySourceInternal) {
source_tag = "[I] ";
}
furi_string_printf(output, "%u. %s%.*s", display_idx, source_tag, (int)len, str);
}
void protopirate_history_get_text_item_detail(
ProtoPirateHistory* instance,
void gdr_history_get_text_item_detail(
GDRHistory* instance,
uint16_t idx,
FuriString* output,
SubGhzEnvironment* environment) {
@@ -424,20 +424,20 @@ void protopirate_history_get_text_item_detail(
furi_check(output);
UNUSED(environment);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
if(idx >= GDRHistoryItemArray_size(instance->data)) {
furi_string_set(output, "---");
return;
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
const char* arena = furi_string_get_cstr(instance->text_arena);
furi_string_set_strn(output, arena + item->text_offset, item->text_len);
}
FlipperFormat* protopirate_history_get_raw_data(ProtoPirateHistory* instance, uint16_t idx) {
FlipperFormat* gdr_history_get_raw_data(GDRHistory* instance, uint16_t idx) {
furi_check(instance);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
if(idx >= GDRHistoryItemArray_size(instance->data)) {
return NULL;
}
@@ -445,10 +445,10 @@ FlipperFormat* protopirate_history_get_raw_data(ProtoPirateHistory* instance, ui
return instance->loaded_ff;
}
protopirate_history_release_scratch(instance);
gdr_history_release_scratch(instance);
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
protopirate_history_build_path(instance, item->seq_id, instance->scratch_path);
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
gdr_history_build_path(instance, item->seq_id, instance->scratch_path);
instance->loaded_ff = flipper_format_file_alloc(instance->storage);
furi_check(instance->loaded_ff);
@@ -464,19 +464,19 @@ FlipperFormat* protopirate_history_get_raw_data(ProtoPirateHistory* instance, ui
return instance->loaded_ff;
}
void protopirate_history_set_item_str(ProtoPirateHistory* instance, uint16_t idx, const char* str) {
void gdr_history_set_item_str(GDRHistory* instance, uint16_t idx, const char* str) {
furi_check(instance);
furi_check(str);
if(idx >= ProtoPirateHistoryItemArray_size(instance->data)) {
if(idx >= GDRHistoryItemArray_size(instance->data)) {
return;
}
ProtoPirateHistoryItem* item = ProtoPirateHistoryItemArray_get(instance->data, idx);
GDRHistoryItem* item = GDRHistoryItemArray_get(instance->data, idx);
uint16_t old_offset = item->text_offset;
uint16_t old_len = item->text_len;
protopirate_history_arena_remove(instance, old_offset, old_len);
gdr_history_arena_remove(instance, old_offset, old_len);
size_t new_offset = furi_string_size(instance->text_arena);
size_t new_len = strlen(str);
@@ -0,0 +1,63 @@
// gdr_history.h
#pragma once
#include <stddef.h>
#include <lib/subghz/receiver.h>
#include <lib/subghz/protocols/base.h>
#define GDR_HISTORY_MAX 10
typedef struct SubGhzEnvironment SubGhzEnvironment;
typedef struct GDRHistory GDRHistory;
typedef enum {
GDRHistorySourceUnknown = 0,
GDRHistorySourceExternal,
GDRHistorySourceInternal,
GDRHistorySourceCount,
} GDRHistorySource;
GDRHistory* gdr_history_alloc(void);
void gdr_history_free(GDRHistory* instance);
void gdr_history_reset(GDRHistory* instance);
uint16_t gdr_history_get_item(GDRHistory* instance);
uint16_t gdr_history_get_last_index(GDRHistory* instance);
GDRHistorySource gdr_history_get_source(
GDRHistory* instance,
uint16_t idx);
const char* gdr_history_source_name(GDRHistorySource source);
void gdr_history_format_status_text(
GDRHistory* instance,
char* output,
size_t output_size);
void gdr_history_get_status_text(GDRHistory* instance, FuriString* output);
bool gdr_history_get_capture_path(
GDRHistory* instance,
uint16_t idx,
FuriString* out_path);
bool gdr_history_capture_path_equals(
GDRHistory* instance,
uint16_t idx,
const char* path);
bool gdr_history_add_to_history(
GDRHistory* instance,
void* context,
SubGhzRadioPreset* preset,
GDRHistorySource source);
void gdr_history_delete_item(GDRHistory* instance, uint16_t idx);
void gdr_history_get_text_item_menu(
GDRHistory* instance,
FuriString* output,
uint16_t idx);
void gdr_history_get_text_item_detail(
GDRHistory* instance,
uint16_t idx,
FuriString* output,
SubGhzEnvironment* environment);
FlipperFormat* gdr_history_get_raw_data(GDRHistory* instance, uint16_t idx);
void gdr_history_release_scratch(GDRHistory* instance);
void gdr_history_set_item_str(GDRHistory* instance, uint16_t idx, const char* str);
@@ -9,5 +9,5 @@ extern const Icon I_PP_scanning_123x52;
extern const Icon I_PP_scanning_ext_123x52;
extern const Icon I_Pin_back_arrow_10x8;
extern const Icon I_WarningDolphin_45x42;
extern const Icon I_protopirate_10px;
extern const Icon I_gdr_10px;
extern const Icon I_subghz_10px;
@@ -0,0 +1,14 @@
#include "gdr_psa_bf_host.h"
bool gdr_psa_bf_plugin_ensure_loaded(GDRApp* app) {
(void)app;
return false;
}
void gdr_psa_bf_plugin_unload_if_idle(GDRApp* app) {
(void)app;
}
void gdr_psa_bf_context_release(GDRApp* app) {
(void)app;
}
@@ -0,0 +1,15 @@
#pragma once
#include <stdbool.h>
typedef struct GDRApp GDRApp;
bool gdr_psa_bf_plugin_ensure_loaded(GDRApp* app);
void gdr_psa_bf_plugin_unload_if_idle(GDRApp* app);
void gdr_psa_bf_context_release(GDRApp* app);
void gdr_receiver_info_rebuild_normal_widget(void* app);
#ifdef ENABLE_SUB_DECODE_SCENE
void gdr_subdecode_psa_bf_complete_refresh(void* app);
#endif
@@ -1,5 +1,5 @@
// helpers/protopirate_rx_chain.c
#include "protopirate_rx_chain.h"
// helpers/gdr_rx_chain.c
#include "gdr_rx_chain.h"
#if defined(ENABLE_DUAL_RX_SCENE) || defined(ENABLE_SHIELD_RX_SCENE)
@@ -7,33 +7,33 @@
#include <loader/firmware_api/firmware_api.h>
#include "../protocols/keys.h"
#define TAG "ProtoPirateRxChain"
#define TAG "GDRRxChain"
#define PROTOPIRATE_CHAIN_KEYSTORE_DIR APP_ASSETS_PATH("encrypted")
#define GDR_CHAIN_KEYSTORE_DIR APP_ASSETS_PATH("encrypted")
#define PROTOPIRATE_CC1101_REG_FIFOTHR 0x03U
#define PROTOPIRATE_CC1101_REG_FSCTRL1 0x07U
#define PROTOPIRATE_CC1101_REG_MDMCFG4 0x10U
#define PROTOPIRATE_CC1101_REG_MDMCFG3 0x11U
#define PROTOPIRATE_CC1101_REG_MDMCFG2 0x12U
#define PROTOPIRATE_CC1101_REG_DEVIATN 0x15U
#define PROTOPIRATE_CC1101_REG_AGCCTRL2 0x1BU
#define PROTOPIRATE_CC1101_REG_AGCCTRL1 0x1CU
#define PROTOPIRATE_CC1101_REG_AGCCTRL0 0x1DU
#define PROTOPIRATE_CC1101_REG_FREND1 0x21U
#define PROTOPIRATE_CC1101_REG_TEST2 0x2CU
#define PROTOPIRATE_CC1101_REG_TEST1 0x2DU
#define GDR_CC1101_REG_FIFOTHR 0x03U
#define GDR_CC1101_REG_FSCTRL1 0x07U
#define GDR_CC1101_REG_MDMCFG4 0x10U
#define GDR_CC1101_REG_MDMCFG3 0x11U
#define GDR_CC1101_REG_MDMCFG2 0x12U
#define GDR_CC1101_REG_DEVIATN 0x15U
#define GDR_CC1101_REG_AGCCTRL2 0x1BU
#define GDR_CC1101_REG_AGCCTRL1 0x1CU
#define GDR_CC1101_REG_AGCCTRL0 0x1DU
#define GDR_CC1101_REG_FREND1 0x21U
#define GDR_CC1101_REG_TEST2 0x2CU
#define GDR_CC1101_REG_TEST1 0x2DU
#define PROTOPIRATE_CC1101_CHANBW_135_KHZ_MASK 0xA0U
#define PROTOPIRATE_CC1101_XTAL_HZ 26000000UL
#define PROTOPIRATE_FM_BANDWIDTH_GUARD_HZ 50000UL
#define GDR_CC1101_CHANBW_135_KHZ_MASK 0xA0U
#define GDR_CC1101_XTAL_HZ 26000000UL
#define GDR_FM_BANDWIDTH_GUARD_HZ 50000UL
#define PROTOPIRATE_CC1101_MOD_FORMAT_MASK 0x70U
#define PROTOPIRATE_CC1101_MOD_FORMAT_2FSK 0x00U
#define PROTOPIRATE_CC1101_MOD_FORMAT_GFSK 0x10U
#define PROTOPIRATE_CC1101_MOD_FORMAT_OOK 0x30U
#define GDR_CC1101_MOD_FORMAT_MASK 0x70U
#define GDR_CC1101_MOD_FORMAT_2FSK 0x00U
#define GDR_CC1101_MOD_FORMAT_GFSK 0x10U
#define GDR_CC1101_MOD_FORMAT_OOK 0x30U
static bool protopirate_rx_chain_preset_get_register(
static bool gdr_rx_chain_preset_get_register(
const uint8_t* data,
size_t size,
uint8_t reg,
@@ -53,7 +53,7 @@ static bool protopirate_rx_chain_preset_get_register(
return false;
}
static bool protopirate_rx_chain_preset_set_register(
static bool gdr_rx_chain_preset_set_register(
uint8_t* data,
size_t size,
uint8_t reg,
@@ -73,7 +73,7 @@ static bool protopirate_rx_chain_preset_set_register(
return false;
}
static bool protopirate_rx_chain_preset_find_terminator(
static bool gdr_rx_chain_preset_find_terminator(
const uint8_t* data,
size_t size,
size_t* offset) {
@@ -89,29 +89,29 @@ static bool protopirate_rx_chain_preset_find_terminator(
return false;
}
static uint32_t protopirate_rx_chain_channel_bandwidth_hz(uint8_t mdmcfg4) {
static uint32_t gdr_rx_chain_channel_bandwidth_hz(uint8_t mdmcfg4) {
uint8_t exponent = (mdmcfg4 >> 6U) & 0x03U;
uint8_t mantissa = (mdmcfg4 >> 4U) & 0x03U;
uint32_t denominator = 8UL * (4UL + mantissa) * (1UL << exponent);
return PROTOPIRATE_CC1101_XTAL_HZ / denominator;
return GDR_CC1101_XTAL_HZ / denominator;
}
static uint32_t protopirate_rx_chain_data_rate_hz(uint8_t mdmcfg4, uint8_t mdmcfg3) {
static uint32_t gdr_rx_chain_data_rate_hz(uint8_t mdmcfg4, uint8_t mdmcfg3) {
uint8_t exponent = mdmcfg4 & 0x0FU;
uint64_t numerator =
(uint64_t)(256UL + mdmcfg3) * (1ULL << exponent) * PROTOPIRATE_CC1101_XTAL_HZ;
(uint64_t)(256UL + mdmcfg3) * (1ULL << exponent) * GDR_CC1101_XTAL_HZ;
return (uint32_t)((numerator + (1ULL << 27U)) >> 28U);
}
static uint32_t protopirate_rx_chain_deviation_hz(uint8_t deviatn) {
static uint32_t gdr_rx_chain_deviation_hz(uint8_t deviatn) {
uint8_t exponent = (deviatn >> 4U) & 0x07U;
uint8_t mantissa = deviatn & 0x07U;
uint64_t numerator =
(uint64_t)PROTOPIRATE_CC1101_XTAL_HZ * (8UL + mantissa) * (1ULL << exponent);
(uint64_t)GDR_CC1101_XTAL_HZ * (8UL + mantissa) * (1ULL << exponent);
return (uint32_t)((numerator + (1ULL << 16U)) >> 17U);
}
static uint8_t protopirate_rx_chain_select_bandwidth_bits(
static uint8_t gdr_rx_chain_select_bandwidth_bits(
uint32_t minimum_hz,
uint32_t* selected_hz) {
static const uint8_t bandwidth_bits[] = {
@@ -136,7 +136,7 @@ static uint8_t protopirate_rx_chain_select_bandwidth_bits(
const size_t bandwidth_count = sizeof(bandwidth_bits) / sizeof(bandwidth_bits[0]);
for(size_t i = 0; i < bandwidth_count; i++) {
uint32_t bandwidth =
protopirate_rx_chain_channel_bandwidth_hz((uint8_t)(bandwidth_bits[i] << 4U));
gdr_rx_chain_channel_bandwidth_hz((uint8_t)(bandwidth_bits[i] << 4U));
if(bandwidth >= minimum_hz) {
if(selected_hz) {
*selected_hz = bandwidth;
@@ -146,30 +146,30 @@ static uint8_t protopirate_rx_chain_select_bandwidth_bits(
}
if(selected_hz) {
*selected_hz = protopirate_rx_chain_channel_bandwidth_hz(0x00U);
*selected_hz = gdr_rx_chain_channel_bandwidth_hz(0x00U);
}
return 0x00U;
}
static const char* protopirate_rx_chain_plugin_path(ProtoPirateProtocolRegistryFilter filter) {
return (filter == ProtoPirateProtocolRegistryFilterFM) ?
APP_ASSETS_PATH("plugins/protopirate_fm_plugin.fal") :
APP_ASSETS_PATH("plugins/protopirate_am_plugin.fal");
static const char* gdr_rx_chain_plugin_path(GDRProtocolRegistryFilter filter) {
return (filter == GDRProtocolRegistryFilterFM) ?
APP_ASSETS_PATH("plugins/gdr_fm_plugin.fal") :
APP_ASSETS_PATH("plugins/gdr_am_plugin.fal");
}
ProtoPirateRxChain* protopirate_rx_chain_alloc(char label) {
ProtoPirateRxChain* chain = malloc(sizeof(ProtoPirateRxChain));
GDRRxChain* gdr_rx_chain_alloc(char label) {
GDRRxChain* chain = malloc(sizeof(GDRRxChain));
furi_check(chain);
memset(chain, 0, sizeof(ProtoPirateRxChain));
memset(chain, 0, sizeof(GDRRxChain));
chain->label = label;
chain->preset.name = furi_string_alloc();
furi_check(chain->preset.name);
chain->state = ProtoPirateTxRxStateIDLE;
chain->filter = ProtoPirateProtocolRegistryFilterAM;
chain->state = GDRTxRxStateIDLE;
chain->filter = GDRProtocolRegistryFilterAM;
return chain;
}
static void protopirate_rx_chain_unload_plugin(ProtoPirateRxChain* chain) {
static void gdr_rx_chain_unload_plugin(GDRRxChain* chain) {
chain->plugin = NULL;
chain->registry = NULL;
if(chain->plugin_manager) {
@@ -182,13 +182,13 @@ static void protopirate_rx_chain_unload_plugin(ProtoPirateRxChain* chain) {
}
}
void protopirate_rx_chain_free(ProtoPirateRxChain* chain) {
void gdr_rx_chain_free(GDRRxChain* chain) {
if(!chain) {
return;
}
// Make sure RX is stopped before tearing anything down.
protopirate_rx_chain_stop(chain);
gdr_rx_chain_stop(chain);
if(chain->receiver) {
subghz_receiver_set_rx_callback(chain->receiver, NULL, NULL);
@@ -210,7 +210,7 @@ void protopirate_rx_chain_free(ProtoPirateRxChain* chain) {
chain->device = NULL;
}
protopirate_rx_chain_unload_plugin(chain);
gdr_rx_chain_unload_plugin(chain);
if(chain->environment) {
subghz_environment_free(chain->environment);
@@ -230,8 +230,8 @@ void protopirate_rx_chain_free(ProtoPirateRxChain* chain) {
free(chain);
}
bool protopirate_rx_chain_acquire_device(
ProtoPirateRxChain* chain,
bool gdr_rx_chain_acquire_device(
GDRRxChain* chain,
SubGhzRadioDeviceType type) {
furi_check(chain);
@@ -258,8 +258,8 @@ bool protopirate_rx_chain_acquire_device(
return true;
}
bool protopirate_rx_chain_set_preset(
ProtoPirateRxChain* chain,
bool gdr_rx_chain_set_preset(
GDRRxChain* chain,
SubGhzSetting* setting,
const char* preset_name,
uint32_t frequency) {
@@ -270,7 +270,7 @@ bool protopirate_rx_chain_set_preset(
size_t preset_count = subghz_setting_get_preset_count(setting);
for(size_t i = 0; i < preset_count; i++) {
if(strcmp(subghz_setting_get_preset_name(setting, i), preset_name) == 0) {
return protopirate_rx_chain_set_preset_data(
return gdr_rx_chain_set_preset_data(
chain,
preset_name,
subghz_setting_get_preset_data(setting, i),
@@ -283,8 +283,8 @@ bool protopirate_rx_chain_set_preset(
return false;
}
bool protopirate_rx_chain_set_preset_data(
ProtoPirateRxChain* chain,
bool gdr_rx_chain_set_preset_data(
GDRRxChain* chain,
const char* preset_name,
uint8_t* preset_data,
size_t preset_data_size,
@@ -310,57 +310,57 @@ bool protopirate_rx_chain_set_preset_data(
chain->base_preset_data_size = preset_data_size;
chain->frequency = frequency;
chain->filter =
protopirate_get_protocol_registry_filter_for_preset(preset_data, preset_data_size);
gdr_get_protocol_registry_filter_for_preset(preset_data, preset_data_size);
uint8_t mdmcfg4 = 0U;
chain->rx_bandwidth_hz = protopirate_rx_chain_preset_get_register(
chain->rx_bandwidth_hz = gdr_rx_chain_preset_get_register(
preset_data,
preset_data_size,
PROTOPIRATE_CC1101_REG_MDMCFG4,
GDR_CC1101_REG_MDMCFG4,
&mdmcfg4) ?
protopirate_rx_chain_channel_bandwidth_hz(mdmcfg4) :
gdr_rx_chain_channel_bandwidth_hz(mdmcfg4) :
0U;
return true;
}
static bool protopirate_rx_chain_apply_ook_shield_profile(ProtoPirateRxChain* chain) {
static bool gdr_rx_chain_apply_ook_shield_profile(GDRRxChain* chain) {
if(!chain->base_preset_data || chain->preset.data_size < 2U) {
FURI_LOG_E(TAG, "[%c] cannot narrow RX BW without preset data", chain->label);
return false;
}
uint8_t mdmcfg4 = 0U;
if(!protopirate_rx_chain_preset_get_register(
if(!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
PROTOPIRATE_CC1101_REG_MDMCFG4,
GDR_CC1101_REG_MDMCFG4,
&mdmcfg4)) {
FURI_LOG_W(TAG, "[%c] OOK preset missing MDMCFG4; retaining original", chain->label);
return true;
}
const uint8_t narrowed_mdmcfg4 =
(uint8_t)((mdmcfg4 & 0x0FU) | PROTOPIRATE_CC1101_CHANBW_135_KHZ_MASK);
(uint8_t)((mdmcfg4 & 0x0FU) | GDR_CC1101_CHANBW_135_KHZ_MASK);
const struct {
uint8_t reg;
uint8_t value;
} narrow_registers[] = {
{PROTOPIRATE_CC1101_REG_FIFOTHR, 0x47U},
{PROTOPIRATE_CC1101_REG_FSCTRL1, 0x06U},
{PROTOPIRATE_CC1101_REG_MDMCFG4, narrowed_mdmcfg4},
{PROTOPIRATE_CC1101_REG_AGCCTRL2, 0x04U},
{PROTOPIRATE_CC1101_REG_AGCCTRL1, 0x00U},
{PROTOPIRATE_CC1101_REG_AGCCTRL0, 0x92U},
{PROTOPIRATE_CC1101_REG_FREND1, 0x56U},
{PROTOPIRATE_CC1101_REG_TEST2, 0x81U},
{PROTOPIRATE_CC1101_REG_TEST1, 0x35U},
{GDR_CC1101_REG_FIFOTHR, 0x47U},
{GDR_CC1101_REG_FSCTRL1, 0x06U},
{GDR_CC1101_REG_MDMCFG4, narrowed_mdmcfg4},
{GDR_CC1101_REG_AGCCTRL2, 0x04U},
{GDR_CC1101_REG_AGCCTRL1, 0x00U},
{GDR_CC1101_REG_AGCCTRL0, 0x92U},
{GDR_CC1101_REG_FREND1, 0x56U},
{GDR_CC1101_REG_TEST2, 0x81U},
{GDR_CC1101_REG_TEST1, 0x35U},
};
const size_t register_count = sizeof(narrow_registers) / sizeof(narrow_registers[0]);
size_t missing_count = 0U;
for(size_t i = 0; i < register_count; i++) {
uint8_t value = 0U;
if(!protopirate_rx_chain_preset_get_register(
if(!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
narrow_registers[i].reg,
@@ -370,7 +370,7 @@ static bool protopirate_rx_chain_apply_ook_shield_profile(ProtoPirateRxChain* ch
}
size_t terminator_offset = 0U;
if(!protopirate_rx_chain_preset_find_terminator(
if(!gdr_rx_chain_preset_find_terminator(
chain->base_preset_data, chain->base_preset_data_size, &terminator_offset)) {
FURI_LOG_W(TAG, "[%c] OOK preset has no terminator; retaining original", chain->label);
return true;
@@ -387,7 +387,7 @@ static bool protopirate_rx_chain_apply_ook_shield_profile(ProtoPirateRxChain* ch
size_t write_offset = terminator_offset;
for(size_t i = 0; i < register_count; i++) {
uint8_t value = 0U;
if(!protopirate_rx_chain_preset_get_register(
if(!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
narrow_registers[i].reg,
@@ -402,7 +402,7 @@ static bool protopirate_rx_chain_apply_ook_shield_profile(ProtoPirateRxChain* ch
chain->base_preset_data_size - terminator_offset);
for(size_t i = 0; i < register_count; i++) {
if(!protopirate_rx_chain_preset_set_register(
if(!gdr_rx_chain_preset_set_register(
copy, expanded_size, narrow_registers[i].reg, narrow_registers[i].value)) {
FURI_LOG_E(TAG, "[%c] failed to patch narrow RX preset", chain->label);
free(copy);
@@ -414,55 +414,55 @@ static bool protopirate_rx_chain_apply_ook_shield_profile(ProtoPirateRxChain* ch
chain->preset.data = copy;
chain->preset.data_size = expanded_size;
chain->rx_bandwidth_hz =
protopirate_rx_chain_channel_bandwidth_hz(PROTOPIRATE_CC1101_CHANBW_135_KHZ_MASK);
gdr_rx_chain_channel_bandwidth_hz(GDR_CC1101_CHANBW_135_KHZ_MASK);
FURI_LOG_I(TAG, "[%c] applied TI 135 kHz OOK sensitivity profile", chain->label);
return true;
}
static bool protopirate_rx_chain_apply_fm_shield_profile(ProtoPirateRxChain* chain) {
static bool gdr_rx_chain_apply_fm_shield_profile(GDRRxChain* chain) {
uint8_t mdmcfg2 = 0U;
uint8_t mdmcfg3 = 0U;
uint8_t mdmcfg4 = 0U;
uint8_t deviatn = 0U;
if(!protopirate_rx_chain_preset_get_register(
if(!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
PROTOPIRATE_CC1101_REG_MDMCFG2,
GDR_CC1101_REG_MDMCFG2,
&mdmcfg2) ||
!protopirate_rx_chain_preset_get_register(
!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
PROTOPIRATE_CC1101_REG_MDMCFG3,
GDR_CC1101_REG_MDMCFG3,
&mdmcfg3) ||
!protopirate_rx_chain_preset_get_register(
!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
PROTOPIRATE_CC1101_REG_MDMCFG4,
GDR_CC1101_REG_MDMCFG4,
&mdmcfg4) ||
!protopirate_rx_chain_preset_get_register(
!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
PROTOPIRATE_CC1101_REG_DEVIATN,
GDR_CC1101_REG_DEVIATN,
&deviatn)) {
FURI_LOG_W(TAG, "[%c] incomplete FM preset; retaining original bandwidth", chain->label);
return true;
}
uint8_t modulation = mdmcfg2 & PROTOPIRATE_CC1101_MOD_FORMAT_MASK;
if(modulation != PROTOPIRATE_CC1101_MOD_FORMAT_2FSK &&
modulation != PROTOPIRATE_CC1101_MOD_FORMAT_GFSK) {
uint8_t modulation = mdmcfg2 & GDR_CC1101_MOD_FORMAT_MASK;
if(modulation != GDR_CC1101_MOD_FORMAT_2FSK &&
modulation != GDR_CC1101_MOD_FORMAT_GFSK) {
FURI_LOG_W(TAG, "[%c] unsupported FM format; retaining original bandwidth", chain->label);
return true;
}
uint32_t data_rate = protopirate_rx_chain_data_rate_hz(mdmcfg4, mdmcfg3);
uint32_t deviation = protopirate_rx_chain_deviation_hz(deviatn);
uint32_t data_rate = gdr_rx_chain_data_rate_hz(mdmcfg4, mdmcfg3);
uint32_t deviation = gdr_rx_chain_deviation_hz(deviatn);
uint32_t minimum_bandwidth =
data_rate + (2UL * deviation) + PROTOPIRATE_FM_BANDWIDTH_GUARD_HZ;
data_rate + (2UL * deviation) + GDR_FM_BANDWIDTH_GUARD_HZ;
uint32_t selected_bandwidth = 0U;
uint8_t bandwidth_bits =
protopirate_rx_chain_select_bandwidth_bits(minimum_bandwidth, &selected_bandwidth);
uint32_t original_bandwidth = protopirate_rx_chain_channel_bandwidth_hz(mdmcfg4);
gdr_rx_chain_select_bandwidth_bits(minimum_bandwidth, &selected_bandwidth);
uint32_t original_bandwidth = gdr_rx_chain_channel_bandwidth_hz(mdmcfg4);
if(selected_bandwidth >= original_bandwidth) {
chain->rx_bandwidth_hz = original_bandwidth;
@@ -482,10 +482,10 @@ static bool protopirate_rx_chain_apply_fm_shield_profile(ProtoPirateRxChain* cha
memcpy(copy, chain->base_preset_data, chain->base_preset_data_size);
uint8_t profiled_mdmcfg4 = (uint8_t)((mdmcfg4 & 0x0FU) | (bandwidth_bits << 4U));
if(!protopirate_rx_chain_preset_set_register(
if(!gdr_rx_chain_preset_set_register(
copy,
chain->base_preset_data_size,
PROTOPIRATE_CC1101_REG_MDMCFG4,
GDR_CC1101_REG_MDMCFG4,
profiled_mdmcfg4)) {
free(copy);
return true;
@@ -493,15 +493,15 @@ static bool protopirate_rx_chain_apply_fm_shield_profile(ProtoPirateRxChain* cha
if(selected_bandwidth <= 101562UL) {
uint8_t agcctrl2 = 0U;
if(protopirate_rx_chain_preset_get_register(
if(gdr_rx_chain_preset_get_register(
copy,
chain->base_preset_data_size,
PROTOPIRATE_CC1101_REG_AGCCTRL2,
GDR_CC1101_REG_AGCCTRL2,
&agcctrl2)) {
protopirate_rx_chain_preset_set_register(
gdr_rx_chain_preset_set_register(
copy,
chain->base_preset_data_size,
PROTOPIRATE_CC1101_REG_AGCCTRL2,
GDR_CC1101_REG_AGCCTRL2,
(uint8_t)((agcctrl2 & 0xF8U) | 0x03U));
}
}
@@ -520,7 +520,7 @@ static bool protopirate_rx_chain_apply_fm_shield_profile(ProtoPirateRxChain* cha
return true;
}
bool protopirate_rx_chain_apply_shield_profile(ProtoPirateRxChain* chain) {
bool gdr_rx_chain_apply_shield_profile(GDRRxChain* chain) {
furi_check(chain);
if(chain->owned_preset_data) {
@@ -531,28 +531,28 @@ bool protopirate_rx_chain_apply_shield_profile(ProtoPirateRxChain* chain) {
chain->preset.data_size = chain->base_preset_data_size;
uint8_t mdmcfg2 = 0U;
if(!protopirate_rx_chain_preset_get_register(
if(!gdr_rx_chain_preset_get_register(
chain->base_preset_data,
chain->base_preset_data_size,
PROTOPIRATE_CC1101_REG_MDMCFG2,
GDR_CC1101_REG_MDMCFG2,
&mdmcfg2)) {
FURI_LOG_W(TAG, "[%c] preset modulation unknown; retaining original", chain->label);
return true;
}
switch(mdmcfg2 & PROTOPIRATE_CC1101_MOD_FORMAT_MASK) {
case PROTOPIRATE_CC1101_MOD_FORMAT_OOK:
return protopirate_rx_chain_apply_ook_shield_profile(chain);
case PROTOPIRATE_CC1101_MOD_FORMAT_2FSK:
case PROTOPIRATE_CC1101_MOD_FORMAT_GFSK:
return protopirate_rx_chain_apply_fm_shield_profile(chain);
switch(mdmcfg2 & GDR_CC1101_MOD_FORMAT_MASK) {
case GDR_CC1101_MOD_FORMAT_OOK:
return gdr_rx_chain_apply_ook_shield_profile(chain);
case GDR_CC1101_MOD_FORMAT_2FSK:
case GDR_CC1101_MOD_FORMAT_GFSK:
return gdr_rx_chain_apply_fm_shield_profile(chain);
default:
FURI_LOG_W(TAG, "[%c] no Shield profile for modulation; retaining original", chain->label);
return true;
}
}
bool protopirate_rx_chain_init_receiver(ProtoPirateRxChain* chain) {
bool gdr_rx_chain_init_receiver(GDRRxChain* chain) {
furi_check(chain);
if(!chain->environment) {
@@ -572,8 +572,8 @@ bool protopirate_rx_chain_init_receiver(ProtoPirateRxChain* chain) {
composite_api_resolver_add(resolver, firmware_api_interface);
PluginManager* manager = plugin_manager_alloc(
PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
GDR_PROTOCOL_PLUGIN_APP_ID,
GDR_PROTOCOL_PLUGIN_API_VERSION,
composite_api_resolver_get(resolver));
if(!manager) {
FURI_LOG_E(TAG, "[%c] Failed to allocate plugin manager", chain->label);
@@ -581,7 +581,7 @@ bool protopirate_rx_chain_init_receiver(ProtoPirateRxChain* chain) {
return false;
}
const char* plugin_path = protopirate_rx_chain_plugin_path(chain->filter);
const char* plugin_path = gdr_rx_chain_plugin_path(chain->filter);
PluginManagerError error = plugin_manager_load_single(manager, plugin_path);
if(error != PluginManagerErrorNone) {
FURI_LOG_E(TAG, "[%c] Failed to load plugin %s: %d", chain->label, plugin_path, (int)error);
@@ -590,7 +590,7 @@ bool protopirate_rx_chain_init_receiver(ProtoPirateRxChain* chain) {
return false;
}
const ProtoPirateProtocolPlugin* plugin = plugin_manager_get_ep(manager, 0U);
const GDRProtocolPlugin* plugin = plugin_manager_get_ep(manager, 0U);
if(!plugin || !plugin->registry || plugin->filter != chain->filter) {
FURI_LOG_E(TAG, "[%c] Invalid plugin entry point", chain->label);
plugin_manager_free(manager);
@@ -606,8 +606,8 @@ bool protopirate_rx_chain_init_receiver(ProtoPirateRxChain* chain) {
subghz_environment_set_protocol_registry(chain->environment, chain->registry);
subghz_environment_load_keystore(chain->environment, PROTOPIRATE_CHAIN_KEYSTORE_DIR);
protopirate_keys_load(chain->environment);
subghz_environment_load_keystore(chain->environment, GDR_CHAIN_KEYSTORE_DIR);
gdr_keys_load(chain->environment);
if(!chain->receiver) {
chain->receiver = subghz_receiver_alloc_init(chain->environment);
@@ -634,8 +634,8 @@ bool protopirate_rx_chain_init_receiver(ProtoPirateRxChain* chain) {
return true;
}
void protopirate_rx_chain_set_decode_callback(
ProtoPirateRxChain* chain,
void gdr_rx_chain_set_decode_callback(
GDRRxChain* chain,
SubGhzReceiverCallback callback,
void* context) {
furi_check(chain);
@@ -643,13 +643,13 @@ void protopirate_rx_chain_set_decode_callback(
subghz_receiver_set_rx_callback(chain->receiver, callback, context);
}
bool protopirate_rx_chain_start(ProtoPirateRxChain* chain) {
bool gdr_rx_chain_start(GDRRxChain* chain) {
furi_check(chain);
if(!chain->device || !chain->worker || !chain->receiver) {
FURI_LOG_E(TAG, "[%c] start rejected (incomplete stack)", chain->label);
return false;
}
if(chain->state == ProtoPirateTxRxStateRx) {
if(chain->state == GDRTxRxStateRx) {
return true;
}
@@ -669,16 +669,16 @@ bool protopirate_rx_chain_start(ProtoPirateRxChain* chain) {
subghz_devices_start_async_rx(chain->device, subghz_worker_rx_callback, chain->worker);
subghz_worker_start(chain->worker);
chain->state = ProtoPirateTxRxStateRx;
chain->state = GDRTxRxStateRx;
FURI_LOG_I(TAG, "[%c] RX started on %lu Hz", chain->label, chain->frequency);
return true;
}
void protopirate_rx_chain_stop(ProtoPirateRxChain* chain) {
void gdr_rx_chain_stop(GDRRxChain* chain) {
if(!chain) {
return;
}
if(chain->state != ProtoPirateTxRxStateRx) {
if(chain->state != GDRTxRxStateRx) {
return;
}
@@ -689,12 +689,12 @@ void protopirate_rx_chain_stop(ProtoPirateRxChain* chain) {
subghz_devices_stop_async_rx(chain->device);
subghz_devices_idle(chain->device);
}
chain->state = ProtoPirateTxRxStateIDLE;
chain->state = GDRTxRxStateIDLE;
}
float protopirate_rx_chain_get_rssi(ProtoPirateRxChain* chain) {
float gdr_rx_chain_get_rssi(GDRRxChain* chain) {
furi_check(chain);
if(!chain->device || chain->state != ProtoPirateTxRxStateRx) {
if(!chain->device || chain->state != GDRTxRxStateRx) {
return -127.0f;
}
return subghz_devices_get_rssi(chain->device);
@@ -1,7 +1,7 @@
// helpers/protopirate_rx_chain.h
// helpers/gdr_rx_chain.h
#pragma once
#include "protopirate_types.h"
#include "gdr_types.h"
#if defined(ENABLE_DUAL_RX_SCENE) || defined(ENABLE_SHIELD_RX_SCENE)
@@ -15,7 +15,7 @@
#include "radio_device_loader.h"
#include "../protocols/protocol_items.h"
#include "../protocols/protopirate_protocol_plugins.h"
#include "../protocols/gdr_protocol_plugins.h"
typedef struct {
char label; // 'A' or 'B' (display tag)
@@ -28,9 +28,9 @@ typedef struct {
CompositeApiResolver* resolver;
PluginManager* plugin_manager;
const ProtoPirateProtocolPlugin* plugin;
const GDRProtocolPlugin* plugin;
const SubGhzProtocolRegistry* registry;
ProtoPirateProtocolRegistryFilter filter;
GDRProtocolRegistryFilter filter;
SubGhzRadioPreset preset; // .name is an owned FuriString
uint8_t* base_preset_data;
@@ -40,43 +40,43 @@ typedef struct {
uint32_t frequency;
uint32_t rx_bandwidth_hz;
ProtoPirateTxRxState state;
} ProtoPirateRxChain;
GDRTxRxState state;
} GDRRxChain;
ProtoPirateRxChain* protopirate_rx_chain_alloc(char label);
GDRRxChain* gdr_rx_chain_alloc(char label);
void protopirate_rx_chain_free(ProtoPirateRxChain* chain);
void gdr_rx_chain_free(GDRRxChain* chain);
bool protopirate_rx_chain_acquire_device(
ProtoPirateRxChain* chain,
bool gdr_rx_chain_acquire_device(
GDRRxChain* chain,
SubGhzRadioDeviceType type);
bool protopirate_rx_chain_set_preset(
ProtoPirateRxChain* chain,
bool gdr_rx_chain_set_preset(
GDRRxChain* chain,
SubGhzSetting* setting,
const char* preset_name,
uint32_t frequency);
bool protopirate_rx_chain_set_preset_data(
ProtoPirateRxChain* chain,
bool gdr_rx_chain_set_preset_data(
GDRRxChain* chain,
const char* preset_name,
uint8_t* preset_data,
size_t preset_data_size,
uint32_t frequency);
bool protopirate_rx_chain_apply_shield_profile(ProtoPirateRxChain* chain);
bool gdr_rx_chain_apply_shield_profile(GDRRxChain* chain);
bool protopirate_rx_chain_init_receiver(ProtoPirateRxChain* chain);
bool gdr_rx_chain_init_receiver(GDRRxChain* chain);
void protopirate_rx_chain_set_decode_callback(
ProtoPirateRxChain* chain,
void gdr_rx_chain_set_decode_callback(
GDRRxChain* chain,
SubGhzReceiverCallback callback,
void* context);
bool protopirate_rx_chain_start(ProtoPirateRxChain* chain);
bool gdr_rx_chain_start(GDRRxChain* chain);
void protopirate_rx_chain_stop(ProtoPirateRxChain* chain);
void gdr_rx_chain_stop(GDRRxChain* chain);
float protopirate_rx_chain_get_rssi(ProtoPirateRxChain* chain);
float gdr_rx_chain_get_rssi(GDRRxChain* chain);
#endif // ENABLE_DUAL_RX_SCENE || ENABLE_SHIELD_RX_SCENE
@@ -1,5 +1,5 @@
// helpers/protopirate_settings.c
#include "protopirate_settings.h"
// helpers/gdr_settings.c
#include "gdr_settings.h"
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#include <furi.h>
@@ -7,12 +7,12 @@
#include "../defines.h"
#include "../protocols/protocols_common.h"
#define TAG "ProtoPirateSettings"
#define TAG "GDRSettings"
#define SETTINGS_FILE_HEADER "ProtoPirate Settings"
#define SETTINGS_FILE_HEADER "GDR Settings"
#define SETTINGS_FILE_VERSION 1
void protopirate_settings_set_defaults(ProtoPirateSettings* settings) {
void gdr_settings_set_defaults(GDRSettings* settings) {
settings->frequency = 433920000;
settings->preset_index = 0;
settings->tx_power = 0;
@@ -31,15 +31,15 @@ void protopirate_settings_set_defaults(ProtoPirateSettings* settings) {
settings->shield_tx_power = 0;
}
void protopirate_settings_load(ProtoPirateSettings* settings) {
void gdr_settings_load(GDRSettings* settings) {
// Set defaults first
protopirate_settings_set_defaults(settings);
gdr_settings_set_defaults(settings);
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_file_alloc(storage);
do {
if(!flipper_format_file_open_existing(ff, PROTOPIRATE_SETTINGS_FILE)) {
if(!flipper_format_file_open_existing(ff, GDR_SETTINGS_FILE)) {
FURI_LOG_I(TAG, "Settings file not found, using defaults");
break;
}
@@ -177,16 +177,16 @@ void protopirate_settings_load(ProtoPirateSettings* settings) {
furi_record_close(RECORD_STORAGE);
}
void protopirate_settings_save(ProtoPirateSettings* settings) {
void gdr_settings_save(GDRSettings* settings) {
Storage* storage = furi_record_open(RECORD_STORAGE);
// Ensure directory exists
storage_simply_mkdir(storage, PROTOPIRATE_SETTINGS_DIR);
storage_simply_mkdir(storage, GDR_SETTINGS_DIR);
FlipperFormat* ff = flipper_format_file_alloc(storage);
do {
if(!flipper_format_file_open_always(ff, PROTOPIRATE_SETTINGS_FILE)) {
if(!flipper_format_file_open_always(ff, GDR_SETTINGS_FILE)) {
FURI_LOG_E(TAG, "Failed to open settings file for writing");
break;
}
@@ -0,0 +1,32 @@
// helpers/gdr_settings.h
#pragma once
#include <stdint.h>
#include <stdbool.h>
#define GDR_SETTINGS_FILE APP_DATA_PATH("settings.txt")
#define GDR_SETTINGS_DIR APP_DATA_PATH()
#define GDR_PRESET_NAME_MAX 64U
typedef struct {
uint32_t frequency;
uint8_t preset_index;
uint8_t tx_power;
bool auto_save;
bool hopping_enabled;
bool emulate_feature_enabled;
uint32_t dual_freq_a;
uint32_t dual_freq_b;
uint8_t dual_preset_a;
uint8_t dual_preset_b;
char dual_preset_name_a[GDR_PRESET_NAME_MAX];
char dual_preset_name_b[GDR_PRESET_NAME_MAX];
uint32_t shield_freq;
uint8_t shield_preset_index;
uint8_t shield_tx_offset_index;
uint8_t shield_tx_power;
} GDRSettings;
void gdr_settings_load(GDRSettings* settings);
void gdr_settings_save(GDRSettings* settings);
void gdr_settings_set_defaults(GDRSettings* settings);
@@ -1,70 +1,70 @@
// helpers/protopirate_storage.c
#include "protopirate_storage.h"
// helpers/gdr_storage.c
#include "gdr_storage.h"
#include "../defines.h"
#include "../protocols/protocols_common.h"
#define TAG "ProtoPirateStorage"
#define TAG "GDRStorage"
bool protopirate_storage_init(void) {
bool gdr_storage_init(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
bool result = storage_simply_mkdir(storage, PROTOPIRATE_APP_FOLDER);
bool result = storage_simply_mkdir(storage, GDR_APP_FOLDER);
furi_record_close(RECORD_STORAGE);
return result;
}
void protopirate_storage_wipe_history_cache(void) {
void gdr_storage_wipe_history_cache(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_dir_exists(storage, PROTOPIRATE_HISTORY_FOLDER)) {
storage_simply_remove_recursive(storage, PROTOPIRATE_HISTORY_FOLDER);
if(storage_dir_exists(storage, GDR_HISTORY_FOLDER)) {
storage_simply_remove_recursive(storage, GDR_HISTORY_FOLDER);
FURI_LOG_I(TAG, "Wiped history cache");
}
furi_record_close(RECORD_STORAGE);
}
void protopirate_storage_purge_temp_history_at_startup(void) {
void gdr_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);
if(storage_dir_exists(storage, GDR_HISTORY_FOLDER)) {
storage_simply_remove_recursive(storage, GDR_HISTORY_FOLDER);
}
furi_record_close(RECORD_STORAGE);
}
bool protopirate_storage_ensure_history_folder(void) {
if(!protopirate_storage_init()) {
bool gdr_storage_ensure_history_folder(void) {
if(!gdr_storage_init()) {
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);
storage_simply_mkdir(storage, GDR_CACHE_FOLDER);
bool ok = storage_simply_mkdir(storage, GDR_HISTORY_FOLDER);
furi_record_close(RECORD_STORAGE);
return ok;
}
void protopirate_storage_build_history_path(uint32_t seq, FuriString* out) {
void gdr_storage_build_history_path(uint32_t seq, FuriString* out) {
furi_check(out);
furi_string_printf(
out,
"%s/hist_%08lu%s",
PROTOPIRATE_HISTORY_FOLDER,
GDR_HISTORY_FOLDER,
(unsigned long)seq,
PROTOPIRATE_APP_EXTENSION);
GDR_APP_EXTENSION);
}
bool protopirate_storage_save_history_capture(
bool gdr_storage_save_history_capture(
FlipperFormat* flipper_format,
uint32_t seq,
FuriString* out_path) {
furi_check(flipper_format);
furi_check(out_path);
if(!protopirate_storage_ensure_history_folder()) {
if(!gdr_storage_ensure_history_folder()) {
FURI_LOG_E(TAG, "History folder missing");
return false;
}
protopirate_storage_build_history_path(seq, out_path);
gdr_storage_build_history_path(seq, out_path);
return protopirate_storage_save_capture_to_path(
return gdr_storage_save_capture_to_path(
flipper_format, furi_string_get_cstr(out_path));
}
@@ -90,7 +90,7 @@ static void sanitize_filename(const char* input, char* output, size_t output_siz
output[j] = '\0';
}
bool protopirate_storage_get_next_filename(const char* protocol_name, FuriString* out_filename) {
bool gdr_storage_get_next_filename(const char* protocol_name, FuriString* out_filename) {
if(!protocol_name || !out_filename) return false;
Storage* storage = furi_record_open(RECORD_STORAGE);
FuriString* temp_path = furi_string_alloc();
@@ -104,10 +104,10 @@ bool protopirate_storage_get_next_filename(const char* protocol_name, FuriString
furi_string_printf(
temp_path,
"%s/%s_%03lu%s",
PROTOPIRATE_APP_FOLDER,
GDR_APP_FOLDER,
safe_name,
(unsigned long)index,
PROTOPIRATE_APP_EXTENSION);
GDR_APP_EXTENSION);
if(!storage_file_exists(storage, furi_string_get_cstr(temp_path))) {
furi_string_set(out_filename, temp_path);
@@ -122,7 +122,7 @@ bool protopirate_storage_get_next_filename(const char* protocol_name, FuriString
return found;
}
static const char* const protopirate_storage_base_u32_fields[] = {
static const char* const gdr_storage_base_u32_fields[] = {
"TE",
FF_SERIAL,
FF_BTN,
@@ -137,7 +137,7 @@ static const char* const protopirate_storage_base_u32_fields[] = {
FF_TYPE,
};
static const char* const protopirate_storage_tail_u32_fields[] = {
static const char* const gdr_storage_tail_u32_fields[] = {
"DataHi",
"DataLo",
"RawCnt",
@@ -147,7 +147,7 @@ static const char* const protopirate_storage_tail_u32_fields[] = {
"Checksum",
};
static bool protopirate_storage_fail(const char* action, const char* key) {
static bool gdr_storage_fail(const char* action, const char* key) {
UNUSED(action);
UNUSED(key);
FURI_LOG_E(TAG, "%s failed: %s", action, key);
@@ -155,13 +155,13 @@ static bool protopirate_storage_fail(const char* action, const char* key) {
}
static bool
protopirate_storage_get_count(FlipperFormat* flipper_format, const char* key, uint32_t* count) {
gdr_storage_get_count(FlipperFormat* flipper_format, const char* key, uint32_t* count) {
*count = 0;
flipper_format_rewind(flipper_format);
return flipper_format_get_value_count(flipper_format, key, count) && (*count > 0);
}
static bool protopirate_storage_copy_string_optional(
static bool gdr_storage_copy_string_optional(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
@@ -171,30 +171,30 @@ static bool protopirate_storage_copy_string_optional(
return true;
}
if(!flipper_format_write_string(save_file, key, value)) {
return protopirate_storage_fail("Write", key);
return gdr_storage_fail("Write", key);
}
return true;
}
static bool protopirate_storage_copy_string_if_present(
static bool gdr_storage_copy_string_if_present(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
FuriString* value) {
uint32_t count = 0;
if(!protopirate_storage_get_count(flipper_format, key, &count)) {
if(!gdr_storage_get_count(flipper_format, key, &count)) {
return true;
}
if(!flipper_format_read_string(flipper_format, key, value)) {
return protopirate_storage_fail("Read", key);
return gdr_storage_fail("Read", key);
}
if(!flipper_format_write_string(save_file, key, value)) {
return protopirate_storage_fail("Write", key);
return gdr_storage_fail("Write", key);
}
return true;
}
static bool protopirate_storage_copy_u32_optional(
static bool gdr_storage_copy_u32_optional(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key) {
@@ -204,25 +204,25 @@ static bool protopirate_storage_copy_u32_optional(
return true;
}
if(!flipper_format_write_uint32(save_file, key, &value, 1)) {
return protopirate_storage_fail("Write", key);
return gdr_storage_fail("Write", key);
}
return true;
}
static bool protopirate_storage_copy_u32_fields(
static bool gdr_storage_copy_u32_fields(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* const* fields,
size_t field_count) {
for(size_t i = 0; i < field_count; i++) {
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, fields[i])) {
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, fields[i])) {
return false;
}
}
return true;
}
static bool protopirate_storage_copy_hex_fixed(
static bool gdr_storage_copy_hex_fixed(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
@@ -242,12 +242,12 @@ static bool protopirate_storage_copy_hex_fixed(
*copied = true;
}
if(!flipper_format_write_hex(save_file, key, data, len)) {
return protopirate_storage_fail("Write", key);
return gdr_storage_fail("Write", key);
}
return true;
}
static bool protopirate_storage_copy_u32_array(
static bool gdr_storage_copy_u32_array(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
@@ -267,9 +267,9 @@ static bool protopirate_storage_copy_u32_array(
bool status = false;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, key, data, count)) {
protopirate_storage_fail("Read", key);
gdr_storage_fail("Read", key);
} else if(!flipper_format_write_uint32(save_file, key, data, count)) {
protopirate_storage_fail("Write", key);
gdr_storage_fail("Write", key);
} else {
status = true;
}
@@ -278,25 +278,25 @@ static bool protopirate_storage_copy_u32_array(
return status;
}
static bool protopirate_storage_copy_u32_array_if_present(
static bool gdr_storage_copy_u32_array_if_present(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
uint32_t max_count) {
uint32_t count = 0;
if(!protopirate_storage_get_count(flipper_format, key, &count)) {
if(!gdr_storage_get_count(flipper_format, key, &count)) {
return true;
}
return protopirate_storage_copy_u32_array(save_file, flipper_format, key, count, max_count);
return gdr_storage_copy_u32_array(save_file, flipper_format, key, count, max_count);
}
static bool protopirate_storage_copy_hex_array_if_present(
static bool gdr_storage_copy_hex_array_if_present(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
uint32_t max_count) {
uint32_t count = 0;
if(!protopirate_storage_get_count(flipper_format, key, &count)) {
if(!gdr_storage_get_count(flipper_format, key, &count)) {
return true;
}
if(count >= max_count) {
@@ -313,9 +313,9 @@ static bool protopirate_storage_copy_hex_array_if_present(
bool status = false;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_hex(flipper_format, key, data, count)) {
protopirate_storage_fail("Read", key);
gdr_storage_fail("Read", key);
} else if(!flipper_format_write_hex(save_file, key, data, count)) {
protopirate_storage_fail("Write", key);
gdr_storage_fail("Write", key);
} else {
status = true;
}
@@ -324,7 +324,7 @@ static bool protopirate_storage_copy_hex_array_if_present(
return status;
}
static bool protopirate_storage_copy_key(
static bool gdr_storage_copy_key(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
FuriString* value) {
@@ -333,46 +333,46 @@ static bool protopirate_storage_copy_key(
flipper_format_rewind(flipper_format);
if(flipper_format_read_string(flipper_format, FF_KEY, value)) {
if(!flipper_format_write_string(save_file, FF_KEY, value)) {
return protopirate_storage_fail("Write", FF_KEY);
return gdr_storage_fail("Write", FF_KEY);
}
return true;
}
if(protopirate_storage_get_count(flipper_format, FF_KEY, &count)) {
return protopirate_storage_copy_u32_array(save_file, flipper_format, FF_KEY, count, 1024);
if(gdr_storage_get_count(flipper_format, FF_KEY, &count)) {
return gdr_storage_copy_u32_array(save_file, flipper_format, FF_KEY, count, 1024);
}
return protopirate_storage_copy_hex_fixed(save_file, flipper_format, FF_KEY, 8, NULL);
return gdr_storage_copy_hex_fixed(save_file, flipper_format, FF_KEY, 8, NULL);
}
static bool protopirate_storage_copy_hex_or_u32(
static bool gdr_storage_copy_hex_or_u32(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
const char* key,
size_t hex_len) {
bool copied = false;
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, key, hex_len, &copied)) {
if(!gdr_storage_copy_hex_fixed(save_file, flipper_format, key, hex_len, &copied)) {
return false;
}
return copied || protopirate_storage_copy_u32_optional(save_file, flipper_format, key);
return copied || gdr_storage_copy_u32_optional(save_file, flipper_format, key);
}
static bool protopirate_storage_copy_key_2(
static bool gdr_storage_copy_key_2(
FlipperFormat* save_file,
FlipperFormat* flipper_format,
FuriString* value) {
bool copied = false;
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Key_2", 8, &copied)) {
if(!gdr_storage_copy_hex_fixed(save_file, flipper_format, "Key_2", 8, &copied)) {
return false;
}
if(copied) {
return true;
}
return protopirate_storage_copy_string_optional(save_file, flipper_format, "Key_2", value) &&
protopirate_storage_copy_u32_optional(save_file, flipper_format, "Key_2");
return gdr_storage_copy_string_optional(save_file, flipper_format, "Key_2", value) &&
gdr_storage_copy_u32_optional(save_file, flipper_format, "Key_2");
}
static bool protopirate_storage_write_capture_data(
static bool gdr_storage_write_capture_data(
FlipperFormat* save_file,
FlipperFormat* flipper_format) {
furi_check(save_file);
@@ -386,51 +386,51 @@ static bool protopirate_storage_write_capture_data(
bool status = false;
do {
if(!protopirate_storage_copy_string_optional(
if(!gdr_storage_copy_string_optional(
save_file, flipper_format, FF_PROTOCOL, string_value))
break;
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, FF_BIT)) break;
if(!protopirate_storage_copy_key(save_file, flipper_format, string_value)) break;
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, FF_FREQUENCY)) break;
if(!protopirate_storage_copy_string_optional(
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, FF_BIT)) break;
if(!gdr_storage_copy_key(save_file, flipper_format, string_value)) break;
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, FF_FREQUENCY)) break;
if(!gdr_storage_copy_string_optional(
save_file, flipper_format, FF_PRESET, string_value))
break;
if(!protopirate_storage_copy_string_optional(
if(!gdr_storage_copy_string_optional(
save_file, flipper_format, "RadioDevice", string_value))
break;
if(!protopirate_storage_copy_string_if_present(
if(!gdr_storage_copy_string_if_present(
save_file, flipper_format, "Custom_preset_module", string_value))
break;
if(!protopirate_storage_copy_hex_array_if_present(
if(!gdr_storage_copy_hex_array_if_present(
save_file, flipper_format, "Custom_preset_data", 1024))
break;
if(!protopirate_storage_copy_u32_fields(
if(!gdr_storage_copy_u32_fields(
save_file,
flipper_format,
protopirate_storage_base_u32_fields,
COUNT_OF(protopirate_storage_base_u32_fields)))
gdr_storage_base_u32_fields,
COUNT_OF(gdr_storage_base_u32_fields)))
break;
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Key2", 8, NULL)) 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))
if(!gdr_storage_copy_hex_fixed(save_file, flipper_format, "Key2", 8, NULL)) break;
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, "KeyIdx")) break;
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, "Seed")) break;
if(!gdr_storage_copy_hex_or_u32(save_file, flipper_format, "ValidationField", 2))
break;
if(!protopirate_storage_copy_key_2(save_file, flipper_format, string_value)) break;
if(!protopirate_storage_copy_hex_or_u32(save_file, flipper_format, "Key_3", 4)) break;
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Key_4")) break;
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_u32_array_if_present(
if(!gdr_storage_copy_key_2(save_file, flipper_format, string_value)) break;
if(!gdr_storage_copy_hex_or_u32(save_file, flipper_format, "Key_3", 4)) break;
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, "Key_4")) break;
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, "Fx")) break;
if(!gdr_storage_copy_hex_fixed(save_file, flipper_format, "Key1", 8, NULL)) break;
if(!gdr_storage_copy_u32_optional(save_file, flipper_format, "Check")) break;
if(!gdr_storage_copy_u32_array_if_present(
save_file, flipper_format, "RAW_Data", 4096))
break;
if(!protopirate_storage_copy_u32_fields(
if(!gdr_storage_copy_u32_fields(
save_file,
flipper_format,
protopirate_storage_tail_u32_fields,
COUNT_OF(protopirate_storage_tail_u32_fields)))
gdr_storage_tail_u32_fields,
COUNT_OF(gdr_storage_tail_u32_fields)))
break;
if(!protopirate_storage_copy_string_optional(
if(!gdr_storage_copy_string_optional(
save_file, flipper_format, FF_MANUFACTURE, string_value))
break;
status = true;
@@ -441,11 +441,11 @@ static bool protopirate_storage_write_capture_data(
return status;
}
bool protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path) {
bool gdr_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path) {
furi_check(flipper_format);
furi_check(full_path);
if(!protopirate_storage_init()) {
if(!gdr_storage_init()) {
FURI_LOG_E(TAG, "Failed to create app folder");
return false;
}
@@ -470,7 +470,7 @@ bool protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, con
break;
}
if(!protopirate_storage_write_capture_data(save_file, flipper_format)) {
if(!gdr_storage_write_capture_data(save_file, flipper_format)) {
FURI_LOG_E(TAG, "Failed to write capture data");
break;
}
@@ -485,16 +485,16 @@ bool protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, con
return result;
}
void protopirate_storage_delete_temp(void) {
void gdr_storage_delete_temp(void) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_file_exists(storage, PROTOPIRATE_TEMP_FILE)) {
storage_simply_remove(storage, PROTOPIRATE_TEMP_FILE);
if(storage_file_exists(storage, GDR_TEMP_FILE)) {
storage_simply_remove(storage, GDR_TEMP_FILE);
FURI_LOG_I(TAG, "Deleted temp file");
}
furi_record_close(RECORD_STORAGE);
}
bool protopirate_storage_save_capture(
bool gdr_storage_save_capture(
FlipperFormat* flipper_format,
const char* protocol_name,
FuriString* out_path) {
@@ -502,14 +502,14 @@ bool protopirate_storage_save_capture(
furi_check(protocol_name);
furi_check(out_path);
if(!protopirate_storage_init()) {
if(!gdr_storage_init()) {
FURI_LOG_E(TAG, "Failed to create app folder");
return false;
}
FuriString* file_path = furi_string_alloc();
if(!protopirate_storage_get_next_filename(protocol_name, file_path)) {
if(!gdr_storage_get_next_filename(protocol_name, file_path)) {
FURI_LOG_E(TAG, "Failed to get next filename");
furi_string_free(file_path);
return false;
@@ -530,7 +530,7 @@ bool protopirate_storage_save_capture(
break;
}
if(!protopirate_storage_write_capture_data(save_file, flipper_format)) {
if(!gdr_storage_write_capture_data(save_file, flipper_format)) {
FURI_LOG_E(TAG, "Failed to write capture data");
break;
}
@@ -548,7 +548,7 @@ bool protopirate_storage_save_capture(
return result;
}
bool protopirate_storage_delete_file(const char* file_path) {
bool gdr_storage_delete_file(const char* file_path) {
Storage* storage = furi_record_open(RECORD_STORAGE);
bool result = storage_simply_remove(storage, file_path);
furi_record_close(RECORD_STORAGE);
@@ -0,0 +1,59 @@
// helpers/gdr_storage.h
#pragma once
#include <furi.h>
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#define GDR_APP_FOLDER APP_DATA_PATH("saved")
#define GDR_APP_EXTENSION ".psf"
#define GDR_APP_FILE_VERSION 1
#define GDR_TEMP_FILE APP_DATA_PATH("saved/.temp.psf")
#define GDR_CACHE_FOLDER APP_DATA_PATH("cache")
#define GDR_HISTORY_FOLDER APP_DATA_PATH("cache/history")
// Initialize storage (create folder if needed)
bool gdr_storage_init(void);
// Save a capture to a new file (auto-generated name)
bool gdr_storage_save_capture(
FlipperFormat* flipper_format,
const char* protocol_name,
FuriString* out_path);
// Save a capture to a specific file path (user-chosen name)
bool gdr_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path);
// Save to temp file for emulation
bool gdr_storage_save_temp(FlipperFormat* flipper_format);
// Delete temp file
void gdr_storage_delete_temp(void);
// Get next available filename for a protocol
bool gdr_storage_get_next_filename(const char* protocol_name, FuriString* out_filename);
// Delete a file
bool gdr_storage_delete_file(const char* file_path);
// Load a file (caller must close with gdr_storage_close_file)
FlipperFormat* gdr_storage_load_file(const char* file_path);
// Close a loaded file (by gdr_storage_load_file only)
void gdr_storage_close_file(FlipperFormat* flipper_format);
// Check if file exists
bool gdr_storage_file_exists(const char* file_path);
bool gdr_storage_ensure_history_folder(void);
void gdr_storage_purge_temp_history_at_startup(void);
void gdr_storage_wipe_history_cache(void);
bool gdr_storage_save_history_capture(
FlipperFormat* flipper_format,
uint32_t seq,
FuriString* out_path);
void gdr_storage_build_history_path(uint32_t seq, FuriString* out);
@@ -1,19 +1,19 @@
// helpers/protopirate_tx_chain.c
#include "protopirate_tx_chain.h"
// helpers/gdr_tx_chain.c
#include "gdr_tx_chain.h"
#ifdef ENABLE_SHIELD_RX_SCENE
#include <furi.h>
#include <string.h>
#define TAG "ProtoPirateTxChain"
#define TAG "GDRTxChain"
#define PROTOPIRATE_TX_CARRIER_PRESET "AM650"
#define PROTOPIRATE_TX_POWER_COUNT 9U
#define PROTOPIRATE_TX_PRESET_VALUES_AM 8U
#define PROTOPIRATE_TX_PRESET_VALUES_COUNT 17U
#define GDR_TX_CARRIER_PRESET "AM650"
#define GDR_TX_POWER_COUNT 9U
#define GDR_TX_PRESET_VALUES_AM 8U
#define GDR_TX_PRESET_VALUES_COUNT 17U
static const uint8_t protopirate_tx_power_value[PROTOPIRATE_TX_PRESET_VALUES_COUNT] = {
static const uint8_t gdr_tx_power_value[GDR_TX_PRESET_VALUES_COUNT] = {
0,
0xC0,
0xC8,
@@ -34,7 +34,7 @@ static const uint8_t protopirate_tx_power_value[PROTOPIRATE_TX_PRESET_VALUES_COU
};
static size_t
protopirate_tx_chain_get_pa_table_offset(const uint8_t* preset_data, size_t preset_size) {
gdr_tx_chain_get_pa_table_offset(const uint8_t* preset_data, size_t preset_size) {
size_t offset = 0;
while((offset + 1U) < preset_size) {
if(preset_data[offset] == 0U) {
@@ -45,15 +45,15 @@ static size_t
return 0U;
}
static void protopirate_tx_chain_apply_tx_power(
static void gdr_tx_chain_apply_tx_power(
uint8_t* preset_data,
size_t preset_size,
uint8_t tx_power) {
if(!tx_power || tx_power >= PROTOPIRATE_TX_POWER_COUNT) {
if(!tx_power || tx_power >= GDR_TX_POWER_COUNT) {
return;
}
const size_t pa_offset = protopirate_tx_chain_get_pa_table_offset(preset_data, preset_size);
const size_t pa_offset = gdr_tx_chain_get_pa_table_offset(preset_data, preset_size);
if(!pa_offset) {
return;
}
@@ -65,15 +65,15 @@ static void protopirate_tx_chain_apply_tx_power(
return;
}
if(fm_byte) {
preset_data[pa_offset] = protopirate_tx_power_value[tx_power];
preset_data[pa_offset] = gdr_tx_power_value[tx_power];
} else if(am_byte) {
preset_data[pa_offset + 1U] =
protopirate_tx_power_value[PROTOPIRATE_TX_PRESET_VALUES_AM + tx_power];
gdr_tx_power_value[GDR_TX_PRESET_VALUES_AM + tx_power];
}
}
static uint8_t*
protopirate_tx_chain_copy_preset(const uint8_t* src, size_t src_size, size_t* out_size) {
gdr_tx_chain_copy_preset(const uint8_t* src, size_t src_size, size_t* out_size) {
if(!src || !src_size || !out_size) {
return NULL;
}
@@ -88,22 +88,22 @@ static uint8_t*
return copy;
}
ProtoPirateTxChain* protopirate_tx_chain_alloc(void) {
ProtoPirateTxChain* chain = malloc(sizeof(ProtoPirateTxChain));
GDRTxChain* gdr_tx_chain_alloc(void) {
GDRTxChain* chain = malloc(sizeof(GDRTxChain));
furi_check(chain);
memset(chain, 0, sizeof(ProtoPirateTxChain));
memset(chain, 0, sizeof(GDRTxChain));
chain->preset_name = furi_string_alloc();
furi_check(chain->preset_name);
chain->state = ProtoPirateTxRxStateIDLE;
chain->state = GDRTxRxStateIDLE;
return chain;
}
void protopirate_tx_chain_free(ProtoPirateTxChain* chain) {
void gdr_tx_chain_free(GDRTxChain* chain) {
if(!chain) {
return;
}
protopirate_tx_chain_stop(chain);
gdr_tx_chain_stop(chain);
if(chain->device) {
subghz_devices_idle(chain->device);
@@ -125,7 +125,7 @@ void protopirate_tx_chain_free(ProtoPirateTxChain* chain) {
free(chain);
}
bool protopirate_tx_chain_acquire_device(ProtoPirateTxChain* chain) {
bool gdr_tx_chain_acquire_device(GDRTxChain* chain) {
furi_check(chain);
chain->device = radio_device_loader_set(NULL, SubGhzRadioDeviceTypeInternal);
@@ -154,8 +154,8 @@ bool protopirate_tx_chain_acquire_device(ProtoPirateTxChain* chain) {
return true;
}
bool protopirate_tx_chain_configure(
ProtoPirateTxChain* chain,
bool gdr_tx_chain_configure(
GDRTxChain* chain,
SubGhzSetting* setting,
uint32_t rx_frequency,
int32_t offset_hz,
@@ -164,23 +164,23 @@ bool protopirate_tx_chain_configure(
furi_check(setting);
const uint8_t* source_preset =
subghz_setting_get_preset_data_by_name(setting, PROTOPIRATE_TX_CARRIER_PRESET);
subghz_setting_get_preset_data_by_name(setting, GDR_TX_CARRIER_PRESET);
if(!source_preset) {
FURI_LOG_E(TAG, "Carrier preset %s is unavailable", PROTOPIRATE_TX_CARRIER_PRESET);
FURI_LOG_E(TAG, "Carrier preset %s is unavailable", GDR_TX_CARRIER_PRESET);
return false;
}
size_t preset_count = subghz_setting_get_preset_count(setting);
size_t source_size = 0;
for(size_t i = 0; i < preset_count; i++) {
if(strcmp(subghz_setting_get_preset_name(setting, i), PROTOPIRATE_TX_CARRIER_PRESET) ==
if(strcmp(subghz_setting_get_preset_name(setting, i), GDR_TX_CARRIER_PRESET) ==
0) {
source_size = subghz_setting_get_preset_data_size(setting, i);
break;
}
}
if(!source_size) {
FURI_LOG_E(TAG, "Carrier preset %s has zero size", PROTOPIRATE_TX_CARRIER_PRESET);
FURI_LOG_E(TAG, "Carrier preset %s has zero size", GDR_TX_CARRIER_PRESET);
return false;
}
@@ -191,14 +191,14 @@ bool protopirate_tx_chain_configure(
}
chain->preset_data =
protopirate_tx_chain_copy_preset(source_preset, source_size, &chain->preset_data_size);
gdr_tx_chain_copy_preset(source_preset, source_size, &chain->preset_data_size);
if(!chain->preset_data) {
FURI_LOG_E(TAG, "Failed to copy preset data");
return false;
}
protopirate_tx_chain_apply_tx_power(chain->preset_data, chain->preset_data_size, tx_power);
furi_string_set(chain->preset_name, PROTOPIRATE_TX_CARRIER_PRESET);
gdr_tx_chain_apply_tx_power(chain->preset_data, chain->preset_data_size, tx_power);
furi_string_set(chain->preset_name, GDR_TX_CARRIER_PRESET);
const int64_t tx_frequency_signed = (int64_t)rx_frequency + offset_hz;
if(tx_frequency_signed <= 0 || tx_frequency_signed > UINT32_MAX) {
@@ -224,13 +224,13 @@ bool protopirate_tx_chain_configure(
return true;
}
bool protopirate_tx_chain_start_carrier(ProtoPirateTxChain* chain) {
bool gdr_tx_chain_start_carrier(GDRTxChain* chain) {
furi_check(chain);
if(!chain->device || !chain->data_gpio || !chain->preset_data) {
FURI_LOG_E(TAG, "start rejected (incomplete stack)");
return false;
}
if(chain->state == ProtoPirateTxRxStateTx) {
if(chain->state == GDRTxRxStateTx) {
return true;
}
@@ -247,16 +247,16 @@ bool protopirate_tx_chain_start_carrier(ProtoPirateTxChain* chain) {
subghz_devices_idle(chain->device);
return false;
}
chain->state = ProtoPirateTxRxStateTx;
chain->state = GDRTxRxStateTx;
FURI_LOG_I(TAG, "Carrier TX started on %lu Hz", chain->frequency);
return true;
}
void protopirate_tx_chain_stop(ProtoPirateTxChain* chain) {
void gdr_tx_chain_stop(GDRTxChain* chain) {
if(!chain) {
return;
}
if(chain->state != ProtoPirateTxRxStateTx) {
if(chain->state != GDRTxRxStateTx) {
return;
}
@@ -267,7 +267,7 @@ void protopirate_tx_chain_stop(ProtoPirateTxChain* chain) {
furi_hal_gpio_write(chain->data_gpio, false);
furi_hal_gpio_init(chain->data_gpio, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
chain->state = ProtoPirateTxRxStateIDLE;
chain->state = GDRTxRxStateIDLE;
}
#endif // ENABLE_SHIELD_RX_SCENE
@@ -0,0 +1,38 @@
// helpers/gdr_tx_chain.h
#pragma once
#include "gdr_types.h"
#ifdef ENABLE_SHIELD_RX_SCENE
#include <lib/subghz/subghz_setting.h>
#include <lib/subghz/devices/devices.h>
#include "radio_device_loader.h"
typedef struct {
const SubGhzDevice* device;
const GpioPin* data_gpio;
uint8_t* preset_data;
size_t preset_data_size;
FuriString* preset_name;
uint32_t frequency;
GDRTxRxState state;
} GDRTxChain;
GDRTxChain* gdr_tx_chain_alloc(void);
void gdr_tx_chain_free(GDRTxChain* chain);
bool gdr_tx_chain_acquire_device(GDRTxChain* chain);
bool gdr_tx_chain_configure(
GDRTxChain* chain,
SubGhzSetting* setting,
uint32_t rx_frequency,
int32_t offset_hz,
uint8_t tx_power);
bool gdr_tx_chain_start_carrier(GDRTxChain* chain);
void gdr_tx_chain_stop(GDRTxChain* chain);
#endif // ENABLE_SHIELD_RX_SCENE
@@ -0,0 +1,95 @@
// helpers/gdr_types.h
#pragma once
#include <furi.h>
#include <furi_hal.h>
#include "../defines.h"
typedef enum {
GDRViewVariableItemList,
GDRViewSubmenu,
GDRViewWidget,
GDRViewReceiver,
GDRViewAbout,
GDRViewFileBrowser,
GDRViewTextInput,
#ifdef ENABLE_DUAL_RX_SCENE
GDRViewDualReceiver,
#endif
} GDRView;
typedef enum {
// Custom events for views
GDRCustomEventViewReceiverOK,
GDRCustomEventViewReceiverConfig,
GDRCustomEventViewReceiverBack,
GDRCustomEventViewReceiverDeleteItem,
GDRCustomEventViewReceiverUnlock,
// Custom events for scenes
GDRCustomEventSceneReceiverUpdate,
GDRCustomEventReceiverDeferredRxStart,
GDRCustomEventSceneSettingLock,
// File management
GDRCustomEventReceiverInfoSave,
GDRCustomEventReceiverInfoSaveConfirm,
GDRCustomEventReceiverInfoEmulate,
GDRCustomEventReceiverInfoBruteforceStart,
GDRCustomEventReceiverInfoBruteforceCancel,
GDRCustomEventSavedInfoDelete,
// Emulator
GDRCustomEventSavedInfoEmulate,
GDRCustomEventEmulateTransmit,
GDRCustomEventEmulateStop,
GDRCustomEventEmulateExit,
// Sub decode
GDRCustomEventSubDecodeUpdate,
GDRCustomEventSubDecodeSave,
GDRCustomEventSubDecodeBruteforceStart,
GDRCustomEventPsaBruteforceComplete,
// File Browser
GDRCustomEventSavedFileSelected,
// Need saving confirmation
GDRCustomEventSceneStay,
GDRCustomEventSceneExit,
// About scene
GDRCustomEventAboutToggleEmulate,
#ifdef ENABLE_DUAL_RX_SCENE
// Dual RX scene
GDRCustomEventDualReceiverDeferredRxStart,
GDRCustomEventDualReceiverUpdate,
GDRCustomEventViewDualReceiverOK,
GDRCustomEventViewDualReceiverBack,
GDRCustomEventViewDualReceiverDeleteItem,
GDRCustomEventViewDualReceiverConfig,
#endif
#ifdef ENABLE_SHIELD_RX_SCENE
GDRCustomEventShieldReceiverDeferredStart,
GDRCustomEventShieldReceiverUpdate,
#endif
} GDRCustomEvent;
typedef enum {
GDRLockOff,
GDRLockOn,
} GDRLock;
typedef enum {
GDRTxRxStateIDLE,
GDRTxRxStateRx,
GDRTxRxStateTx,
GDRTxRxStateSleep,
} GDRTxRxState;
typedef enum {
GDRHopperStateOFF,
GDRHopperStateRunning,
GDRHopperStatePause,
GDRHopperStateRSSITimeOut,
} GDRHopperState;
typedef enum {
GDRRxKeyStateIDLE,
GDRRxKeyStateBack,
GDRRxKeyStateStart,
GDRRxKeyStateAddKey,
} GDRRxKeyState;
@@ -1,14 +0,0 @@
#include "protopirate_psa_bf_host.h"
bool protopirate_psa_bf_plugin_ensure_loaded(ProtoPirateApp* app) {
(void)app;
return false;
}
void protopirate_psa_bf_plugin_unload_if_idle(ProtoPirateApp* app) {
(void)app;
}
void protopirate_psa_bf_context_release(ProtoPirateApp* app) {
(void)app;
}
@@ -1,15 +0,0 @@
#pragma once
#include <stdbool.h>
typedef struct ProtoPirateApp ProtoPirateApp;
bool protopirate_psa_bf_plugin_ensure_loaded(ProtoPirateApp* app);
void protopirate_psa_bf_plugin_unload_if_idle(ProtoPirateApp* app);
void protopirate_psa_bf_context_release(ProtoPirateApp* app);
void protopirate_receiver_info_rebuild_normal_widget(void* app);
#ifdef ENABLE_SUB_DECODE_SCENE
void protopirate_subdecode_psa_bf_complete_refresh(void* app);
#endif
@@ -1,32 +0,0 @@
// helpers/protopirate_settings.h
#pragma once
#include <stdint.h>
#include <stdbool.h>
#define PROTOPIRATE_SETTINGS_FILE APP_DATA_PATH("settings.txt")
#define PROTOPIRATE_SETTINGS_DIR APP_DATA_PATH()
#define PROTOPIRATE_PRESET_NAME_MAX 64U
typedef struct {
uint32_t frequency;
uint8_t preset_index;
uint8_t tx_power;
bool auto_save;
bool hopping_enabled;
bool emulate_feature_enabled;
uint32_t dual_freq_a;
uint32_t dual_freq_b;
uint8_t dual_preset_a;
uint8_t dual_preset_b;
char dual_preset_name_a[PROTOPIRATE_PRESET_NAME_MAX];
char dual_preset_name_b[PROTOPIRATE_PRESET_NAME_MAX];
uint32_t shield_freq;
uint8_t shield_preset_index;
uint8_t shield_tx_offset_index;
uint8_t shield_tx_power;
} ProtoPirateSettings;
void protopirate_settings_load(ProtoPirateSettings* settings);
void protopirate_settings_save(ProtoPirateSettings* settings);
void protopirate_settings_set_defaults(ProtoPirateSettings* settings);
@@ -1,59 +0,0 @@
// helpers/protopirate_storage.h
#pragma once
#include <furi.h>
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#define PROTOPIRATE_APP_FOLDER APP_DATA_PATH("saved")
#define PROTOPIRATE_APP_EXTENSION ".psf"
#define PROTOPIRATE_APP_FILE_VERSION 1
#define PROTOPIRATE_TEMP_FILE APP_DATA_PATH("saved/.temp.psf")
#define PROTOPIRATE_CACHE_FOLDER APP_DATA_PATH("cache")
#define PROTOPIRATE_HISTORY_FOLDER APP_DATA_PATH("cache/history")
// Initialize storage (create folder if needed)
bool protopirate_storage_init(void);
// Save a capture to a new file (auto-generated name)
bool protopirate_storage_save_capture(
FlipperFormat* flipper_format,
const char* protocol_name,
FuriString* out_path);
// 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);
// 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);
void protopirate_storage_wipe_history_cache(void);
bool protopirate_storage_save_history_capture(
FlipperFormat* flipper_format,
uint32_t seq,
FuriString* out_path);
void protopirate_storage_build_history_path(uint32_t seq, FuriString* out);
@@ -1,38 +0,0 @@
// helpers/protopirate_tx_chain.h
#pragma once
#include "protopirate_types.h"
#ifdef ENABLE_SHIELD_RX_SCENE
#include <lib/subghz/subghz_setting.h>
#include <lib/subghz/devices/devices.h>
#include "radio_device_loader.h"
typedef struct {
const SubGhzDevice* device;
const GpioPin* data_gpio;
uint8_t* preset_data;
size_t preset_data_size;
FuriString* preset_name;
uint32_t frequency;
ProtoPirateTxRxState state;
} ProtoPirateTxChain;
ProtoPirateTxChain* protopirate_tx_chain_alloc(void);
void protopirate_tx_chain_free(ProtoPirateTxChain* chain);
bool protopirate_tx_chain_acquire_device(ProtoPirateTxChain* chain);
bool protopirate_tx_chain_configure(
ProtoPirateTxChain* chain,
SubGhzSetting* setting,
uint32_t rx_frequency,
int32_t offset_hz,
uint8_t tx_power);
bool protopirate_tx_chain_start_carrier(ProtoPirateTxChain* chain);
void protopirate_tx_chain_stop(ProtoPirateTxChain* chain);
#endif // ENABLE_SHIELD_RX_SCENE
@@ -1,95 +0,0 @@
// helpers/protopirate_types.h
#pragma once
#include <furi.h>
#include <furi_hal.h>
#include "../defines.h"
typedef enum {
ProtoPirateViewVariableItemList,
ProtoPirateViewSubmenu,
ProtoPirateViewWidget,
ProtoPirateViewReceiver,
ProtoPirateViewAbout,
ProtoPirateViewFileBrowser,
ProtoPirateViewTextInput,
#ifdef ENABLE_DUAL_RX_SCENE
ProtoPirateViewDualReceiver,
#endif
} ProtoPirateView;
typedef enum {
// Custom events for views
ProtoPirateCustomEventViewReceiverOK,
ProtoPirateCustomEventViewReceiverConfig,
ProtoPirateCustomEventViewReceiverBack,
ProtoPirateCustomEventViewReceiverDeleteItem,
ProtoPirateCustomEventViewReceiverUnlock,
// Custom events for scenes
ProtoPirateCustomEventSceneReceiverUpdate,
ProtoPirateCustomEventReceiverDeferredRxStart,
ProtoPirateCustomEventSceneSettingLock,
// File management
ProtoPirateCustomEventReceiverInfoSave,
ProtoPirateCustomEventReceiverInfoSaveConfirm,
ProtoPirateCustomEventReceiverInfoEmulate,
ProtoPirateCustomEventReceiverInfoBruteforceStart,
ProtoPirateCustomEventReceiverInfoBruteforceCancel,
ProtoPirateCustomEventSavedInfoDelete,
// Emulator
ProtoPirateCustomEventSavedInfoEmulate,
ProtoPirateCustomEventEmulateTransmit,
ProtoPirateCustomEventEmulateStop,
ProtoPirateCustomEventEmulateExit,
// Sub decode
ProtoPirateCustomEventSubDecodeUpdate,
ProtoPirateCustomEventSubDecodeSave,
ProtoPirateCustomEventSubDecodeBruteforceStart,
ProtoPirateCustomEventPsaBruteforceComplete,
// File Browser
ProtoPirateCustomEventSavedFileSelected,
// Need saving confirmation
ProtoPirateCustomEventSceneStay,
ProtoPirateCustomEventSceneExit,
// About scene
ProtoPirateCustomEventAboutToggleEmulate,
#ifdef ENABLE_DUAL_RX_SCENE
// Dual RX scene
ProtoPirateCustomEventDualReceiverDeferredRxStart,
ProtoPirateCustomEventDualReceiverUpdate,
ProtoPirateCustomEventViewDualReceiverOK,
ProtoPirateCustomEventViewDualReceiverBack,
ProtoPirateCustomEventViewDualReceiverDeleteItem,
ProtoPirateCustomEventViewDualReceiverConfig,
#endif
#ifdef ENABLE_SHIELD_RX_SCENE
ProtoPirateCustomEventShieldReceiverDeferredStart,
ProtoPirateCustomEventShieldReceiverUpdate,
#endif
} ProtoPirateCustomEvent;
typedef enum {
ProtoPirateLockOff,
ProtoPirateLockOn,
} ProtoPirateLock;
typedef enum {
ProtoPirateTxRxStateIDLE,
ProtoPirateTxRxStateRx,
ProtoPirateTxRxStateTx,
ProtoPirateTxRxStateSleep,
} ProtoPirateTxRxState;
typedef enum {
ProtoPirateHopperStateOFF,
ProtoPirateHopperStateRunning,
ProtoPirateHopperStatePause,
ProtoPirateHopperStateRSSITimeOut,
} ProtoPirateHopperState;
typedef enum {
ProtoPirateRxKeyStateIDLE,
ProtoPirateRxKeyStateBack,
ProtoPirateRxKeyStateStart,
ProtoPirateRxKeyStateAddKey,
} ProtoPirateRxKeyState;
@@ -1,6 +1,6 @@
#pragma once
#include "../protopirate_app_i.h"
#include "../gdr_app_i.h"
#ifdef ENABLE_SUB_DECODE_SCENE
#include <furi.h>
#include <storage/storage.h>

Before

Width:  |  Height:  |  Size: 171 B

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

@@ -4,11 +4,11 @@
#include <lib/subghz/types.h>
#include "protocol_items.h"
#define PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID "protopirate_protocol_plugins"
#define PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION 1U
#define GDR_PROTOCOL_PLUGIN_APP_ID "gdr_protocol_plugins"
#define GDR_PROTOCOL_PLUGIN_API_VERSION 1U
typedef struct {
const char* plugin_name;
ProtoPirateProtocolRegistryFilter filter;
GDRProtocolRegistryFilter filter;
const SubGhzProtocolRegistry* registry;
} ProtoPirateProtocolPlugin;
} GDRProtocolPlugin;
@@ -10,7 +10,7 @@ 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) {
void gdr_keys_load(SubGhzEnvironment* environment) {
SubGhzKeystore* keystore = subghz_environment_get_keystore(environment);
// Load keys from secure keystore
for
@@ -18,4 +18,4 @@ uint64_t get_kia_v6_keystore_b();
uint64_t get_kia_v5_key();
void protopirate_keys_load(SubGhzEnvironment* environment);
void gdr_keys_load(SubGhzEnvironment* environment);
@@ -0,0 +1,54 @@
#include "../gdr_protocol_plugins.h"
#include "../alutech_at_4n.h"
#include "../beninca_arc.h"
#include "../came.h"
#include "../came_atomo.h"
#include "../came_twee.h"
#include "../chamberlain_code.h"
#include "../clemsa.h"
#include "../dooya.h"
#include "../faac_slh.h"
#include "../gate_tx.h"
#include "../hormann.h"
#include "../keeloq.h"
#include "../linear.h"
#include "../linear_delta3.h"
#include "../megacode.h"
#include "../nice_flo.h"
#include "../nice_flor_s.h"
#include "../princeton.h"
#include "../somfy_keytis.h"
#include "../somfy_telis.h"
#define GDR_AM_PROTOCOL(symbol)
#include "gdr_am_protocols_list.inc"
#undef GDR_AM_PROTOCOL
static const SubGhzProtocol* const gdr_protocol_registry_am_items[] = {
#define GDR_AM_PROTOCOL(symbol) &symbol,
#include "gdr_am_protocols_list.inc"
#undef GDR_AM_PROTOCOL
};
static const SubGhzProtocolRegistry gdr_protocol_registry_am = {
.items = gdr_protocol_registry_am_items,
.size = sizeof(gdr_protocol_registry_am_items) /
sizeof(gdr_protocol_registry_am_items[0]),
};
static const GDRProtocolPlugin gdr_am_plugin = {
.plugin_name = "Garage Door Remote AM Registry",
.filter = GDRProtocolRegistryFilterAM,
.registry = &gdr_protocol_registry_am,
};
static const FlipperAppPluginDescriptor gdr_am_plugin_descriptor = {
.appid = GDR_PROTOCOL_PLUGIN_APP_ID,
.ep_api_version = GDR_PROTOCOL_PLUGIN_API_VERSION,
.entry_point = &gdr_am_plugin,
};
const FlipperAppPluginDescriptor* gdr_am_plugin_ep(void) {
return &gdr_am_plugin_descriptor;
}
@@ -0,0 +1,20 @@
GDR_AM_PROTOCOL(subghz_protocol_alutech_at_4n)
GDR_AM_PROTOCOL(subghz_protocol_beninca_arc)
GDR_AM_PROTOCOL(subghz_protocol_came)
GDR_AM_PROTOCOL(subghz_protocol_came_atomo)
GDR_AM_PROTOCOL(subghz_protocol_came_twee)
GDR_AM_PROTOCOL(subghz_protocol_chamb_code)
GDR_AM_PROTOCOL(subghz_protocol_clemsa)
GDR_AM_PROTOCOL(subghz_protocol_dooya)
GDR_AM_PROTOCOL(subghz_protocol_faac_slh)
GDR_AM_PROTOCOL(subghz_protocol_gate_tx)
GDR_AM_PROTOCOL(subghz_protocol_hormann)
GDR_AM_PROTOCOL(subghz_protocol_keeloq)
GDR_AM_PROTOCOL(subghz_protocol_linear)
GDR_AM_PROTOCOL(subghz_protocol_linear_delta3)
GDR_AM_PROTOCOL(subghz_protocol_megacode)
GDR_AM_PROTOCOL(subghz_protocol_nice_flo)
GDR_AM_PROTOCOL(subghz_protocol_nice_flor_s)
GDR_AM_PROTOCOL(subghz_protocol_princeton)
GDR_AM_PROTOCOL(subghz_protocol_somfy_keytis)
GDR_AM_PROTOCOL(subghz_protocol_somfy_telis)
@@ -0,0 +1,28 @@
#include "../gdr_protocol_plugins.h"
#include "../ansonic.h"
static const SubGhzProtocol* const gdr_protocol_registry_fm_items[] = {
&subghz_protocol_ansonic,
};
static const SubGhzProtocolRegistry gdr_protocol_registry_fm = {
.items = gdr_protocol_registry_fm_items,
.size = sizeof(gdr_protocol_registry_fm_items) /
sizeof(gdr_protocol_registry_fm_items[0]),
};
static const GDRProtocolPlugin gdr_fm_plugin = {
.plugin_name = "Garage Door Remote FM Registry",
.filter = GDRProtocolRegistryFilterFM,
.registry = &gdr_protocol_registry_fm,
};
static const FlipperAppPluginDescriptor gdr_fm_plugin_descriptor = {
.appid = GDR_PROTOCOL_PLUGIN_APP_ID,
.ep_api_version = GDR_PROTOCOL_PLUGIN_API_VERSION,
.entry_point = &gdr_fm_plugin,
};
const FlipperAppPluginDescriptor* gdr_fm_plugin_ep(void) {
return &gdr_fm_plugin_descriptor;
}
@@ -1,54 +0,0 @@
#include "../protopirate_protocol_plugins.h"
#include "../alutech_at_4n.h"
#include "../beninca_arc.h"
#include "../came.h"
#include "../came_atomo.h"
#include "../came_twee.h"
#include "../chamberlain_code.h"
#include "../clemsa.h"
#include "../dooya.h"
#include "../faac_slh.h"
#include "../gate_tx.h"
#include "../hormann.h"
#include "../keeloq.h"
#include "../linear.h"
#include "../linear_delta3.h"
#include "../megacode.h"
#include "../nice_flo.h"
#include "../nice_flor_s.h"
#include "../princeton.h"
#include "../somfy_keytis.h"
#include "../somfy_telis.h"
#define PROTOPIRATE_AM_PROTOCOL(symbol)
#include "protopirate_am_protocols_list.inc"
#undef PROTOPIRATE_AM_PROTOCOL
static const SubGhzProtocol* const protopirate_protocol_registry_am_items[] = {
#define PROTOPIRATE_AM_PROTOCOL(symbol) &symbol,
#include "protopirate_am_protocols_list.inc"
#undef PROTOPIRATE_AM_PROTOCOL
};
static const SubGhzProtocolRegistry protopirate_protocol_registry_am = {
.items = protopirate_protocol_registry_am_items,
.size = sizeof(protopirate_protocol_registry_am_items) /
sizeof(protopirate_protocol_registry_am_items[0]),
};
static const ProtoPirateProtocolPlugin protopirate_am_plugin = {
.plugin_name = "Garage Door Remote AM Registry",
.filter = ProtoPirateProtocolRegistryFilterAM,
.registry = &protopirate_protocol_registry_am,
};
static const FlipperAppPluginDescriptor protopirate_am_plugin_descriptor = {
.appid = PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
.ep_api_version = PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
.entry_point = &protopirate_am_plugin,
};
const FlipperAppPluginDescriptor* protopirate_am_plugin_ep(void) {
return &protopirate_am_plugin_descriptor;
}
@@ -1,20 +0,0 @@
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_alutech_at_4n)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_beninca_arc)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_came)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_came_atomo)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_came_twee)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_chamb_code)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_clemsa)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_dooya)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_faac_slh)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_gate_tx)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_hormann)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_keeloq)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_linear)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_linear_delta3)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_megacode)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_nice_flo)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_nice_flor_s)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_princeton)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_somfy_keytis)
PROTOPIRATE_AM_PROTOCOL(subghz_protocol_somfy_telis)
@@ -1,28 +0,0 @@
#include "../protopirate_protocol_plugins.h"
#include "../ansonic.h"
static const SubGhzProtocol* const protopirate_protocol_registry_fm_items[] = {
&subghz_protocol_ansonic,
};
static const SubGhzProtocolRegistry protopirate_protocol_registry_fm = {
.items = protopirate_protocol_registry_fm_items,
.size = sizeof(protopirate_protocol_registry_fm_items) /
sizeof(protopirate_protocol_registry_fm_items[0]),
};
static const ProtoPirateProtocolPlugin protopirate_fm_plugin = {
.plugin_name = "Garage Door Remote FM Registry",
.filter = ProtoPirateProtocolRegistryFilterFM,
.registry = &protopirate_protocol_registry_fm,
};
static const FlipperAppPluginDescriptor protopirate_fm_plugin_descriptor = {
.appid = PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
.ep_api_version = PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
.entry_point = &protopirate_fm_plugin,
};
const FlipperAppPluginDescriptor* protopirate_fm_plugin_ep(void) {
return &protopirate_fm_plugin_descriptor;
}
@@ -4,17 +4,17 @@
#include <string.h>
#endif
#define TAG "ProtoPirateRegistry"
#define TAG "GDRRegistry"
#define PROTOPIRATE_CC1101_REG_MDMCFG2 0x12U
#define PROTOPIRATE_CC1101_MOD_FORMAT_MASK 0x70U
#define PROTOPIRATE_CC1101_MOD_FORMAT_2FSK 0x00U
#define PROTOPIRATE_CC1101_MOD_FORMAT_GFSK 0x10U
#define PROTOPIRATE_CC1101_MOD_FORMAT_ASK_OOK 0x30U
#define PROTOPIRATE_CC1101_MOD_FORMAT_4FSK 0x40U
#define PROTOPIRATE_CC1101_MOD_FORMAT_MSK 0x70U
#define GDR_CC1101_REG_MDMCFG2 0x12U
#define GDR_CC1101_MOD_FORMAT_MASK 0x70U
#define GDR_CC1101_MOD_FORMAT_2FSK 0x00U
#define GDR_CC1101_MOD_FORMAT_GFSK 0x10U
#define GDR_CC1101_MOD_FORMAT_ASK_OOK 0x30U
#define GDR_CC1101_MOD_FORMAT_4FSK 0x40U
#define GDR_CC1101_MOD_FORMAT_MSK 0x70U
static bool protopirate_preset_try_get_register(
static bool gdr_preset_try_get_register(
const uint8_t* preset_data,
size_t preset_data_size,
uint8_t reg,
@@ -40,41 +40,41 @@ static bool protopirate_preset_try_get_register(
return false;
}
ProtoPirateProtocolRegistryFilter protopirate_get_protocol_registry_filter_for_preset(
GDRProtocolRegistryFilter gdr_get_protocol_registry_filter_for_preset(
const uint8_t* preset_data,
size_t preset_data_size) {
uint8_t mdmcfg2 = 0U;
if(!protopirate_preset_try_get_register(
preset_data, preset_data_size, PROTOPIRATE_CC1101_REG_MDMCFG2, &mdmcfg2)) {
if(!gdr_preset_try_get_register(
preset_data, preset_data_size, GDR_CC1101_REG_MDMCFG2, &mdmcfg2)) {
FURI_LOG_W(TAG, "Preset missing MDMCFG2, defaulting to AM registry");
return ProtoPirateProtocolRegistryFilterAM;
return GDRProtocolRegistryFilterAM;
}
// MDMCFG2[6:4] stores the CC1101 modulation format.
// ASK/OOK maps to our AM decoder set; the FSK-family formats map to FM.
switch(mdmcfg2 & PROTOPIRATE_CC1101_MOD_FORMAT_MASK) {
case PROTOPIRATE_CC1101_MOD_FORMAT_ASK_OOK:
return ProtoPirateProtocolRegistryFilterAM;
case PROTOPIRATE_CC1101_MOD_FORMAT_2FSK:
case PROTOPIRATE_CC1101_MOD_FORMAT_GFSK:
case PROTOPIRATE_CC1101_MOD_FORMAT_4FSK:
case PROTOPIRATE_CC1101_MOD_FORMAT_MSK:
return ProtoPirateProtocolRegistryFilterFM;
switch(mdmcfg2 & GDR_CC1101_MOD_FORMAT_MASK) {
case GDR_CC1101_MOD_FORMAT_ASK_OOK:
return GDRProtocolRegistryFilterAM;
case GDR_CC1101_MOD_FORMAT_2FSK:
case GDR_CC1101_MOD_FORMAT_GFSK:
case GDR_CC1101_MOD_FORMAT_4FSK:
case GDR_CC1101_MOD_FORMAT_MSK:
return GDRProtocolRegistryFilterFM;
default:
FURI_LOG_W(TAG, "Unknown MDMCFG2 0x%02X, defaulting to AM registry", mdmcfg2);
return ProtoPirateProtocolRegistryFilterAM;
return GDRProtocolRegistryFilterAM;
}
}
const char*
protopirate_get_protocol_registry_filter_name(ProtoPirateProtocolRegistryFilter filter) {
return (filter == ProtoPirateProtocolRegistryFilterFM) ? "FM" : "AM";
gdr_get_protocol_registry_filter_name(GDRProtocolRegistryFilter filter) {
return (filter == GDRProtocolRegistryFilterFM) ? "FM" : "AM";
}
#ifdef ENABLE_TIMING_TUNER_SCENE
// Protocol timing definitions - mirrors the SubGhzBlockConst in each protocol
static const ProtoPirateProtocolTiming protocol_timings[] = {
static const GDRProtocolTiming protocol_timings[] = {
// Star Line: PWM 250/500µs
{
.name = "Star Line",
@@ -96,7 +96,7 @@ static const ProtoPirateProtocolTiming protocol_timings[] = {
static const size_t protocol_timings_count = COUNT_OF(protocol_timings);
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing(const char* protocol_name) {
const GDRProtocolTiming* gdr_get_protocol_timing(const char* protocol_name) {
if(!protocol_name) return NULL;
for(size_t i = 0; i < protocol_timings_count; i++) {
@@ -124,12 +124,12 @@ const ProtoPirateProtocolTiming* protopirate_get_protocol_timing(const char* pro
return NULL;
}
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing_by_index(size_t index) {
const GDRProtocolTiming* gdr_get_protocol_timing_by_index(size_t index) {
if(index >= protocol_timings_count) return NULL;
return &protocol_timings[index];
}
size_t protopirate_get_protocol_timing_count(void) {
size_t gdr_get_protocol_timing_count(void) {
return protocol_timings_count;
}
#endif
@@ -6,16 +6,16 @@
#include "keeloq.h"
typedef enum {
ProtoPirateProtocolRegistryFilterAM = 0,
ProtoPirateProtocolRegistryFilterFM,
} ProtoPirateProtocolRegistryFilter;
GDRProtocolRegistryFilterAM = 0,
GDRProtocolRegistryFilterFM,
} GDRProtocolRegistryFilter;
ProtoPirateProtocolRegistryFilter protopirate_get_protocol_registry_filter_for_preset(
GDRProtocolRegistryFilter gdr_get_protocol_registry_filter_for_preset(
const uint8_t* preset_data,
size_t preset_data_size);
const char*
protopirate_get_protocol_registry_filter_name(ProtoPirateProtocolRegistryFilter filter);
gdr_get_protocol_registry_filter_name(GDRProtocolRegistryFilter filter);
#ifdef ENABLE_TIMING_TUNER_SCENE
// Timing information for protocol analysis
@@ -25,14 +25,14 @@ typedef struct {
uint32_t te_long;
uint32_t te_delta;
uint32_t min_count_bit;
} ProtoPirateProtocolTiming;
} GDRProtocolTiming;
// Get timing info for a protocol by name (returns NULL if not found)
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing(const char* protocol_name);
const GDRProtocolTiming* gdr_get_protocol_timing(const char* protocol_name);
// Get timing info by index (for iteration)
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing_by_index(size_t index);
const GDRProtocolTiming* gdr_get_protocol_timing_by_index(size_t index);
// Get number of protocols with timing info
size_t protopirate_get_protocol_timing_count(void);
size_t gdr_get_protocol_timing_count(void);
#endif
@@ -249,7 +249,7 @@ size_t
void pp_encoder_free(void* context) {
furi_check(context);
ProtoPirateEncoderHeader* hdr = context;
GDREncoderHeader* hdr = context;
hdr->encoder.upload = NULL;
hdr->encoder.size_upload = 0;
free(hdr);
@@ -257,14 +257,14 @@ void pp_encoder_free(void* context) {
void pp_encoder_stop(void* context) {
furi_check(context);
ProtoPirateEncoderHeader* hdr = context;
GDREncoderHeader* hdr = context;
hdr->encoder.is_running = false;
hdr->encoder.front = 0;
}
LevelDuration pp_encoder_yield(void* context) {
furi_check(context);
ProtoPirateEncoderHeader* hdr = context;
GDREncoderHeader* hdr = context;
if(hdr->encoder.repeat == 0 || !hdr->encoder.is_running || hdr->encoder.size_upload == 0) {
hdr->encoder.is_running = false;
return level_duration_reset();
@@ -298,7 +298,7 @@ void pp_shared_upload_release(void) {
void pp_encoder_buffer_ensure(void* context, size_t capacity) {
furi_check(context);
ProtoPirateEncoderHeader* hdr = context;
GDREncoderHeader* hdr = context;
furi_check(capacity <= PP_SHARED_UPLOAD_CAPACITY);
hdr->encoder.upload = pp_shared_upload_buffer();
hdr->encoder.size_upload = capacity;
@@ -306,7 +306,7 @@ void pp_encoder_buffer_ensure(void* context, size_t capacity) {
uint8_t pp_decoder_hash_blocks(void* context) {
furi_check(context);
ProtoPirateDecoderHeader* hdr = context;
GDRDecoderHeader* hdr = context;
return subghz_protocol_blocks_get_hash_data(
&hdr->decoder, (hdr->decoder.decode_count_bit / 8U) + 1U);
}

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