ソースを参照

trade: Fixes, features, and improvements

This commit wraps up a number of changes that aim to improve the trade
process, its robustness, add some features, and fix some bugs.

Change list in rough order of importance:

More robust logic during trading which more correctly syncs states
between the Flipper and Game Boy.
Fixes #20 as a side effect.

Implement trade patch list. This allows for the Flipper to both send and
receive patch data.
Fixes #18

Ability for the Flipper to correctly receive the Pokemon traded to it
and put that Pokemon in the Flipper's trade buffer. This allows for
trading back and forth, as well as the following feature.

Ability for the Flipper to back out of the trade menu, modify the
current Pokemon, and re-enter the trade menu without having to
completely reset the Game Boy and re-enter the Cable Club.
Fixes #19

Completely isolate Trade's context and scope. The _alloc() function now
sets itself up with a View and adds it to the app's view_dispatcher.
Then returns an anonymous pointer to the Trade context.

Adds a huge comment block in the trade source outlining the current
understanding of the actual trade data protocol. Also adds specific
comments for more context specific details through various trade states.

Sets up draw timer callback on 250 ms intervals. This callback toggles a
variable that is used to switch between the two screens during the
TRADING state. The actual draw callback may be called more often. Using
a timer and variable modification allows us exact control of screen
changes.

Clean up overall state tracking. There are now two states that are
tracked, the Game Boy/link state, and the actual trade states. This also
allows elimination of additional bools that were used for state tracking
in parallel. State transitions and meanings should now be a bit more
straightforward.

CLK pin now implements an interrupt on either edge. The ISR was updated
to shift data out on the falling edge, and read data in on the rising
edge. This eliminates delays while in an interrupt context as well as
mimics Game Boy behavior by matching the setup/hold times.

Remove use of magic numbers as much as possible. Bytes to/from the
Pokemon game now use macros for most of the bytes. Many of these were
pulled from https://github.com/pret/pokered defines.

Clean up cycle counter to real-time maths. Copied general paradigms from
Flipper onewire code. This also includes the bit counter timeout and now
ensures correct timeouts are measured between transmitted bytes.
Kris Bahnsen 2 年 前
コミット
c617ce38bf
10 ファイル変更768 行追加349 行削除
  1. 26 17
      pokemon_app.c
  2. 3 11
      pokemon_app.h
  3. 0 1
      pokemon_char_encode.c
  4. 3 0
      pokemon_data.h
  5. 2 1
      scenes/pokemon_level.c
  6. 8 0
      scenes/pokemon_menu.c
  7. 610 317
      views/trade.c
  8. 6 2
      views/trade.h
  9. 86 0
      views/trade_patch_list.c
  10. 24 0
      views/trade_patch_list.h

+ 26 - 17
pokemon_app.c

@@ -1863,17 +1863,23 @@ const NamedList type_list[] = {
     {},
 };
 
+int pokemon_table_get_num_from_index(const PokemonTable* table, uint8_t index) {
+    int i;
+
+    for(i = 0;; i++) {
+        if(table[i].index == index) return i;
+        if(table[i].name == NULL) break;
+    }
+
+    return 0;
+}
+
 int pokemon_named_list_get_num_elements(const NamedList* list) {
     int i;
 
     for(i = 0;; i++) {
         if(list[i].name == NULL) return i;
     }
-
-    /* XXX: Would be faster to do something like this, but, can't easily do
-     * that using the current pointers. Might be able to clean this up later?
-     */
-    //return sizeof(type_list)/sizeof(type_list[0]);
 }
 
 int pokemon_named_list_get_list_pos_from_index(const NamedList* list, uint8_t index) {
@@ -1884,7 +1890,7 @@ int pokemon_named_list_get_list_pos_from_index(const NamedList* list, uint8_t in
         if(index == list[i].index) return i;
     }
 
-    /* XXX: This will return the first entry in case index is not matched.
+    /* This will return the first entry in case index is not matched.
      * Could be surprising at runtime.
      */
     return 0;
@@ -1898,7 +1904,7 @@ const char* pokemon_named_list_get_name_from_index(const NamedList* list, uint8_
         if(index == list[i].index) return list[i].name;
     }
 
-    /* XXX: This will return the first entry in the case index is not matched,
+    /* This will return the first entry in the case index is not matched,
      * this could be confusing/problematic at runtime.
      */
     return list[0].name;
@@ -1912,9 +1918,6 @@ void pokemon_trade_block_set_default_name(char* dest, PokemonFap* pokemon_fap, s
     /* Walk through the default name, toupper() each character, encode it, and
      * then write that to the same position in the trade_block.
      */
-    /* XXX: The limit of this is hard-coded to a length of 11 at most. This may
-     * be a problem down the road!
-     */
     for(i = 0; i < 11; i++) {
         pokemon_fap->trade_block->nickname[0].str[i] = pokemon_char_to_encoded(
             toupper(pokemon_fap->pokemon_table[pokemon_fap->curr_pokemon].name[i]));
@@ -2117,9 +2120,11 @@ static TradeBlock* trade_block_alloc(void) {
     /* OT trainer ID# */
     trade->party[0].ot_id = __builtin_bswap16(42069);
 
-    /* XXX: move pp isn't explicitly set up, should be fine */
-    /* XXX: catch/held isn't explicitly set up, should be okay for only Gen I support now */
-    /* XXX: Status condition isn't explicity let up, would you ever want to? */
+    /* Notes:
+     * Move pp isn't explicitly set up, should be fine
+     * Catch/held isn't explicitly set up, should be okay for only Gen I support now
+     * Status condition isn't explicity let up, would you ever want to?
+     */
 
     /* Set up initial level */
     trade->party[0].level = 2;
@@ -2180,8 +2185,12 @@ PokemonFap* pokemon_alloc() {
         pokemon_fap->view_dispatcher, AppViewSelectPokemon, pokemon_fap->select_view);
 
     // Trade View
-    pokemon_fap->trade_view = trade_alloc(pokemon_fap);
-    view_dispatcher_add_view(pokemon_fap->view_dispatcher, AppViewTrade, pokemon_fap->trade_view);
+    /* Allocates its own view and adds it to the main view_dispatcher */
+    pokemon_fap->trade = trade_alloc(
+        pokemon_fap->trade_block,
+        pokemon_fap->pokemon_table,
+        pokemon_fap->view_dispatcher,
+        AppViewTrade);
 
     return pokemon_fap;
 }
@@ -2193,8 +2202,8 @@ void free_app(PokemonFap* pokemon_fap) {
     view_dispatcher_remove_view(pokemon_fap->view_dispatcher, AppViewSelectPokemon);
     select_pokemon_free(pokemon_fap);
 
-    view_dispatcher_remove_view(pokemon_fap->view_dispatcher, AppViewTrade);
-    trade_free(pokemon_fap);
+    /* Also removes itself from the view_dispatcher */
+    trade_free(pokemon_fap->view_dispatcher, AppViewTrade, pokemon_fap->trade);
 
     view_dispatcher_remove_view(pokemon_fap->view_dispatcher, AppViewMainMenu);
 

+ 3 - 11
pokemon_app.h

@@ -44,22 +44,12 @@ struct named_list {
 
 typedef struct named_list NamedList;
 
-typedef enum {
-    GAMEBOY_INITIAL,
-    GAMEBOY_READY,
-    GAMEBOY_WAITING,
-    GAMEBOY_TRADE_READY,
-    GAMEBOY_SEND,
-    GAMEBOY_PENDING,
-    GAMEBOY_TRADING
-} render_gameboy_state_t;
-
 struct pokemon_fap {
     ViewDispatcher* view_dispatcher;
 
     /* View ports for each of the application's steps */
     View* select_view;
-    View* trade_view;
+    void* trade;
 
     /* Scene manager */
     SceneManager* scene_manager;
@@ -105,6 +95,8 @@ typedef enum {
     AppViewExitConfirm,
 } AppView;
 
+int pokemon_table_get_num_from_index(const PokemonTable* table, uint8_t index);
+
 int pokemon_named_list_get_num_elements(const NamedList* list);
 
 int pokemon_named_list_get_list_pos_from_index(const NamedList* list, uint8_t index);

+ 0 - 1
pokemon_char_encode.c

@@ -3,7 +3,6 @@
 
 #include "pokemon_char_encode.h"
 
-/* XXX Current text_input module only offers alnum and space input */
 char pokemon_char_to_encoded(int byte) {
     switch(byte) {
     case 'A':

+ 3 - 0
pokemon_data.h

@@ -13,6 +13,7 @@
  * GB/Z80. e.g. a uint16_t value of 0x2c01 translates to 0x012c.
  * Need to use __builtin_bswap16(val) to switch between Flipper and Pokemon.
  */
+/* This is 44 bytes in memory */
 struct __attribute__((__packed__)) pokemon_structure {
     uint8_t index;
     uint16_t hp; // Calculated from level
@@ -50,7 +51,9 @@ struct __attribute__((__packed__)) name {
     unsigned char str[11];
 };
 
+/* This is 415 bytes in memory/transmitted */
 struct __attribute__((__packed__)) trade_data_block {
+    /* TODO: Change this to use struct name above */
     unsigned char trainer_name[11];
     uint8_t party_cnt;
     /* Only the first pokemon is ever used even though there are 7 bytes here.

+ 2 - 1
scenes/pokemon_level.c

@@ -19,7 +19,7 @@ static bool select_level_input_validator(const char* text, FuriString* error, vo
         rc = false;
     } else {
         pokemon_fap->trade_block->party[0].level = level_val;
-        /* XXX: Need to recalculate other stats with level updated! */
+        pokemon_fap->trade_block->party[0].level_again = level_val;
     }
 
     return rc;
@@ -28,6 +28,7 @@ static bool select_level_input_validator(const char* text, FuriString* error, vo
 static void select_level_input_callback(void* context) {
     PokemonFap* pokemon_fap = (PokemonFap*)context;
 
+    /* Recalculate all stats from updated level */
     pokemon_trade_block_recalculate_stats_from_level(pokemon_fap);
     scene_manager_previous_scene(pokemon_fap->scene_manager);
 }

+ 8 - 0
scenes/pokemon_menu.c

@@ -38,6 +38,14 @@ void main_menu_scene_on_enter(void* context) {
      */
     scene_manager_set_scene_state(pokemon_fap->scene_manager, SelectMoveScene, 0);
 
+    /* HACK: Since we may have come from trade view, we cannot assume that
+     * pokemon_fap->curr_pokemon is correct.
+     * The proper way to do this would be to instead of tracking curr_pokemon
+     * separately, have it always be derived fro the current trade_block.
+     */
+    pokemon_fap->curr_pokemon = pokemon_table_get_num_from_index(
+        pokemon_fap->pokemon_table, pokemon_fap->trade_block->party_members[0]);
+
     submenu_reset(pokemon_fap->submenu);
 
     snprintf(

+ 610 - 317
views/trade.c

@@ -1,10 +1,91 @@
-#include <furi_hal_light.h>
+/*
+ * This setup always forces the flipper to the follower/slave role in the link.
+ * This just makes our logic consistent and since we're going to be gobs faster
+ * than a real Game Boy, we can be guaranteed to always be ready to respond.
+ *
+ * As documented here: http://www.adanscotney.com/2014/01/spoofing-pokemon-trades-with-stellaris.html
+ * The general gist of the communication is as follows:
+ * 1) Each Game Boy tries to listen for an external clock coming in on the link cable.
+ *     After some unknown timeout, this Game Boy decides its going to take the leader/master role.
+ *     In this state, it generates a clock and repeatedly sends out PKMN_MASTER(0x01)
+ * 2) The other side, sensing a clock from the leader/master, then responds with PKMN_SLAVE(0x02)
+ * 3) Once both sides understand their roles, they both respond with PKMN_BLANK(0x00) for a bit.
+ * 4) Next, the leader/master sends CONNECTED(0x60) bytes that the follower/slave repeats
+ *     back. Then a bunch of BLANK bytes.
+ * 5) At this point, each Game Boy repeatedly sends the menu item it has highlighted,
+ *     prepended by a BLANK, in groups of 3 bytes. These are ITEM_*_HIGHLIGHTED.
+ * 6) Then, once both sides send ITEM_*_SELECTED, the next step occurs.
+ *     This application, from steps 3 through 6, just repeats bytes back and lets the Game Boy
+ *     dictate the steps. We stay here until we start seeing PREAMBLE(0xFD) bytes,
+ *     as those dictate the start of the next sections.
+ *
+ * The Flipper is now in the "READY" state.
+ *
+ * 7) Once the player on the Game Boy side uses the trade table, a block of data is
+ *     transmitted. This starts with 10x PREAMBLE(0xFD) bytes, 10x random bytes (to
+ *     sync the RNG between two devices, unused at this time), and then the 415 trade_block,
+ *     struct gets transferred. At the end of this is 3 ending bytes, DF FE 15. And, weirdly,
+ *     3 PREAMBLE(0xFD) bytes.
+ * 8) The patch list starts with 3x more PREAMBLE(0xFD) bytes for a total of 6x PREAMBLE,
+ *     followed by 7x BLANK bytes. Then remaining 189 bytes of patch list data. The patch
+ *     list is used to compensate for byte values of NO_DATA_BYE(0xFE) being transmitted.
+ *     The patch list is specifically for the party data of the trade_block. To patch
+ *     outgoing data, if a byte is 0xFE, it is changed to 0xFF, and the index+1 is
+ *     added to the patch list. There are two parts to the patch list as the data it
+ *     covers is longer than 0xFC. After each part is complete, 0xFF is added to the
+ *     patch list. The first part of the patch list can patch 0x00:0xFB of the party,
+ *     the second part can patch 0xFC:0x107 of the party. If there are no bytes to
+ *     patch in a part, 0xFF is just appended. After both terminators, it is expected
+ *     all remaining bytes are 0x00.
+ *
+ * The Flipper is now in the "WAITING" state.
+ *
+ * 9) At this point, both sides have full copies of each other's current party. The sides
+ *     simply indicate which Pokemon they are sending. This is done with a BLANK byte to
+ *     start, and then each side indicates which Pokemon it wants to trade with SEL_NUM_MASK(0x60)
+ *     + party index. We always transmit the first Pokemon. Once in a agreement, both
+ *     sides transmit a handful of BLANK bytes.
+ *
+ * The Flipper is now in the "DEAL?" state.
+ *
+ * A) Starting with another BLANK byte, both sides need to agree to the trade by
+ *     sending TRADE_ACCEPT(0x62) repeatedly, and then a handful of BLANK bytes.
+ *     To disagree with a trade, either side would send TRADE_REJECT(0x61), the
+ *     Flipper will never send this on its own. If the Game Boy does, both it and
+ *     the flipper got back to step 9 again.
+ *
+ * The Flipper is now in the "TRADING" state.
+ *
+ * B) The Flipper actually goes back to step 7, but keeps the drawing mode as
+ *     TRADING. After the trade is complete on the Game Boy, it re-sends the
+ *     trade_block data. This re-syncs the states between the Flipper and
+ *     Game Boy and another trade can occur.
+ *
+ * *) A point of note is that the Flipper can go back to the main menu from
+ *     any state. Though, doing so in the TRADING state might actually cause
+ *     the Game Boy to have issues. When in READY or WAITING state, the Flipper
+ *     can go back and modify the Pokemon that the Game Boy sent to it. If the
+ *     Flipper then goes back to Trade from the main menu, it will be in the
+ *     READY state. If the Game Boy is still on the trade menu, and it tries
+ *     to trade, the trade will be rejected. The Game Boy needs to exit the
+ *     trade menu, and then re-enter it by selecting the table in the trade
+ *     center. This will then push the Flipper to the WAITING state, and the
+ *     trade_blocks will re-sync between them with the new data. If the Game Boy
+ *     leave the trade menu while the Flipper is in the WAITING state, the
+ *     Flipper will go back to the READY state.
+ *
+ *    TODO: Set up requiring a long back press to go back to the main menu
+ *     from the TRADING state or from the main menu to exit the application.
+ */
+
 #include <furi.h>
+#include <furi_hal.h>
 
 #include <gui/view.h>
 #include <pokemon_icons.h>
 
 #include "../pokemon_app.h"
+#include "trade_patch_list.h"
 
 #define GAME_BOY_CLK gpio_ext_pb2
 #define GAME_BOY_SI gpio_ext_pc3
@@ -20,10 +101,23 @@
 #define ITEM_2_SELECTED 0xD5
 #define ITEM_3_SELECTED 0xD6
 
+#define SERIAL_PREAMBLE_BYTE 0xFD
+
+#define SERIAL_PREAMBLE_LENGTH 6
+#define SERIAL_RN_PREAMBLE_LENGTH 7
+#define SERIAL_TRADE_PREAMBLE_LENGTH 9
+#define SERIAL_RNS_LENGTH 10
+#define SERIAL_PATCH_LIST_PART_TERMINATOR 0xFF
+#define SERIAL_NO_DATA_BYTE 0xFE
+
 #define PKMN_MASTER 0x01
 #define PKMN_SLAVE 0x02
 #define PKMN_CONNECTED 0x60
-#define PKMN_WAIT 0x7F
+#define PKMN_TRADE_ACCEPT 0x62
+#define PKMN_TRADE_REJECT 0x61
+#define PKMN_TABLE_LEAVE 0x6f
+#define PKMN_SEL_NUM_MASK 0x60
+#define PKMN_SEL_NUM_ONE 0x60
 
 #define PKMN_ACTION 0x60
 
@@ -31,48 +125,75 @@
 #define PKMN_COLOSSEUM ITEM_2_SELECTED
 #define PKMN_BREAK_LINK ITEM_3_SELECTED
 
-#define TRADE_CENTRE_WAIT 0xFD
+/* States specific to the trade process. */
+typedef enum {
+    TRADE_RESET,
+    TRADE_INIT,
+    TRADE_RANDOM,
+    TRADE_DATA,
+    TRADE_PATCH_HEADER,
+    TRADE_PATCH_DATA,
+    TRADE_SELECT,
+    TRADE_PENDING,
+    TRADE_CONFIRMATION,
+    TRADE_DONE
+} trade_centre_state_t;
+
+/* Global states for the trade logic. These are used to dictate what gets drawn
+ * to the screen but also handle a few sync states. The CONN states are to denote
+ * if a link has been established or note. READY through TRADING are all specific
+ * screens to draw in the trade center. COLOSSEUM causes a data loopback so the
+ * player can fight themselves.
+ */
+typedef enum {
+    GAMEBOY_CONN_FALSE,
+    GAMEBOY_CONN_TRUE,
+    GAMEBOY_READY,
+    GAMEBOY_WAITING,
+    GAMEBOY_TRADE_PENDING,
+    GAMEBOY_TRADING,
+    GAMEBOY_COLOSSEUM
+} render_gameboy_state_t;
+
+/* Anonymous struct */
+struct trade_ctx {
+    trade_centre_state_t trade_centre_state;
+    FuriTimer* draw_timer;
+    View* view;
+    uint8_t in_data;
+    uint8_t out_data;
+    uint8_t shift;
+    TradeBlock* trade_block;
+    TradeBlock* input_block;
+    const PokemonTable* pokemon_table;
+    struct patch_list* patch_list;
+};
 
+/* These are the needed variables for the draw callback */
 struct trade_model {
-    bool trading;
-    bool connected;
     render_gameboy_state_t gameboy_status;
+    bool ledon; // Controls the blue LED during trade
     uint8_t curr_pokemon;
     const PokemonTable* pokemon_table;
-    FuriTimer* draw_timer;
 };
 
-typedef unsigned char byte;
-typedef enum { NOT_CONNECTED, CONNECTED, TRADE_CENTRE, COLOSSEUM } connection_state_t;
-typedef enum {
-    INIT,
-    READY_TO_GO,
-    SEEN_FIRST_WAIT,
-    SENDING_RANDOM_DATA,
-    WAITING_TO_SEND_DATA,
-    START_SENDING_DATA,
-    SENDING_DATA,
-    DATA_SENT,
-    SENDING_PATCH_DATA,
-    TRADE_PENDING,
-    TRADE_CONFIRMATION,
-    DONE
-} trade_centre_state_t;
+/* A callback function that must be called outside of an interrupt context,
+ * This will completely destroy the current patch list, and then rebuild it from
+ * the current trade_block state. This is used mostly after a trade to rebuild
+ * the list with the new data we just copied in.
+ */
+static void pokemon_plist_recreate_callback(void* context, uint32_t arg) {
+    furi_assert(context);
+    UNUSED(arg);
+    struct trade_ctx* trade = context;
+
+    plist_create(&(trade->patch_list), trade->trade_block);
+}
+
+/* Draws a whole screen image with Flipper mascot, Game Boy, etc. */
+static void trade_draw_connect(Canvas* const canvas) {
+    furi_assert(canvas);
 
-/* TODO: Convert all of these to be maintained in a struct in the Trade context */
-uint8_t out_data = 0; // Should be able to be made as part of view model or static in used function
-uint8_t in_data = 0; //Should be able to be made as part of view model, is used in multiple funcs
-uint8_t shift = 0; //Should be able to be made as part of view model, is used in multiple funcs
-uint32_t time = 0; //Should be able to be made static in used function
-volatile int counter = 0; // Should be able to be made static in used function
-volatile bool procesing = true; // Review this vars use, it could potentially be removed
-volatile connection_state_t connection_state =
-    NOT_CONNECTED; // Should be made in to view model struct
-volatile trade_centre_state_t trade_centre_state =
-    INIT; // Should be able to be made part of view model
-unsigned char INPUT_BLOCK[405]; // Put this in pokemon_fap? Not sure yet
-
-void screen_gameboy_connect(Canvas* const canvas) {
     canvas_draw_frame(canvas, 0, 0, 128, 64);
     canvas_draw_icon(canvas, 1, 21, &I_Connect_me_62x31);
     canvas_draw_icon(canvas, 0, 53, &I_Background_128x11);
@@ -80,7 +201,11 @@ void screen_gameboy_connect(Canvas* const canvas) {
     canvas_draw_icon(canvas, 8, 2, &I_Space_65x18);
     canvas_draw_str(canvas, 18, 13, "Connect GB");
 }
-void screen_gameboy_connected(Canvas* const canvas) {
+
+/* Draws a whole screen image with Flipper mascot, Game Boy, etc. */
+static void trade_draw_connected(Canvas* const canvas) {
+    furi_assert(canvas);
+
     canvas_draw_frame(canvas, 0, 0, 128, 64);
     canvas_draw_icon(canvas, 1, 21, &I_Connected_62x31);
     canvas_draw_icon(canvas, 0, 53, &I_Background_128x11);
@@ -89,98 +214,107 @@ void screen_gameboy_connected(Canvas* const canvas) {
     canvas_draw_str(canvas, 18, 13, "Connected!");
 }
 
-int time_in_seconds = 0;
-
-static void trade_draw_callback(Canvas* canvas, void* model) {
-    const char* gameboy_status_text = NULL;
-    struct trade_model* view_model = model;
-    uint8_t curr_pokemon = view_model->curr_pokemon;
+/* Draws a frame around the screen, with a box at the top for a text string,
+ * and an icon of the player.
+ */
+static void trade_draw_frame(Canvas* canvas, const char* str) {
+    furi_assert(canvas);
 
-    canvas_clear(canvas);
-    if(!view_model->trading) {
-        if(!view_model->connected) {
-            furi_hal_light_set(LightGreen, 0x00);
-            furi_hal_light_set(LightBlue, 0x00);
-            furi_hal_light_set(LightRed, 0xff);
-            screen_gameboy_connect(canvas);
-        } else {
-            furi_hal_light_set(LightGreen, 0xff);
-            furi_hal_light_set(LightBlue, 0x00);
-            furi_hal_light_set(LightRed, 0x00);
-            screen_gameboy_connected(canvas);
-        }
-    } else {
-        switch(view_model->gameboy_status) {
-        case GAMEBOY_TRADING:
-            furi_hal_light_set(LightGreen, 0x00);
-            furi_hal_light_set(LightRed, 0x00);
-            if(time_in_seconds % 2 == 1) {
-                furi_hal_light_set(LightBlue, 0xff);
-                canvas_draw_icon(canvas, 0, 0, &I_gb_step_1);
-            } else {
-                furi_hal_light_set(LightBlue, 0x00);
-                canvas_draw_icon(canvas, 0, 0, &I_gb_step_2);
-            }
-            break;
-        case GAMEBOY_READY:
-        case GAMEBOY_WAITING:
-        case GAMEBOY_SEND:
-            canvas_draw_icon(canvas, 38, 11, view_model->pokemon_table[curr_pokemon].icon);
-            break;
-        default:
-            // Default state added to eliminated enum warning
-            break;
-        }
-        canvas_draw_icon(canvas, 0, 53, &I_Background_128x11);
+    canvas_draw_icon(canvas, 0, 53, &I_Background_128x11);
+    canvas_draw_frame(canvas, 0, 0, 128, 64);
+    canvas_draw_icon(canvas, 24, 0, &I_Space_80x18);
+    canvas_draw_str(canvas, 48, 12, str);
+    canvas_draw_icon(canvas, 27, 1, &I_red_16x15);
+}
 
-        canvas_draw_frame(canvas, 0, 0, 128, 64);
-        canvas_draw_icon(canvas, 24, 0, &I_Space_80x18);
+/* Draws the Pokemon's image in the middle of the screen */
+static void trade_draw_pkmn_avatar(Canvas* canvas, const Icon* icon) {
+    furi_assert(canvas);
+    furi_assert(icon);
 
-        switch(view_model->gameboy_status) {
-        case GAMEBOY_READY:
-            gameboy_status_text = "READY";
-            break;
-        case GAMEBOY_WAITING:
-            gameboy_status_text = "WAITING";
-            break;
-        case GAMEBOY_TRADE_READY:
-            gameboy_status_text = "READY";
-            break;
-        case GAMEBOY_SEND:
-            gameboy_status_text = "DEAL...";
-            break;
-        case GAMEBOY_PENDING:
-            gameboy_status_text = "PENDING...";
-            break;
-        case GAMEBOY_TRADING:
-            gameboy_status_text = "TRADING...";
-            break;
-        default:
-            gameboy_status_text = "INITIAL";
-            break;
-        }
+    canvas_draw_icon(canvas, 38, 11, icon);
+    furi_hal_light_set(LightBlue, 0x00);
+    furi_hal_light_set(LightGreen, 0x00);
+}
 
-        canvas_draw_str(canvas, 48, 12, gameboy_status_text);
+/* Called every 250 ms on a timer. This controls the blue LED when in TRADING
+ * state. This is necessary as Flipper OS does not make any guarantees on when
+ * draw updates may or may not be called. There are situations where a draw
+ * update is called much faster. Therefore, we need to control the update rate
+ * via the ledon view_model variable.
+ */
+static void trade_draw_timer_callback(void* context) {
+    furi_assert(context);
 
-        canvas_draw_icon(canvas, 27, 1, &I_red_16x15);
+    struct trade_ctx* trade = (struct trade_ctx*)context;
 
-        time_in_seconds = (int)DWT->CYCCNT / (72000000.0f / 4); //  250ms
-    }
+    with_view_model(
+        trade->view, struct trade_model * model, { model->ledon ^= 1; }, true);
 }
 
-uint32_t micros() {
-    return DWT->CYCCNT / 64;
+static void trade_draw_callback(Canvas* canvas, void* view_model) {
+    furi_assert(view_model);
+    struct trade_model* model = view_model;
+    const Icon* icon = model->pokemon_table[model->curr_pokemon].icon;
+
+    canvas_clear(canvas);
+    switch(model->gameboy_status) {
+    case GAMEBOY_CONN_FALSE:
+        furi_hal_light_set(LightGreen, 0x00);
+        furi_hal_light_set(LightRed, 0xff);
+        trade_draw_connect(canvas);
+        break;
+    case GAMEBOY_CONN_TRUE:
+        furi_hal_light_set(LightGreen, 0xff);
+        furi_hal_light_set(LightRed, 0x00);
+        trade_draw_connected(canvas);
+        break;
+    case GAMEBOY_READY:
+        trade_draw_pkmn_avatar(canvas, icon);
+        trade_draw_frame(canvas, "READY");
+        break;
+    case GAMEBOY_WAITING:
+        trade_draw_pkmn_avatar(canvas, icon);
+        trade_draw_frame(canvas, "WAITING");
+        break;
+    case GAMEBOY_TRADE_PENDING:
+        trade_draw_pkmn_avatar(canvas, icon);
+        trade_draw_frame(canvas, "DEAL?");
+        break;
+    case GAMEBOY_TRADING:
+        furi_hal_light_set(LightGreen, 0x00);
+        if(model->ledon) {
+            furi_hal_light_set(LightBlue, 0xff);
+            canvas_draw_icon(canvas, 0, 0, &I_gb_step_1);
+        } else {
+            furi_hal_light_set(LightBlue, 0x00);
+            canvas_draw_icon(canvas, 0, 0, &I_gb_step_2);
+        }
+        trade_draw_frame(canvas, "TRADING");
+        break;
+    case GAMEBOY_COLOSSEUM:
+        trade_draw_frame(canvas, "FIGHT!");
+        break;
+    default:
+        trade_draw_frame(canvas, "INITIAL");
+        break;
+    }
 }
 
 /* Get the response byte from the link partner, updating the connection
  * state if needed.
  */
-byte getConnectResponse(byte in) {
-    byte ret;
+static uint8_t getConnectResponse(struct trade_ctx* trade) {
+    furi_assert(trade);
+    uint8_t ret;
 
-    switch(in) {
+    switch(trade->in_data) {
     case PKMN_CONNECTED:
-        connection_state = CONNECTED;
+        with_view_model(
+            trade->view,
+            struct trade_model * model,
+            { model->gameboy_status = GAMEBOY_CONN_TRUE; },
+            false);
         ret = PKMN_CONNECTED;
         break;
     case PKMN_MASTER:
@@ -190,7 +324,11 @@ byte getConnectResponse(byte in) {
         ret = PKMN_BLANK;
         break;
     default:
-        connection_state = NOT_CONNECTED;
+        with_view_model(
+            trade->view,
+            struct trade_model * model,
+            { model->gameboy_status = GAMEBOY_CONN_FALSE; },
+            false);
         ret = PKMN_BREAK_LINK;
         break;
     }
@@ -198,142 +336,286 @@ byte getConnectResponse(byte in) {
     return ret;
 }
 
-/* Figure out what the pokemon game is requesting and move to that mode.
+/* Receive what the Pokemon game is requesting and move to that mode.
+ *
+ * This reads bytes sent by the Game Boy and responds. The only things
+ * we care about are when menu items are actually selected. The protocol
+ * seems to send data both when one of the link menu items is highlighted
+ * and when one of them is selected.
+ *
+ * If somehow we get a leader/master byte received, then go back to the
+ * NOT_CONNECTED state. For the leader/master byte likely means that
+ * the linked Game Boy is still trying to negotiate roles and we need to
+ * respond with a follower/slave byte.
+ *
+ * Note that, we can probably eventually drop colosseum/battle connections,
+ * though it may be an interesting exercise in better understanding how the
+ * "random" seeding is done between the units. As noted here:
+ * http://www.adanscotney.com/2014/01/spoofing-pokemon-trades-with-stellaris.html
+ * it is presumed these bytes are to sync the RNG seed between the units to
+ * not need arbitration on various die rolls.
  */
-byte getMenuResponse(byte in) {
-    /* TODO: Find out what this byte means */
-    byte response = 0x00;
+static uint8_t getMenuResponse(struct trade_ctx* trade) {
+    furi_assert(trade);
+
+    uint8_t response = PKMN_BLANK;
 
-    switch(in) {
+    switch(trade->in_data) {
     case PKMN_CONNECTED:
         response = PKMN_CONNECTED;
         break;
     case PKMN_TRADE_CENTRE:
-        connection_state = TRADE_CENTRE;
+        with_view_model(
+            trade->view,
+            struct trade_model * model,
+            { model->gameboy_status = GAMEBOY_READY; },
+            false);
         break;
     case PKMN_COLOSSEUM:
-        connection_state = COLOSSEUM;
+        with_view_model(
+            trade->view,
+            struct trade_model * model,
+            { model->gameboy_status = GAMEBOY_COLOSSEUM; },
+            false);
         break;
     case PKMN_BREAK_LINK:
     case PKMN_MASTER:
-        connection_state = NOT_CONNECTED;
+        with_view_model(
+            trade->view,
+            struct trade_model * model,
+            { model->gameboy_status = GAMEBOY_CONN_FALSE; },
+            false);
         response = PKMN_BREAK_LINK;
         break;
     default:
-        response = in;
+        response = trade->in_data;
         break;
     }
 
     return response;
 }
 
-byte getTradeCentreResponse(byte in, void* context) {
-    PokemonFap* pokemon_fap = (PokemonFap*)context;
-    uint8_t* trade_block_flat = (uint8_t*)pokemon_fap->trade_block;
-    render_gameboy_state_t gameboy_status;
-    byte send = in;
-
-    furi_assert(context);
+static uint8_t getTradeCentreResponse(struct trade_ctx* trade) {
+    furi_assert(trade);
+
+    uint8_t* trade_block_flat = (uint8_t*)trade->trade_block;
+    uint8_t* input_block_flat = (uint8_t*)trade->input_block;
+    uint8_t* input_party_flat = (uint8_t*)trade->input_block->party;
+    struct trade_model* model = NULL;
+    uint8_t in = trade->in_data;
+    uint8_t send = in;
+    static bool patch_pt_2;
+    static int counter;
+    static uint8_t in_pkmn_idx;
+
+    /* TODO: Figure out how we should respond to a no_data_byte and/or how to
+     * send one and what response to expect.
+     *
+     * This isn't a high priority since it should be unlikely that we would
+     * actually ever receive a NO_DATA_BYE as the Game Boy is the leader/master
+     * and therefore would only transmit when it has data ready.
+     */
+
+    /* Since this is a fairly long function, it doesn't call any other functions,
+     * the view model isn't locked, and we're in an interrupt context, lets just
+     * map the view model to a local var and commit it back when we're done.
+     */
+    model = view_get_model(trade->view);
+
+    /* There is a handful of communications that happen once the Game Boy
+     * clicks on the table. For all of them, the Flipper can just mirror back
+     * the byte the Game Boy sends. We can spin in this forever until we see 10x
+     * SERIAL_PREAMBLE_BYTEs. Once we receive those, the counters are synced,
+     * and every byte after that can be easily counted for the actual transfer
+     * of Pokemon data.
+     */
+    switch(trade->trade_centre_state) {
+    case TRADE_RESET:
+        /* Reset counters and other static variables */
+        counter = 0;
+        patch_pt_2 = false;
+        trade->trade_centre_state = TRADE_INIT;
+        break;
 
-    with_view_model(
-        pokemon_fap->trade_view,
-        struct trade_model * model,
-        { gameboy_status = model->gameboy_status; },
-        false);
-
-    switch(trade_centre_state) {
-    case INIT:
-        // TODO: What does this value of in mean?
-        if(in == 0x00) {
-            // TODO: What does counter signify here?
-            if(counter == 5) {
-                trade_centre_state = READY_TO_GO;
-                //  CLICK EN LA MESA
-                gameboy_status = GAMEBOY_READY;
-            }
+    /* This state runs through the end of the random preamble */
+    case TRADE_INIT:
+        if(in == SERIAL_PREAMBLE_BYTE) {
             counter++;
+            model->gameboy_status = GAMEBOY_WAITING;
+        } else if((in & PKMN_SEL_NUM_MASK) == PKMN_SEL_NUM_MASK) {
+            send = PKMN_TABLE_LEAVE;
+        }
+        if(counter == SERIAL_RNS_LENGTH) {
+            trade->trade_centre_state = TRADE_RANDOM;
+            counter = 0;
         }
         break;
 
-    case READY_TO_GO:
-        if((in & 0xF0) == 0xF0) trade_centre_state = SEEN_FIRST_WAIT;
+    /* Once we start getting PKMN_BLANKs, we mirror them until we get 10x
+     * SERIAL_PREAMBLE_BYTE, and then 10 random numbers. The 10 random
+     * numbers are for synchronizing the PRNG between the two systems,
+     * we do not use these numbers at this time.
+     *
+     * This waits through the end of the trade block preamble, a total of 20
+     * bytes.
+     */
+    case TRADE_RANDOM:
+        counter++;
+        if(counter == (SERIAL_RNS_LENGTH + SERIAL_TRADE_PREAMBLE_LENGTH)) {
+            trade->trade_centre_state = TRADE_DATA;
+            counter = 0;
+        }
         break;
 
-    case SEEN_FIRST_WAIT:
-        if((in & 0xF0) != 0xF0) {
+    /* This is where we exchange trade_block data with the Game Boy */
+    case TRADE_DATA:
+        input_block_flat[counter] = in;
+        send = trade_block_flat[counter];
+        counter++;
+
+        if(counter == sizeof(TradeBlock)) {
+            trade->trade_centre_state = TRADE_PATCH_HEADER;
             counter = 0;
-            trade_centre_state = SENDING_RANDOM_DATA;
         }
+
         break;
 
-    case SENDING_RANDOM_DATA:
-        if((in & 0xF0) == 0xF0) {
-            if(counter == 5) {
-                trade_centre_state = WAITING_TO_SEND_DATA;
-                gameboy_status = GAMEBOY_WAITING;
-            }
+    /* This absorbs the 3 byte ending sequence (DF FE 15) after the trade data is
+     * swapped, then the 3x SERIAL_PREAMBLE_BYTEs that end the trade data, and
+     * another 3x of them that start the patch data. By the time we're done with
+     * this state, the patch list BLANK bytes are ready to be transmitted.
+     * We only care about the 6x total preamble bytes.
+     */
+    case TRADE_PATCH_HEADER:
+        if(in == SERIAL_PREAMBLE_BYTE) {
             counter++;
         }
-        break;
 
-    case WAITING_TO_SEND_DATA:
-        if((in & 0xF0) != 0xF0) {
+        if(counter == 6) {
             counter = 0;
-            INPUT_BLOCK[counter] = in;
-            send = trade_block_flat[counter];
-            counter++;
-            trade_centre_state = SENDING_DATA;
+            trade->trade_centre_state = TRADE_PATCH_DATA;
+        } else {
+            break;
         }
-        break;
-
-    case SENDING_DATA:
-        INPUT_BLOCK[counter] = in;
-        send = trade_block_flat[counter];
+        [[fallthrough]];
+    case TRADE_PATCH_DATA:
         counter++;
-        if(counter == 405) //TODO: replace with sizeof struct rather than static number
-            trade_centre_state = SENDING_PATCH_DATA;
-        break;
+        /* This magic number is basically the header length, 10, minus
+	 * the 3x 0xFD that we should be transmitting as part of the patch
+	 * list header.
+	 */
+        if(counter > 7) {
+            send = plist_index_get(trade->patch_list, (counter - 8));
+        }
 
-    case SENDING_PATCH_DATA:
-        if(in == 0xFD) {
-            counter = 0;
-            send = 0xFD;
-        } else {
-            counter++;
-            if(counter == 197) // TODO: What is this magic value?
-                trade_centre_state = TRADE_PENDING;
+        /* Patch received data */
+        /* This relies on the data sent only ever sending 0x00 after
+         * part 2 of the patch list has been terminated. This is the
+         * case in official Gen I code at this time.
+         */
+        switch(in) {
+        case PKMN_BLANK:
+            break;
+        case SERIAL_PATCH_LIST_PART_TERMINATOR:
+            patch_pt_2 = true;
+            break;
+        default: // Any nonzero value will cause a patch
+            if(!patch_pt_2) {
+                /* Pt 1 is 0x00 - 0xFB */
+                input_party_flat[in - 1] = SERIAL_NO_DATA_BYTE;
+            } else {
+                /* Pt 2 is 0xFC - 0x107
+		 * 0xFC + in - 1
+		 */
+                input_party_flat[0xFB + in] = SERIAL_NO_DATA_BYTE;
+            }
+            break;
         }
+
+        /* What is interesting about the following check, is the Pokemon code
+	 * seems to allocate 203 bytes, 3x for the preamble, and then 200 bytes
+	 * of patch list. But in practice, the Game Boy seems to transmit 3x
+	 * preamble bytes, 7x 0x00, then 189 bytes for the patch list. A
+	 * total of 199 bytes transmitted.
+	 */
+        if(counter == 196) trade->trade_centre_state = TRADE_SELECT;
+
         break;
 
+    /* Resets the incoming Pokemon index, and once a BLANK byte is received,
+     * moves to the pending state.
+     */
+    case TRADE_SELECT:
+        in_pkmn_idx = 0;
+        if(in == PKMN_BLANK) {
+            trade->trade_centre_state = TRADE_PENDING;
+        } else {
+            break;
+        }
+        [[fallthrough]];
+    /* Handle the Game Boy selecting a Pokemon to trade, or leaving the table */
     case TRADE_PENDING:
-        /* TODO: What are these states */
-        if(in == 0x6F) {
-            trade_centre_state = READY_TO_GO;
-            send = 0x6F;
-            gameboy_status = GAMEBOY_TRADE_READY;
-        } else if((in & 0x60) == 0x60) {
-            send = 0x60; // first pokemon
-            gameboy_status = GAMEBOY_SEND;
-        } else if(in == 0x00) {
-            send = 0;
-            trade_centre_state = TRADE_CONFIRMATION;
+        /* If the player leaves the trade menu and returns to the room */
+        if(in == PKMN_TABLE_LEAVE) {
+            trade->trade_centre_state = TRADE_RESET;
+            send = PKMN_TABLE_LEAVE;
+            model->gameboy_status = GAMEBOY_READY;
+            /* If the player selected a Pokemon to send from the Game Boy */
+        } else if((in & PKMN_SEL_NUM_MASK) == PKMN_SEL_NUM_MASK) {
+            in_pkmn_idx = in;
+            send = PKMN_SEL_NUM_ONE; // We always send the first Pokemon
+            model->gameboy_status = GAMEBOY_TRADE_PENDING;
+            /* BLANKs are sent in a few places, we want to do nothing about them
+	 * unless the Game Boy already sent us an index they want to trade.
+	 */
+        } else if(in == PKMN_BLANK) {
+            if(in_pkmn_idx != 0) {
+                send = 0;
+                trade->trade_centre_state = TRADE_CONFIRMATION;
+                in_pkmn_idx &= 0x0F;
+            }
         }
         break;
 
+    /* Handle the Game Boy accepting or rejecting a trade deal */
     case TRADE_CONFIRMATION:
-        if(in == 0x61) {
-            trade_centre_state = TRADE_PENDING;
-            gameboy_status = GAMEBOY_PENDING;
-        } else if((in & 0x60) == 0x60) {
-            trade_centre_state = DONE;
+        if(in == PKMN_TRADE_REJECT) {
+            trade->trade_centre_state = TRADE_SELECT;
+            model->gameboy_status = GAMEBOY_WAITING;
+        } else if(in == PKMN_TRADE_ACCEPT) {
+            trade->trade_centre_state = TRADE_DONE;
         }
         break;
 
-    case DONE:
-        if(in == 0x00) {
-            send = 0;
-            trade_centre_state = INIT;
-            gameboy_status = GAMEBOY_TRADING;
+    /* Start the actual trade. Waits in reset until the Game Boy is done with
+     * its animation and re-exchanges updated party data.
+     */
+    case TRADE_DONE:
+        if(in == PKMN_BLANK) {
+            trade->trade_centre_state = TRADE_RESET;
+            model->gameboy_status = GAMEBOY_TRADING;
+
+            /* Copy the traded-in Pokemon's main data to our struct */
+            trade->trade_block->party_members[0] = trade->input_block->party_members[in_pkmn_idx];
+            memcpy(
+                &(trade->trade_block->party[0]),
+                &(trade->input_block->party[in_pkmn_idx]),
+                sizeof(struct pokemon_structure));
+            memcpy(
+                &(trade->trade_block->nickname[0]),
+                &(trade->input_block->nickname[in_pkmn_idx]),
+                sizeof(struct name));
+            memcpy(
+                &(trade->trade_block->ot_name[0]),
+                &(trade->input_block->ot_name[in_pkmn_idx]),
+                sizeof(struct name));
+            model->curr_pokemon = pokemon_table_get_num_from_index(
+                trade->pokemon_table, trade->trade_block->party_members[0]);
+
+            /* Schedule a callback outside of ISR context to rebuild the patch
+	     * list with the new Pokemon that we just accepted.
+	     */
+            furi_timer_pending_callback(pokemon_plist_recreate_callback, trade, 0);
         }
         break;
 
@@ -342,175 +624,186 @@ byte getTradeCentreResponse(byte in, void* context) {
         break;
     }
 
-    with_view_model(
-        pokemon_fap->trade_view,
-        struct trade_model * model,
-        { model->gameboy_status = gameboy_status; },
-        false);
+    view_commit_model(trade->view, false);
 
     return send;
 }
 
 void transferBit(void* context) {
-    PokemonFap* pokemon_fap = (PokemonFap*)context;
     furi_assert(context);
-    bool connected;
-    bool trading;
+
+    struct trade_ctx* trade = (struct trade_ctx*)context;
+    render_gameboy_state_t status;
 
     with_view_model(
-        pokemon_fap->trade_view,
-        struct trade_model * model,
-        {
-            connected = model->connected;
-            trading = model->trading;
-        },
-        false);
-
-    byte raw_data = furi_hal_gpio_read(&GAME_BOY_SI);
-    in_data |= raw_data << (7 - shift);
-    if(++shift > 7) {
-        shift = 0;
-        switch(connection_state) {
-        case NOT_CONNECTED:
-            connected = false;
-            out_data = getConnectResponse(in_data);
+        trade->view, struct trade_model * model, { status = model->gameboy_status; }, false);
+
+    /* Shift data in every clock */
+    trade->in_data <<= 1;
+    trade->in_data |= furi_hal_gpio_read(&GAME_BOY_SI);
+    trade->shift++;
+
+    /* Once a byte of data has been shifted in, process it */
+    if(trade->shift == 8) {
+        trade->shift = 0;
+        switch(status) {
+        case GAMEBOY_CONN_FALSE:
+            trade->out_data = getConnectResponse(trade);
             break;
-        case CONNECTED:
-            connected = true;
-            out_data = getMenuResponse(in_data);
+        case GAMEBOY_CONN_TRUE:
+            trade->out_data = getMenuResponse(trade);
             break;
-        case TRADE_CENTRE:
-            out_data = getTradeCentreResponse(in_data, pokemon_fap);
+        case GAMEBOY_COLOSSEUM:
+            trade->out_data = trade->in_data;
             break;
+        /* Every other state is trade related */
         default:
-            out_data = in_data;
+            trade->out_data = getTradeCentreResponse(trade);
             break;
         }
-
-        in_data = 0; // TODO: I don't think this is necessary?
     }
-
-    while(procesing && !furi_hal_gpio_read(&GAME_BOY_CLK))
-        ;
-
-    furi_hal_gpio_write(&GAME_BOY_SO, out_data & 0x80 ? true : false);
-    furi_delay_us(
-        DELAY_MICROSECONDS); // Wait 20-60us ... 120us max (in slave mode is not necessary)
-    // TODO: The above comment doesn't make sense as DELAY_MICROSECONDS is defined as 15
-
-    if(trade_centre_state == READY_TO_GO) trading = true;
-
-    out_data = out_data << 1;
-
-    with_view_model(
-        pokemon_fap->trade_view,
-        struct trade_model * model,
-        {
-            model->connected = connected;
-            model->trading = trading;
-        },
-        false);
 }
 
 void input_clk_gameboy(void* context) {
     furi_assert(context);
 
-    if(time > 0) {
-        //  if there is no response from the master in 120 microseconds, the counters are reset
-        if(micros() - time > 120) {
-            //  IDLE & Reset
-            in_data = 0;
-            shift = 0;
+    struct trade_ctx* trade = (struct trade_ctx*)context;
+    static uint32_t time;
+    /* Clocks idle between bytes is nominally 430 us long for burst data,
+     * 15 ms for idle polling (e.g. waiting for menu selection), some oddball
+     * 2 ms gaps that appears between one 0xFE byte from the Game Boy every trade;
+     * clock period is nominally 122 us.
+     * Therefore, if we haven't seen a clock in 500 us, reset our bit counter.
+     * Note that, this should never actually be a concern, but it is an additional
+     * safeguard against desyncing.
+     */
+    const uint32_t time_ticks = furi_hal_cortex_instructions_per_microsecond() * 500;
+
+    if(furi_hal_gpio_read(&GAME_BOY_CLK)) {
+        if((DWT->CYCCNT - time) > time_ticks) {
+            trade->in_data = 0;
+            trade->shift = 0;
         }
+        transferBit(trade);
+        time = DWT->CYCCNT;
+    } else {
+        /* On the falling edge of each clock, set up the next bit */
+        furi_hal_gpio_write(&GAME_BOY_SO, !!(trade->out_data & 0x80));
+        trade->out_data <<= 1;
     }
-
-    transferBit(context);
-    time = micros();
-}
-
-void trade_draw_timer_callback(void* context) {
-    PokemonFap* pokemon_fap = (PokemonFap*)context;
-
-    with_view_model(
-        pokemon_fap->trade_view, struct trade_model * model, { UNUSED(model); }, true);
 }
 
 void trade_enter_callback(void* context) {
-    PokemonFap* pokemon_fap = (PokemonFap*)context;
     furi_assert(context);
+    struct trade_ctx* trade = (struct trade_ctx*)context;
+    struct trade_model* model;
 
-    with_view_model(
-        pokemon_fap->trade_view,
-        struct trade_model * model,
-        {
-            model->trading = false;
-            model->connected = false;
-            model->gameboy_status = GAMEBOY_INITIAL;
-            model->pokemon_table = pokemon_fap->pokemon_table;
-            model->curr_pokemon = (uint8_t)pokemon_fap->curr_pokemon;
-            model->draw_timer =
-                furi_timer_alloc(trade_draw_timer_callback, FuriTimerTypePeriodic, pokemon_fap);
-            /* Every 100 ms, trigger a draw update */
-            furi_timer_start(model->draw_timer, 100);
-        },
-        true);
+    model = view_get_model(trade->view);
+
+    if(model->gameboy_status == GAMEBOY_COLOSSEUM) {
+        model->gameboy_status = GAMEBOY_CONN_FALSE;
+    } else if(model->gameboy_status > GAMEBOY_READY) {
+        model->gameboy_status = GAMEBOY_READY;
+    }
+    trade->trade_centre_state = TRADE_RESET;
+    model->pokemon_table = trade->pokemon_table;
+    model->curr_pokemon = pokemon_table_get_num_from_index(
+        trade->pokemon_table, trade->trade_block->party_members[0]);
+    model->ledon = false;
+
+    view_commit_model(trade->view, true);
+
+    trade->in_data = 0;
+    trade->out_data = 0;
+    trade->shift = 0;
+
+    /* Every 250 ms, trigger a draw update. 250 ms was chosen so that during
+     * the trade process, each update can flip the LED and screen to make the
+     * trade animation.
+     */
+    trade->draw_timer = furi_timer_alloc(trade_draw_timer_callback, FuriTimerTypePeriodic, trade);
+    furi_timer_start(trade->draw_timer, furi_ms_to_ticks(250));
 
     // B3 (Pin6) / SO (2)
     furi_hal_gpio_write(&GAME_BOY_SO, false);
     furi_hal_gpio_init(&GAME_BOY_SO, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
     // B2 (Pin5) / SI (3)
     furi_hal_gpio_write(&GAME_BOY_SI, false);
-    furi_hal_gpio_init(&GAME_BOY_SI, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
+    furi_hal_gpio_init(&GAME_BOY_SI, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
     // // C3 (Pin7) / CLK (5)
-    furi_hal_gpio_init(&GAME_BOY_CLK, GpioModeInterruptRise, GpioPullNo, GpioSpeedVeryHigh);
+    furi_hal_gpio_init(&GAME_BOY_CLK, GpioModeInterruptRiseFall, GpioPullUp, GpioSpeedVeryHigh);
     furi_hal_gpio_remove_int_callback(&GAME_BOY_CLK);
-    furi_hal_gpio_add_int_callback(&GAME_BOY_CLK, input_clk_gameboy, pokemon_fap);
+
+    furi_hal_gpio_add_int_callback(&GAME_BOY_CLK, input_clk_gameboy, trade);
+
+    /* Create a trade patch list from the current trade block */
+    plist_create(&(trade->patch_list), trade->trade_block);
 }
 
 void disconnect_pin(const GpioPin* pin) {
-    furi_hal_gpio_init(pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
-    furi_hal_gpio_write(pin, true);
+    /* Existing projects seem to set the pin back to analog mode upon exit */
+    furi_hal_gpio_init_simple(pin, GpioModeAnalog);
 }
 
 void trade_exit_callback(void* context) {
-    PokemonFap* pokemon_fap = (PokemonFap*)context;
     furi_assert(context);
-    procesing = false;
+
+    struct trade_ctx* trade = (struct trade_ctx*)context;
+
     furi_hal_light_set(LightGreen, 0x00);
     furi_hal_light_set(LightBlue, 0x00);
     furi_hal_light_set(LightRed, 0x00);
+
     /* Stop the timer, and deallocate it as the enter callback allocates it on entry */
-    with_view_model(
-        pokemon_fap->trade_view,
-        struct trade_model * model,
-        { furi_timer_free(model->draw_timer); },
-        false);
+    furi_timer_free(trade->draw_timer);
+    trade->draw_timer = NULL;
+
+    /* Unset our interrupt callback */
+    furi_hal_gpio_remove_int_callback(&GAME_BOY_CLK);
+    disconnect_pin(&GAME_BOY_CLK);
+
+    /* Destroy the patch list, it is allocated on the enter callback */
+    plist_free(trade->patch_list);
+    trade->patch_list = NULL;
 }
 
-View* trade_alloc(PokemonFap* pokemon_fap) {
-    View* view;
+void* trade_alloc(
+    TradeBlock* trade_block,
+    const PokemonTable* table,
+    ViewDispatcher* view_dispatcher,
+    uint32_t view_id) {
+    furi_assert(trade_block);
+
+    struct trade_ctx* trade = malloc(sizeof(struct trade_ctx));
 
-    view = view_alloc();
-    procesing = true;
+    memset(trade, '\0', sizeof(struct trade_ctx));
+    trade->view = view_alloc();
+    trade->trade_block = trade_block;
+    trade->input_block = malloc(sizeof(TradeBlock));
+    trade->pokemon_table = table;
+    trade->patch_list = NULL;
 
-    view_set_context(view, pokemon_fap);
-    view_allocate_model(view, ViewModelTypeLockFree, sizeof(struct trade_model));
+    view_set_context(trade->view, trade);
+    view_allocate_model(trade->view, ViewModelTypeLockFree, sizeof(struct trade_model));
 
-    view_set_draw_callback(view, trade_draw_callback);
-    view_set_enter_callback(view, trade_enter_callback);
-    view_set_exit_callback(view, trade_exit_callback);
+    view_set_draw_callback(trade->view, trade_draw_callback);
+    view_set_enter_callback(trade->view, trade_enter_callback);
+    view_set_exit_callback(trade->view, trade_exit_callback);
 
-    return view;
+    view_dispatcher_add_view(view_dispatcher, view_id, trade->view);
+
+    return trade;
 }
 
-void trade_free(PokemonFap* pokemon_fap) {
-    furi_assert(pokemon_fap);
-    // Free resources
-    procesing = false;
-    furi_hal_gpio_remove_int_callback(&GAME_BOY_CLK);
+void trade_free(ViewDispatcher* view_dispatcher, uint32_t view_id, void* trade_ctx) {
+    furi_assert(trade_ctx);
 
-    disconnect_pin(&GAME_BOY_CLK);
+    struct trade_ctx* trade = (struct trade_ctx*)trade_ctx;
+
+    view_dispatcher_remove_view(view_dispatcher, view_id);
 
-    view_free_model(pokemon_fap->trade_view);
-    view_free(pokemon_fap->trade_view);
+    view_free_model(trade->view);
+    view_free(trade->view);
+    free(trade->input_block);
+    free(trade);
 }

+ 6 - 2
views/trade.h

@@ -6,8 +6,12 @@
 #include <gui/view.h>
 #include "../pokemon_app.h"
 
-View* trade_alloc(PokemonFap* pokemon_fap);
+void* trade_alloc(
+    TradeBlock* trade_block,
+    const PokemonTable* table,
+    ViewDispatcher* view_dispatcher,
+    uint32_t view_id);
 
-void trade_free(PokemonFap* pokemon_fap);
+void trade_free(ViewDispatcher* view_dispatcher, uint32_t view_id, void* trade_ctx);
 
 #endif /* TRADE_H */

+ 86 - 0
views/trade_patch_list.c

@@ -0,0 +1,86 @@
+#include "../pokemon_app.h"
+#include "trade_patch_list.h"
+
+struct patch_list* plist_alloc(void) {
+    struct patch_list* plist = NULL;
+
+    plist = malloc(sizeof(struct patch_list));
+    plist->index = 0;
+    plist->next = NULL;
+    return plist;
+}
+
+void plist_append(struct patch_list* plist, uint8_t index) {
+    furi_assert(plist);
+
+    for(;;) {
+        if(plist->next == NULL) break;
+        plist = plist->next;
+    }
+    plist->index = index;
+    plist->next = plist_alloc();
+}
+
+void plist_free(struct patch_list* plist) {
+    struct patch_list* plist_next = NULL;
+
+    while(plist != NULL) {
+        plist_next = plist->next;
+        free(plist);
+        plist = plist_next;
+    }
+}
+
+/* Returns the index value at offset member of the list. If offset is beyond
+ * the length of the allocated list, it will just return 0.
+ */
+uint8_t plist_index_get(struct patch_list* plist, int offset) {
+    furi_assert(plist);
+    int i;
+
+    for(i = 0; i < offset; i++) {
+        if(plist->next == NULL) break;
+        plist = plist->next;
+    }
+
+    return plist->index;
+}
+
+void plist_create(struct patch_list** pplist, TradeBlock* trade_block) {
+    furi_assert(trade_block);
+    uint8_t* trade_party_flat = (uint8_t*)trade_block->party;
+    int i;
+
+    /* If plist is non-NULL that means its already been created. Tear it down
+     * first.
+     */
+    if(*pplist != NULL) {
+        plist_free(*pplist);
+        *pplist = NULL;
+    }
+
+    *pplist = plist_alloc();
+    /* NOTE: 264 magic number is the length of the party block, 44 * 6 */
+    /* The first half of the patch list covers offsets 0x00 - 0xfb, which
+     * is expressed as 0x01 - 0xfc. An 0xFF byte is added to signify the
+     * end of the first part. The second half of the patch list covers
+     * offsets 0xfc - 0x107. Which is expressed as 0x01 - 0xc. A 0xFF byte
+     * is added to signify the end of the second part/
+     */
+    for(i = 0; i < 264; i++) {
+        FURI_LOG_D(TAG, "%02X", trade_party_flat[i]);
+        if(i == 0xFC) {
+            FURI_LOG_D(TAG, "[plist] part 1 end");
+            plist_append(*pplist, 0xFF);
+        }
+
+        if(trade_party_flat[i] == 0xFE) {
+            FURI_LOG_D(
+                TAG, "[plist] patching byte 0x%02X, adding 0x%02X to plist", i, (i % 0xfc) + 1);
+            plist_append(*pplist, (i % 0xfc) + 1);
+            trade_party_flat[i] = 0xFF;
+        }
+    }
+    FURI_LOG_D(TAG, "[plist] part 2 end");
+    plist_append(*pplist, 0xFF);
+}

+ 24 - 0
views/trade_patch_list.h

@@ -0,0 +1,24 @@
+#ifndef TRADE_PATCH_LIST_H
+#define TRADE_PATCH_LIST_H
+
+#pragma once
+
+#include <gui/view.h>
+#include "../pokemon_app.h"
+
+struct patch_list {
+    uint8_t index;
+    struct patch_list* next;
+};
+
+struct patch_list* plist_alloc(void);
+
+void plist_append(struct patch_list* plist, uint8_t index);
+
+void plist_free(struct patch_list* plist);
+
+uint8_t plist_index_get(struct patch_list* plist, int offset);
+
+void plist_create(struct patch_list** pplist, TradeBlock* trade_block);
+
+#endif /* TRADE_PATCH_LIST_H */