Просмотр исходного кода

Merge pull request #21 from kbembedded/trade-features

Trade: Overhaul for features and fixes. Bump to 1.4
Kris Bahnsen 2 лет назад
Родитель
Сommit
78c99b8d68

+ 7 - 5
README.md

@@ -22,7 +22,7 @@ It currently trades a Pokemon based on your choice of Pokemon, Level, Stats and
 
 
 ## Installation Directions
 ## Installation Directions
 
 
-This project is intended to be overlayed on top of an existing firmware repo,  in my case the **Release 0.79.1** version.
+This project is intended to be overlaid on top of an existing firmware repo,  in my case the **Release 0.79.1** version.
 
 
 - Clone the [Flipper Zero firmware repository](https://github.com/flipperdevices/flipperzero-firmware). Refer to [this tutorial](https://github.com/jamisonderek/flipper-zero-tutorials/tree/main/firmware/updating/README.md) for updating the firmware.
 - Clone the [Flipper Zero firmware repository](https://github.com/flipperdevices/flipperzero-firmware). Refer to [this tutorial](https://github.com/jamisonderek/flipper-zero-tutorials/tree/main/firmware/updating/README.md) for updating the firmware.
 - Copy the "pokemon" folder into the `/applications_user/pokemon` folder in your firmware.
 - Copy the "pokemon" folder into the `/applications_user/pokemon` folder in your firmware.
@@ -87,7 +87,7 @@ These instructions assume that you are starting at the Flipper Zero desktop. Oth
         <br />
         <br />
     </p>
     </p>
 
 
-- The `Select Moves` menu is used to pick the set the traded Pokemon's moves. They are pre-populated with the moveset that the Pokemon would know at level 1. Selecting a move slot will bring up an alphabetical index of moves. Additionally, `No Move` and `Default` can be quickliy selected. Note that any move after the first `No Move` is ignored. 
+- The `Select Moves` menu is used to pick the set the traded Pokemon's moves. They are pre-populated with the moveset that the Pokemon would know at level 1. Selecting a move slot will bring up an alphabetical index of moves. Additionally, `No Move` and `Default` can be quickly selected. Note that any move after the first `No Move` is ignored. 
 
 
     <p align='center'>
     <p align='center'>
         <br />
         <br />
@@ -115,7 +115,7 @@ These instructions assume that you are starting at the Flipper Zero desktop. Oth
 - The Pokemon's stats can also be influenced. The current settings are:
 - The Pokemon's stats can also be influenced. The current settings are:
   - `Random IV, Zero EV` Mimics stats of a caught wild Pokemon.
   - `Random IV, Zero EV` Mimics stats of a caught wild Pokemon.
   - `Random IV, Max EV / Level` IV is randomized, but EV is set to the maximum a trained Pokemon could be for its current level.
   - `Random IV, Max EV / Level` IV is randomized, but EV is set to the maximum a trained Pokemon could be for its current level.
-  - `Randon IV, Max EV` IV is randomized, EV is set to the abosolute max for a perfectly trained Pokemon.
+  - `Randon IV, Max EV` IV is randomized, EV is set to the absolute max for a perfectly trained Pokemon.
   - `Max IV, Zero EV` Mimics stats of a caught wild Pokemon, but with the maximum IV possible.
   - `Max IV, Zero EV` Mimics stats of a caught wild Pokemon, but with the maximum IV possible.
   - `Max IV, Max EV / Level` IV is max, EV is set to the maximum a trained Pokemon could be for its current level.
   - `Max IV, Max EV / Level` IV is max, EV is set to the maximum a trained Pokemon could be for its current level.
   - `Max IV, Max EV` Absolutely perfect and overly powerful Pokemon.
   - `Max IV, Max EV` Absolutely perfect and overly powerful Pokemon.
@@ -126,7 +126,7 @@ These instructions assume that you are starting at the Flipper Zero desktop. Oth
         <br />
         <br />
     </p>
     </p>
 
 
-- The `OT ID#` and `OT Name` of the Pokemon can also be set. The `OT ID#` must be between `0` and `65535`. Setting the `OT ID#` and `OT Name` to the same as your current trainer's causes the game to believe it was a wild caught Pokemon and not one that was traded. This means high level Pokmon will still obey you without badges, but, will not get the experience boost of a traded Pokemon.
+- The `OT ID#` and `OT Name` of the Pokemon can also be set. The `OT ID#` must be between `0` and `65535`. Setting the `OT ID#` and `OT Name` to the same as your current trainer's causes the game to believe it was a wild caught Pokemon and not one that was traded. This means high level Pokemon will still obey you without badges, but, will not get the experience boost of a traded Pokemon.
 
 
     <p align='center'>
     <p align='center'>
         <br />
         <br />
@@ -220,6 +220,8 @@ These instructions assume that you are starting at the Flipper Zero desktop. Oth
         <img src="./docs/images/flipper-zero-flat-12.png" width="400" /><br />
         <img src="./docs/images/flipper-zero-flat-12.png" width="400" /><br />
     </p>
     </p>
 
 
+- Once the trade is complete, both the **Game Boy** and the **Flipper Zero** will return to the `WAITING` state. If the **Game Boy** selects `CANCEL`, the **Flipper Zero** will return to the `READY` state. The <img src="./docs/images/back.png" /> BACK button can be pressed to return to the main menu. The traded Pokemon can be modified, or completely changed, if desired. Once the **Flipper Zero** Re-enters the Trade screen, and the **Game Boy** re-selects the trade table in-game, another trade can be completed. This allows for trading multiple Pokemon without having to reset the **Game Boy** each time.
+
     If the Flipper Zero gets stuck at the end of the exchange, you must reboot it by pressing the <img src="./docs/images/left.png" /> LEFT + <img src="./docs/images/back.png" /> BACK key combination.
     If the Flipper Zero gets stuck at the end of the exchange, you must reboot it by pressing the <img src="./docs/images/left.png" /> LEFT + <img src="./docs/images/back.png" /> BACK key combination.
 
 
     <p align='center'>
     <p align='center'>
@@ -328,7 +330,7 @@ For each image, the color `#aaa` was transformed to `#fff` so that Flipper Zero
 ## Links
 ## Links
 
 
 - [Flipper Zero firmware source code](https://github.com/flipperdevices/flipperzero-firmware)
 - [Flipper Zero firmware source code](https://github.com/flipperdevices/flipperzero-firmware)
-- Adan Scotney's pokemon [trade protocol specification](http://www.adanscotney.com/2014/01/spoofing-pokemon-trades-with-stellaris.html) and implementation
+- Adan Scotney's Pokemon [trade protocol specification](http://www.adanscotney.com/2014/01/spoofing-pokemon-trades-with-stellaris.html) and implementation
 - Derek Jamison - [Youtube Channel](https://www.youtube.com/@MrDerekJamison)
 - Derek Jamison - [Youtube Channel](https://www.youtube.com/@MrDerekJamison)
 - Matt Penny - [GBPlay Blog](https://blog.gbplay.io/)
 - Matt Penny - [GBPlay Blog](https://blog.gbplay.io/)
 - [Pokémon data structure (Generation I)](<https://bulbapedia.bulbagarden.net/wiki/Pok%C3%A9mon_data_structure_(Generation_I)>)
 - [Pokémon data structure (Generation I)](<https://bulbapedia.bulbagarden.net/wiki/Pok%C3%A9mon_data_structure_(Generation_I)>)

+ 10 - 9
TODO.md

@@ -11,8 +11,8 @@
     - [x] Add view to allow the traded Pokemon's level to be chosen between 2 and 100  
     - [x] Add view to allow the traded Pokemon's level to be chosen between 2 and 100  
     - [x] Add view to allow the traded Pokemon's hidden stats to be chosen (IV and EV) from some options  
     - [x] Add view to allow the traded Pokemon's hidden stats to be chosen (IV and EV) from some options  
       - [ ] Are there any better ways to present these options?  
       - [ ] Are there any better ways to present these options?  
-    - [ ] Debug traded Pokemon level issue where after a battle the Pokemon's level drops (doesn't affect all traded Pokemon)  
-    - [ ] Optimise the level selection screen to be a number slider input instead of the current slideshow style selector (Implemented as text input that only accepts numbers)  
+    - [x] Debug traded Pokemon level issue where after a battle the Pokemon's level drops (doesn't affect all traded Pokemon)  
+    - [x] Optimise the level selection screen to be a number slider input instead of the current slideshow style selector (Implemented as text input that only accepts numbers)  
   - Moves  
   - Moves  
     - [x] Add view to allow the traded Pokemon's moveset to be chosen (all 4 moves) allowing no move as an option  
     - [x] Add view to allow the traded Pokemon's moveset to be chosen (all 4 moves) allowing no move as an option  
     - [ ] Find a way to get faster scrolling through the move select submenu  
     - [ ] Find a way to get faster scrolling through the move select submenu  
@@ -24,7 +24,7 @@
     - [x] Support setting pokemon type(s)  
     - [x] Support setting pokemon type(s)  
     - [ ] Implement a save/revert to default workflow on the select types scene  
     - [ ] Implement a save/revert to default workflow on the select types scene  
   - Trade  
   - Trade  
-    - [ ] Investigate Trade screens not always blinking  
+    - [x] Investigate Trade screens not always blinking  
   - UI  
   - UI  
     - [ ] Find a way to line up submenu items so the main menu looks cleaner  
     - [ ] Find a way to line up submenu items so the main menu looks cleaner  
       - They currently _mostly_ line up thanks to some manual spacing, but tabs don't appear to be supported to force that alignment  
       - They currently _mostly_ line up thanks to some manual spacing, but tabs don't appear to be supported to force that alignment  
@@ -32,15 +32,16 @@
 - Documentation  
 - Documentation  
   - [x] Add images for the level selection screen, stats selection screen, and move selection screens as per the original README  
   - [x] Add images for the level selection screen, stats selection screen, and move selection screens as per the original README  
 - Codebase  
 - Codebase  
-  - [ ] Reimplement Logging calls  
+  - [x] Reimplement Logging calls  
   - [ ] Clean up the codebase as it is now, there are a lot of optimizations in speed and code complexity that can be made, especially in added code in pokemon_app and maybe some code reduction/reuse in scenes  
   - [ ] Clean up the codebase as it is now, there are a lot of optimizations in speed and code complexity that can be made, especially in added code in pokemon_app and maybe some code reduction/reuse in scenes  
   - [ ] Consider using a single View in main app struct and only allocate a view as needed to reduce memory footprint  
   - [ ] Consider using a single View in main app struct and only allocate a view as needed to reduce memory footprint  
 
 
 - Future Wants  
 - Future Wants  
-  - [ ] Trading to Gen II games with both Gen I and Gen II pokemon  
+  - [ ] Trading to Gen II games with both Gen I and Gen II Pokemon  
   - [ ] Enable IR mystery gift usage in Gen II using Flipper  
   - [ ] Enable IR mystery gift usage in Gen II using Flipper  
-  - [ ] Be able to set up mutiple pokemon to be able to trade more than one per trip to trade center  
-  - [ ] Be able to trade back and forth for e.g. trading a pokemon that evolves only when traded  
-  - [ ] Would Separateing out link cable states result in a cleaner API?  
+  - [ ] Be able to set up multiple Pokemon to be able to trade more than one per trip to trade center  
+  - [x] Be able to trade back and forth for e.g. trading a Pokemon that evolves only when traded  
+  - [x] Would Separating out link cable states result in a cleaner API?  
   - [ ] Implement some simple logic to be able to "battle" the Flipper?  
   - [ ] Implement some simple logic to be able to "battle" the Flipper?  
-  - [ ] There was a suggestion to be able to trade in a pokemon to harvest OT name and ID on the flipper and set it to that.  
+  - [ ] There was a suggestion to be able to trade in a Pokemon to harvest OT name and ID on the flipper and set it to that.  
+  - [ ] Ability to save Pokemon to SD card. Either created on, or traded to, the Flipper app.  

+ 1 - 1
application.fam

@@ -5,7 +5,7 @@ App(
     entry_point="pokemon_app",
     entry_point="pokemon_app",
     requires=["gui"],
     requires=["gui"],
     stack_size=2 * 1024,
     stack_size=2 * 1024,
-    fap_version=[1,3],
+    fap_version=[1,4],
     fap_category="GPIO",
     fap_category="GPIO",
     fap_icon="pokemon_10px.png",
     fap_icon="pokemon_10px.png",
     fap_icon_assets="assets",
     fap_icon_assets="assets",

+ 11 - 6
changelog.md

@@ -1,16 +1,21 @@
 # Changelog - Patch Notes
 # Changelog - Patch Notes
 
 
-## Version 1.2.3
+## Version 1.4
+- **Bug Fixes:** More robust trade logic fixes issues with names, remove ability to use numbers in Pokemon/Trainer names as the game itself will not allow that, fix trade animation not always being animated, make FAP icon 1bpp.
+- **Add Features:** Implement trade patch list that Game Boy expects and uses, add ability to return to main menu to modify a Pokemon traded to the Flipper and re-enter trade without the Game Boy needing to power cycle and re-connect through the Link Club, add back debug logging.
+- **Trade Refactor:** Eliminate extraneous code, improve robustness of state tracking during trades, isolate Trade's scope to the compilation unit, add notes on exchanged bytes during a trade, improve timing of animation during trade, reduce time spent in interrupt context, follow same setup/hold times for data exchange that the Game Boy uses, reduce use of magic numbers, clean up and improve code tracking real world time
+
+## Version 1.3
 - **Refactor and UI cleanup:** Convert to Flipper Zero UI modules for simpler interface, reduce binary size.
 - **Refactor and UI cleanup:** Convert to Flipper Zero UI modules for simpler interface, reduce binary size.
-- **Add Features:** Add ability to set custom pokemon nickname or default, add ability to set OT name and ID, add ability to select pokemon type(s). Note that, an evolution as well as a couple of different attacks will cause this to be overwritten with the pokemon's default values.
-- **Bug Fixes:** Fix strange issue with exp gain causing traded pokemon to de-level and result in incorrect stats.
+- **Add Features:** Add ability to set custom Pokemon nickname or default, add ability to set OT name and ID, add ability to select Pokemon type(s). Note that, an evolution as well as a couple of different attacks will cause this to be overwritten with the Pokemon's default values.
+- **Bug Fixes:** Fix strange issue with exp gain causing traded Pokemon to de-level and result in incorrect stats.
 
 
 ## Version 1.2.2
 ## Version 1.2.2
-- **Extended Functionality:** Add support to set level, select moves, set up EV/IV to a few predefined configurations, set up stats based on level and EV/IV settings, set nickname to default pokemon name.
+- **Extended Functionality:** Add support to set level, select moves, set up EV/IV to a few predefined configurations, set up stats based on level and EV/IV settings, set nickname to default Pokemon name.
 
 
 ## Version 1.2.1
 ## Version 1.2.1
-- **Add github action to build**
+- **Add GitHub action to build**
 
 
 ## Version 1.2.0
 ## Version 1.2.0
-- **Cleanup data structs:** This refactors the main data blocks for defining pokemon, the icon, their species/hex value, as well as the large trade array in to more human friendly structs. Laying some groundwork to be able to adjust pokemon details pre-trade by @kbembedded .
+- **Cleanup data structs:** This refactors the main data blocks for defining Pokemon, the icon, their species/hex value, as well as the large trade array in to more human friendly structs. Laying some groundwork to be able to adjust Pokemon details pre-trade by @kbembedded .
 - **Bug Fixes:** Fix furi crash, Fixes #9 by @kbembedded .
 - **Bug Fixes:** Fix furi crash, Fixes #9 by @kbembedded .

BIN
pokemon_10px.png


+ 56 - 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 pokemon_named_list_get_num_elements(const NamedList* list) {
     int i;
     int i;
 
 
     for(i = 0;; i++) {
     for(i = 0;; i++) {
         if(list[i].name == NULL) return 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) {
 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;
         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.
      * Could be surprising at runtime.
      */
      */
     return 0;
     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;
         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.
      * this could be confusing/problematic at runtime.
      */
      */
     return list[0].name;
     return list[0].name;
@@ -1912,14 +1918,12 @@ void pokemon_trade_block_set_default_name(char* dest, PokemonFap* pokemon_fap, s
     /* Walk through the default name, toupper() each character, encode it, and
     /* Walk through the default name, toupper() each character, encode it, and
      * then write that to the same position in the trade_block.
      * 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++) {
     for(i = 0; i < 11; i++) {
         pokemon_fap->trade_block->nickname[0].str[i] = pokemon_char_to_encoded(
         pokemon_fap->trade_block->nickname[0].str[i] = pokemon_char_to_encoded(
             toupper(pokemon_fap->pokemon_table[pokemon_fap->curr_pokemon].name[i]));
             toupper(pokemon_fap->pokemon_table[pokemon_fap->curr_pokemon].name[i]));
         buf[i] = toupper(pokemon_fap->pokemon_table[pokemon_fap->curr_pokemon].name[i]);
         buf[i] = toupper(pokemon_fap->pokemon_table[pokemon_fap->curr_pokemon].name[i]);
     }
     }
+    FURI_LOG_D(TAG, "[app] Set default nickname");
 
 
     if(dest != NULL) {
     if(dest != NULL) {
         strncpy(dest, buf, n);
         strncpy(dest, buf, n);
@@ -1972,6 +1976,8 @@ void pokemon_trade_block_recalculate_stats_from_level(PokemonFap* pokemon_fap) {
 
 
     pkmn->level_again = level;
     pkmn->level_again = level;
     UINT32_TO_EXP(experience, pkmn->exp);
     UINT32_TO_EXP(experience, pkmn->exp);
+    FURI_LOG_D(TAG, "[app] Set pkmn level %d", level);
+    FURI_LOG_D(TAG, "[app] Set pkmn exp %d", (int)experience);
 
 
     /* Generate STATEXP */
     /* Generate STATEXP */
     switch(curr_stats) {
     switch(curr_stats) {
@@ -1988,6 +1994,7 @@ void pokemon_trade_block_recalculate_stats_from_level(PokemonFap* pokemon_fap) {
         break;
         break;
     }
     }
 
 
+    FURI_LOG_D(TAG, "[app] EVs set to %d", stat);
     stat = __builtin_bswap16(stat);
     stat = __builtin_bswap16(stat);
 
 
     pkmn->hp_ev = stat;
     pkmn->hp_ev = stat;
@@ -2006,33 +2013,50 @@ void pokemon_trade_block_recalculate_stats_from_level(PokemonFap* pokemon_fap) {
                    ((special_iv & 0x0f));
                    ((special_iv & 0x0f));
         hp_iv = (pkmn->iv & 0xAA) >> 4;
         hp_iv = (pkmn->iv & 0xAA) >> 4;
     }
     }
+    FURI_LOG_D(
+        TAG,
+        "[app] atk_iv %d, def_iv %d, spd_iv %d, spc_iv %d, hp_iv %d",
+        atk_iv,
+        def_iv,
+        spd_iv,
+        special_iv,
+        hp_iv);
 
 
     /* Calculate HP */
     /* Calculate HP */
     // https://bulbapedia.bulbagarden.net/wiki/Stat#Generations_I_and_II
     // https://bulbapedia.bulbagarden.net/wiki/Stat#Generations_I_and_II
     stat = floor((((2 * (table->base_hp + hp_iv)) + floor(sqrt(pkmn->hp_ev) / 4)) * level) / 100) +
     stat = floor((((2 * (table->base_hp + hp_iv)) + floor(sqrt(pkmn->hp_ev) / 4)) * level) / 100) +
            (level + 10);
            (level + 10);
+    FURI_LOG_D(TAG, "[app] HP set to %d", stat);
     pkmn->hp = __builtin_bswap16(stat);
     pkmn->hp = __builtin_bswap16(stat);
     pkmn->max_hp = pkmn->hp;
     pkmn->max_hp = pkmn->hp;
 
 
     /* Calculate ATK, DEF, SPD, SP */
     /* Calculate ATK, DEF, SPD, SP */
+    /* TODO: these all use the same calculations, could put the stats in a sub-array and iterate
+     * through each element in order rather than having to repeat the code. IVs would also need
+     * to be in a similar array.
+     **/
     // https://bulbapedia.bulbagarden.net/wiki/Stat#Generations_I_and_II
     // https://bulbapedia.bulbagarden.net/wiki/Stat#Generations_I_and_II
     stat =
     stat =
         floor((((2 * (table->base_atk + atk_iv)) + floor(sqrt(pkmn->atk_ev) / 4)) * level) / 100) +
         floor((((2 * (table->base_atk + atk_iv)) + floor(sqrt(pkmn->atk_ev) / 4)) * level) / 100) +
         5;
         5;
+    FURI_LOG_D(TAG, "[app] ATK set to %d", stat);
     pkmn->atk = __builtin_bswap16(stat);
     pkmn->atk = __builtin_bswap16(stat);
     stat =
     stat =
         floor((((2 * (table->base_def + def_iv)) + floor(sqrt(pkmn->def_ev) / 4)) * level) / 100) +
         floor((((2 * (table->base_def + def_iv)) + floor(sqrt(pkmn->def_ev) / 4)) * level) / 100) +
         5;
         5;
+    FURI_LOG_D(TAG, "[app] DEF set to %d", stat);
     pkmn->def = __builtin_bswap16(stat);
     pkmn->def = __builtin_bswap16(stat);
     stat =
     stat =
         floor((((2 * (table->base_spd + spd_iv)) + floor(sqrt(pkmn->spd_ev) / 4)) * level) / 100) +
         floor((((2 * (table->base_spd + spd_iv)) + floor(sqrt(pkmn->spd_ev) / 4)) * level) / 100) +
         5;
         5;
+    FURI_LOG_D(TAG, "[app] SPD set to %d", stat);
     pkmn->spd = __builtin_bswap16(stat);
     pkmn->spd = __builtin_bswap16(stat);
     stat = floor(
     stat = floor(
                (((2 * (table->base_special + special_iv)) + floor(sqrt(pkmn->special_ev) / 4)) *
                (((2 * (table->base_special + special_iv)) + floor(sqrt(pkmn->special_ev) / 4)) *
                 level) /
                 level) /
                100) +
                100) +
            5;
            5;
+    FURI_LOG_D(TAG, "[app] SPC set to %d", stat);
     pkmn->special = __builtin_bswap16(stat);
     pkmn->special = __builtin_bswap16(stat);
 }
 }
 
 
@@ -2045,15 +2069,24 @@ void pokemon_trade_block_recalculate(PokemonFap* pokemon_fap) {
     /* Set current pokemon to the trade structure */
     /* Set current pokemon to the trade structure */
     pkmn->index = table->index;
     pkmn->index = table->index;
     pokemon_fap->trade_block->party_members[0] = table->index;
     pokemon_fap->trade_block->party_members[0] = table->index;
+    FURI_LOG_D(TAG, "[app] Set %s in trade block", table->name);
 
 
     /* Set current pokemon's moves to the trade structure */
     /* Set current pokemon's moves to the trade structure */
     for(i = 0; i < 4; i++) {
     for(i = 0; i < 4; i++) {
         pkmn->move[i] = table->move[i];
         pkmn->move[i] = table->move[i];
+        FURI_LOG_D(
+            TAG,
+            "[app] Set %s in trade block",
+            pokemon_named_list_get_name_from_index(pokemon_fap->move_list, pkmn->move[i]));
     }
     }
 
 
     /* Set current pokemon's types to the trade structure */
     /* Set current pokemon's types to the trade structure */
     for(i = 0; i < 2; i++) {
     for(i = 0; i < 2; i++) {
         pkmn->type[i] = table->type[i];
         pkmn->type[i] = table->type[i];
+        FURI_LOG_D(
+            TAG,
+            "[app] Set %s in trade block",
+            pokemon_named_list_get_name_from_index(pokemon_fap->type_list, pkmn->type[i]));
     }
     }
 
 
     pokemon_trade_block_recalculate_stats_from_level(pokemon_fap);
     pokemon_trade_block_recalculate_stats_from_level(pokemon_fap);
@@ -2087,9 +2120,11 @@ static TradeBlock* trade_block_alloc(void) {
     /* OT trainer ID# */
     /* OT trainer ID# */
     trade->party[0].ot_id = __builtin_bswap16(42069);
     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 */
     /* Set up initial level */
     trade->party[0].level = 2;
     trade->party[0].level = 2;
@@ -2150,8 +2185,12 @@ PokemonFap* pokemon_alloc() {
         pokemon_fap->view_dispatcher, AppViewSelectPokemon, pokemon_fap->select_view);
         pokemon_fap->view_dispatcher, AppViewSelectPokemon, pokemon_fap->select_view);
 
 
     // Trade 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;
     return pokemon_fap;
 }
 }
@@ -2163,8 +2202,8 @@ void free_app(PokemonFap* pokemon_fap) {
     view_dispatcher_remove_view(pokemon_fap->view_dispatcher, AppViewSelectPokemon);
     view_dispatcher_remove_view(pokemon_fap->view_dispatcher, AppViewSelectPokemon);
     select_pokemon_free(pokemon_fap);
     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);
     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 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 {
 struct pokemon_fap {
     ViewDispatcher* view_dispatcher;
     ViewDispatcher* view_dispatcher;
 
 
     /* View ports for each of the application's steps */
     /* View ports for each of the application's steps */
     View* select_view;
     View* select_view;
-    View* trade_view;
+    void* trade;
 
 
     /* Scene manager */
     /* Scene manager */
     SceneManager* scene_manager;
     SceneManager* scene_manager;
@@ -105,6 +95,8 @@ typedef enum {
     AppViewExitConfirm,
     AppViewExitConfirm,
 } AppView;
 } 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_num_elements(const NamedList* list);
 
 
 int pokemon_named_list_get_list_pos_from_index(const NamedList* list, uint8_t index);
 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"
 #include "pokemon_char_encode.h"
 
 
-/* XXX Current text_input module only offers alnum and space input */
 char pokemon_char_to_encoded(int byte) {
 char pokemon_char_to_encoded(int byte) {
     switch(byte) {
     switch(byte) {
     case 'A':
     case 'A':

+ 3 - 0
pokemon_data.h

@@ -13,6 +13,7 @@
  * GB/Z80. e.g. a uint16_t value of 0x2c01 translates to 0x012c.
  * 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.
  * Need to use __builtin_bswap16(val) to switch between Flipper and Pokemon.
  */
  */
+/* This is 44 bytes in memory */
 struct __attribute__((__packed__)) pokemon_structure {
 struct __attribute__((__packed__)) pokemon_structure {
     uint8_t index;
     uint8_t index;
     uint16_t hp; // Calculated from level
     uint16_t hp; // Calculated from level
@@ -50,7 +51,9 @@ struct __attribute__((__packed__)) name {
     unsigned char str[11];
     unsigned char str[11];
 };
 };
 
 
+/* This is 415 bytes in memory/transmitted */
 struct __attribute__((__packed__)) trade_data_block {
 struct __attribute__((__packed__)) trade_data_block {
+    /* TODO: Change this to use struct name above */
     unsigned char trainer_name[11];
     unsigned char trainer_name[11];
     uint8_t party_cnt;
     uint8_t party_cnt;
     /* Only the first pokemon is ever used even though there are 7 bytes here.
     /* 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;
         rc = false;
     } else {
     } else {
         pokemon_fap->trade_block->party[0].level = level_val;
         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;
     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) {
 static void select_level_input_callback(void* context) {
     PokemonFap* pokemon_fap = (PokemonFap*)context;
     PokemonFap* pokemon_fap = (PokemonFap*)context;
 
 
+    /* Recalculate all stats from updated level */
     pokemon_trade_block_recalculate_stats_from_level(pokemon_fap);
     pokemon_trade_block_recalculate_stats_from_level(pokemon_fap);
     scene_manager_previous_scene(pokemon_fap->scene_manager);
     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);
     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);
     submenu_reset(pokemon_fap->submenu);
 
 
     snprintf(
     snprintf(

+ 6 - 0
scenes/pokemon_move.c

@@ -15,6 +15,12 @@ static void select_move_selected_callback(void* context, uint32_t index) {
     } else {
     } else {
         pokemon_fap->trade_block->party[0].move[move] = (uint8_t)index;
         pokemon_fap->trade_block->party[0].move[move] = (uint8_t)index;
     }
     }
+    FURI_LOG_D(
+        TAG,
+        "[move] Set move %s to %d",
+        pokemon_named_list_get_name_from_index(
+            pokemon_fap->move_list, pokemon_fap->trade_block->party[0].move[move]),
+        (int)move);
 
 
     /* Move back to move menu */
     /* Move back to move menu */
     scene_manager_search_and_switch_to_previous_scene(pokemon_fap->scene_manager, SelectMoveScene);
     scene_manager_search_and_switch_to_previous_scene(pokemon_fap->scene_manager, SelectMoveScene);

+ 10 - 3
scenes/pokemon_nickname.c

@@ -22,6 +22,7 @@ static char name_buf[11];
 static bool select_nickname_input_validator(const char* text, FuriString* error, void* context) {
 static bool select_nickname_input_validator(const char* text, FuriString* error, void* context) {
     PokemonFap* pokemon_fap = (PokemonFap*)context;
     PokemonFap* pokemon_fap = (PokemonFap*)context;
     bool rc = true;
     bool rc = true;
+    unsigned int i;
 
 
     if(text[0] == '\0') {
     if(text[0] == '\0') {
         /* Get the pokemon's name and populate our buffer with it */
         /* Get the pokemon's name and populate our buffer with it */
@@ -30,15 +31,21 @@ static bool select_nickname_input_validator(const char* text, FuriString* error,
         return true;
         return true;
     }
     }
 
 
-    if(rc == false) {
-        furi_string_printf(error, "Some error?");
-    } else {
+    for(i = 0; i < strlen(text); i++) {
+        if(isdigit((unsigned int)text[i])) {
+            furi_string_printf(error, "Name cannot\ncontain\nnumbers!");
+            rc = false;
+        }
+    }
+
+    if(rc == true) {
         /* Clear existing nickname in trade block*/
         /* Clear existing nickname in trade block*/
         memset(pokemon_fap->trade_block->nickname, TERM_, sizeof(struct name));
         memset(pokemon_fap->trade_block->nickname, TERM_, sizeof(struct name));
 
 
         /* Encode string to nickname */
         /* Encode string to nickname */
         pokemon_str_to_encoded_array(
         pokemon_str_to_encoded_array(
             (uint8_t*)pokemon_fap->trade_block->nickname, (char*)text, strlen(text));
             (uint8_t*)pokemon_fap->trade_block->nickname, (char*)text, strlen(text));
+        FURI_LOG_D(TAG, "[nickname] Set nickname to %s", text);
     }
     }
 
 
     return rc;
     return rc;

+ 2 - 0
scenes/pokemon_ot_id.c

@@ -36,6 +36,8 @@ static bool select_ot_id_input_validator(const char* text, FuriString* error, vo
         pokemon_fap->trade_block->party[0].ot_id = ot_id_16;
         pokemon_fap->trade_block->party[0].ot_id = ot_id_16;
     }
     }
 
 
+    FURI_LOG_D(TAG, "[ot_id] Set OT ID to %05d", (uint16_t)ot_id);
+
     return rc;
     return rc;
 }
 }
 
 

+ 4 - 7
scenes/pokemon_ot_name.c

@@ -15,26 +15,23 @@ static bool select_ot_name_input_validator(const char* text, FuriString* error,
     bool rc = true;
     bool rc = true;
     unsigned int i;
     unsigned int i;
 
 
-    // XXX If no pokemon name, use default. How TF to have text input handle that?
     // OT name is 7 chars max on gen 1, so only take that and then fill the rest of the 11 bytes with term
     // OT name is 7 chars max on gen 1, so only take that and then fill the rest of the 11 bytes with term
 
 
     for(i = 0; i < sizeof(ot_name_buf); i++) {
     for(i = 0; i < sizeof(ot_name_buf); i++) {
-        if(!isalnum((unsigned int)text[i])) {
-            if(text[i] == '\0') break;
+        if(isdigit((unsigned int)text[i])) {
+            furi_string_printf(error, "Name cannot\ncontain\nnumbers!");
             rc = false;
             rc = false;
-            break;
         }
         }
     }
     }
 
 
-    if(rc == false) {
-        furi_string_printf(error, "Some error?");
-    } else {
+    if(rc == true) {
         /* Clear existing OT Name in trade block*/
         /* Clear existing OT Name in trade block*/
         memset(pokemon_fap->trade_block->ot_name, TERM_, sizeof(struct name));
         memset(pokemon_fap->trade_block->ot_name, TERM_, sizeof(struct name));
 
 
         /* Encode string to OT Name */
         /* Encode string to OT Name */
         pokemon_str_to_encoded_array(
         pokemon_str_to_encoded_array(
             (uint8_t*)pokemon_fap->trade_block->ot_name, (char*)text, strlen(text));
             (uint8_t*)pokemon_fap->trade_block->ot_name, (char*)text, strlen(text));
+        FURI_LOG_D(TAG, "[ot_name] Set OT name to %s", text);
     }
     }
 
 
     return rc;
     return rc;

+ 2 - 0
scenes/pokemon_stats.c

@@ -19,6 +19,8 @@ static void select_stats_selected_callback(void* context, uint32_t index) {
 
 
     pokemon_trade_block_recalculate_stats_from_level(pokemon_fap);
     pokemon_trade_block_recalculate_stats_from_level(pokemon_fap);
 
 
+    FURI_LOG_D(TAG, "[stats] Set stats to %s", stats_text[index]);
+
     scene_manager_previous_scene(pokemon_fap->scene_manager);
     scene_manager_previous_scene(pokemon_fap->scene_manager);
 }
 }
 
 

+ 12 - 0
scenes/pokemon_type.c

@@ -18,6 +18,12 @@ static void select_type_1_callback(VariableItem* item) {
 
 
     variable_item_set_current_value_text(item, pokemon_fap->type_list[index].name);
     variable_item_set_current_value_text(item, pokemon_fap->type_list[index].name);
     pokemon_fap->trade_block->party[0].type[0] = pokemon_fap->type_list[index].index;
     pokemon_fap->trade_block->party[0].type[0] = pokemon_fap->type_list[index].index;
+
+    FURI_LOG_D(
+        TAG,
+        "[type] Set type1 to %s",
+        pokemon_named_list_get_name_from_index(
+            pokemon_fap->type_list, pokemon_fap->type_list[index].index));
 }
 }
 
 
 static void select_type_2_callback(VariableItem* item) {
 static void select_type_2_callback(VariableItem* item) {
@@ -26,6 +32,12 @@ static void select_type_2_callback(VariableItem* item) {
 
 
     variable_item_set_current_value_text(item, pokemon_fap->type_list[index].name);
     variable_item_set_current_value_text(item, pokemon_fap->type_list[index].name);
     pokemon_fap->trade_block->party[0].type[1] = pokemon_fap->type_list[index].index;
     pokemon_fap->trade_block->party[0].type[1] = pokemon_fap->type_list[index].index;
+
+    FURI_LOG_D(
+        TAG,
+        "[type] Set type2 to %s",
+        pokemon_named_list_get_name_from_index(
+            pokemon_fap->type_list, pokemon_fap->type_list[index].index));
 }
 }
 
 
 void select_type_scene_on_exit(void* context) {
 void select_type_scene_on_exit(void* context) {

+ 1 - 0
views/select_pokemon.c

@@ -49,6 +49,7 @@ static bool select_pokemon_input_callback(InputEvent* event, void* context) {
     /* Advance to next view with the selected pokemon */
     /* Advance to next view with the selected pokemon */
     case InputKeyOk:
     case InputKeyOk:
         pokemon_fap->curr_pokemon = selected_pokemon;
         pokemon_fap->curr_pokemon = selected_pokemon;
+        FURI_LOG_D(TAG, "[Select] Selected %s", pokemon_fap->pokemon_table[selected_pokemon].name);
         scene_manager_previous_scene(pokemon_fap->scene_manager);
         scene_manager_previous_scene(pokemon_fap->scene_manager);
         consumed = true;
         consumed = true;
         break;
         break;

+ 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.h>
+#include <furi_hal.h>
 
 
 #include <gui/view.h>
 #include <gui/view.h>
 #include <pokemon_icons.h>
 #include <pokemon_icons.h>
 
 
 #include "../pokemon_app.h"
 #include "../pokemon_app.h"
+#include "trade_patch_list.h"
 
 
 #define GAME_BOY_CLK gpio_ext_pb2
 #define GAME_BOY_CLK gpio_ext_pb2
 #define GAME_BOY_SI gpio_ext_pc3
 #define GAME_BOY_SI gpio_ext_pc3
@@ -20,10 +101,23 @@
 #define ITEM_2_SELECTED 0xD5
 #define ITEM_2_SELECTED 0xD5
 #define ITEM_3_SELECTED 0xD6
 #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_MASTER 0x01
 #define PKMN_SLAVE 0x02
 #define PKMN_SLAVE 0x02
 #define PKMN_CONNECTED 0x60
 #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
 #define PKMN_ACTION 0x60
 
 
@@ -31,48 +125,75 @@
 #define PKMN_COLOSSEUM ITEM_2_SELECTED
 #define PKMN_COLOSSEUM ITEM_2_SELECTED
 #define PKMN_BREAK_LINK ITEM_3_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 {
 struct trade_model {
-    bool trading;
-    bool connected;
     render_gameboy_state_t gameboy_status;
     render_gameboy_state_t gameboy_status;
+    bool ledon; // Controls the blue LED during trade
     uint8_t curr_pokemon;
     uint8_t curr_pokemon;
     const PokemonTable* pokemon_table;
     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_frame(canvas, 0, 0, 128, 64);
     canvas_draw_icon(canvas, 1, 21, &I_Connect_me_62x31);
     canvas_draw_icon(canvas, 1, 21, &I_Connect_me_62x31);
     canvas_draw_icon(canvas, 0, 53, &I_Background_128x11);
     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_icon(canvas, 8, 2, &I_Space_65x18);
     canvas_draw_str(canvas, 18, 13, "Connect GB");
     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_frame(canvas, 0, 0, 128, 64);
     canvas_draw_icon(canvas, 1, 21, &I_Connected_62x31);
     canvas_draw_icon(canvas, 1, 21, &I_Connected_62x31);
     canvas_draw_icon(canvas, 0, 53, &I_Background_128x11);
     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!");
     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
 /* Get the response byte from the link partner, updating the connection
  * state if needed.
  * 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:
     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;
         ret = PKMN_CONNECTED;
         break;
         break;
     case PKMN_MASTER:
     case PKMN_MASTER:
@@ -190,7 +324,11 @@ byte getConnectResponse(byte in) {
         ret = PKMN_BLANK;
         ret = PKMN_BLANK;
         break;
         break;
     default:
     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;
         ret = PKMN_BREAK_LINK;
         break;
         break;
     }
     }
@@ -198,142 +336,286 @@ byte getConnectResponse(byte in) {
     return ret;
     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:
     case PKMN_CONNECTED:
         response = PKMN_CONNECTED;
         response = PKMN_CONNECTED;
         break;
         break;
     case PKMN_TRADE_CENTRE:
     case PKMN_TRADE_CENTRE:
-        connection_state = TRADE_CENTRE;
+        with_view_model(
+            trade->view,
+            struct trade_model * model,
+            { model->gameboy_status = GAMEBOY_READY; },
+            false);
         break;
         break;
     case PKMN_COLOSSEUM:
     case PKMN_COLOSSEUM:
-        connection_state = COLOSSEUM;
+        with_view_model(
+            trade->view,
+            struct trade_model * model,
+            { model->gameboy_status = GAMEBOY_COLOSSEUM; },
+            false);
         break;
         break;
     case PKMN_BREAK_LINK:
     case PKMN_BREAK_LINK:
     case PKMN_MASTER:
     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;
         response = PKMN_BREAK_LINK;
         break;
         break;
     default:
     default:
-        response = in;
+        response = trade->in_data;
         break;
         break;
     }
     }
 
 
     return response;
     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++;
             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;
         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;
         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;
             counter = 0;
-            trade_centre_state = SENDING_RANDOM_DATA;
         }
         }
+
         break;
         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++;
             counter++;
         }
         }
-        break;
 
 
-    case WAITING_TO_SEND_DATA:
-        if((in & 0xF0) != 0xF0) {
+        if(counter == 6) {
             counter = 0;
             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++;
         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;
         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:
     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;
         break;
 
 
+    /* Handle the Game Boy accepting or rejecting a trade deal */
     case TRADE_CONFIRMATION:
     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;
         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;
         break;
 
 
@@ -342,175 +624,186 @@ byte getTradeCentreResponse(byte in, void* context) {
         break;
         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;
     return send;
 }
 }
 
 
 void transferBit(void* context) {
 void transferBit(void* context) {
-    PokemonFap* pokemon_fap = (PokemonFap*)context;
     furi_assert(context);
     furi_assert(context);
-    bool connected;
-    bool trading;
+
+    struct trade_ctx* trade = (struct trade_ctx*)context;
+    render_gameboy_state_t status;
 
 
     with_view_model(
     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;
             break;
-        case CONNECTED:
-            connected = true;
-            out_data = getMenuResponse(in_data);
+        case GAMEBOY_CONN_TRUE:
+            trade->out_data = getMenuResponse(trade);
             break;
             break;
-        case TRADE_CENTRE:
-            out_data = getTradeCentreResponse(in_data, pokemon_fap);
+        case GAMEBOY_COLOSSEUM:
+            trade->out_data = trade->in_data;
             break;
             break;
+        /* Every other state is trade related */
         default:
         default:
-            out_data = in_data;
+            trade->out_data = getTradeCentreResponse(trade);
             break;
             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) {
 void input_clk_gameboy(void* context) {
     furi_assert(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) {
 void trade_enter_callback(void* context) {
-    PokemonFap* pokemon_fap = (PokemonFap*)context;
     furi_assert(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)
     // B3 (Pin6) / SO (2)
     furi_hal_gpio_write(&GAME_BOY_SO, false);
     furi_hal_gpio_write(&GAME_BOY_SO, false);
     furi_hal_gpio_init(&GAME_BOY_SO, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
     furi_hal_gpio_init(&GAME_BOY_SO, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
     // B2 (Pin5) / SI (3)
     // B2 (Pin5) / SI (3)
     furi_hal_gpio_write(&GAME_BOY_SI, false);
     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)
     // // 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_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) {
 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) {
 void trade_exit_callback(void* context) {
-    PokemonFap* pokemon_fap = (PokemonFap*)context;
     furi_assert(context);
     furi_assert(context);
-    procesing = false;
+
+    struct trade_ctx* trade = (struct trade_ctx*)context;
+
     furi_hal_light_set(LightGreen, 0x00);
     furi_hal_light_set(LightGreen, 0x00);
     furi_hal_light_set(LightBlue, 0x00);
     furi_hal_light_set(LightBlue, 0x00);
     furi_hal_light_set(LightRed, 0x00);
     furi_hal_light_set(LightRed, 0x00);
+
     /* Stop the timer, and deallocate it as the enter callback allocates it on entry */
     /* 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 <gui/view.h>
 #include "../pokemon_app.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 */
 #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 */