MX 2 лет назад
Родитель
Сommit
f720fbb727

+ 25 - 9
README.md

@@ -20,9 +20,23 @@ This is a Pokemon exchange application from Flipper Zero to Game Boy [(Generacti
 
 
 It currently trades a Pokemon based on your choice of Pokemon, Level, Stats and 4 Moves.
 It currently trades a Pokemon based on your choice of Pokemon, Level, Stats and 4 Moves.
 
 
+## Hardware Interface
+The Game Boy is connected to the Flipper Zero's GPIO pins via a GBC style Game Link Cable. The [Flipper GB Link module](https://www.tindie.com/products/kbembedded/game-link-gpio-module-for-flipper-zero-game-boy/) is an easy way to connect a Game Boy via a Game Link Cable to the Flipper Zero.
+<p align='center'>
+<a href="https://www.tindie.com/stores/kbembedded/?ref=offsite_badges&utm_source=sellers_kbembedded&utm_medium=badges&utm_campaign=badge_large"><img src="https://d2ss6ovg47m0r5.cloudfront.net/badges/tindie-larges.png" alt="I sell on Tindie" width="200" height="104"></a>
+</p>
+
+Additionally, the [MALVEKE - GAME BOY Tools for Flipper Zero](https://www.tindie.com/products/efuentealba/malveke-game-boy-tools-for-flipper-zero/) is supported by this tool.
+
+<p align='center'>
+<a href="https://www.tindie.com/stores/efuentealba/?ref=offsite_badges&utm_source=sellers_efuentealba&utm_medium=badges&utm_campaign=badge_large"><img src="https://d2ss6ovg47m0r5.cloudfront.net/badges/tindie-larges.png" alt="I sell on Tindie" width="200" height="104"></a>
+</p>
+
+Details on the hardware interface, as well as how to create your own adapter board, can be found in the [How Does It Work](#how-does-it-work) section below.
+
 ## 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.
@@ -48,6 +62,7 @@ And use [**qFlipper**](https://flipperzero.one/update) to copy the generated **p
 
 
 These instructions assume that you are starting at the Flipper Zero desktop. Otherwise, press the Back button until you are at the desktop.
 These instructions assume that you are starting at the Flipper Zero desktop. Otherwise, press the Back button until you are at the desktop.
 
 
+- If using a MALVEKE board, plug it in to the GPIO header now. The app will auto-detect and select the correct pinout to support the MALVEKE EXT1 interface. If using the Flipper GB Link board, or any other pinout, they can be connected to the Flipper Zero now, or at any point in the future.
 - Press the `OK` button on the Flipper to open the main menu.
 - Press the `OK` button on the Flipper to open the main menu.
 - Choose `Applications` from the menu.
 - Choose `Applications` from the menu.
 - Choose `GPIO` from the submenu.
 - Choose `GPIO` from the submenu.
@@ -87,7 +102,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 +130,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 +141,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 +235,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'>
@@ -252,7 +269,7 @@ You can learn more about it in the following video. [**Analyzing the Different V
 
 
 ## Board for Flipper Zero with PortData EXT Link.
 ## Board for Flipper Zero with PortData EXT Link.
 
 
-For the Flipper Zero board, a [PortData EXT Link](https://es.aliexpress.com/item/1005004116983895.html) and a 2x8  [prototype board](https://es.aliexpress.com/item/32478242317.html) were used.
+For the Flipper Zero board, a [PortData EXT Link](https://s.click.aliexpress.com/e/_Dm3EqlR) and a 2x8  [prototype board](https://s.click.aliexpress.com/e/_DETrjpL) were used.
 
 
 <p align='center'>
 <p align='center'>
 <br />
 <br />
@@ -322,14 +339,13 @@ For each image, the color `#aaa` was transformed to `#fff` so that Flipper Zero
 - Game Boy Advance (GBA)
 - Game Boy Advance (GBA)
 
 
 ## Contributors
 ## Contributors
-<a href="https://github.com/EstebanFuentealba/Flipper-Zero-Game-Boy-Pokemon-Trading/">EstebanFuentealba</a><br />
-<a href="https://github.com/R4g3D/Flipper-Zero-Game-Boy-Pokemon-Trading/">R4g3D</a><br />
-<a href="https://github.com/kbembedded/Flipper-Zero-Game-Boy-Pokemon-Trading/">kbembedded</a>
+[![Contributors](https://contrib.rocks/image?repo=EstebanFuentealba/Flipper-Zero-Game-Boy-Pokemon-Trading)](https://github.com/EstebanFuentealba/Flipper-Zero-Game-Boy-Pokemon-Trading/graphs/contributors)
+
 
 
 ## 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)>)

+ 41 - 0
README_catalog.md

@@ -0,0 +1,41 @@
+# 🐬 Flipper Zero - Pokemon Trading in Game Boy
+
+## Introduction
+
+Now supports MALVEKE board!
+
+This is a Pokemon exchange application from Flipper Zero to Game Boy (Generación I). Flipper Zero emulates a "Slave" Game Boy connected to a Game Link Cable to be able to exchange any Pokemon from the First Generation (Red, Blue, Yellow) to a real Game Boy.
+
+If a MALVEKE board is plugged in to GPIO before starting the app, the app will default to using the MALVEKE EXT1 interface.
+
+
+## Connection: Flipper Zero GPIO - Game Boy
+
+The original pinout is as follows:
+
+| Cable Game Link (Socket) | Flipper Zero GPIO |
+| ------------------------ | ----------------- |
+| 6 (GND)                  | 8 (GND)           |
+| 5 (CLK)                  | 6 (B2)            |
+| 3 (SI)                   | 7 (C3)            |
+| 2 (SO)                   | 5 (B3)            |
+
+Using the "Select Pinout" option, the Original, MALVEKE, or any custom pin configuration can be selected.
+
+
+## How does it work?
+
+The method used to communicate 2 Game Boys is based on the SPI protocol, which is a very simple serial communication protocol in which a master device communicates with one or more slave devices. The protocol is bidirectional and synchronous, and uses three basic signals:
+
+- A clock signal (CLK).
+- An output signal (Serial Out or SO).
+- An input signal (Serial In or SI).
+
+In the Game Boy, games store data in an internal shift register that is used to send and receive information. The SPI protocol used by the Game Boy uses the clock signal to indicate when data is being transferred.
+
+The Game Boy link protocol is synchronous and requires the slave device to respond at the same rate as the master device. The master device supplies an 8KHz clock (data transfer rate of 1KB/s). The time window for responding is only **~120μs**. However, the slave device has no restrictions and can respond when it receives data. The clock can vary and there is no lower limit.
+
+
+##  Tested In
+- Game Boy Color (GBC)
+- Game Boy Advance (GBA)

+ 328 - 0
README_es.md

@@ -0,0 +1,328 @@
+# 🐬 Flipper Zero - Pokemon Trading in Game Boy
+
+<p align="center">
+<a target="_blank" href="https://www.reddit.com/r/flipperzero/comments/121ncot/flipper_zero_game_boy_pokemon_trading/">
+  <img align="center" alt="Flipper Zero - Pokemon Trading Game Boy" src="./docs/images/youtube.png" />
+  <br />
+</p>
+<div align="center">
+
+**FW Official** | **FW Unleashed** | **FW RogueMaster**
+:- | :- | :- 
+[![FlipC.org](https://flipc.org/EstebanFuentealba/Flipper-Zero-Game-Boy-Pokemon-Trading/badge?branch=main)](https://flipc.org/EstebanFuentealba/Flipper-Zero-Game-Boy-Pokemon-Trading?branch=main)|[![FlipC.org](https://flipc.org/EstebanFuentealba/Flipper-Zero-Game-Boy-Pokemon-Trading/badge?branch=main&firmware=unleashed)](https://flipc.org/EstebanFuentealba/Flipper-Zero-Game-Boy-Pokemon-Trading?branch=main&firmware=unleashed)|[![FlipC.org](https://flipc.org/EstebanFuentealba/Flipper-Zero-Game-Boy-Pokemon-Trading/badge?branch=main&firmware=roguemaster)](https://flipc.org/EstebanFuentealba/Flipper-Zero-Game-Boy-Pokemon-Trading?branch=main&firmware=roguemaster)
+</div>
+
+## Introducción
+
+Esta es una aplicación de intercambio de Pokemon's desde de Flipper Zero a Game Boy [(Generación I)](https://bulbapedia.bulbagarden.net/wiki/Generation_I). Flipper Zero emula un Game Boy "Esclavo" conectado a **Cable Game Link** para poder intercambiar cualquier Pokemon de la Primera Generación (Red, Blue, Yellow) a un Game Boy Real.
+
+Es una Prueba de concepto (POC) para utilizar vistas, GPIO y FURI (Flipper Universal Registry Implementation).
+
+## Instrucciones de instalación
+
+Este proyecto está destinado a ser superpuesto encima de un repositorio de firmware existente, en mi caso la versión **Release 0.79.1**.
+
+- Clona el [Repositorio del firmware de Flipper Zero](https://github.com/flipperdevices/flipperzero-firmware). Consulta este [tutorial](https://github.com/jamisonderek/flipper-zero-tutorials/tree/main/firmware/updating/README.md) para actualizar el firmware.
+- Copia la [carpeta "pokemon"](..) en la carpeta `/applications_user/pokemon` del firmware que clonaste.
+- Corre el comando `fbt launch` para correr en tu Flipper Zero.
+
+```bash
+./fbt launch APPSRC=pokemon
+```
+
+- NOTA: Si sólo quieres generar el archivo `fap` debes correr el siguiente comando.
+
+```bash
+./fbt fap_pokemon
+```
+
+y usa [**qFlipper**](https://flipperzero.one/update) para copiar el archivo **pokemon.fap** generado a la carpeta `SD Card/apps/Game Boy`.
+
+<p align='center'>
+<img src="./docs/images/qFlipper.png" width="400" /><br />
+</p>
+
+## Instrucciones de Uso
+
+Estas instrucciones asumen que estás comenzando desde el escritorio de Flipper Zero. De lo contrario, presiona el botón Atrás hasta que estés en el escritorio.
+
+- Presiona el botón `OK` en Flipper para abrir el menú principal.
+- Elige `Applications` en el menú.
+- Selecciona `GPIO` en el submenú.
+- Selecciona `Pokemon Trading`.
+- Flipper Zero mostrará el menú principal de la aplicación. La primera opción es seleccionar el Pokémon a intercambiar.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/flipper-zero-flat-1.png" width="400" />
+      <br />
+  </p>
+- Presiona los botones `IZQUIERDA`/`DERECHA` para paginar la selección de Pokémon de a 1.
+- Presiona los botones `ARRIBA`/`ABAJO` para paginar la selección de Pokémon de a 10.
+- Presiona el botón `OK` para seleccionar el Pokémon a intercambiar y volver al menú principal.
+
+  <p align='center'>
+       <br />
+      <img src="./docs/images/flipper-zero-flat-2.png" width="400" /><br />
+  </p>
+- El nombre del Pokémon intercambiado se puede establecer. Cuando se selecciona un Pokémon, el nombre por defecto es el nombre de la especie en mayúsculas. Esto imita a un Pokémon sin un nombre personalizado. Para restablecer este nombre a su valor predeterminado, borra el campo de entrada de texto, presiona `OK` en el botón `Save`. Esto llenará el cuadro de texto con el nombre predeterminado. Presiona `Save` nuevamente para establecer este nombre.
+
+    - **Nota**: Los nombres de Nidoran♀ y Nidoran♂ no se renderizan correctamente. Esto se debe a que Flipper actualmente no puede imprimir caracteres Unicode en la pantalla. Siguiendo las instrucciones anteriores, se llenará el campo de entrada de texto con `NIDORAN ` con un espacio después. Este espacio es el símbolo no renderizable ♀/♂. Una vez intercambiado, se mostrará correctamente.
+
+    - **Nota**: Solo se admiten caracteres alfanuméricos en el nombre del Pokémon en este momento.
+
+    <p align='center'>
+        <br />
+        <img src="./docs/images/flipper-zero-flat-1-1.png" width="400" />
+        <br />
+    </p>
+- También se puede ajustar el nivel del Pokémon presionando `OK` en la opción de nivel. El nivel mínimo es `2` y el máximo es `100`. El nivel se ingresa a través de un cuadro de texto. (Los niveles por debajo de 2 causan una falla de desbordamiento en los juegos de Gen I que haría que el nivel saltara a 100, así que si deseas esto, simplemente establece el nivel del Pokémon en 100).
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/flipper-zero-flat-3.png" width="400" />
+      <br />
+  </p>
+- El menú `Select Moves` se utiliza para elegir los movimientos del Pokémon intercambiado. Están predefinidos con el conjunto de movimientos que el Pokémon conocería al nivel 1. Seleccionar una ranura de movimiento mostrará un índice alfabético de movimientos. Además, se pueden seleccionar rápidamente `No Move` y `Default`. Ten en cuenta que cualquier movimiento después del primer `No Move` se ignora.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/flipper-zero-flat-7.png" width="400" />
+      <br />
+  </p>
+  <p align='center'>
+      <br />
+      <img src="./docs/images/flipper-zero-flat-8.png" width="400" />
+      <br />
+  </p>
+- El menú `Select Types` puede cambiar los tipos del Pokémon intercambiado. Los tipos están predefinidos según lo que normalmente es el Pokémon seleccionado.
+
+  - Los Pokémon con un solo tipo tendrán el mismo tipo establecido para ambos tipos.
+
+  - **Nota**: A diferencia de otros menús, cambiar cualquiera de los tipos lo guarda inmediatamente. Presionar `Back` mantendrá los cambios. Esto se abordará en una versión posterior. Si necesitas volver a los tipos predeterminados, puedes seleccionar un Pokémon diferente y luego volver a seleccionar el Pokémon deseado.
+
+  - **Nota**: Al cambiar los tipos, las estadísticas del Pokémon en el juego NO reflejarán los tipos elegidos. Además, estos pueden sobrescribirse de nuevo en el juego si el Pokémon usa un movimiento que afecta a los tipos (por ejemplo, `Transform`) o el Pokémon evoluciona.
+
+    <p align='center'>
+        <br />
+        <img src="./docs/images/flipper-zero-flat-8-1.png" width="400" />
+        <br />
+    </p>
+- Las estadísticas del Pokémon también se pueden influir. Las configuraciones actuales son:
+
+  - `Random IV, Zero EV`  Imita las estadísticas de un Pokémon salvaje atrapado.
+  - `Random IV, Max EV / Level` IV es aleatorio, pero EV se establece en el máximo que un Pokémon entrenado podría tener para su nivel actual.
+  - `Randon IV, Max EV` IV es aleatorio, EV se establece en el máximo absoluto para un Pokémon perfectamente entrenado.
+  - `Max IV, Zero EV` Imita las estadísticas de un Pokémon salvaje atrapado, pero con el IV máximo posible.
+  - `Max IV, Max EV / Level` IV es máximo, EV se establece en el máximo que un Pokémon entrenado podría tener para su nivel actual.
+  - `Max IV, Max EV` Máximo Pokémon absolutamente perfectos y poderosos.
+
+    <p align='center'>
+        <br />
+        <img src="./docs/images/flipper-zero-flat-5.png" width="400" />
+        <br />
+    </p>
+
+- También se puede configurar el `OT ID#` y el `OT Name` del Pokémon. El `OT ID#` debe estar entre `0` y `65535`. Establecer el `OT ID#` y el `OT Name` igual que tu entrenador actual hace que el juego crea que fue un Pokémon capturado en estado salvaje y no uno que se intercambió. Esto significa que los Pokémon de alto nivel seguirán obedeciéndote sin medallas, pero no obtendrán el aumento de experiencia de un Pokémon intercambiado.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/flipper-zero-flat-6.png" width="400" /><br />
+  </p>
+  <p align='center'>
+      <br />
+      <img src="./docs/images/flipper-zero-flat-6-1.png" width="400" /><br />
+  </p>
+- Finalmente, selecciona Intercambiar PKMN para iniciar el proceso de intercambio.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/flipper-zero-flat-6-2.png" width="400" /><br />
+  </p>
+  <p align='center'>
+      <br />
+      <img src="./docs/images/flipper-zero-flat-9.png" width="400" /><br />
+  </p>
+- En tu Game Boy, debes conectar el **Game Link Cable** a la Game Boy y, en el juego, ve al **Pokemon Center** más cercano.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/game_boy_pokemon_center.png" width="400" /><br />
+  </p>
+- Habla con la chica en el mostrador de la derecha. La chica nos dirá que tenemos que guardar el juego antes de jugar, responderemos **YES** presionando el botón **A**.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/game_boy_save.png" width="400" /><br />
+  </p>
+- Flipper Zero mostrará que estamos conectados.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/flipper-zero-flat-10.png" width="400" /><br />
+  </p>
+- En la Game Boy, se nos preguntará qué opción queremos, y seleccionamos **TRADE CENTER**.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/game_boy_save_trade.png" width="400" /><br />
+  </p>
+- Ingresarás al Centro de Intercambio donde debes presionar el botón A en la Game Boy en tu lado de la mesa.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/game_boy_trade_room_2.png" width="400" /><br />
+  </p>
+- Flipper Zero permanecerá en una pantalla de espera con el Pokémon que seleccionaste.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/flipper-zero-flat-10.png" width="400" /><br />
+  </p>
+- Verás tu Pokémon y el Pokémon que seleccionaste en Flipper Zero, en este caso, `Mew`. Debes seleccionar el Pokémon que deseas intercambiar y presionar **TRADE**.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/game_boy_trade_list_select_trade.png" width="400" /><br />
+  </p>
+- Debes confirmar el intercambio seleccionando **TRADE**.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/game_boy_trade_list_select_trade_confirm.png" width="400" /><br />
+  </p>
+- Flipper Zero permanecerá en una pantalla de espera con el Pokémon que seleccionaste.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/flipper-zero-flat-11.png" width="400" /><br />
+  </p>
+- Finalmente, el intercambio de Pokémon comenzará desde **Flipper Zero** hacia la **Game Boy**.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/flipper-zero-flat-12.png" width="400" /><br />
+  </p>
+- Si Flipper Zero se queda atascado al final del intercambio, debes reiniciarlo presionando la combinación de teclas <img src="./docs/images/left.png" /> `IZQUIERDA` + <img src="./docs/images/back.png" /> `ATRÁS`.
+
+  <p align='center'>
+      <br />
+      <img src="./docs/images/reboot.png" width="400" /><br />
+  </p>
+
+## ¿Cómo trabaja?
+
+El método utilizado para comunicar 2 Game Boy se basa en el protocolo SPI, que es un protocolo de comunicación serial muy simple en el que un dispositivo maestro se comunica con uno o más dispositivos esclavos. El protocolo es bidireccional y sincrónico, y utiliza tres señales básicas:
+
+- Una señal de reloj (CLK).
+- Una señal de salida (Serial Out o SO).
+- Una señal de entrada (Serial In o SI).
+
+En el Game Boy, los juegos almacenan los datos en un registro de cambio interno que se utiliza para enviar y recibir información. El protocolo SPI utilizado por el Game Boy utiliza la señal de reloj para indicar cuándo se transfieren los datos.
+
+El protocolo de enlace de Game Boy es síncrono y requiere que el dispositivo esclavo responda al mismo ritmo que el dispositivo maestro. El dispositivo maestro suministra un reloj de 8KHz (velocidad de transferencia de datos de 1KB/s). La ventana de tiempo para responder es de solo **~120μs**. Sin embargo, el dispositivo esclavo no tiene restricciones y puede responder cuando recibe los datos. El reloj puede variar y no hay un límite inferior.
+
+<p align='center'>
+<br />
+<img src="./docs/images/gb_spi.png" width="400" /><br />
+</p>
+
+_Una transferencia de ejemplo de GB SPI. Aquí, el maestro envía 0xD9 (217) y el esclavo envía 0x45 (69)._
+
+<br />
+
+Se puede conocer mas al respecto en el siguiente Video [**Analyzing the Different Versions of the Link Cable**](https://youtu.be/h1KKkCfzOws?t=151).
+
+## Placa para Flipper Zero con Socket PortData EXT Link
+
+Para la placa del Fipper Zero se utilizó un [PortData EXT Link](https://es.aliexpress.com/item/1005004116983895.html) y una [place de prototipo](https://es.aliexpress.com/item/32478242317.html) de 2x8.
+
+<p align='center'>
+<br />
+<img src="./docs/images/EXT-Link.png" width="400" /><br />
+</p>
+
+_PortData EXT Link para Game Boy Color, Game Boy Pocket, GBC, GBP, GBL._
+
+<p align='center'>
+<br />
+<img src="./docs/images/pcb.png" width="400" /><br />
+</p>
+<p align='center'>
+<br />
+<img src="./docs/images/flipper-zero-pcb.png" width="400" /><br />
+</p>
+Usé una resistencia de 33kΩ en CLK, pero es opcional, se puede conectar directamente.
+
+## Conexión: Flipper Zero GPIO - Game Boy
+
+Se deben conectar los Pines de la siguiente manera
+
+<p align='center'>
+<br />
+<img src="./docs/images/wgbl-0.png" width="400" /><br />
+</p>
+
+<picture>
+    <source media="(prefers-color-scheme: dark)" srcset="./docs/images/GPIO-GBPIN_light-v2.png">
+    <source media="(prefers-color-scheme: light)" srcset="./docs/images/GPIO-GBPIN-v2.png">
+    <img
+        alt="Connect Flipper Zero GPIO to Game Boy Pins"
+        src="./docs/images/GPIO-GBPIN-v2.png">
+</picture>
+
+| Cable Game Link (Socket) | Flipper Zero GPIO |
+| ------------------------ | ----------------- |
+| 6 (GND)                  | 8 (GND)           |
+| 5 (CLK)                  | 6 (B2)            |
+| 3 (SI)                   | 7 (C3)            |
+| 2 (SO)                   | 5 (B3)            |
+
+
+## Conectar a Flipper Zero sin Socket PortData EXT Link
+
+Pudes cortar un cable directamente sin usar el socket pero debes tener en cuenta que el es un cable cruzado SI-SO.
+
+<p align='center'>
+<br />
+<img src="./docs/images/cut-cable-v3.png" width="400" /><br />
+</p>
+
+*"Cable Game Link" cortado y conectado directamente a los pines de Flipper Zero.*
+
+
+**NOTA**: No guiarse por los colores porque dependiendo del fabricante estos pueden cambiar, con un multímetro medir continuidad e identificar que cable es de que pin
+
+
+## GUI
+
+Para generar la Interfaz gráfica se utilizó la herramienta [**FUI-Editor**](https://ilin.pt/stuff/fui-editor/).
+Además se utilizaron los sprites originales del juego _Pokemon Yellow_ que se encuentran en el repositorio [**Disassembly of Pokemon Yellow**](https://github.com/pret/pokeyellow/tree/master/gfx/pokemon/front).
+
+De cada imagen se transformó el color `#aaa` a `#fff` para que Flipper Zero la renderizara bien. Para eso se utilizó un **Batch** para [Photopea](https://www.photopea.com/), el editor de imagenes online.
+
+##  Implementado en
+- Game Boy Color (GBC)
+- Game Boy Advance (GBA)
+
+## Contribuidores
+<a href="https://github.com/EstebanFuentealba/Flipper-Zero-Game-Boy-Pokemon-Trading/">EstebanFuentealba</a><br />
+<a href="https://github.com/R4g3D/Flipper-Zero-Game-Boy-Pokemon-Trading/">R4g3D</a><br />
+<a href="https://github.com/kbembedded/Flipper-Zero-Game-Boy-Pokemon-Trading/">kbembedded</a>
+
+## Links
+
+- [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
+- Derek Jamison - [Youtube Channel](https://www.youtube.com/@MrDerekJamison)
+- 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)>)
+- [Disassembly of Pokemon Yellow](https://github.com/pret/pokeyellow)
+- [Arduino-Spoofing-Gameboy-Pokemon-Trades](https://github.com/EstebanFuentealba/Arduino-Spoofing-Gameboy-Pokemon-Trades)
+- [🎮 Gameboy link cable breakout PCB](https://github.com/Palmr/gb-link-cable)
+
+<p align='center'>
+<br />
+<br />
+Desde Talcahuano 🇨🇱 con ❤ 
+</p>

+ 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.  

+ 10 - 3
application.fam

@@ -5,10 +5,17 @@ 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,5],
     fap_category="GPIO",
     fap_category="GPIO",
     fap_icon="pokemon_10px.png",
     fap_icon="pokemon_10px.png",
     fap_icon_assets="assets",
     fap_icon_assets="assets",
-    fap_author="Esteban Fuentealba",
-    fap_weburl="https://github.com/EstebanFuentealba"
+    fap_author="Esteban Fuentealba, Kris Bahnsen, Darryn Cull",
+    fap_weburl="https://github.com/EstebanFuentealba",
+    fap_description="Pokemon exchange from Flipper Zero to Game Boy for Generation I (Pokemon Red, Blue, Yellow)",
+    fap_private_libs=[
+        Lib(
+            name="flipper-gblink",
+            sources=["gblink.c"],
+        ),
+    ],
 )
 )

+ 15 - 6
changelog.md

@@ -1,16 +1,25 @@
 # Changelog - Patch Notes
 # Changelog - Patch Notes
 
 
-## Version 1.2.3
+## Version 1.5
+- **Add Features:** Incorporate flipper-gblink library; Add support for MALVEKE board as well as custom pin selection; If MALVEKE board is detected, default to that pinout, otherwise use the original documented pinout.
+- **BUG:** The current MALVEKE pinout and interrupt use breaks the OK button after entering the trade screen.
+
+## 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


+ 76 - 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;
@@ -2101,6 +2136,20 @@ static void trade_block_free(TradeBlock* trade) {
     free(trade);
     free(trade);
 }
 }
 
 
+/* The MALVEKE board has an esp32 which is set to TX on the flipper's default
+ * UART pins. If this pin shows signs of something connected, assume a MALVEKE
+ * board is being used.
+ */
+static bool detect_malveke(void) {
+    bool rc;
+
+    furi_hal_gpio_init(&gpio_usart_rx, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
+    rc = furi_hal_gpio_read(&gpio_usart_rx);
+    furi_hal_gpio_init_simple(&gpio_usart_rx, GpioModeAnalog);
+
+    return rc;
+}
+
 PokemonFap* pokemon_alloc() {
 PokemonFap* pokemon_alloc() {
     PokemonFap* pokemon_fap = (PokemonFap*)malloc(sizeof(PokemonFap));
     PokemonFap* pokemon_fap = (PokemonFap*)malloc(sizeof(PokemonFap));
 
 
@@ -2122,6 +2171,11 @@ PokemonFap* pokemon_alloc() {
     // Set up defaults
     // Set up defaults
     pokemon_fap->curr_pokemon = 0;
     pokemon_fap->curr_pokemon = 0;
     pokemon_fap->curr_stats = 0;
     pokemon_fap->curr_stats = 0;
+    pokemon_fap->malveke_detected = detect_malveke();
+    memcpy(
+        &pokemon_fap->pins,
+        &common_pinouts[pokemon_fap->malveke_detected],
+        sizeof(struct gblink_pins));
 
 
     // Set up trade party struct
     // Set up trade party struct
     pokemon_fap->trade_block = trade_block_alloc();
     pokemon_fap->trade_block = trade_block_alloc();
@@ -2150,8 +2204,13 @@ 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->pins,
+        pokemon_fap->view_dispatcher,
+        AppViewTrade);
 
 
     return pokemon_fap;
     return pokemon_fap;
 }
 }
@@ -2163,8 +2222,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);
 
 

+ 8 - 11
pokemon_app.h

@@ -10,6 +10,7 @@
 #include <gui/modules/submenu.h>
 #include <gui/modules/submenu.h>
 #include <gui/modules/text_input.h>
 #include <gui/modules/text_input.h>
 #include <gui/modules/variable_item_list.h>
 #include <gui/modules/variable_item_list.h>
+#include <gblink.h>
 
 
 #include "pokemon_data.h"
 #include "pokemon_data.h"
 
 
@@ -44,22 +45,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;
@@ -85,6 +76,10 @@ struct pokemon_fap {
      */
      */
     TradeBlock* trade_block;
     TradeBlock* trade_block;
 
 
+    /* Pin definition to actual Game Link Cable interface */
+    struct gblink_pins pins;
+    int malveke_detected;
+
     /* The currently selected pokemon */
     /* The currently selected pokemon */
     int curr_pokemon;
     int curr_pokemon;
 
 
@@ -105,6 +100,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);
 }
 }

+ 18 - 0
scenes/pokemon_menu.c

@@ -11,6 +11,7 @@
 #include "pokemon_ot_id.h"
 #include "pokemon_ot_id.h"
 #include "pokemon_ot_name.h"
 #include "pokemon_ot_name.h"
 #include "pokemon_trade.h"
 #include "pokemon_trade.h"
+#include "pokemon_pins.h"
 
 
 static void scene_change_from_main_cb(void* context, uint32_t index) {
 static void scene_change_from_main_cb(void* context, uint32_t index) {
     PokemonFap* pokemon_fap = (PokemonFap*)context;
     PokemonFap* pokemon_fap = (PokemonFap*)context;
@@ -38,6 +39,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(
@@ -87,6 +96,12 @@ void main_menu_scene_on_enter(void* context) {
         pokemon_fap->submenu, buf, SelectOTNameScene, scene_change_from_main_cb, pokemon_fap);
         pokemon_fap->submenu, buf, SelectOTNameScene, scene_change_from_main_cb, pokemon_fap);
     submenu_add_item(
     submenu_add_item(
         pokemon_fap->submenu, "Trade PKMN", TradeScene, scene_change_from_main_cb, pokemon_fap);
         pokemon_fap->submenu, "Trade PKMN", TradeScene, scene_change_from_main_cb, pokemon_fap);
+    submenu_add_item(
+        pokemon_fap->submenu,
+        "Select Pinout",
+        SelectPinsScene,
+        scene_change_from_main_cb,
+        pokemon_fap);
 
 
     submenu_set_selected_item(
     submenu_set_selected_item(
         pokemon_fap->submenu,
         pokemon_fap->submenu,
@@ -120,6 +135,7 @@ void (*const pokemon_scene_on_enter_handlers[])(void*) = {
     select_ot_id_scene_on_enter,
     select_ot_id_scene_on_enter,
     select_ot_name_scene_on_enter,
     select_ot_name_scene_on_enter,
     trade_scene_on_enter,
     trade_scene_on_enter,
+    select_pins_scene_on_enter,
 };
 };
 
 
 void (*const pokemon_scene_on_exit_handlers[])(void*) = {
 void (*const pokemon_scene_on_exit_handlers[])(void*) = {
@@ -135,6 +151,7 @@ void (*const pokemon_scene_on_exit_handlers[])(void*) = {
     select_ot_id_scene_on_exit,
     select_ot_id_scene_on_exit,
     select_ot_name_scene_on_exit,
     select_ot_name_scene_on_exit,
     null_scene_on_exit,
     null_scene_on_exit,
+    select_pins_scene_on_exit,
 };
 };
 
 
 bool (*const pokemon_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
 bool (*const pokemon_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
@@ -150,6 +167,7 @@ bool (*const pokemon_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
     null_scene_on_event,
     null_scene_on_event,
     null_scene_on_event,
     null_scene_on_event,
     null_scene_on_event,
     null_scene_on_event,
+    null_scene_on_event,
 };
 };
 
 
 const SceneManagerHandlers pokemon_scene_manager_handlers = {
 const SceneManagerHandlers pokemon_scene_manager_handlers = {

+ 1 - 0
scenes/pokemon_menu.h

@@ -18,6 +18,7 @@ typedef enum {
     SelectOTIDScene,
     SelectOTIDScene,
     SelectOTNameScene,
     SelectOTNameScene,
     TradeScene,
     TradeScene,
+    SelectPinsScene,
     SceneCount,
     SceneCount,
 } AppScene;
 } AppScene;
 
 

+ 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;

+ 169 - 0
scenes/pokemon_pins.c

@@ -0,0 +1,169 @@
+#include <gui/modules/variable_item_list.h>
+#include <furi.h>
+
+#include "../pokemon_app.h"
+#include "pokemon_menu.h"
+
+struct named_pins {
+    const char* text;
+    const GpioPin* pin;
+};
+
+static const struct named_pins named_pins[] = {
+    {"PA7", &gpio_ext_pa7},
+    {"PA6", &gpio_ext_pa6},
+    {"PA4", &gpio_ext_pa4},
+    {"PB3", &gpio_ext_pb3},
+    {"PB2", &gpio_ext_pb2},
+    {"PC3", &gpio_ext_pc3},
+    {"PC1", &gpio_ext_pc1},
+    {"PC0", &gpio_ext_pc0},
+    {},
+};
+
+#define NUM_PINS 8
+
+/* This must match gblink's enum order */
+static const char* named_groups[] = {
+    "Original",
+    "Malveke",
+    "Custom",
+    "",
+};
+
+struct itemlist_builder {
+    VariableItem* named;
+    VariableItem* serin;
+    VariableItem* serout;
+    VariableItem* clk;
+    uint8_t named_index;
+    uint8_t serin_index;
+    uint8_t serout_index;
+    uint8_t clk_index;
+};
+
+/* Just make it a global, whatever */
+static struct itemlist_builder builder = {0};
+static void select_pins_rebuild_list(PokemonFap* pokemon_fap);
+
+static void select_pins_set(PokemonFap* pokemon_fap) {
+    pokemon_fap->pins.serin = named_pins[builder.serin_index].pin;
+    pokemon_fap->pins.serout = named_pins[builder.serout_index].pin;
+    pokemon_fap->pins.clk = named_pins[builder.clk_index].pin;
+}
+
+static void select_named_group_callback(VariableItem* item) {
+    uint8_t index = variable_item_get_current_value_index(item);
+    PokemonFap* pokemon_fap = variable_item_get_context(item);
+
+    variable_item_set_current_value_text(item, named_groups[index]);
+    builder.named_index = index;
+    select_pins_rebuild_list(pokemon_fap);
+    variable_item_list_set_selected_item(pokemon_fap->variable_item_list, 0);
+}
+
+static void select_pins_serin_callback(VariableItem* item) {
+    uint8_t index = variable_item_get_current_value_index(item);
+    PokemonFap* pokemon_fap = variable_item_get_context(item);
+
+    variable_item_set_current_value_text(item, named_pins[index].text);
+    builder.serin_index = index;
+    select_pins_rebuild_list(pokemon_fap);
+    variable_item_list_set_selected_item(pokemon_fap->variable_item_list, 1);
+}
+
+static void select_pins_serout_callback(VariableItem* item) {
+    uint8_t index = variable_item_get_current_value_index(item);
+    PokemonFap* pokemon_fap = variable_item_get_context(item);
+
+    variable_item_set_current_value_text(item, named_pins[index].text);
+    builder.serout_index = index;
+    select_pins_rebuild_list(pokemon_fap);
+    variable_item_list_set_selected_item(pokemon_fap->variable_item_list, 2);
+}
+
+static void select_pins_clk_callback(VariableItem* item) {
+    uint8_t index = variable_item_get_current_value_index(item);
+    PokemonFap* pokemon_fap = variable_item_get_context(item);
+
+    variable_item_set_current_value_text(item, named_pins[index].text);
+    builder.clk_index = index;
+    select_pins_rebuild_list(pokemon_fap);
+    variable_item_list_set_selected_item(pokemon_fap->variable_item_list, 3);
+}
+
+static void select_pins_rebuild_list(PokemonFap* pokemon_fap) {
+    int num;
+
+    /* HACK: TODO: It would be better to do this programmatically, but, I'm kind
+     * of done working on this feature so its going to be hardcoded for now.
+     */
+    switch(builder.named_index) {
+    case 0: // Original
+        num = 1;
+        builder.serin_index = 5;
+        builder.serout_index = 3;
+        builder.clk_index = 4;
+        break;
+    case 1: // MALVEKE
+        num = 1;
+        builder.serin_index = 1;
+        builder.serout_index = 0;
+        builder.clk_index = 3;
+        break;
+    default:
+        num = NUM_PINS;
+        break;
+    }
+
+    /* HACK: */
+    pokemon_fap->malveke_detected = builder.named_index;
+
+    select_pins_set(pokemon_fap);
+
+    variable_item_list_reset(pokemon_fap->variable_item_list);
+
+    builder.named = variable_item_list_add(
+        pokemon_fap->variable_item_list, "Mode", 3, select_named_group_callback, pokemon_fap);
+    builder.serin = variable_item_list_add(
+        pokemon_fap->variable_item_list, "SI:", num, select_pins_serin_callback, pokemon_fap);
+    builder.serout = variable_item_list_add(
+        pokemon_fap->variable_item_list, "SO:", num, select_pins_serout_callback, pokemon_fap);
+    builder.clk = variable_item_list_add(
+        pokemon_fap->variable_item_list, "CLK:", num, select_pins_clk_callback, pokemon_fap);
+
+    variable_item_set_current_value_index(builder.named, builder.named_index);
+    variable_item_set_current_value_text(builder.named, named_groups[builder.named_index]);
+
+    variable_item_set_current_value_index(builder.serin, (num == 1 ? 0 : builder.serin_index));
+    variable_item_set_current_value_text(builder.serin, named_pins[builder.serin_index].text);
+
+    variable_item_set_current_value_index(builder.serout, (num == 1 ? 0 : builder.serout_index));
+    variable_item_set_current_value_text(builder.serout, named_pins[builder.serout_index].text);
+
+    variable_item_set_current_value_index(builder.clk, (num == 1 ? 0 : builder.clk_index));
+    variable_item_set_current_value_text(builder.clk, named_pins[builder.clk_index].text);
+}
+
+void select_pins_scene_on_exit(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewMainMenu);
+    view_dispatcher_remove_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}
+
+void select_pins_scene_on_enter(void* context) {
+    PokemonFap* pokemon_fap = (PokemonFap*)context;
+
+    /* TODO: Figure out what defaults we should use for pins based on attached board! */
+    /* HACK: */
+    if(builder.named_index < 2) builder.named_index = pokemon_fap->malveke_detected;
+
+    select_pins_rebuild_list(pokemon_fap);
+
+    view_dispatcher_add_view(
+        pokemon_fap->view_dispatcher,
+        AppViewOpts,
+        variable_item_list_get_view(pokemon_fap->variable_item_list));
+    view_dispatcher_switch_to_view(pokemon_fap->view_dispatcher, AppViewOpts);
+}

+ 9 - 0
scenes/pokemon_pins.h

@@ -0,0 +1,9 @@
+#ifndef POKEMON_PINS_H
+#define POKEMON_PINS_H
+
+#pragma once
+
+void select_pins_scene_on_enter(void* context);
+void select_pins_scene_on_exit(void* context);
+
+#endif // POKEMON_PINS_H

+ 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;

+ 593 - 340
views/trade.c

@@ -1,14 +1,92 @@
-#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 <gblink.h>
 
 
 #include "../pokemon_app.h"
 #include "../pokemon_app.h"
-
-#define GAME_BOY_CLK gpio_ext_pb2
-#define GAME_BOY_SI gpio_ext_pc3
-#define GAME_BOY_SO gpio_ext_pb3
+#include "trade_patch_list.h"
 
 
 #define DELAY_MICROSECONDS 15
 #define DELAY_MICROSECONDS 15
 #define PKMN_BLANK 0x00
 #define PKMN_BLANK 0x00
@@ -20,10 +98,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 +122,77 @@
 #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;
+    void* gblink_handle;
+    struct gblink_pins* gblink_pins;
+};
 
 
+/* 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 +200,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 +213,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 +323,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 +335,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 +623,147 @@ 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) {
-    PokemonFap* pokemon_fap = (PokemonFap*)context;
+static void transferBit(void* context, uint8_t in_byte) {
     furi_assert(context);
     furi_assert(context);
-    bool connected;
-    bool trading;
 
 
-    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);
-            break;
-        case CONNECTED:
-            connected = true;
-            out_data = getMenuResponse(in_data);
-            break;
-        case TRADE_CENTRE:
-            out_data = getTradeCentreResponse(in_data, pokemon_fap);
-            break;
-        default:
-            out_data = in_data;
-            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;
+    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,
-        {
-            model->connected = connected;
-            model->trading = trading;
-        },
-        false);
-}
+        trade->view, struct trade_model * model, { status = model->gameboy_status; }, false);
 
 
-void input_clk_gameboy(void* context) {
-    furi_assert(context);
+    trade->in_data = in_byte;
 
 
-    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;
-        }
+    /* Once a byte of data has been shifted in, process it */
+    switch(status) {
+    case GAMEBOY_CONN_FALSE:
+        gblink_transfer(trade->gblink_handle, getConnectResponse(trade));
+        break;
+    case GAMEBOY_CONN_TRUE:
+        gblink_transfer(trade->gblink_handle, getMenuResponse(trade));
+        break;
+    case GAMEBOY_COLOSSEUM:
+        gblink_transfer(trade->gblink_handle, in_byte);
+        break;
+    /* Every other state is trade related */
+    default:
+        gblink_transfer(trade->gblink_handle, getTradeCentreResponse(trade));
+        break;
     }
     }
-
-    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;
+    struct gblink_def gblink_def = {0};
 
 
-    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);
-
-    // 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);
-    // // C3 (Pin7) / CLK (5)
-    furi_hal_gpio_init(&GAME_BOY_CLK, GpioModeInterruptRise, GpioPullNo, GpioSpeedVeryHigh);
-    furi_hal_gpio_remove_int_callback(&GAME_BOY_CLK);
-    furi_hal_gpio_add_int_callback(&GAME_BOY_CLK, input_clk_gameboy, pokemon_fap);
+    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);
+
+    /* TODO: This should be moved further back to struct pokemon_fap for whole
+     * app flexibility since it would probably be written to by a different scene
+     */
+    gblink_def.pins = trade->gblink_pins;
+    gblink_def.callback = transferBit;
+    gblink_def.cb_context = trade;
+
+    trade->gblink_handle = gblink_alloc(&gblink_def);
+    gblink_nobyte_set(trade->gblink_handle, SERIAL_NO_DATA_BYTE);
+
+    /* 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));
+
+    /* 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 the pin settings */
+    gblink_free(trade->gblink_handle);
+
+    /* 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,
+    struct gblink_pins* gblink_pins,
+    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;
+    trade->gblink_pins = gblink_pins;
 
 
-    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);
+
+    struct trade_ctx* trade = (struct trade_ctx*)trade_ctx;
 
 
-    disconnect_pin(&GAME_BOY_CLK);
+    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);
 }
 }

+ 7 - 2
views/trade.h

@@ -6,8 +6,13 @@
 #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,
+    struct gblink_pins* gblink_pins,
+    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 */