diff --git a/applications/external/jetpack_joyride/application.fam b/applications/external/jetpack_joyride/application.fam new file mode 100644 index 0000000000..1b98e11ce8 --- /dev/null +++ b/applications/external/jetpack_joyride/application.fam @@ -0,0 +1,15 @@ +# For details & more options, see documentation/AppManifests.md in firmware repo + +App( + appid="jetpack_joyride", + name="Jetpack Joyride", + apptype=FlipperAppType.EXTERNAL, + entry_point="jetpack_game_app", + cdefines=["APP_JETPACK_GAME"], + requires=["gui"], + stack_size=4 * 1024, + order=100, + fap_icon="icon.png", + fap_category="Games", + fap_icon_assets="assets", +) diff --git a/applications/external/jetpack_joyride/assets/air_vent.png b/applications/external/jetpack_joyride/assets/air_vent.png new file mode 100644 index 0000000000..a7fcf0b203 Binary files /dev/null and b/applications/external/jetpack_joyride/assets/air_vent.png differ diff --git a/applications/external/jetpack_joyride/assets/alert/frame_01.png b/applications/external/jetpack_joyride/assets/alert/frame_01.png new file mode 100644 index 0000000000..ac4cca1b1f Binary files /dev/null and b/applications/external/jetpack_joyride/assets/alert/frame_01.png differ diff --git a/applications/external/jetpack_joyride/assets/alert/frame_02.png b/applications/external/jetpack_joyride/assets/alert/frame_02.png new file mode 100644 index 0000000000..c3955f090f Binary files /dev/null and b/applications/external/jetpack_joyride/assets/alert/frame_02.png differ diff --git a/applications/external/jetpack_joyride/assets/alert/frame_rate b/applications/external/jetpack_joyride/assets/alert/frame_rate new file mode 100644 index 0000000000..e440e5c842 --- /dev/null +++ b/applications/external/jetpack_joyride/assets/alert/frame_rate @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/applications/external/jetpack_joyride/assets/barry/frame_01.png b/applications/external/jetpack_joyride/assets/barry/frame_01.png new file mode 100644 index 0000000000..8abdcaf615 Binary files /dev/null and b/applications/external/jetpack_joyride/assets/barry/frame_01.png differ diff --git a/applications/external/jetpack_joyride/assets/barry/frame_02.png b/applications/external/jetpack_joyride/assets/barry/frame_02.png new file mode 100644 index 0000000000..5a4587ac8f Binary files /dev/null and b/applications/external/jetpack_joyride/assets/barry/frame_02.png differ diff --git a/applications/external/jetpack_joyride/assets/barry/frame_03.png b/applications/external/jetpack_joyride/assets/barry/frame_03.png new file mode 100644 index 0000000000..d188aed3c6 Binary files /dev/null and b/applications/external/jetpack_joyride/assets/barry/frame_03.png differ diff --git a/applications/external/jetpack_joyride/assets/barry/frame_rate b/applications/external/jetpack_joyride/assets/barry/frame_rate new file mode 100644 index 0000000000..e440e5c842 --- /dev/null +++ b/applications/external/jetpack_joyride/assets/barry/frame_rate @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/applications/external/jetpack_joyride/assets/barry_infill.png b/applications/external/jetpack_joyride/assets/barry_infill.png new file mode 100644 index 0000000000..9462801f02 Binary files /dev/null and b/applications/external/jetpack_joyride/assets/barry_infill.png differ diff --git a/applications/external/jetpack_joyride/assets/bg1.png b/applications/external/jetpack_joyride/assets/bg1.png new file mode 100644 index 0000000000..82d614e1cc Binary files /dev/null and b/applications/external/jetpack_joyride/assets/bg1.png differ diff --git a/applications/external/jetpack_joyride/assets/bg2.png b/applications/external/jetpack_joyride/assets/bg2.png new file mode 100644 index 0000000000..ec8590d3ad Binary files /dev/null and b/applications/external/jetpack_joyride/assets/bg2.png differ diff --git a/applications/external/jetpack_joyride/assets/bg3.png b/applications/external/jetpack_joyride/assets/bg3.png new file mode 100644 index 0000000000..ebb1dd66ba Binary files /dev/null and b/applications/external/jetpack_joyride/assets/bg3.png differ diff --git a/applications/external/jetpack_joyride/assets/coin.png b/applications/external/jetpack_joyride/assets/coin.png new file mode 100644 index 0000000000..a2b5a409e4 Binary files /dev/null and b/applications/external/jetpack_joyride/assets/coin.png differ diff --git a/applications/external/jetpack_joyride/assets/coin_infill.png b/applications/external/jetpack_joyride/assets/coin_infill.png new file mode 100644 index 0000000000..ab37874ff8 Binary files /dev/null and b/applications/external/jetpack_joyride/assets/coin_infill.png differ diff --git a/applications/external/jetpack_joyride/assets/dead_scientist.png b/applications/external/jetpack_joyride/assets/dead_scientist.png new file mode 100644 index 0000000000..cd7a9993a3 Binary files /dev/null and b/applications/external/jetpack_joyride/assets/dead_scientist.png differ diff --git a/applications/external/jetpack_joyride/assets/dead_scientist_infill.png b/applications/external/jetpack_joyride/assets/dead_scientist_infill.png new file mode 100644 index 0000000000..6f036fde2f Binary files /dev/null and b/applications/external/jetpack_joyride/assets/dead_scientist_infill.png differ diff --git a/applications/external/jetpack_joyride/assets/door.png b/applications/external/jetpack_joyride/assets/door.png new file mode 100644 index 0000000000..1ef8610546 Binary files /dev/null and b/applications/external/jetpack_joyride/assets/door.png differ diff --git a/applications/external/jetpack_joyride/assets/missile/frame_01.png b/applications/external/jetpack_joyride/assets/missile/frame_01.png new file mode 100644 index 0000000000..f0b62ed030 Binary files /dev/null and b/applications/external/jetpack_joyride/assets/missile/frame_01.png differ diff --git a/applications/external/jetpack_joyride/assets/missile/frame_rate b/applications/external/jetpack_joyride/assets/missile/frame_rate new file mode 100644 index 0000000000..e440e5c842 --- /dev/null +++ b/applications/external/jetpack_joyride/assets/missile/frame_rate @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/applications/external/jetpack_joyride/assets/missile_infill.png b/applications/external/jetpack_joyride/assets/missile_infill.png new file mode 100644 index 0000000000..d15f5a88eb Binary files /dev/null and b/applications/external/jetpack_joyride/assets/missile_infill.png differ diff --git a/applications/external/jetpack_joyride/assets/pillar.png b/applications/external/jetpack_joyride/assets/pillar.png new file mode 100644 index 0000000000..61979b393a Binary files /dev/null and b/applications/external/jetpack_joyride/assets/pillar.png differ diff --git a/applications/external/jetpack_joyride/assets/scientist_left.png b/applications/external/jetpack_joyride/assets/scientist_left.png new file mode 100644 index 0000000000..a9e880b6b2 Binary files /dev/null and b/applications/external/jetpack_joyride/assets/scientist_left.png differ diff --git a/applications/external/jetpack_joyride/assets/scientist_left_infill.png b/applications/external/jetpack_joyride/assets/scientist_left_infill.png new file mode 100644 index 0000000000..4639cb957a Binary files /dev/null and b/applications/external/jetpack_joyride/assets/scientist_left_infill.png differ diff --git a/applications/external/jetpack_joyride/assets/scientist_right.png b/applications/external/jetpack_joyride/assets/scientist_right.png new file mode 100644 index 0000000000..dc40b560de Binary files /dev/null and b/applications/external/jetpack_joyride/assets/scientist_right.png differ diff --git a/applications/external/jetpack_joyride/assets/scientist_right_infill.png b/applications/external/jetpack_joyride/assets/scientist_right_infill.png new file mode 100644 index 0000000000..e4bc7def88 Binary files /dev/null and b/applications/external/jetpack_joyride/assets/scientist_right_infill.png differ diff --git a/applications/external/jetpack_joyride/icon.png b/applications/external/jetpack_joyride/icon.png new file mode 100644 index 0000000000..0d2d6cf42a Binary files /dev/null and b/applications/external/jetpack_joyride/icon.png differ diff --git a/applications/external/jetpack_joyride/includes/background_asset.c b/applications/external/jetpack_joyride/includes/background_asset.c new file mode 100644 index 0000000000..0a5ecdda91 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/background_asset.c @@ -0,0 +1,81 @@ +#include + +#include "background_assets.h" + +static AssetProperties assetProperties[BG_ASSETS_MAX] = { + {.width = 27, .spawn_chance = 1, .x_offset = 24, .y_offset = 36, .sprite = &I_door}, + {.width = 12, .spawn_chance = 6, .x_offset = 33, .y_offset = 14, .sprite = &I_air_vent}}; + +void background_assets_tick(BackgroundAsset* const assets) { + // Move assets towards the player + for(int i = 0; i < BG_ASSETS_MAX; i++) { + if(assets[i].visible) { + assets[i].point.x -= 1; // move left by 2 units + if(assets[i].point.x <= + -assets[i].properties->width) { // if the asset is out of screen + assets[i].visible = false; // set asset x coordinate to 0 to mark it as "inactive" + } + } + } +} + +void spawn_random_background_asset(BackgroundAsset* const assets) { + // Calculate the total spawn chances for all assets + int total_spawn_chance = 0; + for(int i = 0; i < BG_ASSETS_MAX; ++i) { + total_spawn_chance += assetProperties[i].spawn_chance; + } + + // Generate a random number between 0 and total_spawn_chance + int random_number = rand() % total_spawn_chance; + + // Select the asset based on the random number + int chosen_asset = -1; + int accumulated_chance = 0; + for(int i = 0; i < BG_ASSETS_MAX; ++i) { + accumulated_chance += assetProperties[i].spawn_chance; + if(random_number < accumulated_chance) { + chosen_asset = i; + break; + } + } + + // If no asset is chosen, return + if(chosen_asset == -1) { + return; + } + + // Look for an available slot for the chosen asset + for(int i = 0; i < BG_ASSETS_MAX; ++i) { + if(assets[i].visible == false) { + // Spawn the asset + assets[i].point.x = 127 + assetProperties[chosen_asset].x_offset; + assets[i].point.y = assetProperties[chosen_asset].y_offset; + assets[i].properties = &assetProperties[chosen_asset]; + assets[i].visible = true; + break; + } + } +} + +void draw_background_assets(const BackgroundAsset* assets, Canvas* const canvas, int distance) { + canvas_draw_box(canvas, 0, 6, 128, 1); + canvas_draw_box(canvas, 0, 56, 128, 2); + + // Calculate the pillar offset based on the traveled distance + int pillar_offset = distance % 64; + + // Draw pillars + for(int x = -pillar_offset; x < 128; x += 64) { + canvas_draw_icon(canvas, x, 6, &I_pillar); + } + + // Draw assets + for(int i = 0; i < BG_ASSETS_MAX; ++i) { + if(assets[i].visible) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon( + canvas, assets[i].point.x, assets[i].point.y, assets[i].properties->sprite); + } + } +} diff --git a/applications/external/jetpack_joyride/includes/background_assets.h b/applications/external/jetpack_joyride/includes/background_assets.h new file mode 100644 index 0000000000..d42fcfd711 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/background_assets.h @@ -0,0 +1,34 @@ +#ifndef BACKGROUND_ASSETS_H +#define BACKGROUND_ASSETS_H + +#include +#include + +#include + +#include "point.h" +#include "states.h" +#include "game_sprites.h" +#include + +#define BG_ASSETS_MAX 3 + +typedef struct { + int width; + int spawn_chance; + int x_offset; + int y_offset; + const Icon* sprite; +} AssetProperties; + +typedef struct { + POINT point; + AssetProperties* properties; + bool visible; +} BackgroundAsset; + +void background_assets_tick(BackgroundAsset* const assets); +void spawn_random_background_asset(BackgroundAsset* const assets); +void draw_background_assets(const BackgroundAsset* assets, Canvas* const canvas, int distance); + +#endif // BACKGROUND_ASSETS_H diff --git a/applications/external/jetpack_joyride/includes/barry.c b/applications/external/jetpack_joyride/includes/barry.c new file mode 100644 index 0000000000..61d3a6fc4d --- /dev/null +++ b/applications/external/jetpack_joyride/includes/barry.c @@ -0,0 +1,33 @@ +#include "barry.h" +#include "game_sprites.h" + +#include +#include + +void barry_tick(BARRY* const barry) { + // Do jetpack things + if(barry->isBoosting) { + barry->gravity += GRAVITY_BOOST; // Increase upward momentum + } else { + barry->gravity += GRAVITY_FALL; // Increase downward momentum faster + } + + barry->point.y += barry->gravity; + + // Constrain barry's height within sprite_height and 64 - sprite_height + if(barry->point.y > (64 - BARRY_HEIGHT)) { + barry->point.y = 64 - BARRY_HEIGHT; + barry->gravity = 0; // stop upward momentum + } else if(barry->point.y < 0) { + barry->point.y = 0; + barry->gravity = 0; // stop downward momentum + } +} + +void draw_barry(const BARRY* barry, Canvas* const canvas, const GameSprites* sprites) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon_animation(canvas, barry->point.x, barry->point.y, sprites->barry); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon(canvas, barry->point.x, barry->point.y, sprites->barry_infill); +} \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/barry.h b/applications/external/jetpack_joyride/includes/barry.h new file mode 100644 index 0000000000..494af434d4 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/barry.h @@ -0,0 +1,23 @@ +#ifndef BARRY_H +#define BARRY_H + +#include + +#include +#include "point.h" +#include "game_sprites.h" + +#define GRAVITY_TICK 0.2 +#define GRAVITY_BOOST -0.4 +#define GRAVITY_FALL 0.3 + +typedef struct { + float gravity; + POINT point; + bool isBoosting; +} BARRY; + +void barry_tick(BARRY* const barry); +void draw_barry(const BARRY* barry, Canvas* const canvas, const GameSprites* sprites); + +#endif // BARRY_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/coin.c b/applications/external/jetpack_joyride/includes/coin.c new file mode 100644 index 0000000000..7a3811a8c9 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/coin.c @@ -0,0 +1,98 @@ +#include +#include + +#include +#include + +#include "coin.h" +#include "barry.h" + +#define PATTERN_MAX_HEIGHT 40 + +// Patterns +const COIN_PATTERN coin_patterns[] = { + {// Square pattern + .count = 9, + .coins = {{0, 0}, {8, 0}, {16, 0}, {0, 8}, {8, 8}, {16, 8}, {0, 16}, {8, 16}, {16, 16}}}, + {// Wavy pattern (approximate sine wave) + .count = 8, + .coins = {{0, 8}, {8, 16}, {16, 24}, {24, 16}, {32, 8}, {40, 0}, {48, 8}, {56, 16}}}, + {// Diagonal pattern + .count = 5, + .coins = {{0, 0}, {8, 8}, {16, 16}, {24, 24}, {32, 32}}}, + // Add more patterns here +}; + +void coin_tick(COIN* const coins, BARRY* const barry, int* const total_coins) { + // Move coins towards the player + for(int i = 0; i < COINS_MAX; i++) { + if(coin_colides(&coins[i], barry)) { + coins[i].point.x = 0; // Remove the coin + (*total_coins)++; + } + if(coins[i].point.x > 0) { + coins[i].point.x -= 1; // move left by 1 unit + if(coins[i].point.x < -COIN_WIDTH) { // if the coin is out of screen + coins[i].point.x = 0; // set coin x coordinate to 0 to mark it as "inactive" + } + } + } +} + +bool coin_colides(COIN* const coin, BARRY* const barry) { + return !( + barry->point.x > coin->point.x + COIN_WIDTH || // Barry is to the right of the coin + barry->point.x + BARRY_WIDTH < coin->point.x || // Barry is to the left of the coin + barry->point.y > coin->point.y + COIN_WIDTH || // Barry is below the coin + barry->point.y + BARRY_HEIGHT < coin->point.y); // Barry is above the coin +} + +void spawn_random_coin(COIN* const coins) { + // Select a random pattern + int pattern_index = rand() % (sizeof(coin_patterns) / sizeof(coin_patterns[0])); + const COIN_PATTERN* pattern = &coin_patterns[pattern_index]; + + // Count available slots for new coins + int available_slots = 0; + for(int i = 0; i < COINS_MAX; ++i) { + if(coins[i].point.x <= 0) { + ++available_slots; + } + } + + // If there aren't enough slots, return without spawning coins + if(available_slots < pattern->count) return; + + // Spawn coins according to the selected pattern + int coin_index = 0; + int random_offset = rand() % (SCREEN_HEIGHT - PATTERN_MAX_HEIGHT); + int random_offset_x = rand() % 16; + for(int i = 0; i < pattern->count; ++i) { + // Find an available slot for a new coin + while(coins[coin_index].point.x > 0 && coin_index < COINS_MAX) { + ++coin_index; + } + // If no slot is available, stop spawning coins + if(coin_index == COINS_MAX) break; + + // Spawn the coin + coins[coin_index].point.x = SCREEN_WIDTH - 1 + pattern->coins[i].x + random_offset_x; + coins[coin_index].point.y = + random_offset + + pattern->coins[i] + .y; // The pattern is spawned at a random y position, but not too close to the screen edge + } +} + +void draw_coins(const COIN* coins, Canvas* const canvas, const GameSprites* sprites) { + canvas_set_color(canvas, ColorBlack); + for(int i = 0; i < COINS_MAX; ++i) { + if(coins[i].point.x > 0) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon(canvas, coins[i].point.x, coins[i].point.y, sprites->coin); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon(canvas, coins[i].point.x, coins[i].point.y, sprites->coin_infill); + } + } +} diff --git a/applications/external/jetpack_joyride/includes/coin.h b/applications/external/jetpack_joyride/includes/coin.h new file mode 100644 index 0000000000..41fd21ddc1 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/coin.h @@ -0,0 +1,26 @@ +#ifndef COIN_H +#define COIN_H + +#include + +#include "point.h" +#include "barry.h" + +#define COINS_MAX 15 + +typedef struct { + float gravity; + POINT point; +} COIN; + +typedef struct { + int count; + POINT coins[COINS_MAX]; +} COIN_PATTERN; + +void coin_tick(COIN* const coins, BARRY* const barry, int* const poins); +void spawn_random_coin(COIN* const coins); +bool coin_colides(COIN* const coin, BARRY* const barry); +void draw_coins(const COIN* coins, Canvas* const canvas, const GameSprites* sprites); + +#endif // COIN_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/game_sprites.h b/applications/external/jetpack_joyride/includes/game_sprites.h new file mode 100644 index 0000000000..d38494bf86 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/game_sprites.h @@ -0,0 +1,35 @@ +#ifndef GAME_SPRITES_H +#define GAME_SPRITES_H + +#include "point.h" +#include + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 + +#define BARRY_WIDTH 11 +#define BARRY_HEIGHT 15 + +#define MISSILE_WIDTH 26 +#define MISSILE_HEIGHT 12 + +#define SCIENTIST_WIDTH 9 +#define SCIENTIST_HEIGHT 14 + +#define COIN_WIDTH 7 + +typedef struct { + IconAnimation* barry; + const Icon* barry_infill; + const Icon* scientist_left; + const Icon* scientist_left_infill; + const Icon* scientist_right; + const Icon* scientist_right_infill; + const Icon* coin; + const Icon* coin_infill; + IconAnimation* missile; + IconAnimation* alert; + const Icon* missile_infill; +} GameSprites; + +#endif // GAME_SPRITES_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/game_state.c b/applications/external/jetpack_joyride/includes/game_state.c new file mode 100644 index 0000000000..a8a9db6185 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/game_state.c @@ -0,0 +1,5 @@ +#include "game_state.h" + +void game_state_tick(GameState* const game_state) { + game_state->distance++; +} diff --git a/applications/external/jetpack_joyride/includes/game_state.h b/applications/external/jetpack_joyride/includes/game_state.h new file mode 100644 index 0000000000..1e97aaf180 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/game_state.h @@ -0,0 +1,34 @@ +#ifndef GAMESTATE_H +#define GAMESTATE_H + +#include +#include + +#include "barry.h" +#include "scientist.h" +#include "coin.h" +#include "particle.h" +#include "game_sprites.h" +#include "states.h" +#include "missile.h" +#include "background_assets.h" +typedef struct { + int total_coins; + int distance; + bool new_highscore; + BARRY barry; + COIN coins[COINS_MAX]; + PARTICLE particles[PARTICLES_MAX]; + SCIENTIST scientists[SCIENTISTS_MAX]; + MISSILE missiles[MISSILES_MAX]; + BackgroundAsset bg_assets[BG_ASSETS_MAX]; + State state; + GameSprites sprites; + FuriMutex* mutex; + FuriTimer* timer; + void (*death_handler)(); +} GameState; + +void game_state_tick(GameState* const game_state); + +#endif // GAMESTATE_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/missile.c b/applications/external/jetpack_joyride/includes/missile.c new file mode 100644 index 0000000000..af47e8478d --- /dev/null +++ b/applications/external/jetpack_joyride/includes/missile.c @@ -0,0 +1,86 @@ +#include +#include + +#include +#include + +#include "states.h" +#include "game_sprites.h" +#include "missile.h" +#include "barry.h" + +void missile_tick(MISSILE* const missiles, BARRY* const barry, void (*death_handler)()) { + // Move missiles towards the player + for(int i = 0; i < MISSILES_MAX; i++) { + if(missiles[i].visible && missile_colides(&missiles[i], barry)) { + death_handler(); + } + if(missiles[i].visible) { + missiles[i].point.x -= 2; // move left by 2 units + if(missiles[i].point.x < -MISSILE_WIDTH) { // if the missile is out of screen + missiles[i].visible = false; // set missile as "inactive" + } + } + } +} + +void spawn_random_missile(MISSILE* const missiles) { + // Check for an available slot for a new missile + for(int i = 0; i < MISSILES_MAX; ++i) { + if(!missiles[i].visible) { + missiles[i].point.x = 2 * SCREEN_WIDTH; + missiles[i].point.y = rand() % (SCREEN_HEIGHT - MISSILE_HEIGHT); + missiles[i].visible = true; + break; + } + } +} + +void draw_missiles(const MISSILE* missiles, Canvas* const canvas, const GameSprites* sprites) { + for(int i = 0; i < MISSILES_MAX; ++i) { + if(missiles[i].visible) { + canvas_set_color(canvas, ColorBlack); + + if(missiles[i].point.x > 128) { + canvas_draw_icon_animation( + canvas, SCREEN_WIDTH - 7, missiles[i].point.y, sprites->alert); + } else { + canvas_draw_icon_animation( + canvas, missiles[i].point.x, missiles[i].point.y, sprites->missile); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon( + canvas, missiles[i].point.x, missiles[i].point.y, sprites->missile_infill); + } + } + } +} + +bool missile_colides(MISSILE* const missile, BARRY* const barry) { + return !( + barry->point.x > + missile->point.x + MISSILE_WIDTH - 14 || // Barry is to the right of the missile + barry->point.x + BARRY_WIDTH - 3 < + missile->point.x || // Barry is to the left of the missile + barry->point.y > missile->point.y + MISSILE_HEIGHT || // Barry is below the missile + barry->point.y + BARRY_HEIGHT < missile->point.y); // Barry is above the missile +} + +int get_rocket_spawn_distance(int player_distance) { + // Define the start and end points for rocket spawn distance + int start_distance = 256; + int end_distance = 24; + + // Define the maximum player distance at which the spawn distance should be at its minimum + int max_player_distance = 5000; // Adjust this value based on your game's difficulty curve + + if(player_distance >= max_player_distance) { + return end_distance; + } + + // Calculate the linear interpolation factor + float t = (float)player_distance / max_player_distance; + + // Interpolate the rocket spawn distance + return start_distance + t * (end_distance - start_distance); +} diff --git a/applications/external/jetpack_joyride/includes/missile.h b/applications/external/jetpack_joyride/includes/missile.h new file mode 100644 index 0000000000..a5af4e885d --- /dev/null +++ b/applications/external/jetpack_joyride/includes/missile.h @@ -0,0 +1,24 @@ +#ifndef MISSILE_H +#define MISSILE_H + +#include +#include "game_sprites.h" + +#include "states.h" +#include "point.h" +#include "barry.h" + +#define MISSILES_MAX 5 + +typedef struct { + POINT point; + bool visible; +} MISSILE; + +void missile_tick(MISSILE* const missiles, BARRY* const barry, void (*death_handler)()); +void spawn_random_missile(MISSILE* const MISSILEs); +bool missile_colides(MISSILE* const MISSILE, BARRY* const barry); +int get_rocket_spawn_distance(int player_distance); +void draw_missiles(const MISSILE* missiles, Canvas* const canvas, const GameSprites* sprites); + +#endif // MISSILE_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/particle.c b/applications/external/jetpack_joyride/includes/particle.c new file mode 100644 index 0000000000..cf8e6e0a60 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/particle.c @@ -0,0 +1,57 @@ +#include + +#include "particle.h" +#include "scientist.h" +#include "barry.h" + +void particle_tick(PARTICLE* const particles, SCIENTIST* const scientists) { + // Move particles + for(int i = 0; i < PARTICLES_MAX; i++) { + if(particles[i].point.y > 0) { + particles[i].point.y += PARTICLE_VELOCITY; + + // Check collision with scientists + for(int j = 0; j < SCIENTISTS_MAX; j++) { + if(scientists[j].state == ScientistStateAlive && scientists[j].point.x > 0) { + // Check whether the particle lies within the scientist's bounding box + if(!(particles[i].point.x > scientists[j].point.x + SCIENTIST_WIDTH || + particles[i].point.x < scientists[j].point.x || + particles[i].point.y > scientists[j].point.y + SCIENTIST_HEIGHT || + particles[i].point.y < scientists[j].point.y)) { + scientists[j].state = ScientistStateDead; + // (*points) += 2; // Increase the score by 2 + } + } + } + + if(particles[i].point.x < 0 || particles[i].point.x > SCREEN_WIDTH || + particles[i].point.y < 0 || particles[i].point.y > SCREEN_HEIGHT) { + particles[i].point.y = 0; + } + } + } +} + +void spawn_random_particles(PARTICLE* const particles, BARRY* const barry) { + for(int i = 0; i < PARTICLES_MAX; i++) { + if(particles[i].point.y <= 0) { + particles[i].point.x = barry->point.x + (rand() % 4); + particles[i].point.y = barry->point.y + 14; + break; + } + } +} + +void draw_particles(const PARTICLE* particles, Canvas* const canvas) { + canvas_set_color(canvas, ColorBlack); + for(int i = 0; i < PARTICLES_MAX; i++) { + if(particles[i].point.y > 0) { + canvas_draw_line( + canvas, + particles[i].point.x, + particles[i].point.y, + particles[i].point.x, + particles[i].point.y + 3); + } + } +} diff --git a/applications/external/jetpack_joyride/includes/particle.h b/applications/external/jetpack_joyride/includes/particle.h new file mode 100644 index 0000000000..3442c9c4ec --- /dev/null +++ b/applications/external/jetpack_joyride/includes/particle.h @@ -0,0 +1,21 @@ + + +#ifndef PARTICLE_H +#define PARTICLE_H + +#include "point.h" +#include "scientist.h" +#include "barry.h" + +#define PARTICLES_MAX 50 +#define PARTICLE_VELOCITY 2 + +typedef struct { + POINT point; +} PARTICLE; + +void particle_tick(PARTICLE* const particles, SCIENTIST* const scientists); +void spawn_random_particles(PARTICLE* const particles, BARRY* const barry); +void draw_particles(const PARTICLE* particles, Canvas* const canvas); + +#endif // PARTICLE_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/point.h b/applications/external/jetpack_joyride/includes/point.h new file mode 100644 index 0000000000..02c9a6ce4a --- /dev/null +++ b/applications/external/jetpack_joyride/includes/point.h @@ -0,0 +1,14 @@ +#ifndef POINT_H +#define POINT_H + +typedef struct { + int x; + int y; +} POINT; + +typedef struct { + float x; + float y; +} POINTF; + +#endif // POINT_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/scientist.c b/applications/external/jetpack_joyride/includes/scientist.c new file mode 100644 index 0000000000..b1a8a14c03 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/scientist.c @@ -0,0 +1,77 @@ +#include "scientist.h" +#include "game_sprites.h" + +#include +#include + +void scientist_tick(SCIENTIST* const scientists) { + for(int i = 0; i < SCIENTISTS_MAX; i++) { + if(scientists[i].visible) { + if(scientists[i].point.x < 64) scientists[i].velocity_x = 0.5f; + + scientists[i].point.x -= scientists[i].state == ScientistStateAlive ? + 1 - scientists[i].velocity_x : + 1; // move based on velocity_x + int width = (scientists[i].state == ScientistStateAlive) ? SCIENTIST_WIDTH : + SCIENTIST_HEIGHT; + if(scientists[i].point.x <= -width) { // if the scientist is out of screen + scientists[i].visible = false; + } + } + } +} + +void spawn_random_scientist(SCIENTIST* const scientists) { + float velocities[] = {-0.5f, 0.0f, 0.5f, -1.0f}; + // Check for an available slot for a new scientist + for(int i = 0; i < SCIENTISTS_MAX; ++i) { + if(!scientists[i].visible && + (rand() % 1000) < 10) { // Spawn rate is less frequent than coins + scientists[i].state = ScientistStateAlive; + scientists[i].point.x = 127; + scientists[i].point.y = 49; + scientists[i].velocity_x = velocities[rand() % 4]; + scientists[i].visible = true; + break; + } + } +} + +void draw_scientists(const SCIENTIST* scientists, Canvas* const canvas, const GameSprites* sprites) { + for(int i = 0; i < SCIENTISTS_MAX; ++i) { + if(scientists[i].visible) { + canvas_set_color(canvas, ColorBlack); + if(scientists[i].state == ScientistStateAlive) { + canvas_draw_icon( + canvas, + (int)scientists[i].point.x, + scientists[i].point.y, + scientists[i].velocity_x >= 0 ? sprites->scientist_right : + sprites->scientist_left); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon( + canvas, + (int)scientists[i].point.x, + scientists[i].point.y, + scientists[i].velocity_x >= 0 ? sprites->scientist_right_infill : + sprites->scientist_left_infill); + + } else { + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon( + canvas, + (int)scientists[i].point.x, + scientists[i].point.y + 5, + &I_dead_scientist); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_icon( + canvas, + (int)scientists[i].point.x, + scientists[i].point.y + 5, + &I_dead_scientist_infill); + } + } + } +} \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/scientist.h b/applications/external/jetpack_joyride/includes/scientist.h new file mode 100644 index 0000000000..a49e8028c7 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/scientist.h @@ -0,0 +1,29 @@ +#ifndef SCIENTIST_H +#define SCIENTIST_H + +#include "point.h" +#include "game_sprites.h" +#include + +#define SCIENTIST_VELOCITY_MIN -0.5f +#define SCIENTIST_VELOCITY_MAX 0.5f + +#define SCIENTISTS_MAX 6 + +typedef enum { + ScientistStateAlive, + ScientistStateDead, +} ScientistState; + +typedef struct { + bool visible; + POINTF point; + float velocity_x; + ScientistState state; +} SCIENTIST; + +void scientist_tick(SCIENTIST* const scientist); +void spawn_random_scientist(SCIENTIST* const scientists); +void draw_scientists(const SCIENTIST* scientists, Canvas* const canvas, const GameSprites* sprites); + +#endif // SCIENTIST_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/includes/states.h b/applications/external/jetpack_joyride/includes/states.h new file mode 100644 index 0000000000..d58e3e1f66 --- /dev/null +++ b/applications/external/jetpack_joyride/includes/states.h @@ -0,0 +1,9 @@ +#ifndef STATE_H +#define STATE_H + +typedef enum { + GameStateLife, + GameStateGameOver, +} State; + +#endif // STATE_H \ No newline at end of file diff --git a/applications/external/jetpack_joyride/jetpack.c b/applications/external/jetpack_joyride/jetpack.c new file mode 100644 index 0000000000..c12f094c94 --- /dev/null +++ b/applications/external/jetpack_joyride/jetpack.c @@ -0,0 +1,379 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "includes/point.h" +#include "includes/barry.h" +#include "includes/scientist.h" +#include "includes/particle.h" +#include "includes/coin.h" +#include "includes/missile.h" +#include "includes/background_assets.h" + +#include "includes/game_state.h" + +#define TAG "Jetpack Joyride" +#define SAVING_DIRECTORY "/ext/apps/Games" +#define SAVING_FILENAME SAVING_DIRECTORY "/jetpack.save" +static GameState* global_state; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} GameEvent; + +typedef struct { + int max_distance; + int total_coins; +} SaveGame; + +static SaveGame save_game; + +static bool storage_game_state_load() { + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + uint16_t bytes_readed = 0; + if(storage_file_open(file, SAVING_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) + bytes_readed = storage_file_read(file, &save_game, sizeof(SaveGame)); + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + return bytes_readed == sizeof(SaveGame); +} + +static void storage_game_state_save() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + if(storage_common_stat(storage, SAVING_DIRECTORY, NULL) == FSE_NOT_EXIST) { + if(!storage_simply_mkdir(storage, SAVING_DIRECTORY)) { + return; + } + } + + File* file = storage_file_alloc(storage); + if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + storage_file_write(file, &save_game, sizeof(SaveGame)); + } + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); +} + +void handle_death() { + global_state->state = GameStateGameOver; + global_state->new_highscore = global_state->distance > save_game.max_distance; + + if(global_state->distance > save_game.max_distance) { + save_game.max_distance = global_state->distance; + } + + save_game.total_coins += global_state->total_coins; + + storage_game_state_save(); +} + +static void jetpack_game_state_init(GameState* const game_state) { + UNUSED(game_state); + UNUSED(storage_game_state_save); + BARRY barry; + barry.gravity = 0; + barry.point.x = 32 + 5; + barry.point.y = 32; + barry.isBoosting = false; + + GameSprites sprites; + sprites.barry = icon_animation_alloc(&A_barry); + sprites.barry_infill = &I_barry_infill; + + sprites.scientist_left = (&I_scientist_left); + sprites.scientist_left_infill = (&I_scientist_left_infill); + sprites.scientist_right = (&I_scientist_right); + sprites.scientist_right_infill = (&I_scientist_right_infill); + + sprites.coin = (&I_coin); + sprites.coin_infill = (&I_coin_infill); + + sprites.missile = icon_animation_alloc(&A_missile); + sprites.missile_infill = &I_missile_infill; + + sprites.alert = icon_animation_alloc(&A_alert); + + icon_animation_start(sprites.barry); + icon_animation_start(sprites.missile); + icon_animation_start(sprites.alert); + + game_state->barry = barry; + game_state->total_coins = 0; + game_state->distance = 0; + game_state->new_highscore = false; + game_state->sprites = sprites; + game_state->state = GameStateLife; + game_state->death_handler = handle_death; + + memset(game_state->bg_assets, 0, sizeof(game_state->bg_assets)); + + memset(game_state->scientists, 0, sizeof(game_state->scientists)); + memset(game_state->coins, 0, sizeof(game_state->coins)); + memset(game_state->particles, 0, sizeof(game_state->particles)); + memset(game_state->missiles, 0, sizeof(game_state->missiles)); +} + +static void jetpack_game_state_free(GameState* const game_state) { + icon_animation_free(game_state->sprites.barry); + icon_animation_free(game_state->sprites.missile); + icon_animation_free(game_state->sprites.alert); + + free(game_state); +} + +static void jetpack_game_tick(GameState* const game_state) { + if(game_state->state == GameStateGameOver) return; + barry_tick(&game_state->barry); + game_state_tick(game_state); + coin_tick(game_state->coins, &game_state->barry, &game_state->total_coins); + particle_tick(game_state->particles, game_state->scientists); + scientist_tick(game_state->scientists); + missile_tick(game_state->missiles, &game_state->barry, game_state->death_handler); + + background_assets_tick(game_state->bg_assets); + + // generate background every 64px aka. ticks + if(game_state->distance % 64 == 0 && rand() % 3 == 0) { + spawn_random_background_asset(game_state->bg_assets); + } + + if(game_state->distance % 48 == 0 && rand() % 2 == 0) { + spawn_random_coin(game_state->coins); + } + + if(game_state->distance % get_rocket_spawn_distance(game_state->distance) == 0 && + rand() % 2 == 0) { + spawn_random_missile(game_state->missiles); + } + + spawn_random_scientist(game_state->scientists); + + if(game_state->barry.isBoosting) { + spawn_random_particles(game_state->particles, &game_state->barry); + } +} + +static void jetpack_game_render_callback(Canvas* const canvas, void* ctx) { + furi_assert(ctx); + const GameState* game_state = ctx; + furi_mutex_acquire(game_state->mutex, FuriWaitForever); + + if(game_state->state == GameStateLife) { + canvas_set_bitmap_mode(canvas, false); + + draw_background_assets(game_state->bg_assets, canvas, game_state->distance); + + canvas_set_bitmap_mode(canvas, true); + + draw_coins(game_state->coins, canvas, &game_state->sprites); + draw_scientists(game_state->scientists, canvas, &game_state->sprites); + draw_particles(game_state->particles, canvas); + draw_missiles(game_state->missiles, canvas, &game_state->sprites); + + draw_barry(&game_state->barry, canvas, &game_state->sprites); + + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontSecondary); + char buffer[12]; + snprintf(buffer, sizeof(buffer), "%u m", game_state->distance / 10); + canvas_draw_str_aligned(canvas, 123, 15, AlignRight, AlignBottom, buffer); + + snprintf(buffer, sizeof(buffer), "$%u", game_state->total_coins); + canvas_draw_str_aligned(canvas, 5, 15, AlignLeft, AlignBottom, buffer); + } + + if(game_state->state == GameStateGameOver) { + // Show highscore + char buffer[64]; + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignTop, "You flew"); + + snprintf( + buffer, + sizeof(buffer), + game_state->new_highscore ? "%u m (new best)" : "%u m", + game_state->distance / 10); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 16, AlignCenter, AlignTop, buffer); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignTop, "and collected"); + + snprintf(buffer, sizeof(buffer), "$%u", game_state->total_coins); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, buffer); + + snprintf( + buffer, + sizeof(buffer), + "Best: %u m, Tot: $%u", + save_game.max_distance / 10, + save_game.total_coins); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 64, 63, AlignCenter, AlignBottom, buffer); + + canvas_draw_rframe(canvas, 0, 3, 128, 49, 5); + + // char buffer[12]; + // snprintf(buffer, sizeof(buffer), "Dist: %u", game_state->distance); + // canvas_draw_str_aligned(canvas, 123, 12, AlignRight, AlignBottom, buffer); + + // snprintf(buffer, sizeof(buffer), "Score: %u", game_state->points); + // canvas_draw_str_aligned(canvas, 5, 12, AlignLeft, AlignBottom, buffer); + + // canvas_draw_str_aligned(canvas, 64, 34, AlignCenter, AlignCenter, "Highscore:"); + // snprintf(buffer, sizeof(buffer), "Dist: %u", save_game.max_distance); + // canvas_draw_str_aligned(canvas, 123, 50, AlignRight, AlignBottom, buffer); + + // snprintf(buffer, sizeof(buffer), "Score: %u", save_game.max_score); + // canvas_draw_str_aligned(canvas, 5, 50, AlignLeft, AlignBottom, buffer); + + // canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, "boom."); + + // if(furi_timer_is_running(game_state->timer)) { + // furi_timer_start(game_state->timer, 0); + // } + } + + // canvas_draw_frame(canvas, 0, 0, 128, 64); + + furi_mutex_release(game_state->mutex); +} + +static void jetpack_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void jetpack_game_update_timer_callback(FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + GameEvent event = {.type = EventTypeTick}; + furi_message_queue_put(event_queue, &event, 0); +} + +int32_t jetpack_game_app(void* p) { + UNUSED(p); + int32_t return_code = 0; + + if(!storage_game_state_load()) { + memset(&save_game, 0, sizeof(save_game)); + } + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent)); + + GameState* game_state = malloc(sizeof(GameState)); + + global_state = game_state; + jetpack_game_state_init(game_state); + + game_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + if(!game_state->mutex) { + FURI_LOG_E(TAG, "cannot create mutex\r\n"); + return_code = 255; + goto free_and_exit; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, jetpack_game_render_callback, game_state); + view_port_input_callback_set(view_port, jetpack_game_input_callback, event_queue); + + FuriTimer* timer = + furi_timer_alloc(jetpack_game_update_timer_callback, FuriTimerTypePeriodic, event_queue); + furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25); + + game_state->timer = timer; + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + GameEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + furi_mutex_acquire(game_state->mutex, FuriWaitForever); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypeRelease && event.input.key == InputKeyOk) { + game_state->barry.isBoosting = false; + } + + // Reset highscore, for debug purposes + if(event.input.type == InputTypeLong && event.input.key == InputKeyLeft) { + save_game.max_distance = 0; + save_game.total_coins = 0; + storage_game_state_save(); + } + + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + break; + case InputKeyLeft: + break; + case InputKeyOk: + if(game_state->state == GameStateGameOver) { + jetpack_game_state_init(game_state); + } + + if(game_state->state == GameStateLife) { + // Do something + game_state->barry.isBoosting = true; + } + + break; + case InputKeyBack: + processing = false; + break; + default: + break; + } + } + } else if(event.type == EventTypeTick) { + jetpack_game_tick(game_state); + } + } + + view_port_update(view_port); + furi_mutex_release(game_state->mutex); + } + + furi_timer_free(timer); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_mutex_free(game_state->mutex); + +free_and_exit: + jetpack_game_state_free(game_state); + furi_message_queue_free(event_queue); + + return return_code; +} \ No newline at end of file diff --git a/applications/external/nfc_maker/application.fam b/applications/external/nfc_maker/application.fam new file mode 100644 index 0000000000..89bbb202e3 --- /dev/null +++ b/applications/external/nfc_maker/application.fam @@ -0,0 +1,14 @@ +App( + appid="nfc_maker", + name="NFC Maker", + apptype=FlipperAppType.EXTERNAL, + entry_point="nfc_maker", + cdefines=["APP_NFC_MAKER"], + requires=[ + "storage", + "gui", + ], + stack_size=1 * 1024, + fap_icon="nfc_maker_10px.png", + fap_category="NFC", +) diff --git a/applications/external/nfc_maker/nfc_maker.c b/applications/external/nfc_maker/nfc_maker.c new file mode 100644 index 0000000000..578054adea --- /dev/null +++ b/applications/external/nfc_maker/nfc_maker.c @@ -0,0 +1,81 @@ +#include "nfc_maker.h" + +static bool nfc_maker_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + NfcMaker* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool nfc_maker_back_event_callback(void* context) { + furi_assert(context); + NfcMaker* app = context; + + return scene_manager_handle_back_event(app->scene_manager); +} + +NfcMaker* nfc_maker_alloc() { + NfcMaker* app = malloc(sizeof(NfcMaker)); + app->gui = furi_record_open(RECORD_GUI); + + // View Dispatcher and Scene Manager + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&nfc_maker_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, nfc_maker_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, nfc_maker_back_event_callback); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + // Gui Modules + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, NfcMakerViewSubmenu, submenu_get_view(app->submenu)); + + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, NfcMakerViewTextInput, text_input_get_view(app->text_input)); + + app->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, NfcMakerViewByteInput, byte_input_get_view(app->byte_input)); + + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, NfcMakerViewPopup, popup_get_view(app->popup)); + + return app; +} + +void nfc_maker_free(NfcMaker* app) { + furi_assert(app); + + // Gui modules + view_dispatcher_remove_view(app->view_dispatcher, NfcMakerViewSubmenu); + submenu_free(app->submenu); + view_dispatcher_remove_view(app->view_dispatcher, NfcMakerViewTextInput); + text_input_free(app->text_input); + view_dispatcher_remove_view(app->view_dispatcher, NfcMakerViewByteInput); + byte_input_free(app->byte_input); + view_dispatcher_remove_view(app->view_dispatcher, NfcMakerViewPopup); + popup_free(app->popup); + + // View Dispatcher and Scene Manager + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Records + furi_record_close(RECORD_GUI); + free(app); +} + +extern int32_t nfc_maker(void* p) { + UNUSED(p); + NfcMaker* app = nfc_maker_alloc(); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneMenu); + view_dispatcher_run(app->view_dispatcher); + nfc_maker_free(app); + return 0; +} diff --git a/applications/external/nfc_maker/nfc_maker.h b/applications/external/nfc_maker/nfc_maker.h new file mode 100644 index 0000000000..388dc71963 --- /dev/null +++ b/applications/external/nfc_maker/nfc_maker.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "scenes/nfc_maker_scene.h" +#include +#include +#include +#include + +#define TEXT_INPUT_LEN 248 +#define WIFI_INPUT_LEN 90 + +typedef enum { + WifiAuthenticationOpen = 0x01, + WifiAuthenticationWpa2Personal = 0x20, + WifiAuthenticationWpa2Enterprise = 0x10, + WifiAuthenticationWpaPersonal = 0x02, + WifiAuthenticationWpaEnterprise = 0x08, + WifiAuthenticationShared = 0x04, +} WifiAuthentication; + +typedef enum { + WifiEncryptionAes = 0x08, + WifiEncryptionWep = 0x02, + WifiEncryptionTkip = 0x04, + WifiEncryptionNone = 0x01, +} WifiEncryption; + +typedef struct { + Gui* gui; + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + Submenu* submenu; + TextInput* text_input; + ByteInput* byte_input; + Popup* popup; + + uint8_t mac_buf[GAP_MAC_ADDR_SIZE]; + char text_buf[TEXT_INPUT_LEN]; + char pass_buf[WIFI_INPUT_LEN]; + char name_buf[TEXT_INPUT_LEN]; +} NfcMaker; + +typedef enum { + NfcMakerViewSubmenu, + NfcMakerViewTextInput, + NfcMakerViewByteInput, + NfcMakerViewPopup, +} NfcMakerView; diff --git a/applications/external/nfc_maker/nfc_maker_10px.png b/applications/external/nfc_maker/nfc_maker_10px.png new file mode 100644 index 0000000000..a9e2443f17 Binary files /dev/null and b/applications/external/nfc_maker/nfc_maker_10px.png differ diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene.c b/applications/external/nfc_maker/scenes/nfc_maker_scene.c new file mode 100644 index 0000000000..3a8b7d5025 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene.c @@ -0,0 +1,30 @@ +#include "nfc_maker_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const nfc_maker_on_enter_handlers[])(void*) = { +#include "nfc_maker_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const nfc_maker_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "nfc_maker_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const nfc_maker_on_exit_handlers[])(void* context) = { +#include "nfc_maker_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers nfc_maker_scene_handlers = { + .on_enter_handlers = nfc_maker_on_enter_handlers, + .on_event_handlers = nfc_maker_on_event_handlers, + .on_exit_handlers = nfc_maker_on_exit_handlers, + .scene_num = NfcMakerSceneNum, +}; diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene.h b/applications/external/nfc_maker/scenes/nfc_maker_scene.h new file mode 100644 index 0000000000..5ad69f2330 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) NfcMakerScene##id, +typedef enum { +#include "nfc_maker_scene_config.h" + NfcMakerSceneNum, +} NfcMakerScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers nfc_maker_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "nfc_maker_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "nfc_maker_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "nfc_maker_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c new file mode 100644 index 0000000000..4e70a184df --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_bluetooth.c @@ -0,0 +1,57 @@ +#include "../nfc_maker.h" + +enum ByteInputResult { + ByteInputResultOk, +}; + +static void nfc_maker_scene_bluetooth_byte_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, ByteInputResultOk); +} + +void nfc_maker_scene_bluetooth_on_enter(void* context) { + NfcMaker* app = context; + ByteInput* byte_input = app->byte_input; + + byte_input_set_header_text(byte_input, "Enter Bluetooth MAC:"); + + for(size_t i = 0; i < GAP_MAC_ADDR_SIZE; i++) { + app->mac_buf[i] = 0x69; + } + + byte_input_set_result_callback( + byte_input, + nfc_maker_scene_bluetooth_byte_input_callback, + NULL, + app, + app->mac_buf, + GAP_MAC_ADDR_SIZE); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewByteInput); +} + +bool nfc_maker_scene_bluetooth_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case ByteInputResultOk: + furi_hal_bt_reverse_mac_addr(app->mac_buf); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_bluetooth_on_exit(void* context) { + NfcMaker* app = context; + byte_input_set_result_callback(app->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(app->byte_input, ""); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h b/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h new file mode 100644 index 0000000000..a89b4198c4 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_config.h @@ -0,0 +1,13 @@ +ADD_SCENE(nfc_maker, menu, Menu) +ADD_SCENE(nfc_maker, bluetooth, Bluetooth) +ADD_SCENE(nfc_maker, https, Https) +ADD_SCENE(nfc_maker, mail, Mail) +ADD_SCENE(nfc_maker, phone, Phone) +ADD_SCENE(nfc_maker, text, Text) +ADD_SCENE(nfc_maker, url, Url) +ADD_SCENE(nfc_maker, wifi, Wifi) +ADD_SCENE(nfc_maker, wifi_auth, WifiAuth) +ADD_SCENE(nfc_maker, wifi_encr, WifiEncr) +ADD_SCENE(nfc_maker, wifi_pass, WifiPass) +ADD_SCENE(nfc_maker, name, Name) +ADD_SCENE(nfc_maker, result, Result) diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c new file mode 100644 index 0000000000..9697c0d1c0 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_https.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_https_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_https_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter Https Link:"); + + strlcpy(app->text_buf, "flipper-xtre.me", TEXT_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_https_text_input_callback, + app, + app->text_buf, + TEXT_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_https_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_https_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c new file mode 100644 index 0000000000..5b76f56a26 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_mail.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_mail_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_mail_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter Mail Address:"); + + strlcpy(app->text_buf, "ben.dover@example.com", TEXT_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_mail_text_input_callback, + app, + app->text_buf, + TEXT_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_mail_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_mail_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c new file mode 100644 index 0000000000..4268e7e2f1 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_menu.c @@ -0,0 +1,61 @@ +#include "../nfc_maker.h" + +void nfc_maker_scene_menu_submenu_callback(void* context, uint32_t index) { + NfcMaker* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void nfc_maker_scene_menu_on_enter(void* context) { + NfcMaker* app = context; + Submenu* submenu = app->submenu; + + submenu_set_header(submenu, "NFC Tag Maker:"); + + submenu_add_item( + submenu, + "Bluetooth MAC", + NfcMakerSceneBluetooth, + nfc_maker_scene_menu_submenu_callback, + app); + + submenu_add_item( + submenu, "HTTPS Link", NfcMakerSceneHttps, nfc_maker_scene_menu_submenu_callback, app); + + submenu_add_item( + submenu, "Mail Address", NfcMakerSceneMail, nfc_maker_scene_menu_submenu_callback, app); + + submenu_add_item( + submenu, "Phone Number", NfcMakerScenePhone, nfc_maker_scene_menu_submenu_callback, app); + + submenu_add_item( + submenu, "Text Note", NfcMakerSceneText, nfc_maker_scene_menu_submenu_callback, app); + + submenu_add_item( + submenu, "Plain URL", NfcMakerSceneUrl, nfc_maker_scene_menu_submenu_callback, app); + + submenu_add_item( + submenu, "WiFi Login", NfcMakerSceneWifi, nfc_maker_scene_menu_submenu_callback, app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneMenu)); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewSubmenu); +} + +bool nfc_maker_scene_menu_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, NfcMakerSceneMenu, event.event); + consumed = true; + scene_manager_next_scene(app->scene_manager, event.event); + } + + return consumed; +} + +void nfc_maker_scene_menu_on_exit(void* context) { + NfcMaker* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c new file mode 100644 index 0000000000..dd5170d94a --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_name.c @@ -0,0 +1,57 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_name_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_name_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Name the NFC tag:"); + + set_random_name(app->name_buf, TEXT_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_name_text_input_callback, + app, + app->name_buf, + TEXT_INPUT_LEN, + true); + + ValidatorIsFile* validator_is_file = + validator_is_file_alloc_init(NFC_APP_FOLDER, NFC_APP_EXTENSION, NULL); + text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_name_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneResult); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_name_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c new file mode 100644 index 0000000000..4e70bcb09b --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_phone.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_phone_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_phone_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter Phone Number:"); + + strlcpy(app->text_buf, "+", TEXT_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_phone_text_input_callback, + app, + app->text_buf, + TEXT_INPUT_LEN, + false); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_phone_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_phone_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c new file mode 100644 index 0000000000..912bf3c9fc --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_result.c @@ -0,0 +1,359 @@ +#include "../nfc_maker.h" + +enum PopupEvent { + PopupEventExit, +}; + +static void nfc_maker_scene_result_popup_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, PopupEventExit); +} + +void nfc_maker_scene_result_on_enter(void* context) { + NfcMaker* app = context; + Popup* popup = app->popup; + bool success = false; + + FlipperFormat* file = flipper_format_file_alloc(furi_record_open(RECORD_STORAGE)); + FuriString* path = furi_string_alloc(); + furi_string_printf(path, NFC_APP_FOLDER "/%s" NFC_APP_EXTENSION, app->name_buf); + do { + if(!flipper_format_file_open_new(file, furi_string_get_cstr(path))) break; + + uint32_t pages = 42; + size_t size = pages * 4; + uint8_t* buf = malloc(size); + + if(!flipper_format_write_header_cstr(file, "Flipper NFC device", 3)) break; + if(!flipper_format_write_string_cstr(file, "Device type", "NTAG203")) break; + + // Serial number + buf[0] = 0x04; + furi_hal_random_fill_buf(&buf[1], 8); + uint8_t uid[7]; + memcpy(&uid[0], &buf[0], 3); + memcpy(&uid[3], &buf[4], 4); + + if(!flipper_format_write_hex(file, "UID", uid, sizeof(uid))) break; + if(!flipper_format_write_string_cstr(file, "ATQA", "00 44")) break; + if(!flipper_format_write_string_cstr(file, "SAK", "00")) break; + // TODO: Maybe randomize? + if(!flipper_format_write_string_cstr( + file, + "Signature", + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")) + break; + if(!flipper_format_write_string_cstr(file, "Mifare version", "00 00 00 00 00 00 00 00")) + break; + + if(!flipper_format_write_string_cstr(file, "Counter 0", "0")) break; + if(!flipper_format_write_string_cstr(file, "Tearing 0", "00")) break; + if(!flipper_format_write_string_cstr(file, "Counter 1", "0")) break; + if(!flipper_format_write_string_cstr(file, "Tearing 1", "00")) break; + if(!flipper_format_write_string_cstr(file, "Counter 2", "0")) break; + if(!flipper_format_write_string_cstr(file, "Tearing 2", "00")) break; + if(!flipper_format_write_uint32(file, "Pages total", &pages, 1)) break; + + // Static data + buf[9] = 0x48; // Internal + buf[10] = 0x00; // Lock bytes + buf[11] = 0x00; // ... + buf[12] = 0xE1; // Capability container + buf[13] = 0x10; // ... + buf[14] = 0x12; // ... + buf[15] = 0x00; // ... + buf[16] = 0x01; // ... + buf[17] = 0x03; // ... + buf[18] = 0xA0; // ... + buf[19] = 0x10; // ... + buf[20] = 0x44; // ... + buf[21] = 0x03; // Message flags + + size_t msg_len = 0; + switch(scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneMenu)) { + case NfcMakerSceneBluetooth: { + msg_len = 0x2B; + + buf[23] = 0xD2; + buf[24] = 0x20; + buf[25] = 0x08; + buf[26] = 0x61; + buf[27] = 0x70; + + buf[28] = 0x70; + buf[29] = 0x6C; + buf[30] = 0x69; + buf[31] = 0x63; + + buf[32] = 0x61; + buf[33] = 0x74; + buf[34] = 0x69; + buf[35] = 0x6F; + + buf[36] = 0x6E; + buf[37] = 0x2F; + buf[38] = 0x76; + buf[39] = 0x6E; + + buf[40] = 0x64; + buf[41] = 0x2E; + buf[42] = 0x62; + buf[43] = 0x6C; + + buf[44] = 0x75; + buf[45] = 0x65; + buf[46] = 0x74; + buf[47] = 0x6F; + + buf[48] = 0x6F; + buf[49] = 0x74; + buf[50] = 0x68; + buf[51] = 0x2E; + + buf[52] = 0x65; + buf[53] = 0x70; + buf[54] = 0x2E; + buf[55] = 0x6F; + + buf[56] = 0x6F; + buf[57] = 0x62; + buf[58] = 0x08; + buf[59] = 0x00; + + memcpy(&buf[60], app->mac_buf, GAP_MAC_ADDR_SIZE); + break; + } + case NfcMakerSceneHttps: { + uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + msg_len = data_len + 5; + + buf[23] = 0xD1; + buf[24] = 0x01; + buf[25] = data_len + 1; + buf[26] = 0x55; + + buf[27] = 0x04; // Prepend "https://" + memcpy(&buf[28], app->text_buf, data_len); + break; + } + case NfcMakerSceneMail: { + uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + msg_len = data_len + 5; + + buf[23] = 0xD1; + buf[24] = 0x01; + buf[25] = data_len + 1; + buf[26] = 0x55; + + buf[27] = 0x06; // Prepend "mailto:" + memcpy(&buf[28], app->text_buf, data_len); + break; + } + case NfcMakerScenePhone: { + uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + msg_len = data_len + 5; + + buf[23] = 0xD1; + buf[24] = 0x01; + buf[25] = data_len + 1; + buf[26] = 0x55; + + buf[27] = 0x05; // Prepend "tel:" + memcpy(&buf[28], app->text_buf, data_len); + break; + } + case NfcMakerSceneText: { + uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + msg_len = data_len + 7; + + buf[23] = 0xD1; + buf[24] = 0x01; + buf[25] = data_len + 3; + buf[26] = 0x54; + + buf[27] = 0x02; + buf[28] = 0x65; // e + buf[29] = 0x6E; // n + memcpy(&buf[30], app->text_buf, data_len); + break; + } + case NfcMakerSceneUrl: { + uint8_t data_len = strnlen(app->text_buf, TEXT_INPUT_LEN); + msg_len = data_len + 5; + + buf[23] = 0xD1; + buf[24] = 0x01; + buf[25] = data_len + 1; + buf[26] = 0x55; + + buf[27] = 0x00; // No prepend + memcpy(&buf[28], app->text_buf, data_len); + break; + } + case NfcMakerSceneWifi: { + uint8_t ssid_len = strnlen(app->text_buf, WIFI_INPUT_LEN); + uint8_t pass_len = strnlen(app->pass_buf, WIFI_INPUT_LEN); + uint8_t data_len = ssid_len + pass_len; + msg_len = data_len + 73; + + buf[23] = 0xD2; + buf[24] = 0x17; + buf[25] = data_len + 47; + buf[26] = 0x61; + buf[27] = 0x70; + + buf[28] = 0x70; + buf[29] = 0x6C; + buf[30] = 0x69; + buf[31] = 0x63; + + buf[32] = 0x61; + buf[33] = 0x74; + buf[34] = 0x69; + buf[35] = 0x6F; + + buf[36] = 0x6E; + buf[37] = 0x2F; + buf[38] = 0x76; + buf[39] = 0x6E; + + buf[40] = 0x64; + buf[41] = 0x2E; + buf[42] = 0x77; + buf[43] = 0x66; + + buf[44] = 0x61; + buf[45] = 0x2E; + buf[46] = 0x77; + buf[47] = 0x73; + + buf[48] = 0x63; + buf[49] = 0x10; + buf[50] = 0x0E; + buf[51] = 0x00; + + buf[52] = data_len + 43; + buf[53] = 0x10; + buf[54] = 0x26; + buf[55] = 0x00; + + buf[56] = 0x01; + buf[57] = 0x01; + buf[58] = 0x10; + buf[59] = 0x45; + + buf[60] = 0x00; + buf[61] = ssid_len; + memcpy(&buf[62], app->text_buf, ssid_len); + size_t ssid = 62 + ssid_len; + buf[ssid + 0] = 0x10; + buf[ssid + 1] = 0x03; + + buf[ssid + 2] = 0x00; + buf[ssid + 3] = 0x02; + buf[ssid + 4] = 0x00; + buf[ssid + 5] = + scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiAuth); + + buf[ssid + 6] = 0x10; + buf[ssid + 7] = 0x0F; + buf[ssid + 8] = 0x00; + buf[ssid + 9] = 0x02; + + buf[ssid + 10] = 0x00; + buf[ssid + 11] = + scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiEncr); + buf[ssid + 12] = 0x10; + buf[ssid + 13] = 0x27; + + buf[ssid + 14] = 0x00; + buf[ssid + 15] = pass_len; + memcpy(&buf[ssid + 16], app->pass_buf, pass_len); + size_t pass = ssid + 16 + pass_len; + buf[pass + 0] = 0x10; + buf[pass + 1] = 0x20; + + buf[pass + 2] = 0x00; + buf[pass + 3] = 0x06; + buf[pass + 4] = 0xFF; + buf[pass + 5] = 0xFF; + + buf[pass + 6] = 0xFF; + buf[pass + 7] = 0xFF; + buf[pass + 8] = 0xFF; + buf[pass + 9] = 0xFF; + + break; + } + default: + break; + } + + // Message length and terminator + buf[22] = msg_len; + size_t msg_end = 23 + msg_len; + buf[msg_end] = 0xFE; + + // Padding + for(size_t i = msg_end + 1; i < size; i++) { + buf[i] = 0x00; + } + + char str[16]; + bool ok = true; + for(size_t page = 0; page < pages; page++) { + snprintf(str, sizeof(str), "Page %u", page); + if(!flipper_format_write_hex(file, str, &buf[page * 4], 4)) { + ok = false; + break; + } + } + if(!ok) break; + + free(buf); + success = true; + + } while(false); + furi_string_free(path); + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); + + if(success) { + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); + } else { + popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(popup, "Saved!", 13, 22, AlignLeft, AlignBottom); + } + popup_set_timeout(popup, 1500); + popup_set_context(popup, app); + popup_set_callback(popup, nfc_maker_scene_result_popup_callback); + popup_enable_timeout(popup); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewPopup); +} + +bool nfc_maker_scene_result_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case PopupEventExit: + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, NfcMakerSceneMenu); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_result_on_exit(void* context) { + NfcMaker* app = context; + popup_reset(app->popup); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c new file mode 100644 index 0000000000..17d84e0a95 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_text.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_text_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_text_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter Text Note:"); + + strlcpy(app->text_buf, "Lorem ipsum", TEXT_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_text_text_input_callback, + app, + app->text_buf, + TEXT_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_text_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_text_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c new file mode 100644 index 0000000000..1250b46afa --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_url.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_url_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_url_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter Plain URL:"); + + strlcpy(app->text_buf, "https://flipper-xtre.me", TEXT_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_url_text_input_callback, + app, + app->text_buf, + TEXT_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_url_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_url_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c new file mode 100644 index 0000000000..68efaf7f6e --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi.c @@ -0,0 +1,55 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_wifi_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_wifi_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter WiFi SSID:"); + + strlcpy(app->text_buf, "Bill Wi the Science Fi", WIFI_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_wifi_text_input_callback, + app, + app->text_buf, + WIFI_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_wifi_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_set_scene_state( + app->scene_manager, NfcMakerSceneWifiAuth, WifiAuthenticationWpa2Personal); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneWifiAuth); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_wifi_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c new file mode 100644 index 0000000000..4cb90dabe9 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_auth.c @@ -0,0 +1,83 @@ +#include "../nfc_maker.h" + +void nfc_maker_scene_wifi_auth_submenu_callback(void* context, uint32_t index) { + NfcMaker* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void nfc_maker_scene_wifi_auth_on_enter(void* context) { + NfcMaker* app = context; + Submenu* submenu = app->submenu; + + submenu_set_header(submenu, "Authentication Type:"); + + submenu_add_item( + submenu, "Open", WifiAuthenticationOpen, nfc_maker_scene_wifi_auth_submenu_callback, app); + + submenu_add_item( + submenu, + "WPA 2 Personal", + WifiAuthenticationWpa2Personal, + nfc_maker_scene_wifi_auth_submenu_callback, + app); + + submenu_add_item( + submenu, + "WPA 2 Enterprise", + WifiAuthenticationWpa2Enterprise, + nfc_maker_scene_wifi_auth_submenu_callback, + app); + + submenu_add_item( + submenu, + "WPA Personal", + WifiAuthenticationWpaPersonal, + nfc_maker_scene_wifi_auth_submenu_callback, + app); + + submenu_add_item( + submenu, + "WPA Enterprise", + WifiAuthenticationWpaEnterprise, + nfc_maker_scene_wifi_auth_submenu_callback, + app); + + submenu_add_item( + submenu, + "Shared", + WifiAuthenticationShared, + nfc_maker_scene_wifi_auth_submenu_callback, + app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiAuth)); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewSubmenu); +} + +bool nfc_maker_scene_wifi_auth_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, NfcMakerSceneWifiAuth, event.event); + consumed = true; + if(event.event == WifiAuthenticationOpen) { + scene_manager_set_scene_state( + app->scene_manager, NfcMakerSceneWifiEncr, WifiEncryptionNone); + strcpy(app->pass_buf, ""); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + } else { + scene_manager_set_scene_state( + app->scene_manager, NfcMakerSceneWifiEncr, WifiEncryptionAes); + scene_manager_next_scene(app->scene_manager, NfcMakerSceneWifiEncr); + } + } + + return consumed; +} + +void nfc_maker_scene_wifi_auth_on_exit(void* context) { + NfcMaker* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_encr.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_encr.c new file mode 100644 index 0000000000..d1a21f51a5 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_encr.c @@ -0,0 +1,48 @@ +#include "../nfc_maker.h" + +void nfc_maker_scene_wifi_encr_submenu_callback(void* context, uint32_t index) { + NfcMaker* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void nfc_maker_scene_wifi_encr_on_enter(void* context) { + NfcMaker* app = context; + Submenu* submenu = app->submenu; + + submenu_set_header(submenu, "Encryption Type:"); + + submenu_add_item( + submenu, "AES", WifiEncryptionAes, nfc_maker_scene_wifi_encr_submenu_callback, app); + + submenu_add_item( + submenu, "WEP", WifiEncryptionWep, nfc_maker_scene_wifi_encr_submenu_callback, app); + + submenu_add_item( + submenu, "TKIP", WifiEncryptionTkip, nfc_maker_scene_wifi_encr_submenu_callback, app); + + submenu_add_item( + submenu, "None", WifiEncryptionNone, nfc_maker_scene_wifi_encr_submenu_callback, app); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(app->scene_manager, NfcMakerSceneWifiEncr)); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewSubmenu); +} + +bool nfc_maker_scene_wifi_encr_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, NfcMakerSceneWifiEncr, event.event); + consumed = true; + scene_manager_next_scene(app->scene_manager, NfcMakerSceneWifiPass); + } + + return consumed; +} + +void nfc_maker_scene_wifi_encr_on_exit(void* context) { + NfcMaker* app = context; + submenu_reset(app->submenu); +} diff --git a/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c new file mode 100644 index 0000000000..3f5b4bd769 --- /dev/null +++ b/applications/external/nfc_maker/scenes/nfc_maker_scene_wifi_pass.c @@ -0,0 +1,53 @@ +#include "../nfc_maker.h" + +enum TextInputResult { + TextInputResultOk, +}; + +static void nfc_maker_scene_wifi_pass_text_input_callback(void* context) { + NfcMaker* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk); +} + +void nfc_maker_scene_wifi_pass_on_enter(void* context) { + NfcMaker* app = context; + TextInput* text_input = app->text_input; + + text_input_set_header_text(text_input, "Enter WiFi Password:"); + + strlcpy(app->pass_buf, "244466666", WIFI_INPUT_LEN); + + text_input_set_result_callback( + text_input, + nfc_maker_scene_wifi_pass_text_input_callback, + app, + app->pass_buf, + WIFI_INPUT_LEN, + true); + + view_dispatcher_switch_to_view(app->view_dispatcher, NfcMakerViewTextInput); +} + +bool nfc_maker_scene_wifi_pass_on_event(void* context, SceneManagerEvent event) { + NfcMaker* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case TextInputResultOk: + scene_manager_next_scene(app->scene_manager, NfcMakerSceneName); + break; + default: + break; + } + } + + return consumed; +} + +void nfc_maker_scene_wifi_pass_on_exit(void* context) { + NfcMaker* app = context; + text_input_reset(app->text_input); +}