mirror of
https://protopirate.net/ProtoPirate/ProtoPirate.git
synced 2026-10-15 15:19:55 +00:00
319 lines
9.2 KiB
C
319 lines
9.2 KiB
C
#include "../../protopirate_app_i.h"
|
|
//#include "protopirate_about_plugin.h"
|
|
#include "pp_about_icons.h"
|
|
|
|
static const ProtoPirateAboutSceneHostApi* g_about_scene_host_api = NULL;
|
|
|
|
#define TAG "PPAboutPlugin"
|
|
|
|
#define CREDITS_START_Y 28
|
|
#define CREDITS_END_Y 52
|
|
#define CREDIT_LINE_HEIGHT 10
|
|
#define SCROLL_SPEED 1
|
|
|
|
#ifdef ENABLE_EMULATE_FEATURE
|
|
static const InputKey EMULATE_TOGGLE_COMBO[] = {
|
|
InputKeyUp,
|
|
InputKeyUp,
|
|
InputKeyDown,
|
|
InputKeyDown,
|
|
InputKeyLeft,
|
|
InputKeyRight,
|
|
InputKeyLeft,
|
|
InputKeyRight,
|
|
};
|
|
#define EMULATE_TOGGLE_COMBO_LEN (sizeof(EMULATE_TOGGLE_COMBO) / sizeof(EMULATE_TOGGLE_COMBO[0]))
|
|
#endif
|
|
|
|
static const char* credits[] = {
|
|
"",
|
|
"-=> App Development by <=-",
|
|
"RocketGod",
|
|
"MMX",
|
|
"Leeroy",
|
|
"gullradriel",
|
|
"Skorp's Weather App",
|
|
"Vadim's Radio Driver",
|
|
"",
|
|
"-=> Protocol Magic by <=-",
|
|
"L0rdDiakon",
|
|
"Leeroy",
|
|
"Li0ard",
|
|
"MMX",
|
|
"YougZ",
|
|
"DoobTheGoober",
|
|
"RocketGod",
|
|
"Skorp",
|
|
"Slackware",
|
|
"Trikk",
|
|
"Wootini",
|
|
"",
|
|
"-=> RE Support <=-",
|
|
"DoobTheGoober",
|
|
"Li0ard",
|
|
"MMX",
|
|
"NeedNotApply",
|
|
"RocketGod",
|
|
"Slackware",
|
|
"Trikk",
|
|
// can add more
|
|
};
|
|
|
|
#define CREDITS_COUNT (sizeof(credits) / sizeof(credits[0]))
|
|
|
|
typedef struct {
|
|
uint8_t frame;
|
|
uint8_t seed;
|
|
int16_t scroll_offset;
|
|
#ifdef ENABLE_EMULATE_FEATURE
|
|
uint8_t combo_progress;
|
|
#endif
|
|
} GlitchState;
|
|
|
|
static GlitchState g_state = {0};
|
|
|
|
static void draw_noise_pixels(Canvas* canvas, uint8_t density) {
|
|
for(uint8_t i = 0; i < density; i++) {
|
|
canvas_draw_dot(canvas, rand() % 128, rand() % 64);
|
|
}
|
|
}
|
|
|
|
static void about_draw_callback(Canvas* canvas, void* context) {
|
|
UNUSED(context);
|
|
|
|
srand(g_state.seed);
|
|
canvas_clear(canvas);
|
|
|
|
// Light background static
|
|
canvas_set_color(canvas, ColorBlack);
|
|
draw_noise_pixels(canvas, 6 + (rand() % 6));
|
|
|
|
// Occasional subtle x-jitter
|
|
int8_t x_off = (rand() % 15 == 0) ? ((rand() % 4) - 2) : 0;
|
|
|
|
// Animated TPP decoration (centered)
|
|
canvas_set_font(canvas, FontKeyboard);
|
|
if(g_state.frame % 8 < 4) {
|
|
canvas_draw_str_aligned(
|
|
canvas, 64, 18, AlignCenter, AlignBottom, ">>>=================<<<");
|
|
} else {
|
|
canvas_draw_str_aligned(
|
|
canvas, 64, 18, AlignCenter, AlignBottom, ">>>======[TPP]======<<<");
|
|
}
|
|
|
|
// Draw credits region (clip area)
|
|
canvas_set_font(canvas, FontSecondary);
|
|
|
|
// Calculate total scroll height
|
|
int16_t total_height = CREDITS_COUNT * CREDIT_LINE_HEIGHT;
|
|
|
|
// Draw scrolling credits
|
|
for(size_t i = 0; i < CREDITS_COUNT; i++) {
|
|
int16_t y = CREDITS_START_Y + (i * CREDIT_LINE_HEIGHT) - g_state.scroll_offset;
|
|
|
|
// Wrap around for endless scroll
|
|
while(y < CREDITS_START_Y - CREDIT_LINE_HEIGHT) {
|
|
y += total_height;
|
|
}
|
|
while(y > CREDITS_START_Y + total_height) {
|
|
y -= total_height;
|
|
}
|
|
|
|
// Only draw if in visible region
|
|
if(y >= CREDITS_START_Y - CREDIT_LINE_HEIGHT && y <= CREDITS_END_Y) {
|
|
canvas_draw_str(canvas, x_off, y, credits[i]);
|
|
}
|
|
}
|
|
|
|
// Draw fade/mask bars at top and bottom of credits area
|
|
canvas_set_color(canvas, ColorWhite);
|
|
canvas_draw_box(canvas, 0, 0, 128, CREDITS_START_Y - CREDIT_LINE_HEIGHT);
|
|
canvas_draw_box(canvas, 0, CREDITS_END_Y, 128, 14);
|
|
|
|
// Redraw header over mask
|
|
canvas_set_color(canvas, ColorBlack);
|
|
canvas_set_font(canvas, FontPrimary);
|
|
|
|
//Need to get the FAP_VERSION FROM THE HOST API
|
|
char fap_name_and_version[20];
|
|
snprintf(
|
|
fap_name_and_version,
|
|
sizeof(fap_name_and_version),
|
|
"ProtoPirate v%s",
|
|
g_about_scene_host_api->fap_version);
|
|
canvas_draw_str(canvas, 0, 10, fap_name_and_version);
|
|
|
|
canvas_set_font(canvas, FontKeyboard);
|
|
if(g_state.frame % 8 < 4) {
|
|
canvas_draw_str_aligned(
|
|
canvas, 64, 18, AlignCenter, AlignBottom, ">>>=================<<<");
|
|
} else {
|
|
canvas_draw_str_aligned(
|
|
canvas, 64, 18, AlignCenter, AlignBottom, ">>>======[TPP]======<<<");
|
|
}
|
|
|
|
// Redraw static in header area
|
|
srand(g_state.seed + 1);
|
|
for(uint8_t i = 0; i < 3; i++) {
|
|
canvas_draw_dot(canvas, rand() % 128, rand() % (CREDITS_START_Y - CREDIT_LINE_HEIGHT));
|
|
}
|
|
|
|
// Footer: The Pirate's Plunder Discord
|
|
canvas_set_font(canvas, FontKeyboard);
|
|
canvas_draw_str_aligned(canvas, 127, 62, AlignRight, AlignBottom, "discord.gg/thepirates");
|
|
|
|
// Rare subtle glitch bar
|
|
if(rand() % 30 == 0) {
|
|
canvas_set_color(canvas, ColorXOR);
|
|
uint8_t y = rand() % 60;
|
|
canvas_draw_box(canvas, 0, y, 128, 2);
|
|
}
|
|
}
|
|
|
|
static bool about_input_callback(InputEvent* event, void* context) {
|
|
furi_check(context);
|
|
#ifdef ENABLE_EMULATE_FEATURE
|
|
ProtoPirateApp* app = context;
|
|
|
|
if(event->type != InputTypePress) {
|
|
return false;
|
|
}
|
|
|
|
if(event->key == InputKeyBack) {
|
|
return false;
|
|
}
|
|
|
|
InputKey expected = EMULATE_TOGGLE_COMBO[g_state.combo_progress];
|
|
if(event->key == expected) {
|
|
g_state.combo_progress++;
|
|
if(g_state.combo_progress >= EMULATE_TOGGLE_COMBO_LEN) {
|
|
g_state.combo_progress = 0;
|
|
view_dispatcher_send_custom_event(
|
|
app->view_dispatcher, ProtoPirateCustomEventAboutToggleEmulate);
|
|
}
|
|
} else if(event->key == EMULATE_TOGGLE_COMBO[0]) {
|
|
g_state.combo_progress = 1;
|
|
} else {
|
|
g_state.combo_progress = 0;
|
|
}
|
|
|
|
return true;
|
|
#else
|
|
UNUSED(event);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#ifdef ENABLE_EMULATE_FEATURE
|
|
static void about_show_emulate_toggle_popup(ProtoPirateApp* app) {
|
|
const bool now_enabled = app->emulate_feature_enabled;
|
|
|
|
DialogMessage* message = dialog_message_alloc();
|
|
dialog_message_set_buttons(message, NULL, "OK", NULL);
|
|
dialog_message_set_icon(message, &I_WarningDolphin_45x42, 0, 12);
|
|
dialog_message_set_header(
|
|
message, now_enabled ? "Emulate Unlocked" : "Emulate Locked", 64, 0, AlignCenter, AlignTop);
|
|
dialog_message_set_text(
|
|
message,
|
|
now_enabled ? "Transmission is\nnow enabled!" : "Transmission is\nnow disabled!",
|
|
50,
|
|
14,
|
|
AlignLeft,
|
|
AlignTop);
|
|
dialog_message_show(app->dialogs, message);
|
|
dialog_message_free(message);
|
|
}
|
|
#endif
|
|
|
|
void plugin_protopirate_scene_about_on_enter(void* context) {
|
|
furi_check(context);
|
|
ProtoPirateApp* app = context;
|
|
|
|
if(!g_about_scene_host_api->ensure_view_about(app)) {
|
|
notification_message(app->notifications, &sequence_error);
|
|
scene_manager_previous_scene(app->scene_manager);
|
|
return;
|
|
}
|
|
|
|
g_state.frame = 0;
|
|
g_state.seed = furi_get_tick() & 0xFF;
|
|
g_state.scroll_offset = 0;
|
|
#ifdef ENABLE_EMULATE_FEATURE
|
|
g_state.combo_progress = 0;
|
|
#endif
|
|
|
|
view_set_draw_callback(app->view_about, about_draw_callback);
|
|
view_set_input_callback(app->view_about, about_input_callback);
|
|
view_set_context(app->view_about, app);
|
|
|
|
view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewAbout);
|
|
}
|
|
|
|
bool plugin_protopirate_scene_about_on_event(void* context, SceneManagerEvent event) {
|
|
ProtoPirateApp* app = context;
|
|
bool consumed = false;
|
|
|
|
if(event.type == SceneManagerEventTypeTick) {
|
|
g_state.frame++;
|
|
|
|
if(g_state.frame % 2 == 0) {
|
|
g_state.scroll_offset += SCROLL_SPEED;
|
|
int16_t total_height = CREDITS_COUNT * CREDIT_LINE_HEIGHT;
|
|
if(g_state.scroll_offset >= total_height) {
|
|
g_state.scroll_offset = 0;
|
|
}
|
|
}
|
|
|
|
view_commit_model(app->view_about, true);
|
|
consumed = true;
|
|
}
|
|
#ifdef ENABLE_EMULATE_FEATURE
|
|
else if(event.type == SceneManagerEventTypeCustom) {
|
|
if(event.event == ProtoPirateCustomEventAboutToggleEmulate) {
|
|
//Toggle Emulate on
|
|
app->emulate_feature_enabled = !app->emulate_feature_enabled;
|
|
|
|
ProtoPirateSettings settings;
|
|
g_about_scene_host_api->settings_load(&settings);
|
|
|
|
settings.emulate_feature_enabled = app->emulate_feature_enabled;
|
|
g_about_scene_host_api->settings_save(&settings);
|
|
|
|
notification_message(
|
|
app->notifications,
|
|
app->emulate_feature_enabled ? &sequence_success : &sequence_semi_success);
|
|
|
|
about_show_emulate_toggle_popup(app);
|
|
consumed = true;
|
|
}
|
|
}
|
|
#endif
|
|
return consumed;
|
|
}
|
|
|
|
void plugin_protopirate_scene_about_on_exit(void* context) {
|
|
UNUSED(context);
|
|
}
|
|
|
|
void about_plugin_set_host_api(const ProtoPirateAboutSceneHostApi* host_api) {
|
|
g_about_scene_host_api = host_api;
|
|
}
|
|
|
|
static const ProtoPirateAboutPlugin protopirate_about_plugin = {
|
|
.plugin_name = "About",
|
|
.on_enter = plugin_protopirate_scene_about_on_enter,
|
|
.on_event = plugin_protopirate_scene_about_on_event,
|
|
.on_exit = plugin_protopirate_scene_about_on_exit,
|
|
.set_host_api = about_plugin_set_host_api,
|
|
};
|
|
|
|
static const FlipperAppPluginDescriptor protopirate_about_plugin_descriptor = {
|
|
.appid = PROTOPIRATE_ABOUT_PLUGIN_APP_ID,
|
|
.ep_api_version = PROTOPIRATE_ABOUT_PLUGIN_API_VERSION,
|
|
.entry_point = &protopirate_about_plugin,
|
|
};
|
|
|
|
const FlipperAppPluginDescriptor* protopirate_about_plugin_ep(void) {
|
|
return &protopirate_about_plugin_descriptor;
|
|
}
|